Adds Coroner to Mafia & Bunch of Mafia changes (#92158)

Changeling chat is now at night, rather than :j saymode, and it is also
separated from normal messages as [CHANGELING CHAT]
Adds a new [DEAD CHAT], all dead players in Mafia are corpselocked and
talking will instead go to Dead chat.
The Chaplain's ability is now being able to hear Dead chat at night, and
being able to in turn speak to the Dead.
The Chaplain's old ability has been given to a new role, the Coroner.
"Pray" is now "Autopsy".
Deaths in the Mafia arena aren't broadcasted anymore, to lessen
annoyance to round observers.

Also updates role icons & some outfits, as well as some bug fixes I
encountered while messing with it on localhost. I also tried (but not
fully) to make Mafia games more modular and independent, so maybe in the
future we can have more than one Mafia game running at a time.

I am limited to 2 player testing for this, so it is very much possible
that there's some issues I haven't found from my local testing, let me
know if you find anything please.

Being dead in Mafia boots you out of the round regardless of how
invested you were, that kidna sucks so hopefully being able to still
contribute something to the game, or at least discuss it with other dead
players in your own chat, makes players feel important to the game
they're playing.

I have a previous attempt of this here -
https://github.com/tgstation/tgstation/pull/75879 - but it staled out.
This differs from that attempt, as only dead players from the Mafia game
can speak in dead chat, while the old attempt allowed anyone that was
observing a mafia sign post (so dead players from the game, but not
observing the post, weren't able to speak to Chaplains, making him very
hard to be useful especially since getting information like that across
is a little hard). Being corpselocked also prevents them from being able
to see who Changelings are by simply looking at who has maptext at
night, and keeps them more focused on the game being played.

🆑
add: Added a new role to Mafia; the Coroner, which takes the Chaplain's
ability to see dead people's roles.
add: Mafia Chaplains now speak with the dead at night instead, and the
dead are corpselocked to prevent cheating.
fix: Mafia's HoS doesn't kill himself when executing non-townies.
qol: You can now update your notes & send them in chat while dead, as
well as look up the descriptions of other roles.
/🆑
This commit is contained in:
John Willard
2025-07-30 05:59:30 -04:00
committed by Roxy
parent 3e2bd28b17
commit 2a637dd6bb
29 changed files with 241 additions and 155 deletions

View File

@@ -70,6 +70,7 @@
#define MAFIA_MEDAL_DETECTIVE "Detective"
#define MAFIA_MEDAL_PSYCHOLOGIST "Psychologist"
#define MAFIA_MEDAL_CHAPLAIN "Chaplain"
#define MAFIA_MEDAL_CORONER "Coroner"
#define MAFIA_MEDAL_MD "Medical Doctor"
#define MAFIA_MEDAL_OFFICER "Security Officer"
#define MAFIA_MEDAL_LAWYER "Lawyer"

View File

@@ -5,6 +5,8 @@
#define RADIO_EXTENSION "department specific"
#define RADIO_KEY "department specific key"
#define LANGUAGE_EXTENSION "language specific"
///This is a mob that is forcing us to say something, so we can use the mob typing the text for bans rather than the one speaking.
#define MANNEQUIN_CONTROLLED "mannequin controlled"
/// Message mod which contains a list of bonus "mutual understanding" to allow arbitrary understanding of any speech
#define LANGUAGE_MUTUAL_BONUS "language mutual bonus"
#define SAY_MOD_VERB "say_mod_verb"

View File

@@ -23,10 +23,16 @@
/datum/award/achievement/mafia/chaplain
name = "Chaplain Victory"
desc = "Useless... until the one night the thoughtfeeder confidently claims themselves as detective. Mafia's true bullshit detector."
desc = "The curse finally broken."
database_id = MAFIA_MEDAL_CHAPLAIN
icon_state = "town"
/datum/award/achievement/mafia/coroner
name = "Coroner Victory"
desc = "Useless... until the one night the thoughtfeeder confidently claims themselves as detective. Mafia's true bullshit detector."
database_id = MAFIA_MEDAL_CORONER
icon_state = "town"
/datum/award/achievement/mafia/md
name = "Medical Doctor Victory"
desc = "Congratulations on learning how to not talk!"

View File

@@ -24,31 +24,34 @@
///How fast the game will speed up when half the players are gone.
#define MAFIA_SPEEDUP_INCREASE 2
#define MAFIA_TEAM_TOWN "town"
#define MAFIA_TEAM_MAFIA "mafia"
#define MAFIA_TEAM_SOLO "solo"
//'Team' flags, used to know which alliance your role is part of.
#define MAFIA_TEAM_TOWN (1<<0)
#define MAFIA_TEAM_MAFIA (1<<1)
#define MAFIA_TEAM_SOLO (1<<2)
//those part of the 'dead' faction can hear dead chat, not a "faction" of players.
#define MAFIA_TEAM_DEAD (1<<3)
//types of town roles for random setup gen
/// Add this if you don't want a role to be a choice in the selection
#define TOWN_OVERFLOW "overflow"
#define TOWN_OVERFLOW "town overflow"
/// roles that learn info about others in the game (chaplain, detective, psych)
#define TOWN_INVEST "invest"
#define TOWN_INVEST "town invest"
/// roles that keep other roles safe (doctor, sec officer, and weirdly enough lawyer counts)
#define TOWN_PROTECT "protect"
#define TOWN_PROTECT "town protect"
/// roles that are only there to kill bad guys.
#define TOWN_KILLING "killing"
#define TOWN_KILLING "town killing"
/// roles that don't fit into anything else (hop)
#define TOWN_SUPPORT "support"
#define TOWN_SUPPORT "town support"
//other types (mafia team, neutrals)
/// normal vote kill changelings
#define MAFIA_REGULAR "regular"
#define MAFIA_REGULAR "mafia"
/// every other changeling role that has extra abilities
#define MAFIA_SPECIAL "special"
#define MAFIA_SPECIAL "mafia special"
/// role that wins solo that nobody likes
#define NEUTRAL_KILL "kill"
#define NEUTRAL_KILL "neutral killing"
/// role that upsets the game aka obsessed, usually worse for town than mafia but they can vote against mafia
#define NEUTRAL_DISRUPT "disrupt"
#define NEUTRAL_DISRUPT "neutral chaos"
//role flags (special status of roles like detection immune)
///to all forms of detection, shows themselves as an assistant.

View File

@@ -17,12 +17,12 @@
///The mafia role this ability is targeting, if necessary.
var/datum/mafia_role/target_role
/datum/mafia_ability/New(datum/mafia_controller/game, datum/mafia_role/host_role)
/datum/mafia_ability/New(datum/mafia_role/host_role)
. = ..()
src.host_role = host_role
if(action_priority)
RegisterSignal(game, action_priority, PROC_REF(perform_action_target))
RegisterSignal(game, COMSIG_MAFIA_NIGHT_END, PROC_REF(clean_action_refs))
RegisterSignal(host_role.mafia_game_controller, action_priority, PROC_REF(perform_action_target))
RegisterSignal(host_role.mafia_game_controller, COMSIG_MAFIA_NIGHT_END, PROC_REF(clean_action_refs))
/datum/mafia_ability/Destroy(force)
host_role = null
@@ -35,7 +35,7 @@
return FALSE
/**
* Called when refs need to be cleared, when the target is no longer set.
* Called when refs need to be cleared, the last thing that is called in a night cycle.
*/
/datum/mafia_ability/proc/clean_action_refs(datum/mafia_controller/game)
SIGNAL_HANDLER
@@ -51,16 +51,16 @@
* potential_target - The player we are attempting to validate the action on.
* silent - Whether to give feedback to the player about why the action cannot be used.
*/
/datum/mafia_ability/proc/validate_action_target(datum/mafia_controller/game, datum/mafia_role/potential_target, silent = FALSE)
/datum/mafia_ability/proc/validate_action_target(datum/mafia_role/potential_target, silent = FALSE)
SHOULD_CALL_PARENT(TRUE)
if(game.phase != valid_use_period)
if(host_role.game_status == MAFIA_DEAD)
return FALSE
if(host_role.mafia_game_controller.phase != valid_use_period)
return FALSE
if(host_role.role_flags & ROLE_ROLEBLOCKED)
host_role.send_message_to_player(span_warning("You were roleblocked!"))
return FALSE
if(host_role.game_status == MAFIA_DEAD)
return FALSE
if(potential_target)
if(use_flags & CAN_USE_ON_DEAD)
@@ -95,9 +95,7 @@
if(!using_ability)
return FALSE
if(host_role.game_status == MAFIA_DEAD)
return FALSE
if(!validate_action_target(game, target_role))
if(!validate_action_target(target_role))
return FALSE
if(target_role)
@@ -114,8 +112,8 @@
* Sets the ability's target, which will cause the action to be performed on them at the end of the night.
* Subtypes can override this for things like self-abilities (such as shooting visitors).
*/
/datum/mafia_ability/proc/set_target(datum/mafia_controller/game, datum/mafia_role/new_target)
if(!validate_action_target(game, new_target))
/datum/mafia_ability/proc/set_target(datum/mafia_role/new_target)
if(!validate_action_target(new_target))
return FALSE
var/feedback_text = "You will %WILL_PERFORM% [ability_action]%SELF%"

View File

@@ -0,0 +1,18 @@
/**
* Autopsy
*
* During the night, choose someone to check their role.
*/
/datum/mafia_ability/autopsy
name = "Autopsy"
ability_action = "perform an autopsy on"
use_flags = CAN_USE_ON_OTHERS|CAN_USE_ON_DEAD
/datum/mafia_ability/autopsy/perform_action_target(datum/mafia_controller/game, datum/mafia_role/day_target)
. = ..()
if(!.)
return FALSE
to_chat(host_role.body, span_warning("Your autopsy report on [target_role.body.real_name] \
reveals their role was <b>[target_role.name]<b>."))
return TRUE

View File

@@ -1,18 +0,0 @@
/**
* Pray
*
* During the night, revealing someone will announce their role when day comes.
* This is one time use, we'll delete ourselves once done.
*/
/datum/mafia_ability/seance
name = "Seance"
ability_action = "commune with the spirit of"
use_flags = CAN_USE_ON_OTHERS|CAN_USE_ON_DEAD
/datum/mafia_ability/seance/perform_action_target(datum/mafia_controller/game, datum/mafia_role/day_target)
. = ..()
if(!.)
return FALSE
host_role.send_message_to_player(span_warning("You invoke spirit of [target_role.body.real_name] and learn their role was <b>[target_role.name]<b>."))
return TRUE

View File

@@ -8,7 +8,7 @@
ability_action = "send any visitors home with buckshot tonight"
use_flags = CAN_USE_ON_SELF
/datum/mafia_ability/attack_visitors/set_target(datum/mafia_controller/game, datum/mafia_role/new_target)
/datum/mafia_ability/attack_visitors/set_target(datum/mafia_role/new_target)
. = ..()
if(!.)
return FALSE

View File

@@ -21,7 +21,7 @@
host_role.send_message_to_player(span_danger("Your attempt at killing [target_role.body.real_name] was prevented!"))
else
target_role.send_message_to_player(span_userdanger("You have been [attack_action] \a [host_role.name]!"))
if(honorable && (target_role.team != MAFIA_TEAM_TOWN))
if(honorable && (target_role.team & MAFIA_TEAM_TOWN))
host_role.send_message_to_player(span_userdanger("You have killed an innocent crewmember. You will die tomorrow night."))
RegisterSignal(game, COMSIG_MAFIA_SUNDOWN, PROC_REF(internal_affairs))
return TRUE

View File

@@ -13,7 +13,7 @@
///The message sent when you've successfully saved someone.
var/saving_message = "someone nursed you back to health"
/datum/mafia_ability/heal/set_target(datum/mafia_controller/game, datum/mafia_role/new_target)
/datum/mafia_ability/heal/set_target(datum/mafia_role/new_target)
. = ..()
if(!.)
return FALSE

View File

@@ -13,7 +13,7 @@
///Amount of vests that can be used until the power deletes itself.
var/charges = STARTING_VEST_AMOUNT
/datum/mafia_ability/vest/set_target(datum/mafia_controller/game, datum/mafia_role/new_target)
/datum/mafia_ability/vest/set_target(datum/mafia_role/new_target)
. = ..()
if(!.)
return FALSE

View File

@@ -0,0 +1,24 @@
/**
* Seance
*
* An ability that doesn't give you any actions, instead grants the ability to speak with the dead during the Night.
*/
/datum/mafia_ability/seance
name = "Speak with the Dead"
action_priority = null
use_flags = NONE
/**
* handle_message
*
* During the night, Seancers speaking will instead be talking to deadchat.
*/
/datum/mafia_ability/seance/handle_speech(datum/source, list/speech_args)
. = ..()
if(host_role.mafia_game_controller.phase != MAFIA_PHASE_NIGHT)
return FALSE
var/message = span_changeling("<b>\[DEAD CHAT - CHAPLAIN\] [source]</b>: [html_decode(speech_args[SPEECH_MESSAGE])]")
host_role.mafia_game_controller.send_message(message, team = MAFIA_TEAM_DEAD)
speech_args[SPEECH_MESSAGE] = ""
return TRUE

View File

@@ -34,28 +34,25 @@
game.send_message(span_danger("[host_role.body.real_name] was selected to attack [target_role.body.real_name] tonight!"), MAFIA_TEAM_MAFIA)
return TRUE
/datum/mafia_ability/changeling_kill/set_target(datum/mafia_controller/game, datum/mafia_role/new_target)
if(new_target.team == MAFIA_TEAM_MAFIA)
/datum/mafia_ability/changeling_kill/set_target(datum/mafia_role/new_target)
if(new_target.team & MAFIA_TEAM_MAFIA)
return FALSE
if(!validate_action_target(game, new_target))
if(!validate_action_target(new_target))
return FALSE
using_ability = TRUE
game.vote_for(host_role, new_target, "Mafia", MAFIA_TEAM_MAFIA)
host_role.mafia_game_controller.vote_for(host_role, new_target, "Mafia", MAFIA_TEAM_MAFIA)
/**
* handle_message
* handle_speech
*
* During the night, Changelings talking will instead redirect it to Changeling chat.
*/
/datum/mafia_ability/changeling_kill/handle_speech(datum/source, list/speech_args)
. = ..()
var/datum/mafia_controller/mafia_game = GLOB.mafia_game
if(!mafia_game)
return FALSE
if (mafia_game.phase != MAFIA_PHASE_NIGHT)
if (host_role.mafia_game_controller.phase != MAFIA_PHASE_NIGHT)
return FALSE
var/phrase = html_decode(speech_args[SPEECH_MESSAGE])
mafia_game.send_message(span_changeling("<b>[host_role.body.real_name]:</b> [phrase]"), MAFIA_TEAM_MAFIA)
var/message = span_changeling("<b>\[CHANGELING CHAT\] [source]</b>: [html_decode(speech_args[SPEECH_MESSAGE])]")
host_role.mafia_game_controller.send_message(message, MAFIA_TEAM_MAFIA)
speech_args[SPEECH_MESSAGE] = ""
return TRUE

View File

@@ -88,7 +88,7 @@ GLOBAL_LIST_INIT(mafia_role_by_alignment, setup_mafia_role_by_alignment())
/proc/setup_mafia_roles_by_name()
var/list/rolelist_dict = list()
for(var/datum/mafia_role/mafia_role as anything in typesof(/datum/mafia_role))
rolelist_dict[initial(mafia_role.name) + " ([uppertext(initial(mafia_role.team))])"] = mafia_role
rolelist_dict[initial(mafia_role.name) + " ([uppertext(initial(mafia_role.role_type))])"] = mafia_role
return rolelist_dict
/proc/setup_mafia_role_by_alignment()
@@ -170,11 +170,23 @@ GLOBAL_LIST_INIT(mafia_role_by_alignment, setup_mafia_role_by_alignment())
role.player_pda = selected_player
ready_ghosts_and_pdas -= selected_player
///Sends a global message to all players, or just 'team' if set.
/**
* send_message
* By default, this will send a message to every single role in the game, and putting it in their role message history
* to view on their PDA/Mafia panel.
* Args:
* - msg: The message being sent.
* - team: A specific team flag that will receive the message, so people not part of it will not get it. Ex: Changeling-only messages.
* - log_only: Will not send the message to the player's chat, only their PDA/Mafia panel, for messages that aren't
* needed to be flooding their chat for people who are there physically, such as Day/Night starting.
*/
/datum/mafia_controller/proc/send_message(msg, team, log_only = FALSE)
for(var/datum/mafia_role/role as anything in all_roles)
if(team && role.team != team)
if(team && !(role.team & team))
continue
//people who can "hear" the dead, but are alive, can only hear at night.
if((team & MAFIA_TEAM_DEAD) && role.game_status == MAFIA_ALIVE && phase != MAFIA_PHASE_NIGHT)
return
role.role_messages += msg
if(!log_only)
to_chat(role.body, msg)
@@ -336,20 +348,21 @@ GLOBAL_LIST_INIT(mafia_role_by_alignment, setup_mafia_role_by_alignment())
var/town_can_kill = FALSE
for(var/datum/mafia_role/R as anything in living_roles)
switch(R.team)
if(MAFIA_TEAM_MAFIA)
living_mafia += R
if(MAFIA_TEAM_TOWN)
living_town += R
anti_mafia_power += R.vote_power
//the game cannot autoresolve with killing roles (unless a solo wins anyways, like traitors who are immune)
if(R.role_flags & ROLE_CAN_KILL)
town_can_kill = TRUE
if(MAFIA_TEAM_SOLO)
living_neutrals += R
anti_mafia_power += R.vote_power
if(R.role_flags & ROLE_CAN_KILL)
neutral_killers += R
if(R.team & MAFIA_TEAM_MAFIA)
living_mafia += R
else if(R.team & MAFIA_TEAM_TOWN)
living_town += R
anti_mafia_power += R.vote_power
//the game cannot autoresolve with killing roles (unless a solo wins anyways, like traitors who are immune)
if(R.role_flags & ROLE_CAN_KILL)
town_can_kill = TRUE
else if(R.team & MAFIA_TEAM_SOLO)
living_neutrals += R
anti_mafia_power += R.vote_power
if(R.role_flags & ROLE_CAN_KILL)
neutral_killers += R
else
stack_trace("[R] somehow lacks a faction while alive in a Mafia match!")
if(living_mafia.len && living_town.len && living_neutrals.len)
return FALSE
@@ -581,7 +594,7 @@ GLOBAL_LIST_INIT(mafia_role_by_alignment, setup_mafia_role_by_alignment())
if(phase != MAFIA_PHASE_VOTING)
return
var/v = get_vote_count(get_role_player(source),"Day")
var/v = get_vote_count(get_role_player(source), "Day")
var/mutable_appearance/MA = mutable_appearance('icons/obj/mafia.dmi',"vote_[v > 12 ? "over_12" : v]")
overlay_list += MA

View File

@@ -17,6 +17,7 @@
"desc" = user_role.desc,
"hud_icon" = user_role.hud_icon,
"revealed_icon" = user_role.revealed_icon,
"role_dead" = (user_role.game_status == MAFIA_DEAD),
)
return data
@@ -68,7 +69,7 @@
if(user_role) //not observer
player_info["is_you"] = (role.body.real_name == user_role.body.real_name)
for(var/datum/mafia_ability/action as anything in user_role.role_unique_actions)
if(action.validate_action_target(src, potential_target = role, silent = TRUE))
if(action.validate_action_target(potential_target = role, silent = TRUE))
player_info["possible_actions"] += list(list("name" = action, "ref" = REF(action)))
data["players"] += list(player_info)
@@ -88,6 +89,7 @@
var/obj/item/modular_computer/modpc = ui.src_object
if(!istype(modpc))
modpc = null
//Admin actions
if(ui.user.client.holder)
switch(action)
@@ -147,21 +149,10 @@
if("start_now")
forced_setup()
switch(action) //both living and dead
if("mf_lookup")
var/role_lookup = params["role_name"]
var/datum/mafia_role/helper
for(var/datum/mafia_role/role as anything in all_roles)
if(role_lookup == role.name)
helper = role
break
helper.show_help(usr)
if(!user_role)//just the dead
if(!user_role) // non-player & pre-game actions.
switch(action)
if("mf_signup")
if(signup_mafia(usr, ui.user.client, modpc))
return TRUE
signup_mafia(ui.user, ui.user.client, modpc)
if("vote_to_start")
var/client/ghost_client = ui.user.client
if(phase != MAFIA_PHASE_SETUP)
@@ -191,35 +182,41 @@
to_chat(usr, span_notice("You vote to start the game early ([length(GLOB.mafia_early_votes)] out of [max(round(length(GLOB.mafia_signup + GLOB.pda_mafia_signup) / 2), round(MAFIA_MIN_PLAYER_COUNT / 2))])."))
if(check_start_votes()) //See if we have enough votes to start
forced_setup()
return TRUE
return TRUE
if(user_role && user_role.game_status == MAFIA_DEAD)
return
//User actions (just living)
switch(action)
switch(action) //actions that both living and dead players can perform.
if("mf_lookup")
var/role_lookup = params["role_name"]
var/datum/mafia_role/helper
for(var/datum/mafia_role/role as anything in all_roles)
if(role_lookup == role.name)
helper = role
break
helper.show_help(usr)
if("change_notes")
if(user_role.game_status == MAFIA_DEAD)
return TRUE
user_role.written_notes = sanitize_text(params["new_notes"])
user_role.send_message_to_player("notes saved", balloon_alert = TRUE)
return TRUE
if("send_message_to_chat")
if(user_role.game_status == MAFIA_DEAD)
return TRUE
var/message_said = sanitize_text(params["message"])
user_role.body.say(message_said, forced = "mafia chat (sent by [ui.user.client])")
return TRUE
if("send_notes_to_chat")
if(user_role.game_status == MAFIA_DEAD || !user_role.written_notes)
return TRUE
if(phase == MAFIA_PHASE_NIGHT)
if(!user_role.written_notes)
return TRUE
if(!COOLDOWN_FINISHED(user_role, note_chat_sending_cooldown))
return FALSE
COOLDOWN_START(user_role, note_chat_sending_cooldown, MAFIA_NOTE_SENDING_COOLDOWN)
user_role.body.say("[user_role.written_notes]", forced = "mafia notes sending")
var/list/message_mods = list()
message_mods[MANNEQUIN_CONTROLLED] = ui.user
user_role.body.say("[user_role.written_notes]", forced = "mafia notes sending", message_mods = message_mods)
return TRUE
if("send_message_to_chat")
var/message_said = sanitize_text(params["message"])
var/list/message_mods = list()
message_mods[MANNEQUIN_CONTROLLED] = ui.user
user_role.body.say(message_said, forced = "mafia chat (sent by [ui.user.client])", message_mods = message_mods)
if(user_role.game_status == MAFIA_DEAD)
return TRUE
switch(action) //actions that only living players can perform.
if("perform_action")
var/datum/mafia_role/target = locate(params["target"]) in all_roles
if(!istype(target))
@@ -232,11 +229,11 @@
used_action.using_ability = TRUE
used_action.perform_action_target(src, target)
if(MAFIA_PHASE_NIGHT)
used_action.set_target(src, target)
used_action.set_target(target)
return TRUE
if(user_role != on_trial)
switch(action)
switch(action) // actions that can only be done while someone is on stand (that isn't you)
if("vote_abstain")
if(phase != MAFIA_PHASE_JUDGEMENT || (user_role in judgement_abstain_votes))
return

