werewolves/werewolves-proto/src/message/night.rs

299 lines
8.9 KiB
Rust
Raw Normal View History

use serde::{Deserialize, Serialize};
use werewolves_macros::{ChecksAs, Titles};
use crate::{
error::GameError,
message::CharacterIdentity,
player::CharacterId,
role::{Alignment, PreviousGuardianAction, RoleTitle},
};
type Result<T> = core::result::Result<T, GameError>;
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, PartialOrd)]
pub enum ActionType {
Cover,
WolvesIntro,
Protect,
WolfPackKill,
Direwolf,
OtherWolf,
Block,
Other,
RoleChange,
}
impl ActionType {
const fn is_wolfy(&self) -> bool {
matches!(
self,
ActionType::Direwolf
| ActionType::OtherWolf
| ActionType::WolfPackKill
| ActionType::WolvesIntro
)
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, ChecksAs, Titles)]
pub enum ActionPrompt {
#[checks(ActionType::Cover)]
CoverOfDarkness,
#[checks(ActionType::WolfPackKill)]
WolvesIntro {
wolves: Box<[(CharacterIdentity, RoleTitle)]>,
},
#[checks(ActionType::RoleChange)]
RoleChange {
character_id: CharacterIdentity,
new_role: RoleTitle,
},
#[checks(ActionType::Other)]
Seer {
character_id: CharacterIdentity,
living_players: Box<[CharacterIdentity]>,
marked: Option<CharacterId>,
},
#[checks(ActionType::Protect)]
Protector {
character_id: CharacterIdentity,
targets: Box<[CharacterIdentity]>,
marked: Option<CharacterId>,
},
#[checks(ActionType::Other)]
Arcanist {
character_id: CharacterIdentity,
living_players: Box<[CharacterIdentity]>,
marked: (Option<CharacterId>, Option<CharacterId>),
},
#[checks(ActionType::Other)]
Gravedigger {
character_id: CharacterIdentity,
dead_players: Box<[CharacterIdentity]>,
marked: Option<CharacterId>,
},
#[checks(ActionType::Other)]
Hunter {
character_id: CharacterIdentity,
current_target: Option<CharacterIdentity>,
living_players: Box<[CharacterIdentity]>,
marked: Option<CharacterId>,
},
#[checks(ActionType::Other)]
Militia {
character_id: CharacterIdentity,
living_players: Box<[CharacterIdentity]>,
marked: Option<CharacterId>,
},
#[checks(ActionType::Other)]
MapleWolf {
character_id: CharacterIdentity,
kill_or_die: bool,
living_players: Box<[CharacterIdentity]>,
marked: Option<CharacterId>,
},
#[checks(ActionType::Protect)]
Guardian {
character_id: CharacterIdentity,
previous: Option<PreviousGuardianAction>,
living_players: Box<[CharacterIdentity]>,
marked: Option<CharacterId>,
},
#[checks(ActionType::WolfPackKill)]
WolfPackKill {
living_villagers: Box<[CharacterIdentity]>,
marked: Option<CharacterId>,
},
#[checks(ActionType::OtherWolf)]
Shapeshifter { character_id: CharacterIdentity },
#[checks(ActionType::OtherWolf)]
AlphaWolf {
character_id: CharacterIdentity,
living_villagers: Box<[CharacterIdentity]>,
marked: Option<CharacterId>,
},
#[checks(ActionType::Direwolf)]
DireWolf {
character_id: CharacterIdentity,
living_players: Box<[CharacterIdentity]>,
marked: Option<CharacterId>,
},
}
impl ActionPrompt {
pub(crate) fn with_mark(&self, mark: CharacterId) -> Result<ActionPrompt> {
let mut prompt = self.clone();
match &mut prompt {
ActionPrompt::WolvesIntro { .. }
| 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 {
*marked = (Some(m.clone()), Some(mark));
}
}
(None, None) => *marked = (Some(mark), None),
(Some(m1), Some(m2)) => {
if *m1 == mark {
*marked = (Some(m2.clone()), None);
} else if *m2 == mark {
*marked = (Some(m1.clone()), None);
} else {
*marked = (Some(m2.clone()), Some(mark));
}
}
}
Ok(prompt)
}
ActionPrompt::Protector {
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)
}
}
}
pub const fn is_wolfy(&self) -> bool {
self.action_type().is_wolfy()
|| match self {
ActionPrompt::RoleChange {
character_id: _,
new_role,
} => new_role.wolf(),
_ => false,
}
}
}
impl PartialOrd for ActionPrompt {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
self.action_type().partial_cmp(&other.action_type())
}
}
#[derive(Debug, Clone, Serialize, PartialEq, Deserialize)]
pub enum ActionResponse {
// Seer(CharacterId),
// Arcanist(Option<CharacterId>, Option<CharacterId>),
// Gravedigger(CharacterId),
// Hunter(CharacterId),
// Militia(Option<CharacterId>),
// MapleWolf(Option<CharacterId>),
// Guardian(CharacterId),
// WolfPackKillVote(CharacterId),
// AlphaWolf(Option<CharacterId>),
// Direwolf(CharacterId),
// Protector(CharacterId),
MarkTarget(CharacterId),
Shapeshift,
Continue,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum ActionResult {
RoleBlocked,
Seer(Alignment),
Arcanist { same: bool },
GraveDigger(Option<RoleTitle>),
GoBackToSleep,
Continue,
}