bloodletter: wip
This commit is contained in:
parent
ad29c3d59c
commit
bdfd4034b9
|
|
@ -0,0 +1,53 @@
|
||||||
|
// 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/>.
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::game::{GameTime, Village};
|
||||||
|
const BLOODLET_DURATION_DAYS: u8 = 2;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
|
||||||
|
pub enum Aura {
|
||||||
|
Drunk,
|
||||||
|
Insane,
|
||||||
|
Bloodlet { night: u8 },
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Aura {
|
||||||
|
pub const fn expired(&self, village: &Village) -> bool {
|
||||||
|
match self {
|
||||||
|
Aura::Drunk | Aura::Insane => false,
|
||||||
|
Aura::Bloodlet { night } => match village.time() {
|
||||||
|
GameTime::Day { .. } => false,
|
||||||
|
GameTime::Night { number } => {
|
||||||
|
night.saturating_add(BLOODLET_DURATION_DAYS) >= number
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn refreshes(&self, other: &Aura) -> bool {
|
||||||
|
match (self, other) {
|
||||||
|
(Aura::Bloodlet { .. }, Aura::Bloodlet { .. }) => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn refresh(&mut self, other: Aura) {
|
||||||
|
match (self, other) {
|
||||||
|
(Aura::Bloodlet { night }, Aura::Bloodlet { night: new_night }) => *night = new_night,
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -22,11 +22,11 @@ use rand::seq::SliceRandom;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
aura::Aura,
|
||||||
diedto::DiedTo,
|
diedto::DiedTo,
|
||||||
error::GameError,
|
error::GameError,
|
||||||
game::{GameTime, Village},
|
game::{GameTime, Village},
|
||||||
message::{CharacterIdentity, Identification, PublicIdentity, night::ActionPrompt},
|
message::{CharacterIdentity, Identification, PublicIdentity, night::ActionPrompt},
|
||||||
modifier::Modifier,
|
|
||||||
player::{PlayerId, RoleChange},
|
player::{PlayerId, RoleChange},
|
||||||
role::{
|
role::{
|
||||||
Alignment, Killer, KillingWolfOrder, MAPLE_WOLF_ABSTAIN_LIMIT, Powerful,
|
Alignment, Killer, KillingWolfOrder, MAPLE_WOLF_ABSTAIN_LIMIT, Powerful,
|
||||||
|
|
@ -59,7 +59,7 @@ pub struct Character {
|
||||||
player_id: PlayerId,
|
player_id: PlayerId,
|
||||||
identity: CharacterIdentity,
|
identity: CharacterIdentity,
|
||||||
role: Role,
|
role: Role,
|
||||||
modifier: Option<Modifier>,
|
auras: Vec<Aura>,
|
||||||
died_to: Option<DiedTo>,
|
died_to: Option<DiedTo>,
|
||||||
role_changes: Vec<RoleChange>,
|
role_changes: Vec<RoleChange>,
|
||||||
}
|
}
|
||||||
|
|
@ -76,19 +76,20 @@ impl Character {
|
||||||
},
|
},
|
||||||
}: Identification,
|
}: Identification,
|
||||||
role: Role,
|
role: Role,
|
||||||
|
auras: Vec<Aura>,
|
||||||
) -> Option<Self> {
|
) -> Option<Self> {
|
||||||
Some(Self {
|
Some(Self {
|
||||||
role,
|
role,
|
||||||
|
auras,
|
||||||
|
player_id,
|
||||||
|
died_to: None,
|
||||||
|
role_changes: Vec::new(),
|
||||||
identity: CharacterIdentity {
|
identity: CharacterIdentity {
|
||||||
character_id: CharacterId::new(),
|
character_id: CharacterId::new(),
|
||||||
name,
|
name,
|
||||||
pronouns,
|
pronouns,
|
||||||
number: number?,
|
number: number?,
|
||||||
},
|
},
|
||||||
player_id,
|
|
||||||
modifier: None,
|
|
||||||
died_to: None,
|
|
||||||
role_changes: Vec::new(),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -298,6 +299,14 @@ impl Character {
|
||||||
AsCharacter(char)
|
AsCharacter(char)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn apply_aura(&mut self, aura: Aura) {
|
||||||
|
if let Some(existing) = self.auras.iter_mut().find(|aura| aura.refreshes(&aura)) {
|
||||||
|
existing.refresh(aura);
|
||||||
|
} else {
|
||||||
|
self.auras.push(aura);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn night_action_prompts(&self, village: &Village) -> Result<Box<[ActionPrompt]>> {
|
pub fn night_action_prompts(&self, village: &Village) -> Result<Box<[ActionPrompt]>> {
|
||||||
if self.mason_leader().is_ok() {
|
if self.mason_leader().is_ok() {
|
||||||
return self.mason_prompts(village);
|
return self.mason_prompts(village);
|
||||||
|
|
@ -345,6 +354,11 @@ impl Character {
|
||||||
return Ok(Box::new([]));
|
return Ok(Box::new([]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Role::Bloodletter => ActionPrompt::Bloodletter {
|
||||||
|
character_id: self.identity(),
|
||||||
|
living_players: village.living_villagers(),
|
||||||
|
marked: None,
|
||||||
|
},
|
||||||
Role::Seer => ActionPrompt::Seer {
|
Role::Seer => ActionPrompt::Seer {
|
||||||
character_id: self.identity(),
|
character_id: self.identity(),
|
||||||
living_players: village.living_players_excluding(self.character_id()),
|
living_players: village.living_players_excluding(self.character_id()),
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,11 @@ impl ActionPrompt {
|
||||||
..
|
..
|
||||||
} => Some(Unless::TargetsBlocked(*marked1, *marked2)),
|
} => Some(Unless::TargetsBlocked(*marked1, *marked2)),
|
||||||
|
|
||||||
ActionPrompt::LoneWolfKill {
|
ActionPrompt::Bloodletter {
|
||||||
|
marked: Some(marked),
|
||||||
|
..
|
||||||
|
}
|
||||||
|
| ActionPrompt::LoneWolfKill {
|
||||||
marked: Some(marked),
|
marked: Some(marked),
|
||||||
..
|
..
|
||||||
}
|
}
|
||||||
|
|
@ -148,7 +152,8 @@ impl ActionPrompt {
|
||||||
..
|
..
|
||||||
} => Some(Unless::TargetBlocked(*marked)),
|
} => Some(Unless::TargetBlocked(*marked)),
|
||||||
|
|
||||||
ActionPrompt::LoneWolfKill { marked: None, .. }
|
ActionPrompt::Bloodletter { .. }
|
||||||
|
| ActionPrompt::LoneWolfKill { marked: None, .. }
|
||||||
| ActionPrompt::Seer { marked: None, .. }
|
| ActionPrompt::Seer { marked: None, .. }
|
||||||
| ActionPrompt::Protector { marked: None, .. }
|
| ActionPrompt::Protector { marked: None, .. }
|
||||||
| ActionPrompt::Gravedigger { marked: None, .. }
|
| ActionPrompt::Gravedigger { marked: None, .. }
|
||||||
|
|
@ -1083,6 +1088,12 @@ impl Night {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// returns the matching [Character] with the current night's aura changes
|
||||||
|
/// applied
|
||||||
|
fn character_with_current_auras(&self, id: CharacterId) -> Result<Character> {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
|
||||||
fn changes_from_actions(&self) -> Box<[NightChange]> {
|
fn changes_from_actions(&self) -> Box<[NightChange]> {
|
||||||
self.used_actions
|
self.used_actions
|
||||||
.iter()
|
.iter()
|
||||||
|
|
@ -1109,7 +1120,12 @@ impl Night {
|
||||||
.then(|| self.village.killing_wolf().map(|c| c.identity()))
|
.then(|| self.village.killing_wolf().map(|c| c.identity()))
|
||||||
.flatten(),
|
.flatten(),
|
||||||
|
|
||||||
ActionPrompt::Seer {
|
ActionPrompt::Bloodletter {
|
||||||
|
character_id,
|
||||||
|
marked: Some(marked),
|
||||||
|
..
|
||||||
|
}
|
||||||
|
| ActionPrompt::Seer {
|
||||||
character_id,
|
character_id,
|
||||||
marked: Some(marked),
|
marked: Some(marked),
|
||||||
..
|
..
|
||||||
|
|
@ -1200,7 +1216,8 @@ impl Night {
|
||||||
..
|
..
|
||||||
} => (*marked == visit_char).then(|| character_id.clone()),
|
} => (*marked == visit_char).then(|| character_id.clone()),
|
||||||
|
|
||||||
ActionPrompt::WolfPackKill { marked: None, .. }
|
ActionPrompt::Bloodletter { .. }
|
||||||
|
| ActionPrompt::WolfPackKill { marked: None, .. }
|
||||||
| ActionPrompt::Arcanist { marked: _, .. }
|
| ActionPrompt::Arcanist { marked: _, .. }
|
||||||
| ActionPrompt::LoneWolfKill { marked: None, .. }
|
| ActionPrompt::LoneWolfKill { marked: None, .. }
|
||||||
| ActionPrompt::Seer { marked: None, .. }
|
| ActionPrompt::Seer { marked: None, .. }
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ use serde::{Deserialize, Serialize};
|
||||||
use werewolves_macros::Extract;
|
use werewolves_macros::Extract;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
aura::Aura,
|
||||||
character::CharacterId,
|
character::CharacterId,
|
||||||
diedto::DiedTo,
|
diedto::DiedTo,
|
||||||
player::Protection,
|
player::Protection,
|
||||||
|
|
@ -59,6 +60,11 @@ pub enum NightChange {
|
||||||
empath: CharacterId,
|
empath: CharacterId,
|
||||||
scapegoat: CharacterId,
|
scapegoat: CharacterId,
|
||||||
},
|
},
|
||||||
|
ApplyAura {
|
||||||
|
source: CharacterId,
|
||||||
|
target: CharacterId,
|
||||||
|
aura: Aura,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct ChangesLookup<'a>(&'a [NightChange], Vec<usize>);
|
pub struct ChangesLookup<'a>(&'a [NightChange], Vec<usize>);
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@
|
||||||
use core::num::NonZeroU8;
|
use core::num::NonZeroU8;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
aura::Aura,
|
||||||
diedto::DiedTo,
|
diedto::DiedTo,
|
||||||
error::GameError,
|
error::GameError,
|
||||||
game::night::{
|
game::night::{
|
||||||
|
|
@ -108,6 +109,19 @@ impl Night {
|
||||||
};
|
};
|
||||||
|
|
||||||
match current_prompt {
|
match current_prompt {
|
||||||
|
ActionPrompt::Bloodletter {
|
||||||
|
character_id,
|
||||||
|
living_players,
|
||||||
|
marked: Some(marked),
|
||||||
|
} => Ok(ActionComplete {
|
||||||
|
result: ActionResult::GoBackToSleep,
|
||||||
|
change: Some(NightChange::ApplyAura {
|
||||||
|
source: character_id.character_id,
|
||||||
|
aura: Aura::Bloodlet { night: self.night },
|
||||||
|
target: *marked,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
.into()),
|
||||||
ActionPrompt::LoneWolfKill {
|
ActionPrompt::LoneWolfKill {
|
||||||
character_id,
|
character_id,
|
||||||
marked: Some(marked),
|
marked: Some(marked),
|
||||||
|
|
@ -143,7 +157,7 @@ impl Night {
|
||||||
marked: Some(marked),
|
marked: Some(marked),
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
let alignment = self.village.character_by_id(*marked)?.alignment();
|
let alignment = self.character_with_current_auras(*marked)?.alignment();
|
||||||
Ok(ResponseOutcome::ActionComplete(ActionComplete {
|
Ok(ResponseOutcome::ActionComplete(ActionComplete {
|
||||||
result: ActionResult::Seer(alignment),
|
result: ActionResult::Seer(alignment),
|
||||||
change: None,
|
change: None,
|
||||||
|
|
@ -166,8 +180,8 @@ impl Night {
|
||||||
marked: (Some(marked1), Some(marked2)),
|
marked: (Some(marked1), Some(marked2)),
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
let same = self.village.character_by_id(*marked1)?.alignment()
|
let same = self.character_with_current_auras(*marked1)?.alignment()
|
||||||
== self.village.character_by_id(*marked2)?.alignment();
|
== self.character_with_current_auras(*marked2)?.alignment();
|
||||||
|
|
||||||
Ok(ResponseOutcome::ActionComplete(ActionComplete {
|
Ok(ResponseOutcome::ActionComplete(ActionComplete {
|
||||||
result: ActionResult::Arcanist(AlignmentEq::new(same)),
|
result: ActionResult::Arcanist(AlignmentEq::new(same)),
|
||||||
|
|
@ -178,7 +192,9 @@ impl Night {
|
||||||
marked: Some(marked),
|
marked: Some(marked),
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
let dig_role = self.village.character_by_id(*marked)?.gravedigger_dig();
|
let dig_role = self
|
||||||
|
.character_with_current_auras(*marked)?
|
||||||
|
.gravedigger_dig();
|
||||||
Ok(ResponseOutcome::ActionComplete(ActionComplete {
|
Ok(ResponseOutcome::ActionComplete(ActionComplete {
|
||||||
result: ActionResult::GraveDigger(dig_role),
|
result: ActionResult::GraveDigger(dig_role),
|
||||||
change: None,
|
change: None,
|
||||||
|
|
@ -359,7 +375,7 @@ impl Night {
|
||||||
..
|
..
|
||||||
} => Ok(ActionComplete {
|
} => Ok(ActionComplete {
|
||||||
result: ActionResult::Adjudicator {
|
result: ActionResult::Adjudicator {
|
||||||
killer: self.village.character_by_id(*marked)?.killer(),
|
killer: self.character_with_current_auras(*marked)?.killer(),
|
||||||
},
|
},
|
||||||
change: None,
|
change: None,
|
||||||
}
|
}
|
||||||
|
|
@ -369,7 +385,7 @@ impl Night {
|
||||||
..
|
..
|
||||||
} => Ok(ActionComplete {
|
} => Ok(ActionComplete {
|
||||||
result: ActionResult::PowerSeer {
|
result: ActionResult::PowerSeer {
|
||||||
powerful: self.village.character_by_id(*marked)?.powerful(),
|
powerful: self.character_with_current_auras(*marked)?.powerful(),
|
||||||
},
|
},
|
||||||
change: None,
|
change: None,
|
||||||
}
|
}
|
||||||
|
|
@ -489,7 +505,8 @@ impl Night {
|
||||||
}
|
}
|
||||||
.into()),
|
.into()),
|
||||||
|
|
||||||
ActionPrompt::Adjudicator { marked: None, .. }
|
ActionPrompt::Bloodletter { marked: None, .. }
|
||||||
|
| ActionPrompt::Adjudicator { marked: None, .. }
|
||||||
| ActionPrompt::PowerSeer { marked: None, .. }
|
| ActionPrompt::PowerSeer { marked: None, .. }
|
||||||
| ActionPrompt::Mortician { marked: None, .. }
|
| ActionPrompt::Mortician { marked: None, .. }
|
||||||
| ActionPrompt::Beholder { marked: None, .. }
|
| ActionPrompt::Beholder { marked: None, .. }
|
||||||
|
|
|
||||||
|
|
@ -23,10 +23,10 @@ use uuid::Uuid;
|
||||||
use werewolves_macros::{All, ChecksAs, Titles};
|
use werewolves_macros::{All, ChecksAs, Titles};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
aura::Aura,
|
||||||
character::Character,
|
character::Character,
|
||||||
error::GameError,
|
error::GameError,
|
||||||
message::Identification,
|
message::Identification,
|
||||||
modifier::Modifier,
|
|
||||||
player::PlayerId,
|
player::PlayerId,
|
||||||
role::{Role, RoleTitle},
|
role::{Role, RoleTitle},
|
||||||
};
|
};
|
||||||
|
|
@ -127,6 +127,8 @@ pub enum SetupRole {
|
||||||
Shapeshifter,
|
Shapeshifter,
|
||||||
#[checks(Category::Wolves)]
|
#[checks(Category::Wolves)]
|
||||||
LoneWolf,
|
LoneWolf,
|
||||||
|
#[checks(Category::Wolves)]
|
||||||
|
Bloodletter,
|
||||||
|
|
||||||
#[checks(Category::Intel)]
|
#[checks(Category::Intel)]
|
||||||
Adjudicator,
|
Adjudicator,
|
||||||
|
|
@ -157,6 +159,7 @@ pub enum SetupRole {
|
||||||
impl SetupRoleTitle {
|
impl SetupRoleTitle {
|
||||||
pub fn into_role(self) -> Role {
|
pub fn into_role(self) -> Role {
|
||||||
match self {
|
match self {
|
||||||
|
SetupRoleTitle::Bloodletter => Role::Bloodletter,
|
||||||
SetupRoleTitle::Insomniac => Role::Insomniac,
|
SetupRoleTitle::Insomniac => Role::Insomniac,
|
||||||
SetupRoleTitle::LoneWolf => Role::LoneWolf,
|
SetupRoleTitle::LoneWolf => Role::LoneWolf,
|
||||||
SetupRoleTitle::Villager => Role::Villager,
|
SetupRoleTitle::Villager => Role::Villager,
|
||||||
|
|
@ -208,6 +211,7 @@ impl SetupRoleTitle {
|
||||||
impl Display for SetupRole {
|
impl Display for SetupRole {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
f.write_str(match self {
|
f.write_str(match self {
|
||||||
|
SetupRole::Bloodletter => "Bloodletter",
|
||||||
SetupRole::Insomniac => "Insomniac",
|
SetupRole::Insomniac => "Insomniac",
|
||||||
SetupRole::LoneWolf => "Lone Wolf",
|
SetupRole::LoneWolf => "Lone Wolf",
|
||||||
SetupRole::Villager => "Villager",
|
SetupRole::Villager => "Villager",
|
||||||
|
|
@ -244,6 +248,7 @@ impl Display for SetupRole {
|
||||||
impl SetupRole {
|
impl SetupRole {
|
||||||
pub fn into_role(self, roles_in_game: &[RoleTitle]) -> Result<Role, GameError> {
|
pub fn into_role(self, roles_in_game: &[RoleTitle]) -> Result<Role, GameError> {
|
||||||
Ok(match self {
|
Ok(match self {
|
||||||
|
Self::Bloodletter => Role::Bloodletter,
|
||||||
SetupRole::Insomniac => Role::Insomniac,
|
SetupRole::Insomniac => Role::Insomniac,
|
||||||
SetupRole::LoneWolf => Role::LoneWolf,
|
SetupRole::LoneWolf => Role::LoneWolf,
|
||||||
SetupRole::Villager => Role::Villager,
|
SetupRole::Villager => Role::Villager,
|
||||||
|
|
@ -321,6 +326,7 @@ impl From<SetupRole> for RoleTitle {
|
||||||
impl From<RoleTitle> for SetupRole {
|
impl From<RoleTitle> for SetupRole {
|
||||||
fn from(value: RoleTitle) -> Self {
|
fn from(value: RoleTitle) -> Self {
|
||||||
match value {
|
match value {
|
||||||
|
RoleTitle::Bloodletter => SetupRole::Bloodletter,
|
||||||
RoleTitle::Insomniac => SetupRole::Insomniac,
|
RoleTitle::Insomniac => SetupRole::Insomniac,
|
||||||
RoleTitle::LoneWolf => SetupRole::LoneWolf,
|
RoleTitle::LoneWolf => SetupRole::LoneWolf,
|
||||||
RoleTitle::Villager => SetupRole::Villager,
|
RoleTitle::Villager => SetupRole::Villager,
|
||||||
|
|
@ -373,7 +379,7 @@ impl SlotId {
|
||||||
pub struct SetupSlot {
|
pub struct SetupSlot {
|
||||||
pub slot_id: SlotId,
|
pub slot_id: SlotId,
|
||||||
pub role: SetupRole,
|
pub role: SetupRole,
|
||||||
pub modifiers: Vec<Modifier>,
|
pub auras: Vec<Aura>,
|
||||||
pub assign_to: Option<PlayerId>,
|
pub assign_to: Option<PlayerId>,
|
||||||
pub created_order: u32,
|
pub created_order: u32,
|
||||||
}
|
}
|
||||||
|
|
@ -384,7 +390,7 @@ impl SetupSlot {
|
||||||
created_order,
|
created_order,
|
||||||
assign_to: None,
|
assign_to: None,
|
||||||
role: title.into(),
|
role: title.into(),
|
||||||
modifiers: Vec::new(),
|
auras: Vec::new(),
|
||||||
slot_id: SlotId::new(),
|
slot_id: SlotId::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -394,7 +400,11 @@ impl SetupSlot {
|
||||||
ident: Identification,
|
ident: Identification,
|
||||||
roles_in_game: &[RoleTitle],
|
roles_in_game: &[RoleTitle],
|
||||||
) -> Result<Character, GameError> {
|
) -> Result<Character, GameError> {
|
||||||
Character::new(ident.clone(), self.role.into_role(roles_in_game)?)
|
Character::new(
|
||||||
|
ident.clone(),
|
||||||
|
self.role.into_role(roles_in_game)?,
|
||||||
|
self.auras,
|
||||||
|
)
|
||||||
.ok_or(GameError::PlayerNotAssignedNumber(ident.to_string()))
|
.ok_or(GameError::PlayerNotAssignedNumber(ident.to_string()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -199,11 +199,23 @@ pub enum StoryActionPrompt {
|
||||||
Insomniac {
|
Insomniac {
|
||||||
character_id: CharacterId,
|
character_id: CharacterId,
|
||||||
},
|
},
|
||||||
|
BloodLetter {
|
||||||
|
character_id: CharacterId,
|
||||||
|
chosen: CharacterId,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StoryActionPrompt {
|
impl StoryActionPrompt {
|
||||||
pub fn new(prompt: ActionPrompt) -> Option<Self> {
|
pub fn new(prompt: ActionPrompt) -> Option<Self> {
|
||||||
Some(match prompt {
|
Some(match prompt {
|
||||||
|
ActionPrompt::Bloodletter {
|
||||||
|
character_id,
|
||||||
|
marked: Some(marked),
|
||||||
|
..
|
||||||
|
} => Self::BloodLetter {
|
||||||
|
character_id: character_id.character_id,
|
||||||
|
chosen: marked,
|
||||||
|
},
|
||||||
ActionPrompt::Seer {
|
ActionPrompt::Seer {
|
||||||
character_id,
|
character_id,
|
||||||
marked: Some(marked),
|
marked: Some(marked),
|
||||||
|
|
@ -378,7 +390,8 @@ impl StoryActionPrompt {
|
||||||
character_id: character_id.character_id,
|
character_id: character_id.character_id,
|
||||||
},
|
},
|
||||||
|
|
||||||
ActionPrompt::Protector { .. }
|
ActionPrompt::Bloodletter { .. }
|
||||||
|
| ActionPrompt::Protector { .. }
|
||||||
| ActionPrompt::Gravedigger { .. }
|
| ActionPrompt::Gravedigger { .. }
|
||||||
| ActionPrompt::Hunter { .. }
|
| ActionPrompt::Hunter { .. }
|
||||||
| ActionPrompt::Militia { .. }
|
| ActionPrompt::Militia { .. }
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use super::Result;
|
use super::Result;
|
||||||
use crate::{
|
use crate::{
|
||||||
|
aura::Aura,
|
||||||
character::{Character, CharacterId},
|
character::{Character, CharacterId},
|
||||||
diedto::DiedTo,
|
diedto::DiedTo,
|
||||||
error::GameError,
|
error::GameError,
|
||||||
|
|
@ -294,6 +295,7 @@ impl Village {
|
||||||
impl RoleTitle {
|
impl RoleTitle {
|
||||||
pub fn title_to_role_excl_apprentice(self) -> Role {
|
pub fn title_to_role_excl_apprentice(self) -> Role {
|
||||||
match self {
|
match self {
|
||||||
|
RoleTitle::Bloodletter => Role::Bloodletter,
|
||||||
RoleTitle::Insomniac => Role::Insomniac,
|
RoleTitle::Insomniac => Role::Insomniac,
|
||||||
RoleTitle::LoneWolf => Role::LoneWolf,
|
RoleTitle::LoneWolf => Role::LoneWolf,
|
||||||
RoleTitle::Villager => Role::Villager,
|
RoleTitle::Villager => Role::Villager,
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@
|
||||||
use core::num::NonZeroU8;
|
use core::num::NonZeroU8;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
aura::Aura,
|
||||||
diedto::DiedTo,
|
diedto::DiedTo,
|
||||||
error::GameError,
|
error::GameError,
|
||||||
game::{
|
game::{
|
||||||
|
|
@ -52,6 +53,10 @@ impl Village {
|
||||||
let mut new_village = self.clone();
|
let mut new_village = self.clone();
|
||||||
for change in all_changes {
|
for change in all_changes {
|
||||||
match change {
|
match change {
|
||||||
|
NightChange::ApplyAura { target, aura, .. } => {
|
||||||
|
let target = new_village.character_by_id_mut(*target)?;
|
||||||
|
target.apply_aura(*aura);
|
||||||
|
}
|
||||||
NightChange::ElderReveal { elder } => {
|
NightChange::ElderReveal { elder } => {
|
||||||
new_village.character_by_id_mut(*elder)?.elder_reveal()
|
new_village.character_by_id_mut(*elder)?.elder_reveal()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
#![allow(clippy::new_without_default)]
|
#![allow(clippy::new_without_default)]
|
||||||
|
|
||||||
|
pub mod aura;
|
||||||
pub mod character;
|
pub mod character;
|
||||||
pub mod diedto;
|
pub mod diedto;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
|
|
@ -21,7 +22,6 @@ pub mod game;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod game_test;
|
mod game_test;
|
||||||
pub mod message;
|
pub mod message;
|
||||||
pub mod modifier;
|
|
||||||
pub mod nonzero;
|
pub mod nonzero;
|
||||||
pub mod player;
|
pub mod player;
|
||||||
pub mod role;
|
pub mod role;
|
||||||
|
|
|
||||||
|
|
@ -203,6 +203,12 @@ pub enum ActionPrompt {
|
||||||
},
|
},
|
||||||
#[checks(ActionType::Insomniac)]
|
#[checks(ActionType::Insomniac)]
|
||||||
Insomniac { character_id: CharacterIdentity },
|
Insomniac { character_id: CharacterIdentity },
|
||||||
|
#[checks(ActionType::OtherWolf)]
|
||||||
|
Bloodletter {
|
||||||
|
character_id: CharacterIdentity,
|
||||||
|
living_players: Box<[CharacterIdentity]>,
|
||||||
|
marked: Option<CharacterId>,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ActionPrompt {
|
impl ActionPrompt {
|
||||||
|
|
@ -230,6 +236,7 @@ impl ActionPrompt {
|
||||||
| ActionPrompt::Empath { character_id, .. }
|
| ActionPrompt::Empath { character_id, .. }
|
||||||
| ActionPrompt::Vindicator { character_id, .. }
|
| ActionPrompt::Vindicator { character_id, .. }
|
||||||
| ActionPrompt::PyreMaster { character_id, .. }
|
| ActionPrompt::PyreMaster { character_id, .. }
|
||||||
|
| ActionPrompt::Bloodletter { character_id, .. }
|
||||||
| ActionPrompt::DireWolf { character_id, .. } => Some(character_id.character_id),
|
| ActionPrompt::DireWolf { character_id, .. } => Some(character_id.character_id),
|
||||||
|
|
||||||
ActionPrompt::WolvesIntro { .. }
|
ActionPrompt::WolvesIntro { .. }
|
||||||
|
|
@ -241,6 +248,7 @@ impl ActionPrompt {
|
||||||
pub(crate) fn matches_beholding(&self, target: CharacterId) -> bool {
|
pub(crate) fn matches_beholding(&self, target: CharacterId) -> bool {
|
||||||
match self {
|
match self {
|
||||||
ActionPrompt::Insomniac { character_id, .. }
|
ActionPrompt::Insomniac { character_id, .. }
|
||||||
|
| ActionPrompt::Bloodletter { character_id, .. }
|
||||||
| ActionPrompt::Seer { character_id, .. }
|
| ActionPrompt::Seer { character_id, .. }
|
||||||
| ActionPrompt::Arcanist { character_id, .. }
|
| ActionPrompt::Arcanist { character_id, .. }
|
||||||
| ActionPrompt::Gravedigger { character_id, .. }
|
| ActionPrompt::Gravedigger { character_id, .. }
|
||||||
|
|
@ -344,7 +352,12 @@ impl ActionPrompt {
|
||||||
Ok(prompt)
|
Ok(prompt)
|
||||||
}
|
}
|
||||||
|
|
||||||
ActionPrompt::LoneWolfKill {
|
ActionPrompt::Bloodletter {
|
||||||
|
living_players: targets,
|
||||||
|
marked,
|
||||||
|
..
|
||||||
|
}
|
||||||
|
| ActionPrompt::LoneWolfKill {
|
||||||
living_players: targets,
|
living_players: targets,
|
||||||
marked,
|
marked,
|
||||||
..
|
..
|
||||||
|
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
// 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/>.
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
|
|
||||||
pub enum Modifier {
|
|
||||||
Drunk,
|
|
||||||
Insane,
|
|
||||||
}
|
|
||||||
|
|
@ -271,6 +271,11 @@ pub enum Role {
|
||||||
#[checks(Powerful::Powerful)]
|
#[checks(Powerful::Powerful)]
|
||||||
#[checks("wolf")]
|
#[checks("wolf")]
|
||||||
LoneWolf,
|
LoneWolf,
|
||||||
|
#[checks(Alignment::Wolves)]
|
||||||
|
#[checks(Killer::Killer)]
|
||||||
|
#[checks(Powerful::Powerful)]
|
||||||
|
#[checks("wolf")]
|
||||||
|
Bloodletter,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Role {
|
impl Role {
|
||||||
|
|
@ -313,6 +318,7 @@ impl Role {
|
||||||
|
|
||||||
Role::Werewolf => KillingWolfOrder::Werewolf,
|
Role::Werewolf => KillingWolfOrder::Werewolf,
|
||||||
Role::AlphaWolf { .. } => KillingWolfOrder::AlphaWolf,
|
Role::AlphaWolf { .. } => KillingWolfOrder::AlphaWolf,
|
||||||
|
Role::BloodLetter { .. } => KillingWolfOrder::Bloodletter,
|
||||||
Role::DireWolf { .. } => KillingWolfOrder::DireWolf,
|
Role::DireWolf { .. } => KillingWolfOrder::DireWolf,
|
||||||
Role::Shapeshifter { .. } => KillingWolfOrder::Shapeshifter,
|
Role::Shapeshifter { .. } => KillingWolfOrder::Shapeshifter,
|
||||||
Role::LoneWolf => KillingWolfOrder::LoneWolf,
|
Role::LoneWolf => KillingWolfOrder::LoneWolf,
|
||||||
|
|
@ -325,7 +331,7 @@ impl Role {
|
||||||
| Role::Adjudicator
|
| Role::Adjudicator
|
||||||
| Role::DireWolf { .. }
|
| Role::DireWolf { .. }
|
||||||
| Role::Arcanist
|
| Role::Arcanist
|
||||||
| Role::Seer => true,
|
| Role::Seer | Role::BloodLetter => true,
|
||||||
|
|
||||||
Role::Insomniac // has to at least get one good night of sleep, right?
|
Role::Insomniac // has to at least get one good night of sleep, right?
|
||||||
| Role::Beholder
|
| Role::Beholder
|
||||||
|
|
@ -400,6 +406,7 @@ impl Role {
|
||||||
| Role::Militia { targeted: None }
|
| Role::Militia { targeted: None }
|
||||||
| Role::MapleWolf { .. }
|
| Role::MapleWolf { .. }
|
||||||
| Role::Guardian { .. }
|
| Role::Guardian { .. }
|
||||||
|
| Role::BloodLetter { .. }
|
||||||
| Role::Seer => true,
|
| Role::Seer => true,
|
||||||
|
|
||||||
Role::Apprentice(title) => village
|
Role::Apprentice(title) => village
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue