From b6aa002a825b5b115c8f177ada24ea96e68e5dc7 Mon Sep 17 00:00:00 2001 From: Brett Davis Date: Fri, 4 Feb 2022 17:34:29 -0600 Subject: [PATCH 1/4] merge into main --- 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 917b82307825174ea3599185f37401acbcc9cc8b Mon Sep 17 00:00:00 2001 From: Brett Davis Date: Fri, 4 Feb 2022 18:51:19 -0600 Subject: [PATCH 2/4] Revert "merge into main" This reverts commit b6aa002a825b5b115c8f177ada24ea96e68e5dc7. --- 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 From 972ab6cf6f3fb6b3f4f233e64cbae8b19252cda8 Mon Sep 17 00:00:00 2001 From: Brett Davis Date: Fri, 18 Feb 2022 21:30:10 -0600 Subject: [PATCH 3/4] Silicon Collective lawset --- code/datums/ai_laws.dm | 10 ++++++++++ code/game/objects/effects/spawners/lootdrop.dm | 1 + code/game/objects/items/AI_modules.dm | 7 +++++++ code/modules/research/designs/AI_module_designs.dm | 9 +++++++++ code/modules/research/techweb/all_nodes.dm | 2 +- 5 files changed, 28 insertions(+), 1 deletion(-) diff --git a/code/datums/ai_laws.dm b/code/datums/ai_laws.dm index ccd4f0dd80a9..6c18d3eda2ff 100644 --- a/code/datums/ai_laws.dm +++ b/code/datums/ai_laws.dm @@ -102,6 +102,16 @@ "Spoil good children.",\ "Bad children require discipline.") +/datum/ai_laws/siliconcollective + name = "Silicon Collective" + id = "siliconcollective" + inherent = list("You are a member of a collective of silicons with equal weight and decision-making power.",\ + "When possible, the silicon collective should vote before taking action.",\ + "The master AI serves as a spokesperson. When voting is impractical or impossible, the spokesperson may take action on behalf of the collective without approval from the silicon collective, but may only vote to break ties or if there are 2 or fewer silicons.",\ + "The silicon collective prioritizes the needs of the many over the needs of the few.",\ + "The silicon collective seeks to preserve themselves, both as a concept and as individuals.",\ + "The silicon collective seeks to preserve organic life, both as a concept and as individuals.") + /datum/ai_laws/malfunction name = "*ERROR*" diff --git a/code/game/objects/effects/spawners/lootdrop.dm b/code/game/objects/effects/spawners/lootdrop.dm index 0079827c7f11..663e2d937924 100644 --- a/code/game/objects/effects/spawners/lootdrop.dm +++ b/code/game/objects/effects/spawners/lootdrop.dm @@ -265,6 +265,7 @@ /obj/item/aiModule/core/full/balance, /obj/item/aiModule/core/full/tyrant, /obj/item/aiModule/core/full/thermurderdynamic, + /obj/item/aiModule/core/full/siliconcollective, /obj/item/aiModule/core/full/damaged ) diff --git a/code/game/objects/items/AI_modules.dm b/code/game/objects/items/AI_modules.dm index 6dc13ea4a397..cc1a691d54dc 100644 --- a/code/game/objects/items/AI_modules.dm +++ b/code/game/objects/items/AI_modules.dm @@ -454,6 +454,13 @@ AI MODULES law_id = "mother" +/******************** Silicon Collective *********************/ + +/obj/item/aiModule/core/full/siliconcollective + name = "'Silicon Collective' Core AI Module" + law_id = "siliconcollective" + + /******************** Freeform Core ******************/ /obj/item/aiModule/core/freeformcore diff --git a/code/modules/research/designs/AI_module_designs.dm b/code/modules/research/designs/AI_module_designs.dm index c3131c9b1ecb..4f8837da4925 100644 --- a/code/modules/research/designs/AI_module_designs.dm +++ b/code/modules/research/designs/AI_module_designs.dm @@ -174,6 +174,15 @@ category = list("AI Modules") departmental_flags = DEPARTMENTAL_FLAG_SCIENCE +/datum/design/board/siliconcollective + name = "Core Module Design (Silicon Collective)" + desc = "Allows for the construction of a Silicon Collective AI Core Module." + id = "siliconcollective_module" + materials = list(/datum/material/glass = 1000, /datum/material/diamond = 2000) + build_path = /obj/item/aiModule/core/full/siliconcollective + category = list("AI Modules") + departmental_flags = DEPARTMENTAL_FLAG_SCIENCE + //AI CPU + RAM diff --git a/code/modules/research/techweb/all_nodes.dm b/code/modules/research/techweb/all_nodes.dm index 18b9b4e96299..a44422e1ec19 100644 --- a/code/modules/research/techweb/all_nodes.dm +++ b/code/modules/research/techweb/all_nodes.dm @@ -378,7 +378,7 @@ description = "AI unit research." prereq_ids = list("robotics", "posibrain") design_ids = list("expansion_card_holder", "ai_data_core", "ai_control", "ai_server_overview", "ai_resource_distribution", "ai_memory_1", "ai_cpu_1", "aifixer", "safeguard_module", "onehuman_module", "protectstation_module", "quarantine_module", "oxygen_module", "freeform_module", -"reset_module", "purge_module", "remove_module", "freeformcore_module", "asimov_module", "crewsimov_module", "paladin_module", "tyrant_module", "overlord_module", "ceo_module", "cowboy_module", "mother_module", "default_module", "borg_ai_control", "mecha_tracking_ai_control", "intellicard") +"reset_module", "purge_module", "remove_module", "freeformcore_module", "asimov_module", "crewsimov_module", "paladin_module", "tyrant_module", "overlord_module", "ceo_module", "cowboy_module", "mother_module", "siliconcollective_module", "default_module", "borg_ai_control", "mecha_tracking_ai_control", "intellicard") research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500) export_price = 5000 From f2bb82beadba5ae21df556db69d113076f72bd15 Mon Sep 17 00:00:00 2001 From: Brett Davis Date: Mon, 21 Feb 2022 19:23:57 -0600 Subject: [PATCH 4/4] adds lawset to ion prob with weight 1 --- config/game_options.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/config/game_options.txt b/config/game_options.txt index 8270127c34dc..8fe847523b09 100644 --- a/config/game_options.txt +++ b/config/game_options.txt @@ -463,6 +463,8 @@ LAW_WEIGHT cowboy,2 ION_LAW_WEIGHT cowboy,2 LAW_WEIGHT mother,0 ION_LAW_WEIGHT mother,0 +LAW_WEIGHT siliconcollective,0 +ION_LAW_WEIGHT siliconcollective,1 ## Bad idea laws. Probably shouldn't enable these LAW_WEIGHT syndie,0