From fb2610776de49d76bf1dcd98f8d7318a60694e93 Mon Sep 17 00:00:00 2001 From: MrGloopy <155857749+MrGloopy@users.noreply.github.com> Date: Thu, 28 May 2026 20:11:07 +1000 Subject: [PATCH] Add persocomm to cybernetic brains (Nova port), add EMP effects to surplus brain and fix lone infil uplink spawn on synths (#5527) ## About The Pull Request Adds the Synth Persocomm to both cybernetic brains. Allows automatic connection of ID to internal computer as well with the ID in your ID slot. No more linking it via janky methods (though you still can). Fixes the bug with spawning as lone infil as a synth. No more bugged uplink in your brain computer. ## Why It's Good For The Game If my brain is half computer I should be able to use it as a computer. Fixes multiple issues with the Persocom and adds it to organics with an augmented brain. Also bugfix good. ## Proof Of Testing image image image image
Screenshots/Videos
## Changelog :cl: fix: Uplink location being set to the Persocom when playing as a synth. It will now fall back to one of the other options. add: Small persocom update. Automatic ID imprinting and PDA theme application on spawn. Ghost roles will not automatically imprint their ID. Don't imprint it as a Persistence role or Ruggero will punch you. add: Persocom to cybernetic brain options. add: Additional EMP effects such as stuttering and jittering to the surplus brain to bring it in line with the cortical brain. EMP effects are tripled compared to the cortical brain. Woe. /:cl: --------- Co-authored-by: nikothedude <59709059+nikothedude@users.noreply.github.com> --- .../_globalvars/lists/invalid_uplink_item.dm | 4 + code/datums/mind/antag.dm | 4 +- code/modules/jobs/job_types/_job.dm | 5 +- modular_skyrat/modules/organs/code/brain.dm | 92 ++++++++ .../internal_computer/internal_computer.dm | 205 +++++++++++++----- tgstation.dme | 1 + 6 files changed, 257 insertions(+), 54 deletions(-) create mode 100644 code/__DEFINES/~~bubber_defines/_globalvars/lists/invalid_uplink_item.dm diff --git a/code/__DEFINES/~~bubber_defines/_globalvars/lists/invalid_uplink_item.dm b/code/__DEFINES/~~bubber_defines/_globalvars/lists/invalid_uplink_item.dm new file mode 100644 index 00000000000..bd7d7c6e422 --- /dev/null +++ b/code/__DEFINES/~~bubber_defines/_globalvars/lists/invalid_uplink_item.dm @@ -0,0 +1,4 @@ +//Whether an item is a valid uplink location or not +GLOBAL_LIST_INIT(invalid_uplink_location, typecacheof(list( + /obj/item/modular_computer/pda/synth +))) diff --git a/code/datums/mind/antag.dm b/code/datums/mind/antag.dm index 4641abe1c89..312f6838577 100644 --- a/code/datums/mind/antag.dm +++ b/code/datums/mind/antag.dm @@ -86,7 +86,9 @@ * Returns the item found, or null if no item was found. */ /mob/living/carbon/proc/get_uplink_location(desired_location = UPLINK_PDA) - var/list/all_contents = get_all_contents() + //BUBBER EDIT BEGIN - original line is var/list/all_contents = get_all_contents() + var/list/all_contents = typecache_filter_list_reverse(get_all_contents(), GLOB.invalid_uplink_location) + //BUBBER EDIT END var/obj/item/modular_computer/pda/my_pda = locate() in all_contents var/obj/item/radio/my_radio = locate() in all_contents var/obj/item/pen/my_pen = (locate() in my_pda) || (locate() in all_contents) diff --git a/code/modules/jobs/job_types/_job.dm b/code/modules/jobs/job_types/_job.dm index 21c81e9d711..5edbf0780f7 100644 --- a/code/modules/jobs/job_types/_job.dm +++ b/code/modules/jobs/job_types/_job.dm @@ -480,7 +480,10 @@ if(equipped_client) pda.update_pda_prefs(equipped_client) - + //BUBBER EDIT BEGIN + var/obj/item/modular_computer/pda/synth/brainpooter = locate() in equipped.get_all_contents() + brainpooter.update_user_settings(equipped_client) + //BUBBER EDIT END /datum/outfit/job/get_chameleon_disguise_info() var/list/types = ..() types -= /obj/item/storage/backpack //otherwise this will override the actual backpacks diff --git a/modular_skyrat/modules/organs/code/brain.dm b/modular_skyrat/modules/organs/code/brain.dm index 03695119579..50fa77cda76 100644 --- a/modular_skyrat/modules/organs/code/brain.dm +++ b/modular_skyrat/modules/organs/code/brain.dm @@ -9,6 +9,43 @@ organ_flags = ORGAN_ORGANIC | ORGAN_ROBOTIC | ORGAN_VITAL | ORGAN_PROMINENT //it's a bit weird to be both organic and robotic, but yk emp_dmg_mult = 1.5 //Note that the base damage is 20/10 + //internal computer for cortical + var/obj/item/modular_computer/pda/synth/internal_computer + actions_types = list(/datum/action/item_action/synth/open_internal_computer) + +/obj/item/organ/brain/cybernetic/cortical/Initialize(mapload) + . = ..() + internal_computer = new(src) + ADD_TRAIT(src, TRAIT_SILICON_EMOTES_ALLOWED, INNATE_TRAIT) + +/obj/item/organ/brain/cybernetic/cortical/Destroy() + QDEL_NULL(internal_computer) + return ..() + +/obj/item/organ/brain/cybernetic/cortical/on_mob_insert(mob/living/carbon/human/brain_owner, special, movement_flags) + . = ..() + if(!istype(brain_owner)) + return + RegisterSignal(brain_owner, COMSIG_MOB_EQUIPPED_ITEM, PROC_REF(on_equip_signal)) + if(internal_computer && brain_owner.wear_id) + internal_computer.handle_id_slot(brain_owner, brain_owner.wear_id) + +/obj/item/organ/brain/cybernetic/cortical/on_mob_remove(mob/living/carbon/human/brain_owner, special) + . = ..() + if(!istype(brain_owner)) + return + UnregisterSignal(brain_owner, COMSIG_MOB_EQUIPPED_ITEM) + if(internal_computer) + internal_computer.handle_id_slot(brain_owner) + internal_computer.clear_id_slot_signals(brain_owner.wear_id) + +/obj/item/organ/brain/cybernetic/cortical/proc/on_equip_signal(datum/source, obj/item/item, slot) + SIGNAL_HANDLER + if(isnull(internal_computer)) + return + if(slot == ITEM_SLOT_ID) + internal_computer.handle_id_slot(owner, item) + //Extra effects /obj/item/organ/brain/cybernetic/cortical/emp_act(severity) . = ..() @@ -45,6 +82,61 @@ emp_dmg_mult = 1.5 //Note that the base damage is 20/10 emp_dmg_max = 999 + //internal computer for surplus + var/obj/item/modular_computer/pda/synth/internal_computer + actions_types = list(/datum/action/item_action/synth/open_internal_computer) +/obj/item/organ/brain/cybernetic/surplus/Initialize(mapload) + . = ..() + internal_computer = new(src) + ADD_TRAIT(src, TRAIT_SILICON_EMOTES_ALLOWED, INNATE_TRAIT) + +/obj/item/organ/brain/cybernetic/surplus/Destroy() + QDEL_NULL(internal_computer) + return ..() + +/obj/item/organ/brain/cybernetic/surplus/on_mob_insert(mob/living/carbon/human/brain_owner, special, movement_flags) + . = ..() + if(!istype(brain_owner)) + return + RegisterSignal(brain_owner, COMSIG_MOB_EQUIPPED_ITEM, PROC_REF(on_equip_signal)) + if(internal_computer && brain_owner.wear_id) + internal_computer.handle_id_slot(brain_owner, brain_owner.wear_id) + +/obj/item/organ/brain/cybernetic/surplus/on_mob_remove(mob/living/carbon/human/brain_owner, special) + . = ..() + if(!istype(brain_owner)) + return + UnregisterSignal(brain_owner, COMSIG_MOB_EQUIPPED_ITEM) + if(internal_computer) + internal_computer.handle_id_slot(brain_owner) + internal_computer.clear_id_slot_signals(brain_owner.wear_id) + +/obj/item/organ/brain/cybernetic/surplus/proc/on_equip_signal(datum/source, obj/item/item, slot) + SIGNAL_HANDLER + if(isnull(internal_computer)) + return + if(slot == ITEM_SLOT_ID) + internal_computer.handle_id_slot(owner, item) + + + +//Extra effects for surplus - tripled from cortical +/obj/item/organ/brain/cybernetic/surplus/emp_act(severity) + . = ..() + if(. & EMP_PROTECT_SELF) + return + if(owner.stat == DEAD) + return + switch(severity) + if(1) + owner.set_jitter_if_lower(90 SECONDS) + owner.adjust_stutter(90 SECONDS) + owner.adjust_confusion(30 SECONDS) + if(2) + owner.set_jitter_if_lower(45 SECONDS) + owner.adjust_stutter(45 SECONDS) + owner.adjust_confusion(9 SECONDS) + /obj/item/organ/brain/cybernetic/surplus/Initialize(mapload) . = ..() AddElement(/datum/element/dangerous_organ_removal, /*surgical = */ TRUE, /*annihilate = */ FALSE) //annihilate just means it wont qdel when removed diff --git a/modular_skyrat/modules/synths/code/bodyparts/internal_computer/internal_computer.dm b/modular_skyrat/modules/synths/code/bodyparts/internal_computer/internal_computer.dm index 0f564989296..55638f18ceb 100644 --- a/modular_skyrat/modules/synths/code/bodyparts/internal_computer/internal_computer.dm +++ b/modular_skyrat/modules/synths/code/bodyparts/internal_computer/internal_computer.dm @@ -5,7 +5,6 @@ icon_state = "posibrain" base_icon_state = "posibrain" greyscale_config = null - post_init_icon_state = null base_active_power_usage = 0 WATTS base_idle_power_usage = 0 WATTS @@ -13,32 +12,31 @@ max_idle_programs = 3 - max_capacity = parent_type::max_capacity * 2 + max_capacity = 64 + flags_1 = parent_type::flags_1 | NO_NEW_GAGS_PREVIEW_1 /obj/item/modular_computer/pda/synth/Initialize(mapload) . = ..() - // prevent these from being created outside of synth brains - if(!istype(loc, /obj/item/organ/brain/synth)) + // prevent these from being created outside of valid brains + if(!istype(loc, /obj/item/organ/brain/synth) && !istype(loc, /obj/item/organ/brain/cybernetic/cortical) && !istype(loc, /obj/item/organ/brain/cybernetic/surplus)) return INITIALIZE_HINT_QDEL - //this code has to be called at a delay because the required data (synth owner's job) is null when initialized - addtimer(CALLBACK(src, TYPE_PROC_REF(/obj/item/modular_computer/pda/synth, post_initialize)), 1 DECISECONDS) +/obj/item/modular_computer/pda/synth/proc/update_user_settings(client/client) + var/obj/item/organ/brain/cybernetic/brainpooter = loc + var/mob/living/brain_owner = brainpooter.owner + if(!isnull(brain_owner)) + imprint_id("[brain_owner.real_name] Persocom", brain_owner.job) + var/datum/mind/owner_mind = brain_owner?.mind + if(!isnull(owner_mind)) + update_ringtone(owner_mind?.assigned_role?.job_tone) + UpdateDisplay() + if(!isnull(client)) + update_pda_prefs(client) -/obj/item/modular_computer/pda/synth/proc/post_initialize() - var/obj/item/organ/brain/synth/brain_loc = loc - var/mob/living/carbon/owner = brain_loc?.bodypart_owner?.owner - if(istype(owner)) - var/obj/item/modular_computer/pda/job_pda = SSjob.get_pda_type_by_job(owner.job) - if(istype(job_pda)) - starting_programs += job_pda.starting_programs - var/obj/item/modular_computer/pda/heads/head_pda = job_pda - if(istype(head_pda)) - starting_programs += head_pda.head_programs - install_default_programs() - QDEL_NULL(job_pda) +/obj/item/modular_computer/pda/synth/check_power_override() + return TRUE -/* Action for opening the synthbrain computer */ /datum/action/item_action/synth/open_internal_computer name = "Open persocom emulation" desc = "Accesses your built-in virtual machine." @@ -46,18 +44,26 @@ /datum/action/item_action/synth/open_internal_computer/Trigger(trigger_flags) . = ..() - var/obj/item/organ/brain/synth/targetmachine = target - targetmachine.internal_computer.interact(owner) + if(!.) + return + var/obj/item/organ/brain/synth/synth_brain = target + if(istype(synth_brain)) + synth_brain.internal_computer.interact(owner) + return + var/obj/item/organ/brain/cybernetic/cortical/cort_brain = target + if(istype(cort_brain)) + cort_brain.internal_computer.interact(owner) + return + var/obj/item/organ/brain/cybernetic/surplus/surp_brain = target + if(istype(surp_brain)) + surp_brain.internal_computer.interact(owner) -/* -Various overrides necessary to get the persocom working, namely ui status, power, and ID handling -*/ /obj/item/modular_computer/pda/synth/ui_state(mob/user) return GLOB.default_state /obj/item/modular_computer/pda/synth/ui_status(mob/user) - var/obj/item/organ/brain/synth/brain_loc = loc - if(!istype(brain_loc)) + var/obj/item/organ/brain/brain_loc = loc + if(!(istype(brain_loc, /obj/item/organ/brain/synth) || istype(brain_loc, /obj/item/organ/brain/cybernetic/cortical) || istype(brain_loc, /obj/item/organ/brain/cybernetic/surplus))) return UI_CLOSE if(!QDELETED(brain_loc.owner)) @@ -68,37 +74,110 @@ Various overrides necessary to get the persocom working, namely ui status, power ) return ..() -// Needed to tell the power check that we're handling power ourselves -/obj/item/modular_computer/pda/synth/check_power_override(amount) - return TRUE +/// Id card arg is optional. Leaving it null causes the id to become unpaired from the synth computer +/obj/item/modular_computer/pda/synth/proc/update_id_slot(obj/item/card/id/id_card) + var/obj/item/organ/brain/brain_loc = loc + if(!(istype(brain_loc, /obj/item/organ/brain/synth) || istype(brain_loc, /obj/item/organ/brain/cybernetic/cortical) || istype(brain_loc, /obj/item/organ/brain/cybernetic/surplus))) + return + if(istype(brain_loc, /obj/item/organ/brain/synth)) + var/obj/item/organ/brain/synth/synth_brain = brain_loc + if(isnull(synth_brain.internal_computer)) + return + synth_brain.internal_computer.handle_id_slot(synth_brain.owner, id_card) + return + var/obj/item/organ/brain/cybernetic/cortical/cortical_brain = brain_loc + if(isnull(cortical_brain.internal_computer)) + return + cortical_brain.internal_computer.handle_id_slot(cortical_brain.owner, id_card) + var/obj/item/organ/brain/cybernetic/surplus/surplus_brain = brain_loc + if(isnull(surplus_brain.internal_computer)) + return + surplus_brain.internal_computer.handle_id_slot(surplus_brain.owner, id_card) -// Handles the id slot reading our id -/obj/item/modular_computer/pda/synth/proc/handle_id_slot(mob/living/carbon/human/synth) + +/// Called when id slot item is unequipped from the id slot +/obj/item/modular_computer/pda/synth/proc/on_id_item_unequipped(datum/source) + SIGNAL_HANDLER + clear_id_slot_signals(source) + update_id_slot() + +/// Called when id slot item's contained id is moved out of the id slot item +/obj/item/modular_computer/pda/synth/proc/on_id_item_moved(datum/source) + SIGNAL_HANDLER + clear_id_slot_signals(source) + update_id_slot() + +/// Called when something is inserted into an id slot wallet or pda +/obj/item/modular_computer/pda/synth/proc/on_id_item_stored(datum/source, obj/item/card/id/to_insert) + SIGNAL_HANDLER + if(!istype(to_insert)) + return + + UnregisterSignal(source, COMSIG_STORAGE_STORED_ITEM) + update_id_slot(to_insert) + +/obj/item/modular_computer/pda/synth/proc/clear_id_slot_signals(obj/item/id_slot_item) + if(!istype(id_slot_item)) + return + + UnregisterSignal(id_slot_item, list( + COMSIG_ITEM_POST_UNEQUIP, + COMSIG_MOVABLE_MOVED, + COMSIG_ITEM_UNSTORED, + COMSIG_STORAGE_STORED_ITEM, + COMSIG_MODULAR_COMPUTER_INSERTED_ID, + )) + + // make sure we clear all the signals on the contained id too + var/obj/item/card/id/contained_id_item + if(istype(id_slot_item, /obj/item/modular_computer/pda)) + var/obj/item/modular_computer/pda/id_slot_pda = id_slot_item + contained_id_item = id_slot_pda.stored_id + else if(istype(id_slot_item, /obj/item/storage/wallet)) + var/obj/item/storage/wallet/id_slot_wallet = id_slot_item + contained_id_item = id_slot_wallet.GetID() + + if(contained_id_item) + UnregisterSignal(contained_id_item, list(COMSIG_MOVABLE_MOVED, COMSIG_ITEM_UNSTORED)) + +/obj/item/modular_computer/pda/synth/proc/handle_id_slot(mob/living/carbon/human/synth, obj/item/id_item) if(!istype(synth)) return - var/obj/item = synth.wear_id - if(item) - if(istype(item, /obj/item/card/id)) - stored_id = item - else if(istype(item, /obj/item/modular_computer)) - var/obj/item/modular_computer/pda = item - stored_id = pda.stored_id - else if(istype(item, /obj/item/storage/wallet)) - var/obj/item/storage/wallet/wallet = item - stored_id = wallet.front_id - else - stored_id = null + if(isnull(id_item)) + if(stored_id) + to_chat(synth, span_notice("Persocom RFID link disconnected.")) + stored_id = null + return + if(istype(id_item, /obj/item/card/id)) + stored_id = id_item + to_chat(synth, span_notice("Persocom establishing new RFID link with [id_item].")) + RegisterSignal(id_item, COMSIG_ITEM_POST_UNEQUIP, PROC_REF(on_id_item_unequipped)) + else if(istype(id_item, /obj/item/modular_computer)) + var/obj/item/modular_computer/pda = id_item + stored_id = pda.stored_id + to_chat(synth, span_notice("Persocom establishing new RFID link with [pda].")) + RegisterSignal(pda, COMSIG_ITEM_POST_UNEQUIP, PROC_REF(on_id_item_unequipped)) + RegisterSignal(pda, COMSIG_MODULAR_COMPUTER_INSERTED_ID, PROC_REF(on_id_item_stored)) + RegisterSignal(pda.stored_id, COMSIG_MOVABLE_MOVED, PROC_REF(on_id_item_moved)) + else if(istype(id_item, /obj/item/storage/wallet)) + var/obj/item/storage/wallet/your_wallet = id_item + stored_id = your_wallet.GetID() + to_chat(synth, span_notice("Persocom establishing new RFID link with [your_wallet].")) + RegisterSignal(your_wallet, COMSIG_ITEM_POST_UNEQUIP, PROC_REF(on_id_item_unequipped)) + RegisterSignal(your_wallet, COMSIG_STORAGE_STORED_ITEM, PROC_REF(on_id_item_stored)) + RegisterSignal(your_wallet.GetID(), COMSIG_ITEM_UNSTORED, PROC_REF(on_id_item_moved)) + else stored_id = null -/* /obj/item/modular_computer/pda/synth/RemoveID(mob/user, silent = TRUE) - return */ // Fix this if we need it. +/obj/item/modular_computer/pda/synth/remove_id(mob/user, silent = FALSE) + return /obj/item/modular_computer/pda/synth/get_ntnet_status() . = ..() - if(is_centcom_level(loc.z)) // Centcom is excluded because cafe would allow people to abuse these - . = NTNET_NO_SIGNAL - return . + if(is_centcom_level(loc.z)) // Centcom is excluded because cafe + return NTNET_NO_SIGNAL + /obj/item/modular_computer/pda/attack(mob/living/target_mob, mob/living/user, params) var/mob/living/carbon/human/targetmachine = target_mob @@ -110,18 +189,40 @@ Various overrides necessary to get the persocom working, namely ui status, power if(user.zone_selected == BODY_ZONE_PRECISE_EYES) balloon_alert(user, "establishing SSH login with persocom...") if(do_after(user, 5 SECONDS)) - balloon_alert(user, "connection established!") + balloon_alert(user, "connection established") to_chat(targetmachine, span_notice("[user] establishes an SSH connection between [src] and your persocom emulation.")) robotbrain.internal_computer.interact(user) return + + // Also supports cortical augmented brains and surplus + var/obj/item/organ/brain/cybernetic/cortical/cortbrain = targetmachine.get_organ_slot(ORGAN_SLOT_BRAIN) + if(istype(cortbrain)) + if(user.zone_selected == BODY_ZONE_PRECISE_EYES) + balloon_alert(user, "establishing SSH login with persocom...") + if(do_after(user, 5 SECONDS)) + balloon_alert(user, "connection established") + to_chat(targetmachine, span_notice("[user] establishes an SSH connection between [src] and your persocom emulation.")) + cortbrain.internal_computer.interact(user) + return + + var/obj/item/organ/brain/cybernetic/surplus/surpbrain = targetmachine.get_organ_slot(ORGAN_SLOT_BRAIN) + if(istype(surpbrain)) + if(user.zone_selected == BODY_ZONE_PRECISE_EYES) + balloon_alert(user, "establishing SSH login with persocom...") + if(do_after(user, 5 SECONDS)) + balloon_alert(user, "connection established") + to_chat(targetmachine, span_notice("[user] establishes an SSH connection between [src] and your persocom emulation.")) + surpbrain.internal_computer.interact(user) + return return ..() + /obj/item/modular_computer/pda/synth/get_header_data() var/list/data = ..() - var/obj/item/organ/brain/synth/brain_loc = loc + var/obj/item/organ/brain/brain_loc = loc // Battery level is now according to the synth charge - if(istype(brain_loc)) - var/charge_level = (brain_loc.owner.nutrition / NUTRITION_LEVEL_ALMOST_FULL) * 100 + if(istype(brain_loc, /obj/item/organ/brain/synth) || istype(brain_loc, /obj/item/organ/brain/cybernetic/cortical) || istype(brain_loc, /obj/item/organ/brain/cybernetic/surplus)) + var/charge_level = (brain_loc.owner.nutrition / NUTRITION_LEVEL_FULL) * 100 switch(charge_level) if(80 to 110) data["PC_batteryicon"] = "batt_100.gif" diff --git a/tgstation.dme b/tgstation.dme index 7f129ce594f..08571afb412 100644 --- a/tgstation.dme +++ b/tgstation.dme @@ -562,6 +562,7 @@ #include "code\__DEFINES\~~bubber_defines\transport.dm" #include "code\__DEFINES\~~bubber_defines\vehicles.dm" #include "code\__DEFINES\~~bubber_defines\vomit.dm" +#include "code\__DEFINES\~~bubber_defines\_globalvars\lists\invalid_uplink_item.dm" #include "code\__DEFINES\~~bubber_defines\atmospherics\atmos_mob_interaction.dm" #include "code\__DEFINES\~~bubber_defines\research\techweb_nodes.dm" #include "code\__DEFINES\~~bubber_defines\traits\declarations.dm"