-
- "}
- else //
- dat += span_bold("Radio Uplink") + " "
- dat += span_red(span_italics("Radio firmware not loaded. Please install a pAI personality to load firmware.")) + " "
- /* - //A button for instantly deleting people from the game is lame, especially considering that pAIs on our server tend to activate without a master.
- dat += {"
-
Requesting 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.
Each time this button is pressed, a request will be sent out to any available personalities. Check back often give plenty of time for personalities to respond. This process could take anywhere from 15 seconds to several minutes, depending on the available personalities' timeliness.
"))
- if(href_list["request"])
- src.looking_for_personality = 1
- paiController.findPAI(src, usr)
- if(href_list["wipe"])
- var/confirm = tgui_alert(usr, "Are you CERTAIN you wish to delete the current personality? This action cannot be undone.", "Personality Wipe", list("Yes", "No"))
- if(confirm == "Yes")
- for(var/mob/M in src)
- to_chat(M, span_red("
You feel yourself slipping away from reality.
"))
- to_chat(M, "
Byte by byte you lose your sense of self.
")
- to_chat(M, "
Your mental faculties leave you.
")
- to_chat(M, "
oblivion...
")
- M.death(0)
- removePersonality()
- if(href_list["wires"])
- var/t1 = text2num(href_list["wires"])
- switch(t1)
- if(4)
- radio.ToggleBroadcast()
- if(2)
- radio.ToggleReception()
- if(href_list["setlaws"])
- var/newlaws = sanitize(tgui_input_text(usr, "Enter any additional directives you would like your pAI personality to follow. Note that these directives will not override the personality's allegiance to its imprinted master. Conflicting directives will be ignored.", "pAI Directive Configuration", pai.pai_laws, MAX_MESSAGE_LEN, encode = FALSE, multiline = TRUE, prevent_enter = TRUE), MAX_MESSAGE_LEN, FALSE, FALSE, TRUE)
- if(newlaws)
- pai.pai_laws = newlaws
- to_chat(pai, "Your supplemental directives have been updated. Your new directives are:")
- to_chat(pai, "Prime Directive: [pai.pai_law0]")
- to_chat(pai, "Supplemental Directives: [pai.pai_laws]")
- attack_self(usr)
+ if("select_pai")
+ if(pai)
+ return FALSE
+ if(in_use)
+ return FALSE
+ in_use = TRUE
+ SSpai.invite_ghost(ui.user, params["ref"], src)
+ in_use = FALSE
+ selected_pai = null
+ return TRUE
-// WIRE_SIGNAL = 1
-// WIRE_RECEIVE = 2
-// WIRE_TRANSMIT = 4
+/obj/item/paicard/proc/show_laws(updated = FALSE)
+ to_chat(pai, examine_block(span_notice((updated ? "Your supplemental directives have been updated. Your new" : "Your") + " directives are:") + " " + "Prime Directive: [span_info(pai.pai_law0)] Supplemental Directives: [span_info(pai.pai_laws)]"))
/obj/item/paicard/proc/setPersonality(mob/living/silicon/pai/personality)
- src.pai = personality
+ pai = personality
setEmotion(1)
/obj/item/paicard/proc/removePersonality()
- src.pai = null
- cut_overlays()
+ pai = null
setEmotion(16)
-/obj/item/paicard
- var/current_emotion = 1
/obj/item/paicard/proc/setEmotion(var/emotion)
- if(pai)
- cut_overlays()
- qdel(screen_layer)
- screen_layer = null
- switch(emotion)
- if(1) screen_layer = image(icon, "pai-neutral")
- if(2) screen_layer = image(icon, "pai-what")
- if(3) screen_layer = image(icon, "pai-happy")
- if(4) screen_layer = image(icon, "pai-cat")
- if(5) screen_layer = image(icon, "pai-extremely-happy")
- if(6) screen_layer = image(icon, "pai-face")
- if(7) screen_layer = image(icon, "pai-laugh")
- if(8) screen_layer = image(icon, "pai-sad")
- if(9) screen_layer = image(icon, "pai-angry")
- if(10) screen_layer = image(icon, "pai-silly")
- if(11) screen_layer = image(icon, "pai-nose")
- if(12) screen_layer = image(icon, "pai-smirk")
- if(13) screen_layer = image(icon, "pai-exclamation")
- if(14) screen_layer = image(icon, "pai-question")
- if(15) screen_layer = image(icon, "pai-blank")
- if(16) screen_layer = image(icon, "pai-off")
+ cut_overlays()
+ qdel(screen_layer)
+ screen_layer = null
+ switch(emotion)
+ if(1) screen_layer = image(icon, "pai-neutral")
+ if(2) screen_layer = image(icon, "pai-what")
+ if(3) screen_layer = image(icon, "pai-happy")
+ if(4) screen_layer = image(icon, "pai-cat")
+ if(5) screen_layer = image(icon, "pai-extremely-happy")
+ if(6) screen_layer = image(icon, "pai-face")
+ if(7) screen_layer = image(icon, "pai-laugh")
+ if(8) screen_layer = image(icon, "pai-sad")
+ if(9) screen_layer = image(icon, "pai-angry")
+ if(10) screen_layer = image(icon, "pai-silly")
+ if(11) screen_layer = image(icon, "pai-nose")
+ if(12) screen_layer = image(icon, "pai-smirk")
+ if(13) screen_layer = image(icon, "pai-exclamation")
+ if(14) screen_layer = image(icon, "pai-question")
+ if(15) screen_layer = image(icon, "pai-blank")
+ if(16) screen_layer = image(icon, "pai-off")
- screen_layer.color = pai.eye_color
- add_overlay(screen_layer)
- current_emotion = emotion
+ screen_layer.color = pai ? pai.eye_color : initial(screen_color)
+ add_overlay(screen_layer)
+ current_emotion = emotion
/obj/item/paicard/proc/alertUpdate()
if(pai)
@@ -442,10 +341,401 @@
pai.show_message(rendered, type)
..()
+/obj/item/paicard/proc/clear_invite_overlay()
+ SHOULD_NOT_OVERRIDE(TRUE)
+ PRIVATE_PROC(TRUE)
+ if(pai) // Don't wipe emotion if a pai was invited
+ return
+ setEmotion(16)
+/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)
+ tgui_interact(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
+
+///////////////////////////////
+//////////pAI Parts //////////
+///////////////////////////////
+/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"
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// VoreEdit: Living Machine Stuff after this.
// This adds a var and proc for all machines to take a pAI. (The pAI can't control anything, it's just for RP.)
// You need to add usage of the proc to each machine to actually add support. For an example of this, see code\modules\food\kitchen\microwave.dm
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/obj/machinery
var/obj/item/paicard/paicard = null
diff --git a/code/game/objects/items/devices/paicard_vr.dm b/code/game/objects/items/devices/paicard_vr.dm
deleted file mode 100644
index e9db9a1806..0000000000
--- a/code/game/objects/items/devices/paicard_vr.dm
+++ /dev/null
@@ -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"
diff --git a/code/game/objects/objs.dm b/code/game/objects/objs.dm
index c382a84b5d..1936cf9040 100644
--- a/code/game/objects/objs.dm
+++ b/code/game/objects/objs.dm
@@ -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)
diff --git a/code/game/objects/structures/crates_lockers/__closets.dm b/code/game/objects/structures/crates_lockers/__closets.dm
index dc067df234..54632231e0 100644
--- a/code/game/objects/structures/crates_lockers/__closets.dm
+++ b/code/game/objects/structures/crates_lockers/__closets.dm
@@ -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()
diff --git a/code/game/objects/structures/fence.dm b/code/game/objects/structures/fence.dm
index 4e0dafc9ac..298e5e33eb 100644
--- a/code/game/objects/structures/fence.dm
+++ b/code/game/objects/structures/fence.dm
@@ -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)
diff --git a/code/game/objects/structures/watercloset.dm b/code/game/objects/structures/watercloset.dm
index 4f5e054119..dd511a7e6c 100644
--- a/code/game/objects/structures/watercloset.dm
+++ b/code/game/objects/structures/watercloset.dm
@@ -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)
diff --git a/code/modules/admin/verbs/debug.dm b/code/modules/admin/verbs/debug.dm
index 12699adf13..8074220965 100644
--- a/code/modules/admin/verbs/debug.dm
+++ b/code/modules/admin/verbs/debug.dm
@@ -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!
diff --git a/code/modules/asset_cache/assets/spritesheets/pai_icons.dm b/code/modules/asset_cache/assets/spritesheets/pai_icons.dm
new file mode 100644
index 0000000000..75fffd8732
--- /dev/null
+++ b/code/modules/asset_cache/assets/spritesheets/pai_icons.dm
@@ -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)
diff --git a/code/modules/asset_cache/assets/spritesheets/robot_icons.dm b/code/modules/asset_cache/assets/spritesheets/robot_icons.dm
index 429e5f3432..9194281420 100644
--- a/code/modules/asset_cache/assets/spritesheets/robot_icons.dm
+++ b/code/modules/asset_cache/assets/spritesheets/robot_icons.dm
@@ -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)
diff --git a/code/modules/client/preference_setup/general/07_antagonism.dm b/code/modules/client/preference_setup/general/07_antagonism.dm
index 9d14c3654f..48f0c4a7fa 100644
--- a/code/modules/client/preference_setup/general/07_antagonism.dm
+++ b/code/modules/client/preference_setup/general/07_antagonism.dm
@@ -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
diff --git a/code/modules/client/preferences/migrations/19_paifile.dm b/code/modules/client/preferences/migrations/19_paifile.dm
new file mode 100644
index 0000000000..8cab27586d
--- /dev/null
+++ b/code/modules/client/preferences/migrations/19_paifile.dm
@@ -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()
diff --git a/code/modules/client/preferences/types/character/pai/01_basic.dm b/code/modules/client/preferences/types/character/pai/01_basic.dm
new file mode 100644
index 0000000000..b5df4c5c04
--- /dev/null
+++ b/code/modules/client/preferences/types/character/pai/01_basic.dm
@@ -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
diff --git a/code/modules/client/preferences_savefile.dm b/code/modules/client/preferences_savefile.dm
index 858a26269a..37534d4dc0 100644
--- a/code/modules/client/preferences_savefile.dm
+++ b/code/modules/client/preferences_savefile.dm
@@ -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)
diff --git a/code/modules/client/preferences_tgui.dm b/code/modules/client/preferences_tgui.dm
index 87e63ff698..3e1c5069ba 100644
--- a/code/modules/client/preferences_tgui.dm
+++ b/code/modules/client/preferences_tgui.dm
@@ -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)
diff --git a/code/modules/emotes/definitions/audible_furry_vr.dm b/code/modules/emotes/definitions/audible_furry_vr.dm
index 4e2aa85770..479f91cde0 100644
--- a/code/modules/emotes/definitions/audible_furry_vr.dm
+++ b/code/modules/emotes/definitions/audible_furry_vr.dm
@@ -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)
diff --git a/code/modules/instruments/stationary.dm b/code/modules/instruments/stationary.dm
index d2d83fd9a0..71f4bb930c 100644
--- a/code/modules/instruments/stationary.dm
+++ b/code/modules/instruments/stationary.dm
@@ -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)
. = ..()
diff --git a/code/modules/mining/shelters_vr.dm b/code/modules/mining/shelters_vr.dm
index 45fcac9f0f..4ae99d1af6 100644
--- a/code/modules/mining/shelters_vr.dm
+++ b/code/modules/mining/shelters_vr.dm
@@ -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
diff --git a/code/modules/mob/dead/observer/observer.dm b/code/modules/mob/dead/observer/observer.dm
index 7a3f2efd65..7aaadfd01a 100644
--- a/code/modules/mob/dead/observer/observer.dm
+++ b/code/modules/mob/dead/observer/observer.dm
@@ -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"
diff --git a/code/modules/mob/living/login.dm b/code/modules/mob/living/login.dm
index 1d117d730d..56bc850d3d 100644
--- a/code/modules/mob/living/login.dm
+++ b/code/modules/mob/living/login.dm
@@ -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)
diff --git a/code/modules/mob/living/silicon/pai/chassis_ui.dm b/code/modules/mob/living/silicon/pai/chassis_ui.dm
new file mode 100644
index 0000000000..4624ed60f7
--- /dev/null
+++ b/code/modules/mob/living/silicon/pai/chassis_ui.dm
@@ -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
diff --git a/code/modules/mob/living/silicon/pai/death.dm b/code/modules/mob/living/silicon/pai/death.dm
index 5180ec1a90..30edde7839 100644
--- a/code/modules/mob/living/silicon/pai/death.dm
+++ b/code/modules/mob/living/silicon/pai/death.dm
@@ -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")
diff --git a/code/modules/mob/living/silicon/pai/life.dm b/code/modules/mob/living/silicon/pai/life.dm
index b2893fe172..c518959c65 100644
--- a/code/modules/mob/living/silicon/pai/life.dm
+++ b/code/modules/mob/living/silicon/pai/life.dm
@@ -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)
diff --git a/code/modules/mob/living/silicon/pai/pai.dm b/code/modules/mob/living/silicon/pai/pai.dm
index e02028bfb8..0a463d9d53 100644
--- a/code/modules/mob/living/silicon/pai/pai.dm
+++ b/code/modules/mob/living/silicon/pai/pai.dm
@@ -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"
diff --git a/code/modules/mob/living/silicon/pai/pai_folding.dm b/code/modules/mob/living/silicon/pai/pai_folding.dm
new file mode 100644
index 0000000000..75b1b0ebb2
--- /dev/null
+++ b/code/modules/mob/living/silicon/pai/pai_folding.dm
@@ -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))
diff --git a/code/modules/mob/living/silicon/pai/pai_hud.dm b/code/modules/mob/living/silicon/pai/pai_hud.dm
index a8e4385a77..45af2b6941 100644
--- a/code/modules/mob/living/silicon/pai/pai_hud.dm
+++ b/code/modules/mob/living/silicon/pai/pai_hud.dm
@@ -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
diff --git a/code/modules/mob/living/silicon/pai/pai_software.dm b/code/modules/mob/living/silicon/pai/pai_software.dm
new file mode 100644
index 0000000000..1240f50fff
--- /dev/null
+++ b/code/modules/mob/living/silicon/pai/pai_software.dm
@@ -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")
diff --git a/code/modules/mob/living/silicon/pai/pai_verbs.dm b/code/modules/mob/living/silicon/pai/pai_verbs.dm
new file mode 100644
index 0000000000..e5319fea38
--- /dev/null
+++ b/code/modules/mob/living/silicon/pai/pai_verbs.dm
@@ -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)
diff --git a/code/modules/mob/living/silicon/pai/pai_vr.dm b/code/modules/mob/living/silicon/pai/pai_vr.dm
deleted file mode 100644
index 7c5972b46e..0000000000
--- a/code/modules/mob/living/silicon/pai/pai_vr.dm
+++ /dev/null
@@ -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()
diff --git a/code/modules/mob/living/silicon/pai/personality.dm b/code/modules/mob/living/silicon/pai/personality.dm
deleted file mode 100644
index 1824789f55..0000000000
--- a/code/modules/mob/living/silicon/pai/personality.dm
+++ /dev/null
@@ -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
diff --git a/code/modules/mob/living/silicon/pai/recruit.dm b/code/modules/mob/living/silicon/pai/recruit.dm
deleted file mode 100644
index 1a6603c78a..0000000000
--- a/code/modules/mob/living/silicon/pai/recruit.dm
+++ /dev/null
@@ -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 += {"
-
- "}
-
- dat += {"
-
- pAI Personality Configuration
-
Please configure your pAI personality's options. Remember, what you enter here could determine whether or not the user requesting a personality chooses you!
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.
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.
- "}
- dat += {"
-
- "}
-
- M << browse("[dat]", "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 += {"
-
-
-
-
-
-
- pAI Availability List
- "}
- dat += "
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.
"
-
- for(var/datum/paiCandidate/c in available)
- dat += {"
-