Skip to content

Commit

Permalink
AAC tablet for speech-impaired characters (new-frontiers-14#1491)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>
  • Loading branch information
2 people authored and dvir001 committed Dec 26, 2024
1 parent a9efdd6 commit 54a326f
Show file tree
Hide file tree
Showing 49 changed files with 3,934 additions and 0 deletions.
38 changes: 38 additions & 0 deletions Content.Client/DeltaV/AACTablet/UI/AACBoundUserInterface.cs
Original file line number Diff line number Diff line change
@@ -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();
}
}
9 changes: 9 additions & 0 deletions Content.Client/DeltaV/AACTablet/UI/AACWindow.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<controls:FancyWindow xmlns="https://spacestation14.io"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
Title="AAC Tablet"
Resizable="False"
SetSize="540 300"
MinSize="540 300">
<ScrollContainer HScrollEnabled="False" Name="WindowBody">
</ScrollContainer>
</controls:FancyWindow>
147 changes: 147 additions & 0 deletions Content.Client/DeltaV/AACTablet/UI/AACWindow.xaml.cs
Original file line number Diff line number Diff line change
@@ -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<string>? PhraseButtonPressed;

public AACWindow(AACBoundUserInterface ui, IPrototypeManager prototypeManager)
{
RobustXamlLoader.Load(this);
_prototypeManager = prototypeManager;
PopulateGui(ui);
}

private void PopulateGui(AACBoundUserInterface ui)
{
var loc = IoCManager.Resolve<ILocalizationManager>();
var phrases = _prototypeManager.EnumeratePrototypes<QuickPhrasePrototype>().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<string, Dictionary<string, List<QuickPhrasePrototype>>> sortedTabs)
{
var tabContainer = new TabContainer();
var loc = IoCManager.Resolve<ILocalizationManager>();

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<string, List<QuickPhrasePrototype>> 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<QuickPhrasePrototype> phrases)
{
var loc = IoCManager.Resolve<ILocalizationManager>();
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);
}
}
111 changes: 111 additions & 0 deletions Content.Client/Stylesheets/StyleNano.cs
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,35 @@ public sealed class StyleNano : StyleBase

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";
Expand Down Expand Up @@ -1634,6 +1663,88 @@ public StyleNano(IResourceCache resCache) : base(resCache)
BackgroundColor = FancyTreeSelectedRowColor,
}),

// DeltaV - AAC button styles
Element<ContainerButton>()
.Class(CommandButtonClass)
.Pseudo(ContainerButton.StylePseudoClassNormal)
.Prop(Control.StylePropertyModulateSelf, CommandButtonColorDefault),

Element<ContainerButton>()
.Class(CommandButtonClass)
.Pseudo(ContainerButton.StylePseudoClassHover)
.Prop(Control.StylePropertyModulateSelf, CommandColorHovered),

Element<ContainerButton>()
.Class(EngineeringButtonClass)
.Pseudo(ContainerButton.StylePseudoClassNormal)
.Prop(Control.StylePropertyModulateSelf, EngineeringButtonColorDefault),

Element<ContainerButton>()
.Class(EngineeringButtonClass)
.Pseudo(ContainerButton.StylePseudoClassHover)
.Prop(Control.StylePropertyModulateSelf, EngineeringColorHovered),

Element<ContainerButton>()
.Class(EpistemicsButtonClass)
.Pseudo(ContainerButton.StylePseudoClassNormal)
.Prop(Control.StylePropertyModulateSelf, EpistemicsButtonColorDefault),

Element<ContainerButton>()
.Class(EpistemicsButtonClass)
.Pseudo(ContainerButton.StylePseudoClassHover)
.Prop(Control.StylePropertyModulateSelf, EpistemicsColorHovered),

Element<ContainerButton>()
.Class(LogisticsButtonClass)
.Pseudo(ContainerButton.StylePseudoClassNormal)
.Prop(Control.StylePropertyModulateSelf, LogisticsButtonColorDefault),

Element<ContainerButton>()
.Class(LogisticsButtonClass)
.Pseudo(ContainerButton.StylePseudoClassHover)
.Prop(Control.StylePropertyModulateSelf, LogisticsColorHovered),

