From 66f1bd221e875a181a213b891d907e0d00a0147b Mon Sep 17 00:00:00 2001 From: Sam Maselli Date: Thu, 4 Jan 2024 05:11:28 -0500 Subject: [PATCH 1/4] werewolf start and tag --- client/src/game/gameState.d.tsx | 1 + client/src/game/roleState.d.tsx | 3 + client/src/resources/lang/en_us.json | 1 + server/src/game/end_game_condition.rs | 1 + server/src/game/role/doomsayer.rs | 2 +- server/src/game/role/mod.rs | 1 + server/src/game/role/werewolf.rs | 163 ++++++++++++++++++++++++++ server/src/game/tag.rs | 4 +- 8 files changed, 174 insertions(+), 2 deletions(-) create mode 100644 server/src/game/role/werewolf.rs diff --git a/client/src/game/gameState.d.tsx b/client/src/game/gameState.d.tsx index 5add7eb98..c10abfc1a 100644 --- a/client/src/game/gameState.d.tsx +++ b/client/src/game/gameState.d.tsx @@ -85,6 +85,7 @@ export type PhaseTimes = { } export type Tag = | "godfatherBackup" +| "werewolfTracked" | "doused" | "hexed" | "necronomicon" diff --git a/client/src/game/roleState.d.tsx b/client/src/game/roleState.d.tsx index 0da3a4fef..eed294cad 100644 --- a/client/src/game/roleState.d.tsx +++ b/client/src/game/roleState.d.tsx @@ -71,6 +71,9 @@ Doomsayer role: "politician" } | { role: "arsonist" +} | { + role: "werewolf" + trackedPlayers: PlayerIndex[] } | { role: "death", souls: number diff --git a/client/src/resources/lang/en_us.json b/client/src/resources/lang/en_us.json index e55c9f181..eb726570a 100644 --- a/client/src/resources/lang/en_us.json +++ b/client/src/resources/lang/en_us.json @@ -311,6 +311,7 @@ "tag.godfatherBackup":"🌹", "tag.executionerTarget":"🎯", + "tag.werewolfTracked": "🐾", "tag.necronomicon":"📓", "tag.doused":"🔥", "tag.insane":"🪬", diff --git a/server/src/game/end_game_condition.rs b/server/src/game/end_game_condition.rs index 9a519c236..01edd1e79 100644 --- a/server/src/game/end_game_condition.rs +++ b/server/src/game/end_game_condition.rs @@ -4,6 +4,7 @@ pub enum EndGameCondition { Town, Vampire, Arsonist, + Werewolf, None } diff --git a/server/src/game/role/doomsayer.rs b/server/src/game/role/doomsayer.rs index 7d68bd1d8..6281bdfff 100644 --- a/server/src/game/role/doomsayer.rs +++ b/server/src/game/role/doomsayer.rs @@ -53,7 +53,7 @@ impl DoomsayerGuess{ Role::Janitor | Role::Framer => Some(DoomsayerGuess::Mafia), //Neutral Role::Jester | Role::Executioner | Role::Doomsayer | Role::Politician | - Role::Arsonist | Role::Death | + Role::Arsonist | Role::Werewolf | Role::Death | Role::Vampire | Role::Amnesiac => Some(DoomsayerGuess::Neutral), } } diff --git a/server/src/game/role/mod.rs b/server/src/game/role/mod.rs index ab55445eb..efa2048f3 100644 --- a/server/src/game/role/mod.rs +++ b/server/src/game/role/mod.rs @@ -84,6 +84,7 @@ macros::roles! { Politician : politician, Arsonist : arsonist, + Werewolf : werewolf, Death : death, Vampire : vampire, diff --git a/server/src/game/role/werewolf.rs b/server/src/game/role/werewolf.rs new file mode 100644 index 000000000..f1379ae30 --- /dev/null +++ b/server/src/game/role/werewolf.rs @@ -0,0 +1,163 @@ +use serde::Serialize; + +use crate::game::chat::{ChatGroup, ChatMessage}; +use crate::game::grave::GraveKiller; +use crate::game::phase::PhaseType; +use crate::game::player::PlayerReference; +use crate::game::role_list::FactionAlignment; +use crate::game::end_game_condition::EndGameCondition; +use crate::game::tag::Tag; +use crate::game::visit::Visit; +use crate::game::team::Team; +use crate::game::Game; +use super::{Priority, RoleStateImpl, Role, RoleState}; + + +#[derive(Clone, Debug, Serialize, Default)] +#[serde(rename_all = "camelCase")] +pub struct Werewolf{ + pub tracked_players: Vec, +} + +pub(super) const FACTION_ALIGNMENT: FactionAlignment = FactionAlignment::NeutralKilling; +pub(super) const MAXIMUM_COUNT: Option = None; + +impl RoleStateImpl for Werewolf { + fn suspicious(&self, _game: &Game, _actor_ref: PlayerReference) -> bool {true} + fn defense(&self, _game: &Game, _actor_ref: PlayerReference) -> u8 {1} + fn control_immune(&self, _game: &Game, _actor_ref: PlayerReference) -> bool {false} + fn roleblock_immune(&self, _game: &Game, _actor_ref: PlayerReference) -> bool {false} + fn end_game_condition(&self, _game: &Game, _actor_ref: PlayerReference) -> EndGameCondition {EndGameCondition::Werewolf} + fn team(&self, _game: &Game, _actor_ref: PlayerReference) -> Option {None} + + fn do_night_action(self, game: &mut Game, actor_ref: PlayerReference, priority: Priority) { + + + match priority { + Priority::Deception => { + //make werewolf look like jester on night 1 and 3 + if game.day_number() == 1 || game.day_number() == 3 { + actor_ref.set_night_appeared_role(game, Role::Jester); + } + }, + Priority::Kill => { + + if game.day_number() != 1 && game.day_number() != 3 { + return; + } + + if let Some(first_visit) = actor_ref.night_visits(game).first() { + //rampage at target + let target_ref = first_visit.target; + if target_ref.night_jailed(game){ + actor_ref.push_night_message(game, ChatMessage::TargetJailed); + return + } + + + for other_player_ref in + target_ref.lookout_seen_players(game).into_iter().filter(|p|actor_ref!=*p) + .collect::>() + { + other_player_ref.try_night_kill(actor_ref, game, GraveKiller::Role(Role::Werewolf), 2, true); + } + target_ref.try_night_kill(actor_ref, game, GraveKiller::Role(Role::Werewolf), 2, true); + + }else{ + //rampage at home + + for other_player_ref in + actor_ref.lookout_seen_players(game).into_iter().filter(|p|actor_ref!=*p) + .collect::>() + { + other_player_ref.try_night_kill(actor_ref, game, GraveKiller::Role(Role::Werewolf), 2, true); + } + + if actor_ref.night_jailed(game){ + //kill all jailors NOT trying to execute me + for player_ref in PlayerReference::all_players(game){ + if + player_ref.alive(game) && + player_ref.role(game) == Role::Jailor && + player_ref.tracker_seen_visits(game).into_iter().any(|v|v.target!=actor_ref) + { + player_ref.try_night_kill(actor_ref, game, GraveKiller::Role(Role::Werewolf), 2, true); + } + } + } + } + }, + Priority::Investigative => { + + + + //on night 1 and 3, werewolf can track the scent of players who visit them and their target + if game.day_number() == 1 || game.day_number() == 3 { + + + let mut tracked_players: Vec = actor_ref.lookout_seen_players(game).into_iter().filter(|p|actor_ref!=*p).collect(); + + if let Some(first_visit) = actor_ref.night_visits(game).first() { + let target_ref = first_visit.target; + + if target_ref.night_jailed(game){ + actor_ref.push_night_message(game, ChatMessage::TargetJailed); + }else{ + tracked_players.push(target_ref); + } + } + + + actor_ref.remove_player_tag_on_all(game, Tag::WerewolfTracked); + + //send the list to the werewolf using tags + for player_ref in tracked_players.iter() { + actor_ref.push_player_tag(game, *player_ref, crate::game::tag::Tag::WerewolfTracked); + } + + actor_ref.set_role_state(game, RoleState::Werewolf(Werewolf { + tracked_players + })); + + } + + //track the scent of players + + + + + + }, + _ => {} + } + } + fn can_night_target(self, game: &Game, actor_ref: PlayerReference, target_ref: PlayerReference) -> bool { + crate::game::role::common_role::can_night_target(game, actor_ref, target_ref) + } + fn do_day_action(self, _game: &mut Game, _actor_ref: PlayerReference, _target_ref: PlayerReference) { + + } + fn can_day_target(self, _game: &Game, _actor_ref: PlayerReference, _target_ref: PlayerReference) -> bool { + false + } + fn convert_targets_to_visits(self, game: &Game, actor_ref: PlayerReference, target_refs: Vec) -> Vec { + crate::game::role::common_role::convert_targets_to_visits(game, actor_ref, target_refs, false, true) + } + fn get_current_send_chat_groups(self, game: &Game, actor_ref: PlayerReference) -> Vec { + crate::game::role::common_role::get_current_send_chat_groups(game, actor_ref, vec![]) + } + fn get_current_receive_chat_groups(self, game: &Game, actor_ref: PlayerReference) -> Vec { + crate::game::role::common_role::get_current_receive_chat_groups(game, actor_ref) + } + fn get_won_game(self, game: &Game, actor_ref: PlayerReference) -> bool { + crate::game::role::common_role::get_won_game(game, actor_ref) + } + fn on_phase_start(self, _game: &mut Game, _actor_ref: PlayerReference, _phase: PhaseType){ + } + fn on_role_creation(self, _game: &mut Game, _actor_ref: PlayerReference){ + } + fn on_any_death(self, _game: &mut Game, _actor_ref: PlayerReference, _dead_player_ref: PlayerReference){ + } + fn on_game_ending(self, _game: &mut Game, _actor_ref: PlayerReference){ + } +} \ No newline at end of file diff --git a/server/src/game/tag.rs b/server/src/game/tag.rs index f6cc9b6f1..c2080cf55 100644 --- a/server/src/game/tag.rs +++ b/server/src/game/tag.rs @@ -6,8 +6,10 @@ use serde::Serialize; pub enum Tag{ GodfatherBackup, Doused, + WerewolfTracked, + ExecutionerTarget, + Hexed, Necronomicon, - ExecutionerTarget, Insane } \ No newline at end of file From bec6b5ab1384a58eb852b85c4a1e8025841bab2a Mon Sep 17 00:00:00 2001 From: Sam Maselli Date: Thu, 4 Jan 2024 05:59:22 -0500 Subject: [PATCH 2/4] wiki --- client/src/components/ChatMessage.tsx | 12 ++++--- client/src/resources/lang/en_us.json | 12 ++++++- client/src/resources/roles.json | 10 ++++++ client/src/resources/styling/chatMessage.json | 3 +- server/src/game/chat/mod.rs | 4 ++- server/src/game/role/werewolf.rs | 31 ++++++++++++------- 6 files changed, 52 insertions(+), 20 deletions(-) diff --git a/client/src/components/ChatMessage.tsx b/client/src/components/ChatMessage.tsx index 8232ea0b1..b78c9d95c 100644 --- a/client/src/components/ChatMessage.tsx +++ b/client/src/components/ChatMessage.tsx @@ -267,12 +267,15 @@ export function translateChatMessage(message: ChatMessage): string { return translate("chatMessage.mediumSeance", GAME_MANAGER.state.players[message.player].toString()); case "witchedYou": return translate("chatMessage.witchedYou" + (message.immune ? ".immune" : "")); + case "werewolfTrackingResult": + return translate("chatMessage.werewolfTrackingResult", + GAME_MANAGER.state.players[message.trackedPlayer].toString(), + playerListToString(message.players) + ); case "playerWithNecronomicon": return translate("chatMessage.playerWithNecronomicon", GAME_MANAGER.state.players[message.playerIndex].toString()); case "deputyShotSomeoneSurvived": case "deathCollectedSouls": - case "arsonistCleanedSelf": - case "arsonistDousedPlayers": case "targetWasAttacked": case "youWereProtected": case "executionerWon": @@ -467,9 +470,8 @@ export type ChatMessage = { type: "witchMessage", message: ChatMessage } | { - type: "arsonistCleanedSelf" -} | { - type: "arsonistDousedPlayers", + type: "werewolfTrackingResult", + trackedPlayer: PlayerIndex players: PlayerIndex[] } | { type: "jesterWon" diff --git a/client/src/resources/lang/en_us.json b/client/src/resources/lang/en_us.json index eb726570a..cd2717fca 100644 --- a/client/src/resources/lang/en_us.json +++ b/client/src/resources/lang/en_us.json @@ -264,6 +264,11 @@ "role.arsonist.target": "Target", "role.arsonist.detarget": "Detarget", "role.arsonist.dayTarget": "Special", + + "role.werewolf.name": "Werewolf", + "role.werewolf.target": "Target", + "role.werewolf.detarget": "Detarget", + "role.werewolf.dayTarget": "Special", "role.death.name": "Death", "role.death.target": "Collect", @@ -417,6 +422,7 @@ "chatMessage.witchedYou":"You were possessed by a Witch.", "chatMessage.witchedYou.immune":"A Witch tried to possess you but you were immune.", "chatMessage.witchTargetImmune":"Your target was immune to possession.", + "chatMessage.werewolfTrackingResult":"\\1 visited \\2.", "chatMessage.veteranAttackedYou":"You were attacked by the veteran you visited.", "chatMessage.veteranAttackedVisitor":"You attacked a visitor.", "chatMessage.vigilanteSuicide":"You committed suicide over the guilt of killing an innocent person.", @@ -587,7 +593,11 @@ "wiki.entry.role.arsonist.title": "Arsonist", "wiki.entry.role.arsonist.abilities":"* Target someone to douse them\n* Target yourself to ignite all doused players", - "wiki.entry.role.arsonist.attributes":"* When you ignite you deal an unstoppable attack to all doused players\n* You douse all visitors to you\n* You can't be doused\n* If you are jailed, you will douse all Jailors and will still douse visitors to you\n* You do not douse visiting players who visited using only an astral visit\n* When you ignite, the kill is astral", + "wiki.entry.role.arsonist.attributes":"* When you ignite you deal an unstoppable attack to all doused players\n* You douse all visitors to you\n* You can't be doused\n* If you are jailed, you will douse all Jailors and will still douse visitors to you\n* You do not douse visiting players who visited using only an astral visit\n* When you ignite, the kill is astral\n* You will know who is doused", + + "wiki.entry.role.werewolf.title": "Werewolf", + "wiki.entry.role.werewolf.abilities":"* On full moon nights target a player to kill them and every visitor to them \n* On non full moon nights Target a player to gain their scent, and gain the ability to see who they visit every following night, you will also gain the scent of all players who visit you on non full moon nights\n* If you target nobody on a full moon night, you kill all visitors to yourself\n* On non full moon nights, you will look like the jester to investigative roles", + "wiki.entry.role.werewolf.attributes":"* Full moon nights are every night except night one and three\n* You have a powerfull attack\n* If you are jailed then you will attack all Jailors who did not execute you, but not their visitors\n* You will still attack or gain the scent of visits to you when you are roleblocked but not when you are jailed\n* You will know who you have the scent of\n* You dont kill or gain the scent of astral visitors\n* The visit you do to the visitors to your target when you kill or gain their scent is astral", "wiki.entry.role.death.title": "Death", "wiki.entry.role.death.abilities":"* Target a player on the night they die to collect a soul\n* Once you've collected six souls, everyone is notified, and at the start of the next night, you kill everyone, and win.", diff --git a/client/src/resources/roles.json b/client/src/resources/roles.json index 92f477b23..6a07f3278 100644 --- a/client/src/resources/roles.json +++ b/client/src/resources/roles.json @@ -319,6 +319,16 @@ "maxCount": null, "largeRoleSpecificMenu": false }, + "werewolf": { + "factionAlignment": "neutralKilling", + "suspicious": true, + "roleblockable": true, + "defense": 1, + "witchable": true, + "endGameCondition": "SingleRole", + "maxCount": null, + "largeRoleSpecificMenu": false + }, "death":{ "factionAlignment": "neutralKilling", "suspicious": false, diff --git a/client/src/resources/styling/chatMessage.json b/client/src/resources/styling/chatMessage.json index 4d339c3ba..68068a1b8 100644 --- a/client/src/resources/styling/chatMessage.json +++ b/client/src/resources/styling/chatMessage.json @@ -35,8 +35,6 @@ "consigliereResult": "result", "silenced": "warning", "mediumSeance": "special", - "arsonistCleanedSelf": "result", - "arsonistDousedPlayers": "result", "targetWasAttacked": "result", "youWereProtected": "result", "executionerWon": "result", @@ -56,6 +54,7 @@ "witchMessage": "result", "witchTargetImmune": "result", "witchedYou": "result", + "werewolfTrackingResult": "result", "youSurvivedAttack": "result", "doomsayerFailed": "result", "doomsayerWon": "result" diff --git a/server/src/game/chat/mod.rs b/server/src/game/chat/mod.rs index 051076218..b23ecac6a 100644 --- a/server/src/game/chat/mod.rs +++ b/server/src/game/chat/mod.rs @@ -1,4 +1,3 @@ - use serde::{Serialize, Deserialize}; use crate::game::{grave::Grave, role::Role, player::{PlayerIndex, PlayerReference}, verdict::Verdict, phase::PhaseType, Game}; @@ -158,6 +157,9 @@ pub enum ChatMessage { WitchedYou { immune: bool }, WitchMessage{message: Box}, + #[serde(rename_all = "camelCase")] + WerewolfTrackingResult{tracked_player: PlayerIndex, players: Vec}, + JesterWon, // TODO Rename ExecutionerYouWon ExecutionerWon, diff --git a/server/src/game/role/werewolf.rs b/server/src/game/role/werewolf.rs index f1379ae30..bdc663205 100644 --- a/server/src/game/role/werewolf.rs +++ b/server/src/game/role/werewolf.rs @@ -66,12 +66,7 @@ impl RoleStateImpl for Werewolf { }else{ //rampage at home - for other_player_ref in - actor_ref.lookout_seen_players(game).into_iter().filter(|p|actor_ref!=*p) - .collect::>() - { - other_player_ref.try_night_kill(actor_ref, game, GraveKiller::Role(Role::Werewolf), 2, true); - } + if actor_ref.night_jailed(game){ //kill all jailors NOT trying to execute me @@ -84,6 +79,13 @@ impl RoleStateImpl for Werewolf { player_ref.try_night_kill(actor_ref, game, GraveKiller::Role(Role::Werewolf), 2, true); } } + }else{ + for other_player_ref in + actor_ref.lookout_seen_players(game).into_iter().filter(|p|actor_ref!=*p) + .collect::>() + { + other_player_ref.try_night_kill(actor_ref, game, GraveKiller::Role(Role::Werewolf), 2, true); + } } } }, @@ -122,11 +124,18 @@ impl RoleStateImpl for Werewolf { } //track the scent of players - - - - - + let RoleState::Werewolf(werewolf) = actor_ref.role_state(game) else { + unreachable!("Werewolf role state should be Werewolf") + }; + let tracked_players = werewolf.tracked_players.clone(); + tracked_players.into_iter().for_each(|player_ref|{ + actor_ref.push_night_message(game, + ChatMessage::WerewolfTrackingResult{ + tracked_player: player_ref.index(), + players: player_ref.tracker_seen_visits(game).into_iter().map(|p|p.target.index()).collect() + } + ); + }); }, _ => {} } From 4dce56a4e5862a5e3f2cfea04a950a046cad38a4 Mon Sep 17 00:00:00 2001 From: Sam Maselli Date: Thu, 4 Jan 2024 06:17:14 -0500 Subject: [PATCH 3/4] werewolf wiki cont. --- client/src/resources/lang/en_us.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/resources/lang/en_us.json b/client/src/resources/lang/en_us.json index cd2717fca..417d4e20a 100644 --- a/client/src/resources/lang/en_us.json +++ b/client/src/resources/lang/en_us.json @@ -596,8 +596,8 @@ "wiki.entry.role.arsonist.attributes":"* When you ignite you deal an unstoppable attack to all doused players\n* You douse all visitors to you\n* You can't be doused\n* If you are jailed, you will douse all Jailors and will still douse visitors to you\n* You do not douse visiting players who visited using only an astral visit\n* When you ignite, the kill is astral\n* You will know who is doused", "wiki.entry.role.werewolf.title": "Werewolf", - "wiki.entry.role.werewolf.abilities":"* On full moon nights target a player to kill them and every visitor to them \n* On non full moon nights Target a player to gain their scent, and gain the ability to see who they visit every following night, you will also gain the scent of all players who visit you on non full moon nights\n* If you target nobody on a full moon night, you kill all visitors to yourself\n* On non full moon nights, you will look like the jester to investigative roles", - "wiki.entry.role.werewolf.attributes":"* Full moon nights are every night except night one and three\n* You have a powerfull attack\n* If you are jailed then you will attack all Jailors who did not execute you, but not their visitors\n* You will still attack or gain the scent of visits to you when you are roleblocked but not when you are jailed\n* You will know who you have the scent of\n* You dont kill or gain the scent of astral visitors\n* The visit you do to the visitors to your target when you kill or gain their scent is astral", + "wiki.entry.role.werewolf.abilities":"* On full moon nights target a player to kill them and every visitor to them \n* On non full moon nights target a player to gain their scent, and gain the ability to see who they visit every following night, you will also gain the scent of all players who visit you on non full moon nights\n* If you target nobody on a full moon night, you kill all visitors to yourself\n* On non full moon nights, you will look like the jester to investigative roles", + "wiki.entry.role.werewolf.attributes":"* Full moon nights are every night except night one and three\n* You have a powerfull attack\n* If you are jailed then you will attack all Jailors who did not execute you, but not their visitors\n* You will still attack or gain the scent of visits to you when you are roleblocked but not when you are jailed\n* You will know who you have the scent of\n* You still track the scent of players after you die\n* You dont kill or gain the scent of astral visitors\n* The visit you do to the visitors to your target when you kill or gain their scent is astral", "wiki.entry.role.death.title": "Death", "wiki.entry.role.death.abilities":"* Target a player on the night they die to collect a soul\n* Once you've collected six souls, everyone is notified, and at the start of the next night, you kill everyone, and win.", From d36b0a4fc9576a08d46bff1b86bffff053b5c8f7 Mon Sep 17 00:00:00 2001 From: Sam Maselli Date: Thu, 4 Jan 2024 06:35:13 -0500 Subject: [PATCH 4/4] exclude werewolf --- client/src/resources/excludedRolePresets.json | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/client/src/resources/excludedRolePresets.json b/client/src/resources/excludedRolePresets.json index ef20fcebd..aec8d1933 100644 --- a/client/src/resources/excludedRolePresets.json +++ b/client/src/resources/excludedRolePresets.json @@ -83,6 +83,10 @@ "role": "politician" }, + { + "type": "exact", + "role": "werewolf" + }, { "type": "exact", "role": "death" @@ -138,7 +142,11 @@ "type": "exact", "role": "politician" }, - + + { + "type": "exact", + "role": "werewolf" + }, { "type": "exact", "role": "death"