diff --git a/aurorastation.dme b/aurorastation.dme index 5e6732c5cce..152d15605cc 100644 --- a/aurorastation.dme +++ b/aurorastation.dme @@ -73,6 +73,7 @@ #include "code\__DEFINES\gradient.dm" #include "code\__DEFINES\guns.dm" #include "code\__DEFINES\hallucinations.dm" +#include "code\__DEFINES\hardsuits.dm" #include "code\__DEFINES\html.dm" #include "code\__DEFINES\hud.dm" #include "code\__DEFINES\hydroponics.dm" @@ -2121,7 +2122,6 @@ #include "code\modules\clothing\spacesuits\miscellaneous.dm" #include "code\modules\clothing\spacesuits\spacesuits.dm" #include "code\modules\clothing\spacesuits\syndi.dm" -#include "code\modules\clothing\spacesuits\rig\_defines.dm" #include "code\modules\clothing\spacesuits\rig\hardsuit_token.dm" #include "code\modules\clothing\spacesuits\rig\rig.dm" #include "code\modules\clothing\spacesuits\rig\rig_attackby.dm" diff --git a/code/__DEFINES/hardsuits.dm b/code/__DEFINES/hardsuits.dm new file mode 100644 index 00000000000..bb283f25f98 --- /dev/null +++ b/code/__DEFINES/hardsuits.dm @@ -0,0 +1,43 @@ +/** MODULE TYPES */ +/** + * Note that these types describe the primary functionality of a given module; additional configuration data provided by modules can + * give them behavior modes that might seem to fall into multiple types below. + * + * For example, the Leg Actuators is type MODULETYPE_USABLE_ACTIVE, because it has a middle-click use functionality as its primary. However, + * it provides a configuration option to toggle fall damping on and off. + */ +/// Passive module, just acts when put in naturally. +#define MODULETYPE_PASSIVE 0 +/// Toggle module: you turn it on/off and it does stuff. +#define MODULETYPE_TOGGLE 1 +/// Usable module: you can use these for a one-time effect; they are things that just Happen w/o a click action. +#define MODULETYPE_USABLE 2 +/// Actively usable module: you may only have one selected at a time, and give you a special click action. +#define MODULETYPE_USABLE_ACTIVE 3 + +/* + * Hardsuit power usage drains this much energy per tick from the cell. We don't accurately model this with CELLRATE and etc. because + * in testing, it just didn't feel as good; static values felt better because they were more consistent/predictable w different configs. + * There are 3600 machine ticks in a 2hr round, and a default cell has 30000 power. + * + * Note that every additional 1 power usage per tick (due to modules) will DECREASE cell lifetime by ~8m + */ + +/// By itself, cell drains fully in 2hr5m +#define CHARGE_DRAIN_LOW 8 +/// By itself, cell drains fully in 1hr40m +#define CHARGE_DRAIN_DEFAULT 10 +/// By itself, cell drains fully in 1hr23m +#define CHARGE_DRAIN_HIGH 12 + +#define MODULE_GENERAL 1 +#define MODULE_LIGHT_COMBAT 2 +#define MODULE_HEAVY_COMBAT 4 +#define MODULE_UTILITY 8 +#define MODULE_MEDICAL 16 +#define MODULE_SPECIAL 32 +#define MODULE_VAURCA 64 + +#define ONLY_DEPLOY 1 +#define ONLY_RETRACT 2 +#define SEAL_DELAY 30 diff --git a/code/game/machinery/cryopod.dm b/code/game/machinery/cryopod.dm index e4ed3970123..1a3085e6d31 100644 --- a/code/game/machinery/cryopod.dm +++ b/code/game/machinery/cryopod.dm @@ -489,7 +489,7 @@ src.add_fingerprint(user) /obj/machinery/cryopod/verb/eject() - set name = "Eject Pod" + set name = "Eject from Pod" set category = "Object" set src in oview(1) if(use_check_and_message(usr)) diff --git a/code/game/machinery/rechargestation.dm b/code/game/machinery/rechargestation.dm index 369d3321732..691247d6946 100644 --- a/code/game/machinery/rechargestation.dm +++ b/code/game/machinery/rechargestation.dm @@ -140,11 +140,12 @@ target = IC.cell // Different reactor types have different external recharge speeds. - reactor = H.internal_organs_by_name[BP_REACTOR] - if(!istype(reactor)) - return + if(isipc(H)) + reactor = H.internal_organs_by_name[BP_REACTOR] + if(!istype(reactor)) + return - if((!target || target.percent() > 95) && istype(H.back, /obj/item/rig)) + if((!target || target.percent() > 98) && istype(H.back, /obj/item/rig)) var/obj/item/rig/R = H.back if(R.cell && !R.cell.fully_charged()) target = R.cell @@ -306,6 +307,20 @@ return TRUE return FALSE +/obj/machinery/recharge_station/verb/eject() + set src in oview(1) + set category = "Object" + set name = "Eject from Recharge Station" + + if(!use_check_and_message(usr)) + return + src.go_out() + add_fingerprint(usr) + return + +/obj/machinery/recharge_station/AltClick() + eject() + /obj/machinery/recharge_station/proc/go_out() if(!occupant) return diff --git a/code/game/objects/effects/burnt_wall.dm b/code/game/objects/effects/burnt_wall.dm index f54afe35233..57d7fbd1956 100644 --- a/code/game/objects/effects/burnt_wall.dm +++ b/code/game/objects/effects/burnt_wall.dm @@ -35,8 +35,8 @@ return ..() /obj/effect/overlay/thermite - name = "burning thermite" - desc = "It's flaming ball of thermite!" + name = "blazing heat" + desc = "It's blazing wall of heat!" icon = 'icons/effects/fire.dmi' icon_state = "2" anchored = TRUE diff --git a/code/game/objects/items/weapons/tanks/jetpack.dm b/code/game/objects/items/weapons/tanks/jetpack.dm index 1ccdf6b8996..010ba06d784 100644 --- a/code/game/objects/items/weapons/tanks/jetpack.dm +++ b/code/game/objects/items/weapons/tanks/jetpack.dm @@ -59,11 +59,23 @@ toggle_rockets_stabilization(usr) -/obj/item/tank/jetpack/proc/toggle_rockets_stabilization(mob/user, var/list/message_mobs) - stabilization_on = !stabilization_on - to_chat(user, SPAN_NOTICE("You toggle \the [src]'s stabilization [stabilization_on ? "on" : "off"].")) - for(var/M in message_mobs) - to_chat(M, SPAN_NOTICE("[user] toggles \the [src]'s stabilization [stabilization_on ? "on" : "off"].")) +/// This toggle proc is used for the verb, but we break activation and deactivation into separate procs for management from other objs (like hardsuits). +/// Param 'send_message' will make proc send feedback messages if default TRUE, but proc will be silent if FALSE. +/obj/item/tank/jetpack/proc/toggle_rockets_stabilization(mob/user) + if(stabilization_on) + disable_rockets_stabilization(user) + else + enable_rockets_stabilization(user) + +/// Exists to be called directly from other objs (like hardsuits). +/obj/item/tank/jetpack/proc/enable_rockets_stabilization(mob/user) + stabilization_on = TRUE + balloon_alert(user, "stabilizers on!") + +/// Exists to be called directly from other objs (like hardsuits). +/obj/item/tank/jetpack/proc/disable_rockets_stabilization(mob/user, var/message = TRUE) + stabilization_on = FALSE + balloon_alert(user, "stabilizers off!") /obj/item/tank/jetpack/verb/toggle() set name = "Toggle Jetpack" @@ -72,20 +84,33 @@ toggle_jetpack(usr) -/obj/item/tank/jetpack/proc/toggle_jetpack(mob/user, var/list/message_mobs) - on = !on - toggle_rockets_stabilization(user, message_mobs) +/// This toggle proc is used for the verb, but we break activation and deactivation into separate procs for management from other objs (like hardsuits). +/obj/item/tank/jetpack/proc/toggle_jetpack(mob/user) if(on) - icon_state = "[icon_state]-on" + disable_jetpack(user) else - icon_state = initial(icon_state) + enable_jetpack(user) + +/// Exists to be called directly from other objs (like hardsuits). +/obj/item/tank/jetpack/proc/enable_jetpack(mob/user) + on = TRUE + enable_rockets_stabilization(user, FALSE) + icon_state = "[icon_state]-on" user.update_inv_back() user.update_action_buttons() - to_chat(user, SPAN_NOTICE("You toggle \the [src]'s thrusters [on ? "on" : "off"].")) - for(var/M in message_mobs) - to_chat(M, SPAN_NOTICE("[user] toggles \the [src]'s thrusters [on ? "on" : "off"].")) + balloon_alert(user, "jetpack on!") + +/// Exists to be called directly from other objs (like hardsuits). +/obj/item/tank/jetpack/proc/disable_jetpack(mob/user, var/list/message_mobs) + on = FALSE + icon_state = initial(icon_state) + + user.update_inv_back() + user.update_action_buttons() + + balloon_alert(user, "jetpack off!") /obj/item/tank/jetpack/proc/allow_thrust(num, mob/living/user as mob) if(!(src.on)) diff --git a/code/game/turfs/simulated/walls.dm b/code/game/turfs/simulated/walls.dm index a2fce843828..c6fe32cdc34 100644 --- a/code/game/turfs/simulated/walls.dm +++ b/code/game/turfs/simulated/walls.dm @@ -187,7 +187,7 @@ new /obj/effect/decal/cleanable/molten_item(src) if(do_message) - visible_message(SPAN_DANGER("\The [src] spontaneously combusts!")) //!!OH SHIT!! + visible_message(SPAN_DANGER("\The [src] melts into slag!")) //!!OH SHIT!! /turf/simulated/wall/add_damage(damage, damage_flags, damage_type, armor_penetration, obj/weapon) if(locate(/obj/effect/overlay/wallrot) in src) @@ -268,11 +268,14 @@ if(!can_melt()) return - var/obj/effect/overlay/thermite/O = new /obj/effect/overlay/thermite(src) to_chat(user, SPAN_WARNING("The thermite starts melting through the wall.")) - QDEL_IN(O, 100) - addtimer(CALLBACK(src, TYPE_PROC_REF(/atom, melt), FALSE), 100) + create_melt_overlay(10 SECONDS) + addtimer(CALLBACK(src, TYPE_PROC_REF(/atom, melt), FALSE), 10 SECONDS) + +/turf/simulated/wall/proc/create_melt_overlay(overlay_lifetime = 2 SECONDS) + var/obj/effect/overlay/thermite/O = new /obj/effect/overlay/thermite(src) + QDEL_IN(O, overlay_lifetime) /turf/simulated/wall/proc/radiate() var/total_radiation = material.radioactivity + (reinf_material ? reinf_material.radioactivity / 2 : 0) diff --git a/code/modules/clothing/spacesuits/rig/_defines.dm b/code/modules/clothing/spacesuits/rig/_defines.dm deleted file mode 100644 index e197f715a51..00000000000 --- a/code/modules/clothing/spacesuits/rig/_defines.dm +++ /dev/null @@ -1,7 +0,0 @@ -#define MODULE_GENERAL 1 -#define MODULE_LIGHT_COMBAT 2 -#define MODULE_HEAVY_COMBAT 4 -#define MODULE_UTILITY 8 -#define MODULE_MEDICAL 16 -#define MODULE_SPECIAL 32 -#define MODULE_VAURCA 64 diff --git a/code/modules/clothing/spacesuits/rig/modules/combat.dm b/code/modules/clothing/spacesuits/rig/modules/combat.dm index 0b298620152..b8e3db68846 100644 --- a/code/modules/clothing/spacesuits/rig/modules/combat.dm +++ b/code/modules/clothing/spacesuits/rig/modules/combat.dm @@ -20,7 +20,7 @@ /obj/item/rig_module/grenade_launcher name = "mounted grenade launcher" desc = "A shoulder-mounted micro-explosive dispenser." - selectable = TRUE + module_type = MODULETYPE_USABLE_ACTIVE icon_state = "grenade" interface_name = "integrated grenade launcher" @@ -78,7 +78,7 @@ return FALSE if(charge.charges <= 0) - to_chat(user, SPAN_WARNING("Insufficient grenades!")) + balloon_alert(user, "insufficient grenades!") return FALSE charge.charges-- @@ -91,7 +91,6 @@ /obj/item/rig_module/grenade_launcher/frag name = "mounted frag grenade launcher" desc = "A shoulder-mounted fragmentation explosives dispenser." - selectable = TRUE icon_state = "grenade" interface_name = "integrated frag grenade launcher" @@ -114,8 +113,7 @@ /obj/item/rig_module/mounted name = "mounted laser cannon" desc = "A shoulder-mounted battery-powered laser cannon mount." - selectable = TRUE - usable = TRUE + module_type = MODULETYPE_USABLE_ACTIVE module_cooldown = 0 icon_state = "lcannon" @@ -160,6 +158,8 @@ construction_cost= list(DEFAULT_WALL_MATERIAL = 7000, MATERIAL_GLASS = 2250, MATERIAL_URANIUM = 3250, MATERIAL_GOLD = 2500) construction_time = 300 + suit_overlay_active = "mounted-lascannon" + interface_name = "mounted energy gun" interface_desc = "A forearm-mounted suit-powered energy gun." @@ -174,10 +174,7 @@ construction_cost = list(DEFAULT_WALL_MATERIAL = 7000, MATERIAL_GLASS = 5250) construction_time = 300 - usable = FALSE - suit_overlay_active = "mounted-taser" - suit_overlay_inactive = "mounted-taser" interface_name = "mounted taser" interface_desc = "A shoulder-mounted cell-powered taser." @@ -191,6 +188,8 @@ desc = "A shoulder-mounted battery-powered pulse rifle mount." icon_state = "pulse" + suit_overlay_active = "mounted-ion" + interface_name = "mounted pulse rifle" interface_desc = "A shoulder-mounted cell-powered pulse rifle." @@ -212,6 +211,8 @@ desc = "A forearm-mounted suit-powered ballistic submachine gun." icon_state = "smg" + suit_overlay_active = "mounted-ion" + interface_name = "mounted submachine gun" interface_desc = "A forearm-mounted suit-powered ballistic submachine gun." @@ -222,6 +223,8 @@ desc = "A forearm-mounted suit-powered xray laser gun." icon_state = "xray" + suit_overlay_active = "mounted-ion" + interface_name = "mounted xray laser gun" interface_desc = "A forearm-mounted suit-powered xray laser gun." @@ -232,6 +235,8 @@ desc = "A shoulder-mounted battery-powered ion rifle mount." icon_state = "ion" + suit_overlay_active = "mounted-ion" + interface_name = "mounted ion rifle" interface_desc = "A shoulder-mounted cell-powered ion rifle." @@ -242,6 +247,8 @@ desc = "A shoulder-mounted battery-powered tesla carbine mount." icon_state = "tesla" + suit_overlay_active = "mounted-ion" + interface_name = "mounted tesla carbine" interface_desc = "A shoulder-mounted cell-powered tesla carbine." @@ -249,25 +256,44 @@ /obj/item/rig_module/mounted/plasmacutter name = "hardsuit plasma cutter" - desc = "A forearm mounted kinetic accelerator" + desc = "A forearm-mounted plasma arc cutter." icon_state = "plasmacutter" interface_name = "plasma cutter" interface_desc = "A self-sustaining plasma arc capable of cutting through walls." - suit_overlay_active = "plasmacutter" - suit_overlay_inactive = "plasmacutter" + suit_overlay_active = "mounted-plasmacutter" construction_cost = list(MATERIAL_GLASS = 5250, DEFAULT_WALL_MATERIAL = 30000, MATERIAL_SILVER = 5250, MATERIAL_PHORON = 7250) + activates_on_touch = TRUE construction_time = 300 + use_power_cost = 15 category = MODULE_UTILITY gun_type = /obj/item/gun/energy/plasmacutter/mounted +/// Snowflake; this is a tool more than a gun, so it has special adjacency behaviors. +/obj/item/rig_module/mounted/plasmacutter/engage(atom/target, mob/user) + if(!check_can_use(user)) + return FALSE + + if(!confined_use && !isturf(user.loc)) + to_chat(user, SPAN_DANGER("You cannot use the suit in a confined space.")) + return FALSE + + if(target) + // Don't prevent the player from trying to shoot someone who is up in their face if needs be. + if(isliving(target) || !target.Adjacent(user)) + gun.Fire(target, user) + else + target.attackby(gun, user) + return TRUE + /obj/item/rig_module/mounted/thermalldrill name = "hardsuit thermal drill" desc = "An incredibly lethal looking thermal drill." icon_state = "thermaldrill" interface_name = "thermal drill" interface_desc = "A potent drill that can pierce rock walls over long distances." + use_power_cost = 20 gun_type = /obj/item/gun/energy/vaurca/thermaldrill/mounted @@ -279,20 +305,18 @@ icon_state = "eblade" activate_string = "Project Blade" - deactivate_string = "Cancel Blade" interface_name = "spider fang blade" interface_desc = "A lethal energy projector that can shape a blade projected from the hand of the wearer or launch radioactive darts." - usable = FALSE - selectable = TRUE - toggleable = TRUE use_power_cost = 50 active_power_cost = 10 passive_power_cost = 0 gun_type = /obj/item/gun/energy/crossbow/ninja + var/blade_deployed = FALSE + category = MODULE_SPECIAL /obj/item/rig_module/mounted/energy_blade/process() @@ -303,6 +327,18 @@ return ..() +/obj/item/rig_module/mounted/energy_blade/get_configuration() + . = ..() + .["deploy_blade"] = add_ui_configuration(activate_string, "bool", blade_deployed) + +/obj/item/rig_module/mounted/energy_blade/configure_edit(key, value) + switch(key) + if("deploy_blade") + if(!blade_deployed) + activate(src, usr) + else + deactivate() + /obj/item/rig_module/mounted/energy_blade/activate(mob/user) ..() @@ -319,6 +355,7 @@ var/obj/item/melee/energy/blade/blade = new(M) blade.creator = M M.put_in_hands(blade) + blade_deployed = TRUE /obj/item/rig_module/mounted/energy_blade/deactivate() ..() @@ -330,12 +367,12 @@ for(var/obj/item/melee/energy/blade/blade in M.contents) qdel(blade) + blade_deployed = FALSE /obj/item/rig_module/fabricator name = "matter fabricator" desc = "A self-contained microfactory system for hardsuit integration." - selectable = TRUE - usable = TRUE + module_type = MODULETYPE_USABLE_ACTIVE use_power_cost = 10 icon_state = "enet" @@ -356,7 +393,7 @@ var/mob/living/H = holder.wearer - if(target) + if(target && target != H) var/obj/item/firing = new fabrication_type(get_turf(src)) holder.wearer.visible_message(SPAN_DANGER("[user] launches \a [firing]!")) firing.throw_at(target, fire_force, fire_distance) @@ -388,14 +425,12 @@ name = "mounted tesla coil" desc = "A mounted tesla coil that discharges a powerful lightning strike around the user." icon_state = "ewar" + suit_overlay_active = "mounted-ion" interface_name = "mounted tesla coil" interface_desc ="Discharges a powerful lightning strike around the user." - + module_type = MODULETYPE_USABLE use_power_cost = 30 module_cooldown = 100 - - usable = TRUE - category = MODULE_LIGHT_COMBAT /obj/item/rig_module/tesla_coil/engage(atom/target, mob/user) diff --git a/code/modules/clothing/spacesuits/rig/modules/computer.dm b/code/modules/clothing/spacesuits/rig/modules/computer.dm index 7be43b9d1d4..9f6719c7b9c 100644 --- a/code/modules/clothing/spacesuits/rig/modules/computer.dm +++ b/code/modules/clothing/spacesuits/rig/modules/computer.dm @@ -23,7 +23,7 @@ to_chat(usr, SPAN_WARNING("Your module is not installed in a hardsuit.")) return - module.holder.ui_interact(usr, nano_state = GLOB.contained_state) + module.holder.ui_interact(usr) /mob var/get_rig_stats = 0 @@ -32,9 +32,7 @@ name = "IIS module" desc = "An integrated intelligence system module suitable for most hardsuits." icon_state = "IIS" - toggleable = TRUE - usable = TRUE - disruptive = FALSE + module_type = MODULETYPE_TOGGLE activates_on_touch = TRUE confined_use = TRUE @@ -46,10 +44,12 @@ deactivate_string = "Disable Dataspike" interface_name = "integrated intelligence system" - interface_desc = "A socket that supports a range of artificial intelligence systems." + interface_desc = "A socket that supports a range of artificial intelligence systems. When active, you can click on any active AI (including traditional ship/station AIs, pAIs, and robot intelligence circuits) to attempt to integrate it into your suit systems." - var/mob/integrated_ai // Direct reference to the actual mob held in the suit. - var/obj/item/ai_card // Reference to the MMI, posibrain, intellicard or pAI card previously holding the AI. + /// Direct reference to the actual mob held in the suit. + var/mob/integrated_ai + /// Reference to the MMI, intellicard or pAI card previously holding the AI. + var/obj/item/ai_card var/obj/item/ai_verbs/verb_holder category = MODULE_GENERAL @@ -137,6 +137,18 @@ return FALSE +/obj/item/rig_module/ai_container/get_configuration() + . = ..() + var/button_label = "No AI Currently Installed" + if(integrated_ai) + button_label = integrated_ai.name + .["eject"] = add_ui_configuration(engage_string, "button", button_label) + +/obj/item/rig_module/ai_container/configure_edit(key, value, user) + switch(key) + if("eject") + engage(null, user) + /obj/item/rig_module/ai_container/engage(atom/target, mob/user) if(!..()) return FALSE @@ -227,10 +239,10 @@ name = "datajack module" desc = "A simple induction datalink module." icon_state = "datajack" - toggleable = TRUE activates_on_touch = TRUE - usable = FALSE + module_type = MODULETYPE_USABLE + engage_string = "Eject AI" activate_string = "Enable Datajack" deactivate_string = "Disable Datajack" @@ -318,8 +330,7 @@ name = "electrowarfare module" desc = "A bewilderingly complex bundle of fiber optics and chips." icon_state = "ewar" - toggleable = TRUE - usable = FALSE + module_type = MODULETYPE_TOGGLE confined_use = TRUE activate_string = "Enable Countermeasures" @@ -352,7 +363,7 @@ name = "hardsuit power sink" desc = "An heavy-duty power sink." icon_state = "powersink" - toggleable = TRUE + module_type = MODULETYPE_TOGGLE activates_on_touch = TRUE disruptive = FALSE diff --git a/code/modules/clothing/spacesuits/rig/modules/modules.dm b/code/modules/clothing/spacesuits/rig/modules/modules.dm index e23339cf70a..f635371fc80 100644 --- a/code/modules/clothing/spacesuits/rig/modules/modules.dm +++ b/code/modules/clothing/spacesuits/rig/modules/modules.dm @@ -15,7 +15,9 @@ icon_state = "generic" matter = list(DEFAULT_WALL_MATERIAL = 20000, MATERIAL_PLASTIC = 30000, MATERIAL_GLASS = 5000) + /// This is literally never read anywhere. All construction cost info lives within the /design. Due for removal. var/list/construction_cost = list(DEFAULT_WALL_MATERIAL=7000, MATERIAL_GLASS =7000) + /// This is literally never read anywhere. All construction cost info lives within the /design. Due for removal. var/construction_time = 100 var/damage = 0 @@ -24,42 +26,67 @@ var/module_cooldown = 10 var/next_use = 0 - var/engage_on_activate = TRUE // Whether the rig should call engage() in its activate() proc - var/toggleable // Set to 1 for the device to show up as an active effect. - var/usable // Set to 1 for the device to have an on-use effect. - var/selectable // Set to 1 to be able to assign the device as primary system. - var/redundant // Set to 1 to ignore duplicate module checking when installing. - var/permanent // If set, the module can't be removed. - var/disruptive = 1 // Can disrupt by other effects. - var/activates_on_touch // If set, unarmed attacks will call engage() on the target. - var/confined_use = FALSE // If set, can be used inside mechs and other vehicles. + /// The type of module this is. + var/module_type + /// Set TRUE to ignore duplicate module checking when installing. + var/redundant + /// If set, the module can't be removed. + var/permanent + /// Can disrupt by other effects. + var/disruptive = 1 + /// If set, unarmed attacks will call engage() on the target. + var/activates_on_touch + /// If set, can be used inside mechs and other vehicles. + var/confined_use = FALSE - var/active // Basic module status - var/disruptable // Will deactivate if some other powers are used. - var/attackdisrupts = 0 // Will deactivate if user attacks + /// Basic module status + var/active + /// Will deactivate if some other powers are used. + var/disruptable + /// Will deactivate if user attacks + var/attackdisrupts = 0 - var/use_power_cost = 0 // Power used when single-use ability called. - var/active_power_cost = 0 // Power used when turned on. - var/passive_power_cost = 0 // Power used when turned off. + /// Power used when single-use ability called. + var/use_power_cost = 0 + /// Power used when turned on. + var/active_power_cost = 0 + /// Power used when turned off. + var/passive_power_cost = 0 - var/list/charges // Associative list of charge types and remaining numbers. - var/charge_selected // Currently selected option used for charge dispensing. + /// Associative list of charge types and remaining numbers. + var/list/charges + /// Currently selected option used for charge dispensing. + var/charge_selected // Icons. var/suit_overlay - var/suit_overlay_active // If set, drawn over icon and mob when effect is active. - var/suit_overlay_inactive // As above, inactive. - var/suit_overlay_used // As above, when engaged. + /// If set, drawn over icon and mob when module is active OR selected. + var/suit_overlay_active + /// As above, when inactive. + var/suit_overlay_inactive + /// As above, when engaged. Not currently used, but in future should be a flick animation. + var/suit_overlay_used //Display fluff var/interface_name = "hardsuit upgrade" var/interface_desc = "A generic hardsuit upgrade." + /// This is a string when a module with an active use is engaged, fire and forget-style. The UI will default to 'Engage' if this value is somehow nulled. var/engage_string = "Engage" + /// This is when a module is toggled on. The UI will default to 'Activate' if this value is somehow nulled. var/activate_string = "Activate" + /// This is when a module is toggled off. The UI will default to 'Deactivate' if this value is somehow nulled. var/deactivate_string = "Deactivate" var/list/stat_rig_module/stat_modules = new() - var/category // Use for restricting modules for specific suits, to specialize + /// Use for restricting modules for specific suits, to specialize + var/category + + /// Sound played (to user only) when user changes a module configuration setting. + var/sound_config = 'sound/machines/terminal/terminal_select.ogg' + /// Sound played (to user only) when user activates a module. + var/sound_activate = 'sound/machines/terminal/terminal_prompt_confirm.ogg' + /// Sound played (to user only) when user deactivates a module. + var/sound_deactivate = 'sound/machines/terminal/terminal_prompt_deny.ogg' /obj/item/rig_module/get_examine_text(mob/user, distance, is_adjacent, infix, suffix) . = ..() @@ -160,22 +187,24 @@ if(B) B.set_color_for(COLOR_RED, module_cooldown) -//Proc for one-use abilities like teleport. -/obj/item/rig_module/proc/engage(atom/target, mob/user) +/// Handles all usage checks for rig module use; both engage and activate use this. +/obj/item/rig_module/proc/check_can_use(mob/user) if(damage >= 2) - to_chat(user, SPAN_WARNING("\The [interface_name] is damaged beyond use!")) + sound_to(user, 'sound/machines/terminal/terminal_error.ogg') + balloon_alert(user, "[interface_name] is damaged!") return FALSE if(world.time < next_use) - to_chat(user, SPAN_WARNING("You cannot use \the [interface_name] again so soon.")) + sound_to(user, 'sound/machines/terminal/terminal_error.ogg') + balloon_alert(user, "[interface_name] on cooldown!") return FALSE if(!holder || holder.canremove) - to_chat(user, SPAN_WARNING("The suit is not initialized.")) + sound_to(user, 'sound/machines/terminal/terminal_error.ogg') + balloon_alert(user, "suit not initialized!") return FALSE - if(user.lying || user.stat || user.stunned || user.paralysis || user.weakened) - to_chat(user, SPAN_WARNING("You cannot use the suit in this state.")) + if(use_check_and_message(user, USE_ALLOW_NON_ADJACENT)) return FALSE if(holder.wearer && holder.wearer.lying) @@ -183,26 +212,39 @@ return FALSE if(holder.security_check_enabled && holder.locked && !holder.check_suit_access(user)) - to_chat(user, SPAN_DANGER("Access denied.")) + // No sound or balloon alert here, because they live in check_suit_access() return FALSE - if(!holder.check_power_cost(user, use_power_cost, 0, src, (istype(user,/mob/living/silicon ? 1 : 0) ) ) ) + if(holder.sealing) + sound_to(user, 'sound/machines/terminal/terminal_error.ogg') + balloon_alert(user, "suit busy adjusting seals!") return FALSE - if(!confined_use && istype(user.loc, /mob/living/heavy_vehicle)) - to_chat(user, SPAN_DANGER("You cannot use the suit in the confined space.")) + var/is_user_silicon = issilicon(user) + if(!holder.check_power_cost(user, use_power_cost, 0, src, is_user_silicon) || !holder.check_power_cost(user, active_power_cost, 0, src, is_user_silicon)) + sound_to(user, 'sound/effects/pop.ogg') + balloon_alert(user, "insufficient power!") return FALSE return TRUE -// Proc for toggling on active abilities. +/// Proc for one-use abilities like teleport. +/obj/item/rig_module/proc/engage(atom/target, mob/user) + if(!check_can_use(user)) + return FALSE + + if(!confined_use && !isturf(user.loc)) + to_chat(user, SPAN_DANGER("You cannot use the suit in a confined space.")) + return FALSE + + return TRUE + +/// Proc for toggling on active abilities. /obj/item/rig_module/proc/activate(mob/user) if(active) return FALSE - if(engage_on_activate && !do_engage(null, user)) - return FALSE - if(use_check_and_message(user, USE_ALLOW_NON_ADJACENT)) + if(!check_can_use(user)) return FALSE active = TRUE @@ -211,7 +253,8 @@ suit_overlay = suit_overlay_active else suit_overlay = null - holder.update_icon() + + holder?.update_icon(TRUE) return TRUE @@ -229,8 +272,8 @@ suit_overlay = suit_overlay_inactive else suit_overlay = null - if(holder) - holder.update_icon() + + holder?.update_icon(TRUE) return TRUE @@ -259,6 +302,19 @@ to_chat(holder.wearer, wearer_text) return +/// Creates a list of configuring options for this module. Possible configs include number, bool, color, list, button. +/obj/item/rig_module/proc/get_configuration(mob/user) + return list() + +/// Generates an element of the get_configuration list with a display name, type and value +/obj/item/rig_module/proc/add_ui_configuration(display_name, type, value, list/values) + return list("display_name" = display_name, "type" = type, "value" = value, "values" = values) + +/// Receives configure edits from the TGUI and edits the vars +/obj/item/rig_module/proc/configure_edit(key, value, mob/user) + return + +// Procs handling the statpanel 'Hardsuit Modules' tab, which is auto-populated by controls for all modules. /mob/living/carbon/human/get_actions_for_statpanel() var/list/data = ..() if(istype(back,/obj/item/rig)) @@ -314,33 +370,33 @@ ..() name = module.activate_string if(module.active_power_cost) - name += " ([module.active_power_cost*10]A)" + name += " ([module.active_power_cost]A)" module_mode = "activate" /stat_rig_module/activate/CanUse() - return module.toggleable && !module.active + return (module.module_type == MODULETYPE_TOGGLE) && !module.active /stat_rig_module/deactivate/New(var/obj/item/rig_module/module) ..() name = module.deactivate_string // Show cost despite being 0, if it means changing from an active cost. if(module.active_power_cost || module.passive_power_cost) - name += " ([module.passive_power_cost*10]P)" + name += " ([module.passive_power_cost]P)" module_mode = "deactivate" /stat_rig_module/deactivate/CanUse() - return module.toggleable && module.active + return (module.module_type == MODULETYPE_TOGGLE) && module.active /stat_rig_module/engage/New(var/obj/item/rig_module/module) ..() name = module.engage_string if(module.use_power_cost) - name += " ([module.use_power_cost*10]E)" + name += " ([module.use_power_cost]E)" module_mode = "engage" /stat_rig_module/engage/CanUse() - return module.usable + return module.module_type == MODULETYPE_USABLE && !module.active /stat_rig_module/select/New() ..() @@ -348,7 +404,7 @@ module_mode = "select" /stat_rig_module/select/CanUse() - if(module.selectable) + if(module.module_type == MODULETYPE_USABLE_ACTIVE) name = module.holder.selected_module == module ? "Selected" : "Select" return TRUE return FALSE diff --git a/code/modules/clothing/spacesuits/rig/modules/ninja.dm b/code/modules/clothing/spacesuits/rig/modules/ninja.dm index 170e7a352f1..3853ec56494 100644 --- a/code/modules/clothing/spacesuits/rig/modules/ninja.dm +++ b/code/modules/clothing/spacesuits/rig/modules/ninja.dm @@ -14,7 +14,7 @@ desc = "A robust hardsuit-integrated stealth module." icon_state = "cloak" - toggleable = TRUE + module_type = MODULETYPE_TOGGLE disruptable = TRUE disruptive = FALSE attackdisrupts = TRUE @@ -69,9 +69,8 @@ desc = "A complex, sleek-looking, hardsuit-integrated teleportation module that exploits bluespace energy to phase from one location to another instantaneously." icon_state = "teleporter" use_power_cost = 40 - redundant = 1 - usable = TRUE - selectable = 1 + redundant = TRUE + module_type = MODULETYPE_USABLE_ACTIVE var/lastteleport var/phase_in_visual = /obj/effect/temp_visual/phase var/phase_out_visual = /obj/effect/temp_visual/phase/out @@ -186,8 +185,7 @@ name = "self-destruct module" desc = "Oh my God, Captain. A bomb." icon_state = "deadman" - usable = TRUE - active = TRUE + module_type = MODULETYPE_USABLE permanent = TRUE engage_string = "Detonate" @@ -230,7 +228,7 @@ name = "anti-theft system" desc = "An advanced anti-theft system that tracks the user's lifesigns." icon_state = "deadman" - usable = FALSE + module_type = MODULETYPE_PASSIVE active = FALSE permanent = FALSE @@ -249,8 +247,7 @@ name = "EMP dissipation module" desc = "A bewilderingly complex bundle of fiber optics and chips. Seems like it uses a good deal of power." active_power_cost = 10 - toggleable = TRUE - usable = FALSE + module_type = MODULETYPE_TOGGLE use_power_cost = 70 module_cooldown = 30 @@ -279,8 +276,7 @@ name = "emergency power generator" desc = "A high yield power generating device that takes a long time to recharge." active_power_cost = 0 - toggleable = FALSE - usable = TRUE + module_type = MODULETYPE_USABLE confined_use = TRUE var/cooldown = 0 @@ -337,10 +333,7 @@ desc = "An advanced door hacking tool that sports a low power cost and incredibly quick door hacking time. The device also supports hacking several signals at once remotely, and the last 10 doors hacked can be instantly accessed." use_power_cost = 10 module_cooldown = 5 - - usable = FALSE - selectable = 0 - toggleable = TRUE + module_type = MODULETYPE_TOGGLE interface_name = "advanced door hacking tool" interface_desc = "An advanced door hacking tool that sports a low power cost and incredibly quick door hacking time. The device also supports hacking several signals at once remotely, and the last 10 doors hacked can be instantly accessed." diff --git a/code/modules/clothing/spacesuits/rig/modules/storage.dm b/code/modules/clothing/spacesuits/rig/modules/storage.dm index 75b6ac8118f..b64f0728611 100644 --- a/code/modules/clothing/spacesuits/rig/modules/storage.dm +++ b/code/modules/clothing/spacesuits/rig/modules/storage.dm @@ -2,6 +2,7 @@ name = "mounted storage unit" interface_name = "mounted storage unit" interface_desc = "A storage unit for storing a precious few items in your hardsuit." + module_type = MODULETYPE_PASSIVE icon_state = "paper" origin_tech = list(TECH_MAGNET = 2, TECH_MATERIAL = 2, TECH_ENGINEERING = 3) @@ -10,7 +11,7 @@ var/obj/item/storage/internal/hardsuit/pockets var/storage_slots = null var/storage_max_w_class = WEIGHT_CLASS_NORMAL - var/storage_max_storage_space = 9 + var/storage_max_storage_space = DEFAULT_BOX_STORAGE /obj/item/rig_module/storage/Initialize() . = ..() diff --git a/code/modules/clothing/spacesuits/rig/modules/utility.dm b/code/modules/clothing/spacesuits/rig/modules/utility.dm index 5e8b9f7c039..6804554448e 100644 --- a/code/modules/clothing/spacesuits/rig/modules/utility.dm +++ b/code/modules/clothing/spacesuits/rig/modules/utility.dm @@ -19,17 +19,22 @@ * /obj/item/rig_module/actuators/combat */ +/// Device-type modules create a contained /obj/item, which can then be used with middle-click. /obj/item/rig_module/device name = "mounted device" desc = "Some kind of hardsuit mount." - usable = FALSE - selectable = 1 - toggleable = FALSE + module_type = MODULETYPE_USABLE_ACTIVE disruptive = FALSE + use_power_cost = 5 var/device_type var/obj/item/device +/obj/item/rig_module/device/Initialize() + . = ..() + if(device_type) + device = new device_type(src) + /obj/item/rig_module/device/Destroy() if(!ispath(src.device)) QDEL_NULL(src.device) @@ -46,6 +51,8 @@ construction_cost = list("$glass" = 5250, DEFAULT_WALL_MATERIAL = 2500) construction_time = 300 + engage_string = "Run Self-Diagnostic" + device_type = /obj/item/healthanalyzer category = MODULE_MEDICAL @@ -56,21 +63,17 @@ interface_name = "vitals tracker" interface_desc = "Shows an informative health readout of the user." - usable = TRUE - selectable = 0 - category = MODULE_GENERAL - /obj/item/rig_module/device/drill name = "hardsuit diamond drill mount" desc = "A very heavy diamond-tipped drill." icon_state = "drill" + module_type = MODULETYPE_USABLE_ACTIVE interface_name = "mounted drill" interface_desc = "A diamond-tipped industrial drill." suit_overlay_active = "mounted-drill" - suit_overlay_inactive = "mounted-drill" - use_power_cost = 0.1 + use_power_cost = 0.2 construction_cost = list(DEFAULT_WALL_MATERIAL = 55000, MATERIAL_GLASS = 2250, MATERIAL_SILVER = 5250, MATERIAL_DIAMOND = 3750) construction_time = 350 @@ -85,7 +88,6 @@ interface_name = "mounted drill" interface_desc = "A basic industrial drill." suit_overlay_active = "mounted-drill" - suit_overlay_inactive = "mounted-drill" use_power_cost = 0.1 device_type = /obj/item/pickaxe/drill @@ -99,8 +101,6 @@ interface_name = "Alden-Saraspova counter" interface_desc = "An exotic particle detector commonly used by xenoarchaeologists." engage_string = "Begin Scan" - usable = TRUE - selectable = 0 device_type = /obj/item/ano_scanner category = MODULE_UTILITY @@ -112,8 +112,6 @@ interface_name = "ore detector" interface_desc = "A sonar system for detecting large masses of ore." engage_string = "Begin Scan" - usable = TRUE - selectable = 0 device_type = /obj/item/mining_scanner category = MODULE_UTILITY @@ -122,17 +120,44 @@ name = "RFD-C mount" desc = "A cell-powered rapid construction device for a hardsuit." icon_state = "rcd" + suit_overlay_active = "mounted-rfd" interface_name = "mounted RFD-C" interface_desc = "A device for building or removing walls. Cell-powered." - usable = TRUE + module_type = MODULETYPE_USABLE_ACTIVE engage_string = "Configure RFD-C" construction_cost = list(DEFAULT_WALL_MATERIAL = 30000, MATERIAL_PHORON = 12500, MATERIAL_SILVER = 10000, MATERIAL_GOLD = 10000) construction_time = 1000 device_type = /obj/item/rfd/construction/mounted + /// Name of the atom (or mode, if deconstructing) to display in the UI. Default for RFD-C is a wall. + var/current_setting = "wall" category = MODULE_UTILITY +/obj/item/rig_module/device/rfd_c/proc/get_current_setting() + var/obj/item/rfd/construction/mounted/our_device = device + switch(our_device?.mode) + if(RFD_FLOORS_AND_WALL) + return "Floors & Wall" + if(RFD_WINDOW_AND_FRAME) + return "Window & Frame" + if(RFD_AIRLOCK) + return "Airlock" + if(RFD_DECONSTRUCT) + return "Deconstruct" + // How? + return "ERROR - CONSULT MANUFACTURER" + +/obj/item/rig_module/device/rfd_c/get_configuration() + . = ..() + .["rfd_settings"] = add_ui_configuration("Configure RFD-C Settings", "button", get_current_setting()) + +/obj/item/rig_module/device/rfd_c/configure_edit(key, value, mob/user) + var/obj/item/rfd/construction/mounted/our_device = device + switch(key) + if("rfd_settings") + our_device.attack_self(user) + /obj/item/rig_module/device/rfd_c/handle_device_engage(atom/target, mob/user) var/resolved = target.attackby(device, user) if(!resolved && device && target) @@ -142,17 +167,13 @@ return FALSE return TRUE -/obj/item/rig_module/device/Initialize() - . = ..() - if(device_type) - device = new device_type(src) - -/obj/item/rig_module/device/engage(atom/target, mob/user) +/obj/item/rig_module/device/rfd_c/engage(atom/target, mob/user) if(!..() || !device) return FALSE + var/obj/item/rfd/construction/mounted/our_device = device if(!target) - device.attack_self(user) + our_device.attack_self(user) return TRUE var/turf/T = get_turf(target) @@ -174,11 +195,11 @@ /obj/item/rig_module/chem_dispenser name = "mounted chemical dispenser" desc = "A complex web of tubing and needles suitable for hardsuit use." + suit_overlay_active = "mounted-injector" + interface_desc = "Dispenses loaded chemicals directly into the wearer's (or adjacent target's) bloodstream." icon_state = "injector" - usable = TRUE - selectable = FALSE - toggleable = FALSE - disruptive = FALSE + module_type = MODULETYPE_USABLE_ACTIVE + disruptive = TRUE confined_use = TRUE construction_cost = list(DEFAULT_WALL_MATERIAL=10000, MATERIAL_GLASS =9250, MATERIAL_GOLD =2500, MATERIAL_SILVER =4250,"phoron"=5500) construction_time = 400 @@ -192,9 +213,9 @@ list("tricordrazine", "tricordrazine", /singleton/reagent/tricordrazine, 80), list("mortaphenyl", "mortaphenyl", /singleton/reagent/mortaphenyl, 80), list("dexalin plus", "dexalinp", /singleton/reagent/dexalin/plus, 80), - list("antibiotics", "thetamycin", /singleton/reagent/thetamycin, 80), - list("antitoxins", "dylovene", /singleton/reagent/dylovene, 80), - list("nutrients", "glucose", /singleton/reagent/nutriment/glucose, 80), + list("thetamycin", "thetamycin", /singleton/reagent/thetamycin, 80), + list("dylovene", "dylovene", /singleton/reagent/dylovene, 80), + list("glucose", "glucose", /singleton/reagent/nutriment/glucose, 80), list("hyronalin", "hyronalin", /singleton/reagent/hyronalin, 80), list("synaptizine", "synaptizine", /singleton/reagent/synaptizine, 80), list("radium", "radium", /singleton/reagent/radium, 80) @@ -205,21 +226,21 @@ category = MODULE_HEAVY_COMBAT /obj/item/rig_module/chem_dispenser/ninja - interface_desc = "Dispenses loaded chemicals directly into the wearer's bloodstream. This variant is made to be extremely light and flexible." + interface_desc = "Dispenses loaded chemicals directly into the wearer's (or adjacent target's) bloodstream. This variant is made to be extremely light and flexible." //just over a syringe worth of each. Want more? Go refill. Gives the ninja another reason to have to show their face. charges = list( list("tricordrazine", "tricordrazine", /singleton/reagent/tricordrazine, 20), list("mortaphenyl", "mortaphenyl", /singleton/reagent/mortaphenyl, 20), list("dexalin plus", "dexalinp", /singleton/reagent/dexalin/plus, 20), - list("antibiotics", "thetamycin", /singleton/reagent/thetamycin, 20), - list("antitoxins", "dylovene", /singleton/reagent/dylovene, 20), - list("nutrients", "glucose", /singleton/reagent/nutriment/glucose, 80), + list("thetamycin", "thetamycin", /singleton/reagent/thetamycin, 20), + list("dylovene", "dylovene", /singleton/reagent/dylovene, 20), + list("glucose", "glucose", /singleton/reagent/nutriment/glucose, 80), list("hyronalin", "hyronalin", /singleton/reagent/hyronalin, 20), list("synaptizine", "synaptizine", /singleton/reagent/synaptizine, 20), list("radium", "radium", /singleton/reagent/radium, 20) ) - + disruptive = FALSE category = MODULE_UTILITY /obj/item/rig_module/chem_dispenser/accepts_item(var/obj/item/input_item, var/mob/living/user) @@ -261,7 +282,7 @@ var/mob/living/carbon/human/H = holder.wearer if(!charge_selected) - to_chat(user, SPAN_WARNING("You have not selected a chemical type.")) + balloon_alert(user, "no chem selected!") return FALSE var/datum/rig_charge/charge = charges[charge_selected] @@ -271,7 +292,7 @@ var/chems_to_use = 5 if(charge.charges <= 0) - to_chat(user, SPAN_WARNING("Insufficient chems!")) + balloon_alert(user, "out of chem!") return FALSE else if(charge.charges < chems_to_use) chems_to_use = charge.charges @@ -300,6 +321,8 @@ if(charge.charges < 0) charge.charges = 0 + playsound(src,'sound/items/reagent_containers/liquid/plastic_bottle_liquid_slosh1.ogg',25,1) + return TRUE /obj/item/rig_module/chem_dispenser/combat @@ -314,7 +337,6 @@ ) interface_name = "combat chem dispenser" - interface_desc = "Dispenses loaded chemicals directly into the bloodstream." category = MODULE_LIGHT_COMBAT @@ -331,7 +353,6 @@ ) interface_name = "vaurca combat chem dispenser" - interface_desc = "Dispenses loaded chemicals directly into the bloodstream." category = MODULE_VAURCA @@ -346,21 +367,18 @@ ) interface_name = "chem dispenser" - interface_desc = "Dispenses loaded chemicals directly into the bloodstream." category = MODULE_GENERAL /obj/item/rig_module/chem_dispenser/injector name = "mounted chemical injector" desc = "A complex web of tubing and a large needle suitable for hardsuit use." - usable = FALSE - selectable = 1 - disruptive = 1 + module_type = MODULETYPE_USABLE_ACTIVE construction_cost = list(DEFAULT_WALL_MATERIAL = 10000, MATERIAL_GLASS = 9250, MATERIAL_GOLD = 2500, MATERIAL_SILVER = 4250, MATERIAL_PHORON = 5500) construction_time = 400 interface_name = "mounted chem injector" - interface_desc = "Dispenses loaded chemicals via an arm-mounted injector." + interface_desc = "Dispenses loaded chemicals directly into the wearer's (or adjacent target's) bloodstream. This variant is made to be extremely light and flexible." category = MODULE_MEDICAL @@ -373,19 +391,15 @@ ) /obj/item/rig_module/voice - name = "hardsuit voice synthesiser" + name = "hardsuit voice synthesizer" desc = "A speaker box and sound processor." icon_state = "megaphone" - usable = TRUE - selectable = 0 - toggleable = FALSE + module_type = MODULETYPE_TOGGLE disruptive = FALSE confined_use = TRUE - engage_string = "Configure Synthesiser" - - interface_name = "voice synthesiser" - interface_desc = "A flexible and powerful voice modulator system." + interface_name = "voice synthesizer" + interface_desc = "A flexible and powerful voice modulator system. Can mimic both names and accents." var/obj/item/voice_changer/voice_holder @@ -406,30 +420,31 @@ ..() holder.speech = src +/obj/item/rig_module/voice/get_configuration() + . = ..() + .["configure"] = add_ui_configuration(engage_string, "button", "Name: [voice_holder?.name], Accent: [voice_holder?.current_accent]") + +/obj/item/rig_module/voice/configure_edit(key, value, user) + switch(key) + if("configure") + engage(null, user) + /obj/item/rig_module/voice/engage(atom/target, mob/user) if(!..()) return FALSE - var/choice= tgui_input_list(user, "Would you like to toggle the synthesiser, set the name or set an accent?", "Synthesizer", list("Enable","Disable","Set Name", "Set Accent")) + var/choice= tgui_input_list(user, "Would you like to set the name or accent for the synthesizer?", src, list("Set Name", "Set Accent")) if(!choice) return FALSE switch(choice) - if("Enable") - active = TRUE - voice_holder.active = TRUE - message_user(user, SPAN_NOTICE("You enable the speech synthesiser."), SPAN_NOTICE("\The [user] enables the speech synthesiser.")) - if("Disable") - active = FALSE - voice_holder.active = FALSE - message_user(user, SPAN_NOTICE("You disable the speech synthesiser."), SPAN_NOTICE("\The [user] disables the speech synthesiser.")) if("Set Name") var/raw_choice = sanitize(input(user, "Please enter a new name.") as text|null, MAX_NAME_LEN) if(!raw_choice) return FALSE voice_holder.voice = raw_choice - message_user(user, SPAN_NOTICE("You set the synthesizer to mimic [voice_holder.voice]."), SPAN_NOTICE("\The [user] set the speech synthesizer to mimic [voice_holder.voice].")) + message_user(user, SPAN_NOTICE("You set the synthesizer to mimic [raw_choice]."), SPAN_NOTICE("\The [user] set the speech synthesizer to mimic [voice_holder.voice].")) if("Set Accent") var/raw_choice = tgui_input_list(user, "Please choose an accent to mimick.", "Accent Mimicry", SSrecords.accents) if(!raw_choice) @@ -438,28 +453,39 @@ message_user(user, SPAN_NOTICE("You set the synthesizer to mimic the [raw_choice] accent."), SPAN_NOTICE("\The [user] set the speech synthesizer the [raw_choice] accent.")) return TRUE +/obj/item/rig_module/voice/activate(mob/user) + if(!..()) + return + voice_holder.active = TRUE + message_user(user, SPAN_NOTICE("You enable the speech synthesizer. Name: [voice_holder?.name], Accent: [voice_holder?.current_accent]"), SPAN_NOTICE("\The [user] enables the speech synthesizer.")) + return TRUE + +/obj/item/rig_module/voice/deactivate(mob/user) + if(!..()) + return FALSE + + voice_holder.active = FALSE + message_user(user, SPAN_NOTICE("You disable the speech synthesizer."), SPAN_NOTICE("\The [user] disables the speech synthesizer.")) + return TRUE + /obj/item/rig_module/maneuvering_jets name = "hardsuit maneuvering jets" desc = "A compact gas thruster system for a hardsuit." icon_state = "thrusters" - usable = TRUE - toggleable = TRUE - selectable = 0 + module_type = MODULETYPE_TOGGLE disruptive = FALSE construction_cost = list(DEFAULT_WALL_MATERIAL = 15000, MATERIAL_GLASS = 4250, MATERIAL_SILVER = 4250, MATERIAL_URANIUM = 5250) construction_time = 300 suit_overlay_active = "maneuvering_active" - suit_overlay_inactive = null //"maneuvering_inactive" - - engage_string = "Toggle Stabilizers" - activate_string = "Activate Thrusters" - deactivate_string = "Deactivate Thrusters" + suit_overlay_inactive = "maneuvering_inactive" interface_name = "maneuvering jets" interface_desc = "An inbuilt EVA maneuvering system that runs off the hardsuit air supply." var/obj/item/tank/jetpack/rig/jets + /// Do we have stabilizers? If yes the user won't move from inertia. + var/stabilize = TRUE category = MODULE_GENERAL @@ -467,49 +493,35 @@ QDEL_NULL(jets) . = ..() -/obj/item/rig_module/maneuvering_jets/engage(atom/target, mob/user) - if(!..()) - return FALSE - var/list/extra_mobs = list() - if(user != holder.wearer) - extra_mobs += holder.wearer - jets.toggle_rockets_stabilization(user, extra_mobs) - return TRUE +/obj/item/rig_module/maneuvering_jets/get_configuration() + . = ..() + .["stabilizers"] = add_ui_configuration("Stabilizers", "bool", stabilize) + +/obj/item/rig_module/maneuvering_jets/configure_edit(key, value, mob/user) + switch(key) + if("stabilizers") + if(engage(src, user)) + jets.toggle_rockets_stabilization(user) /obj/item/rig_module/maneuvering_jets/activate(mob/user) - if(active) + if(!..()) return FALSE - if(use_check_and_message(user)) - return FALSE - - active = TRUE - - if(suit_overlay_active) - suit_overlay = suit_overlay_active - else - suit_overlay = null - holder.update_icon() - if(!jets.on) - var/list/extra_mobs = list() - if(user != holder.wearer) - extra_mobs += holder.wearer - jets.toggle_jetpack(user, extra_mobs) + jets.enable_jetpack(user) + stabilize = jets.stabilization_on return TRUE /obj/item/rig_module/maneuvering_jets/deactivate(mob/user) if(!..()) return FALSE if(jets.on) - var/list/extra_mobs = list() - if(user != holder.wearer) - extra_mobs += holder.wearer - jets.toggle_jetpack(user, extra_mobs) + jets.disable_jetpack(user) return TRUE /obj/item/rig_module/maneuvering_jets/New() ..() jets = new(src) + stabilize = jets.stabilization_on /obj/item/rig_module/maneuvering_jets/installed() ..() @@ -526,8 +538,6 @@ interface_name = "paper dispenser" interface_desc = "Dispenses warm, clean, and crisp sheets of paper." engage_string = "Dispense" - usable = TRUE - selectable = 0 device_type = /obj/item/paper_bin category = MODULE_GENERAL @@ -544,10 +554,10 @@ name = "mounted pen" desc = "For mecha John Hancocks." icon_state = "pen" + module_type = MODULETYPE_USABLE interface_name = "mounted pen" interface_desc = "Signatures with style(tm)." engage_string = "Change color" - usable = TRUE device_type = /obj/item/pen/multi category = MODULE_GENERAL @@ -555,11 +565,11 @@ /obj/item/rig_module/device/stamp name = "mounted internal affairs stamp" desc = "DENIED." + module_type = MODULETYPE_USABLE icon_state = "stamp" interface_name = "mounted stamp" interface_desc = "Leave your mark." engage_string = "Toggle stamp type" - usable = TRUE var/iastamp var/deniedstamp @@ -606,67 +616,51 @@ name = "leg actuators" desc = "A set of electromechanical actuators, for safe traversal of multilevelled areas." icon_state = "actuators" + module_type = MODULETYPE_USABLE_ACTIVE interface_name = "leg actuators" - interface_desc = "Allows you to fall from heights and to jump up onto ledges." + interface_desc = "Allows you to fall from heights and dash up to 4 tiles at once. To jump onto ledges, stand adjacent and facing a climbable wall (one with a walkable turf above it), then target your own turf to rapidly climb to the turf above!" construction_cost = list(DEFAULT_WALL_MATERIAL=15000, MATERIAL_GLASS = 1250, MATERIAL_SILVER =5250) construction_time = 300 disruptive = FALSE - use_power_cost = 5 + active_power_cost = 1 + use_power_cost = 20 module_cooldown = 25 - /* - * TOGGLE - dampens fall, on or off. - * SELECTABLE - Jump forward or up! - */ - toggleable = TRUE - selectable = TRUE - usable = FALSE - - engage_string = "Toggle Leg Actuators" - activate_string = "Enable Leg Actuators" - deactivate_string = "Disable Leg Actuators" - - var/combatType = 0 // Determines whether or not the actuators can do special combat oriented tasks. - // Such as leaping faster, or grappling targets. - var/leapDistance = 4 // Determines how far the actuators allow you to leap (radius, inclusive). - + /// Determines whether or not the actuators can do special combat oriented tasks, + /// such as leaping faster, or grappling targets. + var/combatType = 0 + /// Determines how far the actuators allow you to leap (radius, inclusive). + var/leapDistance = 4 category = MODULE_GENERAL /obj/item/rig_module/actuators/combat name = "military grade leg actuators" desc = "A set of high-powered hydraulic actuators, for improved traversal of multilevelled areas." interface_name = "combat leg actuators" - + interface_desc = "Allows you to fall from heights and dash up to 7 tiles at once. To jump onto ledges, stand adjacent and facing a climbable wall (one with a walkable turf above it), \ + then target your own turf to instantly leap to the turf above! These leg actuators also let you aggressively lunge and grap people by targeting them directly with your dash ability." combatType = 1 leapDistance = 7 - use_power_cost = 10 + use_power_cost = 50 category = MODULE_LIGHT_COMBAT -/obj/item/rig_module/actuators/proc/is_valid_turf(var/turf/T) - if(!T || istype(T, /turf/space) || T.density || T.contains_dense_objects()) - return null - if(isopenturf(T)) - var/obj/structure/lattice/L = locate() in T - if(L) - return L.name - var/turf/leapBelow = GET_TURF_BELOW(T) - if(leapBelow.density) - return leapBelow.name - else if(T.contains_dense_objects()) - return "structure" - else - return null - return T.name +/obj/item/rig_module/actuators/get_configuration() + . = ..() + .["fall_damping"] = add_ui_configuration("Fall Damping", "bool", active) + +/obj/item/rig_module/actuators/configure_edit(key, value, mob/user) + switch(key) + if("fall_damping") + active = !active + balloon_alert(user, "fall damping [active ? "active" : "inactive"]!") /obj/item/rig_module/actuators/engage(atom/target, mob/user) - // This is for when you toggle it on or off. Why do they both run the same - // proc chain ...? :l - if (!target) + if(!target) return TRUE var/mob/living/carbon/human/H = holder.wearer @@ -765,10 +759,25 @@ H.forceMove(leapEnd) return TRUE +/obj/item/rig_module/actuators/proc/is_valid_turf(var/turf/T) + if(!T || istype(T, /turf/space) || T.density || T.contains_dense_objects()) + return null + if(isopenturf(T)) + var/obj/structure/lattice/L = locate() in T + if(L) + return L.name + var/turf/leapBelow = GET_TURF_BELOW(T) + if(leapBelow.density) + return leapBelow.name + else if(T.contains_dense_objects()) + return "structure" + else + return null + return T.name /obj/item/rig_module/cooling_unit name = "mounted cooling unit" - toggleable = TRUE + module_type = MODULETYPE_TOGGLE origin_tech = list(TECH_MAGNET = 2, TECH_MATERIAL = 2, TECH_ENGINEERING = 3) interface_name = "mounted cooling unit" interface_desc = "A heat sink with liquid cooled radiator." @@ -819,14 +828,13 @@ icon_state = "actuators" interface_name = "boring laser" interface_desc = "Allows you to burrow to the z-level below." + module_type = MODULETYPE_USABLE disruptive = 1 use_power_cost = 5 module_cooldown = 25 - usable = TRUE - category = MODULE_VAURCA /obj/item/rig_module/boring/engage(atom/target, mob/user) @@ -850,10 +858,11 @@ GLOBAL_LIST_EMPTY(lattice_users) icon_state = "actuators" interface_name = "neural lattice" interface_desc = "Synchronize neural lattice to reduce pain." + module_type = MODULETYPE_USABLE disruptive = FALSE - toggleable = TRUE + module_type = MODULETYPE_TOGGLE confined_use = TRUE category = MODULE_VAURCA @@ -877,9 +886,11 @@ GLOBAL_LIST_EMPTY(lattice_users) /obj/item/rig_module/foam_sprayer name = "mounted foam sprayer" desc = "A shoulder-mounted metal foam sprayer." - selectable = TRUE + module_type = MODULETYPE_USABLE_ACTIVE icon_state = "actuators" + suit_overlay_active = "mounted-gun" + interface_name = "integrated foam sprayer" interface_desc = "Projects a line of metal foam where the user selects." @@ -922,26 +933,27 @@ GLOBAL_LIST_EMPTY(lattice_users) /obj/item/rig_module/recharger name = "weapon recharge module" desc = "A specialised power cable designed to connect an energy weapon to a hardsuit's power supply." - toggleable = TRUE + module_type = MODULETYPE_TOGGLE icon_state = "powersink" interface_name = "integrated weapon recharger" interface_desc = "Can connect to an energy weapon, recharging it off the hardsuit's power supply. Drag the weapon onto the hardsuit control module to connect it." category = MODULE_LIGHT_COMBAT - usable = FALSE disruptive = FALSE confined_use = TRUE - ///The gun charging off our hardsuit + /// The gun charging off our hardsuit var/obj/item/gun/energy/connected -/obj/item/rig_module/recharger/activate(mob/user) +/obj/item/rig_module/recharger/engage(atom/target, mob/user) if (!..()) return FALSE - if(!connected) to_chat(user, SPAN_NOTICE("\The [src] does not have a connected energy weapon to charge!")) return FALSE + if(!active) + balloon_alert(user, "module inactive!") + return FALSE to_chat(user, SPAN_NOTICE("\The [connected] is now connected to your hardsuit power supply. Deactivate this module to disconnect it.")) return TRUE @@ -949,9 +961,8 @@ GLOBAL_LIST_EMPTY(lattice_users) /obj/item/rig_module/recharger/deactivate(mob/user) if (!..()) return FALSE - - if(connected) + balloon_alert(user, "[connected] disconnected!") connected.disconnect() diff --git a/code/modules/clothing/spacesuits/rig/modules/vision.dm b/code/modules/clothing/spacesuits/rig/modules/vision.dm index 018428d6f60..01922a2595d 100644 --- a/code/modules/clothing/spacesuits/rig/modules/vision.dm +++ b/code/modules/clothing/spacesuits/rig/modules/vision.dm @@ -53,13 +53,10 @@ name = "hardsuit visor" desc = "A layered, translucent visor system for a hardsuit." icon_state = "optics" - + module_type = MODULETYPE_TOGGLE interface_name = "optical scanners" interface_desc = "An integrated multi-mode vision system." - engage_on_activate = FALSE - usable = TRUE - toggleable = TRUE disruptive = FALSE confined_use = TRUE @@ -67,7 +64,9 @@ activate_string = "Enable Visor" deactivate_string = "Disable Visor" - var/datum/rig_vision/vision + active_power_cost = 2 + + var/datum/rig_vision/vision_mode var/list/vision_modes = list( /datum/rig_vision/nvg, /datum/rig_vision/thermal @@ -77,11 +76,77 @@ category = MODULE_GENERAL +/// There should only ever be one vision module installed in a suit. +/obj/item/rig_module/vision/installed() + ..() + holder.visor = src + +/obj/item/rig_module/vision/New() + ..() + + if(!vision_modes) + return + + var/list/processed_vision = list() + for(var/new_vision_mode in vision_modes) + var/datum/rig_vision/vision_datum = new new_vision_mode + processed_vision += vision_datum + + vision_modes = processed_vision + + vision_index = 1 + vision_mode = length(vision_modes) ? vision_modes[vision_index] : null + +/obj/item/rig_module/vision/get_configuration(mob/user) + . = ..() + if(!vision_modes || length(vision_modes) <= 1) + return . + + var/list/modes = list() + for(var/datum/rig_vision/added_mode in vision_modes) + modes += added_mode.mode + + .["vision_mode"] = add_ui_configuration("Visor mode", "list", vision_mode.mode, modes) + +/obj/item/rig_module/vision/configure_edit(key, value, mob/user) + switch(key) + if("vision_mode") + set_vision_mode(user, value) + +/obj/item/rig_module/vision/proc/set_vision_mode(mob/user, var/new_mode) + if(!new_mode || new_mode == "") + return FALSE + + if(!active) + to_chat(user, SPAN_WARNING("\The [src] isn't activated!")) + return FALSE + + var/i = 1 + for(var/datum/rig_vision/V in vision_modes) + if(V.mode == new_mode) + vision_index = i + vision_mode = V + sound_to(user, 'sound/machines/terminal/terminal_select.ogg') + return TRUE + i++ + + return FALSE + +/obj/item/rig_module/vision/activate(mob/user) + . = ..() + sound_to(user, 'sound/items/goggles_charge.ogg') + +/obj/item/rig_module/vision/deactivate(mob/user) + . = ..() + sound_to(user, 'sound/effects/pop.ogg') + /obj/item/rig_module/vision/multi name = "hardsuit optical package" desc = "A complete visor system of optical scanners and vision modes." icon_state = "fulloptics" + active_power_cost = 3 + interface_name = "multi optical visor" interface_desc = "An integrated multi-mode vision system." @@ -100,11 +165,11 @@ desc = "A layered, translucent visor system for a hardsuit." icon_state = "meson" - usable = FALSE - construction_cost = list(DEFAULT_WALL_MATERIAL = 1500, MATERIAL_GLASS = 5000) construction_time = 300 + active_power_cost = 2 + interface_name = "meson/material scanner" interface_desc = "An integrated meson/material scanner." @@ -118,7 +183,7 @@ desc = "A layered, translucent visor system for a hardsuit." icon_state = "thermal" - usable = FALSE + active_power_cost = 2 interface_name = "thermal scanner" interface_desc = "An integrated thermal scanner." @@ -132,11 +197,11 @@ desc = "A multi input night vision system for a hardsuit." icon_state = "night" - usable = FALSE - construction_cost = list(DEFAULT_WALL_MATERIAL = 1500, MATERIAL_GLASS = 5000, MATERIAL_URANIUM = 5000) construction_time = 300 + active_power_cost = 2 + interface_name = "night vision interface" interface_desc = "An integrated night vision system." @@ -149,8 +214,6 @@ desc = "A simple tactical information system for a hardsuit." icon_state = "securityhud" - usable = FALSE - construction_cost = list(DEFAULT_WALL_MATERIAL = 1500, MATERIAL_GLASS = 5000) construction_time = 300 @@ -166,8 +229,6 @@ desc = "A simple medical status indicator for a hardsuit." icon_state = "healthhud" - usable = FALSE - construction_cost = list(DEFAULT_WALL_MATERIAL = 1500, MATERIAL_GLASS = 5000) construction_time = 300 @@ -177,42 +238,3 @@ vision_modes = list(/datum/rig_vision/medhud) category = MODULE_MEDICAL - -// There should only ever be one vision module installed in a suit. -/obj/item/rig_module/vision/installed() - ..() - holder.visor = src - -/obj/item/rig_module/vision/engage(atom/target, mob/user) - if(!..() || !vision_modes) - return FALSE - if(!active) - to_chat(user, SPAN_WARNING("\The [src] isn't activated!")) - return FALSE - - if(vision_modes.len > 1) - vision_index++ - if(vision_index > vision_modes.len) - vision_index = 1 - vision = vision_modes[vision_index] - - message_user(user, SPAN_NOTICE("You cycle \the [src] to [vision.mode] mode."), SPAN_NOTICE("\The [user] cycles \the [src] to [vision.mode] mode.")) - else - to_chat(user, SPAN_WARNING("\The [src] only has one mode.")) - return TRUE - -/obj/item/rig_module/vision/New() - ..() - - if(!vision_modes) - return - - vision_index = 1 - var/list/processed_vision = list() - - for(var/vision_mode in vision_modes) - var/datum/rig_vision/vision_datum = new vision_mode - if(!vision) vision = vision_datum - processed_vision += vision_datum - - vision_modes = processed_vision diff --git a/code/modules/clothing/spacesuits/rig/rig.dm b/code/modules/clothing/spacesuits/rig/rig.dm index 4347d51dba3..d69cf68e206 100644 --- a/code/modules/clothing/spacesuits/rig/rig.dm +++ b/code/modules/clothing/spacesuits/rig/rig.dm @@ -1,7 +1,3 @@ -#define ONLY_DEPLOY 1 -#define ONLY_RETRACT 2 -#define SEAL_DELAY 30 - /* * Defines the behavior of hardsuits/rigs/power armor. */ @@ -36,17 +32,18 @@ unacidable = 1 slowdown = 0.5 // All rigs by default should have slowdown. + action_button_name = "Open Hardsuit Interface" + var/has_sealed_state = FALSE var/has_hidden_jumpsuit = FALSE - var/interface_path = "hardsuit.tmpl" - var/ai_interface_path = "hardsuit.tmpl" - var/interface_title = "Hardsuit Controller" - var/wearer_move_delay //Used for AI moving. + /// Used for AI moving. + var/wearer_move_delay var/ai_controlled_move_delay = 10 - var/last_remote_message // when did a mounted pAI or AI use a module? used to prevent admin msg spam + /// When did a mounted pAI or AI use a module? used to prevent admin msg spam + var/last_remote_message - // Keeps track of what this rig should spawn with. + /// Keeps track of what this rig should spawn with. var/suit_type = "hardsuit" var/list/initial_modules var/chest_type = /obj/item/clothing/suit/space/rig @@ -57,51 +54,82 @@ var/air_type = /obj/item/tank/oxygen //Component/device holders. - var/obj/item/tank/air_supply // Air tank, if any. - var/obj/item/clothing/shoes/boots = null // Deployable boots, if any. - var/obj/item/clothing/suit/space/rig/chest // Deployable chestpiece, if any. - var/obj/item/clothing/head/helmet/space/rig/helmet = null // Deployable helmet, if any. - var/obj/item/clothing/gloves/rig/gloves = null // Deployable gauntlets, if any. - var/obj/item/cell/cell // Power supply, if any. - var/obj/item/rig_module/selected_module = null // Primary system (used with middle-click) - var/obj/item/rig_module/vision/visor // Kinda shitty to have a var for a module, but saves time. - var/obj/item/rig_module/voice/speech // As above. - var/mob/living/carbon/human/wearer // The person currently wearing the rig. - var/image/mob_icon // Holder for on-mob icon. - var/list/installed_modules = list() // Power consumption/use bookkeeping. + /// Air tank, if any. + var/obj/item/tank/air_supply + /// Deployable boots, if any. + var/obj/item/clothing/shoes/boots = null + /// Deployable chestpiece, if any. + var/obj/item/clothing/suit/space/rig/chest + /// Deployable helmet, if any. + var/obj/item/clothing/head/helmet/space/rig/helmet = null + /// Deployable gauntlets, if any. + var/obj/item/clothing/gloves/rig/gloves = null + /// Power supply, if any. + var/obj/item/cell/cell + /// Primary system (used with middle-click) + var/obj/item/rig_module/selected_module = null + /// Kinda shitty to have a var for a module, but saves time. + var/obj/item/rig_module/vision/visor + /// As above. + var/obj/item/rig_module/voice/speech + /// The person currently wearing the rig. + var/mob/living/carbon/human/wearer + /// Holder for on-mob icon. + var/image/mob_icon + /// Power consumption/use bookkeeping. + var/list/installed_modules = list() // Rig status vars. - var/open = 0 // Access panel status. - var/locked = 1 // Lock status. - var/dnaLock // To whom do we belong? - var/crushing = FALSE // Are we crushing the occupant to death? + /// Access panel status. + var/open = FALSE + /// Lock status. + var/locked = TRUE + /// To whom do we belong? + var/dnaLock + /// Are we crushing the occupant to death? + var/crushing = FALSE var/subverted = 0 - var/interface_locked = 0 - var/control_overridden = 0 - var/ai_override_enabled = 0 - var/security_check_enabled = 1 + /// Interfaces get locked when the corresponding rig wire is pulsed. + var/interface_locked = FALSE + /// Whether or not the AI is currently overriding control of the suit. + var/control_overridden = FALSE + /// Whether or not the AI is capable of overriding control of the suit. + var/ai_override_enabled = FALSE + /// Access req status. + var/security_check_enabled = TRUE var/malfunctioning = 0 var/malfunction_delay = 0 var/electrified = 0 - var/locked_down = 0 + + /// Rate at which the suit cell will passively use power when online. + var/cell_draw_rate = CHARGE_DRAIN_DEFAULT var/seal_delay = SEAL_DELAY - var/sealing // Keeps track of seal status independantly of canremove. - var/offline = 1 // Should we be applying suit maluses? - var/offline_slowdown = 1.5 // If the suit is deployed and unpowered, it sets slowdown to this. + /// Keeps track of seal status independantly of canremove. + var/sealing + /// Should we be applying suit maluses? Note that this is tristate: 0 = online; 1 = offline, modules still active; 2 = offline, inc. modules + /// Generally speaking, a suit is 0 when on, 1 when just turned off, and 2 after it has run process(), which deactivates all modules if offline == 1 + var/offline = 0 + /// If the suit is deployed and unpowered, it sets slowdown to this. + var/offline_slowdown = 1.5 var/vision_restriction = TINT_NONE var/offline_vision_restriction = TINT_HEAVY - var/airtight = 1 //If set, will adjust the ITEM_FLAG_AIRTIGHT flag on components. Otherwise it should leave them untouched. + ///If set, will adjust the ITEM_FLAG_AIRTIGHT flag on components. Otherwise it should leave them untouched. + var/airtight = TRUE var/emp_protection = 0 - // Wiring! How exciting.] + // Wiring! How exciting. var/datum/wires/rig/wires var/datum/effect_system/sparks/spark_system - var/allowed_module_types = MODULE_GENERAL // All rigs by default should have access to general + /// All rigs by default should have access to general + var/allowed_module_types = MODULE_GENERAL var/list/species_restricted = list(BODYTYPE_HUMAN,BODYTYPE_TAJARA,BODYTYPE_UNATHI, BODYTYPE_SKRELL, BODYTYPE_IPC, BODYTYPE_IPC_BISHOP, BODYTYPE_IPC_ZENGHU) - var/anomaly_protection = FALSE //If TRUE, this rig will protect against anomalies. Currently only used for AMI hardsuit. + ///If TRUE, this rig will protect against anomalies. Currently only used for AMI hardsuit. + var/anomaly_protection = FALSE + + var/ui_configure_ref /obj/item/rig/get_examine_text(mob/user, distance, is_adjacent, infix, suffix) . = ..() @@ -122,7 +150,7 @@ wires = new(src) if(!LAZYLEN(req_access) && !LAZYLEN(req_one_access)) - locked = 0 + locked = FALSE spark_system = bind_spark(src, 5) @@ -220,6 +248,7 @@ return 0 return 1 +/// Not currently used. /obj/item/rig/proc/reset() offline = 2 canremove = 1 @@ -335,7 +364,7 @@ var/datum/component/armor/armor_component = piece.GetComponent(/datum/component/armor) if(istype(armor_component)) armor_component.sealed = !seal_target - playsound(src, "[!seal_target ? 'sound/machines/rig/rig_deploy.ogg' : 'sound/machines/rig/rig_retract.ogg']", 20, FALSE) + playsound(src, "[!seal_target ? 'sound/machines/rig/rig_deploy.ogg' : 'sound/machines/rig/rig_retract.ogg']", 30, FALSE) else failed_to_seal = 1 @@ -361,7 +390,7 @@ // Success! canremove = seal_target to_chat(wearer, SPAN_NOTICE("Your entire suit [canremove ? "loosens as the components relax" : "tightens around you as the components lock into place"].")) - playsound(src, 'sound/items/rped.ogg', 20, FALSE) + playsound(src, 'sound/items/rped.ogg', 30, FALSE) if (has_sealed_state) icon_state = canremove ? initial(icon_state) : "[initial(icon_state)]_sealed" if(dnaLock && !offline) @@ -411,15 +440,18 @@ piece.forceMove(src) var/previous_offline_status = offline + // Consume energy per tick while active. If you don't have a cell... Weird, but we'll deal with that shortly. + var/power_usage_this_tick = ((!offline && cell) ? cell_draw_rate : 0.0) + if(!istype(wearer) || loc != wearer || wearer.back != src || canremove || !cell || cell.charge <= 0) if(!cell || cell.charge <= 0) if(electrified > 0) electrified = 0 if(!offline) if(istype(wearer)) - playsound(src, 'sound/machines/rig/rig_shutdown.ogg', 20, FALSE) + playsound(src, 'sound/machines/rig/rig_shutdown.ogg', 35, FALSE) if(!canremove) - if (offline_slowdown < 3) + if(offline_slowdown < 3) to_chat(wearer, SPAN_DANGER("Your suit beeps stridently, and suddenly goes dead.")) else to_chat(wearer, SPAN_DANGER("Your suit beeps stridently, and suddenly you're wearing a leaden mass of metal and plastic composites instead of a powered suit.")) @@ -442,6 +474,16 @@ update_icon(TRUE) set_vision(!offline) + + if(cell && cell.charge > 0 && electrified > 0) + electrified-- + + if(crushing) + wearer.apply_damage(10) // Applies 10 brute damage to a random extremity each process + if(wearer.stat == DEAD) + crushing = FALSE + visible_message(SPAN_DANGER("A squelching sound comes from within the sealed hardsuit..")) // this denotes that the user inside has died. + if(offline) crushing = FALSE if(offline == 1) @@ -453,15 +495,6 @@ wearer?.update_equipment_speed_mods() return - if(crushing) - wearer.apply_damage(10) // Applies 10 brute damage to a random extremity each process - if(wearer.stat == DEAD) - crushing = FALSE - visible_message(SPAN_DANGER("A squelching sound comes from within the sealed hardsuit..")) // this denotes that the user inside has died. - - if(cell && cell.charge > 0 && electrified > 0) - electrified-- - if(malfunction_delay > 0) malfunction_delay-- else if(malfunctioning) @@ -469,12 +502,15 @@ malfunction() for(var/obj/item/rig_module/module in installed_modules) - cell.use(module.process()*10) + power_usage_this_tick += module.process() + + if(power_usage_this_tick) + cell.use(power_usage_this_tick) /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)) - return 0 + return FALSE var/fail_msg @@ -490,13 +526,15 @@ fail_msg = SPAN_WARNING("You are in no fit state to do that.") else if(!cell) fail_msg = SPAN_WARNING("There is no cell installed in the suit.") - else if(cost && cell.charge < cost * 10) //TODO: Cellrate? + else if(cost && cell?.charge < cost) //TODO: Cellrate? fail_msg = SPAN_WARNING("Not enough stored power.") if(fail_msg) - to_chat(user, "[fail_msg]") + // If a module has been passed to this proc, then the module will handle its own error messages. + if(!mod) + to_chat(user, "[fail_msg]") playsound(src, 'sound/items/rfd_empty.ogg', 20, FALSE) - return 0 + return FALSE // This is largely for cancelling stealth and whatever. if(mod && mod.disruptive) @@ -504,86 +542,10 @@ if(module.active && module.disruptable) module.deactivate() - cell.use(cost*10) - return 1 - -/obj/item/rig/ui_interact(mob/user, ui_key = "main", var/datum/nanoui/ui = null, var/force_open = 1, var/nano_state = GLOB.inventory_state) - if(!user) - return - - var/list/data = list() - - if(selected_module) - data["primarysystem"] = "[selected_module.interface_name]" - - if(src.loc != user) - data["ai"] = 1 - - data["seals"] = "[src.canremove]" - data["sealing"] = "[src.sealing]" - data["helmet"] = (helmet ? "[helmet.name]" : "None.") - data["gauntlets"] = (gloves ? "[gloves.name]" : "None.") - data["boots"] = (boots ? "[boots.name]" : "None.") - data[BP_CHEST] = (chest ? "[chest.name]" : "None.") - - data["charge"] = cell ? round(cell.charge,1) : 0 - data["maxcharge"] = cell ? cell.maxcharge : 0 - data["chargestatus"] = cell ? FLOOR((cell.charge/cell.maxcharge)*50, 1) : 0 - - data["emagged"] = subverted - data["coverlock"] = locked - data["interfacelock"] = interface_locked - data["aicontrol"] = control_overridden - data["aioverride"] = ai_override_enabled - data["securitycheck"] = security_check_enabled - data["malf"] = malfunction_delay - - - var/list/module_list = list() - var/i = 1 - for(var/obj/item/rig_module/module in installed_modules) - var/list/module_data = list( - "index" = i, - "name" = "[module.interface_name]", - "desc" = "[module.interface_desc]", - "can_use" = "[module.usable]", - "can_select" = "[module.selectable]", - "can_toggle" = "[module.toggleable]", - "is_active" = "[module.active]", - "engagecost" = module.use_power_cost*10, - "activecost" = module.active_power_cost*10, - "passivecost" = module.passive_power_cost*10, - "engagestring" = module.engage_string, - "activatestring" = module.activate_string, - "deactivatestring" = module.deactivate_string, - "damage" = module.damage - ) - - if(module.charges && module.charges.len) - - module_data["charges"] = list() - var/datum/rig_charge/selected = module.charges[module.charge_selected] - module_data["chargetype"] = selected ? "[selected.display_name]" : "none" - - for(var/chargetype in module.charges) - var/datum/rig_charge/charge = module.charges[chargetype] - module_data["charges"] += list(list("caption" = "[chargetype] ([charge.charges])", "index" = "[chargetype]")) - - module_list += list(module_data) - i++ - - if(module_list.len) - data["modules"] = module_list - - ui = SSnanoui.try_update_ui(user, src, ui_key, ui, data, force_open) - if (!ui) - ui = new(user, src, ui_key, ((src.loc != user) ? ai_interface_path : interface_path), interface_title, 480, 550, state = nano_state) - ui.set_initial_data(data) - ui.open() - ui.set_auto_update(1) + cell.use(cost) + return TRUE /obj/item/rig/update_icon(var/update_mob_icon) - //TODO: Maybe consider a cache for this (use mob_icon as blank canvas, use suit icon overlay). ClearOverlays() if(!mob_icon || update_mob_icon) @@ -594,7 +556,7 @@ species_icon = sprite_sheets[wearer.species.get_bodytype()] mob_icon = image("icon" = species_icon, "icon_state" = "[icon_state]") - if(installed_modules.len) + if(length(installed_modules)) for(var/obj/item/rig_module/module in installed_modules) if(module.suit_overlay) chest.AddOverlays(image("icon" = 'icons/mob/rig_modules.dmi', "icon_state" = "[module.suit_overlay]", "dir" = SOUTH)) @@ -607,6 +569,15 @@ wearer.update_inv_back() return +/obj/item/rig/get_mob_overlay(var/mob/living/carbon/human/H, var/mob_icon, var/mob_state, var/slot) + var/image/I = ..() + if(length(installed_modules)) + if((chest_type && (chest && wearer?.wear_suit == chest))) + for(var/obj/item/rig_module/module in installed_modules) + if(module.suit_overlay) + I.AddOverlays(image('icons/mob/rig_modules.dmi', null, module.suit_overlay)) + return I + /obj/item/rig/get_cell() if(cell) return cell @@ -621,80 +592,234 @@ return FALSE /obj/item/rig/proc/check_suit_access(var/mob/living/user) - if(!security_check_enabled || !locked) - return 1 + return TRUE if(is_integrated_rig_ai(user)) - return 1 + return TRUE if(ishuman(user)) var/mob/living/carbon/human/H = user if(malfunction_check(H)) - return 0 + return FALSE if(H.back != src) - return 0 + return FALSE else if(!src.allowed(H)) - to_chat(H, SPAN_DANGER("Unauthorized user. Access denied.")) - return 0 + sound_to(H, 'sound/machines/terminal/terminal_error.ogg') + balloon_alert(H, "access denied!") + return FALSE else if(!ai_override_enabled) to_chat(user, SPAN_DANGER("Synthetic access disabled. Please consult hardware provider.")) - return 0 + return FALSE - return 1 + return TRUE -//TODO: Fix Topic vulnerabilities for malfunction and AI override. -/obj/item/rig/Topic(href,href_list) - if(href_list["examine_armor"]) - var/list/armor_details = list() - for(var/armor_type in armor) - armor_details[armor_type] = armor[armor_type] - var/datum/tgui_module/armor_values/AV = new /datum/tgui_module/armor_values(usr, capitalize_first_letters(name), armor_details) - AV.ui_interact(usr) - if(href_list["examine_fluff"]) - examine(usr, show_extended = TRUE) - if(ismob(href)) - do_rig_thing(href, href_list) +/obj/item/rig/ui_interact(mob/user, datum/tgui/ui) + if(!user) return - do_rig_thing(usr, href_list) -/obj/item/rig/proc/do_rig_thing(mob/user, var/list/href_list) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "Hardsuit", src, 480, 550) + ui.open() + ui.set_autoupdate(TRUE) + +/obj/item/rig/ui_data(mob/user) + var/list/data = list() + + if(selected_module) + data["primarysystem"] = selected_module.interface_name + data["primarysystem_ref"] = REF(selected_module) + // Needs to be explicitly nulled when de-selected, otherwise the old data persists in the UI. + else + data["primarysystem"] = null + data["primarysystem_ref"] = null + + data["ai"] = (src.loc != user) + + data["seals"] = canremove + data["sealing"] = sealing + + data["helmet"] = helmet ? helmet.name : "None." + data["gauntlets"] = gloves ? gloves.name : "None." + data["boots"] = boots ? boots.name : "None." + data["chest"] = chest ? chest.name : "None." + + var/data_charge = cell ? round(cell.charge, 1) : 0 + var/data_maxcharge = cell ? cell.maxcharge : 1 + data["charge"] = data_charge + data["maxcharge"] = data_maxcharge + data["chargedisplay"] = "[power_joules_readable(data_charge)] / [power_joules_readable(data_maxcharge)]" + data["chargestatus"] = cell ? FLOOR((cell.charge / cell.maxcharge) * 50, 1) : 0 + + data["emagged"] = subverted + data["coverlock"] = locked + data["interfacelock"] = interface_locked + data["aicontrol"] = control_overridden + data["aioverride"] = ai_override_enabled + data["id_lock"] = security_check_enabled + data["malf"] = malfunction_delay + + var/list/module_list = list() + var/i = 1 + for(var/obj/item/rig_module/module in installed_modules) + var/list/module_data = list( + "index" = i, + "module_name" = module.interface_name, + "desc" = module.interface_desc, + "module_type" = module.module_type, + "module_active" = module.active, + "module_selected" = selected_module == module ? TRUE : FALSE, + "engagecost" = module.use_power_cost, + "activecost" = module.active_power_cost, + "passivecost" = module.passive_power_cost, + "engagestring" = module.engage_string, + "activatestring" = module.activate_string, + "deactivatestring" = module.deactivate_string, + "damage" = module.damage, + "ref" = REF(module), + "configuration_data" = module.get_configuration(user) + ) + + if(module.charges && module.charges.len) + module_data["charges"] = list() + + var/datum/rig_charge/selected = module.charges[module.charge_selected] + module_data["charge_selected"] = "[module.charge_selected]" + module_data["chargetype"] = selected ? selected.display_name : "none" + + for(var/chargetype in module.charges) + var/datum/rig_charge/charge = module.charges[chargetype] + module_data["charges"] += list(list( + "chargetype" = "[chargetype]", + "charges" = "[charge.charges]", + "index" = "[chargetype]" + )) + + module_list += list(module_data) + i++ + + if(module_list.len) + data["modules"] = module_list + + return data + +/obj/item/rig/ui_act(action, params) + . = ..() + if(.) + return TRUE + + var/mob/user = usr + if(!user) + return TRUE + // Just handles the UI's examine buttons. Doesn't need to check for access. + switch(action) + // Returns the Armor Values UI. + if("examine_armor") + var/list/armor_details = list() + for(var/armor_type in armor) + armor_details[armor_type] = armor[armor_type] + var/datum/tgui_module/armor_values/AV = new /datum/tgui_module/armor_values(user, capitalize_first_letters(name), armor_details) + AV.ui_interact(user) + return TRUE + if("examine_fluff") + examine(user, show_extended = TRUE) + return TRUE + if(!check_suit_access(user)) - return 0 + return FALSE - if(href_list["toggle_piece"]) - if(ishuman(user) && (user.stat || user.stunned || user.lying)) - return FALSE - toggle_piece(href_list["toggle_piece"], user) - else if(href_list["toggle_seals"]) - toggle_seals(user) - else if(href_list["interact_module"]) + // Suit access-gated actions. + switch(action) + // Toggles a 'clothing' piece on or off. + if("toggle_piece") + var/piece = "[params["piece"]]" + if(!piece) + return TRUE - var/module_index = text2num(href_list["interact_module"]) + if(ishuman(user) && (user.stat || user.stunned || user.lying)) + return TRUE - if(module_index > 0 && module_index <= installed_modules.len) - var/obj/item/rig_module/module = installed_modules[module_index] - switch(href_list["module_mode"]) - if("activate") - module.activate(user) - if("deactivate") - module.deactivate(user) - if("engage") - module.do_engage(null, user) - if("select") - selected_module = module - if("select_charge_type") - module.charge_selected = href_list["charge_type"] - else if(href_list["toggle_ai_control"]) - ai_override_enabled = !ai_override_enabled - notify_ai("Synthetic suit control has been [ai_override_enabled ? "enabled" : "disabled"].") - else if(href_list["toggle_suit_lock"]) - locked = !locked + toggle_piece(piece, user) + + // Deploys all pieces. + if("toggle_seals") + toggle_seals(user) + + // Module interactions. + if("interact_module") + var/module_index = text2num(params["index"]) + if(!module_index) + return TRUE + + if(module_index > 0 && module_index <= installed_modules.len) + var/obj/item/rig_module/module = installed_modules[module_index] + var/mode = "[params["mode"]]" + switch(mode) + if("activate") + module.activate(user) + sound_to(usr, module?.sound_activate) + if("deactivate") + module.deactivate(user) + sound_to(usr, module?.sound_deactivate) + if("engage") + module.do_engage(null, user) + if("select") + if(selected_module != module) + // Clear the currently selected module's overlay. + if(selected_module) + selected_module.suit_overlay = null + selected_module = module + // Set the new selected module's suit overlay. + if(selected_module.suit_overlay_active) + selected_module.suit_overlay = selected_module.suit_overlay_active + sound_to(usr, module?.sound_activate) + else + sound_to(usr, selected_module?.sound_deactivate) + selected_module.suit_overlay = null + selected_module = null + update_icon(TRUE) + playsound(src.loc, 'sound/items/rfd_dispense.ogg', 25, FALSE) + if("select_charge_type") + module.charge_selected = "[params["charge_type"]]" + sound_to(usr, module.sound_config) + + if("toggle_ai_control") + ai_override_enabled = !ai_override_enabled + notify_ai("Synthetic suit control has been [ai_override_enabled ? "enabled" : "disabled"].") + sound_to(usr, 'sound/machines/terminal/terminal_prompt_deny.ogg') + + // Cover panel locked or unlocked (by access ID). + if("toggle_suit_lock") + locked = !locked + sound_to(usr, 'sound/machines/terminal/terminal_prompt_deny.ogg') + // Toggles whether access locks are enabled. + if("toggle_id_lock") + // Only those people who have access rights in the first place should be able to toggle access on or off. + if(!src.allowed(user)) + sound_to(user, 'sound/machines/terminal/terminal_error.ogg') + balloon_alert(user, "access denied!") + else + security_check_enabled = !security_check_enabled + sound_to(usr, 'sound/machines/terminal/terminal_prompt_deny.ogg') + + if("configure") + var/obj/item/rig_module/module = locate(params["ref"]) in installed_modules + if(!module) + return + module.configure_edit(params["key"], params["value"], user) + sound_to(usr, module.sound_config) user.set_machine(src) src.add_fingerprint(user) - return FALSE + + return TRUE + +// Makes sure the UI is only available if you're wearing it, unless you're an AI. +/obj/item/rig/ui_state(mob/user) + if(!issilicon(user)) + return GLOB.inventory_state /obj/item/rig/proc/notify_ai(var/message) for(var/obj/item/rig_module/ai_container/module in installed_modules) @@ -712,7 +837,7 @@ M.visible_message(SPAN_NOTICE("[M] struggles into \the [src]."), SPAN_NOTICE("You struggle into \the [src].")) wearer = M wearer.wearing_rig = src - update_icon() + update_icon(TRUE) return TRUE return TRUE @@ -753,25 +878,43 @@ check_slot = wearer.wear_suit if(use_obj) + // Handle retracting the piece, if possible. if(check_slot == use_obj && deploy_mode != ONLY_DEPLOY) - 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') + return - if(use_obj) - holder = use_obj.loc - if(istype(holder)) - if(use_obj && 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', 20, FALSE) - use_obj.canremove = 1 - holder.drop_from_inventory(use_obj,get_turf(src)) //TODO: TEST THIS CODE! - use_obj.dropped(wearer) - use_obj.canremove = 0 - use_obj.forceMove(src) + 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) + use_obj.canremove = 1 + holder.drop_from_inventory(use_obj,get_turf(src)) //TODO: TEST THIS CODE! + use_obj.dropped(wearer) + use_obj.canremove = 0 + use_obj.forceMove(src) + // Handle deploying the piece, if possible. else if (deploy_mode != ONLY_RETRACT) if(check_slot && check_slot == use_obj) - return + return TRUE + if(!do_after(wearer, 8)) + if(wearer) + to_chat(wearer, SPAN_WARNING("You must remain still while the suit deploys its parts.")) + 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) + if(!toggle_piece("boots", initiator, ONLY_DEPLOY)) + to_chat(initiator, SPAN_DANGER("You are unable to deploy \the [piece] as the boots were unable to also deploy!")) + playsound(src, 'sound/items/rfd_empty.ogg', 20, FALSE) + return FALSE use_obj.forceMove(wearer) if(src.color) use_obj.color = src.color @@ -780,14 +923,18 @@ if(check_slot) to_chat(initiator, SPAN_DANGER("You are unable to deploy \the [piece] as \the [check_slot] [check_slot.gender == PLURAL ? "are" : "is"] in the way.")) playsound(src, 'sound/items/rfd_empty.ogg', 20, FALSE) - return + return FALSE else to_chat(wearer, SPAN_NOTICE("Your [use_obj.name] [use_obj.gender == PLURAL ? "deploy" : "deploys"] swiftly.")) - playsound(src, 'sound/machines/rig/rig_deploy.ogg', 20, FALSE) + playsound(src, 'sound/machines/rig/rig_deploy.ogg', 30, FALSE) if(piece == "helmet" && helmet) helmet.update_light(wearer) + update_icon(TRUE) + + return TRUE + /obj/item/rig/proc/deploy(mob/M,var/sealed) var/mob/living/carbon/human/H = M @@ -864,16 +1011,18 @@ //possibly damage some modules take_hit((100/severity), "electrical pulse", 1) +/// Handles running electrocute_mob() on the passed user. If the user is wearing the suit, it picks between head, chest, and groin for contact_zone. +/// If not wearing the suit (i.e. tampering with the wires), contact_zone is assumed to be the passed user's hands. /obj/item/rig/proc/shock(mob/user) var/touchy = pick(BP_CHEST,BP_HEAD,BP_GROIN) if(!wearer) touchy = "hand" - if (electrocute_mob(user, cell, src, contact_zone = touchy)) //electrocute_mob() handles removing charge from the cell, no need to do that here. + if(electrocute_mob(user, cell, src, contact_zone = touchy)) //electrocute_mob() handles removing charge from the cell, no need to do that here. spark_system.queue() if(user.stunned) - return 1 - return 0 + return TRUE + return FALSE /obj/item/rig/proc/take_hit(damage, source, is_emp=0) @@ -956,7 +1105,7 @@ to_chat(user, SPAN_WARNING("Your host module is unable to interface with the suit.")) return 0 - if(offline || !cell || !cell.charge || locked_down) + if(offline || !cell || !cell.charge) if(user) to_chat(user, SPAN_WARNING("Your host rig is unpowered and unresponsive.")) return 0 if(!wearer || wearer.back != src) @@ -1100,7 +1249,3 @@ air_supply.remove_air(air_supply.air_contents.total_moles) else air_supply = null - -#undef ONLY_DEPLOY -#undef ONLY_RETRACT -#undef SEAL_DELAY diff --git a/code/modules/clothing/spacesuits/rig/rig_attackby.dm b/code/modules/clothing/spacesuits/rig/rig_attackby.dm index f22c997ff3c..14c511e0f2b 100644 --- a/code/modules/clothing/spacesuits/rig/rig_attackby.dm +++ b/code/modules/clothing/spacesuits/rig/rig_attackby.dm @@ -83,20 +83,20 @@ var/mob/living/carbon/human/H = src.loc if(H.back == src) to_chat(user, SPAN_DANGER("You can't install a hardsuit module while the suit is being worn.")) - return 1 + return TRUE if(!installed_modules) installed_modules = list() if(!(module.category & allowed_module_types)) var/mod_name = get_module_category(module.category) to_chat(user, SPAN_WARNING("\The [src] does not support [mod_name] modules!")) - return 0 + return FALSE if(installed_modules.len) for(var/obj/item/rig_module/installed_mod in installed_modules) if(!installed_mod.redundant && istype(installed_mod, attacking_item)) to_chat(user, SPAN_NOTICE("The hardsuit already has a module of that class installed.")) - return 1 + return TRUE var/obj/item/rig_module/mod = attacking_item to_chat(user, SPAN_NOTICE("You begin installing \the [mod] into \the [src].")) @@ -113,7 +113,7 @@ mod.forceMove(src) mod.installed(src) update_icon() - return 1 + return TRUE else if(!cell && istype(attacking_item,/obj/item/cell)) @@ -222,6 +222,10 @@ return ..() +/obj/item/rig/attack_self(var/mob/user) + if(wearer && wearer.back == src) + ui_interact(usr) + /obj/item/rig/emag_act(var/remaining_charges, var/mob/user) if(!subverted) req_access.Cut() @@ -229,7 +233,7 @@ locked = FALSE subverted = 1 to_chat(user, SPAN_DANGER("You short out the access protocol for the suit.")) - return 1 + return TRUE /obj/item/rig/proc/get_module_category(var/category) switch(category) diff --git a/code/modules/clothing/spacesuits/rig/rig_verbs.dm b/code/modules/clothing/spacesuits/rig/rig_verbs.dm index 8231e2e2ee2..42e6d4303e0 100644 --- a/code/modules/clothing/spacesuits/rig/rig_verbs.dm +++ b/code/modules/clothing/spacesuits/rig/rig_verbs.dm @@ -2,7 +2,7 @@ /obj/item/rig/verb/hardsuit_interface() set name = "Open Hardsuit Interface" set desc = "Open the hardsuit system interface." - set category = "Hardsuit" + set category = "Hardsuit.Core Functions" set src = usr.contents if(wearer && wearer.back == src) @@ -11,7 +11,7 @@ /obj/item/rig/verb/toggle_vision() set name = "Toggle Visor" set desc = "Turns your rig visor off or on." - set category = "Hardsuit" + set category = "Hardsuit.Module Control" set src = usr.contents if(!istype(wearer) || !wearer.back == src) @@ -40,7 +40,7 @@ /obj/item/rig/proc/toggle_helmet() set name = "Toggle Helmet" set desc = "Deploys or retracts your helmet." - set category = "Hardsuit" + set category = "Hardsuit.Part Toggles" set src = usr.contents if(!istype(wearer) || !wearer.back == src) @@ -58,7 +58,7 @@ /obj/item/rig/proc/toggle_chest() set name = "Toggle Chestpiece" set desc = "Deploys or retracts your chestpiece." - set category = "Hardsuit" + set category = "Hardsuit.Part Toggles" set src = usr.contents if(!istype(wearer) || !wearer.back == src) @@ -76,7 +76,7 @@ /obj/item/rig/proc/toggle_gauntlets() set name = "Toggle Gauntlets" set desc = "Deploys or retracts your gauntlets." - set category = "Hardsuit" + set category = "Hardsuit.Part Toggles" set src = usr.contents if(!istype(wearer) || !wearer.back == src) @@ -94,7 +94,7 @@ /obj/item/rig/proc/toggle_boots() set name = "Toggle Boots" set desc = "Deploys or retracts your boots." - set category = "Hardsuit" + set category = "Hardsuit.Part Toggles" set src = usr.contents if(!istype(wearer) || !wearer.back == src) @@ -110,9 +110,9 @@ toggle_piece("boots",wearer) /obj/item/rig/verb/deploy_suit() - set name = "Deploy Hardsuit" + set name = "Deploy All Hardsuit Parts" set desc = "Deploys helmet, gloves and boots." - set category = "Hardsuit" + set category = "Hardsuit.Core Functions" set src = usr.contents if(!istype(wearer) || !wearer.back == src) @@ -131,9 +131,9 @@ deploy(wearer) /obj/item/rig/verb/toggle_seals_verb() - set name = "Toggle Hardsuit" - set desc = "Activates or deactivates your rig." - set category = "Hardsuit" + set name = "Engage/Disengage Hardsuit" + set desc = "Activates or deactivates your hardsuit." + set category = "Hardsuit.Core Functions" set src = usr.contents if(!istype(wearer) || !wearer.back == src) @@ -151,7 +151,7 @@ /obj/item/rig/verb/switch_vision_mode() set name = "Switch Vision Mode" set desc = "Switches between available vision modes." - set category = "Hardsuit" + set category = "Hardsuit.Module Control" set src = usr.contents if(malfunction_check(usr)) @@ -180,7 +180,7 @@ /obj/item/rig/verb/alter_voice() set name = "Configure Voice Synthesiser" set desc = "Toggles or configures your voice synthesizer." - set category = "Hardsuit" + set category = "Hardsuit.Module Control" set src = usr.contents if(malfunction_check(usr)) @@ -203,7 +203,7 @@ /obj/item/rig/verb/select_module() set name = "Select Module" set desc = "Selects a module as your primary system." - set category = "Hardsuit" + set category = "Hardsuit.Module Control" set src = usr.contents if(malfunction_check(usr)) @@ -222,7 +222,7 @@ var/list/selectable = list() for(var/obj/item/rig_module/module in installed_modules) - if(module.selectable) + if(module.module_type == MODULETYPE_USABLE_ACTIVE) selectable |= module var/obj/item/rig_module/module = tgui_input_list(usr, "Which module do you wish to select?", "Select Module", selectable) @@ -238,7 +238,7 @@ /obj/item/rig/verb/toggle_module() set name = "Toggle Module" set desc = "Toggle a system module." - set category = "Hardsuit" + set category = "Hardsuit.Module Control" set src = usr.contents if(malfunction_check(usr)) @@ -257,7 +257,7 @@ var/list/selectable = list() for(var/obj/item/rig_module/module in installed_modules) - if(module.toggleable) + if(module.module_type == MODULETYPE_TOGGLE) selectable |= module var/obj/item/rig_module/module = tgui_input_list(usr, "Which module do you wish to toggle?", "Toggle Module", selectable) @@ -275,7 +275,7 @@ /obj/item/rig/verb/engage_module() set name = "Engage Module" set desc = "Engages a system module." - set category = "Hardsuit" + set category = "Hardsuit.Module Control" set src = usr.contents if(malfunction_check(usr)) @@ -294,7 +294,7 @@ var/list/selectable = list() for(var/obj/item/rig_module/module in installed_modules) - if(module.usable) + if(module.module_type == MODULETYPE_USABLE) selectable |= module var/obj/item/rig_module/module = tgui_input_list(usr, "Which module do you wish to engage?", "Engage Module", selectable) diff --git a/code/modules/clothing/spacesuits/rig/suits/light.dm b/code/modules/clothing/spacesuits/rig/suits/light.dm index e2ee2f0a7f0..c0e55fe7fc3 100644 --- a/code/modules/clothing/spacesuits/rig/suits/light.dm +++ b/code/modules/clothing/spacesuits/rig/suits/light.dm @@ -51,7 +51,7 @@ req_access = list(ACCESS_SYNDICATE) - airtight = 0 + airtight = FALSE seal_delay = 5 //not being vaccum-proof has an upside I guess helm_type = /obj/item/clothing/head/lightrig/hacker @@ -215,7 +215,7 @@ RAD = ARMOR_RAD_MINOR ) slowdown = 0 - airtight = 0 + airtight = FALSE seal_delay = 5 helm_type = /obj/item/clothing/head/lightrig/offworlder chest_type = /obj/item/clothing/suit/lightrig/offworlder @@ -262,7 +262,7 @@ ) slowdown = -0.3 offline_slowdown = 0 - airtight = 1 + airtight = TRUE offline_vision_restriction = TINT_HEAVY siemens_coefficient = 0.2 icon_supported_species_tags = null diff --git a/code/modules/mob/living/carbon/human/human_helpers.dm b/code/modules/mob/living/carbon/human/human_helpers.dm index be976911410..74d88cb403a 100644 --- a/code/modules/mob/living/carbon/human/human_helpers.dm +++ b/code/modules/mob/living/carbon/human/human_helpers.dm @@ -93,8 +93,8 @@ G.process_hud(src) /mob/living/carbon/human/proc/process_rig(var/obj/item/rig/O) - if(O.visor && O.visor.active && O.visor.vision && O.visor.vision.glasses && (!O.helmet || (head && O.helmet == head))) - process_glasses(O.visor.vision.glasses) + if(O.visor && O.visor.active && O.visor.vision_mode && O.visor.vision_mode.glasses && (!O.helmet || (head && O.helmet == head))) + process_glasses(O.visor.vision_mode.glasses) /// Applies organ/markings prefs to this mob. /mob/living/carbon/human/proc/sync_organ_prefs_to_mob(datum/preferences/prefs, apply_prosthetics = TRUE, apply_markings = TRUE) diff --git a/code/modules/power/power.dm b/code/modules/power/power.dm index 20973556675..6ee53eb27dd 100644 --- a/code/modules/power/power.dm +++ b/code/modules/power/power.dm @@ -24,10 +24,13 @@ /////////////////////////////// // General procedures ////////////////////////////// -// Proc: power_wattage_readable() -// Parameters: 1 (amount - Power in Watts to be converted to W, kW or MW) -// Description: Helper proc that converts reading in Watts to kW or MW (returns string version of amount parameter) -/obj/machinery/proc/power_wattage_readable(var/amount = 0) +/** + * Proc: power_wattage_readable() + * Parameters: 1 (amount - Power in Watts to be converted to W, kW or MW) + * Description: Helper proc that converts reading in Watts to kW or MW (returns string version of amount parameter) + * NOTE: This needs to be replaced by a common SIUnits proc once energy is normalized (watts, volts, joules, etc.) + */ +/proc/power_wattage_readable(var/amount = 0) var/units = "" // 10kW and less - Watts if(amount < 10000) @@ -45,10 +48,35 @@ else return "[amount] [units]" -/obj/machinery/power/proc/disconnect_terminal() // machines without a terminal will just return, no harm no fowl. +/** + * Proc: power_joules_readable() + * Parameters: 1 (amount - Power in Joules to be converted to J, kJ or MJ) + * Description: Helper proc that converts reading in Joules to kJ or MJ (returns string version of amount parameter) + * NOTE: This needs to be replaced by a common SIUnits proc once energy is normalized (watts, volts, joules, etc.) + */ +/proc/power_joules_readable(var/amount = 0) + var/units = "" + // 10kW and less - Watts + if(amount < 10000) + units = "W" + // 10MW and less - KiloWatts + else if(amount < 10000000) + units = "kJ" + amount = (round(amount/100) / 10) + // More than 10MW - MegaWatts + else + units = "MJ" + amount = (round(amount/10000) / 100) + if (units == "J") + return "[amount] J" + else + return "[amount] [units]" + +/// Machines without a terminal will just return, no harm no fowl. +/obj/machinery/power/proc/disconnect_terminal() return -// connect the machine to a powernet if a node cable is present on the turf +/// Connect the machine to a powernet if a node cable is present on the turf /obj/machinery/power/proc/connect_to_network() var/turf/T = src.loc if(!T || !istype(T)) @@ -61,15 +89,17 @@ C.powernet.add_machine(src) return 1 -// remove and disconnect the machine from its current powernet +/// Remove and disconnect the machine from its current powernet. /obj/machinery/power/proc/disconnect_from_network() if(!powernet) return 0 powernet.remove_machine(src) return 1 -// attach a wire to a power machine - leads from the turf you are standing on -//almost never called, overwritten by all power machines but terminal and generator +/** + * Attach a wire to a power machine - leads from the turf you are standing on + * almost never called, overwritten by all power machines but terminal and generator + */ /obj/machinery/power/attackby(obj/item/attacking_item, mob/user) if(attacking_item.tool_behaviour == TOOL_CABLECOIL) @@ -94,8 +124,8 @@ // Powernet handling helpers ////////////////////////////////////////// -//returns all the cables WITHOUT a powernet in neighbors turfs, -//pointing towards the turf the machine is located at +/// Returns all the cables WITHOUT a powernet in neighbors turfs, +/// pointing towards the turf the machine is located at. /obj/machinery/power/proc/get_connections() . = list() @@ -113,8 +143,8 @@ . += C return . -//returns all the cables in neighbors turfs, -//pointing towards the turf the machine is located at +/// Returns all the cables in neighbors turfs, +/// pointing towards the turf the machine is located at /obj/machinery/power/proc/get_marked_connections() . = list() @@ -131,7 +161,7 @@ . += C return . -//returns all the NODES (O-X) cables WITHOUT a powernet in the turf the machine is located at +/// Returns all the NODES (O-X) cables WITHOUT a powernet in the turf the machine is located at /obj/machinery/power/proc/get_indirect_connections() . = list() for(var/obj/structure/cable/C in loc) @@ -144,10 +174,11 @@ // GLOBAL PROCS for powernets handling ////////////////////////////////////////// - -// returns a list of all power-related objects (nodes, cable, junctions) in turf, -// excluding source, that match the direction d -// if unmarked==1, only return those with no powernet +/** + * Returns a list of all power-related objects (nodes, cable, junctions) in turf, + * excluding source, that match the direction d. + * If unmarked==1, only return those with no powernet + */ /proc/power_list(var/turf/T, var/source, var/d, var/unmarked=0, var/cable_only = 0) . = list() var/fdir = (!d)? 0 : turn(d, 180) // the opposite direction to d (or 0 if d==0) @@ -183,7 +214,7 @@ . += C return . -//remove the old powernet and replace it with a new one throughout the network. +/// Remove the old powernet and replace it with a new one throughout the network. /proc/propagate_network(var/obj/O, var/datum/powernet/PN) //world.log << "propagating new network" var/list/worklist = list() @@ -216,7 +247,7 @@ PM.disconnect_from_network() //... so disconnect if already on a powernet -//Merge two powernets, the bigger (in cable length term) absorbing the other +/// Merge two powernets, the bigger (in cable length term) absorbing the other /proc/merge_powernets(var/datum/powernet/net1, var/datum/powernet/net2) if(!net1 || !net2) //if one of the powernet doesn't exist, return return @@ -280,10 +311,10 @@ if (apc.terminal) PN = apc.terminal.powernet else if (!power_source) - return 0 + return FALSE else log_admin("ERROR: /proc/electrocute_mob([victim], [power_source], [source]): wrong power_source") - return 0 + return FALSE //Triggers powernet warning, but only for 5 ticks (if applicable) //If following checks determine user is protected we won't alarm for long. if(PN) diff --git a/code/modules/projectiles/guns/energy/mining.dm b/code/modules/projectiles/guns/energy/mining.dm index 97271caf98b..87d70740098 100644 --- a/code/modules/projectiles/guns/energy/mining.dm +++ b/code/modules/projectiles/guns/energy/mining.dm @@ -69,13 +69,14 @@ /obj/projectile/beam/plasmacutter name = "plasma arc" icon_state = "omnilaser" - damage = 20 + damage = 32 damage_type = DAMAGE_BURN check_armor = LASER - range = 5 + range = 3 pass_flags = PASSTABLE|PASSRAILING - var/mineral_passes = 2 // amount of mineral turfs it passes through before ending + /// Number of mineral turfs it passes through before ending. + var/mineral_passes = 2 muzzle_type = /obj/effect/projectile/muzzle/plasma_cutter tracer_type = /obj/effect/projectile/tracer/plasma_cutter diff --git a/html/changelogs/Bat-HardsuitRefactor.yml b/html/changelogs/Bat-HardsuitRefactor.yml new file mode 100644 index 00000000000..c42b6935f8a --- /dev/null +++ b/html/changelogs/Bat-HardsuitRefactor.yml @@ -0,0 +1,58 @@ +################################ +# Example Changelog File +# +# Note: This file, and files beginning with ".", and files that don't end in ".yml" will not be read. If you change this file, you will look really dumb. +# +# Your changelog will be merged with a master changelog. (New stuff added only, and only on the date entry for the day it was merged.) +# When it is, any changes listed below will disappear. +# +# Valid Prefixes: +# bugfix +# - (fixes bugs) +# wip +# - (work in progress) +# qol +# - (quality of life) +# soundadd +# - (adds a sound) +# sounddel +# - (removes a sound) +# rscadd +# - (adds a feature) +# rscdel +# - (removes a feature) +# imageadd +# - (adds an image or sprite) +# imagedel +# - (removes an image or sprite) +# spellcheck +# - (fixes spelling or grammar) +# experiment +# - (experimental change) +# balance +# - (balance changes) +# code_imp +# - (misc internal code change) +# refactor +# - (refactors code) +# config +# - (makes a change to the config files) +# admin +# - (makes changes to administrator tools) +# server +# - (miscellaneous changes to server) +################################# + +author: Batrachophrenoboocosmomachia +delete-after: True +changes: + - refactor: "Migrates Hardsuit NanoUI to TGUI." + - refactor: "Updates Hardsuit module type definitions, configuration data, many misc other functions for the purpose of the UI refactor (general utility and readability improvements)." + - balance: "Hardsuits now passively consume a small amount of energy while online, even when retracted." + - balance: "Hardsuit boots can no longer be retracted without also retracting the chest piece first." + - balance: "Mounted hardsuit storage module max space increased from 9 to 14." + - balance: "Plasma cutter (standalone and mounted) damage increased and range decreased by 50% each." + - soundadd: "Adds several new sounds for hardsuit use (attribution located w/ files)." + - code_imp: "Makes power_wattage_readable() a global proc and adds power_joules_readable()." + - code_imp: "Lots of misc DMdoc updates." + - bugfix: "Synthetic Charging Stations now charge hardsuit power cells as intended." diff --git a/icons/mob/rig_modules.dmi b/icons/mob/rig_modules.dmi index 3d18b5435ce..f921263c754 100644 Binary files a/icons/mob/rig_modules.dmi and b/icons/mob/rig_modules.dmi differ diff --git a/sound/items/reagent_containers/liquid/attribution.txt b/sound/items/reagent_containers/liquid/attribution.txt new file mode 100644 index 00000000000..d04c4d98e91 --- /dev/null +++ b/sound/items/reagent_containers/liquid/attribution.txt @@ -0,0 +1,2 @@ +plastic_bottle_liquid_slosh: +liquid in bottle shaking by mrrap4food -- https://freesound.org/s/470606/ -- License: Creative Commons 0 \ No newline at end of file diff --git a/sound/items/reagent_containers/liquid/plastic_bottle_liquid_slosh1.ogg b/sound/items/reagent_containers/liquid/plastic_bottle_liquid_slosh1.ogg new file mode 100644 index 00000000000..df5a6f6d161 Binary files /dev/null and b/sound/items/reagent_containers/liquid/plastic_bottle_liquid_slosh1.ogg differ diff --git a/sound/items/reagent_containers/liquid/plastic_bottle_liquid_slosh2.ogg b/sound/items/reagent_containers/liquid/plastic_bottle_liquid_slosh2.ogg new file mode 100644 index 00000000000..d5260c7d527 Binary files /dev/null and b/sound/items/reagent_containers/liquid/plastic_bottle_liquid_slosh2.ogg differ diff --git a/tgui/packages/tgui/interfaces/Hardsuit.tsx b/tgui/packages/tgui/interfaces/Hardsuit.tsx new file mode 100644 index 00000000000..a0a9d5570c1 --- /dev/null +++ b/tgui/packages/tgui/interfaces/Hardsuit.tsx @@ -0,0 +1,673 @@ +import { useBackend } from '../backend'; +import { + Box, + Button, + Collapsible, + Dropdown, + LabeledList, + NoticeBox, + NumberInput, + ProgressBar, + Section, + Stack, + Table, +} from '../components'; +import { Window } from '../layouts'; +import { BooleanLike } from '../../common/react'; + +type RigCharge = { + chargetype: string; + charges: number; + index: string; +}; + +type RigModule = { + index: number; + module_name: string; + desc: string; + + module_type: number; + module_toggleable: BooleanLike; + module_active: BooleanLike; + module_selected: BooleanLike; + + engagecost: number; + activecost: number; + passivecost: number; + + engagestring: string; + activatestring: string; + deactivatestring: string; + + damage: number; + + chargetype?: string; + charges?: RigCharge[]; + ref: string; + configuration_data?: ModuleConfig; +}; + +type ModuleConfigEntry = { + key: string; + display_name: string; + type: 'number' | 'bool' | 'color' | 'list' | 'button' | 'pin'; + value: any; + values?: any[]; +}; + +type ModuleConfig = Record; + +type Data = { + primarysystem?: string; + primarysystem_ref?: string; + + ai?: BooleanLike; + + seals: BooleanLike; + sealing: BooleanLike; + + helmet: string; + gauntlets: string; + boots: string; + chest: string; + + charge: number; + maxcharge: number; + chargedisplay: string; + chargestatus: number; + time_to_drain: string; + emagged: BooleanLike; + + coverlock: BooleanLike; + interfacelock: BooleanLike; + + aicontrol: BooleanLike; + aioverride: BooleanLike; + + id_lock: BooleanLike; + + securitycheck: BooleanLike; + malf: number; + + modules?: RigModule[]; +}; + +const pill = (text: string, color?: string) => ( + + {text} + +); + +const SuitStatusSection = (props, context) => { + const { act, data } = useBackend(context); + const interfaceOffline = !!data.interfacelock || (data.malf ?? 0) > 0; + const aiOverriddenForWearer = !!data.aicontrol && !data.ai; + + const chargeFrac = + data.maxcharge > 0 + ? Math.max(0, Math.min(1, data.charge / data.maxcharge)) + : 0; + + const suitStatus = (() => { + if (data.sealing) return pill('PROCESSING', 'average'); + if (data.seals) return pill('INACTIVE', 'bad'); + return pill('ACTIVE', 'good'); + })(); + + return ( +
+ + + + + } + > + {interfaceOffline ? ( + ERROR: INTERFACE OFFLINE + ) : aiOverriddenForWearer ? ( + CONTROL OVERRIDDEN BY AI + ) : null} + + + + {data.chargedisplay} + + + {data.time_to_drain} + + + {suitStatus} + + + {' '} + + + + + + + + + + + +
+ ); +}; + +const HardwareSection = (props, context) => { + const { act, data } = useBackend(context); + const pieceDisabled = !!data.sealing; + + return ( +
+ + + + + + } + > + + act('toggle_piece', { piece: 'helmet' })} + > + Toggle + + } + > + + {data.helmet} + + + + act('toggle_piece', { piece: 'gauntlets' })} + > + Toggle + + } + > + + {data.gauntlets} + + + + act('toggle_piece', { piece: 'boots' })} + > + Toggle + + } + > + + {data.boots} + + + + act('toggle_piece', { piece: 'chest' })} + > + Toggle + + } + > + + {data.chest} + + + +
+ ); +}; + +const ModulesSection = (props, context) => { + const { act, data } = useBackend(context); + const systemsOffline = !!data.seals || !!data.sealing; + + if (systemsOffline) { + return ( +
+ HARDSUIT SYSTEMS OFFLINE +
+ ); + } + + const modules = data.modules ?? []; + + const hasBoolConfigEntry = (configuration_data: any): boolean => { + if (!configuration_data) return false; + + if (Array.isArray(configuration_data)) { + return ( + configuration_data.some((e) => e?.type === 'bool') || + configuration_data.some( + (e) => + Array.isArray(e?.entries) && + e.entries.some((x) => x?.type === 'bool'), + ) + ); + } + + if (typeof configuration_data === 'object') { + const values = Object.values(configuration_data); + return values.some((v: any) => hasBoolConfigEntry(v)); + } + + return false; + }; + + return ( +
+ {!modules.length ? ( + No Modules Detected + ) : ( + + + + + Name + + + + ))} + + )} + + + + { + + } + + + + + {m.passivecost} + + + {m.activecost} + + + {m.engagecost} + + + ); + })} +
+ )} +
+ ); +}; + +const ConfigureScreen = (props, context) => { + const { configuration_data, module_ref } = props; + + const keys = Object.keys(configuration_data || {}); + if (!keys.length) { + return null; + } + + return ( + + + {keys.map((k) => { + const entry = configuration_data[k]; + return ( + + ); + })} + + + ); +}; + +const ConfigureDataEntry = (props, context) => { + const { type } = props; + const configureEntryTypes = { + number: , + bool: , + color: , + list: , + button: , + pin: , + }; + + return ( + + {configureEntryTypes[type]} + + ); +}; + +const ConfigureNumberEntry = (props, context) => { + const { act } = useBackend(context); + const { entryKey, value, module_ref } = props; + return ( + + act('configure', { + key: entryKey, + value: value, + ref: module_ref, + }) + } + /> + ); +}; + +const ConfigureBoolEntry = (props, context) => { + const { act } = useBackend(context); + const { entryKey, value, module_ref } = props; + + return ( + + act('configure', { + key: entryKey, + value: value, + ref: module_ref, + }) + } + /> + ); +}; + +const ConfigureColorEntry = (props, context) => { + const { act } = useBackend(context); + const { entryKey, value, module_ref } = props; + return ( + <> + + ); +}; + +export const Hardsuit = (props, context) => { + const { data } = useBackend(context); + const interfaceBreak = !!data.interfacelock || (data.malf ?? 0) > 0; + + return ( + + + + + + + + + + + + + + + + + + + + + ); +};