diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm index b65bae6777e..ba2c6696c77 100644 --- a/code/__DEFINES/traits.dm +++ b/code/__DEFINES/traits.dm @@ -749,6 +749,9 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai /// For clarity, this trait should always be associated/tied to a reference to the mob that suicided- not anything else. #define TRAIT_SUICIDED "committed_suicide" +///Trait given to a mob with a ckey currently in a temporary body, allowing people to know someone will re-enter the round later. +#define TRAIT_MIND_TEMPORARILY_GONE "temporarily_gone" + // common trait sources #define TRAIT_GENERIC "generic" #define UNCONSCIOUS_TRAIT "unconscious" diff --git a/code/datums/components/temporary_body.dm b/code/datums/components/temporary_body.dm new file mode 100644 index 00000000000..bdb97dace42 --- /dev/null +++ b/code/datums/components/temporary_body.dm @@ -0,0 +1,55 @@ +/** + * ##temporary_body + * + * Used on living mobs when they are meant to be a 'temporary body' + * Holds a reference to an old mind & body, to put them back in + * once the body this component is attached to, is being deleted. + */ +/datum/component/temporary_body + ///The old mind we will be put back into when parent is being deleted. + var/datum/weakref/old_mind_ref + ///The old body we will be put back into when parent is being deleted. + var/datum/weakref/old_body_ref + +/datum/component/temporary_body/Initialize(datum/mind/old_mind, mob/living/old_body) + if(!isliving(parent) || !isliving(old_body)) + return COMPONENT_INCOMPATIBLE + ADD_TRAIT(old_body, TRAIT_MIND_TEMPORARILY_GONE, REF(src)) + src.old_mind_ref = WEAKREF(old_mind) + src.old_body_ref = WEAKREF(old_body) + +/datum/component/temporary_body/RegisterWithParent() + RegisterSignal(parent, COMSIG_PARENT_QDELETING, PROC_REF(on_parent_destroy)) + +/datum/component/temporary_body/UnregisterFromParent() + UnregisterSignal(parent, COMSIG_PARENT_QDELETING) + +/** + * Sends the mind of the temporary body back into their previous host + * If the previous host is alive, we'll force them into the body. + * Otherwise we'll let them hang out as a ghost still. + */ +/datum/component/temporary_body/proc/on_parent_destroy() + SIGNAL_HANDLER + var/datum/mind/old_mind = old_mind_ref?.resolve() + var/mob/living/old_body = old_body_ref?.resolve() + + if(!old_mind || !old_body) + return + + var/mob/living/living_parent = parent + var/mob/dead/observer/ghost = living_parent.ghostize() + if(!ghost) + ghost = living_parent.get_ghost() + if(!ghost) + CRASH("[src] belonging to [parent] was completely unable to find a ghost to put back into a body!") + ghost.mind = old_mind + if(old_body.stat != DEAD) + old_mind.transfer_to(old_body, force_key_move = TRUE) + else + old_mind.set_current(old_body) + + REMOVE_TRAIT(old_body, TRAIT_MIND_TEMPORARILY_GONE, REF(src)) + + old_mind = null + old_body = null diff --git a/code/modules/hydroponics/grown/replicapod.dm b/code/modules/hydroponics/grown/replicapod.dm index ef7f18c2c89..695420b4f23 100644 --- a/code/modules/hydroponics/grown/replicapod.dm +++ b/code/modules/hydroponics/grown/replicapod.dm @@ -136,7 +136,7 @@ make_podman = TRUE break else - if(M.ckey == ckey && M.stat == DEAD && !HAS_TRAIT(M, TRAIT_SUICIDED)) + if(M.ckey == ckey && M.stat == DEAD && !HAS_TRAIT(M, TRAIT_SUICIDED) && !HAS_TRAIT(M, TRAIT_MIND_TEMPORARILY_GONE)) make_podman = TRUE break else //If the player has ghosted from his corpse before blood was drawn, his ckey is no longer attached to the mob, so we need to match up the cloned player through the mind key diff --git a/code/modules/mafia/abilities/investigative/pray.dm b/code/modules/mafia/abilities/investigative/pray.dm index e72f4d4119a..b5bd6ee7e88 100644 --- a/code/modules/mafia/abilities/investigative/pray.dm +++ b/code/modules/mafia/abilities/investigative/pray.dm @@ -7,7 +7,7 @@ /datum/mafia_ability/seance name = "Seance" ability_action = "commune with the spirit of" - use_flags = CAN_USE_ON_DEAD + 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) . = ..() diff --git a/code/modules/mafia/controller.dm b/code/modules/mafia/controller.dm index 4c85f511239..7ab757ff3f4 100644 --- a/code/modules/mafia/controller.dm +++ b/code/modules/mafia/controller.dm @@ -7,8 +7,6 @@ GLOBAL_LIST_INIT(mafia_role_by_alignment, setup_mafia_role_by_alignment()) * It is first created when the first ghost signs up to play. */ /datum/mafia_controller - ///list of observers that should get game updates. - var/list/mafia_spectators = list() ///all roles in the game, dead or alive. check their game status if you only want living or dead. var/list/datum/mafia_role/all_roles = list() ///all living roles in the game, removed on death. @@ -107,9 +105,10 @@ GLOBAL_LIST_INIT(mafia_role_by_alignment, setup_mafia_role_by_alignment()) /datum/mafia_controller/Destroy(force, ...) . = ..() - GLOB.mafia_game = null + if(GLOB.mafia_game == src) + GLOB.mafia_game = null end_game() - qdel(map_deleter) + QDEL_NULL(map_deleter) /** * Triggers at beginning of the game when there is a confirmed list of valid, ready players. @@ -152,7 +151,8 @@ GLOBAL_LIST_INIT(mafia_role_by_alignment, setup_mafia_role_by_alignment()) var/datum/mafia_role/new_role = new rtype(src) all_roles += new_role living_roles += new_role - current_setup_text += "[new_role.name] x[setup_list[new_role.type]]" + var/datum/mafia_role/role_type = rtype + current_setup_text += "[initial(role_type.name)] x[setup_list[role_type]]" var/list/spawnpoints = landmarks.Copy() for(var/datum/mafia_role/role as anything in all_roles) role.assigned_landmark = pick_n_take(spawnpoints) @@ -212,7 +212,7 @@ GLOBAL_LIST_INIT(mafia_role_by_alignment, setup_mafia_role_by_alignment()) */ /datum/mafia_controller/proc/start_voting_phase() phase = MAFIA_PHASE_VOTING - next_phase_timer = addtimer(CALLBACK(src, PROC_REF(check_trial), TRUE), (VOTING_PERIOD_LENGTH / time_speedup),TIMER_STOPPABLE) //be verbose! + next_phase_timer = addtimer(CALLBACK(src, PROC_REF(check_trial), TRUE), (VOTING_PERIOD_LENGTH / time_speedup), TIMER_STOPPABLE) //be verbose! send_message("Voting started! Vote for who you want to see on trial today.") /** @@ -276,7 +276,7 @@ GLOBAL_LIST_INIT(mafia_role_by_alignment, setup_mafia_role_by_alignment()) if(total_guilty_votes > total_innocent_votes) //strictly need majority guilty to lynch send_message(span_red("Guilty wins majority, [on_trial.body.real_name] has been lynched.")) on_trial.kill(src, lynch = TRUE) - addtimer(CALLBACK(src, PROC_REF(send_home), on_trial), (LYNCH_PERIOD_LENGTH / time_speedup)) + next_phase_timer = addtimer(CALLBACK(src, PROC_REF(send_home), on_trial), (LYNCH_PERIOD_LENGTH / time_speedup), TIMER_STOPPABLE) else send_message(span_green("Innocent wins majority, [on_trial.body.real_name] has been spared.")) on_trial.body.forceMove(get_turf(on_trial.assigned_landmark)) @@ -337,7 +337,7 @@ GLOBAL_LIST_INIT(mafia_role_by_alignment, setup_mafia_role_by_alignment()) var/victory_message - if((living_mafia.len + living_town.len) <= 1) + if(living_mafia.len + living_town.len <= 0) victory_message = "Draw!" //this is in-case no neutrals won, but there's no town/mafia left. for(var/datum/mafia_role/solo as anything in neutral_killers) victory_message = "[uppertext(solo.name)] VICTORY!" @@ -369,7 +369,7 @@ GLOBAL_LIST_INIT(mafia_role_by_alignment, setup_mafia_role_by_alignment()) * * role: mafia_role datum to reward. */ /datum/mafia_controller/proc/award_role(award, datum/mafia_role/rewarded) - var/client/role_client = GLOB.directory[rewarded.player_key] + var/client/role_client = GLOB.directory[rewarded.body.client] role_client?.give_award(award, rewarded.body) /** @@ -389,28 +389,17 @@ GLOBAL_LIST_INIT(mafia_role_by_alignment, setup_mafia_role_by_alignment()) roles.mafia_alert.update_text("[message]") roles.reveal_role(src) phase = MAFIA_PHASE_VICTORY_LAP - next_phase_timer = addtimer(CALLBACK(src, PROC_REF(end_game)), VICTORY_LAP_PERIOD_LENGTH) + next_phase_timer = QDEL_IN(src, VICTORY_LAP_PERIOD_LENGTH) /** * Cleans up the game, resetting variables back to the beginning and removing the map with the generator. */ /datum/mafia_controller/proc/end_game() - map_deleter.generate() //remove the map, it will be loaded at the start of the next one QDEL_LIST(all_roles) living_roles.Cut() - current_setup_text = null - custom_setup = list() - turn = 0 - votes = list() - - time_speedup = initial(time_speedup) - - //map gen does not deal with landmarks QDEL_LIST(landmarks) QDEL_NULL(town_center_landmark) - phase = MAFIA_PHASE_SETUP - - early_start = initial(early_start) + map_deleter.generate() //remove the map, it will be loaded at the start of the next onemap_deleter.generate() //remove the map, it will be loaded at the start of the next one /** * After the voting and judgement phases, the game goes to night shutting the windows and beginning night with a proc. @@ -524,11 +513,11 @@ GLOBAL_LIST_INIT(mafia_role_by_alignment, setup_mafia_role_by_alignment()) * * vote_type: the vote type (getting how many day votes were for the role, or mafia night votes for the role) */ /datum/mafia_controller/proc/get_vote_count(role,vote_type) - . = 0 - for(var/v in votes[vote_type]) - var/datum/mafia_role/votee = v + var/total_votes = 0 + for(var/datum/mafia_role/votee as anything in votes[vote_type]) if(votes[vote_type][votee] == role) - . += votee.vote_power + total_votes += votee.vote_power + return total_votes /** * Returns whichever role got the most votes, in whatever vote (day vote, night kill vote) @@ -600,7 +589,7 @@ GLOBAL_LIST_INIT(mafia_role_by_alignment, setup_mafia_role_by_alignment()) player_client.prefs.safe_transfer_prefs_to(H, is_antag = TRUE) role.body = H player_role_lookup[H] = role - H.key = role.player_key + role.put_player_in_body(player_client) role.greet() /datum/mafia_controller/ui_static_data(mob/user) @@ -634,9 +623,6 @@ GLOBAL_LIST_INIT(mafia_role_by_alignment, setup_mafia_role_by_alignment()) var/list/lobby_member = list() lobby_member["name"] = key lobby_member["status"] = (key in GLOB.mafia_bad_signup) ? "Disconnected" : "Ready" - lobby_member["spectating"] = "Ghost" - if(key in mafia_spectators) - lobby_member["spectating"] = "Spectator" data["lobbydata"] += list(lobby_member) return data @@ -677,12 +663,14 @@ GLOBAL_LIST_INIT(mafia_role_by_alignment, setup_mafia_role_by_alignment()) if(usr.client?.holder) switch(action) if("new_game") - end_game() + if(phase == MAFIA_PHASE_SETUP) + return basic_setup() if("nuke") - end_game() qdel(src) if("next_phase") + if(phase == MAFIA_PHASE_SETUP) + return var/datum/timedevent/timer = SStimer.timer_id_dict[next_phase_timer] if(!timer.spent) var/datum/callback/tc = timer.callBack @@ -709,10 +697,14 @@ GLOBAL_LIST_INIT(mafia_role_by_alignment, setup_mafia_role_by_alignment()) while(!done) to_chat(usr, "You have a total player count of [assoc_value_sum(debug_setup)] in this setup.") var/chosen_role_name = tgui_input_list(usr, "Select a role!", "Custom Setup Creation", rolelist_dict) + if(!chosen_role_name) + return switch(chosen_role_name) if("CANCEL") + done = TRUE return if("FINISH") + done = TRUE break else var/found_path = rolelist_dict[chosen_role_name] @@ -756,15 +748,6 @@ GLOBAL_LIST_INIT(mafia_role_by_alignment, setup_mafia_role_by_alignment()) check_signups() try_autostart() return TRUE - if("mf_spectate") - var/client/C = ui.user.client - if(C.ckey in mafia_spectators) - to_chat(usr, span_notice("You will no longer get messages from the game.")) - mafia_spectators -= C.ckey - else - to_chat(usr, span_notice("You will now get messages from the game.")) - mafia_spectators += C.ckey - return TRUE if("vote_to_start") var/client/C = ui.user.client if(phase != MAFIA_PHASE_SETUP) diff --git a/code/modules/mafia/roles/roles.dm b/code/modules/mafia/roles/roles.dm index 9156831a963..e8b5f8a63ab 100644 --- a/code/modules/mafia/roles/roles.dm +++ b/code/modules/mafia/roles/roles.dm @@ -48,9 +48,22 @@ /datum/mafia_role/Destroy(force, ...) QDEL_NULL(mafia_alert) QDEL_NULL(body) - QDEL_NULL(role_unique_actions) + QDEL_LIST(role_unique_actions) return ..() +/** + * Puts the player in their body and keeps track of their previous one to put them back in later. + * Adds the playing_mafia trait so people examining them will know why they're currently lacking a soul. + */ +/datum/mafia_role/proc/put_player_in_body(client/player) + if(player.mob.mind && player.mob.mind.current) + body.AddComponent( \ + /datum/component/temporary_body, \ + old_mind = player.mob.mind, \ + old_body = player.mob.mind.current, \ + ) + body.key = player.key + /** * Tests kill immunities, if nothing prevents the kill, kills this role. * @@ -66,8 +79,6 @@ body.death() if(lynch) reveal_role(game, verbose = TRUE) - if(!(player_key in game.mafia_spectators)) //people who played will want to see the end of the game more often than not - game.mafia_spectators += player_key game.living_roles -= src return TRUE diff --git a/code/modules/mob/living/carbon/human/human_helpers.dm b/code/modules/mob/living/carbon/human/human_helpers.dm index 83d2c8d9d6a..7a0ade3707a 100644 --- a/code/modules/mob/living/carbon/human/human_helpers.dm +++ b/code/modules/mob/living/carbon/human/human_helpers.dm @@ -234,7 +234,7 @@ var/t_his = p_their() var/t_is = p_are() //This checks to see if the body is revivable - if(key || !get_organ_by_type(/obj/item/organ/internal/brain) || ghost?.can_reenter_corpse) + if(key || !get_organ_by_type(/obj/item/organ/internal/brain) || ghost?.can_reenter_corpse || HAS_TRAIT(src, TRAIT_MIND_TEMPORARILY_GONE)) return span_deadsay("[t_He] [t_is] limp and unresponsive; there are no signs of life...") else return span_deadsay("[t_He] [t_is] limp and unresponsive; there are no signs of life and [t_his] soul has departed...") diff --git a/icons/obj/mafia.dmi b/icons/obj/mafia.dmi index 47d4080cea9..7c3b525c005 100644 Binary files a/icons/obj/mafia.dmi and b/icons/obj/mafia.dmi differ diff --git a/tgstation.dme b/tgstation.dme index 117525da1d4..9f25567ac5c 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -1024,6 +1024,7 @@ #include "code\datums\components\tattoo.dm" #include "code\datums\components\technointrovert.dm" #include "code\datums\components\technoshy.dm" +#include "code\datums\components\temporary_body.dm" #include "code\datums\components\tether.dm" #include "code\datums\components\thermite.dm" #include "code\datums\components\tippable.dm" diff --git a/tgui/packages/tgui/interfaces/MafiaPanel.tsx b/tgui/packages/tgui/interfaces/MafiaPanel.tsx index a905e2fd341..2820822eaf4 100644 --- a/tgui/packages/tgui/interfaces/MafiaPanel.tsx +++ b/tgui/packages/tgui/interfaces/MafiaPanel.tsx @@ -29,7 +29,6 @@ type ActionInfo = { type LobbyData = { name: string; status: string; - spectating: string; }; type MafiaData = { @@ -157,19 +156,6 @@ const MafiaLobby = (props, context) => { content="Sign Up" onClick={() => act('mf_signup')} /> -