Skip to content
Open
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
using Content.Shared.Imperial.XxRaay.Components;
using Robust.Client.UserInterface;

namespace Content.Client.Imperial.XxRaay.UI;

public sealed class AnimatronicControllerBoundUserInterface : BoundUserInterface
{
[ViewVariables]
private AnimatronicControllerWindow? _window;

public AnimatronicControllerBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
{
}

protected override void Open()
{
base.Open();

_window = this.CreateWindow<AnimatronicControllerWindow>();

_window.OnAnimatronicWaypointSelected += (animEntity, wpEntity) =>
{
SendMessage(new SetAnimatronicTargetEvent(animEntity, wpEntity));
};

_window.OnAnimatronicClearTarget += (animEntity) =>
{
SendMessage(new SetAnimatronicTargetEvent(animEntity, true));
};

_window.OnAnimatronicObserving += (animEntity) =>
{
SendMessage(new SetAnimatronicObservingEvent(animEntity));
};

SendMessage(new RequestAnimDataEvent());
}

protected override void UpdateState(BoundUserInterfaceState state)
{
base.UpdateState(state);

if (state is AnimDataStateEvent animState)
{
_window?.UpdateState(animState);
}
}

protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (disposing)
{
_window?.Dispose();
}
}
}

15 changes: 15 additions & 0 deletions Content.Client/Imperial/XxRaay/UI/AnimatronicControllerWindow.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<i:BaseImperialWindow
xmlns="https://spacestation14.io"
xmlns:i="clr-namespace:Content.Client.Imperial.UI.Windows.BaseImperialWindow"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
xmlns:ac="clr-namespace:Content.Client.Imperial.XxRaay.UI"
Comment on lines +4 to +5
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Неиспользуемые пространства имён controls и ac.

Оба xmlns-объявления (xmlns:controls и xmlns:ac) не используются в XAML. Их можно удалить.

♻️ Предлагаемое исправление
 <i:BaseImperialWindow
     xmlns="https://spacestation14.io"
     xmlns:i="clr-namespace:Content.Client.Imperial.UI.Windows.BaseImperialWindow"
-    xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
-    xmlns:ac="clr-namespace:Content.Client.Imperial.XxRaay.UI"
     HeaderText="{Loc animatronic-controller-window-title}"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
xmlns:ac="clr-namespace:Content.Client.Imperial.XxRaay.UI"
<i:BaseImperialWindow
xmlns="https://spacestation14.io"
xmlns:i="clr-namespace:Content.Client.Imperial.UI.Windows.BaseImperialWindow"
HeaderText="{Loc animatronic-controller-window-title}"
🤖 Prompt for AI Agents
In `@Content.Client/Imperial/XxRaay/UI/AnimatronicControllerWindow.xaml` around
lines 4 - 5, Удалите неиспользуемые декларации пространств имён xmlns:controls и
xmlns:ac из XAML в файле AnimatronicControllerWindow.xaml: найдите атрибуты
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls" и
xmlns:ac="clr-namespace:Content.Client.Imperial.XxRaay.UI" в корневом элементе и
удалите их, оставив только используемые xmlns, чтобы убрать лишние объявления и
предупреждения.

HeaderText="Animatronic Controller"
MinSize="700 400"
SetSize="800 450"
Resizable="True">
<BoxContainer Name="Contents" Access="Public" Orientation="Vertical" Margin="20 20 20 20">
<ScrollContainer VerticalExpand="True" MinHeight="300">
<BoxContainer Name="AnimatronicsList" Access="Public" Orientation="Vertical" SeparationOverride="10" />
</ScrollContainer>
</BoxContainer>
</i:BaseImperialWindow>
299 changes: 299 additions & 0 deletions Content.Client/Imperial/XxRaay/UI/AnimatronicControllerWindow.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,299 @@
using System.Collections.Generic;
using System.Numerics;
using Content.Client.Imperial.UI.Windows.BaseImperialWindow;
using Content.Shared.Imperial.XxRaay.Components;
using Robust.Client.AutoGenerated;
using Robust.Client.Graphics;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Network;
using Robust.Shared.Maths;
using Robust.Client.GameObjects;

namespace Content.Client.Imperial.XxRaay.UI;

