diff --git a/code/__DEFINES/is_helpers.dm b/code/__DEFINES/is_helpers.dm
index 03778ec77400..dae1c56072c0 100644
--- a/code/__DEFINES/is_helpers.dm
+++ b/code/__DEFINES/is_helpers.dm
@@ -111,6 +111,8 @@ GLOBAL_LIST_INIT(turfs_without_ground, typecacheof(list(
#define isrevenant(A) (istype(A, /mob/living/simple_animal/revenant))
+#define ishorror(A) (istype(A, /mob/living/simple_animal/horror))
+
#define isbot(A) (istype(A, /mob/living/simple_animal/bot))
#define isshade(A) (istype(A, /mob/living/simple_animal/shade))
diff --git a/code/__DEFINES/role_preferences.dm b/code/__DEFINES/role_preferences.dm
index 3c4a3043527e..27e99baa6dde 100644
--- a/code/__DEFINES/role_preferences.dm
+++ b/code/__DEFINES/role_preferences.dm
@@ -45,6 +45,7 @@
#define ROLE_GANG "gangster" // Yogs
#define ROLE_DARKSPAWN "darkspawn" //Yogs
#define ROLE_HOLOPARASITE "Holoparasite" // Yogs
+#define ROLE_HORROR "Eldritch Horror" // Yogs
#define ROLE_ZOMBIE "Zombie"
@@ -63,6 +64,7 @@ GLOBAL_LIST_INIT(special_roles, list(
ROLE_MALF,
ROLE_REV = /datum/game_mode/revolution,
ROLE_ALIEN,
+ ROLE_HORROR,
ROLE_PAI,
ROLE_CULTIST = /datum/game_mode/cult,
ROLE_BLOB,
diff --git a/code/_globalvars/lists/names.dm b/code/_globalvars/lists/names.dm
index e36e464685e0..32029882bf1d 100644
--- a/code/_globalvars/lists/names.dm
+++ b/code/_globalvars/lists/names.dm
@@ -20,6 +20,7 @@ GLOBAL_LIST_INIT(plasmaman_names, world.file2list("strings/names/plasmaman.txt")
GLOBAL_LIST_INIT(ethereal_names, world.file2list("strings/names/ethereal.txt"))
GLOBAL_LIST_INIT(posibrain_names, world.file2list("strings/names/posibrain.txt"))
GLOBAL_LIST_INIT(nightmare_names, world.file2list("strings/names/nightmare.txt"))
+GLOBAL_LIST_INIT(horror_names, world.file2list("strings/names/horror.txt"))
GLOBAL_LIST_INIT(megacarp_first_names, world.file2list("strings/names/megacarp1.txt"))
GLOBAL_LIST_INIT(megacarp_last_names, world.file2list("strings/names/megacarp2.txt"))
diff --git a/code/_onclick/hud/horror.dm b/code/_onclick/hud/horror.dm
new file mode 100644
index 000000000000..a625df716845
--- /dev/null
+++ b/code/_onclick/hud/horror.dm
@@ -0,0 +1,17 @@
+/obj/screen/horror_chemicals
+ name = "chemicals"
+ icon_state = "horror_counter"
+ screen_loc = ui_lingchemdisplay
+
+/datum/hud/chemical_counter
+ ui_style = 'icons/mob/screen_midnight.dmi'
+ var/obj/screen/horror_chemicals/chemical_counter
+
+/datum/hud/chemical_counter/New(mob/owner)
+ . = ..()
+ chemical_counter = new /obj/screen/horror_chemicals
+ infodisplay += chemical_counter
+
+/datum/hud/chemical_counter/Destroy()
+ . = ..()
+ chemical_counter = null
\ No newline at end of file
diff --git a/code/game/objects/effects/temporary_visuals/miscellaneous.dm b/code/game/objects/effects/temporary_visuals/miscellaneous.dm
index 6bd9e304cd8b..a716841b6317 100644
--- a/code/game/objects/effects/temporary_visuals/miscellaneous.dm
+++ b/code/game/objects/effects/temporary_visuals/miscellaneous.dm
@@ -476,3 +476,8 @@
/obj/effect/temp_visual/dir_setting/space_wind/Initialize(mapload, set_dir, set_alpha = 255)
. = ..()
alpha = set_alpha
+
+/obj/effect/temp_visual/summon
+ randomdir = 0
+ duration = 20
+ icon_state = "summon"
diff --git a/code/game/objects/items/storage/uplink_kits.dm b/code/game/objects/items/storage/uplink_kits.dm
index 163550862f7a..9e5c8e9a6b54 100644
--- a/code/game/objects/items/storage/uplink_kits.dm
+++ b/code/game/objects/items/storage/uplink_kits.dm
@@ -598,3 +598,16 @@
/obj/item/storage/box/syndie_kit/bee_grenades/PopulateContents()
for(var/i in 1 to 3)
new /obj/item/grenade/spawnergrenade/buzzkill(src)
+
+/obj/item/storage/box/syndie_kit/horror
+ name = "horror-in-a-box"
+ illustration = "writing_syndie"
+ desc = "A sleek, sturdy box with tentacles slithering from the inside. Uh oh."
+
+obj/item/storage/box/syndie_kit/horror/PopulateContents()
+ var/datum/reagent/W = pick(/datum/reagent/water/holywater, /datum/reagent/consumable/garlic, /datum/reagent/consumable/ketchup, /datum/reagent/consumable/eggyolk, /datum/reagent/consumable/sodiumchloride, /datum/reagent/consumable/hot_ramen)
+ W = new W
+ var/obj/item/horrorspawner/C = new /obj/item/horrorspawner(src)
+ C.weakness = W
+ var/obj/item/paper/crumpled/horrorweakness/N = new /obj/item/paper/crumpled/horrorweakness(src)
+ N.info = "..it's weakness...is...[W.name]...do.....not....
....let....it...."
diff --git a/code/modules/admin/sql_ban_system.dm b/code/modules/admin/sql_ban_system.dm
index 3480853aee8b..2d9c7b033784 100644
--- a/code/modules/admin/sql_ban_system.dm
+++ b/code/modules/admin/sql_ban_system.dm
@@ -266,10 +266,10 @@
break_counter++
output += ""
var/list/long_job_lists = list("Civilian" = GLOB.civilian_positions,
- "Ghost and Other Roles" = list(ROLE_BRAINWASHED, ROLE_DEATHSQUAD, ROLE_DRONE, ROLE_FUGITIVE, ROLE_HOLOPARASITE, ROLE_LAVALAND, ROLE_MIND_TRANSFER, ROLE_POSIBRAIN, ROLE_SENTIENCE),
+ "Ghost and Other Roles" = list(ROLE_BRAINWASHED, ROLE_DEATHSQUAD, ROLE_DRONE, ROLE_FUGITIVE, ROLE_HOLOPARASITE, ROLE_HORROR, ROLE_LAVALAND, ROLE_MIND_TRANSFER, ROLE_POSIBRAIN, ROLE_SENTIENCE),
"Antagonist Positions" = list(ROLE_ABDUCTOR, ROLE_ALIEN, ROLE_BLOB,
ROLE_BROTHER, ROLE_CHANGELING, ROLE_CULTIST,
- ROLE_DEVIL, ROLE_FUGITIVE, ROLE_HOLOPARASITE, ROLE_INTERNAL_AFFAIRS, ROLE_MALF,
+ ROLE_DEVIL, ROLE_FUGITIVE, ROLE_HOLOPARASITE, ROLE_HORROR, ROLE_INTERNAL_AFFAIRS, ROLE_MALF,
ROLE_MONKEY, ROLE_NINJA, ROLE_OPERATIVE,
ROLE_OVERTHROW, ROLE_REV, ROLE_REVENANT,
ROLE_REV_HEAD, ROLE_SERVANT_OF_RATVAR, ROLE_SYNDICATE,
diff --git a/code/modules/antagonists/changeling/powers/headcrab.dm b/code/modules/antagonists/changeling/powers/headcrab.dm
index 09a062747d2e..0a220e9a8ec9 100644
--- a/code/modules/antagonists/changeling/powers/headcrab.dm
+++ b/code/modules/antagonists/changeling/powers/headcrab.dm
@@ -32,6 +32,9 @@
to_chat(S, "Your sensors are disabled by a shower of blood!")
S.Paralyze(60)
var/turf = get_turf(user)
+ var/mob/living/simple_animal/horror/H = user.has_horror_inside()
+ if(H)
+ H.leave_victim()
user.gib()
. = TRUE
sleep(5) // So it's not killed in explosion
diff --git a/code/modules/antagonists/changeling/powers/panacea.dm b/code/modules/antagonists/changeling/powers/panacea.dm
index bf6547f83cc5..a41d804accfb 100644
--- a/code/modules/antagonists/changeling/powers/panacea.dm
+++ b/code/modules/antagonists/changeling/powers/panacea.dm
@@ -10,6 +10,13 @@
//Heals the things that the other regenerative abilities don't.
/datum/action/changeling/panacea/sting_action(mob/user)
to_chat(user, "We cleanse impurities from our form.")
+ var/mob/living/simple_animal/horror/H = user.has_horror_inside()
+ if(H)
+ H.leave_victim()
+ if(iscarbon(user))
+ var/mob/living/carbon/C = user
+ C.vomit(0, toxic = TRUE)
+ to_chat(user, "A parasite exits our form.")
..()
var/list/bad_organs = list(
user.getorgan(/obj/item/organ/body_egg),
diff --git a/code/modules/antagonists/horror/horror.dm b/code/modules/antagonists/horror/horror.dm
new file mode 100644
index 000000000000..8a5dba3473db
--- /dev/null
+++ b/code/modules/antagonists/horror/horror.dm
@@ -0,0 +1,864 @@
+/mob/living/simple_animal/horror
+ name = "eldritch horror"
+ real_name = "eldritch horror"
+ desc = "Your eyes can barely comprehend what they're looking at."
+ icon_state = "horror"
+ icon_living = "horror"
+ icon_dead = "horror_dead"
+ health = 50
+ maxHealth = 50
+ melee_damage_lower = 10
+ melee_damage_upper = 10
+ see_in_dark = 7
+ stop_automated_movement = TRUE
+ attacktext = "bites"
+ speak_emote = list("gurgles")
+ attack_sound = 'sound/weapons/bite.ogg'
+ pass_flags = PASSTABLE | PASSMOB
+ mob_size = MOB_SIZE_SMALL
+ faction = list("neutral","silicon","hostile","creature","heretics")
+ ventcrawler = VENTCRAWLER_ALWAYS
+ initial_language_holder = /datum/language_holder/universal
+ hud_type = /datum/hud/chemical_counter
+
+ atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0)
+ minbodytemp = 0
+ maxbodytemp = 1500
+
+ var/playstyle_string = "You are an eldritch horror, an evermutating parasitic abomination. Seek human souls to consume. \
+ Crawl into people's heads and steal their essence. Use it to mutate yourself, giving you access to more power and abilities. \
+ You operate on chemicals that get built up while you spend time in someone's head. You are weak when outside, play carefully.\
+ Check your notes to see which chemical reagent is your bane, and avoid from getting in contact with it. "
+
+ var/datum/reagent/weakness = /datum/reagent/consumable/sugar //default is sugar, but a random one is attributed
+ var/mob/living/carbon/victim
+ var/datum/mind/target
+ var/mob/living/captive_brain/host_brain
+ var/truename = null
+ var/available_points = 4
+ var/consumed_souls = 0
+ var/list/horrorabilities = list() //An associative list ("id" = ability datum) containing the abilities the horror has
+ var/list/horrorupgrades = list() //same, but for permanent upgrades
+ var/docile = FALSE
+ var/bonding = FALSE
+ var/controlling = FALSE
+ var/chemicals = 10
+ var/chem_regen_rate = 2
+ var/used_freeze
+ var/used_target
+ var/horror_chems = list(/datum/horror_chem/epinephrine,/datum/horror_chem/mannitol,/datum/horror_chem/bicaridine,/datum/horror_chem/kelotane,/datum/horror_chem/charcoal)
+
+ var/leaving = FALSE
+ var/hiding = FALSE
+ var/invisible = FALSE
+ var/waketimerid = null
+ var/datum/action/innate/horror/talk_to_horror/talk_to_horror_action = new
+
+/mob/living/simple_animal/horror/Initialize(mapload, gen=1)
+ ..()
+ real_name = "Eldritch horror"
+ truename = "[pick(GLOB.horror_names)]"
+
+ //default abilities
+ add_ability("mutate")
+ add_ability("seek_soul")
+ add_ability("consume_soul")
+ add_ability("talk_to_host")
+ add_ability("freeze_victim")
+ add_ability("infest")
+ add_ability("toggle_hide")
+ add_ability("talk_to_brain")
+ add_ability("take_control")
+ add_ability("leave_body")
+ add_ability("make_chems")
+ add_ability("talk_to_brain")
+ add_ability("release_control")
+ RefreshAbilities()
+
+ var/datum/atom_hud/hud = GLOB.huds[DATA_HUD_MEDICAL_ADVANCED]
+ hud.add_hud_to(src)
+ update_horror_hud()
+
+
+/mob/living/simple_animal/horror/Destroy()
+ host_brain = null
+ victim = null
+ return ..()
+
+
+/mob/living/simple_animal/horror/proc/has_chemicals(amt)
+ return chemicals >= amt
+
+/mob/living/simple_animal/horror/proc/use_chemicals(amt)
+ if(!has_chemicals(amt))
+ return FALSE
+ chemicals -= amt
+ update_horror_hud()
+ return TRUE
+
+/mob/living/simple_animal/horror/proc/regenerate_chemicals(amt)
+ chemicals += amt
+ chemicals = min(250, chemicals)
+ update_horror_hud()
+
+/mob/living/simple_animal/horror/proc/update_horror_hud()
+ if(!src || !hud_used)
+ return
+ var/datum/hud/chemical_counter/H = hud_used
+ var/obj/screen/counter = H.chemical_counter
+ counter.maptext = "
[chemicals]
"
+
+/mob/living/simple_animal/horror/proc/can_use_ability()
+ if(stat != CONSCIOUS)
+ to_chat(src, "You cannot do that in your current state.")
+ return FALSE
+ if(docile)
+ to_chat(src, "You are feeling far too docile to do that.")
+ return FALSE
+ return TRUE
+
+/mob/living/simple_animal/horror/proc/SearchTarget()
+ if(target)
+ if(world.time - used_target < 3 MINUTES)
+ to_chat(src, "You cannot use that ability again so soon.")
+ return
+ if(alert("You already have a target ([target.name]). Would you like to change that target?","Swap targets?","Yes","No") != "Yes")
+ return
+
+ var/datum/objective/A = new
+ A.owner = mind
+ var/list/targets = list()
+ for(var/i in 0 to 4)
+ var/datum/mind/targeted
+ if(mind.enslaved_to)
+ targeted = A.find_target(null, list(mind.enslaved_to.mind, target))
+ else
+ targeted = A.find_target(null, list(target))
+ if(!targeted || !targeted.hasSoul)
+ break
+ targets[targeted.current.real_name] = targeted.current
+
+ target = targets[input(src,"Choose your next target","Target") in targets]
+ qdel(A)
+
+ if(target)
+ used_target = world.time
+ to_chat(src,"Your new target has been selected, go and consume [target.name]'s soul!")
+ apply_status_effect(/datum/status_effect/agent_pinpointer/horror)
+ for(var/datum/status_effect/agent_pinpointer/horror/status in status_effects)
+ status.scan_target = target
+ else
+ to_chat(src,"A new target could not be found.")
+
+/mob/living/simple_animal/horror/proc/ConsumeSoul()
+ if(!can_use_ability())
+ return
+
+ if(!src.victim.mind.hasSoul)
+ to_chat(src, "This host doesn't have a soul!")
+ return
+
+ if(victim == mind.enslaved_to)
+ to_chat(src, "No, not yet... We still need them...")
+ return
+
+ if(victim != target)
+ to_chat(src, "This soul isn't your target, you can't consume it!")
+ return
+
+ to_chat(src, "You begin consuming [src.victim.name]'s soul!")
+ addtimer(CALLBACK(src, .proc/consume), 200)
+
+/mob/living/simple_animal/horror/proc/consume()
+ if(!can_use_ability() || !victim || !victim.mind.hasSoul)
+ return
+ consumed_souls++
+ available_points++
+ to_chat(src, "You succeed in consuming [victim.name]'s soul!")
+ to_chat(src.victim, "You suddenly feel weak and hollow inside...")
+ victim.health -= 20
+ victim.maxHealth -= 20
+ victim.mind.hasSoul = FALSE
+ target = null
+ remove_status_effect(/datum/status_effect/agent_pinpointer/horror)
+ playsound(src, 'sound/effects/curseattack.ogg', 150)
+ playsound(src, 'sound/effects/ghost.ogg', 50)
+
+/mob/living/simple_animal/horror/proc/Communicate()
+ if(!can_use_ability())
+ return
+ if(!src.victim)
+ to_chat(src, "You do not have a host to communicate with!")
+ return
+
+ var/input = stripped_input(src, "Please enter a message to tell your host.", "Horror", null)
+ if(!input)
+ return
+
+ if(src && !QDELETED(src) && !QDELETED(src.victim))
+ var/say_string = (src.docile) ? "slurs" :"states"
+ if(src.victim)
+ to_chat(victim, "[truename] [say_string]: [input]")
+ log_say("Horror Communication: [key_name(src)] -> [key_name(victim)] : [input]")
+ for(var/M in GLOB.dead_mob_list)
+ if(isobserver(M))
+ var/rendered = "Horror Communication from [truename] : [input]"
+ var/link = FOLLOW_LINK(M, src)
+ to_chat(M, "[link] [rendered]")
+ to_chat(src, "[truename] [say_string]: [input]")
+ add_verb(victim, /mob/living/proc/horror_comm)
+ talk_to_horror_action.Grant(victim)
+
+/mob/living/proc/horror_comm()
+ set name = "Converse with Horror"
+ set category = "Horror"
+ set desc = "Communicate mentally with the thing in your head."
+
+ var/mob/living/simple_animal/horror/B = has_horror_inside()
+ if(B)
+ var/input = stripped_input(src, "Please enter a message to tell the horror.", "Message", "")
+ if(!input)
+ return
+
+ to_chat(B, "[src.name] says: [input]")
+ src.log_talk("Horror Communication: [key_name(src)] -> [key_name(B)] : [input]", LOG_SAY, tag="changeling")
+
+ for(var/M in GLOB.dead_mob_list)
+ if(isobserver(M))
+ var/rendered = "Horror Communication from [B.truename] : [input]"
+ var/link = FOLLOW_LINK(M, src)
+ to_chat(M, "[link] [rendered]")
+ to_chat(src, "[src] says: [input]")
+
+/mob/living/proc/trapped_mind_comm()
+ var/mob/living/simple_animal/horror/B = has_horror_inside()
+ if(!B || !B.host_brain)
+ return
+ var/mob/living/captive_brain/CB = B.host_brain
+ var/input = stripped_input(src, "Please enter a message to tell the trapped mind.", "Message", null)
+ if(!input)
+ return
+
+ to_chat(CB, "[B.truename] says: [input]")
+ log_say("Horror Communication: [key_name(B)] -> [key_name(CB)] : [input]")
+
+ for(var/M in GLOB.dead_mob_list)
+ if(isobserver(M))
+ var/rendered = "Horror Communication from [B.truename] : [input]"
+ var/link = FOLLOW_LINK(M, src)
+ to_chat(M, "[link] [rendered]")
+ to_chat(src, "[B.truename] says: [input]")
+
+/mob/living/simple_animal/horror/Life()
+ ..()
+ if(horrorupgrades["regen"])
+ heal_overall_damage(5)
+
+ if(invisible) //don't regenerate chemicals when invisible
+ if(has_chemicals(5))
+ use_chemicals(5)
+ alpha = max(alpha - 100, 1)
+ else
+ to_chat(src, "You ran out of chemicals to support your invisibility.")
+ invisible = FALSE
+ Update_Invisibility_Button()
+ else
+ if(horrorupgrades["nohost_regen"])
+ regenerate_chemicals(chem_regen_rate)
+ else if(victim)
+ if(victim.stat == DEAD)
+ regenerate_chemicals(1)
+ else
+ regenerate_chemicals(chem_regen_rate)
+ alpha = min(255, alpha + 50)
+
+ if(victim)
+ if(stat != DEAD && victim.stat != DEAD)
+ heal_overall_damage(1)
+ if(victim.reagents.has_reagent(weakness.type))
+ if(!docile || waketimerid)
+ if(controlling)
+ to_chat(victim, "You feel the soporific flow of [weakness.name] in your host's blood, lulling you into docility.")
+ else
+ to_chat(src, "You feel the soporific flow of [weakness.name] in your host's blood, lulling you into docility.")
+ if(waketimerid)
+ deltimer(waketimerid)
+ waketimerid = null
+ docile = TRUE
+ else
+ if(docile && !waketimerid)
+ if(controlling)
+ to_chat(victim, "You start shaking off your lethargy as the [weakness.name] leaves your host's blood. This will take about 10 seconds...")
+ else
+ to_chat(src, "You start shaking off your lethargy as the [weakness.name] leaves your host's blood. This will take about 10 seconds...")
+
+ waketimerid = addtimer(CALLBACK(src, "wakeup"), 10, TIMER_STOPPABLE)
+ if(controlling)
+ if(docile)
+ to_chat(victim, "You are feeling far too docile to continue controlling your host...")
+ victim.release_control()
+ return
+
+
+/mob/living/simple_animal/horror/proc/wakeup()
+ if(controlling)
+ to_chat(victim, "You finish shaking off your lethargy.")
+ else
+ to_chat(src, "You finish shaking off your lethargy.")
+ docile = FALSE
+ if(waketimerid)
+ waketimerid = null
+
+/mob/living/simple_animal/horror/say(message, bubble_type, var/list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null)
+ if(victim)
+ to_chat(src, "You cannot speak out loud while inside a host!")
+ return
+ return ..()
+
+/mob/living/simple_animal/horror/emote(act, m_type = null, message = null, intentional = FALSE)
+ if(victim)
+ to_chat(src, "You cannot emote while inside a host!")
+ return
+ return ..()
+
+/mob/living/simple_animal/horror/UnarmedAttack(atom/A)
+ if(istype(A, /obj/machinery/door/airlock))
+ visible_message("[src] slips their tentacles into the airlock and starts prying it open!", "You start moving onto the airlock.")
+ playsound(A, 'sound/misc/splort.ogg', 50, 1)
+ if(!do_after(src, 5 SECONDS, target = A))
+ return
+ visible_message("[src] forces themselves through the airlock!", "You force yourself through the airlock.")
+ forceMove(get_turf(A))
+ playsound(A, 'sound/machines/airlock_alien_prying.ogg', 50, 1)
+ return
+ if(isliving(A))
+ if(victim || A == src.mind.enslaved_to)
+ healthscan(usr, A)
+ chemscan(usr, A)
+ else
+ alpha = 255
+ if(hiding)
+ var/datum/action/innate/horror/H = has_ability("toggle_hide")
+ H.Activate()
+ if(invisible)
+ var/datum/action/innate/horror/H = has_ability("chameleon")
+ H.Activate()
+ Update_Invisibility_Button()
+ ..()
+
+/mob/living/simple_animal/horror/ex_act()
+ if(victim)
+ return
+
+ ..()
+
+/mob/living/simple_animal/horror/proc/infect_victim()
+ if(!can_use_ability())
+ return
+ if(victim)
+ to_chat(src, "You are already within a host.")
+
+ if(stat == DEAD)
+ return
+
+ var/list/choices = list()
+ for(var/mob/living/carbon/H in view(1,src))
+ if(H!=src && Adjacent(H))
+ choices += H
+
+ if(!choices.len)
+ return
+ var/mob/living/carbon/H = choices.len > 1 ? input(src,"Who do you wish to infest?") in null|choices : choices[1]
+ if(!H || !src)
+ return
+
+ if(!Adjacent(H))
+ return
+
+ if(H.has_horror_inside())
+ to_chat(src, "[H] is already infested!")
+ return
+
+ to_chat(src, "You slither your tentacles up [H] and begin probing at their ear canal...")
+ if(!do_mob(src, H, 30))
+ to_chat(src, "As [H] moves away, you are dislodged and fall to the ground.")
+ return
+
+ if(!H || !src)
+ return
+
+ Infect(H)
+
+/mob/living/simple_animal/horror/proc/Infect(mob/living/carbon/C)
+ if(!C)
+ return
+ var/obj/item/bodypart/head/head = C.get_bodypart(BODY_ZONE_HEAD)
+ if(!head)
+ to_chat(src, "[C] doesn't have a head!")
+ return
+ var/hasbrain = FALSE
+ for(var/obj/item/organ/brain/X in C.internal_organs)
+ hasbrain = TRUE
+ break
+ if(!hasbrain)
+ to_chat(src, "[C] doesn't have a brain! ")
+ return
+
+ if(C.has_horror_inside())
+ to_chat(src, "[C] is already infested!")
+ return
+
+ if((!C.key || !C.mind) && C != target)
+ to_chat(src, "[C]'s mind seems unresponsive. Try someone else!")
+ return
+
+ invisible = FALSE
+ Update_Invisibility_Button()
+ victim = C
+ forceMove(victim)
+ RefreshAbilities()
+ log_game("[src]/([src.ckey]) has infested [victim]/([victim.ckey]")
+
+/mob/living/simple_animal/horror/proc/secrete_chemicals()
+ if(!victim)
+ to_chat(src, "You are not inside a host body.")
+ return
+
+ if(!can_use_ability())
+ return
+
+ var content = ""
+ content += "Chemicals: [chemicals]
"
+
+ content += ""
+
+ for(var/datum in horror_chems)
+ var/datum/horror_chem/C = new datum()
+ if(C.chemname)
+ content += "| [C.chemname] ([C.chemuse]) [C.chem_desc] |
"
+
+ content += "
"
+
+ var/html = get_html_template(content)
+
+ usr << browse(null, "window=ViewHorror\ref[src]Chems;size=600x800")
+ usr << browse(html, "window=ViewHorror\ref[src]Chems;size=600x800")
+
+/mob/living/simple_animal/horror/proc/hide()
+ if(victim)
+ to_chat(src, "You cannot do this while you're inside a host.")
+ return
+
+ if(stat != CONSCIOUS)
+ return
+
+ if(!hiding)
+ layer = LATTICE_LAYER
+ visible_message("[src] scurries to the ground!", \
+ "You are now hiding.")
+ hiding = TRUE
+ else
+ layer = MOB_LAYER
+ visible_message("[src] slowly peaks up from the ground...", \
+ "You stop hiding.")
+ hiding = FALSE
+
+/mob/living/simple_animal/horror/proc/go_invisible()
+ if(victim)
+ to_chat(src, "You cannot do this while you're inside a host.")
+ return
+
+ if(!can_use_ability())
+ return
+
+ if(!has_chemicals(10))
+ to_chat(src, "You don't have enough chemicals to do that.")
+ return
+
+ if(!invisible)
+ to_chat(src, "You focus your chameleon skin to blend into the environment.")
+ invisible = TRUE
+ else
+ to_chat(src, "You stop your camouflage.")
+ invisible = FALSE
+
+/mob/living/simple_animal/horror/proc/freeze_victim()
+ if(world.time - used_freeze < 150)
+ to_chat(src, "You cannot use that ability again so soon.")
+ return
+
+ if(victim)
+ to_chat(src, "You cannot do that from within a host body.")
+ return
+
+ if(!can_use_ability())
+ return
+
+ var/list/choices = list()
+ for(var/mob/living/carbon/C in view(1,src))
+ if(C.stat == CONSCIOUS)
+ choices += C
+
+ if(!choices.len)
+ return
+ var/mob/living/carbon/M = choices.len > 1 ? input(src,"Who do you wish to stun?") in null|choices : choices[1]
+
+
+ if(!M || !src || stat != CONSCIOUS || victim || (world.time - used_freeze < 150))
+ return
+ if(!Adjacent(M))
+ return
+
+ layer = MOB_LAYER
+ if(horrorupgrades["paralysis"])
+ to_chat(src, "You whip your electrocharged tentacle at [M]'s leg and knock them down!")
+ playsound(loc, "sound/effects/sparks4.ogg", 30, 1, -1)
+ M.SetSleeping(70)
+ M.electrocute_act(15, src, 1, FALSE, FALSE, FALSE, 1, FALSE)
+ else
+ to_chat(src, "You whip your tentacle at [M]'s leg and knock them down!")
+ to_chat(M, "You feel something wrapping around your leg, pulling you down!")
+ playsound(loc, "sound/weapons/whipgrab.ogg", 30, 1, -1)
+ M.Stun(50)
+ M.Knockdown(70)
+ used_freeze = world.time
+
+/mob/living/simple_animal/horror/proc/release_victim()
+ if(!victim)
+ to_chat(src, "You are not inside a host body.")
+ return
+
+ if(!can_use_ability())
+ return
+
+ if(leaving)
+ leaving = FALSE
+ to_chat(src, "You decide against leaving your host.")
+ return
+
+ to_chat(src, "You begin disconnecting from [victim]'s synapses and prodding at their internal ear canal.")
+
+ if(victim.stat != DEAD && !horrorupgrades["invisible_exit"])
+ to_chat(victim, "An odd, uncomfortable pressure begins to build inside your skull, behind your ear...")
+
+ leaving = TRUE
+
+ addtimer(CALLBACK(src, .proc/release_host), 100)
+
+/mob/living/simple_animal/horror/proc/release_host()
+ if(!victim || !src || QDELETED(victim) || QDELETED(src))
+ return
+ if(!leaving)
+ return
+ if(controlling)
+ return
+
+ if(stat != CONSCIOUS)
+ to_chat(src, "You cannot release your host in your current state.")
+ return
+
+ if(horrorupgrades["invisible_exit"])
+ alpha = 60
+ if(has_ability("chameleon"))
+ invisible = TRUE
+ Update_Invisibility_Button()
+ to_chat(src, "You silently wiggle out of [victim]'s ear and plop to the ground before vanishing via reflective solution that covers you.")
+ else
+ to_chat(src, "You wiggle out of [victim]'s ear and plop to the ground.")
+ if(victim.mind)
+ if(!horrorupgrades["invisible_exit"])
+ to_chat(victim, "Something slimy wiggles out of your ear and plops to the ground!")
+
+ leaving = FALSE
+
+ leave_victim()
+
+/mob/living/simple_animal/horror/proc/leave_victim()
+ if(!victim)
+ return
+
+ if(controlling)
+ detatch()
+
+ forceMove(get_turf(victim))
+
+ reset_perspective(null)
+ machine = null
+
+ victim.reset_perspective(null)
+ victim.machine = null
+
+ var/mob/living/V = victim
+ remove_verb(V, /mob/living/proc/horror_comm)
+ talk_to_horror_action.Remove(victim)
+
+ for(var/obj/item/horrortentacle/T in victim)
+ victim.visible_message("[victim]'s tentacle transforms back!", "Your tentacle disappears!")
+ playsound(victim, 'sound/effects/blobattack.ogg', 30, 1)
+ qdel(T)
+ victim = null
+
+ RefreshAbilities()
+ return
+
+/mob/living/simple_animal/horror/proc/jumpstart()
+ if(!victim)
+ to_chat(src, "You need a host to be able to use this.")
+ return
+
+ if(!can_use_ability())
+ return
+
+ if(victim.stat != DEAD)
+ to_chat(src, "Your host is already alive!")
+ return
+
+ if(!has_chemicals(250))
+ to_chat(src, "You need 250 chemicals to use this!")
+ return
+
+ if(victim.stat == DEAD)
+ victim.tod = null
+ victim.setToxLoss(0)
+ victim.setOxyLoss(0)
+ victim.setCloneLoss(0)
+ victim.SetUnconscious(0)
+ victim.SetStun(0)
+ victim.SetKnockdown(0)
+ victim.radiation = 0
+ victim.heal_overall_damage(victim.getBruteLoss(), victim.getFireLoss())
+ victim.reagents.clear_reagents()
+ if(ishuman(victim))
+ var/mob/living/carbon/human/H = victim
+ H.restore_blood()
+ H.remove_all_embedded_objects()
+ victim.revive()
+ log_game("[src]/([src.ckey]) has revived [victim]/([victim.ckey]")
+ chemicals -= 250
+ to_chat(src, "You send a jolt of energy to your host, reviving them!")
+ victim.grab_ghost(force = TRUE) //brings the host back, no eggscape
+ to_chat(victim, "You bolt upright, gasping for breath!")
+
+/mob/living/simple_animal/horror/proc/view_memory()
+ if(!victim)
+ to_chat(src, "You need a host to be able to use this.")
+ return
+
+ if(!can_use_ability())
+ return
+
+ if(victim.stat == DEAD)
+ to_chat(src, "Your host brain is unresponsive. They are dead!")
+ return
+
+ if(prob(20))
+ to_chat(victim, "You suddenly feel your memory being tangled with...")//chance to alert the victim
+
+ if(victim.mind)
+ var/datum/mind/suckedbrain = victim.mind
+ to_chat(src, "You skim through [victim]'s memories...[suckedbrain.memory]")
+ for(var/A in suckedbrain.antag_datums)
+ var/datum/antagonist/antag_types = A
+ var/list/all_objectives = antag_types.objectives.Copy()
+ if(antag_types.antag_memory)
+ to_chat(src, "[antag_types.antag_memory]")
+ if(LAZYLEN(all_objectives))
+ to_chat(src, "Objectives:")
+ var/obj_count = 1
+ for(var/O in all_objectives)
+ var/datum/objective/objective = O
+ to_chat(src, "Objective #[obj_count++]: [objective.explanation_text]")
+ var/list/datum/mind/other_owners = objective.get_owners() - suckedbrain
+ if(other_owners.len)
+ for(var/mind in other_owners)
+ var/datum/mind/M = mind
+ to_chat(src, "Conspirator: [M.name]")
+
+ var/list/recent_speech = list()
+ var/list/say_log = list()
+ var/log_source = victim.logging
+ for(var/log_type in log_source)
+ var/nlog_type = text2num(log_type)
+ if(nlog_type & LOG_SAY)
+ var/list/reversed = log_source[log_type]
+ if(islist(reversed))
+ say_log = reverseRange(reversed.Copy())
+ break
+ if(LAZYLEN(say_log))
+ for(var/spoken_memory in say_log)
+ if(recent_speech.len >= 5)//up to 5 random lines of speech, favoring more recent speech
+ break
+ if(prob(50))
+ recent_speech[spoken_memory] = say_log[spoken_memory]
+ if(recent_speech.len)
+ to_chat(src, "You catch some drifting memories of their past conversations...")
+ for(var/spoken_memory in recent_speech)
+ to_chat(src, "[recent_speech[spoken_memory]]")
+ var/mob/living/carbon/human/H = victim
+ var/datum/dna/the_dna = H.has_dna()
+ if(the_dna)
+ to_chat(src, "You uncover that [H.p_their()] true identity is [the_dna.real_name].")
+
+/mob/living/simple_animal/horror/proc/bond_brain()
+ if(!victim)
+ to_chat(src, "You are not inside a host body.")
+ return
+
+ if(!can_use_ability())
+ return
+
+ if(victim.stat == DEAD)
+ to_chat(src, "This host lacks enough brain function to control.")
+ return
+
+ if(bonding)
+ bonding = FALSE
+ to_chat(src, "You stop attempting to take control of your host.")
+ return
+
+ to_chat(src, "You begin delicately adjusting your connection to the host brain...")
+
+ if(QDELETED(src) || QDELETED(victim))
+ return
+
+ bonding = TRUE
+
+ var/delay = 200
+ if(horrorupgrades["fast_control"])
+ delay -= 120
+ addtimer(CALLBACK(src, .proc/assume_control), delay)
+
+/mob/living/simple_animal/horror/proc/assume_control()
+ if(!victim || !src || controlling || victim.stat == DEAD)
+ return
+ if(!bonding)
+ return
+ if(docile)
+ to_chat(src, "You are feeling far too docile to do that.")
+ bonding = FALSE
+ return
+ if(is_servant_of_ratvar(victim) || iscultist(victim))
+ to_chat(src, "[victim]'s mind seems to be blocked by some unknown force!")
+ bonding = FALSE
+ return
+ if(HAS_TRAIT(victim, TRAIT_MINDSHIELD))
+ to_chat(src, "[victim]'s mind seems to be shielded from your influence!")
+ bonding = FALSE
+ return
+ else
+ RegisterSignal(victim, COMSIG_MOB_APPLY_DAMAGE, .proc/hit_detatch)
+ log_game("[src]/([src.ckey]) assumed control of [victim]/([victim.ckey] with eldritch powers.")
+ to_chat(src, "You plunge your probosci deep into the cortex of the host brain, interfacing directly with their nervous system.")
+ to_chat(victim, "You feel a strange shifting sensation behind your eyes as an alien consciousness displaces yours.")
+
+ qdel(host_brain)
+ host_brain = new(src)
+ host_brain.H = src
+ victim.mind.transfer_to(host_brain)
+
+ to_chat(host_brain, "You are trapped in your own mind. You feel that there must be a way to resist!")
+
+ mind.transfer_to(victim)
+
+ bonding = FALSE
+ controlling = TRUE
+
+ remove_verb(victim, /mob/living/proc/horror_comm)
+ talk_to_horror_action.Remove(victim)
+ GrantControlActions()
+
+ victim.med_hud_set_status()
+ if(target)
+ victim.apply_status_effect(/datum/status_effect/agent_pinpointer/horror)
+ for(var/datum/status_effect/agent_pinpointer/horror/status in victim.status_effects)
+ status.scan_target = target
+
+/mob/living/carbon/proc/release_control()
+ var/mob/living/simple_animal/horror/B = has_horror_inside()
+ if(B && B.host_brain)
+ to_chat(src, "You withdraw your probosci, releasing control of [B.host_brain]")
+ B.detatch()
+
+//Check for brain worms in head.
+/mob/proc/has_horror_inside()
+ for(var/I in contents)
+ if(ishorror(I))
+ return I
+ return FALSE
+
+/mob/living/simple_animal/horror/proc/hit_detatch()
+ if(victim.health <= 75)
+ detatch()
+ to_chat(src, "Upon taking damage, [victim]s brain detected danger, and hastily took over.")
+ to_chat(victim, "Your body is under attack, your brain immediately took over!")
+
+/mob/living/simple_animal/horror/proc/detatch()
+ if(!victim || !controlling)
+ return
+
+ controlling = FALSE
+ UnregisterSignal(victim, COMSIG_MOB_APPLY_DAMAGE)
+ add_verb(victim, /mob/living/proc/horror_comm)
+ RemoveControlActions()
+ RefreshAbilities()
+ talk_to_horror_action.Grant(victim)
+
+ victim.med_hud_set_status()
+ victim.remove_status_effect(/datum/status_effect/agent_pinpointer/horror)
+
+ victim.mind.transfer_to(src)
+ if(host_brain)
+ host_brain.mind.transfer_to(victim)
+
+ log_game("[src]/([src.ckey]) released control of [victim]/([victim.ckey]")
+ qdel(host_brain)
+
+/mob/living/simple_animal/horror/proc/Update_Invisibility_Button()
+ var/datum/action/innate/horror/action = has_ability("chameleon")
+ if(action)
+ action.button_icon_state = "horror_sneak_[invisible ? "true" : "false"]"
+ action.UpdateButtonIcon()
+
+/mob/living/simple_animal/horror/proc/GrantHorrorActions()
+ for(var/A in horrorabilities)
+ var/datum/action/innate/horror/ability = horrorabilities[A]
+ if("horror" in ability.category)
+ ability.Grant(src)
+
+/mob/living/simple_animal/horror/proc/RemoveHorrorActions()
+ for(var/A in horrorabilities)
+ var/datum/action/innate/horror/ability = horrorabilities[A]
+ if("horror" in ability.category)
+ ability.Remove(src)
+
+/mob/living/simple_animal/horror/proc/GrantInfestActions()
+ for(var/A in horrorabilities)
+ var/datum/action/innate/horror/ability = horrorabilities[A]
+ if("infest" in ability.category)
+ ability.Grant(src)
+
+/mob/living/simple_animal/horror/proc/RemoveInfestActions()
+ for(var/A in horrorabilities)
+ var/datum/action/innate/horror/ability = horrorabilities[A]
+ if("infest" in ability.category)
+ ability.Remove(src)
+
+/mob/living/simple_animal/horror/proc/GrantControlActions()
+ for(var/A in horrorabilities)
+ var/datum/action/innate/horror/ability = horrorabilities[A]
+ if("control" in ability.category)
+ ability.Grant(victim)
+
+/mob/living/simple_animal/horror/proc/RemoveControlActions()
+ for(var/A in horrorabilities)
+ var/datum/action/innate/horror/ability = horrorabilities[A]
+ if("control" in ability.category)
+ ability.Remove(victim)
+
+/mob/living/simple_animal/horror/proc/RefreshAbilities() //control abilities technically don't belong to horror
+ if(victim)
+ RemoveHorrorActions()
+ GrantInfestActions()
+ else
+ RemoveInfestActions()
+ GrantHorrorActions()
\ No newline at end of file
diff --git a/code/modules/antagonists/horror/horror_abilities_and_upgrades.dm b/code/modules/antagonists/horror/horror_abilities_and_upgrades.dm
new file mode 100644
index 000000000000..04bf0568e91f
--- /dev/null
+++ b/code/modules/antagonists/horror/horror_abilities_and_upgrades.dm
@@ -0,0 +1,443 @@
+//ABILITIES
+
+/datum/action/innate/horror
+ background_icon_state = "bg_ecult"
+ icon_icon = 'icons/mob/actions/actions_horror.dmi'
+ var/id //The ability's ID, for giving, taking and such
+ var/blacklisted = FALSE //If the ability can't be mutated
+ var/soul_price = 0 //How much souls the ability costs to buy; if this is 0, it isn't listed on the catalog
+ var/chemical_cost = 0 //How much chemicals the ability costs to use
+ var/mob/living/simple_animal/horror/B //Horror holding the ability
+ var/category //category for when the ability is active, "horror" is for creature, "infest" is during infestation, "controlling" is when a horror is controlling a body
+
+/datum/action/innate/horror/New(Target, horror)
+ B = horror
+ ..()
+
+/datum/action/innate/horror/IsAvailable()
+ if(!B)
+ return
+ if(!B.has_chemicals(chemical_cost))
+ return
+ . = ..()
+
+/datum/action/innate/horror/mutate
+ name = "Mutate"
+ id = "mutate"
+ desc = "Use consumed souls to mutate your abilities."
+ button_icon_state = "mutate"
+ blacklisted = TRUE
+ category = list("horror","infest")
+
+/datum/action/innate/horror/mutate/Activate()
+ to_chat(usr, "You focus on mutating your body...")
+ B.ui_interact(usr)
+ return TRUE
+
+/datum/action/innate/horror/seek_soul
+ name = "Seek target soul"
+ id = "seek_soul"
+ desc = "Search for a soul weak enough for you to consume."
+ button_icon_state = "seek_soul"
+ blacklisted = TRUE
+ category = list("horror","infest")
+
+/datum/action/innate/horror/seek_soul/Activate()
+ B.SearchTarget()
+
+/datum/action/innate/horror/consume_soul
+ name = "Consume soul"
+ id = "consume_soul"
+ desc = "Consume your target's soul."
+ button_icon_state = "consume_soul"
+ blacklisted = TRUE
+ category = list("infest")
+
+/datum/action/innate/horror/consume_soul/Activate()
+ B.ConsumeSoul()
+
+/datum/action/innate/horror/talk_to_host
+ name = "Converse with Host"
+ id = "talk_to_host"
+ desc = "Send a silent message to your host."
+ button_icon_state = "talk_to_host"
+ blacklisted = TRUE
+ category = list("infest")
+
+/datum/action/innate/horror/talk_to_host/Activate()
+ B.Communicate()
+
+/datum/action/innate/horror/infest_host
+ name = "Infest"
+ id = "infest"
+ desc = "Infest a suitable humanoid host."
+ button_icon_state = "infest"
+ blacklisted = TRUE
+ category = list("horror")
+
+/datum/action/innate/horror/infest_host/Activate()
+ B.infect_victim()
+
+/datum/action/innate/horror/toggle_hide
+ name = "Toggle Hide"
+ id = "toggle_hide"
+ desc = "Become invisible to the common eye. Toggled on or off."
+ button_icon_state = "horror_hiding_false"
+ blacklisted = TRUE
+ category = list("horror")
+
+/datum/action/innate/horror/toggle_hide/Activate()
+ B.hide()
+ button_icon_state = "horror_hiding_[B.hiding ? "true" : "false"]"
+ UpdateButtonIcon()
+
+/datum/action/innate/horror/talk_to_horror
+ name = "Converse with Horror"
+ id = "talk_to_horror"
+ desc = "Communicate mentally with your horror."
+ button_icon_state = "talk_to_horror"
+ blacklisted = TRUE
+
+/datum/action/innate/horror/talk_to_horror/Activate()
+ var/mob/living/O = owner
+ O.horror_comm()
+
+/datum/action/innate/horror/talk_to_brain
+ name = "Converse with Trapped Mind"
+ id = "talk_to_brain"
+ desc = "Communicate mentally with the trapped mind of your host."
+ button_icon_state = "talk_to_trapped_mind"
+ blacklisted = TRUE
+ category = list("control")
+
+/datum/action/innate/horror/talk_to_brain/Activate()
+ B.victim.trapped_mind_comm()
+
+/datum/action/innate/horror/take_control
+ name = "Assume Control"
+ id = "take_control"
+ desc = "Fully connect to the brain of your host."
+ button_icon_state = "horror_brain"
+ blacklisted = TRUE
+ category = list("infest")
+
+/datum/action/innate/horror/take_control/Activate()
+ B.bond_brain()
+
+/datum/action/innate/horror/give_back_control
+ name = "Release Control"
+ id = "release_control"
+ desc = "Release control of your host's body."
+ button_icon_state = "horror_leave"
+ blacklisted = TRUE
+ category = list("control")
+
+/datum/action/innate/horror/give_back_control/Activate()
+ B.victim.release_control()
+
+/datum/action/innate/horror/leave_body
+ name = "Release Host"
+ id = "leave_body"
+ desc = "Slither out of your host."
+ button_icon_state = "horror_leave"
+ blacklisted = TRUE
+ category = list("infest")
+
+/datum/action/innate/horror/leave_body/Activate()
+ B.release_victim()
+
+/datum/action/innate/horror/make_chems
+ name = "Secrete chemicals"
+ id = "make_chems"
+ desc = "Push some chemicals into your host's bloodstream."
+ icon_icon = 'icons/obj/chemical.dmi'
+ button_icon_state = "minidispenser"
+ blacklisted = TRUE
+ category = list("infest")
+
+/datum/action/innate/horror/make_chems/Activate()
+ B.secrete_chemicals()
+
+/datum/action/innate/horror/freeze_victim
+ name = "Knockdown victim"
+ id = "freeze_victim"
+ desc = "Use your tentacle to trip a victim, stunning for a short duration."
+ button_icon_state = "trip"
+ blacklisted = TRUE
+ category = list("horror")
+
+/datum/action/innate/horror/freeze_victim/Activate()
+ B.freeze_victim()
+ UpdateButtonIcon()
+ addtimer(CALLBACK(src, .proc/UpdateButtonIcon), 150)
+
+/datum/action/innate/horror/freeze_victim/IsAvailable()
+ if(world.time - B.used_freeze < 150)
+ return FALSE
+ else
+ return ..()
+
+//non-default abilities, can be mutated
+
+/datum/action/innate/horror/tentacle
+ name = "Grow Tentacle"
+ id = "tentacle"
+ desc = "Makes your host grow a tentacle in their arm. Costs 50 chemicals to activate."
+ button_icon_state = "tentacle"
+ chemical_cost = 50
+ category = list("infest", "control")
+ soul_price = 2
+
+/datum/action/innate/horror/tentacle/IsAvailable()
+ if(!active && !B.has_chemicals(chemical_cost))
+ return FALSE
+ return ..()
+
+/datum/action/innate/horror/tentacle/New()
+ ..()
+ START_PROCESSING(SSfastprocess, src)
+
+/datum/action/innate/horror/tentacle/Destroy()
+ STOP_PROCESSING(SSfastprocess, src)
+ return ..()
+
+/datum/action/innate/horror/tentacle/process()
+ ..()
+ active = locate(/obj/item/horrortentacle) in B.victim
+ UpdateButtonIcon()
+
+
+/datum/action/innate/horror/tentacle/Activate()
+ B.use_chemicals(50)
+ B.victim.visible_message("[B.victim]'s arm contorts into tentacles!", "Your arm transforms into a giant tentacle. Examine it to see possible uses.")
+ playsound(B.victim, 'sound/effects/blobattack.ogg', 30, 1)
+ to_chat(B, "You transform [B.victim]'s arm into a tentacle!")
+ var/obj/item/horrortentacle/T = new
+ B.victim.put_in_hands(T)
+ return TRUE
+
+/datum/action/innate/horror/tentacle/Deactivate()
+ B.victim.visible_message("[B.victim]'s tentacle transforms back!", "Your tentacle disappears!")
+ playsound(B.victim, 'sound/effects/blobattack.ogg', 30, 1)
+ to_chat(B, "You transform [B.victim]'s arm back.")
+ for(var/obj/item/horrortentacle/T in B.victim)
+ qdel(T)
+ return TRUE
+
+/datum/action/innate/horror/transfer_host
+ name = "Transfer to another Host"
+ id = "transfer_host"
+ desc = "Move into another host directly. Grabbing makes the process faster."
+ button_icon_state = "transfer_host"
+ category = list("infest", "control")
+ soul_price = 1
+
+/datum/action/innate/horror/transfer_host/Activate()
+ var/list/choices = list()
+ for(var/mob/living/carbon/C in range(1,B.victim))
+ if(C!=B.victim && C.Adjacent(B.victim))
+ choices += C
+
+ if(!choices.len)
+ return
+ var/mob/living/carbon/C = choices.len > 1 ? input(owner,"Who do you wish to infest?") in null|choices : choices[1]
+ if(!C || !B)
+ return
+ if(!C.Adjacent(B.victim))
+ return
+ var/obj/item/bodypart/head/head = C.get_bodypart(BODY_ZONE_HEAD)
+ if(!head)
+ to_chat(owner, "[C] doesn't have a head!")
+ return
+ var/hasbrain = FALSE
+ for(var/obj/item/organ/brain/X in C.internal_organs)
+ hasbrain = TRUE
+ break
+ if(!hasbrain)
+ to_chat(owner, "[C] doesn't have a brain! ")
+ return
+ if((!C.key || !C.mind) && C != B.target)
+ to_chat(owner, "[C]'s mind seems unresponsive. Try someone else!")
+ return
+ if(C.has_horror_inside())
+ to_chat(owner, "[C] is already infested!")
+ return
+
+ to_chat(owner, "You move your tentacles away from [B.victim] and begin to transfer to [C]...")
+ var/delay = 20 SECONDS
+ var/silent
+ if(B.victim.pulling != C)
+ silent = TRUE
+ else
+ switch(B.victim.grab_state)
+ if(GRAB_PASSIVE)
+ delay = 10 SECONDS
+ if(GRAB_AGGRESSIVE)
+ delay = 5 SECONDS
+ if(GRAB_NECK)
+ delay = 3 SECONDS
+ else
+ delay = 1 SECONDS
+
+ if(!do_mob(B, C, delay))
+ to_chat(owner, "As [C] moves away, your transfer gets interrupted!")
+ return
+
+ if(!C || !B)
+ return
+ B.leave_victim()
+ B.Infect(C)
+ if(!silent)
+ to_chat(C, "Something slimy wiggles into your ear!")
+ playsound(B, 'sound/effects/blobattack.ogg', 30, 1)
+
+/datum/action/innate/horror/jumpstart_host
+ name = "Revive Host"
+ id = "jumpstart_host"
+ desc = "Bring your host back to life."
+ button_icon_state = "revive"
+ category = list("infest")
+ soul_price = 2
+
+/datum/action/innate/horror/jumpstart_host/Activate()
+ B.jumpstart()
+
+/datum/action/innate/horror/view_memory
+ name = "View Memory"
+ id = "view_memory"
+ desc = "Read recent memory of the host you're inside of."
+ button_icon_state = "view_memory"
+ category = list("infest")
+ soul_price = 1
+
+/datum/action/innate/horror/view_memory/Activate()
+ B.view_memory()
+
+/datum/action/innate/horror/chameleon
+ name = "Chameleon Skin"
+ id = "chameleon"
+ desc = "Adjust your skin color to blend into environment. Costs 5 chemicals per tick, also stopping chemical regeneration while active. Attacking stops the invisibility completely."
+ button_icon_state = "horror_sneak_false"
+ category = list("horror")
+ soul_price = 1
+
+/datum/action/innate/horror/chameleon/Activate()
+ B.go_invisible()
+ button_icon_state = "horror_sneak_[B.invisible ? "true" : "false"]"
+ UpdateButtonIcon()
+
+//UPGRADES
+/datum/horror_upgrade
+ var/name = "horror upgrade"
+ var/desc = "This is an upgrade."
+ var/id
+ var/soul_price = 0 //How much souls an upgrade costs to buy
+ var/mob/living/simple_animal/horror/B //Horror holding the upgrades
+
+/datum/horror_upgrade/proc/unlock()
+ if(!B)
+ return
+ apply_effects()
+ qdel(src)
+ return TRUE
+
+/datum/horror_upgrade/New(owner)
+ ..()
+ B = owner
+
+/datum/horror_upgrade/proc/apply_effects()
+ return
+
+//Upgrades the stun ability
+/datum/horror_upgrade/paralysis
+ name = "Electrocharged tentacle"
+ id = "paralysis"
+ desc = "Empowers your tentacle knockdown ability by giving it extra charge, knocking your victim down unconcious."
+ soul_price = 3
+
+/datum/horror_upgrade/paralysis/apply_effects()
+ var/datum/action/innate/horror/A = B.has_ability("freeze_victim")
+ if(A)
+ A.name = "Paralyze Victim"
+ A.desc = "Shock a victim with an electrically charged tentacle."
+ A.button_icon_state = "paralyze"
+ B.update_action_buttons()
+
+//Increases chemical regeneration rate by 2
+/datum/horror_upgrade/chemical_regen
+ name = "Efficient chemical glands"
+ id = "chem_regen"
+ desc = "Your chemical glands work more efficiently. Unlocking this increases your chemical regeneration."
+ soul_price = 2
+
+/datum/horror_upgrade/chemical_regen/apply_effects()
+ B.chem_regen_rate += 2
+
+//Lets horror regenerate chemicals outside of a host
+/datum/horror_upgrade/nohost_regen
+ name = "Independent chemical glands"
+ id = "nohost_regen"
+ desc = "Your chemical glands become less parasitic and let you regenerate chemicals on their own without need for a host."
+ soul_price = 2
+
+//Lets horror regenerate health
+/datum/horror_upgrade/regen
+ name = "Regenerative skin"
+ id = "regen"
+ desc = "Your skin adapts to sustained damage and slowly regenerates itself, healing your wounds over time."
+ soul_price = 1
+
+//Triples horror's health pool
+/datum/horror_upgrade/hp_up
+ name = "Rhino skin" //Horror can....roll?
+ id = "hp_up"
+ desc = "Your skin becomes hard as rock, greatly increasing your maximum health - and odds of survival outside of host."
+ soul_price = 2
+
+/datum/horror_upgrade/hp_up/apply_effects()
+ B.health = round(min(B.maxHealth,B.health * 3))
+ B.maxHealth = round(B.maxHealth * 3)
+
+//Makes horror almost invisible for a short time after leaving a host
+/datum/horror_upgrade/invisibility
+ name = "Reflective fluids"
+ id = "invisible_exit"
+ desc = "You build up reflective solution inside host's brain. Upon exiting a host, you're briefly covered in it, rendering you near invisible for a few seconds. This mutation also makes the host unable to notice you exiting it directly."
+ soul_price = 2
+
+//Increases melee damage to 20
+/datum/horror_upgrade/dmg_up
+ name = "Sharpened teeth"
+ id = "dmg_up"
+ desc = "Your teeth become sharp blades, this mutation increases your melee damage."
+ soul_price = 2
+
+/datum/horror_upgrade/dmg_up/apply_effects()
+ B.attacktext = "crushes"
+ B.attack_sound = 'sound/weapons/pierce_slow.ogg' //chunky
+ B.melee_damage_lower += 10
+ B.melee_damage_upper += 10
+
+//Expands the reagent selection horror can make
+/datum/horror_upgrade/upgraded_chems
+ name = "Advanced reagent synthesis"
+ id = "upgraded_chems"
+ desc = "Lets you synthetize adrenaline, salicyclic acid, oxandrolone, pentetic acid and rezadone into your host."
+ soul_price = 2
+
+/datum/horror_upgrade/upgraded_chems/apply_effects()
+ B.horror_chems += list(/datum/horror_chem/adrenaline,/datum/horror_chem/sal_acid,/datum/horror_chem/oxandrolone,/datum/horror_chem/pen_acid,/datum/horror_chem/rezadone)
+
+//faster mind control
+/datum/horror_upgrade/fast_control
+ name = "Precise probosci"
+ id = "fast_control"
+ desc = "Your probosci become more precise, allowing you to take control over your host's brain noticably faster."
+ soul_price = 2
+
+//makes it longer for host to snap out of mind control
+/datum/horror_upgrade/deep_control
+ name = "Insulated probosci"
+ id = "deep_control"
+ desc = "Your probosci become insulated, protecting them from neural shocks. This makes it harder for the host to regain control over their body."
+ soul_price = 2
\ No newline at end of file
diff --git a/code/modules/antagonists/horror/horror_chemicals.dm b/code/modules/antagonists/horror/horror_chemicals.dm
new file mode 100644
index 000000000000..71b42fca9676
--- /dev/null
+++ b/code/modules/antagonists/horror/horror_chemicals.dm
@@ -0,0 +1,96 @@
+/mob/living/simple_animal/horror/Topic(href, href_list, hsrc)
+ if(href_list["horror_use_chem"])
+ locate(href_list["src"])
+ if(!istype(src, /mob/living/simple_animal/horror))
+ return
+
+ var/topic_chem = href_list["horror_use_chem"]
+ var/datum/horror_chem/C
+
+ for(var/datum in typesof(/datum/horror_chem))
+ var/datum/horror_chem/test = new datum()
+ if(test.chemname == topic_chem)
+ C = test
+ break
+
+ if(!istype(C, /datum/horror_chem))
+ return
+
+ if(!C || !victim || controlling || !src || stat)
+ return
+
+ if(!istype(C, /datum/horror_chem))
+ return
+
+ if(chemicals < C.chemuse)
+ to_chat(src, "You need [C.chemuse] chemicals stored to use this chemical!")
+ return
+
+ to_chat(src, "You squirt a measure of [C.chemname] from your reservoirs into [victim]'s bloodstream.")
+ victim.reagents.add_reagent(C.R, C.quantity)
+ chemicals -= C.chemuse
+ log_game("[src]/([src.ckey]) has injected [C.chemname] into their host [victim]/([victim.ckey])")
+
+ src << output(chemicals, "ViewHorror\ref[src]Chems.browser:update_chemicals")
+
+ ..()
+
+/datum/horror_chem
+ var/chemname
+ var/chem_desc = "This is a chemical"
+ var/datum/reagent/R
+ var/chemuse = 30
+ var/quantity = 10
+
+/datum/horror_chem/epinephrine
+ chemname = "epinephrine"
+ R = /datum/reagent/medicine/epinephrine
+ chem_desc = "Stabilizes critical condition and slowly restores oxygen damage."
+
+/datum/horror_chem/mannitol
+ chemname = "mannitol"
+ R = /datum/reagent/medicine/mannitol
+ chem_desc = "Heals brain damage."
+
+/datum/horror_chem/bicaridine
+ chemname = "bicaridine"
+ R = /datum/reagent/medicine/bicaridine
+ chem_desc = "Heals brute damage."
+
+/datum/horror_chem/kelotane
+ chemname = "kelotane"
+ R = /datum/reagent/medicine/kelotane
+ chem_desc = "Heals burn damage."
+
+/datum/horror_chem/charcoal
+ chemname = "charcoal"
+ R = /datum/reagent/medicine/charcoal
+ chem_desc = "Slowly heals toxin damage, while also slowly removing any other chemicals."
+
+/datum/horror_chem/adrenaline
+ chemname = "adrenaline"
+ R = /datum/reagent/medicine/changelingadrenaline
+ chemuse = 100
+ chem_desc = "Stimulates the brain, shrugging off effect of stuns while regenerating stamina."
+
+/datum/horror_chem/rezadone
+ chemname = "rezadone"
+ R = /datum/reagent/medicine/rezadone
+ chemuse = 50
+ chem_desc = "Heals cellular damage."
+
+/datum/horror_chem/pen_acid
+ chemname = "pentetic acid"
+ R = /datum/reagent/medicine/pen_acid
+ chemuse = 50
+ chem_desc = "Reduces massive amounts of radiation and toxin damage while purging other chemicals from the body."
+
+/datum/horror_chem/sal_acid
+ chemname = "salicyclic acid"
+ R = /datum/reagent/medicine/sal_acid
+ chem_desc = "Stimulates the healing of severe bruises. Rapidly heals severe bruising and slowly heals minor ones."
+
+/datum/horror_chem/oxandrolone
+ chemname = "oxandrolone"
+ R = /datum/reagent/medicine/oxandrolone
+ chem_desc = "Stimulates the healing of severe burns. Rapidly heals severe burns and slowly heals minor ones."
\ No newline at end of file
diff --git a/code/modules/antagonists/horror/horror_datums.dm b/code/modules/antagonists/horror/horror_datums.dm
new file mode 100644
index 000000000000..7d97c1ecd124
--- /dev/null
+++ b/code/modules/antagonists/horror/horror_datums.dm
@@ -0,0 +1,334 @@
+//ANTAG DATUMS
+/datum/antagonist/horror
+ name = "Horror"
+ show_in_antagpanel = TRUE
+ prevent_roundtype_conversion = FALSE
+ show_name_in_check_antagonists = TRUE
+ show_to_ghosts = TRUE
+ var/datum/mind/summoner
+
+/datum/antagonist/horror/on_gain()
+ . = ..()
+ give_objectives()
+ if(ishorror(owner) && owner.current.mind)
+ var/mob/living/simple_animal/horror/H = owner.current
+ H.update_horror_hud()
+ H.mind.store_memory("You become docile after contact with [H.weakness.name]. Avoid it.")
+
+/datum/antagonist/horror/proc/give_objectives()
+ if(summoner)
+ var/datum/objective/newobjective = new
+ newobjective.explanation_text = "Serve your summoner, [summoner.name]."
+ newobjective.owner = owner
+ newobjective.completed = TRUE
+ objectives += newobjective
+ else
+ var/datum/objective/horrorascend/ascend = new
+ ascend.owner = owner
+ ascend.target_amount = rand(6, 10)
+ objectives += ascend
+ ascend.update_explanation_text()
+ var/datum/objective/survive/survive = new
+ survive.owner = owner
+ objectives += survive
+
+/datum/objective/horrorascend
+ name = "consume souls"
+
+/datum/objective/horrorascend/update_explanation_text()
+ . = ..()
+ explanation_text = "Consume [target_amount] souls."
+
+/datum/objective/horrorascend/check_completion()
+ var/mob/living/simple_animal/horror/H = owner.current
+ if(istype(H) && H.consumed_souls > target_amount)
+ return TRUE
+ return FALSE
+
+//SPAWNER
+/obj/item/horrorspawner
+ name = "suspicious pet carrier"
+ desc = "It contains some sort of creature inside. You can see tentacles sticking out of it."
+ icon = 'icons/obj/pet_carrier.dmi'
+ lefthand_file = 'icons/mob/inhands/items_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/items_righthand.dmi'
+ item_state = "pet_carrier"
+ icon_state = "pet_carrier_occupied"
+ var/used = FALSE
+ var/weakness
+ color = rgb(130, 105, 160)
+
+/obj/item/horrorspawner/attack_self(mob/living/user)
+ if(used)
+ to_chat(user, "The pet carrier appears unresponsive.")
+ return
+ used = TRUE
+ to_chat(user, "You're attempting to wake up the creature inside the box...")
+ sleep(5 SECONDS)
+ var/list/mob/dead/observer/candidates = pollGhostCandidates("Do you want to play as the eldritch horror in service of [user.real_name]?", ROLE_HORROR, null, FALSE, 100)
+ if(LAZYLEN(candidates))
+ var/mob/dead/observer/C = pick(candidates)
+ var/mob/living/simple_animal/horror/H = new /mob/living/simple_animal/horror(get_turf(src))
+ H.weakness = weakness
+ H.key = C.key
+ H.mind.enslave_mind_to_creator(user)
+ H.mind.add_antag_datum(C)
+ H.mind.memory += "You are [H.truename], an eldritch horror. Consume souls to evolve.
"
+ var/datum/antagonist/horror/S = new
+ S.summoner = user.mind
+ S.antag_memory += "[user.mind] woke you from your eternal slumber. Aid them in their objectives as a token of gratitude.
"
+ H.mind.add_antag_datum(S)
+ log_game("[key_name(user)] has summoned [key_name(H)], an eldritch horror.")
+ to_chat(user, "[H.truename] has awoken into your service!")
+ used = TRUE
+ icon_state = "pet_carrier_open"
+ sleep(5)
+ var/obj/item/horrorsummonhorn/horn = new /obj/item/horrorsummonhorn(get_turf(src))
+ horn.summoner = user.mind
+ horn.horror = H
+ to_chat(user, "A strange looking [horn] falls out of [src]!")
+ else
+ to_chat(user, "The creatures looks at you with one of it's eyes before going back to slumber.")
+ used = FALSE
+ return
+
+//Summoning horn
+/obj/item/horrorsummonhorn
+ name = "old horn"
+ desc = "A very old horn. You feel an incredible urge to blow into it."
+ icon = 'icons/obj/items_and_weapons.dmi'
+ lefthand_file = 'icons/mob/inhands/items_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/items_righthand.dmi'
+ item_state = "horn"
+ icon_state = "horn"
+ var/datum/mind/summoner
+ var/mob/living/simple_animal/horror/horror
+ var/cooldown
+
+/obj/item/horrorsummonhorn/examine(mob/user)
+ . = ..()
+ if(user.mind == summoner)
+ to_chat(user, "Blowing into this horn will recall the horror back to you. Be wary, the horn is loud, and may attract unwanted attention.")
+
+/obj/item/horrorsummonhorn/attack_self(mob/living/user)
+ if(cooldown > world.time)
+ to_chat(user, "Take a breath before you blow [src] again.")
+ return
+ to_chat(user, "You take a deep breath and prepare to blow into [src]...")
+ if(do_mob(user, src, 10 SECONDS))
+ if(cooldown > world.time)
+ return
+ cooldown = world.time + 10 SECONDS
+ to_chat(src, "You blow the horn...")
+ playsound(loc, "sound/items/airhorn.ogg", 100, 1, 30)
+ var/turf/summonplace = get_turf(src)
+ sleep(5 SECONDS)
+ if(prob(20)) //yeah you're summoning an eldritch horror allright
+ new /obj/effect/temp_visual/summon(summonplace)
+ sleep(10)
+ var/type = pick(typesof(/mob/living/simple_animal/hostile/abomination))
+ var/mob/R = new type(summonplace)
+ playsound(summonplace, "sound/effects/phasein.ogg", 30)
+ summonplace.visible_message("[R] emerges!")
+ else
+ if(!horror || horror.stat == DEAD)
+ summonplace.visible_message("But nothing responds to the call!")
+ else
+ new /obj/effect/temp_visual/summon(summonplace)
+ sleep(10)
+ horror.leave_victim()
+ horror.forceMove(summonplace)
+ playsound(summonplace, "sound/effects/phasein.ogg", 30)
+ summonplace.visible_message("[horror] appears out of nowhere!")
+ if(user.mind != summoner)
+ sleep(2 SECONDS)
+ playsound(summonplace, "sound/effects/glassbr2.ogg", 30, 1)
+ to_chat(user, "[src] breaks!")
+ qdel(src)
+
+/obj/item/horrorsummonhorn/suicide_act(mob/living/user) //"I am the prettiest unicorn that ever was!" ~Spy 2013
+ user.visible_message("[user] stabs [user.p_their()] forehead with [src]! It looks like [user.p_theyre()] trying to commit suicide!")
+ return BRUTELOSS
+
+/obj/item/paper/crumpled/horrorweakness
+ name = "scruffed note"
+ infolang = /datum/language/aphasia //his previous owner got brought to madness, luckily curator can translate
+
+//Tentacle arm
+/obj/item/horrortentacle
+ name = "tentacle"
+ desc = "A long, slimy, arm-like appendage."
+ icon = 'icons/obj/items_and_weapons.dmi'
+ icon_state = "horrortentacle"
+ item_state = "tentacle"
+ lefthand_file = 'icons/mob/inhands/antag/horror_lefthand.dmi'
+ righthand_file = 'icons/mob/inhands/antag/horror_righthand.dmi'
+ resistance_flags = ACID_PROOF
+ force = 17
+ item_flags = ABSTRACT | DROPDEL
+ reach = 2
+ hitsound = 'sound/weapons/whip.ogg'
+
+/obj/item/horrortentacle/Initialize(mapload)
+ . = ..()
+ ADD_TRAIT(src, TRAIT_NODROP, ABSTRACT_ITEM_TRAIT)
+
+/obj/item/horrortentacle/examine(mob/user)
+ . = ..()
+ to_chat(user, "Functions:")
+ to_chat(user, "All attacks work up to 2 tiles away.")
+ to_chat(user, "Help intent: Usual help function of an arm.")
+ to_chat(user, "Disarm intent: Whips the tentacle, disarming your opponent.")
+ to_chat(user, "Grab intent: Instant aggressive grab on an opponent. Can also throw them!")
+ to_chat(user, "Harm intent: Whips the tentacle, damaging your opponent.")
+ to_chat(user, "Also functions to pry open unbolted airlocks.")
+
+/obj/item/horrortentacle/attack(atom/target, mob/living/user)
+ if(isliving(target))
+ user.Beam(target,"purpletentacle",time=5)
+ var/mob/living/L = target
+ switch(user.a_intent)
+ if(INTENT_HELP)
+ L.attack_hand(user)
+ return
+ if(INTENT_GRAB)
+ if(L != user)
+ L.grabbedby(user)
+ L.grippedby(user, instant = TRUE)
+ L.Knockdown(30)
+ return
+ if(INTENT_DISARM)
+ if(iscarbon(L))
+ var/mob/living/carbon/C = L
+ var/obj/item/I = C.get_active_held_item()
+ if(I)
+ if(C.dropItemToGround(I))
+ playsound(loc, "sound/weapons/whipgrab.ogg", 30)
+ target.visible_message("[I] is whipped out of [C]'s hand by [user]!","A tentacle whips [I] out of your hand!")
+ return
+ else
+ to_chat(user, "You can't seem to pry [I] off [C]'s hands!")
+ return
+ else
+ C.attack_hand(user)
+ return
+ . = ..()
+
+/obj/item/horrortentacle/afterattack(atom/target, mob/user, proximity)
+ if(isliving(user.pulling) && user.pulling != target)
+ var/mob/living/H = user.pulling
+ user.visible_message("[user] throws [H] with [user.p_their()] [src]!", "You throw [H] with [src].")
+ H.throw_at(target, 8, 2)
+ H.Knockdown(30)
+ return
+ if(!proximity)
+ return
+ if(istype(target, /obj/machinery/door/airlock))
+ var/obj/machinery/door/airlock/A = target
+
+ if((!A.requiresID() || A.allowed(user)) && A.hasPower())
+ return
+ if(A.locked)
+ to_chat(user, "The airlock's bolts prevent it from being forced!")
+ return
+
+ if(A.hasPower())
+ user.visible_message("[user] jams [src] into the airlock and starts prying it open!", "You start forcing the airlock open.",
+ "You hear a metal screeching sound.")
+ playsound(A, 'sound/machines/airlock_alien_prying.ogg', 150, 1)
+ if(!do_after(user, 10 SECONDS, target = A))
+ return
+ user.visible_message("[user] forces the airlock to open with [user.p_their()] [src]!", "You force the airlock to open.",
+ "You hear a metal screeching sound.")
+ A.open(2)
+ return
+ . = ..()
+
+/obj/item/horrortentacle/suicide_act(mob/user) //funnily enough, this will never be called, since horror stops suicide
+ user.visible_message("[src] coils itself around [user] tightly gripping [user.p_their()] neck! It looks like [user.p_theyre()] trying to commit suicide!")
+ return (OXYLOSS)
+
+//Pinpointer
+/obj/screen/alert/status_effect/agent_pinpointer/horror
+ name = "Soul locator"
+ desc = "Find your target soul."
+
+/datum/status_effect/agent_pinpointer/horror
+ id = "horror_pinpointer"
+ minimum_range = 0
+ range_fuzz_factor = 0
+ tick_interval = 20
+ alert_type = /obj/screen/alert/status_effect/agent_pinpointer/horror
+
+/datum/status_effect/agent_pinpointer/horror/scan_for_target()
+ return
+
+//TRAPPED MIND - when horror takes control over your body, you become a mute trapped mind
+/mob/living/captive_brain
+ name = "host brain"
+ real_name = "host brain"
+ var/datum/action/innate/resist_control/R
+ var/mob/living/simple_animal/horror/H
+
+/mob/living/captive_brain/Initialize(mapload, gen=1)
+ ..()
+ R = new
+ R.Grant(src)
+
+/mob/living/captive_brain/say(message, bubble_type, var/list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null)
+ if(client)
+ if(client.prefs.muted & MUTE_IC)
+ to_chat(src, "You cannot speak in IC (muted).")
+ return
+ if(client.handle_spam_prevention(message,MUTE_IC))
+ return
+
+ if(ishorror(loc))
+ message = sanitize(message)
+ if(!message)
+ return
+ log_say("[key_name(src)] : [message]")
+ if(stat == 2)
+ return say_dead(message)
+
+ to_chat(src, "You whisper silently, \"[message]\"")
+ to_chat(H.victim, "The captive mind of [src] whispers, \"[message]\"")
+
+ for (var/mob/M in GLOB.player_list)
+ if(isnewplayer(M))
+ continue
+ else if(M.stat == 2 && M.client.prefs.toggles & CHAT_GHOSTEARS)
+ to_chat(M, "Thought-speech, [src] -> [H.truename]: [message]")
+
+/mob/living/captive_brain/emote(act, m_type = null, message = null, intentional = FALSE)
+ return
+
+/datum/action/innate/resist_control
+ name = "Resist control"
+ desc = "Try to take back control over your brain. A strong nerve impulse should do it."
+ background_icon_state = "bg_ecult"
+ icon_icon = 'icons/mob/actions/actions_horror.dmi'
+ button_icon_state = "resist_control"
+
+/datum/action/innate/resist_control/Activate()
+ var/mob/living/captive_brain/B = owner
+ if(B)
+ B.try_resist()
+
+/mob/living/captive_brain/resist()
+ try_resist()
+
+/mob/living/captive_brain/proc/try_resist()
+ var/delay = rand(20 SECONDS,30 SECONDS)
+ if(H.horrorupgrades["deep_control"])
+ delay += rand(20 SECONDS,30 SECONDS)
+ to_chat(src, "You begin doggedly resisting the parasite's control.")
+ to_chat(H.victim, "You feel the captive mind of [src] begin to resist your control.")
+ addtimer(CALLBACK(src, .proc/return_control), delay)
+
+/mob/living/captive_brain/proc/return_control()
+ if(!H || !H.controlling)
+ return
+ to_chat(src, "With an immense exertion of will, you regain control of your body!")
+ to_chat(H.victim, "You feel control of the host brain ripped from your grasp, and retract your probosci before the wild neural impulses can damage you.")
+ H.detatch()
\ No newline at end of file
diff --git a/code/modules/antagonists/horror/horror_html.dm b/code/modules/antagonists/horror/horror_html.dm
new file mode 100644
index 000000000000..4f70c856805d
--- /dev/null
+++ b/code/modules/antagonists/horror/horror_html.dm
@@ -0,0 +1,102 @@
+/mob/living/simple_animal/horror/proc/get_html_template(content)
+ var/html = {"
+
+
+ Horror Chemicals
+
+
+
+
+
+
+
+
+
+ [content]
+
"}
+ return html
\ No newline at end of file
diff --git a/code/modules/antagonists/horror/horror_mutate.dm b/code/modules/antagonists/horror/horror_mutate.dm
new file mode 100644
index 000000000000..8c48fd808d99
--- /dev/null
+++ b/code/modules/antagonists/horror/horror_mutate.dm
@@ -0,0 +1,99 @@
+// Horror mutation menu
+// Totally not a copypaste of darkspawn menu, not a copypaste of cellular emporium, i swear.
+
+/mob/living/simple_animal/horror/proc/has_ability(id)
+ if(isnull(horrorabilities[id]))
+ return
+ return horrorabilities[id]
+
+/mob/living/simple_animal/horror/proc/add_ability(id)
+ if(has_ability(id))
+ return
+ for(var/V in subtypesof(/datum/action/innate/horror))
+ var/datum/action/innate/horror/H = V
+ if(initial(H.id) == id)
+ var/datum/action/innate/horror/action = new H(null, src)
+ horrorabilities[id] = action
+ action.B = src
+ RefreshAbilities()
+ to_chat(src, "You have mutated the [action.name].")
+ available_points = max(0, available_points - action.soul_price)
+ return TRUE
+
+/mob/living/simple_animal/horror/proc/has_upgrade(id)
+ return horrorupgrades[id]
+
+/mob/living/simple_animal/horror/proc/add_upgrade(id)
+ if(has_upgrade(id))
+ return
+ for(var/V in subtypesof(/datum/horror_upgrade))
+ var/datum/horror_upgrade/_U = V
+ if(initial(_U.id) == id)
+ var/datum/horror_upgrade/U = new _U(src)
+ horrorupgrades[id] = TRUE
+ to_chat(src, "You have adapted the \"[U.name]\" upgrade.")
+ available_points = max(0, available_points - U.soul_price)
+ U.unlock()
+
+//mutation menu, 100% ripoff of psiweb, pls don't sue
+
+/mob/living/simple_animal/horror/ui_state(mob/user)
+ return GLOB.always_state
+
+/mob/living/simple_animal/horror/ui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "HorrorMutate", "Horror Mutation")
+ ui.open()
+
+/mob/living/simple_animal/horror/ui_data(mob/user)
+ var/list/data = list()
+
+ data["available_points"] = "[available_points] | [consumed_souls] consumed souls total"
+
+ var/list/abilities = list()
+ var/list/upgrades = list()
+
+ for(var/path in subtypesof(/datum/action/innate/horror))
+ var/datum/action/innate/horror/ability = path
+
+ if(initial(ability.blacklisted))
+ continue
+
+ var/list/AL = list()
+ AL["name"] = initial(ability.name)
+ AL["id"] = initial(ability.id)
+ AL["desc"] = initial(ability.desc)
+ AL["soul_cost"] = initial(ability.soul_price)
+ AL["owned"] = has_ability(initial(ability.id))
+ AL["can_purchase"] = !AL["owned"] && available_points >= initial(ability.soul_price)
+
+ abilities += list(AL)
+
+ data["abilities"] = abilities
+
+ for(var/path in subtypesof(/datum/horror_upgrade))
+ var/datum/horror_upgrade/upgrade = path
+
+ var/list/DE = list()
+ DE["name"] = initial(upgrade.name)
+ DE["id"] = initial(upgrade.id)
+ DE["desc"] = initial(upgrade.desc)
+ DE["soul_cost"] = initial(upgrade.soul_price)
+ DE["owned"] = has_upgrade(initial(upgrade.id))
+ DE["can_purchase"] = !DE["owned"] && available_points >= initial(upgrade.soul_price)
+
+ upgrades += list(DE)
+
+ data["upgrades"] = upgrades
+
+ return data
+
+/mob/living/simple_animal/horror/ui_act(action, params)
+ if(..())
+ return
+ switch(action)
+ if("unlock")
+ add_ability(params["id"])
+ if("upgrade")
+ add_upgrade(params["id"])
\ No newline at end of file
diff --git a/code/modules/client/verbs/suicide.dm b/code/modules/client/verbs/suicide.dm
index 4e773fc274bb..f8cbe7e361b4 100644
--- a/code/modules/client/verbs/suicide.dm
+++ b/code/modules/client/verbs/suicide.dm
@@ -274,4 +274,7 @@
if(!(mobility_flags & MOBILITY_USE)) //just while I finish up the new 'fun' suiciding verb. This is to prevent metagaming via suicide
to_chat(src, "You can't commit suicide whilst immobile! ((You can type Ghost instead however.))")
return
+ if(has_horror_inside())
+ to_chat(src, "Something inside your head stops your action!")
+ return
return TRUE
diff --git a/code/modules/events/horror.dm b/code/modules/events/horror.dm
new file mode 100644
index 000000000000..e9a8efbe5f2d
--- /dev/null
+++ b/code/modules/events/horror.dm
@@ -0,0 +1,37 @@
+/datum/round_event_control/horror
+ name = "Spawn Eldritch Horror"
+ typepath = /datum/round_event/ghost_role/horror
+ max_occurrences = 2
+ min_players = 15
+ earliest_start = 20 MINUTES
+
+/datum/round_event/ghost_role/horror
+ minimum_required = 1
+ role_name = "horror"
+ fakeable = FALSE
+
+/datum/round_event/ghost_role/horror/spawn_role()
+ var/list/candidates = get_candidates(ROLE_HORROR, null, ROLE_HORROR)
+ if(!candidates.len)
+ return NOT_ENOUGH_PLAYERS
+
+ var/mob/dead/selected = pick_n_take(candidates)
+
+ var/datum/mind/player_mind = new /datum/mind(selected.key)
+ player_mind.active = 1
+ if(!GLOB.generic_event_spawns)
+ return MAP_ERROR
+ var/mob/living/simple_animal/horror/S = new /mob/living/simple_animal/horror(get_turf(pick(GLOB.generic_event_spawns)))
+ var/datum/reagent/W = pick(/datum/reagent/water/holywater, /datum/reagent/consumable/garlic, /datum/reagent/consumable/ketchup, /datum/reagent/consumable/eggyolk, /datum/reagent/consumable/sodiumchloride, /datum/reagent/consumable/hot_ramen)
+ W = new W
+ S.weakness = W
+ player_mind.transfer_to(S)
+ player_mind.assigned_role = "Eldritch Horror"
+ player_mind.special_role = "Eldritch Horror"
+ player_mind.add_antag_datum(/datum/antagonist/horror)
+ to_chat(S, S.playstyle_string)
+ SEND_SOUND(S, sound('sound/hallucinations/growl2.ogg'))
+ message_admins("[ADMIN_LOOKUPFLW(S)] has been made into an eldritch horror by an event.")
+ log_game("[key_name(S)] was spawned as an eldritch horror by an event.")
+ spawned_mobs += S
+ return SUCCESSFUL_SPAWN
diff --git a/code/modules/mob/living/brain/brain_item.dm b/code/modules/mob/living/brain/brain_item.dm
index f1d6672df55a..3cf269bd69c1 100644
--- a/code/modules/mob/living/brain/brain_item.dm
+++ b/code/modules/mob/living/brain/brain_item.dm
@@ -56,6 +56,10 @@
/obj/item/organ/brain/Remove(mob/living/carbon/C, special = 0, no_id_transfer = FALSE)
..()
+ if(!special)
+ if(C.has_horror_inside())
+ var/mob/living/simple_animal/horror/B = C.has_horror_inside()
+ B.leave_victim()
if(C.mind && C.mind.has_antag_datum(/datum/antagonist/changeling))
var/datum/antagonist/changeling/bruh = C.mind.has_antag_datum(/datum/antagonist/changeling)
for(var/d in bruh.purchasedpowers)
diff --git a/code/modules/mob/living/carbon/death.dm b/code/modules/mob/living/carbon/death.dm
index 3a998a7d4a2b..61cb09bc23da 100644
--- a/code/modules/mob/living/carbon/death.dm
+++ b/code/modules/mob/living/carbon/death.dm
@@ -25,7 +25,10 @@
stomach_contents.Remove(M)
//yogs end
M.forceMove(Tsec)
- visible_message("[M] bursts out of [src]!")
+ if(ishorror(M)) //eldritch horror(aka. borer) check, they die along with their host to prevent mind controlled suicides
+ M.gib()
+ else
+ visible_message("[M] bursts out of [src]!")
..()
/mob/living/carbon/spill_organs(no_brain, no_organs, no_bodyparts)
diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm
index eacb0caba5c8..465b35a87c72 100644
--- a/code/modules/mob/living/carbon/human/human.dm
+++ b/code/modules/mob/living/carbon/human/human.dm
@@ -73,7 +73,10 @@
. += ""
. += "Hivemind Vessels: [hivemind.hive_size] (+[hivemind.size_mod])"
. += "Psychic Link Duration: [(hivemind.track_bonus + TRACKER_DEFAULT_TIME)/10] seconds"
-
+ var/mob/living/simple_animal/horror/H = has_horror_inside()
+ if(H && H.controlling)
+ . += ""
+ . += "Horror chemicals: [H.chemicals]"
if(mind)
var/datum/antagonist/changeling/changeling = mind.has_antag_datum(/datum/antagonist/changeling)
if(changeling)
diff --git a/code/modules/mob/transform_procs.dm b/code/modules/mob/transform_procs.dm
index a26791e84b88..6c85ca92c621 100644
--- a/code/modules/mob/transform_procs.dm
+++ b/code/modules/mob/transform_procs.dm
@@ -3,6 +3,9 @@
/mob/living/carbon/proc/monkeyize(tr_flags = (TR_KEEPITEMS | TR_KEEPVIRUS | TR_KEEPSTUNS | TR_KEEPREAGENTS | TR_DEFAULTMSG))
if (notransform || transformation_timer)
return
+ if(has_horror_inside())
+ to_chat(src, "You feel something strongly clinging to your humanity!")
+ return
//Handle items on mob
if(tr_flags & TR_KEEPITEMS)
@@ -647,4 +650,4 @@
//Not in here? Must be good!
return 1
-#undef TRANSFORMATION_DURATION
\ No newline at end of file
+#undef TRANSFORMATION_DURATION
diff --git a/code/modules/surgery/organ_manipulation.dm b/code/modules/surgery/organ_manipulation.dm
index 8c6b731bb3d7..7cce0956e78f 100644
--- a/code/modules/surgery/organ_manipulation.dm
+++ b/code/modules/surgery/organ_manipulation.dm
@@ -102,6 +102,11 @@
else if(implement_type in implements_extract)
current_type = "extract"
var/list/organs = target.getorganszone(target_zone)
+ var/mob/living/simple_animal/horror/H = target.has_horror_inside()
+ if(H)
+ user.visible_message("[user] begins to extract [H] from [target]'s [parse_zone(target_zone)].",
+ "You begin to extract [H] from [target]'s [parse_zone(target_zone)]...")
+ return TRUE
if(!organs.len)
to_chat(user, "There are no removable organs in [target]'s [parse_zone(target_zone)]!")
return -1
@@ -143,6 +148,13 @@
"[user] inserts something into [target]'s [parse_zone(target_zone)]!")
else if(current_type == "extract")
+ var/mob/living/simple_animal/horror/H = target.has_horror_inside()
+ if(H && H.victim == target)
+ user.visible_message("[user] successfully extracts [H] from [target]'s [parse_zone(target_zone)]!",
+ "You successfully extract [H] from [target]'s [parse_zone(target_zone)].")
+ log_combat(user, target, "surgically removed [H] from", addition="INTENT: [uppertext(user.a_intent)]")
+ H.leave_victim()
+ return FALSE
if(I && I.owner == target)
display_results(user, target, "You successfully extract [I] from [target]'s [parse_zone(target_zone)].",
"[user] successfully extracts [I] from [target]'s [parse_zone(target_zone)]!",
diff --git a/code/modules/uplink/uplink_items.dm b/code/modules/uplink/uplink_items.dm
index 9526565bd306..7d5b7df245c9 100644
--- a/code/modules/uplink/uplink_items.dm
+++ b/code/modules/uplink/uplink_items.dm
@@ -1902,6 +1902,17 @@ GLOBAL_LIST_INIT(uplink_items, subtypesof(/datum/uplink_item))
cost = 20
restricted_roles = list("Chaplain")
surplus = 5 //Very low chance to get it in a surplus crate even without being the chaplain
+
+/datum/uplink_item/role_restricted/horror
+ name = "Horror-in-a-box"
+ desc = "When dissecting the head of a dead Nanotrasen scientist, our surgeons noticed an incredibly peculiar creature inside and managed to extract it into safe containment. \
+ Either a failed experiment or otherworldly monster, this creature has been trained to aid whoever wakes it up. If you aren't afraid of it entering your head, it can prove a useful ally. \
+ We take no responsibility for your newfound madness and accept no refunds."
+ item = /obj/item/storage/box/syndie_kit/horror
+ cost = 16
+ surplus = 0
+ restricted_roles = list("Curator")
+ player_minimum = 20
/datum/uplink_item/role_restricted/explosive_hot_potato
name = "Exploding Hot Potato"
diff --git a/icons/effects/beam.dmi b/icons/effects/beam.dmi
index 0f7daf276d07..fbe6182716b7 100644
Binary files a/icons/effects/beam.dmi and b/icons/effects/beam.dmi differ
diff --git a/icons/effects/effects.dmi b/icons/effects/effects.dmi
index 1a3d0b762527..59a5ad1a79a3 100644
Binary files a/icons/effects/effects.dmi and b/icons/effects/effects.dmi differ
diff --git a/icons/mob/actions/actions_horror.dmi b/icons/mob/actions/actions_horror.dmi
new file mode 100644
index 000000000000..c8a0029e8cf5
Binary files /dev/null and b/icons/mob/actions/actions_horror.dmi differ
diff --git a/icons/mob/animal.dmi b/icons/mob/animal.dmi
index 6da9b16d5a0b..2c27d854e073 100644
Binary files a/icons/mob/animal.dmi and b/icons/mob/animal.dmi differ
diff --git a/icons/mob/inhands/antag/horror_lefthand.dmi b/icons/mob/inhands/antag/horror_lefthand.dmi
new file mode 100644
index 000000000000..c28c81ab31ba
Binary files /dev/null and b/icons/mob/inhands/antag/horror_lefthand.dmi differ
diff --git a/icons/mob/inhands/antag/horror_righthand.dmi b/icons/mob/inhands/antag/horror_righthand.dmi
new file mode 100644
index 000000000000..10d2c64115a5
Binary files /dev/null and b/icons/mob/inhands/antag/horror_righthand.dmi differ
diff --git a/icons/mob/screen_gen.dmi b/icons/mob/screen_gen.dmi
index 75911b35aa75..bb78ada5d519 100644
Binary files a/icons/mob/screen_gen.dmi and b/icons/mob/screen_gen.dmi differ
diff --git a/icons/obj/items_and_weapons.dmi b/icons/obj/items_and_weapons.dmi
index f5d6c1d49f71..4b57eed5dcb3 100644
Binary files a/icons/obj/items_and_weapons.dmi and b/icons/obj/items_and_weapons.dmi differ
diff --git a/strings/names/horror.txt b/strings/names/horror.txt
new file mode 100644
index 000000000000..8472fe71611e
--- /dev/null
+++ b/strings/names/horror.txt
@@ -0,0 +1,7 @@
+F'tan
+Hoe'lef
+Uhluhtc
+Ret'sehc
+Fnurgeetav
+Man'lei
+Gvyrm-itei
diff --git a/tgui/packages/tgui/interfaces/HorrorMutate.js b/tgui/packages/tgui/interfaces/HorrorMutate.js
new file mode 100644
index 000000000000..abc08b4202bd
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/HorrorMutate.js
@@ -0,0 +1,58 @@
+import { useBackend } from '../backend';
+import { Button, LabeledList, Section, Box } from '../components';
+import { Window } from '../layouts';
+
+export const HorrorMutate = (props, context) => {
+ const { act, data } = useBackend(context);
+ return (
+
+
+
+
+
+ {data.available_points}
+
+
+
+
+
+ {data.abilities.map(ability => (
+
+
+ {ability.desc}
+ Cost to unlock: {ability.soul_cost}
+
+ ))}
+
+
+
+
+ {data.upgrades.map(upgrade => (
+
+
+ {upgrade.desc}
+ Cost to unlock: {upgrade.soul_cost}
+
+ ))}
+
+
+
+
+ );
+};
\ No newline at end of file
diff --git a/yogstation.dme b/yogstation.dme
index c76072307d76..6c9b0568c37c 100644
--- a/yogstation.dme
+++ b/yogstation.dme
@@ -223,6 +223,7 @@
#include "code\_onclick\hud\generic_dextrous.dm"
#include "code\_onclick\hud\ghost.dm"
#include "code\_onclick\hud\guardian.dm"
+#include "code\_onclick\hud\horror.dm"
#include "code\_onclick\hud\hud.dm"
#include "code\_onclick\hud\human.dm"
#include "code\_onclick\hud\lavaland_elite.dm"
@@ -1465,6 +1466,12 @@
#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\horror\horror.dm"
+#include "code\modules\antagonists\horror\horror_abilities_and_upgrades.dm"
+#include "code\modules\antagonists\horror\horror_chemicals.dm"
+#include "code\modules\antagonists\horror\horror_datums.dm"
+#include "code\modules\antagonists\horror\horror_html.dm"
+#include "code\modules\antagonists\horror\horror_mutate.dm"
#include "code\modules\antagonists\kudzu\kudzu.dm"
#include "code\modules\antagonists\magic_servant\servant.dm"
#include "code\modules\antagonists\malf\malf.dm"
@@ -1803,6 +1810,7 @@
#include "code\modules\events\grid_check.dm"
#include "code\modules\events\heart_attack.dm"
#include "code\modules\events\high_priority_bounty.dm"
+#include "code\modules\events\horror.dm"
#include "code\modules\events\immovable_rod.dm"
#include "code\modules\events\ion_storm.dm"
#include "code\modules\events\major_dust.dm"