diff --git a/code/__DEFINES/jobs.dm b/code/__DEFINES/jobs.dm
index 959d7d30b1a..2b169b004ec 100644
--- a/code/__DEFINES/jobs.dm
+++ b/code/__DEFINES/jobs.dm
@@ -39,6 +39,7 @@
//Silicon
#define JOB_AI "AI"
#define JOB_CYBORG "Cyborg"
+#define JOB_PERSONAL_AI "Personal AI"
//Security
#define JOB_WARDEN "Warden"
#define JOB_DETECTIVE "Detective"
diff --git a/code/__DEFINES/pai.dm b/code/__DEFINES/pai.dm
deleted file mode 100644
index 4359cf9d8ea..00000000000
--- a/code/__DEFINES/pai.dm
+++ /dev/null
@@ -1,2 +0,0 @@
-/// Total percent required to hack a door.
-#define HACK_COMPLETE 100
diff --git a/code/_globalvars/lists/poll_ignore.dm b/code/_globalvars/lists/poll_ignore.dm
index 1d464c16456..7fb95512adf 100644
--- a/code/_globalvars/lists/poll_ignore.dm
+++ b/code/_globalvars/lists/poll_ignore.dm
@@ -44,7 +44,7 @@ GLOBAL_LIST_INIT(poll_ignore_desc, list(
POLL_IGNORE_CONTRACTOR_SUPPORT = "Contractor Support Unit",
POLL_IGNORE_ACADEMY_WIZARD = "Academy Wizard Defender",
POLL_IGNORE_CORTICAL_BORER = "Cortical Borer",
- POLL_IGNORE_PAI = "Personal AI"
+ POLL_IGNORE_PAI = JOB_PERSONAL_AI,
))
//Skyrat Edit Stop: Cortical Borer
GLOBAL_LIST_INIT(poll_ignore, init_poll_ignore())
diff --git a/code/_onclick/item_attack.dm b/code/_onclick/item_attack.dm
index f1ce7ede67f..86febd87c0d 100644
--- a/code/_onclick/item_attack.dm
+++ b/code/_onclick/item_attack.dm
@@ -144,8 +144,8 @@
return SECONDARY_ATTACK_CALL_NORMAL
-/obj/attackby(obj/item/I, mob/living/user, params)
- return ..() || ((obj_flags & CAN_BE_HIT) && I.attack_atom(src, user, params))
+/obj/attackby(obj/item/attacking_item, mob/user, params)
+ return ..() || ((obj_flags & CAN_BE_HIT) && attacking_item.attack_atom(src, user, params))
/mob/living/attackby(obj/item/attacking_item, mob/living/user, params)
if(..())
diff --git a/code/game/objects/items/devices/paicard.dm b/code/game/objects/items/devices/paicard.dm
index beee288737e..b465dec9060 100644
--- a/code/game/objects/items/devices/paicard.dm
+++ b/code/game/objects/items/devices/paicard.dm
@@ -9,7 +9,9 @@
w_class = WEIGHT_CLASS_SMALL
slot_flags = ITEM_SLOT_BELT
custom_premium_price = PAYCHECK_HARD * 1.25
- ///don't spam alert messages.
+ resistance_flags = FIRE_PROOF | ACID_PROOF | INDESTRUCTIBLE
+
+ /// Spam alert prevention
var/alert_cooldown
/// If the pAIcard is slotted in a PDA
var/slotted = FALSE
@@ -17,7 +19,6 @@
var/mob/living/silicon/pai/pai
///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)
user.visible_message(span_suicide("[user] is staring sadly at [src]! [user.p_they()] can't keep living without real human intimacy!"))
@@ -102,16 +103,16 @@
break
if(isnull(candidate))
return FALSE
- if(src.pai)
+ if(pai)
return FALSE
if(SSpai.check_ready(candidate) != candidate)
return FALSE
/// The newly downloaded pAI personality
- var/mob/living/silicon/pai/pai = new(src)
- pai.name = candidate.name || pick(GLOB.ninja_names)
- pai.real_name = pai.name
- pai.key = candidate.key
- src.setPersonality(pai)
+ var/mob/living/silicon/pai/new_pai = new(src)
+ new_pai.name = candidate.name || pick(GLOB.ninja_names)
+ new_pai.real_name = new_pai.name
+ new_pai.key = candidate.key
+ setPersonality(new_pai)
SSpai.candidates -= candidate
if("fix_speech")
to_chat(pai, span_notice("Your owner has corrected your speech modulation!"))
@@ -159,13 +160,15 @@
to_chat(pai, span_notice("Your owner has [transmit_holder ? "enabled" : "disabled"] your [transmitting ? "outgoing" : "incoming"] radio transmissions!"))
if("wipe_pai")
var/confirm = tgui_alert(usr, "Are you certain you wish to delete the current personality? This action cannot be undone.", "Personality Wipe", list("Yes", "No"))
- if(confirm == "Yes")
- if(pai)
- to_chat(pai, span_warning("You feel yourself slipping away from reality."))
- to_chat(pai, span_danger("Byte by byte you lose your sense of self."))
- to_chat(pai, span_userdanger("Your mental faculties leave you."))
- to_chat(pai, span_rose("oblivion... "))
- qdel(pai)
+ if(confirm != "Yes")
+ return
+ if(!pai)
+ return
+ to_chat(pai, span_warning("You feel yourself slipping away from reality."))
+ to_chat(pai, span_danger("Byte by byte you lose your sense of self."))
+ to_chat(pai, span_userdanger("Your mental faculties leave you."))
+ to_chat(pai, span_rose("oblivion... "))
+ qdel(pai)
return
// WIRE_SIGNAL = 1
diff --git a/code/game/objects/items/devices/radio/radio.dm b/code/game/objects/items/devices/radio/radio.dm
index 05c9ad4e8c5..b07cdf80e12 100644
--- a/code/game/objects/items/devices/radio/radio.dm
+++ b/code/game/objects/items/devices/radio/radio.dm
@@ -439,16 +439,13 @@
else
. += span_notice("It cannot be modified or attached.")
-/obj/item/radio/attackby(obj/item/W, mob/user, params)
+/obj/item/radio/screwdriver_act(mob/living/user, obj/item/tool)
add_fingerprint(user)
- if(W.tool_behaviour == TOOL_SCREWDRIVER)
- unscrewed = !unscrewed
- if(unscrewed)
- to_chat(user, span_notice("The radio can now be attached and modified!"))
- else
- to_chat(user, span_notice("The radio can no longer be modified or attached!"))
+ unscrewed = !unscrewed
+ if(unscrewed)
+ to_chat(user, span_notice("The radio can now be attached and modified!"))
else
- return ..()
+ to_chat(user, span_notice("The radio can no longer be modified or attached!"))
/obj/item/radio/emp_act(severity)
. = ..()
@@ -508,36 +505,36 @@
. = ..()
set_frequency(FREQ_SYNDICATE)
-/obj/item/radio/borg/attackby(obj/item/W, mob/user, params)
+/obj/item/radio/borg/screwdriver_act(mob/living/user, obj/item/tool)
+ if(!keyslot)
+ to_chat(user, span_warning("This radio doesn't have any encryption keys!"))
+ return
- if(W.tool_behaviour == TOOL_SCREWDRIVER)
- if(keyslot)
- for(var/ch_name in channels)
- SSradio.remove_object(src, GLOB.radiochannels[ch_name])
- secure_radio_connections[ch_name] = null
+ for(var/ch_name in channels)
+ SSradio.remove_object(src, GLOB.radiochannels[ch_name])
+ secure_radio_connections[ch_name] = null
+ if(keyslot)
+ var/turf/user_turf = get_turf(user)
+ if(user_turf)
+ keyslot.forceMove(user_turf)
+ keyslot = null
- if(keyslot)
- var/turf/T = get_turf(user)
- if(T)
- keyslot.forceMove(T)
- keyslot = null
+ recalculateChannels()
+ to_chat(user, span_notice("You pop out the encryption key in the radio."))
+ return ..()
- recalculateChannels()
- to_chat(user, span_notice("You pop out the encryption key in the radio."))
+/obj/item/radio/borg/attackby(obj/item/attacking_item, mob/user, params)
- else
- to_chat(user, span_warning("This radio doesn't have any encryption keys!"))
-
- else if(istype(W, /obj/item/encryptionkey/))
+ if(istype(attacking_item, /obj/item/encryptionkey))
if(keyslot)
to_chat(user, span_warning("The radio can't hold another key!"))
return
if(!keyslot)
- if(!user.transferItemToLoc(W, src))
+ if(!user.transferItemToLoc(attacking_item, src))
return
- keyslot = W
+ keyslot = attacking_item
recalculateChannels()
diff --git a/code/game/objects/items/paiwire.dm b/code/game/objects/items/paiwire.dm
index f9ba79253ef..118da08cf44 100644
--- a/code/game/objects/items/paiwire.dm
+++ b/code/game/objects/items/paiwire.dm
@@ -6,7 +6,7 @@
item_flags = NOBLUDGEON
var/obj/machinery/machine //what machine we're currently hacking.
-/obj/item/pai_cable/machine/Destroy()
+/obj/item/pai_cable/Destroy()
machine = null
return ..()
diff --git a/code/game/say.dm b/code/game/say.dm
index b1a8bda6bae..f01573a16d5 100644
--- a/code/game/say.dm
+++ b/code/game/say.dm
@@ -225,7 +225,7 @@ INITIALIZE_IMMEDIATE(/atom/movable/virtualspeaker)
var/mob/living/silicon/robot/B = M
job = "[B.designation] Cyborg"
else if(istype(M, /mob/living/silicon/pai)) // Personal AI (pAI)
- job = "Personal AI"
+ job = JOB_PERSONAL_AI
else if(isobj(M)) // Cold, emotionless machines
job = "Machine"
else // Unidentifiable mob
diff --git a/code/modules/mob/living/silicon/pai/login.dm b/code/modules/mob/living/silicon/pai/login.dm
new file mode 100644
index 00000000000..0f418c4bf32
--- /dev/null
+++ b/code/modules/mob/living/silicon/pai/login.dm
@@ -0,0 +1,10 @@
+/mob/living/silicon/pai/Login()
+ . = ..()
+ if(!. || !client)
+ return FALSE
+
+ client.perspective = EYE_PERSPECTIVE
+ if(holoform)
+ client.eye = src
+ else
+ client.eye = card
diff --git a/code/modules/mob/living/silicon/pai/pai.dm b/code/modules/mob/living/silicon/pai/pai.dm
index ddd88e42018..69e02e80d21 100644
--- a/code/modules/mob/living/silicon/pai/pai.dm
+++ b/code/modules/mob/living/silicon/pai/pai.dm
@@ -1,6 +1,9 @@
/mob/living/silicon/pai
name = "pAI"
icon = 'icons/mob/pai.dmi'
+ held_lh = 'icons/mob/pai_item_lh.dmi'
+ held_rh = 'icons/mob/pai_item_rh.dmi'
+ head_icon = 'icons/mob/pai_item_head.dmi'
icon_state = "repairbot"
mouse_opacity = MOUSE_OPACITY_ICON
density = FALSE
@@ -16,74 +19,78 @@
pull_force = 0
move_resist = 0
worn_slot_flags = ITEM_SLOT_HEAD
- held_lh = 'icons/mob/pai_item_lh.dmi'
- held_rh = 'icons/mob/pai_item_rh.dmi'
- head_icon = 'icons/mob/pai_item_head.dmi'
radio = /obj/item/radio/headset/silicon/pai
can_buckle_to = FALSE
mobility_flags = MOBILITY_FLAGS_REST_CAPABLE_DEFAULT
- var/network = "ss13"
- var/obj/machinery/camera/current = null
+
+ /// The card we inhabit
+ var/obj/item/paicard/card
+
/// Used as currency to purchase different abilities
var/ram = 100
/// Installed software on the pAI
var/list/software = list()
- /// current user's DNA
- var/userDNA
- /// The card we inhabit
- var/obj/item/paicard/card
- /// Are we hacking a door?
- var/hacking = FALSE
- /// The progress for hacking
- var/datum/progressbar/hackbar
+ /// The strength of the internal flashlight
+ var/brightness_power = 5
/// Changes the display to syndi if true
var/emagged = FALSE
- var/speakStatement = "states"
- var/speakExclamation = "declares"
- var/speakDoubleExclamation = "alarms"
- var/speakQuery = "queries"
- /// The cable we produce when hacking a door
- var/obj/item/pai_cable/hacking_cable
/// Name of the one who commands us
var/master
/// DNA string for owner verification
var/master_dna
+ /// Whether we are currently holoformed
+ var/holoform = FALSE
+ /// Whether the pAI can enter holoform or not
+ var/canholo = TRUE
+ /// Whether this pAI can transmit radio messages
+ var/can_transmit = TRUE
+ /// Whether this pAI can recieve radio messages
+ var/can_receive = TRUE
-// Various software-specific vars
+
+ // Various software-specific vars
/// Toggles whether the Security HUD is active or not
var/secHUD = FALSE
/// Toggles whether the Medical HUD is active or not
var/medHUD = FALSE
+ /// Toggles whether the pAI can hold encryption keys or not
+ var/encryptmod = FALSE
/// Toggles whether universal translator has been activated. Cannot be reversed
var/languages_granted = FALSE
- /// The airlock being hacked
- var/obj/machinery/door/hackdoor
- /// Possible values: 0 - 100, >= 100 means the hack is complete and will be reset upon next check
- var/hackprogress = 0
-
- // Software
/// Atmospheric analyzer
var/obj/item/analyzer/atmos_analyzer
/// AI's signaler
var/obj/item/assembly/signaler/internal/signaler
- /// Synthesizer
+ /// pAI Synthesizer
var/obj/item/instrument/piano_synth/internal_instrument
/// pAI Newscaster
var/obj/machinery/newscaster
/// pAI healthanalyzer
var/obj/item/healthanalyzer/hostscan
/// Internal pAI GPS, enabled if pAI downloads GPS software, and then uses it.
- var/obj/item/gps/pai/internal_gps = null
+ var/obj/item/gps/pai/internal_gps
+ /// The cable we produce when hacking a door
+ var/obj/item/pai_cable/hacking_cable
- var/encryptmod = FALSE
- var/holoform = FALSE
- var/canholo = TRUE
- var/can_transmit = TRUE
- var/can_receive = TRUE
+ /// The current chasis that will appear when in holoform
var/chassis = "repairbot"
- var/list/possible_chassis = list("cat" = TRUE, "mouse" = TRUE, "monkey" = TRUE, "corgi" = FALSE, "fox" = FALSE, "repairbot" = TRUE, "rabbit" = TRUE, "bat" = FALSE, "butterfly" = FALSE, "hawk" = FALSE, "lizard" = FALSE, "duffel" = TRUE) //assoc value is whether it can be picked up.
+ /// List of all possible chasises. TRUE means the pAI can be picked up in this chasis.
+ var/list/possible_chassis = list(
+ "cat" = TRUE,
+ "mouse" = TRUE,
+ "monkey" = TRUE,
+ "corgi" = FALSE,
+ "fox" = FALSE,
+ "repairbot" = TRUE,
+ "rabbit" = TRUE,
+ "bat" = FALSE,
+ "butterfly" = FALSE,
+ "hawk" = FALSE,
+ "lizard" = FALSE,
+ "duffel" = TRUE,
+ )
var/emitterhealth = 20
var/emittermaxhealth = 20
@@ -92,62 +99,68 @@
var/emitteroverloadcd = 100
var/emittersemicd = FALSE
- var/overload_ventcrawl = 0
- var/overload_bulletblock = 0 //Why is this a good idea?
- var/overload_maxhealth = 0
- var/silent = FALSE
- var/brightness_power = 5
+ /// Bool that determines if the pAI can refresh medical/security records.
+ var/refresh_spam = FALSE
+ /// Cached list for medical records to send as static data
+ var/list/medical_records = list()
+ /// Cached list for security records to send as static data
+ var/list/security_records = list()
+
+ var/list/available_software = list(
+ "crew manifest" = 5,
+ "digital messenger" = 5,
+ "atmosphere sensor" = 5,
+ "photography module" = 5,
+ "camera zoom" = 10,
+ "printer module" = 10,
+ "remote signaler" = 10,
+ "medical records" = 10,
+ "security records" = 10,
+ "host scan" = 10,
+ "medical HUD" = 20,
+ "security HUD" = 20,
+ "loudness booster" = 20,
+ "newscaster" = 20,
+ "door jack" = 25,
+ "encryption keys" = 25,
+ "internal gps" = 35,
+ "universal translator" = 35,
+ )
/mob/living/silicon/pai/add_sensors() //pAIs have to buy their HUDs
return
-/mob/living/silicon/pai/handle_atom_del(atom/A)
- if(A == hacking_cable)
+/mob/living/silicon/pai/handle_atom_del(atom/deleting_atom)
+ if(deleting_atom == hacking_cable)
hacking_cable = null
if(!QDELETED(card))
card.update_appearance()
- if(A == atmos_analyzer)
+ if(deleting_atom == atmos_analyzer)
atmos_analyzer = null
- if(A == internal_instrument)
+ if(deleting_atom == internal_instrument)
internal_instrument = null
- if(A == newscaster)
+ if(deleting_atom == newscaster)
newscaster = null
- if(A == signaler)
+ if(deleting_atom == signaler)
signaler = null
- if(A == hostscan)
+ if(deleting_atom == hostscan)
hostscan = null
- if(A == internal_gps)
+ if(deleting_atom == internal_gps)
internal_gps = null
return ..()
-/mob/living/silicon/pai/Destroy()
- QDEL_NULL(atmos_analyzer)
- QDEL_NULL(internal_instrument)
- QDEL_NULL(hacking_cable)
- QDEL_NULL(newscaster)
- QDEL_NULL(signaler)
- QDEL_NULL(hostscan)
- QDEL_NULL(internal_gps)
- if(!QDELETED(card) && loc != card)
- card.forceMove(drop_location())
- card.pai = null //these are otherwise handled by paicard/handle_atom_del()
- card.emotion_icon = initial(card.emotion_icon)
- card.update_appearance()
- GLOB.pai_list -= src
- return ..()
-
/mob/living/silicon/pai/Initialize(mapload)
- var/obj/item/paicard/P = loc
+ var/obj/item/paicard/pai_card = loc
START_PROCESSING(SSfastprocess, src)
GLOB.pai_list += src
make_laws()
- if(!istype(P)) //when manually spawning a pai, we create a card to put it into.
- var/newcardloc = P
- P = new /obj/item/paicard(newcardloc)
- P.setPersonality(src)
- forceMove(P)
- card = P
- job = "Personal AI"
+ if(!istype(pai_card)) //when manually spawning a pai, we create a card to put it into.
+ var/newcardloc = pai_card
+ pai_card = new /obj/item/paicard(newcardloc)
+ pai_card.setPersonality(src)
+ forceMove(pai_card)
+ card = pai_card
+ job = JOB_PERSONAL_AI
atmos_analyzer = new /obj/item/analyzer(src)
signaler = new /obj/item/assembly/signaler/internal(src)
hostscan = new /obj/item/healthanalyzer(src)
@@ -167,57 +180,37 @@
ADD_TRAIT(src, TRAIT_IMMOBILIZED, PAI_FOLDED)
ADD_TRAIT(src, TRAIT_HANDS_BLOCKED, PAI_FOLDED)
+/mob/living/silicon/pai/Destroy()
+ QDEL_NULL(atmos_analyzer)
+ QDEL_NULL(internal_instrument)
+ QDEL_NULL(hacking_cable)
+ QDEL_NULL(newscaster)
+ QDEL_NULL(signaler)
+ QDEL_NULL(hostscan)
+ QDEL_NULL(internal_gps)
+ if(!QDELETED(card) && loc != card)
+ card.forceMove(drop_location())
+ card.pai = null //these are otherwise handled by paicard/handle_atom_del()
+ card.emotion_icon = initial(card.emotion_icon)
+ card.update_appearance()
+ GLOB.pai_list -= src
+ return ..()
/mob/living/silicon/pai/proc/pdaconfig()
//PDA
- aiPDA = new/obj/item/pda/ai(src)
+ aiPDA = new /obj/item/pda/ai(src)
aiPDA.owner = real_name
aiPDA.ownjob = "pAI Messenger"
- aiPDA.name = real_name + " (" + aiPDA.ownjob + ")"
-
-/mob/living/silicon/pai/proc/process_hack(delta_time, times_fired)
- 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 >= HACK_COMPLETE)
- hackprogress = 0
- hacking = FALSE
- hackbar.end_progress()
- var/obj/machinery/door/door = hacking_cable.machine
- door.open()
- QDEL_NULL(hackbar)
- QDEL_NULL(hacking_cable)
+ aiPDA.name = "[real_name] ([aiPDA.ownjob])"
/mob/living/silicon/pai/make_laws()
laws = new /datum/ai_laws/pai()
return TRUE
-/mob/living/silicon/pai/Login()
- . = ..()
- if(!. || !client)
- return FALSE
-
- client.perspective = EYE_PERSPECTIVE
- if(holoform)
- client.eye = src
- else
- client.eye = card
-
/mob/living/silicon/pai/get_status_tab_items()
. += ..()
if(!stat)
- . += text("Emitter Integrity: [emitterhealth * (100/emittermaxhealth)]")
+ . += text("Emitter Integrity: [emitterhealth * (100 / emittermaxhealth)]")
else
. += text("Systems nonfunctional")
@@ -236,65 +229,6 @@
if(delold)
qdel(src)
-/datum/action/innate/pai
- name = "PAI Action"
- icon_icon = 'icons/mob/actions/actions_silicon.dmi'
- var/mob/living/silicon/pai/P
-
-/datum/action/innate/pai/Trigger()
- if(!ispAI(owner))
- return 0
- P = owner
-
-/datum/action/innate/pai/software
- name = "Software Interface"
- button_icon_state = "pai"
- background_icon_state = "bg_tech"
-
-/datum/action/innate/pai/software/Trigger()
- ..()
- P.ui_act()
-
-/datum/action/innate/pai/shell
- name = "Toggle Holoform"
- button_icon_state = "pai_holoform"
- background_icon_state = "bg_tech"
-
-/datum/action/innate/pai/shell/Trigger()
- ..()
- if(P.holoform)
- P.fold_in(0)
- else
- P.fold_out()
-
-/datum/action/innate/pai/chassis
- name = "Holochassis Appearance Composite"
- button_icon_state = "pai_chassis"
- background_icon_state = "bg_tech"
-
-/datum/action/innate/pai/chassis/Trigger()
- ..()
- P.choose_chassis()
-
-/datum/action/innate/pai/rest
- name = "Rest"
- button_icon_state = "pai_rest"
- background_icon_state = "bg_tech"
-
-/datum/action/innate/pai/rest/Trigger()
- ..()
- P.toggle_resting()
-
-/datum/action/innate/pai/light
- name = "Toggle Integrated Lights"
- icon_icon = 'icons/mob/actions/actions_spells.dmi'
- button_icon_state = "emp"
- background_icon_state = "bg_tech"
-
-/datum/action/innate/pai/light/Trigger()
- ..()
- P.toggle_integrated_light()
-
/mob/living/silicon/pai/Process_Spacemove(movement_dir = 0)
. = ..()
if(!.)
@@ -307,21 +241,6 @@
. = ..()
. += "A personal AI in holochassis mode. Its master ID string seems to be [master]."
-/mob/living/silicon/pai/Life(delta_time = SSMOBS_DT, times_fired)
- . = ..()
- if(QDELETED(src) || stat == DEAD)
- return
- if(hacking_cable)
- if(get_dist(src, hacking_cable) > 1)
- var/turf/T = get_turf(src)
- T.visible_message(span_warning("[hacking_cable] rapidly retracts back into its spool."), span_hear("You hear a click and the sound of wire spooling rapidly."))
- QDEL_NULL(hacking_cable)
- if(!QDELETED(card))
- card.update_appearance()
- else if(hacking)
- process_hack(delta_time, times_fired)
- silent = max(silent - (0.5 * delta_time), 0)
-
/mob/living/silicon/pai/updatehealth()
if(status_flags & GODMODE)
return
@@ -337,8 +256,11 @@
return ..()
+/obj/item/paicard/screwdriver_act(mob/living/user, obj/item/tool)
+ return pai.radio.screwdriver_act(user, tool)
+
/obj/item/paicard/attackby(obj/item/used, mob/user, params)
- if(pai && (istype(used, /obj/item/encryptionkey) || used.tool_behaviour == TOOL_SCREWDRIVER))
+ if(pai && istype(used, /obj/item/encryptionkey))
if(!pai.encryptmod)
to_chat(user, span_alert("Encryption Key ports not configured."))
return
diff --git a/code/modules/mob/living/silicon/pai/pai_actions.dm b/code/modules/mob/living/silicon/pai/pai_actions.dm
new file mode 100644
index 00000000000..c6e92cea578
--- /dev/null
+++ b/code/modules/mob/living/silicon/pai/pai_actions.dm
@@ -0,0 +1,58 @@
+/datum/action/innate/pai
+ name = "PAI Action"
+ icon_icon = 'icons/mob/actions/actions_silicon.dmi'
+ var/mob/living/silicon/pai/pai_owner
+
+/datum/action/innate/pai/Trigger()
+ if(!ispAI(owner))
+ return FALSE
+ pai_owner = owner
+
+/datum/action/innate/pai/software
+ name = "Software Interface"
+ button_icon_state = "pai"
+ background_icon_state = "bg_tech"
+
+/datum/action/innate/pai/software/Trigger()
+ ..()
+ pai_owner.ui_act()
+
+/datum/action/innate/pai/shell
+ name = "Toggle Holoform"
+ button_icon_state = "pai_holoform"
+ background_icon_state = "bg_tech"
+
+/datum/action/innate/pai/shell/Trigger()
+ ..()
+ if(pai_owner.holoform)
+ pai_owner.fold_in(0)
+ else
+ pai_owner.fold_out()
+
+/datum/action/innate/pai/chassis
+ name = "Holochassis Appearance Composite"
+ button_icon_state = "pai_chassis"
+ background_icon_state = "bg_tech"
+
+/datum/action/innate/pai/chassis/Trigger()
+ ..()
+ pai_owner.choose_chassis()
+
+/datum/action/innate/pai/rest
+ name = "Rest"
+ button_icon_state = "pai_rest"
+ background_icon_state = "bg_tech"
+
+/datum/action/innate/pai/rest/Trigger()
+ ..()
+ pai_owner.toggle_resting()
+
+/datum/action/innate/pai/light
+ name = "Toggle Integrated Lights"
+ icon_icon = 'icons/mob/actions/actions_spells.dmi'
+ button_icon_state = "emp"
+ background_icon_state = "bg_tech"
+
+/datum/action/innate/pai/light/Trigger()
+ ..()
+ pai_owner.toggle_integrated_light()
diff --git a/code/modules/mob/living/silicon/pai/pai_defense.dm b/code/modules/mob/living/silicon/pai/pai_defense.dm
index 6a4263fd56c..517f2608b5f 100644
--- a/code/modules/mob/living/silicon/pai/pai_defense.dm
+++ b/code/modules/mob/living/silicon/pai/pai_defense.dm
@@ -6,9 +6,8 @@
. = ..()
if(. & EMP_PROTECT_SELF)
return
- take_holo_damage(50/severity)
- Paralyze(400/severity)
- silent = max(20/severity, silent)
+ take_holo_damage(50 / severity)
+ Stun(400 / severity)
if(holoform)
fold_in(force = TRUE)
//Need more effects that aren't instadeath or permanent law corruption.
@@ -41,19 +40,20 @@
Paralyze(200)
/mob/living/silicon/pai/attack_hand(mob/living/carbon/human/user, list/modifiers)
- if(user.combat_mode)
- user.do_attack_animation(src)
- if (user.name == master)
- visible_message(span_notice("Responding to its master's touch, [src] disengages its holochassis emitter, rapidly losing coherence."))
- if(do_after(user, 1 SECONDS, TRUE, src))
- fold_in()
- if(user.put_in_hands(card))
- user.visible_message(span_notice("[user] promptly scoops up [user.p_their()] pAI's card."))
- else
- visible_message(span_danger("[user] stomps on [src]!."))
- take_holo_damage(2)
- else
+ if(!user.combat_mode)
visible_message(span_notice("[user] gently pats [src] on the head, eliciting an off-putting buzzing from its holographic field."))
+ return
+ user.do_attack_animation(src)
+ if(user.name != master)
+ visible_message(span_danger("[user] stomps on [src]!."))
+ take_holo_damage(2)
+ return
+ visible_message(span_notice("Responding to its master's touch, [src] disengages its holochassis emitter, rapidly losing coherence."))
+ if(!do_after(user, 1 SECONDS, TRUE, src))
+ return
+ fold_in()
+ if(user.put_in_hands(card))
+ user.visible_message(span_notice("[user] promptly scoops up [user.p_their()] pAI's card."))
/mob/living/silicon/pai/bullet_act(obj/projectile/Proj)
if(Proj.stun)
@@ -61,14 +61,15 @@
src.visible_message(span_warning("The electrically-charged projectile disrupts [src]'s holomatrix, forcing [src] to fold in!"))
. = ..(Proj)
-/mob/living/silicon/pai/IgniteMob(mob/living/silicon/pai/P)
- return FALSE //No we're not flammable
+/mob/living/silicon/pai/IgniteMob()
+ fire_stacks = 0
+ . = ..()
/mob/living/silicon/pai/proc/take_holo_damage(amount)
emitterhealth = clamp((emitterhealth - amount), -50, emittermaxhealth)
if(emitterhealth < 0)
fold_in(force = TRUE)
- if (amount > 0)
+ if(amount > 0)
to_chat(src, span_userdanger("The impact degrades your holochassis!"))
return amount
diff --git a/code/modules/mob/living/silicon/pai/pai_say.dm b/code/modules/mob/living/silicon/pai/pai_say.dm
index d98f615a879..b35abfe7f9d 100644
--- a/code/modules/mob/living/silicon/pai/pai_say.dm
+++ b/code/modules/mob/living/silicon/pai/pai_say.dm
@@ -1,8 +1,2 @@
-/mob/living/silicon/pai/say(message, bubble_type, list/spans = list(), sanitize = TRUE, datum/language/language = null, ignore_spam = FALSE, forced = null, filterproof = null)
- if(silent)
- to_chat(src, span_warning("Communication circuits remain uninitialized."))
- else
- ..()
-
/mob/living/silicon/pai/binarycheck()
return radio?.translate_binary
diff --git a/code/modules/mob/living/silicon/pai/personality.dm b/code/modules/mob/living/silicon/pai/personality.dm
index b00fe994c10..92cf5747801 100644
--- a/code/modules/mob/living/silicon/pai/personality.dm
+++ b/code/modules/mob/living/silicon/pai/personality.dm
@@ -1,11 +1,11 @@
-/*
- name
- key
- description
- role
- comments
- ready = 0
-*/
+/**
+ * name
+ * key
+ * description
+ * role
+ * comments
+ * ready = TRUE
+ */
/datum/pai_candidate/proc/savefile_path(mob/user)
return "data/player_saves/[user.ckey[1]]/[user.ckey]/pai.sav"
@@ -24,17 +24,17 @@
// loads the savefile corresponding to the mob's ckey
// if silent=true, report incompatible savefiles
-// returns 1 if loaded (or file was incompatible)
-// returns 0 if savefile did not exist
+// returns TRUE if loaded (or file was incompatible)
+// returns FALSE if savefile did not exist
/datum/pai_candidate/proc/savefile_load(mob/user, silent = TRUE)
if (is_guest_key(user.key))
- return 0
+ return FALSE
var/path = savefile_path(user)
if (!fexists(path))
- return 0
+ return FALSE
var/savefile/F = new /savefile(path)
@@ -48,9 +48,9 @@
fdel(path)
if (!silent)
tgui_alert(user, "Your savefile was incompatible with this version and was deleted.")
- return 0
+ return FALSE
F["name"] >> src.name
F["description"] >> src.description
F["comments"] >> src.comments
- return 1
+ return TRUE
diff --git a/code/modules/mob/living/silicon/pai/software.dm b/code/modules/mob/living/silicon/pai/software.dm
index 6f541bdbf99..0c4a0fcdc08 100644
--- a/code/modules/mob/living/silicon/pai/software.dm
+++ b/code/modules/mob/living/silicon/pai/software.dm
@@ -1,35 +1,3 @@
-/mob/living/silicon/pai/var/list/available_software = list(
- //Nightvision
- //T-Ray
- //radiation eyes
- //chem goggs
- //mesons
- "crew manifest" = 5,
- "digital messenger" = 5,
- "atmosphere sensor" = 5,
- "photography module" = 5,
- "camera zoom" = 10,
- "printer module" = 10,
- "remote signaler" = 10,
- "medical records" = 10,
- "security records" = 10,
- "host scan" = 10,
- "medical HUD" = 20,
- "security HUD" = 20,
- "loudness booster" = 20,
- "newscaster" = 20,
- "door jack" = 25,
- "encryption keys" = 25,
- "internal gps" = 35,
- "universal translator" = 35
- )
-/// Bool that determines if the pAI can refresh medical/security records.
-/mob/living/silicon/pai/var/refresh_spam = FALSE
-/// Cached list for medical records to send as static data
-/mob/living/silicon/pai/var/list/medical_records = list()
-/// Cached list for security records to send as static data
-/mob/living/silicon/pai/var/list/security_records = list()
-
// Opens TGUI interface
/mob/living/silicon/pai/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
@@ -124,10 +92,8 @@
if("door_jack")
if(params["jack"] == "jack")
if(hacking_cable?.machine)
- hackdoor = hacking_cable.machine
- hackloop()
+ hack_door()
if(params["jack"] == "cancel")
- hackdoor = null
QDEL_NULL(hacking_cable)
if(params["jack"] == "cable")
extendcable()
@@ -213,20 +179,20 @@
return
to_chat(pai, span_notice("Requesting a DNA sample."))
var/confirm = tgui_alert(master, "[pai] is requesting a DNA sample from you. Will you allow it to confirm your identity?", "Checking DNA", list("Yes", "No"))
- if(confirm == "Yes")
- master.visible_message(span_notice("[master] presses [master.p_their()] thumb against [pai]."),\
- span_notice("You press your thumb against [pai]."),\
- span_notice("[pai] makes a sharp clicking sound as it extracts DNA material from [master]."))
- if(!master.has_dna())
- to_chat(pai, "No DNA detected.")
- return
- to_chat(pai, "[master]'s UE string : [master.dna.unique_enzymes]
")
- if(master.dna.unique_enzymes == pai.master_dna)
- to_chat(pai, "DNA is a match to stored Master DNA.")
- else
- to_chat(pai, "DNA does not match stored Master DNA.")
- else
+ if(confirm != "Yes")
to_chat(pai, span_warning("[master] does not seem like [master.p_theyre()] going to provide a DNA sample willingly."))
+ return
+ master.visible_message(span_notice("[master] presses [master.p_their()] thumb against [pai]."),\
+ span_notice("You press your thumb against [pai]."),\
+ span_notice("[pai] makes a sharp clicking sound as it extracts DNA material from [master]."))
+ if(!master.has_dna())
+ to_chat(pai, "No DNA detected.")
+ return
+ to_chat(pai, "[master]'s UE string : [master.dna.unique_enzymes]
")
+ if(master.dna.unique_enzymes == pai.master_dna)
+ to_chat(pai, span_bold("DNA is a match to stored Master DNA."))
+ else
+ to_chat(pai, span_bold("DNA does not match stored Master DNA."))
/**
* Host scan supporting proc
@@ -253,35 +219,46 @@
/mob/living/silicon/pai/proc/extendcable()
QDEL_NULL(hacking_cable) //clear any old cables
hacking_cable = new
- var/transfered_to_mob
- if(isliving(card.loc))
- var/mob/living/hacker = card.loc
- if(hacker.put_in_hands(hacking_cable))
- transfered_to_mob = TRUE
- hacker.visible_message(span_warning("A port on [src] opens to reveal \a [hacking_cable], which you quickly grab hold of."), span_hear("You hear the soft click of something light and manage to catch hold of [hacking_cable]."))
- if(!transfered_to_mob)
- hacking_cable.forceMove(drop_location())
- hacking_cable.visible_message(span_warning("A port on [src] opens to reveal \a [hacking_cable], which promptly falls to the floor."), span_hear("You hear the soft click of something light and hard falling to the ground."))
+ if(!isliving(card.loc))
+ return
+ var/mob/living/hacker = card.loc
+ if(hacker.put_in_hands(hacking_cable))
+ hacker.visible_message(span_warning("A port on [src] opens to reveal \a [hacking_cable], which you quickly grab hold of."), span_hear("You hear the soft click of something light and manage to catch hold of [hacking_cable]."))
+ return
+ hacking_cable.forceMove(drop_location())
+ hacking_cable.visible_message(span_warning("A port on [src] opens to reveal \a [hacking_cable], which promptly falls to the floor."), span_hear("You hear the soft click of something light and hard falling to the ground."))
/**
* Door jacking supporting proc
*
- * This begins the hacking process on a door.
- * Mostly, this gives UI feedback, while the "hack"
- * is handled inside pai.dm itself.
+ * This will, after alerting any AIs on station, begin to hack open a door.
+ * After a 10 second timer, the door will crack open, provided they don't move out of the way.
*/
-/mob/living/silicon/pai/proc/hackloop()
+
+/mob/living/silicon/pai/proc/hack_door()
var/turf/turf = get_turf(src)
playsound(src, 'sound/machines/airlock_alien_prying.ogg', 50, TRUE)
to_chat(usr, span_boldnotice("You begin overriding the airlock security protocols."))
- for(var/mob/living/silicon/ai/AI in GLOB.player_list)
+ for(var/mob/living/silicon/ai/all_ais in GLOB.player_list)
+ if(!all_ais.stat)
+ continue
if(turf.loc)
- to_chat(AI, "Network Alert: Brute-force security override in progress in [turf.loc].")
+ to_chat(all_ais, span_boldannounce("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
+ to_chat(all_ais, span_boldannounce("Network Alert: Brute-force security override in progress. Unable to pinpoint location."))
+ //Now begin hacking
+ if(!do_after(src, 10 SECONDS, hacking_cable.machine, timed_action_flags = NONE, progress = TRUE))
+ to_chat(src, span_notice("Door Jack: Connection to airlock has been lost. Hack aborted."))
+ hacking_cable.visible_message(
+ span_warning("[hacking_cable] rapidly retracts back into its spool."),\
+ span_hear("You hear a click and the sound of wire spooling rapidly."))
+ QDEL_NULL(hacking_cable)
+ if(!QDELETED(card))
+ card.update_appearance()
+ return
+ var/obj/machinery/door/door = hacking_cable.machine
+ door.open()
+ QDEL_NULL(hacking_cable)
/**
* Proc that switches whether a pAI can refresh
diff --git a/code/modules/mob/living/silicon/robot/robot_defense.dm b/code/modules/mob/living/silicon/robot/robot_defense.dm
index 11f94a5361e..5035055472e 100644
--- a/code/modules/mob/living/silicon/robot/robot_defense.dm
+++ b/code/modules/mob/living/silicon/robot/robot_defense.dm
@@ -82,7 +82,7 @@ GLOBAL_LIST_INIT(blacklisted_borg_hats, typecacheof(list( //Hats that don't real
if(shell)
to_chat(user, span_warning("You cannot seem to open the radio compartment!")) //Prevent AI radio key theft
else if(radio)
- radio.attackby(W,user)//Push it to the radio to let it handle everything
+ radio.screwdriver_act(user, W)//Push it to the radio to let it handle everything
else
to_chat(user, span_warning("Unable to locate a radio!"))
update_icons()
@@ -159,7 +159,7 @@ GLOBAL_LIST_INIT(blacklisted_borg_hats, typecacheof(list( //Hats that don't real
MOD.install(laws, user) //Proc includes a success mesage so we don't need another one
return
- if(istype(W, /obj/item/encryptionkey/) && opened)
+ if(istype(W, /obj/item/encryptionkey) && opened)
if(radio)//sanityyyyyy
radio.attackby(W,user)//GTFO, you have your own procs
else
diff --git a/tgstation.dme b/tgstation.dme
index d099575acb1..901908aa0b2 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -111,7 +111,6 @@
#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"
@@ -3149,7 +3148,9 @@
#include "code\modules\mob\living\silicon\ai\freelook\chunk.dm"
#include "code\modules\mob\living\silicon\ai\freelook\eye.dm"
#include "code\modules\mob\living\silicon\pai\death.dm"
+#include "code\modules\mob\living\silicon\pai\login.dm"
#include "code\modules\mob\living\silicon\pai\pai.dm"
+#include "code\modules\mob\living\silicon\pai\pai_actions.dm"
#include "code\modules\mob\living\silicon\pai\pai_defense.dm"
#include "code\modules\mob\living\silicon\pai\pai_say.dm"
#include "code\modules\mob\living\silicon\pai\pai_shell.dm"