From ee0e52d9883466da43cb9350dbbe1667cb89e4ce Mon Sep 17 00:00:00 2001 From: Sam Maselli Date: Sun, 8 Dec 2024 19:21:16 -0500 Subject: [PATCH] godfather and imposter backup --- .../ability_input/saved_controllers_map.rs | 137 +++++++------ server/src/game/components/mafia.rs | 22 ++- server/src/game/event/mod.rs | 1 + .../event/on_controller_selection_changed.rs | 16 ++ server/src/game/role/counterfeiter.rs | 181 +++++------------- server/src/game/role/godfather.rs | 9 - server/src/game/role/impostor.rs | 9 - server/src/game/role/mortician.rs | 1 - 8 files changed, 163 insertions(+), 213 deletions(-) create mode 100644 server/src/game/event/on_controller_selection_changed.rs diff --git a/server/src/game/ability_input/saved_controllers_map.rs b/server/src/game/ability_input/saved_controllers_map.rs index d84b57a39..e6da19f50 100644 --- a/server/src/game/ability_input/saved_controllers_map.rs +++ b/server/src/game/ability_input/saved_controllers_map.rs @@ -4,7 +4,12 @@ use crate::{ game::{ chat::ChatMessageVariant, components::{ forfeit_vote::ForfeitVote, insider_group::InsiderGroupID, mafia::Mafia, pitchfork::Pitchfork, syndicate_gun_item::SyndicateGunItem - }, event::on_validated_ability_input_received::OnValidatedAbilityInputReceived, phase::PhaseType, player::PlayerReference, Game + }, + event::{ + on_controller_selection_changed::OnControllerSelectionChanged, + on_validated_ability_input_received::OnValidatedAbilityInputReceived + }, + phase::PhaseType, player::PlayerReference, Game }, packet::ToClientPacket, vec_map::VecMap, vec_set::VecSet }; @@ -62,7 +67,7 @@ impl SavedControllersMap{ } - // new mutators + // mutators fn update_controllers_from_parameters(game: &mut Game){ let mut new_controller_parameters_map = ControllerParametersMap::default(); @@ -90,6 +95,75 @@ impl SavedControllersMap{ } } + pub fn send_selection_message( + game: &mut Game, + player_ref: PlayerReference, + id: ControllerID, + selection: AbilitySelection + ){ + let chat_message = ChatMessageVariant::AbilityUsed{ + player: player_ref.index(), + ability_id: id, + selection: selection.clone() + }; + + let mut target_message_sent = false; + for insider_group in InsiderGroupID::all_insider_groups_with_player(game, player_ref){ + game.add_message_to_chat_group( insider_group.get_insider_chat_group(), chat_message.clone()); + target_message_sent = true; + } + if !target_message_sent{ + player_ref.add_private_chat_message(game, chat_message); + } + } + + /// Keeps old selection if its valid, otherwise uses default_selection, + /// even if default selection is invalid + fn set_controller_parameters(game: &mut Game, new_controller_parameters_map: ControllerParametersMap){ + + let controller_ids_to_remove = game.saved_controllers.controller_parameters().controller_parameters().keys() + .filter(|id| !new_controller_parameters_map.controller_parameters().contains_key(id)) + .cloned() + .collect::>(); + + for id in controller_ids_to_remove{ + game.saved_controllers.saved_controllers.remove(&id); + } + + for (id, controller_parameters) in new_controller_parameters_map.controller_parameters().iter(){ + let mut new_selection = controller_parameters.default_selection().clone(); + + let mut kept_old_selection = false; + + + if let Some(SavedController{selection: old_selection, ..}) = game.saved_controllers.saved_controllers.get(&id) { + if + controller_parameters.validate_selection(game, old_selection) && + !controller_parameters.dont_save() && + !controller_parameters.grayed_out() + { + new_selection = old_selection.clone(); + kept_old_selection = true; + } + } + + + game.saved_controllers.saved_controllers.insert( + id.clone(), + SavedController::new( + new_selection, + controller_parameters.clone() + ) + ); + + if !kept_old_selection { + OnControllerSelectionChanged::new(id.clone()).invoke(game); + } + } + + Self::send_saved_controllers_to_clients(game); + } + /// return true if selection was valid pub fn set_selection_in_controller( game: &mut Game, @@ -125,6 +199,7 @@ impl SavedControllersMap{ if !available_ability_data.dont_save() { *saved_selection = selection.clone(); + OnControllerSelectionChanged::new(id).invoke(game); } true @@ -286,64 +361,6 @@ impl SavedControllersMap{ ) } - //mutators - /// Keeps old selection if its valid, otherwise uses default_selection, - /// even if default selection is invalid - fn set_controller_parameters(game: &mut Game, new_controller_parameters_map: ControllerParametersMap){ - - let controller_ids_to_remove = game.saved_controllers.controller_parameters().controller_parameters().keys() - .filter(|id| !new_controller_parameters_map.controller_parameters().contains_key(id)) - .cloned() - .collect::>(); - - for id in controller_ids_to_remove{ - game.saved_controllers.saved_controllers.remove(&id); - } - - for (id, controller_parameters) in new_controller_parameters_map.controller_parameters().iter(){ - let mut new_selection = controller_parameters.default_selection().clone(); - - if !controller_parameters.dont_save() && !controller_parameters.grayed_out(){ - if let Some(SavedController{selection: old_selection, ..}) = game.saved_controllers.saved_controllers.get(&id) { - if controller_parameters.validate_selection(game, old_selection){ - new_selection = old_selection.clone() - } - } - } - - game.saved_controllers.saved_controllers.insert( - id.clone(), - SavedController::new( - new_selection, - controller_parameters.clone() - ) - ); - } - - Self::send_saved_controllers_to_clients(game); - } - - pub fn send_selection_message( - game: &mut Game, - player_ref: PlayerReference, - id: ControllerID, - selection: AbilitySelection - ){ - let chat_message = ChatMessageVariant::AbilityUsed{ - player: player_ref.index(), - ability_id: id, - selection: selection.clone() - }; - - let mut target_message_sent = false; - for insider_group in InsiderGroupID::all_insider_groups_with_player(game, player_ref){ - game.add_message_to_chat_group( insider_group.get_insider_chat_group(), chat_message.clone()); - target_message_sent = true; - } - if !target_message_sent{ - player_ref.add_private_chat_message(game, chat_message); - } - } // game stuff diff --git a/server/src/game/components/mafia.rs b/server/src/game/components/mafia.rs index 4344a1f8f..9bf4d03cc 100644 --- a/server/src/game/components/mafia.rs +++ b/server/src/game/components/mafia.rs @@ -1,7 +1,7 @@ use rand::seq::SliceRandom; use crate::{game::{ - ability_input::{AbilitySelection, AvailableAbilitySelection, ControllerID, ControllerParametersMap, PlayerListSelection}, attack_power::AttackPower, chat::{ChatGroup, ChatMessageVariant}, grave::GraveKiller, phase::PhaseType, player::PlayerReference, role::{Priority, RoleState}, role_list::RoleSet, visit::Visit, Game + ability_input::{AbilitySelection, AvailableAbilitySelection, ControllerID, ControllerParametersMap, PlayerListSelection}, attack_power::AttackPower, chat::{ChatGroup, ChatMessageVariant}, grave::GraveKiller, phase::PhaseType, player::PlayerReference, role::{Priority, RoleState}, role_list::RoleSet, tag::Tag, visit::Visit, Game }, vec_set::{vec_set, VecSet}}; use super::{detained::Detained, insider_group::InsiderGroupID, night_visits::NightVisits, syndicate_gun_item::SyndicateGunItem}; @@ -150,6 +150,26 @@ impl Mafia{ } } + pub fn on_controller_selection_changed(game: &mut Game, controller_id: ControllerID){ + if controller_id != ControllerID::syndicate_choose_backup() {return}; + + let backup = + game.saved_controllers.get_controller_current_selection_player_list(controller_id) + .map(|b|b.0.first().cloned()) + .flatten(); + + + for player_ref in PlayerReference::all_players(game){ + if !InsiderGroupID::Mafia.is_player_in_revealed_group(game, player_ref) {continue} + player_ref.remove_player_tag_on_all(game, Tag::GodfatherBackup); + } + if let Some(backup) = backup{ + for player_ref in PlayerReference::all_players(game){ + if !InsiderGroupID::Mafia.is_player_in_revealed_group(game, player_ref) {continue} + player_ref.push_player_tag(game, backup, Tag::GodfatherBackup); + } + } + } /// - This must go after rolestate on any death /// - Godfathers backup should become godfather if godfather dies as part of the godfathers ability diff --git a/server/src/game/event/mod.rs b/server/src/game/event/mod.rs index 9ba2e1a9b..20340804a 100644 --- a/server/src/game/event/mod.rs +++ b/server/src/game/event/mod.rs @@ -12,6 +12,7 @@ pub mod on_remove_role_label; pub mod before_initial_role_creation; pub mod on_ability_input_received; pub mod on_validated_ability_input_received; +pub mod on_controller_selection_changed; pub mod on_tick; diff --git a/server/src/game/event/on_controller_selection_changed.rs b/server/src/game/event/on_controller_selection_changed.rs new file mode 100644 index 000000000..89461ce0f --- /dev/null +++ b/server/src/game/event/on_controller_selection_changed.rs @@ -0,0 +1,16 @@ +use crate::game::{ + ability_input::ControllerID, components::mafia::Mafia, Game +}; + +#[must_use = "Event must be invoked"] +pub struct OnControllerSelectionChanged{ + id: ControllerID, +} +impl OnControllerSelectionChanged{ + pub fn new(id: ControllerID) -> Self{ + Self{id} + } + pub fn invoke(self, game: &mut Game){ + Mafia::on_controller_selection_changed(game, self.id); + } +} \ No newline at end of file diff --git a/server/src/game/role/counterfeiter.rs b/server/src/game/role/counterfeiter.rs index 3ee7093a0..50cc51ec2 100644 --- a/server/src/game/role/counterfeiter.rs +++ b/server/src/game/role/counterfeiter.rs @@ -1,25 +1,21 @@ use serde::{Deserialize, Serialize}; use crate::game::attack_power::{AttackPower, DefensePower}; -use crate::game::chat::{ChatGroup, ChatMessageVariant}; -use crate::game::components::insider_group::InsiderGroupID; +use crate::game::chat::ChatMessageVariant; use crate::game::grave::GraveKiller; use crate::game::phase::PhaseType; use crate::game::player::PlayerReference; use crate::game::role_list::RoleSet; -use crate::game::tag::Tag; use crate::game::visit::Visit; use crate::game::Game; -use super::{GetClientRoleState, Priority, Role, RoleState, RoleStateImpl}; +use super::{ControllerID, GetClientRoleState, PlayerListSelection, Priority, Role, RoleStateImpl}; #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] pub struct Counterfeiter{ - backup: Option, - pub fake_role: Role, pub fake_will: String, pub forges_remaining: u8, @@ -31,8 +27,6 @@ pub struct Counterfeiter{ #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] pub struct ClientRoleState{ - backup: Option, - pub fake_role: Role, pub fake_will: String, pub forges_remaining: u8, @@ -49,8 +43,6 @@ pub enum CounterfeiterAction{ impl Default for Counterfeiter { fn default() -> Self { Counterfeiter { - backup: None, - forges_remaining: 3, forged_ref: None, fake_role: Role::Jester, @@ -68,148 +60,72 @@ pub(super) const DEFENSE: DefensePower = DefensePower::None; impl RoleStateImpl for Counterfeiter { type ClientRoleState = ClientRoleState; fn do_night_action(self, game: &mut Game, actor_ref: PlayerReference, priority: Priority) { + if game.day_number() <= 1 {return} - - if game.day_number() == 1 {return} - - if actor_ref.night_blocked(game) { - if let Some(backup) = self.backup { - match priority { - Priority::Deception => { - let mut visits = backup.untagged_night_visits_cloned(game).clone(); - if let Some(visit) = visits.first_mut() { - visit.attack = true; - game.add_message_to_chat_group(ChatGroup::Mafia, ChatMessageVariant::GodfatherBackupKilled { backup: backup.index() }); - } - backup.set_night_visits(game, visits); - }, - Priority::Kill => { - if let Some(visit) = backup.untagged_night_visits_cloned(game).first(){ - let target_ref = visit.target; - target_ref.try_night_kill_single_attacker( - backup, game, GraveKiller::RoleSet(RoleSet::Mafia), AttackPower::Basic, false - ); - } - }, - _ => {} - } - } - }else{ - match priority { - Priority::Deception => { - if self.forges_remaining == 0 || self.action == CounterfeiterAction::NoForge {return} + match priority { + Priority::Deception => { + if self.forges_remaining == 0 || self.action == CounterfeiterAction::NoForge {return} - let actor_visits = actor_ref.untagged_night_visits_cloned(game); + let actor_visits = actor_ref.untagged_night_visits_cloned(game); let Some(visit) = actor_visits.first() else{return}; + let target_ref = visit.target; + + target_ref.set_night_grave_role(game, Some(self.fake_role)); + target_ref.set_night_grave_will(game, self.fake_will.clone()); + actor_ref.set_role_state(game, Counterfeiter { + forges_remaining: self.forges_remaining.saturating_sub(1), + forged_ref: Some(target_ref), + ..self + }); + }, + Priority::Kill => { + let actor_visits = actor_ref.untagged_night_visits_cloned(game); + if let Some(visit) = actor_visits.first(){ let target_ref = visit.target; - - target_ref.set_night_grave_role(game, Some(self.fake_role)); - target_ref.set_night_grave_will(game, self.fake_will.clone()); - actor_ref.set_role_state(game, RoleState::Counterfeiter(Counterfeiter { - forges_remaining: self.forges_remaining - 1, - forged_ref: Some(target_ref), - ..self - })); - }, - Priority::Kill => { - let actor_visits = actor_ref.untagged_night_visits_cloned(game); - if let Some(visit) = actor_visits.first(){ - let target_ref = visit.target; - - target_ref.try_night_kill_single_attacker( - actor_ref, game, GraveKiller::RoleSet(RoleSet::Mafia), AttackPower::Basic, false - ); - } - }, - Priority::Investigative => { - if let Some(forged_ref) = self.forged_ref { - if forged_ref.night_died(game) { - actor_ref.push_night_message(game, ChatMessageVariant::PlayerRoleAndAlibi{ - player: forged_ref, - role: forged_ref.role(game), - will: forged_ref.will(game).to_string(), - }); - } + target_ref.try_night_kill_single_attacker( + actor_ref, game, GraveKiller::RoleSet(RoleSet::Mafia), AttackPower::Basic, false + ); + } + }, + Priority::Investigative => { + if let Some(forged_ref) = self.forged_ref { + if forged_ref.night_died(game) { + actor_ref.push_night_message(game, ChatMessageVariant::PlayerRoleAndAlibi{ + player: forged_ref, + role: forged_ref.role(game), + will: forged_ref.will(game).to_string(), + }); } - }, - _ => {} - } + } + }, + _ => {} } } fn can_select(self, game: &Game, actor_ref: PlayerReference, target_ref: PlayerReference) -> bool { crate::game::role::common_role::can_night_select(game, actor_ref, target_ref) && game.day_number() > 1 } - fn do_day_action(self, game: &mut Game, actor_ref: PlayerReference, target_ref: PlayerReference) { - if let Some(old_target_ref) = self.backup { - if old_target_ref == target_ref { - actor_ref.set_role_state(game, RoleState::Counterfeiter(Counterfeiter{backup: None, ..self})); - } else { - actor_ref.set_role_state(game, RoleState::Counterfeiter(Counterfeiter{backup: Some(target_ref), ..self})); - } - } else { - actor_ref.set_role_state(game, RoleState::Counterfeiter(Counterfeiter{backup: Some(target_ref), ..self})); - } - - let RoleState::Counterfeiter(Counterfeiter { backup, .. }) = *actor_ref.role_state(game) else { - unreachable!("Role was just set to Counterfeiter"); - }; - - game.add_message_to_chat_group(ChatGroup::Mafia, ChatMessageVariant::GodfatherBackup { backup: backup.map(|p|p.index()) }); - - for player_ref in PlayerReference::all_players(game){ - if !InsiderGroupID::Mafia.is_player_in_revealed_group(game, player_ref) { - continue; - } - player_ref.remove_player_tag_on_all(game, Tag::GodfatherBackup); - } - - if let Some(backup) = backup { - for player_ref in PlayerReference::all_players(game){ - if !InsiderGroupID::Mafia.is_player_in_revealed_group(game, player_ref) { - continue; - } - player_ref.push_player_tag(game, backup, Tag::GodfatherBackup); - } - } - - } - fn can_day_target(self, game: &Game, actor_ref: PlayerReference, target_ref: PlayerReference) -> bool { - actor_ref != target_ref && - actor_ref.alive(game) && target_ref.alive(game) && - RoleSet::Mafia.get_roles().contains(&target_ref.role(game)) && - InsiderGroupID::Mafia.is_player_in_revealed_group(game, target_ref) - } fn convert_selection_to_visits(self, game: &Game, actor_ref: PlayerReference, target_refs: Vec) -> Vec { crate::game::role::common_role::convert_selection_to_visits(game, actor_ref, target_refs, true) } fn on_phase_start(self, game: &mut Game, actor_ref: PlayerReference, _phase: PhaseType){ - actor_ref.set_role_state(game, RoleState::Counterfeiter(Counterfeiter{ + actor_ref.set_role_state(game, Counterfeiter{ forged_ref: None, ..self - })); + }); } fn on_any_death(self, game: &mut Game, actor_ref: PlayerReference, dead_player_ref: PlayerReference){ - if actor_ref == dead_player_ref { - let Some(backup) = self.backup else {return}; - - actor_ref.set_role_state(game, RoleState::Counterfeiter(Counterfeiter{backup: None, ..self.clone()})); - for player_ref in PlayerReference::all_players(game){ - if InsiderGroupID::Mafia.is_player_in_revealed_group(game, player_ref){ - continue; - } - player_ref.remove_player_tag_on_all(game, Tag::GodfatherBackup); - } - - if !backup.alive(game){return} - - //convert backup to Counterfeiter - backup.set_role_and_win_condition_and_revealed_group(game, RoleState::Counterfeiter(Counterfeiter{backup: None, ..self})); - } - else if self.backup.is_some_and(|p|p == dead_player_ref) { - actor_ref.set_role_state(game, RoleState::Counterfeiter(Counterfeiter{backup: None, ..self})); - } + let Some(PlayerListSelection(backup)) = game.saved_controllers + .get_controller_current_selection_player_list( + ControllerID::syndicate_choose_backup() + ) + else {return}; + let Some(backup) = backup.first() else {return}; + if actor_ref != dead_player_ref {return} + + //convert backup to godfather + backup.set_role_and_win_condition_and_revealed_group(game, self); } fn default_revealed_groups(self) -> crate::vec_set::VecSet { vec![ @@ -220,7 +136,6 @@ impl RoleStateImpl for Counterfeiter { impl GetClientRoleState for Counterfeiter { fn get_client_role_state(self, _game: &Game, _actor_ref: PlayerReference) -> ClientRoleState { ClientRoleState{ - backup: self.backup, fake_role: self.fake_role, fake_will: self.fake_will, forges_remaining: self.forges_remaining, diff --git a/server/src/game/role/godfather.rs b/server/src/game/role/godfather.rs index b3d444c25..d1db25c67 100644 --- a/server/src/game/role/godfather.rs +++ b/server/src/game/role/godfather.rs @@ -1,12 +1,10 @@ use serde::Serialize; use crate::game::attack_power::{AttackPower, DefensePower}; -use crate::game::components::insider_group::InsiderGroupID; use crate::game::grave::GraveKiller; use crate::game::player::PlayerReference; use crate::game::role_list::RoleSet; -use crate::game::tag::Tag; use crate::game::visit::Visit; use crate::game::Game; @@ -51,13 +49,6 @@ impl RoleStateImpl for Godfather { let Some(backup) = backup.first() else {return}; if actor_ref != dead_player_ref {return} - for player_ref in PlayerReference::all_players(game){ - if !InsiderGroupID::Mafia.is_player_in_revealed_group(game, player_ref) { - continue; - } - player_ref.remove_player_tag_on_all(game, Tag::GodfatherBackup); - } - //convert backup to godfather backup.set_role_and_win_condition_and_revealed_group(game, Godfather); } diff --git a/server/src/game/role/impostor.rs b/server/src/game/role/impostor.rs index 0869aaf7d..54faac4c4 100644 --- a/server/src/game/role/impostor.rs +++ b/server/src/game/role/impostor.rs @@ -2,11 +2,9 @@ use serde::Serialize; use crate::game::ability_input::*; use crate::game::attack_power::DefensePower; -use crate::game::components::insider_group::InsiderGroupID; use crate::game::grave::GraveInformation; use crate::game::player::PlayerReference; -use crate::game::tag::Tag; use crate::game::visit::Visit; use crate::game::Game; @@ -93,13 +91,6 @@ impl RoleStateImpl for Impostor { let Some(backup) = backup.first() else {return}; if actor_ref != dead_player_ref {return} - for player_ref in PlayerReference::all_players(game){ - if !InsiderGroupID::Mafia.is_player_in_revealed_group(game, player_ref) { - continue; - } - player_ref.remove_player_tag_on_all(game, Tag::GodfatherBackup); - } - //convert backup to godfather backup.set_role_and_win_condition_and_revealed_group(game, Godfather); } diff --git a/server/src/game/role/mortician.rs b/server/src/game/role/mortician.rs index c5a64e2c5..c0075d006 100644 --- a/server/src/game/role/mortician.rs +++ b/server/src/game/role/mortician.rs @@ -80,7 +80,6 @@ impl RoleStateImpl for Mortician { .filter(|p| *p != actor_ref) .filter(|player| player.alive(game) && - !InsiderGroupID::in_same_revealed_group(game, actor_ref, *player) && !self.obscured_players.contains(&player) ) .collect(),