werewolves/werewolves-proto/src/game/village/apply.rs

185 lines
7.1 KiB
Rust

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, RoleBlock, RoleTitle},
};
type Result<T> = core::result::Result<T, GameError>;
impl Village {
pub fn with_day_changes(&self, day_changes: &[DayDetail]) -> Result<Self> {
let mut new_village = self.clone();
let executions = day_changes
.iter()
.map(|c| match c {
DayDetail::Execute(c) => *c,
})
.collect::<Box<[_]>>();
new_village.execute(&executions)?;
Ok(new_village)
}
pub fn with_night_changes(&self, all_changes: &[NightChange]) -> Result<Self> {
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 {
source,
target,
block_type: RoleBlock::Direwolf,
} => {
new_village
.character_by_id_mut(*source)?
.direwolf_mut()?
.replace(*target);
}
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::<Box<[_]>>();
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)
}
}