From 9b54dbf97479d2e683a26782b5acda52ed064ad2 Mon Sep 17 00:00:00 2001 From: Y0SH1M4S73R Date: Mon, 26 May 2025 22:06:13 -0400 Subject: [PATCH] Adds usb support to advanced camera consoles (#91235) ## About The Pull Request This PR adds usb functionality to advanced camera consoles. The advanced camera console component gets the last valid position viewed from the console, which is usually the current viewed position, unless you move the view into static. The advanced camera target intercept outputs the position you left/right click (except shift-clicking), and what you clicked on. This only works for atoms the camera can actually see, even if you somehow glitch your way into not seeing static on your client. Equipment action components on circuits attached to a camera console will add their actions to be used from the console. ## Why It's Good For The Game Expands the use cases for the advanced camera console, providing a means to extend their functionality, as well as a potentially powerful source of input data for circuit users able to acquire illegal tech (or get the board from a space ruin). ## Changelog :cl: add: Advanced camera consoles now have usb ports. Connected circuits can get the position the camera is viewing, get the location of targeted entities, and provide the console with actions that the user can activate. /:cl: --------- Co-authored-by: Mothblocks <35135081+Mothblocks@users.noreply.github.com> --- code/__DEFINES/dcs/signals/signals_circuit.dm | 6 + .../signals/signals_mob/signals_mob_main.dm | 3 + code/__DEFINES/dcs/signals/signals_object.dm | 3 + code/datums/components/usb_port.dm | 26 +- .../machinery/computer/camera_advanced.dm | 236 ++++++++++++++++++ .../construction_console.dm | 1 + code/modules/mob/eye/camera/remote.dm | 1 + .../shuttle_consoles/navigation_computer.dm | 1 + 8 files changed, 276 insertions(+), 1 deletion(-) diff --git a/code/__DEFINES/dcs/signals/signals_circuit.dm b/code/__DEFINES/dcs/signals/signals_circuit.dm index 14be995d6ce..bdc4c3e1a9b 100644 --- a/code/__DEFINES/dcs/signals/signals_circuit.dm +++ b/code/__DEFINES/dcs/signals/signals_circuit.dm @@ -92,3 +92,9 @@ #define COMSIG_SHELL_CIRCUIT_ATTACHED "shell_circuit_attached" ///Sent to the shell component when a circuit is removed. #define COMSIG_SHELL_CIRCUIT_REMOVED "shell_circuit_removed" + +/// Called when a usb port registers signals on a circuit or shell (atom/movable/new_physical_object) +#define COMSIG_USB_PORT_REGISTER_PHYSICAL_OBJECT "usb_port_register_physical_object" + +/// Called when a usb port unregisters signals from a circuit or shell (atom/movable/old_physical_object) +#define COMSIG_USB_PORT_UNREGISTER_PHYSICAL_OBJECT "usb_port_unregister_physical_object" diff --git a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_main.dm b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_main.dm index 978b02413ca..3106afa7975 100644 --- a/code/__DEFINES/dcs/signals/signals_mob/signals_mob_main.dm +++ b/code/__DEFINES/dcs/signals/signals_mob/signals_mob_main.dm @@ -276,3 +276,6 @@ ///from /obj/item/crusher_trophy/on_mark_activate(): (trophy, user) #define COMSIG_MOB_TROPHY_ACTIVATED(identifier) "COMSIG_MOB_TROPHY_ACTIVATED[identifier]" + +/// from /mob/eye/camera/remote/assign_user(): (mob/living/new_user, mob/living/old_user) +#define COMSIG_REMOTE_CAMERA_ASSIGN_USER "remote_camera_assign_user" diff --git a/code/__DEFINES/dcs/signals/signals_object.dm b/code/__DEFINES/dcs/signals/signals_object.dm index b693999cddb..13805468562 100644 --- a/code/__DEFINES/dcs/signals/signals_object.dm +++ b/code/__DEFINES/dcs/signals/signals_object.dm @@ -594,3 +594,6 @@ /// Sent from /obj/item/kinetic_crusher/proc/fire_kinetic_blast() : (atom/target, mob/living/user, obj/projectile/destabilizer/destabilizer) #define COMSIG_CRUSHER_FIRED_BLAST "crusher_fired_blast" + +/// Sent from /obj/machinert/console/camera_advanced/attack_hand() : (mob/eye/camera/remote/new_camera) +#define COMSIG_ADVANCED_CAMERA_EYE_CREATED "advanced_camera_eye_created" diff --git a/code/datums/components/usb_port.dm b/code/datums/components/usb_port.dm index c996475a239..b5a370d12c6 100644 --- a/code/datums/components/usb_port.dm +++ b/code/datums/components/usb_port.dm @@ -27,13 +27,21 @@ /// Used to prevent range checking during shuttle movement, which moves atoms en-masse. var/defer_range_checks = 0 -/datum/component/usb_port/Initialize(list/circuit_component_types) + /// An extra callback to invoke when registering this component with its parent. Can be a proc name or a callback datum. + var/extra_registration_callback + + /// An extra callback to invoke when unregistering this component from its parent. Can be a proc name or a callback datum. + var/extra_unregistration_callback + +/datum/component/usb_port/Initialize(list/circuit_component_types, extra_registration_callback, extra_unregistration_callback) if (!isatom(parent)) return COMPONENT_INCOMPATIBLE circuit_components = list() src.circuit_component_types = circuit_component_types + src.extra_registration_callback = extra_registration_callback + src.extra_unregistration_callback = extra_unregistration_callback /datum/component/usb_port/proc/set_circuit_components(list/components) var/should_register = FALSE @@ -65,6 +73,13 @@ for(var/obj/item/circuit_component/component as anything in circuit_components) component.register_usb_parent(parent) + if(extra_registration_callback) + if(istype(extra_registration_callback, /datum/callback)) + var/datum/callback/callback = extra_registration_callback + callback.Invoke(src) + else + call(parent, extra_registration_callback)(src) + /datum/component/usb_port/UnregisterFromParent() UnregisterSignal(parent, list( COMSIG_ATOM_USB_CABLE_TRY_ATTACH, @@ -78,6 +93,13 @@ for(var/obj/item/circuit_component/component as anything in circuit_components) component.unregister_usb_parent(parent) + if(extra_unregistration_callback) + if(istype(extra_unregistration_callback, /datum/callback)) + var/datum/callback/callback = extra_unregistration_callback + callback.Invoke(src) + else + call(parent, extra_unregistration_callback)(src) + unregister_circuit_signals() unregister_physical_signals() attached_circuit = null @@ -125,6 +147,7 @@ if (isnull(physical_object)) return + SEND_SIGNAL(src, COMSIG_USB_PORT_UNREGISTER_PHYSICAL_OBJECT, physical_object) UnregisterSignal(physical_object, list( COMSIG_MOVABLE_MOVED, COMSIG_ATOM_BEFORE_SHUTTLE_MOVE, @@ -209,6 +232,7 @@ RegisterSignal(new_physical_object, COMSIG_ATOM_BEFORE_SHUTTLE_MOVE, PROC_REF(before_physical_object_shuttle_move)) RegisterSignal(new_physical_object, COMSIG_ATOM_AFTER_SHUTTLE_MOVE, PROC_REF(after_physical_object_shuttle_move)) RegisterSignal(new_physical_object, COMSIG_ATOM_EXAMINE, PROC_REF(on_examine_shell)) + SEND_SIGNAL(src, COMSIG_USB_PORT_REGISTER_PHYSICAL_OBJECT, new_physical_object) physical_object = new_physical_object // Adds support for loading circuits without shells but with usb cables, or loading circuits with shells because the shells might not load first. diff --git a/code/game/machinery/computer/camera_advanced.dm b/code/game/machinery/computer/camera_advanced.dm index 88ae57055df..73827f37ae5 100644 --- a/code/game/machinery/computer/camera_advanced.dm +++ b/code/game/machinery/computer/camera_advanced.dm @@ -25,6 +25,8 @@ var/list/actions = list() ///Should we supress any view changes? var/should_supress_view_changes = TRUE + ///Should we add a usb port to this console? + var/add_usb_port = TRUE interaction_flags_machine = INTERACT_MACHINE_ALLOW_SILICON | INTERACT_MACHINE_REQUIRES_SIGHT @@ -51,6 +53,15 @@ //Camera action button to move down a Z level if(move_down_action) actions += new move_down_action(src) + if(add_usb_port) + AddComponent(/datum/component/usb_port, \ + list( + /obj/item/circuit_component/advanced_camera, + /obj/item/circuit_component/advanced_camera_intercept, + ), \ + extra_registration_callback = PROC_REF(register_usb_port), \ + extra_unregistration_callback = PROC_REF(unregister_usb_port) \ + ) /obj/machinery/computer/camera_advanced/Destroy() unset_machine() @@ -162,6 +173,8 @@ to_chat(user, span_warning("\The [src] flashes a bunch of never-ending errors on the display. Something is really wrong.")) return + SEND_SIGNAL(src, COMSIG_ADVANCED_CAMERA_EYE_CREATED, eyeobj) + var/camera_location var/turf/myturf = get_turf(src) var/consider_zlock = (!!length(z_lock)) @@ -273,3 +286,226 @@ new /obj/item/secure_camera_console_pod(get_turf(src)) qdel(src) return ITEM_INTERACT_SUCCESS + +/// Equipment action component support + +/obj/machinery/computer/camera_advanced/proc/register_usb_port(datum/component/usb_port/port) + RegisterSignal(port, COMSIG_USB_PORT_REGISTER_PHYSICAL_OBJECT, PROC_REF(on_port_register_object)) + RegisterSignal(port, COMSIG_USB_PORT_UNREGISTER_PHYSICAL_OBJECT, PROC_REF(on_port_unregister_object)) + if(port.physical_object) + on_port_register_object(port, port.physical_object) + +/obj/machinery/computer/camera_advanced/proc/on_port_register_object(datum/component/usb_port/source, atom/movable/object) + SIGNAL_HANDLER + var/obj/item/integrated_circuit/circuit = source.attached_circuit + if(object == circuit) + return + RegisterSignal(object, COMSIG_CIRCUIT_ACTION_COMPONENT_REGISTERED, PROC_REF(add_circuit_action)) + RegisterSignal(object, COMSIG_CIRCUIT_ACTION_COMPONENT_UNREGISTERED, PROC_REF(remove_circuit_action)) + for(var/obj/item/circuit_component/equipment_action/action_comp in circuit.attached_components) + add_circuit_action(null, action_comp) + +/obj/machinery/computer/camera_advanced/proc/add_circuit_action(datum/_source, obj/item/circuit_component/equipment_action/action_comp) + SIGNAL_HANDLER + var/datum/action/innate/camera_circuit_action/new_action = new(src, action_comp) + LAZYADD(actions, new_action) + if(current_user) + new_action.Grant(current_user) + +/obj/machinery/computer/camera_advanced/proc/remove_circuit_action(datum/_source, obj/item/circuit_component/equipment_action/action_comp) + SIGNAL_HANDLER + var/datum/action/innate/camera_circuit_action/action = action_comp.granted_to[REF(src)] + if(!istype(action)) + return + LAZYREMOVE(actions, action) + qdel(action) + +/obj/machinery/computer/camera_advanced/proc/on_port_unregister_object(datum/component/usb_port/source, atom/movable/object) + SIGNAL_HANDLER + var/obj/item/integrated_circuit/circuit = source.attached_circuit + for(var/obj/item/circuit_component/equipment_action/action_comp in circuit.attached_components) + remove_circuit_action(null, action_comp) + UnregisterSignal(object, list(COMSIG_CIRCUIT_ACTION_COMPONENT_REGISTERED, COMSIG_CIRCUIT_ACTION_COMPONENT_UNREGISTERED)) + +/obj/machinery/computer/camera_advanced/proc/unregister_usb_port(datum/component/usb_port/port) + if(port.physical_object) + on_port_unregister_object(port, port.physical_object) + UnregisterSignal(port, list(COMSIG_USB_PORT_REGISTER_PHYSICAL_OBJECT, COMSIG_USB_PORT_UNREGISTER_PHYSICAL_OBJECT)) + +/datum/action/innate/camera_circuit_action + name = "Action" + button_icon = 'icons/mob/actions/actions_items.dmi' + button_icon_state = "bci_power" + + var/obj/machinery/computer/camera_advanced/console + var/obj/item/circuit_component/equipment_action/action_comp + +/datum/action/innate/camera_circuit_action/New(obj/machinery/computer/camera_advanced/console, obj/item/circuit_component/equipment_action/action_comp) + . = ..() + src.console = console + action_comp.granted_to[REF(console)] = src + src.action_comp = action_comp + +/datum/action/innate/camera_circuit_action/Destroy() + action_comp.granted_to -= REF(console) + action_comp = null + + return ..() + +/datum/action/innate/camera_circuit_action/Activate() + action_comp.user.set_output(owner) + action_comp.signal.set_output(COMPONENT_SIGNAL) + + return ..() + +/// Advanced camera component + +/obj/item/circuit_component/advanced_camera + display_name = "Advanced Camera Console" + desc = "Gets the position being viewed through the console." + + var/datum/port/output/eye_x + var/datum/port/output/eye_y + var/datum/port/output/eye_z + + var/obj/machinery/computer/camera_advanced/attached_console + +/obj/item/circuit_component/advanced_camera/populate_ports() + eye_x = add_output_port("X", PORT_TYPE_NUMBER) + eye_y = add_output_port("Y", PORT_TYPE_NUMBER) + eye_z = add_output_port("Z", PORT_TYPE_NUMBER) + +/obj/item/circuit_component/advanced_camera/register_usb_parent(atom/movable/parent) + . = ..() + if(istype(parent, /obj/machinery/computer/camera_advanced)) + attached_console = parent + if(attached_console.eyeobj) + register_eyeobj(attached_console.eyeobj) + else + RegisterSignal(attached_console, COMSIG_ADVANCED_CAMERA_EYE_CREATED, PROC_REF(on_parent_eye_created)) + +/obj/item/circuit_component/advanced_camera/proc/on_parent_eye_created(datum/_source, mob/eye/camera/remote/eyeobj) + SIGNAL_HANDLER + UnregisterSignal(attached_console, COMSIG_ADVANCED_CAMERA_EYE_CREATED) + register_eyeobj(eyeobj) + +/obj/item/circuit_component/advanced_camera/proc/register_eyeobj(mob/eye/camera/remote/eyeobj) + RegisterSignal(eyeobj, COMSIG_MOVABLE_MOVED, PROC_REF(on_eyeobj_moved)) + +/obj/item/circuit_component/advanced_camera/unregister_usb_parent(atom/movable/parent) + if(istype(parent, /obj/machinery/computer/camera_advanced)) + UnregisterSignal(attached_console, COMSIG_ADVANCED_CAMERA_EYE_CREATED) + if(attached_console.eyeobj) + UnregisterSignal(attached_console.eyeobj, COMSIG_MOVABLE_MOVED) + attached_console = null + return ..() + +/obj/item/circuit_component/advanced_camera/proc/on_eyeobj_moved(atom/movable/source) + SIGNAL_HANDLER + var/turf/eye_turf = get_turf(source) + if(!eye_turf) + return + if(!GLOB.cameranet.checkTurfVis(eye_turf)) + return + eye_x.set_output(source.x) + eye_y.set_output(source.y) + eye_z.set_output(source.z) + +/// Advanced camera target intercept component + +/obj/item/circuit_component/advanced_camera_intercept + display_name = "Advanced Camera Target Intercept" + desc = "Allows the user to target an entity or position with the console." + + var/datum/port/input/enabled + + var/datum/port/output/target_x + var/datum/port/output/target_y + var/datum/port/output/target_z + + var/datum/port/output/target_port + + var/datum/port/output/primary_click + var/datum/port/output/secondary_click + + var/obj/machinery/computer/camera_advanced/attached_console + +/obj/item/circuit_component/advanced_camera_intercept/populate_ports() + . = ..() + enabled = add_input_port("Enabled", PORT_TYPE_NUMBER) + + target_x = add_output_port("X", PORT_TYPE_NUMBER) + target_y = add_output_port("Y", PORT_TYPE_NUMBER) + target_z = add_output_port("Z", PORT_TYPE_NUMBER) + + target_port = add_output_port("Target", PORT_TYPE_ATOM) + + primary_click = add_output_port("Primary", PORT_TYPE_SIGNAL) + secondary_click = add_output_port("Secondary", PORT_TYPE_SIGNAL) + +/obj/item/circuit_component/advanced_camera_intercept/register_usb_parent(atom/movable/parent) + . = ..() + if(istype(parent, /obj/machinery/computer/camera_advanced)) + attached_console = parent + if(attached_console.eyeobj) + register_eyeobj(attached_console.eyeobj) + else + RegisterSignal(attached_console, COMSIG_ADVANCED_CAMERA_EYE_CREATED, PROC_REF(on_parent_eye_created)) + +/obj/item/circuit_component/advanced_camera_intercept/input_received(datum/port/input/port, list/return_values) + if(port != enabled) + return + if(enabled.value) + attached_console.current_user?.click_intercept = src + else + attached_console.current_user?.click_intercept = null + +/obj/item/circuit_component/advanced_camera_intercept/proc/on_parent_eye_created(datum/_source, mob/eye/camera/remote/eyeobj) + SIGNAL_HANDLER + UnregisterSignal(attached_console, COMSIG_ADVANCED_CAMERA_EYE_CREATED) + register_eyeobj(eyeobj) + +/obj/item/circuit_component/advanced_camera_intercept/proc/register_eyeobj(mob/eye/camera/remote/eyeobj) + RegisterSignal(eyeobj, COMSIG_REMOTE_CAMERA_ASSIGN_USER, PROC_REF(on_parent_assign_user)) + if(enabled.value) + attached_console.current_user?.click_intercept = src + +/obj/item/circuit_component/advanced_camera_intercept/unregister_usb_parent(atom/movable/parent) + if(istype(parent, /obj/machinery/computer/camera_advanced)) + attached_console.current_user?.click_intercept = null + if(attached_console.eyeobj) + UnregisterSignal(attached_console.eyeobj, COMSIG_REMOTE_CAMERA_ASSIGN_USER) + UnregisterSignal(attached_console, COMSIG_ADVANCED_CAMERA_EYE_CREATED) + attached_console = null + return ..() + +/obj/item/circuit_component/advanced_camera_intercept/proc/on_parent_assign_user(datum/_source, mob/living/new_user, mob/living/old_user) + SIGNAL_HANDLER + old_user?.click_intercept = null + if(enabled.value) + new_user?.click_intercept = src + +/obj/item/circuit_component/advanced_camera_intercept/proc/InterceptClickOn(mob/user, params, atom/target) + var/list/modifiers = params2list(params) + if(LAZYACCESS(modifiers, SHIFT_CLICK)) + return + var/turf/target_turf = get_turf(target) + if(!target_turf) + return + if(!GLOB.cameranet.checkTurfVis(target_turf)) + return + if(TIMER_COOLDOWN_RUNNING(parent.shell, COOLDOWN_CIRCUIT_TARGET_INTERCEPT)) + return + target_x.set_output(target.x) + target_y.set_output(target.y) + target_z.set_output(target.z) + + target_port.set_output(target) + + if(LAZYACCESS(modifiers, RIGHT_CLICK)) + secondary_click.set_output(COMPONENT_SIGNAL) + else + primary_click.set_output(COMPONENT_SIGNAL) + if(parent.shell) + TIMER_COOLDOWN_START(parent.shell, COOLDOWN_CIRCUIT_TARGET_INTERCEPT, 1 SECONDS) + return TRUE diff --git a/code/game/objects/structures/construction_console/construction_console.dm b/code/game/objects/structures/construction_console/construction_console.dm index 042511cceb8..249860c99b6 100644 --- a/code/game/objects/structures/construction_console/construction_console.dm +++ b/code/game/objects/structures/construction_console/construction_console.dm @@ -17,6 +17,7 @@ icon_screen = "mining" icon_keyboard = "rd_key" light_color = LIGHT_COLOR_PINK + add_usb_port = FALSE ///Area that the eyeobj will be constrained to. If null, eyeobj will be able to build and move anywhere. var/area/allowed_area ///Assoc. list ("structure_name" : count) that keeps track of the number of special structures that can't be built with an RCD, for example, tiny fans or turrets. diff --git a/code/modules/mob/eye/camera/remote.dm b/code/modules/mob/eye/camera/remote.dm index 901e3b16436..08fc09fd37c 100644 --- a/code/modules/mob/eye/camera/remote.dm +++ b/code/modules/mob/eye/camera/remote.dm @@ -51,6 +51,7 @@ /mob/eye/camera/remote/proc/assign_user(mob/living/new_user) var/mob/living/old_user = user_ref?.resolve() + SEND_SIGNAL(src, COMSIG_REMOTE_CAMERA_ASSIGN_USER, new_user, old_user) if(old_user) old_user.remote_control = null old_user.reset_perspective(null) diff --git a/code/modules/shuttle/shuttle_consoles/navigation_computer.dm b/code/modules/shuttle/shuttle_consoles/navigation_computer.dm index 377955ae328..c10b490950f 100644 --- a/code/modules/shuttle/shuttle_consoles/navigation_computer.dm +++ b/code/modules/shuttle/shuttle_consoles/navigation_computer.dm @@ -3,6 +3,7 @@ desc = "Used to designate a precise transit location for a spacecraft." jump_action = null should_supress_view_changes = FALSE + add_usb_port = FALSE // Docking cameras should only interact with their current z-level. move_up_action = null