// 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::{ 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::InvalidMessageForGameState), }; } 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, )), })); } } }; match current_prompt { 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.village.character_by_id(*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.village.character_by_id(*marked1)?.alignment() == self.village.character_by_id(*marked2)?.alignment(); Ok(ResponseOutcome::ActionComplete(ActionComplete { result: ActionResult::Arcanist(AlignmentEq::new(same)), change: None, })) } ActionPrompt::Gravedigger { marked: Some(marked), .. } => { let dig_role = self.village.character_by_id(*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::InvalidMessageForGameState)?, }, }), })), 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: Some(NightChange::Kill { target: *marked, died_to: DiedTo::MapleWolf { source: character_id.character_id, night: NonZeroU8::new(self.night) .ok_or(GameError::InvalidMessageForGameState)?, 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::InvalidMessageForGameState)?, }, }), })), 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::InvalidMessageForGameState), }, })) } 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::InvalidMessageForGameState)?, }, }), })), 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.village.character_by_id(*marked)?.killer(), }, change: None, } .into()), ActionPrompt::PowerSeer { marked: Some(marked), .. } => Ok(ActionComplete { result: ActionResult::PowerSeer { powerful: self.village.character_by_id(*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.dies_tonight(*marked)? { Ok(ActionComplete { result: if matches!(result, ActionResult::RoleBlocked) { 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::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::InvalidMessageForGameState), } } }