Skip to content

Commit

Permalink
Merge pull request #401 from ItsSammyM/werewolf
Browse files Browse the repository at this point in the history
Werewolf
  • Loading branch information
ItsSammyM authored Jan 4, 2024
2 parents 3ad6fe8 + 675b53e commit d59fe3d
Show file tree
Hide file tree
Showing 13 changed files with 224 additions and 12 deletions.
12 changes: 7 additions & 5 deletions client/src/components/ChatMessage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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":
Expand Down Expand Up @@ -467,9 +470,8 @@ export type ChatMessage = {
type: "witchMessage",
message: ChatMessage
} | {
type: "arsonistCleanedSelf"
} | {
type: "arsonistDousedPlayers",
type: "werewolfTrackingResult",
trackedPlayer: PlayerIndex
players: PlayerIndex[]
} | {
type: "jesterWon"
Expand Down
1 change: 1 addition & 0 deletions client/src/game/gameState.d.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ export type PhaseTimes = {
}
export type Tag =
| "godfatherBackup"
| "werewolfTracked"
| "doused"
| "hexed"
| "necronomicon"
Expand Down
3 changes: 3 additions & 0 deletions client/src/game/roleState.d.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ Doomsayer
role: "politician"
} | {
role: "arsonist"
} | {
role: "werewolf"
trackedPlayers: PlayerIndex[]
} | {
role: "death",
souls: number
Expand Down
10 changes: 9 additions & 1 deletion client/src/resources/excludedRolePresets.json
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,10 @@
"role": "politician"
},

{
"type": "exact",
"role": "werewolf"
},
{
"type": "exact",
"role": "death"
Expand Down Expand Up @@ -138,7 +142,11 @@
"type": "exact",
"role": "politician"
},


{
"type": "exact",
"role": "werewolf"
},
{
"type": "exact",
"role": "death"
Expand Down
13 changes: 12 additions & 1 deletion client/src/resources/lang/en_us.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -312,6 +317,7 @@

"tag.godfatherBackup":"🌹",
"tag.executionerTarget":"🎯",
"tag.werewolfTracked": "🐾",
"tag.necronomicon":"📓",
"tag.doused":"🔥",
"tag.insane":"🪬",
Expand Down Expand Up @@ -417,6 +423,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.",
Expand Down Expand Up @@ -587,7 +594,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 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.",
Expand Down
10 changes: 10 additions & 0 deletions client/src/resources/roles.json
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,16 @@
"maxCount": null,
"largeRoleSpecificMenu": false
},
"werewolf": {
"factionAlignment": "neutralKilling",
"suspicious": true,
"roleblockable": true,
"defense": 1,
"witchable": true,
"endGameCondition": "SingleRole",
"maxCount": null,
"largeRoleSpecificMenu": false
},
"vampire": {
"factionAlignment": "neutralChaos",
"suspicious": true,
Expand Down
3 changes: 1 addition & 2 deletions client/src/resources/styling/chatMessage.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,6 @@
"consigliereResult": "result",
"silenced": "warning",
"mediumSeance": "special",
"arsonistCleanedSelf": "result",
"arsonistDousedPlayers": "result",
"targetWasAttacked": "result",
"youWereProtected": "result",
"executionerWon": "result",
Expand All @@ -56,6 +54,7 @@
"witchMessage": "result",
"witchTargetImmune": "result",
"witchedYou": "result",
"werewolfTrackingResult": "result",
"youSurvivedAttack": "result",
"doomsayerFailed": "result",
"doomsayerWon": "result"
Expand Down
4 changes: 3 additions & 1 deletion server/src/game/chat/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

use serde::{Serialize, Deserialize};
use crate::game::{grave::Grave, role::Role, player::{PlayerIndex, PlayerReference}, verdict::Verdict, phase::PhaseType, Game};

Expand Down Expand Up @@ -158,6 +157,9 @@ pub enum ChatMessage {
WitchedYou { immune: bool },
WitchMessage{message: Box<ChatMessage>},

#[serde(rename_all = "camelCase")]
WerewolfTrackingResult{tracked_player: PlayerIndex, players: Vec<PlayerIndex>},

JesterWon,
// TODO Rename ExecutionerYouWon
ExecutionerWon,
Expand Down
1 change: 1 addition & 0 deletions server/src/game/end_game_condition.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ pub enum EndGameCondition {
Town,
Vampire,
Arsonist,
Werewolf,

None
}
Expand Down
2 changes: 1 addition & 1 deletion server/src/game/role/doomsayer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
}
}
Expand Down
1 change: 1 addition & 0 deletions server/src/game/role/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ macros::roles! {
Politician : politician,

Arsonist : arsonist,
Werewolf : werewolf,
Death : death,

Vampire : vampire,
Expand Down
172 changes: 172 additions & 0 deletions server/src/game/role/werewolf.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
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<PlayerReference>,
}

pub(super) const FACTION_ALIGNMENT: FactionAlignment = FactionAlignment::NeutralKilling;
pub(super) const MAXIMUM_COUNT: Option<u8> = 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<Team> {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::<Vec<PlayerReference>>()
{
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



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);
}
}
}else{
for other_player_ref in
actor_ref.lookout_seen_players(game).into_iter().filter(|p|actor_ref!=*p)
.collect::<Vec<PlayerReference>>()
{
other_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<PlayerReference> = 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
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()
}
);
});
},
_ => {}
}
}
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<PlayerReference>) -> Vec<Visit> {
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<ChatGroup> {
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<ChatGroup> {
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){
}
}
4 changes: 3 additions & 1 deletion server/src/game/tag.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ use serde::Serialize;
pub enum Tag{
GodfatherBackup,
Doused,
WerewolfTracked,
ExecutionerTarget,

Hexed,
Necronomicon,
ExecutionerTarget,
Insane
}

1 comment on commit d59fe3d

@vercel
Copy link

@vercel vercel bot commented on d59fe3d Jan 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

mafia-game – ./

mafia-game-git-00x-main-itssammym.vercel.app
mafia-game-itssammym.vercel.app
mafia-game.vercel.app

Please sign in to comment.