From aef7cc4ab2a384e959fe35b1944f388c48e585fc Mon Sep 17 00:00:00 2001
From: SkyratBot <59378654+SkyratBot@users.noreply.github.com>
Date: Mon, 14 Feb 2022 07:10:49 +0100
Subject: [PATCH] [MIRROR] Refactors slimelink and mansus link to be a
component to cut down on hard-dels / code copy+paste [MDB IGNORE] (#11496)
* Refactors slimelink and mansus link to be a component to cut down on hard-dels / code copy+paste (#64836)
So this started out as a hard delete fix for the mansus link
Then I realized the mansus link was literally copy+pasted exactly the same code
And the reason why mansus link was hard-delling was because slime link used to hard-del and those were fixed but those fixes were never copied over
So in a fit of rage I refactored the slimelink to be a component, datum/component/mind_linker. Raw Prophets and Stargazers use it.
I'm not 100% certain on some of the methods used but it works? It works.
* Refactors slimelink and mansus link to be a component to cut down on hard-dels / code copy+paste
Co-authored-by: MrMelbert <51863163+MrMelbert@users.noreply.github.com>
---
code/datums/action.dm | 2 +-
code/datums/components/mind_linker.dm | 213 ++++++++++++++++++
.../antagonists/heretic/magic/manse_link.dm | 127 ++++++-----
.../carbon/human/species_types/jellypeople.dm | 206 +++++++----------
.../living/simple_animal/heretic_monsters.dm | 74 ++----
tgstation.dme | 1 +
6 files changed, 377 insertions(+), 246 deletions(-)
create mode 100644 code/datums/components/mind_linker.dm
diff --git a/code/datums/action.dm b/code/datums/action.dm
index 0e31fcc7957..9dede2540a2 100644
--- a/code/datums/action.dm
+++ b/code/datums/action.dm
@@ -695,7 +695,7 @@
// Shares cooldowns with other cooldown abilities of the same value, not active if null
var/shared_cooldown
-/datum/action/cooldown/New()
+/datum/action/cooldown/New(Target)
..()
button.maptext = ""
button.maptext_x = 8
diff --git a/code/datums/components/mind_linker.dm b/code/datums/components/mind_linker.dm
new file mode 100644
index 00000000000..209a0c431ac
--- /dev/null
+++ b/code/datums/components/mind_linker.dm
@@ -0,0 +1,213 @@
+/**
+ * # Mind Linker
+ *
+ * A component that handles linking multiple player's minds
+ * into one network which allows them to talk directly to one another.
+ * Like telepathy but for multiple people at once!
+ */
+/datum/component/mind_linker
+ /// The name of our network, displayed to all users.
+ var/network_name = "Mind Link"
+ /// The color of the network when talking in chat
+ var/chat_color
+ /// The message sent to someone when linked up.
+ var/link_message
+ /// The message sent to someone when unlinked.
+ var/unlink_message
+ /// A list of all signals that will call qdel() on our component if triggered. Optional.
+ var/list/signals_which_destroy_us
+ /// A callback invoked after an unlink is done. Optional.
+ var/datum/callback/post_unlink_callback
+ /// The icon file given to the speech action handed out.
+ var/speech_action_icon = 'icons/mob/actions/actions_slime.dmi'
+ /// The icon state applied to the speech action handed out.
+ var/speech_action_icon_state = "link_speech"
+ /// The icon background for the speech action handed out.
+ var/speech_action_background_icon_state = "bg_alien"
+ /// The master's linking action, which allows them to link people to the network.
+ var/datum/action/linker_action
+ /// The master's speech action. The owner of the link shouldn't lose this as long as the link remains.
+ var/datum/action/innate/linked_speech/master_speech
+ /// An assoc list of [mob/living]s to [datum/action/innate/linked_speech]s. All the mobs that are linked to our network.
+ var/list/mob/living/linked_mobs = list()
+
+/datum/component/mind_linker/Initialize(
+ network_name = "Mind Link",
+ chat_color = "#008CA2",
+ linker_action_path,
+ link_message,
+ unlink_message,
+ signals_which_destroy_us,
+ datum/callback/post_unlink_callback,
+ speech_action_icon = 'icons/mob/actions/actions_slime.dmi',
+ speech_action_icon_state = "link_speech",
+ speech_action_background_icon_state = "bg_alien",
+ )
+
+ if(!isliving(parent))
+ return COMPONENT_INCOMPATIBLE
+
+ var/mob/living/owner = parent
+
+ src.network_name = network_name
+ src.chat_color = chat_color
+ src.link_message = link_message || "You are now connected to [owner.real_name]'s [network_name]."
+ src.unlink_message = unlink_message || "You are no longer connected to [owner.real_name]'s [network_name]."
+
+ if(islist(signals_which_destroy_us))
+ src.signals_which_destroy_us = signals_which_destroy_us
+ if(post_unlink_callback)
+ src.post_unlink_callback = post_unlink_callback
+
+ src.speech_action_icon = speech_action_icon
+ src.speech_action_icon_state = speech_action_icon_state
+ src.speech_action_background_icon_state = speech_action_background_icon_state
+
+ if(ispath(linker_action_path))
+ linker_action = new linker_action_path(src)
+ linker_action.Grant(owner)
+ else
+ stack_trace("[type] was created without a valid linker_action_path. No one will be able to link to it.")
+
+ master_speech = new(src)
+ master_speech.Grant(owner)
+
+ to_chat(owner, span_boldnotice("You establish a [network_name], allowing you to link minds to communicate telepathically."))
+
+/datum/component/mind_linker/Destroy(force, silent)
+ for(var/mob/living/remaining_mob as anything in linked_mobs)
+ unlink_mob(remaining_mob)
+ linked_mobs.Cut()
+ QDEL_NULL(linker_action)
+ QDEL_NULL(master_speech)
+ QDEL_NULL(post_unlink_callback)
+ return ..()
+
+/datum/component/mind_linker/RegisterWithParent()
+ if(signals_which_destroy_us)
+ RegisterSignal(parent, signals_which_destroy_us, .proc/destroy_link)
+
+/datum/component/mind_linker/UnregisterFromParent()
+ if(signals_which_destroy_us)
+ UnregisterSignal(parent, signals_which_destroy_us)
+
+/**
+ * Attempts to link [to_link] to our network, giving them a speech action.
+ *
+ * Returns TRUE if successful, FALSE otherwise
+ */
+/datum/component/mind_linker/proc/link_mob(mob/living/to_link)
+ if(QDELETED(to_link) || to_link.stat == DEAD)
+ return FALSE
+ if(HAS_TRAIT(to_link, TRAIT_MINDSHIELD)) // Mindshield implant - no dice
+ return FALSE
+ if(to_link.anti_magic_check(FALSE, FALSE, TRUE, 0))
+ return FALSE
+ if(linked_mobs[to_link])
+ return FALSE
+
+ var/mob/living/owner = parent
+ if(to_link == owner)
+ return FALSE
+
+ to_chat(to_link, span_notice(link_message))
+ to_chat(owner, span_notice("You connect [to_link]'s mind to your [network_name]."))
+
+ for(var/mob/living/other_link as anything in linked_mobs)
+ to_chat(other_link, span_notice("You feel a new presence within [owner.real_name]'s [network_name]."))
+
+ var/datum/action/innate/linked_speech/new_link = new(src)
+ new_link.Grant(to_link)
+
+ linked_mobs[to_link] = new_link
+ RegisterSignal(to_link, list(COMSIG_LIVING_DEATH, COMSIG_PARENT_QDELETING, COMSIG_MINDSHIELD_IMPLANTED), .proc/unlink_mob)
+
+ return TRUE
+
+/**
+ * Unlinks [to_unlink] from our network, deleting their speech action
+ * and cleaning up anything involved.
+ *
+ * Also invokes post_unlink_callback, if supplied.
+ */
+/datum/component/mind_linker/proc/unlink_mob(mob/living/to_unlink)
+ SIGNAL_HANDLER
+
+ if(!linked_mobs[to_unlink])
+ return
+
+ to_chat(to_unlink, span_warning(unlink_message))
+ post_unlink_callback?.Invoke(to_unlink)
+
+ UnregisterSignal(to_unlink, list(COMSIG_LIVING_DEATH, COMSIG_PARENT_QDELETING, COMSIG_MINDSHIELD_IMPLANTED))
+
+ var/datum/action/innate/linked_speech/old_link = linked_mobs[to_unlink]
+ linked_mobs -= to_unlink
+ qdel(old_link)
+
+ var/mob/living/owner = parent
+
+ to_chat(owner, span_warning("You feel someone disconnect from your [network_name]."))
+ for(var/mob/living/other_link as anything in linked_mobs)
+ to_chat(other_link, span_warning("You feel a pressence disappear from [owner.real_name]'s [network_name]."))
+
+/**
+ * Signal proc sent from any signals given to us initialize.
+ * Destroys our component and unlinks everyone.
+ */
+/datum/component/mind_linker/proc/destroy_link(datum/source)
+ SIGNAL_HANDLER
+
+ if(isliving(source))
+ var/mob/living/owner = source
+ to_chat(owner, span_boldwarning("Your [network_name] breaks!"))
+
+ qdel(src)
+
+/datum/action/innate/linked_speech
+ name = "Mind Link Speech"
+ desc = "Send a psychic message to everyone connected to your Link."
+ button_icon_state = "link_speech"
+ icon_icon = 'icons/mob/actions/actions_slime.dmi'
+ background_icon_state = "bg_alien"
+
+/datum/action/innate/linked_speech/New(Target)
+ . = ..()
+ if(!istype(Target, /datum/component/mind_linker))
+ stack_trace("[name] ([type]) was instantiated on a non-mind_linker target, this doesn't work.")
+ qdel(src)
+ return
+
+ var/datum/component/mind_linker/linker = Target
+ name = "[linker.network_name] Speech"
+ desc = "Send a psychic message to everyone connected to your [linker.network_name]."
+ icon_icon = linker.speech_action_icon
+ button_icon_state = linker.speech_action_icon_state
+ background_icon_state = linker.speech_action_background_icon_state
+
+/datum/action/innate/linked_speech/IsAvailable()
+ return ..() && (owner.stat != DEAD)
+
+/datum/action/innate/linked_speech/Activate()
+
+ var/datum/component/mind_linker/linker = target
+ var/mob/living/linker_parent = linker.parent
+
+ var/message = sanitize(tgui_input_text(owner, "Enter a message to transmit.", "[linker.network_name] Telepathy"))
+ if(!message || QDELETED(src) || QDELETED(owner) || owner.stat == DEAD)
+ return
+
+ if(QDELETED(linker))
+ to_chat(owner, span_warning("The link seems to have been severed."))
+ return
+
+ var/formatted_message = "\[[linker_parent.real_name]'s [linker.network_name]\] [owner]: [message]"
+ log_directed_talk(owner, linker_parent, message, LOG_SAY, "mind link ([linker.network_name])")
+
+ var/list/all_who_can_hear = assoc_to_keys(linker.linked_mobs) + linker_parent
+
+ for(var/mob/living/recipient as anything in all_who_can_hear)
+ to_chat(recipient, formatted_message)
+
+ for(var/mob/recipient as anything in GLOB.dead_mob_list)
+ to_chat(recipient, "[FOLLOW_LINK(recipient, owner)] [formatted_message]")
diff --git a/code/modules/antagonists/heretic/magic/manse_link.dm b/code/modules/antagonists/heretic/magic/manse_link.dm
index e4f03646216..1b5cdea5b75 100644
--- a/code/modules/antagonists/heretic/magic/manse_link.dm
+++ b/code/modules/antagonists/heretic/magic/manse_link.dm
@@ -1,71 +1,74 @@
-/obj/effect/proc_holder/spell/pointed/manse_link
- name = "Mansus Link"
- desc = "Piercing through reality, connecting minds. This spell allows you to add people to a Mansus Net, allowing them to communicate with each other from afar."
- action_icon = 'icons/mob/actions/actions_ecult.dmi'
- action_icon_state = "mansus_link"
- action_background_icon_state = "bg_ecult"
- invocation = "PI'RC' TH' M'ND"
- invocation_type = INVOCATION_WHISPER
- school = SCHOOL_FORBIDDEN
- charge_max = 300
- clothes_req = FALSE
- range = 10
+// Manse link action for Raw Prophets
+// Actually an action larping as a spell, because spells don't track what they're attached to.
+/datum/action/cooldown/manse_link
+ name = "Manse Link"
+ desc = "This spell allows you to pierce through reality and connect minds to one another \
+ via your Mansus Link. All minds connected to your Mansus Link will be able to communicate discreetly across great distances."
+ icon_icon = 'icons/mob/actions/actions_ecult.dmi'
+ button_icon_state = "mansus_link"
+ background_icon_state = "bg_ecult"
+ cooldown_time = 20 SECONDS
+ text_cooldown = FALSE
+ click_to_activate = TRUE
+ /// The time it takes to link to a mob.
+ var/link_time = 6 SECONDS
+ /// The range of the cast. Expanded beyond normal view range by default, as Raw Prophets have a larger sight range.
+ var/range = 10
+ /// The text the caster is forced tos ay.
+ var/invocation_text = "PI'RC' TH' M'ND"
-/obj/effect/proc_holder/spell/pointed/manse_link/can_target(atom/target, mob/user, silent)
- if(!isliving(target))
+/datum/action/cooldown/manse_link/New(Target)
+ . = ..()
+ if(!istype(Target, /datum/component/mind_linker))
+ stack_trace("[name] ([type]) was instantiated on a non-mind_linker target, this doesn't work.")
+ qdel(src)
+
+/datum/action/cooldown/manse_link/InterceptClickOn(mob/living/caller, params, atom/clicked_on)
+ if(!isliving(clicked_on))
return FALSE
+ if(clicked_on == caller)
+ return FALSE
+ if(get_dist(caller, clicked_on) > range)
+ to_chat(caller, span_warning("[clicked_on] is too far to establish a link.")) // Not a balloon alert due being so zoomed out.
+ return FALSE
+
+ return ..()
+
+/datum/action/cooldown/manse_link/Activate(atom/victim)
+ owner.say("#[invocation_text]", forced = "spell")
+
+ // Short cooldown placed during the channel to prevent spam links.
+ StartCooldown(10 SECONDS)
+
+ // If we link successfuly, we can start the full cooldown duration.
+ if(do_linking(victim))
+ StartCooldown()
+
return TRUE
-/obj/effect/proc_holder/spell/pointed/manse_link/cast(list/targets, mob/user)
- var/mob/living/simple_animal/hostile/heretic_summon/raw_prophet/originator = user
+/**
+ * The actual process of linking [linkee] to our network.
+ */
+/datum/action/cooldown/manse_link/proc/do_linking(mob/living/linkee)
+ var/datum/component/mind_linker/linker = target
+ if(linkee.stat == DEAD)
+ to_chat(owner, span_warning("They're dead!"))
+ return FALSE
- var/mob/living/target = targets[1]
+ to_chat(owner, span_notice("You begin linking [linkee]'s mind to yours..."))
+ to_chat(linkee, span_warning("You feel your mind being pulled somewhere... connected... intertwined with the very fabric of reality..."))
- to_chat(originator, span_notice("You begin linking [target]'s mind to yours..."))
- to_chat(target, span_warning("You feel your mind being pulled... connected... intertwined with the very fabric of reality..."))
- if(!do_after(originator, 6 SECONDS, target))
- return
- if(!originator.link_mob(target))
- to_chat(originator, span_warning("You can't seem to link [target]'s mind..."))
- to_chat(target, span_warning("The foreign presence leaves your mind."))
- return
- to_chat(originator, span_notice("You connect [target]'s mind to your mansus link!"))
+ if(!do_after(owner, link_time, linkee))
+ to_chat(owner, span_warning("You fail to link to [linkee]'s mind."))
+ to_chat(linkee, span_warning("The foreign presence leaves your mind."))
+ return FALSE
+ if(QDELETED(src) || QDELETED(owner) || QDELETED(linkee))
+ return FALSE
-/datum/action/innate/mansus_speech
- name = "Mansus Link"
- desc = "Send a psychic message to everyone connected to your Mansus Net."
- button_icon_state = "link_speech"
- icon_icon = 'icons/mob/actions/actions_slime.dmi'
- background_icon_state = "bg_ecult"
- /// The raw prophet that hosts our link.
- var/mob/living/simple_animal/hostile/heretic_summon/raw_prophet/originator
+ if(!linker.link_mob(linkee))
+ to_chat(owner, span_warning("You can't seem to link to [linkee]'s mind."))
+ to_chat(linkee, span_warning("The foreign presence leaves your mind."))
+ return FALSE
-/datum/action/innate/mansus_speech/New(originator)
- . = ..()
- src.originator = originator
-
-/datum/action/innate/mansus_speech/Activate()
- var/mob/living/living_owner = owner
- if(!originator?.linked_mobs[living_owner])
- CRASH("Uh oh, a Mansus Link ([type]) got somehow called Activate() [isnull(originator) ? "without an originator Raw Prophet" : "without being in the originator's linked_mobs list"].")
-
- var/message = sanitize(tgui_input_text(living_owner, "Enter your message", "Telepathy from the Manse"))
- if(!message)
- return
-
- if(QDELETED(living_owner))
- return
-
- if(!originator?.linked_mobs[living_owner])
- to_chat(living_owner, span_warning("The link seems to have been severed..."))
- Remove(living_owner)
- return
-
- var/msg = "\[Mansus Link\] [living_owner]: [message]"
- log_directed_talk(living_owner, originator, msg, LOG_SAY, "Mansus Link")
- to_chat(originator.linked_mobs, msg)
-
- for(var/dead_mob in GLOB.dead_mob_list)
- var/link = FOLLOW_LINK(dead_mob, living_owner)
- to_chat(dead_mob, "[link] [msg]")
+ return TRUE
diff --git a/code/modules/mob/living/carbon/human/species_types/jellypeople.dm b/code/modules/mob/living/carbon/human/species_types/jellypeople.dm
index 3c335a1e106..f910bd41a52 100644
--- a/code/modules/mob/living/carbon/human/species_types/jellypeople.dm
+++ b/code/modules/mob/living/carbon/human/species_types/jellypeople.dm
@@ -598,125 +598,28 @@
/datum/species/jelly/stargazer
name = "Stargazer"
id = SPECIES_STARGAZER
- var/datum/action/innate/project_thought/project_thought
- var/datum/action/innate/link_minds/link_minds
- var/datum/action/innate/linked_speech/master_speech
- var/list/mob/living/linked_mobs = list()
- var/datum/weakref/slimelink_owner
- var/current_link_id = 0
+ /// Special "project thought" telepathy action for stargazers.
+ var/datum/action/innate/project_thought/project_action
+
+/datum/species/jelly/stargazer/on_species_gain(mob/living/carbon/grant_to, datum/species/old_species)
+ . = ..()
+ project_action = new(src)
+ project_action.Grant(grant_to)
+
+ grant_to.AddComponent(/datum/component/mind_linker, \
+ network_name = "Slime Link", \
+ linker_action_path = /datum/action/innate/link_minds, \
+ signals_which_destroy_us = list(COMSIG_SPECIES_LOSS), \
+ )
//Species datums don't normally implement destroy, but JELLIES SUCK ASS OUT OF A STEEL STRAW
/datum/species/jelly/stargazer/Destroy()
- for(var/mob/living/link_to_clear as anything in linked_mobs)
- unlink_mob(link_to_clear)
- linked_mobs.Cut()
- QDEL_NULL(project_thought)
- QDEL_NULL(link_minds)
- QDEL_NULL(master_speech)
- slimelink_owner = null
+ QDEL_NULL(project_action)
return ..()
-/datum/species/jelly/stargazer/on_species_loss(mob/living/carbon/C)
- ..()
- for(var/mob/living/link_to_clear as anything in linked_mobs)
- unlink_mob(link_to_clear)
- if(project_thought)
- QDEL_NULL(project_thought)
- if(link_minds)
- QDEL_NULL(link_minds)
- if(master_speech)
- QDEL_NULL(master_speech)
- slimelink_owner = null
-
-/datum/species/jelly/stargazer/spec_death(gibbed, mob/living/carbon/human/H)
- ..()
- for(var/mob/living/link_to_clear as anything in linked_mobs)
- unlink_mob(link_to_clear)
-
-/datum/species/jelly/stargazer/on_species_gain(mob/living/carbon/C, datum/species/old_species)
- ..()
- project_thought = new(src)
- project_thought.Grant(C)
- link_minds = new(src)
- link_minds.Grant(C)
- master_speech = new(src)
- master_speech.Grant(C)
- slimelink_owner = WEAKREF(C)
-
-/**
- * Adds a living, not mind/psych shielded mob to the linked_mobs list and gives him
- * the ability to chat with other mobs in the same lists.
- * Don't call this proc on the owner; stat, mindshield, antimagic checks and superfluous comsigs
- * will only lead to bugs in that case. He has his own ability.
- */
-/datum/species/jelly/stargazer/proc/link_mob(mob/living/mob_linked)
- if(QDELETED(mob_linked) || mob_linked.stat == DEAD)
- return FALSE
- if(HAS_TRAIT(mob_linked, TRAIT_MINDSHIELD)) //mindshield implant, no dice
- return FALSE
- if(mob_linked.anti_magic_check(FALSE, FALSE, TRUE, 0))
- return FALSE
- if(mob_linked in linked_mobs)
- return FALSE
- var/mob/living/carbon/human/owner = slimelink_owner.resolve()
- if(!owner)
- return FALSE
- to_chat(mob_linked, span_notice("You are now connected to [owner.real_name]'s Slime Link."))
- var/datum/action/innate/linked_speech/action = new(src)
- action.Grant(mob_linked)
- linked_mobs[mob_linked] = action
- RegisterSignal(mob_linked, list(COMSIG_LIVING_DEATH, COMSIG_PARENT_QDELETING, SIGNAL_ADDTRAIT(TRAIT_MINDSHIELD)), .proc/unlink_mob)
- return TRUE
-
-/datum/species/jelly/stargazer/proc/unlink_mob(mob/living/mob_linked)
- SIGNAL_HANDLER
- if(!linked_mobs.Find(mob_linked))
- return
- UnregisterSignal(mob_linked, list(COMSIG_LIVING_DEATH, COMSIG_PARENT_QDELETING, SIGNAL_ADDTRAIT(TRAIT_MINDSHIELD)))
- var/datum/action/innate/linked_speech/action = linked_mobs[mob_linked]
- var/mob/living/carbon/human/owner = slimelink_owner.resolve()
- if(owner)
- to_chat(mob_linked, span_notice("You are no longer connected to [owner.real_name]'s Slime Link."))
- linked_mobs -= mob_linked
- qdel(action)
-
-/datum/action/innate/linked_speech
- name = "Slimelink"
- desc = "Send a psychic message to everyone connected to your slime link."
- button_icon_state = "link_speech"
- icon_icon = 'icons/mob/actions/actions_slime.dmi'
- background_icon_state = "bg_alien"
-
-/datum/action/innate/linked_speech/Activate()
- var/mob/living/carbon/human/human_user = owner
- if(human_user.stat == DEAD)
- return
-
- var/datum/species/jelly/stargazer/master_species = target
- var/mob/living/carbon/human/star_owner = master_species.slimelink_owner.resolve()
-
- if(star_owner != human_user && !(human_user in master_species.linked_mobs))
- to_chat(human_user, span_warning("The link seems to have been severed..."))
- return
-
- var/message = sanitize(tgui_input_text(human_user, "Enter a message", "Slime Telepathy"))
-
- if(!message)
- return
-
- if(QDELETED(master_species) || (star_owner != human_user && !(human_user in master_species.linked_mobs)))
- to_chat(human_user, span_warning("The link seems to have been severed..."))
- return
-
- var/msg = "\[[star_owner.real_name]'s Slime Link\] [human_user]: [message]"
- log_directed_talk(human_user, star_owner, msg, LOG_SAY, "slime link")
- to_chat(star_owner, msg)
- for(var/mob/living/recipient as anything in master_species.linked_mobs)
- to_chat(recipient, msg)
-
- for(var/mob/recipient as anything in GLOB.dead_mob_list)
- var/link = FOLLOW_LINK(recipient, human_user)
- to_chat(recipient, "[link] [msg]")
+/datum/species/jelly/stargazer/on_species_loss(mob/living/carbon/remove_from)
+ QDEL_NULL(project_action)
+ return ..()
/datum/action/innate/project_thought
name = "Send Thought"
@@ -762,26 +665,69 @@
button_icon_state = "mindlink"
icon_icon = 'icons/mob/actions/actions_slime.dmi'
background_icon_state = "bg_alien"
+ /// The species required to use this ability. Typepath.
+ var/req_species = /datum/species/jelly/stargazer
+ /// Whether we're currently linking to someone.
+ var/currently_linking = FALSE
+
+/datum/action/innate/link_minds/New(Target)
+ . = ..()
+ if(!istype(Target, /datum/component/mind_linker))
+ stack_trace("[name] ([type]) was instantiated on a non-mind_linker target, this doesn't work.")
+ qdel(src)
+
+/datum/action/innate/link_minds/IsAvailable()
+ . = ..()
+ if(!.)
+ return
+ if(!ishuman(owner) || !is_species(owner, req_species))
+ return FALSE
+ if(currently_linking)
+ return FALSE
+
+ return TRUE
/datum/action/innate/link_minds/Activate()
- var/mob/living/carbon/human/human_user = owner
- if(!is_species(human_user, /datum/species/jelly/stargazer))
+ if(!isliving(owner.pulling) || owner.grab_state < GRAB_AGGRESSIVE)
+ to_chat(owner, span_warning("You need to aggressively grab someone to link minds!"))
return
- if(!human_user.pulling || !isliving(human_user.pulling) || human_user.grab_state < GRAB_AGGRESSIVE)
- to_chat(human_user, span_warning("You need to aggressively grab someone to link minds!"))
+ var/mob/living/living_target = owner.pulling
+ if(living_target.stat == DEAD)
+ to_chat(owner, span_warning("They're dead!"))
return
- var/mob/living/living_target = human_user.pulling
- var/datum/species/jelly/stargazer/user_species = human_user.dna.species
-
- to_chat(human_user, span_notice("You begin linking [living_target]'s mind to yours..."))
+ to_chat(owner, span_notice("You begin linking [living_target]'s mind to yours..."))
to_chat(living_target, span_warning("You feel a foreign presence within your mind..."))
- if(do_after(human_user, 60, target = living_target))
- if(human_user.pulling != living_target || human_user.grab_state < GRAB_AGGRESSIVE)
- return
- if(user_species?.link_mob(living_target))
- to_chat(human_user, span_notice("You connect [living_target]'s mind to your slime link!"))
- else
- to_chat(human_user, span_warning("You can't seem to link [living_target]'s mind..."))
- to_chat(living_target, span_warning("The foreign presence leaves your mind."))
+ currently_linking = TRUE
+
+ if(!do_after(owner, 6 SECONDS, target = living_target, extra_checks = CALLBACK(src, .proc/while_link_callback, living_target)))
+ to_chat(owner, span_warning("You can't seem to link [living_target]'s mind."))
+ to_chat(living_target, span_warning("The foreign presence leaves your mind."))
+ currently_linking = FALSE
+ return
+
+ currently_linking = FALSE
+ if(QDELETED(src) || QDELETED(owner) || QDELETED(living_target))
+ return
+
+ var/datum/component/mind_linker/linker = target
+ if(!linker.link_mob(living_target))
+ to_chat(owner, span_warning("You can't seem to link [living_target]'s mind."))
+ to_chat(living_target, span_warning("The foreign presence leaves your mind."))
+
+
+/// Callback ran during the do_after of Activate() to see if we can keep linking with someone.
+/datum/action/innate/link_minds/proc/while_link_callback(mob/living/linkee)
+ if(!is_species(owner, req_species))
+ return FALSE
+ if(!owner.pulling)
+ return FALSE
+ if(owner.pulling != linkee)
+ return FALSE
+ if(owner.grab_state < GRAB_AGGRESSIVE)
+ return FALSE
+ if(linkee.stat == DEAD)
+ return FALSE
+
+ return TRUE
diff --git a/code/modules/mob/living/simple_animal/heretic_monsters.dm b/code/modules/mob/living/simple_animal/heretic_monsters.dm
index 6d91b60d240..c83635110ba 100644
--- a/code/modules/mob/living/simple_animal/heretic_monsters.dm
+++ b/code/modules/mob/living/simple_animal/heretic_monsters.dm
@@ -61,73 +61,41 @@
sight = SEE_MOBS|SEE_OBJS|SEE_TURFS
spells_to_add = list(
/obj/effect/proc_holder/spell/targeted/ethereal_jaunt/shift/ash/long,
- /obj/effect/proc_holder/spell/pointed/manse_link,
/obj/effect/proc_holder/spell/targeted/telepathy/eldritch,
/obj/effect/proc_holder/spell/pointed/trigger/blind/eldritch,
)
- /// A assoc list of [mob/living ref] to [datum/action ref] - all the mobs linked to our mansus network.
- var/list/mob/living/linked_mobs = list()
-
/mob/living/simple_animal/hostile/heretic_summon/raw_prophet/Initialize(mapload)
. = ..()
- link_mob(src)
+ var/on_link_message = "You feel something new enter your sphere of mind... \
+ You hear whispers of people far away, screeches of horror and a huming of welcome to [src]'s Mansus Link."
+
+ var/on_unlink_message = "Your mind shatters as [src]'s Mansus Link leaves your mind."
+
+ AddComponent(/datum/component/mind_linker, \
+ network_name = "Mansus Link", \
+ chat_color = "#568b00", \
+ linker_action_path = /datum/action/cooldown/manse_link, \
+ link_message = on_link_message, \
+ unlink_message = on_unlink_message, \
+ post_unlink_callback = CALLBACK(src, .proc/after_unlink), \
+ speech_action_background_icon_state = "bg_ecult", \
+ )
/mob/living/simple_animal/hostile/heretic_summon/raw_prophet/Login()
. = ..()
client?.view_size.setTo(10)
-/**
- * Link [linked_mob] to our mansus link, if possible.
- * Creates a mansus speech action and grants it to the linked mob,
- * storing it in our linked_mobs list.
+/*
+ * Callback for the mind_linker component.
+ * Stuns people who are ejected from the network.
*/
-/mob/living/simple_animal/hostile/heretic_summon/raw_prophet/proc/link_mob(mob/living/mob_linked)
- if(QDELETED(mob_linked) || mob_linked.stat == DEAD)
- return FALSE
- if(HAS_TRAIT(mob_linked, TRAIT_MINDSHIELD)) //mindshield implant, no dice
- return FALSE
- if(mob_linked.anti_magic_check(FALSE, FALSE, TRUE, 0))
- return FALSE
- if(linked_mobs[mob_linked])
- return FALSE
-
- to_chat(mob_linked, span_notice("You feel something new enter your sphere of mind... \
- You hear whispers of people far away, screeches of horror and a huming of welcome to [src]'s Mansus Link."))
-
- var/datum/action/innate/mansus_speech/action = new(src)
- linked_mobs[mob_linked] = action
- action.Grant(mob_linked)
-
- RegisterSignal(mob_linked, list(COMSIG_LIVING_DEATH, COMSIG_PARENT_QDELETING, SIGNAL_ADDTRAIT(TRAIT_MINDSHIELD)), .proc/unlink_mob)
-
- return TRUE
-
-/**
- * Signal proc that handles removing mobs from our mansus link.
- *
- * Remove the [mob_linked] from our list of linked mobs, and delete the associated action.
- */
-/mob/living/simple_animal/hostile/heretic_summon/raw_prophet/proc/unlink_mob(mob/living/mob_linked)
- SIGNAL_HANDLER
-
- if(QDELETED(linked_mobs[mob_linked]))
+/mob/living/simple_animal/hostile/heretic_summon/raw_prophet/proc/after_unlink(mob/living/unlinked_mob)
+ if(QDELETED(unlinked_mob) || unlinked_mob.stat == DEAD)
return
- UnregisterSignal(mob_linked, list(COMSIG_LIVING_DEATH, COMSIG_PARENT_QDELETING, SIGNAL_ADDTRAIT(TRAIT_MINDSHIELD)))
- var/datum/action/innate/mansus_speech/action = linked_mobs[mob_linked]
- action.Remove(mob_linked)
- qdel(action)
- to_chat(mob_linked, span_notice("Your mind shatters as [src]'s Mansus Link leaves your mind."))
- INVOKE_ASYNC(mob_linked, /mob.proc/emote, "scream")
- mob_linked.AdjustParalyzed(0.5 SECONDS) //micro stun
-
- linked_mobs -= mob_linked
-
-/mob/living/simple_animal/hostile/heretic_summon/raw_prophet/Destroy()
- for(var/linked_mob in linked_mobs)
- unlink_mob(linked_mob)
- return ..()
+ INVOKE_ASYNC(unlinked_mob, /mob.proc/emote, "scream")
+ unlinked_mob.AdjustParalyzed(0.5 SECONDS) //micro stun
// What if we took a linked list... But made it a mob?
/// The "Terror of the Night" / Armsy, a large worm made of multiple bodyparts that occupies multiple tiles
diff --git a/tgstation.dme b/tgstation.dme
index 2f5f066031e..645d6056193 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -768,6 +768,7 @@
#include "code\datums\components\manual_blinking.dm"
#include "code\datums\components\manual_breathing.dm"
#include "code\datums\components\material_container.dm"
+#include "code\datums\components\mind_linker.dm"
#include "code\datums\components\mirage_border.dm"
#include "code\datums\components\mirv.dm"
#include "code\datums\components\mood.dm"