diff --git a/code/__defines/obj.dm b/code/__defines/obj.dm
index a2b6ac18afa..698102a16f7 100644
--- a/code/__defines/obj.dm
+++ b/code/__defines/obj.dm
@@ -1,5 +1,6 @@
#define OBJ_FLAG_ROTATABLE (1<<1) //Can this object be rotated?
#define OBJ_FLAG_ROTATABLE_ANCHORED (1<<2) // This object can be rotated even while anchored
+#define OBJ_FLAG_SIGNALER (1<<3) // Can this take a signaler? only in use for machinery
/obj/proc/iswrench()
return FALSE
diff --git a/code/_helpers/logging.dm b/code/_helpers/logging.dm
index 55a08f5e634..6bae7006b25 100644
--- a/code/_helpers/logging.dm
+++ b/code/_helpers/logging.dm
@@ -50,6 +50,14 @@
game_log("ADMIN", text)
send_gelf_log(short_message=text, long_message="[time_stamp()]: [text]",level=level,category="ADMIN",additional_data=list("_ckey"=html_encode(ckey),"_admin_key"=html_encode(admin_key),"_ckey_target"=html_encode(ckey_target)))
+/proc/log_signal(var/text)
+ if(length(signal_log) >= 100)
+ signal_log.Cut(1, 2)
+ signal_log.Add("|[time_stamp()]| [text]")
+ if(config.log_signaler)
+ game_log("SIGNALER", text)
+ send_gelf_log(short_message=text, long_message="[time_stamp()]: [text]",level=SEVERITY_NOTICE,category="SIGNALER")
+
/proc/log_debug(text,level = SEVERITY_DEBUG)
if (config.log_debug)
game_log("DEBUG", text)
diff --git a/code/controllers/configuration.dm b/code/controllers/configuration.dm
index 91714717f02..38029204416 100644
--- a/code/controllers/configuration.dm
+++ b/code/controllers/configuration.dm
@@ -8,6 +8,7 @@ var/list/gamemode_cache = list()
var/log_access = 0 // log login/logout
var/log_say = 0 // log client say
var/log_admin = 0 // log admin actions
+ var/log_signaler = 0 // log signaler actions
var/log_debug = 1 // log debug output
var/log_game = 0 // log game events
var/log_vote = 0 // log voting
@@ -405,6 +406,9 @@ var/list/gamemode_cache = list()
if ("log_admin")
config.log_admin = 1
+ if ("log_signaler")
+ config.log_signaler = 1
+
if ("log_debug")
config.log_debug = text2num(value)
diff --git a/code/game/machinery/atmoalter/canister.dm b/code/game/machinery/atmoalter/canister.dm
index a00b498ce03..0f53287bbac 100644
--- a/code/game/machinery/atmoalter/canister.dm
+++ b/code/game/machinery/atmoalter/canister.dm
@@ -11,6 +11,7 @@
density = 1
var/health = 100.0
flags = CONDUCT
+ obj_flags = OBJ_FLAG_SIGNALER
w_class = ITEMSIZE_HUGE
var/valve_open = 0
@@ -131,6 +132,9 @@
else
update_flag |= 32
+ if(signaler)
+ update_flag |= 64
+
if(update_flag == old_flag)
return 1
else
@@ -162,6 +166,9 @@ update_flag
cut_overlays()
set_light(FALSE)
+ if(signaler)
+ add_overlay("signaler")
+
if(update_flag & 1)
add_overlay("can-open")
if(update_flag & 2)
@@ -182,7 +189,6 @@ update_flag
var/mutable_appearance/indicator_overlay = mutable_appearance(icon, "can-o3", EFFECTS_ABOVE_LIGHTING_LAYER)
add_overlay(indicator_overlay)
set_light(1.4, 1, COLOR_BRIGHT_GREEN)
- return
/obj/machinery/portable_atmospherics/canister/fire_act(datum/gas_mixture/air, exposed_temperature, exposed_volume)
if(exposed_temperature > temperature_resistance)
@@ -191,21 +197,25 @@ update_flag
/obj/machinery/portable_atmospherics/canister/proc/healthcheck()
if(destroyed)
- return 1
+ return TRUE
if (src.health <= 10)
var/atom/location = src.loc
location.assume_air(air_contents)
- src.destroyed = 1
+ destroyed = TRUE
+ obj_flags &= ~OBJ_FLAG_SIGNALER
playsound(src.loc, 'sound/effects/spray.ogg', 10, 1, -3)
- src.density = 0
- update_icon()
+ density = FALSE
if (src.holding)
src.holding.forceMove(src.loc)
src.holding = null
+ detach_signaler()
+
+ update_icon()
+
return 1
else
return 1
@@ -285,12 +295,17 @@ update_flag
valve_open = !valve_open
/obj/machinery/portable_atmospherics/canister/attackby(var/obj/item/W as obj, var/mob/user as mob)
- if(!W.iswrench() && !istype(W, /obj/item/tank) && !istype(W, /obj/item/device/analyzer) && !istype(W, /obj/item/modular_computer))
- visible_message("\The [user] hits \the [src] with \a [W]!")
+ if(!W.iswrench() && !istype(W, /obj/item/tank) && !istype(W, /obj/item/device/analyzer) && !istype(W, /obj/item/modular_computer) && !issignaler(W) && !(W.iswirecutter() && signaler))
+ if(W.flags & NOBLUDGEON)
+ return
+ visible_message(SPAN_WARNING("\The [user] hits \the [src] with \the [W]!"), SPAN_NOTICE("You hit \the [src] with \the [W]."))
+ user.do_attack_animation(src, W)
+ playsound(src, 'sound/weapons/smash.ogg', 60, 1)
src.health -= W.force
if(!istype(W, /obj/item/forensics))
src.add_fingerprint(user)
healthcheck()
+ return
if(istype(user, /mob/living/silicon/robot) && istype(W, /obj/item/tank/jetpack))
var/datum/gas_mixture/thejetpack = W:air_contents
@@ -307,6 +322,7 @@ update_flag
..()
+ update_icon()
SSnanoui.update_uis(src) // Update all NanoUIs attached to src
/obj/machinery/portable_atmospherics/canister/attack_ai(var/mob/user as mob)
@@ -415,6 +431,11 @@ update_flag
return 1
+/obj/machinery/portable_atmospherics/canister/do_signaler()
+ valve_open = !valve_open
+ if(valve_open)
+ log_open_userless("a signaler")
+
/obj/machinery/portable_atmospherics/canister/phoron/Initialize()
. = ..()
diff --git a/code/game/machinery/atmoalter/portable_atmospherics.dm b/code/game/machinery/atmoalter/portable_atmospherics.dm
index 286f07dfade..58357409a9c 100644
--- a/code/game/machinery/atmoalter/portable_atmospherics.dm
+++ b/code/game/machinery/atmoalter/portable_atmospherics.dm
@@ -133,8 +133,7 @@
A.analyze_gases(src, user)
return
- return
-
+ return ..()
/obj/machinery/portable_atmospherics/powered
@@ -195,3 +194,9 @@
log_admin("[user] ([user.ckey]) opened '[src.name]' containing [gases].", ckey=key_name(user))
message_admins("[key_name_admin(user)] opened '[src.name]' containing [gases]. (JMP)")
+
+/obj/machinery/portable_atmospherics/proc/log_open_userless(var/cause)
+ if(air_contents.gas.len == 0)
+ return
+
+ message_admins("'[src.name]' was opened[cause ? " by [cause]" : ""], containing [english_list(air_contents.gas)]. (JMP)")
\ No newline at end of file
diff --git a/code/game/machinery/machinery.dm b/code/game/machinery/machinery.dm
index 19161455e55..d454dcbe550 100644
--- a/code/game/machinery/machinery.dm
+++ b/code/game/machinery/machinery.dm
@@ -164,6 +164,11 @@ Class Procs:
return ..()
+/obj/machinery/examine(mob/user)
+ . = ..()
+ if(signaler && Adjacent(user))
+ to_chat(user, SPAN_WARNING("\The [src] has a hidden signaler attached to it."))
+
/obj/machinery/proc/machinery_process() //If you dont use process or power why are you here
if(!(use_power || idle_power_usage || active_power_usage))
return PROCESS_KILL
@@ -280,6 +285,44 @@ Class Procs:
return ..()
+/obj/machinery/attackby(obj/item/W, mob/user)
+ if(obj_flags & OBJ_FLAG_SIGNALER)
+ if(issignaler(W))
+ if(signaler)
+ to_chat(user, SPAN_WARNING("\The [src] already has a signaler attached."))
+ return
+ var/obj/item/device/assembly/signaler/S = W
+ user.drop_from_inventory(W, src)
+ signaler = S
+ S.machine = src
+ user.visible_message("[user] attaches \the [S] to \the [src].", SPAN_NOTICE("You attach \the [S] to \the [src]."), range = 3)
+ log_and_message_admins("has attached a signaler to \the [src].", user, get_turf(src))
+ return
+ else if(W.iswirecutter() && signaler)
+ user.visible_message("[user] removes \the [signaler] from \the [src].", SPAN_NOTICE("You remove \the [signaler] from \the [src]."), range = 3)
+ user.put_in_hands(detach_signaler())
+ return
+
+ return ..()
+
+/obj/machinery/proc/detach_signaler(var/turf/detach_turf)
+ if(!signaler)
+ return
+
+ if(!detach_turf)
+ detach_turf = get_turf(src)
+ if(!detach_turf)
+ log_debug("[src] tried to drop a signaler, but it had no turf ([src.x]-[src.y]-[src.z])")
+ return
+
+ var/obj/item/device/assembly/signaler/S = signaler
+
+ signaler.forceMove(detach_turf)
+ signaler.machine = null
+ signaler = null
+
+ return S
+
/obj/machinery/proc/RefreshParts()
/obj/machinery/proc/assign_uid()
diff --git a/code/global.dm b/code/global.dm
index ec92e970fed..a442f7ebfb3 100644
--- a/code/global.dm
+++ b/code/global.dm
@@ -40,6 +40,7 @@ var/host = null //only here until check @ code\modules\ghosttrap\trap.dm:112 is
var/list/jobMax = list()
var/list/bombers = list()
var/list/admin_log = list()
+var/list/signal_log = list()
var/list/lastsignalers = list() // Keeps last 100 signals here in format: "[src] used \ref[src] @ location [src.loc]: [freq]/[code]"
var/list/lawchanges = list() // Stores who uploaded laws to which silicon-based lifeform, and what the law was.
var/list/reg_dna = list()
diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm
index e6f82a4c239..c381f73e6cb 100644
--- a/code/modules/admin/admin_verbs.dm
+++ b/code/modules/admin/admin_verbs.dm
@@ -30,6 +30,7 @@ var/list/admin_verbs_admin = list(
/datum/admins/proc/access_news_network, /*allows access of newscasters*/
/client/proc/giveruntimelog, /*allows us to give access to runtime logs to somebody*/
/client/proc/getserverlog, /*allows us to fetch server logs (diary) for other days*/
+ /client/proc/view_signal_log, /*allows admins to check the log of signaler uses*/
/client/proc/jumptocoord, /*we ghost and jump to a coordinate*/
/client/proc/Getmob, /*teleports a mob to our location*/
/client/proc/Getkey, /*teleports a mob with a certain ckey to our location*/
diff --git a/code/modules/admin/verbs/getlogs.dm b/code/modules/admin/verbs/getlogs.dm
index 731fa95b71b..0e6af92039a 100644
--- a/code/modules/admin/verbs/getlogs.dm
+++ b/code/modules/admin/verbs/getlogs.dm
@@ -78,3 +78,16 @@
//Other log stuff put here for the sake of organisation
+
+/client/proc/view_signal_log()
+ set name = "View Signaler Log"
+ set desc = "Use this to view who sent signaler signals to things."
+ set category = "Admin"
+
+ var/text_signal_log = ""
+ for(var/log in signal_log)
+ text_signal_log += "[log]
"
+
+ var/datum/browser/signal_win = new(usr, "signallog", "Signal Log", 550, 500)
+ signal_win.set_content(text_signal_log)
+ signal_win.open()
\ No newline at end of file
diff --git a/code/modules/assembly/signaler.dm b/code/modules/assembly/signaler.dm
index 7e90e173a30..1f56ed070e8 100644
--- a/code/modules/assembly/signaler.dm
+++ b/code/modules/assembly/signaler.dm
@@ -67,12 +67,12 @@
if(href_list["send"])
spawn( 0 )
- signal()
+ signal(usr)
var/datum/vueui/ui = SSvueui.get_open_ui(usr, src)
ui.check_for_change()
-/obj/item/device/assembly/signaler/proc/signal()
+/obj/item/device/assembly/signaler/proc/signal(var/mob/user)
if(!radio_connection)
return
if(within_jamming_range(src))
@@ -81,6 +81,8 @@
signal.source = src
signal.encryption = code
signal.data["message"] = "ACTIVATE"
+ if(user)
+ signal.data["user"] = WEAKREF(user)
radio_connection.post_signal(src, signal)
return
@@ -111,6 +113,15 @@
pulse(TRUE)
if(machine)
+ var/found_user = FALSE
+ if(signal.data["user"])
+ var/datum/weakref/user_ref = signal.data["user"]
+ var/mob/user = user_ref.resolve()
+ if(user)
+ found_user = TRUE
+ log_signal("[key_name(user)] has sent a signal to [machine.name] ([machine.x]-[machine.y]-[machine.z])")
+ if(!found_user)
+ log_signal("A userless signaler has sent a signal to [machine.name] ([machine.x]-[machine.y]-[machine.z])")
machine.audible_message("[icon2html(machine, viewers(get_turf(machine)))] [capitalize_first_letters(src.name)] beeps, \"Beep beep!\"")
else if(!holder)
audible_message("[icon2html(src, viewers(get_turf(src)))] [capitalize_first_letters(src.name)] beeps, \"Beep beep!\"")
@@ -154,7 +165,7 @@
if(use_check_and_message(user))
return
to_chat(user, SPAN_NOTICE("You click \the [src]'s signal button."))
- signal()
+ signal(user)
/obj/item/device/assembly/signaler/proc/deadman_trigger(var/mob/user)
if(deadman) //If its not activated, there is no point in triggering it
diff --git a/code/modules/power/singularity/emitter.dm b/code/modules/power/singularity/emitter.dm
index d53ba8fc0cf..05307743f43 100644
--- a/code/modules/power/singularity/emitter.dm
+++ b/code/modules/power/singularity/emitter.dm
@@ -11,7 +11,7 @@
anchored = FALSE
density = TRUE
req_access = list(access_engine_equip)
- obj_flags = OBJ_FLAG_ROTATABLE
+ obj_flags = OBJ_FLAG_ROTATABLE | OBJ_FLAG_SIGNALER
var/id
use_power = 0 //uses powernet power, not APC power
@@ -47,8 +47,6 @@
to_chat(user, SPAN_WARNING("\The [src] is bolted and welded to the floor, and ready to fire."))
if(Adjacent(user))
to_chat(user, SPAN_NOTICE("The shot counter display reads: [shot_counter]"))
- if(signaler)
- to_chat(user, SPAN_WARNING("\The [src] has a hidden signaler attached to it."))
/obj/machinery/power/emitter/Destroy()
if(special_emitter)
@@ -163,21 +161,6 @@
shot_counter++
/obj/machinery/power/emitter/attackby(obj/item/W, mob/user)
- if(istype(W, /obj/item/device/assembly/signaler))
- var/obj/item/device/assembly/signaler/S = W
- user.drop_from_inventory(W, src)
- signaler = S
- S.machine = src
- user.visible_message(SPAN_WARNING("\The [user] attaches \the [S] to \the [src]."),
- SPAN_NOTICE("You attach \the [S] to \the [src]."))
- return
- if(W.iswirecutter() && signaler)
- signaler.forceMove(get_turf(user))
- signaler.machine = null
- user.visible_message(SPAN_WARNING("\The [user] removes \the [signaler] from \the [src]."),
- SPAN_NOTICE("You remove \the [signaler] to \the [src]."))
- signaler = null
- return
if(W.iswrench())
if(active)
to_chat(user, SPAN_WARNING("You cannot unbolt \the [src] while it's active."))
diff --git a/config/example/config.txt b/config/example/config.txt
index 2d0bf2de89e..47d71d6088c 100644
--- a/config/example/config.txt
+++ b/config/example/config.txt
@@ -54,6 +54,9 @@ LOG_SAY
## log admin actions
LOG_ADMIN
+## log signaler actions
+LOG_SIGNALER
+
## log client access (logon/logoff)
LOG_ACCESS
diff --git a/html/changelogs/geeves-remote_canisters.yml b/html/changelogs/geeves-remote_canisters.yml
new file mode 100644
index 00000000000..390e01bd9db
--- /dev/null
+++ b/html/changelogs/geeves-remote_canisters.yml
@@ -0,0 +1,7 @@
+author: Geeves
+
+delete-after: True
+
+changes:
+ - rscadd: "You can now attach signalers to canisters. When activated, the signaler will toggle the valve open or shut."
+ - tweak: "Attacking a canister now has a sound and an animation."
\ No newline at end of file
diff --git a/icons/obj/atmos.dmi b/icons/obj/atmos.dmi
index 5e8d5a28a07..7ddebb5e747 100644
Binary files a/icons/obj/atmos.dmi and b/icons/obj/atmos.dmi differ