Eldritch horror - the final PR (#13033)

* Update is_helpers.dm

* Update role_preferences.dm

* Update traits.dm

* Update names.dm

* Update traumas.dm

* Update miscellaneous.dm

* Update scanners.dm

* Update sql_ban_system.dm

* Add files via upload

* Update headcrab.dm

* Update panacea.dm

* Create e

* Delete e

* adds horror

* Add files via upload

* Update suicide.dm

* Add files via upload

* Update brain_item.dm

* Update death.dm

* Update human.dm

* Update transform_procs.dm

* Update gun.dm

* Update organ_manipulation.dm

* Update uplink_items.dm

* Add files via upload

* Add files via upload

* Add files via upload

* Add files via upload

* Add files via upload

* Add files via upload

* Add files via upload

* Create horror.txt

* Add files via upload

* Update yogstation.dme

* adds horror-relatated tips

* fixes a typo

* Update code/_onclick/hud/horror.dm

Co-authored-by: TheGamerdk <5618080+TheGamerdk@users.noreply.github.com>

* Update code/game/objects/effects/temporary_visuals/miscellaneous.dm

Co-authored-by: TheGamerdk <5618080+TheGamerdk@users.noreply.github.com>

* Update code/game/objects/effects/temporary_visuals/miscellaneous.dm

Co-authored-by: TheGamerdk <5618080+TheGamerdk@users.noreply.github.com>

* Update code/modules/antagonists/horror/horror.dm

Co-authored-by: TheGamerdk <5618080+TheGamerdk@users.noreply.github.com>

* Update code/modules/antagonists/horror/horror.dm

Co-authored-by: TheGamerdk <5618080+TheGamerdk@users.noreply.github.com>

* Update code/modules/antagonists/horror/horror.dm

Co-authored-by: TheGamerdk <5618080+TheGamerdk@users.noreply.github.com>

* Update code/modules/antagonists/horror/horror.dm

Co-authored-by: TheGamerdk <5618080+TheGamerdk@users.noreply.github.com>

* Update code/modules/antagonists/horror/horror.dm

Co-authored-by: TheGamerdk <5618080+TheGamerdk@users.noreply.github.com>

* Update code/modules/antagonists/horror/horror.dm

Co-authored-by: TheGamerdk <5618080+TheGamerdk@users.noreply.github.com>

* Update code/modules/antagonists/horror/horror.dm

Co-authored-by: TheGamerdk <5618080+TheGamerdk@users.noreply.github.com>

* Update scanners.dm

* Update code/modules/antagonists/changeling/powers/headcrab.dm

Co-authored-by: TheGamerdk <5618080+TheGamerdk@users.noreply.github.com>

* i hope this won't break again

* actually makes horror unable to force lings to suicide

* fixes duplicate definition

* Not needed, changeling code already checks for that

* Cleans up a lot of code

* thank you theos for absolutely nerfing the crap out of horror tentacle

* cleans up a lot of bibby's suggestions and fixes a few bugs

* not sure if i changed anything in this file, but fuck it

* lets fix that merge conflict

* i sure hope github doesn't go nuclear

* oops made horror unable to work

* i hate logic gates

* sorry for commit spam but this thing literally broke horror and i wanna fix it as soon as possibles

* HAHA STUPID DE MORGAN! (tested works as intended)

* abominations summoned by curator horn should not attack horror anymore

* adds missing "

* cannot use lube ability whilst dead anymore

* Update horror_abilities_and_upgrades.dm

* flailing lube stops when you're killed while doing it

* fixes ordering so you can't get stuck in wrong animation at the last spin

* Horror can no longer squeeze through welded airlocks

* Infesting people is now assigned to Alt+Click

* makes tentacle arm no longer affected by range multipliers

Co-authored-by: TheGamerdk <5618080+TheGamerdk@users.noreply.github.com>
This commit is contained in:
ChesterTheCheesy
2022-02-02 19:44:08 +01:00
committed by GitHub
parent 56aaf483c4
commit 4d369ae1db
38 changed files with 2147 additions and 8 deletions

View File

@@ -117,6 +117,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))

View File

@@ -44,6 +44,7 @@
#define ROLE_GANG "gangster" // Yogs
#define ROLE_DARKSPAWN "darkspawn" // Yogs
#define ROLE_HOLOPARASITE "Holoparasite" // Yogs
#define ROLE_HORROR "Eldritch Horror" // Yogs
#define ROLE_INFILTRATOR "Infiltrator" // 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,

View File

@@ -319,6 +319,7 @@
#define GUARDIAN_TRAIT "guardian_trait"
#define RANDOM_BLACKOUTS "random_blackouts"
#define MADE_UNCLONEABLE "made-uncloneable"
#define HORROR_TRAIT "horror"
///Traits given by station traits
#define STATION_TRAIT_BANANIUM_SHIPMENTS "station_trait_bananium_shipments"

View File

@@ -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"))

View File

@@ -0,0 +1,18 @@
/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()
. = ..()
QDEL_NULL(chemical_counter)

View File

