Reworks AI Status Display and AI Core Display UX (#93196)

## About The Pull Request

Ready for merge post review

No gameplay balance changes, just better AI expression options and
polished interfaces for doing so.

- Adds 32 AI Core animation sprites to the AI status screens as a player
choice
- Gives two new menus to change status displays or core display with
TGUI
- Fixes Friend Computer emote incorrectly affecting evacuation displays
- Special mention to MrMelbert who gave a few very useful points as I
embraced TGUI suffering :)

<img width="1500" height="750" alt="image"
src="https://github.com/user-attachments/assets/fbae0ea9-5220-4cf9-9fa0-c5f0385f9e52"
/>
<img width="1746" height="821" alt="image"
src="https://github.com/user-attachments/assets/f1c08fdf-8dc8-44eb-b7cd-cd86f9b8f903"
/>


There is also a new verb under "AI Commands"
<img width="433" height="127" alt="image"
src="https://github.com/user-attachments/assets/2cd69b71-7a0c-4199-959e-acd4de9d5dad"
/>

<img width="364" height="528" alt="image"
src="https://github.com/user-attachments/assets/e083784e-8251-4a55-a9e2-e27e53dfa780"
/>


## Why It's Good For The Game

- Allows AI players to express themselves more in round / stand out more
- Now it is a TGUI menu, Much easier to see what is available and change
mid round as needed.
- We had talented Spriters make these and now more players can see them

## Changelog
🆑
qol: New verb in "AI commands" to update AI display status
fix: Stopped "Friend Computer" emotion affecting evac displays instead
of just AI displays
code: Replaced radial wheel for AI status or AI core display with TGUI
menus
image: AIs can now update their status displays with way more graphics
to match core display options onto the TVs.
/🆑

---------

