Update Space Villain's UI (#40963)

* Rewrite `SpaceVillainArcadeMenu`'s layout code

* Rewrite `SpaceVillainArcadeMenu` to use XAML

* Run "Format document" on the XAML

* Make `SpaceVillainGame` strings more specific

* Update Space Villain's localization

Potentially breaking: All strings related to Space Villain now share the `space-villain` prefix, instead of mixing `spacevillain` and `space-villain`.

* Use `StyleClasses` to make the action buttons display as a single line

* Capitalize the Space Villain action button text

* Tweak Space Villain colors a bit

* Capitalize Space Villain's name in action messages

* Capitalize slime enemy last name

* Fix accidentaly find-and-replaced comment

* Revert consistency changes to localization keys

* Remove unnecessary expand and stretch properties

* Rewrite Space Villain's info grid into a series of nested `BoxContainer`

* Make HP and MP labels start with a space so that the spacer keeps a fixed size

* Change spacers into plain `Control`s

* fix

---------

Co-authored-by: Pok245 <113675512+Pok27@users.noreply.github.com>
This commit is contained in:
Stefano Pigozzi 2026-07-17 18:33:56 +02:00 • committed by GitHub
parent c32ccf787b
commit 06da8d4b7f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 163 additions and 128 deletions

View file

@ -1,114 +0,0 @@
using System.Numerics;
using Content.Client.Arcade.UI;
using Content.Shared.Arcade;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
namespace Content.Client.Arcade
{
public sealed class SpaceVillainArcadeMenu : DefaultWindow
{
private readonly Label _enemyNameLabel;
private readonly Label _playerInfoLabel;
private readonly Label _enemyInfoLabel;
private readonly Label _playerActionLabel;
private readonly Label _enemyActionLabel;
private readonly Button[] _gameButtons = new Button[3]; //used to disable/enable all game buttons
public event Action<SharedSpaceVillainArcadeComponent.PlayerAction>? OnPlayerAction;
public SpaceVillainArcadeMenu()
{
MinSize = SetSize = new Vector2(300, 225);
Title = Loc.GetString("spacevillain-menu-title");
var grid = new GridContainer { Columns = 1 };
var infoGrid = new GridContainer { Columns = 3 };
infoGrid.AddChild(new Label { Text = Loc.GetString("spacevillain-menu-label-player"), Align = Label.AlignMode.Center });
infoGrid.AddChild(new Label { Text = "|", Align = Label.AlignMode.Center });
_enemyNameLabel = new Label { Align = Label.AlignMode.Center };
infoGrid.AddChild(_enemyNameLabel);
_playerInfoLabel = new Label { Align = Label.AlignMode.Center };
infoGrid.AddChild(_playerInfoLabel);
infoGrid.AddChild(new Label { Text = "|", Align = Label.AlignMode.Center });
_enemyInfoLabel = new Label { Align = Label.AlignMode.Center };
infoGrid.AddChild(_enemyInfoLabel);
var centerContainer = new CenterContainer();
centerContainer.AddChild(infoGrid);
grid.AddChild(centerContainer);
_playerActionLabel = new Label { Align = Label.AlignMode.Center };
grid.AddChild(_playerActionLabel);
_enemyActionLabel = new Label { Align = Label.AlignMode.Center };
grid.AddChild(_enemyActionLabel);
var buttonGrid = new GridContainer { Columns = 3 };
_gameButtons[0] = new Button()
{
Text = Loc.GetString("spacevillain-menu-button-attack")
};
_gameButtons[0].OnPressed +=
_ => OnPlayerAction?.Invoke(SharedSpaceVillainArcadeComponent.PlayerAction.Attack);
buttonGrid.AddChild(_gameButtons[0]);
_gameButtons[1] = new Button()
{
Text = Loc.GetString("spacevillain-menu-button-heal")
};
_gameButtons[1].OnPressed +=
_ => OnPlayerAction?.Invoke(SharedSpaceVillainArcadeComponent.PlayerAction.Heal);
buttonGrid.AddChild(_gameButtons[1]);
_gameButtons[2] = new Button()
{
Text = Loc.GetString("spacevillain-menu-button-recharge")
};
_gameButtons[2].OnPressed +=
_ => OnPlayerAction?.Invoke(SharedSpaceVillainArcadeComponent.PlayerAction.Recharge);
buttonGrid.AddChild(_gameButtons[2]);
centerContainer = new CenterContainer();
centerContainer.AddChild(buttonGrid);
grid.AddChild(centerContainer);
var newGame = new Button()
{
Text = Loc.GetString("spacevillain-menu-button-new-game")
};
newGame.OnPressed += _ => OnPlayerAction?.Invoke(SharedSpaceVillainArcadeComponent.PlayerAction.NewGame);
grid.AddChild(newGame);
ContentsContainer.AddChild(grid);
}
private void UpdateMetadata(SharedSpaceVillainArcadeComponent.SpaceVillainArcadeMetaDataUpdateMessage message)
{
Title = message.GameTitle;
_enemyNameLabel.Text = message.EnemyName;
foreach (var gameButton in _gameButtons)
{
gameButton.Disabled = message.ButtonsDisabled;
}
}
public void UpdateInfo(SharedSpaceVillainArcadeComponent.SpaceVillainArcadeDataUpdateMessage message)
{
if (message is SharedSpaceVillainArcadeComponent.SpaceVillainArcadeMetaDataUpdateMessage metaMessage)
UpdateMetadata(metaMessage);
_playerInfoLabel.Text = $"HP: {message.PlayerHP} MP: {message.PlayerMP}";
_enemyInfoLabel.Text = $"HP: {message.EnemyHP} MP: {message.EnemyMP}";
_playerActionLabel.Text = message.PlayerActionMessage;
_enemyActionLabel.Text = message.EnemyActionMessage;
}
}
}

