2025-11-05 20:24:51 +00:00
|
|
|
// 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};
|
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-12 23:48:52 +01:00
|
|
|
game::{Village, night::changes::ChangesLookup},
|
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-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(())
|
|
|
|
|
}
|
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
|
|
|
}
|
|
|
|
|
}
|