rpfm_extensions/startpos/world/writer/
family.rs1use std::collections::HashMap;
21
22use rpfm_lib::error::{RLibError, Result};
23use rpfm_lib::files::esf::{NodeType, RecordNode};
24
25use super::{CharacterInfo, CharacterWriter, DetailsSubject};
26use crate::startpos::layout::child_path;
27use crate::startpos::world::{CharacterEntry, FactionEntry};
28use crate::startpos::writer::{bool_node, u32_node};
29
30#[cfg(test)] #[path = "family_test.rs"] mod family_test;
31
32const FAMILY_TREE_PATH: &str = "CAMPAIGN_STARTPOS/CAMPAIGN_ENV/CAMPAIGN_MODEL/WORLD/FAMILY_TREE";
34
35const ADULT_AGE: i64 = 16;
37
38pub(crate) const DEATH_TYPE_CODES: [(&str, i32); 4] = [("KILLED_IN_ACTION", 1), ("OLD_AGE", 3), ("EXECUTED", 5), ("DISEASE", 17)];
40
41#[derive(Debug, Clone, Default)]
47struct Member {
48 mother: u32,
49 father: u32,
50 spouse: u32,
51 former_spouses: Vec<u32>,
52 children: Vec<u32>,
53 adopted_children: Vec<u32>,
54 bastard: bool,
55 adopted: bool,
56}
57
58impl CharacterWriter<'_, '_> {
63
64 pub fn write_family_tree(&self) -> Result<RecordNode> {
70 let index = self.inputs.index;
71 let entries = index.characters().iter().filter_map(|entry| self.entry_family_ids.get(entry.id()).map(|id| (*id, entry))).collect::<HashMap<_, _>>();
72 let members = self.family_links(&entries)?;
73 let count = self.family_ids.iter().chain(self.entry_family_ids.values()).copied().max().unwrap_or_default();
74
75 let mut children = vec![u32_node(count)];
76 for id in 1..=count {
77 let member = members.get(&id).cloned().unwrap_or_default();
78 children.push(NodeType::Record(Box::new(self.write_member(id, entries.get(&id).copied(), &member)?)));
79 }
80
81 children.push(u32_node(0));
83 self.inputs.layout.raw_record(FAMILY_TREE_PATH, children)
84 }
85
86 fn family_links(&self, entries: &HashMap<u32, &CharacterEntry>) -> Result<HashMap<u32, Member>> {
88 let mut members = HashMap::<u32, Member>::new();
89 let is_dead = |id: u32| entries.get(&id).is_some_and(|entry| !entry.death_type().is_empty());
90 for row in self.inputs.tables.table("start_pos_family_relationships")?.rows() {
91 let (Some(&character), Some(&related)) = (self.entry_family_ids.get(row.str("character")?.as_ref()), self.entry_family_ids.get(row.str("related_to")?.as_ref())) else { continue };
92 match row.str("relationship")?.as_ref() {
93 "child" => {
94 let adopted = row.bool("adopted")?;
95 let parent_is_male = entries.get(&related).and_then(|entry| self.rows.get(entry.id())).map(|row| row.bool("is_male")).transpose()?.unwrap_or(true);
96 let member = members.entry(character).or_default();
97 if parent_is_male { member.father = related } else { member.mother = related }
98 member.bastard |= row.bool("bastard")?;
99 member.adopted |= adopted;
100 let parent = members.entry(related).or_default();
101 let list = if adopted { &mut parent.adopted_children } else { &mut parent.children };
102 if !list.contains(&character) {
103 list.push(character);
104 }
105 },
106 "spouse" if is_dead(character) || is_dead(related) => {
107 for (member, other) in [(character, related), (related, character)] {
108 let former = &mut members.entry(member).or_default().former_spouses;
109 if !former.contains(&other) {
110 former.push(other);
111 }
112 }
113 },
114 "spouse" => {
115 members.entry(character).or_default().spouse = related;
116 members.entry(related).or_default().spouse = character;
117 },
118 _ => {},
119 }
120 }
121 Ok(members)
122 }
123
124 fn write_member(&self, id: u32, entry: Option<&CharacterEntry>, member: &Member) -> Result<RecordNode> {
127 let layout = self.inputs.layout;
128 let path = child_path(FAMILY_TREE_PATH, "FAMILY_MEMBER");
129 let date = |year: u32| -> Result<NodeType> {
130 let mut record = layout.builder(&child_path(&path, "DATE"))?;
131 record.group()?.set_u32("^+1", year)?;
132 Ok(NodeType::Record(Box::new(record.build()?)))
133 };
134 let list = |ids: &[u32]| std::iter::once(u32_node(ids.len() as u32)).chain(ids.iter().map(|id| u32_node(*id))).collect::<Vec<_>>();
135
136 let dead = entry.filter(|entry| !entry.death_type().is_empty());
137 let mut children = vec![u32_node(id), bool_node(dead.is_some())];
138 if let Some(entry) = dead {
139 let faction = self.inputs.index.faction(entry.faction()).ok_or_else(|| missing_faction(entry))?;
140 children.push(NodeType::Ascii(faction.key().to_owned()));
141 children.push(NodeType::Record(Box::new(self.write_dead_details(id, entry, faction, &path)?)));
142 }
143
144 let married = member.spouse != 0 || !member.former_spouses.is_empty();
145 let adult = entry.filter(|entry| entry.death_type().is_empty()).is_none_or(|entry| entry.age() >= ADULT_AGE);
146 children.extend([u32_node(0), u32_node(member.mother), u32_node(member.father), u32_node(member.spouse)]);
147 children.push(date(if married { self.start_year } else { 0 })?);
148 children.extend(list(&member.former_spouses));
149 children.extend(list(&member.children));
150 children.extend(list(&member.adopted_children));
151 children.extend([bool_node(false), date(0)?, u32_node(0), bool_node(adult), bool_node(false), bool_node(member.bastard), bool_node(false)]);
152 children.extend([u32_node(0), u32_node(u32::MAX), bool_node(false), bool_node(entry.is_some_and(|entry| entry.progenitor())), bool_node(member.adopted)]);
153 children.push(NodeType::Ascii(String::new()));
154 layout.raw_record(&path, children)
155 }
156
157 fn write_dead_details(&self, family_id: u32, entry: &CharacterEntry, faction: &FactionEntry, member_path: &str) -> Result<RecordNode> {
159 let row = self.rows.get(entry.id());
160 let died_before = row.map(|row| row.str("turns_died_before_start")).transpose()?.and_then(|value| value.parse::<i64>().ok()).unwrap_or_default();
162 let death_year = u32::try_from(i64::from(self.start_year) - died_before).unwrap_or_default();
163 let info = CharacterInfo {
164 agent: entry.agent().to_owned(),
165 is_male: row.map(|row| row.bool("is_male")).transpose()?.unwrap_or(true),
166 birth_year: u32::try_from(i64::from(death_year) - entry.age()).unwrap_or_default(),
167 row,
168 };
169 let code = DEATH_TYPE_CODES.iter().find(|(death_type, _)| *death_type == entry.death_type()).map(|(_, code)| *code).unwrap_or_default();
170 let path = child_path(member_path, "CHARACTER_DETAILS");
171 let subject = DetailsSubject { path: &path, faction, origin: faction, entry: Some(entry), character_id: 0, family_id, death: Some((death_year, code)), bodyguard: None };
172 self.write_details(&subject, &info)
173 }
174}
175
176fn missing_faction(entry: &CharacterEntry) -> RLibError {
178 RLibError::StartposInvalidValue("start_pos_characters".to_owned(), "faction".to_owned(), entry.faction().to_owned())
179}