mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-07-20 04:26:03 +01:00
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:
@@ -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!"))
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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."))
|
||||
|
||||
Reference in New Issue
Block a user