diff --git a/code/__DEFINES/subsystems.dm b/code/__DEFINES/subsystems.dm
index 232da0d2cacd..7925edb76a18 100644
--- a/code/__DEFINES/subsystems.dm
+++ b/code/__DEFINES/subsystems.dm
@@ -59,8 +59,8 @@
#define INIT_ORDER_TRAITS 11
#define INIT_ORDER_TICKER 10
#define INIT_ORDER_MAPPING 9
-#define INIT_ORDER_ATOMS 8
-#define INIT_ORDER_NETWORKS 7
+#define INIT_ORDER_NETWORKS 8
+#define INIT_ORDER_ATOMS 7
#define INIT_ORDER_LANGUAGE 6
#define INIT_ORDER_MACHINES 5
#define INIT_ORDER_CIRCUIT 4
diff --git a/code/datums/components/ntnet_interface.dm b/code/datums/components/ntnet_interface.dm
index 14705691bbc9..c346279b3258 100644
--- a/code/datums/components/ntnet_interface.dm
+++ b/code/datums/components/ntnet_interface.dm
@@ -33,6 +33,9 @@
parent.ntnet_recieve(data)
/datum/component/ntnet_interface/proc/__network_send(datum/netdata/data, netid) //Do not directly proccall!
+ // Process data before sending it
+ data.pre_send(src)
+
if(netid)
if(networks_connected_by_id[netid])
var/datum/ntnet/net = networks_connected_by_id[netid]
diff --git a/code/datums/wires/airlock.dm b/code/datums/wires/airlock.dm
index 5a01227b0336..31156491f503 100644
--- a/code/datums/wires/airlock.dm
+++ b/code/datums/wires/airlock.dm
@@ -48,17 +48,15 @@
return
if(!A.requiresID() || A.check_access(null))
if(A.density)
- A.open()
+ INVOKE_ASYNC(A, /obj/machinery/door/airlock.proc/open)
else
- A.close()
+ INVOKE_ASYNC(A, /obj/machinery/door/airlock.proc/close)
if(WIRE_BOLTS) // Pulse to toggle bolts (but only raise if power is on).
if(!A.locked)
A.bolt()
- A.audible_message("You hear a click from the bottom of the door.", null, 1)
else
if(A.hasPower())
A.unbolt()
- A.audible_message("You hear a click from the bottom of the door.", null, 1)
A.update_icon()
if(WIRE_IDSCAN) // Pulse to disable emergency access and flash red lights.
if(A.hasPower() && A.density)
diff --git a/code/game/machinery/doors/airlock.dm b/code/game/machinery/doors/airlock.dm
index 609da46c52ae..b1075a41a056 100644
--- a/code/game/machinery/doors/airlock.dm
+++ b/code/game/machinery/doors/airlock.dm
@@ -153,6 +153,7 @@
/obj/machinery/door/airlock/ComponentInitialize()
. = ..()
+ AddComponent(/datum/component/ntnet_interface)
AddComponent(/datum/component/rad_insulation, RAD_MEDIUM_INSULATION)
/obj/machinery/door/airlock/proc/update_other_id()
@@ -189,6 +190,55 @@
if ("cyclelinkeddir")
cyclelinkairlock()
+/obj/machinery/door/airlock/check_access_ntnet(datum/netdata/data)
+ return !requiresID() || ..()
+
+/obj/machinery/door/airlock/ntnet_recieve(datum/netdata/data)
+ // Check if the airlock is powered and can accept control packets.
+ if(!hasPower() || !canAIControl())
+ return
+
+ // Check packet access level.
+ if(!check_access_ntnet(data))
+ return
+
+ // Handle recieved packet.
+ var/command = lowertext(data.plaintext_data)
+ var/command_value = lowertext(data.plaintext_data_secondary)
+ switch(command)
+ if("open")
+ if(command_value == "on" && !density)
+ return
+
+ if(command_value == "off" && density)
+ return
+
+ if(density)
+ INVOKE_ASYNC(src, .proc/open)
+ else
+ INVOKE_ASYNC(src, .proc/close)
+
+ if("bolt")
+ if(command_value == "on" && locked)
+ return
+
+ if(command_value == "off" && !locked)
+ return
+
+ if(locked)
+ unbolt()
+ else
+ bolt()
+
+ if("emergency")
+ if(command_value == "on" && emergency)
+ return
+
+ if(command_value == "off" && !emergency)
+ return
+
+ emergency = !emergency
+ update_icon()
/obj/machinery/door/airlock/lock()
bolt()
@@ -198,6 +248,7 @@
return
locked = TRUE
playsound(src,boltDown,30,0,3)
+ audible_message("You hear a click from the bottom of the door.", null, 1)
update_icon()
/obj/machinery/door/airlock/unlock()
@@ -208,6 +259,7 @@
return
locked = FALSE
playsound(src,boltUp,30,0,3)
+ audible_message("You hear a click from the bottom of the door.", null, 1)
update_icon()
/obj/machinery/door/airlock/narsie_act()
@@ -321,7 +373,7 @@
return FALSE
/obj/machinery/door/airlock/proc/canAIControl(mob/user)
- return ((aiControlDisabled != 1) && (!isAllPowerCut()));
+ return ((aiControlDisabled != 1) && !isAllPowerCut())
/obj/machinery/door/airlock/proc/canAIHack()
return ((aiControlDisabled==1) && (!hackProof) && (!isAllPowerCut()));
diff --git a/code/game/machinery/doors/door.dm b/code/game/machinery/doors/door.dm
index 0a808ef3e61e..e9244722705d 100644
--- a/code/game/machinery/doors/door.dm
+++ b/code/game/machinery/doors/door.dm
@@ -45,11 +45,6 @@
if(!poddoor)
to_chat(user, "Its maintenance panel is screwed in place.")
-/obj/machinery/door/check_access(access)
- if(red_alert_access && GLOB.security_level >= SEC_LEVEL_RED)
- return TRUE
- return ..()
-
/obj/machinery/door/check_access_list(list/access_list)
if(red_alert_access && GLOB.security_level >= SEC_LEVEL_RED)
return TRUE
diff --git a/code/game/mecha/mech_fabricator.dm b/code/game/mecha/mech_fabricator.dm
index 8a5044fdfa21..c548e0b89da6 100644
--- a/code/game/mecha/mech_fabricator.dm
+++ b/code/game/mecha/mech_fabricator.dm
@@ -64,17 +64,6 @@
time_coeff = round(initial(time_coeff) - (initial(time_coeff)*(T))/5,0.01)
-/obj/machinery/mecha_part_fabricator/check_access(obj/item/card/id/I)
- if(istype(I, /obj/item/device/pda))
- var/obj/item/device/pda/pda = I
- I = pda.id
- if(!istype(I) || !I.access) //not ID or no access
- return FALSE
- for(var/req in req_access)
- if(!(req in I.access)) //doesn't have this access
- return FALSE
- return TRUE
-
/obj/machinery/mecha_part_fabricator/emag_act()
if(obj_flags & EMAGGED)
return
diff --git a/code/game/objects/items/control_wand.dm b/code/game/objects/items/control_wand.dm
index ca51595124c9..ce674ceb9464 100644
--- a/code/game/objects/items/control_wand.dm
+++ b/code/game/objects/items/control_wand.dm
@@ -13,12 +13,12 @@
w_class = WEIGHT_CLASS_TINY
var/mode = WAND_OPEN
var/region_access = 1 //See access.dm
- var/obj/item/card/id/ID
+ var/list/access_list
-/obj/item/door_remote/New()
- ..()
- ID = new /obj/item/card/id
- ID.access = get_region_accesses(region_access)
+/obj/item/door_remote/Initialize()
+ . = ..()
+ access_list = get_region_accesses(region_access)
+ AddComponent(/datum/component/ntnet_interface)
/obj/item/door_remote/attack_self(mob/user)
switch(mode)
@@ -30,35 +30,30 @@
mode = WAND_OPEN
to_chat(user, "Now in mode: [mode].")
-/obj/item/door_remote/afterattack(obj/machinery/door/airlock/D, mob/user)
- if(!istype(D))
+// Airlock remote works by sending NTNet packets to whatever it's pointed at.
+/obj/item/door_remote/afterattack(atom/A, mob/user)
+ GET_COMPONENT_FROM(target_interface, /datum/component/ntnet_interface, A)
+
+ if(!target_interface)
return
- if(!(D.hasPower()))
- to_chat(user, "[D] has no power!")
- return
- if(!D.requiresID())
- to_chat(user, "[D]'s ID scan is disabled!")
- return
- if(D.check_access(ID) && D.canAIControl(user))
- switch(mode)
- if(WAND_OPEN)
- if(D.density)
- D.open()
- else
- D.close()
- if(WAND_BOLT)
- if(D.locked)
- D.unbolt()
- else
- D.bolt()
- if(WAND_EMERGENCY)
- if(D.emergency)
- D.emergency = FALSE
- else
- D.emergency = TRUE
- D.update_icon()
- else
- to_chat(user, "[src] does not have access to this door.")
+
+ // Generate a control packet.
+ var/datum/netdata/data = new
+ data.recipient_ids = list(target_interface.hardware_id)
+
+ switch(mode)
+ if(WAND_OPEN)
+ data.plaintext_data = "open"
+ if(WAND_BOLT)
+ data.plaintext_data = "bolt"
+ if(WAND_EMERGENCY)
+ data.plaintext_data = "emergency"
+
+ data.plaintext_data_secondary = "toggle"
+ data.passkey = access_list
+
+ ntnet_send(data)
+
/obj/item/door_remote/omni
name = "omni door remote"
diff --git a/code/game/objects/objs.dm b/code/game/objects/objs.dm
index fdd331a6f12d..ae85b3b57949 100644
--- a/code/game/objects/objs.dm
+++ b/code/game/objects/objs.dm
@@ -22,6 +22,11 @@
var/current_skin //Has the item been reskinned?
var/list/unique_reskin //List of options to reskin.
+ // Access levels, used in modules\jobs\access.dm
+ var/list/req_access
+ var/req_access_txt = "0"
+ var/list/req_one_access
+ var/req_one_access_txt = "0"
/obj/vv_edit_var(vname, vval)
diff --git a/code/modules/NTNet/netdata.dm b/code/modules/NTNet/netdata.dm
index 7d3d8f2b5df0..d84ab43a6ab4 100644
--- a/code/modules/NTNet/netdata.dm
+++ b/code/modules/NTNet/netdata.dm
@@ -6,7 +6,24 @@
var/plaintext_data
var/plaintext_data_secondary
- var/plaintext_passkey
+ var/encrypted_passkey
+
+ var/list/passkey
+
+// Process data before sending it
+/datum/netdata/proc/pre_send(datum/component/ntnet_interface/interface)
+ // Decrypt the passkey.
+ if(encrypted_passkey && !passkey)
+ passkey = json_decode(XorEncrypt(hextostr(encrypted_passkey, TRUE), SScircuit.cipherkey))
+
+ // Encrypt the passkey.
+ if(!encrypted_passkey && passkey)
+ encrypted_passkey = strtohex(XorEncrypt(json_encode(passkey), SScircuit.cipherkey))
+
+ // If there is no sender ID, set the default one.
+ if(!sender_id && interface)
+ sender_id = interface.hardware_id
+
/datum/netdata/proc/json_list_generation_admin() //for admin logs and such.
. = list()
@@ -21,9 +38,9 @@
. = list()
.["recipient_ids"] = recipient_ids
.["sender_id"] = sender_id
- .["plaintext_data"] = plaintext_data
- .["plaintext_data_secondary"] = plaintext_data_secondary
- .["plaintext_passkey"] = plaintext_passkey
+ .["data"] = plaintext_data
+ .["data_secondary"] = plaintext_data_secondary
+ .["passkey"] = encrypted_passkey
/datum/netdata/proc/generate_netlog()
return "[json_encode(json_list_generation_netlog())]"
diff --git a/code/modules/integrated_electronics/subtypes/access.dm b/code/modules/integrated_electronics/subtypes/access.dm
new file mode 100644
index 000000000000..0f0626057a45
--- /dev/null
+++ b/code/modules/integrated_electronics/subtypes/access.dm
@@ -0,0 +1,37 @@
+/obj/item/integrated_circuit/input/card_reader
+ name = "card reader"
+ desc = "A circuit that can read registred name, assignment and a PassKey string from an ID card."
+ icon_state = "card_reader"
+
+ complexity = 4
+ spawn_flags = IC_SPAWN_DEFAULT|IC_SPAWN_RESEARCH
+ outputs = list(
+ "registered name" = IC_PINTYPE_STRING,
+ "assignment" = IC_PINTYPE_STRING,
+ "passkey" = IC_PINTYPE_STRING
+ )
+ activators = list(
+ "on read" = IC_PINTYPE_PULSE_OUT
+ )
+
+/obj/item/integrated_circuit/input/card_reader/attackby_react(obj/item/I, mob/living/user, intent)
+ var/obj/item/card/id/card = I.GetID()
+ var/list/access = I.GetAccess()
+ var/passkey = strtohex(XorEncrypt(json_encode(access), SScircuit.cipherkey))
+
+ if(card) // An ID card.
+ set_pin_data(IC_OUTPUT, 1, card.registered_name)
+ set_pin_data(IC_OUTPUT, 2, card.assignment)
+
+ else if(length(access)) // A non-card object that has access levels.
+ set_pin_data(IC_OUTPUT, 1, null)
+ set_pin_data(IC_OUTPUT, 2, null)
+
+ else
+ return FALSE
+
+ set_pin_data(IC_OUTPUT, 3, passkey)
+
+ push_data()
+ activate_pin(1)
+ return TRUE
diff --git a/code/modules/integrated_electronics/subtypes/input.dm b/code/modules/integrated_electronics/subtypes/input.dm
index fa292c56449b..c666f05d88d4 100644
--- a/code/modules/integrated_electronics/subtypes/input.dm
+++ b/code/modules/integrated_electronics/subtypes/input.dm
@@ -629,17 +629,16 @@
var/datum/netdata/data = new
data.recipient_ids = splittext(target_address, ";")
- data.sender_id = address
data.plaintext_data = message
data.plaintext_data_secondary = text
- data.plaintext_passkey = key
+ data.encrypted_passkey = key
ntnet_send(data)
/obj/item/integrated_circuit/input/ntnet_recieve(datum/netdata/data)
set_pin_data(IC_OUTPUT, 1, data.sender_id)
set_pin_data(IC_OUTPUT, 2, data.plaintext_data)
set_pin_data(IC_OUTPUT, 3, data.plaintext_data_secondary)
- set_pin_data(IC_OUTPUT, 4, data.plaintext_passkey)
+ set_pin_data(IC_OUTPUT, 4, data.encrypted_passkey)
push_data()
activate_pin(2)
diff --git a/code/modules/jobs/access.dm b/code/modules/jobs/access.dm
index 67bbd2fad97f..f08bf5908eca 100644
--- a/code/modules/jobs/access.dm
+++ b/code/modules/jobs/access.dm
@@ -1,9 +1,4 @@
-/obj/var/list/req_access = null
-/obj/var/req_access_txt = "0" as text
-/obj/var/list/req_one_access = null
-/obj/var/req_one_access_txt = "0" as text
-
//returns TRUE if this mob has sufficient access to use this object
/obj/proc/allowed(mob/M)
//check if it doesn't require any access at all
@@ -60,48 +55,36 @@
for(var/b in text2access(req_one_access_txt))
req_one_access += b
+// Check if an item has access to this object
/obj/proc/check_access(obj/item/I)
+ return check_access_list(I ? I.GetAccess() : null)
+
+
+/obj/proc/check_access_list(list/access_list)
gen_access()
- if(!istype(src.req_access, /list)) //something's very wrong
+ if(!islist(req_access)) //something's very wrong
return TRUE
- var/list/L = src.req_access
- if(!L.len && (!src.req_one_access || !src.req_one_access.len)) //no requirements
+ if(!req_access.len && !length(req_one_access))
return TRUE
- if(!I)
+
+ if(!length(access_list) || !islist(access_list))
return FALSE
- for(var/req in src.req_access)
- if(!(req in I.GetAccess())) //doesn't have this access
+
+ for(var/req in req_access)
+ if(!(req in access_list)) //doesn't have this access
return FALSE
- if(src.req_one_access && src.req_one_access.len)
- for(var/req in src.req_one_access)
- if(req in I.GetAccess()) //has an access from the single access list
+
+ if(length(req_one_access))
+ for(var/req in req_one_access)
+ if(req in access_list) //has an access from the single access list
return TRUE
return FALSE
return TRUE
-
-/obj/proc/check_access_list(list/L)
- if(!src.req_access && !src.req_one_access)
- return TRUE
- if(!istype(src.req_access, /list))
- return TRUE
- if(!src.req_access.len && (!src.req_one_access || !src.req_one_access.len))
- return TRUE
- if(!L)
- return FALSE
- if(!istype(L, /list))
- return FALSE
- for(var/req in src.req_access)
- if(!(req in L)) //doesn't have this access
- return FALSE
- if(src.req_one_access && src.req_one_access.len)
- for(var/req in src.req_one_access)
- if(req in L) //has an access from the single access list
- return TRUE
- return FALSE
- return TRUE
+/obj/proc/check_access_ntnet(datum/netdata/data)
+ return check_access_list(data.passkey)
/proc/get_centcom_access(job)
switch(job)
diff --git a/code/modules/jobs/job_types/job.dm b/code/modules/jobs/job_types/job.dm
index 077759e8b10d..704722dc13b7 100644
--- a/code/modules/jobs/job_types/job.dm
+++ b/code/modules/jobs/job_types/job.dm
@@ -182,6 +182,7 @@
var/obj/item/card/id/C = H.wear_id
if(istype(C))
C.access = J.get_access()
+ shuffle_inplace(C.access) // Shuffle access list to make NTNet passkeys less predictable
C.registered_name = H.real_name
C.assignment = J.title
C.update_label()
diff --git a/tgstation.dme b/tgstation.dme
index 651cffcd5729..421f49f360a9 100755
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -1603,6 +1603,7 @@
#include "code\modules\integrated_electronics\core\special_pins\string_pin.dm"
#include "code\modules\integrated_electronics\passive\passive.dm"
#include "code\modules\integrated_electronics\passive\power.dm"
+#include "code\modules\integrated_electronics\subtypes\access.dm"
#include "code\modules\integrated_electronics\subtypes\arithmetic.dm"
#include "code\modules\integrated_electronics\subtypes\converters.dm"
#include "code\modules\integrated_electronics\subtypes\data_transfer.dm"