@@ -46,7 +46,7 @@ SUBSYSTEM_DEF(traumas)
"doctors" = typecacheof(list(/mob/living/simple_animal/bot/medbot)),
"the supernatural" = typecacheof(list(/mob/living/simple_animal/hostile/construct,
/mob/living/simple_animal/hostile/clockwork, /mob/living/simple_animal/drone/cogscarab,
/mob/living/simple_animal/revenant, /mob/living/simple_animal/shade)),
/mob/living/simple_animal/revenant, /mob/living/simple_animal/shade, /mob/living/simple_animal/horror)),
"aliens" = typecacheof(list(/mob/living/carbon/alien, /mob/living/simple_animal/slime)),
"conspiracies" = typecacheof(list(/mob/living/simple_animal/bot/secbot, /mob/living/simple_animal/bot/ed209, /mob/living/simple_animal/drone,
/mob/living/simple_animal/pet/penguin)),
@@ -115,7 +115,7 @@ SUBSYSTEM_DEF(traumas)
/obj/item/clothing/suit/cultrobes, /obj/item/clothing/suit/space/hardsuit/cult,
/obj/item/clothing/suit/hooded/cultrobes, /obj/item/clothing/head/hooded/cult_hoodie, /obj/effect/rune,
/obj/item/stack/sheet/runed_metal, /obj/machinery/door/airlock/cult, /obj/singularity/narsie,
/obj/item/soulstone,
/obj/item/soulstone, /obj/item/horrortentacle,
/obj/structure/destructible/clockwork, /obj/item/clockwork, /obj/item/clothing/suit/armor/clockwork,
/obj/item/clothing/glasses/judicial_visor, /obj/effect/clockwork/sigil/, /obj/item/stack/tile/brass,
/obj/machinery/door/airlock/clockwork,
@@ -142,7 +142,7 @@ SUBSYSTEM_DEF(traumas)
"anime" = typecacheof(list(/obj/item/clothing/under/schoolgirl, /obj/item/katana, /obj/item/reagent_containers/food/snacks/sashimi, /obj/item/reagent_containers/food/snacks/chawanmushi,
/obj/item/reagent_containers/food/drinks/bottle/sake, /obj/item/throwing_star, /obj/item/clothing/head/kitty/genuine, /obj/item/clothing/suit/space/space_ninja,
/obj/item/clothing/mask/gas/space_ninja, /obj/item/clothing/shoes/space_ninja, /obj/item/clothing/gloves/space_ninja, /obj/item/twohanded/vibro_weapon,
/obj/item/clothing/mask/gas/space_ninja, /obj/item/clothing/shoes/space_ninja, /obj/item/clothing/gloves/space_ninja, /obj/item/twohanded/vibro_weapon, /obj/item/horrortentacle,
/obj/item/nullrod/scythe/vibro, /obj/item/energy_katana, /obj/item/toy/katana, /obj/item/nullrod/claymore/katana, /obj/structure/window/paperframe, /obj/structure/mineral_door/paperframe))
)

View File

@@ -483,3 +483,8 @@
layer = FLY_LAYER
duration = 0.48 SECONDS
mouse_opacity = 0
/obj/effect/temp_visual/summon
randomdir = FALSE
duration = 2 SECONDS
icon_state = "summon"

View File

@@ -200,6 +200,9 @@ GENE SCANNER
to_chat(user, "\t[span_info("Subject is allergic to the chemical [C.allergies].")]")
if(advanced)
to_chat(user, "\t[span_info("Brain Activity Level: [(200 - M.getOrganLoss(ORGAN_SLOT_BRAIN))/2]%.")]")
if(M.has_horror_inside())
to_chat(user, "\t[span_alert("Detected parasitic organism residing in the cranial area.")]")
to_chat(user, "\t[span_alert("Recommended course of action: <b>organ manipulation surgery performed on head.</b>.")]")
if (M.radiation)
to_chat(user, "\t[span_alert("Subject is irradiated.")]")

View File

