Station AI customizations (#34501)

Co-authored-by: Nemanja <98561806+EmoGarbage404@users.noreply.github.com>
Co-authored-by: ArtisticRoomba <145879011+ArtisticRoomba@users.noreply.github.com>
This commit is contained in:
chromiumboy
2025-05-13 03:50:43 -05:00
committed by GitHub
parent 0eeb46cf9e
commit 7ac67ebf8c
29 changed files with 974 additions and 101 deletions

View File

@@ -0,0 +1,40 @@
using Content.Shared.Silicons.StationAi;
using Robust.Shared.Prototypes;
namespace Content.Client.Silicons.StationAi;
public sealed class StationAiCustomizationBoundUserInterface : BoundUserInterface
{
private StationAiCustomizationMenu? _menu;
public StationAiCustomizationBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
{
}
protected override void Open()
{
base.Open();
_menu = new StationAiCustomizationMenu(Owner);
_menu.OpenCentered();
_menu.OnClose += Close;
_menu.SendStationAiCustomizationMessageAction += SendStationAiCustomizationMessage;
}
public void SendStationAiCustomizationMessage(ProtoId<StationAiCustomizationGroupPrototype> groupProtoId, ProtoId<StationAiCustomizationPrototype> customizationProtoId)
{
SendPredictedMessage(new StationAiCustomizationMessage(groupProtoId, customizationProtoId));
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (!disposing)
return;
_menu?.Dispose();
}
}

View File

@@ -0,0 +1,23 @@
<controls:FancyWindow xmlns="https://spacestation14.io"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
xmlns:graphics="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
SetSize="600 600"
MinSize="600 220">
<BoxContainer Orientation="Vertical" VerticalExpand="True">
<BoxContainer Orientation="Horizontal" HorizontalExpand="True" Margin="10 5 10 -5">
<controls:StripeBack HorizontalExpand="True">
<BoxContainer Orientation="Horizontal" HorizontalExpand="True">
<Control SetWidth="200">
<Label Text="{Loc 'station-ai-customization-categories'}" Margin="0 5 0 5" HorizontalExpand="True" HorizontalAlignment="Center" />
</Control>
<Label Text="{Loc 'station-ai-customization-options'}" Margin="0 5 0 5" HorizontalExpand="True" HorizontalAlignment="Center"/>
</BoxContainer>
</controls:StripeBack>
</BoxContainer>
<VerticalTabContainer Name="CustomizationGroupsContainer"
VerticalExpand="True"
HorizontalExpand="True">
</VerticalTabContainer>
</BoxContainer>
</controls:FancyWindow>

View File

@@ -0,0 +1,176 @@
using Content.Client.UserInterface.Controls;
using Content.Shared.Silicons.StationAi;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes;
using Robust.Shared.Utility;
using System.Linq;
using System.Numerics;
namespace Content.Client.Silicons.StationAi;
[GenerateTypedNameReferences]
public sealed partial class StationAiCustomizationMenu : FancyWindow
{
[Dependency] private readonly IEntityManager _entManager = default!;
[Dependency] private readonly IPrototypeManager _protoManager = default!;
private Dictionary<ProtoId<StationAiCustomizationGroupPrototype>, StationAiCustomizationGroupContainer> _groupContainers = new();
private Dictionary<ProtoId<StationAiCustomizationGroupPrototype>, ButtonGroup> _buttonGroups = new();
public event Action<ProtoId<StationAiCustomizationGroupPrototype>, ProtoId<StationAiCustomizationPrototype>>? SendStationAiCustomizationMessageAction;
private const float IconScale = 1.75f;
public StationAiCustomizationMenu(EntityUid owner)
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
var stationAiSystem = _entManager.System<SharedStationAiSystem>();
// Load customziation data
_entManager.TryGetComponent<StationAiCoreComponent>(owner, out var stationAiCore);
stationAiSystem.TryGetHeld((owner, stationAiCore), out var insertedAi);
_entManager.TryGetComponent<StationAiCustomizationComponent>(insertedAi, out var stationAiCustomization);
// Create UI entires for each group of customizations
var groupPrototypes = _protoManager.EnumeratePrototypes<StationAiCustomizationGroupPrototype>();
groupPrototypes = groupPrototypes.OrderBy(x => x.ID); // To ensure consistency in presentation
foreach (var groupPrototype in groupPrototypes)
{
StationAiCustomizationPrototype? selectedPrototype = null;
if (stationAiCustomization?.ProtoIds.TryGetValue(groupPrototype, out var selectedProtoId) == true)
_protoManager.TryIndex(selectedProtoId, out selectedPrototype);
_buttonGroups[groupPrototype] = new ButtonGroup();
_groupContainers[groupPrototype] = new StationAiCustomizationGroupContainer(groupPrototype, selectedPrototype, _buttonGroups[groupPrototype], this, _protoManager);
CustomizationGroupsContainer.AddTab(_groupContainers[groupPrototype], Loc.GetString(groupPrototype.Name));
}
Title = Loc.GetString("station-ai-customization-menu");
}
public void OnSendStationAiCustomizationMessage
(ProtoId<StationAiCustomizationGroupPrototype> groupProtoId, ProtoId<StationAiCustomizationPrototype> customizationProtoId)
{
SendStationAiCustomizationMessageAction?.Invoke(groupProtoId, customizationProtoId);
}
private sealed class StationAiCustomizationGroupContainer : BoxContainer
{
public StationAiCustomizationGroupContainer
(StationAiCustomizationGroupPrototype groupPrototype,
StationAiCustomizationPrototype? selectedPrototype,
ButtonGroup buttonGroup,
StationAiCustomizationMenu menu,
IPrototypeManager protoManager)
{
Orientation = LayoutOrientation.Vertical;
HorizontalExpand = true;
VerticalExpand = true;
// Create UI entries for all customization in the group
foreach (var protoId in groupPrototype.ProtoIds)
{
if (!protoManager.TryIndex(protoId, out var prototype))
continue;
var entry = new StationAiCustomizationEntryContainer(groupPrototype, prototype, buttonGroup, menu);
AddChild(entry);
if (prototype == selectedPrototype)
entry.SelectButton.Pressed = true;
}
}
}
private sealed class StationAiCustomizationEntryContainer : BoxContainer
{
public ProtoId<StationAiCustomizationPrototype> ProtoId;
public Button SelectButton;
public StationAiCustomizationEntryContainer
(StationAiCustomizationGroupPrototype groupPrototype,
StationAiCustomizationPrototype prototype,
ButtonGroup buttonGroup,
StationAiCustomizationMenu menu)
{
ProtoId = prototype;
Orientation = LayoutOrientation.Horizontal;
HorizontalExpand = true;
// Create a selection button
SelectButton = new Button
{
Text = Loc.GetString(prototype.Name),
HorizontalExpand = true,
ToggleMode = true,
Group = buttonGroup,
};
SelectButton.OnPressed += args =>
{
menu.OnSendStationAiCustomizationMessage(groupPrototype, prototype);
};
AddChild(SelectButton);
// Creat a background for the preview
var background = new AnimatedTextureRect
{
HorizontalAlignment = HAlignment.Center,
VerticalAlignment = VAlignment.Center,
SetWidth = 56,
SetHeight = 56,
Margin = new Thickness(10f, 2f),
};
background.DisplayRect.TextureScale = new Vector2(IconScale, IconScale);
if (prototype.PreviewBackground != null)
{
background.SetFromSpriteSpecifier(prototype.PreviewBackground);
}
AddChild(background);
// Create a preview icon
var icon = new AnimatedTextureRect
{
HorizontalAlignment = HAlignment.Center,
VerticalAlignment = VAlignment.Center,
SetWidth = 56,
SetHeight = 56,
};
icon.DisplayRect.TextureScale = new Vector2(IconScale, IconScale);
// Default RSI path/state
var rsiPath = prototype.LayerData.FirstOrNull()?.Value.RsiPath;
var rsiState = prototype.LayerData.FirstOrNull()?.Value.State;
// Specified RSI path/state
if (!string.IsNullOrEmpty(prototype.PreviewKey) && prototype.LayerData.TryGetValue(prototype.PreviewKey, out var layerData))
{
rsiPath = layerData.RsiPath;
rsiState = layerData.State;
}
// Update icon
if (rsiPath != null && rsiState != null)
{
var specifier = new SpriteSpecifier.Rsi(new ResPath(rsiPath), rsiState);
icon.SetFromSpriteSpecifier(specifier);
}
background.AddChild(icon);
}
}
}

