diff --git a/code/__DEFINES/pai.dm b/code/__DEFINES/pai.dm
new file mode 100644
index 00000000000..4359cf9d8ea
--- /dev/null
+++ b/code/__DEFINES/pai.dm
@@ -0,0 +1,2 @@
+/// Total percent required to hack a door.
+#define HACK_COMPLETE 100
diff --git a/code/controllers/subsystem/pai.dm b/code/controllers/subsystem/pai.dm
index 3171a27ebbf..3c66d28dc15 100644
--- a/code/controllers/subsystem/pai.dm
+++ b/code/controllers/subsystem/pai.dm
@@ -3,16 +3,26 @@ SUBSYSTEM_DEF(pai)
flags = SS_NO_INIT|SS_NO_FIRE
+ /// List of pAI candidates, including those not submitted.
var/list/candidates = list()
+ /// Prevents a crew member from hitting "request pAI"
var/request_spam = FALSE
+ /// Prevents a pAI from submitting itself repeatedly and sounding an alert.
var/submit_spam = FALSE
+ /// All pAI cards on the map.
var/list/pai_card_list = list()
+/// Created when a user clicks the "pAI candidate" window
/datum/pai_candidate
+ /// User inputted OOC comments
var/comments
+ /// User inputted behavior description
var/description
+ /// User's ckey - not input
var/key
+ /// User's pAI name. If blank, ninja name.
var/name
+ /// If the user has hit "submit"
var/ready = FALSE
/**
@@ -53,11 +63,9 @@ SUBSYSTEM_DEF(pai)
candidates.Add(candidate)
ui_interact(user)
-/// Ensures an observer has the window open
/datum/controller/subsystem/pai/ui_state(mob/user)
return GLOB.observer_state
-/// Opens the TGUI window
/datum/controller/subsystem/pai/ui_interact(mob/user, datum/tgui/ui)
. = ..()
ui = SStgui.try_update_ui(user, src, ui)
@@ -65,7 +73,6 @@ SUBSYSTEM_DEF(pai)
ui = new(user, src, "PaiSubmit")
ui.open()
-/// The data sent to the window.
/datum/controller/subsystem/pai/ui_static_data(mob/user)
. = ..()
var/list/data = list()
@@ -78,7 +85,6 @@ SUBSYSTEM_DEF(pai)
data["name"] = candidate.name
return data
-/// Actions sent by TGUI
/datum/controller/subsystem/pai/ui_act(action, list/params, datum/tgui/ui)
. = ..()
if(.)
@@ -86,6 +92,8 @@ SUBSYSTEM_DEF(pai)
/// The matching candidate from search
var/datum/pai_candidate/candidate = check_candidate(usr)
if(isnull(candidate))
+ to_chat(usr, span_warning("There was an error. Please resubmit."))
+ ui.close()
return FALSE
switch(action)
if("submit")
@@ -100,7 +108,6 @@ SUBSYSTEM_DEF(pai)
candidate.description = params["candidate"]["description"]
candidate.name = params["candidate"]["name"]
candidate.savefile_save(usr)
- to_chat(usr, span_boldnotice("You have saved pAI information locally."))
if("load")
candidate.savefile_load(usr)
//In case people have saved unsanitized stuff.
diff --git a/code/game/objects/items/devices/PDA/PDA.dm b/code/game/objects/items/devices/PDA/PDA.dm
index 5be3455948f..5accfb8efc9 100644
--- a/code/game/objects/items/devices/PDA/PDA.dm
+++ b/code/game/objects/items/devices/PDA/PDA.dm
@@ -678,6 +678,7 @@ GLOBAL_LIST_EMPTY(PDAs)
pai.attack_self(U)
if("2") // Eject pAI device
usr.put_in_hands(pai)
+ pai.slotted = FALSE
to_chat(usr, span_notice("You remove the pAI from the [name]."))
//SKILL FUNCTIONS===================================
@@ -1085,6 +1086,7 @@ GLOBAL_LIST_EMPTY(PDAs)
if(!user.transferItemToLoc(C, src))
return
pai = C
+ pai.slotted = TRUE
to_chat(user, span_notice("You slot \the [C] into [src]."))
update_appearance()
updateUsrDialog()
diff --git a/code/game/objects/items/devices/paicard.dm b/code/game/objects/items/devices/paicard.dm
index efc25807657..32e6db7dbc1 100644
--- a/code/game/objects/items/devices/paicard.dm
+++ b/code/game/objects/items/devices/paicard.dm
@@ -11,8 +11,12 @@
custom_premium_price = PAYCHECK_HARD * 1.25
///don't spam alert messages.
var/alert_cooldown
+ /// If the pAIcard is slotted in a PDA
+ var/slotted = FALSE
+ /// Any pAI personalities inserted
var/mob/living/silicon/pai/pai
- var/emotion_icon = "off" ///what emotion icon we have. handled in /mob/living/silicon/pai/Topic()
+ ///what emotion icon we have. handled in /mob/living/silicon/pai/Topic()
+ var/emotion_icon = "off"
resistance_flags = FIRE_PROOF | ACID_PROOF | INDESTRUCTIBLE
/obj/item/paicard/suicide_act(mob/living/user)
@@ -63,7 +67,7 @@
ui.open()
/obj/item/paicard/ui_state(mob/user)
- return GLOB.inventory_state
+ return GLOB.paicard_state
/obj/item/paicard/ui_data(mob/user)
. = ..()
diff --git a/code/modules/mob/living/silicon/pai/pai.dm b/code/modules/mob/living/silicon/pai/pai.dm
index 920cf9e8c19..ddd88e42018 100644
--- a/code/modules/mob/living/silicon/pai/pai.dm
+++ b/code/modules/mob/living/silicon/pai/pai.dm
@@ -34,6 +34,8 @@
var/obj/item/paicard/card
/// Are we hacking a door?
var/hacking = FALSE
+ /// The progress for hacking
+ var/datum/progressbar/hackbar
/// Changes the display to syndi if true
var/emagged = FALSE
@@ -174,22 +176,27 @@
aiPDA.name = real_name + " (" + aiPDA.ownjob + ")"
/mob/living/silicon/pai/proc/process_hack(delta_time, times_fired)
- if(hacking_cable && hacking_cable.machine && istype(hacking_cable.machine, /obj/machinery/door) && hacking_cable.machine == hackdoor && get_dist(src, hackdoor) <= 1)
- hackprogress = clamp(hackprogress + (2 * delta_time), 0, 100)
+ if(hacking_cable?.machine && istype(hacking_cable.machine, /obj/machinery/door) && hacking_cable.machine == hackdoor && get_dist(src, hackdoor) <= 1)
+ hackprogress = clamp(hackprogress + (2 * delta_time), 0, HACK_COMPLETE)
+ hackbar.update(hackprogress)
else
to_chat(src, span_notice("Door Jack: Connection to airlock has been lost. Hack aborted."))
hackprogress = 0
hacking = FALSE
hackdoor = null
+ hackbar.end_progress()
+ QDEL_NULL(hackbar)
QDEL_NULL(hacking_cable)
if(!QDELETED(card))
card.update_appearance()
return
- if(hackprogress >= 100)
+ if(hackprogress >= HACK_COMPLETE)
hackprogress = 0
- var/obj/machinery/door/D = hacking_cable.machine
- D.open()
hacking = FALSE
+ hackbar.end_progress()
+ var/obj/machinery/door/door = hacking_cable.machine
+ door.open()
+ QDEL_NULL(hackbar)
QDEL_NULL(hacking_cable)
/mob/living/silicon/pai/make_laws()
diff --git a/code/modules/mob/living/silicon/pai/personality.dm b/code/modules/mob/living/silicon/pai/personality.dm
index 57f248cba48..b00fe994c10 100644
--- a/code/modules/mob/living/silicon/pai/personality.dm
+++ b/code/modules/mob/living/silicon/pai/personality.dm
@@ -12,17 +12,14 @@
/datum/pai_candidate/proc/savefile_save(mob/user)
if(is_guest_key(user.key))
+ to_chat(usr, span_warning("You cannot save pAI information as a guest."))
return FALSE
-
var/savefile/F = new /savefile(src.savefile_path(user))
-
-
WRITE_FILE(F["name"], name)
WRITE_FILE(F["description"], description)
WRITE_FILE(F["comments"], comments)
-
WRITE_FILE(F["version"], 1)
-
+ to_chat(usr, span_boldnotice("You have saved pAI information locally."))
return TRUE
// loads the savefile corresponding to the mob's ckey
diff --git a/code/modules/mob/living/silicon/pai/software.dm b/code/modules/mob/living/silicon/pai/software.dm
index 3a52930f388..433dae8614d 100644
--- a/code/modules/mob/living/silicon/pai/software.dm
+++ b/code/modules/mob/living/silicon/pai/software.dm
@@ -277,6 +277,8 @@
to_chat(AI, "Network Alert: Brute-force security override in progress in [turf.loc].")
else
to_chat(AI, "Network Alert: Brute-force security override in progress. Unable to pinpoint location.")
+ if(!hackbar)
+ hackbar = new(src, HACK_COMPLETE, hacking_cable.machine)
hacking = TRUE
/**
diff --git a/code/modules/tgui/states/paicard.dm b/code/modules/tgui/states/paicard.dm
new file mode 100644
index 00000000000..a2450b7588c
--- /dev/null
+++ b/code/modules/tgui/states/paicard.dm
@@ -0,0 +1,20 @@
+/**
+ * tgui state: paicard_state
+ *
+ * Checks that the paicard is in the user's top-level
+ * (hand, ear, pocket, belt, etc) inventory OR
+ * if the paicard has been slotted into a PDA which
+ * is also on the user's person.
+ *
+ */
+
+GLOBAL_DATUM_INIT(paicard_state, /datum/ui_state/paicard_state, new)
+
+/datum/ui_state/paicard_state/can_use_topic(obj/item/paicard/paicard, mob/user)
+ /// paicard is in the user's closest inventory
+ if(!paicard.slotted && (paicard in user))
+ return user.shared_ui_interaction(paicard)
+ /// paicard is in a pda slot which is in the user's closest inventory
+ if(paicard.slotted && (paicard.loc in user))
+ return user.shared_ui_interaction(paicard)
+ return UI_CLOSE
diff --git a/tgstation.dme b/tgstation.dme
index 44aa93739be..ef03c15dbce 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -109,6 +109,7 @@
#include "code\__DEFINES\networks.dm"
#include "code\__DEFINES\nitrile.dm"
#include "code\__DEFINES\obj_flags.dm"
+#include "code\__DEFINES\pai.dm"
#include "code\__DEFINES\pinpointers.dm"
#include "code\__DEFINES\pipe_construction.dm"
#include "code\__DEFINES\plumbing.dm"
@@ -3851,6 +3852,7 @@
#include "code\modules\tgui\states\not_incapacitated.dm"
#include "code\modules\tgui\states\notcontained.dm"
#include "code\modules\tgui\states\observer.dm"
+#include "code\modules\tgui\states\paicard.dm"
#include "code\modules\tgui\states\physical.dm"
#include "code\modules\tgui\states\self.dm"
#include "code\modules\tgui\states\zlevel.dm"
diff --git a/tgui/packages/tgui/interfaces/PaiCard.tsx b/tgui/packages/tgui/interfaces/PaiCard.tsx
index e0fb1f56839..a46017eb750 100644
--- a/tgui/packages/tgui/interfaces/PaiCard.tsx
+++ b/tgui/packages/tgui/interfaces/PaiCard.tsx
@@ -169,7 +169,7 @@ const PaiOptions = (_, context) => {
)}
- {master && {dna}}
+ {!!master && {dna}}
{laws}