From 58f8cd11ac12874ca0f07b24680c22b1e47190c4 Mon Sep 17 00:00:00 2001 From: VMSolidus Date: Thu, 7 Aug 2025 18:42:58 -0400 Subject: [PATCH] Mech Quality Of Life Additions (#21081) This PR adds a few much needed new features to mechs, with the main one being Mechanics Hints. Mechs have a unique way of handling mechanical hints, in that shift-clicking on a mech to get these hints will reveal the hints for its equipped modules. I've also gone through and eliminated all of the lag causing hard deletes present in the mech code. In a typical round these won't occur all that often, but if I'm touching all these files I might as well do my part and certify them free of hard deletes. Lastly, after talking with Shaft Mining players, I discovered that the main reason they don't ever want to use mining mechs is that the warp extraction bag is strictly superior to mining with a mech, as it lets them teleport ores directly to the ship. So I've added an option to link ore summoners directly to these "Warp crates", allowing a mech equipped with one to teleport ores to the ship. Just like the bag can. --- code/game/atom/atom_examine.dm | 6 +- .../heavy_vehicle/equipment/_equipment.dm | 12 ++++ .../modules/heavy_vehicle/equipment/combat.dm | 15 ++++ code/modules/heavy_vehicle/equipment/cult.dm | 4 ++ .../heavy_vehicle/equipment/engineering.dm | 5 ++ .../heavy_vehicle/equipment/medical.dm | 19 +++++ .../modules/heavy_vehicle/equipment/mining.dm | 70 ++++++++++++++++--- .../heavy_vehicle/equipment/utility.dm | 57 ++++++++++++++- code/modules/heavy_vehicle/mecha.dm | 17 +++++ html/changelogs/Hellfirejag-exosuit-stuff.yml | 6 ++ 10 files changed, 200 insertions(+), 11 deletions(-) create mode 100644 html/changelogs/Hellfirejag-exosuit-stuff.yml diff --git a/code/game/atom/atom_examine.dm b/code/game/atom/atom_examine.dm index 2a19ebe33bd..d91c050b727 100644 --- a/code/game/atom/atom_examine.dm +++ b/code/game/atom/atom_examine.dm @@ -177,7 +177,11 @@ usr_client.Click(src, loc, null, mouseparams) return TRUE -/// Builds the text block variables for get_examine_text +/** + * Builds the text block variables for get_examine_text. + * Objects themselves are responsible for handling their own logic to build these hints. + * See mecha.dm for an example of a system that relays hints from contained items to a parent. + */ /atom/proc/update_desc_blocks(mob/user, distance, is_adjacent) var/list/mechanics_hints = mechanics_hints(user, distance, is_adjacent) var/list/assembly_hints = assembly_hints(user, distance, is_adjacent) diff --git a/code/modules/heavy_vehicle/equipment/_equipment.dm b/code/modules/heavy_vehicle/equipment/_equipment.dm index bd74ad98b3a..edee00efdef 100644 --- a/code/modules/heavy_vehicle/equipment/_equipment.dm +++ b/code/modules/heavy_vehicle/equipment/_equipment.dm @@ -16,6 +16,7 @@ var/active_power_use = 1 KILO WATTS var/require_adjacent = TRUE var/active = FALSE //For gear that has an active state (ie, floodlights) + var/list/module_hints = list() /obj/item/mecha_equipment/get_examine_text(mob/user, distance, is_adjacent, infix, suffix) . = ..() @@ -26,6 +27,17 @@ var/software = english_list(restricted_software, and_text = ", ") . += SPAN_NOTICE("Exosuit Software Requirement: [software]") +/obj/item/mecha_equipment/mechanics_hints(mob/user, distance, is_adjacent) + . += ..() + if (!length(module_hints)) + return + . += "When mounted on a mech, it gains the following mechanics:" + . += module_hints + +/// Called from the parent mech when a player requests the mech's mechanics hints. +/obj/item/mecha_equipment/proc/relayed_mechanics_hints(mob/user, distance, is_adjacent) + . += module_hints + /obj/item/mecha_equipment/attack(mob/living/target_mob, mob/living/user, target_zone) //Generally it's not desired to be able to attack with items return 0 diff --git a/code/modules/heavy_vehicle/equipment/combat.dm b/code/modules/heavy_vehicle/equipment/combat.dm index ba48870f6cb..a4a1ec56cab 100644 --- a/code/modules/heavy_vehicle/equipment/combat.dm +++ b/code/modules/heavy_vehicle/equipment/combat.dm @@ -6,6 +6,11 @@ icon_state = "mecha_taser" restricted_hardpoints = list(HARDPOINT_LEFT_HAND, HARDPOINT_RIGHT_HAND, HARDPOINT_LEFT_SHOULDER, HARDPOINT_RIGHT_SHOULDER) restricted_software = list(MECH_SOFTWARE_WEAPONS) + module_hints = list( + "Left Click: Fire a projectile in the target direction.", + "A mech can only fire within a 90 degree arc in the direction it is currently facing.", + "This weapon passively regenerates its ammunition using the mech's power supply.", + ) /obj/item/mecha_equipment/mounted_system/combat/CtrlClick(mob/user) if(owner && istype(holding, /obj/item/gun)) @@ -288,6 +293,12 @@ var/cooldown = 3.5 SECONDS // Time until we can recharge again after a blocked impact restricted_hardpoints = list(HARDPOINT_BACK) restricted_software = list(MECH_SOFTWARE_WEAPONS) + module_hints = list( + "Alt Click(Icon): Activates the shield drone.", + "When active, a large energy shield will appear in the mech's front facing arc.", + "The shield consumes a kilowatt of power each second while active.", + "It also consumes power to block incoming damage, and will shut down if it runs out.", + ) /obj/item/mecha_equipment/shield/installed(mob/living/heavy_vehicle/_owner) . = ..() @@ -367,6 +378,10 @@ /obj/item/mecha_equipment/shield/get_hardpoint_maptext() return "[(aura && aura.active) ? "ONLINE" : "OFFLINE"]: [round((charge / max_charge) * 100)]%" +/obj/item/mecha_equipment/shield/Destroy() + aura = null + return ..() + /obj/aura/mechshield icon = 'icons/mecha/shield.dmi' name = "mechshield" diff --git a/code/modules/heavy_vehicle/equipment/cult.dm b/code/modules/heavy_vehicle/equipment/cult.dm index 8542df6d587..8b6b6dc3068 100644 --- a/code/modules/heavy_vehicle/equipment/cult.dm +++ b/code/modules/heavy_vehicle/equipment/cult.dm @@ -25,3 +25,7 @@ if(!owner) return doomblade.attack(target_mob, user, user.zone_sel.selecting) + +/obj/item/mecha_equipment/doomblade/Destroy() + doomblade = null + return ..() diff --git a/code/modules/heavy_vehicle/equipment/engineering.dm b/code/modules/heavy_vehicle/equipment/engineering.dm index 366541d1ae6..c717ed1acd4 100644 --- a/code/modules/heavy_vehicle/equipment/engineering.dm +++ b/code/modules/heavy_vehicle/equipment/engineering.dm @@ -4,6 +4,11 @@ holding_type = /obj/item/rfd/construction/mounted/exosuit restricted_hardpoints = list(HARDPOINT_LEFT_HAND, HARDPOINT_RIGHT_HAND) restricted_software = list(MECH_SOFTWARE_ENGINEERING) + module_hints = list( + "Control Click(Self): Activate the RFD's mode selection radial menu.", + "Left Click(Target): Use the RFD on the target.", + "Matter used for the RFD is synthesized directly from the mech's power supply via a phoron-catalyzed mass fabricator.", + ) /obj/item/mecha_equipment/mounted_system/rfd/CtrlClick(mob/user) if(owner && istype(holding, /obj/item/rfd/construction/mounted/exosuit)) diff --git a/code/modules/heavy_vehicle/equipment/medical.dm b/code/modules/heavy_vehicle/equipment/medical.dm index 9f906dcc6a9..396d1499a2b 100644 --- a/code/modules/heavy_vehicle/equipment/medical.dm +++ b/code/modules/heavy_vehicle/equipment/medical.dm @@ -9,6 +9,12 @@ origin_tech = list(TECH_DATA = 2, TECH_BIO = 3) passive_power_use = 25 var/obj/machinery/sleeper/mounted/sleeper = null + module_hints = list( + "Left Click(Living Target): Load the target into the mech's onboard Medical Sleeper unit.", + "Alt Click(Icon): Activate the sleeper unit's control interface.", + "Mounted sleepers are capable of performing any task that a stationary sleeper can do.", + "This includes putting a patient into stasis, injecting medicines, and performing kidney dialysis.", + ) /obj/item/mecha_equipment/sleeper/Initialize() . = ..() @@ -132,7 +138,16 @@ var/mob/living/carbon/Target = null var/datum/beam/MyBeam = null + module_hints = list( + "Alt Click(Drone Icon): Activates the drone. When active, it will bob up and down rapidly as a visual indication.", + "When active, a crisis drone will constantly attempt to heal injured people within 3 tiles.", + "The drone is capable of healing most damage types, it will even alleviate a patient's pain. It cannot heal patients with less than 30 total damage.", + "It cannot heal the dead. It also cannot mend broken bones.", + ) + /obj/item/mecha_equipment/crisis_drone/Destroy() + Target = null + MyBeam = null STOP_PROCESSING(SSprocessing, src) . = ..() @@ -279,6 +294,10 @@ holding_type = /obj/item/device/healthanalyzer/mech restricted_hardpoints = list(HARDPOINT_LEFT_HAND, HARDPOINT_RIGHT_HAND) restricted_software = list(MECH_SOFTWARE_MEDICAL) + module_hints = list( + "Left Click(Target): Instantly perform a basic medical scan of the target.", + "Ctrl Click(Target): After remaining still for 7 seconds, print a full body scan of the target.", + ) /// Special health analyzer used by the exosuit health analyzer. /obj/item/device/healthanalyzer/mech diff --git a/code/modules/heavy_vehicle/equipment/mining.dm b/code/modules/heavy_vehicle/equipment/mining.dm index 736d1164fa3..46ad6e97bb9 100644 --- a/code/modules/heavy_vehicle/equipment/mining.dm +++ b/code/modules/heavy_vehicle/equipment/mining.dm @@ -9,6 +9,11 @@ var/obj/machinery/mining/drill/held_drill var/list/obj/machinery/mining/brace/held_braces var/charging = FALSE + module_hints = list( + "Left Click (Target Mining Drill or Brace): Load the target into the module's internal storage.", + "Alt Click (Icon): Deploy a held mining drill at the user's current location.", + "A drill mover can only hold one drill, and two braces at a time.", + ) /obj/item/mecha_equipment/drill_mover/afterattack(var/atom/target, var/mob/living/user, var/inrange, var/params) . = ..() @@ -85,6 +90,11 @@ held_braces -= brace . = ..() +/obj/item/mecha_equipment/drill_mover/Destroy() + QDEL_NULL(held_drill) + QDEL_NULL_LIST(held_braces) + return ..() + ABSTRACT_TYPE(/obj/item/mecha_equipment/mounted_system/mining) name = "mounted mining equipment" desc = DESC_PARENT @@ -97,6 +107,12 @@ ABSTRACT_TYPE(/obj/item/mecha_equipment/mounted_system/mining) desc = "A heavy-duty kinetic accelerator designed to be mounted on an exosuit." icon_state = "mecha_taser" // would be too difficult to get a proper sprite for this, would rather just get it in holding_type = /obj/item/gun/custom_ka/exosuit + module_hints = list( + "Left Click: Fire a kinetic blast in the target direction.", + "WARNING: This weapon produces a mining blast eight tiles in radius.", + "A mech can only fire within a 90 degree arc in the direction it is currently facing.", + "This weapon passively regenerates its ammunition using the mech's power supply.", + ) /obj/item/mecha_equipment/mounted_system/mining/kinetic_accelerator/get_hardpoint_status_value() if(!holding) @@ -137,7 +153,25 @@ ABSTRACT_TYPE(/obj/item/mecha_equipment/mounted_system/mining) /// How many chunks of ore can be moved at one time var/static/ore_limit = 50 + /// Radius of the summoner's pickup range. + var/pickup_range = 7 + + /// The linked warp extraction ore box. + var/obj/structure/ore_box/linked_box + + module_hints = list( + "Left Click (Target Ore Box): If the the target has a warp extraction beacon, link the summoner to it.", + "Alt Click (Icon): Teleport up to 50 ores within a radius of 7 tiles to the user.", + "If a warp ore box has been linked, it will teleport ores there.", + "Otherwise if the mech is also equipped with a mounted clamp that contains an ore box, ores will be teleported to said box.", + ) + /obj/item/mecha_equipment/ore_summoner/afterattack(var/atom/target, var/mob/living/user, var/inrange, var/params) + if(istype(target, /obj/structure/ore_box)) + var/obj/structure/ore_box/box = target + if (box.warp_core) + linked_box = target + to_chat(user, SPAN_NOTICE("You link \the [src] to \the [target]")) return FALSE /obj/item/mecha_equipment/ore_summoner/attack_self(var/mob/user) @@ -151,19 +185,22 @@ ABSTRACT_TYPE(/obj/item/mecha_equipment/mounted_system/mining) return var/obj/structure/ore_box/ore_box - for(var/hardpoint in owner.hardpoints) - var/obj/item/mecha_equipment/clamp/clamp = owner.hardpoints[hardpoint] - if(!istype(clamp)) - continue - var/obj/structure/ore_box/box = locate() in clamp - if(box) - ore_box = box - break + if(check_linked_box(user)) + ore_box = linked_box + else + for(var/hardpoint in owner.hardpoints) + var/obj/item/mecha_equipment/clamp/clamp = owner.hardpoints[hardpoint] + if(!istype(clamp)) + continue + var/obj/structure/ore_box/box = locate() in clamp + if(box) + ore_box = box + break var/turf/our_turf = get_turf(owner) var/limit = ore_limit - for(var/obj/item/ore/ore in range(7, owner)) + for(var/obj/item/ore/ore in range(pickup_range, owner)) if(limit <= 0) break if(ore_box) @@ -180,3 +217,18 @@ ABSTRACT_TYPE(/obj/item/mecha_equipment/mounted_system/mining) /obj/item/mecha_equipment/ore_summoner/get_hardpoint_maptext() return last_use + cooldown_time < world.time ? "Ready" : "Recharging" + +/obj/item/mecha_equipment/ore_summoner/proc/check_linked_box(var/mob/user) + if(!linked_box) + return FALSE + + if(!linked_box.warp_core) + to_chat(user, SPAN_WARNING("\The [linked_box] lost its warp beacon!")) + linked_box = null + return FALSE + + return TRUE + +/obj/item/mecha_equipment/ore_summoner/Destroy() + linked_box = null // Clear the reference to prevent hard deletes. + return ..() diff --git a/code/modules/heavy_vehicle/equipment/utility.dm b/code/modules/heavy_vehicle/equipment/utility.dm index 04700271283..02cf1bbfab4 100644 --- a/code/modules/heavy_vehicle/equipment/utility.dm +++ b/code/modules/heavy_vehicle/equipment/utility.dm @@ -9,6 +9,13 @@ var/list/obj/carrying = list() origin_tech = list(TECH_MATERIAL = 2, TECH_ENGINEERING = 2) var/list/afterattack_types = list(/obj/structure/closet, /obj/machinery/door/airlock) + module_hints = list( + "Attack(Harm): Swing the claw at a target to deal damage.", + "Attack(Non-Harm): Shove the target backwards,", + "Left Click(Door): Force open any door, even bolted and/or welded airlocks.", + "Left Click(Any other nonliving object): Pick up the object, storing it inside the clamp. Each clamp can hold up to 5 objects.", + "Alt Click(Clamp): Drop a selected held item.", + ) /obj/item/mecha_equipment/clamp/resolve_attackby(atom/A, mob/user, click_params) if(is_type_in_list(A, afterattack_types) && owner) @@ -172,6 +179,10 @@ /obj/item/weldingtool/get_hardpoint_maptext() return "[get_fuel()]/[max_fuel]" +/obj/item/mecha_equipment/clamp/Destroy() + QDEL_NULL_LIST(carrying) + return ..() + /obj/item/mecha_equipment/mounted_system/plasmacutter name = "mounted plasma cutter" desc = "An industrial plasma cutter mounted onto the chassis of the mech. " @@ -180,6 +191,11 @@ restricted_hardpoints = list(HARDPOINT_LEFT_HAND, HARDPOINT_RIGHT_HAND) restricted_software = list(MECH_SOFTWARE_UTILITY) origin_tech = list(TECH_MATERIAL = 4, TECH_PHORON = 4, TECH_ENGINEERING = 6, TECH_COMBAT = 3) + module_hints = list( + "Left Click(Target): Fire a plasma cutting beam in the target direction.", + "In addition to dealing damage, the plasma cutter can also mine through rocks at a time.", + "It also pierces through the target, striking an additional object behind the first one.", + ) /obj/item/gun/energy/plasmacutter/mounted/mech use_external_power = TRUE @@ -197,6 +213,9 @@ light_color = LIGHT_COLOR_TUNGSTEN light_wedge = LIGHT_WIDE origin_tech = list(TECH_MATERIAL = 1, TECH_ENGINEERING = 1) + module_hints = list( + "Alt Click(Icon): Light up a large area in front of the mech.", + ) /obj/item/mecha_equipment/light/attack_self(var/mob/user) . = ..() @@ -404,6 +423,12 @@ var/obj/item/material/drill_head/drill_head origin_tech = list(TECH_MATERIAL = 2, TECH_ENGINEERING = 2) + module_hints = list( + "Left Click(Target): Attack the target with a drill.", + "The drill is capable of destroying almost any structure, and can also destroy floors.", + "Using the drill consumes some of its durability. When it runs out, a new drill head will have to be placed in the module.", + ) + /obj/item/mecha_equipment/drill/Initialize() . = ..() drill_head = new /obj/item/material/drill_head(src, DEFAULT_WALL_MATERIAL)//You start with a basic steel head @@ -516,6 +541,10 @@ holding_type = /obj/item/gun/launcher/mech/flarelauncher restricted_hardpoints = list(HARDPOINT_LEFT_SHOULDER, HARDPOINT_RIGHT_SHOULDER) restricted_software = list(MECH_SOFTWARE_UTILITY) + module_hints = list( + "Left Click(Target): Fire an illuminating signal flare at the target location.", + "This weapon passively regenerates its ammunition using the mech's power supply.", + ) /obj/item/gun/launcher/mech/flarelauncher name = "mounted flare launcher" @@ -558,6 +587,10 @@ restricted_software = null origin_tech = list(TECH_MATERIAL = 2, TECH_ENGINEERING = 2) passive_power_use = 15 + module_hints = list( + "Left Click(Living Target): Load the target into the mech's onboard passenger compartment.", + "Alt Click(Icon): Eject the contents of the compartment.", + ) /obj/item/mecha_equipment/sleeper/passenger_compartment/uninstalled() . = ..() @@ -592,6 +625,10 @@ restricted_software = list(MECH_SOFTWARE_UTILITY) origin_tech = list(TECH_MATERIAL = 2, TECH_ENGINEERING = 2) var/obj/machinery/autolathe/mounted/lathe + module_hints = list( + "Left Click (Target Materials): Load materials into the autolathe.", + "Alt Click (Icon): Open the autolathe's UI.", + ) /obj/item/mecha_equipment/autolathe/get_hardpoint_maptext() if(lathe?.currently_printing) @@ -651,6 +688,11 @@ var/obj/item/powerdrill/mech/mounted_tool origin_tech = list(TECH_MATERIAL = 2, TECH_ENGINEERING = 2) + module_hints = list( + "Alt Click (Icon): Open a radial menu for selecting any desired tool.", + "Left Click (Target): Use the currently selected tool on the target.", + ) + /obj/item/mecha_equipment/toolset/Initialize() . = ..() mounted_tool = new/obj/item/powerdrill/mech(src) @@ -708,10 +750,14 @@ restricted_hardpoints = list(HARDPOINT_BACK) w_class = WEIGHT_CLASS_HUGE origin_tech = list(TECH_MATERIAL = 2, TECH_ENGINEERING = 3) + var/entry_speed_divisor = 6 + module_hints = list( + "Allows a user to enter the mech 6 times faster.", + ) /obj/item/mecha_equipment/quick_enter/installed() ..() - owner.entry_speed = 5 + owner.entry_speed /= entry_speed_divisor /obj/item/mecha_equipment/quick_enter/uninstalled() owner.entry_speed = initial(owner.entry_speed) @@ -735,6 +781,11 @@ var/obj/item/anomaly_core/AC var/image/anomaly_overlay + module_hints = list( + "Alt Click (Icon): Become incorporeal, gaining the ability to move through walls unimpeded.", + "This system consumes 88 kilowatts of power while active.", + ) + /obj/item/mecha_equipment/phazon/assembly_hints(mob/user, distance, is_adjacent) . += ..() if(!AC) @@ -803,3 +854,7 @@ holding_type = /obj/item/gun/launcher/mech/mountedgl/cl restricted_hardpoints = list(HARDPOINT_LEFT_SHOULDER, HARDPOINT_RIGHT_SHOULDER) restricted_software = list(MECH_SOFTWARE_UTILITY) + module_hints = list( + "Left Click(Target): Fire an cleaning grenade at the target location.", + "This weapon passively regenerates its ammunition using the mech's power supply.", + ) diff --git a/code/modules/heavy_vehicle/mecha.dm b/code/modules/heavy_vehicle/mecha.dm index 900c09d1754..35f8f6c9656 100644 --- a/code/modules/heavy_vehicle/mecha.dm +++ b/code/modules/heavy_vehicle/mecha.dm @@ -175,6 +175,23 @@ damage_string = SPAN_DANGER("destroyed") . += "Its [thing.name] [thing.gender == PLURAL ? "are" : "is"] [damage_string]." +/mob/living/heavy_vehicle/mechanics_hints(mob/user, distance, is_adjacent) + . += ..() + var/list/hardpoint_hints = list() + for(var/hardpoint in hardpoints) + var/obj/item/mecha_equipment/I = hardpoints[hardpoint] + if(!istype(I) || !length(I.module_hints)) + continue + hardpoint_hints += "- [hardpoint]: [SPAN_NOTICE("[I]")]" + hardpoint_hints += I.relayed_mechanics_hints(user, distance, is_adjacent) + + if(length(hardpoint_hints)) + . += "Its hardpoints have the following mechanics:" + . += hardpoint_hints + return + // Mech has no hardpoints, let's teach them how to fix that instead. + . += "modules can be attached to a mech by clicking on the mech with a module in hand." + /mob/living/heavy_vehicle/Topic(href,href_list[]) if (href_list["examine"]) var/mob/M = locate(href_list["examine"]) diff --git a/html/changelogs/Hellfirejag-exosuit-stuff.yml b/html/changelogs/Hellfirejag-exosuit-stuff.yml new file mode 100644 index 00000000000..3afbcb4993f --- /dev/null +++ b/html/changelogs/Hellfirejag-exosuit-stuff.yml @@ -0,0 +1,6 @@ +author: Hellfirejag +delete-after: True +changes: + - rscadd: "Added mechanics examine text for mech modules. Uniquely, examining a mech lets you see the mechanics of its equipped modules." + - rscadd: "Mech ore summoners can now be linked to warp extraction ore boxes." + - refactor: "Removed all lag-inducing hard deletes from mech code."