[GenerateTypedNameReferences]
public sealed partial class AnimatronicControllerWindow : BaseImperialWindow
{
[Dependency] private readonly IEntityManager _entityManager = default!;

public event Action<NetEntity, NetEntity>? OnAnimatronicWaypointSelected;
public event Action<NetEntity>? OnAnimatronicClearTarget;
public event Action<NetEntity>? OnAnimatronicObserving;

private List<AnimDto> _currentAnims = new();
private List<WaypointDto> _currentWaypoints = new();
private Dictionary<NetEntity, PanelContainer> _animCards = new();
private Dictionary<NetEntity, Color> _buttonOriginalColors = new();

public AnimatronicControllerWindow()
{
RobustXamlLoader.Load(this);
IoCManager.InjectDependencies(this);
}

public void UpdateState(AnimDataStateEvent state)
{
_currentAnims = state.Animatronics;
_currentWaypoints = state.Waypoints;

var animScrollContainer = Contents.GetChild(0) as ScrollContainer;
var animList = animScrollContainer?.GetChild(0) as BoxContainer;

animList?.RemoveAllChildren();
_animCards.Clear();
_buttonOriginalColors.Clear();

foreach (var anim in state.Animatronics)
{
var card = CreateAnimatronicCard(anim, state.Waypoints);
animList?.AddChild(card);
_animCards[anim.Entity] = card;
}
}

private PanelContainer CreateAnimatronicCard(AnimDto anim, List<WaypointDto> waypoints)
{
var card = new PanelContainer
{
StyleClasses = { "AngleRectPass" },
HorizontalExpand = true,
Margin = new Thickness(0, 0, 0, 5)
};

var cardContent = new BoxContainer
{
Orientation = BoxContainer.LayoutOrientation.Horizontal,
Margin = new Thickness(10, 10, 10, 10),
SeparationOverride = 0,
HorizontalExpand = true
};

var spritePanel = new PanelContainer
{
PanelOverride = new StyleBoxFlat
{
BackgroundColor = Color.FromHex("#2A2A2A")
},
VerticalExpand = true
};

var spriteView = new SpriteView
{
OverrideDirection = Direction.South,
Scale = new Vector2(5f, 5f),
SetSize = new Vector2(96, 96),
Margin = new Thickness(5, 5, 5, 5),
VerticalExpand = true,
VerticalAlignment = VAlignment.Center
};

var entity = _entityManager.GetEntity(anim.Entity);
if (entity.Valid)
{
spriteView.SetEntity(entity);
}
Comment on lines +81 to +85
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Нет обработки случая невалидной entity.

Если entity.Valid == false, spriteView останется пустым без визуальной индикации. Стоит рассмотреть отображение placeholder-текста или иконки ошибки для отладки.

🤖 Prompt for AI Agents
In `@Content.Client/Imperial/XxRaay/UI/AnimatronicControllerWindow.xaml.cs` around
lines 87 - 91, The code currently only handles the valid case of
_entityManager.GetEntity(anim.Entity); add an else branch for when entity.Valid
== false to avoid leaving spriteView empty: clear any previous entity via
spriteView.SetEntity(null) or spriteView.ClearEntity, then set a visible
placeholder or error indicator (e.g., spriteView.ShowPlaceholder("Invalid
entity") or spriteView.SetErrorIcon) so the UI shows that the entity is invalid;
update the block around _entityManager.GetEntity, entity.Valid, and
spriteView.SetEntity accordingly.


spritePanel.AddChild(spriteView);

var nameStatusPanel = new PanelContainer
{
PanelOverride = new StyleBoxFlat
{
BackgroundColor = Color.FromHex("#333333")
},
HorizontalExpand = true,
VerticalExpand = true
};

var nameStatusContent = new BoxContainer
{
Orientation = BoxContainer.LayoutOrientation.Horizontal,
Margin = new Thickness(5, 5, 5, 5),
SeparationOverride = 10,
HorizontalExpand = true
};

var nameStatusContainer = new BoxContainer
{
Orientation = BoxContainer.LayoutOrientation.Vertical,
VerticalExpand = true,
VerticalAlignment = VAlignment.Center
};

var nameLabel = new Label
{
Text = anim.DisplayName,
StyleClasses = { "LabelSubTextPassExtraBoldServerName" },
ModulateSelfOverride = Color.White
};

var statusContainer = new BoxContainer
{
Orientation = BoxContainer.LayoutOrientation.Horizontal,
SeparationOverride = 3
};

if (anim.CurrentWaypointId != null)
{
var dotsLabel = new Label
{
Text = "···",
StyleClasses = { "LabelSubTextPassMediumBuy" },
ModulateSelfOverride = Color.FromHex("#FFD700"),
VerticalAlignment = VAlignment.Center,
Margin = new Thickness(0, 0, 3, 0)
};

var waypointLabel = new Label
{
Text = anim.CurrentWaypointId,
StyleClasses = { "LabelSubTextPassMediumBuy" },
ModulateSelfOverride = Color.FromHex("#FFD700"),
VerticalAlignment = VAlignment.Center
};

statusContainer.AddChild(dotsLabel);
statusContainer.AddChild(waypointLabel);
}
else
{
var statusLabel = new Label
{
Text = "Idle",
StyleClasses = { "LabelSubTextPassMediumBuy" },
ModulateSelfOverride = Color.FromHex("#888888")
};
statusContainer.AddChild(statusLabel);
}

nameStatusContainer.AddChild(nameLabel);
nameStatusContainer.AddChild(statusContainer);

var waypointButtonsContent = new BoxContainer
{
Orientation = BoxContainer.LayoutOrientation.Horizontal,
SeparationOverride = 5,
HorizontalExpand = true
};

var waypointGrid = new GridContainer
{
Columns = 2,
HorizontalExpand = true
};

foreach (var wp in waypoints)
{
var isActive = anim.CurrentWaypointId == wp.WaypointId;
var wpButton = new Button
{
Text = wp.WaypointId,
HorizontalExpand = true
};

var originalColor = isActive ? Color.FromHex("#4CAF50") : Color.FromHex("#888888");
_buttonOriginalColors[wp.Entity] = originalColor;
wpButton.ModulateSelfOverride = originalColor;

wpButton.StyleClasses.Add("ButtonOpenBoth");

var animEntity = anim.Entity;
var wpEntity = wp.Entity;

wpButton.OnPressed += _ =>
{
OnAnimatronicWaypointSelected?.Invoke(animEntity, wpEntity);
};

wpButton.OnMouseEntered += _ =>
{
if (!isActive)
wpButton.ModulateSelfOverride = Color.FromHex("#AAAAAA");
};

wpButton.OnMouseExited += _ =>
{
if (!isActive && _buttonOriginalColors.TryGetValue(wpEntity, out var original))
wpButton.ModulateSelfOverride = original;
};

waypointGrid.AddChild(wpButton);
}

var buttonsContainer = new BoxContainer
{
Orientation = BoxContainer.LayoutOrientation.Vertical,
SeparationOverride = 5,
VerticalExpand = true
};

var clearButton = new Button
{
Text = "Clear",
StyleClasses = { "ButtonOpenBoth" },
ModulateSelfOverride = Color.FromHex("#FF4444"),
VerticalExpand = true,
HorizontalExpand = true
};

var clearButtonOriginalColor = Color.FromHex("#FF4444");
var animEntityForClear = anim.Entity;
clearButton.OnPressed += _ =>
{
OnAnimatronicClearTarget?.Invoke(animEntityForClear);
};

clearButton.OnMouseEntered += _ =>
{
clearButton.ModulateSelfOverride = Color.FromHex("#FF6666");
};

clearButton.OnMouseExited += _ =>
{
clearButton.ModulateSelfOverride = clearButtonOriginalColor;
};

var observingButton = new Button
{
Text = "Observing",
StyleClasses = { "ButtonOpenBoth" },
ModulateSelfOverride = Color.FromHex("#4A90E2"),
VerticalExpand = true,
HorizontalExpand = true
};

var observingButtonOriginalColor = Color.FromHex("#4A90E2");
var animEntityForObserving = anim.Entity;
observingButton.OnPressed += _ =>
{
OnAnimatronicObserving?.Invoke(animEntityForObserving);
};

observingButton.OnMouseEntered += _ =>
{
observingButton.ModulateSelfOverride = Color.FromHex("#6BA3E8");
};

observingButton.OnMouseExited += _ =>
{
observingButton.ModulateSelfOverride = observingButtonOriginalColor;
};

buttonsContainer.AddChild(clearButton);
buttonsContainer.AddChild(observingButton);

waypointButtonsContent.AddChild(waypointGrid);
waypointButtonsContent.AddChild(buttonsContainer);

nameStatusContent.AddChild(nameStatusContainer);
nameStatusContent.AddChild(waypointButtonsContent);
nameStatusPanel.AddChild(nameStatusContent);

cardContent.AddChild(spritePanel);
cardContent.AddChild(nameStatusPanel);
card.AddChild(cardContent);

return card;
}
}
Loading
Loading