diff --git a/code/__DEFINES/status_effects.dm b/code/__DEFINES/status_effects.dm
index f451a34a8b7..9e55ae1ff00 100644
--- a/code/__DEFINES/status_effects.dm
+++ b/code/__DEFINES/status_effects.dm
@@ -1,3 +1,4 @@
+
//These are all the different status effects. Use the paths for each effect in the defines.
#define STATUS_EFFECT_MULTIPLE 0 //if it allows multiple instances of the effect
@@ -6,7 +7,7 @@
#define STATUS_EFFECT_REPLACE 2 //if it allows only one, but new instances replace
-#define BASIC_STATUS_EFFECT /datum/status_effect //Has no effect.
+#define STATUS_EFFECT_REFRESH 3 // if it only allows one, and new instances just instead refresh the timer
///////////
// BUFFS //
@@ -16,6 +17,8 @@
#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_EXERCISED /datum/status_effect/exercised //Prevents heart disease
+#define STATUS_EFFECT_REGENERATIVE_CORE /datum/status_effect/regenerative_core
+
//#define STATUS_EFFECT_VANGUARD /datum/status_effect/vanguard_shield //Grants temporary stun absorption, but will stun the user based on how many stuns they absorbed.
//#define STATUS_EFFECT_INATHNEQS_ENDOWMENT /datum/status_effect/inathneqs_endowment //A 15-second invulnerability and stun absorption, granted by Inath-neq.
//#define STATUS_EFFECT_WRAITHSPECS /datum/status_effect/wraith_spectacles
diff --git a/code/datums/status_effects/buffs.dm b/code/datums/status_effects/buffs.dm
index 25673377bee..378ee0eb676 100644
--- a/code/datums/status_effects/buffs.dm
+++ b/code/datums/status_effects/buffs.dm
@@ -55,6 +55,7 @@
. = ..()
if(.)
if(ishuman(owner))
+ owner.status_flags |= IGNORESLOWDOWN
var/mob/living/carbon/human/H = owner
for(var/X in H.bodyparts)
var/obj/item/organ/external/BP = X
@@ -65,8 +66,7 @@
H.dna.species.clone_mod *= 0.1
H.dna.species.stamina_mod *= 0.1
add_attack_logs(owner, owner, "gained blood-drunk stun immunity", ATKLOG_ALL)
- var/status = CANSTUN | CANWEAKEN | CANPARALYSE | IGNORESLOWDOWN
- owner.status_flags &= ~status
+ owner.add_stun_absorption("blooddrunk", INFINITY, 4)
owner.playsound_local(get_turf(owner), 'sound/effects/singlebeat.ogg', 40, 1)
/datum/status_effect/blooddrunk/on_remove()
@@ -81,7 +81,9 @@
H.dna.species.clone_mod *= 10
H.dna.species.stamina_mod *= 10
add_attack_logs(owner, owner, "lost blood-drunk stun immunity", ATKLOG_ALL)
- owner.status_flags |= CANSTUN | CANWEAKEN | CANPARALYSE | IGNORESLOWDOWN
+ owner.status_flags &= ~IGNORESLOWDOWN
+ if(islist(owner.stun_absorption) && owner.stun_absorption["blooddrunk"])
+ owner.stun_absorption -= "blooddrunk"
/datum/status_effect/exercised
id = "Exercised"
@@ -96,3 +98,23 @@
/datum/status_effect/exercised/Destroy()
. = ..()
STOP_PROCESSING(SSprocessing, src)
+
+/obj/screen/alert/status_effect/regenerative_core
+ name = "Reinforcing Tendrils"
+ desc = "You can move faster than your broken body could normally handle!"
+ icon_state = "regenerative_core"
+ name = "Regenerative Core Tendrils"
+
+/datum/status_effect/regenerative_core
+ id = "Regenerative Core"
+ duration = 1 MINUTES
+ status_type = STATUS_EFFECT_REPLACE
+ alert_type = /obj/screen/alert/status_effect/regenerative_core
+
+/datum/status_effect/regenerative_core/on_apply()
+ owner.status_flags |= IGNORESLOWDOWN
+ owner.revive()
+ return TRUE
+
+/datum/status_effect/regenerative_core/on_remove()
+ owner.status_flags &= ~IGNORESLOWDOWN
\ No newline at end of file
diff --git a/code/datums/status_effects/neutral.dm b/code/datums/status_effects/neutral.dm
index 8f5023e4a74..b2365ad4e1c 100644
--- a/code/datums/status_effects/neutral.dm
+++ b/code/datums/status_effects/neutral.dm
@@ -43,5 +43,5 @@
duration = 40
alert_type = null
-/datum/status_effect/high_five/on_timeout()
- owner.visible_message("[owner] was left hanging....")
+/datum/status_effect/high_five/on_remove()
+ owner.visible_message("[owner] was left hanging....")
\ No newline at end of file
diff --git a/code/datums/status_effects/status_effect.dm b/code/datums/status_effects/status_effect.dm
index dd478a8f64c..8ed41c559b5 100644
--- a/code/datums/status_effects/status_effect.dm
+++ b/code/datums/status_effects/status_effect.dm
@@ -1,4 +1,3 @@
-
//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!
@@ -52,13 +51,11 @@
tick()
tick_interval = world.time + initial(tick_interval)
if(duration != -1 && duration < world.time)
- on_timeout()
qdel(src)
/datum/status_effect/proc/on_apply() //Called whenever the buff is applied; returning FALSE will cause it to autoremove itself.
return TRUE
/datum/status_effect/proc/tick() //Called every tick.
-/datum/status_effect/proc/on_timeout()//called when a buff times out
/datum/status_effect/proc/on_remove() //Called whenever the buff expires or is removed; do note that at the point this is called, it is out of the owner's status_effects but owner is not yet null
/datum/status_effect/proc/be_replaced() //Called instead of on_remove when a status effect is replaced by itself or when a status effect with on_remove_on_mob_delete = FALSE has its mob deleted
owner.clear_alert(id)
@@ -66,6 +63,19 @@
owner = null
qdel(src)
+/datum/status_effect/proc/refresh()
+ var/original_duration = initial(duration)
+ if(original_duration == -1)
+ return
+ duration = world.time + original_duration
+
+//clickdelay/nextmove modifiers!
+/datum/status_effect/proc/nextmove_modifier()
+ return 1
+
+/datum/status_effect/proc/nextmove_adjust()
+ return 0
+
////////////////
// ALERT HOOK //
////////////////
@@ -87,6 +97,9 @@
if(S.id == initial(S1.id) && S.status_type)
if(S.status_type == STATUS_EFFECT_REPLACE)
S.be_replaced()
+ else if(S.status_type == STATUS_EFFECT_REFRESH)
+ S.refresh()
+ return
else
return
var/list/arguments = args.Copy()
@@ -117,4 +130,108 @@
var/datum/status_effect/S1 = effect
for(var/datum/status_effect/S in status_effects)
if(initial(S1.id) == S.id)
- . += S
\ No newline at end of file
+ . += S
+
+//////////////////////
+// STACKING EFFECTS //
+//////////////////////
+
+/datum/status_effect/stacking
+ id = "stacking_base"
+ duration = -1 //removed under specific conditions
+ alert_type = null
+ var/stacks = 0 //how many stacks are accumulated, also is # of stacks that target will have when first applied
+ var/delay_before_decay //deciseconds until ticks start occuring, which removes stacks (first stack will be removed at this time plus tick_interval)
+ tick_interval = 10 //deciseconds between decays once decay starts
+ var/stack_decay = 1 //how many stacks are lost per tick (decay trigger)
+ var/stack_threshold //special effects trigger when stacks reach this amount
+ var/max_stacks //stacks cannot exceed this amount
+ var/consumed_on_threshold = TRUE //if status should be removed once threshold is crossed
+ var/threshold_crossed = FALSE //set to true once the threshold is crossed, false once it falls back below
+ var/overlay_file
+ var/underlay_file
+ var/overlay_state // states in .dmi must be given a name followed by a number which corresponds to a number of stacks. put the state name without the number in these state vars
+ var/underlay_state // the number is concatonated onto the string based on the number of stacks to get the correct state name
+ var/mutable_appearance/status_overlay
+ var/mutable_appearance/status_underlay
+
+/datum/status_effect/stacking/proc/threshold_cross_effect() //what happens when threshold is crossed
+
+/datum/status_effect/stacking/proc/stacks_consumed_effect() //runs if status is deleted due to threshold being crossed
+
+/datum/status_effect/stacking/proc/fadeout_effect() //runs if status is deleted due to being under one stack
+
+/datum/status_effect/stacking/proc/stack_decay_effect() //runs every time tick() causes stacks to decay
+
+/datum/status_effect/stacking/proc/on_threshold_cross()
+ threshold_cross_effect()
+ if(consumed_on_threshold)
+ stacks_consumed_effect()
+ qdel(src)
+
+/datum/status_effect/stacking/proc/on_threshold_drop()
+
+/datum/status_effect/stacking/proc/can_have_status()
+ return owner.stat != DEAD
+
+/datum/status_effect/stacking/proc/can_gain_stacks()
+ return owner.stat != DEAD
+
+/datum/status_effect/stacking/tick()
+ if(!can_have_status())
+ qdel(src)
+ else
+ add_stacks(-stack_decay)
+ stack_decay_effect()
+
+/datum/status_effect/stacking/proc/add_stacks(stacks_added)
+ if(stacks_added > 0 && !can_gain_stacks())
+ return FALSE
+ owner.cut_overlay(status_overlay)
+ owner.underlays -= status_underlay
+ stacks += stacks_added
+ if(stacks > 0)
+ if(stacks >= stack_threshold && !threshold_crossed) //threshold_crossed check prevents threshold effect from occuring if changing from above threshold to still above threshold
+ threshold_crossed = TRUE
+ on_threshold_cross()
+ else if(stacks < stack_threshold && threshold_crossed)
+ threshold_crossed = FALSE //resets threshold effect if we fall below threshold so threshold effect can trigger again
+ on_threshold_drop()
+ if(stacks_added > 0)
+ tick_interval += delay_before_decay //refreshes time until decay
+ stacks = min(stacks, max_stacks)
+ status_overlay.icon_state = "[overlay_state][stacks]"
+ status_underlay.icon_state = "[underlay_state][stacks]"
+ owner.add_overlay(status_overlay)
+ owner.underlays += status_underlay
+ else
+ fadeout_effect()
+ qdel(src) //deletes status if stacks fall under one
+
+/datum/status_effect/stacking/on_creation(mob/living/new_owner, stacks_to_apply)
+ ..()
+ add_stacks(stacks_to_apply)
+
+/datum/status_effect/stacking/on_apply()
+ if(!can_have_status())
+ return FALSE
+ status_overlay = mutable_appearance(overlay_file, "[overlay_state][stacks]")
+ status_underlay = mutable_appearance(underlay_file, "[underlay_state][stacks]")
+ var/icon/I = icon(owner.icon, owner.icon_state, owner.dir)
+ var/icon_height = I.Height()
+ status_overlay.pixel_x = -owner.pixel_x
+ status_overlay.pixel_y = FLOOR(icon_height * 0.25, 1)
+ status_overlay.transform = matrix() * (icon_height/world.icon_size) //scale the status's overlay size based on the target's icon size
+ status_underlay.pixel_x = -owner.pixel_x
+ status_underlay.transform = matrix() * (icon_height/world.icon_size) * 3
+ status_underlay.alpha = 40
+ owner.add_overlay(status_overlay)
+ owner.underlays += status_underlay
+ return ..()
+
+/datum/status_effect/stacking/Destroy()
+ if(owner)
+ owner.cut_overlay(status_overlay)
+ owner.underlays -= status_underlay
+ QDEL_NULL(status_overlay)
+ return ..()
\ No newline at end of file
diff --git a/code/modules/mining/equipment/regenerative_core.dm b/code/modules/mining/equipment/regenerative_core.dm
index 9d56d8ccd6e..4b5e5c911da 100644
--- a/code/modules/mining/equipment/regenerative_core.dm
+++ b/code/modules/mining/equipment/regenerative_core.dm
@@ -84,7 +84,7 @@
else
to_chat(user, "You start to smear [src] on yourself. Disgusting tendrils hold you together and allow you to keep moving, but for how long?")
feedback_add_details("hivelord_core","[src.type]|used|self")
- H.revive()
+ H.apply_status_effect(STATUS_EFFECT_REGENERATIVE_CORE)
user.drop_item()
qdel(src)
diff --git a/code/modules/mob/living/status_procs.dm b/code/modules/mob/living/status_procs.dm
index ad6d00d88aa..0e1a19da662 100644
--- a/code/modules/mob/living/status_procs.dm
+++ b/code/modules/mob/living/status_procs.dm
@@ -576,8 +576,10 @@
"visible_message" = message, "self_message" = self_message, "examine_message" = examine_message)
/mob/living/proc/absorb_stun(amount, ignoring_flag_presence)
- if(!amount || amount <= 0 || stat || ignoring_flag_presence || !islist(stun_absorption))
+ if(amount < 0 || stat || ignoring_flag_presence || !islist(stun_absorption))
return FALSE
+ if(!amount)
+ amount = 0
var/priority_absorb_key
var/highest_priority
for(var/i in stun_absorption)
@@ -585,12 +587,13 @@
priority_absorb_key = stun_absorption[i]
highest_priority = priority_absorb_key["priority"]
if(priority_absorb_key)
- if(priority_absorb_key["visible_message"] || priority_absorb_key["self_message"])
- if(priority_absorb_key["visible_message"] && priority_absorb_key["self_message"])
- visible_message("[src][priority_absorb_key["visible_message"]]", "[priority_absorb_key["self_message"]]")
- else if(priority_absorb_key["visible_message"])
- visible_message("[src][priority_absorb_key["visible_message"]]")
- else if(priority_absorb_key["self_message"])
- to_chat(src, "[priority_absorb_key["self_message"]]")
- priority_absorb_key["stuns_absorbed"] += amount
+ if(amount) //don't spam up the chat for continuous stuns
+ if(priority_absorb_key["visible_message"] || priority_absorb_key["self_message"])
+ if(priority_absorb_key["visible_message"] && priority_absorb_key["self_message"])
+ visible_message("[src][priority_absorb_key["visible_message"]]", "[priority_absorb_key["self_message"]]")
+ else if(priority_absorb_key["visible_message"])
+ visible_message("[src][priority_absorb_key["visible_message"]]")
+ else if(priority_absorb_key["self_message"])
+ to_chat(src, "[priority_absorb_key["self_message"]]")
+ priority_absorb_key["stuns_absorbed"] += amount
return TRUE
\ No newline at end of file
diff --git a/icons/mob/screen_alert.dmi b/icons/mob/screen_alert.dmi
index 2b29cad5b0c..2fe2c7cd21c 100644
Binary files a/icons/mob/screen_alert.dmi and b/icons/mob/screen_alert.dmi differ