2025-11-05 20:24:51 +00:00
|
|
|
// 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/>.
|
2025-10-07 17:45:21 +01:00
|
|
|
use core::{num::NonZeroU8, ops::Deref};
|
2025-10-06 20:45:15 +01:00
|
|
|
|
2025-06-23 09:48:28 +01:00
|
|
|
use serde::{Deserialize, Serialize};
|
2025-09-30 13:07:59 +01:00
|
|
|
use werewolves_macros::{ChecksAs, Titles};
|
2025-06-23 09:48:28 +01:00
|
|
|
|
|
|
|
|
use crate::{
|
2025-10-06 20:45:15 +01:00
|
|
|
character::CharacterId,
|
|
|
|
|
diedto::DiedToTitle,
|
2025-10-03 22:47:38 +01:00
|
|
|
error::GameError,
|
2025-09-30 13:07:59 +01:00
|
|
|
message::CharacterIdentity,
|
2025-10-12 23:48:52 +01:00
|
|
|
role::{Alignment, AlignmentEq, Killer, Powerful, PreviousGuardianAction, RoleTitle},
|
2025-06-23 09:48:28 +01:00
|
|
|
};
|
|
|
|
|
|
2025-10-03 22:47:38 +01:00
|
|
|
type Result<T> = core::result::Result<T, GameError>;
|
|
|
|
|
|
2025-11-05 19:45:29 +00:00
|
|
|
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, PartialOrd, ChecksAs)]
|
2025-09-28 02:13:34 +01:00
|
|
|
pub enum ActionType {
|
2025-09-30 13:07:59 +01:00
|
|
|
Cover,
|
2025-11-05 19:45:29 +00:00
|
|
|
#[checks("is_wolfy")]
|
2025-09-30 13:07:59 +01:00
|
|
|
WolvesIntro,
|
2025-11-05 19:45:29 +00:00
|
|
|
RoleChange,
|
2025-09-30 13:07:59 +01:00
|
|
|
Protect,
|
2025-11-05 19:45:29 +00:00
|
|
|
#[checks("is_wolfy")]
|
2025-09-30 13:07:59 +01:00
|
|
|
WolfPackKill,
|
2025-11-05 19:45:29 +00:00
|
|
|
#[checks("is_wolfy")]
|
|
|
|
|
Shapeshifter,
|
|
|
|
|
#[checks("is_wolfy")]
|
|
|
|
|
AlphaWolfKill,
|
|
|
|
|
#[checks("is_wolfy")]
|
2025-09-30 13:07:59 +01:00
|
|
|
OtherWolf,
|
2025-11-05 19:45:29 +00:00
|
|
|
#[checks("is_wolfy")]
|
|
|
|
|
Direwolf,
|
2025-10-07 02:52:06 +01:00
|
|
|
LoneWolfKill,
|
2025-09-30 13:07:59 +01:00
|
|
|
Block,
|
2025-11-05 19:45:29 +00:00
|
|
|
VillageKill,
|
2025-10-06 20:45:15 +01:00
|
|
|
Intel,
|
2025-09-30 13:07:59 +01:00
|
|
|
Other,
|
2025-10-06 20:45:15 +01:00
|
|
|
MasonRecruit,
|
|
|
|
|
MasonsWake,
|
2025-10-07 17:45:21 +01:00
|
|
|
Insomniac,
|
2025-10-06 20:45:15 +01:00
|
|
|
Beholder,
|
2025-09-28 02:13:34 +01:00
|
|
|
}
|
|
|
|
|
|
2025-09-30 13:07:59 +01:00
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, ChecksAs, Titles)]
|
2025-06-23 09:48:28 +01:00
|
|
|
pub enum ActionPrompt {
|
2025-09-30 13:07:59 +01:00
|
|
|
#[checks(ActionType::Cover)]
|
|
|
|
|
CoverOfDarkness,
|
2025-09-28 02:13:34 +01:00
|
|
|
#[checks(ActionType::WolfPackKill)]
|
2025-10-02 17:52:12 +01:00
|
|
|
WolvesIntro {
|
|
|
|
|
wolves: Box<[(CharacterIdentity, RoleTitle)]>,
|
|
|
|
|
},
|
2025-09-28 02:13:34 +01:00
|
|
|
#[checks(ActionType::RoleChange)]
|
2025-09-30 13:07:59 +01:00
|
|
|
RoleChange {
|
|
|
|
|
character_id: CharacterIdentity,
|
|
|
|
|
new_role: RoleTitle,
|
|
|
|
|
},
|
2025-10-05 10:52:37 +01:00
|
|
|
#[checks(ActionType::RoleChange)]
|
|
|
|
|
ElderReveal { character_id: CharacterIdentity },
|
2025-10-06 20:45:15 +01:00
|
|
|
#[checks(ActionType::Intel)]
|
2025-09-30 13:07:59 +01:00
|
|
|
Seer {
|
|
|
|
|
character_id: CharacterIdentity,
|
2025-10-02 17:52:12 +01:00
|
|
|
living_players: Box<[CharacterIdentity]>,
|
2025-10-03 22:47:38 +01:00
|
|
|
marked: Option<CharacterId>,
|
2025-09-30 13:07:59 +01:00
|
|
|
},
|
2025-09-28 02:13:34 +01:00
|
|
|
#[checks(ActionType::Protect)]
|
2025-09-30 13:07:59 +01:00
|
|
|
Protector {
|
|
|
|
|
character_id: CharacterIdentity,
|
2025-10-02 17:52:12 +01:00
|
|
|
targets: Box<[CharacterIdentity]>,
|
2025-10-03 22:47:38 +01:00
|
|
|
marked: Option<CharacterId>,
|
2025-09-30 13:07:59 +01:00
|
|
|
},
|
2025-10-06 20:45:15 +01:00
|
|
|
#[checks(ActionType::Intel)]
|
2025-09-30 13:07:59 +01:00
|
|
|
Arcanist {
|
|
|
|
|
character_id: CharacterIdentity,
|
2025-10-02 17:52:12 +01:00
|
|
|
living_players: Box<[CharacterIdentity]>,
|
2025-10-03 22:47:38 +01:00
|
|
|
marked: (Option<CharacterId>, Option<CharacterId>),
|
2025-09-30 13:07:59 +01:00
|
|
|
},
|
2025-10-06 20:45:15 +01:00
|
|
|
#[checks(ActionType::Intel)]
|
2025-09-30 13:07:59 +01:00
|
|
|
Gravedigger {
|
|
|
|
|
character_id: CharacterIdentity,
|
2025-10-02 17:52:12 +01:00
|
|
|
dead_players: Box<[CharacterIdentity]>,
|
2025-10-03 22:47:38 +01:00
|
|
|
marked: Option<CharacterId>,
|
2025-09-30 13:07:59 +01:00
|
|
|
},
|
2025-09-28 02:13:34 +01:00
|
|
|
#[checks(ActionType::Other)]
|
2025-06-23 09:48:28 +01:00
|
|
|
Hunter {
|
2025-09-30 13:07:59 +01:00
|
|
|
character_id: CharacterIdentity,
|
2025-10-02 17:52:12 +01:00
|
|
|
current_target: Option<CharacterIdentity>,
|
|
|
|
|
living_players: Box<[CharacterIdentity]>,
|
2025-10-03 22:47:38 +01:00
|
|
|
marked: Option<CharacterId>,
|
2025-06-23 09:48:28 +01:00
|
|
|
},
|
2025-11-05 19:45:29 +00:00
|
|
|
#[checks(ActionType::VillageKill)]
|
2025-09-30 13:07:59 +01:00
|
|
|
Militia {
|
|
|
|
|
character_id: CharacterIdentity,
|
2025-10-02 17:52:12 +01:00
|
|
|
living_players: Box<[CharacterIdentity]>,
|
2025-10-03 22:47:38 +01:00
|
|
|
marked: Option<CharacterId>,
|
2025-09-30 13:07:59 +01:00
|
|
|
},
|
2025-09-28 02:13:34 +01:00
|
|
|
#[checks(ActionType::Other)]
|
2025-06-23 09:48:28 +01:00
|
|
|
MapleWolf {
|
2025-09-30 13:07:59 +01:00
|
|
|
character_id: CharacterIdentity,
|
2025-06-23 09:48:28 +01:00
|
|
|
kill_or_die: bool,
|
2025-10-02 17:52:12 +01:00
|
|
|
living_players: Box<[CharacterIdentity]>,
|
2025-10-03 22:47:38 +01:00
|
|
|
marked: Option<CharacterId>,
|
2025-06-23 09:48:28 +01:00
|
|
|
},
|
2025-09-28 02:13:34 +01:00
|
|
|
#[checks(ActionType::Protect)]
|
2025-06-23 09:48:28 +01:00
|
|
|
Guardian {
|
2025-09-30 13:07:59 +01:00
|
|
|
character_id: CharacterIdentity,
|
2025-06-23 09:48:28 +01:00
|
|
|
previous: Option<PreviousGuardianAction>,
|
2025-10-02 17:52:12 +01:00
|
|
|
living_players: Box<[CharacterIdentity]>,
|
2025-10-03 22:47:38 +01:00
|
|
|
marked: Option<CharacterId>,
|
2025-06-23 09:48:28 +01:00
|
|
|
},
|
2025-10-06 20:45:15 +01:00
|
|
|
#[checks(ActionType::Intel)]
|
|
|
|
|
Adjudicator {
|
|
|
|
|
character_id: CharacterIdentity,
|
|
|
|
|
living_players: Box<[CharacterIdentity]>,
|
|
|
|
|
marked: Option<CharacterId>,
|
|
|
|
|
},
|
|
|
|
|
#[checks(ActionType::Intel)]
|
|
|
|
|
PowerSeer {
|
|
|
|
|
character_id: CharacterIdentity,
|
|
|
|
|
living_players: Box<[CharacterIdentity]>,
|
|
|
|
|
marked: Option<CharacterId>,
|
|
|
|
|
},
|
|
|
|
|
#[checks(ActionType::Intel)]
|
|
|
|
|
Mortician {
|
|
|
|
|
character_id: CharacterIdentity,
|
|
|
|
|
dead_players: Box<[CharacterIdentity]>,
|
|
|
|
|
marked: Option<CharacterId>,
|
|
|
|
|
},
|
|
|
|
|
#[checks(ActionType::Beholder)]
|
|
|
|
|
Beholder {
|
|
|
|
|
character_id: CharacterIdentity,
|
|
|
|
|
living_players: Box<[CharacterIdentity]>,
|
|
|
|
|
marked: Option<CharacterId>,
|
|
|
|
|
},
|
|
|
|
|
#[checks(ActionType::MasonsWake)]
|
|
|
|
|
MasonsWake {
|
2025-10-13 23:29:10 +01:00
|
|
|
leader: CharacterIdentity,
|
2025-10-06 20:45:15 +01:00
|
|
|
masons: Box<[CharacterIdentity]>,
|
|
|
|
|
},
|
|
|
|
|
#[checks(ActionType::MasonRecruit)]
|
|
|
|
|
MasonLeaderRecruit {
|
|
|
|
|
character_id: CharacterIdentity,
|
|
|
|
|
recruits_left: NonZeroU8,
|
|
|
|
|
potential_recruits: Box<[CharacterIdentity]>,
|
|
|
|
|
marked: Option<CharacterId>,
|
|
|
|
|
},
|
|
|
|
|
#[checks(ActionType::Intel)]
|
|
|
|
|
Empath {
|
|
|
|
|
character_id: CharacterIdentity,
|
|
|
|
|
living_players: Box<[CharacterIdentity]>,
|
|
|
|
|
marked: Option<CharacterId>,
|
|
|
|
|
},
|
|
|
|
|
#[checks(ActionType::Protect)]
|
|
|
|
|
Vindicator {
|
|
|
|
|
character_id: CharacterIdentity,
|
|
|
|
|
living_players: Box<[CharacterIdentity]>,
|
|
|
|
|
marked: Option<CharacterId>,
|
|
|
|
|
},
|
2025-11-05 19:45:29 +00:00
|
|
|
#[checks(ActionType::VillageKill)]
|
2025-10-06 20:45:15 +01:00
|
|
|
PyreMaster {
|
|
|
|
|
character_id: CharacterIdentity,
|
|
|
|
|
living_players: Box<[CharacterIdentity]>,
|
|
|
|
|
marked: Option<CharacterId>,
|
|
|
|
|
},
|
|
|
|
|
|
2025-09-30 13:07:59 +01:00
|
|
|
#[checks(ActionType::WolfPackKill)]
|
2025-10-02 17:52:12 +01:00
|
|
|
WolfPackKill {
|
|
|
|
|
living_villagers: Box<[CharacterIdentity]>,
|
2025-10-03 22:47:38 +01:00
|
|
|
marked: Option<CharacterId>,
|
2025-10-02 17:52:12 +01:00
|
|
|
},
|
2025-11-05 19:45:29 +00:00
|
|
|
#[checks(ActionType::Shapeshifter)]
|
2025-09-30 13:07:59 +01:00
|
|
|
Shapeshifter { character_id: CharacterIdentity },
|
2025-11-05 19:45:29 +00:00
|
|
|
#[checks(ActionType::AlphaWolfKill)]
|
2025-09-30 13:07:59 +01:00
|
|
|
AlphaWolf {
|
|
|
|
|
character_id: CharacterIdentity,
|
2025-10-02 17:52:12 +01:00
|
|
|
living_villagers: Box<[CharacterIdentity]>,
|
2025-10-03 22:47:38 +01:00
|
|
|
marked: Option<CharacterId>,
|
2025-09-30 13:07:59 +01:00
|
|
|
},
|
2025-09-28 02:13:34 +01:00
|
|
|
#[checks(ActionType::Direwolf)]
|
2025-09-30 13:07:59 +01:00
|
|
|
DireWolf {
|
|
|
|
|
character_id: CharacterIdentity,
|
2025-10-02 17:52:12 +01:00
|
|
|
living_players: Box<[CharacterIdentity]>,
|
2025-10-03 22:47:38 +01:00
|
|
|
marked: Option<CharacterId>,
|
2025-09-30 13:07:59 +01:00
|
|
|
},
|
2025-10-07 02:52:06 +01:00
|
|
|
#[checks(ActionType::LoneWolfKill)]
|
|
|
|
|
LoneWolfKill {
|
|
|
|
|
character_id: CharacterIdentity,
|
|
|
|
|
living_players: Box<[CharacterIdentity]>,
|
|
|
|
|
marked: Option<CharacterId>,
|
|
|
|
|
},
|
2025-10-07 17:45:21 +01:00
|
|
|
#[checks(ActionType::Insomniac)]
|
|
|
|
|
Insomniac { character_id: CharacterIdentity },
|
2025-09-30 13:07:59 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl ActionPrompt {
|
2025-11-05 19:45:29 +00:00
|
|
|
pub(crate) const fn character_id(&self) -> Option<CharacterId> {
|
|
|
|
|
match self {
|
|
|
|
|
ActionPrompt::Insomniac { character_id, .. }
|
|
|
|
|
| ActionPrompt::LoneWolfKill { character_id, .. }
|
|
|
|
|
| ActionPrompt::ElderReveal { character_id }
|
|
|
|
|
| ActionPrompt::RoleChange { character_id, .. }
|
|
|
|
|
| ActionPrompt::Seer { character_id, .. }
|
|
|
|
|
| ActionPrompt::Protector { character_id, .. }
|
|
|
|
|
| ActionPrompt::Arcanist { character_id, .. }
|
|
|
|
|
| ActionPrompt::Gravedigger { character_id, .. }
|
|
|
|
|
| ActionPrompt::Hunter { character_id, .. }
|
|
|
|
|
| ActionPrompt::Militia { character_id, .. }
|
|
|
|
|
| ActionPrompt::MapleWolf { character_id, .. }
|
|
|
|
|
| ActionPrompt::Guardian { character_id, .. }
|
|
|
|
|
| ActionPrompt::Shapeshifter { character_id }
|
|
|
|
|
| ActionPrompt::AlphaWolf { character_id, .. }
|
|
|
|
|
| ActionPrompt::Adjudicator { character_id, .. }
|
|
|
|
|
| ActionPrompt::PowerSeer { character_id, .. }
|
|
|
|
|
| ActionPrompt::Mortician { character_id, .. }
|
|
|
|
|
| ActionPrompt::Beholder { character_id, .. }
|
|
|
|
|
| ActionPrompt::MasonLeaderRecruit { character_id, .. }
|
|
|
|
|
| ActionPrompt::Empath { character_id, .. }
|
|
|
|
|
| ActionPrompt::Vindicator { character_id, .. }
|
|
|
|
|
| ActionPrompt::PyreMaster { character_id, .. }
|
|
|
|
|
| ActionPrompt::DireWolf { character_id, .. } => Some(character_id.character_id),
|
|
|
|
|
|
|
|
|
|
ActionPrompt::WolvesIntro { .. }
|
|
|
|
|
| ActionPrompt::MasonsWake { .. }
|
|
|
|
|
| ActionPrompt::WolfPackKill { .. }
|
|
|
|
|
| ActionPrompt::CoverOfDarkness => None,
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-10-06 20:45:15 +01:00
|
|
|
pub(crate) fn matches_beholding(&self, target: CharacterId) -> bool {
|
|
|
|
|
match self {
|
2025-10-07 17:45:21 +01:00
|
|
|
ActionPrompt::Insomniac { character_id, .. }
|
|
|
|
|
| ActionPrompt::Seer { character_id, .. }
|
2025-10-06 20:45:15 +01:00
|
|
|
| ActionPrompt::Arcanist { character_id, .. }
|
|
|
|
|
| ActionPrompt::Gravedigger { character_id, .. }
|
|
|
|
|
| ActionPrompt::Adjudicator { character_id, .. }
|
|
|
|
|
| ActionPrompt::PowerSeer { character_id, .. }
|
|
|
|
|
| ActionPrompt::Mortician { character_id, .. }
|
|
|
|
|
| ActionPrompt::Vindicator { character_id, .. } => character_id.character_id == target,
|
|
|
|
|
|
|
|
|
|
ActionPrompt::Beholder { .. }
|
|
|
|
|
| ActionPrompt::CoverOfDarkness
|
|
|
|
|
| ActionPrompt::WolvesIntro { .. }
|
|
|
|
|
| ActionPrompt::RoleChange { .. }
|
|
|
|
|
| ActionPrompt::ElderReveal { .. }
|
|
|
|
|
| ActionPrompt::Protector { .. }
|
|
|
|
|
| ActionPrompt::Hunter { .. }
|
|
|
|
|
| ActionPrompt::Militia { .. }
|
|
|
|
|
| ActionPrompt::MapleWolf { .. }
|
|
|
|
|
| ActionPrompt::Guardian { .. }
|
|
|
|
|
| ActionPrompt::PyreMaster { .. }
|
|
|
|
|
| ActionPrompt::Shapeshifter { .. }
|
|
|
|
|
| ActionPrompt::AlphaWolf { .. }
|
|
|
|
|
| ActionPrompt::DireWolf { .. }
|
|
|
|
|
| ActionPrompt::Empath { .. }
|
|
|
|
|
| ActionPrompt::MasonsWake { .. }
|
|
|
|
|
| ActionPrompt::MasonLeaderRecruit { .. }
|
2025-10-07 02:52:06 +01:00
|
|
|
| ActionPrompt::WolfPackKill { .. }
|
|
|
|
|
| ActionPrompt::LoneWolfKill { .. } => false,
|
2025-10-06 20:45:15 +01:00
|
|
|
}
|
|
|
|
|
}
|
2025-10-05 01:22:55 +01:00
|
|
|
pub(crate) fn with_mark(&self, mark: CharacterId) -> Result<ActionPrompt> {
|
2025-10-03 22:47:38 +01:00
|
|
|
let mut prompt = self.clone();
|
|
|
|
|
match &mut prompt {
|
2025-10-07 17:45:21 +01:00
|
|
|
ActionPrompt::Insomniac { .. }
|
|
|
|
|
| ActionPrompt::MasonsWake { .. }
|
2025-10-06 20:45:15 +01:00
|
|
|
| ActionPrompt::ElderReveal { .. }
|
2025-10-05 10:52:37 +01:00
|
|
|
| ActionPrompt::WolvesIntro { .. }
|
2025-10-03 22:47:38 +01:00
|
|
|
| ActionPrompt::RoleChange { .. }
|
|
|
|
|
| ActionPrompt::Shapeshifter { .. }
|
|
|
|
|
| ActionPrompt::CoverOfDarkness => Err(GameError::InvalidMessageForGameState),
|
|
|
|
|
|
|
|
|
|
ActionPrompt::Guardian {
|
|
|
|
|
previous,
|
|
|
|
|
living_players,
|
|
|
|
|
marked,
|
|
|
|
|
..
|
|
|
|
|
} => {
|
|
|
|
|
if !living_players.iter().any(|c| c.character_id == mark)
|
|
|
|
|
|| previous
|
|
|
|
|
.as_ref()
|
|
|
|
|
.and_then(|p| match p {
|
|
|
|
|
PreviousGuardianAction::Protect(_) => None,
|
|
|
|
|
PreviousGuardianAction::Guard(c) => Some(c.character_id == mark),
|
|
|
|
|
})
|
|
|
|
|
.unwrap_or_default()
|
|
|
|
|
{
|
|
|
|
|
// not in target list OR guarded target previous night
|
|
|
|
|
return Err(GameError::InvalidTarget);
|
|
|
|
|
}
|
|
|
|
|
match marked.as_mut() {
|
|
|
|
|
Some(marked_cid) => {
|
|
|
|
|
if marked_cid == &mark {
|
|
|
|
|
marked.take();
|
|
|
|
|
} else {
|
|
|
|
|
marked.replace(mark);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
None => {
|
|
|
|
|
marked.replace(mark);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Ok(prompt)
|
|
|
|
|
}
|
|
|
|
|
ActionPrompt::Arcanist {
|
|
|
|
|
living_players: targets,
|
|
|
|
|
marked,
|
|
|
|
|
..
|
|
|
|
|
} => {
|
|
|
|
|
if !targets.iter().any(|t| t.character_id == mark) {
|
|
|
|
|
return Err(GameError::InvalidTarget);
|
|
|
|
|
}
|
|
|
|
|
match marked {
|
|
|
|
|
(None, Some(m)) | (Some(m), None) => {
|
|
|
|
|
if *m == mark {
|
|
|
|
|
*marked = (None, None);
|
|
|
|
|
} else {
|
2025-10-05 10:54:47 +01:00
|
|
|
*marked = (Some(*m), Some(mark));
|
2025-10-03 22:47:38 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
(None, None) => *marked = (Some(mark), None),
|
|
|
|
|
(Some(m1), Some(m2)) => {
|
|
|
|
|
if *m1 == mark {
|
2025-10-05 10:54:47 +01:00
|
|
|
*marked = (Some(*m2), None);
|
2025-10-03 22:47:38 +01:00
|
|
|
} else if *m2 == mark {
|
2025-10-05 10:54:47 +01:00
|
|
|
*marked = (Some(*m1), None);
|
2025-10-03 22:47:38 +01:00
|
|
|
} else {
|
2025-10-05 10:54:47 +01:00
|
|
|
*marked = (Some(*m2), Some(mark));
|
2025-10-03 22:47:38 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(prompt)
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-07 02:52:06 +01:00
|
|
|
ActionPrompt::LoneWolfKill {
|
|
|
|
|
living_players: targets,
|
|
|
|
|
marked,
|
|
|
|
|
..
|
|
|
|
|
}
|
|
|
|
|
| ActionPrompt::Adjudicator {
|
2025-10-06 20:45:15 +01:00
|
|
|
living_players: targets,
|
|
|
|
|
marked,
|
|
|
|
|
..
|
|
|
|
|
}
|
|
|
|
|
| ActionPrompt::PowerSeer {
|
|
|
|
|
living_players: targets,
|
|
|
|
|
marked,
|
|
|
|
|
..
|
|
|
|
|
}
|
|
|
|
|
| ActionPrompt::Mortician {
|
|
|
|
|
dead_players: targets,
|
|
|
|
|
marked,
|
|
|
|
|
..
|
|
|
|
|
}
|
|
|
|
|
| ActionPrompt::Beholder {
|
|
|
|
|
living_players: targets,
|
|
|
|
|
marked,
|
|
|
|
|
..
|
|
|
|
|
}
|
|
|
|
|
| ActionPrompt::MasonLeaderRecruit {
|
|
|
|
|
potential_recruits: targets,
|
|
|
|
|
marked,
|
|
|
|
|
..
|
|
|
|
|
}
|
|
|
|
|
| ActionPrompt::Empath {
|
|
|
|
|
living_players: targets,
|
|
|
|
|
marked,
|
|
|
|
|
..
|
|
|
|
|
}
|
|
|
|
|
| ActionPrompt::Vindicator {
|
|
|
|
|
living_players: targets,
|
|
|
|
|
marked,
|
|
|
|
|
..
|
|
|
|
|
}
|
|
|
|
|
| ActionPrompt::PyreMaster {
|
|
|
|
|
living_players: targets,
|
|
|
|
|
marked,
|
|
|
|
|
..
|
|
|
|
|
}
|
|
|
|
|
| ActionPrompt::Protector {
|
2025-10-03 22:47:38 +01:00
|
|
|
targets, marked, ..
|
|
|
|
|
}
|
|
|
|
|
| ActionPrompt::Seer {
|
|
|
|
|
living_players: targets,
|
|
|
|
|
marked,
|
|
|
|
|
..
|
|
|
|
|
}
|
|
|
|
|
| ActionPrompt::Gravedigger {
|
|
|
|
|
dead_players: targets,
|
|
|
|
|
marked,
|
|
|
|
|
..
|
|
|
|
|
}
|
|
|
|
|
| ActionPrompt::Hunter {
|
|
|
|
|
living_players: targets,
|
|
|
|
|
marked,
|
|
|
|
|
..
|
|
|
|
|
}
|
|
|
|
|
| ActionPrompt::Militia {
|
|
|
|
|
living_players: targets,
|
|
|
|
|
marked,
|
|
|
|
|
..
|
|
|
|
|
}
|
|
|
|
|
| ActionPrompt::MapleWolf {
|
|
|
|
|
living_players: targets,
|
|
|
|
|
marked,
|
|
|
|
|
..
|
|
|
|
|
}
|
|
|
|
|
| ActionPrompt::WolfPackKill {
|
|
|
|
|
living_villagers: targets,
|
|
|
|
|
marked,
|
|
|
|
|
..
|
|
|
|
|
}
|
|
|
|
|
| ActionPrompt::AlphaWolf {
|
|
|
|
|
living_villagers: targets,
|
|
|
|
|
marked,
|
|
|
|
|
..
|
|
|
|
|
}
|
|
|
|
|
| ActionPrompt::DireWolf {
|
|
|
|
|
living_players: targets,
|
|
|
|
|
marked,
|
|
|
|
|
..
|
|
|
|
|
} => {
|
|
|
|
|
if !targets.iter().any(|t| t.character_id == mark) {
|
|
|
|
|
return Err(GameError::InvalidTarget);
|
|
|
|
|
}
|
|
|
|
|
if let Some(marked_char) = marked.as_ref()
|
|
|
|
|
&& *marked_char == mark
|
|
|
|
|
{
|
|
|
|
|
marked.take();
|
|
|
|
|
} else {
|
|
|
|
|
marked.replace(mark);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Ok(prompt)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-30 13:07:59 +01:00
|
|
|
pub const fn is_wolfy(&self) -> bool {
|
|
|
|
|
self.action_type().is_wolfy()
|
|
|
|
|
|| match self {
|
|
|
|
|
ActionPrompt::RoleChange {
|
|
|
|
|
character_id: _,
|
|
|
|
|
new_role,
|
|
|
|
|
} => new_role.wolf(),
|
|
|
|
|
_ => false,
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-06-23 09:48:28 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl PartialOrd for ActionPrompt {
|
|
|
|
|
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
2025-09-28 02:13:34 +01:00
|
|
|
self.action_type().partial_cmp(&other.action_type())
|
2025-06-23 09:48:28 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-03 22:47:38 +01:00
|
|
|
#[derive(Debug, Clone, Serialize, PartialEq, Deserialize)]
|
2025-06-23 09:48:28 +01:00
|
|
|
pub enum ActionResponse {
|
2025-10-03 22:47:38 +01:00
|
|
|
MarkTarget(CharacterId),
|
|
|
|
|
Shapeshift,
|
|
|
|
|
Continue,
|
2025-06-23 09:48:28 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
|
|
|
pub enum ActionResult {
|
|
|
|
|
RoleBlocked,
|
|
|
|
|
Seer(Alignment),
|
2025-10-12 23:48:52 +01:00
|
|
|
PowerSeer { powerful: Powerful },
|
|
|
|
|
Adjudicator { killer: Killer },
|
|
|
|
|
Arcanist(AlignmentEq),
|
2025-06-23 09:48:28 +01:00
|
|
|
GraveDigger(Option<RoleTitle>),
|
2025-10-06 20:45:15 +01:00
|
|
|
Mortician(DiedToTitle),
|
2025-10-07 17:45:21 +01:00
|
|
|
Insomniac(Visits),
|
2025-10-06 20:45:15 +01:00
|
|
|
Empath { scapegoat: bool },
|
2025-06-23 09:48:28 +01:00
|
|
|
GoBackToSleep,
|
2025-09-30 13:07:59 +01:00
|
|
|
Continue,
|
2025-06-23 09:48:28 +01:00
|
|
|
}
|
2025-10-07 17:45:21 +01:00
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
|
|
|
pub struct Visits(Box<[CharacterIdentity]>);
|
|
|
|
|
|
|
|
|
|
impl Visits {
|
|
|
|
|
pub(crate) const fn new(visits: Box<[CharacterIdentity]>) -> Self {
|
|
|
|
|
Self(visits)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl Deref for Visits {
|
|
|
|
|
type Target = [CharacterIdentity];
|
|
|
|
|
|
|
|
|
|
fn deref(&self) -> &Self::Target {
|
|
|
|
|
&self.0
|
|
|
|
|
}
|
|
|
|
|
}
|