diff --git a/code/WorkInProgress/carn/debug_powernets.dm b/code/WorkInProgress/carn/debug_powernets.dm
new file mode 100644
index 00000000000..600b0315507
--- /dev/null
+++ b/code/WorkInProgress/carn/debug_powernets.dm
@@ -0,0 +1,86 @@
+//Inefficient as hell so don't copypasta this anywhere! It's only here as a tool for debugging
+//prints a map of the powernetworks on z-level 1
+/client/verb/print_powernets()
+ set name = "print powernets"
+
+ var/file = file("powernets_map.html")
+
+ var/list/grid[255][255]
+
+ var/list/checklist = list()
+ for(var/obj/structure/cable/C in world)
+ if(C.z != 1) continue
+ if(C.x < 1 || C.x > 255) continue
+ if(C.y < 1 || C.y > 255) continue
+ grid[C.x][C.y] = C.netnum
+ checklist |= C.netnum
+
+ sleep(1)
+ file << ""
+ for(var/netnum in checklist)
+ file << "[netnum]
"
+
+ for(var/j=255, j>=1, j--)
+ var/line = "
"
+ for(var/i=1, i<=255, i++)
+ switch(grid[i][j])
+ if(null) line += " "
+ if(0 to 9) line += "[grid[i][j]]"
+ if(10) line += "a"
+ if(11) line += "b"
+ if(12) line += "c"
+ if(13) line += "d"
+ if(14) line += "e"
+ if(15) line += "f"
+ if(16) line += "g"
+ if(17) line += "h"
+ if(18) line += "i"
+ if(19) line += "j"
+ if(20) line += "k"
+ if(21) line += "l"
+ if(22) line += "m"
+ if(23) line += "n"
+ if(24) line += "o"
+ if(25) line += "p"
+ if(26) line += "q"
+ if(27) line += "r"
+ if(28) line += "s"
+ if(29) line += "t"
+ if(30) line += "u"
+ if(31) line += "v"
+ if(32) line += "w"
+ if(33) line += "x"
+ if(34) line += "y"
+ if(35) line += "z"
+ if(36) line += "A"
+ if(37) line += "B"
+ if(38) line += "C"
+ if(39) line += "D"
+ if(40) line += "E"
+ if(41) line += "F"
+ if(42) line += "G"
+ if(43) line += "H"
+ if(44) line += "I"
+ if(45) line += "J"
+ if(46) line += "K"
+ if(47) line += "L"
+ if(48) line += "M"
+ if(49) line += "N"
+ if(50) line += "O"
+ if(51) line += "P"
+ if(52) line += "Q"
+ if(53) line += "R"
+ if(54) line += "S"
+ if(55) line += "T"
+ if(56) line += "U"
+ if(57) line += "V"
+ if(58) line += "W"
+ if(59) line += "X"
+ if(60) line += "Y"
+ if(61) line += "Z"
+ else line += "#"
+
+ file << line
+ file << ""
+ src << "printed to powernets_map.html"
+ src << browse(file)
\ No newline at end of file
diff --git a/code/WorkInProgress/carn/master_controller_semi-seq.dm b/code/WorkInProgress/carn/master_controller_semi-seq.dm
new file mode 100644
index 00000000000..96e7eacda6e
--- /dev/null
+++ b/code/WorkInProgress/carn/master_controller_semi-seq.dm
@@ -0,0 +1,258 @@
+//Nothing spectacular, just a slightly more configurable MC.
+
+var/global/datum/controller/game_controller/master_controller //Set in world.New()
+var/global/datum/failsafe/Failsafe
+var/global/controller_iteration = 0
+
+
+var/global/last_tick_timeofday = world.timeofday
+var/global/last_tick_duration = 0
+
+var/global/obj/machinery/last_obj_processed //Used for MC 'proc break' debugging
+var/global/datum/disease/last_disease_processed //Used for MC 'proc break' debugging
+var/global/obj/machinery/last_machine_processed //Used for MC 'proc break' debugging
+
+datum/controller/game_controller
+ var/processing = 0
+ var/breather_ticks = 1 //a somewhat crude attempt to iron over the 'bumps' caused by high-cpu use by letting the MC have a breather for this many ticks after every step
+ var/minimum_ticks = 10 //The minimum length of time between MC ticks
+
+ var/global/air_master_ready = 0
+ var/global/tension_master_ready = 0
+ var/global/sun_ready = 0
+ var/global/mobs_ready = 0
+ var/global/diseases_ready = 0
+ var/global/machines_ready = 0
+ var/global/objects_ready = 0
+ var/global/networks_ready = 0
+ var/global/powernets_ready = 0
+ var/global/ticker_ready = 0
+
+datum/controller/game_controller/New()
+ //There can be only one master_controller. Out with the old and in with the new.
+ if(master_controller)
+ if(master_controller != src)
+ del(master_controller)
+ master_controller = src
+
+ if(!air_master)
+ air_master = new /datum/controller/air_system()
+ air_master.setup()
+
+ if(!job_master)
+ job_master = new /datum/controller/occupations()
+ if(job_master.SetupOccupations())
+ world << "\red \b Job setup complete"
+ job_master.LoadJobs("config/jobs.txt")
+
+ if(!tension_master) tension_master = new /datum/tension()
+ if(!syndicate_code_phrase) syndicate_code_phrase = generate_code_phrase()
+ if(!syndicate_code_response) syndicate_code_response = generate_code_phrase()
+ if(!ticker) ticker = new /datum/controller/gameticker()
+ if(!emergency_shuttle) emergency_shuttle = new /datum/shuttle_controller/emergency_shuttle()
+
+
+datum/controller/game_controller/proc/setup()
+ world.tick_lag = config.Ticklag
+
+ createRandomZlevel()
+ setup_objects()
+ setupgenetics()
+ setupfactions()
+
+ for(var/i = 0, i < max_secret_rooms, i++)
+ make_mining_asteroid_secret()
+
+ spawn(0)
+ if(ticker)
+ ticker.pregame()
+
+datum/controller/game_controller/proc/setup_objects()
+ world << "\red \b Initializing objects"
+ sleep(-1)
+ for(var/obj/object in world)
+ object.initialize()
+
+ world << "\red \b Initializing pipe networks"
+ sleep(-1)
+ for(var/obj/machinery/atmospherics/machine in world)
+ machine.build_network()
+
+ world << "\red \b Initializing atmos machinery."
+ sleep(-1)
+ for(var/obj/machinery/atmospherics/unary/vent_pump/T in world)
+ T.broadcast_status()
+ for(var/obj/machinery/atmospherics/unary/vent_scrubber/T in world)
+ T.broadcast_status()
+
+ world << "\red \b Initializations complete."
+ sleep(-1)
+
+
+datum/controller/game_controller/proc/process()
+ set background = 1
+ processing = 1
+ while(1) //far more efficient than recursively calling ourself
+ if(!Failsafe) new /datum/failsafe()
+
+ var/currenttime = world.timeofday
+ last_tick_duration = (currenttime - last_tick_timeofday) / 10
+ last_tick_timeofday = currenttime
+
+ if(processing)
+ var/start_time = world.timeofday
+ controller_iteration++
+
+ air_master_ready = 0
+ tension_master_ready = 0
+ sun_ready = 0
+ mobs_ready = 0
+ diseases_ready = 0
+ machines_ready = 0
+ objects_ready = 0
+ networks_ready = 0
+ powernets_ready = 0
+ ticker_ready = 0
+
+ spawn(0)
+ air_master.process()
+ air_master_ready = 1
+ sleep(breather_ticks)
+
+ spawn(0)
+ tension_master.process()
+ tension_master_ready = 1
+ sleep(breather_ticks)
+
+ spawn(0)
+ sun.calc_position()
+ sun_ready = 1
+ sleep(breather_ticks)
+
+ spawn(0)
+ for(var/mob/living/M in world) //only living mobs have life processes
+ M.Life()
+ mobs_ready = 1
+ sleep(breather_ticks)
+
+ spawn(0)
+ for(var/datum/disease/D in active_diseases)
+ last_disease_processed = D
+ D.process()
+ diseases_ready = 1
+ sleep(breather_ticks)
+
+ spawn(0)
+ for(var/obj/machinery/machine in machines)
+ if(machine)
+ last_machine_processed = machine
+ machine.process()
+ if(machine && machine.use_power)
+ machine.auto_use_power()
+ machines_ready = 1
+ sleep(breather_ticks)
+
+ spawn(0)
+ for(var/obj/object in processing_objects)
+ last_obj_processed = object
+ object.process()
+ objects_ready = 1
+ sleep(breather_ticks)
+
+ spawn(0)
+ for(var/datum/pipe_network/network in pipe_networks)
+ network.process()
+ networks_ready = 1
+ sleep(breather_ticks)
+
+ spawn(0)
+ for(var/datum/powernet/P in powernets)
+ P.reset()
+ powernets_ready = 1
+ sleep(breather_ticks)
+
+ spawn(0)
+ ticker.process()
+ ticker_ready = 1
+
+ sleep( minimum_ticks - max(world.timeofday-start_time,0) ) //to prevent long delays happening at midnight
+
+ var/IL_check = 0 //Infinite loop check (To report when the master controller breaks.)
+ while(!air_master_ready || !tension_master_ready || !sun_ready || !mobs_ready || !diseases_ready || !machines_ready || !objects_ready || !networks_ready || !powernets_ready || !ticker_ready)
+ IL_check++
+ if(IL_check > 200)
+ var/MC_report = "air_master_ready = [air_master_ready]; tension_master_ready = [tension_master_ready]; sun_ready = [sun_ready]; mobs_ready = [mobs_ready]; diseases_ready = [diseases_ready]; machines_ready = [machines_ready]; objects_ready = [objects_ready]; networks_ready = [networks_ready]; powernets_ready = [powernets_ready]; ticker_ready = [ticker_ready];"
+ var/MC_admin_report = "PROC BREAKAGE WARNING: The game's master contorller appears to be stuck in one of it's cycles. It has looped through it's delaying loop [IL_check] times.
The master controller reports: [MC_report]
"
+ if(!diseases_ready)
+ if(last_disease_processed)
+ MC_admin_report += "DISEASE PROCESSING stuck on [last_disease_processed]
"
+ else
+ MC_admin_report += "DISEASE PROCESSING stuck on unknown
"
+ if(!machines_ready)
+ if(last_machine_processed)
+ MC_admin_report += "MACHINE PROCESSING stuck on [last_machine_processed]
"
+ else
+ MC_admin_report += "MACHINE PROCESSING stuck on unknown
"
+ if(!objects_ready)
+ if(last_obj_processed)
+ MC_admin_report += "OBJ PROCESSING stuck on [last_obj_processed]
"
+ else
+ MC_admin_report += "OBJ PROCESSING stuck on unknown
"
+ MC_admin_report += "Master controller breaking out of delaying loop. Restarting the round is advised if problem persists. DO NOT manually restart the master controller.
"
+ message_admins(MC_admin_report)
+ log_admin("PROC BREAKAGE WARNING: infinite_loop_check = [IL_check]; [MC_report];")
+ break
+ sleep(3)
+ else
+ sleep(10)
+
+
+
+/datum/failsafe // This thing pretty much just keeps poking the master controller
+ var/spinning = 1
+ var/current_iteration = 0
+ var/ticks_per_spin = 200 //poke the MC every 20 seconds
+ var/defcon = 0 //alert level. For every poke that fails this is raised by 1. When it reaches 5 the MC is replaced with a new one. (effectively killing any master_controller.process() and starting a new one)
+
+/datum/failsafe/New()
+ //There can be only one failsafe. Out with the old in with the new (that way we can restart the Failsafe by spawning a new one)
+ if(Failsafe && (Failsafe != src))
+ del(Failsafe)
+ Failsafe = src
+
+ current_iteration = controller_iteration
+ spawn(0)
+ Failsafe.spin()
+
+
+/datum/failsafe/proc/spin()
+ set background = 1
+ while(1) //more efficient than recursivly calling ourself over and over. background = 1 ensures we do not trigger an infinite loop
+ if(master_controller)
+ if(spinning && master_controller.processing) //only poke if these overrides aren't in effect
+ if(current_iteration == controller_iteration) //master_controller hasn't finished processing in the defined interval
+ switch(defcon)
+ if(0 to 3)
+ defcon++
+ if(4)
+ defcon = 5
+ for(var/client/C)
+ if(C.holder)
+ C << "Warning. The Master Controller has not fired in the last [4*ticks_per_spin] ticks. Automatic restart in [ticks_per_spin] ticks."
+ if(5)
+ for(var/client/C)
+ if(C.holder)
+ C << "Warning. The Master Controller has still not fired within the last [5*ticks_per_spin] ticks. Killing and restarting..."
+ spawn(0)
+ new /datum/controller/game_controller() //replace the old master_controller (hence killing the old one's process)
+ master_controller.process() //Start it rolling again
+ defcon = 0
+ else
+ defcon = 0
+ current_iteration = controller_iteration
+ else
+ defcon = 0
+ else
+ new /datum/controller/game_controller() //replace the missing master_controller! This should never happen.
+ sleep(ticks_per_spin)
+
diff --git a/code/WorkInProgress/carn/master_controller_sequential.dm b/code/WorkInProgress/carn/master_controller_sequential.dm
new file mode 100644
index 00000000000..f0b97fa0520
--- /dev/null
+++ b/code/WorkInProgress/carn/master_controller_sequential.dm
@@ -0,0 +1,213 @@
+//simplified MC that is designed to fail when procs 'break'. When it fails it's just replaced with a new one.
+//It ensures master_controller.process() is never doubled up by killing the MC (hence terminating any of its sleeping procs)
+//WIP, needs lots of work still
+
+var/global/datum/controller/game_controller/master_controller //Set in world.New()
+var/global/datum/failsafe/Failsafe
+var/global/controller_iteration = 0
+
+
+var/global/last_tick_timeofday = world.timeofday
+var/global/last_tick_duration = 0
+
+var/global/obj/machinery/last_obj_processed //Used for MC 'proc break' debugging
+var/global/datum/disease/last_disease_processed //Used for MC 'proc break' debugging
+var/global/obj/machinery/last_machine_processed //Used for MC 'proc break' debugging
+
+datum/controller/game_controller
+ var/processing = 0
+ var/breather_ticks = 2 //a somewhat crude attempt to iron over the 'bumps' caused by high-cpu use by letting the MC have a breather for this many ticks after every loop
+ var/minimum_ticks = 20 //The minimum length of time between MC ticks
+
+ var/global/air_master_ready = 0
+ var/global/tension_master_ready = 0
+ var/global/sun_ready = 0
+ var/global/mobs_ready = 0
+ var/global/diseases_ready = 0
+ var/global/machines_ready = 0
+ var/global/objects_ready = 0
+ var/global/networks_ready = 0
+ var/global/powernets_ready = 0
+ var/global/ticker_ready = 0
+
+datum/controller/game_controller/New()
+ //There can be only one master_controller. Out with the old and in with the new.
+ if(master_controller)
+ if(master_controller != src)
+ del(master_controller)
+ master_controller = src
+
+ if(!air_master)
+ air_master = new /datum/controller/air_system()
+ air_master.setup()
+
+ if(!job_master)
+ job_master = new /datum/controller/occupations()
+ if(job_master.SetupOccupations())
+ world << "\red \b Job setup complete"
+ job_master.LoadJobs("config/jobs.txt")
+
+ if(!tension_master) tension_master = new /datum/tension()
+ if(!syndicate_code_phrase) syndicate_code_phrase = generate_code_phrase()
+ if(!syndicate_code_response) syndicate_code_response = generate_code_phrase()
+ if(!ticker) ticker = new /datum/controller/gameticker()
+ if(!emergency_shuttle) emergency_shuttle = new /datum/shuttle_controller/emergency_shuttle()
+
+
+datum/controller/game_controller/proc/setup()
+ world.tick_lag = config.Ticklag
+
+ createRandomZlevel()
+ setup_objects()
+ setupgenetics()
+ setupfactions()
+
+ for(var/i = 0, i < max_secret_rooms, i++)
+ make_mining_asteroid_secret()
+
+ spawn(0)
+ if(ticker)
+ ticker.pregame()
+
+datum/controller/game_controller/proc/setup_objects()
+ world << "\red \b Initializing objects"
+ sleep(-1)
+ for(var/obj/object in world)
+ object.initialize()
+
+ world << "\red \b Initializing pipe networks"
+ sleep(-1)
+ for(var/obj/machinery/atmospherics/machine in world)
+ machine.build_network()
+
+ world << "\red \b Initializing atmos machinery."
+ sleep(-1)
+ for(var/obj/machinery/atmospherics/unary/vent_pump/T in world)
+ T.broadcast_status()
+ for(var/obj/machinery/atmospherics/unary/vent_scrubber/T in world)
+ T.broadcast_status()
+
+ world << "\red \b Initializations complete."
+ sleep(-1)
+
+
+datum/controller/game_controller/proc/process()
+ set background = 1
+ processing = 1
+ while(1) //far more efficient than recursively calling ourself
+ if(!Failsafe) new /datum/failsafe()
+
+ var/currenttime = world.timeofday
+ last_tick_duration = (currenttime - last_tick_timeofday) / 10
+ last_tick_timeofday = currenttime
+
+ if(processing)
+ var/start_time = world.timeofday
+ controller_iteration++
+
+ air_master.process()
+ sleep(breather_ticks)
+
+ tension_master.process()
+ sleep(breather_ticks)
+
+ sun.calc_position()
+ sleep(breather_ticks)
+
+ for(var/mob/living/M in world) //only living mobs have life processes
+ M.Life()
+ sleep(breather_ticks)
+
+ for(var/datum/disease/D in active_diseases)
+ last_disease_processed = D
+ D.process()
+ sleep(breather_ticks)
+
+ for(var/obj/machinery/machine in machines)
+ if(machine)
+ last_machine_processed = machine
+ machine.process()
+ if(machine && machine.use_power)
+ machine.auto_use_power()
+ sleep(breather_ticks)
+
+ for(var/obj/object in processing_objects)
+ last_obj_processed = object
+ object.process()
+ sleep(breather_ticks)
+
+ for(var/datum/pipe_network/network in pipe_networks)
+ network.process()
+ sleep(breather_ticks)
+
+
+ for(var/datum/powernet/P in powernets)
+ P.reset()
+ sleep(breather_ticks)
+
+ ticker.process()
+
+ sleep( minimum_ticks - max(world.timeofday-start_time,0) ) //to prevent long delays happening at midnight
+
+ else
+ sleep(10)
+
+
+
+/datum/failsafe // This thing pretty much just keeps poking the master controller
+ var/spinning = 1
+ var/current_iteration = 0
+ var/ticks_per_spin = 100 //poke the MC every 10 seconds
+ var/defcon = 0 //alert level. For every poke that fails this is raised by 1. When it reaches 5 the MC is replaced with a new one. (effectively killing any master_controller.process() and starting a new one)
+
+/datum/failsafe/New()
+ //There can be only one failsafe. Out with the old in with the new (that way we can restart the Failsafe by spawning a new one)
+ if(Failsafe && (Failsafe != src))
+ del(Failsafe)
+ Failsafe = src
+
+ current_iteration = controller_iteration
+ spawn(0)
+ Failsafe.spin()
+
+
+/datum/failsafe/proc/spin()
+ set background = 1
+ while(1) //more efficient than recursivly calling ourself over and over. background = 1 ensures we do not trigger an infinite loop
+ if(master_controller)
+ if(spinning && master_controller.processing) //only poke if these overrides aren't in effect
+ if(current_iteration == controller_iteration) //master_controller hasn't finished processing in the defined interval
+ switch(defcon)
+ if(0 to 3)
+ defcon++
+ if(4)
+ defcon = 5
+ for(var/client/C)
+ if(C.holder)
+ C << "Warning. The Master Controller has not fired in the last [defcon*ticks_per_spin] ticks. Automatic restart in [ticks_per_spin] ticks."
+ if(5)
+ for(var/client/C)
+ if(C.holder)
+ C << "Warning. The Master Controller has still not fired within the last [defcon*ticks_per_spin] ticks. Killing and restarting..."
+ spawn(0)
+ new /datum/controller/game_controller() //replace the old master_controller (hence killing the old one's process)
+ master_controller.process() //Start it rolling again
+ defcon = 0
+ else
+ defcon = 0
+ current_iteration = controller_iteration
+ else
+ defcon = 0
+ else
+ new /datum/controller/game_controller() //replace the missing master_controller! This should never happen.
+ sleep(ticks_per_spin)
+
+//DEBUG VERBS
+/*
+/client/verb/spawn_MC()
+ new /datum/controller/game_controller()
+
+
+/client/verb/spawn_FS()
+ new /datum/failsafe()
+*/
\ No newline at end of file
diff --git a/code/defines/mob/living/silicon/pai.dm b/code/defines/mob/living/silicon/pai.dm
index 6837ba99b77..2f1bcc2ade0 100644
--- a/code/defines/mob/living/silicon/pai.dm
+++ b/code/defines/mob/living/silicon/pai.dm
@@ -48,4 +48,6 @@
var/datum/data/record/securityActive2
var/obj/machinery/door/hackdoor // The airlock being hacked
- var/hackprogress = 0 // Possible values: 0 - 100, >= 100 means the hack is complete and will be reset upon next check
\ No newline at end of file
+ var/hackprogress = 0 // Possible values: 0 - 100, >= 100 means the hack is complete and will be reset upon next check
+
+ var/obj/item/radio/integrated/signal/sradio // AI's signaller
\ No newline at end of file
diff --git a/code/modules/mob/living/silicon/pai/pai.dm b/code/modules/mob/living/silicon/pai/pai.dm
index 78883f7ebd0..7a8f6fad20a 100644
--- a/code/modules/mob/living/silicon/pai/pai.dm
+++ b/code/modules/mob/living/silicon/pai/pai.dm
@@ -2,6 +2,7 @@
canmove = 0
src.loc = paicard
card = paicard
+ sradio = new(src)
if(card)
if(!card.radio)
card.radio = new /obj/item/device/radio(src.card)
diff --git a/code/modules/mob/living/silicon/pai/software.dm b/code/modules/mob/living/silicon/pai/software.dm
index 138be7efa5c..ab035c15923 100644
--- a/code/modules/mob/living/silicon/pai/software.dm
+++ b/code/modules/mob/living/silicon/pai/software.dm
@@ -19,6 +19,7 @@
"medical HUD" = 20,
"universal translator" = 35,
//"projection array" = 15
+ "remote signaller" = 5,
)
/mob/living/silicon/pai/verb/paiInterface()
@@ -63,6 +64,8 @@
left_part = src.softwareDoor()
if("camerajack")
left_part = src.softwareCamera()
+ if("signaller")
+ left_part = src.softwareSignal()
//usr << browse_rsc('windowbak.png') // This has been moved to the mob's Login() proc
@@ -141,6 +144,30 @@
if("radio")
src.card.radio.attack_self(src)
+ if("signaller")
+
+ if(href_list["send"])
+
+ sradio.send_signal("ACTIVATE")
+ for(var/mob/O in hearers(1, src.loc))
+ O.show_message(text("\icon[] *beep* *beep*", src), 3, "*beep* *beep*", 2)
+
+ if(href_list["freq"])
+
+ var/new_frequency = (sradio.frequency + text2num(href_list["freq"]))
+ if(new_frequency < 1200 || new_frequency > 1600)
+ new_frequency = sanitize_frequency(new_frequency)
+ sradio.set_frequency(new_frequency)
+
+ if(href_list["code"])
+
+ sradio.code += text2num(href_list["code"])
+ sradio.code = round(sradio.code)
+ sradio.code = min(100, sradio.code)
+ sradio.code = max(1, sradio.code)
+
+
+
if("directive")
if(href_list["getdna"])
var/mob/living/M = src.loc
@@ -171,13 +198,14 @@
return
var/AnsweringMS = 0
- for (var/obj/machinery/message_server/MS in world)
- MS.send_pda_message("[P.owner]","[src]","[t]")
+ for (var/obj/machinery/message_server/MS in message_servers)
if(MS.active)
- AnsweringMS++
+ MS.send_pda_message("[P.owner]","[src]","[t]")
+ AnsweringMS = 1
+ break
if(!AnsweringMS)
- return
+ return alert("ERROR: No response from server!")
tnote += "→ To [P.owner]:
[t]
"
P.tnote += "← From [src]:
[t]
"
@@ -202,13 +230,13 @@
var/mob/living/silicon/pai/P = target
var/AnsweringMS = 0
- for (var/obj/machinery/message_server/MS in world)
+ for (var/obj/machinery/message_server/MS in message_servers)
MS.send_pda_message("[P]","[src]","[t]")
- if(MS.active)
- AnsweringMS++
+ AnsweringMS = 1
+ break
if(!AnsweringMS)
- return
+ return alert("ERROR: No response from server!")
tnote += "→ To [P]:
[t]
"
@@ -277,6 +305,7 @@
src.paiInterface() // So we'll just call the update directly rather than doing some default checks
return
+// MENUS
/mob/living/silicon/pai/proc/softwareMenu() // Populate the right menu
var/dat = ""
@@ -300,6 +329,8 @@
dat += "Security Records
"
if(s == "camera")
dat += "Camera Jack
"
+ if(s == "remote signaller")
+ dat += "Remote Signaller
"
dat += "
"
// Advanced
@@ -386,6 +417,28 @@
// -=-=-=-= Software =-=-=-=-=- //
+//Remote Signaller
+/mob/living/silicon/pai/proc/softwareSignal()
+ var/dat = ""
+ dat += "