From 8113154a2289f6d387b99efe7bd460e358b0ebf7 Mon Sep 17 00:00:00 2001 From: Kierany9 Date: Thu, 24 Jan 2019 18:14:08 +0100 Subject: [PATCH] The PR that fixed Assimilation (#42156) lots of commits --- code/__DEFINES/status_effects.dm | 4 + code/game/gamemodes/hivemind/hivemind.dm | 13 +++- code/game/gamemodes/hivemind/radar.dm | 74 +++++++++++++++++++ .../items/implants/implant_mindshield.dm | 14 ++-- code/modules/antagonists/hivemind/hivemind.dm | 47 ++++++++---- .../traitor/IAA/internal_affairs.dm | 16 ++-- code/modules/spells/spell_types/hivemind.dm | 32 +++++--- tgstation.dme | 1 + 8 files changed, 163 insertions(+), 38 deletions(-) create mode 100644 code/game/gamemodes/hivemind/radar.dm diff --git a/code/__DEFINES/status_effects.dm b/code/__DEFINES/status_effects.dm index d390b0c7994..1077c6c16cd 100644 --- a/code/__DEFINES/status_effects.dm +++ b/code/__DEFINES/status_effects.dm @@ -99,6 +99,10 @@ #define STATUS_EFFECT_INLOVE /datum/status_effect/in_love //Displays you as being in love with someone else, and makes hearts appear around them. +#define STATUS_EFFECT_HIVE_TRACKER /datum/status_effect/hive_track + +#define STATUS_EFFECT_HIVE_RADAR /datum/status_effect/agent_pinpointer/hivemind + ///////////// // SLIME // ///////////// diff --git a/code/game/gamemodes/hivemind/hivemind.dm b/code/game/gamemodes/hivemind/hivemind.dm index e3d64b59534..5ee1c798023 100644 --- a/code/game/gamemodes/hivemind/hivemind.dm +++ b/code/game/gamemodes/hivemind/hivemind.dm @@ -36,6 +36,17 @@ return TRUE return FALSE +/mob/living/proc/get_real_hivehost() //Returns src unless it's under mind control, then it returns the original body + var/mob/living/M = src + if(!M) + return + if(!is_hivehost(M) || is_real_hivehost(M)) + return M + var/obj/effect/proc_holder/spell/target_hive/hive_control/the_spell = locate(/obj/effect/proc_holder/spell/target_hive/hive_control) in M.mind.spell_list + if(the_spell?.active) + return the_spell.original_body + return M + /proc/is_hivemember(mob/living/M) if(!M) return FALSE @@ -91,4 +102,4 @@ /datum/game_mode/hivemind/generate_report() return "Reports of psychic activity have been showing up in this sector, and we believe this may have to do with a containment breach on \[REDACTED\] last month \ when a sapient hive intelligence displaying paranormal powers escaped into the unknown. They present a very large risk as they can assimilate people into \ - the hivemind with ease, although they appear unable to affect mindshielded personnel." \ No newline at end of file + the hivemind with ease, although they appear unable to affect mindshielded personnel." diff --git a/code/game/gamemodes/hivemind/radar.dm b/code/game/gamemodes/hivemind/radar.dm new file mode 100644 index 00000000000..ccf7ab9a9b5 --- /dev/null +++ b/code/game/gamemodes/hivemind/radar.dm @@ -0,0 +1,74 @@ +#define HIVEMIND_RADAR_MIN_DISTANCE 7 //Very generous, as the targets are only tracked for a few minutes. +#define HIVEMIND_RADAR_MAX_DISTANCE 50 +#define HIVEMIND_RADAR_PING_TIME 20 //2s update time. + +//Modified IA/changeling pinpointer, points to the nearest person who is afflicted with the hive tracker status effect +/datum/status_effect/agent_pinpointer/hivemind + id = "hive_pinpointer" + alert_type = /obj/screen/alert/status_effect/agent_pinpointer/hivemind + minimum_range = HIVEMIND_RADAR_MIN_DISTANCE + tick_interval = HIVEMIND_RADAR_PING_TIME + range_fuzz_factor = 0 + +/datum/status_effect/agent_pinpointer/hivemind/point_to_target() //If we found what we're looking for, show the distance and direction + if(scan_target) + if(owner.mind) + var/datum/antagonist/hivemind/hive = owner.mind.has_antag_datum(/datum/antagonist/hivemind) + if(hive) + range_far = range_mid * (2-hive.get_threat_multiplier()) + if(scan_target.mind) + var/datum/antagonist/hivemind/enemy_hive = scan_target.mind.has_antag_datum(/datum/antagonist/hivemind) + if(enemy_hive) + range_far = max(range_mid * (1+enemy_hive.get_threat_multiplier()), range_far) + + ..() + +/datum/status_effect/agent_pinpointer/hivemind/scan_for_target() + var/turf/my_loc = get_turf(owner) + + var/list/mob/living/carbon/targets = list() + var/trackable_targets_exist = FALSE + + for(var/mob/living/carbon/C in GLOB.alive_mob_list) + if(C == owner) + continue + var/datum/status_effect/hive_track/mark = C.has_status_effect(STATUS_EFFECT_HIVE_TRACKER) + if(mark?.tracked_by == owner) + trackable_targets_exist = TRUE + var/their_loc = get_turf(C) + var/distance = get_dist_euclidian(my_loc, their_loc) + if (distance < HIVEMIND_RADAR_MAX_DISTANCE) + var/multiplier = 0.5 + if(C.mind) + var/datum/antagonist/hivemind/hive = C.mind.has_antag_datum(/datum/antagonist/hivemind) + if(hive) + multiplier = hive.get_threat_multiplier() + targets[C] = ((HIVEMIND_RADAR_MAX_DISTANCE ** 2) - (distance ** 2)) * multiplier + + if(targets.len) + scan_target = pickweight(targets) //Point at a 'random' target, biasing heavily towards closer ones. + else + scan_target = null + if(!trackable_targets_exist) + to_chat(owner, "The psychic energies eminating from afar have died down... for now") + owner.remove_status_effect(STATUS_EFFECT_HIVE_RADAR) + +//"Trackable" status effect +/datum/status_effect/hive_track + id = "hive_track" + duration = 1200 + status_type = STATUS_EFFECT_MULTIPLE + alert_type = null + var/mob/living/tracked_by + +/datum/status_effect/hive_track/on_creation(mob/living/new_owner, mob/living/hunter, set_duration) + . = ..() + if(.) + tracked_by = hunter + if(isnum(set_duration)) + duration = set_duration + +//Screen alert +/obj/screen/alert/status_effect/agent_pinpointer/hivemind + name = "Psychic link" + desc = "Somebody is there, and they're definitely not friendly." diff --git a/code/game/objects/items/implants/implant_mindshield.dm b/code/game/objects/items/implants/implant_mindshield.dm index 07fd67410ff..9547506e940 100644 --- a/code/game/objects/items/implants/implant_mindshield.dm +++ b/code/game/objects/items/implants/implant_mindshield.dm @@ -42,13 +42,17 @@ return FALSE if(is_hivemember(target)) - var/warning = "" for(var/datum/antagonist/hivemind/hive in GLOB.antagonists) if(hive.hivemembers.Find(target)) - var/hive_name = hive.get_real_name() - if(hive_name) - warning += "[hive_name]. " - to_chat(target, "You hear supernatural wailing echo throughout your mind. If you listen closely you can hear... [warning]Are those... names?") + var/mob/living/carbon/C = hive.owner.current.get_real_hivehost() + if(C) + C.apply_status_effect(STATUS_EFFECT_HIVE_TRACKER, target) + target.apply_status_effect(STATUS_EFFECT_HIVE_TRACKER, C) + if(C.mind) //If you were using mind control, too bad + C.apply_status_effect(STATUS_EFFECT_HIVE_RADAR) + to_chat(C, "We detect a surge of psionic energy from a far away vessel before they disappear from the hive. Whatever happened, there's a good chance they're after us now.") + to_chat(target, "You hear supernatural wailing echo throughout your mind as you are finally set free. Deep down, you can feel the lingering presence of those who enslaved you... as can they!") + target.apply_status_effect(STATUS_EFFECT_HIVE_RADAR) remove_hivemember(target) var/datum/antagonist/rev/rev = target.mind.has_antag_datum(/datum/antagonist/rev) diff --git a/code/modules/antagonists/hivemind/hivemind.dm b/code/modules/antagonists/hivemind/hivemind.dm index 314ffa67aa4..99b834872cf 100644 --- a/code/modules/antagonists/hivemind/hivemind.dm +++ b/code/modules/antagonists/hivemind/hivemind.dm @@ -7,6 +7,7 @@ var/special_role = ROLE_HIVE var/list/hivemembers = list() var/hive_size = 0 + var/threat_level = 0 // Part of what determines how strong the radar is, on a scale of 0 to 10 var/static/datum/objective/hivemind/assimilate_common/common_assimilation_obj //Make it static since we want a common target for all the antags var/list/upgrade_tiers = list( @@ -34,6 +35,10 @@ if(hive_size != old_size) check_powers() +/datum/antagonist/hivemind/proc/get_threat_multiplier() + calc_size() + return min(hive_size/50 + threat_level/20, 1) + /datum/antagonist/hivemind/proc/check_powers() for(var/power in upgrade_tiers) var/level = upgrade_tiers[power] @@ -43,27 +48,13 @@ if(hive_size > 0) to_chat(owner, "We have unlocked [the_spell.name]. [the_spell.desc]") -/datum/antagonist/hivemind/proc/get_real_name() //Gets the real name of the host, even if they're temporarily in another one - var/obj/effect/proc_holder/spell/target_hive/hive_control/the_spell = locate(/obj/effect/proc_holder/spell/target_hive/hive_control) in owner.spell_list - var/datum/mind/M = owner - if(M) - var/mob/living/L = owner.current - if(L) - if(the_spell && the_spell.active) - if(the_spell.original_body) - return the_spell.original_body.real_name - return L.real_name - return "" - /datum/antagonist/hivemind/proc/add_to_hive(var/mob/living/carbon/human/H) - var/warning = "We detect a surge of psionic energy from [H.real_name] before they disappear from the hive. An enemy host, or simply a stolen vessel?" var/user_warning = "We have detected an enemy hivemind using our physical form as a vessel and have begun ejecting their mind! They will be alerted of our disappearance once we succeed!" for(var/datum/antagonist/hivemind/enemy_hive in GLOB.antagonists) - if(H.mind == enemy_hive.owner) + if(is_real_hivehost(H)) var/eject_time = rand(1400,1600) //2.5 minutes +- 10 seconds addtimer(CALLBACK(GLOBAL_PROC, /proc/to_chat, H, user_warning), rand(500,1300)) // If the host has assimilated an enemy hive host, alert the enemy before booting them from the hive after a short while - addtimer(CALLBACK(GLOBAL_PROC, /proc/to_chat, owner, warning), eject_time) //As well as the host who just added them as soon as they're ejected - addtimer(CALLBACK(GLOBAL_PROC, /proc/remove_hivemember, H), eject_time) + addtimer(CALLBACK(src, .proc/handle_ejection, H), eject_time) hivemembers |= H calc_size() @@ -71,6 +62,30 @@ hivemembers -= H calc_size() +/datum/antagonist/hivemind/proc/handle_ejection(mob/living/carbon/human/H) + var/user_warning = "The enemy host has been ejected from our mind" + + if(!H || !owner) + return + + var/mob/living/carbon/human/H2 = owner.current + if(!H2) + return + + var/mob/living/real_H = H.get_real_hivehost() + var/mob/living/real_H2 = H2.get_real_hivehost() + + if(is_real_hivehost(H)) + real_H2.apply_status_effect(STATUS_EFFECT_HIVE_TRACKER, real_H) + real_H.apply_status_effect(STATUS_EFFECT_HIVE_RADAR) + to_chat(real_H, "We detect a surge of psionic energy from a far away vessel before they disappear from the hive. Whatever happened, there's a good chance they're after us now.") + if(is_real_hivehost(H2)) + real_H.apply_status_effect(STATUS_EFFECT_HIVE_TRACKER, real_H2) + real_H2.apply_status_effect(STATUS_EFFECT_HIVE_RADAR) + user_warning += " and we've managed to pinpoint their location" + + to_chat(H2, "[user_warning]!") + /datum/antagonist/hivemind/proc/destroy_hive() hivemembers = list() calc_size() diff --git a/code/modules/antagonists/traitor/IAA/internal_affairs.dm b/code/modules/antagonists/traitor/IAA/internal_affairs.dm index 46945fc75c8..16e219f62e1 100644 --- a/code/modules/antagonists/traitor/IAA/internal_affairs.dm +++ b/code/modules/antagonists/traitor/IAA/internal_affairs.dm @@ -46,6 +46,8 @@ var/minimum_range = PINPOINTER_MINIMUM_RANGE var/range_fuzz_factor = PINPOINTER_EXTRA_RANDOM_RANGE var/mob/scan_target = null + var/range_mid = 8 + var/range_far = 16 /obj/screen/alert/status_effect/agent_pinpointer name = "Internal Affairs Integrated Pinpointer" @@ -66,13 +68,13 @@ linked_alert.icon_state = "pinondirect" else linked_alert.setDir(get_dir(here, there)) - switch(get_dist(here, there)) - if(1 to 8) - linked_alert.icon_state = "pinonclose" - if(9 to 16) - linked_alert.icon_state = "pinonmedium" - if(16 to INFINITY) - linked_alert.icon_state = "pinonfar" + var/dist = (get_dist(here, there)) + if(dist >= 1 && dist <= range_mid) + linked_alert.icon_state = "pinonclose" + else if(dist > range_mid && dist <= range_far) + linked_alert.icon_state = "pinonmedium" + else if(dist > range_far) + linked_alert.icon_state = "pinonfar" /datum/status_effect/agent_pinpointer/proc/scan_for_target() scan_target = null diff --git a/code/modules/spells/spell_types/hivemind.dm b/code/modules/spells/spell_types/hivemind.dm index dc0b457be66..f7f0f71d437 100644 --- a/code/modules/spells/spell_types/hivemind.dm +++ b/code/modules/spells/spell_types/hivemind.dm @@ -248,7 +248,7 @@ /obj/effect/proc_holder/spell/target_hive/hive_control name = "Mind Control" - desc = "We assume direct control of one of our vessels, leaving our current body for up to ten seconds, although a larger hive may be able to sustain it for up to two minutes. It can be cancelled at any time by casting it again. Powers can be used via our vessel, although if it dies, the entire hivemind will come down with it." + desc = "We assume direct control of one of our vessels, leaving our current body for up to ten seconds, although a larger hive may be able to sustain it for up to two minutes. It can be cancelled at any time by casting it again. Powers can be used via our vessel, although if it dies, the entire hivemind will come down with it. Our ability to sense psionic energy is completely nullified while using this power." charge_max = 600 action_icon_state = "force" active = FALSE @@ -285,6 +285,14 @@ QDEL_NULL(backseat) + if(original_body?.mind) + var/datum/antagonist/hivemind/hive = original_body.mind.has_antag_datum(/datum/antagonist/hivemind) + if(hive) + if((world.time-time_initialized) <= 300) + hive.threat_level += 0.5 + else + hive.threat_level += 1 + /obj/effect/proc_holder/spell/target_hive/hive_control/on_lose(mob/user) release_control() @@ -456,6 +464,9 @@ deadchat_broadcast("[target] has suffered a mysterious heart attack!", target) else to_chat(user, "We are unable to induce a heart attack!") + var/datum/antagonist/hivemind/hive = user.mind.has_antag_datum(/datum/antagonist/hivemind) + if(hive) + hive.threat_level += 2 /obj/effect/proc_holder/spell/target_hive/hive_warp name = "Distortion Field" @@ -516,7 +527,6 @@ var/mob/living/carbon/human/target = targets[1] var/in_hive = hive.hivemembers.Find(target) var/list/enemies = list() - var/enemy_names = "" to_chat(user, "We begin probing [target.name]'s mind!") if(do_after(user,100,0,target)) @@ -528,22 +538,26 @@ return for(var/datum/antagonist/hivemind/enemy in GLOB.antagonists) var/datum/mind/M = enemy.owner - if(!M || M.current == user) + if(M?.current == user) continue if(enemy.hivemembers.Find(target)) - var/hive_name = enemy.get_real_name() - if(hive_name) - enemies += hive_name + var/mob/living/real_enemy = (M.current.get_real_hivehost()) + enemies += real_enemy enemy.remove_from_hive(target) - to_chat(M.current, "We detect a surge of psionic energy from [target.real_name] before they disappear from the hive. An enemy host, or simply a stolen vessel?") + real_enemy.apply_status_effect(STATUS_EFFECT_HIVE_TRACKER, user) + if(is_real_hivehost(M.current)) //If they were using mind control, too bad + real_enemy.apply_status_effect(STATUS_EFFECT_HIVE_RADAR) + target.apply_status_effect(STATUS_EFFECT_HIVE_TRACKER, real_enemy) + to_chat(real_enemy, "We detect a surge of psionic energy from a far away vessel before they disappear from the hive. Whatever happened, there's a good chance they're after us now.") + if(enemy.owner == target && is_real_hivehost(target)) user.Stun(70) user.Jitter(14) to_chat(user, "A sudden surge of psionic energy rushes into your mind, only a Hive host could have such power!!") return if(enemies.len) - enemy_names = enemies.Join(". ") - to_chat(user, "In a moment of clarity, we see all. Another hive. Faces. Our nemesis. [enemy_names]. They are watching us. They know we are coming.") + to_chat(user, "In a moment of clarity, we see all. Another hive. Faces. Our nemesis. They have heard our call. They know we are coming.") + user.apply_status_effect(STATUS_EFFECT_HIVE_RADAR) else to_chat(user, "We peer into the inner depths of their mind and see nothing, no enemies lurk inside this mind.") else diff --git a/tgstation.dme b/tgstation.dme index 606fd1dcbd3..54470db3b91 100755 --- a/tgstation.dme +++ b/tgstation.dme @@ -548,6 +548,7 @@ #include "code\game\gamemodes\extended\extended.dm" #include "code\game\gamemodes\hivemind\hivemind.dm" #include "code\game\gamemodes\hivemind\objectives.dm" +#include "code\game\gamemodes\hivemind\radar.dm" #include "code\game\gamemodes\meteor\meteor.dm" #include "code\game\gamemodes\meteor\meteors.dm" #include "code\game\gamemodes\monkey\monkey.dm"