proto changes to accomodate leptos client

This commit is contained in:
emilis 2026-02-17 17:17:25 +00:00
parent 78ecb6c164
commit 314e113a46
No known key found for this signature in database
13 changed files with 214 additions and 35 deletions

View File

@ -47,6 +47,12 @@ impl CharacterId {
pub const fn from_u128(v: u128) -> Self { pub const fn from_u128(v: u128) -> Self {
Self(uuid::Uuid::from_u128(v)) Self(uuid::Uuid::from_u128(v))
} }
pub const fn from_uuid(v: uuid::Uuid) -> Self {
Self(v)
}
pub const fn into_uuid(self) -> uuid::Uuid {
self.0
}
} }
impl Display for CharacterId { impl Display for CharacterId {
@ -66,7 +72,11 @@ pub struct Character {
} }
impl Character { impl Character {
pub fn new( pub fn new(ident: Identification, role: Role, auras: Vec<Aura>) -> Option<Self> {
Self::new_with_character_id(ident, role, auras, CharacterId::new())
}
pub(crate) fn new_with_character_id(
Identification { Identification {
player_id, player_id,
public: public:
@ -78,6 +88,7 @@ impl Character {
}: Identification, }: Identification,
role: Role, role: Role,
auras: Vec<Aura>, auras: Vec<Aura>,
character_id: CharacterId,
) -> Option<Self> { ) -> Option<Self> {
Some(Self { Some(Self {
role, role,
@ -86,7 +97,7 @@ impl Character {
auras: Auras::new(auras), auras: Auras::new(auras),
role_changes: Vec::new(), role_changes: Vec::new(),
identity: CharacterIdentity { identity: CharacterIdentity {
character_id: CharacterId::new(), character_id,
name, name,
pronouns, pronouns,
number: number?, number: number?,

View File

@ -109,4 +109,16 @@ pub enum GameError {
NoCurrentPromptForAura, NoCurrentPromptForAura,
#[error("you're not dead")] #[error("you're not dead")]
NotDead, NotDead,
#[error("invalid character id assignment for player ID {for_player}")]
InvalidCharacterIdAssignment { for_player: PlayerId },
#[error("already joined")]
AlreadyJoined,
#[error("cannot join own game")]
CannotJoinOwnGame,
#[error("cannot leave a started game")]
CannotLeaveOnceStarted,
#[error("cannot join a started game")]
CannotJoinStartedGame,
#[error("game already started")]
GameAlreadyStarted,
} }

View File

@ -61,6 +61,19 @@ pub struct Game {
} }
impl Game { impl Game {
pub fn new_with_assigned_character_ids(
players: &[(Identification, CharacterId)],
settings: GameSettings,
) -> Result<Self> {
let village = Village::new_with_assigned_character_ids(players, settings)?;
Ok(Self {
started: Utc::now(),
history: GameStory::new(village.clone()),
state: GameState::Night {
night: Night::new(village)?,
},
})
}
pub fn new(players: &[Identification], settings: GameSettings) -> Result<Self> { pub fn new(players: &[Identification], settings: GameSettings) -> Result<Self> {
let village = Village::new(players, settings)?; let village = Village::new(players, settings)?;
Ok(Self { Ok(Self {

View File

@ -829,7 +829,7 @@ impl Night {
NightChange::Protection { NightChange::Protection {
target, target,
protection: _, protection: _,
} => target == kill_target, } => target == kill_target || target == *source,
_ => false, _ => false,
}) { }) {
// there is protection, so the kill doesn't happen -> no shapeshift // there is protection, so the kill doesn't happen -> no shapeshift

View File

@ -23,7 +23,12 @@ use super::Result;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::{character::Character, error::GameError, message::Identification, role::RoleTitle}; use crate::{
character::{Character, CharacterId},
error::GameError,
message::Identification,
role::RoleTitle,
};
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct GameSettings { pub struct GameSettings {
@ -115,8 +120,12 @@ impl GameSettings {
} }
} }
pub fn assign(&self, players: &[Identification]) -> Result<Box<[Character]>> { pub fn assign_with_set_character_ids(
self.check_with_player_list(players)?; &self,
players: &[(Identification, CharacterId)],
) -> Result<Box<[Character]>> {
let idents_only = players.iter().map(|i| i.0.clone()).collect::<Box<_>>();
self.check_with_player_list(&idents_only)?;
let roles_in_game = self let roles_in_game = self
.roles .roles
@ -131,7 +140,7 @@ impl GameSettings {
s.assign_to.as_ref().map(|assign_to| { s.assign_to.as_ref().map(|assign_to| {
players players
.iter() .iter()
.find(|pid| pid.player_id == *assign_to) .find(|(pid, _)| pid.player_id == *assign_to)
.ok_or(GameError::AssignedPlayerMissing(*assign_to)) .ok_or(GameError::AssignedPlayerMissing(*assign_to))
.map(|id| (id, s)) .map(|id| (id, s))
}) })
@ -140,10 +149,10 @@ impl GameSettings {
let mut random_assign_players = players let mut random_assign_players = players
.iter() .iter()
.filter(|p| { .filter(|(p, _)| {
!with_assigned_roles !with_assigned_roles
.iter() .iter()
.any(|(r, _)| r.player_id == p.player_id) .any(|((r, _), _)| r.player_id == p.player_id)
}) })
.collect::<Box<[_]>>(); .collect::<Box<[_]>>();
@ -156,10 +165,21 @@ impl GameSettings {
.into_iter() .into_iter()
.zip(self.roles.iter().filter(|s| s.assign_to.is_none())), .zip(self.roles.iter().filter(|s| s.assign_to.is_none())),
) )
.map(|(id, slot)| slot.clone().into_character(id.clone(), &roles_in_game)) .map(|((ident, char_id), slot)| {
slot.clone()
.into_character_with_id(ident.clone(), &roles_in_game, *char_id)
})
.collect::<Result<Box<[_]>>>() .collect::<Result<Box<[_]>>>()
} }
pub fn assign(&self, players: &[Identification]) -> Result<Box<[Character]>> {
let with_cids = players
.iter()
.map(|ident| (ident.clone(), CharacterId::new()))
.collect::<Box<_>>();
self.assign_with_set_character_ids(&with_cids)
}
pub fn check_with_player_list(&self, players: &[Identification]) -> Result<()> { pub fn check_with_player_list(&self, players: &[Identification]) -> Result<()> {
self.check()?; self.check()?;
let (p_len, r_len) = (players.len(), self.roles.len()); let (p_len, r_len) = (players.len(), self.roles.len());

View File

@ -24,7 +24,7 @@ use werewolves_macros::{All, ChecksAs, Titles};
use crate::{ use crate::{
aura::AuraTitle, aura::AuraTitle,
character::Character, character::{Character, CharacterId},
error::GameError, error::GameError,
message::Identification, message::Identification,
player::PlayerId, player::PlayerId,
@ -435,6 +435,12 @@ impl SlotId {
} }
} }
impl Display for SlotId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Display::fmt(&self.0, f)
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct SetupSlot { pub struct SetupSlot {
pub slot_id: SlotId, pub slot_id: SlotId,
@ -470,17 +476,22 @@ impl SetupSlot {
) )
.ok_or(GameError::PlayerNotAssignedNumber(ident.to_string())) .ok_or(GameError::PlayerNotAssignedNumber(ident.to_string()))
} }
}
impl Category { pub fn into_character_with_id(
pub const fn class(&self) -> &'static str { self,
match self { ident: Identification,
Category::Wolves => "wolves", roles_in_game: &[RoleTitle],
Category::Villager => "village", id: CharacterId,
Category::Intel => "intel", ) -> Result<Character, GameError> {
Category::Defensive => "defensive", Character::new_with_character_id(
Category::Offensive => "offensive", ident.clone(),
Category::StartsAsVillager => "starts-as-villager", self.role.into_role(roles_in_game)?,
} self.auras
.into_iter()
.map(|aura| aura.into_aura())
.collect(),
id,
)
.ok_or(GameError::PlayerNotAssignedNumber(ident.to_string()))
} }
} }

View File

@ -43,6 +43,27 @@ pub struct Village {
} }
impl Village { impl Village {
pub fn new_with_assigned_character_ids(
players: &[(Identification, CharacterId)],
settings: GameSettings,
) -> Result<Self> {
if settings.min_players_needed() > players.len() {
return Err(GameError::TooManyRoles {
players: players.len() as u8,
roles: settings.min_players_needed() as u8,
});
}
let mut characters = settings.assign_with_set_character_ids(players)?;
assert_eq!(characters.len(), players.len());
characters.sort_by_key(|l| l.number());
Ok(Self {
settings,
characters,
time: GameTime::Night { number: 0 },
dead_chat: DeadChat::new(),
})
}
pub fn new(players: &[Identification], settings: GameSettings) -> Result<Self> { pub fn new(players: &[Identification], settings: GameSettings) -> Result<Self> {
if settings.min_players_needed() > players.len() { if settings.min_players_needed() > players.len() {
return Err(GameError::TooManyRoles { return Err(GameError::TooManyRoles {

View File

@ -1119,7 +1119,7 @@ fn big_game_test_based_on_story_test() {
); );
game.execute().title().vindicator(); game.execute().title().vindicator();
game.mark(game.character_by_player_id(shapeshifter).character_id()); game.mark(game.character_by_player_id(insomniac).character_id());
game.r#continue().sleep(); game.r#continue().sleep();
game.next().title().wolf_pack_kill(); game.next().title().wolf_pack_kill();
@ -1127,11 +1127,16 @@ fn big_game_test_based_on_story_test() {
game.r#continue().r#continue(); game.r#continue().r#continue();
game.next().title().shapeshifter(); game.next().title().shapeshifter();
game.process(HostGameMessage::Night(HostNightMessage::ActionResponse( assert_eq!(
ActionResponse::Shapeshift, game.process(HostGameMessage::Night(HostNightMessage::ActionResponse(
))) ActionResponse::Shapeshift,
.expect("shapeshift"); )))
// game.r#continue().r#continue(); .expect("shapeshift"),
ServerToHostMessage::ActionResult(
Some(game.character_by_player_id(shapeshifter).identity()),
ActionResult::Continue
)
);
assert_eq!( assert_eq!(
game.next(), game.next(),

View File

@ -444,3 +444,52 @@ fn shapeshift_removes_village_prompt_but_previous_can_bring_it_back() {
None None
); );
} }
#[test]
fn shapeshifter_protected_when_shifting_prevents_shift() {
init_log();
let players = gen_players(1..21);
let mut player_ids = players.iter().map(|p| p.player_id);
let shapeshifter = player_ids.next().unwrap();
let wolf = player_ids.next().unwrap();
let protector = player_ids.next().unwrap();
let hunter = player_ids.next().unwrap();
let mut settings = GameSettings::empty();
settings.add_and_assign(SetupRole::Shapeshifter, shapeshifter);
settings.add_and_assign(SetupRole::Werewolf, wolf);
settings.add_and_assign(SetupRole::Protector, protector);
settings.add_and_assign(SetupRole::Hunter, hunter);
settings.fill_remaining_slots_with_villagers(players.len());
let mut game = Game::new(&players, settings).unwrap();
game.r#continue().r#continue();
assert_eq!(game.next().title(), ActionPromptTitle::WolvesIntro);
game.r#continue().sleep();
game.next_expect_day();
game.execute().title().protector();
game.mark(game.character_by_player_id(shapeshifter).character_id());
game.r#continue().sleep();
game.next().title().wolf_pack_kill();
game.mark(game.character_by_player_id(hunter).character_id());
game.r#continue().r#continue();
game.next().title().shapeshifter();
match game
.process(HostGameMessage::Night(HostNightMessage::ActionResponse(
ActionResponse::Shapeshift,
)))
.unwrap()
{
ServerToHostMessage::ActionResult(_, res) => assert_eq!(res, ActionResult::ShiftFailed),
other => panic!("expected action result, got {other:?}"),
}
game.r#continue().sleep();
game.next().title().hunter();
game.mark_villager();
game.r#continue().sleep();
game.next_expect_day();
}

View File

@ -22,6 +22,7 @@ use core::num::NonZeroU8;
use chrono::{DateTime, Utc}; use chrono::{DateTime, Utc};
pub use ident::*; pub use ident::*;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use werewolves_macros::Titles;
use crate::{ use crate::{
character::CharacterId, character::CharacterId,
@ -62,7 +63,7 @@ pub struct DayCharacter {
pub alive: bool, pub alive: bool,
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Titles)]
pub enum ServerToClientMessage { pub enum ServerToClientMessage {
Disconnect, Disconnect,
LobbyInfo { LobbyInfo {
@ -73,9 +74,6 @@ pub enum ServerToClientMessage {
GameStart { GameStart {
role: RoleTitle, role: RoleTitle,
}, },
InvalidMessageForGameState,
NoSuchTarget,
GameOver(GameOver),
Story(GameStory), Story(GameStory),
Update(PlayerUpdate), Update(PlayerUpdate),
DeadChat(Box<[DeadChatMessage]>), DeadChat(Box<[DeadChatMessage]>),
@ -85,7 +83,7 @@ pub enum ServerToClientMessage {
Error(GameError), Error(GameError),
} }
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum PlayerUpdate { pub enum PlayerUpdate {
Number(NonZeroU8), Number(NonZeroU8),
} }

View File

@ -105,8 +105,8 @@ pub enum ServerToHostMessage {
Lobby { Lobby {
players: Box<[PlayerState]>, players: Box<[PlayerState]>,
settings: GameSettings, settings: GameSettings,
qr_mode: bool,
}, },
QrMode(bool),
Error(GameError), Error(GameError),
GameOver(GameOver), GameOver(GameOver),
WaitingForRoleRevealAcks { WaitingForRoleRevealAcks {

View File

@ -31,6 +31,15 @@ impl PlayerId {
pub const fn from_u128(v: u128) -> Self { pub const fn from_u128(v: u128) -> Self {
Self(uuid::Uuid::from_u128(v)) Self(uuid::Uuid::from_u128(v))
} }
pub const fn from_uuid(uuid: uuid::Uuid) -> Self {
Self(uuid)
}
}
impl From<PlayerId> for uuid::Uuid {
fn from(value: PlayerId) -> Self {
value.0
}
} }
impl Display for PlayerId { impl Display for PlayerId {

View File

@ -20,7 +20,7 @@ use werewolves_macros::{All, ChecksAs, RefAndMut, Titles};
use crate::{ use crate::{
character::CharacterId, character::CharacterId,
diedto::DiedTo, diedto::DiedTo,
game::{GameTime, Village}, game::{Category, GameTime, Village},
message::CharacterIdentity, message::CharacterIdentity,
}; };
@ -122,50 +122,59 @@ pub enum Role {
#[checks(Alignment::Village)] #[checks(Alignment::Village)]
#[checks(Killer::NotKiller)] #[checks(Killer::NotKiller)]
#[checks(Powerful::NotPowerful)] #[checks(Powerful::NotPowerful)]
#[checks(Category::Villager)]
Villager, Villager,
#[checks(Alignment::Wolves)] #[checks(Alignment::Wolves)]
#[checks(Killer::Killer)] #[checks(Killer::Killer)]
#[checks(Powerful::Powerful)] #[checks(Powerful::Powerful)]
#[checks(Category::Villager)]
Scapegoat { redeemed: bool }, Scapegoat { redeemed: bool },
#[checks(Alignment::Village)] #[checks(Alignment::Village)]
#[checks(Powerful::Powerful)] #[checks(Powerful::Powerful)]
#[checks(Killer::NotKiller)] #[checks(Killer::NotKiller)]
#[checks("is_mentor")] #[checks("is_mentor")]
#[checks("doesnt_wake_if_died_tonight")] #[checks("doesnt_wake_if_died_tonight")]
#[checks(Category::Intel)]
Seer, Seer,
#[checks(Alignment::Village)] #[checks(Alignment::Village)]
#[checks(Powerful::Powerful)] #[checks(Powerful::Powerful)]
#[checks(Killer::NotKiller)] #[checks(Killer::NotKiller)]
#[checks("is_mentor")] #[checks("is_mentor")]
#[checks("doesnt_wake_if_died_tonight")] #[checks("doesnt_wake_if_died_tonight")]
#[checks(Category::Intel)]
Arcanist, Arcanist,
#[checks(Alignment::Village)] #[checks(Alignment::Village)]
#[checks(Powerful::Powerful)] #[checks(Powerful::Powerful)]
#[checks(Killer::NotKiller)] #[checks(Killer::NotKiller)]
#[checks("is_mentor")] #[checks("is_mentor")]
#[checks("doesnt_wake_if_died_tonight")] #[checks("doesnt_wake_if_died_tonight")]
#[checks(Category::Intel)]
Adjudicator, Adjudicator,
#[checks(Alignment::Village)] #[checks(Alignment::Village)]
#[checks(Powerful::Powerful)] #[checks(Powerful::Powerful)]
#[checks(Killer::NotKiller)] #[checks(Killer::NotKiller)]
#[checks("is_mentor")] #[checks("is_mentor")]
#[checks("doesnt_wake_if_died_tonight")] #[checks("doesnt_wake_if_died_tonight")]
#[checks(Category::Intel)]
PowerSeer, PowerSeer,
#[checks(Alignment::Village)] #[checks(Alignment::Village)]
#[checks(Powerful::Powerful)] #[checks(Powerful::Powerful)]
#[checks(Killer::NotKiller)] #[checks(Killer::NotKiller)]
#[checks("is_mentor")] #[checks("is_mentor")]
#[checks("doesnt_wake_if_died_tonight")] #[checks("doesnt_wake_if_died_tonight")]
#[checks(Category::Intel)]
Mortician, Mortician,
#[checks(Alignment::Village)] #[checks(Alignment::Village)]
#[checks(Powerful::Powerful)] #[checks(Powerful::Powerful)]
#[checks(Killer::NotKiller)] #[checks(Killer::NotKiller)]
#[checks("is_mentor")] #[checks("is_mentor")]
#[checks("doesnt_wake_if_died_tonight")] #[checks("doesnt_wake_if_died_tonight")]
#[checks(Category::Intel)]
Beholder, Beholder,
#[checks(Alignment::Village)] #[checks(Alignment::Village)]
#[checks(Powerful::Powerful)] #[checks(Powerful::Powerful)]
#[checks(Killer::NotKiller)] #[checks(Killer::NotKiller)]
#[checks(Category::Intel)]
MasonLeader { MasonLeader {
recruits_available: u8, recruits_available: u8,
recruits: Box<[CharacterId]>, recruits: Box<[CharacterId]>,
@ -173,58 +182,69 @@ pub enum Role {
#[checks(Alignment::Village)] #[checks(Alignment::Village)]
#[checks(Powerful::Powerful)] #[checks(Powerful::Powerful)]
#[checks(Killer::NotKiller)] #[checks(Killer::NotKiller)]
#[checks(Category::Intel)]
Empath { cursed: bool }, Empath { cursed: bool },
#[checks(Alignment::Village)] #[checks(Alignment::Village)]
#[checks(Powerful::Powerful)] #[checks(Powerful::Powerful)]
#[checks(Killer::NotKiller)] #[checks(Killer::NotKiller)]
#[checks("is_mentor")] #[checks("is_mentor")]
#[checks(Category::Defensive)]
Vindicator, Vindicator,
#[checks(Alignment::Village)] #[checks(Alignment::Village)]
#[checks(Powerful::Powerful)] #[checks(Powerful::Powerful)]
#[checks(Killer::NotKiller)] #[checks(Killer::NotKiller)]
#[checks("is_mentor")] #[checks("is_mentor")]
#[checks(Category::Defensive)]
Diseased, Diseased,
#[checks(Alignment::Village)] #[checks(Alignment::Village)]
#[checks(Powerful::Powerful)] #[checks(Powerful::Powerful)]
#[checks(Killer::NotKiller)] #[checks(Killer::NotKiller)]
#[checks("is_mentor")] #[checks("is_mentor")]
#[checks(Category::Defensive)]
BlackKnight { attacked: Option<DiedTo> }, BlackKnight { attacked: Option<DiedTo> },
#[checks(Alignment::Village)] #[checks(Alignment::Village)]
#[checks(Powerful::Powerful)] #[checks(Powerful::Powerful)]
#[checks(Killer::NotKiller)] #[checks(Killer::NotKiller)]
#[checks("is_mentor")] #[checks("is_mentor")]
#[checks(Category::Offensive)]
Weightlifter, Weightlifter,
#[checks(Alignment::Village)] #[checks(Alignment::Village)]
#[checks(Powerful::Powerful)] #[checks(Powerful::Powerful)]
#[checks(Killer::Killer)] #[checks(Killer::Killer)]
#[checks("is_mentor")] #[checks("is_mentor")]
#[checks(Category::Offensive)]
PyreMaster { villagers_killed: u8 }, PyreMaster { villagers_killed: u8 },
#[checks(Alignment::Village)] #[checks(Alignment::Village)]
#[checks(Powerful::Powerful)] #[checks(Powerful::Powerful)]
#[checks(Killer::NotKiller)] #[checks(Killer::NotKiller)]
#[checks("is_mentor")] #[checks("is_mentor")]
#[checks("doesnt_wake_if_died_tonight")] #[checks("doesnt_wake_if_died_tonight")]
#[checks(Category::Intel)]
Gravedigger, Gravedigger,
#[checks(Alignment::Village)] #[checks(Alignment::Village)]
#[checks(Killer::Killer)] #[checks(Killer::Killer)]
#[checks(Powerful::Powerful)] #[checks(Powerful::Powerful)]
#[checks("is_mentor")] #[checks("is_mentor")]
#[checks] #[checks]
#[checks(Category::Offensive)]
Hunter { target: Option<CharacterId> }, Hunter { target: Option<CharacterId> },
#[checks(Alignment::Village)] #[checks(Alignment::Village)]
#[checks(Killer::Killer)] #[checks(Killer::Killer)]
#[checks(Powerful::Powerful)] #[checks(Powerful::Powerful)]
#[checks("is_mentor")] #[checks("is_mentor")]
#[checks(Category::Offensive)]
Militia { targeted: Option<CharacterId> }, Militia { targeted: Option<CharacterId> },
#[checks(Alignment::Wolves)] #[checks(Alignment::Wolves)]
#[checks(Killer::Killer)] #[checks(Killer::Killer)]
#[checks(Powerful::Powerful)] #[checks(Powerful::Powerful)]
#[checks("is_mentor")] #[checks("is_mentor")]
#[checks(Category::Offensive)]
MapleWolf { last_kill_on_night: u8 }, MapleWolf { last_kill_on_night: u8 },
#[checks(Alignment::Village)] #[checks(Alignment::Village)]
#[checks(Powerful::Powerful)] #[checks(Powerful::Powerful)]
#[checks(Killer::Killer)] #[checks(Killer::Killer)]
#[checks("is_mentor")] #[checks("is_mentor")]
#[checks(Category::Defensive)]
Guardian { Guardian {
last_protected: Option<PreviousGuardianAction>, last_protected: Option<PreviousGuardianAction>,
}, },
@ -232,14 +252,17 @@ pub enum Role {
#[checks(Powerful::Powerful)] #[checks(Powerful::Powerful)]
#[checks(Killer::NotKiller)] #[checks(Killer::NotKiller)]
#[checks("is_mentor")] #[checks("is_mentor")]
#[checks(Category::Defensive)]
Protector { last_protected: Option<CharacterId> }, Protector { last_protected: Option<CharacterId> },
#[checks(Alignment::Village)] #[checks(Alignment::Village)]
#[checks(Powerful::Powerful)] #[checks(Powerful::Powerful)]
#[checks(Killer::NotKiller)] #[checks(Killer::NotKiller)]
#[checks(Category::StartsAsVillager)]
Apprentice(RoleTitle), Apprentice(RoleTitle),
#[checks(Alignment::Village)] #[checks(Alignment::Village)]
#[checks(Powerful::Powerful)] #[checks(Powerful::Powerful)]
#[checks(Killer::NotKiller)] #[checks(Killer::NotKiller)]
#[checks(Category::StartsAsVillager)]
Elder { Elder {
knows_on_night: NonZeroU8, knows_on_night: NonZeroU8,
woken_for_reveal: bool, woken_for_reveal: bool,
@ -249,6 +272,7 @@ pub enum Role {
#[checks(Powerful::Powerful)] #[checks(Powerful::Powerful)]
#[checks(Killer::NotKiller)] #[checks(Killer::NotKiller)]
#[checks("doesnt_wake_if_died_tonight")] #[checks("doesnt_wake_if_died_tonight")]
#[checks(Category::Intel)]
Insomniac, Insomniac,
#[checks(Alignment::Wolves)] #[checks(Alignment::Wolves)]
@ -256,33 +280,39 @@ pub enum Role {
#[checks(Powerful::Powerful)] #[checks(Powerful::Powerful)]
#[checks("wolf")] #[checks("wolf")]
#[checks("killing_wolf")] #[checks("killing_wolf")]
#[checks(Category::Wolves)]
Werewolf, Werewolf,
#[checks(Alignment::Wolves)] #[checks(Alignment::Wolves)]
#[checks(Killer::Killer)] #[checks(Killer::Killer)]
#[checks(Powerful::Powerful)] #[checks(Powerful::Powerful)]
#[checks("wolf")] #[checks("wolf")]
#[checks("killing_wolf")] #[checks("killing_wolf")]
#[checks(Category::Wolves)]
AlphaWolf { killed: Option<CharacterId> }, AlphaWolf { killed: Option<CharacterId> },
#[checks(Alignment::Village)] #[checks(Alignment::Village)]
#[checks(Killer::Killer)] #[checks(Killer::Killer)]
#[checks(Powerful::Powerful)] #[checks(Powerful::Powerful)]
#[checks("wolf")] #[checks("wolf")]
#[checks(Category::Wolves)]
DireWolf { last_blocked: Option<CharacterId> }, DireWolf { last_blocked: Option<CharacterId> },
#[checks(Alignment::Wolves)] #[checks(Alignment::Wolves)]
#[checks(Killer::Killer)] #[checks(Killer::Killer)]
#[checks(Powerful::Powerful)] #[checks(Powerful::Powerful)]
#[checks("wolf")] #[checks("wolf")]
#[checks("killing_wolf")] #[checks("killing_wolf")]
#[checks(Category::Wolves)]
Shapeshifter { shifted_into: Option<CharacterId> }, Shapeshifter { shifted_into: Option<CharacterId> },
#[checks(Alignment::Wolves)] #[checks(Alignment::Wolves)]
#[checks(Killer::Killer)] #[checks(Killer::Killer)]
#[checks(Powerful::Powerful)] #[checks(Powerful::Powerful)]
#[checks("wolf")] #[checks("wolf")]
#[checks(Category::Wolves)]
LoneWolf, LoneWolf,
#[checks(Alignment::Wolves)] #[checks(Alignment::Wolves)]
#[checks(Killer::Killer)] #[checks(Killer::Killer)]
#[checks(Powerful::Powerful)] #[checks(Powerful::Powerful)]
#[checks("wolf")] #[checks("wolf")]
#[checks(Category::Wolves)]
Bloodletter, Bloodletter,
} }