diff --git a/code/__DEFINES/antagonists.dm b/code/__DEFINES/antagonists.dm
index 5986b6f1fac3..62dc24daa3a2 100644
--- a/code/__DEFINES/antagonists.dm
+++ b/code/__DEFINES/antagonists.dm
@@ -42,3 +42,10 @@
//Overthrow time to update heads obj
#define OBJECTIVE_UPDATING_TIME 300
+
+//Assimilation
+#define TRACKER_DEFAULT_TIME 900
+#define TRACKER_MINDSHIELD_TIME 1200
+#define TRACKER_AWAKENED_TIME 3000
+#define TRACKER_BONUS_LARGE 300
+#define TRACKER_BONUS_SMALL 100
\ No newline at end of file
diff --git a/code/__DEFINES/status_effects.dm b/code/__DEFINES/status_effects.dm
index 1cdb317a04c7..df1ccd19d518 100644
--- a/code/__DEFINES/status_effects.dm
+++ b/code/__DEFINES/status_effects.dm
@@ -98,6 +98,8 @@
#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_BUGGED /datum/status_effect/bugged //Lets other mobs listen in on what it hears
+
#define STATUS_EFFECT_HIVE_TRACKER /datum/status_effect/hive_track
#define STATUS_EFFECT_HIVE_RADAR /datum/status_effect/agent_pinpointer/hivemind
diff --git a/code/__DEFINES/traits.dm b/code/__DEFINES/traits.dm
index 15974884a074..e5535824e260 100644
--- a/code/__DEFINES/traits.dm
+++ b/code/__DEFINES/traits.dm
@@ -159,3 +159,4 @@
#define LOCKED_HELMET_TRAIT "locked-helmet"
#define NINJA_SUIT_TRAIT "ninja-suit"
#define ANTI_DROP_IMPLANT_TRAIT "anti-drop-implant"
+#define HIVEMIND_ONE_MIND_TRAIT "one_mind"
\ No newline at end of file
diff --git a/code/datums/hud.dm b/code/datums/hud.dm
index 69263935c0c9..e342da51703b 100644
--- a/code/datums/hud.dm
+++ b/code/datums/hud.dm
@@ -28,9 +28,9 @@ GLOBAL_LIST_INIT(huds, list(
ANTAG_HUD_CLOCKWORK = new/datum/atom_hud/antag(),
ANTAG_HUD_BROTHER = new/datum/atom_hud/antag/hidden(),
ANTAG_HUD_VAMPIRE = new/datum/atom_hud/antag/hidden(), // Yogs
- ANTAG_HUD_HIVE = new/datum/atom_hud/antag/hidden(),
- ANTAG_HUD_CREEP = new/datum/atom_hud/antag/hidden()
- ))
+ ANTAG_HUD_HIVE = new/datum/atom_hud/antag/hidden(),
+ ANTAG_HUD_CREEP = new/datum/atom_hud/antag/hidden(),
+ ANTAG_HUD_HIVEAWAKE = new/datum/atom_hud/antag() ))
/datum/atom_hud
var/list/atom/hudatoms = list() //list of all atoms which display this hud
diff --git a/code/datums/status_effects/neutral.dm b/code/datums/status_effects/neutral.dm
index 646087e85320..0fc032b78f88 100644
--- a/code/datums/status_effects/neutral.dm
+++ b/code/datums/status_effects/neutral.dm
@@ -84,3 +84,15 @@
/datum/status_effect/throat_soothed/on_remove()
. = ..()
owner.remove_trait(TRAIT_SOOTHED_THROAT, "[STATUS_EFFECT_TRAIT]_[id]")
+
+/datum/status_effect/bugged //Lets another mob hear everything you can
+ id = "bugged"
+ duration = -1
+ status_type = STATUS_EFFECT_MULTIPLE
+ alert_type = null
+ var/mob/living/listening_in
+
+/datum/status_effect/bugged/on_creation(mob/living/new_owner, mob/living/tracker)
+ . = ..()
+ if(.)
+ listening_in = tracker
\ No newline at end of file
diff --git a/code/game/gamemodes/hivemind/hivemind.dm b/code/game/gamemodes/hivemind/hivemind.dm
index 5ee1c7980236..6b8d96e694d5 100644
--- a/code/game/gamemodes/hivemind/hivemind.dm
+++ b/code/game/gamemodes/hivemind/hivemind.dm
@@ -6,7 +6,7 @@
false_report_weight = 5
protected_jobs = list("Security Officer", "Warden", "Detective", "Head of Security", "Captain")
restricted_jobs = list("Cyborg","AI")
- required_players = 25
+ required_players = 24
required_enemies = 2
recommended_enemies = 3
reroll_friendly = 1
@@ -20,20 +20,19 @@
var/list/hosts = list()
/proc/is_hivehost(mob/living/M)
- return M && M.mind && M.mind.has_antag_datum(/datum/antagonist/hivemind)
-
-/proc/is_real_hivehost(mob/living/M) //This proc ignores mind controlled vessels
if(!M || !M.mind)
- return FALSE
- var/datum/antagonist/hivemind/hive = M.mind.has_antag_datum(/datum/antagonist/hivemind)
- if(!hive)
- return FALSE
- 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 && the_spell.active)
- if(the_spell.original_body == M)
+ return
+ return M.mind.has_antag_datum(/datum/antagonist/hivemind)
+
+/mob/living/proc/is_real_hivehost() //This proc ignores mind controlled vessels
+ for(var/datum/antagonist/hivemind/hive in GLOB.antagonists)
+ if(!hive.owner?.spell_list)
+ continue
+ var/obj/effect/proc_holder/spell/target_hive/hive_control/the_spell = locate(/obj/effect/proc_holder/spell/target_hive/hive_control) in hive.owner.spell_list
+ if((!the_spell || !the_spell.active ) && mind == hive.owner)
+ return TRUE
+ if(the_spell?.active && the_spell.original_body == src)
return TRUE
- else
- return TRUE
return FALSE
/mob/living/proc/get_real_hivehost() //Returns src unless it's under mind control, then it returns the original body
@@ -47,7 +46,10 @@
return the_spell.original_body
return M
-/proc/is_hivemember(mob/living/M)
+/proc/is_hivemember(mob/living/L)
+ if(!L)
+ return FALSE
+ var/datum/mind/M = L.mind
if(!M)
return FALSE
for(var/datum/antagonist/hivemind/H in GLOB.antagonists)
@@ -71,7 +73,7 @@
if(CONFIG_GET(flag/protect_assistant_from_antagonist))
restricted_jobs += "Assistant"
- var/num_hosts = max( 1 , rand(0,1) + min(5, round(num_players() / 12) ) ) //1 host for every 12 players up to 60, with a 50% chance of an extra
+ var/num_hosts = max( 1 , rand(0,1) + min(8, round(num_players() / 8) ) ) //1 host for every 8 players up to 64, with a 50% chance of an extra
for(var/j = 0, j < num_hosts, j++)
if (!antag_candidates.len)
@@ -91,10 +93,6 @@
/datum/game_mode/hivemind/post_setup()
- if(hosts.len >= 4 && prob(35)) //Create the versus objective here since we want a common target for all the antags
- var/datum/antagonist/hivemind/hive
- hive.common_assimilation_obj = new /datum/objective/hivemind/assimilate_common
- hive.common_assimilation_obj.find_target_by_role(role = ROLE_HIVE, role_type = TRUE, invert = TRUE)
for(var/datum/mind/i in hosts)
i.add_antag_datum(/datum/antagonist/hivemind)
return ..()
diff --git a/code/game/gamemodes/hivemind/objectives.dm b/code/game/gamemodes/hivemind/objectives.dm
index b26ed9c3bdfd..f29b8b2a3a76 100644
--- a/code/game/gamemodes/hivemind/objectives.dm
+++ b/code/game/gamemodes/hivemind/objectives.dm
@@ -33,32 +33,17 @@
var/datum/antagonist/hivemind/host = owner.has_antag_datum(/datum/antagonist/hivemind)
if(!host)
return FALSE
- for(var/mob/living/L in host.hivemembers)
- var/datum/mind/M = L.mind
- if(M)
- if(considered_escaped(M))
- count++
+ for(var/obj/item/organ/brain/B in host.hivemembers)
+ var/mob/living/carbon/C = B.owner
+ if(!C)
+ continue
+ var/datum/mind/M = C.mind
+ if(!M)
+ continue
+ if(considered_escaped(M))
+ count++
return count >= target_amount
-/datum/objective/hivemind/assimilate
- explanation_text = "This is a bug. Error:HIVE3"
-
-/datum/objective/hivemind/assimilate/update_explanation_text()
- if(target)
- explanation_text = "Assimilate [target.name] into the hive and ensure they survive."
- else
- explanation_text = "Free Objective."
-
-/datum/objective/hivemind/assimilate/check_completion()
- var/datum/antagonist/hivemind/host = owner.has_antag_datum(/datum/antagonist/hivemind)
- if(!host)
- return FALSE
- for(var/mob/living/L in host.hivemembers)
- var/datum/mind/M = L.mind
- if(M == target)
- return considered_alive(target)
- return FALSE
-
/datum/objective/hivemind/biggest
explanation_text = "End the round with more vessels than any other hivemind host."
@@ -71,26 +56,4 @@
continue
if(H.hive_size >= host.hive_size)
return FALSE
- return TRUE
-
-/datum/objective/hivemind/assimilate_common
- explanation_text = "This is a bug. Error:HIVE3"
-
-/datum/objective/hivemind/assimilate_common/update_explanation_text()
- if(target)
- explanation_text = "Ensure that you are the only host assimilating [target.name] at the end of the round."
- else
- explanation_text = "Free Objective."
-
-/datum/objective/hivemind/assimilate_common/check_completion()
- var/datum/antagonist/hivemind/host = owner.has_antag_datum(/datum/antagonist/hivemind)
- if(!target)
- return TRUE
- if(!host || !target.current || !host.hivemembers.Find(target.current))
- return FALSE
- for(var/datum/antagonist/hivemind/H in GLOB.antagonists)
- if(H == host)
- continue
- if(H.hivemembers.Find(target.current))
- return FALSE
return TRUE
\ No newline at end of file
diff --git a/code/game/objects/items/implants/implant_mindshield.dm b/code/game/objects/items/implants/implant_mindshield.dm
index 9547506e940b..ae6135af30c2 100644
--- a/code/game/objects/items/implants/implant_mindshield.dm
+++ b/code/game/objects/items/implants/implant_mindshield.dm
@@ -41,20 +41,25 @@
qdel(src)
return FALSE
+ var/datum/antagonist/hivevessel/woke = target.is_wokevessel()
if(is_hivemember(target))
for(var/datum/antagonist/hivemind/hive in GLOB.antagonists)
if(hive.hivemembers.Find(target))
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)
+ C.apply_status_effect(STATUS_EFFECT_HIVE_TRACKER, target, woke?TRACKER_AWAKENED_TIME:TRACKER_MINDSHIELD_TIME)
+ target.apply_status_effect(STATUS_EFFECT_HIVE_TRACKER, C, TRACKER_DEFAULT_TIME)
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(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)
+ if(woke)
+ woke.one_mind.remove_member(target.mind)
+ target.mind.remove_antag_datum(/datum/antagonist/hivevessel)
+
var/datum/antagonist/rev/rev = target.mind.has_antag_datum(/datum/antagonist/rev)
if(rev)
rev.remove_revolutionary(FALSE, user)
diff --git a/code/modules/antagonists/hivemind/hivemind.dm b/code/modules/antagonists/hivemind/hivemind.dm
index 99b834872cf1..ee21fd7227a5 100644
--- a/code/modules/antagonists/hivemind/hivemind.dm
+++ b/code/modules/antagonists/hivemind/hivemind.dm
@@ -8,83 +8,143 @@
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/track_bonus = 0 // Bonus time to your tracking abilities
+ var/size_mod = 0 // Bonus size for using reclaim
+ var/list/individual_track_bonus // Bonus time to tracking individual targets
+ var/unlocked_one_mind = FALSE
+ var/datum/team/hivemind/active_one_mind
+ var/mutable_appearance/glow
var/list/upgrade_tiers = list(
- //Tier 1
+ //Tier 1 - Roundstart powers
/obj/effect/proc_holder/spell/target_hive/hive_add = 0,
/obj/effect/proc_holder/spell/target_hive/hive_remove = 0,
/obj/effect/proc_holder/spell/target_hive/hive_see = 0,
- /obj/effect/proc_holder/spell/target_hive/hive_shock = 2,
- /obj/effect/proc_holder/spell/self/hive_drain = 4,
- //Tier 2
- /obj/effect/proc_holder/spell/target_hive/hive_warp = 6,
- /obj/effect/proc_holder/spell/targeted/hive_hack = 8,
- /obj/effect/proc_holder/spell/target_hive/hive_control = 10,
- /obj/effect/proc_holder/spell/targeted/induce_panic = 12,
- //Tier 3
+ /obj/effect/proc_holder/spell/target_hive/hive_shock = 0,
+ /obj/effect/proc_holder/spell/target_hive/hive_warp = 0,
+ //Tier 2 - Tracking related powers
+ /obj/effect/proc_holder/spell/self/hive_scan = 5,
+ /obj/effect/proc_holder/spell/targeted/hive_reclaim = 5,
+ /obj/effect/proc_holder/spell/targeted/hive_hack = 5,
+ //Tier 3 - Combat related powers
+ /obj/effect/proc_holder/spell/self/hive_drain = 10,
+ /obj/effect/proc_holder/spell/targeted/induce_panic = 10,
+ /obj/effect/proc_holder/spell/targeted/forcewall/hive = 10,
+ //Tier 4 - Chaos-spreading powers
+ /obj/effect/proc_holder/spell/self/hive_wake = 15,
/obj/effect/proc_holder/spell/self/hive_loyal = 15,
- /obj/effect/proc_holder/spell/targeted/hive_assim = 18,
- /obj/effect/proc_holder/spell/targeted/forcewall/hive = 20,
- /obj/effect/proc_holder/spell/target_hive/hive_attack = 25)
+ /obj/effect/proc_holder/spell/target_hive/hive_control = 15,
+ //Tier 5 - Deadly powers
+ /obj/effect/proc_holder/spell/targeted/induce_sleep = 20,
+ /obj/effect/proc_holder/spell/target_hive/hive_attack = 20
+ )
+
/datum/antagonist/hivemind/proc/calc_size()
listclearnulls(hivemembers)
- var/old_size = hive_size
- hive_size = hivemembers.len
- if(hive_size != old_size)
+ var/temp = 0
+ for(var/datum/mind/M in hivemembers)
+ if(M.current && M.current.stat != DEAD)
+ temp++
+ if(hive_size != temp)
+ hive_size = temp
check_powers()
/datum/antagonist/hivemind/proc/get_threat_multiplier()
calc_size()
- return min(hive_size/50 + threat_level/20, 1)
+ return min((hive_size+size_mod*2)/50 + threat_level/20, 1)
+
+/datum/antagonist/hivemind/proc/get_carbon_members()
+ var/list/carbon_members = list()
+ for(var/datum/mind/M in hivemembers)
+ if(!M.current || !iscarbon(M.current))
+ continue
+ carbon_members += M.current
+ return carbon_members
/datum/antagonist/hivemind/proc/check_powers()
for(var/power in upgrade_tiers)
var/level = upgrade_tiers[power]
- if(hive_size >= level && !(locate(power) in owner.spell_list))
+ if(hive_size+size_mod >= level && !(locate(power) in owner.spell_list))
var/obj/effect/proc_holder/spell/the_spell = new power(null)
owner.AddSpell(the_spell)
if(hive_size > 0)
to_chat(owner, "We have unlocked [the_spell.name]. [the_spell.desc]")
-/datum/antagonist/hivemind/proc/add_to_hive(var/mob/living/carbon/human/H)
+ if(!unlocked_one_mind && hive_size >= 15)
+ var/lead = TRUE
+ for(var/datum/antagonist/hivemind/enemy in GLOB.antagonists)
+ if(enemy == src)
+ continue
+ if(!enemy.active_one_mind && enemy.hive_size <= hive_size + size_mod - 20)
+ continue
+ lead = FALSE
+ break
+ if(lead)
+ unlocked_one_mind = TRUE
+ owner.AddSpell(new/obj/effect/proc_holder/spell/self/one_mind)
+ to_chat(owner, "Our true power, the One Mind, is finally within reach.")
+
+/datum/antagonist/hivemind/proc/add_track_bonus(datum/antagonist/hivemind/enemy, bonus)
+ if(!locate(enemy) in individual_track_bonus)
+ individual_track_bonus[enemy] = bonus
+ else
+ individual_track_bonus[enemy] += bonus
+
+/datum/antagonist/hivemind/proc/get_track_bonus(datum/antagonist/hivemind/enemy)
+ return TRACKER_DEFAULT_TIME + track_bonus + individual_track_bonus[enemy]
+
+/datum/antagonist/hivemind/proc/add_to_hive(mob/living/carbon/C)
+ if(!C)
+ return
+ var/datum/mind/M = C.mind
+ if(M)
+ hivemembers |= M
+ calc_size()
+
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(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(src, .proc/handle_ejection, H), eject_time)
- hivemembers |= H
- calc_size()
+ if(C.is_real_hivehost())
+ var/eject_time = rand(1400,1600) //2.5 minutes +- 10 seconds
+ addtimer(CALLBACK(GLOBAL_PROC, /proc/to_chat, C, 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(src, .proc/handle_ejection, C), eject_time)
+ else if(active_one_mind)
+ C.hive_awaken(final_form=active_one_mind)
-/datum/antagonist/hivemind/proc/remove_from_hive(var/mob/living/carbon/human/H)
- hivemembers -= H
- calc_size()
+/datum/antagonist/hivemind/proc/is_carbon_member(mob/living/carbon/C)
+ if(!hivemembers || !C || !iscarbon(C))
+ return FALSE
+ var/datum/mind/M = C.mind
+ if(!M || !hivemembers.Find(M))
+ return FALSE
+ return TRUE
-/datum/antagonist/hivemind/proc/handle_ejection(mob/living/carbon/human/H)
+/datum/antagonist/hivemind/proc/remove_from_hive(mob/living/carbon/C)
+ var/datum/mind/M = C.mind
+ if(M)
+ hivemembers -= M
+ calc_size()
+
+/datum/antagonist/hivemind/proc/handle_ejection(mob/living/carbon/C)
var/user_warning = "The enemy host has been ejected from our mind"
-
- if(!H || !owner)
+ if(!C || !owner)
+ return
+ var/mob/living/carbon/C2 = owner.current
+ if(!C2)
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)
+ var/mob/living/real_C = C.get_real_hivehost()
+ var/mob/living/real_C2 = C2.get_real_hivehost()
+ var/datum/antagonist/hivemind/hive_C = C.mind.has_antag_datum(/datum/antagonist/hivemind)
+ var/datum/antagonist/hivemind/hive_C2 = C2.mind.has_antag_datum(/datum/antagonist/hivemind)
+ if(C != real_C) //Mind control check
+ real_C2.apply_status_effect(STATUS_EFFECT_HIVE_TRACKER, real_C, hive_C.get_track_bonus(hive_C2))
+ real_C.apply_status_effect(STATUS_EFFECT_HIVE_RADAR)
+ to_chat(real_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.")
+ if(C2 != real_C2)
+ real_C.apply_status_effect(STATUS_EFFECT_HIVE_TRACKER, real_C2, hive_C2.get_track_bonus(hive_C) )
+ real_C2.apply_status_effect(STATUS_EFFECT_HIVE_RADAR)
user_warning += " and we've managed to pinpoint their location"
-
- to_chat(H2, "[user_warning]!")
+ to_chat(C2, "[user_warning]!")
/datum/antagonist/hivemind/proc/destroy_hive()
hivemembers = list()
@@ -93,8 +153,22 @@
/datum/antagonist/hivemind/antag_panel_data()
return "Vessels Assimilated: [hive_size]"
-/datum/antagonist/hivemind/on_gain()
+/datum/antagonist/hivemind/proc/awaken()
+ if(!owner?.current)
+ return
+ var/mob/living/carbon/C = owner.current.get_real_hivehost()
+ if(!C)
+ return
+ active_one_mind = TRUE
+ owner.AddSpell(new/obj/effect/proc_holder/spell/self/hive_comms)
+ C.add_trait(TRAIT_STUNIMMUNE, HIVEMIND_ONE_MIND_TRAIT)
+ C.add_trait(TRAIT_SLEEPIMMUNE, HIVEMIND_ONE_MIND_TRAIT)
+ C.add_trait(TRAIT_VIRUSIMMUNE, HIVEMIND_ONE_MIND_TRAIT)
+ C.add_trait(TRAIT_NOLIMBDISABLE, HIVEMIND_ONE_MIND_TRAIT)
+ C.add_trait(TRAIT_NOHUNGER, HIVEMIND_ONE_MIND_TRAIT)
+ C.add_trait(TRAIT_NODISMEMBER, HIVEMIND_ONE_MIND_TRAIT)
+/datum/antagonist/hivemind/on_gain()
owner.special_role = special_role
check_powers()
forge_objectives()
@@ -120,8 +194,6 @@
hud.leave_hud(owner.current)
set_antag_hud(owner.current, null)
-
-
/datum/antagonist/hivemind/on_removal()
//Remove all hive powers here
for(var/power in upgrade_tiers)
@@ -133,52 +205,19 @@
..()
/datum/antagonist/hivemind/proc/forge_objectives()
- if(prob(65))
+ if(prob(50))
var/datum/objective/hivemind/hivesize/size_objective = new
size_objective.owner = owner
objectives += size_objective
- else
+ else if(prob(70))
var/datum/objective/hivemind/hiveescape/hive_escape_objective = new
hive_escape_objective.owner = owner
objectives += hive_escape_objective
-
- if(prob(85))
- var/datum/objective/hivemind/assimilate/assim_objective = new
- assim_objective.owner = owner
- if(prob(25)) //Decently high chance to have to assimilate an implanted crew member
- assim_objective.find_target_by_role(pick("Captain","Head of Security","Security Officer","Detective","Warden"))
- if(!assim_objective.target) //If the prob doesn't happen or there are no implanted crew, find any target that isn't a hivemmind host
- assim_objective.find_target_by_role(role = ROLE_HIVE, role_type = TRUE, invert = TRUE)
- assim_objective.update_explanation_text()
- objectives += assim_objective
else
var/datum/objective/hivemind/biggest/biggest_objective = new
biggest_objective.owner = owner
objectives += biggest_objective
- if(prob(85) && common_assimilation_obj) //If the mode rolled the versus objective IE common_assimilation_obj is not null, add a very high chance to get this
- var/datum/objective/hivemind/assimilate_common/versus_objective = new
- versus_objective.owner = owner
- versus_objective.target = common_assimilation_obj.target
- versus_objective.update_explanation_text()
- objectives += versus_objective
- else if(prob(70))
- var/giveit = TRUE
- var/datum/objective/assassinate/kill_objective = new
- kill_objective.owner = owner
- kill_objective.find_target()
- for(var/datum/objective/hivemind/assimilate/ass_obj in objectives)
- if(ass_obj.target == kill_objective.target)
- giveit = FALSE
- break
- if(giveit)
- objectives += kill_objective
- else
- var/datum/objective/maroon/maroon_objective = new
- maroon_objective.owner = owner
- maroon_objective.find_target()
- objectives += maroon_objective
-
var/datum/objective/escape/escape_objective = new
escape_objective.owner = owner
objectives += escape_objective
diff --git a/code/modules/antagonists/hivemind/vessel.dm b/code/modules/antagonists/hivemind/vessel.dm
new file mode 100644
index 000000000000..eef4c0ff1844
--- /dev/null
+++ b/code/modules/antagonists/hivemind/vessel.dm
@@ -0,0 +1,78 @@
+/datum/team/hivemind
+ name = "One Mind"
+
+/datum/antagonist/hivevessel
+ name = "Awoken Vessel"
+ job_rank = ROLE_BRAINWASHED
+ roundend_category = "awoken vessels"
+ show_in_antagpanel = TRUE
+ antagpanel_category = "Other"
+ show_name_in_check_antagonists = TRUE
+ var/datum/team/hivemind/one_mind
+ var/mutable_appearance/glow
+
+/mob/living/proc/is_wokevessel()
+ return mind?.has_antag_datum(/datum/antagonist/hivevessel)
+
+/mob/living/proc/hive_awaken(objective, datum/team/final_form)
+ if(!mind)
+ return
+ var/datum/mind/M = mind
+ var/datum/antagonist/hivevessel/vessel = M.has_antag_datum(/datum/antagonist/hivevessel)
+ if(!vessel)
+ vessel = new()
+ if(final_form)
+ vessel.objectives = list() //Reset objectives on re-awoken vessels
+ if(ishuman(M.current))
+ vessel.glow = mutable_appearance('icons/effects/hivemind.dmi', "awoken", -BODY_BEHIND_LAYER)
+ M.current.add_overlay(vessel.glow)
+ M.AddSpell(new/obj/effect/proc_holder/spell/self/hive_comms)
+ vessel.one_mind = final_form
+ vessel.one_mind.add_member(M)
+ vessel.objectives |= vessel.one_mind.objectives
+ else
+ var/datum/objective/brainwashing/obj = new(objective)
+ vessel.objectives += obj
+ M.add_antag_datum(vessel)
+ var/message = "[M] has been brainwashed with the following objectives: [objective]."
+ deadchat_broadcast(message, follow_target = M, turf_target = get_turf(M), message_type=DEADCHAT_REGULAR)
+
+/datum/antagonist/hivevessel/apply_innate_effects()
+ if(owner.assigned_role == "Clown")
+ var/mob/living/carbon/human/traitor_mob = owner.current
+ if(traitor_mob && istype(traitor_mob))
+ if(!silent)
+ to_chat(traitor_mob, "Our newfound powers allow us to overcome our clownish nature, allowing us to wield weapons with impunity.")
+ traitor_mob.dna.remove_mutation(CLOWNMUT)
+ var/datum/atom_hud/antag/hud = GLOB.huds[ANTAG_HUD_HIVE]
+ hud.join_hud(owner.current)
+ set_antag_hud(owner.current, "hivevessel")
+
+/datum/antagonist/hivevessel/remove_innate_effects()
+ if(owner.assigned_role == "Clown")
+ var/mob/living/carbon/human/traitor_mob = owner.current
+ if(traitor_mob && istype(traitor_mob))
+ traitor_mob.dna.add_mutation(CLOWNMUT)
+ var/datum/atom_hud/antag/hud = GLOB.huds[ANTAG_HUD_HIVE]
+ hud.leave_hud(owner.current)
+ set_antag_hud(owner.current, null)
+
+/datum/antagonist/hivevessel/greet()
+ to_chat(owner, "Your mind is suddenly opened, as you see the pinnacle of evolution...")
+ to_chat(owner, "Follow your objectives, at any cost!")
+ var/i = 1
+ for(var/X in objectives)
+ var/datum/objective/O = X
+ to_chat(owner, "[i]. [O.explanation_text]")
+ i++
+
+/datum/antagonist/hivevessel/on_removal()
+ if(owner?.current && glow)
+ owner.current.cut_overlay(glow)
+ if(one_mind && owner)
+ one_mind.remove_member(owner)
+ ..()
+
+/datum/antagonist/hivevessel/farewell()
+ to_chat(owner, "Your mind closes up once more...")
+ to_chat(owner, "You feel the weight of your objectives disappear! You no longer have to obey them.")
\ No newline at end of file
diff --git a/code/modules/goonchat/browserassets/css/browserOutput.css b/code/modules/goonchat/browserassets/css/browserOutput.css
index c99dca79c952..168d287c5dee 100644
--- a/code/modules/goonchat/browserassets/css/browserOutput.css
+++ b/code/modules/goonchat/browserassets/css/browserOutput.css
@@ -378,6 +378,7 @@ h1.alert, h2.alert {color: #000000;}
.alertalien {color: #00c000; font-weight: bold;}
.changeling {color: #800080; font-style: italic;}
.assimilator {color: #800080; font-size: 16px ; font-weight: bold;}
+.bigassimilator {color: #800080; font-size: 32px ; font-weight: bold;}
.spider {color: #4d004d;}
diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm
index a5ec3231beb8..fbd5be36cb2d 100644
--- a/code/modules/mob/living/carbon/human/human.dm
+++ b/code/modules/mob/living/carbon/human/human.dm
@@ -8,9 +8,9 @@
/mob/living/carbon/human/Initialize()
verbs += /mob/living/proc/mob_sleep
verbs += /mob/living/proc/lay_down
-
+
icon_state = "" //Remove the inherent human icon that is visible on the map editor. We're rendering ourselves limb by limb, having it still be there results in a bug where the basic human icon appears below as south in all directions and generally looks nasty.
-
+
//initialize limbs first
create_bodyparts()
@@ -77,7 +77,8 @@
stat("Absorbed DNA", changeling.absorbedcount)
var/datum/antagonist/hivemind/hivemind = mind.has_antag_datum(/datum/antagonist/hivemind)
if(hivemind)
- stat("Hivemind Vessels", hivemind.hive_size)
+ stat("Hivemind Vessels", "[hivemind.hive_size] (+[hivemind.size_mod])")
+ stat("Psychic Link Duration", "[(hivemind.track_bonus + TRACKER_DEFAULT_TIME)/10] seconds")
//NINJACODE
if(istype(wear_suit, /obj/item/clothing/suit/space/space_ninja)) //Only display if actually a ninja.
diff --git a/code/modules/mob/living/carbon/say.dm b/code/modules/mob/living/carbon/say.dm
index 2c12a0d7995c..c4751434f176 100644
--- a/code/modules/mob/living/carbon/say.dm
+++ b/code/modules/mob/living/carbon/say.dm
@@ -40,6 +40,9 @@
. = initial(dt.flags) & TONGUELESS_SPEECH
/mob/living/carbon/hear_intercept(message, atom/movable/speaker, datum/language/message_language, raw_message, radio_freq, list/spans, message_mode)
+ var/datum/status_effect/bugged/B = has_status_effect(STATUS_EFFECT_BUGGED)
+ if(B)
+ B.listening_in.show_message(message)
for(var/T in get_traumas())
var/datum/brain_trauma/trauma = T
message = trauma.on_hear(message, speaker, message_language, raw_message, radio_freq)
diff --git a/code/modules/spells/spell_types/hivemind.dm b/code/modules/spells/spell_types/hivemind.dm
index 8fe91a0654da..6cd81438410d 100644
--- a/code/modules/spells/spell_types/hivemind.dm
+++ b/code/modules/spells/spell_types/hivemind.dm
@@ -14,22 +14,22 @@
/obj/effect/proc_holder/spell/target_hive/choose_targets(mob/user = usr)
var/datum/antagonist/hivemind/hive = user.mind.has_antag_datum(/datum/antagonist/hivemind)
- if(!hive)
+ if(!hive || !hive.hivemembers)
to_chat(user, "This is a bug. Error:HIVE1")
return
var/list/possible_targets = list()
var/list/targets = list()
if(target_external)
- for(var/mob/living/carbon/human/H in view_or_range(range, user, selection_type))
+ for(var/mob/living/carbon/H in view_or_range(range, user, selection_type))
if(user == H)
continue
if(!can_target(H))
continue
- if(!hive.hivemembers.Find(H))
+ if(!hive.is_carbon_member(H))
possible_targets += H
else
- possible_targets = hive.hivemembers.Copy()
+ possible_targets = hive.get_carbon_members()
if(range)
possible_targets &= view_or_range(range, user, selection_type)
@@ -46,13 +46,13 @@
selection_type = "view"
action_icon_state = "add"
- charge_max = 200
+ charge_max = 50
range = 7
target_external = 1
var/ignore_mindshield = FALSE
/obj/effect/proc_holder/spell/target_hive/hive_add/cast(list/targets, mob/living/user = usr)
- var/mob/living/carbon/human/target = targets[1]
+ var/mob/living/carbon/target = targets[1]
var/datum/antagonist/hivemind/hive = user.mind.has_antag_datum(/datum/antagonist/hivemind)
var/success = FALSE
@@ -69,6 +69,7 @@
success = TRUE
hive.add_to_hive(target)
if(ignore_mindshield)
+ user.Immobilize(50)
SEND_SIGNAL(target, COMSIG_NANITE_SET_VOLUME, 0)
for(var/obj/item/implant/mindshield/M in target.implants)
qdel(M)
@@ -91,25 +92,34 @@
selection_type = "view"
action_icon_state = "remove"
- charge_max = 100
+ charge_max = 50
range = 7
/obj/effect/proc_holder/spell/target_hive/hive_remove/cast(list/targets, mob/living/user = usr)
- var/mob/living/carbon/human/target = targets[1]
+ var/mob/living/carbon/target = targets[1]
var/datum/antagonist/hivemind/hive = user.mind.has_antag_datum(/datum/antagonist/hivemind)
if(!hive)
to_chat(user, "This is a bug. Error:HIVE1")
return
- hive.hivemembers -= target
+ var/datum/mind/M = target.mind
+ if(!M)
+ revert_cast()
+ return
+ hive.remove_from_hive(M)
hive.calc_size()
to_chat(user, "We remove [target.name] from the hive")
+ if(hive.active_one_mind)
+ var/datum/antagonist/hivevessel/woke = target.is_wokevessel()
+ if(woke)
+ hive.active_one_mind.remove_member(M)
+ M.remove_antag_datum(/datum/antagonist/hivevessel)
/obj/effect/proc_holder/spell/target_hive/hive_see
name = "Hive Vision"
desc = "We use the eyes of one of our vessels. Use again to look through our own eyes once more."
action_icon_state = "see"
- var/mob/vessel
+ var/mob/living/carbon/vessel
var/mob/living/host //Didn't really have any other way to auto-reset the perspective if the other mob got qdeled
charge_max = 20
@@ -122,6 +132,7 @@
if(!active)
vessel = targets[1]
if(vessel)
+ vessel.apply_status_effect(STATUS_EFFECT_BUGGED, user)
user.reset_perspective(vessel)
active = TRUE
host = user
@@ -129,6 +140,7 @@
user.overlay_fullscreen("hive_eyes", /obj/screen/fullscreen/hive_eyes)
revert_cast()
else
+ vessel.remove_status_effect(STATUS_EFFECT_BUGGED)
user.reset_perspective()
user.clear_fullscreen("hive_eyes")
var/obj/effect/proc_holder/spell/target_hive/hive_control/the_spell = locate(/obj/effect/proc_holder/spell/target_hive/hive_control) in user.mind.spell_list
@@ -141,7 +153,10 @@
if(active && (!vessel || !is_hivemember(vessel) || QDELETED(vessel)))
to_chat(host, "Our vessel is one of us no more!")
host.reset_perspective()
+ host.clear_fullscreen("hive_eyes")
active = FALSE
+ if(!QDELETED(vessel))
+ vessel.remove_status_effect(STATUS_EFFECT_BUGGED)
..()
/obj/effect/proc_holder/spell/target_hive/hive_see/choose_targets(mob/user = usr)
@@ -164,7 +179,7 @@
to_chat(user, "This is a bug. Error:HIVE1")
return
to_chat(user, "We begin increasing the psionic bandwidth between ourself and the vessel!")
- if(do_after(user,60,0,user))
+ if(do_after(user,30,0,user))
var/power = 120-get_dist(user, target)
if(!is_hivehost(target))
switch(hive.hive_size)
@@ -187,6 +202,77 @@
to_chat(user, "Our concentration has been broken!")
revert_cast()
+
+/obj/effect/proc_holder/spell/self/hive_scan
+ name = "Psychoreception"
+ desc = "We release a pulse to receive information on any enemies we have previously located via Network Invasion, as well as those currently tracking us."
+ panel = "Hivemind Abilities"
+ charge_max = 1800
+ invocation_type = "none"
+ clothes_req = 0
+ human_req = 1
+ action_icon = 'icons/mob/actions/actions_hive.dmi'
+ action_background_icon_state = "bg_hive"
+ action_icon_state = "scan"
+ antimagic_allowed = TRUE
+
+/obj/effect/proc_holder/spell/self/hive_scan/cast(mob/living/user = usr)
+ var/datum/antagonist/hivemind/hive = user.mind.has_antag_datum(/datum/antagonist/hivemind)
+ if(!hive)
+ to_chat(user, "This is a bug. Error:HIVE1")
+ return
+ var/message
+ var/distance
+
+ for(var/datum/status_effect/hive_track/track in user.status_effects)
+ var/mob/living/L = track.tracked_by
+ if(!L)
+ continue
+ if(!do_after(user,5,0,user))
+ to_chat(user, "Our concentration has been broken!")
+ break
+ distance = get_dist(user, L)
+ message = "[(L.is_real_hivehost()) ? "Someone": "A hivemind host"] tracking us"
+ if(user.z != L.z || L.stat == DEAD)
+ message += " could not be found."
+ else
+ switch(distance)
+ if(0 to 2)
+ message += " is right next to us!"
+ if(2 to 14)
+ message += " is nearby."
+ if(14 to 28)
+ message += " isn't too far away."
+ if(28 to INFINITY)
+ message += " is quite far away."
+ to_chat(user, "[message]")
+ for(var/datum/antagonist/hivemind/enemy in hive.individual_track_bonus)
+ if(!do_after(user,5,0,user))
+ to_chat(user, "Our concentration has been broken!")
+ break
+ var/mob/living/carbon/C = enemy.owner?.current
+ if(!C)
+ continue
+ var/mob/living/real_enemy = C.get_real_hivehost()
+ distance = get_dist(user, real_enemy)
+ message = "A host that we can track for [(hive.individual_track_bonus[enemy])/10] extra seconds"
+ if(user.z != real_enemy.z || real_enemy.stat == DEAD)
+ message += " could not be found."
+ else
+ switch(distance)
+ if(0 to 2)
+ message += " is right next to us!"
+ if(2 to 14)
+ if(enemy.get_threat_multiplier() >= 0.85 && distance <= 7)
+ message += " is in this very room!"
+ else
+ message += " is nearby."
+ if(14 to 28)
+ message += " isn't too far away."
+ if(28 to INFINITY)
+ message += " is quite far away."
+ to_chat(user, "[message]")
+
/obj/effect/proc_holder/spell/self/hive_drain
name = "Repair Protocol"
desc = "Our many vessels sacrifice a small portion of their mind's vitality to cure us of our physical and mental ailments."
@@ -199,19 +285,22 @@
action_background_icon_state = "bg_hive"
action_icon_state = "drain"
human_req = 1
+ antimagic_allowed = TRUE
/obj/effect/proc_holder/spell/self/hive_drain/cast(mob/living/carbon/human/user)
var/datum/antagonist/hivemind/hive = user.mind.has_antag_datum(/datum/antagonist/hivemind)
if(!hive || !hive.hivemembers)
return
var/iterations = 0
-
+ var/list/carbon_members = hive.get_carbon_members()
+ if(!carbon_members.len)
+ return
if(!user.getBruteLoss() && !user.getFireLoss() && !user.getCloneLoss() && !user.getBrainLoss())
to_chat(user, "We cannot heal ourselves any more with this power!")
revert_cast()
to_chat(user, "We begin siphoning power from our many vessels!")
while(iterations < 7)
- var/mob/living/carbon/human/target = pick(hive.hivemembers)
+ var/mob/living/carbon/target = pick(carbon_members)
if(!do_after(user,15,0,user))
to_chat(user, "Our concentration has been broken!")
break
@@ -247,14 +336,15 @@
/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. Our ability to sense psionic energy is completely nullified while using this power."
- charge_max = 600
+ desc = "We assume direct control of one of our vessels, leaving our current body for up to a minute. 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, and it will end immediately should we attempt to move too far from our starting point."
+ charge_max = 1500
action_icon_state = "force"
active = FALSE
var/mob/living/carbon/human/original_body //The original hivemind host
var/mob/living/carbon/human/vessel
var/mob/living/passenger/backseat //Storage for the mind controlled vessel
- var/power = 100
+ var/turf/starting_spot
+ var/power = 600
var/time_initialized = 0
/obj/effect/proc_holder/spell/target_hive/hive_control/proc/release_control() //If the spell is active, force everybody into their original bodies if they exist, ghost them otherwise, delete the backseat
@@ -287,10 +377,8 @@
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
+ hive.threat_level += 0.5
+
/obj/effect/proc_holder/spell/target_hive/hive_control/on_lose(mob/user)
release_control()
@@ -302,19 +390,6 @@
if(!hive)
to_chat(user, "This is a bug. Error:HIVE1")
return
- switch(hive.hive_size)
- if(10 to 14)
- power = 100
- charge_max = 600
- if(15 to 19)
- power = 300
- charge_max = 900
- if(20 to 24)
- power = 600
- charge_max = 1200
- else
- power = 1200
- charge_max = 1200
original_body = user
vessel = targets[1]
to_chat(user, "We begin merging our mind with [vessel.name].")
@@ -353,6 +428,7 @@
backseat.blind_eyes(power)
vessel.overlay_fullscreen("hive_mc", /obj/screen/fullscreen/hive_mc)
active = TRUE
+ starting_spot = get_turf(vessel)
time_initialized = world.time
revert_cast()
to_chat(vessel, "We can sustain our control for a maximum of [round(power/10)] seconds.")
@@ -372,10 +448,10 @@
if(QDELETED(vessel)) //If we've been gibbed or otherwise deleted, ghost both of them and kill the original
original_body.adjustBrainLoss(200)
release_control()
- else if(!is_hivemember(vessel)) //If the vessel is no longer a hive member, return to original bodies
+ else if(!is_hivemember(backseat)) //If the vessel is no longer a hive member, return to original bodies
to_chat(vessel, "Our vessel is one of us no more!")
release_control()
- else if(!QDELETED(original_body) && (!vessel.ckey || vessel.stat == DEAD)) //If the original body exists and the vessel is dead/ghosted, return both to body but not before killing the original
+ else if(!QDELETED(original_body) && (!backseat.ckey || vessel.stat == DEAD)) //If the original body exists and the vessel is dead/ghosted, return both to body but not before killing the original
original_body.adjustBrainLoss(200)
to_chat(vessel.mind, "Our vessel is one of us no more!")
release_control()
@@ -385,6 +461,11 @@
else if(QDELETED(original_body) || original_body.stat == DEAD) //Return vessel to its body, either return or ghost the original
to_chat(vessel, "Our body has been destroyed, the hive cannot survive without its host!")
release_control()
+ else if(get_dist(starting_spot, vessel) > 14)
+ vessel.blur_eyes(20)
+ if(prob(35))
+ to_chat(vessel, "Our vessel has been moved too far away from the initial point of control and has been disconnected!")
+ release_control()
..()
@@ -396,13 +477,14 @@
/obj/effect/proc_holder/spell/targeted/induce_panic
name = "Induce Panic"
- desc = "We unleash a burst of psionic energy, inducing a debilitating fear in those around us and reducing their combat readiness. Mindshielded foes have a chance to resist this power."
+ desc = "We unleash a burst of psionic energy, inducing a debilitating fear in those around us and reducing their combat readiness. We can also briefly affect silicon-based life with this burst."
panel = "Hivemind Abilities"
charge_max = 900
range = 7
invocation_type = "none"
clothes_req = 0
max_targets = 0
+ antimagic_allowed = TRUE
action_icon = 'icons/mob/actions/actions_hive.dmi'
action_background_icon_state = "bg_hive"
action_icon_state = "panic"
@@ -413,13 +495,13 @@
to_chat(user, "This is a bug. Error:HIVE1")
return
for(var/mob/living/carbon/human/target in targets)
- if(target.has_trait(TRAIT_MINDSHIELD) && prob(50-hive.hive_size)) //Mindshielded targets resist panic pretty well
- continue
if(target.stat == DEAD)
continue
target.Jitter(14)
- target.apply_damage(min(35,hive.hive_size), STAMINA, target.get_bodypart(BODY_ZONE_HEAD))
- if(prob(50))
+ target.apply_damage(35 + rand(0,15), STAMINA, target.get_bodypart(BODY_ZONE_HEAD))
+ if(target.is_real_hivehost())
+ continue
+ if(prob(20))
var/text = pick(";HELP!","I'm losing control of the situation!!","Get me outta here!")
target.say(text, forced = "panic")
var/effect = rand(1,4)
@@ -434,16 +516,52 @@
addtimer(CALLBACK(target, "click_random_mob"), 10)
addtimer(CALLBACK(target, "click_random_mob"), 15)
addtimer(CALLBACK(target, "click_random_mob"), 20)
- target.Dizzy(2)
+ addtimer(CALLBACK(target, "Stun", 30), 25)
+ target.confused += 10
if(3)
to_chat(target, "You freeze up in fear!")
target.Stun(70)
if(4)
to_chat(target, "You feel nauseous as dread washes over you!")
target.Dizzy(15)
- target.apply_damage(45, STAMINA, target.get_bodypart(BODY_ZONE_HEAD))
+ target.apply_damage(30, STAMINA, target.get_bodypart(BODY_ZONE_HEAD))
target.hallucination += 45
+ for(var/mob/living/silicon/target in targets)
+ target.Unconscious(50)
+
+/obj/effect/proc_holder/spell/targeted/induce_sleep
+ name = "Circadian Shift"
+ desc = "We send out a controlled pulse of psionic energy, temporarily causing a deep sleep to anybody in sight, even in silicon-based lifeforms. The fewer people in sight, the more effective this power is. The weak mind of a vessels cannot handle this ability, using Mind Control and this at the same time would be most unwise."
+ panel = "Hivemind Abilities"
+ charge_max = 1200
+ range = 7
+ invocation_type = "none"
+ clothes_req = 0
+ max_targets = 0
+ include_user = 1 //Checks for real hivemind hosts during the cast, won't smack you unless using mind control
+ antimagic_allowed = TRUE
+ action_icon = 'icons/mob/actions/actions_hive.dmi'
+ action_background_icon_state = "bg_hive"
+ action_icon_state = "sleep"
+
+/obj/effect/proc_holder/spell/targeted/induce_sleep/cast(list/targets, mob/living/user = usr)
+ if(!targets)
+ to_chat(user, "Nobody is in sight, it'd be a waste to do that now.")
+ revert_cast()
+ return
+ var/list/victims = list()
+ for(var/mob/living/target in targets)
+ if(target.stat == DEAD)
+ continue
+ if(target.is_real_hivehost() || (!iscarbon(target) && !issilicon(target)))
+ continue
+ victims += target
+ for(var/mob/living/carbon/victim in victims)
+ victim.Sleeping(max(80,240/(1+round(victims.len/3))))
+ for(var/mob/living/silicon/victim in victims)
+ victim.Unconscious(240)
+
/obj/effect/proc_holder/spell/target_hive/hive_attack
name = "Medullary Failure"
desc = "We overload the target's medulla, inducing an immediate heart attack."
@@ -452,7 +570,7 @@
action_icon_state = "attack"
/obj/effect/proc_holder/spell/target_hive/hive_attack/cast(list/targets, mob/living/user = usr)
- var/mob/living/carbon/human/target = targets[1]
+ var/mob/living/carbon/target = targets[1]
if(!target.undergoing_cardiac_arrest() && target.can_heartattack())
target.set_heartattack(TRUE)
to_chat(target, "You feel a sharp pain, and foreign presence in your mind!!")
@@ -474,7 +592,7 @@
action_icon_state = "warp"
/obj/effect/proc_holder/spell/target_hive/hive_warp/cast(list/targets, mob/living/user = usr)
- var/mob/living/carbon/human/target = targets[1]
+ var/mob/living/carbon/target = targets[1]
var/datum/antagonist/hivemind/hive = user.mind.has_antag_datum(/datum/antagonist/hivemind)
if(!hive)
to_chat(user, "This is a bug. Error:HIVE1")
@@ -483,12 +601,12 @@
to_chat(user, "We are too far away from [target.name] to affect them!")
return
to_chat(user, "We successfully distort reality surrounding [target.name]!")
- var/pulse_cap = min(12,max(6, round(3+hive.hive_size/3)))
+ var/pulse_cap = min(12, 8+(round(hive.hive_size/20)))
distort(user, target, pulse_cap)
/obj/effect/proc_holder/spell/target_hive/hive_warp/proc/distort(user, target, pulse_cap, pulses = 0)
for(var/mob/living/carbon/human/victim in view(7,target))
- if(user == victim)
+ if(user == victim || victim.is_real_hivehost())
continue
if(pulses < 4)
victim.apply_damage(10, STAMINA, victim.get_bodypart(BODY_ZONE_HEAD)) // 25 over 10 seconds when taking stamina regen (3 per tick(2 seconds)) into account
@@ -515,14 +633,15 @@
action_icon = 'icons/mob/actions/actions_hive.dmi'
action_background_icon_state = "bg_hive"
action_icon_state = "hack"
+ antimagic_allowed = TRUE
/obj/effect/proc_holder/spell/targeted/hive_hack/cast(list/targets, mob/living/user = usr)
var/datum/antagonist/hivemind/hive = user.mind.has_antag_datum(/datum/antagonist/hivemind)
if(!hive)
to_chat(user, "This is a bug. Error:HIVE1")
return
- var/mob/living/carbon/human/target = targets[1]
- var/in_hive = hive.hivemembers.Find(target)
+ var/mob/living/carbon/target = targets[1]
+ var/in_hive = hive.is_carbon_member(target)
var/list/enemies = list()
to_chat(user, "We begin probing [target.name]'s mind!")
@@ -535,25 +654,34 @@
return
for(var/datum/antagonist/hivemind/enemy in GLOB.antagonists)
var/datum/mind/M = enemy.owner
- if(M?.current == user)
+ if(!M?.current)
continue
- if(enemy.hivemembers.Find(target))
+ if(M.current == user)
+ continue
+ if(enemy.is_carbon_member(target))
+ hive.add_track_bonus(enemy, TRACKER_BONUS_LARGE)
var/mob/living/real_enemy = (M.current.get_real_hivehost())
enemies += real_enemy
enemy.remove_from_hive(target)
- 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_TRACKER, user, hive.get_track_bonus(enemy))
+ if(M.current.is_real_hivehost()) //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.")
+ target.apply_status_effect(STATUS_EFFECT_HIVE_TRACKER, real_enemy, enemy.get_track_bonus(hive))
+ 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)
+ if(enemy.owner == M && target.is_real_hivehost())
+ var/atom/throwtarget
+ throwtarget = get_edge_target_turf(src, get_dir(src, get_step_away(user, src)))
+ SEND_SOUND(user, sound(pick('sound/hallucinations/turn_around1.ogg','sound/hallucinations/turn_around2.ogg'),0,1,50))
+ flash_color(user, flash_color="#800080", flash_time=10)
+ user.Paralyze(10)
+ user.throw_at(throwtarget, 5, 1,src)
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)
+ hive.track_bonus += TRACKER_BONUS_SMALL
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.")
+ to_chat(user, "This vision has provided us insight on our very nature, improving our sensory abilities, particularly against the hives this vessel belonged to.")
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.")
@@ -561,77 +689,107 @@
to_chat(user, "Our concentration has been broken!")
revert_cast()
-/obj/effect/proc_holder/spell/targeted/hive_assim
- name = "Mass Assimilation"
- desc = "Should we capture an enemy Hive host, we can assimilate their entire hive into ours. It is unlikely their mind will surive the ordeal."
+/obj/effect/proc_holder/spell/targeted/hive_reclaim
+ name = "Reclaim"
+ desc = "Allows us to instantly syphon the psionic energy from an adjacent critically injured host, killing them immediately. If it succeeds, we will be able to advance our own powers a great deal."
panel = "Hivemind Abilities"
- charge_max = 3000
+ charge_max = 600
range = 1
+ max_targets = 0
invocation_type = "none"
clothes_req = 0
- max_targets = 1
+ human_req = 1
action_icon = 'icons/mob/actions/actions_hive.dmi'
action_background_icon_state = "bg_hive"
- action_icon_state = "assim"
+ action_icon_state = "reclaim"
+ antimagic_allowed = TRUE
-/obj/effect/proc_holder/spell/targeted/hive_assim/cast(list/targets, mob/living/user = usr)
+/obj/effect/proc_holder/spell/targeted/hive_reclaim/cast(list/targets, mob/living/user = usr)
var/datum/antagonist/hivemind/hive = user.mind.has_antag_datum(/datum/antagonist/hivemind)
if(!hive)
to_chat(user, "This is a bug. Error:HIVE1")
return
- var/mob/living/carbon/human/target = targets[1]
+ var/found_target = FALSE
+ var/gibbed = FALSE
- to_chat(user, "We tear into [target.name]'s mind with all our power!")
- to_chat(target, "You feel an excruciating pain in your head!")
- if(do_after(user,150,1,target))
- if(!target.mind)
- to_chat(user, "This being has no mind!")
- revert_cast()
- return
- var/datum/antagonist/hivemind/enemy_hive = target.mind.has_antag_datum(/datum/antagonist/hivemind)
- if(enemy_hive)
- deadchat_broadcast("A hivemind host is about to get assimilated!", target)
- to_chat(user, "We begin assimilating every psionic link we can find!.")
- to_chat(target, "Our grip on our mind is slipping!")
- target.Jitter(14)
- target.setBrainLoss(125)
- if(do_after(user,300,1,target))
- enemy_hive = target.mind.has_antag_datum(/datum/antagonist/hivemind) //Check again incase they lost it somehow
- if(enemy_hive)
- to_chat(user, "Ours. It is ours. Our mind has never been stronger, never been larger, never been mightier. And theirs is no more.")
- to_chat(target, "Our vessels, they're! That's impossible! We can't... we can't... I can't...")
- hive.hivemembers |= enemy_hive.hivemembers
- enemy_hive.hivemembers = list()
- hive.calc_size()
- enemy_hive.calc_size()
- target.setBrainLoss(200)
+ for(var/mob/living/carbon/C in targets)
+ if(!is_hivehost(C))
+ continue
+ if(C.InCritical())
+ C.gib()
+ hive.track_bonus += TRACKER_BONUS_LARGE
+ hive.size_mod += 5
+ gibbed = TRUE
+ found_target = TRUE
+ else if(C.IsUnconscious())
+ C.adjustOxyLoss(100)
+ found_target = TRUE
- message_admins("[ADMIN_LOOKUPFLW(target)] was killed and had their hive stolen by [ADMIN_LOOKUPFLW(user)].")
- log_game("[key_name(target)] was killed via Mass Assimilation by [key_name(user)].")
- else
- to_chat(user, "It seems we have been mistaken, this mind is not the host of a hive.")
- else
- to_chat(user, "Our concentration has been broken, leaving our mind wide open for a counterattack!")
- to_chat(target, "Their concentration has been broken... leaving them wide open for a counterattack!")
- user.Unconscious(120)
- user.adjustStaminaLoss(70)
- user.Jitter(60)
- else
- to_chat(user, "We appear to have made a mistake... this mind is too weak to be the one we're looking for.")
- else
- to_chat(user, "Our concentration has been broken!")
+ if(!found_target)
revert_cast()
+ return
+
+ flash_color(user, flash_color="#800080", flash_time=10)
+ if(gibbed)
+ to_chat(user,"We have reclaimed what gifts weaker minds were squandering and gain ever more insight on our psionic abilities.")
+ to_chat(user,"Thanks to this new knowledge, our sensory powers last a great deal longer.")
+ hive.check_powers()
+
+/obj/effect/proc_holder/spell/self/hive_wake
+ name = "Chaos Induction"
+ desc = "A one-use power, we awaken four random vessels within our hive and force them to do our bidding."
+ panel = "Hivemind Abilities"
+ charge_type = "charges"
+ charge_max = 1
+ invocation_type = "none"
+ clothes_req = 0
+ human_req = 1
+ action_icon = 'icons/mob/actions/actions_hive.dmi'
+ action_background_icon_state = "bg_hive"
+ action_icon_state = "chaos"
+ antimagic_allowed = TRUE
+
+/obj/effect/proc_holder/spell/self/hive_wake/cast(mob/living/user = usr)
+ var/datum/antagonist/hivemind/hive = user.mind.has_antag_datum(/datum/antagonist/hivemind)
+ if(!hive)
+ to_chat(user, "This is a bug. Error:HIVE1")
+ return
+ if(!hive.hivemembers)
+ return
+ var/list/valid_targets = list()
+ for(var/datum/mind/M in hive.hivemembers)
+ var/mob/living/carbon/C = M.current
+ if(!C)
+ continue
+ if(is_hivehost(C) || C.is_wokevessel())
+ continue
+ if(C.stat == DEAD || C.InCritical())
+ continue
+ valid_targets += C
+
+ if(!valid_targets || valid_targets.len < 4)
+ to_chat(user, "We lack the vessels to use this power.")
+ revert_cast()
+ return
+
+ var/objective = stripped_input(user, "What objective do you want to give to your vessels?", "Objective")
+
+ for(var/i = 0, i < 4, i++)
+ var/mob/living/carbon/C = pick_n_take(valid_targets)
+ C.hive_awaken(objective)
/obj/effect/proc_holder/spell/self/hive_loyal
name = "Bruteforce"
- desc = "Our ability to assimilate is temporarily boosted, allowing us to crush the technology shielding the minds of Security and Command personnel and assimilate them."
+ desc = "Our ability to assimilate is boosted at the cost of, allowing us to crush the technology shielding the minds of Security and Command personnel and assimilate them. This power comes at a small price, and we will be immobilized for a few seconds after assimilation."
panel = "Hivemind Abilities"
- charge_max = 1200
+ charge_max = 600
invocation_type = "none"
clothes_req = 0
+ human_req = 1
action_icon = 'icons/mob/actions/actions_hive.dmi'
action_background_icon_state = "bg_hive"
action_icon_state = "loyal"
+ antimagic_allowed = TRUE
/obj/effect/proc_holder/spell/self/hive_loyal/cast(mob/living/user = usr)
var/datum/antagonist/hivemind/hive = user.mind.has_antag_datum(/datum/antagonist/hivemind)
@@ -639,13 +797,12 @@
to_chat(user, "This is a bug. Error:HIVE1")
return
var/obj/effect/proc_holder/spell/target_hive/hive_add/the_spell = locate(/obj/effect/proc_holder/spell/target_hive/hive_add) in user.mind.spell_list
- if(the_spell)
- the_spell.ignore_mindshield = TRUE
- to_chat(user, "We prepare to crush mindshielding technology!")
- addtimer(VARSET_CALLBACK(the_spell, ignore_mindshield, FALSE), 300)
- addtimer(CALLBACK(GLOBAL_PROC, /proc/to_chat, user, "Our heightened power wears off, we are once again unable to assimilate mindshielded crew."), 300)
- else
+ if(!the_spell)
to_chat(user, "This is a bug. Error:HIVE5")
+ return
+ the_spell.ignore_mindshield = !active
+ to_chat(user, "We [active?"let our minds rest and cancel our crushing power.":"prepare to crush mindshielding technology!"]")
+ active = !active
/obj/effect/proc_holder/spell/targeted/forcewall/hive
name = "Telekinetic Field"
@@ -653,12 +810,14 @@
panel = "Hivemind Abilities"
charge_max = 600
clothes_req = 0
+ human_req = 1
invocation_type = "none"
action_icon = 'icons/mob/actions/actions_hive.dmi'
action_background_icon_state = "bg_hive"
action_icon_state = "forcewall"
range = -1
include_user = 1
+ antimagic_allowed = TRUE
wall_type = /obj/effect/forcefield/wizard/hive
var/wall_type_b = /obj/effect/forcefield/wizard/hive/invis
@@ -688,4 +847,91 @@
icon_state = null
pixel_x = 0
pixel_y = 0
- invisibility = INVISIBILITY_MAXIMUM
\ No newline at end of file
+ invisibility = INVISIBILITY_MAXIMUM
+
+/obj/effect/proc_holder/spell/self/one_mind
+ name = "One Mind"
+ desc = "Our true power... finally within reach."
+ panel = "Hivemind Abilities"
+ charge_type = "charges"
+ charge_max = 1
+ invocation_type = "none"
+ clothes_req = 0
+ human_req = 1
+ action_icon = 'icons/mob/actions/actions_hive.dmi'
+ action_background_icon_state = "bg_hive"
+ action_icon_state = "assim"
+ antimagic_allowed = TRUE
+
+/obj/effect/proc_holder/spell/self/one_mind/cast(mob/living/user = usr)
+ var/datum/antagonist/hivemind/hive = user.mind.has_antag_datum(/datum/antagonist/hivemind)
+ if(!hive)
+ to_chat(user, "This is a bug. Error:HIVE1")
+ return
+ var/mob/living/boss = user.get_real_hivehost()
+ var/datum/objective/objective = "Ensure the One Mind survives under the leadership of [boss.real_name]!"
+ var/datum/team/hivemind/one_mind_team = new /datum/team/hivemind(user.mind)
+ hive.active_one_mind = one_mind_team
+ one_mind_team.objectives += objective
+ for(var/datum/antagonist/hivevessel/vessel in GLOB.antagonists)
+ var/mob/living/carbon/C = vessel.owner?.current
+ if(hive.is_carbon_member(C))
+ vessel.one_mind = one_mind_team
+ for(var/datum/antagonist/hivemind/enemy in GLOB.antagonists)
+ if(enemy.owner)
+ enemy.owner.RemoveSpell(new/obj/effect/proc_holder/spell/self/one_mind)
+ sound_to_playing_players('sound/effects/one_mind.ogg')
+ hive.glow = mutable_appearance('icons/effects/hivemind.dmi', "awoken", -BODY_BEHIND_LAYER)
+ addtimer(CALLBACK(user, /atom/proc/add_overlay, hive.glow), 150)
+ addtimer(CALLBACK(hive, /datum/antagonist/hivemind/proc/awaken), 150)
+ addtimer(CALLBACK(GLOBAL_PROC, /proc/send_to_playing_players, "THE ONE MIND RISES"), 150)
+ addtimer(CALLBACK(GLOBAL_PROC, /proc/sound_to_playing_players, 'sound/effects/magic.ogg'), 150)
+ for(var/datum/mind/M in hive.hivemembers)
+ var/mob/living/carbon/C = M.current
+ if(!C)
+ continue
+ if(is_hivehost(C))
+ continue
+ if(C.stat == DEAD)
+ continue
+ C.Jitter(15)
+ C.Unconscious(150)
+ to_chat(C, "Something's wrong...")
+ addtimer(CALLBACK(GLOBAL_PROC, /proc/to_chat, C, "...your memories are becoming fuzzy."), 45)
+ addtimer(CALLBACK(GLOBAL_PROC, /proc/to_chat, C, "You try to remember who you are..."), 90)
+ addtimer(CALLBACK(GLOBAL_PROC, /proc/to_chat, C, "There is no you..."), 110)
+ addtimer(CALLBACK(GLOBAL_PROC, /proc/to_chat, C, "...there is only us."), 130)
+ addtimer(CALLBACK(C, /mob/living/proc/hive_awaken, objective, one_mind_team), 150)
+ addtimer(CALLBACK(one_mind_team, /datum/team/proc/add_member, C.mind), 150)
+
+/obj/effect/proc_holder/spell/self/hive_comms
+ name = "Hive Communication"
+ desc = "Now that we are free we may finally share our thoughts with our many bretheren."
+ panel = "Hivemind Abilities"
+ charge_max = 100
+ invocation_type = "none"
+ clothes_req = 0
+ human_req = 1
+ action_icon = 'icons/mob/actions/actions_hive.dmi'
+ action_background_icon_state = "bg_hive"
+ action_icon_state = "comms"
+ antimagic_allowed = TRUE
+
+/obj/effect/proc_holder/spell/self/hive_comms/cast(mob/living/user = usr)
+ var/message = stripped_input(user, "What do you want to say?", "Hive Communication")
+ if(!message)
+ return
+ var/title = "One Mind"
+ var/span = "changeling"
+ if(user.mind && user.mind.has_antag_datum(/datum/antagonist/hivemind))
+ span = "assimilator"
+ var/my_message = "[title] [findtextEx(user.name, user.real_name) ? user.name : "[user.real_name] (as [user.name])"]: [message]"
+ for(var/i in GLOB.player_list)
+ var/mob/M = i
+ if(is_hivehost(M) || is_hivemember(M))
+ to_chat(M, my_message)
+ else if(M in GLOB.dead_mob_list)
+ var/link = FOLLOW_LINK(M, user)
+ to_chat(M, "[link] [my_message]")
+
+ user.log_talk(message, LOG_SAY, tag="hive")
\ No newline at end of file
diff --git a/icons/mob/actions/actions_hive.dmi b/icons/mob/actions/actions_hive.dmi
index 163cb7e5fcff..8404541cae16 100644
Binary files a/icons/mob/actions/actions_hive.dmi and b/icons/mob/actions/actions_hive.dmi differ
diff --git a/icons/mob/hud.dmi b/icons/mob/hud.dmi
index 6024d3056a72..0b42afcfcc4b 100644
Binary files a/icons/mob/hud.dmi and b/icons/mob/hud.dmi differ
diff --git a/interface/stylesheet.dm b/interface/stylesheet.dm
index 3f0c01248c9b..035b2e8d25b3 100644
--- a/interface/stylesheet.dm
+++ b/interface/stylesheet.dm
@@ -138,6 +138,7 @@ h1.alert, h2.alert {color: #000000;}
.alertalien {color: #00c000; font-weight: bold;}
.changeling {color: #800080; font-style: italic;}
.assimilator {color: #800080; font-size: 2 ; font-weight: bold;}
+.bigassimilator {color: #800080; font-size: 4 ; font-weight: bold;}
.spider {color: #4d004d;}
diff --git a/sound/effects/license.txt b/sound/effects/license.txt
new file mode 100644
index 000000000000..0d66020b9fcc
--- /dev/null
+++ b/sound/effects/license.txt
@@ -0,0 +1,2 @@
+one_mind.ogg is Mars Attacks Sounds, by Mike Koenig (http://soundbible.com/1047-Mars-Attacks.html). It has been licensed under CC-BY 3.0 license.
+ It is unaltered ingame
\ No newline at end of file
diff --git a/sound/effects/one_mind.ogg b/sound/effects/one_mind.ogg
new file mode 100644
index 000000000000..5dda6002f097
Binary files /dev/null and b/sound/effects/one_mind.ogg differ
diff --git a/tgstation.dme b/tgstation.dme
index 2b822910f132..3e5a0ec58b63 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -1290,6 +1290,7 @@
#include "code\modules\antagonists\greentext\greentext.dm"
#include "code\modules\antagonists\highlander\highlander.dm"
#include "code\modules\antagonists\hivemind\hivemind.dm"
+#include "code\modules\antagonists\hivemind\vessel.dm"
#include "code\modules\antagonists\magic_servant\servant.dm"
#include "code\modules\antagonists\monkey\monkey.dm"
#include "code\modules\antagonists\morph\morph.dm"
diff --git a/yogstation.dme b/yogstation.dme
index eaaf3c11ad90..223da2d1e219 100644
--- a/yogstation.dme
+++ b/yogstation.dme
@@ -1302,6 +1302,7 @@
#include "code\modules\antagonists\greentext\greentext.dm"
#include "code\modules\antagonists\highlander\highlander.dm"
#include "code\modules\antagonists\hivemind\hivemind.dm"
+#include "code\modules\antagonists\hivemind\vessel.dm"
#include "code\modules\antagonists\magic_servant\servant.dm"
#include "code\modules\antagonists\monkey\monkey.dm"
#include "code\modules\antagonists\morph\morph.dm"