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."