diff --git a/code/__defines/misc.dm b/code/__defines/misc.dm
index 55fbbecd14d..9fcc10c7a52 100644
--- a/code/__defines/misc.dm
+++ b/code/__defines/misc.dm
@@ -378,3 +378,9 @@
// Default UI style applied to client prefs.
#define DEFAULT_UI_STYLE /decl/ui_style/midnight
+
+// Indicates a status effect will never expire.
+#define STATUS_EFFECT_INDEFINITE (-1)
+
+// Converts a real time in deciseconds into an approximate number of SSmobs (Life()) ticks.
+#define DS_TO_LIFE_TICKS(X) (ceil(X / SSmobs.wait))
diff --git a/code/__defines/mob_status.dm b/code/__defines/mob_status.dm
index 42bc8854a7a..238ac560411 100644
--- a/code/__defines/mob_status.dm
+++ b/code/__defines/mob_status.dm
@@ -1,5 +1,5 @@
#define PENDING_STATUS(MOB, COND) (LAZYACCESS(MOB.pending_status_counters, COND) || LAZYACCESS(MOB.status_counters, COND))
#define GET_STATUS(MOB, COND) (LAZYACCESS(MOB.status_counters, COND))
#define HAS_STATUS(MOB, COND) (GET_STATUS(MOB, COND) > 0)
-#define ADJ_STATUS(MOB, COND, AMT) (MOB.set_status(COND, PENDING_STATUS(MOB, COND) + AMT))
-#define SET_STATUS_MAX(MOB, COND, AMT) (MOB.set_status(COND, max(PENDING_STATUS(MOB, COND), AMT)))
\ No newline at end of file
+#define ADJ_STATUS(MOB, COND, AMT) (MOB.set_status_condition(COND, PENDING_STATUS(MOB, COND) + AMT))
+#define SET_STATUS_MAX(MOB, COND, AMT) (MOB.set_status_condition(COND, max(PENDING_STATUS(MOB, COND), AMT)))
\ No newline at end of file
diff --git a/code/__defines/mobs.dm b/code/__defines/mobs.dm
index 022c6011a7a..820ce611c47 100644
--- a/code/__defines/mobs.dm
+++ b/code/__defines/mobs.dm
@@ -389,7 +389,8 @@ var/global/list/dexterity_levels = list(
#define HO_HANDCUFF_LAYER 25
#define HO_INHAND_LAYER 26
#define HO_FIRE_LAYER 27 //If you're on fire
-#define TOTAL_OVER_LAYERS 27
+#define HO_EFFECT_LAYER 28
+#define TOTAL_OVER_LAYERS 28
//////////////////////////////////
// Underlay defines; vestigal implementation currently.
diff --git a/code/_onclick/hud/hud.dm b/code/_onclick/hud/hud.dm
index 48829da57dd..203f646702f 100644
--- a/code/_onclick/hud/hud.dm
+++ b/code/_onclick/hud/hud.dm
@@ -44,6 +44,9 @@
var/obj/screen/action_button/hide_toggle/hide_actions_toggle
var/action_buttons_hidden = FALSE
+ var/obj/screen/status_effect_master/status_effects
+
+
/datum/hud/New(mob/owner)
mymob = owner
instantiate()
diff --git a/code/datums/cinematic.dm b/code/datums/cinematic.dm
index 86ddc55b91f..bf03370505e 100644
--- a/code/datums/cinematic.dm
+++ b/code/datums/cinematic.dm
@@ -27,7 +27,7 @@ var/global/datum/cinematic/cinematic = new
if(M.client)
M.client.screen += cinematic_screen //show every client the cinematic
viewers[M.client] = GET_STATUS(M, STAT_STUN)
- M.set_status(STAT_STUN, 8000)
+ M.set_status_condition(STAT_STUN, 8000)
override.nuke_act(cinematic_screen, station_missed) //cinematic happens here, as does mob death.
//If it's actually the end of the round, wait for it to end.
@@ -36,6 +36,6 @@ var/global/datum/cinematic/cinematic = new
for(var/client/C in viewers)
if(C.mob)
- C.mob.set_status(STAT_STUN, viewers[C])
+ C.mob.set_status_condition(STAT_STUN, viewers[C])
C.screen -= cinematic_screen
QDEL_NULL(cinematic_screen)
\ No newline at end of file
diff --git a/code/datums/wires/camera.dm b/code/datums/wires/camera.dm
index 09046ea83f1..94260cca9de 100644
--- a/code/datums/wires/camera.dm
+++ b/code/datums/wires/camera.dm
@@ -42,7 +42,7 @@ var/global/const/CAMERA_WIRE_ALARM = 8
if(CAMERA_WIRE_POWER)
C.cut_power = !mended
- C.set_status(mended, usr)
+ C.set_status_condition(mended, usr)
if(CAMERA_WIRE_LIGHT)
C.light_disabled = !mended
diff --git a/code/game/gamemodes/endgame/ftl_jump/ftl_jump.dm b/code/game/gamemodes/endgame/ftl_jump/ftl_jump.dm
index aed8aa4e95e..7e94d52d67e 100644
--- a/code/game/gamemodes/endgame/ftl_jump/ftl_jump.dm
+++ b/code/game/gamemodes/endgame/ftl_jump/ftl_jump.dm
@@ -53,14 +53,14 @@
if(M.client)
to_chat(M,"You feel oddly light, and somewhat disoriented as everything around you shimmers and warps ever so slightly.")
M.overlay_fullscreen("wormhole", /obj/screen/fullscreen/wormhole_overlay)
- M.set_status(STAT_CONFUSE, 20)
+ M.set_status_condition(STAT_CONFUSE, 20)
bluegoasts += new/obj/effect/bluegoast/(get_turf(M),M)
/datum/universal_state/jump/proc/clear_duplicated(var/mob/living/M)
if(M.client)
to_chat(M,"You feel rooted in material world again.")
M.clear_fullscreen("wormhole")
- M.set_status(STAT_CONFUSE, 0)
+ M.set_status_condition(STAT_CONFUSE, 0)
for(var/mob/goast in global.ghost_mob_list)
goast.mouse_opacity = initial(goast.mouse_opacity)
goast.set_invisibility(initial(goast.invisibility))
diff --git a/code/game/machinery/_machines_base/stock_parts/_stock_parts.dm b/code/game/machinery/_machines_base/stock_parts/_stock_parts.dm
index 7ce4792bc9f..7e44ab9a6d5 100644
--- a/code/game/machinery/_machines_base/stock_parts/_stock_parts.dm
+++ b/code/game/machinery/_machines_base/stock_parts/_stock_parts.dm
@@ -15,7 +15,7 @@
return FALSE // Can potentially add uninstall code here, but not currently supported.
return ..()
-/obj/item/stock_parts/proc/set_status(var/obj/machinery/machine, var/flag)
+/obj/item/stock_parts/proc/set_status_condition(var/obj/machinery/machine, var/flag)
var/old_stat = status
status |= flag
if(old_stat != status)
@@ -34,7 +34,7 @@
machine.component_stat_change(src, old_stat, flag)
/obj/item/stock_parts/proc/on_install(var/obj/machinery/machine)
- set_status(machine, PART_STAT_INSTALLED)
+ set_status_condition(machine, PART_STAT_INSTALLED)
/obj/item/stock_parts/proc/on_uninstall(var/obj/machinery/machine, var/temporary = FALSE)
unset_status(machine, PART_STAT_INSTALLED)
@@ -48,7 +48,7 @@
if(istype(machine))
LAZYDISTINCTADD(machine.processing_parts, src)
START_PROCESSING_MACHINE(machine, MACHINERY_PROCESS_COMPONENTS)
- set_status(machine, PART_STAT_PROCESSING)
+ set_status_condition(machine, PART_STAT_PROCESSING)
/obj/item/stock_parts/proc/stop_processing(var/obj/machinery/machine)
if(istype(machine))
diff --git a/code/game/machinery/_machines_base/stock_parts/power/battery.dm b/code/game/machinery/_machines_base/stock_parts/power/battery.dm
index c78b358aea6..f4ccdd2c701 100644
--- a/code/game/machinery/_machines_base/stock_parts/power/battery.dm
+++ b/code/game/machinery/_machines_base/stock_parts/power/battery.dm
@@ -49,7 +49,7 @@
if(istype(machine))
machine.power_change()
machine.queue_icon_update()
- set_status(machine, PART_STAT_CONNECTED)
+ set_status_condition(machine, PART_STAT_CONNECTED)
update_icon()
return cell
@@ -112,7 +112,7 @@
/obj/item/stock_parts/power/battery/can_provide_power(var/obj/machinery/machine)
if(is_functional() && cell && cell.check_charge(CELLRATE * machine.get_power_usage()))
machine.update_power_channel(LOCAL)
- set_status(machine, PART_STAT_ACTIVE)
+ set_status_condition(machine, PART_STAT_ACTIVE)
return TRUE
return FALSE
diff --git a/code/game/machinery/_machines_base/stock_parts/power/terminal.dm b/code/game/machinery/_machines_base/stock_parts/power/terminal.dm
index 732d90839c9..c86c170eb97 100644
--- a/code/game/machinery/_machines_base/stock_parts/power/terminal.dm
+++ b/code/game/machinery/_machines_base/stock_parts/power/terminal.dm
@@ -48,7 +48,7 @@
//Is willing to provide power if the wired contribution is nonnegligible and there is enough total local power to run the machine.
/obj/item/stock_parts/power/terminal/can_provide_power(var/obj/machinery/machine)
if(is_functional() && terminal && terminal.surplus() && machine.can_use_power_oneoff(machine.get_power_usage(), LOCAL) <= 0)
- set_status(machine, PART_STAT_ACTIVE)
+ set_status_condition(machine, PART_STAT_ACTIVE)
machine.update_power_channel(LOCAL)
return TRUE
return FALSE
@@ -76,7 +76,7 @@
terminal.queue_icon_update()
set_extension(src, /datum/extension/event_registration/shuttle_stationary, GET_DECL(/decl/observ/moved), machine, PROC_REF(machine_moved), get_area(src))
- set_status(machine, PART_STAT_CONNECTED)
+ set_status_condition(machine, PART_STAT_CONNECTED)
start_processing(machine)
/obj/item/stock_parts/power/terminal/proc/machine_moved(var/obj/machinery/machine, var/turf/old_loc, var/turf/new_loc)
diff --git a/code/game/machinery/camera/camera.dm b/code/game/machinery/camera/camera.dm
index 3262bf5a423..54234e2b26a 100644
--- a/code/game/machinery/camera/camera.dm
+++ b/code/game/machinery/camera/camera.dm
@@ -86,7 +86,7 @@
set_extension(src, /datum/extension/network_device/camera, null, null, null, TRUE, preset_channels, c_tag, cameranet_enabled, requires_connection)
/obj/machinery/camera/Destroy()
- set_status(0) //kick anyone viewing out
+ set_status_condition(0) //kick anyone viewing out
return ..()
/obj/machinery/camera/Process()
@@ -227,7 +227,7 @@
//sparks
spark_at(loc, amount=5)
-/obj/machinery/camera/proc/set_status(var/newstatus, var/mob/user)
+/obj/machinery/camera/proc/set_status_condition(var/newstatus, var/mob/user)
if (status != newstatus && (!cut_power || status == TRUE))
status = newstatus
// The only way for AI to reactivate cameras are malf abilities, this gives them different messages.
@@ -337,7 +337,7 @@
)
/obj/machinery/camera/proc/toggle_status()
- set_status(!status)
+ set_status_condition(!status)
/decl/public_access/public_method/toggle_camera
name = "toggle camera"
diff --git a/code/game/machinery/doors/airlock_interactions.dm b/code/game/machinery/doors/airlock_interactions.dm
index 2ad96280a52..2406735f21a 100644
--- a/code/game/machinery/doors/airlock_interactions.dm
+++ b/code/game/machinery/doors/airlock_interactions.dm
@@ -59,8 +59,8 @@
for(var/i in 1 to round(crush_damage/AIRLOCK_CRUSH_INCREMENT, 1))
apply_damage(AIRLOCK_CRUSH_INCREMENT, BRUTE)
- set_status(STAT_STUN, round(crush_damage / 8, 1))
- set_status(STAT_WEAK, round(crush_damage / 8, 1))
+ set_status_condition(STAT_STUN, round(crush_damage / 8, 1))
+ set_status_condition(STAT_WEAK, round(crush_damage / 8, 1))
var/turf/T = loc
if(!istype(T))
diff --git a/code/game/machinery/jukebox.dm b/code/game/machinery/jukebox.dm
index 1fb6b7666b7..1f9777dfac9 100644
--- a/code/game/machinery/jukebox.dm
+++ b/code/game/machinery/jukebox.dm
@@ -126,7 +126,7 @@
var/mob/living/human/H = M
if(H.get_sound_volume_multiplier() < 0.2)
continue
- M.set_status(STAT_ASLEEP, 0)
+ M.set_status_condition(STAT_ASLEEP, 0)
ADJ_STATUS(M, STAT_STUTTER, 20)
SET_STATUS_MAX(M, STAT_DEAF, 30)
SET_STATUS_MAX(M, STAT_WEAK, 3)
@@ -134,7 +134,7 @@
SET_STATUS_MAX(M, STAT_STUN, 10)
SET_STATUS_MAX(M, STAT_PARA, 4)
else
- M.set_status(STAT_JITTER, 400)
+ M.set_status_condition(STAT_JITTER, 400)
spawn(15)
explode()
diff --git a/code/game/objects/items/weapons/implants/implants/adrenaline.dm b/code/game/objects/items/weapons/implants/implants/adrenaline.dm
index 7c3f0036fa7..55adb2cb11d 100644
--- a/code/game/objects/items/weapons/implants/implants/adrenaline.dm
+++ b/code/game/objects/items/weapons/implants/implants/adrenaline.dm
@@ -26,9 +26,9 @@
uses--
to_chat(imp_in, "You feel a sudden surge of energy!")
- imp_in.set_status(STAT_STUN, 0)
- imp_in.set_status(STAT_WEAK, 0)
- imp_in.set_status(STAT_PARA, 0)
+ imp_in.set_status_condition(STAT_STUN, 0)
+ imp_in.set_status_condition(STAT_WEAK, 0)
+ imp_in.set_status_condition(STAT_PARA, 0)
/obj/item/implant/adrenalin/implanted(mob/source)
source.StoreMemory("A implant can be activated by using the pale emote, say *pale to attempt to activate.", /decl/memory_options/system)
diff --git a/code/game/objects/items/weapons/stunbaton.dm b/code/game/objects/items/weapons/stunbaton.dm
index f1d555da4d0..546fa11da25 100644
--- a/code/game/objects/items/weapons/stunbaton.dm
+++ b/code/game/objects/items/weapons/stunbaton.dm
@@ -35,7 +35,7 @@
/obj/item/baton/infinite/Initialize(var/ml, var/material_key, var/loaded_cell_type)
. = ..(ml, material_key, loaded_cell_type = /obj/item/cell/device/infinite)
- set_status(1, null)
+ set_status_condition(1, null)
/obj/item/baton/proc/update_status()
var/obj/item/cell/cell = get_cell()
@@ -66,10 +66,10 @@
set_light(0)
/obj/item/baton/attack_self(mob/user)
- set_status(!status, user)
+ set_status_condition(!status, user)
add_fingerprint(user)
-/obj/item/baton/proc/set_status(var/newstatus, mob/user)
+/obj/item/baton/proc/set_status_condition(var/newstatus, mob/user)
var/obj/item/cell/cell = get_cell()
if(cell?.charge >= hitcost)
if(status != newstatus)
diff --git a/code/modules/abstract/follower.dm b/code/modules/abstract/follower.dm
new file mode 100644
index 00000000000..226477471d4
--- /dev/null
+++ b/code/modules/abstract/follower.dm
@@ -0,0 +1,16 @@
+// Simple obj for following another obj around (for light effects or such that need a physical reference)
+/obj/abstract/follower
+ anchored = TRUE
+ simulated = FALSE
+ invisibility = INVISIBILITY_ABSTRACT
+
+/obj/abstract/follower/Initialize()
+ . = ..()
+ name = ""
+ verbs.Cut()
+
+/obj/abstract/follower/proc/follow_owner(atom/movable/owner)
+ if(istype(owner) && !QDELETED(owner) && owner.loc)
+ forceMove(owner.loc)
+ else
+ forceMove(null)
diff --git a/code/modules/admin/admin.dm b/code/modules/admin/admin.dm
index c211f274d60..9594f9dd3dd 100644
--- a/code/modules/admin/admin.dm
+++ b/code/modules/admin/admin.dm
@@ -1415,10 +1415,10 @@ var/global/BSACooldown = 0
SPAN_OCCULT("OOC: \The [M] has been paralyzed by a staff member. Please hold all interactions with them until staff have finished with them."),
SPAN_OCCULT("OOC: You have been paralyzed by a staff member. Please refer to your currently open admin help ticket or, if you don't have one, admin help for assistance.")
)
- M.set_status(STAT_PARA, 8000)
+ M.set_status_condition(STAT_PARA, 8000)
M.admin_paralyzed = TRUE
else
- M.set_status(STAT_PARA, 0)
+ M.set_status_condition(STAT_PARA, 0)
M.admin_paralyzed = FALSE
M.visible_message(SPAN_OCCULT("OOC: \The [M] has been released from paralysis by staff. You may resume interactions with them."))
to_chat(M, SPAN_OCCULT("OOC: You have been released from paralysis by staff and can return to your game."))
diff --git a/code/modules/admin/topic.dm b/code/modules/admin/topic.dm
index c9e2798773b..45976fa2381 100644
--- a/code/modules/admin/topic.dm
+++ b/code/modules/admin/topic.dm
@@ -992,7 +992,7 @@
M.take_damage(min(99, M.current_health - 1))
SET_STATUS_MAX(M, STAT_STUN, 20)
SET_STATUS_MAX(M, STAT_WEAK, 20)
- M.set_status(STAT_STUTTER, 20)
+ M.set_status_condition(STAT_STUTTER, 20)
else if(href_list["CentcommReply"])
var/mob/living/L = locate(href_list["CentcommReply"])
diff --git a/code/modules/admin/view_variables/helpers.dm b/code/modules/admin/view_variables/helpers.dm
index 7b7155b8391..adbd2a25ab5 100644
--- a/code/modules/admin/view_variables/helpers.dm
+++ b/code/modules/admin/view_variables/helpers.dm
@@ -64,6 +64,8 @@
return ..() + {"
+
+
diff --git a/code/modules/admin/view_variables/topic.dm b/code/modules/admin/view_variables/topic.dm
index 971e7a394bc..5298721b88b 100644
--- a/code/modules/admin/view_variables/topic.dm
+++ b/code/modules/admin/view_variables/topic.dm
@@ -658,7 +658,7 @@
return
if(amt < 0)
amt += GET_STATUS(victim, selected_condition.type)
- victim.set_status(selected_condition.type, amt)
+ victim.set_status_condition(selected_condition.type, amt)
log_and_message_admins("set [selected_condition.name] to [amt] on \the [victim].")
else if(href_list["setmaterial"])
@@ -705,6 +705,35 @@
else
to_chat(usr, SPAN_WARNING("Failed to remove [ability] from [target]!"))
+ else if (href_list["give_status_effect"])
+ var/mob/living/target = locate(href_list["give_status_effect"])
+ if(!istype(target) || QDELETED(target))
+ to_chat(usr, SPAN_WARNING("Only /mob/living mobs can have status effects."))
+ else
+ // Evil pyramid due to apparently not being able to return early in this Topic()
+ var/decl/status_effect/effect = input(usr, "Which effect do you wish to give?", "Add Status Effect") as null|anything in decls_repository.get_decls_of_type_unassociated(/decl/status_effect)
+ if(istype(effect) && !QDELETED(target))
+ var/duration = input(usr, "How long do you wish this effect to last, in life ticks (roughly 2 seconds each)? Enter -1 for a permanent effect.", "Add Status Effect") as num|null
+ if(!isnull(duration))
+ duration = clamp(duration, STATUS_EFFECT_INDEFINITE, 100)
+ if(duration != 0 && !QDELETED(target))
+ if(target.set_status_effect(effect, duration))
+ to_chat(usr, SPAN_NOTICE("Added [effect] to [target] for [duration] life ticks."))
+ else
+ to_chat(usr, SPAN_WARNING("Failed to add [effect] to [target]."))
+
+ else if (href_list["remove_status_effect"])
+ var/mob/living/target = locate(href_list["remove_status_effect"])
+ if(!istype(target) && !QDELETED(target))
+ to_chat(usr, SPAN_WARNING("Only /mob/living mobs can have status effects."))
+ else
+ var/decl/status_effect/effect = input(usr, "Which effect do you wish to remove?", "Remove Status Effect") as null|anything in target.status_effects
+ if(istype(effect))
+ if(target.clear_status_effect(effect))
+ to_chat(usr, SPAN_NOTICE("Removed [effect] from [target]."))
+ else
+ to_chat(usr, SPAN_WARNING("Failed to remove [effect] from [target]."))
+
if(href_list["datumrefresh"])
var/datum/datum_to_refresh = locate(href_list["datumrefresh"])
if(istype(datum_to_refresh, /datum) || istype(datum_to_refresh, /client))
diff --git a/code/modules/clothing/_clothing.dm b/code/modules/clothing/_clothing.dm
index b6cc37b0ada..cf23ae5cd17 100644
--- a/code/modules/clothing/_clothing.dm
+++ b/code/modules/clothing/_clothing.dm
@@ -71,7 +71,7 @@
/obj/item/clothing/Destroy()
if(is_accessory())
- on_removed()
+ on_accessory_removed()
return ..()
/obj/item/clothing/get_fallback_slot(slot)
diff --git a/code/modules/clothing/_clothing_accessories.dm b/code/modules/clothing/_clothing_accessories.dm
index 0469f605d01..d958fc571e7 100644
--- a/code/modules/clothing/_clothing_accessories.dm
+++ b/code/modules/clothing/_clothing_accessories.dm
@@ -91,7 +91,7 @@
/obj/item/clothing/proc/remove_accessory(mob/user, obj/item/clothing/accessory)
if(!accessory || !(accessory in accessories) || !accessory.accessory_removable || !accessory.canremove)
return
- accessory.on_removed(user)
+ accessory.on_accessory_removed(user)
update_icon()
/obj/item/clothing/proc/removetie_verb()
@@ -178,7 +178,7 @@
return TRUE
return FALSE
-/obj/item/clothing/proc/on_removed(var/mob/user)
+/obj/item/clothing/proc/on_accessory_removed(var/mob/user)
var/obj/item/clothing/holder = loc
if(istype(holder))
if(user)
diff --git a/code/modules/clothing/sensors/vitals_sensor.dm b/code/modules/clothing/sensors/vitals_sensor.dm
index 8ed94b74013..d029f8b33e1 100644
--- a/code/modules/clothing/sensors/vitals_sensor.dm
+++ b/code/modules/clothing/sensors/vitals_sensor.dm
@@ -44,7 +44,7 @@
. = ..()
update_removable()
-/obj/item/clothing/sensor/vitals/on_removed(mob/user)
+/obj/item/clothing/sensor/vitals/on_accessory_removed(mob/user)
. = ..()
update_removable()
diff --git a/code/modules/clothing/spacesuits/spacesuits.dm b/code/modules/clothing/spacesuits/spacesuits.dm
index 896bd3db241..f822bd442c6 100644
--- a/code/modules/clothing/spacesuits/spacesuits.dm
+++ b/code/modules/clothing/spacesuits/spacesuits.dm
@@ -60,10 +60,10 @@
if(ispath(camera))
camera = new camera(src)
- camera.set_status(0)
+ camera.set_status_condition(0)
if(camera)
- camera.set_status(!camera.status)
+ camera.set_status_condition(!camera.status)
if(camera.status)
camera.c_tag = user.get_id_name()
to_chat(user, "User scanned as [camera.c_tag]. Camera activated.")
diff --git a/code/modules/clothing/webbing/holster.dm b/code/modules/clothing/webbing/holster.dm
index 1d49eaabc2b..efb1d4881aa 100644
--- a/code/modules/clothing/webbing/holster.dm
+++ b/code/modules/clothing/webbing/holster.dm
@@ -36,7 +36,7 @@
if(istype(holder))
holder.verbs |= /atom/proc/holster_verb
-/obj/item/clothing/webbing/holster/on_removed(mob/user)
+/obj/item/clothing/webbing/holster/on_accessory_removed(mob/user)
var/obj/item/clothing/holder = loc
if(istype(holder))
var/remove_verb = TRUE
diff --git a/code/modules/mechs/mech_damage_immunity.dm b/code/modules/mechs/mech_damage_immunity.dm
index 067c613d56d..01a8ef4a0f1 100644
--- a/code/modules/mechs/mech_damage_immunity.dm
+++ b/code/modules/mechs/mech_damage_immunity.dm
@@ -5,7 +5,7 @@
STAT_PARA
)
-/mob/living/exosuit/set_status(condition, amount)
+/mob/living/exosuit/set_status_condition(condition, amount)
. = !(condition in ignore_status_conditions) && ..()
/mob/living/exosuit/getOxyLoss()
diff --git a/code/modules/mob/death.dm b/code/modules/mob/death.dm
index 4986952e130..a88a887cac4 100644
--- a/code/modules/mob/death.dm
+++ b/code/modules/mob/death.dm
@@ -92,7 +92,7 @@
reset_plane_and_layer()
update_posture()
if(!gibbed)
- clear_status_effects()
+ clear_status_conditions()
set_sight(sight|SEE_TURFS|SEE_MOBS|SEE_OBJS)
set_see_in_dark(8)
diff --git a/code/modules/mob/living/bot/bot.dm b/code/modules/mob/living/bot/bot.dm
index 9f3089994e6..339769eea84 100644
--- a/code/modules/mob/living/bot/bot.dm
+++ b/code/modules/mob/living/bot/bot.dm
@@ -56,9 +56,9 @@
/mob/living/bot/handle_regular_status_updates()
. = ..()
if(.)
- set_status(STAT_WEAK, 0)
- set_status(STAT_STUN, 0)
- set_status(STAT_PARA, 0)
+ set_status_condition(STAT_WEAK, 0)
+ set_status_condition(STAT_STUN, 0)
+ set_status_condition(STAT_PARA, 0)
/mob/living/bot/get_life_damage_types()
var/static/list/life_damage_types = list(
diff --git a/code/modules/mob/living/bot/secbot.dm b/code/modules/mob/living/bot/secbot.dm
index faca3a286c2..8f650dfa637 100644
--- a/code/modules/mob/living/bot/secbot.dm
+++ b/code/modules/mob/living/bot/secbot.dm
@@ -51,11 +51,11 @@
/mob/living/bot/secbot/turn_on()
..()
- stun_baton.set_status(on, null)
+ stun_baton.set_status_condition(on, null)
/mob/living/bot/secbot/turn_off()
..()
- stun_baton.set_status(on, null)
+ stun_baton.set_status_condition(on, null)
/mob/living/bot/secbot/on_update_icon()
..()
diff --git a/code/modules/mob/living/human/life.dm b/code/modules/mob/living/human/life.dm
index ebc47ef1202..8028fcd2ba2 100644
--- a/code/modules/mob/living/human/life.dm
+++ b/code/modules/mob/living/human/life.dm
@@ -125,8 +125,8 @@
vision = GET_INTERNAL_ORGAN(src, vision_organ_tag)
if(!vision_organ_tag) // Presumably if a species has no vision organs, they see via some other means.
- set_status(STAT_BLIND, 0)
- set_status(STAT_BLURRY, 0)
+ set_status_condition(STAT_BLIND, 0)
+ set_status_condition(STAT_BLURRY, 0)
else if(!vision || (vision && !vision.is_usable())) // Vision organs cut out or broken? Permablind.
SET_STATUS_MAX(src, STAT_BLIND, 2)
SET_STATUS_MAX(src, STAT_BLURRY, 1)
diff --git a/code/modules/mob/living/life.dm b/code/modules/mob/living/life.dm
index 05d2002770b..e662a4ef866 100644
--- a/code/modules/mob/living/life.dm
+++ b/code/modules/mob/living/life.dm
@@ -42,6 +42,7 @@
handle_grasp()
handle_stance()
handle_regular_hud_updates()
+ handle_status_conditions()
handle_status_effects()
return 1
@@ -445,9 +446,21 @@
//this handles hud updates. Calls update_vision() and handle_hud_icons()
/mob/living/proc/handle_regular_hud_updates()
+
SHOULD_CALL_PARENT(TRUE)
if(!should_do_hud_updates())
return FALSE
+
+ if(buckled || restrained())
+ set_status_effect(/decl/status_effect/restrained)
+ else
+ clear_status_effect(/decl/status_effect/restrained)
+
+ if(current_posture?.prone)
+ set_status_effect(/decl/status_effect/prone)
+ else
+ clear_status_effect(/decl/status_effect/prone)
+
handle_hud_icons()
handle_vision()
handle_low_light_vision()
diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm
index dcbc0d2917e..0fa8f528b8a 100644
--- a/code/modules/mob/living/living.dm
+++ b/code/modules/mob/living/living.dm
@@ -232,8 +232,8 @@ default behaviour is:
if(stat != DEAD && should_be_dead())
death()
if(!QDELETED(src)) // death() may delete or remove us
- set_status(STAT_BLIND, 1)
- set_status(STAT_SILENCE, 0)
+ set_status_condition(STAT_BLIND, 1)
+ set_status_condition(STAT_SILENCE, 0)
return TRUE
//This proc is used for mobs which are affected by pressure to calculate the amount of pressure that actually
@@ -329,17 +329,17 @@ default behaviour is:
set_damage(OXY, 0)
set_damage(CLONE, 0)
set_damage(BRAIN, 0)
- set_status(STAT_PARA, 0)
- set_status(STAT_STUN, 0)
- set_status(STAT_WEAK, 0)
+ set_status_condition(STAT_PARA, 0)
+ set_status_condition(STAT_STUN, 0)
+ set_status_condition(STAT_WEAK, 0)
// shut down ongoing problems
radiation = 0
bodytemperature = get_species()?.body_temperature || initial(bodytemperature)
reset_genetic_conditions()
- // fix all status conditions including blind/deaf
- clear_status_effects()
+ // clear all status effects and conditions including blind/deaf
+ clear_status_conditions()
heal_overall_damage(get_damage(BRUTE), get_damage(BURN))
@@ -901,7 +901,7 @@ default behaviour is:
if(!HAS_STATUS(src, STAT_PARA) && stat == CONSCIOUS)
visible_message(SPAN_DANGER("\The [src] starts having a seizure!"))
SET_STATUS_MAX(src, STAT_PARA, rand(8,16))
- set_status(STAT_JITTER, rand(150,200))
+ set_status_condition(STAT_JITTER, rand(150,200))
take_damage(rand(50, 60), PAIN)
/mob/living/proc/get_digestion_product()
diff --git a/code/modules/mob/living/living_appearance.dm b/code/modules/mob/living/living_appearance.dm
index ea2bb37147e..e3f4369ece7 100644
--- a/code/modules/mob/living/living_appearance.dm
+++ b/code/modules/mob/living/living_appearance.dm
@@ -77,3 +77,24 @@
/mob/living/get_current_mob_underlay(var/underlay_layer)
return mob_underlays[underlay_layer]
+
+/mob/living/refresh_visible_overlays()
+ if((. = ..()))
+ var/list/status_effect_overlays
+ for(var/decl/status_effect/effect in status_effects)
+ var/image/status_overlay = effect.get_mob_overlay(src)
+ if(status_overlay)
+ LAZYADD(status_effect_overlays, status_overlay)
+ set_current_mob_overlay(HO_EFFECT_LAYER, status_effect_overlays, FALSE)
+
+/decl/status_effect/proc/get_mob_overlay(mob/living/owner)
+ if(!mob_overlay_icon || !mob_overlay_state || !istype(owner))
+ return null
+ var/image/mob_overlay = overlay_image(mob_overlay_icon, mob_overlay_state, COLOR_WHITE, RESET_COLOR)
+ var/decl/bodytype/owner_bodytype = owner.get_bodytype()
+ if(owner_bodytype)
+ if(owner_bodytype.pixel_offset_x)
+ mob_overlay.pixel_x += -(owner_bodytype.pixel_offset_x)
+ if(owner_bodytype.pixel_offset_y)
+ mob_overlay.pixel_y += -(owner_bodytype.pixel_offset_y)
+ return mob_overlay
diff --git a/code/modules/mob/living/living_defense.dm b/code/modules/mob/living/living_defense.dm
index fc07b274edc..67c1a09ab5f 100644
--- a/code/modules/mob/living/living_defense.dm
+++ b/code/modules/mob/living/living_defense.dm
@@ -139,7 +139,7 @@
if(31 to INFINITY)
SET_STATUS_MAX(src, STAT_WEAK, 10) //This should work for now, more is really silly and makes you lay there forever
- set_status(STAT_JITTER, min(shock_damage*5, 200))
+ set_status_condition(STAT_JITTER, min(shock_damage*5, 200))
spark_at(loc, amount=5, cardinal_only = TRUE)
diff --git a/code/modules/mob/living/living_status.dm b/code/modules/mob/living/living_status.dm
index 645b5323448..32024893a1d 100644
--- a/code/modules/mob/living/living_status.dm
+++ b/code/modules/mob/living/living_status.dm
@@ -1,9 +1,18 @@
-/mob // Defined on /mob to avoid having to pass args to every single attack_foo() proc.
+// Defined on /mob to avoid having to pass args to every single attack_foo() proc.
+/mob
+ // A STATUS CONDITION is a counter on an general incapacitating effect like sleep or blindness.
+ // STATUS CONDITION TRACKERS:
var/list/status_counters
var/list/pending_status_counters
var/datum/status_marker_holder/status_markers
-/mob/living/set_status(var/condition, var/amount)
+/mob/living
+ // A STATUS EFFECT is a generalised effect on the mob, positive or negative, like buckling, healing or a curse.
+ // STATUS EFFECT TRACKERS:
+ var/list/status_effects
+
+// Status condition procs:
+/mob/living/set_status_condition(var/condition, var/amount)
if(QDELETED(src))
return FALSE
if(!ispath(condition, /decl/status_condition))
@@ -57,7 +66,7 @@
var/decl/status_condition/status = GET_DECL(condition)
status.handle_changed_amount(src, new_amount, last_amount)
-/mob/living/handle_status_effects()
+/mob/living/handle_status_conditions()
. = ..()
var/refresh_icon = FALSE
for(var/condition in status_counters)
@@ -69,12 +78,75 @@
if(refresh_icon)
update_icon()
-/mob/living/clear_status_effects()
+/mob/living/clear_status_conditions()
var/had_counters = !!LAZYLEN(status_counters)
for(var/stype in status_counters)
- set_status(stype, 0)
+ set_status_condition(stype, 0)
status_counters = null
pending_status_counters = null
if(had_counters)
rebuild_status_markers()
update_icon()
+
+// Status effect procs:
+/mob/living/proc/handle_status_effects()
+ SHOULD_CALL_PARENT(TRUE)
+ for(var/effect as anything in status_effects)
+ var/datum/status_effect/effect_data = status_effects[effect]
+ effect_data.on_effect_mob_life()
+ status_effect_indicators?.refresh_status_effects()
+
+/mob/living/proc/clear_status_effects()
+ for(var/effect as anything in status_effects)
+ var/datum/status_effect/effect_data = status_effects[effect]
+ effect_data.on_effect_expiry()
+ status_effects = null
+
+/mob/living/proc/clear_status_effect(var/decl/status_effect/effect, skip_update = FALSE)
+ if(ispath(effect))
+ effect = GET_DECL(effect)
+ if(!istype(effect))
+ return FALSE
+ var/datum/status_effect/existing_effect = LAZYACCESS(status_effects, effect)
+ if(!istype(existing_effect))
+ return FALSE
+ existing_effect.on_effect_removed()
+ if(!skip_update)
+ status_effect_indicators?.refresh_status_effects()
+ try_refresh_visible_overlays()
+ return TRUE
+
+/mob/living/proc/has_status_effect(decl/status_effect/effect)
+ if(ispath(effect))
+ effect = GET_DECL(effect)
+ if(!istype(effect))
+ return FALSE
+ return !!LAZYACCESS(status_effects, effect)
+
+/mob/living/proc/set_status_effect(decl/status_effect/effect, duration = STATUS_EFFECT_INDEFINITE, skip_update = FALSE)
+ if(ispath(effect))
+ effect = GET_DECL(effect)
+ if(!istype(effect))
+ return FALSE
+ var/datum/status_effect/existing_effect = LAZYACCESS(status_effects, effect)
+ if(istype(existing_effect))
+ // If the effect already exists, just update the timer.
+ existing_effect.lifetime = duration
+ else
+ // Otherwise, create a new datum.
+ existing_effect = new effect.status_effect_type(effect, src)
+ existing_effect.lifetime = duration
+ existing_effect.on_effect_added()
+ if(!skip_update)
+ status_effect_indicators?.refresh_status_effects()
+ try_refresh_visible_overlays()
+ return TRUE
+
+// DEBUG, REMOVE WHEN PROPERLY INTEGRATED
+/mob/living
+ var/obj/screen/status_effect_master/status_effect_indicators
+
+/mob/living/Login()
+ ..()
+ status_effect_indicators ||= new(null, src)
+ client.screen |= status_effect_indicators
diff --git a/code/modules/mob/living/silicon/login.dm b/code/modules/mob/living/silicon/login.dm
index 3ff4653c93d..9f5a88f6cd1 100644
--- a/code/modules/mob/living/silicon/login.dm
+++ b/code/modules/mob/living/silicon/login.dm
@@ -1,3 +1,3 @@
/mob/living/silicon/Login()
..()
- set_status(STAT_ASLEEP, 0)
+ set_status_condition(STAT_ASLEEP, 0)
diff --git a/code/modules/mob/living/silicon/pai/software.dm b/code/modules/mob/living/silicon/pai/software.dm
index b53b8a9cfe2..bb0c5ebccab 100644
--- a/code/modules/mob/living/silicon/pai/software.dm
+++ b/code/modules/mob/living/silicon/pai/software.dm
@@ -39,7 +39,7 @@ var/global/list/default_pai_software = list()
if(user != src || !istype(card))
if(ui)
- ui.set_status(STATUS_CLOSE, 0)
+ ui.set_status_condition(STATUS_CLOSE, 0)
return
if(ui_key != "main")
@@ -47,7 +47,7 @@ var/global/list/default_pai_software = list()
if(S && !S.toggle)
S.on_ui_interact(src, ui, force_open)
else
- if(ui) ui.set_status(STATUS_CLOSE, 0)
+ if(ui) ui.set_status_condition(STATUS_CLOSE, 0)
return
var/data[0]
diff --git a/code/modules/mob/living/silicon/robot/life.dm b/code/modules/mob/living/silicon/robot/life.dm
index 4e2bc0fe3cf..d594285f78a 100644
--- a/code/modules/mob/living/silicon/robot/life.dm
+++ b/code/modules/mob/living/silicon/robot/life.dm
@@ -48,7 +48,7 @@
SHOULD_CALL_PARENT(FALSE)
update_health()
- set_status(STAT_PARA, min(GET_STATUS(src, STAT_PARA), 30))
+ set_status_condition(STAT_PARA, min(GET_STATUS(src, STAT_PARA), 30))
if(HAS_STATUS(src, STAT_ASLEEP))
SET_STATUS_MAX(src, STAT_PARA, 3)
@@ -68,7 +68,7 @@
SET_STATUS_MAX(src, STAT_BLIND, 2)
if(has_genetic_condition(GENE_COND_DEAFENED))
- src.set_status(STAT_DEAF, 1)
+ src.set_status_condition(STAT_DEAF, 1)
//update the state of modules and components here
if (stat != CONSCIOUS)
diff --git a/code/modules/mob/mob.dm b/code/modules/mob/mob.dm
index ad15792b151..48663b8a4ec 100644
--- a/code/modules/mob/mob.dm
+++ b/code/modules/mob/mob.dm
@@ -1388,7 +1388,7 @@
/// THIS DOES NOT RELATE TO HELD ITEM SLOTS. It is very specifically a functional BP_L_HAND or BP_R_HAND organ, not necessarily a gripper.
/mob/proc/get_usable_hand_slot_organ()
var/obj/item/organ/external/paw = GET_EXTERNAL_ORGAN(src, BP_L_HAND)
- if(!istype(paw) && !paw.is_usable())
+ if(!istype(paw) || !paw.is_usable())
paw = GET_EXTERNAL_ORGAN(src, BP_R_HAND)
if(istype(paw) && paw.is_usable())
return paw
diff --git a/code/modules/mob/mob_status.dm b/code/modules/mob/mob_status.dm
index 0e2c45822b9..726edf26264 100644
--- a/code/modules/mob/mob_status.dm
+++ b/code/modules/mob/mob_status.dm
@@ -1,9 +1,9 @@
// Stubs; see living_status.dm
-/mob/proc/handle_status_effects()
+/mob/proc/handle_status_conditions()
SHOULD_CALL_PARENT(TRUE)
-/mob/proc/clear_status_effects()
+/mob/proc/clear_status_conditions()
return
-/mob/proc/set_status(var/condition, var/amount)
+/mob/proc/set_status_condition(var/condition, var/amount)
return
diff --git a/code/modules/mob/transform_procs.dm b/code/modules/mob/transform_procs.dm
index 6ec207ea1b8..21e905f85a6 100644
--- a/code/modules/mob/transform_procs.dm
+++ b/code/modules/mob/transform_procs.dm
@@ -5,7 +5,7 @@
drop_from_inventory(W)
try_refresh_visible_overlays()
ADD_TRANSFORMATION_MOVEMENT_HANDLER(src)
- set_status(STAT_STUN, 1)
+ set_status_condition(STAT_STUN, 1)
icon = null
set_invisibility(INVISIBILITY_ABSTRACT)
for(var/t in get_external_organs())
@@ -18,7 +18,7 @@
//animation = null
DEL_TRANSFORMATION_MOVEMENT_HANDLER(src)
- set_status(STAT_STUN, 0)
+ set_status_condition(STAT_STUN, 0)
update_posture()
set_invisibility(initial(invisibility))
diff --git a/code/modules/modular_computers/hardware/lan_port.dm b/code/modules/modular_computers/hardware/lan_port.dm
index 8ffecb3d26a..443ebe3a137 100644
--- a/code/modules/modular_computers/hardware/lan_port.dm
+++ b/code/modules/modular_computers/hardware/lan_port.dm
@@ -31,7 +31,7 @@
terminal = new(get_turf(parent))
set_extension(src, /datum/extension/event_registration/shuttle_stationary, GET_DECL(/decl/observ/moved), parent, PROC_REF(check_terminal_prox), get_area(src))
events_repository.register(/decl/observ/destroyed, terminal, src, PROC_REF(unset_terminal))
- set_status(parent, PART_STAT_CONNECTED)
+ set_status_condition(parent, PART_STAT_CONNECTED)
/obj/item/stock_parts/computer/lan_port/proc/unset_terminal()
remove_extension(src, /datum/extension/event_registration/shuttle_stationary)
diff --git a/code/modules/nano/nanoui.dm b/code/modules/nano/nanoui.dm
index e315b28c580..319be31eec5 100644
--- a/code/modules/nano/nanoui.dm
+++ b/code/modules/nano/nanoui.dm
@@ -134,7 +134,7 @@ nanoui is used to open and update nano browser uis
*
* @return nothing
*/
-/datum/nanoui/proc/set_status(state, push_update)
+/datum/nanoui/proc/set_status_condition(state, push_update)
if (state != status) // Only update if it is different
if (status == STATUS_DISABLED)
status = state
@@ -164,7 +164,7 @@ nanoui is used to open and update nano browser uis
if(new_status == STATUS_CLOSE)
close()
return 1
- set_status(new_status, push_update)
+ set_status_condition(new_status, push_update)
/**
* Set the ui to auto update (every master_controller tick)
diff --git a/code/modules/organs/internal/brain.dm b/code/modules/organs/internal/brain.dm
index c4980c3a303..966ecffc80b 100644
--- a/code/modules/organs/internal/brain.dm
+++ b/code/modules/organs/internal/brain.dm
@@ -210,7 +210,7 @@
owner.custom_pain("Your head feels numb and painful.",10)
if(is_bruised() && prob(1) && !HAS_STATUS(owner, STAT_BLURRY))
to_chat(owner, "It becomes hard to see for some reason.")
- owner.set_status(STAT_BLURRY, 10)
+ owner.set_status_condition(STAT_BLURRY, 10)
var/held = owner.get_active_held_item()
if(damage >= 0.5*max_damage && prob(1) && held)
to_chat(owner, "Your hand won't respond properly, and you drop what you are holding!")
diff --git a/code/modules/organs/internal/heart.dm b/code/modules/organs/internal/heart.dm
index 2cc637a076c..ae158663cdc 100644
--- a/code/modules/organs/internal/heart.dm
+++ b/code/modules/organs/internal/heart.dm
@@ -173,7 +173,7 @@
FONT_HUGE(SPAN_DANGER("Blood sprays out from your [spray_organ]!"))
)
SET_STATUS_MAX(owner, STAT_STUN, 1)
- owner.set_status(STAT_BLURRY, 2)
+ owner.set_status_condition(STAT_BLURRY, 2)
//AB occurs every heartbeat, this only throttles the visible effect
next_blood_squirt = world.time + 80
diff --git a/code/modules/reagents/chems/chems_drinks.dm b/code/modules/reagents/chems/chems_drinks.dm
index eca5276b785..ceb15af9a17 100644
--- a/code/modules/reagents/chems/chems_drinks.dm
+++ b/code/modules/reagents/chems/chems_drinks.dm
@@ -549,7 +549,7 @@
SET_STATUS_MAX(M, STAT_DIZZY, 20)
ADJ_STATUS(M, STAT_DIZZY, 2)
ADJ_STATUS(M, STAT_JITTER, 2)
- M.set_status(STAT_DROWSY, 0)
+ M.set_status_condition(STAT_DROWSY, 0)
/decl/material/liquid/drink/grenadine
name = "grenadine syrup"
diff --git a/code/modules/status_conditions/_status.dm b/code/modules/status_conditions/_status.dm
index 49750b10a03..0c474cf9a7f 100644
--- a/code/modules/status_conditions/_status.dm
+++ b/code/modules/status_conditions/_status.dm
@@ -1,11 +1,15 @@
var/global/list/status_marker_holders = list()
-// Check code/modules/mob/mob_status.dm code/modules/mob/living/living_status.dm
-// for the procs that check/set/process these status conditions.
+// Check code/modules/mob/mob_status.dm code/modules/mob/living/living_status.dm
+// for the procs that check/set/process these status conditions.
/decl/status_condition
var/name
var/check_flags = 0
var/list/victim_data
+
+ var/hud_icon
+ var/hud_state
+
var/status_marker_icon = 'icons/effects/status.dmi'
var/status_marker_state
var/status_marker_private = FALSE
diff --git a/code/modules/status_conditions/_status_markers.dm b/code/modules/status_conditions/_status_markers.dm
index 9731e1881e6..c6e0f8ec8e5 100644
--- a/code/modules/status_conditions/_status_markers.dm
+++ b/code/modules/status_conditions/_status_markers.dm
@@ -1,3 +1,12 @@
+var/global/list/_status_marker_decls
+/proc/get_status_marker_decls()
+ if(!global._status_marker_decls)
+ global._status_marker_decls = list()
+ for(var/decl/status_condition/cond as anything in decls_repository.get_decls_of_subtype_unassociated(/decl/status_condition))
+ if(cond.status_marker_icon && cond.status_marker_state)
+ global._status_marker_decls += cond
+ return global._status_marker_decls
+
/obj/status_marker
name = ""
mouse_opacity = MOUSE_OPACITY_UNCLICKABLE
@@ -58,18 +67,14 @@
mob_image_personal.layer = POINTER_LAYER
animate(mob_image_personal, pixel_z = 1, time = 3, easing = (SINE_EASING | EASE_OUT), loop = -1)
- animate( pixel_z = -1, time = 6, easing = SINE_EASING, loop = -1)
- animate( pixel_z = 0, time = 3, easing = (SINE_EASING | EASE_IN), loop = -1)
+ animate( pixel_z = -1, time = 6, easing = SINE_EASING, loop = -1)
+ animate( pixel_z = 0, time = 3, easing = (SINE_EASING | EASE_IN), loop = -1)
- var/list/all_status = decls_repository.get_decls_of_subtype(/decl/status_condition)
- for(var/status_type in all_status)
- var/decl/status_condition/status = all_status[status_type]
+ for(var/decl/status_condition/status in get_status_marker_decls())
if(status.status_marker_icon && status.status_marker_state)
-
var/obj/status_marker/marker = new(null, status)
mob_image.add_vis_contents(marker)
LAZYSET(markers, status, marker)
-
marker = new(null, status)
mob_image_personal.add_vis_contents(marker)
LAZYSET(markers_personal, status, marker)
diff --git a/code/modules/status_conditions/definitions/status_effect_object.dm b/code/modules/status_conditions/definitions/status_effect_object.dm
new file mode 100644
index 00000000000..665f9ae2e8b
--- /dev/null
+++ b/code/modules/status_conditions/definitions/status_effect_object.dm
@@ -0,0 +1,20 @@
+/decl/status_effect/object
+ abstract_type = /decl/status_effect/object
+ status_effect_type = /decl/status_effect/object
+
+/datum/status_effect/object
+ var/obj/object = /obj/abstract/follower
+ var/owner_moved_callback = TYPE_PROC_REF(/obj/abstract/follower, follow_owner)
+
+/datum/status_effect/object/on_effect_added(mob/living/owner, datum/status_effect/effect)
+ . = ..()
+ if(ispath(object))
+ object = new object(get_turf(owner))
+ if(owner_moved_callback)
+ events_repository.register(/decl/observ/moved, owner, object, owner_moved_callback)
+
+/datum/status_effect/object/on_effect_removed(mob/living/owner, datum/status_effect/effect)
+ if(istype(object) && !QDELETED(object))
+ qdel(object)
+ object = null
+ . = ..()
diff --git a/code/modules/status_conditions/definitions/status_effect_prone.dm b/code/modules/status_conditions/definitions/status_effect_prone.dm
new file mode 100644
index 00000000000..4f6b22e7289
--- /dev/null
+++ b/code/modules/status_conditions/definitions/status_effect_prone.dm
@@ -0,0 +1,9 @@
+/decl/status_effect/prone
+ name = "Prone"
+ desc = "You are lying prone and may need to stand up before taking action."
+ hud_icon_state = "prone"
+
+/decl/status_effect/prone/on_effect_click(mob/living/owner, datum/status_effect/effect, params)
+ if(owner.current_posture?.prone)
+ owner.lay_down()
+ return TRUE
diff --git a/code/modules/status_conditions/definitions/status_effect_restrained.dm b/code/modules/status_conditions/definitions/status_effect_restrained.dm
new file mode 100644
index 00000000000..8a3cc14bc16
--- /dev/null
+++ b/code/modules/status_conditions/definitions/status_effect_restrained.dm
@@ -0,0 +1,8 @@
+/decl/status_effect/restrained
+ name = "Restrained"
+ desc = "You are restrained and need to resist to get out of your bindings."
+ hud_icon_state = "restrained"
+
+/decl/status_effect/restrained/on_effect_click(mob/living/owner, datum/status_effect/effect, params)
+ owner.resist()
+ return TRUE
diff --git a/code/modules/status_conditions/status_effect.dm b/code/modules/status_conditions/status_effect.dm
new file mode 100644
index 00000000000..c6848181b6c
--- /dev/null
+++ b/code/modules/status_conditions/status_effect.dm
@@ -0,0 +1,106 @@
+/obj/screen/status_effect_master
+ screen_loc = "CENTER,TOP"
+ requires_ui_style = FALSE
+ // TODO: consider pooling these.
+ var/list/elements
+
+/obj/screen/status_effect_master/Destroy()
+ QDEL_NULL_LIST(elements)
+ var/mob/living/owner = owner_ref?.resolve()
+ if(istype(owner) && owner.status_effect_indicators == src)
+ owner.status_effect_indicators = null
+ return ..()
+
+/obj/screen/status_effect_master/proc/refresh_status_effects()
+
+ if(QDELETED(src))
+ return
+
+ var/mob/living/owner = owner_ref?.resolve()
+ if(!istype(owner))
+ return
+
+ var/list/seen_archetypes
+ var/list/elements_to_keep
+ var/list/elements_to_add
+ var/list/elements_to_remove
+
+ // Track deltas for keeping/removing existing elements.
+ for(var/obj/screen/status_effect/element in elements)
+ var/effect = LAZYACCESS(owner.status_effects, element.archetype)
+ if(effect)
+ LAZYADD(elements_to_keep, element)
+ else
+ LAZYADD(elements_to_remove, element)
+ LAZYDISTINCTADD(seen_archetypes, element.archetype)
+
+ // Create elements for new effects.
+ for(var/decl/status_effect/archetype in owner.status_effects)
+ if(archetype in seen_archetypes)
+ continue
+ var/obj/screen/status_effect/element = new(null, owner)
+ element.archetype = archetype
+ element.master = src
+ element.pixel_y = 32
+ element.icon = archetype.hud_icon
+ element.icon_state = archetype.hud_icon_state
+ element.alpha = 0
+ LAZYADD(elements_to_add, element)
+
+ // Fade out and delete expired markers.
+ if(LAZYLEN(elements_to_remove))
+ LAZYREMOVE(elements, elements_to_remove)
+ for(var/obj/screen/status_effect/element in elements_to_remove)
+ animate(element, alpha = 0, pixel_y = 32, time = 5)
+ QDEL_IN(element, 5)
+
+ // Add our new records.
+ if(LAZYLEN(elements_to_add))
+ LAZYADD(elements, elements_to_add)
+ add_vis_contents(elements_to_add)
+
+ // Adjust positions and fade in new elements.
+ if(length(elements))
+ var/offset_x = -(((length(elements)-1) * (world.icon_size + 2)) / 2)
+ for(var/obj/screen/element in elements)
+ if(element in elements_to_add)
+ pixel_x = offset_x
+ animate(element, alpha = 255, pixel_y = 0, time = 5)
+ else
+ animate(element, alpha = 255, pixel_x = offset_x, pixel_y = 0, time = 5)
+ offset_x += world.icon_size + 2
+
+/obj/screen/status_effect
+ alpha = 0
+ requires_ui_style = FALSE
+ screen_loc = null // not handled via screen loc, but via vis contents of the master object.
+ var/decl/status_effect/archetype
+ var/obj/screen/status_effect_master/master
+
+/obj/screen/status_effect/Destroy()
+ if(master)
+ LAZYREMOVE(master.elements, src)
+ master.remove_vis_contents(src)
+ master = null
+ return ..()
+
+/obj/screen/status_effect/handle_click(mob/user, params)
+ if((. = ..()))
+ var/mob/living/owner = owner_ref?.resolve()
+ if(istype(owner) && archetype)
+ var/datum/status_effect/effect = LAZYACCESS(owner.status_effects, archetype)
+ if(istype(effect))
+ effect.on_effect_click(params)
+
+/obj/screen/status_effect/MouseEntered(location, control, params)
+ if(archetype && (archetype.name || archetype.desc))
+ openToolTip(user = usr, tip_src = src, params = params, title = archetype.name, content = archetype.desc)
+ ..()
+
+/obj/screen/status_effect/MouseDown()
+ closeToolTip(usr)
+ ..()
+
+/obj/screen/status_effect/MouseExited()
+ closeToolTip(usr)
+ ..()
diff --git a/code/modules/status_conditions/status_effect_archetype.dm b/code/modules/status_conditions/status_effect_archetype.dm
new file mode 100644
index 00000000000..b2f6f27f865
--- /dev/null
+++ b/code/modules/status_conditions/status_effect_archetype.dm
@@ -0,0 +1,59 @@
+/// Instanced 'effects' that sit on top of a mob and can expire over time or linger until dispelled.
+/// Some are purely visual, others have associated effects.
+/decl/status_effect
+ abstract_type = /decl/status_effect
+ var/name
+ var/desc
+ var/hud_icon = 'icons/screen/status_effects.dmi'
+ var/hud_icon_state
+ var/mob_overlay_icon
+ var/mob_overlay_state
+ var/status_effect_type = /datum/status_effect
+ var/on_add_message_1p
+ var/on_add_message_3p
+ var/on_end_message_1p
+ var/on_end_message_3p
+
+/decl/status_effect/validate()
+ . = ..()
+ if(!hud_icon)
+ . += "no hud_icon set"
+ if(!istext(hud_icon_state))
+ . += "null or invalid hud_icon_state"
+ if(hud_icon && hud_icon_state && !check_state_in_icon(hud_icon_state, hud_icon))
+ . += "hud_icon '[hud_icon]' missing hud_icon_state '[hud_icon_state]'"
+
+ if(mob_overlay_icon)
+ if(istext(mob_overlay_state))
+ if(!check_state_in_icon(mob_overlay_state, mob_overlay_icon))
+ . += "mob_overlay_icon '[mob_overlay_icon]' missing mob_overlay_state '[mob_overlay_state]'"
+ else
+ . += "null or invalid mob_overlay_state"
+ else if(mob_overlay_state)
+ . += "mob_overlay_state set but mob_overlay_icon not set"
+
+/decl/status_effect/proc/replace_tokens(message, mob/user)
+ return capitalize(emote_replace_user_tokens(message, user))
+
+/decl/status_effect/proc/on_effect_added(mob/living/owner, datum/status_effect/effect)
+ if(on_add_message_3p)
+ owner.visible_message(replace_tokens(on_add_message_3p), replace_tokens(on_add_message_3p || on_add_message_1p))
+ else if(on_add_message_1p)
+ to_chat(owner, replace_tokens(on_add_message_1p))
+ return TRUE
+
+/decl/status_effect/proc/on_effect_removed(mob/living/owner, datum/status_effect/effect)
+ if(on_end_message_3p)
+ owner.visible_message(replace_tokens(on_end_message_3p), replace_tokens(on_end_message_3p || on_end_message_1p))
+ else if(on_end_message_1p)
+ to_chat(owner, replace_tokens(on_end_message_1p))
+ return TRUE
+
+/decl/status_effect/proc/on_effect_expiry(mob/living/owner, datum/status_effect/effect)
+ return TRUE
+
+/decl/status_effect/proc/on_effect_mob_life(mob/living/owner, datum/status_effect/effect)
+ return TRUE
+
+/decl/status_effect/proc/on_effect_click(mob/living/owner, datum/status_effect/effect, params)
+ return FALSE
diff --git a/code/modules/status_conditions/status_effect_datum.dm b/code/modules/status_conditions/status_effect_datum.dm
new file mode 100644
index 00000000000..fbb581bb3be
--- /dev/null
+++ b/code/modules/status_conditions/status_effect_datum.dm
@@ -0,0 +1,63 @@
+// Subtype for tracking timer etc. No actual behavior associated with these.
+/datum/status_effect
+ var/mob/living/owner
+ var/decl/status_effect/archetype
+ var/lifetime = STATUS_EFFECT_INDEFINITE
+
+/datum/status_effect/New(decl/status_effect/_archetype, mob/living/_owner)
+ archetype = istype(_archetype) ? _archetype : GET_DECL(_archetype)
+ owner = _owner
+
+/datum/status_effect/Destroy(force)
+ if(owner)
+ if(LAZYACCESS(owner.status_effects, archetype) == src)
+ LAZYREMOVE(owner.status_effects, archetype)
+ owner = null
+ return ..()
+
+/datum/status_effect/proc/on_effect_mob_life()
+ SHOULD_CALL_PARENT(TRUE)
+
+ // We should not exist without an owner.
+ if(!istype(owner))
+ qdel(src)
+ return PROCESS_KILL
+
+ // Count down our timer, if necessary.
+ if(lifetime != STATUS_EFFECT_INDEFINITE)
+ lifetime--
+ if(lifetime <= 0)
+ on_effect_expiry()
+ return PROCESS_KILL
+
+ // Pass off to the general behavior thingy to handle our actual logic.
+ if(!archetype.on_effect_mob_life(owner, src))
+ on_effect_expiry()
+ return PROCESS_KILL
+
+/datum/status_effect/proc/on_effect_removed()
+ SHOULD_CALL_PARENT(TRUE)
+ if(!istype(owner))
+ return FALSE
+ LAZYREMOVE(owner.status_effects, archetype)
+ archetype.on_effect_removed(owner, src)
+ owner = null
+ qdel(src)
+ return TRUE
+
+/datum/status_effect/proc/on_effect_added()
+ SHOULD_CALL_PARENT(TRUE)
+ if(!istype(owner))
+ return FALSE
+ LAZYSET(owner.status_effects, archetype, src)
+ archetype.on_effect_added(owner, src)
+ return TRUE
+
+/datum/status_effect/proc/on_effect_expiry()
+ SHOULD_CALL_PARENT(TRUE)
+ archetype.on_effect_expiry(owner, src)
+ return on_effect_removed()
+
+/datum/status_effect/proc/on_effect_click(params)
+ SHOULD_CALL_PARENT(TRUE)
+ archetype.on_effect_click(owner, src, params)
diff --git a/code/modules/xenoarcheaology/artifacts/effects/sleepy.dm b/code/modules/xenoarcheaology/artifacts/effects/sleepy.dm
index 6bb1478a0ba..77ac2a2bd89 100644
--- a/code/modules/xenoarcheaology/artifacts/effects/sleepy.dm
+++ b/code/modules/xenoarcheaology/artifacts/effects/sleepy.dm
@@ -40,5 +40,5 @@
return
if(prob(message_prob))
to_chat(H, SPAN_NOTICE(pick(sleepy_messages)))
- H.set_status(STAT_DROWSY, min(GET_STATUS(H, STAT_DROWSY) + speed * weakness, limit * weakness))
- H.set_status(STAT_BLURRY, min(GET_STATUS(H, STAT_BLURRY) + speed * weakness, limit * weakness))
\ No newline at end of file
+ H.set_status_condition(STAT_DROWSY, min(GET_STATUS(H, STAT_DROWSY) + speed * weakness, limit * weakness))
+ H.set_status_condition(STAT_BLURRY, min(GET_STATUS(H, STAT_BLURRY) + speed * weakness, limit * weakness))
\ No newline at end of file
diff --git a/icons/screen/status_effects.dmi b/icons/screen/status_effects.dmi
new file mode 100644
index 00000000000..ceb8879815d
Binary files /dev/null and b/icons/screen/status_effects.dmi differ
diff --git a/mods/content/psionics/system/psionics/faculties/coercion.dm b/mods/content/psionics/system/psionics/faculties/coercion.dm
index 8a92f058ad1..eca5436d631 100644
--- a/mods/content/psionics/system/psionics/faculties/coercion.dm
+++ b/mods/content/psionics/system/psionics/faculties/coercion.dm
@@ -242,7 +242,7 @@
var/coercion_rank = psi?.get_rank(PSI_COERCION)
if(coercion_rank >= PSI_RANK_GRANDMASTER)
ADJ_STATUS(target, STAT_PARA, -1)
- target.set_status(STAT_DROWSY, 0)
+ target.set_status_condition(STAT_DROWSY, 0)
if(isliving(target))
var/mob/living/M = target
M.adjust_hallucination(-30)
diff --git a/mods/gamemodes/cult/mobs/constructs/constructs.dm b/mods/gamemodes/cult/mobs/constructs/constructs.dm
index 1eb37f6980b..c3536733e70 100644
--- a/mods/gamemodes/cult/mobs/constructs/constructs.dm
+++ b/mods/gamemodes/cult/mobs/constructs/constructs.dm
@@ -124,7 +124,7 @@
_base_attack_force = 30
/mob/living/simple_animal/construct/armoured/handle_regular_status_updates()
- set_status(STAT_WEAK, 0)
+ set_status_condition(STAT_WEAK, 0)
if ((. = ..()))
return
diff --git a/mods/mobs/borers/mob/borer/borer.dm b/mods/mobs/borers/mob/borer/borer.dm
index f9dbbe2f6e1..ede49edb624 100644
--- a/mods/mobs/borers/mob/borer/borer.dm
+++ b/mods/mobs/borers/mob/borer/borer.dm
@@ -100,8 +100,8 @@
/mob/living/simple_animal/borer/handle_vision()
. = ..()
- set_status(STAT_BLIND, host ? GET_STATUS(host, STAT_BLIND) : 0)
- set_status(STAT_BLURRY, host ? GET_STATUS(host, STAT_BLURRY) : 0)
+ set_status_condition(STAT_BLIND, host ? GET_STATUS(host, STAT_BLIND) : 0)
+ set_status_condition(STAT_BLURRY, host ? GET_STATUS(host, STAT_BLURRY) : 0)
/mob/living/simple_animal/borer/handle_disabilities()
. = ..()
diff --git a/mods/species/drakes/drake_abilities_friendly.dm b/mods/species/drakes/drake_abilities_friendly.dm
index a224540e988..823ecf13e89 100644
--- a/mods/species/drakes/drake_abilities_friendly.dm
+++ b/mods/species/drakes/drake_abilities_friendly.dm
@@ -49,7 +49,7 @@ var/global/list/_wounds_being_tended_by_drakes = list()
return TRUE
// Are we already regenerating?
- if(friend.has_aura(/obj/aura/sifsap_salve))
+ if(friend.has_status_effect(/decl/status_effect/sifsap_salve))
if(friend == user)
to_chat(user, SPAN_WARNING("Your wounds have already been cleaned."))
else
@@ -72,7 +72,7 @@ var/global/list/_wounds_being_tended_by_drakes = list()
var/friend_ref = "\ref[friend]"
global._wounds_being_tended_by_drakes[friend_ref] = world.time + (8 SECONDS)
- if(!do_after(user, 8 SECONDS, friend) || QDELETED(friend) || friend.has_aura(/obj/aura/sifsap_salve) || user.incapacitated() || !drake_spend_sap(user, 10))
+ if(!do_after(user, 8 SECONDS, friend) || QDELETED(friend) || friend.has_status_effect(/decl/status_effect/sifsap_salve) || user.incapacitated() || !drake_spend_sap(user, 10))
global._wounds_being_tended_by_drakes -= friend_ref
return TRUE
@@ -86,7 +86,7 @@ var/global/list/_wounds_being_tended_by_drakes = list()
// Sivian animals get a heal buff from the modifier, others just
// get it to stop friendly drakes constantly licking their wounds.
// Organ wounds are closed, but the owners get sifsap injected via open wounds.
- friend.add_aura(new /obj/aura/sifsap_salve(friend, 60 SECONDS))
+ friend.set_status_effect(/decl/status_effect/sifsap_salve, DS_TO_LIFE_TICKS(60 SECONDS))
var/list/friend_organs = friend.get_external_organs()
if(length(friend_organs))
for (var/obj/item/organ/external/E in friend_organs)
diff --git a/mods/species/drakes/drake_modifiers.dm b/mods/species/drakes/drake_modifiers.dm
index a540b0f0c3b..2c15c5e3f0a 100644
--- a/mods/species/drakes/drake_modifiers.dm
+++ b/mods/species/drakes/drake_modifiers.dm
@@ -1,46 +1,37 @@
-/obj/aura/sifsap_salve
- name = "Sifsap Salve"
- icon = 'icons/effects/sparkles.dmi'
- icon_state = "cyan_sparkles"
- var/expiry
- var/descriptor = "glowing sap"
-
-/obj/aura/sifsap_salve/Initialize(ml, _lifetime)
- expiry = world.time + _lifetime
- return ..()
-
-/obj/aura/sifsap_salve/added_to(var/mob/living/L)
- ..()
- to_chat(user, SPAN_NOTICE("The [descriptor] seethes and bubbles in your wounds, tingling and stinging."))
-
-/obj/aura/sifsap_salve/removed()
- to_chat(user, SPAN_NOTICE("The last of the [descriptor] in your wounds fizzles away."))
- ..()
-
-/obj/aura/sifsap_salve/life_tick()
-
- if(!user || user.stat == DEAD || user.isSynthetic())
- return 0
-
- if(world.time >= expiry)
- if(user)
- user.remove_aura(src)
- return 0
-
- if(!user.has_trait(/decl/trait/sivian_biochemistry))
- user.heal_damage(BRUTE, 1, do_update_health = FALSE)
- user.heal_damage(BURN, 1, do_update_health = TRUE)
- return 1
-
- if(user.current_health >= user.get_max_health())
- return 0
-
- if(user.current_posture?.prone)
- user.heal_damage(BRUTE, 3, do_update_health = FALSE)
- user.heal_damage(BURN, 3, do_update_health = FALSE)
- user.heal_damage(TOX, 2, do_update_health = TRUE)
+/decl/status_effect/sifsap_salve
+ name = "Sifsap Salve"
+ desc = "Your wounds have been cleaned with drake spittle, which is beneficial to drakes - and not great for anyone else."
+ hud_icon = 'mods/species/drakes/icons/sifsap.dmi'
+ hud_icon_state = "sifsap_hud"
+ mob_overlay_icon = 'icons/effects/sparkles.dmi'
+ mob_overlay_state = "cyan_sparkles"
+ /// Defined here to allow overriding in fantasy modpack.
+ var/descriptor = "glowing sap"
+
+/decl/status_effect/sifsap_salve/Initialize()
+ on_add_message_1p = SPAN_NOTICE("The [descriptor] seethes and bubbles in your wounds, tingling and stinging.")
+ on_end_message_1p = SPAN_NOTICE("The last of the [descriptor] in your wounds fizzles away.")
+ . = ..()
+
+/decl/status_effect/sifsap_salve/on_effect_mob_life(mob/living/owner, datum/status_effect/effect)
+
+ if(!owner || owner.stat == DEAD || owner.isSynthetic())
+ return FALSE
+
+ if(!owner.has_trait(/decl/trait/sivian_biochemistry))
+ owner.heal_damage(BRUTE, 1, do_update_health = FALSE)
+ owner.heal_damage(BURN, 1, do_update_health = TRUE)
+ return TRUE
+
+ if(owner.current_health >= owner.get_max_health())
+ return FALSE
+
+ if(owner.current_posture?.prone)
+ owner.heal_damage(BRUTE, 3, do_update_health = FALSE)
+ owner.heal_damage(BURN, 3, do_update_health = FALSE)
+ owner.heal_damage(TOX, 2, do_update_health = TRUE)
else
- user.heal_damage(BRUTE, 2, do_update_health = FALSE)
- user.heal_damage(BURN, 2, do_update_health = FALSE)
- user.heal_damage(TOX, 1, do_update_health = TRUE)
- return 1
+ owner.heal_damage(BRUTE, 2, do_update_health = FALSE)
+ owner.heal_damage(BURN, 2, do_update_health = FALSE)
+ owner.heal_damage(TOX, 1, do_update_health = TRUE)
+ return TRUE
diff --git a/mods/species/drakes/icons/sifsap.dmi b/mods/species/drakes/icons/sifsap.dmi
new file mode 100644
index 00000000000..6942ef6d839
Binary files /dev/null and b/mods/species/drakes/icons/sifsap.dmi differ
diff --git a/mods/~compatibility/patches/fantasy/drake_fantasy.dm b/mods/~compatibility/patches/fantasy/drake_fantasy.dm
index 70ad5e628e7..694bebc1b4c 100644
--- a/mods/~compatibility/patches/fantasy/drake_fantasy.dm
+++ b/mods/~compatibility/patches/fantasy/drake_fantasy.dm
@@ -16,6 +16,6 @@
name = "drake spittle"
lore_text = "A complex chemical slurry brewed up in the gullet of meredrakes."
-/obj/aura/sifsap_salve
+/decl/status_effect/sifsap_salve
name = "Drakespittle Salve"
- descriptor = "glowing spittle"
+ desc = "glowing spittle"
diff --git a/nebula.dme b/nebula.dme
index 399eb186d43..8f801dd3fc1 100644
--- a/nebula.dme
+++ b/nebula.dme
@@ -5,6 +5,584 @@
// END_INTERNALS
// BEGIN_FILE_DIR
#define FILE_DIR .
+#define FILE_DIR "code"
+#define FILE_DIR "code/modules"
+#define FILE_DIR "code/modules/synthesized_instruments"
+#define FILE_DIR "code/modules/synthesized_instruments/samples"
+#define FILE_DIR "code/modules/synthesized_instruments/samples/brass"
+#define FILE_DIR "code/modules/synthesized_instruments/samples/brass/crisis_brass"
+#define FILE_DIR "code/modules/synthesized_instruments/samples/brass/crisis_trombone"
+#define FILE_DIR "code/modules/synthesized_instruments/samples/brass/crisis_trumpet"
+#define FILE_DIR "code/modules/synthesized_instruments/samples/chromatic"
+#define FILE_DIR "code/modules/synthesized_instruments/samples/chromatic/fluid_celeste"
+#define FILE_DIR "code/modules/synthesized_instruments/samples/chromatic/sgmbox"
+#define FILE_DIR "code/modules/synthesized_instruments/samples/chromatic/vibraphone1"
+#define FILE_DIR "code/modules/synthesized_instruments/samples/guitar"
+#define FILE_DIR "code/modules/synthesized_instruments/samples/guitar/crisis_clean"
+#define FILE_DIR "code/modules/synthesized_instruments/samples/guitar/crisis_muted"
+#define FILE_DIR "code/modules/synthesized_instruments/samples/guitar/crisis_nylon"
+#define FILE_DIR "code/modules/synthesized_instruments/samples/guitar/crisis_steel"
+#define FILE_DIR "code/modules/synthesized_instruments/samples/obsolete"
+#define FILE_DIR "code/modules/synthesized_instruments/samples/obsolete/piano"
+#define FILE_DIR "code/modules/synthesized_instruments/samples/obsolete/violin"
+#define FILE_DIR "code/modules/synthesized_instruments/samples/organ"
+#define FILE_DIR "code/modules/synthesized_instruments/samples/organ/crisis_accordian"
+#define FILE_DIR "code/modules/synthesized_instruments/samples/organ/crisis_church"
+#define FILE_DIR "code/modules/synthesized_instruments/samples/organ/crisis_hammond"
+#define FILE_DIR "code/modules/synthesized_instruments/samples/organ/crisis_harmonica"
+#define FILE_DIR "code/modules/synthesized_instruments/samples/organ/crisis_tangaccordian"
+#define FILE_DIR "code/modules/synthesized_instruments/samples/piano"
+#define FILE_DIR "code/modules/synthesized_instruments/samples/piano/crisis_bright_piano"
+#define FILE_DIR "code/modules/synthesized_instruments/samples/piano/crisis_grand_piano"
+#define FILE_DIR "code/modules/synthesized_instruments/samples/piano/crisis_harpsichord"
+#define FILE_DIR "code/modules/synthesized_instruments/samples/piano/fluid_harpsi"
+#define FILE_DIR "code/modules/synthesized_instruments/samples/piano/fluid_piano"
+#define FILE_DIR "code/modules/synthesized_instruments/samples/tones"
+#define FILE_DIR "data"
+#define FILE_DIR "data/secrets"
+#define FILE_DIR "data/secrets/example"
+#define FILE_DIR "html"
+#define FILE_DIR "html/images"
+#define FILE_DIR "icons"
+#define FILE_DIR "icons/atmos"
+#define FILE_DIR "icons/clothing"
+#define FILE_DIR "icons/clothing/accessories"
+#define FILE_DIR "icons/clothing/accessories/armbands"
+#define FILE_DIR "icons/clothing/accessories/armor"
+#define FILE_DIR "icons/clothing/accessories/badges"
+#define FILE_DIR "icons/clothing/accessories/clothing"
+#define FILE_DIR "icons/clothing/accessories/holsters"
+#define FILE_DIR "icons/clothing/accessories/jewelry"
+#define FILE_DIR "icons/clothing/accessories/jewelry/pendants"
+#define FILE_DIR "icons/clothing/accessories/jewelry/religious"
+#define FILE_DIR "icons/clothing/accessories/jewelry/rings"
+#define FILE_DIR "icons/clothing/accessories/medals"
+#define FILE_DIR "icons/clothing/accessories/pouches"
+#define FILE_DIR "icons/clothing/accessories/storage"
+#define FILE_DIR "icons/clothing/accessories/tags"
+#define FILE_DIR "icons/clothing/accessories/ties"
+#define FILE_DIR "icons/clothing/belt"
+#define FILE_DIR "icons/clothing/costumes"
+#define FILE_DIR "icons/clothing/dresses"
+#define FILE_DIR "icons/clothing/ears"
+#define FILE_DIR "icons/clothing/eyes"
+#define FILE_DIR "icons/clothing/feet"
+#define FILE_DIR "icons/clothing/hands"
+#define FILE_DIR "icons/clothing/head"
+#define FILE_DIR "icons/clothing/head/armor"
+#define FILE_DIR "icons/clothing/head/bandana"
+#define FILE_DIR "icons/clothing/head/biosuit"
+#define FILE_DIR "icons/clothing/head/hairflower"
+#define FILE_DIR "icons/clothing/head/hardhat"
+#define FILE_DIR "icons/clothing/head/space"
+#define FILE_DIR "icons/clothing/head/space/syndicate"
+#define FILE_DIR "icons/clothing/head/welding"
+#define FILE_DIR "icons/clothing/head/wizard"
+#define FILE_DIR "icons/clothing/jumpsuits"
+#define FILE_DIR "icons/clothing/mask"
+#define FILE_DIR "icons/clothing/mask/chewables"
+#define FILE_DIR "icons/clothing/mask/smokables"
+#define FILE_DIR "icons/clothing/pants"
+#define FILE_DIR "icons/clothing/pants/leggings"
+#define FILE_DIR "icons/clothing/plate_armour"
+#define FILE_DIR "icons/clothing/rigs"
+#define FILE_DIR "icons/clothing/rigs/boots"
+#define FILE_DIR "icons/clothing/rigs/chests"
+#define FILE_DIR "icons/clothing/rigs/ert"
+#define FILE_DIR "icons/clothing/rigs/ert/asset_protection"
+#define FILE_DIR "icons/clothing/rigs/ert/commander"
+#define FILE_DIR "icons/clothing/rigs/ert/engineer"
+#define FILE_DIR "icons/clothing/rigs/ert/janitor"
+#define FILE_DIR "icons/clothing/rigs/ert/medic"
+#define FILE_DIR "icons/clothing/rigs/ert/security"
+#define FILE_DIR "icons/clothing/rigs/gloves"
+#define FILE_DIR "icons/clothing/rigs/helmets"
+#define FILE_DIR "icons/clothing/shirts"
+#define FILE_DIR "icons/clothing/shirts/tunics"
+#define FILE_DIR "icons/clothing/skirts"
+#define FILE_DIR "icons/clothing/spacesuit"
+#define FILE_DIR "icons/clothing/spacesuit/cult"
+#define FILE_DIR "icons/clothing/spacesuit/emergency"
+#define FILE_DIR "icons/clothing/spacesuit/generic"
+#define FILE_DIR "icons/clothing/spacesuit/void"
+#define FILE_DIR "icons/clothing/spacesuit/void/atmos"
+#define FILE_DIR "icons/clothing/spacesuit/void/atmos_alt"
+#define FILE_DIR "icons/clothing/spacesuit/void/deathsquad"
+#define FILE_DIR "icons/clothing/spacesuit/void/engineering"
+#define FILE_DIR "icons/clothing/spacesuit/void/engineering_alt"
+#define FILE_DIR "icons/clothing/spacesuit/void/excavation"
+#define FILE_DIR "icons/clothing/spacesuit/void/medical"
+#define FILE_DIR "icons/clothing/spacesuit/void/medical_alt"
+#define FILE_DIR "icons/clothing/spacesuit/void/merc"
+#define FILE_DIR "icons/clothing/spacesuit/void/mining"
+#define FILE_DIR "icons/clothing/spacesuit/void/mining_alt"
+#define FILE_DIR "icons/clothing/spacesuit/void/nasa"
+#define FILE_DIR "icons/clothing/spacesuit/void/pilot"
+#define FILE_DIR "icons/clothing/spacesuit/void/salvage"
+#define FILE_DIR "icons/clothing/spacesuit/void/sec"
+#define FILE_DIR "icons/clothing/spacesuit/void/sec_alt"
+#define FILE_DIR "icons/clothing/spacesuit/void/wizard"
+#define FILE_DIR "icons/clothing/suits"
+#define FILE_DIR "icons/clothing/suits/armor"
+#define FILE_DIR "icons/clothing/suits/biosuit"
+#define FILE_DIR "icons/clothing/suits/cloaks"
+#define FILE_DIR "icons/clothing/suits/dashiki"
+#define FILE_DIR "icons/clothing/suits/deity"
+#define FILE_DIR "icons/clothing/suits/hazard_vest"
+#define FILE_DIR "icons/clothing/suits/jackets"
+#define FILE_DIR "icons/clothing/suits/labcoat"
+#define FILE_DIR "icons/clothing/suits/poncho"
+#define FILE_DIR "icons/clothing/suits/space"
+#define FILE_DIR "icons/clothing/suits/space/syndicate"
+#define FILE_DIR "icons/clothing/suits/tracksuit"
+#define FILE_DIR "icons/clothing/suits/wintercoat"
+#define FILE_DIR "icons/clothing/suits/wizard"
+#define FILE_DIR "icons/clothing/suits/wizard/servant"
+#define FILE_DIR "icons/effects"
+#define FILE_DIR "icons/effects/decals"
+#define FILE_DIR "icons/effects/mouse_pointers"
+#define FILE_DIR "icons/effects/projectiles"
+#define FILE_DIR "icons/mecha"
+#define FILE_DIR "icons/misc"
+#define FILE_DIR "icons/mob"
+#define FILE_DIR "icons/mob/bot"
+#define FILE_DIR "icons/mob/footprints"
+#define FILE_DIR "icons/mob/human_races"
+#define FILE_DIR "icons/mob/human_races/cyberlimbs"
+#define FILE_DIR "icons/mob/human_races/cyberlimbs/morgan"
+#define FILE_DIR "icons/mob/human_races/species"
+#define FILE_DIR "icons/mob/human_races/species/blueforged"
+#define FILE_DIR "icons/mob/human_races/species/golem"
+#define FILE_DIR "icons/mob/human_races/species/human"
+#define FILE_DIR "icons/mob/human_races/species/humanoid"
+#define FILE_DIR "icons/mob/human_races/species/monkey"
+#define FILE_DIR "icons/mob/human_races/species/shadow"
+#define FILE_DIR "icons/mob/human_races/species/starborn"
+#define FILE_DIR "icons/mob/onmob"
+#define FILE_DIR "icons/mob/onmob/items"
+#define FILE_DIR "icons/mob/robots"
+#define FILE_DIR "icons/mob/robots/drones"
+#define FILE_DIR "icons/mob/robots/flying"
+#define FILE_DIR "icons/mob/robots/pai"
+#define FILE_DIR "icons/mob/screen"
+#define FILE_DIR "icons/mob/screen/styles"
+#define FILE_DIR "icons/mob/screen/styles/constructs"
+#define FILE_DIR "icons/mob/screen/styles/constructs/artificer"
+#define FILE_DIR "icons/mob/screen/styles/constructs/harvester"
+#define FILE_DIR "icons/mob/screen/styles/constructs/juggernaut"
+#define FILE_DIR "icons/mob/screen/styles/constructs/wraith"
+#define FILE_DIR "icons/mob/screen/styles/midnight"
+#define FILE_DIR "icons/mob/screen/styles/minimalist"
+#define FILE_DIR "icons/mob/screen/styles/old"
+#define FILE_DIR "icons/mob/screen/styles/old_noborder"
+#define FILE_DIR "icons/mob/screen/styles/orange"
+#define FILE_DIR "icons/mob/screen/styles/robot"
+#define FILE_DIR "icons/mob/screen/styles/underworld"
+#define FILE_DIR "icons/mob/screen/styles/white"
+#define FILE_DIR "icons/mob/simple_animal"
+#define FILE_DIR "icons/obj"
+#define FILE_DIR "icons/obj/action_buttons"
+#define FILE_DIR "icons/obj/ammo"
+#define FILE_DIR "icons/obj/ammo/casings"
+#define FILE_DIR "icons/obj/assemblies"
+#define FILE_DIR "icons/obj/atmospherics"
+#define FILE_DIR "icons/obj/bedsheets"
+#define FILE_DIR "icons/obj/closets"
+#define FILE_DIR "icons/obj/closets/bases"
+#define FILE_DIR "icons/obj/closets/decals"
+#define FILE_DIR "icons/obj/clothing"
+#define FILE_DIR "icons/obj/doors"
+#define FILE_DIR "icons/obj/doors/blast_doors"
+#define FILE_DIR "icons/obj/doors/centcomm"
+#define FILE_DIR "icons/obj/doors/double"
+#define FILE_DIR "icons/obj/doors/elevator"
+#define FILE_DIR "icons/obj/doors/external"
+#define FILE_DIR "icons/obj/doors/hatch"
+#define FILE_DIR "icons/obj/doors/hazard"
+#define FILE_DIR "icons/obj/doors/secure"
+#define FILE_DIR "icons/obj/doors/shutters"
+#define FILE_DIR "icons/obj/doors/station"
+#define FILE_DIR "icons/obj/doors/vault"
+#define FILE_DIR "icons/obj/drink_glasses"
+#define FILE_DIR "icons/obj/flora"
+#define FILE_DIR "icons/obj/food"
+#define FILE_DIR "icons/obj/food/baked"
+#define FILE_DIR "icons/obj/food/baked/bread"
+#define FILE_DIR "icons/obj/food/baked/bread/slices"
+#define FILE_DIR "icons/obj/food/baked/cakes"
+#define FILE_DIR "icons/obj/food/baked/cakes/slices"
+#define FILE_DIR "icons/obj/food/baked/pies"
+#define FILE_DIR "icons/obj/food/baked/waffles"
+#define FILE_DIR "icons/obj/food/burgers"
+#define FILE_DIR "icons/obj/food/butchery"
+#define FILE_DIR "icons/obj/food/canned"
+#define FILE_DIR "icons/obj/food/condiments"
+#define FILE_DIR "icons/obj/food/condiments/packets"
+#define FILE_DIR "icons/obj/food/containers"
+#define FILE_DIR "icons/obj/food/cooking_vessels"
+#define FILE_DIR "icons/obj/food/custom"
+#define FILE_DIR "icons/obj/food/dairy"
+#define FILE_DIR "icons/obj/food/donuts"
+#define FILE_DIR "icons/obj/food/eggs"
+#define FILE_DIR "icons/obj/food/fried"
+#define FILE_DIR "icons/obj/food/junk"
+#define FILE_DIR "icons/obj/food/mre"
+#define FILE_DIR "icons/obj/food/nuggets"
+#define FILE_DIR "icons/obj/food/old"
+#define FILE_DIR "icons/obj/food/pasta"
+#define FILE_DIR "icons/obj/food/pizzas"
+#define FILE_DIR "icons/obj/food/plates"
+#define FILE_DIR "icons/obj/food/pudding"
+#define FILE_DIR "icons/obj/food/rice"
+#define FILE_DIR "icons/obj/food/salads"
+#define FILE_DIR "icons/obj/food/tofu"
+#define FILE_DIR "icons/obj/food/utensils"
+#define FILE_DIR "icons/obj/grown"
+#define FILE_DIR "icons/obj/guns"
+#define FILE_DIR "icons/obj/guns/foam"
+#define FILE_DIR "icons/obj/guns/launcher"
+#define FILE_DIR "icons/obj/guns/random_pistol"
+#define FILE_DIR "icons/obj/guns/random_pistol/handle"
+#define FILE_DIR "icons/obj/guns/random_pistol/looks"
+#define FILE_DIR "icons/obj/guns/shotgun"
+#define FILE_DIR "icons/obj/guns/xenoarch"
+#define FILE_DIR "icons/obj/hydroponics"
+#define FILE_DIR "icons/obj/id"
+#define FILE_DIR "icons/obj/items"
+#define FILE_DIR "icons/obj/items/banners"
+#define FILE_DIR "icons/obj/items/bladed"
+#define FILE_DIR "icons/obj/items/books"
+#define FILE_DIR "icons/obj/items/borg_module"
+#define FILE_DIR "icons/obj/items/chem"
+#define FILE_DIR "icons/obj/items/chem/beakers"
+#define FILE_DIR "icons/obj/items/cosmetics"
+#define FILE_DIR "icons/obj/items/device"
+#define FILE_DIR "icons/obj/items/device/radio"
+#define FILE_DIR "icons/obj/items/device/radio/headsets"
+#define FILE_DIR "icons/obj/items/device/scanner"
+#define FILE_DIR "icons/obj/items/device/tape_recorder"
+#define FILE_DIR "icons/obj/items/flame"
+#define FILE_DIR "icons/obj/items/gemstones"
+#define FILE_DIR "icons/obj/items/grenades"
+#define FILE_DIR "icons/obj/items/grooming"
+#define FILE_DIR "icons/obj/items/handmade"
+#define FILE_DIR "icons/obj/items/implant"
+#define FILE_DIR "icons/obj/items/paperwork"
+#define FILE_DIR "icons/obj/items/pens"
+#define FILE_DIR "icons/obj/items/shield"
+#define FILE_DIR "icons/obj/items/stacks"
+#define FILE_DIR "icons/obj/items/stamps"
+#define FILE_DIR "icons/obj/items/stock_parts"
+#define FILE_DIR "icons/obj/items/storage"
+#define FILE_DIR "icons/obj/items/storage/backpack"
+#define FILE_DIR "icons/obj/items/storage/baskets"
+#define FILE_DIR "icons/obj/items/storage/cigpack"
+#define FILE_DIR "icons/obj/items/storage/lunchboxes"
+#define FILE_DIR "icons/obj/items/storage/toolboxes"
+#define FILE_DIR "icons/obj/items/surgery"
+#define FILE_DIR "icons/obj/items/tanks"
+#define FILE_DIR "icons/obj/items/tool"
+#define FILE_DIR "icons/obj/items/tool/axes"
+#define FILE_DIR "icons/obj/items/tool/components"
+#define FILE_DIR "icons/obj/items/tool/drills"
+#define FILE_DIR "icons/obj/items/tool/hammers"
+#define FILE_DIR "icons/obj/items/tool/hoes"
+#define FILE_DIR "icons/obj/items/tool/shovels"
+#define FILE_DIR "icons/obj/items/tool/welders"
+#define FILE_DIR "icons/obj/items/training_dummies"
+#define FILE_DIR "icons/obj/items/weapon"
+#define FILE_DIR "icons/obj/items/weapon/knives"
+#define FILE_DIR "icons/obj/items/weapon/knives/folding"
+#define FILE_DIR "icons/obj/items/weapon/knives/xenoarch"
+#define FILE_DIR "icons/obj/items/weapon/machetes"
+#define FILE_DIR "icons/obj/items/weapon/swords"
+#define FILE_DIR "icons/obj/items/weapon/swords/xenoarch"
+#define FILE_DIR "icons/obj/lighting"
+#define FILE_DIR "icons/obj/machines"
+#define FILE_DIR "icons/obj/machines/chemistry"
+#define FILE_DIR "icons/obj/machines/fabricators"
+#define FILE_DIR "icons/obj/machines/power"
+#define FILE_DIR "icons/obj/machines/smartfridges"
+#define FILE_DIR "icons/obj/machines/tcomms"
+#define FILE_DIR "icons/obj/machines/vending"
+#define FILE_DIR "icons/obj/materials"
+#define FILE_DIR "icons/obj/metalworking"
+#define FILE_DIR "icons/obj/modular_computers"
+#define FILE_DIR "icons/obj/modular_computers/holo"
+#define FILE_DIR "icons/obj/modular_computers/pda"
+#define FILE_DIR "icons/obj/modules"
+#define FILE_DIR "icons/obj/pipes"
+#define FILE_DIR "icons/obj/seeds"
+#define FILE_DIR "icons/obj/signs"
+#define FILE_DIR "icons/obj/structures"
+#define FILE_DIR "icons/obj/structures/barrels"
+#define FILE_DIR "icons/obj/structures/decorations"
+#define FILE_DIR "icons/obj/structures/forging"
+#define FILE_DIR "icons/obj/structures/pedestals"
+#define FILE_DIR "icons/obj/structures/pillars"
+#define FILE_DIR "icons/obj/structures/snowmen"
+#define FILE_DIR "icons/obj/structures/target_stakes"
+#define FILE_DIR "icons/obj/toy"
+#define FILE_DIR "icons/screen"
+#define FILE_DIR "icons/skybox"
+#define FILE_DIR "icons/Testing"
+#define FILE_DIR "icons/turf"
+#define FILE_DIR "icons/turf/flooring"
+#define FILE_DIR "icons/turf/walls"
+#define FILE_DIR "maps"
+#define FILE_DIR "maps/away"
+#define FILE_DIR "maps/away/casino"
+#define FILE_DIR "maps/away/errant_pisces"
+#define FILE_DIR "maps/away/errant_pisces/icons"
+#define FILE_DIR "maps/away/liberia"
+#define FILE_DIR "maps/away/lost_supply_base"
+#define FILE_DIR "maps/away/magshield"
+#define FILE_DIR "maps/away/mobius_rift"
+#define FILE_DIR "maps/away/smugglers"
+#define FILE_DIR "maps/away/unishi"
+#define FILE_DIR "maps/away/yacht"
+#define FILE_DIR "maps/example"
+#define FILE_DIR "maps/exodus"
+#define FILE_DIR "maps/exodus/lobby"
+#define FILE_DIR "maps/ministation"
+#define FILE_DIR "maps/random_ruins"
+#define FILE_DIR "maps/random_ruins/exoplanet_ruins"
+#define FILE_DIR "maps/random_ruins/exoplanet_ruins/hydrobase"
+#define FILE_DIR "maps/random_ruins/exoplanet_ruins/marooned"
+#define FILE_DIR "maps/random_ruins/exoplanet_ruins/marooned/icons"
+#define FILE_DIR "maps/random_ruins/exoplanet_ruins/playablecolony"
+#define FILE_DIR "maps/shaded_hills"
+#define FILE_DIR "maps/shaded_hills/areas"
+#define FILE_DIR "maps/tradeship"
+#define FILE_DIR "maps/tradeship/lobby"
+#define FILE_DIR "mods"
+#define FILE_DIR "mods/content"
+#define FILE_DIR "mods/content/byond_membership"
+#define FILE_DIR "mods/content/corporate"
+#define FILE_DIR "mods/content/corporate/away_sites"
+#define FILE_DIR "mods/content/corporate/away_sites/lar_maria"
+#define FILE_DIR "mods/content/corporate/icons"
+#define FILE_DIR "mods/content/corporate/icons/clothing"
+#define FILE_DIR "mods/content/corporate/icons/clothing/accessories"
+#define FILE_DIR "mods/content/corporate/icons/clothing/accessories/jackets"
+#define FILE_DIR "mods/content/corporate/icons/clothing/accessories/ties"
+#define FILE_DIR "mods/content/corporate/icons/clothing/accessories/tunic"
+#define FILE_DIR "mods/content/corporate/icons/clothing/head"
+#define FILE_DIR "mods/content/corporate/icons/clothing/head/beret"
+#define FILE_DIR "mods/content/corporate/icons/clothing/head/ert"
+#define FILE_DIR "mods/content/corporate/icons/clothing/mask"
+#define FILE_DIR "mods/content/corporate/icons/clothing/suit"
+#define FILE_DIR "mods/content/corporate/icons/clothing/suit/armor"
+#define FILE_DIR "mods/content/corporate/icons/clothing/suit/navy"
+#define FILE_DIR "mods/content/corporate/icons/clothing/under"
+#define FILE_DIR "mods/content/corporate/icons/cyberlimbs"
+#define FILE_DIR "mods/content/corporate/icons/cyberlimbs/bishop"
+#define FILE_DIR "mods/content/corporate/icons/cyberlimbs/hephaestus"
+#define FILE_DIR "mods/content/corporate/icons/cyberlimbs/morpheus"
+#define FILE_DIR "mods/content/corporate/icons/cyberlimbs/nanotrasen"
+#define FILE_DIR "mods/content/corporate/icons/cyberlimbs/shellguard"
+#define FILE_DIR "mods/content/corporate/icons/cyberlimbs/veymed"
+#define FILE_DIR "mods/content/corporate/icons/cyberlimbs/wardtakahashi"
+#define FILE_DIR "mods/content/corporate/icons/cyberlimbs/xion"
+#define FILE_DIR "mods/content/corporate/icons/cyberlimbs/zenghu"
+#define FILE_DIR "mods/content/corporate/icons/obj"
+#define FILE_DIR "mods/content/dungeon_loot"
+#define FILE_DIR "mods/content/dungeon_loot/icons"
+#define FILE_DIR "mods/content/fantasy"
+#define FILE_DIR "mods/content/fantasy/icons"
+#define FILE_DIR "mods/content/fantasy/icons/clothing"
+#define FILE_DIR "mods/content/fantasy/icons/hnoll"
+#define FILE_DIR "mods/content/fantasy/icons/kobaloi"
+#define FILE_DIR "mods/content/fantasy/icons/structures"
+#define FILE_DIR "mods/content/government"
+#define FILE_DIR "mods/content/government/away_sites"
+#define FILE_DIR "mods/content/government/away_sites/icarus"
+#define FILE_DIR "mods/content/government/icons"
+#define FILE_DIR "mods/content/inertia"
+#define FILE_DIR "mods/content/inertia/icons"
+#define FILE_DIR "mods/content/item_sharpening"
+#define FILE_DIR "mods/content/item_sharpening/icons"
+#define FILE_DIR "mods/content/psionics"
+#define FILE_DIR "mods/content/psionics/icons"
+#define FILE_DIR "mods/content/standard_jobs"
+#define FILE_DIR "mods/content/standard_jobs/icons"
+#define FILE_DIR "mods/content/tabloids"
+#define FILE_DIR "mods/content/tabloids/icons"
+#define FILE_DIR "mods/content/xenobiology"
+#define FILE_DIR "mods/content/xenobiology/icons"
+#define FILE_DIR "mods/content/xenobiology/icons/slimes"
+#define FILE_DIR "mods/gamemodes"
+#define FILE_DIR "mods/gamemodes/cult"
+#define FILE_DIR "mods/gamemodes/cult/icons"
+#define FILE_DIR "mods/mobs"
+#define FILE_DIR "mods/mobs/borers"
+#define FILE_DIR "mods/mobs/borers/icons"
+#define FILE_DIR "mods/mobs/dionaea"
+#define FILE_DIR "mods/mobs/dionaea/icons"
+#define FILE_DIR "mods/mobs/dionaea/sounds"
+#define FILE_DIR "mods/species"
+#define FILE_DIR "mods/species/ascent"
+#define FILE_DIR "mods/species/ascent/icons"
+#define FILE_DIR "mods/species/ascent/icons/alate_spacesuit"
+#define FILE_DIR "mods/species/ascent/icons/clothing"
+#define FILE_DIR "mods/species/ascent/icons/doors"
+#define FILE_DIR "mods/species/ascent/icons/harness"
+#define FILE_DIR "mods/species/ascent/icons/magboots"
+#define FILE_DIR "mods/species/ascent/icons/particle_rifle"
+#define FILE_DIR "mods/species/ascent/icons/rig"
+#define FILE_DIR "mods/species/ascent/icons/species"
+#define FILE_DIR "mods/species/ascent/icons/species/body"
+#define FILE_DIR "mods/species/ascent/icons/species/body/alate"
+#define FILE_DIR "mods/species/ascent/icons/species/body/gyne"
+#define FILE_DIR "mods/species/ascent/icons/species/mantid"
+#define FILE_DIR "mods/species/ascent/sounds"
+#define FILE_DIR "mods/species/bayliens"
+#define FILE_DIR "mods/species/bayliens/adherent"
+#define FILE_DIR "mods/species/bayliens/adherent/icons"
+#define FILE_DIR "mods/species/bayliens/adherent/sound"
+#define FILE_DIR "mods/species/bayliens/skrell"
+#define FILE_DIR "mods/species/bayliens/skrell/icons"
+#define FILE_DIR "mods/species/bayliens/skrell/icons/body"
+#define FILE_DIR "mods/species/bayliens/skrell/icons/clothing"
+#define FILE_DIR "mods/species/bayliens/skrell/icons/clothing/accessories"
+#define FILE_DIR "mods/species/bayliens/skrell/icons/clothing/ears"
+#define FILE_DIR "mods/species/bayliens/skrell/icons/clothing/head"
+#define FILE_DIR "mods/species/bayliens/skrell/icons/clothing/mask"
+#define FILE_DIR "mods/species/bayliens/skrell/icons/clothing/suit"
+#define FILE_DIR "mods/species/bayliens/skrell/icons/clothing/under"
+#define FILE_DIR "mods/species/bayliens/skrell/icons/gear"
+#define FILE_DIR "mods/species/bayliens/skrell/icons/rigs"
+#define FILE_DIR "mods/species/bayliens/skrell/icons/rigs/combat"
+#define FILE_DIR "mods/species/bayliens/skrell/icons/rigs/command"
+#define FILE_DIR "mods/species/bayliens/skrell/icons/rigs/engineering"
+#define FILE_DIR "mods/species/bayliens/skrell/icons/rigs/medical"
+#define FILE_DIR "mods/species/bayliens/skrell/icons/rigs/standard"
+#define FILE_DIR "mods/species/bayliens/skrell/icons/turf"
+#define FILE_DIR "mods/species/bayliens/skrell/sound"
+#define FILE_DIR "mods/species/bayliens/skrell/sound/drop"
+#define FILE_DIR "mods/species/bayliens/skrell/sound/pickup"
+#define FILE_DIR "mods/species/bayliens/tajaran"
+#define FILE_DIR "mods/species/bayliens/tajaran/icons"
+#define FILE_DIR "mods/species/bayliens/tajaran/icons/clothing"
+#define FILE_DIR "mods/species/bayliens/tajaran/icons/clothing/atmos"
+#define FILE_DIR "mods/species/bayliens/tajaran/icons/clothing/atmos_alt"
+#define FILE_DIR "mods/species/bayliens/tajaran/icons/clothing/deathsquad"
+#define FILE_DIR "mods/species/bayliens/tajaran/icons/clothing/engineering"
+#define FILE_DIR "mods/species/bayliens/tajaran/icons/clothing/engineering_alt"
+#define FILE_DIR "mods/species/bayliens/tajaran/icons/clothing/excavation"
+#define FILE_DIR "mods/species/bayliens/tajaran/icons/clothing/medical"
+#define FILE_DIR "mods/species/bayliens/tajaran/icons/clothing/medical_alt"
+#define FILE_DIR "mods/species/bayliens/tajaran/icons/clothing/merc"
+#define FILE_DIR "mods/species/bayliens/tajaran/icons/clothing/mining"
+#define FILE_DIR "mods/species/bayliens/tajaran/icons/clothing/mining_alt"
+#define FILE_DIR "mods/species/bayliens/tajaran/icons/clothing/nasa"
+#define FILE_DIR "mods/species/bayliens/tajaran/icons/clothing/pilot"
+#define FILE_DIR "mods/species/bayliens/tajaran/icons/clothing/salvage"
+#define FILE_DIR "mods/species/bayliens/tajaran/icons/clothing/sec"
+#define FILE_DIR "mods/species/bayliens/tajaran/icons/clothing/sec_alt"
+#define FILE_DIR "mods/species/bayliens/tajaran/icons/clothing/wizard"
+#define FILE_DIR "mods/species/bayliens/tajaran/sound"
+#define FILE_DIR "mods/species/bayliens/tritonian"
+#define FILE_DIR "mods/species/bayliens/tritonian/icons"
+#define FILE_DIR "mods/species/bayliens/unathi"
+#define FILE_DIR "mods/species/bayliens/unathi/icons"
+#define FILE_DIR "mods/species/bayliens/unathi/sound"
+#define FILE_DIR "mods/species/drakes"
+#define FILE_DIR "mods/species/drakes/icons"
+#define FILE_DIR "mods/species/drakes/icons/clothing"
+#define FILE_DIR "mods/species/drakes/sounds"
+#define FILE_DIR "mods/species/neoavians"
+#define FILE_DIR "mods/species/neoavians/icons"
+#define FILE_DIR "mods/species/neoavians/icons/clothing"
+#define FILE_DIR "mods/species/neoavians/icons/clothing/accessory"
+#define FILE_DIR "mods/species/neoavians/icons/clothing/feet"
+#define FILE_DIR "mods/species/neoavians/icons/clothing/head"
+#define FILE_DIR "mods/species/neoavians/icons/clothing/spacesuit"
+#define FILE_DIR "mods/species/neoavians/icons/clothing/spacesuit/void"
+#define FILE_DIR "mods/species/neoavians/icons/clothing/spacesuit/void/atmos"
+#define FILE_DIR "mods/species/neoavians/icons/clothing/spacesuit/void/engineering"
+#define FILE_DIR "mods/species/neoavians/icons/clothing/spacesuit/void/medical"
+#define FILE_DIR "mods/species/neoavians/icons/clothing/spacesuit/void/medical_alt"
+#define FILE_DIR "mods/species/neoavians/icons/clothing/spacesuit/void/merc"
+#define FILE_DIR "mods/species/neoavians/icons/clothing/spacesuit/void/mining"
+#define FILE_DIR "mods/species/neoavians/icons/clothing/spacesuit/void/pilot"
+#define FILE_DIR "mods/species/neoavians/icons/clothing/spacesuit/void/salvage"
+#define FILE_DIR "mods/species/neoavians/icons/clothing/spacesuit/void/sec"
+#define FILE_DIR "mods/species/neoavians/icons/clothing/suit"
+#define FILE_DIR "mods/species/neoavians/icons/clothing/under"
+#define FILE_DIR "mods/species/neoavians/sound"
+#define FILE_DIR "mods/species/serpentid"
+#define FILE_DIR "mods/species/serpentid/icons"
+#define FILE_DIR "mods/species/utility_frames"
+#define FILE_DIR "mods/species/utility_frames/icons"
+#define FILE_DIR "mods/species/vox"
+#define FILE_DIR "mods/species/vox/icons"
+#define FILE_DIR "mods/species/vox/icons/body"
+#define FILE_DIR "mods/species/vox/icons/body/servitor"
+#define FILE_DIR "mods/species/vox/icons/body/soldier"
+#define FILE_DIR "mods/species/vox/icons/body/stanchion"
+#define FILE_DIR "mods/species/vox/icons/clothing"
+#define FILE_DIR "mods/species/vox/icons/gear"
+#define FILE_DIR "mods/species/vox/icons/rig"
+#define FILE_DIR "mods/species/vox/sounds"
+#define FILE_DIR "nano"
+#define FILE_DIR "nano/images"
+#define FILE_DIR "nano/images/example"
+#define FILE_DIR "nano/images/exodus"
+#define FILE_DIR "nano/images/ministation"
+#define FILE_DIR "nano/images/modular_computers"
+#define FILE_DIR "nano/images/overmap_example"
+#define FILE_DIR "nano/images/shaded_hills"
+#define FILE_DIR "nano/images/status_icons"
+#define FILE_DIR "nano/images/tradeship"
+#define FILE_DIR "sound"
+#define FILE_DIR "sound/AI"
+#define FILE_DIR "sound/ambience"
+#define FILE_DIR "sound/effects"
+#define FILE_DIR "sound/effects/footstep"
+#define FILE_DIR "sound/effects/holster"
+#define FILE_DIR "sound/effects/plants"
+#define FILE_DIR "sound/effects/projectile_impact"
+#define FILE_DIR "sound/effects/psi"
+#define FILE_DIR "sound/effects/storage"
+#define FILE_DIR "sound/effects/thunder"
+#define FILE_DIR "sound/effects/turret"
+#define FILE_DIR "sound/effects/weather"
+#define FILE_DIR "sound/effects/wind"
+#define FILE_DIR "sound/foley"
+#define FILE_DIR "sound/hallucinations"
+#define FILE_DIR "sound/items"
+#define FILE_DIR "sound/items/containers"
+#define FILE_DIR "sound/items/drop"
+#define FILE_DIR "sound/items/pickup"
+#define FILE_DIR "sound/machines"
+#define FILE_DIR "sound/machines/keyboard"
+#define FILE_DIR "sound/machines/kitchen"
+#define FILE_DIR "sound/machines/kitchen/grill"
+#define FILE_DIR "sound/machines/microwave"
+#define FILE_DIR "sound/machines/phone"
+#define FILE_DIR "sound/machines/sensors"
+#define FILE_DIR "sound/machines/sm"
+#define FILE_DIR "sound/machines/sm/accent"
+#define FILE_DIR "sound/machines/sm/accent/delam"
+#define FILE_DIR "sound/machines/sm/accent/normal"
+#define FILE_DIR "sound/machines/sm/loops"
+#define FILE_DIR "sound/magic"
+#define FILE_DIR "sound/mecha"
+#define FILE_DIR "sound/misc"
+#define FILE_DIR "sound/music"
+#define FILE_DIR "sound/music/europa"
+#define FILE_DIR "sound/piano"
+#define FILE_DIR "sound/violin"
+#define FILE_DIR "sound/voice"
+#define FILE_DIR "sound/voice/eal"
+#define FILE_DIR "sound/voice/emotes"
+#define FILE_DIR "sound/voice/medbot"
+#define FILE_DIR "sound/voice/Serithi"
+#define FILE_DIR "sound/weapons"
+#define FILE_DIR "sound/weapons/guns"
+#define FILE_DIR "sound/weapons/guns/interaction"
+#define FILE_DIR "sound/weapons/gunshot"
// END_FILE_DIR
// BEGIN_PREFERENCES
#define DEBUG
@@ -1656,6 +2234,7 @@
#include "code\modules\abstract\abstract_fluid_direction.dm"
#include "code\modules\abstract\abstract_ramp_sculptor.dm"
#include "code\modules\abstract\airlock_helper.dm"
+#include "code\modules\abstract\follower.dm"
#include "code\modules\acting\acting_items.dm"
#include "code\modules\admin\admin.dm"
#include "code\modules\admin\admin_attack_log.dm"
@@ -3827,11 +4406,17 @@
#include "code\modules\status_conditions\_status_markers.dm"
#include "code\modules\status_conditions\status_counters_simple.dm"
#include "code\modules\status_conditions\status_dizzy.dm"
+#include "code\modules\status_conditions\status_effect.dm"
+#include "code\modules\status_conditions\status_effect_archetype.dm"
+#include "code\modules\status_conditions\status_effect_datum.dm"
#include "code\modules\status_conditions\status_jittery.dm"
#include "code\modules\status_conditions\status_paralyzed.dm"
#include "code\modules\status_conditions\status_sleeping.dm"
#include "code\modules\status_conditions\status_stunned.dm"
#include "code\modules\status_conditions\status_weakened.dm"
+#include "code\modules\status_conditions\definitions\status_effect_object.dm"
+#include "code\modules\status_conditions\definitions\status_effect_prone.dm"
+#include "code\modules\status_conditions\definitions\status_effect_restrained.dm"
#include "code\modules\stressors\_stressor.dm"
#include "code\modules\stressors\stressor_definitions.dm"
#include "code\modules\submaps\_submap.dm"