diff --git a/code/__defines/_macros.dm b/code/__defines/_macros.dm
index 73f3e756845..7653ec7518e 100644
--- a/code/__defines/_macros.dm
+++ b/code/__defines/_macros.dm
@@ -104,10 +104,13 @@
#define isclient(A) istype(A, /client)
-#define isprojectile(A) istype(A, /obj/item/projectile)
-
#define isclothing(A) istype(A, /obj/item/clothing)
+/// Projectile helpers
+#define isprojectile(A) istype(A, /obj/item/projectile)
+#define isbeam(A) istype(A, /obj/item/projectile/beam)
+#define isenergy(A) istype(A, /obj/item/projectile/energy)
+
/// General I/O helpers
#define to_target(target, payload) target << (payload)
#define from_target(target, receiver) target >> (receiver)
diff --git a/code/game/objects/items/weapons/shields.dm b/code/game/objects/items/weapons/shields.dm
index 84649f5c308..0da30662f07 100644
--- a/code/game/objects/items/weapons/shields.dm
+++ b/code/game/objects/items/weapons/shields.dm
@@ -2,7 +2,7 @@
//These are shared by various items that have shield-like behaviour
//bad_arc is the ABSOLUTE arc of directions from which we cannot block. If you want to fix it to e.g. the user's facing you will need to rotate the dirs yourself.
-/proc/check_shield_arc(mob/user, var/bad_arc, atom/damage_source = null, mob/attacker = null)
+/proc/check_shield_arc(mob/user, bad_arc, atom/damage_source = null, mob/attacker = null)
//check attack direction
var/attack_dir = 0 //direction from the user to the source of the attack
if(istype(damage_source, /obj/item/projectile))
@@ -39,20 +39,15 @@
var/base_block_chance = 50
recyclable = TRUE
-/obj/item/shield/handle_shield(mob/user, var/on_back, var/damage, atom/damage_source = null, mob/attacker = null, var/def_zone = null, var/attack_text = "the attack")
- if(user.incapacitated())
+/obj/item/shield/handle_shield(mob/user, var/on_back, var/damage, atom/damage_source = null, mob/attacker = null, def_zone = null, attack_text = "the attack")
+ var/shield_dir = on_back ? user.dir : reverse_dir[user.dir]
+
+ if(user.incapacitated() || !(check_shield_arc(user, shield_dir, damage_source, attacker)))
return FALSE
- var/shield_dir = reverse_dir[user.dir]
- if(on_back)
- shield_dir = user.dir
-
- //block as long as they are not directly behind us
- var/bad_arc = shield_dir //arc of directions from which we cannot block
- if(check_shield_arc(user, bad_arc, damage_source, attacker))
- if(prob(get_block_chance(user, damage, damage_source, attacker)))
- user.visible_message("\The [user] blocks [attack_text] with \the [src]!")
- return PROJECTILE_STOPPED
+ if(prob(get_block_chance(user, damage, damage_source, attacker)))
+ user.visible_message(SPAN_DANGER("\The [user] blocks [attack_text] with \the [src]!"))
+ return PROJECTILE_STOPPED
return FALSE
/obj/item/shield/can_shield_back()
@@ -134,7 +129,7 @@
/obj/item/shield/energy
name = "energy combat shield"
desc = "A shield capable of stopping most projectile and melee attacks. It can be retracted, expanded, and stored anywhere."
- icon_state = "eshield0" // eshield1 for expanded
+ icon_state = "eshield0"
flags = CONDUCT
force = 3.0
throwforce = 5.0
@@ -144,105 +139,113 @@
origin_tech = list(TECH_MATERIAL = 4, TECH_MAGNET = 3, TECH_ILLEGAL = 4)
attack_verb = list("shoved", "bashed")
var/shield_power = 150
- var/active = 0
-
-/obj/item/shield/energy/handle_shield(mob/user, var/on_back, var/damage, atom/damage_source = null, mob/attacker = null, var/def_zone = null, var/attack_text = "the attack")
- if(!active)
- return FALSE //turn it on first!
-
- if(user.incapacitated())
- return FALSE
-
- if(.)
- spark(user.loc, 5)
- playsound(user.loc, 'sound/weapons/blade.ogg', 50, 1)
-
- var/shield_dir = reverse_dir[user.dir]
- if(on_back)
- shield_dir = user.dir
-
- //block as long as they are not directly behind us
- var/bad_arc = shield_dir //arc of directions from which we cannot block
- if(check_shield_arc(user, bad_arc, damage_source, attacker))
-
- if(prob(get_block_chance(user, damage, damage_source, attacker)))
- spark(user.loc, 5)
- playsound(user.loc, 'sound/weapons/blade.ogg', 50, 1)
- shield_power -= round(damage/4)
-
- if(shield_power <= 0)
- visible_message("\The [user]'s [src.name] overloads!")
- active = 0
- force = 3
- update_icon()
- w_class = ITEMSIZE_TINY
- playsound(user, 'sound/weapons/saberoff.ogg', 50, 1)
- shield_power = initial(shield_power)
- return FALSE
-
- if(istype(damage_source, /obj/item/projectile/energy) || istype(damage_source, /obj/item/projectile/beam))
- var/obj/item/projectile/P = damage_source
-
- var/reflectchance = 80 - round(damage/3)
- if(P.starting && prob(reflectchance))
- visible_message("\The [user]'s [src.name] reflects [attack_text]!")
-
- // Find a turf near or on the original location to bounce to
- var/new_x = P.starting.x + pick(0, 0, 0, 0, 0, -1, 1, -2, 2)
- var/new_y = P.starting.y + pick(0, 0, 0, 0, 0, -1, 1, -2, 2)
-
- // redirect the projectile
- P.firer = user
- P.old_style_target(locate(new_x, new_y, P.z))
-
- return PROJECTILE_CONTINUE // complete projectile permutation
- else
- user.visible_message("\The [user] blocks [attack_text] with \the [src]!")
- return PROJECTILE_STOPPED
- else
- user.visible_message("\The [user] blocks [attack_text] with \the [src]!")
- return PROJECTILE_STOPPED
-
-/obj/item/shield/energy/get_block_chance(mob/user, var/damage, atom/damage_source = null, mob/attacker = null)
- if(istype(damage_source, /obj/item/projectile))
- var/obj/item/projectile/P = damage_source
- if((is_sharp(P) && damage > 10) || istype(P, /obj/item/projectile/beam))
- return (base_block_chance - round(damage / 3)) //block bullets and beams using the old block chance
- return base_block_chance
-
-/obj/item/shield/energy/attack_self(mob/living/user as mob)
- if ((user.is_clumsy()) && prob(50))
- to_chat(user, "You beat yourself in the head with [src].")
- user.take_organ_damage(5)
- active = !active
- if (active)
- force = 10
- update_icon()
- w_class = ITEMSIZE_LARGE
- playsound(user, 'sound/weapons/saberon.ogg', 50, 1)
- to_chat(user, "\The [src] is now active.")
-
- else
- force = 3
- update_icon()
- w_class = ITEMSIZE_TINY
- playsound(user, 'sound/weapons/saberoff.ogg', 50, 1)
- to_chat(user, "\The [src] can now be concealed.")
-
- if(istype(user,/mob/living/carbon/human))
- var/mob/living/carbon/human/H = user
- H.update_inv_l_hand()
- H.update_inv_r_hand()
-
- add_fingerprint(user)
- return
+ var/active = FALSE
+ var/next_action
+ var/sound_token
+ var/sound_id
+
+/obj/item/shield/energy/Destroy()
+ QDEL_NULL(sound_token)
+ return ..()
+/obj/item/shield/energy/Initialize()
+ . = ..()
+ sound_id = "[sequential_id(/obj/item/shield/energy)]"
+
/obj/item/shield/energy/update_icon()
icon_state = "eshield[active]"
if(active)
set_light(1.5, 1.5, "#006AFF")
else
set_light(0)
+
+/obj/item/shield/energy/attack_self(mob/living/user)
+ var/time = world.time
+ if(time < next_action)
+ return
+ next_action = time + 3 SECONDS
+ active = !active
+ if(active)
+ HandleTurnOn()
+ else
+ HandleShutOff()
+ add_fingerprint(user)
+ update_icon()
+ user.update_inv_l_hand()
+ user.update_inv_r_hand()
+
+/obj/item/shield/energy/handle_shield(mob/user, on_back, damage, atom/damage_source = null, mob/attacker = null, def_zone = null, attack_text = "the attack")
+ var/shield_dir = on_back ? user.dir : reverse_dir[user.dir]
+
+ if(!active || user.incapacitated() || !(check_shield_arc(user, shield_dir, damage_source, attacker)))
+ return FALSE
+ if(.)
+ spark(user.loc, 5)
+
+ if(prob(get_block_chance(user, damage, damage_source, attacker)))
+ spark(user.loc, 5)
+ shield_power -= round(damage/4)
+
+ if(shield_power <= 0)
+ visible_message(SPAN_DANGER("\The [user]'s [src.name] overloads!"))
+ active = FALSE
+ HandleShutOff()
+
+ if(isenergy(damage_source) || isbeam(damage_source))
+ var/obj/item/projectile/P = damage_source
+
+ var/reflectchance = 80 - (damage/3)
+ if(P.starting && prob(reflectchance))
+ visible_message(SPAN_DANGER("\The [user]'s [src.name] reflects [attack_text]!"))
+
+ // Find a turf near or on the original location to bounce to
+ var/new_x = P.starting.x + pick(0, 0, 0, 0, 0, -1, 1, -2, 2)
+ var/new_y = P.starting.y + pick(0, 0, 0, 0, 0, -1, 1, -2, 2)
+ var/turf/curloc = get_turf(src)
+
+ // redirect the projectile
+ P.original = locate(new_x, new_y, P.z)
+ P.starting = curloc
+ P.firer = user
+ P.yo = new_y - curloc.y
+ P.xo = new_x - curloc.x
+ var/new_angle_s = P.Angle + rand(120,240)
+ while(new_angle_s > 180) // Translate to regular projectile degrees
+ new_angle_s -= 360
+ P.set_angle(new_angle_s)
+
+ return PROJECTILE_CONTINUE // complete projectile permutation
+ else
+ user.visible_message(SPAN_DANGER("\The [user] blocks [attack_text] with \the [src]!"))
+ return PROJECTILE_STOPPED
+ else
+ user.visible_message(SPAN_DANGER("\The [user] blocks [attack_text] with \the [src]!"))
+ return PROJECTILE_STOPPED
+
+
+/obj/item/shield/energy/get_block_chance(mob/user, damage, atom/damage_source = null, mob/attacker = null)
+ if(isprojectile(damage_source))
+ if((is_sharp(damage_source) && damage > 10) || isbeam(damage_source))
+ return (base_block_chance - round(damage / 3))
+ return base_block_chance
+
+/obj/item/shield/energy/proc/HandleTurnOn()
+ addtimer(CALLBACK(src, /obj/item/shield/energy/proc/UpdateSoundLoop), 0.25 SECONDS)
+ playsound(src, 'sound/items/shield/energy/shield-start.ogg', 40)
+ force = 10
+ w_class = ITEMSIZE_LARGE
+
+/obj/item/shield/energy/proc/HandleShutOff()
+ addtimer(CALLBACK(src, /obj/item/shield/energy/proc/UpdateSoundLoop), 0.1 SECONDS)
+ playsound(src, 'sound/items/shield/energy/shield-stop.ogg', 40)
+ force = initial(force)
+ w_class = initial(w_class)
+
+/obj/item/shield/energy/proc/UpdateSoundLoop()
+ if (!active)
+ QDEL_NULL(sound_token)
+ return
+ sound_token = sound_player.PlayLoopingSound(src, sound_id,'sound/items/shield/energy/shield-loop.ogg', 10, 4)
/obj/item/shield/energy/hegemony
name = "hegemony barrier"
diff --git a/html/changelogs/Generalcamo - EnergyShields.yml b/html/changelogs/Generalcamo - EnergyShields.yml
new file mode 100644
index 00000000000..428b14eface
--- /dev/null
+++ b/html/changelogs/Generalcamo - EnergyShields.yml
@@ -0,0 +1,42 @@
+################################
+# Example Changelog File
+#
+# Note: This file, and files beginning with ".", and files that don't end in ".yml" will not be read. If you change this file, you will look really dumb.
+#
+# Your changelog will be merged with a master changelog. (New stuff added only, and only on the date entry for the day it was merged.)
+# When it is, any changes listed below will disappear.
+#
+# Valid Prefixes:
+# bugfix
+# wip (For works in progress)
+# tweak
+# soundadd
+# sounddel
+# rscadd (general adding of nice things)
+# rscdel (general deleting of nice things)
+# imageadd
+# imagedel
+# maptweak
+# spellcheck (typo fixes)
+# experiment
+# balance
+# admin
+# backend
+# security
+# refactor
+#################################
+
+# Your name.
+author: GeneralCamo, Spookerton
+
+# Optional: Remove this file after generating master changelog. Useful for PR changelogs that won't get used again.
+delete-after: True
+
+# Any changes you've made. See valid prefix list above.
+# INDENT WITH TWO SPACES. NOT TABS. SPACES.
+# SCREW THIS UP AND IT WON'T WORK.
+# Also, all entries are changed into a single [] after a master changelog generation. Just remove the brackets when you add new entries.
+# Please surround your changes in double quotes ("), as certain characters otherwise screws up compiling. The quotes will not show up in the changelog.
+changes:
+ - rscadd: "Energy shields now have a looping sound when on."
+ - refactor: "Optimized shield blocking code."
diff --git a/sound/attributions.txt b/sound/attributions.txt
index 317fbc04692..04c5dcb38d7 100644
--- a/sound/attributions.txt
+++ b/sound/attributions.txt
@@ -51,4 +51,10 @@ ATTRIBUTIONS SECTION END
ATTRIBUTIONS SECTION START
sound/items/geiger.ogg - https://freesound.org/people/leonelmail/sounds/328381/ -- Under creative commons 0
+ATTRIBUTIONS SECTION END
+
+ATTRIBUTIONS SECTION START
+sound/items/shield/energy/shield-loop.ogg - By spookerton for Baystation12 - Licensed under GNU Affero General Public License v3
+sound/items/shield/energy/shield-start.ogg - By spookerton for Baystation12 - Licensed under GNU Affero General Public License v3
+sound/items/shield/energy/shield-stop.ogg - By spookerton for Baystation12 - Licensed under GNU Affero General Public License v3
ATTRIBUTIONS SECTION END
\ No newline at end of file
diff --git a/sound/items/shield/energy/shield-loop.ogg b/sound/items/shield/energy/shield-loop.ogg
new file mode 100644
index 00000000000..c9c5eb3aea7
Binary files /dev/null and b/sound/items/shield/energy/shield-loop.ogg differ
diff --git a/sound/items/shield/energy/shield-start.ogg b/sound/items/shield/energy/shield-start.ogg
new file mode 100644
index 00000000000..80c4efa5e7c
Binary files /dev/null and b/sound/items/shield/energy/shield-start.ogg differ
diff --git a/sound/items/shield/energy/shield-stop.ogg b/sound/items/shield/energy/shield-stop.ogg
new file mode 100644
index 00000000000..d40575282d0
Binary files /dev/null and b/sound/items/shield/energy/shield-stop.ogg differ