409 lines
14 KiB
Rust
409 lines
14 KiB
Rust
use core::{
|
|
fmt::{Debug, Display},
|
|
num::NonZeroU8,
|
|
};
|
|
|
|
use rand::distr::{Distribution, StandardUniform};
|
|
use serde::{Deserialize, Serialize};
|
|
use uuid::Uuid;
|
|
use werewolves_macros::{All, ChecksAs, Titles};
|
|
|
|
use crate::{
|
|
character::Character,
|
|
error::GameError,
|
|
message::Identification,
|
|
modifier::Modifier,
|
|
player::PlayerId,
|
|
role::{Role, RoleTitle},
|
|
};
|
|
|
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
|
|
pub enum OrRandom<T>
|
|
where
|
|
T: Debug + Clone + PartialEq,
|
|
StandardUniform: Distribution<T>,
|
|
{
|
|
Determined(T),
|
|
#[default]
|
|
Random,
|
|
}
|
|
|
|
impl<T> OrRandom<T>
|
|
where
|
|
for<'a> T: Debug + Clone + PartialEq,
|
|
StandardUniform: Distribution<T>,
|
|
{
|
|
pub fn into_concrete(self) -> T {
|
|
match self {
|
|
Self::Determined(value) => value,
|
|
Self::Random => rand::random(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(
|
|
Debug, PartialOrd, Ord, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, ChecksAs, All,
|
|
)]
|
|
pub enum Category {
|
|
#[checks]
|
|
Wolves,
|
|
Villager,
|
|
Intel,
|
|
Defensive,
|
|
Offensive,
|
|
StartsAsVillager,
|
|
}
|
|
impl Category {
|
|
pub fn entire_category(&self) -> Box<[SetupRoleTitle]> {
|
|
SetupRoleTitle::ALL
|
|
.iter()
|
|
.filter(|r| r.category() == *self)
|
|
.cloned()
|
|
.collect()
|
|
}
|
|
}
|
|
|
|
impl Display for Category {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
f.write_str(match self {
|
|
Category::Wolves => "Wolves",
|
|
Category::Villager => "Villager",
|
|
Category::Intel => "Intel",
|
|
Category::Defensive => "Defensive",
|
|
Category::Offensive => "Offensive",
|
|
Category::StartsAsVillager => "Starts As Villager",
|
|
})
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, ChecksAs, Titles)]
|
|
pub enum SetupRole {
|
|
#[checks(Category::Villager)]
|
|
Villager,
|
|
#[checks(Category::Villager)]
|
|
Scapegoat { redeemed: OrRandom<bool> },
|
|
#[checks(Category::Intel)]
|
|
Seer,
|
|
#[checks(Category::Intel)]
|
|
Arcanist,
|
|
#[checks(Category::Intel)]
|
|
Gravedigger,
|
|
#[checks(Category::Offensive)]
|
|
Hunter,
|
|
#[checks(Category::Offensive)]
|
|
Militia,
|
|
#[checks(Category::Offensive)]
|
|
MapleWolf,
|
|
#[checks(Category::Defensive)]
|
|
Guardian,
|
|
#[checks(Category::Defensive)]
|
|
Protector,
|
|
#[checks(Category::StartsAsVillager)]
|
|
Apprentice { to: Option<RoleTitle> },
|
|
#[checks(Category::StartsAsVillager)]
|
|
Elder { knows_on_night: NonZeroU8 },
|
|
|
|
#[checks(Category::Wolves)]
|
|
Werewolf,
|
|
#[checks(Category::Wolves)]
|
|
AlphaWolf,
|
|
#[checks(Category::Wolves)]
|
|
DireWolf,
|
|
#[checks(Category::Wolves)]
|
|
Shapeshifter,
|
|
|
|
#[checks(Category::Intel)]
|
|
Adjudicator,
|
|
#[checks(Category::Intel)]
|
|
PowerSeer,
|
|
#[checks(Category::Intel)]
|
|
Mortician,
|
|
#[checks(Category::Intel)]
|
|
Beholder,
|
|
#[checks(Category::Intel)]
|
|
MasonLeader { recruits_available: NonZeroU8 },
|
|
#[checks(Category::Intel)]
|
|
Empath,
|
|
#[checks(Category::Defensive)]
|
|
Vindicator,
|
|
#[checks(Category::Defensive)]
|
|
Diseased,
|
|
#[checks(Category::Defensive)]
|
|
BlackKnight,
|
|
#[checks(Category::Offensive)]
|
|
Weightlifter,
|
|
#[checks(Category::Offensive)]
|
|
PyreMaster,
|
|
}
|
|
|
|
impl SetupRoleTitle {
|
|
pub fn into_role(self) -> Role {
|
|
match self {
|
|
SetupRoleTitle::Villager => Role::Villager,
|
|
SetupRoleTitle::Scapegoat => Role::Scapegoat { redeemed: false },
|
|
SetupRoleTitle::Seer => Role::Seer,
|
|
SetupRoleTitle::Arcanist => Role::Arcanist,
|
|
SetupRoleTitle::Gravedigger => Role::Gravedigger,
|
|
SetupRoleTitle::Hunter => Role::Hunter { target: None },
|
|
SetupRoleTitle::Militia => Role::Militia { targeted: None },
|
|
SetupRoleTitle::MapleWolf => Role::MapleWolf {
|
|
last_kill_on_night: 0,
|
|
},
|
|
SetupRoleTitle::Guardian => Role::Guardian {
|
|
last_protected: None,
|
|
},
|
|
SetupRoleTitle::Protector => Role::Protector {
|
|
last_protected: None,
|
|
},
|
|
SetupRoleTitle::Apprentice => Role::Apprentice(RoleTitle::Arcanist),
|
|
SetupRoleTitle::Elder => Role::Elder {
|
|
woken_for_reveal: false,
|
|
lost_protection_night: None,
|
|
knows_on_night: NonZeroU8::new(1).unwrap(),
|
|
},
|
|
SetupRoleTitle::Werewolf => Role::Werewolf,
|
|
SetupRoleTitle::AlphaWolf => Role::AlphaWolf { killed: None },
|
|
SetupRoleTitle::DireWolf => Role::DireWolf,
|
|
SetupRoleTitle::Shapeshifter => Role::Shapeshifter { shifted_into: None },
|
|
SetupRoleTitle::Adjudicator => Role::Adjudicator,
|
|
SetupRoleTitle::PowerSeer => Role::PowerSeer,
|
|
SetupRoleTitle::Mortician => Role::Mortician,
|
|
SetupRoleTitle::Beholder => Role::Beholder,
|
|
SetupRoleTitle::MasonLeader => Role::MasonLeader {
|
|
recruits_available: 1,
|
|
recruits: Box::new([]),
|
|
},
|
|
SetupRoleTitle::Empath => Role::Empath { cursed: false },
|
|
SetupRoleTitle::Vindicator => Role::Vindicator,
|
|
SetupRoleTitle::Diseased => Role::Diseased,
|
|
SetupRoleTitle::BlackKnight => Role::BlackKnight { attacked: false },
|
|
SetupRoleTitle::Weightlifter => Role::Weightlifter,
|
|
SetupRoleTitle::PyreMaster => Role::PyreMaster {
|
|
villagers_killed: 0,
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Display for SetupRole {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
f.write_str(match self {
|
|
SetupRole::Villager => "Villager",
|
|
SetupRole::Scapegoat { .. } => "Scapegoat",
|
|
SetupRole::Seer => "Seer",
|
|
SetupRole::Arcanist => "Arcanist",
|
|
SetupRole::Gravedigger => "Gravedigger",
|
|
SetupRole::Hunter => "Hunter",
|
|
SetupRole::Militia => "Militia",
|
|
SetupRole::MapleWolf => "MapleWolf",
|
|
SetupRole::Guardian => "Guardian",
|
|
SetupRole::Protector => "Protector",
|
|
SetupRole::Apprentice { .. } => "Apprentice",
|
|
SetupRole::Elder { .. } => "Elder",
|
|
SetupRole::Werewolf => "Werewolf",
|
|
SetupRole::AlphaWolf => "AlphaWolf",
|
|
SetupRole::DireWolf => "DireWolf",
|
|
SetupRole::Shapeshifter => "Shapeshifter",
|
|
SetupRole::Adjudicator => "Adjudicator",
|
|
SetupRole::PowerSeer => "PowerSeer",
|
|
SetupRole::Mortician => "Mortician",
|
|
SetupRole::Beholder => "Beholder",
|
|
SetupRole::MasonLeader { .. } => "Mason Leader",
|
|
SetupRole::Empath => "Empath",
|
|
SetupRole::Vindicator => "Vindicator",
|
|
SetupRole::Diseased => "Diseased",
|
|
SetupRole::BlackKnight => "Black Knight",
|
|
SetupRole::Weightlifter => "Weightlifter",
|
|
SetupRole::PyreMaster => "Pyremaster",
|
|
})
|
|
}
|
|
}
|
|
|
|
impl SetupRole {
|
|
pub fn into_role(self, roles_in_game: &[RoleTitle]) -> Result<Role, GameError> {
|
|
Ok(match self {
|
|
SetupRole::Villager => Role::Villager,
|
|
SetupRole::Scapegoat { redeemed } => Role::Scapegoat {
|
|
redeemed: redeemed.into_concrete(),
|
|
},
|
|
SetupRole::Seer => Role::Seer,
|
|
SetupRole::Arcanist => Role::Arcanist,
|
|
SetupRole::Gravedigger => Role::Gravedigger,
|
|
SetupRole::Hunter => Role::Hunter { target: None },
|
|
SetupRole::Militia => Role::Militia { targeted: None },
|
|
SetupRole::MapleWolf => Role::MapleWolf {
|
|
last_kill_on_night: 0,
|
|
},
|
|
SetupRole::Guardian => Role::Guardian {
|
|
last_protected: None,
|
|
},
|
|
SetupRole::Protector => Role::Protector {
|
|
last_protected: None,
|
|
},
|
|
SetupRole::Apprentice { to: Some(role) } => Role::Apprentice(role),
|
|
SetupRole::Apprentice { to: None } => {
|
|
let mentors = roles_in_game
|
|
.iter()
|
|
.filter(|r| r.is_mentor())
|
|
.collect::<Box<[_]>>();
|
|
if mentors.is_empty() {
|
|
return Err(GameError::NoApprenticeMentor);
|
|
}
|
|
let mentor = *mentors[rand::random_range(0..mentors.len())];
|
|
Role::Apprentice(mentor)
|
|
}
|
|
SetupRole::Elder { knows_on_night } => Role::Elder {
|
|
knows_on_night,
|
|
woken_for_reveal: false,
|
|
lost_protection_night: None,
|
|
},
|
|
SetupRole::Werewolf => Role::Werewolf,
|
|
SetupRole::AlphaWolf => Role::AlphaWolf { killed: None },
|
|
SetupRole::DireWolf => Role::DireWolf,
|
|
SetupRole::Shapeshifter => Role::Shapeshifter { shifted_into: None },
|
|
SetupRole::MasonLeader { recruits_available } => Role::MasonLeader {
|
|
recruits_available: recruits_available.get(),
|
|
recruits: Box::new([]),
|
|
},
|
|
SetupRole::Adjudicator => Role::Adjudicator,
|
|
SetupRole::PowerSeer => Role::PowerSeer,
|
|
SetupRole::Mortician => Role::Mortician,
|
|
SetupRole::Beholder => Role::Beholder,
|
|
SetupRole::Empath => Role::Empath { cursed: false },
|
|
SetupRole::Vindicator => Role::Vindicator,
|
|
SetupRole::Diseased => Role::Diseased,
|
|
SetupRole::BlackKnight => Role::BlackKnight { attacked: false },
|
|
SetupRole::Weightlifter => Role::Weightlifter,
|
|
SetupRole::PyreMaster => Role::PyreMaster {
|
|
villagers_killed: 0,
|
|
},
|
|
})
|
|
}
|
|
}
|
|
|
|
impl From<SetupRole> for RoleTitle {
|
|
fn from(value: SetupRole) -> Self {
|
|
match value {
|
|
SetupRole::Villager => RoleTitle::Villager,
|
|
SetupRole::Scapegoat { .. } => RoleTitle::Scapegoat,
|
|
SetupRole::Seer => RoleTitle::Seer,
|
|
SetupRole::Arcanist => RoleTitle::Arcanist,
|
|
SetupRole::Gravedigger => RoleTitle::Gravedigger,
|
|
SetupRole::Hunter => RoleTitle::Hunter,
|
|
SetupRole::Militia => RoleTitle::Militia,
|
|
SetupRole::MapleWolf => RoleTitle::MapleWolf,
|
|
SetupRole::Guardian => RoleTitle::Guardian,
|
|
SetupRole::Protector => RoleTitle::Protector,
|
|
SetupRole::Apprentice { .. } => RoleTitle::Apprentice,
|
|
SetupRole::Elder { .. } => RoleTitle::Elder,
|
|
SetupRole::Werewolf => RoleTitle::Werewolf,
|
|
SetupRole::AlphaWolf => RoleTitle::AlphaWolf,
|
|
SetupRole::DireWolf => RoleTitle::DireWolf,
|
|
SetupRole::Shapeshifter => RoleTitle::Shapeshifter,
|
|
SetupRole::Adjudicator => RoleTitle::Adjudicator,
|
|
SetupRole::PowerSeer => RoleTitle::PowerSeer,
|
|
SetupRole::Mortician => RoleTitle::Mortician,
|
|
SetupRole::Beholder => RoleTitle::Beholder,
|
|
SetupRole::MasonLeader { .. } => RoleTitle::MasonLeader,
|
|
SetupRole::Empath => RoleTitle::Empath,
|
|
SetupRole::Vindicator => RoleTitle::Vindicator,
|
|
SetupRole::Diseased => RoleTitle::Diseased,
|
|
SetupRole::BlackKnight => RoleTitle::BlackKnight,
|
|
SetupRole::Weightlifter => RoleTitle::Weightlifter,
|
|
SetupRole::PyreMaster => RoleTitle::PyreMaster,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl From<RoleTitle> for SetupRole {
|
|
fn from(value: RoleTitle) -> Self {
|
|
match value {
|
|
RoleTitle::Villager => SetupRole::Villager,
|
|
RoleTitle::Scapegoat => SetupRole::Scapegoat {
|
|
redeemed: Default::default(),
|
|
},
|
|
RoleTitle::Seer => SetupRole::Seer,
|
|
RoleTitle::Arcanist => SetupRole::Arcanist,
|
|
RoleTitle::Gravedigger => SetupRole::Gravedigger,
|
|
RoleTitle::Hunter => SetupRole::Hunter,
|
|
RoleTitle::Militia => SetupRole::Militia,
|
|
RoleTitle::MapleWolf => SetupRole::MapleWolf,
|
|
RoleTitle::Guardian => SetupRole::Guardian,
|
|
RoleTitle::Protector => SetupRole::Protector,
|
|
RoleTitle::Apprentice => SetupRole::Apprentice { to: None },
|
|
RoleTitle::Elder => SetupRole::Elder {
|
|
knows_on_night: NonZeroU8::new(3).unwrap(),
|
|
},
|
|
RoleTitle::Werewolf => SetupRole::Werewolf,
|
|
RoleTitle::AlphaWolf => SetupRole::AlphaWolf,
|
|
RoleTitle::DireWolf => SetupRole::DireWolf,
|
|
RoleTitle::Shapeshifter => SetupRole::Shapeshifter,
|
|
RoleTitle::Adjudicator => SetupRole::Adjudicator,
|
|
RoleTitle::PowerSeer => SetupRole::PowerSeer,
|
|
RoleTitle::Mortician => SetupRole::Mortician,
|
|
RoleTitle::Beholder => SetupRole::Beholder,
|
|
RoleTitle::MasonLeader => SetupRole::MasonLeader {
|
|
recruits_available: NonZeroU8::new(1).unwrap(),
|
|
},
|
|
RoleTitle::Empath => SetupRole::Empath,
|
|
RoleTitle::Vindicator => SetupRole::Vindicator,
|
|
RoleTitle::Diseased => SetupRole::Diseased,
|
|
RoleTitle::BlackKnight => SetupRole::BlackKnight,
|
|
RoleTitle::Weightlifter => SetupRole::Weightlifter,
|
|
RoleTitle::PyreMaster => SetupRole::PyreMaster,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
|
|
pub struct SlotId(Uuid);
|
|
|
|
impl SlotId {
|
|
pub fn new() -> Self {
|
|
Self(Uuid::new_v4())
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
pub struct SetupSlot {
|
|
pub slot_id: SlotId,
|
|
pub role: SetupRole,
|
|
pub modifiers: Vec<Modifier>,
|
|
pub assign_to: Option<PlayerId>,
|
|
pub created_order: u32,
|
|
}
|
|
|
|
impl SetupSlot {
|
|
pub fn new(title: RoleTitle, created_order: u32) -> Self {
|
|
Self {
|
|
created_order,
|
|
assign_to: None,
|
|
role: title.into(),
|
|
modifiers: Vec::new(),
|
|
slot_id: SlotId::new(),
|
|
}
|
|
}
|
|
|
|
pub fn into_character(
|
|
self,
|
|
ident: Identification,
|
|
roles_in_game: &[RoleTitle],
|
|
) -> Result<Character, GameError> {
|
|
Character::new(ident.clone(), self.role.into_role(roles_in_game)?)
|
|
.ok_or(GameError::PlayerNotAssignedNumber(ident.to_string()))
|
|
}
|
|
}
|
|
|
|
impl Category {
|
|
pub const fn class(&self) -> &'static str {
|
|
match self {
|
|
Category::Wolves => "wolves",
|
|
Category::Villager => "village",
|
|
Category::Intel => "intel",
|
|
Category::Defensive => "defensive",
|
|
Category::Offensive => "offensive",
|
|
Category::StartsAsVillager => "starts-as-villager",
|
|
}
|
|
}
|
|
}
|