diff --git a/baystation12.dme b/baystation12.dme
index 0d8b5e670f9..ca75948dfff 100644
--- a/baystation12.dme
+++ b/baystation12.dme
@@ -49,6 +49,7 @@
#include "code\_helpers\mobs.dm"
#include "code\_helpers\names.dm"
#include "code\_helpers\sanitize_values.dm"
+#include "code\_helpers\spawn_sync.dm"
#include "code\_helpers\text.dm"
#include "code\_helpers\time.dm"
#include "code\_helpers\turfs.dm"
@@ -141,6 +142,7 @@
#include "code\controllers\Processes\ticker.dm"
#include "code\controllers\Processes\turf.dm"
#include "code\controllers\Processes\vote.dm"
+#include "code\controllers\Processes\wireless.dm"
#include "code\controllers\ProcessScheduler\core\_stubs.dm"
#include "code\controllers\ProcessScheduler\core\process.dm"
#include "code\controllers\ProcessScheduler\core\processScheduler.dm"
@@ -1833,6 +1835,8 @@
#include "code\modules\virus2\helpers.dm"
#include "code\modules\virus2\isolator.dm"
#include "code\modules\virus2\items_devices.dm"
+#include "code\modules\wireless\devices.dm"
+#include "code\modules\wireless\interfaces.dm"
#include "code\modules\xgm\xgm_gas_data.dm"
#include "code\modules\xgm\xgm_gas_mixture.dm"
#include "code\ZAS\_docs.dm"
diff --git a/code/_helpers/spawn_sync.dm b/code/_helpers/spawn_sync.dm
new file mode 100644
index 00000000000..91631d86712
--- /dev/null
+++ b/code/_helpers/spawn_sync.dm
@@ -0,0 +1,86 @@
+//-------------------------------
+/*
+ Spawn sync helper
+
+ Helps syncronize spawn()ing multiple processes in loops.
+
+ Example for using this:
+
+ //Create new spawn_sync datum
+ var/datum/spawn_sync/sync = new()
+
+ for(var/obj/O in list)
+ //Call start_worker(), passing it first the object, then a string of the name of the proc you want called, then
+ // any and all arguments you want passed to the proc.
+ sync.start_worker(O, "do_something", arg1, arg2)
+
+ //Finally call wait_until_done()
+ sync.wait_until_done()
+
+ //Once all the workers have completed, or the failsafe has triggered, the code will continue. By default the
+ // failsafe is roughly 10 seconds (100 checks).
+*/
+//-------------------------------
+/datum/spawn_sync
+ var/count = 1
+ var/failsafe = 100 //how many checks before the failsafe triggers and the helper stops waiting
+
+//Opens a thread counter
+/datum/spawn_sync/proc/open()
+ count++
+
+//Closes a thread counter
+/datum/spawn_sync/proc/close()
+ count--
+
+//Finalizes the spawn sync by removing the original starting count
+/datum/spawn_sync/proc/finalize()
+ close()
+
+//Resets the counter if you want to utilize the same datum multiple times
+// Optional: pass the number of checks you want for the failsafe
+/datum/spawn_sync/proc/reset(var/safety = 100)
+ count = 1
+ failsafe = safety
+
+//Check if all threads have returned
+// Returns 0 if not all threads have completed
+// Returns 1 if all threads have completed
+/datum/spawn_sync/proc/check()
+ safety_check()
+ return count > 0 ? 1 : 0
+
+//Failsafe in case something breaks horribly
+/datum/spawn_sync/proc/safety_check()
+ failsafe--
+ if(failsafe < 1)
+ count = 0
+
+//Set failsafe check count in case you need more time for the workers to return
+/datum/spawn_sync/proc/set_failsafe(var/safety)
+ failsafe = safety
+
+/datum/spawn_sync/proc/start_worker()
+ //Extract the thread run proc and it's arguments from the variadic args list.
+ ASSERT(args.len > 0)
+ var/obj = args[1]
+ var/thread_proc = args[2]
+
+ //dispatch a new thread
+ open()
+ spawn()
+ //Utilise try/catch keywords here so the code continues even if an error occurs.
+ try
+ call(obj, thread_proc)(arglist(args.Copy(3)))
+ catch(var/exception/e)
+ error("[e] on [e.file]:[e.line]")
+ close()
+
+/datum/spawn_sync/proc/wait_until_done()
+ finalize()
+
+ //Create a while loop to check if the sync is complete yet, it will return once all the spawn threads have
+ // completed, or the failsafe has expired.
+ while(check())
+ //Add a sleep call to delay each check.
+ sleep(1)
diff --git a/code/controllers/Processes/wireless.dm b/code/controllers/Processes/wireless.dm
new file mode 100644
index 00000000000..5af6af5c3d3
--- /dev/null
+++ b/code/controllers/Processes/wireless.dm
@@ -0,0 +1,71 @@
+//-------------------------------
+/*
+ Wireless controller
+
+ Used for connecting devices to each other (i.e. machinery, doors, emitters, etc.)
+ Unlike the radio controller, the wireless controller does not pass communications between devices. Once the devices
+ have been connected they call each others procs directly, they do not use the wireless controller to communicate.
+
+ See code\modules\wireless\interfaces.dm for details of how to connect devices.
+*/
+//-------------------------------
+
+var/datum/controller/process/wireless/wirelessProcess
+
+/datum/controller/process/wireless
+ var/list/receiver_list
+ var/list/pending_connections
+ var/list/retry_connections
+ var/list/failed_connections
+
+/datum/controller/process/wireless/setup()
+ name = "wireless"
+ schedule_interval = 50
+ pending_connections = new()
+ retry_connections = new()
+ failed_connections = new()
+ receiver_list = new()
+ wirelessProcess = src
+
+/datum/controller/process/wireless/proc/add_device(var/datum/wifi/receiver/R)
+ if(receiver_list)
+ receiver_list |= R
+ else
+ receiver_list = new()
+ receiver_list |= R
+
+/datum/controller/process/wireless/proc/remove_device(var/datum/wifi/receiver/R)
+ if(receiver_list)
+ receiver_list -= R
+
+/datum/controller/process/wireless/proc/add_request(var/datum/connection_request/C)
+ if(pending_connections)
+ pending_connections += C
+ else
+ pending_connections = new()
+ pending_connections += C
+
+/datum/controller/process/wireless/doWork()
+ //process any connection requests waiting to be retried
+ if(retry_connections.len > 0)
+ //any that fail are moved into the failed connections list
+ process_queue(retry_connections, failed_connections)
+
+ //process any pending connection requests
+ if(pending_connections.len > 0)
+ //any that fail are moved to the retry queue
+ process_queue(pending_connections, retry_connections)
+
+/datum/controller/process/wireless/proc/process_queue(var/list/process_conections, var/list/unsuccesful_connections)
+ for(var/datum/connection_request/C in process_conections)
+ var/target_found = 0
+ for(var/datum/wifi/receiver/R in receiver_list)
+ if(R.id == C.id)
+ var/datum/wifi/sender/S = C.source
+ S.connect_device(R)
+ R.connect_device(S)
+ target_found = 1
+ process_conections -= C
+ if(!target_found)
+ unsuccesful_connections += C
+ SCHECK
diff --git a/code/controllers/verbs.dm b/code/controllers/verbs.dm
index c394318c286..f47070b6b20 100644
--- a/code/controllers/verbs.dm
+++ b/code/controllers/verbs.dm
@@ -25,7 +25,7 @@
usr.client.debug_variables(antag)
message_admins("Admin [key_name_admin(usr)] is debugging the [antag.role_text] template.")
-/client/proc/debug_controller(controller in list("Master","Ticker","Ticker Process","Air","Jobs","Sun","Radio","Supply","Shuttles","Emergency Shuttle","Configuration","pAI", "Cameras", "Transfer Controller", "Gas Data","Event","Plants","Alarm","Nano","Chemistry"))
+/client/proc/debug_controller(controller in list("Master","Ticker","Ticker Process","Air","Jobs","Sun","Radio","Supply","Shuttles","Emergency Shuttle","Configuration","pAI", "Cameras", "Transfer Controller", "Gas Data","Event","Plants","Alarm","Nano","Chemistry","Wireless"))
set category = "Debug"
set name = "Debug Controller"
set desc = "Debug the various periodic loop controllers for the game (be careful!)"
@@ -92,5 +92,8 @@
if("Chemistry")
debug_variables(chemistryProcess)
feedback_add_details("admin_verb", "DChem")
+ if("Wireless")
+ debug_variables(wirelessProcess)
+ feedback_add_details("admin_verb", "DWifi")
message_admins("Admin [key_name_admin(usr)] is debugging the [controller] controller.")
return
diff --git a/code/game/machinery/buttons.dm b/code/game/machinery/buttons.dm
index 70557a2e18b..a5475561a8b 100644
--- a/code/game/machinery/buttons.dm
+++ b/code/game/machinery/buttons.dm
@@ -5,14 +5,127 @@
desc = "A remote control switch for something."
var/id = null
var/active = 0
+ var/operating = 0
anchored = 1.0
use_power = 1
idle_power_usage = 2
active_power_usage = 4
+ var/_wifi_id
+ var/datum/wifi/sender/button/wifi_sender
+/obj/machinery/button/initialize()
+ ..()
+ update_icon()
+ if(_wifi_id)
+ wifi_sender = new(_wifi_id, src)
+
+/obj/machinery/button/Destroy()
+ qdel(wifi_sender)
+ wifi_sender = null
+ return..()
/obj/machinery/button/attack_ai(mob/user as mob)
- return src.attack_hand(user)
+ return attack_hand(user)
/obj/machinery/button/attackby(obj/item/weapon/W, mob/user as mob)
- return src.attack_hand(user)
\ No newline at end of file
+ return attack_hand(user)
+
+/obj/machinery/button/attack_hand(mob/living/user)
+ ..()
+ activate(user)
+
+/obj/machinery/button/proc/activate(mob/living/user)
+ if(operating || !istype(wifi_sender))
+ return
+
+ operating = 1
+ active = 1
+ use_power(5)
+ update_icon()
+ wifi_sender.activate(user)
+ sleep(10)
+ active = 0
+ update_icon()
+ operating = 0
+
+/obj/machinery/button/update_icon()
+ if(active)
+ icon_state = "launcheract"
+ else
+ icon_state = "launcherbtt"
+
+//alternate button with the same functionality, except has a lightswitch sprite instead
+/obj/machinery/button/alternate
+ icon = 'icons/obj/power.dmi'
+ icon_state = "light0"
+
+/obj/machinery/button/alternate/update_icon()
+ icon_state = "light[active]"
+
+//Toggle button with two states (on and off) and calls seperate procs for each state
+/obj/machinery/button/toggle/activate(mob/living/user)
+ if(operating || !istype(wifi_sender))
+ return
+
+ operating = 1
+ active = !active
+ use_power(5)
+ if(active)
+ wifi_sender.activate(user)
+ else
+ wifi_sender.deactivate(user)
+ update_icon()
+ operating = 0
+
+/obj/machinery/button/toggle/alternate
+ icon = 'icons/obj/power.dmi'
+ icon_state = "light0"
+
+/obj/machinery/button/toggle/alternate/update_icon()
+ icon_state = "light[active]"
+
+
+//-------------------------------
+// Mass Driver Button
+// Passes the activate call to a mass driver wifi sender
+//-------------------------------
+/obj/machinery/button/mass_driver
+ var/datum/wifi/sender/mass_driver/sender
+
+/obj/machinery/button/mass_driver/initialize()
+ ..()
+ sender = new(_wifi_id, src)
+
+/obj/machinery/button/mass_driver/activate(mob/living/user)
+ if(active || !istype(wifi_sender))
+ return
+ active = 1
+ use_power(5)
+ update_icon()
+ sender.cycle()
+ active = 0
+ update_icon()
+
+//-------------------------------
+// Door Button
+//-------------------------------
+/obj/machinery/button/toggle/door
+ var/datum/wifi/sender/door/sender
+
+/obj/machinery/button/toggle/door/initialize()
+ ..()
+ sender = new(_wifi_id, src)
+
+/obj/machinery/button/toggle/door/activate(mob/living/user)
+ if(operating || !istype(sender))
+ return
+
+ operating = 1
+ active = !active
+ use_power(5)
+ update_icon()
+ if(!active)
+ sender.open()
+ else
+ sender.close()
+ operating = 0
diff --git a/code/game/machinery/doors/airlock.dm b/code/game/machinery/doors/airlock.dm
index 25492ce3d5f..5fa7010f53e 100644
--- a/code/game/machinery/doors/airlock.dm
+++ b/code/game/machinery/doors/airlock.dm
@@ -34,6 +34,9 @@
var/open_sound_powered = 'sound/machines/airlock.ogg'
var/open_sound_unpowered = 'sound/machines/airlock_creaking.ogg'
+ var/_wifi_id
+ var/datum/wifi/receiver/button/door/wifi_receiver
+
/obj/machinery/door/airlock/attack_generic(var/mob/user, var/damage)
if(stat & (BROKEN|NOPOWER))
if(damage >= 10)
@@ -1037,9 +1040,15 @@ About the new airlock wires panel:
src.closeOther = A
break
+ //wireless connection
+ if(_wifi_id)
+ wifi_receiver = new(_wifi_id, src)
+
/obj/machinery/door/airlock/Destroy()
qdel(wires)
wires = null
+ qdel(wifi_receiver)
+ wifi_receiver = null
return ..()
// Most doors will never be deconstructed over the course of a round,
diff --git a/code/game/machinery/doors/blast_door.dm b/code/game/machinery/doors/blast_door.dm
index 8c960e7bdf4..145bb63ab0c 100644
--- a/code/game/machinery/doors/blast_door.dm
+++ b/code/game/machinery/doors/blast_door.dm
@@ -28,6 +28,19 @@
//turning this off prevents awkward zone geometry in places like medbay lobby, for example.
block_air_zones = 0
+ var/_wifi_id
+ var/datum/wifi/receiver/button/door/wifi_receiver
+
+/obj/machinery/door/blast/initialize()
+ ..()
+ if(_wifi_id)
+ wifi_receiver = new(_wifi_id, src)
+
+/obj/machinery/door/airlock/Destroy()
+ qdel(wifi_receiver)
+ wifi_receiver = null
+ return ..()
+
// Proc: Bumped()
// Parameters: 1 (AM - Atom that tried to walk through this object)
// Description: If we are open returns zero, otherwise returns result of parent function.
@@ -144,7 +157,7 @@
if(stat & BROKEN)
stat &= ~BROKEN
-
+
/obj/machinery/door/blast/CanPass(atom/movable/mover, turf/target, height=0, air_group=0)
if(air_group) return 1
return ..()
diff --git a/code/game/machinery/flasher.dm b/code/game/machinery/flasher.dm
index e6fb41193cb..bea0c443a74 100644
--- a/code/game/machinery/flasher.dm
+++ b/code/game/machinery/flasher.dm
@@ -15,6 +15,8 @@
use_power = 1
idle_power_usage = 2
flags = PROXMOVE
+ var/_wifi_id
+ var/datum/wifi/receiver/button/flasher/wifi_receiver
/obj/machinery/flasher/portable //Portable version of the flasher. Only flashes when anchored
name = "portable flasher"
@@ -25,6 +27,16 @@
base_state = "pflash"
density = 1
+/obj/machinery/flasher/initialize()
+ ..()
+ if(_wifi_id)
+ wifi_receiver = new(_wifi_id, src)
+
+/obj/machinery/flasher/Destroy()
+ qdel(wifi_receiver)
+ wifi_receiver = null
+ return ..()
+
/obj/machinery/flasher/power_change()
..()
if ( !(stat & NOPOWER) )
diff --git a/code/game/machinery/holosign.dm b/code/game/machinery/holosign.dm
index 63198710227..69f12726342 100644
--- a/code/game/machinery/holosign.dm
+++ b/code/game/machinery/holosign.dm
@@ -12,6 +12,18 @@
var/lit = 0
var/id = null
var/on_icon = "sign_on"
+ var/_wifi_id
+ var/datum/wifi/receiver/button/holosign/wifi_receiver
+
+/obj/machinery/holosign/initialize()
+ ..()
+ if(_wifi_id)
+ wifi_receiver = new(_wifi_id, src)
+
+/obj/machinery/holosign/Destroy()
+ qdel(wifi_receiver)
+ wifi_receiver = null
+ return ..()
/obj/machinery/holosign/proc/toggle()
if (stat & (BROKEN|NOPOWER))
diff --git a/code/game/machinery/igniter.dm b/code/game/machinery/igniter.dm
index 5fd791843d9..70527609282 100755
--- a/code/game/machinery/igniter.dm
+++ b/code/game/machinery/igniter.dm
@@ -4,11 +4,32 @@
icon = 'icons/obj/stationobjs.dmi'
icon_state = "igniter1"
var/id = null
- var/on = 1.0
- anchored = 1.0
+ var/on = 0
+ anchored = 1
use_power = 1
idle_power_usage = 2
active_power_usage = 4
+ var/_wifi_id
+ var/datum/wifi/receiver/button/igniter/wifi_receiver
+
+/obj/machinery/igniter/New()
+ ..()
+ update_icon()
+
+/obj/machinery/igniter/initialize()
+ ..()
+ update_icon()
+ if(_wifi_id)
+ wifi_receiver = new(_wifi_id, src)
+
+/obj/machinery/igniter/update_icon()
+ ..()
+ icon_state = "igniter[on]"
+
+/obj/machinery/igniter/Destroy()
+ qdel(wifi_receiver)
+ wifi_receiver = null
+ return ..()
/obj/machinery/igniter/attack_ai(mob/user as mob)
return src.attack_hand(user)
@@ -17,29 +38,25 @@
if(..())
return
add_fingerprint(user)
-
- use_power(50)
- src.on = !( src.on )
- src.icon_state = text("igniter[]", src.on)
+ ignite()
return
/obj/machinery/igniter/process() //ugh why is this even in process()?
- if (src.on && !(stat & NOPOWER) )
+ if (on && powered() )
var/turf/location = src.loc
if (isturf(location))
location.hotspot_expose(1000,500,1)
return 1
-/obj/machinery/igniter/New()
- ..()
- icon_state = "igniter[on]"
-
/obj/machinery/igniter/power_change()
..()
- if(!( stat & NOPOWER) )
- icon_state = "igniter[src.on]"
- else
- icon_state = "igniter0"
+ update_icon()
+
+/obj/machinery/igniter/proc/ignite()
+ use_power(50)
+ on = !on
+ update_icon()
+
// Wall mounted remote-control igniter.
@@ -56,50 +73,59 @@
use_power = 1
idle_power_usage = 2
active_power_usage = 4
+ var/_wifi_id
+ var/datum/wifi/receiver/button/sparker/wifi_receiver
-
-/obj/machinery/sparker/New()
+/obj/machinery/sparker/initialize()
..()
+ if(_wifi_id)
+ wifi_receiver = new(_wifi_id, src)
+
+/obj/machinery/sparker/Destroy()
+ qdel(wifi_receiver)
+ wifi_receiver = null
+ return ..()
+
+/obj/machinery/sparker/update_icon()
+ ..()
+ if(disable)
+ icon_state = "migniter-d"
+ else if(powered())
+ icon_state = "migniter"
+// src.sd_SetLuminosity(2)
+ else
+ icon_state = "migniter-p"
+// src.sd_SetLuminosity(0)
/obj/machinery/sparker/power_change()
..()
- if ( !(stat & NOPOWER) && disable == 0 )
-
- icon_state = "[base_state]"
-// src.sd_SetLuminosity(2)
- else
- icon_state = "[base_state]-p"
-// src.sd_SetLuminosity(0)
+ update_icon()
/obj/machinery/sparker/attackby(obj/item/weapon/W as obj, mob/user as mob)
if (istype(W, /obj/item/weapon/screwdriver))
add_fingerprint(user)
- src.disable = !src.disable
- if (src.disable)
+ disable = !disable
+ if(disable)
user.visible_message("[user] has disabled the [src]!", "You disable the connection to the [src].")
- icon_state = "[base_state]-d"
- if (!src.disable)
+ else if(!disable)
user.visible_message("[user] has reconnected the [src]!", "You fix the connection to the [src].")
- if(src.powered())
- icon_state = "[base_state]"
- else
- icon_state = "[base_state]-p"
+ update_icon()
/obj/machinery/sparker/attack_ai()
- if (src.anchored)
- return src.ignite()
+ if (anchored)
+ return ignite()
else
return
/obj/machinery/sparker/proc/ignite()
- if (!(powered()))
+ if (!powered())
return
- if ((src.disable) || (src.last_spark && world.time < src.last_spark + 50))
+ if (disable || (last_spark && world.time < last_spark + 50))
return
- flick("[base_state]-spark", src)
+ flick("migniter-spark", src)
var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread
s.set_up(2, 1, src)
s.start()
@@ -132,15 +158,13 @@
icon_state = "launcheract"
for(var/obj/machinery/sparker/M in machines)
- if (M.id == src.id)
+ if (M.id == id)
spawn( 0 )
M.ignite()
for(var/obj/machinery/igniter/M in machines)
- if(M.id == src.id)
- use_power(50)
- M.on = !( M.on )
- M.icon_state = text("igniter[]", M.on)
+ if(M.id == id)
+ M.ignite()
sleep(50)
diff --git a/code/game/machinery/mass_driver.dm b/code/game/machinery/mass_driver.dm
index abd98091760..7b2775d9547 100644
--- a/code/game/machinery/mass_driver.dm
+++ b/code/game/machinery/mass_driver.dm
@@ -14,7 +14,18 @@
var/code = 1.0
var/id = 1.0
var/drive_range = 50 //this is mostly irrelevant since current mass drivers throw into space, but you could make a lower-range mass driver for interstation transport or something I guess.
+ var/_wifi_id
+ var/datum/wifi/receiver/button/mass_driver/wifi_receiver
+/obj/machinery/mass_driver/initialize()
+ ..()
+ if(_wifi_id)
+ wifi_receiver = new(_wifi_id, src)
+
+/obj/machinery/mass_driver/Destroy()
+ qdel(wifi_receiver)
+ wifi_receiver = null
+ return ..()
/obj/machinery/mass_driver/proc/drive(amount)
if(stat & (BROKEN|NOPOWER))
diff --git a/code/game/objects/structures/morgue.dm b/code/game/objects/structures/morgue.dm
index 9f8eed2eb57..8a9e29a0da2 100644
--- a/code/game/objects/structures/morgue.dm
+++ b/code/game/objects/structures/morgue.dm
@@ -186,11 +186,21 @@
var/cremating = 0
var/id = 1
var/locked = 0
+ var/_wifi_id
+ var/datum/wifi/receiver/button/crematorium/wifi_receiver
+
+/obj/structure/crematorium/initialize()
+ ..()
+ if(_wifi_id)
+ wifi_receiver = new(_wifi_id, src)
/obj/structure/crematorium/Destroy()
if(connected)
qdel(connected)
connected = null
+ if(wifi_receiver)
+ qdel(wifi_receiver)
+ wifi_receiver = null
return ..()
/obj/structure/crematorium/proc/update()
diff --git a/code/modules/power/singularity/emitter.dm b/code/modules/power/singularity/emitter.dm
index 51093296b27..53668d80de7 100644
--- a/code/modules/power/singularity/emitter.dm
+++ b/code/modules/power/singularity/emitter.dm
@@ -24,6 +24,8 @@
var/state = 0
var/locked = 0
+ var/_wifi_id
+ var/datum/wifi/receiver/button/emitter/wifi_receiver
/obj/machinery/power/emitter/verb/rotate()
set name = "Rotate"
@@ -40,12 +42,16 @@
..()
if(state == 2 && anchored)
connect_to_network()
+ if(_wifi_id)
+ wifi_receiver = new(_wifi_id, src)
/obj/machinery/power/emitter/Destroy()
message_admins("Emitter deleted at ([x],[y],[z] - JMP)",0,1)
log_game("Emitter deleted at ([x],[y],[z])")
investigate_log("deleted at ([x],[y],[z])","singulo")
- ..()
+ qdel(wifi_receiver)
+ wifi_receiver = null
+ return ..()
/obj/machinery/power/emitter/update_icon()
if (active && powernet && avail(active_power_usage))
diff --git a/code/modules/wireless/devices.dm b/code/modules/wireless/devices.dm
new file mode 100644
index 00000000000..f3a56867a7c
--- /dev/null
+++ b/code/modules/wireless/devices.dm
@@ -0,0 +1,164 @@
+//-------------------------------
+// Buttons
+// Sender: intended to be used by buttons, when the button is pressed it will call activate() on all connected /button
+// receivers.
+// Receiver: does whatever the subtype does. deactivate() by default calls activate(), so you will have to override in
+// it in a subtype if you want it to do something.
+//-------------------------------
+/datum/wifi/sender/button/proc/activate(mob/living/user)
+ for(var/datum/wifi/receiver/button/B in connected_devices)
+ B.activate(user)
+
+/datum/wifi/sender/button/proc/deactivate(mob/living/user)
+ for(var/datum/wifi/receiver/button/B in connected_devices)
+ B.deactivate(user)
+
+/datum/wifi/receiver/button/proc/activate(mob/living/user)
+
+/datum/wifi/receiver/button/proc/deactivate(mob/living/user)
+ activate(user) //override this if you want deactivate to actually do something
+
+//-------------------------------
+// Doors
+// Sender: sends an open/close request to all connected /door receivers. Utilises spawn_sync to trigger all doors to
+// open at approximately the same time. Waits until all doors have finished opening before returning.
+// Receiver: will try to open/close the parent door when activate/deactivate is called.
+//-------------------------------
+/datum/wifi/sender/door/proc/open()
+ var/datum/spawn_sync/S = new()
+
+ for(var/datum/wifi/receiver/button/door/D in connected_devices)
+ S.start_worker(D, "activate")
+ S.wait_until_done()
+ return
+
+/datum/wifi/sender/door/proc/close()
+ var/datum/spawn_sync/S = new()
+
+ for(var/datum/wifi/receiver/button/door/D in connected_devices)
+ S.start_worker(D, "deactivate")
+ S.wait_until_done()
+ return
+
+/datum/wifi/receiver/button/door/activate()
+ var/obj/machinery/door/D = parent
+ if(istype(D) && D.can_open())
+ D.open()
+
+/datum/wifi/receiver/button/door/deactivate()
+ var/obj/machinery/door/D = parent
+ if(istype(D) && D.can_close())
+ D.close()
+
+//-------------------------------
+// Emitter
+// Activates/deactivates the parent emitter.
+//-------------------------------
+/datum/wifi/receiver/button/emitter/activate(mob/living/user)
+ ..()
+ var/obj/machinery/power/emitter/E = parent
+ if(istype(E) && !E.active)
+ E.activate(user) //if the emitter is not active, trigger the activate proc to toggle it
+
+/datum/wifi/receiver/button/emitter/deactivate(mob/living/user)
+ var/obj/machinery/power/emitter/E = parent
+ if(istype(E) && E.active)
+ E.activate(user) //if the emitter is active, trigger the activate proc to toggle it
+
+//-------------------------------
+// Crematorium
+// Triggers cremate() on the parent /crematorium.
+//-------------------------------
+/datum/wifi/receiver/button/crematorium/activate(mob/living/user)
+ ..()
+ var/obj/structure/crematorium/C = parent
+ if(istype(C))
+ C.cremate(user)
+
+//-------------------------------
+// Mounted Flash
+// Triggers flash() on the parent /flasher.
+//-------------------------------
+/datum/wifi/receiver/button/flasher/activate(mob/living/user)
+ ..()
+ var/obj/machinery/flasher/F = parent
+ if(istype(F))
+ F.flash()
+
+//-------------------------------
+// Holosign
+// Turns the parent /holosign on/off.
+//-------------------------------
+/datum/wifi/receiver/button/holosign/activate(mob/living/user)
+ ..()
+ var/obj/machinery/holosign/H = parent
+ if(istype(H) && !H.lit)
+ H.toggle()
+
+/datum/wifi/receiver/button/holosign/deactivate(mob/living/user)
+ var/obj/machinery/holosign/H = parent
+ if(istype(H) && H.lit)
+ H.toggle()
+
+//-------------------------------
+// Igniter
+// Turns the parent /igniter on/off.
+//-------------------------------
+/datum/wifi/receiver/button/igniter/activate(mob/living/user)
+ ..()
+ var/obj/machinery/igniter/I = parent
+ if(istype(I))
+ if(!I.on)
+ I.ignite()
+
+/datum/wifi/receiver/button/igniter/deactivate(mob/living/user)
+ if(istype(parent, /obj/machinery/igniter))
+ var/obj/machinery/igniter/I = parent
+ if(I.on)
+ I.ignite()
+
+//-------------------------------
+// Sparker
+// Triggers the parent /sparker to ignite().
+//-------------------------------
+/datum/wifi/receiver/button/sparker/activate(mob/living/user)
+ ..()
+ var/obj/machinery/sparker/S = parent
+ if(istype(S))
+ S.ignite()
+
+//-------------------------------
+// Mass Driver
+// Sender: carries out a sequence of first opening all connected doors, then activating all connected mass drivers,
+// then closes all connected doors. It will wait before continuing the sequence after opening/closing the doors.
+// Receiver: Triggers the parent mass dirver to activate.
+//-------------------------------
+/datum/wifi/sender/mass_driver/proc/cycle()
+ var/datum/spawn_sync/S = new()
+
+ //tell all doors to open
+ for(var/datum/wifi/receiver/button/door/D in connected_devices)
+ S.start_worker(D, "activate")
+ S.wait_until_done()
+
+ S.reset()
+ //tell all mass drivers to launch
+ for(var/datum/wifi/receiver/button/mass_driver/M in connected_devices)
+ spawn()
+ M.activate()
+
+ sleep(20)
+
+ //tell all doors to close
+ S.reset()
+ for(var/datum/wifi/receiver/button/door/D in connected_devices)
+ S.start_worker(D, "deactivate")
+ S.wait_until_done()
+
+ return
+
+/datum/wifi/receiver/button/mass_driver/activate(mob/living/user)
+ ..()
+ var/obj/machinery/mass_driver/M = parent
+ if(istype(M))
+ M.drive()
diff --git a/code/modules/wireless/interfaces.dm b/code/modules/wireless/interfaces.dm
new file mode 100644
index 00000000000..0d80b84b1fa
--- /dev/null
+++ b/code/modules/wireless/interfaces.dm
@@ -0,0 +1,110 @@
+//-------------------------------
+/*
+ Interfaces
+
+ These are the datums that an object needs to connect via the wireless controller. You will need a /wifi/receiver to
+ allow other devices to connect to your device and send it instructions. You will need a /wifi/sender to send signals
+ to other devices with wifi receivers. You can have multiple devices (senders and receivers) if you program your
+ device to handle them.
+
+ Each wifi interface has one "id". This identifies which devices can connect to each other. Multiple senders can
+ connect to multiple receivers as long as they have the same id.
+
+ Variants are found in devices.dm
+
+ To add a receiver to an object:
+ Add the following variables to the object:
+ var/_wifi_id << variable that can be configured on the map, this is passed to the receiver later
+ var/datum/wifi/receiver/subtype/wifi_receiver << the receiver (and subtype itself)
+
+ Add or modify the objects initialize() proc to include:
+ if(_wifi_id) << only creates a wifi receiver if an id is set
+ wifi_receiver = new(_wifi_id, src) << this needs to be in initialize() as New() is usually too
+ early, and the receiver will try to connect to the controller
+ before it is setup.
+
+ Add or modify the objects Destroy() proc to include:
+ qdel(wifi_receiver)
+ wifi_receiver = null
+
+ Senders are setup the same way, except with a var/datum/wifi/sender/subtype/wifi_sender variable instead of (or in
+ addition to) a /wifi/receiver variable.
+ You will however need to call the /wifi/senders code to pass commands onto any connected receivers.
+ Example:
+ /obj/machinery/button/attack_hand()
+ wifi_sender.activate()
+*/
+//-------------------------------
+
+
+//-------------------------------
+// Wifi
+//-------------------------------
+/datum/wifi
+ var/obj/parent
+ var/list/connected_devices
+ var/id
+
+/datum/wifi/New(var/new_id, var/obj/O)
+ connected_devices = new()
+ id = new_id
+ if(istype(O))
+ parent = O
+
+/datum/wifi/Destroy(var/wifi/device)
+ parent = null
+ for(var/datum/wifi/D in connected_devices)
+ D.disconnect_device(src)
+ disconnect_device(D)
+ return ..()
+
+/datum/wifi/proc/connect_device(var/datum/wifi/device)
+ if(connected_devices)
+ connected_devices |= device
+ else
+ connected_devices = new()
+ connected_devices |= device
+
+/datum/wifi/proc/disconnect_device(var/datum/wifi/device)
+ if(connected_devices)
+ connected_devices -= device
+
+//-------------------------------
+// Receiver
+//-------------------------------
+/datum/wifi/receiver/New()
+ ..()
+ if(wirelessProcess)
+ wirelessProcess.add_device(src)
+
+/datum/wifi/receiver/Destroy()
+ if(wirelessProcess)
+ wirelessProcess.remove_device(src)
+ return ..()
+
+//-------------------------------
+// Sender
+//-------------------------------
+/datum/wifi/sender/New()
+ ..()
+ send_connection_request()
+
+/datum/wifi/sender/proc/set_target(var/new_target)
+ id = new_target
+
+/datum/wifi/sender/proc/send_connection_request()
+ var/datum/connection_request/C = new(src, id)
+ wirelessProcess.add_request(C)
+
+
+//-------------------------------
+// Connection request
+//-------------------------------
+/datum/connection_request
+ var/datum/wifi/sender/source //wifi/sender object creating the request
+ var/id //id tag of the target device(s) to try to connect to
+
+/datum/connection_request/New(var/datum/wifi/sender/sender, var/receiver)
+ if(istype(sender))
+ source = sender
+ id = receiver
diff --git a/html/changelogs/Loganbacca-wireless.yml b/html/changelogs/Loganbacca-wireless.yml
new file mode 100644
index 00000000000..20e7eff0cf9
--- /dev/null
+++ b/html/changelogs/Loganbacca-wireless.yml
@@ -0,0 +1,4 @@
+author: Loganbacca
+delete-after: True
+changes:
+ - rscadd: "Added a backend (wireless) system for communication between machinery and other devices."
\ No newline at end of file