mirror of
https://github.com/Aurorastation/Aurora.3.git
synced 2026-08-28 07:20:09 +01:00
Please Come Save Me, I'm In A Lifepod Just Outside! (#22868)
<img width="592" height="283" alt="image" src="https://github.com/user-attachments/assets/0a3f1e59-b0f7-47e8-9575-adc161fc12ba" /> Please come outside, I'd love to meet you. Leave your space suit, you won't need it. <img width="250" height="250" alt="image" src="https://github.com/user-attachments/assets/f1866082-6360-4274-959a-a91336ac2df7" />
This commit is contained in:
@@ -49,15 +49,19 @@
|
||||
while (i <= num_groups)
|
||||
var/group_size = rand(group_size_min, group_size_max)
|
||||
for(var/j = 1, j <= group_size, j++)
|
||||
if(prob(95))
|
||||
if(prob(75))
|
||||
var/basic_psiren
|
||||
if (prob(50))
|
||||
basic_psiren = new /mob/living/simple_animal/hostile/psiren(spawn_locations[i])
|
||||
else basic_psiren = new /mob/living/simple_animal/hostile/psiren/ranged(spawn_locations[i])
|
||||
spawned_psirens += WEAKREF(basic_psiren)
|
||||
else
|
||||
var/mob/living/simple_animal/hostile/psiren/omen/new_omen = new(spawn_locations[i])
|
||||
spawned_psirens += WEAKREF(new_omen)
|
||||
if (prob(75))
|
||||
var/mob/living/simple_animal/hostile/psiren/omen/new_omen = new(spawn_locations[i])
|
||||
spawned_psirens += WEAKREF(new_omen)
|
||||
else
|
||||
var/mob/living/simple_animal/hostile/psiren/omen/beckoner/new_beckoner = new(spawn_locations[i])
|
||||
spawned_psirens += WEAKREF(new_beckoner)
|
||||
CHECK_TICK
|
||||
i++
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/// Psiren "Lasher"
|
||||
/mob/living/simple_animal/hostile/psiren
|
||||
name = "Psiren"
|
||||
name = "psiren lasher"
|
||||
desc = "A strange, squid-like xenofauna. Its eye is full of malevolence."
|
||||
icon = 'icons/mob/npc/psirens.dmi'
|
||||
icon_state = "psiren_lasher"
|
||||
@@ -10,10 +10,10 @@
|
||||
blood_overlay_icon = 'icons/mob/npc/blood_overlay.dmi'
|
||||
health = 65
|
||||
maxhealth = 65
|
||||
melee_damage_lower = 10
|
||||
melee_damage_upper = 10
|
||||
melee_damage_lower = 8
|
||||
melee_damage_upper = 8
|
||||
armor_penetration = 40
|
||||
attack_flags = DAMAGE_FLAG_SHARP|DAMAGE_FLAG_EDGE|DAMAGE_FLAG_PSIONIC
|
||||
attack_flags = DAMAGE_FLAG_EDGE|DAMAGE_FLAG_PSIONIC
|
||||
destroy_surroundings = FALSE
|
||||
break_stuff_probability = 0
|
||||
attacktext = "lashes"
|
||||
@@ -49,6 +49,7 @@
|
||||
smart_melee = TRUE
|
||||
smart_ranged = FALSE
|
||||
|
||||
/// Messages used by the psiren for its psychic damage effects.
|
||||
var/list/cursed_messages = list("DRINK DEEPLY OF THE FINAL NIGHT.",
|
||||
"YOUR LOVED ONES WILL NEVER KNOW OF YOUR JOY AGAIN.",
|
||||
"LEAVE THIS PLACE THROUGH THE NEAREST AIRLOCK.",
|
||||
@@ -80,6 +81,19 @@
|
||||
"YOU ARE GUILTY OF YOUR FOREFATHERS SINS.",
|
||||
"YOUR MISSION FAILED BEFORE IT BEGAN, THE PHORON YOU SEEK WAS NEVER REAL. YOUR NATION WILL DROWN FOR ITS WANT. IT'S ALL YOUR FAULT.")
|
||||
|
||||
/// How much damage the psiren's attacks deal directly to the victim's morale on a successful hit.
|
||||
var/psi_damage = -0.1
|
||||
|
||||
/// How much extra psychic damage is dealt per point of psi-sensitivity.
|
||||
var/psi_sensitivity_multiplier = -0.05
|
||||
|
||||
/// The chance per successful hit of triggering an ADPI message on the target.
|
||||
var/adpi_chance = 5
|
||||
|
||||
/**
|
||||
* Psirens have a unique ability to telepathically retaliate against anyone who tries to examine them.
|
||||
* It only works if the psiren is alive, and the target isn't mindshielded.
|
||||
*/
|
||||
/mob/living/simple_animal/hostile/psiren/get_examine_text(mob/user, distance, is_adjacent, infix, suffix)
|
||||
// If the victim has any psi-protection, we give them a completely normal examine proc.
|
||||
if (stat == DEAD || user.is_psi_blocked(src, FALSE))
|
||||
@@ -88,147 +102,22 @@
|
||||
// Otherwise HIJACK IT.
|
||||
return list(FONT_HUGE(SPAN_CULT(pick(cursed_messages))))
|
||||
|
||||
/datum/moodlet/psiren_psi_damage
|
||||
// Descriptor and Initial logic is bespoke for this moodlet.
|
||||
moodlet_descriptor = "residual psionic pressure."
|
||||
initial_descriptor = null
|
||||
duration = 15.0 MINUTES
|
||||
var/list/cursed_messages = list("DRINK DEEPLY OF THE FINAL NIGHT.",
|
||||
"YOUR LOVED ONES WILL NEVER KNOW OF YOUR JOY AGAIN.",
|
||||
"LEAVE THIS PLACE THROUGH THE NEAREST AIRLOCK.",
|
||||
"DEATH IS BUT THE FIRST DANCE IN ETERNITY.",
|
||||
"YOU DESERVE ALL OF YOUR SUFFERING.",
|
||||
"THE STARS HAVE FORGOTTEN YOUR NAME.",
|
||||
"YOUR BONES REMEMBER CRIMES YOU NEVER COMMITTED.",
|
||||
"EVERY GOD YOU HAVE EVER PRAYED TO IS DEAD.",
|
||||
"THE UNIVERSE REGRETS YOUR CREATION.",
|
||||
"YOU HAVE ALREADY FAILED.",
|
||||
"YOU WILL NEVER WAKE UP FROM THIS.",
|
||||
"YOUR SCREAMS ARRIVE BEFORE YOU MAKE THEM.",
|
||||
"YOU WERE WARNED.",
|
||||
"THE RIVERS RUN COLD WITH MEMORY.",
|
||||
"THE LEMURIAN SEA HUNGERS.",
|
||||
"THE LEMURIAN SEA HAS BECOME YOUR TOMB.",
|
||||
"THERE IS A DRUMBEAT BELOW REALITY.",
|
||||
"THE SUNSET LASTS FOREVER HERE.",
|
||||
"YOU CANNOT HIDE INSIDE YOUR OWN HEAD.",
|
||||
"I KNOW WHAT YOU DREAM ABOUT.",
|
||||
"I AM WEARING YOUR VOICE.",
|
||||
"YOU INVITED ME IN",
|
||||
"THE SCC SENT YOU HERE AS A FEAST FOR GIBBERING MOUTHS. YOUR LEADERS SENT YOU HERE TO DIE.",
|
||||
"YOUR MEMORY FURNISHES A REPAST FOR THE MATRIARCH.",
|
||||
"THE NIGHT SHEDS A TEAR TO TELL YOU OF FEAR, OF SORROW, OF PAIN.",
|
||||
"THE SCC HAS ALREADY VOIDED YOUR CONTRACT, THEY KNEW YOU WOULD DIE HERE.",
|
||||
"I SAW YOU WHEN YOU THOUGHT YOU WERE ALONE.",
|
||||
"THE BLOOD WILL NEVER WASH FROM YOUR HANDS.",
|
||||
"YOU ARE GUILTY OF YOUR FOREFATHERS SINS.",
|
||||
"YOUR MISSION FAILED BEFORE IT BEGAN, THE PHORON YOU SEEK WAS NEVER REAL. YOUR NATION WILL DROWN FOR ITS WANT. IT'S ALL YOUR FAULT.")
|
||||
|
||||
/datum/moodlet/psiren_psi_damage/get_moodlet_descriptor()
|
||||
// This SHOULD exist but I'm doing my due diligence in checking.
|
||||
var/mob/grandparent = astype(astype(morale_component?.resolve(), MORALE_COMPONENT)?.parent, /mob)
|
||||
if (!grandparent)
|
||||
return // No message, nobody to send a message to.
|
||||
|
||||
// Having any psi-protection will let you see the underlying moodlet.
|
||||
if (grandparent.is_psi_blocked(null, FALSE))
|
||||
/// Protection from the ranged attacks of other psirens.
|
||||
/mob/living/simple_animal/hostile/psiren/bullet_act(obj/projectile/hitting_projectile, def_zone, piercing_hit)
|
||||
var/mob/shooter = astype(hitting_projectile?.firer, /mob)
|
||||
if (!shooter || shooter.faction != "psiren")
|
||||
return ..()
|
||||
|
||||
var/spooky_message = pick(cursed_messages)
|
||||
if (prob(10))
|
||||
grandparent.play_screen_text(spooky_message, /atom/movable/screen/text/screen_text/adpi_message, COLOR_PURPLE)
|
||||
return BULLET_ACT_BLOCK
|
||||
|
||||
return FONT_HUGE(SPAN_CULT(spooky_message))
|
||||
|
||||
/datum/moodlet/psiren_psi_damage/send_initial_description(datum/owner)
|
||||
astype(astype(morale_component?.resolve(), MORALE_COMPONENT)?.parent, /mob)?.play_screen_text(pick(cursed_messages), /atom/movable/screen/text/screen_text/adpi_message, COLOR_PURPLE)
|
||||
return
|
||||
|
||||
/**
|
||||
* Special bullet type used for Psiren Darters
|
||||
* Very low damage, but very high penetration. Resisted (or strengthened) by psi-sensitivity.
|
||||
*/
|
||||
/obj/projectile/bullet/pistol/psiren
|
||||
name = "kinesis bolt"
|
||||
damage = 8
|
||||
armor_penetration = 40
|
||||
sharp = 1
|
||||
embed = FALSE
|
||||
damage_flags = DAMAGE_FLAG_BULLET | DAMAGE_FLAG_PSIONIC
|
||||
muzzle_type = /obj/effect/projectile/muzzle/laser/psiren
|
||||
tracer_type = /obj/effect/projectile/tracer/laser/psiren
|
||||
impact_type = /obj/effect/projectile/impact/laser/psiren
|
||||
icon_state = "purple_laser"
|
||||
|
||||
/// How much damage this attack deals directly to the victim's morale on a successful hit.
|
||||
var/psi_damage = -0.1
|
||||
|
||||
/// How much extra psychic damage is dealt per point of psi-sensitivity.
|
||||
var/psi_sensitivity_multiplier = -0.05
|
||||
|
||||
/// The chance per successful hit of triggering an ADPI message on the target.
|
||||
var/adpi_chance = 0
|
||||
|
||||
/obj/projectile/bullet/pistol/psiren/omen
|
||||
damage = 3
|
||||
armor_penetration = 60
|
||||
psi_damage = -5.0
|
||||
psi_sensitivity_multiplier = -2.5
|
||||
adpi_chance = 5
|
||||
|
||||
/**
|
||||
* Psiren ranged attacks deal a unique form of "psychic damage" which is governed by the full suite of rules regarding Psionic Sensitivity.
|
||||
* This is in addition to their physical "Telekinetic" damage component. Being immune to the psychic damage does not make one immune to the physical damage.
|
||||
* One can be immune to the psychic damage for a very large variety of reasons, not all of them static.
|
||||
*
|
||||
* "Psychic damage" attacks a character's Morale statistic rather than physical health, which inflicts gradually stacking penalties to Skill Tests.
|
||||
*/
|
||||
/obj/projectile/bullet/pistol/psiren/on_hit(atom/target, blocked, def_zone)
|
||||
/// Psiren melee attacks deal psychic damage, just like their ranged attacks.
|
||||
/mob/living/simple_animal/hostile/psiren/on_attack_mob(mob/hit_mob, obj/item/organ/external/limb)
|
||||
. = ..()
|
||||
if (. != BULLET_ACT_HIT || !firer)
|
||||
return .
|
||||
try_deal_psychic_damage(hit_mob, src, psi_damage, psi_sensitivity_multiplier, adpi_chance)
|
||||
|
||||
var/mob/living/victim = target
|
||||
// Immunity from telepathy will fully negate the psychic damage (but not physical damage) component.
|
||||
// This is getting checked before the morale_comp because I want to give Active Psi Protection a chance to give the player feedback that a psychic effect occurred.
|
||||
if (!istype(victim) || victim.stat == DEAD || victim.is_psi_blocked(firer, FALSE))
|
||||
return .
|
||||
|
||||
// A morale component is also required for psychic damage.
|
||||
var/datum/component/morale/morale_comp = victim.GetComponent(MORALE_COMPONENT)
|
||||
if (!morale_comp)
|
||||
return .
|
||||
|
||||
var/victim_psi_sensitivity = victim.check_psi_sensitivity()
|
||||
var/datum/moodlet/psi_damage_moodlet = morale_comp.load_moodlet(/datum/moodlet/psiren_psi_damage, FALSE)
|
||||
psi_damage_moodlet.set_moodlet(psi_damage_moodlet.get_morale_modifier() + psi_damage + min(0, (victim_psi_sensitivity * psi_sensitivity_multiplier)))
|
||||
psi_damage_moodlet.refresh_moodlet()
|
||||
|
||||
if (!prob(adpi_chance) || !SShallucinations)
|
||||
return .
|
||||
// At this point in time all checks that would be satisfied by the ADPI subsystem have already been handled,
|
||||
// now we just need to fetch a message and send one.
|
||||
var/adpi_message = SShallucinations.pick_adpi_message(victim, TRUE)
|
||||
if (!adpi_message)
|
||||
return .
|
||||
|
||||
SShallucinations.deliver_adpi_message(victim, adpi_message)
|
||||
|
||||
/obj/effect/projectile/muzzle/laser/psiren
|
||||
icon_state = "muzzle_scc" // I just like the pattern here
|
||||
light_color = COLOR_VIOLET
|
||||
|
||||
/obj/effect/projectile/tracer/laser/psiren
|
||||
icon_state = "beam_scc"
|
||||
light_color = COLOR_VIOLET
|
||||
|
||||
/obj/effect/projectile/impact/laser/psiren
|
||||
icon_state = "impact_scc" // I also just like the pattern here
|
||||
light_color = COLOR_VIOLET
|
||||
|
||||
|
||||
/// Psiren "Darter"
|
||||
/// Psiren "Darter", a simple ranged attacker with weak damage, high penetration, and weak psychic damage.
|
||||
/mob/living/simple_animal/hostile/psiren/ranged
|
||||
name = "psiren darter"
|
||||
icon_state = "psiren_darter"
|
||||
icon_living = "psiren_darter"
|
||||
icon_dead = "psiren_darter_dead"
|
||||
@@ -236,79 +125,12 @@
|
||||
projectilesound = 'sound/weapons/wave.ogg'
|
||||
projectiletype = /obj/projectile/bullet/pistol/psiren
|
||||
|
||||
/// Smoke effect used for the Omen.
|
||||
/datum/effect/effect/system/smoke_spread/psiren
|
||||
smoke_type = /obj/effect/smoke/psiren
|
||||
smoke_duration = 20 SECONDS
|
||||
|
||||
/// "Ink Cloud" colored smoke effect.
|
||||
/obj/effect/smoke/psiren
|
||||
color = "#21093a"
|
||||
time_to_live = 20 SECONDS
|
||||
|
||||
/**
|
||||
* Psiren "Omen", a support-caster type enemy that primarily inflicts direct psychic damage.
|
||||
* Relatively squishy for a medium enemy, they can react exactly once per being shot with a cloud of violet smoke.
|
||||
*/
|
||||
/mob/living/simple_animal/hostile/psiren/omen
|
||||
icon_state = "psiren_omen"
|
||||
icon_living = "psiren_omen"
|
||||
icon_dead = "psiren_omen_dead"
|
||||
health = 120
|
||||
maxhealth = 120
|
||||
ranged = TRUE
|
||||
projectilesound = 'sound/weapons/wave.ogg'
|
||||
projectiletype = /obj/projectile/bullet/pistol/psiren/omen
|
||||
var/has_ink = TRUE
|
||||
|
||||
/mob/living/simple_animal/hostile/psiren/omen/bullet_act(obj/projectile/hitting_projectile, def_zone, piercing_hit)
|
||||
if (has_ink)
|
||||
var/datum/effect/effect/system/smoke_spread/psiren/S = new/datum/effect/effect/system/smoke_spread/psiren()
|
||||
S.set_up(5, 0, loc, null)
|
||||
S.start()
|
||||
has_ink = FALSE
|
||||
return ..()
|
||||
|
||||
/mob/living/simple_animal/hostile/psiren/matriarch
|
||||
name = "Matriarch"
|
||||
icon = 'icons/mob/npc/psiren_matriarch.dmi'
|
||||
icon_state = "psiren_matriarch"
|
||||
icon_living = "psiren_matriarch"
|
||||
icon_dead = "psiren_matriarch_dead"
|
||||
health = 9999
|
||||
maxhealth = 9999
|
||||
// Speaks via Xenoglossia.
|
||||
universal_speak = TRUE
|
||||
universal_understand = TRUE
|
||||
mob_size = MOB_LARGE
|
||||
mob_weight = MOB_WEIGHT_SUPERHEAVY
|
||||
melee_damage_lower = 35
|
||||
melee_damage_upper = 40
|
||||
melee_reach = 2
|
||||
armor_penetration = 30
|
||||
meat_amount = 5
|
||||
meat_type = /obj/item/reagent_containers/food/snacks/squidmeat/psiren_tentacle_meat
|
||||
butchering_products = list(/obj/item/reagent_containers/food/snacks/squidmeat/psiren_body_meat = 5)
|
||||
|
||||
/mob/living/simple_animal/hostile/psiren/matriarch/Initialize()
|
||||
. = ..()
|
||||
add_spell(new /spell/targeted/eject_ink_cloud, "const_spell_ready")
|
||||
|
||||
/spell/targeted/eject_ink_cloud
|
||||
name = "Eject Ink Cloud"
|
||||
desc = "Eject a cloud of Lemurian fog."
|
||||
feedback = "CEIB"
|
||||
range = 0
|
||||
spell_flags = INCLUDEUSER
|
||||
charge_max = 30 SECONDS
|
||||
max_targets = 1
|
||||
|
||||
invocation_type = SpI_EMOTE
|
||||
invocation = "ejects a cloud of Lemurian fog."
|
||||
|
||||
hud_state = "cloud"
|
||||
|
||||
/spell/targeted/eject_ink_cloud/cast(mob/target, mob/living/user as mob)
|
||||
..()
|
||||
var/datum/effect/effect/system/smoke_spread/psiren/S = new/datum/effect/effect/system/smoke_spread/psiren()
|
||||
S.set_up(15, 0, user.loc, null)
|
||||
S.start()
|
||||
return TRUE
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
* A larger and older variant of an Omen. The Beckoner is a tanky medium support enemy.
|
||||
* They share their attack and defense pattern with the Omen.
|
||||
* But also gain a periodic psychic damage aura.
|
||||
*/
|
||||
/mob/living/simple_animal/hostile/psiren/omen/beckoner
|
||||
name = "psiren beckoner"
|
||||
icon = 'icons/mob/npc/psiren_beckoner.dmi'
|
||||
icon_state = "psiren_beckoner"
|
||||
icon_living = "psiren_beckoner"
|
||||
icon_dead = "psiren_beckoner_dead"
|
||||
health = 300
|
||||
maxhealth = 300
|
||||
ranged = TRUE
|
||||
projectilesound = 'sound/weapons/wave.ogg'
|
||||
projectiletype = /obj/projectile/bullet/pistol/psiren/omen
|
||||
psi_damage = -5.0
|
||||
psi_sensitivity_multiplier = -2.5
|
||||
adpi_chance = 20
|
||||
cursed_messages = list(
|
||||
"Please help me, I'm trapped outside the airlocks!",
|
||||
"You feel drawn toward the nearest airlock.",
|
||||
"Fresh air... no, vacuum... feels right somehow.",
|
||||
"The void feels strangely comforting.",
|
||||
"Please come save me, I'm in a lifepod just outside!",
|
||||
"The ship feels unbearably cramped.",
|
||||
"You need fresh air, go for a walk outside.",
|
||||
"You don't need a space suit.",
|
||||
"Did someone just knock on the hull?",
|
||||
"I left something outside. I should go get it.",
|
||||
"Come outside.",
|
||||
"The nearest airlock suddenly seems very important.",
|
||||
"Someone is calling for help outside!")
|
||||
|
||||
/// Next scheduled time to fire a "beckon" pulse.
|
||||
var/next_aura_pulse
|
||||
|
||||
/// Minimum time between beckoning pulses.
|
||||
var/min_pulse_time = 3 MINUTES
|
||||
|
||||
/// Maximum time between beckoning pulses.
|
||||
var/max_pulse_time = 9 MINUTES
|
||||
|
||||
/// Maximum radial distance at which the Omen can hit a target with a psi damage pulse.
|
||||
var/pulse_range_meters = 50 // Meters
|
||||
|
||||
/// Base psi-damage dealt per pulse. This falls off with radial distance.
|
||||
var/aura_psi_damage = -2.5
|
||||
|
||||
/mob/living/simple_animal/hostile/psiren/omen/beckoner/Initialize()
|
||||
. = ..()
|
||||
next_aura_pulse = REALTIMEOFDAY + rand(min_pulse_time, max_pulse_time)
|
||||
hunting_pulse()
|
||||
|
||||
/mob/living/simple_animal/hostile/psiren/omen/beckoner/Life(seconds_per_tick, times_fired)
|
||||
. = ..()
|
||||
if (stat == DEAD || next_aura_pulse > REALTIMEOFDAY)
|
||||
return
|
||||
|
||||
next_aura_pulse = REALTIMEOFDAY + rand(min_pulse_time, max_pulse_time)
|
||||
hunting_pulse()
|
||||
|
||||
/mob/living/simple_animal/hostile/psiren/omen/beckoner/proc/hunting_pulse()
|
||||
// TODO: Really should port tg's handling of per-z-level mob lists.
|
||||
for (var/mob/player as anything in GLOB.player_list)
|
||||
if (!player || z != player.z || player.stat == DEAD)
|
||||
continue
|
||||
|
||||
var/dx = player.x - x
|
||||
var/dy = player.y - y
|
||||
var/r = sqrt((dx * dx) + (dy * dy))
|
||||
if (r > pulse_range_meters)
|
||||
continue
|
||||
|
||||
try_deal_psychic_damage(player,
|
||||
src,
|
||||
r <= 1 ? aura_psi_damage : aura_psi_damage / r,
|
||||
r <= 1 ? psi_sensitivity_multiplier : psi_sensitivity_multiplier / r,
|
||||
100,
|
||||
/datum/moodlet/psiren_psi_damage/beckoner,
|
||||
pick(cursed_messages))
|
||||
@@ -0,0 +1,45 @@
|
||||
/mob/living/simple_animal/hostile/psiren/matriarch
|
||||
name = "Matriarch"
|
||||
icon = 'icons/mob/npc/psiren_matriarch.dmi'
|
||||
icon_state = "psiren_matriarch"
|
||||
icon_living = "psiren_matriarch"
|
||||
icon_dead = "psiren_matriarch_dead"
|
||||
health = 9999
|
||||
maxhealth = 9999
|
||||
// Speaks via Xenoglossia.
|
||||
universal_speak = TRUE
|
||||
universal_understand = TRUE
|
||||
mob_size = MOB_LARGE
|
||||
mob_weight = MOB_WEIGHT_SUPERHEAVY
|
||||
melee_damage_lower = 35
|
||||
melee_damage_upper = 40
|
||||
melee_reach = 2
|
||||
armor_penetration = 30
|
||||
meat_amount = 5
|
||||
meat_type = /obj/item/reagent_containers/food/snacks/squidmeat/psiren_tentacle_meat
|
||||
butchering_products = list(/obj/item/reagent_containers/food/snacks/squidmeat/psiren_body_meat = 5)
|
||||
|
||||
/mob/living/simple_animal/hostile/psiren/matriarch/Initialize()
|
||||
. = ..()
|
||||
add_spell(new /spell/targeted/eject_ink_cloud, "const_spell_ready")
|
||||
|
||||
/spell/targeted/eject_ink_cloud
|
||||
name = "Eject Ink Cloud"
|
||||
desc = "Eject a cloud of Lemurian fog."
|
||||
feedback = "CEIB"
|
||||
range = 0
|
||||
spell_flags = INCLUDEUSER
|
||||
charge_max = 30 SECONDS
|
||||
max_targets = 1
|
||||
|
||||
invocation_type = SpI_EMOTE
|
||||
invocation = "ejects a cloud of Lemurian fog."
|
||||
|
||||
hud_state = "cloud"
|
||||
|
||||
/spell/targeted/eject_ink_cloud/cast(mob/target, mob/living/user as mob)
|
||||
..()
|
||||
var/datum/effect/effect/system/smoke_spread/psiren/S = new/datum/effect/effect/system/smoke_spread/psiren()
|
||||
S.set_up(15, 0, user.loc, null)
|
||||
S.start()
|
||||
return TRUE
|
||||
@@ -0,0 +1,26 @@
|
||||
/**
|
||||
* Psiren "Omen", a ranged support-caster type enemy.
|
||||
* Their attacks deal almost no physical damage at all, but very high psychic damage.
|
||||
* Relatively squishy for a medium enemy, they can react exactly once per being shot with a cloud of violet smoke.
|
||||
*/
|
||||
/mob/living/simple_animal/hostile/psiren/omen
|
||||
name = "psiren omen"
|
||||
icon_state = "psiren_omen"
|
||||
icon_living = "psiren_omen"
|
||||
icon_dead = "psiren_omen_dead"
|
||||
health = 120
|
||||
maxhealth = 120
|
||||
ranged = TRUE
|
||||
projectilesound = 'sound/weapons/wave.ogg'
|
||||
projectiletype = /obj/projectile/bullet/pistol/psiren/omen
|
||||
var/has_ink = TRUE
|
||||
|
||||
/mob/living/simple_animal/hostile/psiren/omen/bullet_act(obj/projectile/hitting_projectile, def_zone, piercing_hit)
|
||||
. = ..()
|
||||
if (. == BULLET_ACT_BLOCK || !has_ink)
|
||||
return
|
||||
|
||||
var/datum/effect/effect/system/smoke_spread/psiren/S = new/datum/effect/effect/system/smoke_spread/psiren()
|
||||
S.set_up(5, 0, loc, null)
|
||||
S.start()
|
||||
has_ink = FALSE
|
||||
@@ -0,0 +1,62 @@
|
||||
/**
|
||||
* Special bullet type used for Psiren Darters
|
||||
* Very low damage, but very high penetration. Resisted (or strengthened) by psi-sensitivity.
|
||||
*/
|
||||
/obj/projectile/bullet/pistol/psiren
|
||||
name = "kinesis bolt"
|
||||
damage = 4
|
||||
armor_penetration = 40
|
||||
sharp = 1
|
||||
embed = FALSE
|
||||
damage_flags = DAMAGE_FLAG_BULLET | DAMAGE_FLAG_PSIONIC
|
||||
pass_flags = PASSTABLE | PASSGLASS | PASSGRILLE | PASSRAILING
|
||||
muzzle_type = /obj/effect/projectile/muzzle/laser/psiren
|
||||
tracer_type = /obj/effect/projectile/tracer/laser/psiren
|
||||
impact_type = /obj/effect/projectile/impact/laser/psiren
|
||||
icon_state = "purple_laser"
|
||||
|
||||
/// How much damage this attack deals directly to the victim's morale on a successful hit.
|
||||
var/psi_damage = -0.1
|
||||
|
||||
/// How much extra psychic damage is dealt per point of psi-sensitivity.
|
||||
var/psi_sensitivity_multiplier = -0.05
|
||||
|
||||
/// The chance per successful hit of triggering an ADPI message on the target.
|
||||
var/adpi_chance = 0
|
||||
|
||||
/**
|
||||
* Variant psiren bullet type for Omens.
|
||||
* Almost no physical damage at all, but very high psychic damage.
|
||||
*/
|
||||
/obj/projectile/bullet/pistol/psiren/omen
|
||||
damage = 1
|
||||
armor_penetration = 60
|
||||
psi_damage = -5.0
|
||||
psi_sensitivity_multiplier = -2.5
|
||||
adpi_chance = 5
|
||||
|
||||
/**
|
||||
* Psiren ranged attacks deal a unique form of "psychic damage" which is governed by the full suite of rules regarding Psionic Sensitivity.
|
||||
* This is in addition to their physical "Telekinetic" damage component. Being immune to the psychic damage does not make one immune to the physical damage.
|
||||
* One can be immune to the psychic damage for a very large variety of reasons, not all of them static.
|
||||
*
|
||||
* "Psychic damage" attacks a character's Morale statistic rather than physical health, which inflicts gradually stacking penalties to Skill Tests.
|
||||
*/
|
||||
/obj/projectile/bullet/pistol/psiren/on_hit(atom/target, blocked, def_zone)
|
||||
. = ..()
|
||||
if (. != BULLET_ACT_HIT || !firer)
|
||||
return
|
||||
|
||||
try_deal_psychic_damage(target, firer, psi_damage, psi_sensitivity_multiplier, adpi_chance)
|
||||
|
||||
/obj/effect/projectile/muzzle/laser/psiren
|
||||
icon_state = "muzzle_scc" // I just like the pattern here
|
||||
light_color = COLOR_VIOLET
|
||||
|
||||
/obj/effect/projectile/tracer/laser/psiren
|
||||
icon_state = "beam_scc"
|
||||
light_color = COLOR_VIOLET
|
||||
|
||||
/obj/effect/projectile/impact/laser/psiren
|
||||
icon_state = "impact_scc" // I also just like the pattern here
|
||||
light_color = COLOR_VIOLET
|
||||
@@ -0,0 +1,106 @@
|
||||
/**
|
||||
* All of the moodlet functionality required for the psychic damage effect,
|
||||
* is contained within this file.
|
||||
*/
|
||||
/datum/moodlet/psiren_psi_damage
|
||||
// Descriptor and Initial logic is bespoke for this moodlet.
|
||||
moodlet_descriptor = "residual psionic pressure."
|
||||
initial_descriptor = null
|
||||
duration = 15.0 MINUTES
|
||||
var/list/cursed_messages = list("DRINK DEEPLY OF THE FINAL NIGHT.",
|
||||
"YOUR LOVED ONES WILL NEVER KNOW OF YOUR JOY AGAIN.",
|
||||
"LEAVE THIS PLACE THROUGH THE NEAREST AIRLOCK.",
|
||||
"DEATH IS BUT THE FIRST DANCE IN ETERNITY.",
|
||||
"YOU DESERVE ALL OF YOUR SUFFERING.",
|
||||
"THE STARS HAVE FORGOTTEN YOUR NAME.",
|
||||
"YOUR BONES REMEMBER CRIMES YOU NEVER COMMITTED.",
|
||||
"EVERY GOD YOU HAVE EVER PRAYED TO IS DEAD.",
|
||||
"THE UNIVERSE REGRETS YOUR CREATION.",
|
||||
"YOU HAVE ALREADY FAILED.",
|
||||
"YOU WILL NEVER WAKE UP FROM THIS.",
|
||||
"YOUR SCREAMS ARRIVE BEFORE YOU MAKE THEM.",
|
||||
"YOU WERE WARNED.",
|
||||
"THE RIVERS RUN COLD WITH MEMORY.",
|
||||
"THE LEMURIAN SEA HUNGERS.",
|
||||
"THE LEMURIAN SEA HAS BECOME YOUR TOMB.",
|
||||
"THERE IS A DRUMBEAT BELOW REALITY.",
|
||||
"THE SUNSET LASTS FOREVER HERE.",
|
||||
"YOU CANNOT HIDE INSIDE YOUR OWN HEAD.",
|
||||
"I KNOW WHAT YOU DREAM ABOUT.",
|
||||
"I AM WEARING YOUR VOICE.",
|
||||
"YOU INVITED ME IN",
|
||||
"THE SCC SENT YOU HERE AS A FEAST FOR GIBBERING MOUTHS. YOUR LEADERS SENT YOU HERE TO DIE.",
|
||||
"YOUR MEMORY FURNISHES A REPAST FOR THE MATRIARCH.",
|
||||
"THE NIGHT SHEDS A TEAR TO TELL YOU OF FEAR, OF SORROW, OF PAIN.",
|
||||
"THE SCC HAS ALREADY VOIDED YOUR CONTRACT, THEY KNEW YOU WOULD DIE HERE.",
|
||||
"I SAW YOU WHEN YOU THOUGHT YOU WERE ALONE.",
|
||||
"THE BLOOD WILL NEVER WASH FROM YOUR HANDS.",
|
||||
"YOU ARE GUILTY OF YOUR FOREFATHERS SINS.",
|
||||
"YOUR MISSION FAILED BEFORE IT BEGAN, THE PHORON YOU SEEK WAS NEVER REAL. YOUR NATION WILL DROWN FOR ITS WANT. IT'S ALL YOUR FAULT.")
|
||||
|
||||
/// Unique psi-damage variant used by the beckoner
|
||||
/datum/moodlet/psiren_psi_damage/beckoner
|
||||
duration = 10.0 MINUTES
|
||||
cursed_messages = list(
|
||||
"Please help me, I'm trapped outside the airlocks!",
|
||||
"You feel drawn toward the nearest airlock.",
|
||||
"Fresh air... no, vacuum... feels right somehow.",
|
||||
"The void feels strangely comforting.",
|
||||
"Please come save me, I'm in a lifepod just outside!",
|
||||
"The ship feels unbearably cramped.",
|
||||
"You need fresh air, go for a walk outside.",
|
||||
"You don't need a space suit.",
|
||||
"Did someone just knock on the hull?",
|
||||
"I left something outside. I should go get it.",
|
||||
"Come outside.",
|
||||
"The nearest airlock suddenly seems very important.",
|
||||
"Someone is calling for help outside!")
|
||||
|
||||
/datum/moodlet/psiren_psi_damage/get_moodlet_descriptor()
|
||||
// This SHOULD exist but I'm doing my due diligence in checking.
|
||||
var/mob/grandparent = astype(astype(morale_component?.resolve(), MORALE_COMPONENT)?.parent, /mob)
|
||||
if (!grandparent)
|
||||
return // No message, nobody to send a message to.
|
||||
|
||||
// Having any psi-protection will let you see the underlying moodlet.
|
||||
if (grandparent.is_psi_blocked(null, FALSE))
|
||||
return ..()
|
||||
|
||||
var/spooky_message = pick(cursed_messages)
|
||||
if (prob(10))
|
||||
grandparent.play_screen_text(spooky_message, /atom/movable/screen/text/screen_text/adpi_message/psiren_spam, COLOR_PURPLE)
|
||||
|
||||
return FONT_HUGE(SPAN_CULT(spooky_message))
|
||||
|
||||
/datum/moodlet/psiren_psi_damage/send_initial_description(datum/owner)
|
||||
astype(astype(morale_component?.resolve(), MORALE_COMPONENT)?.parent, /mob)?.play_screen_text(pick(cursed_messages), /atom/movable/screen/text/screen_text/adpi_message/psiren_spam, COLOR_PURPLE)
|
||||
return
|
||||
|
||||
/proc/try_deal_psychic_damage(mob/living/victim, mob/attacker, psi_damage = -0.1, psi_sensitivity_multiplier = -0.05, adpi_chance = 0, moodlet_type = /datum/moodlet/psiren_psi_damage, message_override)
|
||||
// Immunity from telepathy will fully negate the psychic damage (but not physical damage) component.
|
||||
// This is getting checked before the morale_comp because I want to give Active Psi Protection a chance to give the player feedback that a psychic effect occurred.
|
||||
if (!istype(victim) || victim.stat == DEAD || victim.is_psi_blocked(attacker, FALSE))
|
||||
return FALSE
|
||||
|
||||
// A morale component is also required for psychic damage.
|
||||
var/datum/component/morale/morale_comp = victim.GetComponent(MORALE_COMPONENT)
|
||||
if (!morale_comp)
|
||||
return FALSE
|
||||
|
||||
var/victim_psi_sensitivity = victim.check_psi_sensitivity()
|
||||
var/datum/moodlet/psi_damage_moodlet = morale_comp.load_moodlet(moodlet_type, FALSE)
|
||||
psi_damage_moodlet.set_moodlet(psi_damage_moodlet.get_morale_modifier() + psi_damage + min(0, (victim_psi_sensitivity * psi_sensitivity_multiplier)))
|
||||
psi_damage_moodlet.refresh_moodlet()
|
||||
|
||||
if (!prob(adpi_chance) || !SShallucinations)
|
||||
return FALSE
|
||||
|
||||
// At this point in time all checks that would be satisfied by the ADPI subsystem have already been handled,
|
||||
// now we just need to fetch a message and send one.
|
||||
var/adpi_message = message_override ? message_override : SShallucinations.pick_adpi_message(victim, TRUE)
|
||||
if (!adpi_message)
|
||||
return FALSE
|
||||
|
||||
victim.play_screen_text("[adpi_message]", /atom/movable/screen/text/screen_text/adpi_message/psiren_spam, COLOR_PURPLE)
|
||||
to_chat(victim, SPAN_CULT(FONT_LARGE("[adpi_message]")))
|
||||
return TRUE
|
||||
Reference in New Issue
Block a user