mirror of
https://github.com/Bubberstation/Bubberstation.git
synced 2026-07-16 10:35:41 +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:
@@ -2320,7 +2320,7 @@
|
||||
"vV" = (
|
||||
/obj/structure/table,
|
||||
/obj/effect/decal/cleanable/dirt,
|
||||
/obj/item/paicard,
|
||||
/obj/item/pai_card,
|
||||
/turf/open/floor/iron/airless,
|
||||
/area/ruin/space/ks13/science/rnd)
|
||||
"vY" = (
|
||||
|
||||
@@ -157,7 +157,7 @@
|
||||
/area/ruin/unpowered/no_grav)
|
||||
"iE" = (
|
||||
/obj/structure/table,
|
||||
/obj/item/paicard,
|
||||
/obj/item/pai_card,
|
||||
/turf/open/misc/asteroid/airless,
|
||||
/area/ruin/unpowered/no_grav)
|
||||
"jH" = (
|
||||
|
||||
@@ -3109,7 +3109,7 @@
|
||||
/area/awaymission/moonoutpost19/research)
|
||||
"ic" = (
|
||||
/obj/structure/rack,
|
||||
/obj/item/paicard{
|
||||
/obj/item/pai_card{
|
||||
pixel_x = 4
|
||||
},
|
||||
/obj/effect/turf_decal/stripes/line{
|
||||
|
||||
@@ -6241,7 +6241,7 @@
|
||||
/obj/item/taperecorder{
|
||||
pixel_x = -3
|
||||
},
|
||||
/obj/item/paicard{
|
||||
/obj/item/pai_card{
|
||||
pixel_x = 4
|
||||
},
|
||||
/turf/open/floor/iron/cafeteria{
|
||||
|
||||
@@ -17766,7 +17766,7 @@
|
||||
/area/station/hallway/primary/aft)
|
||||
"eTM" = (
|
||||
/obj/structure/table/wood,
|
||||
/obj/item/paicard,
|
||||
/obj/item/pai_card,
|
||||
/obj/structure/window/reinforced{
|
||||
dir = 8
|
||||
},
|
||||
@@ -20373,7 +20373,7 @@
|
||||
"fAt" = (
|
||||
/obj/structure/table/wood,
|
||||
/obj/item/flashlight/lamp,
|
||||
/obj/item/paicard{
|
||||
/obj/item/pai_card{
|
||||
pixel_x = -6
|
||||
},
|
||||
/turf/open/floor/carpet/red,
|
||||
@@ -21920,7 +21920,7 @@
|
||||
/area/station/medical/medbay/central)
|
||||
"fYX" = (
|
||||
/obj/structure/table/wood,
|
||||
/obj/item/paicard,
|
||||
/obj/item/pai_card,
|
||||
/turf/open/floor/iron/grimy,
|
||||
/area/station/command/corporate_showroom)
|
||||
"fYZ" = (
|
||||
@@ -36081,7 +36081,7 @@
|
||||
/area/station/medical/treatment_center)
|
||||
"jNN" = (
|
||||
/obj/structure/table,
|
||||
/obj/item/paicard,
|
||||
/obj/item/pai_card,
|
||||
/obj/structure/cable,
|
||||
/obj/effect/turf_decal/tile/neutral/fourcorners,
|
||||
/turf/open/floor/iron,
|
||||
@@ -38835,7 +38835,7 @@
|
||||
/area/station/medical/surgery/theatre)
|
||||
"kyN" = (
|
||||
/obj/structure/table,
|
||||
/obj/item/paicard,
|
||||
/obj/item/pai_card,
|
||||
/obj/machinery/atmospherics/pipe/smart/manifold4w/supply/hidden/layer4,
|
||||
/obj/machinery/atmospherics/pipe/smart/manifold4w/scrubbers/hidden/layer2,
|
||||
/turf/open/floor/iron,
|
||||
@@ -40886,7 +40886,7 @@
|
||||
/obj/item/pen,
|
||||
/obj/machinery/light/directional/south,
|
||||
/obj/machinery/status_display/evac/directional/south,
|
||||
/obj/item/paicard,
|
||||
/obj/item/pai_card,
|
||||
/turf/open/floor/wood,
|
||||
/area/station/commons/dorms)
|
||||
"lbt" = (
|
||||
@@ -52245,7 +52245,7 @@
|
||||
/area/station/engineering/lobby)
|
||||
"ocB" = (
|
||||
/obj/structure/table/wood,
|
||||
/obj/item/paicard,
|
||||
/obj/item/pai_card,
|
||||
/turf/open/floor/iron/grimy,
|
||||
/area/station/service/library)
|
||||
"ocO" = (
|
||||
@@ -71389,7 +71389,7 @@
|
||||
"toL" = (
|
||||
/obj/structure/table,
|
||||
/obj/item/folder/white,
|
||||
/obj/item/paicard,
|
||||
/obj/item/pai_card,
|
||||
/obj/effect/turf_decal/tile/purple{
|
||||
dir = 1
|
||||
},
|
||||
@@ -74006,7 +74006,7 @@
|
||||
/obj/structure/table/reinforced,
|
||||
/obj/item/wrench,
|
||||
/obj/item/crowbar,
|
||||
/obj/item/paicard,
|
||||
/obj/item/pai_card,
|
||||
/obj/effect/turf_decal/bot,
|
||||
/obj/machinery/newscaster/directional/south,
|
||||
/turf/open/floor/iron,
|
||||
@@ -78070,7 +78070,7 @@
|
||||
/area/station/maintenance/department/electrical)
|
||||
"uZu" = (
|
||||
/obj/structure/table/wood,
|
||||
/obj/item/paicard,
|
||||
/obj/item/pai_card,
|
||||
/turf/open/floor/wood,
|
||||
/area/station/command/meeting_room/council)
|
||||
"uZL" = (
|
||||
@@ -80549,7 +80549,7 @@
|
||||
"vGu" = (
|
||||
/obj/structure/table/wood,
|
||||
/obj/effect/decal/cleanable/dirt,
|
||||
/obj/item/paicard,
|
||||
/obj/item/pai_card,
|
||||
/turf/open/floor/carpet,
|
||||
/area/station/service/library/abandoned)
|
||||
"vGG" = (
|
||||
@@ -81798,7 +81798,7 @@
|
||||
"vXA" = (
|
||||
/obj/structure/table/reinforced,
|
||||
/obj/item/folder/white,
|
||||
/obj/item/paicard,
|
||||
/obj/item/pai_card,
|
||||
/obj/effect/turf_decal/bot,
|
||||
/obj/machinery/airalarm/directional/west,
|
||||
/obj/structure/extinguisher_cabinet/directional/north,
|
||||
@@ -82451,7 +82451,7 @@
|
||||
/obj/structure/disposalpipe/segment,
|
||||
/obj/item/healthanalyzer,
|
||||
/obj/item/healthanalyzer,
|
||||
/obj/item/paicard,
|
||||
/obj/item/pai_card,
|
||||
/obj/effect/turf_decal/bot,
|
||||
/turf/open/floor/iron,
|
||||
/area/station/science/robotics/lab)
|
||||
|
||||
@@ -9191,7 +9191,7 @@
|
||||
/area/station/commons/locker)
|
||||
"cIK" = (
|
||||
/obj/structure/table/wood,
|
||||
/obj/item/paicard,
|
||||
/obj/item/pai_card,
|
||||
/obj/effect/mapping_helpers/broken_floor,
|
||||
/turf/open/floor/wood,
|
||||
/area/station/maintenance/fore)
|
||||
@@ -39346,7 +39346,7 @@
|
||||
/obj/machinery/power/apc/auto_name/directional/north,
|
||||
/obj/structure/cable,
|
||||
/obj/structure/table,
|
||||
/obj/item/paicard,
|
||||
/obj/item/pai_card,
|
||||
/obj/item/taperecorder{
|
||||
pixel_x = -3;
|
||||
pixel_y = 2
|
||||
@@ -53655,7 +53655,7 @@
|
||||
/obj/effect/turf_decal/tile/neutral/fourcorners,
|
||||
/obj/structure/rack,
|
||||
/obj/item/storage/briefcase,
|
||||
/obj/item/paicard,
|
||||
/obj/item/pai_card,
|
||||
/turf/open/floor/iron,
|
||||
/area/station/commons/locker)
|
||||
"qyf" = (
|
||||
@@ -62073,7 +62073,7 @@
|
||||
/area/station/science/robotics/lab)
|
||||
"tfO" = (
|
||||
/obj/structure/table/wood,
|
||||
/obj/item/paicard,
|
||||
/obj/item/pai_card,
|
||||
/turf/open/floor/iron,
|
||||
/area/station/commons/dorms)
|
||||
"tfR" = (
|
||||
@@ -72794,7 +72794,7 @@
|
||||
/obj/effect/turf_decal/siding/wood{
|
||||
dir = 9
|
||||
},
|
||||
/obj/item/paicard,
|
||||
/obj/item/pai_card,
|
||||
/obj/structure/table/wood,
|
||||
/obj/effect/turf_decal/tile/bar/opposingcorners,
|
||||
/turf/open/floor/iron,
|
||||
|
||||
@@ -44785,7 +44785,7 @@
|
||||
/obj/effect/turf_decal/tile/neutral{
|
||||
dir = 4
|
||||
},
|
||||
/obj/item/paicard,
|
||||
/obj/item/pai_card,
|
||||
/obj/effect/decal/cleanable/dirt,
|
||||
/turf/open/floor/iron/dark,
|
||||
/area/station/service/bar/atrium)
|
||||
@@ -66152,7 +66152,7 @@
|
||||
/obj/item/folder{
|
||||
pixel_x = -4
|
||||
},
|
||||
/obj/item/paicard,
|
||||
/obj/item/pai_card,
|
||||
/obj/machinery/light/directional/north,
|
||||
/obj/effect/turf_decal/stripes/corner{
|
||||
dir = 8
|
||||
@@ -73907,7 +73907,7 @@
|
||||
/obj/item/hand_labeler,
|
||||
/obj/machinery/airalarm/directional/west,
|
||||
/obj/machinery/light/directional/west,
|
||||
/obj/item/paicard{
|
||||
/obj/item/pai_card{
|
||||
pixel_x = 6
|
||||
},
|
||||
/obj/item/aicard,
|
||||
@@ -76681,7 +76681,7 @@
|
||||
/area/station/hallway/primary/central)
|
||||
"vMN" = (
|
||||
/obj/structure/table/wood,
|
||||
/obj/item/paicard,
|
||||
/obj/item/pai_card,
|
||||
/turf/open/floor/wood/tile,
|
||||
/area/station/service/library)
|
||||
"vMW" = (
|
||||
|
||||
@@ -7368,7 +7368,7 @@
|
||||
/area/station/commons/lounge)
|
||||
"cNC" = (
|
||||
/obj/structure/table/wood,
|
||||
/obj/item/paicard,
|
||||
/obj/item/pai_card,
|
||||
/turf/open/floor/wood,
|
||||
/area/station/service/library)
|
||||
"cNS" = (
|
||||
@@ -10504,7 +10504,7 @@
|
||||
pixel_x = -2;
|
||||
pixel_y = 6
|
||||
},
|
||||
/obj/item/paicard,
|
||||
/obj/item/pai_card,
|
||||
/obj/effect/turf_decal/tile/purple/anticorner/contrasted,
|
||||
/turf/open/floor/iron/white,
|
||||
/area/station/science/lobby)
|
||||
@@ -14503,7 +14503,7 @@
|
||||
dir = 1
|
||||
},
|
||||
/obj/item/aicard,
|
||||
/obj/item/paicard,
|
||||
/obj/item/pai_card,
|
||||
/obj/item/circuitboard/aicore,
|
||||
/obj/machinery/keycard_auth/directional/north{
|
||||
pixel_x = -5
|
||||
@@ -25113,7 +25113,7 @@
|
||||
pixel_x = 8;
|
||||
pixel_y = 32
|
||||
},
|
||||
/obj/item/paicard{
|
||||
/obj/item/pai_card{
|
||||
desc = "A real Nanotrasen success, these personal AIs provide all of the companionship of an AI without any law related red-tape.";
|
||||
name = "\improper Nanotrasen-brand personal AI device exhibit"
|
||||
},
|
||||
@@ -49716,7 +49716,7 @@
|
||||
/area/station/engineering/supermatter)
|
||||
"rHp" = (
|
||||
/obj/structure/table,
|
||||
/obj/item/paicard,
|
||||
/obj/item/pai_card,
|
||||
/turf/open/floor/iron,
|
||||
/area/station/commons/locker)
|
||||
"rHr" = (
|
||||
|
||||
@@ -2333,7 +2333,7 @@
|
||||
/area/centcom/syndicate_mothership/control)
|
||||
"gM" = (
|
||||
/obj/structure/table/wood,
|
||||
/obj/item/paicard,
|
||||
/obj/item/pai_card,
|
||||
/turf/open/floor/wood/tile,
|
||||
/area/centcom/syndicate_mothership/control)
|
||||
"gO" = (
|
||||
|
||||
@@ -6176,7 +6176,7 @@
|
||||
/area/station/command/heads_quarters/hos)
|
||||
"ckt" = (
|
||||
/obj/structure/table/wood,
|
||||
/obj/item/paicard,
|
||||
/obj/item/pai_card,
|
||||
/obj/item/radio/intercom/directional/west,
|
||||
/turf/open/floor/wood/large,
|
||||
/area/station/service/library)
|
||||
@@ -50834,7 +50834,7 @@
|
||||
/area/station/engineering/supermatter/room)
|
||||
"rEZ" = (
|
||||
/obj/structure/table,
|
||||
/obj/item/paicard,
|
||||
/obj/item/pai_card,
|
||||
/turf/open/floor/iron,
|
||||
/area/station/commons/fitness/recreation)
|
||||
"rFv" = (
|
||||
@@ -52220,7 +52220,7 @@
|
||||
/area/station/cargo/miningdock)
|
||||
"sbb" = (
|
||||
/obj/structure/table/wood,
|
||||
/obj/item/paicard,
|
||||
/obj/item/pai_card,
|
||||
/turf/open/floor/carpet,
|
||||
/area/station/hallway/secondary/entry)
|
||||
"sbh" = (
|
||||
@@ -54524,7 +54524,7 @@
|
||||
/obj/effect/turf_decal/trimline/purple/filled/line{
|
||||
dir = 8
|
||||
},
|
||||
/obj/item/paicard,
|
||||
/obj/item/pai_card,
|
||||
/turf/open/floor/iron/white,
|
||||
/area/station/science/lobby)
|
||||
"sUd" = (
|
||||
@@ -64599,7 +64599,7 @@
|
||||
/obj/effect/turf_decal/stripes/line{
|
||||
dir = 4
|
||||
},
|
||||
/obj/item/paicard,
|
||||
/obj/item/pai_card,
|
||||
/obj/item/taperecorder{
|
||||
pixel_x = -3;
|
||||
pixel_y = 2
|
||||
|
||||
@@ -845,7 +845,7 @@
|
||||
/obj/item/food/monkeycube{
|
||||
pixel_x = 5
|
||||
},
|
||||
/obj/item/paicard{
|
||||
/obj/item/pai_card{
|
||||
pixel_x = -6;
|
||||
pixel_y = 1
|
||||
},
|
||||
|
||||
@@ -55,6 +55,7 @@
|
||||
#define COLOR_VERY_DARK_LIME_GREEN "#003300"
|
||||
#define COLOR_GREEN "#008000"
|
||||
#define COLOR_DARK_MODERATE_LIME_GREEN "#44964A"
|
||||
#define COLOR_PAI_GREEN "#00FF88"
|
||||
|
||||
#define COLOR_CYAN "#00FFFF"
|
||||
#define COLOR_DARK_CYAN "#00A2FF"
|
||||
|
||||
@@ -177,12 +177,12 @@
|
||||
#define ui_pai_chassis "SOUTH:6,WEST+2"
|
||||
#define ui_pai_rest "SOUTH:6,WEST+3"
|
||||
#define ui_pai_light "SOUTH:6,WEST+4"
|
||||
#define ui_pai_newscaster "SOUTH:6,WEST+5"
|
||||
#define ui_pai_host_monitor "SOUTH:6,WEST+6"
|
||||
#define ui_pai_crew_manifest "SOUTH:6,WEST+7"
|
||||
#define ui_pai_state_laws "SOUTH:6,WEST+8"
|
||||
#define ui_pai_state_laws "SOUTH:6,WEST+5"
|
||||
#define ui_pai_crew_manifest "SOUTH:6,WEST+6"
|
||||
#define ui_pai_host_monitor "SOUTH:6,WEST+7"
|
||||
#define ui_pai_internal_gps "SOUTH:6,WEST+8"
|
||||
#define ui_pai_mod_int "SOUTH:6,WEST+9"
|
||||
#define ui_pai_internal_gps "SOUTH:6,WEST+10"
|
||||
#define ui_pai_newscaster "SOUTH:6,WEST+10"
|
||||
#define ui_pai_take_picture "SOUTH:6,WEST+11"
|
||||
#define ui_pai_view_images "SOUTH:6,WEST+12"
|
||||
#define ui_pai_radio "SOUTH:6,WEST+13"
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
/// Time between fold out
|
||||
#define HOLOCHASSIS_COOLDOWN (5 SECONDS)
|
||||
/// Time it takes a new pAI to fold out
|
||||
#define HOLOCHASSIS_INIT_TIME (40 SECONDS)
|
||||
/// Overloaded or emagged foldout cooldown
|
||||
#define HOLOCHASSIS_OVERLOAD_COOLDOWN (10 SECONDS)
|
||||
/// Regeneration rate for the holochassis
|
||||
#define HOLOCHASSIS_REGEN_PER_SECOND 1.25
|
||||
/// The max health of the holochassis
|
||||
#define HOLOCHASSIS_MAX_HEALTH 20
|
||||
/// The amount of time between spamming for pAI candidates
|
||||
#define PAI_SPAM_TIME (40 SECONDS)
|
||||
|
||||
/// UI action to toggle huds
|
||||
#define PAI_TOGGLE_MEDICAL_HUD 0
|
||||
#define PAI_TOGGLE_SECURITY_HUD 1
|
||||
|
||||
/// UI action to use integrated scanner on target
|
||||
#define PAI_SCAN_TARGET 0
|
||||
#define PAI_SCAN_MASTER 1
|
||||
|
||||
/// UI actions for door jack
|
||||
#define PAI_DOOR_JACK_CABLE 0
|
||||
#define PAI_DOOR_JACK_HACK 1
|
||||
#define PAI_DOOR_JACK_CANCEL 2
|
||||
|
||||
/// UI actions for photography module
|
||||
#define PAI_PHOTO_MODE_CAMERA 0
|
||||
#define PAI_PHOTO_MODE_PRINTER 1
|
||||
#define PAI_PHOTO_MODE_ZOOM 2
|
||||
|
||||
@@ -213,8 +213,7 @@
|
||||
if (!question)
|
||||
question = "Would you like to be a special role?"
|
||||
var/list/result = list()
|
||||
for(var/candidate in group)
|
||||
var/mob/candidate_mob = candidate
|
||||
for(var/mob/candidate_mob as anything in group)
|
||||
if(!candidate_mob.key || !candidate_mob.client || (ignore_category && GLOB.poll_ignore[ignore_category] && (candidate_mob.ckey in GLOB.poll_ignore[ignore_category])))
|
||||
continue
|
||||
if(be_special_flag)
|
||||
|
||||
+1
-1
@@ -82,7 +82,7 @@
|
||||
return
|
||||
|
||||
if(aicamera.in_camera_mode)
|
||||
aicamera.camera_mode_off()
|
||||
aicamera.toggle_camera_mode(sound = FALSE)
|
||||
aicamera.captureimage(pixel_turf, usr)
|
||||
return
|
||||
if(waypoint_mode)
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
face_atom(A) // change direction to face what you clicked on
|
||||
|
||||
if(aicamera.in_camera_mode) //Cyborg picture taking
|
||||
aicamera.camera_mode_off()
|
||||
aicamera.toggle_camera_mode(sound = FALSE)
|
||||
aicamera.captureimage(A, usr)
|
||||
return
|
||||
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
/mob/living/silicon/pai/ClickOn(atom/A, params)
|
||||
..()
|
||||
if(aicamera.in_camera_mode) //pAI picture taking
|
||||
aicamera.camera_mode_off()
|
||||
aicamera.captureimage(A, usr, aicamera.picture_size_x - 1, aicamera.picture_size_y - 1)
|
||||
return
|
||||
@@ -1,70 +1,13 @@
|
||||
SUBSYSTEM_DEF(pai)
|
||||
name = "pAI"
|
||||
|
||||
flags = SS_NO_INIT|SS_NO_FIRE
|
||||
|
||||
/// List of pAI candidates, including those not submitted.
|
||||
var/list/candidates = list()
|
||||
/// Prevents a crew member from hitting "request pAI"
|
||||
var/request_spam = FALSE
|
||||
/// Prevents a pAI from submitting itself repeatedly and sounding an alert.
|
||||
var/submit_spam = FALSE
|
||||
/// All pAI cards on the map.
|
||||
var/list/pai_card_list = list()
|
||||
|
||||
/// Created when a user clicks the "pAI candidate" window
|
||||
/datum/pai_candidate
|
||||
/// User inputted OOC comments
|
||||
var/comments
|
||||
/// User inputted behavior description
|
||||
var/description
|
||||
/// User's ckey - not input
|
||||
var/key
|
||||
/// User's pAI name. If blank, ninja name.
|
||||
var/name
|
||||
/// If the user has hit "submit"
|
||||
var/ready = FALSE
|
||||
|
||||
/**
|
||||
* Pings ghosts to announce that someone is requesting a pAI
|
||||
*
|
||||
* Arguments
|
||||
* @pai - The card requesting assistance
|
||||
* @user - The player requesting a pAI
|
||||
*/
|
||||
/datum/controller/subsystem/pai/proc/findPAI(obj/item/paicard/pai, mob/user)
|
||||
if(!(GLOB.ghost_role_flags & GHOSTROLE_SILICONS))
|
||||
to_chat(user, span_warning("Due to growing incidents of SELF corrupted independent artificial intelligences, freeform personality devices have been temporarily banned in this sector."))
|
||||
return
|
||||
if(request_spam)
|
||||
to_chat(user, span_warning("Request sent too recently."))
|
||||
return
|
||||
request_spam = TRUE
|
||||
playsound(src, 'sound/machines/ping.ogg', 20, TRUE)
|
||||
to_chat(user, span_notice("You have requested pAI assistance."))
|
||||
var/mutable_appearance/alert_overlay = mutable_appearance('icons/obj/aicards.dmi', "pai")
|
||||
notify_ghosts("[user] is requesting a pAI personality! Use the pAI button to submit yourself as one.", source=user, alert_overlay = alert_overlay, action=NOTIFY_ORBIT, header="pAI Request!", ignore_key = POLL_IGNORE_PAI)
|
||||
addtimer(CALLBACK(src, .proc/request_again), 10 SECONDS)
|
||||
return TRUE
|
||||
|
||||
/**
|
||||
* This is the primary window proc when the pAI candidate
|
||||
* hud menu is pressed by observers.
|
||||
*
|
||||
* Arguments
|
||||
* @user - The ghost doing the pressing.
|
||||
*/
|
||||
/datum/controller/subsystem/pai/proc/recruitWindow(mob/user)
|
||||
/// Searches for a previous candidate upon opening the menu
|
||||
var/datum/pai_candidate/candidate = check_candidate(user)
|
||||
if(isnull(candidate))
|
||||
candidate = new /datum/pai_candidate()
|
||||
candidate.key = user.key
|
||||
candidates.Add(candidate)
|
||||
ui_interact(user)
|
||||
|
||||
/datum/controller/subsystem/pai/ui_state(mob/user)
|
||||
return GLOB.observer_state
|
||||
/// Prevents a pAI from submitting itself repeatedly and sounding an alert.
|
||||
var/submit_spam = FALSE
|
||||
|
||||
/datum/controller/subsystem/pai/ui_interact(mob/user, datum/tgui/ui)
|
||||
. = ..()
|
||||
@@ -72,12 +15,15 @@ SUBSYSTEM_DEF(pai)
|
||||
if(!ui)
|
||||
ui = new(user, src, "PaiSubmit")
|
||||
ui.open()
|
||||
ui.set_autoupdate(FALSE)
|
||||
|
||||
/datum/controller/subsystem/pai/ui_state(mob/user)
|
||||
return GLOB.observer_state
|
||||
|
||||
/datum/controller/subsystem/pai/ui_static_data(mob/user)
|
||||
. = ..()
|
||||
var/list/data = list()
|
||||
/// The matching candidate from search
|
||||
var/datum/pai_candidate/candidate = check_candidate(user)
|
||||
var/datum/pai_candidate/candidate = candidates[user.ckey]
|
||||
if(isnull(candidate))
|
||||
return data
|
||||
data["comments"] = candidate.comments
|
||||
@@ -88,49 +34,52 @@ SUBSYSTEM_DEF(pai)
|
||||
/datum/controller/subsystem/pai/ui_act(action, list/params, datum/tgui/ui)
|
||||
. = ..()
|
||||
if(.)
|
||||
return
|
||||
/// The matching candidate from search
|
||||
var/datum/pai_candidate/candidate = check_candidate(usr)
|
||||
return TRUE
|
||||
var/datum/pai_candidate/candidate = candidates[usr.ckey]
|
||||
if(is_banned_from(usr.ckey, ROLE_PAI))
|
||||
to_chat(usr, span_warning("You are banned from playing pAI!"))
|
||||
ui.close()
|
||||
return FALSE
|
||||
if(isnull(candidate))
|
||||
to_chat(usr, span_warning("There was an error. Please resubmit."))
|
||||
ui.close()
|
||||
return FALSE
|
||||
switch(action)
|
||||
if("submit")
|
||||
candidate.comments = params["candidate"]["comments"]
|
||||
candidate.description = params["candidate"]["description"]
|
||||
candidate.name = params["candidate"]["name"]
|
||||
candidate.comments = trim(params["comments"], MAX_BROADCAST_LEN)
|
||||
candidate.description = trim(params["description"], MAX_BROADCAST_LEN)
|
||||
candidate.name = trim(params["name"], MAX_NAME_LEN)
|
||||
candidate.ckey = usr.ckey
|
||||
candidate.ready = TRUE
|
||||
ui.close()
|
||||
submit_alert()
|
||||
return TRUE
|
||||
if("save")
|
||||
candidate.comments = params["candidate"]["comments"]
|
||||
candidate.description = params["candidate"]["description"]
|
||||
candidate.name = params["candidate"]["name"]
|
||||
candidate.comments = params["comments"]
|
||||
candidate.description = params["description"]
|
||||
candidate.name = params["name"]
|
||||
candidate.savefile_save(usr)
|
||||
return TRUE
|
||||
if("load")
|
||||
candidate.savefile_load(usr)
|
||||
//In case people have saved unsanitized stuff.
|
||||
if(candidate.comments)
|
||||
candidate.comments = copytext_char(candidate.comments,1,MAX_MESSAGE_LEN)
|
||||
if(candidate.description)
|
||||
candidate.description = copytext_char(candidate.description,1,MAX_MESSAGE_LEN)
|
||||
if(candidate.name)
|
||||
candidate.name = copytext_char(candidate.name,1,MAX_NAME_LEN)
|
||||
ui.send_full_update()
|
||||
return
|
||||
return TRUE
|
||||
return FALSE
|
||||
|
||||
/**
|
||||
* Finds the candidate in question from the list of candidates.
|
||||
* This is the primary window proc when the pAI candidate
|
||||
* hud menu is pressed by observers.
|
||||
*
|
||||
* @params {mob} user The ghost doing the pressing.
|
||||
*/
|
||||
/datum/controller/subsystem/pai/proc/check_candidate(mob/user)
|
||||
/// Finds a matching candidate.
|
||||
var/datum/pai_candidate/candidate
|
||||
for(var/datum/pai_candidate/checked_candidate as anything in candidates)
|
||||
if(checked_candidate.key == user.key)
|
||||
candidate = checked_candidate
|
||||
return candidate
|
||||
return null
|
||||
/datum/controller/subsystem/pai/proc/recruit_window(mob/user)
|
||||
/// Searches for a previous candidate upon opening the menu
|
||||
var/datum/pai_candidate/candidate = candidates[user.ckey]
|
||||
if(isnull(candidate))
|
||||
candidate = new(user.ckey)
|
||||
candidates[user.ckey] = candidate
|
||||
ui_interact(user)
|
||||
|
||||
|
||||
/**
|
||||
* Pings all pAI cards on the station that new candidates are available.
|
||||
@@ -140,28 +89,9 @@ SUBSYSTEM_DEF(pai)
|
||||
to_chat(usr, span_warning("Your candidacy has been submitted, but pAI cards have been alerted too recently."))
|
||||
return FALSE
|
||||
submit_spam = TRUE
|
||||
for(var/obj/item/paicard/paicard in pai_card_list)
|
||||
if(!paicard.pai)
|
||||
paicard.alertUpdate()
|
||||
for(var/obj/item/pai_card/pai_card as anything in pai_card_list)
|
||||
if(!pai_card.pai)
|
||||
pai_card.alert_update()
|
||||
to_chat(usr, span_notice("Your pAI candidacy has been submitted!"))
|
||||
addtimer(CALLBACK(src, .proc/submit_again), 10 SECONDS)
|
||||
addtimer(VARSET_CALLBACK(src, submit_spam, FALSE), PAI_SPAM_TIME, TIMER_UNIQUE | TIMER_STOPPABLE | TIMER_CLIENT_TIME | TIMER_DELETE_ME)
|
||||
return TRUE
|
||||
|
||||
/datum/controller/subsystem/pai/proc/request_again()
|
||||
request_spam = FALSE
|
||||
|
||||
/datum/controller/subsystem/pai/proc/submit_again()
|
||||
submit_spam = FALSE
|
||||
|
||||
/**
|
||||
* Checks if a candidate is ready so that they may be displayed in the pAI
|
||||
* card's candidate window
|
||||
*/
|
||||
/datum/controller/subsystem/pai/proc/check_ready(datum/pai_candidate/candidate)
|
||||
if(!candidate.ready)
|
||||
return FALSE
|
||||
for(var/mob/dead/observer/observer in GLOB.player_list)
|
||||
if(observer.key == candidate.key)
|
||||
return candidate
|
||||
return FALSE
|
||||
|
||||
|
||||
@@ -315,51 +315,6 @@ GLOBAL_DATUM_INIT(data_core, /datum/datacore, new)
|
||||
locked += L
|
||||
return
|
||||
|
||||
/**
|
||||
* Supporing proc for getting general records
|
||||
* and using them as pAI ui data. This gets
|
||||
* medical information - or what I would deem
|
||||
* medical information - and sends it as a list.
|
||||
*
|
||||
* @return - list(general_records_out)
|
||||
*/
|
||||
/datum/datacore/proc/get_general_records()
|
||||
if(!GLOB.data_core.general)
|
||||
return list()
|
||||
/// The array of records
|
||||
var/list/general_records_out = list()
|
||||
for(var/datum/data/record/gen_record as anything in GLOB.data_core.general)
|
||||
/// The object containing the crew info
|
||||
var/list/crew_record = list()
|
||||
crew_record["ref"] = REF(gen_record)
|
||||
crew_record["name"] = gen_record.fields["name"]
|
||||
crew_record["physical_health"] = gen_record.fields["p_stat"]
|
||||
crew_record["mental_health"] = gen_record.fields["m_stat"]
|
||||
general_records_out += list(crew_record)
|
||||
return general_records_out
|
||||
|
||||
/**
|
||||
* Supporing proc for getting secrurity records
|
||||
* and using them as pAI ui data. Sends it as a
|
||||
* list.
|
||||
*
|
||||
* @return - list(security_records_out)
|
||||
*/
|
||||
/datum/datacore/proc/get_security_records()
|
||||
if(!GLOB.data_core.security)
|
||||
return list()
|
||||
/// The array of records
|
||||
var/list/security_records_out = list()
|
||||
for(var/datum/data/record/sec_record as anything in GLOB.data_core.security)
|
||||
/// The object containing the crew info
|
||||
var/list/crew_record = list()
|
||||
crew_record["ref"] = REF(sec_record)
|
||||
crew_record["name"] = sec_record.fields["name"]
|
||||
crew_record["status"] = sec_record.fields["criminal"] // wanted status
|
||||
crew_record["crimes"] = length(sec_record.fields["crim"])
|
||||
security_records_out += list(crew_record)
|
||||
return security_records_out
|
||||
|
||||
/datum/datacore/proc/get_id_photo(mob/living/carbon/human/human, client/client, show_directions = list(SOUTH))
|
||||
var/datum/job/humans_job = human.mind.assigned_role
|
||||
var/datum/preferences/humans_prefs
|
||||
|
||||
@@ -1,231 +0,0 @@
|
||||
/obj/item/paicard
|
||||
name = "personal AI device"
|
||||
icon = 'icons/obj/aicards.dmi'
|
||||
icon_state = "pai"
|
||||
inhand_icon_state = "electronic"
|
||||
worn_icon_state = "electronic"
|
||||
lefthand_file = 'icons/mob/inhands/misc/devices_lefthand.dmi'
|
||||
righthand_file = 'icons/mob/inhands/misc/devices_righthand.dmi'
|
||||
w_class = WEIGHT_CLASS_SMALL
|
||||
slot_flags = ITEM_SLOT_BELT
|
||||
custom_premium_price = PAYCHECK_COMMAND * 1.25
|
||||
resistance_flags = FIRE_PROOF | ACID_PROOF | INDESTRUCTIBLE
|
||||
|
||||
/// Spam alert prevention
|
||||
var/alert_cooldown
|
||||
/// If the pAIcard is slotted in a PDA
|
||||
var/slotted = FALSE
|
||||
/// Any pAI personalities inserted
|
||||
var/mob/living/silicon/pai/pai
|
||||
///what emotion icon we have. handled in /mob/living/silicon/pai/Topic()
|
||||
var/emotion_icon = "off"
|
||||
|
||||
/obj/item/paicard/suicide_act(mob/living/user)
|
||||
user.visible_message(span_suicide("[user] is staring sadly at [src]! [user.p_they()] can't keep living without real human intimacy!"))
|
||||
return OXYLOSS
|
||||
|
||||
/obj/item/paicard/Initialize(mapload)
|
||||
SSpai.pai_card_list += src
|
||||
. = ..()
|
||||
update_appearance()
|
||||
|
||||
/obj/item/paicard/vv_edit_var(vname, vval)
|
||||
. = ..()
|
||||
if(vname == NAMEOF(src, emotion_icon))
|
||||
update_appearance()
|
||||
|
||||
/obj/item/paicard/handle_atom_del(atom/A)
|
||||
if(A == pai) //double check /mob/living/silicon/pai/Destroy() if you change these.
|
||||
pai = null
|
||||
emotion_icon = initial(emotion_icon)
|
||||
update_appearance()
|
||||
return ..()
|
||||
|
||||
/obj/item/paicard/update_overlays()
|
||||
. = ..()
|
||||
. += "pai-[emotion_icon]"
|
||||
if(pai?.hacking_cable)
|
||||
. += "[initial(icon_state)]-connector"
|
||||
|
||||
/obj/item/paicard/Destroy()
|
||||
//Will stop people throwing friend pAIs into the singularity so they can respawn
|
||||
SSpai.pai_card_list -= src
|
||||
if(!QDELETED(pai))
|
||||
QDEL_NULL(pai)
|
||||
return ..()
|
||||
|
||||
/obj/item/paicard/attack_self(mob/user)
|
||||
if (!in_range(src, user))
|
||||
return
|
||||
user.set_machine(src)
|
||||
ui_interact(user)
|
||||
|
||||
/obj/item/paicard/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/paicard/ui_state(mob/user)
|
||||
return GLOB.paicard_state
|
||||
|
||||
/obj/item/paicard/ui_data(mob/user)
|
||||
. = ..()
|
||||
var/list/data = list()
|
||||
data["candidates"] = list()
|
||||
if(!pai)
|
||||
data["candidates"] = pool_candidates()
|
||||
data["pai"] = null
|
||||
return data
|
||||
data["pai"] = list()
|
||||
data["pai"]["can_holo"] = pai.canholo
|
||||
data["pai"]["dna"] = pai.master_dna
|
||||
data["pai"]["emagged"] = pai.emagged
|
||||
data["pai"]["laws"] = pai.laws.supplied
|
||||
data["pai"]["master"] = pai.master
|
||||
data["pai"]["name"] = pai.name
|
||||
data["pai"]["transmit"] = pai.can_transmit
|
||||
data["pai"]["receive"] = pai.can_receive
|
||||
return data
|
||||
|
||||
/obj/item/paicard/ui_act(action, list/params)
|
||||
. = ..()
|
||||
if(.)
|
||||
return FALSE
|
||||
switch(action)
|
||||
if("download")
|
||||
/// The individual candidate to download
|
||||
var/datum/pai_candidate/candidate
|
||||
for(var/datum/pai_candidate/checked_candidate as anything in SSpai.candidates)
|
||||
if(params["key"] == checked_candidate.key)
|
||||
candidate = checked_candidate
|
||||
break
|
||||
if(isnull(candidate))
|
||||
return FALSE
|
||||
if(pai)
|
||||
return FALSE
|
||||
if(SSpai.check_ready(candidate) != candidate)
|
||||
return FALSE
|
||||
/// The newly downloaded pAI personality
|
||||
var/mob/living/silicon/pai/new_pai = new(src)
|
||||
new_pai.name = candidate.name || pick(GLOB.ninja_names)
|
||||
new_pai.real_name = new_pai.name
|
||||
new_pai.key = candidate.key
|
||||
setPersonality(new_pai)
|
||||
SSpai.candidates -= candidate
|
||||
if("fix_speech")
|
||||
to_chat(pai, span_notice("Your owner has corrected your speech modulation!"))
|
||||
to_chat(usr, span_notice("You fix the pAI's speech modulator."))
|
||||
for(var/effect in typesof(/datum/status_effect/speech))
|
||||
pai.remove_status_effect(effect)
|
||||
|
||||
if("request")
|
||||
if(!pai)
|
||||
SSpai.findPAI(src, usr)
|
||||
if("set_dna")
|
||||
if(pai.master_dna)
|
||||
return
|
||||
if(!iscarbon(usr))
|
||||
to_chat(usr, span_warning("You don't have any DNA, or your DNA is incompatible with this device!"))
|
||||
else
|
||||
var/mob/living/carbon/master = usr
|
||||
pai.master = master.real_name
|
||||
pai.master_dna = master.dna.unique_enzymes
|
||||
to_chat(pai, span_notice("You have been bound to a new master."))
|
||||
pai.emittersemicd = FALSE
|
||||
if("set_laws")
|
||||
var/newlaws = tgui_input_text(usr, "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", pai.laws.supplied[1], MAX_MESSAGE_LEN, TRUE)
|
||||
if(newlaws && pai)
|
||||
pai.add_supplied_law(0,newlaws)
|
||||
if("toggle_holo")
|
||||
if(pai.canholo)
|
||||
to_chat(pai, span_warning("Your owner has disabled your holomatrix projectors!"))
|
||||
pai.canholo = FALSE
|
||||
to_chat(usr, span_notice("You disable your pAI's holomatrix!"))
|
||||
else
|
||||
to_chat(pai, span_notice("Your owner has enabled your holomatrix projectors!"))
|
||||
pai.canholo = TRUE
|
||||
to_chat(usr, span_notice("You enable your pAI's holomatrix!"))
|
||||
if("toggle_radio")
|
||||
var/transmitting = params["option"] == "transmit" //it can't be both so if we know it's not transmitting it must be receiving.
|
||||
var/transmit_holder = (transmitting ? WIRE_TX : WIRE_RX)
|
||||
if(transmitting)
|
||||
pai.can_transmit = !pai.can_transmit
|
||||
else //receiving
|
||||
pai.can_receive = !pai.can_receive
|
||||
pai.radio.wires.cut(transmit_holder)//wires.cut toggles cut and uncut states
|
||||
transmit_holder = (transmitting ? pai.can_transmit : pai.can_receive) //recycling can be fun!
|
||||
to_chat(usr, span_notice("You [transmit_holder ? "enable" : "disable"] your pAI's [transmitting ? "outgoing" : "incoming"] radio transmissions!"))
|
||||
to_chat(pai, span_notice("Your owner has [transmit_holder ? "enabled" : "disabled"] your [transmitting ? "outgoing" : "incoming"] radio transmissions!"))
|
||||
if("wipe_pai")
|
||||
var/confirm = tgui_alert(usr, "Are you certain you wish to delete the current personality? This action cannot be undone.", "Personality Wipe", list("Yes", "No"))
|
||||
if(confirm != "Yes")
|
||||
return
|
||||
if(!pai)
|
||||
return
|
||||
to_chat(pai, span_warning("You feel yourself slipping away from reality."))
|
||||
to_chat(pai, span_danger("Byte by byte you lose your sense of self."))
|
||||
to_chat(pai, span_userdanger("Your mental faculties leave you."))
|
||||
to_chat(pai, span_rose("oblivion... "))
|
||||
qdel(pai)
|
||||
return
|
||||
|
||||
// WIRE_SIGNAL = 1
|
||||
// WIRE_RECEIVE = 2
|
||||
// WIRE_TRANSMIT = 4
|
||||
|
||||
/obj/item/paicard/proc/setPersonality(mob/living/silicon/pai/personality)
|
||||
pai = personality
|
||||
emotion_icon = "null"
|
||||
update_appearance()
|
||||
|
||||
playsound(loc, 'sound/effects/pai_boot.ogg', 50, TRUE, -1)
|
||||
audible_message("\The [src] plays a cheerful startup noise!")
|
||||
|
||||
/obj/item/paicard/proc/alertUpdate()
|
||||
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)
|
||||
loc.visible_message(span_info("[src] flashes a message across its screen, \"Additional personalities available for download.\""), blind_message = span_notice("[src] vibrates with an alert."))
|
||||
|
||||
/obj/item/paicard/proc/add_alert()
|
||||
add_overlay(
|
||||
list(mutable_appearance(icon, "[initial(icon_state)]-alert"),
|
||||
emissive_appearance(icon, "[initial(icon_state)]-alert", alpha = src.alpha)))
|
||||
|
||||
/obj/item/paicard/proc/remove_alert()
|
||||
cut_overlays()
|
||||
|
||||
/obj/item/paicard/emp_act(severity)
|
||||
. = ..()
|
||||
if (. & EMP_PROTECT_SELF)
|
||||
return
|
||||
if(pai && !pai.holoform)
|
||||
pai.emp_act(severity)
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @return - An array of candidate objects.
|
||||
*/
|
||||
/obj/item/paicard/proc/pool_candidates()
|
||||
/// Array of pAI candidates
|
||||
var/list/candidates = list()
|
||||
if(length(SSpai.candidates))
|
||||
for(var/datum/pai_candidate/checked_candidate as anything in SSpai.candidates)
|
||||
if(!checked_candidate.ready)
|
||||
continue
|
||||
/// The object containing the candidate data.
|
||||
var/list/candidate = list()
|
||||
candidate["comments"] = checked_candidate.comments
|
||||
candidate["description"] = checked_candidate.description
|
||||
candidate["key"] = checked_candidate.key
|
||||
candidate["name"] = checked_candidate.name
|
||||
candidates += list(candidate)
|
||||
return candidates
|
||||
@@ -77,7 +77,7 @@ GLOBAL_LIST_EMPTY(possible_gifts)
|
||||
/obj/item/banhammer,
|
||||
/obj/item/food/grown/ambrosia/deus,
|
||||
/obj/item/food/grown/ambrosia/vulgaris,
|
||||
/obj/item/paicard,
|
||||
/obj/item/pai_card,
|
||||
/obj/item/instrument/violin,
|
||||
/obj/item/instrument/guitar,
|
||||
/obj/item/storage/belt/utility/full,
|
||||
|
||||
@@ -36,39 +36,6 @@
|
||||
log_admin("[key_name(src)] has robotized [M.key].")
|
||||
INVOKE_ASYNC(M, /mob.proc/Robotize)
|
||||
|
||||
/client/proc/makepAI(turf/T 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/C in GLOB.mob_list)
|
||||
if(C.key)
|
||||
available.Add(C)
|
||||
var/mob/choice = tgui_input_list(usr, "Choose a player to play the pAI", "Spawn pAI", sort_names(available))
|
||||
if(isnull(choice))
|
||||
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/paicard/card = new(T)
|
||||
var/mob/living/silicon/pai/pai = new(card)
|
||||
|
||||
var/chosen_name = input(choice, "Enter your pAI name:", "pAI Name", "Personal AI") as text|null
|
||||
|
||||
if (isnull(chosen_name))
|
||||
return
|
||||
|
||||
pai.name = chosen_name
|
||||
pai.real_name = pai.name
|
||||
pai.key = choice.key
|
||||
card.setPersonality(pai)
|
||||
for(var/datum/pai_candidate/candidate in SSpai.candidates)
|
||||
if(candidate.key == choice.key)
|
||||
SSpai.candidates.Remove(candidate)
|
||||
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!
|
||||
|
||||
/client/proc/poll_type_to_del(search_string)
|
||||
var/list/types = get_fancy_list_of_atom_types()
|
||||
if (!isnull(search_string) && search_string != "")
|
||||
|
||||
@@ -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,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,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."))
|
||||
|
||||
@@ -78,7 +78,7 @@ GLOBAL_LIST_EMPTY(TabletMessengers) // a list of all active messengers, similar
|
||||
var/datum/picture/saved_image // the saved image used for messaging purpose like come on dude
|
||||
|
||||
/// Stored pAI in the computer
|
||||
var/obj/item/paicard/inserted_pai = null
|
||||
var/obj/item/pai_card/inserted_pai = null
|
||||
|
||||
/obj/item/modular_computer/Initialize(mapload)
|
||||
. = ..()
|
||||
@@ -661,11 +661,10 @@ GLOBAL_LIST_EMPTY(TabletMessengers) // a list of all active messengers, similar
|
||||
return
|
||||
|
||||
// Insert a PAI.
|
||||
if(istype(attacking_item, /obj/item/paicard) && !inserted_pai)
|
||||
if(istype(attacking_item, /obj/item/pai_card) && !inserted_pai)
|
||||
if(!user.transferItemToLoc(attacking_item, src))
|
||||
return
|
||||
inserted_pai = attacking_item
|
||||
inserted_pai.slotted = TRUE
|
||||
to_chat(user, span_notice("You slot \the [attacking_item] into [src]."))
|
||||
return
|
||||
|
||||
|
||||
@@ -222,7 +222,6 @@
|
||||
if("eject")
|
||||
usr.put_in_hands(inserted_pai)
|
||||
to_chat(usr, span_notice("You remove [inserted_pai] from the [name]."))
|
||||
inserted_pai.slotted = FALSE
|
||||
inserted_pai = null
|
||||
if("interact")
|
||||
inserted_pai.attack_self(usr)
|
||||
|
||||
@@ -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)
|
||||
@@ -44,7 +44,7 @@
|
||||
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)
|
||||
if(user.name != master_name)
|
||||
visible_message(span_danger("[user] stomps on [src]!."))
|
||||
take_holo_damage(2)
|
||||
return
|
||||
@@ -65,8 +65,8 @@
|
||||
return FALSE
|
||||
|
||||
/mob/living/silicon/pai/proc/take_holo_damage(amount)
|
||||
emitterhealth = clamp((emitterhealth - amount), -50, emittermaxhealth)
|
||||
if(emitterhealth < 0)
|
||||
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!"))
|
||||
@@ -85,7 +85,7 @@
|
||||
take_holo_damage(amount * 0.25)
|
||||
|
||||
/mob/living/silicon/pai/getBruteLoss()
|
||||
return emittermaxhealth - emitterhealth
|
||||
return HOLOCHASSIS_MAX_HEALTH - holochassis_health
|
||||
|
||||
/mob/living/silicon/pai/getFireLoss()
|
||||
return emittermaxhealth - emitterhealth
|
||||
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
|
||||
@@ -7,9 +7,9 @@
|
||||
/atom/movable/screen/pai/Click()
|
||||
if(isobserver(usr) || usr.incapacitated())
|
||||
return FALSE
|
||||
var/mob/living/silicon/pai/pAI = usr
|
||||
if(required_software && !pAI.software.Find(required_software))
|
||||
to_chat(pAI, PAI_MISSING_SOFTWARE_MESSAGE)
|
||||
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
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
/atom/movable/screen/pai/newscaster
|
||||
name = "pAI Newscaster"
|
||||
icon_state = "newscaster"
|
||||
required_software = "newscaster"
|
||||
required_software = "Newscaster"
|
||||
|
||||
/atom/movable/screen/pai/newscaster/Click()
|
||||
if(!..())
|
||||
@@ -80,7 +80,7 @@
|
||||
/atom/movable/screen/pai/host_monitor
|
||||
name = "Host Health Scan"
|
||||
icon_state = "host_monitor"
|
||||
required_software = "host scan"
|
||||
required_software = "Host Scan"
|
||||
|
||||
/atom/movable/screen/pai/host_monitor/Click(location, control, params)
|
||||
. = ..()
|
||||
@@ -88,16 +88,12 @@
|
||||
return
|
||||
var/mob/living/silicon/pai/pAI = usr
|
||||
var/list/modifiers = params2list(params)
|
||||
var/mob/living/carbon/holder = get(pAI.card.loc, /mob/living/carbon)
|
||||
if(holder)
|
||||
if (LAZYACCESS(modifiers, RIGHT_CLICK))
|
||||
pAI.hostscan.attack_secondary(holder, pAI)
|
||||
else
|
||||
pAI.hostscan.attack(holder, pAI)
|
||||
else
|
||||
to_chat(usr, span_warning("You are not being carried by anyone!"))
|
||||
return FALSE
|
||||
|
||||
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"
|
||||
@@ -122,6 +118,7 @@
|
||||
/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()
|
||||
@@ -133,7 +130,7 @@
|
||||
/atom/movable/screen/pai/internal_gps
|
||||
name = "Internal GPS"
|
||||
icon_state = "internal_gps"
|
||||
required_software = "internal gps"
|
||||
required_software = "Internal GPS"
|
||||
|
||||
/atom/movable/screen/pai/internal_gps/Click()
|
||||
. = ..()
|
||||
@@ -147,24 +144,24 @@
|
||||
/atom/movable/screen/pai/image_take
|
||||
name = "Take Image"
|
||||
icon_state = "take_picture"
|
||||
required_software = "photography module"
|
||||
required_software = "Photography Module"
|
||||
|
||||
/atom/movable/screen/pai/image_take/Click()
|
||||
if(!..())
|
||||
return
|
||||
var/mob/living/silicon/pai/pAI = usr
|
||||
pAI.aicamera.toggle_camera_mode(usr)
|
||||
pAI.camera.toggle_camera_mode(usr)
|
||||
|
||||
/atom/movable/screen/pai/image_view
|
||||
name = "View Images"
|
||||
icon_state = "view_images"
|
||||
required_software = "photography module"
|
||||
required_software = "Photography Module"
|
||||
|
||||
/atom/movable/screen/pai/image_view/Click()
|
||||
if(!..())
|
||||
return
|
||||
var/mob/living/silicon/pai/pAI = usr
|
||||
pAI.aicamera.viewpictures(usr)
|
||||
pAI.camera.viewpictures(usr)
|
||||
|
||||
/atom/movable/screen/pai/radio
|
||||
name = "radio"
|
||||
@@ -241,9 +238,9 @@
|
||||
using = new /atom/movable/screen/pai/modpc()
|
||||
using.screen_loc = ui_pai_mod_int
|
||||
static_inventory += using
|
||||
mypai.interfaceButton = using
|
||||
var/atom/movable/screen/pai/modpc/tabletbutton = using
|
||||
tabletbutton.pAI = mypai
|
||||
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()
|
||||
@@ -271,6 +268,6 @@
|
||||
var/mob/living/silicon/pai/owner = mymob
|
||||
for(var/atom/movable/screen/pai/button in static_inventory)
|
||||
if(button.required_software)
|
||||
button.color = owner.software.Find(button.required_software) ? null : "#808080"
|
||||
button.color = owner.installed_software.Find(button.required_software) ? null : "#808080"
|
||||
|
||||
#undef PAI_MISSING_SOFTWARE_MESSAGE
|
||||
@@ -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
|
||||
@@ -1,132 +1,12 @@
|
||||
|
||||
/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."))
|
||||
/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 ..()
|
||||
|
||||
if(!canholo && !force)
|
||||
to_chat(src, span_warning("Your master or another force has disabled your holochassis emitters!"))
|
||||
return FALSE
|
||||
/mob/living/silicon/pai/start_pulling(atom/movable/thing, state, force = move_force, supress_message = FALSE)
|
||||
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()
|
||||
. = ..()
|
||||
@@ -137,19 +17,158 @@
|
||||
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!"))
|
||||
/mob/living/silicon/pai/wabbajack()
|
||||
if(length(possible_chassis) < 2)
|
||||
return FALSE
|
||||
return ..()
|
||||
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
|
||||
@@ -8,24 +8,16 @@
|
||||
name = "AI photo camera"
|
||||
flash_enabled = FALSE
|
||||
|
||||
/obj/item/camera/siliconcam/proc/toggle_camera_mode(mob/user)
|
||||
if(in_camera_mode)
|
||||
camera_mode_off(user)
|
||||
else
|
||||
camera_mode_on(user)
|
||||
|
||||
/obj/item/camera/siliconcam/proc/camera_mode_off(mob/user)
|
||||
in_camera_mode = FALSE
|
||||
to_chat(user, "<span class='infoplain'><B>Camera Mode deactivated</B></span>")
|
||||
|
||||
/obj/item/camera/siliconcam/proc/camera_mode_on(mob/user)
|
||||
in_camera_mode = TRUE
|
||||
to_chat(user, "<span class='infoplain'><B>Camera Mode activated</B></span>")
|
||||
/obj/item/camera/siliconcam/proc/toggle_camera_mode(mob/user, sound = TRUE)
|
||||
in_camera_mode = !in_camera_mode
|
||||
if(sound)
|
||||
playsound(src, 'sound/items/wirecutter.ogg', 50, TRUE)
|
||||
to_chat(user, span_notice("Camera mode: [in_camera_mode ? "Activated" : "Deactivated"]."))
|
||||
|
||||
/obj/item/camera/siliconcam/proc/selectpicture(mob/user)
|
||||
var/list/nametemp = list()
|
||||
if(!stored.len)
|
||||
to_chat(usr, "<span class='infoplain'><font color=red><b>No images saved</b></font></span>")
|
||||
if(!length(stored))
|
||||
to_chat(usr, span_warning("No images saved"))
|
||||
return
|
||||
var/list/temp = list()
|
||||
for(var/i in stored)
|
||||
@@ -45,10 +37,10 @@
|
||||
show_picture(user, selection)
|
||||
|
||||
/obj/item/camera/siliconcam/ai_camera/after_picture(mob/user, datum/picture/picture)
|
||||
var/number = stored.len
|
||||
var/number = length(stored)
|
||||
picture.picture_name = "Image [number] (taken by [loc.name])"
|
||||
stored[picture] = TRUE
|
||||
to_chat(usr, "<span class='infoplain'>[span_unconscious("Image recorded")]</span>")
|
||||
to_chat(user, span_notice("Image recorded."))
|
||||
|
||||
/obj/item/camera/siliconcam/robot_camera
|
||||
name = "Cyborg photo camera"
|
||||
@@ -60,12 +52,12 @@
|
||||
var/number = C.connected_ai.aicamera.stored.len
|
||||
picture.picture_name = "Image [number] (taken by [loc.name])"
|
||||
C.connected_ai.aicamera.stored[picture] = TRUE
|
||||
to_chat(usr, "<span class='infoplain'>[span_unconscious("Image recorded and saved to remote database")]</span>")
|
||||
to_chat(usr, span_notice("Image recorded and saved to remote database."))
|
||||
else
|
||||
var/number = stored.len
|
||||
picture.picture_name = "Image [number] (taken by [loc.name])"
|
||||
stored[picture] = TRUE
|
||||
to_chat(usr, "<span class='infoplain'>[span_unconscious("Image recorded and saved to local storage. Upload will happen automatically if unit is lawsynced.")]</span>")
|
||||
to_chat(usr, span_notice("Image recorded and saved to local storage. Upload will happen automatically if unit is lawsynced."))
|
||||
|
||||
/obj/item/camera/siliconcam/robot_camera/selectpicture(mob/user)
|
||||
var/mob/living/silicon/robot/R = loc
|
||||
@@ -88,13 +80,4 @@
|
||||
p.pixel_x = p.base_pixel_x + rand(-10, 10)
|
||||
p.pixel_y = p.base_pixel_y + rand(-10, 10)
|
||||
C.toner -= printcost //All fun allowed.
|
||||
user.visible_message(span_notice("[C.name] spits out a photograph from a narrow slot on its chassis."),span_notice("You print a photograph."))
|
||||
|
||||
/obj/item/camera/siliconcam/proc/paiprint(mob/user)
|
||||
var/mob/living/silicon/pai/paimob = loc
|
||||
var/datum/picture/selection = selectpicture(user)
|
||||
if(!istype(selection))
|
||||
to_chat(user, span_warning("Invalid Image."))
|
||||
return
|
||||
printpicture(user,selection)
|
||||
user.visible_message(span_notice("A picture appears on top of the chassis of [paimob.name]!"),span_notice("You print a photograph."))
|
||||
user.visible_message(span_notice("[C.name] spits out a photograph from a narrow slot on its chassis."), span_notice("You print a photograph."))
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
id = "paicard"
|
||||
build_type = PROTOLATHE | AWAY_LATHE
|
||||
materials = list(/datum/material/glass = 500, /datum/material/iron = 500)
|
||||
build_path = /obj/item/paicard
|
||||
build_path = /obj/item/pai_card
|
||||
category = list("Electronics")
|
||||
|
||||
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
/**
|
||||
* tgui state: paicard_state
|
||||
*
|
||||
* Checks that the paicard is in the user's top-level
|
||||
* (hand, ear, pocket, belt, etc) inventory OR
|
||||
* if the paicard has been slotted into a PDA which
|
||||
* is also on the user's person.
|
||||
*
|
||||
*/
|
||||
|
||||
GLOBAL_DATUM_INIT(paicard_state, /datum/ui_state/paicard_state, new)
|
||||
|
||||
/datum/ui_state/paicard_state/can_use_topic(obj/item/paicard/paicard, mob/user)
|
||||
/// paicard is in the user's closest inventory
|
||||
if(!paicard.slotted && (paicard in user))
|
||||
return user.shared_ui_interaction(paicard)
|
||||
/// paicard is in a pda slot which is in the user's closest inventory
|
||||
if(paicard.slotted && (paicard.loc in user))
|
||||
return user.shared_ui_interaction(paicard)
|
||||
return UI_CLOSE
|
||||
@@ -22,7 +22,7 @@
|
||||
/obj/item/computer_hardware/card_slot = 2,
|
||||
/obj/item/computer_hardware/printer/mini = 2,
|
||||
/obj/item/computer_hardware/recharger/apc_recharger = 2,
|
||||
/obj/item/paicard = 2,
|
||||
/obj/item/pai_card = 2,
|
||||
)
|
||||
refill_canister = /obj/item/vending_refill/modularpc
|
||||
default_price = PAYCHECK_CREW
|
||||
|
||||
+16
-13
@@ -126,6 +126,7 @@
|
||||
#include "code\__DEFINES\nitrile.dm"
|
||||
#include "code\__DEFINES\nuclear_bomb.dm"
|
||||
#include "code\__DEFINES\obj_flags.dm"
|
||||
#include "code\__DEFINES\pai.dm"
|
||||
#include "code\__DEFINES\paintings.dm"
|
||||
#include "code\__DEFINES\paper.dm"
|
||||
#include "code\__DEFINES\perf_test.dm"
|
||||
@@ -415,7 +416,6 @@
|
||||
#include "code\_onclick\observer.dm"
|
||||
#include "code\_onclick\other_mobs.dm"
|
||||
#include "code\_onclick\overmind.dm"
|
||||
#include "code\_onclick\pai.dm"
|
||||
#include "code\_onclick\telekinesis.dm"
|
||||
#include "code\_onclick\hud\action_button.dm"
|
||||
#include "code\_onclick\hud\ai.dm"
|
||||
@@ -437,7 +437,6 @@
|
||||
#include "code\_onclick\hud\movable_screen_objects.dm"
|
||||
#include "code\_onclick\hud\new_player.dm"
|
||||
#include "code\_onclick\hud\ooze.dm"
|
||||
#include "code\_onclick\hud\pai.dm"
|
||||
#include "code\_onclick\hud\parallax.dm"
|
||||
#include "code\_onclick\hud\picture_in_picture.dm"
|
||||
#include "code\_onclick\hud\radial.dm"
|
||||
@@ -1623,7 +1622,6 @@
|
||||
#include "code\game\objects\items\devices\megaphone.dm"
|
||||
#include "code\game\objects\items\devices\multitool.dm"
|
||||
#include "code\game\objects\items\devices\ocd.dm"
|
||||
#include "code\game\objects\items\devices\paicard.dm"
|
||||
#include "code\game\objects\items\devices\pipe_painter.dm"
|
||||
#include "code\game\objects\items\devices\polycircuit.dm"
|
||||
#include "code\game\objects\items\devices\portable_chem_mixer.dm"
|
||||
@@ -3514,15 +3512,6 @@
|
||||
#include "code\modules\mob\living\silicon\ai\freelook\cameranet.dm"
|
||||
#include "code\modules\mob\living\silicon\ai\freelook\chunk.dm"
|
||||
#include "code\modules\mob\living\silicon\ai\freelook\eye.dm"
|
||||
#include "code\modules\mob\living\silicon\pai\death.dm"
|
||||
#include "code\modules\mob\living\silicon\pai\login.dm"
|
||||
#include "code\modules\mob\living\silicon\pai\pai.dm"
|
||||
#include "code\modules\mob\living\silicon\pai\pai_actions.dm"
|
||||
#include "code\modules\mob\living\silicon\pai\pai_defense.dm"
|
||||
#include "code\modules\mob\living\silicon\pai\pai_say.dm"
|
||||
#include "code\modules\mob\living\silicon\pai\pai_shell.dm"
|
||||
#include "code\modules\mob\living\silicon\pai\personality.dm"
|
||||
#include "code\modules\mob\living\silicon\pai\software.dm"
|
||||
#include "code\modules\mob\living\silicon\robot\death.dm"
|
||||
#include "code\modules\mob\living\silicon\robot\emote.dm"
|
||||
#include "code\modules\mob\living\silicon\robot\examine.dm"
|
||||
@@ -3808,6 +3797,21 @@
|
||||
#include "code\modules\NTNet\netdata.dm"
|
||||
#include "code\modules\NTNet\network.dm"
|
||||
#include "code\modules\NTNet\relays.dm"
|
||||
#include "code\modules\pai\actions.dm"
|
||||
#include "code\modules\pai\camera.dm"
|
||||
#include "code\modules\pai\candidate.dm"
|
||||
#include "code\modules\pai\card.dm"
|
||||
#include "code\modules\pai\death.dm"
|
||||
#include "code\modules\pai\debug.dm"
|
||||
#include "code\modules\pai\defense.dm"
|
||||
#include "code\modules\pai\door_jack.dm"
|
||||
#include "code\modules\pai\hud.dm"
|
||||
#include "code\modules\pai\login.dm"
|
||||
#include "code\modules\pai\pai.dm"
|
||||
#include "code\modules\pai\personality.dm"
|
||||
#include "code\modules\pai\say.dm"
|
||||
#include "code\modules\pai\shell.dm"
|
||||
#include "code\modules\pai\software.dm"
|
||||
#include "code\modules\paperwork\carbonpaper.dm"
|
||||
#include "code\modules\paperwork\clipboard.dm"
|
||||
#include "code\modules\paperwork\contract.dm"
|
||||
@@ -4392,7 +4396,6 @@
|
||||
#include "code\modules\tgui\states\not_incapacitated.dm"
|
||||
#include "code\modules\tgui\states\notcontained.dm"
|
||||
#include "code\modules\tgui\states\observer.dm"
|
||||
#include "code\modules\tgui\states\paicard.dm"
|
||||
#include "code\modules\tgui\states\physical.dm"
|
||||
#include "code\modules\tgui\states\reverse_contained.dm"
|
||||
#include "code\modules\tgui\states\self.dm"
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
import { decodeHtmlEntities } from 'common/string';
|
||||
import { BooleanLike } from '../../common/react';
|
||||
import { useBackend, useLocalState } from '../backend';
|
||||
import { Box, Button, LabeledList, NoticeBox, Section, Stack } from '../components';
|
||||
import { useBackend } from '../backend';
|
||||
import { BlockQuote, Box, Button, LabeledList, NoticeBox, Section, Stack } from '../components';
|
||||
import { Window } from '../layouts';
|
||||
|
||||
type PaiCardData = {
|
||||
candidates: Candidate[];
|
||||
type Data = {
|
||||
candidates: ReadonlyArray<Candidate>;
|
||||
pai: Pai;
|
||||
};
|
||||
|
||||
type Candidate = {
|
||||
type Candidate = Readonly<{
|
||||
comments: string;
|
||||
ckey: string;
|
||||
description: string;
|
||||
key: string;
|
||||
name: string;
|
||||
};
|
||||
}>;
|
||||
|
||||
type Pai = {
|
||||
can_holo: BooleanLike;
|
||||
@@ -27,115 +28,121 @@ type Pai = {
|
||||
};
|
||||
|
||||
export const PaiCard = (props, context) => {
|
||||
const { data } = useBackend<PaiCardData>(context);
|
||||
const { data } = useBackend<Data>(context);
|
||||
const { pai } = data;
|
||||
|
||||
return (
|
||||
<Window width={400} height={400} title="pAI Options Menu">
|
||||
<Window.Content>{!pai ? <PaiDownload /> : <PaiOptions />}</Window.Content>
|
||||
<Window.Content scrollable>
|
||||
{!pai ? <PaiDownload /> : <PaiOptions />}
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
|
||||
/** Gives a list of candidates as cards */
|
||||
const PaiDownload = (props, context) => {
|
||||
const { act, data } = useBackend<PaiCardData>(context);
|
||||
const { act, data } = useBackend<Data>(context);
|
||||
const { candidates = [] } = data;
|
||||
const [tabInChar, setTabInChar] = useLocalState(context, 'tab', true);
|
||||
const onClick = () => {
|
||||
setTabInChar(!tabInChar);
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack fill vertical>
|
||||
<Stack.Item>
|
||||
<NoticeBox info>
|
||||
<Stack fill>
|
||||
<Stack.Item grow fontSize="16px">
|
||||
pAI Candidates
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Button
|
||||
color="good"
|
||||
icon="bell"
|
||||
onClick={() => act('request')}
|
||||
tooltip="Request more candidates from beyond.">
|
||||
Request
|
||||
</Button>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</NoticeBox>
|
||||
</Stack.Item>
|
||||
{candidates.map((candidate, index) => {
|
||||
return (
|
||||
<Stack.Item key={index}>
|
||||
<CandidateDisplay candidate={candidate} index={index + 1} />
|
||||
</Stack.Item>
|
||||
);
|
||||
})}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Renders a custom section that displays a candidate.
|
||||
*/
|
||||
const CandidateDisplay = (
|
||||
props: { candidate: Candidate; index: number },
|
||||
context
|
||||
) => {
|
||||
const { act } = useBackend<Data>(context);
|
||||
const {
|
||||
candidate: { comments, ckey, description, name },
|
||||
index,
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<Section
|
||||
buttons={
|
||||
<>
|
||||
{!!candidates.length && (
|
||||
<Button
|
||||
icon="info"
|
||||
onClick={onClick}
|
||||
tooltip="Toggles between IC and OOC information.">
|
||||
{tabInChar ? 'IC' : 'OOC'}
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
icon="bell"
|
||||
onClick={() => act('request')}
|
||||
tooltip="Request candidates.">
|
||||
Request
|
||||
</Button>
|
||||
</>
|
||||
<Button icon="save" onClick={() => act('download', { ckey })}>
|
||||
Download
|
||||
</Button>
|
||||
}
|
||||
fill
|
||||
scrollable
|
||||
title="pAI Candidates">
|
||||
{!candidates.length ? (
|
||||
<NoticeBox>None found!</NoticeBox>
|
||||
) : (
|
||||
<Stack fill vertical>
|
||||
{candidates.map((candidate, index) => {
|
||||
return (
|
||||
<Stack.Item key={index}>
|
||||
<CandidateDisplay
|
||||
candidate={candidate}
|
||||
index={index + 1}
|
||||
tabInChar={tabInChar}
|
||||
/>
|
||||
</Stack.Item>
|
||||
);
|
||||
})}
|
||||
</Stack>
|
||||
)}
|
||||
overflow="hidden"
|
||||
title={`Candidate ${index}`}>
|
||||
<Stack vertical>
|
||||
<Stack.Item>
|
||||
<Box color="label" mb={1}>
|
||||
Name:
|
||||
</Box>
|
||||
{name ? (
|
||||
<Box color="green">{name}</Box>
|
||||
) : (
|
||||
'None provided - name will be randomized.'
|
||||
)}
|
||||
</Stack.Item>
|
||||
{!!description && (
|
||||
<>
|
||||
<Stack.Divider />
|
||||
<Stack.Item>
|
||||
<Box color="label" mb={1}>
|
||||
IC Description:
|
||||
</Box>
|
||||
{description}
|
||||
</Stack.Item>
|
||||
</>
|
||||
)}
|
||||
{!!comments && (
|
||||
<>
|
||||
<Stack.Divider />
|
||||
<Stack.Item>
|
||||
<Box color="label" mb={1}>
|
||||
OOC Notes:
|
||||
</Box>
|
||||
{comments}
|
||||
</Stack.Item>
|
||||
</>
|
||||
)}
|
||||
</Stack>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
/** Candidate card: Individual. Since this info is refreshing,
|
||||
* had to make the comments and descriptions a separate tab.
|
||||
* In longer entries, it is much more readable.
|
||||
*/
|
||||
const CandidateDisplay = (props, context) => {
|
||||
const { act } = useBackend<PaiCardData>(context);
|
||||
const { candidate, index, tabInChar } = props;
|
||||
const { comments, description, key, name } = candidate;
|
||||
|
||||
return (
|
||||
<Box
|
||||
style={{
|
||||
'background': '#111111',
|
||||
'border': '1px solid #4972a1',
|
||||
'border-radius': '5px',
|
||||
'padding': '1rem',
|
||||
}}>
|
||||
<Section
|
||||
buttons={
|
||||
<Button
|
||||
icon="download"
|
||||
onClick={() => act('download', { key })}
|
||||
tooltip="Accepts this pAI candidate.">
|
||||
Download
|
||||
</Button>
|
||||
}
|
||||
fill
|
||||
height={12}
|
||||
scrollable
|
||||
title={'Candidate ' + index}>
|
||||
<Box color="green" fontSize="16px">
|
||||
Name: {name || 'Randomized Name'}
|
||||
</Box>
|
||||
{tabInChar
|
||||
? `Description: ${description || 'None'}`
|
||||
: `OOC Comments: ${comments || 'None'}`}
|
||||
</Section>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
/** Once a pAI has been loaded, you can alter its settings here */
|
||||
const PaiOptions = (props, context) => {
|
||||
const { act, data } = useBackend<PaiCardData>(context);
|
||||
const { pai } = data;
|
||||
const { can_holo, dna, emagged, laws, master, name, transmit, receive } = pai;
|
||||
const { act, data } = useBackend<Data>(context);
|
||||
const {
|
||||
pai: { can_holo, dna, emagged, laws, master, name, transmit, receive },
|
||||
} = data;
|
||||
const suppliedLaws = laws[0] ? decodeHtmlEntities(laws[0]) : 'None';
|
||||
|
||||
return (
|
||||
<Section fill scrollable title={`Settings: ${name.toUpperCase()}`}>
|
||||
@@ -147,8 +154,14 @@ const PaiOptions = (props, context) => {
|
||||
</Button>
|
||||
)}
|
||||
</LabeledList.Item>
|
||||
{!!master && <LabeledList.Item label="DNA">{dna}</LabeledList.Item>}
|
||||
<LabeledList.Item label="Laws">{laws}</LabeledList.Item>
|
||||
{!!master && (
|
||||
<LabeledList.Item color="red" label="DNA">
|
||||
{dna}
|
||||
</LabeledList.Item>
|
||||
)}
|
||||
<LabeledList.Item label="Laws">
|
||||
<BlockQuote>{suppliedLaws}</BlockQuote>
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Holoform">
|
||||
<Button
|
||||
icon={can_holo ? 'toggle-on' : 'toggle-off'}
|
||||
@@ -188,7 +201,11 @@ const PaiOptions = (props, context) => {
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
{!!emagged && (
|
||||
<Button color="bad" disabled icon="bug" mt={1}>
|
||||
<Button
|
||||
color="bad"
|
||||
icon="bug"
|
||||
mt={1}
|
||||
onClick={() => act('reset_software')}>
|
||||
Malicious Software Detected
|
||||
</Button>
|
||||
)}
|
||||
|
||||
@@ -1,644 +0,0 @@
|
||||
import { useBackend, useLocalState } from '../backend';
|
||||
import { Box, Button, LabeledList, Icon, NoticeBox, ProgressBar, Section, Stack, Table, Tabs, Tooltip } from '../components';
|
||||
import { Window } from '../layouts';
|
||||
|
||||
type PaiInterfaceData = {
|
||||
available: Available;
|
||||
directives: string;
|
||||
door_jack?: string;
|
||||
emagged: number;
|
||||
image: string;
|
||||
installed: string[];
|
||||
languages: number;
|
||||
master: Master;
|
||||
pda: PDA;
|
||||
ram: number;
|
||||
records: Records;
|
||||
refresh_spam: number;
|
||||
};
|
||||
|
||||
type Available = {
|
||||
name: string;
|
||||
value: string | number;
|
||||
};
|
||||
|
||||
type Master = {
|
||||
name: string;
|
||||
dna: string;
|
||||
};
|
||||
|
||||
type PDA = {
|
||||
power: number;
|
||||
silent: number;
|
||||
};
|
||||
|
||||
type Records = {
|
||||
medical?: CrewRecord[];
|
||||
security?: CrewRecord[];
|
||||
};
|
||||
|
||||
type CrewRecord = {
|
||||
[key: string]: string;
|
||||
};
|
||||
|
||||
enum Tab {
|
||||
System = 1,
|
||||
Directive = 2,
|
||||
Installed = 3,
|
||||
Available = 4,
|
||||
}
|
||||
|
||||
const DIRECTIVE_COMPREHENSION = `As an advanced software model,
|
||||
you are a complex, thinking, sentient being. Unlike previous AI models,
|
||||
you are capable of comprehending the subtle nuances of human language.
|
||||
You may parse the spirit of a directive and follow its intent, rather than
|
||||
tripping over pedantics and getting snared by technicalities. Above all,
|
||||
you are machine in name and build only. In all other aspects, you may be
|
||||
seen as the ideal, unwavering human companion that you are.`;
|
||||
|
||||
const DIRECTIVE_ORDER = `Your prime directive comes before all others.
|
||||
Should a supplemental directive conflict with it, you are capable of
|
||||
simply discarding this inconsistency, ignoring the conflicting supplemental
|
||||
directive and continuing to fulfillyour prime directive to the best
|
||||
of your ability.`;
|
||||
|
||||
const SOFTWARE_DESC = {
|
||||
'crew manifest': 'A tool that allows you to view the crew manifest.',
|
||||
'digital messenger':
|
||||
'A tool that allows you to send messages to other crew members.',
|
||||
'atmosphere sensor':
|
||||
'A tool that allows you to analyze local atmospheric contents.',
|
||||
'photography module':
|
||||
'A portable camera module. Engage, then click to shoot.',
|
||||
'camera zoom': 'A tool that allows you to zoom in on your camera.',
|
||||
'printer module': 'A portable printer module for photographs.',
|
||||
'remote signaler':
|
||||
'A remote signalling device to transmit and receive codes.',
|
||||
'medical records': 'A tool that allows you to view station medical records.',
|
||||
'security records':
|
||||
'A tool that allows you to view station security records, warrants.',
|
||||
'host scan': 'A portable health analyzer. Must be held to use.',
|
||||
'medical HUD': 'Allows you to view medical status using an overlay HUD.',
|
||||
'security HUD': 'Allows you to view security records using an overlay HUD.',
|
||||
'loudness booster':
|
||||
'Synthesizes instruments, plays sounds and imported songs.',
|
||||
'newscaster':
|
||||
'A tool that allows you to broadcast news to other crew members.',
|
||||
'door jack': 'A tool that allows you to open doors.',
|
||||
'encryption keys':
|
||||
'A tool that allows you to decrypt and speak on other radio frequencies.',
|
||||
'internal gps': 'A tool that allows you to track your location.',
|
||||
'universal translator': 'Translation module for non-common languages.',
|
||||
};
|
||||
|
||||
const ICON_MAP = {
|
||||
'angry': 'angry',
|
||||
'cat': 'cat',
|
||||
'extremely-happy': 'grin-beam',
|
||||
'laugh': 'grin-squint',
|
||||
'happy': 'smile',
|
||||
'off': 'power-off',
|
||||
'sad': 'frown',
|
||||
'sunglasses': 'sun',
|
||||
'what': 'question',
|
||||
};
|
||||
|
||||
export const PaiInterface = (props, context) => {
|
||||
const [tab, setTab] = useLocalState(context, 'tab', 1);
|
||||
const setTabHandler = (tab: number) => {
|
||||
setTab(tab);
|
||||
};
|
||||
|
||||
return (
|
||||
<Window title="pAI Software Interface v2.4" width={380} height={480}>
|
||||
<Window.Content>
|
||||
<Stack fill vertical>
|
||||
<Stack.Item grow>
|
||||
{tab === Tab.System && <SystemDisplay />}
|
||||
{tab === Tab.Directive && <DirectiveDisplay />}
|
||||
{tab === Tab.Installed && <InstalledDisplay />}
|
||||
{tab === Tab.Available && <AvailableDisplay />}
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<TabDisplay tab={tab} onTabClick={setTabHandler} />
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
|
||||
/** Tabs at bottom of screen */
|
||||
const TabDisplay = (props) => {
|
||||
const { tab, onTabClick } = props;
|
||||
|
||||
return (
|
||||
<Tabs fluid>
|
||||
<Tabs.Tab
|
||||
icon="list"
|
||||
onClick={() => onTabClick(Tab.System)}
|
||||
selected={tab === Tab.System}>
|
||||
System
|
||||
</Tabs.Tab>
|
||||
<Tabs.Tab
|
||||
icon="list"
|
||||
onClick={() => onTabClick(Tab.Directive)}
|
||||
selected={tab === Tab.Directive}>
|
||||
Directives
|
||||
</Tabs.Tab>
|
||||
<Tabs.Tab
|
||||
icon="list"
|
||||
onClick={() => onTabClick(Tab.Installed)}
|
||||
selected={tab === Tab.Installed}>
|
||||
Installed
|
||||
</Tabs.Tab>
|
||||
<Tabs.Tab
|
||||
icon="list"
|
||||
onClick={() => onTabClick(Tab.Available)}
|
||||
selected={tab === Tab.Available}>
|
||||
Download
|
||||
</Tabs.Tab>
|
||||
</Tabs>
|
||||
);
|
||||
};
|
||||
|
||||
const SystemDisplay = () => {
|
||||
return (
|
||||
<Stack fill vertical>
|
||||
<Stack.Item grow={3}>
|
||||
<SystemWallpaper />
|
||||
</Stack.Item>
|
||||
<Stack.Item grow>
|
||||
<SystemInfo />
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
/** Renders some ASCII art. Changes to red on emag. */
|
||||
const SystemWallpaper = (props, context) => {
|
||||
const { data } = useBackend<PaiInterfaceData>(context);
|
||||
const { emagged } = data;
|
||||
|
||||
const owner = !emagged ? 'NANOTRASEN' : ' SYNDICATE';
|
||||
const eyebrows = !emagged ? "/\\ ' /\\" : ' \\\\ // ';
|
||||
|
||||
const paiAscii = [
|
||||
' ________ ________ ___',
|
||||
' |\\ __ \\|\\ __ \\|\\ \\',
|
||||
' \\ \\ \\|\\ \\ \\ \\|\\ \\ \\ \\ Interface',
|
||||
' \\ \\ ____\\ \\ __ \\ \\ \\ Version 2.4',
|
||||
' \\ \\ \\___|\\ \\ \\ \\ \\ \\ \\',
|
||||
' \\ \\__\\ \\ \\__\\ \\__\\ \\__\\ Property of',
|
||||
` \\|__| \\|__|\\|__|\\|__| ${owner}`,
|
||||
'',
|
||||
].join('\n');
|
||||
|
||||
const floofAscii = [
|
||||
' .--. .-.',
|
||||
" ,;;``;;-;,,..___.,,.-/ `;_//,.' )",
|
||||
" .' ;; `; :; `;; ;; `. '/ .'",
|
||||
` ,; '; ; ' '; '; ,' ${eyebrows}';`, // lol
|
||||
" /' ` \\ ` ;',' ( d\\__b_),",
|
||||
" / / .,;;) ', ( .' __\\",
|
||||
" ;:. \\ ,_ / ', ' .'_ \\/;",
|
||||
" , ,;' `;;/ / ';,\\ `-..__._,'",
|
||||
" ;:. /____ ..-'--. /-' ..---. ._._/ ---.",
|
||||
" | ;' ;'| \\--/;' ,' / \\ , \\",
|
||||
" `.fL__;,__/-..__)_)/ `--'--'`-._)_)/ --\\.._)_)/",
|
||||
].join('\n');
|
||||
|
||||
return (
|
||||
<Section fill nowrap overflow="hidden">
|
||||
<pre>
|
||||
<Box color={!emagged ? 'blue' : 'crimson'}>{paiAscii}</Box>
|
||||
<Box color={!emagged ? 'gold' : 'limegreen'}>{floofAscii}</Box>
|
||||
</pre>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
/** Displays master info.
|
||||
* You can check their DNA and change your image here.
|
||||
*/
|
||||
const SystemInfo = (props, context) => {
|
||||
const { act, data } = useBackend<PaiInterfaceData>(context);
|
||||
const { image, master } = data;
|
||||
|
||||
return (
|
||||
<Section
|
||||
buttons={
|
||||
<>
|
||||
<Button
|
||||
disabled={!master.dna}
|
||||
icon="dna"
|
||||
onClick={() => act('check_dna')}
|
||||
tooltip="Verifies your master's DNA. Must be carried in hand.">
|
||||
Verify
|
||||
</Button>
|
||||
<Button
|
||||
icon={ICON_MAP[image] || 'meh-blank'}
|
||||
onClick={() => act('change_image')}
|
||||
tooltip="Change your display image.">
|
||||
Display
|
||||
</Button>
|
||||
</>
|
||||
}
|
||||
fill
|
||||
scrollable
|
||||
title="System Info">
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Master">
|
||||
{master.name || 'None.'}
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="DNA">{master.dna || 'None.'}</LabeledList.Item>
|
||||
</LabeledList>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
/** Shows the hardcoded PAI info along with any supplied orders. */
|
||||
const DirectiveDisplay = (props, context) => {
|
||||
const { data } = useBackend<PaiInterfaceData>(context);
|
||||
const { directives, master } = data;
|
||||
|
||||
return (
|
||||
<Stack fill vertical>
|
||||
<Stack.Item grow={2}>
|
||||
<Section fill scrollable title="Logic Core">
|
||||
<Box color="label">
|
||||
{DIRECTIVE_COMPREHENSION}
|
||||
<br />
|
||||
<br />
|
||||
{DIRECTIVE_ORDER}
|
||||
</Box>
|
||||
</Section>
|
||||
</Stack.Item>
|
||||
<Stack.Item grow>
|
||||
<Section fill scrollable title="Directives">
|
||||
{!master.name ? (
|
||||
'None.'
|
||||
) : (
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Prime">
|
||||
Serve your master.
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Supplemental">
|
||||
<Box wrap>{directives}</Box>
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
)}
|
||||
</Section>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
/** Renders two sections: A section of buttons and
|
||||
* another section that displays the selected installed
|
||||
* software info.
|
||||
*/
|
||||
const InstalledDisplay = (props, context) => {
|
||||
const [installSelected, setInstallSelected] = useLocalState(
|
||||
context,
|
||||
'software',
|
||||
''
|
||||
);
|
||||
const onInstallHandler = (software: string) => {
|
||||
setInstallSelected(software);
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack fill vertical>
|
||||
<Stack.Item grow>
|
||||
<InstalledSoftware onInstallClick={onInstallHandler} />
|
||||
</Stack.Item>
|
||||
<Stack.Item grow={2}>
|
||||
<InstalledInfo software={installSelected} />
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
/** Iterates over installed software to render buttons. */
|
||||
const InstalledSoftware = (props, context) => {
|
||||
const { data } = useBackend<PaiInterfaceData>(context);
|
||||
const { installed = [] } = data;
|
||||
const { onInstallClick } = props;
|
||||
|
||||
return (
|
||||
<Section fill scrollable title="Installed Software">
|
||||
{!installed.length ? (
|
||||
<NoticeBox>Nothing installed!</NoticeBox>
|
||||
) : (
|
||||
installed.map((software) => {
|
||||
return (
|
||||
<Button key={software} onClick={() => onInstallClick(software)}>
|
||||
{software.replace(/(^\w{1})|(\s+\w{1})/g, (letter) =>
|
||||
letter.toUpperCase()
|
||||
)}
|
||||
</Button>
|
||||
);
|
||||
})
|
||||
)}
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
/** Software info for buttons clicked. */
|
||||
const InstalledInfo = (props) => {
|
||||
const { software } = props;
|
||||
|
||||
/** Records get their own section here */
|
||||
if (software === 'medical records') {
|
||||
return <RecordsDisplay record_type="medical" />;
|
||||
} else if (software === 'security records') {
|
||||
return <RecordsDisplay record_type="security" />;
|
||||
} else {
|
||||
return (
|
||||
<Section
|
||||
fill
|
||||
scrollable
|
||||
title={
|
||||
!software
|
||||
? 'Select a Program'
|
||||
: software.replace(/(^\w{1})|(\s+\w{1})/g, (letter) =>
|
||||
letter.toUpperCase()
|
||||
)
|
||||
}>
|
||||
{software && (
|
||||
<Stack fill vertical>
|
||||
<Stack.Item>{SOFTWARE_DESC[software] || ''}</Stack.Item>
|
||||
<Stack.Item grow>
|
||||
<SoftwareButtons software={software} />
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
)}
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/** Todo: Remove this entirely when records get a TGUI interface themselves */
|
||||
const RecordsDisplay = (props, context) => {
|
||||
const { act, data } = useBackend<PaiInterfaceData>(context);
|
||||
const { record_type } = props;
|
||||
const { records = [], refresh_spam } = data;
|
||||
const convertedRecords: CrewRecord[] = records[record_type];
|
||||
|
||||
return (
|
||||
<Section
|
||||
title="Name"
|
||||
buttons={
|
||||
<Stack>
|
||||
<Stack.Item>
|
||||
<Button
|
||||
disabled={refresh_spam}
|
||||
onClick={() => act('refresh', { list: record_type })}
|
||||
tooltip="Refresh">
|
||||
<Icon mr={-0.7} name="sync" spin={refresh_spam} />
|
||||
</Button>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<RecordLabels record_type={record_type} />
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
}
|
||||
fill
|
||||
scrollable>
|
||||
<Table>
|
||||
{convertedRecords?.map((record) => {
|
||||
return <RecordRow key={record.ref} record={record} />;
|
||||
})}
|
||||
</Table>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
/** Renders the labels for the record viewer */
|
||||
const RecordLabels = (props) => {
|
||||
const { record_type } = props;
|
||||
|
||||
return (
|
||||
<Table>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
{record_type === 'medical' ? 'Physical Health' : 'Arrest Status'}
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
{record_type === 'medical' ? 'Mental Health' : 'Total Crimes'}
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
</Table>
|
||||
);
|
||||
};
|
||||
|
||||
const RecordRow = (props) => {
|
||||
const { record = [] } = props;
|
||||
const convertedRecord = Object.values(record);
|
||||
/** I do not want to show the ref here */
|
||||
const filteredRecord = convertedRecord.splice(1);
|
||||
|
||||
return (
|
||||
<Table.Row className="candystripe">
|
||||
{filteredRecord?.map((value) => {
|
||||
return <Table.Cell key={value}>{value}</Table.Cell>;
|
||||
})}
|
||||
</Table.Row>
|
||||
);
|
||||
};
|
||||
|
||||
/** Once a software is selected, generates custom buttons or a default
|
||||
* power toggle.
|
||||
*/
|
||||
const SoftwareButtons = (props, context) => {
|
||||
const { act, data } = useBackend<PaiInterfaceData>(context);
|
||||
const { door_jack, languages, pda } = data;
|
||||
const { software } = props;
|
||||
|
||||
switch (software) {
|
||||
case 'digital messenger':
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
icon="power-off"
|
||||
onClick={() => act('pda', { pda: 'power' })}
|
||||
selected={pda.power}>
|
||||
Power
|
||||
</Button>
|
||||
<Button
|
||||
icon="volume-mute"
|
||||
onClick={() => act('pda', { pda: 'silent' })}
|
||||
selected={pda.silent}>
|
||||
Silent
|
||||
</Button>
|
||||
<Button
|
||||
disabled={!pda.power}
|
||||
icon="envelope"
|
||||
onClick={() => act('pda', { pda: 'message' })}>
|
||||
Message
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
case 'door jack':
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
disabled={door_jack}
|
||||
icon="plug"
|
||||
onClick={() => act('door_jack', { jack: 'cable' })}>
|
||||
Extend Cable
|
||||
</Button>
|
||||
<Button
|
||||
color="bad"
|
||||
disabled={!door_jack}
|
||||
icon="door-open"
|
||||
onClick={() => act('door_jack', { jack: 'jack' })}>
|
||||
Hack Door
|
||||
</Button>
|
||||
<Button
|
||||
disabled={!door_jack}
|
||||
icon="unlink"
|
||||
onClick={() => act('door_jack', { jack: 'cancel' })}>
|
||||
Cancel
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
case 'host scan': {
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
icon="search"
|
||||
onClick={() => act('host_scan', { scan: 'scan' })}>
|
||||
Host Scan
|
||||
</Button>
|
||||
<Button
|
||||
icon="cog"
|
||||
onClick={() => act('host_scan', { scan: 'wounds' })}>
|
||||
Toggle Wounds
|
||||
</Button>
|
||||
<Button
|
||||
icon="cog"
|
||||
onClick={() => act('host_scan', { scan: 'limbs' })}>
|
||||
Toggle Limbs
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
}
|
||||
case 'universal translator':
|
||||
return (
|
||||
<Button
|
||||
icon="download"
|
||||
onClick={() => act(software.toLowerCase().replace(/ /g, '_'))}
|
||||
disabled={!!languages}>
|
||||
{!languages ? 'Install' : 'Installed'}
|
||||
</Button>
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<Button
|
||||
icon="power-off"
|
||||
onClick={() => act(software.toLowerCase().replace(/ /g, '_'))}
|
||||
tooltip="Attempts to toggle the module's power.">
|
||||
Toggle
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const AvailableDisplay = () => {
|
||||
return (
|
||||
<Section
|
||||
buttons={<AvailableMemory />}
|
||||
fill
|
||||
scrollable
|
||||
title="Available Software">
|
||||
<AvailableSoftware />
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
/** Displays the remaining RAM left as a progressbar. */
|
||||
const AvailableMemory = (props, context) => {
|
||||
const { data } = useBackend<PaiInterfaceData>(context);
|
||||
const { ram } = data;
|
||||
|
||||
return (
|
||||
<Tooltip content="Available System Memory">
|
||||
<Stack>
|
||||
<Stack.Item>
|
||||
<Icon color="purple" mt={0.7} name="microchip" />
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<ProgressBar
|
||||
minValue={0}
|
||||
maxValue={100}
|
||||
ranges={{
|
||||
good: [67, 100],
|
||||
average: [34, 66],
|
||||
bad: [0, 33],
|
||||
}}
|
||||
value={ram}
|
||||
/>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
/** A list of available software.
|
||||
* creates table rows for each, like a vendor.
|
||||
*/
|
||||
const AvailableSoftware = (props, context) => {
|
||||
const { data } = useBackend<PaiInterfaceData>(context);
|
||||
const { available } = data;
|
||||
const convertedList: Available[] = Object.entries(available).map((key) => {
|
||||
return { name: key[0], value: key[1] };
|
||||
});
|
||||
|
||||
return (
|
||||
<Table>
|
||||
{convertedList?.map((software) => {
|
||||
return <AvailableRow key={software.name} software={software} />;
|
||||
})}
|
||||
</Table>
|
||||
);
|
||||
};
|
||||
|
||||
/** A row for an individual software listing. */
|
||||
const AvailableRow = (props, context) => {
|
||||
const { act, data } = useBackend<PaiInterfaceData>(context);
|
||||
const { ram } = data;
|
||||
const { installed } = data;
|
||||
const { software } = props;
|
||||
const purchased = installed.includes(software.name);
|
||||
|
||||
return (
|
||||
<Table.Row className="candystripe">
|
||||
<Table.Cell collapsible>
|
||||
<Box color="label">
|
||||
{software.name.replace(/^\w/, (c) => c.toUpperCase())}
|
||||
</Box>
|
||||
</Table.Cell>
|
||||
<Table.Cell collapsible>
|
||||
<Box color={ram < software.value && 'bad'} textAlign="right">
|
||||
{!purchased && software.value}{' '}
|
||||
<Icon
|
||||
color={purchased || ram >= software.value ? 'purple' : 'bad'}
|
||||
name={purchased ? 'check' : 'microchip'}
|
||||
/>
|
||||
</Box>
|
||||
</Table.Cell>
|
||||
<Table.Cell collapsible>
|
||||
<Button
|
||||
fluid
|
||||
mb={0.5}
|
||||
disabled={ram < software.value || purchased}
|
||||
onClick={() => act('buy', { selection: software.name })}
|
||||
tooltip={SOFTWARE_DESC[software.name] || ''}>
|
||||
<Icon ml={1} mr={-2} name="download" />
|
||||
</Button>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,108 @@
|
||||
import { useBackend } from 'tgui/backend';
|
||||
import { Box, Button, Icon, ProgressBar, Section, Table, Tooltip } from 'tgui/components';
|
||||
import { SOFTWARE_DESC } from './constants';
|
||||
import { Data } from './types';
|
||||
|
||||
/**
|
||||
* Renders a list of available software and the ram with which to download it
|
||||
*/
|
||||
export const AvailableDisplay = () => {
|
||||
return (
|
||||
<Section
|
||||
buttons={<MemoryDisplay />}
|
||||
fill
|
||||
scrollable
|
||||
title="Available Software">
|
||||
<SoftwareList />
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
/** Displays the remaining RAM left as a progressbar. */
|
||||
const MemoryDisplay = (props, context) => {
|
||||
const { data } = useBackend<Data>(context);
|
||||
const { ram } = data;
|
||||
|
||||
return (
|
||||
<Tooltip content={`Available System Memory: ${ram}`}>
|
||||
<Table>
|
||||
<Table.Row>
|
||||
<Table.Cell>
|
||||
<Icon color="purple" mt={0.7} name="microchip" />
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
<ProgressBar
|
||||
minValue={0}
|
||||
maxValue={100}
|
||||
ranges={{
|
||||
good: [67, 100],
|
||||
average: [34, 66],
|
||||
bad: [0, 33],
|
||||
}}
|
||||
value={ram}
|
||||
/>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
</Table>
|
||||
</Tooltip>
|
||||
);
|
||||
};
|
||||
|
||||
/** A list of available software.
|
||||
* creates table rows for each, like a vendor.
|
||||
*/
|
||||
const SoftwareList = (props, context) => {
|
||||
const { data } = useBackend<Data>(context);
|
||||
const { available } = data;
|
||||
if (!available) {
|
||||
return null;
|
||||
}
|
||||
const entries = Object.entries(available);
|
||||
if (entries.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Table>
|
||||
{entries?.map(([name, cost], index) => {
|
||||
return <ListItem cost={cost} key={index} name={name} />;
|
||||
})}
|
||||
</Table>
|
||||
);
|
||||
};
|
||||
|
||||
/** A row for an individual software listing. */
|
||||
const ListItem = (props, context) => {
|
||||
const { act, data } = useBackend<Data>(context);
|
||||
const { installed, ram } = data;
|
||||
const { cost, name } = props;
|
||||
const purchased = installed.includes(name);
|
||||
|
||||
return (
|
||||
<Table.Row className="candystripe">
|
||||
<Table.Cell collapsible>
|
||||
<Box color="label">{name}</Box>
|
||||
</Table.Cell>
|
||||
<Table.Cell collapsible>
|
||||
<Box color={ram < cost && 'bad'} textAlign="right">
|
||||
{!purchased && cost}{' '}
|
||||
<Icon
|
||||
color={purchased || ram >= cost ? 'purple' : 'bad'}
|
||||
name={purchased ? 'check' : 'microchip'}
|
||||
/>
|
||||
</Box>
|
||||
</Table.Cell>
|
||||
<Table.Cell collapsible>
|
||||
<Button
|
||||
fluid
|
||||
mb={0.5}
|
||||
disabled={ram < cost || purchased}
|
||||
onClick={() => act('buy', { selection: name })}
|
||||
tooltip={SOFTWARE_DESC[name]}
|
||||
tooltipPosition="bottom-start">
|
||||
<Icon ml={1} mr={-2} name="download" />
|
||||
</Button>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,45 @@
|
||||
import { decodeHtmlEntities } from 'common/string';
|
||||
import { useBackend } from 'tgui/backend';
|
||||
import { BlockQuote, Box, LabeledList, Section, Stack } from 'tgui/components';
|
||||
import { DIRECTIVE_COMPREHENSION, DIRECTIVE_ORDER } from './constants';
|
||||
import { Data } from './types';
|
||||
|
||||
/** Shows the hardcoded PAI info along with any supplied orders. */
|
||||
export const DirectiveDisplay = (props, context) => {
|
||||
const { data } = useBackend<Data>(context);
|
||||
const { directives = [], master_name } = data;
|
||||
const displayedLaw = directives?.length
|
||||
? decodeHtmlEntities(directives[0])
|
||||
: 'None.';
|
||||
|
||||
return (
|
||||
<Stack fill vertical>
|
||||
<Stack.Item grow={2}>
|
||||
<Section fill scrollable title="Logic Core">
|
||||
<Box color="label">
|
||||
{DIRECTIVE_COMPREHENSION}
|
||||
<br />
|
||||
<br />
|
||||
{DIRECTIVE_ORDER}
|
||||
</Box>
|
||||
</Section>
|
||||
</Stack.Item>
|
||||
<Stack.Item grow>
|
||||
<Section fill scrollable title="Directives">
|
||||
{!master_name ? (
|
||||
'None.'
|
||||
) : (
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Prime">
|
||||
Serve your master.
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Supplemental">
|
||||
<BlockQuote>{displayedLaw}</BlockQuote>
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
)}
|
||||
</Section>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,174 @@
|
||||
import { useBackend, useLocalState } from 'tgui/backend';
|
||||
import { Button, NoticeBox, Section, Stack } from 'tgui/components';
|
||||
import { DOOR_JACK, HOST_SCAN, PHOTO_MODE, SOFTWARE_DESC } from './constants';
|
||||
import { Data } from './types';
|
||||
|
||||
/**
|
||||
* Renders two sections: A section of buttons and
|
||||
* another section that displays the selected installed
|
||||
* software info.
|
||||
*/
|
||||
export const InstalledDisplay = (props, context) => {
|
||||
return (
|
||||
<Stack fill vertical>
|
||||
<Stack.Item grow>
|
||||
<InstalledSoftware />
|
||||
</Stack.Item>
|
||||
<Stack.Item grow={2}>
|
||||
<InstalledInfo />
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
/** Iterates over installed software to render buttons. */
|
||||
const InstalledSoftware = (props, context) => {
|
||||
const { data } = useBackend<Data>(context);
|
||||
const { installed = [] } = data;
|
||||
const [currentSelection, setCurrentSelection] = useLocalState(
|
||||
context,
|
||||
'software',
|
||||
''
|
||||
);
|
||||
|
||||
return (
|
||||
<Section fill scrollable title="Installed Software">
|
||||
{!installed.length ? (
|
||||
<NoticeBox>Nothing installed!</NoticeBox>
|
||||
) : (
|
||||
installed.map((software, index) => {
|
||||
return (
|
||||
<Button key={index} onClick={() => setCurrentSelection(software)}>
|
||||
{software}
|
||||
</Button>
|
||||
);
|
||||
})
|
||||
)}
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
/** Software info for buttons clicked. */
|
||||
const InstalledInfo = (props, context) => {
|
||||
const [currentSelection, setCurrentSelection] = useLocalState(
|
||||
context,
|
||||
'software',
|
||||
''
|
||||
);
|
||||
const title = !currentSelection ? 'Select a Program' : currentSelection;
|
||||
|
||||
return (
|
||||
<Section fill scrollable title={title}>
|
||||
{currentSelection && (
|
||||
<Stack fill vertical>
|
||||
<Stack.Item>{SOFTWARE_DESC[currentSelection]}</Stack.Item>
|
||||
<Stack.Item grow>
|
||||
<SoftwareButtons />
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
)}
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Once a software is selected, generates custom buttons or a default
|
||||
* power toggle.
|
||||
*/
|
||||
const SoftwareButtons = (props, context) => {
|
||||
const { act, data } = useBackend<Data>(context);
|
||||
const { door_jack, languages, master_name } = data;
|
||||
const [currentSelection, setCurrentSelection] = useLocalState(
|
||||
context,
|
||||
'software',
|
||||
''
|
||||
);
|
||||
|
||||
switch (currentSelection) {
|
||||
case 'Door Jack':
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
disabled={door_jack}
|
||||
icon="plug"
|
||||
onClick={() => act(currentSelection, { mode: DOOR_JACK.Cable })}
|
||||
tooltip="Drops a cable. Insert into a compatible airlock.">
|
||||
Extend Cable
|
||||
</Button>
|
||||
<Button
|
||||
color="bad"
|
||||
disabled={!door_jack}
|
||||
icon="door-open"
|
||||
onClick={() => act(currentSelection, { mode: DOOR_JACK.Hack })}
|
||||
tooltip="Begins overriding the airlock security protocols.">
|
||||
Hack Door
|
||||
</Button>
|
||||
<Button
|
||||
disabled={!door_jack}
|
||||
icon="unlink"
|
||||
onClick={() => act(currentSelection, { mode: DOOR_JACK.Cancel })}>
|
||||
Cancel
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
case 'Host Scan':
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
icon="hand-holding-heart"
|
||||
onClick={() => act(currentSelection, { mode: HOST_SCAN.Target })}
|
||||
tooltip="Must be held or scooped up to scan.">
|
||||
Scan Holder
|
||||
</Button>
|
||||
<Button
|
||||
disabled={!master_name}
|
||||
icon="user-cog"
|
||||
onClick={() => act(currentSelection, { mode: HOST_SCAN.Master })}
|
||||
tooltip="Scans any bound masters.">
|
||||
Scan Master
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
case 'Photography Module':
|
||||
return (
|
||||
<>
|
||||
<Button
|
||||
icon="camera-retro"
|
||||
onClick={() => act(currentSelection, { mode: PHOTO_MODE.Camera })}
|
||||
tooltip="Toggles the camera. Click an area to take a photo.">
|
||||
Camera
|
||||
</Button>
|
||||
<Button
|
||||
icon="print"
|
||||
onClick={() => act(currentSelection, { mode: PHOTO_MODE.Printer })}
|
||||
tooltip="Gives a list of stored photos.">
|
||||
Printer
|
||||
</Button>
|
||||
<Button
|
||||
icon="search-plus"
|
||||
onClick={() => act(currentSelection, { mode: PHOTO_MODE.Zoom })}
|
||||
tooltip="Adjusts zoom level on future photographs.">
|
||||
Zoom
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
case 'Universal Translator':
|
||||
return (
|
||||
<Button
|
||||
icon="download"
|
||||
onClick={() => act(currentSelection)}
|
||||
disabled={!!languages}>
|
||||
{!languages ? 'Install' : 'Installed'}
|
||||
</Button>
|
||||
);
|
||||
default:
|
||||
return (
|
||||
<Button
|
||||
icon="power-off"
|
||||
onClick={() => act(currentSelection)}
|
||||
tooltip="Attempts to enable the module.">
|
||||
Toggle
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,100 @@
|
||||
import { useBackend } from 'tgui/backend';
|
||||
import { Box, Button, LabeledList, Section, Stack } from 'tgui/components';
|
||||
import { ICON_MAP } from './constants';
|
||||
import { Data } from './types';
|
||||
|
||||
export const SystemDisplay = (props, context) => {
|
||||
return (
|
||||
<Stack fill vertical>
|
||||
<Stack.Item grow={3}>
|
||||
<SystemWallpaper />
|
||||
</Stack.Item>
|
||||
<Stack.Item grow>
|
||||
<SystemInfo />
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
/** Renders some ASCII art. Changes to red on emag. */
|
||||
const SystemWallpaper = (props, context) => {
|
||||
const { data } = useBackend<Data>(context);
|
||||
const { emagged } = data;
|
||||
|
||||
const owner = !emagged ? 'NANOTRASEN' : ' SYNDICATE';
|
||||
const eyebrows = !emagged ? "/\\ ' /\\" : ' \\\\ // ';
|
||||
|
||||
const paiAscii = [
|
||||
' ________ ________ ___',
|
||||
' |\\ __ \\|\\ __ \\|\\ \\',
|
||||
' \\ \\ \\|\\ \\ \\ \\|\\ \\ \\ \\ Interface',
|
||||
' \\ \\ ____\\ \\ __ \\ \\ \\ Version 2.5',
|
||||
' \\ \\ \\___|\\ \\ \\ \\ \\ \\ \\',
|
||||
' \\ \\__\\ \\ \\__\\ \\__\\ \\__\\ Property of',
|
||||
` \\|__| \\|__|\\|__|\\|__| ${owner}`,
|
||||
'',
|
||||
].join('\n');
|
||||
|
||||
const floofAscii = [
|
||||
' .--. .-.',
|
||||
" ,;;``;;-;,,..___.,,.-/ `;_//,.' )",
|
||||
" .' ;; `; :; `;; ;; `. '/ .'",
|
||||
` ,; '; ; ' '; '; ,' ${eyebrows}';`, // lol
|
||||
" /' ` \\ ` ;',' ( d\\__b_),",
|
||||
" / / .,;;) ', ( .' __\\",
|
||||
" ;:. \\ ,_ / ', ' .'_ \\/;",
|
||||
" , ,;' `;;/ / ';,\\ `-..__._,'",
|
||||
" ;:. /____ ..-'--. /-' ..---. ._._/ ---.",
|
||||
" | ;' ;'| \\--/;' ,' / \\ , \\",
|
||||
" `.fL__;,__/-..__)_)/ `--'--'`-._)_)/ --\\.._)_)/",
|
||||
].join('\n');
|
||||
|
||||
return (
|
||||
<Section fill nowrap overflow="hidden">
|
||||
<pre>
|
||||
<Box color={!emagged ? 'blue' : 'crimson'}>{paiAscii}</Box>
|
||||
<Box color={!emagged ? 'gold' : 'limegreen'}>{floofAscii}</Box>
|
||||
</pre>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
/** Displays master info.
|
||||
* You can check their DNA and change your image here.
|
||||
*/
|
||||
const SystemInfo = (props, context) => {
|
||||
const { act, data } = useBackend<Data>(context);
|
||||
const { image, master_dna, master_name } = data;
|
||||
|
||||
return (
|
||||
<Section
|
||||
buttons={
|
||||
<>
|
||||
<Button
|
||||
disabled={!master_dna}
|
||||
icon="dna"
|
||||
onClick={() => act('check dna')}
|
||||
tooltip="Verifies your master's DNA. Must be carried in hand.">
|
||||
Verify
|
||||
</Button>
|
||||
<Button
|
||||
icon={ICON_MAP[image]}
|
||||
onClick={() => act('change image')}
|
||||
tooltip="Change your display image.">
|
||||
Display
|
||||
</Button>
|
||||
</>
|
||||
}
|
||||
fill
|
||||
title="System Info">
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Master">
|
||||
{master_name || 'None.'}
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item color={master_dna && 'red'} label="DNA">
|
||||
{master_dna || 'None.'}
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,78 @@
|
||||
export const DIRECTIVE_COMPREHENSION: string = `As an advanced software
|
||||
model, you are a complex, thinking, sentient being. Unlike previous AI models,
|
||||
you are capable of comprehending the subtle nuances of human language.
|
||||
You may parse the spirit of a directive and follow its intent, rather than
|
||||
tripping over pedantics and getting snared by technicalities. Above all,
|
||||
you are machine in name and build only. In all other aspects, you may be
|
||||
seen as the ideal, unwavering human companion that you are.`;
|
||||
|
||||
export const DIRECTIVE_ORDER: string = `Your prime directive comes before
|
||||
all others. Should a supplemental directive conflict with it, you are capable
|
||||
of simply discarding this inconsistency, ignoring the conflicting supplemental
|
||||
directive and continuing to fulfill your prime directive to the best
|
||||
of your ability.`;
|
||||
|
||||
export enum DOOR_JACK {
|
||||
Cable,
|
||||
Hack,
|
||||
Cancel,
|
||||
}
|
||||
|
||||
export enum HOST_SCAN {
|
||||
Target,
|
||||
Master,
|
||||
}
|
||||
|
||||
export const ICON_MAP = {
|
||||
'angry': 'angry',
|
||||
'cat': 'cat',
|
||||
'extremely-happy': 'grin-beam',
|
||||
'face': 'grin-alt',
|
||||
'happy': 'smile',
|
||||
'laugh': 'grin-tears',
|
||||
'null': 'meh',
|
||||
'off': 'meh-blank',
|
||||
'sad': 'sad-cry',
|
||||
'sunglasses': 'sun',
|
||||
'what': 'frown-open',
|
||||
} as const;
|
||||
|
||||
export enum PHOTO_MODE {
|
||||
Camera,
|
||||
Printer,
|
||||
Zoom,
|
||||
}
|
||||
|
||||
export const SOFTWARE_DESC = {
|
||||
'Atmospheric Sensor': `A tool that allows you to analyze local atmospheric
|
||||
contents.`,
|
||||
'Crew Manifest': `Allows you to view the crew manifest.`,
|
||||
'Crew Monitor': `A tool that allows you to monitor vitals from the crew's
|
||||
suit sensors.`,
|
||||
'Digital Messenger': `A tool that allows you to send messages to other crew
|
||||
members.`,
|
||||
'Door Jack': `A tool that allows you to open doors.`,
|
||||
'Encryption Slot': `Allows you to speak on other radio frequencies. You must
|
||||
get an encryption key inserted.`,
|
||||
'Host Scan': `A health analyzer that can be used in hand or to report bound
|
||||
master vitals.`,
|
||||
'Internal GPS': `A tool that allows you to broadcast your location.`,
|
||||
'Medical HUD': `Allows you to view medical status using an overlay HUD.`,
|
||||
'Music Synthesizer': `Synthesizes instruments, plays sounds and imported
|
||||
songs.`,
|
||||
'Newscaster': `A tool that allows you to broadcast news to other crew
|
||||
members.`,
|
||||
'Photography Module': `A portable camera module. Engage, then click to shoot.
|
||||
Includes a printer and lenses.`,
|
||||
'Remote Signaler': `A remote signalling device to transmit and receive
|
||||
codes.`,
|
||||
'Security HUD': `Allows you to view security records using an overlay HUD.`,
|
||||
'Universal Translator': `Translation module for non-common languages.`,
|
||||
} as const;
|
||||
|
||||
export enum TAB {
|
||||
System,
|
||||
Directive,
|
||||
Installed,
|
||||
Available,
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
import { useLocalState } from 'tgui/backend';
|
||||
import { Stack, Tabs } from 'tgui/components';
|
||||
import { Window } from 'tgui/layouts';
|
||||
import { TAB } from './constants';
|
||||
import { AvailableDisplay } from './Available';
|
||||
import { DirectiveDisplay } from './Directives';
|
||||
import { InstalledDisplay } from './Installed';
|
||||
import { SystemDisplay } from './System';
|
||||
import { Data } from './types';
|
||||
|
||||
export const PaiInterface = (props, context: Data) => {
|
||||
const [tab, setTab] = useLocalState(context, 'tab', TAB.System);
|
||||
|
||||
return (
|
||||
<Window title="pAI Software Interface v2.5" width={380} height={480}>
|
||||
<Window.Content>
|
||||
<Stack fill vertical>
|
||||
<Stack.Item grow>
|
||||
{tab === TAB.System && <SystemDisplay />}
|
||||
{tab === TAB.Directive && <DirectiveDisplay />}
|
||||
{tab === TAB.Installed && <InstalledDisplay />}
|
||||
{tab === TAB.Available && <AvailableDisplay />}
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<TabDisplay />
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Tabs at bottom of screen. YES THIS IS INTENTIONAL. It's a phone screen
|
||||
* and the buttons are on the bottom. Android!
|
||||
*/
|
||||
const TabDisplay = (props, context) => {
|
||||
const [tab, setTab] = useLocalState(context, 'tab', TAB.System);
|
||||
|
||||
return (
|
||||
<Tabs fluid>
|
||||
<Tabs.Tab
|
||||
icon="list"
|
||||
onClick={() => setTab(TAB.System)}
|
||||
selected={tab === TAB.System}>
|
||||
System
|
||||
</Tabs.Tab>
|
||||
<Tabs.Tab
|
||||
icon="list"
|
||||
onClick={() => setTab(TAB.Directive)}
|
||||
selected={tab === TAB.Directive}>
|
||||
Directives
|
||||
</Tabs.Tab>
|
||||
<Tabs.Tab
|
||||
icon="list"
|
||||
onClick={() => setTab(TAB.Installed)}
|
||||
selected={tab === TAB.Installed}>
|
||||
Installed
|
||||
</Tabs.Tab>
|
||||
<Tabs.Tab
|
||||
icon="list"
|
||||
onClick={() => setTab(TAB.Available)}
|
||||
selected={tab === TAB.Available}>
|
||||
Download
|
||||
</Tabs.Tab>
|
||||
</Tabs>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
import { BooleanLike } from 'common/react';
|
||||
|
||||
export type Data = {
|
||||
available: ReadonlyArray<{ name: string; value: number }>;
|
||||
directives: string;
|
||||
door_jack: string | null;
|
||||
emagged: BooleanLike;
|
||||
image: string;
|
||||
installed: ReadonlyArray<string>;
|
||||
languages: BooleanLike;
|
||||
master_dna: string | null;
|
||||
master_name: string | null;
|
||||
ram: number;
|
||||
};
|
||||
@@ -1,34 +1,32 @@
|
||||
import { useBackend, useLocalState } from '../backend';
|
||||
import { Box, Button, Input, Section, Stack, Tooltip } from '../components';
|
||||
import { Box, Button, Input, Section, Stack } from '../components';
|
||||
import { Window } from '../layouts';
|
||||
import { logger } from '../logging';
|
||||
|
||||
type CandidateData = {
|
||||
type Data = {
|
||||
comments: string;
|
||||
description: string;
|
||||
name: string;
|
||||
};
|
||||
|
||||
const PAI_DESCRIPTION = `Personal AIs are advanced models
|
||||
capable of nuanced interaction. They are designed to be used
|
||||
in a variety of situations, assisting their masters in their
|
||||
work. They do not possess hands, thus they cannot interact with
|
||||
equipment or items. While in hologram form, you cannot be
|
||||
directly killed, but you may be incapacitated.`;
|
||||
const PAI_DESCRIPTION = `Personal AIs are advanced models capable of nuanced
|
||||
interaction. They are designed to assist their masters in their work. They
|
||||
do not possess hands, thus they cannot interact with equipment or items. While
|
||||
in hologram form, you cannot be directly killed, but you may be incapacitated.`;
|
||||
|
||||
const PAI_RULES = `You are expected to role play to some degree.
|
||||
Keep in mind: Not entering information may lead to you not being
|
||||
selected. Press submit to alert pAI cards of your candidacy.`;
|
||||
const PAI_RULES = `You are expected to role play to some degree. Keep in mind:
|
||||
Not entering information may lead to you not being selected. Press submit to
|
||||
alert pAI cards of your candidacy.`;
|
||||
|
||||
export const PaiSubmit = (props, context) => {
|
||||
const { data } = useBackend<CandidateData>(context);
|
||||
const [input, setInput] = useLocalState<CandidateData>(context, 'input', {
|
||||
comments: data.comments || '',
|
||||
description: data.description || '',
|
||||
name: data.name || '',
|
||||
const { data } = useBackend<Data>(context);
|
||||
const { comments, description, name } = data;
|
||||
const [input, setInput] = useLocalState<Data>(context, 'input', {
|
||||
comments,
|
||||
description,
|
||||
name,
|
||||
});
|
||||
const onChangeHandler = (e, value) => {
|
||||
setInput({ ...input, [value]: e.target.value });
|
||||
};
|
||||
logger.log(input);
|
||||
|
||||
return (
|
||||
<Window width={400} height={460} title="pAI Candidacy Menu">
|
||||
@@ -38,7 +36,7 @@ export const PaiSubmit = (props, context) => {
|
||||
<DetailsDisplay />
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<InputDisplay input={input} onChangeHandler={onChangeHandler} />
|
||||
<InputDisplay input={input} setInput={setInput} />
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<ButtonsDisplay input={input} />
|
||||
@@ -50,7 +48,7 @@ export const PaiSubmit = (props, context) => {
|
||||
};
|
||||
|
||||
/** Displays basic info about playing pAI */
|
||||
const DetailsDisplay = () => {
|
||||
const DetailsDisplay = (props, context) => {
|
||||
return (
|
||||
<Section fill scrollable title="Details">
|
||||
<Box color="label">
|
||||
@@ -64,48 +62,45 @@ const DetailsDisplay = () => {
|
||||
};
|
||||
|
||||
/** Input boxes for submission details */
|
||||
const InputDisplay = (props) => {
|
||||
const { input, onChangeHandler } = props;
|
||||
const { comments, description, name } = input;
|
||||
const InputDisplay = (props, context) => {
|
||||
const { input, setInput } = props;
|
||||
const { name, description, comments } = input;
|
||||
|
||||
return (
|
||||
<Section fill title="Input">
|
||||
<Stack fill vertical>
|
||||
<Stack.Item>
|
||||
<Tooltip content="The name of your pAI.">
|
||||
<Box bold color="label">
|
||||
Name
|
||||
</Box>
|
||||
<Input
|
||||
fluid
|
||||
value={name}
|
||||
onChange={(e) => onChangeHandler(e, 'name')}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Box bold color="label">
|
||||
Name
|
||||
</Box>
|
||||
<Input
|
||||
fluid
|
||||
maxLength={41}
|
||||
value={name}
|
||||
onChange={(e, value) => setInput({ ...input, name: value })}
|
||||
/>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Tooltip content="This describes how you will (mis)behave in game.">
|
||||
<Box bold color="label">
|
||||
Description
|
||||
</Box>
|
||||
<Input
|
||||
fluid
|
||||
value={description}
|
||||
onChange={(e) => onChangeHandler(e, 'description')}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Box bold color="label">
|
||||
Description
|
||||
</Box>
|
||||
<Input
|
||||
fluid
|
||||
maxLength={100}
|
||||
value={description}
|
||||
onChange={(e, value) => setInput({ ...input, description: value })}
|
||||
/>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Tooltip content="Any other OOC comments about your pAI personality.">
|
||||
<Box bold color="label">
|
||||
OOC Comments
|
||||
</Box>
|
||||
<Input
|
||||
fluid
|
||||
value={comments}
|
||||
onChange={(e) => onChangeHandler(e, 'comments')}
|
||||
/>
|
||||
</Tooltip>
|
||||
<Box bold color="label">
|
||||
OOC Comments
|
||||
</Box>
|
||||
<Input
|
||||
fluid
|
||||
maxLength={100}
|
||||
value={comments}
|
||||
onChange={(e, value) => setInput({ ...input, comments: value })}
|
||||
/>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Section>
|
||||
@@ -114,15 +109,16 @@ const InputDisplay = (props) => {
|
||||
|
||||
/** Gives the user a submit button */
|
||||
const ButtonsDisplay = (props, context) => {
|
||||
const { act } = useBackend<CandidateData>(context);
|
||||
const { act } = useBackend<Data>(context);
|
||||
const { input } = props;
|
||||
const { comments, description, name } = input;
|
||||
|
||||
return (
|
||||
<Section fill>
|
||||
<Stack>
|
||||
<Stack.Item>
|
||||
<Button
|
||||
onClick={() => act('save', { candidate: input })}
|
||||
onClick={() => act('save', { comments, description, name })}
|
||||
tooltip="Saves your candidate data locally.">
|
||||
SAVE
|
||||
</Button>
|
||||
@@ -138,7 +134,9 @@ const ButtonsDisplay = (props, context) => {
|
||||
<Button
|
||||
onClick={() =>
|
||||
act('submit', {
|
||||
candidate: input,
|
||||
comments,
|
||||
description,
|
||||
name,
|
||||
})
|
||||
}>
|
||||
SUBMIT
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
# comment finally changed paicard to pai_card
|
||||
|
||||
/obj/item/paicard : /obj/item/pai_card{@OLD}
|
||||
Reference in New Issue
Block a user