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))