From 7c994669ab6aaecc93ffe6cb7b8692165bdd1a74 Mon Sep 17 00:00:00 2001 From: Loganbacca Date: Sun, 15 Nov 2015 18:02:48 +1300 Subject: [PATCH 01/10] Added wireless connection controller Base framework for wireless connection controller to replace radio controller for device communication --- baystation12.dme | 4 ++ code/controllers/Processes/wireless.dm | 6 +++ code/controllers/verbs.dm | 5 ++- code/modules/wireless/interfaces.dm | 56 ++++++++++++++++++++++++++ code/modules/wireless/manager.dm | 47 +++++++++++++++++++++ 5 files changed, 117 insertions(+), 1 deletion(-) create mode 100644 code/controllers/Processes/wireless.dm create mode 100644 code/modules/wireless/interfaces.dm create mode 100644 code/modules/wireless/manager.dm diff --git a/baystation12.dme b/baystation12.dme index 4d326ffcedb..b9a1e34febf 100644 --- a/baystation12.dme +++ b/baystation12.dme @@ -139,6 +139,8 @@ #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\_define.dm" #include "code\controllers\ProcessScheduler\core\_stubs.dm" #include "code\controllers\ProcessScheduler\core\process.dm" #include "code\controllers\ProcessScheduler\core\processScheduler.dm" @@ -1830,6 +1832,8 @@ #include "code\modules\virus2\helpers.dm" #include "code\modules\virus2\isolator.dm" #include "code\modules\virus2\items_devices.dm" +#include "code\modules\wireless\interfaces.dm" +#include "code\modules\wireless\manager.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/controllers/Processes/wireless.dm b/code/controllers/Processes/wireless.dm new file mode 100644 index 00000000000..8cc3d039d8b --- /dev/null +++ b/code/controllers/Processes/wireless.dm @@ -0,0 +1,6 @@ +/datum/controller/process/wireless/setup() + name = "wireless" + schedule_interval = 50 + +/datum/controller/process/wireless/doWork() + wifi_manager.process() \ No newline at end of file diff --git a/code/controllers/verbs.dm b/code/controllers/verbs.dm index c394318c286..ad82eae3bf5 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(wifi_manager) + feedback_add_details("admin_verb", "DWifi") message_admins("Admin [key_name_admin(usr)] is debugging the [controller] controller.") return diff --git a/code/modules/wireless/interfaces.dm b/code/modules/wireless/interfaces.dm new file mode 100644 index 00000000000..63f9c8630f2 --- /dev/null +++ b/code/modules/wireless/interfaces.dm @@ -0,0 +1,56 @@ +/datum/wifi + var/list/connected_devices + +/datum/wifi/New() + connected_devices = new() + +/datum/wifi/Destroy(var/wifi/device) + ..() + for(var/datum/wifi/D in connected_devices) + D.disconnect_device(src) + +/datum/wifi/proc/connect_device(var/datum/wifi/device) + connected_devices |= device + +/datum/wifi/proc/disconnect_device(var/datum/wifi/device) + connected_devices -= device + + + +/datum/wifi/receiver + var/id + +/datum/wifi/receiver/New(var/new_id) + ..() + id = new_id + wifi_manager.add_device(src) + +/datum/wifi/receiver/Destroy() + ..() + wifi_manager.remove_device(src) + + + +/datum/wifi/sender + var/target + +/datum/wifi/sender/New(var/new_target) + ..() + target = new_target + +/datum/wifi/sender/proc/set_target(var/new_target) + target = new_target + +/datum/wifi/sender/proc/send_connection_request() + var/datum/connection_request/C = new(src, target) + wifi_manager.add_request(C) + + + +/datum/connection_request + var/datum/wifi/sender/source //wifi_sender object creating the request + var/target //id tag of the target device to try to connect to + +/datum/connection_request/New(var/datum/wifi/sender/sender, var/receiver) + source = sender + target = receiver diff --git a/code/modules/wireless/manager.dm b/code/modules/wireless/manager.dm new file mode 100644 index 00000000000..4922732785f --- /dev/null +++ b/code/modules/wireless/manager.dm @@ -0,0 +1,47 @@ +/datum/wifi_manager + var/list/pending_connections + var/list/receiver_list + var/list/failed_connections + +/datum/wifi_manager/New() + pending_connections = new() + receiver_list = new() + failed_connections = new() + +/datum/wifi_manager/proc/add_device(var/datum/wifi/receiver/R) + receiver_list |= R + +/datum/wifi_manager/proc/remove_device(var/datum/wifi/receiver/R) + receiver_list -= R + +/datum/wifi_manager/proc/add_request(var/datum/connection_request/C) + pending_connections += C + +/datum/wifi_manager/proc/process() + //check any failed connections from the previous pass + if(failed_connections.len > 0) + for(var/datum/connection_request/C in failed_connections) + var/target_found = 0 + for(var/datum/wifi/receiver/R in receiver_list) + if(R.id == C.target) + var/datum/wifi/sender/S = C.source + S.connect_device(R) + R.connect_device(S) + target_found = 1 + failed_connections -= C + if(!target_found) + log_debug("[C.source] could not connect to \"[C.target]\"") + + //check and try assigning any pending connections + if(pending_connections.len > 0) + for(var/datum/connection_request/C in pending_connections) + var/target_found = 0 + for(var/datum/wifi/receiver/R in receiver_list) + if(R.id == C.target) + var/datum/wifi/sender/S = C.source + S.connect_device(R) + R.connect_device(S) + target_found = 1 + pending_connections -= C + if(!target_found) + failed_connections += C From c1159c8f4d3393415102106e696816b24941d133 Mon Sep 17 00:00:00 2001 From: Loganbacca Date: Mon, 16 Nov 2015 22:09:20 +1300 Subject: [PATCH 02/10] Wireless controller update Rewrote wireless controller to be a full processScheduler controller. Added a basic wireless reconfiguration tool. --- baystation12.dme | 1 - code/controllers/Processes/wireless.dm | 55 ++++++++++++++++++++++--- code/controllers/verbs.dm | 2 +- code/modules/wireless/interfaces.dm | 18 ++++++-- icons/obj/device.dmi | Bin 24779 -> 24130 bytes 5 files changed, 65 insertions(+), 11 deletions(-) diff --git a/baystation12.dme b/baystation12.dme index b9a1e34febf..33e934a351d 100644 --- a/baystation12.dme +++ b/baystation12.dme @@ -1833,7 +1833,6 @@ #include "code\modules\virus2\isolator.dm" #include "code\modules\virus2\items_devices.dm" #include "code\modules\wireless\interfaces.dm" -#include "code\modules\wireless\manager.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/controllers/Processes/wireless.dm b/code/controllers/Processes/wireless.dm index 8cc3d039d8b..ccb99734dc3 100644 --- a/code/controllers/Processes/wireless.dm +++ b/code/controllers/Processes/wireless.dm @@ -1,6 +1,49 @@ -/datum/controller/process/wireless/setup() - name = "wireless" - schedule_interval = 50 - -/datum/controller/process/wireless/doWork() - wifi_manager.process() \ No newline at end of file +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) + receiver_list |= R + +/datum/controller/process/wireless/proc/remove_device(var/datum/wifi/receiver/R) + receiver_list -= R + +/datum/controller/process/wireless/proc/add_request(var/datum/connection_request/C) + pending_connections += C + +/datum/controller/process/wireless/doWork() + //process any pending connection requests + if(pending_connections.len > 0) + process_queue(pending_connections, retry_connections) + return //quit here so the retry connection attempt is delayed + + //process any connection request waiting to be retried + if(retry_connections.len > 0) + process_queue(retry_connections, failed_connections) + +/datum/controller/process/wireless/proc/process_queue(var/list/input_queue, var/list/output_queue) + for(var/datum/connection_request/C in input_queue) + var/target_found = 0 + for(var/datum/wifi/receiver/R in receiver_list) + if(R.id == C.target) + var/datum/wifi/sender/S = C.source + S.connect_device(R) + R.connect_device(S) + target_found = 1 + input_queue -= C + if(!target_found) + output_queue += C + scheck() \ No newline at end of file diff --git a/code/controllers/verbs.dm b/code/controllers/verbs.dm index ad82eae3bf5..f47070b6b20 100644 --- a/code/controllers/verbs.dm +++ b/code/controllers/verbs.dm @@ -93,7 +93,7 @@ debug_variables(chemistryProcess) feedback_add_details("admin_verb", "DChem") if("Wireless") - debug_variables(wifi_manager) + 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/modules/wireless/interfaces.dm b/code/modules/wireless/interfaces.dm index 63f9c8630f2..275c3f90358 100644 --- a/code/modules/wireless/interfaces.dm +++ b/code/modules/wireless/interfaces.dm @@ -23,11 +23,11 @@ /datum/wifi/receiver/New(var/new_id) ..() id = new_id - wifi_manager.add_device(src) + wirelessProcess.add_device(src) /datum/wifi/receiver/Destroy() ..() - wifi_manager.remove_device(src) + wirelessProcess.remove_device(src) @@ -43,7 +43,7 @@ /datum/wifi/sender/proc/send_connection_request() var/datum/connection_request/C = new(src, target) - wifi_manager.add_request(C) + wirelessProcess.add_request(C) @@ -54,3 +54,15 @@ /datum/connection_request/New(var/datum/wifi/sender/sender, var/receiver) source = sender target = receiver + + +/obj/item/device/wireless_tool + name = "wireless tool" + desc = "Used for connecting machinery to controls for remote operation." + icon_state = "wirelesstool_off" + +/obj/item/device/wireless_tool/attack_self() + if(icon_state == "wirelesstool_off") + icon_state = "wirelesstool_on" + else + icon_state = "wirelesstool_off" diff --git a/icons/obj/device.dmi b/icons/obj/device.dmi index 281ec1e62ba3827875dd1416e1bac56bc2a2cda0..57d4baeb7e25256851b7f9d7479d5dbe0219cb3d 100644 GIT binary patch literal 24130 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 literal 24779 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 Wf2&6^S~ddU>)|~e^+Hw4;Qs+y%hNjm From a9555b01758f05d650e7aeef8452aa420c3fd1da Mon Sep 17 00:00:00 2001 From: Loganbacca Date: Mon, 16 Nov 2015 22:42:11 +1300 Subject: [PATCH 03/10] Converted emitter remote control Also converted crematoriums, although they are not working at the moment. Removed redundant wireless manager file. --- baystation12.dme | 1 + code/controllers/Processes/wireless.dm | 2 +- code/game/machinery/buttons.dm | 15 +++++++- code/game/objects/structures/morgue.dm | 8 ++++ code/modules/power/singularity/emitter.dm | 3 ++ code/modules/wireless/devices.dm | 37 ++++++++++++++++++ code/modules/wireless/manager.dm | 47 ----------------------- 7 files changed, 63 insertions(+), 50 deletions(-) create mode 100644 code/modules/wireless/devices.dm delete mode 100644 code/modules/wireless/manager.dm diff --git a/baystation12.dme b/baystation12.dme index 33e934a351d..49e1fe9bcb4 100644 --- a/baystation12.dme +++ b/baystation12.dme @@ -1832,6 +1832,7 @@ #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" diff --git a/code/controllers/Processes/wireless.dm b/code/controllers/Processes/wireless.dm index ccb99734dc3..9db8cb9b550 100644 --- a/code/controllers/Processes/wireless.dm +++ b/code/controllers/Processes/wireless.dm @@ -46,4 +46,4 @@ var/datum/controller/process/wireless/wirelessProcess input_queue -= C if(!target_found) output_queue += C - scheck() \ No newline at end of file + scheck() diff --git a/code/game/machinery/buttons.dm b/code/game/machinery/buttons.dm index 70557a2e18b..bd9fc8a7d10 100644 --- a/code/game/machinery/buttons.dm +++ b/code/game/machinery/buttons.dm @@ -10,9 +10,20 @@ idle_power_usage = 2 active_power_usage = 4 + var/_wifi_id + var/datum/wifi/sender/button/wifi_sender + +/obj/machinery/button/New() + ..() + if(_wifi_id) + wifi_sender = new(_wifi_id) /obj/machinery/button/attack_ai(mob/user as mob) return src.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 +/obj/machinery/button/attackby(obj/item/weapon/W, mob/user as mob) return src.attack_hand(user) + +/obj/machinery/button/attack_hand(mob/living/user) + ..() + if(wifi_sender) + wifi_sender.activate(user) diff --git a/code/game/objects/structures/morgue.dm b/code/game/objects/structures/morgue.dm index 9f8eed2eb57..3cf38a6ef7d 100644 --- a/code/game/objects/structures/morgue.dm +++ b/code/game/objects/structures/morgue.dm @@ -187,6 +187,14 @@ var/id = 1 var/locked = 0 + var/_wifi_id + var/datum/wifi/receiver/button/crematorium/wifi_receiver + +/obj/structure/crematorium/New() + ..() + if(_wifi_id) + wifi_receiver = new(_wifi_id) + /obj/structure/crematorium/Destroy() if(connected) qdel(connected) diff --git a/code/modules/power/singularity/emitter.dm b/code/modules/power/singularity/emitter.dm index 51093296b27..11ad0b7e7a1 100644 --- a/code/modules/power/singularity/emitter.dm +++ b/code/modules/power/singularity/emitter.dm @@ -24,6 +24,7 @@ var/state = 0 var/locked = 0 + var/datum/wifi/receiver/button/emitter/wifi_receiver /obj/machinery/power/emitter/verb/rotate() set name = "Rotate" @@ -40,11 +41,13 @@ ..() if(state == 2 && anchored) connect_to_network() + wifi_receiver = new(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") + wifi_receiver.Destroy() ..() /obj/machinery/power/emitter/update_icon() diff --git a/code/modules/wireless/devices.dm b/code/modules/wireless/devices.dm new file mode 100644 index 00000000000..fbc3bfc321d --- /dev/null +++ b/code/modules/wireless/devices.dm @@ -0,0 +1,37 @@ +/datum/wifi/receiver/button + var/obj/parent + +/datum/wifi/receiver/button/proc/activate(mob/living/user) + + +/datum/wifi/sender/button/proc/activate(mob/living/user) + for(var/datum/wifi/receiver/button/B in connected_devices) + B.activate(user) + +//------------------------------- +// Emitter +//------------------------------- +/datum/wifi/receiver/button/emitter/New(var/new_id, var/obj/machinery/power/emitter/E) + id = new_id + if(istype(E)) + parent = E + ..() + +/datum/wifi/receiver/button/emitter/activate(mob/living/user) + ..() + var/obj/machinery/power/emitter/E = parent + E.activate(user) + +//------------------------------- +// Crematorium +//------------------------------- +/datum/wifi/receiver/button/crematorium/New(var/new_id, var/obj/structure/crematorium/C) + id = new_id + if(istype(C)) + parent = C + ..() + +/datum/wifi/receiver/button/crematorium/activate(mob/living/user) + ..() + var/obj/structure/crematorium/C = parent + C.cremate(user) diff --git a/code/modules/wireless/manager.dm b/code/modules/wireless/manager.dm deleted file mode 100644 index 4922732785f..00000000000 --- a/code/modules/wireless/manager.dm +++ /dev/null @@ -1,47 +0,0 @@ -/datum/wifi_manager - var/list/pending_connections - var/list/receiver_list - var/list/failed_connections - -/datum/wifi_manager/New() - pending_connections = new() - receiver_list = new() - failed_connections = new() - -/datum/wifi_manager/proc/add_device(var/datum/wifi/receiver/R) - receiver_list |= R - -/datum/wifi_manager/proc/remove_device(var/datum/wifi/receiver/R) - receiver_list -= R - -/datum/wifi_manager/proc/add_request(var/datum/connection_request/C) - pending_connections += C - -/datum/wifi_manager/proc/process() - //check any failed connections from the previous pass - if(failed_connections.len > 0) - for(var/datum/connection_request/C in failed_connections) - var/target_found = 0 - for(var/datum/wifi/receiver/R in receiver_list) - if(R.id == C.target) - var/datum/wifi/sender/S = C.source - S.connect_device(R) - R.connect_device(S) - target_found = 1 - failed_connections -= C - if(!target_found) - log_debug("[C.source] could not connect to \"[C.target]\"") - - //check and try assigning any pending connections - if(pending_connections.len > 0) - for(var/datum/connection_request/C in pending_connections) - var/target_found = 0 - for(var/datum/wifi/receiver/R in receiver_list) - if(R.id == C.target) - var/datum/wifi/sender/S = C.source - S.connect_device(R) - R.connect_device(S) - target_found = 1 - pending_connections -= C - if(!target_found) - failed_connections += C From 750414e280004eef9f93987dc764f6095f8f69bb Mon Sep 17 00:00:00 2001 From: Loganbacca Date: Wed, 18 Nov 2015 22:39:53 +1300 Subject: [PATCH 04/10] GC cleanup Cleaned up some GC code and properly implemented it for wireless datums Remote buttons are now fully working with emitters and crematoriums --- code/game/machinery/buttons.dm | 8 ++++++- code/game/objects/structures/morgue.dm | 7 ++++-- code/modules/power/singularity/emitter.dm | 8 +++++-- code/modules/wireless/devices.dm | 28 +++++++++++------------ code/modules/wireless/interfaces.dm | 7 +++--- 5 files changed, 36 insertions(+), 22 deletions(-) diff --git a/code/game/machinery/buttons.dm b/code/game/machinery/buttons.dm index bd9fc8a7d10..63af5844e70 100644 --- a/code/game/machinery/buttons.dm +++ b/code/game/machinery/buttons.dm @@ -13,11 +13,17 @@ var/_wifi_id var/datum/wifi/sender/button/wifi_sender -/obj/machinery/button/New() +/obj/machinery/button/initialize() ..() if(_wifi_id) wifi_sender = new(_wifi_id) +/obj/machinery/button/Destroy() + if(wifi_sender) + qdel(wifi_sender) + wifi_sender = null + return..() + /obj/machinery/button/attack_ai(mob/user as mob) return src.attack_hand(user) diff --git a/code/game/objects/structures/morgue.dm b/code/game/objects/structures/morgue.dm index 3cf38a6ef7d..98d4b928a42 100644 --- a/code/game/objects/structures/morgue.dm +++ b/code/game/objects/structures/morgue.dm @@ -190,15 +190,18 @@ var/_wifi_id var/datum/wifi/receiver/button/crematorium/wifi_receiver -/obj/structure/crematorium/New() +/obj/structure/crematorium/initialize() ..() if(_wifi_id) - wifi_receiver = new(_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 11ad0b7e7a1..3a8441f0188 100644 --- a/code/modules/power/singularity/emitter.dm +++ b/code/modules/power/singularity/emitter.dm @@ -24,6 +24,7 @@ var/state = 0 var/locked = 0 + var/_wifi_id var/datum/wifi/receiver/button/emitter/wifi_receiver /obj/machinery/power/emitter/verb/rotate() @@ -41,13 +42,16 @@ ..() if(state == 2 && anchored) connect_to_network() - wifi_receiver = new(id, src) + 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") - wifi_receiver.Destroy() + if(wifi_receiver) + qdel(wifi_receiver) + wifi_receiver = null ..() /obj/machinery/power/emitter/update_icon() diff --git a/code/modules/wireless/devices.dm b/code/modules/wireless/devices.dm index fbc3bfc321d..40a293c17de 100644 --- a/code/modules/wireless/devices.dm +++ b/code/modules/wireless/devices.dm @@ -1,6 +1,16 @@ /datum/wifi/receiver/button var/obj/parent +/datum/wifi/receiver/button/New(var/new_id, var/obj/O) + id = new_id + if(istype(O)) + parent = O + ..() + +/datum/wifi/receiver/button/Destroy() + parent = null + return ..() + /datum/wifi/receiver/button/proc/activate(mob/living/user) @@ -11,27 +21,17 @@ //------------------------------- // Emitter //------------------------------- -/datum/wifi/receiver/button/emitter/New(var/new_id, var/obj/machinery/power/emitter/E) - id = new_id - if(istype(E)) - parent = E - ..() - /datum/wifi/receiver/button/emitter/activate(mob/living/user) ..() var/obj/machinery/power/emitter/E = parent - E.activate(user) + if(istype(E)) + E.activate(user) //------------------------------- // Crematorium //------------------------------- -/datum/wifi/receiver/button/crematorium/New(var/new_id, var/obj/structure/crematorium/C) - id = new_id - if(istype(C)) - parent = C - ..() - /datum/wifi/receiver/button/crematorium/activate(mob/living/user) ..() var/obj/structure/crematorium/C = parent - C.cremate(user) + if(istype(C)) + C.cremate(user) diff --git a/code/modules/wireless/interfaces.dm b/code/modules/wireless/interfaces.dm index 275c3f90358..22dfa92e149 100644 --- a/code/modules/wireless/interfaces.dm +++ b/code/modules/wireless/interfaces.dm @@ -5,9 +5,10 @@ connected_devices = new() /datum/wifi/Destroy(var/wifi/device) - ..() 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) connected_devices |= device @@ -26,9 +27,8 @@ wirelessProcess.add_device(src) /datum/wifi/receiver/Destroy() - ..() wirelessProcess.remove_device(src) - + return ..() /datum/wifi/sender @@ -37,6 +37,7 @@ /datum/wifi/sender/New(var/new_target) ..() target = new_target + send_connection_request() /datum/wifi/sender/proc/set_target(var/new_target) target = new_target From 56bf894ee19db7abc9502f63edb1397b63d84ab1 Mon Sep 17 00:00:00 2001 From: Loganbacca Date: Thu, 19 Nov 2015 22:18:29 +1300 Subject: [PATCH 05/10] Wireless button updates Added toggle variants for buttons, allowing them to "deactivate" connected devices. Converted flashers and holosigns. Fixes to keep in line with upstream. --- baystation12.dme | 1 - code/controllers/Processes/wireless.dm | 2 +- code/game/machinery/buttons.dm | 44 +++++++++++++++--- code/game/machinery/flasher.dm | 12 +++++ code/game/machinery/holosign.dm | 12 +++++ code/game/objects/structures/morgue.dm | 1 - code/modules/power/singularity/emitter.dm | 5 +-- code/modules/wireless/devices.dm | 55 ++++++++++++++++------- code/modules/wireless/interfaces.dm | 31 ++++++++++--- 9 files changed, 129 insertions(+), 34 deletions(-) diff --git a/baystation12.dme b/baystation12.dme index 49e1fe9bcb4..bae35c729bc 100644 --- a/baystation12.dme +++ b/baystation12.dme @@ -140,7 +140,6 @@ #include "code\controllers\Processes\turf.dm" #include "code\controllers\Processes\vote.dm" #include "code\controllers\Processes\wireless.dm" -#include "code\controllers\ProcessScheduler\core\_define.dm" #include "code\controllers\ProcessScheduler\core\_stubs.dm" #include "code\controllers\ProcessScheduler\core\process.dm" #include "code\controllers\ProcessScheduler\core\processScheduler.dm" diff --git a/code/controllers/Processes/wireless.dm b/code/controllers/Processes/wireless.dm index 9db8cb9b550..cc3338a198f 100644 --- a/code/controllers/Processes/wireless.dm +++ b/code/controllers/Processes/wireless.dm @@ -46,4 +46,4 @@ var/datum/controller/process/wireless/wirelessProcess input_queue -= C if(!target_found) output_queue += C - scheck() + SCHECK diff --git a/code/game/machinery/buttons.dm b/code/game/machinery/buttons.dm index 63af5844e70..381d2768bc5 100644 --- a/code/game/machinery/buttons.dm +++ b/code/game/machinery/buttons.dm @@ -9,19 +9,19 @@ use_power = 1 idle_power_usage = 2 active_power_usage = 4 - var/_wifi_id + var/_wifi_toggle = 0 var/datum/wifi/sender/button/wifi_sender /obj/machinery/button/initialize() ..() + update_icon() if(_wifi_id) - wifi_sender = new(_wifi_id) + wifi_sender = new(_wifi_id, src) /obj/machinery/button/Destroy() - if(wifi_sender) - qdel(wifi_sender) - wifi_sender = null + qdel(wifi_sender) + wifi_sender = null return..() /obj/machinery/button/attack_ai(mob/user as mob) @@ -31,5 +31,37 @@ /obj/machinery/button/attack_hand(mob/living/user) ..() - if(wifi_sender) + + if(!wifi_sender) + return + + use_power(5) + + if(_wifi_toggle) + active = !active + if(active) + wifi_sender.activate(user) + else + wifi_sender.deactivate(user) + update_icon() + else + active = 1 + update_icon() wifi_sender.activate(user) + sleep(10) + active = 0 + update_icon() + +/obj/machinery/button/update_icon() + if(active) + icon_state = "launcheract" + else + icon_state = "launcherbtt" + +/obj/machinery/button/toggle + icon = 'icons/obj/power.dmi' + icon_state = "light0" + _wifi_toggle = 1 + +/obj/machinery/button/toggle/update_icon() + icon_state = "light[active]" 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/objects/structures/morgue.dm b/code/game/objects/structures/morgue.dm index 98d4b928a42..8a9e29a0da2 100644 --- a/code/game/objects/structures/morgue.dm +++ b/code/game/objects/structures/morgue.dm @@ -186,7 +186,6 @@ var/cremating = 0 var/id = 1 var/locked = 0 - var/_wifi_id var/datum/wifi/receiver/button/crematorium/wifi_receiver diff --git a/code/modules/power/singularity/emitter.dm b/code/modules/power/singularity/emitter.dm index 3a8441f0188..084f9ef55f2 100644 --- a/code/modules/power/singularity/emitter.dm +++ b/code/modules/power/singularity/emitter.dm @@ -49,9 +49,8 @@ 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") - if(wifi_receiver) - qdel(wifi_receiver) - wifi_receiver = null + qdel(wifi_receiver) + wifi_receiver = null ..() /obj/machinery/power/emitter/update_icon() diff --git a/code/modules/wireless/devices.dm b/code/modules/wireless/devices.dm index 40a293c17de..96400eb9941 100644 --- a/code/modules/wireless/devices.dm +++ b/code/modules/wireless/devices.dm @@ -1,31 +1,32 @@ -/datum/wifi/receiver/button - var/obj/parent - -/datum/wifi/receiver/button/New(var/new_id, var/obj/O) - id = new_id - if(istype(O)) - parent = O - ..() - -/datum/wifi/receiver/button/Destroy() - parent = null - return ..() - +//------------------------------- +// Buttons +//------------------------------- /datum/wifi/receiver/button/proc/activate(mob/living/user) +/datum/wifi/receiver/button/proc/deactivate(mob/living/user) /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) + //------------------------------- // Emitter //------------------------------- /datum/wifi/receiver/button/emitter/activate(mob/living/user) ..() var/obj/machinery/power/emitter/E = parent - if(istype(E)) - E.activate(user) + 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 @@ -35,3 +36,27 @@ var/obj/structure/crematorium/C = parent if(istype(C)) C.cremate(user) + +//------------------------------- +// Mounted Flash +//------------------------------- +/datum/wifi/receiver/button/flasher/activate(mob/living/user) + ..() + var/obj/machinery/flasher/F = parent + if(istype(F)) + F.flash() + +//------------------------------- +// Holosign +//------------------------------- +/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() diff --git a/code/modules/wireless/interfaces.dm b/code/modules/wireless/interfaces.dm index 22dfa92e149..5afd379063a 100644 --- a/code/modules/wireless/interfaces.dm +++ b/code/modules/wireless/interfaces.dm @@ -1,10 +1,17 @@ +//------------------------------- +// Wifi +//------------------------------- /datum/wifi + var/obj/parent var/list/connected_devices -/datum/wifi/New() +/datum/wifi/New(var/obj/O) connected_devices = new() + 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) @@ -17,12 +24,14 @@ connected_devices -= device - +//------------------------------- +// Receiver +//------------------------------- /datum/wifi/receiver var/id -/datum/wifi/receiver/New(var/new_id) - ..() +/datum/wifi/receiver/New(var/new_id, var/obj/O) + ..(O) id = new_id wirelessProcess.add_device(src) @@ -31,11 +40,14 @@ return ..() +//------------------------------- +// Sender +//------------------------------- /datum/wifi/sender var/target -/datum/wifi/sender/New(var/new_target) - ..() +/datum/wifi/sender/New(var/new_target, var/obj/O) + ..(O) target = new_target send_connection_request() @@ -47,7 +59,9 @@ wirelessProcess.add_request(C) - +//------------------------------- +// Connection request +//------------------------------- /datum/connection_request var/datum/wifi/sender/source //wifi_sender object creating the request var/target //id tag of the target device to try to connect to @@ -57,6 +71,9 @@ target = receiver +//------------------------------- +// Wireless tool (temp) +//------------------------------- /obj/item/device/wireless_tool name = "wireless tool" desc = "Used for connecting machinery to controls for remote operation." From e7ec675befa0d65f8cddcc228461a8ce70dfa84c Mon Sep 17 00:00:00 2001 From: Loganbacca Date: Sun, 22 Nov 2015 21:56:25 +1300 Subject: [PATCH 06/10] Spawn sync helper See spawn_sync.dm for documentations --- baystation12.dme | 1 + code/_helpers/spawn_sync.dm | 71 +++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+) create mode 100644 code/_helpers/spawn_sync.dm diff --git a/baystation12.dme b/baystation12.dme index bae35c729bc..167a35e0c66 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" diff --git a/code/_helpers/spawn_sync.dm b/code/_helpers/spawn_sync.dm new file mode 100644 index 00000000000..875549a98f8 --- /dev/null +++ b/code/_helpers/spawn_sync.dm @@ -0,0 +1,71 @@ +//------------------------------- +/* 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) + //Open a sync counter before a spawn call + sync.open() + spawn() + //Utilise try/catch keywords here so the code continues even if an error occurs + try + //code to do stuff goes here + O.do_stuff() + catch + + //Close the sync counter + sync.close() + + //Call sync.finalize() after the last spawn call to finalize the syncronize process + sync.finalize() + + //Create a while loop to check if the sync is complete yet, it will return true once all the spawn threads have completed + while(sync.check()) + //Add a sleep call to delay each check + sleep(1) + + //Once all the threads have closed, or the failsafe has triggered, the code will continue +*/ +//------------------------------- +/datum/spawn_sync + var/count = 1 + var/safety = 100 //aprox 10 seconds + +//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 +/datum/spawn_sync/proc/reset() + count = 1 + safety = 100 + +//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() + safety-- + if(safety < 1) + count = 0 + +//Set failsafe check count in case you need more +/datum/spawn_sync/proc/set_safety_check(var/num) + safety = num From 012f82fe23cb40403803257fd6c6fe56459c8397 Mon Sep 17 00:00:00 2001 From: Loganbacca Date: Sun, 22 Nov 2015 22:11:01 +1300 Subject: [PATCH 07/10] Wireless control additions Mass drivers, doors (airlocks and blast doors), igniters and sparkers, and emitters. Sparker cleanup. --- code/game/machinery/buttons.dm | 57 +++++++++- code/game/machinery/doors/airlock.dm | 9 ++ code/game/machinery/doors/blast_door.dm | 15 ++- code/game/machinery/igniter.dm | 108 +++++++++++------- code/game/machinery/mass_driver.dm | 11 ++ code/modules/power/singularity/emitter.dm | 2 +- code/modules/wireless/devices.dm | 131 +++++++++++++++++++++- 7 files changed, 283 insertions(+), 50 deletions(-) diff --git a/code/game/machinery/buttons.dm b/code/game/machinery/buttons.dm index 381d2768bc5..c1f145182e8 100644 --- a/code/game/machinery/buttons.dm +++ b/code/game/machinery/buttons.dm @@ -5,6 +5,7 @@ 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 @@ -31,10 +32,13 @@ /obj/machinery/button/attack_hand(mob/living/user) ..() + activate(user) - if(!wifi_sender) +/obj/machinery/button/proc/activate(mob/living/user) + if(operating || !wifi_sender) return + operating = 1 use_power(5) if(_wifi_toggle) @@ -51,6 +55,7 @@ sleep(10) active = 0 update_icon() + operating = 0 /obj/machinery/button/update_icon() if(active) @@ -58,10 +63,54 @@ else icon_state = "launcherbtt" -/obj/machinery/button/toggle +/obj/machinery/button/alternate icon = 'icons/obj/power.dmi' icon_state = "light0" - _wifi_toggle = 1 -/obj/machinery/button/toggle/update_icon() +/obj/machinery/button/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 || !wifi_sender) + return + use_power(5) + active = 1 + update_icon() + sender.cycle() + active = 0 + update_icon() + +//------------------------------- +// Door Button +//------------------------------- +/obj/machinery/button/door + var/datum/wifi/sender/door/sender + +/obj/machinery/button/door/initialize() + ..() + sender = new(_wifi_id, src) + +/obj/machinery/button/door/activate(mob/living/user) + if(operating || !wifi_sender) + return + + operating = 1 + use_power(5) + active = !active + 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 dabe11853f1..3888686821a 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) @@ -1040,9 +1043,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/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/modules/power/singularity/emitter.dm b/code/modules/power/singularity/emitter.dm index 084f9ef55f2..53668d80de7 100644 --- a/code/modules/power/singularity/emitter.dm +++ b/code/modules/power/singularity/emitter.dm @@ -51,7 +51,7 @@ 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 index 96400eb9941..201fcde30cf 100644 --- a/code/modules/wireless/devices.dm +++ b/code/modules/wireless/devices.dm @@ -1,9 +1,58 @@ +//------------------------------- +// Doors +//------------------------------- +/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() + +/datum/wifi/sender/door/proc/open() + var/datum/spawn_sync/S = new() + + for(var/datum/wifi/receiver/button/door/D in connected_devices) + S.open() + spawn() + try + D.activate() + catch + + S.close() + S.finalize() + + while(S.check()) + sleep(1) + 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.open() + spawn() + try + D.deactivate() + catch + + S.close() + S.finalize() + + while(S.check()) + sleep(1) + return + + //------------------------------- // Buttons //------------------------------- /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 /datum/wifi/sender/button/proc/activate(mob/living/user) for(var/datum/wifi/receiver/button/B in connected_devices) @@ -23,7 +72,6 @@ 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 @@ -56,7 +104,86 @@ 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 +//------------------------------- +/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 +//------------------------------- +/datum/wifi/receiver/button/sparker/activate(mob/living/user) + ..() + var/obj/machinery/sparker/S = parent + if(istype(S)) + S.ignite() + +//------------------------------- +// Mass Driver Button +//------------------------------- +/datum/wifi/receiver/button/mass_driver/activate(mob/living/user) + ..() + var/obj/machinery/mass_driver/M = parent + if(istype(M)) + M.drive() + +//------------------------------- +// Mass Driver Process +//------------------------------- +/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.open() + spawn() + try + D.activate() + catch + + S.close() + S.finalize() + + //wait until they have all opened to continue + while(S.check()) + sleep(1) + + //tell all mass drivers to launch + for(var/datum/wifi/receiver/button/mass_driver/M in connected_devices) + if(istype(M)) + spawn() + M.activate() + + sleep(20) + + //tell all doors to close + S.reset() + for(var/datum/wifi/receiver/button/door/D in connected_devices) + S.open() + spawn() + try + D.deactivate() + catch + + S.close() + S.finalize() + + //wait until they have all closed to continue + while(S.check()) + sleep(1) + return From da7a4a78d8bc736cc6f489a1d70aeee23fef9f73 Mon Sep 17 00:00:00 2001 From: Loganbacca Date: Mon, 23 Nov 2015 21:46:29 +1300 Subject: [PATCH 08/10] Documentation and stuff Documentation, documentation, documentation. Safety checks. Reshuffled some code. Modified wifi senders and receivers to both use var/id, instead of two different vars for basically the same purpose. --- code/_helpers/spawn_sync.dm | 22 +++--- code/controllers/Processes/wireless.dm | 36 ++++++++-- code/game/machinery/buttons.dm | 14 ++-- code/modules/wireless/devices.dm | 62 ++++++++++------- code/modules/wireless/interfaces.dm | 92 ++++++++++++++++++-------- 5 files changed, 154 insertions(+), 72 deletions(-) diff --git a/code/_helpers/spawn_sync.dm b/code/_helpers/spawn_sync.dm index 875549a98f8..02be4334ca2 100644 --- a/code/_helpers/spawn_sync.dm +++ b/code/_helpers/spawn_sync.dm @@ -1,9 +1,10 @@ //------------------------------- -/* Spawn sync helper +/* + Spawn sync helper - Helps syncronize spawn()ing multiple processes in loops. + Helps syncronize spawn()ing multiple processes in loops. - Example for using this: + Example for using this: //Create new spawn_sync datum var/datum/spawn_sync/sync = new() @@ -12,24 +13,27 @@ //Open a sync counter before a spawn call sync.open() spawn() - //Utilise try/catch keywords here so the code continues even if an error occurs + //Utilise try/catch keywords here so the code continues even if an error occurs. try //code to do stuff goes here O.do_stuff() catch + //code here under catch is optional - //Close the sync counter + //Close a sync counter sync.close() - //Call sync.finalize() after the last spawn call to finalize the syncronize process + //Call sync.finalize() after the last spawn call to finalize the syncronize process. sync.finalize() - //Create a while loop to check if the sync is complete yet, it will return true once all the spawn threads have completed + //Create a while loop to check if the sync is complete yet, it will return true once all the spawn threads have + // completed. while(sync.check()) - //Add a sleep call to delay each check + //Add a sleep call to delay each check. sleep(1) - //Once all the threads have closed, or the failsafe has triggered, the code will continue + //Once all the threads have closed, or the failsafe has triggered, the code will continue. By default the failsafe + // is aprox 10 seconds. */ //------------------------------- /datum/spawn_sync diff --git a/code/controllers/Processes/wireless.dm b/code/controllers/Processes/wireless.dm index cc3338a198f..64d0920c67b 100644 --- a/code/controllers/Processes/wireless.dm +++ b/code/controllers/Processes/wireless.dm @@ -1,3 +1,15 @@ +//------------------------------- +/* + 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 @@ -16,29 +28,41 @@ var/datum/controller/process/wireless/wirelessProcess wirelessProcess = src /datum/controller/process/wireless/proc/add_device(var/datum/wifi/receiver/R) - receiver_list |= 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) - receiver_list -= R + if(receiver_list) + receiver_list -= R /datum/controller/process/wireless/proc/add_request(var/datum/connection_request/C) - pending_connections += C + if(pending_connections) + pending_connections += C + else + pending_connections = new() + pending_connections += C /datum/controller/process/wireless/doWork() //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) - return //quit here so the retry connection attempt is delayed + //quit here so the retry connection attempt is delayed one cycle + return - //process any connection request waiting to be retried + //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) /datum/controller/process/wireless/proc/process_queue(var/list/input_queue, var/list/output_queue) for(var/datum/connection_request/C in input_queue) var/target_found = 0 for(var/datum/wifi/receiver/R in receiver_list) - if(R.id == C.target) + if(R.id == C.id) var/datum/wifi/sender/S = C.source S.connect_device(R) R.connect_device(S) diff --git a/code/game/machinery/buttons.dm b/code/game/machinery/buttons.dm index c1f145182e8..d0d34422394 100644 --- a/code/game/machinery/buttons.dm +++ b/code/game/machinery/buttons.dm @@ -11,7 +11,7 @@ idle_power_usage = 2 active_power_usage = 4 var/_wifi_id - var/_wifi_toggle = 0 + var/_wifi_toggle = 0 //set this to 1 if you want the button to have both an on and off state, not just fire a single command var/datum/wifi/sender/button/wifi_sender /obj/machinery/button/initialize() @@ -26,16 +26,17 @@ 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) +/obj/machinery/button/attackby(obj/item/weapon/W, mob/user as mob) + return attack_hand(user) /obj/machinery/button/attack_hand(mob/living/user) ..() activate(user) /obj/machinery/button/proc/activate(mob/living/user) - if(operating || !wifi_sender) + if(operating || !istype(wifi_sender)) return operating = 1 @@ -63,6 +64,7 @@ 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" @@ -82,7 +84,7 @@ sender = new(_wifi_id, src) /obj/machinery/button/mass_driver/activate(mob/living/user) - if(active || !wifi_sender) + if(active || !istype(wifi_sender)) return use_power(5) active = 1 @@ -102,7 +104,7 @@ sender = new(_wifi_id, src) /obj/machinery/button/door/activate(mob/living/user) - if(operating || !wifi_sender) + if(operating || !istype(sender)) return operating = 1 diff --git a/code/modules/wireless/devices.dm b/code/modules/wireless/devices.dm index 201fcde30cf..f2c3d7862dc 100644 --- a/code/modules/wireless/devices.dm +++ b/code/modules/wireless/devices.dm @@ -1,16 +1,9 @@ //------------------------------- // 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/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() - /datum/wifi/sender/door/proc/open() var/datum/spawn_sync/S = new() @@ -45,15 +38,23 @@ sleep(1) 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() //------------------------------- // 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/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 - /datum/wifi/sender/button/proc/activate(mob/living/user) for(var/datum/wifi/receiver/button/B in connected_devices) B.activate(user) @@ -62,8 +63,14 @@ 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 + //------------------------------- // Emitter +// Activates/deactivates the parent emitter. //------------------------------- /datum/wifi/receiver/button/emitter/activate(mob/living/user) ..() @@ -78,6 +85,7 @@ //------------------------------- // Crematorium +// Triggers cremate() on the parent /crematorium. //------------------------------- /datum/wifi/receiver/button/crematorium/activate(mob/living/user) ..() @@ -87,6 +95,7 @@ //------------------------------- // Mounted Flash +// Triggers flash() on the parent /flasher. //------------------------------- /datum/wifi/receiver/button/flasher/activate(mob/living/user) ..() @@ -96,6 +105,7 @@ //------------------------------- // Holosign +// Turns the parent /holosign on/off. //------------------------------- /datum/wifi/receiver/button/holosign/activate(mob/living/user) ..() @@ -110,6 +120,7 @@ //------------------------------- // Igniter +// Turns the parent /igniter on/off. //------------------------------- /datum/wifi/receiver/button/igniter/activate(mob/living/user) ..() @@ -126,6 +137,7 @@ //------------------------------- // Sparker +// Triggers the parent /sparker to ignite(). //------------------------------- /datum/wifi/receiver/button/sparker/activate(mob/living/user) ..() @@ -134,16 +146,10 @@ S.ignite() //------------------------------- -// Mass Driver Button -//------------------------------- -/datum/wifi/receiver/button/mass_driver/activate(mob/living/user) - ..() - var/obj/machinery/mass_driver/M = parent - if(istype(M)) - M.drive() - -//------------------------------- -// Mass Driver Process +// 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() @@ -187,3 +193,9 @@ while(S.check()) sleep(1) 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 index 5afd379063a..920708925b5 100644 --- a/code/modules/wireless/interfaces.dm +++ b/code/modules/wireless/interfaces.dm @@ -1,12 +1,53 @@ +//------------------------------- +/* + 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/obj/O) +/datum/wifi/New(var/new_id, var/obj/O) connected_devices = new() + id = new_id if(istype(O)) parent = O @@ -18,44 +59,41 @@ return ..() /datum/wifi/proc/connect_device(var/datum/wifi/device) - connected_devices |= device + if(connected_devices) + connected_devices |= device + else + connected_devices = new() + connected_devices |= device /datum/wifi/proc/disconnect_device(var/datum/wifi/device) - connected_devices -= device - + if(connected_devices) + connected_devices -= device //------------------------------- // Receiver //------------------------------- -/datum/wifi/receiver - var/id - -/datum/wifi/receiver/New(var/new_id, var/obj/O) - ..(O) - id = new_id - wirelessProcess.add_device(src) +/datum/wifi/receiver/New() + ..() + if(wirelessProcess) + wirelessProcess.add_device(src) /datum/wifi/receiver/Destroy() - wirelessProcess.remove_device(src) + if(wirelessProcess) + wirelessProcess.remove_device(src) return ..() - //------------------------------- // Sender //------------------------------- -/datum/wifi/sender - var/target - -/datum/wifi/sender/New(var/new_target, var/obj/O) - ..(O) - target = new_target +/datum/wifi/sender/New() + ..() send_connection_request() /datum/wifi/sender/proc/set_target(var/new_target) - target = new_target + id = new_target /datum/wifi/sender/proc/send_connection_request() - var/datum/connection_request/C = new(src, target) + var/datum/connection_request/C = new(src, id) wirelessProcess.add_request(C) @@ -63,17 +101,18 @@ // Connection request //------------------------------- /datum/connection_request - var/datum/wifi/sender/source //wifi_sender object creating the request - var/target //id tag of the target device to try to connect to + 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) - source = sender - target = receiver - + if(istype(sender)) + source = sender + id = receiver //------------------------------- // Wireless tool (temp) //------------------------------- +/* /obj/item/device/wireless_tool name = "wireless tool" desc = "Used for connecting machinery to controls for remote operation." @@ -84,3 +123,4 @@ icon_state = "wirelesstool_on" else icon_state = "wirelesstool_off" +*/ \ No newline at end of file From d7602a2cd595854279bb4c1dc22722dac7dc6037 Mon Sep 17 00:00:00 2001 From: Loganbacca Date: Tue, 24 Nov 2015 22:53:21 +1300 Subject: [PATCH 09/10] Wireless and spawn_sync tweaks Modified spawn_sync to include the spawning code, thus simplifying it's usage. Modified spawn_sync to use actual time for failsafes. A couple of sensibility tweaks for the wireless controller. Cleaned up devices.dm to use new spawn_sync code. Suggestions courtesy of @mwerezak Added a changelog --- code/_helpers/spawn_sync.dm | 76 +++++++++------- code/controllers/Processes/wireless.dm | 20 ++--- code/modules/wireless/devices.dm | 115 ++++++++---------------- html/changelogs/Loganbacca-wireless.yml | 4 + 4 files changed, 98 insertions(+), 117 deletions(-) create mode 100644 html/changelogs/Loganbacca-wireless.yml diff --git a/code/_helpers/spawn_sync.dm b/code/_helpers/spawn_sync.dm index 02be4334ca2..581d18aa3cc 100644 --- a/code/_helpers/spawn_sync.dm +++ b/code/_helpers/spawn_sync.dm @@ -10,35 +10,25 @@ var/datum/spawn_sync/sync = new() for(var/obj/O in list) - //Open a sync counter before a spawn call - sync.open() - spawn() - //Utilise try/catch keywords here so the code continues even if an error occurs. - try - //code to do stuff goes here - O.do_stuff() - catch - //code here under catch is optional + //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) - //Close a sync counter - sync.close() + //Finally call wait_until_done() + sync.wait_until_done() - //Call sync.finalize() after the last spawn call to finalize the syncronize process. - sync.finalize() - - //Create a while loop to check if the sync is complete yet, it will return true once all the spawn threads have - // completed. - while(sync.check()) - //Add a sleep call to delay each check. - sleep(1) - - //Once all the threads have closed, or the failsafe has triggered, the code will continue. By default the failsafe - // is aprox 10 seconds. + //Once all the workers have completed, or the failsafe has triggered, the code will continue. By default the + // failsafe is 10 seconds. */ //------------------------------- /datum/spawn_sync var/count = 1 - var/safety = 100 //aprox 10 seconds + var/safety = 10 //how many seconds before the failsafe triggers and the helper stops waiting + var/timer + +/datum/spawn_sync/New(var/failsafe = 10) + ..() + safety = failsafe //Opens a thread counter /datum/spawn_sync/proc/open() @@ -51,11 +41,13 @@ //Finalizes the spawn sync by removing the original starting count /datum/spawn_sync/proc/finalize() close() + timer = TimeOfHour + safety * 10 //Resets the counter if you want to utilize the same datum multiple times -/datum/spawn_sync/proc/reset() +// Optional: pass the number of seconds you want for the failsafe +/datum/spawn_sync/proc/reset(var/failsafe = 10) count = 1 - safety = 100 + safety = failsafe //Check if all threads have returned // Returns 0 if not all threads have completed @@ -66,10 +58,34 @@ //Failsafe in case something breaks horribly /datum/spawn_sync/proc/safety_check() - safety-- - if(safety < 1) + if(TimeOfHour > timer) count = 0 -//Set failsafe check count in case you need more -/datum/spawn_sync/proc/set_safety_check(var/num) - safety = num +//Set failsafe check count in case you need more time +/datum/spawn_sync/proc/set_failsafe(var/failsafe) + safety = failsafe + +/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 timeout 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 index 64d0920c67b..5af6af5c3d3 100644 --- a/code/controllers/Processes/wireless.dm +++ b/code/controllers/Processes/wireless.dm @@ -46,20 +46,18 @@ var/datum/controller/process/wireless/wirelessProcess pending_connections += C /datum/controller/process/wireless/doWork() - //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) - //quit here so the retry connection attempt is delayed one cycle - return - //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) -/datum/controller/process/wireless/proc/process_queue(var/list/input_queue, var/list/output_queue) - for(var/datum/connection_request/C in input_queue) + //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) @@ -67,7 +65,7 @@ var/datum/controller/process/wireless/wirelessProcess S.connect_device(R) R.connect_device(S) target_found = 1 - input_queue -= C + process_conections -= C if(!target_found) - output_queue += C + unsuccesful_connections += C SCHECK diff --git a/code/modules/wireless/devices.dm b/code/modules/wireless/devices.dm index f2c3d7862dc..f3a56867a7c 100644 --- a/code/modules/wireless/devices.dm +++ b/code/modules/wireless/devices.dm @@ -1,53 +1,3 @@ -//------------------------------- -// 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.open() - spawn() - try - D.activate() - catch - - S.close() - S.finalize() - - while(S.check()) - sleep(1) - 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.open() - spawn() - try - D.deactivate() - catch - - S.close() - S.finalize() - - while(S.check()) - sleep(1) - 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() - //------------------------------- // Buttons // Sender: intended to be used by buttons, when the button is pressed it will call activate() on all connected /button @@ -68,6 +18,38 @@ /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. @@ -156,42 +138,23 @@ //tell all doors to open for(var/datum/wifi/receiver/button/door/D in connected_devices) - S.open() - spawn() - try - D.activate() - catch - - S.close() - S.finalize() - - //wait until they have all opened to continue - while(S.check()) - sleep(1) + 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) - if(istype(M)) - spawn() - M.activate() + spawn() + M.activate() sleep(20) //tell all doors to close S.reset() for(var/datum/wifi/receiver/button/door/D in connected_devices) - S.open() - spawn() - try - D.deactivate() - catch + S.start_worker(D, "deactivate") + S.wait_until_done() - S.close() - S.finalize() - - //wait until they have all closed to continue - while(S.check()) - sleep(1) return /datum/wifi/receiver/button/mass_driver/activate(mob/living/user) 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 From 3a14650dc1f2d94306c91040453cbf52c738f6a9 Mon Sep 17 00:00:00 2001 From: Loganbacca Date: Wed, 25 Nov 2015 20:46:34 +1300 Subject: [PATCH 10/10] Wireless controller cleanup Changed back to count based spawn_sync failsafe Moved toggle buttons to be a subtype Purged unfinished wireless tool (code and icon) --- code/_helpers/spawn_sync.dm | 27 ++++++-------- code/game/machinery/buttons.dm | 55 +++++++++++++++++----------- code/modules/wireless/interfaces.dm | 16 -------- icons/obj/device.dmi | Bin 24130 -> 24779 bytes 4 files changed, 45 insertions(+), 53 deletions(-) diff --git a/code/_helpers/spawn_sync.dm b/code/_helpers/spawn_sync.dm index 581d18aa3cc..91631d86712 100644 --- a/code/_helpers/spawn_sync.dm +++ b/code/_helpers/spawn_sync.dm @@ -18,17 +18,12 @@ sync.wait_until_done() //Once all the workers have completed, or the failsafe has triggered, the code will continue. By default the - // failsafe is 10 seconds. + // failsafe is roughly 10 seconds (100 checks). */ //------------------------------- /datum/spawn_sync var/count = 1 - var/safety = 10 //how many seconds before the failsafe triggers and the helper stops waiting - var/timer - -/datum/spawn_sync/New(var/failsafe = 10) - ..() - safety = failsafe + var/failsafe = 100 //how many checks before the failsafe triggers and the helper stops waiting //Opens a thread counter /datum/spawn_sync/proc/open() @@ -41,13 +36,12 @@ //Finalizes the spawn sync by removing the original starting count /datum/spawn_sync/proc/finalize() close() - timer = TimeOfHour + safety * 10 //Resets the counter if you want to utilize the same datum multiple times -// Optional: pass the number of seconds you want for the failsafe -/datum/spawn_sync/proc/reset(var/failsafe = 10) +// Optional: pass the number of checks you want for the failsafe +/datum/spawn_sync/proc/reset(var/safety = 100) count = 1 - safety = failsafe + failsafe = safety //Check if all threads have returned // Returns 0 if not all threads have completed @@ -58,12 +52,13 @@ //Failsafe in case something breaks horribly /datum/spawn_sync/proc/safety_check() - if(TimeOfHour > timer) + failsafe-- + if(failsafe < 1) count = 0 -//Set failsafe check count in case you need more time -/datum/spawn_sync/proc/set_failsafe(var/failsafe) - safety = failsafe +//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. @@ -85,7 +80,7 @@ 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 timeout has expired. + // completed, or the failsafe has expired. while(check()) //Add a sleep call to delay each check. sleep(1) diff --git a/code/game/machinery/buttons.dm b/code/game/machinery/buttons.dm index d0d34422394..a5475561a8b 100644 --- a/code/game/machinery/buttons.dm +++ b/code/game/machinery/buttons.dm @@ -11,7 +11,6 @@ idle_power_usage = 2 active_power_usage = 4 var/_wifi_id - var/_wifi_toggle = 0 //set this to 1 if you want the button to have both an on and off state, not just fire a single command var/datum/wifi/sender/button/wifi_sender /obj/machinery/button/initialize() @@ -40,22 +39,13 @@ return operating = 1 + active = 1 use_power(5) - - if(_wifi_toggle) - active = !active - if(active) - wifi_sender.activate(user) - else - wifi_sender.deactivate(user) - update_icon() - else - active = 1 - update_icon() - wifi_sender.activate(user) - sleep(10) - active = 0 - update_icon() + update_icon() + wifi_sender.activate(user) + sleep(10) + active = 0 + update_icon() operating = 0 /obj/machinery/button/update_icon() @@ -72,6 +62,29 @@ /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 @@ -86,8 +99,8 @@ /obj/machinery/button/mass_driver/activate(mob/living/user) if(active || !istype(wifi_sender)) return - use_power(5) active = 1 + use_power(5) update_icon() sender.cycle() active = 0 @@ -96,20 +109,20 @@ //------------------------------- // Door Button //------------------------------- -/obj/machinery/button/door +/obj/machinery/button/toggle/door var/datum/wifi/sender/door/sender -/obj/machinery/button/door/initialize() +/obj/machinery/button/toggle/door/initialize() ..() sender = new(_wifi_id, src) -/obj/machinery/button/door/activate(mob/living/user) +/obj/machinery/button/toggle/door/activate(mob/living/user) if(operating || !istype(sender)) return operating = 1 - use_power(5) active = !active + use_power(5) update_icon() if(!active) sender.open() diff --git a/code/modules/wireless/interfaces.dm b/code/modules/wireless/interfaces.dm index 920708925b5..0d80b84b1fa 100644 --- a/code/modules/wireless/interfaces.dm +++ b/code/modules/wireless/interfaces.dm @@ -108,19 +108,3 @@ if(istype(sender)) source = sender id = receiver - -//------------------------------- -// Wireless tool (temp) -//------------------------------- -/* -/obj/item/device/wireless_tool - name = "wireless tool" - desc = "Used for connecting machinery to controls for remote operation." - icon_state = "wirelesstool_off" - -/obj/item/device/wireless_tool/attack_self() - if(icon_state == "wirelesstool_off") - icon_state = "wirelesstool_on" - else - icon_state = "wirelesstool_off" -*/ \ No newline at end of file diff --git a/icons/obj/device.dmi b/icons/obj/device.dmi index 57d4baeb7e25256851b7f9d7479d5dbe0219cb3d..281ec1e62ba3827875dd1416e1bac56bc2a2cda0 100644 GIT binary patch literal 24779 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 Wf2&6^S~ddU>)|~e^+Hw4;Qs+y%hNjm literal 24130 zcmeFYXIN8h(>5BKAXP=A6R_b80@784&{2`z6oH_CfCvHUEkO`asoqFY#2_fWNbiKA zAWeEN0qHeB2&88(e9HTL@80`8zVA5p&;7%tkmXvFYvwxVJZI*5ZEUE=!79KC0)aU0 z+}1V)f#_O}|FE0@exrD))*A$(uMIN4@1yPH?eN^y)5jI&0Rjbl{A$(Xv3QOBmN@iu z<@ombgz7aXnV54iBGoASE9czVM%xqD7$-}iHx13k%g$d`WhskTNX)vz8LC2}?62b7 z56MTq^>pBO6&2755)!-aSa{L$pxoL}oF}imZRb*^dS<8Ki;l?jb!`doTBvNsSv$V9 zPYHR(TJdxI>btxzDp-!*P?@CsY(GLc-EQuEv#^9Ob3aeAJ=-!c-zFM=i-#r7V#u~C zY3`EA)H1!T3_jP(=CjUi(oA+rm%EgU{K(aZT*bH!mWwSeL}{t zoNX}kL3m_`ij3d-v+YN{j(w!Ei2S6bm#5!Wdr3=GO;^v3H0QQMG%q|1xb+apP}5zX zb-UqWnw0jJ?1RIqi_ATr8QjzhI&54&y7A86eOUf^?BlICFNbND(69Le^LzVg?Z3Je zU(HPVaKs3>z<%lP-t7pw_dSzjQQg=qk6VA*29N1g5a7AVd&eKfE13J-e#XPa_-C~p(XTH^8BL@_iv)k#EE#E9x&4owv?vvwe~k^l7<`-OjBjqvQKMyHIipGw(B8;2HJB z`Pp9v%syvrmE=t4Da1(A5gyx~i$e`nS{hP5M+qhs9bNv((v~ObFWnOJ6$geBA z9M6!_H!1N*dMI><0sQ>Os-@CYWM`fv$nf`b=ciw9pdZJzef)5J)g$CFKkv>fgVf0n zBg(MK-B>pCgl0{)`I)+}e`#v56l*%kx;Hy+JXPX%-XLzIJ{s89)r+2M} zuHg9@)2Jd4=%LOXZ4L8)sqKuD_Gc~Xt5KFI->%TFAoAI&r#YUe}!dipqI?(ML%J?eSw!s4vpc_v=x~ z&D>#}n*4ozE8_Y_i5e_4H|20?W#tU8f##LE7o>o{!$7g>C^)5Zvcfrdx0((F^0WYr zl|sQ7KH$S6CehXVEzo__N8pWA$g3c^utH9y+*?r>0yvhk%way%7Cn4Wa0KsEnBoX6luz9?CE zaX(F(9FLDsY%a7cmg^{O+`sEU3fcR?yFQX{*5P2N+_qbYV-URr>RS+}?`Q3Yc>QMN zIeV6GVNIK*X7IaddxCVpnCpMyXlwrgTGnvP2V-bDl#{vFI_N!f*&^Os7#8x zMt(~1SeXmPjM|0bEV#&LBwGqgn{V0&VORRTrnnA$dA%ny;qbcLjRbeN?$r4@j4)h^ zn&a~ui9RBTG}ypA>&l*JUuxsnOBzPwE^g4a+i`SQ6-()T63P`(GfuEk#=9RfyM-_z z;lzB@UZ0J(4Gm`dp#&wJE3n{6{b6(qGs|3C!AoY*NMz*^#ctQ2hd7(+8;bp57v^`; z39z6%12@{DOo@%bFf%#fbeLf)$J)0?HBV>y&bx%Hf2A%QAmMWLnyy>yL2I9*$(>@g zn}!PWhQzK_7&Gbu)jkeW&ml;T!=$?*vaUkS(kbm~)&W?>cnh^wviFA4K|jreHtybg zVVB|IgT{ce%TcrSzStI-GWy0obEXgNM)#mQ^KXI$p%_`_9luAl)7#AGJ@J)+)(C~& zW>T!oCFP-@3(vMf-#lfcmT&ULl}^GuLhANaoO{%0<=ir;mbV>{cSuR((<83GCS?aC zMe*BC`%7Kcd7V+5!W(7MZW&f%6n14Iu-s;6)bh6yO6YAqTZM5Q7`{;z^9a#P5PnE} z(Fs0MZsQOPI=Vew?XK^q*!BdWakOJTpZ=@sXX1`k6SWPQj09gppF|N}vUxA|bf%}R zC*x>onhBDob{#u_1?Z{yc33HrL5HTNJQr|5%DOUd#F$G^kG^vY0W+twh4;G>7pnHZ zMkzch&xU*B97Iz2wf|TXGXM1J=T43dshXL`AAL-kbmKC67}H@^i!fZmxzD*jVVoj7 z@bW0sFa4fWmESN^>~*B;?HQJIL9`PRF0gA|%euVyHtSI}8-A3~mppz1A*-td;>*-- zX^c=w;Gb!yKWkPDV?MLDWB&SijSN|GzP{nS()&Iew>>@Xven0v7}%Wjr5;ki`mHCP z{$@b>6zlG+KU%Tg9v^cq)*icE6c$eC)3`W{s8ICMo^f31;FS$5p{Z`Aev>@=&5$6u4(N=v~ z|5})S625{*QolG-Py^8!Mjt(UVLL&yb>8BApKi!IViE|!EiZniI?O)rk(!dxiyufU zb5PwQTA@)NUO}uw#bIPbu=X-obt9AlJAiz+caosvfWW+5cm(EBLYL?>&CDn8g@t?$ z&T|7fJ{Cb{0tl!K(#We9qNJC7dnP?XId!0 z-!@+uuq|cB(PWvUgzw>>Cn|TM>}_KD#;s!(GO}qa94|!a9v$JH=WlTdhLL-?em}-L zvb5UsoV;MzRrAtfLrCH0^$V1*rA^bzj7?{Rd30Q&7QdRw3`HpP>>_Snj3`00HGD$G|HY4@ zYQk9d6RE+Ma3u>(<}TzA)V>fSB1d5Ga9<);LJpic#hW+ zW{5xZY9I@To43kQR!a0NsGJ(~6L(OGcy0W9t%1L;?_S4$ujTKrWZyW@ZoV+5=r+d3 zlTg+!8>HYX0+~x&A^i4|m7XYfin&1jfub2^;66)0N)a^`zp}zNM%}TgbP{VNx;6o0-?IpZsWi@ltbJtk z0qqH2KVf}h-Zpb?1FHtZ+&;4KaTz3a)Y{G>?|FI28jK549yjeTbRcv4JhNdUQx9G+ z;c;ToCK3dPyK>F551#1i=BuEt2MQ`k(g>u`kPIu zB8Wg%6U@fk-dwIxDjv5hpE6n(*wyDnDz+J`Fq`PiC}}aB1p9*C$a`u=3Zum&EupJjSq58Z)7X6H?JQZ6&|b9t zyWOg=*LKmXTqP&xR9auz$DC+BH&C((I^sF28`9`8y+YT%tz>Brpvp}U;rS~z=__^1 z<{vN$TineRgV)K|FJ|CXTU;bhWe5KrHgd&GIUVf3#0ik|mwCm6iUh|HZqm#R^b4t& zRndi0u{hY)fj3LRE;gV1xx|PDlzBOn{BiCT$g+k-zP+}dXe#r7$W zl-Q~SA*dl6gN3QGXLp-e6D;zL49VyC$s1&H2w9Tb#w$(hzE9KpRp0e74(eGfL#6`A z;(T)m*-S+SfaB%WRY9#NLA0%o4joh%Z`K3jc;q%-IJvoW3A9^y#4ukEQc!Lilx9Ob zCG?d(>WJl6c+|!SY{z%9OY&S8#IAc_vO zV*xJz_#aj$L}}MQccY`j(X(*iE~ev;ezLawh~ag6JW>2mdEEj;-Z^9dg_VPrbYXLi zf!zWS{Ooqk{vwo`g=_lFYQ5JhFMxUBth5^@fX0WH#^XE-p(|iM~^-unqy_e$h+-GYw9aH z{9Y#*3qdfoIMcuJYNGRL9EFdL}2|0Zj_gJHKT>0LmQ^8?!w@=WP=pcfNT2RYH^tVS5vcMPf zzC=~!wR4Fbx2&WVP&{a4on3=6FG}{!6ZjQ8ZyuaNG&eS$2hZIg!sc|Z-DYq-!aWAQ zs7zaKtCKK`+?oddG(KSv%vmn_q z2O{+YU`>y`1`5`B{J;vTm@{4Jy2#b4O)+5-RSv3|3o1ke0VdHU_~(t!K&UzkC!UVv zy)A};$d{5n=%v4PrUCiL5>BM#dn=W;AuD0bNY(lM-ERzdyr(U21(GuVn=F3 z?S>BToui6WO2Iffj#}rv8bmzU$aX&xj=&xS=~*A?58+7%gSDG6{5P>7>(rIL^wgUt zE)GM}8^pT&**(Uk#|g7k0+#4KF5bT2oC+xbU6q#30UR!<i<1Y!O-#18b>TSA#4PlHRRs{>m+8{&A5_s7FQg}9B;Lg$b4q4V3)K6CE+ zU*IPfj6`U^V-8JfGI(1srVu21dnbc1o4Q)E>PVGc_%;*)KEQ0Ns_7x=-*9df+S z!z4BShmHO}K*N7Sg)3%cLg|qkn1VKU^6!Cp;e4lvFw}uPg$@9cd5I0hrq}~e8C2*(ds&Ee^{zRPi-X8}onK)|5zJg;LqkKaXsWmo zv)yUoFk8FO4j0dq8B$v34Cv&!WEmsnPior}tkx8G?y~RoQPDPx2jbS3D&hsQ^sYBv z(S7RO2L;#1_Y#CvER*=dXYk5yW3d7XF822l_+&Mc_%-vFWOxhqPIGW2K)*ec;u!B# zS>rf`jT8xi;@kQKV*M2EwvyZ2LWOtRVKopt!y?MZwZoNedJue-a-LjZRcc5&!sLEX zbTcI#Zhwu;bRiE953lbow09&@$~CWyl9$^Q)nZ*jKCK{3%tOZQf{O>Q{Fr8m+>aSo zKW)cM-WC6?$HWNn z>QVlh3{P0&kyL$#$9?lpYIS9fFcP3>vjCxg;wlQ@YuPW(#2L?`KI%l%glTu%xKMWy z3UL^oSr1m?1aBcEhqDhiJ~%@PNOdbwN~x(h?bkrtY2~_t<2`pyqWJ#d)+7UJm`7Au zlaj(D@^GPJb=M)=#h~gbUC*Z*&tEnNJ)y^KjK;)|&fQU-@jEF+_H@&fk$8s+Xp>ji z|D~p21LH>-zeke=!JMX=~}!Bjf}EtjM?Ac7lH^LBwN|-y>hUxTJ1mTHxg9p-Bk%?mu@k- zj9?&8Gi=e=e$+zl!U0eyNRsLVwM*N4!BEcfM1_QR2TNy>Yib0w#IKC+%OM!9MBOq< zq4m})9vtvU(bK1`b{)u%jf?icY@PRRIuat9Fl2$9q$Ou1^7EnbRZH#QX42nE;Axwu z?=L5(8*dkuB>hHP)E$WIMRq>b$AW9uT=JcMO+UEgG>&{=yyv!m>;4e;-d0=S$cv3G z$%unp!6%#MyfmU2r(9uT*1& z)SoOu`XSAXijR;Rhn60EI&eq)R-DO2i5udxU$mDFM~tU_piSx{H-dkT-`J74=?|f- z$H#wNK@i7M>(PPn3Tg)axGj^X)EOrIFL;Y$M1K+@lS$@*gnD-puXaYyby2@CBJ=HZxoM4uEp3mtjMrnFKVY}LbO`*~ zTKrcw6Af>aPl)S>NqiN{E2mryI?50Hx-y=9Q(MliO&K+S;z~WtbtlHH$>i5iSCZ^K zMrqEv#5>o5SU$W(oO$yUwXTkcIY*>DobWz#?~Cr056jy&uJ{G3buPVO)WMgWFY%7M z%wIJ&N+5>eUafboNeJ=)*r|lLLR8&SJta$m-k=7$tv2fx6K5C@ti(R?*87u{u1R~f zNt49k9Vm_2#~hU8l4es`LeX@DnM1J8VQraAle zw`oruq9ER^oeQ9EwQ7tD{-xl5@U;JZ_y+jff4ezRAPsid6uhd`-Pecv8KPE0I2{H8 zC0^X^YwnEa&)%jHoIfPlFM|8Ab$tl{c?`Q)1d^ARS5%SAbotcp4rdg-;!O_SlALm~ z^2<^zRLgb-gSTFwZ4Wlb`_XxQpH!!l?!vu)3^gGTL7`(;rQWV?!nO3K$iqfwjhI$W zAZF;U2khEQNlC@w(Pl=fmqIW2G@y&0ZO&K)`>hLcMT3;7w`PrzQE1f#e;Vaz*eEF5 z^8S67dp+zLxs;VRP8~%L4mvr7&jdAtYcN>>lGl{+i>&G%1y!u^5_xeC?SVNf6Uy$FBJ?vx=!gJLA(Lo4c6-&_&GG;z1R=1$)}mj)REE_)}6X& z>R`R%X_`YbL3cyx{xx?2O16IL-~9S16GZYD>#M@ladg0imDS05U&;0UBu}Q44M05_ zD^S1!hc#5!J))JEt_8@1J zyqwudFsGaJYP?*QQ8lvrP*U{M#Jbx6c7H#XFf;dR@Afc_;`l(5=Y9dni~U-Dmz?HL z!CMb>Sxy%ZMC;D3lZ?G%IXd2pnr?>B@qOFfOnL zZPwOYPE`{rE%E-znf#XXgy@8T^uu#fSKo`27-8)0=a6NYM6Dn}U%3y;#)==R*hSX$ zLrs0Xc3-JWoXq|Ev%JQaDU6Q7#>_mD;F}~18(p1SXy<|yC9CA-d&#{ri$hIQo*X<) zNRfdWA`#R Y1a=9ld=^*>N>EvM|g{+)prcFGn?SzfDqhds|-FTBt3_GS=3G(s2a__ z9C3?y-~JR+oA|ejBAQ<*??zMDtDGYl5hu@wl%S}@%mi6Y9?M+nvcIr5 z7&RtW|JIit^gDN!Zx-$ko4xDLv8F#cIXRN=peG?Aku_Rq8A%~^A_Kz5iXUi6S{7v@ z{oGGpf8n?}R@~^lapybgXuGDJh(XGCnjlcTb}XARV}^76=N@KCa^$6<>QpKTrp{_K&44?cfNFkSV5L+U=1&OrTF?J7*Vf9wr2jxcxWy zqM#9kQaK{6r%WWg94SoSUqxXu%sT z!Z<<4m-u$_1jUQVzSIspU}#2KDF$8gh+J=S8T!(~Y7A9%C2sH-4J+pim+CONKyEaj zl6Y@sN?^)zy_CPSch9>QYD(zZWstYQ?Lk&P1$Pd(m8ywR6v-35vOJY+AFXIrF<%Lg zdEtH(rPwiws#*lQ@mGflq(LfGV+5@5Qxd$6kpPzg>9 zIdiv#?e;>F=ku(*>W}Qgqwp*%XsAVuUtKV!X*{U_uC^jxuX=-goiWas=p|(kNRaF# ztBb4*Q{HP!s&8U1Z6s9DS56x#k-W~_J45=BKRV}y*{kd|a90#~$8+)3)Q1kMFE7Ct z!Eul^-Q2T&8h)da&Aw+On@y>_cA37MmLZKYH*C;v&xyadXie9o=623toVJJM(er;D zRY>+iGCDXdcEoio8hY@1956SP-0nH~{L19}0h4X#Ln3jXpq=C?p8jgtbQGpK=+xa~ z`vu>}4py^ScVfxk!Q^2)pF2>-SNhhi1trw!AZT+z@}3YwuJ8oyNkMvzld zuOaDQgC^zve#sq3eS1MK==%>eZra9TV=p`z&959Ka%1Ry4`^n`$i?|2_2oB9^|u~u zPm067Ut;njAmcnYZ{$*V&z_ywNZ7o{M~G7;zYV`VCe>w5tlNVs1d7H9E5};`dN%E- zZQX4R=AI%*2SVAX-6zxeZjV_^rl5BsxYp83Hpnr)2<3E&ugufiIx01T3%|Lk=^8Pt zdCN|zgnk!xW|>Z&foV*mg924~z5fmotK*$3P($#NtSmnPM)Y)h6(a>{oj8OtXT~E1 zss%A6mv_ZRD|M)ES6|l&FDucWu)Y$8gfXR*Dvt=!-_y%VGrD-%XQVy5>uaj%A|Fg= z31W4zfn=79Oz?e>@WKnoDhvK1)KTWmQjOh{ zqUG>2UHH-0Gd6Melt0gJg1qJBmk9~r@uG3M70Uw0bm$B_vD9LC%^Dq~bZ4GWOaEr| z$%x0^1(2C2%k2Cr$!7nxXoFI(ByQp}x1tcu$)|=?Eavj5LwRt8b$}VnpnvdP0E_*B z_14bc(F29cMQo(2Hq0W~KX>|v)}bE%2xB#eO)e3bInKK=6`{ncRZ)c1u$2iUSA432m*jDm9Zqk{?_?alxyAB9f;&=(pvq@VqQY{$o|3-R$DD^O%a86sOx@} zBezuIZ?thi$h#^F3HRxq<93OHuq@ERw!_WmOGX{vw3so83xRTE zpb~Kh#WOeYNeyNBeYrdMmXCL`H+eW$G47nOq+#te&mY78LVRaU6!#+vj5Idha)1M> zk+S$U=K|$os5Zh<{^tElNcfoG?$Mlzy0D6fs6nBY_e!H{tjxpe1ma<_x{gjm+@53! zK2o~wiY=V0C3Ctf#Fp!p*+*vXJI+1GMxf2S?ox%FWJ5yf^d z;RrLHpyUB0HZJg<(zcZKeK}Pd@<3+SuVjhfy*4{Nf+Fu53 zhwCMDp>gHqW}Wfo=H}W-&;YTJ#f)_Q&9S1-Jd&0JMh(@BA6v?NH!an^@Kg&*r_q46 z?dKc=0W+u2xlt83=O(SpTSnsQ)i1&-e#RuuTo?QW)%CT(*q6tVvxVVpg?`KgYom_R z?`eo`R^{a>srw)sV*4-i*Mv?>s;ez%nhT}cP5fL7UJgWw;Dn8PS@xb0bQwHi_sdC# zJ5soiL38ny370I|B_y$hFrM?8JWza~}RXCxz2s0Nc zA#*=(tEy|4xlkLZiTyl@niTJ_Xr>iFXpUIH#byV4Lws?GIi9N1$ggB|dWH4khmL8R z7Z}yr+-&4b`?JI^E z;`=(&&u>$DtZAk^i7@7tlc>?;t4P#Cir+KDa=2pVii3fMWa72^lBYJYI>TYLqYz@w#Twsvzlwa126ql(PoZzbLeYeh)_j-Zr}L8g+V9g7ip{5qDs%~7uq41s9!p#*6JuP z*(Wy>xb4*oj3q!SPo4Cxo%n(qJ%*!QF?fDzDc8KA?gW^E!;ee-^3ye)@-6~ zAZ^eKgrO6u$C{;{z$Z%nY((~)0_U@xMIzpaFf7TRb;wu${ugEMvFeGR&VjlQ>GmIF z8bNMIiUCnqLzGF}Qg&!=QMKNx=Vo+h`Z;=i&LLw}ONha91bhO2i6F%bS6Q#Ib2NN3 z#}z_#YKKaIHtj|d-}rfCxBn0)D?6Oyx!uzvjYXR9aoWX8Cf1+>J6%FxL&&6q)tD7a z8%tV6|J+h3DBmAekap;H|L8UMsT?qVlOK4*B? zD4IfQkJc~Y4l(5FH`x58|MSnPv|D(Fz?$xd_s2Iu{s3|=xfK>wY{kwN%CY-hra5y< z*5S9^i&~Ep?)&~kl4kdgXPU>x+nwUXjTEj;Nr{^VZ0I8qsU_5%kw4EX-rXnr?|BTo zrV6nFwSF#?88uP4o$5BKP+x7U;$WA=Xc;D%7yvvD2eV!c{TN+ys1~!*gl&*@4EVUP zjy5dA-lCQ7&BflF9D}ByU&mcH0m`#*e1_=nez8ju$`I%+2=m#ega%S@w6IDu;ojE* zf3?di|OZd*@ zhs~#Am_Z;bt1v&-$DwLKFNqG+QuRM%#sBEK{AX$P-Fwi-8xB#`9+aS`lUYn0eAkVM z!n%K~1~Q>E;vU`4A-#$UMIOmxz(01KA2FL%z4`U?=x&SHy}Lw|>}(A4BYyi(k6k@QH56X_N~XNAQ%l+EMkgfHde~*K?DtE=3(!Ar%h=VMWvbtg>dk}t zq#HetdsN8Evq4ejvaM@dT7zQ@#29`!A2c zK&l0jz`?q=-aWX@2|Ca#{=r>FHVu$t>+~l@Mh7}zNnS~k+bqJ%p$YdOnh@XP#?#}u zO~Y!@M4a`Re5J4oWbiFyoC95Lb1-i~qy6V{o6-+CjRIcb@YDF~S6T9IZUN-O-S6%gR1q%W@C)06P4EWp`f`FFb7s*q#Pt&_ z>iq5h0UD;vLJTj#RJpYpKg0sXKNn)IxKRl)<#l-wNONFS-_(g;I2HQMZ}#Nzx^f+0 z_dXfRoW!71$iLH&>?quMtS3wlxHX$o-7EOL%Yxd(pY6E+$DKg!IT!L6^nv>wruZC( z<}*Fwv6GL3pBw0O{q!GNQF+?gpb2b=6aVl(z*=O)9WI?=`j@Luq-p@=9nVXD=-L0R zm-pY<+5bx%{wXMhU1V>0FaN}4bF4HsMap_0>;=PDw3yJpUbNqNHtcGsg33d)vj4EX z0JH?YxMf`TM%L~QO*!YGztqI@R`|Dpc=Yq|JN!f+0s|{pmj_3j3c`V@EV$HcS>QA_HhW-yl0ZqT!KX5U7@p6J;#M(#=`R z2{c81v!FbaGq=Vs3?uJ;2780vbe970oJUUwD$!D)cAaG`w~J9E+wz31v@GsWH$Zp$Xug&cmFdqg^>tN+sp-tmJ-{BA-PCF(Lct--Zid53+YaJmkVRDSUpy? z$xRri29{I365bh4x5FoA?&``0csn=e-{GhnZ7){@q&@Cpuy`v7)O~hLw}0FDQMVn< zJO~9eii8-B8>ShY8p>dXmym-yH{tg)b2$I$&Je2&wVKDh7Kn0e8E*e(GgFrQR0FL@kmHAIVBAG2kvw)qCWTDe&B z@n87qf%pz`oE%;qe}g~Y^S_dICA*OGF#3OQsf2nqpJk5sCa5(fWJKQV_BILJnirtc~ewo5$!J)gEP)C1uHuh~j z&5xbGpz>bS@tPZ1pjcXsXOa2M)VK8-*|^1vz_jU=h*|<8-yqY6wY`>FhHIw0SK{gZ zsE~GKFEx#RAn0w~^@WRkSzKc!Ds+6!TU&Xlk$)Tr(V@f%yncU@@c-lA{}=dY`0={z zZ#keeNH?72Qx)Ne9WNe_c*BbJ-zK(~&h<}czE>eCJnyBA%}CJ=kGZ4Q0U{b97EC#2 z|KkuIaxMFL!|zexV@;*=P4|Av_%^1Y`v^S2K!5GUE}PZKZn&x(g(Wm>*9^s-)eQh- z0_fg6TjC$#Vh>X$_;vJ7XA9PZTBx-AK}!9C(3NpI8gtZ0XgB@ zw>i2Fm|wodumP#Nu~lb^`&||FtAr?;GmINvDEUAQQA4zVA^s$Mi)n;hBkk+d7rtpo zC%tE9;%8H(Mq`fhBtZXz4IGvL?ND;=zp{Ia`!w<%LLvFp^c({Ct^0UQ5snKNl!Z&} zatIsK<#PVggd9=>wJ8R9K%}LDDQ4>w>55KJ(gNShZd^2koI2==_@eqKg!+6A@dtnR zyiYwoCIBV>Bmkm$NK%7Vzi{ip~g|n|Uk$*5#L)>Mm z(#!h17@ryF*GE~chc$53s%L}YC)kCqG;Me`Kz9LL6@+k=?-#YKBPg5Dc+};+;frrQ zH#m2IPk^j-o_yJ$JG+Bp|3GZ;+oNr6*48xoL_x|i5wQw zyH%jOZu!%m`6n}xGB3)1&Rk-E!vF0BR)SD~yzXeZZSMgIQ%_-4>SUUR3f zP1h$dkex7gkoJWm46P6XDso~bjWY1JO3m}4U=GZ_1CAK zEwXh76m$o;jF;H{>^#WyC$k+>_J(novaB#eh}%xes~(_aW71N&cC#|Vo|@Hm#E?@_ zQ6Y>q0)WN7-lIvxbR6oV?1_Ir*%Yiins3&bcWyf;J0FcaqQ*9TOP17^QqD9G>{K${ z6PWi$LUD)8C?W=xyx|rz%4jdwCg#YY@xZR(^lZw4gd!(oa8+6Y!m0L2y#8Lh1>&_3 zn}UBRyU_JSU9=B(9JDs^Ng8n}}GCBFd*3kULXF!lxh{^clMy;4XE}c)QI+2BQh zD;-wLq<%OYY_8Z+Py(((y?%j`?FWlX?!+oYs#uJbJsk^Egi{h*M-t7ljgGymzZl|0WPz_|jPJGfPNv16!TE^gS8@e|h9k*u@DpBkf#LLM}2y5#P zvbTKJPFa-NsTQ1yH787ZUEKu&RX!?&5SgshT+vePF}pDl%=#y^WKJz~YEedia;VQ= z{e5r1m?!hS2@}SoBbey`DXFh%#NLxCgMoUWn?ozT7CUNxffUiywTUtZD&h05w#9+! zB*#Pfv5I*|5E}G1MW}VBk1j`G*KQdB-srOofy=L2$Zlwrr6MZ zkZkp|$c%LeTez}G1-KbrQUlw;Q*^%4GAN|UFes>gd-HqE4Y>=XtBsD>9yPX+Ei12c znUkfzul{r-0yqq)N$8J53?smlaOb_tZ4TUZg%~O}4X^eJKMKiTsfj}8o8^W}ry}1P z9gvc#u3lvY>_yHCT|R|}%qnb#Igp75VdZGV=7U6!|9V`1s^+sPg)8$fKWal7V^vkE%Kl;_uD_C6%f%UkY;y?+HrlwP7 zIG%1Fo(uyoyhbOodKrRgnm0fGnGi1?q|aCwUY^OGz$uNySqMy+p<0kt6t>bW|@A)z^7P4{NU*p2DcF3?Z_@{qO-n+p}^?{?~q zL-+!HLIZ@GaM}hKT?Ns96}tilU$d5zlRE{pA;LgcNn(6%i9fK)$3HOM;SHCg0w)&# z`(*6@C}O&8K%D$?+UasmtkT~O2TP%l#nj)<|C(j>kAc=kB6FRZIU4}sG7k*@hL6aH zg0gj+^dCIX=7omDTn@f)K$7>NNxdN}hHj#b-lEj2P%%`hb3S%_ghFVfQbybncWY?$ zNN67swbvo&GwVCPPK`;%V;YX^Xti6r>oaxoqj@F~9w>@mz0PxzLoUkZ>JT6$?jS0X zV(XiavJ#`YFVAGr%(krRs3e|JMCdF^`zzu#RphD(p1MgTddyKbC2?rqaof97q6qKs&PBP!#xR$0({R<0;Kun z#yI*}&zt9X;Fyf^4r&!WxVU=z8Y4xJHc>V!gCOtG1mtmE4j0~iJIzQ8CX!_&_n~j+u~Bq|!-?$3&1f2nlz&NT)1?30eifG>x`-Zb{3=ieGwRD6P2e{5d_ z<`HfbKdh1d{oL^MFU)O_T)HxBETUNL_>fvVhoBN4N>e(gUj=gu6CPsO@%Q(H7*9{i zBA9j?ABewkhNaaM3Rhd(Sw>NH=QkT~xi0X|BOa8~G?O5DFA$|q5k`w&cq7PvpZfcV zdZb_&B>QviFLY@TX}rDkJ8B#pUQQN*hYYc`FP>)}aEiNi(^1G8L1j+>C7bf4XM4I z1dZZFDW08WxN+kWwC$tN`ZR=WiCO~v?CRf(7ERDUa(?$xG+|i!8A6V*-y=h5?OZHh zNP>KyOaLJ5OR}WZ->h3jE#2l!SK17HqTjf3>LGYt;;9@}>m$!Nzj@5o|De_wqU^!Ld z;O_@NDPdVun*}(nT=@_4Z-PVWk>>at40z*rxDa-Ea;=%z+B@KM*uW2e_+Pk7_6i2s zLAVY6*`gR;h{kMvoXILe6F54%hWScbl{|ocA^A%jV1_BYR}K)Kr+qi;hnuEpzqx!6 zR*}>nUf`V9ycIOA?P(G~; z-3g#rY^t-^uHvSSzq{{4T1!2i!TJ4N*N_L`z5Q`6Ho z@BC?L0|@{@3rV zmBmpe4cg8Wq;g2|8z$-3@$4Julx)a#n<8!2zfRxAZH^nhYt zK6DW_l8r@{vZk%AU3uTGJ6?aLz4of*w-{vA!bxh;2&Xo?G@oyy{pq9YvAwf@k zbNV)`2a`&vak_J)aM@>YcZa7#V$VzjrLz#-dhPPI_4#(~mn&rkLC{Ceb$WXG^HZb8# zhaCb&oiPrb=U>s?PSlDnT&Kkk@uXVqS(yl_J)VG&F4M{%t1-t_sr)S|#|aiWclPY1 zuaFhc(h0vDk$_usIR=6^VV#^eJ?BAtjdW)O(56&n@Mi{XQ_9RdHXvI!ZcQDxK@|7< zDM-&st}%y*j`n|xi6{jI#Obh(k)qLPD|f8^6US4wDpC=;PR?%JmkkfDH|>~6uinvt z+zthb1y1axwT=0*OCEY9wb!53{+5g8GV1?OJFphdu3OnjH76`?&@VsLf0-Egsfg=# z6;hDbP3vMstI2k$O`Y8KT+?&|gXeshY`;*{(9Z?a5tSfqt{=fW4gF=O6i{dzU$&cG z%9UR-IAuSP9+V*9Pjc^7PPAA`yZIXC&(C}4c`rMC0<)_5jUUDT>PSo&yp#=gei5D) zxK-8}3o0?mj)H)auN5B@J%ly%?cFuDT#Kat%$m+ubM2BMY#y z>VJL^-58`|az6!2dj^pM7AHibGh;B7?78O38CJei}QHGu6)bbsviAT+Sud~JHOm_me1kg^bR&-(Mea~UP3#G zM~r93@$VPfqXnCR87!;CQ|5zL5J!E=)V;!~DnNByzH-~(rbqWkiejG*UcV)XJ-)Pp zJ5^x!q&R!@DJwf6HF9k-v^ztekn{N&Wbr<4URXr5slJV9h{1Zn) zM@3^&Ps-O{27lG|7{5%+`-C@TaPxPlb=y*kYp8duh4F?S%|6M8yYaeQL$ryv<~4Jl z1TaA;ykO=$xtHhjaH>Xa+zAuK?Yp<3??6h{-OnODF-)PwSk2)q(ie)n~sF=bd3X)hD zSUj~P1}16o=E*NDFS~9}*BpH(DpxnZx~j^kN}zfnMBkrIFzVU0bc)z|NQ)OXSa9wT zhn!mBv*sqqpxIpqU4A|!oP~0y`leVw1^jw*LeW*K8PGM0uUis#SkPD$_ zFF-A^Xfz1Ya*@q(QCx`h+t^4^T7`Kebr;*Wiu#C@#v4!P%rE=+rEJJXe(w1wQDy4mKotu4ovu}$BAzjaAaa2n zE4=@(=`&g;_gi7%aQruk&Erwy;a59G^EO%vaB@K3$Tz(r59&cKgui_-TQ7L)Aovzo z52u{J#0)gQ?qZx4V~t#2McIJcj_0NG7(0oe|B=l9NhSZzC#T-@8YH;X8H`d`nE{cc z4@LLhD+Yi);pt@M8=idKxD5rj!}RLeU%hvLk*P4gwFz0Xp073uE{xzn$D~6t)<6|V z^xY%HX45Rmu}W4^w$g}Yi48R7m)xQr(0gO(N!f$7J3X!J+86X~24A*Y*~Q2M=UPxj zZ!l!+uWTfj(5{sUGna^5xKrO0N2P<_F8Z(m|C`t$*ZAzgUMkQozV=A7#UMa0P9Spn zQJv5nTmR3l^A^GLk1F7c# zl=pz;Km@Bm?!XrpqR@9-gYKr5dMafg;2a;YV?(uvEZ4~*L2F_c4j40T`ew~G_;agb z*_&3`rp(zjIZ?pLrfFgbP~3|+!9kwxZNN0^y90&A19`%BO}>wl8&z(`Nb;`vEuWh3T3Yel;N zhwcv&5ajkWk3%Zy;7)>w?x~!|EIX4GohhpeqRR-w$V^3Y8o(}}8b*p@B4N{W?Vi$U z=#h%pPpQPZZ;1Jpst*O*^i-Sv(NQ_uCaR%3?T221t_>iJTcm#ic{}wEqV9fMLVMrc zFNtSg+(*G@U!wUqQNU3iMn&g{auGcvMOB0DmYnLSU$DTEYd+>vqCS?7#%+^U&-jC1w^_&i6Vx_i*Uj(8omXZsJt@R${`5+@`u_m<%j^7+J7~zCS8`Qej zfTo-fIHDL-i4p#x-~iYtweU{bLX~~v%e4{h+z;|zwF8j+90ddcZuQuv>T}Tv`c7$U z-w)cawwE^wi_g-H@Svm6P?BveE>fZ5|Nc`=feLMH7RX-xjX?n8V-_cz%&e`pXsUY%| z4R!o$YZ!4#O@*b}IK$X51}lMek=D0hyTWs%S<_Z=cjlK^oLJyLtGq_Q;+;@p9^iudWz!DxgVbe|d+#ejWQ3${SG9&p$m^7EftDh@ETc*k>MGR2w9vDqdf5 z^qu1L!sD9d;^shb{)@J-9KrGYrn)iz6;1vqY7F|>!y7d3AGcuHShv_hPK;r)5c|+8 z{-}+jeZimQ6(i7GJ#W$?Mfk^TkY{lPu0O?Vo%ASk8BIc~H0B3or92}CNvQoGlz1cP zDz;OUbvVR^98PZ}?`Xd$z*G_oFbByBozFp?F2ZE=^}LjVme^Pud(-hfvQB+R8%WuG z?1NSC9{1d38_@oxbWoa6s@nU2r86%}Mc?%&E4_aBAK-+D{L!fj>lEx7e1@B@*N zzrwx~taW$K!G8zvmg;k+43ai|ctF}cpSrDZ$!@Tl!K;m5E{a)Bx0^o7R~MO9A03Bl z2})cvIUu-G`biehjPUKb&*k(|V1MYz-$kOH_CSM3ASHD+Wm#R{l@`A9%;mP7m+M(n z9ou)+yq0t7WT6EI9oCF1Q$@DBIsu_Q*=-z|_JmW~= z-Wkl^UJ!bFb^fg6)q%3=?<^c-)&tLFrLPis~J6T z(>Z)r&VhqtS1Rih$UFEX^b9#gOvCQ;AvwRGIlmEoy7HEe$@Id4mc zM+>@7acTm$ydVxvm&8+VmtyAKimJ|AG8eJjV$aH*#;k;@a%hh)6tsJ$b{RdXe2SfA zMgz+Zm7il;nA#L{*HOq{@~eI$`uEYb45JUpMyYbCpr`rg9M7&XKTXqiPdR4UMikv} zUnMmHe;C&7PY(#MZ+gbG>kMZUgrKf=`D_b0b<#kv-yS9w4q%bM?XS9Q(Am=D4@w7i zI-mDmgm-o3XXEBq^4L|wmQ5?FZSU&IdO@%52`fc(tsWHZ)2}Zi}(J@1C()*#@x=OTL;oI zy4DfpOiND~?WYn~2ER%XhXBIs!|lWiE{w-C3VCk?B!z|}!Q{1mB9HE|)5wj^w(;+N zn5!e(Zh0P$RpAx_=vMrsP`|d*R;YA@|0N`fK6%E{RM& zJzj2rhMlO4JlGAB`Yc_Zd4%|uLrcpCOCAnzcEzY&xHx|>mnN~-9nJG&P_ffhTcIDC zt=uOT^;fg_q+Tg`Z9+m!_?<1%;;2o-gqEkdy;tNnv%imAd*aA1jz9;v377?A2REmi zN5oJwG&_LxpB9ciA`xEwW?lAYnt7f?#EYOjsNTg_4*|n{L@6_h`1$3JquWN9}eSn)rIM(P_A2mRMHSq0q_oTDB{xxzpWM zZY2v?sltipt7ytw#{~%c&0|H_F?zkON{dhlZDA_{pwQ(;C~c<^%Q_3&EO0g;5|dIQbFXRQEJAq;m;ydW>)&HH=S z9`AY7D@J!VMbpTWC-ZzoEd+ii395Kd4|jkP0v-{KdtL&UwFHt6K#`(4u!ggYXZYZV zVxvJQ1D@JY>gCi~MhS0kS94Nm<;tGKL~hXvt^>QWwYQIv90IokCXx*3is+%2A$!wO zS=IGs^$sx7Bd*}mp<3M7dERuIr6}v-I`vD*8DtMvuf#wGFaSV6r~QeA7pK^5wTtT1 z(=Jq#cFde+>Dkhav@I6 zDeiU-Dv5IIBHy#Wt^$#qx}<=l-*$7@kPvTMzf1+E2H*rw@89O1EoPC9wovk&(O~TG z#2!2k$(L)v>cO}7V$luPhZmma#j~ULP-i66t1;h*&|Ft6mO`BrJe~_-jPU<4+`igmekssL$;W>WpIz^^;6Zs^|Z}G0V*m=E8h0s*n zWSkfd)k?iF&}BI|0;Yn$hH{+U$xpi&fXE54cU+f_O$wpcy7Y=1Nyc+qJ4OY3W*5+S z6tk1^bx;vwS{3dz@gGl?FcbJpkvhv1E5NW%s65NtuD2427E}wptoFq3B=y>+x-Ik_ z^}{Dx;Hc|E^S!SF0A17c_mb8G1PoO61`FMNqT_k=Z}>AyrrpI9Dn*PGOk61%TX@Uy37U{~Ap+x;fXhaY~_ zEqk^wJV{F~SfO0sVkf~mlITaDc`(Fn|K>X==@G*RjdUl&H&pGhn!Y}3;!I$)0%9=} zx{_OALmQVi7rC<{Ao0a~zdVF6Rvz;G(1d-ieq{OCA*%3PJ~G-cxkGl7>vtdi#atrU z@b?6-h-QH6DxOtD?c1lgG#6_$@=0te&EM}$44xYZwGj)}?TA``-m9TKqB*>u&hY28 z>uT<`1T?w_x#tkla;sE*LsJ${d@5G=gkPlfU)Yip{a1@je+;^efZnT+E zog|T%w-AnwZ+^Axl;|-Y-=@nfWaW4?Z+P$w`6QjA7xIh8iJNG$5q*`;o&4HyS7O5{dbd68(~klfVF{(!sjLnEpW9@IRNmkHjS`t?NsnnVKCJp{b3dAl4R zx1WJ9Dc1JrGmt-c|35yat*#!wH~vQRfD7NqJNy8Wl1VOhksl$&Tn!j-em7}b@%l=1 z1n+r{iedNYfP0iT@>cPkdi0*0gQgHiRj|2lGf+yhRZ4_DhgMiAWIT8bRI=p*AV_FQ zIZnJF%kqtWTDz{wwL>)o^_eQa^JzW1Qeoyg>7jTXs9^FxNEx@jyok-MWOA#*IP@qk z0U+PmE}S?+(|j?aoET_ugFWX;n`tE^(v={^`}K#EWLwl+9NtsoiGH5zf8c9U8F)0C zxSuMUt;1%=S#R-dS?!rCr~h%jZZd2;3QTE89iDk#uBhAC-2d77GE1=GUCM;NB1a!2R0EahV72h14p> zEC0Yte@41kE_w28HcEnwGOsc*y}!f$u-V+_(of|nN5QA9)QpeiXpEK>X;#@boZ?5V z;_qA#JDZ%3P(BO*c|%cSuLmza*+mo8bp({~nFse@syD9Oqxt7fFw z>6OY?{nsJzm5^S=OT9soNua2Fz5S=r*e!{5@M{7zwHGTfx{aBO~yD-8c4v_KwN6-=(4s#;{u!ENLBsOQYKY|Qvxi4X1`=P zp-Q^X5k*~!jOf0T0gG_#+c_yu7u3X^dqj1Qe5pviMJ{8Ei?oU34tMj=3YIz5kv!+m z-&@Y)RxQIk|9>k1FDi5aJ#MBx)+gZf-n}rFS9O9B)$dBJ2bVh2$a9y%@j1KX<@g+l z=Y`zN9%nFynUC~Saka@5OqMy;!qK2tM!wXOqJ?@K60EcMy~wI;ZN(GuQikW)(t^FH z7%OzYHGqS>M&MNQ4Z3TLm(mg6b0vm=@TO|&;5+VHr;r=X|Ap7oelvM}cUJWH?7ub- zj8_sFVy&z0o9DRpCF^GJ+Gx?5`uaNB;9E6jon;g{oNmZJv0cKQ2wdSLG5TER&v!3t z))o@t5C-v0Ap!%nj6PJ0>x(7^pR3xaVFPO`mHq=Fsq8rUcy`?=+$! z)B&-n-rT)Q>p$KM<1Qo9h*r>n9TEZB>h@I|#r=uaL3`nos@Y_o0##E7g^qAyii(PU zq>hC(loz)BBp=+Ux1%Uwr>QJqETfd()T1LDdMxc9W;$EN0y_zbB>;))_3T-Rax)Tc zOXVol?FJ$CvJ>zDl2rXiLYA71lX!mSt8HLfnBYn4z-2-@Kg#?+$En&Ogugb&BZtnl z1(y`X!|bW8#Z7*az~+$47G~K~uz_L9M+?CD!ltf89gS4n30mc=VGvZlKa%GEhOcxy z!A~SpSS$46Xjrt|&K6^K`r@~JQkY+eP7R}bt35g7-&TlLZ;}B<=0krQU5fah;h_?n zTQiI6Q~nEMnXeWwm!lZ@t+kJ4TMA$^E&h5VB{LXIP@+}+V>2wic1RtWu_Jt|6|oem z-5MG^*ejR66`oWkN*n#r88+8veGBW+ydyzJ-T!M?d_1_S)!L`lLI2iu>P{1bw}#OQ z#(-17=?gVKTfU&5PePo!3LxltMM94BH1x^t;0*m`%pxa7ZcCF>vmc%2@$`hVb_KkiHu|n9#E0WzngK%FT>w0K2{^f!Av zchmUD4gyUPgYDZ+xN8WY49Nm!El2vJA7r+hd&^c1`(GwtkryN(%c>iPOR}dzFBF-g zYwLO4js;0}EuW~=qa0b3|AlH(2pm08@8@=C19U<~TQ>X7rlo3{oN0sPl`|0|Qkf~f zA15^PF=Fbzxn<*kNa)_Rz5YH<4B?+}&iUplw0mU#g3!D)D6n$3xO=-kR@g#V!qfhH zHSa$lsRQ)b#qZ1*r9keqyKCQZw{284?W4eKKtB;I8;rB|G=AJTP3+pfZEy{gOi-Mz z5h77YjVNTRL9O#5w)}qZdt({E8W*s$_>R(*@Xnv~fUQ^gfoHg#8f> zd90q^Gel{D@cKlc6JoYh^+HP;8Ab5;wMibw!or9ZxiR*R+5KR^)|!F(bvRjIP`mn? z)1(YXX>ViMQ2w+?NJq&llzI{Jg@alzVW|29YxzHF+Wvndkr=HxMQ z(&xan%OrbjbmQjTaW9m|kXQpl5!YYlEVYEQ>#aKg9I`ePn+2dwi+LTs!8QOzga1BH zoD=2LwWRT@50k0LZilb$OG*SuLh4R z{Xjl$P2{Dqu1423O(1?ymX9amA>uVGa{IJ=(I~moF*04xWj%RmF{Qs~Y32oBB5jwy z^0^oaaTpBrVi*CG^}Bz*nsH?`w?3INDR4md*Ze9#*}F?#O69sov8h z!IZcIs~!tZoGDoe|EuT{slgo}1$Z|HYgCYje!)Jkd&qIEY&KASNv~NiNMnhxN(dz(-`z)jqSr`#up8HBep^<{d+ra zpKkXmPG6G&Thg|9*Hh;VfE?F-psz<|Vb_qO@1YJKOZ{{dg+rpo{T