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.



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);
+ }
+ }}
+ />
+
+ ) : (
+
+ )}
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};