// 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 . 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>, ) -> Result<()> { match self { KillOutcome::Single(character_id, died_to) => { 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); } 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, target: CharacterId, protection: &Protection, night: NonZeroU8, ) -> Option { match protection { Protection::Guardian { source, guarding: true, } => Some(KillOutcome::Guarding { original_killer: killer, guardian: *source, 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<'_>, target: CharacterId, died_to: &DiedTo, night: u8, village: &Village, ) -> Result> { if let DiedTo::MapleWolf { source, night, starves_if_fails: true, } = died_to && let Some(protection) = changes.protected_take(target) { return Ok(Some( 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( killing_wolf.character_id(), died_to, target, &protection, *night, )); } None => { // Wolf kill went through -- can kill shifter return Ok(Some(KillOutcome::Single( *ss_source, DiedTo::Shapeshift { into: target, night: *night, }, ))); } }; } let protection = match changes.protected_take(target) { Some(prot) => prot, None => return Ok(Some(KillOutcome::Single(target, died_to.clone()))), }; match protection { Protection::Guardian { source, guarding: true, } => Ok(Some(KillOutcome::Guarding { original_killer: died_to .killer() .ok_or(GameError::GuardianInvalidOriginalKill)?, original_target: target, original_kill: died_to.clone(), guardian: source, night: NonZeroU8::new(night).unwrap(), })), Protection::Guardian { guarding: false, .. } | Protection::Vindicator { .. } | Protection::Protector { .. } => Ok(None), } }