Reworks pAIs (#68241)

A pretty heavy refactor for pAIs that just spilled into a rework.

Attempts to fully document and organize backend code.
Fixes a large number of bugs left untouched for a decade.
Breaks down the frontend into subcomponents.
Rebalances their software modules.
(should) fix pAI faces get removed if you activate them during alert #68242
This commit is contained in:
Jeremiah
2022-07-24 08:18:59 -07:00
committed by GitHub
parent c529eab0e0
commit 86e801987e
63 changed files with 2304 additions and 2067 deletions
+1 -1
View File
@@ -948,7 +948,7 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
/mob/dead/observer/proc/register_pai()
if(isobserver(src))
SSpai.recruitWindow(src)
SSpai.recruit_window(src)
else
to_chat(usr, span_warning("Can't become a pAI candidate while not dead!"))
+1 -1
View File
@@ -18,7 +18,7 @@
/mob/living/silicon/proc/post_lawchange(announce = TRUE)
throw_alert(ALERT_NEW_LAW, /atom/movable/screen/alert/newlaw)
if(announce && last_lawchange_announce != world.time)
to_chat(src, "<b>Your laws have been changed.</b>")
to_chat(src, span_boldannounce("Your laws have been changed."))
// lawset modules cause this function to be executed multiple times in a tick, so we wait for the next tick in order to be able to see the entire lawset
addtimer(CALLBACK(src, .proc/show_laws), 0)
addtimer(CALLBACK(src, .proc/deadchat_lawchange), 0)
@@ -1,10 +0,0 @@
/mob/living/silicon/pai/death(gibbed)
if(stat == DEAD)
return
set_stat(DEAD)
update_sight()
clear_fullscreens()
//New pAI's get a brand new mind to prevent meta stuff from their previous life. This new mind causes problems down the line if it's not deleted here.
ghostize()
qdel(src)
@@ -1,10 +0,0 @@
/mob/living/silicon/pai/Login()
. = ..()
if(!. || !client)
return FALSE
client.perspective = EYE_PERSPECTIVE
if(holoform)
client.eye = src
else
client.eye = card
-286
View File
@@ -1,286 +0,0 @@
/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
hud_type = /datum/hud/pai
pass_flags = PASSTABLE | PASSMOB
mob_size = MOB_SIZE_TINY
desc = "A generic pAI mobile hard-light holographics emitter. It seems to be deactivated."
health = 500
maxHealth = 500
layer = LOW_MOB_LAYER
can_be_held = TRUE
move_force = 0
pull_force = 0
move_resist = 0
worn_slot_flags = ITEM_SLOT_HEAD
radio = /obj/item/radio/headset/silicon/pai
can_buckle_to = FALSE
mobility_flags = MOBILITY_FLAGS_REST_CAPABLE_DEFAULT
/// 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()
/// The strength of the internal flashlight
var/brightness_power = 5
/// 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
/// 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
/// Atmospheric analyzer
var/obj/item/analyzer/atmos_analyzer
/// AI's signaler
var/obj/item/assembly/signaler/internal/signaler
/// 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
/// The cable we produce when hacking a door
var/obj/item/pai_cable/hacking_cable
/// The current chasis that will appear when in holoform
var/chassis = "repairbot"
/// 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,
"crow" = TRUE,
)
var/emitterhealth = 20
var/emittermaxhealth = 20
var/emitter_regen_per_second = 1.25
var/emittercd = 50
var/emitteroverloadcd = 100
var/emittersemicd = FALSE
/// 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,
)
var/atom/movable/screen/ai/modpc/interfaceButton
/mob/living/silicon/pai/add_sensors() //pAIs have to buy their HUDs
return
/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(deleting_atom == atmos_analyzer)
atmos_analyzer = null
if(deleting_atom == internal_instrument)
internal_instrument = null
if(deleting_atom == newscaster)
newscaster = null
if(deleting_atom == signaler)
signaler = null
if(deleting_atom == hostscan)
hostscan = null
if(deleting_atom == internal_gps)
internal_gps = null
return ..()
/mob/living/silicon/pai/Initialize(mapload)
var/obj/item/paicard/pai_card = loc
START_PROCESSING(SSfastprocess, src)
GLOB.pai_list += src
make_laws()
for (var/law in laws.inherent)
lawcheck += law
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)
newscaster = new /obj/machinery/newscaster/pai(src)
if(!aicamera)
aicamera = new /obj/item/camera/siliconcam/ai_camera(src)
aicamera.flash_enabled = TRUE
. = ..()
create_modularInterface()
emittersemicd = TRUE
addtimer(CALLBACK(src, .proc/emittercool), 600)
if(!holoform)
ADD_TRAIT(src, TRAIT_IMMOBILIZED, PAI_FOLDED)
ADD_TRAIT(src, TRAIT_HANDS_BLOCKED, PAI_FOLDED)
return INITIALIZE_HINT_LATELOAD
/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/LateInitialize()
. = ..()
modularInterface.saved_identification = name
/mob/living/silicon/pai/make_laws()
laws = new /datum/ai_laws/pai()
return TRUE
/mob/living/silicon/pai/get_status_tab_items()
. += ..()
if(!stat)
. += text("Emitter Integrity: [emitterhealth * (100 / emittermaxhealth)]")
else
. += text("Systems nonfunctional")
// See software.dm for Topic()
/mob/living/silicon/pai/canUseTopic(atom/movable/M, be_close=FALSE, no_dexterity=FALSE, no_tk=FALSE, need_hands = FALSE, floor_okay=FALSE)
return ..(M, be_close, no_dexterity, no_tk, need_hands, TRUE) //Resting is just an aesthetic feature for them.
/mob/proc/makePAI(delold)
var/obj/item/paicard/card = new /obj/item/paicard(get_turf(src))
var/mob/living/silicon/pai/pai = new /mob/living/silicon/pai(card)
pai.key = key
pai.name = name
card.setPersonality(pai)
if(delold)
qdel(src)
/mob/living/silicon/pai/Process_Spacemove(movement_dir = 0, continuous_move = FALSE)
. = ..()
if(!.)
add_movespeed_modifier(/datum/movespeed_modifier/pai_spacewalk)
return TRUE
remove_movespeed_modifier(/datum/movespeed_modifier/pai_spacewalk)
return TRUE
/mob/living/silicon/pai/examine(mob/user)
. = ..()
. += "A personal AI in holochassis mode. Its master ID string seems to be [master]."
/mob/living/silicon/pai/updatehealth()
if(status_flags & GODMODE)
return
set_health(maxHealth - getBruteLoss() - getFireLoss())
update_stat()
/mob/living/silicon/pai/process(delta_time)
emitterhealth = clamp((emitterhealth + (emitter_regen_per_second * delta_time)), -50, emittermaxhealth)
/mob/living/silicon/pai/can_interact_with(atom/A)
if(A == signaler) // Bypass for signaler
return TRUE
if(A == modularInterface)
return TRUE
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))
if(!pai.encryptmod)
to_chat(user, span_alert("Encryption Key ports not configured."))
return
user.set_machine(src)
pai.radio.attackby(used, user, params)
to_chat(user, span_notice("You insert [used] into the [src]."))
return
return ..()
/obj/item/paicard/emag_act(mob/user) // Emag to wipe the master DNA and supplemental directive, changes the display to syndi
if(!pai)
return
to_chat(user, span_notice("You override [pai]'s directive system, clearing its master string and supplied directive."))
to_chat(pai, span_userdanger("Warning: System override detected, check directive sub-system for any changes."))
log_game("[key_name(user)] emagged [key_name(pai)], wiping their master DNA and supplemental directive.")
pai.emagged = TRUE
pai.master = null
pai.master_dna = null
pai.laws.supplied[1] = "None." // Sets supplemental directive to this
@@ -1,58 +0,0 @@
/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(trigger_flags)
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(trigger_flags)
..()
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(trigger_flags)
..()
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(trigger_flags)
..()
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(trigger_flags)
..()
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(trigger_flags)
..()
pai_owner.toggle_integrated_light()
@@ -1,91 +0,0 @@
/mob/living/silicon/pai/blob_act(obj/structure/blob/B)
return FALSE
/mob/living/silicon/pai/emp_act(severity)
. = ..()
if(. & EMP_PROTECT_SELF)
return
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.
//Ask and you shall receive
switch(rand(1, 3))
if(1)
adjust_timed_status_effect(1 MINUTES / severity, /datum/status_effect/speech/stutter)
to_chat(src, span_danger("Warning: Feedback loop detected in speech module."))
if(2)
adjust_timed_status_effect(INFINITY, /datum/status_effect/speech/slurring/drunk)
to_chat(src, span_danger("Warning: Audio synthesizer CPU stuck."))
if(3)
adjust_timed_status_effect(INFINITY, /datum/status_effect/speech/stutter/derpspeech)
to_chat(src, span_danger("Warning: Vocabulary databank corrupted."))
if(prob(40))
mind.language_holder.selected_language = get_random_spoken_language()
/mob/living/silicon/pai/ex_act(severity, target)
take_holo_damage(50 * severity)
switch(severity)
if(EXPLODE_DEVASTATE) //RIP
qdel(card)
qdel(src)
if(EXPLODE_HEAVY)
fold_in(force = 1)
Paralyze(400)
if(EXPLODE_LIGHT)
fold_in(force = 1)
Paralyze(200)
/mob/living/silicon/pai/attack_hand(mob/living/carbon/human/user, list/modifiers)
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)
fold_in(force = TRUE)
src.visible_message(span_warning("The electrically-charged projectile disrupts [src]'s holomatrix, forcing [src] to fold in!"))
. = ..(Proj)
/mob/living/silicon/pai/ignite_mob()
return FALSE
/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)
to_chat(src, span_userdanger("The impact degrades your holochassis!"))
return amount
/mob/living/silicon/pai/adjustBruteLoss(amount, updating_health = TRUE, forced = FALSE)
return take_holo_damage(amount)
/mob/living/silicon/pai/adjustFireLoss(amount, updating_health = TRUE, forced = FALSE)
return take_holo_damage(amount)
/mob/living/silicon/pai/adjustStaminaLoss(amount, updating_health, forced = FALSE)
if(forced)
take_holo_damage(amount)
else
take_holo_damage(amount * 0.25)
/mob/living/silicon/pai/getBruteLoss()
return emittermaxhealth - emitterhealth
/mob/living/silicon/pai/getFireLoss()
return emittermaxhealth - emitterhealth
@@ -1,2 +0,0 @@
/mob/living/silicon/pai/binarycheck()
return radio?.translate_binary
@@ -1,155 +0,0 @@
/mob/living/silicon/pai/proc/fold_out(force = FALSE)
if(emitterhealth < 0)
to_chat(src, span_warning("Your holochassis emitters are still too unstable! Please wait for automatic repair."))
return FALSE
if(!canholo && !force)
to_chat(src, span_warning("Your master or another force has disabled your holochassis emitters!"))
return FALSE
if(holoform)
. = fold_in(force)
return
if(emittersemicd)
to_chat(src, span_warning("Error: Holochassis emitters recycling. Please try again later."))
return FALSE
emittersemicd = TRUE
addtimer(CALLBACK(src, .proc/emittercool), emittercd)
REMOVE_TRAIT(src, TRAIT_IMMOBILIZED, PAI_FOLDED)
REMOVE_TRAIT(src, TRAIT_HANDS_BLOCKED, PAI_FOLDED)
set_density(TRUE)
if(istype(card.loc, /obj/item/modular_computer))
var/obj/item/modular_computer/P = card.loc
P.inserted_pai = null
P.visible_message(span_notice("[src] ejects itself from [P]!"))
if(isliving(card.loc))
var/mob/living/L = card.loc
if(!L.temporarilyRemoveItemFromInventory(card))
to_chat(src, span_warning("Error: Unable to expand to mobile form. Chassis is restrained by some device or person."))
return FALSE
forceMove(get_turf(card))
card.forceMove(src)
if(client)
client.perspective = EYE_PERSPECTIVE
client.eye = src
set_light(0)
icon_state = "[chassis]"
held_state = "[chassis]"
visible_message(span_boldnotice("[src] folds out its holochassis emitter and forms a holoshell around itself!"))
holoform = TRUE
/mob/living/silicon/pai/proc/emittercool()
emittersemicd = FALSE
/mob/living/silicon/pai/proc/fold_in(force = FALSE)
emittersemicd = TRUE
if(!force)
addtimer(CALLBACK(src, .proc/emittercool), emittercd)
else
addtimer(CALLBACK(src, .proc/emittercool), emitteroverloadcd)
icon_state = "[chassis]"
if(!holoform)
. = fold_out(force)
return
visible_message(span_notice("[src] deactivates its holochassis emitter and folds back into a compact card!"))
stop_pulling()
if(istype(loc, /obj/item/clothing/head/mob_holder))
var/obj/item/clothing/head/mob_holder/MH = loc
MH.release(display_messages = FALSE)
if(client)
client.perspective = EYE_PERSPECTIVE
client.eye = card
var/turf/T = drop_location()
card.forceMove(T)
forceMove(card)
ADD_TRAIT(src, TRAIT_IMMOBILIZED, PAI_FOLDED)
ADD_TRAIT(src, TRAIT_HANDS_BLOCKED, PAI_FOLDED)
set_density(FALSE)
set_light(0)
holoform = FALSE
set_resting(resting)
/**
* Sets a new holochassis skin based on a pAI's choice
*/
/mob/living/silicon/pai/proc/choose_chassis()
var/list/skins = list()
for(var/holochassis_option in possible_chassis)
var/image/item_image = image(icon = src.icon, icon_state = holochassis_option)
skins += list("[holochassis_option]" = item_image)
sort_list(skins)
var/atom/anchor = get_atom_on_turf(src)
var/choice = show_radial_menu(src, anchor, skins, custom_check = CALLBACK(src, .proc/check_menu, anchor), radius = 40, require_near = TRUE)
if(!choice)
return FALSE
set_holochassis(choice)
to_chat(src, span_boldnotice("You switch your holochassis projection composite to [choice]."))
update_resting()
/**
* Sets the holochassis skin and updates the icons
* * Arguments:
* * choice The animal skin that will be used for the pAI holoform
*/
/mob/living/silicon/pai/proc/set_holochassis(choice)
if(!choice)
return FALSE
chassis = choice
icon_state = "[chassis]"
held_state = "[chassis]"
/**
* Polymorphs the pai into a random holoform
*/
/mob/living/silicon/pai/wabbajack()
if(length(possible_chassis) < 2)
return
var/holochassis = pick(possible_chassis - chassis)
set_holochassis(holochassis)
to_chat(src, span_boldnotice("Your holochassis form morphs into that of a [holochassis]."))
/**
* Checks if we are allowed to interact with a radial menu
*
* * Arguments:
* * anchor The atom that is anchoring the menu
*/
/mob/living/silicon/pai/proc/check_menu(atom/anchor)
if(incapacitated())
return FALSE
if(get_turf(src) != get_turf(anchor))
return FALSE
if(!isturf(loc) && loc != card)
to_chat(src, span_boldwarning("You can not change your holochassis composite while not on the ground or in your card!"))
return FALSE
return TRUE
/mob/living/silicon/pai/update_resting()
. = ..()
if(resting)
icon_state = "[chassis]_rest"
else
icon_state = "[chassis]"
if(loc != card)
visible_message(span_notice("[src] [resting? "lays down for a moment..." : "perks up from the ground."]"))
/mob/living/silicon/pai/start_pulling(atom/movable/AM, state, force = move_force, supress_message = FALSE)
return FALSE
/mob/living/silicon/pai/proc/toggle_integrated_light()
if(!light_range)
set_light(brightness_power)
to_chat(src, span_notice("You enable your integrated light."))
else
set_light(0)
to_chat(src, span_notice("You disable your integrated light."))
/mob/living/silicon/pai/mob_try_pickup(mob/living/user, instant=FALSE)
if(!possible_chassis[chassis])
to_chat(user, span_warning("[src]'s current form isn't able to be carried!"))
return FALSE
return ..()
@@ -1,56 +0,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"
/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
// if silent=true, report incompatible savefiles
// 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 FALSE
var/path = savefile_path(user)
if (!fexists(path))
return FALSE
var/savefile/F = new /savefile(path)
if(!F)
return //Not everyone has a pai savefile.
var/version = null
F["version"] >> version
if (isnull(version) || version != 1)
fdel(path)
if (!silent)
tgui_alert(user, "Your savefile was incompatible with this version and was deleted.")
return FALSE
F["name"] >> src.name
F["description"] >> src.description
F["comments"] >> src.comments
return TRUE
@@ -1,256 +0,0 @@
// Opens TGUI interface
/mob/living/silicon/pai/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "PaiInterface", name)
ui.open()
// Static UI data
/mob/living/silicon/pai/ui_static_data(mob/user)
var/list/data = list()
var/mob/living/silicon/pai/pai = user
data["available"] = available_software
data["records"] = list()
if("medical records" in pai.software)
data["records"]["medical"] = medical_records
if("security records" in pai.software)
data["records"]["security"] = security_records
return data
// Variables sent to TGUI
/mob/living/silicon/pai/ui_data(mob/user)
var/list/data = list()
data["directives"] = laws.supplied
data["door_jack"] = hacking_cable || null
data["emagged"] = emagged
data["image"] = card.emotion_icon
data["installed"] = software
data["languages"] = languages_granted
data["master"] = list()
data["pda"] = list()
data["ram"] = ram
data["refresh_spam"] = refresh_spam
if(master)
data["master"]["name"] = master
data["master"]["dna"] = master_dna
return data
// Actions received from TGUI
/mob/living/silicon/pai/ui_act(action, list/params, datum/tgui/ui)
. = ..()
if(.)
return
switch(action)
if("buy")
if(available_software.Find(params["selection"]) && !software.Find(params["selection"]))
/// Cost of the software to purchase
var/cost = available_software[params["selection"]]
if(ram >= cost)
software.Add(params["selection"])
ram -= cost
var/datum/hud/pai/pAIhud = hud_used
pAIhud?.update_software_buttons()
else
to_chat(usr, span_notice("Insufficient RAM available."))
else
to_chat(usr, span_notice("Software not found."))
if("atmosphere_sensor")
if(!holoform)
to_chat(usr, span_notice("You must be mobile to do this!"))
return FALSE
if(!atmos_analyzer)
atmos_analyzer = new(src)
atmos_analyzer.attack_self(src)
if("camera_zoom")
aicamera.adjust_zoom(usr)
if("change_image")
var/newImage = tgui_input_list(usr, "Select your new display image", "Display Image", sort_list(list("Happy", "Cat", "Extremely Happy", "Face", "Laugh", "Off", "Sad", "Angry", "What", "Sunglasses", "None")))
if(isnull(newImage))
return FALSE
switch(newImage)
if("None")
card.emotion_icon = "null"
if("Extremely Happy")
card.emotion_icon = "extremely-happy"
else
card.emotion_icon = "[lowertext(newImage)]"
card.update_appearance()
if("check_dna")
if(!master_dna)
to_chat(src, span_warning("You do not have a master DNA to compare to!"))
return FALSE
if(iscarbon(card.loc))
CheckDNA(card.loc, src) //you should only be able to check when directly in hand, muh immersions?
else
to_chat(src, span_warning("You are not being carried by anyone!"))
return FALSE
if("crew_manifest")
ai_roster()
if("door_jack")
if(params["jack"] == "jack")
if(hacking_cable?.machine)
hack_door()
if(params["jack"] == "cancel")
QDEL_NULL(hacking_cable)
if(params["jack"] == "cable")
extendcable()
if("encryption_keys")
to_chat(src, span_notice("You have [!encryptmod ? "enabled" : "disabled"] encrypted radio frequencies."))
encryptmod = !encryptmod
radio.subspace_transmission = !radio.subspace_transmission
if("host_scan")
if(!hostscan)
hostscan = new(src)
if(params["scan"] == "scan")
hostscan()
if(params["scan"] == "wounds")
hostscan.attack_self(usr)
if(params["scan"] == "limbs")
hostscan.AltClick(usr)
if("internal_gps")
if(!internal_gps)
internal_gps = new(src)
internal_gps.attack_self(src)
if("loudness_booster")
if(!internal_instrument)
internal_instrument = new(src)
internal_instrument.interact(src) // Open Instrument
if("medical_hud")
medHUD = !medHUD
if(medHUD)
var/datum/atom_hud/med = GLOB.huds[med_hud]
med.show_to(src)
else
var/datum/atom_hud/med = GLOB.huds[med_hud]
med.hide_from(src)
if("newscaster")
newscaster.ui_interact(src)
if("photography_module")
aicamera.toggle_camera_mode(usr)
if("printer_module")
aicamera.paiprint(usr)
if("radio")
radio.attack_self(src)
if("refresh")
if(refresh_spam)
return FALSE
refresh_spam = TRUE
if(params["list"] == "medical")
medical_records = GLOB.data_core.get_general_records()
if(params["list"] == "security")
security_records = GLOB.data_core.get_security_records()
ui.send_full_update()
addtimer(CALLBACK(src, .proc/refresh_again), 3 SECONDS)
if("remote_signaler")
signaler.ui_interact(src)
if("security_hud")
secHUD = !secHUD
if(secHUD)
var/datum/atom_hud/sec = GLOB.huds[sec_hud]
sec.show_to(src)
else
var/datum/atom_hud/sec = GLOB.huds[sec_hud]
sec.hide_from(src)
if("universal_translator")
if(!languages_granted)
grant_all_languages(TRUE, TRUE, TRUE, LANGUAGE_SOFTWARE)
languages_granted = TRUE
return
/**
* Supporting proc for the pAI to prick it's master's hand
* or... whatever. It must be held in order to work
* Gives the owner a popup if they want to get the jab.
*/
/mob/living/silicon/pai/proc/CheckDNA(mob/living/carbon/master, mob/living/silicon/pai/pai)
if(!istype(master))
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")
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, "<b>No DNA detected.</b>")
return
to_chat(pai, "<font color = red><h3>[master]'s UE string : [master.dna.unique_enzymes]</h3></font>")
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
*
* Allows the pAI to scan its host's health vitals
* An integrated health analyzer.
*/
/mob/living/silicon/pai/proc/hostscan()
var/mob/living/silicon/pai/pAI = usr
var/mob/living/carbon/holder = get(pAI.card.loc, /mob/living/carbon)
if(holder)
pAI.hostscan.attack(holder, pAI)
else
to_chat(usr, span_warning("You are not being carried by anyone!"))
return FALSE
/**
* Extend cable supporting proc
*
* When doorjack is installed, allows the pAI to drop
* a cable which is placed either on the floor or in
* someone's hands based (on distance).
*/
/mob/living/silicon/pai/proc/extendcable()
QDEL_NULL(hacking_cable) //clear any old cables
hacking_cable = new
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 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/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/all_ais in GLOB.player_list)
if(!all_ais.stat)
continue
if(turf.loc)
to_chat(all_ais, span_boldannounce("Network Alert: Brute-force security override in progress in [turf.loc]."))
else
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
* the records window again.
*/
/mob/living/silicon/pai/proc/refresh_again()
refresh_spam = FALSE
@@ -40,7 +40,7 @@
///People currently looking into a bot's UI panel.
var/list/users = list()
///The inserted (if any) pAI in this bot.
var/obj/item/paicard/paicard
var/obj/item/pai_card/paicard
///The type of bot it is, for radio control.
var/bot_type = NONE
@@ -398,7 +398,7 @@
/mob/living/simple_animal/bot/attackby(obj/item/attacking_item, mob/living/user, params)
if(attacking_item.GetID())
unlock_with_id(user)
else if(istype(attacking_item, /obj/item/paicard))
else if(istype(attacking_item, /obj/item/pai_card))
insertpai(user, attacking_item)
else if(attacking_item.tool_behaviour == TOOL_HEMOSTAT && paicard)
if(bot_cover_flags & BOT_COVER_OPEN)
@@ -936,7 +936,7 @@ Pass a positive integer as an argument to override a bot's default speed.
return TRUE
return FALSE
/mob/living/simple_animal/bot/proc/insertpai(mob/user, obj/item/paicard/card)
/mob/living/simple_animal/bot/proc/insertpai(mob/user, obj/item/pai_card/card)
if(paicard)
to_chat(user, span_warning("A [paicard] is already inserted!"))
return
@@ -766,7 +766,7 @@
else
return ..()
/mob/living/simple_animal/bot/mulebot/insertpai(mob/user, obj/item/paicard/card)
/mob/living/simple_animal/bot/mulebot/insertpai(mob/user, obj/item/pai_card/card)
. = ..()
if(.)
visible_message(span_notice("[src]'s safeties are locked on."))