werewolves/werewolves-proto/src/aura.rs

154 lines
4.6 KiB
Rust
Raw Normal View History

use core::fmt::Display;
// 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 serde::{Deserialize, Serialize};
use werewolves_macros::ChecksAs;
use crate::{
game::{GameTime, Village},
role::{Alignment, Killer, Powerful},
team::Team,
};
const BLOODLET_DURATION_DAYS: u8 = 2;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, ChecksAs)]
pub enum Aura {
Traitor,
#[checks("cleansible")]
Drunk,
Insane,
#[checks("cleansible")]
Bloodlet {
night: u8,
},
}
impl Display for Aura {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(match self {
Aura::Traitor => "Traitor",
Aura::Drunk => "Drunk",
Aura::Insane => "Insane",
Aura::Bloodlet { .. } => "Bloodlet",
})
}
}
impl Aura {
pub const fn expired(&self, village: &Village) -> bool {
match self {
Aura::Traitor | Aura::Drunk | Aura::Insane => false,
Aura::Bloodlet {
night: applied_night,
} => match village.time() {
GameTime::Day { .. } => false,
GameTime::Night {
number: current_night,
} => current_night >= applied_night.saturating_add(BLOODLET_DURATION_DAYS),
},
}
}
pub const fn refreshes(&self, other: &Aura) -> bool {
matches!(
(self, other),
(Aura::Bloodlet { .. }, Aura::Bloodlet { .. })
)
}
pub fn refresh(&mut self, other: Aura) {
if let (Aura::Bloodlet { night }, Aura::Bloodlet { night: new_night }) = (self, other) {
*night = new_night
}
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Auras(Vec<Aura>);
impl Auras {
pub const fn new(auras: Vec<Aura>) -> Self {
Self(auras)
}
pub fn list(&self) -> &[Aura] {
&self.0
}
pub fn remove_aura(&mut self, aura: Aura) {
self.0.retain(|a| *a != aura);
}
/// purges expired [Aura]s and returns the ones that were removed
pub fn purge_expired(&mut self, village: &Village) -> Box<[Aura]> {
let mut auras = Vec::with_capacity(self.0.len());
core::mem::swap(&mut self.0, &mut auras);
let (expired, retained): (Vec<_>, Vec<_>) =
auras.into_iter().partition(|aura| aura.expired(village));
self.0 = retained;
expired.into_boxed_slice()
}
pub fn add(&mut self, aura: Aura) {
if let Some(existing) = self.0.iter_mut().find(|aura| aura.refreshes(aura)) {
existing.refresh(aura);
} else {
self.0.push(aura);
}
}
pub fn cleanse(&mut self) {
self.0.retain(|aura| !aura.cleansible());
}
/// returns [Some] if the auras override the player's [Team]
pub fn overrides_team(&self) -> Option<Team> {
if self.0.iter().any(|a| matches!(a, Aura::Traitor)) {
return Some(Team::AnyEvil);
}
None
}
/// returns [Some] if the auras override the player's [Alignment]
pub fn overrides_alignment(&self) -> Option<Alignment> {
for aura in self.0.iter() {
match aura {
Aura::Traitor => return Some(Alignment::Traitor),
Aura::Bloodlet { .. } => return Some(Alignment::Wolves),
Aura::Drunk | Aura::Insane => {}
}
}
None
}
/// returns [Some] if the auras override whether the player is a [Killer]
pub fn overrides_killer(&self) -> Option<Killer> {
self.0
.iter()
.any(|a| matches!(a, Aura::Bloodlet { .. }))
.then_some(Killer::Killer)
}
/// returns [Some] if the auras override whether the player is [Powerful]
pub fn overrides_powerful(&self) -> Option<Powerful> {
self.0
.iter()
.any(|a| matches!(a, Aura::Bloodlet { .. }))
.then_some(Powerful::Powerful)
}
}