From 081a804b4436bcdae127a46df1ff06ab99c2803b Mon Sep 17 00:00:00 2001 From: Iajret <8430839+Iajret@users.noreply.github.com> Date: Wed, 9 Apr 2025 17:43:54 +0300 Subject: [PATCH] Adds silicon access and built-in radio to AI Uplink Brain (only when controlled by AI) (#90112) ## About The Pull Request - adds silicon access to body controlled by an AI. This is done just by adding a trait - adds internal headset with same channels as AI has in its core (including syndicate and special channels for special AIs) - binary channel required some additional code to work properly for carbons without binary key so I decided to also fix big bold text for borg shells and for big brother. They all have big texts in binary. This is done by adding new `TRAIT_LOUD_BINARY` - turns out borg shells also lack loud mode for regular radio, so it is added too - ai also locks bodies for themselves the same way as they do with shells, for consistency ## Why It's Good For The Game While AIs losing their ability to listen radio when inside uplink brain can provide so much needed human VS ai conflict (because AI ignored open door request from that one assistant), it feels a bit unjustified. Same reason for AI lacking its innate all-access while in carbon body. You can still open doors for yourself by juggling between uplink brain and your core so why bother people with additional clicks. It brings carbon shell on par with silicon shells (and makes them superior cough cough) ## Changelog :cl: balance: AI uplink brain now comes with built-in radio and free silicon access for the AI to use balance: AI entering uplink brain now locks it for themselves. You can reinsert that brain to reset its controlling AI fix: fixed AI borg shells and big brother not having loud binary. Now they do. /:cl: --- code/__DEFINES/traits/declarations.dm | 2 ++ code/_globalvars/traits/_traits.dm | 1 + .../objects/items/devices/radio/headset.dm | 8 +++++ .../mining_loot/megafauna/the_thing.dm | 36 ++++++++++++++++++- .../mob/living/carbon/human/human_say.dm | 13 ++++--- code/modules/mob/living/silicon/ai/ai.dm | 3 +- .../modules/mob/living/silicon/robot/robot.dm | 2 ++ .../modules/mob/living/silicon/silicon_say.dm | 8 ++++- 8 files changed, 65 insertions(+), 8 deletions(-) diff --git a/code/__DEFINES/traits/declarations.dm b/code/__DEFINES/traits/declarations.dm index a2af9b51277..77bba8dd65f 100644 --- a/code/__DEFINES/traits/declarations.dm +++ b/code/__DEFINES/traits/declarations.dm @@ -1185,6 +1185,8 @@ Remember to update _globalvars/traits.dm if you're adding/removing/renaming trai ///The entity has AI 'access', so is either an AI, has an access wand, or is an admin ghost AI. Used to block off regular Silicons from things. ///This is put on the mob, it is used on the client for Admins but they are the exception as they use `isAdminGhostAI`. #define TRAIT_AI_ACCESS "ai_access_trait" +///The entity should have `SPAN_COMMAND` in binary the same way as AI does. +#define TRAIT_LOUD_BINARY "loud_binary_trait" ///The entity is able to receive a tracking button in chat #define TRAIT_CAN_GET_AI_TRACKING_MESSAGE "can_get_ai_tracking_message" diff --git a/code/_globalvars/traits/_traits.dm b/code/_globalvars/traits/_traits.dm index 3da1f1436b0..0f86185324c 100644 --- a/code/_globalvars/traits/_traits.dm +++ b/code/_globalvars/traits/_traits.dm @@ -342,6 +342,7 @@ GLOBAL_LIST_INIT(traits_by_type, list( "TRAIT_LIMBATTACHMENT" = TRAIT_LIMBATTACHMENT, "TRAIT_LITERATE" = TRAIT_LITERATE, "TRAIT_LIVERLESS_METABOLISM" = TRAIT_LIVERLESS_METABOLISM, + "TRAIT_LOUD_BINARY" = TRAIT_LOUD_BINARY, "TRAIT_MADNESS_IMMUNE" = TRAIT_MADNESS_IMMUNE, "TRAIT_MAFIAINITIATE" = TRAIT_MAFIAINITIATE, "TRAIT_MAGICALLY_GIFTED" = TRAIT_MAGICALLY_GIFTED, diff --git a/code/game/objects/items/devices/radio/headset.dm b/code/game/objects/items/devices/radio/headset.dm index 7a17329648a..a90cdefe04f 100644 --- a/code/game/objects/items/devices/radio/headset.dm +++ b/code/game/objects/items/devices/radio/headset.dm @@ -411,6 +411,14 @@ GLOBAL_LIST_INIT(channel_tokens, list( keyslot2 = new /obj/item/encryptionkey/ai_with_binary command = TRUE +/obj/item/radio/headset/silicon/human_ai/equipped(mob/user, slot, initial) + . = ..() + ADD_TRAIT(user, TRAIT_LOUD_BINARY, REF(src)) + +/obj/item/radio/headset/silicon/human_ai/dropped(mob/user, slot, initial) + . = ..() + REMOVE_TRAIT(user, TRAIT_LOUD_BINARY, REF(src)) + /obj/item/radio/headset/silicon/ai/evil name = "\proper Evil Integrated Subspace Transceiver" keyslot2 = new /obj/item/encryptionkey/ai/evil diff --git a/code/modules/mining/lavaland/mining_loot/megafauna/the_thing.dm b/code/modules/mining/lavaland/mining_loot/megafauna/the_thing.dm index 309cdba355d..eefd66ca35b 100644 --- a/code/modules/mining/lavaland/mining_loot/megafauna/the_thing.dm +++ b/code/modules/mining/lavaland/mining_loot/megafauna/the_thing.dm @@ -18,8 +18,12 @@ can_smoothen_out = FALSE /// if connected, our AI var/mob/living/silicon/ai/mainframe + /// owner AI of this body (resets in on_mob_remove) + var/mob/living/silicon/ai/connected_ai /// action for undeployment var/datum/action/innate/brain_undeployment/undeployment_action = new + /// Weakref to our imaginary brain radio implant + var/datum/weakref/radio_weakref /obj/item/organ/brain/cybernetic/ai/Initialize(mapload) . = ..() @@ -42,12 +46,21 @@ RegisterSignals(brain_owner, list(COMSIG_QDELETING, COMSIG_LIVING_PRE_WABBAJACKED), PROC_REF(undeploy)) RegisterSignal(brain_owner, COMSIG_CARBON_GAIN_ORGAN, PROC_REF(on_organ_gain)) + var/obj/item/implant/radio/radio = new(owner) + radio.implant(owner, null, TRUE, TRUE) + radio_weakref = WEAKREF(radio) + /obj/item/organ/brain/cybernetic/ai/on_mob_remove(mob/living/carbon/organ_owner, special, movement_flags) undeploy() . = ..() organ_owner.remove_traits(list(HUMAN_SENSORS_VISIBLE_WITHOUT_SUIT, TRAIT_NO_MINDSWAP, TRAIT_CORPSELOCKED), REF(src)) UnregisterSignal(organ_owner, list(COMSIG_LIVING_HEALTH_UPDATE, COMSIG_CLICK, COMSIG_MOB_GET_STATUS_TAB_ITEMS, COMSIG_MOB_MIND_BEFORE_MIDROUND_ROLL, COMSIG_QDELETING, COMSIG_LIVING_PRE_WABBAJACKED)) + var/obj/item/implant/radio/radio = radio_weakref.resolve() + if(radio) + QDEL_NULL(radio) + connected_ai = null + /obj/item/organ/brain/cybernetic/ai/proc/cancel_rolls(mob/living/source, datum/mind/mind, datum/antagonist/antagonist) SIGNAL_HANDLER if(ispath(antagonist, /datum/antagonist/malf_ai)) @@ -88,6 +101,8 @@ lines += "Estimated organic/inorganic integrity: [owner.health]" if(mainframe) lines += span_warning("Already occupied by another digital entity.") + else if(connected_ai && connected_ai != user) + lines += span_warning("Uplink is locked by another digital entity.") else if(!is_sufficiently_augmented()) lines += span_warning("Organic organs detected. Robotic organs only, cannot take over.") else @@ -118,14 +133,27 @@ ADD_TRAIT(AI.mind, TRAIT_UNCONVERTABLE, REF(src)) ADD_TRAIT(AI, TRAIT_MIND_TEMPORARILY_GONE, REF(src)) AI.mind.transfer_to(owner) + to_chat(owner, span_boldbig("You are still considered a silicon/cyborg/AI. Follow your laws.")) /obj/item/organ/brain/cybernetic/ai/proc/deploy_init(mob/living/silicon/ai/AI) //todo camera maybe mainframe = AI + connected_ai = AI RegisterSignal(AI, COMSIG_QDELETING, PROC_REF(ai_deleted)) undeployment_action.Grant(owner) update_med_hud_status(owner) - to_chat(owner, span_boldbig("You are still considered a silicon/cyborg/AI. Follow your laws.")) + + owner.add_traits(list(TRAIT_SILICON_ACCESS, TRAIT_LOUD_BINARY), REF(src)) + + var/obj/item/implant/radio/implant = radio_weakref.resolve() + if(implant.radio && AI.radio) + if((AI.radio.special_channels & RADIO_SPECIAL_SYNDIE)) + implant.radio.make_syndie() + implant.radio.subspace_transmission = TRUE + implant.radio.command = TRUE + implant.radio.channels = AI.radio.channels + for(var/channel in implant.radio.channels) + implant.radio.secure_radio_connections[channel] = add_radio(implant.radio, GLOB.radiochannels[channel]) /obj/item/organ/brain/cybernetic/ai/proc/undeploy(datum/source) SIGNAL_HANDLER @@ -144,6 +172,12 @@ mainframe.eyeobj.setLoc(loc) REMOVE_TRAIT(mainframe.mind, TRAIT_UNCONVERTABLE, REF(src)) REMOVE_TRAIT(mainframe, TRAIT_MIND_TEMPORARILY_GONE, REF(src)) + owner.remove_traits(list(TRAIT_SILICON_ACCESS, TRAIT_LOUD_BINARY), REF(src)) // we don't want randoms using our body as free AA, so we only have it when we active. + + var/obj/item/implant/radio/implant = radio_weakref.resolve() + if(implant) + implant.radio.resetChannels() + mainframe = null update_med_hud_status(owner) diff --git a/code/modules/mob/living/carbon/human/human_say.dm b/code/modules/mob/living/carbon/human/human_say.dm index 2908ae52364..d2cae392447 100644 --- a/code/modules/mob/living/carbon/human/human_say.dm +++ b/code/modules/mob/living/carbon/human/human_say.dm @@ -65,15 +65,18 @@ return special_voice /mob/living/carbon/human/binarycheck() - if(stat >= SOFT_CRIT || !ears) - return FALSE - var/obj/item/radio/headset/dongle = ears - if(!istype(dongle)) + if(stat >= SOFT_CRIT) return FALSE var/area/our_area = get_area(src) if(our_area.area_flags & BINARY_JAMMING) return FALSE - return (dongle.special_channels & RADIO_SPECIAL_BINARY) + var/obj/item/organ/brain/cybernetic/ai/brain = get_organ_slot(ORGAN_SLOT_BRAIN) + if(istype(brain)) + return TRUE + var/obj/item/radio/headset/dongle = ears + if(!istype(dongle)) + return FALSE + return dongle.special_channels & RADIO_SPECIAL_BINARY /mob/living/carbon/human/radio(message, list/message_mods = list(), list/spans, language) //Poly has a copy of this, lazy bastard . = ..() diff --git a/code/modules/mob/living/silicon/ai/ai.dm b/code/modules/mob/living/silicon/ai/ai.dm index cc3ac916631..9b1a58c386c 100644 --- a/code/modules/mob/living/silicon/ai/ai.dm +++ b/code/modules/mob/living/silicon/ai/ai.dm @@ -84,7 +84,7 @@ RegisterSignal(ai_tracking_tool, COMSIG_TRACKABLE_TRACKING_TARGET, PROC_REF(on_track_target)) RegisterSignal(ai_tracking_tool, COMSIG_TRACKABLE_GLIDE_CHANGED, PROC_REF(tracked_glidesize_changed)) - add_traits(list(TRAIT_PULL_BLOCKED, TRAIT_AI_ACCESS, TRAIT_HANDS_BLOCKED, TRAIT_CAN_GET_AI_TRACKING_MESSAGE), INNATE_TRAIT) + add_traits(list(TRAIT_PULL_BLOCKED, TRAIT_AI_ACCESS, TRAIT_HANDS_BLOCKED, TRAIT_CAN_GET_AI_TRACKING_MESSAGE, TRAIT_LOUD_BINARY), INNATE_TRAIT) alert_control = new(src, list(ALARM_ATMOS, ALARM_FIRE, ALARM_POWER, ALARM_CAMERA, ALARM_BURGLAR, ALARM_MOTION), list(z), camera_view = TRUE) RegisterSignal(alert_control.listener, COMSIG_ALARM_LISTENER_TRIGGERED, PROC_REF(alarm_triggered)) @@ -967,6 +967,7 @@ deployed_shell = target target.deploy_init(src) mind.transfer_to(target) + ADD_TRAIT(target, TRAIT_LOUD_BINARY, REF(src)) diag_hud_set_deployed() /datum/action/innate/deploy_shell diff --git a/code/modules/mob/living/silicon/robot/robot.dm b/code/modules/mob/living/silicon/robot/robot.dm index 02e6b8bb382..7b38a5df990 100644 --- a/code/modules/mob/living/silicon/robot/robot.dm +++ b/code/modules/mob/living/silicon/robot/robot.dm @@ -895,6 +895,7 @@ if((AI.radio.special_channels & RADIO_SPECIAL_SYNDIE)) radio.make_syndie() radio.subspace_transmission = TRUE + radio.command = TRUE radio.channels = AI.radio.channels for(var/chan in radio.channels) radio.secure_radio_connections[chan] = add_radio(radio, GLOB.radiochannels[chan]) @@ -927,6 +928,7 @@ deployed = FALSE mainframe.deployed_shell = null undeployment_action.Remove(src) + REMOVE_TRAIT(src, TRAIT_LOUD_BINARY, REF(mainframe)) if(radio) //Return radio to normal radio.recalculateChannels() if(!QDELETED(builtInCamera)) diff --git a/code/modules/mob/living/silicon/silicon_say.dm b/code/modules/mob/living/silicon/silicon_say.dm index 4061a35e481..3c3813e110a 100644 --- a/code/modules/mob/living/silicon/silicon_say.dm +++ b/code/modules/mob/living/silicon/silicon_say.dm @@ -11,7 +11,7 @@ if(HAS_TRAIT(mind, TRAIT_DISPLAY_JOB_IN_BINARY)) designation = mind.assigned_role.title - if(isAI(src)) + if(HAS_TRAIT(src, TRAIT_LOUD_BINARY)) // AIs are loud and ugly spans |= SPAN_COMMAND @@ -25,6 +25,12 @@ if(iscarbon(src)) namepart = GetVoice() + // AI in carbon body should still have its real name + var/obj/item/organ/brain/cybernetic/ai/brain = get_organ_slot(ORGAN_SLOT_BRAIN) + if(istype(brain)) + namepart = brain.mainframe.name + designation = brain.mainframe.job + for(var/mob/M in GLOB.player_list) if(M.binarycheck()) if(isAI(M))