diff --git a/code/game/jobs/access.dm b/code/game/jobs/access.dm
index 558595930fa..f4836ee6283 100644
--- a/code/game/jobs/access.dm
+++ b/code/game/jobs/access.dm
@@ -92,9 +92,14 @@
//check if it doesn't require any access at all
if(src.check_access(null))
return 1
+
if(istype(M, /mob/living/silicon))
- //AI can do whatever he wants
- return 1
+ if(ispAI(M))
+ var/mob/living/silicon/pai/P = M
+ if(P.ID && src.check_access(P.ID))
+ return 1
+ else//AI can do whatever he wants
+ return 1
else if(istype(M, /mob/living/carbon/human))
var/mob/living/carbon/human/H = M
//if they are holding or wearing a card that has access, that works
diff --git a/code/game/machinery/doors/door.dm b/code/game/machinery/doors/door.dm
index 22041ae091f..8e7c952def7 100644
--- a/code/game/machinery/doors/door.dm
+++ b/code/game/machinery/doors/door.dm
@@ -105,8 +105,8 @@
playsound(src.loc, hatch_open_sound, 40, 1, -1)
hatchclosetime = world.time + 29
- if (istype(mover, /mob/living/silicon))
- var/mob/living/silicon/S = mover
+ if (istype(mover, /mob/living))
+ var/mob/living/S = mover
S.under_door()
@@ -190,8 +190,14 @@
if(mover.checkpass(PASSGLASS))
return !opacity
if(density && hashatch && mover.checkpass(PASSDOORHATCH))
- open_hatch(mover)
- return 1//If this door is closed, but it has hatches, and this creature can go through hatches. Then we let it through without opening
+ if (istype(mover, /mob/living/silicon/pai))
+ var/mob/living/silicon/pai/P = mover
+ if (allowed(P))
+ open_hatch(mover)
+ return 1
+ else
+ open_hatch(mover)
+ return 1//If this door is closed, but it has hatches, and this creature can go through hatches. Then we let it through without opening
return !density
diff --git a/code/game/objects/items/devices/paicard.dm b/code/game/objects/items/devices/paicard.dm
index 70fa3a409f3..8306c5f06e6 100644
--- a/code/game/objects/items/devices/paicard.dm
+++ b/code/game/objects/items/devices/paicard.dm
@@ -10,6 +10,7 @@
var/looking_for_personality = 0
var/mob/living/silicon/pai/pai
+
/*/obj/item/device/paicard/relaymove(var/mob/user, var/direction)
if(src.loc && istype(src.loc.loc, /obj/item/rig_module))
var/obj/item/rig_module/module = src.loc.loc
@@ -27,6 +28,45 @@
pai.death(0)
..()
+/obj/item/device/paicard/attackby(obj/item/C as obj, mob/user as mob)
+ if(istype(C, /obj/item/weapon/card/id))
+ scan_ID(C, user)
+
+
+//This proc is called when the user scans their ID on the pAI card.
+//It registers their ID and copies their access to the pai, allowing it to use airlocks the owner can
+//Scanning an ID replaces any previously stored access with the new set.
+//Only cards that match the imprinted DNA can be used, it's not a free Agent ID card.
+//Possible TODO in future, allow emagging a paicard to let it work like an agent ID, accumulating access from any ID
+/obj/item/device/paicard/proc/scan_ID(var/obj/item/weapon/card/id/card, var/mob/user)
+ if (!pai)
+ user << "Error: ID Registration failed. No pAI personality installed."
+ playsound(src.loc, 'sound/machines/buzz-two.ogg', 20, 0)
+ return 0
+
+ if (!pai.master_dna)
+ user << "Error: ID Registration failed. User not registered as owner. Please complete imprinting process first."
+ playsound(src.loc, 'sound/machines/buzz-two.ogg', 20, 0)
+ return 0
+
+ if (pai.master_dna != card.dna_hash)
+ user << "Error: ID Registration failed. Biometric data on ID card does not match DNA sample of registered owner."
+ playsound(src.loc, 'sound/machines/buzz-two.ogg', 20, 0)
+ return 0
+
+ pai.ID.access.Cut()
+ pai.ID.access = card.access.Copy()
+ pai.ID.registered_name = card.registered_name
+ playsound(src.loc, 'sound/machines/ping.ogg', 50, 0)
+ user << "ID Registration for [pai.ID.registered_name] is a success. PAI access updated!"
+ return 1
+
+/obj/item/device/paicard/proc/ID_readout()
+ if (pai.ID.registered_name)
+ return "Identity of owner: [pai.ID.registered_name] registered."
+ else
+ return "No ID card registered! Please scan your ID to share access."
+
/obj/item/device/paicard/attack_self(mob/user)
if (!in_range(src, user))
return
@@ -143,6 +183,10 @@
Additional directives: |
[pai.pai_laws] |
+
+ | ID: |
+ [ID_readout()] |
+
"}
diff --git a/code/game/objects/items/weapons/cards_ids.dm b/code/game/objects/items/weapons/cards_ids.dm
index 6c7dcf9d5ba..3bec295e313 100644
--- a/code/game/objects/items/weapons/cards_ids.dm
+++ b/code/game/objects/items/weapons/cards_ids.dm
@@ -128,7 +128,7 @@
desc = "A card used to provide ID and determine access across the station."
icon_state = "id"
item_state = "card-id"
- var/access = list()
+ var/list/access = list()
var/registered_name = "Unknown" // The name registered_name on the card
slot_flags = SLOT_ID
@@ -147,16 +147,29 @@
/obj/item/weapon/card/id/New()
..()
spawn(30)
- if(istype(loc, /mob/living/carbon/human))
- var/mob/living/carbon/human/H = loc
- blood_type = H.dna.b_type
- dna_hash = H.dna.unique_enzymes
- fingerprint_hash = md5(H.dna.uni_identity)
- citizenship = H.citizenship
- religion = H.religion
- age = H.age
+ if(istype(loc, /mob/living/carbon/human))
+ var/mob/living/carbon/human/H = loc
+ blood_type = H.dna.b_type
+ dna_hash = H.dna.unique_enzymes
+ fingerprint_hash = md5(H.dna.uni_identity)
+ citizenship = H.citizenship
+ religion = H.religion
+ age = H.age
/obj/item/weapon/card/id/attack_self(mob/user as mob)
+ if (dna_hash == "\[UNSET\]" && ishuman(user))
+ var/response = alert(user, "This ID card has not been imprinted with biometric data. Would you like to imprint yours now?", "Biometric Imprinting", "Yes", "No")
+ if (response == "Yes")
+ var/mob/living/carbon/human/H = user
+ blood_type = H.dna.b_type
+ dna_hash = H.dna.unique_enzymes
+ fingerprint_hash = md5(H.dna.uni_identity)
+ citizenship = H.citizenship
+ religion = H.religion
+ age = H.age
+ user << "Biometric Imprinting Successful!."
+ return
+
for(var/mob/O in viewers(user, null))
O.show_message(text("[] shows you: \icon[] []: assignment: []", user, src, src.name, src.assignment), 1)
diff --git a/code/modules/mob/living/carbon/brain/emote.dm b/code/modules/mob/living/carbon/brain/emote.dm
index 818af164161..65f6bbe4eb4 100644
--- a/code/modules/mob/living/carbon/brain/emote.dm
+++ b/code/modules/mob/living/carbon/brain/emote.dm
@@ -28,15 +28,12 @@
if ("custom")
return custom_emote(m_type, message)
if ("alarm")
- src << "You sound an alarm."
message = "[src] sounds an alarm."
m_type = 2
if ("alert")
- src << "You let out a distressed noise."
message = "[src] lets out a distressed noise."
m_type = 2
if ("notice")
- src << "You play a loud tone."
message = "[src] plays a loud tone."
m_type = 2
if ("flash")
@@ -46,15 +43,21 @@
message = "[src] blinks."
m_type = 1
if ("whistle")
- src << "You whistle."
message = "[src] whistles."
m_type = 2
- if ("beep")
- src << "You beep."
+ if("beep")
message = "[src] beeps."
+ playsound(src.loc, 'sound/machines/twobeep.ogg', 50, 0)
+ m_type = 2
+ if("ping")
+ message = "[src] pings."
+ playsound(src.loc, 'sound/machines/ping.ogg', 50, 0)
+ m_type = 2
+ if("buzz")
+ message = "[src] buzzes."
+ playsound(src.loc, 'sound/machines/buzz-sigh.ogg', 50, 0)
m_type = 2
if ("boop")
- src << "You boop."
message = "[src] boops."
m_type = 2
if ("help")
diff --git a/code/modules/mob/living/living.dm b/code/modules/mob/living/living.dm
index 98f4e191691..39195ff1efb 100644
--- a/code/modules/mob/living/living.dm
+++ b/code/modules/mob/living/living.dm
@@ -676,6 +676,12 @@ default behaviour is:
/mob/living/proc/slip(var/slipped_on,stun_duration=8)
return 0
+/mob/living/proc/under_door()
+ //This function puts a silicon on a layer that makes it draw under doors, then periodically checks if its still standing on a door
+ if (layer > UNDERDOOR)//Don't toggle it if we're hiding
+ layer = UNDERDOOR
+ underdoor = 1
+
/mob/living/carbon/drop_from_inventory(var/obj/item/W, var/atom/Target = null)
if(W in internal_organs)
return
diff --git a/code/modules/mob/living/living_defines.dm b/code/modules/mob/living/living_defines.dm
index 36119b3e6b4..eddf2f4c2d3 100644
--- a/code/modules/mob/living/living_defines.dm
+++ b/code/modules/mob/living/living_defines.dm
@@ -45,4 +45,5 @@
var/composition_reagent_quantity
var/mouth_size = 2//how large of a creature it can swallow at once, and how big of a bite it can take out of larger things
var/eat_types = 0//This is a bitfield which must be initialised in New(). The valid values for it are in devour.dm
- var/datum/reagents/metabolism/ingested = null
\ No newline at end of file
+ var/datum/reagents/metabolism/ingested = null
+ var/underdoor //Used for mobs that can walk through maintenance hatches - drones, pais, and spiderbots
\ No newline at end of file
diff --git a/code/modules/mob/living/silicon/pai/pai.dm b/code/modules/mob/living/silicon/pai/pai.dm
index 336050268b3..5517d5463dc 100644
--- a/code/modules/mob/living/silicon/pai/pai.dm
+++ b/code/modules/mob/living/silicon/pai/pai.dm
@@ -5,8 +5,9 @@
emote_type = 2 // pAIs emotes are heard, not seen, so they can be seen through a container (eg. person)
small = 1
- pass_flags = 1
+ pass_flags = PASSTABLE | PASSDOORHATCH
density = 0
+ mob_size = 1//As a holographic projection, a pAI is massless except for its card device
mob_size = 1
@@ -14,7 +15,6 @@
var/network = "SS13"
var/obj/machinery/camera/current = null
-
var/ram = 100 // Used as currency to purchase different abilities
var/list/software = list()
var/userDNA // The DNA string of our assigned user
@@ -34,10 +34,12 @@
"Natural" = list("says","yells","asks"),
"Beep" = list("beeps","beeps loudly","boops"),
"Chirp" = list("chirps","chirrups","cheeps"),
- "Feline" = list("purrs","yowls","meows")
+ "Feline" = list("purrs","yowls","meows"),
+ "Rodent" = list("squeaks","squeals","squeeks")
)
var/obj/item/weapon/pai_cable/cable // The cable we produce and use when door or camera jacking
+ var/obj/item/weapon/card/id/ID = null //Internal ID used to store copied owner access, and to check access for airlocks
var/master // Name of the one who commands us
var/master_dna // DNA string for owner verification
@@ -106,6 +108,8 @@
//PDA
pda = new(src)
+ ID = new(src)
+ ID.registered_name = ""
spawn(5)
pda.ownjob = "Personal Assistant"
pda.owner = text("[]", src)
diff --git a/code/modules/mob/living/silicon/silicon.dm b/code/modules/mob/living/silicon/silicon.dm
index 1e12a07419a..218e633eba3 100644
--- a/code/modules/mob/living/silicon/silicon.dm
+++ b/code/modules/mob/living/silicon/silicon.dm
@@ -24,7 +24,7 @@
var/next_alarm_notice
var/list/datum/alarm/queued_alarms = new()
- var/underdoor
+
#define SEC_HUD 1 //Security HUD mode
#define MED_HUD 2 //Medical HUD mode
@@ -370,10 +370,4 @@
spawn(3)//A slight delay to let us finish walking out from under the door
layer = initial(layer)
-/mob/living/silicon/proc/under_door()
- //This function puts a silicon on a layer that makes it draw under doors, then periodically checks if its still standing on a door
- if (layer > UNDERDOOR)//Don't toggle it if we're hiding
- layer = UNDERDOOR
- underdoor = 1
-/mob/living/silicon/proc/not_under_door()
\ No newline at end of file
diff --git a/code/modules/mob/living/simple_animal/friendly/spiderbot.dm b/code/modules/mob/living/simple_animal/friendly/spiderbot.dm
index 14ab5cf7b37..f65d471bcb3 100644
--- a/code/modules/mob/living/simple_animal/friendly/spiderbot.dm
+++ b/code/modules/mob/living/simple_animal/friendly/spiderbot.dm
@@ -23,7 +23,7 @@
icon_dead = "spiderbot-smashed"
wander = 0
-
+ density = 0
health = 25
maxHealth = 25
hunger_enabled = 0
@@ -39,7 +39,7 @@
var/emagged = 0
var/obj/item/held_item = null //Storage for single item they can hold.
speed = -1 //Spiderbots gotta go fast.
- pass_flags = PASSTABLE
+ pass_flags = PASSTABLE | PASSDOORHATCH
small = 1
speak_emote = list("beeps","clicks","chirps")
@@ -298,3 +298,17 @@
/mob/living/simple_animal/spiderbot/binarycheck()
return positronic
+
+/mob/living/simple_animal/spiderbot/Move(newloc, direct)
+ ..(newloc,direct)
+ if (underdoor)
+ underdoor = 0
+ if ((layer == UNDERDOOR))//if this is false, then we must have used hide, or had our layer changed by something else. We wont do anymore checks for this move proc
+ for (var/obj/machinery/door/D in loc)
+ if (D.hashatch)
+ underdoor = 1
+ break
+
+ if (!underdoor)
+ spawn(3)//A slight delay to let us finish walking out from under the door
+ layer = initial(layer)
\ No newline at end of file
diff --git a/html/changelogs/Nanako-PAIhatch.yml b/html/changelogs/Nanako-PAIhatch.yml
new file mode 100644
index 00000000000..6e777f2b457
--- /dev/null
+++ b/html/changelogs/Nanako-PAIhatch.yml
@@ -0,0 +1,42 @@
+################################
+# Example Changelog File
+#
+# Note: This file, and files beginning with ".", and files that don't end in ".yml" will not be read. If you change this file, you will look really dumb.
+#
+# Your changelog will be merged with a master changelog. (New stuff added only, and only on the date entry for the day it was merged.)
+# When it is, any changes listed below will disappear.
+#
+# Valid Prefixes:
+# bugfix
+# wip (For works in progress)
+# tweak
+# soundadd
+# sounddel
+# rscadd (general adding of nice things)
+# rscdel (general deleting of nice things)
+# imageadd
+# imagedel
+# maptweak
+# spellcheck (typo fixes)
+# experiment
+#################################
+
+# Your name.
+author: Nanako
+
+# Optional: Remove this file after generating master changelog. Useful for PR changelogs that won't get used again.
+delete-after: True
+
+# Any changes you've made. See valid prefix list above.
+# INDENT WITH TWO SPACES. NOT TABS. SPACES.
+# SCREW THIS UP AND IT WON'T WORK.
+# Also, all entries are changed into a single [] after a master changelog generation. Just remove the brackets when you add new entries.
+# Please surround your changes in double quotes ("), as certain characters otherwise screws up compiling. The quotes will not show up in the changelog.
+changes:
+ - tweak: "Spiderbots no longer block movement."
+ - tweak: "Spiderbots can now use airlock maintenance hatches."
+ - rscadd: "PAIs can now have the owner's ID card scanned onto them to share access."
+ - tweak: "PAIs can now use airlock maintenance hatches, but only on airlocks they have access to. Requires a scanned ID"
+ - tweak: "Added new rodent speech verbs for PAIs"
+ - tweak: "Positronic brains and MMIs outside of a chassis can now use ping/beep/buzz audio emotes."
+
\ No newline at end of file