use serde::{Deserialize, Serialize}; use werewolves_macros::{ChecksAs, Titles}; use crate::{ error::GameError, message::CharacterIdentity, player::CharacterId, 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, Other, 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::Other)] Seer { character_id: CharacterIdentity, living_players: Box<[CharacterIdentity]>, marked: Option, }, #[checks(ActionType::Protect)] Protector { character_id: CharacterIdentity, targets: Box<[CharacterIdentity]>, marked: Option, }, #[checks(ActionType::Other)] Arcanist { character_id: CharacterIdentity, living_players: Box<[CharacterIdentity]>, marked: (Option, Option), }, #[checks(ActionType::Other)] 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::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 fn with_mark(&self, mark: CharacterId) -> Result { let mut prompt = self.clone(); match &mut prompt { 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.clone()), Some(mark)); } } (None, None) => *marked = (Some(mark), None), (Some(m1), Some(m2)) => { if *m1 == mark { *marked = (Some(m2.clone()), None); } else if *m2 == mark { *marked = (Some(m1.clone()), None); } else { *marked = (Some(m2.clone()), Some(mark)); } } } Ok(prompt) } 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 { // Seer(CharacterId), // Arcanist(Option, Option), // Gravedigger(CharacterId), // Hunter(CharacterId), // Militia(Option), // MapleWolf(Option), // Guardian(CharacterId), // WolfPackKillVote(CharacterId), // AlphaWolf(Option), // Direwolf(CharacterId), // Protector(CharacterId), MarkTarget(CharacterId), Shapeshift, Continue, } #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub enum ActionResult { RoleBlocked, Seer(Alignment), Arcanist { same: bool }, GraveDigger(Option), GoBackToSleep, Continue, }