From a91f04d435b2cd02d87beb4892543a9ddaa4d08b Mon Sep 17 00:00:00 2001 From: MrMelbert <51863163+MrMelbert@users.noreply.github.com> Date: Sat, 14 Feb 2026 10:26:24 -0600 Subject: [PATCH] Being absorbed now makes you into an imaginary friend of the changeling (#94956) ## About The Pull Request When you get absorbed by a changeling, you get made into an imaginary friend of the changeling that absorbed you image image image This imaginary friend doesn't show up on health scan (obviously), and they communicate via hivemind chat, meaning all other changelings are capable of hearing them If your body is revived, you get yoinked out of the hivemind as you'd expect You also have a button to eject yourself from the hivemind (if you prefer to observe). And the ling themselves has a button to eject people from their hivemind that are being annoying ## Why It's Good For The Game Makes being absorbed a little less of a bummer, now you can tag along and maybe even give them some tips about how your character would act if they choose to disguise as you. ## Changelog :cl: Melbert add: When you get absorbed by a changeling, you get made into an imaginary friend of the changeling that absorbed you. refactor: Refactored how people are returned to their bodies after occupying separate bodies, such as bitrunners and ghost roles. Report any oddities or failure to return correctly. /:cl: --- code/__DEFINES/traits/declarations.dm | 4 +- code/_globalvars/traits/_traits.dm | 2 +- code/datums/brain_damage/imaginary_friend.dm | 26 ++-- code/datums/components/temporary_body.dm | 108 +++++++++----- code/datums/saymode.dm | 44 +++--- .../antagonists/changeling/changeling.dm | 5 + .../antagonists/changeling/powers/absorb.dm | 135 +++++++++++++++++- code/modules/antagonists/cult/runes.dm | 1 - code/modules/basketball/controller.dm | 1 - code/modules/bitrunning/server/threats.dm | 2 +- code/modules/bitrunning/spawners.dm | 2 +- code/modules/capture_the_flag/ctf_game.dm | 1 - code/modules/deathmatch/deathmatch_lobby.dm | 1 - code/modules/mafia/roles/roles.dm | 1 - .../chemistry/reagents/other_reagents.dm | 1 - 15 files changed, 256 insertions(+), 78 deletions(-) diff --git a/code/__DEFINES/traits/declarations.dm b/code/__DEFINES/traits/declarations.dm index 6d9cb8e2255..74ad478db45 100644 --- a/code/__DEFINES/traits/declarations.dm +++ b/code/__DEFINES/traits/declarations.dm @@ -1241,8 +1241,8 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai /// This item is currently under the control of telekinesis #define TRAIT_TELEKINESIS_CONTROLLED "telekinesis_controlled" -/// changelings with this trait can no longer talk over the hivemind -#define TRAIT_CHANGELING_HIVEMIND_MUTE "ling_mute" +/// mobs with this trait can talk over the hivemind +#define TRAIT_CHANGELING_HIVEMIND "ling_hivemind" /// This guy is a hulk! (Bulky and green, lacks tact) #define TRAIT_HULK "hulk" /// Isn't attacked harmfully by blob structures diff --git a/code/_globalvars/traits/_traits.dm b/code/_globalvars/traits/_traits.dm index da19eacc3cc..bbde567880c 100644 --- a/code/_globalvars/traits/_traits.dm +++ b/code/_globalvars/traits/_traits.dm @@ -226,7 +226,7 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_CAN_THROW_ITEMS" = TRAIT_CAN_THROW_ITEMS, "TRAIT_CAN_USE_NUKE" = TRAIT_CAN_USE_NUKE, "TRAIT_CATLIKE_GRACE" = TRAIT_CATLIKE_GRACE, - "TRAIT_CHANGELING_HIVEMIND_MUTE" = TRAIT_CHANGELING_HIVEMIND_MUTE, + "TRAIT_CHANGELING_HIVEMIND" = TRAIT_CHANGELING_HIVEMIND, "TRAIT_CHASM_DESTROYED" = TRAIT_CHASM_DESTROYED, "TRAIT_CHEF_KISS" = TRAIT_CHEF_KISS, "TRAIT_CHUNKYFINGERS" = TRAIT_CHUNKYFINGERS, diff --git a/code/datums/brain_damage/imaginary_friend.dm b/code/datums/brain_damage/imaginary_friend.dm index 3df17cb204e..b8691007d2c 100644 --- a/code/datums/brain_damage/imaginary_friend.dm +++ b/code/datums/brain_damage/imaginary_friend.dm @@ -89,13 +89,17 @@ see_invisible = SEE_INVISIBLE_LIVING invisibility = INVISIBILITY_MAXIMUM has_emotes = TRUE - var/icon/human_image + var/icon/human_icon var/image/current_image var/hidden = FALSE var/move_delay = 0 var/mob/living/owner var/bubble_icon = "default" + /// Max distance we can be from our owner before we teleport to them + var/distance_allowance = 9 + /// If TRUE, we must keep line of sight with the owner + var/require_los = FALSE /// Whether our host and other imaginary friends can hear us only when nearby or practically anywhere. var/extended_message_range = TRUE @@ -145,7 +149,7 @@ gender = pick(MALE, FEMALE) real_name = generate_random_name_species_based(gender, FALSE, /datum/species/human) name = real_name - human_image = get_flat_human_icon(null, pick(SSjob.joinable_occupations)) + human_icon = get_flat_human_icon(null, pick(SSjob.joinable_occupations)) Show() /** @@ -176,11 +180,11 @@ appearance_job = SSjob.get_job(JOB_ASSISTANT) if(istype(appearance_job, /datum/job/ai)) - human_image = icon('icons/mob/silicon/ai.dmi', icon_state = resolve_ai_icon(appearance_from_prefs.read_preference(/datum/preference/choiced/ai_core_display)), dir = SOUTH) + human_icon = icon('icons/mob/silicon/ai.dmi', icon_state = resolve_ai_icon(appearance_from_prefs.read_preference(/datum/preference/choiced/ai_core_display)), dir = SOUTH) else if(istype(appearance_job, /datum/job/cyborg)) - human_image = icon('icons/mob/silicon/robots.dmi', icon_state = "robot") + human_icon = icon('icons/mob/silicon/robots.dmi', icon_state = "robot") else - human_image = get_flat_human_icon(null, appearance_job, appearance_from_prefs) + human_icon = get_flat_human_icon(null, appearance_job, appearance_from_prefs) Show() /// Returns all member clients of the imaginary_group @@ -200,7 +204,7 @@ remove_image_from_clients(current_image, friend_clients) //Generate image from the static icon and the current dir - current_image = image(human_image, src, , MOB_LAYER, dir=src.dir) + current_image = image(human_icon, src, , MOB_LAYER, dir=src.dir) current_image.override = TRUE current_image.name = name if(hidden) @@ -213,10 +217,8 @@ src.client.images |= current_image /mob/eye/imaginary_friend/Destroy() - if(owner?.client) - owner.client.images.Remove(human_image) - if(client) - client.images.Remove(human_image) + owner.client?.images -= current_image + client?.images -= current_image owner.imaginary_group -= src return ..() @@ -431,7 +433,7 @@ if(world.time < move_delay) return FALSE setDir(Dir) - if(get_dist(src, owner) > 9) + if(get_dist(src, owner) > distance_allowance || (require_los && !can_see(owner, src, distance_allowance))) recall() move_delay = world.time + 10 return FALSE @@ -541,7 +543,7 @@ /mob/eye/imaginary_friend/trapped/setup_friend() real_name = "[owner.real_name]?" name = real_name - human_image = icon('icons/mob/simple/lavaland/lavaland_monsters.dmi', icon_state = "curseblob") + human_icon = icon('icons/mob/simple/lavaland/lavaland_monsters.dmi', icon_state = "curseblob") #undef IMAGINARY_FRIEND_RANGE #undef IMAGINARY_FRIEND_SPEECH_RANGE diff --git a/code/datums/components/temporary_body.dm b/code/datums/components/temporary_body.dm index deadf90e6f9..d069116cf0a 100644 --- a/code/datums/components/temporary_body.dm +++ b/code/datums/components/temporary_body.dm @@ -1,63 +1,101 @@ /** - * ##temporary_body + * ## 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 + * Used on mobs when they are meant to be a 'temporary body' + * Holds a reference to an old mind, 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 + var/datum/mind/old_mind ///The old body we will be put back into when parent is being deleted. - var/datum/weakref/old_body_ref - /// Returns the mind if the parent dies by any means - var/delete_on_death = FALSE + var/mob/old_body + /// Returns the mind if the PARENT dies by any means + var/return_on_death = FALSE + /// Returns the mind if the OLD_BODY is revived + var/return_on_revive = FALSE -/datum/component/temporary_body/Initialize(datum/mind/old_mind, mob/living/old_body, delete_on_death = FALSE) - if(!isliving(parent)) +/datum/component/temporary_body/Initialize(datum/mind/old_mind, return_on_death = FALSE, return_on_revive = FALSE) + if(!ismob(parent)) return COMPONENT_INCOMPATIBLE - src.old_mind_ref = WEAKREF(old_mind) - if(istype(old_body)) - ADD_TRAIT(old_body, TRAIT_MIND_TEMPORARILY_GONE, REF(src)) - src.old_body_ref = WEAKREF(old_body) - src.delete_on_death = delete_on_death + if(isnull(old_mind)) + stack_trace("Tried to create a temporary_body component without an old_mind!") + return COMPONENT_INCOMPATIBLE + + src.old_mind = old_mind + src.old_body = old_mind.current + src.return_on_death = return_on_death + src.return_on_revive = return_on_revive /datum/component/temporary_body/RegisterWithParent() - RegisterSignal(parent, COMSIG_QDELETING, PROC_REF(on_parent_destroy)) + RegisterSignal(parent, COMSIG_QDELETING, PROC_REF(return_mind)) + if(return_on_death) + RegisterSignal(parent, COMSIG_LIVING_DEATH, PROC_REF(return_mind)) - if(delete_on_death) - RegisterSignal(parent, COMSIG_LIVING_DEATH, PROC_REF(on_parent_destroy)) + RegisterSignal(old_mind, COMSIG_MIND_TRANSFERRED, PROC_REF(on_mind_transfer)) + + if(!isnull(old_body)) + register_body_signals() + +/datum/component/temporary_body/proc/register_body_signals() + ADD_TRAIT(old_body, TRAIT_MIND_TEMPORARILY_GONE, REF(src)) + RegisterSignal(old_body, COMSIG_QDELETING, PROC_REF(on_body_destroy)) + if(return_on_revive) + RegisterSignal(old_body, COMSIG_LIVING_REVIVE, PROC_REF(return_mind)) /datum/component/temporary_body/UnregisterFromParent() UnregisterSignal(parent, COMSIG_QDELETING) + UnregisterSignal(parent, COMSIG_LIVING_DEATH) + UnregisterSignal(old_mind, COMSIG_MIND_TRANSFERRED) + if(!isnull(old_body)) + unregister_body_signals() + +/datum/component/temporary_body/proc/unregister_body_signals() + REMOVE_TRAIT(old_body, TRAIT_MIND_TEMPORARILY_GONE, REF(src)) + UnregisterSignal(old_body, COMSIG_QDELETING) + UnregisterSignal(old_body, COMSIG_LIVING_REVIVE) + +/datum/component/temporary_body/Destroy() + . = ..() + old_mind = null + old_body = null + +/// Swap the target of old_body if old_mind is transferred somewhere while we're away +/datum/component/temporary_body/proc/on_mind_transfer(...) + SIGNAL_HANDLER + + if(!isnull(old_body)) + unregister_body_signals() + old_body = old_mind.current + if(!isnull(old_body)) + register_body_signals() /** * 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. + * If the previous host is alive, we'll also force them into the body. * Otherwise we'll let them hang out as a ghost still. */ -/datum/component/temporary_body/proc/on_parent_destroy() +/datum/component/temporary_body/proc/return_mind(...) SIGNAL_HANDLER - var/datum/mind/old_mind = old_mind_ref?.resolve() - var/mob/living/old_body = old_body_ref?.resolve() || old_mind.current - if(!old_mind) + var/mob/new_body = parent + var/mob/dead/observer/ghost = new_body.get_ghost() || new_body.ghostize() + if(QDELETED(ghost)) + stack_trace("[src] belonging to [parent] was completely unable to find a ghost to put back into a body!") + qdel(src) // i guess this is useless now 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) - if(old_body) - REMOVE_TRAIT(old_body, TRAIT_MIND_TEMPORARILY_GONE, REF(src)) + if(old_mind.current != old_body) + stack_trace("Temporary body returning mind to old body, but the mind's current body doesn't match the old body!") + old_mind.set_current(old_body) + if(old_body.stat != DEAD) + ghost.reenter_corpse() - old_mind = null + qdel(src) // we're done here + +/// Body reference handling +/datum/component/temporary_body/proc/on_body_destroy(...) + SIGNAL_HANDLER old_body = null diff --git a/code/datums/saymode.dm b/code/datums/saymode.dm index 951c805f0af..9ca4568ac2b 100644 --- a/code/datums/saymode.dm +++ b/code/datums/saymode.dm @@ -35,20 +35,32 @@ mode = MODE_CHANGELING /datum/saymode/changeling/can_be_used_by(mob/living/user) - if(!user.mind) - return FALSE - if(user.mind.has_antag_datum(/datum/antagonist/fallen_changeling)) + if(user.mind?.has_antag_datum(/datum/antagonist/fallen_changeling)) + // special message for you to_chat(user, span_changeling("We're cut off from the hivemind! We've lost everything! EVERYTHING!!")) return FALSE - var/datum/antagonist/changeling/ling_sender = IS_CHANGELING(user) - if(!ling_sender) + if(!HAS_TRAIT(user, TRAIT_CHANGELING_HIVEMIND)) return FALSE - if(HAS_TRAIT(user, TRAIT_CHANGELING_HIVEMIND_MUTE)) + if(is_muted(user)) to_chat(user, span_warning("The poison in the air hinders our ability to interact with the hivemind.")) return FALSE return TRUE -/datum/saymode/changeling/handle_message/handle_message( +/datum/saymode/changeling/proc/is_muted(mob/living/user) + return user.reagents?.has_reagent(/datum/reagent/bz_metabolites, needs_metabolizing = TRUE) + +/datum/saymode/changeling/proc/get_lings() + . = list() + for(var/mob/ling_mob as anything in GLOB.mob_list) + //removes types that override the presence of being changeling (for example, borged lings still can't hivemind chat) + if(!HAS_TRAIT(ling_mob, TRAIT_CHANGELING_HIVEMIND) || issilicon(ling_mob) || isbrain(ling_mob) ) + continue + // can't receive messages on the hivemind right now + if(is_muted(ling_mob)) + continue + . += ling_mob + +/datum/saymode/changeling/handle_message( mob/living/user, message, list/spans = list(), @@ -56,20 +68,14 @@ list/message_mods = list() ) var/datum/antagonist/changeling/ling_sender = IS_CHANGELING(user) - user.log_talk(message, LOG_SAY, tag = "changeling [ling_sender.changelingID]") - var/msg = span_changeling("[ling_sender.changelingID]: [message]") + + var/id = ling_sender?.changelingID || user.real_name + + user.log_talk(message, LOG_SAY, tag = "[id]") + var/msg = span_changeling("[id]: [message]") // Send the message to our other changelings. - for(var/datum/antagonist/changeling/ling_receiver in GLOB.antagonists) - if(!ling_receiver.owner) - continue - var/mob/living/ling_mob = ling_receiver.owner.current - //removes types that override the presence of being changeling (for example, borged lings still can't hivemind chat) - if(!isliving(ling_mob) || issilicon(ling_mob) || isbrain(ling_mob)) - continue - // can't receive messages on the hivemind right now - if(HAS_TRAIT(ling_mob, TRAIT_CHANGELING_HIVEMIND_MUTE)) - continue + for(var/mob/ling_mob as anything in get_lings()) to_chat(ling_mob, msg, type = MESSAGE_TYPE_RADIO, avoid_highlighting = ling_mob == user) for(var/mob/dead/ghost as anything in GLOB.dead_mob_list) diff --git a/code/modules/antagonists/changeling/changeling.dm b/code/modules/antagonists/changeling/changeling.dm index 4374213117b..dd6b874786f 100644 --- a/code/modules/antagonists/changeling/changeling.dm +++ b/code/modules/antagonists/changeling/changeling.dm @@ -145,6 +145,7 @@ RegisterSignals(living_mob, list(COMSIG_MOB_MIDDLECLICKON, COMSIG_MOB_ALTCLICKON), PROC_REF(on_click_sting)) ADD_TRAIT(living_mob, TRAIT_FAKE_SOULLESS, CHANGELING_TRAIT) ADD_TRAIT(living_mob, TRAIT_BRAINLESS_CARBON, CHANGELING_TRAIT) + ADD_TRAIT(living_mob, TRAIT_CHANGELING_HIVEMIND, CHANGELING_TRAIT) if(living_mob.hud_used) var/datum/hud/hud_used = living_mob.hud_used @@ -205,6 +206,10 @@ UnregisterSignal(living_mob, list(COMSIG_MOB_LOGIN, COMSIG_LIVING_LIFE, COMSIG_LIVING_POST_FULLY_HEAL, COMSIG_MOB_GET_STATUS_TAB_ITEMS, COMSIG_MOB_MIDDLECLICKON, COMSIG_MOB_ALTCLICKON)) REMOVE_TRAIT(living_mob, TRAIT_FAKE_SOULLESS, CHANGELING_TRAIT) REMOVE_TRAIT(living_mob, TRAIT_BRAINLESS_CARBON, CHANGELING_TRAIT) + REMOVE_TRAIT(living_mob, TRAIT_CHANGELING_HIVEMIND, CHANGELING_TRAIT) + + for(var/mob/eye/imaginary_friend/hivemind/hivemind_member in living_mob.imaginary_group) + qdel(hivemind_member) if(living_mob.hud_used) var/datum/hud/hud_used = living_mob.hud_used diff --git a/code/modules/antagonists/changeling/powers/absorb.dm b/code/modules/antagonists/changeling/powers/absorb.dm index d8ebc7c5998..23dad3450c3 100644 --- a/code/modules/antagonists/changeling/powers/absorb.dm +++ b/code/modules/antagonists/changeling/powers/absorb.dm @@ -40,7 +40,15 @@ SSblackbox.record_feedback("nested tally", "changeling_powers", 1, list("Absorb DNA", "4")) owner.visible_message(span_danger("[owner] sucks the fluids from [target]!"), span_notice("We have absorbed [target].")) - to_chat(target, span_userdanger("You are absorbed by the changeling!")) + + if(target.client && target.mind) + var/mob/eye/imaginary_friend/hivemind/new_member = new(target.loc) + new_member.AddComponent(/datum/component/temporary_body, old_mind = target.mind, return_on_revive = TRUE) + new_member.real_name = target.real_name + new_member.gender = target.gender + new_member.human_icon = get_flat_human_icon(null, target.mind.assigned_role, target.client.prefs) + new_member.PossessByPlayer(target.ckey) + new_member.attach_to_owner(owner) var/true_absorbtion = (!isnull(target.client) || !isnull(target.mind) || !isnull(target.last_mind)) if (!true_absorbtion) @@ -160,3 +168,128 @@ is_absorbing = FALSE return FALSE return TRUE + +/mob/eye/imaginary_friend/hivemind + name = "hivemind member" + desc = "A drifting thought in the ocean that is the changeling hivemind." + distance_allowance = 5 + require_los = TRUE + hidden = TRUE + /// Don't spam the hivemind on login logout + var/alerted = FALSE + +/mob/eye/imaginary_friend/hivemind/Initialize(mapload) + . = ..() + ADD_TRAIT(src, TRAIT_CHANGELING_HIVEMIND, INNATE_TRAIT) + var/datum/action/innate/exit_hivemind/exit_action = new(src) + exit_action.Grant(src) + +/mob/eye/imaginary_friend/hivemind/Login() + . = ..() + client.eye = owner || src + +/mob/eye/imaginary_friend/hivemind/greet() + + var/greet_message = "" + greet_message += separator_hr(span_danger(span_slightly_larger("You are absorbed by the changeling!"))) + greet_message += "You are now a part of the changeling hivemind, and can communicate with them freely by speaking. \ + Your knowledge may be useful to them..." + greet_message += "
You may also choose to exit the hivemind \ + if you prefer to observe instead." + greet_message += "
Either way, if your body or brain is revived, you will be returned to it as normal." + + to_chat(src, boxed_message(span_changeling(greet_message))) + + if(!alerted) + alerted = TRUE + alert_hivemind("You sense the presence of [real_name] in the hivemind.") + +/mob/eye/imaginary_friend/hivemind/send_speech(message, range, obj/source, bubble_type, list/spans, datum/language/message_language, list/message_mods = list(), forced) + // chops any message mods, even though they won't actually do anything + message = get_message_mods(message, message_mods) + // forces message through the changeling saymode + var/datum/saymode/changeling/saymode = SSradio.saymodes[/datum/saymode/changeling::key] + saymode.handle_message(src, message, spans, message_language, message_mods) + +/mob/eye/imaginary_friend/hivemind/Topic(href, list/href_list) + . = ..() + if(href_list["exit_hivemind"] && !QDELETED(src)) + exit_hivemind() + +/mob/eye/imaginary_friend/hivemind/verb/exit_hivemind() + set category = "IC" + set name = "Exit Hivemind" + set desc = "Leave the hivemind behind and enter the land of the dead." + + var/response = tgui_alert(src, "Are you sure you want to exit the hivemind? \ + You can't re-enter it, though you can still be revived.", "Confirm Exit", list("Exit", "Stay")) + if(response != "Exit" || QDELETED(src)) + return + ghostize(TRUE) + +/mob/eye/imaginary_friend/hivemind/ghostize(can_reenter_corpse, admin_ghost) + . = ..() + if(admin_ghost) + return + + alert_hivemind("You sense the presence of [real_name] disappear from the hivemind.") + if(!QDELING(src)) + qdel(src) + +/mob/eye/imaginary_friend/hivemind/proc/alert_hivemind(message) + var/datum/saymode/changeling/saymode = SSradio.saymodes[/datum/saymode/changeling::key] + for(var/mob/ling_mob as anything in saymode.get_lings() - src) + to_chat(ling_mob, span_changeling("[message]")) + +/mob/eye/imaginary_friend/hivemind/attach_to_owner(mob/living/imaginary_friend_owner) + . = ..() + client?.eye = owner + if(locate(/datum/action/changeling/eject_from_hivemind) in owner.actions) + return + var/datum/action/changeling/eject_from_hivemind/ejector = new(owner) + ejector.Grant(owner) + +/mob/eye/imaginary_friend/hivemind/Destroy() + for(var/mob/eye/imaginary_friend/hivemind/other_member in owner.imaginary_group - src) + return ..() + + var/datum/action/changeling/eject_from_hivemind/ejector = locate() in owner.actions + qdel(ejector) + return ..() + +/datum/action/innate/exit_hivemind + name = "Exit Hivemind" + desc = "Exit the changeling hivemind permanently. You may still be revived later." + // button_icon_state = "exit_hivemind" + +/datum/action/innate/exit_hivemind/Activate() + var/mob/eye/imaginary_friend/hivemind/member = owner + member.exit_hivemind() + +/// Allow the changeling to nuke people griffing them +/datum/action/changeling/eject_from_hivemind + name = "Eject from Hivemind" + desc = "Eject an unwanted member from the hivemind." + // button_icon_state = "eject_hivemind" + chemical_cost = 0 + dna_cost = CHANGELING_POWER_UNOBTAINABLE + +/datum/action/changeling/eject_from_hivemind/sting_action(mob/living/user) + var/list/freeloaders = list() + for(var/mob/eye/imaginary_friend/hivemind/freeloader in user.imaginary_group) + freeloaders[freeloader.real_name] = freeloader + + if(!length(freeloaders)) + stack_trace("Tried to eject from hivemind with no hivemind members!") + qdel(src) + return FALSE + + var/chosen = tgui_input_list(owner, "Choose a member to eject from the hivemind.", "Eject Hivemind Member", freeloaders) + var/mob/eye/imaginary_friend/hivemind/freeloader = freeloaders[chosen] + if(QDELETED(freeloader) || QDELETED(src)) + return FALSE + + to_chat(freeloader, span_userdanger("You have been ejected from the changeling hivemind!")) + qdel(freeloader) + ..() + return TRUE diff --git a/code/modules/antagonists/cult/runes.dm b/code/modules/antagonists/cult/runes.dm index be3a5b92d2d..ab217c39956 100644 --- a/code/modules/antagonists/cult/runes.dm +++ b/code/modules/antagonists/cult/runes.dm @@ -1016,7 +1016,6 @@ GLOBAL_VAR_INIT(narsie_summon_count, 0) new_human.AddComponent( \ /datum/component/temporary_body, \ old_mind = ghost_to_spawn.mind, \ - old_body = ghost_to_spawn.mind.current, \ ) new_human.PossessByPlayer(ghost_to_spawn.key) var/datum/antagonist/cult/created_cultist = new_human.mind?.add_antag_datum(/datum/antagonist/cult) diff --git a/code/modules/basketball/controller.dm b/code/modules/basketball/controller.dm index de32aa766bb..078624e775e 100644 --- a/code/modules/basketball/controller.dm +++ b/code/modules/basketball/controller.dm @@ -196,7 +196,6 @@ GLOBAL_VAR(basketball_game) baller.AddComponent( \ /datum/component/temporary_body, \ old_mind = player_client.mob.mind, \ - old_body = player_client.mob.mind.current, \ ) baller.PossessByPlayer(player_key) minigame_basketball_mobs |= baller diff --git a/code/modules/bitrunning/server/threats.dm b/code/modules/bitrunning/server/threats.dm index c8d21785960..34583aa358d 100644 --- a/code/modules/bitrunning/server/threats.dm +++ b/code/modules/bitrunning/server/threats.dm @@ -114,7 +114,7 @@ new_mob.PossessByPlayer(ghost.key) if(ghost_mind) - new_mob.AddComponent(/datum/component/temporary_body, ghost_mind, ghost_mind.current, TRUE) + new_mob.AddComponent(/datum/component/temporary_body, ghost_mind, return_on_death = TRUE) var/datum/mind/antag_mind = new_mob.mind antag_mind.add_antag_datum(chosen_role) diff --git a/code/modules/bitrunning/spawners.dm b/code/modules/bitrunning/spawners.dm index a7f742d9d42..36fd7ba2b2c 100644 --- a/code/modules/bitrunning/spawners.dm +++ b/code/modules/bitrunning/spawners.dm @@ -12,7 +12,7 @@ /obj/effect/mob_spawn/ghost_role/human/virtual_domain/special(mob/living/spawned_mob, mob/mob_possessor, apply_prefs) var/datum/mind/ghost_mind = mob_possessor.mind if(ghost_mind) // Preserves any previous bodies before making the switch - spawned_mob.AddComponent(/datum/component/temporary_body, ghost_mind, ghost_mind.current, TRUE) + spawned_mob.AddComponent(/datum/component/temporary_body, ghost_mind, return_on_death = TRUE) ..() diff --git a/code/modules/capture_the_flag/ctf_game.dm b/code/modules/capture_the_flag/ctf_game.dm index 56636f28a73..b10513eca27 100644 --- a/code/modules/capture_the_flag/ctf_game.dm +++ b/code/modules/capture_the_flag/ctf_game.dm @@ -157,7 +157,6 @@ player_mob.AddComponent( \ /datum/component/temporary_body, \ old_mind = new_member_mind, \ - old_body = new_member_mind.current, \ ) player_mob.ckey = new_team_member.ckey diff --git a/code/modules/deathmatch/deathmatch_lobby.dm b/code/modules/deathmatch/deathmatch_lobby.dm index af4408ef58c..3bb15bd8105 100644 --- a/code/modules/deathmatch/deathmatch_lobby.dm +++ b/code/modules/deathmatch/deathmatch_lobby.dm @@ -145,7 +145,6 @@ new_player.AddComponent( \ /datum/component/temporary_body, \ old_mind = observer.mind, \ - old_body = observer.mind.current, \ ) new_player.equipOutfit(loadout) // Loadout new_player.PossessByPlayer(ckey) diff --git a/code/modules/mafia/roles/roles.dm b/code/modules/mafia/roles/roles.dm index 5f22153a03e..a26c6f40d38 100644 --- a/code/modules/mafia/roles/roles.dm +++ b/code/modules/mafia/roles/roles.dm @@ -130,7 +130,6 @@ body.AddComponent( \ /datum/component/temporary_body, \ old_mind = player.mob.mind, \ - old_body = player.mob.mind.current, \ ) body.PossessByPlayer(player.key) ADD_TRAIT(body, TRAIT_CORPSELOCKED, MAFIA_TRAIT) diff --git a/code/modules/reagents/chemistry/reagents/other_reagents.dm b/code/modules/reagents/chemistry/reagents/other_reagents.dm index 5157eee170c..fe3effbc61f 100644 --- a/code/modules/reagents/chemistry/reagents/other_reagents.dm +++ b/code/modules/reagents/chemistry/reagents/other_reagents.dm @@ -2641,7 +2641,6 @@ taste_description = "acrid cinnamon" metabolization_rate = 0.2 * REAGENTS_METABOLISM chemical_flags = REAGENT_CAN_BE_SYNTHESIZED|REAGENT_NO_RANDOM_RECIPE - metabolized_traits = list(TRAIT_CHANGELING_HIVEMIND_MUTE) /datum/reagent/bz_metabolites/on_mob_life(mob/living/carbon/target, seconds_per_tick, metabolization_ratio) . = ..()