diff --git a/code/__DEFINES/components.dm b/code/__DEFINES/components.dm
index a836c90386d..8e8846da0e2 100644
--- a/code/__DEFINES/components.dm
+++ b/code/__DEFINES/components.dm
@@ -253,6 +253,10 @@
// /obj/item/projectile signals (sent to the firer)
#define COMSIG_PROJECTILE_ON_HIT "projectile_on_hit" // from base of /obj/item/projectile/proc/on_hit(): (atom/movable/firer, atom/target, Angle)
#define COMSIG_PROJECTILE_BEFORE_FIRE "projectile_before_fire" // from base of /obj/item/projectile/proc/fire(): (obj/item/projectile, atom/original_target)
+#define COMSIG_PROJECTILE_PREHIT "com_proj_prehit" // sent to targets during the process_hit proc of projectiles
+
+// /obj/mecha signals
+#define COMSIG_MECHA_ACTION_ACTIVATE "mecha_action_activate" //sent from mecha action buttons to the mecha they're linked to
// /mob/living/carbon/human signals
#define COMSIG_HUMAN_MELEE_UNARMED_ATTACK "human_melee_unarmed_attack" //from mob/living/carbon/human/UnarmedAttack(): (atom/target)
diff --git a/code/game/mecha/combat/durand.dm b/code/game/mecha/combat/durand.dm
index 0ff90cdba32..5fe76b9eeeb 100644
--- a/code/game/mecha/combat/durand.dm
+++ b/code/game/mecha/combat/durand.dm
@@ -11,6 +11,20 @@
infra_luminosity = 8
force = 40
wreckage = /obj/structure/mecha_wreckage/durand
+ var/obj/durand_shield/shield
+
+/obj/mecha/combat/durand/Initialize()
+ shield = new/obj/durand_shield
+ shield.chassis = src
+ shield.layer = layer
+ RegisterSignal(src, COMSIG_MECHA_ACTION_ACTIVATE, .proc/relay)
+ RegisterSignal(src, COMSIG_PROJECTILE_PREHIT, .proc/prehit)
+ . = ..()
+
+/obj/mecha/combat/durand/Destroy()
+ if(shield)
+ qdel(shield)
+ . = ..()
/obj/mecha/combat/durand/GrantActions(mob/living/user, human_occupant = 0)
..()
@@ -20,3 +34,176 @@
..()
defense_action.Remove(user)
+/obj/mecha/combat/durand/process()
+ . = ..()
+ if(defense_mode && !use_power(100))
+ defense_action.Activate(forced_state = TRUE)
+
+/obj/mecha/combat/durand/domove(direction)
+ . = ..()
+ if(shield)
+ shield.forceMove(loc)
+ shield.dir = dir
+
+/obj/mecha/combat/durand/forceMove(var/turf/T)
+ . = ..()
+ shield.forceMove(T)
+
+/obj/mecha/combat/durand/go_out(forced, atom/newloc = loc)
+ if(defense_mode)
+ defense_action.Activate(forced_state = TRUE)
+ . = ..()
+
+///Relays the signal from the action button to the shield, and creates a new shield if the old one is MIA.
+/obj/mecha/combat/durand/proc/relay(datum/source, list/signal_args)
+ if(!shield) //if the shield somehow got deleted
+ shield = new/obj/durand_shield
+ shield.chassis = src
+ shield.layer = layer
+ shield.forceMove(loc)
+ shield.dir = dir
+ SEND_SIGNAL(shield, COMSIG_MECHA_ACTION_ACTIVATE, source, signal_args)
+
+//Redirects projectiles to the shield if defense_check decides they should be blocked and returns true.
+/obj/mecha/combat/durand/proc/prehit(obj/item/projectile/source, list/signal_args)
+ if(defense_check(source.loc) && shield)
+ signal_args[2] = shield
+
+
+/**Checks if defense mode is enabled, and if the attacker is standing in an area covered by the shield.
+Expects a turf. Returns true if the attack should be blocked, false if not.*/
+/obj/mecha/combat/durand/proc/defense_check(var/turf/aloc)
+ if (!defense_mode || !shield || shield.switching)
+ return FALSE
+ . = FALSE
+ switch(dir)
+ if (1)
+ if(abs(x - aloc.x) <= (y - aloc.y) * -2)
+ . = TRUE
+ if (2)
+ if(abs(x - aloc.x) <= (y - aloc.y) * 2)
+ . = TRUE
+ if (4)
+ if(abs(y - aloc.y) <= (x - aloc.x) * -2)
+ . = TRUE
+ if (8)
+ if(abs(y - aloc.y) <= (x - aloc.x) * 2)
+ . = TRUE
+ return
+
+obj/mecha/combat/durand/attack_generic(mob/user, damage_amount = 0, damage_type = BRUTE, damage_flag = 0, sound_effect = 1, armor_penetration = 0)
+ if(defense_check(user.loc))
+ log_message("Attack absorbed by defense field. Attacker - [user].", LOG_MECHA, color="orange")
+ shield.attack_generic(user, damage_amount, damage_type, damage_flag, sound_effect, armor_penetration)
+ else
+ . = ..()
+
+/obj/mecha/combat/durand/blob_act(obj/structure/blob/B)
+ if(defense_check(B.loc))
+ log_message("Attack by blob. Attacker - [B].", LOG_MECHA, color="red")
+ log_message("Attack absorbed by defense field.", LOG_MECHA, color="orange")
+ shield.blob_act(B)
+ else
+ . = ..()
+
+/obj/mecha/combat/durand/attackby(obj/item/W as obj, mob/user as mob, params)
+ if(defense_check(user.loc))
+ log_message("Attack absorbed by defense field. Attacker - [user], with [W]", LOG_MECHA, color="orange")
+ shield.attackby(W, user, params)
+ else
+ . = ..()
+
+/obj/mecha/combat/durand/hitby(atom/movable/AM, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum)
+ if(defense_check(AM.loc))
+ log_message("Impact with [AM] absorbed by defense field.", LOG_MECHA, color="orange")
+ shield.hitby(AM, skipcatch, hitpush, blocked, throwingdatum)
+ else
+ . = ..()
+
+////////////////////////////
+///// Shield processing ////
+////////////////////////////
+
+/**An object to take the hit for us when using the Durand's defense mode.
+It is spawned in during the durand's initilization, and always stays on the same tile.
+Normally invisible, until defense mode is actvated. When the durand detects an attack that should be blocked, the
+attack is passed to the shield. The shield takes the damage, uses it to calculate charge cost, and then sets its
+own integrity back to max. Shield is automatically dropped if we run out of power or the user gets out.*/
+
+/obj/durand_shield //projectiles get passed to this when defense mode is enabled
+ name = "defense grid"
+ icon = 'icons/mecha/durand_shield.dmi'
+ icon_state = "shield_null"
+ invisibility = INVISIBILITY_MAXIMUM //no showing on right-click
+ pixel_y = 4
+ max_integrity = 10000
+ obj_integrity = 10000
+ var/obj/mecha/combat/durand/chassis ///Our link back to the durand
+ var/switching = FALSE ///To keep track of things during the animation
+
+/obj/durand_shield/Initialize()
+ . = ..()
+ RegisterSignal(src, COMSIG_MECHA_ACTION_ACTIVATE, .proc/activate)
+
+/obj/durand_shield/Destroy()
+ if(chassis)
+ chassis.shield = null
+ . = ..()
+
+/**Handles activating and deactivating the shield. This proc is called by a signal sent from the mech's action button
+and relayed by the mech itself. The "forced" variabe, signal_args[1], will skip the to-pilot text and is meant for when
+the shield is disabled by means other than the action button (like running out of power)*/
+
+/obj/durand_shield/proc/activate(datum/source, var/datum/action/innate/mecha/mech_defense_mode/button, list/signal_args)
+ if(!chassis || !chassis.occupant)
+ return
+ if(switching && !signal_args[1])
+ return
+ if(!chassis.defense_mode && (!chassis.cell || chassis.cell.charge < 100)) //If it's off, and we have less than 100 units of power
+ chassis.occupant_message("Insufficient power; cannot activate defense mode.")
+ return
+ switching = TRUE
+ chassis.defense_mode = !chassis.defense_mode
+ chassis.defense_action.button_icon_state = "mech_defense_mode_[chassis.defense_mode ? "on" : "off"]" //This is backwards because we haven't changed the var yet
+ if(!signal_args[1])
+ chassis.occupant_message("Defense mode [chassis.defense_mode?"enabled":"disabled"].")
+ chassis.log_message("User has toggled defense mode -- now [chassis.defense_mode?"enabled":"disabled"].", LOG_MECHA)
+ else
+ chassis.log_message("defense mode state changed -- now [chassis.defense_mode?"enabled":"disabled"].", LOG_MECHA)
+ chassis.defense_action.UpdateButtonIcon()
+
+ if(chassis.defense_mode)
+ invisibility = 0
+ flick("shield_raise", src)
+ playsound(src, 'sound/mecha/mech_shield_raise.ogg', 50, FALSE)
+ set_light(l_range = MINIMUM_USEFUL_LIGHT_RANGE , l_power = 5, l_color = "#00FFFF")
+ sleep(3)
+ icon_state = "shield"
+ else
+ flick("shield_drop", src)
+ playsound(src, 'sound/mecha/mech_shield_drop.ogg', 50, FALSE)
+ sleep(5)
+ set_light(0)
+ icon_state = "shield_null"
+ invisibility = INVISIBILITY_MAXIMUM //no showing on right-click
+ switching = FALSE
+
+/obj/durand_shield/take_damage()
+ if(!chassis)
+ qdel(src)
+ return
+ if(!chassis.defense_mode) //if defense mode is disabled, we're taking damage that we shouldn't be taking
+ return
+ . = ..()
+ flick("shield_impact", src)
+ if(!chassis.use_power((max_integrity - obj_integrity) * 100))
+ chassis.cell?.charge = 0
+ chassis.defense_action.Activate(forced_state = TRUE)
+ obj_integrity = 10000
+
+/obj/durand_shield/play_attack_sound()
+ playsound(src, 'sound/mecha/mech_shield_deflect.ogg', 100, TRUE)
+
+/obj/durand_shield/bullet_act()
+ play_attack_sound()
+ . = ..()
\ No newline at end of file
diff --git a/code/game/mecha/mecha.dm b/code/game/mecha/mecha.dm
index 3f38dd948d1..f4ff16dd0d1 100644
--- a/code/game/mecha/mecha.dm
+++ b/code/game/mecha/mecha.dm
@@ -98,7 +98,7 @@
var/datum/action/innate/mecha/mech_toggle_lights/lights_action = new
var/datum/action/innate/mecha/mech_view_stats/stats_action = new
var/datum/action/innate/mecha/mech_toggle_thrusters/thrusters_action = new
- var/datum/action/innate/mecha/mech_defence_mode/defense_action = new
+ var/datum/action/innate/mecha/mech_defense_mode/defense_action = new
var/datum/action/innate/mecha/mech_overload_mode/overload_action = new
var/datum/effect_system/smoke_spread/smoke_system = new //not an action, but trigged by one
var/datum/action/innate/mecha/mech_smoke/smoke_action = new
@@ -109,8 +109,7 @@
//Action vars
var/thrusters_active = FALSE
- var/defence_mode = FALSE
- var/defence_mode_deflect_chance = 35
+ var/defense_mode = FALSE
var/leg_overload_mode = FALSE
var/leg_overload_coeff = 100
var/zoom_mode = FALSE
@@ -577,11 +576,6 @@
return 0
if(!has_charge(step_energy_drain))
return 0
- if(defence_mode)
- if(world.time - last_message > 20)
- occupant_message("Unable to move while in defence mode")
- last_message = world.time
- return 0
if(zoom_mode)
if(world.time - last_message > 20)
occupant_message("Unable to move while in zoom mode.")
@@ -1100,8 +1094,7 @@ GLOBAL_VAR_INIT(year_integer, text2num(year)) // = 2013???
return max(0, cell.charge)
/obj/mecha/proc/use_power(amount)
- if(get_charge())
- cell.use(amount)
+ if(get_charge() && cell.use(amount))
return 1
return 0
diff --git a/code/game/mecha/mecha_actions.dm b/code/game/mecha/mecha_actions.dm
index 30d5b8266fa..ffd53b7ea19 100644
--- a/code/game/mecha/mecha_actions.dm
+++ b/code/game/mecha/mecha_actions.dm
@@ -164,27 +164,13 @@
chassis.log_message("Toggled thrusters.", LOG_MECHA)
chassis.occupant_message("Thrusters [chassis.thrusters_active ?"en":"dis"]abled.")
-
-/datum/action/innate/mecha/mech_defence_mode
- name = "Toggle Defence Mode"
+/datum/action/innate/mecha/mech_defense_mode
+ name = "Toggle an energy shield that blocks all attacks from the faced direction at a heavy power cost."
button_icon_state = "mech_defense_mode_off"
+ var/image/def_overlay
-/datum/action/innate/mecha/mech_defence_mode/Activate(forced_state = null)
- if(!owner || !chassis || chassis.occupant != owner)
- return
- if(!isnull(forced_state))
- chassis.defence_mode = forced_state
- else
- chassis.defence_mode = !chassis.defence_mode
- button_icon_state = "mech_defense_mode_[chassis.defence_mode ? "on" : "off"]"
- if(chassis.defence_mode)
- chassis.deflect_chance = chassis.defence_mode_deflect_chance
- chassis.occupant_message("You enable [chassis] defence mode.")
- else
- chassis.deflect_chance = initial(chassis.deflect_chance)
- chassis.occupant_message("You disable [chassis] defence mode.")
- chassis.log_message("Toggled defence mode.", LOG_MECHA)
- UpdateButtonIcon()
+/datum/action/innate/mecha/mech_defense_mode/Activate(forced_state = FALSE)
+ SEND_SIGNAL(chassis, COMSIG_MECHA_ACTION_ACTIVATE, args) ///Signal sent to the mech, to be handed to the shield. See durand.dm for more details
/datum/action/innate/mecha/mech_overload_mode
name = "Toggle leg actuators overload"
diff --git a/code/game/mecha/mecha_defense.dm b/code/game/mecha/mecha_defense.dm
index 98115e8fa5c..92a27489358 100644
--- a/code/game/mecha/mecha_defense.dm
+++ b/code/game/mecha/mecha_defense.dm
@@ -53,7 +53,6 @@
if(.)
. *= booster_damage_modifier
-
/obj/mecha/attack_hand(mob/living/user)
. = ..()
if(.)
@@ -67,7 +66,6 @@
/obj/mecha/attack_paw(mob/user as mob)
return attack_hand(user)
-
/obj/mecha/attack_alien(mob/living/user)
log_message("Attack by alien. Attacker - [user].", LOG_MECHA, color="red")
playsound(src.loc, 'sound/weapons/slash.ogg', 100, 1)
@@ -87,8 +85,8 @@
if(user.obj_damage)
animal_damage = user.obj_damage
animal_damage = min(animal_damage, 20*user.environment_smash)
- attack_generic(user, animal_damage, user.melee_damage_type, "melee", play_soundeffect)
log_combat(user, src, "attacked")
+ attack_generic(user, animal_damage, user.melee_damage_type, "melee", play_soundeffect)
return 1
@@ -102,6 +100,7 @@
log_combat(user, src, "punched", "hulk powers")
/obj/mecha/blob_act(obj/structure/blob/B)
+ log_message("Attack by blob. Attacker - [B].", LOG_MECHA, color="red")
take_damage(30, BRUTE, "melee", 0, get_dir(src, B))
/obj/mecha/attack_tk()
@@ -111,7 +110,6 @@
log_message("Hit by [AM].", LOG_MECHA, color="red")
. = ..()
-
/obj/mecha/bullet_act(obj/item/projectile/Proj) //wrapper
if (!enclosed && occupant && !silicon_pilot && !Proj.force_hit && (Proj.def_zone == BODY_ZONE_HEAD || Proj.def_zone == BODY_ZONE_CHEST)) //allows bullets to hit the pilot of open-canopy mechs
occupant.bullet_act(Proj) //If the sides are open, the occupant can be hit
diff --git a/code/game/mecha/mecha_topic.dm b/code/game/mecha/mecha_topic.dm
index 62b4674e991..62e414cef64 100644
--- a/code/game/mecha/mecha_topic.dm
+++ b/code/game/mecha/mecha_topic.dm
@@ -91,7 +91,7 @@
Cabin temperature: [internal_tank?"[return_temperature()]°K|[return_temperature() - T0C]°C":"N/A"]
[dna_lock?"DNA-locked:
[dna_lock] \[Reset\]
":""]
[thrusters_action.owner ? "Thrusters: [thrusters_active ? "Enabled" : "Disabled"]
" : ""]
- [defense_action.owner ? "Defence Mode: [defence_mode ? "Enabled" : "Disabled"]
" : ""]
+ [defense_action.owner ? "Defense Mode: [defense_mode ? "Enabled" : "Disabled"]
" : ""]
[overload_action.owner ? "Leg Actuators Overload: [leg_overload_mode ? "Enabled" : "Disabled"]
" : ""]
[smoke_action.owner ? "Smoke: [smoke]
" : ""]
[zoom_action.owner ? "Zoom: [zoom_mode ? "Enabled" : "Disabled"]
" : ""]
diff --git a/code/modules/mob/living/simple_animal/hostile/mecha_pilot.dm b/code/modules/mob/living/simple_animal/hostile/mecha_pilot.dm
index 2e783d84d84..b42627b2bbd 100644
--- a/code/modules/mob/living/simple_animal/hostile/mecha_pilot.dm
+++ b/code/modules/mob/living/simple_animal/hostile/mecha_pilot.dm
@@ -32,7 +32,7 @@
//Vars that control when the pilot uses their mecha's abilities (if the mecha has that ability)
var/threat_use_mecha_smoke = 5 //5 mobs is enough to engage crowd control
- var/defence_mode_chance = 35 //Chance to engage Defence mode when damaged
+ var/defense_mode_chance = 35 //Chance to engage Defense mode when damaged
var/smoke_chance = 20 //Chance to deploy smoke for crowd control
var/retreat_chance = 40 //Chance to run away
@@ -226,19 +226,19 @@
if(mecha.smoke_action && mecha.smoke_action.owner && mecha.smoke)
mecha.smoke_action.Activate()
- //Heavy damage - Defence Power or Retreat
+ //Heavy damage - Defense Power or Retreat
if(mecha.obj_integrity < mecha.max_integrity*0.25)
- if(prob(defence_mode_chance))
- if(mecha.defense_action && mecha.defense_action.owner && !mecha.defence_mode)
+ if(prob(defense_mode_chance))
+ if(mecha.defense_action && mecha.defense_action.owner && !mecha.defense_mode)
mecha.leg_overload_mode = 0
mecha.defense_action.Activate(TRUE)
- addtimer(CALLBACK(mecha.defense_action, /datum/action/innate/mecha/mech_defence_mode.proc/Activate, FALSE), 100) //10 seconds of defence, then toggle off
+ addtimer(CALLBACK(mecha.defense_action, /datum/action/innate/mecha/mech_defense_mode.proc/Activate, FALSE), 100) //10 seconds of defense, then toggle off
else if(prob(retreat_chance))
//Speed boost if possible
if(mecha.overload_action && mecha.overload_action.owner && !mecha.leg_overload_mode)
mecha.overload_action.Activate(TRUE)
- addtimer(CALLBACK(mecha.overload_action, /datum/action/innate/mecha/mech_defence_mode.proc/Activate, FALSE), 100) //10 seconds of speeeeed, then toggle off
+ addtimer(CALLBACK(mecha.overload_action, /datum/action/innate/mecha/mech_defense_mode.proc/Activate, FALSE), 100) //10 seconds of speeeeed, then toggle off
retreat_distance = 50
spawn(100)
diff --git a/code/modules/projectiles/projectile.dm b/code/modules/projectiles/projectile.dm
index befffbe2bcd..b835ec9c178 100644
--- a/code/modules/projectiles/projectile.dm
+++ b/code/modules/projectiles/projectile.dm
@@ -257,6 +257,7 @@
permutated |= target //Make sure we're never hitting it again. If we ever run into weirdness with piercing projectiles needing to hit something multiple times.. well.. that's a to-do.
if(!prehit(target))
return process_hit(T, select_target(T), qdel_self, hit_something) //Hit whatever else we can since that didn't work.
+ SEND_SIGNAL(target, COMSIG_PROJECTILE_PREHIT, args)
var/result = target.bullet_act(src, def_zone)
if(result == BULLET_ACT_FORCE_PIERCE)
if(!CHECK_BITFIELD(movement_type, UNSTOPPABLE))
diff --git a/icons/mecha/durand_shield.dmi b/icons/mecha/durand_shield.dmi
new file mode 100644
index 00000000000..0600e352b31
Binary files /dev/null and b/icons/mecha/durand_shield.dmi differ
diff --git a/sound/mecha/mech_shield_deflect.ogg b/sound/mecha/mech_shield_deflect.ogg
new file mode 100644
index 00000000000..5c1970d8724
Binary files /dev/null and b/sound/mecha/mech_shield_deflect.ogg differ
diff --git a/sound/mecha/mech_shield_drop.ogg b/sound/mecha/mech_shield_drop.ogg
new file mode 100644
index 00000000000..21c6cb5edb9
Binary files /dev/null and b/sound/mecha/mech_shield_drop.ogg differ
diff --git a/sound/mecha/mech_shield_raise.ogg b/sound/mecha/mech_shield_raise.ogg
new file mode 100644
index 00000000000..65ad70ad14d
Binary files /dev/null and b/sound/mecha/mech_shield_raise.ogg differ