use core::{fmt::Display, num::NonZeroU8, ops::Not}; use rand::seq::SliceRandom; use serde::{Deserialize, Serialize}; use crate::{ diedto::DiedTo, error::GameError, game::{DateTime, Village}, message::{CharacterIdentity, Identification, PublicIdentity, night::ActionPrompt}, modifier::Modifier, player::{PlayerId, RoleChange}, role::{Alignment, MAPLE_WOLF_ABSTAIN_LIMIT, PreviousGuardianAction, Role, RoleTitle}, }; type Result = core::result::Result; #[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)] pub struct CharacterId(uuid::Uuid); impl CharacterId { pub fn new() -> Self { Self(uuid::Uuid::new_v4()) } pub const fn from_u128(v: u128) -> Self { Self(uuid::Uuid::from_u128(v)) } } impl Display for CharacterId { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.0.fmt(f) } } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Character { player_id: PlayerId, identity: CharacterIdentity, role: Role, modifier: Option, died_to: Option, role_changes: Vec, } impl Character { pub fn new( Identification { player_id, public: PublicIdentity { name, pronouns, number, }, }: Identification, role: Role, ) -> Option { Some(Self { role, identity: CharacterIdentity { character_id: CharacterId::new(), name, pronouns, number: number?, }, player_id, modifier: None, died_to: None, role_changes: Vec::new(), }) } pub const fn is_power_role(&self) -> bool { match &self.role { Role::Scapegoat { .. } | Role::Villager => false, _ => true, } } pub fn identity(&self) -> CharacterIdentity { self.identity.clone() } pub fn name(&self) -> &str { self.identity.name.as_str() } pub const fn number(&self) -> NonZeroU8 { self.identity.number } pub const fn pronouns(&self) -> Option<&str> { match self.identity.pronouns.as_ref() { Some(p) => Some(p.as_str()), None => None, } } pub fn died_to(&self) -> Option<&DiedTo> { self.died_to.as_ref() } pub fn kill(&mut self, died_to: DiedTo) { match (&mut self.role, died_to.date_time()) { ( Role::Elder { lost_protection_night: Some(_), .. }, _, ) => {} ( Role::Elder { lost_protection_night, .. }, DateTime::Night { number: night }, ) => { *lost_protection_night = lost_protection_night .is_none() .then_some(night) .and_then(NonZeroU8::new); return; } _ => {} } match &self.died_to { Some(_) => {} None => self.died_to = Some(died_to), } } pub const fn alive(&self) -> bool { self.died_to.is_none() } pub fn execute(&mut self, day: NonZeroU8) -> Result<()> { if self.died_to.is_some() { return Err(GameError::CharacterAlreadyDead); } self.died_to = Some(DiedTo::Execution { day }); Ok(()) } pub const fn character_id(&self) -> CharacterId { self.identity.character_id } pub const fn player_id(&self) -> PlayerId { self.player_id } pub const fn role_title(&self) -> RoleTitle { self.role.title() } pub const fn gravedigger_dig(&self) -> Option { match &self.role { Role::Shapeshifter { shifted_into: Some(_), } => None, _ => Some(self.role.title()), } } pub const fn alignment(&self) -> Alignment { if let Role::Empath { cursed: true } = &self.role { return Alignment::Wolves; } self.role.alignment() } pub fn elder_reveal(&mut self) { if let Role::Elder { woken_for_reveal, .. } = &mut self.role { *woken_for_reveal = true } } pub fn role_change(&mut self, new_role: RoleTitle, at: DateTime) -> Result<()> { let mut role = new_role.title_to_role_excl_apprentice(); core::mem::swap(&mut role, &mut self.role); self.role_changes.push(RoleChange { role, new_role, changed_on_night: match at { DateTime::Day { number: _ } => return Err(GameError::NotNight), DateTime::Night { number } => number, }, }); Ok(()) } pub const fn is_wolf(&self) -> bool { self.role.wolf() } pub const fn is_village(&self) -> bool { !self.is_wolf() } pub const fn known_elder(&self) -> bool { matches!( self.role, Role::Elder { woken_for_reveal: true, .. } ) } pub fn night_action_prompts(&self, village: &Village) -> Result> { if !self.alive() || !self.role.wakes(village) { return Ok(Box::new([])); } let night = match village.date_time() { DateTime::Day { number: _ } => return Err(GameError::NotNight), DateTime::Night { number } => number, }; Ok(Box::new([match &self.role { Role::Empath { cursed: true } | Role::Diseased | Role::Weightlifter | Role::BlackKnight { .. } | Role::Shapeshifter { shifted_into: Some(_), } | Role::AlphaWolf { killed: Some(_) } | Role::Militia { targeted: Some(_) } | Role::Scapegoat { redeemed: false } | Role::Elder { woken_for_reveal: true, .. } | Role::Villager => return Ok(Box::new([])), Role::Scapegoat { redeemed: true } => { let mut dead = village.dead_characters(); dead.shuffle(&mut rand::rng()); if let Some(pr) = dead .into_iter() .find_map(|d| (d.is_village() && d.is_power_role()).then_some(d.role_title())) { ActionPrompt::RoleChange { character_id: self.identity(), new_role: pr, } } else { return Ok(Box::new([])); } } Role::Seer => ActionPrompt::Seer { character_id: self.identity(), living_players: village.living_players_excluding(self.character_id()), marked: None, }, Role::Arcanist => ActionPrompt::Arcanist { character_id: self.identity(), living_players: village.living_players_excluding(self.character_id()), marked: (None, None), }, Role::Protector { last_protected: Some(last_protected), } => ActionPrompt::Protector { character_id: self.identity(), targets: village.living_players_excluding(*last_protected), marked: None, }, Role::Protector { last_protected: None, } => ActionPrompt::Protector { character_id: self.identity(), targets: village.living_players_excluding(self.character_id()), marked: None, }, Role::Apprentice(role) => { let current_night = match village.date_time() { DateTime::Day { number: _ } => return Ok(Box::new([])), DateTime::Night { number } => number, }; return Ok(village .characters() .into_iter() .filter(|c| c.role_title() == *role) .filter_map(|char| char.died_to) .any(|died_to| match died_to.date_time() { DateTime::Day { number } => number.get() + 1 >= current_night, DateTime::Night { number } => number + 1 >= current_night, }) .then(|| ActionPrompt::RoleChange { character_id: self.identity(), new_role: *role, }) .into_iter() .collect()); } Role::Elder { knows_on_night, woken_for_reveal: false, .. } => { let current_night = match village.date_time() { DateTime::Day { number: _ } => return Ok(Box::new([])), DateTime::Night { number } => number, }; return Ok((current_night >= knows_on_night.get()) .then_some({ ActionPrompt::ElderReveal { character_id: self.identity(), } }) .into_iter() .collect()); } Role::Militia { targeted: None } => ActionPrompt::Militia { character_id: self.identity(), living_players: village.living_players_excluding(self.character_id()), marked: None, }, Role::Werewolf => ActionPrompt::WolfPackKill { living_villagers: village.living_players(), marked: None, }, Role::AlphaWolf { killed: None } => ActionPrompt::AlphaWolf { character_id: self.identity(), living_villagers: village.living_players_excluding(self.character_id()), marked: None, }, Role::DireWolf => ActionPrompt::DireWolf { character_id: self.identity(), living_players: village.living_players(), marked: None, }, Role::Shapeshifter { shifted_into: None } => ActionPrompt::Shapeshifter { character_id: self.identity(), }, Role::Gravedigger => { let dead = village.dead_targets(); if dead.is_empty() { return Ok(Box::new([])); } ActionPrompt::Gravedigger { character_id: self.identity(), dead_players: village.dead_targets(), marked: None, } } Role::Hunter { target } => ActionPrompt::Hunter { character_id: self.identity(), current_target: target.as_ref().and_then(|t| village.target_by_id(*t).ok()), living_players: village.living_players_excluding(self.character_id()), marked: None, }, Role::MapleWolf { last_kill_on_night } => ActionPrompt::MapleWolf { character_id: self.identity(), kill_or_die: last_kill_on_night + MAPLE_WOLF_ABSTAIN_LIMIT.get() == night, living_players: village.living_players_excluding(self.character_id()), marked: None, }, Role::Guardian { last_protected: Some(PreviousGuardianAction::Guard(prev_target)), } => ActionPrompt::Guardian { character_id: self.identity(), previous: Some(PreviousGuardianAction::Guard(prev_target.clone())), living_players: village.living_players_excluding(prev_target.character_id), marked: None, }, Role::Guardian { last_protected: Some(PreviousGuardianAction::Protect(prev_target)), } => ActionPrompt::Guardian { character_id: self.identity(), previous: Some(PreviousGuardianAction::Protect(prev_target.clone())), living_players: village.living_players(), marked: None, }, Role::Guardian { last_protected: None, } => ActionPrompt::Guardian { character_id: self.identity(), previous: None, living_players: village.living_players(), marked: None, }, Role::Adjudicator => ActionPrompt::Adjudicator { character_id: self.identity(), living_players: village.living_players_excluding(self.character_id()), marked: None, }, Role::PowerSeer => ActionPrompt::PowerSeer { character_id: self.identity(), living_players: village.living_players_excluding(self.character_id()), marked: None, }, Role::Mortician => ActionPrompt::Mortician { character_id: self.identity(), dead_players: village.dead_targets(), marked: None, }, Role::Beholder => ActionPrompt::Beholder { character_id: self.identity(), living_players: village.living_players_excluding(self.character_id()), marked: None, }, Role::MasonLeader { recruits_available, recruits, } => { return Ok(recruits .is_empty() .not() .then_some(ActionPrompt::MasonsWake { character_id: self.identity(), masons: recruits .iter() .map(|r| village.character_by_id(*r).map(|c| c.identity())) .collect::>>()?, }) .into_iter() .chain( NonZeroU8::new(*recruits_available).map(|recruits_available| { ActionPrompt::MasonLeaderRecruit { character_id: self.identity(), recruits_left: recruits_available, potential_recruits: village .living_players_excluding(self.character_id()) .into_iter() .filter(|c| !recruits.contains(&c.character_id)) .collect(), marked: None, } }), ) .collect()); } Role::Empath { cursed: false } => ActionPrompt::Empath { character_id: self.identity(), living_players: village.living_players_excluding(self.character_id()), marked: None, }, Role::Vindicator => { let last_day = match village.date_time() { DateTime::Day { .. } => { log::error!( "vindicator trying to get a prompt during the day? village state: {village:?}" ); return Ok(Box::new([])); } DateTime::Night { number } => { if number == 0 { return Ok(Box::new([])); } NonZeroU8::new(number).unwrap() } }; return Ok(village .executions_on_day(last_day) .iter() .any(|c| c.is_village()) .then(|| ActionPrompt::Vindicator { character_id: self.identity(), living_players: village.living_players_excluding(self.character_id()), marked: None, }) .into_iter() .collect()); } Role::PyreMaster { .. } => ActionPrompt::PyreMaster { character_id: self.identity(), living_players: village.living_players_excluding(self.character_id()), marked: None, }, }])) } #[cfg(test)] pub const fn role(&self) -> &Role { &self.role } pub const fn killer(&self) -> bool { if let Role::Empath { cursed: true } = &self.role { return true; } self.role.killer() } pub const fn powerful(&self) -> bool { if let Role::Empath { cursed: true } = &self.role { return true; } self.role.powerful() } pub const fn hunter<'a>(&'a self) -> Result> { match &self.role { Role::Hunter { target } => Ok(Hunter(target)), _ => Err(GameError::InvalidRole { expected: RoleTitle::Hunter, got: self.role_title(), }), } } pub const fn hunter_mut<'a>(&'a mut self) -> Result> { let title = self.role.title(); match &mut self.role { Role::Hunter { target } => Ok(HunterMut(target)), _ => Err(GameError::InvalidRole { expected: RoleTitle::Hunter, got: title, }), } } pub const fn shapeshifter<'a>(&'a self) -> Result> { match &self.role { Role::Shapeshifter { shifted_into } => Ok(Shapeshifter(shifted_into)), _ => Err(GameError::InvalidRole { expected: RoleTitle::Shapeshifter, got: self.role_title(), }), } } pub const fn shapeshifter_mut<'a>(&'a mut self) -> Result> { let title = self.role.title(); match &mut self.role { Role::Shapeshifter { shifted_into } => Ok(ShapeshifterMut(shifted_into)), _ => Err(GameError::InvalidRole { expected: RoleTitle::Shapeshifter, got: title, }), } } pub const fn mason_leader<'a>(&'a self) -> Result> { match &self.role { Role::MasonLeader { recruits_available, recruits, } => Ok(MasonLeader(recruits_available, recruits)), _ => Err(GameError::InvalidRole { expected: RoleTitle::MasonLeader, got: self.role_title(), }), } } pub const fn mason_leader_mut<'a>(&'a mut self) -> Result> { let title = self.role.title(); match &mut self.role { Role::MasonLeader { recruits_available, recruits, } => Ok(MasonLeaderMut(recruits_available, recruits)), _ => Err(GameError::InvalidRole { expected: RoleTitle::MasonLeader, got: title, }), } } pub const fn scapegoat<'a>(&'a self) -> Result> { match &self.role { Role::Scapegoat { redeemed } => Ok(Scapegoat(redeemed)), _ => Err(GameError::InvalidRole { expected: RoleTitle::Scapegoat, got: self.role_title(), }), } } pub const fn scapegoat_mut<'a>(&'a mut self) -> Result> { let title = self.role.title(); match &mut self.role { Role::Scapegoat { redeemed } => Ok(ScapegoatMut(redeemed)), _ => Err(GameError::InvalidRole { expected: RoleTitle::Scapegoat, got: title, }), } } pub const fn empath<'a>(&'a self) -> Result> { match &self.role { Role::Empath { cursed } => Ok(Empath(cursed)), _ => Err(GameError::InvalidRole { expected: RoleTitle::Empath, got: self.role_title(), }), } } pub const fn empath_mut<'a>(&'a mut self) -> Result> { let title = self.role.title(); match &mut self.role { Role::Empath { cursed } => Ok(EmpathMut(cursed)), _ => Err(GameError::InvalidRole { expected: RoleTitle::Empath, got: title, }), } } pub const fn initial_shown_role(&self) -> RoleTitle { self.role.initial_shown_role() } } macro_rules! decl_ref_and_mut { ($($name:ident, $name_mut:ident: $contains:ty;)*) => { $( pub struct $name<'a>(&'a $contains); impl core::ops::Deref for $name<'_> { type Target = $contains; fn deref(&self) -> &Self::Target { self.0 } } pub struct $name_mut<'a>(&'a mut $contains); impl core::ops::Deref for $name_mut<'_> { type Target = $contains; fn deref(&self) -> &Self::Target { self.0 } } impl core::ops::DerefMut for $name_mut<'_> { fn deref_mut(&mut self) -> &mut Self::Target { self.0 } } )* }; } decl_ref_and_mut!( Hunter, HunterMut: Option; Shapeshifter, ShapeshifterMut: Option; Scapegoat, ScapegoatMut: bool; Empath, EmpathMut: bool; ); pub struct MasonLeader<'a>(&'a u8, &'a [CharacterId]); impl MasonLeader<'_> { pub const fn remaining_recruits(&self) -> u8 { *self.0 } pub const fn recruits(&self) -> usize { self.1.len() } } pub struct MasonLeaderMut<'a>(&'a mut u8, &'a mut Box<[CharacterId]>); impl MasonLeaderMut<'_> { pub const fn remaining_recruits(&self) -> u8 { *self.0 } pub fn recruit(self, target: CharacterId) { let mut recruits = self.1.to_vec(); recruits.push(target); *self.1 = recruits.into_boxed_slice(); if let Some(new) = self.0.checked_sub(1) { *self.0 = new; } } }