lib_efo/
stats.rs

1//! Module that holds all statistics for an entity and the helper methods that operate on them.
2//!
3//! The statistics are grouped into thematic sections (Demographics, Economy, Social, Military,
4//! Infrastructure).  Each field is a `GenericStat`, an internal wrapper around a numeric value
5//! that provides convenience methods such as `max()`, `clamp()`, and arithmetic ops.
6//!
7//! The documentation for each field follows the same three-part template:
8//!   * **Purpose** - What the stat represents.
9//!   * **Effect** - Gameplay consequence of the stat.
10//!   * **Source** - Formula or function that generates the stat value.
11
12#![deny(clippy::missing_docs_in_private_items)]
13
14use crate::{entity::EntityIdentifier, error::EpochError, thin_newtype, utils::RngLevel};
15use cli_table::{CellStruct, Color, Style, Table};
16use colored::Colorize;
17use derive_more::{Add, Display, Mul, Sub};
18use rand::{Rng, distr::Distribution, rngs::ThreadRng};
19use serde::{Deserialize, Serialize};
20use uuid::Uuid;
21mod implementations;
22
23/// Aggregated statistics for a single entity.
24///
25/// The struct is intentionally flat to keep access fast; the fields are logically grouped
26/// by comment blocks that describe each thematic section.
27#[derive(Debug, Clone, Default, Serialize, Deserialize)]
28// Derive more
29#[derive(Table, Add, Sub)]
30pub struct OverallStats {
31    // ──────────────────────── Demographics ────────────────────────
32    /// Total economic health of the entity.
33    ///
34    /// **Purpose** - A single value summarizing all economic indicators.
35    /// **Effect** - Drives tax revenue, spending power, and overall economic influence.
36    /// **Source** - Sum of `industry`, `science`, `trade`, and other economic contributors.
37    #[table(title = "DEM", customize_fn = "color_cell_categories")]
38    pub demographics: GenericStat,
39    /// Population count.
40    ///
41    /// **Purpose** - The number of citizens living in the entity.
42    /// **Effect** - Determines resource consumption, tax base, and potential unrest.
43    /// **Source** - Updated by the growth/decay formulas each turn.
44    #[table(title = "POP", customize_fn = "color_cell")]
45    pub population: GenericStat,
46
47    /// Per-turn change in population.
48    ///
49    /// **Purpose** - The growth rate of the population.
50    /// **Effect** - Predicts future needs and signals potential social unrest.
51    /// **Source** - Derived from `population`, `happiness`, and `stability`.
52    #[table(title = "GRW", customize_fn = "color_cell")]
53    pub growth_rate: GenericStat,
54
55    /// Crime level (0-1 scale).
56    ///
57    /// **Purpose** - How frequently crimes occur.
58    /// **Effect** - Can trigger riots and reduce stability.
59    /// **Source** - Result of the crime function (influenced by class disparity and law enforcement).
60    #[table(title = "CRM", customize_fn = "color_cell")]
61    pub crime: GenericStat,
62
63    /// Base happiness of the population (0-1 scale).
64    ///
65    /// **Purpose** - A measure of citizen contentment.
66    /// **Effect** - Influences productivity, tax compliance, and unrest.
67    /// **Source** - Output of the happiness function (affected by class, war, infrastructure).
68    #[table(title = "HPY", customize_fn = "color_cell")]
69    pub happiness: GenericStat,
70
71    // ──────────────────────── Economy ────────────────────────
72    /// Aggregate economic health of the entity.
73    ///
74    /// **Purpose** - A single value summarizing all economic metrics.
75    /// **Effect** - Drives revenue, spending, and empire-wide economic influence.
76    /// **Source** - Sum of `food`, `industry`, `science`, `trade`, etc.
77    #[table(title = "ECO", customize_fn = "color_cell_categories")]
78    pub economy: GenericStat,
79
80    /// Food produced by the entity.
81    ///
82    /// **Purpose** - Amount of consumable resources available.
83    /// **Effect** - Keeps the population alive and productive.
84    /// **Source** - `food = farm_yield + tech_level`.
85    #[table(title = "FOD", customize_fn = "color_cell")]
86    pub food: GenericStat,
87
88    /// Strength of the industry.
89    ///
90    /// **Purpose** - Industrial production capacity.
91    /// **Effect** - Drives tax revenue and unit production.
92    /// **Source** - `industry = factory_output + tech_level`.
93    #[table(title = "IND", customize_fn = "color_cell")]
94    pub industry: GenericStat,
95
96    /// Scientific output.
97    ///
98    /// **Purpose** - Rate of research progress.
99    /// **Effect** - Determines how quickly new technologies are unlocked.
100    /// **Source** - `science = research_center_output + tech_level`.
101    #[table(title = "SCI", customize_fn = "color_cell")]
102    pub science: GenericStat,
103
104    /// Trade volume / exports.
105    ///
106    /// **Purpose** - Measure of export activity.
107    /// **Effect** - Influences influence and wealth.
108    /// **Source** - `trade = trade_network_output`.
109    #[table(title = "TRD", customize_fn = "color_cell")]
110    pub trade: GenericStat,
111    // // ──────────────────────── Social ────────────────────────
112    // /// Overall social prestige.
113    // ///
114    // /// **Purpose** - Reflects the cultural standing of the entity.
115    // /// **Effect** - Affects diplomacy and citizen morale.
116    // /// **Source** - Sum of `culture`, `education`, `tourism`, etc.
117    // #[table(title = "SOC", color = "Color::Blue", bold, customize_fn = "color_cell")]
118    // pub social: GenericStat,
119
120    // /// Cultural output from arts, theatres, museums, etc.
121    // ///
122    // /// **Purpose** - Measure of cultural production.
123    // /// **Effect** - Drives tourism, prestige, and happiness.
124    // /// **Source** - `culture = Σ(cultural_building_output)`.
125    // #[table(title = "CLU", customize_fn = "color_cell")]
126    // pub culture: GenericStat,
127
128    // /// Level of education/literacy.
129    // ///
130    // /// **Purpose** - Measure of the citizenry's knowledge.
131    // /// **Effect** - Improves productivity, science output, and reduces unrest.
132    // /// **Source** - `education = Σ(education_building_output)`.
133    // #[table(title = "EDU", customize_fn = "color_cell")]
134    // pub education: GenericStat,
135
136    // /// Tourism revenue.
137    // ///
138    // /// **Purpose** - Income generated by visitors.
139    // /// **Effect** - Provides an additional income stream.
140    // /// **Source** - `tourism = number_of_tourists x price_per_tourist`.
141    // #[table(title = "TOR", customize_fn = "color_cell")]
142    // pub tourism: GenericStat,
143
144    // /// Cultural influence over the region.
145    // ///
146    // /// **Purpose** - Measure of soft power.
147    // /// **Effect** - Grants diplomatic bonuses and increases sway in the empire.
148    // /// **Source** - `influence = culture x population`.
149    // #[table(title = "INF", customize_fn = "color_cell")]
150    // pub influence: GenericStat,
151
152    // ──────────────────────── Military ────────────────────────
153    /// Overall military power.
154    ///
155    /// **Purpose** - Composite measure of combat capability.
156    /// **Effect** - Determines defensive and offensive strength.
157    /// **Source** - Sum of `army`, `strength`, `fortification`, etc.
158    #[table(title = "MIL", customize_fn = "color_cell_categories")]
159    pub military: GenericStat,
160
161    /// Number of battle-ready units.
162    ///
163    /// **Purpose** - Size of the active army.
164    /// **Effect** - Directly dictates defense and attack potential.
165    /// **Source** - Sum of all trained and equipped units.
166    #[table(title = "ARM", customize_fn = "color_cell")]
167    pub army: GenericStat,
168
169    /// Composite strength of the army.
170    ///
171    /// **Purpose** - Tech, training, and equipment bonuses aggregated.
172    /// **Effect** - Determines combat effectiveness and required force to defeat an enemy.
173    /// **Source** - `strength = Σ(unit_strength) x tech_bonus x training_bonus`.
174    #[table(title = "STR", customize_fn = "color_cell")]
175    pub strength: GenericStat,
176
177    /// Defensive bonus from fortifications.
178    ///
179    /// **Purpose** - Wall and tower defenses.
180    /// **Effect** - Adds a multiplier to overall defense.
181    /// **Source** - `fortification = Σ(fortification_building_bonus)`.
182    #[table(title = "FRT", customize_fn = "color_cell")]
183    pub fortification: GenericStat,
184
185    /// Threat level facing the entity.
186    ///
187    /// **Purpose** - Likelihood of being attacked.
188    /// **Effect** - Triggers early warning alerts and strategic decisions.
189    /// **Source** - `threat = (enemy_proximity + enemy_strength) / defense`.
190    #[table(title = "THR", customize_fn = "color_cell")]
191    pub threat: GenericStat,
192
193    // ──────────────────────── Infrastructure ────────────────────────
194    /// Total infrastructure power.
195    ///
196    /// **Purpose** - Overall built-environment capacity.
197    /// **Effect** - Supports population size and efficiency.
198    /// **Source** - Sum of all infrastructure components.
199    #[table(title = "IFR", customize_fn = "color_cell_categories")]
200    pub infrastructure: GenericStat,
201
202    /// Bureaucratic power.
203    ///
204    /// **Purpose** - Efficiency of the city's administrative machinery.
205    /// **Effect** - Improves resource management and policy implementation.
206    /// **Source** - `bureaucracy = Σ(bureaucracy_building_output)`.
207    #[table(title = "BUR", customize_fn = "color_cell")]
208    pub bureaucracy: GenericStat,
209
210    /// Transportation level.
211    ///
212    /// **Purpose** - Availability and quality of transport infrastructure.
213    /// **Effect** - Impacts logistics, mobility, and trade.
214    /// **Source** - `transportation = Σ(transport_building_output)`.
215    #[table(title = "TRP", customize_fn = "color_cell")]
216    pub transportation: GenericStat,
217
218    /// Sprawl / urban density.
219    ///
220    /// **Purpose** - Measure of city sprawl and clutter.
221    /// **Effect** - Affects health, happiness, and infrastructure efficiency.
222    /// **Source** - `sprawl = Σ(sprawl_factor)`.
223    #[table(title = "SPR", customize_fn = "color_cell")]
224    pub sprawl: GenericStat,
225
226    /// Health infrastructure quality.
227    ///
228    /// **Purpose** - Public health capacity.
229    /// **Effect** - Affects population health and can generate unrest.
230    /// **Source** - `health = output_of_health_function` (influenced by sanitation, disease spread, etc.).
231    #[table(title = "HLT", customize_fn = "color_cell")]
232    pub health: GenericStat,
233}
234
235/// Color the base cells
236fn color_cell(cell: CellStruct, value: &GenericStat) -> CellStruct {
237    match (**value).cmp(&0) {
238        std::cmp::Ordering::Less => cell.foreground_color(Some(Color::Red)),
239        std::cmp::Ordering::Equal => cell,
240        std::cmp::Ordering::Greater => cell, //.foreground_color(Some(Color::Green)),
241    }
242}
243
244/// Color the cell categories
245fn color_cell_categories(cell: CellStruct, value: &GenericStat) -> CellStruct {
246    match (**value).cmp(&0) {
247        std::cmp::Ordering::Less => cell.foreground_color(Some(Color::Red)).bold(true),
248        std::cmp::Ordering::Equal => cell.foreground_color(Some(Color::Blue)).bold(true),
249        std::cmp::Ordering::Greater => cell.foreground_color(Some(Color::Green)).bold(true),
250    }
251}
252
253impl OverallStats {
254    /// Create Stats with a given rng distribution
255    pub(crate) fn create_rng(
256        rng: &RngLevel,
257        mut rng_t: &mut ThreadRng,
258    ) -> Result<OverallStats, EpochError> {
259        let r = rng.to_rng()?;
260        let mut overall_stats = Self {
261            demographics: 0.into(),
262            population: (r.sample(&mut rng_t) as isize).into(),
263            growth_rate: (r.sample(&mut rng_t) as isize).into(),
264            crime: (r.sample(&mut rng_t) as isize).into(),
265            happiness: (r.sample(&mut rng_t) as isize).into(),
266            economy: 0.into(),
267            food: (r.sample(&mut rng_t) as isize).into(),
268            industry: (r.sample(&mut rng_t) as isize).into(),
269            science: (r.sample(&mut rng_t) as isize).into(),
270            trade: (r.sample(&mut rng_t) as isize).into(),
271            military: 0.into(),
272            army: (r.sample(&mut rng_t) as isize).into(),
273            strength: (r.sample(&mut rng_t) as isize).into(),
274            fortification: (r.sample(&mut rng_t) as isize).into(),
275            threat: (r.sample(&mut rng_t) as isize).into(),
276            infrastructure: 0.into(),
277            bureaucracy: (r.sample(&mut rng_t) as isize).into(),
278            transportation: (r.sample(&mut rng_t) as isize).into(),
279            sprawl: (r.sample(&mut rng_t) as isize).into(),
280            health: (r.sample(&mut rng_t) as isize).into(),
281        };
282        overall_stats.update();
283        Ok(overall_stats)
284    }
285
286    /// Updates the inner meta values
287    // TODO: Force the use of this ?
288    pub fn update(&mut self) {
289        self.demographics =
290            (*(self.population + self.growth_rate + self.happiness - self.crime) / 4).into();
291        self.economy = (*(self.food + self.industry + self.trade + self.science) / 4).into();
292        self.military =
293            (*(self.army + self.strength + self.fortification + self.threat) / 4).into();
294        self.infrastructure =
295            (*(self.bureaucracy + self.transportation + self.sprawl + self.health) / 4).into();
296    }
297
298    /// Updates with events
299    /// Updates with leader
300    /// etc.
301    // TODO: Fill this
302    #[allow(dead_code)]
303    fn apply_exteriors(&mut self) {}
304
305    #[cfg(test)]
306    pub(crate) fn linear(base: isize) -> OverallStats {
307        use crate::stats;
308
309        Self {
310            demographics: stats::GenericStat(1 + base),
311            population: stats::GenericStat(2 + base),
312            growth_rate: stats::GenericStat(3 + base),
313            crime: stats::GenericStat(4 + base),
314            happiness: stats::GenericStat(5 + base),
315            economy: stats::GenericStat(6 + base),
316            food: stats::GenericStat(7 + base),
317            industry: stats::GenericStat(8 + base),
318            science: stats::GenericStat(9 + base),
319            trade: stats::GenericStat(10 + base),
320            military: stats::GenericStat(11 + base),
321            army: stats::GenericStat(12 + base),
322            strength: stats::GenericStat(13 + base),
323            fortification: stats::GenericStat(14 + base),
324            threat: stats::GenericStat(15 + base),
325            infrastructure: stats::GenericStat(16 + base),
326            bureaucracy: stats::GenericStat(17 + base),
327            transportation: stats::GenericStat(18 + base),
328            sprawl: stats::GenericStat(19 + base),
329            health: stats::GenericStat(20 + base),
330        }
331    }
332}
333
334/// Strategic context and meta-data for an entity.
335///
336/// All fields are intentionally simple - the logic that interprets them
337/// lives in separate modules (e.g. `map`, `network`, `governance`).
338#[derive(Debug, Clone, Default, Serialize, Deserialize)]
339pub struct MetaStats {
340    // ──── Location & Spatial Data (hidden) ───────────────────────────────
341    /// Cartesian coordinates of the entity on the world map.
342    ///
343    /// **Purpose** - Position of the entity in the simulation grid.
344    /// **Effect** - Used for distance calculations, visibility, and
345    /// map-based event logic.
346    /// **Source** - Supplied by the map generator.
347    pub position: Position,
348
349    /// Identifier for the capital
350    pub capital: Option<TownId>,
351
352    // /// List of nearby resource nodes (mines, forests, farms, etc.).
353    // ///
354    // /// **Purpose** - Resource map around the entity.
355    // /// **Effect** - Drives the entity's specialization and determines
356    // /// the default output mix.
357    // /// **Source** - `resources = filter(resource_list, distance ≤ detection_radius)`.
358    // pub resources: Vec<String>,
359
360    // /// The entity's chosen specialization tag
361    // /// (e.g., “Agricultural Hub”, “Industrial Center”).
362    // ///
363    // /// **Purpose** - Identity that dictates default production multipliers.
364    // /// **Effect** - Alters output curves and trade attractiveness.
365    // /// **Source** - `specialization = tag_assigned_by_player_or_ai`.
366    // pub specialization: String,
367
368    // ──── Strategic Positioning (hidden + public) ─────────────────────
369    /// Current governor or leader assigned to the entity.
370    ///
371    /// **Purpose** - Leadership identity.
372    /// **Effect** - Adds personal bonuses and may modify policies
373    /// or diplomatic stance.
374    /// **Source** - `governor = selected_leader_uuid`.
375    pub governor: Option<CharacterId>,
376
377    // /// The entity's classification (capital, trade hub, frontier post, etc.).
378    // ///
379    // /// **Purpose** - Functional role within the empire.
380    // /// **Effect** - Alters base production, tax rates, and
381    // /// certain event probabilities.
382    // /// **Source** - `role = tag_assigned_by_player_or_ai`.
383    // pub role: String,
384    /// The faction or empire to which the entity currently belongs.
385    ///
386    /// **Purpose** - Political affiliation.
387    /// **Effect** - Determines diplomatic relations, war status,
388    /// and access to empire-wide bonuses.
389    /// **Source** - `faction = current_faction_uuid`.
390    pub faction: Option<FactionId>,
391
392    /// UIDs of all entities the entity is linked to (roads, trade routes,
393    /// alliances, war declarations, etc.).
394    pub links: Vec<TypedLink>,
395
396    /// Prestige / reputation of the entity in the eyes of the empire
397    /// and foreign powers.
398    ///
399    /// **Purpose** - Social standing metric.
400    /// **Effect** - Affects diplomatic influence, trade bonuses,
401    /// and event outcomes.
402    /// **Source** - `prestige = calculate_prestige(...)`.
403    pub prestige: GenericStat,
404
405    /// The reach of the entity, wether local or worldly
406    ///
407    /// **Purpose** - Event validation
408    pub reach: Reach,
409}
410
411/// A generic stat pair with (ListStats, GenericStat), used mainly for clap
412/// Accepts two forms
413/// 1. "cri 10"  (two separate tokens)
414/// 2. "cri:10"  (one token with a separator)
415#[derive(Debug, Serialize, Deserialize, Clone)]
416pub struct StatPair {
417    /// The stat selected
418    pub stat: ListStats,
419    /// The value of the modifier
420    pub value: GenericStat,
421}
422
423/// A two-dimensional point on the world map.
424///  
425/// The struct is a thin wrapper around a pair of `isize` values
426/// representing the X and Y coordinates.
427#[derive(Debug, Default, Serialize, Deserialize, Clone, Add, Sub, Mul)]
428pub struct Position(isize, isize);
429
430/// A grid-aligned position used for chunk look-ups.
431///  
432/// A `PositionGrid` stores the coordinates of a chunk in the world
433/// grid.  Each component is the index of the chunk in the X and Y
434/// direction.
435#[derive(Debug, Default, Serialize, Deserialize, Clone, Add, Sub, Mul)]
436pub struct PositionGrid(isize, isize);
437
438/// The size of the whole world in pixels.
439///
440/// These constants are internal to the module; they are not part
441/// of the public API.
442// TODO(9): Change to be at least initialized with the world ?
443const GRID_SIZE: (i32, i32) = (1920, 1080);
444/// The size of one world chunk in pixels.
445const CHUNK_SIZE: isize = 200;
446
447impl Position {
448    /// Calculates the Euclidean distance between two points.
449    ///
450    /// The distance is computed in the 2-D Euclidean space.
451    pub fn distance(&self, other: &Self) -> isize {
452        ((self.0 - other.0).pow(2) + (self.1 - other.1).pow(2)).isqrt()
453    }
454
455    /// Converts this point to its corresponding grid position.
456    ///
457    /// The conversion divides the world coordinates by the
458    /// `CHUNK_SIZE` to obtain the chunk indices.
459    pub fn to_grid_distance(&self) -> PositionGrid {
460        PositionGrid(self.0 / CHUNK_SIZE, self.1 / CHUNK_SIZE)
461    }
462
463    /// Returns the inner tuple representation of the position.
464    ///
465    /// This is a convenience method to obtain the raw `(isize, isize)`
466    /// coordinates without wrapping them in the struct.
467    pub fn to_inner(&self) -> (isize, isize) {
468        (self.0, self.1)
469    }
470
471    /// Generates a random position within the world bounds.
472    ///
473    /// The position is chosen uniformly at random using the supplied
474    /// random number generator.
475    pub fn create_rng(r: &mut ThreadRng) -> Result<Self, EpochError> {
476        let x = r.random_range(0..GRID_SIZE.0);
477        let y = r.random_range(0..GRID_SIZE.1);
478        Ok(Self(x as isize, y as isize))
479    }
480}
481
482impl PositionGrid {
483    /// Calculates the Euclidean distance between two grid positions.
484    ///
485    /// The distance is computed by converting both grid positions to
486    /// world coordinates and then using `Position::distance`.
487    pub fn distance(&self, other: &PositionGrid) -> isize {
488        self.to_position().distance(&other.to_position())
489    }
490
491    /// Converts the grid coordinates to world coordinates.
492    ///
493    /// Each component is multiplied by `CHUNK_SIZE` to obtain the
494    /// corresponding world position.
495    pub fn to_position(&self) -> Position {
496        Position(CHUNK_SIZE * self.0, CHUNK_SIZE * self.1)
497    }
498}
499/// A summary of the stats, only for display purposes
500#[allow(clippy::missing_docs_in_private_items)]
501#[derive(Table)]
502pub struct SummaryStats {
503    #[table(title = "Demo")]
504    demographics: GenericStat,
505    #[table(title = "Eco")]
506    economy: GenericStat,
507    // #[table(title = "Social")]
508    // social: GenericStat,
509    #[table(title = "Milit")]
510    military: GenericStat,
511    #[table(title = "Infra")]
512    infrastructure: GenericStat,
513}
514
515impl From<OverallStats> for SummaryStats {
516    fn from(value: OverallStats) -> Self {
517        Self {
518            demographics: value.demographics,
519            economy: value.economy,
520            military: value.military,
521            infrastructure: value.infrastructure,
522        }
523    }
524}
525
526/// Bridge between the CLI and the statistics logic.
527///
528/// Helps convert clap arguments to OverallStats
529#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
530#[allow(missing_docs)]
531pub enum ListStats {
532    Demographics,
533    Population,
534    Growthrate,
535    Crime,
536    Happiness,
537    Economy,
538    Food,
539    Industry,
540    Science,
541    Trade,
542    Social,
543    Culture,
544    Education,
545    Tourism,
546    Influence,
547    Military,
548    Army,
549    Strength,
550    Fortification,
551    Threat,
552    Infrastructure,
553    Bureaucracy,
554    Transportation,
555    Sprawl,
556    Health,
557}
558
559/// The list of all types of entities
560#[allow(missing_docs)]
561#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Default)]
562pub enum EntityType {
563    Character,
564    Player,
565    Town,
566    Monster,
567    Blocker,
568    #[default]
569    Event,
570}
571
572/// Trait to implement some symbol representations
573pub trait Symbols {
574    /// Have a small Symbol to resume it
575    fn to_symbol(&self) -> String;
576    /// Have the shortest display possible
577    fn to_shortest(&self) -> String {
578        self.to_symbol()
579    }
580}
581
582// impl<T: Symbols> Display for T {
583//     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
584//         write!(f, "{}", self.to_symbol())
585//     }
586// }
587
588// A stat that is shown to the player *and* can be modified manually.
589thin_newtype!(GenericStat, isize, derives: [Add, Sub]);
590thin_newtype!(TownId, Uuid);
591thin_newtype!(CharacterId, Uuid);
592thin_newtype!(FactionId, usize, derives: [Add, Sub]);
593
594#[derive(Copy, Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Display, Default)]
595/// A typed link between two entities.
596///
597/// A link is a lightweight association that ties an entity's **identifier**
598/// (`id`) to a *semantic role* (`role`).  The role is expressed by the
599/// [`Role`] enum and defines what the link actually means in the
600/// domain model.
601#[display("{}{}({}{})",role, role.to_symbol(),other_id.to_symbol(), other_type.to_symbol())]
602pub struct TypedLink {
603    /// The UUID of the entity that is linked.
604    ///
605    /// The link is *unidirectional*; the `TypedLink` itself does not
606    /// keep a back-reference to the source entity.
607    pub other_id: Uuid,
608    /// The semantic role that this link plays.
609    ///
610    /// For example, a link with `Role::Capital` indicates that the entity
611    /// identified by `id` is the capital of the entity that owns the link.
612    pub role: Role,
613    /// The Type of the entity linked
614    ///
615    /// Even if it can mostly be inferred, some edge cases can be squashed
616    /// early in exchange of some data storred
617    pub other_type: EntityType,
618}
619
620impl TypedLink {
621    /// Simple initializer for the TypedLink type
622    pub fn new(other_id: Uuid, role: Role, other_type: EntityType) -> Self {
623        Self {
624            other_id,
625            role,
626            other_type,
627        }
628    }
629}
630
631#[derive(Copy, Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Display, Default)]
632/// Semantic role of a link.
633///
634/// The role encodes *what the link represents* in the model.  The
635/// variants are intentionally limited to the most common use-cases,
636/// but the enum can be extended if the domain evolves.
637pub enum Role {
638    /// The linked entity has governing authority over the link owner
639    Govern,
640    /// The linked entity is the capital of the link owner
641    Capital,
642    #[default]
643    /// No role has been assigned.
644    Undefined,
645}
646
647// --- TRAITS ---------------------------------------------------------------
648/// A stat is a *game value* that can be converted to two different
649/// normalised ranges - a 100-point range for “gaming” logic and a
650/// 10-point range for “roll & UI” logic.  The trait also exposes the
651/// underlying raw value and its visibility.
652///
653// TODO(7): Change this with Deref+From
654pub trait GameStat {
655    /// Convert the raw integer to a 50-point scale.
656    fn to_100(&self) -> isize {
657        (self.get_value().clamp(0, 120) - 10).min(100)
658    }
659
660    /// Convert the raw integer to a 10-point scale.
661    fn to_10(&self) -> isize {
662        self.to_100() / 10
663    }
664
665    /// As a colored delta (when the stat is used as a delta)
666    fn to_delta(&self) -> String {
667        let get_value = self.get_value();
668        if get_value < 0 {
669            format!("{}", get_value.to_string().red())
670        } else {
671            format!("{}", get_value.to_string().green())
672        }
673    }
674
675    /// Return the raw integer value, always bound to 0-70
676    fn get_value(&self) -> isize;
677
678    /// Return the raw integer value, always bound to 0-70
679    fn change_value(&mut self, value: isize);
680
681    /// Create a Gamestat from it's underlying value
682    fn from_value(value: isize) -> Self
683    where
684        Self: Sized;
685}
686
687/// Trait for objects that can expose a stat via a string key.
688/// The trait returns a *boxed* trait object so callers do not need to know
689/// the concrete type that was stored.
690pub trait Accessible {
691    /// Return the requested stat as a `Box<dyn GameStat>`.
692    /// `None` if the key does not exist.
693    fn get_val(&self, key: &str) -> Option<Box<dyn GameStat>>;
694}
695
696/// Represents a position that can be identified in three different ways.
697///
698/// The string format is deliberately simple and is designed to be easy to write
699/// by hand while still being unambiguous to a machine.
700///
701/// | Variant | Syntax in a string |
702/// |---------|--------------------|
703/// | `Exact` | `X,Y`              |
704/// | `Grid`  | `~X,Y` or `gX,Y`   |
705/// | `Id`    | `@{uuid}`          |
706#[derive(Debug, Clone)]
707pub enum PositionIdentifier {
708    /// Exact position specified as `X,Y`.
709    Exact(Position),
710
711    /// Grid-aligned position specified as `~X,Y` or `gX,Y`.
712    Grid(PositionGrid),
713
714    /// Reference to an epoch by UUID, written as `@{uuid}`.
715    Id(EntityIdentifier),
716}
717
718#[derive(Debug, Clone, Default, Serialize, Deserialize)]
719/// The reach of the entity
720pub enum Reach {
721    /// The entity can affect **any** target - no positional or faction
722    /// restrictions.  Use this when the effect is truly global.
723    Global,
724    /// The entity has a *limited* area of effect. The `usize` usually
725    /// represents the radius around the entity's position that the effect covers.
726    Local(usize),
727    /// The entity can only affect members of a particular faction.
728    Faction(FactionId),
729    // /// The entity only affect a specific entity
730    // Entity(EntityIdentifier),
731    /// No reach - the entity should not be applied globally.
732    #[default]
733    None,
734}
735
736/// A description of how a value should be updated.
737///
738/// The enum is intentionally tiny – only the three actions that
739/// matter in our state‑diff engine.  Each variant is documented
740/// with a short, practical example so you know exactly when
741/// you’d pick it.
742///
743/// The enum is `Copy`, so you can freely pass it around without
744/// ownership worries.  The `From<&str>` implementation below
745/// lets you read the operation name from configuration files or
746/// CLI flags:
747#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
748pub enum Modification {
749    /// The modification will be a *delta*, i.e. an increment.
750    ///  
751    /// For numeric values this is equivalent to adding the new
752    /// value to the existing one.  For collections it usually
753    /// means “append”.
754    Delta,
755
756    /// The modification will *replace* the existing value.
757    ///  
758    /// This is a hard overwrite: the old value is dropped
759    /// entirely and the new one is written in its place.
760    Replace,
761
762    /// The modification will *reset* the underlying value.
763    ///  
764    /// For plain types this is typically a default/zero value.
765    /// For `Option<T>` it sets the field to `None`.  It can
766    /// also be interpreted as “delete the field entirely”.
767    Delete,
768}