rpfm_extensions/startpos/world/
diplomacy.rs1use getset::{CopyGetters, Getters};
21
22use std::collections::{HashMap, HashSet};
23
24use rpfm_lib::error::Result;
25
26use super::CampaignIndex;
27use crate::startpos::region_map::RegionMap;
28use crate::startpos::tables::StartposTables;
29
30#[cfg(test)] #[path = "diplomacy_test.rs"] mod diplomacy_test;
31
32pub const NEUTRAL_STANCE: &str = "neutral";
34
35const WAR_STANCE: &str = "war";
37
38const VASSAL_VALUE: i32 = 20;
41
42#[derive(Debug, Clone, PartialEq, Eq, Getters, CopyGetters)]
48pub struct Relationship {
49
50 #[getset(get = "pub")]
53 stance: String,
54
55 #[getset(get_copy = "pub")]
57 trade: bool,
58
59 #[getset(get_copy = "pub")]
61 non_aggression: bool,
62
63 #[getset(get_copy = "pub")]
65 military_access: bool,
66
67 #[getset(get_copy = "pub")]
69 vassal_values: (i32, i32),
70
71 #[getset(get = "pub")]
73 allied_against: Vec<(String, bool)>,
74
75 #[getset(get_copy = "pub")]
77 known: bool,
78}
79
80#[derive(Debug, Clone, Default)]
82pub struct Diplomacy {
83
84 relationships: HashMap<(String, String), Relationship>,
86}
87
88impl Default for Relationship {
93 fn default() -> Self {
94 Self {
95 stance: NEUTRAL_STANCE.to_owned(),
96 trade: false,
97 non_aggression: false,
98 military_access: false,
99 vassal_values: (0, 0),
100 allied_against: vec![],
101 known: false,
102 }
103 }
104}
105
106impl Diplomacy {
107
108 pub fn new(tables: &StartposTables, index: &CampaignIndex, map: &RegionMap) -> Result<Self> {
120 let mut rows = vec![];
121 for row in tables.table("start_pos_diplomacy")?.rows() {
122 let (Some(first), Some(second)) = (index.faction_by_id(&row.str("faction1")?), index.faction_by_id(&row.str("faction2")?)) else { continue };
123 rows.push((first.key().to_owned(), second.key().to_owned(), row.str("stance")?.into_owned(), row.bool("grants_trade_agreement")?, row.bool("non_aggression_pact")?, row.bool("grants_military_access")?));
124 }
125
126 let vassals = rows.iter().filter(|row| row.2 == "vassal").map(|row| row.0.to_owned()).collect::<HashSet<_>>();
127 let mut diplomacy = Self::default();
128 for (first, second, stance, trade, non_aggression, military_access) in &rows {
129 let of_vassal = stance != "vassal" && (vassals.contains(first) || vassals.contains(second));
130 let (first_stance, second_stance) = match stance.as_str() {
131 "vassal" => ("vassal", "master"),
132 "client_state" => ("client_state", "protector"),
133 other => (other, other),
134 };
135 let vassal = stance == "vassal";
136 for (faction, other, stance) in [(first, second, first_stance), (second, first, second_stance)] {
137 let relationship = diplomacy.entry(faction, other);
138 relationship.stance = stance.to_owned();
139 relationship.trade = *trade;
140 relationship.non_aggression = *non_aggression && !of_vassal;
141 relationship.military_access = *military_access && !of_vassal;
142 relationship.known = true;
143 }
144 if vassal {
145 diplomacy.entry(first, second).vassal_values = (VASSAL_VALUE, 0);
146 diplomacy.entry(second, first).vassal_values = (0, VASSAL_VALUE);
147 }
148 }
149
150 let wars = rows.iter().filter(|row| row.2 == WAR_STANCE).map(|row| (row.0.to_owned(), row.1.to_owned())).collect::<Vec<_>>();
152 let enemies_of = |faction: &str| wars.iter()
153 .filter_map(|(first, second)| if first == faction { Some(second.to_owned()) } else if second == faction { Some(first.to_owned()) } else { None })
154 .collect::<Vec<_>>();
155 for (subject, master, ..) in rows.iter().filter(|row| row.2 == "vassal") {
156 for enemy in enemies_of(master) {
157 if enemy != *subject && !diplomacy.relationships.contains_key(&(subject.to_owned(), enemy.to_owned())) {
158 for (faction, other) in [(subject, &enemy), (&enemy, subject)] {
159 let relationship = diplomacy.entry(faction, other);
160 relationship.stance = WAR_STANCE.to_owned();
161 relationship.known = true;
162 }
163 }
164 }
165 }
166
167 let defensive_allies_of = |faction: &str| rows.iter()
169 .filter(|row| row.2 == "defensive_allies")
170 .filter_map(|row| if row.0 == faction { Some(row.1.to_owned()) } else if row.1 == faction { Some(row.0.to_owned()) } else { None })
171 .collect::<Vec<_>>();
172 for (first, second, ..) in rows.iter().filter(|row| matches!(row.2.as_str(), "vassal" | "client_state" | "military_allies" | "defensive_allies")) {
173 let second_enemies = enemies_of(second).into_iter().collect::<HashSet<_>>();
174 let common = enemies_of(first).into_iter().filter(|enemy| second_enemies.contains(enemy)).collect::<Vec<_>>();
175 let mut against = vec![];
176 for enemy in common {
177 let allies = defensive_allies_of(&enemy);
178 against.push((enemy, false));
179 against.extend(allies.into_iter().map(|ally| (ally, true)));
180 }
181 if !against.is_empty() {
182 diplomacy.entry(first, second).allied_against = against.clone();
183 diplomacy.entry(second, first).allied_against = against;
184 }
185 }
186
187 let owners = index.regions().iter().filter_map(|region| region.owner().as_ref().map(|owner| (region.key().as_str(), owner.as_str()))).collect::<HashMap<_, _>>();
189 for (region, other_region) in map.neighbour_pairs() {
190 if let (Some(owner), Some(other_owner)) = (owners.get(region), owners.get(other_region)) {
191 if owner != other_owner {
192 diplomacy.entry(owner, other_owner).known = true;
193 diplomacy.entry(other_owner, owner).known = true;
194 }
195 }
196 }
197
198 for row in tables.table("start_pos_past_events")?.rows() {
200 let (Some(source), Some(target)) = (index.faction_by_id(&row.str("source")?), index.faction_by_id(&row.str("target")?)) else { continue };
201 if source.key() != target.key() {
202 diplomacy.entry(source.key(), target.key()).known = true;
203 diplomacy.entry(target.key(), source.key()).known = true;
204 }
205 }
206
207 Ok(diplomacy)
208 }
209
210 pub fn relationship(&self, faction: &str, other: &str) -> Relationship {
212 self.relationships.get(&(faction.to_owned(), other.to_owned())).cloned().unwrap_or_default()
213 }
214
215 fn entry(&mut self, faction: &str, other: &str) -> &mut Relationship {
217 self.relationships.entry((faction.to_owned(), other.to_owned())).or_default()
218 }
219}