diff --git a/code/__defines/preferences.dm b/code/__defines/preferences.dm index eb4cf69ceb..3b027e78dc 100644 --- a/code/__defines/preferences.dm +++ b/code/__defines/preferences.dm @@ -75,3 +75,4 @@ #define PAI_UNSET "None Set" #define PAI_DEFAULT_CHASSIS "Drone" +#define PAI_DEFAULT_EMAGGED_CHASSIS "Syndicate Fox" diff --git a/code/datums/components/traits/crowd_detection.dm b/code/datums/components/traits/crowd_detection.dm index 8ff4aed238..cda8198525 100644 --- a/code/datums/components/traits/crowd_detection.dm +++ b/code/datums/components/traits/crowd_detection.dm @@ -96,7 +96,7 @@ var/list/in_range = list() if(!istype(M)) return in_range - var/social_check = only_people && !istype(M, /mob/living/carbon) && !istype(M, /mob/living/silicon/robot) + var/social_check = only_people && !istype(M, /mob/living/carbon) && !istype(M, /mob/living/silicon/robot) && !istype(M, /mob/living/silicon/pai) var/self_invisible_check = M == human_parent || M.invisibility > human_parent.see_invisible var/ckey_check = only_people && !M.ckey var/overall_checks = M == human_parent || M.stat == DEAD || social_check || ckey_check @@ -199,6 +199,10 @@ for(var/obj/item/toy/plushie/teshari/P in range(5, human_parent)) calm_discomfort() return + for(var/obj/item/paicard/P in range(5, human_parent)) + if(P.pai && !P.pai.stat && P.pai.client) // Must have pai and it must be alive + calm_discomfort() + return increase_discomfort() diff --git a/code/datums/wires/wires.dm b/code/datums/wires/wires.dm index 0a281aaae6..baa29bd2d0 100644 --- a/code/datums/wires/wires.dm +++ b/code/datums/wires/wires.dm @@ -165,6 +165,14 @@ return var/obj/item/I = ui.user.get_active_hand() + if(istype(I, /obj/item/paicard)) // Get pai builtin emag tools + var/obj/item/paicard/card = I + if(card.emagged && card.has_emag_toolkit) + switch(card.selected_system) + if("MultiTool") + I = card.multitool + if("Signaler") + I = card.signaler var/color = lowertext(params["wire"]) holder.add_hiddenprint(ui.user) diff --git a/code/game/machinery/atmo_control.dm b/code/game/machinery/atmo_control.dm index 7d61d7d6b4..77af6e7b8c 100644 --- a/code/game/machinery/atmo_control.dm +++ b/code/game/machinery/atmo_control.dm @@ -186,7 +186,7 @@ . = ..(W, user) -/obj/machinery/computer/general_air_control/allow_pai_interaction(proximity_flag) +/obj/machinery/computer/general_air_control/allow_pai_interaction(mob/living/silicon/pai/user, proximity_flag) return proximity_flag /obj/machinery/computer/general_air_control/receive_signal(datum/signal/signal) diff --git a/code/game/machinery/buttons.dm b/code/game/machinery/buttons.dm index 5ee5da62df..609f2b009e 100644 --- a/code/game/machinery/buttons.dm +++ b/code/game/machinery/buttons.dm @@ -17,5 +17,5 @@ /obj/machinery/button/attackby(obj/item/W, mob/user as mob) return attack_hand(user) -/obj/machinery/button/allow_pai_interaction(proximity_flag) +/obj/machinery/button/allow_pai_interaction(mob/living/silicon/pai/user, proximity_flag) return proximity_flag diff --git a/code/game/machinery/computer/atmos_alert.dm b/code/game/machinery/computer/atmos_alert.dm index 4c78c2d9e8..4c0cfc26cb 100644 --- a/code/game/machinery/computer/atmos_alert.dm +++ b/code/game/machinery/computer/atmos_alert.dm @@ -17,7 +17,7 @@ /obj/machinery/computer/atmos_alert/attack_hand(mob/user) tgui_interact(user) -/obj/machinery/computer/atmos_alert/allow_pai_interaction(proximity_flag) +/obj/machinery/computer/atmos_alert/allow_pai_interaction(mob/living/silicon/pai/user, proximity_flag) return proximity_flag /obj/machinery/computer/atmos_alert/tgui_interact(mob/user, datum/tgui/ui) diff --git a/code/game/machinery/computer/camera.dm b/code/game/machinery/computer/camera.dm index c3a7ee5043..5b481a6987 100644 --- a/code/game/machinery/computer/camera.dm +++ b/code/game/machinery/computer/camera.dm @@ -68,7 +68,7 @@ density = FALSE circuit = null -/obj/machinery/computer/security/telescreen/allow_pai_interaction(proximity_flag) +/obj/machinery/computer/security/telescreen/allow_pai_interaction(mob/living/silicon/pai/user, proximity_flag) return proximity_flag GLOBAL_LIST_EMPTY(entertainment_screens) diff --git a/code/game/machinery/computer/crew.dm b/code/game/machinery/computer/crew.dm index 2045154cd0..2d94a74652 100644 --- a/code/game/machinery/computer/crew.dm +++ b/code/game/machinery/computer/crew.dm @@ -28,7 +28,7 @@ return tgui_interact(user) -/obj/machinery/computer/crew/allow_pai_interaction(proximity_flag) +/obj/machinery/computer/crew/allow_pai_interaction(mob/living/silicon/pai/user, proximity_flag) return proximity_flag /obj/machinery/computer/crew/tgui_interact(mob/user, datum/tgui/ui = null) diff --git a/code/game/machinery/computer/station_alert.dm b/code/game/machinery/computer/station_alert.dm index a365fa66a9..d973f57662 100644 --- a/code/game/machinery/computer/station_alert.dm +++ b/code/game/machinery/computer/station_alert.dm @@ -41,7 +41,7 @@ tgui_interact(user) return -/obj/machinery/computer/station_alert/allow_pai_interaction(proximity_flag) +/obj/machinery/computer/station_alert/allow_pai_interaction(mob/living/silicon/pai/user, proximity_flag) return proximity_flag /obj/machinery/computer/station_alert/tgui_interact(mob/user) diff --git a/code/game/machinery/doors/airlock_control.dm b/code/game/machinery/doors/airlock_control.dm index 3779df3582..f6b8b99a1f 100644 --- a/code/game/machinery/doors/airlock_control.dm +++ b/code/game/machinery/doors/airlock_control.dm @@ -270,7 +270,7 @@ return TRUE return ..() -/obj/machinery/airlock_sensor/allow_pai_interaction(proximity_flag) +/obj/machinery/airlock_sensor/allow_pai_interaction(mob/living/silicon/pai/user, proximity_flag) return proximity_flag /obj/machinery/airlock_sensor/airlock_interior @@ -364,7 +364,7 @@ radio_connection.post_signal(src, signal, range = AIRLOCK_CONTROL_RANGE, radio_filter = RADIO_AIRLOCK) flick("access_button_cycle", src) -/obj/machinery/access_button/allow_pai_interaction(proximity_flag) +/obj/machinery/access_button/allow_pai_interaction(mob/living/silicon/pai/user, proximity_flag) return proximity_flag /obj/machinery/access_button/proc/set_frequency(new_frequency) diff --git a/code/game/machinery/jukebox.dm b/code/game/machinery/jukebox.dm index fec3d3d3d3..bd7f44761c 100644 --- a/code/game/machinery/jukebox.dm +++ b/code/game/machinery/jukebox.dm @@ -252,7 +252,7 @@ /obj/machinery/media/jukebox/attack_hand(var/mob/user as mob) interact(user) -/obj/machinery/media/jukebox/allow_pai_interaction(proximity_flag) +/obj/machinery/media/jukebox/allow_pai_interaction(mob/living/silicon/pai/user, proximity_flag) return proximity_flag /obj/machinery/media/jukebox/proc/explode() diff --git a/code/game/machinery/lightswitch.dm b/code/game/machinery/lightswitch.dm index 933cac4d64..d8809a3485 100644 --- a/code/game/machinery/lightswitch.dm +++ b/code/game/machinery/lightswitch.dm @@ -71,7 +71,7 @@ area.power_change() GLOB.lights_switched_on_roundstat++ -/obj/machinery/light_switch/allow_pai_interaction(proximity_flag) +/obj/machinery/light_switch/allow_pai_interaction(mob/living/silicon/pai/user, proximity_flag) return proximity_flag /obj/machinery/light_switch/power_change() diff --git a/code/game/machinery/newscaster.dm b/code/game/machinery/newscaster.dm index 6d5877effa..de4a915a86 100644 --- a/code/game/machinery/newscaster.dm +++ b/code/game/machinery/newscaster.dm @@ -273,7 +273,7 @@ GLOBAL_LIST_BOILERPLATE(allCasters, /obj/machinery/newscaster) tgui_interact(user) -/obj/machinery/newscaster/allow_pai_interaction(proximity_flag) +/obj/machinery/newscaster/allow_pai_interaction(mob/living/silicon/pai/user, proximity_flag) return proximity_flag /** diff --git a/code/game/objects/items/devices/flipper.dm b/code/game/objects/items/devices/flipper.dm new file mode 100644 index 0000000000..1cd547e3b8 --- /dev/null +++ b/code/game/objects/items/devices/flipper.dm @@ -0,0 +1,112 @@ +/* +* Your Multi-Functional Security Companion +*/ + +// Card +/obj/item/paicard/flipper + name = "Vix" + +/obj/item/paicard/flipper/Initialize(mapload) + emag_act() + pai = new /mob/living/silicon/pai/flipper(src) + . = ..() + desc = "The [name] is a versatile security device designed to protect and empower users in a variety of contexts. With features such as wireless hacking, radio analysis, signal jamming, and physical lock picking, the [name] is the ultimate tool for security professionals, hobbyists, and anyone seeking to better understand and defend against modern threats. Whether you're investigating a security breach, testing your own defenses, or simply curious about the workings of wireless technology, the [name] has you covered." + setEmotion(4) + +// Mob +/mob/living/silicon/pai/flipper + name = "Vix" + chassis_name = PAI_DEFAULT_EMAGGED_CHASSIS + default_pai_card_path = /obj/item/paicard/flipper // Triggers the emag state + +// Vore +/mob/living/silicon/pai/flipper/init_vore(force) + var/obj/belly/B = new /obj/belly/flipper/maw(src) + B.affects_vore_sprites = FALSE + B.emote_lists[DM_HOLD] = list( + "The wet, plush lining of [name]'s maw surrounds you, enveloping you in a soft and slightly sticky embrace. The walls pulse and undulate gently, as if alive, creating a soothing rhythm that lulls you into a sense of calm.", + "You're encased in the warm and humid space of [name]'s maw, the slick surfaces glistening around you. The soft movements of the walls feel almost like the caress of a living creature.", + "The wet, fleshy walls of [name]'s maw seem to pulse and throb in time with your heartbeat, as if they're alive and responding to your presence. The soft and yielding surface wraps around you like a protective cocoon.", + "You feel the gentle pressure of [name]'s maw all around you, the slick and wet surfaces pulsating softly. The sensation is oddly comforting, as if you're being cradled in the embrace of a nurturing being.", + "The warm, moist environment of [name]'s maw surrounds you completely, the walls of flesh glistening and undulating gently. The soft, plush surface seems to mold itself to your form, creating a snug and comfortable space for you to rest in.") + vore_selected = B + + B = new /obj/belly/flipper/throat(src) + B.affects_vore_sprites = FALSE + B.emote_lists[DM_HOLD] = list( + "The narrow passage of [name]'s throat envelops you in its warm embrace, the smooth metallic walls pressing snugly against your body.", + "You feel the intricate machinery of [name]'s throat pulse and undulate around you.", + "As you navigate the cramped yet reassuring space of [name]'s throat, you feel a sense of comfort and safety.", + "[name]'s throat closes tightly around you, cocooning you in its silken embrace as it works to keep you safe and secure.", + "The warm, inviting space of [name]'s throat wraps snugly around you, providing a sense of comfort and reassurance.") + + B = new /obj/belly/flipper/stomach(src) + B.emote_lists[DM_DIGEST] = list( + "The cavernous stomach of the [name] rumbles and churns,.", + "The walls of [name]'s stomach squish and squelch around you, the sophisticated machinery working in perfect harmony.", + "The rhythmic kneading and massaging of [name]'s stomach is both comforting and reassuring.", + "The warm and inviting embrace of [name]'s stomach envelops you in its comforting embrace.", + "[name]'s stomach compresses tightly around you, its powerful systems working aroud you.", + "[name]'s stomach hums and churns around you.") + . = ..() + +//Belly stuff +/obj/belly/flipper + autotransferchance = 50 + autotransferwait = 150 + escapable = 1 + escapechance = 100 + escapetime = 30 + fancy_vore = 1 + contamination_color = "grey" + contamination_flavor = "Wet" + vore_verb = "slurp" + belly_fullscreen_color = "#711e1e" + +/obj/belly/flipper/maw + name = "Maw" + desc = "As you're caught off guard by the sudden movement, The AIs mechanical jaws snap shut to trap you. You find yourself inside the warm and soft mechanical maw. The interior of the maw is lined with a smooth, metallic coating that glints in the light. The tongue, lifts you up against the insides of its metallic teeth and against the ridges of its palate, which are covered in various ports and connectors. It's growled breaths gust from its back hatch, blasting you with warm air and making your body shiver. You can sense the maw is preparing to unleash you into its next stage of operation." + escapechance = 100 + struggle_messages_inside = list( + "You wriggle and wrestle for purchase against the metallic tongue, which lifts and cocoons you tightly between itself and the palate.", + "You try to pry at the interlocked teeth, but they don't budge. The jaws snap shut once more, trapping you inside the maw.", + "You try to wriggle towards the front of the jaws to avoid the next stage of operation. However, the tongue scoops you up and brings you close to the hatch of the back throat.", + "You brace yourself against the metallic mattress of the tongue, pressing your limbs against the roof, and try to force the jaws open. However, your efforts are futile, and their growled breaths gust over you more forcefully.", + "You squirm and struggle, trying to find a way out, but the tongue slithers out to wrap around your limbs, tugging you back inside with a noisy sound of metallic lips.") + autotransferlocation = "Throat" + belly_fullscreen = "a_tumby" + vore_sound = "Insertion1" + +/obj/belly/flipper/throat + name = "Throat" + transferchance = 20 + transferlocation = "Maw" + escapechance = 0 + desc = "You are swallowed,, surrounded by a warm, oily mechanical tunnel. The walls pulsate rhythmically around you, clenching and relaxing in time with the AI's breathing. You feel a sense of claustrophobia as you are forced deeper and deeper into its throat, inching closer to your final destination." + struggle_messages_inside = list( + "You squirm and struggle, trying to push your way back out through the AI's throat. It's no use, however, as the walls continue to squeeze tighter around you.", + "You try to force your way out through the walls of the throat, but they resist your efforts, leaving you trapped inside the metallic chamber.", + "The walls of the throat press tightly against you, leaving you struggling for breath as you're pulled deeper into the AI's body.", + "You strain against the walls of the throat, hoping to force your way out through the metallic chamber. It's no use, however, as they continue to squeeze tighter around you.", + "You writhe and squirm, trying to escape the claustrophobic confines of the AI's throat. It's no use, however, as you're forced ever deeper into the warm, oily tunnel.") + autotransferlocation = "Stomach" + belly_fullscreen = "another_tumby" + vore_sound = "Tauric Swallow" + +/obj/belly/flipper/stomach + name = "Stomach" + escapechance = 0 + transferchance = 10 + transferlocation = "Throat" + desc = "The final destination of your mechanical journey, a warm and soft stomach. After being swallowed, you find yourself immersed in a thick, gooey pool of digestive juices that softly sloshes with each movement of the creature. The walls around you are warm and plush, constantly pulsating with rhythmic undulations that squeeze and massage your entire body. There's no escape from this cozy and comforting chamber, as you feel the gentle yet relentless contractions pushing you deeper into the digestive system. The air is thick and heavy with the scent of acid, which only gets stronger as you sink further down into the stomach. Every now and then, the walls spasm and contract, squeezing the viscous juices all over your body, coating you in their warm and slimy embrace. You can hear the distant gurgles and groans of the digestive system, a reminder of your inescapable fate as its prey." + digest_mode = DM_DIGEST + digest_brute = 0 + digest_burn = 2 + struggle_messages_inside = list( + "You struggle and squirm, but the walls around you are too soft and pliable, yielding to your every movement without any resistance. Every time you try to push your way out, the walls just squeeze you tighter, reminding you that there's no escape from this warm and cozy prison.", + "You search for any signs of an exit, but the walls around you are too thick and plush, absorbing all of your movements without a trace. You're trapped here, at the mercy of their digestive system, slowly melting away in its warm and slimy embrace.", + "You press your limbs and body against the soft walls, trying to push your way out. But the walls just close in around you, enveloping you in a tight and suffocating hug. You can feel the warm and slimy digestive juices slowly dissolving your flesh, as you become one with the creature that swallowed you.", + "You thrash and flail, trying to break free from the soft and pliable walls that surround you. But every movement just makes the stomach walls pulsate and squeeze tighter around you, coaxing you deeper into the creature's digestive system.", + "You try to resist the relentless contractions of the stomach walls, but they're too warm and plush, too soft and pliable. Every movement you make just sends ripples of pleasure throughout the creature's digestive system, making it squeeze and massage you even harder.") + belly_fullscreen = "da_tumby" + vore_sound = "Stomach Move" diff --git a/code/game/objects/items/devices/paicard.dm b/code/game/objects/items/devices/paicard.dm index 236157dbb2..a438bb7ad9 100644 --- a/code/game/objects/items/devices/paicard.dm +++ b/code/game/objects/items/devices/paicard.dm @@ -33,7 +33,17 @@ var/special_handling = FALSE var/selected_pai -/obj/item/paicard/relaymove(var/mob/user, var/direction) + // Special modules + var/emagged = FALSE + var/has_emag_toolkit = TRUE + var/obj/item/multitool/multitool + var/obj/item/assembly/signaler/signaler + + //Currently selected SubSystem + var/static/list/systems_list = list("pAI","MultiTool","Emag","Signaler") + var/selected_system = "pAI" + +/obj/item/paicard/relaymove(mob/user, direction) if(user.stat || user.stunned) return var/obj/item/rig/rig = src.get_rig() @@ -49,9 +59,11 @@ if(!QDELETED(pai)) // Either the pai or card could be deleted first, prevent a loop pai.death(0) QDEL_NULL(radio) + QDEL_NULL(multitool) + QDEL_NULL(signaler) return ..() -/obj/item/paicard/attack_ghost(mob/user as mob) +/obj/item/paicard/attack_ghost(mob/user) if(pai) //Have a person in them already? return ..() if(is_damage_critical()) @@ -116,9 +128,9 @@ "selected_pai_data" = null, "available_pais" = null, "waiting_for_response" = in_use, + "emag_systems" = null, ) - if(pai) // Only set pai data if we have one data["active_pai_data"] = get_active_data() return data @@ -137,6 +149,13 @@ "radio_recieve" = radio.listening, ) + var/list/emag_data + if(emagged && has_emag_toolkit) // Special pAI tools + emag_data = list( + "emag_systems" = systems_list, + "selected_system" = selected_system + ) + var/datum/asset/spritesheet_batched/pai_icons/spritesheet = get_asset_datum(/datum/asset/spritesheet_batched/pai_icons) var/datum/pai_sprite/sprite_datum = SSpai.chassis_data(pai.chassis_name) var/css_class = sanitize_css_class_name("[sprite_datum.type]") @@ -153,6 +172,7 @@ "radio_data" = radio_data, "sprite_datum_class" = css_class, "sprite_datum_size" = spritesheet.icon_size_id(css_class + "S"), // just get the south icon's size, the rest will be the same + "emag_data" = emag_data, ) /obj/item/paicard/tgui_act(action, list/params, datum/tgui/ui, datum/tgui_state/state) @@ -275,6 +295,43 @@ selected_pai = null return TRUE + if("select_tool") // Emag tools + if(!emagged || !has_emag_toolkit) + return FALSE + var/new_tool = params["tool"] + if(!(new_tool in systems_list)) + return FALSE + selected_system = new_tool + return TRUE + + if("activate_tool") // Emag tools + if(!emagged || !has_emag_toolkit || !selected_system) + return FALSE + switch(selected_system) + if("MultiTool") + multitool.attack_self(ui.user) + return TRUE + if("Signaler") + signaler.attack_self(ui.user) + return TRUE + return FALSE + +/obj/item/paicard/pre_attack(atom/A, mob/user, params) + if(emagged && has_emag_toolkit) + // Perform builtin tool actions if we have a emag system selected + switch(selected_system) + if("Emag") + if(istype(A,/obj/machinery/door)) + return TRUE //for doors use the doorjack + return A.emag_act(1,user,src) + if("MultiTool") + A.attackby(multitool,user) + return TRUE + if("Signaler") + A.attackby(signaler,user) + return TRUE + . = ..() + /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)]")) @@ -286,7 +343,7 @@ pai = null setEmotion(16) -/obj/item/paicard/proc/setEmotion(var/emotion) +/obj/item/paicard/proc/setEmotion(emotion) cut_overlays() qdel(screen_layer) screen_layer = null @@ -680,6 +737,23 @@ return TRUE return FALSE +/obj/item/paicard/emag_act(remaining_charges, mob/user, emag_source) + . = ..() + if(!pai) + if(!emagged) + to_chat(user, span_warning("Without a pAI inhabiting \the [src] nothing happens.")) + return + if(!emagged) + if(user) + to_chat(user, span_notice("\The [src] buzzes and beeps.")) + playsound(src, 'sound/machines/buzzbeep.ogg', 50, 0) + emagged = TRUE + // Add tools + if(has_emag_toolkit) + multitool = new /obj/item/multitool(src) + signaler = new /obj/item/assembly/signaler(src) + return 1 + /////////////////////////////// //////////pAI Parts ////////// /////////////////////////////// @@ -780,7 +854,7 @@ icon_state = "radio" loudspeaker = FALSE -/obj/item/radio/borg/pai/attackby(obj/item/W as obj, mob/user as mob) +/obj/item/radio/borg/pai/attackby(obj/item/W, mob/user) return /obj/item/radio/borg/pai/recalculateChannels() diff --git a/code/game/objects/objs.dm b/code/game/objects/objs.dm index 1936cf9040..0e647d87fd 100644 --- a/code/game/objects/objs.dm +++ b/code/game/objects/objs.dm @@ -145,7 +145,7 @@ return // If returns true, pai can interact with the object with a click -/obj/proc/allow_pai_interaction(proximity_flag) +/obj/proc/allow_pai_interaction(mob/living/silicon/pai/user, proximity_flag) return FALSE //To be called from things that spill objects on the floor. diff --git a/code/game/objects/structures/crates_lockers/__closets.dm b/code/game/objects/structures/crates_lockers/__closets.dm index 54632231e0..640d898014 100644 --- a/code/game/objects/structures/crates_lockers/__closets.dm +++ b/code/game/objects/structures/crates_lockers/__closets.dm @@ -539,7 +539,7 @@ M.Translate(closet_appearance.door_hinge, 0) return M -/obj/structure/closet/allow_pai_interaction(proximity_flag) +/obj/structure/closet/allow_pai_interaction(mob/living/silicon/pai/user, proximity_flag) return proximity_flag //verb to eat people in the same closet as yourself diff --git a/code/game/objects/structures/fence.dm b/code/game/objects/structures/fence.dm index 298e5e33eb..a02c38b29c 100644 --- a/code/game/objects/structures/fence.dm +++ b/code/game/objects/structures/fence.dm @@ -211,7 +211,7 @@ if(get_dist(user,src) <= 1) //not remotely though return attack_hand(user) -/obj/structure/fence/door/allow_pai_interaction(proximity_flag) +/obj/structure/fence/door/allow_pai_interaction(mob/living/silicon/pai/user, proximity_flag) return proximity_flag /obj/structure/fence/door/proc/toggle(mob/user) diff --git a/code/game/objects/structures/watercloset.dm b/code/game/objects/structures/watercloset.dm index dd511a7e6c..45ec2b934d 100644 --- a/code/game/objects/structures/watercloset.dm +++ b/code/game/objects/structures/watercloset.dm @@ -201,7 +201,7 @@ 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) +/obj/machinery/shower/allow_pai_interaction(mob/living/silicon/pai/user, proximity_flag) return proximity_flag /obj/machinery/shower/click_alt(mob/user) diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm index 979d75a7c6..cc30b31c79 100644 --- a/code/modules/admin/admin_verbs.dm +++ b/code/modules/admin/admin_verbs.dm @@ -310,7 +310,7 @@ ADMIN_VERB(admin_give_modifier, R_EVENT, "Give Modifier", "Makes a mob weaker or living_target.add_modifier(new_modifier_type, duration) log_and_message_admins("has given [key_name(living_target)] the modifer [new_modifier_type], with a duration of [duration ? "[duration / 600] minutes" : "forever"].", user) -ADMIN_VERB(make_sound, R_FUN, "Make Sound", "Display a message to everyone who can hear the target.", ADMIN_CATEGORY_FUN_SOUNDS, obj/target_object in world) +ADMIN_VERB_AND_CONTEXT_MENU(make_sound, R_FUN, "Make Sound", "Display a message to everyone who can hear the target.", ADMIN_CATEGORY_FUN_SOUNDS, obj/target_object in world) if(!target_object) return diff --git a/code/modules/admin/player_effects.dm b/code/modules/admin/player_effects.dm index e43fdc47af..029cdb7b08 100644 --- a/code/modules/admin/player_effects.dm +++ b/code/modules/admin/player_effects.dm @@ -1,5 +1,5 @@ -ADMIN_VERB(player_effects, R_FUN, "Player Effects", "Modify a player character with various 'special treatments' from a list.", ADMIN_CATEGORY_FUN_EVENT_KIT, mob/target in GLOB.mob_list) +ADMIN_VERB_AND_CONTEXT_MENU(player_effects, R_FUN, "Player Effects", "Modify a player character with various 'special treatments' from a list.", ADMIN_CATEGORY_FUN_EVENT_KIT, mob/target in GLOB.mob_list) var/datum/eventkit/player_effects/spawner = new() spawner.target = target spawner.tgui_interact(user.mob) diff --git a/code/modules/admin/verbs/adminjump.dm b/code/modules/admin/verbs/adminjump.dm index 4d56c1e89a..255ebbf4c4 100644 --- a/code/modules/admin/verbs/adminjump.dm +++ b/code/modules/admin/verbs/adminjump.dm @@ -36,7 +36,7 @@ message_admins("[key_name_admin(src)] jumped to [target_area]") feedback_add_details("admin_verb","JA") //If you are copy-pasting this, ensure the 2nd parameter is unique to the new proc! -ADMIN_VERB(jumptoturf, R_ADMIN|R_MOD|R_DEBUG|R_EVENT, "Jump to Turf", "Jump to a specific turf in the game.", ADMIN_CATEGORY_GAME, turf/T in world) +ADMIN_VERB_AND_CONTEXT_MENU(jumptoturf, R_ADMIN|R_MOD|R_DEBUG|R_EVENT, "Jump to Turf", "Jump to a specific turf in the game.", ADMIN_CATEGORY_GAME, turf/T in world) if(CONFIG_GET(flag/allow_admin_jump)) log_admin("[key_name(user)] jumped to [T.x],[T.y],[T.z] in [T.loc]") message_admins("[key_name_admin(user)] jumped to [T.x],[T.y],[T.z] in [T.loc]") diff --git a/code/modules/instruments/stationary.dm b/code/modules/instruments/stationary.dm index 71f4bb930c..3ceacfa154 100644 --- a/code/modules/instruments/stationary.dm +++ b/code/modules/instruments/stationary.dm @@ -40,7 +40,7 @@ /obj/structure/musician/tgui_interact(mob/user) return song.tgui_interact(user) -/obj/structure/musician/allow_pai_interaction(proximity_flag) +/obj/structure/musician/allow_pai_interaction(mob/living/silicon/pai/user, proximity_flag) return proximity_flag /* FIXME diff --git a/code/modules/library/wikicomp.dm b/code/modules/library/wikicomp.dm index 83c85d0333..03f23b7dbc 100644 --- a/code/modules/library/wikicomp.dm +++ b/code/modules/library/wikicomp.dm @@ -33,6 +33,9 @@ tgui_interact(user) playsound(src, "keyboard", 40) // into console +/obj/machinery/librarywikicomp/allow_pai_interaction() + return TRUE + /obj/machinery/librarywikicomp/tgui_interact(mob/user, datum/tgui/ui) ui = SStgui.try_update_ui(user, src, ui) if(!ui) diff --git a/code/modules/mob/living/silicon/pai/chassis_ui.dm b/code/modules/mob/living/silicon/pai/chassis_ui.dm index 4624ed60f7..be3a47f30d 100644 --- a/code/modules/mob/living/silicon/pai/chassis_ui.dm +++ b/code/modules/mob/living/silicon/pai/chassis_ui.dm @@ -13,20 +13,23 @@ get_asset_datum(/datum/asset/spritesheet_batched/pai_icons), ) -/datum/tgui_module/pai_chassis/tgui_static_data() +/datum/tgui_module/pai_chassis/tgui_static_data(mob/user) var/list/data = ..() var/list/available_sprites = list() + var/mob/living/silicon/pai/pai_host = host 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" + if(current_sprite.emagged && (!pai_host.card.emagged || !pai_host.card.has_emag_toolkit)) + continue 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() +/datum/tgui_module/pai_chassis/tgui_data(mob/user, datum/tgui/ui, datum/tgui_state/state) var/list/data = ..() var/mob/living/silicon/pai/pai_host = host diff --git a/code/modules/mob/living/silicon/pai/pai.dm b/code/modules/mob/living/silicon/pai/pai.dm index 0a463d9d53..89e8f13e18 100644 --- a/code/modules/mob/living/silicon/pai/pai.dm +++ b/code/modules/mob/living/silicon/pai/pai.dm @@ -22,6 +22,8 @@ var/ram = 100 // Used as currency to purchase different abilities var/list/software = list() var/userDNA // The DNA string of our assigned user + + var/default_pai_card_path = /obj/item/paicard // Used when the pai is spawned directly by mapping or admin var/obj/item/paicard/card // The card we inhabit var/obj/item/radio/borg/pai/radio // Our primary radio var/obj/item/communicator/integrated/communicator // Our integrated communicator. @@ -106,7 +108,7 @@ card = loc if(!istype(card)) - card = new(src) + card = new default_pai_card_path(src) card.pai = src sradio = new(src) @@ -131,6 +133,7 @@ add_verb(src, /mob/living/proc/dominate_prey) add_verb(src, /mob/living/proc/set_size) add_verb(src, /mob/living/proc/shred_limb) + add_verb(src, /mob/living/proc/toggle_trash_catching) remove_verb(src, /mob/verb/toggle_gun_mode) // Pai doesn't have support for this and shouldn't be able to use guns anyway @@ -144,6 +147,9 @@ if(M) M.toff = FALSE + if(chassis_name != PAI_DEFAULT_CHASSIS) // For subtypes that override base chassis( like the syndi pet pai ) + internal_set_chassis( SSpai.chassis_data(chassis_name)) + /mob/living/silicon/pai/Login() . = ..() if(!holo_icon_south) @@ -218,26 +224,55 @@ /mob/living/silicon/pai/proc/change_chassis(new_chassis) if(!(new_chassis in SSpai.get_chassis_list())) new_chassis = PAI_DEFAULT_CHASSIS + var/datum/pai_sprite/chassis_data = SSpai.chassis_data(chassis_name) + if(chassis_data.emagged && !src.card.emagged) + return chassis_name = new_chassis + // Get icon data setup + if(chassis_data.holo_projector) + internal_set_holoprojection(chassis_data) + else + internal_set_chassis(chassis_data) + +/// Rebuild holosprite from character save slot +/mob/living/silicon/pai/proc/internal_set_holoprojection(datum/pai_sprite/chassis_data) + SHOULD_NOT_OVERRIDE(TRUE) + PRIVATE_PROC(TRUE) + + //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) + if(!holo_icon_south) + get_character_icon() + + update_icon() + resize(oursize, FALSE, TRUE, TRUE, FALSE) //And then back again now that we're sure the vis_height is correct. + post_chassis_change(chassis_data) + +/// Use data from our sprite datum to set icon +/mob/living/silicon/pai/proc/internal_set_chassis(datum/pai_sprite/chassis_data) + SHOULD_NOT_OVERRIDE(TRUE) + PRIVATE_PROC(TRUE) + //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 + 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 + + update_icon() + resize(oursize, FALSE, TRUE, TRUE, FALSE) //And then back again now that we're sure the vis_height is correct. + post_chassis_change(chassis_data) + +/mob/living/silicon/pai/proc/post_chassis_change(datum/pai_sprite/chassis_data) + SHOULD_NOT_OVERRIDE(TRUE) + PRIVATE_PROC(TRUE) // Drops you if you change to a non-flying chassis if(chassis_data.flying) @@ -255,10 +290,6 @@ 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 ////////////////////////////////////////////////////////////////////////////////////////////////// @@ -287,9 +318,6 @@ 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) @@ -297,22 +325,26 @@ else visible_message(span_warning("[user.name] bonks [src] harmlessly with [W].")) spawn(1) - if(stat != DEAD) close_up() + 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.")) + visible_message(span_notice("\The [user] pats \the [src].")) + return + if(user.a_intent == I_DISARM) + visible_message(span_danger("\The [user] boops \the [src] on the head.")) close_up() + return + . = ..() /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)) + if(istype(O) && O.allow_pai_interaction(src, proximity_flag)) O.attack_hand(src) return @@ -333,6 +365,10 @@ to_chat(src,span_warning("A firewall prevents you from interfacing with this device!")) return + if(istype(A,/obj/item/modular_computer)) + to_chat(src,span_warning("Anti-tamper locks prevents you from interfacing with this device! You need your master's permission before going online!")) + return + if(!ismob(A) || A == src) return diff --git a/code/modules/mob/living/silicon/pai/sprites/_sprite_datum.dm b/code/modules/mob/living/silicon/pai/sprites/_sprite_datum.dm index c1425a2ba9..7fb4f89031 100644 --- a/code/modules/mob/living/silicon/pai/sprites/_sprite_datum.dm +++ b/code/modules/mob/living/silicon/pai/sprites/_sprite_datum.dm @@ -20,6 +20,8 @@ var/icon_y = 32 var/vis_height = 32 + var/emagged = FALSE + // PAI sprites /datum/pai_sprite/drone name = PAI_DEFAULT_CHASSIS @@ -155,6 +157,15 @@ sprite_icon_state = "type13" holo_eyes_icon_state = "type13-eyes" +/datum/pai_sprite/syndi_fox + name = PAI_DEFAULT_EMAGGED_CHASSIS + sprite_icon = 'icons/mob/pai.dmi' + sprite_icon_state = "syndifox" + resting_belly = FALSE + has_eye_sprites = FALSE + belly_states = 0 + emagged = TRUE + // Large pai sprites /datum/pai_sprite/large pixel_x = -16 diff --git a/code/modules/modular_computers/computers/modular_computer/interaction.dm b/code/modules/modular_computers/computers/modular_computer/interaction.dm index 0533bca66d..ebda912bed 100644 --- a/code/modules/modular_computers/computers/modular_computer/interaction.dm +++ b/code/modules/modular_computers/computers/modular_computer/interaction.dm @@ -109,7 +109,7 @@ return attack_self(user) /obj/item/modular_computer/attack_hand(var/mob/user) - if(anchored) + if(anchored || ispAI(user)) return attack_self(user) return ..() @@ -212,3 +212,10 @@ return ..() + +/obj/item/modular_computer/allow_pai_interaction(mob/living/silicon/pai/user, proximity_flag) + if(!card_slot?.stored_card?.dna_hash || !user.master_dna) + return FALSE + if(card_slot.stored_card.dna_hash != user.master_dna) + return FALSE + return proximity_flag diff --git a/code/modules/power/sensors/sensor_monitoring.dm b/code/modules/power/sensors/sensor_monitoring.dm index 2def9f56a1..f8bc9728ec 100644 --- a/code/modules/power/sensors/sensor_monitoring.dm +++ b/code/modules/power/sensors/sensor_monitoring.dm @@ -49,7 +49,7 @@ return tgui_interact(user) -/obj/machinery/computer/power_monitor/allow_pai_interaction(proximity_flag) +/obj/machinery/computer/power_monitor/allow_pai_interaction(mob/living/silicon/pai/user, proximity_flag) return proximity_flag // Uses dark magic to operate the NanoUI of this computer. diff --git a/code/modules/recycling/conveyor2.dm b/code/modules/recycling/conveyor2.dm index 37321fadbd..dd95156982 100644 --- a/code/modules/recycling/conveyor2.dm +++ b/code/modules/recycling/conveyor2.dm @@ -342,7 +342,7 @@ to_chat(user, "You adjust the speed of the conveyor switch") return -/obj/machinery/conveyor_switch/allow_pai_interaction(proximity_flag) +/obj/machinery/conveyor_switch/allow_pai_interaction(mob/living/silicon/pai/user, proximity_flag) return proximity_flag /obj/machinery/conveyor_switch/oneway diff --git a/code/modules/recycling/disposal_machines.dm b/code/modules/recycling/disposal_machines.dm index 7a727e316c..56e70b5148 100644 --- a/code/modules/recycling/disposal_machines.dm +++ b/code/modules/recycling/disposal_machines.dm @@ -170,7 +170,7 @@ user.visible_message("[user] places \the [I] into the [src].", "You place \the [I] into the [src].","Ca-Clunk") update_icon() -/obj/machinery/disposal/allow_pai_interaction(proximity_flag) +/obj/machinery/disposal/allow_pai_interaction(mob/living/silicon/pai/user, proximity_flag) return proximity_flag // Transform into next machine type diff --git a/code/modules/resleeving/sleevecard.dm b/code/modules/resleeving/sleevecard.dm index 645d1a5f4e..3b46fbfa8c 100644 --- a/code/modules/resleeving/sleevecard.dm +++ b/code/modules/resleeving/sleevecard.dm @@ -4,12 +4,12 @@ catalogue_data = list(/datum/category_item/catalogue/technology/resleeving) origin_tech = list(TECH_DATA = 2) show_messages = 0 - var/emagged = FALSE matter = list(MAT_STEEL = 4000, MAT_GLASS = 4000) + has_emag_toolkit = FALSE // sleevecards don't have multitools or signalers, you can just change their laws special_handling = TRUE /obj/item/paicard/sleevecard/attack_ghost(mob/user as mob) - return + return // No ghosts can invite, these are intended for sleevemates only /obj/item/paicard/sleevecard/attackby(var/obj/item/I as obj, mob/user as mob) if(istype(I,/obj/item/sleevemate)) @@ -95,6 +95,8 @@ pda.owner = text("[]", src) pda.name = pda.owner + " (" + pda.ownjob + ")" + default_language = GLOB.all_languages[LANGUAGE_GALCOM] // Same issue as bots + /mob/living/silicon/pai/infomorph/tgui_data(mob/user, datum/tgui/ui, datum/tgui_state/state) var/list/data = ..() diff --git a/code/modules/tgui/modules/_base.dm b/code/modules/tgui/modules/_base.dm index cd3e98153c..fbee6c1415 100644 --- a/code/modules/tgui/modules/_base.dm +++ b/code/modules/tgui/modules/_base.dm @@ -62,7 +62,7 @@ Code is pretty much ripped verbatim from nano modules, but with un-needed stuff return 0 -/datum/tgui_module/tgui_static_data() +/datum/tgui_module/tgui_static_data(mob/user) . = ..() var/obj/item/modular_computer/host = tgui_host() diff --git a/code/modules/tgui/states/default.dm b/code/modules/tgui/states/default.dm index b5737eee3d..3fb40fd23c 100644 --- a/code/modules/tgui/states/default.dm +++ b/code/modules/tgui/states/default.dm @@ -74,7 +74,7 @@ GLOBAL_DATUM_INIT(tgui_default_state, /datum/tgui_state/default, new) /mob/living/silicon/pai/default_can_use_tgui_topic(src_object) // Allows few objects... var/obj/check_obj = src_object - if(!stat && istype(check_obj) && check_obj.allow_pai_interaction(get_dist(check_obj, src) <= 1)) + if(!stat && istype(check_obj) && check_obj.allow_pai_interaction(src, get_dist(check_obj, src) <= 1)) return STATUS_INTERACTIVE // ...otherwise pAIs can only use themselves and the owner's radio. if((src_object == src || src_object == radio || src_object == communicator) && !stat) diff --git a/code/modules/turbolift/turbolift_console.dm b/code/modules/turbolift/turbolift_console.dm index 9b74ee66e7..7bef222a4c 100644 --- a/code/modules/turbolift/turbolift_console.dm +++ b/code/modules/turbolift/turbolift_console.dm @@ -97,7 +97,7 @@ return lift.queue_move_to(floor) -/obj/structure/lift/button/allow_pai_interaction(proximity_flag) +/obj/structure/lift/button/allow_pai_interaction(mob/living/silicon/pai/user, proximity_flag) return proximity_flag /obj/structure/lift/button/proc/light_up() @@ -143,7 +143,7 @@ /obj/structure/lift/panel/attack_ghost(var/mob/user) return interact(user) -/obj/structure/lift/panel/allow_pai_interaction(proximity_flag) +/obj/structure/lift/panel/allow_pai_interaction(mob/living/silicon/pai/user, proximity_flag) return proximity_flag /obj/structure/lift/panel/interact(var/mob/user) diff --git a/icons/mob/pai.dmi b/icons/mob/pai.dmi index 1e377affd3..65823d051a 100644 Binary files a/icons/mob/pai.dmi and b/icons/mob/pai.dmi differ diff --git a/tgui/packages/tgui/interfaces/PAICard/PAIActiveCompanion.tsx b/tgui/packages/tgui/interfaces/PAICard/PAIActiveCompanion.tsx index 656ceaea8a..d42311268b 100644 --- a/tgui/packages/tgui/interfaces/PAICard/PAIActiveCompanion.tsx +++ b/tgui/packages/tgui/interfaces/PAICard/PAIActiveCompanion.tsx @@ -3,17 +3,19 @@ import { useBackend } from 'tgui/backend'; import { Box, Button, + Dropdown, LabeledList, Section, Stack, TextArea, } from 'tgui-core/components'; +import { configTools } from './constants'; import { healthToColor } from './functions'; import { PaiIcon } from './PaiIcon'; -import type { ActivePAIData } from './types'; +import type { ActivePAIData, Data, RadioData } from './types'; export const PAIActiveCompanion = (props: { activeData: ActivePAIData }) => { - const { act } = useBackend(); + const { data, act } = useBackend(); const [newDirective, setNewDirective] = useState(''); const { activeData } = props; @@ -30,6 +32,7 @@ export const PAIActiveCompanion = (props: { activeData: ActivePAIData }) => { radio_data, sprite_datum_class, sprite_datum_size, + emag_data, } = activeData; return ( @@ -54,7 +57,7 @@ export const PAIActiveCompanion = (props: { activeData: ActivePAIData }) => { + } + > + + + + act('select_tool', { tool: value }) + } + options={emag_data.emag_systems} + selected={emag_data.selected_system} + /> + + + + + )} - +
{ buttons={ - + Clear