// Copyright (C) 2025 Emilis Bliūdžius // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as // published by the Free Software Foundation, either version 3 of the // License, or (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . use std::collections::HashMap; use serde::{Deserialize, Serialize}; use crate::{ character::CharacterId, diedto::DiedToTitle, error::GameError, game::{GameTime, Village, night::changes::NightChange}, message::night::{ActionPrompt, ActionResult}, role::{Alignment, AlignmentEq, Killer, Powerful, PreviousGuardianAction, RoleTitle}, }; type Result = core::result::Result; #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum GameActions { DayDetails(Box<[DayDetail]>), NightDetails(NightDetails), } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum DayDetail { Execute(CharacterId), } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct NightDetails { pub choices: Box<[NightChoice]>, pub changes: Box<[NightChange]>, } impl NightDetails { pub fn new( choices: &[(ActionPrompt, ActionResult)], changes: Box<[NightChange]>, village: &Village, ) -> Self { Self { changes, choices: choices .iter() .cloned() .filter_map(|(prompt, result)| NightChoice::new(prompt, result, village)) .collect(), } } } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct NightChoice { pub prompt: StoryActionPrompt, pub result: Option, } impl NightChoice { pub fn new(prompt: ActionPrompt, result: ActionResult, village: &Village) -> Option { Some(Self { prompt: StoryActionPrompt::new(prompt, village)?, result: StoryActionResult::new(result), }) } } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum StoryActionResult { RoleBlocked, Seer(CharacterId, Alignment), PowerSeer { target: CharacterId, powerful: Powerful, }, Adjudicator { target: CharacterId, killer: Killer, }, Arcanist((CharacterId, CharacterId), AlignmentEq), GraveDigger(CharacterId, Option), Mortician(CharacterId, DiedToTitle), Insomniac { visits: Box<[CharacterId]>, }, Empath { target: CharacterId, scapegoat: bool, }, BeholderSawNothing, BeholderSawEverything, Drunk, ShiftFailed, } impl StoryActionResult { pub fn new(result: ActionResult) -> Option { Some(match result { ActionResult::ShiftFailed => Self::ShiftFailed, ActionResult::BeholderSawNothing => Self::BeholderSawNothing, ActionResult::BeholderSawEverything => Self::BeholderSawEverything, ActionResult::Drunk => Self::Drunk, ActionResult::RoleBlocked => Self::RoleBlocked, ActionResult::Seer(target, alignment) => Self::Seer(target.character_id, alignment), ActionResult::PowerSeer { target, powerful } => Self::PowerSeer { powerful, target: target.character_id, }, ActionResult::Adjudicator { target, killer } => Self::Adjudicator { killer, target: target.character_id, }, ActionResult::Arcanist((target1, target2), same) => { Self::Arcanist((target1.character_id, target2.character_id), same) } ActionResult::GraveDigger(target, role_title) => { Self::GraveDigger(target.character_id, role_title) } ActionResult::Mortician(target, died_to) => { Self::Mortician(target.character_id, died_to) } ActionResult::Insomniac(visits) => Self::Insomniac { visits: visits.iter().map(|c| c.character_id).collect(), }, ActionResult::Empath { target, scapegoat } => Self::Empath { scapegoat, target: target.character_id, }, ActionResult::GoBackToSleep | ActionResult::Continue => return None, }) } } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum StoryActionPrompt { Seer { character_id: CharacterId, chosen: CharacterId, }, Protector { character_id: CharacterId, chosen: CharacterId, }, Arcanist { character_id: CharacterId, chosen: (CharacterId, CharacterId), }, Gravedigger { character_id: CharacterId, chosen: CharacterId, }, Hunter { character_id: CharacterId, chosen: CharacterId, }, Militia { character_id: CharacterId, chosen: CharacterId, }, MapleWolf { character_id: CharacterId, chosen: CharacterId, }, Guardian { character_id: CharacterId, chosen: CharacterId, guarding: bool, }, Adjudicator { character_id: CharacterId, chosen: CharacterId, }, PowerSeer { character_id: CharacterId, chosen: CharacterId, }, Mortician { character_id: CharacterId, chosen: CharacterId, }, Beholder { character_id: CharacterId, chosen: CharacterId, }, MasonsWake { leader: CharacterId, masons: Box<[CharacterId]>, }, MasonLeaderRecruit { character_id: CharacterId, chosen: CharacterId, }, Empath { character_id: CharacterId, chosen: CharacterId, }, Vindicator { character_id: CharacterId, chosen: CharacterId, }, PyreMaster { character_id: CharacterId, chosen: CharacterId, }, WolfPackKill { killing_wolf: CharacterId, chosen: CharacterId, }, Shapeshifter { character_id: CharacterId, }, AlphaWolf { character_id: CharacterId, chosen: CharacterId, }, DireWolf { character_id: CharacterId, chosen: CharacterId, }, LoneWolfKill { character_id: CharacterId, chosen: CharacterId, }, Insomniac { character_id: CharacterId, }, Bloodletter { character_id: CharacterId, chosen: CharacterId, }, BeholderWakes { character_id: CharacterId, }, } impl StoryActionPrompt { pub fn new(prompt: ActionPrompt, village: &Village) -> Option { Some(match prompt { ActionPrompt::BeholderWakes { character_id } => Self::BeholderWakes { character_id: character_id.character_id, }, ActionPrompt::Bloodletter { character_id, marked: Some(marked), .. } => Self::Bloodletter { character_id: character_id.character_id, chosen: marked, }, ActionPrompt::Seer { character_id, marked: Some(marked), .. } => Self::Seer { character_id: character_id.character_id, chosen: marked, }, ActionPrompt::Arcanist { character_id, marked: (Some(marked1), Some(marked2)), .. } => Self::Arcanist { character_id: character_id.character_id, chosen: (marked1, marked2), }, ActionPrompt::Protector { character_id, marked: Some(marked), .. } => Self::Protector { character_id: character_id.character_id, chosen: marked, }, ActionPrompt::Gravedigger { character_id, marked: Some(marked), .. } => Self::Gravedigger { character_id: character_id.character_id, chosen: marked, }, ActionPrompt::Hunter { character_id, marked: Some(marked), .. } => Self::Hunter { character_id: character_id.character_id, chosen: marked, }, ActionPrompt::Militia { character_id, marked: Some(marked), .. } => Self::Militia { character_id: character_id.character_id, chosen: marked, }, ActionPrompt::MapleWolf { character_id, marked: Some(marked), .. } => Self::MapleWolf { character_id: character_id.character_id, chosen: marked, }, ActionPrompt::Guardian { character_id, previous, marked: Some(marked), .. } => Self::Guardian { character_id: character_id.character_id, chosen: marked, guarding: previous .map(|prev| match prev { PreviousGuardianAction::Protect(id) => id.character_id == marked, _ => false, }) .unwrap_or_default(), }, ActionPrompt::Adjudicator { character_id, marked: Some(marked), .. } => Self::Adjudicator { character_id: character_id.character_id, chosen: marked, }, ActionPrompt::PowerSeer { character_id, marked: Some(marked), .. } => Self::PowerSeer { character_id: character_id.character_id, chosen: marked, }, ActionPrompt::Mortician { character_id, marked: Some(marked), .. } => Self::Mortician { character_id: character_id.character_id, chosen: marked, }, ActionPrompt::BeholderChooses { character_id, marked: Some(marked), .. } => Self::Beholder { character_id: character_id.character_id, chosen: marked, }, ActionPrompt::MasonsWake { leader, masons } => Self::MasonsWake { leader: leader.character_id, masons: masons.into_iter().map(|c| c.character_id).collect(), }, ActionPrompt::MasonLeaderRecruit { character_id, marked: Some(marked), .. } => Self::MasonLeaderRecruit { character_id: character_id.character_id, chosen: marked, }, ActionPrompt::Empath { character_id, marked: Some(marked), .. } => Self::Empath { character_id: character_id.character_id, chosen: marked, }, ActionPrompt::Vindicator { character_id, marked: Some(marked), .. } => Self::Vindicator { character_id: character_id.character_id, chosen: marked, }, ActionPrompt::PyreMaster { character_id, marked: Some(marked), .. } => Self::PyreMaster { character_id: character_id.character_id, chosen: marked, }, ActionPrompt::WolfPackKill { marked: Some(marked), .. } => Self::WolfPackKill { chosen: marked, killing_wolf: village.killing_wolf().map(|c| c.character_id())?, }, ActionPrompt::Shapeshifter { character_id } => Self::Shapeshifter { character_id: character_id.character_id, }, ActionPrompt::AlphaWolf { character_id, marked: Some(marked), .. } => Self::AlphaWolf { character_id: character_id.character_id, chosen: marked, }, ActionPrompt::DireWolf { character_id, marked: Some(marked), .. } => Self::DireWolf { character_id: character_id.character_id, chosen: marked, }, ActionPrompt::LoneWolfKill { character_id, marked: Some(marked), .. } => Self::LoneWolfKill { character_id: character_id.character_id, chosen: marked, }, ActionPrompt::Insomniac { character_id } => Self::Insomniac { character_id: character_id.character_id, }, ActionPrompt::TraitorIntro { .. } | ActionPrompt::Bloodletter { .. } | ActionPrompt::Protector { .. } | ActionPrompt::Gravedigger { .. } | ActionPrompt::Hunter { .. } | ActionPrompt::Militia { .. } | ActionPrompt::MapleWolf { .. } | ActionPrompt::Guardian { .. } | ActionPrompt::Adjudicator { .. } | ActionPrompt::PowerSeer { .. } | ActionPrompt::Mortician { .. } | ActionPrompt::BeholderChooses { .. } | ActionPrompt::MasonLeaderRecruit { .. } | ActionPrompt::Empath { .. } | ActionPrompt::Vindicator { .. } | ActionPrompt::PyreMaster { .. } | ActionPrompt::WolfPackKill { .. } | ActionPrompt::AlphaWolf { .. } | ActionPrompt::DireWolf { .. } | ActionPrompt::LoneWolfKill { .. } | ActionPrompt::Seer { .. } | ActionPrompt::Arcanist { .. } | ActionPrompt::WolvesIntro { .. } | ActionPrompt::RoleChange { .. } | ActionPrompt::ElderReveal { .. } | ActionPrompt::CoverOfDarkness => return None, }) } pub const fn character_id(&self) -> Option { match self { StoryActionPrompt::MasonsWake { .. } => None, StoryActionPrompt::WolfPackKill { killing_wolf: character_id, .. } | StoryActionPrompt::Seer { character_id, .. } | StoryActionPrompt::Protector { character_id, .. } | StoryActionPrompt::Arcanist { character_id, .. } | StoryActionPrompt::Gravedigger { character_id, .. } | StoryActionPrompt::Hunter { character_id, .. } | StoryActionPrompt::Militia { character_id, .. } | StoryActionPrompt::MapleWolf { character_id, .. } | StoryActionPrompt::Guardian { character_id, .. } | StoryActionPrompt::Adjudicator { character_id, .. } | StoryActionPrompt::PowerSeer { character_id, .. } | StoryActionPrompt::Mortician { character_id, .. } | StoryActionPrompt::Beholder { character_id, .. } | StoryActionPrompt::MasonLeaderRecruit { character_id, .. } | StoryActionPrompt::Empath { character_id, .. } | StoryActionPrompt::Vindicator { character_id, .. } | StoryActionPrompt::PyreMaster { character_id, .. } | StoryActionPrompt::Shapeshifter { character_id, .. } | StoryActionPrompt::AlphaWolf { character_id, .. } | StoryActionPrompt::DireWolf { character_id, .. } | StoryActionPrompt::LoneWolfKill { character_id, .. } | StoryActionPrompt::Insomniac { character_id, .. } | StoryActionPrompt::Bloodletter { character_id, .. } | StoryActionPrompt::BeholderWakes { character_id, .. } => Some(*character_id), } } } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct GameStory { pub starting_village: Village, pub changes: HashMap, } impl GameStory { pub fn new(starting_village: Village) -> Self { Self { starting_village, changes: HashMap::new(), } } pub fn add(&mut self, time: GameTime, changes: GameActions) -> Result<()> { if self.changes.contains_key(&time) { return Err(GameError::ChangesAlreadySet(time)); } self.changes.insert(time, changes); Ok(()) } pub fn final_village(&self) -> Result { let mut village = self.starting_village.clone(); for (_, actions) in self.iter() { village = match actions { GameActions::DayDetails(day_details) => village.with_day_changes(day_details)?, GameActions::NightDetails(night_details) => { village.with_night_changes(&night_details.changes)?.0 } }; } Ok(village) } pub fn village_at(&self, at_time: GameTime) -> Result> { let mut village = self.starting_village.clone(); for (time, actions) in self.iter() { village = match actions { GameActions::DayDetails(day_details) => village.with_day_changes(day_details)?, GameActions::NightDetails(night_details) => { village.with_night_changes(&night_details.changes)?.0 } }; if time == at_time { return Ok(Some(village)); } } Ok(None) } pub fn iter<'a>(&'a self) -> StoryIterator<'a> { StoryIterator { story: self, time: self.starting_village.time(), } } } pub struct StoryIterator<'a> { story: &'a GameStory, time: GameTime, } impl<'a> Iterator for StoryIterator<'a> { type Item = (GameTime, &'a GameActions); fn next(&mut self) -> Option { match self.story.changes.get(&self.time) { Some(changes) => { let changes_time = self.time; self.time = self.time.next(); Some((changes_time, changes)) } None => None, } } }