View File

@@ -1,10 +1,8 @@
/obj/effect/landmark/mafia_game_area //locations where mafia will be loaded by the datum
name = "Mafia Area Spawn"
var/game_id = "mafia"
/obj/effect/landmark/mafia
name = "Mafia Player Spawn"
var/game_id = "mafia"
/obj/effect/landmark/mafia/town_center
name = "Mafia Town Center"
@@ -15,7 +13,6 @@
icon = 'icons/obj/mafia.dmi'
icon_state = "board"
anchored = TRUE
var/game_id = "mafia"
var/datum/mafia_controller/MF
/obj/mafia_game_board/attack_ghost(mob/user)

View File

@@ -77,6 +77,7 @@
name = "Mafia Chaplain"
uniform = /obj/item/clothing/under/rank/civilian/chaplain
shoes = /obj/item/clothing/shoes/sneakers/black
/datum/outfit/mafia/md
name = "Mafia Medical Doctor"
@@ -101,6 +102,14 @@
suit = /obj/item/clothing/suit/toggle/lawyer
shoes = /obj/item/clothing/shoes/laceup
/datum/outfit/mafia/coroner
name = "Mafia Coroner"
gloves = /obj/item/clothing/gloves/latex/coroner
head = /obj/item/clothing/head/utility/surgerycap/black
uniform = /obj/item/clothing/under/rank/medical/scrubs/coroner
shoes = /obj/item/clothing/shoes/sneakers/black
/datum/outfit/mafia/hop
name = "Mafia Head of Personnel"

