werewolves/werewolves-proto/src/game/kill.rs

200 lines
6.2 KiB
Rust
Raw Normal View History

// 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/>.
2025-10-03 00:00:39 +01:00
use core::{num::NonZeroU8, ops::Not};
use super::Result;
use crate::{
character::CharacterId,
diedto::DiedTo,
error::GameError,
game::{
Village,
night::changes::{ChangesLookup, NightChange},
},
player::Protection,
};
#[derive(Debug, PartialEq)]
pub enum KillOutcome {
Single(CharacterId, DiedTo),
Guarding {
original_killer: CharacterId,
original_target: CharacterId,
original_kill: DiedTo,
guardian: CharacterId,
night: NonZeroU8,
},
}
impl KillOutcome {
pub fn apply_to_village(
self,
village: &mut Village,
recorded_changes: Option<&mut Vec<NightChange>>,
) -> Result<()> {
match self {
2025-10-03 00:00:39 +01:00
KillOutcome::Single(character_id, died_to) => {
2025-11-07 21:10:17 +00:00
village
.character_by_id_mut(character_id)?
.kill(died_to.clone());
if let DiedTo::Militia { killer, .. } = died_to
&& let Some(existing) = village
.character_by_id_mut(killer)?
.militia_mut()?
.replace(character_id)
{
log::error!("militia kill after already recording a kill on {existing}");
return Err(GameError::MilitiaSpent);
}
2025-10-03 00:00:39 +01:00
Ok(())
}
KillOutcome::Guarding {
original_killer,
original_target,
original_kill,
guardian,
night,
} => {
// check if guardian exists before we mutably borrow killer, which would
// prevent us from borrowing village to check after.
village.character_by_id(guardian)?;
let guardian_kill = DiedTo::GuardianProtecting {
night,
source: guardian,
protecting: original_target,
protecting_from: original_killer,
protecting_from_cause: Box::new(original_kill.clone()),
};
if let Some(recorded_changes) = recorded_changes {
recorded_changes.push(NightChange::Kill {
target: original_killer,
died_to: guardian_kill.clone(),
});
}
village
.character_by_id_mut(original_killer)?
.kill(guardian_kill);
village.character_by_id_mut(guardian)?.kill(original_kill);
Ok(())
}
}
}
}
fn resolve_protection(
killer: CharacterId,
killed_with: &DiedTo,
2025-10-06 21:59:44 +01:00
target: CharacterId,
protection: &Protection,
night: NonZeroU8,
) -> Option<KillOutcome> {
match protection {
Protection::Guardian {
source,
guarding: true,
} => Some(KillOutcome::Guarding {
original_killer: killer,
2025-10-05 10:54:47 +01:00
guardian: *source,
2025-10-06 21:59:44 +01:00
original_target: target,
original_kill: killed_with.clone(),
night,
}),
Protection::Guardian {
source: _,
guarding: false,
}
| Protection::Vindicator { .. }
| Protection::Protector { source: _ } => None,
}
}
pub fn resolve_kill(
changes: &mut ChangesLookup<'_>,
2025-10-06 21:59:44 +01:00
target: CharacterId,
died_to: &DiedTo,
night: u8,
village: &Village,
) -> Result<Option<KillOutcome>> {
if let DiedTo::MapleWolf {
source,
night,
starves_if_fails: true,
} = died_to
&& let Some(protection) = changes.protected_take(target)
{
return Ok(Some(
2025-10-05 10:54:47 +01:00
resolve_protection(*source, died_to, target, &protection, *night).unwrap_or(
KillOutcome::Single(*source, DiedTo::MapleWolfStarved { night: *night }),
),
));
}
if let DiedTo::Wolfpack {
night,
killing_wolf,
} = died_to
&& let Some(ss_source) = changes.shapeshifter()
{
let killing_wolf = village.character_by_id(*killing_wolf)?;
match changes.protected_take(target) {
Some(protection) => {
return Ok(resolve_protection(
2025-10-05 10:54:47 +01:00
killing_wolf.character_id(),
died_to,
target,
&protection,
*night,
));
}
None => {
// Wolf kill went through -- can kill shifter
return Ok(Some(KillOutcome::Single(
2025-10-05 10:54:47 +01:00
*ss_source,
DiedTo::Shapeshift {
2025-10-06 21:59:44 +01:00
into: target,
night: *night,
},
)));
}
};
}
let protection = match changes.protected_take(target) {
Some(prot) => prot,
2025-10-06 21:59:44 +01:00
None => return Ok(Some(KillOutcome::Single(target, died_to.clone()))),
};
2025-10-03 00:00:39 +01:00
match protection {
Protection::Guardian {
source,
guarding: true,
} => Ok(Some(KillOutcome::Guarding {
original_killer: died_to
.killer()
2025-10-05 10:54:47 +01:00
.ok_or(GameError::GuardianInvalidOriginalKill)?,
2025-10-06 21:59:44 +01:00
original_target: target,
original_kill: died_to.clone(),
2025-10-05 10:54:47 +01:00
guardian: source,
night: NonZeroU8::new(night).unwrap(),
})),
Protection::Guardian {
guarding: false, ..
}
| Protection::Vindicator { .. }
| Protection::Protector { .. } => Ok(None),
}
}