From accc11769639767f8b1246cb4a3a2707005a0f99 Mon Sep 17 00:00:00 2001 From: ToasterBiome Date: Sun, 6 Feb 2022 21:39:15 -0600 Subject: [PATCH 1/3] ok whatever --- code/_onclick/hud/horror.dm | 36 +- code/modules/antagonists/horror/horror.dm | 1694 ++++++++--------- .../horror/horror_abilities_and_upgrades.dm | 920 ++++----- .../antagonists/horror/horror_chemicals.dm | 190 +- .../antagonists/horror/horror_datums.dm | 666 +++---- .../modules/antagonists/horror/horror_html.dm | 202 +- .../antagonists/horror/horror_mutate.dm | 192 +- code/modules/events/horror.dm | 66 +- 8 files changed, 1983 insertions(+), 1983 deletions(-) diff --git a/code/_onclick/hud/horror.dm b/code/_onclick/hud/horror.dm index 876dc00be9ca..a9043b5754a5 100644 --- a/code/_onclick/hud/horror.dm +++ b/code/_onclick/hud/horror.dm @@ -1,18 +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) - +/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) + diff --git a/code/modules/antagonists/horror/horror.dm b/code/modules/antagonists/horror/horror.dm index e0653bcbb8e6..5da28bbddbaf 100644 --- a/code/modules/antagonists/horror/horror.dm +++ b/code/modules/antagonists/horror/horror.dm @@ -1,848 +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 = "
[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 - 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("[real_name] slurs: [input]")) - for(var/M in GLOB.dead_mob_list) - if(isobserver(M)) - var/rendered = span_changeling("Horror Communication from [real_name] : [input]") - var/link = FOLLOW_LINK(M, src) - to_chat(M, "[link] [rendered]") - to_chat(src, span_changeling("[real_name] slurs: [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("[real_name] says: [input]")) - - for(var/M in GLOB.dead_mob_list) - if(isobserver(M)) - var/rendered = span_changeling("Horror Communication from [real_name] : [input]") - var/link = FOLLOW_LINK(M, src) - to_chat(M, "[link] [rendered]") - to_chat(src, span_changeling("[real_name] 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, span_changeling("[B.real_name] says: [input]")) - - for(var/M in GLOB.dead_mob_list) - if(isobserver(M)) - var/rendered = span_changeling("Horror Communication from [B.real_name] : [input]") - var/link = FOLLOW_LINK(M, src) - to_chat(M, "[link] [rendered]") - to_chat(src, span_changeling("[B.real_name] says: [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 = "

Chemicals: [chemicals]

" - content += "" - - for(var/path in subtypesof(/datum/horror_chem)) - var/datum/horror_chem/chem = path - if(path in horror_chems) - content += "" - - content += "
[initial(chem.chemname)] ([initial(chem.chemuse)])

[initial(chem.chem_desc)]

" - - 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() +/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 = "
[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 + 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("[real_name] slurs: [input]")) + for(var/M in GLOB.dead_mob_list) + if(isobserver(M)) + var/rendered = span_changeling("Horror Communication from [real_name] : [input]") + var/link = FOLLOW_LINK(M, src) + to_chat(M, "[link] [rendered]") + to_chat(src, span_changeling("[real_name] slurs: [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("[real_name] says: [input]")) + + for(var/M in GLOB.dead_mob_list) + if(isobserver(M)) + var/rendered = span_changeling("Horror Communication from [real_name] : [input]") + var/link = FOLLOW_LINK(M, src) + to_chat(M, "[link] [rendered]") + to_chat(src, span_changeling("[real_name] 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, span_changeling("[B.real_name] says: [input]")) + + for(var/M in GLOB.dead_mob_list) + if(isobserver(M)) + var/rendered = span_changeling("Horror Communication from [B.real_name] : [input]") + var/link = FOLLOW_LINK(M, src) + to_chat(M, "[link] [rendered]") + to_chat(src, span_changeling("[B.real_name] says: [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 = "

Chemicals: [chemicals]

" + content += "" + + for(var/path in subtypesof(/datum/horror_chem)) + var/datum/horror_chem/chem = path + if(path in horror_chems) + content += "" + + content += "
[initial(chem.chemname)] ([initial(chem.chemuse)])

[initial(chem.chem_desc)]

" + + 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() \ 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 index e5512caf24af..ddc238a8d2e1 100644 --- a/code/modules/antagonists/horror/horror_abilities_and_upgrades.dm +++ b/code/modules/antagonists/horror/horror_abilities_and_upgrades.dm @@ -1,461 +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." +//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 \ No newline at end of file diff --git a/code/modules/antagonists/horror/horror_chemicals.dm b/code/modules/antagonists/horror/horror_chemicals.dm index d0c6bdc4e04b..50cfdafb7f60 100644 --- a/code/modules/antagonists/horror/horror_chemicals.dm +++ b/code/modules/antagonists/horror/horror_chemicals.dm @@ -1,96 +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 +/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." \ No newline at end of file diff --git a/code/modules/antagonists/horror/horror_datums.dm b/code/modules/antagonists/horror/horror_datums.dm index 3ab9138356a4..6f75827e090a 100644 --- a/code/modules/antagonists/horror/horror_datums.dm +++ b/code/modules/antagonists/horror/horror_datums.dm @@ -1,333 +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.
" - 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, span_bold("[H.real_name] 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 unwanted 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("All attacks work up to 2 tiles away.")) - to_chat(user, span_velvet("Help intent: Usual help function of an arm.")) - to_chat(user, span_velvet("Disarm intent: Whips the tentacle, disarming your opponent.")) - to_chat(user, span_velvet("Grab intent: Instant aggressive grab on an opponent. Can also throw them!")) - to_chat(user, span_velvet("Harm intent: 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("[src] transfers: \"[message]\"") - 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() +//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.
" + 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, span_bold("[H.real_name] 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 unwanted 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("All attacks work up to 2 tiles away.")) + to_chat(user, span_velvet("Help intent: Usual help function of an arm.")) + to_chat(user, span_velvet("Disarm intent: Whips the tentacle, disarming your opponent.")) + to_chat(user, span_velvet("Grab intent: Instant aggressive grab on an opponent. Can also throw them!")) + to_chat(user, span_velvet("Harm intent: 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("[src] transfers: \"[message]\"") + 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() diff --git a/code/modules/antagonists/horror/horror_html.dm b/code/modules/antagonists/horror/horror_html.dm index 4f70c856805d..9cf8882f8015 100644 --- a/code/modules/antagonists/horror/horror_html.dm +++ b/code/modules/antagonists/horror/horror_html.dm @@ -1,102 +1,102 @@ -/mob/living/simple_animal/horror/proc/get_html_template(content) - var/html = {" - - - Horror Chemicals - - - - - - -
-

Horror Chemicals

-
- [content] -
"} +/mob/living/simple_animal/horror/proc/get_html_template(content) + var/html = {" + + + Horror Chemicals + + + + + + +
+

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 index 2eaa8e93af92..97cb19c20572 100644 --- a/code/modules/antagonists/horror/horror_mutate.dm +++ b/code/modules/antagonists/horror/horror_mutate.dm @@ -1,97 +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 [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["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") +// 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 [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["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"]) \ No newline at end of file diff --git a/code/modules/events/horror.dm b/code/modules/events/horror.dm index dd42420f8477..afa1182ff04e 100644 --- a/code/modules/events/horror.dm +++ b/code/modules/events/horror.dm @@ -1,34 +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 +/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 \ No newline at end of file From bd9643014ac5e90ecb898a0fe31b3226717ba61e Mon Sep 17 00:00:00 2001 From: ToasterBiome Date: Sun, 6 Feb 2022 21:46:21 -0600 Subject: [PATCH 2/3] Update hud.dmi --- yogstation/icons/mob/hud.dmi | Bin 18158 -> 18267 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/yogstation/icons/mob/hud.dmi b/yogstation/icons/mob/hud.dmi index f828bb621d865be37a21da68b99f1b5d68c774aa..ee9b1a8e2ea7b2f2c1429da28f58360740dc8026 100644 GIT binary patch delta 15876 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
  • -|2q$|Gae>aFH(bYZ+;nhdiwBO2$ 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 Date: Sat, 12 Feb 2022 21:03:39 -0600 Subject: [PATCH 3/3] Revert "ok whatever" This reverts commit accc11769639767f8b1246cb4a3a2707005a0f99. --- code/_onclick/hud/horror.dm | 36 +- code/modules/antagonists/horror/horror.dm | 1694 ++++++++--------- .../horror/horror_abilities_and_upgrades.dm | 920 ++++----- .../antagonists/horror/horror_chemicals.dm | 190 +- .../antagonists/horror/horror_datums.dm | 666 +++---- .../modules/antagonists/horror/horror_html.dm | 202 +- .../antagonists/horror/horror_mutate.dm | 192 +- code/modules/events/horror.dm | 66 +- 8 files changed, 1983 insertions(+), 1983 deletions(-) diff --git a/code/_onclick/hud/horror.dm b/code/_onclick/hud/horror.dm index a9043b5754a5..876dc00be9ca 100644 --- a/code/_onclick/hud/horror.dm +++ b/code/_onclick/hud/horror.dm @@ -1,18 +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) - +/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) + diff --git a/code/modules/antagonists/horror/horror.dm b/code/modules/antagonists/horror/horror.dm index 5da28bbddbaf..e0653bcbb8e6 100644 --- a/code/modules/antagonists/horror/horror.dm +++ b/code/modules/antagonists/horror/horror.dm @@ -1,848 +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 = "
    [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 - 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("[real_name] slurs: [input]")) - for(var/M in GLOB.dead_mob_list) - if(isobserver(M)) - var/rendered = span_changeling("Horror Communication from [real_name] : [input]") - var/link = FOLLOW_LINK(M, src) - to_chat(M, "[link] [rendered]") - to_chat(src, span_changeling("[real_name] slurs: [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("[real_name] says: [input]")) - - for(var/M in GLOB.dead_mob_list) - if(isobserver(M)) - var/rendered = span_changeling("Horror Communication from [real_name] : [input]") - var/link = FOLLOW_LINK(M, src) - to_chat(M, "[link] [rendered]") - to_chat(src, span_changeling("[real_name] 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, span_changeling("[B.real_name] says: [input]")) - - for(var/M in GLOB.dead_mob_list) - if(isobserver(M)) - var/rendered = span_changeling("Horror Communication from [B.real_name] : [input]") - var/link = FOLLOW_LINK(M, src) - to_chat(M, "[link] [rendered]") - to_chat(src, span_changeling("[B.real_name] says: [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 = "

    Chemicals: [chemicals]

    " - content += "" - - for(var/path in subtypesof(/datum/horror_chem)) - var/datum/horror_chem/chem = path - if(path in horror_chems) - content += "" - - content += "
    [initial(chem.chemname)] ([initial(chem.chemuse)])

    [initial(chem.chem_desc)]

    " - - 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() +/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 = "
    [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 + 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("[real_name] slurs: [input]")) + for(var/M in GLOB.dead_mob_list) + if(isobserver(M)) + var/rendered = span_changeling("Horror Communication from [real_name] : [input]") + var/link = FOLLOW_LINK(M, src) + to_chat(M, "[link] [rendered]") + to_chat(src, span_changeling("[real_name] slurs: [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("[real_name] says: [input]")) + + for(var/M in GLOB.dead_mob_list) + if(isobserver(M)) + var/rendered = span_changeling("Horror Communication from [real_name] : [input]") + var/link = FOLLOW_LINK(M, src) + to_chat(M, "[link] [rendered]") + to_chat(src, span_changeling("[real_name] 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, span_changeling("[B.real_name] says: [input]")) + + for(var/M in GLOB.dead_mob_list) + if(isobserver(M)) + var/rendered = span_changeling("Horror Communication from [B.real_name] : [input]") + var/link = FOLLOW_LINK(M, src) + to_chat(M, "[link] [rendered]") + to_chat(src, span_changeling("[B.real_name] says: [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 = "

    Chemicals: [chemicals]

    " + content += "" + + for(var/path in subtypesof(/datum/horror_chem)) + var/datum/horror_chem/chem = path + if(path in horror_chems) + content += "" + + content += "
    [initial(chem.chemname)] ([initial(chem.chemuse)])

    [initial(chem.chem_desc)]

    " + + 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() \ 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 index ddc238a8d2e1..e5512caf24af 100644 --- a/code/modules/antagonists/horror/horror_abilities_and_upgrades.dm +++ b/code/modules/antagonists/horror/horror_abilities_and_upgrades.dm @@ -1,461 +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." +//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 \ No newline at end of file diff --git a/code/modules/antagonists/horror/horror_chemicals.dm b/code/modules/antagonists/horror/horror_chemicals.dm index 50cfdafb7f60..d0c6bdc4e04b 100644 --- a/code/modules/antagonists/horror/horror_chemicals.dm +++ b/code/modules/antagonists/horror/horror_chemicals.dm @@ -1,96 +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 +/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." \ No newline at end of file diff --git a/code/modules/antagonists/horror/horror_datums.dm b/code/modules/antagonists/horror/horror_datums.dm index 6f75827e090a..3ab9138356a4 100644 --- a/code/modules/antagonists/horror/horror_datums.dm +++ b/code/modules/antagonists/horror/horror_datums.dm @@ -1,333 +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.
    " - 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, span_bold("[H.real_name] 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 unwanted 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("All attacks work up to 2 tiles away.")) - to_chat(user, span_velvet("Help intent: Usual help function of an arm.")) - to_chat(user, span_velvet("Disarm intent: Whips the tentacle, disarming your opponent.")) - to_chat(user, span_velvet("Grab intent: Instant aggressive grab on an opponent. Can also throw them!")) - to_chat(user, span_velvet("Harm intent: 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("[src] transfers: \"[message]\"") - 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() +//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.
    " + 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, span_bold("[H.real_name] 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 unwanted 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("All attacks work up to 2 tiles away.")) + to_chat(user, span_velvet("Help intent: Usual help function of an arm.")) + to_chat(user, span_velvet("Disarm intent: Whips the tentacle, disarming your opponent.")) + to_chat(user, span_velvet("Grab intent: Instant aggressive grab on an opponent. Can also throw them!")) + to_chat(user, span_velvet("Harm intent: 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("[src] transfers: \"[message]\"") + 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() diff --git a/code/modules/antagonists/horror/horror_html.dm b/code/modules/antagonists/horror/horror_html.dm index 9cf8882f8015..4f70c856805d 100644 --- a/code/modules/antagonists/horror/horror_html.dm +++ b/code/modules/antagonists/horror/horror_html.dm @@ -1,102 +1,102 @@ -/mob/living/simple_animal/horror/proc/get_html_template(content) - var/html = {" - - - Horror Chemicals - - - - - - -
    -

    Horror Chemicals

    -
    - [content] -
    "} +/mob/living/simple_animal/horror/proc/get_html_template(content) + var/html = {" + + + Horror Chemicals + + + + + + +
    +

    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 index 97cb19c20572..2eaa8e93af92 100644 --- a/code/modules/antagonists/horror/horror_mutate.dm +++ b/code/modules/antagonists/horror/horror_mutate.dm @@ -1,97 +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 [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["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") +// 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 [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["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"]) \ No newline at end of file diff --git a/code/modules/events/horror.dm b/code/modules/events/horror.dm index afa1182ff04e..dd42420f8477 100644 --- a/code/modules/events/horror.dm +++ b/code/modules/events/horror.dm @@ -1,34 +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 +/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 \ No newline at end of file