proto changes to accomodate leptos client
This commit is contained in:
parent
78ecb6c164
commit
314e113a46
|
|
@ -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?,
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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());
|
||||||
|
|
|
||||||
|
|
@ -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()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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(),
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue