2025-10-03 00:00:39 +01:00
|
|
|
use core::{num::NonZeroU8, ops::Not};
|
2025-09-28 02:13:34 +01:00
|
|
|
|
|
|
|
|
use super::Result;
|
|
|
|
|
use crate::{
|
2025-10-06 20:45:15 +01:00
|
|
|
character::CharacterId,
|
2025-09-28 02:13:34 +01:00
|
|
|
diedto::DiedTo,
|
|
|
|
|
error::GameError,
|
2025-10-03 00:00:39 +01:00
|
|
|
game::{Village, night::NightChange},
|
2025-10-06 20:45:15 +01:00
|
|
|
player::Protection,
|
2025-09-28 02:13:34 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
#[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) -> Result<()> {
|
|
|
|
|
match self {
|
2025-10-03 00:00:39 +01:00
|
|
|
KillOutcome::Single(character_id, died_to) => {
|
2025-10-06 20:45:15 +01:00
|
|
|
village.character_by_id_mut(character_id)?.kill(died_to);
|
2025-10-03 00:00:39 +01:00
|
|
|
Ok(())
|
|
|
|
|
}
|
2025-09-28 02:13:34 +01:00
|
|
|
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.
|
2025-10-06 20:45:15 +01:00
|
|
|
village.character_by_id(guardian)?;
|
2025-09-28 02:13:34 +01:00
|
|
|
village
|
2025-10-06 20:45:15 +01:00
|
|
|
.character_by_id_mut(original_killer)?
|
2025-09-28 02:13:34 +01:00
|
|
|
.kill(DiedTo::GuardianProtecting {
|
|
|
|
|
night,
|
2025-10-05 10:54:47 +01:00
|
|
|
source: guardian,
|
2025-09-28 02:13:34 +01:00
|
|
|
protecting: original_target,
|
|
|
|
|
protecting_from: original_killer,
|
|
|
|
|
protecting_from_cause: Box::new(original_kill.clone()),
|
|
|
|
|
});
|
2025-10-06 20:45:15 +01:00
|
|
|
village.character_by_id_mut(guardian)?.kill(original_kill);
|
2025-09-28 02:13:34 +01:00
|
|
|
Ok(())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn resolve_protection(
|
|
|
|
|
killer: CharacterId,
|
|
|
|
|
killed_with: &DiedTo,
|
2025-10-06 21:59:44 +01:00
|
|
|
target: CharacterId,
|
2025-09-28 02:13:34 +01:00
|
|
|
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,
|
2025-09-28 02:13:34 +01:00
|
|
|
original_kill: killed_with.clone(),
|
|
|
|
|
night,
|
|
|
|
|
}),
|
|
|
|
|
Protection::Guardian {
|
|
|
|
|
source: _,
|
|
|
|
|
guarding: false,
|
|
|
|
|
}
|
2025-10-06 20:45:15 +01:00
|
|
|
| Protection::Vindicator { .. }
|
2025-09-28 02:13:34 +01:00
|
|
|
| Protection::Protector { source: _ } => None,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn resolve_kill(
|
|
|
|
|
changes: &mut ChangesLookup<'_>,
|
2025-10-06 21:59:44 +01:00
|
|
|
target: CharacterId,
|
2025-09-28 02:13:34 +01:00
|
|
|
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 }),
|
2025-09-28 02:13:34 +01:00
|
|
|
),
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
if let DiedTo::Wolfpack {
|
|
|
|
|
night,
|
|
|
|
|
killing_wolf,
|
|
|
|
|
} = died_to
|
|
|
|
|
&& let Some(ss_source) = changes.shapeshifter()
|
|
|
|
|
{
|
2025-10-06 20:45:15 +01:00
|
|
|
let killing_wolf = village.character_by_id(*killing_wolf)?;
|
2025-09-28 02:13:34 +01:00
|
|
|
|
|
|
|
|
match changes.protected_take(target) {
|
|
|
|
|
Some(protection) => {
|
|
|
|
|
return Ok(resolve_protection(
|
2025-10-05 10:54:47 +01:00
|
|
|
killing_wolf.character_id(),
|
2025-09-28 02:13:34 +01:00
|
|
|
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,
|
2025-09-28 02:13:34 +01:00
|
|
|
DiedTo::Shapeshift {
|
2025-10-06 21:59:44 +01:00
|
|
|
into: target,
|
2025-09-28 02:13:34 +01:00
|
|
|
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-09-28 02:13:34 +01:00
|
|
|
};
|
|
|
|
|
|
2025-10-03 00:00:39 +01:00
|
|
|
match protection {
|
2025-09-28 02:13:34 +01:00
|
|
|
Protection::Guardian {
|
|
|
|
|
source,
|
|
|
|
|
guarding: true,
|
|
|
|
|
} => Ok(Some(KillOutcome::Guarding {
|
2025-10-06 20:45:15 +01:00
|
|
|
original_killer: died_to
|
2025-09-28 02:13:34 +01:00
|
|
|
.killer()
|
2025-10-05 10:54:47 +01:00
|
|
|
.ok_or(GameError::GuardianInvalidOriginalKill)?,
|
2025-10-06 21:59:44 +01:00
|
|
|
original_target: target,
|
2025-09-28 02:13:34 +01:00
|
|
|
original_kill: died_to.clone(),
|
2025-10-05 10:54:47 +01:00
|
|
|
guardian: source,
|
2025-09-28 02:13:34 +01:00
|
|
|
night: NonZeroU8::new(night).unwrap(),
|
|
|
|
|
})),
|
|
|
|
|
Protection::Guardian {
|
2025-10-06 20:45:15 +01:00
|
|
|
guarding: false, ..
|
2025-09-28 02:13:34 +01:00
|
|
|
}
|
2025-10-06 20:45:15 +01:00
|
|
|
| Protection::Vindicator { .. }
|
|
|
|
|
| Protection::Protector { .. } => Ok(None),
|
2025-09-28 02:13:34 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub struct ChangesLookup<'a>(&'a [NightChange], Vec<usize>);
|
|
|
|
|
|
|
|
|
|
impl<'a> ChangesLookup<'a> {
|
|
|
|
|
pub fn new(changes: &'a [NightChange]) -> Self {
|
|
|
|
|
Self(changes, Vec::new())
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-06 21:59:44 +01:00
|
|
|
pub fn killed(&self, target: CharacterId) -> Option<&'a DiedTo> {
|
2025-09-28 02:13:34 +01:00
|
|
|
self.0.iter().enumerate().find_map(|(idx, c)| {
|
|
|
|
|
self.1
|
|
|
|
|
.contains(&idx)
|
|
|
|
|
.not()
|
|
|
|
|
.then(|| match c {
|
2025-10-06 21:59:44 +01:00
|
|
|
NightChange::Kill { target: t, died_to } => (*t == target).then_some(died_to),
|
2025-09-28 02:13:34 +01:00
|
|
|
_ => None,
|
|
|
|
|
})
|
|
|
|
|
.flatten()
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-06 21:59:44 +01:00
|
|
|
pub fn protected_take(&mut self, target: CharacterId) -> Option<Protection> {
|
2025-09-28 02:13:34 +01:00
|
|
|
if let Some((idx, c)) = self.0.iter().enumerate().find_map(|(idx, c)| {
|
|
|
|
|
self.1
|
|
|
|
|
.contains(&idx)
|
|
|
|
|
.not()
|
|
|
|
|
.then(|| match c {
|
|
|
|
|
NightChange::Protection {
|
|
|
|
|
target: t,
|
|
|
|
|
protection,
|
2025-10-06 21:59:44 +01:00
|
|
|
} => (*t == target).then_some((idx, protection)),
|
2025-09-28 02:13:34 +01:00
|
|
|
_ => None,
|
|
|
|
|
})
|
|
|
|
|
.flatten()
|
|
|
|
|
}) {
|
|
|
|
|
self.1.push(idx);
|
2025-10-03 00:00:39 +01:00
|
|
|
Some(c.clone())
|
2025-09-28 02:13:34 +01:00
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
pub fn protected(&self, target: &CharacterId) -> Option<&'a Protection> {
|
|
|
|
|
self.0.iter().enumerate().find_map(|(idx, c)| {
|
|
|
|
|
self.1
|
|
|
|
|
.contains(&idx)
|
|
|
|
|
.not()
|
|
|
|
|
.then(|| match c {
|
|
|
|
|
NightChange::Protection {
|
|
|
|
|
target: t,
|
|
|
|
|
protection,
|
|
|
|
|
} => (t == target).then_some(protection),
|
|
|
|
|
_ => None,
|
|
|
|
|
})
|
|
|
|
|
.flatten()
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn shapeshifter(&self) -> Option<&'a CharacterId> {
|
|
|
|
|
self.0.iter().enumerate().find_map(|(idx, c)| {
|
|
|
|
|
self.1
|
|
|
|
|
.contains(&idx)
|
|
|
|
|
.not()
|
|
|
|
|
.then_some(match c {
|
|
|
|
|
NightChange::Shapeshift { source } => Some(source),
|
|
|
|
|
_ => None,
|
|
|
|
|
})
|
|
|
|
|
.flatten()
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn wolf_pack_kill_target(&self) -> Option<&'a CharacterId> {
|
|
|
|
|
self.0.iter().enumerate().find_map(|(idx, c)| {
|
|
|
|
|
self.1
|
|
|
|
|
.contains(&idx)
|
|
|
|
|
.not()
|
|
|
|
|
.then_some(match c {
|
|
|
|
|
NightChange::Kill {
|
|
|
|
|
target,
|
|
|
|
|
died_to:
|
|
|
|
|
DiedTo::Wolfpack {
|
|
|
|
|
night: _,
|
|
|
|
|
killing_wolf: _,
|
|
|
|
|
},
|
|
|
|
|
} => Some(target),
|
|
|
|
|
_ => None,
|
|
|
|
|
})
|
|
|
|
|
.flatten()
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|