From 68e1a9cde1342688dc502fa40ba5793a676bed88 Mon Sep 17 00:00:00 2001
From: portfiend <109661617+portfiend@users.noreply.github.com>
Date: Mon, 12 Aug 2024 11:15:33 -0400
Subject: [PATCH] AAC tablet for speech-impaired characters (#1491)
* basic AAC tablet prototype
using station map as a base
* set up aac component/system
* quick phrase prototype
will probably touch this up later
* basic example phrases
just so i have data to work with
* get AACWindow to iterate over quick phrases
* add the rest of the job phrases
* fix this one job name
* actually fix prison guard name
* buttons for aac window
* fix phrase inheritance
* add tabs to aac contaner
* fix column spacing and add button padding
* aac tablet button colors
* AAC tablet sends messages now
* add aac tablet voice sound yay
* add a 1 second cooldown between phrases
* subjects for most departments
* location phrases
* more phrases
* cleanup + sort buttons alphabetically
* fix these phrases
* even more departmental subject phrases
* common phrases
* cleanup imports
* show name of player that pressed button
* aac tablet can be used by multipel people
after all it does not rely on state changes and also multiple people can press buttons on a tablet at once
* capitalize aac
its an acronym
* you know what it is its more phrases!!!!
* SAFETY PHRASES
* last second phrases
* redundant phrase
* and one more hazard phrase for the road
* change voice of aac tablet from borg to alto
just sounds nicer
* localize ALL Phrases
i love utility scripting to automate tedious tasks
* add AAC tablet to loadout
* add AAC tablet to medfab
* tweak: use multiple parents instead of whatever this is
* add: justice department phrases
* add: time quantity phrases
* add: ores and kitchen appliance phrases
* fix: resolve duplicate phrases
* add: aac tablet sprites
* add: justice button style
* fix: misplaced this line oops
* add: justice dept locations
* remove: redundant phrase
* re-run tests
* fix: move aac tablet loadout format
* fix: use Identity instead of Name for aac tablet sender
* fix: return on send phrase if id is invalid
* fix: remove redundant line
* fix: use LocId instead of String for phrase text type
* add: new phrases bc upstream updates
* fix: newlines
* tweak: add end comments to these style comments
* fix: this phrase was broken lol
---------
Co-authored-by: deltanedas <39013340+deltanedas@users.noreply.github.com>
---
.../AACTablet/UI/AACBoundUserInterface.cs | 38 ++
.../DeltaV/AACTablet/UI/AACWindow.xaml | 9 +
.../DeltaV/AACTablet/UI/AACWindow.xaml.cs | 147 ++++++
Content.Client/Stylesheets/StyleNano.cs | 111 +++++
.../DeltaV/AACTablet/AACTabletComponent.cs | 13 +
.../DeltaV/AACTablet/AACTabletSystem.cs | 47 ++
.../DeltaV/AACTablet/SharedAACTabletSystem.cs | 19 +
.../QuickPhrase/QuickPhrasePrototype.cs | 51 ++
.../Locale/en-US/deltav/phrases/common.ftl | 92 ++++
.../Locale/en-US/deltav/phrases/species.ftl | 42 ++
.../Locale/en-US/deltav/phrases/subjects.ftl | 195 ++++++++
.../Locale/en-US/deltav/phrases/threats.ftl | 52 +++
.../en-US/deltav/phrases/uncategorized.ftl | 7 +
.../Entities/Objects/Devices/aac_tablet.yml | 43 ++
.../Loadouts/Miscellaneous/trinkets.yml | 5 +
.../DeltaV/QuickPhrases/Common/commands.yml | 125 +++++
.../DeltaV/QuickPhrases/Common/manners.yml | 75 +++
.../DeltaV/QuickPhrases/Common/numbers.yml | 100 ++++
.../DeltaV/QuickPhrases/Common/pronouns.yml | 65 +++
.../QuickPhrases/Common/qualitative.yml | 70 +++
.../DeltaV/QuickPhrases/Common/questions.yml | 55 +++
.../DeltaV/QuickPhrases/Species/animals.yml | 165 +++++++
.../DeltaV/QuickPhrases/Species/crew.yml | 65 +++
.../QuickPhrases/Species/generic_species.yml | 55 +++
.../DeltaV/QuickPhrases/Subjects/command.yml | 34 ++
.../QuickPhrases/Subjects/engineering.yml | 119 +++++
.../QuickPhrases/Subjects/epistemics.yml | 89 ++++
.../DeltaV/QuickPhrases/Subjects/generic.yml | 230 ++++++++++
.../DeltaV/QuickPhrases/Subjects/justice.yml | 104 +++++
.../QuickPhrases/Subjects/logistics.yml | 156 +++++++
.../DeltaV/QuickPhrases/Subjects/medical.yml | 179 ++++++++
.../DeltaV/QuickPhrases/Subjects/security.yml | 74 +++
.../DeltaV/QuickPhrases/Subjects/service.yml | 144 ++++++
.../DeltaV/QuickPhrases/Threats/hazards.yml | 106 +++++
.../DeltaV/QuickPhrases/Threats/hostiles.yml | 66 +++
.../DeltaV/QuickPhrases/Threats/status.yml | 108 +++++
.../Prototypes/DeltaV/QuickPhrases/base.yml | 86 ++++
.../Prototypes/DeltaV/QuickPhrases/jobs.yml | 309 +++++++++++++
.../DeltaV/QuickPhrases/locations.yml | 434 ++++++++++++++++++
.../DeltaV/Recipes/Lathes/medical.yml | 7 +
.../Entities/Structures/Machines/lathe.yml | 1 +
.../Prototypes/Loadouts/loadout_groups.yml | 1 +
.../Devices/tablets.rsi/aac-inhand-left.png | Bin 0 -> 352 bytes
.../Devices/tablets.rsi/aac-inhand-right.png | Bin 0 -> 345 bytes
.../tablets.rsi/aac_screen-inhand-left.png | Bin 0 -> 238 bytes
.../tablets.rsi/aac_screen-inhand-right.png | Bin 0 -> 238 bytes
.../Devices/tablets.rsi/aac_screen.png | Bin 0 -> 268 bytes
.../Devices/tablets.rsi/aac_tablet.png | Bin 0 -> 320 bytes
.../Objects/Devices/tablets.rsi/meta.json | 33 ++
49 files changed, 3926 insertions(+)
create mode 100644 Content.Client/DeltaV/AACTablet/UI/AACBoundUserInterface.cs
create mode 100644 Content.Client/DeltaV/AACTablet/UI/AACWindow.xaml
create mode 100644 Content.Client/DeltaV/AACTablet/UI/AACWindow.xaml.cs
create mode 100644 Content.Server/DeltaV/AACTablet/AACTabletComponent.cs
create mode 100644 Content.Server/DeltaV/AACTablet/AACTabletSystem.cs
create mode 100644 Content.Shared/DeltaV/AACTablet/SharedAACTabletSystem.cs
create mode 100644 Content.Shared/DeltaV/QuickPhrase/QuickPhrasePrototype.cs
create mode 100644 Resources/Locale/en-US/deltav/phrases/common.ftl
create mode 100644 Resources/Locale/en-US/deltav/phrases/species.ftl
create mode 100644 Resources/Locale/en-US/deltav/phrases/subjects.ftl
create mode 100644 Resources/Locale/en-US/deltav/phrases/threats.ftl
create mode 100644 Resources/Locale/en-US/deltav/phrases/uncategorized.ftl
create mode 100644 Resources/Prototypes/DeltaV/Entities/Objects/Devices/aac_tablet.yml
create mode 100644 Resources/Prototypes/DeltaV/Loadouts/Miscellaneous/trinkets.yml
create mode 100644 Resources/Prototypes/DeltaV/QuickPhrases/Common/commands.yml
create mode 100644 Resources/Prototypes/DeltaV/QuickPhrases/Common/manners.yml
create mode 100644 Resources/Prototypes/DeltaV/QuickPhrases/Common/numbers.yml
create mode 100644 Resources/Prototypes/DeltaV/QuickPhrases/Common/pronouns.yml
create mode 100644 Resources/Prototypes/DeltaV/QuickPhrases/Common/qualitative.yml
create mode 100644 Resources/Prototypes/DeltaV/QuickPhrases/Common/questions.yml
create mode 100644 Resources/Prototypes/DeltaV/QuickPhrases/Species/animals.yml
create mode 100644 Resources/Prototypes/DeltaV/QuickPhrases/Species/crew.yml
create mode 100644 Resources/Prototypes/DeltaV/QuickPhrases/Species/generic_species.yml
create mode 100644 Resources/Prototypes/DeltaV/QuickPhrases/Subjects/command.yml
create mode 100644 Resources/Prototypes/DeltaV/QuickPhrases/Subjects/engineering.yml
create mode 100644 Resources/Prototypes/DeltaV/QuickPhrases/Subjects/epistemics.yml
create mode 100644 Resources/Prototypes/DeltaV/QuickPhrases/Subjects/generic.yml
create mode 100644 Resources/Prototypes/DeltaV/QuickPhrases/Subjects/justice.yml
create mode 100644 Resources/Prototypes/DeltaV/QuickPhrases/Subjects/logistics.yml
create mode 100644 Resources/Prototypes/DeltaV/QuickPhrases/Subjects/medical.yml
create mode 100644 Resources/Prototypes/DeltaV/QuickPhrases/Subjects/security.yml
create mode 100644 Resources/Prototypes/DeltaV/QuickPhrases/Subjects/service.yml
create mode 100644 Resources/Prototypes/DeltaV/QuickPhrases/Threats/hazards.yml
create mode 100644 Resources/Prototypes/DeltaV/QuickPhrases/Threats/hostiles.yml
create mode 100644 Resources/Prototypes/DeltaV/QuickPhrases/Threats/status.yml
create mode 100644 Resources/Prototypes/DeltaV/QuickPhrases/base.yml
create mode 100644 Resources/Prototypes/DeltaV/QuickPhrases/jobs.yml
create mode 100644 Resources/Prototypes/DeltaV/QuickPhrases/locations.yml
create mode 100644 Resources/Prototypes/DeltaV/Recipes/Lathes/medical.yml
create mode 100644 Resources/Textures/DeltaV/Objects/Devices/tablets.rsi/aac-inhand-left.png
create mode 100644 Resources/Textures/DeltaV/Objects/Devices/tablets.rsi/aac-inhand-right.png
create mode 100644 Resources/Textures/DeltaV/Objects/Devices/tablets.rsi/aac_screen-inhand-left.png
create mode 100644 Resources/Textures/DeltaV/Objects/Devices/tablets.rsi/aac_screen-inhand-right.png
create mode 100644 Resources/Textures/DeltaV/Objects/Devices/tablets.rsi/aac_screen.png
create mode 100644 Resources/Textures/DeltaV/Objects/Devices/tablets.rsi/aac_tablet.png
create mode 100644 Resources/Textures/DeltaV/Objects/Devices/tablets.rsi/meta.json
diff --git a/Content.Client/DeltaV/AACTablet/UI/AACBoundUserInterface.cs b/Content.Client/DeltaV/AACTablet/UI/AACBoundUserInterface.cs
new file mode 100644
index 0000000000..6a9330598f
--- /dev/null
+++ b/Content.Client/DeltaV/AACTablet/UI/AACBoundUserInterface.cs
@@ -0,0 +1,38 @@
+using Content.Shared.DeltaV.AACTablet;
+using Robust.Shared.Prototypes;
+
+namespace Content.Client.DeltaV.AACTablet.UI;
+
+public sealed class AACBoundUserInterface : BoundUserInterface
+{
+ [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
+
+ [ViewVariables]
+ private AACWindow? _window;
+
+ public AACBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
+ {
+ }
+
+ protected override void Open()
+ {
+ base.Open();
+ _window?.Close();
+ _window = new AACWindow(this, _prototypeManager);
+ _window.OpenCentered();
+
+ _window.PhraseButtonPressed += OnPhraseButtonPressed;
+ _window.OnClose += Close;
+ }
+
+ private void OnPhraseButtonPressed(string phraseId)
+ {
+ SendMessage(new AACTabletSendPhraseMessage(phraseId));
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ base.Dispose(disposing);
+ _window?.Dispose();
+ }
+}
diff --git a/Content.Client/DeltaV/AACTablet/UI/AACWindow.xaml b/Content.Client/DeltaV/AACTablet/UI/AACWindow.xaml
new file mode 100644
index 0000000000..e418393420
--- /dev/null
+++ b/Content.Client/DeltaV/AACTablet/UI/AACWindow.xaml
@@ -0,0 +1,9 @@
+
+
+
+
\ No newline at end of file
diff --git a/Content.Client/DeltaV/AACTablet/UI/AACWindow.xaml.cs b/Content.Client/DeltaV/AACTablet/UI/AACWindow.xaml.cs
new file mode 100644
index 0000000000..97767affac
--- /dev/null
+++ b/Content.Client/DeltaV/AACTablet/UI/AACWindow.xaml.cs
@@ -0,0 +1,147 @@
+using System.Linq;
+using System.Numerics;
+using Content.Client.UserInterface.Controls;
+using Content.Shared.DeltaV.QuickPhrase;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Prototypes;
+
+namespace Content.Client.DeltaV.AACTablet.UI;
+
+[GenerateTypedNameReferences]
+public sealed partial class AACWindow : FancyWindow
+{
+ private IPrototypeManager _prototypeManager;
+ public event Action? PhraseButtonPressed;
+
+ public AACWindow(AACBoundUserInterface ui, IPrototypeManager prototypeManager)
+ {
+ RobustXamlLoader.Load(this);
+ _prototypeManager = prototypeManager;
+ PopulateGui(ui);
+ }
+
+ private void PopulateGui(AACBoundUserInterface ui)
+ {
+ var loc = IoCManager.Resolve();
+ var phrases = _prototypeManager.EnumeratePrototypes().ToList();
+
+ // take ALL phrases and turn them into tabs and groups, so the buttons are sorted and tabbed
+ var sortedTabs = phrases
+ .GroupBy(p => p.Tab)
+ .OrderBy(g => g.Key)
+ .ToDictionary(
+ g => g.Key,
+ g => g.GroupBy(p => p.Group)
+ .OrderBy(gg => gg.Key)
+ .ToDictionary(
+ gg => gg.Key,
+ gg => gg.OrderBy(p => loc.GetString(p.Text)).ToList()
+ )
+ );
+
+ var tabContainer = CreateTabContainer(sortedTabs);
+ WindowBody.AddChild(tabContainer);
+ }
+
+ private TabContainer CreateTabContainer(Dictionary>> sortedTabs)
+ {
+ var tabContainer = new TabContainer();
+ var loc = IoCManager.Resolve();
+
+ foreach (var tab in sortedTabs)
+ {
+ var tabName = loc.GetString(tab.Key);
+ var boxContainer = CreateBoxContainerForTab(tab.Value);
+ tabContainer.AddChild(boxContainer);
+ tabContainer.SetTabTitle(tabContainer.ChildCount - 1, tabName);
+ }
+
+ return tabContainer;
+ }
+
+ private BoxContainer CreateBoxContainerForTab(Dictionary> groups)
+ {
+ var boxContainer = new BoxContainer()
+ {
+ HorizontalExpand = true,
+ Orientation = BoxContainer.LayoutOrientation.Vertical
+ };
+
+ foreach (var group in groups)
+ {
+ var buttonContainer = CreateButtonContainerForGroup(group.Value);
+ boxContainer.AddChild(buttonContainer);
+ }
+
+ return boxContainer;
+ }
+
+ private GridContainer CreateButtonContainerForGroup(List phrases)
+ {
+ var loc = IoCManager.Resolve();
+ var buttonContainer = CreateButtonContainer();
+ foreach (var phrase in phrases)
+ {
+ var text = loc.GetString(phrase.Text);
+ var button = CreatePhraseButton(text, phrase.StyleClass);
+ button.OnPressed += _ => OnPhraseButtonPressed(phrase.ID);
+ buttonContainer.AddChild(button);
+ }
+ return buttonContainer;
+ }
+
+ private static GridContainer CreateButtonContainer()
+ {
+ var buttonContainer = new GridContainer
+ {
+ Margin = new Thickness(10),
+ Columns = 4
+ };
+
+ return buttonContainer;
+ }
+
+ private static Button CreatePhraseButton(string text, string styleClass)
+ {
+ var buttonWidth = GetButtonWidth();
+ var phraseButton = new Button
+ {
+ Access = AccessLevel.Public,
+ MaxSize = new Vector2(buttonWidth, buttonWidth),
+ ClipText = false,
+ HorizontalExpand = true,
+ StyleClasses = { styleClass }
+ };
+
+ var buttonLabel = new RichTextLabel
+ {
+ Margin = new Thickness(0, 5),
+ StyleClasses = { "WhiteText" }
+ };
+
+ buttonLabel.SetMessage(text);
+ phraseButton.AddChild(buttonLabel);
+ return phraseButton;
+ }
+
+ private static int GetButtonWidth()
+ {
+ var spaceWidth = 10;
+ var parentWidth = 540;
+ var columnCount = 4;
+
+ var paddingSize = spaceWidth * 2;
+ var gutterScale = (columnCount - 1) / columnCount;
+ var columnWidth = (parentWidth - paddingSize) / columnCount;
+ var buttonWidth = columnWidth - spaceWidth * gutterScale;
+ return buttonWidth;
+ }
+
+ private void OnPhraseButtonPressed(string phraseId)
+ {
+ PhraseButtonPressed?.Invoke(phraseId);
+ }
+}
diff --git a/Content.Client/Stylesheets/StyleNano.cs b/Content.Client/Stylesheets/StyleNano.cs
index fe3843c34a..0cc40abae0 100644
--- a/Content.Client/Stylesheets/StyleNano.cs
+++ b/Content.Client/Stylesheets/StyleNano.cs
@@ -152,6 +152,35 @@ namespace Content.Client.Stylesheets
public static readonly Color ChatBackgroundColor = Color.FromHex("#25252ADD");
+ // DeltaV - AAC button variables
+ public static readonly string CommandButtonClass = "CommandButton";
+ public static readonly string EngineeringButtonClass = "EngineeringButton";
+ public static readonly string EpistemicsButtonClass = "EpistemicsButton";
+ public static readonly string JusticeButtonClass = "JusticeButton";
+ public static readonly string LogisticsButtonClass = "LogisticsButton";
+ public static readonly string MedicalButtonClass = "MedicalButton";
+ public static readonly string SecurityButtonClass = "SecurityButton";
+ public static readonly string ServiceButtonClass = "ServiceButton";
+
+ // DeltaV - AAC button colors
+ public static readonly Color CommandButtonColorDefault = Color.FromHex("#404A58");
+ public static readonly Color CommandColorHovered = Color.FromHex("#4F587B");
+ public static readonly Color EngineeringButtonColorDefault = Color.FromHex("#77684B");
+ public static readonly Color EngineeringColorHovered = Color.FromHex("#776D71");
+ public static readonly Color EpistemicsButtonColorDefault = Color.FromHex("#6F5973");
+ public static readonly Color EpistemicsColorHovered = Color.FromHex("#71638E");
+ public static readonly Color LogisticsButtonColorDefault = Color.FromHex("#61503A");
+ public static readonly Color LogisticsColorHovered = Color.FromHex("#675C64");
+ public static readonly Color JusticeButtonColorDefault = Color.FromHex("#4F3D4C");
+ public static readonly Color JusticeColorHovered = Color.FromHex("#5C4B5A");
+ public static readonly Color MedicalButtonColorDefault = Color.FromHex("#49687D");
+ public static readonly Color MedicalColorHovered = Color.FromHex("#556E95");
+ public static readonly Color SecurityButtonColorDefault = Color.FromHex("#724449");
+ public static readonly Color SecurityColorHovered = Color.FromHex("#745370");
+ public static readonly Color ServiceButtonColorDefault = Color.FromHex("#607952");
+ public static readonly Color ServiceColorHovered = Color.FromHex("#667A76");
+ // End DeltaV
+
//Bwoink
public const string StyleClassPinButtonPinned = "pinButtonPinned";
public const string StyleClassPinButtonUnpinned = "pinButtonUnpinned";
@@ -1615,6 +1644,88 @@ namespace Content.Client.Stylesheets
BackgroundColor = FancyTreeSelectedRowColor,
}),
+ // DeltaV - AAC button styles
+ Element()
+ .Class(CommandButtonClass)
+ .Pseudo(ContainerButton.StylePseudoClassNormal)
+ .Prop(Control.StylePropertyModulateSelf, CommandButtonColorDefault),
+
+ Element()
+ .Class(CommandButtonClass)
+ .Pseudo(ContainerButton.StylePseudoClassHover)
+ .Prop(Control.StylePropertyModulateSelf, CommandColorHovered),
+
+ Element()
+ .Class(EngineeringButtonClass)
+ .Pseudo(ContainerButton.StylePseudoClassNormal)
+ .Prop(Control.StylePropertyModulateSelf, EngineeringButtonColorDefault),
+
+ Element()
+ .Class(EngineeringButtonClass)
+ .Pseudo(ContainerButton.StylePseudoClassHover)
+ .Prop(Control.StylePropertyModulateSelf, EngineeringColorHovered),
+
+ Element()
+ .Class(EpistemicsButtonClass)
+ .Pseudo(ContainerButton.StylePseudoClassNormal)
+ .Prop(Control.StylePropertyModulateSelf, EpistemicsButtonColorDefault),
+
+ Element()
+ .Class(EpistemicsButtonClass)
+ .Pseudo(ContainerButton.StylePseudoClassHover)
+ .Prop(Control.StylePropertyModulateSelf, EpistemicsColorHovered),
+
+ Element()
+ .Class(LogisticsButtonClass)
+ .Pseudo(ContainerButton.StylePseudoClassNormal)
+ .Prop(Control.StylePropertyModulateSelf, LogisticsButtonColorDefault),
+
+ Element()
+ .Class(LogisticsButtonClass)
+ .Pseudo(ContainerButton.StylePseudoClassHover)
+ .Prop(Control.StylePropertyModulateSelf, LogisticsColorHovered),
+
+ Element()
+ .Class(MedicalButtonClass)
+ .Pseudo(ContainerButton.StylePseudoClassNormal)
+ .Prop(Control.StylePropertyModulateSelf, MedicalButtonColorDefault),
+
+ Element()
+ .Class(MedicalButtonClass)
+ .Pseudo(ContainerButton.StylePseudoClassHover)
+ .Prop(Control.StylePropertyModulateSelf, MedicalColorHovered),
+
+ Element()
+ .Class(SecurityButtonClass)
+ .Pseudo(ContainerButton.StylePseudoClassNormal)
+ .Prop(Control.StylePropertyModulateSelf, SecurityButtonColorDefault),
+
+ Element()
+ .Class(SecurityButtonClass)
+ .Pseudo(ContainerButton.StylePseudoClassHover)
+ .Prop(Control.StylePropertyModulateSelf, SecurityColorHovered),
+
+ Element()
+ .Class(ServiceButtonClass)
+ .Pseudo(ContainerButton.StylePseudoClassNormal)
+ .Prop(Control.StylePropertyModulateSelf, ServiceButtonColorDefault),
+
+ Element()
+ .Class(ServiceButtonClass)
+ .Pseudo(ContainerButton.StylePseudoClassHover)
+ .Prop(Control.StylePropertyModulateSelf, ServiceColorHovered),
+
+ Element()
+ .Class(JusticeButtonClass)
+ .Pseudo(ContainerButton.StylePseudoClassNormal)
+ .Prop(Control.StylePropertyModulateSelf, JusticeButtonColorDefault),
+
+ Element()
+ .Class(JusticeButtonClass)
+ .Pseudo(ContainerButton.StylePseudoClassHover)
+ .Prop(Control.StylePropertyModulateSelf, JusticeColorHovered),
+ // End DeltaV
+
// Silicon law edit ui
Element