Paicontroller subsystem port (#19165)
* to subsystem * wip subsystem pain * some big recoding * seperated * oop * fucking lua * working on tgui foundation * compile fixes * nice and clean * type * inviting works * unit testing * agony * test good * typo * that should be the basics * Complete recode of pai saving * restore fluff text * migration * pai notification automation * swap * length * more fixes * pai sprite datums * type 13 uses all four directions * eye handling * use accessors * working on icontests * fix * hologram eye test * oop * wait that doesn't work at all * text * eye colors and holo eyes * lots of fixes * fix those up * organized * belly cap * that too * death sprites * resting eyes * resting flag * bad check here * fixing icon files * proper file * garble * fix some states * pai death * collapse pai files into single file * allow some restricted pai interactions * lamp * block invites button * collapse in remaining vr file * this is actually needed * documenting and organizing * update to TGprefs * remove gun toggle verb on pai * forbid default name joins * pai cable retraction * various pai fixes * icon forge pai * pai eyes * type 13 iconstate * directionless * small fix * pai dir fix * missing icon * no fresh pais * oops * fixed pai join * fixed change chassis * pai UI * . * . * reuse * . * . * . * push that * Horro rmasks * pai invite data change * splitting * use refs * initial * with PAI * soem more * . * . * . * . * ugh * next * pai invite * only preview * that * use from char pref * fixed pai invite not working * small tgui fixes * remove temp code * . * pai comments properly sent * ooc * horror * need to use this for now * . * fix * lol * raw mob spawn fix * pai software * push --------- Co-authored-by: Kashargul <144968721+Kashargul@users.noreply.github.com>
@@ -43,7 +43,7 @@ GLOBAL_LIST_INIT(be_special_flags, list(
|
||||
"Wizard" = BE_WIZARD,
|
||||
"Malf AI" = BE_MALF,
|
||||
"Revolutionary" = BE_REV,
|
||||
"Genaprawn" = BE_ALIEN,
|
||||
"Alien Candidate" = BE_ALIEN,
|
||||
"Positronic Brain" = BE_AI,
|
||||
"Cultist" = BE_CULTIST,
|
||||
"Renegade" = BE_RENEGADE,
|
||||
|
||||
@@ -72,3 +72,6 @@
|
||||
#define WRITE_PREF_NORMAL 1
|
||||
#define WRITE_PREF_INSTANT 2
|
||||
#define WRITE_PREF_MANUAL 3
|
||||
|
||||
#define PAI_UNSET "None Set"
|
||||
#define PAI_DEFAULT_CHASSIS "Drone"
|
||||
|
||||
@@ -969,16 +969,13 @@ GLOBAL_LIST_INIT(special_roles, list(
|
||||
"ninja" = 0, // 10
|
||||
"raider" = 0, // 11
|
||||
"diona" = 0, // 12
|
||||
"mutineer" = 0, // 13
|
||||
"loyalist" = 0, // 14
|
||||
"pAI candidate" = 1, // 15
|
||||
"lost drone" = 1, // 16
|
||||
"maint pred" = 1, // 17
|
||||
"maint lurker" = 1, // 18
|
||||
"morph" = 1, // 19
|
||||
"corgi" = 1, // 20
|
||||
"cursed sword" = 1, // 21
|
||||
"Ship Survivor" = 1, // 22
|
||||
"loyalist" = 0, // 13
|
||||
"pAI" = 1, // 14
|
||||
"lost drone" = 1, // 15
|
||||
"maint critter" = 1, // 16
|
||||
"corgi" = 1, // 17
|
||||
"cursed sword" = 1, // 18
|
||||
"ship survivor" = 1, // 19
|
||||
))
|
||||
|
||||
GLOBAL_LIST_INIT(maint_mob_pred_options, list(
|
||||
@@ -1095,23 +1092,27 @@ GLOBAL_LIST_INIT(breach_burn_descriptors, list(
|
||||
"huge scorched area"
|
||||
))
|
||||
|
||||
GLOBAL_LIST_INIT(wide_chassis, list(
|
||||
"rat",
|
||||
"panther",
|
||||
"teppi",
|
||||
"pai-diredog",
|
||||
"pai-horse_lune",
|
||||
"pai-horse_soleil",
|
||||
"pai-pdragon",
|
||||
"pai-protodog"
|
||||
))
|
||||
GLOBAL_LIST_EMPTY(paikeys)
|
||||
|
||||
GLOBAL_LIST_INIT(flying_chassis, list(
|
||||
"pai-parrot",
|
||||
"pai-bat",
|
||||
"pai-butterfly",
|
||||
"pai-hawk",
|
||||
"cyberelf"
|
||||
GLOBAL_LIST_EMPTY(pai_software_by_key)
|
||||
GLOBAL_LIST_EMPTY(default_pai_software)
|
||||
GLOBAL_LIST_INIT(pai_emotions, list(
|
||||
"Neutral" = 1,
|
||||
"What" = 2,
|
||||
"Happy" = 3,
|
||||
"Cat" = 4,
|
||||
"Extremely Happy" = 5,
|
||||
"Face" = 6,
|
||||
"Laugh" = 7,
|
||||
"Sad" = 8,
|
||||
"Angry" = 9,
|
||||
"Silly" = 10,
|
||||
"Nose" = 11,
|
||||
"Smirk" = 12,
|
||||
"Exclamation Points" = 13,
|
||||
"Question Mark" = 14,
|
||||
"Blank" = 15,
|
||||
"Off" = 16
|
||||
))
|
||||
|
||||
//Sure I could spend all day making wacky overlays for all of the different forms
|
||||
@@ -1303,41 +1304,6 @@ GLOBAL_LIST_EMPTY(sparring_attack_cache)
|
||||
|
||||
GLOBAL_LIST_EMPTY(protean_abilities)
|
||||
|
||||
//PAI stuff
|
||||
GLOBAL_LIST_INIT(possible_chassis, list(
|
||||
"Drone" = "pai-repairbot",
|
||||
"Cat" = "pai-cat",
|
||||
"Mouse" = "pai-mouse",
|
||||
"Monkey" = "pai-monkey",
|
||||
"Borgi" = "pai-borgi",
|
||||
"Fox" = "pai-fox",
|
||||
"Parrot" = "pai-parrot",
|
||||
"Rabbit" = "pai-rabbit",
|
||||
"Dire wolf" = "pai-diredog",
|
||||
"Horse (Lune)" = "pai-horse_lune",
|
||||
"Horse (Soleil)" = "pai-horse_soleil",
|
||||
"Dragon" = "pai-pdragon",
|
||||
"Bear" = "pai-bear",
|
||||
"Fennec" = "pai-fen",
|
||||
"Type Zero" = "pai-typezero",
|
||||
"Raccoon" = "pai-raccoon",
|
||||
"Raptor" = "pai-raptor",
|
||||
"Corgi" = "pai-corgi",
|
||||
"Bat" = "pai-bat",
|
||||
"Butterfly" = "pai-butterfly",
|
||||
"Hawk" = "pai-hawk",
|
||||
"Duffel" = "pai-duffel",
|
||||
"Rat" = "rat",
|
||||
"Panther" = "panther",
|
||||
"Cyber Elf" = "cyberelf",
|
||||
"Teppi" = "teppi",
|
||||
"Catslug" = "catslug",
|
||||
"Car" = "car",
|
||||
"Type One" = "typeone",
|
||||
"Type Thirteen" = "13",
|
||||
"Protogen Dog" = "pai-protodog"
|
||||
))
|
||||
|
||||
//PAI stuff
|
||||
GLOBAL_LIST_INIT(possible_say_verbs, list(
|
||||
"Robotic" = list("states","declares","queries"),
|
||||
|
||||
@@ -353,7 +353,7 @@ GLOBAL_LIST_INIT(alphabet_upper, list("A","B","C","D","E","F","G","H","I","J","K
|
||||
else
|
||||
return string
|
||||
else
|
||||
return "[copytext_preserve_html(string, 1, 37)]..."
|
||||
return "[copytext_preserve_html(string, 1, len - 3)]..."
|
||||
|
||||
//alternative copytext() for encoded text, doesn't break html entities (" and other)
|
||||
/proc/copytext_preserve_html(var/text, var/first, var/last)
|
||||
|
||||
@@ -80,7 +80,6 @@ ADMIN_VERB(debug_controller, R_DEBUG, "Debug Controller", "Debug the various per
|
||||
options["LEGACY: master_controller"] = GLOB.master_controller
|
||||
options["LEGACY: job_master"] = GLOB.job_master
|
||||
options["LEGACY: emergency_shuttle"] = GLOB.emergency_shuttle
|
||||
options["LEGACY: paiController"] = paiController
|
||||
options["LEGACY: cameranet"] = GLOB.cameranet
|
||||
options["LEGACY: transfer_controller"] = GLOB.transfer_controller
|
||||
|
||||
|
||||
@@ -0,0 +1,224 @@
|
||||
#define PAI_DELAY_TIME 1 MINUTE
|
||||
|
||||
////////////////////////////////
|
||||
//// Pai join and management subsystem
|
||||
////////////////////////////////
|
||||
SUBSYSTEM_DEF(pai)
|
||||
name = "Pai"
|
||||
wait = 4 SECONDS
|
||||
dependencies = list(
|
||||
/datum/controller/subsystem/atoms
|
||||
)
|
||||
VAR_PRIVATE/list/datum/pai_sprite/pai_chassis_sprites = list()
|
||||
VAR_PRIVATE/list/current_run = list()
|
||||
VAR_PRIVATE/list/pai_ghosts = list()
|
||||
VAR_PRIVATE/list/asked = list()
|
||||
|
||||
/datum/controller/subsystem/pai/Initialize()
|
||||
// Get all software setup
|
||||
for(var/type in subtypesof(/datum/pai_software))
|
||||
var/datum/pai_software/P = new type()
|
||||
GLOB.pai_software_by_key[P.id] = P
|
||||
if(P.default)
|
||||
GLOB.default_pai_software[P.id] = P
|
||||
|
||||
// Get all valid chassis types
|
||||
for(var/datum/pai_sprite/sprite as anything in subtypesof(/datum/pai_sprite))
|
||||
if(!initial(sprite.sprite_icon) || initial(sprite.hidden))
|
||||
continue
|
||||
pai_chassis_sprites[initial(sprite.name)] = new sprite()
|
||||
|
||||
return SS_INIT_SUCCESS
|
||||
|
||||
/datum/controller/subsystem/pai/stat_entry(msg)
|
||||
msg = "C:[length(pai_ghosts)]"
|
||||
return ..()
|
||||
|
||||
/datum/controller/subsystem/pai/fire(resumed)
|
||||
if(!resumed)
|
||||
pai_ghosts.Cut()
|
||||
current_run = GLOB.observer_mob_list.Copy()
|
||||
|
||||
while(length(current_run))
|
||||
if(MC_TICK_CHECK)
|
||||
return
|
||||
|
||||
var/mob/observer/ghost = current_run[length(current_run)]
|
||||
current_run.len--
|
||||
if(!invite_valid(ghost))
|
||||
continue
|
||||
|
||||
// Create candidate
|
||||
pai_ghosts[REF(ghost)] = WEAKREF(ghost)
|
||||
|
||||
/datum/controller/subsystem/pai/proc/get_chassis_list()
|
||||
RETURN_TYPE(/list/datum/pai_sprite)
|
||||
SHOULD_NOT_OVERRIDE(TRUE)
|
||||
return pai_chassis_sprites
|
||||
|
||||
/datum/controller/subsystem/pai/proc/chassis_data(id_name)
|
||||
RETURN_TYPE(/datum/pai_sprite)
|
||||
SHOULD_NOT_OVERRIDE(TRUE)
|
||||
if(!(id_name in pai_chassis_sprites))
|
||||
return pai_chassis_sprites[PAI_DEFAULT_CHASSIS]
|
||||
return pai_chassis_sprites[id_name]
|
||||
|
||||
/datum/controller/subsystem/pai/proc/invite_valid(mob/user)
|
||||
SHOULD_NOT_OVERRIDE(TRUE)
|
||||
if(!user.client?.prefs || !user.ckey)
|
||||
return FALSE
|
||||
if(!user.MayRespawn())
|
||||
return FALSE
|
||||
if(jobban_isbanned(user, "pAI"))
|
||||
return FALSE
|
||||
if(!(user.client.prefs.be_special & BE_PAI))
|
||||
return FALSE
|
||||
if(check_is_delayed(REF(user)))
|
||||
return FALSE
|
||||
if(check_is_already_pai(user.ckey))
|
||||
return FALSE
|
||||
if(user.client.prefs.read_preference(/datum/preference/text/pai_name) == PAI_UNSET) // Forbid unset name
|
||||
return FALSE
|
||||
return TRUE
|
||||
|
||||
/datum/controller/subsystem/pai/proc/check_is_delayed(ghost_ref)
|
||||
SHOULD_NOT_OVERRIDE(TRUE)
|
||||
if(ghost_ref in asked)
|
||||
if(world.time < asked[ghost_ref] + PAI_DELAY_TIME)
|
||||
return TRUE
|
||||
return FALSE
|
||||
|
||||
/datum/controller/subsystem/pai/proc/check_is_already_pai(check_ckey)
|
||||
SHOULD_NOT_OVERRIDE(TRUE)
|
||||
return (check_ckey in GLOB.paikeys)
|
||||
|
||||
/datum/controller/subsystem/pai/proc/get_ghost_from_ref(ghost_ref)
|
||||
RETURN_TYPE(/mob/observer)
|
||||
SHOULD_NOT_OVERRIDE(TRUE)
|
||||
PRIVATE_PROC(TRUE)
|
||||
if(!ghost_ref)
|
||||
return null
|
||||
var/datum/weakref/WF = pai_ghosts[ghost_ref]
|
||||
return WF?.resolve()
|
||||
|
||||
/datum/controller/subsystem/pai/proc/get_invite_list_data()
|
||||
RETURN_TYPE(/list)
|
||||
SHOULD_NOT_OVERRIDE(TRUE)
|
||||
|
||||
var/list/data = list()
|
||||
for(var/ghost_ref in pai_ghosts)
|
||||
var/mob/observer/ghost = get_ghost_from_ref(ghost_ref)
|
||||
if(!istype(ghost) || !ghost.client?.prefs)
|
||||
continue
|
||||
|
||||
var/datum/preferences/pref = ghost.client.prefs
|
||||
var/datum/asset/spritesheet_batched/pai_icons/spritesheet = get_asset_datum(/datum/asset/spritesheet_batched/pai_icons)
|
||||
var/chassis = pref.read_preference(/datum/preference/text/pai_chassis)
|
||||
var/datum/pai_sprite/sprite_datum = SSpai.chassis_data(chassis)
|
||||
var/css_class = sanitize_css_class_name("[sprite_datum.type]")
|
||||
UNTYPED_LIST_ADD(data, list(
|
||||
"ref" = REF(ghost),
|
||||
"name" = pref.read_preference(/datum/preference/text/pai_name),
|
||||
"gender" = pref.read_preference(/datum/preference/choiced/gender/biological), // Cannot use identifying yet due to byond limits
|
||||
"role" = TextPreview(pref.read_preference(/datum/preference/text/pai_role), 152),
|
||||
"ad" = TextPreview(pref.read_preference(/datum/preference/text/pai_ad), 244),
|
||||
"eyecolor" = pref.read_preference(/datum/preference/color/pai_eye_color),
|
||||
"chassis" = chassis,
|
||||
"emotion" = pref.read_preference(/datum/preference/text/pai_emotion),
|
||||
"sprite_datum_class" = css_class,
|
||||
"sprite_datum_size" = spritesheet.icon_size_id(css_class + "S"), // just get the south icon's size, the rest will be the same
|
||||
))
|
||||
return data
|
||||
|
||||
/datum/controller/subsystem/pai/proc/get_detailed_invite_data(var/ghost_ref)
|
||||
RETURN_TYPE(/list)
|
||||
SHOULD_NOT_OVERRIDE(TRUE)
|
||||
|
||||
if(!(ghost_ref in pai_ghosts))
|
||||
return null
|
||||
|
||||
var/datum/weakref/WF = pai_ghosts[ghost_ref]
|
||||
var/mob/observer/ghost = WF?.resolve()
|
||||
if(!istype(ghost) || !ghost.client?.prefs)
|
||||
return null
|
||||
|
||||
var/datum/preferences/pref = ghost.client.prefs
|
||||
var/datum/asset/spritesheet_batched/pai_icons/spritesheet = get_asset_datum(/datum/asset/spritesheet_batched/pai_icons)
|
||||
var/chassis = pref.read_preference(/datum/preference/text/pai_chassis)
|
||||
var/datum/pai_sprite/sprite_datum = SSpai.chassis_data(chassis)
|
||||
var/css_class = sanitize_css_class_name("[sprite_datum.type]")
|
||||
return list(
|
||||
"ref" = ghost_ref,
|
||||
"name" = pref.read_preference(/datum/preference/text/pai_name),
|
||||
"gender" = pref.read_preference(/datum/preference/choiced/gender/biological), // Cannot use identifying yet due to byond limits
|
||||
// Description
|
||||
"role" = pref.read_preference(/datum/preference/text/pai_role),
|
||||
"description" = pref.read_preference(/datum/preference/text/pai_description),
|
||||
"ad" = pref.read_preference(/datum/preference/text/pai_ad),
|
||||
"comments" = pref.read_preference(/datum/preference/text/pai_comments),
|
||||
// Appearance
|
||||
"eyecolor" = pref.read_preference(/datum/preference/color/pai_eye_color),
|
||||
"chassis" = chassis,
|
||||
"emotion" = pref.read_preference(/datum/preference/text/pai_emotion),
|
||||
// Sprites
|
||||
"sprite_datum_class" = css_class,
|
||||
"sprite_datum_size" = spritesheet.icon_size_id(css_class + "S"), // just get the south icon's size, the rest will be the same
|
||||
)
|
||||
|
||||
/datum/controller/subsystem/pai/proc/invite_ghost(mob/inquirer, ghost_ref, obj/item/paicard/card)
|
||||
SHOULD_NOT_OVERRIDE(TRUE)
|
||||
// Is our card legal to inhabit?
|
||||
if(QDELETED(card) || card.pai || card.is_damage_critical())
|
||||
to_chat(inquirer, span_warning("This [card] can no longer be used to house a pAI."))
|
||||
return
|
||||
|
||||
// Check if the ghost stopped existing
|
||||
var/mob/observer/ghost = get_ghost_from_ref(ghost_ref)
|
||||
if(!isobserver(ghost) || !ghost.client)
|
||||
to_chat(inquirer, span_warning("This pAI has gone offline."))
|
||||
return
|
||||
|
||||
// Time delay if the ghost cancels your invite.
|
||||
if(check_is_delayed(REF(ghost)))
|
||||
to_chat(inquirer, span_notice("This pAI is responding to a request, but may become available again shortly..."))
|
||||
return
|
||||
asked[REF(ghost)] = world.time
|
||||
|
||||
// Can't play, still respawning
|
||||
var/time_till_respawn = ghost.time_till_respawn()
|
||||
if(time_till_respawn == -1 || time_till_respawn)
|
||||
to_chat(inquirer, span_warning("This pAI is still downloading..."))
|
||||
return
|
||||
|
||||
// Send it!
|
||||
to_chat(inquirer, span_info("A request has been sent!"))
|
||||
var/client/target = ghost.client
|
||||
var/response = tgui_alert(target, "[inquirer] is requesting a pAI personality. Would you like to play as a personal AI?", "pAI Request", list("Yes", "No", "Never for this round"))
|
||||
if(!response || !target || !isobserver(target.mob) || ghost != target.mob)
|
||||
return // Nice try smartass
|
||||
if(check_is_already_pai(target.ckey))
|
||||
to_chat(inquirer, span_warning("This pAI has already been downloaded."))
|
||||
return
|
||||
if(QDELETED(card) || card.pai)
|
||||
to_chat(inquirer, span_warning("This [card] can no longer be used to house a pAI."))
|
||||
return
|
||||
|
||||
switch(response)
|
||||
if("Yes")
|
||||
var/new_pai = card.ghost_inhabit(target.mob, TRUE)
|
||||
to_chat(inquirer, span_info("[new_pai] has accepted your pAI request!"))
|
||||
return
|
||||
if("Never for this round")
|
||||
SSpai.block_pai_invites(REF(ghost))
|
||||
|
||||
to_chat(inquirer, span_warning("The pAI denied the request."))
|
||||
|
||||
/datum/controller/subsystem/pai/proc/block_pai_invites(ghost_ref)
|
||||
SHOULD_NOT_OVERRIDE(TRUE)
|
||||
asked[ghost_ref] = world.time + 99 HOURS // We never want to be asked again
|
||||
|
||||
/datum/controller/subsystem/pai/proc/clear_pai_block_delay(ghost_ref)
|
||||
SHOULD_NOT_OVERRIDE(TRUE)
|
||||
asked -= ghost_ref
|
||||
|
||||
#undef PAI_DELAY_TIME
|
||||
@@ -186,6 +186,9 @@
|
||||
|
||||
. = ..(W, user)
|
||||
|
||||
/obj/machinery/computer/general_air_control/allow_pai_interaction(proximity_flag)
|
||||
return proximity_flag
|
||||
|
||||
/obj/machinery/computer/general_air_control/receive_signal(datum/signal/signal)
|
||||
if(!signal || signal.encryption) return
|
||||
|
||||
|
||||
@@ -16,3 +16,6 @@
|
||||
|
||||
/obj/machinery/button/attackby(obj/item/W, mob/user as mob)
|
||||
return attack_hand(user)
|
||||
|
||||
/obj/machinery/button/allow_pai_interaction(proximity_flag)
|
||||
return proximity_flag
|
||||
|
||||
@@ -17,6 +17,9 @@
|
||||
/obj/machinery/computer/atmos_alert/attack_hand(mob/user)
|
||||
tgui_interact(user)
|
||||
|
||||
/obj/machinery/computer/atmos_alert/allow_pai_interaction(proximity_flag)
|
||||
return proximity_flag
|
||||
|
||||
/obj/machinery/computer/atmos_alert/tgui_interact(mob/user, datum/tgui/ui)
|
||||
ui = SStgui.try_update_ui(user, src, ui)
|
||||
if(!ui)
|
||||
|
||||
@@ -68,6 +68,9 @@
|
||||
density = FALSE
|
||||
circuit = null
|
||||
|
||||
/obj/machinery/computer/security/telescreen/allow_pai_interaction(proximity_flag)
|
||||
return proximity_flag
|
||||
|
||||
GLOBAL_LIST_EMPTY(entertainment_screens)
|
||||
/obj/machinery/computer/security/telescreen/entertainment
|
||||
name = "entertainment monitor"
|
||||
|
||||
@@ -28,6 +28,9 @@
|
||||
return
|
||||
tgui_interact(user)
|
||||
|
||||
/obj/machinery/computer/crew/allow_pai_interaction(proximity_flag)
|
||||
return proximity_flag
|
||||
|
||||
/obj/machinery/computer/crew/tgui_interact(mob/user, datum/tgui/ui = null)
|
||||
crew_monitor.tgui_interact(user, ui)
|
||||
|
||||
|
||||
@@ -41,6 +41,9 @@
|
||||
tgui_interact(user)
|
||||
return
|
||||
|
||||
/obj/machinery/computer/station_alert/allow_pai_interaction(proximity_flag)
|
||||
return proximity_flag
|
||||
|
||||
/obj/machinery/computer/station_alert/tgui_interact(mob/user)
|
||||
alarm_monitor.tgui_interact(user)
|
||||
|
||||
|
||||
@@ -270,6 +270,9 @@
|
||||
return TRUE
|
||||
return ..()
|
||||
|
||||
/obj/machinery/airlock_sensor/allow_pai_interaction(proximity_flag)
|
||||
return proximity_flag
|
||||
|
||||
/obj/machinery/airlock_sensor/airlock_interior
|
||||
command = "cycle_interior"
|
||||
|
||||
@@ -361,6 +364,8 @@
|
||||
radio_connection.post_signal(src, signal, range = AIRLOCK_CONTROL_RANGE, radio_filter = RADIO_AIRLOCK)
|
||||
flick("access_button_cycle", src)
|
||||
|
||||
/obj/machinery/access_button/allow_pai_interaction(proximity_flag)
|
||||
return proximity_flag
|
||||
|
||||
/obj/machinery/access_button/proc/set_frequency(new_frequency)
|
||||
SSradio.remove_object(src, frequency)
|
||||
|
||||
@@ -252,6 +252,9 @@
|
||||
/obj/machinery/media/jukebox/attack_hand(var/mob/user as mob)
|
||||
interact(user)
|
||||
|
||||
/obj/machinery/media/jukebox/allow_pai_interaction(proximity_flag)
|
||||
return proximity_flag
|
||||
|
||||
/obj/machinery/media/jukebox/proc/explode()
|
||||
walk_to(src,0)
|
||||
src.visible_message(span_danger("\The [src] blows apart!"), 1)
|
||||
|
||||
@@ -71,6 +71,9 @@
|
||||
area.power_change()
|
||||
GLOB.lights_switched_on_roundstat++
|
||||
|
||||
/obj/machinery/light_switch/allow_pai_interaction(proximity_flag)
|
||||
return proximity_flag
|
||||
|
||||
/obj/machinery/light_switch/power_change()
|
||||
|
||||
if(!otherarea)
|
||||
|
||||
@@ -273,6 +273,9 @@ GLOBAL_LIST_BOILERPLATE(allCasters, /obj/machinery/newscaster)
|
||||
|
||||
tgui_interact(user)
|
||||
|
||||
/obj/machinery/newscaster/allow_pai_interaction(proximity_flag)
|
||||
return proximity_flag
|
||||
|
||||
/**
|
||||
* Sets a temporary message to display to the user
|
||||
*
|
||||
|
||||
@@ -1,386 +0,0 @@
|
||||
GLOBAL_LIST_EMPTY(paikeys)
|
||||
|
||||
/obj/item/paicard
|
||||
var/panel_open = FALSE
|
||||
var/cell = PP_FUNCTIONAL //critical- power
|
||||
var/processor = PP_FUNCTIONAL //critical- the thinky part
|
||||
var/board = PP_FUNCTIONAL //critical- makes everything work
|
||||
var/capacitor = PP_FUNCTIONAL //critical- power processing
|
||||
var/projector = PP_FUNCTIONAL //non-critical- affects unfolding
|
||||
var/emitter = PP_FUNCTIONAL //non-critical- affects unfolding
|
||||
var/speech_synthesizer = PP_FUNCTIONAL //non-critical- affects speech
|
||||
|
||||
/obj/item/paicard/attackby(var/obj/item/I as obj, mob/user as mob)
|
||||
if(istype(I,/obj/item/tool/screwdriver))
|
||||
if(panel_open)
|
||||
panel_open = FALSE
|
||||
user.visible_message(span_notice("\The [user] secured \the [src]'s maintenance panel."))
|
||||
playsound(src, 'sound/items/Screwdriver.ogg', 50, 1)
|
||||
else if(pai)
|
||||
if(do_after(user, 3 SECONDS, target = src))
|
||||
panel_open = TRUE
|
||||
user.visible_message(span_warning("\The [user] opened \the [src]'s maintenance panel."))
|
||||
playsound(src, 'sound/items/Screwdriver.ogg', 50, 1)
|
||||
if(istype(I,/obj/item/robotanalyzer))
|
||||
if(!panel_open)
|
||||
to_chat(user, span_warning("The panel isn't open. You will need to unscrew it to open it."))
|
||||
else
|
||||
if(cell == PP_FUNCTIONAL)
|
||||
to_chat(user,"Power cell: " + span_notice("functional"))
|
||||
else if(cell == PP_BROKEN)
|
||||
to_chat(user,"Power cell: " + span_warning("damaged - CRITICAL"))
|
||||
else
|
||||
to_chat(user,"Power cell: " + span_warning("missing - CRITICAL"))
|
||||
|
||||
if(processor == PP_FUNCTIONAL)
|
||||
to_chat(user,"Processor: " + span_notice("functional"))
|
||||
else if(processor == PP_BROKEN)
|
||||
to_chat(user,"Processor: " + span_warning("damaged - CRITICAL"))
|
||||
else
|
||||
to_chat(user,"Processor: " + span_warning("missing - CRITICAL"))
|
||||
|
||||
if(board == PP_FUNCTIONAL)
|
||||
to_chat(user,"Board: " + span_notice("functional"))
|
||||
else if(board == PP_BROKEN)
|
||||
to_chat(user,"Board: " + span_warning("damaged - CRITICAL"))
|
||||
else
|
||||
to_chat(user,"Board: " + span_warning("missing - CRITICAL"))
|
||||
|
||||
if(capacitor == PP_FUNCTIONAL)
|
||||
to_chat(user,"Capacitors: " + span_notice("functional"))
|
||||
else if(capacitor == PP_BROKEN)
|
||||
to_chat(user,"Capacitors: " + span_warning("damaged - CRITICAL"))
|
||||
else
|
||||
to_chat(user,"Capacitors: " + span_warning("missing - CRITICAL"))
|
||||
|
||||
if(projector == PP_FUNCTIONAL)
|
||||
to_chat(user,"Projectors: " + span_notice("functional"))
|
||||
else if(projector == PP_BROKEN)
|
||||
to_chat(user,"Projectors: " + span_warning("damaged"))
|
||||
else
|
||||
to_chat(user,"Projectors: " + span_warning("missing"))
|
||||
|
||||
if(emitter == PP_FUNCTIONAL)
|
||||
to_chat(user,"Emitters: " + span_notice("functional"))
|
||||
else if(emitter == PP_BROKEN)
|
||||
to_chat(user,"Emitters: " + span_warning("damaged"))
|
||||
else
|
||||
to_chat(user,"Emitters: " + span_warning("missing"))
|
||||
|
||||
if(speech_synthesizer == PP_FUNCTIONAL)
|
||||
to_chat(user,"Speech Synthesizer: " + span_notice("functional"))
|
||||
else if(speech_synthesizer == PP_BROKEN)
|
||||
to_chat(user,"Speech Synthesizer: " + span_warning("damaged"))
|
||||
else
|
||||
to_chat(user,"Speech Synthesizer: " + span_warning("missing"))
|
||||
|
||||
if(istype(I,/obj/item/multitool))
|
||||
if(!panel_open)
|
||||
to_chat(user, span_warning("You can't do that in this state."))
|
||||
else
|
||||
var/list/parts = list()
|
||||
if(cell != PP_MISSING)
|
||||
parts |= "cell"
|
||||
if(processor != PP_MISSING)
|
||||
parts |= "processor"
|
||||
if(board != PP_MISSING)
|
||||
parts |= "board"
|
||||
if(capacitor != PP_MISSING)
|
||||
parts |= "capacitor"
|
||||
if(projector != PP_MISSING)
|
||||
parts |= "projector"
|
||||
if(emitter != PP_MISSING)
|
||||
parts |= "emitter"
|
||||
if(speech_synthesizer != PP_MISSING)
|
||||
parts |= "speech synthesizer"
|
||||
|
||||
var/choice = tgui_input_list(user, "Which part would you like to check?", "Check part", parts)
|
||||
switch(choice)
|
||||
if("cell")
|
||||
if(cell == PP_FUNCTIONAL)
|
||||
to_chat(user,"Power cell: " + span_notice("functional"))
|
||||
else if(speech_synthesizer == PP_BROKEN)
|
||||
to_chat(user,"Power cell: " + span_warning("damaged"))
|
||||
else
|
||||
to_chat(user,"Power cell: " + span_warning("missing"))
|
||||
|
||||
if("processor")
|
||||
if(processor == PP_FUNCTIONAL)
|
||||
to_chat(user,"Processor: " + span_notice("functional"))
|
||||
else if(speech_synthesizer == PP_BROKEN)
|
||||
to_chat(user,"Processor: " + span_warning("damaged"))
|
||||
else
|
||||
to_chat(user,"Processor: " + span_warning("missing"))
|
||||
|
||||
if("board")
|
||||
if(board == PP_FUNCTIONAL)
|
||||
to_chat(user,"Board: " + span_notice("functional"))
|
||||
else if(speech_synthesizer == PP_BROKEN)
|
||||
to_chat(user,"Board: " + span_warning("damaged"))
|
||||
else
|
||||
to_chat(user,"Board: " + span_warning("missing"))
|
||||
|
||||
if("capacitor")
|
||||
if(capacitor == PP_FUNCTIONAL)
|
||||
to_chat(user,"Capacitors: " + span_notice("functional"))
|
||||
else if(speech_synthesizer == PP_BROKEN)
|
||||
to_chat(user,"Capacitors: " + span_warning("damaged"))
|
||||
else
|
||||
to_chat(user,"Capacitors: " + span_warning("missing"))
|
||||
|
||||
if("projector")
|
||||
if(projector == PP_FUNCTIONAL)
|
||||
to_chat(user,"Projectors: " + span_notice("functional"))
|
||||
else if(speech_synthesizer == PP_BROKEN)
|
||||
to_chat(user,"Projectors: " + span_warning("damaged"))
|
||||
else
|
||||
to_chat(user,"Projectors: " + span_warning("missing"))
|
||||
|
||||
if("emitter")
|
||||
if(emitter == PP_FUNCTIONAL)
|
||||
to_chat(user,"Emitters: " + span_notice("functional"))
|
||||
else if(speech_synthesizer == PP_BROKEN)
|
||||
to_chat(user,"Emitters: " + span_warning("damaged"))
|
||||
else
|
||||
to_chat(user,"Emitters: " + span_warning("missing"))
|
||||
|
||||
if("speech synthesizer")
|
||||
if(speech_synthesizer == PP_FUNCTIONAL)
|
||||
to_chat(user,"Speech Synthesizer: " + span_notice("functional"))
|
||||
else if(speech_synthesizer == PP_BROKEN)
|
||||
to_chat(user,"Speech Synthesizer: " + span_warning("damaged"))
|
||||
else
|
||||
to_chat(user,"Speech Synthesizer: " + span_warning("missing"))
|
||||
|
||||
if(istype(I,/obj/item/paiparts/cell))
|
||||
if(cell == PP_MISSING)
|
||||
if(do_after(user, 3 SECONDS, target = src))
|
||||
user.visible_message(span_notice("\The [user] installs \the [I] into \the [src]."),span_notice("You install \the [I] into \the [src]."))
|
||||
cell = PP_FUNCTIONAL
|
||||
user.drop_from_inventory(I)
|
||||
qdel(I)
|
||||
else
|
||||
to_chat(user, span_warning("You would need to remove the installed [I] first!"))
|
||||
if(istype(I,/obj/item/paiparts/processor))
|
||||
if(processor == PP_MISSING)
|
||||
if(do_after(user, 3 SECONDS, target = src))
|
||||
user.visible_message(span_notice("\The [user] installs \the [I] into \the [src]."),span_notice("You install \the [I] into \the [src]."))
|
||||
processor = PP_FUNCTIONAL
|
||||
user.drop_from_inventory(I)
|
||||
qdel(I)
|
||||
else
|
||||
to_chat(user, span_warning("You would need to remove the installed [I] first!"))
|
||||
if(istype(I,/obj/item/paiparts/board))
|
||||
if(board == PP_MISSING)
|
||||
if(do_after(user, 3 SECONDS, target = src))
|
||||
user.visible_message(span_notice("\The [user] installs \the [I] into \the [src]."),span_notice("You install \the [I] into \the [src]."))
|
||||
board = PP_FUNCTIONAL
|
||||
user.drop_from_inventory(I)
|
||||
qdel(I)
|
||||
else
|
||||
to_chat(user, span_warning("You would need to remove the installed [I] first!"))
|
||||
if(istype(I,/obj/item/paiparts/capacitor))
|
||||
if(capacitor == PP_MISSING)
|
||||
if(do_after(user, 3 SECONDS, target = src))
|
||||
user.visible_message(span_notice("\The [user] installs \the [I] into \the [src]."),span_notice("You install \the [I] into \the [src]."))
|
||||
capacitor = PP_FUNCTIONAL
|
||||
user.drop_from_inventory(I)
|
||||
qdel(I)
|
||||
else
|
||||
to_chat(user, span_warning("You would need to remove the installed [I] first!"))
|
||||
if(istype(I,/obj/item/paiparts/projector))
|
||||
if(projector == PP_MISSING)
|
||||
if(do_after(user, 3 SECONDS, target = src))
|
||||
user.visible_message(span_notice("\The [user] installs \the [I] into \the [src]."),span_notice("You install \the [I] into \the [src]."))
|
||||
projector = PP_FUNCTIONAL
|
||||
user.drop_from_inventory(I)
|
||||
qdel(I)
|
||||
else
|
||||
to_chat(user, span_warning("You would need to remove the installed [I] first!"))
|
||||
if(istype(I,/obj/item/paiparts/emitter))
|
||||
if(emitter == PP_MISSING)
|
||||
if(do_after(user, 3 SECONDS, target = src))
|
||||
user.visible_message(span_notice("\The [user] installs \the [I] into \the [src]."),span_notice("You install \the [I] into \the [src]."))
|
||||
emitter = PP_FUNCTIONAL
|
||||
user.drop_from_inventory(I)
|
||||
qdel(I)
|
||||
else
|
||||
to_chat(user, span_warning("You would need to remove the installed [I] first!"))
|
||||
if(istype(I,/obj/item/paiparts/speech_synthesizer))
|
||||
if(speech_synthesizer == PP_MISSING)
|
||||
if(do_after(user, 3 SECONDS, target = src))
|
||||
user.visible_message(span_notice("\The [user] installs \the [I] into \the [src]."),span_notice("You install \the [I] into \the [src]."))
|
||||
speech_synthesizer = PP_FUNCTIONAL
|
||||
user.drop_from_inventory(I)
|
||||
qdel(I)
|
||||
else
|
||||
to_chat(user, span_warning("You would need to remove the installed [I] first!"))
|
||||
|
||||
/obj/item/paicard/attack_self(mob/user, callback)
|
||||
. = ..(user)
|
||||
if(.)
|
||||
return TRUE
|
||||
if(special_handling && !callback)
|
||||
return FALSE
|
||||
if(!panel_open)
|
||||
access_screen(user)
|
||||
return
|
||||
var/list/parts = list()
|
||||
if(cell != PP_MISSING)
|
||||
parts |= "cell"
|
||||
if(processor != PP_MISSING)
|
||||
parts |= "processor"
|
||||
if(board != PP_MISSING)
|
||||
parts |= "board"
|
||||
if(capacitor != PP_MISSING)
|
||||
parts |= "capacitor"
|
||||
if(projector != PP_MISSING)
|
||||
parts |= "projector"
|
||||
if(emitter != PP_MISSING)
|
||||
parts |= "emitter"
|
||||
if(speech_synthesizer != PP_MISSING)
|
||||
parts |= "speech synthesizer"
|
||||
|
||||
var/choice = tgui_input_list(user, "Which part would you like to remove?", "Remove part", parts)
|
||||
if(choice)
|
||||
playsound(src, 'sound/items/pickup/component.ogg', vary = TRUE)
|
||||
else
|
||||
return
|
||||
if(!do_after(user, 3 SECONDS, target = src))
|
||||
return
|
||||
switch(choice)
|
||||
if("cell")
|
||||
if(cell == PP_FUNCTIONAL)
|
||||
new /obj/item/paiparts/cell(get_turf(user))
|
||||
else
|
||||
new /obj/item/paiparts(get_turf(user))
|
||||
user.visible_message(span_warning("\The [user] removes \the [choice] from \the [src]."),span_warning("You remove \the [choice] from \the [src]."))
|
||||
cell = PP_MISSING
|
||||
if("processor")
|
||||
if(processor == PP_FUNCTIONAL)
|
||||
new /obj/item/paiparts/processor(get_turf(user))
|
||||
else
|
||||
new /obj/item/paiparts(get_turf(user))
|
||||
user.visible_message(span_warning("\The [user] removes \the [choice] from \the [src]."),span_warning("You remove \the [choice] from \the [src]."))
|
||||
processor = PP_MISSING
|
||||
if("board")
|
||||
board = PP_MISSING
|
||||
if(board == PP_FUNCTIONAL)
|
||||
new /obj/item/paiparts/board(get_turf(user))
|
||||
else
|
||||
new /obj/item/paiparts(get_turf(user))
|
||||
user.visible_message(span_warning("\The [user] removes \the [choice] from \the [src]."),span_warning("You remove \the [choice] from \the [src]."))
|
||||
|
||||
if("capacitor")
|
||||
if(capacitor == PP_FUNCTIONAL)
|
||||
new /obj/item/paiparts/capacitor(get_turf(user))
|
||||
else
|
||||
new /obj/item/paiparts(get_turf(user))
|
||||
user.visible_message(span_warning("\The [user] removes \the [choice] from \the [src]."),span_warning("You remove \the [choice] from \the [src]."))
|
||||
capacitor = PP_MISSING
|
||||
if("projector")
|
||||
if(projector == PP_FUNCTIONAL)
|
||||
new /obj/item/paiparts/projector(get_turf(user))
|
||||
else
|
||||
new /obj/item/paiparts(get_turf(user))
|
||||
user.visible_message(span_warning("\The [user] removes \the [choice] from \the [src]."),span_warning("You remove \the [choice] from \the [src]."))
|
||||
projector = PP_MISSING
|
||||
if("emitter")
|
||||
if(emitter == PP_FUNCTIONAL)
|
||||
new /obj/item/paiparts/emitter(get_turf(user))
|
||||
else
|
||||
new /obj/item/paiparts(get_turf(user))
|
||||
user.visible_message(span_warning("\The [user] removes \the [choice] from \the [src]."),span_warning("You remove \the [choice] from \the [src]."))
|
||||
emitter = PP_MISSING
|
||||
if("speech synthesizer")
|
||||
if(speech_synthesizer == PP_FUNCTIONAL)
|
||||
new /obj/item/paiparts/speech_synthesizer(get_turf(user))
|
||||
else
|
||||
new /obj/item/paiparts(get_turf(user))
|
||||
user.visible_message(span_warning("\The [user] removes \the [choice] from \the [src]."),span_warning("You remove \the [choice] from \the [src]."))
|
||||
speech_synthesizer = PP_MISSING
|
||||
|
||||
/obj/item/paicard/proc/death_damage()
|
||||
|
||||
var/number = rand(1,4)
|
||||
while(number)
|
||||
number --
|
||||
switch(rand(1,4))
|
||||
if(1)
|
||||
cell = PP_BROKEN
|
||||
if(2)
|
||||
processor = PP_BROKEN
|
||||
if(3)
|
||||
board = PP_BROKEN
|
||||
if(4)
|
||||
capacitor = PP_BROKEN
|
||||
|
||||
|
||||
/obj/item/paicard/proc/damage_random_component(nonfatal = FALSE)
|
||||
var/datum/effect/effect/system/spark_spread/s = new /datum/effect/effect/system/spark_spread
|
||||
s.set_up(2, 1, src)
|
||||
s.start()
|
||||
if(prob(80) || nonfatal) //Way more likely to be non-fatal part damage
|
||||
switch(rand(1,3))
|
||||
if(1)
|
||||
projector = PP_BROKEN
|
||||
if(2)
|
||||
emitter = PP_BROKEN
|
||||
if(3)
|
||||
speech_synthesizer = PP_BROKEN
|
||||
else
|
||||
switch(rand(1,4))
|
||||
if(1)
|
||||
cell = PP_BROKEN
|
||||
if(2)
|
||||
processor = PP_BROKEN
|
||||
if(3)
|
||||
board = PP_BROKEN
|
||||
if(4)
|
||||
capacitor = PP_BROKEN
|
||||
|
||||
/obj/item/paicard/proc/is_damage_critical()
|
||||
if(cell != PP_FUNCTIONAL || processor != PP_FUNCTIONAL || board != PP_FUNCTIONAL || capacitor != PP_FUNCTIONAL)
|
||||
return TRUE
|
||||
return FALSE
|
||||
|
||||
/obj/item/paiparts
|
||||
name = "broken pAI component"
|
||||
desc = "It's broken scrap from a pAI card!"
|
||||
icon = 'icons/obj/paicard.dmi'
|
||||
icon_state = "broken"
|
||||
pickup_sound = 'sound/items/pickup/card.ogg'
|
||||
drop_sound = 'sound/items/drop/card.ogg'
|
||||
/obj/item/paiparts/Initialize(mapload)
|
||||
. = ..()
|
||||
pixel_x = rand(-10,10)
|
||||
pixel_y = rand(-10,10)
|
||||
|
||||
/obj/item/paiparts/cell
|
||||
name = "pAI power cell"
|
||||
desc = "It's very small and efficient! It powers the pAI!"
|
||||
icon_state = "cell"
|
||||
/obj/item/paiparts/processor
|
||||
name = "pAI processor"
|
||||
desc = "It's the brain of your computer friend!"
|
||||
icon_state = "processor"
|
||||
/obj/item/paiparts/board
|
||||
name = "pAI board"
|
||||
desc = "It's the thing all the other parts get attatched to!"
|
||||
icon_state = "board"
|
||||
/obj/item/paiparts/capacitor
|
||||
name = "pAI capacitor"
|
||||
desc = "It helps regulate power flow!"
|
||||
icon_state = "capacitor"
|
||||
/obj/item/paiparts/projector
|
||||
name = "pAI projector"
|
||||
desc = "It projects the pAI's form!"
|
||||
icon_state = "projector"
|
||||
/obj/item/paiparts/emitter
|
||||
name = "pAI emitter"
|
||||
desc = "It emits the fields to help the pAI get around!"
|
||||
icon_state = "emitter"
|
||||
/obj/item/paiparts/speech_synthesizer
|
||||
name = "pAI speech synthesizer"
|
||||
desc = "It's a little voice box!"
|
||||
icon_state = "speech_synthesizer"
|
||||
@@ -144,6 +144,10 @@
|
||||
/obj/proc/container_resist(var/mob/living)
|
||||
return
|
||||
|
||||
// If returns true, pai can interact with the object with a click
|
||||
/obj/proc/allow_pai_interaction(proximity_flag)
|
||||
return FALSE
|
||||
|
||||
//To be called from things that spill objects on the floor.
|
||||
//Makes an object move around randomly for a couple of tiles
|
||||
/obj/proc/tumble(var/dist = 2)
|
||||
|
||||
@@ -539,6 +539,9 @@
|
||||
M.Translate(closet_appearance.door_hinge, 0)
|
||||
return M
|
||||
|
||||
/obj/structure/closet/allow_pai_interaction(proximity_flag)
|
||||
return proximity_flag
|
||||
|
||||
//verb to eat people in the same closet as yourself
|
||||
|
||||
/obj/structure/closet/verb/hidden_vore()
|
||||
|
||||
@@ -211,6 +211,9 @@
|
||||
if(get_dist(user,src) <= 1) //not remotely though
|
||||
return attack_hand(user)
|
||||
|
||||
/obj/structure/fence/door/allow_pai_interaction(proximity_flag)
|
||||
return proximity_flag
|
||||
|
||||
/obj/structure/fence/door/proc/toggle(mob/user)
|
||||
switch(open)
|
||||
if(FALSE)
|
||||
|
||||
@@ -201,6 +201,9 @@
|
||||
if(istype(I, /obj/item/analyzer)) //Lol? Why...
|
||||
to_chat(user, span_notice("The water temperature seems to be [current_temperature]."))
|
||||
|
||||
/obj/machinery/shower/allow_pai_interaction(proximity_flag)
|
||||
return proximity_flag
|
||||
|
||||
/obj/machinery/shower/click_alt(mob/user)
|
||||
..()
|
||||
var/list/temperature_settings = list(SHOWER_NORMAL, SHOWER_BOILING, SHOWER_FREEZING)
|
||||
|
||||
@@ -150,13 +150,9 @@
|
||||
pai.key = choice.key
|
||||
card.setPersonality(pai)
|
||||
if(tgui_alert(pai, "Do you want to load your pAI data?", "Load", list("Yes", "No")) == "Yes")
|
||||
pai.savefile_load(pai)
|
||||
pai.apply_preferences(pai.client)
|
||||
else
|
||||
pai.name = sanitizeSafe(tgui_input_text(pai, "Enter your pAI name:", "pAI Name", "Personal AI", encode = FALSE))
|
||||
card.setPersonality(pai)
|
||||
for(var/datum/paiCandidate/candidate in paiController.pai_candidates)
|
||||
if(candidate.key == choice.key)
|
||||
paiController.pai_candidates.Remove(candidate)
|
||||
log_admin("made a pAI with key=[pai.key] at ([T.x],[T.y],[T.z])")
|
||||
feedback_add_details("admin_verb","MPAI") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc!
|
||||
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
/datum/asset/spritesheet_batched/pai_icons
|
||||
name = "pai_icons"
|
||||
|
||||
/datum/asset/spritesheet_batched/pai_icons/create_spritesheets()
|
||||
for(var/name, current_sprite in SSpai.get_chassis_list())
|
||||
var/datum/pai_sprite/sprite = current_sprite
|
||||
if(!sprite.name || !sprite.sprite_icon_state)
|
||||
continue
|
||||
|
||||
var/datum/universal_icon/I_N = uni_icon(sprite.sprite_icon, sprite.sprite_icon_state, NORTH)
|
||||
var/datum/universal_icon/I_S = uni_icon(sprite.sprite_icon, sprite.sprite_icon_state, SOUTH)
|
||||
var/datum/universal_icon/I_W = uni_icon(sprite.sprite_icon, sprite.sprite_icon_state, WEST)
|
||||
var/datum/universal_icon/I_E = uni_icon(sprite.sprite_icon, sprite.sprite_icon_state, EAST)
|
||||
|
||||
if(sprite.icon_x > sprite.icon_y)
|
||||
var/buffer = (sprite.icon_x - sprite.icon_y) / 2
|
||||
I_N.crop(0, -buffer, sprite.icon_x, sprite.icon_y + buffer)
|
||||
I_S.crop(0, -buffer, sprite.icon_x, sprite.icon_y + buffer)
|
||||
I_W.crop(0, -buffer, sprite.icon_x, sprite.icon_y + buffer)
|
||||
I_E.crop(0, -buffer, sprite.icon_x, sprite.icon_y + buffer)
|
||||
else if (sprite.icon_x < sprite.icon_y)
|
||||
var/buffer = (sprite.icon_y - sprite.icon_x) / 2
|
||||
I_N.crop(-buffer, 0, sprite.icon_x + buffer, sprite.icon_y)
|
||||
I_S.crop(-buffer, 0, sprite.icon_x + buffer, sprite.icon_y)
|
||||
I_W.crop(-buffer, 0, sprite.icon_x + buffer, sprite.icon_y)
|
||||
I_E.crop(-buffer, 0, sprite.icon_x + buffer, sprite.icon_y)
|
||||
I_N.scale(120, 120)
|
||||
I_S.scale(120, 120)
|
||||
I_W.scale(120, 120)
|
||||
I_E.scale(120, 120)
|
||||
|
||||
var/imgid = sanitize_css_class_name("[sprite.type]")
|
||||
insert_icon(imgid + "N", I_N)
|
||||
insert_icon(imgid + "S", I_S)
|
||||
insert_icon(imgid + "W", I_W)
|
||||
insert_icon(imgid + "E", I_E)
|
||||
|
||||
if(sprite.has_eye_sprites)
|
||||
var/datum/universal_icon/I_NE = uni_icon(sprite.sprite_icon, "[sprite.sprite_icon_state]-eyes", NORTH)
|
||||
var/datum/universal_icon/I_SE = uni_icon(sprite.sprite_icon, "[sprite.sprite_icon_state]-eyes", SOUTH)
|
||||
var/datum/universal_icon/I_WE = uni_icon(sprite.sprite_icon, "[sprite.sprite_icon_state]-eyes", WEST)
|
||||
var/datum/universal_icon/I_EE = uni_icon(sprite.sprite_icon, "[sprite.sprite_icon_state]-eyes", EAST)
|
||||
|
||||
if(sprite.icon_x > sprite.icon_y)
|
||||
var/buffer = (sprite.icon_x - sprite.icon_y) / 2
|
||||
I_NE.crop(0, -buffer, sprite.icon_x, sprite.icon_y + buffer)
|
||||
I_SE.crop(0, -buffer, sprite.icon_x, sprite.icon_y + buffer)
|
||||
I_WE.crop(0, -buffer, sprite.icon_x, sprite.icon_y + buffer)
|
||||
I_EE.crop(0, -buffer, sprite.icon_x, sprite.icon_y + buffer)
|
||||
else if (sprite.icon_x < sprite.icon_y)
|
||||
var/buffer = (sprite.icon_y - sprite.icon_x) / 2
|
||||
I_NE.crop(-buffer, 0, sprite.icon_x + buffer, sprite.icon_y)
|
||||
I_SE.crop(-buffer, 0, sprite.icon_x + buffer, sprite.icon_y)
|
||||
I_WE.crop(-buffer, 0, sprite.icon_x + buffer, sprite.icon_y)
|
||||
I_EE.crop(-buffer, 0, sprite.icon_x + buffer, sprite.icon_y)
|
||||
I_NE.scale(120, 120)
|
||||
I_SE.scale(120, 120)
|
||||
I_WE.scale(120, 120)
|
||||
I_EE.scale(120, 120)
|
||||
|
||||
insert_icon(imgid + "NE", I_NE)
|
||||
insert_icon(imgid + "SE", I_SE)
|
||||
insert_icon(imgid + "WE", I_WE)
|
||||
insert_icon(imgid + "EE", I_EE)
|
||||
@@ -103,20 +103,19 @@ GLOBAL_LIST_EMPTY(robot_sprite_sheets)
|
||||
var/datum/universal_icon/I_NE = uni_icon(S.sprite_icon, "[S.sprite_icon_state]-eyes", NORTH)
|
||||
if(I_NE)
|
||||
I_N.blend_icon(I_NE, ICON_OVERLAY)
|
||||
if(S.has_eye_sprites)
|
||||
|
||||
var/datum/universal_icon/I_SE = uni_icon(S.sprite_icon, "[S.sprite_icon_state]-eyes", SOUTH)
|
||||
if(I_SE)
|
||||
I_S.blend_icon(I_SE, ICON_OVERLAY)
|
||||
if(S.has_eye_sprites)
|
||||
|
||||
var/datum/universal_icon/I_WE = uni_icon(S.sprite_icon, "[S.sprite_icon_state]-eyes", WEST)
|
||||
if(I_WE)
|
||||
I_W.blend_icon(I_WE, ICON_OVERLAY)
|
||||
if(S.has_eye_sprites)
|
||||
|
||||
var/datum/universal_icon/I_EE = uni_icon(S.sprite_icon, "[S.sprite_icon_state]-eyes", EAST)
|
||||
if(I_EE)
|
||||
I_E.blend_icon(I_EE, ICON_OVERLAY)
|
||||
|
||||
var/imgid = sanitize_css_class_name("[S.type]")
|
||||
if(S.icon_x > S.icon_y)
|
||||
var/buffer = (S.icon_x - S.icon_y) / 2
|
||||
I_N.crop(0, -buffer, S.icon_x, S.icon_y + buffer)
|
||||
@@ -134,6 +133,7 @@ GLOBAL_LIST_EMPTY(robot_sprite_sheets)
|
||||
I_W.scale(120, 120)
|
||||
I_E.scale(120, 120)
|
||||
|
||||
var/imgid = sanitize_css_class_name("[S.type]")
|
||||
insert_icon(imgid + "N", I_N)
|
||||
insert_icon(imgid + "S", I_S)
|
||||
insert_icon(imgid + "W", I_W)
|
||||
|
||||
@@ -2,12 +2,6 @@
|
||||
name = "Basic"
|
||||
sort_order = 7
|
||||
|
||||
var/datum/paiCandidate/candidate
|
||||
|
||||
/datum/category_item/player_setup_item/general/basic_antagonism/New()
|
||||
. = ..()
|
||||
candidate = new()
|
||||
|
||||
/datum/category_item/player_setup_item/general/basic_antagonism/load_character(list/save_data)
|
||||
pref.exploit_record = save_data["exploit_record"]
|
||||
pref.antag_faction = save_data["antag_faction"]
|
||||
@@ -19,29 +13,18 @@
|
||||
save_data["antag_vis"] = pref.antag_vis
|
||||
|
||||
/datum/category_item/player_setup_item/general/basic_antagonism/load_preferences(datum/json_savefile/savefile)
|
||||
if(!candidate)
|
||||
candidate = new()
|
||||
|
||||
var/preference_mob = preference_mob()
|
||||
if(!preference_mob)// No preference mob - this happens when we're called from client/New() before it calls ..() (via datum/preferences/New())
|
||||
spawn()
|
||||
preference_mob = preference_mob()
|
||||
if(!preference_mob)
|
||||
return
|
||||
candidate.savefile_load(preference_mob)
|
||||
return
|
||||
|
||||
candidate.savefile_load(preference_mob)
|
||||
|
||||
/datum/category_item/player_setup_item/general/basic_antagonism/save_preferences(datum/json_savefile/savefile)
|
||||
if(!candidate)
|
||||
return
|
||||
|
||||
if(!preference_mob())
|
||||
return
|
||||
|
||||
candidate.savefile_save(preference_mob())
|
||||
|
||||
/datum/category_item/player_setup_item/general/basic_antagonism/sanitize_character()
|
||||
if(!pref.antag_faction) pref.antag_faction = "None"
|
||||
if(!pref.antag_vis) pref.antag_vis = "Hidden"
|
||||
@@ -62,13 +45,23 @@
|
||||
if(!jobban_isbanned(user, "Records"))
|
||||
data["exploitable_record"] = TextPreview(pref.exploit_record, 40)
|
||||
|
||||
if(!candidate)
|
||||
CRASH("[user] pAI prefs have a null candidate var.")
|
||||
data["pai_name"] = pref.read_preference(/datum/preference/text/pai_name)
|
||||
data["pai_desc"] = pref.read_preference(/datum/preference/text/pai_description)
|
||||
data["pai_role"] = pref.read_preference(/datum/preference/text/pai_role)
|
||||
data["pai_ad"] = pref.read_preference(/datum/preference/text/pai_ad)
|
||||
data["pai_comments"] = pref.read_preference(/datum/preference/text/pai_comments)
|
||||
data["pai_eyecolor"] = pref.read_preference(/datum/preference/color/pai_eye_color)
|
||||
var/pai_chassis = pref.read_preference(/datum/preference/text/pai_chassis)
|
||||
data["pai_chassis"] = pai_chassis
|
||||
data["pai_chassises"] = SSpai.get_chassis_list()
|
||||
data["pai_emotion"] = pref.read_preference(/datum/preference/text/pai_emotion)
|
||||
data["pai_emotions"] = GLOB.pai_emotions
|
||||
|
||||
data["pai_name"] = candidate.name ? candidate.name : "None Set"
|
||||
data["pai_desc"] = candidate.description ? TextPreview(candidate.description, 40) : "None Set"
|
||||
data["pai_role"] = candidate.role ? TextPreview(candidate.role, 40) : "None Set"
|
||||
data["pai_comments"] = candidate.comments ? TextPreview(candidate.comments, 40) : "None Set"
|
||||
var/datum/pai_sprite/sprite_datum = SSpai.chassis_data(pai_chassis)
|
||||
if(sprite_datum)
|
||||
var/datum/asset/spritesheet_batched/pai_icons/spritesheet = get_asset_datum(/datum/asset/spritesheet_batched/pai_icons)
|
||||
data["pai_sprite_datum_class"] = sanitize_css_class_name("[sprite_datum.type]")
|
||||
data["pai_sprite_datum_size"] = spritesheet.icon_size_id(data["pai_sprite_datum_class"] + "S") // just get the south icon's size, the rest will be the same
|
||||
|
||||
return data
|
||||
|
||||
@@ -111,23 +104,39 @@
|
||||
pref.antag_vis = choice
|
||||
return TOPIC_REFRESH
|
||||
|
||||
if("option")
|
||||
if("pai_option")
|
||||
var/t
|
||||
switch(params["option"])
|
||||
switch(params["pai_option"])
|
||||
if("name")
|
||||
t = sanitizeName(tgui_input_text(user, "Enter a name for your pAI", "Global Preference", candidate.name, MAX_NAME_LEN), MAX_NAME_LEN, 1)
|
||||
t = sanitizeName(params["name"], MAX_NAME_LEN, TRUE)
|
||||
if(t && CanUseTopic(user))
|
||||
candidate.name = t
|
||||
pref.update_preference_by_type(/datum/preference/text/pai_name, t)
|
||||
if("desc")
|
||||
t = tgui_input_text(user, "Enter a description for your pAI", "Global Preference", html_decode(candidate.description), multiline = TRUE, prevent_enter = TRUE)
|
||||
t = sanitize(params["desc"], MAX_MESSAGE_LEN * 4)
|
||||
if(!isnull(t) && CanUseTopic(user))
|
||||
candidate.description = sanitize(t)
|
||||
pref.update_preference_by_type(/datum/preference/text/pai_description, sanitize(t))
|
||||
if("ad")
|
||||
t = sanitize(params["ad"])
|
||||
if(!isnull(t) && CanUseTopic(user))
|
||||
pref.update_preference_by_type(/datum/preference/text/pai_ad, sanitize(t))
|
||||
if("role")
|
||||
t = tgui_input_text(user, "Enter a role for your pAI", "Global Preference", html_decode(candidate.role))
|
||||
t = sanitize(params["role"])
|
||||
if(!isnull(t) && CanUseTopic(user))
|
||||
candidate.role = sanitize(t)
|
||||
pref.update_preference_by_type(/datum/preference/text/pai_role, sanitize(t))
|
||||
if("ooc")
|
||||
t = tgui_input_text(user, "Enter any OOC comments", "Global Preference", html_decode(candidate.comments), multiline = TRUE, prevent_enter = TRUE)
|
||||
t = sanitize(params["ooc"])
|
||||
if(!isnull(t) && CanUseTopic(user))
|
||||
candidate.comments = sanitize(t)
|
||||
pref.update_preference_by_type(/datum/preference/text/pai_comments, sanitize(t))
|
||||
if("color")
|
||||
var/new_color = sanitize_hexcolor(params["color"])
|
||||
if(new_color && CanUseTopic(user))
|
||||
pref.update_preference_by_type(/datum/preference/color/pai_eye_color, new_color)
|
||||
if("chassis")
|
||||
var/new_chassis = params["chassis"]
|
||||
if(new_chassis && CanUseTopic(user) && (new_chassis in SSpai.get_chassis_list()))
|
||||
pref.update_preference_by_type(/datum/preference/text/pai_chassis, new_chassis)
|
||||
if("emotion")
|
||||
var/new_emotion = params["emotion"]
|
||||
if(new_emotion && CanUseTopic(user) && (new_emotion in GLOB.pai_emotions))
|
||||
pref.update_preference_by_type(/datum/preference/text/pai_emotion, new_emotion)
|
||||
return TOPIC_REFRESH
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
/// Moves pai save file to tg pref in current file slot
|
||||
/datum/preferences/proc/migration_19_paifile(datum/json_savefile/S)
|
||||
//Not everyone has a pai savefile.
|
||||
var/pai_path = "data/player_saves/[copytext(client_ckey, 1, 2)]/[client_ckey]/pai.sav"
|
||||
if(!fexists(pai_path))
|
||||
return
|
||||
var/savefile/F = new /savefile(pai_path)
|
||||
if(!F)
|
||||
return
|
||||
|
||||
// Should never happen, but if someone has a wonked out file then lets avoid it too
|
||||
var/version = null
|
||||
F["version"] >> version
|
||||
if(isnull(version) || version != 1)
|
||||
fdel(pai_path)
|
||||
return
|
||||
|
||||
// Put the pai in slot 0
|
||||
var/list/prefs = S.get_entry("character1", null)
|
||||
if(!islist(prefs))
|
||||
return
|
||||
|
||||
// Get data
|
||||
var/pai_name
|
||||
var/pai_description
|
||||
var/pai_role
|
||||
var/pai_comments
|
||||
var/pai_eye_color
|
||||
var/pai_chassis
|
||||
var/pai_ouremotion
|
||||
F["name"] >> pai_name
|
||||
F["description"] >> pai_description
|
||||
F["role"] >> pai_role
|
||||
F["comments"] >> pai_comments
|
||||
F["eyecolor"] >> pai_eye_color
|
||||
F["chassis"] >> pai_chassis
|
||||
F["emotion"] >> pai_ouremotion
|
||||
// F["gender"] >> pai_gender // We use the character slot's now
|
||||
|
||||
if(pai_name)
|
||||
prefs["Pai_Name"] = pai_name
|
||||
if(pai_role)
|
||||
prefs["Pai_Role"] = pai_role
|
||||
if(pai_description)
|
||||
prefs["Pai_Desc"] = pai_description
|
||||
if(pai_comments)
|
||||
prefs["Pai_Comments"] = pai_comments
|
||||
if(pai_eye_color)
|
||||
prefs["Pai_EyeColor"] = pai_eye_color
|
||||
if(pai_chassis)
|
||||
prefs["Pai_Chassis"] = pai_chassis
|
||||
if(pai_ouremotion)
|
||||
prefs["Pai_Emotion"] = pai_ouremotion
|
||||
|
||||
fdel(pai_path)
|
||||
|
||||
S.save()
|
||||
@@ -0,0 +1,116 @@
|
||||
/datum/preference/text/pai_name
|
||||
category = PREFERENCE_CATEGORY_MANUALLY_RENDERED
|
||||
savefile_identifier = PREFERENCE_CHARACTER
|
||||
savefile_key = "Pai_Name"
|
||||
maximum_value_length = MAX_NAME_LEN
|
||||
can_randomize = FALSE
|
||||
|
||||
/datum/preference/text/pai_name/create_default_value()
|
||||
return PAI_UNSET
|
||||
|
||||
/datum/preference/text/pai_name/apply_pref_to(mob/living, value)
|
||||
return
|
||||
|
||||
|
||||
/datum/preference/text/pai_description
|
||||
category = PREFERENCE_CATEGORY_MANUALLY_RENDERED
|
||||
savefile_identifier = PREFERENCE_CHARACTER
|
||||
savefile_key = "Pai_Desc"
|
||||
maximum_value_length = MAX_MESSAGE_LEN * 4
|
||||
can_randomize = FALSE
|
||||
|
||||
/datum/preference/text/pai_description/create_default_value()
|
||||
return PAI_UNSET
|
||||
|
||||
/datum/preference/text/pai_description/apply_pref_to(mob/living, value)
|
||||
return
|
||||
|
||||
|
||||
/datum/preference/text/pai_role
|
||||
category = PREFERENCE_CATEGORY_MANUALLY_RENDERED
|
||||
savefile_identifier = PREFERENCE_CHARACTER
|
||||
savefile_key = "Pai_Role"
|
||||
maximum_value_length = MAX_MESSAGE_LEN
|
||||
can_randomize = FALSE
|
||||
|
||||
/datum/preference/text/pai_role/create_default_value()
|
||||
return PAI_UNSET
|
||||
|
||||
/datum/preference/text/pai_role/apply_pref_to(mob/living, value)
|
||||
return
|
||||
|
||||
|
||||
/datum/preference/text/pai_ad
|
||||
category = PREFERENCE_CATEGORY_MANUALLY_RENDERED
|
||||
savefile_identifier = PREFERENCE_CHARACTER
|
||||
savefile_key = "Pai_Ad"
|
||||
maximum_value_length = MAX_MESSAGE_LEN
|
||||
can_randomize = FALSE
|
||||
|
||||
/datum/preference/text/pai_ad/create_default_value()
|
||||
return PAI_UNSET
|
||||
|
||||
/datum/preference/text/pai_ad/apply_pref_to(mob/living, value)
|
||||
return
|
||||
|
||||
|
||||
/datum/preference/text/pai_comments
|
||||
category = PREFERENCE_CATEGORY_MANUALLY_RENDERED
|
||||
savefile_identifier = PREFERENCE_CHARACTER
|
||||
savefile_key = "Pai_Comments"
|
||||
maximum_value_length = MAX_MESSAGE_LEN
|
||||
can_randomize = FALSE
|
||||
|
||||
/datum/preference/text/pai_comments/create_default_value()
|
||||
return PAI_UNSET
|
||||
|
||||
/datum/preference/text/pai_comments/apply_pref_to(mob/living, value)
|
||||
return
|
||||
|
||||
|
||||
/datum/preference/color/pai_eye_color
|
||||
category = PREFERENCE_CATEGORY_MANUALLY_RENDERED
|
||||
savefile_identifier = PREFERENCE_CHARACTER
|
||||
savefile_key = "Pai_EyeColor"
|
||||
can_randomize = TRUE
|
||||
|
||||
/datum/preference/color/pai_eye_color/apply_pref_to(mob/living, value)
|
||||
return
|
||||
|
||||
|
||||
/datum/preference/text/pai_chassis
|
||||
category = PREFERENCE_CATEGORY_MANUALLY_RENDERED
|
||||
savefile_identifier = PREFERENCE_CHARACTER
|
||||
savefile_key = "Pai_Chassis"
|
||||
maximum_value_length = MAX_NAME_LEN
|
||||
can_randomize = FALSE
|
||||
|
||||
/datum/preference/text/pai_chassis/create_default_value()
|
||||
return PAI_DEFAULT_CHASSIS
|
||||
|
||||
/datum/preference/text/pai_chassis/is_valid(value)
|
||||
if(!(value in SSpai.get_chassis_list()))
|
||||
return FALSE
|
||||
. = ..()
|
||||
|
||||
/datum/preference/text/pai_chassis/apply_pref_to(mob/living, value)
|
||||
return
|
||||
|
||||
|
||||
/datum/preference/text/pai_emotion
|
||||
category = PREFERENCE_CATEGORY_MANUALLY_RENDERED
|
||||
savefile_identifier = PREFERENCE_CHARACTER
|
||||
savefile_key = "Pai_Emotion"
|
||||
maximum_value_length = MAX_NAME_LEN
|
||||
can_randomize = FALSE
|
||||
|
||||
/datum/preference/text/pai_emotion/create_default_value()
|
||||
return GLOB.pai_emotions[1]
|
||||
|
||||
/datum/preference/text/pai_emotion/is_valid(value)
|
||||
if(!(value in GLOB.pai_emotions))
|
||||
return FALSE
|
||||
. = ..()
|
||||
|
||||
/datum/preference/text/pai_emotion/apply_pref_to(mob/living, value)
|
||||
return
|
||||
@@ -1,5 +1,5 @@
|
||||
#define SAVEFILE_VERSION_MIN 8
|
||||
#define SAVEFILE_VERSION_MAX 18
|
||||
#define SAVEFILE_VERSION_MAX 19
|
||||
|
||||
/*
|
||||
SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Carn
|
||||
@@ -98,6 +98,16 @@ SAVEFILE UPDATING/VERSIONING - 'Simplified', or rather, more coder-friendly ~Car
|
||||
|
||||
log_world("[client_ckey] preferences successfully migrated from [current_version] to v18.")
|
||||
to_chat(client, span_danger("v18 savefile migration complete."))
|
||||
|
||||
// Migration for pai to tg pregs
|
||||
if(current_version < 19)
|
||||
log_world("[client_ckey] preferences migrating from [current_version] to v19....")
|
||||
to_chat(client, span_danger("Migrating savefile from version [current_version] to v19..."))
|
||||
|
||||
migration_19_paifile(S)
|
||||
|
||||
log_world("[client_ckey] preferences successfully migrated from [current_version] to v19.")
|
||||
to_chat(client, span_danger("v19 savefile migration complete."))
|
||||
/datum/preferences/proc/update_character(current_version, list/save_data)
|
||||
// Migration from BYOND savefiles to JSON: Important milemark.
|
||||
if(current_version == -3)
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
get_asset_datum(/datum/asset/simple/preferences),
|
||||
get_asset_datum(/datum/asset/spritesheet/preferences),
|
||||
get_asset_datum(/datum/asset/json/preferences),
|
||||
get_asset_datum(/datum/asset/spritesheet_batched/pai_icons),
|
||||
)
|
||||
|
||||
for (var/datum/preference_middleware/preference_middleware as anything in middleware)
|
||||
|
||||
@@ -175,7 +175,7 @@
|
||||
emote_sound = pick(bigsound)
|
||||
else if(ispAI(user))
|
||||
var/mob/living/silicon/pai/me = user
|
||||
if(me.chassis == "teppi")
|
||||
if(istype(SSpai.chassis_data(me.chassis_name), /datum/pai_sprite/large))
|
||||
emote_sound = pick(bigsound)
|
||||
else if(user.size_multiplier >= 1.5)
|
||||
emote_sound = pick(bigsound)
|
||||
@@ -202,7 +202,7 @@
|
||||
emote_sound = pick(bigsound)
|
||||
else if(ispAI(user))
|
||||
var/mob/living/silicon/pai/me = user
|
||||
if(me.chassis == "teppi")
|
||||
if(istype(SSpai.chassis_data(me.chassis_name), /datum/pai_sprite/large))
|
||||
emote_sound = pick(bigsound)
|
||||
else if(user.size_multiplier >= 1.5)
|
||||
emote_sound = pick(bigsound)
|
||||
|
||||
@@ -40,6 +40,9 @@
|
||||
/obj/structure/musician/tgui_interact(mob/user)
|
||||
return song.tgui_interact(user)
|
||||
|
||||
/obj/structure/musician/allow_pai_interaction(proximity_flag)
|
||||
return proximity_flag
|
||||
|
||||
/* FIXME
|
||||
/obj/structure/musician/wrench_act(mob/living/user, obj/item/tool)
|
||||
. = ..()
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
/datum/map_template/shelter/proc/check_deploy(turf/deploy_location, var/is_ship)
|
||||
var/affected = get_affected_turfs(deploy_location, centered=TRUE)
|
||||
for(var/turf/T in affected)
|
||||
var/shelter_status = get_turf_deployability(T)
|
||||
var/shelter_status = get_turf_deployability(T, is_ship)
|
||||
if(shelter_status != SHELTER_DEPLOY_ALLOWED)
|
||||
return shelter_status
|
||||
return SHELTER_DEPLOY_ALLOWED
|
||||
|
||||
@@ -965,24 +965,28 @@ This is the proc mobs get to turn into a ghost. Forked from ghostize due to comp
|
||||
to_chat(src,span_warning("You cannot alert pAI cards when you are banned from playing as a pAI."))
|
||||
return
|
||||
|
||||
if(src.client.prefs?.be_special & BE_PAI)
|
||||
var/choice = tgui_alert(src, "Would you like to submit yourself to the recruitment list too?", "Confirmation", list("No", "Yes"))
|
||||
if(!choice)
|
||||
return
|
||||
if(choice == "Yes")
|
||||
paiController.recruitWindow(src)
|
||||
var/count = 0
|
||||
for(var/obj/item/paicard/p in GLOB.all_pai_cards)
|
||||
var/obj/item/paicard/PP = p
|
||||
if(PP.pai == null)
|
||||
count++
|
||||
PP.add_overlay("pai-ghostalert")
|
||||
PP.alertUpdate()
|
||||
spawn(54)
|
||||
PP.cut_overlays()
|
||||
to_chat(src,span_notice("Flashing the displays of [count] unoccupied PAIs."))
|
||||
else
|
||||
to_chat(src,span_warning("You have 'Be pAI' disabled in your character prefs, so we can't help you."))
|
||||
if(!(src.client.prefs?.be_special & BE_PAI))
|
||||
to_chat(src,span_warning("You have 'Be pAI' disabled in your character prefs."))
|
||||
return
|
||||
|
||||
var/choice = tgui_alert(src, "Would you like to submit yourself to the recruitment list too?", "Confirmation", list("No", "Yes"))
|
||||
if(!choice || choice != "Yes")
|
||||
return
|
||||
|
||||
to_chat(src,span_notice("Flashing the displays of [pai_card_ping()] unoccupied PAIs."))
|
||||
|
||||
/mob/observer/dead/proc/pai_card_ping()
|
||||
var/count = 0
|
||||
for(var/obj/item/paicard/p in GLOB.all_pai_cards)
|
||||
var/obj/item/paicard/PP = p
|
||||
if(PP.pai)
|
||||
continue
|
||||
count++
|
||||
PP.cut_overlays()
|
||||
PP.add_overlay("pai-ghostalert")
|
||||
PP.alertUpdate()
|
||||
addtimer(CALLBACK(PP, TYPE_PROC_REF(/obj/item/paicard, clear_invite_overlay)), 1 MINUTE, TIMER_DELETE_ME)
|
||||
return count
|
||||
|
||||
/mob/observer/dead/speech_bubble_appearance()
|
||||
return "ghost"
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
add_verb(src,/mob/proc/nme_vore_ch)
|
||||
add_verb(src,/mob/proc/enter_soulcatcher)
|
||||
|
||||
if(!voice_sounds_list.len || !voice_sounds_list)
|
||||
if(!length(voice_sounds_list))
|
||||
if(client.prefs.voice_sound)
|
||||
var/prefsound = client.prefs.voice_sound
|
||||
voice_sounds_list = get_talk_sound(prefsound)
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
// Pai chassis selection
|
||||
/datum/tgui_module/pai_chassis
|
||||
name = "PAI Chassis Configurator"
|
||||
tgui_id = "PaiChoose"
|
||||
var/selected_chassis
|
||||
var/selected_color
|
||||
|
||||
/datum/tgui_module/pai_chassis/tgui_state(mob/user)
|
||||
return GLOB.tgui_self_state
|
||||
|
||||
/datum/tgui_module/pai_chassis/ui_assets(mob/user)
|
||||
return list(
|
||||
get_asset_datum(/datum/asset/spritesheet_batched/pai_icons),
|
||||
)
|
||||
|
||||
/datum/tgui_module/pai_chassis/tgui_static_data()
|
||||
var/list/data = ..()
|
||||
var/list/available_sprites = list()
|
||||
for(var/key, value in SSpai.get_chassis_list())
|
||||
var/datum/pai_sprite/current_sprite = value
|
||||
var/model_type = "def"
|
||||
if(istype(current_sprite, /datum/pai_sprite/large))
|
||||
model_type = "big"
|
||||
UNTYPED_LIST_ADD(available_sprites, list("sprite" = current_sprite.name, "belly" = current_sprite.belly_states, "type" = model_type))
|
||||
data["pai_chassises"] = available_sprites
|
||||
|
||||
return data
|
||||
|
||||
/datum/tgui_module/pai_chassis/tgui_data()
|
||||
var/list/data = ..()
|
||||
|
||||
var/mob/living/silicon/pai/pai_host = host
|
||||
data["pai_color"] = selected_color ? selected_color : pai_host.eye_color
|
||||
|
||||
var/datum/pai_sprite/sprite_datum = SSpai.chassis_data(selected_chassis || pai_host.chassis_name)
|
||||
if(sprite_datum)
|
||||
var/datum/asset/spritesheet_batched/pai_icons/spritesheet = get_asset_datum(/datum/asset/spritesheet_batched/pai_icons)
|
||||
data["pai_chassis"] = sprite_datum.name
|
||||
data["selected_chassis"] = selected_chassis
|
||||
data["sprite_datum_class"] = sanitize_css_class_name("[sprite_datum.type]")
|
||||
data["sprite_datum_size"] = spritesheet.icon_size_id(data["sprite_datum_class"] + "S") // just get the south icon's size, the rest will be the same
|
||||
|
||||
return data
|
||||
|
||||
/datum/tgui_module/pai_chassis/tgui_act(action, list/params, datum/tgui/ui, datum/tgui_state/state)
|
||||
. = ..()
|
||||
if(.)
|
||||
return
|
||||
|
||||
switch(action)
|
||||
if("pick_icon")
|
||||
var/new_chassis = params["value"]
|
||||
if(new_chassis && (new_chassis in SSpai.get_chassis_list()))
|
||||
selected_chassis = new_chassis
|
||||
return TRUE
|
||||
if("confirm")
|
||||
if(!selected_chassis)
|
||||
return FALSE
|
||||
var/mob/living/silicon/pai/pai_host = host
|
||||
if(selected_color)
|
||||
pai_host.eye_color = selected_color
|
||||
pai_host.change_chassis(selected_chassis)
|
||||
return TRUE
|
||||
if("change_color")
|
||||
var/new_color = sanitize_hexcolor(params["color"])
|
||||
if(!new_color)
|
||||
return FALSE
|
||||
selected_color = new_color
|
||||
return TRUE
|
||||
@@ -19,7 +19,8 @@
|
||||
card.damage_random_component()
|
||||
|
||||
if(gibbed)
|
||||
qdel(card)
|
||||
if(!QDELETED(card)) // Either the pai or card could be deleted first, prevent a loop
|
||||
qdel(card)
|
||||
..(gibbed)
|
||||
else
|
||||
card.add_overlay("pai-dead")
|
||||
|
||||
@@ -1,16 +1,7 @@
|
||||
/mob/living/silicon/pai/Life()
|
||||
check_retract_cable()
|
||||
|
||||
if(src.cable)
|
||||
if(get_dist(src, src.cable) > 1)
|
||||
var/turf/T = get_turf_or_move(src.loc)
|
||||
for (var/mob/M in viewers(T))
|
||||
M.show_message(span_red("The data cable rapidly retracts back into its spool."), 3, span_red("You hear a click and the sound of wire spooling rapidly."), 2)
|
||||
playsound(src, 'sound/machines/click.ogg', 50, 1)
|
||||
|
||||
qdel(src.cable)
|
||||
src.cable = null
|
||||
|
||||
if (src.stat == DEAD)
|
||||
if(stat == DEAD)
|
||||
return
|
||||
|
||||
if(card.cell != PP_FUNCTIONAL|| card.processor != PP_FUNCTIONAL || card.board != PP_FUNCTIONAL || card.capacitor != PP_FUNCTIONAL)
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
emote_type = 2 // pAIs emotes are heard, not seen, so they can be seen through a container (eg. person)
|
||||
pass_flags = 1
|
||||
mob_size = MOB_SMALL
|
||||
softfall = TRUE
|
||||
|
||||
holder_type = /obj/item/holder/pai
|
||||
|
||||
@@ -25,7 +26,9 @@
|
||||
var/obj/item/radio/borg/pai/radio // Our primary radio
|
||||
var/obj/item/communicator/integrated/communicator // Our integrated communicator.
|
||||
|
||||
var/chassis = "pai-repairbot" // A record of your chosen chassis.
|
||||
var/atom/movable/screen/pai/pai_fold_display = null
|
||||
|
||||
var/chassis_name = PAI_DEFAULT_CHASSIS // A record of your chosen chassis.
|
||||
|
||||
var/obj/item/pai_cable/cable // The cable we produce and use when door or camera jacking
|
||||
|
||||
@@ -68,15 +71,47 @@
|
||||
|
||||
var/our_icon_rotation = 0
|
||||
|
||||
var/eye_glow = TRUE
|
||||
var/hide_glow = FALSE
|
||||
var/image/eye_layer = null // Holds the eye overlay.
|
||||
var/eye_color = "#00ff0d"
|
||||
var/icon/holo_icon_south
|
||||
var/icon/holo_icon_north
|
||||
var/icon/holo_icon_east
|
||||
var/icon/holo_icon_west
|
||||
var/holo_icon_dimension_X = 32
|
||||
var/holo_icon_dimension_Y = 32
|
||||
|
||||
//These vars keep track of whether you have the related software, used for easily updating the UI
|
||||
var/soft_ut = FALSE //universal translator
|
||||
var/soft_mr = FALSE //medical records
|
||||
var/soft_sr = FALSE //security records
|
||||
var/soft_dj = FALSE //door jack
|
||||
var/soft_as = FALSE //atmosphere sensor
|
||||
var/soft_si = FALSE //signaler
|
||||
var/soft_ar = FALSE //ar hud
|
||||
var/soft_da = FALSE //death alarm
|
||||
|
||||
var/datum/tgui_module/pai_chassis/pai_ui_chassis
|
||||
|
||||
vore_capacity = 1
|
||||
vore_capacity_ex = list("stomach" = 1)
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Init and destroy
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
/mob/living/silicon/pai/Initialize(mapload)
|
||||
. = ..()
|
||||
|
||||
card = loc
|
||||
if(!istype(card))
|
||||
return INITIALIZE_HINT_QDEL
|
||||
card = new(src)
|
||||
card.pai = src
|
||||
|
||||
sradio = new(src)
|
||||
communicator = new(src)
|
||||
pai_ui_chassis = new(src)
|
||||
if(card)
|
||||
if(!card.radio)
|
||||
card.radio = new /obj/item/radio/borg/pai(src.card)
|
||||
@@ -92,6 +127,12 @@
|
||||
|
||||
add_verb(src, /mob/living/silicon/pai/proc/choose_chassis)
|
||||
add_verb(src, /mob/living/silicon/pai/proc/choose_verbs)
|
||||
add_verb(src, /mob/proc/dominate_predator)
|
||||
add_verb(src, /mob/living/proc/dominate_prey)
|
||||
add_verb(src, /mob/living/proc/set_size)
|
||||
add_verb(src, /mob/living/proc/shred_limb)
|
||||
|
||||
remove_verb(src, /mob/verb/toggle_gun_mode) // Pai doesn't have support for this and shouldn't be able to use guns anyway
|
||||
|
||||
//PDA
|
||||
pda = new(src)
|
||||
@@ -104,7 +145,11 @@
|
||||
M.toff = FALSE
|
||||
|
||||
/mob/living/silicon/pai/Login()
|
||||
..()
|
||||
. = ..()
|
||||
if(!holo_icon_south)
|
||||
last_special = world.time + 100 //Let's give get_character_icon time to work
|
||||
get_character_icon()
|
||||
|
||||
// Vorestation Edit: Meta Info for pAI
|
||||
if (client.prefs)
|
||||
ooc_notes = client.prefs.read_preference(/datum/preference/text/living/ooc_notes)
|
||||
@@ -117,19 +162,233 @@
|
||||
|
||||
src << sound('sound/effects/pai_login.ogg', volume = 75) //VOREStation Add
|
||||
|
||||
// this function shows the information about being silenced as a pAI in the Status panel
|
||||
/mob/living/silicon/pai/proc/show_silenced()
|
||||
. = ""
|
||||
if(src.silence_time)
|
||||
var/timeleft = round((silence_time - world.timeofday)/10 ,1)
|
||||
. += "Communications system reboot in -[(timeleft / 60) % 60]:[add_zero(num2text(timeleft % 60), 2)]"
|
||||
/// Load pref save data from client and apply it to the pai.
|
||||
/mob/living/silicon/pai/proc/apply_preferences(client/cli, var/silent = 1)
|
||||
if(!cli?.prefs)
|
||||
return FALSE
|
||||
var/datum/preferences/pref = cli.prefs
|
||||
|
||||
SetName(pref.read_preference(/datum/preference/text/pai_name))
|
||||
flavor_text = pref.read_preference(/datum/preference/text/pai_description)
|
||||
change_chassis(pref.read_preference(/datum/preference/text/pai_chassis))
|
||||
gender = pref.read_preference(/datum/preference/choiced/gender/biological) // Cannot use identifying yet due to byond limits
|
||||
eye_color = pref.read_preference(/datum/preference/color/pai_eye_color)
|
||||
card.screen_color = eye_color
|
||||
card.setEmotion(GLOB.pai_emotions[pref.read_preference(/datum/preference/text/pai_emotion)])
|
||||
|
||||
update_icon()
|
||||
return TRUE
|
||||
|
||||
/mob/living/silicon/pai/Destroy()
|
||||
release_vore_contents()
|
||||
check_retract_cable()
|
||||
if(!QDELETED(card))
|
||||
QDEL_NULL(card)
|
||||
QDEL_NULL(pai_ui_chassis)
|
||||
QDEL_NULL(sradio)
|
||||
QDEL_NULL(communicator)
|
||||
QDEL_NULL(pda)
|
||||
if(pai_fold_display)
|
||||
QDEL_NULL(pai_fold_display)
|
||||
securityActive1 = null
|
||||
securityActive2 = null
|
||||
hackdoor = null
|
||||
current = null
|
||||
if(ckey)
|
||||
GLOB.paikeys -= ckey
|
||||
return ..()
|
||||
|
||||
/mob/living/silicon/pai/clear_client()
|
||||
if(ckey)
|
||||
GLOB.paikeys -= ckey
|
||||
return ..()
|
||||
|
||||
// No binary for pAIs.
|
||||
/mob/living/silicon/pai/binarycheck()
|
||||
return 0
|
||||
|
||||
/// Verb used to select a chassis from the list of available chassis
|
||||
/mob/living/silicon/pai/proc/choose_chassis()
|
||||
set category = "Abilities.pAI Commands"
|
||||
set name = "Choose Chassis"
|
||||
|
||||
pai_ui_chassis.tgui_interact(src)
|
||||
|
||||
/// Change pai sprite and offsets based upon the selected chassis id
|
||||
/mob/living/silicon/pai/proc/change_chassis(new_chassis)
|
||||
if(!(new_chassis in SSpai.get_chassis_list()))
|
||||
new_chassis = PAI_DEFAULT_CHASSIS
|
||||
chassis_name = new_chassis
|
||||
|
||||
//We resize ourselves to normal here for a moment to let the vis_height get reset
|
||||
var/oursize = size_multiplier
|
||||
resize(1, FALSE, TRUE, TRUE, FALSE)
|
||||
|
||||
// Get icon data setup
|
||||
var/datum/pai_sprite/chassis_data = SSpai.chassis_data(chassis_name)
|
||||
if(chassis_data.holo_projector)
|
||||
// Rebuild holosprite from character
|
||||
if(!holo_icon_south)
|
||||
get_character_icon()
|
||||
else
|
||||
// Get data from our sprite datum
|
||||
icon = chassis_data.sprite_icon
|
||||
pixel_x = chassis_data.pixel_x
|
||||
default_pixel_x = pixel_x
|
||||
pixel_y = chassis_data.pixel_y
|
||||
default_pixel_y = pixel_y
|
||||
vis_height = chassis_data.vis_height
|
||||
|
||||
// Drops you if you change to a non-flying chassis
|
||||
if(chassis_data.flying)
|
||||
hovering = TRUE
|
||||
else
|
||||
hovering = FALSE
|
||||
if(isopenspace(loc))
|
||||
fall()
|
||||
|
||||
// Set vore size.
|
||||
vore_capacity = max(1, chassis_data.belly_states) // Minimum of 1
|
||||
vore_capacity_ex = list("stomach" = vore_capacity)
|
||||
|
||||
// Emergency eject if you change to a smaller belly
|
||||
if(vore_fullness > vore_capacity && vore_selected)
|
||||
vore_selected.release_all_contents(TRUE)
|
||||
|
||||
update_icon()
|
||||
resize(oursize, FALSE, TRUE, TRUE, FALSE) //And then back again now that we're sure the vis_height is correct.
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Click interactions
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/mob/living/silicon/pai/attackby(obj/item/W as obj, mob/user as mob)
|
||||
var/obj/item/card/id/ID = W.GetID()
|
||||
if(ID)
|
||||
if (idaccessible == 1)
|
||||
switch(tgui_alert(user, "Do you wish to add access to [src] or remove access from [src]?","Access Modify",list("Add Access","Remove Access", "Cancel")))
|
||||
if("Add Access")
|
||||
idcard.access |= ID.GetAccess()
|
||||
to_chat(user, span_notice("You add the access from the [W] to [src]."))
|
||||
to_chat(src, span_notice("\The [user] swipes the [W] over you. You copy the access codes."))
|
||||
if(radio)
|
||||
radio.recalculateChannels()
|
||||
return
|
||||
if("Remove Access")
|
||||
idcard.access = list()
|
||||
to_chat(user, span_notice("You remove the access from [src]."))
|
||||
to_chat(src, span_warning("\The [user] swipes the [W] over you, removing access codes from you."))
|
||||
if(radio)
|
||||
radio.recalculateChannels()
|
||||
return
|
||||
if("Cancel", null)
|
||||
return
|
||||
else if (istype(W, /obj/item/card/id) && idaccessible == 0)
|
||||
to_chat(user, span_notice("[src] is not accepting access modifcations at this time."))
|
||||
return
|
||||
|
||||
//Overriding this will stop a number of headaches down the track.
|
||||
/mob/living/silicon/pai/attackby(obj/item/W as obj, mob/user as mob)
|
||||
if(W.force)
|
||||
visible_message(span_danger("[user.name] attacks [src] with [W]!"))
|
||||
src.adjustBruteLoss(W.force)
|
||||
src.updatehealth()
|
||||
else
|
||||
visible_message(span_warning("[user.name] bonks [src] harmlessly with [W]."))
|
||||
spawn(1)
|
||||
if(stat != DEAD) close_up()
|
||||
return
|
||||
|
||||
/mob/living/silicon/pai/attack_hand(mob/user as mob)
|
||||
if(user.a_intent == I_HELP)
|
||||
visible_message(span_notice("[user.name] pats [src]."))
|
||||
else
|
||||
visible_message(span_danger("[user.name] boops [src] on the head."))
|
||||
close_up()
|
||||
|
||||
/mob/living/silicon/pai/UnarmedAttack(atom/A, proximity_flag)
|
||||
. = ..()
|
||||
|
||||
// Some restricted objects to interact with
|
||||
var/obj/O = A
|
||||
if(istype(O) && O.allow_pai_interaction(proximity_flag))
|
||||
O.attack_hand(src)
|
||||
return
|
||||
|
||||
// Zmovement already allows these to be used with the verbs anyway
|
||||
if(istype(A,/obj/structure/ladder))
|
||||
var/obj/structure/ladder/L = A
|
||||
L.attack_hand(src)
|
||||
return
|
||||
|
||||
// We don't want to pick these up, just toggle them
|
||||
if(istype(A,/obj/item/flashlight/lamp))
|
||||
var/obj/item/flashlight/lamp/L = A
|
||||
L.toggle_light()
|
||||
return
|
||||
|
||||
// All other computers explain why it's not accessible by showing a firewall warning
|
||||
if(istype(A,/obj/machinery/computer))
|
||||
to_chat(src,span_warning("A firewall prevents you from interfacing with this device!"))
|
||||
return
|
||||
|
||||
if(!ismob(A) || A == src)
|
||||
return
|
||||
|
||||
switch(a_intent)
|
||||
if(I_HELP)
|
||||
if(isliving(A))
|
||||
hug(src, A)
|
||||
if(I_GRAB)
|
||||
pai_nom(A)
|
||||
|
||||
// Allow card inhabited machines to be interacted with
|
||||
// This has to override ClickOn because of storage depth nonsense with how pAIs are in cards in GLOB.machines
|
||||
/mob/living/silicon/pai/ClickOn(var/atom/A, var/params)
|
||||
if(istype(A, /obj/machinery))
|
||||
var/obj/machinery/M = A
|
||||
if(M.paicard == card)
|
||||
M.attack_ai(src)
|
||||
return
|
||||
return ..()
|
||||
|
||||
// Handle being picked up.
|
||||
/mob/living/silicon/pai/get_scooped(var/mob/living/carbon/grabber, var/self_drop)
|
||||
var/obj/item/holder/H = ..(grabber, self_drop)
|
||||
if(!istype(H))
|
||||
return
|
||||
|
||||
H.icon_state = SSpai.chassis_data(chassis_name).sprite_icon_state
|
||||
grabber.update_inv_l_hand()
|
||||
grabber.update_inv_r_hand()
|
||||
return H
|
||||
|
||||
/mob/living/silicon/pai/Moved(atom/oldloc, direct, forced, movetime)
|
||||
. = ..()
|
||||
check_retract_cable()
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Status and damage
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/mob/living/silicon/pai/get_status_tab_items()
|
||||
. = ..()
|
||||
. += ""
|
||||
. += show_silenced()
|
||||
|
||||
/mob/living/silicon/pai/adjustBruteLoss(amount, include_robo)
|
||||
. = ..()
|
||||
if(amount > 0 && health <= 90) //Something's probably attacking us!
|
||||
if(prob(amount)) //The more damage it is doing, the more likely it is to damage something important!
|
||||
card.damage_random_component()
|
||||
|
||||
/mob/living/silicon/pai/adjustFireLoss(amount, include_robo)
|
||||
. = ..()
|
||||
if(amount > 0 && health <= 90)
|
||||
if(prob(amount))
|
||||
card.damage_random_component()
|
||||
|
||||
/mob/living/silicon/pai/restrained()
|
||||
if(istype(src.loc,/obj/item/paicard))
|
||||
return 0
|
||||
@@ -169,145 +428,36 @@
|
||||
if(3)
|
||||
to_chat(src, span_infoplain(span_green("You feel an electric surge run through your circuitry and become acutely aware at how lucky you are that you can still feel at all.")))
|
||||
|
||||
/mob/living/silicon/pai/proc/switchCamera(var/obj/machinery/camera/C)
|
||||
if (!C)
|
||||
src.reset_perspective()
|
||||
return 0
|
||||
if (stat == 2 || !C.status || !(src.network in C.network)) return 0
|
||||
/// this function shows the information about being silenced as a pAI in the Status panel
|
||||
/mob/living/silicon/pai/proc/show_silenced()
|
||||
. = ""
|
||||
if(src.silence_time)
|
||||
var/timeleft = round((silence_time - world.timeofday)/10 ,1)
|
||||
. += "Communications system reboot in -[(timeleft / 60) % 60]:[add_zero(num2text(timeleft % 60), 2)]"
|
||||
|
||||
// ok, we're alive, camera is good and in our network...
|
||||
src.current = C
|
||||
src.AddComponent(/datum/component/remote_view, focused_on = C, viewsize = null, vconfig_path = /datum/remote_view_config/camera_standard)
|
||||
return 1
|
||||
/// Fully heals a pai, used when a pai is repaired
|
||||
/mob/living/silicon/pai/proc/full_restore()
|
||||
adjustBruteLoss(- bruteloss)
|
||||
adjustFireLoss(- fireloss)
|
||||
addtimer(CALLBACK(src, PROC_REF(restore_delay_start)), 5 SECONDS, TIMER_DELETE_ME)
|
||||
|
||||
/mob/living/silicon/pai/verb/reset_record_view()
|
||||
set category = "Abilities.pAI Commands"
|
||||
set name = "Reset Records Software"
|
||||
/mob/living/silicon/pai/proc/restore_delay_start()
|
||||
SHOULD_NOT_OVERRIDE(TRUE)
|
||||
PRIVATE_PROC(TRUE)
|
||||
card.setEmotion(16)
|
||||
stat = CONSCIOUS
|
||||
addtimer(CALLBACK(src, PROC_REF(restore_delay_end)), 1 SECONDS, TIMER_DELETE_ME)
|
||||
|
||||
securityActive1 = null
|
||||
securityActive2 = null
|
||||
security_cannotfind = 0
|
||||
medicalActive1 = null
|
||||
medicalActive2 = null
|
||||
medical_cannotfind = 0
|
||||
SStgui.update_uis(src)
|
||||
to_chat(src, span_notice("You reset your record-viewing software."))
|
||||
|
||||
/mob/living/silicon/pai/cancel_camera()
|
||||
set category = "Abilities.pAI Commands"
|
||||
set name = "Cancel Camera View"
|
||||
reset_perspective()
|
||||
|
||||
/mob/living/silicon/pai/reset_perspective(atom/new_eye)
|
||||
. = ..()
|
||||
current = null
|
||||
|
||||
// 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
|
||||
|
||||
/mob/living/silicon/pai/verb/fold_out()
|
||||
set category = "Abilities.pAI Commands"
|
||||
set name = "Unfold Chassis"
|
||||
|
||||
if(stat || sleeping || paralysis || weakened)
|
||||
return
|
||||
|
||||
if(loc != card)
|
||||
return
|
||||
|
||||
// Lets not trap the pai forever. These are special cases we want to escape out of when in our card
|
||||
if(istype(loc.loc, /obj/item/pda))
|
||||
var/obj/item/pda/ourpda = loc.loc
|
||||
if(ourpda.pai == card)
|
||||
ourpda.pai.forceMove(ourpda.loc)
|
||||
ourpda.pai = null
|
||||
visible_message(span_warning("\The [card] ejects itself from \the [ourpda]."))
|
||||
return
|
||||
if(istype(loc.loc, /obj/item/storage/vore_egg))
|
||||
var/obj/item/storage/vore_egg/ouregg = loc.loc
|
||||
to_chat(src, span_notice("You craftily use your built in rumble function to break free of \the [ouregg]'s confines!"))
|
||||
ouregg.hatch(src)
|
||||
return
|
||||
|
||||
if(is_folding_unsafe(loc.loc))
|
||||
to_chat(src, span_danger("It's not safe to unfold while inside a [loc.loc]!"))
|
||||
return
|
||||
|
||||
if(card.projector != PP_FUNCTIONAL && card.emitter != PP_FUNCTIONAL)
|
||||
to_chat(src, span_warning("ERROR: System malfunction. Service required!"))
|
||||
|
||||
if(world.time <= last_special)
|
||||
to_chat(src, span_warning("You can't unfold yet."))
|
||||
return
|
||||
|
||||
last_special = world.time + 100
|
||||
|
||||
if(istype(card.loc, /obj/machinery)) // VOREStation edit, this statement allows pAIs stuck in a machine to eject themselves.
|
||||
var/obj/machinery/M = card.loc
|
||||
M.ejectpai()
|
||||
//I'm not sure how much of this is necessary, but I would rather avoid issues.
|
||||
if(istype(card.loc,/obj/item/rig_module))
|
||||
to_chat(src, span_filter_notice("There is no room to unfold inside this rig module. You're good and stuck."))
|
||||
return 0
|
||||
else if(istype(card.loc,/mob))
|
||||
var/mob/holder = card.loc
|
||||
if(ishuman(holder))
|
||||
var/mob/living/carbon/human/H = holder
|
||||
for(var/obj/item/organ/external/affecting in H.organs)
|
||||
if(card in affecting.implants)
|
||||
affecting.take_damage(rand(30,50))
|
||||
affecting.implants -= card
|
||||
H.visible_message(span_danger("\The [src] explodes out of \the [H]'s [affecting.name] in shower of gore!"))
|
||||
break
|
||||
holder.drop_from_inventory(card)
|
||||
else if(isbelly(card.loc)) //VOREStation edit.
|
||||
to_chat(src, span_notice("There is no room to unfold in here. You're good and stuck.")) //VOREStation edit.
|
||||
return 0 //VOREStation edit.
|
||||
else if(istype(card.loc,/obj/item/pda))
|
||||
var/obj/item/pda/holder = card.loc
|
||||
holder.pai = null
|
||||
|
||||
src.forceMove(card.loc)
|
||||
card.forceMove(src)
|
||||
card.screen_loc = null
|
||||
/mob/living/silicon/pai/proc/restore_delay_end()
|
||||
SHOULD_NOT_OVERRIDE(TRUE)
|
||||
PRIVATE_PROC(TRUE)
|
||||
var/mob/observer/dead/ghost = get_ghost()
|
||||
if(ghost)
|
||||
ghost.notify_revive("Someone is trying to revive you. Re-enter your body if you want to be revived!", 'sound/effects/pai-restore.ogg', source = card)
|
||||
canmove = TRUE
|
||||
|
||||
if(isturf(loc))
|
||||
var/turf/T = get_turf(src)
|
||||
if(istype(T)) T.visible_message(span_filter_notice(span_bold("[src]") + " folds outwards, expanding into a mobile form."))
|
||||
|
||||
add_verb(src, /mob/living/silicon/pai/proc/pai_nom)
|
||||
add_verb(src, /mob/living/proc/vertical_nom)
|
||||
update_icon()
|
||||
|
||||
/mob/living/silicon/pai/verb/fold_up()
|
||||
set category = "Abilities.pAI Commands"
|
||||
set name = "Collapse Chassis"
|
||||
|
||||
if(stat || sleeping || paralysis || weakened)
|
||||
return
|
||||
|
||||
if(src.loc == card)
|
||||
return
|
||||
|
||||
if(world.time <= last_special)
|
||||
to_chat(src, span_warning("You can't fold up yet."))
|
||||
return
|
||||
|
||||
close_up()
|
||||
|
||||
/mob/living/silicon/pai/proc/choose_verbs()
|
||||
set category = "Abilities.pAI Commands"
|
||||
set name = "Choose Speech Verbs"
|
||||
|
||||
var/choice = tgui_input_list(src,"What theme would you like to use for your speech verbs?","Theme Choice", GLOB.possible_say_verbs)
|
||||
if(!choice) return
|
||||
|
||||
var/list/sayverbs = GLOB.possible_say_verbs[choice]
|
||||
speak_statement = sayverbs[1]
|
||||
speak_exclamation = sayverbs[(sayverbs.len>1 ? 2 : sayverbs.len)]
|
||||
speak_query = sayverbs[(sayverbs.len>2 ? 3 : sayverbs.len)]
|
||||
card.setEmotion(15)
|
||||
playsound(card, 'sound/effects/pai-restore.ogg', 50, FALSE)
|
||||
card.visible_message(span_filter_notice("\The [card] chimes."), runemessage = "chime")
|
||||
|
||||
/mob/living/silicon/pai/lay_down()
|
||||
set name = "Rest"
|
||||
@@ -320,193 +470,166 @@
|
||||
if(istype(rig))
|
||||
rig.force_rest(src)
|
||||
return
|
||||
else if(chassis == "13")
|
||||
resting = !resting
|
||||
//update_transform() I want this to make you ROTATE like normal HUMANS do! But! There's lots of problems and I don't know how to fix them!
|
||||
else
|
||||
resting = !resting
|
||||
icon_state = resting ? "[chassis]_rest" : "[chassis]"
|
||||
update_icon() //VOREStation edit
|
||||
update_icon()
|
||||
to_chat(src, span_notice("You are now [resting ? "resting" : "getting up"]."))
|
||||
|
||||
canmove = !resting
|
||||
|
||||
/*
|
||||
/mob/living/silicon/pai/update_transform()
|
||||
/mob/living/silicon/pai/proc/check_retract_cable()
|
||||
if(!cable)
|
||||
return
|
||||
|
||||
var/desired_scale_x = size_multiplier * icon_scale_x
|
||||
var/desired_scale_y = size_multiplier * icon_scale_y
|
||||
var/turf/current = get_turf(src)
|
||||
var/turf/cableturf = get_turf(cable)
|
||||
if(get_dist(current, cableturf) <= 1)
|
||||
return
|
||||
|
||||
// Now for the regular stuff.
|
||||
var/matrix/M = matrix()
|
||||
M.Scale(desired_scale_x, desired_scale_y)
|
||||
M.Translate(0, (vis_height/2)*(desired_scale_y-1))
|
||||
cableturf.visible_message("The data cable rapidly retracts back into its spool.", "You hear a click and the sound of wire spooling rapidly.")
|
||||
playsound(src, 'sound/machines/click.ogg', 50, 1)
|
||||
QDEL_NULL(cable)
|
||||
|
||||
if(chassis != "13")
|
||||
appearance_flags |= PIXEL_SCALE
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Update icons
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
var/anim_time = 3
|
||||
/mob/living/silicon/pai/update_icon()
|
||||
. = ..()
|
||||
|
||||
if(resting)
|
||||
M.Turn(90)
|
||||
M.Scale(desired_scale_y, desired_scale_x)
|
||||
if(holo_icon_dimension_X == 64 && holo_icon_dimension_Y == 64)
|
||||
M.Translate(13,-22)
|
||||
else if(holo_icon_dimension_X == 32 && holo_icon_dimension_Y == 64)
|
||||
M.Translate(1,-22)
|
||||
else if(holo_icon_dimension_X == 64 && holo_icon_dimension_Y == 32)
|
||||
M.Translate(13,-6)
|
||||
var/datum/pai_sprite/chassis_data = SSpai.chassis_data(chassis_name)
|
||||
if(chassis_data.holo_projector)
|
||||
icon_state = null
|
||||
icon = holo_icon_south
|
||||
add_eyes()
|
||||
return
|
||||
|
||||
update_fullness()
|
||||
|
||||
// Don't get a vore belly size if we have no belly size set!
|
||||
var/belly_size = CLAMP(vore_fullness, 0, chassis_data.belly_states)
|
||||
if(resting && !chassis_data.resting_belly) // check if we have a belly while resting
|
||||
belly_size = 0
|
||||
var/fullness_extension = ""
|
||||
if(belly_size > 1) // Multibelly support
|
||||
fullness_extension = "_[belly_size]"
|
||||
icon_state = "[chassis_data.sprite_icon_state][resting && chassis_data.can_rest ? "_rest" : ""][belly_size ? "_full[fullness_extension]" : ""]"
|
||||
|
||||
add_eyes()
|
||||
|
||||
/// Applies the eye overlay if the chassis has it
|
||||
/mob/living/silicon/pai/proc/add_eyes()
|
||||
remove_eyes()
|
||||
|
||||
var/datum/pai_sprite/chassis_data = SSpai.chassis_data(chassis_name)
|
||||
if(chassis_data.holo_projector)
|
||||
// Special eyes that are based on holoprojection of your character's icon size
|
||||
if(holo_icon_south.Width() > 32)
|
||||
holo_icon_dimension_X = 64
|
||||
pixel_x = -16
|
||||
default_pixel_x = -16
|
||||
// Get height too
|
||||
if(holo_icon_south.Height() > 32)
|
||||
holo_icon_dimension_Y = 64
|
||||
vis_height = 64
|
||||
// Set eyes
|
||||
if(holo_icon_dimension_X == 32 && holo_icon_dimension_Y == 32)
|
||||
eye_layer = image('icons/mob/pai.dmi', chassis_data.holo_eyes_icon_state)
|
||||
else if(holo_icon_dimension_X == 32 && holo_icon_dimension_Y == 64)
|
||||
eye_layer = image('icons/mob/pai32x64.dmi', chassis_data.holo_eyes_icon_state)
|
||||
else if(holo_icon_dimension_X == 64 && holo_icon_dimension_Y == 32)
|
||||
eye_layer = image('icons/mob/pai64x32.dmi', chassis_data.holo_eyes_icon_state)
|
||||
else if(holo_icon_dimension_X == 64 && holo_icon_dimension_Y == 64)
|
||||
eye_layer = image('icons/mob/pai64x64.dmi', chassis_data.holo_eyes_icon_state)
|
||||
else if(chassis_data.has_eye_sprites)
|
||||
// Default eye handling
|
||||
eye_layer = image(icon, "[icon_state]-eyes")
|
||||
else
|
||||
// No eyes, so don't bother setting icon stuff
|
||||
return
|
||||
eye_layer.appearance_flags = appearance_flags
|
||||
eye_layer.color = eye_color
|
||||
if(eye_glow && !hide_glow)
|
||||
eye_layer.plane = PLANE_LIGHTING_ABOVE
|
||||
add_overlay(eye_layer)
|
||||
|
||||
/// Removes the eye overlay if it has one
|
||||
/mob/living/silicon/pai/proc/remove_eyes()
|
||||
if(!eye_layer)
|
||||
return
|
||||
cut_overlay(eye_layer)
|
||||
qdel(eye_layer)
|
||||
eye_layer = null
|
||||
|
||||
/// Gets icons for all four directions based on the character slot currently loaded
|
||||
/mob/living/silicon/pai/proc/get_character_icon()
|
||||
if(!client || !client.prefs) return FALSE
|
||||
var/mob/living/carbon/human/dummy/dummy = new ()
|
||||
//This doesn't include custom_items because that's ... hard.
|
||||
client.prefs.dress_preview_mob(dummy)
|
||||
sleep(1 SECOND) //Strange bug in preview code? Without this, certain things won't show up. Yay race conditions?
|
||||
dummy.regenerate_icons()
|
||||
|
||||
var/icon/new_holo = getCompoundIcon(dummy)
|
||||
|
||||
dummy.tail_layering = TRUE
|
||||
dummy.set_dir(NORTH)
|
||||
var/icon/new_holo_north = getCompoundIcon(dummy)
|
||||
dummy.set_dir(EAST)
|
||||
var/icon/new_holo_east = getCompoundIcon(dummy)
|
||||
dummy.set_dir(WEST)
|
||||
var/icon/new_holo_west = getCompoundIcon(dummy)
|
||||
|
||||
qdel(holo_icon_south)
|
||||
qdel(holo_icon_north)
|
||||
qdel(holo_icon_east)
|
||||
qdel(holo_icon_west)
|
||||
qdel(dummy)
|
||||
holo_icon_south = new_holo
|
||||
holo_icon_north = new_holo_north
|
||||
holo_icon_east = new_holo_east
|
||||
holo_icon_west = new_holo_west
|
||||
return TRUE
|
||||
|
||||
/mob/living/silicon/pai/set_dir(var/new_dir)
|
||||
. = ..()
|
||||
if(. && SSpai.chassis_data(chassis_name).holo_projector)
|
||||
switch(dir)
|
||||
if(SOUTH)
|
||||
icon = holo_icon_south
|
||||
if(NORTH)
|
||||
icon = holo_icon_north
|
||||
if(EAST)
|
||||
icon = holo_icon_east
|
||||
if(WEST)
|
||||
icon = holo_icon_west
|
||||
else
|
||||
M.Translate(1,-6)
|
||||
layer = MOB_LAYER -0.01 // Fix for a byond bug where turf entry order no longer matters
|
||||
else
|
||||
M.Scale(desired_scale_x, desired_scale_y)
|
||||
M.Translate(0, (vis_height/2)*(desired_scale_y-1))
|
||||
layer = MOB_LAYER // Fix for a byond bug where turf entry order no longer matters
|
||||
animate(src, transform = M, time = anim_time)
|
||||
src.transform = M
|
||||
handle_status_indicators()
|
||||
*/
|
||||
//Overriding this will stop a number of headaches down the track.
|
||||
/mob/living/silicon/pai/attackby(obj/item/W as obj, mob/user as mob)
|
||||
if(W.force)
|
||||
visible_message(span_danger("[user.name] attacks [src] with [W]!"))
|
||||
src.adjustBruteLoss(W.force)
|
||||
src.updatehealth()
|
||||
else
|
||||
visible_message(span_warning("[user.name] bonks [src] harmlessly with [W]."))
|
||||
spawn(1)
|
||||
if(stat != 2) close_up()
|
||||
return
|
||||
icon = holo_icon_north
|
||||
|
||||
/mob/living/silicon/pai/attack_hand(mob/user as mob)
|
||||
if(user.a_intent == I_HELP)
|
||||
visible_message(span_notice("[user.name] pats [src]."))
|
||||
else
|
||||
visible_message(span_danger("[user.name] boops [src] on the head."))
|
||||
close_up()
|
||||
/mob/living/silicon/pai/a_intent_change(input as text)
|
||||
. = ..()
|
||||
|
||||
//I'm not sure how much of this is necessary, but I would rather avoid issues.
|
||||
/mob/living/silicon/pai/proc/close_up(silent= FALSE)
|
||||
switch(a_intent)
|
||||
if(I_HELP)
|
||||
hud_used.help_intent.icon_state = "intent_help-s"
|
||||
hud_used.disarm_intent.icon_state = "intent_disarm-n"
|
||||
hud_used.grab_intent.icon_state = "intent_grab-n"
|
||||
hud_used.hurt_intent.icon_state = "intent_harm-n"
|
||||
|
||||
last_special = world.time + 100
|
||||
if(I_DISARM)
|
||||
hud_used.help_intent.icon_state = "intent_help-n"
|
||||
hud_used.disarm_intent.icon_state = "intent_disarm-s"
|
||||
hud_used.grab_intent.icon_state = "intent_grab-n"
|
||||
hud_used.hurt_intent.icon_state = "intent_harm-n"
|
||||
|
||||
if(loc == card)
|
||||
return
|
||||
if(I_GRAB)
|
||||
hud_used.help_intent.icon_state = "intent_help-n"
|
||||
hud_used.disarm_intent.icon_state = "intent_disarm-n"
|
||||
hud_used.grab_intent.icon_state = "intent_grab-s"
|
||||
hud_used.hurt_intent.icon_state = "intent_harm-n"
|
||||
|
||||
// some snowflake locations where we really shouldn't fold up...
|
||||
if(is_folding_unsafe(loc))
|
||||
to_chat(src, span_danger("It's not safe to fold up while inside a [loc]!"))
|
||||
return
|
||||
|
||||
release_vore_contents(FALSE) //VOREStation Add
|
||||
|
||||
var/turf/T = get_turf(src)
|
||||
if(istype(T) && !silent) T.visible_message(span_filter_notice(span_bold("[src]") + " neatly folds inwards, compacting down to a rectangular card."))
|
||||
|
||||
stop_pulling()
|
||||
|
||||
//stop resting
|
||||
resting = 0
|
||||
|
||||
// If we are being held, handle removing our holder from their inv.
|
||||
var/obj/item/holder/our_holder = loc
|
||||
if(istype(our_holder))
|
||||
var/turf/drop_turf = get_turf(our_holder)
|
||||
var/mob/living/M = our_holder.loc
|
||||
if(istype(M))
|
||||
M.drop_from_inventory(our_holder)
|
||||
src.forceMove(card)
|
||||
card.forceMove(drop_turf)
|
||||
|
||||
if(isbelly(loc)) //If in tumby, when fold up, card go into tumby
|
||||
var/obj/belly/B = loc
|
||||
src.forceMove(card)
|
||||
card.forceMove(B)
|
||||
|
||||
if(isdisposalpacket(loc))
|
||||
var/obj/structure/disposalholder/hold = loc
|
||||
src.forceMove(card)
|
||||
card.forceMove(hold)
|
||||
|
||||
else //Otherwise go on floor
|
||||
card.forceMove(get_turf(src))
|
||||
src.forceMove(card)
|
||||
|
||||
canmove = 1
|
||||
resting = 0
|
||||
icon_state = "[chassis]"
|
||||
if(isopenspace(card.loc))
|
||||
fall()
|
||||
remove_verb(src, /mob/living/silicon/pai/proc/pai_nom)
|
||||
remove_verb(src, /mob/living/proc/vertical_nom)
|
||||
|
||||
/mob/living/silicon/pai/proc/is_folding_unsafe(check_location)
|
||||
return isbelly(check_location) || istype(check_location, /obj/machinery) || istype(check_location, /obj/item/storage/vore_egg || istype(check_location, /obj/item/pda))
|
||||
|
||||
// No binary for pAIs.
|
||||
/mob/living/silicon/pai/binarycheck()
|
||||
return 0
|
||||
|
||||
// Handle being picked up.
|
||||
/mob/living/silicon/pai/get_scooped(var/mob/living/carbon/grabber, var/self_drop)
|
||||
var/obj/item/holder/H = ..(grabber, self_drop)
|
||||
if(!istype(H))
|
||||
return
|
||||
|
||||
H.icon_state = "[chassis]"
|
||||
grabber.update_inv_l_hand()
|
||||
grabber.update_inv_r_hand()
|
||||
return H
|
||||
|
||||
/mob/living/silicon/pai/attackby(obj/item/W as obj, mob/user as mob)
|
||||
var/obj/item/card/id/ID = W.GetID()
|
||||
if(ID)
|
||||
if (idaccessible == 1)
|
||||
switch(tgui_alert(user, "Do you wish to add access to [src] or remove access from [src]?","Access Modify",list("Add Access","Remove Access", "Cancel")))
|
||||
if("Add Access")
|
||||
idcard.access |= ID.GetAccess()
|
||||
to_chat(user, span_notice("You add the access from the [W] to [src]."))
|
||||
to_chat(src, span_notice("\The [user] swipes the [W] over you. You copy the access codes."))
|
||||
if(radio)
|
||||
radio.recalculateChannels()
|
||||
return
|
||||
if("Remove Access")
|
||||
idcard.access = list()
|
||||
to_chat(user, span_notice("You remove the access from [src]."))
|
||||
to_chat(src, span_warning("\The [user] swipes the [W] over you, removing access codes from you."))
|
||||
if(radio)
|
||||
radio.recalculateChannels()
|
||||
return
|
||||
if("Cancel", null)
|
||||
return
|
||||
else if (istype(W, /obj/item/card/id) && idaccessible == 0)
|
||||
to_chat(user, span_notice("[src] is not accepting access modifcations at this time."))
|
||||
return
|
||||
|
||||
/mob/living/silicon/pai/verb/allowmodification()
|
||||
set name = "Change Access Modifcation Permission"
|
||||
set category = "Abilities.pAI Commands"
|
||||
set desc = "Allows people to modify your access or block people from modifying your access."
|
||||
|
||||
if(idaccessible == 0)
|
||||
idaccessible = 1
|
||||
visible_message(span_notice("\The [src] clicks as their access modification slot opens."),span_notice("You allow access modifications."), runemessage = "click")
|
||||
else
|
||||
idaccessible = 0
|
||||
visible_message(span_notice("\The [src] clicks as their access modification slot closes."),span_notice("You block access modfications."), runemessage = "click")
|
||||
|
||||
|
||||
/mob/living/silicon/pai/verb/wipe_software()
|
||||
set name = "Enter Storage"
|
||||
set category = "Abilities.pAI Commands"
|
||||
set desc = "Upload your personality to the cloud and wipe your software from the card. This is functionally equivalent to cryo or robotic storage, freeing up your job slot."
|
||||
|
||||
// Make sure people don't kill themselves accidentally
|
||||
if(tgui_alert(src, "WARNING: This will immediately wipe your software and ghost you, removing your character from the round permanently (similar to cryo and robotic storage). Are you entirely sure you want to do this?", "Wipe Software", list("No", "Yes")) != "Yes")
|
||||
return
|
||||
|
||||
close_up()
|
||||
visible_message(span_filter_notice(span_bold("[src]") + " fades away from the screen, the pAI device goes silent."))
|
||||
card.removePersonality()
|
||||
clear_client()
|
||||
if(I_HURT)
|
||||
hud_used.help_intent.icon_state = "intent_help-n"
|
||||
hud_used.disarm_intent.icon_state = "intent_disarm-n"
|
||||
hud_used.grab_intent.icon_state = "intent_grab-n"
|
||||
hud_used.hurt_intent.icon_state = "intent_harm-s"
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
/mob/living/silicon/pai/verb/fold_out()
|
||||
set category = "Abilities.pAI Commands"
|
||||
set name = "Unfold Chassis"
|
||||
|
||||
if(stat || sleeping || paralysis || weakened)
|
||||
return
|
||||
|
||||
if(loc != card)
|
||||
return
|
||||
|
||||
// Lets not trap the pai forever. These are special cases we want to escape out of when in our card
|
||||
if(istype(loc.loc, /obj/item/pda))
|
||||
var/obj/item/pda/ourpda = loc.loc
|
||||
if(ourpda.pai == card)
|
||||
ourpda.pai.forceMove(ourpda.loc)
|
||||
ourpda.pai = null
|
||||
visible_message(span_warning("\The [card] ejects itself from \the [ourpda]."))
|
||||
return
|
||||
if(istype(loc.loc, /obj/item/storage/vore_egg))
|
||||
var/obj/item/storage/vore_egg/ouregg = loc.loc
|
||||
to_chat(src, span_notice("You craftily use your built in rumble function to break free of \the [ouregg]'s confines!"))
|
||||
ouregg.hatch(src)
|
||||
return
|
||||
|
||||
if(is_folding_unsafe(loc.loc))
|
||||
to_chat(src, span_danger("It's not safe to unfold while inside a [loc.loc]!"))
|
||||
return
|
||||
|
||||
if(card.projector != PP_FUNCTIONAL && card.emitter != PP_FUNCTIONAL)
|
||||
to_chat(src, span_warning("ERROR: System malfunction. Service required!"))
|
||||
|
||||
if(world.time <= last_special)
|
||||
to_chat(src, span_warning("You can't unfold yet."))
|
||||
return
|
||||
|
||||
last_special = world.time + 100
|
||||
|
||||
if(istype(card.loc, /obj/machinery)) // VOREStation edit, this statement allows pAIs stuck in a machine to eject themselves.
|
||||
var/obj/machinery/M = card.loc
|
||||
M.ejectpai()
|
||||
//I'm not sure how much of this is necessary, but I would rather avoid issues.
|
||||
if(istype(card.loc,/obj/item/rig_module))
|
||||
to_chat(src, span_filter_notice("There is no room to unfold inside this rig module. You're good and stuck."))
|
||||
return 0
|
||||
else if(istype(card.loc,/mob))
|
||||
var/mob/holder = card.loc
|
||||
if(ishuman(holder))
|
||||
var/mob/living/carbon/human/H = holder
|
||||
for(var/obj/item/organ/external/affecting in H.organs)
|
||||
if(card in affecting.implants)
|
||||
affecting.take_damage(rand(30,50))
|
||||
affecting.implants -= card
|
||||
H.visible_message(span_danger("\The [src] explodes out of \the [H]'s [affecting.name] in shower of gore!"))
|
||||
break
|
||||
holder.drop_from_inventory(card)
|
||||
else if(isbelly(card.loc)) //VOREStation edit.
|
||||
to_chat(src, span_notice("There is no room to unfold in here. You're good and stuck.")) //VOREStation edit.
|
||||
return 0 //VOREStation edit.
|
||||
else if(istype(card.loc,/obj/item/pda))
|
||||
var/obj/item/pda/holder = card.loc
|
||||
holder.pai = null
|
||||
|
||||
src.forceMove(card.loc)
|
||||
card.forceMove(src)
|
||||
card.screen_loc = null
|
||||
canmove = TRUE
|
||||
|
||||
if(isturf(loc))
|
||||
var/turf/T = get_turf(src)
|
||||
if(istype(T)) T.visible_message(span_filter_notice(span_bold("[src]") + " folds outwards, expanding into a mobile form."))
|
||||
|
||||
add_verb(src, /mob/living/silicon/pai/proc/pai_nom)
|
||||
add_verb(src, /mob/living/proc/vertical_nom)
|
||||
update_icon()
|
||||
|
||||
/mob/living/silicon/pai/verb/fold_up()
|
||||
set category = "Abilities.pAI Commands"
|
||||
set name = "Collapse Chassis"
|
||||
|
||||
if(stat || sleeping || paralysis || weakened)
|
||||
return
|
||||
|
||||
if(src.loc == card)
|
||||
return
|
||||
|
||||
if(world.time <= last_special)
|
||||
to_chat(src, span_warning("You can't fold up yet."))
|
||||
return
|
||||
|
||||
close_up()
|
||||
|
||||
//I'm not sure how much of this is necessary, but I would rather avoid issues.
|
||||
/mob/living/silicon/pai/proc/close_up(silent= FALSE)
|
||||
|
||||
last_special = world.time + 100
|
||||
|
||||
if(loc == card)
|
||||
return
|
||||
|
||||
// some snowflake locations where we really shouldn't fold up...
|
||||
if(is_folding_unsafe(loc))
|
||||
to_chat(src, span_danger("It's not safe to fold up while inside a [loc]!"))
|
||||
return
|
||||
|
||||
release_vore_contents(FALSE) //VOREStation Add
|
||||
|
||||
var/turf/T = get_turf(src)
|
||||
if(istype(T) && !silent) T.visible_message(span_filter_notice(span_bold("[src]") + " neatly folds inwards, compacting down to a rectangular card."))
|
||||
|
||||
stop_pulling()
|
||||
|
||||
//stop resting
|
||||
resting = 0
|
||||
|
||||
// If we are being held, handle removing our holder from their inv.
|
||||
var/obj/item/holder/our_holder = loc
|
||||
if(istype(our_holder))
|
||||
var/turf/drop_turf = get_turf(our_holder)
|
||||
var/mob/living/M = our_holder.loc
|
||||
if(istype(M))
|
||||
M.drop_from_inventory(our_holder)
|
||||
src.forceMove(card)
|
||||
card.forceMove(drop_turf)
|
||||
|
||||
if(isbelly(loc)) //If in tumby, when fold up, card go into tumby
|
||||
var/obj/belly/B = loc
|
||||
src.forceMove(card)
|
||||
card.forceMove(B)
|
||||
|
||||
if(isdisposalpacket(loc))
|
||||
var/obj/structure/disposalholder/hold = loc
|
||||
src.forceMove(card)
|
||||
card.forceMove(hold)
|
||||
|
||||
else //Otherwise go on floor
|
||||
card.forceMove(get_turf(src))
|
||||
src.forceMove(card)
|
||||
|
||||
canmove = 1
|
||||
resting = 0
|
||||
icon_state = SSpai.chassis_data(chassis_name).sprite_icon_state
|
||||
if(isopenspace(card.loc))
|
||||
fall()
|
||||
remove_verb(src, /mob/living/silicon/pai/proc/pai_nom)
|
||||
remove_verb(src, /mob/living/proc/vertical_nom)
|
||||
|
||||
/mob/living/silicon/pai/proc/is_folding_unsafe(check_location)
|
||||
return isbelly(check_location) || istype(check_location, /obj/machinery) || istype(check_location, /obj/item/storage/vore_egg || istype(check_location, /obj/item/pda))
|
||||
@@ -1,6 +1,3 @@
|
||||
/mob/living/silicon/pai
|
||||
var/atom/movable/screen/pai/pai_fold_display = null
|
||||
|
||||
/atom/movable/screen/pai
|
||||
icon = 'icons/mob/pai_hud.dmi'
|
||||
var/base_state
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
/mob/living/silicon/pai/proc/refresh_software_status() //This manages the pAI software status buttons icon states based on if you have them and if they are enabled
|
||||
for(var/thing in software) //this only gets called when you click one of the relevent buttons, rather than all the time!
|
||||
var/datum/pai_software/soft = software[thing]
|
||||
if(istype(soft,/datum/pai_software/med_records))
|
||||
soft_mr = TRUE
|
||||
if(istype(soft,/datum/pai_software/sec_records))
|
||||
soft_sr = TRUE
|
||||
if(istype(soft,/datum/pai_software/door_jack))
|
||||
soft_dj = TRUE
|
||||
if(istype(soft,/datum/pai_software/atmosphere_sensor))
|
||||
soft_as = TRUE
|
||||
if(istype(soft,/datum/pai_software/pai_hud))
|
||||
soft_ar = TRUE
|
||||
if(istype(soft,/datum/pai_software/translator))
|
||||
soft_ut = TRUE
|
||||
if(istype(soft,/datum/pai_software/signaller))
|
||||
soft_si = TRUE
|
||||
if(istype(soft,/datum/pai_software/deathalarm))
|
||||
soft_da = TRUE
|
||||
for(var/atom/movable/screen/pai/button in hud_used.other)
|
||||
if(button.name == "medical records")
|
||||
if(soft_mr)
|
||||
button.icon_state = "[button.base_state]"
|
||||
else
|
||||
button.icon_state = "[button.base_state]_o"
|
||||
if(button.name == "security records")
|
||||
if(soft_sr)
|
||||
button.icon_state = "[button.base_state]"
|
||||
else
|
||||
button.icon_state = "[button.base_state]_o"
|
||||
if(button.name == "door jack")
|
||||
if(soft_dj)
|
||||
button.icon_state = "[button.base_state]"
|
||||
else
|
||||
button.icon_state = "[button.base_state]_o"
|
||||
if(button.name == "atmosphere sensor")
|
||||
if(soft_as)
|
||||
button.icon_state = "[button.base_state]"
|
||||
else
|
||||
button.icon_state = "[button.base_state]_o"
|
||||
if(button.name == "remote signaler")
|
||||
if(soft_si)
|
||||
button.icon_state = "[button.base_state]"
|
||||
else
|
||||
button.icon_state = "[button.base_state]_o"
|
||||
if(button.name == "universal translator")
|
||||
if(soft_ut && translator_on)
|
||||
button.icon_state = "[button.base_state]"
|
||||
else
|
||||
button.icon_state = "[button.base_state]_o"
|
||||
if(button.name == "ar hud")
|
||||
if(soft_ar && paiHUD)
|
||||
button.icon_state = "[button.base_state]"
|
||||
else
|
||||
button.icon_state = "[button.base_state]_o"
|
||||
if(button.name == "death alarm")
|
||||
if(soft_da && paiDA)
|
||||
button.icon_state = "[button.base_state]"
|
||||
else
|
||||
button.icon_state = "[button.base_state]_o"
|
||||
|
||||
/mob/living/silicon/pai/verb/wipe_software()
|
||||
set name = "Enter Storage"
|
||||
set category = "Abilities.pAI Commands"
|
||||
set desc = "Upload your personality to the cloud and wipe your software from the card. This is functionally equivalent to cryo or robotic storage, freeing up your job slot."
|
||||
|
||||
// Make sure people don't kill themselves accidentally
|
||||
if(tgui_alert(src, "WARNING: This will immediately wipe your software and ghost you, removing your character from the round permanently (similar to cryo and robotic storage). Are you entirely sure you want to do this?", "Wipe Software", list("No", "Yes")) != "Yes")
|
||||
return
|
||||
|
||||
close_up()
|
||||
visible_message(span_filter_notice(span_bold("[src]") + " fades away from the screen, the pAI device goes silent."))
|
||||
card.removePersonality()
|
||||
clear_client()
|
||||
|
||||
/mob/living/silicon/pai/proc/touch_window(soft_name) //This lets us touch TGUI procs and windows that may be nested behind other TGUI procs and windows
|
||||
if(stat != CONSCIOUS) //so we can access our software without having to open up the software interface TGUI window
|
||||
to_chat(src, span_warning("You can't do that right now."))
|
||||
return
|
||||
for(var/thing in software)
|
||||
var/datum/pai_software/S = software[thing]
|
||||
if(istype(S, /datum/pai_software) && S.name == soft_name)
|
||||
if(S.toggle)
|
||||
S.toggle(src)
|
||||
to_chat(src, span_notice("You toggled [S.name]."))
|
||||
refresh_software_status()
|
||||
else
|
||||
S.tgui_interact(src)
|
||||
refresh_software_status()
|
||||
return
|
||||
for(var/thing in GLOB.pai_software_by_key)
|
||||
var/datum/pai_software/our_soft = GLOB.pai_software_by_key[thing]
|
||||
if(our_soft.name == soft_name)
|
||||
if(!(ram >= our_soft.ram_cost))
|
||||
to_chat(src, span_warning("Insufficient RAM for download. (Cost [our_soft.ram_cost] : [ram] Remaining)"))
|
||||
return
|
||||
if(tgui_alert(src, "Do you want to download [our_soft.name]? It costs [our_soft.ram_cost], and you have [ram] remaining.", "Download [our_soft.name]", list("Yes", "No")) == "Yes")
|
||||
if(!(ram >= our_soft.ram_cost))
|
||||
return
|
||||
if(software[our_soft.id])
|
||||
return
|
||||
ram -= our_soft.ram_cost
|
||||
software[our_soft.id] = our_soft
|
||||
to_chat(src, span_notice("You downloaded [our_soft.name]. ([ram] RAM remaining.)"))
|
||||
refresh_software_status()
|
||||
|
||||
//Procs for using the various UI buttons for your softwares
|
||||
/mob/living/silicon/pai/proc/directives()
|
||||
touch_window("Directives")
|
||||
|
||||
/mob/living/silicon/pai/proc/crew_manifest()
|
||||
touch_window("Crew Manifest")
|
||||
|
||||
/mob/living/silicon/pai/proc/med_records()
|
||||
touch_window("Medical Records")
|
||||
|
||||
/mob/living/silicon/pai/proc/sec_records()
|
||||
touch_window("Security Records")
|
||||
|
||||
/mob/living/silicon/pai/proc/remote_signal()
|
||||
touch_window("Remote Signaler")
|
||||
|
||||
/mob/living/silicon/pai/proc/atmos_sensor()
|
||||
touch_window("Atmosphere Sensor")
|
||||
|
||||
/mob/living/silicon/pai/proc/translator()
|
||||
touch_window("Universal Translator")
|
||||
|
||||
/mob/living/silicon/pai/proc/door_jack()
|
||||
touch_window("Door Jack")
|
||||
|
||||
/mob/living/silicon/pai/proc/ar_hud()
|
||||
touch_window("AR HUD")
|
||||
|
||||
/mob/living/silicon/pai/proc/death_alarm()
|
||||
touch_window("Death Alarm")
|
||||
@@ -0,0 +1,219 @@
|
||||
/// Change currently viewed camera
|
||||
/mob/living/silicon/pai/proc/switchCamera(var/obj/machinery/camera/C)
|
||||
if (!C)
|
||||
src.reset_perspective()
|
||||
return 0
|
||||
if (stat == 2 || !C.status || !(src.network in C.network)) return 0
|
||||
|
||||
// ok, we're alive, camera is good and in our network...
|
||||
src.current = C
|
||||
src.AddComponent(/datum/component/remote_view, focused_on = C, viewsize = null, vconfig_path = /datum/remote_view_config/camera_standard)
|
||||
return 1
|
||||
|
||||
/mob/living/silicon/pai/cancel_camera()
|
||||
set category = "Abilities.pAI Commands"
|
||||
set name = "Cancel Camera View"
|
||||
reset_perspective()
|
||||
|
||||
/mob/living/silicon/pai/reset_perspective(atom/new_eye)
|
||||
. = ..()
|
||||
current = null
|
||||
|
||||
/mob/living/silicon/pai/verb/reset_record_view()
|
||||
set category = "Abilities.pAI Commands"
|
||||
set name = "Reset Records Software"
|
||||
|
||||
securityActive1 = null
|
||||
securityActive2 = null
|
||||
security_cannotfind = 0
|
||||
medicalActive1 = null
|
||||
medicalActive2 = null
|
||||
medical_cannotfind = 0
|
||||
SStgui.update_uis(src)
|
||||
to_chat(src, span_notice("You reset your record-viewing software."))
|
||||
|
||||
/mob/living/silicon/pai/proc/choose_verbs()
|
||||
set category = "Abilities.pAI Commands"
|
||||
set name = "Choose Speech Verbs"
|
||||
|
||||
var/choice = tgui_input_list(src,"What theme would you like to use for your speech verbs?","Theme Choice", GLOB.possible_say_verbs)
|
||||
if(!choice) return
|
||||
|
||||
var/list/sayverbs = GLOB.possible_say_verbs[choice]
|
||||
speak_statement = sayverbs[1]
|
||||
speak_exclamation = sayverbs[(sayverbs.len>1 ? 2 : sayverbs.len)]
|
||||
speak_query = sayverbs[(sayverbs.len>2 ? 3 : sayverbs.len)]
|
||||
|
||||
/mob/living/silicon/pai/verb/allowmodification()
|
||||
set name = "Change Access Modifcation Permission"
|
||||
set category = "Abilities.pAI Commands"
|
||||
set desc = "Allows people to modify your access or block people from modifying your access."
|
||||
|
||||
if(idaccessible == 0)
|
||||
idaccessible = 1
|
||||
visible_message(span_notice("\The [src] clicks as their access modification slot opens."),span_notice("You allow access modifications."), runemessage = "click")
|
||||
else
|
||||
idaccessible = 0
|
||||
visible_message(span_notice("\The [src] clicks as their access modification slot closes."),span_notice("You block access modfications."), runemessage = "click")
|
||||
|
||||
/mob/living/silicon/pai/verb/toggle_gender_identity_vr()
|
||||
set name = "Set Gender Identity"
|
||||
set desc = "Sets the pronouns when examined and performing an emote."
|
||||
set category = "IC.Settings"
|
||||
var/new_gender_identity = tgui_input_list(src, "Please select a gender Identity:", "Set Gender Identity", list(FEMALE, MALE, NEUTER, PLURAL, HERM))
|
||||
if(!new_gender_identity)
|
||||
return 0
|
||||
gender = new_gender_identity
|
||||
return 1
|
||||
|
||||
/mob/living/silicon/pai/verb/pai_hide()
|
||||
set name = "Hide"
|
||||
set desc = "Allows to hide beneath tables or certain items. Toggled on or off."
|
||||
set category = "Abilities.pAI"
|
||||
|
||||
hide()
|
||||
if(status_flags & HIDING)
|
||||
hide_glow = TRUE
|
||||
else
|
||||
hide_glow = FALSE
|
||||
update_icon()
|
||||
|
||||
/mob/living/silicon/pai/verb/screen_message(message as text|null)
|
||||
set category = "Abilities.pAI Commands"
|
||||
set name = "Screen Message"
|
||||
set desc = "Allows you to display a message on your screen. This will show up in the chat of anyone who is holding your card."
|
||||
|
||||
if (src.client)
|
||||
if(client.prefs.muted & MUTE_IC)
|
||||
to_chat(src, span_warning("You cannot speak in IC (muted)."))
|
||||
return
|
||||
if(loc != card)
|
||||
to_chat(src, span_warning("Your message won't be visible while unfolded!"))
|
||||
if (!message)
|
||||
message = tgui_input_text(src, "Enter text you would like to show on your screen.","Screen Message", encode = FALSE)
|
||||
message = sanitize_or_reflect(message,src)
|
||||
if (!message)
|
||||
return
|
||||
message = capitalize(message)
|
||||
if (stat == DEAD)
|
||||
return
|
||||
card.screen_msg = message
|
||||
var/logmsg = "(CARD SCREEN)[message]"
|
||||
log_talk(logmsg, LOG_SAY)
|
||||
to_chat(src, span_filter_say(span_cult("You print a message to your screen, \"[message]\"")))
|
||||
if(isliving(card.loc))
|
||||
var/mob/living/L = card.loc
|
||||
if(L.client)
|
||||
to_chat(L, span_filter_say(span_cult("[src.name]'s screen prints, \"[message]\"")))
|
||||
else return
|
||||
else if(isbelly(card.loc))
|
||||
var/obj/belly/b = card.loc
|
||||
if(b.owner.client)
|
||||
to_chat(b.owner, span_filter_say(span_cult("[src.name]'s screen prints, \"[message]\"")))
|
||||
else return
|
||||
else if(istype(card.loc, /obj/item/pda))
|
||||
var/obj/item/pda/p = card.loc
|
||||
if(isliving(p.loc))
|
||||
var/mob/living/L = p.loc
|
||||
if(L.client)
|
||||
to_chat(L, span_filter_say(span_cult("[src.name]'s screen prints, \"[message]\"")))
|
||||
else return
|
||||
else if(isbelly(p.loc))
|
||||
var/obj/belly/b = card.loc
|
||||
if(b.owner.client)
|
||||
to_chat(b.owner, span_filter_say(span_cult("[src.name]'s screen prints, \"[message]\"")))
|
||||
else return
|
||||
else return
|
||||
else return
|
||||
to_chat(src, span_notice("Your message was relayed."))
|
||||
for (var/mob/G in GLOB.player_list)
|
||||
if (isnewplayer(G))
|
||||
continue
|
||||
else if(isobserver(G) && G.client?.prefs?.read_preference(/datum/preference/toggle/ghost_ears))
|
||||
if((client?.prefs?.read_preference(/datum/preference/toggle/whisubtle_vis) || check_rights_for(G.client, R_HOLDER)) && \
|
||||
G.client?.prefs?.read_preference(/datum/preference/toggle/ghost_see_whisubtle))
|
||||
to_chat(G, span_filter_say(span_cult("[src.name]'s screen prints, \"[message]\"")))
|
||||
|
||||
/mob/living/silicon/pai/proc/pai_nom(var/mob/living/T in oview(1))
|
||||
set name = "pAI Nom"
|
||||
set category = "Abilities.pAI Commands"
|
||||
set desc = "Allows you to eat someone while unfolded. Can't be used while in card form."
|
||||
|
||||
if (stat != CONSCIOUS)
|
||||
return
|
||||
return feed_grabbed_to_self(src,T)
|
||||
|
||||
/mob/living/silicon/pai/verb/toggle_eyeglow()
|
||||
set category = "Abilities.pAI Commands"
|
||||
set name = "Toggle Eye Glow"
|
||||
|
||||
if(!SSpai.chassis_data(chassis_name).has_eye_sprites)
|
||||
to_chat(src, span_filter_notice("Your selected chassis cannot modify its eye glow!"))
|
||||
return
|
||||
|
||||
if(eye_glow && !hide_glow)
|
||||
eye_glow = FALSE
|
||||
else
|
||||
eye_glow = TRUE
|
||||
hide_glow = FALSE
|
||||
update_icon()
|
||||
|
||||
/mob/living/silicon/pai/verb/pick_eye_color()
|
||||
set category = "Abilities.pAI Commands"
|
||||
set name = "Pick Eye Color"
|
||||
|
||||
if(!SSpai.chassis_data(chassis_name).has_eye_sprites)
|
||||
to_chat(src, span_warning("Your selected chassis eye color can not be modified. The color you pick will only apply to supporting chassis and your card screen."))
|
||||
return
|
||||
|
||||
var/new_eye_color = tgui_color_picker(src, "Choose your character's eye color:", "Eye Color")
|
||||
if(new_eye_color)
|
||||
eye_color = new_eye_color
|
||||
update_icon()
|
||||
card.setEmotion(card.current_emotion)
|
||||
|
||||
/mob/living/silicon/pai/proc/hug(var/mob/living/silicon/pai/H, var/mob/living/target)
|
||||
|
||||
var/t_him = "them"
|
||||
if(ishuman(target))
|
||||
var/mob/living/carbon/human/T = target
|
||||
switch(T.identifying_gender)
|
||||
if(MALE)
|
||||
t_him = "him"
|
||||
if(FEMALE)
|
||||
t_him = "her"
|
||||
if(NEUTER)
|
||||
t_him = "it"
|
||||
if(HERM)
|
||||
t_him = "hir"
|
||||
else
|
||||
t_him = "them"
|
||||
else
|
||||
switch(target.gender)
|
||||
if(MALE)
|
||||
t_him = "him"
|
||||
if(FEMALE)
|
||||
t_him = "her"
|
||||
if(NEUTER)
|
||||
t_him = "it"
|
||||
if(HERM)
|
||||
t_him = "hir"
|
||||
else
|
||||
t_him = "them"
|
||||
|
||||
if(H.zone_sel.selecting == BP_HEAD)
|
||||
H.visible_message( \
|
||||
span_notice("[H] pats [target] on the head."), \
|
||||
span_notice("You pat [target] on the head."), )
|
||||
else if(H.zone_sel.selecting == BP_R_HAND || H.zone_sel.selecting == BP_L_HAND)
|
||||
H.visible_message( \
|
||||
span_notice("[H] shakes [target]'s hand."), \
|
||||
span_notice("You shake [target]'s hand."), )
|
||||
else if(H.zone_sel.selecting == "mouth")
|
||||
H.visible_message( \
|
||||
span_notice("[H] boops [target]'s nose."), \
|
||||
span_notice("You boop [target] on the nose."), )
|
||||
else
|
||||
H.visible_message(span_notice("[H] hugs [target] to make [t_him] feel better!"), \
|
||||
span_notice("You hug [target] to make [t_him] feel better!"))
|
||||
playsound(src, 'sound/weapons/thudswoosh.ogg', 50, 1, -1)
|
||||
@@ -1,659 +0,0 @@
|
||||
/mob/living/silicon/pai
|
||||
icon = 'icons/mob/pai_vr.dmi'
|
||||
softfall = TRUE
|
||||
var/eye_glow = TRUE
|
||||
var/hide_glow = FALSE
|
||||
var/image/eye_layer = null // Holds the eye overlay.
|
||||
var/eye_color = "#00ff0d"
|
||||
var/icon/holo_icon
|
||||
var/icon/holo_icon_north
|
||||
var/holo_icon_dimension_X = 32
|
||||
var/holo_icon_dimension_Y = 32
|
||||
//These vars keep track of whether you have the related software, used for easily updating the UI
|
||||
var/soft_ut = FALSE //universal translator
|
||||
var/soft_mr = FALSE //medical records
|
||||
var/soft_sr = FALSE //security records
|
||||
var/soft_dj = FALSE //door jack
|
||||
var/soft_as = FALSE //atmosphere sensor
|
||||
var/soft_si = FALSE //signaler
|
||||
var/soft_ar = FALSE //ar hud
|
||||
var/soft_da = FALSE //death alarm
|
||||
|
||||
vore_capacity = 1
|
||||
vore_capacity_ex = list("stomach" = 1)
|
||||
|
||||
/mob/living/silicon/pai/Initialize(mapload)
|
||||
. = ..()
|
||||
|
||||
add_verb(src, /mob/proc/dominate_predator)
|
||||
add_verb(src, /mob/living/proc/dominate_prey)
|
||||
add_verb(src, /mob/living/proc/set_size)
|
||||
add_verb(src, /mob/living/proc/shred_limb)
|
||||
|
||||
/mob/living/silicon/pai/Login()
|
||||
. = ..()
|
||||
if(!holo_icon)
|
||||
last_special = world.time + 100 //Let's give get_character_icon time to work
|
||||
get_character_icon()
|
||||
if(stat == DEAD)
|
||||
healths.icon_state = "health7"
|
||||
|
||||
/mob/living/silicon/pai/proc/full_restore() //This is using do_after all kinds of weird...
|
||||
adjustBruteLoss(- bruteloss)
|
||||
adjustFireLoss(- fireloss)
|
||||
do_after(src, 1 SECONDS, target = src)
|
||||
card.setEmotion(16)
|
||||
stat = CONSCIOUS
|
||||
do_after(src, 5 SECONDS, target = src)
|
||||
var/mob/observer/dead/ghost = src.get_ghost()
|
||||
if(ghost)
|
||||
ghost.notify_revive("Someone is trying to revive you. Re-enter your body if you want to be revived!", 'sound/effects/pai-restore.ogg', source = card)
|
||||
canmove = TRUE
|
||||
card.setEmotion(15)
|
||||
playsound(card, 'sound/effects/pai-restore.ogg', 50, FALSE)
|
||||
card.visible_message(span_filter_notice("\The [card] chimes."), runemessage = "chime")
|
||||
|
||||
/mob/living/silicon/pai/proc/pai_nom(var/mob/living/T in oview(1))
|
||||
set name = "pAI Nom"
|
||||
set category = "Abilities.pAI Commands"
|
||||
set desc = "Allows you to eat someone while unfolded. Can't be used while in card form."
|
||||
|
||||
if (stat != CONSCIOUS)
|
||||
return
|
||||
return feed_grabbed_to_self(src,T)
|
||||
|
||||
/mob/living/silicon/pai/update_icon() //Some functions cause this to occur, such as resting
|
||||
..()
|
||||
if(chassis == "13")
|
||||
icon = holo_icon
|
||||
add_eyes()
|
||||
return
|
||||
|
||||
update_fullness()
|
||||
|
||||
//Add a check when selecting a chassis if you add in support for this, to set vore_capacity to 2 or however many states you have.
|
||||
var/fullness_extension = ""
|
||||
if(vore_capacity > 1 && vore_fullness > 1)
|
||||
fullness_extension = "_[vore_fullness]"
|
||||
|
||||
if(!vore_fullness && !resting)
|
||||
icon_state = "[chassis]" //Using icon_state here resulted in quite a few bugs. Chassis is much less buggy.
|
||||
else if(!vore_fullness && resting)
|
||||
icon_state = "[chassis]_rest"
|
||||
|
||||
// Unfortunately not all these states exist, ugh.
|
||||
else if(vore_fullness && !resting)
|
||||
if(icon_exists(icon, "[chassis]_full[fullness_extension]"))
|
||||
icon_state = "[chassis]_full[fullness_extension]"
|
||||
else
|
||||
icon_state = "[chassis]"
|
||||
else if(vore_fullness && resting)
|
||||
if(icon_exists(icon, "[chassis]_rest_full[fullness_extension]"))
|
||||
icon_state = "[chassis]_rest_full[fullness_extension]"
|
||||
else
|
||||
icon_state = "[chassis]_rest"
|
||||
if(chassis in GLOB.wide_chassis)
|
||||
pixel_x = -16
|
||||
default_pixel_x = -16
|
||||
else
|
||||
pixel_x = 0
|
||||
default_pixel_x = 0
|
||||
add_eyes()
|
||||
|
||||
/mob/living/silicon/pai/update_icons() //And other functions cause this to occur, such as digesting someone.
|
||||
..()
|
||||
if(chassis == "13")
|
||||
icon = holo_icon
|
||||
add_eyes()
|
||||
return
|
||||
update_fullness()
|
||||
//Add a check when selecting a chassis if you add in support for this, to set vore_capacity to 2 or however many states you have.
|
||||
var/fullness_extension = ""
|
||||
if(vore_capacity > 1 && vore_fullness > 1)
|
||||
fullness_extension = "_[vore_fullness]"
|
||||
if(!vore_fullness && !resting)
|
||||
icon_state = "[chassis]"
|
||||
else if(!vore_fullness && resting)
|
||||
icon_state = "[chassis]_rest"
|
||||
else if(vore_fullness && !resting)
|
||||
icon_state = "[chassis]_full[fullness_extension]"
|
||||
else if(vore_fullness && resting)
|
||||
icon_state = "[chassis]_rest_full[fullness_extension]"
|
||||
if(chassis in GLOB.wide_chassis)
|
||||
pixel_x = -16
|
||||
default_pixel_x = -16
|
||||
else
|
||||
pixel_x = 0
|
||||
default_pixel_x = 0
|
||||
add_eyes()
|
||||
|
||||
//proc override to avoid pAI players being invisible while the chassis selection window is open
|
||||
/mob/living/silicon/pai/proc/choose_chassis()
|
||||
set category = "Abilities.pAI Commands"
|
||||
set name = "Choose Chassis"
|
||||
var/choice
|
||||
|
||||
choice = tgui_input_list(src, "What would you like to use for your mobile chassis icon?", "Chassis Choice", GLOB.possible_chassis)
|
||||
if(!choice) return
|
||||
var/oursize = size_multiplier
|
||||
resize(1, FALSE, TRUE, TRUE, FALSE) //We resize ourselves to normal here for a moment to let the vis_height get reset
|
||||
chassis = GLOB.possible_chassis[choice]
|
||||
|
||||
vore_capacity = 1
|
||||
vore_capacity_ex = list("stomach" = 1)
|
||||
|
||||
if(chassis == "13")
|
||||
if(!holo_icon)
|
||||
if(!get_character_icon())
|
||||
return
|
||||
icon_state = null
|
||||
icon = holo_icon
|
||||
else if(chassis in GLOB.wide_chassis)
|
||||
icon = 'icons/mob/pai_vr64x64.dmi'
|
||||
vis_height = 64
|
||||
else
|
||||
icon = 'icons/mob/pai_vr.dmi'
|
||||
vis_height = 32
|
||||
resize(oursize, FALSE, TRUE, TRUE, FALSE) //And then back again now that we're sure the vis_height is correct.
|
||||
|
||||
if(chassis in GLOB.flying_chassis)
|
||||
hovering = TRUE
|
||||
else
|
||||
hovering = FALSE
|
||||
if(isopenspace(loc))
|
||||
fall()
|
||||
|
||||
update_icon()
|
||||
|
||||
/mob/living/silicon/pai/verb/toggle_eyeglow()
|
||||
set category = "Abilities.pAI Commands"
|
||||
set name = "Toggle Eye Glow"
|
||||
|
||||
if(chassis in GLOB.allows_eye_color)
|
||||
if(eye_glow && !hide_glow)
|
||||
eye_glow = FALSE
|
||||
else
|
||||
eye_glow = TRUE
|
||||
hide_glow = FALSE
|
||||
update_icon()
|
||||
else
|
||||
to_chat(src, span_filter_notice("Your selected chassis cannot modify its eye glow!"))
|
||||
return
|
||||
|
||||
|
||||
/mob/living/silicon/pai/verb/pick_eye_color()
|
||||
set category = "Abilities.pAI Commands"
|
||||
set name = "Pick Eye Color"
|
||||
if(!(chassis in GLOB.allows_eye_color))
|
||||
to_chat(src, span_warning("Your selected chassis eye color can not be modified. The color you pick will only apply to supporting chassis and your card screen."))
|
||||
|
||||
var/new_eye_color = tgui_color_picker(src, "Choose your character's eye color:", "Eye Color")
|
||||
if(new_eye_color)
|
||||
eye_color = new_eye_color
|
||||
update_icon()
|
||||
card.setEmotion(card.current_emotion)
|
||||
|
||||
// Release belly contents before being gc'd!
|
||||
/mob/living/silicon/pai/Destroy()
|
||||
release_vore_contents()
|
||||
if(ckey)
|
||||
GLOB.paikeys -= ckey
|
||||
return ..()
|
||||
|
||||
/mob/living/silicon/pai/clear_client()
|
||||
if(ckey)
|
||||
GLOB.paikeys -= ckey
|
||||
return ..()
|
||||
|
||||
/mob/living/silicon/pai/proc/add_eyes()
|
||||
remove_eyes()
|
||||
if(chassis == "13")
|
||||
if(holo_icon.Width() > 32)
|
||||
holo_icon_dimension_X = 64
|
||||
pixel_x = -16
|
||||
default_pixel_x = -16
|
||||
if(holo_icon.Height() > 32)
|
||||
holo_icon_dimension_Y = 64
|
||||
if(holo_icon_dimension_X == 32 && holo_icon_dimension_Y == 32)
|
||||
eye_layer = image('icons/mob/pai_vr.dmi', "type13-eyes")
|
||||
else if(holo_icon_dimension_X == 32 && holo_icon_dimension_Y == 64)
|
||||
eye_layer = image('icons/mob/pai_vr32x64.dmi', "type13-eyes")
|
||||
else if(holo_icon_dimension_X == 64 && holo_icon_dimension_Y == 32)
|
||||
eye_layer = image('icons/mob/pai_vr64x32.dmi', "type13-eyes")
|
||||
else if(holo_icon_dimension_X == 64 && holo_icon_dimension_Y == 64)
|
||||
eye_layer = image('icons/mob/pai_vr64x64.dmi', "type13-eyes")
|
||||
else if(chassis in GLOB.allows_eye_color)
|
||||
eye_layer = image(icon, "[icon_state]-eyes")
|
||||
else return
|
||||
eye_layer.appearance_flags = appearance_flags
|
||||
eye_layer.color = eye_color
|
||||
if(eye_glow && !hide_glow)
|
||||
eye_layer.plane = PLANE_LIGHTING_ABOVE
|
||||
add_overlay(eye_layer)
|
||||
|
||||
/mob/living/silicon/pai/proc/remove_eyes()
|
||||
cut_overlay(eye_layer)
|
||||
qdel(eye_layer)
|
||||
eye_layer = null
|
||||
|
||||
/mob/living/silicon/pai/UnarmedAttack(atom/A, proximity_flag)
|
||||
. = ..()
|
||||
|
||||
if(istype(A,/obj/structure/ladder))
|
||||
// Zmovement already allows these to be used with the verbs anyway
|
||||
var/obj/structure/ladder/L = A
|
||||
L.attack_hand(src)
|
||||
return
|
||||
|
||||
if(!ismob(A) || A == src)
|
||||
return
|
||||
|
||||
switch(a_intent)
|
||||
if(I_HELP)
|
||||
if(isliving(A))
|
||||
hug(src, A)
|
||||
if(I_GRAB)
|
||||
pai_nom(A)
|
||||
|
||||
// Allow card inhabited machines to be interacted with
|
||||
// This has to override ClickOn because of storage depth nonsense with how pAIs are in cards in GLOB.machines
|
||||
/mob/living/silicon/pai/ClickOn(var/atom/A, var/params)
|
||||
if(istype(A, /obj/machinery))
|
||||
var/obj/machinery/M = A
|
||||
if(M.paicard == card)
|
||||
M.attack_ai(src)
|
||||
return
|
||||
return ..()
|
||||
|
||||
/mob/living/silicon/pai/proc/hug(var/mob/living/silicon/pai/H, var/mob/living/target)
|
||||
|
||||
var/t_him = "them"
|
||||
if(ishuman(target))
|
||||
var/mob/living/carbon/human/T = target
|
||||
switch(T.identifying_gender)
|
||||
if(MALE)
|
||||
t_him = "him"
|
||||
if(FEMALE)
|
||||
t_him = "her"
|
||||
if(NEUTER)
|
||||
t_him = "it"
|
||||
if(HERM)
|
||||
t_him = "hir"
|
||||
else
|
||||
t_him = "them"
|
||||
else
|
||||
switch(target.gender)
|
||||
if(MALE)
|
||||
t_him = "him"
|
||||
if(FEMALE)
|
||||
t_him = "her"
|
||||
if(NEUTER)
|
||||
t_him = "it"
|
||||
if(HERM)
|
||||
t_him = "hir"
|
||||
else
|
||||
t_him = "them"
|
||||
|
||||
if(H.zone_sel.selecting == BP_HEAD)
|
||||
H.visible_message( \
|
||||
span_notice("[H] pats [target] on the head."), \
|
||||
span_notice("You pat [target] on the head."), )
|
||||
else if(H.zone_sel.selecting == BP_R_HAND || H.zone_sel.selecting == BP_L_HAND)
|
||||
H.visible_message( \
|
||||
span_notice("[H] shakes [target]'s hand."), \
|
||||
span_notice("You shake [target]'s hand."), )
|
||||
else if(H.zone_sel.selecting == "mouth")
|
||||
H.visible_message( \
|
||||
span_notice("[H] boops [target]'s nose."), \
|
||||
span_notice("You boop [target] on the nose."), )
|
||||
else
|
||||
H.visible_message(span_notice("[H] hugs [target] to make [t_him] feel better!"), \
|
||||
span_notice("You hug [target] to make [t_him] feel better!"))
|
||||
playsound(src, 'sound/weapons/thudswoosh.ogg', 50, 1, -1)
|
||||
|
||||
/mob/living/silicon/pai/proc/savefile_path(mob/user)
|
||||
return "data/player_saves/[copytext(user.ckey, 1, 2)]/[user.ckey]/pai.sav"
|
||||
|
||||
/mob/living/silicon/pai/proc/savefile_save(mob/user)
|
||||
if(IsGuestKey(user.key))
|
||||
return 0
|
||||
|
||||
var/savefile/F = new /savefile(src.savefile_path(user))
|
||||
|
||||
|
||||
F["name"] << src.name
|
||||
F["description"] << src.flavor_text
|
||||
F["eyecolor"] << src.eye_color
|
||||
F["chassis"] << src.chassis
|
||||
F["emotion"] << src.card.current_emotion
|
||||
F["gender"] << src.gender
|
||||
F["version"] << 1
|
||||
|
||||
return 1
|
||||
|
||||
/mob/living/silicon/pai/proc/savefile_load(mob/user, var/silent = 1)
|
||||
if (IsGuestKey(user.key))
|
||||
return 0
|
||||
|
||||
var/path = savefile_path(user)
|
||||
|
||||
if (!fexists(path))
|
||||
return 0
|
||||
|
||||
var/savefile/F = new /savefile(path)
|
||||
|
||||
if(!F) return //Not everyone has a pai savefile.
|
||||
|
||||
var/version = null
|
||||
F["version"] >> version
|
||||
|
||||
if (isnull(version) || version != 1)
|
||||
fdel(path)
|
||||
if (!silent)
|
||||
tgui_alert_async(user, "Your savefile was incompatible with this version and was deleted.")
|
||||
return 0
|
||||
var/ourname
|
||||
var/ouremotion
|
||||
var/ourdesc
|
||||
var/oureyes
|
||||
var/ourchassis
|
||||
var/ourgender
|
||||
F["name"] >> ourname
|
||||
F["description"] >> ourdesc
|
||||
F["eyecolor"] >> oureyes
|
||||
F["chassis"] >> ourchassis
|
||||
F["emotion"] >> ouremotion
|
||||
F["gender"] >> ourgender
|
||||
if(ourname)
|
||||
SetName(ourname)
|
||||
if(ourdesc)
|
||||
flavor_text = ourdesc
|
||||
if(ourchassis)
|
||||
chassis = ourchassis
|
||||
if(ourgender)
|
||||
gender = ourgender
|
||||
if(oureyes)
|
||||
card.screen_color = oureyes
|
||||
eye_color = oureyes
|
||||
if(ouremotion)
|
||||
card.setEmotion(ouremotion)
|
||||
|
||||
update_icon()
|
||||
return 1
|
||||
|
||||
/mob/living/silicon/pai/verb/save_pai_to_slot()
|
||||
set category = "Abilities.pAI Commands"
|
||||
set name = "Save Configuration"
|
||||
savefile_save(src)
|
||||
to_chat(src, span_filter_notice("[name] configuration saved to global pAI settings."))
|
||||
|
||||
/mob/living/silicon/pai/a_intent_change(input as text)
|
||||
. = ..()
|
||||
|
||||
switch(a_intent)
|
||||
if(I_HELP)
|
||||
hud_used.help_intent.icon_state = "intent_help-s"
|
||||
hud_used.disarm_intent.icon_state = "intent_disarm-n"
|
||||
hud_used.grab_intent.icon_state = "intent_grab-n"
|
||||
hud_used.hurt_intent.icon_state = "intent_harm-n"
|
||||
|
||||
if(I_DISARM)
|
||||
hud_used.help_intent.icon_state = "intent_help-n"
|
||||
hud_used.disarm_intent.icon_state = "intent_disarm-s"
|
||||
hud_used.grab_intent.icon_state = "intent_grab-n"
|
||||
hud_used.hurt_intent.icon_state = "intent_harm-n"
|
||||
|
||||
if(I_GRAB)
|
||||
hud_used.help_intent.icon_state = "intent_help-n"
|
||||
hud_used.disarm_intent.icon_state = "intent_disarm-n"
|
||||
hud_used.grab_intent.icon_state = "intent_grab-s"
|
||||
hud_used.hurt_intent.icon_state = "intent_harm-n"
|
||||
|
||||
if(I_HURT)
|
||||
hud_used.help_intent.icon_state = "intent_help-n"
|
||||
hud_used.disarm_intent.icon_state = "intent_disarm-n"
|
||||
hud_used.grab_intent.icon_state = "intent_grab-n"
|
||||
hud_used.hurt_intent.icon_state = "intent_harm-s"
|
||||
|
||||
/mob/living/silicon/pai/verb/toggle_gender_identity_vr()
|
||||
set name = "Set Gender Identity"
|
||||
set desc = "Sets the pronouns when examined and performing an emote."
|
||||
set category = "IC.Settings"
|
||||
var/new_gender_identity = tgui_input_list(src, "Please select a gender Identity:", "Set Gender Identity", list(FEMALE, MALE, NEUTER, PLURAL, HERM))
|
||||
if(!new_gender_identity)
|
||||
return 0
|
||||
gender = new_gender_identity
|
||||
return 1
|
||||
|
||||
/mob/living/silicon/pai/verb/pai_hide()
|
||||
set name = "Hide"
|
||||
set desc = "Allows to hide beneath tables or certain items. Toggled on or off."
|
||||
set category = "Abilities.pAI"
|
||||
|
||||
hide()
|
||||
if(status_flags & HIDING)
|
||||
hide_glow = TRUE
|
||||
else
|
||||
hide_glow = FALSE
|
||||
update_icon()
|
||||
|
||||
/mob/living/silicon/pai/verb/screen_message(message as text|null)
|
||||
set category = "Abilities.pAI Commands"
|
||||
set name = "Screen Message"
|
||||
set desc = "Allows you to display a message on your screen. This will show up in the chat of anyone who is holding your card."
|
||||
|
||||
if (src.client)
|
||||
if(client.prefs.muted & MUTE_IC)
|
||||
to_chat(src, span_warning("You cannot speak in IC (muted)."))
|
||||
return
|
||||
if(loc != card)
|
||||
to_chat(src, span_warning("Your message won't be visible while unfolded!"))
|
||||
if (!message)
|
||||
message = tgui_input_text(src, "Enter text you would like to show on your screen.","Screen Message", encode = FALSE)
|
||||
message = sanitize_or_reflect(message,src)
|
||||
if (!message)
|
||||
return
|
||||
message = capitalize(message)
|
||||
if (stat == DEAD)
|
||||
return
|
||||
card.screen_msg = message
|
||||
var/logmsg = "(CARD SCREEN)[message]"
|
||||
log_talk(logmsg, LOG_SAY)
|
||||
to_chat(src, span_filter_say(span_cult("You print a message to your screen, \"[message]\"")))
|
||||
if(isliving(card.loc))
|
||||
var/mob/living/L = card.loc
|
||||
if(L.client)
|
||||
to_chat(L, span_filter_say(span_cult("[src.name]'s screen prints, \"[message]\"")))
|
||||
else return
|
||||
else if(isbelly(card.loc))
|
||||
var/obj/belly/b = card.loc
|
||||
if(b.owner.client)
|
||||
to_chat(b.owner, span_filter_say(span_cult("[src.name]'s screen prints, \"[message]\"")))
|
||||
else return
|
||||
else if(istype(card.loc, /obj/item/pda))
|
||||
var/obj/item/pda/p = card.loc
|
||||
if(isliving(p.loc))
|
||||
var/mob/living/L = p.loc
|
||||
if(L.client)
|
||||
to_chat(L, span_filter_say(span_cult("[src.name]'s screen prints, \"[message]\"")))
|
||||
else return
|
||||
else if(isbelly(p.loc))
|
||||
var/obj/belly/b = card.loc
|
||||
if(b.owner.client)
|
||||
to_chat(b.owner, span_filter_say(span_cult("[src.name]'s screen prints, \"[message]\"")))
|
||||
else return
|
||||
else return
|
||||
else return
|
||||
to_chat(src, span_notice("Your message was relayed."))
|
||||
for (var/mob/G in GLOB.player_list)
|
||||
if (isnewplayer(G))
|
||||
continue
|
||||
else if(isobserver(G) && G.client?.prefs?.read_preference(/datum/preference/toggle/ghost_ears))
|
||||
if((client?.prefs?.read_preference(/datum/preference/toggle/whisubtle_vis) || check_rights_for(G.client, R_HOLDER)) && \
|
||||
G.client?.prefs?.read_preference(/datum/preference/toggle/ghost_see_whisubtle))
|
||||
to_chat(G, span_filter_say(span_cult("[src.name]'s screen prints, \"[message]\"")))
|
||||
|
||||
/mob/living/silicon/pai/proc/touch_window(soft_name) //This lets us touch TGUI procs and windows that may be nested behind other TGUI procs and windows
|
||||
if(stat != CONSCIOUS) //so we can access our software without having to open up the software interface TGUI window
|
||||
to_chat(src, span_warning("You can't do that right now."))
|
||||
return
|
||||
for(var/thing in software)
|
||||
var/datum/pai_software/S = software[thing]
|
||||
if(istype(S, /datum/pai_software) && S.name == soft_name)
|
||||
if(S.toggle)
|
||||
S.toggle(src)
|
||||
to_chat(src, span_notice("You toggled [S.name]."))
|
||||
refresh_software_status()
|
||||
else
|
||||
S.tgui_interact(src)
|
||||
refresh_software_status()
|
||||
return
|
||||
for(var/thing in GLOB.pai_software_by_key)
|
||||
var/datum/pai_software/our_soft = GLOB.pai_software_by_key[thing]
|
||||
if(our_soft.name == soft_name)
|
||||
if(!(ram >= our_soft.ram_cost))
|
||||
to_chat(src, span_warning("Insufficient RAM for download. (Cost [our_soft.ram_cost] : [ram] Remaining)"))
|
||||
return
|
||||
if(tgui_alert(src, "Do you want to download [our_soft.name]? It costs [our_soft.ram_cost], and you have [ram] remaining.", "Download [our_soft.name]", list("Yes", "No")) == "Yes")
|
||||
if(!(ram >= our_soft.ram_cost))
|
||||
return
|
||||
if(software[our_soft.id])
|
||||
return
|
||||
ram -= our_soft.ram_cost
|
||||
software[our_soft.id] = our_soft
|
||||
to_chat(src, span_notice("You downloaded [our_soft.name]. ([ram] RAM remaining.)"))
|
||||
refresh_software_status()
|
||||
|
||||
/mob/living/silicon/pai/proc/refresh_software_status() //This manages the pAI software status buttons icon states based on if you have them and if they are enabled
|
||||
for(var/thing in software) //this only gets called when you click one of the relevent buttons, rather than all the time!
|
||||
var/datum/pai_software/soft = software[thing]
|
||||
if(istype(soft,/datum/pai_software/med_records))
|
||||
soft_mr = TRUE
|
||||
if(istype(soft,/datum/pai_software/sec_records))
|
||||
soft_sr = TRUE
|
||||
if(istype(soft,/datum/pai_software/door_jack))
|
||||
soft_dj = TRUE
|
||||
if(istype(soft,/datum/pai_software/atmosphere_sensor))
|
||||
soft_as = TRUE
|
||||
if(istype(soft,/datum/pai_software/pai_hud))
|
||||
soft_ar = TRUE
|
||||
if(istype(soft,/datum/pai_software/translator))
|
||||
soft_ut = TRUE
|
||||
if(istype(soft,/datum/pai_software/signaller))
|
||||
soft_si = TRUE
|
||||
if(istype(soft,/datum/pai_software/deathalarm))
|
||||
soft_da = TRUE
|
||||
for(var/atom/movable/screen/pai/button in hud_used.other)
|
||||
if(button.name == "medical records")
|
||||
if(soft_mr)
|
||||
button.icon_state = "[button.base_state]"
|
||||
else
|
||||
button.icon_state = "[button.base_state]_o"
|
||||
if(button.name == "security records")
|
||||
if(soft_sr)
|
||||
button.icon_state = "[button.base_state]"
|
||||
else
|
||||
button.icon_state = "[button.base_state]_o"
|
||||
if(button.name == "door jack")
|
||||
if(soft_dj)
|
||||
button.icon_state = "[button.base_state]"
|
||||
else
|
||||
button.icon_state = "[button.base_state]_o"
|
||||
if(button.name == "atmosphere sensor")
|
||||
if(soft_as)
|
||||
button.icon_state = "[button.base_state]"
|
||||
else
|
||||
button.icon_state = "[button.base_state]_o"
|
||||
if(button.name == "remote signaler")
|
||||
if(soft_si)
|
||||
button.icon_state = "[button.base_state]"
|
||||
else
|
||||
button.icon_state = "[button.base_state]_o"
|
||||
if(button.name == "universal translator")
|
||||
if(soft_ut && translator_on)
|
||||
button.icon_state = "[button.base_state]"
|
||||
else
|
||||
button.icon_state = "[button.base_state]_o"
|
||||
if(button.name == "ar hud")
|
||||
if(soft_ar && paiHUD)
|
||||
button.icon_state = "[button.base_state]"
|
||||
else
|
||||
button.icon_state = "[button.base_state]_o"
|
||||
if(button.name == "death alarm")
|
||||
if(soft_da && paiDA)
|
||||
button.icon_state = "[button.base_state]"
|
||||
else
|
||||
button.icon_state = "[button.base_state]_o"
|
||||
|
||||
//Procs for using the various UI buttons for your softwares
|
||||
/mob/living/silicon/pai/proc/directives()
|
||||
touch_window("Directives")
|
||||
|
||||
/mob/living/silicon/pai/proc/crew_manifest()
|
||||
touch_window("Crew Manifest")
|
||||
|
||||
/mob/living/silicon/pai/proc/med_records()
|
||||
touch_window("Medical Records")
|
||||
|
||||
/mob/living/silicon/pai/proc/sec_records()
|
||||
touch_window("Security Records")
|
||||
|
||||
/mob/living/silicon/pai/proc/remote_signal()
|
||||
touch_window("Remote Signaler")
|
||||
|
||||
/mob/living/silicon/pai/proc/atmos_sensor()
|
||||
touch_window("Atmosphere Sensor")
|
||||
|
||||
/mob/living/silicon/pai/proc/translator()
|
||||
touch_window("Universal Translator")
|
||||
|
||||
/mob/living/silicon/pai/proc/door_jack()
|
||||
touch_window("Door Jack")
|
||||
|
||||
/mob/living/silicon/pai/proc/ar_hud()
|
||||
touch_window("AR HUD")
|
||||
|
||||
/mob/living/silicon/pai/proc/death_alarm()
|
||||
touch_window("Death Alarm")
|
||||
|
||||
/mob/living/silicon/pai/proc/get_character_icon()
|
||||
if(!client || !client.prefs) return FALSE
|
||||
var/mob/living/carbon/human/dummy/dummy = new ()
|
||||
//This doesn't include custom_items because that's ... hard.
|
||||
client.prefs.dress_preview_mob(dummy)
|
||||
sleep(1 SECOND) //Strange bug in preview code? Without this, certain things won't show up. Yay race conditions?
|
||||
dummy.regenerate_icons()
|
||||
|
||||
var/icon/new_holo = getCompoundIcon(dummy)
|
||||
|
||||
dummy.tail_layering = TRUE
|
||||
dummy.set_dir(NORTH)
|
||||
var/icon/new_holo_north = getCompoundIcon(dummy)
|
||||
|
||||
qdel(holo_icon)
|
||||
qdel(holo_icon_north)
|
||||
qdel(dummy)
|
||||
holo_icon = new_holo
|
||||
holo_icon_north = new_holo_north
|
||||
return TRUE
|
||||
|
||||
/mob/living/silicon/pai/set_dir(var/new_dir)
|
||||
. = ..()
|
||||
if(. && (chassis == "13"))
|
||||
switch(dir)
|
||||
if(SOUTH)
|
||||
icon = holo_icon
|
||||
else
|
||||
icon = holo_icon_north
|
||||
|
||||
/mob/living/silicon/pai/adjustBruteLoss(amount, include_robo)
|
||||
. = ..()
|
||||
if(amount > 0 && health <= 90) //Something's probably attacking us!
|
||||
if(prob(amount)) //The more damage it is doing, the more likely it is to damage something important!
|
||||
card.damage_random_component()
|
||||
|
||||
/mob/living/silicon/pai/adjustFireLoss(amount, include_robo)
|
||||
. = ..()
|
||||
if(amount > 0 && health <= 90)
|
||||
if(prob(amount))
|
||||
card.damage_random_component()
|
||||
@@ -1,65 +0,0 @@
|
||||
/*
|
||||
name
|
||||
key
|
||||
description
|
||||
role
|
||||
comments
|
||||
ready = 0
|
||||
*/
|
||||
|
||||
/datum/paiCandidate/proc/savefile_path(mob/user)
|
||||
return "data/player_saves/[copytext(user.ckey, 1, 2)]/[user.ckey]/pai.sav"
|
||||
|
||||
/datum/paiCandidate/proc/savefile_save(mob/user)
|
||||
if(IsGuestKey(user.key))
|
||||
return 0
|
||||
|
||||
var/savefile/F = new /savefile(src.savefile_path(user))
|
||||
|
||||
|
||||
F["name"] << src.name
|
||||
F["description"] << src.description
|
||||
F["role"] << src.role
|
||||
F["comments"] << src.comments
|
||||
|
||||
F["version"] << 1
|
||||
|
||||
return 1
|
||||
|
||||
// loads the savefile corresponding to the mob's ckey
|
||||
// if silent=true, report incompatible savefiles
|
||||
// returns 1 if loaded (or file was incompatible)
|
||||
// returns 0 if savefile did not exist
|
||||
|
||||
/datum/paiCandidate/proc/savefile_load(mob/user, var/silent = 1)
|
||||
if (IsGuestKey(user.key))
|
||||
return 0
|
||||
|
||||
var/path = savefile_path(user)
|
||||
|
||||
if (!fexists(path))
|
||||
return 0
|
||||
|
||||
var/savefile/F = new /savefile(path)
|
||||
|
||||
if(!F) return //Not everyone has a pai savefile.
|
||||
|
||||
var/version = null
|
||||
F["version"] >> version
|
||||
|
||||
if (isnull(version) || version != 1)
|
||||
fdel(path)
|
||||
if (!silent)
|
||||
tgui_alert_async(user, "Your savefile was incompatible with this version and was deleted.")
|
||||
return 0
|
||||
|
||||
F["name"] >> src.name
|
||||
F["description"] >> src.description
|
||||
F["role"] >> src.role
|
||||
F["comments"] >> src.comments
|
||||
F["eyecolor"] >> src.eye_color
|
||||
F["chassis"] >> src.chassis
|
||||
F["emotion"] >> src.ouremotion
|
||||
F["gender"] >> src.gender
|
||||
|
||||
return 1
|
||||
@@ -1,398 +0,0 @@
|
||||
//This file was auto-corrected by findeclaration.exe on 25.5.2012 20:42:33
|
||||
|
||||
// Recruiting observers to play as pAIs
|
||||
|
||||
var/datum/paiController/paiController // Global handler for pAI candidates
|
||||
|
||||
/datum/paiCandidate
|
||||
var/name
|
||||
var/key
|
||||
var/description
|
||||
var/role
|
||||
var/comments
|
||||
var/ready = 0
|
||||
var/chassis
|
||||
var/ouremotion
|
||||
var/eye_color
|
||||
var/gender
|
||||
|
||||
/hook/startup/proc/paiControllerSetup()
|
||||
paiController = new /datum/paiController()
|
||||
return 1
|
||||
|
||||
|
||||
/datum/paiController
|
||||
var/inquirer = null
|
||||
var/list/pai_candidates = list()
|
||||
var/list/asked = list()
|
||||
|
||||
var/askDelay = 10 * 60 * 1 // One minute [ms * sec * min]
|
||||
|
||||
/datum/paiController/Topic(href, href_list[])
|
||||
if(href_list["download"])
|
||||
var/datum/paiCandidate/candidate = locate(href_list["candidate"])
|
||||
var/obj/item/paicard/card = locate(href_list["device"])
|
||||
if(card.pai)
|
||||
return
|
||||
if(istype(card,/obj/item/paicard) && istype(candidate,/datum/paiCandidate))
|
||||
var/mob/living/silicon/pai/pai = new(card)
|
||||
pai.key = candidate.key
|
||||
GLOB.paikeys |= pai.ckey
|
||||
card.setPersonality(pai)
|
||||
if(!candidate.name)
|
||||
pai.SetName(pick(GLOB.ninja_names))
|
||||
else
|
||||
pai.SetName(candidate.name)
|
||||
if(candidate.description)
|
||||
pai.flavor_text = candidate.description
|
||||
if(candidate.eye_color)
|
||||
pai.eye_color = candidate.eye_color
|
||||
card.screen_color = pai.eye_color
|
||||
if(candidate.chassis)
|
||||
pai.chassis = candidate.chassis
|
||||
if(candidate.ouremotion)
|
||||
card.setEmotion(candidate.ouremotion)
|
||||
if(candidate.gender)
|
||||
pai.gender = candidate.gender
|
||||
pai.update_icon()
|
||||
pai.real_name = pai.name
|
||||
card.looking_for_personality = 0
|
||||
|
||||
if(pai.mind) update_antag_icons(pai.mind)
|
||||
|
||||
pai_candidates -= candidate
|
||||
usr << browse(null, "window=findPai")
|
||||
|
||||
if(href_list["new"])
|
||||
var/datum/paiCandidate/candidate = locate(href_list["candidate"])
|
||||
var/option = href_list["option"]
|
||||
var/t = ""
|
||||
|
||||
switch(option)
|
||||
if("name")
|
||||
t = sanitizeSafe(tgui_input_text(usr, "Enter a name for your pAI", "pAI Name", candidate.name, MAX_NAME_LEN, encode = FALSE), MAX_NAME_LEN)
|
||||
if(t)
|
||||
candidate.name = t
|
||||
if("desc")
|
||||
t = tgui_input_text(usr, "Enter a description for your pAI", "pAI Description", candidate.description, multiline = TRUE, prevent_enter = TRUE)
|
||||
if(t)
|
||||
candidate.description = sanitize(t, encode = FALSE)
|
||||
if("role")
|
||||
t = tgui_input_text(usr, "Enter a role for your pAI", "pAI Role", candidate.role)
|
||||
if(t)
|
||||
candidate.role = sanitize(t, encode = FALSE)
|
||||
if("ooc")
|
||||
t = tgui_input_text(usr, "Enter any OOC comments", "pAI OOC Comments", candidate.comments, multiline = TRUE, prevent_enter = TRUE)
|
||||
if(t)
|
||||
candidate.comments = sanitize(t, encode = FALSE)
|
||||
if("save")
|
||||
candidate.savefile_save(usr)
|
||||
if("load")
|
||||
candidate.savefile_load(usr)
|
||||
//In case people have saved unsanitized stuff.
|
||||
if(candidate.name)
|
||||
candidate.name = sanitizeSafe(candidate.name, MAX_NAME_LEN)
|
||||
if(candidate.description)
|
||||
candidate.description = sanitize(candidate.description)
|
||||
if(candidate.role)
|
||||
candidate.role = sanitize(candidate.role)
|
||||
if(candidate.comments)
|
||||
candidate.comments = sanitize(candidate.comments)
|
||||
|
||||
if("submit")
|
||||
if(candidate)
|
||||
candidate.ready = 1
|
||||
for(var/obj/item/paicard/p in GLOB.all_pai_cards)
|
||||
if(p.looking_for_personality == 1)
|
||||
p.alertUpdate()
|
||||
usr << browse(null, "window=paiRecruit")
|
||||
return
|
||||
|
||||
recruitWindow(usr, href_list["allow_submit"] != "0")
|
||||
|
||||
/datum/paiController/proc/recruitWindow(var/mob/M as mob, allowSubmit = 1)
|
||||
var/datum/paiCandidate/candidate
|
||||
for(var/datum/paiCandidate/c in pai_candidates)
|
||||
if(!istype(c) || !istype(M))
|
||||
break
|
||||
if(c.key == M.key)
|
||||
candidate = c
|
||||
if(!candidate)
|
||||
candidate = new /datum/paiCandidate()
|
||||
candidate.key = M.key
|
||||
pai_candidates.Add(candidate)
|
||||
|
||||
var/dat = ""
|
||||
dat += {"
|
||||
<style type="text/css">
|
||||
body {
|
||||
margin-top:5px;
|
||||
font-family:Verdana;
|
||||
color:white;
|
||||
font-size:13px;
|
||||
background-image:url('uiBackground.png');
|
||||
background-repeat:repeat-x;
|
||||
background-color:#272727;
|
||||
background-position:center top;
|
||||
}
|
||||
table {
|
||||
border-collapse:collapse;
|
||||
font-size:13px;
|
||||
}
|
||||
th, td {
|
||||
border: 1px solid #333333;
|
||||
}
|
||||
p.top {
|
||||
background-color: none;
|
||||
color: white;
|
||||
}
|
||||
tr.d0 td {
|
||||
background-color: #c0c0c0;
|
||||
color: black;
|
||||
border:0px;
|
||||
border: 1px solid #333333;
|
||||
}
|
||||
tr.d0 th {
|
||||
background-color: none;
|
||||
color: #4477E0;
|
||||
text-align:right;
|
||||
vertical-align:top;
|
||||
width:120px;
|
||||
border:0px;
|
||||
}
|
||||
tr.d1 td {
|
||||
background-color: #555555;
|
||||
color: white;
|
||||
}
|
||||
td.button {
|
||||
border: 1px solid #161616;
|
||||
background-color: #40628a;
|
||||
}
|
||||
td.desc {
|
||||
font-weight:bold;
|
||||
}
|
||||
a {
|
||||
color:#4477E0;
|
||||
}
|
||||
a.button {
|
||||
color:white;
|
||||
text-decoration: none;
|
||||
}
|
||||
</style>
|
||||
"}
|
||||
|
||||
dat += {"
|
||||
<body>
|
||||
<b><font size="3px">pAI Personality Configuration</font></b>
|
||||
<p class="top">Please configure your pAI personality's options. Remember, what you enter here could determine whether or not the user requesting a personality chooses you!</p>
|
||||
|
||||
<table>
|
||||
<tr class="d0">
|
||||
<th rowspan="2"><a href='byond://?src=\ref[src];option=name;new=1;allow_submit=[allowSubmit];candidate=\ref[candidate]'>Name</a>:</th>
|
||||
<td class="desc">[candidate.name] </td>
|
||||
</tr>
|
||||
<tr class="d1">
|
||||
<td>What you plan to call yourself. Suggestions: Any character name you would choose for a station character OR an AI.</td>
|
||||
</tr>
|
||||
<tr class="d0">
|
||||
<th rowspan="2"><a href='byond://?src=\ref[src];option=desc;new=1;allow_submit=[allowSubmit];candidate=\ref[candidate]'>Description</a>:</th>
|
||||
<td class="desc">[candidate.description] </td>
|
||||
</tr>
|
||||
<tr class="d1">
|
||||
<td>What sort of pAI you typically play; your mannerisms, your quirks, etc. This can be as sparse or as detailed as you like.</td>
|
||||
</tr>
|
||||
<tr class="d0">
|
||||
<th rowspan="2"><a href='byond://?src=\ref[src];option=role;new=1;allow_submit=[allowSubmit];candidate=\ref[candidate]'>Preferred Role</a>:</th>
|
||||
<td class="desc">[candidate.role] </td>
|
||||
</tr>
|
||||
<tr class="d1">
|
||||
<td>Do you like to partner with sneaky social ninjas? Like to help security hunt down thugs? Enjoy watching an engineer's back while he saves the station yet again? This doesn't have to be limited to just station jobs. Pretty much any general descriptor for what you'd like to be doing works here.</td>
|
||||
</tr>
|
||||
<tr class="d0">
|
||||
<th rowspan="2"><a href='byond://?src=\ref[src];option=ooc;new=1;allow_submit=[allowSubmit];candidate=\ref[candidate]'>OOC Comments</a>:</th>
|
||||
<td class="desc">[candidate.comments] </td>
|
||||
</tr>
|
||||
<tr class="d1">
|
||||
<td>Anything you'd like to address specifically to the player reading this in an OOC manner. \"I prefer more serious RP.\", \"I'm still learning the interface!\", etc. Feel free to leave this blank if you want.</td>
|
||||
</tr>
|
||||
</table>
|
||||
<br>
|
||||
<table>
|
||||
<tr>
|
||||
<td class="button">
|
||||
<a href='byond://?src=\ref[src];option=save;new=1;allow_submit=[allowSubmit];candidate=\ref[candidate]' class="button">Save Personality</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="button">
|
||||
<a href='byond://?src=\ref[src];option=load;new=1;allow_submit=[allowSubmit];candidate=\ref[candidate]' class="button"><b><font size="3px">Load Personality</font></b></a>
|
||||
</td>
|
||||
</tr>
|
||||
</table><br>
|
||||
"}
|
||||
if(allowSubmit)
|
||||
dat += {"
|
||||
<table>
|
||||
<td class="button"><a href='byond://?src=\ref[src];option=submit;new=1;candidate=\ref[candidate]' class="button"><b><font size="4px">Submit Personality</font></b></a></td>
|
||||
</table><br>
|
||||
"}
|
||||
dat += {"
|
||||
</body>
|
||||
"}
|
||||
|
||||
M << browse("<html>[dat]</html>", "window=paiRecruit;size=580x580;")
|
||||
|
||||
/datum/paiController/proc/findPAI(var/obj/item/paicard/p, var/mob/user)
|
||||
requestRecruits(user)
|
||||
var/list/available = list()
|
||||
for(var/datum/paiCandidate/c in paiController.pai_candidates)
|
||||
if(c.ready)
|
||||
var/found = 0
|
||||
for(var/mob/observer/dead/o in GLOB.player_list)
|
||||
if(o.key == c.key && o.MayRespawn())
|
||||
found = 1
|
||||
if(found)
|
||||
available.Add(c)
|
||||
var/dat = ""
|
||||
|
||||
dat += {"
|
||||
<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
body {
|
||||
margin-top:5px;
|
||||
font-family:Verdana;
|
||||
color:white;
|
||||
font-size:13px;
|
||||
background-image:url('uiBackground.png');
|
||||
background-repeat:repeat-x;
|
||||
background-color:#272727;
|
||||
background-position:center top;
|
||||
}
|
||||
table {
|
||||
font-size:13px;
|
||||
}
|
||||
table.desc {
|
||||
border-collapse:collapse;
|
||||
font-size:13px;
|
||||
border: 1px solid #161616;
|
||||
width:100%;
|
||||
}
|
||||
table.download {
|
||||
border-collapse:collapse;
|
||||
font-size:13px;
|
||||
border: 1px solid #161616;
|
||||
width:100%;
|
||||
}
|
||||
tr.d0 td, tr.d0 th {
|
||||
background-color: #506070;
|
||||
color: white;
|
||||
}
|
||||
tr.d1 td, tr.d1 th {
|
||||
background-color: #708090;
|
||||
color: white;
|
||||
}
|
||||
tr.d2 td {
|
||||
background-color: #00FF00;
|
||||
color: white;
|
||||
text-align:center;
|
||||
}
|
||||
td.button {
|
||||
border: 1px solid #161616;
|
||||
background-color: #40628a;
|
||||
text-align: center;
|
||||
}
|
||||
td.download {
|
||||
border: 1px solid #161616;
|
||||
background-color: #40628a;
|
||||
text-align: center;
|
||||
}
|
||||
th {
|
||||
text-align:left;
|
||||
width:125px;
|
||||
vertical-align:top;
|
||||
}
|
||||
a.button {
|
||||
color:white;
|
||||
text-decoration: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<b><font size='3px'>pAI Availability List</font></b><br><br>
|
||||
"}
|
||||
dat += "<p>Displaying available AI personalities from central database... If there are no entries, or if a suitable entry is not listed, check again later as more personalities may be added.</p>"
|
||||
|
||||
for(var/datum/paiCandidate/c in available)
|
||||
dat += {"
|
||||
<table class="desc">
|
||||
<tr class="d0">
|
||||
<th>Name:</th>
|
||||
<td>[c.name]</td>
|
||||
</tr>
|
||||
<tr class="d1">
|
||||
<th>Description:</th>
|
||||
<td>[c.description]</td>
|
||||
</tr>
|
||||
<tr class="d0">
|
||||
<th>Preferred Role:</th>
|
||||
<td>[c.role]</td>
|
||||
</tr>
|
||||
<tr class="d1">
|
||||
<th>OOC Comments:</th>
|
||||
<td>[c.comments]</td>
|
||||
</tr>
|
||||
</table>
|
||||
<table class="download">
|
||||
<td class="download"><a href='byond://?src=\ref[src];download=1;candidate=\ref[c];device=\ref[p]' class="button"><b>Download [c.name]</b></a>
|
||||
</td>
|
||||
</table>
|
||||
<br>
|
||||
"}
|
||||
|
||||
dat += {"
|
||||
</body>
|
||||
</html>
|
||||
"}
|
||||
|
||||
user << browse(dat, "window=findPai")
|
||||
|
||||
|
||||
/datum/paiController/proc/requestRecruits(var/mob/user)
|
||||
inquirer = user
|
||||
for(var/mob/observer/dead/O in GLOB.player_list)
|
||||
if(!O.MayRespawn())
|
||||
continue
|
||||
if(jobban_isbanned(O, "pAI"))
|
||||
continue
|
||||
if(asked.Find(O.key))
|
||||
if(world.time < asked[O.key] + askDelay)
|
||||
continue
|
||||
else
|
||||
asked.Remove(O.key)
|
||||
if(O.client)
|
||||
if(O.client.prefs.be_special & BE_PAI)
|
||||
question(O.client)
|
||||
|
||||
/datum/paiController/proc/question(var/client/C)
|
||||
spawn(0)
|
||||
if(!C) return
|
||||
asked.Add(C.key)
|
||||
asked[C.key] = world.time
|
||||
|
||||
var/mob/ourmob = C.mob
|
||||
if(ourmob)
|
||||
var/time_till_respawn = ourmob.time_till_respawn()
|
||||
if(time_till_respawn == -1 || time_till_respawn)
|
||||
return
|
||||
for(var/ourkey in GLOB.paikeys)
|
||||
if(ourkey == ourmob.ckey)
|
||||
return
|
||||
|
||||
var/response = tgui_alert(C, "[inquirer] is requesting a pAI personality. Would you like to play as a personal AI?", "pAI Request", list("Yes", "No", "Never for this round"))
|
||||
if(!C) return //handle logouts that happen whilst the alert is waiting for a response.
|
||||
if(response == "Yes")
|
||||
recruitWindow(C.mob)
|
||||
else if (response == "Never for this round")
|
||||
C.prefs.be_special ^= BE_PAI
|
||||
@@ -1,39 +1,3 @@
|
||||
var/list/pai_emotions = list(
|
||||
"Neutral" = 1,
|
||||
"What" = 2,
|
||||
"Happy" = 3,
|
||||
"Cat" = 4,
|
||||
"Extremely Happy" = 5,
|
||||
"Face" = 6,
|
||||
"Laugh" = 7,
|
||||
"Sad" = 8,
|
||||
"Angry" = 9,
|
||||
"Silly" = 10,
|
||||
"Nose" = 11,
|
||||
"Smirk" = 12,
|
||||
"Exclamation Points" = 13,
|
||||
"Question Mark" = 14,
|
||||
"Blank" = 15,
|
||||
"Off" = 16
|
||||
)
|
||||
|
||||
|
||||
GLOBAL_LIST_EMPTY(pai_software_by_key)
|
||||
GLOBAL_LIST_EMPTY(default_pai_software)
|
||||
/hook/startup/proc/populate_pai_software_list()
|
||||
var/r = 1 // I would use ., but it'd sacrifice runtime detection
|
||||
for(var/type in subtypesof(/datum/pai_software))
|
||||
var/datum/pai_software/P = new type()
|
||||
if(GLOB.pai_software_by_key[P.id])
|
||||
var/datum/pai_software/O = GLOB.pai_software_by_key[P.id]
|
||||
to_chat(world, span_warning("pAI software module [P.name] has the same key as [O.name]!"))
|
||||
r = 0
|
||||
continue
|
||||
GLOB.pai_software_by_key[P.id] = P
|
||||
if(P.default)
|
||||
GLOB.default_pai_software[P.id] = P
|
||||
return r
|
||||
|
||||
/mob/living/silicon/pai/Initialize(mapload)
|
||||
. = ..()
|
||||
software = GLOB.default_pai_software.Copy()
|
||||
@@ -79,11 +43,12 @@ GLOBAL_LIST_EMPTY(default_pai_software)
|
||||
|
||||
// Emotions
|
||||
var/list/emotions = list()
|
||||
for(var/name in pai_emotions)
|
||||
var/list/emote = list()
|
||||
emote["name"] = name
|
||||
emote["id"] = pai_emotions[name]
|
||||
emotions.Add(list(emote))
|
||||
for(var/name in GLOB.pai_emotions)
|
||||
var/list/emote = list(
|
||||
"displayText" = name,
|
||||
"value" = GLOB.pai_emotions[name]
|
||||
)
|
||||
UNTYPED_LIST_ADD(emotions, emote)
|
||||
|
||||
data["emotions"] = emotions
|
||||
data["current_emotion"] = card.current_emotion
|
||||
@@ -114,6 +79,6 @@ GLOBAL_LIST_EMPTY(default_pai_software)
|
||||
|
||||
if("image")
|
||||
var/img = text2num(params["image"])
|
||||
if(1 <= img && img <= (pai_emotions.len))
|
||||
if(1 <= img && img <= (GLOB.pai_emotions.len))
|
||||
card.setEmotion(img)
|
||||
return TRUE
|
||||
|
||||
@@ -0,0 +1,209 @@
|
||||
/datum/pai_sprite
|
||||
var/name = DEVELOPER_WARNING_NAME
|
||||
var/hidden = FALSE
|
||||
|
||||
var/sprite_icon = null
|
||||
var/sprite_icon_state = null
|
||||
var/has_eye_sprites = TRUE
|
||||
var/flying = FALSE
|
||||
var/belly_states = 1
|
||||
|
||||
var/can_rest = TRUE // We really shouldn't be permitting this... eventually. Remove when all sprite can rest.
|
||||
var/resting_belly = TRUE
|
||||
|
||||
var/holo_projector = FALSE // Special AI hologram-like handling
|
||||
var/holo_eyes_icon_state = null
|
||||
|
||||
var/pixel_x = 0
|
||||
var/pixel_y = 0
|
||||
var/icon_x = 32
|
||||
var/icon_y = 32
|
||||
var/vis_height = 32
|
||||
|
||||
// PAI sprites
|
||||
/datum/pai_sprite/drone
|
||||
name = PAI_DEFAULT_CHASSIS
|
||||
sprite_icon = 'icons/mob/pai.dmi'
|
||||
sprite_icon_state = "pai-repairbot"
|
||||
|
||||
/datum/pai_sprite/cat
|
||||
name = "Cat"
|
||||
sprite_icon = 'icons/mob/pai.dmi'
|
||||
sprite_icon_state = "pai-cat"
|
||||
|
||||
/datum/pai_sprite/mouse
|
||||
name = "Mouse"
|
||||
sprite_icon = 'icons/mob/pai.dmi'
|
||||
sprite_icon_state = "pai-mouse"
|
||||
|
||||
/datum/pai_sprite/monkey
|
||||
name = "Monkey"
|
||||
sprite_icon = 'icons/mob/pai.dmi'
|
||||
sprite_icon_state = "pai-monkey"
|
||||
|
||||
/datum/pai_sprite/borgi
|
||||
name = "Borgi"
|
||||
sprite_icon = 'icons/mob/pai.dmi'
|
||||
sprite_icon_state = "pai-borgi"
|
||||
has_eye_sprites = FALSE
|
||||
|
||||
/datum/pai_sprite/fox
|
||||
name = "Fox"
|
||||
sprite_icon = 'icons/mob/pai.dmi'
|
||||
sprite_icon_state = "pai-fox"
|
||||
has_eye_sprites = FALSE
|
||||
|
||||
/datum/pai_sprite/parrot
|
||||
name = "Parrot"
|
||||
sprite_icon = 'icons/mob/pai.dmi'
|
||||
sprite_icon_state = "pai-parrot"
|
||||
has_eye_sprites = FALSE
|
||||
flying = TRUE
|
||||
|
||||
/datum/pai_sprite/rabbit
|
||||
name = "Rabbit"
|
||||
sprite_icon = 'icons/mob/pai.dmi'
|
||||
sprite_icon_state = "pai-rabbit"
|
||||
has_eye_sprites = FALSE
|
||||
|
||||
/datum/pai_sprite/bear
|
||||
name = "Bear"
|
||||
sprite_icon = 'icons/mob/pai.dmi'
|
||||
sprite_icon_state = "pai-bear"
|
||||
|
||||
/datum/pai_sprite/fennec
|
||||
name = "Fennec"
|
||||
sprite_icon = 'icons/mob/pai.dmi'
|
||||
sprite_icon_state = "pai-fen"
|
||||
belly_states = 0
|
||||
|
||||
/datum/pai_sprite/type_zero
|
||||
name = "Type Zero"
|
||||
sprite_icon = 'icons/mob/pai.dmi'
|
||||
sprite_icon_state = "pai-typezero"
|
||||
|
||||
/datum/pai_sprite/raccoon
|
||||
name = "Raccoon"
|
||||
sprite_icon = 'icons/mob/pai.dmi'
|
||||
sprite_icon_state = "pai-raccoon"
|
||||
|
||||
/datum/pai_sprite/raptor
|
||||
name = "Raptor"
|
||||
sprite_icon = 'icons/mob/pai.dmi'
|
||||
sprite_icon_state = "pai-raptor"
|
||||
|
||||
/datum/pai_sprite/corgi
|
||||
name = "Corgi"
|
||||
sprite_icon = 'icons/mob/pai.dmi'
|
||||
sprite_icon_state = "pai-corgi"
|
||||
has_eye_sprites = FALSE
|
||||
|
||||
/datum/pai_sprite/bat
|
||||
name = "Bat"
|
||||
sprite_icon = 'icons/mob/pai.dmi'
|
||||
sprite_icon_state = "pai-bat"
|
||||
flying = TRUE
|
||||
|
||||
/datum/pai_sprite/butterfly
|
||||
name = "Butterfly"
|
||||
sprite_icon = 'icons/mob/pai.dmi'
|
||||
sprite_icon_state = "pai-butterfly"
|
||||
flying = TRUE
|
||||
belly_states = 0
|
||||
|
||||
/datum/pai_sprite/hawk
|
||||
name = "Hawk"
|
||||
sprite_icon = 'icons/mob/pai.dmi'
|
||||
sprite_icon_state = "pai-hawk"
|
||||
has_eye_sprites = FALSE
|
||||
flying = TRUE
|
||||
belly_states = 0
|
||||
|
||||
/datum/pai_sprite/duffel
|
||||
name = "Duffel"
|
||||
sprite_icon = 'icons/mob/pai.dmi'
|
||||
sprite_icon_state = "pai-duffel"
|
||||
has_eye_sprites = FALSE
|
||||
|
||||
/datum/pai_sprite/cyber_elf
|
||||
name = "Cyber Elf"
|
||||
sprite_icon = 'icons/mob/pai.dmi'
|
||||
sprite_icon_state = "cyberelf"
|
||||
flying = TRUE
|
||||
resting_belly = FALSE
|
||||
|
||||
/datum/pai_sprite/catslug
|
||||
name = "Catslug"
|
||||
sprite_icon = 'icons/mob/pai.dmi'
|
||||
sprite_icon_state = "catslug"
|
||||
resting_belly = FALSE
|
||||
|
||||
/datum/pai_sprite/car
|
||||
name = "Car"
|
||||
sprite_icon = 'icons/mob/pai.dmi'
|
||||
sprite_icon_state = "car"
|
||||
|
||||
/datum/pai_sprite/type_one
|
||||
name = "Type One"
|
||||
sprite_icon = 'icons/mob/pai.dmi'
|
||||
sprite_icon_state = "typeone"
|
||||
|
||||
/datum/pai_sprite/type_thirteen
|
||||
name = "Type Thirteen"
|
||||
sprite_icon = 'icons/mob/pai.dmi'
|
||||
holo_projector = TRUE
|
||||
sprite_icon_state = "type13"
|
||||
holo_eyes_icon_state = "type13-eyes"
|
||||
|
||||
// Large pai sprites
|
||||
/datum/pai_sprite/large
|
||||
pixel_x = -16
|
||||
icon_x = 64
|
||||
icon_y = 64
|
||||
vis_height = 64
|
||||
|
||||
/datum/pai_sprite/large/dire_wolf
|
||||
name = "Dire wolf"
|
||||
sprite_icon = 'icons/mob/pai64x64.dmi'
|
||||
sprite_icon_state = "pai-diredog"
|
||||
|
||||
/datum/pai_sprite/large/horse_lune
|
||||
name = "Horse (Lune)"
|
||||
sprite_icon = 'icons/mob/pai64x64.dmi'
|
||||
sprite_icon_state = "pai-horse_lune"
|
||||
can_rest = FALSE // FIX ME!
|
||||
resting_belly = FALSE
|
||||
|
||||
/datum/pai_sprite/large/horse_soleil
|
||||
name = "Horse (Soleil)"
|
||||
sprite_icon = 'icons/mob/pai64x64.dmi'
|
||||
sprite_icon_state = "pai-horse_soleil"
|
||||
can_rest = FALSE // FIX ME!
|
||||
resting_belly = FALSE
|
||||
|
||||
/datum/pai_sprite/large/dragon
|
||||
name = "Dragon"
|
||||
sprite_icon = 'icons/mob/pai64x64.dmi'
|
||||
sprite_icon_state = "pai-pdragon"
|
||||
can_rest = FALSE // FIX ME!
|
||||
resting_belly = FALSE
|
||||
|
||||
/datum/pai_sprite/large/rat
|
||||
name = "Rat"
|
||||
sprite_icon = 'icons/mob/pai64x64.dmi'
|
||||
sprite_icon_state = "rat"
|
||||
|
||||
/datum/pai_sprite/large/panther
|
||||
name = "Panther"
|
||||
sprite_icon = 'icons/mob/pai64x64.dmi'
|
||||
sprite_icon_state = "panther"
|
||||
|
||||
/datum/pai_sprite/large/teppi
|
||||
name = "Teppi"
|
||||
sprite_icon = 'icons/mob/pai64x64.dmi'
|
||||
sprite_icon_state = "teppi"
|
||||
|
||||
/datum/pai_sprite/large/protogen_dog
|
||||
name = "Protogen Dog"
|
||||
sprite_icon = 'icons/mob/pai64x64.dmi'
|
||||
sprite_icon_state = "pai-protodog"
|
||||
@@ -84,7 +84,7 @@
|
||||
model_type = "wide"
|
||||
if(istype(S, /datum/robot_sprite/dogborg/tall))
|
||||
model_type = "tall"
|
||||
available_sprites += list(list("sprite" = S.name, "belly" = S.has_vore_belly_sprites, "type" = model_type))
|
||||
UNTYPED_LIST_ADD(available_sprites, list("sprite" = S.name, "belly" = S.has_vore_belly_sprites, "type" = model_type))
|
||||
|
||||
data["possible_sprites"] = available_sprites
|
||||
data["sprite_datum"] = sprite_datum
|
||||
|
||||
@@ -141,6 +141,11 @@
|
||||
QDEL_NULL(mind)
|
||||
qdel(src)
|
||||
|
||||
// pAI notify if we have be pAI invite on
|
||||
SSpai.clear_pai_block_delay(REF(observer)) // Reset invite cooldown if we cancelled all invites for the round
|
||||
if(SSpai.invite_valid(observer))
|
||||
observer.pai_card_ping()
|
||||
|
||||
return TRUE
|
||||
if("shownews")
|
||||
handle_server_news()
|
||||
|
||||
@@ -363,8 +363,8 @@
|
||||
|
||||
//Eye color/icon
|
||||
var/should_have_eyes = owner.should_have_organ(O_EYES)
|
||||
var/has_eye_color = owner.species.appearance_flags & HAS_EYE_COLOR
|
||||
if((should_have_eyes || has_eye_color) && eye_icon)
|
||||
var/has_eye_sprites = owner.species.appearance_flags & HAS_EYE_COLOR
|
||||
if((should_have_eyes || has_eye_sprites) && eye_icon)
|
||||
var/obj/item/organ/internal/eyes/eyes = owner.internal_organs_by_name[O_EYES]
|
||||
var/icon/eyes_icon = new/icon(eye_icon_location, eye_icon)
|
||||
//Do we have a special eye icon with its own coloration? Remove
|
||||
@@ -373,7 +373,7 @@
|
||||
if(should_have_eyes)
|
||||
//And we have them
|
||||
if(eyes)
|
||||
if(has_eye_color)
|
||||
if(has_eye_sprites)
|
||||
eyes_icon.Blend(rgb(eyes.eye_colour[1], eyes.eye_colour[2], eyes.eye_colour[3]), ICON_ADD)
|
||||
//They're gone!
|
||||
else
|
||||
|
||||
@@ -49,6 +49,9 @@
|
||||
return
|
||||
tgui_interact(user)
|
||||
|
||||
/obj/machinery/computer/power_monitor/allow_pai_interaction(proximity_flag)
|
||||
return proximity_flag
|
||||
|
||||
// Uses dark magic to operate the NanoUI of this computer.
|
||||
/obj/machinery/computer/power_monitor/tgui_interact(mob/user, var/datum/tgui/ui = null)
|
||||
power_monitor.tgui_interact(user, ui)
|
||||
|
||||
@@ -342,6 +342,9 @@
|
||||
to_chat(user, "You adjust the speed of the conveyor switch")
|
||||
return
|
||||
|
||||
/obj/machinery/conveyor_switch/allow_pai_interaction(proximity_flag)
|
||||
return proximity_flag
|
||||
|
||||
/obj/machinery/conveyor_switch/oneway
|
||||
oneway = 1
|
||||
|
||||
|
||||
@@ -170,6 +170,9 @@
|
||||
user.visible_message("[user] places \the [I] into the [src].", "You place \the [I] into the [src].","Ca-Clunk")
|
||||
update_icon()
|
||||
|
||||
/obj/machinery/disposal/allow_pai_interaction(proximity_flag)
|
||||
return proximity_flag
|
||||
|
||||
// Transform into next machine type
|
||||
/obj/machinery/disposal/proc/alter_bin_type(mob/user)
|
||||
if(contents.len > 0)
|
||||
|
||||
@@ -124,10 +124,10 @@
|
||||
|
||||
// Emotions
|
||||
var/list/emotions = list()
|
||||
for(var/name in pai_emotions)
|
||||
for(var/name in GLOB.pai_emotions)
|
||||
var/list/emote = list()
|
||||
emote["name"] = name
|
||||
emote["id"] = pai_emotions[name]
|
||||
emote["id"] = GLOB.pai_emotions[name]
|
||||
emotions.Add(list(emote))
|
||||
|
||||
data["emotions"] = emotions
|
||||
|
||||
@@ -72,7 +72,11 @@ GLOBAL_DATUM_INIT(tgui_default_state, /datum/tgui_state/default, new)
|
||||
. = min(., shared_living_tgui_distance(src_object)) //simple animals can only use things they're near.
|
||||
|
||||
/mob/living/silicon/pai/default_can_use_tgui_topic(src_object)
|
||||
// pAIs can only use themselves and the owner's radio.
|
||||
// Allows few objects...
|
||||
var/obj/check_obj = src_object
|
||||
if(!stat && istype(check_obj) && check_obj.allow_pai_interaction(get_dist(check_obj, src) <= 1))
|
||||
return STATUS_INTERACTIVE
|
||||
// ...otherwise pAIs can only use themselves and the owner's radio.
|
||||
if((src_object == src || src_object == radio || src_object == communicator) && !stat)
|
||||
return STATUS_INTERACTIVE
|
||||
else
|
||||
|
||||
@@ -97,6 +97,9 @@
|
||||
return
|
||||
lift.queue_move_to(floor)
|
||||
|
||||
/obj/structure/lift/button/allow_pai_interaction(proximity_flag)
|
||||
return proximity_flag
|
||||
|
||||
/obj/structure/lift/button/proc/light_up()
|
||||
light_up = TRUE
|
||||
update_icon()
|
||||
@@ -140,6 +143,9 @@
|
||||
/obj/structure/lift/panel/attack_ghost(var/mob/user)
|
||||
return interact(user)
|
||||
|
||||
/obj/structure/lift/panel/allow_pai_interaction(proximity_flag)
|
||||
return proximity_flag
|
||||
|
||||
/obj/structure/lift/panel/interact(var/mob/user)
|
||||
if(!..())
|
||||
return
|
||||
|
||||
@@ -106,7 +106,9 @@
|
||||
#include "mapping.dm"
|
||||
#include "material_tests.dm"
|
||||
// #include "nuke_cinematic.dm" // TODO: This is probably fixed later on
|
||||
#include "pai_tests.dm"
|
||||
#include "poster_tests.dm"
|
||||
#include "special_roles.dm"
|
||||
// #include "preferences.dm" // This unit test is missing some other stuff
|
||||
#include "reagent_tests.dm"
|
||||
#include "recipe_tests.dm"
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
/datum/unit_test/pai_software_shall_have_unique_id
|
||||
|
||||
/datum/unit_test/pai_software_shall_have_unique_id/Run()
|
||||
for(var/datum/pai_software/P as anything in subtypesof(/datum/pai_software))
|
||||
// Compare against all other pai softwares
|
||||
for(var/datum/pai_software/O as anything in subtypesof(/datum/pai_software))
|
||||
if(P == O) // Don't test against ourselves
|
||||
continue
|
||||
if(initial(P.id) == initial(O.id))
|
||||
TEST_FAIL("pAI software module [P] has the same id as [O]!")
|
||||
|
||||
/datum/unit_test/pai_sprites_shall_be_valid
|
||||
|
||||
/datum/unit_test/pai_sprites_shall_be_valid/Run()
|
||||
var/failed = FALSE
|
||||
var/list/used_names = list()
|
||||
for(var/sprite_key in SSpai.get_chassis_list())
|
||||
var/datum/pai_sprite/sprite = SSpai.chassis_data(sprite_key)
|
||||
if(sprite.sprite_icon == null)
|
||||
continue
|
||||
|
||||
if(sprite.name in used_names)
|
||||
TEST_NOTICE(src, "[sprite.type] - Pai sprite name is not unique: \"[sprite.name]\"")
|
||||
failed = TRUE
|
||||
used_names.Add(sprite.name)
|
||||
|
||||
if(!sprite.name || sprite.name == DEVELOPER_WARNING_NAME)
|
||||
TEST_NOTICE(src, "[sprite.type] - Pai sprite had invalid name: \"[sprite.name]\"")
|
||||
failed = TRUE
|
||||
|
||||
|
||||
if(!sprite.holo_projector)
|
||||
// check if we have valid icons
|
||||
if(!sprite.sprite_icon_state)
|
||||
TEST_NOTICE(src, "[sprite.type] - Pai sprite had no iconstate assigned")
|
||||
failed = TRUE
|
||||
|
||||
if(!icon_exists(sprite.sprite_icon, sprite.sprite_icon_state))
|
||||
TEST_NOTICE(src, "[sprite.type] - Pai sprite state \"[sprite.sprite_icon_state]\" did not exist in the dmi set \"[sprite.sprite_icon]\"")
|
||||
failed = TRUE
|
||||
|
||||
if(sprite.can_rest)
|
||||
if(!icon_exists(sprite.sprite_icon, "[sprite.sprite_icon_state]_rest"))
|
||||
TEST_NOTICE(src, "[sprite.type] - Pai sprite state \"[sprite.sprite_icon_state]_rest\" did not exist in the dmi set \"[sprite.sprite_icon]\"")
|
||||
failed = TRUE
|
||||
|
||||
// Check for vore stuff
|
||||
if(sprite.belly_states > 0)
|
||||
for(var/i = 1 to sprite.belly_states)
|
||||
var/fullness_extension = ""
|
||||
if(i > 1)
|
||||
fullness_extension = "_[i]"
|
||||
// standing bellies
|
||||
if(!icon_exists(sprite.sprite_icon, "[sprite.sprite_icon_state]_full[fullness_extension]"))
|
||||
TEST_NOTICE(src, "[sprite.type] - Pai sprite state \"[sprite.sprite_icon_state]_full[fullness_extension]\" did not exist in the dmi set \"[sprite.sprite_icon]\"")
|
||||
failed = TRUE
|
||||
if(sprite.can_rest && sprite.resting_belly)
|
||||
// resting bellies
|
||||
if(!icon_exists(sprite.sprite_icon, "[sprite.sprite_icon_state]_rest_full[fullness_extension]"))
|
||||
TEST_NOTICE(src, "[sprite.type] - Pai sprite state \"[sprite.sprite_icon_state]_rest_full[fullness_extension]\" did not exist in the dmi set \"[sprite.sprite_icon]\"")
|
||||
failed = TRUE
|
||||
|
||||
// Does it have colorable eyes?
|
||||
if(sprite.has_eye_sprites)
|
||||
if(!icon_exists(sprite.sprite_icon, "[sprite.sprite_icon_state]-eyes"))
|
||||
TEST_NOTICE(src, "[sprite.type] - Pai sprite state \"[sprite.sprite_icon_state]-eyes\" did not exist in the dmi set \"[sprite.sprite_icon]\"")
|
||||
failed = TRUE
|
||||
if(sprite.can_rest)
|
||||
if(!icon_exists(sprite.sprite_icon, "[sprite.sprite_icon_state]_rest-eyes"))
|
||||
TEST_NOTICE(src, "[sprite.type] - Pai sprite state \"[sprite.sprite_icon_state]_rest-eyes\" did not exist in the dmi set \"[sprite.sprite_icon]\"")
|
||||
failed = TRUE
|
||||
|
||||
// Death
|
||||
if(!icon_exists(sprite.sprite_icon, "[sprite.sprite_icon_state]_dead"))
|
||||
TEST_NOTICE(src, "[sprite.type] - Pai sprite state \"[sprite.sprite_icon_state]_dead\" did not exist in the dmi set \"[sprite.sprite_icon]\"")
|
||||
failed = TRUE
|
||||
|
||||
else // Holoprojector uses ai holo instead of a sprite
|
||||
// Holoprojectil eyes need to exist at all sizes for the state provided
|
||||
if(sprite.holo_eyes_icon_state)
|
||||
if(!icon_exists('icons/mob/pai.dmi', sprite.holo_eyes_icon_state))
|
||||
TEST_NOTICE(src, "[sprite.type] - Pai holoprojection eyes state \"[sprite.holo_eyes_icon_state]\" did not exist in the dmi set \"['icons/mob/pai.dmi']\"")
|
||||
failed = TRUE
|
||||
if(!icon_exists('icons/mob/pai32x64.dmi', sprite.holo_eyes_icon_state))
|
||||
TEST_NOTICE(src, "[sprite.type] - Pai holoprojection eyes state \"[sprite.holo_eyes_icon_state]\" did not exist in the dmi set \"['icons/mob/pai32x64.dmi']\"")
|
||||
failed = TRUE
|
||||
if(!icon_exists('icons/mob/pai64x32.dmi', sprite.holo_eyes_icon_state))
|
||||
TEST_NOTICE(src, "[sprite.type] - Pai holoprojection eyes state \"[sprite.holo_eyes_icon_state]\" did not exist in the dmi set \"['icons/mob/pai64x32.dmi']\"")
|
||||
failed = TRUE
|
||||
if(!icon_exists('icons/mob/pai64x64.dmi', sprite.holo_eyes_icon_state))
|
||||
TEST_NOTICE(src, "[sprite.type] - Pai holoprojection eyes state \"[sprite.holo_eyes_icon_state]\" did not exist in the dmi set \"['icons/mob/pai64x64.dmi']\"")
|
||||
failed = TRUE
|
||||
|
||||
if(failed)
|
||||
TEST_FAIL("PAI sprites had invalid entries.")
|
||||
@@ -0,0 +1,13 @@
|
||||
/datum/unit_test/special_role_flags_syncronized
|
||||
|
||||
/datum/unit_test/special_role_flags_syncronized/Run()
|
||||
if(GLOB.special_roles.len != GLOB.be_special_flags.len)
|
||||
TEST_FAIL("GLOB.special_roles and GLOB.be_special_flags lengths do not match. [GLOB.special_roles] vs [GLOB.be_special_flags].")
|
||||
|
||||
// These get out of order way too often.
|
||||
var/index = 1
|
||||
for(var/key in GLOB.special_roles)
|
||||
var/check_key = GLOB.be_special_flags[index]
|
||||
if(lowertext(check_key) != lowertext(key))
|
||||
TEST_FAIL("Special role flag misalignment index: [index], be_special_flags: [check_key], special_roles: [key]. Keys must also match when caste to lowercase.")
|
||||
index++
|
||||
|
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 188 KiB |
|
Before Width: | Height: | Size: 602 B After Width: | Height: | Size: 602 B |
|
After Width: | Height: | Size: 507 B |
|
After Width: | Height: | Size: 101 KiB |
|
Before Width: | Height: | Size: 181 KiB |
|
Before Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 104 KiB |
@@ -58,3 +58,7 @@ export function chatMessage(payload: string): void {
|
||||
|
||||
pushMessage(message);
|
||||
}
|
||||
|
||||
export function exportChatlog() {
|
||||
chatRenderer.saveToDisk();
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { loadStyleSheet } from 'common/assets';
|
||||
import { EventBus } from 'tgui-core/eventbus';
|
||||
import { playMusic, stopMusic } from '../audio/handlers';
|
||||
import { chatMessage } from '../chat/handlers';
|
||||
import { chatMessage, exportChatlog } from '../chat/handlers';
|
||||
import { pingReply, pingSoft } from '../ping/handlers';
|
||||
import {
|
||||
handleTelemetryData,
|
||||
@@ -25,6 +25,7 @@ const listeners = {
|
||||
'telemetry/request': telemetryRequest,
|
||||
testTelemetryCommand,
|
||||
update: handleTelemetryData,
|
||||
saveToDiskCommand: exportChatlog,
|
||||
} as const;
|
||||
|
||||
export const bus = new EventBus(listeners);
|
||||
|
||||
@@ -0,0 +1,178 @@
|
||||
import { useState } from 'react';
|
||||
import { useBackend } from 'tgui/backend';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
LabeledList,
|
||||
Section,
|
||||
Stack,
|
||||
TextArea,
|
||||
} from 'tgui-core/components';
|
||||
import { healthToColor } from './functions';
|
||||
import { PaiIcon } from './PaiIcon';
|
||||
import type { ActivePAIData } from './types';
|
||||
|
||||
export const PAIActiveCompanion = (props: { activeData: ActivePAIData }) => {
|
||||
const { act } = useBackend();
|
||||
const [newDirective, setNewDirective] = useState('');
|
||||
|
||||
const { activeData } = props;
|
||||
const {
|
||||
name,
|
||||
color,
|
||||
health,
|
||||
chassis,
|
||||
law_zero,
|
||||
law_extra,
|
||||
master_name,
|
||||
master_dna,
|
||||
screen_msg,
|
||||
radio_data,
|
||||
sprite_datum_class,
|
||||
sprite_datum_size,
|
||||
} = activeData;
|
||||
|
||||
return (
|
||||
<Section fill>
|
||||
<Stack vertical fill>
|
||||
<Stack.Item grow>
|
||||
<Section
|
||||
title={name}
|
||||
fill
|
||||
scrollable
|
||||
buttons={
|
||||
<Stack>
|
||||
<Stack.Item>
|
||||
<Button.Confirm
|
||||
color="red"
|
||||
icon="ban"
|
||||
disabled={!master_name || !master_dna}
|
||||
onClick={() => act('cleardna')}
|
||||
>
|
||||
Clear DNA
|
||||
</Button.Confirm>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Button
|
||||
icon="lightbulb-o"
|
||||
disabled={!!master_name && !!master_dna}
|
||||
onClick={() => act('setdna')}
|
||||
>
|
||||
Imprint DNA
|
||||
</Button>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
}
|
||||
>
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Owner">
|
||||
{!!master_name && !!master_dna ? (
|
||||
<>
|
||||
<Box style={{ textDecoration: 'underline' }} inline>
|
||||
{master_name}
|
||||
</Box>
|
||||
<Box inline>: {master_dna}</Box>
|
||||
</>
|
||||
) : (
|
||||
'unbound'
|
||||
)}
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Divider />
|
||||
<LabeledList.Item verticalAlign="top" label="Chassis">
|
||||
<PaiIcon
|
||||
label
|
||||
color={color}
|
||||
icon={sprite_datum_class}
|
||||
size={sprite_datum_size}
|
||||
chassis={chassis}
|
||||
/>
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Divider />
|
||||
<LabeledList.Item label="Integrity">
|
||||
<Box color={healthToColor(health)}>{health}</Box>
|
||||
</LabeledList.Item>
|
||||
{!!screen_msg && (
|
||||
<LabeledList.Item label="Screen">{screen_msg}</LabeledList.Item>
|
||||
)}
|
||||
<LabeledList.Divider />
|
||||
<LabeledList.Item label="Prime Directive">
|
||||
{law_zero}
|
||||
</LabeledList.Item>
|
||||
{!!law_extra && (
|
||||
<LabeledList.Item label="Additional Directives">
|
||||
{law_extra}
|
||||
</LabeledList.Item>
|
||||
)}
|
||||
</LabeledList>
|
||||
</Section>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Section title="Radio Uplink">
|
||||
{radio_data ? (
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Receiving">
|
||||
<RadioBox status={!!radio_data.radio_recieve} />
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Transmitting">
|
||||
<RadioBox status={!!radio_data.radio_transmit} />
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
) : (
|
||||
'Radio firmware not loaded. Please install a pAI personality to load firmware.'
|
||||
)}
|
||||
</Section>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Section
|
||||
title="Manage Additional Directives:"
|
||||
buttons={
|
||||
<Stack>
|
||||
<Stack.Item>
|
||||
<Button.Confirm color="red" disabled={!law_extra} icon="ban">
|
||||
Clear
|
||||
</Button.Confirm>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Button
|
||||
icon="lightbulb-o"
|
||||
tooltip="Enter any additional directives you would like your pAI personality to follow. Note that these directives will not override the personality's allegiance to its imprinted master. Conflicting directives will be ignored."
|
||||
disabled={!newDirective}
|
||||
onClick={() => {
|
||||
act('setlaws', { directive: newDirective });
|
||||
setNewDirective('');
|
||||
}}
|
||||
>
|
||||
Apply
|
||||
</Button>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
}
|
||||
>
|
||||
<LabeledList>
|
||||
<LabeledList.Item verticalAlign="top" label="New Directive">
|
||||
<TextArea
|
||||
fluid
|
||||
height="80px"
|
||||
maxLength={4096}
|
||||
placeholder="Set new directive..."
|
||||
value={newDirective}
|
||||
onChange={setNewDirective}
|
||||
/>
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
</Section>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
const RadioBox = (props: { status: boolean }) => {
|
||||
const { status } = props;
|
||||
|
||||
return status ? (
|
||||
<Box color="red">Disabled</Box>
|
||||
) : (
|
||||
<Box color="green">Enabled</Box>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,110 @@
|
||||
import { useBackend } from 'tgui/backend';
|
||||
import { Button, Icon, Section, Stack, Tooltip } from 'tgui-core/components';
|
||||
import { capitalize } from 'tgui-core/string';
|
||||
import { gender2icon } from '../PreferencesMenu/bay_prefs/general/functions';
|
||||
import { genderToColor } from './constants';
|
||||
import { PaiIcon } from './PaiIcon';
|
||||
import type { Data, DetailedInvitePAIData } from './types';
|
||||
|
||||
export const PAIDataEntry = (props: {
|
||||
paiEntry: DetailedInvitePAIData;
|
||||
detailed?: boolean;
|
||||
}) => {
|
||||
const { act } = useBackend<Data>();
|
||||
|
||||
const { paiEntry, detailed } = props;
|
||||
|
||||
const {
|
||||
ref,
|
||||
name,
|
||||
gender,
|
||||
role,
|
||||
ad,
|
||||
eyecolor,
|
||||
chassis,
|
||||
sprite_datum_class,
|
||||
sprite_datum_size,
|
||||
description,
|
||||
comments,
|
||||
} = paiEntry;
|
||||
|
||||
return (
|
||||
<Stack vertical fill>
|
||||
<Stack.Item>
|
||||
<Stack>
|
||||
<Stack.Item bold color="label">
|
||||
{name}
|
||||
</Stack.Item>
|
||||
<Stack.Item grow bold color={genderToColor[gender]}>
|
||||
<Tooltip content={gender}>
|
||||
<Icon name={gender2icon(capitalize(gender))} />
|
||||
</Tooltip>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
{detailed ? (
|
||||
<Button
|
||||
icon="arrow-left-long"
|
||||
onClick={() => act('clear_preview')}
|
||||
>
|
||||
Return
|
||||
</Button>
|
||||
) : (
|
||||
<Button icon="bars" onClick={() => act('preview', { ref: ref })}>
|
||||
Details
|
||||
</Button>
|
||||
)}
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Button
|
||||
icon="lightbulb-o"
|
||||
onClick={() => act('select_pai', { ref: ref })}
|
||||
>
|
||||
Invite
|
||||
</Button>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Stack.Item>
|
||||
<Stack.Divider />
|
||||
<Stack.Item grow={2}>
|
||||
<Stack fill>
|
||||
<PaiIcon
|
||||
color={eyecolor}
|
||||
icon={sprite_datum_class}
|
||||
size={sprite_datum_size}
|
||||
chassis={chassis}
|
||||
/>
|
||||
<Stack.Divider />
|
||||
<Stack.Item grow>
|
||||
<Stack fill vertical>
|
||||
<Stack.Item grow>
|
||||
<Section scrollable={detailed} fill title="Role">
|
||||
{role}
|
||||
</Section>
|
||||
</Stack.Item>
|
||||
<Stack.Item grow>
|
||||
<Section scrollable={detailed} fill title="Ad">
|
||||
{ad}
|
||||
</Section>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Stack.Item>
|
||||
<Stack.Divider />
|
||||
{detailed && (
|
||||
<>
|
||||
<Stack.Item grow>
|
||||
<Section scrollable fill title="Description">
|
||||
{description}
|
||||
</Section>
|
||||
</Stack.Item>
|
||||
<Stack.Item grow>
|
||||
<Section scrollable fill title="OOC Notes">
|
||||
{comments}
|
||||
</Section>
|
||||
</Stack.Item>
|
||||
</>
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
import { useBackend } from 'tgui/backend';
|
||||
import { Section, Stack } from 'tgui-core/components';
|
||||
import { PAIDataEntry } from './PAIDataEntry';
|
||||
import type { Data, InvitePAIData } from './types';
|
||||
|
||||
export const PAIFindCompanion = (props: { availablePais: InvitePAIData[] }) => {
|
||||
const { data, act } = useBackend<Data>();
|
||||
|
||||
const { waiting_for_response, selected, selected_pai_data } = data;
|
||||
const { availablePais } = props;
|
||||
|
||||
return (
|
||||
<Section fill scrollable title="Find Companion">
|
||||
{!waiting_for_response ? (
|
||||
!selected_pai_data ? (
|
||||
<Stack vertical>
|
||||
{availablePais.map((paiEntry) => (
|
||||
<Stack.Item grow key={paiEntry.ref}>
|
||||
<PAIDataEntry paiEntry={paiEntry} />
|
||||
</Stack.Item>
|
||||
))}
|
||||
</Stack>
|
||||
) : (
|
||||
<PAIDataEntry detailed paiEntry={selected_pai_data} />
|
||||
)
|
||||
) : (
|
||||
'Awaiting Response...'
|
||||
)}
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,60 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { resolveAsset } from 'tgui/assets';
|
||||
import { Box, Stack } from 'tgui-core/components';
|
||||
import { classes } from 'tgui-core/react';
|
||||
import { fetchSpritePositions } from '../PaiChoose/functions';
|
||||
|
||||
export const PaiIcon = (props: {
|
||||
icon?: string;
|
||||
size?: string;
|
||||
color: string;
|
||||
chassis: string;
|
||||
label?: boolean;
|
||||
}) => {
|
||||
const { icon, size, color, chassis, label } = props;
|
||||
const [iconPositions, setIconPositions] = useState<Record<string, string>>(
|
||||
{},
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchPositions() {
|
||||
const positions = await fetchSpritePositions(
|
||||
resolveAsset('spritesheet_pai_icons.css'),
|
||||
);
|
||||
setIconPositions(positions);
|
||||
}
|
||||
fetchPositions();
|
||||
}, []);
|
||||
|
||||
if (!icon || !size) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack align="center">
|
||||
<Stack.Item width="60px" height="60px">
|
||||
<Box
|
||||
position="relative"
|
||||
style={{ transform: 'scale(0.5) translate(-60px, -60px)' }}
|
||||
>
|
||||
<Box className={classes([size, `${icon}S`])} style={{}} />
|
||||
{!!iconPositions[`${icon}SE`] && (
|
||||
<Box
|
||||
position="absolute"
|
||||
top="0"
|
||||
left="0"
|
||||
className={size}
|
||||
style={{
|
||||
WebkitMaskImage: `url(${resolveAsset('pai_icons_120x120.png')})`,
|
||||
WebkitMaskRepeat: 'no-repeat',
|
||||
WebkitMaskPosition: `${iconPositions[`${icon}SE`]}`,
|
||||
backgroundColor: `${color}`,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</Stack.Item>
|
||||
{label && <Stack.Item>{chassis}</Stack.Item>}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,7 @@
|
||||
export const genderToColor = {
|
||||
female: 'pink',
|
||||
male: 'blue',
|
||||
plural: 'purple',
|
||||
neuter: 'gray',
|
||||
herm: 'teal',
|
||||
} as const;
|
||||
@@ -0,0 +1,12 @@
|
||||
export function healthToColor(health: number): string {
|
||||
if (health > 60) {
|
||||
return 'green';
|
||||
}
|
||||
if (health > 30) {
|
||||
return 'yellow';
|
||||
}
|
||||
if (health > 30) {
|
||||
return 'orange';
|
||||
}
|
||||
return 'red';
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import { useBackend } from 'tgui/backend';
|
||||
import { Window } from 'tgui/layouts';
|
||||
import { PAIActiveCompanion } from './PAIActiveCompanion';
|
||||
import { PAIFindCompanion } from './PAIFindCompanion';
|
||||
import type { Data } from './types';
|
||||
|
||||
export const PAICard = (props) => {
|
||||
const { data } = useBackend<Data>();
|
||||
|
||||
const { active_pai_data, available_pais } = data;
|
||||
|
||||
return (
|
||||
<Window width={450} height={600}>
|
||||
<Window.Content>
|
||||
{(!!active_pai_data && (
|
||||
<PAIActiveCompanion activeData={active_pai_data} />
|
||||
)) ||
|
||||
(!!available_pais && (
|
||||
<PAIFindCompanion availablePais={available_pais} />
|
||||
))}
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,44 @@
|
||||
import type { BooleanLike } from 'tgui-core/react';
|
||||
|
||||
export type Data = {
|
||||
active_pai_data: ActivePAIData | null;
|
||||
selected_pai_data: DetailedInvitePAIData | null;
|
||||
available_pais: InvitePAIData[] | null;
|
||||
waiting_for_response: BooleanLike;
|
||||
};
|
||||
|
||||
export type DetailedInvitePAIData = {
|
||||
description?: string;
|
||||
comments?: string;
|
||||
} & Required<InvitePAIData>;
|
||||
|
||||
export type InvitePAIData = {
|
||||
ref: string;
|
||||
name: string;
|
||||
gender: string;
|
||||
role: string;
|
||||
ad: string;
|
||||
eyecolor: string;
|
||||
chassis: string;
|
||||
emotion: string;
|
||||
sprite_datum_class: string;
|
||||
sprite_datum_size: string;
|
||||
};
|
||||
|
||||
export type ActivePAIData = {
|
||||
name: string;
|
||||
color: string;
|
||||
chassis: string;
|
||||
health: number;
|
||||
law_zero: string;
|
||||
law_extra: string | null;
|
||||
master_name: string | null;
|
||||
master_dna: string | null;
|
||||
screen_msg: string | null;
|
||||
radio_data: {
|
||||
radio_transmit: BooleanLike;
|
||||
radio_recieve: BooleanLike;
|
||||
} | null;
|
||||
sprite_datum_class: string;
|
||||
sprite_datum_size: string;
|
||||
};
|
||||
@@ -0,0 +1,44 @@
|
||||
import { useBackend } from 'tgui/backend';
|
||||
import { Button, Divider, Section, Stack } from 'tgui-core/components';
|
||||
import { PreferenceEditColor } from '../PreferencesMenu/elements/ColorInput';
|
||||
import { PaiPreview } from './PaiPreview';
|
||||
import type { Data } from './types';
|
||||
|
||||
export const IconSection = (props) => {
|
||||
const { act, data } = useBackend<Data>();
|
||||
const { sprite_datum_class, sprite_datum_size, selected_chassis, pai_color } =
|
||||
data;
|
||||
|
||||
return (
|
||||
<Section
|
||||
title="Sprite"
|
||||
fill
|
||||
buttons={
|
||||
<Button disabled={!selected_chassis} onClick={() => act('confirm')}>
|
||||
Apply
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<Stack vertical fill>
|
||||
<Stack.Item>
|
||||
<Stack>
|
||||
<Stack.Item color="label">Color:</Stack.Item>
|
||||
<Stack.Item>
|
||||
<PreferenceEditColor
|
||||
onClose={(value) => act('change_color', { color: value })}
|
||||
tooltip="Choose your pAI's default glow colour."
|
||||
back_color={pai_color}
|
||||
/>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Stack.Item>
|
||||
<Divider />
|
||||
<PaiPreview
|
||||
icon={sprite_datum_class}
|
||||
size={sprite_datum_size}
|
||||
color={pai_color}
|
||||
/>
|
||||
</Stack>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,135 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { resolveAsset } from 'tgui/assets';
|
||||
import { Box, Stack } from 'tgui-core/components';
|
||||
import { classes } from 'tgui-core/react';
|
||||
import { fetchSpritePositions } from './functions';
|
||||
|
||||
export const PaiPreview = (props: {
|
||||
icon?: string;
|
||||
size?: string;
|
||||
color: string;
|
||||
}) => {
|
||||
const { icon, size, color } = props;
|
||||
const [iconPositions, setIconPositions] = useState<Record<string, string>>(
|
||||
{},
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchPositions() {
|
||||
const positions = await fetchSpritePositions(
|
||||
resolveAsset('spritesheet_pai_icons.css'),
|
||||
);
|
||||
setIconPositions(positions);
|
||||
}
|
||||
fetchPositions();
|
||||
}, []);
|
||||
|
||||
if (!icon || !size) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Stack.Item>
|
||||
<Stack>
|
||||
<Stack.Item grow />
|
||||
<Stack.Item>
|
||||
<Box position="relative">
|
||||
<Box className={classes([size, `${icon}N`])} />
|
||||
{!!iconPositions[`${icon}NE`] && (
|
||||
<Box
|
||||
position="absolute"
|
||||
top="0"
|
||||
left="0"
|
||||
className={size}
|
||||
style={{
|
||||
WebkitMaskImage: `url(${resolveAsset('pai_icons_120x120.png')})`,
|
||||
WebkitMaskRepeat: 'no-repeat',
|
||||
WebkitMaskPosition: `${iconPositions[`${icon}NE`]}`,
|
||||
backgroundColor: `${color}`,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</Stack.Item>
|
||||
<Stack.Item grow />
|
||||
</Stack>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Stack>
|
||||
<Stack.Item grow />
|
||||
<Stack.Item>
|
||||
<Box position="relative">
|
||||
<Box className={classes([size, `${icon}S`])} />
|
||||
{!!iconPositions[`${icon}SE`] && (
|
||||
<Box
|
||||
position="absolute"
|
||||
top="0"
|
||||
left="0"
|
||||
className={size}
|
||||
style={{
|
||||
WebkitMaskImage: `url(${resolveAsset('pai_icons_120x120.png')})`,
|
||||
WebkitMaskRepeat: 'no-repeat',
|
||||
WebkitMaskPosition: `${iconPositions[`${icon}SE`]}`,
|
||||
backgroundColor: `${color}`,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</Stack.Item>
|
||||
<Stack.Item grow />
|
||||
</Stack>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Stack>
|
||||
<Stack.Item grow />
|
||||
<Stack.Item>
|
||||
<Box position="relative">
|
||||
<Box className={classes([size, `${icon}W`])} />
|
||||
{!!iconPositions[`${icon}WE`] && (
|
||||
<Box
|
||||
position="absolute"
|
||||
top="0"
|
||||
left="0"
|
||||
className={size}
|
||||
style={{
|
||||
WebkitMaskImage: `url(${resolveAsset('pai_icons_120x120.png')})`,
|
||||
WebkitMaskRepeat: 'no-repeat',
|
||||
WebkitMaskPosition: `${iconPositions[`${icon}WE`]}`,
|
||||
backgroundColor: `${color}`,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</Stack.Item>
|
||||
<Stack.Item grow />
|
||||
</Stack>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Stack>
|
||||
<Stack.Item grow />
|
||||
<Stack.Item>
|
||||
<Box position="relative">
|
||||
<Box className={classes([size, `${icon}E`])} />
|
||||
{!!iconPositions[`${icon}EE`] && (
|
||||
<Box
|
||||
position="absolute"
|
||||
top="0"
|
||||
left="0"
|
||||
className={size}
|
||||
style={{
|
||||
WebkitMaskImage: `url(${resolveAsset('pai_icons_120x120.png')})`,
|
||||
WebkitMaskRepeat: 'no-repeat',
|
||||
WebkitMaskPosition: `${iconPositions[`${icon}EE`]}`,
|
||||
backgroundColor: `${color}`,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</Stack.Item>
|
||||
<Stack.Item grow />
|
||||
</Stack>
|
||||
</Stack.Item>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,76 @@
|
||||
import { useState } from 'react';
|
||||
import { useBackend } from 'tgui/backend';
|
||||
import { Button, Input, Section, Stack } from 'tgui-core/components';
|
||||
import { SelectorElement } from '../RobotChoose/SelectorElement';
|
||||
import { paiSpriteSearcher } from './functions';
|
||||
import type { Data } from './types';
|
||||
|
||||
export const SpriteSection = (props) => {
|
||||
const { data } = useBackend<Data>();
|
||||
const { pai_chassises, pai_chassis, selected_chassis } = data;
|
||||
|
||||
const [searchText, setSearchText] = useState<string>('');
|
||||
const [includeDefault, setIncludeDefault] = useState<boolean>(false);
|
||||
const [includeBig, setIncludeBig] = useState<boolean>(false);
|
||||
|
||||
const filtered = paiSpriteSearcher(
|
||||
searchText,
|
||||
includeDefault,
|
||||
includeBig,
|
||||
pai_chassises,
|
||||
);
|
||||
return (
|
||||
<Section
|
||||
title="Sprites"
|
||||
fill
|
||||
buttons={
|
||||
<Stack g={0.2}>
|
||||
<Stack.Item>
|
||||
<Button.Checkbox
|
||||
checked={includeDefault}
|
||||
onClick={() => setIncludeDefault(!includeDefault)}
|
||||
>
|
||||
Def
|
||||
</Button.Checkbox>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Button.Checkbox
|
||||
checked={includeBig}
|
||||
onClick={() => setIncludeBig(!includeBig)}
|
||||
>
|
||||
Big
|
||||
</Button.Checkbox>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
}
|
||||
>
|
||||
<Stack vertical fill>
|
||||
<Stack.Item>
|
||||
<Input
|
||||
fluid
|
||||
value={searchText}
|
||||
placeholder="Search for sprites..."
|
||||
onChange={(value: string) => setSearchText(value)}
|
||||
/>
|
||||
</Stack.Item>
|
||||
<Stack.Divider />
|
||||
<Stack.Item grow>
|
||||
<Section fill scrollable>
|
||||
<Stack vertical fill>
|
||||
{!!filtered &&
|
||||
filtered.map((filter) => (
|
||||
<SelectorElement
|
||||
key={filter.sprite}
|
||||
option={filter.sprite}
|
||||
action="pick_icon"
|
||||
selected={selected_chassis ?? pai_chassis}
|
||||
belly={filter.belly}
|
||||
/>
|
||||
))}
|
||||
</Stack>
|
||||
</Section>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,65 @@
|
||||
import { flow } from 'tgui-core/fp';
|
||||
import { fetchRetry } from 'tgui-core/http';
|
||||
import { createSearch } from 'tgui-core/string';
|
||||
import type { spriteOption } from './types';
|
||||
|
||||
export function paiSpriteSearcher(
|
||||
searchText: string,
|
||||
includeDefault: boolean,
|
||||
includeBig: boolean,
|
||||
sprites?: spriteOption[],
|
||||
): spriteOption[] {
|
||||
const testSearch = createSearch(
|
||||
searchText,
|
||||
(sprite: spriteOption) => sprite.sprite,
|
||||
);
|
||||
if (!sprites) {
|
||||
return [];
|
||||
}
|
||||
const subtypes: string[] = [];
|
||||
if (includeDefault) {
|
||||
subtypes.push('def');
|
||||
}
|
||||
if (includeBig) {
|
||||
subtypes.push('big');
|
||||
}
|
||||
return flow([
|
||||
(sprites: spriteOption[]) => {
|
||||
if (!searchText) {
|
||||
return sprites;
|
||||
} else {
|
||||
return sprites.filter(testSearch);
|
||||
}
|
||||
},
|
||||
(sprites: spriteOption[]) => {
|
||||
if (!subtypes.length) {
|
||||
return sprites;
|
||||
} else {
|
||||
return sprites.filter((sprite) => subtypes.includes(sprite.type));
|
||||
}
|
||||
},
|
||||
])(sprites);
|
||||
}
|
||||
|
||||
export async function fetchSpritePositions(assetCssUrl: string) {
|
||||
const response = await fetchRetry(assetCssUrl);
|
||||
const cssText = await response.text();
|
||||
|
||||
const spritePositions: Record<string, string> = {};
|
||||
const regex =
|
||||
/.*(datumpaisprite[A-Za-z0-9]+)\s*\{\s*background-position:\s*([^;]+);/g;
|
||||
|
||||
let match = regex.exec(cssText);
|
||||
while (match !== null) {
|
||||
const spriteName = match[1];
|
||||
const position = match[2];
|
||||
if (!spriteName || !position) {
|
||||
match = regex.exec(cssText);
|
||||
continue;
|
||||
}
|
||||
spritePositions[spriteName] = position;
|
||||
match = regex.exec(cssText);
|
||||
}
|
||||
|
||||
return spritePositions;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { Window } from 'tgui/layouts';
|
||||
import { Stack } from 'tgui-core/components';
|
||||
import { IconSection } from './IconSection';
|
||||
import { SpriteSection } from './SpriteSection';
|
||||
|
||||
export const PaiChoose = () => {
|
||||
return (
|
||||
<Window width={600} height={670}>
|
||||
<Window.Content>
|
||||
<Stack fill>
|
||||
<Stack.Item basis="50%">
|
||||
<SpriteSection />
|
||||
</Stack.Item>
|
||||
<Stack.Item grow>
|
||||
<IconSection />
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,10 @@
|
||||
export type Data = {
|
||||
selected_chassis: string | null;
|
||||
pai_color: string;
|
||||
pai_chassises?: spriteOption[];
|
||||
pai_chassis?: string;
|
||||
sprite_datum_class?: string;
|
||||
sprite_datum_size?: string;
|
||||
};
|
||||
|
||||
export type spriteOption = { sprite: string; belly: boolean; type: string };
|
||||
@@ -34,7 +34,7 @@ export const CharacterPreferenceWindow = (props) => {
|
||||
|
||||
return (
|
||||
<Window
|
||||
width={1000}
|
||||
width={1065}
|
||||
height={800}
|
||||
buttons={
|
||||
<Button
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
} from 'tgui-core/components';
|
||||
|
||||
import {
|
||||
byondGenders,
|
||||
Gender,
|
||||
type GeneralData,
|
||||
type GeneralDataConstant,
|
||||
@@ -21,6 +22,7 @@ import { SpeciesBaseStats } from './stats/SpeciesSpaceStats';
|
||||
|
||||
export const GenderButton = (props: {
|
||||
gender: Gender;
|
||||
possibleGenders: Gender[];
|
||||
usePronouns?: boolean;
|
||||
setGender: (gender: Gender) => void;
|
||||
}) => {
|
||||
@@ -30,7 +32,7 @@ export const GenderButton = (props: {
|
||||
placement="right-end"
|
||||
content={
|
||||
<Stack backgroundColor="black" ml={0.5} pl={0.5} pr={0.5}>
|
||||
{Object.values(Gender).map((x) => (
|
||||
{props.possibleGenders.map((x) => (
|
||||
<Button
|
||||
selected={props.gender === x}
|
||||
key={x}
|
||||
@@ -121,12 +123,14 @@ export const SubtabInfo = (props: {
|
||||
<LabeledList.Item label="Biological Sex">
|
||||
<GenderButton
|
||||
gender={data.biological_sex}
|
||||
possibleGenders={byondGenders}
|
||||
setGender={(gender: Gender) => act('bio_gender', { gender })}
|
||||
/>
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Pronouns">
|
||||
<GenderButton
|
||||
gender={data.identifying_gender}
|
||||
possibleGenders={Object.values(Gender)}
|
||||
setGender={(gender: Gender) => act('id_gender', { gender })}
|
||||
usePronouns
|
||||
/>
|
||||
|
||||
@@ -0,0 +1,248 @@
|
||||
import { useBackend } from 'tgui/backend';
|
||||
import { PaiPreview } from 'tgui/interfaces/PaiChoose/PaiPreview';
|
||||
import { VorePanelTooltip } from 'tgui/interfaces/VorePanel/VorePanelElements/VorePanelTooltip';
|
||||
import {
|
||||
Box,
|
||||
Dropdown,
|
||||
Input,
|
||||
LabeledList,
|
||||
Stack,
|
||||
TextArea,
|
||||
} from 'tgui-core/components';
|
||||
import { PreferenceEditColor } from '../../elements/ColorInput';
|
||||
import type {
|
||||
GeneralData,
|
||||
GeneralDataConstant,
|
||||
GeneralDataStatic,
|
||||
} from './data';
|
||||
|
||||
export const SubtabPai = (props: {
|
||||
data: GeneralData;
|
||||
staticData: GeneralDataStatic;
|
||||
serverData: GeneralDataConstant;
|
||||
}) => {
|
||||
const { act } = useBackend();
|
||||
const { data, staticData, serverData } = props;
|
||||
const {
|
||||
pai_name,
|
||||
pai_desc,
|
||||
pai_role,
|
||||
pai_ad,
|
||||
pai_comments,
|
||||
pai_eyecolor,
|
||||
pai_chassis,
|
||||
pai_chassises,
|
||||
pai_emotion,
|
||||
pai_emotions,
|
||||
pai_sprite_datum_class,
|
||||
pai_sprite_datum_size,
|
||||
} = data;
|
||||
|
||||
return (
|
||||
<Stack vertical fill textAlign="center">
|
||||
<Stack.Item mt={2}>
|
||||
<Stack>
|
||||
<Stack.Item grow>
|
||||
<Stack fill vertical align="center">
|
||||
<Stack.Item>
|
||||
<Box bold>pAI Settings</Box>
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Name">
|
||||
<Stack>
|
||||
<Stack.Item>
|
||||
<Input
|
||||
fluid
|
||||
value={pai_name}
|
||||
maxLength={52}
|
||||
onBlur={(value) =>
|
||||
act('pai_option', {
|
||||
pai_option: 'name',
|
||||
name: value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<VorePanelTooltip
|
||||
tooltip="What you plan to call yourself. Suggestions: Any character name you would choose for a station character OR an AI."
|
||||
displayText="?"
|
||||
/>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item verticalAlign="top" label="Role">
|
||||
<Stack>
|
||||
<Stack.Item>
|
||||
<TextArea
|
||||
fluid
|
||||
maxLength={4096}
|
||||
value={pai_role}
|
||||
height="100px"
|
||||
onBlur={(value) =>
|
||||
act('pai_option', {
|
||||
pai_option: 'role',
|
||||
role: value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<VorePanelTooltip
|
||||
tooltip="Do you like to partner with sneaky social ninjas? Like to help security hunt down thugs? Enjoy watching an engineer's back while he saves the station yet again? This doesn't have to be limited to just station jobs. Pretty much any general descriptor for what you'd like to be doing works here."
|
||||
displayText="?"
|
||||
/>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item verticalAlign="top" label="Description">
|
||||
<Stack>
|
||||
<Stack.Item>
|
||||
<TextArea
|
||||
fluid
|
||||
maxLength={16384}
|
||||
height="100px"
|
||||
value={pai_desc}
|
||||
onBlur={(value) =>
|
||||
act('pai_option', {
|
||||
pai_option: 'desc',
|
||||
desc: value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<VorePanelTooltip
|
||||
tooltip="What sort of pAI you typically play; your mannerisms, your quirks, etc. This can be as sparse or as detailed as you like."
|
||||
displayText="?"
|
||||
/>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item verticalAlign="top" label="Ad">
|
||||
<Stack>
|
||||
<Stack.Item>
|
||||
<TextArea
|
||||
fluid
|
||||
maxLength={4096}
|
||||
height="100px"
|
||||
value={pai_ad}
|
||||
onBlur={(value) =>
|
||||
act('pai_option', {
|
||||
pai_option: 'ad',
|
||||
ad: value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<VorePanelTooltip
|
||||
tooltip="Enter an advertisement for your pAI."
|
||||
displayText="?"
|
||||
/>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item verticalAlign="top" label="OOC Comments">
|
||||
<Stack>
|
||||
<Stack.Item>
|
||||
<TextArea
|
||||
fluid
|
||||
maxLength={4096}
|
||||
height="100px"
|
||||
value={pai_comments}
|
||||
onBlur={(value) =>
|
||||
act('pai_option', {
|
||||
pai_option: 'ooc',
|
||||
ooc: value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<VorePanelTooltip
|
||||
tooltip={`Anything you'd like to address specifically to the player reading this in an OOC manner. "I prefer more serious RP.", "I'm still learning the interface!", etc. Feel free to leave this blank if you want.`}
|
||||
displayText="?"
|
||||
/>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Color">
|
||||
<PreferenceEditColor
|
||||
back_color={pai_eyecolor}
|
||||
tooltip="Choose your pAI's default glow colour."
|
||||
onClose={(value) =>
|
||||
act('pai_option', {
|
||||
pai_option: 'color',
|
||||
color: value,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Chassis">
|
||||
<Stack>
|
||||
<Stack.Item>
|
||||
<Dropdown
|
||||
fluid
|
||||
onSelected={(value) =>
|
||||
act('pai_option', {
|
||||
pai_option: 'chassis',
|
||||
chassis: value,
|
||||
})
|
||||
}
|
||||
options={Object.keys(pai_chassises)}
|
||||
selected={pai_chassis}
|
||||
/>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<VorePanelTooltip
|
||||
tooltip="Choose your pAI's default chassis."
|
||||
displayText="?"
|
||||
/>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Emotion">
|
||||
<Stack>
|
||||
<Stack.Item>
|
||||
<Dropdown
|
||||
fluid
|
||||
onSelected={(value) =>
|
||||
act('pai_option', {
|
||||
pai_option: 'emotion',
|
||||
emotion: value,
|
||||
})
|
||||
}
|
||||
options={Object.keys(pai_emotions)}
|
||||
selected={pai_emotion}
|
||||
/>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<VorePanelTooltip
|
||||
tooltip="Choose your pAI's default emotion."
|
||||
displayText="?"
|
||||
/>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Stack.Item>
|
||||
<Stack.Divider />
|
||||
<Stack.Item grow>
|
||||
<Stack fill vertical align="center">
|
||||
<Stack.Item>
|
||||
<Box bold>Sprite Preview</Box>
|
||||
</Stack.Item>
|
||||
<PaiPreview
|
||||
icon={pai_sprite_datum_class}
|
||||
size={pai_sprite_datum_size}
|
||||
color={pai_eyecolor}
|
||||
/>
|
||||
</Stack>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
@@ -48,10 +48,6 @@ export const SubtabSettings = (props: {
|
||||
uplink_type,
|
||||
record_banned,
|
||||
exploitable_record,
|
||||
pai_name,
|
||||
pai_desc,
|
||||
pai_role,
|
||||
pai_comments,
|
||||
syndicate_ban,
|
||||
special_roles,
|
||||
custom_footstep,
|
||||
@@ -291,8 +287,8 @@ export const SubtabSettings = (props: {
|
||||
{role.banned
|
||||
? 'BANNED'
|
||||
: role.selected
|
||||
? 'Yes'
|
||||
: 'No'}
|
||||
? 'Yes'
|
||||
: 'No'}
|
||||
</Button>
|
||||
</LabeledList.Item>
|
||||
))}
|
||||
@@ -408,31 +404,6 @@ export const SubtabSettings = (props: {
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Box bold>pAI Settings</Box>
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Name">
|
||||
<Button onClick={() => act('option', { option: 'name' })}>
|
||||
{pai_name || 'None Set'}
|
||||
</Button>
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Description">
|
||||
<Button onClick={() => act('option', { option: 'desc' })}>
|
||||
{pai_desc || 'None Set'}
|
||||
</Button>
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="Role">
|
||||
<Button onClick={() => act('option', { option: 'role' })}>
|
||||
{pai_role || 'None Set'}
|
||||
</Button>
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Item label="OOC Comments">
|
||||
<Button onClick={() => act('option', { option: 'ooc' })}>
|
||||
{pai_comments || 'None Set'}
|
||||
</Button>
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Box bold>Persistence Settings</Box>
|
||||
<LabeledList>
|
||||
|
||||
@@ -15,8 +15,16 @@ export enum Gender {
|
||||
Female = 'Female',
|
||||
Neuter = 'Neuter',
|
||||
Plural = 'Plural',
|
||||
Herm = 'Herm',
|
||||
}
|
||||
|
||||
export const byondGenders: Gender[] = [
|
||||
Gender.Male,
|
||||
Gender.Female,
|
||||
Gender.Neuter,
|
||||
Gender.Plural,
|
||||
];
|
||||
|
||||
export enum PersistanceEnum {
|
||||
PERSIST_SPAWN = 0x01,
|
||||
PERSIST_WEIGHT = 0x02,
|
||||
@@ -238,12 +246,20 @@ export type AntagonismData = {
|
||||
uplink_type: string;
|
||||
record_banned: BooleanLike;
|
||||
exploitable_record?: string;
|
||||
pai_name?: string;
|
||||
pai_desc?: string;
|
||||
pai_role?: string;
|
||||
pai_comments?: string;
|
||||
pai_name: string;
|
||||
pai_desc: string;
|
||||
pai_role: string;
|
||||
pai_ad: string;
|
||||
pai_comments: string;
|
||||
pai_eyecolor: string;
|
||||
pai_chassis: string;
|
||||
pai_chassises: Record<string, string>[];
|
||||
pai_emotion: string;
|
||||
pai_emotions: Record<string, number>[];
|
||||
syndicate_ban: BooleanLike;
|
||||
special_roles: SpecialRole[];
|
||||
pai_sprite_datum_class?: string;
|
||||
pai_sprite_datum_size?: string;
|
||||
};
|
||||
|
||||
export const REQUIRED_FLAVOR_TEXT_LENGTH = 30;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Gender } from './data';
|
||||
|
||||
export function gender2icon(gender: Gender): string {
|
||||
export function gender2icon(gender: Gender | string): string {
|
||||
switch (gender) {
|
||||
case Gender.Female: {
|
||||
return 'venus';
|
||||
@@ -14,6 +14,12 @@ export function gender2icon(gender: Gender): string {
|
||||
case Gender.Neuter: {
|
||||
return 'neuter';
|
||||
}
|
||||
case Gender.Herm: {
|
||||
return 'mars-and-venus';
|
||||
}
|
||||
default: {
|
||||
return 'question';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,6 +37,9 @@ export function gender2pronouns(gender: Gender): string {
|
||||
case Gender.Neuter: {
|
||||
return 'It/Its';
|
||||
}
|
||||
case Gender.Herm: {
|
||||
return 'Shi/Hir';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import type {
|
||||
import { SubtabBody } from './SubtabBody';
|
||||
import { SubtabInfo } from './SubtabInfo';
|
||||
import { SubtabNotes } from './SubtabNotes';
|
||||
import { SubtabPai } from './SubtabPai';
|
||||
import { SubtabSettings } from './SubtabSettings';
|
||||
import { SubtabSpecies } from './SubtabSpecies';
|
||||
import { SubtabTraits } from './SubtabTraits';
|
||||
@@ -38,6 +39,7 @@ enum SubtabState {
|
||||
CharSettings = 'CharSettings',
|
||||
Notes = 'Notes',
|
||||
Traits = 'Traits',
|
||||
Pai = 'Pai',
|
||||
}
|
||||
|
||||
const SUBTAB_TO_NAME = {
|
||||
@@ -71,6 +73,11 @@ const SUBTAB_TO_NAME = {
|
||||
Traits <Icon name="tractor" />
|
||||
</Box>
|
||||
),
|
||||
[SubtabState.Pai]: (
|
||||
<Box inline>
|
||||
Pai <Icon name="robot" />
|
||||
</Box>
|
||||
),
|
||||
};
|
||||
|
||||
const SUBTAB_TO_COMPONENT: Record<
|
||||
@@ -87,6 +94,7 @@ const SUBTAB_TO_COMPONENT: Record<
|
||||
[SubtabState.CharSettings]: (props) => <SubtabSettings {...props} />,
|
||||
[SubtabState.Notes]: (props) => <SubtabNotes {...props} />,
|
||||
[SubtabState.Traits]: (props) => <SubtabTraits {...props} />,
|
||||
[SubtabState.Pai]: (props) => <SubtabPai {...props} />,
|
||||
};
|
||||
|
||||
export const GeneralContent = (props: {
|
||||
@@ -105,7 +113,7 @@ export const GeneralContent = (props: {
|
||||
return (
|
||||
<Section
|
||||
title={
|
||||
<Tabs>
|
||||
<Tabs scrollable>
|
||||
{all_subtabs.map((state) => {
|
||||
const title = SUBTAB_TO_NAME[state];
|
||||
return (
|
||||
|
||||
@@ -0,0 +1,187 @@
|
||||
import {
|
||||
type ComponentProps,
|
||||
type ReactNode,
|
||||
useEffect,
|
||||
useEffectEvent,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { useBackend } from 'tgui/backend';
|
||||
import { ColorSelector } from 'tgui/interfaces/ColorPickerModal';
|
||||
import { VorePanelTooltip } from 'tgui/interfaces/VorePanel/VorePanelElements/VorePanelTooltip';
|
||||
import { type HsvaColor, hexToHsva, hsvaToHex } from 'tgui-core/color';
|
||||
import { Box, Floating, Stack } from 'tgui-core/components';
|
||||
|
||||
export const PreferenceEditColor = (
|
||||
props: {
|
||||
/** Our action when closing the floating */
|
||||
onClose: (value: string) => void;
|
||||
/** The displayed color of the color box */
|
||||
back_color: string;
|
||||
} & Partial<{
|
||||
/** Our displayed tooltip behind the input element */
|
||||
tooltip: ReactNode;
|
||||
/** The position of the tooltip if static */
|
||||
tooltipPosition: ComponentProps<typeof Floating>['placement'];
|
||||
/** User preset colors */
|
||||
presets: string;
|
||||
/**The set action on color changes */
|
||||
onRealtimeValue: (val: string) => void;
|
||||
}>,
|
||||
) => {
|
||||
const { act } = useBackend();
|
||||
const {
|
||||
onClose,
|
||||
back_color,
|
||||
presets,
|
||||
tooltip,
|
||||
tooltipPosition,
|
||||
onRealtimeValue,
|
||||
} = props;
|
||||
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [initialColor, setInitialColor] = useState(back_color);
|
||||
const [currentColor, setCurrentColor] = useState<HsvaColor>(
|
||||
hexToHsva(back_color),
|
||||
);
|
||||
const [selectedPreset, setSelectedPreset] = useState<number | undefined>(
|
||||
undefined,
|
||||
);
|
||||
const [allowEditing, setAllowEditing] = useState<boolean>(false);
|
||||
const [lastSelectedColor, setLastSelectedColor] = useState<string>('');
|
||||
|
||||
useEffect(() => {
|
||||
let timeoutId: NodeJS.Timeout;
|
||||
|
||||
if (!isOpen) {
|
||||
timeoutId = setTimeout(() => {
|
||||
setInitialColor(back_color);
|
||||
setCurrentColor(hexToHsva(back_color));
|
||||
}, 100);
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (timeoutId) {
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
};
|
||||
}, [isOpen, back_color]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!onRealtimeValue) return;
|
||||
if (!isOpen) return;
|
||||
|
||||
const timeoutId = setTimeout(() => {
|
||||
onRealtimeValue(hsvaToHex(currentColor));
|
||||
}, 100);
|
||||
|
||||
return () => {
|
||||
clearTimeout(timeoutId);
|
||||
};
|
||||
}, [currentColor]);
|
||||
|
||||
const syncColorPreset = useEffectEvent(() => {
|
||||
const hexCol = hsvaToHex(currentColor);
|
||||
|
||||
if (
|
||||
selectedPreset !== undefined &&
|
||||
lastSelectedColor !== hexCol &&
|
||||
allowEditing
|
||||
) {
|
||||
setLastSelectedColor(hexCol);
|
||||
act('preset', { color: hexCol, index: selectedPreset + 1 });
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const timeoutId = setTimeout(() => {
|
||||
syncColorPreset();
|
||||
}, 100);
|
||||
|
||||
return () => clearTimeout(timeoutId);
|
||||
}, [currentColor]);
|
||||
|
||||
const pixelSize = 20;
|
||||
const parentSize = `${pixelSize}px`;
|
||||
const childSize = `${pixelSize - 4}px`;
|
||||
let presetList;
|
||||
if (presets) {
|
||||
const ourPresets = presets
|
||||
.replaceAll('#', '')
|
||||
.replace(/(^;)|(;$)/g, '')
|
||||
.split(';');
|
||||
while (ourPresets.length < 20) {
|
||||
ourPresets.push('FFFFFF');
|
||||
}
|
||||
presetList = ourPresets.reduce(
|
||||
(input, entry, index) => {
|
||||
if (index < 10) {
|
||||
return [[...input[0], entry], input[1]];
|
||||
} else {
|
||||
return [input[0], [...input[1], entry]];
|
||||
}
|
||||
},
|
||||
[[], []],
|
||||
);
|
||||
}
|
||||
|
||||
function handleSetColor(value: HsvaColor | ((prev: HsvaColor) => HsvaColor)) {
|
||||
const newColor = typeof value === 'function' ? value(currentColor) : value;
|
||||
setCurrentColor(newColor);
|
||||
}
|
||||
|
||||
function handleIsOpen(newState: boolean) {
|
||||
setIsOpen(newState);
|
||||
if (!newState) {
|
||||
const newHexColor = hsvaToHex(currentColor).toLowerCase();
|
||||
if (back_color.toLowerCase() !== newHexColor) {
|
||||
onClose(newHexColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack>
|
||||
<Stack.Item shrink>
|
||||
<Floating
|
||||
onOpenChange={handleIsOpen}
|
||||
placement="top-end"
|
||||
contentClasses="VorePanel__Floating"
|
||||
content={
|
||||
<ColorSelector
|
||||
color={currentColor}
|
||||
setColor={handleSetColor}
|
||||
presetList={presetList}
|
||||
defaultColor={initialColor}
|
||||
selectedPreset={selectedPreset}
|
||||
onSelectedPreset={setSelectedPreset}
|
||||
allowEditing={allowEditing}
|
||||
onAllowEditing={setAllowEditing}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Box
|
||||
style={{
|
||||
border: '2px solid white',
|
||||
cursor: 'pointer',
|
||||
}}
|
||||
width={parentSize}
|
||||
height={parentSize}
|
||||
>
|
||||
<Box
|
||||
backgroundColor={hsvaToHex(currentColor) ?? back_color}
|
||||
width={childSize}
|
||||
height={childSize}
|
||||
/>
|
||||
</Box>
|
||||
</Floating>
|
||||
</Stack.Item>
|
||||
<Stack.Item>
|
||||
<VorePanelTooltip
|
||||
tooltip={tooltip}
|
||||
tooltipPosition={tooltipPosition}
|
||||
displayText="?"
|
||||
/>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
@@ -14,7 +14,7 @@ export const SpriteSection = (props: {
|
||||
|
||||
const [searchText, setSearchText] = useState<string>('');
|
||||
const [includeDefault, setIncludeDefault] = useState<boolean>(false);
|
||||
const [includeWide, setInclideDog] = useState<boolean>(false);
|
||||
const [includeWide, setIncludeWide] = useState<boolean>(false);
|
||||
const [includeTall, setIncludeTall] = useState<boolean>(false);
|
||||
|
||||
const filtered = robotSpriteSearcher(
|
||||
@@ -41,7 +41,7 @@ export const SpriteSection = (props: {
|
||||
<Stack.Item>
|
||||
<Button.Checkbox
|
||||
checked={includeWide}
|
||||
onClick={() => setInclideDog(!includeWide)}
|
||||
onClick={() => setIncludeWide(!includeWide)}
|
||||
>
|
||||
Wide
|
||||
</Button.Checkbox>
|
||||
|
||||
@@ -149,9 +149,9 @@ export const VorePanelEditColor = (
|
||||
function handleIsOpen(newState: boolean) {
|
||||
setIsOpen(newState);
|
||||
if (!newState) {
|
||||
const newHeColor = hsvaToHex(currentColor).toLowerCase();
|
||||
if (back_color.toLowerCase() !== newHeColor) {
|
||||
act(action, { attribute: subAction, val: newHeColor });
|
||||
const newHexColor = hsvaToHex(currentColor).toLowerCase();
|
||||
if (back_color.toLowerCase() !== newHexColor) {
|
||||
act(action, { attribute: subAction, val: newHexColor });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,27 @@
|
||||
import { useBackend } from 'tgui/backend';
|
||||
import { Window } from 'tgui/layouts';
|
||||
import { Button, LabeledList, Section } from 'tgui-core/components';
|
||||
import {
|
||||
Button,
|
||||
Dropdown,
|
||||
Icon,
|
||||
LabeledList,
|
||||
ProgressBar,
|
||||
Section,
|
||||
Stack,
|
||||
} from 'tgui-core/components';
|
||||
import type { BooleanLike } from 'tgui-core/react';
|
||||
|
||||
type Data = {
|
||||
bought: { id: string; name: string; on: BooleanLike }[];
|
||||
not_bought: { id: string; name: string; ram: number }[];
|
||||
available_ram: number;
|
||||
emotions: { id: string; name: string }[];
|
||||
current_emotion: string;
|
||||
emotions: DropdownEntry[];
|
||||
current_emotion: number;
|
||||
};
|
||||
|
||||
type DropdownEntry = {
|
||||
displayText: string;
|
||||
value: string;
|
||||
};
|
||||
|
||||
export const pAIInterface = (props) => {
|
||||
@@ -18,45 +31,100 @@ export const pAIInterface = (props) => {
|
||||
|
||||
return (
|
||||
<Window width={450} height={600}>
|
||||
<Window.Content scrollable>
|
||||
<Section title="Emotion">
|
||||
{emotions.map((emote) => (
|
||||
<Button
|
||||
key={emote.id}
|
||||
selected={emote.id === current_emotion}
|
||||
onClick={() => act('image', { image: emote.id })}
|
||||
<Window.Content>
|
||||
<Stack fill vertical>
|
||||
<Stack.Item>
|
||||
<Section title="Emotion">
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Selected">
|
||||
<Dropdown
|
||||
onSelected={(value) => act('image', { image: value })}
|
||||
options={emotions}
|
||||
selected={emotions[current_emotion - 1].displayText}
|
||||
/>
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
</Section>
|
||||
</Stack.Item>
|
||||
<Stack.Item grow>
|
||||
<Section
|
||||
fill
|
||||
title="Software"
|
||||
buttons={
|
||||
<Stack align="baseline">
|
||||
<Stack.Item color="label">Available RAM:</Stack.Item>
|
||||
<Stack.Item>
|
||||
<ProgressBar
|
||||
width="150px"
|
||||
value={available_ram}
|
||||
minValue={0}
|
||||
maxValue={100}
|
||||
ranges={{
|
||||
good: [60, Infinity],
|
||||
average: [20, 60],
|
||||
bad: [-Infinity, 20],
|
||||
}}
|
||||
>
|
||||
{available_ram}
|
||||
</ProgressBar>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
}
|
||||
scrollable
|
||||
>
|
||||
{emote.name}
|
||||
</Button>
|
||||
))}
|
||||
</Section>
|
||||
<Section title={`Software (Available RAM: ${available_ram})`}>
|
||||
<LabeledList>
|
||||
<LabeledList.Item label="Installed">
|
||||
{bought.map((app) => (
|
||||
<Button
|
||||
key={app.id}
|
||||
selected={app.on}
|
||||
onClick={() => act('software', { software: app.id })}
|
||||
>
|
||||
{app.name}
|
||||
</Button>
|
||||
))}
|
||||
</LabeledList.Item>
|
||||
<LabeledList.Divider />
|
||||
<LabeledList.Item label="Downloadable">
|
||||
{not_bought.map((app) => (
|
||||
<Button
|
||||
key={app.id}
|
||||
disabled={app.ram > available_ram}
|
||||
onClick={() => act('purchase', { purchase: app.id })}
|
||||
>
|
||||
{`${app.name} (${app.ram})`}
|
||||
</Button>
|
||||
))}
|
||||
</LabeledList.Item>
|
||||
</LabeledList>
|
||||
</Section>
|
||||
<Stack fill>
|
||||
<Stack.Item basis="50%">
|
||||
<Stack vertical g={0.5}>
|
||||
<Stack.Item textAlign="center">Downloadable</Stack.Item>
|
||||
<Stack.Divider />
|
||||
{not_bought.map((app) => (
|
||||
<Stack.Item key={app.id}>
|
||||
<Button
|
||||
fluid
|
||||
disabled={app.ram > available_ram}
|
||||
color={app.ram <= available_ram ? 'green' : 'red'}
|
||||
onClick={() => act('purchase', { purchase: app.id })}
|
||||
>
|
||||
<Stack>
|
||||
<Stack.Item
|
||||
grow
|
||||
>{`${app.name} (${app.ram})`}</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Icon name="arrow-right-from-bracket" />
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Button>
|
||||
</Stack.Item>
|
||||
))}
|
||||
</Stack>
|
||||
</Stack.Item>
|
||||
<Stack.Divider />
|
||||
<Stack.Item basis="50%">
|
||||
<Stack vertical g={0.5}>
|
||||
<Stack.Item textAlign="center">Installed</Stack.Item>
|
||||
<Stack.Divider />
|
||||
{bought.map((app) => (
|
||||
<Stack.Item key={app.id}>
|
||||
<Button
|
||||
fluid
|
||||
selected={app.on}
|
||||
onClick={() => act('software', { software: app.id })}
|
||||
>
|
||||
<Stack>
|
||||
<Stack.Item grow>{app.name}</Stack.Item>
|
||||
<Stack.Item>
|
||||
<Icon name="arrow-up-right-from-square" />
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Button>
|
||||
</Stack.Item>
|
||||
))}
|
||||
</Stack>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Section>
|
||||
</Stack.Item>
|
||||
</Stack>
|
||||
</Window.Content>
|
||||
</Window>
|
||||
);
|
||||
|
||||
@@ -584,6 +584,7 @@
|
||||
#include "code\controllers\subsystems\nightshift.dm"
|
||||
#include "code\controllers\subsystems\overlays.dm"
|
||||
#include "code\controllers\subsystems\overmap_renamer_vr.dm"
|
||||
#include "code\controllers\subsystems\pai.dm"
|
||||
#include "code\controllers\subsystems\persist_vr.dm"
|
||||
#include "code\controllers\subsystems\persistence.dm"
|
||||
#include "code\controllers\subsystems\ping.dm"
|
||||
@@ -1684,7 +1685,6 @@
|
||||
#include "code\game\objects\items\devices\modkit.dm"
|
||||
#include "code\game\objects\items\devices\multitool.dm"
|
||||
#include "code\game\objects\items\devices\paicard.dm"
|
||||
#include "code\game\objects\items\devices\paicard_vr.dm"
|
||||
#include "code\game\objects\items\devices\personal_shield_generator_vr.dm"
|
||||
#include "code\game\objects\items\devices\pipe_painter.dm"
|
||||
#include "code\game\objects\items\devices\powersink.dm"
|
||||
@@ -2321,6 +2321,7 @@
|
||||
#include "code\modules\asset_cache\assets\spritesheets\chat.dm"
|
||||
#include "code\modules\asset_cache\assets\spritesheets\chem_master.dm"
|
||||
#include "code\modules\asset_cache\assets\spritesheets\kitchen_recipes.dm"
|
||||
#include "code\modules\asset_cache\assets\spritesheets\pai_icons.dm"
|
||||
#include "code\modules\asset_cache\assets\spritesheets\pipes.dm"
|
||||
#include "code\modules\asset_cache\assets\spritesheets\robot_icons.dm"
|
||||
#include "code\modules\asset_cache\assets\spritesheets\stacks.dm"
|
||||
@@ -2479,6 +2480,7 @@
|
||||
#include "code\modules\client\preferences\migrations\16_colors.dm"
|
||||
#include "code\modules\client\preferences\migrations\17_tails.dm"
|
||||
#include "code\modules\client\preferences\migrations\18_jukebox.dm"
|
||||
#include "code\modules\client\preferences\migrations\19_paifile.dm"
|
||||
#include "code\modules\client\preferences\types\character\gender.dm"
|
||||
#include "code\modules\client\preferences\types\character\names.dm"
|
||||
#include "code\modules\client\preferences\types\character\antagonism\01_basic.dm"
|
||||
@@ -2486,6 +2488,7 @@
|
||||
#include "code\modules\client\preferences\types\character\general\03_body.dm"
|
||||
#include "code\modules\client\preferences\types\character\general\06_special.dm"
|
||||
#include "code\modules\client\preferences\types\character\general\11_misc.dm"
|
||||
#include "code\modules\client\preferences\types\character\pai\01_basic.dm"
|
||||
#include "code\modules\client\preferences\types\game\admin.dm"
|
||||
#include "code\modules\client\preferences\types\game\adult.dm"
|
||||
#include "code\modules\client\preferences\types\game\auto_fit_viewport.dm"
|
||||
@@ -3558,17 +3561,19 @@
|
||||
#include "code\modules\mob\living\silicon\decoy\death.dm"
|
||||
#include "code\modules\mob\living\silicon\decoy\decoy.dm"
|
||||
#include "code\modules\mob\living\silicon\decoy\life.dm"
|
||||
#include "code\modules\mob\living\silicon\pai\chassis_ui.dm"
|
||||
#include "code\modules\mob\living\silicon\pai\death.dm"
|
||||
#include "code\modules\mob\living\silicon\pai\examine.dm"
|
||||
#include "code\modules\mob\living\silicon\pai\life.dm"
|
||||
#include "code\modules\mob\living\silicon\pai\pai.dm"
|
||||
#include "code\modules\mob\living\silicon\pai\pai_folding.dm"
|
||||
#include "code\modules\mob\living\silicon\pai\pai_hud.dm"
|
||||
#include "code\modules\mob\living\silicon\pai\pai_vr.dm"
|
||||
#include "code\modules\mob\living\silicon\pai\personality.dm"
|
||||
#include "code\modules\mob\living\silicon\pai\recruit.dm"
|
||||
#include "code\modules\mob\living\silicon\pai\pai_software.dm"
|
||||
#include "code\modules\mob\living\silicon\pai\pai_verbs.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\sprites\_sprite_datum.dm"
|
||||
#include "code\modules\mob\living\silicon\robot\analyzer.dm"
|
||||
#include "code\modules\mob\living\silicon\robot\cloak.dm"
|
||||
#include "code\modules\mob\living\silicon\robot\component.dm"
|
||||
|
||||