diff --git a/_maps/map_files/Yogsmeta/Yogsmeta.dmm b/_maps/map_files/Yogsmeta/Yogsmeta.dmm index bfa969d11e2c..ccaf68c5bc76 100644 --- a/_maps/map_files/Yogsmeta/Yogsmeta.dmm +++ b/_maps/map_files/Yogsmeta/Yogsmeta.dmm @@ -5132,12 +5132,6 @@ }, /turf/open/floor/plasteel, /area/security/main) -"akN" = ( -/obj/effect/turf_decal/tile/neutral{ - dir = 1 - }, -/turf/open/floor/plasteel, -/area/crew_quarters/fitness/recreation) "akQ" = ( /obj/machinery/atmospherics/pipe/simple/supply/hidden/layer2{ dir = 10 @@ -72521,6 +72515,15 @@ }, /turf/open/floor/plasteel, /area/hallway/secondary/exit/departure_lounge) +"lTY" = ( +/obj/effect/turf_decal/tile/neutral{ + dir = 1 + }, +/obj/structure/cable/yellow{ + icon_state = "4-8" + }, +/turf/open/floor/plasteel, +/area/crew_quarters/fitness/recreation) "lUl" = ( /obj/effect/turf_decal/tile/blue{ dir = 1 @@ -119386,7 +119389,7 @@ jPM oSR qca vLb -akN +lTY ahg ahg ahg 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/datums/components/anti_magic.dm b/code/datums/components/anti_magic.dm index 86724591adc9..c573b38837d9 100644 --- a/code/datums/components/anti_magic.dm +++ b/code/datums/components/anti_magic.dm @@ -7,9 +7,8 @@ var/blocks_self = TRUE var/datum/callback/reaction var/datum/callback/expire - var/special_role = 0 -/datum/component/anti_magic/Initialize(_magic = FALSE, _holy = FALSE, _psychic = FALSE, _allowed_slots, _charges, _blocks_self = TRUE, datum/callback/_reaction, datum/callback/_expire, _special_role = 0) +/datum/component/anti_magic/Initialize(_magic = FALSE, _holy = FALSE, _psychic = FALSE, _allowed_slots, _charges, _blocks_self = TRUE, datum/callback/_reaction, datum/callback/_expire) if(isitem(parent)) RegisterSignal(parent, COMSIG_ITEM_EQUIPPED, .proc/on_equip) RegisterSignal(parent, COMSIG_ITEM_DROPPED, .proc/on_drop) @@ -28,14 +27,12 @@ blocks_self = _blocks_self reaction = _reaction expire = _expire - special_role = _special_role /datum/component/anti_magic/proc/on_equip(datum/source, mob/equipper, slot) if(!CHECK_BITFIELD(allowed_slots, slotdefine2slotbit(slot))) //Check that the slot is valid for antimagic UnregisterSignal(equipper, COMSIG_MOB_RECEIVE_MAGIC) return - if(equipper.mind?.holy_role >= special_role) //CONVERT OR DIE - RegisterSignal(equipper, COMSIG_MOB_RECEIVE_MAGIC, .proc/protect, TRUE) + RegisterSignal(equipper, COMSIG_MOB_RECEIVE_MAGIC, .proc/protect, TRUE) /datum/component/anti_magic/proc/on_drop(datum/source, mob/user) UnregisterSignal(user, COMSIG_MOB_RECEIVE_MAGIC) diff --git a/code/datums/components/footstep.dm b/code/datums/components/footstep.dm index 9d842625edc3..e5dfbe9d7759 100644 --- a/code/datums/components/footstep.dm +++ b/code/datums/components/footstep.dm @@ -88,13 +88,13 @@ var/mob/living/carbon/human/H = LM var/feetCover = (H.wear_suit && (H.wear_suit.body_parts_covered & FEET)) || (H.w_uniform && (H.w_uniform.body_parts_covered & FEET)) - if(H.shoes || feetCover) //are we wearing shoes + if((H.shoes && !istype(H.shoes, /obj/item/clothing/shoes/xeno_wraps)) || feetCover) playsound(T, pick(GLOB.footstep[T.footstep][1]), GLOB.footstep[T.footstep][2] * v, TRUE, GLOB.footstep[T.footstep][3] + e) - if((!H.shoes && !feetCover)) //are we NOT wearing shoes + else if(H.dna.species.special_step_sounds) playsound(T, pick(H.dna.species.special_step_sounds), 50, TRUE) else diff --git a/code/game/objects/items/kitchen.dm b/code/game/objects/items/kitchen.dm index 9b89a705d1b0..5504f2270fad 100644 --- a/code/game/objects/items/kitchen.dm +++ b/code/game/objects/items/kitchen.dm @@ -224,7 +224,7 @@ // Shank - Makeshift weapon that can embed on throw /obj/item/kitchen/knife/shank - name = "Shank" + name = "shank" desc = "A crude knife fashioned by securing a glass shard and a rod together with cables, and welding them together." icon = 'icons/obj/items_and_weapons.dmi' icon_state = "shank" @@ -240,6 +240,7 @@ weapon_stats = list(SWING_SPEED = 0.8, ENCUMBRANCE = 0, ENCUMBRANCE_TIME = 0, REACH = 1, DAMAGE_LOW = 5, DAMAGE_HIGH = 7) embedding = list("embedded_pain_multiplier" = 3, "embed_chance" = 20, "embedded_fall_chance" = 10) // Incentive to disengage/stop chasing when stuck attack_verb = list("stuck", "shanked", "stabbed", "shivved") + materials = list(/datum/material/iron=1150, /datum/material/glass=2075) /obj/item/kitchen/rollingpin name = "rolling pin" diff --git a/code/game/objects/items/storage/book.dm b/code/game/objects/items/storage/book.dm index b72fe4aaac8e..f31025e42764 100644 --- a/code/game/objects/items/storage/book.dm +++ b/code/game/objects/items/storage/book.dm @@ -43,7 +43,7 @@ GLOBAL_LIST_INIT(bibleitemstates, list("bible", "koran", "scrapbook", "burning", /obj/item/storage/book/bible/Initialize() . = ..() - AddComponent(/datum/component/anti_magic, FALSE, TRUE, FALSE, null, null, TRUE, null, null, HOLY_ROLE_PRIEST) + AddComponent(/datum/component/anti_magic, FALSE, TRUE) /obj/item/storage/book/bible/suicide_act(mob/user) user.visible_message(span_suicide("[user] is offering [user.p_them()]self to [deity_name]! It looks like [user.p_theyre()] trying to commit suicide!")) diff --git a/code/modules/antagonists/highlander/highlander.dm b/code/modules/antagonists/highlander/highlander.dm index d52407c81fac..20d586054de3 100644 --- a/code/modules/antagonists/highlander/highlander.dm +++ b/code/modules/antagonists/highlander/highlander.dm @@ -47,11 +47,13 @@ qdel(I) if(!isplasmaman(H)) //no killing plasmies H.equip_to_slot_or_del(new /obj/item/clothing/under/kilt/highlander(H), SLOT_W_UNIFORM) + H.equip_to_slot_or_del(new /obj/item/clothing/head/beret/highlander(H), SLOT_HEAD) else H.equip_to_slot_or_del(new /obj/item/clothing/under/plasmaman(H), SLOT_W_UNIFORM) - H.equip_to_slot_or_del(new /obj/item/tank/internals/plasmaman(H), SLOT_W_UNIFORM) + H.equip_to_slot_or_del(new /obj/item/tank/internals/plasmaman/belt/full(H), SLOT_BELT) + H.equip_to_slot_or_del(new /obj/item/clothing/head/helmet/space/plasmaman(H), SLOT_HEAD) + H.equip_to_slot_or_del(new /obj/item/clothing/mask/breath(H), SLOT_WEAR_MASK) H.equip_to_slot_or_del(new /obj/item/radio/headset/heads/captain(H), SLOT_EARS) - H.equip_to_slot_or_del(new /obj/item/clothing/head/beret/highlander(H), SLOT_HEAD) H.equip_to_slot_or_del(new /obj/item/clothing/shoes/combat(H), SLOT_SHOES) H.equip_to_slot_or_del(new /obj/item/pinpointer/nuke(H), SLOT_L_STORE) //Yogs Start: Pacifists want to play too diff --git a/code/modules/antagonists/horror/horror.dm b/code/modules/antagonists/horror/horror.dm index e0653bcbb8e6..9663c48abb60 100644 --- a/code/modules/antagonists/horror/horror.dm +++ b/code/modules/antagonists/horror/horror.dm @@ -1,848 +1,836 @@ -/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 +/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, 4 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, 30 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 + 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, 300, target = victim, extra_checks = CALLBACK(src, .proc/is_leaving), stayStill = FALSE)) //Enough time to do quick surgery + release_host() + +/mob/living/simple_animal/horror/proc/release_host() + if(!victim || QDELETED(victim) || QDELETED(src) || controlling) + return + + if(!can_use_ability()) + return + 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..32220b11d088 100644 --- a/code/modules/antagonists/horror/horror_abilities_and_upgrades.dm +++ b/code/modules/antagonists/horror/horror_abilities_and_upgrades.dm @@ -1,461 +1,440 @@ -//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 +//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 = 30 SECONDS + var/silent + if(B.victim.pulling != C) + silent = TRUE + else + switch(B.victim.grab_state) + if(GRAB_PASSIVE) + delay = 20 SECONDS + if(GRAB_AGGRESSIVE) + delay = 10 SECONDS + if(GRAB_NECK) + delay = 5 SECONDS + else + delay = 3 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 + +//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) + +//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/clothing/suits/reactive_armour.dm b/code/modules/clothing/suits/reactive_armour.dm index d1cba3cb7878..8103092fa58f 100644 --- a/code/modules/clothing/suits/reactive_armour.dm +++ b/code/modules/clothing/suits/reactive_armour.dm @@ -159,6 +159,7 @@ name = "reactive tesla armor" desc = "An experimental suit of armor with sensitive detectors hooked up to a huge capacitor grid, with emitters strutting out of it. Zap." siemens_coefficient = -1 + reactivearmor_cooldown_duration = 3 SECONDS var/tesla_power = 25000 var/tesla_range = 20 var/tesla_flags = TESLA_MOB_DAMAGE | TESLA_OBJ_DAMAGE @@ -195,6 +196,7 @@ /obj/item/clothing/suit/armor/reactive/repulse name = "reactive repulse armor" desc = "An experimental suit of armor that violently throws back attackers." + reactivearmor_cooldown_duration = 5 SECONDS var/repulse_force = MOVE_FORCE_EXTREMELY_STRONG /obj/item/clothing/suit/armor/reactive/repulse/hit_reaction(mob/living/carbon/human/owner, atom/movable/hitby, attack_text = "the attack", final_block_chance = 0, damage = 0, attack_type = MELEE_ATTACK) diff --git a/code/modules/clothing/under/_under.dm b/code/modules/clothing/under/_under.dm index 692b91214274..6fa8dd46db7f 100644 --- a/code/modules/clothing/under/_under.dm +++ b/code/modules/clothing/under/_under.dm @@ -164,6 +164,9 @@ to_chat(user, span_notice("You detach [A] from [src].")) else to_chat(user, span_notice("You detach [A] from [src] and it falls on the floor.")) + var/turf/T = get_turf(src) + if(!T) + T = get_turf(user) A.forceMove(get_turf(src)) if(ishuman(loc)) 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 diff --git a/code/modules/mining/equipment/resonator.dm b/code/modules/mining/equipment/resonator.dm index 4d8fe05e3aa0..89c86e11d527 100644 --- a/code/modules/mining/equipment/resonator.dm +++ b/code/modules/mining/equipment/resonator.dm @@ -71,7 +71,6 @@ transform = matrix()*0.75 animate(src, transform = matrix()*1.5, time = duration) deltimer(timerid) - addtimer(CALLBACK(src, .proc/replicate, get_turf(src), creator, duration), duration, TIMER_STOPPABLE)//yogs: adds field replication timerid = addtimer(CALLBACK(src, .proc/burst), duration, TIMER_STOPPABLE) /obj/effect/temp_visual/resonance/Destroy() @@ -97,6 +96,7 @@ new /obj/effect/temp_visual/resonance_crush(T) if(ismineralturf(T)) var/turf/closed/mineral/M = T + replicate(M) M.gets_drilled(creator) check_pressure(T) playsound(T,'sound/weapons/resonator_blast.ogg',50,1) @@ -117,10 +117,9 @@ transform = matrix()*1.5 animate(src, transform = matrix()*0.1, alpha = 50, time = 4) -/obj/effect/temp_visual/resonance/proc/replicate(turf/closed/mineral/M, creator, timetoburst) //yogs start: adds replication to resonator fields - if(!istype(M)) +/obj/effect/temp_visual/resonance/proc/replicate(turf/closed/mineral/M) //yogs start: adds replication to resonator fields + if(!istype(M) || !M.mineralType) // so we don't end up in the ultimate chain reaction return for(var/turf/closed/mineral/T in orange(1, M)) - if(istype(T)) - if(M.mineralType == T.mineralType && M.mineralType != null) // so we don't end up in the ultimate chain reaction - new /obj/effect/temp_visual/resonance(T, creator, null, timetoburst) //yogs end + if(istype(T) && M.mineralType == T.mineralType) + new /obj/effect/temp_visual/resonance(T, creator, null, duration) //yogs end diff --git a/code/modules/modular_computers/computers/item/computer.dm b/code/modules/modular_computers/computers/item/computer.dm index 0b629d2224cc..1fe3fa1333b7 100644 --- a/code/modules/modular_computers/computers/item/computer.dm +++ b/code/modules/modular_computers/computers/item/computer.dm @@ -221,6 +221,9 @@ . += get_modular_computer_parts_examine(user) /obj/item/modular_computer/update_icon() + if(!physical) + return + SSvis_overlays.remove_vis_overlay(physical, physical.managed_vis_overlays) var/program_overlay = "" var/is_broken = obj_integrity <= integrity_failure @@ -560,4 +563,3 @@ active_program = program program.alert_pending = FALSE enabled = TRUE - update_icon() diff --git a/code/modules/modular_computers/computers/item/computer_ui.dm b/code/modules/modular_computers/computers/item/computer_ui.dm index 37e0e1dca303..d6297f7c192c 100644 --- a/code/modules/modular_computers/computers/item/computer_ui.dm +++ b/code/modules/modular_computers/computers/item/computer_ui.dm @@ -2,25 +2,21 @@ . = ..() ui_interact(user) -/obj/item/modular_computer/proc/can_show_ui(mob/user, datum/tgui/ui) +/obj/item/modular_computer/proc/can_show_ui(mob/user) if(!enabled) - if(ui) - ui.close() return FALSE if(!use_power()) - if(ui) - ui.close() return FALSE // Robots don't really need to see the screen, their wireless connection works as long as computer is on. if(!screen_on && !issilicon(user)) - if(ui) - ui.close() return FALSE return TRUE // Operates TGUI /obj/item/modular_computer/ui_interact(mob/user, datum/tgui/ui) - if (!can_show_ui(user, ui)) + if (!can_show_ui(user)) + if(ui) + ui.close() return // If we have an active program switch to it now. if(active_program) diff --git a/code/modules/modular_computers/computers/item/laptop.dm b/code/modules/modular_computers/computers/item/laptop.dm index 7164922b0386..69e9353dea82 100644 --- a/code/modules/modular_computers/computers/item/laptop.dm +++ b/code/modules/modular_computers/computers/item/laptop.dm @@ -16,7 +16,7 @@ // No running around with open laptops in hands. item_flags = SLOWS_WHILE_IN_HAND - screen_on = 0 // Starts closed + screen_on = FALSE // Starts closed var/start_open = TRUE // unless this var is set to 1 var/icon_state_closed = "laptop-closed" var/w_class_open = WEIGHT_CLASS_BULKY @@ -95,10 +95,15 @@ to_chat(user, span_notice("You close \the [src].")) slowdown = initial(slowdown) w_class = initial(w_class) + icon_state = icon_state_closed else to_chat(user, span_notice("You open \the [src].")) slowdown = slowdown_open w_class = w_class_open + if(enabled) + icon_state = icon_state_powered + else + icon_state = icon_state_unpowered screen_on = !screen_on update_icon() diff --git a/code/modules/modular_computers/file_system/program.dm b/code/modules/modular_computers/file_system/program.dm index a5d615d7c21d..b435e2834f6e 100644 --- a/code/modules/modular_computers/file_system/program.dm +++ b/code/modules/modular_computers/file_system/program.dm @@ -197,7 +197,9 @@ /datum/computer_file/program/ui_interact(mob/user, datum/tgui/ui) ui = SStgui.try_update_ui(user, src, ui) - if (!computer.can_show_ui(user, ui)) + if (!computer.can_show_ui(user)) + if(ui) + ui.close() return if(!ui && tgui_id) ui = new(user, src, tgui_id, filedesc) @@ -214,10 +216,12 @@ if(computer) switch(action) if("PC_exit") + computer.play_interact_sound() computer.kill_program() ui.close() return TRUE if("PC_shutdown") + computer.play_interact_sound() computer.shutdown_computer() ui.close() return TRUE @@ -225,7 +229,7 @@ var/mob/user = usr if(!computer.active_program || !computer.all_components[MC_CPU]) return - + computer.play_interact_sound() computer.idle_threads.Add(computer.active_program) program_state = PROGRAM_STATE_BACKGROUND // Should close any existing UIs @@ -246,4 +250,6 @@ /datum/computer_file/program/ui_status(mob/user) if(program_state != PROGRAM_STATE_ACTIVE) // Our program was closed. Close the ui if it exists. return UI_CLOSE + if(!computer.can_show_ui(user)) + return UI_CLOSE return ..() diff --git a/code/modules/modular_computers/file_system/programs/crew_monitor.dm b/code/modules/modular_computers/file_system/programs/crew_monitor.dm index 03a116890ab3..a9c1ac91af9d 100644 --- a/code/modules/modular_computers/file_system/programs/crew_monitor.dm +++ b/code/modules/modular_computers/file_system/programs/crew_monitor.dm @@ -38,7 +38,7 @@ var/turf/T = get_turf(computer) z = T.z var/list/death_list = GLOB.crewmonitor.death_list?["[z]"] - if(death_list.len > 0) + if(death_list && death_list.len > 0) alarm = TRUE else alarm = FALSE diff --git a/code/modules/pool/pool.dm b/code/modules/pool/pool.dm index b26eaa7fb4dd..fcbf404c4326 100644 --- a/code/modules/pool/pool.dm +++ b/code/modules/pool/pool.dm @@ -133,6 +133,8 @@ Place a pool filter somewhere in the pool if you want people to be able to modif var/obj/item/clothing/CS = F.wear_suit if (CS.clothing_flags & THICKMATERIAL) zap -- + if(zap > 0) + zap = 3 - zap // 1 is higher severity emp than 2 if(zap > 0) user.emp_act(zap) user.emote("scream") //Chad coders use M.say("*scream") @@ -201,6 +203,7 @@ GLOBAL_LIST_EMPTY(pool_filters) var/desired_temperature = 300 //Room temperature var/current_temperature = 300 //current temp var/preset_reagent_type = null //Set this if you want your pump to start filled with a given reagent. SKEWIUM POOL SKEWIUM POOL! + var/temp_rate = 0.5 /obj/machinery/pool_filter/examine(mob/user) . = ..() @@ -241,9 +244,13 @@ GLOBAL_LIST_EMPTY(pool_filters) if(!LAZYLEN(pool) || !is_operational()) return //No use having one of these processing for no reason is there? use_power(idle_power_usage) - var/delta = (current_temperature > desired_temperature) ? -0.5 : 0.5 - current_temperature += delta - current_temperature = clamp(current_temperature, T0C, desired_temperature) + if (current_temperature > desired_temperature) + current_temperature -= temp_rate + current_temperature = max(desired_temperature, current_temperature) + else if (current_temperature < desired_temperature) + current_temperature += temp_rate + current_temperature = min(desired_temperature, current_temperature) + var/trans_amount = reagents.total_volume / pool.len //Split up the reagents equally. for(var/turf/open/indestructible/sound/pool/water as() in pool) if(reagents.reagent_list.len) @@ -267,12 +274,13 @@ GLOBAL_LIST_EMPTY(pool_filters) var/mob/living/carbon/C = M if(current_temperature <= 283.5) //Colder than 10 degrees is going to make you very cold if(iscarbon(M)) - C.adjust_bodytemperature(-80, 80) + C.adjust_bodytemperature(-80, current_temperature) to_chat(M, "The water is freezing cold!") else if(current_temperature >= 308.5) //Hotter than 35 celsius is going to make you burn up if(iscarbon(M)) - C.adjust_bodytemperature(35, 0, 500) - M.adjustFireLoss(5) + C.adjust_bodytemperature(35, 0, current_temperature) + if(!HAS_TRAIT(C, TRAIT_RESISTHEAT)) + C.adjustFireLoss(5) to_chat(M, "The water is searing hot!") /obj/structure/pool_ladder/attack_hand(mob/user) diff --git a/code/modules/projectiles/gun.dm b/code/modules/projectiles/gun.dm index ef88766149be..8540a6481442 100644 --- a/code/modules/projectiles/gun.dm +++ b/code/modules/projectiles/gun.dm @@ -498,7 +498,7 @@ set_light(gun_light.brightness_on) else set_light(0) - cut_overlays(flashlight_overlay, TRUE) + cut_overlay(flashlight_overlay, TRUE) var/state = "flight[gun_light.on? "_on":""]" //Generic state. if(gun_light.icon_state in icon_states('icons/obj/guns/flashlights.dmi')) //Snowflake state? state = gun_light.icon_state @@ -508,7 +508,7 @@ add_overlay(flashlight_overlay, TRUE) else set_light(0) - cut_overlays(flashlight_overlay, TRUE) + cut_overlay(flashlight_overlay, TRUE) flashlight_overlay = null update_icon(TRUE) for(var/X in actions) diff --git a/code/modules/surgery/organs/autosurgeon.dm b/code/modules/surgery/organs/autosurgeon.dm index 8cd6227f8d45..0572dd6243fc 100644 --- a/code/modules/surgery/organs/autosurgeon.dm +++ b/code/modules/surgery/organs/autosurgeon.dm @@ -96,13 +96,17 @@ starting_organ = /obj/item/organ/cyberimp/chest/reviver /obj/item/autosurgeon/medibeam + uses = 1 starting_organ = /obj/item/organ/cyberimp/arm/medibeam /obj/item/autosurgeon/organ/syndicate/syndie_mantis + uses = 1 starting_organ = /obj/item/organ/cyberimp/arm/syndie_mantis /obj/item/autosurgeon/organ/syndicate/syndie_mantis/l + uses = 1 starting_organ = /obj/item/organ/cyberimp/arm/syndie_mantis/l /obj/item/autosurgeon/plasmavessel //Yogs Start: Just an autosurgeon with a plasma vessel in it, used in /obj/item/storage/box/syndie_kit/xeno_organ_kit + uses = 3 starting_organ = /obj/item/organ/alien/plasmavessel //Yogs End diff --git a/code/modules/surgery/organs/heart.dm b/code/modules/surgery/organs/heart.dm index 1867f7a7c95e..2cceef0a6848 100644 --- a/code/modules/surgery/organs/heart.dm +++ b/code/modules/surgery/organs/heart.dm @@ -103,7 +103,7 @@ /obj/item/organ/heart/cursed name = "cursed heart" desc = "A heart that, when inserted, will force you to pump it manually." - icon_state = "cursedheart-off" + icon_state = "cursedheart" icon_base = "cursedheart" decay_factor = 0 actions_types = list(/datum/action/item_action/organ_action/cursed_heart) diff --git a/code/modules/surgery/organs/lungs.dm b/code/modules/surgery/organs/lungs.dm index fbe91a0bcc1c..39fb0a39c776 100644 --- a/code/modules/surgery/organs/lungs.dm +++ b/code/modules/surgery/organs/lungs.dm @@ -498,7 +498,7 @@ /obj/item/organ/lungs/ghetto name = "oxygen tanks welded to a modular reciever" desc = "A pair of oxygen tanks which have been attached to a modular (oxygen) receiver. They are incapable of supplying air, but can work as a replacement for lungs." - icon_state = "lungs_g" + icon_state = "lungs-g" organ_efficiency = 0.5 organ_flags = ORGAN_SYNTHETIC //the moment i understood the weakness of flesh, it disgusted me, and i yearned for the certainty, of steel diff --git a/config/admins.txt b/config/admins.txt index 536c0fa56db9..ac5ae5d09f43 100644 --- a/config/admins.txt +++ b/config/admins.txt @@ -11,7 +11,7 @@ AshCorr = Host Xantam = Host -Ynot01 = Council Member +tehladyk = Council Member MenacingManatee = Council Member Num1bamf = Council Member adamsogm = Council Member diff --git a/html/changelog.html b/html/changelog.html index 30d8b6388bc5..c9576b4a1e0f 100644 --- a/html/changelog.html +++ b/html/changelog.html @@ -58,6 +58,86 @@ -->
+

10 March 2022

+

ToasterBiome updated:

+ +

adamsong updated:

+ +

cuackles updated:

+ + +

09 March 2022

+

SomeguyManperson updated:

+ + +

07 March 2022

+

ChesterTheCheesy updated:

+ +

Mqiib updated:

+ +

SomeguyManperson updated:

+ +

ToasterBiome updated:

+ +

adamsong updated:

+ +

nmajask updated:

+ +

tattax updated:

+ +

ynot01 updated:

+ + +

06 March 2022

+

Mqiib updated:

+ +

cuackles updated:

+ +

tattax updated:

+ +

03 March 2022

ToasterBiome updated:

GoonStation 13 Development Team diff --git a/html/changelogs/.all_changelog.yml b/html/changelogs/.all_changelog.yml index 498cde35ea64..4f8f82cd4ecb 100644 --- a/html/changelogs/.all_changelog.yml +++ b/html/changelogs/.all_changelog.yml @@ -30235,3 +30235,50 @@ DO NOT EDIT THIS FILE BY HAND! AUTOMATICALLY GENERATED BY ss13_genchangelog.py. - rscadd: Adds the ability for external tooling to send server messages maxion12345: - rscadd: AI's can now research RGB lightings for their datacores. +2022-03-06: + Mqiib: + - bugfix: Fixes the icon state for ghetto lungs + cuackles: + - imageadd: added t45b new sprite + - imagedel: deleted t45b old sprite + tattax: + - rscadd: Adds all four states to the shank sprite in-hands. +2022-03-07: + ChesterTheCheesy: + - tweak: gives the rest of reactive armors respectible cooldowns + Mqiib: + - rscdel: Removed invisible escape and funny aoe knockout ability upgrades + - tweak: Massively increased escape time and soul eating time + - tweak: Moderate increases to direct host transfer time + - tweak: Increases time to crawl into someone's ear + SomeguyManperson: + - tweak: the bible's holy antimagic effect now works on normal people again + ToasterBiome: + - bugfix: cursed heart has correct sprite now + adamsong: + - bugfix: fixed pool emp effect being the wrong severity + - bugfix: fixed pools temperature instantly lowering + - bugfix: fixed pools damaging people with heat resistance + - bugfix: fixed pools violating the laws of thermodynamics + nmajask: + - bugfix: modular laptops now use the correct icons when opened + - bugfix: you can no longer use programs on a modular laptop with its lid is shut + - bugfix: fixed some buttons not having interaction sounds + - bugfix: fixed a missing wire on meta + tattax: + - bugfix: plasmaman now spawn with proper clothing on highlander event + ynot01: + - tweak: All traitor-available autosurgeons are now corrected to be one-time-use +2022-03-09: + SomeguyManperson: + - tweak: resonator field replication now works if you manually detonate the resonatoe + field rather than only working if the field detonates naturally +2022-03-10: + ToasterBiome: + - bugfix: flashlights on guns properly clear their overlay instead of all overlays + adamsong: + - tweak: Veil now works on light prisms + - bugfix: fixed runtime in crew_monitor.dm + - bugfix: fixed runtime in remove_accessory + cuackles: + - tweak: tweaked a few things diff --git a/icons/mob/head.dmi b/icons/mob/head.dmi index 56362a1ee8d1..d62987e6b0bf 100644 Binary files a/icons/mob/head.dmi and b/icons/mob/head.dmi differ diff --git a/icons/mob/inhands/weapons/swords_lefthand.dmi b/icons/mob/inhands/weapons/swords_lefthand.dmi index cbc41eb6ab57..67c021241363 100644 Binary files a/icons/mob/inhands/weapons/swords_lefthand.dmi and b/icons/mob/inhands/weapons/swords_lefthand.dmi differ diff --git a/icons/mob/inhands/weapons/swords_righthand.dmi b/icons/mob/inhands/weapons/swords_righthand.dmi index e4e6d1db1081..c262811a7533 100644 Binary files a/icons/mob/inhands/weapons/swords_righthand.dmi and b/icons/mob/inhands/weapons/swords_righthand.dmi differ diff --git a/icons/mob/suit.dmi b/icons/mob/suit.dmi index fbe889de5ca9..f409f62a6900 100644 Binary files a/icons/mob/suit.dmi and b/icons/mob/suit.dmi differ diff --git a/icons/obj/clothing/hats.dmi b/icons/obj/clothing/hats.dmi index 3798b1ad7a1c..86e3fddb2854 100644 Binary files a/icons/obj/clothing/hats.dmi and b/icons/obj/clothing/hats.dmi differ diff --git a/icons/obj/clothing/suits.dmi b/icons/obj/clothing/suits.dmi index 46d786d8e2fa..39b1807b96d8 100644 Binary files a/icons/obj/clothing/suits.dmi and b/icons/obj/clothing/suits.dmi differ diff --git a/yogstation/code/modules/antagonists/shadowling/shadowling_abilities.dm b/yogstation/code/modules/antagonists/shadowling/shadowling_abilities.dm index 2cc6eb19dcc3..16dbfa3c3ede 100644 --- a/yogstation/code/modules/antagonists/shadowling/shadowling_abilities.dm +++ b/yogstation/code/modules/antagonists/shadowling/shadowling_abilities.dm @@ -175,6 +175,8 @@ if(istype(LO, /obj/machinery/power/floodlight)) var/obj/machinery/power/floodlight/FL = LO FL.change_setting(2) // Set floodlight to lowest setting + if(istype(LO, /obj/structure/light_prism)) + qdel(LO) for(var/obj/structure/glowshroom/G in orange(7, user)) //High radius because glowshroom spam wrecks shadowlings if(!istype(G, /obj/structure/glowshroom/shadowshroom))