// 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::{ fmt::Display, num::NonZeroU8, ops::{Deref, Not}, }; use rand::seq::SliceRandom; use serde::{Deserialize, Serialize}; use crate::{ aura::{Aura, AuraTitle, Auras}, diedto::DiedTo, error::GameError, game::{GameTime, Village}, message::{CharacterIdentity, Identification, PublicIdentity, night::ActionPrompt}, player::{PlayerId, RoleChange}, role::{ Alignment, Killer, KillingWolfOrder, MAPLE_WOLF_ABSTAIN_LIMIT, Powerful, 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, PartialEq, Serialize, Deserialize)] pub struct Character { player_id: PlayerId, identity: CharacterIdentity, role: Role, auras: Auras, died_to: Option, role_changes: Vec, } impl Character { pub fn new( Identification { player_id, public: PublicIdentity { name, pronouns, number, }, }: Identification, role: Role, auras: Vec, ) -> Option { Some(Self { role, player_id, died_to: None, auras: Auras::new(auras), role_changes: Vec::new(), identity: CharacterIdentity { character_id: CharacterId::new(), name, pronouns, number: number?, }, }) } pub const fn is_power_role(&self) -> bool { !matches!(&self.role, Role::Scapegoat { .. } | Role::Villager) } 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) { if self.died_to.is_some() { return; } match (&mut self.role, died_to.date_time()) { (Role::BlackKnight { attacked }, GameTime::Night { .. }) => { attacked.replace(died_to); return; } ( Role::Elder { lost_protection_night: Some(_), .. }, _, ) => {} ( Role::Elder { lost_protection_night, .. }, GameTime::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 fn elder_reveal(&mut self) { if let Role::Elder { woken_for_reveal, .. } = &mut self.role { *woken_for_reveal = true } } pub fn purge_expired_auras(&mut self, village: &Village) -> Box<[Aura]> { self.auras.purge_expired(village) } pub fn auras(&self) -> &[Aura] { self.auras.list() } pub fn auras_mut(&mut self) -> &mut [Aura] { self.auras.list_mut() } pub fn role_change(&mut self, new_role: RoleTitle, at: GameTime) -> 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 { GameTime::Day { number: _ } => return Err(GameError::NotNight), GameTime::Night { number } => number, }, }); Ok(()) } #[cfg(test)] pub fn role_changes(&self) -> &[RoleChange] { &self.role_changes } 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, .. } ) } fn mason_prompts(&self, village: &Village) -> Result> { if !self.role.wakes(village) { return Ok(Vec::new()); } let (recruits, recruits_available) = match &self.role { Role::MasonLeader { recruits, recruits_available, } => (recruits, *recruits_available), _ => { return Err(GameError::InvalidRole { expected: RoleTitle::MasonLeader, got: self.role_title(), }); } }; let recruits = recruits .iter() .filter_map(|r| village.character_by_id(*r).ok()) .filter_map(|c| c.is_village().then_some(c.identity())) .chain((self.is_village() && self.alive()).then_some(self.identity())) .collect::>(); Ok(recruits .is_empty() .not() .then_some(ActionPrompt::MasonsWake { leader: self.identity(), masons: recruits.clone(), }) .into_iter() .chain( self.alive() .then_some(()) .and_then(|_| 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.iter().any(|r| r.character_id == c.character_id)) .collect(), marked: None, }), ) .collect()) } /// Returns a copy of this character with their role replaced /// in a read-only type pub fn as_role(&self, role: Role) -> AsCharacter { let mut char = self.clone(); char.role = role; AsCharacter(char) } pub fn apply_aura(&mut self, aura: Aura) { self.auras.add(aura); } pub fn remove_aura(&mut self, aura: AuraTitle) { self.auras.remove_aura(aura); } pub fn night_action_prompts(&self, village: &Village) -> Result> { let mut prompts = Vec::new(); if self.mason_leader().is_ok() { // add them here so masons wake up even with a dead leader prompts.append(&mut self.mason_prompts(village)?); } let night = match village.time() { GameTime::Day { number: _ } => return Err(GameError::NotNight), GameTime::Night { number } => number, }; if night == 0 && self.auras.list().contains(&Aura::Traitor) { log::info!("adding traitor prompt for {}", self.identity()); prompts.push(ActionPrompt::TraitorIntro { character_id: self.identity(), }); } if !self.alive() || !self.role.wakes(village) { return Ok(prompts.into_boxed_slice()); } 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 => {} Role::Insomniac => prompts.push(ActionPrompt::Insomniac { character_id: self.identity(), }), 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())) { prompts.push(ActionPrompt::RoleChange { character_id: self.identity(), new_role: pr, }); } } Role::Bloodletter => prompts.push(ActionPrompt::Bloodletter { character_id: self.identity(), living_players: village.living_villagers(), marked: None, }), Role::Seer => prompts.push(ActionPrompt::Seer { character_id: self.identity(), living_players: village.living_players_excluding(self.character_id()), marked: None, }), Role::Arcanist => prompts.push(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), } => prompts.push(ActionPrompt::Protector { character_id: self.identity(), targets: village.living_players_excluding(*last_protected), marked: None, }), Role::Protector { last_protected: None, } => prompts.push(ActionPrompt::Protector { character_id: self.identity(), targets: village.living_players(), marked: None, }), Role::Apprentice(role) => match village.time() { GameTime::Day { number: _ } => {} GameTime::Night { number: current_night, } => { if village .characters() .into_iter() .filter(|c| c.role_title() == *role) .filter_map(|char| char.died_to) .any(|died_to| match died_to.date_time() { GameTime::Day { number } => number.get() + 1 >= current_night, GameTime::Night { number } => number + 1 >= current_night, }) { prompts.push(ActionPrompt::RoleChange { character_id: self.identity(), new_role: *role, }); } } }, Role::Elder { knows_on_night, woken_for_reveal: false, .. } => match village.time() { GameTime::Day { number: _ } => {} GameTime::Night { number } => { if number >= knows_on_night.get() { prompts.push(ActionPrompt::ElderReveal { character_id: self.identity(), }); } } }, Role::Militia { targeted: None } => prompts.push(ActionPrompt::Militia { character_id: self.identity(), living_players: village.living_players_excluding(self.character_id()), marked: None, }), Role::Werewolf => prompts.push(ActionPrompt::WolfPackKill { living_villagers: village.living_players(), marked: None, }), Role::AlphaWolf { killed: None } => prompts.push(ActionPrompt::AlphaWolf { character_id: self.identity(), living_villagers: village.living_players_excluding(self.character_id()), marked: None, }), Role::DireWolf { last_blocked: Some(last_blocked), } => prompts.push(ActionPrompt::DireWolf { character_id: self.identity(), living_players: village .living_players_excluding(self.character_id()) .into_iter() .filter(|c| c.character_id != *last_blocked) .collect(), marked: None, }), Role::DireWolf { .. } => prompts.push(ActionPrompt::DireWolf { character_id: self.identity(), living_players: village.living_players_excluding(self.character_id()), marked: None, }), Role::Shapeshifter { shifted_into: None } => prompts.push(ActionPrompt::Shapeshifter { character_id: self.identity(), }), Role::Gravedigger => { let dead = village.dead_targets(); if !dead.is_empty() { prompts.push(ActionPrompt::Gravedigger { character_id: self.identity(), dead_players: village.dead_targets(), marked: None, }); } } Role::Hunter { target } => prompts.push(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 } => prompts.push(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)), } => prompts.push(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)), } => prompts.push(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, } => prompts.push(ActionPrompt::Guardian { character_id: self.identity(), previous: None, living_players: village.living_players(), marked: None, }), Role::Adjudicator => prompts.push(ActionPrompt::Adjudicator { character_id: self.identity(), living_players: village.living_players_excluding(self.character_id()), marked: None, }), Role::PowerSeer => prompts.push(ActionPrompt::PowerSeer { character_id: self.identity(), living_players: village.living_players_excluding(self.character_id()), marked: None, }), Role::Mortician => { let dead = village.dead_targets(); if !dead.is_empty() { prompts.push(ActionPrompt::Mortician { character_id: self.identity(), dead_players: dead, marked: None, }); } } Role::Beholder => prompts.push(ActionPrompt::Beholder { character_id: self.identity(), living_players: village.living_players_excluding(self.character_id()), marked: None, }), Role::MasonLeader { .. } => { log::error!( "night_action_prompts got to MasonLeader, should be handled before the living check" ); } Role::Empath { cursed: false } => prompts.push(ActionPrompt::Empath { character_id: self.identity(), living_players: village.living_players_excluding(self.character_id()), marked: None, }), Role::Vindicator => { if night != 0 && let Some(last_day) = NonZeroU8::new(night) && village .executions_on_day(last_day) .iter() .any(|c| c.is_village()) { prompts.push(ActionPrompt::Vindicator { character_id: self.identity(), living_players: village.living_players_excluding(self.character_id()), marked: None, }); } } Role::PyreMaster { .. } => prompts.push(ActionPrompt::PyreMaster { character_id: self.identity(), living_players: village.living_players_excluding(self.character_id()), marked: None, }), Role::LoneWolf => prompts.push(ActionPrompt::LoneWolfKill { character_id: self.identity(), living_players: village.living_players_excluding(self.character_id()), marked: None, }), } Ok(prompts.into_boxed_slice()) } #[cfg(test)] pub const fn role(&self) -> &Role { &self.role } pub const fn killing_wolf_order(&self) -> Option { self.role.killing_wolf_order() } pub fn alignment(&self) -> Alignment { if let Some(alignment) = self.auras.overrides_alignment() { return alignment; } if let Role::Empath { cursed: true } = &self.role { return Alignment::Wolves; } self.role.alignment() } pub fn killer(&self) -> Killer { if let Some(killer) = self.auras.overrides_killer() { return killer; } if let Role::Empath { cursed: true } = &self.role { return Killer::Killer; } self.role.killer() } pub fn powerful(&self) -> Powerful { if let Some(powerful) = self.auras.overrides_powerful() { return powerful; } if let Role::Empath { cursed: true } = &self.role { return Powerful::Powerful; } 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 black_knight<'a>(&'a self) -> Result> { match &self.role { Role::BlackKnight { attacked } => Ok(BlackKnight(attacked)), _ => Err(GameError::InvalidRole { expected: RoleTitle::BlackKnight, got: self.role_title(), }), } } pub const fn black_knight_kill<'a>(&'a mut self) -> Result> { let title = self.role.title(); match &self.role { Role::BlackKnight { attacked } => Ok(BlackKnightKill { attacked, died_to: &mut self.died_to, }), _ => Err(GameError::InvalidRole { expected: RoleTitle::BlackKnight, got: title, }), } } pub const fn black_knight_mut<'a>(&'a mut self) -> Result> { let title = self.role.title(); match &mut self.role { Role::BlackKnight { attacked } => Ok(BlackKnightMut(attacked)), _ => Err(GameError::InvalidRole { expected: RoleTitle::BlackKnight, got: title, }), } } pub const fn guardian<'a>(&'a self) -> Result> { let title = self.role.title(); match &self.role { Role::Guardian { last_protected } => Ok(Guardian(last_protected)), _ => Err(GameError::InvalidRole { expected: RoleTitle::Guardian, got: title, }), } } pub const fn guardian_mut<'a>(&'a mut self) -> Result> { let title = self.role.title(); match &mut self.role { Role::Guardian { last_protected } => Ok(GuardianMut(last_protected)), _ => Err(GameError::InvalidRole { expected: RoleTitle::Guardian, got: title, }), } } pub const fn direwolf<'a>(&'a self) -> Result> { let title = self.role.title(); match &self.role { Role::DireWolf { last_blocked } => Ok(Direwolf(last_blocked)), _ => Err(GameError::InvalidRole { expected: RoleTitle::DireWolf, got: title, }), } } pub const fn direwolf_mut<'a>(&'a mut self) -> Result> { let title = self.role.title(); match &mut self.role { Role::DireWolf { last_blocked } => Ok(DirewolfMut(last_blocked)), _ => Err(GameError::InvalidRole { expected: RoleTitle::DireWolf, got: title, }), } } pub const fn militia<'a>(&'a self) -> Result> { let title = self.role.title(); match &self.role { Role::Militia { targeted } => Ok(Militia(targeted)), _ => Err(GameError::InvalidRole { expected: RoleTitle::Militia, got: title, }), } } pub const fn militia_mut<'a>(&'a mut self) -> Result> { let title = self.role.title(); match &mut self.role { Role::Militia { targeted } => Ok(MilitiaMut(targeted)), _ => Err(GameError::InvalidRole { expected: RoleTitle::Militia, got: title, }), } } pub const fn maple_wolf_mut<'a>(&'a mut self) -> Result> { let title = self.role.title(); match &mut self.role { Role::MapleWolf { last_kill_on_night } => Ok(MapleWolfMut(last_kill_on_night)), _ => Err(GameError::InvalidRole { expected: RoleTitle::MapleWolf, got: title, }), } } pub const fn protector_mut<'a>(&'a mut self) -> Result> { let title = self.role.title(); match &mut self.role { Role::Protector { last_protected } => Ok(ProtectorMut(last_protected)), _ => Err(GameError::InvalidRole { expected: RoleTitle::Protector, 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; BlackKnight, BlackKnightMut: Option; Guardian, GuardianMut: Option; Direwolf, DirewolfMut: Option; Militia, MilitiaMut: Option; MapleWolf, MapleWolfMut: u8; Protector, ProtectorMut: Option; ); pub struct BlackKnightKill<'a> { attacked: &'a Option, died_to: &'a mut Option, } impl BlackKnightKill<'_> { pub fn kill(self) { if let Some(attacked) = self.attacked.as_ref().and_then(|a| a.next_night()) && self.died_to.is_none() { self.died_to.replace(attacked.clone()); } } } 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; } } } pub struct AsCharacter(Character); impl Deref for AsCharacter { type Target = Character; fn deref(&self) -> &Self::Target { &self.0 } }