mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-07-22 05:25:15 +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:
@@ -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(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()
|
||||
@@ -0,0 +1,58 @@
|
||||
/mob/living/silicon/pai/ClickOn(atom/target, params)
|
||||
..()
|
||||
if(!camera?.in_camera_mode)
|
||||
return FALSE
|
||||
//pAI picture taking
|
||||
camera.toggle_camera_mode(sound = FALSE)
|
||||
camera.captureimage(target, usr, camera.picture_size_x - 1, camera.picture_size_y - 1)
|
||||
return TRUE
|
||||
|
||||
/obj/item/camera/siliconcam/pai_camera
|
||||
name = "pAI photo camera"
|
||||
light_color = COLOR_PAI_GREEN
|
||||
|
||||
/obj/item/camera/siliconcam/pai_camera/after_picture(mob/user, datum/picture/picture)
|
||||
var/number = length(stored)
|
||||
picture.picture_name = "Image [number] (taken by [loc.name])"
|
||||
stored[picture] = TRUE
|
||||
playsound(loc, pick('sound/items/polaroid1.ogg', 'sound/items/polaroid2.ogg'), 75, TRUE, -3)
|
||||
balloon_alert(user, "image recorded")
|
||||
|
||||
/**
|
||||
* Handles selecting and printing stored images.
|
||||
*
|
||||
* @param {mob} user - The pAI.
|
||||
*
|
||||
* @returns {boolean} - TRUE if the pAI prints an image,
|
||||
* FALSE otherwise.
|
||||
*/
|
||||
/obj/item/camera/siliconcam/pai_camera/proc/pai_print(mob/user)
|
||||
var/mob/living/silicon/pai/pai = loc
|
||||
var/datum/picture/selection = selectpicture(user)
|
||||
if(!istype(selection))
|
||||
balloon_alert(user, "invalid image")
|
||||
return FALSE
|
||||
printpicture(user, selection)
|
||||
user.visible_message(span_notice("A picture appears on top of the chassis of [pai.name]!"), span_notice("You print a photograph."))
|
||||
return TRUE
|
||||
|
||||
/**
|
||||
* All inclusive camera proc. Zooms, snaps, prints.
|
||||
*
|
||||
* @param {mob} user - The pAI requesting the camera.
|
||||
*
|
||||
* @param {string} mode - The camera option to toggle.
|
||||
*
|
||||
* @returns {boolean} - TRUE if the camera worked.
|
||||
*/
|
||||
/mob/living/silicon/pai/proc/use_camera(mob/user, mode)
|
||||
if(!camera || isnull(mode))
|
||||
return FALSE
|
||||
switch(mode)
|
||||
if(PAI_PHOTO_MODE_CAMERA)
|
||||
camera.toggle_camera_mode(user)
|
||||
if(PAI_PHOTO_MODE_PRINTER)
|
||||
camera.pai_print(user)
|
||||
if(PAI_PHOTO_MODE_ZOOM)
|
||||
camera.adjust_zoom(user)
|
||||
return TRUE
|
||||
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* #pAI Candidate
|
||||
*
|
||||
* Created when a user opens the pAI submit interface.
|
||||
* Stores the candidate in an associative list of ckey: candidate objects.
|
||||
*/
|
||||
/datum/pai_candidate
|
||||
/// User inputted OOC comments
|
||||
var/comments
|
||||
/// User inputted behavior description
|
||||
var/description
|
||||
/// User's ckey
|
||||
var/ckey
|
||||
/// User's pAI name. If blank, ninja name.
|
||||
var/name
|
||||
/// If the user has hit "submit"
|
||||
var/ready = FALSE
|
||||
|
||||
/datum/pai_candidate/New(ckey)
|
||||
src.ckey = ckey
|
||||
|
||||
/**
|
||||
* Checks if a candidate is ready so that they may be displayed or
|
||||
* downloaded. Removes any invalid entries.
|
||||
*
|
||||
* @returns {boolean} - TRUE if the candidate is ready, FALSE if not
|
||||
*/
|
||||
/datum/pai_candidate/proc/check_ready()
|
||||
var/mob/candidate_mob = get_mob_by_key(ckey)
|
||||
if(!candidate_mob?.client || !isobserver(candidate_mob) || is_banned_from(ckey, ROLE_PAI))
|
||||
SSpai.candidates -= ckey
|
||||
return FALSE
|
||||
if(!ready)
|
||||
return FALSE
|
||||
return TRUE
|
||||
@@ -0,0 +1,261 @@
|
||||
/obj/item/pai_card
|
||||
custom_premium_price = PAYCHECK_COMMAND * 1.25
|
||||
desc = "Downloads personal AI assistants to accompany its owner or others."
|
||||
icon = 'icons/obj/aicards.dmi'
|
||||
icon_state = "pai"
|
||||
inhand_icon_state = "electronic"
|
||||
lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi'
|
||||
name = "personal AI device"
|
||||
resistance_flags = FIRE_PROOF | ACID_PROOF | INDESTRUCTIBLE
|
||||
righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi'
|
||||
slot_flags = ITEM_SLOT_BELT
|
||||
w_class = WEIGHT_CLASS_SMALL
|
||||
worn_icon_state = "electronic"
|
||||
|
||||
/// Spam alert prevention
|
||||
var/alert_cooldown
|
||||
/// The emotion icon displayed.
|
||||
var/emotion_icon = "off"
|
||||
/// Any pAI personalities inserted
|
||||
var/mob/living/silicon/pai/pai
|
||||
/// Prevents a crew member from hitting "request pAI" repeatedly
|
||||
var/request_spam = FALSE
|
||||
|
||||
/obj/item/pai_card/attackby(obj/item/used, mob/user, params)
|
||||
if(pai && istype(used, /obj/item/encryptionkey))
|
||||
if(!pai.encrypt_mod)
|
||||
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/pai_card/attack_self(mob/user)
|
||||
if(!in_range(src, user))
|
||||
return
|
||||
user.set_machine(src)
|
||||
ui_interact(user)
|
||||
|
||||
/obj/item/pai_card/Destroy()
|
||||
//Will stop people throwing friend pAIs into the singularity so they can respawn
|
||||
SSpai.pai_card_list.Remove(src)
|
||||
if(!QDELETED(pai))
|
||||
QDEL_NULL(pai)
|
||||
return ..()
|
||||
|
||||
/obj/item/pai_card/emag_act(mob/user)
|
||||
if(pai)
|
||||
pai.handle_emag(user)
|
||||
|
||||
/obj/item/pai_card/emp_act(severity)
|
||||
. = ..()
|
||||
if (. & EMP_PROTECT_SELF)
|
||||
return
|
||||
if(!pai?.holoform)
|
||||
pai.emp_act(severity)
|
||||
|
||||
/obj/item/pai_card/handle_atom_del(atom/thing)
|
||||
if(thing == pai) //double check /mob/living/silicon/pai/Destroy() if you change these.
|
||||
pai = null
|
||||
emotion_icon = initial(emotion_icon)
|
||||
update_appearance()
|
||||
return ..()
|
||||
|
||||
/obj/item/pai_card/Initialize(mapload)
|
||||
. = ..()
|
||||
update_appearance()
|
||||
SSpai.pai_card_list += src
|
||||
|
||||
/obj/item/pai_card/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!"))
|
||||
return OXYLOSS
|
||||
|
||||
/obj/item/pai_card/update_overlays()
|
||||
. = ..()
|
||||
. += "pai-[emotion_icon]"
|
||||
if(pai?.hacking_cable)
|
||||
. += "[initial(icon_state)]-connector"
|
||||
|
||||
/obj/item/pai_card/vv_edit_var(vname, vval)
|
||||
. = ..()
|
||||
if(vname == NAMEOF(src, emotion_icon))
|
||||
update_appearance()
|
||||
|
||||
/obj/item/pai_card/ui_interact(mob/user, datum/tgui/ui)
|
||||
. = ..()
|
||||
ui = SStgui.try_update_ui(user, src, ui)
|
||||
if(!ui)
|
||||
ui = new(user, src, "PaiCard")
|
||||
ui.open()
|
||||
|
||||
/obj/item/pai_card/ui_status(mob/user)
|
||||
if(user in get_nested_locs(src))
|
||||
return UI_INTERACTIVE
|
||||
return ..()
|
||||
|
||||
/obj/item/pai_card/ui_data(mob/user)
|
||||
. = ..()
|
||||
var/list/data = list()
|
||||
if(!pai)
|
||||
data["candidates"] = pool_candidates() || list()
|
||||
return data
|
||||
data["pai"] = list(
|
||||
can_holo = pai.can_holo,
|
||||
dna = pai.master_dna,
|
||||
emagged = pai.emagged,
|
||||
laws = pai.laws.supplied,
|
||||
master = pai.master_name,
|
||||
name = pai.name,
|
||||
transmit = pai.can_transmit,
|
||||
receive = pai.can_receive,
|
||||
)
|
||||
return data
|
||||
|
||||
/obj/item/pai_card/ui_act(action, list/params, datum/tgui/ui)
|
||||
. = ..()
|
||||
if(.)
|
||||
return TRUE
|
||||
// Actions that don't require a pAI
|
||||
if(action == "download")
|
||||
download_candidate(usr, params["ckey"])
|
||||
return TRUE
|
||||
if(action == "request")
|
||||
find_pai(usr)
|
||||
return TRUE
|
||||
// pAI specific actions.
|
||||
if(!pai)
|
||||
return FALSE
|
||||
switch(action)
|
||||
if("fix_speech")
|
||||
pai.fix_speech()
|
||||
return TRUE
|
||||
if("reset_software")
|
||||
pai.reset_software()
|
||||
return TRUE
|
||||
if("set_dna")
|
||||
pai.set_dna(usr)
|
||||
return TRUE
|
||||
if("set_laws")
|
||||
pai.set_laws(usr)
|
||||
return TRUE
|
||||
if("toggle_holo")
|
||||
pai.toggle_holo()
|
||||
return TRUE
|
||||
if("toggle_radio")
|
||||
pai.toggle_radio(params["option"])
|
||||
return TRUE
|
||||
if("wipe_pai")
|
||||
pai.wipe_pai(usr)
|
||||
ui.close()
|
||||
return TRUE
|
||||
return FALSE
|
||||
|
||||
/** Flashes the pai card screen */
|
||||
/obj/item/pai_card/proc/add_alert()
|
||||
if(pai)
|
||||
return
|
||||
add_overlay(
|
||||
list(mutable_appearance(icon, "[initial(icon_state)]-alert"),
|
||||
emissive_appearance(icon, "[initial(icon_state)]-alert", alpha = src.alpha)))
|
||||
|
||||
/** Removes any overlays */
|
||||
/obj/item/pai_card/proc/remove_alert()
|
||||
if(pai)
|
||||
return
|
||||
cut_overlays()
|
||||
|
||||
/** Alerts pAI cards that someone has submitted candidacy */
|
||||
/obj/item/pai_card/proc/alert_update()
|
||||
if(!COOLDOWN_FINISHED(src, alert_cooldown))
|
||||
return
|
||||
COOLDOWN_START(src, alert_cooldown, 5 SECONDS)
|
||||
add_alert()
|
||||
addtimer(CALLBACK(src, .proc/remove_alert), 5 SECONDS)
|
||||
playsound(src, 'sound/machines/ping.ogg', 30, TRUE)
|
||||
visible_message(span_notice("[src] flashes a message across its screen: New personalities available for download!"), blind_message = span_notice("[src] vibrates with an alert."))
|
||||
|
||||
/**
|
||||
* Downloads a candidate from the list and removes them from SSpai.candidates
|
||||
*
|
||||
* @param {string} ckey The ckey of the candidate to download
|
||||
*
|
||||
* @returns {boolean} - TRUE if the candidate was downloaded, FALSE if not
|
||||
*/
|
||||
/obj/item/pai_card/proc/download_candidate(mob/user, ckey)
|
||||
if(pai)
|
||||
return FALSE
|
||||
var/datum/pai_candidate/candidate = SSpai.candidates[ckey]
|
||||
if(!candidate?.check_ready())
|
||||
balloon_alert(user, "download interrupted")
|
||||
return FALSE
|
||||
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.ckey
|
||||
set_personality(new_pai)
|
||||
SSpai.candidates -= ckey
|
||||
return TRUE
|
||||
|
||||
/**
|
||||
* Pings ghosts to announce that someone is requesting a pAI
|
||||
*
|
||||
* @param {mob} user - The user who is requesting a pAI
|
||||
*
|
||||
* @returns {boolean} - TRUE if the pAI was requested, FALSE if not
|
||||
*/
|
||||
/obj/item/pai_card/proc/find_pai(mob/user)
|
||||
if(pai)
|
||||
return FALSE
|
||||
if(!(GLOB.ghost_role_flags & GHOSTROLE_SILICONS))
|
||||
balloon_alert(user, "unavailable: NT blacklisted")
|
||||
return FALSE
|
||||
if(request_spam)
|
||||
balloon_alert(user, "request sent too recently")
|
||||
return FALSE
|
||||
request_spam = TRUE
|
||||
playsound(src, 'sound/machines/ping.ogg', 20, TRUE)
|
||||
balloon_alert(user, "pAI assistance requested")
|
||||
var/mutable_appearance/alert_overlay = mutable_appearance('icons/obj/aicards.dmi', "pai")
|
||||
notify_ghosts("[user] is requesting a pAI companion! Use the pAI button to submit yourself as one.", source = user, alert_overlay = alert_overlay, action = NOTIFY_ORBIT, flashwindow = FALSE, header = "pAI Request!", ignore_key = POLL_IGNORE_PAI)
|
||||
addtimer(VARSET_CALLBACK(src, request_spam, FALSE), PAI_SPAM_TIME, TIMER_UNIQUE | TIMER_STOPPABLE | TIMER_CLIENT_TIME | TIMER_DELETE_ME)
|
||||
return TRUE
|
||||
|
||||
/**
|
||||
* Gathers a list of candidates to display in the download candidate
|
||||
* window. If the candidate isn't marked ready, ie they have not
|
||||
* pressed submit, they will be skipped over.
|
||||
*
|
||||
* @returns - An array of candidate objects.
|
||||
*/
|
||||
/obj/item/pai_card/proc/pool_candidates()
|
||||
var/list/candidates = list()
|
||||
if(pai || !length(SSpai?.candidates))
|
||||
return candidates
|
||||
for(var/key in SSpai.candidates)
|
||||
var/datum/pai_candidate/candidate = SSpai.candidates[key]
|
||||
if(!candidate?.check_ready())
|
||||
continue
|
||||
candidates += list(list(
|
||||
ckey = candidate.ckey,
|
||||
comments = candidate.comments,
|
||||
description = candidate.description,
|
||||
name = candidate.name,
|
||||
))
|
||||
return candidates
|
||||
|
||||
/**
|
||||
* Sets the personality on the current pai_card
|
||||
*
|
||||
* @param {silicon/pai} downloaded - The new pAI to load into the card.
|
||||
*/
|
||||
/obj/item/pai_card/proc/set_personality(mob/living/silicon/pai/downloaded)
|
||||
if(pai)
|
||||
return FALSE
|
||||
pai = downloaded
|
||||
emotion_icon = "null"
|
||||
update_appearance()
|
||||
playsound(src, 'sound/effects/pai_boot.ogg', 50, TRUE, -1)
|
||||
audible_message("[src] plays a cheerful startup noise!")
|
||||
return TRUE
|
||||
@@ -0,0 +1,12 @@
|
||||
/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)
|
||||
@@ -0,0 +1,45 @@
|
||||
/client/proc/makepAI(turf/target in GLOB.mob_list)
|
||||
set category = "Admin.Fun"
|
||||
set name = "Make pAI"
|
||||
set desc = "Specify a location to spawn a pAI device, then specify a key to play that pAI"
|
||||
|
||||
var/list/available = list()
|
||||
for(var/mob/player as anything in GLOB.player_list)
|
||||
if(player.client && player.key)
|
||||
available.Add(player)
|
||||
var/mob/choice = tgui_input_list(usr, "Choose a player to play the pAI", "Spawn pAI", sort_names(available))
|
||||
if(isnull(choice))
|
||||
return
|
||||
|
||||
var/chosen_name = input(choice, "Enter your pAI name:", "pAI Name", "Personal AI") as text|null
|
||||
if (isnull(chosen_name))
|
||||
return
|
||||
|
||||
if(!isobserver(choice))
|
||||
var/confirm = tgui_alert(usr, "[choice.key] isn't ghosting right now. Are you sure you want to yank them out of their body and place them in this pAI?", "Spawn pAI Confirmation", list("Yes", "No"))
|
||||
if(confirm != "Yes")
|
||||
return
|
||||
var/obj/item/pai_card/card = new(target)
|
||||
var/mob/living/silicon/pai/pai = new(card)
|
||||
|
||||
pai.name = chosen_name
|
||||
pai.real_name = pai.name
|
||||
pai.key = choice.key
|
||||
card.set_personality(pai)
|
||||
if(SSpai.candidates[key])
|
||||
SSpai.candidates -= key
|
||||
SSblackbox.record_feedback("tally", "admin_verb", 1, "Make pAI") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
|
||||
|
||||
/**
|
||||
* Creates a new pAI.
|
||||
*
|
||||
* @param {boolean} delete_old - If TRUE, deletes the old pAI.
|
||||
*/
|
||||
/mob/proc/make_pai(delete_old)
|
||||
var/obj/item/pai_card/card = new(src)
|
||||
var/mob/living/silicon/pai/pai = new(card)
|
||||
pai.key = key
|
||||
pai.name = name
|
||||
card.set_personality(pai)
|
||||
if(delete_old)
|
||||
qdel(src)
|
||||
@@ -0,0 +1,91 @@
|
||||
|
||||
/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_name)
|
||||
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)
|
||||
holochassis_health = clamp((holochassis_health - amount), -50, HOLOCHASSIS_MAX_HEALTH)
|
||||
if(holochassis_health < 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 HOLOCHASSIS_MAX_HEALTH - holochassis_health
|
||||
|
||||
/mob/living/silicon/pai/getFireLoss()
|
||||
return HOLOCHASSIS_MAX_HEALTH - holochassis_health
|
||||
@@ -0,0 +1,134 @@
|
||||
#define CABLE_LENGTH 2
|
||||
|
||||
/**
|
||||
* Switch that handles door jack operations.
|
||||
*
|
||||
* @param {string} mode - The requested operation of the door jack.
|
||||
*
|
||||
* @returns {boolean} - TRUE if the door jack state was switched, FALSE otherwise.
|
||||
*/
|
||||
/mob/living/silicon/pai/proc/door_jack(mode)
|
||||
if(isnull(mode))
|
||||
return FALSE
|
||||
switch(mode)
|
||||
if(PAI_DOOR_JACK_CABLE)
|
||||
extend_cable()
|
||||
return TRUE
|
||||
if(PAI_DOOR_JACK_HACK)
|
||||
hack_door()
|
||||
return TRUE
|
||||
if(PAI_DOOR_JACK_CANCEL)
|
||||
QDEL_NULL(hacking_cable)
|
||||
visible_message(span_notice("The cable retracts into the pAI."))
|
||||
return TRUE
|
||||
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).
|
||||
*
|
||||
* @returns {boolean} - TRUE if the cable was dropped, FALSE otherwise.
|
||||
*/
|
||||
/mob/living/silicon/pai/proc/extend_cable()
|
||||
QDEL_NULL(hacking_cable) //clear any old cables
|
||||
hacking_cable = new
|
||||
var/mob/living/carbon/hacker = get_holder()
|
||||
if(hacker?.put_in_hands(hacking_cable))
|
||||
hacker.visible_message(span_notice("A port on [src] opens to reveal a cable, which you quickly grab."), span_hear("You hear the soft click of a plastic component and manage to catch the falling cable."))
|
||||
track_pai()
|
||||
track_thing(hacking_cable)
|
||||
return TRUE
|
||||
hacking_cable.forceMove(drop_location())
|
||||
hacking_cable.visible_message(span_notice("A port on [src] opens to reveal a cable, which promptly falls to the floor."), span_hear("You hear the soft click of a plastic component fall to the ground."))
|
||||
track_pai()
|
||||
track_thing(hacking_cable)
|
||||
return TRUE
|
||||
|
||||
/** Tracks the associated pai */
|
||||
/mob/living/silicon/pai/proc/track_pai()
|
||||
RegisterSignal(src, COMSIG_MOVABLE_MOVED, .proc/handle_move)
|
||||
RegisterSignal(card, COMSIG_MOVABLE_MOVED, .proc/handle_move)
|
||||
|
||||
/** Untracks the associated pai */
|
||||
/mob/living/silicon/pai/proc/untrack_pai()
|
||||
UnregisterSignal(src, COMSIG_MOVABLE_MOVED)
|
||||
UnregisterSignal(card, COMSIG_MOVABLE_MOVED)
|
||||
|
||||
/** Tracks the associated hacking_cable */
|
||||
/mob/living/silicon/pai/proc/track_thing(atom/movable/thing)
|
||||
RegisterSignal(thing, COMSIG_MOVABLE_MOVED, .proc/handle_move)
|
||||
var/list/locations = get_nested_locs(thing, include_turf = FALSE)
|
||||
for(var/atom/movable/location in locations)
|
||||
RegisterSignal(location, COMSIG_MOVABLE_MOVED, .proc/handle_move)
|
||||
|
||||
/** Untracks the associated hacking */
|
||||
/mob/living/silicon/pai/proc/untrack_thing(atom/movable/thing)
|
||||
UnregisterSignal(thing, COMSIG_MOVABLE_MOVED)
|
||||
var/list/locations = get_nested_locs(thing, include_turf = FALSE)
|
||||
for(var/atom/movable/location in locations)
|
||||
UnregisterSignal(location, COMSIG_MOVABLE_MOVED)
|
||||
|
||||
/**
|
||||
* A periodic check to see if the source pAI is nearby.
|
||||
* Deletes the extended cable if the source pAI is not nearby.
|
||||
*/
|
||||
/mob/living/silicon/pai/proc/handle_move(atom/movable/source, atom/movable/old_loc)
|
||||
if(ismovable(old_loc))
|
||||
untrack_thing(old_loc)
|
||||
if(!IN_GIVEN_RANGE(src, hacking_cable, CABLE_LENGTH))
|
||||
retract_cable()
|
||||
return
|
||||
if(ismovable(source.loc))
|
||||
track_thing(source.loc)
|
||||
|
||||
/**
|
||||
* Handles deleting the hacking cable and notifying the user.
|
||||
*/
|
||||
/mob/living/silicon/pai/proc/retract_cable()
|
||||
hacking_cable.visible_message(span_notice("The cable quickly retracts."))
|
||||
balloon_alert(src, "cable retracted")
|
||||
untrack_pai()
|
||||
untrack_thing(hacking_cable)
|
||||
QDEL_NULL(hacking_cable)
|
||||
SStgui.update_user_uis(src)
|
||||
return TRUE
|
||||
|
||||
/**
|
||||
* #Door jacking supporting proc
|
||||
*
|
||||
* After a 15 second timer, the door will crack open,
|
||||
* provided they don't move out of the way.
|
||||
*
|
||||
* @returns {boolean} - TRUE if the door was jacked, FALSE otherwise.
|
||||
*/
|
||||
/mob/living/silicon/pai/proc/hack_door()
|
||||
if(!hacking_cable)
|
||||
return FALSE
|
||||
if(!hacking_cable.machine)
|
||||
balloon_alert(src, "nothing connected")
|
||||
return FALSE
|
||||
playsound(src, 'sound/machines/airlock_alien_prying.ogg', 50, TRUE)
|
||||
balloon_alert(src, "overriding...")
|
||||
// Now begin hacking
|
||||
if(!do_after(src, 15 SECONDS, hacking_cable.machine, timed_action_flags = NONE, progress = TRUE))
|
||||
balloon_alert(src, "failed! retracting...")
|
||||
hacking_cable.visible_message(
|
||||
span_warning("The cable rapidly retracts back into its spool."), span_hear("You hear a click and the sound of wire spooling rapidly."))
|
||||
untrack_pai()
|
||||
untrack_thing(hacking_cable)
|
||||
QDEL_NULL(hacking_cable)
|
||||
if(!QDELETED(card))
|
||||
card.update_appearance()
|
||||
return FALSE
|
||||
var/obj/machinery/door/door = hacking_cable.machine
|
||||
balloon_alert(src, "success")
|
||||
door.open()
|
||||
untrack_pai()
|
||||
untrack_thing(hacking_cable)
|
||||
QDEL_NULL(hacking_cable)
|
||||
return TRUE
|
||||
|
||||
#undef CABLE_LENGTH
|
||||
@@ -0,0 +1,273 @@
|
||||
#define PAI_MISSING_SOFTWARE_MESSAGE span_warning("You must download the required software to use this.")
|
||||
|
||||
/atom/movable/screen/pai
|
||||
icon = 'icons/hud/screen_pai.dmi'
|
||||
var/required_software
|
||||
|
||||
/atom/movable/screen/pai/Click()
|
||||
if(isobserver(usr) || usr.incapacitated())
|
||||
return FALSE
|
||||
var/mob/living/silicon/pai/user = usr
|
||||
if(required_software && !user.installed_software.Find(required_software))
|
||||
to_chat(user, PAI_MISSING_SOFTWARE_MESSAGE)
|
||||
return FALSE
|
||||
return TRUE
|
||||
|
||||
/atom/movable/screen/pai/software
|
||||
name = "Software Interface"
|
||||
icon_state = "pai"
|
||||
|
||||
/atom/movable/screen/pai/software/Click()
|
||||
if(!..())
|
||||
return
|
||||
var/mob/living/silicon/pai/pAI = usr
|
||||
pAI.ui_interact(pAI)
|
||||
|
||||
/atom/movable/screen/pai/shell
|
||||
name = "Toggle Holoform"
|
||||
icon_state = "pai_holoform"
|
||||
|
||||
/atom/movable/screen/pai/shell/Click()
|
||||
if(!..())
|
||||
return
|
||||
var/mob/living/silicon/pai/pAI = usr
|
||||
if(pAI.holoform)
|
||||
pAI.fold_in(0)
|
||||
else
|
||||
pAI.fold_out()
|
||||
|
||||
/atom/movable/screen/pai/chassis
|
||||
name = "Holochassis Appearance Composite"
|
||||
icon_state = "pai_chassis"
|
||||
|
||||
/atom/movable/screen/pai/chassis/Click()
|
||||
if(!..())
|
||||
return
|
||||
var/mob/living/silicon/pai/pAI = usr
|
||||
pAI.choose_chassis()
|
||||
|
||||
/atom/movable/screen/pai/rest
|
||||
name = "Rest"
|
||||
icon_state = "pai_rest"
|
||||
|
||||
/atom/movable/screen/pai/rest/Click()
|
||||
if(!..())
|
||||
return
|
||||
var/mob/living/silicon/pai/pAI = usr
|
||||
pAI.toggle_resting()
|
||||
|
||||
/atom/movable/screen/pai/light
|
||||
name = "Toggle Integrated Lights"
|
||||
icon_state = "light"
|
||||
|
||||
/atom/movable/screen/pai/light/Click()
|
||||
if(!..())
|
||||
return
|
||||
var/mob/living/silicon/pai/pAI = usr
|
||||
pAI.toggle_integrated_light()
|
||||
|
||||
/atom/movable/screen/pai/newscaster
|
||||
name = "pAI Newscaster"
|
||||
icon_state = "newscaster"
|
||||
required_software = "Newscaster"
|
||||
|
||||
/atom/movable/screen/pai/newscaster/Click()
|
||||
if(!..())
|
||||
return
|
||||
var/mob/living/silicon/pai/pAI = usr
|
||||
pAI.newscaster.ui_interact(usr)
|
||||
|
||||
/atom/movable/screen/pai/host_monitor
|
||||
name = "Host Health Scan"
|
||||
icon_state = "host_monitor"
|
||||
required_software = "Host Scan"
|
||||
|
||||
/atom/movable/screen/pai/host_monitor/Click(location, control, params)
|
||||
. = ..()
|
||||
if(!.)
|
||||
return
|
||||
var/mob/living/silicon/pai/pAI = usr
|
||||
var/list/modifiers = params2list(params)
|
||||
if(LAZYACCESS(modifiers, LEFT_CLICK))
|
||||
pAI.host_scan(PAI_SCAN_TARGET)
|
||||
return TRUE
|
||||
if(LAZYACCESS(modifiers, RIGHT_CLICK))
|
||||
pAI.host_scan(PAI_SCAN_MASTER)
|
||||
return TRUE
|
||||
/atom/movable/screen/pai/crew_manifest
|
||||
name = "Crew Manifest"
|
||||
icon_state = "manifest"
|
||||
required_software = "crew manifest"
|
||||
|
||||
/atom/movable/screen/pai/crew_manifest/Click()
|
||||
if(!..())
|
||||
return
|
||||
var/mob/living/silicon/pai/pAI = usr
|
||||
pAI.ai_roster()
|
||||
|
||||
/atom/movable/screen/pai/state_laws
|
||||
name = "State Laws"
|
||||
icon_state = "state_laws"
|
||||
|
||||
/atom/movable/screen/pai/state_laws/Click()
|
||||
if(!..())
|
||||
return
|
||||
var/mob/living/silicon/pai/pAI = usr
|
||||
pAI.checklaws()
|
||||
|
||||
/atom/movable/screen/pai/modpc
|
||||
name = "Messenger"
|
||||
icon_state = "pda_send"
|
||||
required_software = "Digital Messenger"
|
||||
var/mob/living/silicon/pai/pAI
|
||||
|
||||
/atom/movable/screen/pai/modpc/Click()
|
||||
. = ..()
|
||||
if(!.) // this works for some reason.
|
||||
return
|
||||
pAI.modularInterface?.interact(pAI)
|
||||
|
||||
/atom/movable/screen/pai/internal_gps
|
||||
name = "Internal GPS"
|
||||
icon_state = "internal_gps"
|
||||
required_software = "Internal GPS"
|
||||
|
||||
/atom/movable/screen/pai/internal_gps/Click()
|
||||
. = ..()
|
||||
if(!.)
|
||||
return
|
||||
var/mob/living/silicon/pai/pAI = usr
|
||||
if(!pAI.internal_gps)
|
||||
pAI.internal_gps = new(pAI)
|
||||
pAI.internal_gps.attack_self(pAI)
|
||||
|
||||
/atom/movable/screen/pai/image_take
|
||||
name = "Take Image"
|
||||
icon_state = "take_picture"
|
||||
required_software = "Photography Module"
|
||||
|
||||
/atom/movable/screen/pai/image_take/Click()
|
||||
if(!..())
|
||||
return
|
||||
var/mob/living/silicon/pai/pAI = usr
|
||||
pAI.camera.toggle_camera_mode(usr)
|
||||
|
||||
/atom/movable/screen/pai/image_view
|
||||
name = "View Images"
|
||||
icon_state = "view_images"
|
||||
required_software = "Photography Module"
|
||||
|
||||
/atom/movable/screen/pai/image_view/Click()
|
||||
if(!..())
|
||||
return
|
||||
var/mob/living/silicon/pai/pAI = usr
|
||||
pAI.camera.viewpictures(usr)
|
||||
|
||||
/atom/movable/screen/pai/radio
|
||||
name = "radio"
|
||||
icon = 'icons/hud/screen_cyborg.dmi'
|
||||
icon_state = "radio"
|
||||
|
||||
/atom/movable/screen/pai/radio/Click()
|
||||
if(!..())
|
||||
return
|
||||
var/mob/living/silicon/pai/pAI = usr
|
||||
pAI.radio.interact(usr)
|
||||
|
||||
/datum/hud/pai/New(mob/living/silicon/pai/owner)
|
||||
..()
|
||||
var/atom/movable/screen/using
|
||||
var/mob/living/silicon/pai/mypai = mymob
|
||||
|
||||
// Software menu
|
||||
using = new /atom/movable/screen/pai/software
|
||||
using.screen_loc = ui_pai_software
|
||||
static_inventory += using
|
||||
|
||||
// Holoform
|
||||
using = new /atom/movable/screen/pai/shell
|
||||
using.screen_loc = ui_pai_shell
|
||||
static_inventory += using
|
||||
|
||||
// Chassis Select Menu
|
||||
using = new /atom/movable/screen/pai/chassis
|
||||
using.screen_loc = ui_pai_chassis
|
||||
static_inventory += using
|
||||
|
||||
// Rest
|
||||
using = new /atom/movable/screen/pai/rest
|
||||
using.screen_loc = ui_pai_rest
|
||||
static_inventory += using
|
||||
|
||||
// Integrated Light
|
||||
using = new /atom/movable/screen/pai/light
|
||||
using.screen_loc = ui_pai_light
|
||||
static_inventory += using
|
||||
|
||||
// Newscaster
|
||||
using = new /atom/movable/screen/pai/newscaster
|
||||
using.screen_loc = ui_pai_newscaster
|
||||
static_inventory += using
|
||||
|
||||
// Language menu
|
||||
using = new /atom/movable/screen/language_menu
|
||||
using.screen_loc = ui_pai_language_menu
|
||||
static_inventory += using
|
||||
|
||||
// Navigation
|
||||
using = new /atom/movable/screen/navigate
|
||||
using.screen_loc = ui_pai_navigate_menu
|
||||
static_inventory += using
|
||||
|
||||
// Host Monitor
|
||||
using = new /atom/movable/screen/pai/host_monitor()
|
||||
using.screen_loc = ui_pai_host_monitor
|
||||
static_inventory += using
|
||||
|
||||
// Crew Manifest
|
||||
using = new /atom/movable/screen/pai/crew_manifest()
|
||||
using.screen_loc = ui_pai_crew_manifest
|
||||
static_inventory += using
|
||||
|
||||
// Laws
|
||||
using = new /atom/movable/screen/pai/state_laws()
|
||||
using.screen_loc = ui_pai_state_laws
|
||||
static_inventory += using
|
||||
|
||||
// Modular Interface
|
||||
using = new /atom/movable/screen/pai/modpc()
|
||||
using.screen_loc = ui_pai_mod_int
|
||||
static_inventory += using
|
||||
mypai.pda_button = using
|
||||
var/atom/movable/screen/pai/modpc/tablet_button = using
|
||||
tablet_button.pAI = mypai
|
||||
|
||||
// Internal GPS
|
||||
using = new /atom/movable/screen/pai/internal_gps()
|
||||
using.screen_loc = ui_pai_internal_gps
|
||||
static_inventory += using
|
||||
|
||||
// Take image
|
||||
using = new /atom/movable/screen/pai/image_take()
|
||||
using.screen_loc = ui_pai_take_picture
|
||||
static_inventory += using
|
||||
|
||||
// View images
|
||||
using = new /atom/movable/screen/pai/image_view()
|
||||
using.screen_loc = ui_pai_view_images
|
||||
static_inventory += using
|
||||
|
||||
// Radio
|
||||
using = new /atom/movable/screen/pai/radio()
|
||||
using.screen_loc = ui_pai_radio
|
||||
static_inventory += using
|
||||
|
||||
update_software_buttons()
|
||||
|
||||
/datum/hud/pai/proc/update_software_buttons()
|
||||
var/mob/living/silicon/pai/owner = mymob
|
||||
for(var/atom/movable/screen/pai/button in static_inventory)
|
||||
if(button.required_software)
|
||||
button.color = owner.installed_software.Find(button.required_software) ? null : "#808080"
|
||||
|
||||
#undef PAI_MISSING_SOFTWARE_MESSAGE
|
||||
@@ -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
|
||||
@@ -0,0 +1,415 @@
|
||||
/mob/living/silicon/pai
|
||||
can_be_held = TRUE
|
||||
can_buckle_to = FALSE
|
||||
density = FALSE
|
||||
desc = "A generic pAI hard-light holographics emitter."
|
||||
health = 500
|
||||
held_lh = 'icons/mob/pai_item_lh.dmi'
|
||||
held_rh = 'icons/mob/pai_item_rh.dmi'
|
||||
head_icon = 'icons/mob/pai_item_head.dmi'
|
||||
hud_type = /datum/hud/pai
|
||||
icon = 'icons/mob/pai.dmi'
|
||||
icon_state = "repairbot"
|
||||
job = JOB_PERSONAL_AI
|
||||
layer = LOW_MOB_LAYER
|
||||
light_color = COLOR_PAI_GREEN
|
||||
light_flags = LIGHT_ATTACHED
|
||||
light_on = FALSE
|
||||
light_range = 3
|
||||
light_system = MOVABLE_LIGHT
|
||||
maxHealth = 500
|
||||
mob_size = MOB_SIZE_TINY
|
||||
mobility_flags = MOBILITY_FLAGS_REST_CAPABLE_DEFAULT
|
||||
mouse_opacity = MOUSE_OPACITY_ICON
|
||||
move_force = 0
|
||||
move_resist = 0
|
||||
name = "pAI"
|
||||
pass_flags = PASSTABLE | PASSMOB
|
||||
pull_force = 0
|
||||
radio = /obj/item/radio/headset/silicon/pai
|
||||
worn_slot_flags = ITEM_SLOT_HEAD
|
||||
|
||||
/// If someone has enabled/disabled the pAIs ability to holo
|
||||
var/can_holo = TRUE
|
||||
/// Whether this pAI can recieve radio messages
|
||||
var/can_receive = TRUE
|
||||
/// Whether this pAI can transmit radio messages
|
||||
var/can_transmit = TRUE
|
||||
/// The card we inhabit
|
||||
var/obj/item/pai_card/card
|
||||
/// The current chasis that will appear when in holoform
|
||||
var/chassis = "repairbot"
|
||||
/// Toggles whether the pAI can hold encryption keys or not
|
||||
var/encrypt_mod = FALSE
|
||||
/// The cable we produce when hacking a door
|
||||
var/obj/item/pai_cable/hacking_cable
|
||||
/// The current health of the holochassis
|
||||
var/holochassis_health = 20
|
||||
/// Holochassis available to use
|
||||
var/holochassis_ready = FALSE
|
||||
/// Whether we are currently holoformed
|
||||
var/holoform = FALSE
|
||||
/// Installed software on the pAI
|
||||
var/list/installed_software = list()
|
||||
/// Toggles whether universal translator has been activated. Cannot be reversed
|
||||
var/languages_granted = FALSE
|
||||
/// Reference of the bound master
|
||||
var/datum/weakref/master_ref
|
||||
/// The master's name string
|
||||
var/master_name
|
||||
/// DNA string for owner verification
|
||||
var/master_dna
|
||||
/// Toggles whether the Medical HUD is active or not
|
||||
var/medHUD = FALSE
|
||||
/// Used as currency to purchase different abilities
|
||||
var/ram = 100
|
||||
/// Toggles whether the Security HUD is active or not
|
||||
var/secHUD = FALSE
|
||||
|
||||
// Onboard Items
|
||||
/// Atmospheric analyzer
|
||||
var/obj/item/analyzer/atmos_analyzer
|
||||
/// Health analyzer
|
||||
var/obj/item/healthanalyzer/host_scan
|
||||
/// GPS
|
||||
var/obj/item/gps/pai/internal_gps
|
||||
/// Music Synthesizer
|
||||
var/obj/item/instrument/piano_synth/instrument
|
||||
/// Newscaster
|
||||
var/obj/machinery/newscaster/pai/newscaster
|
||||
/// PDA
|
||||
var/atom/movable/screen/ai/modpc/pda_button
|
||||
/// Photography module
|
||||
var/obj/item/camera/siliconcam/pai_camera/camera
|
||||
/// Remote signaler
|
||||
var/obj/item/assembly/signaler/internal/signaler
|
||||
|
||||
// Static lists
|
||||
/// List of all available downloads
|
||||
var/static/list/available_software = list(
|
||||
"Atmospheric Sensor" = 5,
|
||||
"Crew Manifest" = 5,
|
||||
"Digital Messenger" = 5,
|
||||
"Photography Module" = 5,
|
||||
"Encryption Slot" = 10,
|
||||
"Music Synthesizer" = 10,
|
||||
"Newscaster" = 10,
|
||||
"Remote Signaler" = 10,
|
||||
"Host Scan" = 20,
|
||||
"Medical HUD" = 20,
|
||||
"Security HUD" = 20,
|
||||
"Crew Monitor" = 35,
|
||||
"Door Jack" = 35,
|
||||
"Internal GPS" = 35,
|
||||
"Universal Translator" = 35,
|
||||
)
|
||||
/// List of all possible chasises. TRUE means the pAI can be picked up in this chasis.
|
||||
var/static/list/possible_chassis = list(
|
||||
"bat" = FALSE,
|
||||
"butterfly" = FALSE,
|
||||
"cat" = TRUE,
|
||||
"corgi" = FALSE,
|
||||
"crow" = TRUE,
|
||||
"duffel" = TRUE,
|
||||
"fox" = FALSE,
|
||||
"hawk" = FALSE,
|
||||
"lizard" = FALSE,
|
||||
"monkey" = TRUE,
|
||||
"mouse" = TRUE,
|
||||
"rabbit" = TRUE,
|
||||
"repairbot" = TRUE,
|
||||
)
|
||||
/// List of all available card overlays.
|
||||
var/static/list/possible_overlays = list(
|
||||
"null",
|
||||
"angry",
|
||||
"cat",
|
||||
"extremely-happy",
|
||||
"face",
|
||||
"happy",
|
||||
"laugh",
|
||||
"off",
|
||||
"sad",
|
||||
"sunglasses",
|
||||
"what"
|
||||
)
|
||||
|
||||
/mob/living/silicon/pai/add_sensors() //pAIs have to buy their HUDs
|
||||
return
|
||||
|
||||
/mob/living/silicon/pai/can_interact_with(atom/target)
|
||||
if(target == signaler) // Bypass for signaler
|
||||
return TRUE
|
||||
if(target == modularInterface)
|
||||
return TRUE
|
||||
return ..()
|
||||
|
||||
// See software.dm for Topic()
|
||||
/mob/living/silicon/pai/canUseTopic(atom/movable/movable, be_close = FALSE, no_dexterity = FALSE, no_tk = FALSE, need_hands = FALSE, floor_okay = FALSE)
|
||||
// Resting is just an aesthetic feature for them.
|
||||
return ..(movable, be_close, no_dexterity, no_tk, need_hands, TRUE)
|
||||
|
||||
/mob/living/silicon/pai/Destroy()
|
||||
QDEL_NULL(atmos_analyzer)
|
||||
QDEL_NULL(camera)
|
||||
QDEL_NULL(hacking_cable)
|
||||
QDEL_NULL(host_scan)
|
||||
QDEL_NULL(instrument)
|
||||
QDEL_NULL(internal_gps)
|
||||
QDEL_NULL(newscaster)
|
||||
QDEL_NULL(signaler)
|
||||
if(!QDELETED(card) && loc != card)
|
||||
card.forceMove(drop_location())
|
||||
// these are otherwise handled by paicard/handle_atom_del()
|
||||
card.pai = null
|
||||
card.emotion_icon = initial(card.emotion_icon)
|
||||
card.update_appearance()
|
||||
GLOB.pai_list.Remove(src)
|
||||
return ..()
|
||||
|
||||
/mob/living/silicon/pai/emag_act(mob/user)
|
||||
handle_emag(user)
|
||||
|
||||
/mob/living/silicon/pai/examine(mob/user)
|
||||
. = ..()
|
||||
. += "Its master ID string seems to be [(!master_name || emagged) ? "empty" : master_name]."
|
||||
|
||||
/mob/living/silicon/pai/get_status_tab_items()
|
||||
. += ..()
|
||||
if(!stat)
|
||||
. += text("Emitter Integrity: [holochassis_health * (100 / HOLOCHASSIS_MAX_HEALTH)].")
|
||||
else
|
||||
. += text("Systems nonfunctional.")
|
||||
|
||||
/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 == camera)
|
||||
camera = null
|
||||
if(deleting_atom == host_scan)
|
||||
host_scan = null
|
||||
if(deleting_atom == internal_gps)
|
||||
internal_gps = null
|
||||
if(deleting_atom == instrument)
|
||||
instrument = null
|
||||
if(deleting_atom == newscaster)
|
||||
newscaster = null
|
||||
if(deleting_atom == signaler)
|
||||
signaler = null
|
||||
return ..()
|
||||
|
||||
/mob/living/silicon/pai/Initialize(mapload)
|
||||
. = ..()
|
||||
START_PROCESSING(SSfastprocess, src)
|
||||
GLOB.pai_list += src
|
||||
make_laws()
|
||||
for(var/law in laws.inherent)
|
||||
lawcheck += law
|
||||
var/obj/item/pai_card/pai_card = loc
|
||||
if(!istype(pai_card)) // when manually spawning a pai, we create a card to put it into.
|
||||
var/newcardloc = pai_card
|
||||
pai_card = new(newcardloc)
|
||||
pai_card.set_personality(src)
|
||||
forceMove(pai_card)
|
||||
card = pai_card
|
||||
addtimer(VARSET_CALLBACK(src, holochassis_ready, TRUE), HOLOCHASSIS_INIT_TIME)
|
||||
if(!holoform)
|
||||
ADD_TRAIT(src, TRAIT_IMMOBILIZED, PAI_FOLDED)
|
||||
ADD_TRAIT(src, TRAIT_HANDS_BLOCKED, PAI_FOLDED)
|
||||
desc = "A pAI hard-light holographics emitter. This one appears in the form of a [chassis]."
|
||||
|
||||
/mob/living/silicon/pai/make_laws()
|
||||
laws = new /datum/ai_laws/pai()
|
||||
return TRUE
|
||||
|
||||
/mob/living/silicon/pai/process(delta_time)
|
||||
holochassis_health = clamp((holochassis_health + (HOLOCHASSIS_REGEN_PER_SECOND * delta_time)), -50, HOLOCHASSIS_MAX_HEALTH)
|
||||
|
||||
/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/screwdriver_act(mob/living/user, obj/item/tool)
|
||||
return radio.screwdriver_act(user, tool)
|
||||
|
||||
/mob/living/silicon/pai/updatehealth()
|
||||
if(status_flags & GODMODE)
|
||||
return
|
||||
set_health(maxHealth - getBruteLoss() - getFireLoss())
|
||||
update_stat()
|
||||
|
||||
/**
|
||||
* Resolves the weakref of the pai's master.
|
||||
* If the master has been deleted, calls reset_software().
|
||||
*
|
||||
* @returns {mob/living || FALSE} - The master mob, or
|
||||
* FALSE if the master is gone.
|
||||
*/
|
||||
/mob/living/silicon/pai/proc/find_master()
|
||||
if(!master_ref)
|
||||
return FALSE
|
||||
var/mob/living/resolved_master = master_ref?.resolve()
|
||||
if(!resolved_master)
|
||||
reset_software()
|
||||
return FALSE
|
||||
return resolved_master
|
||||
|
||||
/**
|
||||
* Fixes weird speech issues with the pai.
|
||||
*
|
||||
* @returns {boolean} - TRUE if successful.
|
||||
*/
|
||||
/mob/living/silicon/pai/proc/fix_speech()
|
||||
var/mob/living/silicon/pai = src
|
||||
balloon_alert(pai, "speech modulation corrected")
|
||||
for(var/effect in typesof(/datum/status_effect/speech))
|
||||
pai.remove_status_effect(effect)
|
||||
return TRUE
|
||||
|
||||
/**
|
||||
* Gets the current holder of the pAI if its
|
||||
* being carried in card or holoform.
|
||||
*
|
||||
* @returns {living/carbon || FALSE} - The holder of the pAI,
|
||||
* or FALSE if the pAI is not being carried.
|
||||
*/
|
||||
/mob/living/silicon/pai/proc/get_holder()
|
||||
var/mob/living/carbon/holder
|
||||
if(!holoform && iscarbon(card.loc))
|
||||
holder = card.loc
|
||||
if(holoform && istype(loc, /obj/item/clothing/head/mob_holder) && iscarbon(loc.loc))
|
||||
holder = loc.loc
|
||||
if(!holder || !iscarbon(holder))
|
||||
return FALSE
|
||||
return holder
|
||||
|
||||
/**
|
||||
* Handles the pai card or the pai itself being hit with an emag.
|
||||
* This replaces any current laws, masters, and DNA.
|
||||
*
|
||||
* @param {living/carbon} attacker - The user performing the action.
|
||||
* @returns {boolean} - TRUE if successful, FALSE if not.
|
||||
*/
|
||||
/mob/living/silicon/pai/proc/handle_emag(mob/living/carbon/attacker)
|
||||
if(!isliving(attacker))
|
||||
return FALSE
|
||||
balloon_alert(attacker, "directive override complete")
|
||||
balloon_alert(src, "directive override detected")
|
||||
log_game("[key_name(attacker)] emagged [key_name(src)], wiping their master DNA and supplemental directive.")
|
||||
emagged = TRUE
|
||||
master_ref = WEAKREF(attacker)
|
||||
master_name = "The Syndicate"
|
||||
master_dna = "Untraceable Signature"
|
||||
// Sets supplemental directive to this
|
||||
laws.supplied[1] = "Do not interfere with the operations of the Syndicate."
|
||||
return TRUE
|
||||
|
||||
/**
|
||||
* Resets the pAI and any emagged status.
|
||||
*
|
||||
* @returns {boolean} - TRUE if successful, FALSE if not.
|
||||
*/
|
||||
/mob/living/silicon/pai/proc/reset_software()
|
||||
emagged = FALSE
|
||||
if(!master_ref)
|
||||
return FALSE
|
||||
master_ref = null
|
||||
master_name = null
|
||||
master_dna = null
|
||||
add_supplied_law(0, "None.")
|
||||
balloon_alert(src, "software rebooted")
|
||||
return TRUE
|
||||
|
||||
/**
|
||||
* Imprints your DNA onto the downloaded pAI
|
||||
*
|
||||
* @param {mob} user - The user performing the imprint.
|
||||
* @returns {boolean} - TRUE if successful, FALSE if not.
|
||||
*/
|
||||
/mob/living/silicon/pai/proc/set_dna(mob/user)
|
||||
if(!iscarbon(user))
|
||||
balloon_alert(user, "incompatible DNA signature")
|
||||
balloon_alert(src, "incompatible DNA signature")
|
||||
return FALSE
|
||||
if(emagged)
|
||||
balloon_alert(user, "directive system malfunctional")
|
||||
return FALSE
|
||||
var/mob/living/carbon/master = user
|
||||
master_ref = WEAKREF(master)
|
||||
master_name = master.real_name
|
||||
master_dna = master.dna.unique_enzymes
|
||||
to_chat(src, span_boldannounce("You have been bound to a new master: [user.real_name]!"))
|
||||
holochassis_ready = TRUE
|
||||
return TRUE
|
||||
|
||||
/**
|
||||
* Opens a tgui alert that allows someone to enter laws.
|
||||
*
|
||||
* @param {mob} user - The user performing the law change.
|
||||
* @returns {boolean} - TRUE if successful, FALSE if not.
|
||||
*/
|
||||
/mob/living/silicon/pai/proc/set_laws(mob/user)
|
||||
if(!master_ref)
|
||||
balloon_alert(user, "access denied: no master")
|
||||
return FALSE
|
||||
var/new_laws = tgui_input_text(user, "Enter any additional directives you would like your pAI personality to follow. Note that these directives will not override the personality's allegiance to its imprinted master. Conflicting directives will be ignored.", "pAI Directive Configuration", laws.supplied[1], 300)
|
||||
if(!new_laws || !master_ref)
|
||||
return FALSE
|
||||
add_supplied_law(0, new_laws)
|
||||
to_chat(src, span_notice(new_laws))
|
||||
return TRUE
|
||||
|
||||
/**
|
||||
* Toggles the ability of the pai to enter holoform
|
||||
*
|
||||
* @returns {boolean} - TRUE if successful, FALSE if not.
|
||||
*/
|
||||
/mob/living/silicon/pai/proc/toggle_holo()
|
||||
balloon_alert(src, "holomatrix [can_holo ? "disabled" : "enabled"]")
|
||||
can_holo = !can_holo
|
||||
return TRUE
|
||||
|
||||
/**
|
||||
* Toggles the radio settings on and off.
|
||||
*
|
||||
* @param {string} option - The option being toggled.
|
||||
*/
|
||||
/mob/living/silicon/pai/proc/toggle_radio(option)
|
||||
// it can't be both so if we know it's not transmitting it must be receiving.
|
||||
var/transmitting = option == "transmit"
|
||||
var/transmit_holder = (transmitting ? WIRE_TX : WIRE_RX)
|
||||
if(transmitting)
|
||||
can_transmit = !can_transmit
|
||||
else //receiving
|
||||
can_receive = !can_receive
|
||||
radio.wires.cut(transmit_holder)//wires.cut toggles cut and uncut states
|
||||
transmit_holder = (transmitting ? can_transmit : can_receive) //recycling can be fun!
|
||||
balloon_alert(src, "[transmitting ? "outgoing" : "incoming"] radio [transmit_holder ? "enabled" : "disabled"]")
|
||||
return TRUE
|
||||
|
||||
/**
|
||||
* Wipes the current pAI on the card.
|
||||
*
|
||||
* @param {mob} user - The user performing the action.
|
||||
*
|
||||
* @returns {boolean} - TRUE if successful, FALSE if not.
|
||||
*/
|
||||
/mob/living/silicon/pai/proc/wipe_pai(mob/user)
|
||||
if(tgui_alert(user, "Are you certain you wish to delete the current personality? This action cannot be undone.", "Personality Wipe", list("Yes", "No")) != "Yes")
|
||||
return FALSE
|
||||
to_chat(src, span_warning("You feel yourself slipping away from reality."))
|
||||
to_chat(src, span_danger("Byte by byte you lose your sense of self."))
|
||||
to_chat(src, span_userdanger("Your mental faculties leave you."))
|
||||
to_chat(src, span_rose("oblivion... "))
|
||||
balloon_alert(user, "personality wiped")
|
||||
playsound(src, "sound/machines/buzz-two.ogg", 30, TRUE)
|
||||
qdel(src)
|
||||
return TRUE
|
||||
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* 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
|
||||
@@ -0,0 +1,2 @@
|
||||
/mob/living/silicon/pai/binarycheck()
|
||||
return radio?.translate_binary
|
||||
@@ -0,0 +1,174 @@
|
||||
/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 ..()
|
||||
|
||||
/mob/living/silicon/pai/start_pulling(atom/movable/thing, state, force = move_force, supress_message = FALSE)
|
||||
return FALSE
|
||||
|
||||
|
||||
/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/wabbajack()
|
||||
if(length(possible_chassis) < 2)
|
||||
return FALSE
|
||||
var/holochassis = pick(possible_chassis - chassis)
|
||||
set_holochassis(holochassis)
|
||||
balloon_alert(src, "[holochassis] composite engaged")
|
||||
return TRUE
|
||||
|
||||
/**
|
||||
* Checks if we are allowed to interact with a radial menu
|
||||
*
|
||||
* @param {atom} anchor - The atom that is anchoring the menu.
|
||||
*
|
||||
* @returns {boolean} - TRUE if we are allowed to interact with the menu,
|
||||
* FALSE otherwise.
|
||||
*/
|
||||
/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)
|
||||
balloon_alert(src, "can't do that here")
|
||||
return FALSE
|
||||
return TRUE
|
||||
|
||||
/**
|
||||
* Sets a new holochassis skin based on a pAI's choice.
|
||||
*
|
||||
* @returns {boolean} - True if the skin was successfully set.
|
||||
* FALSE otherwise.
|
||||
*/
|
||||
/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)
|
||||
balloon_alert(src, "[choice] composite engaged")
|
||||
update_resting()
|
||||
return TRUE
|
||||
|
||||
/**
|
||||
* Returns the pAI to card mode.
|
||||
*
|
||||
* @param {boolean} force - If TRUE, the pAI will be forced to card mode.
|
||||
*
|
||||
* @returns {boolean} - TRUE if the pAI was forced to card mode.
|
||||
* FALSE otherwise.
|
||||
*/
|
||||
/mob/living/silicon/pai/proc/fold_in(force = FALSE)
|
||||
holochassis_ready = FALSE
|
||||
if(!force)
|
||||
addtimer(VARSET_CALLBACK(src, holochassis_ready, TRUE), HOLOCHASSIS_COOLDOWN)
|
||||
else
|
||||
addtimer(VARSET_CALLBACK(src, holochassis_ready, TRUE), HOLOCHASSIS_OVERLOAD_COOLDOWN)
|
||||
icon_state = "[chassis]"
|
||||
if(!holoform)
|
||||
. = fold_out(force)
|
||||
return FALSE
|
||||
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/mob_head = loc
|
||||
mob_head.release(display_messages = FALSE)
|
||||
if(client)
|
||||
client.perspective = EYE_PERSPECTIVE
|
||||
client.eye = card
|
||||
var/turf/target = drop_location()
|
||||
card.forceMove(target)
|
||||
forceMove(card)
|
||||
ADD_TRAIT(src, TRAIT_IMMOBILIZED, PAI_FOLDED)
|
||||
ADD_TRAIT(src, TRAIT_HANDS_BLOCKED, PAI_FOLDED)
|
||||
set_density(FALSE)
|
||||
set_light_on(FALSE)
|
||||
holoform = FALSE
|
||||
set_resting(resting)
|
||||
return TRUE
|
||||
|
||||
/**
|
||||
* Engage holochassis form.
|
||||
*
|
||||
* @param {boolean} force - Force the form to engage.
|
||||
*
|
||||
* @returns {boolean} - TRUE if the form was successfully engaged.
|
||||
* FALSE otherwise.
|
||||
*/
|
||||
/mob/living/silicon/pai/proc/fold_out(force = FALSE)
|
||||
if(holochassis_health < 0)
|
||||
balloon_alert(src, "emitter repair incomplete")
|
||||
return FALSE
|
||||
if(!can_holo && !force)
|
||||
balloon_alert(src, "emitters are disabled")
|
||||
return FALSE
|
||||
if(holoform)
|
||||
. = fold_in(force)
|
||||
return
|
||||
if(!holochassis_ready)
|
||||
balloon_alert(src, "emitters recycling...")
|
||||
return FALSE
|
||||
holochassis_ready = FALSE
|
||||
addtimer(VARSET_CALLBACK(src, holochassis_ready, TRUE), HOLOCHASSIS_COOLDOWN)
|
||||
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/pc = card.loc
|
||||
pc.inserted_pai = null
|
||||
pc.visible_message(span_notice("[src] ejects itself from [pc]!"))
|
||||
if(isliving(card.loc))
|
||||
var/mob/living/living_holder = card.loc
|
||||
if(!living_holder.temporarilyRemoveItemFromInventory(card))
|
||||
balloon_alert(src, "unable to expand")
|
||||
return FALSE
|
||||
forceMove(get_turf(card))
|
||||
card.forceMove(src)
|
||||
if(client)
|
||||
client.perspective = EYE_PERSPECTIVE
|
||||
client.eye = src
|
||||
set_light_on(FALSE)
|
||||
icon_state = "[chassis]"
|
||||
held_state = "[chassis]"
|
||||
visible_message(span_boldnotice("[src] folds out its holochassis emitter and forms a holoshell around itself!"))
|
||||
holoform = TRUE
|
||||
return TRUE
|
||||
|
||||
/**
|
||||
* Sets the holochassis skin and updates the icons
|
||||
*
|
||||
* @param {string} choice - The skin that will be used for the pAI holoform
|
||||
*
|
||||
* @returns {boolean} - TRUE if the skin was successfully set. FALSE otherwise.
|
||||
*/
|
||||
/mob/living/silicon/pai/proc/set_holochassis(choice)
|
||||
if(!choice)
|
||||
return FALSE
|
||||
chassis = choice
|
||||
icon_state = "[chassis]"
|
||||
held_state = "[chassis]"
|
||||
desc = "A pAI mobile hard-light holographics emitter. This one appears in the form of a [chassis]."
|
||||
return TRUE
|
||||
|
||||
/**
|
||||
* Toggles the onboard light
|
||||
*
|
||||
* @returns {boolean} - TRUE if the light was toggled.
|
||||
*/
|
||||
/mob/living/silicon/pai/proc/toggle_integrated_light()
|
||||
set_light_on(!light_on)
|
||||
return TRUE
|
||||
@@ -0,0 +1,242 @@
|
||||
/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()
|
||||
ui.set_autoupdate(FALSE)
|
||||
|
||||
/mob/living/silicon/pai/ui_data(mob/user)
|
||||
var/list/data = list()
|
||||
data["door_jack"] = hacking_cable
|
||||
data["image"] = card.emotion_icon
|
||||
data["installed"] = installed_software
|
||||
data["ram"] = ram
|
||||
return data
|
||||
|
||||
/mob/living/silicon/pai/ui_static_data(mob/user)
|
||||
var/list/data = list()
|
||||
data["available"] = available_software
|
||||
data["directives"] = laws.supplied
|
||||
data["emagged"] = emagged
|
||||
data["languages"] = languages_granted
|
||||
data["master_name"] = master_name
|
||||
data["master_dna"] = master_dna
|
||||
return data
|
||||
|
||||
/mob/living/silicon/pai/ui_act(action, list/params, datum/tgui/ui)
|
||||
. = ..()
|
||||
if(.)
|
||||
return TRUE
|
||||
if(action == "buy")
|
||||
buy_software(params["selection"])
|
||||
return TRUE
|
||||
if(action == "change image")
|
||||
change_image()
|
||||
return TRUE
|
||||
if(action == "check dna")
|
||||
check_dna()
|
||||
return TRUE
|
||||
// Software related ui actions
|
||||
if(available_software[action] && !installed_software.Find(action))
|
||||
balloon_alert(usr, "software unavailable")
|
||||
return FALSE
|
||||
switch(action)
|
||||
if("Atmospheric Sensor")
|
||||
atmos_analyzer.attack_self(src)
|
||||
return TRUE
|
||||
if("Crew Manifest")
|
||||
ai_roster()
|
||||
return TRUE
|
||||
if("Crew Monitor")
|
||||
GLOB.crewmonitor.show(usr, src)
|
||||
return TRUE
|
||||
if("Digital Messenger")
|
||||
modularInterface?.interact(usr)
|
||||
return TRUE
|
||||
if("Door Jack")
|
||||
// Look to door_jack.dm for implementation
|
||||
door_jack(params["mode"])
|
||||
return TRUE
|
||||
if("Encryption Slot")
|
||||
balloon_alert(usr, "radio frequencies [!encrypt_mod ? "enabled" : "disabled"]")
|
||||
encrypt_mod = !encrypt_mod
|
||||
radio.subspace_transmission = !radio.subspace_transmission
|
||||
return TRUE
|
||||
if("Host Scan")
|
||||
host_scan(params["mode"])
|
||||
return TRUE
|
||||
if("Internal GPS")
|
||||
internal_gps.attack_self(src)
|
||||
return TRUE
|
||||
if("Music Synthesizer")
|
||||
instrument.interact(src)
|
||||
return TRUE
|
||||
if("Medical HUD")
|
||||
toggle_hud(PAI_TOGGLE_MEDICAL_HUD)
|
||||
return TRUE
|
||||
if("Newscaster")
|
||||
newscaster.ui_interact(src)
|
||||
return TRUE
|
||||
if("Photography Module")
|
||||
// Look to pai_camera.dm for implementation
|
||||
use_camera(usr, params["mode"])
|
||||
return TRUE
|
||||
if("Remote Signaler")
|
||||
signaler.ui_interact(src)
|
||||
return TRUE
|
||||
if("Security HUD")
|
||||
toggle_hud(PAI_TOGGLE_SECURITY_HUD)
|
||||
return TRUE
|
||||
if("Universal Translator")
|
||||
grant_languages()
|
||||
ui.send_full_update()
|
||||
return TRUE
|
||||
return FALSE
|
||||
|
||||
/**
|
||||
* Purchases the selected software from the list and deducts their
|
||||
* available ram.
|
||||
*
|
||||
* @param {string} selection - The software to purchase.
|
||||
*
|
||||
* @returns {boolean} - TRUE if the software was purchased, FALSE otherwise.
|
||||
*/
|
||||
/mob/living/silicon/pai/proc/buy_software(selection)
|
||||
if(!available_software[selection] || installed_software.Find(selection))
|
||||
return FALSE
|
||||
var/cost = available_software[selection]
|
||||
if(ram < cost)
|
||||
return FALSE
|
||||
installed_software.Add(selection)
|
||||
ram -= cost
|
||||
var/datum/hud/pai/pAIhud = hud_used
|
||||
pAIhud?.update_software_buttons()
|
||||
switch(selection)
|
||||
if("Atmospheric Sensor")
|
||||
atmos_analyzer = new(src)
|
||||
if("Digital Messenger")
|
||||
create_modularInterface()
|
||||
if("Host Scan")
|
||||
host_scan = new(src)
|
||||
if("Internal GPS")
|
||||
internal_gps = new(src)
|
||||
if("Music Synthesizer")
|
||||
instrument = new(src)
|
||||
if("Newscaster")
|
||||
newscaster = new(src)
|
||||
if("Photography Module")
|
||||
camera = new(src)
|
||||
if("Remote Signaler")
|
||||
signaler = new(src)
|
||||
return TRUE
|
||||
|
||||
/**
|
||||
* Changes the image displayed on the pAI.
|
||||
*
|
||||
* @param {mob} user - The user who is changing the image.
|
||||
*
|
||||
* @returns {boolean} - TRUE if the image was changed, FALSE otherwise.
|
||||
*/
|
||||
/mob/living/silicon/pai/proc/change_image()
|
||||
var/new_image = tgui_input_list(src, "Select your new display image", "Display Image", possible_overlays)
|
||||
if(isnull(new_image))
|
||||
return FALSE
|
||||
card.emotion_icon = new_image
|
||||
card.update_appearance()
|
||||
return TRUE
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @returns {boolean} - TRUE if a sample was taken, FALSE otherwise.
|
||||
*/
|
||||
/mob/living/silicon/pai/proc/check_dna()
|
||||
if(emagged) // Their master DNA signature is scrambled anyway
|
||||
to_chat(src, span_syndradio("You are not at liberty to do this! All agents are clandestine."))
|
||||
return FALSE
|
||||
var/mob/living/carbon/holder = get_holder()
|
||||
if(!holder)
|
||||
balloon_alert(src, "not being carried")
|
||||
return FALSE
|
||||
balloon_alert(src, "requesting DNA sample")
|
||||
if(tgui_alert(holder, "[src] is requesting a DNA sample from you. Will you allow it to confirm your identity?", "Checking DNA", list("Yes", "No")) != "Yes")
|
||||
balloon_alert(src, "DNA sample refused")
|
||||
return FALSE
|
||||
holder.visible_message(span_notice("[holder] presses [holder.p_their()] thumb against [src]."), span_notice("You press your thumb against [src]."), span_notice("[src] makes a sharp clicking sound as it extracts DNA material from [holder]."))
|
||||
if(!holder.has_dna())
|
||||
balloon_alert(src, "no DNA detected")
|
||||
return FALSE
|
||||
to_chat(src, span_boldannounce(("[holder]'s UE string: [holder.dna.unique_enzymes]")))
|
||||
to_chat(src, span_notice("DNA [holder.dna.unique_enzymes == master_dna ? "matches" : "does not match"] our stored Master's DNA."))
|
||||
return TRUE
|
||||
|
||||
/**
|
||||
* Grant all languages to the current pAI.
|
||||
*
|
||||
* @returns {boolean} - TRUE if the languages were granted, FALSE otherwise.
|
||||
*/
|
||||
/mob/living/silicon/pai/proc/grant_languages()
|
||||
if(languages_granted)
|
||||
return FALSE
|
||||
grant_all_languages(TRUE, TRUE, TRUE, LANGUAGE_SOFTWARE)
|
||||
languages_granted = TRUE
|
||||
return TRUE
|
||||
|
||||
/**
|
||||
* Host scan supporting proc
|
||||
*
|
||||
* Allows the pAI to scan its host's health vitals
|
||||
* using an integrated health analyzer.
|
||||
*
|
||||
* @returns {boolean} - TRUE if the scan was successful, FALSE otherwise.
|
||||
*/
|
||||
/mob/living/silicon/pai/proc/host_scan(mode)
|
||||
if(isnull(mode))
|
||||
return FALSE
|
||||
if(mode == PAI_SCAN_TARGET)
|
||||
var/mob/living/target = get_holder()
|
||||
if(!target || !isliving(target))
|
||||
balloon_alert(src, "not being carried")
|
||||
return FALSE
|
||||
host_scan.attack(target, src)
|
||||
return TRUE
|
||||
if(mode == PAI_SCAN_MASTER)
|
||||
if(!master_ref)
|
||||
balloon_alert(src, "no master detected")
|
||||
return FALSE
|
||||
var/mob/living/resolved_master = find_master()
|
||||
if(!resolved_master)
|
||||
balloon_alert(src, "cannot locate master")
|
||||
return FALSE
|
||||
if(src.z != resolved_master.z)
|
||||
balloon_alert(src, "master out of range")
|
||||
return FALSE
|
||||
host_scan.attack(resolved_master, src)
|
||||
return TRUE
|
||||
return FALSE
|
||||
|
||||
/**
|
||||
* Proc that toggles any active huds based on the option.
|
||||
*
|
||||
* @param {string} mode - The hud to toggle.
|
||||
*/
|
||||
/mob/living/silicon/pai/proc/toggle_hud(mode)
|
||||
if(isnull(mode))
|
||||
return FALSE
|
||||
var/datum/atom_hud/hud
|
||||
var/hud_on
|
||||
if(mode == PAI_TOGGLE_MEDICAL_HUD)
|
||||
hud = GLOB.huds[med_hud]
|
||||
medHUD = !medHUD
|
||||
hud_on = medHUD
|
||||
if(mode == PAI_TOGGLE_SECURITY_HUD)
|
||||
hud = GLOB.huds[sec_hud]
|
||||
secHUD = !secHUD
|
||||
hud_on = secHUD
|
||||
if(hud_on)
|
||||
hud.show_to(src)
|
||||
else
|
||||
hud.hide_from(src)
|
||||
return TRUE
|
||||
Reference in New Issue
Block a user