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
<img width="1622" height="823" alt="image"
src="https://github.com/user-attachments/assets/9d259d09-2347-4bee-a61a-e873d3eb8b7e"
/>
<img width="1449" height="647" alt="image"
src="https://github.com/user-attachments/assets/be36fdc7-fbcc-43d6-99d1-e0a7d5fc6dc8"
/>
<img width="1463" height="838" alt="image"
src="https://github.com/user-attachments/assets/18b2dd39-c05e-43fc-bac5-00da4d642fca"
/>
<img width="1520" height="789" alt="image"
src="https://github.com/user-attachments/assets/f63fd2a2-bf3b-4652-9e92-32baff97f36d"
/>


<details>
<summary>Screenshots/Videos</summary>

</details>

## Changelog

🆑
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.
/🆑

---------

Co-authored-by: nikothedude <59709059+nikothedude@users.noreply.github.com>
This commit is contained in:
MrGloopy
2026-05-28 12:11:07 +02:00
committed by GitHub
co-authored by nikothedude
parent 4450a8bc4d
commit fb2610776d
6 changed files with 257 additions and 54 deletions
@@ -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
)))
+3 -1
View File
@@ -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)
+4 -1
View File
@@ -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
@@ -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
@@ -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"
+1
View File
@@ -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"