diff --git a/code/game/mecha/combat/durand.dm b/code/game/mecha/combat/durand.dm
index 4ccf400e6ab11..a42e1e29f7eec 100644
--- a/code/game/mecha/combat/durand.dm
+++ b/code/game/mecha/combat/durand.dm
@@ -12,23 +12,24 @@
force = 40
wreckage = /obj/structure/mecha_wreckage/durand
var/obj/durand_shield/shield
+ var/shield_type = /obj/durand_shield
var/shield_passive_drain = 300
+
/obj/mecha/combat/durand/clip
- desc = "An aging combat exosuit appropriated from abandoned Nanotrasen facilities, now supplied to the CMM-BARD anti-xenofauna division."
+ desc = "An aging combat exosuit appropriated from abandoned Nanotrasen facilities, now supplied to the CMM-BARD anti-xenofauna division. The defence grid has been modified to disperse controlled electric shocks on contact, at the cost of its ability to block ranged projectiles."
name = "\improper Paladin"
icon_state = "clipdurand"
wreckage = /obj/structure/mecha_wreckage/durand/clip
armor = list("melee" = 40, "bullet" = 35, "laser" = 15, "energy" = 10, "bomb" = 20, "bio" = 0, "rad" = 50, "fire" = 100, "acid" = 100)
-
- //TODO: Custom melee backlash shield with no projectile protection
+ shield_passive_drain = 0
+ shield_type = /obj/durand_shield/clip
/obj/mecha/combat/durand/Initialize()
. = ..()
- shield = new /obj/durand_shield(loc, src, layer, dir)
+ shield = new shield_type(loc, src, layer, dir)
RegisterSignal(src, COMSIG_MECHA_ACTION_ACTIVATE, PROC_REF(relay))
- RegisterSignal(src, COMSIG_PROJECTILE_PREHIT, PROC_REF(prehit))
/obj/mecha/combat/durand/Destroy()
@@ -71,22 +72,21 @@
if(!shield) //if the shield somehow got deleted
stack_trace("Durand triggered relay without a shield")
- shield = new /obj/durand_shield(loc, src, layer)
+ shield = new shield_type(loc, src, layer)
shield.setDir(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/projectile/source, list/signal_args)
- SIGNAL_HANDLER
-
- if(defense_check(source.loc) && shield)
- signal_args[2] = shield
-
+/obj/mecha/combat/durand/bullet_act(obj/projectile/source)
+ if(defense_check(source.loc, shield.ranged_pass))
+ shield.bullet_act(source)
+ else
+ . = ..()
/**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(turf/aloc)
- if (!defense_mode || !shield || shield.switching)
+Expects a turf. Returns true if the attack should be blocked, false if not. Skip defence will make the proc return false and the attack will go through*/
+/obj/mecha/combat/durand/proc/defense_check(turf/aloc, skip_defence = FALSE)
+ if (!defense_mode || !shield || shield.switching || skip_defence)
return FALSE
. = FALSE
switch(dir)
@@ -105,26 +105,38 @@ Expects a turf. Returns true if the attack should be blocked, false if not.*/
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))
+ if(defense_check(user.loc, shield.melee_pass))
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/attackby(obj/item/W as obj, mob/user as mob, params)
- if(defense_check(user.loc))
+ if(defense_check(user.loc, shield.melee_pass))
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))
+ if(defense_check(AM.loc, shield.ranged_pass))
log_message("Impact with [AM] absorbed by defense field.", LOG_MECHA, color="orange")
shield.hitby(AM, skipcatch, hitpush, blocked, throwingdatum)
else
. = ..()
+// Walking into the Paladin's shield shocks you.
+
+/obj/mecha/combat/durand/clip/Bump(atom/obstacle)
+ . = ..()
+ if(defense_check(obstacle.loc) && isliving(obstacle))
+ shield.contact(obstacle)
+
+/obj/mecha/combat/durand/clip/Bumped(atom/movable/AM)
+ . = ..()
+ if(defense_check(AM.loc) && isliving(AM))
+ shield.contact(AM)
+
////////////////////////////
///// Shield processing ////
////////////////////////////
@@ -151,7 +163,14 @@ own integrity back to max. Shield is automatically dropped if we run out of powe
light_on = FALSE
var/obj/mecha/combat/durand/chassis ///Our link back to the durand
var/switching = FALSE ///To keep track of things during the animation
+ /// if this shield lets melee attacks pass and hit the mech directly
+ var/melee_pass = FALSE
+ /// if this shield lets projectiles pass and hit the mech directly
+ var/ranged_pass = FALSE
+/obj/durand_shield/clip
+ name = "electric repulsion grid"
+ ranged_pass = TRUE
/obj/durand_shield/Initialize(mapload, _chassis, _layer, _dir)
. = ..()
@@ -230,3 +249,35 @@ the shield is disabled by means other than the action button (like running out o
/obj/durand_shield/bullet_act()
play_attack_sound()
. = ..()
+
+/// a mob has bumped into the shield
+/obj/durand_shield/proc/contact(mob/living/contactor)
+ return
+
+/// Clippy shield
+/obj/durand_shield/clip/attack_generic(mob/user, damage_amount, damage_type, damage_flag, sound_effect, armor_penetration)
+ . = ..()
+ apply_shock(user)
+
+/obj/durand_shield/clip/attackby(obj/item/I, mob/living/user, params)
+ . = ..()
+ apply_shock(user)
+
+/obj/durand_shield/clip/contact(mob/living/contactor)
+ . = ..()
+ apply_shock(contactor)
+
+/obj/durand_shield/clip/proc/apply_shock(mob/attacker)
+ var/did_shock = FALSE
+ if(iscarbon(attacker))
+ var/mob/living/carbon/victim = attacker
+ if(electrocute_mob(victim, chassis.cell, src, 1, FALSE, FALSE))
+ did_shock = TRUE
+ else if(isliving(attacker))
+ var/mob/living/victim = attacker
+ if(victim.apply_damage_type(20,BURN))
+ to_chat(victim,span_userdanger("You're shocked by \the [src]!"))
+ did_shock = TRUE
+ if(did_shock)
+ visible_message(span_bolddanger("\The [src] repels \the [attacker] on contact, shocking [attacker.p_them()]."))
+ do_sparks(5,TRUE,src)
diff --git a/code/game/mecha/combat/gygax.dm b/code/game/mecha/combat/gygax.dm
index 5fe5d9350c14f..02f66b54236a4 100644
--- a/code/game/mecha/combat/gygax.dm
+++ b/code/game/mecha/combat/gygax.dm
@@ -58,6 +58,13 @@
return
cell = new /obj/item/stock_parts/cell/bluespace(src)
+/obj/mecha/combat/gygax/inteq
+ name = "\improper Basenji"
+ desc = "A lightweight security exosuit, modified to IRMG standards. The leg actuators have been maxed out, allowing for powerful short ranged charges."
+ icon_state = "inteqgygax"
+ charge_break_walls = TRUE
+ charge_toss_structures = TRUE
+ charge_toss_mobs = TRUE
/obj/mecha/combat/gygax/GrantActions(mob/living/user, human_occupant = 0)
..()
@@ -67,3 +74,12 @@
/obj/mecha/combat/gygax/RemoveActions(mob/living/user, human_occupant = 0)
..()
overload_action.Remove(user)
+
+/obj/mecha/combat/gygax/inteq/GrantActions(mob/living/user, human_occupant = 0)
+ ..()
+ overload_action.Remove(user)
+ charge_action.Grant(user,src)
+
+/obj/mecha/combat/gygax/inteq/RemoveActions(mob/living/user, human_occupant)
+ . = ..()
+ charge_action.Remove(user)
diff --git a/code/game/mecha/equipment/tools/work_tools.dm b/code/game/mecha/equipment/tools/work_tools.dm
index ee8481255f2d4..44c8c6626dae2 100644
--- a/code/game/mecha/equipment/tools/work_tools.dm
+++ b/code/game/mecha/equipment/tools/work_tools.dm
@@ -575,3 +575,10 @@
icon_state = "clipupgrade"
source_mech = list(/obj/mecha/combat/durand)
result_mech = /obj/mecha/combat/durand/clip
+
+/obj/item/mecha_parts/mecha_equipment/conversion_kit/inteq_gygax
+ name = "IRMG Basenji Conversion Kit"
+ desc = "An IRMG-custom conversion kit for a Gygax combat exosuit, to convert it to the specialized Pyrnese breaching exosuit."
+ source_mech = list(/obj/mecha/combat/gygax,/obj/mecha/combat/gygax/dark)
+ result_mech = /obj/mecha/combat/gygax/inteq
+
diff --git a/code/game/mecha/mecha.dm b/code/game/mecha/mecha.dm
index a1b46fd2fbfa5..db59620b39fd3 100644
--- a/code/game/mecha/mecha.dm
+++ b/code/game/mecha/mecha.dm
@@ -41,6 +41,16 @@
var/last_user_hud = 1 // used to show/hide the mecha hud while preserving previous preference
var/completely_disabled = FALSE //stops the mech from doing anything
+ ///Vars for mech charges
+ var/charging = FALSE
+ var/charge_ready = TRUE
+ var/charge_cooldown = 50
+ var/charge_power_consume = 200
+ var/charge_distance = 5
+ var/charge_break_walls = FALSE
+ var/charge_toss_structures = FALSE
+ var/charge_toss_mobs = FALSE
+
var/bumpsmash = 0 //Whether or not the mech destroys walls by running into it.
//inner atmos
var/use_internal_tank = 0
@@ -90,6 +100,7 @@
var/datum/action/innate/mecha/mech_view_stats/stats_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/action/innate/mecha/mech_charge_mode/charge_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
var/datum/action/innate/mecha/mech_zoom/zoom_action = new
@@ -643,6 +654,7 @@
step_silent = FALSE
/obj/mecha/Bump(atom/obstacle)
+ var/atom/throw_target = get_edge_target_turf(obstacle, dir)
if(phasing && get_charge() >= phasing_energy_drain && !throwing)
if(!can_move)
return
@@ -654,6 +666,27 @@
forceMove(get_step(src,dir))
use_power(phasing_energy_drain)
addtimer(VARSET_CALLBACK(src, can_move, TRUE), step_in*3)
+ else if(charging)
+ if(charge_break_walls && iswallturf(obstacle))
+ var/turf/closed/wall/crushed = obstacle
+ playsound(src, 'sound/effects/meteorimpact.ogg', 100, TRUE)
+ visible_message(span_danger("[src] smashes through [obstacle]"))
+ crushed.dismantle_wall(TRUE)
+ if(isobj(obstacle))
+ var/obj/object = obstacle
+ obstacle.mech_melee_attack(src)
+ if(!(object.resistance_flags & INDESTRUCTIBLE) && charge_toss_structures)
+ object.throw_at(throw_target, 4, 3)
+ visible_message(span_danger("[src] crashes into [obstacle]!"))
+ playsound(src, 'sound/effects/bang.ogg', 50, TRUE)
+ if(ishuman(obstacle))
+ var/mob/living/carbon/human/H = obstacle
+ H.throw_at(throw_target,4,3)
+ visible_message(span_danger("[src] slams into \the [obstacle], sending [obstacle.p_them()] flying!"))
+ playsound(H, 'sound/effects/bang.ogg', 100, FALSE, -1)
+ H.Paralyze(20)
+ H.adjustStaminaLoss(30)
+ H.apply_damage(rand(20,35), BRUTE)
else
if(..()) //mech was thrown
return
@@ -1212,3 +1245,30 @@ GLOBAL_VAR_INIT(year_integer, text2num(year)) // = 2013???
else
to_chat(user, "None of the equipment on this exosuit can use this ammo!")
return FALSE
+
+
+///////////////////////
+////// Charging /////
+///////////////////////
+
+/obj/mecha/proc/start_charge()
+ Shake(15, 15, 1 SECONDS)
+ var/obj/effect/temp_visual/decoy/new_decoy = new /obj/effect/temp_visual/decoy(loc,src)
+ animate(new_decoy, alpha = 0, color = "#5a5858", transform = matrix()*2, time = 2)
+ addtimer(CALLBACK(src,PROC_REF(handle_charge)),0.5 SECONDS, TIMER_STOPPABLE)
+
+/obj/mecha/proc/handle_charge()
+ var/turf/mecha_loc = get_turf(src)
+ charging = TRUE
+ var/turf/charge_target = get_ranged_target_turf(mecha_loc,dir,charge_distance)
+ if(!charge_target)
+ charging = FALSE
+ return
+ cell.use(charge_power_consume)
+ walk_towards(src, charge_target, 0.7)
+ sleep(get_dist(src, charge_target) * 0.7)
+ charge_end()
+
+/obj/mecha/proc/charge_end()
+ walk(src,0)
+ charging = FALSE
diff --git a/code/game/mecha/mecha_actions.dm b/code/game/mecha/mecha_actions.dm
index 6d860558a6522..f10fb49c29bc8 100644
--- a/code/game/mecha/mecha_actions.dm
+++ b/code/game/mecha/mecha_actions.dm
@@ -183,6 +183,20 @@
chassis.occupant_message("You disable leg actuators overload.")
UpdateButtonIcon()
+/datum/action/innate/mecha/mech_charge_mode
+ name = "Charge"
+ button_icon_state = "mech_overload_off"
+
+/datum/action/innate/mecha/mech_charge_mode/Activate()
+ if(!owner || !chassis || chassis.occupant != owner)
+ return
+ if(chassis.charge_ready && !chassis.charging)
+ chassis.start_charge()
+ chassis.charge_ready = FALSE
+ addtimer(VARSET_CALLBACK(chassis, charge_ready, TRUE), chassis.charge_cooldown)
+ else
+ chassis.occupant_message(span_warning("The leg actuators are still recharging!"))
+
/datum/action/innate/mecha/mech_smoke
name = "Smoke"
button_icon_state = "mech_smoke"
diff --git a/code/modules/power/power.dm b/code/modules/power/power.dm
index a386a739622d6..7c7dc8692cb5b 100644
--- a/code/modules/power/power.dm
+++ b/code/modules/power/power.dm
@@ -377,8 +377,9 @@
//source is an object caused electrocuting (airlock, grille, etc)
//siemens_coeff - layman's terms, conductivity
//dist_check - set to only shock mobs within 1 of source (vendors, airlocks, etc.)
+//drain_energy - whether the shock will drain power from the mech. Enabled by default.
//No animations will be performed by this proc.
-/proc/electrocute_mob(mob/living/carbon/victim, power_source, obj/source, siemens_coeff = 1, dist_check = FALSE)
+/proc/electrocute_mob(mob/living/carbon/victim, power_source, obj/source, siemens_coeff = 1, dist_check = FALSE, drain_energy = TRUE)
if(!istype(victim) || ismecha(victim.loc))
return FALSE //feckin mechs are dumb
diff --git a/icons/mecha/inteq_gygax.dmi b/icons/mecha/inteq_gygax.dmi
new file mode 100644
index 0000000000000..08105d783ab99
Binary files /dev/null and b/icons/mecha/inteq_gygax.dmi differ
diff --git a/icons/mecha/mecha.dmi b/icons/mecha/mecha.dmi
index 9b9c2b479c7b0..2993487cb8505 100644
Binary files a/icons/mecha/mecha.dmi and b/icons/mecha/mecha.dmi differ