Skip to main content

canwu_sim/runtime/
legacy_world.rs

1//! Reference-world projection retained for the current runtime facade.
2//!
3//! This type is not a persistence migration path; Format 8 rejects old saves.
4
5use canwu_core::{
6    ArmyId, EntityRef, GovernmentId, LetterId, PersonId, ResourceId, RouteId, TerritoryId,
7};
8use canwu_time::{SimDuration, SimTime};
9use serde::{Deserialize, Serialize};
10
11#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Serialize)]
12pub struct MapPoint {
13    pub x: f32,
14    pub y: f32,
15}
16
17#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
18pub struct Person {
19    pub id: PersonId,
20    pub name: String,
21    pub government: GovernmentId,
22    pub current_location: TerritoryId,
23    pub roles: Vec<String>,
24    #[serde(default, skip_serializing_if = "Option::is_none")]
25    pub transit: Option<PersonTransitState>,
26}
27
28#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
29pub struct PersonTransitState {
30    pub from: TerritoryId,
31    pub to: TerritoryId,
32    pub departed_at: SimTime,
33    pub arrives_at: SimTime,
34}
35
36#[derive(Clone, Copy, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
37#[serde(rename_all = "snake_case")]
38pub enum LetterStatus {
39    HeldByPerson,
40    InTransit,
41    HeldAtLocation,
42    Delivered,
43}
44
45#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
46pub struct LetterCargo {
47    pub id: LetterId,
48    pub sender: PersonId,
49    pub recipient: PersonId,
50    pub body: String,
51    pub status: LetterStatus,
52    #[serde(default, skip_serializing_if = "Option::is_none")]
53    pub carrier: Option<PersonId>,
54    #[serde(default, skip_serializing_if = "Option::is_none")]
55    pub location: Option<TerritoryId>,
56    #[serde(default, skip_serializing_if = "Option::is_none")]
57    pub delivered_at: Option<SimTime>,
58}
59
60#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
61pub struct Government {
62    pub id: GovernmentId,
63    pub name: String,
64    pub capital: TerritoryId,
65}
66
67#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
68pub struct Territory {
69    pub id: TerritoryId,
70    pub name: String,
71    pub controller: GovernmentId,
72    pub position: MapPoint,
73}
74
75#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
76pub struct Route {
77    pub id: RouteId,
78    pub name: String,
79    pub from: TerritoryId,
80    pub to: TerritoryId,
81    pub travel_minutes: i64,
82    pub terrain: String,
83}
84
85impl Route {
86    #[must_use]
87    pub fn connects(&self, first: TerritoryId, second: TerritoryId) -> bool {
88        (self.from == first && self.to == second) || (self.from == second && self.to == first)
89    }
90
91    #[must_use]
92    pub fn other_end(&self, territory: TerritoryId) -> Option<TerritoryId> {
93        if self.from == territory {
94            Some(self.to)
95        } else if self.to == territory {
96            Some(self.from)
97        } else {
98            None
99        }
100    }
101}
102
103#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
104pub struct TransitState {
105    pub from: TerritoryId,
106    pub to: TerritoryId,
107    pub departed_at: SimTime,
108    pub arrives_at: SimTime,
109}
110
111#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
112pub struct Army {
113    pub id: ArmyId,
114    pub name: String,
115    pub government: GovernmentId,
116    pub commander: PersonId,
117    pub location: TerritoryId,
118    pub strength: u32,
119    pub morale: u16,
120    pub transit: Option<TransitState>,
121}
122
123#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
124pub struct WorldSnapshot {
125    pub people: Vec<Person>,
126    pub governments: Vec<Government>,
127    pub territories: Vec<Territory>,
128    pub routes: Vec<Route>,
129    pub armies: Vec<Army>,
130    #[serde(default, skip_serializing_if = "Vec::is_empty")]
131    pub letters: Vec<LetterCargo>,
132}
133
134impl WorldSnapshot {
135    #[must_use]
136    pub fn entities(&self) -> Vec<EntityRef> {
137        let mut entities = Vec::with_capacity(
138            self.armies.len()
139                + self.governments.len()
140                + self.people.len()
141                + self.letters.len()
142                + self.routes.len()
143                + self.territories.len(),
144        );
145        entities.extend(self.armies.iter().map(|value| EntityRef::Army(value.id)));
146        entities.extend(
147            self.governments
148                .iter()
149                .map(|value| EntityRef::Government(value.id)),
150        );
151        entities.extend(self.people.iter().map(|value| EntityRef::Person(value.id)));
152        entities.extend(
153            self.letters
154                .iter()
155                .map(|value| EntityRef::Resource(ResourceId::new(value.id.get()))),
156        );
157        entities.extend(self.routes.iter().map(|value| EntityRef::Route(value.id)));
158        entities.extend(
159            self.territories
160                .iter()
161                .map(|value| EntityRef::Territory(value.id)),
162        );
163        entities.sort();
164        entities
165    }
166
167    #[must_use]
168    pub fn person(&self, id: PersonId) -> Option<&Person> {
169        self.people.iter().find(|person| person.id == id)
170    }
171
172    #[must_use]
173    pub fn government(&self, id: GovernmentId) -> Option<&Government> {
174        self.governments.iter().find(|value| value.id == id)
175    }
176
177    #[must_use]
178    pub fn territory(&self, id: TerritoryId) -> Option<&Territory> {
179        self.territories.iter().find(|value| value.id == id)
180    }
181
182    #[must_use]
183    pub fn route(&self, id: RouteId) -> Option<&Route> {
184        self.routes.iter().find(|value| value.id == id)
185    }
186
187    #[must_use]
188    pub fn army(&self, id: ArmyId) -> Option<&Army> {
189        self.armies.iter().find(|value| value.id == id)
190    }
191
192    #[must_use]
193    pub fn letter(&self, id: LetterId) -> Option<&LetterCargo> {
194        self.letters.iter().find(|value| value.id == id)
195    }
196
197    #[must_use]
198    pub fn route_between(&self, from: TerritoryId, to: TerritoryId) -> Option<&Route> {
199        self.routes.iter().find(|route| route.connects(from, to))
200    }
201
202    pub fn adjacent_territories(
203        &self,
204        territory: TerritoryId,
205    ) -> impl Iterator<Item = TerritoryId> + '_ {
206        self.routes
207            .iter()
208            .filter_map(move |route| route.other_end(territory))
209    }
210
211    #[must_use]
212    pub fn travel_duration(&self, from: TerritoryId, to: TerritoryId) -> Option<SimDuration> {
213        self.route_between(from, to)
214            .map(|route| SimDuration::minutes(route.travel_minutes))
215    }
216
217    #[must_use]
218    pub fn distance(&self, from: TerritoryId, to: TerritoryId) -> Option<f32> {
219        let first = self.territory(from)?.position;
220        let second = self.territory(to)?.position;
221        Some((second.x - first.x).hypot(second.y - first.y))
222    }
223
224    pub fn territories_within(
225        &self,
226        center: TerritoryId,
227        radius: f32,
228    ) -> impl Iterator<Item = &Territory> {
229        let center_position = self.territory(center).map(|territory| territory.position);
230        self.territories.iter().filter(move |territory| {
231            center_position.is_some_and(|point| {
232                (territory.position.x - point.x).hypot(territory.position.y - point.y) <= radius
233            })
234        })
235    }
236}