use anyhow::{bail, Result}; use serde::Deserialize; use std::collections::HashMap; use crate::{handle::FactionHandle, Content, ContentBuildContext}; pub(crate) mod syntax { use std::collections::HashMap; use serde::Deserialize; // Raw serde syntax structs. // These are never seen by code outside this crate. #[derive(Debug, Deserialize)] pub struct Faction { pub display_name: String, pub color: [f32; 3], pub relationship: HashMap, } } /// How two factions should interact with each other. /// Relationships are directional: the relationship of /// `a` to `b` may not equal the relationship of `b` to `a`. /// /// Relationships dictate how a ship of THIS faction /// will interact with a ship of the OTHER faction. #[derive(Debug, Deserialize, Clone, Copy)] pub enum Relationship { /// Attack this faction #[serde(rename = "hostile")] Hostile, /// Ignore this faction #[serde(rename = "neutral")] Neutral, /// Protect this faction #[serde(rename = "friend")] Friend, } /// Represents a game faction #[derive(Debug, Clone)] pub struct Faction { /// The name of this faction pub name: String, /// This faction's color. /// Format is RGB, with each color between 0 and 1. pub color: [f32; 3], /// This faction's handle pub handle: FactionHandle, /// Relationships between this faction and other factions /// This is guaranteed to contain an entry for ALL factions. pub relationships: HashMap, } impl crate::Build for Faction { type InputSyntaxType = HashMap; fn build( factions: Self::InputSyntaxType, _build_context: &mut ContentBuildContext, content: &mut Content, ) -> Result<()> { // Keeps track of position in faction array. // This lets us build FactionHandles before finishing all factions. let faction_names: Vec = factions.keys().map(|x| x.to_owned()).collect(); // Indexing will break if this is false. assert!(content.factions.len() == 0); for f_idx in 0..faction_names.len() { let faction_name = &faction_names[f_idx]; let faction = &factions[faction_name]; // Handle for this faction let h = FactionHandle { index: f_idx }; // Compute relationships let mut relationships = HashMap::new(); for i in 0..faction_names.len() { let f_other = &faction_names[i]; let h_other = FactionHandle { index: i }; if let Some(r) = faction.relationship.get(f_other) { relationships.insert(h_other, *r); } else { // Default relationship, if not specified // Look at reverse direction... let other = factions[f_other].relationship.get(faction_name); relationships.insert( h_other, // ... and pick a relationship based on that. match other { Some(Relationship::Hostile) => Relationship::Hostile {}, _ => Relationship::Neutral {}, }, ); } } if faction.color[0] > 1.0 || faction.color[0] < 0.0 || faction.color[1] > 1.0 || faction.color[1] < 0.0 || faction.color[2] > 1.0 || faction.color[2] < 0.0 { bail!( "Invalid color for faction `{}`. Value out of range.", faction_name ); } content.factions.push(Self { name: faction_name.to_owned(), handle: h, relationships, color: faction.color, }); } return Ok(()); } }