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
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)
. = ..()