// 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; use crate::{ aura::Aura, bag::DrunkRoll, diedto::DiedTo, error::GameError, game::night::{ ActionComplete, CurrentResult, Night, NightState, ResponseOutcome, changes::NightChange, }, message::night::{ActionPrompt, ActionResponse, ActionResult}, player::Protection, role::{AlignmentEq, PreviousGuardianAction, RoleBlock, RoleTitle}, }; type Result = core::result::Result; impl Night { pub(super) fn process(&self, resp: ActionResponse) -> Result { let current_prompt = match &self.night_state { NightState::Active { current_result: CurrentResult::GoBackToSleepAfterShown { .. }, .. } | NightState::Active { current_result: CurrentResult::Result(ActionResult::GoBackToSleep), .. } => return Err(GameError::NightNeedsNext), NightState::Active { current_prompt, current_result: CurrentResult::None, .. } | NightState::Active { current_prompt, current_result: CurrentResult::Result(_), .. } => current_prompt, NightState::Complete => return Err(GameError::NightOver), }; match resp { ActionResponse::MarkTarget(mark) => { return Ok(ResponseOutcome::PromptUpdate( current_prompt.with_mark(mark)?, )); } ActionResponse::Shapeshift => { return match current_prompt { ActionPrompt::Shapeshifter { character_id: source, } => Ok(ResponseOutcome::ActionComplete(ActionComplete { result: ActionResult::GoBackToSleep, change: Some(NightChange::Shapeshift { source: source.character_id, into: self .changes_from_actions() .into_iter() .find_map(|c| match c { NightChange::Kill { target, died_to: DiedTo::Wolfpack { .. }, } => Some(target), _ => None, }) .ok_or(GameError::InvalidTarget)?, }), })), _ => Err(GameError::ShapeshiftingIsForShapeshifters), }; } ActionResponse::Continue => { if let ActionPrompt::Insomniac { character_id } = current_prompt { return Ok(ActionComplete { result: ActionResult::Insomniac( self.get_visits_for(character_id.character_id), ), change: None, } .into()); } if let ActionPrompt::RoleChange { character_id, new_role, } = current_prompt { return Ok(ResponseOutcome::ActionComplete(ActionComplete { result: ActionResult::GoBackToSleep, change: Some(NightChange::RoleChange( character_id.character_id, *new_role, )), })); } } ActionResponse::ContinueToResult => return self.process(ActionResponse::Continue), }; match current_prompt { ActionPrompt::TraitorIntro { .. } => Ok(ActionComplete { result: ActionResult::GoBackToSleep, change: None, } .into()), ActionPrompt::Bloodletter { character_id, marked: Some(marked), .. } => Ok(ActionComplete { result: ActionResult::GoBackToSleep, change: Some(NightChange::ApplyAura { source: character_id.character_id, aura: Aura::Bloodlet { night: self.night }, target: *marked, }), } .into()), ActionPrompt::LoneWolfKill { character_id, marked: Some(marked), .. } => Ok(ActionComplete { result: ActionResult::GoBackToSleep, change: Some(NightChange::Kill { target: *marked, died_to: DiedTo::LoneWolf { killer: character_id.character_id, night: self.night, }, }), } .into()), ActionPrompt::RoleChange { .. } | ActionPrompt::WolvesIntro { .. } | ActionPrompt::CoverOfDarkness => { Ok(ResponseOutcome::ActionComplete(ActionComplete { result: ActionResult::GoBackToSleep, change: None, })) } ActionPrompt::ElderReveal { character_id } => { Ok(ResponseOutcome::ActionComplete(ActionComplete { result: ActionResult::GoBackToSleep, change: Some(NightChange::ElderReveal { elder: character_id.character_id, }), })) } ActionPrompt::Seer { marked: Some(marked), .. } => { let alignment = self.character_with_current_auras(*marked)?.alignment(); Ok(ResponseOutcome::ActionComplete(ActionComplete { result: ActionResult::Seer(alignment), change: None, })) } ActionPrompt::Protector { marked: Some(marked), character_id, .. } => Ok(ResponseOutcome::ActionComplete(ActionComplete { result: ActionResult::GoBackToSleep, change: Some(NightChange::Protection { target: *marked, protection: Protection::Protector { source: character_id.character_id, }, }), })), ActionPrompt::Arcanist { marked: (Some(marked1), Some(marked2)), .. } => { let same = self.character_with_current_auras(*marked1)?.alignment() == self.character_with_current_auras(*marked2)?.alignment(); Ok(ResponseOutcome::ActionComplete(ActionComplete { result: ActionResult::Arcanist(AlignmentEq::new(same)), change: None, })) } ActionPrompt::Gravedigger { marked: Some(marked), .. } => { let dig_role = self .character_with_current_auras(*marked)? .gravedigger_dig(); Ok(ResponseOutcome::ActionComplete(ActionComplete { result: ActionResult::GraveDigger(dig_role), change: None, })) } ActionPrompt::Hunter { character_id, marked: Some(marked), .. } => Ok(ResponseOutcome::ActionComplete(ActionComplete { result: ActionResult::GoBackToSleep, change: Some(NightChange::HunterTarget { source: character_id.character_id, target: *marked, }), })), ActionPrompt::Militia { character_id, marked: Some(marked), .. } => Ok(ResponseOutcome::ActionComplete(ActionComplete { result: ActionResult::GoBackToSleep, change: Some(NightChange::Kill { target: *marked, died_to: DiedTo::Militia { killer: character_id.character_id, night: NonZeroU8::new(self.night) .ok_or(GameError::CannotHappenOnNightZero)?, }, }), })), ActionPrompt::Militia { marked: None, .. } => { Ok(ResponseOutcome::ActionComplete(Default::default())) } ActionPrompt::MapleWolf { character_id, kill_or_die, marked: Some(marked), .. } => Ok(ResponseOutcome::ActionComplete(ActionComplete { result: ActionResult::GoBackToSleep, change: NonZeroU8::new(self.night).map(|night| NightChange::Kill { target: *marked, died_to: DiedTo::MapleWolf { night, source: character_id.character_id, starves_if_fails: *kill_or_die, }, }), })), ActionPrompt::MapleWolf { marked: None, .. } => { Ok(ResponseOutcome::ActionComplete(Default::default())) } ActionPrompt::Guardian { character_id, previous: None, marked: Some(marked), .. } => Ok(ResponseOutcome::ActionComplete(ActionComplete { result: ActionResult::GoBackToSleep, change: Some(NightChange::Protection { target: *marked, protection: Protection::Guardian { source: character_id.character_id, guarding: false, }, }), })), ActionPrompt::Guardian { character_id, previous: Some(PreviousGuardianAction::Guard(prev_target)), marked: Some(marked), .. } => { if prev_target.character_id == *marked { return Err(GameError::InvalidTarget); } Ok(ResponseOutcome::ActionComplete(ActionComplete { result: ActionResult::GoBackToSleep, change: Some(NightChange::Protection { target: *marked, protection: Protection::Guardian { source: character_id.character_id, guarding: false, }, }), })) } ActionPrompt::Guardian { character_id, previous: Some(PreviousGuardianAction::Protect(prev_protect)), marked: Some(marked), .. } => Ok(ResponseOutcome::ActionComplete(ActionComplete { result: ActionResult::GoBackToSleep, change: Some(NightChange::Protection { target: *marked, protection: Protection::Guardian { source: character_id.character_id, guarding: prev_protect.character_id == *marked, }, }), })), ActionPrompt::WolfPackKill { marked: Some(marked), .. } => Ok(ResponseOutcome::ActionComplete(ActionComplete { result: ActionResult::GoBackToSleep, change: Some(NightChange::Kill { target: *marked, died_to: DiedTo::Wolfpack { killing_wolf: self .village .killing_wolf() .ok_or(GameError::NoWolves)? .character_id(), night: NonZeroU8::new(self.night) .ok_or(GameError::CannotHappenOnNightZero)?, }, }), })), ActionPrompt::Shapeshifter { character_id } => { Ok(ResponseOutcome::ActionComplete(ActionComplete { result: ActionResult::GoBackToSleep, change: match &resp { ActionResponse::Continue => None, ActionResponse::Shapeshift => Some(NightChange::Shapeshift { source: character_id.character_id, into: self .changes_from_actions() .into_iter() .find_map(|c| match c { NightChange::Kill { target, died_to: DiedTo::Wolfpack { .. }, } => Some(target), _ => None, }) .ok_or(GameError::InvalidTarget)?, }), _ => return Err(GameError::ShapeshiftingIsForShapeshifters), }, })) } ActionPrompt::AlphaWolf { character_id, marked: Some(marked), .. } => Ok(ResponseOutcome::ActionComplete(ActionComplete { result: ActionResult::GoBackToSleep, change: Some(NightChange::Kill { target: *marked, died_to: DiedTo::AlphaWolf { killer: character_id.character_id, night: NonZeroU8::new(self.night) .ok_or(GameError::CannotHappenOnNightZero)?, }, }), })), ActionPrompt::AlphaWolf { marked: None, .. } => { Ok(ResponseOutcome::ActionComplete(Default::default())) } ActionPrompt::DireWolf { character_id, marked: Some(marked), .. } => Ok(ResponseOutcome::ActionComplete(ActionComplete { result: ActionResult::GoBackToSleep, change: Some(NightChange::RoleBlock { source: character_id.character_id, target: *marked, block_type: RoleBlock::Direwolf, }), })), ActionPrompt::Adjudicator { marked: Some(marked), .. } => Ok(ActionComplete { result: ActionResult::Adjudicator { killer: self.character_with_current_auras(*marked)?.killer(), }, change: None, } .into()), ActionPrompt::PowerSeer { marked: Some(marked), .. } => Ok(ActionComplete { result: ActionResult::PowerSeer { powerful: self.character_with_current_auras(*marked)?.powerful(), }, change: None, } .into()), ActionPrompt::Mortician { marked: Some(marked), .. } => Ok(ActionComplete { result: ActionResult::Mortician( self.village .character_by_id(*marked)? .died_to() .ok_or(GameError::InvalidTarget)? .title(), ), change: None, } .into()), ActionPrompt::Beholder { marked: Some(marked), .. } => { if let Some(result) = self.used_actions.iter().find_map(|(prompt, result, _)| { prompt.matches_beholding(*marked).then_some(result) }) && self.died_to_tonight(*marked)?.is_some() { Ok(ActionComplete { result: if matches!(result, ActionResult::RoleBlocked | ActionResult::Drunk) { ActionResult::BeholderSawNothing } else { result.clone() }, change: None, } .into()) } else { Ok(ActionComplete { result: ActionResult::GoBackToSleep, change: None, } .into()) } } ActionPrompt::MasonsWake { .. } => Ok(ActionComplete { result: ActionResult::GoBackToSleep, change: None, } .into()), ActionPrompt::MasonLeaderRecruit { character_id, marked: Some(marked), .. } => Ok(ActionComplete { result: ActionResult::Continue, change: Some(NightChange::MasonRecruit { mason_leader: character_id.character_id, recruiting: *marked, }), } .into()), ActionPrompt::Empath { character_id, marked: Some(marked), .. } => { let marked = self.village.character_by_id(*marked)?; let scapegoat = marked.role_title() == RoleTitle::Scapegoat; Ok(ActionComplete { result: ActionResult::Empath { scapegoat }, change: scapegoat.then(|| NightChange::EmpathFoundScapegoat { empath: character_id.character_id, scapegoat: marked.character_id(), }), } .into()) } ActionPrompt::Vindicator { character_id, marked: Some(marked), .. } => Ok(ActionComplete { result: ActionResult::GoBackToSleep, change: Some(NightChange::Protection { target: *marked, protection: Protection::Vindicator { source: character_id.character_id, }, }), } .into()), ActionPrompt::Insomniac { .. } => Ok(ActionComplete { result: ActionResult::GoBackToSleep, change: None, } .into()), ActionPrompt::PyreMaster { character_id, marked: Some(marked), .. } => Ok(ActionComplete { result: ActionResult::GoBackToSleep, change: NonZeroU8::new(self.night).map(|night| NightChange::Kill { target: *marked, died_to: DiedTo::PyreMaster { killer: character_id.character_id, night, }, }), } .into()), ActionPrompt::PyreMaster { marked: None, .. } | ActionPrompt::MasonLeaderRecruit { marked: None, .. } => Ok(ActionComplete { result: ActionResult::GoBackToSleep, change: None, } .into()), ActionPrompt::Bloodletter { marked: None, .. } | ActionPrompt::Adjudicator { marked: None, .. } | ActionPrompt::PowerSeer { marked: None, .. } | ActionPrompt::Mortician { marked: None, .. } | ActionPrompt::Beholder { marked: None, .. } | ActionPrompt::Empath { marked: None, .. } | ActionPrompt::Vindicator { marked: None, .. } | ActionPrompt::Protector { marked: None, .. } | ActionPrompt::Arcanist { marked: (None, None), .. } | ActionPrompt::Arcanist { marked: (None, Some(_)), .. } | ActionPrompt::Arcanist { marked: (Some(_), None), .. } | ActionPrompt::LoneWolfKill { marked: None, .. } | ActionPrompt::Gravedigger { marked: None, .. } | ActionPrompt::Hunter { marked: None, .. } | ActionPrompt::Guardian { marked: None, .. } | ActionPrompt::WolfPackKill { marked: None, .. } | ActionPrompt::DireWolf { marked: None, .. } | ActionPrompt::Seer { marked: None, .. } => Err(GameError::MustSelectTarget), } } pub(super) fn received_response_with_auras( &self, resp: ActionResponse, ) -> Result { let outcome = self.process(resp)?; if matches!( self.current_prompt(), Some((ActionPrompt::TraitorIntro { .. }, _)) | Some((ActionPrompt::RoleChange { .. }, _)) | Some((ActionPrompt::ElderReveal { .. }, _)) ) { return Ok(outcome); } let mut act = match outcome { ResponseOutcome::PromptUpdate(prompt) => { return Ok(ResponseOutcome::PromptUpdate(prompt)); } ResponseOutcome::ActionComplete(ActionComplete { result: ActionResult::Drunk, .. }) | ResponseOutcome::ActionComplete(ActionComplete { result: ActionResult::RoleBlocked, .. }) => return Ok(outcome), ResponseOutcome::ActionComplete(act) => act, }; let Some(char) = self.current_character() else { return Ok(ResponseOutcome::ActionComplete(act)); }; for aura in char.auras() { match aura { Aura::Traitor | Aura::Bloodlet { .. } => continue, Aura::Drunk(bag) => { if bag.peek() == DrunkRoll::Drunk { act.change = None; act.result = ActionResult::Drunk; return Ok(ResponseOutcome::ActionComplete(act)); } } Aura::Insane => { if let Some(insane_result) = act.result.insane() { act.result = insane_result; } } } } Ok(ResponseOutcome::ActionComplete(act)) } }