View File

@@ -1,6 +1,6 @@
/datum/mafia_role/mafia
name = "Changeling"
desc = "You're a member of the changeling hive. You may speak with your fellow Changelings at night."
desc = "You're a member of the changeling hive. Talk during the Night period to coordinate with your allies, and kill all non-Changelings."
team = MAFIA_TEAM_MAFIA
role_type = MAFIA_REGULAR
role_flags = ROLE_CAN_KILL
@@ -25,10 +25,10 @@
/datum/mafia_role/mafia/thoughtfeeder
name = "Thoughtfeeder"
desc = "You're a changeling variant that feeds on the memories of others. Use ':j' talk prefix to talk to your fellow lings, and visit people at night to learn their role."
desc = "You're a changeling variant that feeds on the memories of others. Talk during the Night period to coordinate with your allies, \
and kill all non-Changelings, using your ability to learn people's roles to your advantage."
role_type = MAFIA_SPECIAL
hud_icon = "hudthoughtfeeder"
revealed_icon = "thoughtfeeder"
winner_award = /datum/award/achievement/mafia/thoughtfeeder
role_unique_actions = list(/datum/mafia_ability/changeling_kill, /datum/mafia_ability/thoughtfeeder)

View File

@@ -23,7 +23,7 @@
var/list/all_roles_shuffle = shuffle(game.living_roles) - src
for(var/datum/mafia_role/possible as anything in all_roles_shuffle)
if(possible.team == MAFIA_TEAM_TOWN)
if(possible.team & MAFIA_TEAM_TOWN)
obsession = possible
break
if(!obsession)
@@ -59,7 +59,7 @@
team = MAFIA_TEAM_SOLO
role_type = NEUTRAL_DISRUPT
special_ui_theme = "neutral"
hud_icon = "hudclown"
hud_icon = SECHUD_CLOWN
revealed_icon = "clown"
winner_award = /datum/award/achievement/mafia/clown

