diff --git a/code/__DEFINES/status_effects.dm b/code/__DEFINES/status_effects.dm
new file mode 100644
index 00000000000..b214077b684
--- /dev/null
+++ b/code/__DEFINES/status_effects.dm
@@ -0,0 +1,17 @@
+
+//These are all the different status effects. Use the paths for each effect in the defines.
+
+#define BASIC_STATUS_EFFECT /datum/status_effect //Has no effect.
+
+///////////
+// BUFFS //
+///////////
+
+#define STATUS_EFFECT_SHADOW_MEND /datum/status_effect/shadow_mend //Quick, powerful heal that deals damage afterwards. Heals 15 brute/burn every second for 3 seconds.
+#define STATUS_EFFECT_VOID_PRICE /datum/status_effect/void_price //The price of healing yourself with void energy. Deals 3 brute damage every 3 seconds for 30 seconds.
+
+#define STATUS_EFFECT_INATHNEQS_ENDOWMENT /datum/status_effect/inathneqs_endowment //A 15-second invulnerability and stun absorption, granted by Inath-neq.
+
+/////////////
+// DEBUFFS //
+/////////////
diff --git a/code/_onclick/hud/alert.dm b/code/_onclick/hud/alert.dm
index f4afbe8179e..60f0df97dc1 100644
--- a/code/_onclick/hud/alert.dm
+++ b/code/_onclick/hud/alert.dm
@@ -3,7 +3,7 @@
//PUBLIC - call these wherever you want
-/mob/proc/throw_alert(category, type, severity, obj/new_master)
+/mob/proc/throw_alert(category, type, severity, obj/new_master, datum/status_effect/effect)
/* Proc to create or update an alert. Returns the alert if the alert is new or updated, 0 if it was thrown already
category is a text string. Each mob may only have one alert per category; the previous one will be replaced
@@ -43,7 +43,13 @@
alert.icon_state = "template" // We'll set the icon to the client's ui pref in reorganize_alerts()
alert.master = new_master
else
- alert.icon_state = "[initial(alert.icon_state)][severity]"
+ if(effect)
+ alert.name = effect.name
+ alert.desc = effect.desc
+ alert.icon = 'icons/mob/status_effects.dmi'
+ alert.icon_state = effect.icon_state
+ else
+ alert.icon_state = "[initial(alert.icon_state)][severity]"
alert.severity = severity
alerts[category] = alert
@@ -446,6 +452,7 @@ so as to remain in compliance with the most up-to-date laws."
if(isliving(usr))
var/mob/living/L = usr
return L.resist()
+
// PRIVATE = only edit, use, or override these if you're editing the system as a whole
// Re-render all alerts - also called in /datum/hud/show_hud() because it's needed there
diff --git a/code/controllers/subsystem/fastprocess.dm b/code/controllers/subsystem/fastprocess.dm
deleted file mode 100644
index dfe0da21bc0..00000000000
--- a/code/controllers/subsystem/fastprocess.dm
+++ /dev/null
@@ -1,33 +0,0 @@
-var/datum/subsystem/fastprocess/SSfastprocess
-
-/datum/subsystem/fastprocess
- name = "Fast Process"
- priority = 25
- flags = SS_BACKGROUND|SS_POST_FIRE_TIMING|SS_NO_INIT
- wait = 2
-
- var/list/processing = list()
- var/list/currentrun = list()
-
-/datum/subsystem/fastprocess/New()
- NEW_SS_GLOBAL(SSfastprocess)
-
-/datum/subsystem/fastprocess/stat_entry()
- ..("FP:[processing.len]")
-
-
-/datum/subsystem/fastprocess/fire(resumed = 0)
- if (!resumed)
- src.currentrun = processing.Copy()
- //cache for sanic speed (lists are references anyways)
- var/list/currentrun = src.currentrun
-
- while(currentrun.len)
- var/datum/thing = currentrun[currentrun.len]
- currentrun.len--
- if(thing)
- thing.process(wait)
- else
- SSfastprocess.processing -= thing
- if (MC_TICK_CHECK)
- return
diff --git a/code/controllers/subsystem/processing/fastprocess.dm b/code/controllers/subsystem/processing/fastprocess.dm
new file mode 100644
index 00000000000..7a46f253efe
--- /dev/null
+++ b/code/controllers/subsystem/processing/fastprocess.dm
@@ -0,0 +1,10 @@
+//Fires five times every second.
+
+var/datum/subsystem/processing/fastprocess/SSfastprocess
+/datum/subsystem/processing/fastprocess
+ name = "Fast Processing"
+ wait = 2
+ stat_tag = "FP"
+
+/datum/subsystem/processing/fastprocess/New()
+ NEW_SS_GLOBAL(SSfastprocess)
diff --git a/code/controllers/subsystem/objects.dm b/code/controllers/subsystem/processing/objects.dm
similarity index 100%
rename from code/controllers/subsystem/objects.dm
rename to code/controllers/subsystem/processing/objects.dm
diff --git a/code/controllers/subsystem/processing/processing.dm b/code/controllers/subsystem/processing/processing.dm
new file mode 100644
index 00000000000..92540ba442f
--- /dev/null
+++ b/code/controllers/subsystem/processing/processing.dm
@@ -0,0 +1,34 @@
+//Used to process objects. Fires once every second.
+
+var/datum/subsystem/processing/SSprocessing
+/datum/subsystem/processing
+ name = "Processing"
+ priority = 25
+ flags = SS_BACKGROUND|SS_POST_FIRE_TIMING|SS_NO_INIT
+ wait = 10
+
+ var/stat_tag = "P" //Used for logging
+ var/list/processing = list()
+ var/list/currentrun = list()
+
+/datum/subsystem/processing/New()
+ NEW_SS_GLOBAL(SSprocessing)
+
+/datum/subsystem/processing/stat_entry()
+ ..("[stat_tag]:[processing.len]")
+
+/datum/subsystem/processing/fire(resumed = 0)
+ if (!resumed)
+ currentrun = processing.Copy()
+ //cache for sanic speed (lists are references anyways)
+ var/list/current_run = currentrun
+
+ while(current_run.len)
+ var/datum/thing = current_run[current_run.len]
+ current_run.len--
+ if(thing)
+ thing.process(wait)
+ else
+ processing -= thing
+ if (MC_TICK_CHECK)
+ return
diff --git a/code/datums/status_effects/buffs.dm b/code/datums/status_effects/buffs.dm
new file mode 100644
index 00000000000..8a5a1543e84
--- /dev/null
+++ b/code/datums/status_effects/buffs.dm
@@ -0,0 +1,56 @@
+//Largely beneficial effects go here, even if they have drawbacks. An example is provided in Shadow Mend.
+
+/datum/status_effect/shadow_mend
+ name = "Shadow Mend"
+ desc = "Shadowy energies wrap around your wounds, sealing them at a price. After healing, you will slowly lose health every three seconds for thirty seconds."
+ id = "shadow_mend"
+ icon_state = "shadow_mend"
+ duration = 3
+
+/datum/status_effect/shadow_mend/on_apply()
+ owner.visible_message("Violet light wraps around [owner]'s body!", "Violet light wraps around your body!")
+ playsound(owner, 'sound/magic/Teleport_app.ogg', 50, 1)
+
+/datum/status_effect/shadow_mend/tick()
+ owner.adjustBruteLoss(-15)
+ owner.adjustFireLoss(-15)
+
+/datum/status_effect/shadow_mend/on_remove()
+ owner.visible_message("The violet light around [owner] glows black!", "The tendrils around you cinch tightly and reap their toll...")
+ playsound(owner, 'sound/magic/Teleport_diss.ogg', 50, 1)
+ owner.apply_status_effect(STATUS_EFFECT_VOID_PRICE)
+
+/datum/status_effect/void_price
+ name = "Void Price"
+ desc = "Black tendrils cinch tightly against you, digging wicked barbs into your flesh."
+ id = "void_price"
+ icon_state = "shadow_mend"
+ duration = 30
+ tick_interval = 3
+
+/datum/status_effect/void_price/tick()
+ owner << sound('sound/magic/Summon_Karp.ogg', volume = 25)
+ owner.adjustBruteLoss(3)
+
+
+
+/datum/status_effect/inathneqs_endowment
+ name = "Inath-neq's Endowment"
+ desc = "Adrenaline courses through you as the Cogwheel's energy shields you from all harm!"
+ id = "inathneqs_endowment"
+ icon_state = "inathneqs_endowment"
+ duration = 15
+
+/datum/status_effect/inathneqs_endowment/on_apply()
+ owner.visible_message("[owner] shines with azure light!", "You feel Inath-neq's power flow through you! You're invincible!")
+ owner.color = "#1E8CE1"
+ owner.fully_heal()
+ owner.add_stun_absorption("inathneq", 150, 1, "'s flickering blue aura momentarily intensifies!", "Inath-neq's power absorbs the stun!", " is surrounded by blue light!")
+ owner.status_flags |= GODMODE
+ animate(owner, color = initial(owner.color), time = 150, easing = EASE_IN)
+ playsound(owner, 'sound/magic/Ethereal_Enter.ogg', 50, 1)
+
+/datum/status_effect/inathneqs_endowment/on_remove()
+ owner.visible_message("The light around [owner] flickers and dissipates!", "Inath-neq's shield wavers and fails!")
+ owner.status_flags &= ~GODMODE
+ playsound(owner, 'sound/magic/Ethereal_Exit.ogg', 50, 1)
diff --git a/code/datums/status_effects/status_effect.dm b/code/datums/status_effects/status_effect.dm
new file mode 100644
index 00000000000..1754843b5fb
--- /dev/null
+++ b/code/datums/status_effects/status_effect.dm
@@ -0,0 +1,64 @@
+//Status effects are used to apply temporary or permanent effects to mobs. Mobs are aware of their status effects at all times.
+//This file contains their code, plus code for applying and removing them.
+//When making a new status effect, add a define to status_effects.dm in __DEFINES for ease of use!
+var/list/status_effects = list() //All status effects affecting literally anyone
+/datum/status_effect
+ var/name = "Curse of Mundanity"
+ var/desc = "You don't feel any different..."
+ var/id = "effect" //Used for screen alerts.
+ var/icon_state //Used for displaying the status effect.
+ var/duration = -1 //How long the status effect lasts in SECONDS. Enter -1 for an effect that never ends unless removed through some means.
+ var/tick_interval = 1 //How many seconds between ticks. Leave at 1 for every second.
+ var/mob/living/owner //The mob affected by the status effect.
+ var/cosmetic = FALSE //If the status effect only exists for flavor.
+ var/unique = TRUE //If there can be multiple status effects of this type on one mob.
+
+/datum/status_effect/New()
+ ..()
+ status_effects += src
+ spawn(1) //Give us time to set any variables
+ start_ticking()
+
+/datum/status_effect/Destroy()
+ status_effects -= src
+ ..()
+
+/datum/status_effect/proc/start_ticking()
+ if(!src)
+ return
+ on_apply()
+ owner.throw_alert(id, /obj/screen/alert, effect = src)
+ START_PROCESSING(SSprocessing, src)
+
+/datum/status_effect/process()
+ if(duration != -1)
+ duration--
+ tick_interval--
+ if(!tick_interval)
+ tick()
+ tick_interval = initial(tick_interval)
+ if(!duration && duration != -1)
+ cancel_effect()
+
+/datum/status_effect/proc/cancel_effect()
+ STOP_PROCESSING(SSprocessing, src)
+ owner.clear_alert(id)
+ on_remove()
+ qdel(src)
+
+/datum/status_effect/proc/on_apply() //Called whenever the buff is applied.
+/datum/status_effect/proc/tick() //Called every tick.
+/datum/status_effect/proc/on_remove() //Called whenever the buff expires or is removed.
+
+//////////////////
+// HELPER PROCS //
+//////////////////
+
+/mob/living/proc/apply_status_effect(effect)
+ var/datum/status_effect/S = new effect
+ for(var/datum/status_effect/S2 in status_effects)
+ if(S.unique && S2.unique && S.id == S2.id && S2.owner == src)
+ qdel(S)
+ return
+ S.owner = src
+ return 1
diff --git a/code/game/gamemodes/clock_cult/clock_scripture.dm b/code/game/gamemodes/clock_cult/clock_scripture.dm
index 0596d796731..00abf88c55b 100644
--- a/code/game/gamemodes/clock_cult/clock_scripture.dm
+++ b/code/game/gamemodes/clock_cult/clock_scripture.dm
@@ -1208,21 +1208,10 @@ Judgement: 10 servants, 100 CV, and any existing AIs are converted or destroyed
playsound(invoker, 'sound/magic/clockwork/invoke_general.ogg', 50, 0)
if(invoker.real_name == "Lucio")
clockwork_say(invoker, text2ratvar("Aww, let's break it DOWN!!"))
- var/list/affected_servants = list()
for(var/mob/living/L in range(7, invoker))
if(!is_servant_of_ratvar(L) || L.stat == DEAD)
continue
- L << "Inath-neq's power flows through you!"
- L.color = "#1E8CE1"
- L.fully_heal()
- L.add_stun_absorption("inathneq", total_duration, 2, "'s flickering blue aura momentarily intensifies!", "Inath-neq's ward absorbs the stun!", " is glowing with a flickering blue light!")
- L.status_flags |= GODMODE
- animate(L, color = initial(L.color), time = total_duration, easing = EASE_IN)
- affected_servants += L
- spawn(total_duration)
- for(var/mob/living/L in affected_servants)
- L << "You feel Inath-neq's power fade from your body."
- L.status_flags &= ~GODMODE
+ L.apply_status_effect(STATUS_EFFECT_INATHNEQS_ENDOWMENT)
return 1
diff --git a/icons/mob/status_effects.dmi b/icons/mob/status_effects.dmi
new file mode 100644
index 00000000000..d36af5785a4
Binary files /dev/null and b/icons/mob/status_effects.dmi differ
diff --git a/tgstation.dme b/tgstation.dme
index 88edc1336b6..702a97f9e2d 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -50,6 +50,7 @@
#include "code\__DEFINES\shuttles.dm"
#include "code\__DEFINES\sight.dm"
#include "code\__DEFINES\stat.dm"
+#include "code\__DEFINES\status_effects.dm"
#include "code\__DEFINES\tablecrafting.dm"
#include "code\__DEFINES\tgui.dm"
#include "code\__DEFINES\tick.dm"
@@ -139,7 +140,6 @@
#include "code\controllers\subsystem\augury.dm"
#include "code\controllers\subsystem\diseases.dm"
#include "code\controllers\subsystem\events.dm"
-#include "code\controllers\subsystem\fastprocess.dm"
#include "code\controllers\subsystem\garbage.dm"
#include "code\controllers\subsystem\icon_smooth.dm"
#include "code\controllers\subsystem\ipintel.dm"
@@ -150,7 +150,6 @@
#include "code\controllers\subsystem\minimap.dm"
#include "code\controllers\subsystem\mobs.dm"
#include "code\controllers\subsystem\npcpool.dm"
-#include "code\controllers\subsystem\objects.dm"
#include "code\controllers\subsystem\pai.dm"
#include "code\controllers\subsystem\persistence.dm"
#include "code\controllers\subsystem\pool.dm"
@@ -164,6 +163,9 @@
#include "code\controllers\subsystem\timer.dm"
#include "code\controllers\subsystem\voting.dm"
#include "code\controllers\subsystem\weather.dm"
+#include "code\controllers\subsystem\processing\fastprocess.dm"
+#include "code\controllers\subsystem\processing\objects.dm"
+#include "code\controllers\subsystem\processing\processing.dm"
#include "code\datums\action.dm"
#include "code\datums\ai_laws.dm"
#include "code\datums\beam.dm"
@@ -247,6 +249,8 @@
#include "code\datums\martial\wrestling.dm"
#include "code\datums\ruins\lavaland.dm"
#include "code\datums\ruins\space.dm"
+#include "code\datums\status_effects\buffs.dm"
+#include "code\datums\status_effects\status_effect.dm"
#include "code\datums\weather\weather.dm"
#include "code\datums\weather\weather_types.dm"
#include "code\datums\wires\airalarm.dm"