Files
FenodyreeAvandGitHub d47cdb8f04 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>
2026-08-06 09:40:33 +00:00

386 lines
12 KiB
Plaintext

// Big stompy robots.
/mob/living/heavy_vehicle
name = "exosuit"
density = TRUE
anchored = TRUE
status_flags = PASSEMOTES
a_intent = I_HURT
mob_size = MOB_LARGE
mob_push_flags = ALLMOBS
can_be_buckled = FALSE
accent = ACCENT_TTS
appearance_flags = KEEP_TOGETHER | DEFAULT_APPEARANCE_FLAGS | TILE_BOUND | LONG_GLIDE
pass_flags_self = PASSVEHICLE
var/decal
var/emp_damage = 0
// Used to offset a non-32x32 icon appropriately.
var/offset_x = -8
var/offset_y = 0
var/obj/item/radio/exosuit/radio
var/obj/structure/machinery/camera/camera
/// Whether pilots want the external camera to broadcast while its hardware is functional
var/camera_enabled = TRUE
var/wreckage_path = /obj/structure/mech_wreckage
// Access updating/container.
var/obj/item/card/id/mecha/access_card
// Mob we're currently paired with or following | the names are saved to prevent metagaming when returning diagnostics
var/datum/weakref/leader
var/leader_name
var/datum/weakref/following
var/following_name
// Orders from our leader
var/nickname // we'll respond to our name or our nickname
var/follow_distance = 3
// Mob currently piloting the mech.
var/list/pilots
var/list/pilot_overlays
// Remote control stuff
var/remote = FALSE // Spawns a robotic pilot to be remote controlled
var/remote_type = /obj/item/remote_mecha
var/does_hardpoint_lock = TRUE
var/mob/living/simple_animal/spiderbot/dummy // The remote controlled dummy
var/dummy_type = /mob/living/simple_animal/spiderbot
var/dummy_colour
// Visible external components. Not strictly accurately named for non-humanoid machines (submarines) but w/e
var/obj/item/mech_component/manipulators/arms
var/obj/item/mech_component/propulsion/legs
var/obj/item/mech_component/sensors/head
var/obj/item/mech_component/chassis/body
// Equipment tracking vars.
var/obj/item/mecha_equipment/selected_system
var/selected_hardpoint
var/list/hardpoints = list()
var/hardpoints_locked
var/maintenance_protocols
var/lockdown
var/entry_speed = 30
var/loudening = FALSE // whether we're increasing the speech volume of our pilot
// Material
var/singleton/material/material
// Cockpit access vars.
var/hatch_closed = FALSE
var/hatch_locked = FALSE
var/force_locked = FALSE // Is it possible to unlock the hatch?
var/use_air = FALSE
// Interface stuff.
/// The next world tick that the mech has to wait for before it can change its Throttle (forward and backward movement).
var/next_mecha_move = 0
/// The next world tick that the mech has to wait for before it can turn.
var/next_mecha_turn = 0
/// The next world tick that the mech has to wait for before it can strafe.
var/next_mecha_strafe = 0
var/list/hud_elements = list()
var/list/hardpoint_hud_elements = list()
var/atom/movable/screen/mecha/health/hud_health
var/atom/movable/screen/mecha/toggle/hatch_open/hud_open
var/atom/movable/screen/mecha/power/hud_power
var/atom/movable/screen/mecha/toggle/power_control/hud_power_control
//POWER
var/power = MECH_POWER_OFF
/// Sound effect used for mech ambience.
var/datum/looping_sound/mech_power/soundloop
/// Separate tracking of current sound looping so we aren't hammering the sound loop system on every tick.
var/sound_looping = FALSE
/mob/living/heavy_vehicle/Destroy()
unassign_leader()
unassign_following()
selected_system = null
for(var/hardpoint in hardpoints)
var/obj/item/S = remove_system(hardpoint, force = 1)
if(S)
QDEL_NULL(S)
hardpoints = null
for(var/thing in pilots)
var/mob/pilot = thing
if(pilot.client)
pilot.client.screen -= hud_elements
pilot.client.images -= hud_elements
if (!QDELETED(pilot)) // Forcemove doesn't accept QDELETED inputs.
remove_verb(pilot, /mob/proc/toggle_exosuit_camera)
remove_verb(pilot, /mob/proc/change_exosuit_camera_network)
pilot.forceMove(get_turf(src))
pilots = null
QDEL_LIST(hud_elements)
if(remote_network)
SSvirtualreality.remove_mech(src, remote_network)
hardpoint_hud_elements = null
QDEL_NULL(access_card)
QDEL_NULL(arms)
QDEL_NULL(legs)
QDEL_NULL(head)
QDEL_NULL(body)
QDEL_NULL(hud_health)
QDEL_NULL(hud_open)
QDEL_NULL(hud_power)
QDEL_NULL(hud_power_control)
QDEL_NULL(camera)
QDEL_NULL(radio)
QDEL_NULL(soundloop)
. = ..()
/mob/living/heavy_vehicle/IsAdvancedToolUser()
return TRUE
/mob/living/heavy_vehicle/get_examine_text(mob/user, distance, is_adjacent, infix, suffix)
SHOULD_CALL_PARENT(FALSE) //Special snowflake case
. = list()
if(!user || !user.client)
return TRUE
. += "That's \a <b>[src]</b>."
if(desc)
to_chat(user, desc)
if(LAZYLEN(pilots) && (!hatch_closed || body.pilot_coverage < 100 || body.transparent_cabin))
if(length(pilots) == 0)
. += "It has <b>no pilot</b>."
else
for(var/pilot in pilots)
if(ismob(pilot))
var/mob/M = pilot
. += "It is being <b>piloted</b> by <a href='byond://?src=[REF(src)];examine=[REF(M)]'>[M.name]</a>."
else
. += "It is being <b>piloted</b> by <b>[pilot]</b>."
if(hardpoints.len)
. += SPAN_NOTICE("It has the following hardpoints:")
for(var/hardpoint in hardpoints)
var/obj/item/I = hardpoints[hardpoint]
. += "- <b>[hardpoint]</b>: [istype(I) ? "<a href='byond://?src=[REF(src)];examine=[REF(I)]'>[I.name]</a>" : "nothing"]."
else
. += "It has <b>no visible hardpoints</b>."
for(var/obj/item/mech_component/thing in list(arms, legs, head, body))
if(!thing)
continue
var/damage_string = ""
switch(thing.damage_state)
if(1)
damage_string = "undamaged"
if(2)
damage_string = SPAN_WARNING("damaged")
if(3)
damage_string = SPAN_WARNING("badly damaged")
if(4)
damage_string = SPAN_DANGER("destroyed")
. += "Its <b>[thing.name]</b> [thing.gender == PLURAL ? "are" : "is"] [damage_string]."
/mob/living/heavy_vehicle/Topic(href,href_list[])
if (href_list["examine"])
var/mob/M = locate(href_list["examine"])
if(!M)
return
examinate(usr, M)
/mob/living/heavy_vehicle/Initialize(mapload, var/obj/structure/heavy_vehicle_frame/source_frame)
..()
if(!access_card) access_card = new (src)
if(offset_x) pixel_x = offset_x
if(offset_y) pixel_y = offset_y
radio = new(src)
// Grab all the supplied components.
if(source_frame)
if(source_frame.set_name)
name = source_frame.set_name
if(source_frame.arms)
source_frame.arms.forceMove(src)
arms = source_frame.arms
if(source_frame.legs)
source_frame.legs.forceMove(src)
legs = source_frame.legs
if(legs.hover) //Checks here if hoverthrusters
pass_flags |= PASSRAILING
if(source_frame.head)
source_frame.head.forceMove(src)
head = source_frame.head
if(source_frame.body)
source_frame.body.forceMove(src)
body = source_frame.body
if(body.cell)
RegisterSignal(body.cell, COMSIG_CELL_CHARGE, PROC_REF(handle_cell_charge))
updatehealth()
// Generate hardpoint list.
for(var/obj/item/mech_component/thing in list(arms, legs, head, body))
if(thing && thing.has_hardpoints.len)
for(var/hardpoint in thing.has_hardpoints)
hardpoints[hardpoint] = null
if(head && head.radio)
radio = new(src)
if(!camera)
camera = new /obj/structure/machinery/camera(src, 0, TRUE, TRUE)
camera.c_tag = name
camera.replace_networks(list(NETWORK_MECHS))
// Create HUD.
instantiate_hud()
// Build icon.
update_icon()
add_language(LANGUAGE_TCB)
default_language = GLOB.all_languages[LANGUAGE_TCB]
soundloop = new(src)
. = INITIALIZE_HINT_LATELOAD
/mob/living/heavy_vehicle/LateInitialize()
var/obj/structure/machinery/mech_recharger/MR = locate() in get_turf(src)
if(MR)
MR.start_charging(src)
/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
/// Checks if mech can be powered on/off, sends message to pilot if failed
/// `var/remote` can be set to TRUE to have proc adjust where messages and hud elements are presented
/// If `remote` is TRUE, messages and other hud elements are called on the exosuit itself to prevent wierdness, and errors are handled in `handle_hear_say()`
/mob/living/heavy_vehicle/proc/toggle_power(var/mob/user, var/remote = FALSE)
var/cancelled = FALSE
var/delay = 0
SEND_SIGNAL(user, COMSIG_MECH_TOGGLE_POWER, &cancelled, &delay)
if (cancelled || delay && !do_after(user, delay))
to_chat(user, SPAN_WARNING("There's just too many buttons! You fail to find the power switch!"))
return
// if remotely called, send these messages to the exosuit, not the person calling this proc
var/reciever = user
if(remote)
reciever = src
if(power == MECH_POWER_TRANSITION)
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
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
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."))
/obj/item/radio/exosuit
name = "exosuit radio"
cell = null
/obj/item/radio/exosuit/get_cell()
. = ..()
if(!.)
var/mob/living/heavy_vehicle/E = loc
if(istype(E))
return E.get_cell()
/obj/item/radio/exosuit/ui_host()
var/mob/living/heavy_vehicle/E = loc
if(istype(E))
return E
return null
/obj/item/radio/exosuit/attack_self(var/mob/user)
var/mob/living/heavy_vehicle/exosuit = loc
if(istype(exosuit) && exosuit.head && exosuit.head.radio && exosuit.head.radio.is_functional())
user.set_machine(src)
interact(user)
else
to_chat(user, SPAN_WARNING("The radio is too damaged to function."))
/obj/item/radio/exosuit/CanUseTopic()
. = ..()
if(.)
var/mob/living/heavy_vehicle/exosuit = loc
if(istype(exosuit) && exosuit.head && exosuit.head.radio && exosuit.head.radio.is_functional())
return ..()
/mob/living/heavy_vehicle/proc/become_remote()
for(var/mob/user in pilots)
eject(user, FALSE)
remote = TRUE
name = name + " \"[pick("Jaeger", "Reaver", "Templar", "Juggernaut", "Basilisk")]-[rand(0, 999)]\""
if(camera)
camera.c_tag = name
invalidateCameraCache()
if(!remote_network)
remote_network = REMOTE_GENERIC_MECH
SSvirtualreality.add_mech(src, remote_network)
if(hatch_closed)
hatch_closed = FALSE
dummy = new dummy_type(get_turf(src))
dummy.real_name = "Remote-Bot"
dummy.name = dummy.real_name
// Give dummy a blank encryption key for later editing if spiderbot
if(istype(dummy, /mob/living/simple_animal/spiderbot) && !istype(dummy, /mob/living/simple_animal/spiderbot/ai))
dummy.radio.keyslot = new /obj/item/encryptionkey
remove_verb(dummy, /mob/living/proc/ventcrawl)
remove_verb(dummy, /mob/living/proc/hide)
if(dummy_colour)
dummy.color = dummy_colour
enter(dummy, TRUE)
if(!hatch_closed)
hatch_closed = TRUE
hatch_locked = TRUE
if(does_hardpoint_lock)
hardpoints_locked = TRUE
force_locked = TRUE
update_icon()
/mob/living/heavy_vehicle/is_anti_materiel_vulnerable()
return TRUE