mirror of
https://github.com/VOREStation/VOREStation.git
synced 2026-08-23 03:57:48 +01:00
Completes the /datum/component/shadekin work (#17895)
* Makes shadekin a component * Update shadekin.dm * Update human.dm * a * i love typos * Update life.dm * guh * Upports the shadekin stuff Also fixes an 8 year bug where the ability master would delete itself * boh * flashlight flickering * flickery * Update flashlight.dm * i hate lightcode * no clue * reenable * more changes * evil * Update living.dm * simplekin fix * yeah * maw fixes
This commit is contained in:
@@ -1,8 +0,0 @@
|
||||
/datum/component/shadekin
|
||||
var/dark_energy = 100
|
||||
var/max_dark_energy = 100
|
||||
var/dark_energy_infinite = FALSE
|
||||
|
||||
/datum/component/shadekin/Initialize()
|
||||
if(!ishuman(parent) && !issimplekin(parent)) //Both humankin and simplekin can have this component
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
@@ -0,0 +1,161 @@
|
||||
///Returns the shadekin component of the given mob
|
||||
/mob/living/proc/get_shadekin_component()
|
||||
var/datum/component/shadekin/SK = GetComponent(/datum/component/shadekin)
|
||||
if(SK)
|
||||
return SK
|
||||
|
||||
///Handles the shadekin's HUD updates.
|
||||
/datum/component/shadekin/proc/update_shadekin_hud()
|
||||
var/turf/T = get_turf(owner)
|
||||
if(owner.shadekin_display)
|
||||
var/l_icon = 0
|
||||
var/e_icon = 0
|
||||
|
||||
owner.shadekin_display.invisibility = INVISIBILITY_NONE
|
||||
if(T)
|
||||
var/brightness = T.get_lumcount() //Brightness in 0.0 to 1.0
|
||||
var/darkness = 1-brightness //Invert
|
||||
switch(darkness)
|
||||
if(0.80 to 1.00)
|
||||
l_icon = 0
|
||||
if(0.60 to 0.80)
|
||||
l_icon = 1
|
||||
if(0.40 to 0.60)
|
||||
l_icon = 2
|
||||
if(0.20 to 0.40)
|
||||
l_icon = 3
|
||||
if(0.00 to 0.20)
|
||||
l_icon = 4
|
||||
|
||||
switch(shadekin_get_energy())
|
||||
if(0 to 24)
|
||||
e_icon = 0
|
||||
if(25 to 49)
|
||||
e_icon = 1
|
||||
if(50 to 74)
|
||||
e_icon = 2
|
||||
if(75 to 99)
|
||||
e_icon = 3
|
||||
if(100 to INFINITY)
|
||||
e_icon = 4
|
||||
|
||||
owner.shadekin_display.icon_state = "shadekin-[l_icon]-[e_icon]"
|
||||
return
|
||||
|
||||
///For simplekin or those that have a pre-defined eye color
|
||||
/datum/component/shadekin/proc/set_eye_energy()
|
||||
if(!eye_color_influences_energy)
|
||||
return
|
||||
switch(eye_color)
|
||||
//Blue has constant, steady (slow) regen and ignores darkness.
|
||||
if(BLUE_EYES)
|
||||
set_light_and_darkness(0.75,0.75)
|
||||
if(RED_EYES)
|
||||
set_light_and_darkness(-0.5,0.5)
|
||||
if(PURPLE_EYES)
|
||||
set_light_and_darkness(-0.5,1)
|
||||
if(YELLOW_EYES)
|
||||
set_light_and_darkness(-2,3)
|
||||
if(GREEN_EYES)
|
||||
set_light_and_darkness(0.125,2)
|
||||
if(ORANGE_EYES)
|
||||
set_light_and_darkness(-0.25,0.75)
|
||||
|
||||
///Sets our eye color.
|
||||
/datum/component/shadekin/proc/set_shadekin_eyecolor(var/mob/living/carbon/human/H)
|
||||
if(!ishuman(owner))
|
||||
return eye_color //revert to default if we're not a human
|
||||
else
|
||||
H = owner
|
||||
var/eyecolor_rgb = rgb(H.r_eyes, H.g_eyes, H.b_eyes)
|
||||
|
||||
var/eyecolor_hue = rgb2num(eyecolor_rgb, COLORSPACE_HSV)[1]
|
||||
var/eyecolor_sat = rgb2num(eyecolor_rgb, COLORSPACE_HSV)[2]
|
||||
var/eyecolor_val = rgb2num(eyecolor_rgb, COLORSPACE_HSV)[3]
|
||||
|
||||
//First, clamp the saturation/value to prevent black/grey/white eyes
|
||||
if(eyecolor_sat < 10)
|
||||
eyecolor_sat = 10
|
||||
if(eyecolor_val < 40)
|
||||
eyecolor_val = 40
|
||||
|
||||
eyecolor_rgb = rgb(eyecolor_hue, eyecolor_sat, eyecolor_val, space=COLORSPACE_HSV)
|
||||
|
||||
H.r_eyes = rgb2num(eyecolor_rgb)[1]
|
||||
H.g_eyes = rgb2num(eyecolor_rgb)[2]
|
||||
H.b_eyes = rgb2num(eyecolor_rgb)[3]
|
||||
|
||||
//Now determine what color we fall into.
|
||||
switch(eyecolor_hue)
|
||||
if(0 to 20)
|
||||
eye_color = RED_EYES
|
||||
if(21 to 50)
|
||||
eye_color = ORANGE_EYES
|
||||
if(51 to 70)
|
||||
eye_color = YELLOW_EYES
|
||||
if(71 to 160)
|
||||
eye_color = GREEN_EYES
|
||||
if(161 to 260)
|
||||
eye_color = BLUE_EYES
|
||||
if(261 to 340)
|
||||
eye_color = PURPLE_EYES
|
||||
if(341 to 360)
|
||||
eye_color = RED_EYES
|
||||
return eye_color
|
||||
|
||||
///Adds the shadekin abilities to the owner.
|
||||
/datum/component/shadekin/proc/add_shadekin_abilities()
|
||||
if(!owner.ability_master || !istype(owner.ability_master, /obj/screen/movable/ability_master/shadekin))
|
||||
owner.ability_master = null
|
||||
owner.ability_master = new /obj/screen/movable/ability_master/shadekin(owner)
|
||||
for(var/datum/power/shadekin/P in shadekin_ability_datums)
|
||||
if(!(P.verbpath in owner.verbs))
|
||||
add_verb(owner, P.verbpath)
|
||||
owner.ability_master.add_shadekin_ability(
|
||||
object_given = owner,
|
||||
verb_given = P.verbpath,
|
||||
name_given = P.name,
|
||||
ability_icon_given = P.ability_icon_state,
|
||||
arguments = list()
|
||||
)
|
||||
|
||||
//wait, it's all light?
|
||||
///Allows setting the light and darkness gain.
|
||||
///@Args: light_gain, dark_gain
|
||||
/datum/component/shadekin/proc/set_light_and_darkness(light_gain, dark_gain)
|
||||
if(light_gain)
|
||||
energy_light = light_gain
|
||||
if(dark_gain)
|
||||
energy_dark = dark_gain
|
||||
|
||||
|
||||
//ENERGY HELPERS
|
||||
|
||||
/// Returns the shadekin's current energy.
|
||||
/// Returns max_energy if dark_energy_infinite is set to TRUE.
|
||||
/datum/component/shadekin/proc/shadekin_get_energy()
|
||||
if(dark_energy_infinite)
|
||||
return max_dark_energy
|
||||
return dark_energy
|
||||
|
||||
/// Returns the shadekin's maximum energy.
|
||||
/datum/component/shadekin/proc/shadekin_get_max_energy()
|
||||
return max_dark_energy
|
||||
|
||||
///Sets the shadekin's energy TO the given value.
|
||||
/datum/component/shadekin/proc/shadekin_set_energy(var/new_energy)
|
||||
if(!isnum(new_energy))
|
||||
return
|
||||
dark_energy = CLAMP(new_energy, 0, max_dark_energy)
|
||||
|
||||
///Sets the shadekin's maximum energy.
|
||||
/datum/component/shadekin/proc/shadekin_set_max_energy(var/new_max_energy)
|
||||
if(!isnum(new_max_energy))
|
||||
return //No.
|
||||
max_dark_energy = new_max_energy
|
||||
|
||||
///Adjusts the shadekin's energy by the given amount.
|
||||
/datum/component/shadekin/proc/shadekin_adjust_energy(var/amount)
|
||||
if(!isnum(amount))
|
||||
return //No
|
||||
shadekin_set_energy(dark_energy + amount)
|
||||
@@ -0,0 +1,66 @@
|
||||
//////////////////////
|
||||
/// CREATE SHADE ///
|
||||
//////////////////////
|
||||
/datum/power/shadekin/create_shade
|
||||
name = "Create Shade (25)"
|
||||
desc = "Create a field of darkness that follows you."
|
||||
verbpath = /mob/living/proc/create_shade
|
||||
ability_icon_state = "create_shade"
|
||||
|
||||
/mob/living/proc/create_shade()
|
||||
set name = "Create Shade (25)"
|
||||
set desc = "Create a field of darkness that follows you."
|
||||
set category = "Abilities.Shadekin"
|
||||
|
||||
var/ability_cost = 25
|
||||
|
||||
var/datum/component/shadekin/SK = get_shadekin_component()
|
||||
if(!istype(SK))
|
||||
to_chat(src, span_warning("Only a shadekin can use that!"))
|
||||
return FALSE
|
||||
else if(stat)
|
||||
to_chat(src, span_warning("Can't use that ability in your state!"))
|
||||
return FALSE
|
||||
else if(SK.shadekin_get_energy() < ability_cost)
|
||||
to_chat(src, span_warning("Not enough energy for that ability!"))
|
||||
return FALSE
|
||||
else if(SK.in_phase)
|
||||
to_chat(src, span_warning("You can't use that while phase shifted!"))
|
||||
return FALSE
|
||||
|
||||
playsound(src, 'sound/effects/bamf.ogg', 75, 1)
|
||||
|
||||
add_modifier(/datum/modifier/shadekin/create_shade,20 SECONDS)
|
||||
SK.shadekin_adjust_energy(-ability_cost)
|
||||
return TRUE
|
||||
|
||||
/datum/modifier/shadekin/create_shade
|
||||
name = "Shadekin Shadegen"
|
||||
desc = "Darkness envelops you."
|
||||
mob_overlay_state = ""
|
||||
|
||||
on_created_text = span_notice("You drag part of The Dark into realspace, enveloping yourself.")
|
||||
on_expired_text = span_warning("You lose your grasp on The Dark and realspace reasserts itself.")
|
||||
stacks = MODIFIER_STACK_EXTEND
|
||||
var/mob/living/simple_mob/shadekin/my_kin
|
||||
|
||||
/datum/modifier/shadekin/create_shade/tick()
|
||||
var/datum/component/shadekin/SK = my_kin.get_shadekin_component()
|
||||
if(SK && SK.in_phase)
|
||||
expire()
|
||||
|
||||
/datum/modifier/shadekin/create_shade/on_applied()
|
||||
my_kin = holder
|
||||
holder.glow_toggle = TRUE
|
||||
holder.glow_range = 8
|
||||
holder.glow_intensity = -10
|
||||
holder.glow_color = "#FFFFFF"
|
||||
holder.set_light(8, -10, "#FFFFFF")
|
||||
|
||||
/datum/modifier/shadekin/create_shade/on_expire()
|
||||
holder.glow_toggle = initial(holder.glow_toggle)
|
||||
holder.glow_range = initial(holder.glow_range)
|
||||
holder.glow_intensity = initial(holder.glow_intensity)
|
||||
holder.glow_color = initial(holder.glow_color)
|
||||
holder.set_light(0)
|
||||
my_kin = null
|
||||
@@ -0,0 +1,205 @@
|
||||
/datum/power/shadekin/dark_maw
|
||||
name = "Dark Maw (20)"
|
||||
desc = "Create a trap to capture others, or steal people from phase"
|
||||
verbpath = /mob/living/proc/dark_maw
|
||||
ability_icon_state = "dark_maw_ic"
|
||||
|
||||
/mob/living/proc/dark_maw()
|
||||
set name = "Dark Maw (20)"
|
||||
set desc = "Create a trap to capture others, or steal people from phase"
|
||||
set category = "Abilities.Shadekin"
|
||||
|
||||
var/ability_cost = 20
|
||||
|
||||
var/datum/component/shadekin/SK = get_shadekin_component()
|
||||
if(!SK)
|
||||
return FALSE
|
||||
if(stat)
|
||||
to_chat(src, span_warning("Can't use that ability in your state!"))
|
||||
return FALSE
|
||||
|
||||
if(SK.shadekin_get_energy() < ability_cost)
|
||||
to_chat(src, span_warning("Not enough energy for that ability!"))
|
||||
return FALSE
|
||||
|
||||
var/turf/T = get_turf(src)
|
||||
if(!istype(T))
|
||||
to_chat(src, span_warning("You don't seem to be able to set a trap here!"))
|
||||
return FALSE
|
||||
|
||||
if(T.get_lumcount() >= 0.5)
|
||||
to_chat(src, span_warning("There is too much light here for your trap to last!"))
|
||||
return FALSE
|
||||
|
||||
if(do_after(src, 10))
|
||||
if(SK.in_phase)
|
||||
new /obj/effect/abstract/dark_maw(loc, src, TRUE)
|
||||
else
|
||||
new /obj/effect/abstract/dark_maw(loc, src)
|
||||
SK.shadekin_adjust_energy(-ability_cost)
|
||||
|
||||
return TRUE
|
||||
return FALSE
|
||||
|
||||
/mob/living/proc/clear_dark_maws()
|
||||
set name = "Dispel dark maws"
|
||||
set desc = "Dispel any active dark maws in place"
|
||||
set category = "Abilities.Shadekin"
|
||||
|
||||
var/datum/component/shadekin/SK = get_shadekin_component()
|
||||
if(!SK)
|
||||
to_chat(src, span_warning("Only a shadekin can use that!"))
|
||||
return FALSE
|
||||
|
||||
for(var/obj/effect/abstract/dark_maw/dm in SK.active_dark_maws)
|
||||
dm.dispel()
|
||||
|
||||
/obj/effect/abstract/dark_maw
|
||||
var/mob/living/owner = null
|
||||
var/obj/belly/target = null
|
||||
var/has_signal = FALSE
|
||||
icon = 'icons/obj/Shadekin_powers.dmi'
|
||||
icon_state = "dark_maw_waiting"
|
||||
|
||||
/obj/effect/abstract/dark_maw/Initialize(mapload, var/mob/user, var/trigger_now = FALSE)
|
||||
. = ..()
|
||||
if(!isturf(loc))
|
||||
return INITIALIZE_HINT_QDEL
|
||||
var/datum/component/shadekin/SK
|
||||
if(user && isliving(user))
|
||||
owner = user
|
||||
if(owner.vore_selected)
|
||||
target = owner.vore_selected
|
||||
RegisterSignal(owner, COMSIG_PARENT_QDELETING, PROC_REF(drop_everything_and_delete))
|
||||
has_signal = TRUE
|
||||
SK = owner.get_shadekin_component()
|
||||
|
||||
var/turf/T = loc
|
||||
if(T.get_lumcount() >= 0.5)
|
||||
visible_message(span_notice("A set of shadowy lines flickers away in the light."))
|
||||
icon_state = "dark_maw_used"
|
||||
return INITIALIZE_HINT_QDEL
|
||||
|
||||
|
||||
var/mob/living/target_user = null
|
||||
for(var/mob/living/L in T)
|
||||
if(L != owner && !L.is_incorporeal())
|
||||
target_user = L
|
||||
break
|
||||
|
||||
if(istype(target_user))
|
||||
triggered_by(target_user, 1)
|
||||
// to trigger rebuild
|
||||
else if(trigger_now)
|
||||
icon_state = "dark_maw_used"
|
||||
flick("dark_maw_tr", src)
|
||||
visible_message(span_warning("A set of crystals suddenly springs from the ground and shadowy tendrils wrap around nothing before vanishing."))
|
||||
QDEL_IN(src, 3 SECONDS)
|
||||
else
|
||||
if(SK)
|
||||
SK.active_dark_maws += src
|
||||
flick("dark_maw", src)
|
||||
START_PROCESSING(SSobj, src)
|
||||
|
||||
///Called when we get a signal that our owner is being qdel'd
|
||||
/obj/effect/abstract/dark_maw/proc/drop_everything_and_delete()
|
||||
SIGNAL_HANDLER
|
||||
qdel(src)
|
||||
|
||||
/obj/effect/abstract/dark_maw/Destroy()
|
||||
STOP_PROCESSING(SSobj, src)
|
||||
if(owner)
|
||||
if(has_signal)
|
||||
UnregisterSignal(owner, COMSIG_PARENT_QDELETING)
|
||||
var/datum/component/shadekin/SK = owner.get_shadekin_component()
|
||||
if(SK)
|
||||
SK.active_dark_maws -= src
|
||||
owner = null
|
||||
target = null
|
||||
return ..()
|
||||
|
||||
/obj/effect/abstract/dark_maw/Crossed(O)
|
||||
. = ..()
|
||||
if(!isliving(O))
|
||||
return
|
||||
if(icon_state != "dark_maw_waiting")
|
||||
return
|
||||
var/mob/living/L = O
|
||||
if(!L.is_incorporeal() && (!owner || L != owner))
|
||||
triggered_by(L)
|
||||
|
||||
/obj/effect/abstract/dark_maw/process()
|
||||
var/turf/T = get_turf(src)
|
||||
if(!istype(T) || T.get_lumcount() >= 0.5)
|
||||
dispel()
|
||||
|
||||
/obj/effect/abstract/dark_maw/proc/dispel()
|
||||
if(icon_state == "dark_maw_waiting")
|
||||
visible_message(span_notice("A set of shadowy lines flickers away in the light."))
|
||||
else
|
||||
visible_message(span_notice("The crystals and shadowy tendrils dissipate with the light shone on it."))
|
||||
icon_state = "dark_maw_used"
|
||||
qdel(src)
|
||||
|
||||
/obj/effect/abstract/dark_maw/proc/triggered_by(var/mob/living/L, var/triggered_instantly = 0)
|
||||
STOP_PROCESSING(SSobj, src)
|
||||
icon_state = "dark_maw_used"
|
||||
flick("dark_maw_tr", src)
|
||||
L.AdjustStunned(4)
|
||||
visible_message(span_warning("A set of crystals spring out of the ground and shadowy tendrils start wrapping around [L]."))
|
||||
if(owner && !triggered_instantly)
|
||||
to_chat(owner, span_warning("A dark maw you deployed has triggered!"))
|
||||
addtimer(CALLBACK(src, PROC_REF(do_trigger), L), 1 SECOND, TIMER_DELETE_ME)
|
||||
|
||||
/obj/effect/abstract/dark_maw/proc/do_trigger(var/mob/living/L)
|
||||
var/will_vore = 1
|
||||
|
||||
if(!owner || !(target in owner) || !L.devourable || !L.can_be_drop_prey || !owner.can_be_drop_pred || !L.phase_vore)
|
||||
will_vore = 0
|
||||
|
||||
if(!src || src.gc_destroyed)
|
||||
//We got deleted probably, do nothing more
|
||||
return
|
||||
|
||||
if(L.loc != get_turf(src))
|
||||
visible_message(span_notice("The shadowy tendrils fail to catch anything and dissipate."))
|
||||
qdel(src)
|
||||
return
|
||||
|
||||
if(will_vore)
|
||||
visible_message(span_warning("The shadowy tendrils grab around [L] and drag them into the floor, leaving nothing behind."))
|
||||
L.forceMove(target)
|
||||
qdel(src)
|
||||
return
|
||||
|
||||
var/obj/effect/energy_net/dark/net = new /obj/effect/energy_net/dark(get_turf(src))
|
||||
if(net.buckle_mob(L))
|
||||
visible_message(span_warning("The shadowy tendrils wrap around [L] and traps them in a net of dark energy."))
|
||||
else
|
||||
visible_message(span_notice("The shadowy tendrils wrap around [L] and then dissipate, leaving them in place."))
|
||||
qdel(src)
|
||||
|
||||
/obj/effect/energy_net/dark
|
||||
name = "dark net"
|
||||
desc = "It's a net made of dark energy."
|
||||
icon = 'icons/obj/Shadekin_powers.dmi'
|
||||
icon_state = "dark_net"
|
||||
|
||||
escape_time = 30 SECONDS
|
||||
|
||||
/obj/effect/energy_net/dark/user_unbuckle_mob(mob/living/buckled_mob, mob/user)
|
||||
if(isliving(user))
|
||||
var/mob/living/unbuckler = user
|
||||
var/datum/component/shadekin/SK = unbuckler.get_shadekin_component()
|
||||
if(SK)
|
||||
visible_message(span_danger("[user] dissipates \the [src] with a touch!"))
|
||||
unbuckle_mob(buckled_mob)
|
||||
return
|
||||
. = ..()
|
||||
|
||||
/obj/effect/energy_net/dark/process()
|
||||
. = ..()
|
||||
var/turf/T = get_turf(src)
|
||||
if(!istype(T) || T.get_lumcount() >= 0.6)
|
||||
visible_message(span_notice("The tangle of dark tendrils fades away in the light."))
|
||||
qdel(src)
|
||||
@@ -0,0 +1,105 @@
|
||||
//Non-Canon on Virgo. Used downstream.
|
||||
|
||||
/datum/power/shadekin/dark_respite
|
||||
name = "Dark Respite (Only in Dark)"
|
||||
desc = "Focus yourself on healing any injuries sustained."
|
||||
verbpath = /mob/living/proc/dark_respite
|
||||
ability_icon_state = "dark_respite"
|
||||
|
||||
/mob/living/proc/dark_respite()
|
||||
set name = "Dark Respite (Only in Dark)"
|
||||
set desc = "Focus yourself on healing any injuries sustained."
|
||||
set category = "Abilities.Shadekin"
|
||||
|
||||
var/datum/component/shadekin/SK = get_shadekin_component()
|
||||
if(!SK)
|
||||
return FALSE
|
||||
if(stat)
|
||||
to_chat(src, span_warning("Can't use that ability in your state!"))
|
||||
return FALSE
|
||||
|
||||
if(!istype(get_area(src), /area/shadekin))
|
||||
to_chat(src, span_warning("Can only trigger Dark Respite in the Dark!"))
|
||||
return FALSE
|
||||
|
||||
if(SK.in_dark_respite)
|
||||
to_chat(src, span_warning("You can't use that so soon after an emergency warp!"))
|
||||
return FALSE
|
||||
|
||||
if(has_modifier_of_type(/datum/modifier/dark_respite) && !SK.manual_respite)
|
||||
to_chat(src, span_warning("You cannot manually end a Dark Respite triggered by an emergency warp!"))
|
||||
|
||||
if(SK.in_phase)
|
||||
to_chat(src, span_warning("You can't use that while phase shifted!"))
|
||||
return FALSE
|
||||
|
||||
if(has_modifier_of_type(/datum/modifier/dark_respite))
|
||||
to_chat(src, span_notice("You stop focusing the Dark on healing yourself."))
|
||||
SK.manual_respite = FALSE
|
||||
remove_a_modifier_of_type(/datum/modifier/dark_respite)
|
||||
return TRUE
|
||||
to_chat(src, span_notice("You start focusing the Dark on healing yourself. (Leave the dark or trigger the ability again to end this.)"))
|
||||
SK.manual_respite = TRUE
|
||||
add_modifier(/datum/modifier/dark_respite)
|
||||
return TRUE
|
||||
|
||||
/datum/modifier/dark_respite
|
||||
name = "Dark Respite"
|
||||
pain_immunity = 1
|
||||
var/datum/component/shadekin/SK
|
||||
|
||||
// Override this for special effects when it gets added to the mob.
|
||||
/datum/modifier/dark_respite/on_applied()
|
||||
SK = holder.get_shadekin_component()
|
||||
if(!SK)
|
||||
expire()
|
||||
return
|
||||
|
||||
/datum/modifier/dark_respite/tick()
|
||||
if(!SK)
|
||||
expire()
|
||||
return
|
||||
var/mob/living/carbon/human/H
|
||||
if(istype(holder, /mob/living/carbon/human))
|
||||
H = holder
|
||||
if(H.nutrition)
|
||||
H.add_chemical_effect(CE_BLOODRESTORE, 5)
|
||||
|
||||
if(istype(get_area(H), /area/shadekin))
|
||||
pain_immunity = TRUE
|
||||
//Very good healing, but only in the Dark.
|
||||
holder.adjustFireLoss((-0.25))
|
||||
holder.adjustBruteLoss((-0.25))
|
||||
holder.adjustToxLoss((-0.25))
|
||||
holder.heal_organ_damage(3, 0)
|
||||
if(H)
|
||||
H.add_chemical_effect(CE_ANTIBIOTIC, ANTIBIO_SUPER)
|
||||
for(var/obj/item/organ/I in H.internal_organs)
|
||||
if(I.robotic >= ORGAN_ROBOT)
|
||||
continue
|
||||
if(I.damage > 0)
|
||||
I.damage = max(I.damage - 0.25, 0)
|
||||
if(I.damage <= 5 && I.organ_tag == O_EYES)
|
||||
H.sdisabilities &= ~BLIND
|
||||
for(var/obj/item/organ/external/O in H.organs)
|
||||
if(O.status & ORGAN_BROKEN)
|
||||
O.mend_fracture() //Only works if the bone won't rebreak, as usual
|
||||
for(var/datum/wound/W in O.wounds)
|
||||
if(W.bleeding())
|
||||
W.damage = max(W.damage - 3, 0)
|
||||
if(W.damage <= 0)
|
||||
O.wounds -= W
|
||||
if(W.internal)
|
||||
W.damage = max(W.damage - 3, 0)
|
||||
if(W.damage <= 0)
|
||||
O.wounds -= W
|
||||
else
|
||||
if(SK.manual_respite)
|
||||
to_chat(holder, span_notice("As you leave the Dark, you stop focusing the Dark on healing yourself."))
|
||||
SK.manual_respite = FALSE
|
||||
expire()
|
||||
if(pain_immunity)
|
||||
pain_immunity = 0
|
||||
|
||||
/datum/modifier/dark_respite/on_expire()
|
||||
SK = null
|
||||
@@ -0,0 +1,205 @@
|
||||
GLOBAL_LIST_BOILERPLATE(all_darkportal_hubs, /obj/structure/dark_portal/hub)
|
||||
GLOBAL_LIST_BOILERPLATE(all_darkportal_minions, /obj/structure/dark_portal/minion)
|
||||
/obj/structure/dark_portal
|
||||
name = "Dark portal"
|
||||
icon = 'icons/obj/shadekin_portal.dmi'
|
||||
density = TRUE
|
||||
anchored = TRUE
|
||||
var/locked = null
|
||||
var/locked_name = ""
|
||||
var/one_time_use = FALSE
|
||||
var/precision = 1
|
||||
|
||||
/obj/structure/dark_portal/proc/close_portal()
|
||||
return
|
||||
|
||||
/obj/structure/dark_portal/proc/teleport(atom/movable/M as mob|obj)
|
||||
if(!locked)
|
||||
return
|
||||
if(isliving(M))
|
||||
var/mob/living/to_check = M
|
||||
var/datum/component/shadekin/SK = to_check.GetComponent(/datum/component/shadekin)
|
||||
if(SK && SK.in_dark_respite)
|
||||
to_chat(M, span_warning("You can't go through this portal so soon after an emergency warp!"))
|
||||
to_check.Stun(10)
|
||||
return
|
||||
|
||||
do_teleport(M, locked, precision, local = FALSE, bohsafe = TRUE)
|
||||
|
||||
if(one_time_use)
|
||||
one_time_use = 0
|
||||
close_portal()
|
||||
return
|
||||
|
||||
// These things are always on. By default they always teleport you to themselves.
|
||||
/obj/structure/dark_portal/hub
|
||||
icon_state = "hub_portal"
|
||||
desc = "A large portal. Touching it without going through may alter the destination."
|
||||
var/list/destination_station_areas // Override this in map files!
|
||||
var/list/destination_wilderness_areas // Override this in map files!
|
||||
|
||||
/obj/structure/dark_portal/hub/Initialize(mapload)
|
||||
. = ..()
|
||||
locked = src
|
||||
locked_name = src.name
|
||||
|
||||
/obj/structure/dark_portal/hub/Initialize(mapload)
|
||||
. = ..()
|
||||
set_light(2, 3, "#ffffff")
|
||||
|
||||
/obj/structure/dark_portal/hub/close_portal()
|
||||
locked = src
|
||||
locked_name = src.name
|
||||
precision = 1
|
||||
|
||||
/obj/structure/dark_portal/hub/attack_hand(mob/living/user)
|
||||
if(!isliving(user))
|
||||
return
|
||||
var/datum/component/shadekin/SK = user.GetComponent(/datum/component/shadekin)
|
||||
if(SK)
|
||||
if(SK.in_dark_respite)
|
||||
to_chat(user, span_warning("You can't use this so soon after an emergency warp!"))
|
||||
return
|
||||
if(SK.in_phase)
|
||||
to_chat(user, span_warning("You can't use this while phase shifted!"))
|
||||
return
|
||||
if(locked != src)
|
||||
var/confirm = tgui_alert(user, "This portal is currently open to [locked_name]. Change the portal destination?", "Change Portal Destination", list("Yes", "Cancel"))
|
||||
if(!confirm || confirm == "Cancel")
|
||||
return
|
||||
var/list/L = list()
|
||||
for(var/obj/structure/dark_portal/hub/H in GLOB.all_darkportal_hubs)
|
||||
if(H == src)
|
||||
L["This Portal"] = H
|
||||
else
|
||||
L[H.name] = H
|
||||
for(var/obj/structure/dark_portal/minion/M in GLOB.all_darkportal_minions)
|
||||
var/tmpname = "Dark Portal ([get_area(M)])"
|
||||
L[tmpname] = M
|
||||
var/desc = tgui_input_list(user, "Please select a hub portal to connect to.", "Portal Menu", L)
|
||||
if(!desc)
|
||||
return
|
||||
locked = L[desc]
|
||||
locked_name = desc
|
||||
return
|
||||
else if(locked_name == "somewhere on the station" || locked_name == "somewhere in the wilderness")
|
||||
to_chat(user, span_warning("The portal distorts for a moment, before returning to how it was, seemingly already determined where to send you."))
|
||||
return
|
||||
else if(istype(user, /mob/living/carbon/human))
|
||||
var/mob/living/carbon/human/H = user
|
||||
if(H.job && H.job != JOB_OUTSIDER && LAZYLEN(destination_station_areas))
|
||||
var/list/floors = list()
|
||||
var/area/picked_area = pick(destination_station_areas)
|
||||
for(var/turf/simulated/floor/floor in get_area_turfs(picked_area))
|
||||
floors.Add(floor)
|
||||
if(!LAZYLEN(floors))
|
||||
log_and_message_admins("[src]: There were no floors to teleport to in [picked_area]!")
|
||||
to_chat(user, span_warning("The portal distorts for a moment, seemingly unable to determine where to send you."))
|
||||
close_portal()
|
||||
destination_station_areas.Remove(picked_area)
|
||||
return
|
||||
locked = pick(floors)
|
||||
locked_name = "somewhere on the station"
|
||||
one_time_use = TRUE
|
||||
precision = 0
|
||||
to_chat(user, span_notice("The portal distorts for a moment, resolving itself soon after. You feel like it will lead you to the station now."))
|
||||
return
|
||||
if(!LAZYLEN(destination_wilderness_areas))
|
||||
to_chat(user, span_warning("The portal distorts for a moment, seemingly unable to determine where to send you."))
|
||||
close_portal()
|
||||
return
|
||||
var/list/floors = list()
|
||||
var/area/picked_area = pick(destination_wilderness_areas)
|
||||
for(var/turf/simulated/floor/floor in get_area_turfs(picked_area))
|
||||
floors.Add(floor)
|
||||
if(!LAZYLEN(floors))
|
||||
log_and_message_admins("[src]: There were no floors to teleport to in [picked_area]!")
|
||||
to_chat(user, span_warning("The portal distorts for a moment, seemingly unable to determine where to send you."))
|
||||
close_portal()
|
||||
destination_wilderness_areas.Remove(picked_area)
|
||||
return
|
||||
locked = pick(floors)
|
||||
locked_name = "somewhere in the wilderness"
|
||||
one_time_use = TRUE
|
||||
precision = 0
|
||||
to_chat(user, span_notice("The portal distorts for a moment, resolving itself soon after. You feel like it will lead you to somewhere in the wilderness now."))
|
||||
return
|
||||
|
||||
/obj/structure/dark_portal/hub/Bumped(M as mob|obj)
|
||||
spawn()
|
||||
teleport(M)
|
||||
return
|
||||
|
||||
// These things have an off state. A shadekin has to boop them to turn it on
|
||||
/obj/structure/dark_portal/minion
|
||||
icon_state = "minion0"
|
||||
|
||||
/obj/structure/dark_portal/minion/close_portal()
|
||||
locked = null
|
||||
locked_name = ""
|
||||
precision = 1
|
||||
icon_state = "minion0"
|
||||
|
||||
/obj/structure/dark_portal/minion/attack_hand(mob/living/user)
|
||||
if(!isliving(user))
|
||||
return
|
||||
var/datum/component/shadekin/SK = user.GetComponent(/datum/component/shadekin)
|
||||
if(SK)
|
||||
if(SK.in_dark_respite)
|
||||
to_chat(user, span_warning("You can't use this so soon after an emergency warp!"))
|
||||
return FALSE
|
||||
if(SK.in_phase)
|
||||
to_chat(user, span_warning("You can't use this while phase shifted!"))
|
||||
return FALSE
|
||||
if(icon_state == "minion1")
|
||||
var/confirm = tgui_alert(user, "This portal is currently open to [locked_name]. Close this portal to the dark?", "Close Portal", list("Yes", "Cancel"))
|
||||
if(!confirm || confirm == "Cancel")
|
||||
return
|
||||
if(confirm == "Yes")
|
||||
close_portal()
|
||||
return
|
||||
if(SK.shadekin_get_energy() < 10)
|
||||
to_chat(user, span_warning("Not enough energy to open up the portal! (10 required)"))
|
||||
return
|
||||
if(!LAZYLEN(GLOB.all_darkportal_hubs))
|
||||
to_chat(user, span_warning("No hub portals exist!"))
|
||||
return
|
||||
if(LAZYLEN(GLOB.all_darkportal_hubs) == 1)
|
||||
SK.shadekin_adjust_energy(user, -10)
|
||||
var/obj/structure/dark_portal/target = GLOB.all_darkportal_hubs[1]
|
||||
locked = target
|
||||
locked_name = target.name
|
||||
icon_state = "minion1"
|
||||
addtimer(CALLBACK(src, PROC_REF(check_to_close),target), 5 MINUTES, TIMER_DELETE_ME)
|
||||
return
|
||||
var/list/L = list()
|
||||
for(var/obj/structure/dark_portal/hub/H in GLOB.all_darkportal_hubs)
|
||||
L[H.name] = H
|
||||
var/desc = tgui_input_list(user, "Please select a hub portal to connect to.", "Portal Menu", L)
|
||||
if(!desc)
|
||||
return
|
||||
locked = L[desc]
|
||||
locked_name = desc
|
||||
icon_state = "minion1"
|
||||
addtimer(CALLBACK(src, PROC_REF(check_to_close_desc),locked), 5 MINUTES, TIMER_DELETE_ME)
|
||||
return
|
||||
else if(!istype(user, /mob/living))
|
||||
return
|
||||
else if(icon_state == "minion0")
|
||||
to_chat(user, span_notice("You touch the portal... nothing happens."))
|
||||
else
|
||||
to_chat(user, span_notice("You touch the portal, your hand able to pass through without harm."))
|
||||
|
||||
/obj/structure/dark_portal/proc/check_to_close(var/obj/structure/dark_portal/target)
|
||||
if(locked == target)
|
||||
close_portal()
|
||||
|
||||
/obj/structure/dark_portal/proc/check_to_close_desc(var/old_locked)
|
||||
if(locked == old_locked)
|
||||
close_portal()
|
||||
|
||||
/obj/structure/dark_portal/minion/Bumped(M as mob|obj)
|
||||
spawn()
|
||||
if(icon_state == "minion1")
|
||||
teleport(M)
|
||||
return
|
||||
@@ -0,0 +1,88 @@
|
||||
//Non-Canon on Virgo. Used downstream.
|
||||
|
||||
/datum/power/shadekin/dark_tunneling
|
||||
name = "Dark Tunneling (100) (Once)"
|
||||
desc = "Make a passage to the dark."
|
||||
verbpath = /mob/living/proc/dark_tunneling
|
||||
ability_icon_state = "minion0"
|
||||
|
||||
/mob/living/proc/dark_tunneling()
|
||||
set name = "Dark Tunneling (100) (Once)"
|
||||
set desc = "Make a passage to the dark."
|
||||
set category = "Abilities.Shadekin"
|
||||
|
||||
var/template_id = "dark_portal"
|
||||
var/datum/map_template/shelter/template
|
||||
|
||||
var/ability_cost = 100
|
||||
var/tunnel_time = 60 SECONDS
|
||||
|
||||
var/datum/component/shadekin/SK = get_shadekin_component()
|
||||
if(!SK)
|
||||
return FALSE
|
||||
if(stat)
|
||||
to_chat(src, span_warning("Can't use that ability in your state!"))
|
||||
return FALSE
|
||||
|
||||
if(SK.in_phase)
|
||||
to_chat(src, span_warning("You can't use that while phase shifted!"))
|
||||
return FALSE
|
||||
|
||||
if(SK.created_dark_tunnel)
|
||||
to_chat(src, span_warning("You have already made a tunnel to the Dark!"))
|
||||
return FALSE
|
||||
|
||||
if(!template)
|
||||
template = SSmapping.shelter_templates[template_id]
|
||||
if(!template)
|
||||
throw EXCEPTION("Shelter template ([template_id]) not found!")
|
||||
return FALSE
|
||||
|
||||
var/turf/deploy_location = get_turf(src)
|
||||
var/status = template.check_deploy(deploy_location)
|
||||
|
||||
switch(status)
|
||||
//Not allowed due to /area technical reasons
|
||||
if(SHELTER_DEPLOY_BAD_AREA)
|
||||
to_chat(src, span_warning("A tunnel to the Dark will not function in this area."))
|
||||
|
||||
//Anchored objects or no space
|
||||
if(SHELTER_DEPLOY_BAD_TURFS, SHELTER_DEPLOY_ANCHORED_OBJECTS)
|
||||
var/width = template.width
|
||||
var/height = template.height
|
||||
to_chat(src, span_warning("There is not enough open area for a tunnel to the Dark to form! You need to clear a [width]x[height] area!"))
|
||||
|
||||
if(status != SHELTER_DEPLOY_ALLOWED)
|
||||
return FALSE
|
||||
|
||||
var/turf/T = deploy_location
|
||||
var/datum/effect/effect/system/smoke_spread/smoke = new /datum/effect/effect/system/smoke_spread()
|
||||
smoke.attach(T)
|
||||
smoke.set_up(10, 0, T)
|
||||
smoke.start()
|
||||
|
||||
src.visible_message(span_notice("[src] begins pulling dark energies around themselves."))
|
||||
if(do_after(src, tunnel_time))
|
||||
playsound(src, 'sound/effects/phasein.ogg', 100, 1)
|
||||
src.visible_message(span_notice("[src] finishes pulling dark energies around themselves, creating a portal."))
|
||||
|
||||
log_and_message_admins("[key_name_admin(src)] created a tunnel to the dark at [get_area(T)]!")
|
||||
template.annihilate_plants(deploy_location)
|
||||
template.load(deploy_location, centered = TRUE)
|
||||
template.update_lighting(deploy_location)
|
||||
SK.created_dark_tunnel = TRUE
|
||||
SK.shadekin_adjust_energy(-(ability_cost - 10)) //Leaving enough energy to actually activate the portal
|
||||
return TRUE
|
||||
return FALSE
|
||||
|
||||
|
||||
/datum/map_template/shelter/dark_portal
|
||||
name = "Dark Portal"
|
||||
shelter_id = "dark_portal"
|
||||
description = "A portal to a section of the Dark"
|
||||
mappath = "maps/submaps/shelters/dark_portal.dmm"
|
||||
|
||||
/datum/map_template/shelter/dark_portal/New()
|
||||
. = ..()
|
||||
blacklisted_turfs = typecacheof(list(/turf/unsimulated))
|
||||
GLOB.blacklisted_areas = typecacheof(list(/area/centcom, /area/shadekin))
|
||||
@@ -0,0 +1,258 @@
|
||||
/////////////////////
|
||||
/// PHASE SHIFT ///
|
||||
/////////////////////
|
||||
//Visual effect for phase in/out
|
||||
/obj/effect/temp_visual/shadekin
|
||||
randomdir = FALSE
|
||||
duration = 5
|
||||
icon = 'icons/mob/vore_shadekin.dmi'
|
||||
|
||||
/obj/effect/temp_visual/shadekin/phase_in
|
||||
icon_state = "tp_in"
|
||||
|
||||
/obj/effect/temp_visual/shadekin/phase_out
|
||||
icon_state = "tp_out"
|
||||
|
||||
/datum/power/shadekin/phase_shift
|
||||
name = "Phase Shift (100)"
|
||||
desc = "Shift yourself out of alignment with realspace to travel quickly to different areas."
|
||||
verbpath = /mob/living/proc/phase_shift
|
||||
ability_icon_state = "phase_shift"
|
||||
|
||||
/mob/living/proc/phase_shift()
|
||||
set name = "Phase Shift (100)"
|
||||
set desc = "Shift yourself out of alignment with realspace to travel quickly to different areas."
|
||||
set category = "Abilities.Shadekin"
|
||||
|
||||
var/datum/component/shadekin/SK = get_shadekin_component()
|
||||
if(!SK)
|
||||
return FALSE
|
||||
if(stat)
|
||||
to_chat(src, span_warning("Can't use that ability in your state!"))
|
||||
return FALSE
|
||||
var/area/A = get_area(src)
|
||||
if(!client?.holder && A.flag_check(AREA_BLOCK_PHASE_SHIFT))
|
||||
to_chat(src, span_warning("You can't do that here!"))
|
||||
return
|
||||
|
||||
var/ability_cost = 100
|
||||
|
||||
var/darkness = 1
|
||||
var/turf/T = get_turf(src)
|
||||
if(!T)
|
||||
to_chat(src,span_warning("You can't use that here!"))
|
||||
return FALSE
|
||||
|
||||
if(SK.doing_phase)
|
||||
return FALSE
|
||||
|
||||
var/brightness = T.get_lumcount() //Brightness in 0.0 to 1.0
|
||||
darkness = 1-brightness //Invert
|
||||
|
||||
var/watcher = 0
|
||||
for(var/mob/living/carbon/human/watchers in oview(7,src )) // If we can see them...
|
||||
if(watchers in oviewers(7,src)) // And they can see us...
|
||||
if(!(watchers.stat) && !isbelly(watchers.loc) && !istype(watchers.loc, /obj/item/holder)) // And they are alive and not being held by someone...
|
||||
watcher++ // They are watching us!
|
||||
|
||||
ability_cost = CLAMP(ability_cost/(0.01+darkness*2),50, 80)//This allows for 1 watcher in full light
|
||||
if(watcher>0)
|
||||
ability_cost = ability_cost + ( 15 * watcher )
|
||||
/*
|
||||
if(!(SK.in_phase))
|
||||
log_debug("[src] attempted to shift with [watcher] visible Carbons with a cost of [ability_cost] in a darkness level of [darkness]")
|
||||
*/
|
||||
|
||||
if(SK.doing_phase)
|
||||
to_chat(src, span_warning("You are already trying to phase!"))
|
||||
return FALSE
|
||||
else if(SK.shadekin_get_energy() < ability_cost && !(SK.in_phase))
|
||||
to_chat(src, span_warning("Not enough energy for that ability!"))
|
||||
return FALSE
|
||||
|
||||
if(!(SK.in_phase))
|
||||
SK.shadekin_adjust_energy(-ability_cost)
|
||||
playsound(src, 'sound/effects/stealthoff.ogg', 75, 1)
|
||||
|
||||
if(!T.CanPass(src,T) || loc != T)
|
||||
to_chat(src,span_warning("You can't use that here!"))
|
||||
return FALSE
|
||||
|
||||
//Shifting in
|
||||
if(SK.in_phase)
|
||||
phase_in(T, SK)
|
||||
//Shifting out
|
||||
else
|
||||
phase_out(T, SK)
|
||||
|
||||
|
||||
/mob/living/proc/phase_in(var/turf/T, var/datum/component/shadekin/SK)
|
||||
if(SK.in_phase)
|
||||
|
||||
// pre-change
|
||||
if(!isturf(T)) //Sanity
|
||||
return
|
||||
forceMove(T)
|
||||
var/original_canmove = canmove
|
||||
SetStunned(0)
|
||||
SetWeakened(0)
|
||||
if(buckled)
|
||||
buckled.unbuckle_mob()
|
||||
if(pulledby)
|
||||
pulledby.stop_pulling()
|
||||
stop_pulling()
|
||||
|
||||
// change
|
||||
canmove = FALSE
|
||||
SK.in_phase = FALSE
|
||||
SK.doing_phase = TRUE
|
||||
throwpass = FALSE
|
||||
name = get_visible_name()
|
||||
for(var/obj/belly/B as anything in vore_organs)
|
||||
B.escapable = initial(B.escapable)
|
||||
|
||||
//cut_overlays()
|
||||
invisibility = initial(invisibility)
|
||||
see_invisible = initial(see_invisible)
|
||||
incorporeal_move = initial(incorporeal_move)
|
||||
density = initial(density)
|
||||
can_pull_size = initial(can_pull_size)
|
||||
can_pull_mobs = initial(can_pull_mobs)
|
||||
hovering = initial(hovering)
|
||||
update_icon()
|
||||
|
||||
//Cosmetics mostly
|
||||
var/obj/effect/temp_visual/shadekin/phase_in/phaseanim = new /obj/effect/temp_visual/shadekin/phase_in(src.loc)
|
||||
phaseanim.pixel_y = (src.size_multiplier - 1) * 16 // Pixel shift for the animation placement
|
||||
phaseanim.adjust_scale(src.size_multiplier, src.size_multiplier)
|
||||
phaseanim.dir = dir
|
||||
alpha = 0
|
||||
automatic_custom_emote(VISIBLE_MESSAGE,"phases in!")
|
||||
|
||||
addtimer(CALLBACK(src, PROC_REF(shadekin_complete_phase_in), original_canmove, SK), 5, TIMER_DELETE_ME)
|
||||
|
||||
|
||||
/mob/living/proc/shadekin_complete_phase_in(var/original_canmove, var/datum/component/shadekin/SK)
|
||||
canmove = original_canmove
|
||||
alpha = initial(alpha)
|
||||
remove_modifiers_of_type(/datum/modifier/shadekin_phase_vision)
|
||||
remove_modifiers_of_type(/datum/modifier/phased_out)
|
||||
|
||||
//Potential phase-in vore
|
||||
|
||||
if(can_be_drop_pred || can_be_drop_prey) //Toggleable in vore panel
|
||||
var/list/potentials = living_mobs(0)
|
||||
var/mob/living/our_prey
|
||||
if(potentials.len)
|
||||
var/mob/living/target = pick(potentials)
|
||||
if(can_be_drop_pred && istype(target) && target.devourable && target.can_be_drop_prey && target.phase_vore && vore_selected && phase_vore)
|
||||
target.forceMove(vore_selected)
|
||||
to_chat(target, span_vwarning("\The [src] phases in around you, [vore_selected.vore_verb]ing you into their [vore_selected.name]!"))
|
||||
to_chat(src, span_vwarning("You phase around [target], [vore_selected.vore_verb]ing them into your [vore_selected.name]!"))
|
||||
our_prey = target
|
||||
else if(can_be_drop_prey && istype(target) && devourable && target.can_be_drop_pred && target.phase_vore && target.vore_selected && phase_vore)
|
||||
our_prey = src
|
||||
forceMove(target.vore_selected)
|
||||
to_chat(target, span_vwarning("\The [src] phases into you, [target.vore_selected.vore_verb]ing them into your [target.vore_selected.name]!"))
|
||||
to_chat(src, span_vwarning("You phase into [target], having them [target.vore_selected.vore_verb] you into their [target.vore_selected.name]!"))
|
||||
if(our_prey)
|
||||
for(var/obj/item/flashlight/held_lights in our_prey.contents)
|
||||
if(istype(held_lights,/obj/item/flashlight/glowstick) ||istype(held_lights,/obj/item/flashlight/flare) ) //No affecting glowsticks or flares...As funny as that is
|
||||
continue
|
||||
held_lights.on = 0
|
||||
held_lights.update_brightness()
|
||||
|
||||
SK.doing_phase = FALSE
|
||||
if(!SK.flicker_time)
|
||||
return //Early return. No time, no flickering.
|
||||
//Affect nearby lights
|
||||
for(var/obj/machinery/light/L in range(SK.flicker_distance, src))
|
||||
if(prob(SK.flicker_break_chance))
|
||||
addtimer(CALLBACK(L, TYPE_PROC_REF(/obj/machinery/light, broken)), rand(5,25), TIMER_DELETE_ME)
|
||||
else
|
||||
if(SK.flicker_color)
|
||||
L.flicker(SK.flicker_time, SK.flicker_color)
|
||||
else
|
||||
L.flicker(SK.flicker_time)
|
||||
for(var/obj/item/flashlight/flashlights in range(SK.flicker_distance, src)) //Find any flashlights near us and make them flicker too!
|
||||
if(istype(flashlights,/obj/item/flashlight/glowstick) ||istype(flashlights,/obj/item/flashlight/flare)) //No affecting glowsticks or flares...As funny as that is
|
||||
continue
|
||||
flashlights.flicker(SK.flicker_time, SK.flicker_color, TRUE)
|
||||
for(var/mob/living/creatures in range(SK.flicker_distance, src))
|
||||
if(isbelly(creatures.loc)) //don't flicker anyone that gets nomphed.
|
||||
continue
|
||||
for(var/obj/item/flashlight/held_lights in creatures.contents)
|
||||
if(istype(held_lights,/obj/item/flashlight/glowstick) ||istype(held_lights,/obj/item/flashlight/flare) ) //No affecting glowsticks or flares...As funny as that is
|
||||
continue
|
||||
held_lights.flicker(SK.flicker_time, SK.flicker_color, TRUE)
|
||||
|
||||
/mob/living/proc/phase_out(var/turf/T)
|
||||
var/datum/component/shadekin/SK = get_shadekin_component()
|
||||
if(!(SK.in_phase))
|
||||
// pre-change
|
||||
forceMove(T)
|
||||
var/original_canmove = canmove
|
||||
SetStunned(0)
|
||||
SetWeakened(0)
|
||||
if(buckled)
|
||||
buckled.unbuckle_mob()
|
||||
if(pulledby)
|
||||
pulledby.stop_pulling()
|
||||
stop_pulling()
|
||||
if(SK.normal_phase && SK.drop_items_on_phase)
|
||||
drop_both_hands()
|
||||
if(back)
|
||||
unEquip(back)
|
||||
|
||||
can_pull_size = 0
|
||||
can_pull_mobs = MOB_PULL_NONE
|
||||
hovering = TRUE
|
||||
canmove = FALSE
|
||||
|
||||
// change
|
||||
SK.in_phase = TRUE
|
||||
SK.doing_phase = TRUE
|
||||
throwpass = TRUE
|
||||
automatic_custom_emote(VISIBLE_MESSAGE,"phases out!")
|
||||
|
||||
if(real_name) //If we a real name, perfect, let's just set our name to our newfound visible name.
|
||||
name = get_visible_name()
|
||||
else //If we don't, let's put our real_name as our initial name.
|
||||
real_name = initial(name)
|
||||
name = get_visible_name()
|
||||
|
||||
for(var/obj/belly/B as anything in vore_organs)
|
||||
B.escapable = FALSE
|
||||
|
||||
var/obj/effect/temp_visual/shadekin/phase_out/phaseanim = new /obj/effect/temp_visual/shadekin/phase_out(src.loc)
|
||||
phaseanim.pixel_y = (src.size_multiplier - 1) * 16 // Pixel shift for the animation placement
|
||||
phaseanim.adjust_scale(src.size_multiplier, src.size_multiplier)
|
||||
phaseanim.dir = dir
|
||||
alpha = 0
|
||||
add_modifier(/datum/modifier/shadekin_phase_vision)
|
||||
if(SK.normal_phase)
|
||||
add_modifier(/datum/modifier/phased_out)
|
||||
addtimer(CALLBACK(src, PROC_REF(complete_phase_out), original_canmove, SK), 5, TIMER_DELETE_ME)
|
||||
|
||||
|
||||
/mob/living/proc/complete_phase_out(original_canmove, var/datum/component/shadekin/SK)
|
||||
invisibility = INVISIBILITY_SHADEKIN
|
||||
see_invisible = INVISIBILITY_SHADEKIN
|
||||
see_invisible_default = INVISIBILITY_SHADEKIN // Allow seeing phased entities while phased.
|
||||
update_icon()
|
||||
alpha = 127
|
||||
|
||||
canmove = original_canmove
|
||||
incorporeal_move = TRUE
|
||||
density = FALSE
|
||||
SK.doing_phase = FALSE
|
||||
|
||||
/datum/modifier/shadekin_phase_vision
|
||||
name = "Shadekin Phase Vision"
|
||||
vision_flags = SEE_THRU
|
||||
|
||||
/datum/modifier/phased_out
|
||||
name = "Phased Out"
|
||||
desc = "You are currently phased out of realspace, and cannot interact with it."
|
||||
hidden = TRUE
|
||||
//Stops you from using guns. See /obj/item/gun/proc/special_check(var/mob/user)
|
||||
@@ -0,0 +1,66 @@
|
||||
//////////////////////////
|
||||
/// REGENERATE OTHER ///
|
||||
//////////////////////////
|
||||
/datum/power/shadekin/regenerate_other
|
||||
name = "Regenerate Other (50)"
|
||||
desc = "Spend energy to heal physical wounds in another creature."
|
||||
verbpath = /mob/living/proc/regenerate_other
|
||||
ability_icon_state = "shadekin_regen"
|
||||
|
||||
/mob/living/proc/regenerate_other()
|
||||
set name = "Regenerate Other (50)"
|
||||
set desc = "Spend energy to heal physical wounds in another creature."
|
||||
set category = "Abilities.Shadekin"
|
||||
|
||||
var/ability_cost = 50
|
||||
|
||||
var/datum/component/shadekin/SK = get_shadekin_component()
|
||||
if(!SK)
|
||||
return FALSE
|
||||
if(stat)
|
||||
to_chat(src, span_warning("Can't use that ability in your state!"))
|
||||
return FALSE
|
||||
else if(SK.shadekin_get_energy() < ability_cost)
|
||||
to_chat(src, span_warning("Not enough energy for that ability!"))
|
||||
return FALSE
|
||||
else if(SK.in_phase)
|
||||
to_chat(src, span_warning("You can't use that while phase shifted!"))
|
||||
return FALSE
|
||||
|
||||
var/list/viewed = oview(1)
|
||||
var/list/targets = list()
|
||||
for(var/mob/living/L in viewed)
|
||||
targets += L
|
||||
if(!targets.len)
|
||||
to_chat(src,span_warning("Nobody nearby to mend!"))
|
||||
return FALSE
|
||||
|
||||
var/mob/living/target = tgui_input_list(src,"Pick someone to mend:","Mend Other", targets)
|
||||
if(!target)
|
||||
return FALSE
|
||||
|
||||
target.add_modifier(/datum/modifier/shadekin/heal_boop,1 MINUTE)
|
||||
playsound(src, 'sound/effects/EMPulse.ogg', 75, 1)
|
||||
SK.shadekin_adjust_energy(-ability_cost)
|
||||
visible_message(span_notice("\The [src] gently places a hand on \the [target]..."))
|
||||
face_atom(target)
|
||||
return TRUE
|
||||
|
||||
/datum/modifier/shadekin/heal_boop
|
||||
name = "Shadekin Regen"
|
||||
desc = "You feel serene and well rested."
|
||||
mob_overlay_state = "green_sparkles"
|
||||
|
||||
on_created_text = span_notice("Sparkles begin to appear around you, and all your ills seem to fade away.")
|
||||
on_expired_text = span_notice("The sparkles have faded, although you feel much healthier than before.")
|
||||
stacks = MODIFIER_STACK_EXTEND
|
||||
|
||||
/datum/modifier/shadekin/heal_boop/tick()
|
||||
if(!holder.getBruteLoss() && !holder.getFireLoss() && !holder.getToxLoss() && !holder.getOxyLoss() && !holder.getCloneLoss()) // No point existing if the spell can't heal.
|
||||
expire()
|
||||
return
|
||||
holder.adjustBruteLoss(-2)
|
||||
holder.adjustFireLoss(-2)
|
||||
holder.adjustToxLoss(-2)
|
||||
holder.adjustOxyLoss(-2)
|
||||
holder.adjustCloneLoss(-2)
|
||||
@@ -0,0 +1,224 @@
|
||||
//See comp_helpers.dm for helper procs.
|
||||
/datum/component/shadekin
|
||||
VAR_PRIVATE/mob/living/owner
|
||||
dupe_mode = COMPONENT_DUPE_UNIQUE
|
||||
|
||||
//Energy Vars
|
||||
///How much energy we have RIGHT NOW
|
||||
var/dark_energy = 100
|
||||
///How much energy we can have
|
||||
var/max_dark_energy = 100
|
||||
///Always be at our max_dark_energy
|
||||
var/dark_energy_infinite = FALSE
|
||||
///How much energy we generate in the dark
|
||||
var/energy_dark = 0.75
|
||||
///How much energy we generate in the light
|
||||
var/energy_light = 0.25
|
||||
///If we care about eye color when it comes to factoring in energy
|
||||
var/eye_color_influences_energy = TRUE
|
||||
///Energy gain from nutrition
|
||||
var/nutrition_conversion_scaling = 0.5
|
||||
///If we convert nutrition to energy
|
||||
var/nutrition_energy_conversion = FALSE
|
||||
|
||||
//Phase Vars
|
||||
///Are we currently in a phase transition?
|
||||
var/doing_phase = FALSE
|
||||
///Are we currently phased?
|
||||
var/in_phase = FALSE
|
||||
///Chance to break lights on phase-in
|
||||
var/flicker_break_chance = 0
|
||||
///Color that lights will flicker to on phase-in. Off by default.
|
||||
var/flicker_color
|
||||
///Time that lights will flicker on phase-in. Default is 10 times.
|
||||
var/flicker_time = 10
|
||||
///Range that we flicker lights. Default is 10.
|
||||
var/flicker_distance = 10
|
||||
///If we can get the 'phase debuff' applied to us. (No using guns, dropping things in hands, etc).
|
||||
var/normal_phase = TRUE
|
||||
///If we drop items on phase.
|
||||
var/drop_items_on_phase = FALSE
|
||||
|
||||
//Dark Respite Vars (Unused on Virgo)
|
||||
///If we are in dark respite or not
|
||||
var/in_dark_respite = FALSE
|
||||
var/manual_respite = FALSE
|
||||
var/respite_activating = FALSE
|
||||
|
||||
//Dark Tunneling Vars (Unused on Virgo)
|
||||
///If we have already made a dark tunnel
|
||||
var/created_dark_tunnel = FALSE
|
||||
|
||||
//Dark Maw Vars (Unused on Virgo)
|
||||
///Our current active dark maws
|
||||
var/list/active_dark_maws = list()
|
||||
|
||||
//Ability Vars
|
||||
///The innate abilities we start with
|
||||
var/list/shadekin_abilities = list(/datum/power/shadekin/phase_shift,
|
||||
/datum/power/shadekin/regenerate_other,
|
||||
/datum/power/shadekin/create_shade)
|
||||
///Datum holder. Largely ignore this.
|
||||
var/list/shadekin_ability_datums = list()
|
||||
|
||||
//Misc Vars
|
||||
///Eyecolor
|
||||
var/eye_color = BLUE_EYES
|
||||
///For downstream. Enables some extra verbs. Causes things to drop in hand when you phase.
|
||||
var/extended_kin = FALSE
|
||||
|
||||
/datum/component/shadekin/phase_only
|
||||
shadekin_abilities = list(/datum/power/shadekin/phase_shift)
|
||||
|
||||
/datum/component/shadekin/full
|
||||
shadekin_abilities = list(/datum/power/shadekin/phase_shift,
|
||||
/datum/power/shadekin/regenerate_other,
|
||||
/datum/power/shadekin/create_shade,
|
||||
/datum/power/shadekin/dark_maw,
|
||||
/datum/power/shadekin/dark_respite,
|
||||
/datum/power/shadekin/dark_tunneling)
|
||||
extended_kin = TRUE
|
||||
drop_items_on_phase = TRUE
|
||||
|
||||
/datum/component/shadekin/full/rakshasa
|
||||
flicker_time = 0 //Rakshasa don't flicker lights when they phase in.
|
||||
dark_energy_infinite = TRUE
|
||||
normal_phase = FALSE
|
||||
|
||||
/datum/component/shadekin/Initialize()
|
||||
//normal component bs
|
||||
if(!isliving(parent))
|
||||
return COMPONENT_INCOMPATIBLE
|
||||
owner = parent
|
||||
add_shadekin_abilities(owner)
|
||||
if(ishuman(owner))
|
||||
RegisterSignal(owner, COMSIG_SHADEKIN_COMPONENT, PROC_REF(handle_comp)) //Happens every species tick.
|
||||
else
|
||||
RegisterSignal(owner, COMSIG_LIVING_LIFE, PROC_REF(handle_comp)) //Happens every life tick (mobs)
|
||||
|
||||
//generates powers and then adds them
|
||||
for(var/power in shadekin_abilities)
|
||||
var/datum/power/shadekin/SKP = new power(src)
|
||||
shadekin_ability_datums.Add(SKP)
|
||||
add_shadekin_abilities()
|
||||
|
||||
handle_comp() //First hit is free!
|
||||
|
||||
//decides what 'eye color' we are and how much energy we should get
|
||||
set_shadekin_eyecolor() //Gets what eye color we are.
|
||||
set_eye_energy() //Sets the energy values based on our eye color.
|
||||
|
||||
//Misc stuff we need to do
|
||||
if(extended_kin)
|
||||
add_verb(owner, /mob/living/proc/nutrition_conversion_toggle)
|
||||
add_verb(owner, /mob/living/proc/flicker_adjustment)
|
||||
|
||||
/datum/component/shadekin/Destroy(force)
|
||||
if(ishuman(owner))
|
||||
UnregisterSignal(owner, COMSIG_SHADEKIN_COMPONENT)
|
||||
else
|
||||
UnregisterSignal(owner, COMSIG_LIVING_LIFE)
|
||||
if(extended_kin)
|
||||
remove_verb(owner, /mob/living/proc/nutrition_conversion_toggle)
|
||||
remove_verb(owner, /mob/living/proc/flicker_adjustment)
|
||||
for(var/datum/power in shadekin_ability_datums)
|
||||
qdel(power)
|
||||
for(var/obj/effect/abstract/dark_maw/dm as anything in active_dark_maws) //if the component gets destroyed so does your precious maws
|
||||
if(!QDELETED(dm))
|
||||
qdel(dm)
|
||||
active_dark_maws.Cut()
|
||||
shadekin_abilities.Cut()
|
||||
shadekin_ability_datums.Cut()
|
||||
owner = null
|
||||
. = ..()
|
||||
|
||||
///Handles the component running.
|
||||
/datum/component/shadekin/proc/handle_comp()
|
||||
SIGNAL_HANDLER
|
||||
if(QDELETED(parent))
|
||||
return
|
||||
if(owner.stat == DEAD) //dead, don't process.
|
||||
return
|
||||
handle_shade()
|
||||
|
||||
///Handles the shadekin's energy gain and loss.
|
||||
/datum/component/shadekin/proc/handle_shade()
|
||||
//Shifted kin don't gain/lose energy (and save time if we're at the cap)
|
||||
var/darkness = 1
|
||||
var/dark_gains = 0
|
||||
|
||||
var/turf/T = get_turf(owner)
|
||||
if(!T)
|
||||
dark_gains = 0
|
||||
return
|
||||
|
||||
var/brightness = T.get_lumcount() //Brightness in 0.0 to 1.0
|
||||
darkness = 1-brightness //Invert
|
||||
var/is_dark = (darkness >= 0.5)
|
||||
|
||||
if(in_phase)
|
||||
dark_gains = 0
|
||||
else
|
||||
//Heal (very) slowly in good darkness
|
||||
if(is_dark)
|
||||
//The below sends a DB query...This needs to be fixed before this can be enabled as we're now dealing with signal handlers.
|
||||
//Reenable once that mess is taken care of.
|
||||
owner.adjustFireLoss((-0.10)*darkness)
|
||||
owner.adjustBruteLoss((-0.10)*darkness)
|
||||
owner.adjustToxLoss((-0.10)*darkness)
|
||||
//energy_dark and energy_light are set by the shadekin eye traits.
|
||||
//These are balanced around their playstyles and 2 planned new aggressive abilities
|
||||
dark_gains = energy_dark
|
||||
else
|
||||
dark_gains = energy_light
|
||||
|
||||
shadekin_adjust_energy(dark_gains)
|
||||
|
||||
//Update huds
|
||||
update_shadekin_hud()
|
||||
|
||||
|
||||
/mob/living/proc/nutrition_conversion_toggle()
|
||||
set name = "Toggle Energy <-> Nutrition conversions"
|
||||
set desc = "Toggle dark energy and nutrition being converted into each other when full"
|
||||
set category = "Abilities.Shadekin"
|
||||
|
||||
var/datum/component/shadekin/SK = get_shadekin_component()
|
||||
if(!SK)
|
||||
to_chat(src, span_warning("Only a shadekin can use that!"))
|
||||
return FALSE
|
||||
|
||||
if(SK.nutrition_energy_conversion)
|
||||
to_chat(src, span_notice("Nutrition and dark energy conversions disabled."))
|
||||
SK.nutrition_energy_conversion = 0
|
||||
else
|
||||
to_chat(src, span_notice("Nutrition and dark energy conversions enabled."))
|
||||
SK.nutrition_energy_conversion = 1
|
||||
|
||||
/mob/living/proc/flicker_adjustment()
|
||||
set name = "Adjust Light Flicker"
|
||||
set desc = "Allows you to adjust the settings of the light flicker when you phase in!"
|
||||
set category = "Abilities.Shadekin"
|
||||
|
||||
var/datum/component/shadekin/SK = get_shadekin_component()
|
||||
if(!SK)
|
||||
to_chat(src, span_warning("Only a shadekin can use that!"))
|
||||
return FALSE
|
||||
var/flicker_timer = tgui_input_number(src, "Adjust how long lights flicker when you phase in! (Min 10 Max 20 times!)", "Set Flicker", SK.flicker_time, 20, 10)
|
||||
if(flicker_timer > 20 || flicker_timer < 10)
|
||||
to_chat(src,"<span class='warning'>You must choose a number between 10 and 20</span>")
|
||||
return
|
||||
SK.flicker_time = flicker_timer
|
||||
to_chat(src,"<span class='warning'>Flicker timer set to [SK.flicker_time] seconds!</span>")
|
||||
|
||||
var/set_new_color = tgui_color_picker(src,"Select a color you wish the lights to flicker as (Default is #E0EFF0)","Color Selector", SK.flicker_color)
|
||||
if(set_new_color)
|
||||
SK.flicker_color = set_new_color
|
||||
to_chat(src,"<span class='warning'>Flicker color set to [SK.flicker_color]!</span>")
|
||||
|
||||
var/break_chance = tgui_input_number(src, "Adjust the % chance for lights to break when you phase in! (Default 0. Min 0. Max 25)", "Set Break Chance", 0, 25, 0)
|
||||
if(break_chance > 25 || break_chance < 0)
|
||||
to_chat(src,"<span class='warning'>You must choose a number between 0 and 25</span>")
|
||||
return
|
||||
SK.flicker_break_chance = break_chance
|
||||
to_chat(src,"<span class='warning'>Break chance set to [SK.flicker_break_chance]%</span>")
|
||||
Reference in New Issue
Block a user