Skip to content

Commit

Permalink
Merge pull request #618 from mafia-rust/engineer-refactor
Browse files Browse the repository at this point in the history
Entire engineer refactor
  • Loading branch information
ItsSammyM authored Apr 17, 2024
2 parents cd91839 + 6996c09 commit bc69c30
Show file tree
Hide file tree
Showing 14 changed files with 119 additions and 50 deletions.
5 changes: 5 additions & 0 deletions client/src/components/ChatMessage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,8 @@ export function translateChatMessage(message: ChatMessageVariant, playerNames?:
default:
return translate("chatMessage.cultSacrificesRequired", message.required);
}
case "engineerRemoveTrap":
return translate("chatMessage.engineerRemoveTrap." + (message.unset ? "remove" : "keep"));
case "playerWithNecronomicon":
return translate("chatMessage.playerWithNecronomicon", playerNames[message.playerIndex]);
case "deputyShotYou":
Expand Down Expand Up @@ -634,6 +636,9 @@ export type ChatMessageVariant = {
} | {
type: "godfatherBackupKilled",
backup: PlayerIndex
} | {
type: "engineerRemoveTrap",
unset: boolean
} | {
type: "silenced"
} | {
Expand Down
1 change: 1 addition & 0 deletions client/src/game/gameManager.d.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ export type GameManager = {
sendSetForgerWill(role: Role | null, will: string): void;
sendSetAuditorChosenOutline(index: number): void;
sendSetOjoAction(action: OjoAction): void;
sendSetEngineerShouldUnset(unset: boolean): void;

sendVoteFastForwardPhase(fastForward: boolean): void;

Expand Down
6 changes: 6 additions & 0 deletions client/src/game/gameManager.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,12 @@ export function createGameManager(): GameManager {
action: action
});
},
sendSetEngineerShouldUnset(unset) {
this.server.sendPacket({
type: "setEngineerShouldUnset",
unset: unset
});
},

