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
🆑
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.
/🆑
This commit is contained in:
Iajret
2025-04-09 16:43:54 +02:00
committed by GitHub
parent 0ca519e33d
commit 081a804b44
8 changed files with 65 additions and 8 deletions
+2
View File
@@ -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"
+1
View File
@@ -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,
@@ -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
@@ -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)
@@ -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
. = ..()
+2 -1
View File
@@ -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
@@ -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))
@@ -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))