diff --git a/code/__DEFINES/antagonists.dm b/code/__DEFINES/antagonists.dm
index 13de9d280bb..ea91ea25552 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 c6ca0c72d0c..bf95c5a6bbb 100644
--- a/code/__DEFINES/status_effects.dm
+++ b/code/__DEFINES/status_effects.dm
@@ -100,6 +100,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 ab1905dcd53..56a8a6d2759 100644
--- a/code/__DEFINES/traits.dm
+++ b/code/__DEFINES/traits.dm
@@ -160,3 +160,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 21999eeda65..22d39af69f3 100644
--- a/code/datums/hud.dm
+++ b/code/datums/hud.dm
@@ -28,8 +28,8 @@ GLOBAL_LIST_INIT(huds, list(
ANTAG_HUD_CLOCKWORK = new/datum/atom_hud/antag(),
ANTAG_HUD_BROTHER = new/datum/atom_hud/antag/hidden(),
ANTAG_HUD_HIVE = new/datum/atom_hud/antag/hidden(),
- ANTAG_HUD_OBSESSED = new/datum/atom_hud/antag/hidden()
- ))
+ ANTAG_HUD_OBSESSED = 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 646087e8532..0fc032b78f8 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 5ee1c798023..6b8d96e694d 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 b26ed9c3bdf..f29b8b2a3a7 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 9547506e940..ae6135af30c 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 99b834872cf..ee21fd7227a 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 00000000000..eef4c0ff184
--- /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 c50486dca92..721583cf338 100644
--- a/code/modules/goonchat/browserassets/css/browserOutput.css
+++ b/code/modules/goonchat/browserassets/css/browserOutput.css
@@ -376,6 +376,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 52f2079e0bd..6e7dd0225f0 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 2c12a0d7995..c4751434f17 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 f7f0f71d437..6cd81438410 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."
@@ -206,13 +292,15 @@
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
@@ -248,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
@@ -288,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()
@@ -303,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].")
@@ -354,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.")
@@ -373,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()
@@ -386,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()
..()
@@ -397,7 +477,7 @@
/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
@@ -415,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)
@@ -436,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."
@@ -454,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!!")
@@ -476,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")
@@ -485,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
@@ -524,8 +640,8 @@
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!")
@@ -538,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.")
@@ -564,75 +689,103 @@
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"
@@ -644,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"
@@ -658,6 +810,7 @@
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"
@@ -695,3 +848,90 @@
pixel_x = 0
pixel_y = 0
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/effects/hivemind.dmi b/icons/effects/hivemind.dmi
new file mode 100644
index 00000000000..37fec1d8e22
Binary files /dev/null and b/icons/effects/hivemind.dmi differ
diff --git a/icons/mob/actions/actions_hive.dmi b/icons/mob/actions/actions_hive.dmi
index 163cb7e5fcf..8404541cae1 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 6024d3056a7..0b42afcfcc4 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 010f2c8a6c4..01231650583 100644
--- a/interface/stylesheet.dm
+++ b/interface/stylesheet.dm
@@ -136,6 +136,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 00000000000..0d66020b9fc
--- /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 00000000000..5dda6002f09
Binary files /dev/null and b/sound/effects/one_mind.ogg differ
diff --git a/tgstation.dme b/tgstation.dme
index f357ba9905e..22513677730 100755
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -1289,6 +1289,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"