Co-authored-by: loganuk <falseemail@aol.com>
This commit is contained in:
SimplyLogan
2025-10-26 16:45:08 +00:00
committed by GitHub
parent 731a5bdb1f
commit 49e28a0eeb
11 changed files with 650 additions and 35 deletions
+48 -12
View File
@@ -592,7 +592,8 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/status_display/evac, 32)
LAZYOR(active_displays, new_display)
set_display_with_priority(SD_GREENSCREEN, DISPLAY_PRIORITY_MESSAGE, force_override = TRUE)
if("friendcomputer")
friendc = !friendc
// Friend Computer should only affect AI displays, not evac displays
return
update()
/obj/machinery/status_display/evac/vv_edit_var(vname, vval)
@@ -733,32 +734,67 @@ MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/status_display/evac, 32)
desc = "A small screen which the AI can use to present itself."
current_mode = SD_PICTURE
var/emotion = AI_DISPLAY_DONT_GLOW
var/frequency = FREQ_STATUS_DISPLAYS
var/friendc = FALSE // track if Friend Computer mode
var/last_picture // For when Friend Computer mode is undone
MAPPING_DIRECTIONAL_HELPERS(/obj/machinery/status_display/ai, 32)
/obj/machinery/status_display/ai/Initialize(mapload)
. = ..()
// register for radio system to receive AI emote signals
SSradio.add_object(src, frequency)
find_and_hang_on_wall()
/obj/machinery/status_display/ai/Destroy()
SSradio.remove_object(src, frequency)
return ..()
/obj/machinery/status_display/ai/attack_ai(mob/living/silicon/ai/user)
if(!isAI(user))
return
var/list/choices = list()
for(var/emotion_const in GLOB.ai_status_display_emotes)
var/icon_state = GLOB.ai_status_display_emotes[emotion_const]
choices[emotion_const] = image(icon = 'icons/obj/machines/status_display.dmi', icon_state = icon_state)
var/emotion_result = show_radial_menu(user, src, choices, tooltips = TRUE)
for(var/_emote in typesof(/datum/emote/ai/emotion_display))
var/datum/emote/ai/emotion_display/emote = _emote
if(initial(emote.emotion) == emotion_result)
user.emote(initial(emote.key))
break
// Open the TGUI interface for selecting status display options
if(!user.status_display_picker)
user.status_display_picker = new(user, src)
user.status_display_picker.ui_interact(user)
/obj/machinery/status_display/ai/process()
if(machine_stat & NOPOWER)
update_appearance()
return PROCESS_KILL
set_picture(GLOB.ai_status_display_emotes[emotion])
if(!length(GLOB.ai_status_display_all_options))
init_ai_status_display_options()
var/icon_state
// First try the combined list
if(emotion in GLOB.ai_status_display_all_options)
icon_state = GLOB.ai_status_display_all_options[emotion]
// Then try the original emotes list
else if(emotion in GLOB.ai_status_display_emotes)
icon_state = GLOB.ai_status_display_emotes[emotion]
// Default fallback
else
icon_state = "ai_neutral"
set_picture(icon_state)
return PROCESS_KILL
/obj/machinery/status_display/ai/receive_signal(datum/signal/signal)
switch(signal.data["command"])
if("friendcomputer")
friendc = !friendc
if(friendc)
last_picture = emotion // Save current display
emotion = AI_EMOTION_FRIEND_COMPUTER
else
if(last_picture)
emotion = last_picture // Restore previous display
else
emotion = AI_DISPLAY_DONT_GLOW
update_appearance()
/obj/item/circuit_component/status_display
display_name = "Status Display"
desc = "Output text and pictures to a status display."
@@ -6,13 +6,17 @@
should_generate_icons = TRUE
/datum/preference/choiced/ai_emote_display/init_possible_values()
return assoc_to_keys(GLOB.ai_status_display_emotes)
if(!length(GLOB.ai_status_display_all_options))
init_ai_status_display_options()
return assoc_to_keys(GLOB.ai_status_display_all_options)
/datum/preference/choiced/ai_emote_display/icon_for(value)
if (value == "Random")
return uni_icon('icons/mob/silicon/ai.dmi', "questionmark")
else
return uni_icon('icons/obj/machines/status_display.dmi', GLOB.ai_status_display_emotes[value])
if(!length(GLOB.ai_status_display_all_options))
init_ai_status_display_options()
return uni_icon('icons/obj/machines/status_display.dmi', GLOB.ai_status_display_all_options[value])
/datum/preference/choiced/ai_emote_display/is_accessible(datum/preferences/preferences)
if (!..(preferences))
@@ -1,5 +1,6 @@
// A mapping between AI_EMOTION_* string constants, which also double as user readable descriptions, and the name of the iconfile. (used for /obj/machinery/status_display/ai )
GLOBAL_LIST_INIT(ai_status_display_emotes, list(
// Original AI emotion states
AI_EMOTION_AWESOME = "ai_awesome",
AI_EMOTION_BLANK = AI_DISPLAY_DONT_GLOW,
AI_EMOTION_BLUE_GLOW = "ai_sal",
@@ -18,6 +19,60 @@ GLOBAL_LIST_INIT(ai_status_display_emotes, list(
AI_EMOTION_VERY_HAPPY = "ai_veryhappy",
))
// Mapping from AI core display options to new status display icon states
// This allows AI status displays to show the same choice as AI core displays
GLOBAL_LIST_INIT(ai_core_to_status_display_mapping, list(
"Alien" = "ai_status_alien",
"Angel" = "ai_status_angel",
"Banned" = "ai_status_banned",
"Bliss" = "ai_status_bliss",
"Blue" = "ai_status_blue",
"Clown" = "ai_status_clown",
"Database" = "ai_status_database",
"Dorf" = "ai_status_dorf",
"Firewall" = "ai_status_firewall",
"Fuzzy" = "ai_status_fuzzy",
"Gentoo" = "ai_status_gentoo",
"Glitchman" = "ai_status_glitchman",
"Gondola" = "ai_status_gondola",
"Goon" = "ai_status_goon",
"Hades" = "ai_status_hades",
"HAL 9000" = "ai_status_hal9000",
"Heartline" = "ai_status_heartline",
"Helios" = "ai_status_helios",
"House" = "ai_status_house",
"Matrix" = "ai_status_matrix",
"Monochrome" = "ai_status_monochrome",
"Murica" = "ai_status_murica",
"Nanotrasen" = "ai_status_nanotrasen",
"Not Malf" = "ai_status_not_malf",
"President" = "ai_status_president",
"Rainbow" = "ai_status_rainbow",
"Red October" = "ai_status_red_october",
"Red" = "ai_status_red",
"Static" = "ai_status_static",
"Syndicat Meow" = "ai_status_syndicat_meow",
"Text" = "ai_status_text",
"Too Deep" = "ai_status_too_deep",
"Triumvirate" = "ai_status_triumvirate",
"Weird" = "ai_status_weird",
))
// Combined list for AI status display preferences, including both emotion states and AI core display options
GLOBAL_LIST_INIT(ai_status_display_all_options, list())
// Initialize the combined list at runtime
/proc/init_ai_status_display_options()
if(length(GLOB.ai_status_display_all_options)) // Already initialized
return
// Start with original emotes
GLOB.ai_status_display_all_options = GLOB.ai_status_display_emotes.Copy()
// Add AI core display mappings
for(var/core_option in GLOB.ai_core_to_status_display_mapping)
GLOB.ai_status_display_all_options[core_option] = GLOB.ai_core_to_status_display_mapping[core_option]
GLOBAL_LIST_INIT(ai_hologram_category_options, list(
AI_HOLOGRAM_CATEGORY_ANIMAL = list(
AI_HOLOGRAM_BEAR,
+23 -21
View File
@@ -144,6 +144,7 @@
QDEL_NULL(malf_picker)
QDEL_NULL(doomsday_device)
QDEL_NULL(robot_control)
QDEL_NULL(status_display_picker)
QDEL_NULL(aiMulti)
QDEL_NULL(alert_control)
QDEL_NULL(ai_tracking_tool)
@@ -195,7 +196,9 @@
var/emote_choice = player_client.prefs.read_preference(/datum/preference/choiced/ai_emote_display)
if(emote_choice == "Random")
emote_choice = pick(GLOB.ai_status_display_emotes)
if(!length(GLOB.ai_status_display_all_options))
init_ai_status_display_options()
emote_choice = pick(GLOB.ai_status_display_all_options)
apply_emote_display(emote_choice)
@@ -208,29 +211,28 @@
/mob/living/silicon/ai/verb/pick_icon()
set category = "AI Commands"
set name = "Set AI Core Display"
set desc = "Choose what appears on your AI core display"
if(incapacitated)
return
icon = initial(icon)
icon_state = "ai"
cut_overlays()
var/list/iconstates = GLOB.ai_core_display_screens
for(var/option in iconstates)
if(option == "Random")
iconstates[option] = image(icon = src.icon, icon_state = "ai-random")
continue
if(option == "Portrait")
iconstates[option] = image(icon = src.icon, icon_state = "ai-portrait")
continue
iconstates[option] = image(icon = src.icon, icon_state = resolve_ai_icon(option))
view_core()
var/ai_core_icon = show_radial_menu(src, src , iconstates, radius = 42)
if(!ai_core_icon || incapacitated)
to_chat(src, span_warning("You cannot access the core display controls in your current state."))
return
display_icon_override = ai_core_icon
set_core_display_icon(ai_core_icon)
if(!core_display_picker)
core_display_picker = new(src)
core_display_picker.ui_interact(src)
/mob/living/silicon/ai/verb/pick_status_display()
set category = "AI Commands"
set name = "Set AI Status Display"
set desc = "Choose what appears on status displays around the station"
if(incapacitated)
to_chat(src, span_warning("You cannot access the status display controls in your current state."))
return
if(!status_display_picker)
status_display_picker = new(src)
status_display_picker.ui_interact(src)
/mob/living/silicon/ai/get_status_tab_items()
. = ..()
@@ -0,0 +1,79 @@
/**
* AI Core Display Picker TGUI
* Allows AIs to select core display options with search functionality
*/
/datum/ai_core_display_picker
var/mob/living/silicon/ai/ai_user
/datum/ai_core_display_picker/New(mob/living/silicon/ai/user)
ai_user = user
/datum/ai_core_display_picker/ui_status(mob/user, datum/ui_state/state)
if(!ai_user || user != ai_user || ai_user.incapacitated)
return UI_CLOSE
return ..()
/datum/ai_core_display_picker/ui_state(mob/user)
return GLOB.always_state
/datum/ai_core_display_picker/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "AiCoreDisplayPicker")
ui.open()
/datum/ai_core_display_picker/ui_close(mob/user)
if(ai_user)
ai_user.core_display_picker = null
/datum/ai_core_display_picker/ui_data(mob/user)
var/list/data = list()
// If no override is set, find the actual current display from the AI's icon state
var/current_display = ai_user.display_icon_override
if(!current_display)
// Default to "Blue" if no override
current_display = "Blue"
// Try to identify current display
if(ai_user.icon_state)
for(var/display_name in GLOB.ai_core_display_screens)
if("ai-[LOWER_TEXT(display_name)]" == ai_user.icon_state)
current_display = display_name
break
data["current_display"] = current_display
// Get icon for current display
var/current_icon_state = resolve_ai_icon_sync(current_display)
data["current_icon"] = list(
"icon" = 'icons/mob/silicon/ai.dmi',
"icon_state" = current_icon_state
)
var/list/options = list()
for(var/option_name in GLOB.ai_core_display_screens)
var/icon_state = resolve_ai_icon_sync(option_name)
var/list/option_data = list(
"name" = option_name,
"icon_state" = icon_state,
"icon" = 'icons/mob/silicon/ai.dmi'
)
options += list(option_data)
data["options"] = options
return data
/datum/ai_core_display_picker/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
. = ..()
if(.)
return
switch(action)
if("select_option")
var/chosen_option = params["option"]
if(chosen_option in GLOB.ai_core_display_screens)
ai_user.display_icon_override = chosen_option
ai_user.set_core_display_icon(chosen_option)
return TRUE
@@ -163,6 +163,10 @@
/* UI */
/// UI for station alerts
VAR_FINAL/datum/station_alert/alert_control
/// UI for AI status display picker
VAR_FINAL/datum/ai_status_display_picker/status_display_picker
/// UI for AI core display picker
VAR_FINAL/datum/ai_core_display_picker/core_display_picker
/* I'M DUMB AND CAN'T SORT */
/// Used as a fake multitool in tcomms machinery
@@ -0,0 +1,123 @@
/**
* AI Status Display Picker TGUI
* Allows AIs to select status display options with search functionality
*/
/datum/ai_status_display_picker
/// The AI user who opened this interface
var/mob/living/silicon/ai/ai_user
/// The status display that was clicked
var/obj/machinery/status_display/ai/target_display
/datum/ai_status_display_picker/New(mob/living/silicon/ai/user)
ai_user = user
/datum/ai_status_display_picker/ui_status(mob/user, datum/ui_state/state)
if(!ai_user || user != ai_user || ai_user.incapacitated)
return UI_CLOSE
return ..()
/datum/ai_status_display_picker/ui_state(mob/user)
return GLOB.always_state
/datum/ai_status_display_picker/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(!ui)
ui = new(user, src, "AiStatusDisplayPicker")
ui.open()
/datum/ai_status_display_picker/ui_close(mob/user)
if(ai_user)
ai_user.status_display_picker = null
// No assets needed for DMIcon system
/datum/ai_status_display_picker/ui_static_data(mob/user)
// Always ensure initialization
init_ai_status_display_options()
var/list/data = list()
var/list/options = list()
for(var/option_name in GLOB.ai_status_display_emotes)
var/icon_state = GLOB.ai_status_display_emotes[option_name]
var/list/option_data = list(
"name" = option_name,
"icon_state" = icon_state,
"is_original" = TRUE,
"icon" = 'icons/obj/machines/status_display.dmi'
)
options += list(option_data)
for(var/option_name in GLOB.ai_core_to_status_display_mapping)
var/icon_state = GLOB.ai_core_to_status_display_mapping[option_name]
var/list/option_data = list(
"name" = option_name,
"icon_state" = icon_state,
"is_original" = FALSE,
"icon" = 'icons/obj/machines/status_display.dmi'
)
options += list(option_data)
data["options"] = options
return data
/datum/ai_status_display_picker/ui_data(mob/user)
var/list/data = list()
// Find current display emotion from first available AI status display
var/current_emotion = "None"
var/current_icon_state
var/obj/machinery/status_display/ai/first_display = locate() in SSmachines.get_machines_by_type_and_subtypes(/obj/machinery/status_display/ai)
if(first_display?.emotion)
current_emotion = first_display.emotion
if(current_emotion in GLOB.ai_status_display_emotes)
current_icon_state = GLOB.ai_status_display_emotes[current_emotion]
else if(current_emotion in GLOB.ai_core_to_status_display_mapping)
current_icon_state = GLOB.ai_core_to_status_display_mapping[current_emotion]
else
current_icon_state = "ai_neutral"
else
current_icon_state = "ai_neutral"
data["current_emotion"] = current_emotion
data["current_icon"] = list(
"icon" = 'icons/obj/machines/status_display.dmi',
"icon_state" = current_icon_state
)
return data
/datum/ai_status_display_picker/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
. = ..()
if(.)
return
if(!ai_user)
return FALSE
switch(action)
if("select_option")
var/selected_option = params["option"]
if(!selected_option || !(selected_option in GLOB.ai_status_display_all_options))
return FALSE
// Check if this is an original AI emotion (has a corresponding emote)
var/found_emote = FALSE
for(var/_emote in typesof(/datum/emote/ai/emotion_display))
var/datum/emote/ai/emotion_display/emote = _emote
if(initial(emote.emotion) == selected_option)
ai_user.emote(initial(emote.key))
found_emote = TRUE
break
// If no emote found (i.e., it's a new AI core display option), directly apply it
if(!found_emote)
ai_user.apply_emote_display(selected_option)
return TRUE
return FALSE
Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 169 KiB

+2
View File
@@ -5497,10 +5497,12 @@
#include "code\modules\mob\living\silicon\silicon_say.dm"
#include "code\modules\mob\living\silicon\ai\_preferences.dm"
#include "code\modules\mob\living\silicon\ai\ai.dm"
#include "code\modules\mob\living\silicon\ai\ai_core_display_picker.dm"
#include "code\modules\mob\living\silicon\ai\ai_defense.dm"
#include "code\modules\mob\living\silicon\ai\ai_defines.dm"
#include "code\modules\mob\living\silicon\ai\ai_portrait_picker.dm"
#include "code\modules\mob\living\silicon\ai\ai_say.dm"
#include "code\modules\mob\living\silicon\ai\ai_status_display_picker.dm"
#include "code\modules\mob\living\silicon\ai\death.dm"
#include "code\modules\mob\living\silicon\ai\emote.dm"
#include "code\modules\mob\living\silicon\ai\examine.dm"
@@ -0,0 +1,146 @@
import { useState } from 'react';
import {
Box,
Button,
Flex,
ImageButton,
Input,
Section,
Stack,
Table,
DmIcon,
} from 'tgui-core/components';
import { classes } from 'tgui-core/react';
import { useBackend } from '../backend';
import { Window } from '../layouts';
type CoreDisplayOption = {
name: string;
icon_state: string;
icon: string;
};
type CurrentIcon = {
icon: string;
icon_state: string;
};
type Data = {
current_display: string;
current_icon: CurrentIcon | null;
options: CoreDisplayOption[];
};
export const AiCoreDisplayPicker = () => {
return (
<Window width={500} height={600} title="AI Core Display Options">
<Window.Content scrollable>
<AiCoreDisplayPickerContent />
</Window.Content>
</Window>
);
};
const AiCoreDisplayPickerContent = () => {
const { act, data } = useBackend<Data>();
const { current_display, current_icon, options = [] } = data;
const [searchTerm, setSearchTerm] = useState('');
const [tiledView, setTiledView] = useState(false);
// Filter options based on search term
const filteredOptions = options.filter((option) =>
option.name.toLowerCase().includes(searchTerm.toLowerCase())
);
return (
<Stack fill vertical>
<Stack.Item>
<Section title="Current AI Core Display">
<Flex align="center" justify="center" direction="column">
{current_icon && (
<Flex.Item mb={2}>
<Box
style={{
border: '2px solid #4a9eff',
borderRadius: '4px',
backgroundColor: '#1a1a1a',
padding: '8px'
}}
>
<DmIcon
icon={current_icon.icon}
icon_state={current_icon.icon_state}
width="64px"
height="64px"
/>
</Box>
</Flex.Item>
)}
<Flex.Item>
<Box fontSize="1.2em" textAlign="center" bold color="good">
{current_display}
</Box>
</Flex.Item>
</Flex>
</Section>
</Stack.Item>
<Stack.Item>
<Input
placeholder="Search display options..."
value={searchTerm}
onChange={(value) => setSearchTerm(value)}
fluid
/>
</Stack.Item>
<Stack.Item grow>
<Stack fill vertical>
<Stack.Item>
<Section title="AI Core Display Options">
<OptionsList
options={filteredOptions}
/>
</Section>
</Stack.Item>
{filteredOptions.length === 0 && (
<Stack.Item>
<Box textAlign="center" color="average" mt={4}>
No options found matching "{searchTerm}"
</Box>
</Stack.Item>
)}
</Stack>
</Stack.Item>
</Stack>
);
};
const OptionsList = ({
options,
}: {
options: CoreDisplayOption[];
}) => {
const { act } = useBackend<Data>();
return (
<>
{options.map((option) => (
<ImageButton
key={option.name}
dmIcon={option.icon}
dmIconState={option.icon_state}
onClick={() => act('select_option', { option: option.name })}
imageSize={64}
tooltip={option.name}
>
{option.name}
</ImageButton>
))}
</>
);
};
@@ -0,0 +1,164 @@
import { useState } from 'react';
import type { BooleanLike } from 'tgui-core/react';
import {
Box,
Button,
Flex,
ImageButton,
Input,
Section,
Stack,
DmIcon,
} from 'tgui-core/components';
import { useBackend } from '../backend';
import { Window } from '../layouts';
type StatusDisplayOption = {
name: string;
icon_state: string;
is_original: BooleanLike;
dmi: string;
icon: string;
};
type CurrentIcon = {
icon: string;
icon_state: string;
};
type Data = {
current_emotion: string;
current_icon: CurrentIcon | null;
options: StatusDisplayOption[];
};
export const AiStatusDisplayPicker = () => {
return (
<Window width={500} height={600} title="AI Status Display Options">
<Window.Content scrollable>
<AiStatusDisplayPickerContent />
</Window.Content>
</Window>
);
};
const AiStatusDisplayPickerContent = () => {
const { act, data } = useBackend<Data>();
const { current_emotion, current_icon, options = [] } = data;
const [searchTerm, setSearchTerm] = useState('');
// Filter options based on search term
const filteredOptions = options.filter((option) =>
option.name.toLowerCase().includes(searchTerm.toLowerCase())
);
// Separate original emotions and new AI core options
const originalOptions = filteredOptions.filter((option) => option.is_original);
const newOptions = filteredOptions.filter((option) => !option.is_original);
return (
<Stack fill vertical>
<Stack.Item>
<Section title="Current Display">
<Flex align="center" justify="center" direction="column">
{current_icon && (
<Flex.Item mb={2}>
<Box
style={{
border: '2px solid #4a9eff',
borderRadius: '4px',
backgroundColor: '#1a1a1a',
padding: '8px'
}}
>
<DmIcon
icon={current_icon.icon}
icon_state={current_icon.icon_state}
width="64px"
height="64px"
/>
</Box>
</Flex.Item>
)}
<Flex.Item mb={1}>
<Box fontSize="1.4em" textAlign="center" bold color="good">
{current_emotion || 'No Selection'}
</Box>
</Flex.Item>
</Flex>
</Section>
</Stack.Item>
<Stack.Item>
<Section>
<Input
fluid
placeholder="Search display options..."
value={searchTerm}
onChange={(value) => setSearchTerm(value)}
/>
</Section>
</Stack.Item>
<Stack.Item grow>
<Stack fill vertical>
{originalOptions.length > 0 && (
<Stack.Item>
<Section title="AI Emotions">
<OptionsList
options={originalOptions}
/>
</Section>
</Stack.Item>
)}
{newOptions.length > 0 && (
<Stack.Item>
<Section title="Additional Status Display Options">
<OptionsList
options={newOptions}
/>
</Section>
</Stack.Item>
)}
{filteredOptions.length === 0 && (
<Stack.Item>
<Box textAlign="center" color="average" mt={4}>
No options found matching "{searchTerm}"
</Box>
</Stack.Item>
)}
</Stack>
</Stack.Item>
</Stack>
);
};
const OptionsList = ({
options
}: {
options: StatusDisplayOption[];
}) => {
const { act } = useBackend<Data>();
return (
<>
{options.map((option) => (
<ImageButton
key={option.name}
dmIcon={option.icon}
dmIconState={option.icon_state}
onClick={() => act('select_option', { option: option.name })}
imageSize={64}
tooltip={option.name}
>
{option.name}
</ImageButton>
))}
</>
);
};