From 17d39f7af007eae0e762b40abe59dc98e30c3e4c Mon Sep 17 00:00:00 2001 From: Watermelon914 <37270891+Watermelon914@users.noreply.github.com> Date: Thu, 13 Jun 2024 13:06:49 +0000 Subject: [PATCH] Upgrades bot voice lines (#83847) ## About The Pull Request As the title says. Upgrades the menu and allows you to create action button shortcuts. ![image](https://github.com/tgstation/tgstation/assets/37270891/902d5e2b-de50-463e-a566-062ee24cc138) ![image](https://github.com/tgstation/tgstation/assets/37270891/0ce025e3-2f38-4013-8cde-00af12584086) ![image](https://github.com/tgstation/tgstation/assets/37270891/88ad4a30-dd65-4275-912c-b341264d77fd) Additionally, bots can now select the radio channel if they have one. Lowers the cooldown for voice lines. ## Why It's Good For The Game Being able to more conveniently send a voice line by pressing a button without needing to open up an interface and navigate to the correct line will make using these a lot easier in relevant situations. Voice line cooldowns are lowered a bit just to make voice lines easier to use in relevant situations. I.e. 'Extinguishing fire' -> 5 seconds later -> 'Fire extinguished' Abuse should generally just be punished by the admins, the cooldown doesn't need to be too lengthy. ## Changelog :cl: qol: Bots can now bind voice lines to an action slot. Lowers cooldown for all bot voicelines to 5 seconds. /:cl: --------- Co-authored-by: Watermelon914 <3052169-Watermelon914@users.noreply.gitlab.com> --- code/__DEFINES/cooldowns.dm | 1 + code/game/communications.dm | 18 ++ .../simple_animal/bot/bot_announcement.dm | 173 ++++++++++- .../tgui/interfaces/BotAnnouncement.tsx | 270 ++++++++++++++++++ 4 files changed, 452 insertions(+), 10 deletions(-) create mode 100644 tgui/packages/tgui/interfaces/BotAnnouncement.tsx diff --git a/code/__DEFINES/cooldowns.dm b/code/__DEFINES/cooldowns.dm index 0cb559b0e8f..0ff525dac5a 100644 --- a/code/__DEFINES/cooldowns.dm +++ b/code/__DEFINES/cooldowns.dm @@ -66,6 +66,7 @@ #define MOB_SHARED_COOLDOWN_1 (1<<0) #define MOB_SHARED_COOLDOWN_2 (1<<1) #define MOB_SHARED_COOLDOWN_3 (1<<2) +#define MOB_SHARED_COOLDOWN_BOT_ANNOUNCMENT (1<<3) //TIMER COOLDOWN MACROS diff --git a/code/game/communications.dm b/code/game/communications.dm index 0be3ccf17bc..6d26a977993 100644 --- a/code/game/communications.dm +++ b/code/game/communications.dm @@ -129,6 +129,24 @@ GLOBAL_LIST_INIT(reverseradiochannels, list( "[FREQ_CTF_YELLOW]" = RADIO_CHANNEL_CTF_YELLOW )) +GLOBAL_LIST_INIT(radiocolors, list( + RADIO_CHANNEL_COMMON = "#008000", + RADIO_CHANNEL_SCIENCE = "#993399", + RADIO_CHANNEL_COMMAND = "#948f02", + RADIO_CHANNEL_MEDICAL = "#337296", + RADIO_CHANNEL_ENGINEERING = "#fb5613", + RADIO_CHANNEL_SECURITY = "#a30000", + RADIO_CHANNEL_CENTCOM = "#686868", + RADIO_CHANNEL_SYNDICATE = "#6d3f40", + RADIO_CHANNEL_SUPPLY = "#a8732b", + RADIO_CHANNEL_SERVICE = "#6eaa2c", + RADIO_CHANNEL_AI_PRIVATE = "#ff00ff", + RADIO_CHANNEL_CTF_RED = "#ff0000", + RADIO_CHANNEL_CTF_BLUE = "#0000ff", + RADIO_CHANNEL_CTF_GREEN = "#00ff00", + RADIO_CHANNEL_CTF_YELLOW = "#d1ba22" +)) + /datum/radio_frequency /// The frequency of this radio frequency. Of course. var/frequency diff --git a/code/modules/mob/living/simple_animal/bot/bot_announcement.dm b/code/modules/mob/living/simple_animal/bot/bot_announcement.dm index eade5a291c4..061e6375088 100644 --- a/code/modules/mob/living/simple_animal/bot/bot_announcement.dm +++ b/code/modules/mob/living/simple_animal/bot/bot_announcement.dm @@ -6,10 +6,15 @@ overlay_icon_state = "bg_tech_blue_border" button_icon = 'icons/obj/machines/wallmounts.dmi' button_icon_state = "intercom" - cooldown_time = 10 SECONDS + cooldown_time = 5 SECONDS + shared_cooldown = MOB_SHARED_COOLDOWN_BOT_ANNOUNCMENT melee_cooldown_time = 0 SECONDS /// List of strings to sound effects corresponding to automated messages we can play var/list/automated_announcements + /// Maximum amount of buttons this can have + var/max_buttons = 10 + /// List of buttons that automatically correspond to an announcement and channel + var/list/buttons = list() /datum/action/cooldown/bot_announcement/New(Target, original, list/automated_announcements) src.automated_announcements = automated_announcements @@ -29,15 +34,118 @@ return FALSE return TRUE -/datum/action/cooldown/bot_announcement/Activate(trigger_flags, atom/target) - var/picked - if (length(automated_announcements) > 1) - picked = tgui_input_list(owner, message = "Choose announcement to make.", title = "Select announcement", items = automated_announcements) +/datum/action/cooldown/bot_announcement/ui_interact(mob/user, datum/tgui/ui) + ui = SStgui.try_update_ui(user, src, ui) + if(!ui) + ui = new(user, src, "BotAnnouncement", "Select announcement") + ui.set_autoupdate(TRUE) + ui.open() + +/datum/action/cooldown/bot_announcement/ui_state(mob/user) + return GLOB.conscious_state + +/datum/action/cooldown/bot_announcement/ui_status(mob/user, datum/ui_state/state) + . = ..() + if(user != owner) + return UI_CLOSE + +/datum/action/cooldown/bot_announcement/ui_data(mob/user) + var/list/data = list() + + var/mob/living/simple_animal/bot/bot_owner = owner + if(istype(bot_owner)) + var/list/channels = list() + for(var/channel in bot_owner.internal_radio.channels) + channels += channel + data["channels"] = channels else - picked = pick(automated_announcements) - if (isnull(picked)) + data["channels"] = list() + var/list/lines = list() + for(var/line in automated_announcements) + lines += line + data["lines"] = lines + var/list/button_data = list() + for(var/datum/action/cooldown/bot_announcement_shortcut/button as anything in buttons) + button_data += list(list( + "name" = button.name, + "channel" = button.channel + )) + data["button_data"] = button_data + data["cooldown_left"] = next_use_time - world.time + return data + +/datum/action/cooldown/bot_announcement/Destroy() + QDEL_LIST(buttons) + return ..() + +/datum/action/cooldown/bot_announcement/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state) + . = ..() + if(.) return - announce(picked) + + switch(action) + if("announce") + var/picked = params["picked"] + var/channel = params["channel"] + if(!(picked in automated_announcements)) + return + announce(picked, channel) + StartCooldown() + return TRUE + if("set_button") + if(length(buttons) >= max_buttons) + return + var/picked = params["picked"] + var/channel = params["channel"] + if(!(picked in automated_announcements)) + return + create_shortcut(picked, channel) + return TRUE + if("remove_button") + var/index = text2num(params["index"]) + if(!index) + return + if(index < 1 || index > length(buttons)) + return + var/datum/action/button = buttons[index] + qdel(button) + return TRUE + +/datum/action/cooldown/bot_announcement/Activate(trigger_flags, atom/target) + if (length(automated_announcements) > 1) + ui_interact(owner) + return + else if(length(automated_announcements) == 1) + announce(automated_announcements[1]) + return ..() + +/datum/action/cooldown/bot_announcement/proc/create_shortcut(line, channel) + var/datum/action/cooldown/bot_announcement_shortcut/shortcut = new(src) + + shortcut.prefix = copytext(line, 1, 4) + var/color = GLOB.radiocolors["[channel]"] + if(color) + shortcut.prefix_color = color + shortcut.name = line + shortcut.message = line + shortcut.channel = channel + shortcut.linked = src + shortcut.Grant(owner) + RegisterSignal(shortcut, COMSIG_QDELETING, PROC_REF(on_shortcut_deleted)) + buttons += shortcut + +/datum/action/cooldown/bot_announcement/proc/on_shortcut_deleted(datum/shortcut) + SIGNAL_HANDLER + buttons -= shortcut + +/datum/action/cooldown/bot_announcement/Grant(mob/granted_to) + . = ..() + for(var/datum/action/action as anything in buttons) + action.Grant(granted_to) + +/datum/action/cooldown/bot_announcement/Remove(mob/removed_from) + for(var/datum/action/action as anything in buttons) + action.Remove(removed_from) return ..() /// Speak the provided line on the provided radio channel @@ -46,10 +154,9 @@ if (!(bot_owner.bot_mode_flags & BOT_MODE_ON)) return + bot_owner.say(line) if (channel && bot_owner.internal_radio.channels[channel]) bot_owner.internal_radio.talk_into(bot_owner, message = line, channel = channel) - else - bot_owner.say(line) if (length(automated_announcements) && !isnull(automated_announcements[line])) playsound(bot_owner, automated_announcements[line], vol = 50, vary = FALSE) @@ -62,3 +169,49 @@ if(!(bot_owner.medical_mode_flags & MEDBOT_SPEAK_MODE)) return return ..() + +/datum/action/cooldown/bot_announcement_shortcut + desc = "Play a prerecorded message for the benefit of those around you." + shared_cooldown = MOB_SHARED_COOLDOWN_BOT_ANNOUNCMENT + melee_cooldown_time = 0 SECONDS + background_icon_state = "bg_tech_blue" + overlay_icon_state = "bg_tech_blue_border" + button_icon = 'icons/obj/machines/wallmounts.dmi' + button_icon_state = "intercom" + /// The prefix that appears on this button + var/prefix + /// The color of the prefix that appears on the button + var/prefix_color = "#ffffff" + /// The prefix icon that's rendered on the button + var/mutable_appearance/prefix_icon + /// The message to send when this button is clicked + var/message + /// The channel to send this to when clicked + var/channel + + /// The linked bot_announcement ability + var/datum/action/cooldown/bot_announcement/linked + +/datum/action/cooldown/bot_announcement_shortcut/Destroy() + linked = null + return ..() + +/datum/action/cooldown/bot_announcement_shortcut/apply_button_overlay(atom/movable/screen/movable/action_button/current_button, force) + . = ..() + if(prefix_icon) + current_button.cut_overlay(prefix_icon) + if(!prefix) + return + if(!prefix_icon) + prefix_icon = mutable_appearance() + prefix_icon.maptext = MAPTEXT_SPESSFONT("[prefix]") + prefix_icon.maptext_x = -4 + prefix_icon.maptext_y = 8 + current_button.add_overlay(prefix_icon) + +/datum/action/cooldown/bot_announcement_shortcut/Activate(atom/target) + if(!message || !linked) + return + cooldown_time = linked.cooldown_time + linked.announce(message, channel) + return ..() diff --git a/tgui/packages/tgui/interfaces/BotAnnouncement.tsx b/tgui/packages/tgui/interfaces/BotAnnouncement.tsx new file mode 100644 index 00000000000..3292e81a0e7 --- /dev/null +++ b/tgui/packages/tgui/interfaces/BotAnnouncement.tsx @@ -0,0 +1,270 @@ +import { createSearch } from 'common/string'; +import { useState } from 'react'; + +import { useBackend } from '../backend'; +import { + Box, + Button, + Dropdown, + Icon, + Input, + Section, + Stack, + Tabs, +} from '../components'; +import { RADIO_CHANNELS } from '../constants'; +import { Window } from '../layouts'; + +type ButtonData = { + name: string; + channel: string; +}; + +type ButtonDataWithId = { + button: ButtonData; + index: number; +}; + +type StringWithId = { + string: string; + index: number; +}; + +type BotAnnouncementData = { + channels: string[]; + lines: string[]; + button_data: ButtonData[]; + cooldown_left: number; +}; + +enum TAB { + Announcements, + Shortcuts, +} + +export const BotAnnouncement = (props) => { + const { act, data } = useBackend(); + const { channels, lines, button_data, cooldown_left } = data; + + const [tab, setTab] = useState(TAB.Announcements); + const [selectedChannel, setSelectedChannel] = useState(null); + const [selectedLine, setSelectedLine] = useState(null); + const [selectedButton, setSelectedButton] = useState(null); + const [search, setSearch] = useState(''); + + let filteredLines: StringWithId[] = lines.map((val, index) => ({ + string: val, + index, + })); + let filteredShortcuts: ButtonDataWithId[] = button_data.map((val, index) => ({ + button: val, + index, + })); + + if (search !== '') { + if (tab === TAB.Announcements) { + const lineSearch = createSearch( + search, + (item: StringWithId) => item.string, + ); + filteredLines = filteredLines.filter(lineSearch); + } else { + const buttonSearch = createSearch( + search, + (item: ButtonDataWithId) => item.button.name, + ); + filteredShortcuts = filteredShortcuts.filter(buttonSearch); + } + } + + return ( + + +
+ + { + setSearch(''); + setTab(TAB.Announcements); + }} + > + Announcements + + { + setSearch(''); + setTab(TAB.Shortcuts); + }} + > + Shortcuts + + +
+
+ {tab === TAB.Announcements && ( + + {filteredLines.map((val) => ( + + + + ))} + + )} + {tab === TAB.Shortcuts && ( + + {filteredShortcuts.map((val) => ( + + + + ))} + + )} +
+
+ + + setSearch(newValue)} + fluid + autoFocus + placeholder="Search..." + /> + + + + {tab === TAB.Announcements ? ( + + { + if (value === 'No radio channel') { + setSelectedChannel(null); + } else { + setSelectedChannel(value); + } + }} + /> + + ) : ( + + )} + + + + + + + + + +
+
+
+ ); +};