View File

@@ -1,4 +1,5 @@
using Content.Shared.Silicons.StationAi;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.Player;
using Robust.Shared.Player;
@@ -9,6 +10,7 @@ public sealed partial class StationAiSystem : SharedStationAiSystem
{
[Dependency] private readonly IOverlayManager _overlayMgr = default!;
[Dependency] private readonly IPlayerManager _player = default!;
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
private StationAiOverlay? _overlay;
@@ -22,6 +24,7 @@ public sealed partial class StationAiSystem : SharedStationAiSystem
SubscribeLocalEvent<StationAiOverlayComponent, LocalPlayerDetachedEvent>(OnAiDetached);
SubscribeLocalEvent<StationAiOverlayComponent, ComponentInit>(OnAiOverlayInit);
SubscribeLocalEvent<StationAiOverlayComponent, ComponentRemove>(OnAiOverlayRemove);
SubscribeLocalEvent<StationAiCoreComponent, AppearanceChangeEvent>(OnAppearanceChange);
}
private void OnAiOverlayInit(Entity<StationAiOverlayComponent> ent, ref ComponentInit args)
@@ -72,6 +75,17 @@ public sealed partial class StationAiSystem : SharedStationAiSystem
RemoveOverlay();
}
private void OnAppearanceChange(Entity<StationAiCoreComponent> entity, ref AppearanceChangeEvent args)
{
if (args.Sprite == null)
return;
if (_appearance.TryGetData<PrototypeLayerData>(entity.Owner, StationAiVisualState.Key, out var layerData, args.Component))
args.Sprite.LayerSetData(StationAiVisualState.Key, layerData);
args.Sprite.LayerSetVisible(StationAiVisualState.Key, layerData != null);
}
public override void Shutdown()
{
base.Shutdown();