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

322 lines
13 KiB
Rust

// 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 <https://www.gnu.org/licenses/>.
use core::{num::NonZeroU8, ops::Not};
use crate::{
aura::Aura,
diedto::DiedTo,
error::GameError,
game::{
GameTime, Village,
kill::{self, KillOutcome},
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, Box<[NightChange]>)> {
let night = match self.time {
GameTime::Day { .. } => return Err(GameError::NotNight),
GameTime::Night { number } => number,
};
let changes = ChangesLookup::new(all_changes);
let mut new_village = self.clone();
// recorded changes: changes sans failed kills, actions failed due to blocks, etc
let mut recorded_changes = all_changes.to_vec();
// dispose of the current drunk token for every drunk in the village
new_village
.characters_mut()
.iter_mut()
.filter_map(|c| {
c.auras_mut().iter_mut().find_map(|a| match a {
Aura::Drunk(bag) => Some(bag),
_ => None,
})
})
.for_each(|bag| {
// dispose of a token
let _ = bag.pull();
});
for change in all_changes {
match change {
NightChange::ApplyAura { target, aura, .. } => {
let target = new_village.character_by_id_mut(*target)?;
target.apply_aura(aura.clone());
}
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()?.target.replace(*target);
if changes
.died_to(hunter_character.character_id(), night, self)?
.is_some()
&& let Some(kill) = kill::resolve_kill(
&changes,
*target,
&DiedTo::Hunter {
killer: *source,
night: NonZeroU8::new(night)
.ok_or(GameError::CannotHappenOnNightZero)?,
},
night,
&new_village,
)?
{
kill.apply_to_village(&mut new_village, Some(&mut recorded_changes))?;
}
}
NightChange::Kill { target, died_to } => {
if let Some(kill) = kill::resolve_kill(&changes, *target, died_to, night, self)?
{
if let KillOutcome::Guarding {
guardian,
original_kill,
..
} = &kill
{
recorded_changes.retain(|c| c != change);
recorded_changes.push(NightChange::Kill {
target: *guardian,
died_to: original_kill.clone(),
});
}
kill.apply_to_village(&mut new_village, Some(&mut recorded_changes))?;
if let DiedTo::MapleWolf { source, .. } = died_to
&& let Ok(maple) = new_village.character_by_id_mut(*source)
{
*maple.maple_wolf_mut()?.last_kill_on_night = night;
}
} else {
recorded_changes.retain(|c| c != change);
}
match died_to {
DiedTo::Militia { killer, .. } => {
new_village
.character_by_id_mut(*killer)?
.militia_mut()?
.targeted
.replace(*target);
}
DiedTo::AlphaWolf { killer, .. } => {
new_village
.character_by_id_mut(*killer)?
.alpha_wolf_mut()?
.killed
.replace(*target);
}
_ => {}
};
}
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().shifted_into.replace(*target);
ss.kill(DiedTo::Shapeshift {
into: *target,
night: NonZeroU8::new(night).unwrap(),
});
// role change pushed in [apply_shapeshift]
} else {
recorded_changes.retain(|c| c != change);
}
}
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()?
.last_protected
.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)?
.dire_wolf_mut()?
.last_blocked
.replace(*target);
recorded_changes.retain(|c| {
matches!(c, NightChange::RoleBlock { .. })
|| c.target().map(|t| t == *target).unwrap_or_default().not()
});
}
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,
},
);
recorded_changes.retain(|c| c != change);
} 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()?
.cursed = true;
}
NightChange::LostAura { character, aura } => {
new_village
.character_by_id_mut(*character)?
.remove_aura(aura.title());
}
NightChange::Protection { protection, target } => {
let target_ident = new_village.character_by_id(*target)?.identity();
match protection {
Protection::Guardian {
source,
guarding: true,
} => {
new_village
.character_by_id_mut(*source)?
.guardian_mut()?
.last_protected
.replace(PreviousGuardianAction::Guard(target_ident));
}
Protection::Guardian {
source,
guarding: false,
} => {
new_village
.character_by_id_mut(*source)?
.guardian_mut()?
.last_protected
.replace(PreviousGuardianAction::Protect(target_ident));
}
Protection::Protector { source } => {
new_village
.character_by_id_mut(*source)?
.protector_mut()?
.last_protected
.replace(*target);
}
Protection::Vindicator { .. } => {}
}
}
}
}
// black knights death
for knight in new_village
.characters_mut()
.into_iter()
.filter(|k| k.alive())
.filter(|k| {
k.black_knight_ref()
.ok()
.and_then(|t| (*t.attacked).clone())
.is_some()
})
.filter(|k| changes.killed(k.character_id()).is_none())
{
knight.black_knight_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, recorded_changes.into_boxed_slice()))
}
}