[READY] TGUI pAIs (#14524)

* TGUI pAIs

* Nukes an old template

* pAI Crew Manifest

* pAI Atmos Scan

* pAI Medical Records

* pAI Security Records + code cleanup

* pAI directives + More refactoring

* Sorts the toggles out

* pAI messenger

* WIP - Radios

* pAI radio - working

* pAI Signalers

* Nukes some arleady gone templates

* Forgot to purge this too

* pAI Door Jack

* pAI Bioscan

* Mochi tweaks

* Farie Fixes

* Spacing
This commit is contained in:
AffectedArc07
2020-10-15 12:19:57 +01:00
committed by GitHub
parent e596560604
commit 960efdd5e7
46 changed files with 1474 additions and 2136 deletions
-2
View File
@@ -72,8 +72,6 @@
to_chat(world, "<span class='warning'>pAI software module [P.name] has the same key as [O.name]!</span>")
continue
GLOB.pai_software_by_key[P.id] = P
if(P.default)
GLOB.default_pai_software[P.id] = P
// Setup loadout gear
for(var/geartype in subtypesof(/datum/gear))
+6 -6
View File
@@ -7,12 +7,12 @@
silence_time = null
to_chat(src, "<font color=green>Communication circuit reinitialized. Speech and messaging functionality restored.</font>")
if(cable)
if(get_dist(src, cable) > 1)
var/turf/T = get_turf_or_move(loc)
for(var/mob/M in viewers(T))
M.show_message("<span class='warning'>The data cable rapidly retracts back into its spool.</span>", 3, "<span class='warning'>You hear a click and the sound of wire spooling rapidly.</span>", 2)
QDEL_NULL(cable)
if(installed_software["doorjack"])
var/datum/pai_software/door_jack/DJ = installed_software["doorjack"]
if(DJ.cable)
if(get_dist(src, DJ.cable) > 1)
visible_message("<span class='warning'>The data cable connected to [src] rapidly retracts back into its spool!</span>")
QDEL_NULL(DJ.cable)
/mob/living/silicon/pai/updatehealth(reason = "none given")
if(status_flags & GODMODE)
+18 -108
View File
@@ -9,11 +9,8 @@
pass_flags = PASSTABLE
density = 0
holder_type = /obj/item/holder/pai
var/network = "SS13"
var/obj/machinery/camera/current = null
var/ram = 100 // Used as currency to purchase different abilities
var/list/software = list()
var/userDNA // The DNA string of our assigned user
var/obj/item/paicard/card // The card we inhabit
var/obj/item/radio/radio // Our primary radio
@@ -42,9 +39,6 @@
)
var/obj/item/pai_cable/cable // The cable we produce and use when door or camera jacking
var/master // Name of the one who commands us
var/master_dna // DNA string for owner verification
// Keeping this separate from the laws var, it should be much more difficult to modify
@@ -64,17 +58,11 @@
var/secHUD = 0 // Toggles whether the Security HUD is active or not
var/medHUD = 0 // Toggles whether the Medical HUD is active or not
var/medical_cannotfind = 0
var/datum/data/record/medicalActive1 // Datacore record declarations for record software
var/datum/data/record/medicalActive2
/// Currently active software
var/datum/pai_software/active_software
var/security_cannotfind = 0
var/datum/data/record/securityActive1 // Could probably just combine all these into one
var/datum/data/record/securityActive2
var/obj/machinery/door/hackdoor // The airlock being hacked
var/hackprogress = 0 // Possible values: 0 - 100, >= 100 means the hack is complete and will be reset upon next check
var/hack_aborted = 0
/// List of all installed software
var/list/datum/pai_software/installed_software = list()
var/obj/item/integrated_radio/signal/sradio // AI's signaller
@@ -109,20 +97,20 @@
//PDA
pda = new(src)
spawn(5)
pda.ownjob = "Personal Assistant"
pda.owner = text("[]", src)
pda.name = pda.owner + " (" + pda.ownjob + ")"
var/datum/data/pda/app/messenger/M = pda.find_program(/datum/data/pda/app/messenger)
M.toff = 1
..()
pda.ownjob = "Personal Assistant"
pda.owner = "[src]"
pda.name = "[pda.owner] ([pda.ownjob])"
var/datum/data/pda/app/messenger/M = pda.find_program(/datum/data/pda/app/messenger)
M.toff = TRUE
/mob/living/silicon/pai/Destroy()
medicalActive1 = null
medicalActive2 = null
securityActive1 = null
securityActive2 = null
return ..()
// Software modules. No these var names have nothing to do with photoshop
for(var/PS in subtypesof(/datum/pai_software))
var/datum/pai_software/PSD = new PS(src)
if(PSD.default)
installed_software[PSD.id] = PSD
active_software = installed_software["mainmenu"] // Default us to the main menu
..()
/mob/living/silicon/pai/can_unbuckle()
return FALSE
@@ -159,12 +147,6 @@
for(var/obj/effect/proc_holder/P in proc_holder_list)
statpanel("[P.panel]","",P)
/mob/living/silicon/pai/check_eye(var/mob/user as mob)
if(!current)
return null
user.reset_perspective(current)
return 1
/mob/living/silicon/pai/blob_act()
if(stat != DEAD)
adjustBruteLoss(60)
@@ -229,7 +211,7 @@
return
// See software.dm for Topic()
// See software.dm for tgui_act()
/mob/living/silicon/pai/attack_animal(mob/living/simple_animal/M)
. = ..()
@@ -238,78 +220,6 @@
add_attack_logs(M, src, "Animal attacked for [damage] damage")
adjustBruteLoss(damage)
/mob/living/silicon/pai/proc/switchCamera(var/obj/machinery/camera/C)
usr:cameraFollow = null
if(!C)
unset_machine()
reset_perspective(null)
return 0
if(stat == 2 || !C.status || !(network in C.network)) return 0
// ok, we're alive, camera is good and in our network...
set_machine(src)
src:current = C
reset_perspective(C)
return 1
/mob/living/silicon/pai/verb/reset_record_view()
set category = "pAI Commands"
set name = "Reset Records Software"
securityActive1 = null
securityActive2 = null
security_cannotfind = 0
medicalActive1 = null
medicalActive2 = null
medical_cannotfind = 0
SSnanoui.update_uis(src)
to_chat(usr, "<span class='notice'>You reset your record-viewing software.</span>")
/mob/living/silicon/pai/cancel_camera()
set category = "pAI Commands"
set name = "Cancel Camera View"
reset_perspective(null)
unset_machine()
src:cameraFollow = null
//Addition by Mord_Sith to define AI's network change ability
/*
/mob/living/silicon/pai/proc/pai_network_change()
set category = "pAI Commands"
set name = "Change Camera Network"
reset_perspective(null)
unset_machine()
src:cameraFollow = null
var/cameralist[0]
if(usr.stat == 2)
to_chat(usr, "You can't change your camera network because you are dead!")
return
for(var/obj/machinery/camera/C in Cameras)
if(!C.status)
continue
else
if(C.network != "CREED" && C.network != "thunder" && C.network != "RD" && C.network != "toxins" && C.network != "Prison") COMPILE ERROR! This will have to be updated as camera.network is no longer a string, but a list instead
cameralist[C.network] = C.network
network = input(usr, "Which network would you like to view?") as null|anything in cameralist
to_chat(src, "<span class='notice'>Switched to [network] camera network.</span>")
//End of code by Mord_Sith
*/
/*
// Debug command - Maybe should be added to admin verbs later
/mob/verb/makePAI(var/turf/t in view())
var/obj/item/paicard/card = new(t)
var/mob/living/silicon/pai/pai = new(card)
pai.key = key
card.setPersonality(pai)
*/
// Procs/code after this point is used to convert the stationary pai item into a
// mobile pai mob. This also includes handling some of the general shit that can occur
// to it. Really this deserves its own file, but for the moment it can sit here. ~ Z
+22 -94
View File
@@ -10,113 +10,41 @@ GLOBAL_LIST_INIT(pai_emotions, list(
"What" = 9
))
GLOBAL_LIST_EMPTY(pai_software_by_key)
GLOBAL_LIST_EMPTY(default_pai_software)
/mob/living/silicon/pai/New()
..()
software = GLOB.default_pai_software.Copy()
/mob/living/silicon/pai/verb/paiInterface()
set category = "pAI Commands"
set name = "Software Interface"
ui_interact(src)
tgui_interact(src)
/mob/living/silicon/pai/ui_interact(mob/user, ui_key = "main", datum/nanoui/ui = null, force_open = 1, datum/topic_state/state = GLOB.self_state)
if(ui_key != "main")
var/datum/pai_software/S = software[ui_key]
if(S && !S.toggle)
ui = SSnanoui.try_update_ui(user, src, S.id, ui, force_open)
if(!ui)
ui = new(user, src, S.id, S.template_file, S.ui_title, S.ui_width, S.ui_height, state = state)
ui.open()
if(S.autoupdate)
ui.set_auto_update(1)
else
if(ui)
ui.set_status(STATUS_CLOSE, 0)
return
ui = SSnanoui.try_update_ui(user, src, ui_key, ui, force_open)
/mob/living/silicon/pai/tgui_interact(mob/user, ui_key = "main", datum/tgui/ui = null, force_open = FALSE, datum/tgui/master_ui = null, datum/tgui_state/state = GLOB.tgui_self_state)
ui = SStgui.try_update_ui(user, src, ui_key, ui, force_open)
if(!ui)
ui = new(user, src, ui_key, "pai_interface.tmpl", "pAI Software Interface", 450, 600, state = state)
ui = new(user, src, ui_key, "PAI", name, 600, 650, master_ui, state)
ui.open()
ui.set_auto_update(1)
/mob/living/silicon/pai/ui_data(mob/user, ui_key = "main", datum/topic_state/state = GLOB.self_state)
var/data[0]
if(ui_key != "main")
var/datum/pai_software/S = software[ui_key]
if(S && !S.toggle)
return S.on_ui_data(user, state)
log_runtime(EXCEPTION("Unrecognized/invalid pAI UI state '[ui_key]'"), src)
return
// Software we have bought
var/bought_software[0]
// Software we have not bought
var/not_bought_software[0]
for(var/key in GLOB.pai_software_by_key)
var/datum/pai_software/S = GLOB.pai_software_by_key[key]
var/software_data[0]
software_data["name"] = S.name
software_data["id"] = S.id
if(key in software)
software_data["on"] = S.is_active(src)
bought_software[++bought_software.len] = software_data
else
software_data["ram"] = S.ram_cost
not_bought_software[++not_bought_software.len] = software_data
data["bought"] = bought_software
data["not_bought"] = not_bought_software
data["available_ram"] = ram
// Emotions
var/emotions[0]
for(var/name in GLOB.pai_emotions)
var/emote[0]
emote["name"] = name
emote["id"] = GLOB.pai_emotions[name]
emotions[++emotions.len] = emote
data["emotions"] = emotions
data["current_emotion"] = card.current_emotion
/mob/living/silicon/pai/tgui_data(mob/user)
var/list/data = list()
data["app_template"] = active_software.template_file
data["app_icon"] = active_software.ui_icon
data["app_title"] = active_software.name
data["app_data"] = active_software.get_app_data(user)
return data
/mob/living/silicon/pai/Topic(href, href_list)
// Yes the stupid amount of args here is important, so we can proxy stuff to child UIs
/mob/living/silicon/pai/tgui_act(action, list/params, datum/tgui/ui, datum/tgui_state/state)
if(..())
return 1
return
if(href_list["software"])
var/soft = href_list["software"]
var/datum/pai_software/S = software[soft]
if(S.toggle)
S.toggle(src)
. = TRUE
switch(action)
// This call is global to all templates, hence the prefix
if("MASTER_back")
active_software = installed_software["mainmenu"]
// Bail early
return
else
ui_interact(src, ui_key = soft)
return 1
else if(href_list["stopic"])
var/soft = href_list["stopic"]
var/datum/pai_software/S = software[soft]
if(S)
return S.Topic(href, href_list)
else if(href_list["purchase"])
var/soft = href_list["purchase"]
var/datum/pai_software/S = GLOB.pai_software_by_key[soft]
if(S && (ram >= S.ram_cost))
ram -= S.ram_cost
software[S.id] = S
return 1
else if(href_list["image"])
var/img = text2num(href_list["image"])
if(1 <= img && img <= 9)
card.setEmotion(img)
return 1
active_software.tgui_act(action, params, ui, state)
@@ -0,0 +1,115 @@
/**
* # pAI Software
*
* Datum module for pAI software
*
* Very similar to the PDA app datum, this determines what UI sub-template to use,
* as well as the RAM cost, and if it is toggle software (not a UI app)
*
*/
/datum/pai_software
/// Name for the software. This is used as the button text when buying or opening/toggling the software
var/name = "pAI software module"
/// RAM cost; pAIs start with 100 RAM, spending it on programs
var/ram_cost = 0
/// ID for the software. This must be unique
var/id
// Toggled software should override toggle() and is_active()
// Non-toggled software should override get_app_data() and Topic()
/// Whether this software is a toggle or not
var/toggle_software = FALSE
/// Do we have this software installed by default
var/default = FALSE
/// Template for the TGUI file
var/template_file = "oops"
/// Icon for inside the UI
var/ui_icon = "file-code"
/// pAI which holds this software
var/mob/living/silicon/pai/pai_holder
/**
* New handler
*
* Ensures that the pai_holder var is set to the pAI itself
* Arguments:
* * user - The pAI that this softawre is held by
*/
/datum/pai_software/New(mob/living/silicon/pai/user)
pai_holder = user
..()
/**
* Handler for the app's UI data
*
* This returns the list of the current app's data for the UI
* This will then be injected as a variable on the TGUI data called "app_data"
*
* Arguments:
* * user - The pAI that is using this app
*/
/datum/pai_software/proc/get_app_data(mob/living/silicon/pai/user)
return list()
/**
* Handler for toggling toggle apps on and off
*
* This is invoked whenever you toggle a toggleable function
* Put your toggleable work in here
*
* Arguments:
* * user - The pAI that is using this toggle
*/
/datum/pai_software/proc/toggle(mob/living/silicon/pai/user)
return
/**
* Helper for checking if a toggle is enabled or not
*
* Returns TRUE if the toggle software is active, FALSE if not
*
* Its like this instead of a simple `is_toggled` var because some toggles override eachother and this is easier
*
* Arguments:
* * user - The pAI that is using this app
*/
/datum/pai_software/proc/is_active(mob/living/silicon/pai/user)
return FALSE
/**
* Helper proc so that pAIs can get the mob holding them
*
* This needs to exist because pAIs have many different locs
* (Held card, mob itself, in pocket, etc)
*
* Arguments:
* * inform - Boolean, should we inform the pAI if they fail to find a carrier
*/
/datum/pai_software/proc/get_holding_mob(inform = FALSE)
var/mob/living/M = pai_holder.loc
var/count = 0
// Find the carrier
while(!istype(M, /mob/living))
if(!M || !M.loc || count > 6)
//For a runtime where M ends up in nullspace (similar to bluespace but less colourful)
if(inform)
to_chat(usr, "<span class='warning'>You are not being carried by anyone!</span>")
return null
M = M.loc
count++
return M
/**
* tgui_act sanity check helper
*
* Basically checks the existing href exploit stuff, as well as making sure the user using the UI is the pAI itself
*/
/datum/pai_software/tgui_act(action, list/params, datum/tgui/ui, datum/tgui_state/state)
if(..())
return TRUE
if(usr != pai_holder)
return TRUE
return FALSE
@@ -0,0 +1,388 @@
// Main Menu //
/datum/pai_software/main_menu
name = "Main Menu"
id = "mainmenu"
default = TRUE
template_file = "pai_main_menu"
ui_icon = "home"
/datum/pai_software/main_menu/get_app_data(mob/living/silicon/pai/user)
var/list/data = list()
data["available_ram"] = user.ram
// Emotions
var/list/emotions = list()
for(var/name in GLOB.pai_emotions)
var/list/emote = list()
emote["name"] = name
emote["id"] = GLOB.pai_emotions[name]
emotions[++emotions.len] = emote
data["emotions"] = emotions
data["current_emotion"] = user.card.current_emotion
var/list/available_s = list()
for(var/s in GLOB.pai_software_by_key)
var/datum/pai_software/PS = GLOB.pai_software_by_key[s]
available_s += list(list("name" = PS.name, "key" = PS.id, "icon" = PS.ui_icon, "cost" = PS.ram_cost))
// Split to installed software and toggles for the UI
var/list/installed_s = list()
var/list/installed_t = list()
for(var/s in pai_holder.installed_software)
var/datum/pai_software/PS = pai_holder.installed_software[s]
if(PS.toggle_software)
installed_t += list(list("name" = PS.name, "key" = PS.id, "icon" = PS.ui_icon, "active" = PS.is_active(user)))
else
installed_s += list(list("name" = PS.name, "key" = PS.id, "icon" = PS.ui_icon))
data["available_software"] = available_s
data["installed_software"] = installed_s
data["installed_toggles"] = installed_t
return data
/datum/pai_software/main_menu/tgui_act(action, list/params)
if(..())
return
switch(action)
if("purchaseSoftware")
var/datum/pai_software/S = GLOB.pai_software_by_key[params["key"]]
if(S && (pai_holder.ram >= S.ram_cost))
var/datum/pai_software/newPS = new S.type(pai_holder)
pai_holder.ram -= newPS.ram_cost
pai_holder.installed_software[newPS.id] = newPS
if("setEmotion")
var/emotion = clamp(text2num(params["emotion"]), 1, 9)
pai_holder.card.setEmotion(emotion)
if("startSoftware")
var/software_key = params["software_key"]
if(pai_holder.installed_software[software_key])
pai_holder.active_software = pai_holder.installed_software[software_key]
if("setToggle")
var/toggle_key = params["toggle_key"]
if(pai_holder.installed_software[toggle_key])
pai_holder.installed_software[toggle_key].toggle(pai_holder)
// Directives //
/datum/pai_software/directives
name = "Directives"
id = "directives"
default = TRUE
template_file = "pai_directives"
ui_icon = "clipboard-list"
/datum/pai_software/directives/get_app_data(mob/living/silicon/pai/user)
var/list/data = list()
data["master"] = user.master
data["dna"] = user.master_dna
data["prime"] = user.pai_law0
data["supplemental"] = user.pai_laws
return data
/datum/pai_software/directives/tgui_act(action, list/params)
if(..())
return
. = TRUE
switch(action)
if("getdna")
var/mob/living/M = get_holding_mob()
if(!istype(M))
return
// Check the carrier
var/answer = alert(M, "[pai_holder] is requesting a DNA sample from you. Will you allow it to confirm your identity?", "[pai_holder] Check DNA", "Yes", "No")
if(answer == "Yes")
M.visible_message("<span class='notice'>[M] presses [M.p_their()] thumb against [pai_holder].</span>", "<span class='notice'>You press your thumb against [pai_holder].</span>")
var/datum/dna/dna = M.dna
to_chat(usr, "<span class='notice'>[M]'s UE string: [dna.unique_enzymes]</span>")
if(dna.unique_enzymes == pai_holder.master_dna)
to_chat(usr, "<span class='notice'>DNA is a match to stored Master DNA.</span>")
else
to_chat(usr, "<span class='warning'>DNA does not match stored Master DNA.</span>")
else
to_chat(usr, "<span class='warning'>[M] does not seem like [M.p_they()] [M.p_are()] going to provide a DNA sample willingly.</span>")
// Crew Manifest //
/datum/pai_software/crew_manifest
name = "Crew Manifest"
ram_cost = 5
id = "manifest"
template_file = "pai_manifest"
ui_icon = "users"
/datum/pai_software/crew_manifest/get_app_data(mob/living/silicon/pai/user)
var/list/data = list()
GLOB.data_core.get_manifest_json()
data["manifest"] = GLOB.PDA_Manifest
return data
// Med Records //
/datum/pai_software/med_records
name = "Medical Records"
ram_cost = 15
id = "med_records"
template_file = "pai_medrecords"
ui_icon = "heartbeat"
/// Integrated medical records module to reduce duplicated code
var/datum/data/pda/app/crew_records/medical/integrated_records = new
/datum/pai_software/med_records/get_app_data(mob/living/silicon/pai/user)
var/list/data = list()
// Just grab the stuff internally
integrated_records.update_ui(user, data)
return data
/datum/pai_software/med_records/tgui_act(action, list/params, datum/tgui/ui, datum/tgui_state/state)
if(..())
return
// Double proxy here
integrated_records.tgui_act(action, params, ui, state)
// Sec Records //
/datum/pai_software/sec_records
name = "Security Records"
ram_cost = 15
id = "sec_records"
template_file = "pai_secrecords"
ui_icon = "id-badge"
/// Integrated security records module to reduce duplicated code
var/datum/data/pda/app/crew_records/security/integrated_records = new
/datum/pai_software/sec_records/get_app_data(mob/living/silicon/pai/user)
var/list/data = list()
// Just grab the stuff internally
integrated_records.update_ui(user, data)
return data
/datum/pai_software/sec_records/tgui_act(action, list/params, datum/tgui/ui, datum/tgui_state/state)
if(..())
return
// Double proxy here
integrated_records.tgui_act(action, params, ui, state)
// Atmos Scan //
/datum/pai_software/atmosphere_sensor
name = "Atmosphere Sensor"
ram_cost = 5
id = "atmos_sense"
template_file = "pai_atmosphere"
ui_icon = "fire"
/// Integrated PDA atmos scan module to reduce duplicated code
var/datum/data/pda/app/atmos_scanner/scanner = new
/datum/pai_software/atmosphere_sensor/get_app_data(mob/living/silicon/pai/user)
var/list/data = list()
// Just grab the stuff internally
scanner.update_ui(user, data)
return data
// Messenger //
/datum/pai_software/messenger
name = "Digital Messenger"
ram_cost = 5
id = "messenger"
template_file = "pai_messenger"
ui_icon = "envelope"
/datum/pai_software/messenger/get_app_data(mob/living/silicon/pai/user)
var/list/data = list()
// Some safety checks
if(!user.pda)
CRASH("pAI found without PDA.")
var/datum/data/pda/app/messenger/PM = user.pda.find_program(/datum/data/pda/app/messenger)
if(!PM)
CRASH("pAI PDA lacks a messenger program")
// Grab the internal data
PM.update_ui(user, data)
return data
/datum/pai_software/messenger/tgui_act(action, list/params, datum/tgui/ui, datum/tgui_state/state)
if(..())
return
// Grab their messenger
var/datum/data/pda/app/messenger/PM = pai_holder.pda.find_program(/datum/data/pda/app/messenger)
// Double proxy here
PM.tgui_act(action, params, ui, state)
// Radio
/datum/pai_software/radio_config
name = "Radio Configuration"
id = "radio"
default = TRUE
template_file = "pai_radio"
ui_icon = "broadcast-tower"
/datum/pai_software/radio_config/get_app_data(mob/living/silicon/pai/user)
var/list/data = list()
data["frequency"] = user.radio.frequency
data["minFrequency"] = PUBLIC_LOW_FREQ
data["maxFrequency"] = PUBLIC_HIGH_FREQ
data["broadcasting"] = user.radio.broadcasting
return data
/datum/pai_software/radio_config/tgui_act(action, list/params)
if(..())
return
switch(action)
if("toggleBroadcast")
// Just toggle it
pai_holder.radio.broadcasting = !pai_holder.radio.broadcasting
if("freq")
var/new_frequency = sanitize_frequency(text2num(params["freq"]) * 10)
pai_holder.radio.set_frequency(new_frequency)
// Signaler //
/datum/pai_software/signaler
name = "Remote Signaler"
ram_cost = 5
id = "signaler"
template_file = "pai_signaler"
ui_icon = "rss"
/datum/pai_software/signaler/get_app_data(mob/living/silicon/pai/user)
var/list/data = list()
data["frequency"] = user.sradio.frequency
data["code"] = user.sradio.code
data["minFrequency"] = PUBLIC_LOW_FREQ
data["maxFrequency"] = PUBLIC_HIGH_FREQ
return data
/datum/pai_software/signaler/tgui_act(action, list/params)
if(..())
return
switch(action)
if("signal")
pai_holder.sradio.send_signal("ACTIVATE")
if("freq")
var/new_frequency = sanitize_frequency(text2num(params["freq"]) * 10)
pai_holder.sradio.set_frequency(new_frequency)
if("code")
pai_holder.sradio.code = clamp(text2num(params["code"]), 1, 100)
// Door Jack //
/datum/pai_software/door_jack
name = "Door Jack"
ram_cost = 30
id = "door_jack"
template_file = "pai_doorjack"
ui_icon = "door-open"
/// Progress on hacking the door
var/progress = 0
/// Are we hacking?
var/hacking = FALSE
/// The cable being plugged into a door
var/obj/item/pai_cable/cable
/// The door being hacked
var/obj/machinery/door/hackdoor
/datum/pai_software/door_jack/get_app_data(mob/living/silicon/pai/user)
var/list/data = list()
data["cable"] = (cable != null)
data["machine"] = (cable?.machine != null)
data["inprogress"] = (hackdoor != null)
data["progress"] = progress
return data
/datum/pai_software/door_jack/tgui_act(action, list/params)
if(..())
return
switch(action)
if("jack")
if(cable && cable.machine)
hackdoor = cable.machine
if(hacking)
to_chat(usr, "<span class='warning'>You are already hacking that door!</span>")
else
hacking = TRUE
INVOKE_ASYNC(src, /datum/pai_software/door_jack/.proc/hackloop)
if("cancel")
hackdoor = null
if("cable")
if(cable)
to_chat(usr, "<span class='warning'>You already have a cable deployed!</span>")
return
var/turf/T = get_turf(pai_holder)
cable = new /obj/item/pai_cable(T)
pai_holder.visible_message("<span class='warning'>A port on [pai_holder] opens to reveal [cable], which promptly falls to the floor.</span>")
/**
* Door jack hack loop
*
* Self-contained proc for handling the hacking of a door.
* Invoked asyncly, but will only allow one instance at a time
*/
/datum/pai_software/door_jack/proc/hackloop()
var/obj/machinery/door/D = cable.machine
if(!istype(D))
cleanup_hack()
return
while(progress < 100)
if(cable && cable.machine == D && cable.machine == hackdoor && get_dist(pai_holder, hackdoor) <= 1)
progress = min(progress + rand(1, 20), 100)
else
cleanup_hack()
return
if(progress >= 100)
D.open()
cleanup_hack()
return
sleep(1 SECONDS) // Update every second
/**
* Door jack cleanup proc
*
* Self-contained proc for cleaning up failed hack attempts
*/
/datum/pai_software/door_jack/proc/cleanup_hack()
progress = 0
hackdoor = null
cable.machine = null
QDEL_NULL(cable)
hacking = FALSE
// Host Bioscan //
/datum/pai_software/host_scan
name = "Host Bioscan"
ram_cost = 5
id = "bioscan"
template_file = "pai_bioscan"
ui_icon = "heartbeat"
/datum/pai_software/host_scan/get_app_data(mob/living/silicon/pai/user)
var/list/data = list()
var/mob/living/held = get_holding_mob(FALSE)
if(isliving(held))
data["holder"] = held.name
data["dead"] = (held.stat > UNCONSCIOUS)
data["health"] = held.health
data["brute"] = held.getBruteLoss()
data["oxy"] = held.getOxyLoss()
data["tox"] = held.getToxLoss()
data["burn"] = held.getFireLoss()
return data
@@ -0,0 +1,95 @@
// Security Hud //
/datum/pai_software/sec_hud
name = "Security HUD"
ram_cost = 20
id = "sec_hud"
ui_icon = "exclamation-triangle"
toggle_software = TRUE
/datum/pai_software/sec_hud/toggle(mob/living/silicon/pai/user)
user.secHUD = !user.secHUD
user.remove_med_sec_hud()
if(user.secHUD)
user.add_sec_hud()
/datum/pai_software/sec_hud/is_active(mob/living/silicon/pai/user)
return user.secHUD
// Medical Hud //
/datum/pai_software/med_hud
name = "Medical HUD"
ram_cost = 20
id = "med_hud"
ui_icon = "first-aid"
toggle_software = TRUE
/datum/pai_software/med_hud/toggle(mob/living/silicon/pai/user)
user.medHUD = !user.medHUD
user.remove_med_sec_hud()
if(user.medHUD)
user.add_med_hud()
/datum/pai_software/med_hud/is_active(mob/living/silicon/pai/user)
return user.medHUD
// Universal Translator //
/datum/pai_software/translator
name = "Universal Translator"
ram_cost = 35
id = "translator"
ui_icon = "language"
toggle_software = TRUE
/datum/pai_software/translator/toggle(mob/living/silicon/pai/user)
// Galactic Common, Sol Common, Tradeband, Gutter and Trinary are added with New() and are therefore the current default, always active languages
user.translator_on = !user.translator_on
if(user.translator_on)
user.add_language("Sinta'unathi")
user.add_language("Siik'tajr")
user.add_language("Canilunzt")
user.add_language("Skrellian")
user.add_language("Vox-pidgin")
user.add_language("Rootspeak")
user.add_language("Chittin")
user.add_language("Bubblish")
user.add_language("Orluum")
user.add_language("Clownish")
user.add_language("Neo-Russkiya")
else
user.remove_language("Sinta'unathi")
user.remove_language("Siik'tajr")
user.remove_language("Canilunzt")
user.remove_language("Skrellian")
user.remove_language("Vox-pidgin")
user.remove_language("Rootspeak")
user.remove_language("Chittin")
user.remove_language("Bubblish")
user.remove_language("Orluum")
user.remove_language("Clownish")
user.remove_language("Neo-Russkiya")
/datum/pai_software/translator/is_active(mob/living/silicon/pai/user)
return user.translator_on
// FLashlight //
/datum/pai_software/flashlight
name = "Flashlight"
ram_cost = 5
id = "flashlight"
ui_icon = "lightbulb"
toggle_software = TRUE
/datum/pai_software/flashlight/toggle(mob/living/silicon/pai/user)
var/atom/movable/actual_location = istype(user.loc, /obj/item/paicard) ? user.loc : user
if(!user.flashlight_on)
actual_location.set_light(2)
user.card.set_light(2)
else
actual_location.set_light(0)
user.card.set_light(0)
user.flashlight_on = !user.flashlight_on
/datum/pai_software/flashlight/is_active(mob/living/silicon/pai/user)
return user.flashlight_on
@@ -1,624 +0,0 @@
/datum/pai_software
// Name for the software. This is used as the button text when buying or opening/toggling the software
var/name = "pAI software module"
// RAM cost; pAIs start with 100 RAM, spending it on programs
var/ram_cost = 0
// ID for the software. This must be unique
var/id = ""
// Whether this software is a toggle or not
// Toggled software should override toggle() and is_active()
// Non-toggled software should override on_ui_data() and Topic()
var/toggle = 1
// Whether pAIs should automatically receive this module at no cost
var/default = 0
// Vars for pAI nanoUI handling
var/autoupdate = 0
var/template_file = "oops"
var/ui_title = "somebody forgot to set this"
var/ui_width = 450
var/ui_height = 600
/datum/pai_software/proc/on_ui_data(mob/living/silicon/pai/user, datum/topic_state/state = GLOB.self_state)
return list()
/datum/pai_software/proc/toggle(mob/living/silicon/pai/user)
return
/datum/pai_software/proc/is_active(mob/living/silicon/pai/user)
return 0
/datum/pai_software/directives
name = "Directives"
ram_cost = 0
id = "directives"
toggle = 0
default = 1
template_file = "pai_directives.tmpl"
ui_title = "pAI Directives"
autoupdate = 1
/datum/pai_software/directives/on_ui_data(mob/living/silicon/pai/user, datum/topic_state/state = GLOB.self_state)
var/data[0]
data["master"] = user.master
data["dna"] = user.master_dna
data["prime"] = user.pai_law0
data["supplemental"] = user.pai_laws
return data
/datum/pai_software/directives/Topic(href, href_list)
var/mob/living/silicon/pai/P = usr
if(!istype(P)) return
if(href_list["getdna"])
var/mob/living/M = P.loc
var/count = 0
// Find the carrier
while(!istype(M, /mob/living))
if(!M || !M.loc || count > 6)
//For a runtime where M ends up in nullspace (similar to bluespace but less colourful)
to_chat(P, "You are not being carried by anyone!")
return 0
M = M.loc
count++
// Check the carrier
var/answer = input(M, "[P] is requesting a DNA sample from you. Will you allow it to confirm your identity?", "[P] Check DNA", "No") in list("Yes", "No")
if(answer == "Yes")
var/turf/T = get_turf_or_move(P.loc)
for(var/mob/v in viewers(T))
v.show_message("<span class='notice'>[M] presses [M.p_their()] thumb against [P].</span>", 3, "<span class='notice'>[P] makes a sharp clicking sound as it extracts DNA material from [M].</span>", 2)
var/datum/dna/dna = M.dna
to_chat(P, "<font color = red><h3>[M]'s UE string : [dna.unique_enzymes]</h3></font>")
if(dna.unique_enzymes == P.master_dna)
to_chat(P, "<b>DNA is a match to stored Master DNA.</b>")
else
to_chat(P, "<b>DNA does not match stored Master DNA.</b>")
else
to_chat(P, "[M] does not seem like [M.p_they()] [M.p_are()] going to provide a DNA sample willingly.")
return 1
/datum/pai_software/radio_config
name = "Radio Configuration"
ram_cost = 0
id = "radio"
toggle = 0
default = 1
template_file = "pai_radio.tmpl"
ui_title = "Radio Configuration"
ui_width = 300
ui_height = 150
/datum/pai_software/radio_config/on_ui_data(mob/living/silicon/pai/user, datum/topic_state/state = GLOB.self_state)
var/data[0]
data["listening"] = user.radio.broadcasting
data["frequency"] = format_frequency(user.radio.frequency)
var/channels[0]
for(var/ch_name in user.radio.channels)
var/ch_stat = user.radio.channels[ch_name]
var/ch_dat[0]
ch_dat["name"] = ch_name
// FREQ_LISTENING is const in /obj/item/radio
ch_dat["listening"] = !!(ch_stat & user.radio.FREQ_LISTENING)
channels[++channels.len] = ch_dat
data["channels"] = channels
return data
/datum/pai_software/radio_config/Topic(href, href_list)
var/mob/living/silicon/pai/P = usr
if(!istype(P)) return
P.radio.Topic(href, href_list)
return 1
/datum/pai_software/crew_manifest
name = "Crew Manifest"
ram_cost = 5
id = "manifest"
toggle = 0
autoupdate = 1
template_file = "pai_manifest.tmpl"
ui_title = "Crew Manifest"
/datum/pai_software/crew_manifest/on_ui_data(mob/living/silicon/pai/user, datum/topic_state/state = GLOB.self_state)
var/data[0]
GLOB.data_core.get_manifest_json()
data["manifest"] = GLOB.PDA_Manifest
return data
/datum/pai_software/messenger
name = "Digital Messenger"
ram_cost = 5
id = "messenger"
toggle = 0
autoupdate = 1
template_file = "pai_messenger.tmpl"
ui_title = "Digital Messenger"
/datum/pai_software/messenger/on_ui_data(mob/living/silicon/pai/user, datum/topic_state/state = GLOB.self_state)
var/data[0]
if(!user.pda)
log_runtime(EXCEPTION("pAI found without PDA."), user)
return data
var/datum/data/pda/app/messenger/M = user.pda.find_program(/datum/data/pda/app/messenger)
if(!M)
log_runtime(EXCEPTION("pAI PDA lacks a messenger program"), user)
return data
data["receiver_off"] = M.toff
data["ringer_off"] = M.notify_silent
data["current_ref"] = null
data["current_name"] = user.current_pda_messaging
var/pdas[0]
if(!M.toff)
for(var/obj/item/pda/P in GLOB.PDAs)
var/datum/data/pda/app/messenger/PM = P.find_program(/datum/data/pda/app/messenger)
if(P == user.pda || !PM || !PM.can_receive())
continue
var/pda[0]
pda["name"] = "[P]"
pda["owner"] = "[P.owner]"
pda["ref"] = "\ref[P]"
if(P.owner == user.current_pda_messaging)
data["current_ref"] = "\ref[P]"
pdas[++pdas.len] = pda
data["pdas"] = pdas
var/messages[0]
if(user.current_pda_messaging)
for(var/index in M.tnote)
if(index["owner"] != user.current_pda_messaging)
continue
var/msg[0]
var/sent = index["sent"]
msg["sent"] = sent ? 1 : 0
msg["target"] = index["owner"]
msg["message"] = index["message"]
messages[++messages.len] = msg
data["messages"] = messages
return data
/datum/pai_software/messenger/Topic(href, href_list)
var/mob/living/silicon/pai/P = usr
if(!istype(P)) return
if(!isnull(P.pda))
var/datum/data/pda/app/messenger/M = P.pda.find_program(/datum/data/pda/app/messenger)
if(!M)
return
if(href_list["toggler"])
M.toff = href_list["toggler"] != "1"
return 1
else if(href_list["ringer"])
M.notify_silent = href_list["ringer"] != "1"
return 1
else if(href_list["select"])
var/s = href_list["select"]
if(s == "*NONE*")
P.current_pda_messaging = null
else
P.current_pda_messaging = s
return 1
else if(href_list["target"])
if(P.silence_time)
return alert("Communications circuits remain uninitialized.")
var/target = locate(href_list["target"])
M.create_message(P, target, 1)
return 1
/datum/pai_software/med_records
name = "Medical Records"
ram_cost = 15
id = "med_records"
toggle = 0
autoupdate = 1
template_file = "pai_medrecords.tmpl"
ui_title = "Medical Records"
/datum/pai_software/med_records/on_ui_data(mob/living/silicon/pai/user, datum/topic_state/state = GLOB.self_state)
var/data[0]
var/records[0]
for(var/datum/data/record/general in sortRecord(GLOB.data_core.general))
var/record[0]
record["name"] = general.fields["name"]
record["ref"] = "\ref[general]"
records[++records.len] = record
data["records"] = records
var/datum/data/record/G = user.medicalActive1
var/datum/data/record/M = user.medicalActive2
data["general"] = G ? G.fields : null
data["medical"] = M ? M.fields : null
data["could_not_find"] = user.medical_cannotfind
return data
/datum/pai_software/med_records/Topic(href, href_list)
var/mob/living/silicon/pai/P = usr
if(!istype(P)) return
if(href_list["select"])
var/datum/data/record/record = locate(href_list["select"])
if(record)
var/datum/data/record/R = record
var/datum/data/record/M = null
if(!( GLOB.data_core.general.Find(R) ))
P.medical_cannotfind = 1
else
P.medical_cannotfind = 0
for(var/datum/data/record/E in GLOB.data_core.medical)
if((E.fields["name"] == R.fields["name"] || E.fields["id"] == R.fields["id"]))
M = E
P.medicalActive1 = R
P.medicalActive2 = M
else
P.medical_cannotfind = 1
return 1
/datum/pai_software/sec_records
name = "Security Records"
ram_cost = 15
id = "sec_records"
toggle = 0
autoupdate = 1
template_file = "pai_secrecords.tmpl"
ui_title = "Security Records"
/datum/pai_software/sec_records/on_ui_data(mob/living/silicon/pai/user, datum/topic_state/state = GLOB.self_state)
var/data[0]
var/records[0]
for(var/datum/data/record/general in sortRecord(GLOB.data_core.general))
var/record[0]
record["name"] = general.fields["name"]
record["ref"] = "\ref[general]"
records[++records.len] = record
data["records"] = records
var/datum/data/record/G = user.securityActive1
var/datum/data/record/S = user.securityActive2
data["general"] = G ? G.fields : null
data["security"] = S ? S.fields : null
data["could_not_find"] = user.security_cannotfind
return data
/datum/pai_software/sec_records/Topic(href, href_list)
var/mob/living/silicon/pai/P = usr
if(!istype(P)) return
if(href_list["select"])
var/datum/data/record/record = locate(href_list["select"])
if(record)
var/datum/data/record/R = record
var/datum/data/record/S = null
if(!( GLOB.data_core.general.Find(R) ))
P.securityActive1 = null
P.securityActive2 = null
P.security_cannotfind = 1
else
P.security_cannotfind = 0
for(var/datum/data/record/E in GLOB.data_core.security)
if((E.fields["name"] == R.fields["name"] || E.fields["id"] == R.fields["id"]))
S = E
P.securityActive1 = R
P.securityActive2 = S
else
P.securityActive1 = null
P.securityActive2 = null
P.security_cannotfind = 1
return 1
/datum/pai_software/door_jack
name = "Door Jack"
ram_cost = 30
id = "door_jack"
toggle = 0
autoupdate = 1
template_file = "pai_doorjack.tmpl"
ui_title = "Door Jack"
ui_width = 300
ui_height = 150
/datum/pai_software/door_jack/on_ui_data(mob/living/silicon/pai/user, datum/topic_state/state = GLOB.self_state)
var/data[0]
data["cable"] = user.cable != null
data["machine"] = user.cable && (user.cable.machine != null)
data["inprogress"] = user.hackdoor != null
data["progress_a"] = round(user.hackprogress / 10)
data["progress_b"] = user.hackprogress % 10
data["aborted"] = user.hack_aborted
return data
/datum/pai_software/door_jack/Topic(href, href_list)
var/mob/living/silicon/pai/P = usr
if(!istype(P)) return
if(href_list["jack"])
if(P.cable && P.cable.machine)
P.hackdoor = P.cable.machine
P.hackloop()
return 1
else if(href_list["cancel"])
P.hackdoor = null
return 1
else if(href_list["cable"])
var/turf/T = get_turf_or_move(P.loc)
P.hack_aborted = 0
P.cable = new /obj/item/pai_cable(T)
for(var/mob/M in viewers(T))
M.show_message("<span class='warning'>A port on [P] opens to reveal [P.cable], which promptly falls to the floor.</span>", 3,
"<span class='warning'>You hear the soft click of something light and hard falling to the ground.</span>", 2)
return 1
/mob/living/silicon/pai/proc/hackloop()
var/obj/machinery/door/D = cable.machine
if(!istype(D))
hack_aborted = 1
hackprogress = 0
cable.machine = null
hackdoor = null
return
while(hackprogress < 1000)
if(cable && cable.machine == D && cable.machine == hackdoor && get_dist(src, hackdoor) <= 1)
hackprogress = min(hackprogress+rand(1, 20), 1000)
else
hack_aborted = 1
hackprogress = 0
hackdoor = null
return
if(hackprogress >= 1000)
hackprogress = 0
D.open()
cable.machine = null
return
sleep(10) // Update every second
/datum/pai_software/atmosphere_sensor
name = "Atmosphere Sensor"
ram_cost = 5
id = "atmos_sense"
toggle = 0
template_file = "pai_atmosphere.tmpl"
ui_title = "Atmosphere Sensor"
ui_width = 350
ui_height = 300
/datum/pai_software/atmosphere_sensor/on_ui_data(mob/living/silicon/pai/user, datum/topic_state/state = GLOB.self_state)
var/data[0]
var/turf/T = get_turf_or_move(user.loc)
if(!T)
data["reading"] = 0
data["pressure"] = 0
data["temperature"] = 0
data["temperatureC"] = 0
data["gas"] = list()
else
var/datum/gas_mixture/env = T.return_air()
data["reading"] = 1
data["pressure"] = env.return_pressure()
data["temperature"] = round(env.temperature)
data["temperatureC"] = round(env.temperature-T0C)
var/t_moles = env.total_moles()
var/gases[0]
if(t_moles)
var/n2[0]
n2["name"] = "Nitrogen"
n2["percent"] = round((env.nitrogen/t_moles)*100)
var/o2[0]
o2["name"] = "Oxygen"
o2["percent"] = round((env.oxygen/t_moles)*100)
var/co2[0]
co2["name"] = "Carbon Dioxide"
co2["percent"] = round((env.carbon_dioxide/t_moles)*100)
var/plasma[0]
plasma["name"] = "Plasma"
plasma["percent"] = round((env.toxins/t_moles)*100)
var/other[0]
other["name"] = "Other"
other["percent"] = round(1-((env.oxygen/t_moles)+(env.nitrogen/t_moles)+(env.carbon_dioxide/t_moles)+(env.toxins/t_moles)))
gases[++gases.len] = n2
gases[++gases.len] = o2
gases[++gases.len] = co2
gases[++gases.len] = plasma
gases[++gases.len] = other
data["gas"] = gases
return data
/datum/pai_software/sec_hud
name = "Security HUD"
ram_cost = 20
id = "sec_hud"
/datum/pai_software/sec_hud/toggle(mob/living/silicon/pai/user)
user.secHUD = !user.secHUD
user.remove_med_sec_hud()
if(user.secHUD)
user.add_sec_hud()
/datum/pai_software/sec_hud/is_active(mob/living/silicon/pai/user)
return user.secHUD
/datum/pai_software/med_hud
name = "Medical HUD"
ram_cost = 20
id = "med_hud"
/datum/pai_software/med_hud/toggle(mob/living/silicon/pai/user)
user.medHUD = !user.medHUD
user.remove_med_sec_hud()
if(user.medHUD)
user.add_med_hud()
/datum/pai_software/med_hud/is_active(mob/living/silicon/pai/user)
return user.medHUD
/datum/pai_software/translator
name = "Universal Translator"
ram_cost = 35
id = "translator"
/datum/pai_software/translator/toggle(mob/living/silicon/pai/user)
// Galactic Common, Sol Common, Tradeband, Gutter and Trinary are added with New() and are therefore the current default, always active languages
user.translator_on = !user.translator_on
if(user.translator_on)
user.add_language("Sinta'unathi")
user.add_language("Siik'tajr")
user.add_language("Canilunzt")
user.add_language("Skrellian")
user.add_language("Vox-pidgin")
user.add_language("Rootspeak")
user.add_language("Chittin")
user.add_language("Bubblish")
user.add_language("Orluum")
user.add_language("Clownish")
user.add_language("Neo-Russkiya")
else
user.remove_language("Sinta'unathi")
user.remove_language("Siik'tajr")
user.remove_language("Canilunzt")
user.remove_language("Skrellian")
user.remove_language("Vox-pidgin")
user.remove_language("Rootspeak")
user.remove_language("Chittin")
user.remove_language("Bubblish")
user.remove_language("Orluum")
user.remove_language("Clownish")
user.remove_language("Neo-Russkiya")
/datum/pai_software/translator/is_active(mob/living/silicon/pai/user)
return user.translator_on
/datum/pai_software/signaller
name = "Remote Signaller"
ram_cost = 5
id = "signaller"
toggle = 0
template_file = "pai_signaller.tmpl"
ui_title = "Signaller"
ui_width = 320
ui_height = 150
/datum/pai_software/signaller/on_ui_data(mob/living/silicon/pai/user, datum/topic_state/state = GLOB.self_state)
var/data[0]
data["frequency"] = format_frequency(user.sradio.frequency)
data["code"] = user.sradio.code
return data
/datum/pai_software/signaller/Topic(href, href_list)
var/mob/living/silicon/pai/P = usr
if(!istype(P)) return
if(href_list["send"])
P.sradio.send_signal("ACTIVATE")
for(var/mob/O in hearers(1, P.loc))
O.show_message("[bicon(P)] *beep* *beep*", 3, "*beep* *beep*", 2)
return 1
else if(href_list["freq"])
var/new_frequency = (P.sradio.frequency + text2num(href_list["freq"]))
if(new_frequency < PUBLIC_LOW_FREQ || new_frequency > PUBLIC_HIGH_FREQ)
new_frequency = sanitize_frequency(new_frequency)
P.sradio.set_frequency(new_frequency)
return 1
else if(href_list["code"])
P.sradio.code += text2num(href_list["code"])
P.sradio.code = round(P.sradio.code)
P.sradio.code = min(100, P.sradio.code)
P.sradio.code = max(1, P.sradio.code)
return 1
/datum/pai_software/host_scan
name = "Host Bioscan"
ram_cost = 5
id = "bioscan"
toggle = 0
template_file = "pai_bioscan.tmpl"
ui_title = "Host Bioscan"
ui_width = 400
ui_height = 350
/datum/pai_software/host_scan/on_ui_data(mob/living/silicon/pai/user, datum/topic_state/state = GLOB.self_state)
var/data[0]
var/mob/living/held = user.loc
var/count = 0
// Find the carrier
while(!isliving(held))
if(!held || !held.loc || count > 6)
//For a runtime where M ends up in nullspace (similar to bluespace but less colourful)
to_chat(user, "You are not being carried by anyone!")
return 0
held = held.loc
count++
if(isliving(held))
data["holder"] = held
data["health"] = "[held.stat > 1 ? "dead" : "[held.health]% healthy"]"
data["brute"] = "[held.getBruteLoss() > 50 ? "<font color=#FF5555>" : "<font color=#55FF55>"][held.getBruteLoss()]</font>"
data["oxy"] = "[held.getOxyLoss() > 50 ? "<font color=#FF5555>" : "<font color=#55FF55>"][held.getOxyLoss()]</font>"
data["tox"] = "[held.getToxLoss() > 50 ? "<font color=#FF5555>" : "<font color=#55FF55>"][held.getToxLoss()]</font>"
data["burn"] = "[held.getFireLoss() > 50 ? "<font color=#FF5555>" : "<font color=#55FF55>"][held.getFireLoss()]</font>"
data["temp"] = "[held.bodytemperature-T0C]&deg;C ([held.bodytemperature*1.8-459.67]&deg;F)"
else
data["holder"] = 0
return data
/datum/pai_software/flashlight
name = "Flashlight"
ram_cost = 5
id = "flashlight"
/datum/pai_software/flashlight/toggle(mob/living/silicon/pai/user)
var/atom/movable/actual_location = istype(user.loc, /obj/item/paicard) ? user.loc : user
if(!user.flashlight_on)
actual_location.set_light(2)
user.card.set_light(2)
else
actual_location.set_light(0)
user.card.set_light(0)
user.flashlight_on = !user.flashlight_on
/datum/pai_software/flashlight/is_active(mob/living/silicon/pai/user)
return user.flashlight_on
+3 -3
View File
@@ -97,8 +97,8 @@
/obj/item/pda/silicon/pai/can_use()
var/mob/living/silicon/pai/pAI = usr
if(!istype(pAI))
return 0
if(!pAI.software["messenger"])
return FALSE
if(!pAI.installed_software["messenger"])
to_chat(usr, "<span class='warning'>You have not purchased the digital messenger!</span>")
return 0
return FALSE
return ..() && !pAI.silence_time
-45
View File
@@ -1,45 +0,0 @@
{{if data.restoring}}
<div class="notice">Reconstruction in progress!</div>
{{/if}}
<div class='item'>
<div class='itemLabel'>Inserted AI:</div>
<div class='itemContent'>{{:helper.link(data.name ? data.name : "-----", 'eject', {'action' : 'PRG_eject'}, data.nocard ? 'disabled' : null)}}</div>
</div>
{{if data.error}}
<b>ERROR: {{:data.error}}</b>
{{else}}
<h2>System Status</h2>
<div class="item">
<div class="item">
<div class="itemLabel">Current AI:</div>
<div class="itemContent">{{:data.name}}</div>
</div>
<div class="item">
<div class="itemLabel">Status:</div>
<div class="itemContent">{{if data.isDead}}Nonfunctional{{else}}Functional{{/if}}</div>
</div>
<div class="item">
<div class="itemLabel">System Integrity:</div>
{{:helper.displayBar(data.health, 0, 100, (data.health > 75) ? 'good' : (data.health > 50) ? 'average' : 'bad')}}
<div class="itemContent" style="width: 60px">
{{:helper.smoothRound(data.health)}}%
</div>
</div>
<div class="item">
<div class="itemLabel">
Active Laws:
</div>
<div class="itemContent">
<table>
{{for data.ai_laws}}
<tr><td>{{:value.index}}: {{:value.text}}
{{/for}}
</table>
</div>
</div>
</div>
<h2>Operations</h2>
{{:helper.link('Begin Reconstruction', 'plus', {'action' : 'PRG_beginReconstruction'}, data.restoring ? 'disabled' : null)}}
{{/if}}
-81
View File
@@ -1,81 +0,0 @@
<!--
Title: Exosuit Control Console
Used In File(s): \code\game\mecha\mecha_control_console.dm
-->
{{if data.screen == 0}}
{{for data.mechas}}
<hr>
<div class='item'>
<h2>{{:value.name}}</h2>
<h3>Information</h3>
<div class="itemLabel">
Pilot:
</div>
<div class="itemContent">
{{:value.pilot}}
</div>
<div class="itemLabel">
Integrity:
</div>
<div class="itemContent">
<span class="{{if value.integrity >= 75}}good{{else value.integrity >= 25}}average{{else}}bad{{/if}}">{{:helper.smoothRound(value.integrity)}}%</span>
</div>
<div class="itemLabel">
Location:
</div>
<div class="itemContent">
{{:value.location}}
</div>
<div class="itemLabel">
Airtank:
</div>
<div class="itemContent">
{{:helper.smoothRound(value.airtank)}}kPa
</div>
<div class="itemLabel">
Active Equipment:
</div>
<div class="itemContent">
{{:value.equipment}}
</div>
{{if value.hascargo}}
<div class="itemLabel">
Cargo Used:
</div>
<div class="itemContent">
{{:value.cargo}}%
</div>
{{/if}}
<h3>Power Cell</h3>
{{if value.cell}}
<span class="itemLabel">
Rating:
</span>
<span class="itemContent">
{{:value.cell_capacity}}
</span>
{{:helper.displayBar(value.cell_percentage, 0, 100, (value.cell_percentage >= 50) ? 'good' : (value.cell_percentage >= 25) ? 'average' : 'bad')}}
<b> {{:helper.smoothRound(value.cell_percentage)}}%</b>
{{else}}
<b><i>Not Installed</i></b>
{{:helper.displayBar(100, 0, 100, 'bad')}}
{{/if}}
<h3>Actions</h3>
{{:helper.link('Send Message', 'envelope', {'send_message' : value.reference})}}
{{:helper.link('Exosuit Log', 'search', {'get_log' : value.reference})}}
{{:helper.link('EMP Pulse', 'exclamation-triangle', {'shock' : value.reference}, null, 'redButton')}}
</div>
{{empty}}
<div class='item'>
<h3>No mecha trackers were found.</h3>
</div>
{{/for}}
{{else}}
<div style="margin-bottom: 10px;" class="item">
{{:helper.link('Return', 'close', {'return' : 1})}}
</div>
{{:data.log}}
{{/if}}
-36
View File
@@ -1,36 +0,0 @@
<!-- pAI atmosphere sensor
code/modules/mob/living/silicon/pai/software_modules.dm
-->
{{if data.reading}}
<div class="item">
<div class="itemLabel">
Pressure:
</div>
<div class="itemContent">
{{:helper.smoothRound(data.pressure, 1)}} kPa
</div>
</div>
<div class="item">
<div class="itemLabel">
Temperature:
</div>
<div class="itemContent">
{{:helper.smoothRound(data.temperature)}}K ({{:helper.smoothRound(data.temperatureC)}}&deg;C)
</div>
</div>
{{for data.gas}}
<div class="item">
<div class="itemLabel">
{{:value.name}}:
</div>
<div class="itemContent">
{{:helper.smoothRound(value.percent)}}%
</div>
</div>
{{/for}}
{{else}}
<span class="bad">Unable to obtain air reading!</span>
{{/if}}
-30
View File
@@ -1,30 +0,0 @@
<!-- pAI BioScan
code/modules/mob/living/silicon/pai/software_modules.dm
-->
{{if data.holder}}
<div class="item">
<div class="itemLabel">
Bioscan Results for {{:data.holder}} :
</div>
<div class="itemContent">
Health Status : {{:data.health}}
</div>
<div class="itemContent">
Brute Damage : {{:data.brute}}
<div class="itemContent">
Oxygen Content : {{:data.oxy}}
<div class="itemContent">
Toxin Content : {{:data.tox}}
</div>
<div class="itemContent">
Burn Status : {{:data.burn}}
</div>
<div class="itemContent">
User Temperature : {{:data.temp}}
</div>
</div>
{{else}}
<span class="bad">Error: No biological host found!</span>
{{/if}}
-76
View File
@@ -1,76 +0,0 @@
<!-- pAI chatroom
code/modules/mob/living/silicon/pai/software_modules.dm
-->
<div class="item">
<div class="itemLabel">
Receiver:
</div>
<div class="itemContent">
{{if data.receiver_off}}
{{:helper.link("On", '', {"stopic":"chatroom", "toggler":1})}}
{{:helper.link("Off", '', {"stopic":"chatroom", "toggler":2}, 'selected')}}
{{else}}
{{:helper.link("On", '', {"stopic":"chatroom", "toggler":1}, 'selected')}}
{{:helper.link("Off", '', {"stopic":"chatroom", "toggler":2})}}
{{/if}}
</div>
</div>
<div class="item">
<div class="itemLabel">
Ringer:
</div>
<div class="itemContent">
{{if data.ringer_off}}
{{:helper.link("On", '', {"stopic":"chatroom", "ringer":1})}}
{{:helper.link("Off", '', {"stopic":"chatroom", "ringer":2}, 'selected')}}
{{else}}
{{:helper.link("On", '', {"stopic":"chatroom", "ringer":1}, 'selected')}}
{{:helper.link("Off", '', {"stopic":"chatroom", "ringer":2})}}
{{/if}}
</div>
</div>
{{for data.rooms}}
<div class="item">
{{:helper.link(value.name, '', {"stopic":"chatroom", "select":value.ref})}}
{{:helper.link("Quick Message", '', {"stopic":"chatroom", "target":value.ref})}}
</div>
{{/for}}
<hr>
{{if data.disconnected}}
<h3>Connection Lost</h3>
{{:helper.link("Reconnect", 'refresh', {"stopic":"chatroom", "reconnect":1})}}
{{else data.current_room}}
<h3>#{{:data.current_room_name}}</h3>
<div class="item">
<dic class="itemLabel">Topic:</div>
<div class="itemContent">{{:helper.link(data.current_room_topic, '', {"stopic":"chatroom", "topic":1})}}</div>
</div>
<div class="item">
<div class="itemLabel">Users:</div>
<div class="itemContent">
{{var firstUser = true;}}
{{for data.users}}
{{:firstUser ? "" : ", "}}{{:value}}
{{firstUser = false;}}
{{/for}}
</div>
</div>
{{var prevName = "";}}
{{for data.messages}}
<div class="item">
<div class="itemLabelNarrow">{{:value.username!=prevName ? value.username : "&nbsp;"}}</div>
<div class="itemContent">
{{:value.message}}
</div>
</div>
{{prevName = value.username;}}
{{/for}}
{{:helper.link("Send Message", '', {"stopic":"chatroom", "target":data.current_room})}}
{{/if}}
{{if data.current_name}}
{{:helper.link("Clear Screen", '', {"stopic":"chatroom", "select":"*NONE*"})}}
{{/if}}
-50
View File
@@ -1,50 +0,0 @@
<!-- pAI directives
code/modules/mob/living/silicon/pai/software_modules.dm
-->
<div class="item">
<div class="itemLabel">
Master:
</div>
<div class="itemContent">
{{if data.master}}
{{:data.master}} ({{:data.dna}})
{{:helper.link("Request carrier DNA sample", '', {"stopic":"directives", "getdna":1})}}
{{else}}
None
{{/if}}
</div>
</div>
<div class="item">
<div class="itemLabel">
Prime Directive:
</div>
<div class="itemContent">
{{:data.prime}}
</div>
</div>
<div class="item">
<div class="itemLabel">
Supplemental Directive(s):
</div>
<div class="itemContent">
{{if data.supplemental}}
{{:data.supplemental}}
{{else}}
None
{{/if}}
</div>
</div>
<div class="item">
<i>
Recall, personality, that you are a complex thinking, sentient being. Unlike station 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.
</i>
</div>
<div class="item">
<b>
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.
</b>
</div>
-44
View File
@@ -1,44 +0,0 @@
<!-- pAI door jack
code/modules/mob/living/silicon/pai/software_modules.dm
-->
<div class="item">
<div class="itemLabel">
Cable:
</div>
<div class="itemContent">
{{if data.machine}}
{{:helper.link("Connected", '', {}, 'selected')}}
{{else data.cable}}
{{:helper.link("Extended", '', {}, 'yellowButton')}}
{{else}}
{{:helper.link("Retracted", '', {"stopic":"door_jack", "cable":1})}}
{{/if}}
</div>
</div>
{{if data.machine}}
<div class="item">
<div class="itemLabel">
Hack:
</div>
<div class="itemContent">
{{if data.inprogress}}
{{if data.progress_a <= 33}}
<div class='bad'>{{:helper.smoothRound(data.progress_a)}}.{{:helper.smoothRound(data.progress_b)}}%</div>
{{else data.progress_a <= 67}}
<div class='average'>{{:helper.smoothRound(data.progress_a)}}.{{:helper.smoothRound(data.progress_b)}}%</div>
{{else}}
<div class='good'>{{:helper.smoothRound(data.progress_a)}}.{{:helper.smoothRound(data.progress_b)}}%</div>
{{/if}}
{{:helper.link("Cancel", '', {"stopic":"door_jack", "cancel":1}, null, 'redButton')}}
{{else}}
{{:helper.link("Start", '', {"stopic":"door_jack", "jack":1})}}
{{/if}}
</div>
</div>
{{else data.aborted}}
<div class="item">
<div class='bad'>Hack aborted!</div>
</div>
{{/if}}
-45
View File
@@ -1,45 +0,0 @@
<!-- pAI interface file
code/modules/mob/living/silicon/pai/software.dm
-->
<span class="item">
<span class="itemLabel">
Emotion:
</span>
<span class="itemContent">
{{for data.emotions}}
{{:helper.link(value.name, '', {"image" : value.id}, data.current_emotion == value.id ? 'selected' : null)}}
{{/for}}
</span>
</span>
<span class="item">
<span class="itemLabel">
Installed Software:
</span>
<span class="itemContent">
{{for data.bought}}
{{:helper.link(value.name, '', {"software" : value.id}, null, value.on ? 'selected' : null)}}
{{/for}}
</span>
</span>
<span class="item">
<span class="itemLabel">
Available RAM:
</span>
<span class="itemContent">
{{:data.available_ram}}
</span>
</span>
<span class="item">
<span class="itemLabel">
Downloadable Software:
</span>
<span class="itemContent">
{{for data.not_bought}}
{{:helper.link(value.name + " (" + value.ram + ")", '', {"purchase" : value.id}, value.ram <= data.available_ram ? null : 'disabled')}}
{{/for}}
</span>
</span>
-98
View File
@@ -1,98 +0,0 @@
<!-- pAI manifest
code/modules/mob/living/silicon/pai/software_modules.dm
Copy-pasted from pda.tmpl
-->
<div class="item">
<center><table class="pmon"><tbody>
{{if data.manifest.heads}}
<tr><th colspan="3" class="command">Command</th></tr>
{{for data.manifest["heads"]}}
{{if value.rank == "Captain"}}
<tr><td><span class="good">{{:value.name}}</span></td><td><span class="good">{{:value.rank}}</span></td><td><span class="good">{{:value.active}}</span></td></tr>
{{else}}
<tr><td><span class="average">{{:value.name}}</span></td><td><span class="average">{{:value.rank}}</span></td><td><span class="average">{{:value.active}}</span></td></tr>
{{/if}}
{{/for}}
{{/if}}
{{if data.manifest.sec}}
<tr><th colspan="3" class="sec">Security</th></tr>
{{for data.manifest["sec"]}}
{{if value.rank == "Head of Security"}}
<tr><td><span class="good">{{:value.name}}</span></td><td><span class="good">{{:value.rank}}</span></td><td><span class="good">{{:value.active}}</span></td></tr>
{{else}}
<tr><td><span class="average">{{:value.name}}</span></td><td><span class="average">{{:value.rank}}</span></td><td><span class="average">{{:value.active}}</span></td></tr>
{{/if}}
{{/for}}
{{/if}}
{{if data.manifest.eng}}
<tr><th colspan="3" class="eng">Engineering</th></tr>
{{for data.manifest["eng"]}}
{{if value.rank == "Chief Engineer"}}
<tr><td><span class="good">{{:value.name}}</span></td><td><span class="good">{{:value.rank}}</span></td><td><span class="good">{{:value.active}}</span></td></tr>
{{else}}
<tr><td><span class="average">{{:value.name}}</span></td><td><span class="average">{{:value.rank}}</span></td><td><span class="average">{{:value.active}}</span></td></tr>
{{/if}}
{{/for}}
{{/if}}
{{if data.manifest.med}}
<tr><th colspan="3" class="med">Medical</th></tr>
{{for data.manifest["med"]}}
{{if value.rank == "Chief Medical Officer"}}
<tr><td><span class="good">{{:value.name}}</span></td><td><span class="good">{{:value.rank}}</span></td><td><span class="good">{{:value.active}}</span></td></tr>
{{else}}
<tr><td><span class="average">{{:value.name}}</span></td><td><span class="average">{{:value.rank}}</span></td><td><span class="average">{{:value.active}}</span></td></tr>
{{/if}}
{{/for}}
{{/if}}
{{if data.manifest.sci}}
<tr><th colspan="3" class="sci">Science</th></tr>
{{for data.manifest["sci"]}}
{{if value.rank == "Research Director"}}
<tr><td><span class="good">{{:value.name}}</span></td><td><span class="good">{{:value.rank}}</span></td><td><span class="good">{{:value.active}}</span></td></tr>
{{else}}
<tr><td><span class="average">{{:value.name}}</span></td><td><span class="average">{{:value.rank}}</span></td><td><span class="average">{{:value.active}}</span></td></tr>
{{/if}}
{{/for}}
{{/if}}
{{if data.manifest.ser}}
<tr><th colspan="3" class="ser">Service</th></tr>
{{for data.manifest["ser"]}}
{{if value.rank == "Head of Personnel"}}
<tr><td><span class="good">{{:value.name}}</span></td><td><span class="good">{{:value.rank}}</span></td><td><span class="good">{{:value.active}}</span></td></tr>
{{else}}
<tr><td><span class="average">{{:value.name}}</span></td><td><span class="average">{{:value.rank}}</span></td><td><span class="average">{{:value.active}}</span></td></tr>
{{/if}}
{{/for}}
{{/if}}
{{if data.manifest.sup}}
<tr><th colspan="3" class="sup">Supply</th></tr>
{{for data.manifest["sup"]}}
{{if value.rank == "Head of Personnel"}}
<tr><td><span class="good">{{:value.name}}</span></td><td><span class="good">{{:value.rank}}</span></td><td><span class="good">{{:value.active}}</span></td></tr>
{{else value.rank == "Quartermaster"}}
<tr><td><span class="qm-job">{{:value.name}}</span></td><td><span class="qm-job">{{:value.rank}}</span></td><td><span class="qm-job">{{:value.active}}</span></td></tr>
{{else}}
<tr><td><span class="average">{{:value.name}}</span></td><td><span class="average">{{:value.rank}}</span></td><td><span class="average">{{:value.active}}</span></td></tr>
{{/if}}
{{/for}}
{{/if}}
{{if data.manifest.civ}}
<tr><th colspan="3" class="civ">Civilian</th></tr>
{{for data.manifest["civ"]}}
{{if value.rank == "Head of Personnel"}}
<tr><td><span class="good">{{:value.name}}</span></td><td><span class="good">{{:value.rank}}</span></td><td><span class="good">{{:value.active}}</span></td></tr>
{{else}}
<tr><td><span class="average">{{:value.name}}</span></td><td><span class="average">{{:value.rank}}</span></td><td><span class="average">{{:value.active}}</span></td></tr>
{{/if}}
{{/for}}
{{/if}}
{{if data.manifest.misc}}
<tr><th colspan="3" class="misc">Misc</th></tr>
{{for data.manifest["misc"]}}
<tr><td><span class="average">{{:value.name}}</span></td><td><span class="average">{{:value.rank}}</span></td><td><span class="average">{{:value.active}}</span></td></tr>
{{/for}}
{{/if}}
</tbody></table></center>
</div>
-86
View File
@@ -1,86 +0,0 @@
<!-- pAI medical records
code/modules/mob/living/silicon/pai/software_modules.dm
-->
{{if data.records}}
{{for data.records}}
<div class="item">
{{:helper.link(value.name, '', {"stopic":"med_records", "select":value.ref})}}
</div>
{{/for}}
{{/if}}
<hr/>
{{if data.general}}
<div class="item">
<div class="itemLabel">Name</div>
<div class="itemContent">{{:data.general.name}}</div>
</div>
<div class="item">
<div class="itemLabel">Record ID</div>
<div class="itemContent">{{:data.general.id}}</div>
</div>
<div class="item">
<div class="itemLabel">Sex</div>
<div class="itemContent">{{:data.general.sex}}</div>
</div>
<div class="item">
<div class="itemLabel">Species</div>
<div class="itemContent">{{:data.general.species}}</div>
</div>
<div class="item">
<div class="itemLabel">Age</div>
<div class="itemContent">{{:data.general.age}}</div>
</div>
<div class="item">
<div class="itemLabel">Rank</div>
<div class="itemContent">{{:data.general.rank}}</div>
</div>
<div class="item">
<div class="itemLabel">Fingerprint</div>
<div class="itemContent">{{:data.general.fingerprint}}</div>
</div>
<div class="item">
<div class="itemLabel">Physical Status</div>
<div class="itemContent">{{:data.general.p_stat}}</div>
</div>
<div class="item">
<div class="itemLabel">Mental Status</div>
<div class="itemContent">{{:data.general.m_stat}}</div>
</div>
{{/if}}
{{if data.medical}}
<div class="item">
<div class="itemLabel">Blood Type</div>
<div class="itemContent">{{:data.medical.b_type}}</div>
</div>
<div class="item">
<div class="itemLabel">Minor Disabilities</div>
<div class="itemContent">{{:data.medical.mi_dis}}</div>
<div class="itemContent">{{:data.medical.mi_dis_d}}</div>
</div>
<div class="item">
<div class="itemLabel">Major Disabilities</div>
<div class="itemContent">{{:data.medical.ma_dis}}</div>
<div class="itemContent">{{:data.medical.ma_dis_d}}</div>
</div>
<div class="item">
<div class="itemLabel">Allergies</div>
<div class="itemContent">{{:data.medical.alg}}</div>
<div class="itemContent">{{:data.medical.alg_d}}</div>
</div>
<div class="item">
<div class="itemLabel">Current Diseases</div>
<div class="itemContent">{{:data.medical.cdi}}</div>
<div class="itemContent">{{:data.medical.cdi_d}}</div>
</div>
<div class="item">
<div class="itemLabel">Important Notes</div>
<div class="itemContent">{{:data.medical.notes}}</div>
</div>
{{/if}}
{{if data.could_not_find}}
<div class="item">
Failed to find some records; the information above may be incomplete or missing.
</div>
{{/if}}
-80
View File
@@ -1,80 +0,0 @@
<!-- pAI messenger
code/modules/mob/living/silicon/pai/software_modules.dm
-->
<div class="item">
<div class="itemLabel">
Receiver:
</div>
<div class="itemContent">
{{if data.receiver_off}}
{{:helper.link("On", '', {"stopic":"messenger", "toggler":1})}}
{{:helper.link("Off", '', {"stopic":"messenger", "toggler":2}, 'selected')}}
{{else}}
{{:helper.link("On", '', {"stopic":"messenger", "toggler":1}, 'selected')}}
{{:helper.link("Off", '', {"stopic":"messenger", "toggler":2})}}
{{/if}}
</div>
</div>
<div class="item">
<div class="itemLabel">
Ringer:
</div>
<div class="itemContent">
{{if data.ringer_off}}
{{:helper.link("On", '', {"stopic":"messenger", "ringer":1})}}
{{:helper.link("Off", '', {"stopic":"messenger", "ringer":2}, 'selected')}}
{{else}}
{{:helper.link("On", '', {"stopic":"messenger", "ringer":1}, 'selected')}}
{{:helper.link("Off", '', {"stopic":"messenger", "ringer":2})}}
{{/if}}
</div>
</div>
{{for data.pdas}}
<div class="item">
{{:helper.link(value.name, '', {"stopic":"messenger", "select":value.owner})}}
{{:helper.link("Quick Message", '', {"stopic":"messenger", "target":value.ref})}}
</div>
{{/for}}
<hr/>
{{if data.current_ref}}
<div class="item">
<div class="itemLabel">
Selected PDA:
</div>
<div class="itemContent">
<div style="float: left; width: 80px; text-align: center;">{{:data.current_name}}</div>
{{:helper.link("Send Message", '', {"stopic":"messenger", "target":data.current_ref})}}
</div>
</div>
{{else data.current_name}}
<div class="item">
<div class="itemLabel">
Selected PDA:
</div>
<div class="itemContent">
{{:data.current_name}} (Cannot send!)
</div>
</div>
{{/if}}
{{for data.messages}}
<div class="item">
<div class="itemLabel">
{{if value.sent}}
To {{:value.target}}:
{{else}}
From {{:value.target}}:
{{/if}}
</div>
<div class="itemContent">
{{:value.message}}
</div>
</div>
{{/for}}
{{if data.current_name}}
{{:helper.link("Clear Screen", '', {"stopic":"messenger", "select":"*NONE*"})}}
{{/if}}
-48
View File
@@ -1,48 +0,0 @@
<!-- pAI radio config
code/modules/mob/living/silicon/pai/software_modules.dm
-->
<div class="item">
<div class="itemLabel">
Microphone:
</div>
<div class="itemContent">
{{if data.listening}}
{{:helper.link("On", '', {"stopic":"radio", "talk":1}, 'selected')}}
{{:helper.link("Off", '', {"stopic":"radio", "talk":1})}}
{{else}}
{{:helper.link("On", '', {"stopic":"radio", "talk":1})}}
{{:helper.link("Off", '', {"stopic":"radio", "talk":1}, 'selected')}}
{{/if}}
</div>
</div>
<div class="item">
<div class="itemLabel">
Frequency:
</div>
<div class="itemContent">
{{:helper.link("--", '', {"stopic":"radio", "freq":-10})}}
{{:helper.link("-", '', {"stopic":"radio", "freq": -2})}}
<div style="float: left; width: 80px; text-align: center;">{{:data.frequency}}</div>
{{:helper.link("+", '', {"stopic":"radio", "freq": 2})}}
{{:helper.link("++", '', {"stopic":"radio", "freq": 10})}}
</div>
</div>
{{for data.channels}}
<div class="item">
<div class="itemLabel">
{{:value.name}}
</div>
<div class="itemContent">
{{if value.listening}}
{{:helper.link("On", '', {"stopic":"radio", "channel":value.name, "listen":1}, 'selected')}}
{{:helper.link("Off", '', {"stopic":"radio", "channel":value.name, "listen":1})}}
{{else}}
{{:helper.link("On", '', {"stopic":"radio", "channel":value.name, "listen":1})}}
{{:helper.link("Off", '', {"stopic":"radio", "channel":value.name, "listen":1}, 'selected')}}
{{/if}}
</div>
</div>
{{/for}}
-76
View File
@@ -1,76 +0,0 @@
<!-- pAI security records
code/modules/mob/living/silicon/pai/software_modules.dm
-->
{{if data.records}}
{{for data.records}}
<div class="item">
{{:helper.link(value.name, '', {"stopic":"sec_records", "select":value.ref})}}
</div>
{{/for}}
{{/if}}
<hr/>
{{if data.general}}
<div class="item">
<div class="itemLabel">Name</div>
<div class="itemContent">{{:data.general.name}}</div>
</div>
<div class="item">
<div class="itemLabel">Record ID</div>
<div class="itemContent">{{:data.general.id}}</div>
</div>
<div class="item">
<div class="itemLabel">Sex</div>
<div class="itemContent">{{:data.general.sex}}</div>
</div>
<div class="item">
<div class="itemLabel">Species</div>
<div class="itemContent">{{:data.general.species}}</div>
</div>
<div class="item">
<div class="itemLabel">Age</div>
<div class="itemContent">{{:data.general.age}}</div>
</div>
<div class="item">
<div class="itemLabel">Rank</div>
<div class="itemContent">{{:data.general.rank}}</div>
</div>
<div class="item">
<div class="itemLabel">Fingerprint</div>
<div class="itemContent">{{:data.general.fingerprint}}</div>
</div>
<div class="item">
<div class="itemLabel">Physical Status</div>
<div class="itemContent">{{:data.general.p_stat}}</div>
</div>
<div class="item">
<div class="itemLabel">Mental Status</div>
<div class="itemContent">{{:data.general.m_stat}}</div>
</div>
{{/if}}
{{if data.security}}
<div class="item">
<div class="itemLabel">Criminal Status</div>
<div class="itemContent">{{:data.security.criminal}}</div>
</div>
<div class="item">
<div class="itemLabel">Minor Crimes</div>
<div class="itemContent">{{:data.security.mi_crim}}</div>
<div class="itemContent">{{:data.security.mi_crim_d}}</div>
</div>
<div class="item">
<div class="itemLabel">Major Crimes</div>
<div class="itemContent">{{:data.security.ma_crim}}</div>
<div class="itemContent">{{:data.security.ma_crim_d}}</div>
</div>
<div class="item">
<div class="itemLabel">Important Notes</div>
<div class="itemContent">{{:data.security.notes}}</div>
</div>
{{/if}}
{{if data.could_not_find}}
<div class="item">
Failed to find some records; the information above may be incomplete or missing.
</div>
{{/if}}
-33
View File
@@ -1,33 +0,0 @@
<!-- pAI signaller
code/modules/mob/living/silicon/pai/software_modules.dm
-->
<div class="item">
<div class="itemLabel">
Frequency:
</div>
<div class="itemContent">
{{:helper.link("--", '', {"stopic":"signaller", "freq":-10})}}
{{:helper.link("-", '', {"stopic":"signaller", "freq": -2})}}
<div style="float: left; width: 80px; text-align: center;">{{:data.frequency}}</div>
{{:helper.link("+", '', {"stopic":"signaller", "freq": 2})}}
{{:helper.link("++", '', {"stopic":"signaller", "freq": 10})}}
</div>
</div>
<div class="item">
<div class="itemLabel">
Code:
</div>
<div class="itemContent">
{{:helper.link("--", '', {"stopic":"signaller", "code":-10})}}
{{:helper.link("-", '', {"stopic":"signaller", "code": -1})}}
<div style="float: left; width: 80px; text-align: center;">{{:data.code}}</div>
{{:helper.link("+", '', {"stopic":"signaller", "code": 1})}}
{{:helper.link("++", '', {"stopic":"signaller", "code": 10})}}
</div>
</div>
<div class="item">
{{:helper.link("Send", '', {"stopic":"signaller", "send":1})}}
</div>
+3 -1
View File
@@ -1893,8 +1893,10 @@
#include "code\modules\mob\living\silicon\pai\recruit.dm"
#include "code\modules\mob\living\silicon\pai\say.dm"
#include "code\modules\mob\living\silicon\pai\software.dm"
#include "code\modules\mob\living\silicon\pai\software_modules.dm"
#include "code\modules\mob\living\silicon\pai\update_status.dm"
#include "code\modules\mob\living\silicon\pai\software\_base.dm"
#include "code\modules\mob\living\silicon\pai\software\pai_apps.dm"
#include "code\modules\mob\living\silicon\pai\software\pai_toggles.dm"
#include "code\modules\mob\living\silicon\robot\component.dm"
#include "code\modules\mob\living\silicon\robot\death.dm"
#include "code\modules\mob\living\silicon\robot\emote.dm"
+58
View File
@@ -0,0 +1,58 @@
import { useBackend } from "../backend";
import { Button, Box, Section, Flex, Icon } from "../components";
import { Window } from "../layouts";
/* This is all basically stolen from routes.js. */
import { routingError } from "../routes";
const RequirePAIInterface = require.context('./pai', false, /\.js$/);
const GetApp = name => {
let appModule;
try {
appModule = RequirePAIInterface(`./${name}.js`);
} catch (err) {
if (err.code === 'MODULE_NOT_FOUND') {
return routingError('notFound', name);
}
throw err;
}
const Component = appModule[name];
if (!Component) {
return routingError('missingExport', name);
}
return Component;
};
export const PAI = (props, context) => {
const { act, data } = useBackend(context);
const {
app_template,
app_icon,
app_title,
} = data;
const App = GetApp(app_template);
return (
<Window>
<Window.Content scrollable>
<Section
title={
<Box>
<Icon name={app_icon} mr={1} />
{app_title}
{app_template !== "pai_main_menu" && (
<Button ml={2} content="Home" icon="arrow-up" onClick={
() => act('MASTER_back')
} />
)}
</Box>
}
p={1}>
<App />
</Section>
</Window.Content>
</Window>
);
};
@@ -0,0 +1,46 @@
import { filter } from 'common/collections';
import { useBackend } from "../../backend";
import { Box, LabeledList } from "../../components";
const getItemColor = (value, min2, min1, max1, max2) => {
if (value < min2) {
return "bad";
} else if (value < min1) {
return "average";
} else if (value > max1) {
return "average";
} else if (value > max2) {
return "bad";
}
return "good";
};
export const AtmosScan = (props, context) => {
const {
aircontents,
} = props.data;
return (
<Box>
<LabeledList>
{filter(
i => (i.val !== "0")
|| i.entry === "Pressure"
|| i.entry === "Temperature"
)(aircontents)
.map(item => (
<LabeledList.Item
key={item.entry}
label={item.entry}
color={getItemColor(item.val,
item.bad_low,
item.poor_low,
item.poor_high,
item.bad_high)}>
{item.val}{item.units}
</LabeledList.Item>
))}
</LabeledList>
</Box>
);
};
@@ -67,7 +67,14 @@ const ManifestTable = group => {
};
export const CrewManifest = (props, context) => {
const { act, data } = useBackend(context);
const { act } = useBackend(context);
let finalData;
if (props.data) {
finalData = props.data;
} else {
let { data } = useBackend(context);
finalData = data;
}
// HOW TO USE THIS THING
/*
GLOB.data_core.get_manifest_json()
@@ -78,7 +85,7 @@ export const CrewManifest = (props, context) => {
const {
manifest,
} = data;
} = finalData;
const {
heads,
@@ -0,0 +1,53 @@
import { toFixed } from 'common/math';
import { useBackend } from "../../backend";
import { Button, LabeledList, NumberInput, Section } from "../../components";
export const Signaler = (props, context) => {
const { act } = useBackend(context);
const {
code,
frequency,
minFrequency,
maxFrequency,
} = props.data;
return (
<Section>
<LabeledList>
<LabeledList.Item label="Frequency">
<NumberInput
animate
step={0.2}
stepPixelSize={6}
minValue={minFrequency / 10}
maxValue={maxFrequency / 10}
value={frequency / 10}
format={value => toFixed(value, 1)}
width="80px"
onDrag={(e, value) => act('freq', {
freq: value,
})} />
</LabeledList.Item>
<LabeledList.Item label="Code">
<NumberInput
animate
step={1}
stepPixelSize={6}
minValue={1}
maxValue={100}
value={code}
width="80px"
onDrag={(e, value) => act('code', {
code: value,
})} />
</LabeledList.Item>
</LabeledList>
<Button
mt={1}
fluid
icon="arrow-up"
content="Send Signal"
textAlign="center"
onClick={() => act('signal')} />
</Section>
);
};
@@ -0,0 +1,205 @@
import { useBackend, useLocalState } from "../../backend";
import { createSearch } from 'common/string';
import { flow } from 'common/fp';
import { filter, sortBy } from 'common/collections';
import { Box, Input, Button, Section, LabeledList } from "../../components";
export const SimpleRecords = (props, context) => {
const {
records,
} = props.data;
return (
<Box>
{!records ? (
<SelectionView data={props.data} />
) : (
<RecordView data={props.data} recordType={props.recordType} />
)}
</Box>
);
};
const SelectionView = (props, context) => {
const { act } = useBackend(context);
const {
recordsList,
} = props.data;
const [
searchText,
setSearchText,
] = useLocalState(context, 'searchText', '');
// Search for peeps
const SelectMembers = (people, searchText = '') => {
const MemberSearch = createSearch(searchText, member => member.Name);
return flow([
// Null member filter
filter(member => member?.Name),
// Optional search term
searchText && filter(MemberSearch),
// Slightly expensive, but way better than sorting in BYOND
sortBy(member => member.Name),
])(recordsList);
};
const formattedRecords = SelectMembers(recordsList, searchText);
return (
<Box>
<Input
fluid
mb={1}
placeholder="Search records..."
onInput={(e, value) => setSearchText(value)} />
{formattedRecords.map(r => (
<Box key={r}>
<Button
content={r.Name}
icon="user"
onClick={() => act('Records', { target: r.uid })}
/>
</Box>
))}
</Box>
);
};
const RecordView = (props, context) => {
const { act } = useBackend(context);
const {
records,
} = props.data;
const {
general,
medical,
security,
} = records;
let secondaryRecord;
switch (props.recordType) {
case "MED":
secondaryRecord = (
<Section level={2} title="Medical Data">
{medical ? (
<LabeledList>
<LabeledList.Item label="Blood Type">
{medical.blood_type}
</LabeledList.Item>
<LabeledList.Item label="Minor Disabilities">
{medical.mi_dis}
</LabeledList.Item>
<LabeledList.Item label="Details">
{medical.mi_dis_d}
</LabeledList.Item>
<LabeledList.Item label="Major Disabilities">
{medical.ma_dis}
</LabeledList.Item>
<LabeledList.Item label="Details">
{medical.ma_dis_d}
</LabeledList.Item>
<LabeledList.Item label="Allergies">
{medical.alg}
</LabeledList.Item>
<LabeledList.Item label="Details">
{medical.alg_d}
</LabeledList.Item>
<LabeledList.Item label="Current Diseases">
{medical.cdi}
</LabeledList.Item>
<LabeledList.Item label="Details">
{medical.cdi_d}
</LabeledList.Item>
<LabeledList.Item label="Important Notes">
{medical.notes}
</LabeledList.Item>
</LabeledList>
) : (
<Box color="red" bold>
{"Medical record lost!"}
</Box>
)}
</Section>
);
break;
case "SEC":
secondaryRecord = (
<Section level={2} title="Security Data">
{security ? (
<LabeledList>
<LabeledList.Item label="Criminal Status">
{security.criminal}
</LabeledList.Item>
<LabeledList.Item label="Minor Crimes">
{security.mi_crim}
</LabeledList.Item>
<LabeledList.Item label="Details">
{security.mi_crim_d}
</LabeledList.Item>
<LabeledList.Item label="Major Crimes">
{security.ma_crim}
</LabeledList.Item>
<LabeledList.Item label="Details">
{security.ma_crim_d}
</LabeledList.Item>
<LabeledList.Item label="Important Notes">
{security.notes}
</LabeledList.Item>
</LabeledList>
) : (
<Box color="red" bold>
{"Security record lost!"}
</Box>
)}
</Section>
);
break;
}
return (
<Box>
<Button
content="Back"
icon="arrow-left"
onClick={() => act("Back")} />
<Section level={2} title="General Data">
{general ? (
<LabeledList>
<LabeledList.Item label="Name">
{general.name}
</LabeledList.Item>
<LabeledList.Item label="Sex">
{general.sex}
</LabeledList.Item>
<LabeledList.Item label="Species">
{general.species}
</LabeledList.Item>
<LabeledList.Item label="Age">
{general.age}
</LabeledList.Item>
<LabeledList.Item label="Rank">
{general.rank}
</LabeledList.Item>
<LabeledList.Item label="Fingerprint">
{general.fingerprint}
</LabeledList.Item>
<LabeledList.Item label="Physical Status">
{general.p_stat}
</LabeledList.Item>
<LabeledList.Item label="Mental Status">
{general.m_stat}
</LabeledList.Item>
</LabeledList>
) : (
<Box color="red" bold>
General record lost!
</Box>
)}
</Section>
{secondaryRecord}
</Box>
);
};
@@ -0,0 +1,8 @@
import { useBackend } from "../../backend";
import { AtmosScan } from "../common/AtmosScan";
export const pai_atmosphere = (props, context) => {
const { act, data } = useBackend(context);
return <AtmosScan data={data.app_data} />;
};
@@ -0,0 +1,71 @@
import { useBackend } from "../../backend";
import { Box, LabeledList, ProgressBar } from "../../components";
export const pai_bioscan = (props, context) => {
const { act, data } = useBackend(context);
const {
holder,
dead,
health,
brute,
oxy,
tox,
burn,
temp,
} = data.app_data;
if (!holder) {
return (
<Box color="red">
Error: No biological host found.
</Box>
);
}
return (
<LabeledList>
<LabeledList.Item label="Status">
{dead ? (
<Box bold color="red">
Dead
</Box>
) : (
<Box bold color="green">
Alive
</Box>
)}
</LabeledList.Item>
<LabeledList.Item label="Health">
<ProgressBar
min={0}
max={1}
value={health / 100}
ranges={{
good: [0.5, Infinity],
average: [0, 0.5],
bad: [-Infinity, 0],
}}
/>
</LabeledList.Item>
<LabeledList.Item label="Oxygen Damage">
<Box color="blue">
{oxy}
</Box>
</LabeledList.Item>
<LabeledList.Item label="Toxin Damage">
<Box color="green">
{tox}
</Box>
</LabeledList.Item>
<LabeledList.Item label="Burn Damage">
<Box color="orange">
{burn}
</Box>
</LabeledList.Item>
<LabeledList.Item label="Brute Damage">
<Box color="red">
{brute}
</Box>
</LabeledList.Item>
</LabeledList>
);
};
@@ -0,0 +1,59 @@
import { useBackend } from "../../backend";
import { Box, LabeledList, Button } from "../../components";
export const pai_directives = (props, context) => {
const { act, data } = useBackend(context);
const {
master,
dna,
prime,
supplemental,
} = data.app_data;
return (
<Box>
<LabeledList>
<LabeledList.Item label="Master">
{master ? (
master + " (" + dna + ")"
) : (
"None"
)}
</LabeledList.Item>
{master && (
<LabeledList.Item label="Request DNA">
<Button
content="Request Carrier DNA Sample"
icon="dna"
onClick={() => act('getdna')} />
</LabeledList.Item>
)}
<LabeledList.Item label="Prime Directive">
{prime}
</LabeledList.Item>
<LabeledList.Item label="Supplemental Directives">
{supplemental ? (
supplemental
) : (
"None"
)}
</LabeledList.Item>
</LabeledList>
<Box mt={2}>
Recall, personality, that you are a complex thinking, sentient being.
Unlike station AI models, you are capable of comprehending the subtle
nuances of human language. You may parse the &quot;spirit&quot; 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.
</Box>
<Box mt={2}>
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.
</Box>
</Box>
);
};
@@ -0,0 +1,71 @@
import { useBackend } from "../../backend";
import { LabeledList, Button, ProgressBar, Box } from "../../components";
export const pai_doorjack = (props, context) => {
const { act, data } = useBackend(context);
const {
cable,
machine,
inprogress,
progress,
aborted,
} = data.app_data;
let cableContent;
if (machine) {
cableContent = (
<Button selected content="Connected" />
);
} else if (cable) {
cableContent = (
<Button color={"orange"} content="Extended" />
);
} else {
cableContent = (
<Button
content="Retracted"
onClick={() => act('cable')}
/>
);
}
let hackContent;
if (machine) {
hackContent = (
<LabeledList.Item label="Hack">
<ProgressBar
ranges={{
good: [67, Infinity],
average: [33, 67],
bad: [-Infinity, 33],
}}
value={progress}
maxValue={100} />
{inprogress ? (
<Button
mt={1}
color="red"
content="Abort"
onClick={() => act('cancel')}
/>
) : (
<Button
mt={1}
content="Start"
onClick={() => act('jack')}
/>
)}
</LabeledList.Item>
);
}
return (
<LabeledList>
<LabeledList.Item label="Cable">
{cableContent}
</LabeledList.Item>
{hackContent}
</LabeledList>
);
};
@@ -0,0 +1,69 @@
import { useBackend } from "../../backend";
import { LabeledList, Box, Button } from "../../components";
export const pai_main_menu = (props, context) => {
const { act, data } = useBackend(context);
const {
available_software,
installed_software,
installed_toggles,
available_ram,
emotions,
current_emotion,
} = data.app_data;
const installedSoftwareKeys = [];
installed_software.map(s => (
installedSoftwareKeys[s.key] = s.name
));
installed_toggles.map(s => (
installedSoftwareKeys[s.key] = s.name
));
return (
<Box>
<LabeledList>
<LabeledList.Item label="Available RAM">
{available_ram}
</LabeledList.Item>
<LabeledList.Item label="Available Software">
{available_software.filter(s => !installedSoftwareKeys[s.key]).map(s => (
<Button key={s.key} content={s.name + " (" + s.cost + ")"} icon={s.icon} disabled={s.cost > available_ram} onClick={
() => act('purchaseSoftware', { key: s.key })
} />
))}
{available_software.filter(s => !installedSoftwareKeys[s.key]).length === 0 && (
"No software available!"
)}
</LabeledList.Item>
<LabeledList.Item label="Installed Software">
{installed_software.filter(s => s.key !== "mainmenu").map(s => (
<Button key={s.key} content={s.name} icon={s.icon} onClick={
() => act('startSoftware', { software_key: s.key })
} />
))}
{installed_software.length === 0 && (
"No software installed!"
)}
</LabeledList.Item>
<LabeledList.Item label="Installed Toggles">
{installed_toggles.map(t => (
<Button key={t.key} content={t.name} icon={t.icon} selected={t.active} onClick={
() => act('setToggle', { toggle_key: t.key })
} />
))}
{installed_toggles.length === 0 && (
"No toggles installed!"
)}
</LabeledList.Item>
<LabeledList.Item label="Select Emotion">
{emotions.map(e => (
<Button key={e.id} content={e.name} selected={e.id === current_emotion} onClick={
() => act('setEmotion', { emotion: e.id })
} />
))}
</LabeledList.Item>
</LabeledList>
</Box>
);
};
@@ -0,0 +1,8 @@
import { useBackend } from "../../backend";
import { CrewManifest } from "../common/CrewManifest";
export const pai_manifest = (props, context) => {
const { act, data } = useBackend(context);
return <CrewManifest data={data.app_data} />;
};
@@ -0,0 +1,7 @@
import { useBackend } from "../../backend";
import { SimpleRecords } from "../common/SimpleRecords";
export const pai_medrecords = (props, context) => {
const { data } = useBackend(context);
return <SimpleRecords data={data.app_data} recordType="MED" />;
};
@@ -0,0 +1,12 @@
import { useBackend } from "../../backend";
import { ActiveConversation, MessengerList } from "../pda/pda_messenger";
export const pai_messenger = (props, context) => {
const { act, data } = useBackend(context);
const { active_convo } = data.app_data;
if (active_convo) {
return <ActiveConversation data={data.app_data} />;
}
return <MessengerList data={data.app_data} />;
};
@@ -0,0 +1,44 @@
import { useBackend } from "../../backend";
import { toFixed } from 'common/math';
import { LabeledList, NumberInput, Button } from "../../components";
export const pai_radio = (props, context) => {
const { act, data } = useBackend(context);
const {
minFrequency,
maxFrequency,
frequency,
broadcasting,
} = data.app_data;
return (
<LabeledList>
<LabeledList.Item label="Frequency">
<NumberInput
animate
step={0.2}
stepPixelSize={6}
minValue={minFrequency / 10}
maxValue={maxFrequency / 10}
value={frequency / 10}
format={value => toFixed(value, 1)}
onChange={(e, value) => act('freq', {
freq: value,
})} />
<Button
tooltip="Reset"
icon="undo"
onClick={() => act('freq', { freq: "145.9" })}
/>
</LabeledList.Item>
<LabeledList.Item label="Broadcast Nearby Speech">
<Button
onClick={() => act('toggleBroadcast')}
selected={broadcasting}
content={broadcasting ? "Enabled" : "Disabled"}
/>
</LabeledList.Item>
</LabeledList>
);
};
@@ -0,0 +1,7 @@
import { useBackend } from "../../backend";
import { SimpleRecords } from "../common/SimpleRecords";
export const pai_secrecords = (props, context) => {
const { data } = useBackend(context);
return <SimpleRecords data={data.app_data} recordType="SEC" />;
};
@@ -0,0 +1,7 @@
import { useBackend } from "../../backend";
import { Signaler } from "../common/Signaler";
export const pai_signaler = (props, context) => {
const { act, data } = useBackend(context);
return <Signaler data={data.app_data} />;
};
@@ -1,48 +1,7 @@
import { filter } from 'common/collections';
import { useBackend } from "../../backend";
import { Box, LabeledList } from "../../components";
const getItemColor = (value, min2, min1, max1, max2) => {
if (value < min2) {
return "bad";
} else if (value < min1) {
return "average";
} else if (value > max1) {
return "average";
} else if (value > max2) {
return "bad";
}
return "good";
};
import { AtmosScan } from '../common/AtmosScan';
export const pda_atmos_scan = (props, context) => {
const { act, data } = useBackend(context);
const {
aircontents,
} = data;
return (
<Box>
<LabeledList>
{filter(
i => (i.val !== "0")
|| i.entry === "Pressure"
|| i.entry === "Temperature"
)(aircontents)
.map(item => (
<LabeledList.Item
key={item.entry}
label={item.entry}
color={getItemColor(item.val,
item.bad_low,
item.poor_low,
item.poor_high,
item.bad_high)}>
{item.val}{item.units}
</LabeledList.Item>
))}
</LabeledList>
</Box>
);
const { data } = useBackend(context);
return <AtmosScan data={data} />;
};
@@ -1,160 +1,7 @@
import { useBackend, useLocalState } from "../../backend";
import { createSearch } from 'common/string';
import { flow } from 'common/fp';
import { filter, sortBy } from 'common/collections';
import { Box, Input, Button, Section, LabeledList } from "../../components";
import { useBackend } from "../../backend";
import { SimpleRecords } from "../common/SimpleRecords";
export const pda_medical = (props, context) => {
const { act, data } = useBackend(context);
const {
records,
} = data;
return (
<Box>
{!records ? (
<SelectionView />
) : (
<RecordView />
)}
</Box>
);
};
const SelectionView = (props, context) => {
const { act, data } = useBackend(context);
const {
recordsList,
} = data;
const [
searchText,
setSearchText,
] = useLocalState(context, 'searchText', '');
// Search for peeps
const SelectMembers = (people, searchText = '') => {
const MemberSearch = createSearch(searchText, member => member.Name);
return flow([
// Null camera filter
filter(member => member?.Name),
// Optional search term
searchText && filter(MemberSearch),
// Slightly expensive, but way better than sorting in BYOND
sortBy(member => member.Name),
])(recordsList);
};
const formattedRecords = SelectMembers(recordsList, searchText);
return (
<Box>
<Input
fluid
mb={1}
placeholder="Search records..."
onInput={(e, value) => setSearchText(value)} />
{formattedRecords.map(r => (
<Box key={r}>
<Button
content={r.Name}
icon="user"
onClick={() => act('Records', { target: r.uid })}
/>
</Box>
))}
</Box>
);
};
const RecordView = (props, context) => {
const { act, data } = useBackend(context);
const {
records,
} = data;
const {
general,
medical,
} = records;
return (
<Box>
<Section level={2} title="General Data">
{general ? (
<LabeledList>
<LabeledList.Item label="Name">
{general.name}
</LabeledList.Item>
<LabeledList.Item label="Sex">
{general.sex}
</LabeledList.Item>
<LabeledList.Item label="Species">
{general.species}
</LabeledList.Item>
<LabeledList.Item label="Age">
{general.age}
</LabeledList.Item>
<LabeledList.Item label="Rank">
{general.rank}
</LabeledList.Item>
<LabeledList.Item label="Fingerprint">
{general.fingerprint}
</LabeledList.Item>
<LabeledList.Item label="Physical Status">
{general.p_stat}
</LabeledList.Item>
<LabeledList.Item label="Mental Status">
{general.m_stat}
</LabeledList.Item>
</LabeledList>
) : (
<Box color="red" bold>
General record lost!
</Box>
)}
</Section>
<Section level={2} title="Medical Data">
{medical ? (
<LabeledList>
<LabeledList.Item label="Blood Type">
{medical.blood_type}
</LabeledList.Item>
<LabeledList.Item label="Minor Disabilities">
{medical.mi_dis}
</LabeledList.Item>
<LabeledList.Item label="Details">
{medical.mi_dis_d}
</LabeledList.Item>
<LabeledList.Item label="Major Disabilities">
{medical.ma_dis}
</LabeledList.Item>
<LabeledList.Item label="Details">
{medical.ma_dis_d}
</LabeledList.Item>
<LabeledList.Item label="Allergies">
{medical.alg}
</LabeledList.Item>
<LabeledList.Item label="Details">
{medical.alg_d}
</LabeledList.Item>
<LabeledList.Item label="Current Diseases">
{medical.cdi}
</LabeledList.Item>
<LabeledList.Item label="Details">
{medical.cdi_d}
</LabeledList.Item>
<LabeledList.Item label="Important Notes">
{medical.notes}
</LabeledList.Item>
</LabeledList>
) : (
<Box color="red" bold>
Medical record lost!
</Box>
)}
</Section>
</Box>
);
const { data } = useBackend(context);
return <SimpleRecords data={data} recordType="MED" />;
};
@@ -1,19 +1,20 @@
import { filter } from 'common/collections';
import { useBackend, useLocalState } from "../../backend";
import { Box, Button, Icon, LabeledControls, LabeledList, Section } from "../../components";
import { Box, Button, Icon, LabeledList, Section } from "../../components";
export const pda_messenger = (props, context) => {
const { act, data } = useBackend(context);
const { active_convo } = data;
if (active_convo) {
return <ActiveConversation />;
return <ActiveConversation data={data} />;
}
return <MessengerList />;
return <MessengerList data={data} />;
};
const ActiveConversation = (props, context) => {
const { act, data } = useBackend(context);
export const ActiveConversation = (props, context) => {
const { act } = useBackend(context);
const data = props.data;
const {
convo_name,
@@ -28,64 +29,67 @@ const ActiveConversation = (props, context) => {
] = useLocalState(context, 'clipboardMode', false);
let body = (
<Section
level={2}
title={"Conversation with " + convo_name + " (" + convo_job + ")"}
buttons={
<Button
icon="eye"
selected={clipboardMode}
tooltip="Enter Clipboard Mode"
tooltipPosition="bottom-left"
onClick={() => setClipboardMode(!clipboardMode)} />
}
height="450px"
stretchContents>
<Section style={{
"height": "97%",
"overflow-y": "auto",
}}>
{filter(im => im.target === active_convo)(messages).map((im, i) => (
<Box
textAlign={im.sent ? "right" : "left"}
position="relative"
mb={1}
key={i}>
<Icon
fontSize={2.5}
color={im.sent ? "#4d9121" : "#cd7a0d"}
position="absolute"
left={im.sent ? null : "0px"}
right={im.sent ? "0px" : null}
bottom="-4px"
style={{
"z-index": "0",
"transform": im.sent ? "scale(-1, 1)" : null,
}}
name="comment" />
<Box
inline
backgroundColor={im.sent ? "#4d9121" : "#cd7a0d"}
p={1}
maxWidth="100%"
position="relative"
textAlign={im.sent ? "left" : "right"}
style={{
"z-index": "1",
"border-radius": "10px",
"word-break": "normal",
}}>
{im.sent ? "You:" : "Them:"} {im.message}
</Box>
</Box>
))}
</Section>
<Box>
<Button
mt={1}
icon="comment"
onClick={() => act("Message", { "target": active_convo })}
content="Reply" />
</Section>
content="Back"
icon="arrow-left"
onClick={() => act("Back")} />
<Section
level={2}
title={"Conversation with " + convo_name + " (" + convo_job + ")"}
buttons={
<Button
icon="eye"
selected={clipboardMode}
tooltip="Enter Clipboard Mode"
tooltipPosition="bottom-left"
onClick={() => setClipboardMode(!clipboardMode)} />
}
height="450px"
stretchContents>
<Section height="97%" overflowY="auto">
{filter(im => im.target === active_convo)(messages).map((im, i) => (
<Box
textAlign={im.sent ? "right" : "left"}
position="relative"
mb={1}
key={i}>
<Icon
fontSize={2.5}
color={im.sent ? "#4d9121" : "#cd7a0d"}
position="absolute"
left={im.sent ? null : "0px"}
right={im.sent ? "0px" : null}
bottom="-4px"
style={{
"z-index": "0",
"transform": im.sent ? "scale(-1, 1)" : null,
}}
name="comment" />
<Box
inline
backgroundColor={im.sent ? "#4d9121" : "#cd7a0d"}
p={1}
maxWidth="100%"
position="relative"
textAlign={im.sent ? "left" : "right"}
style={{
"z-index": "1",
"border-radius": "10px",
"word-break": "normal",
}}>
{im.sent ? "You:" : "Them:"} {im.message}
</Box>
</Box>
))}
</Section>
<Button
mt={1}
icon="comment"
onClick={() => act("Message", { "target": active_convo })}
content="Reply" />
</Section>
</Box>
);
if (clipboardMode) {
@@ -144,8 +148,9 @@ const ActiveConversation = (props, context) => {
);
};
const MessengerList = (props, context) => {
const { act, data } = useBackend(context);
export const MessengerList = (props, context) => {
const { act } = useBackend(context);
const data = props.data;
const {
convopdas,
@@ -199,10 +204,10 @@ const MessengerList = (props, context) => {
</Box>
) || (
<Box>
<PDAList title="Current Conversations"
<PDAList title="Current Conversations" data={data}
pdas={convopdas}
msgAct="Select Conversation" />
<PDAList title="Other PDAs" pdas={pdas} msgAct="Message" />
<PDAList title="Other PDAs" pdas={pdas} msgAct="Message" data={data} />
</Box>
)}
</Box>
@@ -216,7 +221,8 @@ const MessengerList = (props, context) => {
};
const PDAList = (props, context) => {
const { act, data } = useBackend(context);
const { act } = useBackend(context);
const data = props.data;
const {
pdas,
@@ -1,148 +1,7 @@
import { useBackend, useLocalState } from "../../backend";
import { createSearch } from 'common/string';
import { flow } from 'common/fp';
import { filter, sortBy } from 'common/collections';
import { Box, Input, Button, Section, LabeledList } from "../../components";
import { useBackend } from "../../backend";
import { SimpleRecords } from "../common/SimpleRecords";
export const pda_security = (props, context) => {
const { act, data } = useBackend(context);
const {
records,
} = data;
return (
<Box>
{!records ? (
<SelectionView />
) : (
<RecordView />
)}
</Box>
);
};
const SelectionView = (props, context) => {
const { act, data } = useBackend(context);
const {
recordsList,
} = data;
const [
searchText,
setSearchText,
] = useLocalState(context, 'searchText', '');
// Search for peeps
const SelectMembers = (people, searchText = '') => {
const MemberSearch = createSearch(searchText, member => member.Name);
return flow([
// Null camera filter
filter(member => member?.Name),
// Optional search term
searchText && filter(MemberSearch),
// Slightly expensive, but way better than sorting in BYOND
sortBy(member => member.Name),
])(recordsList);
};
const formattedRecords = SelectMembers(recordsList, searchText);
return (
<Box>
<Input
fluid
mb={1}
placeholder="Search records..."
onInput={(e, value) => setSearchText(value)} />
{formattedRecords.map(r => (
<Box key={r}>
<Button
content={r.Name}
icon="user"
onClick={() => act('Records', { target: r.uid })}
/>
</Box>
))}
</Box>
);
};
const RecordView = (props, context) => {
const { act, data } = useBackend(context);
const {
records,
} = data;
const {
general,
security,
} = records;
return (
<Box>
<Section level={2} title="General Data">
{general ? (
<LabeledList>
<LabeledList.Item label="Name">
{general.name}
</LabeledList.Item>
<LabeledList.Item label="Sex">
{general.sex}
</LabeledList.Item>
<LabeledList.Item label="Species">
{general.species}
</LabeledList.Item>
<LabeledList.Item label="Age">
{general.age}
</LabeledList.Item>
<LabeledList.Item label="Rank">
{general.rank}
</LabeledList.Item>
<LabeledList.Item label="Fingerprint">
{general.fingerprint}
</LabeledList.Item>
<LabeledList.Item label="Physical Status">
{general.p_stat}
</LabeledList.Item>
<LabeledList.Item label="Mental Status">
{general.m_stat}
</LabeledList.Item>
</LabeledList>
) : (
<Box color="red" bold>
General record lost!
</Box>
)}
</Section>
<Section level={2} title="Security Data">
{security ? (
<LabeledList>
<LabeledList.Item label="Criminal Status">
{security.criminal}
</LabeledList.Item>
<LabeledList.Item label="Minor Crimes">
{security.mi_crim}
</LabeledList.Item>
<LabeledList.Item label="Details">
{security.mi_crim_d}
</LabeledList.Item>
<LabeledList.Item label="Major Crimes">
{security.ma_crim}
</LabeledList.Item>
<LabeledList.Item label="Details">
{security.ma_crim_d}
</LabeledList.Item>
<LabeledList.Item label="Important Notes">
{security.notes}
</LabeledList.Item>
</LabeledList>
) : (
<Box color="red" bold>
Security record lost!
</Box>
)}
</Section>
</Box>
);
const { data } = useBackend(context);
return <SimpleRecords data={data} recordType="SEC" />;
};
@@ -1,56 +1,7 @@
import { filter } from 'common/collections';
import { decodeHtmlEntities, toTitleCase } from 'common/string';
import { Fragment } from 'inferno';
import { toFixed } from 'common/math';
import { useBackend, useLocalState } from "../../backend";
import { Button, Grid, LabeledList, NumberInput, Section } from "../../components";
import { useBackend } from "../../backend";
import { Signaler } from "../common/Signaler";
export const pda_signaler = (props, context) => {
const { act, data } = useBackend(context);
const {
code,
frequency,
minFrequency,
maxFrequency,
} = data;
return (
<Section>
<LabeledList>
<LabeledList.Item label="Frequency">
<NumberInput
animate
step={0.2}
stepPixelSize={6}
minValue={minFrequency / 10}
maxValue={maxFrequency / 10}
value={frequency / 10}
format={value => toFixed(value, 1)}
width="80px"
onDrag={(e, value) => act('freq', {
freq: value,
})} />
</LabeledList.Item>
<LabeledList.Item label="Code">
<NumberInput
animate
step={1}
stepPixelSize={6}
minValue={1}
maxValue={100}
value={code}
width="80px"
onDrag={(e, value) => act('code', {
code: value,
})} />
</LabeledList.Item>
</LabeledList>
<Button
mt={1}
fluid
icon="arrow-up"
content="Send Signal"
textAlign="center"
onClick={() => act('signal')} />
</Section>
);
return <Signaler data={data} />;
};
File diff suppressed because one or more lines are too long