diff --git a/code/__defines/dcs/signals.dm b/code/__defines/dcs/signals.dm
index 0bfc1855b85..a6ab40cfa72 100644
--- a/code/__defines/dcs/signals.dm
+++ b/code/__defines/dcs/signals.dm
@@ -860,8 +860,9 @@
#define COMSIG_GARGOYLE_CHECK_ENERGY "gargoyle_check_energy"
// Species Components
-///from /datum/species/xenochimera/handle_environment_special()
+///from /datum/species/proc/handle_species_components()
#define COMSIG_XENOCHIMERA_COMPONENT "xenochimera_component"
+#define COMSIG_SHADEKIN_COMPONENT "shadekin_component"
// Hose Connector Component
#define COMSIG_HOSE_FORCEPUMP "hose_force_pump"
diff --git a/code/__defines/shadekin.dm b/code/__defines/shadekin.dm
index 06950c63a1c..1dd3db67e20 100644
--- a/code/__defines/shadekin.dm
+++ b/code/__defines/shadekin.dm
@@ -2,9 +2,7 @@
#define ONLY_WHILE_SHIFTED 2
#define SHIFTED_OR_NOT 3
-#define AB_PHASE_SHIFTED 0x1
-#define AB_PHASE_SHIFTING 0x2
-#define AB_SHADE_REGEN 0x4
+#define AB_SHADE_REGEN 0x1
//Porting over the type system of the mobs
#define BLUE_EYES 1
diff --git a/code/_helpers/global_lists_vr.dm b/code/_helpers/global_lists_vr.dm
index 2c67f669c9b..f0183359615 100644
--- a/code/_helpers/global_lists_vr.dm
+++ b/code/_helpers/global_lists_vr.dm
@@ -11,8 +11,13 @@ GLOBAL_LIST_EMPTY(everyone_traits_neutral) // Neutral traits available to all sp
GLOBAL_LIST_EMPTY(everyone_traits_negative) // Neutral traits available to all species, indexed by path
GLOBAL_LIST_EMPTY(traits_costs) // Just path = cost list, saves time in char setup
GLOBAL_LIST_EMPTY(all_traits) // All of 'em at once (same instances)
-GLOBAL_LIST_EMPTY(active_ghost_pods)
+GLOBAL_LIST_EMPTY(active_ghost_pods) //NYI - Used downstream
+GLOBAL_LIST_EMPTY(latejoin_gatewaystation) //NYI - Used downstream
+GLOBAL_LIST_EMPTY(latejoin_plainspath) //NYI - Used downstream
+GLOBAL_LIST_EMPTY(latejoin_fueldepot) //NYI - Used downstream
+GLOBAL_LIST_EMPTY(latejoin_tyrvillage) //NYI - Used downstream
+GLOBAL_LIST_EMPTY(latejoin_thedark) //NYI - Used downstream
//Global vars for making the overmap_renamer subsystem.
//Collects all instances by reference of visitable overmap objects of /obj/effect/overmap/visitable like the debris field.
GLOBAL_LIST_EMPTY(visitable_overmap_object_instances)
diff --git a/code/_onclick/drag_drop.dm b/code/_onclick/drag_drop.dm
index 2ea2a4885f6..8ef0c3b3a6d 100644
--- a/code/_onclick/drag_drop.dm
+++ b/code/_onclick/drag_drop.dm
@@ -20,6 +20,8 @@
return
if(!Adjacent(usr) || !over.Adjacent(usr))
return // should stop you from dragging through windows
+ if(is_incorporeal(usr))
+ return
INVOKE_ASYNC(over, TYPE_PROC_REF(/atom, MouseDrop_T), src, usr, src_location, over_location, src_control, over_control, params)
diff --git a/code/_onclick/hud/ability_screen_objects.dm b/code/_onclick/hud/ability_screen_objects.dm
index c13e5cf9793..05e48d8bd39 100644
--- a/code/_onclick/hud/ability_screen_objects.dm
+++ b/code/_onclick/hud/ability_screen_objects.dm
@@ -56,7 +56,7 @@
showing = 0
overlays.len = 0
overlays.Add(closed_state)
- else if(forced_state != 1) // We're opening it, show the icons.
+ else if(forced_state != 1) // We're opening it, show the icons. OR, if forced_state == 2, we're forcing it to open it.
open_ability_master()
update_abilities(1)
showing = 1
@@ -84,6 +84,7 @@
A.screen_loc = "[encode_screen_X(xpos)]:[x_pix],[encode_screen_Y(ypos)]:[y_pix]"
if(my_mob && my_mob.client)
my_mob.client.screen += A
+ my_mob.client.screen |= src
// A.handle_icon_updates = 1
/obj/screen/movable/ability_master/proc/update_abilities(forced = 0, mob/user)
@@ -177,12 +178,12 @@
/mob/Login()
..()
if(ability_master)
- ability_master.toggle_open(1)
- client.screen -= ability_master
+ ability_master.toggle_open(2) //Force it to open on login.
+ //client.screen -= ability_master
/mob/Initialize(mapload)
. = ..()
- if(!ability_master) //VOREStation Edit: S H A D E K I N
+ if(!ability_master)
ability_master = new /obj/screen/movable/ability_master(src)
///////////ACTUAL ABILITIES////////////
diff --git a/code/_onclick/hud/hud.dm b/code/_onclick/hud/hud.dm
index 8cd701e0890..b99532ba107 100644
--- a/code/_onclick/hud/hud.dm
+++ b/code/_onclick/hud/hud.dm
@@ -547,6 +547,31 @@ GLOBAL_LIST_INIT(global_huds, list(
/mob/new_player/add_click_catcher()
return
+/mob/living/create_mob_hud(datum/hud/HUD, apply_to_client = TRUE)
+ ..()
+
+ var/list/hud_elements = list()
+ shadekin_display = new /obj/screen/shadekin()
+ shadekin_display.screen_loc = ui_shadekin_display
+ shadekin_display.icon_state = "shadekin"
+ hud_elements |= shadekin_display
+
+ xenochimera_danger_display = new /obj/screen/xenochimera/danger_level()
+ xenochimera_danger_display.screen_loc = ui_xenochimera_danger_display
+ xenochimera_danger_display.icon_state = "danger00"
+ hud_elements |= xenochimera_danger_display
+
+ lleill_display = new /obj/screen/lleill()
+ lleill_display.screen_loc = ui_lleill_display
+ lleill_display.icon_state = "lleill"
+ hud_elements |= lleill_display
+
+ if(client)
+ client.screen = list()
+
+ client.screen += hud_elements
+ client.screen += client.void
+
/* TGMC Ammo HUD Port
* These procs call to screen_objects.dm's respective procs.
* All these do is manage the amount of huds on screen and set the HUD.
diff --git a/code/_onclick/hud/human.dm b/code/_onclick/hud/human.dm
index 6f2a0f83677..4ed75090fa9 100644
--- a/code/_onclick/hud/human.dm
+++ b/code/_onclick/hud/human.dm
@@ -321,22 +321,10 @@
using.alpha = HUD.ui_alpha
adding |= using
- //VOREStation Addition begin
- shadekin_display = new /obj/screen/shadekin()
- shadekin_display.screen_loc = ui_shadekin_display
- shadekin_display.icon_state = "shadekin"
+ //Component hud elements. Made in /mob/living/create_mob_hud
hud_elements |= shadekin_display
-
- xenochimera_danger_display = new /obj/screen/xenochimera/danger_level()
- xenochimera_danger_display.screen_loc = ui_xenochimera_danger_display
- xenochimera_danger_display.icon_state = "danger00"
hud_elements |= xenochimera_danger_display
-
- lleill_display = new /obj/screen/lleill()
- lleill_display.screen_loc = ui_lleill_display
- lleill_display.icon_state = "lleill"
hud_elements |= lleill_display
- //VOREStation Addition end
ling_chem_display = new /obj/screen/ling/chems()
ling_chem_display.screen_loc = ui_ling_chemical_display
diff --git a/code/_onclick/hud/screen_objects.dm b/code/_onclick/hud/screen_objects.dm
index de8604e5bcc..acb7f309b61 100644
--- a/code/_onclick/hud/screen_objects.dm
+++ b/code/_onclick/hud/screen_objects.dm
@@ -623,7 +623,7 @@
var/mob/living/silicon/ai/AI = usr
AI.view_images()
else
- return attempt_vr(src,"Click_vr",list(location,control,params)) //VOREStation Add - Additional things.
+ return attempt_vr(src,"Click_vr",list(location,control,params))
return 1
/obj/screen/inventory/Click()
diff --git a/code/_onclick/hud/screen_objects_vr.dm b/code/_onclick/hud/screen_objects_vr.dm
index d4073fb6fd4..2df745ef538 100644
--- a/code/_onclick/hud/screen_objects_vr.dm
+++ b/code/_onclick/hud/screen_objects_vr.dm
@@ -1,24 +1,16 @@
/obj/screen/proc/Click_vr(location, control, params)
if(!usr) return 1
switch(name)
-
- //Shadekin
- if("darkness")
- var/turf/T = get_turf(usr)
- var/darkness = round(1 - T.get_lumcount(),0.1)
- to_chat(usr,span_notice(span_bold("Darkness:") + " [darkness]"))
- if("energy")
- var/mob/living/simple_mob/shadekin/SK = usr
- if(istype(SK))
- to_chat(usr,span_notice(span_bold("Energy:") + " [SK.comp.dark_energy] ([SK.dark_gains])"))
if("shadekin status")
var/turf/T = get_turf(usr)
if(T)
var/darkness = round(1 - T.get_lumcount(),0.1)
to_chat(usr,span_notice(span_bold("Darkness:") + " [darkness]"))
- var/mob/living/carbon/human/H = usr
- if(istype(H) && istype(H.species, /datum/species/shadekin))
- to_chat(usr,span_notice(span_bold("Energy:") + " [H.shadekin_get_energy(H)]"))
+ var/mob/living/H = usr
+ if(ismob(H))
+ var/datum/component/shadekin/SK = H.get_shadekin_component()
+ if(SK)
+ to_chat(usr,span_notice(span_bold("Energy:") + " [SK.shadekin_get_energy()]"))
if("glamour")
var/mob/living/carbon/human/H = usr
if(istype(H))
diff --git a/code/datums/components/species/shadekin.dm b/code/datums/components/species/shadekin.dm
deleted file mode 100644
index 797e4c19b46..00000000000
--- a/code/datums/components/species/shadekin.dm
+++ /dev/null
@@ -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
diff --git a/code/datums/components/species/shadekin/helpers/comp_helpers.dm b/code/datums/components/species/shadekin/helpers/comp_helpers.dm
new file mode 100644
index 00000000000..42475beff60
--- /dev/null
+++ b/code/datums/components/species/shadekin/helpers/comp_helpers.dm
@@ -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)
diff --git a/code/datums/components/species/shadekin/powers/create_shade.dm b/code/datums/components/species/shadekin/powers/create_shade.dm
new file mode 100644
index 00000000000..a9c1facd114
--- /dev/null
+++ b/code/datums/components/species/shadekin/powers/create_shade.dm
@@ -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
diff --git a/code/datums/components/species/shadekin/powers/dark_maw.dm b/code/datums/components/species/shadekin/powers/dark_maw.dm
new file mode 100644
index 00000000000..0f66687affe
--- /dev/null
+++ b/code/datums/components/species/shadekin/powers/dark_maw.dm
@@ -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)
diff --git a/code/datums/components/species/shadekin/powers/dark_respite.dm b/code/datums/components/species/shadekin/powers/dark_respite.dm
new file mode 100644
index 00000000000..4bb333d7e69
--- /dev/null
+++ b/code/datums/components/species/shadekin/powers/dark_respite.dm
@@ -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
diff --git a/code/datums/components/species/shadekin/powers/dark_tunnel/dark_tunnel_structures.dm b/code/datums/components/species/shadekin/powers/dark_tunnel/dark_tunnel_structures.dm
new file mode 100644
index 00000000000..959489135a5
--- /dev/null
+++ b/code/datums/components/species/shadekin/powers/dark_tunnel/dark_tunnel_structures.dm
@@ -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
diff --git a/code/datums/components/species/shadekin/powers/dark_tunnel/dark_tunneling.dm b/code/datums/components/species/shadekin/powers/dark_tunnel/dark_tunneling.dm
new file mode 100644
index 00000000000..01cca9732c8
--- /dev/null
+++ b/code/datums/components/species/shadekin/powers/dark_tunnel/dark_tunneling.dm
@@ -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))
diff --git a/code/datums/components/species/shadekin/powers/phase_shift.dm b/code/datums/components/species/shadekin/powers/phase_shift.dm
new file mode 100644
index 00000000000..9a79b362d48
--- /dev/null
+++ b/code/datums/components/species/shadekin/powers/phase_shift.dm
@@ -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)
diff --git a/code/datums/components/species/shadekin/powers/regenerate_other.dm b/code/datums/components/species/shadekin/powers/regenerate_other.dm
new file mode 100644
index 00000000000..375da05fa7e
--- /dev/null
+++ b/code/datums/components/species/shadekin/powers/regenerate_other.dm
@@ -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)
diff --git a/code/datums/components/species/shadekin/shadekin.dm b/code/datums/components/species/shadekin/shadekin.dm
new file mode 100644
index 00000000000..17f2fcd0f2e
--- /dev/null
+++ b/code/datums/components/species/shadekin/shadekin.dm
@@ -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,"You must choose a number between 10 and 20")
+ return
+ SK.flicker_time = flicker_timer
+ to_chat(src,"Flicker timer set to [SK.flicker_time] seconds!")
+
+ 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,"Flicker color set to [SK.flicker_color]!")
+
+ 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,"You must choose a number between 0 and 25")
+ return
+ SK.flicker_break_chance = break_chance
+ to_chat(src,"Break chance set to [SK.flicker_break_chance]%")
diff --git a/code/datums/helper_datums/teleport.dm b/code/datums/helper_datums/teleport.dm
index 994dd65b2fb..5234d3f5e7f 100644
--- a/code/datums/helper_datums/teleport.dm
+++ b/code/datums/helper_datums/teleport.dm
@@ -6,7 +6,7 @@ var/bluespace_item_types = newlist(/obj/item/storage/backpack/holding,
)
//wrapper
-/proc/do_teleport(ateleatom, adestination, aprecision=0, afteleport=1, aeffectin=null, aeffectout=null, asoundin=null, asoundout=null, local=TRUE) //VOREStation Edit
+/proc/do_teleport(ateleatom, adestination, aprecision=0, afteleport=1, aeffectin=null, aeffectout=null, asoundin=null, asoundout=null, local=TRUE, bohsafe=FALSE)
new /datum/teleport/instant/science(arglist(args))
return
@@ -19,26 +19,28 @@ var/bluespace_item_types = newlist(/obj/item/storage/backpack/holding,
var/soundin //soundfile to play before teleportation
var/soundout //soundfile to play after teleportation
var/force_teleport = 1 //if false, teleport will use Move() proc (dense objects will prevent teleportation)
- var/local = TRUE //VOREStation Add - If false, can teleport from/to any z-level
+ var/local = TRUE //If false, can teleport from/to any z-level
+ var/bohsafe = FALSE //If true, can teleport safely with a BoH
-/datum/teleport/New(ateleatom, adestination, aprecision=0, afteleport=1, aeffectin=null, aeffectout=null, asoundin=null, asoundout=null, local=TRUE) //VOREStation Edit
+/datum/teleport/New(ateleatom, adestination, aprecision=0, afteleport=1, aeffectin=null, aeffectout=null, asoundin=null, asoundout=null, local=TRUE, bohsafe=FALSE)
..()
if(!initTeleport(arglist(args)))
return 0
return 1
-/datum/teleport/proc/initTeleport(ateleatom,adestination,aprecision,afteleport,aeffectin,aeffectout,asoundin,asoundout,local) //VOREStation Edit
+/datum/teleport/proc/initTeleport(ateleatom,adestination,aprecision,afteleport,aeffectin,aeffectout,asoundin,asoundout,local,bohsafe)
if(!setTeleatom(ateleatom))
return 0
if(!setDestination(adestination))
return 0
+ src.bohsafe = bohsafe
if(!setPrecision(aprecision))
return 0
setEffects(aeffectin,aeffectout)
setForceTeleport(afteleport)
setSounds(asoundin,asoundout)
- src.local = local // VOREStation Add
+ src.local = local
return 1
//must succeed
@@ -121,7 +123,7 @@ var/bluespace_item_types = newlist(/obj/item/storage/backpack/holding,
var/mob/living/L = teleatom
if(L.buckled)
C = L.buckled
- if(attempt_vr(src,"try_televore",args)) return //VOREStation Edit - Telenoms.
+ if(attempt_vr(src,"try_televore",args)) return
if(force_teleport)
teleatom.forceMove(destturf)
playSpecials(destturf,effectout,soundout)
@@ -140,7 +142,7 @@ var/bluespace_item_types = newlist(/obj/item/storage/backpack/holding,
/datum/teleport/instant //teleports when datum is created
-/datum/teleport/instant/New(ateleatom, adestination, aprecision=0, afteleport=1, aeffectin=null, aeffectout=null, asoundin=null, asoundout=null)
+/datum/teleport/instant/New(ateleatom, adestination, aprecision=0, afteleport=1, bohsafe=0, aeffectin=null, aeffectout=null, asoundin=null, asoundout=null)
if(..())
teleport()
return
@@ -150,14 +152,18 @@ var/bluespace_item_types = newlist(/obj/item/storage/backpack/holding,
if(!aeffectin || !aeffectout)
var/datum/effect/effect/system/spark_spread/aeffect = new
aeffect.set_up(5, 1, teleatom)
+ var/datum/effect/effect/system/spark_spread/aeffect2 = new
+ aeffect2.set_up(5, 1, teleatom)
effectin = effectin || aeffect
- effectout = effectout || aeffect
+ effectout = effectout || aeffect2
return 1
else
return ..()
/datum/teleport/instant/science/setPrecision(aprecision)
..()
+ if(bohsafe)
+ return 1
var/list/bluespace_things = newlist()
@@ -166,14 +172,12 @@ var/bluespace_item_types = newlist(/obj/item/storage/backpack/holding,
precision = rand(1, 100)
bluespace_things |= teleatom.search_contents_for(item)
- //VOREStation Addition Start: Prevent taurriding abuse
if(isliving(teleatom))
var/mob/living/L = teleatom
if(LAZYLEN(L.buckled_mobs))
for(var/mob/rider in L.buckled_mobs)
for (var/item in bluespace_item_types)
bluespace_things |= rider.search_contents_for(item)
- //VOREStation Addition End: Prevent taurriding abuse
if(bluespace_things.len)
precision = max(rand(1,100)*bluespace_things.len,100)
@@ -194,17 +198,6 @@ var/bluespace_item_types = newlist(/obj/item/storage/backpack/holding,
else
teleatom.visible_message(span_danger("\The [teleatom] bounces off of the portal!"))
return 0
- /* VOREStation Removal
- if(destination.z in using_map.admin_levels) //CentCom z-level
- if(istype(teleatom, /obj/mecha))
- var/obj/mecha/MM = teleatom
- to_chat(MM.occupant, span_danger("\The [MM] would not survive the jump to a location so far away!"))
- return 0
- if(!isemptylist(teleatom.search_contents_for(/obj/item/storage/backpack/holding)))
- teleatom.visible_message(span_danger("\The [teleatom] bounces off of the portal!"))
- return 0
- */ //VOREStation Removal End
- //VOREStation Edit Start
var/obstructed = 0
var/turf/dest_turf = get_turf(destination)
if(local && !(dest_turf.z in using_map.player_levels))
@@ -231,4 +224,3 @@ var/bluespace_item_types = newlist(/obj/item/storage/backpack/holding,
return 0
else
return 1
- //VOREStation Edit End
diff --git a/code/game/area/Space Station 13 areas.dm b/code/game/area/Space Station 13 areas.dm
index 21f55597055..978db7f00f4 100755
--- a/code/game/area/Space Station 13 areas.dm
+++ b/code/game/area/Space Station 13 areas.dm
@@ -2666,3 +2666,10 @@ var/list/the_station_areas = list (
luminosity = 1
dynamic_lighting = 0
requires_power = 0
+
+/area/shadekin
+ name = "\improper Shadekin Retreat"
+ icon_state = "blue"
+ requires_power = 0
+ ambience = AMBIENCE_OTHERWORLDLY
+ flags = RAD_SHIELDED | AREA_FLAG_IS_NOT_PERSISTENT | BLUE_SHIELDED | AREA_ALLOW_LARGE_SIZE | AREA_LIMIT_DARK_RESPITE | AREA_ALLOW_CLOCKOUT
diff --git a/code/game/area/areas.dm b/code/game/area/areas.dm
index 869ccd25a65..36b699ee3c6 100644
--- a/code/game/area/areas.dm
+++ b/code/game/area/areas.dm
@@ -563,23 +563,16 @@ GLOBAL_DATUM(spoiler_obfuscation_image, /image)
return (flags & flag) == flag
return flags & flag
-// RS Port #658 Start
-/area/proc/check_phase_shift(var/mob/ourmob)
+/area/proc/check_phase_shift(var/mob/living/ourmob)
if(!flag_check(AREA_BLOCK_PHASE_SHIFT) || !ourmob.is_incorporeal())
return
if(!isliving(ourmob))
return
if(ourmob.client?.holder)
return
- if(issimplekin(ourmob))
- var/mob/living/simple_mob/shadekin/SK = ourmob
- if(SK.ability_flags & AB_PHASE_SHIFTED)
- SK.phase_in(SK.loc)
- if(ishuman(ourmob))
- var/mob/living/carbon/human/SK = ourmob
- if(SK.ability_flags & AB_PHASE_SHIFTED)
- SK.phase_in(SK.loc)
-// RS Port #658 End
+ var/datum/component/shadekin/SK = ourmob.get_shadekin_component()
+ if(SK && SK.in_phase)
+ ourmob.phase_in(ourmob.loc, SK)
/area/proc/isAlwaysIndoors()
return FALSE
diff --git a/code/game/objects/items/devices/flashlight.dm b/code/game/objects/items/devices/flashlight.dm
index af564b54803..8b7a5be3190 100644
--- a/code/game/objects/items/devices/flashlight.dm
+++ b/code/game/objects/items/devices/flashlight.dm
@@ -33,6 +33,7 @@
var/cell_type = /obj/item/cell/device
var/power_usage = 1
var/power_use = 1
+ var/flickering = FALSE
pickup_sound = 'sound/items/pickup/device.ogg'
drop_sound = 'sound/items/drop/device.ogg'
@@ -88,6 +89,9 @@
. += "It appears to have a high amount of power remaining."
/obj/item/flashlight/attack_self(mob/user)
+ if(flickering)
+ to_chat(user, "The light is currently malfunctioning and you're unable to adjust it!") //To prevent some lighting anomalities.
+ return
if(power_use)
if(!isturf(user.loc))
to_chat(user, "You cannot turn the light on while in this [user.loc].") //To prevent some lighting anomalities.
@@ -240,6 +244,48 @@
var/turf/T = get_turf(target)
OL.place_directional_light(T)
+/obj/item/flashlight/proc/flicker(var/amount = rand(10, 20), var/flicker_color, var/forced)
+ if(flickering)
+ return
+ if(!flicker_color)
+ flicker_color = light_color //If we don't have a flicker color, use our current light color.
+ if((!power_use && !forced) || (!power_usage && !forced))
+ return //We don't use power / have no power, so we're probably a flare or dead.
+ flickering = TRUE
+ var/original_color = light_color
+ var/original_on = on
+ var/datum/component/overlay_lighting/OL = GetComponent(/datum/component/overlay_lighting) //BEWARE, ESOTERIC BULLSHIT HERE.
+ if(flicker_color && light_color != flicker_color)
+ set_light_color(flicker_color)
+ OL.directional_atom.color = flicker_color
+ do_flicker(amount, flicker_color, original_color, original_on, OL, 1)
+
+
+/// Args:
+/// amount is how many timer to flicker.
+/// flicker_color is what to set the flashlight to when we flicker.
+/// original_color is what our original color was prior to flickering
+/// original_on is if we were originally on or not.
+/// OL is our overlay for lighting.
+/// ticker is how many times we have flickered so far.
+/obj/item/flashlight/proc/do_flicker(var/amount = rand(10, 20), var/flicker_color, var/original_color, var/original_on, var/datum/component/overlay_lighting/OL, var/ticker)
+ if(ticker >= amount) //We have flickered enough times. Terminate the cycle.
+ finish_flicker(original_color, original_on, OL)
+ return
+ on = !on
+ update_brightness()
+ if(!on) // Only play when the light turns off.
+ playsound(src, 'sound/effects/light_flicker.ogg', 50, 1)
+ addtimer(CALLBACK(src, PROC_REF(do_flicker), amount, flicker_color, original_color, original_on, OL, ++ticker), rand(5,15), TIMER_DELETE_ME)
+
+/obj/item/flashlight/proc/finish_flicker(var/original_color, var/original_on, var/datum/component/overlay_lighting/OL)
+ set_light_color(original_color)
+ OL.directional_atom.color = original_color
+ on = original_on
+ flickering = FALSE
+ update_brightness()
+
+
/obj/item/flashlight/pen
name = "penlight"
desc = "A pen-sized light, used by medical staff."
diff --git a/code/modules/emotes/definitions/audible.dm b/code/modules/emotes/definitions/audible.dm
index c44831f9850..0cba9c91d69 100644
--- a/code/modules/emotes/definitions/audible.dm
+++ b/code/modules/emotes/definitions/audible.dm
@@ -283,3 +283,41 @@
emote_message_1p_target = "You prbt at TARGET."
emote_message_3p_target = "prbts at TARGET."
emote_sound = 'sound/voice/prbt.ogg'
+
+//Some Spooky sounds.
+/decl/emote/audible/evil_laugh
+ key = "evillaugh"
+ emote_message_3p = "laughs!"
+ emote_sound = 'sound/mob/spooky/laugh.ogg'
+
+/decl/emote/audible/evil_no
+ key = "evilno"
+ emote_message_3p = "says no!"
+ emote_sound = 'sound/mob/spooky/no.ogg'
+
+/decl/emote/audible/evil_breathing
+ key = "evilbreath"
+ emote_message_3p = "breaths heavily!"
+ emote_sound = 'sound/mob/spooky/breath1.ogg'
+
+/decl/emote/audible/evil_breathing_2
+ key = "evilbreath2"
+ emote_message_3p = "breaths heavily!"
+ emote_sound = 'sound/mob/spooky/breath2.ogg'
+
+/decl/emote/audible/goodripsound
+ key = "goodripsound"
+ emote_message_3p = "drips goo."
+
+/decl/emote/audible/goodripsound/do_extra(var/mob/user)
+ ..()
+ var/goo_sounds = list (
+ 'sound/mob/spooky/decay1.ogg',
+ 'sound/mob/spooky/decay2.ogg',
+ 'sound/mob/spooky/decay3.ogg',
+ 'sound/mob/spooky/corrosion1.ogg',
+ 'sound/mob/spooky/corrosion2.ogg',
+ 'sound/mob/spooky/corrosion3.ogg'
+ )
+ var/sound = pick(goo_sounds)
+ playsound(user.loc, sound, 100, 1)
diff --git a/code/modules/emotes/definitions/visible.dm b/code/modules/emotes/definitions/visible.dm
index 78cfa4ec7d3..780d1373c0c 100644
--- a/code/modules/emotes/definitions/visible.dm
+++ b/code/modules/emotes/definitions/visible.dm
@@ -331,3 +331,18 @@
/decl/emote/visible/tilt
key = "tilt"
emote_message_3p = "tilts USER_THEIR head."
+
+/decl/emote/visible/goodrip
+ key = "goodrip"
+ emote_message_3p = "drips goo."
+
+/decl/emote/visible/goodrip/do_extra(var/mob/user)
+ ..()
+ new /obj/effect/decal/cleanable/blood/oil(user)
+ var/goo_sounds = list (
+ 'sound/mob/spooky/decay1.ogg',
+ 'sound/mob/spooky/decay2.ogg',
+ 'sound/mob/spooky/decay3.ogg'
+ )
+ var/sound = pick(goo_sounds)
+ playsound(user.loc, sound, 100, 1)
diff --git a/code/modules/emotes/emote_mob.dm b/code/modules/emotes/emote_mob.dm
index d48f0bd0890..fb00512cdb4 100644
--- a/code/modules/emotes/emote_mob.dm
+++ b/code/modules/emotes/emote_mob.dm
@@ -27,15 +27,12 @@
if(world.time < next_emote)
to_chat(src, span_warning("You cannot use another emote yet."))
return
- //VOREStation Addition Start
if(forced_psay)
pme(message)
return
if(autowhisper)
return me_verb_subtle(message)
- //VOREStation Addition End
-
if(act == "help")
if(world.time >= next_emote_refresh)
var/list/usable_emotes = list()
diff --git a/code/modules/mob/living/carbon/human/human.dm b/code/modules/mob/living/carbon/human/human.dm
index 510a729f509..06c21826732 100644
--- a/code/modules/mob/living/carbon/human/human.dm
+++ b/code/modules/mob/living/carbon/human/human.dm
@@ -332,12 +332,13 @@
return if_no_id
//repurposed proc. Now it combines get_id_name() and get_face_name() to determine a mob's name variable. Made into a seperate proc as it'll be useful elsewhere
-/mob/living/carbon/human/proc/get_visible_name()
- if(ability_flags & AB_PHASE_SHIFTED)
+/mob/living/carbon/human/get_visible_name()
+ var/datum/component/shadekin/SK = get_shadekin_component()
+ if(SK && SK.in_phase)
return "Something" // Something
- if( wear_mask && (wear_mask.flags_inv&HIDEFACE) ) //Wearing a mask which hides our face, use id-name if possible
+ if(wear_mask && (wear_mask.flags_inv&HIDEFACE)) //Wearing a mask which hides our face, use id-name if possible
return get_id_name("Unknown")
- if( head && (head.flags_inv&HIDEFACE) )
+ if(head && (head.flags_inv&HIDEFACE))
return get_id_name("Unknown") //Likewise for hats
var/face_name = get_face_name()
var/id_name = get_id_name("")
diff --git a/code/modules/mob/living/carbon/human/human_defines_vr.dm b/code/modules/mob/living/carbon/human/human_defines_vr.dm
index f0f97d9437d..699f5bbc849 100644
--- a/code/modules/mob/living/carbon/human/human_defines_vr.dm
+++ b/code/modules/mob/living/carbon/human/human_defines_vr.dm
@@ -12,43 +12,3 @@
var/low_sorting_priority = FALSE
tail_alt = TAIL_UPPER_LAYER // not a toggle for humans but a pointer for what layer the tail should be when facing North/East/West
-
-/mob/living/carbon/human/proc/shadekin_get_energy()
- var/datum/species/shadekin/SK = species
-
- if(!istype(SK))
- return 0
-
- return SK.get_energy(src)
-
-/mob/living/carbon/human/proc/shadekin_get_max_energy()
- var/datum/species/shadekin/SK = species
-
- if(!istype(SK))
- return 0
-
- return SK.get_max_energy(src)
-
-/mob/living/carbon/human/proc/shadekin_set_energy(var/new_energy)
- var/datum/species/shadekin/SK = species
-
- if(!istype(SK))
- return 0
-
- SK.set_energy(src, new_energy)
-
-/mob/living/carbon/human/proc/shadekin_set_max_energy(var/new_max_energy)
- var/datum/species/shadekin/SK = species
-
- if(!istype(SK))
- return 0
-
- SK.set_max_energy(src, new_max_energy)
-
-/mob/living/carbon/human/proc/shadekin_adjust_energy(var/amount)
- var/datum/species/shadekin/SK = species
-
- if(!istype(SK))
- return 0
-
- SK.set_energy(src, SK.get_energy(src) + amount)
diff --git a/code/modules/mob/living/carbon/human/say.dm b/code/modules/mob/living/carbon/human/say.dm
index f0ef71535a3..4cba87ae1b8 100644
--- a/code/modules/mob/living/carbon/human/say.dm
+++ b/code/modules/mob/living/carbon/human/say.dm
@@ -1,5 +1,6 @@
/mob/living/carbon/human/GetAltName()
- if(ability_flags & AB_PHASE_SHIFTED)
+ var/datum/component/shadekin/SK = get_shadekin_component()
+ if(SK && SK.in_phase)
return ""
if(name != GetVoice())
return " (as [get_id_name("Unknown")])"
diff --git a/code/modules/mob/living/carbon/human/species/shadekin/shadekin.dm b/code/modules/mob/living/carbon/human/species/shadekin/shadekin.dm
index fa011c5662f..27f6c0d78fe 100644
--- a/code/modules/mob/living/carbon/human/species/shadekin/shadekin.dm
+++ b/code/modules/mob/living/carbon/human/species/shadekin/shadekin.dm
@@ -68,7 +68,6 @@
breath_type = null
poison_type = null
water_breather = TRUE //They don't quite breathe
- var/doing_phase = FALSE // Prevent bugs when spamming phase button
vision_flags = SEE_SELF|SEE_MOBS
appearance_flags = HAS_HAIR_COLOR | HAS_LIPS | HAS_SKIN_COLOR | HAS_EYE_COLOR | HAS_UNDERWEAR
@@ -101,22 +100,8 @@
BP_R_FOOT = list("path" = /obj/item/organ/external/foot/right)
)
- //SHADEKIN-UNIQUE STUFF GOES HERE
- var/list/shadekin_abilities = list(/datum/power/shadekin/phase_shift,
- /datum/power/shadekin/regenerate_other,
- /datum/power/shadekin/create_shade)
- var/list/shadekin_ability_datums = list()
- var/kin_type
- var/energy_light = 0.25
- var/energy_dark = 0.75
species_component = /datum/component/shadekin
-/datum/species/shadekin/New()
- ..()
- for(var/power in shadekin_abilities)
- var/datum/power/shadekin/SKP = new power(src)
- shadekin_ability_datums.Add(SKP)
-
/datum/species/shadekin/handle_death(var/mob/living/carbon/human/H)
spawn(1)
for(var/obj/item/W in H)
@@ -129,212 +114,33 @@
/datum/species/shadekin/get_random_name()
return "shadekin"
-/datum/species/shadekin/handle_environment_special(var/mob/living/carbon/human/H)
- handle_shade(H)
- ..()
-
-/datum/species/shadekin/add_inherent_verbs(var/mob/living/carbon/human/H)
- ..()
- add_shadekin_abilities(H)
-
-/datum/species/shadekin/proc/add_shadekin_abilities(var/mob/living/carbon/human/H)
- if(!H.ability_master || !istype(H.ability_master, /obj/screen/movable/ability_master/shadekin))
- H.ability_master = null
- H.ability_master = new /obj/screen/movable/ability_master/shadekin(H)
- for(var/datum/power/shadekin/P in shadekin_ability_datums)
- if(!(P.verbpath in H.verbs))
- add_verb(H, P.verbpath)
- H.ability_master.add_shadekin_ability(
- object_given = H,
- verb_given = P.verbpath,
- name_given = P.name,
- ability_icon_given = P.ability_icon_state,
- arguments = list()
- )
-
-/datum/species/shadekin/proc/handle_shade(var/mob/living/carbon/human/H)
- //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(H)
- 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(H.ability_flags & AB_PHASE_SHIFTED)
- dark_gains = 0
- else
- //Heal (very) slowly in good darkness
- if(is_dark)
- H.adjustFireLoss((-0.10)*darkness)
- H.adjustBruteLoss((-0.10)*darkness)
- H.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
-
- set_energy(H, get_energy(H) + dark_gains)
-
- //Update huds
- update_shadekin_hud(H)
-
-/datum/species/shadekin/proc/get_energy(var/mob/living/carbon/human/H)
- var/datum/component/shadekin/comp = H.GetComponent(/datum/component/shadekin)
- if(!comp)
- return FALSE //No component, no energy to be had.
-
- if(comp.dark_energy_infinite)
- return comp.max_dark_energy
-
- return comp.dark_energy
-
-/datum/species/shadekin/proc/get_max_energy(var/mob/living/carbon/human/H)
- var/datum/component/shadekin/comp = H.GetComponent(/datum/component/shadekin)
- if(!comp)
- return FALSE //No component, no energy to be had.
-
- return comp.max_dark_energy
-
-/datum/species/shadekin/proc/set_energy(var/mob/living/carbon/human/H, var/new_energy)
- var/datum/component/shadekin/comp = H.GetComponent(/datum/component/shadekin)
- if(!comp)
- return FALSE //No component, no energy to be had.
-
- comp.dark_energy = CLAMP(new_energy, 0, get_max_energy(H))
-
-/datum/species/shadekin/proc/set_max_energy(var/mob/living/carbon/human/H, var/new_max_energy)
- var/datum/component/shadekin/comp = H.GetComponent(/datum/component/shadekin)
- if(!comp)
- return FALSE //No component, no energy to be had.
-
- comp.max_dark_energy = new_max_energy
-
-/datum/species/shadekin/proc/update_shadekin_hud(var/mob/living/carbon/human/H)
- var/turf/T = get_turf(H)
- if(H.shadekin_display)
- var/l_icon = 0
- var/e_icon = 0
-
- H.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(get_energy(H))
- 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
-
- H.shadekin_display.icon_state = "shadekin-[l_icon]-[e_icon]"
- return
-
-/datum/species/shadekin/proc/get_shadekin_eyecolor(var/mob/living/carbon/human/H)
- 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.
- var/eyecolor_type = BLUE_EYES
- switch(eyecolor_hue)
- if(0 to 20)
- eyecolor_type = RED_EYES
- if(21 to 50)
- eyecolor_type = ORANGE_EYES
- if(51 to 70)
- eyecolor_type = YELLOW_EYES
- if(71 to 160)
- eyecolor_type = GREEN_EYES
- if(161 to 260)
- eyecolor_type = BLUE_EYES
- if(261 to 340)
- eyecolor_type = PURPLE_EYES
- if(341 to 360)
- eyecolor_type = RED_EYES
-
- return eyecolor_type
-
/datum/species/shadekin/post_spawn_special(var/mob/living/carbon/human/H)
.=..()
- var/eyecolor_type = get_shadekin_eyecolor(H)
+ var/datum/component/shadekin/SK = H.get_shadekin_component()
+ if(!SK)
+ CRASH("A shadekin [H] somehow is missing their shadekin component post-spawn!")
- switch(eyecolor_type)
+ switch(SK.eye_color)
if(BLUE_EYES)
total_health = 100
- energy_light = 0.5
- energy_dark = 0.5
if(RED_EYES)
total_health = 200
- energy_light = -1
- energy_dark = 0.1
if(PURPLE_EYES)
total_health = 150
- energy_light = -0.5
- energy_dark = 1
if(YELLOW_EYES)
total_health = 100
- energy_light = -2
- energy_dark = 3
if(GREEN_EYES)
total_health = 100
- energy_light = 0.125
- energy_dark = 2
if(ORANGE_EYES)
total_health = 175
- energy_light = -0.5
- energy_dark = 0.25
H.maxHealth = total_health
H.health = H.getMaxHealth()
/datum/species/shadekin/produceCopy(var/list/traits, var/mob/living/carbon/human/H, var/custom_base, var/reset_dna = TRUE) // Traitgenes reset_dna flag required, or genes get reset on resleeve
-
var/datum/species/shadekin/new_copy = ..()
-
new_copy.total_health = total_health
- new_copy.energy_light = energy_light
-
- new_copy.energy_dark = energy_dark
-
return new_copy
diff --git a/code/modules/mob/living/carbon/human/species/shadekin/shadekin_abilities.dm b/code/modules/mob/living/carbon/human/species/shadekin/shadekin_abilities.dm
index c92234a39ed..527e8b9903b 100644
--- a/code/modules/mob/living/carbon/human/species/shadekin/shadekin_abilities.dm
+++ b/code/modules/mob/living/carbon/human/species/shadekin/shadekin_abilities.dm
@@ -1,364 +1,22 @@
/datum/power/shadekin
/mob/living/carbon/human/is_incorporeal()
- if(ability_flags & AB_PHASE_SHIFTED) //Shadekin
+ var/datum/component/shadekin/SK = get_shadekin_component()
+ if(SK && SK.in_phase) //Shadekin
return TRUE
return ..()
-/////////////////////
-/// 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/carbon/human/proc/phase_shift
- ability_icon_state = "tech_passwall"
-
-/mob/living/carbon/human/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"
-
- //RS Port #658 Start
- 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
- //RS Port #658 End
-
- 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(ability_flags & AB_PHASE_SHIFTING)
- 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(!(ability_flags & AB_PHASE_SHIFTED))
- log_debug("[src] attempted to shift with [watcher] visible Carbons with a cost of [ability_cost] in a darkness level of [darkness]")
-
- var/datum/species/shadekin/SK = species
- 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.doing_phase)
- to_chat(src, span_warning("You are already trying to phase!"))
- return FALSE
- else if(shadekin_get_energy() < ability_cost && !(ability_flags & AB_PHASE_SHIFTED))
- to_chat(src, span_warning("Not enough energy for that ability!"))
- return FALSE
-
- if(!(ability_flags & AB_PHASE_SHIFTED))
- 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
-
- SK.doing_phase = TRUE // Prevent bugs when spamming phase button
- //Shifting in
- if(ability_flags & AB_PHASE_SHIFTED)
- phase_in(T)
- //Shifting out
- else
- phase_out(T)
- SK.doing_phase = FALSE // Prevent bugs when spamming phase button
-
-
-/mob/living/carbon/human/proc/phase_in(var/turf/T)
- if(ability_flags & AB_PHASE_SHIFTED)
-
- // pre-change
- 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
- ability_flags &= ~AB_PHASE_SHIFTED
- ability_flags |= AB_PHASE_SHIFTING
- 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)
- force_max_speed = initial(force_max_speed)
- 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), 5, TIMER_DELETE_ME)
-
-
-/mob/living/carbon/human/proc/shadekin_complete_phase_in(var/original_canmove)
- canmove = original_canmove
- alpha = initial(alpha)
- remove_modifiers_of_type(/datum/modifier/shadekin_phase_vision)
-
- //Potential phase-in vore
- if(can_be_drop_pred) //Toggleable in vore panel
- var/list/potentials = living_mobs(0)
- if(potentials.len)
- var/mob/living/target = pick(potentials)
- if(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]!"))
-
- ability_flags &= ~AB_PHASE_SHIFTING
-
- //Affect nearby lights
- var/destroy_lights = 0
-
- for(var/obj/machinery/light/L in GLOB.machines)
- if(L.z != z || get_dist(src,L) > 10)
- continue
-
- if(prob(destroy_lights))
- addtimer(CALLBACK(L, TYPE_PROC_REF(/obj/machinery/light, broken)), rand(5,25), TIMER_DELETE_ME)
- else
- L.flicker(10)
-
-/mob/living/carbon/human/proc/phase_out(var/turf/T)
- if(!(ability_flags & AB_PHASE_SHIFTED))
- // pre-change
- forceMove(T)
- var/original_canmove = canmove
- SetStunned(0)
- SetWeakened(0)
- if(buckled)
- buckled.unbuckle_mob()
- if(pulledby)
- pulledby.stop_pulling()
- stop_pulling()
- canmove = FALSE
-
- // change
- ability_flags |= AB_PHASE_SHIFTED
- ability_flags |= AB_PHASE_SHIFTING
- throwpass = TRUE
- automatic_custom_emote(VISIBLE_MESSAGE,"phases out!")
- 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)
- sleep(5)
- invisibility = INVISIBILITY_SHADEKIN
- see_invisible = INVISIBILITY_SHADEKIN
- see_invisible_default = INVISIBILITY_SHADEKIN // Allow seeing phased entities while phased.
- //cut_overlays()
- update_icon()
- alpha = 127
-
- canmove = original_canmove
- incorporeal_move = TRUE
- density = FALSE
- force_max_speed = TRUE
- ability_flags &= ~AB_PHASE_SHIFTING
-
-/datum/modifier/shadekin_phase_vision
- name = "Shadekin Phase Vision"
- vision_flags = SEE_THRU
-
-//////////////////////////
-/// REGENERATE OTHER ///
-//////////////////////////
-/datum/power/shadekin/regenerate_other
- name = "Regenerate Other (50)"
- desc = "Spend energy to heal physical wounds in another creature."
- verbpath = /mob/living/carbon/human/proc/regenerate_other
- ability_icon_state = "tech_biomedaura"
-
-/mob/living/carbon/human/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/species/shadekin/SK = species
- 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(shadekin_get_energy() < ability_cost)
- to_chat(src, span_warning("Not enough energy for that ability!"))
- return FALSE
- else if(ability_flags & AB_PHASE_SHIFTED)
- 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)
- 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)
-
-
-//////////////////////
-/// CREATE SHADE ///
-//////////////////////
-/datum/power/shadekin/create_shade
- name = "Create Shade (25)"
- desc = "Create a field of darkness that follows you."
- verbpath = /mob/living/carbon/human/proc/create_shade
- ability_icon_state = "tech_dispelold"
-
-/mob/living/carbon/human/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/species/shadekin/SK = species
- 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(shadekin_get_energy() < ability_cost)
- to_chat(src, span_warning("Not enough energy for that ability!"))
- return FALSE
- else if(ability_flags & AB_PHASE_SHIFTED)
- 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)
- 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()
- if(my_kin.ability_flags & AB_PHASE_SHIFTED)
- 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
-
// force dephase proc, to be called by other procs to dephase the shadekin. T is the target to force dephase them to.
/mob/living/carbon/human/proc/attack_dephase(var/turf/T = null, atom/dephaser)
- var/datum/species/shadekin/SK = species
-
+ var/datum/component/shadekin/SK = get_shadekin_component()
+ if(!SK)
+ return FALSE
// no assigned dephase-target, just use our own
if(!T)
T = get_turf(src)
// make sure it's possible to be dephased (and we're in phase)
- if(!istype(SK) || SK.doing_phase || !T.CanPass(src,T) || loc != T || !(ability_flags & AB_PHASE_SHIFTED) )
+ if(SK.doing_phase || !T.CanPass(src,T) || loc != T || !(SK.in_phase) )
return FALSE
@@ -366,6 +24,6 @@
message_admins("[key_name_admin(src)] was stunned out of phase at [T.x],[T.y],[T.z] by [dephaser.name], last touched by [dephaser.forensic_data?.get_lastprint()]. (JMP)", 1)
// start the dephase
phase_in(T)
- shadekin_adjust_energy(-20) // loss of energy for the interception
+ SK.shadekin_adjust_energy(-20) // loss of energy for the interception
// apply a little extra stun for good measure
src.Weaken(3)
diff --git a/code/modules/mob/living/carbon/human/species/shadekin/shadekin_hud.dm b/code/modules/mob/living/carbon/human/species/shadekin/shadekin_hud.dm
index 820fbb0c6b9..9749228db1a 100644
--- a/code/modules/mob/living/carbon/human/species/shadekin/shadekin_hud.dm
+++ b/code/modules/mob/living/carbon/human/species/shadekin/shadekin_hud.dm
@@ -26,6 +26,7 @@
/obj/screen/ability/verb_based/shadekin
icon_state = "grey_spell_base"
background_base_state = "grey"
+ icon = 'icons/mob/shadekin_abilities.dmi'
/obj/screen/movable/ability_master/proc/add_shadekin_ability(var/object_given, var/verb_given, var/name_given, var/ability_icon_given, var/arguments)
if(!object_given)
diff --git a/code/modules/mob/living/carbon/human/species/species.dm b/code/modules/mob/living/carbon/human/species/species.dm
index 63d3bb5f47f..7f45c9919f3 100644
--- a/code/modules/mob/living/carbon/human/species/species.dm
+++ b/code/modules/mob/living/carbon/human/species/species.dm
@@ -594,12 +594,11 @@
SEND_SIGNAL(H, COMSIG_XENOCHIMERA_COMPONENT)
//Shadekin Species Component.
- /* //For when shadekin actually have their component control everything.
- var/datum/component/shadekin/sk = H.get_xenochimera_component()
+ //For when shadekin actually have their component control everything.
+ var/datum/component/shadekin/sk = H.get_shadekin_component()
if(sk)
- if(!H.stat || !(xc.revive_ready == REVIVING_NOW || xc.revive_ready == REVIVING_DONE))
+ if(!H.stat)
SEND_SIGNAL(H, COMSIG_SHADEKIN_COMPONENT)
- */
// Used to update alien icons for aliens.
/datum/species/proc/handle_login_special(var/mob/living/carbon/human/H)
diff --git a/code/modules/mob/living/carbon/human/species/species_attack.dm b/code/modules/mob/living/carbon/human/species/species_attack.dm
index d0dca46e40f..e3549fc06db 100644
--- a/code/modules/mob/living/carbon/human/species/species_attack.dm
+++ b/code/modules/mob/living/carbon/human/species/species_attack.dm
@@ -153,14 +153,18 @@
/datum/unarmed_attack/claws/shadekin/apply_effects(var/mob/living/carbon/human/user, var/mob/living/carbon/human/target, var/zone, var/attack_damage)
..()
if(!(target == user))
- user.shadekin_adjust_energy(attack_damage)
+ var/datum/component/shadekin/SK = user.get_shadekin_component()
+ if(SK)
+ SK.shadekin_adjust_energy(attack_damage)
/datum/unarmed_attack/bite/sharp/shadekin
/datum/unarmed_attack/bite/sharp/shadekin/apply_effects(var/mob/living/carbon/human/user, var/mob/living/carbon/human/target, var/zone, var/attack_damage)
..()
if(!(target == user))
- user.shadekin_adjust_energy(attack_damage)
+ var/datum/component/shadekin/SK = user.get_shadekin_component()
+ if(SK)
+ SK.shadekin_adjust_energy(attack_damage)
/datum/unarmed_attack/claws/chimera //special feral attack that gets stronger as they get angrier
diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm
index 7edadff1453..43856552a7b 100644
--- a/code/modules/mob/living/living.dm
+++ b/code/modules/mob/living/living.dm
@@ -18,6 +18,15 @@
selected_image = image(icon = GLOB.buildmode_hud, loc = src, icon_state = "ai_sel")
+/mob/living/proc/get_visible_name()
+ var/datum/component/shadekin/SK = get_shadekin_component()
+ if(SK && SK.in_phase)
+ return "Something"
+ if(real_name)
+ return real_name
+ else
+ return name
+
/mob/living/Destroy()
SSradiation.listeners -= src
remove_all_modifiers(TRUE)
@@ -1257,7 +1266,7 @@
swap_hand()
/mob/living/throw_item(atom/target)
- if(incapacitated() || !target || istype(target, /obj/screen))
+ if(incapacitated() || !target || istype(target, /obj/screen) || is_incorporeal())
return FALSE
var/atom/movable/item = src.get_active_hand()
diff --git a/code/modules/mob/living/silicon/robot/dogborg/dog_modules.dm b/code/modules/mob/living/silicon/robot/dogborg/dog_modules.dm
index 7d8bd2ad173..cba02699c6f 100644
--- a/code/modules/mob/living/silicon/robot/dogborg/dog_modules.dm
+++ b/code/modules/mob/living/silicon/robot/dogborg/dog_modules.dm
@@ -345,9 +345,10 @@
if(get_dist(get_turf(T), get_turf(src)) > leap_distance) return
- if(ishuman(T))
- var/mob/living/carbon/human/H = T
- if(H.get_species() == SPECIES_SHADEKIN && (H.ability_flags & AB_PHASE_SHIFTED))
+ if(isliving(T))
+ var/mob/living/M = T
+ var/datum/component/shadekin/SK = M.get_shadekin_component()
+ if(SK && SK.in_phase)
power_cost *= 2
if(!use_direct_power(power_cost, minimum_power - power_cost))
diff --git a/code/modules/mob/living/silicon/robot/robot_simple_items.dm b/code/modules/mob/living/silicon/robot/robot_simple_items.dm
index 28310b28043..9ca16ceb48e 100644
--- a/code/modules/mob/living/silicon/robot/robot_simple_items.dm
+++ b/code/modules/mob/living/silicon/robot/robot_simple_items.dm
@@ -419,18 +419,18 @@
for(var/datum/matter_synth/our_synth in module.synths)
switch(our_synth.name)
if(METAL_SYNTH)
+ if(has_glass)
+ possible_synths[/obj/item/stack/material/cyborg/glass/reinforced] = list(our_synth, has_glass)
possible_synths += list(/obj/item/stack/material/cyborg/steel = list(our_synth))
possible_synths += list(/obj/item/stack/tile/floor/cyborg = list(our_synth))
possible_synths += list(/obj/item/stack/rods/cyborg = list(our_synth))
possible_synths += list(/obj/item/stack/tile/roofing/cyborg = list(our_synth))
- if(has_glass)
- possible_synths |= list(/obj/item/stack/material/cyborg/glass/reinforced = list(our_synth, has_glass))
if(PLASTEEL_SYNTH)
possible_synths += list(/obj/item/stack/material/cyborg/plasteel = list(our_synth))
if(GLASS_SYNTH)
- possible_synths += list(/obj/item/stack/material/cyborg/glass = list(our_synth))
if(has_steel)
- possible_synths |= list(/obj/item/stack/material/cyborg/glass/reinforced = list(our_synth, has_steel))
+ possible_synths[/obj/item/stack/material/cyborg/glass/reinforced] = list(our_synth, has_steel)
+ possible_synths += list(/obj/item/stack/material/cyborg/glass = list(our_synth))
if(WOOD_SYNTH)
possible_synths += list(/obj/item/stack/tile/wood/cyborg = list(our_synth))
possible_synths += list(/obj/item/stack/material/cyborg/wood = list(our_synth))
diff --git a/code/modules/mob/living/simple_mob/life.dm b/code/modules/mob/living/simple_mob/life.dm
index 299f5064254..f20f55e7803 100644
--- a/code/modules/mob/living/simple_mob/life.dm
+++ b/code/modules/mob/living/simple_mob/life.dm
@@ -105,6 +105,8 @@
if(in_stasis)
return 1 // return early to skip atmos checks
+ if(is_incorporeal())
+ return 1
var/atom/A = src.loc
diff --git a/code/modules/mob/living/simple_mob/simple_hud.dm b/code/modules/mob/living/simple_mob/simple_hud.dm
index 63225d7b42c..2225906bfc2 100644
--- a/code/modules/mob/living/simple_mob/simple_hud.dm
+++ b/code/modules/mob/living/simple_mob/simple_hud.dm
@@ -203,6 +203,11 @@
zone_sel.update_icon()
hud_elements |= zone_sel
+ //Component hud elements. Made in /mob/living/create_mob_hud
+ hud_elements |= shadekin_display
+ hud_elements |= xenochimera_danger_display
+ hud_elements |= lleill_display
+
//Hand things
if(has_hands)
//Drop button
diff --git a/code/modules/mob/living/simple_mob/subtypes/vore/demon/demon_abilities.dm b/code/modules/mob/living/simple_mob/subtypes/vore/demon/demon_abilities.dm
index 71131c7d9ac..ff4acfcffc0 100644
--- a/code/modules/mob/living/simple_mob/subtypes/vore/demon/demon_abilities.dm
+++ b/code/modules/mob/living/simple_mob/subtypes/vore/demon/demon_abilities.dm
@@ -105,7 +105,7 @@
density = FALSE
force_max_speed = TRUE
-/mob/living/simple_mob/vore/demon/verb/phase_shift()
+/mob/living/simple_mob/vore/demon/verb/demonic_phase_shift()
set name = "Phase Shift"
set desc = "Shift out of reality temporarily"
set category = "Abilities.Demon"
diff --git a/code/modules/mob/living/simple_mob/subtypes/vore/mobs_monsters/clowns/bus.dm b/code/modules/mob/living/simple_mob/subtypes/vore/mobs_monsters/clowns/bus.dm
index b3fbd7b3a53..9c5574afb5c 100644
--- a/code/modules/mob/living/simple_mob/subtypes/vore/mobs_monsters/clowns/bus.dm
+++ b/code/modules/mob/living/simple_mob/subtypes/vore/mobs_monsters/clowns/bus.dm
@@ -4,4 +4,4 @@
/mob/living/simple_mob/clowns/big/c_shift/Initialize(mapload)
. = ..()
- add_verb(src, /mob/living/simple_mob/clowns/big/c_shift/proc/phase_shift)
+ comp = LoadComponent(comp)
diff --git a/code/modules/mob/living/simple_mob/subtypes/vore/mobs_monsters/clowns/c_shift.dm b/code/modules/mob/living/simple_mob/subtypes/vore/mobs_monsters/clowns/c_shift.dm
index e02e8b9a5f6..89ccd56b5b9 100644
--- a/code/modules/mob/living/simple_mob/subtypes/vore/mobs_monsters/clowns/c_shift.dm
+++ b/code/modules/mob/living/simple_mob/subtypes/vore/mobs_monsters/clowns/c_shift.dm
@@ -1,92 +1,20 @@
/mob/living/simple_mob/clowns/big/c_shift
- var/ability_flags = 0 //Flags for active abilities
-
-// Phase shifting procs (and related procs)
-/mob/living/simple_mob/clowns/big/c_shift/proc/phase_shift()
- var/turf/T = get_turf(src)
- if(!T.CanPass(src,T) || loc != T)
- to_chat(src,span_warning("You can't use that here!"))
- return FALSE
-
- forceMove(T)
- var/original_canmove = canmove
- SetStunned(0)
- SetWeakened(0)
- if(buckled)
- buckled.unbuckle_mob()
- if(pulledby)
- pulledby.stop_pulling()
- stop_pulling()
- canmove = FALSE
-
- //Shifting in
- if(ability_flags & AB_PHASE_SHIFTED)
- ability_flags &= ~AB_PHASE_SHIFTED
- mouse_opacity = 2
- name = real_name
-
-
- cut_overlays()
- alpha = initial(alpha)
- invisibility = initial(invisibility)
- see_invisible = initial(see_invisible)
- incorporeal_move = initial(incorporeal_move)
- density = initial(density)
- force_max_speed = initial(force_max_speed)
-
- //Cosmetics mostly
- flick("tp_in",src)
- automatic_custom_emote(VISIBLE_MESSAGE,"phases in!")
- sleep(5) //The duration of the TP animation
- canmove = original_canmove
-
- // Do this after the potential vore, so we get the belly
- update_icon()
-
- //Affect nearby lights
-
-
- for(var/obj/machinery/light/L in GLOB.machines)
- if(L.z != z || get_dist(src,L) > 10)
- continue
-
- L.flicker(10)
-
- //Shifting out
- else
- ability_flags |= AB_PHASE_SHIFTED
- mouse_opacity = 0
- automatic_custom_emote(VISIBLE_MESSAGE,"phases out!")
- real_name = name
- name = "Something"
-
- cut_overlays()
- flick("tp_out",src)
- sleep(5)
- invisibility = INVISIBILITY_LEVEL_TWO
- see_invisible = INVISIBILITY_LEVEL_TWO
- update_icon()
- alpha = 127
-
- canmove = original_canmove
- incorporeal_move = TRUE
- density = FALSE
- force_max_speed = TRUE
+ var/datum/component/shadekin/comp = /datum/component/shadekin/phase_only //Component that holds all the shadekin vars.
/mob/living/simple_mob/clowns/big/c_shift/UnarmedAttack()
- if(ability_flags & AB_PHASE_SHIFTED)
+ if(comp.in_phase)
return FALSE //Nope.
. = ..()
/mob/living/simple_mob/clowns/big/c_shift/can_fall()
- if(ability_flags & AB_PHASE_SHIFTED)
+ if(comp.in_phase)
return FALSE //Nope!
return ..()
/mob/living/simple_mob/clowns/big/c_shift/zMove(direction)
- if(ability_flags & AB_PHASE_SHIFTED)
+ if(comp.in_phase)
var/turf/destination = (direction == UP) ? GetAbove(src) : GetBelow(src)
if(destination)
forceMove(destination)
diff --git a/code/modules/mob/living/simple_mob/subtypes/vore/shadekin/ability_objects.dm b/code/modules/mob/living/simple_mob/subtypes/vore/shadekin/ability_objects.dm
index 1305f255648..b39871d4682 100644
--- a/code/modules/mob/living/simple_mob/subtypes/vore/shadekin/ability_objects.dm
+++ b/code/modules/mob/living/simple_mob/subtypes/vore/shadekin/ability_objects.dm
@@ -20,10 +20,9 @@
/obj/effect/shadekin_ability/proc/atom_button_text()
var/shift_denial
-
- if(shift_mode == NOT_WHILE_SHIFTED && (my_kin.ability_flags & AB_PHASE_SHIFTED))
+ if(shift_mode == NOT_WHILE_SHIFTED && my_kin.comp.in_phase)
shift_denial = "Physical Only"
- else if(shift_mode == ONLY_WHILE_SHIFTED && !(my_kin.ability_flags & AB_PHASE_SHIFTED))
+ else if(shift_mode == ONLY_WHILE_SHIFTED && !(my_kin.comp.in_phase))
shift_denial = "Shifted Only"
if(shift_denial)
@@ -47,10 +46,10 @@
if(my_kin.stat)
to_chat(my_kin,span_warning("Can't use that ability in your state!"))
return FALSE
- if(shift_mode == NOT_WHILE_SHIFTED && (my_kin.ability_flags & AB_PHASE_SHIFTED))
+ if(shift_mode == NOT_WHILE_SHIFTED && (my_kin.comp.in_phase))
to_chat(my_kin,span_warning("Can't use that ability while phase shifted!"))
return FALSE
- else if(shift_mode == ONLY_WHILE_SHIFTED && !(my_kin.ability_flags & AB_PHASE_SHIFTED))
+ else if(shift_mode == ONLY_WHILE_SHIFTED && !(my_kin.comp.in_phase))
to_chat(my_kin,span_warning("Can only use that ability while phase shifted!"))
return FALSE
else if(my_kin.comp.dark_energy < cost)
@@ -75,7 +74,7 @@
if(!..())
return
my_kin.phase_shift()
- if(my_kin.ability_flags & AB_PHASE_SHIFTED)
+ if(my_kin.comp.in_phase)
cost = 0 //Shifting back is free (but harmful in light)
else
cost = initial(cost)
@@ -136,7 +135,7 @@
var/mob/living/simple_mob/shadekin/my_kin
/datum/modifier/shadekin/create_shade/tick()
- if(my_kin.ability_flags & AB_PHASE_SHIFTED)
+ if(my_kin.comp.in_phase)
expire()
/datum/modifier/shadekin/create_shade/on_applied()
diff --git a/code/modules/mob/living/simple_mob/subtypes/vore/shadekin/ability_procs.dm b/code/modules/mob/living/simple_mob/subtypes/vore/shadekin/ability_procs.dm
index 0d002852720..ad65d291300 100644
--- a/code/modules/mob/living/simple_mob/subtypes/vore/shadekin/ability_procs.dm
+++ b/code/modules/mob/living/simple_mob/subtypes/vore/shadekin/ability_procs.dm
@@ -1,15 +1,13 @@
// Phase shifting procs (and related procs)
-/mob/living/simple_mob/shadekin/proc/phase_shift()
+/mob/living/simple_mob/shadekin/phase_shift()
var/turf/T = get_turf(src)
- var/area/A = T.loc //RS Port #658
+ var/area/A = T.loc
if(!T.CanPass(src,T) || loc != T)
to_chat(src,span_warning("You can't use that here!"))
return FALSE
- //RS Port #658 Start
if(!client?.holder && A.flag_check(AREA_BLOCK_PHASE_SHIFT))
to_chat(src,span_warning("You can't use that here!"))
return FALSE
- //RS Port #658 End
else if(doing_phase)
to_chat(src, span_warning("You are already trying to phase!"))
@@ -17,133 +15,27 @@
doing_phase = TRUE
//Shifting in
- if(ability_flags & AB_PHASE_SHIFTED)
+ if(comp.in_phase)
phase_in(T)
//Shifting out
else
phase_out(T)
doing_phase = FALSE
-/mob/living/simple_mob/shadekin/proc/phase_in(var/turf/T)
- if(ability_flags & AB_PHASE_SHIFTED)
-
- // pre-change
- forceMove(T)
- var/original_canmove = canmove
- SetStunned(0)
- SetWeakened(0)
- if(buckled)
- buckled.unbuckle_mob()
- if(pulledby)
- pulledby.stop_pulling()
- stop_pulling()
- canmove = FALSE
-
- // change
- ability_flags &= ~AB_PHASE_SHIFTED
- throwpass = FALSE
- name = real_name
- for(var/obj/belly/B as anything in vore_organs)
- B.escapable = initial(B.escapable)
-
- cut_overlays()
- alpha = initial(alpha)
- invisibility = initial(invisibility)
- see_invisible = initial(see_invisible)
- incorporeal_move = initial(incorporeal_move)
- density = initial(density)
- force_max_speed = initial(force_max_speed)
-
- //Cosmetics mostly
- flick("tp_in",src)
- automatic_custom_emote(VISIBLE_MESSAGE,"phases in!")
-
- addtimer(CALLBACK(src, PROC_REF(shadekin_complete_phase_in), original_canmove), 5, TIMER_DELETE_ME)
-
-
-/mob/living/simple_mob/shadekin/proc/shadekin_complete_phase_in(var/original_canmove)
- canmove = original_canmove
-
- //Potential phase-in vore
- if(can_be_drop_pred) //Toggleable in vore panel
- var/list/potentials = living_mobs(0)
- if(potentials.len)
- var/mob/living/target = pick(potentials)
- if(istype(target) && target.devourable && target.can_be_drop_prey && vore_selected)
- 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]!"))
-
- // Do this after the potential vore, so we get the belly
- update_icon()
-
- //Affect nearby lights
- var/destroy_lights = 0
- if(eye_state == RED_EYES)
- destroy_lights = 80
- if(eye_state == PURPLE_EYES)
- destroy_lights = 25
-
- for(var/obj/machinery/light/L in GLOB.machines)
- if(L.z != z || get_dist(src,L) > 10)
- continue
-
- if(prob(destroy_lights))
- addtimer(CALLBACK(L, TYPE_PROC_REF(/obj/machinery/light, broken)), rand(5,25), TIMER_DELETE_ME)
- else
- L.flicker(10)
-
-/mob/living/simple_mob/shadekin/proc/phase_out(var/turf/T)
- if(!(ability_flags & AB_PHASE_SHIFTED))
-
- // pre-change
- forceMove(T)
- var/original_canmove = canmove
- SetStunned(0)
- SetWeakened(0)
- if(buckled)
- buckled.unbuckle_mob()
- if(pulledby)
- pulledby.stop_pulling()
- stop_pulling()
- canmove = FALSE
-
- // change
- ability_flags |= AB_PHASE_SHIFTED
- throwpass = TRUE
- automatic_custom_emote(VISIBLE_MESSAGE,"phases out!")
- real_name = name
- name = "Something"
-
- for(var/obj/belly/B as anything in vore_organs)
- B.escapable = FALSE
-
- cut_overlays()
- flick("tp_out",src)
- sleep(5)
- invisibility = INVISIBILITY_LEVEL_TWO
- see_invisible = INVISIBILITY_LEVEL_TWO
- update_icon()
- alpha = 127
-
- canmove = original_canmove
- incorporeal_move = TRUE
- density = FALSE
- force_max_speed = TRUE
-
/mob/living/simple_mob/shadekin/UnarmedAttack()
- if(ability_flags & AB_PHASE_SHIFTED)
+ if(comp.in_phase)
return FALSE //Nope.
. = ..()
/mob/living/simple_mob/shadekin/can_fall()
- if(ability_flags & AB_PHASE_SHIFTED)
+ if(comp.in_phase)
return FALSE //Nope!
return ..()
/mob/living/simple_mob/shadekin/zMove(direction)
- if(ability_flags & AB_PHASE_SHIFTED)
+ if(comp.in_phase)
var/turf/destination = (direction == UP) ? GetAbove(src) : GetBelow(src)
if(destination)
forceMove(destination)
diff --git a/code/modules/mob/living/simple_mob/subtypes/vore/shadekin/shadekin.dm b/code/modules/mob/living/simple_mob/subtypes/vore/shadekin/shadekin.dm
index c8a7ede6990..17a88ceef18 100644
--- a/code/modules/mob/living/simple_mob/subtypes/vore/shadekin/shadekin.dm
+++ b/code/modules/mob/living/simple_mob/subtypes/vore/shadekin/shadekin.dm
@@ -71,8 +71,6 @@
var/datum/component/shadekin/comp = /datum/component/shadekin //Component that holds all the shadekin vars.
var/dark_gains = 0 //Last tick's change in energy
var/ability_flags = 0 //Flags for active abilities
- var/obj/screen/darkhud //Holder to update this icon
- var/obj/screen/energyhud //Holder to update this icon
var/list/shadekin_abilities
var/check_for_observer = FALSE
@@ -97,6 +95,7 @@
flags |= ATOM_INITIALIZED
return INITIALIZE_HINT_QDEL
comp = LoadComponent(comp)
+ set_eye_energy()
if(icon_state == "map_example")
icon_state = pick("white","dark","brown")
@@ -201,7 +200,7 @@
/mob/living/simple_mob/shadekin/Life()
. = ..()
- if(ability_flags & AB_PHASE_SHIFTED)
+ if(comp.in_phase)
density = FALSE
//Convert spare nutrition into energy at a certain ratio
@@ -215,9 +214,9 @@
if(!issimplekin(M))
non_kin_count ++
// Technically can be combined with ||, they call the same function, but readability is poor
- if(!non_kin_count && (ability_flags & AB_PHASE_SHIFTED))
+ if(!non_kin_count && (comp.in_phase))
phase_shift() // shifting back in, nobody present
- else if (non_kin_count && !(ability_flags & AB_PHASE_SHIFTED))
+ else if (non_kin_count && !(comp.in_phase))
phase_shift() // shifting out, scaredy
/mob/living/simple_mob/shadekin/update_icon()
@@ -248,13 +247,89 @@
//They phase back to the dark when killed
/mob/living/simple_mob/shadekin/death(gibbed, deathmessage = "phases to somewhere far away!")
- cut_overlays()
- icon_state = ""
- flick("tp_out",src)
- spawn(1 SECOND)
- qdel(src) //Back from whence you came!
+ var/special_handling = FALSE //varswitch for downstream
+ if(!special_handling)
+ cut_overlays()
+ icon_state = ""
+ flick("tp_out",src)
+ QDEL_IN(src, 1 SECOND)
+ . = ..(FALSE, deathmessage)
+ else
+ if(comp.respite_activating)
+ return
+ cut_overlays()
+ flick("tp_out",src)
+
+ var/area/current_area = get_area(src)
+ if((comp.in_dark_respite) || current_area.flag_check(AREA_LIMIT_DARK_RESPITE))
+ icon_state = ""
+ spawn(1 SECOND)
+ qdel(src) //Back from whence you came!
+
+ return ..(FALSE, deathmessage)
+
+
+ if(!LAZYLEN(GLOB.latejoin_thedark))
+ log_and_message_admins("[src] died outside of the dark but there were no valid floors to warp to")
+ icon_state = ""
+ spawn(1 SECOND)
+ qdel(src) //Back from whence you came!
+
+ return ..(FALSE, deathmessage)
+
+ visible_message("\The [src.name] [deathmessage]")
+ comp.respite_activating = TRUE
+
+ drop_l_hand()
+ drop_r_hand()
+
+ comp.dark_energy = 0
+ comp.in_dark_respite = TRUE
+ invisibility = INVISIBILITY_LEVEL_TWO
+
+ adjustFireLoss(-(getFireLoss() / 2))
+ adjustBruteLoss(-(getBruteLoss() / 2))
+ adjustToxLoss(-(getToxLoss() / 2))
+ Stun(10)
+ movement_cooldown = 5
+ nutrition = 0
+
+ if(istype(src.loc, /obj/belly))
+ //Yay digestion... presumably...
+ var/obj/belly/belly = src.loc
+ add_attack_logs(belly.owner, src, "Digested in [lowertext(belly.name)]")
+ to_chat(belly.owner, span_notice("\The [src.name] suddenly vanishes within your [belly.name]"))
+ forceMove(pick(GLOB.latejoin_thedark))
+ flick("tp_in",src)
+ comp.respite_activating = FALSE
+ comp.in_dark_respite = TRUE
+ belly.owner.handle_belly_update()
+ clear_fullscreen("belly")
+ if(hud_used)
+ if(!hud_used.hud_shown)
+ toggle_hud_vis()
+ stop_sound_channel(CHANNEL_PREYLOOP)
+
+ addtimer(CALLBACK(src, PROC_REF(can_leave_dark)), 10 MINUTES, TIMER_DELETE_ME)
+ else
+ addtimer(CALLBACK(src, PROC_REF(enter_the_dark)), 1 SECOND, TIMER_DELETE_ME)
+ addtimer(CALLBACK(src, PROC_REF(can_leave_dark)), 15 MINUTES, TIMER_DELETE_ME)
+
+/mob/living/simple_mob/shadekin/proc/enter_the_dark()
+ comp.respite_activating = FALSE
+ comp.in_dark_respite = TRUE
+
+ forceMove(pick(GLOB.latejoin_thedark))
+ update_icon()
+ flick("tp_in",src)
+ invisibility = initial(invisibility)
+ comp.respite_activating = FALSE
+
+/mob/living/simple_mob/shadekin/proc/can_leave_dark()
+ comp.in_dark_respite = FALSE
+ movement_cooldown = initial(movement_cooldown)
+ to_chat(src, span_notice("You feel like you can leave the Dark again"))
- . = ..(FALSE, deathmessage)
/* //VOREStation AI Temporary Removal
//Blue-eyes want to nom people to heal them
@@ -270,97 +345,34 @@
//They reach nutritional equilibrium (important for blue-eyes healbelly)
/mob/living/simple_mob/shadekin/Life()
if((. = ..()))
- handle_shade()
+ comp.handle_comp()
+
+/mob/living/simple_mob/shadekin/proc/set_eye_energy()
+ switch(eye_state)
+ //Blue has constant, steady (slow) regen and ignores darkness.
+ if(BLUE_EYES)
+ comp.set_light_and_darkness(0.75,0.75)
+ if(RED_EYES)
+ comp.set_light_and_darkness(-0.5,0.5)
+ if(PURPLE_EYES)
+ comp.set_light_and_darkness(-0.5,1)
+ if(YELLOW_EYES)
+ comp.set_light_and_darkness(-2,3)
+ if(GREEN_EYES)
+ comp.set_light_and_darkness(0.125,2)
+ if(ORANGE_EYES)
+ comp.set_light_and_darkness(-0.25,0.75)
/mob/living/simple_mob/shadekin/is_incorporeal()
- if(ability_flags & AB_PHASE_SHIFTED)
+ if(comp.in_phase)
return TRUE
return FALSE
/mob/living/simple_mob/shadekin/handle_atmos()
- if(ability_flags & AB_PHASE_SHIFTED)
+ if(comp.in_phase)
return
else
return .=..()
-
-/mob/living/simple_mob/shadekin/proc/handle_shade()
- //Shifted kin don't gain/lose energy (and save time if we're at the cap)
- var/darkness = 1
-
-
- var/turf/T = get_turf(src)
- if(!T)
- dark_gains = 0
- return
-
- var/brightness = T.get_lumcount() //Brightness in 0.0 to 1.0
- darkness = 1-brightness //Invert
-
- if(ability_flags & AB_PHASE_SHIFTED)
- dark_gains = 0
- else
- //Heal (very) slowly in good darkness
- if(darkness >= 0.75)
- adjustFireLoss(-0.05)
- adjustBruteLoss(-0.05)
- adjustToxLoss(-0.05)
-
- switch(eye_state)
- //Blue has constant, steady (slow) regen and ignores darkness.
- if(BLUE_EYES)
- dark_gains = 0.5
- //Red has extremely tiny energy buildup in dark, none in light, and hunts for energy.
- if(RED_EYES)
- if(darkness >= 0.75)
- dark_gains = 0.25
- //Purple eyes have moderate gains in darkness and loss in light.
- if(PURPLE_EYES)
- dark_gains = round((darkness - 0.5) * 2, 0.1)
- //Yellow has extreme gains in darkness and loss in light.
- if(YELLOW_EYES)
- dark_gains = round((darkness - 0.5) * 4, 0.1)
- //Similar to blues, but passive is less, and affected by dark
- if(GREEN_EYES)
- dark_gains = 0.25
- dark_gains += round((darkness - 0.5), 0.1)
- //More able to get energy out of the dark, worse attack gains tho
- if(ORANGE_EYES)
- if(darkness >= 0.65)
- dark_gains = 0.30
-
- comp.dark_energy = max(0,min(initial(comp.dark_energy),comp.dark_energy + dark_gains))
-
- if(comp.dark_energy_infinite)
- comp.dark_energy = 100
-
- //Update turf darkness hud
- if(darkhud)
- switch(darkness)
- if(0.80 to 1.00)
- darkhud.icon_state = "dark2"
- if(0.60 to 0.80)
- darkhud.icon_state = "dark1"
- if(0.40 to 0.60)
- darkhud.icon_state = "dark"
- if(0.20 to 0.40)
- darkhud.icon_state = "dark-1"
- if(0.00 to 0.20)
- darkhud.icon_state = "dark-2"
-
- //Update energy storage hud
- if(energyhud)
- switch(comp.dark_energy)
- if(80 to INFINITY)
- energyhud.icon_state = "energy0"
- if(60 to 80)
- energyhud.icon_state = "energy1"
- if(40 to 60)
- energyhud.icon_state = "energy2"
- if(20 to 40)
- energyhud.icon_state = "energy3"
- if(0 to 20)
- energyhud.icon_state = "energy4"
-
/* //VOREStation AI Removal
//Friendly ones wander towards people, maybe shy-ly if they are set to shy
/mob/living/simple_mob/shadekin/handle_wander_movement()
@@ -443,26 +455,6 @@
comp.dark_energy += gains
-//Special hud elements for darkness and energy gains
-/mob/living/simple_mob/shadekin/extra_huds(var/datum/hud/hud,var/icon/ui_style,var/list/hud_elements)
- //Darkness hud
- darkhud = new /obj/screen()
- darkhud.icon = ui_style
- darkhud.icon_state = "dark"
- darkhud.name = "darkness"
- darkhud.screen_loc = "CENTER-2:16,SOUTH:5" //Left of the left hand
- darkhud.alpha = 150
- hud_elements |= darkhud
-
- //Energy hud
- energyhud = new /obj/screen()
- energyhud.icon = ui_style
- energyhud.icon_state = "energy0"
- energyhud.name = "energy"
- energyhud.screen_loc = "CENTER+1:16,SOUTH:5" //Right of the right hand
- energyhud.alpha = 150
- hud_elements |= energyhud
-
// When someone clicks us with an empty hand
/mob/living/simple_mob/shadekin/attack_hand(mob/living/carbon/human/M as mob)
. = ..()
diff --git a/code/modules/power/lighting.dm b/code/modules/power/lighting.dm
index 2f71507e4da..d5707adb0df 100644
--- a/code/modules/power/lighting.dm
+++ b/code/modules/power/lighting.dm
@@ -402,6 +402,7 @@ var/global/list/light_type_cache = list()
else
base_state = "flamp"
..()
+
/obj/machinery/light/proc/set_alert_atmos()
if(!shows_alerts)
return
@@ -719,27 +720,35 @@ var/global/list/light_type_cache = list()
set_light(brightness_range * bulb_emergency_brightness_mul, max(bulb_emergency_pow_min, bulb_emergency_pow_mul * (cell.charge / cell.maxcharge)), bulb_emergency_colour)
return TRUE
-/obj/machinery/light/proc/flicker(var/amount = rand(10, 20))
+/obj/machinery/light/proc/flicker(var/amount = rand(10, 20), var/flicker_color)
if(flickering) return
if(on && status == LIGHT_OK)
flickering = 1
- do_flicker(amount)
+ do_flicker(amount, flicker_color, brightness_color, brightness_color_ns)
-/obj/machinery/light/proc/do_flicker(remaining_flicks)
+/obj/machinery/light/proc/do_flicker(remaining_flicks, flicker_color, original_color, original_color_ns)
SHOULD_NOT_OVERRIDE(TRUE)
PRIVATE_PROC(TRUE)
if(status != LIGHT_OK)
flickering = 0
return
on = !on
+ if(flicker_color && brightness_color != flicker_color)
+ brightness_color = flicker_color
+ brightness_color_ns = flicker_color
+ update(0) //Yes. This is done here and then immediately followed up with another update(0). Why does it need that? I have no clue. But a single update(0) does not work.
update(0)
if(!on) // Only play when the light turns off.
playsound(src, 'sound/effects/light_flicker.ogg', 50, 1)
if(remaining_flicks > 0)
remaining_flicks--
- addtimer(CALLBACK(src, PROC_REF(do_flicker), remaining_flicks), rand(5, 15), TIMER_DELETE_ME)
+ addtimer(CALLBACK(src, PROC_REF(do_flicker), remaining_flicks, flicker_color, original_color, original_color_ns), rand(5, 15), TIMER_DELETE_ME)
return
+ //All this happens after our final flicker.
on = (status == LIGHT_OK)
+ brightness_color = original_color
+ brightness_color_ns = original_color_ns
+ update(0)
update(0)
flickering = 0
diff --git a/code/modules/projectiles/gun.dm b/code/modules/projectiles/gun.dm
index 52359bf6dc5..5daaedec0f2 100644
--- a/code/modules/projectiles/gun.dm
+++ b/code/modules/projectiles/gun.dm
@@ -174,46 +174,46 @@
/obj/item/gun/proc/special_check(var/mob/user)
if(!isliving(user))
- return 0
+ return FALSE
if(!user.IsAdvancedToolUser())
- return 0
+ return FALSE
if(isanimal(user))
var/mob/living/simple_mob/S = user
if(!S.IsHumanoidToolUser(src))
- return 0
+ return FALSE
var/mob/living/M = user
if(istype(M))
if(M.has_modifier_of_type(/datum/modifier/underwater_stealth))
- to_chat(user,span_warning("You cannot use guns whilst hiding underwater!"))
- return 0
+ to_chat(user, span_warning("You cannot use guns whilst hiding underwater!"))
+ return FALSE
+ else if(M.has_modifier_of_type(/datum/modifier/phased_out))
+ to_chat(user, span_warning("You cannot use guns whilst incorporeal!"))
+ return FALSE
else if(M.has_modifier_of_type(/datum/modifier/rednet))
- to_chat(user,span_warning("Your gun refuses to fire!"))
- return 0
+ to_chat(user, span_warning("Your gun refuses to fire!"))
+ return FALSE
else if(M.has_modifier_of_type(/datum/modifier/trait/thickdigits))
- to_chat(user,span_warning("Your hands can't pull the trigger!!"))
- return 0
+ to_chat(user, span_warning("Your hands can't pull the trigger!!"))
+ return FALSE
else if(M.has_modifier_of_type(/datum/modifier/shield_projection/melee_focus))
- to_chat(user,span_warning("The shield projection around you prevents you from using anything but melee!!"))
- return 0
+ to_chat(user, span_warning("The shield projection around you prevents you from using anything but melee!!"))
+ return FALSE
if(dna_lock && attached_lock.stored_dna)
if(!authorized_user(user))
if(attached_lock.safety_level == 0)
to_chat(M, span_danger("\The [src] buzzes in dissapointment and displays an invalid DNA symbol."))
- return 0
+ return FALSE
if(!attached_lock.exploding)
if(attached_lock.safety_level == 1)
to_chat(M, span_danger("\The [src] hisses in dissapointment."))
visible_message(span_game(span_say(span_name("\The [src]") + " announces, \"Self-destruct occurring in ten seconds.\"")), span_game(span_say(span_name("\The [src]") + " announces, \"Self-destruct occurring in ten seconds.\"")))
attached_lock.exploding = 1
- spawn(100)
- explosion(src, 0, 0, 3, 4)
- sleep(1)
- qdel(src)
- return 0
+ addtimer(CALLBACK(src, PROC_REF(lock_explosion)), 10 SECONDS, TIMER_DELETE_ME)
+ return FALSE
if(HULK in M.mutations)
to_chat(M, span_danger("Your fingers are much too large for the trigger guard!"))
- return 0
+ return FALSE
if((CLUMSY in M.mutations) && prob(40)) //Clumsy handling
var/obj/P = consume_next_projectile()
if(P)
@@ -227,8 +227,12 @@
M.drop_item()
else
handle_click_empty(user)
- return 0
- return 1
+ return FALSE
+ return TRUE
+
+/obj/item/gun/proc/lock_explosion()
+ explosion(src, 0, 0, 3, 4)
+ QDEL_IN(src, 1)
/obj/item/gun/emp_act(severity)
for(var/obj/O in contents)
diff --git a/code/modules/tables/interactions.dm b/code/modules/tables/interactions.dm
index 01f4e18c483..01a8302edd2 100644
--- a/code/modules/tables/interactions.dm
+++ b/code/modules/tables/interactions.dm
@@ -53,9 +53,11 @@
return 1
/obj/structure/table/MouseDrop_T(obj/O, mob/user, src_location, over_location, src_control, over_control, params)
- if(user.is_incorporeal())
+ if(user.stat)
return
- if(ismob(O.loc)) //If placing an item
+ if(can_reinforce && isliving(user) && istype(O, /obj/item/stack/material) && user.get_active_hand() == O && Adjacent(user))
+ reinforce_table(O, user)
+ else if(ismob(O.loc)) //If placing an item
if(!isitem(O) || user.get_active_hand() != O)
return ..()
if(isrobot(user))
diff --git a/code/modules/tables/tables.dm b/code/modules/tables/tables.dm
index 16bdecf8eb1..d07c5d24a3c 100644
--- a/code/modules/tables/tables.dm
+++ b/code/modules/tables/tables.dm
@@ -193,12 +193,6 @@ var/list/table_icon_cache = list()
visible_message(span_infoplain(span_bold("\The [user]") + " scratches at \the [src]!"))
return ..()
-/obj/structure/table/MouseDrop_T(obj/item/stack/material/what, mob/user)
- if(can_reinforce && isliving(user) && (!user.stat) && istype(what) && user.get_active_hand() == what && Adjacent(user))
- reinforce_table(what, user)
- else
- return ..()
-
/obj/structure/table/proc/reinforce_table(obj/item/stack/material/S, mob/user)
if(reinforced)
to_chat(user, span_warning("\The [src] is already reinforced!"))
diff --git a/icons/mob/screen_spells.dmi b/icons/mob/screen_spells.dmi
index 5ce66a1eaa0..60356517abc 100644
Binary files a/icons/mob/screen_spells.dmi and b/icons/mob/screen_spells.dmi differ
diff --git a/icons/mob/shadekin_abilities.dmi b/icons/mob/shadekin_abilities.dmi
new file mode 100644
index 00000000000..94675194bc2
Binary files /dev/null and b/icons/mob/shadekin_abilities.dmi differ
diff --git a/icons/obj/Shadekin_powers.dmi b/icons/obj/Shadekin_powers.dmi
new file mode 100644
index 00000000000..09fc2fe407a
Binary files /dev/null and b/icons/obj/Shadekin_powers.dmi differ
diff --git a/icons/obj/shadekin_portal.dmi b/icons/obj/shadekin_portal.dmi
new file mode 100644
index 00000000000..ca7f1eb07c6
Binary files /dev/null and b/icons/obj/shadekin_portal.dmi differ
diff --git a/maps/submaps/shelters/dark_portal.dmm b/maps/submaps/shelters/dark_portal.dmm
new file mode 100644
index 00000000000..3757fe29150
--- /dev/null
+++ b/maps/submaps/shelters/dark_portal.dmm
@@ -0,0 +1,47 @@
+//MAP CONVERTED BY dmm2tgm.py THIS HEADER COMMENT PREVENTS RECONVERSION, DO NOT REMOVE
+"a" = (
+/turf/template_noop,
+/area/template_noop)
+"B" = (
+/turf/simulated/floor/weird_things/dark,
+/area/template_noop)
+"O" = (
+/obj/structure/dark_portal/minion,
+/turf/template_noop,
+/area/template_noop)
+
+(1,1,1) = {"
+a
+B
+B
+a
+a
+"}
+(2,1,1) = {"
+B
+a
+B
+B
+B
+"}
+(3,1,1) = {"
+B
+B
+O
+B
+a
+"}
+(4,1,1) = {"
+B
+B
+B
+B
+a
+"}
+(5,1,1) = {"
+B
+a
+B
+B
+a
+"}
diff --git a/sound/mob/spooky/breath1.ogg b/sound/mob/spooky/breath1.ogg
new file mode 100644
index 00000000000..463c2998969
Binary files /dev/null and b/sound/mob/spooky/breath1.ogg differ
diff --git a/sound/mob/spooky/breath2.ogg b/sound/mob/spooky/breath2.ogg
new file mode 100644
index 00000000000..a3a3c2c6631
Binary files /dev/null and b/sound/mob/spooky/breath2.ogg differ
diff --git a/sound/mob/spooky/corrosion1.ogg b/sound/mob/spooky/corrosion1.ogg
new file mode 100644
index 00000000000..912e535639f
Binary files /dev/null and b/sound/mob/spooky/corrosion1.ogg differ
diff --git a/sound/mob/spooky/corrosion2.ogg b/sound/mob/spooky/corrosion2.ogg
new file mode 100644
index 00000000000..dfd24cc9d8c
Binary files /dev/null and b/sound/mob/spooky/corrosion2.ogg differ
diff --git a/sound/mob/spooky/corrosion3.ogg b/sound/mob/spooky/corrosion3.ogg
new file mode 100644
index 00000000000..cef4bf5c2d0
Binary files /dev/null and b/sound/mob/spooky/corrosion3.ogg differ
diff --git a/sound/mob/spooky/decay1.ogg b/sound/mob/spooky/decay1.ogg
new file mode 100644
index 00000000000..e65a838e482
Binary files /dev/null and b/sound/mob/spooky/decay1.ogg differ
diff --git a/sound/mob/spooky/decay2.ogg b/sound/mob/spooky/decay2.ogg
new file mode 100644
index 00000000000..b1e0a10e2f8
Binary files /dev/null and b/sound/mob/spooky/decay2.ogg differ
diff --git a/sound/mob/spooky/decay3.ogg b/sound/mob/spooky/decay3.ogg
new file mode 100644
index 00000000000..cfe8b529255
Binary files /dev/null and b/sound/mob/spooky/decay3.ogg differ
diff --git a/sound/mob/spooky/emerge.ogg b/sound/mob/spooky/emerge.ogg
new file mode 100644
index 00000000000..bf665de9088
Binary files /dev/null and b/sound/mob/spooky/emerge.ogg differ
diff --git a/sound/mob/spooky/laugh.ogg b/sound/mob/spooky/laugh.ogg
new file mode 100644
index 00000000000..13b6bc8e5d4
Binary files /dev/null and b/sound/mob/spooky/laugh.ogg differ
diff --git a/sound/mob/spooky/no.ogg b/sound/mob/spooky/no.ogg
new file mode 100644
index 00000000000..1b835e46bce
Binary files /dev/null and b/sound/mob/spooky/no.ogg differ
diff --git a/vorestation.dme b/vorestation.dme
index 634c5e73949..e3bb11a2382 100644
--- a/vorestation.dme
+++ b/vorestation.dme
@@ -578,8 +578,16 @@
#include "code\datums\components\reagent_hose\datum.dm"
#include "code\datums\components\reagent_hose\inflation.dm"
#include "code\datums\components\reagent_hose\item.dm"
-#include "code\datums\components\species\shadekin.dm"
#include "code\datums\components\species\xenochimera.dm"
+#include "code\datums\components\species\shadekin\shadekin.dm"
+#include "code\datums\components\species\shadekin\helpers\comp_helpers.dm"
+#include "code\datums\components\species\shadekin\powers\create_shade.dm"
+#include "code\datums\components\species\shadekin\powers\dark_maw.dm"
+#include "code\datums\components\species\shadekin\powers\dark_respite.dm"
+#include "code\datums\components\species\shadekin\powers\phase_shift.dm"
+#include "code\datums\components\species\shadekin\powers\regenerate_other.dm"
+#include "code\datums\components\species\shadekin\powers\dark_tunnel\dark_tunnel_structures.dm"
+#include "code\datums\components\species\shadekin\powers\dark_tunnel\dark_tunneling.dm"
#include "code\datums\components\traits\burninlight.dm"
#include "code\datums\components\traits\drippy.dm"
#include "code\datums\components\traits\gargoyle.dm"