Hardsuit bugfixes (#22848)

The closest thing to a balance change is the hardsuit boots being
deployable without the chestpiece again.

The SFX can be moved with the recent update to armour sounds if someone
wants, but the slowdown is a heavy enough penalty to discourage it.

- bugfix: "Fixed the Retract All Hardsuit Parts verb, by making hardsuit
boots retractable with the chestpiece deployed, at a heavy slowdown"
- bugfix: "Fixed the stealth system module not decloaking when the user
fires a gun or hits a mob."
- bugfix: "Fixed being able to instantly remove your hardsuit if it had
a storage module in it."
- bugfix: "Fixed mounted energy guns being stuck in taser mode. Also
extends support to other guns with multiple firemods."
  - bugfix: "Fixed most hardsuit modules not having an on-mob sprite."
- bugfix: "Fixed synthetic chargers not charging the hardsuit, if the
wearer was an IPC."
- bugfix: "Fixed the hardsuit UI not updating when toggling jetpack
stabalizers."
- bugfix: "Fixed the advanced hacking tool searching the wearer's entire
contents to see if it had been dropped every tick."
This commit is contained in:
FenodyreeAv
2026-07-17 11:32:03 +00:00
committed by GitHub
parent 53a02f5776
commit affab40530
12 changed files with 144 additions and 64 deletions
@@ -23,6 +23,7 @@
module_type = MODULETYPE_USABLE_ACTIVE
icon_state = "grenade"
suit_overlay_active = "mounted-gun"
interface_name = "integrated grenade launcher"
interface_desc = "Discharges loaded grenades against the wearer's location."
@@ -117,6 +118,8 @@
module_cooldown = 0
icon_state = "lcannon"
suit_overlay_active = "mounted-lascannon"
engage_string = "Configure"
category = MODULE_HEAVY_COMBAT
@@ -137,6 +140,24 @@
QDEL_NULL(gun)
. = ..()
/obj/item/rig_module/mounted/proc/get_firemode_name()
if(!gun || !length(gun.firemodes))
return "No firemodes"
var/datum/firemode/current_mode = gun.firemodes[gun.sel_mode]
return current_mode ? current_mode.name : "Unknown"
/obj/item/rig_module/mounted/get_configuration()
. = ..()
if(gun && length(gun.firemodes) > 1)
.["firemode"] = add_ui_configuration("Fire mode", "button", get_firemode_name())
/obj/item/rig_module/mounted/configure_edit(key, value, mob/user)
switch(key)
if("firemode")
if(gun)
gun.toggle_firing_mode(user)
/obj/item/rig_module/mounted/engage(atom/target, mob/user)
if(!..())
return FALSE
@@ -158,7 +179,7 @@
construction_cost= list(MATERIAL_STEEL = 7000, MATERIAL_GLASS = 2250, MATERIAL_URANIUM = 3250, MATERIAL_GOLD = 2500)
construction_time = 300
suit_overlay_active = "mounted-lascannon"
suit_overlay_active = "mounted-wrist"
interface_name = "mounted energy gun"
interface_desc = "A forearm-mounted suit-powered energy gun."
@@ -211,7 +232,7 @@
desc = "A forearm-mounted suit-powered ballistic submachine gun."
icon_state = "smg"
suit_overlay_active = "mounted-ion"
suit_overlay_active = "mounted-wrist"
interface_name = "mounted submachine gun"
interface_desc = "A forearm-mounted suit-powered ballistic submachine gun."
@@ -223,7 +244,7 @@
desc = "A forearm-mounted suit-powered xray laser gun."
icon_state = "xray"
suit_overlay_active = "mounted-ion"
suit_overlay_active = "mounted-wrist"
interface_name = "mounted xray laser gun"
interface_desc = "A forearm-mounted suit-powered xray laser gun."
@@ -293,6 +314,7 @@
icon_state = "thermaldrill"
interface_name = "thermal drill"
interface_desc = "A potent drill that can pierce rock walls over long distances."
suit_overlay_active = "mounted-wrist"
use_power_cost = 20
gun_type = /obj/item/gun/energy/vaurca/thermaldrill/mounted
@@ -448,6 +470,7 @@
name = "mounted tqi-qop carbine"
desc = "A shoulder-mounted cell-powered tqi-qop carbine."
icon_state = "pulse"
suit_overlay_active = "mounted-gun"
interface_name = "mounted tqi-qop carbine"
interface_desc = "A shoulder-mounted cell-powered tqi-qop carbine."
gun_type = /obj/item/gun/energy/gun/qukala/mounted
@@ -429,19 +429,7 @@
return TRUE
return FALSE
/mob/living/carbon/human/ClickOn(atom/A, params)
. = ..()
if (ismob(A) && istype(back, /obj/item/rig))
var/obj/item/rig/R = back
R.attack_disrupt_check(src)
/mob/living/carbon/human/throw_item(atom/target)
. = ..()
if (ismob(src) && istype(back, /obj/item/rig))
var/obj/item/rig/R = back
R.attack_disrupt_check(src)
/obj/item/rig/proc/attack_disrupt_check()
for (var/obj/item/rig_module/module in installed_modules)
if (module.active && module.attackdisrupts)
module.deactivate()
module.deactivate(src.wearer)
@@ -341,10 +341,11 @@
category = MODULE_SPECIAL
/obj/item/rig_module/device/door_hack/process()
if(holder && holder.wearer)
if(!(locate(/obj/item/multitool/hacktool/rig) in holder.wearer))
deactivate()
return FALSE
if(active) //Don't need to run locate every tick if the suit isn't in use.
if(holder && holder.wearer)
if(!(locate(/obj/item/multitool/hacktool/rig) in holder.wearer))
deactivate()
return FALSE
return ..()
@@ -499,6 +499,7 @@
if("stabilizers")
if(engage(src, user))
jets.toggle_rockets_stabilization(user)
stabilize = jets.stabilization_on
/obj/item/rig_module/maneuvering_jets/activate(mob/user)
if(!..())
+67 -16
View File
@@ -244,6 +244,26 @@
if(helmet)
helmet.tint = (active? vision_restriction : offline_vision_restriction)
/**
* Updates the hardsuit's slowdown if it changes.
* This gives a heavy slowdown penalty if the suit is deployed but the boots are not.
* Otherwise, it is an easy exploit to make any hardsuit stealthy by wearing everything EXCEPT the boots.
* Now you can creep in your hardsuit, but you have to go slowly to do it.
*/
/obj/item/rig/proc/update_slowdown()
var/new_slowdown = initial(slowdown)
if(offline)
new_slowdown = offline_slowdown
else if(chest && wearer?.wear_suit == chest && (!boots || wearer.shoes != boots)) //If the chest is deployed, but the boots are not.
new_slowdown = offline_slowdown + 2 //The boots aren't taking the weight of the suit, so the wearer isn't benefiting from the hardsuit's powered servos.
if(slowdown != new_slowdown)
slowdown = new_slowdown
wearer?.update_equipment_speed_mods() //This should only proc if worn, but just in case we check.
if(slowdown == offline_slowdown + 2)
to_chat(wearer, SPAN_WARNING("Without the boots deployed, the hardsuit's servos aren't supporting the weight of the suit."))
/obj/item/rig/proc/suit_is_deployed()
if(!istype(wearer) || src.loc != wearer || wearer.back != src)
return 0
@@ -475,9 +495,8 @@
offline = 0
if(istype(wearer) && !wearer.wearing_rig)
wearer.wearing_rig = src
if(slowdown != initial(slowdown))
slowdown = initial(slowdown)
wearer?.update_equipment_speed_mods()
update_slowdown()
if(offline != previous_offline_status)
update_icon(TRUE)
@@ -499,9 +518,7 @@
for(var/obj/item/rig_module/module in installed_modules)
module.deactivate()
offline = 2
if(slowdown != offline_slowdown)
slowdown = offline_slowdown
wearer?.update_equipment_speed_mods()
update_slowdown()
return
if(malfunction_delay > 0)
@@ -516,6 +533,9 @@
if(power_usage_this_tick)
cell.use(power_usage_this_tick)
if(!malfunctioning && !electrified && !open && offline && !wearer) //If nothing is wrong with it and it is not in use, it should stop processing.
return PROCESS_KILL
/obj/item/rig/proc/check_power_cost(var/mob/living/user, var/cost, var/use_unconcious, var/obj/item/rig_module/mod, var/user_is_ai)
if(!istype(user))
@@ -784,10 +804,12 @@
if(selected_module.suit_overlay_active)
selected_module.suit_overlay = selected_module.suit_overlay_active
sound_to(usr, module?.sound_activate)
balloon_alert_to_viewers("deploys \the [module.interface_name]", "deploys \the [module.interface_name]")
else
sound_to(usr, selected_module?.sound_deactivate)
selected_module.suit_overlay = null
selected_module = null
balloon_alert_to_viewers("retracts \the [module.interface_name]", "retracts \the [module.interface_name]")
update_icon(TRUE)
playsound(src.loc, 'sound/items/rfd_dispense.ogg', 25, FALSE)
if("select_charge_type")
@@ -843,9 +865,13 @@
if(!do_after(M, seal_delay))
return FALSE
if(wearer && wearer != M)
unregister_wearer_signals(wearer)
M.visible_message(SPAN_NOTICE("<b>[M] struggles into \the [src].</b>"), SPAN_NOTICE("<b>You struggle into \the [src].</b>"))
wearer = M
wearer.wearing_rig = src
register_wearer_signals(wearer)
update_icon(TRUE)
return TRUE
return TRUE
@@ -895,16 +921,6 @@
var/mob/living/carbon/human/holder
holder = use_obj.loc
if(istype(holder))
// Special code for boots. This is to prevent boots from being retracted while the chest piece is deployed.
// Otherwise, it is an easy exploit to make any hardsuit stealthy by wearing everything EXCEPT the boots.
var/obj/item/chest_slot = holder.wear_suit
var/obj/item/chest_obj = chest
if(use_obj == boots && chest_slot == chest_obj)
to_chat(wearer, SPAN_WARNING("The chest piece of the hardsuit must be retracted before you can retract your boots!"))
sound_to(wearer, 'sound/machines/terminal/terminal_error.ogg')
piece_being_deployed = FALSE
return
if(check_slot == use_obj)
to_chat(wearer, "<font color='blue'><b>Your [use_obj.name] [use_obj.gender == PLURAL ? "retract" : "retracts"] swiftly.</b></font>")
playsound(src, 'sound/machines/rig/rig_retract.ogg', 30, FALSE)
@@ -924,6 +940,9 @@
to_chat(wearer, SPAN_WARNING("You must remain still while the suit deploys its parts."))
piece_being_deployed = FALSE
return FALSE
if(!wearer || wearer.back != src) ///Prevents an edge case where a suit with a storage module can be removed while deploying, causing a runtime.
piece_being_deployed = FALSE
return FALSE
// If we're deploying the chest, we also try to deploy boots. If we can't also deploy boots, the entire thing fails.
if(use_obj == chest)
var/obj/item/boots_slot = wearer.shoes
@@ -951,6 +970,7 @@
helmet.update_light(wearer)
update_icon(TRUE)
update_slowdown()
piece_being_deployed = FALSE
return TRUE
@@ -1020,12 +1040,43 @@
toggle_piece(piece, H, ONLY_RETRACT)
/obj/item/rig/proc/null_wearer(var/mob/user)
unregister_wearer_signals(wearer)
for(var/piece in list("helmet","gauntlets",BP_CHEST,"boots"))
toggle_piece(piece, user, ONLY_RETRACT)
if(wearer)
wearer.wearing_rig = null
wearer = null
/obj/item/rig/proc/register_wearer_signals(mob/living/carbon/human/new_wearer)
if(!new_wearer)
return
RegisterSignal(new_wearer, COMSIG_MOB_CLICKON, PROC_REF(handle_wearer_click))
/obj/item/rig/proc/unregister_wearer_signals(mob/living/carbon/human/old_wearer)
if(!old_wearer)
return
UnregisterSignal(old_wearer, COMSIG_MOB_CLICKON)
/obj/item/rig/proc/handle_wearer_click(mob/user, atom/target, modifiers)
SIGNAL_HANDLER
if(offline == 2)
return
if(LAZYACCESS(modifiers, SHIFT_CLICK))
return
if(LAZYACCESS(modifiers, ALT_CLICK))
return
if(LAZYACCESS(modifiers, RIGHT_CLICK))
return
if(user.in_throw_mode && (isturf(target) || isturf(target.loc)) && user.throw_item(target)) //Prevents throwing items while remaining cloaked.
attack_disrupt_check()
if(ismob(target)) //This doesn't prevent guns firing at turfs, that is handled in /obj/item/gun/proc/handle_post_fire
if(target != user)
attack_disrupt_check()
/obj/item/rig/on_slotmove(var/mob/user)
..()
null_wearer(user)
@@ -149,7 +149,7 @@
if(!check_power_cost(usr))
return
deploy(wearer)
retract(wearer)
/obj/item/rig/verb/toggle_seals_verb()
set name = "Engage/Disengage Hardsuit"
+2 -3
View File
@@ -597,12 +597,11 @@ ABSTRACT_TYPE(/obj/item/gun)
if(recoil)
shake_camera(user, recoil + 1, recoil)
if(ishuman(user) && user.invisibility == INVISIBILITY_LEVEL_TWO) //shooting will disable a rig cloaking device
if(ishuman(user))
var/mob/living/carbon/human/H = user
if(istype(H.back, /obj/item/rig))
var/obj/item/rig/R = H.back
for(var/obj/item/rig_module/stealth_field/S in R.installed_modules)
S.deactivate()
R.attack_disrupt_check() //This currently handles decloaking ninjas who shoot guns. Other modules could use attack_disrupt_check() in future.
update_icon()
/obj/item/gun/proc/play_fire_sound()
@@ -104,12 +104,11 @@
D.set_color()
D.set_up(my_target, spray_size, 10)
if(ishuman(user) && user.invisibility == INVISIBILITY_LEVEL_TWO) //shooting will disable a rig cloaking device
if(ishuman(user))
var/mob/living/carbon/human/H = user
if(istype(H.back,/obj/item/rig))
var/obj/item/rig/R = H.back
for(var/obj/item/rig_module/stealth_field/S in R.installed_modules)
S.deactivate()
R.attack_disrupt_check() //This currently handles decloaking ninjas who spray acid or lube. Other modules could use attack_disrupt_check() in future.
/obj/item/reagent_containers/spray/attack_self(var/mob/user)
if(!possible_transfer_amounts)