From affab40530ed20cf3f91cc22e93816111bcb233e Mon Sep 17 00:00:00 2001 From: FenodyreeAv Date: Fri, 17 Jul 2026 12:32:03 +0100 Subject: [PATCH] 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." --- .../objects/items/weapons/storage/internal.dm | 35 +++++--- .../structures/machinery/rechargestation.dm | 14 +-- .../clothing/spacesuits/rig/modules/combat.dm | 29 +++++- .../spacesuits/rig/modules/modules.dm | 14 +-- .../clothing/spacesuits/rig/modules/ninja.dm | 9 +- .../spacesuits/rig/modules/utility.dm | 1 + code/modules/clothing/spacesuits/rig/rig.dm | 83 ++++++++++++++---- .../clothing/spacesuits/rig/rig_verbs.dm | 2 +- code/modules/projectiles/gun.dm | 5 +- .../reagents/reagent_containers/spray.dm | 5 +- .../changelogs/Fenodyree-HardsuitBugfixes.yml | 11 +++ icons/mob/rig_modules.dmi | Bin 4399 -> 4519 bytes 12 files changed, 144 insertions(+), 64 deletions(-) create mode 100644 html/changelogs/Fenodyree-HardsuitBugfixes.yml diff --git a/code/game/objects/items/weapons/storage/internal.dm b/code/game/objects/items/weapons/storage/internal.dm index 698bf98f632..6a1f25f08dc 100644 --- a/code/game/objects/items/weapons/storage/internal.dm +++ b/code/game/objects/items/weapons/storage/internal.dm @@ -21,15 +21,20 @@ /obj/item/storage/internal/mob_can_equip(M, slot, disable_warning = FALSE, bypass_blocked_check = FALSE, is_overlay_check = FALSE) return 0 //make sure this is never picked up -//Helper procs to cleanly implement internal storages - storage items that provide inventory slots for other items. -//These procs are completely optional, it is up to the master item to decide when it's storage get's opened by calling open() -//However they are helpful for allowing the master item to pretend it is a storage item itself. -//If you are using these you will probably want to override attackby() as well. -//See /obj/item/clothing/suit/storage for an example. - -//items that use internal storage have the option of calling this to emulate default storage MouseDrop behaviour. -//returns 1 if the master item's parent's MouseDrop() should be called, 0 otherwise. It's strange, but no other way of -//doing it without the ability to call another proc's parent, really. +/** + * Helper procs to cleanly implement internal storages - storage items that provide inventory slots for other items. + * These procs are completely optional, it is up to the master item to decide when it's storage get's opened by calling open() + * + * However they are helpful for allowing the master item to pretend it is a storage item itself. + * + * If you are using these you will probably want to override attackby() as well. + * See /obj/item/clothing/suit/storage for an example. + * + * items that use internal storage have the option of calling this to emulate default storage MouseDrop behaviour. + * + * Returns 1 if the master item's parent's MouseDrop() should be called, 0 otherwise. + * It's strange, but no other way of doing it without the ability to call another proc's parent, really. + */ /obj/item/storage/internal/proc/handle_mousedrop(mob/user as mob, obj/over_object as obj) if (ishuman(user) || issmall(user)) //so monkeys can take off their backpacks -- Urist @@ -48,20 +53,22 @@ return 0 if (!( user.restrained() ) && !( user.stat )) + if(!user.prepare_for_slotmove(real_master_item)) //Prevents removing hardsuits when they have storage modules in them. + return 0 switch(over_object.name) if("right hand") - user.u_equip(real_master_item) user.equip_to_slot_if_possible(real_master_item, slot_r_hand) if("left hand") - user.u_equip(real_master_item) user.equip_to_slot_if_possible(real_master_item, slot_l_hand) real_master_item.add_fingerprint(user) return 0 return 0 -//items that use internal storage have the option of calling this to emulate default storage attack_hand behaviour. -//returns 1 if the master item's parent's attack_hand() should be called, 0 otherwise. -//It's strange, but no other way of doing it without the ability to call another proc's parent, really. +/** + * items that use internal storage have the option of calling this to emulate default storage attack_hand behaviour. + * returns 1 if the master item's parent's attack_hand() should be called, 0 otherwise. + * It's strange, but no other way of doing it without the ability to call another proc's parent, really. + */ /obj/item/storage/internal/proc/handle_attack_hand(mob/user as mob) var/obj/item/real_master_item = special_master_item_handling ? get_master_item() : master_item diff --git a/code/game/objects/structures/machinery/rechargestation.dm b/code/game/objects/structures/machinery/rechargestation.dm index ad254b64536..c7f0f64314a 100644 --- a/code/game/objects/structures/machinery/rechargestation.dm +++ b/code/game/objects/structures/machinery/rechargestation.dm @@ -135,26 +135,26 @@ if(ishuman(occupant)) var/mob/living/carbon/human/H = occupant - var/obj/item/organ/internal/machine/power_core/IC = H.internal_organs_by_name[BP_CELL] - if(istype(IC)) - target = IC.cell - - // Different reactor types have different external recharge speeds. if(isipc(H)) reactor = H.internal_organs_by_name[BP_REACTOR] if(!istype(reactor)) return - if((!target || target.percent() > 98) && istype(H.back, /obj/item/rig)) + if((!target || target.percent() > 99) && istype(H.back, /obj/item/rig)) var/obj/item/rig/R = H.back if(R.cell && !R.cell.fully_charged()) target = R.cell + if(!target) + var/obj/item/organ/internal/machine/power_core/IC = H.internal_organs_by_name[BP_CELL] + if(istype(IC)) + target = IC.cell + if(target && !target.fully_charged()) var/diff = min(target.maxcharge - target.charge, charging_power * CELLRATE * seconds_per_tick) // Capped by charging_power / tick var/charge_used = cell.use(diff) - if(!reactor) // not an IPC + if(!reactor || target.percent() < 99) // not an IPC target.give(charge_used * charging_efficiency) else reactor.generate_power(charge_used * charging_efficiency * reactor.external_charge_multiplier) diff --git a/code/modules/clothing/spacesuits/rig/modules/combat.dm b/code/modules/clothing/spacesuits/rig/modules/combat.dm index efb4ac97f1c..71ea072c234 100644 --- a/code/modules/clothing/spacesuits/rig/modules/combat.dm +++ b/code/modules/clothing/spacesuits/rig/modules/combat.dm @@ -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 diff --git a/code/modules/clothing/spacesuits/rig/modules/modules.dm b/code/modules/clothing/spacesuits/rig/modules/modules.dm index 489a42f9c5a..3251c73b7a9 100644 --- a/code/modules/clothing/spacesuits/rig/modules/modules.dm +++ b/code/modules/clothing/spacesuits/rig/modules/modules.dm @@ -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) diff --git a/code/modules/clothing/spacesuits/rig/modules/ninja.dm b/code/modules/clothing/spacesuits/rig/modules/ninja.dm index 3853ec56494..7628e857da4 100644 --- a/code/modules/clothing/spacesuits/rig/modules/ninja.dm +++ b/code/modules/clothing/spacesuits/rig/modules/ninja.dm @@ -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 ..() diff --git a/code/modules/clothing/spacesuits/rig/modules/utility.dm b/code/modules/clothing/spacesuits/rig/modules/utility.dm index b472928ddf7..c32639f0abd 100644 --- a/code/modules/clothing/spacesuits/rig/modules/utility.dm +++ b/code/modules/clothing/spacesuits/rig/modules/utility.dm @@ -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(!..()) diff --git a/code/modules/clothing/spacesuits/rig/rig.dm b/code/modules/clothing/spacesuits/rig/rig.dm index bbff4693b8e..c0e2f4792e8 100644 --- a/code/modules/clothing/spacesuits/rig/rig.dm +++ b/code/modules/clothing/spacesuits/rig/rig.dm @@ -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("[M] struggles into \the [src]."), SPAN_NOTICE("You struggle into \the [src].")) 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, "Your [use_obj.name] [use_obj.gender == PLURAL ? "retract" : "retracts"] swiftly.") 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) diff --git a/code/modules/clothing/spacesuits/rig/rig_verbs.dm b/code/modules/clothing/spacesuits/rig/rig_verbs.dm index b5ed0d6e5ef..077a7a08726 100644 --- a/code/modules/clothing/spacesuits/rig/rig_verbs.dm +++ b/code/modules/clothing/spacesuits/rig/rig_verbs.dm @@ -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" diff --git a/code/modules/projectiles/gun.dm b/code/modules/projectiles/gun.dm index 27e7f0df9b8..55f06406841 100644 --- a/code/modules/projectiles/gun.dm +++ b/code/modules/projectiles/gun.dm @@ -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() diff --git a/code/modules/reagents/reagent_containers/spray.dm b/code/modules/reagents/reagent_containers/spray.dm index 842c322d15d..29e258ddab5 100644 --- a/code/modules/reagents/reagent_containers/spray.dm +++ b/code/modules/reagents/reagent_containers/spray.dm @@ -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) diff --git a/html/changelogs/Fenodyree-HardsuitBugfixes.yml b/html/changelogs/Fenodyree-HardsuitBugfixes.yml new file mode 100644 index 00000000000..ab97af516ed --- /dev/null +++ b/html/changelogs/Fenodyree-HardsuitBugfixes.yml @@ -0,0 +1,11 @@ +author: Fenodyree +delete-after: True +changes: + - 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." diff --git a/icons/mob/rig_modules.dmi b/icons/mob/rig_modules.dmi index a184129e69208ed8153558ace0eb977102b1a50b..f18fb52f28c01ece46d3a0b4a1c765320e9590f0 100644 GIT binary patch literal 4519 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 literal 4399 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 U^g-v;;r|e$8!%|eb>~O_1$cKmP5=M^