// 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, night::changes::NightChange},
message::{CharacterIdentity, Identification, PublicIdentity, night::ActionPrompt},
player::{PlayerId, RoleChange},
role::{
Alignment, HunterMut, HunterRef, 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 scapegoat_can_redeem_into(&self) -> bool {
!self.role.wolf()
&& !matches!(
&self.role,
Role::Scapegoat { .. } | Role::Villager | Role::Apprentice(_)
)
}
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_from_aura(
&self,
village: &Village,
) -> Result