use core::num::NonZeroU8; use serde::{Deserialize, Serialize}; use werewolves_macros::{ChecksAs, Titles}; use crate::{ character::CharacterId, diedto::DiedToTitle, error::GameError, message::CharacterIdentity, role::{Alignment, PreviousGuardianAction, RoleTitle}, }; type Result = core::result::Result; #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, PartialOrd)] pub enum ActionType { Cover, WolvesIntro, Protect, WolfPackKill, Direwolf, OtherWolf, Block, Intel, Other, MasonRecruit, MasonsWake, Beholder, RoleChange, } impl ActionType { const fn is_wolfy(&self) -> bool { matches!( self, ActionType::Direwolf | ActionType::OtherWolf | ActionType::WolfPackKill | ActionType::WolvesIntro ) } } #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, ChecksAs, Titles)] pub enum ActionPrompt { #[checks(ActionType::Cover)] CoverOfDarkness, #[checks(ActionType::WolfPackKill)] WolvesIntro { wolves: Box<[(CharacterIdentity, RoleTitle)]>, }, #[checks(ActionType::RoleChange)] RoleChange { character_id: CharacterIdentity, new_role: RoleTitle, }, #[checks(ActionType::RoleChange)] ElderReveal { character_id: CharacterIdentity }, #[checks(ActionType::Intel)] Seer { character_id: CharacterIdentity, living_players: Box<[CharacterIdentity]>, marked: Option, }, #[checks(ActionType::Protect)] Protector { character_id: CharacterIdentity, targets: Box<[CharacterIdentity]>, marked: Option, }, #[checks(ActionType::Intel)] Arcanist { character_id: CharacterIdentity, living_players: Box<[CharacterIdentity]>, marked: (Option, Option), }, #[checks(ActionType::Intel)] Gravedigger { character_id: CharacterIdentity, dead_players: Box<[CharacterIdentity]>, marked: Option, }, #[checks(ActionType::Other)] Hunter { character_id: CharacterIdentity, current_target: Option, living_players: Box<[CharacterIdentity]>, marked: Option, }, #[checks(ActionType::Other)] Militia { character_id: CharacterIdentity, living_players: Box<[CharacterIdentity]>, marked: Option, }, #[checks(ActionType::Other)] MapleWolf { character_id: CharacterIdentity, kill_or_die: bool, living_players: Box<[CharacterIdentity]>, marked: Option, }, #[checks(ActionType::Protect)] Guardian { character_id: CharacterIdentity, previous: Option, living_players: Box<[CharacterIdentity]>, marked: Option, }, #[checks(ActionType::Intel)] Adjudicator { character_id: CharacterIdentity, living_players: Box<[CharacterIdentity]>, marked: Option, }, #[checks(ActionType::Intel)] PowerSeer { character_id: CharacterIdentity, living_players: Box<[CharacterIdentity]>, marked: Option, }, #[checks(ActionType::Intel)] Mortician { character_id: CharacterIdentity, dead_players: Box<[CharacterIdentity]>, marked: Option, }, #[checks(ActionType::Beholder)] Beholder { character_id: CharacterIdentity, living_players: Box<[CharacterIdentity]>, marked: Option, }, #[checks(ActionType::MasonsWake)] MasonsWake { character_id: CharacterIdentity, masons: Box<[CharacterIdentity]>, }, #[checks(ActionType::MasonRecruit)] MasonLeaderRecruit { character_id: CharacterIdentity, recruits_left: NonZeroU8, potential_recruits: Box<[CharacterIdentity]>, marked: Option, }, #[checks(ActionType::Intel)] Empath { character_id: CharacterIdentity, living_players: Box<[CharacterIdentity]>, marked: Option, }, #[checks(ActionType::Protect)] Vindicator { character_id: CharacterIdentity, living_players: Box<[CharacterIdentity]>, marked: Option, }, #[checks(ActionType::Other)] PyreMaster { character_id: CharacterIdentity, living_players: Box<[CharacterIdentity]>, marked: Option, }, #[checks(ActionType::WolfPackKill)] WolfPackKill { living_villagers: Box<[CharacterIdentity]>, marked: Option, }, #[checks(ActionType::OtherWolf)] Shapeshifter { character_id: CharacterIdentity }, #[checks(ActionType::OtherWolf)] AlphaWolf { character_id: CharacterIdentity, living_villagers: Box<[CharacterIdentity]>, marked: Option, }, #[checks(ActionType::Direwolf)] DireWolf { character_id: CharacterIdentity, living_players: Box<[CharacterIdentity]>, marked: Option, }, } impl ActionPrompt { pub(crate) fn matches_beholding(&self, target: CharacterId) -> bool { match self { ActionPrompt::Seer { character_id, .. } | ActionPrompt::Arcanist { character_id, .. } | ActionPrompt::Gravedigger { character_id, .. } | ActionPrompt::Adjudicator { character_id, .. } | ActionPrompt::PowerSeer { character_id, .. } | ActionPrompt::Mortician { character_id, .. } | ActionPrompt::Vindicator { character_id, .. } => character_id.character_id == target, ActionPrompt::Beholder { .. } | ActionPrompt::CoverOfDarkness | ActionPrompt::WolvesIntro { .. } | ActionPrompt::RoleChange { .. } | ActionPrompt::ElderReveal { .. } | ActionPrompt::Protector { .. } | ActionPrompt::Hunter { .. } | ActionPrompt::Militia { .. } | ActionPrompt::MapleWolf { .. } | ActionPrompt::Guardian { .. } | ActionPrompt::PyreMaster { .. } | ActionPrompt::Shapeshifter { .. } | ActionPrompt::AlphaWolf { .. } | ActionPrompt::DireWolf { .. } | ActionPrompt::Empath { .. } | ActionPrompt::MasonsWake { .. } | ActionPrompt::MasonLeaderRecruit { .. } | ActionPrompt::WolfPackKill { .. } => false, } } pub(crate) fn with_mark(&self, mark: CharacterId) -> Result { let mut prompt = self.clone(); match &mut prompt { ActionPrompt::MasonsWake { .. } | ActionPrompt::ElderReveal { .. } | ActionPrompt::WolvesIntro { .. } | ActionPrompt::RoleChange { .. } | ActionPrompt::Shapeshifter { .. } | ActionPrompt::CoverOfDarkness => Err(GameError::InvalidMessageForGameState), ActionPrompt::Guardian { previous, living_players, marked, .. } => { if !living_players.iter().any(|c| c.character_id == mark) || previous .as_ref() .and_then(|p| match p { PreviousGuardianAction::Protect(_) => None, PreviousGuardianAction::Guard(c) => Some(c.character_id == mark), }) .unwrap_or_default() { // not in target list OR guarded target previous night return Err(GameError::InvalidTarget); } match marked.as_mut() { Some(marked_cid) => { if marked_cid == &mark { marked.take(); } else { marked.replace(mark); } } None => { marked.replace(mark); } } Ok(prompt) } ActionPrompt::Arcanist { living_players: targets, marked, .. } => { if !targets.iter().any(|t| t.character_id == mark) { return Err(GameError::InvalidTarget); } match marked { (None, Some(m)) | (Some(m), None) => { if *m == mark { *marked = (None, None); } else { *marked = (Some(*m), Some(mark)); } } (None, None) => *marked = (Some(mark), None), (Some(m1), Some(m2)) => { if *m1 == mark { *marked = (Some(*m2), None); } else if *m2 == mark { *marked = (Some(*m1), None); } else { *marked = (Some(*m2), Some(mark)); } } } Ok(prompt) } ActionPrompt::Adjudicator { living_players: targets, marked, .. } | ActionPrompt::PowerSeer { living_players: targets, marked, .. } | ActionPrompt::Mortician { dead_players: targets, marked, .. } | ActionPrompt::Beholder { living_players: targets, marked, .. } | ActionPrompt::MasonLeaderRecruit { potential_recruits: targets, marked, .. } | ActionPrompt::Empath { living_players: targets, marked, .. } | ActionPrompt::Vindicator { living_players: targets, marked, .. } | ActionPrompt::PyreMaster { living_players: targets, marked, .. } | ActionPrompt::Protector { targets, marked, .. } | ActionPrompt::Seer { living_players: targets, marked, .. } | ActionPrompt::Gravedigger { dead_players: targets, marked, .. } | ActionPrompt::Hunter { living_players: targets, marked, .. } | ActionPrompt::Militia { living_players: targets, marked, .. } | ActionPrompt::MapleWolf { living_players: targets, marked, .. } | ActionPrompt::WolfPackKill { living_villagers: targets, marked, .. } | ActionPrompt::AlphaWolf { living_villagers: targets, marked, .. } | ActionPrompt::DireWolf { living_players: targets, marked, .. } => { if !targets.iter().any(|t| t.character_id == mark) { return Err(GameError::InvalidTarget); } if let Some(marked_char) = marked.as_ref() && *marked_char == mark { marked.take(); } else { marked.replace(mark); } Ok(prompt) } } } pub const fn is_wolfy(&self) -> bool { self.action_type().is_wolfy() || match self { ActionPrompt::RoleChange { character_id: _, new_role, } => new_role.wolf(), _ => false, } } } impl PartialOrd for ActionPrompt { fn partial_cmp(&self, other: &Self) -> Option { self.action_type().partial_cmp(&other.action_type()) } } #[derive(Debug, Clone, Serialize, PartialEq, Deserialize)] pub enum ActionResponse { MarkTarget(CharacterId), Shapeshift, Continue, } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum ActionResult { RoleBlocked, Seer(Alignment), PowerSeer { powerful: bool }, Adjudicator { killer: bool }, Arcanist { same: bool }, GraveDigger(Option), Mortician(DiedToTitle), Empath { scapegoat: bool }, GoBackToSleep, Continue, }