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"