View File

@@ -7,6 +7,8 @@
var/role_type = TOWN_OVERFLOW
///role flags (special status of roles like detection immune)
var/role_flags = NONE
///The mafia controller board this mafia role is tied to, in case there's several Mafia games at once.
var/datum/mafia_controller/mafia_game_controller
///The mafia popup we edit text to give different alerts for (such as when to vote).
var/atom/movable/screen/mafia_popup/mafia_alert
@@ -41,7 +43,7 @@
var/game_status = MAFIA_ALIVE
///icon state in the mafia dmi of the hud of the role, used in the mafia ui
var/hud_icon = "hudassistant"
var/hud_icon = SECHUD_ASSISTANT
///icon state in the mafia dmi of the hud of the role, used in the mafia ui
var/revealed_icon = "assistant"
///set this to something cool for antagonists and their window will look different
@@ -50,15 +52,16 @@
///The cooldown between being able to send your will in chat.
COOLDOWN_DECLARE(note_chat_sending_cooldown)
/datum/mafia_role/New(datum/mafia_controller/game)
/datum/mafia_role/New(datum/mafia_controller/new_game)
. = ..()
mafia_panel = new(null, game)
src.mafia_game_controller = new_game
mafia_panel = new(null, new_game)
for(var/datum/mafia_ability/abilities as anything in role_unique_actions + /datum/mafia_ability/voting)
role_unique_actions += new abilities(game, src)
role_unique_actions += new abilities(src)
role_unique_actions -= abilities
/datum/mafia_role/Destroy(force)
UnregisterSignal(body, COMSIG_MOB_SAY)
UnregisterSignal(body, list(COMSIG_MOB_SAY, COMSIG_MOB_DEADSAY))
QDEL_NULL(mafia_alert)
QDEL_NULL(mafia_panel)
QDEL_LIST(role_unique_actions)
@@ -70,10 +73,11 @@
/datum/mafia_role/proc/register_body(mob/living/carbon/human/new_body)
if(body)
UnregisterSignal(new_body, COMSIG_MOB_SAY)
UnregisterSignal(new_body, list(COMSIG_MOB_SAY, COMSIG_MOB_DEADSAY))
mafia_panel.Remove(body)
body = new_body
RegisterSignal(new_body, COMSIG_MOB_SAY, PROC_REF(handle_speech))
RegisterSignal(new_body, COMSIG_MOB_DEADSAY, PROC_REF(handle_speech_dead))
mafia_panel.Grant(new_body)
/**
@@ -97,19 +101,25 @@
* handle_speech
*
* Handles Mafia roles talking in chat.
* First it will go through their abilities for Ability-specific speech,
* if none affects it, we will go to day chat.
* First we'll go through their abilities for Ability-specific speech,
* if none affects it, we will go to day chat (if it is indeed day).
*/
/datum/mafia_role/proc/handle_speech(datum/source, list/speech_args)
SIGNAL_HANDLER
for(var/datum/mafia_ability/abilities as anything in role_unique_actions)
if(abilities.handle_speech(source, speech_args))
return
var/datum/mafia_controller/mafia_game = GLOB.mafia_game
if(!mafia_game || mafia_game.phase == MAFIA_PHASE_NIGHT)
if(mafia_game_controller.phase == MAFIA_PHASE_NIGHT)
return
var/message = "[source]: [html_decode(speech_args[SPEECH_MESSAGE])]"
mafia_game.send_message(message, log_only = TRUE)
mafia_game_controller.send_message(message, log_only = TRUE)
///Same as handle_speech, but for dead players.
/datum/mafia_role/proc/handle_speech_dead(datum/source, message)
SIGNAL_HANDLER
var/message_sent = span_changeling("<b>\[DEAD CHAT\] [source]</b>: [message]")
mafia_game_controller.send_message(message_sent, team = MAFIA_TEAM_DEAD)
return MOB_DEADSAY_SIGNAL_INTERCEPT
/**
* Puts the player in their body and keeps track of their previous one to put them back in later.
@@ -123,6 +133,7 @@
old_body = player.mob.mind.current, \
)
body.PossessByPlayer(player.key)
ADD_TRAIT(body, TRAIT_CORPSELOCKED, MAFIA_TRAIT)
/**
* Tests kill immunities, if nothing prevents the kill, kills this role.
@@ -137,6 +148,8 @@
if(SEND_SIGNAL(src, COMSIG_MAFIA_ON_KILL, game, attacker, lynch) & MAFIA_PREVENT_KILL)
return FALSE
game_status = MAFIA_DEAD
//can now hear dead chat speaking.
team |= MAFIA_TEAM_DEAD
body.death()
if(lynch)
reveal_role(game, verbose = TRUE)

View File

@@ -5,7 +5,7 @@
role_type = TOWN_INVEST
winner_award = /datum/award/achievement/mafia/detective
hud_icon = "huddetective"
hud_icon = SECHUD_DETECTIVE
revealed_icon = "detective"
role_unique_actions = list(/datum/mafia_ability/investigate)
@@ -17,18 +17,18 @@
role_type = TOWN_INVEST
winner_award = /datum/award/achievement/mafia/psychologist
hud_icon = "hudpsychologist"
hud_icon = SECHUD_PSYCHOLOGIST
revealed_icon = "psychologist"
role_unique_actions = list(/datum/mafia_ability/reveal_role)
/datum/mafia_role/chaplain
name = "Chaplain"
desc = "You can communicate with spirits of the dead each night to discover dead crewmember roles."
revealed_outfit = /datum/outfit/mafia/chaplain
/datum/mafia_role/coroner
name = "Coroner"
desc = "You can perform autopsies on the dead each night to discover their role."
revealed_outfit = /datum/outfit/mafia/coroner
role_type = TOWN_INVEST
hud_icon = "hudchaplain"
revealed_icon = "chaplain"
winner_award = /datum/award/achievement/mafia/chaplain
hud_icon = SECHUD_CORONER
revealed_icon = "coroner"
winner_award = /datum/award/achievement/mafia/coroner
role_unique_actions = list(/datum/mafia_ability/seance)
role_unique_actions = list(/datum/mafia_ability/autopsy)

View File

@@ -5,7 +5,7 @@
role_flags = ROLE_CAN_KILL | ROLE_UNIQUE
revealed_outfit = /datum/outfit/mafia/hos
revealed_icon = "headofsecurity"
hud_icon = "hudheadofsecurity"
hud_icon = SECHUD_HEAD_OF_SECURITY
winner_award = /datum/award/achievement/mafia/hos
role_unique_actions = list(/datum/mafia_ability/attack_player/execution)
@@ -18,7 +18,7 @@
role_flags = ROLE_CAN_KILL
revealed_outfit = /datum/outfit/mafia/warden
revealed_icon = "warden"
hud_icon = "hudwarden"
hud_icon = SECHUD_WARDEN
winner_award = /datum/award/achievement/mafia/warden
role_unique_actions = list(/datum/mafia_ability/attack_visitors)

View File

@@ -3,7 +3,7 @@
desc = "You can protect a single person each night from killing. You can heal yourself once."
revealed_outfit = /datum/outfit/mafia/md
role_type = TOWN_PROTECT
hud_icon = "hudmedicaldoctor"
hud_icon = SECHUD_MEDICAL_DOCTOR
revealed_icon = "medicaldoctor"
winner_award = /datum/award/achievement/mafia/md
@@ -14,7 +14,7 @@
desc = "You can protect a single person each night. If they are attacked, you will retaliate, killing yourself and the attacker. You can protect yourself once."
revealed_outfit = /datum/outfit/mafia/security
revealed_icon = "securityofficer"
hud_icon = "hudsecurityofficer"
hud_icon = SECHUD_SECURITY_OFFICER
role_type = TOWN_PROTECT
role_flags = ROLE_CAN_KILL
winner_award = /datum/award/achievement/mafia/officer

View File

@@ -3,7 +3,7 @@
desc = "You can choose a person to provide extensive legal advice to, preventing night actions."
revealed_outfit = /datum/outfit/mafia/lawyer
role_type = TOWN_SUPPORT
hud_icon = "hudlawyer"
hud_icon = SECHUD_LAWYER
revealed_icon = "lawyer"
winner_award = /datum/award/achievement/mafia/lawyer
@@ -15,9 +15,21 @@
role_type = TOWN_SUPPORT
role_flags = ROLE_UNIQUE
role_flags = ROLE_CAN_KILL
hud_icon = "hudheadofpersonnel"
hud_icon = SECHUD_HEAD_OF_PERSONNEL
revealed_icon = "headofpersonnel"
revealed_outfit = /datum/outfit/mafia/hop
winner_award = /datum/award/achievement/mafia/hop
role_unique_actions = list(/datum/mafia_ability/self_reveal)
/datum/mafia_role/chaplain
name = "Chaplain"
desc = "You can communicate with spirits of the dead each night to discover dead crewmember roles."
role_type = TOWN_INVEST
team = MAFIA_TEAM_TOWN | MAFIA_TEAM_DEAD
hud_icon = SECHUD_CHAPLAIN
revealed_icon = "chaplain"
revealed_outfit = /datum/outfit/mafia/chaplain
winner_award = /datum/award/achievement/mafia/chaplain
role_unique_actions = list(/datum/mafia_ability/seance)

View File

@@ -157,7 +157,7 @@ GLOBAL_LIST_INIT(message_modes_stat_limits, list(
if(!message_mods[WHISPER_MODE])
return
if(DEAD)
say_dead(original_message)
say_dead(original_message, message_mods[MANNEQUIN_CONTROLLED])
return
if(HAS_TRAIT(src, TRAIT_SOFTSPOKEN) && !HAS_TRAIT(src, TRAIT_SIGN_LANG)) // softspoken trait only applies to spoken languages

View File

@@ -108,8 +108,14 @@
return ..()
///Speak as a dead person (ghost etc)
/mob/proc/say_dead(message)
/**
* say_dead
* allows you to speak as a dead person
* Args:
* - message: The message you're sending to chat.
* - mannequin_controller: If someone else is forcing you to speak, this is the mob doing it.
*/
/mob/proc/say_dead(message, mob/mannequin_controller)
var/name = real_name
var/alt_name = ""
@@ -123,7 +129,7 @@
return
//SKYRAT EDIT END
var/jb = is_banned_from(ckey, "Deadchat")
var/jb = is_banned_from(mannequin_controller?.ckey || ckey, "Deadchat")
if(QDELETED(src))
return

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -4868,8 +4868,8 @@
#include "code\modules\mafia\map_pieces.dm"
#include "code\modules\mafia\outfits.dm"
#include "code\modules\mafia\abilities\abilities.dm"
#include "code\modules\mafia\abilities\investigative\autopsy.dm"
#include "code\modules\mafia\abilities\investigative\investigate.dm"
#include "code\modules\mafia\abilities\investigative\pray.dm"
#include "code\modules\mafia\abilities\investigative\reveal.dm"
#include "code\modules\mafia\abilities\investigative\thoughtfeed.dm"
#include "code\modules\mafia\abilities\killing\alert.dm"
@@ -4878,6 +4878,7 @@
#include "code\modules\mafia\abilities\protective\heal.dm"
#include "code\modules\mafia\abilities\protective\vest.dm"
#include "code\modules\mafia\abilities\support\roleblock.dm"
#include "code\modules\mafia\abilities\support\seance.dm"
#include "code\modules\mafia\abilities\support\self_reveal.dm"
#include "code\modules\mafia\abilities\voting\changeling_kill.dm"
#include "code\modules\mafia\abilities\voting\day_voting.dm"

View File

@@ -23,6 +23,7 @@ type RoleInfo = {
desc: string;
hud_icon: string;
revealed_icon: string;
role_dead: string;
};
type PlayerInfo = {
@@ -286,7 +287,7 @@ const MafiaRole = (props) => {
<Section
fill
title={phase + turn}
minHeight="100px"
minHeight="110px"
maxHeight="50px"
buttons={
<Box
@@ -299,10 +300,16 @@ const MafiaRole = (props) => {
</Box>
}
>
<Stack align="center">
<Stack>
<Stack.Item grow>
<Box bold>You are the {roleinfo.role}</Box>
<Box italic>{roleinfo.desc}</Box>
<Box fontSize="16px">You are the {roleinfo.role}</Box>
{!!roleinfo.role_dead && (
<Box bold>
You are currently dead. You may speak with the Chaplain at night,
if there is one.
</Box>
)}
{!roleinfo.role_dead && <Box italic>{roleinfo.desc}</Box>}
</Stack.Item>
<Stack.Item>
<Box