use core::num::NonZeroU8; use crate::{ diedto::DiedTo, error::GameError, game::{ GameTime, Village, kill, night::changes::{ChangesLookup, NightChange}, story::DayDetail, }, player::Protection, role::{PYREMASTER_VILLAGER_KILLS_TO_DIE, PreviousGuardianAction, RoleTitle}, }; type Result = core::result::Result; impl Village { pub fn with_day_changes(&self, day_changes: &[DayDetail]) -> Result { let mut new_village = self.clone(); let executions = day_changes .iter() .map(|c| match c { DayDetail::Execute(c) => *c, }) .collect::>(); new_village.execute(&executions)?; Ok(new_village) } pub fn with_night_changes(&self, all_changes: &[NightChange]) -> Result { let night = match self.time { GameTime::Day { .. } => return Err(GameError::NotNight), GameTime::Night { number } => number, }; let mut changes = ChangesLookup::new(all_changes); let mut new_village = self.clone(); for change in all_changes { match change { NightChange::ElderReveal { elder } => { new_village.character_by_id_mut(*elder)?.elder_reveal() } NightChange::RoleChange(character_id, role_title) => new_village .character_by_id_mut(*character_id)? .role_change(*role_title, GameTime::Night { number: night })?, NightChange::HunterTarget { source, target } => { let hunter_character = new_village.character_by_id_mut(*source).unwrap(); hunter_character.hunter_mut()?.replace(*target); if changes.killed(*source).is_some() && changes.protected(source).is_none() && changes.protected(target).is_none() { new_village .character_by_id_mut(*target) .unwrap() .kill(DiedTo::Hunter { killer: *source, night: NonZeroU8::new(night).unwrap(), }) } } NightChange::Kill { target, died_to } => { if let Some(kill) = kill::resolve_kill(&mut changes, *target, died_to, night, self)? { kill.apply_to_village(&mut new_village)?; } } NightChange::Shapeshift { source, into } => { if let Some(target) = changes.wolf_pack_kill_target() && changes.protected(target).is_none() { if *target != *into { log::error!("shapeshift into({into}) != target({target})"); continue; } let ss = new_village.character_by_id_mut(*source).unwrap(); ss.shapeshifter_mut().unwrap().replace(*target); ss.kill(DiedTo::Shapeshift { into: *target, night: NonZeroU8::new(night).unwrap(), }); // role change pushed in [apply_shapeshift] } } NightChange::Protection { target, protection: Protection::Guardian { source, guarding }, } => { let target = new_village.character_by_id(*target)?.identity(); new_village .character_by_id_mut(*source)? .guardian_mut()? .replace(if *guarding { PreviousGuardianAction::Guard(target) } else { PreviousGuardianAction::Protect(target) }); } NightChange::RoleBlock { .. } | NightChange::Protection { .. } => {} NightChange::MasonRecruit { mason_leader, recruiting, } => { if new_village.character_by_id(*recruiting)?.is_wolf() { new_village.character_by_id_mut(*mason_leader)?.kill( DiedTo::MasonLeaderRecruitFail { night, tried_recruiting: *recruiting, }, ); } else { new_village .character_by_id_mut(*mason_leader)? .mason_leader_mut()? .recruit(*recruiting); } } NightChange::EmpathFoundScapegoat { empath, scapegoat } => { new_village .character_by_id_mut(*scapegoat)? .role_change(RoleTitle::Villager, GameTime::Night { number: night })?; *new_village.character_by_id_mut(*empath)?.empath_mut()? = true; } } } // black knights death for knight in new_village .characters_mut() .into_iter() .filter(|k| k.alive()) .filter(|k| k.black_knight().ok().and_then(|t| (*t).clone()).is_some()) .filter(|k| changes.killed(k.character_id()).is_none()) { knight.black_knight_kill()?.kill(); } // pyre masters death let village_dead = new_village .characters() .into_iter() .filter(|c| c.is_village()) .filter_map(|c| c.died_to().cloned()) .filter_map(|c| c.killer().map(|k| (k, c))) .collect::>(); for pyremaster in new_village .living_characters_by_role_mut(RoleTitle::PyreMaster) .into_iter() .filter(|p| { village_dead .iter() .filter(|(k, _)| *k == p.character_id()) .count() >= PYREMASTER_VILLAGER_KILLS_TO_DIE.get() as usize }) { if let Some(night) = NonZeroU8::new(night) { pyremaster.kill(DiedTo::PyreMasterLynchMob { night, source: pyremaster.character_id(), }); } } if new_village.is_game_over().is_none() { new_village.to_day()?; } Ok(new_village) } }