diff --git a/code/datums/components/shielded.dm b/code/datums/components/shielded.dm
new file mode 100644
index 0000000000..405e7fc064
--- /dev/null
+++ b/code/datums/components/shielded.dm
@@ -0,0 +1,191 @@
+/datum/component/shielded
+ dupe_mode = COMPONENT_DUPE_ALLOWED
+ can_transfer = TRUE
+ var/charges = 3
+ var/max_charges = 3
+ var/recharge_delay = 20 SECONDS //How long after we've been attacked before we can start recharging.
+ var/recharge_rate = 1 //How quickly the shield recharges once it starts charging. Can be a decimal. set to zero to disable.
+ var/last_time_used //Last time the shield attempted to stop an attack.
+ var/accepted_slots
+ var/shield_state = "shield-old" //the state of the shield overlay.
+ var/broken_state //null by default.
+ var/recharge_sound = 'sound/magic/charge.ogg'
+ var/recharge_end_sound = 'sound/machines/ding.ogg'
+ var/mob/living/holder //who is currently benefiting from the shield.
+ var/dissipating = FALSE //Is this shield meant to dissipate over time instead of recharging.
+ var/del_on_overload = FALSE //will delete itself once it has no charges left.
+
+/datum/component/shielded/Initialize(current, max = 3, delay = 20 SECONDS, rate = 1, slots, state = "shield-old", broken, \
+ sound = 'sound/magic/charge.ogg', end_sound = 'sound/machines/ding.ogg', diss = FALSE, del_overload = FALSE)
+ var/isitem = isitem(parent)
+ if(!isitem && !isliving(parent))
+ return COMPONENT_INCOMPATIBLE
+ max_charges = max
+ charges = !isnull(current) ? current : max_charges
+ recharge_delay = delay
+ recharge_rate = rate
+ accepted_slots = slots
+ shield_state = state
+ broken_state = broken
+ recharge_sound = sound
+ recharge_end_sound = end_sound
+ dissipating = diss
+ del_on_overload = del_overload
+ if(dissipating && recharge_rate > 0)
+ recharge_rate = -recharge_rate
+ if(recharge_delay && recharge_rate && (charges < max_charges || dissipating))
+ START_PROCESSING(SSdcs, src)
+
+/datum/component/shielded/RegisterWithParent()
+ . = ..()
+ if(isitem(parent))
+ RegisterSignal(parent, COMSIG_ITEM_EQUIPPED, .proc/on_equip)
+ RegisterSignal(parent, COMSIG_ITEM_DROPPED, .proc/on_drop)
+ else //it's a mob
+ var/mob/living/L = parent
+ RegisterSignal(L, COMSIG_LIVING_RUN_BLOCK, .proc/living_block)
+ holder = L
+ var/to_add = charges >= 1 ? shield_state : broken_state
+ if(to_add)
+ var/mutable_appearance/M = mutable_appearance('icons/effects/effects.dmi', to_add)
+ M.layer = (L.layer > MOB_LAYER ? L.layer : MOB_LAYER) + 0.01
+ holder.add_overlay(M, TRUE)
+
+/datum/component/shielded/UnregisterFromParent()
+ . = ..()
+ if(parent != holder) //not a mob, thus an item.
+ UnregisterSignal(parent, list(COMSIG_ITEM_RUN_BLOCK,COMSIG_ITEM_CHECK_BLOCK,COMSIG_ITEM_EQUIPPED,COMSIG_ITEM_DROPPED))
+ if(holder)
+ UnregisterSignal(holder, list(COMSIG_LIVING_RUN_BLOCK, COMSIG_LIVING_GET_BLOCKING_ITEMS))
+ var/to_remove = charges >= 1 ? shield_state : broken_state
+ if(to_remove)
+ holder.cut_overlay(mutable_appearance('icons/effects/effects.dmi', to_remove), TRUE)
+ holder = null
+
+/datum/component/shielded/process()
+ if(world.time < last_time_used && !dissipating)
+ return
+ var/old_charges = charges
+ charges = CLAMP(charges + recharge_rate, 0, max_charges)
+ if(round(old_charges) >= round(charges)) //only send outputs if it effectively gained at least one charge
+ return
+ var/sound = recharge_sound
+ if(dissipating ? !charges : charges == max_charges )
+ STOP_PROCESSING(SSdcs, src)
+ sound = recharge_end_sound
+ if(parent && sound)
+ playsound(parent, sound, 50, 1)
+ if(charges < 1 && del_on_overload)
+ if(holder)
+ holder.visible_message("[holder]'s shield overloads!")
+ qdel(src)
+ return
+ if(holder && (old_charges < 1 && charges >= 1) || (!del_on_overload && old_charges >= 1 && charges < 1))
+ update_shield_overlay(charges < 1)
+
+/datum/component/shielded/proc/adjust_charges(amount)
+ var/old_charges = charges
+ charges = CLAMP(charges + amount, 0, max_charges)
+ if(recharge_delay && recharge_rate && (dissipating ? !charges : charges == max_charges))
+ STOP_PROCESSING(SSdcs, src)
+ if(charges < 1 && del_on_overload)
+ if(holder)
+ holder.visible_message("[holder]'s shield overloads!")
+ qdel(src)
+ return
+ if(holder && (old_charges < 1 && charges >= 1) || (!del_on_overload && old_charges >= 1 && charges < 1))
+ update_shield_overlay(charges < 1)
+
+/datum/component/shielded/proc/update_shield_overlay(broken)
+ if(!holder)
+ return
+ var/to_remove = broken ? shield_state : broken_state
+ var/to_add = broken ? broken_state : shield_state
+ if(to_remove)
+ holder.cut_overlay(mutable_appearance('icons/effects/effects.dmi', to_remove), TRUE)
+ if(to_add)
+ var/mutable_appearance/M = mutable_appearance('icons/effects/effects.dmi', to_add)
+ M.layer = (holder.layer > MOB_LAYER ? holder.layer : MOB_LAYER) + 0.01
+ holder.add_overlay(M, TRUE)
+
+/datum/component/shielded/proc/on_equip(obj/item/source, mob/living/equipper, slot)
+ if(!(accepted_slots & slotdefine2slotbit(slot)))
+ return
+ holder = equipper
+ RegisterSignal(parent, COMSIG_ITEM_RUN_BLOCK, .proc/on_run_block)
+ RegisterSignal(parent, COMSIG_ITEM_CHECK_BLOCK, .proc/on_check_block)
+ RegisterSignal(equipper, COMSIG_LIVING_GET_BLOCKING_ITEMS, .proc/include_shield)
+ var/to_add = charges >= 1 ? shield_state : broken_state
+ if(to_add)
+ var/mutable_appearance/M = mutable_appearance('icons/effects/effects.dmi', to_add)
+ M.layer = (holder.layer > MOB_LAYER ? holder.layer : MOB_LAYER) + 0.01
+ equipper.add_overlay(M, TRUE)
+
+/datum/component/shielded/proc/on_drop(obj/item/source, mob/dropper)
+ if(holder == dropper)
+ UnregisterSignal(holder, COMSIG_LIVING_GET_BLOCKING_ITEMS)
+ UnregisterSignal(parent, list(COMSIG_ITEM_RUN_BLOCK, COMSIG_ITEM_CHECK_BLOCK))
+ var/to_remove = charges >= 1 ? shield_state : broken_state
+ if(to_remove)
+ holder.cut_overlay(mutable_appearance('icons/effects/effects.dmi', to_remove), TRUE)
+ holder = null
+
+/datum/component/shielded/proc/include_shield(mob/source, list/items)
+ items += parent
+
+/datum/component/shielded/proc/on_run_block(obj/item/source, mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return)
+ if(block_return[BLOCK_RETURN_NORMAL_BLOCK_CHANCE] >= 100) //already blocked by another shielded item, don't do anything.
+ block_return[BLOCK_RETURN_BLOCK_CAPACITY] += round(charges)
+ return BLOCK_NONE
+ last_time_used = world.time + recharge_delay
+ if(charges < 1)
+ return BLOCK_NONE
+ var/datum/effect_system/spark_spread/s = new
+ s.set_up(2, 1, source)
+ s.start()
+ owner.visible_message("[holder]'s shields deflect [attack_text] in a shower of sparks!")
+ charges--
+ var/rounded_charges = round(charges)
+ if(recharge_delay && recharge_rate && !dissipating)
+ START_PROCESSING(SSdcs, src)
+ if(charges < 1)
+ owner.visible_message("[holder]'s shield overloads!")
+ if(del_on_overload)
+ qdel(src)
+ else
+ update_shield_overlay(TRUE)
+ block_return[BLOCK_RETURN_NORMAL_BLOCK_CHANCE] = 100
+ block_return[BLOCK_RETURN_BLOCK_CAPACITY] += rounded_charges
+ return BLOCK_SUCCESS | BLOCK_PHYSICAL_EXTERNAL
+
+/datum/component/shielded/proc/on_check_block(obj/item/source, mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return)
+ if(charges >= 1)
+ block_return[BLOCK_RETURN_NORMAL_BLOCK_CHANCE] = 100
+ block_return[BLOCK_RETURN_BLOCK_CAPACITY] += round(charges)
+
+/datum/component/shielded/proc/living_block(mob/living/source, real_attack, object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, return_list)
+ if(!real_attack)
+ if(charges >= 1)
+ return_list[BLOCK_RETURN_NORMAL_BLOCK_CHANCE] = 100
+ return_list[BLOCK_RETURN_BLOCK_CAPACITY] = round(charges)
+ return
+ last_time_used = world.time + recharge_delay
+ if(charges < 1)
+ return BLOCK_NONE
+ var/datum/effect_system/spark_spread/s = new
+ s.set_up(2, 1, source)
+ s.start()
+ source.visible_message("[source]'s shields deflect [attack_text] in a shower of sparks!")
+ charges--
+ var/rounded_charges = round(charges)
+ if(recharge_delay && recharge_rate && !dissipating)
+ START_PROCESSING(SSdcs, src)
+ if(charges < 1)
+ source.visible_message("[source]'s shield overloads!")
+ if(del_on_overload)
+ qdel(src)
+ else
+ update_shield_overlay(TRUE)
+ return_list[BLOCK_RETURN_NORMAL_BLOCK_CHANCE] = 100
+ return_list[BLOCK_RETURN_BLOCK_CAPACITY] += rounded_charges
+ return BLOCK_SUCCESS | BLOCK_PHYSICAL_EXTERNAL
diff --git a/code/datums/elements/shielded.dm b/code/datums/elements/shielded.dm
deleted file mode 100644
index b9c8c094f6..0000000000
--- a/code/datums/elements/shielded.dm
+++ /dev/null
@@ -1,210 +0,0 @@
-/datum/element/shielded
- element_flags = ELEMENT_BESPOKE|ELEMENT_DETACH
- id_arg_index = 3
- var/max_charges = 3
- var/recharge_delay = 20 SECONDS //How long after we've been shot before we can start recharging.
- var/recharge_rate = 1 //How quickly the shield recharges once it starts charging. Can be a decimal. set to zero to disable.
- var/accepted_slots
- var/shield_state = "shield-old" //the state of the shield overlay.
- var/broken_state //null
- var/recharge_sound = 'sound/magic/charge.ogg'
- var/recharge_end_sound = 'sound/machines/ding.ogg'
- var/list/charges_per_atom = list() //How many charges each atom has.
- var/list/last_use_per_atom = list() //Last time an atom has deflected (or failed due to no charges) an attack.
- var/list/overlay_per_mob = list() //List of mutable overlays per atom.
- var/list/shields_per_mob = list() //number of shields this guy is on, damn.
-
-/datum/element/shielded/Attach(datum/target, current, _max = 3, _delay = 20 SECONDS, _rate = 1, _slots, _state = "shield-old", _broken, _sound = 'sound/magic/charge.ogg', _end_sound = 'sound/machines/ding.ogg')
- . = ..()
- var/isitem = isitem(target)
- if(. == ELEMENT_INCOMPATIBLE || (!isitem && !isliving(target)))
- return ELEMENT_INCOMPATIBLE
- max_charges = _max
- recharge_delay = _delay
- recharge_rate = _rate
- accepted_slots = _slots
- shield_state = _state
- broken_state = _broken
- recharge_sound = 'sound/magic/charge.ogg'
- recharge_end_sound = 'sound/machines/ding.ogg'
- if(isitem)
- RegisterSignal(target, COMSIG_ITEM_RUN_BLOCK, .proc/on_run_block)
- RegisterSignal(target, COMSIG_ITEM_CHECK_BLOCK, .proc/on_check_block)
- RegisterSignal(target, COMSIG_ITEM_EQUIPPED, .proc/on_equip)
- RegisterSignal(target, COMSIG_ITEM_DROPPED, .proc/on_drop)
- else
- RegisterSignal(target, COMSIG_LIVING_RUN_BLOCK, .proc/living_block)
- var/prior_shields = shields_per_mob[target]
- if(prior_shields)
- if(!islist(prior_shields))
- shields_per_mob[target] = list(prior_shields, target)
- else
- prior_shields += target
- else
- var/mob/living/L = target
- var/mutable_appearance/M = mutable_appearance('icons/effects/effects.dmi', shield_state, MOB_LAYER + 0.01)
- overlay_per_mob[L] = M
- L.add_overlay(M, TRUE)
- shields_per_mob[L] = L
- charges_per_atom[target] = !isnull(current) ? current : max_charges
- last_use_per_atom[target] = 0
- if(recharge_delay)
- START_PROCESSING(SSdcs, src)
-
-/datum/element/shielded/Detach(atom/target)
- var/mob/living/L
- if(isitem(target))
- UnregisterSignal(target, list(COMSIG_ITEM_RUN_BLOCK,COMSIG_ITEM_CHECK_BLOCK,COMSIG_ITEM_EQUIPPED,COMSIG_ITEM_DROPPED))
- L = isliving(target.loc) ? target.loc : null
- else
- UnregisterSignal(target, COMSIG_LIVING_RUN_BLOCK)
- L = src
- if(L)
- var/list/shields = shields_per_mob[L]
- if(!shields)
- return ..()
- if(shields == target) //nothing left.
- shields_per_mob -= L
- var/mutable_appearance/M = overlay_per_mob[L]
- L.cut_overlay(M, TRUE)
- overlay_per_mob -= L
- UnregisterSignal(target, COMSIG_LIVING_GET_BLOCKING_ITEMS)
- else //more layers of shielding.
- shields -= target
- if(length(shields) == 1)
- shields_per_mob[L] = shields[1]
- charges_per_atom -= target
- last_use_per_atom -= target
- if(recharge_delay && !length(charges_per_atom)) //nothing left to process.
- STOP_PROCESSING(SSdcs, src)
- return ..()
-
-/datum/element/shielded/process()
- var/list/checked = list()
- for(var/i in last_use_per_atom)
- var/atom/movable/A = i
- recharge(A, recharge_rate, checked)
-
-/datum/element/shielded/proc/recharge(atom/movable/A, amount, list/checked = list(), forced = FALSE)
- var/old_charges = charges_per_atom[A]
- if(old_charges >= max_charges || (!forced && world.time < last_use_per_atom[A]))
- return
- var/new_charges = CLAMP(old_charges + recharge_rate, 0, max_charges)
- charges_per_atom[A] = new_charges
- if(round(old_charges) >= round(new_charges)) //only send outputs if it effectively gained at least one charge
- return
- var/mob/living/L
- var/skip_in = FALSE
- if(isitem(A))
- L = isliving(A.loc) ? A.loc : null
- else
- L = A
- skip_in = TRUE
- if(L && checked[L])
- return
- playsound(A, recharge_sound, 50, 1)
- if(new_charges == max_charges)
- playsound(A, recharge_sound, 50, 1)
- if(L && (skip_in || (A in shields_per_mob[L])))
- var/mutable_appearance/M = overlay_per_mob[L]
- M.icon_state = shield_state
- checked[L] = TRUE
-
-/datum/element/shielded/proc/on_equip(obj/item/source, mob/equipper, slot)
- if(!(accepted_slots & slotdefine2slotbit(slot)))
- return
- var/list/shields = shields_per_mob[equipper]
- if(!shields) //They have none
- RegisterSignal(equipper, COMSIG_LIVING_GET_BLOCKING_ITEMS, .proc/get_shields)
- shields_per_mob[equipper] = source
- var/mutable_appearance/M = mutable_appearance('icons/effects/effects.dmi', charges_per_atom[source] ? shield_state : broken_state, MOB_LAYER + 0.01)
- overlay_per_mob[equipper] = M
- equipper.add_overlay(M, TRUE)
- else if(!islist(shields)) //They have one
- if(shields == equipper)
- RegisterSignal(equipper, COMSIG_LIVING_GET_BLOCKING_ITEMS, .proc/get_shields)
- shields_per_mob[equipper] = list(shields, source)
- else //They have more.
- shields += source
-
-/datum/element/shielded/proc/on_drop(obj/item/source, mob/dropper)
- var/list/shields = shields_per_mob[dropper]
- if(!shields)
- return
- if(shields == source)
- UnregisterSignal(dropper, COMSIG_LIVING_GET_BLOCKING_ITEMS)
- var/mutable_appearance/M = overlay_per_mob[dropper]
- dropper.cut_overlay(M, TRUE)
- overlay_per_mob -= dropper
- shields_per_mob -= dropper
- else
- shields -= source
- if(length(shields) == 1)
- shields_per_mob[dropper] = shields[1]
-
-/datum/element/shielded/proc/get_shields(mob/source, list/items)
- items += shields_per_mob[source]
-
-/datum/element/shielded/proc/on_run_block(obj/item/source, mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return)
- if(block_return[BLOCK_RETURN_NORMAL_BLOCK_CHANCE] >= 100) //already blocked by another shielded item, don't do anything.
- block_return[BLOCK_RETURN_BLOCK_CAPACITY] += round(charges_per_atom[source])
- return BLOCK_NONE
- last_use_per_atom[source] = world.time + recharge_delay
- if(charges_per_atom[source] < 1)
- return BLOCK_NONE
- var/datum/effect_system/spark_spread/s = new
- s.set_up(2, 1, source)
- s.start()
- owner.visible_message("[owner]'s shields deflect [attack_text] in a shower of sparks!")
- var/charges_left = --charges_per_atom[source]
- if(charges_left < 1)
- var/list/shields = shields_per_mob[owner]
- var/vis_change = TRUE
- if(istype(shields))
- for(var/A in shields)
- if(charges_per_atom >= 1)
- vis_change = FALSE
- break
- owner.visible_message("[owner]'s shield overloads!")
- if(vis_change)
- var/mutable_appearance/M = overlay_per_mob[owner]
- M.icon_state = broken_state
- block_return[BLOCK_RETURN_NORMAL_BLOCK_CHANCE] = 100
- block_return[BLOCK_RETURN_BLOCK_CAPACITY] += charges_left
- return BLOCK_SUCCESS | BLOCK_PHYSICAL_EXTERNAL //it's an energy field surrounding you after all.
-
-/datum/element/shielded/proc/on_check_block(obj/item/source, mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return)
- if(charges_per_atom[source] < 1)
- return
- block_return[BLOCK_RETURN_NORMAL_BLOCK_CHANCE] = 100
- block_return[BLOCK_RETURN_BLOCK_CAPACITY] += round(charges_per_atom[source])
-
-/datum/element/shielded/proc/living_block(mob/living/source, real_attack, object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, return_list)
- if(!real_attack)
- if(charges_per_atom[source] >= 1)
- return_list[BLOCK_RETURN_NORMAL_BLOCK_CHANCE] = 100
- return_list[BLOCK_RETURN_BLOCK_CAPACITY] = round(charges_per_atom[source])
- return
- last_use_per_atom[source] = world.time + recharge_delay
- if(charges_per_atom[source] < 1)
- return BLOCK_NONE
- var/datum/effect_system/spark_spread/s = new
- s.set_up(2, 1, source)
- s.start()
- source.visible_message("[source]'s shields deflect [attack_text] in a shower of sparks!")
- var/charges_left = --charges_per_atom[source]
- if(charges_left < 1)
- var/list/shields = shields_per_mob[source]
- var/vis_change = TRUE
- if(istype(shields))
- for(var/A in shields)
- if(charges_per_atom >= 1)
- vis_change = FALSE
- break
- source.visible_message("[source]'s shield overloads!")
- if(vis_change)
- var/mutable_appearance/M = overlay_per_mob[source]
- M.icon_state = broken_state
- return_list[BLOCK_RETURN_NORMAL_BLOCK_CHANCE] = 100
- return_list[BLOCK_RETURN_BLOCK_CAPACITY] += charges_left
- return BLOCK_SUCCESS | BLOCK_PHYSICAL_EXTERNAL
diff --git a/code/modules/clothing/spacesuits/hardsuit.dm b/code/modules/clothing/spacesuits/hardsuit.dm
index 1b2d24e36a..2fe3f3cbf6 100644
--- a/code/modules/clothing/spacesuits/hardsuit.dm
+++ b/code/modules/clothing/spacesuits/hardsuit.dm
@@ -761,7 +761,7 @@
/obj/item/clothing/suit/space/hardsuit/shielded/ComponentInitialize()
. = ..()
- AddElement(/datum/element/shielded, current_charges, max_charges, recharge_delay, recharge_rate, ITEM_SLOT_OCLOTHING, shield_state)
+ AddComponent(/datum/component/shielded, current_charges, max_charges, recharge_delay, recharge_rate, ITEM_SLOT_OCLOTHING, shield_state)
/obj/item/clothing/head/helmet/space/hardsuit/shielded
resistance_flags = FIRE_PROOF | ACID_PROOF
diff --git a/code/modules/clothing/suits/wiz_robe.dm b/code/modules/clothing/suits/wiz_robe.dm
index 6c61cf1255..dabbaabb80 100644
--- a/code/modules/clothing/suits/wiz_robe.dm
+++ b/code/modules/clothing/suits/wiz_robe.dm
@@ -238,7 +238,7 @@
if(!istype(W))
to_chat(user, "The rune can only be used on battlemage armour!")
return
- var/datum/element/shielded/S = SSdcs.GetElement(list(/datum/element/shielded, 0, W.max_charges, W.recharge_delay, W.recharge_rate, ITEM_SLOT_OCLOTHING, W.shield_state))
- S.recharge(W, 8, forced = TRUE)
- to_chat(user, "You charge \the [W]. It can now absorb [S.charges_per_atom[W]] hits.")
+ var/datum/component/shielded/S = GetComponent(/datum/component/shielded)
+ S.adjust_charges(8)
+ to_chat(user, "You charge \the [W]. It can now absorb [S.charges] hits.")
qdel(src)
diff --git a/tgstation.dme b/tgstation.dme
index 593586e98e..bc9a846110 100755
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -408,6 +408,7 @@
#include "code\datums\components\remote_materials.dm"
#include "code\datums\components\riding.dm"
#include "code\datums\components\rotation.dm"
+#include "code\datums\components\shielded.dm"
#include "code\datums\components\shrapnel.dm"
#include "code\datums\components\shrink.dm"
#include "code\datums\components\sizzle.dm"
@@ -514,7 +515,6 @@
#include "code\datums\elements\ghost_role_eligibility.dm"
#include "code\datums\elements\mob_holder.dm"
#include "code\datums\elements\polychromic.dm"
-#include "code\datums\elements\shielded.dm"
#include "code\datums\elements\spellcasting.dm"
#include "code\datums\elements\swimming.dm"
#include "code\datums\elements\sword_point.dm"