sendVoteFastForwardPhase(fastForward: boolean) {
this.server.sendPacket({
Expand Down
3 changes: 3 additions & 0 deletions client/src/game/packet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,9 @@ export type ToServerPacket = {
} | {
type: "setOjoAction",
action: OjoAction
} | {
type: "setEngineerShouldUnset",
unset: boolean
} | {
type: "voteFastForwardPhase",
fastForward: boolean
Expand Down
3 changes: 2 additions & 1 deletion client/src/game/roleState.d.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ export type RoleState = {
} | {
type: "bouncer"
} | {
type: "engineer"
type: "engineer",
trap: {type: "dismantled"} | {type: "ready"} | {type: "set", target: PlayerIndex, shouldUnset: boolean}
} | {
type: "vigilante",
state: {type:"notLoaded"} | {type:"willSuicide"} | {type:"loaded",bullets:number} | {type:"suicided"}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,24 @@ export default class SmallRoleSpecificMenu extends React.Component<SmallRoleSpec
return <StyledText>{translate("role.doctor.roleDataText", this.state.gameState.clientState.roleState.selfHealsRemaining)}</StyledText>;
case "bodyguard":
return <StyledText>{translate("role.bodyguard.roleDataText", this.state.gameState.clientState.roleState.selfShieldsRemaining)}</StyledText>;
case "engineer":
return <>
<div>
<StyledText>{translate("role.engineer.roleDataText." + this.state.gameState.clientState.roleState.trap.type)}</StyledText>
</div>
{
this.state.gameState.clientState.roleState.trap.type === "set" &&
this.state.gameState.phaseState.type === "night" &&
<button className={this.state.gameState.clientState.roleState.trap.shouldUnset?"highlighted":""} onClick={()=>{
if(
this.state.gameState.clientState.type === "player" &&
this.state.gameState.clientState.roleState?.type === "engineer" &&
this.state.gameState.clientState.roleState.trap.type === "set"
)
GAME_MANAGER.sendSetEngineerShouldUnset(!this.state.gameState.clientState.roleState.trap.shouldUnset);
}}>{translate("role.engineer.roleDataText.unset")}</button>
}
</>;
case "vigilante":
switch(this.state.gameState.clientState.roleState.state.type){
case "willSuicide":
Expand Down
17 changes: 12 additions & 5 deletions client/src/resources/lang/en_us.json
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,10 @@
"role.engineer.name:var.0": "Engineers",
"role.engineer.target": "Set Trap",
"role.engineer.dayTarget": "Special",
"role.engineer.roleDataText.dismantled": "The trap is dismantled",
"role.engineer.roleDataText.ready": "The trap is ready",
"role.engineer.roleDataText.set": "The trap is currently set",
"role.engineer.roleDataText.unset": "Remove trap",

"role.mayor.name": "Mayor",
"role.mayor.name:var.0": "Mayors",
Expand Down Expand Up @@ -629,8 +633,9 @@
"chatMessage.copAttackedVisitor":"You attacked a player who visited your target.",
"chatMessage.engineerYouAttackedVisitor": "Your trap attacked someone!",
"chatMessage.engineerVisitorsRole": "A \\0 visited the trapped player.",
"chatMessage.trapState.dismantled": "Your trap is currently dismantled.",
"chatMessage.trapState.set": "Your trap is currently set.",
"chatMessage.trapState.dismantled": "Your trap is currently dismantled. You can't set it yet.",
"chatMessage.trapState.ready": "Your trap is currently ready. It can be set and potentially triggered tonight.",
"chatMessage.trapState.set": "Your trap is currently set. You can dismantle it or wait for it to trigger.",
"chatMessage.vigilanteSuicide":"You committed suicide over the guilt of killing a townie.",
"chatMessage.targetWasAttacked":"Your target was attacked.",
"chatMessage.youWereProtected": "You were attacked but someone protected you.",
Expand All @@ -656,6 +661,8 @@
"chatMessage.cultSacrificesRequired": "The cult requires \\0 more sacrifices to convert.",
"chatMessage.cultSacrificesRequired.1": "The cult requires 1 more sacrifice to convert.",
"chatMessage.cultSacrificesRequired.0": "The cult has enough sacrifices to convert.",
"chatMessage.engineerRemoveTrap.remove": "You have decided to remove your trap tonight.",
"chatMessage.engineerRemoveTrap.keep": "You have decided to keep your trap set.",
"chatMessage.phaseFastForwarded": "The phase has been fast forwarded.",

"chatMessage.doomsayerFailed": "At least one of your guesses was incorrect or invalid.",
Expand Down Expand Up @@ -938,10 +945,10 @@
"wiki.article.role.bouncer.attributes":"* Your target isn't told they are the target of a bouncer",
"wiki.article.role.bouncer.extra":"* You can't select yourself\n* You don't roleblock yourself or roleblock immune roles\n* Your ability fails if your target is in jail",

"wiki.article.role.engineer.guide": "- At night, select a player to set a trap on them\n - A player with a trap is protected\n - The trap is triggered if anyone visits the player with the trap\n- When the trap is triggered\n - You are told the role of every visitor who triggered the trap, and it dismantles\n - If someone attacks the player with the trap, they are attacked, and the player who you protect is told they were protected\n- The trap doesn't work on the night it is set, but it works the night after and all following nights\n- You can select yourself to dismantle the trap\n - On the night you dismantle your trap, it is still active, you can dismantle the trap and have it trigger in the same night.\n\nYou should try to put the trap on townies to protect them.",
"wiki.article.role.engineer.abilities":"* While the trap is dismantled\n * Visit a player to set a trap on them\n * You can't select yourself\n* While the trap is set\n * Visit yourself to dismantle the trap\n * You can only select yourself\n * The player with the trap on them is protected\n * If a player visits the player with the trap, the trap will trigger and dismantle. You will be told which roles trigger the trap and indirectly attack all players attacking the trapped player.",
"wiki.article.role.engineer.guide": "Set a trap on a player, protecting them and killing attackers\n- It takes one night for the trap to be ready, it is not ready on the first night\n- When the trap is ready, you can set it on a player\n\nThe trap will trigger if anyone visits the player with the trap on them\n- You will attack any players who attack the trapped player\n- You will be told the role of every visitor to the trapped player\n- The trap will be dismantled the next night after it triggers\n- If you dismantle the trap manually, you can use it the next night",
"wiki.article.role.engineer.abilities":"* While the trap is dismantled\n * You can't select anyone\n * If you aren't roleblocked, the trap will become ready the next night\n* While the trap is ready\n * Visit a player to set the trap\n * You can't select yourself\n* While the trap is set\n * The player with the trap on them is protected\n * If a player visits the player with the trap, the trap will trigger\n * You can choose to manually dismantle the trap, it only works if you aren't roleblocked\n * You can't select anyone\n* When the trap triggers\n * You are told the roles of the players who visited the trapped player\n * You indirectly attack all players attacking the trapped player\n * The trap dismantles, the next night you wait again",
"wiki.article.role.engineer.attributes":"* You can only have one trap set at a time\n* The trap still triggers even if you are roleblocked or dead\n* When your trap is triggered, you will be told which roles visited the trapped player, even if those roles didn't attack the trapped player\n* The trap will still trigger even if none of the trapped players visitors are attackers\n* The trap will only attack attackers to the trapped player\n* You have an indirect, armor-piercing attack",
"wiki.article.role.engineer.extra":"* You can set a trap on jailed players\n* When the player with the trap is attacked, you are told they are attacked, and they are told they are protected",
"wiki.article.role.engineer.extra":"* You can set a trap on jailed players\n* When the player with the trap is attacked, you are told they are attacked, and they are told they are protected\n* You can never activate your own trap\n* It is possible to set the trap on yourself with the help of other roles like transporter\n* If you stop being the engineer your trap disappears",

"wiki.article.role.escort.guide": "- At night, select a player to roleblock them, stopping them from using their ability\n- Your target is told they were roleblocked\n\n_For example_\n- An escort selects the godfather, so the godfather can't kill anyone, and nobody dies\n\nYou should try to choose evil players, especially the ones with powerful abilities",
"wiki.article.role.escort.abilities":"* Visit a player to roleblock them",
Expand Down
1 change: 1 addition & 0 deletions client/src/resources/roles.json
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@
"canBeConvertedTo": [],
"chatMessages":[
{"type": "trapState", "state":{"type":"dismantled"}},
{"type": "trapState", "state":{"type":"ready"}},
{"type": "trapState", "state":{"type":"set"}},
{"type": "engineerYouAttackedVisitor"},
{"type": "engineerVisitorsRole", "role": "framer"},
Expand Down
1 change: 1 addition & 0 deletions client/src/resources/styling/chatMessage.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
"engineerVisitorsRole": "result",
"engineerYouAttackedVisitor": "result",
"trapState": "special",
"engineerRemoveTrap": "special",
"vigilanteSuicide": "result",
"targetsMessage": "result",
"targetIsPossessionImmune": "result",
Expand Down
3 changes: 3 additions & 0 deletions server/src/game/chat/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,9 @@ pub enum ChatMessageVariant {
GodfatherBackup{backup: Option<PlayerIndex>},
#[serde(rename_all = "camelCase")]
GodfatherBackupKilled{backup: PlayerIndex},

#[serde(rename_all = "camelCase")]
EngineerRemoveTrap{unset: bool},


#[serde(rename_all = "camelCase")]
Expand Down
12 changes: 11 additions & 1 deletion server/src/game/on_client_message.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use super::{
event::on_fast_forward::OnFastForward,
phase::{PhaseState, PhaseType},
player::{PlayerIndex, PlayerReference},
role::{Role, RoleState}, role_list::Faction,
role::{engineer::{Engineer, Trap}, Role, RoleState}, role_list::Faction,
spectator::spectator_pointer::{SpectatorIndex, SpectatorPointer},
Game
};
Expand Down Expand Up @@ -280,6 +280,16 @@ impl Game {
sender_player_ref.add_private_chat_message(self, ChatMessageVariant::OjoSelection { action })
}
},
ToServerPacket::SetEngineerShouldUnset { unset } => {
if let RoleState::Engineer(engineer) = sender_player_ref.role_state(self).clone(){
if let Trap::Set { target, ..} = engineer.trap {
sender_player_ref.set_role_state(self,
RoleState::Engineer(Engineer { trap: Trap::Set { target, should_unset: unset } })
);
sender_player_ref.add_private_chat_message(self, ChatMessageVariant::EngineerRemoveTrap { unset });
}
}
},
ToServerPacket::VoteFastForwardPhase { fast_forward } => {
sender_player_ref.set_fast_forward_vote(self, fast_forward);
}
Expand Down
89 changes: 49 additions & 40 deletions server/src/game/role/engineer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,23 @@ use super::{Priority, Role, RoleState, RoleStateImpl};
#[derive(Default, Clone, Serialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct Engineer {
trap: Trap
pub trap: Trap
}
#[derive(Default, Clone, Serialize, Debug)]
#[serde(tag = "type")]
#[serde(rename_all = "camelCase")]
pub enum Trap {
#[default]
Dismantled,
Set{target: PlayerReference}
Ready,
#[serde(rename_all = "camelCase")]
Set{target: PlayerReference, should_unset: bool}
}
impl Trap {
fn is_dismantled(&self) -> bool {
matches!(self, Trap::Dismantled)
}
fn state(&self) -> TrapState {
match self {
Trap::Dismantled => TrapState::Dismantled,
Trap::Ready => TrapState::Ready,
Trap::Set{..} => TrapState::Set
}
}
Expand All @@ -36,13 +38,15 @@ impl Trap {
pub enum TrapState {
#[default]
Dismantled,
Ready,
Set
}

//engineer prioritys
//tell player state

//protect, kill & investigate
//Set trap
//Set trap / ready up / choose to unset and bring to ready
//protect, kill & investigate, dismantle


pub(super) const FACTION: Faction = Faction::Town;
Expand All @@ -56,70 +60,75 @@ impl RoleStateImpl for Engineer {
fn do_night_action(self, game: &mut Game, actor_ref: PlayerReference, priority: Priority) {
match priority {
Priority::Heal => {
if let Trap::Set { target } = self.trap {
//upgrade state

if !actor_ref.night_roleblocked(game) {
match self.trap {
Trap::Dismantled => {
actor_ref.set_role_state(game, RoleState::Engineer(Engineer {trap: Trap::Ready}))
},
Trap::Ready => {
if let Some(visit) = actor_ref.night_visits(game).first(){
actor_ref.set_role_state(game, RoleState::Engineer(Engineer {trap: Trap::Set{target: visit.target, should_unset: false}}))
}
},
Trap::Set { should_unset: true, .. } => {
actor_ref.set_role_state(game, RoleState::Engineer(Engineer {trap: Trap::Ready}))
},
_ => {}
}
}

if let RoleState::Engineer(Engineer{trap: Trap::Set{target, ..}}) = actor_ref.role_state(game).clone(){
target.increase_defense_to(game, 2);
}
}
Priority::Kill => {
if let Trap::Set { target } = self.trap {
if let Trap::Set { target, .. } = self.trap {
for attacker in PlayerReference::all_players(game) {
if attacker.night_visits(game).iter().any(|visit| visit.target == target && visit.attack){
if
attacker.night_visits(game).iter().any(|visit| visit.target == target && visit.attack) &&
attacker != actor_ref
{
attacker.try_night_kill(actor_ref, game, crate::game::grave::GraveKiller::Role(Role::Engineer), 2, false);
actor_ref.push_night_message(game, ChatMessageVariant::EngineerYouAttackedVisitor);
}
}
}
}
Priority::Investigative => {
if let Trap::Set { target } = self.trap {
if let Trap::Set { target, .. } = self.trap {

let mut should_dismantle = false;

if target.night_attacked(game){
actor_ref.push_night_message(game, ChatMessageVariant::TargetWasAttacked);
target.push_night_message(game, ChatMessageVariant::YouWereProtected);
}

for visitor in PlayerReference::all_players(game) {
if visitor.night_visits(game).iter().any(|visit|visit.target == target){
if
visitor.night_visits(game).iter().any(|visit|visit.target == target) &&
visitor != actor_ref
{
actor_ref.push_night_message(game, ChatMessageVariant::EngineerVisitorsRole { role: visitor.role(game) });
should_dismantle = true;
}
}
}
}
Priority::FinalPriority => {

let mut caught_role = false;
if let Trap::Set { target } = self.trap {
for visitor in PlayerReference::all_players(game) {
if visitor.night_visits(game).iter().any(|visit|visit.target == target){
caught_role = true;
break;
}
}
}


//if trap just triggered or manual dismantle, then dismantle
if
caught_role ||
actor_ref.night_visits(game).iter().any(|visit| visit.target == actor_ref)
{
actor_ref.set_role_state(game, RoleState::Engineer(Engineer {trap: Trap::Dismantled}));
}
//set trap
else if let Some(visit) = actor_ref.night_visits(game).first(){
if self.trap.is_dismantled(){
actor_ref.set_role_state(game, RoleState::Engineer(Engineer {trap: Trap::Set{target: visit.target}}));
if should_dismantle {
actor_ref.set_role_state(game, RoleState::Engineer(Engineer {trap: Trap::Dismantled}));
}
}

}
_ => {}
}
}
fn can_night_target(self, game: &Game, actor_ref: PlayerReference, target_ref: PlayerReference) -> bool {
(match self.trap {
Trap::Set { target } => actor_ref == target,
Trap::Dismantled => actor_ref != target_ref,
Trap::Dismantled => false,
Trap::Ready => actor_ref != target_ref,
Trap::Set { .. } => false,
}) &&
!actor_ref.night_jailed(game) &&
actor_ref.chosen_targets(game).is_empty() &&
Expand Down
1 change: 1 addition & 0 deletions server/src/packet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@ pub enum ToServerPacket{
SetForgerWill{ role: Option<Role>, will: String },
SetAuditorChosenOutline{index: u8},
SetOjoAction{action: OjoAction},
SetEngineerShouldUnset{unset: bool},


#[serde(rename_all = "camelCase")]
Expand Down
Loading

0 comments on commit bc69c30

Please sign in to comment.