diff --git a/code/__defines/shadekin.dm b/code/__defines/shadekin.dm
index 1dd3db67e20..62833a785a0 100644
--- a/code/__defines/shadekin.dm
+++ b/code/__defines/shadekin.dm
@@ -1,9 +1,3 @@
-#define NOT_WHILE_SHIFTED 1
-#define ONLY_WHILE_SHIFTED 2
-#define SHIFTED_OR_NOT 3
-
-#define AB_SHADE_REGEN 0x1
-
//Porting over the type system of the mobs
#define BLUE_EYES 1
#define RED_EYES 2
diff --git a/code/datums/components/species/shadekin/helpers/comp_helpers.dm b/code/datums/components/species/shadekin/helpers/comp_helpers.dm
index 42475beff60..59ab4b8d808 100644
--- a/code/datums/components/species/shadekin/helpers/comp_helpers.dm
+++ b/code/datums/components/species/shadekin/helpers/comp_helpers.dm
@@ -50,28 +50,35 @@
//Blue has constant, steady (slow) regen and ignores darkness.
if(BLUE_EYES)
set_light_and_darkness(0.75,0.75)
+ nutrition_conversion_scaling = 0.5
if(RED_EYES)
set_light_and_darkness(-0.5,0.5)
+ nutrition_conversion_scaling = 2
if(PURPLE_EYES)
set_light_and_darkness(-0.5,1)
+ nutrition_conversion_scaling = 1
if(YELLOW_EYES)
set_light_and_darkness(-2,3)
+ nutrition_conversion_scaling = 0.5
if(GREEN_EYES)
set_light_and_darkness(0.125,2)
+ nutrition_conversion_scaling = 0.5
if(ORANGE_EYES)
set_light_and_darkness(-0.25,0.75)
+ nutrition_conversion_scaling = 1.5
///Sets our eye color.
-/datum/component/shadekin/proc/set_shadekin_eyecolor(var/mob/living/carbon/human/H)
+/datum/component/shadekin/proc/set_shadekin_eyecolor()
if(!ishuman(owner))
return eye_color //revert to default if we're not a human
- else
- H = owner
+
+ var/mob/living/carbon/human/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]
+ var/list/hsv_color = rgb2num(eyecolor_rgb, COLORSPACE_HSV)
+ var/eyecolor_hue = hsv_color[1]
+ var/eyecolor_sat = hsv_color[2]
+ var/eyecolor_val = hsv_color[3]
//First, clamp the saturation/value to prevent black/grey/white eyes
if(eyecolor_sat < 10)
@@ -81,9 +88,10 @@
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]
+ var/list/rgb_color = rgb2num(eyecolor_rgb)
+ H.r_eyes = rgb_color[1]
+ H.g_eyes = rgb_color[2]
+ H.b_eyes = rgb_color[3]
//Now determine what color we fall into.
switch(eyecolor_hue)
@@ -159,3 +167,47 @@
if(!isnum(amount))
return //No
shadekin_set_energy(dark_energy + amount)
+
+/datum/component/shadekin/proc/handle_nutrition_conversion(dark_gains)
+ if(!nutrition_energy_conversion)
+ return
+ if(shadekin_get_energy() == 100 && dark_gains > 0)
+ owner.nutrition += dark_gains * 5 * nutrition_conversion_scaling
+ else if(shadekin_get_energy() < 50 && owner.nutrition > 500)
+ owner.nutrition -= nutrition_conversion_scaling * 50
+ dark_gains += nutrition_conversion_scaling
+
+/datum/component/shadekin/proc/attack_dephase(var/turf/T = null, atom/dephaser)
+ // no assigned dephase-target, just use our own
+ if(!T)
+ T = get_turf(owner)
+
+ if(!in_phase || doing_phase)
+ return FALSE
+
+ // make sure it's possible to be dephased (and we're in phase)
+ if(!T || !T.CanPass(owner,T))
+ return FALSE
+
+
+ log_admin("[key_name_admin(owner)] was stunned out of phase at [T.x],[T.y],[T.z] by [dephaser.name], last touched by [dephaser.forensic_data?.get_lastprint()].")
+ message_admins("[key_name_admin(owner)] 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
+ owner.phase_in(T, src)
+ shadekin_adjust_energy(-20) // loss of energy for the interception
+ // apply a little extra stun for good measure
+ owner.Weaken(3)
+
+/mob/living/carbon/human/is_incorporeal()
+ var/datum/component/shadekin/SK = get_shadekin_component()
+ if(SK && SK.in_phase) //Shadekin
+ return TRUE
+ return ..()
+
+///Proc that takes in special considerations, such as 'no abilities in VR' and the such
+///Returns TRUE if we try to do something forbidden
+/datum/component/shadekin/proc/special_considerations(allow_vr)
+ if(!allow_vr && istype(get_area(owner), /area/vr))
+ to_chat(owner, span_danger("The VR systems cannot comprehend this power! This is useless to you!"))
+ return TRUE
+ return FALSE
diff --git a/code/datums/components/species/shadekin/powers/create_shade.dm b/code/datums/components/species/shadekin/powers/create_shade.dm
index a9c1facd114..506d0f274cb 100644
--- a/code/datums/components/species/shadekin/powers/create_shade.dm
+++ b/code/datums/components/species/shadekin/powers/create_shade.dm
@@ -18,6 +18,8 @@
if(!istype(SK))
to_chat(src, span_warning("Only a shadekin can use that!"))
return FALSE
+ else if(SK.special_considerations(TRUE))
+ return FALSE
else if(stat)
to_chat(src, span_warning("Can't use that ability in your state!"))
return FALSE
diff --git a/code/datums/components/species/shadekin/powers/dark_maw.dm b/code/datums/components/species/shadekin/powers/dark_maw.dm
index 0f66687affe..edf5cdda4a2 100644
--- a/code/datums/components/species/shadekin/powers/dark_maw.dm
+++ b/code/datums/components/species/shadekin/powers/dark_maw.dm
@@ -14,6 +14,8 @@
var/datum/component/shadekin/SK = get_shadekin_component()
if(!SK)
return FALSE
+ if(SK.special_considerations())
+ return FALSE
if(stat)
to_chat(src, span_warning("Can't use that ability in your state!"))
return FALSE
diff --git a/code/datums/components/species/shadekin/powers/dark_respite.dm b/code/datums/components/species/shadekin/powers/dark_respite.dm
index 4bb333d7e69..d71b42dab6c 100644
--- a/code/datums/components/species/shadekin/powers/dark_respite.dm
+++ b/code/datums/components/species/shadekin/powers/dark_respite.dm
@@ -14,6 +14,8 @@
var/datum/component/shadekin/SK = get_shadekin_component()
if(!SK)
return FALSE
+ if(SK.special_considerations())
+ return FALSE
if(stat)
to_chat(src, span_warning("Can't use that ability in your state!"))
return FALSE
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
index 01cca9732c8..261bb61782a 100644
--- a/code/datums/components/species/shadekin/powers/dark_tunnel/dark_tunneling.dm
+++ b/code/datums/components/species/shadekin/powers/dark_tunnel/dark_tunneling.dm
@@ -20,6 +20,8 @@
var/datum/component/shadekin/SK = get_shadekin_component()
if(!SK)
return FALSE
+ if(SK.special_considerations())
+ return FALSE
if(stat)
to_chat(src, span_warning("Can't use that ability in your state!"))
return FALSE
@@ -63,6 +65,9 @@
src.visible_message(span_notice("[src] begins pulling dark energies around themselves."))
if(do_after(src, tunnel_time))
+ if(SK.created_dark_tunnel) //check again because the user may have queued this up multiple times.
+ to_chat(src, span_warning("You have already made a tunnel to the Dark!"))
+ return FALSE
playsound(src, 'sound/effects/phasein.ogg', 100, 1)
src.visible_message(span_notice("[src] finishes pulling dark energies around themselves, creating a portal."))
diff --git a/code/datums/components/species/shadekin/powers/phase_shift.dm b/code/datums/components/species/shadekin/powers/phase_shift.dm
index 4580277558b..a314c72d36a 100644
--- a/code/datums/components/species/shadekin/powers/phase_shift.dm
+++ b/code/datums/components/species/shadekin/powers/phase_shift.dm
@@ -27,6 +27,8 @@
var/datum/component/shadekin/SK = get_shadekin_component()
if(!SK)
return FALSE
+ if(SK.special_considerations())
+ return FALSE
if(stat)
to_chat(src, span_warning("Can't use that ability in your state!"))
return FALSE
@@ -50,10 +52,24 @@
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!
+ for(var/mob/living/thing in orange(7, src)) //Fun fact, doing two typed loops is faster than doing one untyped loop. Check it with Tracy!
+ if(istype(thing, /mob/living/carbon/human))
+ var/mob/living/carbon/human/watchers = thing
+ if(watchers in oviewers(7,src))
+ var/datum/component/shadekin/watcher_SK = watchers.get_shadekin_component()
+ if(!watcher_SK && !(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!
+ if(istype(thing, /mob/living/silicon/robot))
+ var/mob/living/silicon/robot/watchers = thing
+ var/datum/component/shadekin/watcher_SK = watchers.get_shadekin_component() //you never know, man.
+ if(watchers in oviewers(7,src))
+ if(!watcher_SK && !watchers.stat && !isbelly(watchers.loc))
+ watcher++ //The robot is watching us!
+ if(SK.camera_counts_as_watcher)
+ for(var/obj/machinery/camera/watchers in orange(7, src))
+ if(watchers.can_use())
+ if(src in watchers.can_see())
+ watcher++ //The camera is watching us!
ability_cost = CLAMP(ability_cost/(0.01+darkness*2),50, 80)//This allows for 1 watcher in full light
if(watcher>0)
@@ -85,7 +101,6 @@
else
phase_out(T, SK)
-
/mob/living/proc/phase_in(var/turf/T, var/datum/component/shadekin/SK)
if(SK.in_phase)
@@ -163,6 +178,8 @@
held_lights.update_brightness()
SK.doing_phase = FALSE
+ if(SK.flicker_time < 5 || SK.flicker_distance < 5 || SK.flicker_break_chance < 5)
+ Stun(SK.calculate_stun())
if(!SK.flicker_time)
return //Early return. No time, no flickering.
//Affect nearby lights
diff --git a/code/datums/components/species/shadekin/powers/regenerate_other.dm b/code/datums/components/species/shadekin/powers/regenerate_other.dm
index 375da05fa7e..96e895e20c2 100644
--- a/code/datums/components/species/shadekin/powers/regenerate_other.dm
+++ b/code/datums/components/species/shadekin/powers/regenerate_other.dm
@@ -17,6 +17,8 @@
var/datum/component/shadekin/SK = get_shadekin_component()
if(!SK)
return FALSE
+ if(SK.special_considerations(TRUE))
+ return FALSE
if(stat)
to_chat(src, span_warning("Can't use that ability in your state!"))
return FALSE
diff --git a/code/datums/components/species/shadekin/shadekin.dm b/code/datums/components/species/shadekin/shadekin.dm
index 17f2fcd0f2e..921705e14a3 100644
--- a/code/datums/components/species/shadekin/shadekin.dm
+++ b/code/datums/components/species/shadekin/shadekin.dm
@@ -38,6 +38,8 @@
var/normal_phase = TRUE
///If we drop items on phase.
var/drop_items_on_phase = FALSE
+ ///If cameras count as watchers for us
+ var/camera_counts_as_watcher = FALSE
//Dark Respite Vars (Unused on Virgo)
///If we are in dark respite or not
@@ -56,8 +58,8 @@
//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/power/shadekin/regenerate_other,
+ /datum/power/shadekin/create_shade)
///Datum holder. Largely ignore this.
var/list/shadekin_ability_datums = list()
@@ -72,13 +74,14 @@
/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)
+ /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
+ camera_counts_as_watcher = TRUE
/datum/component/shadekin/full/rakshasa
flicker_time = 0 //Rakshasa don't flicker lights when they phase in.
@@ -132,6 +135,10 @@
owner = null
. = ..()
+/datum/component/shadekin/proc/recalc_values()
+ set_shadekin_eyecolor() //Gets what eye color we are.
+ set_eye_energy() //Sets the energy values based on our eye color.
+
///Handles the component running.
/datum/component/shadekin/proc/handle_comp()
SIGNAL_HANDLER
@@ -172,11 +179,74 @@
else
dark_gains = energy_light
+ handle_nutrition_conversion(dark_gains)
+
shadekin_adjust_energy(dark_gains)
//Update huds
update_shadekin_hud()
+/datum/component/shadekin/proc/calculate_stun()
+ var/stun_time = 3
+ if(flicker_time > 0)
+ stun_time -= min(flicker_time / 5, 1)
+ if(flicker_distance > 0)
+ stun_time -= min(flicker_distance / 5, 1)
+ if(flicker_break_chance > 0)
+ stun_time -= min(flicker_break_chance / 5, 1)
+ return stun_time
+
+/datum/component/shadekin/tgui_interact(mob/user, datum/tgui/ui)
+ ui = SStgui.try_update_ui(user, src, ui)
+ if(!ui)
+ ui = new(user, src, "ShadekinConfig", "Shadekin Config")
+ ui.open()
+
+/datum/component/shadekin/tgui_data(mob/user)
+ var/data = list(
+ "stun_time" = calculate_stun(),
+ "flicker_time" = flicker_time,
+ "flicker_color" = flicker_color,
+ "flicker_break_chance" = flicker_break_chance,
+ "flicker_distance" = flicker_distance,
+ )
+
+ return data
+
+
+/datum/component/shadekin/tgui_act(action, list/params, datum/tgui/ui, datum/tgui_state/state)
+ if(..())
+ return TRUE
+
+ switch(action)
+ if("adjust_time")
+ var/new_time = text2num(params["val"])
+ if(!isnum(new_time))
+ return FALSE
+ flicker_time = new_time
+ ui.user.write_preference_directly(/datum/preference/numeric/living/flicker_time, new_time)
+ return TRUE
+ if("adjust_color")
+ var/set_new_color = tgui_color_picker(ui.user, "Select a color you wish the lights to flicker as (Default is #E0EFF0)", "Color Selector", flicker_color)
+ if(!set_new_color)
+ return FALSE
+ flicker_color = set_new_color
+ ui.user.write_preference_directly(/datum/preference/color/living/flicker_color, set_new_color)
+ return TRUE
+ if("adjust_break")
+ var/new_brack_chance = text2num(params["val"])
+ if(!isnum(new_brack_chance))
+ return FALSE
+ flicker_break_chance = new_brack_chance
+ ui.user.write_preference_directly(/datum/preference/numeric/living/flicker_break_chance, new_brack_chance)
+ return TRUE
+ if("adjust_distance")
+ var/new_distance = text2num(params["val"])
+ if(!isnum(new_distance))
+ return FALSE
+ flicker_distance = new_distance
+ ui.user.write_preference_directly(/datum/preference/numeric/living/flicker_distance, new_distance)
+ return TRUE
/mob/living/proc/nutrition_conversion_toggle()
set name = "Toggle Energy <-> Nutrition conversions"
@@ -204,21 +274,5 @@
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]%")
+ SK.tgui_interact(src)
diff --git a/code/game/area/areas.dm b/code/game/area/areas.dm
index 74cd4a6a01c..bf053ef3d26 100644
--- a/code/game/area/areas.dm
+++ b/code/game/area/areas.dm
@@ -141,21 +141,17 @@ GLOBAL_LIST_EMPTY(areas_by_type)
if(fire || party || atmosalm)
firedoors_close()
arfgs_activate()
- // VOREStation Edit - Make the lights colored!
if(fire)
for(var/obj/machinery/light/L in src)
L.set_alert_fire()
else if(atmosalm)
for(var/obj/machinery/light/L in src)
L.set_alert_atmos()
- // VOREStation Edit End
else
firedoors_open()
arfgs_deactivate()
- // VOREStation Edit - Put the lights back!
for(var/obj/machinery/light/L in src)
L.reset_alert()
- // VOREStation Edit End
// Close all firedoors in the area
/area/proc/firedoors_close()
@@ -396,7 +392,7 @@ var/list/mob/living/forced_ambiance_list = list()
play_ambience(L, initial = TRUE)
if(flag_check(AREA_NO_SPOILERS))
L.disable_spoiler_vision()
- check_phase_shift(M) //RS Port #658
+ check_phase_shift(M)
// Update the area's color grading
if(L.client && L.client.color != get_color_tint()) // Try to check if we should bother changing before doing blending
@@ -452,7 +448,7 @@ var/list/mob/living/forced_ambiance_list = list()
return // Being buckled to something solid keeps you in place.
if(istype(H.shoes, /obj/item/clothing/shoes/magboots) && (H.shoes.item_flags & NOSLIP))
return
- if(H.is_incorporeal()) // VOREstation edit - Phaseshifted beings should not be affected by gravity
+ if(H.is_incorporeal()) // Phaseshifted beings should not be affected by gravity
return
if(H.species.can_zero_g_move || H.species.can_space_freemove)
return
@@ -568,11 +564,11 @@ GLOBAL_DATUM(spoiler_obfuscation_image, /image)
return
if(!isliving(ourmob))
return
- if(check_rights_for(ourmob.client, R_HOLDER))
+ if(check_rights_for(ourmob.client, R_HOLDER)) //If we're an admin, we don't get affected by phase blockers.
return
var/datum/component/shadekin/SK = ourmob.get_shadekin_component()
if(SK && SK.in_phase)
- ourmob.phase_in(ourmob.loc, SK)
+ SK.attack_dephase(ourmob.loc, src)
/area/proc/isAlwaysIndoors()
return FALSE
diff --git a/code/game/machinery/doors/airlock.dm b/code/game/machinery/doors/airlock.dm
index 89e7a816864..2fb6f5f4d21 100644
--- a/code/game/machinery/doors/airlock.dm
+++ b/code/game/machinery/doors/airlock.dm
@@ -1013,6 +1013,8 @@ About the new airlock wires panel:
/obj/machinery/door/airlock/CtrlClick(mob/user) //Hold door open
user.setClickCooldown(DEFAULT_ATTACK_COOLDOWN)
+ if(user.is_incorporeal())
+ return
if(!Adjacent(user))
return
if(user.a_intent == I_HURT)
diff --git a/code/game/objects/effects/portals.dm b/code/game/objects/effects/portals.dm
index 66ecb957602..0ea439716d3 100644
--- a/code/game/objects/effects/portals.dm
+++ b/code/game/objects/effects/portals.dm
@@ -25,9 +25,11 @@ GLOBAL_LIST_BOILERPLATE(all_portals, /obj/effect/portal)
if(AM.is_incorporeal())
if(!event)
return
- if(iscarbon(AM))
- var/mob/living/carbon/human/H = AM
- H.attack_dephase(null, src)
+ if(isliving(AM))
+ var/mob/living/L = AM
+ var/datum/component/shadekin/SK = L.get_shadekin_component()
+ if(SK)
+ SK.attack_dephase(null, src)
if(ismob(AM) && !(isliving(AM)))
return //do not send ghosts, zshadows, ai eyes, etc
spawn(0)
diff --git a/code/game/objects/structures/girders.dm b/code/game/objects/structures/girders.dm
index 65c3f38d9d2..db5d7b9ee0c 100644
--- a/code/game/objects/structures/girders.dm
+++ b/code/game/objects/structures/girders.dm
@@ -231,6 +231,9 @@
/obj/structure/girder/proc/construct_wall(obj/item/stack/material/S, mob/user)
var/amount_to_use = reinf_material ? 1 : 2
+ var/time_to_reinforce = 4 SECONDS
+ if(isrobot(user)) //Robots get a speed boost.
+ time_to_reinforce = 1.5 SECONDS
if(S.get_amount() < amount_to_use)
to_chat(user, span_notice("There isn't enough material here to construct a wall."))
return FALSE
@@ -248,7 +251,7 @@
to_chat(user, span_notice("You begin adding the plating..."))
- if(!do_after(user,40) || !S.use(amount_to_use))
+ if(!do_after(user,time_to_reinforce) || !S.use(amount_to_use))
return TRUE //once we've gotten this far don't call parent attackby()
if(anchored)
diff --git a/code/modules/client/preferences/types/character/general/06_special.dm b/code/modules/client/preferences/types/character/general/06_special.dm
new file mode 100644
index 00000000000..65c7f30329e
--- /dev/null
+++ b/code/modules/client/preferences/types/character/general/06_special.dm
@@ -0,0 +1,61 @@
+/datum/preference/color/living/flicker_color
+ category = PREFERENCE_CATEGORY_MANUALLY_RENDERED
+ savefile_identifier = PREFERENCE_CHARACTER
+ savefile_key = "flicker_color"
+ can_randomize = FALSE
+
+/datum/preference/color/living/flicker_color/create_default_value()
+ return "#E0EFF0"
+
+/datum/preference/color/living/flicker_color/apply_to_living(mob/living/target, value)
+ var/datum/component/shadekin/our_SK = target.get_shadekin_component()
+ if(our_SK)
+ our_SK.flicker_color = value
+
+/datum/preference/numeric/living/flicker_time
+ category = PREFERENCE_CATEGORY_MANUALLY_RENDERED
+ savefile_identifier = PREFERENCE_CHARACTER
+ savefile_key = "flicker_time"
+ can_randomize = FALSE
+ minimum = 2
+ maximum = 20
+
+/datum/preference/numeric/living/flicker_time/create_default_value()
+ return 10
+
+/datum/preference/numeric/living/flicker_time/apply_to_living(mob/living/target, value)
+ var/datum/component/shadekin/our_SK = target.get_shadekin_component()
+ if(our_SK)
+ our_SK.flicker_time = value
+
+/datum/preference/numeric/living/flicker_break_chance
+ category = PREFERENCE_CATEGORY_MANUALLY_RENDERED
+ savefile_identifier = PREFERENCE_CHARACTER
+ savefile_key = "flicker_break_chance"
+ can_randomize = FALSE
+ minimum = 0
+ maximum = 25
+
+/datum/preference/numeric/living/flicker_break_chance/create_default_value()
+ return 0
+
+/datum/preference/numeric/living/flicker_break_chance/apply_to_living(mob/living/target, value)
+ var/datum/component/shadekin/our_SK = target.get_shadekin_component()
+ if(our_SK)
+ our_SK.flicker_break_chance = value
+
+/datum/preference/numeric/living/flicker_distance
+ category = PREFERENCE_CATEGORY_MANUALLY_RENDERED
+ savefile_identifier = PREFERENCE_CHARACTER
+ savefile_key = "flicker_distance"
+ can_randomize = FALSE
+ minimum = 4
+ maximum = 10
+
+/datum/preference/numeric/living/flicker_distance/create_default_value()
+ return 4
+
+/datum/preference/numeric/living/flicker_distance/apply_to_living(mob/living/target, value)
+ var/datum/component/shadekin/our_SK = target.get_shadekin_component()
+ if(our_SK)
+ our_SK.flicker_distance = value
diff --git a/code/modules/mining/ore_redemption_machine/equipment_vendor.dm b/code/modules/mining/ore_redemption_machine/equipment_vendor.dm
index 21d578e1af8..dafbe41d516 100644
--- a/code/modules/mining/ore_redemption_machine/equipment_vendor.dm
+++ b/code/modules/mining/ore_redemption_machine/equipment_vendor.dm
@@ -317,6 +317,9 @@
if(!selection)
to_chat(redeemer, span_notice("You decide not to redeem anything for now."))
return
+ if(QDELETED(voucher))
+ to_chat(redeemer, span_warning("The voucher has already been redeemed."))
+ return
switch(selection)
if("Kinetic Accelerator + KA Addon") //1250-2100 points worth
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 27f6c0d78fe..5f1a5d736ce 100644
--- a/code/modules/mob/living/carbon/human/species/shadekin/shadekin.dm
+++ b/code/modules/mob/living/carbon/human/species/shadekin/shadekin.dm
@@ -101,12 +101,115 @@
)
species_component = /datum/component/shadekin
+ component_requires_late_recalc = TRUE
/datum/species/shadekin/handle_death(var/mob/living/carbon/human/H)
- spawn(1)
- for(var/obj/item/W in H)
- H.drop_from_inventory(W)
- qdel(H)
+ var/special_handling = FALSE //varswitch for downstream
+ H.clear_dark_maws() //clear dark maws on death or similar
+ if(!special_handling)
+ spawn(1)
+ for(var/obj/item/W in H)
+ H.drop_from_inventory(W)
+ qdel(H)
+ else
+ var/datum/component/shadekin/SK = H.get_shadekin_component()
+ if(!SK)
+ return
+ if(SK.respite_activating)
+ return TRUE
+ var/area/current_area = get_area(H)
+ if((SK.in_dark_respite) || H.has_modifier_of_type(/datum/modifier/dark_respite) || current_area.flag_check(AREA_LIMIT_DARK_RESPITE))
+ return
+ if(!LAZYLEN(GLOB.latejoin_thedark))
+ log_and_message_admins("[H] died outside of the dark but there were no valid floors to warp to")
+ return
+
+ H.visible_message("\The [H.name] phases to somewhere far away!")
+ var/obj/effect/temp_visual/shadekin/phase_out/phaseanimout = new /obj/effect/temp_visual/shadekin/phase_out(H.loc)
+ phaseanimout.dir = H.dir
+ SK.respite_activating = TRUE
+
+ H.drop_l_hand()
+ H.drop_r_hand()
+
+ SK.shadekin_set_energy(0)
+ SK.in_dark_respite = TRUE
+ H.invisibility = INVISIBILITY_SHADEKIN
+
+ H.adjustFireLoss(-(H.getFireLoss() * 0.75))
+ H.adjustBruteLoss(-(H.getBruteLoss() * 0.75))
+ H.adjustToxLoss(-(H.getToxLoss() * 0.75))
+ H.adjustCloneLoss(-(H.getCloneLoss() * 0.75))
+ H.germ_level = 0 //Take away the germs, or we'll die AGAIN
+ H.vessel.add_reagent(REAGENT_ID_BLOOD,blood_volume-H.vessel.total_volume)
+ for(var/obj/item/organ/external/bp in H.organs)
+ bp.bandage()
+ bp.disinfect()
+ for(var/obj/item/organ/internal/I in H.internal_organs) //other wise their organs stay mush
+ I.damage = 0
+ I.status = 0
+ if(I.organ_tag == O_EYES)
+ H.sdisabilities &= ~BLIND
+ if(I.organ_tag == O_LUNGS)
+ H.SetLosebreath(0)
+ H.nutrition = 0
+ H.invisibility = INVISIBILITY_SHADEKIN
+ BITRESET(H.hud_updateflag, HEALTH_HUD)
+ BITRESET(H.hud_updateflag, STATUS_HUD)
+ BITRESET(H.hud_updateflag, LIFE_HUD)
+
+ if(istype(H.loc, /obj/belly))
+ //Yay digestion... presumably...
+ var/obj/belly/belly = H.loc
+ add_attack_logs(belly.owner, H, "Digested in [lowertext(belly.name)]")
+ to_chat(belly.owner, span_notice("\The [H.name] suddenly vanishes within your [belly.name]"))
+ H.forceMove(pick(GLOB.latejoin_thedark))
+ if(SK.in_phase)
+ H.phase_shift()
+ else
+ var/obj/effect/temp_visual/shadekin/phase_in/phaseanim = new /obj/effect/temp_visual/shadekin/phase_in(H.loc)
+ phaseanim.dir = H.dir
+ H.invisibility = initial(H.invisibility)
+ SK.respite_activating = FALSE
+ belly.owner.handle_belly_update()
+ H.clear_fullscreen("belly")
+ if(H.hud_used)
+ if(!H.hud_used.hud_shown)
+ H.toggle_hud_vis()
+ H.stop_sound_channel(CHANNEL_PREYLOOP)
+ H.add_modifier(/datum/modifier/dark_respite, 10 MINUTES)
+ H.muffled = FALSE
+ H.forced_psay = FALSE
+
+
+ addtimer(CALLBACK(H, TYPE_PROC_REF(/mob/living, can_leave_dark)), 5 MINUTES, TIMER_DELETE_ME)
+ else
+ H.add_modifier(/datum/modifier/dark_respite, 25 MINUTES)
+
+ addtimer(CALLBACK(H, TYPE_PROC_REF(/mob/living, enter_the_dark)), 1 SECOND, TIMER_DELETE_ME)
+
+ addtimer(CALLBACK(H, TYPE_PROC_REF(/mob/living, can_leave_dark)), 15 MINUTES, TIMER_DELETE_ME)
+
+ return TRUE
+
+
+/mob/living/proc/enter_the_dark()
+ var/datum/component/shadekin/SK = get_shadekin_component()
+ if(!SK)
+ return
+ SK.respite_activating = FALSE
+ SK.in_dark_respite = TRUE
+
+ forceMove(pick(GLOB.latejoin_thedark))
+ invisibility = initial(invisibility)
+ SK.respite_activating = FALSE
+
+/mob/living/proc/can_leave_dark()
+ var/datum/component/shadekin/SK = get_shadekin_component()
+ if(!SK)
+ return
+ SK.in_dark_respite = FALSE
+ to_chat(src, span_notice("You feel like you can leave the Dark again"))
/datum/species/shadekin/get_bodytype()
return SPECIES_SHADEKIN
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
deleted file mode 100644
index 527e8b9903b..00000000000
--- a/code/modules/mob/living/carbon/human/species/shadekin/shadekin_abilities.dm
+++ /dev/null
@@ -1,29 +0,0 @@
-/datum/power/shadekin
-
-/mob/living/carbon/human/is_incorporeal()
- var/datum/component/shadekin/SK = get_shadekin_component()
- if(SK && SK.in_phase) //Shadekin
- return TRUE
- return ..()
-
-// 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/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(SK.doing_phase || !T.CanPass(src,T) || loc != T || !(SK.in_phase) )
- return FALSE
-
-
- log_admin("[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()].")
- 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)
- 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/species.dm b/code/modules/mob/living/carbon/human/species/species.dm
index 7f45c9919f3..740e8919366 100644
--- a/code/modules/mob/living/carbon/human/species/species.dm
+++ b/code/modules/mob/living/carbon/human/species/species.dm
@@ -359,6 +359,7 @@
var/food_preference_bonus = 0
var/datum/component/species_component = null // The component that this species uses. Example: Xenochimera use /datum/component/xenochimera
+ var/component_requires_late_recalc = FALSE // If TRUE, the component will do special recalculation stuff at the end of update_icons_body()
// For Lleill and Hanner
var/lleill_energy = 200
diff --git a/code/modules/mob/living/carbon/human/update_icons.dm b/code/modules/mob/living/carbon/human/update_icons.dm
index 3e348d7145f..e9802d12875 100644
--- a/code/modules/mob/living/carbon/human/update_icons.dm
+++ b/code/modules/mob/living/carbon/human/update_icons.dm
@@ -370,6 +370,10 @@ GLOBAL_LIST_EMPTY(damage_icon_parts) //see UpdateDamageIcon()
update_wing_showing()
update_vore_belly_sprite()
update_vore_tail_sprite()
+ if(species && species.component_requires_late_recalc)
+ var/datum/component/shadekin/SK = GetComponent(/datum/component/shadekin)
+ if(SK)
+ SK.recalc_values()
/mob/living/carbon/human/proc/update_skin()
if(QDESTROYING(src))
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 9ca16ceb48e..e9282ed2497 100644
--- a/code/modules/mob/living/silicon/robot/robot_simple_items.dm
+++ b/code/modules/mob/living/silicon/robot/robot_simple_items.dm
@@ -531,14 +531,22 @@
. += wrapped.examine(user)
/obj/item/gripper/CtrlClick(mob/user)
- if(wrapped)
+ if(wrapped && !is_in_use())
wrapped.attack_self(user)
return
/obj/item/gripper/AltClick(mob/user)
- drop_item()
+ if(!is_in_use())
+ drop_item()
return
+//This is used to check if the gripper is currently being used.
+//If it is, we don't allow any other actions to be performed.
+/obj/item/gripper/proc/is_in_use()
+ if(wrapped)
+ if(istype(wrapped.loc, /obj/item/storage/internal/gripper))
+ return FALSE //We are in a gripper storage or another gripper, so we are not in use.
+ return TRUE
//This is the code that updates our pockets and decides if they should have icons or not.
//This should be called every time we use the gripper and our wrapped item is used up.
@@ -565,6 +573,9 @@
photo_images["[pocket_to_check.name]" + "[pocket_content.name]"] = pocket_image
/obj/item/gripper/attack_self(mob/user as mob)
+ if(is_in_use())
+ to_chat(user, span_danger("You are currently using the gripper on something!"))
+ return
generate_icons()
var/list/options = list()
@@ -588,6 +599,9 @@
return ..()
/obj/item/gripper/attackby(var/obj/item/O, var/mob/user)
+ if(is_in_use())
+ to_chat(user, span_danger("You are currently using the gripper on something!"))
+ return FALSE
if(wrapped)
wrapped.loc = src.loc //Place it in to the robot.
var/resolved = wrapped.attackby(O, user)
@@ -621,6 +635,9 @@
if(!wrapped)
to_chat(src, span_warning("You have nothing to drop!"))
return
+ if(is_in_use())
+ to_chat(src, span_danger("You are currently using the gripper on something!"))
+ return
if((wrapped == current_pocket && !istype(wrapped.loc, /obj/item/storage/internal/gripper))) //We have wrapped selected as our current_pocket AND wrapped is not in a gripper storage
wrapped = null
current_pocket = pick(pockets)
@@ -643,6 +660,9 @@
wrapped = null
/obj/item/gripper/attack(mob/living/carbon/M as mob, mob/living/carbon/user as mob)
+ if(is_in_use())
+ to_chat(user, span_danger("You are currently using the gripper on something!"))
+ return FALSE
if(wrapped) //The force of the wrapped obj gets set to zero during the attack() and afterattack().
if(QDELETED(wrapped) || (wrapped.loc != src.loc && !istype(wrapped.loc,/obj/item/storage/internal/gripper))) //If our wrapper was deleted OR it's no longer in our internal gripper storage
wrapped = null //we become null
@@ -659,6 +679,9 @@
/obj/item/gripper/afterattack(var/atom/target, var/mob/living/user, proximity, params)
if(!proximity)
return // This will prevent them using guns at range but adminbuse can add them directly to modules, so eh.
+ if(is_in_use())
+ to_chat(user, span_danger("You are currently using the gripper on something!"))
+ return
var/current_pocket_full = FALSE
//There's some weirdness with items being lost inside the arm. Trying to fix all cases. ~Z
if(wrapped == current_pocket)
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
deleted file mode 100644
index b39871d4682..00000000000
--- a/code/modules/mob/living/simple_mob/subtypes/vore/shadekin/ability_objects.dm
+++ /dev/null
@@ -1,168 +0,0 @@
-/obj/effect/shadekin_ability
- name = ""
- desc = ""
- icon = 'icons/mob/screen_spells.dmi'
- var/ability_name = "FIX ME"
- var/cost = 50
- var/mob/living/simple_mob/shadekin/my_kin
- var/shift_mode = NOT_WHILE_SHIFTED
- var/ab_sound
-
-/obj/effect/shadekin_ability/Initialize(mapload)
- . = ..()
- if(issimplekin(loc))
- my_kin = loc
- loc = null
-
-/obj/effect/shadekin_ability/Destroy()
- my_kin = null
- return ..()
-
-/obj/effect/shadekin_ability/proc/atom_button_text()
- var/shift_denial
- if(shift_mode == NOT_WHILE_SHIFTED && my_kin.comp.in_phase)
- shift_denial = "Physical Only"
- else if(shift_mode == ONLY_WHILE_SHIFTED && !(my_kin.comp.in_phase))
- shift_denial = "Shifted Only"
-
- if(shift_denial)
- name = shift_denial
- else
- name = my_kin.comp.dark_energy >= cost ? "Activate" : "No Energy"
- return src
-
-/obj/effect/shadekin_ability/Click(var/location, var/control, var/params)
- if(my_kin.stat) return
-
- var/list/clickprops = params2list(params)
- var/opts = clickprops["shift"]
-
- if(opts)
- to_chat(my_kin,span_notice(span_bold("[name]") + " (Cost: [cost]%) - [desc]"))
- else
- do_ability(my_kin)
-
-/obj/effect/shadekin_ability/proc/do_ability()
- 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.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.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)
- to_chat(my_kin,span_warning("Not enough energy for that ability!"))
- return FALSE
-
- my_kin.comp.dark_energy -= cost
- if(ab_sound)
- playsound(src,ab_sound,75,1)
-
- return TRUE
-
-/////////////////////////////////////////////////////////////////
-/obj/effect/shadekin_ability/phase_shift
- ability_name = "Phase Shift"
- desc = "Shift yourself out of alignment with realspace to travel quickly between dark areas (or light areas, with a price)."
- icon_state = "tech_passwall"
- cost = 100
- shift_mode = SHIFTED_OR_NOT
- ab_sound = 'sound/effects/stealthoff.ogg'
-/obj/effect/shadekin_ability/phase_shift/do_ability()
- if(!..())
- return
- my_kin.phase_shift()
- if(my_kin.comp.in_phase)
- cost = 0 //Shifting back is free (but harmful in light)
- else
- cost = initial(cost)
-/////////////////////////////////////////////////////////////////
-/obj/effect/shadekin_ability/heal_boop
- ability_name = "Regenerate Other"
- desc = "Spend energy to heal physical wounds in another creature."
- icon_state = "tech_biomedaura"
- cost = 50
- shift_mode = NOT_WHILE_SHIFTED
- ab_sound = 'sound/effects/EMPulse.ogg'
-/obj/effect/shadekin_ability/heal_boop/do_ability()
- if(!..())
- return
- if(!my_kin.mend_other())
- my_kin.comp.dark_energy += cost //Refund due to abort
-/*
-/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)
-*/
-/////////////////////////////////////////////////////////////////
-/obj/effect/shadekin_ability/create_shade
- ability_name = "Create Shade"
- desc = "Create a field of darkness that follows you."
- icon_state = "tech_dispelold"
- cost = 25
- shift_mode = NOT_WHILE_SHIFTED
- ab_sound = 'sound/effects/bamf.ogg'
-/obj/effect/shadekin_ability/create_shade/do_ability()
- if(!..())
- return
- my_kin.add_modifier(/datum/modifier/shadekin/create_shade,20 SECONDS)
-/*
-/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.comp.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
-*/
-/*
-/////////////////////////////////////////////////////////////////
-/obj/effect/shadekin_ability/energy_feast
- ability_name = "Devour Energy"
- desc = "Devour the energy from another creature (potentially fatal)."
- icon_state = "gen_eat"
- cost = 25
- shift_mode = NOT_WHILE_SHIFTED
-/obj/effect/shadekin_ability/energy_feast/do_ability()
- if(!..())
- return
-*/
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 17a88ceef18..28ed6bc691b 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
@@ -124,11 +124,6 @@
if(eye_desc)
desc += " This one has [eye_desc]!"
- var/list/ability_types = subtypesof(/obj/effect/shadekin_ability)
- shadekin_abilities = list()
- for(var/type in ability_types)
- shadekin_abilities += new type(src)
-
update_icon()
add_verb(src, /mob/proc/adjust_hive_range)
@@ -229,22 +224,6 @@
add_overlay(tailimage)
add_overlay(eye_icon_state)
-/mob/living/simple_mob/shadekin/update_misc_tabs()
- ..()
- var/list/L = list()
- for(var/obj/effect/shadekin_ability/A as anything in shadekin_abilities)
- var/client/C = client
- var/img
- if(C && istype(C)) //sanity checks
- if(A.ability_name in C.misc_cache)
- img = C.misc_cache[A.ability_name]
- else
- img = icon2html(A,C,sourceonly=TRUE)
- C.misc_cache[A.ability_name] = img
-
- L[++L.len] = list("[A.ability_name]", A.ability_name, img, A.atom_button_text(), REF(A))
- misc_tabs["Shadekin"] = L
-
//They phase back to the dark when killed
/mob/living/simple_mob/shadekin/death(gibbed, deathmessage = "phases to somewhere far away!")
var/special_handling = FALSE //varswitch for downstream
@@ -315,7 +294,7 @@
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()
+/mob/living/simple_mob/shadekin/enter_the_dark()
comp.respite_activating = FALSE
comp.in_dark_respite = TRUE
@@ -325,7 +304,7 @@
invisibility = initial(invisibility)
comp.respite_activating = FALSE
-/mob/living/simple_mob/shadekin/proc/can_leave_dark()
+/mob/living/simple_mob/shadekin/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"))
@@ -352,16 +331,22 @@
//Blue has constant, steady (slow) regen and ignores darkness.
if(BLUE_EYES)
comp.set_light_and_darkness(0.75,0.75)
+ comp.nutrition_conversion_scaling = 0.5
if(RED_EYES)
comp.set_light_and_darkness(-0.5,0.5)
+ comp.nutrition_conversion_scaling = 2
if(PURPLE_EYES)
comp.set_light_and_darkness(-0.5,1)
+ comp.nutrition_conversion_scaling = 1
if(YELLOW_EYES)
comp.set_light_and_darkness(-2,3)
+ comp.nutrition_conversion_scaling = 0.5
if(GREEN_EYES)
comp.set_light_and_darkness(0.125,2)
+ comp.nutrition_conversion_scaling = 0.5
if(ORANGE_EYES)
comp.set_light_and_darkness(-0.25,0.75)
+ comp.nutrition_conversion_scaling = 1.5
/mob/living/simple_mob/shadekin/is_incorporeal()
if(comp.in_phase)
diff --git a/tgui/packages/tgui/interfaces/ShadekinConfig.tsx b/tgui/packages/tgui/interfaces/ShadekinConfig.tsx
new file mode 100644
index 00000000000..a50931a6cfa
--- /dev/null
+++ b/tgui/packages/tgui/interfaces/ShadekinConfig.tsx
@@ -0,0 +1,132 @@
+import { useBackend } from 'tgui/backend';
+import { Window } from 'tgui/layouts';
+import {
+ Box,
+ Button,
+ ColorBox,
+ LabeledList,
+ NoticeBox,
+ NumberInput,
+ Section,
+ Stack,
+ Tooltip,
+} from 'tgui-core/components';
+
+type Data = {
+ stun_time: number;
+ flicker_time: number;
+ flicker_color: string | null;
+ flicker_break_chance: number;
+ flicker_distance: number;
+};
+
+export const ShadekinConfig = (props) => {
+ const { act, data } = useBackend();
+
+ const {
+ stun_time,
+ flicker_time,
+ flicker_color,
+ flicker_break_chance,
+ flicker_distance,
+ } = data;
+
+ const isSubtle =
+ flicker_time < 5 || flicker_break_chance < 5 || flicker_distance < 5;
+
+ return (
+
+
+
+ {isSubtle && (
+
+ Subtle Phasing, causes {stun_time} s stun.
+
+ )}
+
+
+
+
+
+
+ act('adjust_time', { val: value })}
+ unit="x"
+ />
+
+
+
+ ?
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ act('adjust_break', { val: value })
+ }
+ unit="%"
+ />
+
+
+
+ ?
+
+
+
+
+
+
+
+
+ act('adjust_distance', { val: value })
+ }
+ unit="tiles"
+ />
+
+
+
+ ?
+
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/vorestation.dme b/vorestation.dme
index 31ea7c18805..db3d8a2e75d 100644
--- a/vorestation.dme
+++ b/vorestation.dme
@@ -2227,6 +2227,7 @@
#include "code\modules\client\preferences\types\character\antagonism\01_basic.dm"
#include "code\modules\client\preferences\types\character\general\01_basic.dm"
#include "code\modules\client\preferences\types\character\general\03_body.dm"
+#include "code\modules\client\preferences\types\character\general\06_special.dm"
#include "code\modules\client\preferences\types\game\admin.dm"
#include "code\modules\client\preferences\types\game\adult.dm"
#include "code\modules\client\preferences\types\game\auto_fit_viewport.dm"
@@ -3211,7 +3212,6 @@
#include "code\modules\mob\living\carbon\human\species\outsider\skeleton.dm"
#include "code\modules\mob\living\carbon\human\species\outsider\vox.dm"
#include "code\modules\mob\living\carbon\human\species\shadekin\shadekin.dm"
-#include "code\modules\mob\living\carbon\human\species\shadekin\shadekin_abilities.dm"
#include "code\modules\mob\living\carbon\human\species\shadekin\shadekin_hud.dm"
#include "code\modules\mob\living\carbon\human\species\station\alraune.dm"
#include "code\modules\mob\living\carbon\human\species\station\custom.dm"
@@ -3591,7 +3591,6 @@
#include "code\modules\mob\living\simple_mob\subtypes\vore\mobs_monsters\clowns\honkelemental.dm"
#include "code\modules\mob\living\simple_mob\subtypes\vore\mobs_monsters\clowns\regularclowns.dm"
#include "code\modules\mob\living\simple_mob\subtypes\vore\morph\morph.dm"
-#include "code\modules\mob\living\simple_mob\subtypes\vore\shadekin\ability_objects.dm"
#include "code\modules\mob\living\simple_mob\subtypes\vore\shadekin\ability_procs.dm"
#include "code\modules\mob\living\simple_mob\subtypes\vore\shadekin\shadekin.dm"
#include "code\modules\mob\living\simple_mob\subtypes\vore\shadekin\types.dm"