@@ -266,10 +266,10 @@
break_counter++
output += "</div></div>"
var/list/long_job_lists = list("Civilian" = GLOB.original_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_REV, ROLE_REVENANT,
ROLE_REV_HEAD, ROLE_SERVANT_OF_RATVAR, ROLE_SYNDICATE,

View File

@@ -7,7 +7,6 @@
dna_cost = 1
req_human = 1
ignores_fakedeath = TRUE
req_stat = DEAD
/datum/action/changeling/headcrab/sting_action(mob/user)
set waitfor = FALSE
@@ -33,6 +32,8 @@
to_chat(S, span_userdanger("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()
H?.leave_victim()
user.gib()
. = TRUE
sleep(5) // So it's not killed in explosion

View File

@@ -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, span_notice("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, span_notice("A parasite exits our form."))
..()
var/list/bad_organs = list(
user.getorgan(/obj/item/organ/body_egg),

View File

@@ -0,0 +1,848 @@
/mob/living/simple_animal/horror
name = "eldritch horror"
desc = "Your eyes can barely comprehend what they're looking at."
icon_state = "horror"
icon_living = "horror"
icon_dead = "horror_dead"
icon_gib = "horror_gib"
health = 50
maxHealth = 50
melee_damage_lower = 10
melee_damage_upper = 10
see_in_dark = 5
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","creature","heretics","abomination")
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
unsuitable_atmos_damage = 0.5
var/playstyle_string = span_bold(span_big("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. \
You can attack airlocks to squeeze yourself through them. " + span_danger("Alt+Click on people to infest them."))
var/mob/living/carbon/victim
var/datum/mind/target
var/mob/living/captive_brain/host_brain
var/available_points = 4
var/consumed_souls = 0
//An associative list (associated by ability typepaths) containing the abilities the horror has
var/list/horrorabilities = list()
//same (associated by their ID), but for permanent upgrades
var/list/horrorupgrades = list()
//list storing what items we have to un-glue when stopping mind control
var/list/clothing = list()
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/datum/action/innate/horror/talk_to_horror/talk_to_horror_action = new
/mob/living/simple_animal/horror/Initialize(mapload, gen=1)
..()
real_name = "[pick(GLOB.horror_names)]"
//default abilities
add_ability(/datum/action/innate/horror/mutate)
add_ability(/datum/action/innate/horror/seek_soul)
add_ability(/datum/action/innate/horror/consume_soul)
add_ability(/datum/action/innate/horror/talk_to_host)
add_ability(/datum/action/innate/horror/freeze_victim)
add_ability(/datum/action/innate/horror/toggle_hide)
add_ability(/datum/action/innate/horror/talk_to_brain)
add_ability(/datum/action/innate/horror/take_control)
add_ability(/datum/action/innate/horror/leave_body)
add_ability(/datum/action/innate/horror/make_chems)
add_ability(/datum/action/innate/horror/give_back_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/AltClickOn(atom/A)
if(iscarbon(A))
var/mob/living/carbon/C = A
if(!C || QDELETED(src) || !Adjacent(C) || victim || !can_use_ability())
return
if(victim)
to_chat(src, span_warning("You are already within a host."))
return
to_chat(src, span_warning("You slither your tentacles up [C] and begin probing at their ear canal..."))
if(!do_mob(src, C, 3 SECONDS))
to_chat(src, span_warning("As [C] moves away, you are dislodged and fall to the ground."))
return
if(!C || QDELETED(src))
return
if(C.has_horror_inside())
to_chat(src, span_warning("[C] is already infested!"))
return
Infect(C)
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 = "<div align='center' valign='middle' style='position:relative; top:0px; left:6px'><font color='#7264FF'>[chemicals]</font></div>"
/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
return TRUE
/mob/living/simple_animal/horror/proc/SearchTarget()
if(target)
if(world.time - used_target < 3 MINUTES)
to_chat(src, span_warning("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/list/possible_targets = list()
for(var/datum/mind/M in SSticker.minds)
if(M.current && M.current.stat != DEAD)
if(ishuman(M.current))
if(M.hasSoul && (mind.enslaved_to != M.current))
possible_targets[M] = M
var/list/selected_targets = list()
var/list/icons = list()
while(selected_targets.len != 4)
if(possible_targets.len <= 0)
break
var/datum/mind/M = pick(possible_targets)
selected_targets[M] = M
possible_targets -= M
var/mob/living/carbon/human/H = M.current
icons[M] = H
used_target = world.time
var/entry_name = show_radial_menu(src, (victim ? src.loc : src), icons, tooltips = TRUE)
target = selected_targets[entry_name]
//you didn't select your target? let me do that for you, my friend
if(selected_targets.len > 0 && !target)
target = pick(selected_targets)
if(target)
to_chat(src, span_warning("You caught their scent. Go and consume [target.current.real_name], the [target.assigned_role]'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.current
else
//refund cooldown
used_target = 0
to_chat(src, span_warning("Failed to select a target!"))
/mob/living/simple_animal/horror/proc/ConsumeSoul()
if(!can_use_ability())
return
if(!victim.mind.hasSoul)
to_chat(src, "This host doesn't have a soul!")
return
if(victim == mind.enslaved_to)
to_chat(src, span_userdanger("No, not yet... We still need them..."))
return
if(victim.mind != target)
to_chat(src, "This soul isn't your target, you can't consume it!")
return
to_chat(src, "You begin consuming [victim.name]'s soul!")
if(do_after(src, 20 SECONDS, target = victim, stayStill = FALSE))
consume()
/mob/living/simple_animal/horror/proc/consume()
if(!can_use_ability() || !victim || !victim.mind.hasSoul || victim.mind != target)
return
consumed_souls++
available_points++
to_chat(src, span_userdanger("You succeed in consuming [victim.name]'s soul!"))
to_chat(victim, span_userdanger("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(!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(victim))
if(victim)
to_chat(victim, span_changeling("<i>[real_name] slurs:</i> [input]"))
for(var/M in GLOB.dead_mob_list)
if(isobserver(M))
var/rendered = span_changeling("<i>Horror Communication from <b>[real_name]</b> : [input]</i>")
var/link = FOLLOW_LINK(M, src)
to_chat(M, "[link] [rendered]")
to_chat(src, span_changeling("<i>[real_name] slurs:</i> [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, span_changeling("<i>[real_name] says:</i> [input]"))
for(var/M in GLOB.dead_mob_list)
if(isobserver(M))
var/rendered = span_changeling("<i>Horror Communication from <b>[real_name]</b> : [input]</i>")
var/link = FOLLOW_LINK(M, src)
to_chat(M, "[link] [rendered]")
to_chat(src, span_changeling("<i>[real_name] says:</i> [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, span_changeling("<i>[B.real_name] says:</i> [input]"))
for(var/M in GLOB.dead_mob_list)
if(isobserver(M))
var/rendered = span_changeling("<i>Horror Communication from <b>[B.real_name]</b> : [input]</i>")
var/link = FOLLOW_LINK(M, src)
to_chat(M, "[link] [rendered]")
to_chat(src, span_changeling("<i>[B.real_name] says:</i> [input]"))
/mob/living/simple_animal/horror/Life()
..()
if(has_upgrade("regen"))
heal_overall_damage(5)
if(invisible) //don't regenerate chemicals when invisible
if(use_chemicals(5))
alpha = max(alpha - 100, 1)
else
to_chat(src, span_warning("You ran out of chemicals to support your invisibility."))
invisible = FALSE
Update_Invisibility_Button()
else
if(has_upgrade("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)
/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, span_warning("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, span_warning("You cannot emote while inside a host!"))
return
return ..()
/mob/living/simple_animal/horror/UnarmedAttack(atom/A)
if(istype(A, /obj/machinery/door/airlock))
var/obj/machinery/door/airlock/door = A
if(door.welded)
to_chat(src, span_danger("The door is welded shut!"))
return
visible_message(span_warning("[src] slips their tentacles into the airlock and starts prying it open!"), span_warning("You start moving onto the airlock."))
playsound(A, 'sound/misc/splort.ogg', 50, 1)
if(do_after(src, 5 SECONDS, target = A))
if(door.welded)
to_chat(src, span_danger("The door is welded shut!"))
return
visible_message(span_warning("[src] forces themselves through the airlock!"), span_warning("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(/datum/action/innate/horror/toggle_hide)
H.Activate()
if(invisible)
var/datum/action/innate/horror/H = has_ability(/datum/action/innate/horror/chameleon)
H.Activate()
Update_Invisibility_Button()
..()
/mob/living/simple_animal/horror/ex_act()
if(victim)
return
..()
/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, span_warning("[C] doesn't have a head!"))
return
var/hasbrain = locate(/obj/item/organ/brain) in C.internal_organs
if(!hasbrain)
to_chat(src, span_warning("[C] doesn't have a brain!"))
return
if(C.has_horror_inside())
to_chat(src, span_warning("[C] is already infested!"))
return
//can only infect non-ssd alive people / corpses with ghosts attached / current target
if((C.stat == DEAD || !C.key) && (C.stat != DEAD || !C.get_ghost()) && (!target || C != target.current))
to_chat(src, span_warning("[C]'s mind seems unresponsive. Try someone else!"))
return
if(hiding)
var/datum/action/innate/horror/H = has_ability(/datum/action/innate/horror/toggle_hide)
H.Activate()
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(!can_use_ability())
return
if(!victim)
to_chat(src, span_warning("You are not inside a host body."))
return
var/content = "<p>Chemicals: <span id='chemicals'>[chemicals]</span></p>"
content += "<table>"
for(var/path in subtypesof(/datum/horror_chem))
var/datum/horror_chem/chem = path
if(path in horror_chems)
content += "<tr><td><a class='chem-select' href='?_src_=\ref[src];src=\ref[src];horror_use_chem=[initial(chem.chemname)]'>[initial(chem.chemname)] ([initial(chem.chemuse)])</a><p>[initial(chem.chem_desc)]</p></td></tr>"
content += "</table>"
var/html = get_html_template(content)
usr << browse(html, "window=ViewHorror\ref[src]Chems;size=600x800")
/mob/living/simple_animal/horror/proc/hide()
if(victim)
to_chat(src, span_warning("You cannot do this while you're inside a host."))
return
if(stat != CONSCIOUS)
return
if(!hiding)
layer = LATTICE_LAYER
visible_message(span_name("[src] scurries to the ground!"), \
span_noticealien("You are now hiding."))
hiding = TRUE
else
layer = MOB_LAYER
visible_message("[src] slowly peaks up from the ground...", \
span_noticealien("You stop hiding."))
hiding = FALSE
/mob/living/simple_animal/horror/proc/go_invisible()
if(victim)
to_chat(src, span_warning("You cannot do this while you're inside a host."))
return
if(!can_use_ability())
return
if(!has_chemicals(10))
to_chat(src, span_warning("You don't have enough chemicals to do that."))
return
if(!invisible)
to_chat(src, span_noticealien("You focus your chameleon skin to blend into the environment."))
invisible = TRUE
else
to_chat(src, span_noticealien("You stop your camouflage."))
invisible = FALSE
/mob/living/simple_animal/horror/proc/freeze_victim()
if(world.time - used_freeze < 150)
to_chat(src, span_warning("You cannot use that ability again so soon."))
return
if(victim)
to_chat(src, span_warning("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
if(QDELETED(src) || stat != CONSCIOUS || victim || (world.time - used_freeze < 150))
return
layer = MOB_LAYER
for (var/mob/living/carbon/M in range(1, src))
if(!M || !Adjacent(M))
return
if(has_upgrade("paralysis"))
playsound(loc, "sound/effects/sparks4.ogg", 30, 1, -1)
M.Stun(50)
M.SetSleeping(70) //knocked out cold
M.electrocute_act(15, src, 1, FALSE, FALSE, FALSE, 1, FALSE)
else
to_chat(M, span_userdanger("You feel something wrapping around your leg, pulling you down!"))
playsound(loc, "sound/weapons/whipgrab.ogg", 30, 1, -1)
M.Immobilize(50)
M.Knockdown(70)
used_freeze = world.time
/mob/living/simple_animal/horror/proc/is_leaving()
return leaving
/mob/living/simple_animal/horror/proc/release_victim()
if(!victim)
to_chat(src, span_danger("You are not inside a host body."))
return
if(!can_use_ability())
return
if(leaving)
leaving = FALSE
to_chat(src, span_danger("You decide against leaving your host."))
return
to_chat(src, span_danger("You begin disconnecting from [victim]'s synapses and prodding at their internal ear canal."))
if(victim.stat != DEAD && !has_upgrade("invisible_exit"))
to_chat(victim, span_userdanger("An odd, uncomfortable pressure begins to build inside your skull, behind your ear..."))
leaving = TRUE
if(do_after(src, 100, target = victim, extra_checks = CALLBACK(src, .proc/is_leaving), stayStill = FALSE))
release_host()
/mob/living/simple_animal/horror/proc/release_host()
if(!victim || QDELETED(victim) || QDELETED(src) || controlling)
return
if(!can_use_ability())
return
if(has_upgrade("invisible_exit"))
alpha = 60
if(has_ability(/datum/action/innate/horror/chameleon))
invisible = TRUE
Update_Invisibility_Button()
to_chat(src, span_danger("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, span_danger("You wiggle out of [victim]'s ear and plop to the ground."))
if(victim.mind)
if(!has_upgrade("invisible_exit"))
to_chat(victim, span_danger("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()
unset_machine()
victim.reset_perspective()
victim.unset_machine()
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(span_warning("[victim]'s tentacle transforms back!"), span_notice("Your tentacle disappears!"))
playsound(victim, 'sound/effects/blobattack.ogg', 30, 1)
qdel(T)
victim = null
RefreshAbilities()
/mob/living/simple_animal/horror/proc/jumpstart()
if(!victim)
to_chat(src, span_warning("You need a host to be able to use this."))
return
if(!can_use_ability())
return
if(victim.stat != DEAD)
to_chat(src, span_warning("Your host is already alive!"))
return
if(!has_chemicals(250))
to_chat(src, span_warning("You need 250 chemicals to use this!"))
return
if(HAS_TRAIT_FROM(target, TRAIT_BADDNA, CHANGELING_DRAIN))
to_chat(src, span_warning("Their DNA is completely destroyed! You can't revive them"))
return
if(victim.stat == DEAD)
playsound(src, 'sound/machines/defib_charge.ogg', 50, 1, -1)
sleep(1 SECONDS)
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(HAS_TRAIT_FROM(victim, TRAIT_HUSK, BURN))
victim.cure_husk(BURN)
for(var/organ in victim.internal_organs)
var/obj/item/organ/O = organ
O.setOrganDamage(0)
victim.restore_blood()
victim.remove_all_embedded_objects()
victim.revive()
log_game("[src]/([src.ckey]) has revived [victim]/([victim.ckey]")
chemicals -= 250
to_chat(src, span_notice("You send a jolt of energy to your host, reviving them!"))
victim.grab_ghost(force = TRUE) //brings the host back, no eggscape
victim.adjustOxyLoss(30)
to_chat(victim, span_userdanger("You bolt upright, gasping for breath!"))
victim.electrocute_act(15, src, 1, FALSE, FALSE, FALSE, 1, FALSE)
playsound(src, 'sound/machines/defib_zap.ogg', 50, 1, -1)
/mob/living/simple_animal/horror/proc/view_memory()
if(!victim)
to_chat(src, span_warning("You need a host to be able to use this."))
return
if(!can_use_ability())
return
if(victim.stat == DEAD)
to_chat(src, span_warning("Your host brain is unresponsive. They are dead!"))
return
if(prob(20))
to_chat(victim, span_danger("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, span_boldnotice("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, span_notice("[antag_types.antag_memory]"))
if(LAZYLEN(all_objectives))
to_chat(src, span_boldnotice("Objectives:"))
var/obj_count = 1
for(var/O in all_objectives)
var/datum/objective/objective = O
to_chat(src, span_notice("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, span_notice("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, span_boldnotice("You catch some drifting memories of their past conversations..."))
for(var/spoken_memory in recent_speech)
to_chat(src, span_notice("[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, span_boldnotice("You uncover that [H.p_their()] true identity is [the_dna.real_name]."))
/mob/living/simple_animal/horror/proc/is_bonding()
return bonding
/mob/living/simple_animal/horror/proc/bond_brain()
if(!victim)
to_chat(src, span_warning("You are not inside a host body."))
return
if(!can_use_ability())
return
if(victim.stat == DEAD)
to_chat(src, span_notice("This host lacks enough brain function to control."))
return
if(victim.has_trauma_type(/datum/brain_trauma/severe/split_personality))
to_chat(src, span_notice("This host's brain lobe separation makes it too complex for you to control."))
return
if(bonding)
bonding = FALSE
to_chat(src, span_danger("You stop attempting to take control of your host."))
return
to_chat(src, span_danger("You begin delicately adjusting your connection to the host brain..."))
if(QDELETED(src) || QDELETED(victim))
return
bonding = TRUE
var/delay = 20 SECONDS
if(has_upgrade("fast_control"))
delay -= 12 SECONDS
if(do_after(src, delay, target = victim, extra_checks = CALLBACK(src, .proc/is_bonding), stayStill = FALSE))
assume_control()
/mob/living/simple_animal/horror/proc/assume_control()
if(!victim || !src || controlling || victim.stat == DEAD)
return
if(is_servant_of_ratvar(victim) || iscultist(victim))
to_chat(src, span_warning("[victim]'s mind seems to be blocked by some unknown force!"))
bonding = FALSE
return
if(HAS_TRAIT(victim, TRAIT_MINDSHIELD))
to_chat(src, span_warning("[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, span_warning("You plunge your probosci deep into the cortex of the host brain, interfacing directly with their nervous system."))
to_chat(victim, span_userdanger("You feel a strange shifting sensation behind your eyes as an alien consciousness displaces yours."))
clothing = victim.get_equipped_items()
for(var/obj/item/I in clothing)
ADD_TRAIT(I, TRAIT_NODROP, HORROR_TRAIT)
qdel(host_brain)
host_brain = new(src)
host_brain.H = src
host_brain.name = "Trapped mind of [victim.real_name]"
victim.mind.transfer_to(host_brain)
if(victim.key)
host_brain.key = victim.key
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.current
/mob/living/carbon/proc/release_control()
var/mob/living/simple_animal/horror/B = has_horror_inside()
if(B && B.host_brain)
to_chat(src, span_danger("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
/mob/living/simple_animal/horror/proc/hit_detatch()
if(victim.health <= 75)
detatch()
to_chat(src, span_warning("It appears that [victim]s brain detected danger, and hastily took over."))
to_chat(victim, span_danger("Your body is under attack, you unconciously forced your brain to immediately take 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)
for(var/obj/item/I in clothing)
REMOVE_TRAIT(I, TRAIT_NODROP, HORROR_TRAIT)
clothing = list()
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)
if(host_brain.key)
victim.key = host_brain.key
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(/datum/action/innate/horror/chameleon)
if(action)
action.button_icon_state = "horror_sneak_[invisible ? "true" : "false"]"
action.UpdateButtonIcon()
/mob/living/simple_animal/horror/proc/GrantHorrorActions()
for(var/datum/action/innate/horror/ability in horrorabilities)
if("horror" in ability.category)
ability.Grant(src)
/mob/living/simple_animal/horror/proc/RemoveHorrorActions()
for(var/datum/action/innate/horror/ability in horrorabilities)
if("horror" in ability.category)
ability.Remove(src)
/mob/living/simple_animal/horror/proc/GrantInfestActions()
for(var/datum/action/innate/horror/ability in horrorabilities)
if("infest" in ability.category)
ability.Grant(src)
/mob/living/simple_animal/horror/proc/RemoveInfestActions()
for(var/datum/action/innate/horror/ability in horrorabilities)
if("infest" in ability.category)
ability.Remove(src)
/mob/living/simple_animal/horror/proc/GrantControlActions()
for(var/datum/action/innate/horror/ability in horrorabilities)
if("control" in ability.category)
ability.Grant(victim)
/mob/living/simple_animal/horror/proc/RemoveControlActions()
for(var/datum/action/innate/horror/ability in horrorabilities)
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()

View File

@@ -0,0 +1,461 @@
//ABILITIES
/datum/action/innate/horror
background_icon_state = "bg_ecult"
icon_icon = 'icons/mob/actions/actions_horror.dmi'
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/IsAvailable()
if(!B)
return
if(!B.has_chemicals(chemical_cost))
return
. = ..()
/datum/action/innate/horror/mutate
name = "Mutate"
desc = "Use consumed souls to mutate your abilities."
button_icon_state = "mutate"
blacklisted = TRUE
category = list("horror")
/datum/action/innate/horror/mutate/Activate()
to_chat(usr, span_velvet(span_bold("You focus on mutating your body...")))
B.ui_interact(usr)
return TRUE
/datum/action/innate/horror/seek_soul
name = "Seek target 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"
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"
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/toggle_hide
name = "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"
desc = "Communicate mentally with your horror."
button_icon_state = "talk_to_horror"
blacklisted = TRUE
var/mob/living/O
/datum/action/innate/horror/talk_to_horror/IsAvailable()
if(owner.stat == DEAD)
return
return 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"
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"
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"
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"
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"
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"
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"
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
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(span_warning("[B.victim]'s arm contorts into tentacles!"), span_notice("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, span_warning("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(span_warning("[B.victim]'s tentacle transforms back!"), span_notice("Your tentacle disappears!"))
playsound(B.victim, 'sound/effects/blobattack.ogg', 30, 1)
to_chat(B, span_warning("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"
desc = "Move into another host directly. Grabbing makes the process faster."
button_icon_state = "transfer_host"
category = list("infest", "control")
soul_price = 1
var/transferring = FALSE
/datum/action/innate/horror/transfer_host/proc/is_transferring(var/mob/living/carbon/C)
return transferring && C.Adjacent(B.victim)
/datum/action/innate/horror/transfer_host/Activate()
if(transferring)
transferring = FALSE
to_chat(src, span_warning("You decide against leaving your host."))
return
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, span_warning("[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, span_warning("[C] doesn't have a brain!"))
return
if((!C.key || !C.mind) && C != B.target.current)
to_chat(owner, span_warning("[C]'s mind seems unresponsive. Try someone else!"))
return
if(C.has_horror_inside())
to_chat(owner, span_warning("[C] is already infested!"))
return
to_chat(owner, span_warning("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
transferring = TRUE
if(!do_after(B.victim, delay, target = C, extra_checks = CALLBACK(src, .proc/is_transferring, C), stayStill = FALSE))
to_chat(owner, span_warning("As [C] moves away, your transfer gets interrupted!"))
transferring = FALSE
return
transferring = FALSE
if(!C || !B || !C.Adjacent(B.victim))
return
B.leave_victim()
B.Infect(C)
if(!silent)
to_chat(C, span_warning("Something slimy wiggles into your ear!"))
playsound(B, 'sound/effects/blobattack.ogg', 30, 1)
/datum/action/innate/horror/jumpstart_host
name = "Revive 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"
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"
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()
/datum/action/innate/horror/lube_spill
name = "Lube spill"
desc = "Makes you spin around and flail slippery lube around you. Costs 30 chemicals to activate."
button_icon_state = "lube_spill"
chemical_cost = 30
category = list("horror")
soul_price = 1
var/cooldown = 0
/datum/action/innate/horror/lube_spill/IsAvailable()
if(cooldown > world.time || !B.has_chemicals(chemical_cost) || !B.can_use_ability())
return
return ..()
/datum/action/innate/horror/lube_spill/Activate()
B.use_chemicals(30)
cooldown = world.time + 10 SECONDS
UpdateButtonIcon()
addtimer(CALLBACK(src, .proc/UpdateButtonIcon), 10 SECONDS)
B.visible_message(span_warning("[B] starts spinning and throwing some sort of substance!"), span_notice("Your start to spin and flail oily substance everywhere!"))
var/spins_remaining = 10
B.icon_state = "horror_spin"
while(spins_remaining > 0)
playsound(B, 'sound/effects/blobattack.ogg', rand(20, 30), rand(0.5, 2))
for(var/turf/open/t in range(1, B))
if(prob(60) && B.Adjacent(t))
t.MakeSlippery(TURF_WET_LUBE, 100)
sleep(5)
spins_remaining--
if(!B.can_use_ability())
return TRUE
B.icon_state = "horror"
return TRUE
//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(/datum/action/innate/horror/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
//Doubles 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 a host."
soul_price = 2
/datum/horror_upgrade/hp_up/apply_effects()
B.health = round(min(B.maxHealth,B.health * 2))
B.maxHealth = round(B.maxHealth * 2)
//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

View File

@@ -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, span_boldnotice("You need [C.chemuse] chemicals stored to use this chemical!"))
return
to_chat(src, span_danger("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."

View File

@@ -0,0 +1,333 @@
//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.current) && owner.current.mind)
var/mob/living/simple_animal/horror/H = owner.current
H.update_horror_hud()
/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
//succ some souls
var/datum/objective/horrorascend/ascend = new
ascend.owner = owner
ascend.hor = owner.current
ascend.target_amount = rand(5, 8)
objectives += ascend
ascend.update_explanation_text()
//looking for antagonist we can assist
var/list/possible_targets = list()
for(var/datum/mind/M in SSticker.minds)
if(M.current && M.current.stat != DEAD)
if(ishuman(M.current))
if(M.special_role)
possible_targets += M
if(possible_targets.len)
var/datum/mind/M = pick(possible_targets)
var/datum/objective/protect/O = new
O.owner = owner
O.target = M
O.explanation_text = "Protect and assist \the [M.current.real_name], the [M.assigned_role]."
objectives += O
//don't die while you're at is
var/datum/objective/survive/survive = new
survive.owner = owner
objectives += survive
/datum/objective/horrorascend
name = "consume souls"
var/mob/living/simple_animal/horror/hor
/datum/objective/horrorascend/update_explanation_text()
. = ..()
explanation_text = "Consume [target_amount] souls."
/datum/objective/horrorascend/check_completion()
if(hor && hor.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
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.key = C.key
H.mind.enslave_mind_to_creator(user)
H.mind.add_antag_datum(C)
H.mind.memory += "You are " + span_purple(span_bold("[H.real_name]")) + ", an eldritch horror. Consume souls to evolve.<br>"
var/datum/antagonist/horror/S = new
S.summoner = user.mind
S.antag_memory += "<b>[user.mind]</b> woke you from your eternal slumber. Aid them in their objectives as a token of gratitude.<br>"
H.mind.add_antag_datum(S)
log_game("[key_name(user)] has summoned [key_name(H)], an eldritch horror.")
to_chat(user, span_bold("[H.real_name]</b> 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, span_notice("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, span_purple("Blowing into this horn will recall the horror back to you. Be wary, the horn is loud, and may attract <B>unwanted</B> attention."))
/obj/item/horrorsummonhorn/attack_self(mob/living/user)
if(cooldown > world.time)
to_chat(user, span_notice("Take a breath before you blow [src] again."))
return
to_chat(user, span_notice("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, span_notice("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(span_danger("[R] emerges!"))
else
if(!horror || horror.stat == DEAD)
summonplace.visible_message(span_danger("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(span_notice("[horror] appears out of nowhere!"))
if(user.mind != summoner)
sleep(2 SECONDS)
playsound(summonplace, "sound/effects/glassbr2.ogg", 30, 1)
to_chat(user, span_danger("[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(span_suicide("[user] stabs [user.p_their()] forehead with [src]! It looks like [user.p_theyre()] trying to commit suicide!"))
return BRUTELOSS
//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
weapon_stats = list(SWING_SPEED = 1, ENCUMBRANCE = 0, ENCUMBRANCE_TIME = 0, REACH = 2, DAMAGE_LOW = 0, DAMAGE_HIGH = 0)
range_cooldown_mod = 0 //tentacle is designed to hit from range
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, span_velvet(span_bold("Functions:")))
to_chat(user, span_velvet("<b>All attacks work up to 2 tiles away.</b>"))
to_chat(user, span_velvet("<b>Help intent:</b> Usual help function of an arm."))
to_chat(user, span_velvet("<b>Disarm intent:</b> Whips the tentacle, disarming your opponent."))
to_chat(user, span_velvet("<b>Grab intent:</b> Instant aggressive grab on an opponent. Can also throw them!"))
to_chat(user, span_velvet("<b>Harm intent:</b> Whips the tentacle, damaging your opponent."))
to_chat(user, span_velvet("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(span_danger("[I] is whipped out of [C]'s hand by [user]!"),span_userdanger("A tentacle whips [I] out of your hand!"))
return
else
to_chat(user, span_danger("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(span_warning("[user] throws [H] with [user.p_their()] [src]!"), span_warning("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, span_warning("The airlock's bolts prevent it from being forced!"))
return
if(A.hasPower())
user.visible_message(span_warning("[user] jams [src] into the airlock and starts prying it open!"), span_warning("You start forcing the airlock open."),
span_italics("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(span_warning("[user] forces the airlock to open with [user.p_their()] [src]!"), span_warning("You force the airlock to open."),
span_italics("You hear a metal screeching sound."))
A.open(2)
return
. = ..()
/obj/item/horrortentacle/suicide_act(mob/user) //this will never be called, since horror stops suicide, but might as well if they get tentacle through other means
user.visible_message(span_suicide("[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, span_danger("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
if(stat == 2)
return say_dead(message)
to_chat(src, span_alien(span_italics("You whisper silently, \"[message]\"")))
to_chat(H.victim, span_alien(span_italics("[src] whispers, \"[message]\"")))
for(var/M in GLOB.dead_mob_list)
if(isobserver(M))
var/rendered = span_changeling("<i>[src] transfers: \"[message]\"</i>")
var/link = FOLLOW_LINK(M, H.victim)
to_chat(M, "[link] [rendered]")
/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, span_danger("You begin doggedly resisting the parasite's control."))
to_chat(H.victim, span_danger("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, span_userdanger("With an immense exertion of will, you regain control of your body!"))
to_chat(H.victim, span_danger("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()

View File

@@ -0,0 +1,102 @@
/mob/living/simple_animal/horror/proc/get_html_template(content)
var/html = {"<!DOCTYPE html">
<html>
<head>
<title>Horror Chemicals</title>
<link rel='stylesheet' type='text/css' href='icons.css'>
<link rel='stylesheet' type='text/css' href='shared.css'>
<style type='text/css'>
body {
padding: 10;
margin: 0;
font-size: 12px;
color: #ffffff;
line-height: 170%;
font-family: Verdana, Geneva, sans-serif;
background: #272727 url(uiBackground.png) 50% 0 repeat-x;
overflow-x: hidden;
}
a, a:link, a:visited, a:active, .link, .linkOn, .linkOff, .selected, .disabled {
color: #ffffff;
text-decoration: none;
background: #40628a;
border: 1px solid #161616;
padding: 2px 2px 2px 2px;
margin: 2px 2px 2px 2px;
cursor: pointer;
display: inline-block;
}
a:hover, .linkActive:hover {
background: #507aac;
cursor: pointer;
}
img {
border: 0px;
}
p {
padding: 4px;
margin: 0px;
}
h1, h2, h3, h4, h5, h6 {
margin: 0;
padding: 16px 0 8px 0;
color: #517087;
clear: both;
}
h1 {
font-size: 15px;
}
h2 {
font-size: 14px;
}
h3 {
font-size: 13px;
}
h4 {
font-size: 12px;
}
#header {
margin: 3px;
padding: 0px;
}
table {
width: 570px;
margin: 10px;
}
td {
border: solid 1px #000;
width: 560px;
}
.chem-select {
width: 560px;
margin: 5px;
text-align: center;
}
.enabled {
background-color: #0a0;
}
.disabled {
background-color: #a00;
}
.shown {
display: block;
}
.hidden {
display: none;
}
</style>
<script src="jquery.min.js"></script>
<script type='text/javascript'>
function update_chemicals(chemicals) {
$('#chemicals').text(chemicals);
}
$(function() {
});
</script>
</head>
<body scroll='yes'><div id='content'>
<h1 id='header'>Horror Chemicals</h1>
<br />
[content]
</div></body></html>"}
return html

View File

@@ -0,0 +1,97 @@
// Horror mutation menu
// Totally not a copypaste of darkspawn menu, not a copypaste of cellular emporium, i swear. Edit: now looks like guardianbuilder too
/mob/living/simple_animal/horror/proc/has_ability(typepath)
for(var/datum/action/innate/horror/ability in horrorabilities)
if(istype(ability, typepath))
return ability
return
/mob/living/simple_animal/horror/proc/add_ability(typepath)
if(has_ability(typepath))
return
var/datum/action/innate/horror/action = new typepath
action.B = src
horrorabilities += action
RefreshAbilities()
to_chat(src, span_velvet("You have mutated the <b>[action.name]</b>."))
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, "<span class='velvet bold'>You have adapted the \"[U.name]\" upgrade.</span>")
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["typepath"] = path
AL["desc"] = initial(ability.desc)
AL["soul_cost"] = initial(ability.soul_price)
AL["owned"] = has_ability(path)
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["typepath"])
if("upgrade")
add_upgrade(params["id"])

View File

@@ -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

View File

@@ -0,0 +1,34 @@
/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)))
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

View File

@@ -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)

View File

@@ -25,7 +25,10 @@
stomach_contents.Remove(M)
//yogs end
M.forceMove(Tsec)
visible_message(span_danger("[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(span_danger("[M] bursts out of [src]!"))
..()
/mob/living/carbon/spill_organs(no_brain, no_organs, no_bodyparts)

View File

@@ -73,6 +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)

View File

@@ -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, "<span class='warning'>You feel something strongly clinging to your humanity!</span>")
return
//Handle items on mob
if(tr_flags & TR_KEEPITEMS)

View File

@@ -554,6 +554,10 @@
semicd = FALSE
if(user == target && user.has_horror_inside())
user.visible_message(span_warning("[user] decided not to shoot."), span_notice("Something inside your head stops your action!"))
return
target.visible_message(span_warning("[user] pulls the trigger!"), span_userdanger("[(user == target) ? "You pull" : "[user] pulls"] the trigger!"))
if(chambered && chambered.BB)

View File

@@ -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)].",
"<span class='notice'>You begin to extract [H] from [target]'s [parse_zone(target_zone)]...</span>")
return TRUE
if(!organs.len)
to_chat(user, span_notice("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)]!",
"<span class='notice'>You successfully extract [H] from [target]'s [parse_zone(target_zone)].</span>")
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, span_notice("You successfully extract [I] from [target]'s [parse_zone(target_zone)]."),
"[user] successfully extracts [I] from [target]'s [parse_zone(target_zone)]!",

View File

@@ -2010,6 +2010,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/horrorspawner
cost = 16
surplus = 0
restricted_roles = list("Curator")
player_minimum = 20
/datum/uplink_item/role_restricted/explosive_hot_potato
name = "Exploding Hot Potato"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 112 KiB

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 881 KiB

After

Width:  |  Height:  |  Size: 882 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 230 KiB

After

Width:  |  Height:  |  Size: 250 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 402 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 405 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 113 KiB

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 93 KiB

After

Width:  |  Height:  |  Size: 94 KiB

11
strings/names/horror.txt Normal file
View File

@@ -0,0 +1,11 @@
F'tan
Hoe'lef
Y'gge
Uhluhtc
Ret'sehc
Geansrev
Fnurgeetav
Beq'pewks
Man'lei
Gvyrm-itei
Se pge'ti

View File

@@ -236,6 +236,13 @@ As a Revenant, the illness inflicted on humans by Blight can be easily cured by
As a Swarmer, you can deconstruct more things than you think. Try deconstructing light switches, buttons, air alarms and more. Experiment!
As a Swarmer, you can teleport fellow swarmers away if you think they are in danger.
As a Morph, you can talk while disguised, but your words have a chance of being slurred, giving you away!
As an Eldritch Horror, improvise! You have many abilities you can mutate, most of them applying to diverse situations. Experiment!
As an Eldritch Horror, don't forget that you're not exempt from server rules while mind-controlling someone. Their actions become your actions.
You can remove an Eldritch Horror from someone's head via surgery. Decapitation also works!
You can detect an Eldritch Horror inside someone's head with an advanced health analyzer.
If you see an Eldritch Horror trying to enter someone's head, you can quickly shove it away to interrupt the process!
Eldritch Horrors can enter any creature that has a brain organ. Yes, that includes xenomorphs.
Contrary to popular belief, Eldritch Horrors are not "cute".
As a Drone, you can ping other drones to alert them of areas in the station in need of repair.
As a Ghost, you can see the inside of a container on the ground by clicking on it.
As a Ghost, you can double click on just about anything to follow it. Or just warp around!
@@ -269,4 +276,4 @@ Anything you can light a cigarette with, you can use to cauterize a bleeding wou
Moderate and Severe cuts will slowly clot by themselves and don't require immediate attention on their own. Critical cuts will actively get worse with time, and are an immediate threat to the patient's life.
Laying down will help slow down bloodloss. Death will halt it entirely.
If you're bleeding, you can apply pressure to the limb by grabbing yourself while targeting the bleeding limb. This will slow you down and take up a hand, but it'll slow down how fast you lose blood. Note this won't help the bleeding clot any faster.
The TOME OF HERBAL KNOWLEDGE in the ashwalker's den provides useful information on surviving the lavaland wastes.
The TOME OF HERBAL KNOWLEDGE in the ashwalker's den provides useful information on surviving the lavaland wastes.

View File

@@ -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 (
<Window width={900} height={480} resizable>
<Window.Content>
<Section title="Mutation Menu">
<LabeledList>
<LabeledList.Item label="Available points" right>
{data.available_points}
</LabeledList.Item>
</LabeledList>
</Section>
<Section title="Abilities">
<LabeledList>
{data.abilities.map(ability => (
<LabeledList.Item label={ability.name} key={ability.name}>
<Box>{ability.desc}</Box>
<Box>Cost to unlock: {ability.soul_cost}</Box>
<Button
selected={ability.owned}
disabled={!ability.can_purchase}
onClick={() => act('unlock', {
'typepath': ability.typepath,
})}
content={ability.owned ? "Unlocked" : "Unlock"}
/>
</LabeledList.Item>
))}
</LabeledList>
</Section>
<Section title="Upgrades">
<LabeledList>
{data.upgrades.map(upgrade => (
<LabeledList.Item label={upgrade.name} key={upgrade.name}>
<Box>{upgrade.desc}</Box>
<Box>Cost to unlock: {upgrade.soul_cost}</Box>
<Button
selected={upgrade.owned}
disabled={!upgrade.can_purchase}
onClick={() => act('upgrade', {
'id': upgrade.id,
})}
content={upgrade.owned ? "Unlocked" : "Unlock"}
/>
</LabeledList.Item>
))}
</LabeledList>
</Section>
</Window.Content>
</Window>
);
};

View File

@@ -229,6 +229,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"
@@ -1506,6 +1507,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"
@@ -1841,6 +1848,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"