diff --git a/code/__DEFINES/combat.dm b/code/__DEFINES/combat.dm index 14c78f90d0..e3fbe30b1d 100644 --- a/code/__DEFINES/combat.dm +++ b/code/__DEFINES/combat.dm @@ -302,8 +302,8 @@ GLOBAL_LIST_INIT(shove_disarming_types, typecacheof(list( /// Block priorities #define BLOCK_PRIORITY_HELD_ITEM 100 -#define BLOCK_PRIORITY_CLOTHING 50 #define BLOCK_PRIORITY_WEAR_SUIT 75 +#define BLOCK_PRIORITY_CLOTHING 50 #define BLOCK_PRIORITY_UNIFORM 25 #define BLOCK_PRIORITY_DEFAULT BLOCK_PRIORITY_HELD_ITEM diff --git a/code/__DEFINES/dcs/signals.dm b/code/__DEFINES/dcs/signals.dm index 53348b3467..fab6d689f6 100644 --- a/code/__DEFINES/dcs/signals.dm +++ b/code/__DEFINES/dcs/signals.dm @@ -197,6 +197,7 @@ #define COMSIG_LIVING_RUN_BLOCK "living_do_run_block" //from base of mob/living/do_run_block(): (real_attack, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone) #define COMSIG_LIVING_COMBAT_ENABLED "combatmode_enabled" //from base of mob/living/enable_combat_mode() (was_forced) #define COMSIG_LIVING_COMBAT_DISABLED "combatmode_disabled" //from base of mob/living/disable_combat_mode() (was_forced) +#define COMSIG_LIVING_GET_BLOCKING_ITEMS "get_blocking_items" //from base of mob/living/get_blocking_items(): (list/items) //ALL OF THESE DO NOT TAKE INTO ACCOUNT WHETHER AMOUNT IS 0 OR LOWER AND ARE SENT REGARDLESS! #define COMSIG_LIVING_STATUS_STUN "living_stun" //from base of mob/living/Stun() (amount, update, ignore) diff --git a/code/datums/elements/shielded.dm b/code/datums/elements/shielded.dm new file mode 100644 index 0000000000..913c2df934 --- /dev/null +++ b/code/datums/elements/shielded.dm @@ -0,0 +1,210 @@ +/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. + 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()) + var/old_charges = charges_per_atom[A] + if(old_charges >= max_charges || world.time < last_use_per_atom[A]) + return + var/new_charges = min(old_charges + recharge_rate, max_charges) + charges_per_atom[A] = new_charges + if(new_charges % 1) + 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 8a7092d8a6..1b2d24e36a 100644 --- a/code/modules/clothing/spacesuits/hardsuit.dm +++ b/code/modules/clothing/spacesuits/hardsuit.dm @@ -748,63 +748,20 @@ allowed = null armor = list("melee" = 30, "bullet" = 15, "laser" = 30, "energy" = 10, "bomb" = 10, "bio" = 100, "rad" = 50, "fire" = 100, "acid" = 100) resistance_flags = FIRE_PROOF | ACID_PROOF - var/current_charges = 3 var/max_charges = 3 //How many charges total the shielding has + var/current_charges //if null, will default to max_chargs var/recharge_delay = 200 //How long after we've been shot before we can start recharging. 20 seconds here - var/recharge_cooldown = 0 //Time since we've last been shot var/recharge_rate = 1 //How quickly the shield recharges once it starts charging var/shield_state = "shield-old" - var/shield_on = "shield-old" /obj/item/clothing/suit/space/hardsuit/shielded/Initialize() . = ..() if(!allowed) allowed = GLOB.advanced_hardsuit_allowed -/obj/item/clothing/suit/space/hardsuit/shielded/check_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return) - if(current_charges > 0) - block_return[BLOCK_RETURN_NORMAL_BLOCK_CHANCE] = 100 - block_return[BLOCK_RETURN_BLOCK_CAPACITY] = (block_return[BLOCK_RETURN_BLOCK_CAPACITY] || 0) + current_charges - return ..() - -/obj/item/clothing/suit/space/hardsuit/shielded/run_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return) - recharge_cooldown = world.time + recharge_delay - if(current_charges > 0) - var/datum/effect_system/spark_spread/s = new - s.set_up(2, 1, src) - s.start() - owner.visible_message("[owner]'s shields deflect [attack_text] in a shower of sparks!") - current_charges-- - if(recharge_rate) - START_PROCESSING(SSobj, src) - if(current_charges <= 0) - owner.visible_message("[owner]'s shield overloads!") - shield_state = "broken" - owner.update_inv_wear_suit() - return BLOCK_SUCCESS | BLOCK_PHYSICAL_EXTERNAL - return BLOCK_NONE - -/obj/item/clothing/suit/space/hardsuit/shielded/Destroy() - STOP_PROCESSING(SSobj, src) - return ..() - -/obj/item/clothing/suit/space/hardsuit/shielded/process() - if(world.time > recharge_cooldown && current_charges < max_charges) - current_charges = CLAMP((current_charges + recharge_rate), 0, max_charges) - playsound(loc, 'sound/magic/charge.ogg', 50, 1) - if(current_charges == max_charges) - playsound(loc, 'sound/machines/ding.ogg', 50, 1) - STOP_PROCESSING(SSobj, src) - shield_state = "[shield_on]" - if(ishuman(loc)) - var/mob/living/carbon/human/C = loc - C.update_inv_wear_suit() - -/obj/item/clothing/suit/space/hardsuit/shielded/worn_overlays(isinhands, icon_file, used_state, style_flags = NONE) +/obj/item/clothing/suit/space/hardsuit/shielded/ComponentInitialize() . = ..() - if(!isinhands) - var/file2use = style_flags & STYLE_ALL_TAURIC ? 'modular_citadel/icons/mob/64x32_effects.dmi' : 'icons/effects/effects.dmi' - . += mutable_appearance(file2use, shield_state, MOB_LAYER + 0.01) + AddElement(/datum/element/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 @@ -833,7 +790,6 @@ hardsuit_type = "ert_security" helmettype = /obj/item/clothing/head/helmet/space/hardsuit/shielded/ctf/red shield_state = "shield-red" - shield_on = "shield-red" /obj/item/clothing/suit/space/hardsuit/shielded/ctf/blue name = "blue shielded hardsuit" @@ -896,7 +852,6 @@ item_state = "swat_suit" hardsuit_type = "syndi" max_charges = 4 - current_charges = 4 recharge_delay = 15 armor = list("melee" = 80, "bullet" = 80, "laser" = 50, "energy" = 50, "bomb" = 100, "bio" = 100, "rad" = 100, "fire" = 100, "acid" = 100) strip_delay = 130 diff --git a/code/modules/clothing/suits/wiz_robe.dm b/code/modules/clothing/suits/wiz_robe.dm index c3ddda2328..e4596825d0 100644 --- a/code/modules/clothing/suits/wiz_robe.dm +++ b/code/modules/clothing/suits/wiz_robe.dm @@ -195,10 +195,9 @@ icon_state = "battlemage" item_state = "battlemage" recharge_rate = 0 + max_charges = INFINITY current_charges = 15 - recharge_cooldown = INFINITY shield_state = "shield-red" - shield_on = "shield-red" min_cold_protection_temperature = ARMOR_MIN_TEMP_PROTECT max_heat_protection_temperature = ARMOR_MAX_TEMP_PROTECT helmettype = /obj/item/clothing/head/helmet/space/hardsuit/shielded/wizard @@ -239,6 +238,7 @@ if(!istype(W)) to_chat(user, "The rune can only be used on battlemage armour!") return - W.current_charges += 8 - to_chat(user, "You charge \the [W]. It can now absorb [W.current_charges] hits.") + 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) + to_chat(user, "You charge \the [W]. It can now absorb [S.charges_per_atom[W]] hits.") qdel(src) diff --git a/code/modules/mob/living/carbon/human/human_block.dm b/code/modules/mob/living/carbon/human/human_block.dm index b1a5153ce5..8fe0376a08 100644 --- a/code/modules/mob/living/carbon/human/human_block.dm +++ b/code/modules/mob/living/carbon/human/human_block.dm @@ -1,8 +1,8 @@ /mob/living/carbon/human/get_blocking_items() . = ..() if(wear_suit) - . += wear_suit + . |= wear_suit if(w_uniform) - . += w_uniform + . |= w_uniform if(wear_neck) - . += wear_neck + . |= wear_neck diff --git a/code/modules/mob/living/living_block.dm b/code/modules/mob/living/living_block.dm index d9e3309e47..22940b5846 100644 --- a/code/modules/mob/living/living_block.dm +++ b/code/modules/mob/living/living_block.dm @@ -71,6 +71,7 @@ /// Gets an unsortedlist of objects to run block checks on. /mob/living/proc/get_blocking_items() . = list() + SEND_SIGNAL(src, COMSIG_LIVING_GET_BLOCKING_ITEMS, .) for(var/obj/item/I in held_items) // this is a bad check but i am not removing it until a better catchall is made if(istype(I, /obj/item/clothing)) @@ -85,11 +86,13 @@ /// Runs block and returns flag for do_run_block to process. /obj/item/proc/run_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return) - SEND_SIGNAL(src, COMSIG_ITEM_RUN_BLOCK, owner, object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, final_block_chance, block_return) + . = SEND_SIGNAL(src, COMSIG_ITEM_RUN_BLOCK, owner, object, damage, attack_text, attack_type, armour_penetration, attacker, def_zone, final_block_chance, block_return) + if(. & BLOCK_SUCCESS) + return if(prob(final_block_chance)) owner.visible_message("[owner] blocks [attack_text] with [src]!") - return BLOCK_SUCCESS | BLOCK_PHYSICAL_EXTERNAL - return BLOCK_NONE + return . | BLOCK_SUCCESS | BLOCK_PHYSICAL_EXTERNAL + return . | BLOCK_NONE /// Returns block information using list/block_return. Used for check_block() on mobs. /obj/item/proc/check_block(mob/living/owner, atom/object, damage, attack_text, attack_type, armour_penetration, mob/attacker, def_zone, final_block_chance, list/block_return) diff --git a/tgstation.dme b/tgstation.dme index 26bb4e2e5a..593586e98e 100755 --- a/tgstation.dme +++ b/tgstation.dme @@ -514,6 +514,7 @@ #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"