View file

@ -0,0 +1,98 @@
<DefaultWindow
xmlns="https://spacestation14.io"
Title="{Loc 'spacevillain-menu-title'}"
MinWidth="540"
MinHeight="225">
<BoxContainer
Orientation="Vertical">
<!-- Info grid -->
<BoxContainer>
<!-- Player -->
<BoxContainer
Orientation="Vertical"
HorizontalExpand="True">
<!-- Name -->
<Label
Text="{Loc 'spacevillain-menu-label-player'}"
Align="Center"
HorizontalExpand="True"/>
<!-- Stats -->
<BoxContainer>
<!-- HP -->
<Label
Name="PlayerHpLabel"
Align="Center"
HorizontalExpand="True"
Modulate="#37e928"/>
<!-- MP -->
<Label
Name="PlayerMpLabel"
Align="Center"
HorizontalExpand="True"
Modulate="#3483ff"/>
</BoxContainer>
</BoxContainer>
<!-- Divider -->
<PanelContainer
StyleClasses="LowDivider"/>
<!-- Villain -->
<BoxContainer
Orientation="Vertical"
HorizontalExpand="True">
<!-- Name -->
<Label
Name="EnemyNameLabel"
Text="{Loc 'spacevillain-menu-label-enemy'}"
Align="Center"
HorizontalExpand="True"/>
<!-- Stats -->
<BoxContainer>
<!-- HP -->
<Label
Name="EnemyHpLabel"
Align="Center"
HorizontalExpand="True"
Modulate="#e94740"/>
<!-- MP -->
<Label
Name="EnemyMpLabel"
Align="Center"
HorizontalExpand="True"
Modulate="#3483ff"/>
</BoxContainer>
</BoxContainer>
</BoxContainer>
<!-- Spacer -->
<Control VerticalExpand="True"/>
<!-- Player and enemy action messages -->
<Label Name="PlayerActionLabel"
Align="Left"/>
<Label Name="EnemyActionLabel"
Align="Right"/>
<!-- Spacer -->
<Control VerticalExpand="True"/>
<!-- Action button grid -->
<BoxContainer
Orientation="Horizontal">
<Button
Name="AttackButton"
Text="{Loc spacevillain-menu-button-attack}"
StyleClasses="OpenRight"
HorizontalExpand="True"/>
<Button
Name="HealButton"
Text="{Loc spacevillain-menu-button-heal}"
StyleClasses="ButtonSquare"
HorizontalExpand="True"/>
<Button
Name="RechargeButton"
Text="{Loc spacevillain-menu-button-recharge}"
StyleClasses="OpenLeft"
HorizontalExpand="True"/>
</BoxContainer>
<!-- New game button -->
<Button
Name="NewGameButton"
Text="{Loc spacevillain-menu-button-new-game}"/>
</BoxContainer>
</DefaultWindow>

View file

