diff --git a/code/__DEFINES/wires.dm b/code/__DEFINES/wires.dm
index 2b4c528abc2..633dcc360c2 100644
--- a/code/__DEFINES/wires.dm
+++ b/code/__DEFINES/wires.dm
@@ -64,6 +64,12 @@
#define WIRE_ZAP1 "High Voltage Circuit 1"
#define WIRE_ZAP2 "High Voltage Circuit 2"
#define WIRE_OVERCLOCK "Overclock"
+#define WIRE_VALVE "Valve"
+#define WIRE_SHIELDING "Shielding"
+#define WIRE_REGULATOR_MIN "Minimize Regulator Pressure"
+#define WIRE_REGULATOR_MAX "Maximize Regulator Pressure"
+#define WIRE_TANK_EJECT "Eject Tank"
+#define WIRE_REACTION_SUPPRESSION "Reaction Suppression"
// Wire states for the AI
#define AI_WIRE_NORMAL 0
diff --git a/code/datums/wires/canister.dm b/code/datums/wires/canister.dm
new file mode 100644
index 00000000000..c692ff22872
--- /dev/null
+++ b/code/datums/wires/canister.dm
@@ -0,0 +1,33 @@
+/datum/wires/canister
+ holder_type = /obj/machinery/portable_atmospherics/canister
+ proper_name = "Canister"
+
+/datum/wires/canister/New(atom/holder)
+ wires = list(WIRE_VALVE, WIRE_SHIELDING, WIRE_REGULATOR_MIN, WIRE_REGULATOR_MAX, WIRE_TANK_EJECT, WIRE_REACTION_SUPPRESSION)
+ ..()
+
+/datum/wires/canister/on_pulse(wire)
+ var/obj/machinery/portable_atmospherics/canister/the_canister = holder
+ if(!the_canister.internal_cell)
+ return
+ switch(wire)
+ if(WIRE_VALVE)
+ the_canister.toggle_valve(usr, wire_pulsed = TRUE)
+ if(WIRE_SHIELDING)
+ the_canister.toggle_shielding(usr, wire_pulsed = TRUE)
+ if(WIRE_TANK_EJECT)
+ the_canister.eject_tank(usr, wire_pulsed = TRUE)
+ if(WIRE_REGULATOR_MIN)
+ the_canister.release_pressure = CAN_MIN_RELEASE_PRESSURE
+ the_canister.investigate_log("was set to [the_canister.release_pressure] kPa by [key_name(usr)] via wire pulse.", INVESTIGATE_ATMOS)
+ if(WIRE_REGULATOR_MAX)
+ the_canister.release_pressure = CAN_MAX_RELEASE_PRESSURE
+ the_canister.investigate_log("was set to [the_canister.release_pressure] kPa by [key_name(usr)] via wire pulse.", INVESTIGATE_ATMOS)
+ if(WIRE_REACTION_SUPPRESSION)
+ the_canister.toggle_reaction_suppression(usr, wire_pulsed = TRUE)
+
+/datum/wires/canister/can_reveal_wires(mob/user)
+ if(HAS_TRAIT(user, TRAIT_KNOW_ENGI_WIRES))
+ return TRUE
+
+ return ..()
diff --git a/code/modules/atmospherics/machinery/portable/canister.dm b/code/modules/atmospherics/machinery/portable/canister.dm
index 7479b92c420..bef8eb7300d 100644
--- a/code/modules/atmospherics/machinery/portable/canister.dm
+++ b/code/modules/atmospherics/machinery/portable/canister.dm
@@ -36,10 +36,14 @@
var/shielding_powered = FALSE
///The powercell used to enable shielding
var/obj/item/stock_parts/cell/internal_cell
- ///Is the cell hatch opened
- var/cell_container_opened = FALSE
///used while processing to update appearance only when its pressure state changes
var/current_pressure_state
+ /// An assembly attached to the canister
+ var/obj/item/assembly_holder/rig = null
+ /// The person who attached an assembly to this canister, usually for ignition purposes in tandem with valve wire pulse
+ var/mob/last_rigger = ""
+ //overlay of attached assemblies
+ var/mutable_appearance/assemblies_overlay
/datum/armor/portable_atmospherics_canister
melee = 50
@@ -56,6 +60,8 @@
if(mapload)
internal_cell = new /obj/item/stock_parts/cell/high(src)
+ set_wires(new /datum/wires/canister(src))
+
if(existing_mixture)
air_contents.copy_from(existing_mixture)
else
@@ -73,6 +79,18 @@
AddElement(/datum/element/atmos_sensitive, mapload)
AddElement(/datum/element/volatile_gas_storage)
AddComponent(/datum/component/gas_leaker, leak_rate=0.01)
+ register_context()
+
+/obj/machinery/portable_atmospherics/canister/Destroy()
+ QDEL_NULL(wires)
+ return ..()
+
+/obj/machinery/portable_atmospherics/canister/Exited(atom/movable/gone)
+ if(gone == rig)
+ rig = null
+ last_rigger = null
+ cut_overlays(assemblies_overlay)
+ return ..()
/obj/machinery/portable_atmospherics/canister/interact(mob/user)
. = ..()
@@ -81,6 +99,30 @@
playsound(src, 'sound/misc/compiler-failure.ogg', 50, TRUE)
return
+/obj/machinery/portable_atmospherics/canister/add_context(atom/source, list/context, obj/item/held_item, mob/user)
+ . = ..()
+ if(rig)
+ context[SCREENTIP_CONTEXT_RMB] = "Remove rigged device"
+ if(holding)
+ context[SCREENTIP_CONTEXT_ALT_LMB] = "Remove tank"
+ if(!held_item)
+ return CONTEXTUAL_SCREENTIP_SET
+ if(istype(held_item, /obj/item/assembly_holder))
+ context[SCREENTIP_CONTEXT_LMB] = "Rig"
+ if(istype(held_item, /obj/item/stock_parts/cell))
+ context[SCREENTIP_CONTEXT_LMB] = "Insert cell"
+ switch(held_item.tool_behaviour)
+ if(TOOL_SCREWDRIVER)
+ context[SCREENTIP_CONTEXT_LMB] = "[panel_open ? "Close" : "Open"] hatch"
+ if(TOOL_CROWBAR)
+ if(panel_open && internal_cell)
+ context[SCREENTIP_CONTEXT_LMB] = "Remove cell"
+ if(TOOL_WELDER)
+ context[SCREENTIP_CONTEXT_LMB] = "Repair"
+ context[SCREENTIP_CONTEXT_RMB] = "Dismantle"
+
+ return CONTEXTUAL_SCREENTIP_SET
+
/obj/machinery/portable_atmospherics/canister/examine(user)
. = ..()
. += span_notice("A sticker on its side says MAX SAFE PRESSURE: [siunit_pressure(initial(pressure_limit), 0)]; MAX SAFE TEMPERATURE: [siunit(temp_limit, "K", 0)].")
@@ -88,8 +130,13 @@
. += span_notice("The internal cell has [internal_cell.percent()]% of its total charge.")
else
. += span_notice("Warning, no cell installed, use a screwdriver to open the hatch and insert one.")
- if(cell_container_opened)
- . += span_notice("Cell hatch open, close it with a screwdriver.")
+ if(panel_open)
+ . += span_notice("Hatch open, close it with a screwdriver.")
+ if(get_dist(user, src) <= 2)
+ if(rig)
+ . += span_warning("There is some kind of device rigged to the canister!")
+ else
+ . += span_notice("It looks like you could rig a device to the canister.")
// Please keep the canister types sorted
// Basic canister per gas below here
@@ -286,7 +333,7 @@
. += mutable_appearance('icons/obj/pipes_n_cables/canisters.dmi', "shielding")
. += emissive_appearance('icons/obj/pipes_n_cables/canisters.dmi', "shielding", src)
- if(cell_container_opened)
+ if(panel_open)
. += mutable_appearance('icons/obj/pipes_n_cables/canisters.dmi', "cell_hatch")
///Function is used to actually set the overlays
@@ -352,30 +399,69 @@
/obj/machinery/portable_atmospherics/canister/attackby(obj/item/item, mob/user, params)
if(istype(item, /obj/item/stock_parts/cell))
var/obj/item/stock_parts/cell/active_cell = item
- if(!cell_container_opened)
- balloon_alert(user, "open the hatch first")
+ if(!panel_open)
+ balloon_alert(user, "open hatch first!")
return TRUE
if(!user.transferItemToLoc(active_cell, src))
return TRUE
if(internal_cell)
user.put_in_hands(internal_cell)
- balloon_alert(user, "you successfully replace the cell")
+ balloon_alert(user, "you replace the cell")
else
- balloon_alert(user, "you successfully install the cell")
+ balloon_alert(user, "you install the cell")
internal_cell = active_cell
return TRUE
+ if(is_wire_tool(item) && panel_open)
+ wires.interact(user)
+ return
+ if(istype(item, /obj/item/assembly_holder))
+ if(rig)
+ balloon_alert(user, "another device is in the way!")
+ return ..()
+ var/obj/item/assembly_holder/holder = item
+
+ user.balloon_alert_to_viewers("attaching rig...")
+ add_fingerprint(user)
+ if(!do_after(user, 2 SECONDS, target = src) || !user.transferItemToLoc(holder, src))
+ return
+ rig = holder
+ holder.master = src
+ holder.on_attach()
+ assemblies_overlay = holder
+ assemblies_overlay.layer = 3.3
+ assemblies_overlay.pixel_y -= 5
+ add_overlay(assemblies_overlay)
+ log_bomber(user, "attached [holder.name] to ", src)
+ last_rigger = user
+ user.balloon_alert_to_viewers("attached rig")
+ return
return ..()
+/obj/machinery/portable_atmospherics/canister/attack_hand_secondary(mob/user, list/modifiers)
+ if(!rig)
+ return
+ // mousetrap rigs only make sense if you can set them off, can't step on them
+ // If you see a mousetrap-rigged fuel tank, just leave it alone
+ rig.on_found()
+ if(QDELETED(src))
+ return
+ user.balloon_alert_to_viewers("detaching rig...")
+ if(!do_after(user, 2 SECONDS, target = src))
+ return
+ user.balloon_alert_to_viewers("detached rig")
+ user.log_message("detached [rig] from [src].", LOG_GAME)
+ if(!user.put_in_hands(rig))
+ rig.forceMove(get_turf(user))
+ return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN
+
/obj/machinery/portable_atmospherics/canister/screwdriver_act(mob/living/user, obj/item/screwdriver)
- screwdriver.play_tool_sound(src, 50)
- cell_container_opened = !cell_container_opened
- to_chat(user, span_notice("You [cell_container_opened ? "open" : "close"] the cell container hatch of [src]."))
- update_appearance()
- return ITEM_INTERACT_SUCCESS
+ if(default_deconstruction_screwdriver(user, icon_state, icon_state, screwdriver))
+ update_appearance()
+ return ITEM_INTERACT_SUCCESS
/obj/machinery/portable_atmospherics/canister/crowbar_act(mob/living/user, obj/item/tool)
- if(!cell_container_opened || !internal_cell)
+ if(!panel_open || !internal_cell)
return ITEM_INTERACT_BLOCKING
internal_cell.forceMove(drop_location())
@@ -606,64 +692,19 @@
investigate_log("was set to [release_pressure] kPa by [key_name(usr)].", INVESTIGATE_ATMOS)
if("valve")
- var/logmsg
- var/admin_msg
- var/danger = FALSE
- var/n = 0
- valve_open = !valve_open
- if(valve_open)
- SSair.start_processing_machine(src)
- logmsg = "Valve was opened by [key_name(usr)], starting a transfer into \the [holding || "air"].
"
- if(!holding)
- var/list/gaseslog = list() //list for logging all gases in canister
- for(var/id in air_contents.gases)
- var/gas = air_contents.gases[id]
- gaseslog[gas[GAS_META][META_GAS_NAME]] = gas[MOLES] //adds gases to gaseslog
- if(!gas[GAS_META][META_GAS_DANGER])
- continue
- if(gas[MOLES] > (gas[GAS_META][META_GAS_MOLES_VISIBLE] || MOLES_GAS_VISIBLE)) //if moles_visible is undefined, default to default visibility
- danger = TRUE //at least 1 danger gas
- logmsg = "[key_name(usr)] opened a canister that contains the following:"
- admin_msg = "[ADMIN_LOOKUPFLW(usr)] opened a canister that contains the following at [ADMIN_VERBOSEJMP(src)]:"
- for(var/name in gaseslog)
- n = n + 1
- logmsg += "\n[name]: [gaseslog[name]] moles."
- if(n <= 5) //the first five gases added
- admin_msg += "\n[name]: [gaseslog[name]] moles."
- if(n == 5 && length(gaseslog) > 5) //message added if more than 5 gases
- admin_msg += "\nToo many gases to log. Check investigate log."
- if(danger) //sent to admin's chat if contains dangerous gases
- message_admins(admin_msg)
- else
- logmsg = "valve was closed by [key_name(usr)], stopping the transfer into \the [holding || "air"].
"
- investigate_log(logmsg, INVESTIGATE_ATMOS)
- release_log += logmsg
+ toggle_valve(usr)
. = TRUE
if("eject")
- if(holding)
- if(valve_open)
- message_admins("[ADMIN_LOOKUPFLW(usr)] removed [holding] from [src] with valve still open at [ADMIN_VERBOSEJMP(src)] releasing contents into the [span_boldannounce("air")].")
- usr.investigate_log("removed the [holding], leaving the valve open and transferring into the [span_boldannounce("air")].", INVESTIGATE_ATMOS)
- replace_tank(usr, FALSE)
+ if(eject_tank(usr))
. = TRUE
if("shielding")
- shielding_powered = !shielding_powered
- SSair.start_processing_machine(src)
- message_admins("[ADMIN_LOOKUPFLW(usr)] turned [shielding_powered ? "on" : "off"] the [src] powered shielding.")
- usr.investigate_log("turned [shielding_powered ? "on" : "off"] the [src] powered shielding.")
- update_appearance()
+ toggle_shielding(usr)
. = TRUE
if("reaction_suppression")
- if(!nob_crystal_inserted)
- stack_trace("[usr] tried to toggle reaction suppression on a canister without a noblium crystal inside, possible href exploit attempt.")
- return
- suppress_reactions = !suppress_reactions
- SSair.start_processing_machine(src)
- message_admins("[ADMIN_LOOKUPFLW(usr)] turned [suppress_reactions ? "on" : "off"] the [src] reaction suppression.")
- usr.investigate_log("turned [suppress_reactions ? "on" : "off"] the [src] reaction suppression.")
+ toggle_reaction_suppression(usr)
. = TRUE
if("recolor")
@@ -682,6 +723,70 @@
update_appearance()
+
+/// Opens/closes the canister valve
+/obj/machinery/portable_atmospherics/canister/proc/toggle_valve(mob/user, wire_pulsed = FALSE)
+ var/logmsg
+ var/admin_msg
+ var/danger = FALSE
+ var/n = 0
+ valve_open = !valve_open
+ if(valve_open)
+ SSair.start_processing_machine(src)
+ logmsg = "Valve was opened by [key_name(user)] [wire_pulsed ? "via wire pulse" : ""], starting a transfer into \the [holding || "air"].
"
+ if(!holding)
+ var/list/gaseslog = list() //list for logging all gases in canister
+ for(var/id in air_contents.gases)
+ var/gas = air_contents.gases[id]
+ gaseslog[gas[GAS_META][META_GAS_NAME]] = gas[MOLES] //adds gases to gaseslog
+ if(!gas[GAS_META][META_GAS_DANGER])
+ continue
+ if(gas[MOLES] > (gas[GAS_META][META_GAS_MOLES_VISIBLE] || MOLES_GAS_VISIBLE)) //if moles_visible is undefined, default to default visibility
+ danger = TRUE //at least 1 danger gas
+ logmsg = "[key_name(user)] opened a canister [wire_pulsed ? "via wire pulse" : ""] that contains the following:"
+ admin_msg = "[ADMIN_LOOKUPFLW(user)] opened a canister [wire_pulsed ? "via wire pulse" : ""] that contains the following at [ADMIN_VERBOSEJMP(src)]:"
+ for(var/name in gaseslog)
+ n = n + 1
+ logmsg += "\n[name]: [gaseslog[name]] moles."
+ if(n <= 5) //the first five gases added
+ admin_msg += "\n[name]: [gaseslog[name]] moles."
+ if(n == 5 && length(gaseslog) > 5) //message added if more than 5 gases
+ admin_msg += "\nToo many gases to log. Check investigate log."
+ if(danger) //sent to admin's chat if contains dangerous gases
+ message_admins(admin_msg)
+ else
+ logmsg = "valve was closed by [key_name(user)] [wire_pulsed ? "via wire pulse" : ""], stopping the transfer into \the [holding || "air"].
"
+ investigate_log(logmsg, INVESTIGATE_ATMOS)
+ release_log += logmsg
+
+/// Turns canister shielding on or off
+/obj/machinery/portable_atmospherics/canister/proc/toggle_shielding(mob/user, wire_pulsed = FALSE)
+ shielding_powered = !shielding_powered
+ SSair.start_processing_machine(src)
+ message_admins("[ADMIN_LOOKUPFLW(user)] turned [shielding_powered ? "on" : "off"] [wire_pulsed ? "via wire pulse" : ""] the [src] powered shielding.")
+ user.investigate_log("turned [shielding_powered ? "on" : "off"] [wire_pulsed ? "via wire pulse" : ""] the [src] powered shielding.")
+ update_appearance()
+
+/// Ejects tank from canister, if any
+/obj/machinery/portable_atmospherics/canister/proc/eject_tank(mob/user, wire_pulsed = FALSE)
+ if(holding)
+ if(valve_open)
+ message_admins("[ADMIN_LOOKUPFLW(user)] removed [holding] from [src] with valve still open [wire_pulsed ? "via wire pulse" : ""] at [ADMIN_VERBOSEJMP(src)] releasing contents into the [span_boldannounce("air")].")
+ user.investigate_log("removed the [holding] [wire_pulsed ? "via wire pulse" : ""], leaving the valve open and transferring into the [span_boldannounce("air")].", INVESTIGATE_ATMOS)
+ replace_tank(user, FALSE)
+ return TRUE
+
+/// Turns hyper-noblium crystal reaction suppression in the canister on or off
+/obj/machinery/portable_atmospherics/canister/proc/toggle_reaction_suppression(mob/user, wire_pulsed = FALSE)
+ if(!nob_crystal_inserted)
+ if(!wire_pulsed)
+ stack_trace("[user] tried to toggle reaction suppression on a canister without a noblium crystal inside and without pulsing wires, possible href exploit attempt.")
+ return
+ suppress_reactions = !suppress_reactions
+ SSair.start_processing_machine(src)
+ message_admins("[ADMIN_LOOKUPFLW(user)] turned [suppress_reactions ? "on" : "off"] [wire_pulsed ? "via wire pulse" : ""] the [src] reaction suppression.")
+ user.investigate_log("turned [suppress_reactions ? "on" : "off"] [wire_pulsed ? "via wire pulse" : ""] the [src] reaction suppression.")
+
/obj/machinery/portable_atmospherics/canister/proc/recolor(datum/greyscale_modify_menu/menu)
set_greyscale(menu.split_colors, menu.config.type)
diff --git a/tgstation.dme b/tgstation.dme
index 9065b3f5208..57300cb18b0 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -1811,6 +1811,7 @@
#include "code\datums\wires\airlock.dm"
#include "code\datums\wires\apc.dm"
#include "code\datums\wires\autolathe.dm"
+#include "code\datums\wires\canister.dm"
#include "code\datums\wires\conveyor.dm"
#include "code\datums\wires\ecto_sniffer.dm"
#include "code\datums\wires\emitter.dm"