1use anyhow::{anyhow, Result};
7
8use std::collections::{HashMap, HashSet};
9use std::path::PathBuf;
10use std::slice::from_ref;
11
12use rpfm_lib::files::{Container, ContainerPath, db::DB, FileType, loc::Loc, pack::Pack, RFile, RFileDecoded, table::DecodedData};
13use rpfm_lib::schema::*;
14
15use rpfm_ipc::messages::CeoEntryData;
16
17use rpfm_telemetry::*;
18
19pub fn build_ceo_entries(
24 pack: &mut Pack,
25 schema: &Schema,
26 entries: &[CeoEntryData],
27) -> Result<Vec<ContainerPath>> {
28 let mut added_paths: Vec<ContainerPath> = Vec::new();
29
30 fn crc32_id(data: &[u8]) -> i64 {
34 let mut crc: u32 = 0xFFFF_FFFF;
35 for &b in data {
36 crc ^= b as u32;
37 for _ in 0..8 {
38 if crc & 1 != 0 { crc = (crc >> 1) ^ 0xEDB8_8320; }
39 else { crc >>= 1; }
40 }
41 }
42 (1_500_000_000u32 + (!crc % 647_000_000u32)) as i64
43 }
44
45 fn auto_id(table: &str, row_key: &str) -> i64 {
46 crc32_id(format!("{}|{}", table, row_key).as_bytes())
47 }
48
49 fn get_or_create_db(
52 pack: &mut Pack,
53 schema: &Schema,
54 table_name: &str,
55 file_stem: &str,
56 ) -> Result<String> {
57 let path = format!("ceo_db/{}/{}", table_name, file_stem);
58 let container_path = ContainerPath::File(path.clone());
59
60 if pack.files_by_paths(from_ref(&container_path), false).is_empty() {
62 let _full_table_name = format!("{}_tables", table_name.trim_end_matches("_tables"));
63 let table_name_with_suffix = if table_name.ends_with("_tables") {
64 table_name.to_string()
65 } else {
66 format!("{}_tables", table_name)
67 };
68
69 let def = schema.definitions_by_table_name(&table_name_with_suffix)
70 .and_then(|defs| defs.first())
71 .ok_or_else(|| anyhow!("No schema definition for {}", table_name_with_suffix))?;
72
73 let patches = schema.patches_for_table(&table_name_with_suffix);
74 let db = DB::new(def, patches, &table_name_with_suffix);
75 let rfile = RFile::new_from_decoded(
76 &RFileDecoded::DB(db), 0, &path
77 );
78 pack.insert(rfile)?;
79 }
80 Ok(path)
81 }
82
83 fn insert_row(
86 pack: &mut Pack,
87 schema: &Schema,
88 table_name: &str,
89 file_stem: &str,
90 values: &std::collections::HashMap<&str, String>,
91 ) -> Result<ContainerPath> {
92 let path = get_or_create_db(pack, schema, table_name, file_stem)?;
93 let container_path = ContainerPath::File(path.clone());
94
95 let _table_name_with_suffix = if table_name.ends_with("_tables") {
96 table_name.to_string()
97 } else {
98 format!("{}_tables", table_name)
99 };
100
101 let mut files = pack.files_by_type_and_paths_mut(
102 &[FileType::DB],
103 from_ref(&container_path),
104 true,
105 );
106
107 if let Some(file) = files.first_mut() {
108 if let Ok(RFileDecoded::DB(db)) = file.decoded_mut() {
109 let fields = db.definition().fields_processed().to_vec();
110 let mut row: Vec<DecodedData> = Vec::new();
111
112 for field in &fields {
113 let fname = field.name();
114 let raw = values.get(fname).map(|s| s.as_str()).unwrap_or("");
115
116 let cell = match field.field_type() {
117 FieldType::Boolean =>
118 DecodedData::Boolean(raw == "true" || raw == "1"),
119 FieldType::I16 =>
120 DecodedData::I16(raw.parse().unwrap_or(0)),
121 FieldType::I32 =>
122 DecodedData::I32(raw.parse().unwrap_or(0)),
123 FieldType::I64 =>
124 DecodedData::I64(raw.parse().unwrap_or(0)),
125 FieldType::F32 =>
126 DecodedData::F32(raw.parse().unwrap_or(0.0)),
127 FieldType::F64 =>
128 DecodedData::F64(raw.parse().unwrap_or(0.0)),
129 FieldType::OptionalI16 =>
130 DecodedData::OptionalI16(raw.parse().unwrap_or(0)),
131 FieldType::OptionalI32 =>
132 DecodedData::OptionalI32(raw.parse().unwrap_or(0)),
133 FieldType::OptionalI64 =>
134 DecodedData::OptionalI64(raw.parse().unwrap_or(0)),
135 FieldType::OptionalStringU8 =>
136 DecodedData::OptionalStringU8(raw.to_string()),
137 FieldType::OptionalStringU16 =>
138 DecodedData::OptionalStringU16(raw.to_string()),
139 FieldType::StringU16 =>
140 DecodedData::StringU16(raw.to_string()),
141 _ =>
142 DecodedData::StringU8(raw.to_string()),
143 };
144 row.push(cell);
145 }
146 db.data_mut().push(row);
147 }
148 }
149 Ok(container_path)
150 }
151
152 fn insert_loc_entries(
154 pack: &mut Pack,
155 loc_path: &str,
156 entries: &[(&str, &str)], ) -> Result<ContainerPath> {
158 let container_path = ContainerPath::File(loc_path.to_string());
159
160 if pack.files_by_paths(from_ref(&container_path), false).is_empty() {
161 let loc = Loc::new();
162 let rfile = RFile::new_from_decoded(
163 &RFileDecoded::Loc(loc), 0, loc_path
164 );
165 pack.insert(rfile)?;
166 }
167
168 let mut files = pack.files_by_type_and_paths_mut(
169 &[FileType::Loc],
170 from_ref(&container_path),
171 true,
172 );
173
174 if let Some(file) = files.first_mut() {
175 if let Ok(RFileDecoded::Loc(loc)) = file.decoded_mut() {
176 for (key, text) in entries {
177 loc.data_mut().push(vec![
178 DecodedData::StringU16(key.to_string()),
179 DecodedData::StringU16(text.to_string()),
180 DecodedData::Boolean(true),
181 ]);
182 }
183 }
184 }
185
186 Ok(container_path)
187 }
188
189 macro_rules! row {
191 ($($k:expr => $v:expr),* $(,)?) => {{
192 let mut m = std::collections::HashMap::new();
193 $(m.insert($k, $v.to_string());)*
194 m
195 }};
196 }
197
198 let stem = format!("ceo_{}", entries.first().map(|e| e.name.as_str()).unwrap_or("builder"));
200 let stem = stem.as_str();
201 let loc_path = format!("text/ceo/general_items/{}.loc", stem);
202
203 let mut loc_entries: Vec<(String, String)> = Vec::new();
205
206 for entry in entries {
207 let n = &entry.name;
208 let element = &entry.element;
209 let gender = &entry.gender;
210 let is_unique = entry.option == "unique";
211
212 if is_unique {
213 let p = insert_row(pack, schema, "ceos_tables", stem, &row![
217 "key" => format!("3k_main_ancilliary_armour_{n}_armour_unique"),
218 "exists_in_location" => "character_ceo_manager",
219 "category" => "3k_main_ceo_category_ancillary_armour",
220 "equipped_in_location" => "character_equipment",
221 "priority" => "1",
222 "turns_to_expire" => "0",
223 "point_change_per_turn_if_inactive" => "0",
224 "point_change_per_turn_while_active" => "0",
225 "point_change_per_turn_while_equipped" => "0",
226 "inheritance_chance" => "0",
227 "can_be_looted_post_battle" => "false",
228 "can_be_traded_in_diplomacy" => "false",
229 "can_be_stolen" => "false",
230 "rarity" => "unique",
231 "can_be_unequipped" => "false",
232 "can_be_transferred_if_equipped" => "true",
233 "cannot_reequip_until_next_round_if_unequipped" => "true",
234 "provides_scripted_permissions_on_spawn" => "",
235 ])?;
236 if !added_paths.contains(&p) { added_paths.push(p); }
237
238 let p = insert_row(pack, schema, "ceos_tables", stem, &row![
240 "key" => format!("3k_main_ceo_career_historical_{n}"),
241 "exists_in_location" => "character_ceo_manager",
242 "category" => "3k_main_ceo_category_career",
243 "equipped_in_location" => "character_equipment",
244 "priority" => "1",
245 "turns_to_expire" => "0",
246 "point_change_per_turn_if_inactive" => "0",
247 "point_change_per_turn_while_active" => "0",
248 "point_change_per_turn_while_equipped" => "0",
249 "inheritance_chance" => "0",
250 "can_be_looted_post_battle" => "false",
251 "can_be_traded_in_diplomacy" => "false",
252 "can_be_stolen" => "false",
253 "rarity" => "common",
254 "can_be_unequipped" => "false",
255 "can_be_transferred_if_equipped" => "true",
256 "cannot_reequip_until_next_round_if_unequipped" => "true",
257 "provides_scripted_permissions_on_spawn" => "",
258 ])?;
259 if !added_paths.contains(&p) { added_paths.push(p); }
260
261 let p = insert_row(pack, schema, "ceo_groups_tables", stem, &row![
263 "key" => format!("3k_main_ceo_group_ancillary_armour_character_specific_{n}"),
264 ])?;
265 if !added_paths.contains(&p) { added_paths.push(p); }
266
267 let grp_key = format!("3k_main_ceo_group_ancillary_armour_character_specific_{n}");
269 let armor_key = format!("3k_main_ancilliary_armour_{n}_armour_unique");
270 let career_key_grp = format!("3k_main_ceo_career_historical_{n}");
271 let p = insert_row(pack, schema, "ceo_group_ceos_tables", stem, &row![
273 "ceo_group" => &grp_key,
274 "ceo" => &armor_key,
275 "trigger_weighting" => "0.1",
276 "auto_id" => auto_id("ceo_group_ceos", &format!("{grp_key}|{armor_key}")),
277 ])?;
278 if !added_paths.contains(&p) { added_paths.push(p); }
279 let p = insert_row(pack, schema, "ceo_group_ceos_tables", stem, &row![
281 "ceo_group" => "3k_main_ceo_group_ancillary_armour_type_character_specific",
282 "ceo" => &armor_key,
283 "trigger_weighting" => "0.1",
284 "auto_id" => auto_id("ceo_group_ceos", &format!("3k_main_ceo_group_ancillary_armour_type_character_specific|{armor_key}")),
285 ])?;
286 if !added_paths.contains(&p) { added_paths.push(p); }
287 let p = insert_row(pack, schema, "ceo_group_ceos_tables", stem, &row![
289 "ceo_group" => "3k_main_ceo_group_ancillary_armour_character_all",
290 "ceo" => &armor_key,
291 "trigger_weighting" => "0.1",
292 "auto_id" => auto_id("ceo_group_ceos", &format!("3k_main_ceo_group_ancillary_armour_character_all|{armor_key}")),
293 ])?;
294 if !added_paths.contains(&p) { added_paths.push(p); }
295 let p = insert_row(pack, schema, "ceo_group_ceos_tables", stem, &row![
297 "ceo_group" => "3k_main_ceo_group_career_all",
298 "ceo" => &career_key_grp,
299 "trigger_weighting" => "1",
300 "auto_id" => auto_id("ceo_group_ceos", &format!("3k_main_ceo_group_career_all|{career_key_grp}")),
301 ])?;
302 if !added_paths.contains(&p) { added_paths.push(p); }
303
304 let perm_key = format!("3k_main_ceo_permissions_ancillary_armour_character_specific_{n}");
306 let p = insert_row(pack, schema, "ceo_permissions_tables", stem, &row![
307 "key" => &perm_key,
308 ])?;
309 if !added_paths.contains(&p) { added_paths.push(p); }
310
311 let p = insert_row(pack, schema, "ceo_permissions_groups_tables", stem, &row![
313 "permissions" => &perm_key,
314 "group" => &grp_key,
315 "point_gain_enabled_override" => "true",
316 "point_gain_disabled_override" => "false",
317 "state_active_override" => "true",
318 "state_inactive_override" => "false",
319 "auto_id" => auto_id("ceo_permissions_groups", &format!("{perm_key}|{grp_key}")),
320 ])?;
321 if !added_paths.contains(&p) { added_paths.push(p); }
322
323 let scripted_perm_key = format!("3k_main_ceo_permissions_ancillary_armour_character_specific_{n}");
325 let p = insert_row(pack, schema, "ceo_scripted_permissions_tables", stem, &row![
326 "key" => &scripted_perm_key,
327 "exists_in_and_provides_permission_to_location" => "character_ceo_manager",
328 ])?;
329 if !added_paths.contains(&p) { added_paths.push(p); }
330
331 let p = insert_row(pack, schema, "ceo_scripted_permissions_to_permissions_tables", stem, &row![
333 "scripted_permissions" => &scripted_perm_key,
334 "permissions" => &perm_key,
335 "auto_id" => auto_id("ceo_scripted_permissions_to_permissions", &format!("{scripted_perm_key}|{perm_key}")),
336 ])?;
337 if !added_paths.contains(&p) { added_paths.push(p); }
338
339 let stage1_key = format!("3k_main_ceo_initial_data_stage_character_traits_historical_{n}");
341 let stage2_key = format!("3k_main_ceo_initial_data_character_historical_{n}_ancillaries");
342 for key in &[&stage1_key, &stage2_key] {
343 let p = insert_row(pack, schema, "ceo_initial_data_stages_tables", stem, &row![
344 "key" => *key,
345 ])?;
346 if !added_paths.contains(&p) { added_paths.push(p); }
347 }
348
349 let effect_list_armor = format!("3k_main_ancilliary_armour_{n}_armour_unique");
351 let effect_list_career = format!("3k_main_ceo_career_historical_{n}");
352 for key in &[&effect_list_armor, &effect_list_career] {
353 let p = insert_row(pack, schema, "ceo_effect_lists_tables", stem, &row![
354 "key" => *key,
355 ])?;
356 if !added_paths.contains(&p) { added_paths.push(p); }
357 }
358
359 let initial_data_key = format!("3k_main_ceo_initial_data_character_historical_{n}");
361 let p = insert_row(pack, schema, "ceo_initial_datas_tables", stem, &row![
362 "key" => &initial_data_key,
363 "template_manager" => "character_ceo_manager",
364 ])?;
365 if !added_paths.contains(&p) { added_paths.push(p); }
366
367 let armor_ceo_key = format!("3k_main_ancilliary_armour_{n}_armour_unique");
369 match element.as_str() {
370 "metal" => {
371 let armour_perm_key = format!("3k_main_ceo_permissions_ancillary_armour_character_specific_{n}");
372 for scripted in &[
373 "3k_main_ceo_permissions_ancillary_weapon_character_sword_dual_enable",
374 "3k_main_ceo_permissions_ancillary_weapon_character_axe_dual_enable",
375 "3k_main_ceo_permissions_ancillary_weapon_character_sword_one_handed_enable",
376 "3k_main_ceo_permissions_ancillary_weapon_character_axe_one_handed_enable",
377 "3k_ytr_ceo_permissions_ancillary_weapon_character_mace_dual_enable",
378 armour_perm_key.as_str(),
379 ] {
380 let p = insert_row(pack, schema, "ceo_initial_data_scripted_permissions_tables", stem, &row![
381 "initial_data_stage" => &stage2_key,
382 "scripted_permissions" => *scripted,
383 "auto_id" => auto_id("ceo_initial_data_scripted_permissions", &format!("{stage2_key}|{scripted}")),
384 ])?;
385 if !added_paths.contains(&p) { added_paths.push(p); }
386 }
387 for (category, equipped_ceo) in &[
388 ("3k_main_ceo_category_ancillary_weapon", "3k_main_ancillary_weapon_double_edged_sword_common"),
389 ("3k_main_ceo_category_ancillary_mount", "3k_main_ancillary_mount_grey_horse"),
390 ("3k_main_ceo_category_ancillary_armour", armor_ceo_key.as_str()),
391 ] {
392 let p = insert_row(pack, schema, "ceo_initial_data_equipments_tables", stem, &row![
393 "initial_data_stage" => &stage2_key,
394 "category" => *category,
395 "equipped_ceo" => *equipped_ceo,
396 "slot_index" => "0",
397 "target" => "character_equipment",
398 "auto_id" => auto_id("ceo_initial_data_equipments", &format!("{stage2_key}|{equipped_ceo}")),
399 ])?;
400 if !added_paths.contains(&p) { added_paths.push(p); }
401 }
402 for active_ceo in &[
403 "3k_main_ancillary_weapon_single_edged_sword_common",
404 "3k_ytr_ancillary_weapon_dual_maces_common",
405 "3k_main_ancillary_weapon_one_handed_axe_common",
406 "3k_main_ancillary_weapon_dual_swords_common",
407 "3k_main_ancillary_weapon_double_edged_sword_common",
408 "3k_main_ancillary_mount_grey_horse",
409 ] {
410 let p = insert_row(pack, schema, "ceo_initial_data_active_ceos_tables", stem, &row![
411 "initial_data_stage" => &stage2_key,
412 "active_ceo" => *active_ceo,
413 "starting_points_delta" => "0",
414 "auto_id" => auto_id("ceo_initial_data_active_ceos", &format!("{stage2_key}|{active_ceo}")),
415 ])?;
416 if !added_paths.contains(&p) { added_paths.push(p); }
417 }
418 let p = insert_row(pack, schema, "ceo_initial_data_active_ceos_tables", stem, &row![
420 "initial_data_stage" => &stage1_key,
421 "active_ceo" => "3k_main_ceo_class_metal",
422 "starting_points_delta" => "0",
423 "auto_id" => auto_id("ceo_initial_data_active_ceos", &format!("{stage1_key}|3k_main_ceo_class_metal")),
424 ])?;
425 if !added_paths.contains(&p) { added_paths.push(p); }
426 let p = insert_row(pack, schema, "ceo_initial_data_active_ceos_tables", stem, &row![
428 "initial_data_stage" => &stage2_key,
429 "active_ceo" => &armor_ceo_key,
430 "starting_points_delta" => "0",
431 "auto_id" => auto_id("ceo_initial_data_active_ceos", &format!("{stage2_key}|{armor_ceo_key}")),
432 ])?;
433 if !added_paths.contains(&p) { added_paths.push(p); }
434 if entry.expanded {
436 let p = insert_row(pack, schema, "ceo_initial_data_active_ceos_tables", stem, &row![
437 "initial_data_stage" => &stage2_key,
438 "active_ceo" => "3k_main_ancillary_weapon_sword_and_shield_common",
439 "starting_points_delta" => "0",
440 "auto_id" => auto_id("ceo_initial_data_active_ceos", &format!("{stage2_key}|3k_main_ancillary_weapon_sword_and_shield_common")),
441 ])?;
442 if !added_paths.contains(&p) { added_paths.push(p); }
443 }
444 let p = insert_row(pack, schema, "ceo_effect_list_to_effects_tables", stem, &row![
446 "effect_list" => &effect_list_armor,
447 "effect" => "3k_main_effect_character_attribute_expertise_mod",
448 "value" => "18",
449 "effect_scope" => "character_to_character_own",
450 "optional_only_in_game_mode" => "",
451 "auto_id" => auto_id("ceo_effect_list_to_effects", &format!("{effect_list_armor}|expertise")),
452 ])?;
453 if !added_paths.contains(&p) { added_paths.push(p); }
454 for (id_stage, stage_num) in &[
455 ("3k_main_ceo_initial_data_stage_character_childhood_metal", 17i32),
456 ("3k_main_ceo_initial_data_equipment_permissions_unique_metal", 4i32),
457 ] {
458 let p = insert_row(pack, schema, "ceo_initial_data_to_stages_tables", stem, &row![
459 "ceo_initial_data" => &initial_data_key,
460 "initial_data_stage" => *id_stage,
461 "stage" => stage_num,
462 ])?;
463 if !added_paths.contains(&p) { added_paths.push(p); }
464 }
465 },
466 "water" => {
467 for scripted in &[
468 "3k_main_ceo_permissions_ancillary_weapon_character_sword_one_handed_enable",
469 &format!("3k_main_ceo_permissions_ancillary_armour_character_specific_{n}"),
470 ] {
471 let p = insert_row(pack, schema, "ceo_initial_data_scripted_permissions_tables", stem, &row![
472 "initial_data_stage" => &stage2_key,
473 "scripted_permissions" => *scripted,
474 "auto_id" => auto_id("ceo_initial_data_scripted_permissions", &format!("{stage2_key}|{scripted}")),
475 ])?;
476 if !added_paths.contains(&p) { added_paths.push(p); }
477 }
478 for (category, equipped_ceo) in &[
479 ("3k_main_ceo_category_ancillary_mount", "3k_main_ancillary_mount_black_horse"),
480 ("3k_main_ceo_category_ancillary_weapon", "3k_main_ancillary_weapon_single_edged_sword_common"),
481 ("3k_main_ceo_category_ancillary_armour", armor_ceo_key.as_str()),
482 ] {
483 let p = insert_row(pack, schema, "ceo_initial_data_equipments_tables", stem, &row![
484 "initial_data_stage" => &stage2_key,
485 "category" => *category,
486 "equipped_ceo" => *equipped_ceo,
487 "slot_index" => "0",
488 "target" => "character_equipment",
489 "auto_id" => auto_id("ceo_initial_data_equipments", &format!("{stage2_key}|{equipped_ceo}")),
490 ])?;
491 if !added_paths.contains(&p) { added_paths.push(p); }
492 }
493 for active_ceo in &[
494 "3k_main_ancillary_weapon_single_edged_sword_common",
495 "3k_main_ancillary_weapon_dual_swords_common",
496 "3k_main_ancillary_weapon_double_edged_sword_common",
497 "3k_main_ancillary_mount_black_horse",
498 ] {
499 let p = insert_row(pack, schema, "ceo_initial_data_active_ceos_tables", stem, &row![
500 "initial_data_stage" => &stage2_key,
501 "active_ceo" => *active_ceo,
502 "starting_points_delta" => "0",
503 "auto_id" => auto_id("ceo_initial_data_active_ceos", &format!("{stage2_key}|{active_ceo}")),
504 ])?;
505 if !added_paths.contains(&p) { added_paths.push(p); }
506 }
507 let p = insert_row(pack, schema, "ceo_initial_data_active_ceos_tables", stem, &row![
509 "initial_data_stage" => &stage2_key,
510 "active_ceo" => &armor_ceo_key,
511 "starting_points_delta" => "0",
512 "auto_id" => auto_id("ceo_initial_data_active_ceos", &format!("{stage2_key}|{armor_ceo_key}")),
513 ])?;
514 if !added_paths.contains(&p) { added_paths.push(p); }
515 let p = insert_row(pack, schema, "ceo_initial_data_active_ceos_tables", stem, &row![
517 "initial_data_stage" => &stage1_key,
518 "active_ceo" => "3k_main_ceo_class_water",
519 "starting_points_delta" => "0",
520 "auto_id" => auto_id("ceo_initial_data_active_ceos", &format!("{stage1_key}|3k_main_ceo_class_water")),
521 ])?;
522 if !added_paths.contains(&p) { added_paths.push(p); }
523 let p = insert_row(pack, schema, "ceo_effect_list_to_effects_tables", stem, &row![
525 "effect_list" => &effect_list_armor,
526 "effect" => "3k_main_effect_character_attribute_cunning_mod",
527 "value" => "18",
528 "effect_scope" => "character_to_character_own",
529 "optional_only_in_game_mode" => "",
530 "auto_id" => auto_id("ceo_effect_list_to_effects", &format!("{effect_list_armor}|cunning")),
531 ])?;
532 if !added_paths.contains(&p) { added_paths.push(p); }
533 for (id_stage, stage_num) in &[
534 ("3k_main_ceo_initial_data_stage_character_childhood_water", 17i32),
535 ("3k_main_ceo_initial_data_equipment_permissions_unique_water", 4i32),
536 ] {
537 let p = insert_row(pack, schema, "ceo_initial_data_to_stages_tables", stem, &row![
538 "ceo_initial_data" => &initial_data_key,
539 "initial_data_stage" => *id_stage,
540 "stage" => stage_num,
541 ])?;
542 if !added_paths.contains(&p) { added_paths.push(p); }
543 }
544 },
545 "earth" => {
546 for scripted in &[
547 "3k_main_ceo_permissions_ancillary_weapon_character_axe_dual_enable",
548 "3k_main_ceo_permissions_ancillary_weapon_character_axe_one_handed_enable",
549 &format!("3k_main_ceo_permissions_ancillary_armour_character_specific_{n}"),
550 "3k_main_ceo_permissions_ancillary_weapon_character_sword_one_handed_enable",
551 "3k_main_ceo_permissions_ancillary_weapon_character_sword_dual_enable",
552 ] {
553 let p = insert_row(pack, schema, "ceo_initial_data_scripted_permissions_tables", stem, &row![
554 "initial_data_stage" => &stage2_key,
555 "scripted_permissions" => *scripted,
556 "auto_id" => auto_id("ceo_initial_data_scripted_permissions", &format!("{stage2_key}|{scripted}")),
557 ])?;
558 if !added_paths.contains(&p) { added_paths.push(p); }
559 }
560 for (category, equipped_ceo) in &[
561 ("3k_main_ceo_category_ancillary_weapon", "3k_main_ancillary_weapon_single_edged_sword_common"),
562 ("3k_main_ceo_category_ancillary_mount", "3k_main_ancillary_mount_white_horse"),
563 ("3k_main_ceo_category_ancillary_armour", armor_ceo_key.as_str()),
564 ] {
565 let p = insert_row(pack, schema, "ceo_initial_data_equipments_tables", stem, &row![
566 "initial_data_stage" => &stage2_key,
567 "category" => *category,
568 "equipped_ceo" => *equipped_ceo,
569 "slot_index" => "0",
570 "target" => "character_equipment",
571 "auto_id" => auto_id("ceo_initial_data_equipments", &format!("{stage2_key}|{equipped_ceo}")),
572 ])?;
573 if !added_paths.contains(&p) { added_paths.push(p); }
574 }
575 for active_ceo in &[
576 "3k_main_ancillary_weapon_one_handed_axe_common",
577 "3k_main_ancillary_weapon_dual_swords_common",
578 "3k_main_ancillary_mount_white_horse",
579 "3k_main_ancillary_weapon_single_edged_sword_common",
580 "3k_main_ancillary_weapon_double_edged_sword_common",
581 ] {
582 let p = insert_row(pack, schema, "ceo_initial_data_active_ceos_tables", stem, &row![
583 "initial_data_stage" => &stage2_key,
584 "active_ceo" => *active_ceo,
585 "starting_points_delta" => "0",
586 "auto_id" => auto_id("ceo_initial_data_active_ceos", &format!("{stage2_key}|{active_ceo}")),
587 ])?;
588 if !added_paths.contains(&p) { added_paths.push(p); }
589 }
590 let p = insert_row(pack, schema, "ceo_initial_data_active_ceos_tables", stem, &row![
592 "initial_data_stage" => &stage2_key,
593 "active_ceo" => &armor_ceo_key,
594 "starting_points_delta" => "0",
595 "auto_id" => auto_id("ceo_initial_data_active_ceos", &format!("{stage2_key}|{armor_ceo_key}")),
596 ])?;
597 if !added_paths.contains(&p) { added_paths.push(p); }
598 let p = insert_row(pack, schema, "ceo_initial_data_active_ceos_tables", stem, &row![
600 "initial_data_stage" => &stage1_key,
601 "active_ceo" => "3k_main_ceo_class_earth",
602 "starting_points_delta" => "0",
603 "auto_id" => auto_id("ceo_initial_data_active_ceos", &format!("{stage1_key}|3k_main_ceo_class_earth")),
604 ])?;
605 if !added_paths.contains(&p) { added_paths.push(p); }
606 if entry.expanded {
608 let p = insert_row(pack, schema, "ceo_initial_data_active_ceos_tables", stem, &row![
609 "initial_data_stage" => &stage2_key,
610 "active_ceo" => "3k_main_ancillary_weapon_sword_and_shield_common",
611 "starting_points_delta" => "0",
612 "auto_id" => auto_id("ceo_initial_data_active_ceos", &format!("{stage2_key}|3k_main_ancillary_weapon_sword_and_shield_common")),
613 ])?;
614 if !added_paths.contains(&p) { added_paths.push(p); }
615 }
616 let p = insert_row(pack, schema, "ceo_effect_list_to_effects_tables", stem, &row![
618 "effect_list" => &effect_list_armor,
619 "effect" => "3k_main_effect_character_attribute_authority_mod",
620 "value" => "18",
621 "effect_scope" => "character_to_character_own",
622 "optional_only_in_game_mode" => "",
623 "auto_id" => auto_id("ceo_effect_list_to_effects", &format!("{effect_list_armor}|authority")),
624 ])?;
625 if !added_paths.contains(&p) { added_paths.push(p); }
626 for (id_stage, stage_num) in &[
627 ("3k_main_ceo_initial_data_stage_character_childhood_earth", 17i32),
628 ("3k_main_ceo_initial_data_equipment_permissions_unique_earth", 4i32),
629 ] {
630 let p = insert_row(pack, schema, "ceo_initial_data_to_stages_tables", stem, &row![
631 "ceo_initial_data" => &initial_data_key,
632 "initial_data_stage" => *id_stage,
633 "stage" => stage_num,
634 ])?;
635 if !added_paths.contains(&p) { added_paths.push(p); }
636 }
637 },
638 "fire" => {
639 for scripted in &[
640 "3k_main_ceo_permissions_ancillary_weapon_character_axe_two_handed_enable",
641 "3k_main_ceo_permissions_ancillary_weapon_character_spear_two_handed_long_enable",
642 &format!("3k_main_ceo_permissions_ancillary_armour_character_specific_{n}"),
643 "3k_main_ceo_permissions_ancillary_weapon_character_spear_two_handed_enable",
644 "3k_main_ceo_permissions_ancillary_weapon_character_axe_two_handed_enable",
645 ] {
646 let p = insert_row(pack, schema, "ceo_initial_data_scripted_permissions_tables", stem, &row![
647 "initial_data_stage" => &stage2_key,
648 "scripted_permissions" => *scripted,
649 "auto_id" => auto_id("ceo_initial_data_scripted_permissions", &format!("{stage2_key}|{scripted}")),
650 ])?;
651 if !added_paths.contains(&p) { added_paths.push(p); }
652 }
653 for (category, equipped_ceo) in &[
654 ("3k_main_ceo_category_ancillary_mount", "3k_main_ancillary_mount_red_horse"),
655 ("3k_main_ceo_category_ancillary_weapon", "3k_main_ancillary_weapon_two_handed_spear_common"),
656 ("3k_main_ceo_category_ancillary_armour", armor_ceo_key.as_str()),
657 ] {
658 let p = insert_row(pack, schema, "ceo_initial_data_equipments_tables", stem, &row![
659 "initial_data_stage" => &stage2_key,
660 "category" => *category,
661 "equipped_ceo" => *equipped_ceo,
662 "slot_index" => "0",
663 "target" => "character_equipment",
664 "auto_id" => auto_id("ceo_initial_data_equipments", &format!("{stage2_key}|{equipped_ceo}")),
665 ])?;
666 if !added_paths.contains(&p) { added_paths.push(p); }
667 }
668 for active_ceo in &[
669 "3k_main_ancillary_weapon_hook_sickle_sabre_common",
670 "3k_main_ancillary_weapon_two_handed_axe_common",
671 "3k_main_ancillary_weapon_two_handed_spear_common",
672 "3k_main_ancillary_weapon_halberd_common",
673 "3k_main_ancillary_mount_red_horse",
674 ] {
675 let p = insert_row(pack, schema, "ceo_initial_data_active_ceos_tables", stem, &row![
676 "initial_data_stage" => &stage2_key,
677 "active_ceo" => *active_ceo,
678 "starting_points_delta" => "0",
679 "auto_id" => auto_id("ceo_initial_data_active_ceos", &format!("{stage2_key}|{active_ceo}")),
680 ])?;
681 if !added_paths.contains(&p) { added_paths.push(p); }
682 }
683 let p = insert_row(pack, schema, "ceo_initial_data_active_ceos_tables", stem, &row![
685 "initial_data_stage" => &stage2_key,
686 "active_ceo" => &armor_ceo_key,
687 "starting_points_delta" => "0",
688 "auto_id" => auto_id("ceo_initial_data_active_ceos", &format!("{stage2_key}|{armor_ceo_key}")),
689 ])?;
690 if !added_paths.contains(&p) { added_paths.push(p); }
691 let p = insert_row(pack, schema, "ceo_initial_data_active_ceos_tables", stem, &row![
693 "initial_data_stage" => &stage1_key,
694 "active_ceo" => "3k_main_ceo_class_fire",
695 "starting_points_delta" => "0",
696 "auto_id" => auto_id("ceo_initial_data_active_ceos", &format!("{stage1_key}|3k_main_ceo_class_fire")),
697 ])?;
698 if !added_paths.contains(&p) { added_paths.push(p); }
699 let p = insert_row(pack, schema, "ceo_effect_list_to_effects_tables", stem, &row![
701 "effect_list" => &effect_list_armor,
702 "effect" => "3k_main_effect_character_attribute_instinct_mod",
703 "value" => "18",
704 "effect_scope" => "character_to_character_own",
705 "optional_only_in_game_mode" => "",
706 "auto_id" => auto_id("ceo_effect_list_to_effects", &format!("{effect_list_armor}|instinct")),
707 ])?;
708 if !added_paths.contains(&p) { added_paths.push(p); }
709 for (id_stage, stage_num) in &[
710 ("3k_main_ceo_initial_data_stage_character_childhood_fire", 17i32),
711 ("3k_main_ceo_initial_data_equipment_permissions_unique_fire", 4i32),
712 ] {
713 let p = insert_row(pack, schema, "ceo_initial_data_to_stages_tables", stem, &row![
714 "ceo_initial_data" => &initial_data_key,
715 "initial_data_stage" => *id_stage,
716 "stage" => stage_num,
717 ])?;
718 if !added_paths.contains(&p) { added_paths.push(p); }
719 }
720 },
721 _ => { for scripted in &[
723 &format!("3k_main_ceo_permissions_ancillary_armour_character_specific_{n}"),
724 "3k_main_ceo_permissions_ancillary_weapon_character_spear_two_handed_enable",
725 "3k_main_ceo_permissions_ancillary_weapon_character_spear_two_handed_long_enable",
726 "3k_ytr_ceo_permissions_ancillary_weapon_character_staff_two_handed_enable",
727 "3k_ytr_ceo_permissions_ancillary_weapon_character_mace_two_handed_enable",
728 ] {
729 let p = insert_row(pack, schema, "ceo_initial_data_scripted_permissions_tables", stem, &row![
730 "initial_data_stage" => &stage2_key,
731 "scripted_permissions" => *scripted,
732 "auto_id" => auto_id("ceo_initial_data_scripted_permissions", &format!("{stage2_key}|{scripted}")),
733 ])?;
734 if !added_paths.contains(&p) { added_paths.push(p); }
735 }
736 for (category, equipped_ceo) in &[
737 ("3k_main_ceo_category_ancillary_mount", "3k_main_ancillary_mount_brown_horse"),
738 ("3k_main_ceo_category_ancillary_weapon", "3k_main_ancillary_weapon_two_handed_spear_common"),
739 ("3k_main_ceo_category_ancillary_armour", armor_ceo_key.as_str()),
740 ] {
741 let p = insert_row(pack, schema, "ceo_initial_data_equipments_tables", stem, &row![
742 "initial_data_stage" => &stage2_key,
743 "category" => *category,
744 "equipped_ceo" => *equipped_ceo,
745 "slot_index" => "0",
746 "target" => "character_equipment",
747 "auto_id" => auto_id("ceo_initial_data_equipments", &format!("{stage2_key}|{equipped_ceo}")),
748 ])?;
749 if !added_paths.contains(&p) { added_paths.push(p); }
750 }
751 for active_ceo in &[
752 "3k_ytr_ancillary_weapon_2h_ball_mace_common",
753 "3k_main_ancillary_weapon_hook_sickle_sabre_common",
754 "3k_main_ancillary_mount_brown_horse",
755 "3k_main_ancillary_weapon_two_handed_spear_common",
756 "3k_main_ancillary_weapon_halberd_common",
757 ] {
758 let p = insert_row(pack, schema, "ceo_initial_data_active_ceos_tables", stem, &row![
759 "initial_data_stage" => &stage2_key,
760 "active_ceo" => *active_ceo,
761 "starting_points_delta" => "0",
762 "auto_id" => auto_id("ceo_initial_data_active_ceos", &format!("{stage2_key}|{active_ceo}")),
763 ])?;
764 if !added_paths.contains(&p) { added_paths.push(p); }
765 }
766 let p = insert_row(pack, schema, "ceo_initial_data_active_ceos_tables", stem, &row![
768 "initial_data_stage" => &stage2_key,
769 "active_ceo" => &armor_ceo_key,
770 "starting_points_delta" => "0",
771 "auto_id" => auto_id("ceo_initial_data_active_ceos", &format!("{stage2_key}|{armor_ceo_key}")),
772 ])?;
773 if !added_paths.contains(&p) { added_paths.push(p); }
774 let p = insert_row(pack, schema, "ceo_initial_data_active_ceos_tables", stem, &row![
776 "initial_data_stage" => &stage1_key,
777 "active_ceo" => "3k_main_ceo_class_wood",
778 "starting_points_delta" => "0",
779 "auto_id" => auto_id("ceo_initial_data_active_ceos", &format!("{stage1_key}|3k_main_ceo_class_wood")),
780 ])?;
781 if !added_paths.contains(&p) { added_paths.push(p); }
782 let p = insert_row(pack, schema, "ceo_effect_list_to_effects_tables", stem, &row![
784 "effect_list" => &effect_list_armor,
785 "effect" => "3k_main_effect_character_attribute_resolve_mod",
786 "value" => "18",
787 "effect_scope" => "character_to_character_own",
788 "optional_only_in_game_mode" => "",
789 "auto_id" => auto_id("ceo_effect_list_to_effects", &format!("{effect_list_armor}|resolve")),
790 ])?;
791 if !added_paths.contains(&p) { added_paths.push(p); }
792 for (id_stage, stage_num) in &[
793 ("3k_main_ceo_initial_data_stage_character_childhood_wood", 17i32),
794 ("3k_main_ceo_initial_data_equipment_permissions_unique_wood", 4i32),
795 ] {
796 let p = insert_row(pack, schema, "ceo_initial_data_to_stages_tables", stem, &row![
797 "ceo_initial_data" => &initial_data_key,
798 "initial_data_stage" => *id_stage,
799 "stage" => stage_num,
800 ])?;
801 if !added_paths.contains(&p) { added_paths.push(p); }
802 }
803 },
804 }
805 let gender_stage = if gender == "male" {
807 "3k_main_ceo_initial_data_stage_character_gender_male"
808 } else {
809 "3k_main_ceo_initial_data_stage_character_gender_female"
810 };
811 let p = insert_row(pack, schema, "ceo_initial_data_to_stages_tables", stem, &row![
812 "ceo_initial_data" => &initial_data_key,
813 "initial_data_stage" => gender_stage,
814 "stage" => "13",
815 ])?;
816 if !added_paths.contains(&p) { added_paths.push(p); }
817
818 let p = insert_row(pack, schema, "ceo_initial_data_to_stages_tables", stem, &row![
822 "ceo_initial_data" => &initial_data_key,
823 "initial_data_stage" => &stage2_key,
824 "stage" => "3",
825 ])?;
826 if !added_paths.contains(&p) { added_paths.push(p); }
827
828 let p = insert_row(pack, schema, "ceo_initial_data_active_ceos_tables", stem, &row![
829 "initial_data_stage" => &stage1_key,
830 "active_ceo" => &format!("3k_main_ceo_career_historical_{n}"),
831 "starting_points_delta" => "0",
832 "auto_id" => auto_id("ceo_initial_data_active_ceos", &format!("{stage1_key}|3k_main_ceo_career_historical_{n}")),
833 ])?;
834 if !added_paths.contains(&p) { added_paths.push(p); }
835
836 for (id_stage, stage_num) in &[
837 ("3k_main_initial_data_character_ancillaries_global", 2i32),
838 ("3k_dlc04_ceo_initial_data_character_give_political_support_random", 21i32),
839 ("3k_main_ceo_initial_data_stage_character_wealth_random", 15i32),
840 ("3k_main_ceo_initial_data_stage_character_traits_shared_global_permissions", 10i32),
841 ("3k_main_ceo_initial_data_stage_character_protagonist", 14i32),
842 ] {
843 let p = insert_row(pack, schema, "ceo_initial_data_to_stages_tables", stem, &row![
844 "ceo_initial_data" => &initial_data_key,
845 "initial_data_stage" => *id_stage,
846 "stage" => stage_num,
847 ])?;
848 if !added_paths.contains(&p) { added_paths.push(p); }
849 }
850 let p = insert_row(pack, schema, "ceo_initial_data_to_stages_tables", stem, &row![
851 "ceo_initial_data" => &initial_data_key,
852 "initial_data_stage" => &stage1_key,
853 "stage" => "11",
854 ])?;
855 if !added_paths.contains(&p) { added_paths.push(p); }
856
857 for (ceo_ref, key_val) in &[
859 (format!("3k_main_ceo_career_historical_{n}"), format!("3k_main_ceo_career_historical_{n}")),
860 (format!("3k_main_ancilliary_armour_{n}_armour_unique"), format!("3k_main_ancilliary_armour_{n}_armour_unique")),
861 ] {
862 let p = insert_row(pack, schema, "ceo_thresholds_tables", stem, &row![
863 "key" => key_val,
864 "ceo" => ceo_ref,
865 "point_threshold_to_activate" => "1",
866 "point_theshold_to_destroy" => "0",
867 "starting_points" => "1",
868 "max_points" => "1",
869 "resets_to_starting_points_when_deactivated" => "false",
870 ])?;
871 if !added_paths.contains(&p) { added_paths.push(p); }
872 }
873
874 let p = insert_row(pack, schema, "ceo_nodes_tables", stem, &row![
876 "key" => format!("3k_main_ceo_career_historical_{n}"),
877 "ceo_effect_list" => &effect_list_career,
878 "title" => "placeholder",
879 "description" => "placeholder",
880 "icon_path" => "",
881 "opinion_topic_modifier" => "",
882 "point_change_per_turn_if_active" => "0",
883 ])?;
884 if !added_paths.contains(&p) { added_paths.push(p); }
885 let armour_icon = format!("armours/3k_main_ancillary_{}_armour_unique.png", element);
887 let p = insert_row(pack, schema, "ceo_nodes_tables", stem, &row![
888 "key" => format!("3k_main_ancilliary_armour_{n}_armour_unique"),
889 "ceo_effect_list" => &effect_list_armor,
890 "title" => "placeholder",
891 "description" => "placeholder",
892 "icon_path" => &armour_icon,
893 "opinion_topic_modifier" => "",
894 "point_change_per_turn_if_active" => "0",
895 ])?;
896 if !added_paths.contains(&p) { added_paths.push(p); }
897
898 for (node_key, threshold_key) in &[
900 (format!("3k_main_ceo_career_historical_{n}"), format!("3k_main_ceo_career_historical_{n}")),
901 (format!("3k_main_ancilliary_armour_{n}_armour_unique"), format!("3k_main_ancilliary_armour_{n}_armour_unique")),
902 ] {
903 let p = insert_row(pack, schema, "ceo_threshold_nodes_tables", stem, &row![
904 "ceo_threshold" => threshold_key,
905 "ceo_node" => node_key,
906 "points_threshold_to_activate_node" => "1",
907 "can_downgrade_to_previous_node" => "false",
908 "auto_id" => auto_id("ceo_threshold_nodes", &format!("{threshold_key}|{node_key}")),
909 ])?;
910 if !added_paths.contains(&p) { added_paths.push(p); }
911 }
912
913 let p = insert_row(pack, schema, "ceo_effect_list_to_effects_tables", stem, &row![
915 "effect_list" => &effect_list_armor,
916 "effect" => "3k_dummy_effect_ceo_subcategory_armour_unique",
917 "value" => "0",
918 "effect_scope" => "character_to_character_own",
919 "optional_only_in_game_mode" => "",
920 "auto_id" => auto_id("ceo_effect_list_to_effects", &format!("{effect_list_armor}|dummy_subcategory")),
921 ])?;
922 if !added_paths.contains(&p) { added_paths.push(p); }
923
924 for (effect, val) in &[
926 ("3k_main_character_wealth", "2"),
927 ("3k_main_effect_character_num_lives", "1"),
928 ] {
929 let p = insert_row(pack, schema, "ceo_effect_list_to_effects_tables", stem, &row![
930 "effect_list" => &effect_list_career,
931 "effect" => *effect,
932 "value" => *val,
933 "effect_scope" => "character_to_character_own",
934 "optional_only_in_game_mode" => "",
935 "auto_id" => auto_id("ceo_effect_list_to_effects", &format!("{effect_list_career}|{effect}")),
936 ])?;
937 if !added_paths.contains(&p) { added_paths.push(p); }
938 }
939
940 for (trait_uuid, trait_key) in &entry.traits {
942 let p = insert_row(pack, schema, "ceo_initial_data_active_ceos_tables", stem, &row![
943 "initial_data_stage" => &stage1_key,
944 "active_ceo" => trait_key.as_str(),
945 "starting_points_delta" => "0",
946 "auto_id" => auto_id("ceo_initial_data_active_ceos", &format!("{stage1_key}|{trait_key}|{trait_uuid}")),
947 ])?;
948 if !added_paths.contains(&p) { added_paths.push(p); }
949 }
950
951 let p = insert_row(pack, schema, "ceos_to_equipment_variants_tables", stem, &row![
953 "ceos_key" => &format!("3k_main_ancilliary_armour_{n}_armour_unique"),
954 "game_mode" => "",
955 "armour" => "3k_ytr_hero_scholar_unique",
956 "male_vmd" => "",
957 "female_vmd" => "",
958 "mount" => "",
959 "primary_melee_weapon" => "",
960 "primary_missile_weapon" => "",
961 "shield" => "",
962 "man_animation" => "",
963 "mount_animation" => "",
964 "secondary_weapon_animation" => "",
965 "remap_general_unit_to_hero_unit" => "false",
966 "priority" => "1",
967 "autonomous_rider_group" => "",
968 "ground_type_stat_effect_group" => "",
969 ])?;
970 if !added_paths.contains(&p) { added_paths.push(p); }
971
972 let p = insert_row(pack, schema, "ceo_template_manager_ceo_limits_tables", stem, &row![
974 "ceo_to_limit" => &format!("3k_main_ancilliary_armour_{n}_armour_unique"),
975 "template_manager" => "3k_main_ceo_template_manager_world_generic",
976 "max_limit_that_can_exist_at_once" => "1",
977 "scoped_limit_or_local_only_limit" => "true",
978 "ceo_category_to_limit" => "",
979 "ceo_node_to_limit" => "",
980 "auto_id" => auto_id("ceo_template_manager_ceo_limits", &format!("3k_main_ancilliary_armour_{n}_armour_unique")),
981 ])?;
982 if !added_paths.contains(&p) { added_paths.push(p); }
983
984 let human = n.replace("_ironic", "")
986 .split('_')
987 .map(|w: &str| {
988 let mut c = w.chars();
989 c.next().map(|f: char| f.to_uppercase().collect::<String>() + c.as_str()).unwrap_or_default()
990 })
991 .collect::<Vec<_>>()
992 .join(" ");
993 loc_entries.push((format!("ceo_nodes_title_3k_main_ceo_career_historical_{n}"), "PLACEHOLDER".into()));
994 loc_entries.push((format!("ceo_nodes_description_3k_main_ceo_career_historical_{n}"), "PLACEHOLDER".into()));
995 loc_entries.push((format!("ceo_nodes_title_3k_main_ancilliary_armour_{n}_armour_unique"), format!("{human}'s Armour")));
996 loc_entries.push((format!("ceo_nodes_description_3k_main_ancilliary_armour_{n}_armour_unique"), "The perfect weight and fit, tailored for this warrior of class and distinction.".into()));
997
998 } else {
999 let p = insert_row(pack, schema, "ceos_tables", stem, &row![
1003 "key" => format!("3k_main_ceo_career_historical_{n}"),
1004 "exists_in_location" => "character_ceo_manager",
1005 "category" => "3k_main_ceo_category_career",
1006 "equipped_in_location" => "character_equipment",
1007 "priority" => "1",
1008 "turns_to_expire" => "0",
1009 "point_change_per_turn_if_inactive" => "0",
1010 "point_change_per_turn_while_active" => "0",
1011 "point_change_per_turn_while_equipped" => "0",
1012 "inheritance_chance" => "0",
1013 "can_be_looted_post_battle" => "false",
1014 "can_be_traded_in_diplomacy" => "false",
1015 "can_be_stolen" => "false",
1016 "rarity" => "common",
1017 "can_be_unequipped" => "false",
1018 "can_be_transferred_if_equipped" => "true",
1019 "cannot_reequip_until_next_round_if_unequipped" => "true",
1020 "provides_scripted_permissions_on_spawn" => "",
1021 ])?;
1022 if !added_paths.contains(&p) { added_paths.push(p); }
1023
1024 let career_key_title = format!("3k_main_ceo_career_historical_{n}");
1026 let p = insert_row(pack, schema, "ceo_group_ceos_tables", stem, &row![
1027 "ceo_group" => "3k_main_ceo_group_career_all",
1028 "ceo" => &career_key_title,
1029 "trigger_weighting" => "1",
1030 "auto_id" => auto_id("ceo_group_ceos", &format!("3k_main_ceo_group_career_all|{career_key_title}")),
1031 ])?;
1032 if !added_paths.contains(&p) { added_paths.push(p); }
1033
1034 let stage1_key = format!("3k_main_ceo_initial_data_stage_character_traits_historical_{n}");
1035 let p = insert_row(pack, schema, "ceo_initial_data_stages_tables", stem, &row![
1036 "key" => &stage1_key,
1037 ])?;
1038 if !added_paths.contains(&p) { added_paths.push(p); }
1039
1040 let effect_list_key = format!("3k_main_ceo_career_historical_{n}");
1041 let p = insert_row(pack, schema, "ceo_effect_lists_tables", stem, &row![
1042 "key" => &effect_list_key,
1043 ])?;
1044 if !added_paths.contains(&p) { added_paths.push(p); }
1045
1046 let initial_data_key = format!("3k_main_ceo_initial_data_character_historical_{n}");
1047 let p = insert_row(pack, schema, "ceo_initial_datas_tables", stem, &row![
1048 "key" => &initial_data_key,
1049 "template_manager" => "character_ceo_manager",
1050 ])?;
1051 if !added_paths.contains(&p) { added_paths.push(p); }
1052
1053 let (childhood_stage, equipment_stage, class_ceo, generic_stage, generic_stage_num) = match element.as_str() {
1055 "metal" => ("3k_main_ceo_initial_data_stage_character_childhood_metal", "3k_main_ceo_initial_data_equipment_permissions_title_metal", "3k_main_ceo_class_metal", "3k_main_ceo_initial_data_character_generic_metal_ancillaries_01", 3i32),
1056 "earth" => ("3k_main_ceo_initial_data_stage_character_childhood_earth", "3k_main_ceo_initial_data_equipment_permissions_title_earth", "3k_main_ceo_class_earth", "3k_main_ceo_initial_data_character_generic_earth_ancillaries_01", 3i32),
1057 "water" => ("3k_main_ceo_initial_data_stage_character_childhood_water", "3k_main_ceo_initial_data_equipment_permissions_title_water", "3k_main_ceo_class_water", "3k_main_ceo_initial_data_character_generic_water_ancillaries_01", 3i32),
1058 "fire" => ("3k_main_ceo_initial_data_stage_character_childhood_fire", "3k_main_ceo_initial_data_equipment_permissions_title_fire", "3k_main_ceo_class_fire", "3k_main_ceo_initial_data_character_generic_fire_ancillaries_01", 3i32),
1059 _ => ("3k_main_ceo_initial_data_stage_character_childhood_wood", "3k_main_ceo_initial_data_equipment_permissions_title_wood", "3k_main_ceo_class_wood", "3k_main_ceo_initial_data_character_generic_wood_ancillaries_01", 3i32),
1060 };
1061
1062 for (id_stage, stage_num) in &[
1063 (childhood_stage, 17i32),
1064 (equipment_stage, 4i32),
1065 ] {
1066 let p = insert_row(pack, schema, "ceo_initial_data_to_stages_tables", stem, &row![
1067 "ceo_initial_data" => &initial_data_key,
1068 "initial_data_stage" => *id_stage,
1069 "stage" => stage_num,
1070 ])?;
1071 if !added_paths.contains(&p) { added_paths.push(p); }
1072 }
1073 let p = insert_row(pack, schema, "ceo_initial_data_active_ceos_tables", stem, &row![
1074 "initial_data_stage" => &stage1_key,
1075 "active_ceo" => class_ceo,
1076 "starting_points_delta" => "0",
1077 "auto_id" => auto_id("ceo_initial_data_active_ceos", &format!("{stage1_key}|{class_ceo}")),
1078 ])?;
1079 if !added_paths.contains(&p) { added_paths.push(p); }
1080 let p = insert_row(pack, schema, "ceo_initial_data_to_stages_tables", stem, &row![
1081 "ceo_initial_data" => &initial_data_key,
1082 "initial_data_stage" => generic_stage,
1083 "stage" => &generic_stage_num,
1084 ])?;
1085 if !added_paths.contains(&p) { added_paths.push(p); }
1086
1087 let gender_stage = if gender == "male" {
1089 "3k_main_ceo_initial_data_stage_character_gender_male"
1090 } else {
1091 "3k_main_ceo_initial_data_stage_character_gender_female"
1092 };
1093 let p = insert_row(pack, schema, "ceo_initial_data_to_stages_tables", stem, &row![
1094 "ceo_initial_data" => &initial_data_key,
1095 "initial_data_stage" => gender_stage,
1096 "stage" => "13",
1097 ])?;
1098 if !added_paths.contains(&p) { added_paths.push(p); }
1099
1100 let career_key = format!("3k_main_ceo_career_historical_{n}");
1102 let p = insert_row(pack, schema, "ceo_initial_data_active_ceos_tables", stem, &row![
1103 "initial_data_stage" => &stage1_key,
1104 "active_ceo" => &career_key,
1105 "starting_points_delta" => "0",
1106 "auto_id" => auto_id("ceo_initial_data_active_ceos", &format!("{stage1_key}|{career_key}")),
1107 ])?;
1108 if !added_paths.contains(&p) { added_paths.push(p); }
1109
1110 for (id_stage, stage_num) in &[
1111 ("3k_main_initial_data_character_ancillaries_global", 2i32),
1112 ("3k_dlc04_ceo_initial_data_character_give_political_support_random", 21i32),
1113 ("3k_main_ceo_initial_data_stage_character_wealth_random", 15i32),
1114 ("3k_main_ceo_initial_data_stage_character_traits_shared_global_permissions", 10i32),
1115 ("3k_main_ceo_initial_data_stage_character_protagonist", 14i32),
1116 ] {
1117 let p = insert_row(pack, schema, "ceo_initial_data_to_stages_tables", stem, &row![
1118 "ceo_initial_data" => &initial_data_key,
1119 "initial_data_stage" => *id_stage,
1120 "stage" => stage_num,
1121 ])?;
1122 if !added_paths.contains(&p) { added_paths.push(p); }
1123 }
1124 let p = insert_row(pack, schema, "ceo_initial_data_to_stages_tables", stem, &row![
1125 "ceo_initial_data" => &initial_data_key,
1126 "initial_data_stage" => &stage1_key,
1127 "stage" => "11",
1128 ])?;
1129 if !added_paths.contains(&p) { added_paths.push(p); }
1130
1131 let p = insert_row(pack, schema, "ceo_thresholds_tables", stem, &row![
1133 "key" => &career_key,
1134 "ceo" => &career_key,
1135 "point_threshold_to_activate" => "1",
1136 "point_theshold_to_destroy" => "0",
1137 "starting_points" => "1",
1138 "max_points" => "1",
1139 "resets_to_starting_points_when_deactivated" => "false",
1140 ])?;
1141 if !added_paths.contains(&p) { added_paths.push(p); }
1142
1143 let p = insert_row(pack, schema, "ceo_nodes_tables", stem, &row![
1145 "key" => &career_key,
1146 "ceo_effect_list" => &effect_list_key,
1147 "title" => "placeholder",
1148 "description" => "placeholder",
1149 "icon_path" => "",
1150 "opinion_topic_modifier" => "",
1151 "point_change_per_turn_if_active" => "0",
1152 ])?;
1153 if !added_paths.contains(&p) { added_paths.push(p); }
1154
1155 let p = insert_row(pack, schema, "ceo_threshold_nodes_tables", stem, &row![
1157 "ceo_threshold" => &career_key,
1158 "ceo_node" => &career_key,
1159 "points_threshold_to_activate_node" => "1",
1160 "can_downgrade_to_previous_node" => "false",
1161 "auto_id" => auto_id("ceo_threshold_nodes", &format!("{career_key}|{career_key}")),
1162 ])?;
1163 if !added_paths.contains(&p) { added_paths.push(p); }
1164
1165 for (effect, val) in &[
1167 ("3k_main_character_wealth", "2"),
1168 ("3k_main_effect_character_num_lives", "1"),
1169 ] {
1170 let p = insert_row(pack, schema, "ceo_effect_list_to_effects_tables", stem, &row![
1171 "effect_list" => &effect_list_key,
1172 "effect" => *effect,
1173 "value" => *val,
1174 "effect_scope" => "character_to_character_own",
1175 "optional_only_in_game_mode" => "",
1176 "auto_id" => auto_id("ceo_effect_list_to_effects", &format!("{effect_list_key}|{effect}")),
1177 ])?;
1178 if !added_paths.contains(&p) { added_paths.push(p); }
1179 }
1180
1181 for (trait_uuid, trait_key) in &entry.traits {
1183 let p = insert_row(pack, schema, "ceo_initial_data_active_ceos_tables", stem, &row![
1184 "initial_data_stage" => &stage1_key,
1185 "active_ceo" => trait_key.as_str(),
1186 "starting_points_delta" => "0",
1187 "auto_id" => auto_id("ceo_initial_data_active_ceos", &format!("{stage1_key}|{trait_key}|{trait_uuid}")),
1188 ])?;
1189 if !added_paths.contains(&p) { added_paths.push(p); }
1190 }
1191
1192 loc_entries.push((format!("ceo_nodes_title_3k_main_ceo_career_historical_{n}"), "PLACEHOLDER".into()));
1194 loc_entries.push((format!("ceo_nodes_description_3k_main_ceo_career_historical_{n}"), "PLACEHOLDER".into()));
1195 }
1196 }
1197
1198 if !loc_entries.is_empty() {
1200 let loc_pairs: Vec<(&str, &str)> = loc_entries.iter()
1201 .map(|(k, v)| (k.as_str(), v.as_str()))
1202 .collect();
1203 let p = insert_loc_entries(pack, &loc_path, &loc_pairs)?;
1204 if !added_paths.contains(&p) { added_paths.push(p); }
1205 }
1206
1207 Ok(added_paths)
1208}
1209
1210pub fn get_trait_ceos(deps: &rpfm_extensions::dependencies::Dependencies) -> Vec<(String, String)> {
1212 let ak_tables = deps.asskit_only_db_tables();
1213 let mut trait_ceos: Vec<(String, String)> = Vec::new();
1214
1215 let trait_categories: HashSet<&str> = [
1216 "3k_main_ceo_category_traits_personality",
1217 "3k_main_ceo_category_traits_physical",
1218 ].iter().copied().collect();
1219
1220 info!("GetTraitCeos: AK tables available: {}", ak_tables.len());
1221
1222 fn ak_lookup_pairs(ak_tables: &HashMap<String, DB>, table_name: &str, col_a: &str, col_b: &str) -> Vec<(String, String)> {
1224 let mut result = Vec::new();
1225 if let Some(db) = ak_tables.get(table_name) {
1226 let fields = db.definition().fields_processed();
1227 let a_idx = fields.iter().position(|f| f.name() == col_a);
1228 let b_idx = fields.iter().position(|f| f.name() == col_b);
1229 if let (Some(ai), Some(bi)) = (a_idx, b_idx) {
1230 for row in db.data().iter() {
1231 result.push((row[ai].data_to_string().to_string(), row[bi].data_to_string().to_string()));
1232 }
1233 }
1234 }
1235 result
1236 }
1237
1238 if let Some(ceos_db) = ak_tables.get("ceos_tables") {
1240 let fields = ceos_db.definition().fields_processed();
1241 let key_idx = fields.iter().position(|f| f.name() == "key");
1242 let cat_idx = fields.iter().position(|f| f.name() == "category");
1243
1244 if let (Some(ki), Some(ci)) = (key_idx, cat_idx) {
1245 for row in ceos_db.data().iter() {
1246 let category = row[ci].data_to_string();
1247 if trait_categories.contains(&*category) {
1248 let ceo_key = row[ki].data_to_string().to_string();
1249 trait_ceos.push((ceo_key, String::new()));
1250 }
1251 }
1252 }
1253 }
1254
1255 info!("GetTraitCeos: found {} trait CEOs from AK ceos_tables", trait_ceos.len());
1256
1257 let ceo_to_threshold: HashMap<String, String> =
1259 ak_lookup_pairs(ak_tables, "ceo_thresholds_tables", "ceo", "key")
1260 .into_iter().collect();
1261
1262 let threshold_to_node: HashMap<String, String> =
1263 ak_lookup_pairs(ak_tables, "ceo_threshold_nodes_tables", "ceo_threshold", "ceo_node")
1264 .into_iter().collect();
1265
1266 let node_to_title: HashMap<String, String> =
1267 ak_lookup_pairs(ak_tables, "ceo_nodes_tables", "key", "title")
1268 .into_iter().collect();
1269
1270 let mut loc_lookup: HashMap<String, String> = HashMap::new();
1272 if let Ok(loc_files) = deps.loc_data(true, true) {
1273 for rfile in &loc_files {
1274 if let Ok(RFileDecoded::Loc(loc)) = rfile.decoded() {
1275 for row in loc.data().iter() {
1276 if row.len() >= 2 {
1277 let loc_key = row[0].data_to_string().to_string();
1278 let loc_val = row[1].data_to_string().to_string();
1279 loc_lookup.insert(loc_key, loc_val);
1280 }
1281 }
1282 }
1283 }
1284 }
1285
1286 info!("GetTraitCeos: loc_lookup has {} entries, node_to_title has {} entries", loc_lookup.len(), node_to_title.len());
1287
1288 for (ceo_key, display_name) in &mut trait_ceos {
1290 let resolved = ceo_to_threshold.get(ceo_key.as_str())
1292 .and_then(|thresh| threshold_to_node.get(thresh))
1293 .and_then(|node| {
1294 node_to_title.get(node).and_then(|title_key| {
1296 loc_lookup.get(title_key)
1297 .or_else(|| {
1298 let constructed = format!("ceo_nodes_title_{}", node);
1300 loc_lookup.get(&constructed)
1301 })
1302 })
1303 });
1304
1305 if let Some(name) = resolved {
1306 *display_name = name.clone();
1307 } else {
1308 *display_name = ceo_key
1310 .replace("3k_main_ceo_trait_", "")
1311 .replace("3k_dlc", "")
1312 .replace("3k_ytr_ceo_trait_", "")
1313 .replace('_', " ");
1314 }
1315 }
1316
1317 trait_ceos.sort_by(|a, b| a.1.cmp(&b.1));
1319
1320 info!("GetTraitCeos: returning {} traits", trait_ceos.len());
1321
1322 trait_ceos
1323}
1324
1325pub fn build_ceo_post(pack: &mut Pack, akit_path: &str) -> Result<Vec<ContainerPath>> {
1327 let ceo_ccd_path = PathBuf::from(akit_path)
1328 .join(r"working_data\campaigns\ceo_data.ccd");
1329
1330 if !ceo_ccd_path.exists() {
1331 return Err(anyhow!("ceo_data.ccd not found. Make sure BOB ran successfully."));
1332 }
1333
1334 let raw_bytes = std::fs::read(&ceo_ccd_path)
1335 .map_err(|e| anyhow!("Failed to read ceo_data.ccd: {e}"))?;
1336
1337 let mut rfile = RFile::new_from_vec(&raw_bytes, FileType::Unknown, 0, "campaigns/ceo_data.ccd");
1338 let _ = rfile.guess_file_type();
1339 match pack.insert(rfile) {
1340 Ok(Some(path)) => Ok(vec![path]),
1341 Ok(None) => Ok(vec![]),
1342 Err(e) => Err(anyhow!("{}", e)),
1343 }
1344}