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 if entry.expanded {
428 let p = insert_row(pack, schema, "ceo_initial_data_active_ceos_tables", stem, &row![
429 "initial_data_stage" => &stage2_key,
430 "active_ceo" => "3k_main_ancillary_weapon_sword_and_shield_common",
431 "starting_points_delta" => "0",
432 "auto_id" => auto_id("ceo_initial_data_active_ceos", &format!("{stage2_key}|3k_main_ancillary_weapon_sword_and_shield_common")),
433 ])?;
434 if !added_paths.contains(&p) { added_paths.push(p); }
435 }
436 let p = insert_row(pack, schema, "ceo_effect_list_to_effects_tables", stem, &row![
438 "effect_list" => &effect_list_armor,
439 "effect" => "3k_main_effect_character_attribute_expertise_mod",
440 "value" => "18",
441 "effect_scope" => "character_to_character_own",
442 "optional_only_in_game_mode" => "",
443 "auto_id" => auto_id("ceo_effect_list_to_effects", &format!("{effect_list_armor}|expertise")),
444 ])?;
445 if !added_paths.contains(&p) { added_paths.push(p); }
446 for (id_stage, stage_num) in &[
447 ("3k_main_ceo_initial_data_stage_character_childhood_metal", 17i32),
448 ("3k_main_ceo_initial_data_equipment_permissions_unique_metal", 4i32),
449 ] {
450 let p = insert_row(pack, schema, "ceo_initial_data_to_stages_tables", stem, &row![
451 "ceo_initial_data" => &initial_data_key,
452 "initial_data_stage" => *id_stage,
453 "stage" => stage_num,
454 ])?;
455 if !added_paths.contains(&p) { added_paths.push(p); }
456 }
457 },
458 "water" => {
459 for scripted in &[
460 "3k_main_ceo_permissions_ancillary_weapon_character_sword_one_handed_enable",
461 &format!("3k_main_ceo_permissions_ancillary_armour_character_specific_{n}"),
462 ] {
463 let p = insert_row(pack, schema, "ceo_initial_data_scripted_permissions_tables", stem, &row![
464 "initial_data_stage" => &stage2_key,
465 "scripted_permissions" => *scripted,
466 "auto_id" => auto_id("ceo_initial_data_scripted_permissions", &format!("{stage2_key}|{scripted}")),
467 ])?;
468 if !added_paths.contains(&p) { added_paths.push(p); }
469 }
470 for (category, equipped_ceo) in &[
471 ("3k_main_ceo_category_ancillary_mount", "3k_main_ancillary_mount_black_horse"),
472 ("3k_main_ceo_category_ancillary_weapon", "3k_main_ancillary_weapon_single_edged_sword_common"),
473 ("3k_main_ceo_category_ancillary_armour", armor_ceo_key.as_str()),
474 ] {
475 let p = insert_row(pack, schema, "ceo_initial_data_equipments_tables", stem, &row![
476 "initial_data_stage" => &stage2_key,
477 "category" => *category,
478 "equipped_ceo" => *equipped_ceo,
479 "slot_index" => "0",
480 "target" => "character_equipment",
481 "auto_id" => auto_id("ceo_initial_data_equipments", &format!("{stage2_key}|{equipped_ceo}")),
482 ])?;
483 if !added_paths.contains(&p) { added_paths.push(p); }
484 }
485 for active_ceo in &[
486 "3k_main_ancillary_weapon_single_edged_sword_common",
487 "3k_main_ancillary_weapon_dual_swords_common",
488 "3k_main_ancillary_weapon_double_edged_sword_common",
489 "3k_main_ancillary_mount_black_horse",
490 ] {
491 let p = insert_row(pack, schema, "ceo_initial_data_active_ceos_tables", stem, &row![
492 "initial_data_stage" => &stage2_key,
493 "active_ceo" => *active_ceo,
494 "starting_points_delta" => "0",
495 "auto_id" => auto_id("ceo_initial_data_active_ceos", &format!("{stage2_key}|{active_ceo}")),
496 ])?;
497 if !added_paths.contains(&p) { added_paths.push(p); }
498 }
499 let p = insert_row(pack, schema, "ceo_initial_data_active_ceos_tables", stem, &row![
501 "initial_data_stage" => &stage2_key,
502 "active_ceo" => &armor_ceo_key,
503 "starting_points_delta" => "0",
504 "auto_id" => auto_id("ceo_initial_data_active_ceos", &format!("{stage2_key}|{armor_ceo_key}")),
505 ])?;
506 if !added_paths.contains(&p) { added_paths.push(p); }
507 let p = insert_row(pack, schema, "ceo_initial_data_active_ceos_tables", stem, &row![
509 "initial_data_stage" => &stage1_key,
510 "active_ceo" => "3k_main_ceo_class_water",
511 "starting_points_delta" => "0",
512 "auto_id" => auto_id("ceo_initial_data_active_ceos", &format!("{stage1_key}|3k_main_ceo_class_water")),
513 ])?;
514 if !added_paths.contains(&p) { added_paths.push(p); }
515 let p = insert_row(pack, schema, "ceo_effect_list_to_effects_tables", stem, &row![
517 "effect_list" => &effect_list_armor,
518 "effect" => "3k_main_effect_character_attribute_cunning_mod",
519 "value" => "18",
520 "effect_scope" => "character_to_character_own",
521 "optional_only_in_game_mode" => "",
522 "auto_id" => auto_id("ceo_effect_list_to_effects", &format!("{effect_list_armor}|cunning")),
523 ])?;
524 if !added_paths.contains(&p) { added_paths.push(p); }
525 for (id_stage, stage_num) in &[
526 ("3k_main_ceo_initial_data_stage_character_childhood_water", 17i32),
527 ("3k_main_ceo_initial_data_equipment_permissions_unique_water", 4i32),
528 ] {
529 let p = insert_row(pack, schema, "ceo_initial_data_to_stages_tables", stem, &row![
530 "ceo_initial_data" => &initial_data_key,
531 "initial_data_stage" => *id_stage,
532 "stage" => stage_num,
533 ])?;
534 if !added_paths.contains(&p) { added_paths.push(p); }
535 }
536 },
537 "earth" => {
538 for scripted in &[
539 "3k_main_ceo_permissions_ancillary_weapon_character_axe_dual_enable",
540 "3k_main_ceo_permissions_ancillary_weapon_character_axe_one_handed_enable",
541 &format!("3k_main_ceo_permissions_ancillary_armour_character_specific_{n}"),
542 "3k_main_ceo_permissions_ancillary_weapon_character_sword_one_handed_enable",
543 "3k_main_ceo_permissions_ancillary_weapon_character_sword_dual_enable",
544 ] {
545 let p = insert_row(pack, schema, "ceo_initial_data_scripted_permissions_tables", stem, &row![
546 "initial_data_stage" => &stage2_key,
547 "scripted_permissions" => *scripted,
548 "auto_id" => auto_id("ceo_initial_data_scripted_permissions", &format!("{stage2_key}|{scripted}")),
549 ])?;
550 if !added_paths.contains(&p) { added_paths.push(p); }
551 }
552 for (category, equipped_ceo) in &[
553 ("3k_main_ceo_category_ancillary_weapon", "3k_main_ancillary_weapon_single_edged_sword_common"),
554 ("3k_main_ceo_category_ancillary_mount", "3k_main_ancillary_mount_white_horse"),
555 ("3k_main_ceo_category_ancillary_armour", armor_ceo_key.as_str()),
556 ] {
557 let p = insert_row(pack, schema, "ceo_initial_data_equipments_tables", stem, &row![
558 "initial_data_stage" => &stage2_key,
559 "category" => *category,
560 "equipped_ceo" => *equipped_ceo,
561 "slot_index" => "0",
562 "target" => "character_equipment",
563 "auto_id" => auto_id("ceo_initial_data_equipments", &format!("{stage2_key}|{equipped_ceo}")),
564 ])?;
565 if !added_paths.contains(&p) { added_paths.push(p); }
566 }
567 for active_ceo in &[
568 "3k_main_ancillary_weapon_one_handed_axe_common",
569 "3k_main_ancillary_weapon_dual_swords_common",
570 "3k_main_ancillary_mount_white_horse",
571 "3k_main_ancillary_weapon_single_edged_sword_common",
572 "3k_main_ancillary_weapon_double_edged_sword_common",
573 ] {
574 let p = insert_row(pack, schema, "ceo_initial_data_active_ceos_tables", stem, &row![
575 "initial_data_stage" => &stage2_key,
576 "active_ceo" => *active_ceo,
577 "starting_points_delta" => "0",
578 "auto_id" => auto_id("ceo_initial_data_active_ceos", &format!("{stage2_key}|{active_ceo}")),
579 ])?;
580 if !added_paths.contains(&p) { added_paths.push(p); }
581 }
582 let p = insert_row(pack, schema, "ceo_initial_data_active_ceos_tables", stem, &row![
584 "initial_data_stage" => &stage2_key,
585 "active_ceo" => &armor_ceo_key,
586 "starting_points_delta" => "0",
587 "auto_id" => auto_id("ceo_initial_data_active_ceos", &format!("{stage2_key}|{armor_ceo_key}")),
588 ])?;
589 if !added_paths.contains(&p) { added_paths.push(p); }
590 let p = insert_row(pack, schema, "ceo_initial_data_active_ceos_tables", stem, &row![
592 "initial_data_stage" => &stage1_key,
593 "active_ceo" => "3k_main_ceo_class_earth",
594 "starting_points_delta" => "0",
595 "auto_id" => auto_id("ceo_initial_data_active_ceos", &format!("{stage1_key}|3k_main_ceo_class_earth")),
596 ])?;
597 if !added_paths.contains(&p) { added_paths.push(p); }
598 if entry.expanded {
600 let p = insert_row(pack, schema, "ceo_initial_data_active_ceos_tables", stem, &row![
601 "initial_data_stage" => &stage2_key,
602 "active_ceo" => "3k_main_ancillary_weapon_sword_and_shield_common",
603 "starting_points_delta" => "0",
604 "auto_id" => auto_id("ceo_initial_data_active_ceos", &format!("{stage2_key}|3k_main_ancillary_weapon_sword_and_shield_common")),
605 ])?;
606 if !added_paths.contains(&p) { added_paths.push(p); }
607 }
608 let p = insert_row(pack, schema, "ceo_effect_list_to_effects_tables", stem, &row![
610 "effect_list" => &effect_list_armor,
611 "effect" => "3k_main_effect_character_attribute_authority_mod",
612 "value" => "18",
613 "effect_scope" => "character_to_character_own",
614 "optional_only_in_game_mode" => "",
615 "auto_id" => auto_id("ceo_effect_list_to_effects", &format!("{effect_list_armor}|authority")),
616 ])?;
617 if !added_paths.contains(&p) { added_paths.push(p); }
618 for (id_stage, stage_num) in &[
619 ("3k_main_ceo_initial_data_stage_character_childhood_earth", 17i32),
620 ("3k_main_ceo_initial_data_equipment_permissions_unique_earth", 4i32),
621 ] {
622 let p = insert_row(pack, schema, "ceo_initial_data_to_stages_tables", stem, &row![
623 "ceo_initial_data" => &initial_data_key,
624 "initial_data_stage" => *id_stage,
625 "stage" => stage_num,
626 ])?;
627 if !added_paths.contains(&p) { added_paths.push(p); }
628 }
629 },
630 "fire" => {
631 for scripted in &[
632 "3k_main_ceo_permissions_ancillary_weapon_character_axe_two_handed_enable",
633 "3k_main_ceo_permissions_ancillary_weapon_character_spear_two_handed_long_enable",
634 &format!("3k_main_ceo_permissions_ancillary_armour_character_specific_{n}"),
635 "3k_main_ceo_permissions_ancillary_weapon_character_spear_two_handed_enable",
636 "3k_main_ceo_permissions_ancillary_weapon_character_axe_two_handed_enable",
637 ] {
638 let p = insert_row(pack, schema, "ceo_initial_data_scripted_permissions_tables", stem, &row![
639 "initial_data_stage" => &stage2_key,
640 "scripted_permissions" => *scripted,
641 "auto_id" => auto_id("ceo_initial_data_scripted_permissions", &format!("{stage2_key}|{scripted}")),
642 ])?;
643 if !added_paths.contains(&p) { added_paths.push(p); }
644 }
645 for (category, equipped_ceo) in &[
646 ("3k_main_ceo_category_ancillary_mount", "3k_main_ancillary_mount_red_horse"),
647 ("3k_main_ceo_category_ancillary_weapon", "3k_main_ancillary_weapon_two_handed_spear_common"),
648 ("3k_main_ceo_category_ancillary_armour", armor_ceo_key.as_str()),
649 ] {
650 let p = insert_row(pack, schema, "ceo_initial_data_equipments_tables", stem, &row![
651 "initial_data_stage" => &stage2_key,
652 "category" => *category,
653 "equipped_ceo" => *equipped_ceo,
654 "slot_index" => "0",
655 "target" => "character_equipment",
656 "auto_id" => auto_id("ceo_initial_data_equipments", &format!("{stage2_key}|{equipped_ceo}")),
657 ])?;
658 if !added_paths.contains(&p) { added_paths.push(p); }
659 }
660 for active_ceo in &[
661 "3k_main_ancillary_weapon_hook_sickle_sabre_common",
662 "3k_main_ancillary_weapon_two_handed_axe_common",
663 "3k_main_ancillary_weapon_two_handed_spear_common",
664 "3k_main_ancillary_weapon_halberd_common",
665 "3k_main_ancillary_mount_red_horse",
666 ] {
667 let p = insert_row(pack, schema, "ceo_initial_data_active_ceos_tables", stem, &row![
668 "initial_data_stage" => &stage2_key,
669 "active_ceo" => *active_ceo,
670 "starting_points_delta" => "0",
671 "auto_id" => auto_id("ceo_initial_data_active_ceos", &format!("{stage2_key}|{active_ceo}")),
672 ])?;
673 if !added_paths.contains(&p) { added_paths.push(p); }
674 }
675 let p = insert_row(pack, schema, "ceo_initial_data_active_ceos_tables", stem, &row![
677 "initial_data_stage" => &stage2_key,
678 "active_ceo" => &armor_ceo_key,
679 "starting_points_delta" => "0",
680 "auto_id" => auto_id("ceo_initial_data_active_ceos", &format!("{stage2_key}|{armor_ceo_key}")),
681 ])?;
682 if !added_paths.contains(&p) { added_paths.push(p); }
683 let p = insert_row(pack, schema, "ceo_initial_data_active_ceos_tables", stem, &row![
685 "initial_data_stage" => &stage1_key,
686 "active_ceo" => "3k_main_ceo_class_fire",
687 "starting_points_delta" => "0",
688 "auto_id" => auto_id("ceo_initial_data_active_ceos", &format!("{stage1_key}|3k_main_ceo_class_fire")),
689 ])?;
690 if !added_paths.contains(&p) { added_paths.push(p); }
691 let p = insert_row(pack, schema, "ceo_effect_list_to_effects_tables", stem, &row![
693 "effect_list" => &effect_list_armor,
694 "effect" => "3k_main_effect_character_attribute_instinct_mod",
695 "value" => "18",
696 "effect_scope" => "character_to_character_own",
697 "optional_only_in_game_mode" => "",
698 "auto_id" => auto_id("ceo_effect_list_to_effects", &format!("{effect_list_armor}|instinct")),
699 ])?;
700 if !added_paths.contains(&p) { added_paths.push(p); }
701 for (id_stage, stage_num) in &[
702 ("3k_main_ceo_initial_data_stage_character_childhood_fire", 17i32),
703 ("3k_main_ceo_initial_data_equipment_permissions_unique_fire", 4i32),
704 ] {
705 let p = insert_row(pack, schema, "ceo_initial_data_to_stages_tables", stem, &row![
706 "ceo_initial_data" => &initial_data_key,
707 "initial_data_stage" => *id_stage,
708 "stage" => stage_num,
709 ])?;
710 if !added_paths.contains(&p) { added_paths.push(p); }
711 }
712 },
713 _ => { for scripted in &[
715 &format!("3k_main_ceo_permissions_ancillary_armour_character_specific_{n}"),
716 "3k_main_ceo_permissions_ancillary_weapon_character_spear_two_handed_enable",
717 "3k_main_ceo_permissions_ancillary_weapon_character_spear_two_handed_long_enable",
718 "3k_ytr_ceo_permissions_ancillary_weapon_character_staff_two_handed_enable",
719 "3k_ytr_ceo_permissions_ancillary_weapon_character_mace_two_handed_enable",
720 ] {
721 let p = insert_row(pack, schema, "ceo_initial_data_scripted_permissions_tables", stem, &row![
722 "initial_data_stage" => &stage2_key,
723 "scripted_permissions" => *scripted,
724 "auto_id" => auto_id("ceo_initial_data_scripted_permissions", &format!("{stage2_key}|{scripted}")),
725 ])?;
726 if !added_paths.contains(&p) { added_paths.push(p); }
727 }
728 for (category, equipped_ceo) in &[
729 ("3k_main_ceo_category_ancillary_mount", "3k_main_ancillary_mount_brown_horse"),
730 ("3k_main_ceo_category_ancillary_weapon", "3k_main_ancillary_weapon_two_handed_spear_common"),
731 ("3k_main_ceo_category_ancillary_armour", armor_ceo_key.as_str()),
732 ] {
733 let p = insert_row(pack, schema, "ceo_initial_data_equipments_tables", stem, &row![
734 "initial_data_stage" => &stage2_key,
735 "category" => *category,
736 "equipped_ceo" => *equipped_ceo,
737 "slot_index" => "0",
738 "target" => "character_equipment",
739 "auto_id" => auto_id("ceo_initial_data_equipments", &format!("{stage2_key}|{equipped_ceo}")),
740 ])?;
741 if !added_paths.contains(&p) { added_paths.push(p); }
742 }
743 for active_ceo in &[
744 "3k_ytr_ancillary_weapon_2h_ball_mace_common",
745 "3k_main_ancillary_weapon_hook_sickle_sabre_common",
746 "3k_main_ancillary_mount_brown_horse",
747 "3k_main_ancillary_weapon_two_handed_spear_common",
748 "3k_main_ancillary_weapon_halberd_common",
749 ] {
750 let p = insert_row(pack, schema, "ceo_initial_data_active_ceos_tables", stem, &row![
751 "initial_data_stage" => &stage2_key,
752 "active_ceo" => *active_ceo,
753 "starting_points_delta" => "0",
754 "auto_id" => auto_id("ceo_initial_data_active_ceos", &format!("{stage2_key}|{active_ceo}")),
755 ])?;
756 if !added_paths.contains(&p) { added_paths.push(p); }
757 }
758 let p = insert_row(pack, schema, "ceo_initial_data_active_ceos_tables", stem, &row![
760 "initial_data_stage" => &stage2_key,
761 "active_ceo" => &armor_ceo_key,
762 "starting_points_delta" => "0",
763 "auto_id" => auto_id("ceo_initial_data_active_ceos", &format!("{stage2_key}|{armor_ceo_key}")),
764 ])?;
765 if !added_paths.contains(&p) { added_paths.push(p); }
766 let p = insert_row(pack, schema, "ceo_initial_data_active_ceos_tables", stem, &row![
768 "initial_data_stage" => &stage1_key,
769 "active_ceo" => "3k_main_ceo_class_wood",
770 "starting_points_delta" => "0",
771 "auto_id" => auto_id("ceo_initial_data_active_ceos", &format!("{stage1_key}|3k_main_ceo_class_wood")),
772 ])?;
773 if !added_paths.contains(&p) { added_paths.push(p); }
774 let p = insert_row(pack, schema, "ceo_effect_list_to_effects_tables", stem, &row![
776 "effect_list" => &effect_list_armor,
777 "effect" => "3k_main_effect_character_attribute_resolve_mod",
778 "value" => "18",
779 "effect_scope" => "character_to_character_own",
780 "optional_only_in_game_mode" => "",
781 "auto_id" => auto_id("ceo_effect_list_to_effects", &format!("{effect_list_armor}|resolve")),
782 ])?;
783 if !added_paths.contains(&p) { added_paths.push(p); }
784 for (id_stage, stage_num) in &[
785 ("3k_main_ceo_initial_data_stage_character_childhood_wood", 17i32),
786 ("3k_main_ceo_initial_data_equipment_permissions_unique_wood", 4i32),
787 ] {
788 let p = insert_row(pack, schema, "ceo_initial_data_to_stages_tables", stem, &row![
789 "ceo_initial_data" => &initial_data_key,
790 "initial_data_stage" => *id_stage,
791 "stage" => stage_num,
792 ])?;
793 if !added_paths.contains(&p) { added_paths.push(p); }
794 }
795 },
796 }
797 let gender_stage = if gender == "male" {
799 "3k_main_ceo_initial_data_stage_character_gender_male"
800 } else {
801 "3k_main_ceo_initial_data_stage_character_gender_female"
802 };
803 let p = insert_row(pack, schema, "ceo_initial_data_to_stages_tables", stem, &row![
804 "ceo_initial_data" => &initial_data_key,
805 "initial_data_stage" => gender_stage,
806 "stage" => "13",
807 ])?;
808 if !added_paths.contains(&p) { added_paths.push(p); }
809
810 let p = insert_row(pack, schema, "ceo_initial_data_to_stages_tables", stem, &row![
814 "ceo_initial_data" => &initial_data_key,
815 "initial_data_stage" => &stage2_key,
816 "stage" => "3",
817 ])?;
818 if !added_paths.contains(&p) { added_paths.push(p); }
819
820 let p = insert_row(pack, schema, "ceo_initial_data_active_ceos_tables", stem, &row![
821 "initial_data_stage" => &stage1_key,
822 "active_ceo" => &format!("3k_main_ceo_career_historical_{n}"),
823 "starting_points_delta" => "0",
824 "auto_id" => auto_id("ceo_initial_data_active_ceos", &format!("{stage1_key}|3k_main_ceo_career_historical_{n}")),
825 ])?;
826 if !added_paths.contains(&p) { added_paths.push(p); }
827
828 for (id_stage, stage_num) in &[
829 ("3k_main_initial_data_character_ancillaries_global", 2i32),
830 ("3k_dlc04_ceo_initial_data_character_give_political_support_random", 21i32),
831 ("3k_main_ceo_initial_data_stage_character_wealth_random", 15i32),
832 ("3k_main_ceo_initial_data_stage_character_traits_shared_global_permissions", 10i32),
833 ("3k_main_ceo_initial_data_stage_character_protagonist", 14i32),
834 ] {
835 let p = insert_row(pack, schema, "ceo_initial_data_to_stages_tables", stem, &row![
836 "ceo_initial_data" => &initial_data_key,
837 "initial_data_stage" => *id_stage,
838 "stage" => stage_num,
839 ])?;
840 if !added_paths.contains(&p) { added_paths.push(p); }
841 }
842 let p = insert_row(pack, schema, "ceo_initial_data_to_stages_tables", stem, &row![
843 "ceo_initial_data" => &initial_data_key,
844 "initial_data_stage" => &stage1_key,
845 "stage" => "11",
846 ])?;
847 if !added_paths.contains(&p) { added_paths.push(p); }
848
849 for (ceo_ref, key_val) in &[
851 (format!("3k_main_ceo_career_historical_{n}"), format!("3k_main_ceo_career_historical_{n}")),
852 (format!("3k_main_ancilliary_armour_{n}_armour_unique"), format!("3k_main_ancilliary_armour_{n}_armour_unique")),
853 ] {
854 let p = insert_row(pack, schema, "ceo_thresholds_tables", stem, &row![
855 "key" => key_val,
856 "ceo" => ceo_ref,
857 "point_threshold_to_activate" => "1",
858 "point_theshold_to_destroy" => "0",
859 "starting_points" => "1",
860 "max_points" => "1",
861 "resets_to_starting_points_when_deactivated" => "false",
862 ])?;
863 if !added_paths.contains(&p) { added_paths.push(p); }
864 }
865
866 let p = insert_row(pack, schema, "ceo_nodes_tables", stem, &row![
868 "key" => format!("3k_main_ceo_career_historical_{n}"),
869 "ceo_effect_list" => &effect_list_career,
870 "title" => "placeholder",
871 "description" => "placeholder",
872 "icon_path" => "",
873 "opinion_topic_modifier" => "",
874 "point_change_per_turn_if_active" => "0",
875 ])?;
876 if !added_paths.contains(&p) { added_paths.push(p); }
877 let armour_icon = format!("armours/3k_main_ancillary_{}_armour_unique.png", element);
879 let p = insert_row(pack, schema, "ceo_nodes_tables", stem, &row![
880 "key" => format!("3k_main_ancilliary_armour_{n}_armour_unique"),
881 "ceo_effect_list" => &effect_list_armor,
882 "title" => "placeholder",
883 "description" => "placeholder",
884 "icon_path" => &armour_icon,
885 "opinion_topic_modifier" => "",
886 "point_change_per_turn_if_active" => "0",
887 ])?;
888 if !added_paths.contains(&p) { added_paths.push(p); }
889
890 for (node_key, threshold_key) in &[
892 (format!("3k_main_ceo_career_historical_{n}"), format!("3k_main_ceo_career_historical_{n}")),
893 (format!("3k_main_ancilliary_armour_{n}_armour_unique"), format!("3k_main_ancilliary_armour_{n}_armour_unique")),
894 ] {
895 let p = insert_row(pack, schema, "ceo_threshold_nodes_tables", stem, &row![
896 "ceo_threshold" => threshold_key,
897 "ceo_node" => node_key,
898 "points_threshold_to_activate_node" => "1",
899 "can_downgrade_to_previous_node" => "false",
900 "auto_id" => auto_id("ceo_threshold_nodes", &format!("{threshold_key}|{node_key}")),
901 ])?;
902 if !added_paths.contains(&p) { added_paths.push(p); }
903 }
904
905 let p = insert_row(pack, schema, "ceo_effect_list_to_effects_tables", stem, &row![
907 "effect_list" => &effect_list_armor,
908 "effect" => "3k_dummy_effect_ceo_subcategory_armour_unique",
909 "value" => "0",
910 "effect_scope" => "character_to_character_own",
911 "optional_only_in_game_mode" => "",
912 "auto_id" => auto_id("ceo_effect_list_to_effects", &format!("{effect_list_armor}|dummy_subcategory")),
913 ])?;
914 if !added_paths.contains(&p) { added_paths.push(p); }
915
916 for (effect, val) in &[
918 ("3k_main_character_wealth", "2"),
919 ("3k_main_effect_character_num_lives", "1"),
920 ] {
921 let p = insert_row(pack, schema, "ceo_effect_list_to_effects_tables", stem, &row![
922 "effect_list" => &effect_list_career,
923 "effect" => *effect,
924 "value" => *val,
925 "effect_scope" => "character_to_character_own",
926 "optional_only_in_game_mode" => "",
927 "auto_id" => auto_id("ceo_effect_list_to_effects", &format!("{effect_list_career}|{effect}")),
928 ])?;
929 if !added_paths.contains(&p) { added_paths.push(p); }
930 }
931
932 for (trait_uuid, trait_key) in &entry.traits {
934 let p = insert_row(pack, schema, "ceo_initial_data_active_ceos_tables", stem, &row![
935 "initial_data_stage" => &stage1_key,
936 "active_ceo" => trait_key.as_str(),
937 "starting_points_delta" => "0",
938 "auto_id" => auto_id("ceo_initial_data_active_ceos", &format!("{stage1_key}|{trait_key}|{trait_uuid}")),
939 ])?;
940 if !added_paths.contains(&p) { added_paths.push(p); }
941 }
942
943 let p = insert_row(pack, schema, "ceos_to_equipment_variants_tables", stem, &row![
945 "ceos_key" => &format!("3k_main_ancilliary_armour_{n}_armour_unique"),
946 "game_mode" => "",
947 "armour" => "3k_ytr_hero_scholar_unique",
948 "male_vmd" => "",
949 "female_vmd" => "",
950 "mount" => "",
951 "primary_melee_weapon" => "",
952 "primary_missile_weapon" => "",
953 "shield" => "",
954 "man_animation" => "",
955 "mount_animation" => "",
956 "secondary_weapon_animation" => "",
957 "remap_general_unit_to_hero_unit" => "false",
958 "priority" => "1",
959 "autonomous_rider_group" => "",
960 "ground_type_stat_effect_group" => "",
961 ])?;
962 if !added_paths.contains(&p) { added_paths.push(p); }
963
964 let p = insert_row(pack, schema, "ceo_template_manager_ceo_limits_tables", stem, &row![
966 "ceo_to_limit" => &format!("3k_main_ancilliary_armour_{n}_armour_unique"),
967 "template_manager" => "3k_main_ceo_template_manager_world_generic",
968 "max_limit_that_can_exist_at_once" => "1",
969 "scoped_limit_or_local_only_limit" => "true",
970 "ceo_category_to_limit" => "",
971 "ceo_node_to_limit" => "",
972 "auto_id" => auto_id("ceo_template_manager_ceo_limits", &format!("3k_main_ancilliary_armour_{n}_armour_unique")),
973 ])?;
974 if !added_paths.contains(&p) { added_paths.push(p); }
975
976 let human = n.replace("_ironic", "")
978 .split('_')
979 .map(|w: &str| {
980 let mut c = w.chars();
981 c.next().map(|f: char| f.to_uppercase().collect::<String>() + c.as_str()).unwrap_or_default()
982 })
983 .collect::<Vec<_>>()
984 .join(" ");
985 loc_entries.push((format!("ceo_nodes_title_3k_main_ceo_career_historical_{n}"), "PLACEHOLDER".into()));
986 loc_entries.push((format!("ceo_nodes_description_3k_main_ceo_career_historical_{n}"), "PLACEHOLDER".into()));
987 loc_entries.push((format!("ceo_nodes_title_3k_main_ancilliary_armour_{n}_armour_unique"), format!("{human}'s Armour")));
988 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()));
989
990 } else {
991 let p = insert_row(pack, schema, "ceos_tables", stem, &row![
995 "key" => format!("3k_main_ceo_career_historical_{n}"),
996 "exists_in_location" => "character_ceo_manager",
997 "category" => "3k_main_ceo_category_career",
998 "equipped_in_location" => "character_equipment",
999 "priority" => "1",
1000 "turns_to_expire" => "0",
1001 "point_change_per_turn_if_inactive" => "0",
1002 "point_change_per_turn_while_active" => "0",
1003 "point_change_per_turn_while_equipped" => "0",
1004 "inheritance_chance" => "0",
1005 "can_be_looted_post_battle" => "false",
1006 "can_be_traded_in_diplomacy" => "false",
1007 "can_be_stolen" => "false",
1008 "rarity" => "common",
1009 "can_be_unequipped" => "false",
1010 "can_be_transferred_if_equipped" => "true",
1011 "cannot_reequip_until_next_round_if_unequipped" => "true",
1012 "provides_scripted_permissions_on_spawn" => "",
1013 ])?;
1014 if !added_paths.contains(&p) { added_paths.push(p); }
1015
1016 let career_key_title = format!("3k_main_ceo_career_historical_{n}");
1018 let p = insert_row(pack, schema, "ceo_group_ceos_tables", stem, &row![
1019 "ceo_group" => "3k_main_ceo_group_career_all",
1020 "ceo" => &career_key_title,
1021 "trigger_weighting" => "1",
1022 "auto_id" => auto_id("ceo_group_ceos", &format!("3k_main_ceo_group_career_all|{career_key_title}")),
1023 ])?;
1024 if !added_paths.contains(&p) { added_paths.push(p); }
1025
1026 let stage1_key = format!("3k_main_ceo_initial_data_stage_character_traits_historical_{n}");
1027 let p = insert_row(pack, schema, "ceo_initial_data_stages_tables", stem, &row![
1028 "key" => &stage1_key,
1029 ])?;
1030 if !added_paths.contains(&p) { added_paths.push(p); }
1031
1032 let effect_list_key = format!("3k_main_ceo_career_historical_{n}");
1033 let p = insert_row(pack, schema, "ceo_effect_lists_tables", stem, &row![
1034 "key" => &effect_list_key,
1035 ])?;
1036 if !added_paths.contains(&p) { added_paths.push(p); }
1037
1038 let initial_data_key = format!("3k_main_ceo_initial_data_character_historical_{n}");
1039 let p = insert_row(pack, schema, "ceo_initial_datas_tables", stem, &row![
1040 "key" => &initial_data_key,
1041 "template_manager" => "character_ceo_manager",
1042 ])?;
1043 if !added_paths.contains(&p) { added_paths.push(p); }
1044
1045 let (childhood_stage, equipment_stage, class_ceo, generic_stage, generic_stage_num) = match element.as_str() {
1047 "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),
1048 "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),
1049 "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),
1050 "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),
1051 _ => ("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),
1052 };
1053
1054 for (id_stage, stage_num) in &[
1055 (childhood_stage, 17i32),
1056 (equipment_stage, 4i32),
1057 ] {
1058 let p = insert_row(pack, schema, "ceo_initial_data_to_stages_tables", stem, &row![
1059 "ceo_initial_data" => &initial_data_key,
1060 "initial_data_stage" => *id_stage,
1061 "stage" => stage_num,
1062 ])?;
1063 if !added_paths.contains(&p) { added_paths.push(p); }
1064 }
1065 let p = insert_row(pack, schema, "ceo_initial_data_active_ceos_tables", stem, &row![
1066 "initial_data_stage" => &stage1_key,
1067 "active_ceo" => class_ceo,
1068 "starting_points_delta" => "0",
1069 "auto_id" => auto_id("ceo_initial_data_active_ceos", &format!("{stage1_key}|{class_ceo}")),
1070 ])?;
1071 if !added_paths.contains(&p) { added_paths.push(p); }
1072 let p = insert_row(pack, schema, "ceo_initial_data_to_stages_tables", stem, &row![
1073 "ceo_initial_data" => &initial_data_key,
1074 "initial_data_stage" => generic_stage,
1075 "stage" => &generic_stage_num,
1076 ])?;
1077 if !added_paths.contains(&p) { added_paths.push(p); }
1078
1079 let gender_stage = if gender == "male" {
1081 "3k_main_ceo_initial_data_stage_character_gender_male"
1082 } else {
1083 "3k_main_ceo_initial_data_stage_character_gender_female"
1084 };
1085 let p = insert_row(pack, schema, "ceo_initial_data_to_stages_tables", stem, &row![
1086 "ceo_initial_data" => &initial_data_key,
1087 "initial_data_stage" => gender_stage,
1088 "stage" => "13",
1089 ])?;
1090 if !added_paths.contains(&p) { added_paths.push(p); }
1091
1092 let career_key = format!("3k_main_ceo_career_historical_{n}");
1094 let p = insert_row(pack, schema, "ceo_initial_data_active_ceos_tables", stem, &row![
1095 "initial_data_stage" => &stage1_key,
1096 "active_ceo" => &career_key,
1097 "starting_points_delta" => "0",
1098 "auto_id" => auto_id("ceo_initial_data_active_ceos", &format!("{stage1_key}|{career_key}")),
1099 ])?;
1100 if !added_paths.contains(&p) { added_paths.push(p); }
1101
1102 for (id_stage, stage_num) in &[
1103 ("3k_main_initial_data_character_ancillaries_global", 2i32),
1104 ("3k_dlc04_ceo_initial_data_character_give_political_support_random", 21i32),
1105 ("3k_main_ceo_initial_data_stage_character_wealth_random", 15i32),
1106 ("3k_main_ceo_initial_data_stage_character_traits_shared_global_permissions", 10i32),
1107 ("3k_main_ceo_initial_data_stage_character_protagonist", 14i32),
1108 ] {
1109 let p = insert_row(pack, schema, "ceo_initial_data_to_stages_tables", stem, &row![
1110 "ceo_initial_data" => &initial_data_key,
1111 "initial_data_stage" => *id_stage,
1112 "stage" => stage_num,
1113 ])?;
1114 if !added_paths.contains(&p) { added_paths.push(p); }
1115 }
1116 let p = insert_row(pack, schema, "ceo_initial_data_to_stages_tables", stem, &row![
1117 "ceo_initial_data" => &initial_data_key,
1118 "initial_data_stage" => &stage1_key,
1119 "stage" => "11",
1120 ])?;
1121 if !added_paths.contains(&p) { added_paths.push(p); }
1122
1123 let p = insert_row(pack, schema, "ceo_thresholds_tables", stem, &row![
1125 "key" => &career_key,
1126 "ceo" => &career_key,
1127 "point_threshold_to_activate" => "1",
1128 "point_theshold_to_destroy" => "0",
1129 "starting_points" => "1",
1130 "max_points" => "1",
1131 "resets_to_starting_points_when_deactivated" => "false",
1132 ])?;
1133 if !added_paths.contains(&p) { added_paths.push(p); }
1134
1135 let p = insert_row(pack, schema, "ceo_nodes_tables", stem, &row![
1137 "key" => &career_key,
1138 "ceo_effect_list" => &effect_list_key,
1139 "title" => "placeholder",
1140 "description" => "placeholder",
1141 "icon_path" => "",
1142 "opinion_topic_modifier" => "",
1143 "point_change_per_turn_if_active" => "0",
1144 ])?;
1145 if !added_paths.contains(&p) { added_paths.push(p); }
1146
1147 let p = insert_row(pack, schema, "ceo_threshold_nodes_tables", stem, &row![
1149 "ceo_threshold" => &career_key,
1150 "ceo_node" => &career_key,
1151 "points_threshold_to_activate_node" => "1",
1152 "can_downgrade_to_previous_node" => "false",
1153 "auto_id" => auto_id("ceo_threshold_nodes", &format!("{career_key}|{career_key}")),
1154 ])?;
1155 if !added_paths.contains(&p) { added_paths.push(p); }
1156
1157 for (effect, val) in &[
1159 ("3k_main_character_wealth", "2"),
1160 ("3k_main_effect_character_num_lives", "1"),
1161 ] {
1162 let p = insert_row(pack, schema, "ceo_effect_list_to_effects_tables", stem, &row![
1163 "effect_list" => &effect_list_key,
1164 "effect" => *effect,
1165 "value" => *val,
1166 "effect_scope" => "character_to_character_own",
1167 "optional_only_in_game_mode" => "",
1168 "auto_id" => auto_id("ceo_effect_list_to_effects", &format!("{effect_list_key}|{effect}")),
1169 ])?;
1170 if !added_paths.contains(&p) { added_paths.push(p); }
1171 }
1172
1173 for (trait_uuid, trait_key) in &entry.traits {
1175 let p = insert_row(pack, schema, "ceo_initial_data_active_ceos_tables", stem, &row![
1176 "initial_data_stage" => &stage1_key,
1177 "active_ceo" => trait_key.as_str(),
1178 "starting_points_delta" => "0",
1179 "auto_id" => auto_id("ceo_initial_data_active_ceos", &format!("{stage1_key}|{trait_key}|{trait_uuid}")),
1180 ])?;
1181 if !added_paths.contains(&p) { added_paths.push(p); }
1182 }
1183
1184 loc_entries.push((format!("ceo_nodes_title_3k_main_ceo_career_historical_{n}"), "PLACEHOLDER".into()));
1186 loc_entries.push((format!("ceo_nodes_description_3k_main_ceo_career_historical_{n}"), "PLACEHOLDER".into()));
1187 }
1188 }
1189
1190 if !loc_entries.is_empty() {
1192 let loc_pairs: Vec<(&str, &str)> = loc_entries.iter()
1193 .map(|(k, v)| (k.as_str(), v.as_str()))
1194 .collect();
1195 let p = insert_loc_entries(pack, &loc_path, &loc_pairs)?;
1196 if !added_paths.contains(&p) { added_paths.push(p); }
1197 }
1198
1199 Ok(added_paths)
1200}
1201
1202pub fn get_trait_ceos(deps: &rpfm_extensions::dependencies::Dependencies) -> Vec<(String, String)> {
1204 let ak_tables = deps.asskit_only_db_tables();
1205 let mut trait_ceos: Vec<(String, String)> = Vec::new();
1206
1207 let trait_categories: HashSet<&str> = [
1208 "3k_main_ceo_category_traits_personality",
1209 "3k_main_ceo_category_traits_physical",
1210 ].iter().copied().collect();
1211
1212 info!("GetTraitCeos: AK tables available: {}", ak_tables.len());
1213
1214 fn ak_lookup_pairs(ak_tables: &HashMap<String, DB>, table_name: &str, col_a: &str, col_b: &str) -> Vec<(String, String)> {
1216 let mut result = Vec::new();
1217 if let Some(db) = ak_tables.get(table_name) {
1218 let fields = db.definition().fields_processed();
1219 let a_idx = fields.iter().position(|f| f.name() == col_a);
1220 let b_idx = fields.iter().position(|f| f.name() == col_b);
1221 if let (Some(ai), Some(bi)) = (a_idx, b_idx) {
1222 for row in db.data().iter() {
1223 result.push((row[ai].data_to_string().to_string(), row[bi].data_to_string().to_string()));
1224 }
1225 }
1226 }
1227 result
1228 }
1229
1230 if let Some(ceos_db) = ak_tables.get("ceos_tables") {
1232 let fields = ceos_db.definition().fields_processed();
1233 let key_idx = fields.iter().position(|f| f.name() == "key");
1234 let cat_idx = fields.iter().position(|f| f.name() == "category");
1235
1236 if let (Some(ki), Some(ci)) = (key_idx, cat_idx) {
1237 for row in ceos_db.data().iter() {
1238 let category = row[ci].data_to_string();
1239 if trait_categories.contains(&*category) {
1240 let ceo_key = row[ki].data_to_string().to_string();
1241 trait_ceos.push((ceo_key, String::new()));
1242 }
1243 }
1244 }
1245 }
1246
1247 info!("GetTraitCeos: found {} trait CEOs from AK ceos_tables", trait_ceos.len());
1248
1249 let ceo_to_threshold: HashMap<String, String> =
1251 ak_lookup_pairs(ak_tables, "ceo_thresholds_tables", "ceo", "key")
1252 .into_iter().collect();
1253
1254 let threshold_to_node: HashMap<String, String> =
1255 ak_lookup_pairs(ak_tables, "ceo_threshold_nodes_tables", "ceo_threshold", "ceo_node")
1256 .into_iter().collect();
1257
1258 let node_to_title: HashMap<String, String> =
1259 ak_lookup_pairs(ak_tables, "ceo_nodes_tables", "key", "title")
1260 .into_iter().collect();
1261
1262 let mut loc_lookup: HashMap<String, String> = HashMap::new();
1264 if let Ok(loc_files) = deps.loc_data(true, true) {
1265 for rfile in &loc_files {
1266 if let Ok(RFileDecoded::Loc(loc)) = rfile.decoded() {
1267 for row in loc.data().iter() {
1268 if row.len() >= 2 {
1269 let loc_key = row[0].data_to_string().to_string();
1270 let loc_val = row[1].data_to_string().to_string();
1271 loc_lookup.insert(loc_key, loc_val);
1272 }
1273 }
1274 }
1275 }
1276 }
1277
1278 info!("GetTraitCeos: loc_lookup has {} entries, node_to_title has {} entries", loc_lookup.len(), node_to_title.len());
1279
1280 for (ceo_key, display_name) in &mut trait_ceos {
1282 let resolved = ceo_to_threshold.get(ceo_key.as_str())
1284 .and_then(|thresh| threshold_to_node.get(thresh))
1285 .and_then(|node| {
1286 node_to_title.get(node).and_then(|title_key| {
1288 loc_lookup.get(title_key)
1289 .or_else(|| {
1290 let constructed = format!("ceo_nodes_title_{}", node);
1292 loc_lookup.get(&constructed)
1293 })
1294 })
1295 });
1296
1297 if let Some(name) = resolved {
1298 *display_name = name.clone();
1299 } else {
1300 *display_name = ceo_key
1302 .replace("3k_main_ceo_trait_", "")
1303 .replace("3k_dlc", "")
1304 .replace("3k_ytr_ceo_trait_", "")
1305 .replace('_', " ");
1306 }
1307 }
1308
1309 trait_ceos.sort_by(|a, b| a.1.cmp(&b.1));
1311
1312 info!("GetTraitCeos: returning {} traits", trait_ceos.len());
1313
1314 trait_ceos
1315}
1316
1317pub fn build_ceo_post(pack: &mut Pack, akit_path: &str) -> Result<Vec<ContainerPath>> {
1319 let ceo_ccd_path = PathBuf::from(akit_path)
1320 .join(r"working_data\campaigns\ceo_data.ccd");
1321
1322 if !ceo_ccd_path.exists() {
1323 return Err(anyhow!("ceo_data.ccd not found. Make sure BOB ran successfully."));
1324 }
1325
1326 let raw_bytes = std::fs::read(&ceo_ccd_path)
1327 .map_err(|e| anyhow!("Failed to read ceo_data.ccd: {e}"))?;
1328
1329 let mut rfile = RFile::new_from_vec(&raw_bytes, FileType::Unknown, 0, "campaigns/ceo_data.ccd");
1330 let _ = rfile.guess_file_type();
1331 match pack.insert(rfile) {
1332 Ok(Some(path)) => Ok(vec![path]),
1333 Ok(None) => Ok(vec![]),
1334 Err(e) => Err(anyhow!("{}", e)),
1335 }
1336}