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 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 isbot(A) (istype(A, /mob/living/simple_animal/bot))
|
||||||
|
|
||||||
#define isshade(A) (istype(A, /mob/living/simple_animal/shade))
|
#define isshade(A) (istype(A, /mob/living/simple_animal/shade))
|
||||||
|
|||||||
@@ -44,6 +44,7 @@
|
|||||||
#define ROLE_GANG "gangster" // Yogs
|
#define ROLE_GANG "gangster" // Yogs
|
||||||
#define ROLE_DARKSPAWN "darkspawn" // Yogs
|
#define ROLE_DARKSPAWN "darkspawn" // Yogs
|
||||||
#define ROLE_HOLOPARASITE "Holoparasite" // Yogs
|
#define ROLE_HOLOPARASITE "Holoparasite" // Yogs
|
||||||
|
#define ROLE_HORROR "Eldritch Horror" // Yogs
|
||||||
#define ROLE_INFILTRATOR "Infiltrator" // Yogs
|
#define ROLE_INFILTRATOR "Infiltrator" // Yogs
|
||||||
#define ROLE_ZOMBIE "Zombie"
|
#define ROLE_ZOMBIE "Zombie"
|
||||||
|
|
||||||
@@ -63,6 +64,7 @@ GLOBAL_LIST_INIT(special_roles, list(
|
|||||||
ROLE_MALF,
|
ROLE_MALF,
|
||||||
ROLE_REV = /datum/game_mode/revolution,
|
ROLE_REV = /datum/game_mode/revolution,
|
||||||
ROLE_ALIEN,
|
ROLE_ALIEN,
|
||||||
|
ROLE_HORROR,
|
||||||
ROLE_PAI,
|
ROLE_PAI,
|
||||||
ROLE_CULTIST = /datum/game_mode/cult,
|
ROLE_CULTIST = /datum/game_mode/cult,
|
||||||
ROLE_BLOB,
|
ROLE_BLOB,
|
||||||
|
|||||||
@@ -319,6 +319,7 @@
|
|||||||
#define GUARDIAN_TRAIT "guardian_trait"
|
#define GUARDIAN_TRAIT "guardian_trait"
|
||||||
#define RANDOM_BLACKOUTS "random_blackouts"
|
#define RANDOM_BLACKOUTS "random_blackouts"
|
||||||
#define MADE_UNCLONEABLE "made-uncloneable"
|
#define MADE_UNCLONEABLE "made-uncloneable"
|
||||||
|
#define HORROR_TRAIT "horror"
|
||||||
|
|
||||||
///Traits given by station traits
|
///Traits given by station traits
|
||||||
#define STATION_TRAIT_BANANIUM_SHIPMENTS "station_trait_bananium_shipments"
|
#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(ethereal_names, world.file2list("strings/names/ethereal.txt"))
|
||||||
GLOBAL_LIST_INIT(posibrain_names, world.file2list("strings/names/posibrain.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(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_first_names, world.file2list("strings/names/megacarp1.txt"))
|
||||||
GLOBAL_LIST_INIT(megacarp_last_names, world.file2list("strings/names/megacarp2.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)),
|
"doctors" = typecacheof(list(/mob/living/simple_animal/bot/medbot)),
|
||||||
"the supernatural" = typecacheof(list(/mob/living/simple_animal/hostile/construct,
|
"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/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)),
|
"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,
|
"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)),
|
/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/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/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/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/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/item/clothing/glasses/judicial_visor, /obj/effect/clockwork/sigil/, /obj/item/stack/tile/brass,
|
||||||
/obj/machinery/door/airlock/clockwork,
|
/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,
|
"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/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))
|
/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
|
layer = FLY_LAYER
|
||||||
duration = 0.48 SECONDS
|
duration = 0.48 SECONDS
|
||||||
mouse_opacity = 0
|
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].")]")
|
to_chat(user, "\t[span_info("Subject is allergic to the chemical [C.allergies].")]")
|
||||||
if(advanced)
|
if(advanced)
|
||||||
to_chat(user, "\t[span_info("Brain Activity Level: [(200 - M.getOrganLoss(ORGAN_SLOT_BRAIN))/2]%.")]")
|
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)
|
if (M.radiation)
|
||||||
to_chat(user, "\t[span_alert("Subject is irradiated.")]")
|
to_chat(user, "\t[span_alert("Subject is irradiated.")]")
|
||||||
|
|||||||
@@ -266,10 +266,10 @@
|
|||||||
break_counter++
|
break_counter++
|
||||||
output += "</div></div>"
|
output += "</div></div>"
|
||||||
var/list/long_job_lists = list("Civilian" = GLOB.original_civilian_positions,
|
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,
|
"Antagonist Positions" = list(ROLE_ABDUCTOR, ROLE_ALIEN, ROLE_BLOB,
|
||||||
ROLE_BROTHER, ROLE_CHANGELING, ROLE_CULTIST,
|
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_MONKEY, ROLE_NINJA, ROLE_OPERATIVE,
|
||||||
ROLE_REV, ROLE_REVENANT,
|
ROLE_REV, ROLE_REVENANT,
|
||||||
ROLE_REV_HEAD, ROLE_SERVANT_OF_RATVAR, ROLE_SYNDICATE,
|
ROLE_REV_HEAD, ROLE_SERVANT_OF_RATVAR, ROLE_SYNDICATE,
|
||||||
|
|||||||
@@ -7,7 +7,6 @@
|
|||||||
dna_cost = 1
|
dna_cost = 1
|
||||||
req_human = 1
|
req_human = 1
|
||||||
ignores_fakedeath = TRUE
|
ignores_fakedeath = TRUE
|
||||||
req_stat = DEAD
|
|
||||||
|
|
||||||
/datum/action/changeling/headcrab/sting_action(mob/user)
|
/datum/action/changeling/headcrab/sting_action(mob/user)
|
||||||
set waitfor = FALSE
|
set waitfor = FALSE
|
||||||
@@ -33,6 +32,8 @@
|
|||||||
to_chat(S, span_userdanger("Your sensors are disabled by a shower of blood!"))
|
to_chat(S, span_userdanger("Your sensors are disabled by a shower of blood!"))
|
||||||
S.Paralyze(60)
|
S.Paralyze(60)
|
||||||
var/turf = get_turf(user)
|
var/turf = get_turf(user)
|
||||||
|
var/mob/living/simple_animal/horror/H = user.has_horror_inside()
|
||||||
|
H?.leave_victim()
|
||||||
user.gib()
|
user.gib()
|
||||||
. = TRUE
|
. = TRUE
|
||||||
sleep(5) // So it's not killed in explosion
|
sleep(5) // So it's not killed in explosion
|
||||||
|
|||||||
@@ -10,6 +10,13 @@
|
|||||||
//Heals the things that the other regenerative abilities don't.
|
//Heals the things that the other regenerative abilities don't.
|
||||||
/datum/action/changeling/panacea/sting_action(mob/user)
|
/datum/action/changeling/panacea/sting_action(mob/user)
|
||||||
to_chat(user, span_notice("We cleanse impurities from our form."))
|
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(
|
var/list/bad_organs = list(
|
||||||
user.getorgan(/obj/item/organ/body_egg),
|
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
|
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.))")
|
to_chat(src, "You can't commit suicide whilst immobile! ((You can type Ghost instead however.))")
|
||||||
return
|
return
|
||||||
|
if(has_horror_inside())
|
||||||
|
to_chat(src, "Something inside your head stops your action!")
|
||||||
|
return
|
||||||
return TRUE
|
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)
|
/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))
|
if(C.mind && C.mind.has_antag_datum(/datum/antagonist/changeling))
|
||||||
var/datum/antagonist/changeling/bruh = 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)
|
for(var/d in bruh.purchasedpowers)
|
||||||
|
|||||||
@@ -25,6 +25,9 @@
|
|||||||
stomach_contents.Remove(M)
|
stomach_contents.Remove(M)
|
||||||
//yogs end
|
//yogs end
|
||||||
M.forceMove(Tsec)
|
M.forceMove(Tsec)
|
||||||
|
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]!"))
|
visible_message(span_danger("[M] bursts out of [src]!"))
|
||||||
..()
|
..()
|
||||||
|
|
||||||
|
|||||||
@@ -73,6 +73,10 @@
|
|||||||
. += ""
|
. += ""
|
||||||
. += "Hivemind Vessels: [hivemind.hive_size] (+[hivemind.size_mod])"
|
. += "Hivemind Vessels: [hivemind.hive_size] (+[hivemind.size_mod])"
|
||||||
. += "Psychic Link Duration: [(hivemind.track_bonus + TRACKER_DEFAULT_TIME)/10] seconds"
|
. += "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)
|
if(mind)
|
||||||
var/datum/antagonist/changeling/changeling = mind.has_antag_datum(/datum/antagonist/changeling)
|
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))
|
/mob/living/carbon/proc/monkeyize(tr_flags = (TR_KEEPITEMS | TR_KEEPVIRUS | TR_KEEPSTUNS | TR_KEEPREAGENTS | TR_DEFAULTMSG))
|
||||||
if (notransform || transformation_timer)
|
if (notransform || transformation_timer)
|
||||||
return
|
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
|
//Handle items on mob
|
||||||
|
|
||||||
if(tr_flags & TR_KEEPITEMS)
|
if(tr_flags & TR_KEEPITEMS)
|
||||||
|
|||||||
@@ -554,6 +554,10 @@
|
|||||||
|
|
||||||
semicd = FALSE
|
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!"))
|
target.visible_message(span_warning("[user] pulls the trigger!"), span_userdanger("[(user == target) ? "You pull" : "[user] pulls"] the trigger!"))
|
||||||
|
|
||||||
if(chambered && chambered.BB)
|
if(chambered && chambered.BB)
|
||||||
|
|||||||
@@ -102,6 +102,11 @@
|
|||||||
else if(implement_type in implements_extract)
|
else if(implement_type in implements_extract)
|
||||||
current_type = "extract"
|
current_type = "extract"
|
||||||
var/list/organs = target.getorganszone(target_zone)
|
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)
|
if(!organs.len)
|
||||||
to_chat(user, span_notice("There are no removable organs in [target]'s [parse_zone(target_zone)]!"))
|
to_chat(user, span_notice("There are no removable organs in [target]'s [parse_zone(target_zone)]!"))
|
||||||
return -1
|
return -1
|
||||||
@@ -143,6 +148,13 @@
|
|||||||
"[user] inserts something into [target]'s [parse_zone(target_zone)]!")
|
"[user] inserts something into [target]'s [parse_zone(target_zone)]!")
|
||||||
|
|
||||||
else if(current_type == "extract")
|
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)
|
if(I && I.owner == target)
|
||||||
display_results(user, target, span_notice("You successfully extract [I] from [target]'s [parse_zone(target_zone)]."),
|
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)]!",
|
"[user] successfully extracts [I] from [target]'s [parse_zone(target_zone)]!",
|
||||||
|
|||||||
@@ -2011,6 +2011,17 @@ GLOBAL_LIST_INIT(uplink_items, subtypesof(/datum/uplink_item))
|
|||||||
restricted_roles = list("Chaplain")
|
restricted_roles = list("Chaplain")
|
||||||
surplus = 5 //Very low chance to get it in a surplus crate even without being the 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
|
/datum/uplink_item/role_restricted/explosive_hot_potato
|
||||||
name = "Exploding Hot Potato"
|
name = "Exploding Hot Potato"
|
||||||
desc = "A potato rigged with explosives. On activation, a special mechanism is activated that prevents it from being dropped. \
|
desc = "A potato rigged with explosives. On activation, a special mechanism is activated that prevents it from being dropped. \
|
||||||
|
|||||||
|
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 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 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 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 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 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!
|
As a Ghost, you can double click on just about anything to follow it. Or just warp around!
|
||||||
|
|||||||
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\generic_dextrous.dm"
|
||||||
#include "code\_onclick\hud\ghost.dm"
|
#include "code\_onclick\hud\ghost.dm"
|
||||||
#include "code\_onclick\hud\guardian.dm"
|
#include "code\_onclick\hud\guardian.dm"
|
||||||
|
#include "code\_onclick\hud\horror.dm"
|
||||||
#include "code\_onclick\hud\hud.dm"
|
#include "code\_onclick\hud\hud.dm"
|
||||||
#include "code\_onclick\hud\human.dm"
|
#include "code\_onclick\hud\human.dm"
|
||||||
#include "code\_onclick\hud\lavaland_elite.dm"
|
#include "code\_onclick\hud\lavaland_elite.dm"
|
||||||
@@ -1506,6 +1507,12 @@
|
|||||||
#include "code\modules\antagonists\highlander\highlander.dm"
|
#include "code\modules\antagonists\highlander\highlander.dm"
|
||||||
#include "code\modules\antagonists\hivemind\hivemind.dm"
|
#include "code\modules\antagonists\hivemind\hivemind.dm"
|
||||||
#include "code\modules\antagonists\hivemind\vessel.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\kudzu\kudzu.dm"
|
||||||
#include "code\modules\antagonists\magic_servant\servant.dm"
|
#include "code\modules\antagonists\magic_servant\servant.dm"
|
||||||
#include "code\modules\antagonists\malf\malf.dm"
|
#include "code\modules\antagonists\malf\malf.dm"
|
||||||
@@ -1841,6 +1848,7 @@
|
|||||||
#include "code\modules\events\grid_check.dm"
|
#include "code\modules\events\grid_check.dm"
|
||||||
#include "code\modules\events\heart_attack.dm"
|
#include "code\modules\events\heart_attack.dm"
|
||||||
#include "code\modules\events\high_priority_bounty.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\immovable_rod.dm"
|
||||||
#include "code\modules\events\ion_storm.dm"
|
#include "code\modules\events\ion_storm.dm"
|
||||||
#include "code\modules\events\major_dust.dm"
|
#include "code\modules\events\major_dust.dm"
|
||||||
|
|||||||