werewolves/werewolves-proto/src/role.rs

484 lines
13 KiB
Rust
Raw Normal View History

2025-10-13 22:15:40 +01:00
use core::{fmt::Display, num::NonZeroU8, ops::Not};
use serde::{Deserialize, Serialize};
use werewolves_macros::{ChecksAs, Titles};
use crate::{
character::CharacterId,
2025-10-06 21:59:44 +01:00
diedto::DiedTo,
game::{GameTime, Village},
message::CharacterIdentity,
};
#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize, Default)]
pub enum Killer {
Killer,
#[default]
NotKiller,
}
impl Killer {
pub const fn killer(&self) -> bool {
matches!(self, Killer::Killer)
}
}
impl Display for Killer {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Killer::Killer => f.write_str("Killer"),
Killer::NotKiller => f.write_str("Not a Killer"),
}
}
}
impl Not for Killer {
type Output = Killer;
fn not(self) -> Self::Output {
match self {
Killer::Killer => Killer::NotKiller,
Killer::NotKiller => Killer::Killer,
}
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize, Default)]
pub enum Powerful {
Powerful,
#[default]
NotPowerful,
}
impl Powerful {
pub const fn powerful(&self) -> bool {
matches!(self, Powerful::Powerful)
}
}
impl Display for Powerful {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Powerful::Powerful => f.write_str("Powerful"),
Powerful::NotPowerful => f.write_str("Not Powerful"),
}
}
}
impl Not for Powerful {
type Output = Powerful;
fn not(self) -> Self::Output {
match self {
Powerful::Powerful => Powerful::NotPowerful,
Powerful::NotPowerful => Powerful::Powerful,
}
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize)]
pub enum AlignmentEq {
Same,
Different,
}
impl Not for AlignmentEq {
type Output = AlignmentEq;
fn not(self) -> Self::Output {
match self {
AlignmentEq::Same => Self::Different,
AlignmentEq::Different => Self::Same,
}
}
}
impl AlignmentEq {
pub const fn new(same: bool) -> Self {
match same {
true => Self::Same,
false => Self::Different,
}
}
pub const fn same(&self) -> bool {
matches!(self, Self::Same)
}
}
#[derive(Debug, PartialEq, Clone, Serialize, Deserialize, ChecksAs, Titles)]
pub enum Role {
#[checks(Alignment::Village)]
#[checks(Killer::NotKiller)]
#[checks(Powerful::NotPowerful)]
Villager,
#[checks(Alignment::Wolves)]
#[checks(Killer::Killer)]
#[checks(Powerful::Powerful)]
2025-10-04 17:50:29 +01:00
Scapegoat { redeemed: bool },
#[checks(Alignment::Village)]
#[checks(Powerful::Powerful)]
#[checks(Killer::NotKiller)]
#[checks("is_mentor")]
Seer,
#[checks(Alignment::Village)]
#[checks(Powerful::Powerful)]
#[checks(Killer::NotKiller)]
#[checks("is_mentor")]
Arcanist,
#[checks(Alignment::Village)]
#[checks(Powerful::Powerful)]
#[checks(Killer::NotKiller)]
#[checks("is_mentor")]
Adjudicator,
#[checks(Alignment::Village)]
#[checks(Powerful::Powerful)]
#[checks(Killer::NotKiller)]
#[checks("is_mentor")]
PowerSeer,
#[checks(Alignment::Village)]
#[checks(Powerful::Powerful)]
#[checks(Killer::NotKiller)]
#[checks("is_mentor")]
Mortician,
#[checks(Alignment::Village)]
#[checks(Powerful::Powerful)]
#[checks(Killer::NotKiller)]
#[checks("is_mentor")]
Beholder,
#[checks(Alignment::Village)]
#[checks(Powerful::Powerful)]
#[checks(Killer::NotKiller)]
MasonLeader {
recruits_available: u8,
recruits: Box<[CharacterId]>,
},
#[checks(Alignment::Village)]
#[checks(Powerful::Powerful)]
#[checks(Killer::NotKiller)]
Empath { cursed: bool },
#[checks(Alignment::Village)]
#[checks(Powerful::Powerful)]
#[checks(Killer::NotKiller)]
#[checks("is_mentor")]
Vindicator,
#[checks(Alignment::Village)]
#[checks(Powerful::Powerful)]
#[checks(Killer::NotKiller)]
#[checks("is_mentor")]
Diseased,
#[checks(Alignment::Village)]
#[checks(Powerful::Powerful)]
#[checks(Killer::NotKiller)]
#[checks("is_mentor")]
2025-10-06 21:59:44 +01:00
BlackKnight { attacked: Option<DiedTo> },
#[checks(Alignment::Village)]
#[checks(Powerful::Powerful)]
#[checks(Killer::NotKiller)]
#[checks("is_mentor")]
Weightlifter,
#[checks(Alignment::Village)]
#[checks(Powerful::Powerful)]
#[checks(Killer::Killer)]
#[checks("is_mentor")]
PyreMaster { villagers_killed: u8 },
#[checks(Alignment::Village)]
#[checks(Powerful::Powerful)]
#[checks(Killer::NotKiller)]
#[checks("is_mentor")]
Gravedigger,
#[checks(Alignment::Village)]
#[checks(Killer::Killer)]
#[checks(Powerful::Powerful)]
#[checks("is_mentor")]
#[checks]
Hunter { target: Option<CharacterId> },
#[checks(Alignment::Village)]
#[checks(Killer::Killer)]
#[checks(Powerful::Powerful)]
#[checks("is_mentor")]
Militia { targeted: Option<CharacterId> },
#[checks(Alignment::Wolves)]
#[checks(Killer::Killer)]
#[checks(Powerful::Powerful)]
#[checks("is_mentor")]
MapleWolf { last_kill_on_night: u8 },
#[checks(Alignment::Village)]
#[checks(Powerful::Powerful)]
#[checks(Killer::Killer)]
#[checks("is_mentor")]
Guardian {
last_protected: Option<PreviousGuardianAction>,
},
#[checks(Alignment::Village)]
#[checks(Powerful::Powerful)]
#[checks(Killer::NotKiller)]
#[checks("is_mentor")]
Protector { last_protected: Option<CharacterId> },
#[checks(Alignment::Village)]
#[checks(Powerful::Powerful)]
#[checks(Killer::NotKiller)]
2025-10-04 17:50:29 +01:00
Apprentice(RoleTitle),
#[checks(Alignment::Village)]
#[checks(Powerful::Powerful)]
#[checks(Killer::NotKiller)]
#[checks("is_mentor")]
2025-10-04 17:50:29 +01:00
Elder {
knows_on_night: NonZeroU8,
woken_for_reveal: bool,
lost_protection_night: Option<NonZeroU8>,
2025-10-04 17:50:29 +01:00
},
#[checks(Alignment::Village)]
#[checks(Powerful::Powerful)]
#[checks(Killer::NotKiller)]
Insomniac,
#[checks(Alignment::Wolves)]
#[checks(Killer::Killer)]
#[checks(Powerful::Powerful)]
#[checks("wolf")]
Werewolf,
#[checks(Alignment::Wolves)]
#[checks(Killer::Killer)]
#[checks(Powerful::Powerful)]
#[checks("wolf")]
AlphaWolf { killed: Option<CharacterId> },
#[checks(Alignment::Village)]
#[checks(Killer::Killer)]
#[checks(Powerful::Powerful)]
#[checks("wolf")]
DireWolf { last_blocked: Option<CharacterId> },
#[checks(Alignment::Wolves)]
#[checks(Killer::Killer)]
#[checks(Powerful::Powerful)]
#[checks("wolf")]
Shapeshifter { shifted_into: Option<CharacterId> },
2025-10-07 02:52:06 +01:00
#[checks(Alignment::Wolves)]
#[checks(Killer::Killer)]
#[checks(Powerful::Powerful)]
2025-10-07 02:52:06 +01:00
#[checks("wolf")]
LoneWolf,
}
impl Role {
/// [RoleTitle] as shown to the player on role assignment
pub const fn initial_shown_role(&self) -> RoleTitle {
match self {
Role::Apprentice(_) | Role::Elder { .. } | Role::Insomniac => RoleTitle::Villager,
_ => self.title(),
}
}
pub const fn killing_wolf_order(&self) -> Option<KillingWolfOrder> {
Some(match self {
Role::Villager
| Role::Scapegoat { .. }
| Role::Seer
| Role::Arcanist
| Role::Adjudicator
| Role::PowerSeer
| Role::Mortician
| Role::Beholder
| Role::MasonLeader { .. }
| Role::Empath { .. }
| Role::Vindicator
| Role::Diseased
| Role::BlackKnight { .. }
| Role::Weightlifter
| Role::PyreMaster { .. }
| Role::Gravedigger
| Role::Hunter { .. }
| Role::Militia { .. }
| Role::MapleWolf { .. }
| Role::Guardian { .. }
| Role::Protector { .. }
| Role::Apprentice(..)
| Role::Elder { .. }
| Role::Insomniac => return None,
Role::Werewolf => KillingWolfOrder::Werewolf,
Role::AlphaWolf { .. } => KillingWolfOrder::AlphaWolf,
Role::DireWolf { .. } => KillingWolfOrder::DireWolf,
Role::Shapeshifter { .. } => KillingWolfOrder::Shapeshifter,
Role::LoneWolf => KillingWolfOrder::LoneWolf,
})
}
2025-10-06 01:03:16 +01:00
pub const fn wakes_night_zero(&self) -> bool {
match self {
2025-10-13 22:15:40 +01:00
Role::PowerSeer
| Role::Adjudicator
| Role::DireWolf { .. }
| Role::Arcanist
| Role::Seer => true,
2025-10-06 01:03:16 +01:00
2025-10-13 22:15:40 +01:00
Role::Insomniac // has to at least get one good night of sleep, right?
| Role::Beholder
| Role::LoneWolf
2025-10-07 02:52:06 +01:00
| Role::Shapeshifter { .. }
2025-10-06 01:03:16 +01:00
| Role::Werewolf
| Role::AlphaWolf { .. }
| Role::Elder { .. }
| Role::Gravedigger
| Role::Hunter { .. }
| Role::Militia { .. }
| Role::MapleWolf { .. }
| Role::Guardian { .. }
| Role::Apprentice(_)
| Role::Villager
| Role::Scapegoat { .. }
| Role::Mortician
| Role::MasonLeader { .. }
| Role::Empath { .. }
| Role::Vindicator
| Role::Diseased
| Role::BlackKnight { .. }
| Role::Weightlifter
| Role::PyreMaster { .. }
2025-10-06 01:03:16 +01:00
| Role::Protector { .. } => false,
}
}
pub fn wakes(&self, village: &Village) -> bool {
let night_zero = match village.time() {
GameTime::Day { number: _ } => return false,
GameTime::Night { number } => number == 0,
};
if night_zero {
2025-10-06 01:03:16 +01:00
return self.wakes_night_zero();
}
match self {
Role::AlphaWolf { killed: Some(_) }
| Role::Werewolf
2025-10-04 17:50:29 +01:00
| Role::Scapegoat { redeemed: false }
| Role::Militia { targeted: Some(_) }
| Role::Diseased
| Role::BlackKnight { .. }
| Role::Villager => false,
Role::LoneWolf => match village.time() {
GameTime::Day { number: _ } => return false,
GameTime::Night { number } => NonZeroU8::new(number),
2025-10-07 02:52:06 +01:00
}
.map(|night| village.executions_on_day(night))
.map(|execs| execs.iter().any(|e| e.is_wolf()))
.unwrap_or_default(),
Role::Insomniac
| Role::PowerSeer
| Role::Mortician
| Role::Beholder
| Role::MasonLeader { .. }
| Role::Empath { .. }
| Role::Vindicator
| Role::Weightlifter
| Role::PyreMaster { .. }
| Role::Adjudicator
| Role::Scapegoat { redeemed: true }
2025-10-04 17:50:29 +01:00
| Role::Shapeshifter { .. }
| Role::DireWolf { .. }
| Role::AlphaWolf { killed: None }
| Role::Arcanist
2025-10-04 17:50:29 +01:00
| Role::Protector { .. }
| Role::Gravedigger
2025-10-04 17:50:29 +01:00
| Role::Hunter { .. }
| Role::Militia { targeted: None }
2025-10-04 17:50:29 +01:00
| Role::MapleWolf { .. }
| Role::Guardian { .. }
| Role::Seer => true,
2025-10-04 17:50:29 +01:00
Role::Apprentice(title) => village
.characters()
.iter()
.any(|c| c.role_title() == *title),
Role::Elder {
knows_on_night,
woken_for_reveal,
..
} => {
!woken_for_reveal
&& match village.time() {
GameTime::Night { number } => number == knows_on_night.get(),
_ => false,
}
}
}
}
}
impl RoleTitle {
pub fn falsely_appear_village() -> Box<[RoleTitle]> {
Self::ALL
.iter()
.copied()
.filter(|r| r.wolf() && r.alignment().village())
.collect()
}
pub fn falsely_appear_wolf() -> Box<[RoleTitle]> {
Self::ALL
.iter()
.copied()
.filter(|r| !r.wolf() && r.alignment().wolves())
.collect()
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
pub enum Alignment {
Village,
Wolves,
}
impl Alignment {
pub const fn village(&self) -> bool {
matches!(self, Alignment::Village)
}
pub const fn wolves(&self) -> bool {
matches!(self, Alignment::Wolves)
}
}
impl Display for Alignment {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Alignment::Village => f.write_str("Village"),
Alignment::Wolves => f.write_str("Wolves"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, ChecksAs)]
pub enum ArcanistCheck {
#[checks]
Same,
#[checks]
Different,
}
pub const MAPLE_WOLF_ABSTAIN_LIMIT: NonZeroU8 = NonZeroU8::new(3).unwrap();
2025-10-06 22:30:01 +01:00
pub const PYREMASTER_VILLAGER_KILLS_TO_DIE: NonZeroU8 = NonZeroU8::new(2).unwrap();
2025-10-07 21:18:31 +01:00
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum RoleBlock {
Direwolf,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum PreviousGuardianAction {
Protect(CharacterIdentity),
Guard(CharacterIdentity),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
pub enum KillingWolfOrder {
Werewolf,
AlphaWolf,
Shapeshifter,
Berserker,
Psion,
Bloodletter,
Bloodhound,
DireWolf,
LoneWolf,
}