Element<ContainerButton>()
.Class(MedicalButtonClass)
.Pseudo(ContainerButton.StylePseudoClassNormal)
.Prop(Control.StylePropertyModulateSelf, MedicalButtonColorDefault),

Element<ContainerButton>()
.Class(MedicalButtonClass)
.Pseudo(ContainerButton.StylePseudoClassHover)
.Prop(Control.StylePropertyModulateSelf, MedicalColorHovered),

Element<ContainerButton>()
.Class(SecurityButtonClass)
.Pseudo(ContainerButton.StylePseudoClassNormal)
.Prop(Control.StylePropertyModulateSelf, SecurityButtonColorDefault),

Element<ContainerButton>()
.Class(SecurityButtonClass)
.Pseudo(ContainerButton.StylePseudoClassHover)
.Prop(Control.StylePropertyModulateSelf, SecurityColorHovered),

Element<ContainerButton>()
.Class(ServiceButtonClass)
.Pseudo(ContainerButton.StylePseudoClassNormal)
.Prop(Control.StylePropertyModulateSelf, ServiceButtonColorDefault),

Element<ContainerButton>()
.Class(ServiceButtonClass)
.Pseudo(ContainerButton.StylePseudoClassHover)
.Prop(Control.StylePropertyModulateSelf, ServiceColorHovered),

Element<ContainerButton>()
.Class(JusticeButtonClass)
.Pseudo(ContainerButton.StylePseudoClassNormal)
.Prop(Control.StylePropertyModulateSelf, JusticeButtonColorDefault),

Element<ContainerButton>()
.Class(JusticeButtonClass)
.Pseudo(ContainerButton.StylePseudoClassHover)
.Prop(Control.StylePropertyModulateSelf, JusticeColorHovered),
// End DeltaV

// Silicon law edit ui
Element<Label>().Class(SiliconLawContainer.StyleClassSiliconLawPositionLabel)
.Prop(Label.StylePropertyFontColor, NanoGold),
Expand Down
13 changes: 13 additions & 0 deletions Content.Server/DeltaV/AACTablet/AACTabletComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace Content.Server.DeltaV.AACTablet;

[RegisterComponent]
public sealed partial class AACTabletComponent : Component
{
// Minimum time between each phrase, to prevent spam
[DataField]
public TimeSpan Cooldown = TimeSpan.FromSeconds(1);

// Time that the next phrase can be sent.
[DataField]
public TimeSpan NextPhrase;
}
47 changes: 47 additions & 0 deletions Content.Server/DeltaV/AACTablet/AACTabletSystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
using Content.Server.Chat.Systems;
using Content.Shared.DeltaV.AACTablet;
using Content.Shared.DeltaV.QuickPhrase;
using Content.Shared.IdentityManagement;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;

namespace Content.Server.DeltaV.AACTablet;

public sealed class AACTabletSystem : EntitySystem
{
[Dependency] private readonly ChatSystem _chat = default!;
[Dependency] private readonly ILocalizationManager _loc = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] protected readonly IGameTiming Timing = default!;

public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<AACTabletComponent, AACTabletSendPhraseMessage>(OnSendPhrase);
}

private void OnSendPhrase(EntityUid uid, AACTabletComponent component, AACTabletSendPhraseMessage message)
{
if (component.NextPhrase > Timing.CurTime)
return;

// the AAC tablet uses the name of the person who pressed the tablet button
// for quality of life
var senderName = Identity.Entity(message.Actor, EntityManager);
var speakerName = Loc.GetString("speech-name-relay",
("speaker", Name(uid)),
("originalName", senderName));

if (!_prototypeManager.TryIndex<QuickPhrasePrototype>(message.PhraseID, out var phrase))
return;

_chat.TrySendInGameICMessage(uid,
_loc.GetString(phrase.Text),
InGameICChatType.Speak,
hideChat: false,
nameOverride: speakerName);

var curTime = Timing.CurTime;
component.NextPhrase = curTime + component.Cooldown;
}
}
Loading

0 comments on commit 54a326f

Please sign in to comment.