diff --git a/code/game/objects/items/robot/robot_upgrades.dm b/code/game/objects/items/robot/robot_upgrades.dm index 0c3d7740fb..2746e23598 100644 --- a/code/game/objects/items/robot/robot_upgrades.dm +++ b/code/game/objects/items/robot/robot_upgrades.dm @@ -158,6 +158,7 @@ return 0 R.emag_items = 1 + R.robotact.update_static_data_for_all_viewers() return 1 /obj/item/borg/upgrade/basic/language diff --git a/code/modules/mob/living/silicon/robot/inventory.dm b/code/modules/mob/living/silicon/robot/inventory.dm index 45e5c935f8..30f423ac36 100644 --- a/code/modules/mob/living/silicon/robot/inventory.dm +++ b/code/modules/mob/living/silicon/robot/inventory.dm @@ -18,13 +18,11 @@ set hidden = 1 toggle_module(module) -/mob/living/silicon/robot/proc/uneq_active() - if(isnull(module_active)) +/mob/living/silicon/robot/proc/uneq_specific(obj/item/I) + if(!istype(I)) return - var/obj/item/I = module_active - for(var/datum/action/A as anything in I.actions) - A.Remove(src) - if(module_state_1 == module_active) + + if(module_state_1 == I) if(istype(module_state_1,/obj/item/borg/sight)) sight_mode &= ~module_state_1:sight_mode if (client) @@ -34,30 +32,46 @@ module_state_1:loc = module //So it can be used again later module_state_1 = null inv1.icon_state = "inv1" - else if(module_state_2 == module_active) + else if(module_state_2 == I) if(istype(module_state_2,/obj/item/borg/sight)) sight_mode &= ~module_state_2:sight_mode if (client) client.screen -= module_state_2 contents -= module_state_2 module_active = null - module_state_2:loc = module + module_state_2:loc = module //So it can be used again later module_state_2 = null inv2.icon_state = "inv2" - else if(module_state_3 == module_active) + else if(module_state_3 == I) if(istype(module_state_3,/obj/item/borg/sight)) sight_mode &= ~module_state_3:sight_mode if (client) client.screen -= module_state_3 contents -= module_state_3 module_active = null - module_state_3:loc = module + module_state_3:loc = module //So it can be used again later module_state_3 = null inv3.icon_state = "inv3" + else + return + + for(var/datum/action/A as anything in I.actions) + A.Remove(src) + after_equip() update_icon() hud_used.update_robot_modules_display() +/mob/living/silicon/robot/proc/uneq_active() + if(isnull(module_active)) + return + + var/obj/item/I = module_active + for(var/datum/action/A as anything in I.actions) + A.Remove(src) + + uneq_specific(I) + /mob/living/silicon/robot/proc/uneq_all() module_active = null @@ -100,6 +114,17 @@ after_equip() update_icon() +// Just used for pretty display in TGUI +/mob/living/silicon/robot/proc/get_slot_from_module(obj/item/I) + if(module_state_1 == I) + return 1 + else if(module_state_2 == I) + return 2 + else if(module_state_3 == I) + return 3 + else + return 0 + /mob/living/silicon/robot/proc/activated(obj/item/O) if(module_state_1 == O) return 1 diff --git a/code/modules/mob/living/silicon/robot/robot.dm b/code/modules/mob/living/silicon/robot/robot.dm index a1d73d948c..69dd29784a 100644 --- a/code/modules/mob/living/silicon/robot/robot.dm +++ b/code/modules/mob/living/silicon/robot/robot.dm @@ -135,6 +135,7 @@ spark_system = new /datum/effect/effect/system/spark_spread() spark_system.set_up(5, 0, src) spark_system.attach(src) + robotact = new(src) add_language("Robot Talk", 1) add_language(LANGUAGE_GALCOM, 1) @@ -318,6 +319,7 @@ revert_shell() // To get it out of the GLOB list. qdel(wires) wires = null + QDEL_NULL(robotact) return ..() // CONTINUE CODING HERE @@ -379,6 +381,7 @@ updatename() hud_used.update_robot_modules_display() notify_ai(ROBOT_NOTIFICATION_NEW_MODULE, module.name) + robotact?.update_static_data_for_all_viewers() /mob/living/silicon/robot/proc/update_braintype() if(istype(mmi, /obj/item/mmi/digital/posibrain)) @@ -465,17 +468,6 @@ sprite_datum.handle_extra_customization(src) -/mob/living/silicon/robot/proc/self_diagnosis() - if(!is_component_functioning("diagnosis unit")) - return null - - var/dat = "[src.name] Self-Diagnosis Report\n" - for (var/V in components) - var/datum/robot_component/C = components[V] - dat += span_bold("[C.name]") + "
Brute Damage:[C.brute_damage]
Electronics Damage:[C.electronics_damage]
Powered:[(!C.idle_usage || C.is_powered()) ? "Yes" : "No"]
Toggled:[ C.toggled ? "Yes" : "No"]

