From 824904844ce5ee354a7f81dc723599041f0962ec Mon Sep 17 00:00:00 2001 From: Ghommie <42542238+Ghommie@users.noreply.github.com> Date: Tue, 21 Apr 2020 19:51:44 +0200 Subject: [PATCH] Componentized for sanity. --- code/datums/components/shielded.dm | 191 +++++++++++++++++ code/datums/elements/shielded.dm | 210 ------------------- code/modules/clothing/spacesuits/hardsuit.dm | 2 +- code/modules/clothing/suits/wiz_robe.dm | 6 +- tgstation.dme | 2 +- 5 files changed, 196 insertions(+), 215 deletions(-) create mode 100644 code/datums/components/shielded.dm delete mode 100644 code/datums/elements/shielded.dm 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"