From a436fa65a612339e6f2a6d9371461fab41eb048e Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Sat, 4 Apr 2026 18:27:01 -0400 Subject: [PATCH] More Mech Damage Feedback (#22118) This PR adds some new "Damage Feedback" interactions to mechs, which I'm adding in response to what I think is player perception of mechs being invincible. Previously mech manipulators and torsos had no actual mechanics for damage states, and having 0 damage on a mechs arms was functionally the same thing as having 100 damage, so long as the arm wasn't completely destroyed. This PR changes that by making it so that mech manipulators have a chance to fail (loudly and visibly) on use with a probability that varies based on how much damage the arms have taken. Additionally, there's also now a mechanic for mech ejections and hatch opening to fail if the torso has taken sufficient damage, with chances that also vary based on how much damage said torso has taken. In both cases, the mech plays audible and visual effects to indicate to the players involved that it is being hindered by damage. https://github.com/user-attachments/assets/934e4561-1fda-4c47-9770-438960834aa9 Yes this does mean that you can smash someone's hatch closed, to a point you'll need a welder to get them out. image --- .../heavy_vehicle/components/_components.dm | 6 ++ code/modules/heavy_vehicle/components/body.dm | 7 ++ .../heavy_vehicle/interface/screen_objects.dm | 19 +++- .../modules/heavy_vehicle/mech_interaction.dm | 98 +++++++++++++------ code/modules/heavy_vehicle/premade/combat.dm | 2 +- code/modules/heavy_vehicle/premade/cult.dm | 2 +- code/modules/heavy_vehicle/premade/heavy.dm | 2 +- code/modules/heavy_vehicle/premade/light.dm | 2 +- .../modules/heavy_vehicle/premade/military.dm | 2 +- .../heavy_vehicle/premade/powerloader.dm | 2 +- code/modules/heavy_vehicle/premade/pra.dm | 4 +- .../hellfirejag-mech-damage-feedback.yml | 9 ++ 12 files changed, 118 insertions(+), 37 deletions(-) create mode 100644 html/changelogs/hellfirejag-mech-damage-feedback.yml diff --git a/code/modules/heavy_vehicle/components/_components.dm b/code/modules/heavy_vehicle/components/_components.dm index 6e5d716b27d..07688994f92 100644 --- a/code/modules/heavy_vehicle/components/_components.dm +++ b/code/modules/heavy_vehicle/components/_components.dm @@ -9,13 +9,19 @@ var/total_damage = 0 var/brute_damage = 0 var/burn_damage = 0 + + /// The maximum combined brute and burn damage that the mech component can take before becoming completely broken. var/max_damage = 120 + var/damage_state = 1 var/list/has_hardpoints = list() var/power_use = 0 matter = list(DEFAULT_WALL_MATERIAL = 15000, MATERIAL_PLASTIC = 1000, MATERIAL_OSMIUM = 500) dir = SOUTH + /// Half of the baseline chance that attempting to use these arms will create sparks. Actual chance is twice this since the motivator damage contributes to the ratio. + var/spark_chance_ratio = 50 + /obj/item/mech_component/feedback_hints(mob/user, distance, is_adjacent) . += ..() if(ready_to_install()) diff --git a/code/modules/heavy_vehicle/components/body.dm b/code/modules/heavy_vehicle/components/body.dm index 90397a0b26e..e243cea2052 100644 --- a/code/modules/heavy_vehicle/components/body.dm +++ b/code/modules/heavy_vehicle/components/body.dm @@ -32,6 +32,13 @@ var/hide_pilot = TRUE has_hardpoints = list(HARDPOINT_BACK, HARDPOINT_LEFT_SHOULDER, HARDPOINT_RIGHT_SHOULDER) + /** + * Maximum % chance for an ejection to fail based on how much damage the mech's torso has taken. + * If you're stuck, better hope there's a friend available to bail you out.. + * Or you can keep the hatch open if you would rather risk getting zipped by incoming fire. + */ + var/ejection_fail_chance = 75 + /obj/item/mech_component/chassis/Initialize() . = ..() AddComponent(/datum/component/armor, armor, ARMOR_TYPE_STANDARD|ARMOR_TYPE_EXOSUIT) diff --git a/code/modules/heavy_vehicle/interface/screen_objects.dm b/code/modules/heavy_vehicle/interface/screen_objects.dm index c0760b1608e..2b0faf64527 100644 --- a/code/modules/heavy_vehicle/interface/screen_objects.dm +++ b/code/modules/heavy_vehicle/interface/screen_objects.dm @@ -211,6 +211,8 @@ /atom/movable/screen/mecha/toggle/air/toggled() toggled = !toggled owner.use_air = toggled + if (owner.use_air) + playsound(owner.loc, 'sound/effects/internals.ogg', 75, 1) var/main_color = owner.use_air ? "#d1d1d1" : "#525252" maptext = "AIR" notify_user(usr, SPAN_NOTICE("Auxiliary atmospheric system [owner.use_air ? "enabled" : "disabled"].")) @@ -286,6 +288,7 @@ maptext_y = 11 maptext_x = 5 notify_user(usr, SPAN_NOTICE("The [owner.body.hatch_descriptor] is [owner.hatch_locked ? "now" : "no longer" ] locked.")) + playsound(owner.loc, 'sound/effects/magnetclamp.ogg', 100, 1) /atom/movable/screen/mecha/toggle/hatch_open name = "open or close hatch" @@ -298,15 +301,29 @@ maptext_x = owner.hatch_closed ? 4 : 3 /atom/movable/screen/mecha/toggle/hatch_open/toggled(var/notify_user = TRUE) + var/obj/item/mech_component/chassis/body = owner.body + if(!body) // Edge case for the mech SOMEHOW not having a body. Players shouldn't ever see this. A mech without a body should be in the process of exploding. + notify_user(usr, SPAN_WARNING("What hatch? There's nothing left to open. If you're seeing this, please submit a bug report to our github issue tracker and tell us how!")) + return FALSE + if(owner.hatch_locked && owner.hatch_closed) notify_user(usr, SPAN_WARNING("You cannot open the hatch while it is locked.")) - return + return FALSE + + if(owner.hatch_closed && body.total_damage > 1 && prob((body.total_damage / body.max_damage) * body.ejection_fail_chance)) + to_chat(usr, SPAN_WARNING("You pull the hatch release. The hatch bolts hiss and clang with a deafening screech, but the hatch fails to open!")) + spark(owner, 6, GLOB.alldirs) + playsound(owner.loc, 'sound/effects/metalhit.ogg', 75, 1) + return FALSE + toggled = !toggled owner.hatch_closed = toggled + playsound(owner.loc, owner.hatch_closed ? 'sound/effects/metal_close.ogg' : 'sound/effects/air_seal.ogg', 75, 1) if(notify_user) notify_user(usr, SPAN_NOTICE("The [owner.body.hatch_descriptor] is now [owner.hatch_closed ? "closed" : "open" ].")) update_icon() owner.update_icon() + return TRUE // This is basically just a holder for the updates the mech does. /atom/movable/screen/mecha/health diff --git a/code/modules/heavy_vehicle/mech_interaction.dm b/code/modules/heavy_vehicle/mech_interaction.dm index 73873c580c9..b8b993bebab 100644 --- a/code/modules/heavy_vehicle/mech_interaction.dm +++ b/code/modules/heavy_vehicle/mech_interaction.dm @@ -43,24 +43,29 @@ setClickCooldown(5) return - if(!(user in pilots) && user != src) - return - - // Are we facing the target? - if(!(get_dir(src, A) & dir)) - return - - if(!canClick()) + if(/* Do we have a pilot? */\ + !(user in pilots) && user != src \ + /* Are we facing the target? */\ + || !(get_dir(src, A) & dir) \ + /* Can we click on them? */\ + || !canClick()) return + // Check if the arms were completely destroyed. if(!arms) to_chat(user, SPAN_WARNING("\The [src] has no manipulators!")) setClickCooldown(3) + // Give some visible feedback for the mech damage preventing this. + spark(src, 2, GLOB.alldirs) return - if(!arms.motivator || !arms.motivator.is_functional()) + var/obj/item/robot_parts/robot_component/actuator/motivator = arms.motivator + // Check if the motivators were broken. + if(!motivator || !motivator.is_functional()) to_chat(user, SPAN_WARNING("Your motivators are damaged! You can't use your manipulators!")) setClickCooldown(15) + // Give some visible feedback for the mech damage preventing this. + spark(src, 2, GLOB.alldirs) return if(!checked_use_cell(arms.power_use * CELLRATE)) @@ -74,10 +79,18 @@ else zone_sel.set_selected_zone("chest", user) + // Get the ratio of damage over max damage + var/spark_chance = (motivator.total_dam / motivator.max_dam + arms.total_damage / arms.max_damage) \ + /* Multiply by the spark chance */\ + * arms.spark_chance_ratio \ + /* Then add the contribution from EMP damage. */\ + + emp_damage + // You may attack the target with your exosuit FIST if you're malfunctioning. var/failed = FALSE - if(emp_damage > EMP_ATTACK_DISRUPT && prob(emp_damage*2)) - to_chat(user, SPAN_WARNING("The wiring sparks as you attempt to control the exosuit!")) + if(prob(spark_chance)) + to_chat(user, SPAN_WARNING("Your exosuit's manipulators spark as you attempt to control them!")) + spark(src, 3, GLOB.alldirs) failed = TRUE if(!failed) @@ -146,7 +159,7 @@ D.TryToSwitchState(user) return else if(adj) - setClickCooldown(arms ? arms.action_delay : 15) + setClickCooldown(arms.action_delay) playsound(src.loc, arms.punch_sound, 45 + 25 * (arms.melee_damage / 50), -1 ) if(ismob(A)) var/mob/target = A @@ -246,7 +259,8 @@ if(hatch_locked) if(!silent) to_chat(user, SPAN_WARNING("The [body.hatch_descriptor] is locked.")) return - hud_open.toggled(FALSE) + if(!hud_open.toggled(FALSE)) + return if(!silent) to_chat(user, SPAN_NOTICE("You open the hatch and climb out of \the [src].")) else @@ -362,7 +376,7 @@ /mob/living/heavy_vehicle/Move(atom/newloc, direct, glide_size_override = 0, update_dir = TRUE) // They shouldn't get to this proc without legs in the first place, but its okay to guard here. - if (!legs) + if (!legs || !legs.motivator) return . = ..() set_glide_size(DELAY_TO_GLIDE_SIZE(next_mecha_move - world.time)) @@ -372,6 +386,21 @@ use_cell_power(legs.power_use * CELLRATE) update_icon() + // Handling for leg damage feedback on movement. + var/obj/item/robot_parts/robot_component/actuator/motivator = legs.motivator + + // Get the ratio of damage over max damage. + var/spark_chance = (motivator.total_dam / motivator.max_dam + legs.total_damage / legs.max_damage) \ + /* Then multiply by the spark chance. */\ + * legs.spark_chance_ratio \ + /* And finally add the contribution from EMP damage. */\ + + emp_damage + + if(prob(spark_chance)) + for (var/mob/pilot in pilots) + to_chat(pilot, SPAN_WARNING("Your exosuit's legs spark as you attempt to control them!")) + spark(src, 3, GLOB.alldirs) + /mob/living/heavy_vehicle/Post_Incorpmove() if(istype(hardpoints[HARDPOINT_BACK], /obj/item/mecha_equipment/phazon)) var/obj/item/mecha_equipment/phazon/PZ = hardpoints[HARDPOINT_BACK] @@ -537,28 +566,41 @@ return ..() /mob/living/heavy_vehicle/attack_hand(var/mob/user) - // Drag the pilot out if possible. - if(user.a_intent == I_GRAB) - if(!LAZYLEN(pilots)) - to_chat(user, SPAN_WARNING("There is nobody inside \the [src].")) - else if(!hatch_closed) + // Try to attack on harm intent. + if(user.a_intent == I_HURT) + attack_generic(user) + return + + // Otherwise try to toggle the hatch. + if(!body) // Edge case for the mech SOMEHOW not having a body. Players shouldn't ever see this. A mech without a body should be in the process of exploding. + to_chat(user, SPAN_WARNING("What hatch? There's nothing left to open. If you're seeing this, please submit a bug report to our github issue tracker and tell us how!")) + return + + if(hatch_locked) + to_chat(user, SPAN_WARNING("The [body.hatch_descriptor] is locked.")) + return + + if(!hatch_closed) + // Drag the pilot out if possible. + if(user.a_intent == I_GRAB) + if(!LAZYLEN(pilots)) + to_chat(user, SPAN_WARNING("There is nobody inside \the [src].")) + return + var/mob/pilot = pick(pilots) user.visible_message(SPAN_DANGER("\The [user] is trying to pull \the [pilot] out of \the [src]!")) if(do_after(user, 30) && user.Adjacent(src) && (pilot in pilots) && !hatch_closed) user.visible_message(SPAN_DANGER("\The [user] drags \the [pilot] out of \the [src]!")) eject(pilot, silent=1) - + return + else if (body.total_damage > 1 && prob((body.total_damage / body.max_damage) * body.ejection_fail_chance)) + to_chat(user, SPAN_WARNING("You pull the hatch release. The hatch bolts hiss and clang with a deafening screech, but the hatch fails to open!")) + spark(src, 6, GLOB.alldirs) + playsound(loc, 'sound/effects/metalhit.ogg', 75, 1) return - if(user.a_intent == I_HURT) - attack_generic(user) - return - - // Otherwise toggle the hatch. - if(hatch_locked) - to_chat(user, SPAN_WARNING("The [body.hatch_descriptor] is locked.")) - return hatch_closed = !hatch_closed + playsound(loc, hatch_closed ? 'sound/effects/metal_close.ogg' : 'sound/effects/air_seal.ogg', 75, 1) to_chat(user, SPAN_NOTICE("You [hatch_closed ? "close" : "open"] the [body.hatch_descriptor].")) hud_open.update_icon() update_icon() diff --git a/code/modules/heavy_vehicle/premade/combat.dm b/code/modules/heavy_vehicle/premade/combat.dm index 970a79690cd..c82823f8395 100644 --- a/code/modules/heavy_vehicle/premade/combat.dm +++ b/code/modules/heavy_vehicle/premade/combat.dm @@ -19,7 +19,7 @@ icon_state = "combat_arms" melee_damage = 30 action_delay = 5 - max_damage = 130 + max_damage = 195 power_use = 2500 has_hardpoints = list(HARDPOINT_LEFT_SHOULDER, HARDPOINT_RIGHT_SHOULDER) diff --git a/code/modules/heavy_vehicle/premade/cult.dm b/code/modules/heavy_vehicle/premade/cult.dm index ca4fbbb14e5..7a168930d53 100644 --- a/code/modules/heavy_vehicle/premade/cult.dm +++ b/code/modules/heavy_vehicle/premade/cult.dm @@ -24,7 +24,7 @@ icon_state = "cult_arms" melee_damage = 50 action_delay = 5 - max_damage = 90 + max_damage = 135 power_use = 3500 punch_sound = 'sound/mecha/mech_punch_slow.ogg' diff --git a/code/modules/heavy_vehicle/premade/heavy.dm b/code/modules/heavy_vehicle/premade/heavy.dm index 5757bd6a2dc..43590de0fb2 100644 --- a/code/modules/heavy_vehicle/premade/heavy.dm +++ b/code/modules/heavy_vehicle/premade/heavy.dm @@ -20,7 +20,7 @@ desc = "Designed to function where any other piece of equipment would have long fallen apart, the Hephaestus Superheavy Lifter series can take a beating and excel at delivering it." melee_damage = 50 action_delay = 15 - max_damage = 200 + max_damage = 300 power_use = 3500 punch_sound = 'sound/mecha/mech_punch_slow.ogg' diff --git a/code/modules/heavy_vehicle/premade/light.dm b/code/modules/heavy_vehicle/premade/light.dm index d69dafbc7f7..5678fa094d7 100644 --- a/code/modules/heavy_vehicle/premade/light.dm +++ b/code/modules/heavy_vehicle/premade/light.dm @@ -18,7 +18,7 @@ icon_state = "light_arms" melee_damage = 15 action_delay = 15 - max_damage = 100 + max_damage = 150 power_use = 1500 desc = "As flexible as they are fragile, these Bishop Cybernetics manipulators can follow a pilot's movements in close to real time." punch_sound = 'sound/mecha/mech_punch_fast.ogg' diff --git a/code/modules/heavy_vehicle/premade/military.dm b/code/modules/heavy_vehicle/premade/military.dm index 79d3f691d59..46943b76fc5 100644 --- a/code/modules/heavy_vehicle/premade/military.dm +++ b/code/modules/heavy_vehicle/premade/military.dm @@ -5,7 +5,7 @@ desc = "These basic, heavy plasteel mounts are used for mounting only the meanest weapons. Probably good at breaking things, too." melee_damage = 50 action_delay = 15 - max_damage = 4000 + max_damage = 900 power_use = 3500 has_hardpoints = list(HARDPOINT_LEFT_SHOULDER, HARDPOINT_RIGHT_SHOULDER) punch_sound = 'sound/mecha/mech_punch_slow.ogg' diff --git a/code/modules/heavy_vehicle/premade/powerloader.dm b/code/modules/heavy_vehicle/premade/powerloader.dm index bd788417190..e96afd9c827 100644 --- a/code/modules/heavy_vehicle/premade/powerloader.dm +++ b/code/modules/heavy_vehicle/premade/powerloader.dm @@ -42,7 +42,7 @@ /obj/item/mech_component/manipulators/ripley name = "exosuit arms" exosuit_desc_string = "heavy-duty industrial lifters" - max_damage = 150 + max_damage = 225 power_use = 1000 melee_damage = 40 desc = "The Xion Manufacturing Group Digital Interaction Manifolds allow you poke untold dangers from the relative safety of your cockpit." diff --git a/code/modules/heavy_vehicle/premade/pra.dm b/code/modules/heavy_vehicle/premade/pra.dm index 8468fb05e7d..f724850bcdb 100644 --- a/code/modules/heavy_vehicle/premade/pra.dm +++ b/code/modules/heavy_vehicle/premade/pra.dm @@ -18,7 +18,7 @@ icon_state = "egg_arms" melee_damage = 15 action_delay = 5 - max_damage = 100 + max_damage = 150 power_use = 2500 has_hardpoints = list(HARDPOINT_RIGHT_HAND, HARDPOINT_LEFT_HAND) @@ -81,7 +81,7 @@ desc = "Armored robotics arms designed to carry large weapons." icon_state = "strong_egg_arms" melee_damage = 20 - max_damage = 120 + max_damage = 180 /obj/item/mech_component/sensors/pra_egg/armored name = "armored P'kus-3 sensors" diff --git a/html/changelogs/hellfirejag-mech-damage-feedback.yml b/html/changelogs/hellfirejag-mech-damage-feedback.yml new file mode 100644 index 00000000000..fca80e9e4e7 --- /dev/null +++ b/html/changelogs/hellfirejag-mech-damage-feedback.yml @@ -0,0 +1,9 @@ +author: Hellfirejag +delete-after: True +changes: + - rscadd: "Mech arms now have a chance to fail on use when damaged, which scales proportionally with said damage." + - balance: "Since damage to mech arms now matters, the max health of all mech arms has been increased by 50%." + - balance: "Nerfed Superheavy mech arms to be more in line with other Superheavy mech components." + - rscadd: "Mech arms and legs now have a chance to create electrical sparks when utilized while damaged. This provides visual and audible feedback for a mech being damaged." + - rscadd: "Ejecting from a mech or opening its canopy now has a chance to fail loudly based on how much damage its center torso has taken." + - rscadd: "Added sound effects to mechs for opening and closing hatches, toggling the hatch lock, and for toggling the mech's air supply."