" - - return dat - /mob/living/silicon/robot/verb/toggle_lights() set category = "Abilities.Silicon" set name = "Toggle Lights" @@ -485,44 +477,6 @@ handle_light() update_icon() -/mob/living/silicon/robot/verb/self_diagnosis_verb() - set category = "Abilities.Silicon" - set name = "Self Diagnosis" - - if(!is_component_functioning("diagnosis unit")) - to_chat(src, span_red("Your self-diagnosis component isn't functioning.")) - - var/datum/robot_component/CO = get_component("diagnosis unit") - if (!cell_use_power(CO.active_usage)) - to_chat(src, span_red("Low Power.")) - var/dat = self_diagnosis() - src << browse(dat, "window=robotdiagnosis") - - -/mob/living/silicon/robot/verb/toggle_component() - set category = "Abilities.Silicon" - set name = "Toggle Component" - set desc = "Toggle a component, conserving power." - - var/list/installed_components = list() - for(var/V in components) - if(V == "power cell") continue - var/datum/robot_component/C = components[V] - if(C.installed) - installed_components += V - - var/toggle = tgui_input_list(src, "Which component do you want to toggle?", "Toggle Component", installed_components) - if(!toggle) - return - - var/datum/robot_component/C = components[toggle] - if(C.toggled) - C.toggled = 0 - to_chat(src, span_red("You disable [C.name].")) - else - C.toggled = 1 - to_chat(src, span_red("You enable [C.name].")) - /mob/living/silicon/robot/verb/spark_plug() //So you can still sparkle on demand without violence. set category = "Abilities.Silicon" set name = "Emit Sparks" @@ -858,6 +812,7 @@ module = null updatename("Default") has_recoloured = FALSE + robotact?.update_static_data_for_all_viewers() /mob/living/silicon/robot/proc/ColorMate() set name = "Recolour Module" @@ -1101,42 +1056,7 @@ add_overlay(open_overlay) /mob/living/silicon/robot/proc/installed_modules() - if(weapon_lock) - to_chat(src, span_filter_warning("" + span_red("Weapon lock active, unable to use modules! Count:[weaponlock_time]") + "")) - return - - if(!module) - pick_module() - return - var/dat = "Modules\n" - dat += {" - Activated Modules -
- Module 1: [module_state_1 ? "[module_state_1]" : "No Module"]
- Module 2: [module_state_2 ? "
[module_state_2]" : "No Module"]
- Module 3: [module_state_3 ? "
[module_state_3]" : "No Module"]
-
- Installed Modules

"} - - - for (var/obj in module.modules) - if (!obj) - dat += span_bold("Resource depleted") + "
" - else if(activated(obj)) - dat += text("[obj]: Activated
") - else - dat += text("[obj]:
Activate
") - if (emagged || emag_items) - for (var/obj in module.emag) - if (!obj) - dat += span_bold("Resource depleted") + "
" - else if(activated(obj)) - dat += text("[obj]: Activated
") - else - dat += text("[obj]: Activate
") - - src << browse(dat, "window=robotmod") - + robotact.tgui_interact(src) /mob/living/silicon/robot/Topic(href, href_list) if(..()) @@ -1150,69 +1070,6 @@ subsystem_alarm_monitor() return 1 - if (href_list["mod"]) - var/obj/item/O = locate(href_list["mod"]) - if (istype(O) && (O.loc == src)) - O.attack_self(src) - return 1 - - if (href_list["act"]) - var/obj/item/O = locate(href_list["act"]) - if (!istype(O)) - return 1 - - if(!((O in src.module.modules) || (O in src.module.emag))) - return 1 - - if(activated(O)) - to_chat(src, span_filter_notice("Already activated.")) - return 1 - if(!module_state_1) - module_state_1 = O - O.hud_layerise() - O.equipped_robot() - contents += O - if(istype(module_state_1,/obj/item/borg/sight)) - sight_mode |= module_state_1:sight_mode - else if(!module_state_2) - module_state_2 = O - O.hud_layerise() - O.equipped_robot() - contents += O - if(istype(module_state_2,/obj/item/borg/sight)) - sight_mode |= module_state_2:sight_mode - else if(!module_state_3) - module_state_3 = O - O.hud_layerise() - O.equipped_robot() - contents += O - if(istype(module_state_3,/obj/item/borg/sight)) - sight_mode |= module_state_3:sight_mode - else - to_chat(src, span_filter_notice("You need to disable a module first!")) - installed_modules() - return 1 - - if (href_list["deact"]) - var/obj/item/O = locate(href_list["deact"]) - if(activated(O)) - if(module_state_1 == O) - module_state_1 = null - contents -= O - else if(module_state_2 == O) - module_state_2 = null - contents -= O - else if(module_state_3 == O) - module_state_3 = null - contents -= O - else - to_chat(src, span_filter_notice("Module isn't activated.")) - else - to_chat(src, span_filter_notice("Module isn't activated.")) - installed_modules() - return 1 - return - /mob/living/silicon/robot/proc/radio_menu() radio.interact(src)//Just use the radio's Topic() instead of bullshit special-snowflake code @@ -1492,6 +1349,7 @@ sleep(6) if(prob(50)) emagged = 1 + robotact.update_static_data_for_all_viewers() lawupdate = 0 disconnect_from_ai() to_chat(user, span_filter_notice("You emag [src]'s interface.")) @@ -1783,3 +1641,12 @@ return (has_basic_upgrade(given_type) || has_advanced_upgrade(given_type) || has_restricted_upgrade(given_type) || has_no_prod_upgrade(given_type)) #undef CYBORG_POWER_USAGE_MULTIPLIER + +/mob/living/silicon/robot/vv_edit_var(var_name, var_value) + switch(var_name) + if(NAMEOF(src, emagged)) + robotact?.update_static_data_for_all_viewers() + if(NAMEOF(src, emag_items)) + robotact?.update_static_data_for_all_viewers() + + . = ..() diff --git a/code/modules/mob/living/silicon/robot/robot_ui.dm b/code/modules/mob/living/silicon/robot/robot_ui.dm new file mode 100644 index 0000000000..f7c0e31079 --- /dev/null +++ b/code/modules/mob/living/silicon/robot/robot_ui.dm @@ -0,0 +1,180 @@ +/mob/living/silicon/robot + var/datum/tgui_module/robot_ui/robotact + +// Major Control UI for all things robots can do. +/datum/tgui_module/robot_ui + name = "Robotact" + tgui_id = "Robotact" + +/datum/tgui_module/robot_ui/tgui_state(mob/user) + return GLOB.tgui_self_state + +/datum/tgui_module/robot_ui/tgui_static_data() + var/list/data = ..() + + var/mob/living/silicon/robot/R = host + + if(!R.module) + return data + + var/list/modules = list() + for(var/obj/item/I as anything in R.module.modules) + if(!I) + continue + + UNTYPED_LIST_ADD(modules, list( + "ref" = REF(I), + "name" = "[I]", + "icon" = "[I.icon]", + "icon_state" = "[I.icon_state]", + )) + data["modules_static"] = modules + + var/list/emag_modules = list() + if(R.emagged || R.emag_items) + for(var/obj/item/I as anything in R.module.emag) + if(!I) + continue + + UNTYPED_LIST_ADD(emag_modules, list( + "ref" = REF(I), + "name" = "[I.name]", + "icon" = "[I.icon]", + "icon_state" = "[I.icon_state]", + )) + data["emag_modules_static"] = emag_modules + + return data + +/datum/tgui_module/robot_ui/tgui_data() + var/list/data = ..() + + var/mob/living/silicon/robot/R = host + + data["module_name"] = R.module ? "[R.module]" : null + + if(!R.module) + return data + + data["name"] = R.name + data["ai"] = "[R.connected_ai]" + data["charge"] = R.cell?.charge + data["max_charge"] = R.cell?.maxcharge + data["health"] = R.health + data["max_health"] = R.getMaxHealth() + + data["weapon_lock"] = R.weapon_lock + + var/list/modules = list() + for(var/obj/item/I as anything in R.module.modules) + if(!I) + continue + + LAZYSET(modules, REF(I), R.get_slot_from_module(I)) + data["modules"] = modules + + var/list/emag_modules = list() + if(R.emagged || R.emag_items) + for(var/obj/item/I as anything in R.module.emag) + if(!I) + continue + + LAZYSET(emag_modules, REF(I), R.get_slot_from_module(I)) + data["emag_modules"] = emag_modules + + var/diagnosis_functional = R.is_component_functioning("diagnosis unit") + data["diag_functional"] = diagnosis_functional + + var/list/components = list() + for(var/V in R.components) + var/datum/robot_component/comp = R.components[V] + + UNTYPED_LIST_ADD(components, list( + "key" = V, + "name" = "[comp]", + "brute_damage" = comp.brute_damage, + "electronics_damage" = diagnosis_functional ? comp.electronics_damage : -1, + "max_damage" = diagnosis_functional ? comp.max_damage : -1, + "idle_usage" = diagnosis_functional ? comp.idle_usage : -1, + "is_powered" = diagnosis_functional ? comp.is_powered() : 0, + "toggled" = comp.toggled, + )) + data["components"] = components + + return data + +/datum/tgui_module/robot_ui/tgui_act(action, params) + . = ..() + if(.) + return + + var/mob/living/silicon/robot/R = host + + switch(action) + if("select_module") + R.pick_module() + . = TRUE + if("toggle_component") + var/component = params["component"] + var/datum/robot_component/C = LAZYACCESS(R.components, component) + if(istype(C)) + C.toggled = !C.toggled + if(C.toggled) + to_chat(usr, span_notice("You enable [C].")) + else + to_chat(usr, span_warning("You disable [C].")) + . = TRUE + if("toggle_module") + if(R.weapon_lock) + to_chat(usr, span_danger("Error: Modules locked.")) + return + var/obj/item/module = locate(params["ref"]) + if(istype(module)) + if(R.activated(module)) + R.uneq_specific(module) + else + R.activate_module(module) + . = TRUE + if("activate_module") + var/obj/item/module = locate(params["ref"]) + if(istype(module) && module.loc == R) + module.attack_self(R) + . = TRUE + + // Quick actions + if("quick_action_comm") + R.communicator?.attack_self(R) + . = TRUE + if("quick_action_pda") + R.rbPDA?.tgui_interact(R) + . = TRUE + if("quick_action_crew_manifest") + R.subsystem_crew_manifest() + . = TRUE + if("quick_action_law_manager") + R.subsystem_law_manager() + . = TRUE + if("quick_action_alarm_monitoring") + R.subsystem_alarm_monitor() + . = TRUE + if("quick_action_power_monitoring") + R.subsystem_power_monitor() + . = TRUE + if("quick_action_take_image") + R.take_image() + . = TRUE + if("quick_action_view_images") + R.view_images() + . = TRUE + if("quick_action_delete_images") + R.delete_images() + . = TRUE + if("quick_action_flashlight") + R.toggle_lights() + . = TRUE + if("quick_action_sensors") + R.sensor_mode() + . = TRUE + if("quick_action_sparks") + R.spark_plug() + . = TRUE diff --git a/tgui/packages/tgui/interfaces/Robotact/Diagnostics.tsx b/tgui/packages/tgui/interfaces/Robotact/Diagnostics.tsx new file mode 100644 index 0000000000..45b00946ea --- /dev/null +++ b/tgui/packages/tgui/interfaces/Robotact/Diagnostics.tsx @@ -0,0 +1,92 @@ +import { toTitleCase } from 'common/string'; +import { useBackend } from 'tgui/backend'; +import { + Box, + Button, + FitText, + LabeledList, + NoticeBox, + ProgressBar, + Section, + Stack, +} from 'tgui-core/components'; + +import { Data } from './types'; + +export const ComponentView = (props) => { + const { act, data } = useBackend(); + + const { diag_functional, components } = data; + + if (components.length) { + components.sort((a, b) => a.name.localeCompare(b.name)); + + return ( + + {components.map((mod) => ( + +
+ {toTitleCase(mod.name)} + + } + height={12} + mt={1} + buttons={ +
+
+ ))} +
+ ); + } + + return Diagnosis Module Offline; +}; diff --git a/tgui/packages/tgui/interfaces/Robotact/Modules.tsx b/tgui/packages/tgui/interfaces/Robotact/Modules.tsx new file mode 100644 index 0000000000..67cc3e6eed --- /dev/null +++ b/tgui/packages/tgui/interfaces/Robotact/Modules.tsx @@ -0,0 +1,71 @@ +import { useBackend } from 'tgui/backend'; +import { + Box, + ImageButton, + NoticeBox, + Section, + Stack, +} from 'tgui-core/components'; + +import { Data, Module as ModuleData } from './types'; + +export const Modules = (props) => { + const { act, data } = useBackend(); + + return ( + + +
Modules Locked + ) : null + } + > + + {data.modules_static.map((mod) => ( + + + + ))} + {data.emag_modules_static.map((mod) => ( + + + + ))} + +
+
+
+ ); +}; + +const Module = (props: { mod: ModuleData; activated: number }) => { + const { act } = useBackend(); + const { mod, activated } = props; + return ( + act('toggle_module', { ref: mod.ref })} + onRightClick={() => act('activate_module', { ref: mod.ref })} + tooltip={activated ? 'Right click to trigger' : ''} + tooltipPosition="bottom-end" + position="relative" + > + {!!activated && ( + + {activated} + + )} + {mod.name} + + ); +}; diff --git a/tgui/packages/tgui/interfaces/Robotact/StatusScreen.tsx b/tgui/packages/tgui/interfaces/Robotact/StatusScreen.tsx new file mode 100644 index 0000000000..08ab33efc2 --- /dev/null +++ b/tgui/packages/tgui/interfaces/Robotact/StatusScreen.tsx @@ -0,0 +1,205 @@ +import { useBackend } from 'tgui/backend'; +import { + Box, + ImageButton, + LabeledList, + NoticeBox, + ProgressBar, + Section, + Stack, +} from 'tgui-core/components'; + +import { Data } from './types'; + +const quick_actions: { + name: string; + icon: string; + icon_state: string; + eventName: string; +}[] = [ + { + name: 'Communicator', + icon: 'icons/obj/device.dmi', + icon_state: 'communicator', + eventName: 'quick_action_comm', + }, + { + name: 'PDA', + icon: 'icons/obj/pda_vr.dmi', + icon_state: 'pda', + eventName: 'quick_action_pda', + }, + { + name: 'Crew Manifest', + icon: 'icons/obj/pda_vr.dmi', + icon_state: 'cart', + eventName: 'quick_action_crew_manifest', + }, + { + name: 'Law Manager', + icon: 'icons/mob/screen1_robot.dmi', + icon_state: 'harm', + eventName: 'quick_action_law_manager', + }, + { + name: 'Alarm Monitor', + icon: 'icons/obj/modular_console.dmi', + icon_state: 'alert-green', + eventName: 'quick_action_alarm_monitoring', + }, + { + name: 'Power Monitor', + icon: 'icons/obj/modular_console.dmi', + icon_state: 'power_monitor', + eventName: 'quick_action_power_monitoring', + }, + { + name: 'Take Image', + icon: 'icons/obj/items.dmi', + icon_state: 'camera', + eventName: 'quick_action_take_image', + }, + { + name: 'View Images', + icon: 'icons/obj/items.dmi', + icon_state: 'film', + eventName: 'quick_action_view_images', + }, + { + name: 'Delete Images', + icon: 'icons/obj/items.dmi', + icon_state: 'photo', + eventName: 'quick_action_delete_images', + }, + { + name: 'Toggle Lights', + icon: 'icons/obj/lighting.dmi', + icon_state: 'flashlight', + eventName: 'quick_action_flashlight', + }, + { + name: 'Toggle Sensor Augmentation', + icon: 'icons/inventory/eyes/item.dmi', + icon_state: 'sec_hud', + eventName: 'quick_action_sensors', + }, + { + name: 'Emit Sparks', + icon: 'icons/effects/effects.dmi', + icon_state: 'sparks', + eventName: 'quick_action_sparks', + }, +]; + +export const StatusScreen = (props) => { + const { act } = useBackend(); + + return ( + + + + + + + + + + + + +
+ + + {quick_actions + .slice(0, quick_actions.length / 2) + .map((action) => ( + act(action.eventName)} + fluid + > + {action.name} + + ))} + + + {quick_actions.slice(quick_actions.length / 2).map((action) => ( + act(action.eventName)} + fluid + > + {action.name} + + ))} + + +
+
+
+ ); +}; + +const Configuration = (props) => { + const { data } = useBackend(); + + const { name, module_name, ai } = data; + return ( +
+ + {name} + {module_name} + + {ai ? {ai} : None} + + +
+ ); +}; + +const Status = (props) => { + const { data } = useBackend(); + + const { charge, max_charge, health, max_health } = data; + + return ( +
+ + + {charge && max_charge ? ( + + {charge} / {max_charge} + + ) : ( + NO CELL INSTALLED + )} + + + + + +
+ ); +}; diff --git a/tgui/packages/tgui/interfaces/Robotact/index.tsx b/tgui/packages/tgui/interfaces/Robotact/index.tsx new file mode 100644 index 0000000000..94b9725334 --- /dev/null +++ b/tgui/packages/tgui/interfaces/Robotact/index.tsx @@ -0,0 +1,90 @@ +import { useState } from 'react'; +import { useBackend } from 'tgui/backend'; +import { Window } from 'tgui/layouts'; +import { Button, NoticeBox, Stack, Tabs } from 'tgui-core/components'; + +import { ComponentView } from './Diagnostics'; +import { Modules } from './Modules'; +import { StatusScreen } from './StatusScreen'; +import { Data } from './types'; + +export const Robotact = (props) => { + const { act, data } = useBackend(); + + let content = ; + + if (data.module_name) { + content = ; + } + + return ( + + {content} + + ); +}; + +const NoModuleWarning = (props) => { + const { act } = useBackend(); + + return ( + + + + No Module Activated + + + + + + + ); +}; + +const MainScreen = (props) => { + const [screen, setScreen] = useState(0); + + let content; + switch (screen) { + case 0: + content = ; + break; + case 1: + content = ; + break; + case 2: + content = ; + break; + } + + return ( + <> + + setScreen(0)} + icon="battery-full" + > + Status + + setScreen(1)} + icon="notes-medical" + > + Diagnostics + + setScreen(2)} + icon="wrench" + > + Modules + + + {content} + + ); +}; diff --git a/tgui/packages/tgui/interfaces/Robotact/types.ts b/tgui/packages/tgui/interfaces/Robotact/types.ts new file mode 100644 index 0000000000..9fd62f6280 --- /dev/null +++ b/tgui/packages/tgui/interfaces/Robotact/types.ts @@ -0,0 +1,43 @@ +import { BooleanLike } from 'common/react'; + +export type Component = { + key: string; + name: string; + brute_damage: number; + electronics_damage: number; + max_damage: number; + idle_usage: number; + is_powered: boolean; + toggled: boolean; +}; + +export type Module = { + ref: string; + name: string; + activated: number; + icon: string; + icon_state: string; +}; + +export type Data = { + name: string; + module_name: string | null; + ai: string; + charge: number | null; + max_charge: number | null; + weapon_lock: BooleanLike; + + health: number; + max_health: number; + + // Modules + modules_static: Module[]; + emag_modules_static: Module[]; + + modules: Record; + emag_modules: Record; + + // Diagnosis + diag_functional: BooleanLike; + components: Component[]; +}; diff --git a/vorestation.dme b/vorestation.dme index cb6678fd68..df662e3d2f 100644 --- a/vorestation.dme +++ b/vorestation.dme @@ -3311,6 +3311,7 @@ #include "code\modules\mob\living\silicon\robot\robot_items.dm" #include "code\modules\mob\living\silicon\robot\robot_movement.dm" #include "code\modules\mob\living\silicon\robot\robot_remote_control.dm" +#include "code\modules\mob\living\silicon\robot\robot_ui.dm" #include "code\modules\mob\living\silicon\robot\dogborg\dog_modules_vr.dm" #include "code\modules\mob\living\silicon\robot\dogborg\dog_modules_yw.dm" #include "code\modules\mob\living\silicon\robot\dogborg\dog_sleeper_vr.dm"