werewolves/werewolves-proto/src/character.rs

946 lines
31 KiB
Rust

// 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 <https://www.gnu.org/licenses/>.
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<T> = core::result::Result<T, GameError>;
#[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<DiedTo>,
role_changes: Vec<RoleChange>,
}
impl Character {
pub fn new(
Identification {
player_id,
public:
PublicIdentity {
name,
pronouns,
number,
},
}: Identification,
role: Role,
auras: Vec<Aura>,
) -> Option<Self> {
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<RoleTitle> {
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<Vec<ActionPrompt>> {
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::<Box<[_]>>();
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<Box<[ActionPrompt]>> {
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<KillingWolfOrder> {
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<Hunter<'a>> {
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<HunterMut<'a>> {
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<Shapeshifter<'a>> {
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<ShapeshifterMut<'a>> {
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<MasonLeader<'a>> {
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<MasonLeaderMut<'a>> {
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<Scapegoat<'a>> {
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<ScapegoatMut<'a>> {
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<Empath<'a>> {
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<EmpathMut<'a>> {
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<BlackKnight<'a>> {
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<BlackKnightKill<'a>> {
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<BlackKnightMut<'a>> {
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<Guardian<'a>> {
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<GuardianMut<'a>> {
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<Direwolf<'a>> {
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<DirewolfMut<'a>> {
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<Militia<'a>> {
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<MilitiaMut<'a>> {
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<MapleWolfMut<'a>> {
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<ProtectorMut<'a>> {
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<CharacterId>;
Shapeshifter, ShapeshifterMut: Option<CharacterId>;
Scapegoat, ScapegoatMut: bool;
Empath, EmpathMut: bool;
BlackKnight, BlackKnightMut: Option<DiedTo>;
Guardian, GuardianMut: Option<PreviousGuardianAction>;
Direwolf, DirewolfMut: Option<CharacterId>;
Militia, MilitiaMut: Option<CharacterId>;
MapleWolf, MapleWolfMut: u8;
Protector, ProtectorMut: Option<CharacterId>;
);
pub struct BlackKnightKill<'a> {
attacked: &'a Option<DiedTo>,
died_to: &'a mut Option<DiedTo>,
}
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
}
}