From 0432d97d7c6fc5dfa4cf64796405d66d26c98e39 Mon Sep 17 00:00:00 2001 From: SmArtKar <44720187+SmArtKar@users.noreply.github.com> Date: Mon, 2 Aug 2021 10:19:48 +0300 Subject: [PATCH] BCI overlays and click interception (#60503) --- code/__DEFINES/cooldowns.dm | 1 + .../research/designs/wiremod_designs.dm | 27 ++++ code/modules/research/techweb/all_nodes.dm | 18 ++- code/modules/wiremod/component.dm | 8 ++ .../wiremod/components/hud/bar_overlay.dm | 60 ++++++++ .../wiremod/components/hud/counter_overlay.dm | 102 ++++++++++++++ .../wiremod/components/hud/object_overlay.dm | 132 ++++++++++++++++++ .../components/hud/target_intercept.dm | 64 +++++++++ .../wiremod/shell/brain_computer_interface.dm | 18 +-- icons/hud/screen_bci.dmi | Bin 0 -> 1665 bytes tgstation.dme | 4 + 11 files changed, 423 insertions(+), 11 deletions(-) create mode 100644 code/modules/wiremod/components/hud/bar_overlay.dm create mode 100644 code/modules/wiremod/components/hud/counter_overlay.dm create mode 100644 code/modules/wiremod/components/hud/object_overlay.dm create mode 100644 code/modules/wiremod/components/hud/target_intercept.dm create mode 100644 icons/hud/screen_bci.dmi diff --git a/code/__DEFINES/cooldowns.dm b/code/__DEFINES/cooldowns.dm index 5f87af8fd8e..fe72b872053 100644 --- a/code/__DEFINES/cooldowns.dm +++ b/code/__DEFINES/cooldowns.dm @@ -48,6 +48,7 @@ #define COOLDOWN_CIRCUIT_SPEECH "circuit_speech" #define COOLDOWN_CIRCUIT_PATHFIND_SAME "circuit_pathfind_same" #define COOLDOWN_CIRCUIT_PATHFIND_DIF "circuit_pathfind_different" +#define COOLDOWN_CIRCUIT_TARGET_INTERCEPT "circuit_target_intercept" //TIMER COOLDOWN MACROS diff --git a/code/modules/research/designs/wiremod_designs.dm b/code/modules/research/designs/wiremod_designs.dm index 3b5fb12c639..2d6ec2b75b6 100644 --- a/code/modules/research/designs/wiremod_designs.dm +++ b/code/modules/research/designs/wiremod_designs.dm @@ -238,6 +238,33 @@ id = "comp_module" build_path = /obj/item/circuit_component/module +/datum/design/component/bci + category = list("Circuitry", "BCI Components") + +/datum/design/component/bci/bci_action + name = "BCI Action Component" + id = "comp_bci_action" + build_path = /obj/item/circuit_component/bci_action + +/datum/design/component/bci/object_overlay + name = "Object Overlay Component" + id = "comp_object_overlay" + build_path = /obj/item/circuit_component/object_overlay + +/datum/design/component/bci/bar_overlay + name = "Bar Overlay Component" + id = "comp_bar_overlay" + build_path = /obj/item/circuit_component/object_overlay/bar + +/datum/design/component/bci/target_intercept + name = "BCI Target Interceptor" + id = "comp_target_intercept" + build_path = /obj/item/circuit_component/target_intercept + +/datum/design/component/bci/counter_overlay + name = "Counter Overlay Component" + id = "comp_counter_overlay" + build_path = /obj/item/circuit_component/counter_overlay /datum/design/compact_remote_shell name = "Compact Remote Shell" diff --git a/code/modules/research/techweb/all_nodes.dm b/code/modules/research/techweb/all_nodes.dm index f9241e18cd1..2dcdcee17bc 100644 --- a/code/modules/research/techweb/all_nodes.dm +++ b/code/modules/research/techweb/all_nodes.dm @@ -657,8 +657,6 @@ description = "Grants access to more complicated shell designs." prereq_ids = list("basic_circuitry", "engineering") design_ids = list( - "bci_implanter", - "bci_shell", "bot_shell", "door_shell", "controller_shell", @@ -667,6 +665,22 @@ ) research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 2500) +/datum/techweb_node/bci_shells + id = "bci_shells" + display_name = "Brain-Computer Interfaces" + description = "Grants access to biocompatable shell designs and components." + prereq_ids = list("adv_shells") + design_ids = list( + "bci_implanter", + "bci_shell", + "comp_bar_overlay", + "comp_bci_action", + "comp_target_intercept", + "comp_counter_overlay", + "comp_object_overlay", + ) + research_costs = list(TECHWEB_POINT_TYPE_GENERIC = 500) + /datum/techweb_node/movable_shells_tech id = "movable_shells" display_name = "Movable Shell Research" diff --git a/code/modules/wiremod/component.dm b/code/modules/wiremod/component.dm index 376a7efa004..4016f2e6fb4 100644 --- a/code/modules/wiremod/component.dm +++ b/code/modules/wiremod/component.dm @@ -50,6 +50,9 @@ // Whether the component is removable or not. Only affects user UI var/removable = TRUE + // Defines which shells can accept this component. If set to null then all shells can use it. + var/required_shells = null + /obj/item/circuit_component/Initialize() . = ..() if(name == COMPONENT_DEFAULT_NAME) @@ -200,6 +203,11 @@ /// Called when this component is about to be added to an integrated_circuit. /obj/item/circuit_component/proc/add_to(obj/item/integrated_circuit/added_to) + if(required_shells && LAZYLEN(required_shells)) + for(var/shell_type in required_shells) + if(istype(added_to, shell_type) || istype(added_to.loc, shell_type)) + return TRUE + return FALSE return TRUE /// Called when this component is removed from an integrated_circuit. diff --git a/code/modules/wiremod/components/hud/bar_overlay.dm b/code/modules/wiremod/components/hud/bar_overlay.dm new file mode 100644 index 00000000000..004d7b38e34 --- /dev/null +++ b/code/modules/wiremod/components/hud/bar_overlay.dm @@ -0,0 +1,60 @@ +/** + * # Bar Overlay Component + * + * Basically an advanced verion of object overlay component that shows a horizontal/vertical bar. + * Requires a BCI shell. + */ + +#define BAR_OVERLAY_LIMIT 10 + +/obj/item/circuit_component/object_overlay/bar + display_name = "Bar Overlay" + desc = "Requires a BCI shell. A component that shows a bar overlay ontop of an object from a range of 0 to 100." + + var/datum/port/input/bar_number + +/obj/item/circuit_component/object_overlay/bar/Initialize() + . = ..() + bar_number = add_input_port("Number", PORT_TYPE_NUMBER) + +/obj/item/circuit_component/object_overlay/bar/populate_options() + var/static/component_options_bar = list( + "Vertical", + "Horizontal" + ) + options = component_options_bar + + var/static/options_to_icons_bar = list( + "Vertical" = "barvert", + "Horizontal" = "barhoriz" + ) + options_map = options_to_icons_bar + +/obj/item/circuit_component/object_overlay/bar/show_to_owner(atom/target_atom, mob/living/owner) + if(LAZYLEN(active_overlays) >= BAR_OVERLAY_LIMIT) + return + + if(active_overlays[target_atom]) + QDEL_NULL(active_overlays[target_atom]) + + var/number_clear = clamp(bar_number.input_value, 0, 100) + if(current_option == "Horizontal") + number_clear = round(number_clear / 6.25) * 6.25 + else if(current_option == "Vertical") + number_clear = round(number_clear / 10) * 10 + var/image/cool_overlay = image(icon = 'icons/hud/screen_bci.dmi', loc = target_atom, icon_state = "[options_map[current_option]][number_clear]", layer = RIPPLE_LAYER) + + if(image_pixel_x.input_value) + cool_overlay.pixel_x = image_pixel_x.input_value + + if(image_pixel_y.input_value) + cool_overlay.pixel_y = image_pixel_y.input_value + + active_overlays[target_atom] = WEAKREF(target_atom.add_alt_appearance( + /datum/atom_hud/alternate_appearance/basic/one_person, + "bar_overlay_[REF(src)]", + cool_overlay, + owner, + )) + +#undef BAR_OVERLAY_LIMIT diff --git a/code/modules/wiremod/components/hud/counter_overlay.dm b/code/modules/wiremod/components/hud/counter_overlay.dm new file mode 100644 index 00000000000..c2158ec76a1 --- /dev/null +++ b/code/modules/wiremod/components/hud/counter_overlay.dm @@ -0,0 +1,102 @@ +/** + * # Counter Overlay Component + * + * Shows an counter overlay. + * Requires a BCI shell. + */ + +/obj/item/circuit_component/counter_overlay + display_name = "Counter Overlay" + desc = "A component that shows an three digit counter. Requires a BCI shell." + + required_shells = list(/obj/item/organ/cyberimp/bci) + + var/datum/port/input/counter_number + + var/datum/port/input/image_pixel_x + var/datum/port/input/image_pixel_y + + var/datum/port/input/signal_update + + var/obj/item/organ/cyberimp/bci/bci + var/list/numbers = list() + var/counter_appearance + +/obj/item/circuit_component/counter_overlay/Initialize() + . = ..() + counter_number = add_input_port("Displayed Number", PORT_TYPE_NUMBER) + + signal_update = add_input_port("Update Overlay", PORT_TYPE_SIGNAL) + + image_pixel_x = add_input_port("X-Axis Shift", PORT_TYPE_NUMBER) + image_pixel_y = add_input_port("Y-Axis Shift", PORT_TYPE_NUMBER) + +/obj/item/circuit_component/counter_overlay/register_shell(atom/movable/shell) + bci = shell + RegisterSignal(shell, COMSIG_ORGAN_REMOVED, .proc/on_organ_removed) + +/obj/item/circuit_component/counter_overlay/unregister_shell(atom/movable/shell) + bci = null + QDEL_NULL(counter_appearance) + for(var/number in numbers) + QDEL_NULL(number) + UnregisterSignal(shell, COMSIG_ORGAN_REMOVED) + +/obj/item/circuit_component/counter_overlay/input_received(datum/port/input/port) + . = ..() + + if(. || !bci) + return + + var/mob/living/owner = bci.owner + + if(!owner || !istype(owner) || !owner.client) + return + + for(var/number in numbers) + QDEL_NULL(number) + numbers = list() + + QDEL_NULL(counter_appearance) + var/image/counter = image(icon = 'icons/hud/screen_bci.dmi', icon_state = "hud_numbers", loc = owner) + if(image_pixel_x.input_value) + counter.pixel_x = image_pixel_x.input_value + if(image_pixel_y.input_value) + counter.pixel_y = image_pixel_y.input_value + + counter_appearance = WEAKREF(owner.add_alt_appearance( + /datum/atom_hud/alternate_appearance/basic/one_person, + "counter_overlay_[REF(src)]", + counter, + owner, + )) + + var/cleared_number = clamp(round(counter_number.input_value), 0, 999) + + for(var/i = 1 to 3) + var/cur_num = round(cleared_number / (10 ** (3 - i))) % 10 + var/image/number = image(icon = 'icons/hud/screen_bci.dmi', icon_state = "hud_number_[cur_num]", loc = owner) + + if(image_pixel_x.input_value) + number.pixel_x = image_pixel_x.input_value + (i - 1) * 9 + if(image_pixel_y.input_value) + number.pixel_y = image_pixel_y.input_value + + numbers.Add(WEAKREF(owner.add_alt_appearance( + /datum/atom_hud/alternate_appearance/basic/one_person, + "counter_overlay_[REF(src)]_[i]", + number, + owner, + ))) + +/obj/item/circuit_component/counter_overlay/proc/on_organ_removed(datum/source, mob/living/carbon/owner) + SIGNAL_HANDLER + QDEL_NULL(counter_appearance) + for(var/number in numbers) + QDEL_NULL(number) + +/obj/item/circuit_component/counter_overlay/Destroy() + QDEL_NULL(counter_appearance) + for(var/number in numbers) + QDEL_NULL(number) + return ..() diff --git a/code/modules/wiremod/components/hud/object_overlay.dm b/code/modules/wiremod/components/hud/object_overlay.dm new file mode 100644 index 00000000000..dd502329f97 --- /dev/null +++ b/code/modules/wiremod/components/hud/object_overlay.dm @@ -0,0 +1,132 @@ +/** + * # Object Overlay Component + * + * Shows an overlay ontop of an object. Toggleable. + * Requires a BCI shell. + */ + +#define OBJECT_OVERLAY_LIMIT 10 + +/obj/item/circuit_component/object_overlay + display_name = "Object Overlay" + desc = "Requires a BCI shell. A component that shows an overlay on top of an object." + + required_shells = list(/obj/item/organ/cyberimp/bci) + + /// Target atom + var/datum/port/input/target + + var/datum/port/input/image_pixel_x + var/datum/port/input/image_pixel_y + + /// On/Off signals + var/datum/port/input/signal_on + var/datum/port/input/signal_off + + var/obj/item/organ/cyberimp/bci/bci + var/list/active_overlays = list() + var/list/options_map + +/obj/item/circuit_component/object_overlay/Initialize() + . = ..() + target = add_input_port("Target", PORT_TYPE_ATOM) + + signal_on = add_input_port("Create Overlay", PORT_TYPE_SIGNAL) + signal_off = add_input_port("Remove Overlay", PORT_TYPE_SIGNAL) + + image_pixel_x = add_input_port("X-Axis Shift", PORT_TYPE_NUMBER) + image_pixel_y = add_input_port("Y-Axis Shift", PORT_TYPE_NUMBER) + +/obj/item/circuit_component/object_overlay/Destroy() + for(var/active_overlay in active_overlays) + QDEL_NULL(active_overlay) + return ..() + +/obj/item/circuit_component/object_overlay/populate_options() + var/static/component_options = list( + "Corners (Blue)", + "Corners (Red)", + "Circle (Blue)", + "Circle (Red)", + "Small Corners (Blue)", + "Small Corners (Red)", + "Triangle (Blue)", + "Triangle (Red)", + "HUD mark (Blue)", + "HUD mark (Red)" + ) + options = component_options + + var/static/options_to_icons = list( + "Corners (Blue)" = "hud_corners", + "Corners (Red)" = "hud_corners_red", + "Circle (Blue)" = "hud_circle", + "Circle (Red)" = "hud_circle_red", + "Small Corners (Blue)" = "hud_corners_small", + "Small Corners (Red)" = "hud_corners_small_red", + "Triangle (Blue)" = "hud_triangle", + "Triangle (Red)" = "hud_triangle_red", + "HUD mark (Blue)" = "hud_mark", + "HUD mark (Red)" = "hud_mark_red" + ) + options_map = options_to_icons + +/obj/item/circuit_component/object_overlay/register_shell(atom/movable/shell) + bci = shell + RegisterSignal(shell, COMSIG_ORGAN_REMOVED, .proc/on_organ_removed) + +/obj/item/circuit_component/object_overlay/unregister_shell(atom/movable/shell) + bci = null + UnregisterSignal(shell, COMSIG_ORGAN_REMOVED) + +/obj/item/circuit_component/object_overlay/input_received(datum/port/input/port) + . = ..() + + if(. || !bci) + return + + var/mob/living/owner = bci.owner + var/atom/target_atom = target.input_value + + if(!owner || !istype(owner) || !owner.client || !target_atom) + return + + if(COMPONENT_TRIGGERED_BY(signal_on, port)) + show_to_owner(target_atom, owner) + + if(COMPONENT_TRIGGERED_BY(signal_off, port) && (target_atom in active_overlays)) + QDEL_NULL(active_overlays[target_atom]) + active_overlays.Remove(target_atom) + +/obj/item/circuit_component/object_overlay/proc/show_to_owner(atom/target_atom, mob/living/owner) + if(LAZYLEN(active_overlays) >= OBJECT_OVERLAY_LIMIT) + return + + if(active_overlays[target_atom]) + QDEL_NULL(active_overlays[target_atom]) + + var/image/cool_overlay = image(icon = 'icons/hud/screen_bci.dmi', loc = target_atom, icon_state = options_map[current_option], layer = RIPPLE_LAYER) + + if(image_pixel_x.input_value) + cool_overlay.pixel_x = image_pixel_x.input_value + + if(image_pixel_y.input_value) + cool_overlay.pixel_y = image_pixel_y.input_value + + var/alt_appearance = WEAKREF(target_atom.add_alt_appearance( + /datum/atom_hud/alternate_appearance/basic/one_person, + "object_overlay_[REF(src)]", + cool_overlay, + owner, + )) + + active_overlays[target_atom] = alt_appearance + +/obj/item/circuit_component/object_overlay/proc/on_organ_removed(datum/source, mob/living/carbon/owner) + SIGNAL_HANDLER + + for(var/atom/target_atom in active_overlays) + QDEL_NULL(active_overlays[target_atom]) + active_overlays.Remove(target_atom) + +#undef OBJECT_OVERLAY_LIMIT diff --git a/code/modules/wiremod/components/hud/target_intercept.dm b/code/modules/wiremod/components/hud/target_intercept.dm new file mode 100644 index 00000000000..5de5695da33 --- /dev/null +++ b/code/modules/wiremod/components/hud/target_intercept.dm @@ -0,0 +1,64 @@ +/** + * # Target Intercept Component + * + * When activated intercepts next click and outputs clicked atom. + * Requires a BCI shell. + */ + +/obj/item/circuit_component/target_intercept + display_name = "Target Intercept" + desc = "Requires a BCI shell. When activated, this component will allow user to target an object using their brain and will output the reference to said object." + + required_shells = list(/obj/item/organ/cyberimp/bci) + + var/datum/port/output/clicked_atom + + var/obj/item/organ/cyberimp/bci/bci + var/intercept_cooldown = 1 SECONDS + +/obj/item/circuit_component/target_intercept/Initialize() + . = ..() + trigger_input = add_input_port("Activate", PORT_TYPE_SIGNAL) + trigger_output = add_output_port("Triggered", PORT_TYPE_SIGNAL) + clicked_atom = add_output_port("Targeted Object", PORT_TYPE_ATOM) + +/obj/item/circuit_component/target_intercept/register_shell(atom/movable/shell) + bci = shell + RegisterSignal(shell, COMSIG_ORGAN_REMOVED, .proc/on_organ_removed) + +/obj/item/circuit_component/target_intercept/unregister_shell(atom/movable/shell) + bci = null + UnregisterSignal(shell, COMSIG_ORGAN_REMOVED) + +/obj/item/circuit_component/target_intercept/input_received(datum/port/input/port) + . = ..() + + if(. || !bci) + return + + var/mob/living/owner = bci.owner + if(!owner || !istype(owner) || !owner.client) + return + + if(TIMER_COOLDOWN_CHECK(parent, COOLDOWN_CIRCUIT_TARGET_INTERCEPT)) + return + + to_chat(owner, "Left-click to trigger target interceptor!") + owner.client.click_intercept = src + +/obj/item/circuit_component/target_intercept/proc/on_organ_removed(datum/source, mob/living/carbon/owner) + SIGNAL_HANDLER + + if(owner.client && owner.client.click_intercept == src) + owner.client.click_intercept = null + +/obj/item/circuit_component/target_intercept/proc/InterceptClickOn(mob/user, params, atom/object) + user.client.click_intercept = null + clicked_atom.set_output(object) + trigger_output.set_output(COMPONENT_SIGNAL) + TIMER_COOLDOWN_START(parent, COOLDOWN_CIRCUIT_TARGET_INTERCEPT, intercept_cooldown) + +/obj/item/circuit_component/target_intercept/get_ui_notices() + . = ..() + . += create_ui_notice("Target Interception Cooldown: [DisplayTimeText(intercept_cooldown)]", "orange", "stopwatch") + . += create_ui_notice("Only usable in BCI circuits", "orange", "info") diff --git a/code/modules/wiremod/shell/brain_computer_interface.dm b/code/modules/wiremod/shell/brain_computer_interface.dm index e05720a420f..d43c402efb7 100644 --- a/code/modules/wiremod/shell/brain_computer_interface.dm +++ b/code/modules/wiremod/shell/brain_computer_interface.dm @@ -11,9 +11,9 @@ AddComponent(/datum/component/shell, list( new /obj/item/circuit_component/bci_core, - new /obj/item/circuit_component/bci_action("One"), - new /obj/item/circuit_component/bci_action("Two"), - new /obj/item/circuit_component/bci_action("Three"), + new /obj/item/circuit_component/bci_action(null, "One"), + new /obj/item/circuit_component/bci_action(null, "Two"), + new /obj/item/circuit_component/bci_action(null, "Three"), ), SHELL_CAPACITY_SMALL) /obj/item/organ/cyberimp/bci/Insert(mob/living/carbon/reciever, special, drop_if_replaced) @@ -33,13 +33,10 @@ else return ..() -/obj/item/circuit_component/bci - display_name = "Brain-Computer Interface" - desc = "Used to receive inputs for the brain-computer interface. User is presented with three buttons." - /obj/item/circuit_component/bci_action display_name = "BCI Action" desc = "Represents an action the user can take when implanted with the brain-computer interface." + required_shells = list(/obj/item/organ/cyberimp/bci) /// The name to use for the button var/datum/port/input/button_name @@ -394,12 +391,15 @@ if (. == SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN) return . + if(!user.Adjacent(src)) + return + if (locked) balloon_alert(user, "it's locked!") return SECONDARY_ATTACK_CANCEL_ATTACK_CHAIN var/obj/item/organ/cyberimp/bci/bci_to_implant_resolved = bci_to_implant?.resolve() - if (isnull(bci_to_implant_resolved) && user.Adjacent(src)) + if (isnull(bci_to_implant_resolved)) balloon_alert(user, "no bci inserted!") else user.put_in_hands(bci_to_implant_resolved) @@ -524,7 +524,7 @@ if (!isnull(message)) if (COOLDOWN_FINISHED(src, message_cooldown)) COOLDOWN_START(src, message_cooldown, 5 SECONDS) - balloon_alert(user, "it won't budge!") + balloon_alert(user, message) return diff --git a/icons/hud/screen_bci.dmi b/icons/hud/screen_bci.dmi new file mode 100644 index 0000000000000000000000000000000000000000..2458e03f44ff268f5c409fed4bc97728cd29e71e GIT binary patch literal 1665 zcmV-{27dX8P)%0000UP)t-sz`(#; zm%KrJqQ(&sp%M}R|NrkkB?~4#7DZ#gz`&Bi;WGdL00DGTPE!Ct=GbNc00BRGR9JLG zWpiV4X>fFDZ*Bkpc$}ryOKyWO5C-5iJ_Sm)6|ynUMK@KVDvRa{$cdd`m4`Gqt=iMq z&@QS9lo`6S(KlxJZ2{ZxY5#mUhP#&odowWDz6&42B;qW1x7yWyB4ZZR#$IdpJsF&G z#t%33O;_h0I$yS7fwnOCtAYg=P^}rqzEGP+eTnm5^_owZl zRZa8n1=e=qz^d-L^!#ETFW>A>L95^u4NM&hdezGT`0cnlPNR7__84}cB@Xy?Z_zbv~Q z&Gz*U+J)I?SXJE)vF$VNG-ZEeLk%co+60fvnd^5k0ko>2z%th`U7)j-C~FxehU=B zIdg0=#EvjSH}kKG3vD}AK!pb#Aht6FGz|cZqQWyglEEmtKkpwNc|dpWxsLt-oYL}m++ zKr6Tfcu0~f>LU7#@Q@I|PXKT|37vy7~cGWB#RNJOTLNtt@O6eJ=O#-tTMpmI6Ch{#PcMMWx?^NWhy z1XI*ujZ3rZQ5HKa3^7`+5?Mztx5QnD?9B4AxEpy~+?B}AEH4iL7$6fsKF?nP$me<2 zM{@}{22GRU6}Kuu{O17b;qwZhX#ri(oMBOLcEPZHe!L5Y^GpDv#RpN10UxvpRz2Sa zfKM7V27Hn%I0mi(WE{&eka4@%7&r&e^2U=q@!bGMCL%qR8>zcbwy9VxZFmEFzn*dCJg@Fj5A7Ex60vHc4F%SXR16TqP!1Mqkfe2uJ zfF2+MSRO#L34j0yz&t@;OwJAPO(b7QO=SZOThEJ^g5A4pVgJJRPP1!l@01NY7*xSH z2bfU=KmY_l00ck)1V8`;KmY_V`n4JX%ns1^TJ6UHIMUSX{n76S`05!ipLd#Fzqfaq zU1NKv*)_7=1CU505{X12kw_#GiR4^k*YkgbCIjHBXRv&(vAxr5Zq~nl4uG9}_>V>| zzLXNff3+%>@82)Czyrpwc+9WZ!A(PvCG00000 LNkvXXu0mjfC