Fixes many mech bugs (#22983)

Four major fixes. These are all pretty clearly bugs, but the result is a
substantial mech nerf (except EMPs against EM armor.)

First, EMP armour.
EMP armour did nothing, it was partially ported to use TG's Element flag
system, this finishes that.
Rather than make the EMP armour immune to EMP, it spends power to negate
it.
The cost is 1000 / severity. Heavy EMP is 1, Light EMP is 2. This is 5%
of a basic mech cell for light, 10% for heavy.
Also fixes the pilot getting EMP'd twice for every EMP act on the mech.

Second, open hatches let attacks hit the pilot. Chance is 80% from the
front, 20% from the sides.
The chance is mostly so that turrets that always aim torso don't shoot
mechs forever, always hitting the pilot inside.

Third, cockpit damage actually matters now. Previously shooting the mech
in the chest was nearly worthless, no important components were there
and destroying it did not destroy the mech.
Now if the cockpit is completely destroyed, attacks against it penetrate
through to hit the pilot.
These attacks still account for the mech's armour.
Combined with the hatch getting stuck closed, you can have tense rescues
of pilots bleeding out inside their suits.

Fourth, the mech running out of power actually stops it doing things.
Uranium and Phoron power cores allowed mechs to keep running forever,
even at fractional power, because they recharged slightly every tick.
Now if the mech ever cannot afford the power cost of an action it shuts
down.
It can be powered back up immediately if the power core recharges it,
but it cannot now do costly actions at single digit power.

I fixed mech combat shields.
They suck, they use the auras system which does not play nicely with the
new projectile code.
Recursive calls and spaghetti mess.
Works for now but could use a complete rewrite.

---------

Signed-off-by: FenodyreeAv <fenodyree.av@gmail.com>
This commit is contained in:
FenodyreeAv
2026-08-06 09:40:33 +00:00
committed by GitHub
parent 9e2975ddb3
commit d47cdb8f04
9 changed files with 162 additions and 48 deletions
+9
View File
@@ -15,6 +15,15 @@ They should also be used for when you want to effect the ENTIRE mob, like having
return COMPONENT_BULLET_BLOCKED
/obj/aura/bullet_act(obj/projectile/hitting_projectile, def_zone, piercing_hit)
var/result = ..()
if(result == BULLET_ACT_BLOCK) //The bullet was blocked, don't check the remaining auras.
return AURA_FALSE|AURA_CANCEL
else if(result == BULLET_ACT_FORCE_PIERCE) //The bullet was forced to pierce, check the remaining auras to see if they block.
return AURA_FALSE
else
return
/obj/aura/Destroy()
if(user)
user.remove_aura(src)
@@ -11,6 +11,7 @@
RAD = ARMOR_RAD_MINOR
)
origin_tech = list(TECH_MATERIAL = 1)
var/emp_protection = 0
/obj/item/robot_parts/robot_component/armor/mech/Initialize()
. = ..()
@@ -33,8 +34,8 @@
origin_tech = list(TECH_MATERIAL = 3)
/obj/item/robot_parts/robot_component/armor/mech/em
name = "EM-shielded armor plating"
desc = "A shielded plating that sorrounds the eletronics and protects them from electromagnetic radiation."
name = "anti EM reactive armor plating"
desc = "An advanced active-defense system that insulates the eletronics and destructively interferes with electromagnetic radiation."
icon_state = "armor_e"
icon_state_broken = "armor_e_broken"
armor = list(
@@ -47,6 +48,7 @@
RAD = ARMOR_RAD_SMALL
)
origin_tech = list(TECH_MATERIAL = 3)
emp_protection = 1
/obj/item/robot_parts/robot_component/armor/mech/combat
name = "heavy combat plating"
@@ -19,7 +19,7 @@
var/obj/item/robot_parts/robot_component/diagnosis_unit/diagnostics
var/obj/item/cell/mecha/cell
var/cell_type = /obj/item/cell/mecha
var/obj/item/robot_parts/robot_component/armor/mech_armor
var/obj/item/robot_parts/robot_component/armor/mech/mech_armor
var/obj/structure/machinery/portable_atmospherics/canister/air_supply
var/datum/gas_mixture/cockpit
var/pilot_offset_x = 0
+49 -19
View File
@@ -296,11 +296,12 @@
desc = "The Hephaestus Armature system is a well liked energy deflector system designed to stop any projectile before it has a chance to become a threat."
icon_state = "shield_droid"
var/obj/aura/mechshield/aura
var/max_charge = 150
var/charge = 150
var/max_charge = 100
var/charge = 100
var/last_recharge = 0
var/charging_rate = 7500 * CELLRATE
var/cooldown = 3.5 SECONDS // Time until we can recharge again after a blocked impact
var/efficiency = 0.1 // How much power is required to recharge 1 point of shield charge
restricted_hardpoints = list(HARDPOINT_BACK)
restricted_software = list(MECH_SOFTWARE_WEAPONS)
module_hints = list(
@@ -331,7 +332,7 @@
last_recharge = world.time
if(difference > 0)
if(difference >= 0)
for(var/mob/pilot in owner.pilots)
to_chat(pilot, FONT_LARGE(SPAN_WARNING("Warning: Deflector shield failure detected, shutting down.")))
toggle()
@@ -379,8 +380,10 @@
if((world.time - last_recharge) < cooldown)
return
var/actual_required_power = clamp(max_charge - charge, 0, charging_rate)
owner.use_cell_power(actual_required_power)
var/actual_required_power = clamp(max_charge * efficiency - charge * efficiency, 0, charging_rate)
var/actual_power_used = owner.use_cell_power(actual_required_power)
if(actual_power_used > 0)
charge = clamp(charge + actual_power_used, 0, max_charge)
/obj/item/mecha_equipment/shield/get_hardpoint_status_value()
return charge / max_charge
@@ -434,24 +437,51 @@
else
icon_state = "shield_null"
/obj/aura/mechshield/handle_bullet_act(datum/source, obj/projectile/projectile)
if(!active || !shields || !shields.charge)
return FALSE
var/incoming_attack_direction = projectile.starting ? get_dir(shields, projectile.starting) : null
if(!(dir & incoming_attack_direction))
return FALSE
var/damage = projectile.get_structure_damage()
if(!damage)
damage = projectile.damage
projectile.damage = shields.stop_damage(damage)
user.visible_message(SPAN_WARNING("\The [shields.owner]'s shields flash and crackle."))
flick("shield_impact", src)
playsound(user, 'sound/effects/basscannon.ogg', 35, TRUE)
//light up the night.
new /obj/effect/smoke/illumination(get_turf(src), 5, 4, 1, "#ffffff")
spark(get_turf(src), 5, GLOB.alldirs)
playsound(get_turf(src), SFX_SPARKS, 25, TRUE)
return COMPONENT_BULLET_BLOCKED
/obj/aura/mechshield/bullet_act(obj/projectile/hitting_projectile, def_zone, piercing_hit)
if(!active)
return
SHOULD_CALL_PARENT(FALSE)
. = ..()
if(!active || !shields || !shields.charge)
return FALSE
if(shields?.charge)
hitting_projectile.damage = shields.stop_damage(hitting_projectile.damage)
user.visible_message(SPAN_WARNING("\The [shields.owner]'s shields flash and crackle."))
flick("shield_impact", src)
playsound(user, 'sound/effects/basscannon.ogg', 35, TRUE)
//light up the night.
new /obj/effect/smoke/illumination(get_turf(src), 5, 4, 1, "#ffffff")
if(hitting_projectile.damage <= 0)
return AURA_FALSE|AURA_CANCEL
var/incoming_attack_direction = hitting_projectile.starting ? get_dir(shields, hitting_projectile.starting) : null
if(!(dir & incoming_attack_direction))
return FALSE
spark(get_turf(src), 5, GLOB.alldirs)
playsound(get_turf(src), SFX_SPARKS, 25, TRUE)
var/damage = hitting_projectile.get_structure_damage()
if(!damage)
damage = hitting_projectile.damage
hitting_projectile.damage = shields.stop_damage(damage)
user.visible_message(SPAN_WARNING("\The [shields.owner]'s shields flash and crackle."))
flick("shield_impact", src)
playsound(user, 'sound/effects/basscannon.ogg', 35, TRUE)
//light up the night.
new /obj/effect/smoke/illumination(get_turf(src), 5, 4, 1, "#ffffff")
spark(get_turf(src), 5, GLOB.alldirs)
playsound(get_turf(src), SFX_SPARKS, 25, TRUE)
return AURA_FALSE|AURA_CANCEL
/obj/aura/mechshield/hitby(atom/movable/hitting_atom, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum)
. = ..()
+62 -20
View File
@@ -1,8 +1,35 @@
///Returns TRUE if an incoming attack should target the pilot instead of the mech, FALSE otherwise.
/mob/living/heavy_vehicle/proc/should_target_pilot(atom/attack_source = null)
if(!body || !LAZYLEN(pilots)) //If there is no pilot, or the mech somehow doesn't have a body don't try hit the pilot.
return FALSE
var/incoming_attack_direction = attack_source ? get_dir(src, attack_source) : null
if (istype(hardpoints[HARDPOINT_BACK], /obj/item/mecha_equipment/shield)) //If the mech has a shield.
var/obj/item/mecha_equipment/shield/shield = hardpoints[HARDPOINT_BACK]
if (shield.active && shield.aura && shield.aura.active) //If the shield is active.
if (incoming_attack_direction && (dir & incoming_attack_direction)) //If the attack comes from the front and the shield is active, the pilot is protected.
return FALSE
if(!prob(body.pilot_coverage)) //If the cockpit doesn't cover the pilot completely, attacks have a chance to hit the pilot instead of the mech.
return TRUE
if(!hatch_closed) //Open hatches only expose the pilot to attacks from the front and a little bit from the sides.
if(incoming_attack_direction)
if (dir & incoming_attack_direction)
if(prob(80)) //80% chance to hit the pilot from the front if the hatch is open. If this is 100% turrets will shoot mechs with open hatches forever.
return TRUE
if ((turn(dir, 90) & incoming_attack_direction) || (turn(dir, -90) & incoming_attack_direction))
if(prob(20)) //20% chance to hit the pilot from the sides if the hatch is open.
return TRUE
return FALSE
/mob/living/heavy_vehicle/apply_effect(var/effect = 0,var/effecttype = STUN, var/blocked = 0)
if(!effect || (blocked >= 100))
return 0
if(LAZYLEN(pilots) && (!hatch_closed || !prob(body.pilot_coverage)))
if(should_target_pilot())
if(effect > 0 && effecttype == DAMAGE_RADIATION)
var/mob/living/pilot = pick(pilots)
return pilot.apply_effect(effect, effecttype, blocked)
@@ -10,11 +37,18 @@
. = ..()
/mob/living/heavy_vehicle/hitby(atom/movable/hitting_atom, skipcatch, hitpush, blocked, datum/thrownthing/throwingdatum)
if(LAZYLEN(pilots) && (!hatch_closed || !prob(body.pilot_coverage)))
if(should_target_pilot(hitting_atom))
var/mob/living/pilot = pick(pilots)
return pilot.hitby(arglist(args))
. = ..()
/mob/living/heavy_vehicle/bullet_act(obj/projectile/hitting_projectile, def_zone, piercing_hit)
if(should_target_pilot(hitting_projectile?.starting))
var/mob/living/pilot = pick(pilots)
if(pilot)
return pilot.bullet_act(hitting_projectile, def_zone, piercing_hit)
return ..()
/mob/living/heavy_vehicle/get_armors_by_zone(def_zone, damage_type, damage_flags)
. = ..()
if(body)
@@ -82,6 +116,13 @@
return 0
var/target = zoneToComponent(def_zone)
if(target == body && body.damage_state == MECH_COMPONENT_DAMAGE_DAMAGED_TOTAL) //If the cockpit is destroyed, subsequent damage is applied to the pilot, modified by the mech's armour.
if(body && LAZYLEN(pilots))
var/mob/living/pilot = pick(pilots)
visible_message(SPAN_DANGER("\The [used_weapon] pierces the mangled cockpit of \the [src], striking the pilot inside!"))
pilot.apply_damage(damage, damagetype, def_zone, used_weapon, damage_flags, armor_pen, silent = FALSE)
//Only 2 types of damage concern mechs and vehicles
switch(damagetype)
if(DAMAGE_BRUTE)
@@ -114,25 +155,26 @@
/mob/living/heavy_vehicle/emp_act(severity)
. = ..()
if(. & EMP_PROTECT_SELF)
var/obj/item/cell/C = get_cell()
if(C.charge > 1000) //Don't completely run a mech out of power this way.
var/power_used = use_cell_power(1000 / severity)
var/percent_power_used = 0
if(C && C.maxcharge)
percent_power_used = round((power_used / C.maxcharge) * 100)
visible_message(SPAN_NOTICE("\The [src]'s active EM defenses flash brightly, negating the EMP!"))
for(var/pilot in pilots)
if(ismob(pilot))
to_chat(pilot, SPAN_NOTICE("Your mech reports that negating the EMP cost [(percent_power_used)]% charge."))
else
for(var/pilot in pilots)
if(ismob(pilot))
to_chat(pilot, SPAN_DANGER("Your mech's EMP countermeasures deactivate as power levels drop too low."))
else
var/ratio = get_blocked_ratio(null, DAMAGE_BURN, null, (4-severity) * 20)
emp_damage += round((12 - (severity*3))*( 1 - ratio))
var/ratio = get_blocked_ratio(null, DAMAGE_BURN, null, (4-severity) * 20)
if(ratio >= 0.5)
for(var/mob/living/m in pilots)
to_chat(m, SPAN_NOTICE("Your Faraday shielding absorbed the pulse!"))
return
else if(ratio > 0)
for(var/mob/living/m in pilots)
to_chat(m, SPAN_NOTICE("Your Faraday shielding mitigated the pulse!"))
emp_damage += round((12 - (severity*3))*( 1 - ratio))
for(var/obj/item/thing in list(arms,legs,head,body))
thing.emp_act(severity)
if(!hatch_closed || !prob(body.pilot_coverage))
for(var/thing in pilots)
var/mob/pilot = thing
pilot.emp_act(severity)
update_emp_protection()
/mob/living/heavy_vehicle/fall_impact(levels_fallen, stopped_early = FALSE, var/damage_mod = 1)
// No gravity, stop falling into spess!
+14 -2
View File
@@ -161,20 +161,32 @@
/mob/living/heavy_vehicle/proc/use_cell_power(var/power_to_use)
var/power_used = get_cell()?.use(power_to_use)
if(power_used <= 0)
if(power_used < power_to_use - 1) //Self recharging cells would otherwise allow a mech to run indefinately at full functionality with 0.1% charge.
for(var/hardpoint in hardpoints)
var/obj/item/mecha_equipment/ME = hardpoints[hardpoint]
if(ME)
ME.deactivate()
if(power == MECH_POWER_ON)
playsound(src, 'sound/mecha/mech-shutdown.ogg', 100, 0)
power = MECH_POWER_OFF
update_emp_protection()
if(hud_power_control)
SSicon_update.add_to_queue(hud_power_control)
return power_used
/mob/living/heavy_vehicle/proc/drain_cell_power(var/power_to_drain)
var/power_used = get_cell()?.drain_power(0, 0, power_to_drain)
if(power_used <= 0)
if(power_used < power_to_drain - 1) //Self recharging cells would otherwise allow a mech to run indefinately at full functionality with 0.1% charge.
for(var/hardpoint in hardpoints)
var/obj/item/mecha_equipment/ME = hardpoints[hardpoint]
if(ME)
ME.deactivate()
if(power == MECH_POWER_ON)
playsound(src, 'sound/mecha/mech-shutdown.ogg', 100, 0)
power = MECH_POWER_OFF
update_emp_protection()
if(hud_power_control)
SSicon_update.add_to_queue(hud_power_control)
return power_used
/mob/living/heavy_vehicle/proc/checked_use_cell(var/power_to_drain)
+12 -2
View File
@@ -266,6 +266,12 @@
/mob/living/heavy_vehicle/return_air()
return (body && body.pilot_coverage >= 100 && hatch_closed) ? body.cockpit : loc?.return_air()
/mob/living/heavy_vehicle/proc/update_emp_protection()
RemoveElement(/datum/element/empprotection, EMP_PROTECT_ALL)
if(power == MECH_POWER_ON && body.mech_armor && body.mech_armor.emp_protection)
if(get_cell().charge > 500)
AddElement(/datum/element/empprotection, EMP_PROTECT_ALL)
/mob/living/heavy_vehicle/GetIdCard()
return access_card
@@ -288,18 +294,22 @@
to_chat(reciever, SPAN_NOTICE("Power transition in progress. Please wait."))
else if(power == MECH_POWER_ON) //Turning it off is instant
power = MECH_POWER_OFF
else if(get_cell(TRUE))
update_emp_protection()
else if(get_cell(TRUE).check_charge(1000)) //Check if we have enough charge to power on
//Start power up sequence
power = MECH_POWER_TRANSITION
playsound(src, 'sound/mecha/powerup.ogg', 50, 0)
if(do_after(reciever, 1.5 SECONDS) && power == MECH_POWER_TRANSITION)
power = MECH_POWER_ON
update_emp_protection()
sound_looping = TRUE
soundloop.start()
else
to_chat(reciever, SPAN_WARNING("You abort the powerup sequence."))
power = MECH_POWER_OFF
hud_power_control?.queue_icon_update()
update_emp_protection()
if(hud_power_control)
SSicon_update.add_to_queue(hud_power_control)
else
to_chat(reciever, SPAN_WARNING("Error: No power cell was detected."))
+1 -2
View File
@@ -56,8 +56,7 @@
//Auras, essentially magic and encompassing around us, gets checked first
if(!src.aura_check(AURA_TYPE_BULLET, hitting_projectile, def_zone, piercing_hit))
blocked = 100
return BULLET_ACT_FORCE_PIERCE
return BULLET_ACT_BLOCK
//Shields, in front of us (hopefully), check the bullet before it reaches us
var/shield_check = check_shields(hitting_projectile.damage, hitting_projectile, hitting_projectile.firer, def_zone, "the [hitting_projectile.name]")
@@ -0,0 +1,10 @@
author: Fenodyree
delete-after: True
changes:
- bugfix: "Fixes EMP armor for mechs."
- bugfix: "Fixes mech pilot's getting EMP'd twice for every EMP hitting the mech."
- bugfix: "Fixes shields for mechs."
- bugfix: "Fixes mechs being able to draw infinite power, as long as there was any power in their cell."
- bugfix: "Fixes cockpit coverage not letting the pilot be hit."
- bugfix: "Fixes legacy icon updates."
- rscadd: "Adds a chance for attacks to hit the mech's pilot after the cockpit has been destroyed. This still accounts for the mech's armor and shields."