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.
This commit is contained in:
VMSolidus
2025-08-07 22:42:58 +00:00
committed by GitHub
parent 1acb37c81f
commit 58f8cd11ac
10 changed files with 200 additions and 11 deletions
@@ -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("<b>Exosuit Software Requirement:</b> [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
@@ -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(
"<b>Left Click:</b> 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(
"<b>Alt Click(Icon):</b> 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"
@@ -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 ..()
@@ -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(
"<b>Control Click(Self):</b> Activate the RFD's mode selection radial menu.",
"<b>Left Click(Target):</b> 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))
@@ -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(
"<b>Left Click(Living Target):</b> Load the target into the mech's onboard Medical Sleeper unit.",
"<b>Alt Click(Icon):</b> 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(
"<b>Alt Click(Drone Icon):</b> 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 <b>3</b> 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 <b>cannot</b> heal the dead. It also <b>cannot</b> 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(
"<b>Left Click(Target):</b> Instantly perform a basic medical scan of the target.",
"<b>Ctrl Click(Target):</b> 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
+61 -9
View File
@@ -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(
"<b>Left Click (Target Mining Drill or Brace):</b> Load the target into the module's internal storage.",
"<b>Alt Click (Icon):</b> 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(
"<b>Left Click:</b> Fire a kinetic blast in the target direction.",
"<b>WARNING: This weapon produces a mining blast eight tiles in radius.</b>",
"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(
"<b>Left Click (Target Ore Box):</b> If the the target has a warp extraction beacon, link the summoner to it.",
"<b>Alt Click (Icon):</b> 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 ..()
@@ -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(
"<b>Attack(Harm):</b> Swing the claw at a target to deal damage.",
"<b>Attack(Non-Harm):</b> Shove the target backwards,",
"<b>Left Click(Door):</b> Force open any door, even bolted and/or welded airlocks.",
"<b>Left Click(Any other nonliving object):</b> Pick up the object, storing it inside the clamp. Each clamp can hold up to 5 objects.",
"<b>Alt Click(Clamp):</b> 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(
"<b>Left Click(Target):</b> 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(
"<b>Alt Click(Icon):</b> 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(
"<b>Left Click(Target):</b> 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(
"<b>Left Click(Target):</b> 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(
"<b>Left Click(Living Target):</b> Load the target into the mech's onboard passenger compartment.",
"<b>Alt Click(Icon):</b> 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(
"<b>Left Click (Target Materials):</b> Load materials into the autolathe.",
"<b>Alt Click (Icon):</b> 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(
"<b>Alt Click (Icon):</b> Open a radial menu for selecting any desired tool.",
"<b>Left Click (Target):</b> 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(
"<b>Alt Click (Icon):</b> 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(
"<b>Left Click(Target):</b> Fire an cleaning grenade at the target location.",
"This weapon passively regenerates its ammunition using the mech's power supply.",
)
+17
View File
@@ -175,6 +175,23 @@
damage_string = SPAN_DANGER("destroyed")
. += "Its <b>[thing.name]</b> [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 += "- <b>[hardpoint]</b>: [SPAN_NOTICE("<i>[I]</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"])