Eldritch horror - the final PR (#13033)
* Update is_helpers.dm * Update role_preferences.dm * Update traits.dm * Update names.dm * Update traumas.dm * Update miscellaneous.dm * Update scanners.dm * Update sql_ban_system.dm * Add files via upload * Update headcrab.dm * Update panacea.dm * Create e * Delete e * adds horror * Add files via upload * Update suicide.dm * Add files via upload * Update brain_item.dm * Update death.dm * Update human.dm * Update transform_procs.dm * Update gun.dm * Update organ_manipulation.dm * Update uplink_items.dm * Add files via upload * Add files via upload * Add files via upload * Add files via upload * Add files via upload * Add files via upload * Add files via upload * Create horror.txt * Add files via upload * Update yogstation.dme * adds horror-relatated tips * fixes a typo * Update code/_onclick/hud/horror.dm Co-authored-by: TheGamerdk <5618080+TheGamerdk@users.noreply.github.com> * Update code/game/objects/effects/temporary_visuals/miscellaneous.dm Co-authored-by: TheGamerdk <5618080+TheGamerdk@users.noreply.github.com> * Update code/game/objects/effects/temporary_visuals/miscellaneous.dm Co-authored-by: TheGamerdk <5618080+TheGamerdk@users.noreply.github.com> * Update code/modules/antagonists/horror/horror.dm Co-authored-by: TheGamerdk <5618080+TheGamerdk@users.noreply.github.com> * Update code/modules/antagonists/horror/horror.dm Co-authored-by: TheGamerdk <5618080+TheGamerdk@users.noreply.github.com> * Update code/modules/antagonists/horror/horror.dm Co-authored-by: TheGamerdk <5618080+TheGamerdk@users.noreply.github.com> * Update code/modules/antagonists/horror/horror.dm Co-authored-by: TheGamerdk <5618080+TheGamerdk@users.noreply.github.com> * Update code/modules/antagonists/horror/horror.dm Co-authored-by: TheGamerdk <5618080+TheGamerdk@users.noreply.github.com> * Update code/modules/antagonists/horror/horror.dm Co-authored-by: TheGamerdk <5618080+TheGamerdk@users.noreply.github.com> * Update code/modules/antagonists/horror/horror.dm Co-authored-by: TheGamerdk <5618080+TheGamerdk@users.noreply.github.com> * Update scanners.dm * Update code/modules/antagonists/changeling/powers/headcrab.dm Co-authored-by: TheGamerdk <5618080+TheGamerdk@users.noreply.github.com> * i hope this won't break again * actually makes horror unable to force lings to suicide * fixes duplicate definition * Not needed, changeling code already checks for that * Cleans up a lot of code * thank you theos for absolutely nerfing the crap out of horror tentacle * cleans up a lot of bibby's suggestions and fixes a few bugs * not sure if i changed anything in this file, but fuck it * lets fix that merge conflict * i sure hope github doesn't go nuclear * oops made horror unable to work * i hate logic gates * sorry for commit spam but this thing literally broke horror and i wanna fix it as soon as possibles * HAHA STUPID DE MORGAN! (tested works as intended) * abominations summoned by curator horn should not attack horror anymore * adds missing " * cannot use lube ability whilst dead anymore * Update horror_abilities_and_upgrades.dm * flailing lube stops when you're killed while doing it * fixes ordering so you can't get stuck in wrong animation at the last spin * Horror can no longer squeeze through welded airlocks * Infesting people is now assigned to Alt+Click * makes tentacle arm no longer affected by range multipliers Co-authored-by: TheGamerdk <5618080+TheGamerdk@users.noreply.github.com>
@@ -117,6 +117,8 @@ GLOBAL_LIST_INIT(turfs_without_ground, typecacheof(list(
|
||||
|
||||
#define isrevenant(A) (istype(A, /mob/living/simple_animal/revenant))
|
||||
|
||||
#define ishorror(A) (istype(A, /mob/living/simple_animal/horror))
|
||||
|
||||
#define isbot(A) (istype(A, /mob/living/simple_animal/bot))
|
||||
|
||||
#define isshade(A) (istype(A, /mob/living/simple_animal/shade))
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
#define ROLE_GANG "gangster" // Yogs
|
||||
#define ROLE_DARKSPAWN "darkspawn" // Yogs
|
||||
#define ROLE_HOLOPARASITE "Holoparasite" // Yogs
|
||||
#define ROLE_HORROR "Eldritch Horror" // Yogs
|
||||
#define ROLE_INFILTRATOR "Infiltrator" // Yogs
|
||||
#define ROLE_ZOMBIE "Zombie"
|
||||
|
||||
@@ -63,6 +64,7 @@ GLOBAL_LIST_INIT(special_roles, list(
|
||||
ROLE_MALF,
|
||||
ROLE_REV = /datum/game_mode/revolution,
|
||||
ROLE_ALIEN,
|
||||
ROLE_HORROR,
|
||||
ROLE_PAI,
|
||||
ROLE_CULTIST = /datum/game_mode/cult,
|
||||
ROLE_BLOB,
|
||||
|
||||
@@ -319,6 +319,7 @@
|
||||
#define GUARDIAN_TRAIT "guardian_trait"
|
||||
#define RANDOM_BLACKOUTS "random_blackouts"
|
||||
#define MADE_UNCLONEABLE "made-uncloneable"
|
||||
#define HORROR_TRAIT "horror"
|
||||
|
||||
///Traits given by station traits
|
||||
#define STATION_TRAIT_BANANIUM_SHIPMENTS "station_trait_bananium_shipments"
|
||||
|
||||
@@ -20,6 +20,7 @@ GLOBAL_LIST_INIT(plasmaman_names, world.file2list("strings/names/plasmaman.txt")
|
||||
GLOBAL_LIST_INIT(ethereal_names, world.file2list("strings/names/ethereal.txt"))
|
||||
GLOBAL_LIST_INIT(posibrain_names, world.file2list("strings/names/posibrain.txt"))
|
||||
GLOBAL_LIST_INIT(nightmare_names, world.file2list("strings/names/nightmare.txt"))
|
||||
GLOBAL_LIST_INIT(horror_names, world.file2list("strings/names/horror.txt"))
|
||||
GLOBAL_LIST_INIT(megacarp_first_names, world.file2list("strings/names/megacarp1.txt"))
|
||||
GLOBAL_LIST_INIT(megacarp_last_names, world.file2list("strings/names/megacarp2.txt"))
|
||||
|
||||
|
||||
18
code/_onclick/hud/horror.dm
Normal file
@@ -0,0 +1,18 @@
|
||||
/obj/screen/horror_chemicals
|
||||
name = "chemicals"
|
||||
icon_state = "horror_counter"
|
||||
screen_loc = ui_lingchemdisplay
|
||||
|
||||
/datum/hud/chemical_counter
|
||||
ui_style = 'icons/mob/screen_midnight.dmi'
|
||||
var/obj/screen/horror_chemicals/chemical_counter
|
||||
|
||||
/datum/hud/chemical_counter/New(mob/owner)
|
||||
. = ..()
|
||||
chemical_counter = new /obj/screen/horror_chemicals
|
||||
infodisplay += chemical_counter
|
||||
|
||||
/datum/hud/chemical_counter/Destroy()
|
||||
. = ..()
|
||||
QDEL_NULL(chemical_counter)
|
||||
|
||||
@@ -46,7 +46,7 @@ SUBSYSTEM_DEF(traumas)
|
||||
"doctors" = typecacheof(list(/mob/living/simple_animal/bot/medbot)),
|
||||
"the supernatural" = typecacheof(list(/mob/living/simple_animal/hostile/construct,
|
||||
/mob/living/simple_animal/hostile/clockwork, /mob/living/simple_animal/drone/cogscarab,
|
||||
/mob/living/simple_animal/revenant, /mob/living/simple_animal/shade)),
|
||||
/mob/living/simple_animal/revenant, /mob/living/simple_animal/shade, /mob/living/simple_animal/horror)),
|
||||
"aliens" = typecacheof(list(/mob/living/carbon/alien, /mob/living/simple_animal/slime)),
|
||||
"conspiracies" = typecacheof(list(/mob/living/simple_animal/bot/secbot, /mob/living/simple_animal/bot/ed209, /mob/living/simple_animal/drone,
|
||||
/mob/living/simple_animal/pet/penguin)),
|
||||
@@ -115,7 +115,7 @@ SUBSYSTEM_DEF(traumas)
|
||||
/obj/item/clothing/suit/cultrobes, /obj/item/clothing/suit/space/hardsuit/cult,
|
||||
/obj/item/clothing/suit/hooded/cultrobes, /obj/item/clothing/head/hooded/cult_hoodie, /obj/effect/rune,
|
||||
/obj/item/stack/sheet/runed_metal, /obj/machinery/door/airlock/cult, /obj/singularity/narsie,
|
||||
/obj/item/soulstone,
|
||||
/obj/item/soulstone, /obj/item/horrortentacle,
|
||||
/obj/structure/destructible/clockwork, /obj/item/clockwork, /obj/item/clothing/suit/armor/clockwork,
|
||||
/obj/item/clothing/glasses/judicial_visor, /obj/effect/clockwork/sigil/, /obj/item/stack/tile/brass,
|
||||
/obj/machinery/door/airlock/clockwork,
|
||||
@@ -142,7 +142,7 @@ SUBSYSTEM_DEF(traumas)
|
||||
|
||||
"anime" = typecacheof(list(/obj/item/clothing/under/schoolgirl, /obj/item/katana, /obj/item/reagent_containers/food/snacks/sashimi, /obj/item/reagent_containers/food/snacks/chawanmushi,
|
||||
/obj/item/reagent_containers/food/drinks/bottle/sake, /obj/item/throwing_star, /obj/item/clothing/head/kitty/genuine, /obj/item/clothing/suit/space/space_ninja,
|
||||
/obj/item/clothing/mask/gas/space_ninja, /obj/item/clothing/shoes/space_ninja, /obj/item/clothing/gloves/space_ninja, /obj/item/twohanded/vibro_weapon,
|
||||
/obj/item/clothing/mask/gas/space_ninja, /obj/item/clothing/shoes/space_ninja, /obj/item/clothing/gloves/space_ninja, /obj/item/twohanded/vibro_weapon, /obj/item/horrortentacle,
|
||||
/obj/item/nullrod/scythe/vibro, /obj/item/energy_katana, /obj/item/toy/katana, /obj/item/nullrod/claymore/katana, /obj/structure/window/paperframe, /obj/structure/mineral_door/paperframe))
|
||||
)
|
||||
|
||||
|
||||
@@ -483,3 +483,8 @@
|
||||
layer = FLY_LAYER
|
||||
duration = 0.48 SECONDS
|
||||
mouse_opacity = 0
|
||||
|
||||
/obj/effect/temp_visual/summon
|
||||
randomdir = FALSE
|
||||
duration = 2 SECONDS
|
||||
icon_state = "summon"
|
||||
|
||||
@@ -200,6 +200,9 @@ GENE SCANNER
|
||||
to_chat(user, "\t[span_info("Subject is allergic to the chemical [C.allergies].")]")
|
||||
if(advanced)
|
||||
to_chat(user, "\t[span_info("Brain Activity Level: [(200 - M.getOrganLoss(ORGAN_SLOT_BRAIN))/2]%.")]")
|
||||
if(M.has_horror_inside())
|
||||
to_chat(user, "\t[span_alert("Detected parasitic organism residing in the cranial area.")]")
|
||||
to_chat(user, "\t[span_alert("Recommended course of action: <b>organ manipulation surgery performed on head.</b>.")]")
|
||||
|
||||
if (M.radiation)
|
||||
to_chat(user, "\t[span_alert("Subject is irradiated.")]")
|
||||
|
||||
@@ -266,10 +266,10 @@
|
||||
break_counter++
|
||||
output += "</div></div>"
|
||||
var/list/long_job_lists = list("Civilian" = GLOB.original_civilian_positions,
|
||||
"Ghost and Other Roles" = list(ROLE_BRAINWASHED, ROLE_DEATHSQUAD, ROLE_DRONE, ROLE_FUGITIVE, ROLE_HOLOPARASITE, ROLE_LAVALAND, ROLE_MIND_TRANSFER, ROLE_POSIBRAIN, ROLE_SENTIENCE),
|
||||
"Ghost and Other Roles" = list(ROLE_BRAINWASHED, ROLE_DEATHSQUAD, ROLE_DRONE, ROLE_FUGITIVE, ROLE_HOLOPARASITE, ROLE_HORROR, ROLE_LAVALAND, ROLE_MIND_TRANSFER, ROLE_POSIBRAIN, ROLE_SENTIENCE),
|
||||
"Antagonist Positions" = list(ROLE_ABDUCTOR, ROLE_ALIEN, ROLE_BLOB,
|
||||
ROLE_BROTHER, ROLE_CHANGELING, ROLE_CULTIST,
|
||||
ROLE_DEVIL, ROLE_FUGITIVE, ROLE_HOLOPARASITE, ROLE_INTERNAL_AFFAIRS, ROLE_MALF,
|
||||
ROLE_DEVIL, ROLE_FUGITIVE, ROLE_HOLOPARASITE, ROLE_HORROR, ROLE_INTERNAL_AFFAIRS, ROLE_MALF,
|
||||
ROLE_MONKEY, ROLE_NINJA, ROLE_OPERATIVE,
|
||||
ROLE_REV, ROLE_REVENANT,
|
||||
ROLE_REV_HEAD, ROLE_SERVANT_OF_RATVAR, ROLE_SYNDICATE,
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
dna_cost = 1
|
||||
req_human = 1
|
||||
ignores_fakedeath = TRUE
|
||||
req_stat = DEAD
|
||||
|
||||
/datum/action/changeling/headcrab/sting_action(mob/user)
|
||||
set waitfor = FALSE
|
||||
@@ -33,6 +32,8 @@
|
||||
to_chat(S, span_userdanger("Your sensors are disabled by a shower of blood!"))
|
||||
S.Paralyze(60)
|
||||
var/turf = get_turf(user)
|
||||
var/mob/living/simple_animal/horror/H = user.has_horror_inside()
|
||||
H?.leave_victim()
|
||||
user.gib()
|
||||
. = TRUE
|
||||
sleep(5) // So it's not killed in explosion
|
||||
|
||||
@@ -10,6 +10,13 @@
|
||||
//Heals the things that the other regenerative abilities don't.
|
||||
/datum/action/changeling/panacea/sting_action(mob/user)
|
||||
to_chat(user, span_notice("We cleanse impurities from our form."))
|
||||
var/mob/living/simple_animal/horror/H = user.has_horror_inside()
|
||||
if(H)
|
||||
H.leave_victim()
|
||||
if(iscarbon(user))
|
||||
var/mob/living/carbon/C = user
|
||||
C.vomit(0, toxic = TRUE)
|
||||
to_chat(user, span_notice("A parasite exits our form."))
|
||||
..()
|
||||
var/list/bad_organs = list(
|
||||
user.getorgan(/obj/item/organ/body_egg),
|
||||
|
||||
848
code/modules/antagonists/horror/horror.dm
Normal file
@@ -0,0 +1,848 @@
|
||||
/mob/living/simple_animal/horror
|
||||
name = "eldritch horror"
|
||||
desc = "Your eyes can barely comprehend what they're looking at."
|
||||
icon_state = "horror"
|
||||
icon_living = "horror"
|
||||
icon_dead = "horror_dead"
|
||||
icon_gib = "horror_gib"
|
||||
health = 50
|
||||
maxHealth = 50
|
||||
melee_damage_lower = 10
|
||||
melee_damage_upper = 10
|
||||
see_in_dark = 5
|
||||
stop_automated_movement = TRUE
|
||||
attacktext = "bites"
|
||||
speak_emote = list("gurgles")
|
||||
attack_sound = 'sound/weapons/bite.ogg'
|
||||
pass_flags = PASSTABLE | PASSMOB
|
||||
mob_size = MOB_SIZE_SMALL
|
||||
faction = list("neutral","silicon","creature","heretics","abomination")
|
||||
ventcrawler = VENTCRAWLER_ALWAYS
|
||||
initial_language_holder = /datum/language_holder/universal
|
||||
hud_type = /datum/hud/chemical_counter
|
||||
|
||||
atmos_requirements = list("min_oxy" = 0, "max_oxy" = 0, "min_tox" = 0, "max_tox" = 0, "min_co2" = 0, "max_co2" = 0, "min_n2" = 0, "max_n2" = 0)
|
||||
minbodytemp = 0
|
||||
maxbodytemp = 1500
|
||||
unsuitable_atmos_damage = 0.5
|
||||
|
||||
var/playstyle_string = span_bold(span_big("You are an eldritch horror,") + " an evermutating parasitic abomination. Seek human souls to consume. \
|
||||
Crawl into people's heads and steal their essence. Use it to mutate yourself, giving you access to more power and abilities. \
|
||||
You operate on chemicals that get built up while you spend time in someone's head. You are weak when outside, play carefully. \
|
||||
You can attack airlocks to squeeze yourself through them. " + span_danger("Alt+Click on people to infest them."))
|
||||
|
||||
var/mob/living/carbon/victim
|
||||
var/datum/mind/target
|
||||
var/mob/living/captive_brain/host_brain
|
||||
var/available_points = 4
|
||||
var/consumed_souls = 0
|
||||
|
||||
//An associative list (associated by ability typepaths) containing the abilities the horror has
|
||||
var/list/horrorabilities = list()
|
||||
//same (associated by their ID), but for permanent upgrades
|
||||
var/list/horrorupgrades = list()
|
||||
//list storing what items we have to un-glue when stopping mind control
|
||||
var/list/clothing = list()
|
||||
|
||||
var/bonding = FALSE
|
||||
var/controlling = FALSE
|
||||
var/chemicals = 10
|
||||
var/chem_regen_rate = 2
|
||||
var/used_freeze
|
||||
var/used_target
|
||||
var/horror_chems = list(/datum/horror_chem/epinephrine,/datum/horror_chem/mannitol,/datum/horror_chem/bicaridine,/datum/horror_chem/kelotane,/datum/horror_chem/charcoal)
|
||||
|
||||
var/leaving = FALSE
|
||||
var/hiding = FALSE
|
||||
var/invisible = FALSE
|
||||
var/datum/action/innate/horror/talk_to_horror/talk_to_horror_action = new
|
||||
|
||||
/mob/living/simple_animal/horror/Initialize(mapload, gen=1)
|
||||
..()
|
||||
real_name = "[pick(GLOB.horror_names)]"
|
||||
|
||||
//default abilities
|
||||
add_ability(/datum/action/innate/horror/mutate)
|
||||
add_ability(/datum/action/innate/horror/seek_soul)
|
||||
add_ability(/datum/action/innate/horror/consume_soul)
|
||||
add_ability(/datum/action/innate/horror/talk_to_host)
|
||||
add_ability(/datum/action/innate/horror/freeze_victim)
|
||||
add_ability(/datum/action/innate/horror/toggle_hide)
|
||||
add_ability(/datum/action/innate/horror/talk_to_brain)
|
||||
add_ability(/datum/action/innate/horror/take_control)
|
||||
add_ability(/datum/action/innate/horror/leave_body)
|
||||
add_ability(/datum/action/innate/horror/make_chems)
|
||||
add_ability(/datum/action/innate/horror/give_back_control)
|
||||
RefreshAbilities()
|
||||
|
||||
var/datum/atom_hud/hud = GLOB.huds[DATA_HUD_MEDICAL_ADVANCED]
|
||||
hud.add_hud_to(src)
|
||||
update_horror_hud()
|
||||
|
||||
|
||||
/mob/living/simple_animal/horror/Destroy()
|
||||
host_brain = null
|
||||
victim = null
|
||||
return ..()
|
||||
|
||||
/mob/living/simple_animal/horror/AltClickOn(atom/A)
|
||||
if(iscarbon(A))
|
||||
var/mob/living/carbon/C = A
|
||||
if(!C || QDELETED(src) || !Adjacent(C) || victim || !can_use_ability())
|
||||
return
|
||||
if(victim)
|
||||
to_chat(src, span_warning("You are already within a host."))
|
||||
return
|
||||
|
||||
to_chat(src, span_warning("You slither your tentacles up [C] and begin probing at their ear canal..."))
|
||||
|
||||
if(!do_mob(src, C, 3 SECONDS))
|
||||
to_chat(src, span_warning("As [C] moves away, you are dislodged and fall to the ground."))
|
||||
return
|
||||
|
||||
if(!C || QDELETED(src))
|
||||
return
|
||||
if(C.has_horror_inside())
|
||||
to_chat(src, span_warning("[C] is already infested!"))
|
||||
return
|
||||
Infect(C)
|
||||
return
|
||||
..()
|
||||
|
||||
/mob/living/simple_animal/horror/proc/has_chemicals(amt)
|
||||
return chemicals >= amt
|
||||
|
||||
/mob/living/simple_animal/horror/proc/use_chemicals(amt)
|
||||
if(!has_chemicals(amt))
|
||||
return FALSE
|
||||
chemicals -= amt
|
||||
update_horror_hud()
|
||||
return TRUE
|
||||
|
||||
/mob/living/simple_animal/horror/proc/regenerate_chemicals(amt)
|
||||
chemicals += amt
|
||||
chemicals = min(250, chemicals)
|
||||
update_horror_hud()
|
||||
|
||||
/mob/living/simple_animal/horror/proc/update_horror_hud()
|
||||
if(!src || !hud_used)
|
||||
return
|
||||
var/datum/hud/chemical_counter/H = hud_used
|
||||
var/obj/screen/counter = H.chemical_counter
|
||||
counter.maptext = "<div align='center' valign='middle' style='position:relative; top:0px; left:6px'><font color='#7264FF'>[chemicals]</font></div>"
|
||||
|
||||
/mob/living/simple_animal/horror/proc/can_use_ability()
|
||||
if(stat != CONSCIOUS)
|
||||
to_chat(src, "You cannot do that in your current state.")
|
||||
return FALSE
|
||||
return TRUE
|
||||
|
||||
/mob/living/simple_animal/horror/proc/SearchTarget()
|
||||
if(target)
|
||||
if(world.time - used_target < 3 MINUTES)
|
||||
to_chat(src, span_warning("You cannot use that ability again so soon."))
|
||||
return
|
||||
if(alert("You already have a target ([target.name]). Would you like to change that target?","Swap targets?","Yes","No") != "Yes")
|
||||
return
|
||||
|
||||
var/list/possible_targets = list()
|
||||
for(var/datum/mind/M in SSticker.minds)
|
||||
if(M.current && M.current.stat != DEAD)
|
||||
if(ishuman(M.current))
|
||||
if(M.hasSoul && (mind.enslaved_to != M.current))
|
||||
possible_targets[M] = M
|
||||
|
||||
var/list/selected_targets = list()
|
||||
var/list/icons = list()
|
||||
while(selected_targets.len != 4)
|
||||
if(possible_targets.len <= 0)
|
||||
break
|
||||
var/datum/mind/M = pick(possible_targets)
|
||||
selected_targets[M] = M
|
||||
possible_targets -= M
|
||||
|
||||
var/mob/living/carbon/human/H = M.current
|
||||
icons[M] = H
|
||||
|
||||
used_target = world.time
|
||||
|
||||
var/entry_name = show_radial_menu(src, (victim ? src.loc : src), icons, tooltips = TRUE)
|
||||
target = selected_targets[entry_name]
|
||||
|
||||
//you didn't select your target? let me do that for you, my friend
|
||||
if(selected_targets.len > 0 && !target)
|
||||
target = pick(selected_targets)
|
||||
|
||||
if(target)
|
||||
to_chat(src, span_warning("You caught their scent. Go and consume [target.current.real_name], the [target.assigned_role]'s soul!"))
|
||||
apply_status_effect(/datum/status_effect/agent_pinpointer/horror)
|
||||
for(var/datum/status_effect/agent_pinpointer/horror/status in status_effects)
|
||||
status.scan_target = target.current
|
||||
else
|
||||
//refund cooldown
|
||||
used_target = 0
|
||||
to_chat(src, span_warning("Failed to select a target!"))
|
||||
|
||||
/mob/living/simple_animal/horror/proc/ConsumeSoul()
|
||||
if(!can_use_ability())
|
||||
return
|
||||
|
||||
if(!victim.mind.hasSoul)
|
||||
to_chat(src, "This host doesn't have a soul!")
|
||||
return
|
||||
|
||||
if(victim == mind.enslaved_to)
|
||||
to_chat(src, span_userdanger("No, not yet... We still need them..."))
|
||||
return
|
||||
|
||||
if(victim.mind != target)
|
||||
to_chat(src, "This soul isn't your target, you can't consume it!")
|
||||
return
|
||||
|
||||
to_chat(src, "You begin consuming [victim.name]'s soul!")
|
||||
if(do_after(src, 20 SECONDS, target = victim, stayStill = FALSE))
|
||||
consume()
|
||||
|
||||
/mob/living/simple_animal/horror/proc/consume()
|
||||
if(!can_use_ability() || !victim || !victim.mind.hasSoul || victim.mind != target)
|
||||
return
|
||||
consumed_souls++
|
||||
available_points++
|
||||
to_chat(src, span_userdanger("You succeed in consuming [victim.name]'s soul!"))
|
||||
to_chat(victim, span_userdanger("You suddenly feel weak and hollow inside..."))
|
||||
victim.health -= 20
|
||||
victim.maxHealth -= 20
|
||||
victim.mind.hasSoul = FALSE
|
||||
target = null
|
||||
remove_status_effect(/datum/status_effect/agent_pinpointer/horror)
|
||||
playsound(src, 'sound/effects/curseattack.ogg', 150)
|
||||
playsound(src, 'sound/effects/ghost.ogg', 50)
|
||||
|
||||
/mob/living/simple_animal/horror/proc/Communicate()
|
||||
if(!can_use_ability())
|
||||
return
|
||||
if(!victim)
|
||||
to_chat(src, "You do not have a host to communicate with!")
|
||||
return
|
||||
|
||||
var/input = stripped_input(src, "Please enter a message to tell your host.", "Horror", null)
|
||||
if(!input)
|
||||
return
|
||||
|
||||
if(src && !QDELETED(src) && !QDELETED(victim))
|
||||
if(victim)
|
||||
to_chat(victim, span_changeling("<i>[real_name] slurs:</i> [input]"))
|
||||
for(var/M in GLOB.dead_mob_list)
|
||||
if(isobserver(M))
|
||||
var/rendered = span_changeling("<i>Horror Communication from <b>[real_name]</b> : [input]</i>")
|
||||
var/link = FOLLOW_LINK(M, src)
|
||||
to_chat(M, "[link] [rendered]")
|
||||
to_chat(src, span_changeling("<i>[real_name] slurs:</i> [input]"))
|
||||
add_verb(victim, /mob/living/proc/horror_comm)
|
||||
talk_to_horror_action.Grant(victim)
|
||||
|
||||
/mob/living/proc/horror_comm()
|
||||
set name = "Converse with Horror"
|
||||
set category = "Horror"
|
||||
set desc = "Communicate mentally with the thing in your head."
|
||||
|
||||
var/mob/living/simple_animal/horror/B = has_horror_inside()
|
||||
if(B)
|
||||
var/input = stripped_input(src, "Please enter a message to tell the horror.", "Message", "")
|
||||
if(!input)
|
||||
return
|
||||
|
||||
to_chat(B, span_changeling("<i>[real_name] says:</i> [input]"))
|
||||
|
||||
for(var/M in GLOB.dead_mob_list)
|
||||
if(isobserver(M))
|
||||
var/rendered = span_changeling("<i>Horror Communication from <b>[real_name]</b> : [input]</i>")
|
||||
var/link = FOLLOW_LINK(M, src)
|
||||
to_chat(M, "[link] [rendered]")
|
||||
to_chat(src, span_changeling("<i>[real_name] says:</i> [input]"))
|
||||
|
||||
/mob/living/proc/trapped_mind_comm()
|
||||
var/mob/living/simple_animal/horror/B = has_horror_inside()
|
||||
if(!B || !B.host_brain)
|
||||
return
|
||||
var/mob/living/captive_brain/CB = B.host_brain
|
||||
var/input = stripped_input(src, "Please enter a message to tell the trapped mind.", "Message", null)
|
||||
if(!input)
|
||||
return
|
||||
|
||||
to_chat(CB, span_changeling("<i>[B.real_name] says:</i> [input]"))
|
||||
|
||||
for(var/M in GLOB.dead_mob_list)
|
||||
if(isobserver(M))
|
||||
var/rendered = span_changeling("<i>Horror Communication from <b>[B.real_name]</b> : [input]</i>")
|
||||
var/link = FOLLOW_LINK(M, src)
|
||||
to_chat(M, "[link] [rendered]")
|
||||
to_chat(src, span_changeling("<i>[B.real_name] says:</i> [input]"))
|
||||
|
||||
/mob/living/simple_animal/horror/Life()
|
||||
..()
|
||||
if(has_upgrade("regen"))
|
||||
heal_overall_damage(5)
|
||||
|
||||
if(invisible) //don't regenerate chemicals when invisible
|
||||
if(use_chemicals(5))
|
||||
alpha = max(alpha - 100, 1)
|
||||
else
|
||||
to_chat(src, span_warning("You ran out of chemicals to support your invisibility."))
|
||||
invisible = FALSE
|
||||
Update_Invisibility_Button()
|
||||
else
|
||||
if(has_upgrade("nohost_regen"))
|
||||
regenerate_chemicals(chem_regen_rate)
|
||||
else if(victim)
|
||||
if(victim.stat == DEAD)
|
||||
regenerate_chemicals(1)
|
||||
else
|
||||
regenerate_chemicals(chem_regen_rate)
|
||||
alpha = min(255, alpha + 50)
|
||||
|
||||
if(victim)
|
||||
if(stat != DEAD && victim.stat != DEAD)
|
||||
heal_overall_damage(1)
|
||||
|
||||
/mob/living/simple_animal/horror/say(message, bubble_type, var/list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null)
|
||||
if(victim)
|
||||
to_chat(src, span_warning("You cannot speak out loud while inside a host!"))
|
||||
return
|
||||
return ..()
|
||||
|
||||
/mob/living/simple_animal/horror/emote(act, m_type = null, message = null, intentional = FALSE)
|
||||
if(victim)
|
||||
to_chat(src, span_warning("You cannot emote while inside a host!"))
|
||||
return
|
||||
return ..()
|
||||
|
||||
/mob/living/simple_animal/horror/UnarmedAttack(atom/A)
|
||||
if(istype(A, /obj/machinery/door/airlock))
|
||||
var/obj/machinery/door/airlock/door = A
|
||||
if(door.welded)
|
||||
to_chat(src, span_danger("The door is welded shut!"))
|
||||
return
|
||||
visible_message(span_warning("[src] slips their tentacles into the airlock and starts prying it open!"), span_warning("You start moving onto the airlock."))
|
||||
playsound(A, 'sound/misc/splort.ogg', 50, 1)
|
||||
if(do_after(src, 5 SECONDS, target = A))
|
||||
if(door.welded)
|
||||
to_chat(src, span_danger("The door is welded shut!"))
|
||||
return
|
||||
visible_message(span_warning("[src] forces themselves through the airlock!"), span_warning("You force yourself through the airlock"))
|
||||
forceMove(get_turf(A))
|
||||
playsound(A, 'sound/machines/airlock_alien_prying.ogg', 50, 1)
|
||||
return
|
||||
|
||||
if(isliving(A))
|
||||
if(victim || A == src.mind.enslaved_to)
|
||||
healthscan(usr, A)
|
||||
chemscan(usr, A)
|
||||
else
|
||||
alpha = 255
|
||||
if(hiding)
|
||||
var/datum/action/innate/horror/H = has_ability(/datum/action/innate/horror/toggle_hide)
|
||||
H.Activate()
|
||||
if(invisible)
|
||||
var/datum/action/innate/horror/H = has_ability(/datum/action/innate/horror/chameleon)
|
||||
H.Activate()
|
||||
Update_Invisibility_Button()
|
||||
..()
|
||||
|
||||
/mob/living/simple_animal/horror/ex_act()
|
||||
if(victim)
|
||||
return
|
||||
|
||||
..()
|
||||
|
||||
/mob/living/simple_animal/horror/proc/Infect(mob/living/carbon/C)
|
||||
if(!C)
|
||||
return
|
||||
var/obj/item/bodypart/head/head = C.get_bodypart(BODY_ZONE_HEAD)
|
||||
if(!head)
|
||||
to_chat(src, span_warning("[C] doesn't have a head!"))
|
||||
return
|
||||
var/hasbrain = locate(/obj/item/organ/brain) in C.internal_organs
|
||||
|
||||
if(!hasbrain)
|
||||
to_chat(src, span_warning("[C] doesn't have a brain!"))
|
||||
return
|
||||
|
||||
if(C.has_horror_inside())
|
||||
to_chat(src, span_warning("[C] is already infested!"))
|
||||
return
|
||||
|
||||
//can only infect non-ssd alive people / corpses with ghosts attached / current target
|
||||
if((C.stat == DEAD || !C.key) && (C.stat != DEAD || !C.get_ghost()) && (!target || C != target.current))
|
||||
to_chat(src, span_warning("[C]'s mind seems unresponsive. Try someone else!"))
|
||||
return
|
||||
|
||||
if(hiding)
|
||||
var/datum/action/innate/horror/H = has_ability(/datum/action/innate/horror/toggle_hide)
|
||||
H.Activate()
|
||||
invisible = FALSE
|
||||
Update_Invisibility_Button()
|
||||
|
||||
victim = C
|
||||
forceMove(victim)
|
||||
RefreshAbilities()
|
||||
log_game("[src]/([src.ckey]) has infested [victim]/([victim.ckey]")
|
||||
|
||||
/mob/living/simple_animal/horror/proc/secrete_chemicals()
|
||||
if(!can_use_ability())
|
||||
return
|
||||
if(!victim)
|
||||
to_chat(src, span_warning("You are not inside a host body."))
|
||||
return
|
||||
|
||||
var/content = "<p>Chemicals: <span id='chemicals'>[chemicals]</span></p>"
|
||||
content += "<table>"
|
||||
|
||||
for(var/path in subtypesof(/datum/horror_chem))
|
||||
var/datum/horror_chem/chem = path
|
||||
if(path in horror_chems)
|
||||
content += "<tr><td><a class='chem-select' href='?_src_=\ref[src];src=\ref[src];horror_use_chem=[initial(chem.chemname)]'>[initial(chem.chemname)] ([initial(chem.chemuse)])</a><p>[initial(chem.chem_desc)]</p></td></tr>"
|
||||
|
||||
content += "</table>"
|
||||
|
||||
var/html = get_html_template(content)
|
||||
|
||||
usr << browse(html, "window=ViewHorror\ref[src]Chems;size=600x800")
|
||||
|
||||
/mob/living/simple_animal/horror/proc/hide()
|
||||
if(victim)
|
||||
to_chat(src, span_warning("You cannot do this while you're inside a host."))
|
||||
return
|
||||
|
||||
if(stat != CONSCIOUS)
|
||||
return
|
||||
|
||||
if(!hiding)
|
||||
layer = LATTICE_LAYER
|
||||
visible_message(span_name("[src] scurries to the ground!"), \
|
||||
span_noticealien("You are now hiding."))
|
||||
hiding = TRUE
|
||||
else
|
||||
layer = MOB_LAYER
|
||||
visible_message("[src] slowly peaks up from the ground...", \
|
||||
span_noticealien("You stop hiding."))
|
||||
hiding = FALSE
|
||||
|
||||
/mob/living/simple_animal/horror/proc/go_invisible()
|
||||
if(victim)
|
||||
to_chat(src, span_warning("You cannot do this while you're inside a host."))
|
||||
return
|
||||
|
||||
if(!can_use_ability())
|
||||
return
|
||||
|
||||
if(!has_chemicals(10))
|
||||
to_chat(src, span_warning("You don't have enough chemicals to do that."))
|
||||
return
|
||||
|
||||
if(!invisible)
|
||||
to_chat(src, span_noticealien("You focus your chameleon skin to blend into the environment."))
|
||||
invisible = TRUE
|
||||
else
|
||||
to_chat(src, span_noticealien("You stop your camouflage."))
|
||||
invisible = FALSE
|
||||
|
||||
/mob/living/simple_animal/horror/proc/freeze_victim()
|
||||
if(world.time - used_freeze < 150)
|
||||
to_chat(src, span_warning("You cannot use that ability again so soon."))
|
||||
return
|
||||
|
||||
if(victim)
|
||||
to_chat(src, span_warning("You cannot do that from within a host body."))
|
||||
return
|
||||
|
||||
if(!can_use_ability())
|
||||
return
|
||||
|
||||
var/list/choices = list()
|
||||
for(var/mob/living/carbon/C in view(1,src))
|
||||
if(C.stat == CONSCIOUS)
|
||||
choices += C
|
||||
|
||||
if(!choices.len)
|
||||
return
|
||||
|
||||
if(QDELETED(src) || stat != CONSCIOUS || victim || (world.time - used_freeze < 150))
|
||||
return
|
||||
|
||||
layer = MOB_LAYER
|
||||
for (var/mob/living/carbon/M in range(1, src))
|
||||
if(!M || !Adjacent(M))
|
||||
return
|
||||
|
||||
if(has_upgrade("paralysis"))
|
||||
playsound(loc, "sound/effects/sparks4.ogg", 30, 1, -1)
|
||||
M.Stun(50)
|
||||
M.SetSleeping(70) //knocked out cold
|
||||
M.electrocute_act(15, src, 1, FALSE, FALSE, FALSE, 1, FALSE)
|
||||
else
|
||||
to_chat(M, span_userdanger("You feel something wrapping around your leg, pulling you down!"))
|
||||
playsound(loc, "sound/weapons/whipgrab.ogg", 30, 1, -1)
|
||||
M.Immobilize(50)
|
||||
M.Knockdown(70)
|
||||
used_freeze = world.time
|
||||
|
||||
/mob/living/simple_animal/horror/proc/is_leaving()
|
||||
return leaving
|
||||
|
||||
/mob/living/simple_animal/horror/proc/release_victim()
|
||||
if(!victim)
|
||||
to_chat(src, span_danger("You are not inside a host body."))
|
||||
return
|
||||
|
||||
if(!can_use_ability())
|
||||
return
|
||||
|
||||
if(leaving)
|
||||
leaving = FALSE
|
||||
to_chat(src, span_danger("You decide against leaving your host."))
|
||||
return
|
||||
|
||||
to_chat(src, span_danger("You begin disconnecting from [victim]'s synapses and prodding at their internal ear canal."))
|
||||
|
||||
if(victim.stat != DEAD && !has_upgrade("invisible_exit"))
|
||||
to_chat(victim, span_userdanger("An odd, uncomfortable pressure begins to build inside your skull, behind your ear..."))
|
||||
|
||||
leaving = TRUE
|
||||
if(do_after(src, 100, target = victim, extra_checks = CALLBACK(src, .proc/is_leaving), stayStill = FALSE))
|
||||
release_host()
|
||||
|
||||
/mob/living/simple_animal/horror/proc/release_host()
|
||||
if(!victim || QDELETED(victim) || QDELETED(src) || controlling)
|
||||
return
|
||||
|
||||
if(!can_use_ability())
|
||||
return
|
||||
|
||||
if(has_upgrade("invisible_exit"))
|
||||
alpha = 60
|
||||
if(has_ability(/datum/action/innate/horror/chameleon))
|
||||
invisible = TRUE
|
||||
Update_Invisibility_Button()
|
||||
to_chat(src, span_danger("You silently wiggle out of [victim]'s ear and plop to the ground before vanishing via reflective solution that covers you."))
|
||||
else
|
||||
to_chat(src, span_danger("You wiggle out of [victim]'s ear and plop to the ground."))
|
||||
if(victim.mind)
|
||||
if(!has_upgrade("invisible_exit"))
|
||||
to_chat(victim, span_danger("Something slimy wiggles out of your ear and plops to the ground!"))
|
||||
|
||||
leaving = FALSE
|
||||
|
||||
leave_victim()
|
||||
|
||||
/mob/living/simple_animal/horror/proc/leave_victim()
|
||||
if(!victim)
|
||||
return
|
||||
|
||||
if(controlling)
|
||||
detatch()
|
||||
|
||||
forceMove(get_turf(victim))
|
||||
|
||||
reset_perspective()
|
||||
unset_machine()
|
||||
|
||||
victim.reset_perspective()
|
||||
victim.unset_machine()
|
||||
|
||||
var/mob/living/V = victim
|
||||
remove_verb(V, /mob/living/proc/horror_comm)
|
||||
talk_to_horror_action.Remove(victim)
|
||||
|
||||
for(var/obj/item/horrortentacle/T in victim)
|
||||
victim.visible_message(span_warning("[victim]'s tentacle transforms back!"), span_notice("Your tentacle disappears!"))
|
||||
playsound(victim, 'sound/effects/blobattack.ogg', 30, 1)
|
||||
qdel(T)
|
||||
victim = null
|
||||
|
||||
RefreshAbilities()
|
||||
|
||||
|
||||
/mob/living/simple_animal/horror/proc/jumpstart()
|
||||
if(!victim)
|
||||
to_chat(src, span_warning("You need a host to be able to use this."))
|
||||
return
|
||||
|
||||
if(!can_use_ability())
|
||||
return
|
||||
|
||||
if(victim.stat != DEAD)
|
||||
to_chat(src, span_warning("Your host is already alive!"))
|
||||
return
|
||||
|
||||
if(!has_chemicals(250))
|
||||
to_chat(src, span_warning("You need 250 chemicals to use this!"))
|
||||
return
|
||||
|
||||
if(HAS_TRAIT_FROM(target, TRAIT_BADDNA, CHANGELING_DRAIN))
|
||||
to_chat(src, span_warning("Their DNA is completely destroyed! You can't revive them"))
|
||||
return
|
||||
|
||||
if(victim.stat == DEAD)
|
||||
playsound(src, 'sound/machines/defib_charge.ogg', 50, 1, -1)
|
||||
sleep(1 SECONDS)
|
||||
victim.tod = null
|
||||
victim.setToxLoss(0)
|
||||
victim.setOxyLoss(0)
|
||||
victim.setCloneLoss(0)
|
||||
victim.SetUnconscious(0)
|
||||
victim.SetStun(0)
|
||||
victim.SetKnockdown(0)
|
||||
victim.radiation = 0
|
||||
victim.heal_overall_damage(victim.getBruteLoss(), victim.getFireLoss())
|
||||
victim.reagents.clear_reagents()
|
||||
if(HAS_TRAIT_FROM(victim, TRAIT_HUSK, BURN))
|
||||
victim.cure_husk(BURN)
|
||||
for(var/organ in victim.internal_organs)
|
||||
var/obj/item/organ/O = organ
|
||||
O.setOrganDamage(0)
|
||||
victim.restore_blood()
|
||||
victim.remove_all_embedded_objects()
|
||||
victim.revive()
|
||||
log_game("[src]/([src.ckey]) has revived [victim]/([victim.ckey]")
|
||||
chemicals -= 250
|
||||
to_chat(src, span_notice("You send a jolt of energy to your host, reviving them!"))
|
||||
victim.grab_ghost(force = TRUE) //brings the host back, no eggscape
|
||||
victim.adjustOxyLoss(30)
|
||||
to_chat(victim, span_userdanger("You bolt upright, gasping for breath!"))
|
||||
victim.electrocute_act(15, src, 1, FALSE, FALSE, FALSE, 1, FALSE)
|
||||
playsound(src, 'sound/machines/defib_zap.ogg', 50, 1, -1)
|
||||
|
||||
|
||||
/mob/living/simple_animal/horror/proc/view_memory()
|
||||
if(!victim)
|
||||
to_chat(src, span_warning("You need a host to be able to use this."))
|
||||
return
|
||||
|
||||
if(!can_use_ability())
|
||||
return
|
||||
|
||||
if(victim.stat == DEAD)
|
||||
to_chat(src, span_warning("Your host brain is unresponsive. They are dead!"))
|
||||
return
|
||||
|
||||
if(prob(20))
|
||||
to_chat(victim, span_danger("You suddenly feel your memory being tangled with..."))//chance to alert the victim
|
||||
|
||||
if(victim.mind)
|
||||
var/datum/mind/suckedbrain = victim.mind
|
||||
to_chat(src, span_boldnotice("You skim through [victim]'s memories...[suckedbrain.memory]"))
|
||||
for(var/A in suckedbrain.antag_datums)
|
||||
var/datum/antagonist/antag_types = A
|
||||
var/list/all_objectives = antag_types.objectives.Copy()
|
||||
if(antag_types.antag_memory)
|
||||
to_chat(src, span_notice("[antag_types.antag_memory]"))
|
||||
if(LAZYLEN(all_objectives))
|
||||
to_chat(src, span_boldnotice("Objectives:"))
|
||||
var/obj_count = 1
|
||||
for(var/O in all_objectives)
|
||||
var/datum/objective/objective = O
|
||||
to_chat(src, span_notice("Objective #[obj_count++]: [objective.explanation_text]"))
|
||||
var/list/datum/mind/other_owners = objective.get_owners() - suckedbrain
|
||||
if(other_owners.len)
|
||||
for(var/mind in other_owners)
|
||||
var/datum/mind/M = mind
|
||||
to_chat(src, span_notice("Conspirator: [M.name]"))
|
||||
|
||||
var/list/recent_speech = list()
|
||||
var/list/say_log = list()
|
||||
var/log_source = victim.logging
|
||||
for(var/log_type in log_source)
|
||||
var/nlog_type = text2num(log_type)
|
||||
if(nlog_type & LOG_SAY)
|
||||
var/list/reversed = log_source[log_type]
|
||||
if(islist(reversed))
|
||||
say_log = reverseRange(reversed.Copy())
|
||||
break
|
||||
if(LAZYLEN(say_log))
|
||||
for(var/spoken_memory in say_log)
|
||||
if(recent_speech.len >= 5)//up to 5 random lines of speech, favoring more recent speech
|
||||
break
|
||||
if(prob(50))
|
||||
recent_speech[spoken_memory] = say_log[spoken_memory]
|
||||
if(recent_speech.len)
|
||||
to_chat(src, span_boldnotice("You catch some drifting memories of their past conversations..."))
|
||||
for(var/spoken_memory in recent_speech)
|
||||
to_chat(src, span_notice("[recent_speech[spoken_memory]]"))
|
||||
var/mob/living/carbon/human/H = victim
|
||||
var/datum/dna/the_dna = H.has_dna()
|
||||
if(the_dna)
|
||||
to_chat(src, span_boldnotice("You uncover that [H.p_their()] true identity is [the_dna.real_name]."))
|
||||
|
||||
/mob/living/simple_animal/horror/proc/is_bonding()
|
||||
return bonding
|
||||
|
||||
/mob/living/simple_animal/horror/proc/bond_brain()
|
||||
if(!victim)
|
||||
to_chat(src, span_warning("You are not inside a host body."))
|
||||
return
|
||||
|
||||
if(!can_use_ability())
|
||||
return
|
||||
|
||||
if(victim.stat == DEAD)
|
||||
to_chat(src, span_notice("This host lacks enough brain function to control."))
|
||||
return
|
||||
|
||||
if(victim.has_trauma_type(/datum/brain_trauma/severe/split_personality))
|
||||
to_chat(src, span_notice("This host's brain lobe separation makes it too complex for you to control."))
|
||||
return
|
||||
|
||||
if(bonding)
|
||||
bonding = FALSE
|
||||
to_chat(src, span_danger("You stop attempting to take control of your host."))
|
||||
return
|
||||
|
||||
to_chat(src, span_danger("You begin delicately adjusting your connection to the host brain..."))
|
||||
|
||||
if(QDELETED(src) || QDELETED(victim))
|
||||
return
|
||||
|
||||
bonding = TRUE
|
||||
|
||||
var/delay = 20 SECONDS
|
||||
if(has_upgrade("fast_control"))
|
||||
delay -= 12 SECONDS
|
||||
if(do_after(src, delay, target = victim, extra_checks = CALLBACK(src, .proc/is_bonding), stayStill = FALSE))
|
||||
assume_control()
|
||||
|
||||
/mob/living/simple_animal/horror/proc/assume_control()
|
||||
if(!victim || !src || controlling || victim.stat == DEAD)
|
||||
return
|
||||
if(is_servant_of_ratvar(victim) || iscultist(victim))
|
||||
to_chat(src, span_warning("[victim]'s mind seems to be blocked by some unknown force!"))
|
||||
bonding = FALSE
|
||||
return
|
||||
if(HAS_TRAIT(victim, TRAIT_MINDSHIELD))
|
||||
to_chat(src, span_warning("[victim]'s mind seems to be shielded from your influence!"))
|
||||
bonding = FALSE
|
||||
return
|
||||
else
|
||||
RegisterSignal(victim, COMSIG_MOB_APPLY_DAMAGE, .proc/hit_detatch)
|
||||
log_game("[src]/([src.ckey]) assumed control of [victim]/([victim.ckey] with eldritch powers.")
|
||||
to_chat(src, span_warning("You plunge your probosci deep into the cortex of the host brain, interfacing directly with their nervous system."))
|
||||
to_chat(victim, span_userdanger("You feel a strange shifting sensation behind your eyes as an alien consciousness displaces yours."))
|
||||
|
||||
clothing = victim.get_equipped_items()
|
||||
for(var/obj/item/I in clothing)
|
||||
ADD_TRAIT(I, TRAIT_NODROP, HORROR_TRAIT)
|
||||
|
||||
qdel(host_brain)
|
||||
host_brain = new(src)
|
||||
host_brain.H = src
|
||||
host_brain.name = "Trapped mind of [victim.real_name]"
|
||||
victim.mind.transfer_to(host_brain)
|
||||
if(victim.key)
|
||||
host_brain.key = victim.key
|
||||
|
||||
to_chat(host_brain, "You are trapped in your own mind. You feel that there must be a way to resist!")
|
||||
|
||||
mind.transfer_to(victim)
|
||||
|
||||
bonding = FALSE
|
||||
controlling = TRUE
|
||||
|
||||
remove_verb(victim, /mob/living/proc/horror_comm)
|
||||
talk_to_horror_action.Remove(victim)
|
||||
GrantControlActions()
|
||||
|
||||
victim.med_hud_set_status()
|
||||
if(target)
|
||||
victim.apply_status_effect(/datum/status_effect/agent_pinpointer/horror)
|
||||
for(var/datum/status_effect/agent_pinpointer/horror/status in victim.status_effects)
|
||||
status.scan_target = target.current
|
||||
|
||||
/mob/living/carbon/proc/release_control()
|
||||
var/mob/living/simple_animal/horror/B = has_horror_inside()
|
||||
if(B && B.host_brain)
|
||||
to_chat(src, span_danger("You withdraw your probosci, releasing control of [B.host_brain]"))
|
||||
B.detatch()
|
||||
|
||||
//Check for brain worms in head.
|
||||
/mob/proc/has_horror_inside()
|
||||
for(var/I in contents)
|
||||
if(ishorror(I))
|
||||
return I
|
||||
|
||||
|
||||
/mob/living/simple_animal/horror/proc/hit_detatch()
|
||||
if(victim.health <= 75)
|
||||
detatch()
|
||||
to_chat(src, span_warning("It appears that [victim]s brain detected danger, and hastily took over."))
|
||||
to_chat(victim, span_danger("Your body is under attack, you unconciously forced your brain to immediately take over!"))
|
||||
|
||||
/mob/living/simple_animal/horror/proc/detatch()
|
||||
if(!victim || !controlling)
|
||||
return
|
||||
|
||||
controlling = FALSE
|
||||
UnregisterSignal(victim, COMSIG_MOB_APPLY_DAMAGE)
|
||||
add_verb(victim, /mob/living/proc/horror_comm)
|
||||
RemoveControlActions()
|
||||
RefreshAbilities()
|
||||
talk_to_horror_action.Grant(victim)
|
||||
|
||||
for(var/obj/item/I in clothing)
|
||||
REMOVE_TRAIT(I, TRAIT_NODROP, HORROR_TRAIT)
|
||||
clothing = list()
|
||||
|
||||
victim.med_hud_set_status()
|
||||
victim.remove_status_effect(/datum/status_effect/agent_pinpointer/horror)
|
||||
|
||||
victim.mind.transfer_to(src)
|
||||
if(host_brain)
|
||||
host_brain.mind.transfer_to(victim)
|
||||
if(host_brain.key)
|
||||
victim.key = host_brain.key
|
||||
|
||||
log_game("[src]/([src.ckey]) released control of [victim]/([victim.ckey]")
|
||||
qdel(host_brain)
|
||||
|
||||
/mob/living/simple_animal/horror/proc/Update_Invisibility_Button()
|
||||
var/datum/action/innate/horror/action = has_ability(/datum/action/innate/horror/chameleon)
|
||||
if(action)
|
||||
action.button_icon_state = "horror_sneak_[invisible ? "true" : "false"]"
|
||||
action.UpdateButtonIcon()
|
||||
|
||||
/mob/living/simple_animal/horror/proc/GrantHorrorActions()
|
||||
for(var/datum/action/innate/horror/ability in horrorabilities)
|
||||
if("horror" in ability.category)
|
||||
ability.Grant(src)
|
||||
|
||||
/mob/living/simple_animal/horror/proc/RemoveHorrorActions()
|
||||
for(var/datum/action/innate/horror/ability in horrorabilities)
|
||||
if("horror" in ability.category)
|
||||
ability.Remove(src)
|
||||
|
||||
/mob/living/simple_animal/horror/proc/GrantInfestActions()
|
||||
for(var/datum/action/innate/horror/ability in horrorabilities)
|
||||
if("infest" in ability.category)
|
||||
ability.Grant(src)
|
||||
|
||||
/mob/living/simple_animal/horror/proc/RemoveInfestActions()
|
||||
for(var/datum/action/innate/horror/ability in horrorabilities)
|
||||
if("infest" in ability.category)
|
||||
ability.Remove(src)
|
||||
|
||||
/mob/living/simple_animal/horror/proc/GrantControlActions()
|
||||
for(var/datum/action/innate/horror/ability in horrorabilities)
|
||||
if("control" in ability.category)
|
||||
ability.Grant(victim)
|
||||
|
||||
/mob/living/simple_animal/horror/proc/RemoveControlActions()
|
||||
for(var/datum/action/innate/horror/ability in horrorabilities)
|
||||
if("control" in ability.category)
|
||||
ability.Remove(victim)
|
||||
|
||||
/mob/living/simple_animal/horror/proc/RefreshAbilities() //control abilities technically don't belong to horror
|
||||
if(victim)
|
||||
RemoveHorrorActions()
|
||||
GrantInfestActions()
|
||||
else
|
||||
RemoveInfestActions()
|
||||
GrantHorrorActions()
|
||||
461
code/modules/antagonists/horror/horror_abilities_and_upgrades.dm
Normal file
@@ -0,0 +1,461 @@
|
||||
//ABILITIES
|
||||
|
||||
/datum/action/innate/horror
|
||||
background_icon_state = "bg_ecult"
|
||||
icon_icon = 'icons/mob/actions/actions_horror.dmi'
|
||||
var/blacklisted = FALSE //If the ability can't be mutated
|
||||
var/soul_price = 0 //How much souls the ability costs to buy; if this is 0, it isn't listed on the catalog
|
||||
var/chemical_cost = 0 //How much chemicals the ability costs to use
|
||||
var/mob/living/simple_animal/horror/B //Horror holding the ability
|
||||
var/category //category for when the ability is active, "horror" is for creature, "infest" is during infestation, "controlling" is when a horror is controlling a body
|
||||
|
||||
/datum/action/innate/horror/IsAvailable()
|
||||
if(!B)
|
||||
return
|
||||
if(!B.has_chemicals(chemical_cost))
|
||||
return
|
||||
. = ..()
|
||||
|
||||
/datum/action/innate/horror/mutate
|
||||
name = "Mutate"
|
||||
desc = "Use consumed souls to mutate your abilities."
|
||||
button_icon_state = "mutate"
|
||||
blacklisted = TRUE
|
||||
category = list("horror")
|
||||
|
||||
/datum/action/innate/horror/mutate/Activate()
|
||||
to_chat(usr, span_velvet(span_bold("You focus on mutating your body...")))
|
||||
B.ui_interact(usr)
|
||||
return TRUE
|
||||
|
||||
/datum/action/innate/horror/seek_soul
|
||||
name = "Seek target soul"
|
||||
desc = "Search for a soul weak enough for you to consume."
|
||||
button_icon_state = "seek_soul"
|
||||
blacklisted = TRUE
|
||||
category = list("horror","infest")
|
||||
|
||||
/datum/action/innate/horror/seek_soul/Activate()
|
||||
B.SearchTarget()
|
||||
|
||||
/datum/action/innate/horror/consume_soul
|
||||
name = "Consume soul"
|
||||
desc = "Consume your target's soul."
|
||||
button_icon_state = "consume_soul"
|
||||
blacklisted = TRUE
|
||||
category = list("infest")
|
||||
|
||||
/datum/action/innate/horror/consume_soul/Activate()
|
||||
B.ConsumeSoul()
|
||||
|
||||
/datum/action/innate/horror/talk_to_host
|
||||
name = "Converse with Host"
|
||||
desc = "Send a silent message to your host."
|
||||
button_icon_state = "talk_to_host"
|
||||
blacklisted = TRUE
|
||||
category = list("infest")
|
||||
|
||||
/datum/action/innate/horror/talk_to_host/Activate()
|
||||
B.Communicate()
|
||||
|
||||
/datum/action/innate/horror/toggle_hide
|
||||
name = "Toggle Hide"
|
||||
desc = "Become invisible to the common eye. Toggled on or off."
|
||||
button_icon_state = "horror_hiding_false"
|
||||
blacklisted = TRUE
|
||||
category = list("horror")
|
||||
|
||||
/datum/action/innate/horror/toggle_hide/Activate()
|
||||
B.hide()
|
||||
button_icon_state = "horror_hiding_[B.hiding ? "true" : "false"]"
|
||||
UpdateButtonIcon()
|
||||
|
||||
/datum/action/innate/horror/talk_to_horror
|
||||
name = "Converse with Horror"
|
||||
desc = "Communicate mentally with your horror."
|
||||
button_icon_state = "talk_to_horror"
|
||||
blacklisted = TRUE
|
||||
var/mob/living/O
|
||||
|
||||
/datum/action/innate/horror/talk_to_horror/IsAvailable()
|
||||
if(owner.stat == DEAD)
|
||||
return
|
||||
return TRUE
|
||||
|
||||
/datum/action/innate/horror/talk_to_horror/Activate()
|
||||
var/mob/living/O = owner
|
||||
O.horror_comm()
|
||||
|
||||
/datum/action/innate/horror/talk_to_brain
|
||||
name = "Converse with Trapped Mind"
|
||||
desc = "Communicate mentally with the trapped mind of your host."
|
||||
button_icon_state = "talk_to_trapped_mind"
|
||||
blacklisted = TRUE
|
||||
category = list("control")
|
||||
|
||||
/datum/action/innate/horror/talk_to_brain/Activate()
|
||||
B.victim.trapped_mind_comm()
|
||||
|
||||
/datum/action/innate/horror/take_control
|
||||
name = "Assume Control"
|
||||
desc = "Fully connect to the brain of your host."
|
||||
button_icon_state = "horror_brain"
|
||||
blacklisted = TRUE
|
||||
category = list("infest")
|
||||
|
||||
/datum/action/innate/horror/take_control/Activate()
|
||||
B.bond_brain()
|
||||
|
||||
/datum/action/innate/horror/give_back_control
|
||||
name = "Release Control"
|
||||
desc = "Release control of your host's body."
|
||||
button_icon_state = "horror_leave"
|
||||
blacklisted = TRUE
|
||||
category = list("control")
|
||||
|
||||
/datum/action/innate/horror/give_back_control/Activate()
|
||||
B.victim.release_control()
|
||||
|
||||
/datum/action/innate/horror/leave_body
|
||||
name = "Release Host"
|
||||
desc = "Slither out of your host."
|
||||
button_icon_state = "horror_leave"
|
||||
blacklisted = TRUE
|
||||
category = list("infest")
|
||||
|
||||
/datum/action/innate/horror/leave_body/Activate()
|
||||
B.release_victim()
|
||||
|
||||
/datum/action/innate/horror/make_chems
|
||||
name = "Secrete chemicals"
|
||||
desc = "Push some chemicals into your host's bloodstream."
|
||||
icon_icon = 'icons/obj/chemical.dmi'
|
||||
button_icon_state = "minidispenser"
|
||||
blacklisted = TRUE
|
||||
category = list("infest")
|
||||
|
||||
/datum/action/innate/horror/make_chems/Activate()
|
||||
B.secrete_chemicals()
|
||||
|
||||
/datum/action/innate/horror/freeze_victim
|
||||
name = "Knockdown victim"
|
||||
desc = "Use your tentacle to trip a victim, stunning for a short duration."
|
||||
button_icon_state = "trip"
|
||||
blacklisted = TRUE
|
||||
category = list("horror")
|
||||
|
||||
/datum/action/innate/horror/freeze_victim/Activate()
|
||||
B.freeze_victim()
|
||||
UpdateButtonIcon()
|
||||
addtimer(CALLBACK(src, .proc/UpdateButtonIcon), 150)
|
||||
|
||||
/datum/action/innate/horror/freeze_victim/IsAvailable()
|
||||
if(world.time - B.used_freeze < 150)
|
||||
return FALSE
|
||||
else
|
||||
return ..()
|
||||
|
||||
//non-default abilities, can be mutated
|
||||
|
||||
/datum/action/innate/horror/tentacle
|
||||
name = "Grow Tentacle"
|
||||
desc = "Makes your host grow a tentacle in their arm. Costs 50 chemicals to activate."
|
||||
button_icon_state = "tentacle"
|
||||
chemical_cost = 50
|
||||
category = list("infest", "control")
|
||||
soul_price = 2
|
||||
|
||||
/datum/action/innate/horror/tentacle/IsAvailable()
|
||||
if(!active && !B.has_chemicals(chemical_cost))
|
||||
return
|
||||
return ..()
|
||||
|
||||
/datum/action/innate/horror/tentacle/New()
|
||||
..()
|
||||
START_PROCESSING(SSfastprocess, src)
|
||||
|
||||
/datum/action/innate/horror/tentacle/Destroy()
|
||||
STOP_PROCESSING(SSfastprocess, src)
|
||||
return ..()
|
||||
|
||||
/datum/action/innate/horror/tentacle/process()
|
||||
..()
|
||||
active = locate(/obj/item/horrortentacle) in B.victim
|
||||
UpdateButtonIcon()
|
||||
|
||||
|
||||
/datum/action/innate/horror/tentacle/Activate()
|
||||
B.use_chemicals(50)
|
||||
B.victim.visible_message(span_warning("[B.victim]'s arm contorts into tentacles!"), span_notice("Your arm transforms into a giant tentacle. Examine it to see possible uses."))
|
||||
playsound(B.victim, 'sound/effects/blobattack.ogg', 30, 1)
|
||||
to_chat(B, span_warning("You transform [B.victim]'s arm into a tentacle!"))
|
||||
var/obj/item/horrortentacle/T = new
|
||||
B.victim.put_in_hands(T)
|
||||
return TRUE
|
||||
|
||||
/datum/action/innate/horror/tentacle/Deactivate()
|
||||
B.victim.visible_message(span_warning("[B.victim]'s tentacle transforms back!"), span_notice("Your tentacle disappears!"))
|
||||
playsound(B.victim, 'sound/effects/blobattack.ogg', 30, 1)
|
||||
to_chat(B, span_warning("You transform [B.victim]'s arm back."))
|
||||
for(var/obj/item/horrortentacle/T in B.victim)
|
||||
qdel(T)
|
||||
return TRUE
|
||||
|
||||
/datum/action/innate/horror/transfer_host
|
||||
name = "Transfer to another Host"
|
||||
desc = "Move into another host directly. Grabbing makes the process faster."
|
||||
button_icon_state = "transfer_host"
|
||||
category = list("infest", "control")
|
||||
soul_price = 1
|
||||
var/transferring = FALSE
|
||||
|
||||
/datum/action/innate/horror/transfer_host/proc/is_transferring(var/mob/living/carbon/C)
|
||||
return transferring && C.Adjacent(B.victim)
|
||||
|
||||
/datum/action/innate/horror/transfer_host/Activate()
|
||||
if(transferring)
|
||||
transferring = FALSE
|
||||
to_chat(src, span_warning("You decide against leaving your host."))
|
||||
return
|
||||
|
||||
var/list/choices = list()
|
||||
for(var/mob/living/carbon/C in range(1,B.victim))
|
||||
if(C!=B.victim && C.Adjacent(B.victim))
|
||||
choices += C
|
||||
|
||||
if(!choices.len)
|
||||
return
|
||||
var/mob/living/carbon/C = choices.len > 1 ? input(owner,"Who do you wish to infest?") in null|choices : choices[1]
|
||||
if(!C || !B)
|
||||
return
|
||||
if(!C.Adjacent(B.victim))
|
||||
return
|
||||
var/obj/item/bodypart/head/head = C.get_bodypart(BODY_ZONE_HEAD)
|
||||
if(!head)
|
||||
to_chat(owner, span_warning("[C] doesn't have a head!"))
|
||||
return
|
||||
var/hasbrain = FALSE
|
||||
for(var/obj/item/organ/brain/X in C.internal_organs)
|
||||
hasbrain = TRUE
|
||||
break
|
||||
if(!hasbrain)
|
||||
to_chat(owner, span_warning("[C] doesn't have a brain!"))
|
||||
return
|
||||
if((!C.key || !C.mind) && C != B.target.current)
|
||||
to_chat(owner, span_warning("[C]'s mind seems unresponsive. Try someone else!"))
|
||||
return
|
||||
if(C.has_horror_inside())
|
||||
to_chat(owner, span_warning("[C] is already infested!"))
|
||||
return
|
||||
|
||||
to_chat(owner, span_warning("You move your tentacles away from [B.victim] and begin to transfer to [C]..."))
|
||||
var/delay = 20 SECONDS
|
||||
var/silent
|
||||
if(B.victim.pulling != C)
|
||||
silent = TRUE
|
||||
else
|
||||
switch(B.victim.grab_state)
|
||||
if(GRAB_PASSIVE)
|
||||
delay = 10 SECONDS
|
||||
if(GRAB_AGGRESSIVE)
|
||||
delay = 5 SECONDS
|
||||
if(GRAB_NECK)
|
||||
delay = 3 SECONDS
|
||||
else
|
||||
delay = 1 SECONDS
|
||||
|
||||
transferring = TRUE
|
||||
if(!do_after(B.victim, delay, target = C, extra_checks = CALLBACK(src, .proc/is_transferring, C), stayStill = FALSE))
|
||||
to_chat(owner, span_warning("As [C] moves away, your transfer gets interrupted!"))
|
||||
transferring = FALSE
|
||||
return
|
||||
transferring = FALSE
|
||||
if(!C || !B || !C.Adjacent(B.victim))
|
||||
return
|
||||
B.leave_victim()
|
||||
B.Infect(C)
|
||||
if(!silent)
|
||||
to_chat(C, span_warning("Something slimy wiggles into your ear!"))
|
||||
playsound(B, 'sound/effects/blobattack.ogg', 30, 1)
|
||||
|
||||
/datum/action/innate/horror/jumpstart_host
|
||||
name = "Revive Host"
|
||||
desc = "Bring your host back to life."
|
||||
button_icon_state = "revive"
|
||||
category = list("infest")
|
||||
soul_price = 2
|
||||
|
||||
/datum/action/innate/horror/jumpstart_host/Activate()
|
||||
B.jumpstart()
|
||||
|
||||
/datum/action/innate/horror/view_memory
|
||||
name = "View Memory"
|
||||
desc = "Read recent memory of the host you're inside of."
|
||||
button_icon_state = "view_memory"
|
||||
category = list("infest")
|
||||
soul_price = 1
|
||||
|
||||
/datum/action/innate/horror/view_memory/Activate()
|
||||
B.view_memory()
|
||||
|
||||
/datum/action/innate/horror/chameleon
|
||||
name = "Chameleon Skin"
|
||||
desc = "Adjust your skin color to blend into environment. Costs 5 chemicals per tick, also stopping chemical regeneration while active. Attacking stops the invisibility completely."
|
||||
button_icon_state = "horror_sneak_false"
|
||||
category = list("horror")
|
||||
soul_price = 1
|
||||
|
||||
/datum/action/innate/horror/chameleon/Activate()
|
||||
B.go_invisible()
|
||||
button_icon_state = "horror_sneak_[B.invisible ? "true" : "false"]"
|
||||
UpdateButtonIcon()
|
||||
|
||||
/datum/action/innate/horror/lube_spill
|
||||
name = "Lube spill"
|
||||
desc = "Makes you spin around and flail slippery lube around you. Costs 30 chemicals to activate."
|
||||
button_icon_state = "lube_spill"
|
||||
chemical_cost = 30
|
||||
category = list("horror")
|
||||
soul_price = 1
|
||||
var/cooldown = 0
|
||||
|
||||
/datum/action/innate/horror/lube_spill/IsAvailable()
|
||||
if(cooldown > world.time || !B.has_chemicals(chemical_cost) || !B.can_use_ability())
|
||||
return
|
||||
return ..()
|
||||
|
||||
/datum/action/innate/horror/lube_spill/Activate()
|
||||
B.use_chemicals(30)
|
||||
cooldown = world.time + 10 SECONDS
|
||||
UpdateButtonIcon()
|
||||
addtimer(CALLBACK(src, .proc/UpdateButtonIcon), 10 SECONDS)
|
||||
B.visible_message(span_warning("[B] starts spinning and throwing some sort of substance!"), span_notice("Your start to spin and flail oily substance everywhere!"))
|
||||
var/spins_remaining = 10
|
||||
B.icon_state = "horror_spin"
|
||||
while(spins_remaining > 0)
|
||||
playsound(B, 'sound/effects/blobattack.ogg', rand(20, 30), rand(0.5, 2))
|
||||
for(var/turf/open/t in range(1, B))
|
||||
if(prob(60) && B.Adjacent(t))
|
||||
t.MakeSlippery(TURF_WET_LUBE, 100)
|
||||
sleep(5)
|
||||
spins_remaining--
|
||||
if(!B.can_use_ability())
|
||||
return TRUE
|
||||
B.icon_state = "horror"
|
||||
return TRUE
|
||||
|
||||
//UPGRADES
|
||||
/datum/horror_upgrade
|
||||
var/name = "horror upgrade"
|
||||
var/desc = "This is an upgrade."
|
||||
var/id
|
||||
var/soul_price = 0 //How much souls an upgrade costs to buy
|
||||
var/mob/living/simple_animal/horror/B //Horror holding the upgrades
|
||||
|
||||
/datum/horror_upgrade/proc/unlock()
|
||||
if(!B)
|
||||
return
|
||||
apply_effects()
|
||||
qdel(src)
|
||||
return TRUE
|
||||
|
||||
/datum/horror_upgrade/New(owner)
|
||||
..()
|
||||
B = owner
|
||||
|
||||
/datum/horror_upgrade/proc/apply_effects()
|
||||
return
|
||||
|
||||
//Upgrades the stun ability
|
||||
/datum/horror_upgrade/paralysis
|
||||
name = "Electrocharged tentacle"
|
||||
id = "paralysis"
|
||||
desc = "Empowers your tentacle knockdown ability by giving it extra charge, knocking your victim down unconcious."
|
||||
soul_price = 3
|
||||
|
||||
/datum/horror_upgrade/paralysis/apply_effects()
|
||||
var/datum/action/innate/horror/A = B.has_ability(/datum/action/innate/horror/freeze_victim)
|
||||
if(A)
|
||||
A.name = "Paralyze Victim"
|
||||
A.desc = "Shock a victim with an electrically charged tentacle."
|
||||
A.button_icon_state = "paralyze"
|
||||
B.update_action_buttons()
|
||||
|
||||
//Increases chemical regeneration rate by 2
|
||||
/datum/horror_upgrade/chemical_regen
|
||||
name = "Efficient chemical glands"
|
||||
id = "chem_regen"
|
||||
desc = "Your chemical glands work more efficiently. Unlocking this increases your chemical regeneration."
|
||||
soul_price = 2
|
||||
|
||||
/datum/horror_upgrade/chemical_regen/apply_effects()
|
||||
B.chem_regen_rate += 2
|
||||
|
||||
//Lets horror regenerate chemicals outside of a host
|
||||
/datum/horror_upgrade/nohost_regen
|
||||
name = "Independent chemical glands"
|
||||
id = "nohost_regen"
|
||||
desc = "Your chemical glands become less parasitic and let you regenerate chemicals on their own without need for a host."
|
||||
soul_price = 2
|
||||
|
||||
//Lets horror regenerate health
|
||||
/datum/horror_upgrade/regen
|
||||
name = "Regenerative skin"
|
||||
id = "regen"
|
||||
desc = "Your skin adapts to sustained damage and slowly regenerates itself, healing your wounds over time."
|
||||
soul_price = 1
|
||||
|
||||
//Doubles horror's health pool
|
||||
/datum/horror_upgrade/hp_up
|
||||
name = "Rhino skin" //Horror can....roll?
|
||||
id = "hp_up"
|
||||
desc = "Your skin becomes hard as rock, greatly increasing your maximum health - and odds of survival outside of a host."
|
||||
soul_price = 2
|
||||
|
||||
/datum/horror_upgrade/hp_up/apply_effects()
|
||||
B.health = round(min(B.maxHealth,B.health * 2))
|
||||
B.maxHealth = round(B.maxHealth * 2)
|
||||
|
||||
//Makes horror almost invisible for a short time after leaving a host
|
||||
/datum/horror_upgrade/invisibility
|
||||
name = "Reflective fluids"
|
||||
id = "invisible_exit"
|
||||
desc = "You build up reflective solution inside host's brain. Upon exiting a host, you're briefly covered in it, rendering you near invisible for a few seconds. This mutation also makes the host unable to notice you exiting it directly."
|
||||
soul_price = 2
|
||||
|
||||
//Increases melee damage to 20
|
||||
/datum/horror_upgrade/dmg_up
|
||||
name = "Sharpened teeth"
|
||||
id = "dmg_up"
|
||||
desc = "Your teeth become sharp blades, this mutation increases your melee damage."
|
||||
soul_price = 2
|
||||
|
||||
/datum/horror_upgrade/dmg_up/apply_effects()
|
||||
B.attacktext = "crushes"
|
||||
B.attack_sound = 'sound/weapons/pierce_slow.ogg' //chunky
|
||||
B.melee_damage_lower += 10
|
||||
B.melee_damage_upper += 10
|
||||
|
||||
//Expands the reagent selection horror can make
|
||||
/datum/horror_upgrade/upgraded_chems
|
||||
name = "Advanced reagent synthesis"
|
||||
id = "upgraded_chems"
|
||||
desc = "Lets you synthetize adrenaline, salicyclic acid, oxandrolone, pentetic acid and rezadone into your host."
|
||||
soul_price = 2
|
||||
|
||||
/datum/horror_upgrade/upgraded_chems/apply_effects()
|
||||
B.horror_chems += list(/datum/horror_chem/adrenaline,/datum/horror_chem/sal_acid,/datum/horror_chem/oxandrolone,/datum/horror_chem/pen_acid,/datum/horror_chem/rezadone)
|
||||
|
||||
//faster mind control
|
||||
/datum/horror_upgrade/fast_control
|
||||
name = "Precise probosci"
|
||||
id = "fast_control"
|
||||
desc = "Your probosci become more precise, allowing you to take control over your host's brain noticably faster."
|
||||
soul_price = 2
|
||||
|
||||
//makes it longer for host to snap out of mind control
|
||||
/datum/horror_upgrade/deep_control
|
||||
name = "Insulated probosci"
|
||||
id = "deep_control"
|
||||
desc = "Your probosci become insulated, protecting them from neural shocks. This makes it harder for the host to regain control over their body."
|
||||
soul_price = 2
|
||||
96
code/modules/antagonists/horror/horror_chemicals.dm
Normal file
@@ -0,0 +1,96 @@
|
||||
/mob/living/simple_animal/horror/Topic(href, href_list, hsrc)
|
||||
if(href_list["horror_use_chem"])
|
||||
locate(href_list["src"])
|
||||
if(!istype(src, /mob/living/simple_animal/horror))
|
||||
return
|
||||
|
||||
var/topic_chem = href_list["horror_use_chem"]
|
||||
var/datum/horror_chem/C
|
||||
|
||||
for(var/datum in typesof(/datum/horror_chem))
|
||||
var/datum/horror_chem/test = new datum()
|
||||
if(test.chemname == topic_chem)
|
||||
C = test
|
||||
break
|
||||
|
||||
if(!istype(C, /datum/horror_chem))
|
||||
return
|
||||
|
||||
if(!C || !victim || controlling || !src || stat)
|
||||
return
|
||||
|
||||
if(!istype(C, /datum/horror_chem))
|
||||
return
|
||||
|
||||
if(chemicals < C.chemuse)
|
||||
to_chat(src, span_boldnotice("You need [C.chemuse] chemicals stored to use this chemical!"))
|
||||
return
|
||||
|
||||
to_chat(src, span_danger("You squirt a measure of [C.chemname] from your reservoirs into [victim]'s bloodstream."))
|
||||
victim.reagents.add_reagent(C.R, C.quantity)
|
||||
chemicals -= C.chemuse
|
||||
log_game("[src]/([src.ckey]) has injected [C.chemname] into their host [victim]/([victim.ckey])")
|
||||
|
||||
src << output(chemicals, "ViewHorror\ref[src]Chems.browser:update_chemicals")
|
||||
|
||||
..()
|
||||
|
||||
/datum/horror_chem
|
||||
var/chemname
|
||||
var/chem_desc = "This is a chemical"
|
||||
var/datum/reagent/R
|
||||
var/chemuse = 30
|
||||
var/quantity = 10
|
||||
|
||||
/datum/horror_chem/epinephrine
|
||||
chemname = "epinephrine"
|
||||
R = /datum/reagent/medicine/epinephrine
|
||||
chem_desc = "Stabilizes critical condition and slowly restores oxygen damage."
|
||||
|
||||
/datum/horror_chem/mannitol
|
||||
chemname = "mannitol"
|
||||
R = /datum/reagent/medicine/mannitol
|
||||
chem_desc = "Heals brain damage."
|
||||
|
||||
/datum/horror_chem/bicaridine
|
||||
chemname = "bicaridine"
|
||||
R = /datum/reagent/medicine/bicaridine
|
||||
chem_desc = "Heals brute damage."
|
||||
|
||||
/datum/horror_chem/kelotane
|
||||
chemname = "kelotane"
|
||||
R = /datum/reagent/medicine/kelotane
|
||||
chem_desc = "Heals burn damage."
|
||||
|
||||
/datum/horror_chem/charcoal
|
||||
chemname = "charcoal"
|
||||
R = /datum/reagent/medicine/charcoal
|
||||
chem_desc = "Slowly heals toxin damage, while also slowly removing any other chemicals."
|
||||
|
||||
/datum/horror_chem/adrenaline
|
||||
chemname = "adrenaline"
|
||||
R = /datum/reagent/medicine/changelingadrenaline
|
||||
chemuse = 100
|
||||
chem_desc = "Stimulates the brain, shrugging off effect of stuns while regenerating stamina."
|
||||
|
||||
/datum/horror_chem/rezadone
|
||||
chemname = "rezadone"
|
||||
R = /datum/reagent/medicine/rezadone
|
||||
chemuse = 50
|
||||
chem_desc = "Heals cellular damage."
|
||||
|
||||
/datum/horror_chem/pen_acid
|
||||
chemname = "pentetic acid"
|
||||
R = /datum/reagent/medicine/pen_acid
|
||||
chemuse = 50
|
||||
chem_desc = "Reduces massive amounts of radiation and toxin damage while purging other chemicals from the body."
|
||||
|
||||
/datum/horror_chem/sal_acid
|
||||
chemname = "salicyclic acid"
|
||||
R = /datum/reagent/medicine/sal_acid
|
||||
chem_desc = "Stimulates the healing of severe bruises. Rapidly heals severe bruising and slowly heals minor ones."
|
||||
|
||||
/datum/horror_chem/oxandrolone
|
||||
chemname = "oxandrolone"
|
||||
R = /datum/reagent/medicine/oxandrolone
|
||||
chem_desc = "Stimulates the healing of severe burns. Rapidly heals severe burns and slowly heals minor ones."
|
||||
333
code/modules/antagonists/horror/horror_datums.dm
Normal file
@@ -0,0 +1,333 @@
|
||||
//ANTAG DATUMS
|
||||
/datum/antagonist/horror
|
||||
name = "Horror"
|
||||
show_in_antagpanel = TRUE
|
||||
prevent_roundtype_conversion = FALSE
|
||||
show_name_in_check_antagonists = TRUE
|
||||
show_to_ghosts = TRUE
|
||||
var/datum/mind/summoner
|
||||
|
||||
/datum/antagonist/horror/on_gain()
|
||||
. = ..()
|
||||
give_objectives()
|
||||
if(ishorror(owner.current) && owner.current.mind)
|
||||
var/mob/living/simple_animal/horror/H = owner.current
|
||||
H.update_horror_hud()
|
||||
|
||||
/datum/antagonist/horror/proc/give_objectives()
|
||||
if(summoner)
|
||||
var/datum/objective/newobjective = new
|
||||
newobjective.explanation_text = "Serve your summoner, [summoner.name]."
|
||||
newobjective.owner = owner
|
||||
newobjective.completed = TRUE
|
||||
objectives += newobjective
|
||||
else
|
||||
//succ some souls
|
||||
var/datum/objective/horrorascend/ascend = new
|
||||
ascend.owner = owner
|
||||
ascend.hor = owner.current
|
||||
ascend.target_amount = rand(5, 8)
|
||||
objectives += ascend
|
||||
ascend.update_explanation_text()
|
||||
|
||||
//looking for antagonist we can assist
|
||||
var/list/possible_targets = list()
|
||||
for(var/datum/mind/M in SSticker.minds)
|
||||
if(M.current && M.current.stat != DEAD)
|
||||
if(ishuman(M.current))
|
||||
if(M.special_role)
|
||||
possible_targets += M
|
||||
|
||||
if(possible_targets.len)
|
||||
var/datum/mind/M = pick(possible_targets)
|
||||
var/datum/objective/protect/O = new
|
||||
O.owner = owner
|
||||
O.target = M
|
||||
O.explanation_text = "Protect and assist \the [M.current.real_name], the [M.assigned_role]."
|
||||
objectives += O
|
||||
|
||||
|
||||
//don't die while you're at is
|
||||
var/datum/objective/survive/survive = new
|
||||
survive.owner = owner
|
||||
objectives += survive
|
||||
|
||||
/datum/objective/horrorascend
|
||||
name = "consume souls"
|
||||
var/mob/living/simple_animal/horror/hor
|
||||
|
||||
/datum/objective/horrorascend/update_explanation_text()
|
||||
. = ..()
|
||||
explanation_text = "Consume [target_amount] souls."
|
||||
|
||||
/datum/objective/horrorascend/check_completion()
|
||||
if(hor && hor.consumed_souls >= target_amount)
|
||||
return TRUE
|
||||
return FALSE
|
||||
|
||||
|
||||
//SPAWNER
|
||||
/obj/item/horrorspawner
|
||||
name = "suspicious pet carrier"
|
||||
desc = "It contains some sort of creature inside. You can see tentacles sticking out of it."
|
||||
icon = 'icons/obj/pet_carrier.dmi'
|
||||
lefthand_file = 'icons/mob/inhands/items_lefthand.dmi'
|
||||
righthand_file = 'icons/mob/inhands/items_righthand.dmi'
|
||||
item_state = "pet_carrier"
|
||||
icon_state = "pet_carrier_occupied"
|
||||
var/used = FALSE
|
||||
color = rgb(130, 105, 160)
|
||||
|
||||
/obj/item/horrorspawner/attack_self(mob/living/user)
|
||||
if(used)
|
||||
to_chat(user, "The pet carrier appears unresponsive.")
|
||||
return
|
||||
used = TRUE
|
||||
to_chat(user, "You're attempting to wake up the creature inside the box...")
|
||||
sleep(5 SECONDS)
|
||||
var/list/mob/dead/observer/candidates = pollGhostCandidates("Do you want to play as the eldritch horror in service of [user.real_name]?", ROLE_HORROR, null, FALSE, 100)
|
||||
if(LAZYLEN(candidates))
|
||||
var/mob/dead/observer/C = pick(candidates)
|
||||
var/mob/living/simple_animal/horror/H = new /mob/living/simple_animal/horror(get_turf(src))
|
||||
H.key = C.key
|
||||
H.mind.enslave_mind_to_creator(user)
|
||||
H.mind.add_antag_datum(C)
|
||||
H.mind.memory += "You are " + span_purple(span_bold("[H.real_name]")) + ", an eldritch horror. Consume souls to evolve.<br>"
|
||||
var/datum/antagonist/horror/S = new
|
||||
S.summoner = user.mind
|
||||
S.antag_memory += "<b>[user.mind]</b> woke you from your eternal slumber. Aid them in their objectives as a token of gratitude.<br>"
|
||||
H.mind.add_antag_datum(S)
|
||||
log_game("[key_name(user)] has summoned [key_name(H)], an eldritch horror.")
|
||||
to_chat(user, span_bold("[H.real_name]</b> has awoken into your service!"))
|
||||
used = TRUE
|
||||
icon_state = "pet_carrier_open"
|
||||
sleep(5)
|
||||
var/obj/item/horrorsummonhorn/horn = new /obj/item/horrorsummonhorn(get_turf(src))
|
||||
horn.summoner = user.mind
|
||||
horn.horror = H
|
||||
to_chat(user, span_notice("A strange looking [horn] falls out of [src]!"))
|
||||
else
|
||||
to_chat(user, "The creatures looks at you with one of it's eyes before going back to slumber.")
|
||||
used = FALSE
|
||||
return
|
||||
|
||||
//Summoning horn
|
||||
/obj/item/horrorsummonhorn
|
||||
name = "old horn"
|
||||
desc = "A very old horn. You feel an incredible urge to blow into it."
|
||||
icon = 'icons/obj/items_and_weapons.dmi'
|
||||
lefthand_file = 'icons/mob/inhands/items_lefthand.dmi'
|
||||
righthand_file = 'icons/mob/inhands/items_righthand.dmi'
|
||||
item_state = "horn"
|
||||
icon_state = "horn"
|
||||
var/datum/mind/summoner
|
||||
var/mob/living/simple_animal/horror/horror
|
||||
var/cooldown
|
||||
|
||||
/obj/item/horrorsummonhorn/examine(mob/user)
|
||||
. = ..()
|
||||
if(user.mind == summoner)
|
||||
to_chat(user, span_purple("Blowing into this horn will recall the horror back to you. Be wary, the horn is loud, and may attract <B>unwanted</B> attention."))
|
||||
|
||||
/obj/item/horrorsummonhorn/attack_self(mob/living/user)
|
||||
if(cooldown > world.time)
|
||||
to_chat(user, span_notice("Take a breath before you blow [src] again."))
|
||||
return
|
||||
to_chat(user, span_notice("You take a deep breath and prepare to blow into [src]..."))
|
||||
if(do_mob(user, src, 10 SECONDS))
|
||||
if(cooldown > world.time)
|
||||
return
|
||||
cooldown = world.time + 10 SECONDS
|
||||
to_chat(src, span_notice("You blow the horn..."))
|
||||
playsound(loc, "sound/items/airhorn.ogg", 100, 1, 30)
|
||||
var/turf/summonplace = get_turf(src)
|
||||
sleep(5 SECONDS)
|
||||
if(prob(20)) //yeah you're summoning an eldritch horror allright
|
||||
new /obj/effect/temp_visual/summon(summonplace)
|
||||
sleep(10)
|
||||
var/type = pick(typesof(/mob/living/simple_animal/hostile/abomination))
|
||||
var/mob/R = new type(summonplace)
|
||||
playsound(summonplace, "sound/effects/phasein.ogg", 30)
|
||||
summonplace.visible_message(span_danger("[R] emerges!"))
|
||||
else
|
||||
if(!horror || horror.stat == DEAD)
|
||||
summonplace.visible_message(span_danger("But nothing responds to the call!"))
|
||||
else
|
||||
new /obj/effect/temp_visual/summon(summonplace)
|
||||
sleep(10)
|
||||
horror.leave_victim()
|
||||
horror.forceMove(summonplace)
|
||||
playsound(summonplace, "sound/effects/phasein.ogg", 30)
|
||||
summonplace.visible_message(span_notice("[horror] appears out of nowhere!"))
|
||||
if(user.mind != summoner)
|
||||
sleep(2 SECONDS)
|
||||
playsound(summonplace, "sound/effects/glassbr2.ogg", 30, 1)
|
||||
to_chat(user, span_danger("[src] breaks!"))
|
||||
qdel(src)
|
||||
/obj/item/horrorsummonhorn/suicide_act(mob/living/user) //"I am the prettiest unicorn that ever was!" ~Spy 2013
|
||||
user.visible_message(span_suicide("[user] stabs [user.p_their()] forehead with [src]! It looks like [user.p_theyre()] trying to commit suicide!"))
|
||||
return BRUTELOSS
|
||||
//Tentacle arm
|
||||
/obj/item/horrortentacle
|
||||
name = "tentacle"
|
||||
desc = "A long, slimy, arm-like appendage."
|
||||
icon = 'icons/obj/items_and_weapons.dmi'
|
||||
icon_state = "horrortentacle"
|
||||
item_state = "tentacle"
|
||||
lefthand_file = 'icons/mob/inhands/antag/horror_lefthand.dmi'
|
||||
righthand_file = 'icons/mob/inhands/antag/horror_righthand.dmi'
|
||||
resistance_flags = ACID_PROOF
|
||||
force = 17
|
||||
item_flags = ABSTRACT | DROPDEL
|
||||
weapon_stats = list(SWING_SPEED = 1, ENCUMBRANCE = 0, ENCUMBRANCE_TIME = 0, REACH = 2, DAMAGE_LOW = 0, DAMAGE_HIGH = 0)
|
||||
range_cooldown_mod = 0 //tentacle is designed to hit from range
|
||||
hitsound = 'sound/weapons/whip.ogg'
|
||||
/obj/item/horrortentacle/Initialize(mapload)
|
||||
. = ..()
|
||||
ADD_TRAIT(src, TRAIT_NODROP, ABSTRACT_ITEM_TRAIT)
|
||||
/obj/item/horrortentacle/examine(mob/user)
|
||||
. = ..()
|
||||
to_chat(user, span_velvet(span_bold("Functions:")))
|
||||
to_chat(user, span_velvet("<b>All attacks work up to 2 tiles away.</b>"))
|
||||
to_chat(user, span_velvet("<b>Help intent:</b> Usual help function of an arm."))
|
||||
to_chat(user, span_velvet("<b>Disarm intent:</b> Whips the tentacle, disarming your opponent."))
|
||||
to_chat(user, span_velvet("<b>Grab intent:</b> Instant aggressive grab on an opponent. Can also throw them!"))
|
||||
to_chat(user, span_velvet("<b>Harm intent:</b> Whips the tentacle, damaging your opponent."))
|
||||
to_chat(user, span_velvet("Also functions to pry open unbolted airlocks."))
|
||||
/obj/item/horrortentacle/attack(atom/target, mob/living/user)
|
||||
if(isliving(target))
|
||||
user.Beam(target,"purpletentacle",time=5)
|
||||
var/mob/living/L = target
|
||||
switch(user.a_intent)
|
||||
if(INTENT_HELP)
|
||||
L.attack_hand(user)
|
||||
return
|
||||
if(INTENT_GRAB)
|
||||
if(L != user)
|
||||
L.grabbedby(user)
|
||||
L.grippedby(user, instant = TRUE)
|
||||
L.Knockdown(30)
|
||||
return
|
||||
if(INTENT_DISARM)
|
||||
if(iscarbon(L))
|
||||
var/mob/living/carbon/C = L
|
||||
var/obj/item/I = C.get_active_held_item()
|
||||
if(I)
|
||||
if(C.dropItemToGround(I))
|
||||
playsound(loc, "sound/weapons/whipgrab.ogg", 30)
|
||||
target.visible_message(span_danger("[I] is whipped out of [C]'s hand by [user]!"),span_userdanger("A tentacle whips [I] out of your hand!"))
|
||||
return
|
||||
else
|
||||
to_chat(user, span_danger("You can't seem to pry [I] off [C]'s hands!"))
|
||||
return
|
||||
else
|
||||
C.attack_hand(user)
|
||||
return
|
||||
. = ..()
|
||||
/obj/item/horrortentacle/afterattack(atom/target, mob/user, proximity)
|
||||
if(isliving(user.pulling) && user.pulling != target)
|
||||
var/mob/living/H = user.pulling
|
||||
user.visible_message(span_warning("[user] throws [H] with [user.p_their()] [src]!"), span_warning("You throw [H] with [src]."))
|
||||
H.throw_at(target, 8, 2)
|
||||
H.Knockdown(30)
|
||||
return
|
||||
if(!proximity)
|
||||
return
|
||||
if(istype(target, /obj/machinery/door/airlock))
|
||||
var/obj/machinery/door/airlock/A = target
|
||||
if((!A.requiresID() || A.allowed(user)) && A.hasPower())
|
||||
return
|
||||
if(A.locked)
|
||||
to_chat(user, span_warning("The airlock's bolts prevent it from being forced!"))
|
||||
return
|
||||
if(A.hasPower())
|
||||
user.visible_message(span_warning("[user] jams [src] into the airlock and starts prying it open!"), span_warning("You start forcing the airlock open."),
|
||||
span_italics("You hear a metal screeching sound."))
|
||||
playsound(A, 'sound/machines/airlock_alien_prying.ogg', 150, 1)
|
||||
if(!do_after(user, 10 SECONDS, target = A))
|
||||
return
|
||||
user.visible_message(span_warning("[user] forces the airlock to open with [user.p_their()] [src]!"), span_warning("You force the airlock to open."),
|
||||
span_italics("You hear a metal screeching sound."))
|
||||
A.open(2)
|
||||
return
|
||||
. = ..()
|
||||
/obj/item/horrortentacle/suicide_act(mob/user) //this will never be called, since horror stops suicide, but might as well if they get tentacle through other means
|
||||
user.visible_message(span_suicide("[src] coils itself around [user] tightly gripping [user.p_their()] neck! It looks like [user.p_theyre()] trying to commit suicide!"))
|
||||
return (OXYLOSS)
|
||||
//Pinpointer
|
||||
/obj/screen/alert/status_effect/agent_pinpointer/horror
|
||||
name = "Soul locator"
|
||||
desc = "Find your target soul."
|
||||
/datum/status_effect/agent_pinpointer/horror
|
||||
id = "horror_pinpointer"
|
||||
minimum_range = 0
|
||||
range_fuzz_factor = 0
|
||||
tick_interval = 20
|
||||
alert_type = /obj/screen/alert/status_effect/agent_pinpointer/horror
|
||||
/datum/status_effect/agent_pinpointer/horror/scan_for_target()
|
||||
return
|
||||
//TRAPPED MIND - when horror takes control over your body, you become a mute trapped mind
|
||||
/mob/living/captive_brain
|
||||
name = "host brain"
|
||||
real_name = "host brain"
|
||||
var/datum/action/innate/resist_control/R
|
||||
var/mob/living/simple_animal/horror/H
|
||||
|
||||
/mob/living/captive_brain/Initialize(mapload, gen=1)
|
||||
..()
|
||||
R = new
|
||||
R.Grant(src)
|
||||
|
||||
/mob/living/captive_brain/say(message, bubble_type, var/list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null)
|
||||
if(client)
|
||||
if(client.prefs.muted & MUTE_IC)
|
||||
to_chat(src, span_danger("You cannot speak in IC (muted)."))
|
||||
return
|
||||
if(client.handle_spam_prevention(message,MUTE_IC))
|
||||
return
|
||||
if(ishorror(loc))
|
||||
message = sanitize(message)
|
||||
if(!message)
|
||||
return
|
||||
if(stat == 2)
|
||||
return say_dead(message)
|
||||
to_chat(src, span_alien(span_italics("You whisper silently, \"[message]\"")))
|
||||
to_chat(H.victim, span_alien(span_italics("[src] whispers, \"[message]\"")))
|
||||
for(var/M in GLOB.dead_mob_list)
|
||||
if(isobserver(M))
|
||||
var/rendered = span_changeling("<i>[src] transfers: \"[message]\"</i>")
|
||||
var/link = FOLLOW_LINK(M, H.victim)
|
||||
to_chat(M, "[link] [rendered]")
|
||||
|
||||
/mob/living/captive_brain/emote(act, m_type = null, message = null, intentional = FALSE)
|
||||
return
|
||||
|
||||
/datum/action/innate/resist_control
|
||||
name = "Resist control"
|
||||
desc = "Try to take back control over your brain. A strong nerve impulse should do it."
|
||||
background_icon_state = "bg_ecult"
|
||||
icon_icon = 'icons/mob/actions/actions_horror.dmi'
|
||||
button_icon_state = "resist_control"
|
||||
|
||||
/datum/action/innate/resist_control/Activate()
|
||||
var/mob/living/captive_brain/B = owner
|
||||
if(B)
|
||||
B.try_resist()
|
||||
|
||||
/mob/living/captive_brain/resist()
|
||||
try_resist()
|
||||
|
||||
/mob/living/captive_brain/proc/try_resist()
|
||||
var/delay = rand(20 SECONDS,30 SECONDS)
|
||||
if(H.horrorupgrades["deep_control"])
|
||||
delay += rand(20 SECONDS,30 SECONDS)
|
||||
to_chat(src, span_danger("You begin doggedly resisting the parasite's control."))
|
||||
to_chat(H.victim, span_danger("You feel the captive mind of [src] begin to resist your control."))
|
||||
addtimer(CALLBACK(src, .proc/return_control), delay)
|
||||
|
||||
/mob/living/captive_brain/proc/return_control()
|
||||
if(!H || !H.controlling)
|
||||
return
|
||||
to_chat(src, span_userdanger("With an immense exertion of will, you regain control of your body!"))
|
||||
to_chat(H.victim, span_danger("You feel control of the host brain ripped from your grasp, and retract your probosci before the wild neural impulses can damage you."))
|
||||
H.detatch()
|
||||
102
code/modules/antagonists/horror/horror_html.dm
Normal file
@@ -0,0 +1,102 @@
|
||||
/mob/living/simple_animal/horror/proc/get_html_template(content)
|
||||
var/html = {"<!DOCTYPE html">
|
||||
<html>
|
||||
<head>
|
||||
<title>Horror Chemicals</title>
|
||||
<link rel='stylesheet' type='text/css' href='icons.css'>
|
||||
<link rel='stylesheet' type='text/css' href='shared.css'>
|
||||
<style type='text/css'>
|
||||
body {
|
||||
padding: 10;
|
||||
margin: 0;
|
||||
font-size: 12px;
|
||||
color: #ffffff;
|
||||
line-height: 170%;
|
||||
font-family: Verdana, Geneva, sans-serif;
|
||||
background: #272727 url(uiBackground.png) 50% 0 repeat-x;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
a, a:link, a:visited, a:active, .link, .linkOn, .linkOff, .selected, .disabled {
|
||||
color: #ffffff;
|
||||
text-decoration: none;
|
||||
background: #40628a;
|
||||
border: 1px solid #161616;
|
||||
padding: 2px 2px 2px 2px;
|
||||
margin: 2px 2px 2px 2px;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
}
|
||||
a:hover, .linkActive:hover {
|
||||
background: #507aac;
|
||||
cursor: pointer;
|
||||
}
|
||||
img {
|
||||
border: 0px;
|
||||
}
|
||||
p {
|
||||
padding: 4px;
|
||||
margin: 0px;
|
||||
}
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
margin: 0;
|
||||
padding: 16px 0 8px 0;
|
||||
color: #517087;
|
||||
clear: both;
|
||||
}
|
||||
h1 {
|
||||
font-size: 15px;
|
||||
}
|
||||
h2 {
|
||||
font-size: 14px;
|
||||
}
|
||||
h3 {
|
||||
font-size: 13px;
|
||||
}
|
||||
h4 {
|
||||
font-size: 12px;
|
||||
}
|
||||
#header {
|
||||
margin: 3px;
|
||||
padding: 0px;
|
||||
}
|
||||
table {
|
||||
width: 570px;
|
||||
margin: 10px;
|
||||
}
|
||||
td {
|
||||
border: solid 1px #000;
|
||||
width: 560px;
|
||||
}
|
||||
.chem-select {
|
||||
width: 560px;
|
||||
margin: 5px;
|
||||
text-align: center;
|
||||
}
|
||||
.enabled {
|
||||
background-color: #0a0;
|
||||
}
|
||||
.disabled {
|
||||
background-color: #a00;
|
||||
}
|
||||
.shown {
|
||||
display: block;
|
||||
}
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
<script src="jquery.min.js"></script>
|
||||
<script type='text/javascript'>
|
||||
function update_chemicals(chemicals) {
|
||||
$('#chemicals').text(chemicals);
|
||||
}
|
||||
$(function() {
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
<body scroll='yes'><div id='content'>
|
||||
<h1 id='header'>Horror Chemicals</h1>
|
||||
<br />
|
||||
[content]
|
||||
</div></body></html>"}
|
||||
return html
|
||||
97
code/modules/antagonists/horror/horror_mutate.dm
Normal file
@@ -0,0 +1,97 @@
|
||||
// Horror mutation menu
|
||||
// Totally not a copypaste of darkspawn menu, not a copypaste of cellular emporium, i swear. Edit: now looks like guardianbuilder too
|
||||
|
||||
/mob/living/simple_animal/horror/proc/has_ability(typepath)
|
||||
for(var/datum/action/innate/horror/ability in horrorabilities)
|
||||
if(istype(ability, typepath))
|
||||
return ability
|
||||
return
|
||||
|
||||
/mob/living/simple_animal/horror/proc/add_ability(typepath)
|
||||
if(has_ability(typepath))
|
||||
return
|
||||
var/datum/action/innate/horror/action = new typepath
|
||||
action.B = src
|
||||
horrorabilities += action
|
||||
RefreshAbilities()
|
||||
to_chat(src, span_velvet("You have mutated the <b>[action.name]</b>."))
|
||||
available_points = max(0, available_points - action.soul_price)
|
||||
return TRUE
|
||||
|
||||
/mob/living/simple_animal/horror/proc/has_upgrade(id)
|
||||
return horrorupgrades[id]
|
||||
|
||||
/mob/living/simple_animal/horror/proc/add_upgrade(id)
|
||||
if(has_upgrade(id))
|
||||
return
|
||||
for(var/V in subtypesof(/datum/horror_upgrade))
|
||||
var/datum/horror_upgrade/_U = V
|
||||
if(initial(_U.id) == id)
|
||||
var/datum/horror_upgrade/U = new _U(src)
|
||||
horrorupgrades[id] = TRUE
|
||||
to_chat(src, "<span class='velvet bold'>You have adapted the \"[U.name]\" upgrade.</span>")
|
||||
available_points = max(0, available_points - U.soul_price)
|
||||
U.unlock()
|
||||
|
||||
//mutation menu, 100% ripoff of psiweb, pls don't sue
|
||||
|
||||
/mob/living/simple_animal/horror/ui_state(mob/user)
|
||||
return GLOB.always_state
|
||||
|
||||
/mob/living/simple_animal/horror/ui_interact(mob/user, datum/tgui/ui)
|
||||
ui = SStgui.try_update_ui(user, src, ui)
|
||||
if(!ui)
|
||||
ui = new(user, src, "HorrorMutate", "Horror Mutation")
|
||||
ui.open()
|
||||
|
||||
/mob/living/simple_animal/horror/ui_data(mob/user)
|
||||
var/list/data = list()
|
||||
|
||||
data["available_points"] = "[available_points] | [consumed_souls] consumed souls total"
|
||||
|
||||
var/list/abilities = list()
|
||||
var/list/upgrades = list()
|
||||
|
||||
for(var/path in subtypesof(/datum/action/innate/horror))
|
||||
var/datum/action/innate/horror/ability = path
|
||||
|
||||
if(initial(ability.blacklisted))
|
||||
continue
|
||||
|
||||
var/list/AL = list()
|
||||
AL["name"] = initial(ability.name)
|
||||
AL["typepath"] = path
|
||||
AL["desc"] = initial(ability.desc)
|
||||
AL["soul_cost"] = initial(ability.soul_price)
|
||||
AL["owned"] = has_ability(path)
|
||||
AL["can_purchase"] = !AL["owned"] && available_points >= initial(ability.soul_price)
|
||||
|
||||
abilities += list(AL)
|
||||
|
||||
data["abilities"] = abilities
|
||||
|
||||
for(var/path in subtypesof(/datum/horror_upgrade))
|
||||
var/datum/horror_upgrade/upgrade = path
|
||||
|
||||
var/list/DE = list()
|
||||
DE["name"] = initial(upgrade.name)
|
||||
DE["id"] = initial(upgrade.id)
|
||||
DE["desc"] = initial(upgrade.desc)
|
||||
DE["soul_cost"] = initial(upgrade.soul_price)
|
||||
DE["owned"] = has_upgrade(initial(upgrade.id))
|
||||
DE["can_purchase"] = !DE["owned"] && available_points >= initial(upgrade.soul_price)
|
||||
|
||||
upgrades += list(DE)
|
||||
|
||||
data["upgrades"] = upgrades
|
||||
|
||||
return data
|
||||
|
||||
/mob/living/simple_animal/horror/ui_act(action, params)
|
||||
if(..())
|
||||
return
|
||||
switch(action)
|
||||
if("unlock")
|
||||
add_ability(params["typepath"])
|
||||
if("upgrade")
|
||||
add_upgrade(params["id"])
|
||||
@@ -274,4 +274,7 @@
|
||||
if(!(mobility_flags & MOBILITY_USE)) //just while I finish up the new 'fun' suiciding verb. This is to prevent metagaming via suicide
|
||||
to_chat(src, "You can't commit suicide whilst immobile! ((You can type Ghost instead however.))")
|
||||
return
|
||||
if(has_horror_inside())
|
||||
to_chat(src, "Something inside your head stops your action!")
|
||||
return
|
||||
return TRUE
|
||||
|
||||
34
code/modules/events/horror.dm
Normal file
@@ -0,0 +1,34 @@
|
||||
/datum/round_event_control/horror
|
||||
name = "Spawn Eldritch Horror"
|
||||
typepath = /datum/round_event/ghost_role/horror
|
||||
max_occurrences = 2
|
||||
min_players = 15
|
||||
earliest_start = 20 MINUTES
|
||||
|
||||
/datum/round_event/ghost_role/horror
|
||||
minimum_required = 1
|
||||
role_name = "horror"
|
||||
fakeable = FALSE
|
||||
|
||||
/datum/round_event/ghost_role/horror/spawn_role()
|
||||
var/list/candidates = get_candidates(ROLE_HORROR, null, ROLE_HORROR)
|
||||
if(!candidates.len)
|
||||
return NOT_ENOUGH_PLAYERS
|
||||
|
||||
var/mob/dead/selected = pick_n_take(candidates)
|
||||
|
||||
var/datum/mind/player_mind = new /datum/mind(selected.key)
|
||||
player_mind.active = 1
|
||||
if(!GLOB.generic_event_spawns)
|
||||
return MAP_ERROR
|
||||
var/mob/living/simple_animal/horror/S = new /mob/living/simple_animal/horror(get_turf(pick(GLOB.generic_event_spawns)))
|
||||
player_mind.transfer_to(S)
|
||||
player_mind.assigned_role = "Eldritch Horror"
|
||||
player_mind.special_role = "Eldritch Horror"
|
||||
player_mind.add_antag_datum(/datum/antagonist/horror)
|
||||
to_chat(S, S.playstyle_string)
|
||||
SEND_SOUND(S, sound('sound/hallucinations/growl2.ogg'))
|
||||
message_admins("[ADMIN_LOOKUPFLW(S)] has been made into an eldritch horror by an event.")
|
||||
log_game("[key_name(S)] was spawned as an eldritch horror by an event.")
|
||||
spawned_mobs += S
|
||||
return SUCCESSFUL_SPAWN
|
||||
@@ -56,6 +56,10 @@
|
||||
|
||||
/obj/item/organ/brain/Remove(mob/living/carbon/C, special = 0, no_id_transfer = FALSE)
|
||||
..()
|
||||
if(!special)
|
||||
if(C.has_horror_inside())
|
||||
var/mob/living/simple_animal/horror/B = C.has_horror_inside()
|
||||
B.leave_victim()
|
||||
if(C.mind && C.mind.has_antag_datum(/datum/antagonist/changeling))
|
||||
var/datum/antagonist/changeling/bruh = C.mind.has_antag_datum(/datum/antagonist/changeling)
|
||||
for(var/d in bruh.purchasedpowers)
|
||||
|
||||
@@ -25,7 +25,10 @@
|
||||
stomach_contents.Remove(M)
|
||||
//yogs end
|
||||
M.forceMove(Tsec)
|
||||
visible_message(span_danger("[M] bursts out of [src]!"))
|
||||
if(ishorror(M)) //eldritch horror(aka. borer) check, they die along with their host to prevent mind controlled suicides
|
||||
M.gib()
|
||||
else
|
||||
visible_message(span_danger("[M] bursts out of [src]!"))
|
||||
..()
|
||||
|
||||
/mob/living/carbon/spill_organs(no_brain, no_organs, no_bodyparts)
|
||||
|
||||
@@ -73,6 +73,10 @@
|
||||
. += ""
|
||||
. += "Hivemind Vessels: [hivemind.hive_size] (+[hivemind.size_mod])"
|
||||
. += "Psychic Link Duration: [(hivemind.track_bonus + TRACKER_DEFAULT_TIME)/10] seconds"
|
||||
var/mob/living/simple_animal/horror/H = has_horror_inside()
|
||||
if(H && H.controlling)
|
||||
. += ""
|
||||
. += "Horror chemicals: [H.chemicals]"
|
||||
|
||||
if(mind)
|
||||
var/datum/antagonist/changeling/changeling = mind.has_antag_datum(/datum/antagonist/changeling)
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
/mob/living/carbon/proc/monkeyize(tr_flags = (TR_KEEPITEMS | TR_KEEPVIRUS | TR_KEEPSTUNS | TR_KEEPREAGENTS | TR_DEFAULTMSG))
|
||||
if (notransform || transformation_timer)
|
||||
return
|
||||
if(has_horror_inside())
|
||||
to_chat(src, "<span class='warning'>You feel something strongly clinging to your humanity!</span>")
|
||||
return
|
||||
//Handle items on mob
|
||||
|
||||
if(tr_flags & TR_KEEPITEMS)
|
||||
|
||||
@@ -554,6 +554,10 @@
|
||||
|
||||
semicd = FALSE
|
||||
|
||||
if(user == target && user.has_horror_inside())
|
||||
user.visible_message(span_warning("[user] decided not to shoot."), span_notice("Something inside your head stops your action!"))
|
||||
return
|
||||
|
||||
target.visible_message(span_warning("[user] pulls the trigger!"), span_userdanger("[(user == target) ? "You pull" : "[user] pulls"] the trigger!"))
|
||||
|
||||
if(chambered && chambered.BB)
|
||||
|
||||
@@ -102,6 +102,11 @@
|
||||
else if(implement_type in implements_extract)
|
||||
current_type = "extract"
|
||||
var/list/organs = target.getorganszone(target_zone)
|
||||
var/mob/living/simple_animal/horror/H = target.has_horror_inside()
|
||||
if(H)
|
||||
user.visible_message("[user] begins to extract [H] from [target]'s [parse_zone(target_zone)].",
|
||||
"<span class='notice'>You begin to extract [H] from [target]'s [parse_zone(target_zone)]...</span>")
|
||||
return TRUE
|
||||
if(!organs.len)
|
||||
to_chat(user, span_notice("There are no removable organs in [target]'s [parse_zone(target_zone)]!"))
|
||||
return -1
|
||||
@@ -143,6 +148,13 @@
|
||||
"[user] inserts something into [target]'s [parse_zone(target_zone)]!")
|
||||
|
||||
else if(current_type == "extract")
|
||||
var/mob/living/simple_animal/horror/H = target.has_horror_inside()
|
||||
if(H && H.victim == target)
|
||||
user.visible_message("[user] successfully extracts [H] from [target]'s [parse_zone(target_zone)]!",
|
||||
"<span class='notice'>You successfully extract [H] from [target]'s [parse_zone(target_zone)].</span>")
|
||||
log_combat(user, target, "surgically removed [H] from", addition="INTENT: [uppertext(user.a_intent)]")
|
||||
H.leave_victim()
|
||||
return FALSE
|
||||
if(I && I.owner == target)
|
||||
display_results(user, target, span_notice("You successfully extract [I] from [target]'s [parse_zone(target_zone)]."),
|
||||
"[user] successfully extracts [I] from [target]'s [parse_zone(target_zone)]!",
|
||||
|
||||
@@ -2010,6 +2010,17 @@ GLOBAL_LIST_INIT(uplink_items, subtypesof(/datum/uplink_item))
|
||||
cost = 20
|
||||
restricted_roles = list("Chaplain")
|
||||
surplus = 5 //Very low chance to get it in a surplus crate even without being the chaplain
|
||||
|
||||
/datum/uplink_item/role_restricted/horror
|
||||
name = "Horror-in-a-box"
|
||||
desc = "When dissecting the head of a dead Nanotrasen scientist, our surgeons noticed an incredibly peculiar creature inside and managed to extract it into safe containment. \
|
||||
Either a failed experiment or otherworldly monster, this creature has been trained to aid whoever wakes it up. If you aren't afraid of it entering your head, it can prove a useful ally. \
|
||||
We take no responsibility for your newfound madness and accept no refunds."
|
||||
item = /obj/item/horrorspawner
|
||||
cost = 16
|
||||
surplus = 0
|
||||
restricted_roles = list("Curator")
|
||||
player_minimum = 20
|
||||
|
||||
/datum/uplink_item/role_restricted/explosive_hot_potato
|
||||
name = "Exploding Hot Potato"
|
||||
|
||||
|
Before Width: | Height: | Size: 112 KiB After Width: | Height: | Size: 113 KiB |
|
Before Width: | Height: | Size: 881 KiB After Width: | Height: | Size: 882 KiB |
BIN
icons/mob/actions/actions_horror.dmi
Normal file
|
After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 230 KiB After Width: | Height: | Size: 250 KiB |
BIN
icons/mob/inhands/antag/horror_lefthand.dmi
Normal file
|
After Width: | Height: | Size: 402 B |
BIN
icons/mob/inhands/antag/horror_righthand.dmi
Normal file
|
After Width: | Height: | Size: 405 B |
|
Before Width: | Height: | Size: 113 KiB After Width: | Height: | Size: 114 KiB |
|
Before Width: | Height: | Size: 93 KiB After Width: | Height: | Size: 94 KiB |
11
strings/names/horror.txt
Normal file
@@ -0,0 +1,11 @@
|
||||
F'tan
|
||||
Hoe'lef
|
||||
Y'gge
|
||||
Uhluhtc
|
||||
Ret'sehc
|
||||
Geansrev
|
||||
Fnurgeetav
|
||||
Beq'pewks
|
||||
Man'lei
|
||||
Gvyrm-itei
|
||||
Se pge'ti
|
||||
@@ -236,6 +236,13 @@ As a Revenant, the illness inflicted on humans by Blight can be easily cured by
|
||||
As a Swarmer, you can deconstruct more things than you think. Try deconstructing light switches, buttons, air alarms and more. Experiment!
|
||||
As a Swarmer, you can teleport fellow swarmers away if you think they are in danger.
|
||||
As a Morph, you can talk while disguised, but your words have a chance of being slurred, giving you away!
|
||||
As an Eldritch Horror, improvise! You have many abilities you can mutate, most of them applying to diverse situations. Experiment!
|
||||
As an Eldritch Horror, don't forget that you're not exempt from server rules while mind-controlling someone. Their actions become your actions.
|
||||
You can remove an Eldritch Horror from someone's head via surgery. Decapitation also works!
|
||||
You can detect an Eldritch Horror inside someone's head with an advanced health analyzer.
|
||||
If you see an Eldritch Horror trying to enter someone's head, you can quickly shove it away to interrupt the process!
|
||||
Eldritch Horrors can enter any creature that has a brain organ. Yes, that includes xenomorphs.
|
||||
Contrary to popular belief, Eldritch Horrors are not "cute".
|
||||
As a Drone, you can ping other drones to alert them of areas in the station in need of repair.
|
||||
As a Ghost, you can see the inside of a container on the ground by clicking on it.
|
||||
As a Ghost, you can double click on just about anything to follow it. Or just warp around!
|
||||
@@ -269,4 +276,4 @@ Anything you can light a cigarette with, you can use to cauterize a bleeding wou
|
||||
Moderate and Severe cuts will slowly clot by themselves and don't require immediate attention on their own. Critical cuts will actively get worse with time, and are an immediate threat to the patient's life.
|
||||
Laying down will help slow down bloodloss. Death will halt it entirely.
|
||||
If you're bleeding, you can apply pressure to the limb by grabbing yourself while targeting the bleeding limb. This will slow you down and take up a hand, but it'll slow down how fast you lose blood. Note this won't help the bleeding clot any faster.
|
||||
The TOME OF HERBAL KNOWLEDGE in the ashwalker's den provides useful information on surviving the lavaland wastes.
|
||||
The TOME OF HERBAL KNOWLEDGE in the ashwalker's den provides useful information on surviving the lavaland wastes.
|
||||
58
tgui/packages/tgui/interfaces/HorrorMutate.js
Normal file
@@ -0,0 +1,58 @@
|
||||
import { useBackend } from '../backend';
|
||||
import { Button, LabeledList, Section, Box } from '../components';
|
||||
import { Window } from '../layouts';
|
||||
|
||||
export const HorrorMutate = (props, context) => {
|
||||
const { act, data } = useBackend(context);
|
||||
return (
|
||||
<Window width={900} height={480} resizable>
|
||||
<Window.Content>
|
||||
<Section title="Mutation Menu">
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Available points" right>
|
||||
{data.available_points}
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
</Section>
|
||||
<Section title="Abilities">
|
||||
<LabeledList>
|
||||
{data.abilities.map(ability => (
|
||||
|
||||
<LabeledList.Item label={ability.name} key={ability.name}>
|
||||
<Box>{ability.desc}</Box>
|
||||
<Box>Cost to unlock: {ability.soul_cost}</Box>
|
||||
<Button
|
||||
selected={ability.owned}
|
||||
disabled={!ability.can_purchase}
|
||||
onClick={() => act('unlock', {
|
||||
'typepath': ability.typepath,
|
||||
})}
|
||||
content={ability.owned ? "Unlocked" : "Unlock"}
|
||||
/>
|
||||
</LabeledList.Item>
|
||||
))}
|
||||
</LabeledList>
|
||||
</Section>
|
||||
<Section title="Upgrades">
|
||||
<LabeledList>
|
||||
{data.upgrades.map(upgrade => (
|
||||
|
||||
<LabeledList.Item label={upgrade.name} key={upgrade.name}>
|
||||
<Box>{upgrade.desc}</Box>
|
||||
<Box>Cost to unlock: {upgrade.soul_cost}</Box>
|
||||
<Button
|
||||
selected={upgrade.owned}
|
||||
disabled={!upgrade.can_purchase}
|
||||
onClick={() => act('upgrade', {
|
||||
'id': upgrade.id,
|
||||
})}
|
||||
content={upgrade.owned ? "Unlocked" : "Unlock"}
|
||||
/>
|
||||
</LabeledList.Item>
|
||||
))}
|
||||
</LabeledList>
|
||||
</Section>
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
@@ -229,6 +229,7 @@
|
||||
#include "code\_onclick\hud\generic_dextrous.dm"
|
||||
#include "code\_onclick\hud\ghost.dm"
|
||||
#include "code\_onclick\hud\guardian.dm"
|
||||
#include "code\_onclick\hud\horror.dm"
|
||||
#include "code\_onclick\hud\hud.dm"
|
||||
#include "code\_onclick\hud\human.dm"
|
||||
#include "code\_onclick\hud\lavaland_elite.dm"
|
||||
@@ -1506,6 +1507,12 @@
|
||||
#include "code\modules\antagonists\highlander\highlander.dm"
|
||||
#include "code\modules\antagonists\hivemind\hivemind.dm"
|
||||
#include "code\modules\antagonists\hivemind\vessel.dm"
|
||||
#include "code\modules\antagonists\horror\horror.dm"
|
||||
#include "code\modules\antagonists\horror\horror_abilities_and_upgrades.dm"
|
||||
#include "code\modules\antagonists\horror\horror_chemicals.dm"
|
||||
#include "code\modules\antagonists\horror\horror_datums.dm"
|
||||
#include "code\modules\antagonists\horror\horror_html.dm"
|
||||
#include "code\modules\antagonists\horror\horror_mutate.dm"
|
||||
#include "code\modules\antagonists\kudzu\kudzu.dm"
|
||||
#include "code\modules\antagonists\magic_servant\servant.dm"
|
||||
#include "code\modules\antagonists\malf\malf.dm"
|
||||
@@ -1841,6 +1848,7 @@
|
||||
#include "code\modules\events\grid_check.dm"
|
||||
#include "code\modules\events\heart_attack.dm"
|
||||
#include "code\modules\events\high_priority_bounty.dm"
|
||||
#include "code\modules\events\horror.dm"
|
||||
#include "code\modules\events\immovable_rod.dm"
|
||||
#include "code\modules\events\ion_storm.dm"
|
||||
#include "code\modules\events\major_dust.dm"
|
||||
|
||||