@ -0,0 +1,45 @@
using Content.Shared.Arcade;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
namespace Content.Client.Arcade;
[GenerateTypedNameReferences]
public sealed partial class SpaceVillainArcadeMenu : DefaultWindow
{
public event Action<SharedSpaceVillainArcadeComponent.PlayerAction>? OnPlayerAction;
public SpaceVillainArcadeMenu()
{
RobustXamlLoader.Load(this);
AttackButton.OnPressed += _ => OnPlayerAction?.Invoke(SharedSpaceVillainArcadeComponent.PlayerAction.Attack);
HealButton.OnPressed += _ => OnPlayerAction?.Invoke(SharedSpaceVillainArcadeComponent.PlayerAction.Heal);
RechargeButton.OnPressed += _ => OnPlayerAction?.Invoke(SharedSpaceVillainArcadeComponent.PlayerAction.Recharge);
NewGameButton.OnPressed += _ => OnPlayerAction?.Invoke(SharedSpaceVillainArcadeComponent.PlayerAction.NewGame);
}
private void UpdateMetadata(SharedSpaceVillainArcadeComponent.SpaceVillainArcadeMetaDataUpdateMessage message)
{
Title = message.GameTitle;
EnemyNameLabel.Text = message.EnemyName;
AttackButton.Disabled = message.ButtonsDisabled;
HealButton.Disabled = message.ButtonsDisabled;
RechargeButton.Disabled = message.ButtonsDisabled;
NewGameButton.Disabled = message.ButtonsDisabled;
}
public void UpdateInfo(SharedSpaceVillainArcadeComponent.SpaceVillainArcadeDataUpdateMessage message)
{
if (message is SharedSpaceVillainArcadeComponent.SpaceVillainArcadeMetaDataUpdateMessage metaMessage)
UpdateMetadata(metaMessage);
PlayerHpLabel.Text = Loc.GetString("spacevillain-menu-label-health", ("amount", message.PlayerHP));
PlayerMpLabel.Text = Loc.GetString("spacevillain-menu-label-magic", ("amount", message.PlayerMP));
EnemyHpLabel.Text = Loc.GetString("spacevillain-menu-label-health", ("amount", message.EnemyHP));
EnemyMpLabel.Text = Loc.GetString("spacevillain-menu-label-magic", ("amount", message.EnemyMP));
PlayerActionLabel.Text = message.PlayerActionMessage;
EnemyActionLabel.Text = message.EnemyActionMessage;
}
}

View file

@ -1,14 +1,14 @@
## SpaceVillainGame
space-villain-game-player-attack-message = You attack {$enemyName} for {$attackAmount}!
space-villain-game-player-heal-message = You use {$magicPointAmount} magic to heal for {$healAmount} damage!
space-villain-game-player-recharge-message = You regain {$regainedPoints} points
space-villain-game-player-attack-message = You attack {$enemyName} for {$attackAmount} damage!
space-villain-game-player-heal-message = You use {$magicPointAmount} {-spacevillain-magic} to heal {$healAmount} {-spacevillain-health}!
space-villain-game-player-recharge-message = You regain {$regainedPoints} {-spacevillain-magic}!
space-villain-game-player-wins-message = You won!
space-villain-game-enemy-dies-message = {$enemyName} dies.
space-villain-game-enemy-dies-message = {CAPITALIZE($enemyName)} dies.
space-villain-game-player-loses-message = You lost!
space-villain-game-enemy-cheers-message = {$enemyName} cheers.
space-villain-game-enemy-dies-with-player-message = {$enemyName} dies, but takes you with him.
space-villain-game-enemy-throws-bomb-message = {$enemyName} throws a bomb, exploding you for {$damageReceived} damage!
space-villain-game-enemy-steals-player-power-message = {$enemyName} steals {$stolenAmount} of your power!
space-villain-game-enemy-heals-message = {$enemyName} heals for {$healedAmount} health!
space-villain-game-enemy-attacks-message = {$enemyName} attacks you for {$damageDealt} damage!
space-villain-game-enemy-cheers-message = {CAPITALIZE($enemyName)} cheers.
space-villain-game-enemy-dies-with-player-message = {CAPITALIZE($enemyName)} dies, but takes you with him.
space-villain-game-enemy-throws-bomb-message = {CAPITALIZE($enemyName)} throws a bomb, exploding you for {$damageReceived} damage!
space-villain-game-enemy-steals-player-power-message = {CAPITALIZE($enemyName)} steals {$stolenAmount} of your {-spacevillain-magic}!
space-villain-game-enemy-heals-message = {CAPITALIZE($enemyName)} heals for {$healedAmount} {-spacevillain-health}!
space-villain-game-enemy-attacks-message = {CAPITALIZE($enemyName)} attacks you for {$damageDealt} damage!

View file

@ -1,6 +1,12 @@
spacevillain-menu-title = Space Villain
spacevillain-menu-label-player = Player
spacevillain-menu-button-attack = ATTACK
spacevillain-menu-button-heal = HEAL
spacevillain-menu-button-recharge = RECHARGE
spacevillain-menu-label-enemy = Villain
spacevillain-menu-label-health = {$amount} {-spacevillain-health}
spacevillain-menu-label-magic = {$amount} {-spacevillain-magic}
spacevillain-menu-button-attack = Attack
spacevillain-menu-button-heal = Heal
spacevillain-menu-button-recharge = Recharge
spacevillain-menu-button-new-game = New Game
-spacevillain-health = HP
-spacevillain-magic = MP

View file

@ -37,4 +37,4 @@ arcade-villain-names-enemy-last-11 = Sorcerer
arcade-villain-names-enemy-last-12 = Uhangoid
arcade-villain-names-enemy-last-13 = Unicorn
arcade-villain-names-enemy-last-14 = Vhakoid
arcade-villain-names-enemy-last-15 = slime
arcade-villain-names-enemy-last-15 = Slime