329 lines
10 KiB
Rust
329 lines
10 KiB
Rust
use core::{fmt::Display, num::NonZeroU8};
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
use crate::{
|
|
diedto::DiedTo,
|
|
error::GameError,
|
|
game::{DateTime, Village},
|
|
message::{CharacterIdentity, Identification, PublicIdentity, night::ActionPrompt},
|
|
modifier::Modifier,
|
|
role::{MAPLE_WOLF_ABSTAIN_LIMIT, PreviousGuardianAction, Role, RoleTitle},
|
|
};
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
|
|
pub struct PlayerId(uuid::Uuid);
|
|
|
|
impl PlayerId {
|
|
pub fn new() -> Self {
|
|
Self(uuid::Uuid::new_v4())
|
|
}
|
|
pub const fn from_u128(v: u128) -> Self {
|
|
Self(uuid::Uuid::from_u128(v))
|
|
}
|
|
}
|
|
|
|
impl Display for PlayerId {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
self.0.fmt(f)
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
|
|
pub struct CharacterId(uuid::Uuid);
|
|
|
|
impl CharacterId {
|
|
pub fn new() -> Self {
|
|
Self(uuid::Uuid::new_v4())
|
|
}
|
|
pub const fn from_u128(v: u128) -> Self {
|
|
Self(uuid::Uuid::from_u128(v))
|
|
}
|
|
}
|
|
|
|
impl Display for CharacterId {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
self.0.fmt(f)
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct Player {
|
|
id: PlayerId,
|
|
name: String,
|
|
}
|
|
|
|
impl Player {
|
|
pub fn new(name: String) -> Self {
|
|
Self {
|
|
id: PlayerId::new(),
|
|
name,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
|
|
pub enum Protection {
|
|
Guardian { source: CharacterId, guarding: bool },
|
|
Protector { source: CharacterId },
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
|
|
pub enum KillOutcome {
|
|
Killed,
|
|
Failed,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct Character {
|
|
player_id: PlayerId,
|
|
identity: CharacterIdentity,
|
|
role: Role,
|
|
modifier: Option<Modifier>,
|
|
died_to: Option<DiedTo>,
|
|
role_changes: Vec<RoleChange>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
pub struct RoleChange {
|
|
role: Role,
|
|
new_role: RoleTitle,
|
|
changed_on_night: u8,
|
|
}
|
|
|
|
impl Character {
|
|
pub fn new(
|
|
Identification {
|
|
player_id,
|
|
public:
|
|
PublicIdentity {
|
|
name,
|
|
pronouns,
|
|
number,
|
|
},
|
|
}: Identification,
|
|
role: Role,
|
|
) -> Option<Self> {
|
|
Some(Self {
|
|
role,
|
|
identity: CharacterIdentity {
|
|
character_id: CharacterId::new(),
|
|
name,
|
|
pronouns,
|
|
number: number?,
|
|
},
|
|
player_id,
|
|
modifier: None,
|
|
died_to: None,
|
|
role_changes: Vec::new(),
|
|
})
|
|
}
|
|
|
|
pub fn identity(&self) -> CharacterIdentity {
|
|
self.identity.clone()
|
|
}
|
|
|
|
pub fn name(&self) -> &str {
|
|
self.identity.name.as_str()
|
|
}
|
|
|
|
pub const fn number(&self) -> NonZeroU8 {
|
|
self.identity.number
|
|
}
|
|
|
|
pub const fn pronouns(&self) -> Option<&str> {
|
|
match self.identity.pronouns.as_ref() {
|
|
Some(p) => Some(p.as_str()),
|
|
None => None,
|
|
}
|
|
}
|
|
|
|
pub fn died_to(&self) -> Option<&DiedTo> {
|
|
self.died_to.as_ref()
|
|
}
|
|
|
|
pub fn kill(&mut self, died_to: DiedTo) {
|
|
match &self.died_to {
|
|
Some(_) => {}
|
|
None => self.died_to = Some(died_to),
|
|
}
|
|
}
|
|
|
|
pub const fn alive(&self) -> bool {
|
|
self.died_to.is_none()
|
|
}
|
|
|
|
pub fn execute(&mut self, day: NonZeroU8) -> Result<(), GameError> {
|
|
if self.died_to.is_some() {
|
|
return Err(GameError::CharacterAlreadyDead);
|
|
}
|
|
self.died_to = Some(DiedTo::Execution { day });
|
|
Ok(())
|
|
}
|
|
|
|
pub const fn character_id(&self) -> &CharacterId {
|
|
&self.identity.character_id
|
|
}
|
|
|
|
pub const fn player_id(&self) -> &PlayerId {
|
|
&self.player_id
|
|
}
|
|
|
|
pub const fn role(&self) -> &Role {
|
|
&self.role
|
|
}
|
|
|
|
pub const fn role_mut(&mut self) -> &mut Role {
|
|
&mut self.role
|
|
}
|
|
|
|
pub fn role_change(&mut self, new_role: RoleTitle, at: DateTime) -> Result<(), GameError> {
|
|
let mut role = new_role.title_to_role_excl_apprentice();
|
|
core::mem::swap(&mut role, &mut self.role);
|
|
self.role_changes.push(RoleChange {
|
|
role,
|
|
new_role,
|
|
changed_on_night: match at {
|
|
DateTime::Day { number: _ } => return Err(GameError::NotNight),
|
|
DateTime::Night { number } => number,
|
|
},
|
|
});
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub const fn is_wolf(&self) -> bool {
|
|
self.role.wolf()
|
|
}
|
|
|
|
pub const fn is_village(&self) -> bool {
|
|
!self.is_wolf()
|
|
}
|
|
|
|
pub fn night_action_prompt(
|
|
&self,
|
|
village: &Village,
|
|
) -> Result<Option<ActionPrompt>, GameError> {
|
|
if !self.alive() || !self.role.wakes(village) {
|
|
return Ok(None);
|
|
}
|
|
let night = match village.date_time() {
|
|
DateTime::Day { number: _ } => return Err(GameError::NotNight),
|
|
DateTime::Night { number } => number,
|
|
};
|
|
Ok(Some(match &self.role {
|
|
Role::Shapeshifter {
|
|
shifted_into: Some(_),
|
|
}
|
|
| Role::AlphaWolf { killed: Some(_) }
|
|
| Role::Militia { targeted: Some(_) }
|
|
| Role::Scapegoat
|
|
| Role::Villager => return Ok(None),
|
|
Role::Seer => ActionPrompt::Seer {
|
|
character_id: self.identity(),
|
|
living_players: village.living_players_excluding(self.character_id()),
|
|
},
|
|
Role::Arcanist => ActionPrompt::Arcanist {
|
|
character_id: self.identity(),
|
|
living_players: village.living_players_excluding(self.character_id()),
|
|
},
|
|
Role::Protector {
|
|
last_protected: Some(last_protected),
|
|
} => ActionPrompt::Protector {
|
|
character_id: self.identity(),
|
|
targets: village.living_players_excluding(last_protected),
|
|
},
|
|
Role::Protector {
|
|
last_protected: None,
|
|
} => ActionPrompt::Protector {
|
|
character_id: self.identity(),
|
|
targets: village.living_players_excluding(self.character_id()),
|
|
},
|
|
Role::Apprentice(role) => {
|
|
let current_night = match village.date_time() {
|
|
DateTime::Day { number: _ } => return Ok(None),
|
|
DateTime::Night { number } => number,
|
|
};
|
|
return Ok(village
|
|
.characters()
|
|
.into_iter()
|
|
.filter(|c| c.role().title() == role.title())
|
|
.filter_map(|char| char.died_to)
|
|
.any(|died_to| match died_to.date_time() {
|
|
DateTime::Day { number } => number.get() + 1 >= current_night,
|
|
DateTime::Night { number } => number + 1 >= current_night,
|
|
})
|
|
.then(|| ActionPrompt::RoleChange {
|
|
character_id: self.identity(),
|
|
new_role: role.title(),
|
|
}));
|
|
}
|
|
Role::Elder { knows_on_night } => {
|
|
let current_night = match village.date_time() {
|
|
DateTime::Day { number: _ } => return Ok(None),
|
|
DateTime::Night { number } => number,
|
|
};
|
|
return Ok((current_night == knows_on_night.get()).then_some({
|
|
ActionPrompt::RoleChange {
|
|
character_id: self.identity(),
|
|
new_role: RoleTitle::Elder,
|
|
}
|
|
}));
|
|
}
|
|
Role::Militia { targeted: None } => ActionPrompt::Militia {
|
|
character_id: self.identity(),
|
|
living_players: village.living_players_excluding(self.character_id()),
|
|
},
|
|
Role::Werewolf => ActionPrompt::WolfPackKill {
|
|
living_villagers: village.living_players(),
|
|
},
|
|
Role::AlphaWolf { killed: None } => ActionPrompt::AlphaWolf {
|
|
character_id: self.identity(),
|
|
living_villagers: village.living_players_excluding(self.character_id()),
|
|
},
|
|
Role::DireWolf => ActionPrompt::DireWolf {
|
|
character_id: self.identity(),
|
|
living_players: village.living_players(),
|
|
},
|
|
Role::Shapeshifter { shifted_into: None } => ActionPrompt::Shapeshifter {
|
|
character_id: self.identity(),
|
|
},
|
|
Role::Gravedigger => ActionPrompt::Gravedigger {
|
|
character_id: self.identity(),
|
|
dead_players: village.dead_targets(),
|
|
},
|
|
Role::Hunter { target } => ActionPrompt::Hunter {
|
|
character_id: self.identity(),
|
|
current_target: target.as_ref().and_then(|t| village.target_by_id(t)),
|
|
living_players: village.living_players_excluding(self.character_id()),
|
|
},
|
|
Role::MapleWolf { last_kill_on_night } => ActionPrompt::MapleWolf {
|
|
character_id: self.identity(),
|
|
kill_or_die: last_kill_on_night + MAPLE_WOLF_ABSTAIN_LIMIT.get() == night,
|
|
living_players: village.living_players_excluding(self.character_id()),
|
|
},
|
|
Role::Guardian {
|
|
last_protected: Some(PreviousGuardianAction::Guard(prev_target)),
|
|
} => ActionPrompt::Guardian {
|
|
character_id: self.identity(),
|
|
previous: Some(PreviousGuardianAction::Guard(prev_target.clone())),
|
|
living_players: village.living_players_excluding(&prev_target.character_id),
|
|
},
|
|
Role::Guardian {
|
|
last_protected: Some(PreviousGuardianAction::Protect(prev_target)),
|
|
} => ActionPrompt::Guardian {
|
|
character_id: self.identity(),
|
|
previous: Some(PreviousGuardianAction::Protect(prev_target.clone())),
|
|
living_players: village.living_players(),
|
|
},
|
|
Role::Guardian {
|
|
last_protected: None,
|
|
} => ActionPrompt::Guardian {
|
|
character_id: self.identity(),
|
|
previous: None,
|
|
living_players: village.living_players(),
|
|
},
|
|
}))
|
|
}
|
|
}
|