diff --git a/.envrc b/.envrc
index 3550a30f2de389..5def8fd66a2752 100644
--- a/.envrc
+++ b/.envrc
@@ -1 +1,4 @@
+if ! has nix_direnv_version || ! nix_direnv_version 2.3.0; then
+ source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/2.3.0/direnvrc" "sha256-Dmd+j63L84wuzgyjITIfSxSD57Tx7v51DMxVZOsiUD8="
+fi
use flake
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index 627126c918b1c7..ff52456cf32b69 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -1,15 +1,19 @@
# Last match in file takes precedence.
+# Sorting by path instead of by who added it one day :(
+/Resources/ConfigPresets/WizardsDen/ @Chief-Engineer
+
# Moony's Gargantuan List Of Things She Cares About, or MGLOTSCA for short.
# You need to add your name to these entries, not make a new one, if you care about them.
/Content.*/Bql/ @moonheart08
-/Content.*/Administration/ @moonheart08 @PaulRitter @DrSmugleaf
+/Content.*/Administration/ @moonheart08 @PaulRitter @DrSmugleaf @Chief-Engineer
/Content.*/Station/ @moonheart08
/Content.*/Maps/ @moonheart08
/Content.*/GameTicking/ @moonheart08
-/Resources/Server\ Info/ @moonheart08
-/Resources/engineCommandPerms.yml @moonheart08
-/Resources/clientCommandPerms.yml @moonheart08
+/Resources/ServerInfo/ @moonheart08 @Chief-Engineer
+/Resources/ServerInfo/Guidebook/ @moonheart08
+/Resources/engineCommandPerms.yml @moonheart08 @Chief-Engineer
+/Resources/clientCommandPerms.yml @moonheart08 @Chief-Engineer
/Resources/Prototypes/species.yml @moonheart08
/Resources/Prototypes/Body/ @moonheart08 @mirrorcult @DrSmugleaf # suffering
/Resources/Prototypes/Maps/ @moonheart08 @Emisse
@@ -50,5 +54,6 @@
# SKREEEE
/Content.*.Database/ @PJB3005 @DrSmugleaf
+/Content.Shared.Database/Log*.cs @PJB3005 @DrSmugleaf @Chief-Engineer
/Pow3r/ @PJB3005
/Content.Server/Power/Pow3r/ @PJB3005
diff --git a/.run/Content Server+Client.run.xml b/.run/Content Server+Client.run.xml
index 4ba50d81900b04..0ec9bf16a750ae 100644
--- a/.run/Content Server+Client.run.xml
+++ b/.run/Content Server+Client.run.xml
@@ -4,4 +4,4 @@
-
\ No newline at end of file
+
diff --git a/Content.Client/Access/UI/AgentIDCardBoundUserInterface.cs b/Content.Client/Access/UI/AgentIDCardBoundUserInterface.cs
index b9880eb8b6e912..6b6ce1e5657f2f 100644
--- a/Content.Client/Access/UI/AgentIDCardBoundUserInterface.cs
+++ b/Content.Client/Access/UI/AgentIDCardBoundUserInterface.cs
@@ -25,8 +25,8 @@ protected override void Open()
_window.OpenCentered();
_window.OnClose += Close;
- _window.OnNameEntered += OnNameChanged;
- _window.OnJobEntered += OnJobChanged;
+ _window.OnNameChanged += OnNameChanged;
+ _window.OnJobChanged += OnJobChanged;
}
private void OnNameChanged(string newName)
diff --git a/Content.Client/Access/UI/AgentIDCardWindow.xaml.cs b/Content.Client/Access/UI/AgentIDCardWindow.xaml.cs
index 9813a2946ecfcc..1be816f0276cac 100644
--- a/Content.Client/Access/UI/AgentIDCardWindow.xaml.cs
+++ b/Content.Client/Access/UI/AgentIDCardWindow.xaml.cs
@@ -7,16 +7,18 @@ namespace Content.Client.Access.UI
[GenerateTypedNameReferences]
public sealed partial class AgentIDCardWindow : DefaultWindow
{
- public event Action? OnNameEntered;
-
- public event Action? OnJobEntered;
+ public event Action? OnNameChanged;
+ public event Action? OnJobChanged;
public AgentIDCardWindow()
{
RobustXamlLoader.Load(this);
- NameLineEdit.OnTextEntered += e => OnNameEntered?.Invoke(e.Text);
- JobLineEdit.OnTextEntered += e => OnJobEntered?.Invoke(e.Text);
+ NameLineEdit.OnTextEntered += e => OnNameChanged?.Invoke(e.Text);
+ NameLineEdit.OnFocusExit += e => OnNameChanged?.Invoke(e.Text);
+
+ JobLineEdit.OnTextEntered += e => OnJobChanged?.Invoke(e.Text);
+ JobLineEdit.OnFocusExit += e => OnJobChanged?.Invoke(e.Text);
}
public void SetCurrentName(string name)
diff --git a/Content.Client/Actions/ActionsSystem.cs b/Content.Client/Actions/ActionsSystem.cs
index 08cdb8c832f7d3..3eac41d8356f75 100644
--- a/Content.Client/Actions/ActionsSystem.cs
+++ b/Content.Client/Actions/ActionsSystem.cs
@@ -173,7 +173,7 @@ public void UnlinkAllActions()
public void LinkAllActions(ActionsComponent? actions = null)
{
var player = _playerManager.LocalPlayer?.ControlledEntity;
- if (player == null || !Resolve(player.Value, ref actions))
+ if (player == null || !Resolve(player.Value, ref actions, false))
{
return;
}
diff --git a/Content.Client/Arcade/BlockGameMenu.cs b/Content.Client/Arcade/BlockGameMenu.cs
index 5452d7c5d00392..17f150c756a379 100644
--- a/Content.Client/Arcade/BlockGameMenu.cs
+++ b/Content.Client/Arcade/BlockGameMenu.cs
@@ -1,3 +1,4 @@
+using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
@@ -20,16 +21,16 @@ namespace Content.Client.Arcade
{
public sealed class BlockGameMenu : DefaultWindow
{
- private static readonly Color OverlayBackgroundColor = new(74,74,81,180);
- private static readonly Color OverlayShadowColor = new(0,0,0,83);
+ private static readonly Color OverlayBackgroundColor = new(74, 74, 81, 180);
+ private static readonly Color OverlayShadowColor = new(0, 0, 0, 83);
- private static readonly Vector2 BlockSize = new(15,15);
+ private static readonly Vector2 BlockSize = new(15, 15);
private readonly BlockGameBoundUserInterface _owner;
private readonly PanelContainer _mainPanel;
- private BoxContainer _gameRootContainer;
+ private readonly BoxContainer _gameRootContainer;
private GridContainer _gameGrid = default!;
private GridContainer _nextBlockGrid = default!;
private GridContainer _holdBlockGrid = default!;
@@ -82,7 +83,7 @@ public BlockGameMenu(BlockGameBoundUserInterface owner)
_gameRootContainer.AddChild(_levelLabel);
_gameRootContainer.AddChild(new Control
{
- MinSize = new Vector2(1,5)
+ MinSize = new Vector2(1, 5)
});
_pointsLabel = new Label
@@ -93,7 +94,7 @@ public BlockGameMenu(BlockGameBoundUserInterface owner)
_gameRootContainer.AddChild(_pointsLabel);
_gameRootContainer.AddChild(new Control
{
- MinSize = new Vector2(1,10)
+ MinSize = new Vector2(1, 10)
});
var gameBox = new BoxContainer
@@ -103,12 +104,12 @@ public BlockGameMenu(BlockGameBoundUserInterface owner)
gameBox.AddChild(SetupHoldBox(backgroundTexture));
gameBox.AddChild(new Control
{
- MinSize = new Vector2(10,1)
+ MinSize = new Vector2(10, 1)
});
gameBox.AddChild(SetupGameGrid(backgroundTexture));
gameBox.AddChild(new Control
{
- MinSize = new Vector2(10,1)
+ MinSize = new Vector2(10, 1)
});
gameBox.AddChild(SetupNextBox(backgroundTexture));
@@ -116,7 +117,7 @@ public BlockGameMenu(BlockGameBoundUserInterface owner)
_gameRootContainer.AddChild(new Control
{
- MinSize = new Vector2(1,10)
+ MinSize = new Vector2(1, 10)
});
_pauseButton = new Button
@@ -176,7 +177,7 @@ public BlockGameMenu(BlockGameBoundUserInterface owner)
_owner.SendAction(BlockGamePlayerAction.NewGame);
};
pauseMenuContainer.AddChild(_newGameButton);
- pauseMenuContainer.AddChild(new Control{MinSize = new Vector2(1,10)});
+ pauseMenuContainer.AddChild(new Control { MinSize = new Vector2(1, 10) });
_scoreBoardButton = new Button
{
@@ -185,7 +186,7 @@ public BlockGameMenu(BlockGameBoundUserInterface owner)
};
_scoreBoardButton.OnPressed += (e) => _owner.SendAction(BlockGamePlayerAction.ShowHighscores);
pauseMenuContainer.AddChild(_scoreBoardButton);
- _unpauseButtonMargin = new Control {MinSize = new Vector2(1, 10), Visible = false};
+ _unpauseButtonMargin = new Control { MinSize = new Vector2(1, 10), Visible = false };
pauseMenuContainer.AddChild(_unpauseButtonMargin);
_unpauseButton = new Button
@@ -239,13 +240,13 @@ public BlockGameMenu(BlockGameBoundUserInterface owner)
VerticalAlignment = VAlignment.Center
};
- gameOverMenuContainer.AddChild(new Label{Text = Loc.GetString("blockgame-menu-msg-game-over"),Align = Label.AlignMode.Center});
- gameOverMenuContainer.AddChild(new Control{MinSize = new Vector2(1,10)});
+ gameOverMenuContainer.AddChild(new Label { Text = Loc.GetString("blockgame-menu-msg-game-over"), Align = Label.AlignMode.Center });
+ gameOverMenuContainer.AddChild(new Control { MinSize = new Vector2(1, 10) });
- _finalScoreLabel = new Label{Align = Label.AlignMode.Center};
+ _finalScoreLabel = new Label { Align = Label.AlignMode.Center };
gameOverMenuContainer.AddChild(_finalScoreLabel);
- gameOverMenuContainer.AddChild(new Control{MinSize = new Vector2(1,10)});
+ gameOverMenuContainer.AddChild(new Control { MinSize = new Vector2(1, 10) });
_finalNewGameButton = new Button
{
@@ -275,7 +276,7 @@ public BlockGameMenu(BlockGameBoundUserInterface owner)
HorizontalAlignment = HAlignment.Center
};
- var c = new Color(OverlayBackgroundColor.R,OverlayBackgroundColor.G,OverlayBackgroundColor.B,220);
+ var c = new Color(OverlayBackgroundColor.R, OverlayBackgroundColor.G, OverlayBackgroundColor.B, 220);
var innerBack = new StyleBoxTexture
{
Texture = backgroundTexture,
@@ -298,8 +299,8 @@ public BlockGameMenu(BlockGameBoundUserInterface owner)
VerticalAlignment = VAlignment.Center
};
- menuContainer.AddChild(new Label{Text = Loc.GetString("blockgame-menu-label-highscores")});
- menuContainer.AddChild(new Control{MinSize = new Vector2(1,10)});
+ menuContainer.AddChild(new Label { Text = Loc.GetString("blockgame-menu-label-highscores") });
+ menuContainer.AddChild(new Control { MinSize = new Vector2(1, 10) });
var highScoreBox = new BoxContainer
{
@@ -311,14 +312,14 @@ public BlockGameMenu(BlockGameBoundUserInterface owner)
Align = Label.AlignMode.Center
};
highScoreBox.AddChild(_localHighscoresLabel);
- highScoreBox.AddChild(new Control{MinSize = new Vector2(40,1)});
+ highScoreBox.AddChild(new Control { MinSize = new Vector2(40, 1) });
_globalHighscoresLabel = new Label
{
Align = Label.AlignMode.Center
};
highScoreBox.AddChild(_globalHighscoresLabel);
menuContainer.AddChild(highScoreBox);
- menuContainer.AddChild(new Control{MinSize = new Vector2(1,10)});
+ menuContainer.AddChild(new Control { MinSize = new Vector2(1, 10) });
_highscoreBackButton = new Button
{
Text = Loc.GetString("blockgame-menu-button-back"),
@@ -359,7 +360,7 @@ private Control SetupGameGrid(Texture panelTex)
HSeparationOverride = 1,
VSeparationOverride = 1
};
- UpdateBlocks(new BlockGameBlock[0]);
+ UpdateBlocks(Array.Empty());
var back = new StyleBoxTexture
{
@@ -376,7 +377,7 @@ private Control SetupGameGrid(Texture panelTex)
};
var backgroundPanel = new PanelContainer
{
- PanelOverride = new StyleBoxFlat{BackgroundColor = Color.FromHex("#86868d")}
+ PanelOverride = new StyleBoxFlat { BackgroundColor = Color.FromHex("#86868d") }
};
backgroundPanel.AddChild(_gameGrid);
gamePanel.AddChild(backgroundPanel);
@@ -416,7 +417,7 @@ private Control SetupNextBox(Texture panelTex)
nextBlockPanel.AddChild(nextCenterContainer);
grid.AddChild(nextBlockPanel);
- grid.AddChild(new Label{Text = Loc.GetString("blockgame-menu-label-next"), Align = Label.AlignMode.Center});
+ grid.AddChild(new Label { Text = Loc.GetString("blockgame-menu-label-next"), Align = Label.AlignMode.Center });
return grid;
}
@@ -454,15 +455,17 @@ private Control SetupHoldBox(Texture panelTex)
holdBlockPanel.AddChild(holdCenterContainer);
grid.AddChild(holdBlockPanel);
- grid.AddChild(new Label{Text = Loc.GetString("blockgame-menu-label-hold"), Align = Label.AlignMode.Center});
+ grid.AddChild(new Label { Text = Loc.GetString("blockgame-menu-label-hold"), Align = Label.AlignMode.Center });
return grid;
}
protected override void KeyboardFocusExited()
{
- if (!IsOpen) return;
- if(_gameOver) return;
+ if (!IsOpen)
+ return;
+ if (_gameOver)
+ return;
TryPause();
}
@@ -480,7 +483,8 @@ public void SetStarted()
public void SetScreen(BlockGameMessages.BlockGameScreen screen)
{
- if (_gameOver) return;
+ if (_gameOver)
+ return;
switch (screen)
{
@@ -512,9 +516,12 @@ public void SetScreen(BlockGameMessages.BlockGameScreen screen)
private void CloseMenus()
{
- if(_mainPanel.Children.Contains(_menuRootContainer)) _mainPanel.RemoveChild(_menuRootContainer);
- if(_mainPanel.Children.Contains(_gameOverRootContainer)) _mainPanel.RemoveChild(_gameOverRootContainer);
- if(_mainPanel.Children.Contains(_highscoresRootContainer)) _mainPanel.RemoveChild(_highscoresRootContainer);
+ if (_mainPanel.Children.Contains(_menuRootContainer))
+ _mainPanel.RemoveChild(_menuRootContainer);
+ if (_mainPanel.Children.Contains(_gameOverRootContainer))
+ _mainPanel.RemoveChild(_gameOverRootContainer);
+ if (_mainPanel.Children.Contains(_highscoresRootContainer))
+ _mainPanel.RemoveChild(_highscoresRootContainer);
}
public void SetGameoverInfo(int amount, int? localPlacement, int? globalPlacement)
@@ -563,72 +570,56 @@ protected override void KeyBindDown(GUIBoundKeyEventArgs args)
{
base.KeyBindDown(args);
- if(!_isPlayer || args.Handled) return;
+ if (!_isPlayer || args.Handled)
+ return;
- if (args.Function == ContentKeyFunctions.ArcadeLeft)
- {
+ else if (args.Function == ContentKeyFunctions.ArcadeLeft)
_owner.SendAction(BlockGamePlayerAction.StartLeft);
- }
else if (args.Function == ContentKeyFunctions.ArcadeRight)
- {
_owner.SendAction(BlockGamePlayerAction.StartRight);
- }
else if (args.Function == ContentKeyFunctions.ArcadeUp)
- {
_owner.SendAction(BlockGamePlayerAction.Rotate);
- }
else if (args.Function == ContentKeyFunctions.Arcade3)
- {
_owner.SendAction(BlockGamePlayerAction.CounterRotate);
- }
else if (args.Function == ContentKeyFunctions.ArcadeDown)
- {
_owner.SendAction(BlockGamePlayerAction.SoftdropStart);
- }
else if (args.Function == ContentKeyFunctions.Arcade2)
- {
_owner.SendAction(BlockGamePlayerAction.Hold);
- }
else if (args.Function == ContentKeyFunctions.Arcade1)
- {
_owner.SendAction(BlockGamePlayerAction.Harddrop);
- }
}
protected override void KeyBindUp(GUIBoundKeyEventArgs args)
{
base.KeyBindUp(args);
- if(!_isPlayer || args.Handled) return;
+ if (!_isPlayer || args.Handled)
+ return;
- if (args.Function == ContentKeyFunctions.ArcadeLeft)
- {
+ else if (args.Function == ContentKeyFunctions.ArcadeLeft)
_owner.SendAction(BlockGamePlayerAction.EndLeft);
- }
else if (args.Function == ContentKeyFunctions.ArcadeRight)
- {
_owner.SendAction(BlockGamePlayerAction.EndRight);
- }else if (args.Function == ContentKeyFunctions.ArcadeDown)
- {
+ else if (args.Function == ContentKeyFunctions.ArcadeDown)
_owner.SendAction(BlockGamePlayerAction.SoftdropEnd);
- }
}
public void UpdateNextBlock(BlockGameBlock[] blocks)
{
_nextBlockGrid.RemoveAllChildren();
- if (blocks.Length == 0) return;
+ if (blocks.Length == 0)
+ return;
var columnCount = blocks.Max(b => b.Position.X) + 1;
var rowCount = blocks.Max(b => b.Position.Y) + 1;
_nextBlockGrid.Columns = columnCount;
- for (int y = 0; y < rowCount; y++)
+ for (var y = 0; y < rowCount; y++)
{
- for (int x = 0; x < columnCount; x++)
+ for (var x = 0; x < columnCount; x++)
{
var c = GetColorForPosition(blocks, x, y);
_nextBlockGrid.AddChild(new PanelContainer
{
- PanelOverride = new StyleBoxFlat {BackgroundColor = c},
+ PanelOverride = new StyleBoxFlat { BackgroundColor = c },
MinSize = BlockSize,
RectDrawClipMargin = 0
});
@@ -639,18 +630,19 @@ public void UpdateNextBlock(BlockGameBlock[] blocks)
public void UpdateHeldBlock(BlockGameBlock[] blocks)
{
_holdBlockGrid.RemoveAllChildren();
- if (blocks.Length == 0) return;
+ if (blocks.Length == 0)
+ return;
var columnCount = blocks.Max(b => b.Position.X) + 1;
var rowCount = blocks.Max(b => b.Position.Y) + 1;
_holdBlockGrid.Columns = columnCount;
- for (int y = 0; y < rowCount; y++)
+ for (var y = 0; y < rowCount; y++)
{
- for (int x = 0; x < columnCount; x++)
+ for (var x = 0; x < columnCount; x++)
{
var c = GetColorForPosition(blocks, x, y);
_holdBlockGrid.AddChild(new PanelContainer
{
- PanelOverride = new StyleBoxFlat {BackgroundColor = c},
+ PanelOverride = new StyleBoxFlat { BackgroundColor = c },
MinSize = BlockSize,
RectDrawClipMargin = 0
});
@@ -661,14 +653,14 @@ public void UpdateHeldBlock(BlockGameBlock[] blocks)
public void UpdateBlocks(BlockGameBlock[] blocks)
{
_gameGrid.RemoveAllChildren();
- for (int y = 0; y < 20; y++)
+ for (var y = 0; y < 20; y++)
{
- for (int x = 0; x < 10; x++)
+ for (var x = 0; x < 10; x++)
{
var c = GetColorForPosition(blocks, x, y);
_gameGrid.AddChild(new PanelContainer
{
- PanelOverride = new StyleBoxFlat {BackgroundColor = c},
+ PanelOverride = new StyleBoxFlat { BackgroundColor = c },
MinSize = BlockSize,
RectDrawClipMargin = 0
});
@@ -676,9 +668,9 @@ public void UpdateBlocks(BlockGameBlock[] blocks)
}
}
- private Color GetColorForPosition(BlockGameBlock[] blocks, int x, int y)
+ private static Color GetColorForPosition(BlockGameBlock[] blocks, int x, int y)
{
- Color c = Color.Transparent;
+ var c = Color.Transparent;
var matchingBlock = blocks.FirstOrNull(b => b.Position.X == x && b.Position.Y == y);
if (matchingBlock.HasValue)
{
diff --git a/Content.Client/Arcade/SpaceVillainArcadeMenu.cs b/Content.Client/Arcade/SpaceVillainArcadeMenu.cs
index bbd763545905f7..4f07593b0e1caa 100644
--- a/Content.Client/Arcade/SpaceVillainArcadeMenu.cs
+++ b/Content.Client/Arcade/SpaceVillainArcadeMenu.cs
@@ -24,40 +24,46 @@ public SpaceVillainArcadeMenu(SpaceVillainArcadeBoundUserInterface owner)
Title = Loc.GetString("spacevillain-menu-title");
Owner = owner;
- var grid = new GridContainer {Columns = 1};
+ 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};
+ 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};
+ _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(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};
+ _playerActionLabel = new Label { Align = Label.AlignMode.Center };
grid.AddChild(_playerActionLabel);
- _enemyActionLabel = new Label {Align = Label.AlignMode.Center};
+ _enemyActionLabel = new Label { Align = Label.AlignMode.Center };
grid.AddChild(_enemyActionLabel);
- var buttonGrid = new GridContainer {Columns = 3};
+ var buttonGrid = new GridContainer { Columns = 3 };
_gameButtons[0] = new ActionButton(Owner, SharedSpaceVillainArcadeComponent.PlayerAction.Attack)
- {Text = Loc.GetString("spacevillain-menu-button-attack")};
+ {
+ Text = Loc.GetString("spacevillain-menu-button-attack")
+ };
buttonGrid.AddChild(_gameButtons[0]);
_gameButtons[1] = new ActionButton(Owner, SharedSpaceVillainArcadeComponent.PlayerAction.Heal)
- {Text = Loc.GetString("spacevillain-menu-button-heal")};
+ {
+ Text = Loc.GetString("spacevillain-menu-button-heal")
+ };
buttonGrid.AddChild(_gameButtons[1]);
_gameButtons[2] = new ActionButton(Owner, SharedSpaceVillainArcadeComponent.PlayerAction.Recharge)
- {Text = Loc.GetString("spacevillain-menu-button-recharge")};
+ {
+ Text = Loc.GetString("spacevillain-menu-button-recharge")
+ };
buttonGrid.AddChild(_gameButtons[2]);
centerContainer = new CenterContainer();
@@ -65,7 +71,9 @@ public SpaceVillainArcadeMenu(SpaceVillainArcadeBoundUserInterface owner)
grid.AddChild(centerContainer);
var newGame = new ActionButton(Owner, SharedSpaceVillainArcadeComponent.PlayerAction.NewGame)
- {Text = Loc.GetString("spacevillain-menu-button-new-game")};
+ {
+ Text = Loc.GetString("spacevillain-menu-button-new-game")
+ };
grid.AddChild(newGame);
Contents.AddChild(grid);
@@ -84,7 +92,8 @@ private void UpdateMetadata(SharedSpaceVillainArcadeComponent.SpaceVillainArcade
public void UpdateInfo(SharedSpaceVillainArcadeComponent.SpaceVillainArcadeDataUpdateMessage message)
{
- if(message is SharedSpaceVillainArcadeComponent.SpaceVillainArcadeMetaDataUpdateMessage metaMessage) UpdateMetadata(metaMessage);
+ if (message is SharedSpaceVillainArcadeComponent.SpaceVillainArcadeMetaDataUpdateMessage metaMessage)
+ UpdateMetadata(metaMessage);
_playerInfoLabel.Text = $"HP: {message.PlayerHP} MP: {message.PlayerMP}";
_enemyInfoLabel.Text = $"HP: {message.EnemyHP} MP: {message.EnemyMP}";
@@ -97,7 +106,7 @@ private sealed class ActionButton : Button
private readonly SpaceVillainArcadeBoundUserInterface _owner;
private readonly SharedSpaceVillainArcadeComponent.PlayerAction _playerAction;
- public ActionButton(SpaceVillainArcadeBoundUserInterface owner,SharedSpaceVillainArcadeComponent.PlayerAction playerAction)
+ public ActionButton(SpaceVillainArcadeBoundUserInterface owner, SharedSpaceVillainArcadeComponent.PlayerAction playerAction)
{
_owner = owner;
_playerAction = playerAction;
diff --git a/Content.Client/Arcade/UI/BlockGameBoundUserInterface.cs b/Content.Client/Arcade/UI/BlockGameBoundUserInterface.cs
index cd74077c12fc5a..2abe9311fb5f87 100644
--- a/Content.Client/Arcade/UI/BlockGameBoundUserInterface.cs
+++ b/Content.Client/Arcade/UI/BlockGameBoundUserInterface.cs
@@ -1,78 +1,76 @@
using Content.Shared.Arcade;
using Robust.Client.GameObjects;
-using Robust.Shared.GameObjects;
-namespace Content.Client.Arcade.UI
+namespace Content.Client.Arcade.UI;
+
+public sealed class BlockGameBoundUserInterface : BoundUserInterface
{
- public sealed class BlockGameBoundUserInterface : BoundUserInterface
- {
- private BlockGameMenu? _menu;
+ private BlockGameMenu? _menu;
- public BlockGameBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey)
- {
- }
+ public BlockGameBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey)
+ {
+ }
- protected override void Open()
- {
- base.Open();
+ protected override void Open()
+ {
+ base.Open();
- _menu = new BlockGameMenu(this);
- _menu.OnClose += Close;
- _menu.OpenCentered();
- }
+ _menu = new BlockGameMenu(this);
+ _menu.OnClose += Close;
+ _menu.OpenCentered();
+ }
- protected override void ReceiveMessage(BoundUserInterfaceMessage message)
+ protected override void ReceiveMessage(BoundUserInterfaceMessage message)
+ {
+ switch (message)
{
- switch (message)
- {
- case BlockGameMessages.BlockGameVisualUpdateMessage updateMessage:
- switch (updateMessage.GameVisualType)
- {
- case BlockGameMessages.BlockGameVisualType.GameField:
- _menu?.UpdateBlocks(updateMessage.Blocks);
- break;
- case BlockGameMessages.BlockGameVisualType.HoldBlock:
- _menu?.UpdateHeldBlock(updateMessage.Blocks);
- break;
- case BlockGameMessages.BlockGameVisualType.NextBlock:
- _menu?.UpdateNextBlock(updateMessage.Blocks);
- break;
- }
- break;
- case BlockGameMessages.BlockGameScoreUpdateMessage scoreUpdate:
- _menu?.UpdatePoints(scoreUpdate.Points);
- break;
- case BlockGameMessages.BlockGameUserStatusMessage userMessage:
- _menu?.SetUsability(userMessage.IsPlayer);
- break;
- case BlockGameMessages.BlockGameSetScreenMessage statusMessage:
- if (statusMessage.IsStarted) _menu?.SetStarted();
- _menu?.SetScreen(statusMessage.Screen);
- if (statusMessage is BlockGameMessages.BlockGameGameOverScreenMessage gameOverScreenMessage)
- _menu?.SetGameoverInfo(gameOverScreenMessage.FinalScore, gameOverScreenMessage.LocalPlacement, gameOverScreenMessage.GlobalPlacement);
- break;
- case BlockGameMessages.BlockGameHighScoreUpdateMessage highScoreUpdateMessage:
- _menu?.UpdateHighscores(highScoreUpdateMessage.LocalHighscores,
- highScoreUpdateMessage.GlobalHighscores);
- break;
- case BlockGameMessages.BlockGameLevelUpdateMessage levelUpdateMessage:
- _menu?.UpdateLevel(levelUpdateMessage.Level);
- break;
- }
+ case BlockGameMessages.BlockGameVisualUpdateMessage updateMessage:
+ switch (updateMessage.GameVisualType)
+ {
+ case BlockGameMessages.BlockGameVisualType.GameField:
+ _menu?.UpdateBlocks(updateMessage.Blocks);
+ break;
+ case BlockGameMessages.BlockGameVisualType.HoldBlock:
+ _menu?.UpdateHeldBlock(updateMessage.Blocks);
+ break;
+ case BlockGameMessages.BlockGameVisualType.NextBlock:
+ _menu?.UpdateNextBlock(updateMessage.Blocks);
+ break;
+ }
+ break;
+ case BlockGameMessages.BlockGameScoreUpdateMessage scoreUpdate:
+ _menu?.UpdatePoints(scoreUpdate.Points);
+ break;
+ case BlockGameMessages.BlockGameUserStatusMessage userMessage:
+ _menu?.SetUsability(userMessage.IsPlayer);
+ break;
+ case BlockGameMessages.BlockGameSetScreenMessage statusMessage:
+ if (statusMessage.IsStarted) _menu?.SetStarted();
+ _menu?.SetScreen(statusMessage.Screen);
+ if (statusMessage is BlockGameMessages.BlockGameGameOverScreenMessage gameOverScreenMessage)
+ _menu?.SetGameoverInfo(gameOverScreenMessage.FinalScore, gameOverScreenMessage.LocalPlacement, gameOverScreenMessage.GlobalPlacement);
+ break;
+ case BlockGameMessages.BlockGameHighScoreUpdateMessage highScoreUpdateMessage:
+ _menu?.UpdateHighscores(highScoreUpdateMessage.LocalHighscores,
+ highScoreUpdateMessage.GlobalHighscores);
+ break;
+ case BlockGameMessages.BlockGameLevelUpdateMessage levelUpdateMessage:
+ _menu?.UpdateLevel(levelUpdateMessage.Level);
+ break;
}
+ }
- public void SendAction(BlockGamePlayerAction action)
- {
- SendMessage(new BlockGameMessages.BlockGamePlayerActionMessage(action));
- }
+ public void SendAction(BlockGamePlayerAction action)
+ {
+ SendMessage(new BlockGameMessages.BlockGamePlayerActionMessage(action));
+ }
- protected override void Dispose(bool disposing)
- {
- base.Dispose(disposing);
- if (!disposing)
- return;
+ protected override void Dispose(bool disposing)
+ {
+ base.Dispose(disposing);
+ if (!disposing)
+ return;
- _menu?.Dispose();
- }
+ _menu?.Dispose();
}
}
diff --git a/Content.Client/Arcade/UI/SpaceVillainArcadeBoundUserInterface.cs b/Content.Client/Arcade/UI/SpaceVillainArcadeBoundUserInterface.cs
index bb1582d12793af..2ecc8b46a85a15 100644
--- a/Content.Client/Arcade/UI/SpaceVillainArcadeBoundUserInterface.cs
+++ b/Content.Client/Arcade/UI/SpaceVillainArcadeBoundUserInterface.cs
@@ -3,51 +3,45 @@
using Robust.Shared.ViewVariables;
using static Content.Shared.Arcade.SharedSpaceVillainArcadeComponent;
-namespace Content.Client.Arcade.UI
-{
- public sealed class SpaceVillainArcadeBoundUserInterface : BoundUserInterface
- {
- [ViewVariables] private SpaceVillainArcadeMenu? _menu;
+namespace Content.Client.Arcade.UI;
- //public SharedSpaceVillainArcadeComponent SpaceVillainArcade;
-
- public SpaceVillainArcadeBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey)
- {
- SendAction(PlayerAction.RequestData);
- }
+public sealed class SpaceVillainArcadeBoundUserInterface : BoundUserInterface
+{
+ [ViewVariables] private SpaceVillainArcadeMenu? _menu;
- public void SendAction(PlayerAction action)
- {
- SendMessage(new SpaceVillainArcadePlayerActionMessage(action));
- }
+ //public SharedSpaceVillainArcadeComponent SpaceVillainArcade;
- protected override void Open()
- {
- base.Open();
+ public SpaceVillainArcadeBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey)
+ {
+ SendAction(PlayerAction.RequestData);
+ }
- /*if(!Owner.Owner.TryGetComponent(out SharedSpaceVillainArcadeComponent spaceVillainArcade))
- {
- return;
- }
+ public void SendAction(PlayerAction action)
+ {
+ SendMessage(new SpaceVillainArcadePlayerActionMessage(action));
+ }
- SpaceVillainArcade = spaceVillainArcade;*/
+ protected override void Open()
+ {
+ base.Open();
- _menu = new SpaceVillainArcadeMenu(this);
+ _menu = new SpaceVillainArcadeMenu(this);
- _menu.OnClose += Close;
- _menu.OpenCentered();
- }
+ _menu.OnClose += Close;
+ _menu.OpenCentered();
+ }
- protected override void ReceiveMessage(BoundUserInterfaceMessage message)
- {
- if (message is SpaceVillainArcadeDataUpdateMessage msg) _menu?.UpdateInfo(msg);
- }
+ protected override void ReceiveMessage(BoundUserInterfaceMessage message)
+ {
+ if (message is SpaceVillainArcadeDataUpdateMessage msg)
+ _menu?.UpdateInfo(msg);
+ }
- protected override void Dispose(bool disposing)
- {
- base.Dispose(disposing);
+ protected override void Dispose(bool disposing)
+ {
+ base.Dispose(disposing);
- if (disposing) _menu?.Dispose();
- }
+ if (disposing)
+ _menu?.Dispose();
}
}
diff --git a/Content.Client/Audio/AmbientSoundSystem.cs b/Content.Client/Audio/AmbientSoundSystem.cs
index e357e63c25469e..d241df4f6fc341 100644
--- a/Content.Client/Audio/AmbientSoundSystem.cs
+++ b/Content.Client/Audio/AmbientSoundSystem.cs
@@ -11,6 +11,7 @@
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using System.Linq;
+using Robust.Client.GameObjects;
namespace Content.Client.Audio
{
@@ -106,7 +107,19 @@ private void OnShutdown(EntityUid uid, AmbientSoundComponent component, Componen
_playingCount.Remove(sound.Sound);
}
- private void SetAmbienceVolume(float value) => _ambienceVolume = value;
+ private void SetAmbienceVolume(float value)
+ {
+ _ambienceVolume = value;
+
+ foreach (var (comp, values) in _playingSounds)
+ {
+ if (values.Stream == null)
+ continue;
+
+ var stream = (AudioSystem.PlayingStream) values.Stream;
+ stream.Volume = _params.Volume + comp.Volume + _ambienceVolume;
+ }
+ }
private void SetCooldown(float value) => _cooldown = value;
private void SetAmbientCount(int value) => _maxAmbientCount = value;
private void SetAmbientRange(float value) => _maxAmbientRange = value;
diff --git a/Content.Client/Audio/BackgroundAudioSystem.cs b/Content.Client/Audio/BackgroundAudioSystem.cs
index 50639a6d05eae3..100ee7458e61a3 100644
--- a/Content.Client/Audio/BackgroundAudioSystem.cs
+++ b/Content.Client/Audio/BackgroundAudioSystem.cs
@@ -1,21 +1,12 @@
-using System.Threading;
-using Content.Client.Gameplay;
using Content.Client.GameTicking.Managers;
using Content.Client.Lobby;
using Content.Shared.CCVar;
using JetBrains.Annotations;
using Robust.Client;
-using Robust.Client.GameObjects;
-using Robust.Client.Player;
-using Robust.Client.ResourceManagement;
using Robust.Client.State;
using Robust.Shared.Audio;
using Robust.Shared.Configuration;
using Robust.Shared.Player;
-using Robust.Shared.Prototypes;
-using Robust.Shared.Random;
-using Robust.Shared.Timing;
-using Timer = Robust.Shared.Timing.Timer;
namespace Content.Client.Audio;
@@ -26,57 +17,18 @@ public sealed class BackgroundAudioSystem : EntitySystem
[Dependency] private readonly IBaseClient _client = default!;
[Dependency] private readonly IConfigurationManager _configManager = default!;
[Dependency] private readonly ClientGameTicker _gameTicker = default!;
- [Dependency] private readonly IPlayerManager _playMan = default!;
- [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
- [Dependency] private readonly IRobustRandom _robustRandom = default!;
[Dependency] private readonly IStateManager _stateManager = default!;
- [Dependency] private readonly IGameTiming _timing = default!;
- private readonly AudioParams _ambientParams = new(-10f, 1, "Master", 0, 0, 0, true, 0f);
private readonly AudioParams _lobbyParams = new(-5f, 1, "Master", 0, 0, 0, true, 0f);
- private IPlayingAudioStream? _ambientStream;
private IPlayingAudioStream? _lobbyStream;
- ///
- /// What is currently playing.
- ///
- private SoundCollectionPrototype? _playingCollection;
-
- ///
- /// What the ambience has been set to.
- ///
- private SoundCollectionPrototype? _currentCollection;
- private CancellationTokenSource _timerCancelTokenSource = new();
-
- private SoundCollectionPrototype _spaceAmbience = default!;
- private SoundCollectionPrototype _stationAmbience = default!;
-
public override void Initialize()
{
base.Initialize();
- _stationAmbience = _prototypeManager.Index("StationAmbienceBase");
- _spaceAmbience = _prototypeManager.Index("SpaceAmbienceBase");
- _currentCollection = _stationAmbience;
-
- // TODO: Ideally audio loading streamed better / we have more robust audio but this is quite annoying
- var cache = IoCManager.Resolve();
-
- foreach (var audio in _spaceAmbience.PickFiles)
- {
- cache.GetResource(audio.ToString());
- }
-
- _configManager.OnValueChanged(CCVars.AmbienceVolume, AmbienceCVarChanged);
_configManager.OnValueChanged(CCVars.LobbyMusicEnabled, LobbyMusicCVarChanged);
_configManager.OnValueChanged(CCVars.LobbyMusicVolume, LobbyMusicVolumeCVarChanged);
- _configManager.OnValueChanged(CCVars.StationAmbienceEnabled, StationAmbienceCVarChanged);
- _configManager.OnValueChanged(CCVars.SpaceAmbienceEnabled, SpaceAmbienceCVarChanged);
-
- SubscribeLocalEvent(OnPlayerAttached);
- SubscribeLocalEvent(EntParentChanged);
- SubscribeLocalEvent(OnPlayerDetached);
_stateManager.OnStateChanged += StateManagerOnStateChanged;
@@ -85,28 +37,12 @@ public override void Initialize()
_gameTicker.LobbyStatusUpdated += LobbySongReceived;
}
- private void OnPlayerAttached(PlayerAttachedEvent ev)
- {
- if (!TryComp(ev.Entity, out var xform))
- return;
-
- CheckAmbience(xform);
- }
-
- private void OnPlayerDetached(PlayerDetachedEvent ev)
- {
- EndAmbience();
- }
-
public override void Shutdown()
{
base.Shutdown();
- _configManager.UnsubValueChanged(CCVars.AmbienceVolume, AmbienceCVarChanged);
_configManager.UnsubValueChanged(CCVars.LobbyMusicEnabled, LobbyMusicCVarChanged);
_configManager.UnsubValueChanged(CCVars.LobbyMusicVolume, LobbyMusicVolumeCVarChanged);
- _configManager.UnsubValueChanged(CCVars.StationAmbienceEnabled, StationAmbienceCVarChanged);
- _configManager.UnsubValueChanged(CCVars.SpaceAmbienceEnabled, SpaceAmbienceCVarChanged);
_stateManager.OnStateChanged -= StateManagerOnStateChanged;
@@ -114,61 +50,17 @@ public override void Shutdown()
_gameTicker.LobbyStatusUpdated -= LobbySongReceived;
- EndAmbience();
EndLobbyMusic();
}
- private void CheckAmbience(TransformComponent xform)
- {
- if (xform.GridUid != null)
- ChangeAmbience(_stationAmbience);
- else
- ChangeAmbience(_spaceAmbience);
- }
-
- private void EntParentChanged(ref EntParentChangedMessage message)
- {
- if (_playMan.LocalPlayer is null
- || _playMan.LocalPlayer.ControlledEntity != message.Entity
- || !(_timing.IsFirstTimePredicted || _timing.ApplyingState))
- return;
-
- // Check if we traversed to grid.
- CheckAmbience(message.Transform);
- }
-
- private void ChangeAmbience(SoundCollectionPrototype newAmbience)
- {
- if (_currentCollection == newAmbience)
- return;
-
- _timerCancelTokenSource.Cancel();
- _currentCollection = newAmbience;
- _timerCancelTokenSource = new CancellationTokenSource();
- Timer.Spawn(1500, () =>
- {
- // If we traverse a few times then don't interrupt an existing song.
- // If we are not in gameplay, don't call StartAmbience because of player movement
- if (_playingCollection == _currentCollection || _stateManager.CurrentState is not GameplayState)
- return;
- StartAmbience();
- }, _timerCancelTokenSource.Token);
- }
-
private void StateManagerOnStateChanged(StateChangedEventArgs args)
{
switch (args.NewState)
{
case LobbyState:
- EndAmbience();
StartLobbyMusic();
break;
- case GameplayState:
- EndLobbyMusic();
- StartAmbience();
- break;
default:
- EndAmbience();
EndLobbyMusic();
break;
}
@@ -176,80 +68,9 @@ private void StateManagerOnStateChanged(StateChangedEventArgs args)
private void OnLeave(object? sender, PlayerEventArgs args)
{
- EndAmbience();
EndLobbyMusic();
}
- private void AmbienceCVarChanged(float volume)
- {
- if (_stateManager.CurrentState is GameplayState)
- {
- StartAmbience();
- }
- else
- {
- EndAmbience();
- }
- }
-
- private void StartAmbience()
- {
- EndAmbience();
- if (_currentCollection == null || !CanPlayCollection(_currentCollection))
- return;
- _playingCollection = _currentCollection;
- var file = _robustRandom.Pick(_currentCollection.PickFiles).ToString();
- _ambientStream = _audio.PlayGlobal(file, Filter.Local(), false,
- _ambientParams.WithVolume(_ambientParams.Volume + _configManager.GetCVar(CCVars.AmbienceVolume)));
- }
-
- private void EndAmbience()
- {
- _playingCollection = null;
- _ambientStream?.Stop();
- _ambientStream = null;
- }
-
- private bool CanPlayCollection(SoundCollectionPrototype collection)
- {
- if (collection.ID == _spaceAmbience.ID)
- return _configManager.GetCVar(CCVars.SpaceAmbienceEnabled);
- if (collection.ID == _stationAmbience.ID)
- return _configManager.GetCVar(CCVars.StationAmbienceEnabled);
-
- return true;
- }
-
- private void StationAmbienceCVarChanged(bool enabled)
- {
- if (_currentCollection == null)
- return;
-
- if (enabled && _stateManager.CurrentState is GameplayState && _currentCollection.ID == _stationAmbience.ID)
- {
- StartAmbience();
- }
- else if (_currentCollection.ID == _stationAmbience.ID)
- {
- EndAmbience();
- }
- }
-
- private void SpaceAmbienceCVarChanged(bool enabled)
- {
- if (_currentCollection == null)
- return;
-
- if (enabled && _stateManager.CurrentState is GameplayState && _currentCollection.ID == _spaceAmbience.ID)
- {
- StartAmbience();
- }
- else if (_currentCollection.ID == _spaceAmbience.ID)
- {
- EndAmbience();
- }
- }
-
private void LobbyMusicVolumeCVarChanged(float volume)
{
if (_stateManager.CurrentState is LobbyState)
diff --git a/Content.Client/Audio/ContentAudioSystem.AmbientMusic.cs b/Content.Client/Audio/ContentAudioSystem.AmbientMusic.cs
new file mode 100644
index 00000000000000..34bc83bdfb467e
--- /dev/null
+++ b/Content.Client/Audio/ContentAudioSystem.AmbientMusic.cs
@@ -0,0 +1,262 @@
+using System.Linq;
+using Content.Client.Gameplay;
+using Content.Shared.Audio;
+using Content.Shared.CCVar;
+using Content.Shared.GameTicking;
+using Content.Shared.Random;
+using Robust.Client.GameObjects;
+using Robust.Client.Player;
+using Robust.Client.ResourceManagement;
+using Robust.Client.State;
+using Robust.Shared.Audio;
+using Robust.Shared.Configuration;
+using Robust.Shared.Player;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Random;
+using Robust.Shared.Timing;
+using Robust.Shared.Utility;
+
+namespace Content.Client.Audio;
+
+public sealed partial class ContentAudioSystem
+{
+ [Dependency] private readonly IConfigurationManager _configManager = default!;
+ [Dependency] private readonly IGameTiming _timing = default!;
+ [Dependency] private readonly IPlayerManager _player = default!;
+ [Dependency] private readonly IPrototypeManager _proto = default!;
+ [Dependency] private readonly IResourceCache _resource = default!;
+ [Dependency] private readonly IRobustRandom _random = default!;
+ [Dependency] private readonly IStateManager _state = default!;
+ [Dependency] private readonly RulesSystem _rules = default!;
+ [Dependency] private readonly SharedAudioSystem _audio = default!;
+
+ private readonly TimeSpan _minAmbienceTime = TimeSpan.FromSeconds(30);
+ private readonly TimeSpan _maxAmbienceTime = TimeSpan.FromSeconds(60);
+
+ private const float AmbientMusicFadeTime = 10f;
+ private static float _volumeSlider;
+
+ // Don't need to worry about this being serializable or pauseable as it doesn't affect the sim.
+ private TimeSpan _nextAudio;
+
+ private AudioSystem.PlayingStream? _ambientMusicStream;
+ private AmbientMusicPrototype? _musicProto;
+
+ ///
+ /// If we find a better ambient music proto can we interrupt this one.
+ ///
+ private bool _interruptable;
+
+ ///
+ /// Track what ambient sounds we've played. This is so they all get played an even
+ /// number of times.
+ /// When we get to the end of the list we'll re-shuffle
+ ///
+ private readonly Dictionary> _ambientSounds = new();
+
+ private ISawmill _sawmill = default!;
+
+ private void InitializeAmbientMusic()
+ {
+ // TODO: Shitty preload
+ foreach (var audio in _proto.Index("AmbienceSpace").PickFiles)
+ {
+ _resource.GetResource(audio.ToString());
+ }
+
+ _configManager.OnValueChanged(CCVars.AmbientMusicVolume, AmbienceCVarChanged, true);
+ _sawmill = IoCManager.Resolve().GetSawmill("audio.ambience");
+
+ // Reset audio
+ _nextAudio = TimeSpan.MaxValue;
+
+ SetupAmbientSounds();
+ _proto.PrototypesReloaded += OnProtoReload;
+ _state.OnStateChanged += OnStateChange;
+ // On round end summary OR lobby cut audio.
+ SubscribeNetworkEvent(OnRoundEndMessage);
+ }
+
+ private void AmbienceCVarChanged(float obj)
+ {
+ _volumeSlider = obj;
+
+ if (_ambientMusicStream != null && _musicProto != null)
+ {
+ _ambientMusicStream.Volume = _musicProto.Sound.Params.Volume + _volumeSlider;
+ }
+ }
+
+ private void ShutdownAmbientMusic()
+ {
+ _configManager.UnsubValueChanged(CCVars.AmbientMusicVolume, AmbienceCVarChanged);
+ _proto.PrototypesReloaded -= OnProtoReload;
+ _state.OnStateChanged -= OnStateChange;
+ _ambientMusicStream?.Stop();
+ }
+
+ private void OnProtoReload(PrototypesReloadedEventArgs obj)
+ {
+ if (!obj.ByType.ContainsKey(typeof(AmbientMusicPrototype)) &&
+ !obj.ByType.ContainsKey(typeof(RulesPrototype)))
+ {
+ return;
+ }
+
+ _ambientSounds.Clear();
+ SetupAmbientSounds();
+ }
+
+ private void OnStateChange(StateChangedEventArgs obj)
+ {
+ if (obj.NewState is not GameplayState)
+ return;
+
+ // If they go to game then reset the ambience timer.
+ _nextAudio = _timing.CurTime + _random.Next(_minAmbienceTime, _maxAmbienceTime);
+ }
+
+ private void SetupAmbientSounds()
+ {
+ foreach (var ambience in _proto.EnumeratePrototypes())
+ {
+ var tracks = _ambientSounds.GetOrNew(ambience.ID);
+ RefreshTracks(ambience.Sound, tracks, null);
+ _random.Shuffle(tracks);
+ }
+ }
+
+ private void OnRoundEndMessage(RoundEndMessageEvent ev)
+ {
+ // If scoreboard shows then just stop the music
+ _ambientMusicStream?.Stop();
+ _ambientMusicStream = null;
+ _nextAudio = TimeSpan.FromMinutes(3);
+ }
+
+ private void RefreshTracks(SoundSpecifier sound, List tracks, ResPath? lastPlayed)
+ {
+ DebugTools.Assert(tracks.Count == 0);
+
+ switch (sound)
+ {
+ case SoundCollectionSpecifier collection:
+ if (collection.Collection == null)
+ break;
+
+ var slothCud = _proto.Index(collection.Collection);
+ tracks.AddRange(slothCud.PickFiles);
+ break;
+ case SoundPathSpecifier path:
+ tracks.Add(path.Path);
+ break;
+ }
+
+ // Just so the same track doesn't play twice
+ if (tracks.Count > 1 && tracks[^1] == lastPlayed)
+ {
+ (tracks[0], tracks[^1]) = (tracks[^1], tracks[0]);
+ }
+ }
+
+ private void UpdateAmbientMusic()
+ {
+ // Update still runs in lobby so just ignore it.
+ if (_state.CurrentState is not GameplayState)
+ {
+ FadeOut(_ambientMusicStream);
+ _ambientMusicStream = null;
+ _musicProto = null;
+ return;
+ }
+
+ var isDone = _ambientMusicStream?.Done;
+
+ if (_interruptable)
+ {
+ var player = _player.LocalPlayer?.ControlledEntity;
+
+ if (player == null || _musicProto == null || !_rules.IsTrue(player.Value, _proto.Index(_musicProto.Rules)))
+ {
+ FadeOut(_ambientMusicStream, AmbientMusicFadeTime);
+ _musicProto = null;
+ _interruptable = false;
+ isDone = true;
+ }
+ }
+
+ // Still running existing ambience
+ if (isDone == false)
+ return;
+
+ // If ambience finished reset the CD (this also means if we have long ambience it won't clip)
+ if (isDone == true)
+ {
+ // Also don't need to worry about rounding here as it doesn't affect the sim
+ _nextAudio = _timing.CurTime + _random.Next(_minAmbienceTime, _maxAmbienceTime);
+ }
+
+ _ambientMusicStream = null;
+
+ if (_nextAudio > _timing.CurTime)
+ return;
+
+ _musicProto = GetAmbience();
+
+ if (_musicProto == null)
+ {
+ _interruptable = false;
+ return;
+ }
+
+ _interruptable = _musicProto.Interruptable;
+ var tracks = _ambientSounds[_musicProto.ID];
+
+ var track = tracks[^1];
+ tracks.RemoveAt(tracks.Count - 1);
+
+ var strim = _audio.PlayGlobal(
+ track.ToString(),
+ Filter.Local(),
+ false,
+ AudioParams.Default.WithVolume(_musicProto.Sound.Params.Volume + _volumeSlider));
+
+ if (strim != null)
+ {
+ _ambientMusicStream = (AudioSystem.PlayingStream) strim;
+
+ if (_musicProto.FadeIn)
+ {
+ FadeIn(_ambientMusicStream, AmbientMusicFadeTime);
+ }
+ }
+
+ // Refresh the list
+ if (tracks.Count == 0)
+ {
+ RefreshTracks(_musicProto.Sound, tracks, track);
+ }
+ }
+
+ private AmbientMusicPrototype? GetAmbience()
+ {
+ var player = _player.LocalPlayer?.ControlledEntity;
+
+ if (player == null)
+ return null;
+
+ var ambiences = _proto.EnumeratePrototypes().ToList();
+ ambiences.Sort((x, y) => y.Priority.CompareTo(x.Priority));
+
+ foreach (var amb in ambiences)
+ {
+ if (!_rules.IsTrue(player.Value, _proto.Index(amb.Rules)))
+ continue;
+
+ return amb;
+ }
+
+ _sawmill.Warning($"Unable to find fallback ambience track");
+ return null;
+ }
+}
diff --git a/Content.Client/Audio/ContentAudioSystem.cs b/Content.Client/Audio/ContentAudioSystem.cs
index 7d19acf6efa05a..cd6a88f01d6d28 100644
--- a/Content.Client/Audio/ContentAudioSystem.cs
+++ b/Content.Client/Audio/ContentAudioSystem.cs
@@ -1,8 +1,124 @@
using Content.Shared.Audio;
+using Robust.Client.GameObjects;
namespace Content.Client.Audio;
-public sealed class ContentAudioSystem : SharedContentAudioSystem
+public sealed partial class ContentAudioSystem : SharedContentAudioSystem
{
+ // Need how much volume to change per tick and just remove it when it drops below "0"
+ private readonly Dictionary _fadingOut = new();
+ // Need volume change per tick + target volume.
+ private readonly Dictionary _fadingIn = new();
+
+ private readonly List _fadeToRemove = new();
+
+ private const float MinVolume = -32f;
+ private const float DefaultDuration = 2f;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+ UpdatesOutsidePrediction = true;
+ InitializeAmbientMusic();
+ }
+
+ public override void Shutdown()
+ {
+ base.Shutdown();
+ ShutdownAmbientMusic();
+ }
+
+ public override void Update(float frameTime)
+ {
+ base.Update(frameTime);
+
+ if (!_timing.IsFirstTimePredicted)
+ return;
+
+ UpdateAmbientMusic();
+ UpdateFades(frameTime);
+ }
+
+ #region Fades
+
+ public void FadeOut(AudioSystem.PlayingStream? stream, float duration = DefaultDuration)
+ {
+ if (stream == null || duration <= 0f)
+ return;
+
+ // Just in case
+ // TODO: Maybe handle the removals by making it seamless?
+ _fadingIn.Remove(stream);
+ var diff = stream.Volume - MinVolume;
+ _fadingOut.Add(stream, diff / duration);
+ }
+
+ public void FadeIn(AudioSystem.PlayingStream? stream, float duration = DefaultDuration)
+ {
+ if (stream == null || duration <= 0f || stream.Volume < MinVolume)
+ return;
+
+ _fadingOut.Remove(stream);
+ var curVolume = stream.Volume;
+ var change = (curVolume - MinVolume) / duration;
+ _fadingIn.Add(stream, (change, stream.Volume));
+ stream.Volume = MinVolume;
+ }
+
+ private void UpdateFades(float frameTime)
+ {
+ _fadeToRemove.Clear();
+
+ foreach (var (stream, change) in _fadingOut)
+ {
+ // Cancelled elsewhere
+ if (stream.Done)
+ {
+ _fadeToRemove.Add(stream);
+ continue;
+ }
+
+ var volume = stream.Volume - change * frameTime;
+ stream.Volume = MathF.Max(MinVolume, volume);
+
+ if (stream.Volume.Equals(MinVolume))
+ {
+ stream.Stop();
+ _fadeToRemove.Add(stream);
+ }
+ }
+
+ foreach (var stream in _fadeToRemove)
+ {
+ _fadingOut.Remove(stream);
+ }
+
+ _fadeToRemove.Clear();
+
+ foreach (var (stream, (change, target)) in _fadingIn)
+ {
+ // Cancelled elsewhere
+ if (stream.Done)
+ {
+ _fadeToRemove.Add(stream);
+ continue;
+ }
+
+ var volume = stream.Volume + change * frameTime;
+ stream.Volume = MathF.Min(target, volume);
+
+ if (stream.Volume.Equals(target))
+ {
+ _fadeToRemove.Add(stream);
+ }
+ }
+
+ foreach (var stream in _fadeToRemove)
+ {
+ _fadingIn.Remove(stream);
+ }
+ }
+
+ #endregion
}
diff --git a/Content.Client/Bql/BqlResultsEui.cs b/Content.Client/Bql/BqlResultsEui.cs
new file mode 100644
index 00000000000000..3e9bda57ff3072
--- /dev/null
+++ b/Content.Client/Bql/BqlResultsEui.cs
@@ -0,0 +1,45 @@
+using Content.Client.Eui;
+using Content.Shared.Bql;
+using Content.Shared.Eui;
+using JetBrains.Annotations;
+using Robust.Client.Console;
+
+namespace Content.Client.Bql;
+
+[UsedImplicitly]
+public sealed class BqlResultsEui : BaseEui
+{
+ private readonly BqlResultsWindow _window;
+
+ public BqlResultsEui()
+ {
+ _window = new BqlResultsWindow(
+ IoCManager.Resolve(),
+ IoCManager.Resolve()
+ );
+
+ _window.OnClose += () => SendMessage(new CloseEuiMessage());
+ }
+
+ public override void HandleState(EuiStateBase state)
+ {
+ if (state is not BqlResultsEuiState castState)
+ return;
+
+ _window.Update(castState.Entities);
+ }
+
+ public override void Closed()
+ {
+ base.Closed();
+
+ _window.Close();
+ }
+
+ public override void Opened()
+ {
+ base.Opened();
+
+ _window.OpenCentered();
+ }
+}
diff --git a/Content.Client/Bql/BqlResultsWindow.xaml b/Content.Client/Bql/BqlResultsWindow.xaml
new file mode 100644
index 00000000000000..9affbf2181ac8c
--- /dev/null
+++ b/Content.Client/Bql/BqlResultsWindow.xaml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
diff --git a/Content.Client/Bql/BqlResultsWindow.xaml.cs b/Content.Client/Bql/BqlResultsWindow.xaml.cs
new file mode 100644
index 00000000000000..7adceb5d773781
--- /dev/null
+++ b/Content.Client/Bql/BqlResultsWindow.xaml.cs
@@ -0,0 +1,48 @@
+using Robust.Client.AutoGenerated;
+using Robust.Client.Console;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.CustomControls;
+using Robust.Client.UserInterface.XAML;
+
+namespace Content.Client.Bql;
+
+[GenerateTypedNameReferences]
+internal sealed partial class BqlResultsWindow : DefaultWindow
+{
+ private readonly IClientConsoleHost _console;
+ private readonly ILocalizationManager _loc;
+
+ public BqlResultsWindow(IClientConsoleHost console, ILocalizationManager loc)
+ {
+ _console = console;
+ _loc = loc;
+
+ RobustXamlLoader.Load(this);
+ }
+
+ protected override Vector2 ContentsMinimumSize => (500, 700);
+
+ public void Update((string name, EntityUid entity)[] entities)
+ {
+ StatusLabel.Text = _loc.GetString("ui-bql-results-status", ("count", entities.Length));
+ ItemList.RemoveAllChildren();
+
+ foreach (var (name, entity) in entities)
+ {
+ var nameLabel = new Label { Text = name, HorizontalExpand = true };
+ var tpButton = new Button { Text = _loc.GetString("ui-bql-results-tp") };
+ tpButton.OnPressed += _ => _console.ExecuteCommand($"tpto {entity}");
+ tpButton.ToolTip = _loc.GetString("ui-bql-results-tp-tooltip");
+
+ var vvButton = new Button { Text = _loc.GetString("ui-bql-results-vv") };
+ vvButton.ToolTip = _loc.GetString("ui-bql-results-vv-tooltip");
+ vvButton.OnPressed += _ => _console.ExecuteCommand($"vv {entity}");
+
+ ItemList.AddChild(new BoxContainer
+ {
+ Orientation = BoxContainer.LayoutOrientation.Horizontal,
+ Children = { nameLabel, tpButton, vvButton }
+ });
+ }
+ }
+}
diff --git a/Content.Client/Cargo/BUI/CargoBountyConsoleBoundUserInterface.cs b/Content.Client/Cargo/BUI/CargoBountyConsoleBoundUserInterface.cs
new file mode 100644
index 00000000000000..63587d9875b409
--- /dev/null
+++ b/Content.Client/Cargo/BUI/CargoBountyConsoleBoundUserInterface.cs
@@ -0,0 +1,54 @@
+using Content.Client.Cargo.UI;
+using Content.Shared.Cargo.Components;
+using JetBrains.Annotations;
+using Robust.Client.GameObjects;
+
+namespace Content.Client.Cargo.BUI;
+
+[UsedImplicitly]
+public sealed class CargoBountyConsoleBoundUserInterface : BoundUserInterface
+{
+ [ViewVariables]
+ private CargoBountyMenu? _menu;
+
+ public CargoBountyConsoleBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey)
+ {
+
+ }
+
+ protected override void Open()
+ {
+ base.Open();
+
+ _menu = new();
+
+ _menu.OnClose += Close;
+
+ _menu.OnLabelButtonPressed += id =>
+ {
+ SendMessage(new BountyPrintLabelMessage(id));
+ };
+
+ _menu.OpenCentered();
+ }
+
+ protected override void UpdateState(BoundUserInterfaceState message)
+ {
+ base.UpdateState(message);
+
+ if (message is not CargoBountyConsoleState state)
+ return;
+
+ _menu?.UpdateEntries(state.Bounties);
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ base.Dispose(disposing);
+
+ if (!disposing)
+ return;
+
+ _menu?.Dispose();
+ }
+}
diff --git a/Content.Client/Cargo/CargoTelepadComponent.cs b/Content.Client/Cargo/CargoTelepadComponent.cs
deleted file mode 100644
index 884718f78316aa..00000000000000
--- a/Content.Client/Cargo/CargoTelepadComponent.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-using Content.Shared.Cargo.Components;
-using Robust.Shared.GameObjects;
-
-namespace Content.Client.Cargo;
-
-[RegisterComponent]
-public sealed class CargoTelepadComponent : SharedCargoTelepadComponent
-{
-
-}
diff --git a/Content.Client/Cargo/Systems/CargoSystem.Telepad.cs b/Content.Client/Cargo/Systems/CargoSystem.Telepad.cs
index 6bc0f8800779ad..50d079737de01d 100644
--- a/Content.Client/Cargo/Systems/CargoSystem.Telepad.cs
+++ b/Content.Client/Cargo/Systems/CargoSystem.Telepad.cs
@@ -1,4 +1,6 @@
using Content.Shared.Cargo;
+using Content.Shared.Cargo.Components;
+using JetBrains.Annotations;
using Robust.Client.Animations;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
@@ -52,47 +54,48 @@ private void InitializeCargoTelepad()
private void OnCargoAppChange(EntityUid uid, CargoTelepadComponent component, ref AppearanceChangeEvent args)
{
- OnChangeData(args.Component, args.Sprite);
+ OnChangeData(uid, args.Sprite);
}
private void OnCargoAnimComplete(EntityUid uid, CargoTelepadComponent component, AnimationCompletedEvent args)
{
- if (!TryComp(uid, out var appearance)) return;
-
- OnChangeData(appearance);
+ OnChangeData(uid);
}
- private void OnChangeData(AppearanceComponent component, SpriteComponent? sprite = null)
+ private void OnChangeData(EntityUid uid, SpriteComponent? sprite = null)
{
- if (!Resolve(component.Owner, ref sprite))
+ if (!Resolve(uid, ref sprite))
return;
- _appearance.TryGetData(component.Owner, CargoTelepadVisuals.State, out var state);
+ _appearance.TryGetData(uid, CargoTelepadVisuals.State, out var state);
AnimationPlayerComponent? player = null;
switch (state)
{
case CargoTelepadState.Teleporting:
- if (_player.HasRunningAnimation(component.Owner, TelepadBeamKey)) return;
- _player.Stop(component.Owner, player, TelepadIdleKey);
- _player.Play(component.Owner, player, CargoTelepadBeamAnimation, TelepadBeamKey);
+ if (_player.HasRunningAnimation(uid, TelepadBeamKey))
+ return;
+ _player.Stop(uid, player, TelepadIdleKey);
+ _player.Play(uid, player, CargoTelepadBeamAnimation, TelepadBeamKey);
break;
case CargoTelepadState.Unpowered:
sprite.LayerSetVisible(CargoTelepadLayers.Beam, false);
- _player.Stop(component.Owner, player, TelepadBeamKey);
- _player.Stop(component.Owner, player, TelepadIdleKey);
+ _player.Stop(uid, player, TelepadBeamKey);
+ _player.Stop(uid, player, TelepadIdleKey);
break;
default:
sprite.LayerSetVisible(CargoTelepadLayers.Beam, true);
- if (_player.HasRunningAnimation(component.Owner, player, TelepadIdleKey) ||
- _player.HasRunningAnimation(component.Owner, player, TelepadBeamKey)) return;
+ if (_player.HasRunningAnimation(uid, player, TelepadIdleKey) ||
+ _player.HasRunningAnimation(uid, player, TelepadBeamKey))
+ return;
- _player.Play(component.Owner, player, CargoTelepadIdleAnimation, TelepadIdleKey);
+ _player.Play(uid, player, CargoTelepadIdleAnimation, TelepadIdleKey);
break;
}
}
+ [UsedImplicitly]
private enum CargoTelepadLayers : byte
{
Base = 0,
diff --git a/Content.Client/Cargo/UI/BountyEntry.xaml b/Content.Client/Cargo/UI/BountyEntry.xaml
new file mode 100644
index 00000000000000..e570b03746a00f
--- /dev/null
+++ b/Content.Client/Cargo/UI/BountyEntry.xaml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/Cargo/UI/BountyEntry.xaml.cs b/Content.Client/Cargo/UI/BountyEntry.xaml.cs
new file mode 100644
index 00000000000000..54d11108406630
--- /dev/null
+++ b/Content.Client/Cargo/UI/BountyEntry.xaml.cs
@@ -0,0 +1,54 @@
+using Content.Client.Message;
+using Content.Shared.Cargo;
+using Content.Shared.Cargo.Prototypes;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Timing;
+
+namespace Content.Client.Cargo.UI;
+
+[GenerateTypedNameReferences]
+public sealed partial class BountyEntry : BoxContainer
+{
+ [Dependency] private readonly IGameTiming _timing = default!;
+ [Dependency] private readonly IPrototypeManager _prototype = default!;
+
+ public Action? OnButtonPressed;
+
+ public TimeSpan EndTime;
+
+ public BountyEntry(CargoBountyData bounty)
+ {
+ RobustXamlLoader.Load(this);
+ IoCManager.InjectDependencies(this);
+
+ if (!_prototype.TryIndex(bounty.Bounty, out var bountyPrototype))
+ return;
+
+ EndTime = bounty.EndTime;
+
+ var items = new List();
+ foreach (var entry in bountyPrototype.Entries)
+ {
+ items.Add(Loc.GetString("bounty-console-manifest-entry",
+ ("amount", entry.Amount),
+ ("item", Loc.GetString(entry.Name))));
+ }
+ ManifestLabel.SetMarkup(Loc.GetString("bounty-console-manifest-label", ("item", string.Join(", ", items))));
+ RewardLabel.SetMarkup(Loc.GetString("bounty-console-reward-label", ("reward", bountyPrototype.Reward)));
+ DescriptionLabel.SetMarkup(Loc.GetString("bounty-console-description-label", ("description", Loc.GetString(bountyPrototype.Description))));
+ IdLabel.Text = Loc.GetString("bounty-console-id-label", ("id", bounty.Id));
+
+ PrintButton.OnPressed += _ => OnButtonPressed?.Invoke();
+ }
+
+ protected override void FrameUpdate(FrameEventArgs args)
+ {
+ base.FrameUpdate(args);
+
+ var remaining = TimeSpan.FromSeconds(Math.Max((EndTime - _timing.CurTime).TotalSeconds, 0));
+ TimeLabel.SetMarkup(Loc.GetString("bounty-console-time-label", ("time", remaining.ToString("mm':'ss"))));
+ }
+}
diff --git a/Content.Client/Cargo/UI/CargoBountyMenu.xaml b/Content.Client/Cargo/UI/CargoBountyMenu.xaml
new file mode 100644
index 00000000000000..bb263ff6c4ab7f
--- /dev/null
+++ b/Content.Client/Cargo/UI/CargoBountyMenu.xaml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/Cargo/UI/CargoBountyMenu.xaml.cs b/Content.Client/Cargo/UI/CargoBountyMenu.xaml.cs
new file mode 100644
index 00000000000000..62ff31df899a56
--- /dev/null
+++ b/Content.Client/Cargo/UI/CargoBountyMenu.xaml.cs
@@ -0,0 +1,34 @@
+using Content.Client.UserInterface.Controls;
+using Content.Shared.Cargo;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.XAML;
+
+namespace Content.Client.Cargo.UI;
+
+[GenerateTypedNameReferences]
+public sealed partial class CargoBountyMenu : FancyWindow
+{
+ public Action? OnLabelButtonPressed;
+
+ public CargoBountyMenu()
+ {
+ RobustXamlLoader.Load(this);
+ }
+
+ public void UpdateEntries(List bounties)
+ {
+ BountyEntriesContainer.Children.Clear();
+ foreach (var b in bounties)
+ {
+ var entry = new BountyEntry(b);
+ entry.OnButtonPressed += () => OnLabelButtonPressed?.Invoke(b.Id);
+
+ BountyEntriesContainer.AddChild(entry);
+ }
+ BountyEntriesContainer.AddChild(new Control
+ {
+ MinHeight = 10
+ });
+ }
+}
diff --git a/Content.Client/Cargo/UI/CargoConsoleMenu.xaml.cs b/Content.Client/Cargo/UI/CargoConsoleMenu.xaml.cs
index 79397e15e0cb8e..0cc17a624696f2 100644
--- a/Content.Client/Cargo/UI/CargoConsoleMenu.xaml.cs
+++ b/Content.Client/Cargo/UI/CargoConsoleMenu.xaml.cs
@@ -129,13 +129,13 @@ public void PopulateOrders(IEnumerable orders)
foreach (var order in orders)
{
- var product = _protoManager.Index(order.ProductId);
+ var product = _protoManager.Index(order.ProductId);
var productName = product.Name;
var row = new CargoOrderRow
{
Order = order,
- Icon = { Texture = _spriteSystem.Frame0(product.Icon) },
+ Icon = { Texture = _spriteSystem.Frame0(product) },
ProductName =
{
Text = Loc.GetString(
diff --git a/Content.Client/Cargo/UI/CargoShuttleMenu.xaml.cs b/Content.Client/Cargo/UI/CargoShuttleMenu.xaml.cs
index 64d1e383b2ff0a..c591f917da3b5b 100644
--- a/Content.Client/Cargo/UI/CargoShuttleMenu.xaml.cs
+++ b/Content.Client/Cargo/UI/CargoShuttleMenu.xaml.cs
@@ -39,13 +39,13 @@ public void SetOrders(List orders)
foreach (var order in orders)
{
- var product = _protoManager.Index(order.ProductId);
+ var product = _protoManager.Index(order.ProductId);
var productName = product.Name;
var row = new CargoOrderRow
{
Order = order,
- Icon = { Texture = _spriteSystem.Frame0(product.Icon) },
+ Icon = { Texture = _spriteSystem.Frame0(product) },
ProductName =
{
Text = Loc.GetString(
diff --git a/Content.Client/Chemistry/EntitySystems/ChemistryGuideDataSystem.cs b/Content.Client/Chemistry/EntitySystems/ChemistryGuideDataSystem.cs
new file mode 100644
index 00000000000000..736e22f834ab9b
--- /dev/null
+++ b/Content.Client/Chemistry/EntitySystems/ChemistryGuideDataSystem.cs
@@ -0,0 +1,29 @@
+using Content.Shared.Chemistry;
+
+namespace Content.Client.Chemistry.EntitySystems;
+
+///
+public sealed class ChemistryGuideDataSystem : SharedChemistryGuideDataSystem
+{
+ ///
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeNetworkEvent(OnReceiveRegistryUpdate);
+ }
+
+ private void OnReceiveRegistryUpdate(ReagentGuideRegistryChangedEvent message)
+ {
+ var data = message.Changeset;
+ foreach (var remove in data.Removed)
+ {
+ Registry.Remove(remove);
+ }
+
+ foreach (var (key, val) in data.GuideEntries)
+ {
+ Registry[key] = val;
+ }
+ }
+}
diff --git a/Content.Client/Clothing/ClientClothingSystem.cs b/Content.Client/Clothing/ClientClothingSystem.cs
index 22d639fc2fb86c..5c638f861c43e6 100644
--- a/Content.Client/Clothing/ClientClothingSystem.cs
+++ b/Content.Client/Clothing/ClientClothingSystem.cs
@@ -12,12 +12,15 @@
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Shared.Serialization.TypeSerializers.Implementations;
+using Robust.Shared.Utility;
using static Robust.Client.GameObjects.SpriteComponent;
namespace Content.Client.Clothing;
public sealed class ClientClothingSystem : ClothingSystem
{
+ public const string Jumpsuit = "jumpsuit";
+
///
/// This is a shitty hotfix written by me (Paul) to save me from renaming all files.
/// For some context, im currently refactoring inventory. Part of that is slots not being indexed by a massive enum anymore, but by strings.
@@ -30,7 +33,7 @@ public sealed class ClientClothingSystem : ClothingSystem
{"ears", "EARS"},
{"mask", "MASK"},
{"outerClothing", "OUTERCLOTHING"},
- {"jumpsuit", "INNERCLOTHING"},
+ {Jumpsuit, "INNERCLOTHING"},
{"neck", "NECK"},
{"back", "BACKPACK"},
{"belt", "BELT"},
@@ -60,26 +63,22 @@ public override void Initialize()
private void OnAppearanceUpdate(EntityUid uid, InventoryComponent component, ref AppearanceChangeEvent args)
{
// May need to update jumpsuit stencils if the sex changed. Also required to properly set the stencil on init
- // when sex is first loaded from the profile.
- if (!TryComp(uid, out SpriteComponent? sprite) || !sprite.LayerMapTryGet(HumanoidVisualLayers.StencilMask, out var layer))
+ if (args.Sprite == null)
return;
- if (!TryComp(uid, out HumanoidAppearanceComponent? humanoid)
- || humanoid.Sex != Sex.Female
- || !_inventorySystem.TryGetSlotEntity(uid, "jumpsuit", out var suit, component)
- || !TryComp(suit, out ClothingComponent? clothing))
+ if (_inventorySystem.TryGetSlotEntity(uid, Jumpsuit, out var suit, component)
+ && TryComp(suit, out ClothingComponent? clothing))
{
- sprite.LayerSetVisible(layer, false);
+ SetGenderedMask(uid, args.Sprite, clothing);
return;
}
- sprite.LayerSetState(layer, clothing.FemaleMask switch
+ // No clothing equipped -> make sure the layer is hidden, though this should already be handled by on-unequip.
+ if (args.Sprite.LayerMapTryGet(HumanoidVisualLayers.StencilMask, out var layer))
{
- FemaleClothingMask.NoMask => "female_none",
- FemaleClothingMask.UniformTop => "female_top",
- _ => "female_full",
- });
- sprite.LayerSetVisible(layer, true);
+ DebugTools.Assert(!args.Sprite[layer].Visible);
+ args.Sprite.LayerSetVisible(layer, false);
+ }
}
private void OnGetVisuals(EntityUid uid, ClothingComponent item, GetEquipmentVisualsEvent args)
@@ -179,7 +178,15 @@ private void OnVisualsChanged(EntityUid uid, InventoryComponent component, Visua
private void OnDidUnequip(EntityUid uid, SpriteComponent component, DidUnequipEvent args)
{
- if (!TryComp(uid, out InventoryComponent? inventory) || !TryComp(uid, out InventorySlotsComponent? inventorySlots))
+ // Hide jumpsuit mask layer.
+ if (args.Slot == Jumpsuit
+ && TryComp(uid, out SpriteComponent? sprite)
+ && sprite.LayerMapTryGet(HumanoidVisualLayers.StencilMask, out var maskLayer))
+ {
+ sprite.LayerSetVisible(maskLayer, false);
+ }
+
+ if (!TryComp(uid, out InventorySlotsComponent? inventorySlots))
return;
if (!inventorySlots.VisualLayerKeys.TryGetValue(args.Slot, out var revealedLayers))
@@ -225,21 +232,8 @@ private void RenderEquipment(EntityUid equipee, EntityUid equipment, string slot
return;
}
- if (slot == "jumpsuit" && sprite.LayerMapTryGet(HumanoidVisualLayers.StencilMask, out var suitLayer))
- {
- if (TryComp(equipee, out HumanoidAppearanceComponent? humanoid) && humanoid.Sex == Sex.Female)
- {
- sprite.LayerSetState(suitLayer, clothingComponent.FemaleMask switch
- {
- FemaleClothingMask.NoMask => "female_none",
- FemaleClothingMask.UniformTop => "female_top",
- _ => "female_full",
- });
- sprite.LayerSetVisible(suitLayer, true);
- }
- else
- sprite.LayerSetVisible(suitLayer, false);
- }
+ if (slot == Jumpsuit)
+ SetGenderedMask(equipee, sprite, clothingComponent);
if (!_inventorySystem.TryGetSlot(equipee, slot, out var slotDef, inventory))
return;
@@ -306,7 +300,8 @@ private void RenderEquipment(EntityUid equipee, EntityUid equipment, string slot
// Another "temporary" fix for clothing stencil masks.
// Sprite layer redactor when
- if (slot == "jumpsuit")
+ // Sprite "redactor" just a week away.
+ if (slot == Jumpsuit)
layerData.Shader ??= "StencilDraw";
sprite.LayerSetData(index, layerData);
@@ -315,4 +310,44 @@ private void RenderEquipment(EntityUid equipee, EntityUid equipment, string slot
RaiseLocalEvent(equipment, new EquipmentVisualsUpdatedEvent(equipee, slot, revealedLayers), true);
}
+
+
+ ///
+ /// Sets a sprite's gendered mask based on gender (obviously).
+ ///
+ /// Sprite to modify
+ /// Humanoid, to get gender from
+ /// Clothing component, to get mask sprite type
+ private void SetGenderedMask(EntityUid uid, SpriteComponent sprite, ClothingComponent clothing)
+ {
+ if (!sprite.LayerMapTryGet(HumanoidVisualLayers.StencilMask, out var layer))
+ return;
+
+ ClothingMask mask;
+ string prefix;
+
+ switch (CompOrNull(uid)?.Sex)
+ {
+ case Sex.Male:
+ mask = clothing.MaleMask;
+ prefix = "male_";
+ break;
+ case Sex.Female:
+ mask = clothing.FemaleMask;
+ prefix = "female_";
+ break;
+ default:
+ mask = clothing.UnisexMask;
+ prefix = "unisex_";
+ break;
+ }
+
+ sprite.LayerSetState(layer, mask switch
+ {
+ ClothingMask.NoMask => $"{prefix}none",
+ ClothingMask.UniformTop => $"{prefix}top",
+ _ => $"{prefix}full",
+ });
+ sprite.LayerSetVisible(layer, true);
+ }
}
diff --git a/Content.Client/Configurable/UI/ConfigurationMenu.cs b/Content.Client/Configurable/UI/ConfigurationMenu.cs
index dc1e939eba42fa..483482dad118f9 100644
--- a/Content.Client/Configurable/UI/ConfigurationMenu.cs
+++ b/Content.Client/Configurable/UI/ConfigurationMenu.cs
@@ -92,7 +92,7 @@ public void Populate(ConfigurationBoundUserInterfaceState state)
var input = new LineEdit
{
Name = field.Key + "-input",
- Text = field.Value,
+ Text = field.Value ?? "",
IsValid = Validate,
HorizontalExpand = true,
SizeFlagsStretchRatio = .8f
diff --git a/Content.Client/Crayon/CrayonSystem.cs b/Content.Client/Crayon/CrayonSystem.cs
index 89726a30ffb3aa..e6da15c3bc56a8 100644
--- a/Content.Client/Crayon/CrayonSystem.cs
+++ b/Content.Client/Crayon/CrayonSystem.cs
@@ -62,7 +62,7 @@ protected override void FrameUpdate(FrameEventArgs args)
}
_parent.UIUpdateNeeded = false;
- _label.SetMarkup(Loc.GetString("crayon-drawing-label",
+ _label.SetMarkup(Robust.Shared.Localization.Loc.GetString("crayon-drawing-label",
("color",_parent.Color),
("state",_parent.SelectedState),
("charges", _parent.Charges),
diff --git a/Content.Client/Damage/DamageVisualsSystem.cs b/Content.Client/Damage/DamageVisualsSystem.cs
index 41f705c3f2a7ef..111ee2ddb53612 100644
--- a/Content.Client/Damage/DamageVisualsSystem.cs
+++ b/Content.Client/Damage/DamageVisualsSystem.cs
@@ -351,22 +351,22 @@ protected override void OnAppearanceChange(EntityUid uid, DamageVisualsComponent
if (damageVisComp.Disabled)
return;
- HandleDamage(args.Component, damageVisComp);
+ HandleDamage(uid, args.Component, damageVisComp);
}
- private void HandleDamage(AppearanceComponent component, DamageVisualsComponent damageVisComp)
+ private void HandleDamage(EntityUid uid, AppearanceComponent component, DamageVisualsComponent damageVisComp)
{
- if (!TryComp(component.Owner, out SpriteComponent? spriteComponent)
- || !TryComp(component.Owner, out DamageableComponent? damageComponent))
+ if (!TryComp(uid, out SpriteComponent? spriteComponent)
+ || !TryComp(uid, out DamageableComponent? damageComponent))
return;
if (damageVisComp.TargetLayers != null && damageVisComp.DamageOverlayGroups != null)
- UpdateDisabledLayers(spriteComponent, component, damageVisComp);
+ UpdateDisabledLayers(uid, spriteComponent, component, damageVisComp);
if (damageVisComp.Overlay && damageVisComp.DamageOverlayGroups != null && damageVisComp.TargetLayers == null)
CheckOverlayOrdering(spriteComponent, damageVisComp);
- if (AppearanceSystem.TryGetData(component.Owner, DamageVisualizerKeys.ForceUpdate, out var update, component)
+ if (AppearanceSystem.TryGetData(uid, DamageVisualizerKeys.ForceUpdate, out var update, component)
&& update)
{
ForceUpdateLayers(damageComponent, spriteComponent, damageVisComp);
@@ -376,11 +376,16 @@ private void HandleDamage(AppearanceComponent component, DamageVisualsComponent
if (damageVisComp.TrackAllDamage)
{
UpdateDamageVisuals(damageComponent, spriteComponent, damageVisComp);
+ return;
}
- else if (AppearanceSystem.TryGetData(component.Owner, DamageVisualizerKeys.DamageUpdateGroups, out var data, component))
+
+ if (!AppearanceSystem.TryGetData(uid, DamageVisualizerKeys.DamageUpdateGroups,
+ out var data, component))
{
- UpdateDamageVisuals(data.GroupList, damageComponent, spriteComponent, damageVisComp);
+ data = new DamageVisualizerGroupData(Comp(uid).DamagePerGroup.Keys.ToList());
}
+
+ UpdateDamageVisuals(data.GroupList, damageComponent, spriteComponent, damageVisComp);
}
///
@@ -389,29 +394,30 @@ private void HandleDamage(AppearanceComponent component, DamageVisualsComponent
/// layer will no longer be visible, or obtain
/// any damage updates.
///
- private void UpdateDisabledLayers(SpriteComponent spriteComponent, AppearanceComponent component, DamageVisualsComponent damageVisComp)
+ private void UpdateDisabledLayers(EntityUid uid, SpriteComponent spriteComponent, AppearanceComponent component, DamageVisualsComponent damageVisComp)
{
foreach (var layer in damageVisComp.TargetLayerMapKeys)
{
- bool? layerStatus = null;
- if (AppearanceSystem.TryGetData(component.Owner, layer, out var layerStateEnum, component))
- layerStatus = layerStateEnum;
+ // I assume this gets set by something like body system if limbs are missing???
+ // TODO is this actually used by anything anywhere?
+ AppearanceSystem.TryGetData(uid, layer, out bool disabled, component);
- if (layerStatus == null)
+ if (damageVisComp.DisabledLayers[layer] == disabled)
continue;
- if (damageVisComp.DisabledLayers[layer] != (bool) layerStatus)
+ damageVisComp.DisabledLayers[layer] = disabled;
+ if (damageVisComp.TrackAllDamage)
{
- damageVisComp.DisabledLayers[layer] = (bool) layerStatus;
- if (!damageVisComp.TrackAllDamage && damageVisComp.DamageOverlayGroups != null)
- {
- foreach (var damageGroup in damageVisComp.DamageOverlayGroups!.Keys)
- {
- spriteComponent.LayerSetVisible($"{layer}{damageGroup}", damageVisComp.DisabledLayers[layer]);
- }
- }
- else if (damageVisComp.TrackAllDamage)
- spriteComponent.LayerSetVisible($"{layer}trackDamage", damageVisComp.DisabledLayers[layer]);
+ spriteComponent.LayerSetVisible($"{layer}trackDamage", !disabled);
+ continue;
+ }
+
+ if (damageVisComp.DamageOverlayGroups == null)
+ continue;
+
+ foreach (var damageGroup in damageVisComp.DamageOverlayGroups.Keys)
+ {
+ spriteComponent.LayerSetVisible($"{layer}{damageGroup}", !disabled);
}
}
}
diff --git a/Content.Client/Decals/DecalPlacementSystem.cs b/Content.Client/Decals/DecalPlacementSystem.cs
index 7673378595d5d2..09f971833855df 100644
--- a/Content.Client/Decals/DecalPlacementSystem.cs
+++ b/Content.Client/Decals/DecalPlacementSystem.cs
@@ -157,6 +157,7 @@ private void OnFillSlot(FillActionSlotEvent ev)
DisplayName = $"{_decalId} ({_decalColor.ToHex()}, {(int) _decalAngle.Degrees})", // non-unique actions may be considered duplicates when saving/loading.
Icon = decalProto.Sprite,
Repeat = true,
+ ClientExclusive = true,
CheckCanAccess = false,
CheckCanInteract = false,
Range = -1,
diff --git a/Content.Client/Decals/Overlays/DecalPlacementOverlay.cs b/Content.Client/Decals/Overlays/DecalPlacementOverlay.cs
index 7057187efd49b9..1c65cca045cf88 100644
--- a/Content.Client/Decals/Overlays/DecalPlacementOverlay.cs
+++ b/Content.Client/Decals/Overlays/DecalPlacementOverlay.cs
@@ -39,13 +39,13 @@ protected override void Draw(in OverlayDrawArgs args)
return;
// No map support for decals
- if (!_mapManager.TryFindGridAt(mousePos, out var grid))
+ if (!_mapManager.TryFindGridAt(mousePos, out var gridUid, out var grid))
{
return;
}
- var worldMatrix = _transform.GetWorldMatrix(grid.Owner);
- var invMatrix = _transform.GetInvWorldMatrix(grid.Owner);
+ var worldMatrix = _transform.GetWorldMatrix(gridUid);
+ var invMatrix = _transform.GetInvWorldMatrix(gridUid);
var handle = args.WorldHandle;
handle.SetTransform(worldMatrix);
diff --git a/Content.Client/Disposal/Systems/DisposalUnitSystem.cs b/Content.Client/Disposal/Systems/DisposalUnitSystem.cs
index 2a531e9a4192ed..7f950439cceb20 100644
--- a/Content.Client/Disposal/Systems/DisposalUnitSystem.cs
+++ b/Content.Client/Disposal/Systems/DisposalUnitSystem.cs
@@ -9,12 +9,12 @@ namespace Content.Client.Disposal.Systems;
public sealed class DisposalUnitSystem : SharedDisposalUnitSystem
{
- [Dependency] private readonly AppearanceSystem AppearanceSystem = default!;
- [Dependency] private readonly AnimationPlayerSystem AnimationSystem = default!;
- [Dependency] private readonly SharedAudioSystem SoundSystem = default!;
+ [Dependency] private readonly AppearanceSystem _appearanceSystem = default!;
+ [Dependency] private readonly AnimationPlayerSystem _animationSystem = default!;
+ [Dependency] private readonly SharedAudioSystem _audioSystem = default!;
private const string AnimationKey = "disposal_unit_animation";
- private List PressuringDisposals = new();
+ private readonly List _pressuringDisposals = new();
public override void Initialize()
{
@@ -28,25 +28,25 @@ public void UpdateActive(EntityUid disposalEntity, bool active)
{
if (active)
{
- if (!PressuringDisposals.Contains(disposalEntity))
- PressuringDisposals.Add(disposalEntity);
+ if (!_pressuringDisposals.Contains(disposalEntity))
+ _pressuringDisposals.Add(disposalEntity);
}
else
{
- PressuringDisposals.Remove(disposalEntity);
+ _pressuringDisposals.Remove(disposalEntity);
}
}
public override void FrameUpdate(float frameTime)
{
base.FrameUpdate(frameTime);
- for (var i = PressuringDisposals.Count - 1; i >= 0; i--)
+ for (var i = _pressuringDisposals.Count - 1; i >= 0; i--)
{
- var disposal = PressuringDisposals[i];
+ var disposal = _pressuringDisposals[i];
if (!UpdateInterface(disposal))
continue;
- PressuringDisposals.RemoveAt(i);
+ _pressuringDisposals.RemoveAt(i);
}
}
@@ -79,17 +79,18 @@ private void OnComponentInit(EntityUid uid, DisposalUnitComponent disposalUnit,
if (!TryComp(uid, out var sprite))
return;
- if(!sprite.LayerMapTryGet(DisposalUnitVisualLayers.Base, out var baseLayerIdx))
+ if (!sprite.LayerMapTryGet(DisposalUnitVisualLayers.Base, out var baseLayerIdx))
return; // Couldn't find the "normal" layer to return to after flush animation
- if(!sprite.LayerMapTryGet(DisposalUnitVisualLayers.BaseFlush, out var flushLayerIdx))
+ if (!sprite.LayerMapTryGet(DisposalUnitVisualLayers.BaseFlush, out var flushLayerIdx))
return; // Couldn't find the flush animation layer
var originalBaseState = sprite.LayerGetState(baseLayerIdx);
var flushState = sprite.LayerGetState(flushLayerIdx);
// Setup the flush animation to play
- disposalUnit.FlushAnimation = new Animation {
+ disposalUnit.FlushAnimation = new Animation
+ {
Length = TimeSpan.FromSeconds(disposalUnit.FlushTime),
AnimationTracks = {
new AnimationTrackSpriteFlick {
@@ -109,9 +110,10 @@ private void OnComponentInit(EntityUid uid, DisposalUnitComponent disposalUnit,
if (disposalUnit.FlushSound != null)
{
disposalUnit.FlushAnimation.AnimationTracks.Add(
- new AnimationTrackPlaySound {
+ new AnimationTrackPlaySound
+ {
KeyFrames = {
- new AnimationTrackPlaySound.KeyFrame(SoundSystem.GetSound(disposalUnit.FlushSound), 0)
+ new AnimationTrackPlaySound.KeyFrame(_audioSystem.GetSound(disposalUnit.FlushSound), 0)
}
});
}
@@ -134,7 +136,7 @@ private void OnAppearanceChange(EntityUid uid, DisposalUnitComponent unit, ref A
// Update visuals and tick animation
private void UpdateState(EntityUid uid, DisposalUnitComponent unit, SpriteComponent sprite)
{
- if (!AppearanceSystem.TryGetData(uid, Visuals.VisualState, out var state))
+ if (!_appearanceSystem.TryGetData(uid, Visuals.VisualState, out var state))
{
return;
}
@@ -146,20 +148,20 @@ private void UpdateState(EntityUid uid, DisposalUnitComponent unit, SpriteCompon
if (state == VisualState.Flushing)
{
- if (!AnimationSystem.HasRunningAnimation(uid, AnimationKey))
+ if (!_animationSystem.HasRunningAnimation(uid, AnimationKey))
{
- AnimationSystem.Play(uid, unit.FlushAnimation, AnimationKey);
+ _animationSystem.Play(uid, unit.FlushAnimation, AnimationKey);
}
}
- if (!AppearanceSystem.TryGetData(uid, Visuals.Handle, out var handleState))
+ if (!_appearanceSystem.TryGetData(uid, Visuals.Handle, out var handleState))
{
handleState = HandleState.Normal;
}
sprite.LayerSetVisible(DisposalUnitVisualLayers.OverlayEngaged, handleState != HandleState.Normal);
- if (!AppearanceSystem.TryGetData(uid, Visuals.Light, out var lightState))
+ if (!_appearanceSystem.TryGetData(uid, Visuals.Light, out var lightState))
{
lightState = LightStates.Off;
}
diff --git a/Content.Client/Doors/AirlockSystem.cs b/Content.Client/Doors/AirlockSystem.cs
index cf4c3dfca0d090..18b9eae5f34999 100644
--- a/Content.Client/Doors/AirlockSystem.cs
+++ b/Content.Client/Doors/AirlockSystem.cs
@@ -86,7 +86,8 @@ private void OnAppearanceChange(EntityUid uid, AirlockComponent comp, ref Appear
if (_appearanceSystem.TryGetData(uid, DoorVisuals.Powered, out var powered, args.Component) && powered)
{
- boltedVisible = _appearanceSystem.TryGetData(uid, DoorVisuals.BoltLights, out var lights, args.Component) && lights;
+ boltedVisible = _appearanceSystem.TryGetData(uid, DoorVisuals.BoltLights, out var lights, args.Component)
+ && lights && state == DoorState.Closed;
emergencyLightsVisible = _appearanceSystem.TryGetData(uid, DoorVisuals.EmergencyLights, out var eaLights, args.Component) && eaLights;
unlitVisible =
state == DoorState.Closing
diff --git a/Content.Client/Doors/DoorBoltSystem.cs b/Content.Client/Doors/DoorBoltSystem.cs
new file mode 100644
index 00000000000000..58144cd6e0bb60
--- /dev/null
+++ b/Content.Client/Doors/DoorBoltSystem.cs
@@ -0,0 +1,12 @@
+using Content.Client.Wires.Visualizers;
+using Content.Shared.Doors.Components;
+using Content.Shared.Doors.Systems;
+using Robust.Client.Animations;
+using Robust.Client.GameObjects;
+
+namespace Content.Client.Doors;
+
+public sealed class DoorBoltSystem : SharedDoorBoltSystem
+{
+ // Instantiate sub-class on client for prediction.
+}
diff --git a/Content.Client/Entry/EntryPoint.cs b/Content.Client/Entry/EntryPoint.cs
index 4e6035d17a19cd..49383d97797453 100644
--- a/Content.Client/Entry/EntryPoint.cs
+++ b/Content.Client/Entry/EntryPoint.cs
@@ -14,6 +14,7 @@
using Content.Client.Players.PlayTimeTracking;
using Content.Client.Preferences;
using Content.Client.Radiation.Overlays;
+using Content.Client.Replay;
using Content.Client.Screenshot;
using Content.Client.Singularity;
using Content.Client.Stylesheets;
@@ -60,8 +61,9 @@ public sealed class EntryPoint : GameClient
[Dependency] private readonly DocumentParsingManager _documentParsingManager = default!;
[Dependency] private readonly GhostKickManager _ghostKick = default!;
[Dependency] private readonly ExtendedDisconnectInformationManager _extendedDisconnectInformation = default!;
- [Dependency] private readonly PlayTimeTrackingManager _playTimeTracking = default!;
+ [Dependency] private readonly JobRequirementsManager _jobRequirements = default!;
[Dependency] private readonly ContentLocalizationManager _contentLoc = default!;
+ [Dependency] private readonly ContentReplayPlaybackManager _playbackMan = default!;
public override void Init()
{
@@ -130,7 +132,8 @@ public override void Init()
_viewportManager.Initialize();
_ghostKick.Initialize();
_extendedDisconnectInformation.Initialize();
- _playTimeTracking.Initialize();
+ _jobRequirements.Initialize();
+ _playbackMan.Initialize();
//AUTOSCALING default Setup!
_configManager.SetCVar("interface.resolutionAutoScaleUpperCutoffX", 1080);
@@ -154,7 +157,6 @@ public override void PostInit()
_overlayManager.AddOverlay(new SingularityOverlay());
_overlayManager.AddOverlay(new FlashOverlay());
_overlayManager.AddOverlay(new RadiationPulseOverlay());
-
_chatManager.Initialize();
_clientPreferencesManager.Initialize();
_euiManager.Initialize();
diff --git a/Content.Client/Examine/ExamineSystem.cs b/Content.Client/Examine/ExamineSystem.cs
index 0109f90cc51ae9..cd09ff5fd10f22 100644
--- a/Content.Client/Examine/ExamineSystem.cs
+++ b/Content.Client/Examine/ExamineSystem.cs
@@ -16,6 +16,7 @@
using System.Linq;
using System.Threading;
using Content.Shared.Eye.Blinding.Components;
+using Robust.Client;
using static Content.Shared.Interaction.SharedInteractionSystem;
using static Robust.Client.UserInterface.Controls.BoxContainer;
@@ -28,6 +29,7 @@ public sealed class ExamineSystem : ExamineSystemShared
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly VerbSystem _verbSystem = default!;
+ [Dependency] private readonly IBaseClient _client = default!;
public const string StyleClassEntityTooltip = "entity-tooltip";
@@ -327,9 +329,9 @@ public void VerbButtonPressed(BaseButton.ButtonEventArgs obj)
}
}
- public void DoExamine(EntityUid entity, bool centeredOnCursor=true)
+ public void DoExamine(EntityUid entity, bool centeredOnCursor = true, EntityUid? userOverride = null)
{
- var playerEnt = _playerManager.LocalPlayer?.ControlledEntity;
+ var playerEnt = userOverride ?? _playerManager.LocalPlayer?.ControlledEntity;
if (playerEnt == null)
return;
@@ -341,10 +343,10 @@ public void DoExamine(EntityUid entity, bool centeredOnCursor=true)
canSeeClearly = false;
OpenTooltip(playerEnt.Value, entity, centeredOnCursor, false, knowTarget: canSeeClearly);
- if (entity.IsClientSide())
+ if (entity.IsClientSide()
+ || _client.RunLevel == ClientRunLevel.SinglePlayerGame) // i.e. a replay
{
message = GetExamineText(entity, playerEnt);
-
UpdateTooltipInfo(playerEnt.Value, entity, message);
}
else
diff --git a/Content.Client/GameTicking/Managers/ClientGameTicker.cs b/Content.Client/GameTicking/Managers/ClientGameTicker.cs
index f1e29a2ea1a1ca..33a20c945a902c 100644
--- a/Content.Client/GameTicking/Managers/ClientGameTicker.cs
+++ b/Content.Client/GameTicking/Managers/ClientGameTicker.cs
@@ -21,11 +21,18 @@ public sealed class ClientGameTicker : SharedGameTicker
[Dependency] private readonly IStateManager _stateManager = default!;
[Dependency] private readonly IEntityManager _entityManager = default!;
[Dependency] private readonly IConfigurationManager _configManager = default!;
+ [Dependency] private readonly BackgroundAudioSystem _backgroundAudio = default!;
+ [Dependency] private readonly SharedAudioSystem _audio = default!;
[ViewVariables] private bool _initialized;
private Dictionary> _jobsAvailable = new();
private Dictionary _stationNames = new();
+ ///
+ /// The current round-end window. Could be used to support re-opening the window after closing it.
+ ///
+ private RoundEndSummaryWindow? _window;
+
[ViewVariables] public bool AreWeReady { get; private set; }
[ViewVariables] public bool IsGameStarted { get; private set; }
[ViewVariables] public string? LobbySong { get; private set; }
@@ -127,13 +134,17 @@ private void RoundEnd(RoundEndMessageEvent message)
if (message.LobbySong != null)
{
LobbySong = message.LobbySong;
- Get().StartLobbyMusic();
+ _backgroundAudio.StartLobbyMusic();
}
RestartSound = message.RestartSound;
+ // Don't open duplicate windows (mainly for replays).
+ if (_window?.RoundId == message.RoundId)
+ return;
+
//This is not ideal at all, but I don't see an immediately better fit anywhere else.
- var roundEnd = new RoundEndSummaryWindow(message.GamemodeTitle, message.RoundEndText, message.RoundDuration, message.RoundId, message.AllPlayersEndInfo, _entityManager);
+ _window = new RoundEndSummaryWindow(message.GamemodeTitle, message.RoundEndText, message.RoundDuration, message.RoundId, message.AllPlayersEndInfo, _entityManager);
}
private void RoundRestartCleanup(RoundRestartCleanupEvent ev)
@@ -147,7 +158,7 @@ private void RoundRestartCleanup(RoundRestartCleanupEvent ev)
return;
}
- SoundSystem.Play(RestartSound, Filter.Empty());
+ _audio.PlayGlobal(RestartSound, Filter.Local(), false);
// Cleanup the sound, we only want it to play when the round restarts after it ends normally.
RestartSound = null;
diff --git a/Content.Client/Gameplay/GameplayState.cs b/Content.Client/Gameplay/GameplayState.cs
index 3765753c696105..1efee978f39c0e 100644
--- a/Content.Client/Gameplay/GameplayState.cs
+++ b/Content.Client/Gameplay/GameplayState.cs
@@ -13,7 +13,8 @@
namespace Content.Client.Gameplay
{
- public sealed class GameplayState : GameplayStateBase, IMainViewportState
+ [Virtual]
+ public class GameplayState : GameplayStateBase, IMainViewportState
{
[Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly IOverlayManager _overlayManager = default!;
diff --git a/Content.Client/Gameplay/GameplayStateBase.cs b/Content.Client/Gameplay/GameplayStateBase.cs
index 555cde1a57cef7..b4bd6f9b7260fa 100644
--- a/Content.Client/Gameplay/GameplayStateBase.cs
+++ b/Content.Client/Gameplay/GameplayStateBase.cs
@@ -168,7 +168,8 @@ protected virtual void OnKeyBindStateChanged(ViewportBoundKeyEventArgs args)
var mousePosWorld = vp.ScreenToMap(kArgs.PointerLocation.Position);
entityToClick = GetClickedEntity(mousePosWorld);
- coordinates = _mapManager.TryFindGridAt(mousePosWorld, out var grid) ? grid.MapToGrid(mousePosWorld) :
+ coordinates = _mapManager.TryFindGridAt(mousePosWorld, out _, out var grid) ?
+ grid.MapToGrid(mousePosWorld) :
EntityCoordinates.FromMap(_mapManager, mousePosWorld);
}
diff --git a/Content.Client/Guidebook/Controls/GuideEntityEmbed.xaml.cs b/Content.Client/Guidebook/Controls/GuideEntityEmbed.xaml.cs
index 34b4f3bdaab091..fc885b36553c17 100644
--- a/Content.Client/Guidebook/Controls/GuideEntityEmbed.xaml.cs
+++ b/Content.Client/Guidebook/Controls/GuideEntityEmbed.xaml.cs
@@ -79,7 +79,8 @@ protected override void KeyBindDown(GUIBoundKeyEventArgs args)
// do examination?
if (args.Function == ContentKeyFunctions.ExamineEntity)
{
- _examineSystem.DoExamine(entity.Value);
+ _examineSystem.DoExamine(entity.Value,
+ userOverride: _guidebookSystem.GetGuidebookUser());
args.Handle();
return;
}
diff --git a/Content.Client/Guidebook/Controls/GuideReagentEmbed.xaml b/Content.Client/Guidebook/Controls/GuideReagentEmbed.xaml
new file mode 100644
index 00000000000000..e8a36d57f48b74
--- /dev/null
+++ b/Content.Client/Guidebook/Controls/GuideReagentEmbed.xaml
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/Guidebook/Controls/GuideReagentEmbed.xaml.cs b/Content.Client/Guidebook/Controls/GuideReagentEmbed.xaml.cs
new file mode 100644
index 00000000000000..4b832be02b1ce6
--- /dev/null
+++ b/Content.Client/Guidebook/Controls/GuideReagentEmbed.xaml.cs
@@ -0,0 +1,176 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using Content.Client.Chemistry.EntitySystems;
+using Content.Client.Guidebook.Richtext;
+using Content.Client.Message;
+using Content.Shared.Chemistry.Reaction;
+using Content.Shared.Chemistry.Reagent;
+using JetBrains.Annotations;
+using Robust.Client.AutoGenerated;
+using Robust.Client.Graphics;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Utility;
+
+namespace Content.Client.Guidebook.Controls;
+
+///
+/// Control for embedding a reagent into a guidebook.
+///
+[UsedImplicitly, GenerateTypedNameReferences]
+public sealed partial class GuideReagentEmbed : BoxContainer, IDocumentTag
+{
+ [Dependency] private readonly IEntitySystemManager _systemManager = default!;
+ [Dependency] private readonly IPrototypeManager _prototype = default!;
+
+ private readonly ChemistryGuideDataSystem _chemistryGuideData;
+
+ public GuideReagentEmbed()
+ {
+ RobustXamlLoader.Load(this);
+ IoCManager.InjectDependencies(this);
+ _chemistryGuideData = _systemManager.GetEntitySystem();
+ MouseFilter = MouseFilterMode.Stop;
+ }
+
+ public GuideReagentEmbed(string reagent) : this()
+ {
+ GenerateControl(_prototype.Index(reagent));
+ }
+
+ public GuideReagentEmbed(ReagentPrototype reagent) : this()
+ {
+ GenerateControl(reagent);
+ }
+
+ public bool TryParseTag(Dictionary args, [NotNullWhen(true)] out Control? control)
+ {
+ control = null;
+ if (!args.TryGetValue("Reagent", out var id))
+ {
+ Logger.Error("Reagent embed tag is missing reagent prototype argument");
+ return false;
+ }
+
+ if (!_prototype.TryIndex(id, out var reagent))
+ {
+ Logger.Error($"Specified reagent prototype \"{id}\" is not a valid reagent prototype");
+ return false;
+ }
+
+ GenerateControl(reagent);
+
+ control = this;
+ return true;
+ }
+
+ private void GenerateControl(ReagentPrototype reagent)
+ {
+ NameBackground.PanelOverride = new StyleBoxFlat
+ {
+ BackgroundColor = reagent.SubstanceColor
+ };
+
+ var textColor = Color.ToHsl(reagent.SubstanceColor).Z > 0.45
+ ? Color.Black
+ : Color.White;
+
+ ReagentName.SetMarkup(Loc.GetString("guidebook-reagent-name",
+ ("color", textColor), ("name", reagent.LocalizedName)));
+
+ #region Recipe
+ // by default, we assume that the reaction has the same ID as the reagent.
+ // if this isn't true, we'll loop through reactions.
+ if (!_prototype.TryIndex(reagent.ID, out var reactionPrototype))
+ {
+ reactionPrototype = _prototype.EnumeratePrototypes()
+ .FirstOrDefault(p => p.Products.ContainsKey(reagent.ID));
+ }
+
+ if (reactionPrototype != null)
+ {
+ var reactantMsg = new FormattedMessage();
+ var reactantsCount = reactionPrototype.Reactants.Count;
+ var i = 0;
+ foreach (var (product, reactant) in reactionPrototype.Reactants)
+ {
+ reactantMsg.AddMarkup(Loc.GetString("guidebook-reagent-recipes-reagent-display",
+ ("reagent", _prototype.Index(product).LocalizedName), ("ratio", reactant.Amount)));
+ i++;
+ if (i < reactantsCount)
+ reactantMsg.PushNewline();
+ }
+ reactantMsg.Pop();
+ ReactantsLabel.SetMessage(reactantMsg);
+
+ var productMsg = new FormattedMessage();
+ var productCount = reactionPrototype.Products.Count;
+ var u = 0;
+ foreach (var (product, ratio) in reactionPrototype.Products)
+ {
+ productMsg.AddMarkup(Loc.GetString("guidebook-reagent-recipes-reagent-display",
+ ("reagent", _prototype.Index(product).LocalizedName), ("ratio", ratio)));
+ u++;
+ if (u < productCount)
+ productMsg.PushNewline();
+ }
+ productMsg.Pop();
+ ProductsLabel.SetMessage(productMsg);
+ }
+ else
+ {
+ RecipesContainer.Visible = false;
+ }
+ #endregion
+
+ #region Effects
+ if (_chemistryGuideData.ReagentGuideRegistry.TryGetValue(reagent.ID, out var guideEntryRegistry) &&
+ guideEntryRegistry.GuideEntries != null &&
+ guideEntryRegistry.GuideEntries.Values.Any(pair => pair.EffectDescriptions.Any()))
+ {
+ EffectsDescriptionContainer.Children.Clear();
+ foreach (var (group, effect) in guideEntryRegistry.GuideEntries)
+ {
+ if (!effect.EffectDescriptions.Any())
+ continue;
+
+ var groupLabel = new RichTextLabel();
+ groupLabel.SetMarkup(Loc.GetString("guidebook-reagent-effects-metabolism-group-rate",
+ ("group", group), ("rate", effect.MetabolismRate)));
+ var descriptionLabel = new RichTextLabel
+ {
+ Margin = new Thickness(25, 0, 10, 0)
+ };
+
+ var descMsg = new FormattedMessage();
+ var descriptionsCount = effect.EffectDescriptions.Length;
+ var i = 0;
+ foreach (var effectString in effect.EffectDescriptions)
+ {
+ descMsg.AddMarkup(effectString);
+ i++;
+ if (i < descriptionsCount)
+ descMsg.PushNewline();
+ }
+ descriptionLabel.SetMessage(descMsg);
+
+ EffectsDescriptionContainer.AddChild(groupLabel);
+ EffectsDescriptionContainer.AddChild(descriptionLabel);
+ }
+ }
+ else
+ {
+ EffectsContainer.Visible = false;
+ }
+ #endregion
+
+ FormattedMessage description = new();
+ description.AddText(reagent.LocalizedDescription);
+ description.PushNewline();
+ description.AddText(Loc.GetString("guidebook-reagent-physical-description",
+ ("description", reagent.LocalizedPhysicalDescription)));
+ ReagentDescription.SetMessage(description);
+ }
+}
diff --git a/Content.Client/Guidebook/Controls/GuideReagentGroupEmbed.xaml b/Content.Client/Guidebook/Controls/GuideReagentGroupEmbed.xaml
new file mode 100644
index 00000000000000..da671adaa72b71
--- /dev/null
+++ b/Content.Client/Guidebook/Controls/GuideReagentGroupEmbed.xaml
@@ -0,0 +1,4 @@
+
+
+
diff --git a/Content.Client/Guidebook/Controls/GuideReagentGroupEmbed.xaml.cs b/Content.Client/Guidebook/Controls/GuideReagentGroupEmbed.xaml.cs
new file mode 100644
index 00000000000000..0c9356eccb30fe
--- /dev/null
+++ b/Content.Client/Guidebook/Controls/GuideReagentGroupEmbed.xaml.cs
@@ -0,0 +1,60 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using Content.Client.Guidebook.Richtext;
+using Content.Shared.Chemistry.Reagent;
+using JetBrains.Annotations;
+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.Guidebook.Controls;
+
+///
+/// Control for embedding a reagent into a guidebook.
+///
+[UsedImplicitly, GenerateTypedNameReferences]
+public sealed partial class GuideReagentGroupEmbed : BoxContainer, IDocumentTag
+{
+ [Dependency] private readonly IPrototypeManager _prototype = default!;
+
+ public GuideReagentGroupEmbed()
+ {
+ RobustXamlLoader.Load(this);
+ IoCManager.InjectDependencies(this);
+ MouseFilter = MouseFilterMode.Stop;
+ }
+
+ public GuideReagentGroupEmbed(string group) : this()
+ {
+ var prototypes = _prototype.EnumeratePrototypes()
+ .Where(p => p.Group.Equals(group)).OrderBy(p => p.LocalizedName);
+ foreach (var reagent in prototypes)
+ {
+ var embed = new GuideReagentEmbed(reagent);
+ GroupContainer.AddChild(embed);
+ }
+ }
+
+ public bool TryParseTag(Dictionary args, [NotNullWhen(true)] out Control? control)
+ {
+ control = null;
+ if (!args.TryGetValue("Group", out var group))
+ {
+ Logger.Error("Reagent group embed tag is missing group argument");
+ return false;
+ }
+
+ var prototypes = _prototype.EnumeratePrototypes()
+ .Where(p => p.Group.Equals(group)).OrderBy(p => p.LocalizedName);
+ foreach (var reagent in prototypes)
+ {
+ var embed = new GuideReagentEmbed(reagent);
+ GroupContainer.AddChild(embed);
+ }
+
+ control = this;
+ return true;
+ }
+}
diff --git a/Content.Client/Guidebook/Controls/GuidebookWindow.xaml.cs b/Content.Client/Guidebook/Controls/GuidebookWindow.xaml.cs
index 32a7e2b070bc68..61054e9b0f2730 100644
--- a/Content.Client/Guidebook/Controls/GuidebookWindow.xaml.cs
+++ b/Content.Client/Guidebook/Controls/GuidebookWindow.xaml.cs
@@ -1,7 +1,11 @@
+using System.Diagnostics.CodeAnalysis;
using System.Linq;
+using Content.Client.Guidebook.RichText;
using Content.Client.UserInterface.Controls;
using Content.Client.UserInterface.Controls.FancyTree;
+using JetBrains.Annotations;
using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.ContentPack;
@@ -9,7 +13,7 @@
namespace Content.Client.Guidebook.Controls;
[GenerateTypedNameReferences]
-public sealed partial class GuidebookWindow : FancyWindow
+public sealed partial class GuidebookWindow : FancyWindow, ILinkClickHandler
{
[Dependency] private readonly IResourceManager _resourceManager = default!;
[Dependency] private readonly DocumentParsingManager _parsingMan = default!;
@@ -139,4 +143,20 @@ private void RepopulateTree(List? roots = null, string? forcedRoot = nul
return item;
}
+
+ public void HandleClick(string link)
+ {
+ if (!_entries.TryGetValue(link, out var entry))
+ return;
+
+ if (Tree.TryGetIndexFromMetadata(entry, out var index))
+ {
+ Tree.ExpandParentEntries(index.Value);
+ Tree.SetSelectedIndex(index);
+ }
+ else
+ {
+ ShowGuide(entry);
+ }
+ }
}
diff --git a/Content.Client/Guidebook/GuidebookSystem.cs b/Content.Client/Guidebook/GuidebookSystem.cs
index acaa51524b3392..6ed38ddef0f450 100644
--- a/Content.Client/Guidebook/GuidebookSystem.cs
+++ b/Content.Client/Guidebook/GuidebookSystem.cs
@@ -9,7 +9,9 @@
using Content.Shared.Verbs;
using Robust.Client.GameObjects;
using Robust.Client.Player;
+using Robust.Shared.Map;
using Robust.Shared.Player;
+using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Content.Client.Guidebook;
@@ -19,6 +21,7 @@ namespace Content.Client.Guidebook;
///
public sealed class GuidebookSystem : EntitySystem
{
+ [Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
[Dependency] private readonly SharedAudioSystem _audioSystem = default!;
[Dependency] private readonly VerbSystem _verbSystem = default!;
@@ -29,6 +32,8 @@ public sealed class GuidebookSystem : EntitySystem
public event Action, List?, string?, bool, string?>? OnGuidebookOpen;
public const string GuideEmbedTag = "GuideEmbeded";
+ private EntityUid _defaultUser;
+
///
public override void Initialize()
{
@@ -41,6 +46,23 @@ public override void Initialize()
OnGuidebookControlsTestGetAlternateVerbs);
}
+ ///
+ /// Gets a user entity to use for verbs and examinations. If the player has no attached entity, this will use a
+ /// dummy client-side entity so that users can still use the guidebook when not attached to anything (e.g., in the
+ /// lobby)
+ ///
+ public EntityUid GetGuidebookUser()
+ {
+ var user = _playerManager.LocalPlayer!.ControlledEntity;
+ if (user != null)
+ return user.Value;
+
+ if (!Exists(_defaultUser))
+ _defaultUser = Spawn(null, MapCoordinates.Nullspace);
+
+ return _defaultUser;
+ }
+
private void OnGetVerbs(EntityUid uid, GuideHelpComponent component, GetVerbsEvent args)
{
if (component.Guides.Count == 0 || _tags.HasTag(uid, GuideEmbedTag))
@@ -58,6 +80,9 @@ private void OnGetVerbs(EntityUid uid, GuideHelpComponent component, GetVerbsEve
private void OnInteract(EntityUid uid, GuideHelpComponent component, ActivateInWorldEvent args)
{
+ if (!_timing.IsFirstTimePredicted)
+ return;
+
if (!component.OpenOnActivation || component.Guides.Count == 0 || _tags.HasTag(uid, GuideEmbedTag))
return;
@@ -114,34 +139,26 @@ private void OnGuidebookControlsTestInteractHand(EntityUid uid, GuidebookControl
_audioSystem.PlayGlobal(speech.SpeechSounds, Filter.Local(), false, speech.AudioParams);
}
-
public void FakeClientActivateInWorld(EntityUid activated)
{
- var user = _playerManager.LocalPlayer!.ControlledEntity;
- if (user is null)
- return;
- var activateMsg = new ActivateInWorldEvent(user.Value, activated);
- RaiseLocalEvent(activated, activateMsg, true);
+ var activateMsg = new ActivateInWorldEvent(GetGuidebookUser(), activated);
+ RaiseLocalEvent(activated, activateMsg);
}
public void FakeClientAltActivateInWorld(EntityUid activated)
{
- var user = _playerManager.LocalPlayer!.ControlledEntity;
- if (user is null)
- return;
// Get list of alt-interact verbs
- var verbs = _verbSystem.GetLocalVerbs(activated, user.Value, typeof(AlternativeVerb));
+ var verbs = _verbSystem.GetLocalVerbs(activated, GetGuidebookUser(), typeof(AlternativeVerb), force: true);
if (!verbs.Any())
return;
- _verbSystem.ExecuteVerb(verbs.First(), user.Value, activated);
+ _verbSystem.ExecuteVerb(verbs.First(), GetGuidebookUser(), activated);
}
public void FakeClientUse(EntityUid activated)
{
- var user = _playerManager.LocalPlayer!.ControlledEntity ?? EntityUid.Invalid;
- var activateMsg = new InteractHandEvent(user, activated);
- RaiseLocalEvent(activated, activateMsg, true);
+ var activateMsg = new InteractHandEvent(GetGuidebookUser(), activated);
+ RaiseLocalEvent(activated, activateMsg);
}
}
diff --git a/Content.Client/Guidebook/Richtext/TextLinkTag.cs b/Content.Client/Guidebook/Richtext/TextLinkTag.cs
new file mode 100644
index 00000000000000..b1e8460bb89a89
--- /dev/null
+++ b/Content.Client/Guidebook/Richtext/TextLinkTag.cs
@@ -0,0 +1,70 @@
+using System.Diagnostics.CodeAnalysis;
+using JetBrains.Annotations;
+using Robust.Client.UserInterface;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.RichText;
+using Robust.Shared.Input;
+using Robust.Shared.Utility;
+
+namespace Content.Client.Guidebook.RichText;
+
+[UsedImplicitly]
+public sealed class TextLinkTag : IMarkupTag
+{
+ public string Name => "textlink";
+
+ public Control? Control;
+
+ ///
+ public bool TryGetControl(MarkupNode node, [NotNullWhen(true)] out Control? control)
+ {
+ if (!node.Value.TryGetString(out var text)
+ || !node.Attributes.TryGetValue("link", out var linkParameter)
+ || !linkParameter.TryGetString(out var link))
+ {
+ control = null;
+ return false;
+ }
+
+ var label = new Label();
+ label.Text = text;
+
+ label.MouseFilter = Control.MouseFilterMode.Stop;
+ label.FontColorOverride = Color.CornflowerBlue;
+ label.DefaultCursorShape = Control.CursorShape.Hand;
+
+ label.OnMouseEntered += _ => label.FontColorOverride = Color.LightSkyBlue;
+ label.OnMouseExited += _ => label.FontColorOverride = Color.CornflowerBlue;
+ label.OnKeyBindDown += args => OnKeybindDown(args, link);
+
+ control = label;
+ Control = label;
+ return true;
+ }
+
+ private void OnKeybindDown(GUIBoundKeyEventArgs args, string link)
+ {
+ if (args.Function != EngineKeyFunctions.UIClick)
+ return;
+
+ if (Control == null)
+ return;
+
+ var current = Control;
+ while (current != null)
+ {
+ current = current.Parent;
+
+ if (current is not ILinkClickHandler handler)
+ continue;
+ handler.HandleClick(link);
+ return;
+ }
+ Logger.Warning($"Warning! No valid ILinkClickHandler found.");
+ }
+}
+
+public interface ILinkClickHandler
+{
+ public void HandleClick(string link);
+}
diff --git a/Content.Client/Hands/Systems/HandsSystem.cs b/Content.Client/Hands/Systems/HandsSystem.cs
index 83465dea6f9f58..ba4bbb728bdb29 100644
--- a/Content.Client/Hands/Systems/HandsSystem.cs
+++ b/Content.Client/Hands/Systems/HandsSystem.cs
@@ -348,10 +348,12 @@ private void UpdateHandVisuals(EntityUid uid, EntityUid held, Hand hand, HandsCo
// In case no RSI is given, use the item's base RSI as a default. This cuts down on a lot of unnecessary yaml entries.
if (layerData.RsiPath == null
&& layerData.TexturePath == null
- && sprite[index].Rsi == null
- && TryComp(held, out SpriteComponent? clothingSprite))
+ && sprite[index].Rsi == null)
{
- sprite.LayerSetRSI(index, clothingSprite.BaseRSI);
+ if (TryComp(held, out var itemComponent) && itemComponent.RsiPath != null)
+ sprite.LayerSetRSI(index, itemComponent.RsiPath);
+ else if (TryComp(held, out SpriteComponent? clothingSprite))
+ sprite.LayerSetRSI(index, clothingSprite.BaseRSI);
}
sprite.LayerSetData(index, layerData);
diff --git a/Content.Client/IoC/ClientContentIoC.cs b/Content.Client/IoC/ClientContentIoC.cs
index e8c22ad028e228..b98b907cd03571 100644
--- a/Content.Client/IoC/ClientContentIoC.cs
+++ b/Content.Client/IoC/ClientContentIoC.cs
@@ -18,6 +18,7 @@
using Content.Shared.Administration.Logs;
using Content.Shared.Module;
using Content.Client.Guidebook;
+using Content.Client.Replay;
using Content.Shared.Administration.Managers;
namespace Content.Client.IoC
@@ -42,8 +43,9 @@ public static void Register()
IoCManager.Register();
IoCManager.Register();
IoCManager.Register();
- IoCManager.Register();
+ IoCManager.Register();
IoCManager.Register();
+ IoCManager.Register();
}
}
}
diff --git a/Content.Client/Labels/UI/HandLabelerBoundUserInterface.cs b/Content.Client/Labels/UI/HandLabelerBoundUserInterface.cs
index 40677e151237f2..352e90958b7e4e 100644
--- a/Content.Client/Labels/UI/HandLabelerBoundUserInterface.cs
+++ b/Content.Client/Labels/UI/HandLabelerBoundUserInterface.cs
@@ -26,14 +26,12 @@ protected override void Open()
_window.OpenCentered();
_window.OnClose += Close;
- _window.OnLabelEntered += OnLabelChanged;
-
+ _window.OnLabelChanged += OnLabelChanged;
}
private void OnLabelChanged(string newLabel)
{
SendMessage(new HandLabelerLabelChangedMessage(newLabel));
- Close();
}
///
diff --git a/Content.Client/Labels/UI/HandLabelerWindow.xaml.cs b/Content.Client/Labels/UI/HandLabelerWindow.xaml.cs
index b55c4c069ab441..7706c31f85aac8 100644
--- a/Content.Client/Labels/UI/HandLabelerWindow.xaml.cs
+++ b/Content.Client/Labels/UI/HandLabelerWindow.xaml.cs
@@ -1,7 +1,5 @@
-using System;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.AutoGenerated;
-using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
namespace Content.Client.Labels.UI
@@ -9,13 +7,14 @@ namespace Content.Client.Labels.UI
[GenerateTypedNameReferences]
public sealed partial class HandLabelerWindow : DefaultWindow
{
- public event Action? OnLabelEntered;
+ public event Action? OnLabelChanged;
public HandLabelerWindow()
{
RobustXamlLoader.Load(this);
- LabelLineEdit.OnTextEntered += e => OnLabelEntered?.Invoke(e.Text);
+ LabelLineEdit.OnTextEntered += e => OnLabelChanged?.Invoke(e.Text);
+ LabelLineEdit.OnFocusExit += e => OnLabelChanged?.Invoke(e.Text);
}
public void SetCurrentLabel(string label)
diff --git a/Content.Client/LateJoin/LateJoinGui.cs b/Content.Client/LateJoin/LateJoinGui.cs
index b2d824064452ae..0d9d72480a01c3 100644
--- a/Content.Client/LateJoin/LateJoinGui.cs
+++ b/Content.Client/LateJoin/LateJoinGui.cs
@@ -22,7 +22,7 @@ public sealed class LateJoinGui : DefaultWindow
[Dependency] private readonly IClientConsoleHost _consoleHost = default!;
[Dependency] private readonly IConfigurationManager _configManager = default!;
[Dependency] private readonly IEntitySystemManager _entitySystem = default!;
- [Dependency] private readonly PlayTimeTrackingManager _playTimeTracking = default!;
+ [Dependency] private readonly JobRequirementsManager _jobRequirements = default!;
public event Action<(EntityUid, string)> SelectedId;
@@ -54,6 +54,7 @@ public LateJoinGui()
Contents.AddChild(_base);
+ _jobRequirements.Updated += RebuildUI;
RebuildUI();
SelectedId += x =>
@@ -249,7 +250,7 @@ private void RebuildUI()
jobButton.OnPressed += _ => SelectedId.Invoke((id, jobButton.JobId));
- if (!_playTimeTracking.IsAllowed(prototype, out var reason))
+ if (!_jobRequirements.IsAllowed(prototype, out var reason))
{
jobButton.Disabled = true;
@@ -289,6 +290,7 @@ protected override void Dispose(bool disposing)
if (disposing)
{
+ _jobRequirements.Updated -= RebuildUI;
_gameTicker.LobbyJobsAvailableUpdated -= JobsAvailableUpdated;
_jobButtons.Clear();
_jobCategories.Clear();
diff --git a/Content.Client/Lathe/UI/LatheBoundUserInterface.cs b/Content.Client/Lathe/UI/LatheBoundUserInterface.cs
index 5c3e8ea0369bbb..03159b585936df 100644
--- a/Content.Client/Lathe/UI/LatheBoundUserInterface.cs
+++ b/Content.Client/Lathe/UI/LatheBoundUserInterface.cs
@@ -29,16 +29,15 @@ protected override void Open()
_menu.OnQueueButtonPressed += _ =>
{
- _queueMenu.OpenCenteredLeft();
+ if (_queueMenu.IsOpen)
+ _queueMenu.Close();
+ else
+ _queueMenu.OpenCenteredLeft();
};
_menu.OnServerListButtonPressed += _ =>
{
SendMessage(new ConsoleServerSelectionMessage());
};
- _menu.OnServerSyncButtonPressed += _ =>
- {
- SendMessage(new ConsoleServerSyncMessage());
- };
_menu.RecipeQueueAction += (recipe, amount) =>
{
SendMessage(new LatheQueueRecipeMessage(recipe, amount));
@@ -56,7 +55,7 @@ protected override void UpdateState(BoundUserInterfaceState state)
case LatheUpdateState msg:
if (_menu != null)
_menu.Recipes = msg.Recipes;
- _menu?.PopulateRecipes(Owner.Owner);
+ _menu?.PopulateRecipes(Lathe);
_menu?.PopulateMaterials(Lathe);
_queueMenu?.PopulateList(msg.Queue);
_queueMenu?.SetInfo(msg.CurrentlyProducing);
diff --git a/Content.Client/Lathe/UI/LatheMenu.xaml b/Content.Client/Lathe/UI/LatheMenu.xaml
index f5f6ecff8b0157..50f6fd99481f9f 100644
--- a/Content.Client/Lathe/UI/LatheMenu.xaml
+++ b/Content.Client/Lathe/UI/LatheMenu.xaml
@@ -1,85 +1,84 @@
+ HorizontalExpand="True">
-
-
+
+ HorizontalExpand="True">
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+ HorizontalExpand="True"/>
+
+
+
+
-
-
diff --git a/Content.Client/Lathe/UI/LatheMenu.xaml.cs b/Content.Client/Lathe/UI/LatheMenu.xaml.cs
index fd3319c434faeb..7247db647bedd8 100644
--- a/Content.Client/Lathe/UI/LatheMenu.xaml.cs
+++ b/Content.Client/Lathe/UI/LatheMenu.xaml.cs
@@ -1,4 +1,6 @@
-using System.Text;
+using System.Linq;
+using System.Text;
+using Content.Client.Stylesheets;
using Content.Shared.Lathe;
using Content.Shared.Materials;
using Content.Shared.Research.Prototypes;
@@ -21,7 +23,6 @@ public sealed partial class LatheMenu : DefaultWindow
public event Action? OnQueueButtonPressed;
public event Action? OnServerListButtonPressed;
- public event Action? OnServerSyncButtonPressed;
public event Action? RecipeQueueAction;
public List Recipes = new();
@@ -48,15 +49,13 @@ public LatheMenu(LatheBoundUserInterface owner)
QueueButton.OnPressed += a => OnQueueButtonPressed?.Invoke(a);
ServerListButton.OnPressed += a => OnServerListButtonPressed?.Invoke(a);
- //refresh the bui state
- ServerSyncButton.OnPressed += a => OnServerSyncButtonPressed?.Invoke(a);
-
if (_entityManager.TryGetComponent(owner.Lathe, out var latheComponent))
{
- if (latheComponent.DynamicRecipes == null)
+ if (!latheComponent.DynamicRecipes.Any())
{
ServerListButton.Visible = false;
- ServerSyncButton.Visible = false;
+ QueueButton.RemoveStyleClass(StyleBase.ButtonOpenRight);
+ //QueueButton.AddStyleClass(StyleBase.ButtonSquare);
}
}
}
@@ -132,7 +131,7 @@ public void PopulateRecipes(EntityUid lathe)
sb.Append('\n');
var adjustedAmount = SharedLatheSystem.AdjustMaterial(amount, prototype.ApplyMaterialDiscount, component.MaterialUseMultiplier);
-
+
sb.Append(adjustedAmount);
sb.Append(' ');
sb.Append(Loc.GetString(proto.Name));
diff --git a/Content.Client/Lathe/UI/RecipeControl.xaml b/Content.Client/Lathe/UI/RecipeControl.xaml
index 5ef53421854185..cacbf84ff723b2 100644
--- a/Content.Client/Lathe/UI/RecipeControl.xaml
+++ b/Content.Client/Lathe/UI/RecipeControl.xaml
@@ -2,11 +2,13 @@
[RegisterComponent]
-public sealed class PDABorderColorComponent : Component
+public sealed class PdaBorderColorComponent : Component
{
[DataField("borderColor", required: true)]
public string? BorderColor;
diff --git a/Content.Client/PDA/PDABoundUserInterface.cs b/Content.Client/PDA/PdaBoundUserInterface.cs
similarity index 79%
rename from Content.Client/PDA/PDABoundUserInterface.cs
rename to Content.Client/PDA/PdaBoundUserInterface.cs
index 4553b23fd3feac..6e569dcf13f0ed 100644
--- a/Content.Client/PDA/PDABoundUserInterface.cs
+++ b/Content.Client/PDA/PdaBoundUserInterface.cs
@@ -12,14 +12,14 @@
namespace Content.Client.PDA
{
[UsedImplicitly]
- public sealed class PDABoundUserInterface : CartridgeLoaderBoundUserInterface
+ public sealed class PdaBoundUserInterface : CartridgeLoaderBoundUserInterface
{
[Dependency] private readonly IEntityManager? _entityManager = default!;
[Dependency] private readonly IConfigurationManager _configManager = default!;
- private PDAMenu? _menu;
+ private PdaMenu? _menu;
- public PDABoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey)
+ public PdaBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey)
{
IoCManager.InjectDependencies(this);
}
@@ -27,13 +27,13 @@ public PDABoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : b
protected override void Open()
{
base.Open();
- SendMessage(new PDARequestUpdateInterfaceMessage());
- _menu = new PDAMenu();
+ SendMessage(new PdaRequestUpdateInterfaceMessage());
+ _menu = new PdaMenu();
_menu.OpenCenteredLeft();
_menu.OnClose += Close;
_menu.FlashLightToggleButton.OnToggled += _ =>
{
- SendMessage(new PDAToggleFlashlightMessage());
+ SendMessage(new PdaToggleFlashlightMessage());
};
if (_configManager.GetCVar(CCVars.CrewManifestUnsecure))
@@ -47,32 +47,32 @@ protected override void Open()
_menu.EjectIdButton.OnPressed += _ =>
{
- SendMessage(new ItemSlotButtonPressedEvent(PDAComponent.PDAIdSlotId));
+ SendMessage(new ItemSlotButtonPressedEvent(PdaComponent.PdaIdSlotId));
};
_menu.EjectPenButton.OnPressed += _ =>
{
- SendMessage(new ItemSlotButtonPressedEvent(PDAComponent.PDAPenSlotId));
+ SendMessage(new ItemSlotButtonPressedEvent(PdaComponent.PdaPenSlotId));
};
_menu.ActivateMusicButton.OnPressed += _ =>
{
- SendMessage(new PDAShowMusicMessage());
+ SendMessage(new PdaShowMusicMessage());
};
_menu.AccessRingtoneButton.OnPressed += _ =>
{
- SendMessage(new PDAShowRingtoneMessage());
+ SendMessage(new PdaShowRingtoneMessage());
};
_menu.ShowUplinkButton.OnPressed += _ =>
{
- SendMessage(new PDAShowUplinkMessage());
+ SendMessage(new PdaShowUplinkMessage());
};
_menu.LockUplinkButton.OnPressed += _ =>
{
- SendMessage(new PDALockUplinkMessage());
+ SendMessage(new PdaLockUplinkMessage());
};
_menu.OnProgramItemPressed += ActivateCartridge;
@@ -93,7 +93,7 @@ protected override void UpdateState(BoundUserInterfaceState state)
{
base.UpdateState(state);
- if (state is not PDAUpdateState updateState)
+ if (state is not PdaUpdateState updateState)
return;
_menu?.UpdateState(updateState);
@@ -130,9 +130,9 @@ protected override void Dispose(bool disposing)
_menu?.Dispose();
}
- private PDABorderColorComponent? GetBorderColorComponent()
+ private PdaBorderColorComponent? GetBorderColorComponent()
{
- return _entityManager?.GetComponentOrNull(Owner.Owner);
+ return _entityManager?.GetComponentOrNull(Owner.Owner);
}
}
}
diff --git a/Content.Client/PDA/PDAMenu.xaml b/Content.Client/PDA/PdaMenu.xaml
similarity index 81%
rename from Content.Client/PDA/PDAMenu.xaml
rename to Content.Client/PDA/PdaMenu.xaml
index 0b61cde932b600..a06032d6d2b6ef 100644
--- a/Content.Client/PDA/PDAMenu.xaml
+++ b/Content.Client/PDA/PdaMenu.xaml
@@ -1,25 +1,25 @@
-
-
-
-
+
+
+
-
-
+
-
-
-
-
+
+
+
-
-
-
-
-
@@ -88,8 +88,8 @@
-
-
+
+
-
+
diff --git a/Content.Client/PDA/PDAMenu.xaml.cs b/Content.Client/PDA/PdaMenu.xaml.cs
similarity index 88%
rename from Content.Client/PDA/PDAMenu.xaml.cs
rename to Content.Client/PDA/PdaMenu.xaml.cs
index 4b80795cc1b7ce..e19c6710a2cdd2 100644
--- a/Content.Client/PDA/PDAMenu.xaml.cs
+++ b/Content.Client/PDA/PdaMenu.xaml.cs
@@ -13,7 +13,7 @@
namespace Content.Client.PDA
{
[GenerateTypedNameReferences]
- public sealed partial class PDAMenu : PDAWindow
+ public sealed partial class PdaMenu : PdaWindow
{
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly IEntitySystemManager _entitySystem = default!;
@@ -29,7 +29,7 @@ public sealed partial class PDAMenu : PDAWindow
public event Action? OnProgramItemPressed;
public event Action? OnUninstallButtonPressed;
public event Action? OnInstallButtonPressed;
- public PDAMenu()
+ public PdaMenu()
{
IoCManager.InjectDependencies(this);
_gameTicker = _entitySystem.GetEntitySystem();
@@ -37,11 +37,11 @@ public PDAMenu()
ViewContainer.OnChildAdded += control => control.Visible = false;
- HomeButton.IconTexture = new SpriteSpecifier.Texture(new ("/Textures/Interface/home.png"));
- FlashLightToggleButton.IconTexture = new SpriteSpecifier.Texture(new ("/Textures/Interface/light.png"));
- EjectPenButton.IconTexture = new SpriteSpecifier.Texture(new ("/Textures/Interface/pencil.png"));
- EjectIdButton.IconTexture = new SpriteSpecifier.Texture(new ("/Textures/Interface/eject.png"));
- ProgramCloseButton.IconTexture = new SpriteSpecifier.Texture(new ("/Textures/Interface/Nano/cross.svg.png"));
+ HomeButton.IconTexture = new SpriteSpecifier.Texture(new("/Textures/Interface/home.png"));
+ FlashLightToggleButton.IconTexture = new SpriteSpecifier.Texture(new("/Textures/Interface/light.png"));
+ EjectPenButton.IconTexture = new SpriteSpecifier.Texture(new("/Textures/Interface/pencil.png"));
+ EjectIdButton.IconTexture = new SpriteSpecifier.Texture(new("/Textures/Interface/eject.png"));
+ ProgramCloseButton.IconTexture = new SpriteSpecifier.Texture(new("/Textures/Interface/Nano/cross.svg.png"));
HomeButton.OnPressed += _ => ToHomeScreen();
@@ -88,22 +88,22 @@ public PDAMenu()
ToHomeScreen();
}
- public void UpdateState(PDAUpdateState state)
+ public void UpdateState(PdaUpdateState state)
{
FlashLightToggleButton.IsActive = state.FlashlightEnabled;
- if (state.PDAOwnerInfo.ActualOwnerName != null)
+ if (state.PdaOwnerInfo.ActualOwnerName != null)
{
PdaOwnerLabel.SetMarkup(Loc.GetString("comp-pda-ui-owner",
- ("actualOwnerName", state.PDAOwnerInfo.ActualOwnerName)));
+ ("actualOwnerName", state.PdaOwnerInfo.ActualOwnerName)));
}
- if (state.PDAOwnerInfo.IdOwner != null || state.PDAOwnerInfo.JobTitle != null)
+ if (state.PdaOwnerInfo.IdOwner != null || state.PdaOwnerInfo.JobTitle != null)
{
IdInfoLabel.SetMarkup(Loc.GetString("comp-pda-ui",
- ("owner",state.PDAOwnerInfo.IdOwner ?? Loc.GetString("comp-pda-ui-unknown")),
- ("jobTitle",state.PDAOwnerInfo.JobTitle ?? Loc.GetString("comp-pda-ui-unassigned"))));
+ ("owner", state.PdaOwnerInfo.IdOwner ?? Loc.GetString("comp-pda-ui-unknown")),
+ ("jobTitle", state.PdaOwnerInfo.JobTitle ?? Loc.GetString("comp-pda-ui-unassigned"))));
}
else
{
@@ -111,15 +111,15 @@ public void UpdateState(PDAUpdateState state)
}
StationNameLabel.SetMarkup(Loc.GetString("comp-pda-ui-station",
- ("station",state.StationName ?? Loc.GetString("comp-pda-ui-unknown"))));
+ ("station", state.StationName ?? Loc.GetString("comp-pda-ui-unknown"))));
var stationTime = _gameTiming.CurTime.Subtract(_gameTicker.RoundStartTimeSpan);
StationTimeLabel.SetMarkup(Loc.GetString("comp-pda-ui-station-time",
("time", stationTime.ToString("hh\\:mm\\:ss"))));
- var alertLevel = state.PDAOwnerInfo.StationAlertLevel;
- var alertColor = state.PDAOwnerInfo.StationAlertColor;
+ var alertLevel = state.PdaOwnerInfo.StationAlertLevel;
+ var alertColor = state.PdaOwnerInfo.StationAlertColor;
var alertLevelKey = alertLevel != null ? $"alert-level-{alertLevel}" : "alert-level-unknown";
StationAlertLevelLabel.SetMarkup(Loc.GetString(
@@ -135,7 +135,7 @@ public void UpdateState(PDAUpdateState state)
AddressLabel.Text = state.Address?.ToUpper() ?? " - ";
- EjectIdButton.IsActive = state.PDAOwnerInfo.IdOwner != null || state.PDAOwnerInfo.JobTitle != null;
+ EjectIdButton.IsActive = state.PdaOwnerInfo.IdOwner != null || state.PdaOwnerInfo.JobTitle != null;
EjectPenButton.IsActive = state.HasPen;
ActivateMusicButton.Visible = state.CanPlayMusic;
ShowUplinkButton.Visible = state.HasUplink;
@@ -172,7 +172,7 @@ public void UpdateAvailablePrograms(List<(EntityUid, CartridgeComponent)> progra
ProgramList.AddChild(row);
}
- var item = new PDAProgramItem();
+ var item = new PdaProgramItem();
if (component.Icon is not null)
item.Icon.SetFromSpriteSpecifier(component.Icon);
@@ -263,7 +263,7 @@ public void ChangeView(int view)
_currentView = view;
}
- private BoxContainer CreateProgramListRow()
+ private static BoxContainer CreateProgramListRow()
{
return new BoxContainer()
{
diff --git a/Content.Client/PDA/PDANavigationButton.xaml b/Content.Client/PDA/PdaNavigationButton.xaml
similarity index 78%
rename from Content.Client/PDA/PDANavigationButton.xaml
rename to Content.Client/PDA/PdaNavigationButton.xaml
index 94badd9451595f..03cf68f0ec0b76 100644
--- a/Content.Client/PDA/PDANavigationButton.xaml
+++ b/Content.Client/PDA/PdaNavigationButton.xaml
@@ -1,5 +1,5 @@
-
+
-
+
diff --git a/Content.Client/PDA/PDANavigationButton.xaml.cs b/Content.Client/PDA/PdaNavigationButton.xaml.cs
similarity index 96%
rename from Content.Client/PDA/PDANavigationButton.xaml.cs
rename to Content.Client/PDA/PdaNavigationButton.xaml.cs
index 83907717c32e0d..3e44829411f120 100644
--- a/Content.Client/PDA/PDANavigationButton.xaml.cs
+++ b/Content.Client/PDA/PdaNavigationButton.xaml.cs
@@ -7,7 +7,7 @@
namespace Content.Client.PDA;
[GenerateTypedNameReferences]
-public sealed partial class PDANavigationButton : ContainerButton
+public sealed partial class PdaNavigationButton : ContainerButton
{
private bool _isCurrent;
@@ -100,7 +100,7 @@ public bool IsActive
}
}
- public PDANavigationButton()
+ public PdaNavigationButton()
{
RobustXamlLoader.Load(this);
Background.PanelOverride = _styleBox;
diff --git a/Content.Client/PDA/PDAProgramItem.xaml b/Content.Client/PDA/PdaProgramItem.xaml
similarity index 92%
rename from Content.Client/PDA/PDAProgramItem.xaml
rename to Content.Client/PDA/PdaProgramItem.xaml
index d47df17e7f4261..9f9fba938f0ba3 100644
--- a/Content.Client/PDA/PDAProgramItem.xaml
+++ b/Content.Client/PDA/PdaProgramItem.xaml
@@ -1,4 +1,4 @@
-
@@ -14,4 +14,4 @@
-
+
diff --git a/Content.Client/PDA/PDAProgramItem.xaml.cs b/Content.Client/PDA/PdaProgramItem.xaml.cs
similarity index 86%
rename from Content.Client/PDA/PDAProgramItem.xaml.cs
rename to Content.Client/PDA/PdaProgramItem.xaml.cs
index 5cc16da497b420..9709f5706b72d2 100644
--- a/Content.Client/PDA/PDAProgramItem.xaml.cs
+++ b/Content.Client/PDA/PdaProgramItem.xaml.cs
@@ -1,14 +1,12 @@
using Robust.Client.AutoGenerated;
using Robust.Client.Graphics;
-using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
-using Robust.Shared.Input;
namespace Content.Client.PDA;
[GenerateTypedNameReferences]
-public sealed partial class PDAProgramItem : ContainerButton
+public sealed partial class PdaProgramItem : ContainerButton
{
public const string StylePropertyBgColor = "backgroundColor";
public const string NormalBgColor = "#313138";
@@ -25,7 +23,7 @@ public Color BackgroundColor
set => _styleBox.BackgroundColor = value;
}
- public PDAProgramItem()
+ public PdaProgramItem()
{
RobustXamlLoader.Load(this);
Panel.PanelOverride = _styleBox;
diff --git a/Content.Client/PDA/PDASettingsButton.xaml b/Content.Client/PDA/PdaSettingsButton.xaml
similarity index 82%
rename from Content.Client/PDA/PDASettingsButton.xaml
rename to Content.Client/PDA/PdaSettingsButton.xaml
index 39bd560324c7b9..213616f6c9967a 100644
--- a/Content.Client/PDA/PDASettingsButton.xaml
+++ b/Content.Client/PDA/PdaSettingsButton.xaml
@@ -1,4 +1,4 @@
-
-
+
diff --git a/Content.Client/PDA/PDASettingsButton.xaml.cs b/Content.Client/PDA/PdaSettingsButton.xaml.cs
similarity index 94%
rename from Content.Client/PDA/PDASettingsButton.xaml.cs
rename to Content.Client/PDA/PdaSettingsButton.xaml.cs
index 0fb4ffb5964a91..f460f60200a2f0 100644
--- a/Content.Client/PDA/PDASettingsButton.xaml.cs
+++ b/Content.Client/PDA/PdaSettingsButton.xaml.cs
@@ -6,7 +6,7 @@
namespace Content.Client.PDA;
[GenerateTypedNameReferences]
-public sealed partial class PDASettingsButton : ContainerButton
+public sealed partial class PdaSettingsButton : ContainerButton
{
public const string StylePropertyFgColor = "foregroundColor";
public const string StylePropertyBgColor = "backgroundColor";
@@ -42,14 +42,14 @@ public Color? ForegroundColor
{
get => OptionName.FontColorOverride;
- set
+ set
{
OptionName.FontColorOverride = value;
OptionDescription.FontColorOverride = value;
}
}
- public PDASettingsButton()
+ public PdaSettingsButton()
{
RobustXamlLoader.Load(this);
Panel.PanelOverride = _styleBox;
diff --git a/Content.Client/PDA/PdaSystem.cs b/Content.Client/PDA/PdaSystem.cs
new file mode 100644
index 00000000000000..00a12ae2e696d6
--- /dev/null
+++ b/Content.Client/PDA/PdaSystem.cs
@@ -0,0 +1,48 @@
+using Content.Shared.PDA;
+using Content.Shared.Light;
+using Robust.Client.GameObjects;
+
+namespace Content.Client.PDA;
+
+public sealed class PdaSystem : SharedPdaSystem
+{
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnAppearanceChange);
+ }
+
+ private void OnAppearanceChange(EntityUid uid, PdaComponent component, ref AppearanceChangeEvent args)
+ {
+ if (args.Sprite == null)
+ return;
+
+ if (Appearance.TryGetData(uid, UnpoweredFlashlightVisuals.LightOn, out var isFlashlightOn, args.Component))
+ args.Sprite.LayerSetVisible(PdaVisualLayers.Flashlight, isFlashlightOn);
+
+ if (Appearance.TryGetData(uid, PdaVisuals.IdCardInserted, out var isCardInserted, args.Component))
+ args.Sprite.LayerSetVisible(PdaVisualLayers.IdLight, isCardInserted);
+ }
+
+ protected override void OnComponentInit(EntityUid uid, PdaComponent component, ComponentInit args)
+ {
+ base.OnComponentInit(uid, component, args);
+
+ if (!TryComp(uid, out var sprite))
+ return;
+
+ if (component.State != null)
+ sprite.LayerSetState(PdaVisualLayers.Base, component.State);
+
+ sprite.LayerSetVisible(PdaVisualLayers.Flashlight, component.FlashlightOn);
+ sprite.LayerSetVisible(PdaVisualLayers.IdLight, component.IdSlot.StartingItem != null);
+ }
+
+ public enum PdaVisualLayers : byte
+ {
+ Base,
+ Flashlight,
+ IdLight
+ }
+}
diff --git a/Content.Client/PDA/PDAWindow.xaml b/Content.Client/PDA/PdaWindow.xaml
similarity index 77%
rename from Content.Client/PDA/PDAWindow.xaml
rename to Content.Client/PDA/PdaWindow.xaml
index fd6ec57ebeadfa..0b0898cc3ad24f 100644
--- a/Content.Client/PDA/PDAWindow.xaml
+++ b/Content.Client/PDA/PdaWindow.xaml
@@ -1,11 +1,11 @@
-
-
+
-
-
-
+
+
+
@@ -16,16 +16,16 @@
-
+
-
-
+
+
-
+
-
+
diff --git a/Content.Client/PDA/PDAWindow.xaml.cs b/Content.Client/PDA/PdaWindow.xaml.cs
similarity index 82%
rename from Content.Client/PDA/PDAWindow.xaml.cs
rename to Content.Client/PDA/PdaWindow.xaml.cs
index 8b39d744cdcc4a..cc4d622751c9dc 100644
--- a/Content.Client/PDA/PDAWindow.xaml.cs
+++ b/Content.Client/PDA/PdaWindow.xaml.cs
@@ -6,7 +6,7 @@ namespace Content.Client.PDA;
[Virtual]
[GenerateTypedNameReferences]
-public partial class PDAWindow : BaseWindow
+public partial class PdaWindow : BaseWindow
{
public string? BorderColor
@@ -22,7 +22,7 @@ public string? AccentHColor
set
{
- AccentH.ModulateSelfOverride = Color. FromHex(value, Color.White);
+ AccentH.ModulateSelfOverride = Color.FromHex(value, Color.White);
AccentH.Visible = value != null;
}
}
@@ -33,12 +33,12 @@ public string? AccentVColor
set
{
- AccentV.ModulateSelfOverride = Color. FromHex(value, Color.White);
+ AccentV.ModulateSelfOverride = Color.FromHex(value, Color.White);
AccentV.Visible = value != null;
}
}
- public PDAWindow()
+ public PdaWindow()
{
RobustXamlLoader.Load(this);
diff --git a/Content.Client/PDA/Ringer/RingtoneMenu.xaml.cs b/Content.Client/PDA/Ringer/RingtoneMenu.xaml.cs
index cb638efcd9ea6e..1363fa66d16814 100644
--- a/Content.Client/PDA/Ringer/RingtoneMenu.xaml.cs
+++ b/Content.Client/PDA/Ringer/RingtoneMenu.xaml.cs
@@ -18,20 +18,38 @@ public RingtoneMenu()
RingerNoteInputs = new[] { RingerNoteOneInput, RingerNoteTwoInput, RingerNoteThreeInput, RingerNoteFourInput };
- for (int i = 0; i < RingerNoteInputs.Length; i++)
+ for (var i = 0; i < RingerNoteInputs.Length; ++i)
{
var input = RingerNoteInputs[i];
- int index = i;
- input.OnTextChanged += _ => //Prevents unauthorized characters from being entered into the LineEdit
+ var index = i;
+ var foo = () => // Prevents unauthorized characters from being entered into the LineEdit
{
input.Text = input.Text.ToUpper();
if (!IsNote(input.Text))
+ {
input.Text = PreviousNoteInputs[index];
+ }
else
PreviousNoteInputs[index] = input.Text;
- input.CursorPosition = input.Text.Length; //Resets caret position to the end of the typed input
+ input.RemoveStyleClass("Caution");
+ };
+
+ input.OnFocusExit += _ => foo();
+ input.OnTextEntered += _ =>
+ {
+ foo();
+ input.CursorPosition = input.Text.Length; // Resets caret position to the end of the typed input
+ };
+ input.OnTextChanged += _ =>
+ {
+ input.Text = input.Text.ToUpper();
+
+ if (!IsNote(input.Text))
+ input.AddStyleClass("Caution");
+ else
+ input.RemoveStyleClass("Caution");
};
}
}
diff --git a/Content.Client/ParticleAccelerator/UI/ParticleAcceleratorBoundUserInterface.cs b/Content.Client/ParticleAccelerator/UI/ParticleAcceleratorBoundUserInterface.cs
index c41f67173d986a..734def36535a50 100644
--- a/Content.Client/ParticleAccelerator/UI/ParticleAcceleratorBoundUserInterface.cs
+++ b/Content.Client/ParticleAccelerator/UI/ParticleAcceleratorBoundUserInterface.cs
@@ -1,6 +1,5 @@
using Content.Shared.Singularity.Components;
using Robust.Client.GameObjects;
-using Robust.Shared.GameObjects;
namespace Content.Client.ParticleAccelerator.UI
{
diff --git a/Content.Client/ParticleAccelerator/UI/ParticleAcceleratorControlMenu.cs b/Content.Client/ParticleAccelerator/UI/ParticleAcceleratorControlMenu.cs
index 0b916c76001671..0df9363cb8685e 100644
--- a/Content.Client/ParticleAccelerator/UI/ParticleAcceleratorControlMenu.cs
+++ b/Content.Client/ParticleAccelerator/UI/ParticleAcceleratorControlMenu.cs
@@ -19,10 +19,10 @@ public sealed class ParticleAcceleratorControlMenu : BaseWindow
{
private readonly ShaderInstance _greyScaleShader;
- private readonly ParticleAcceleratorBoundUserInterface Owner;
+ private readonly ParticleAcceleratorBoundUserInterface _owner;
private readonly Label _drawLabel;
- private readonly NoiseGenerator _drawNoiseGenerator;
+ private readonly FastNoiseLite _drawNoiseGenerator;
private readonly Button _onButton;
private readonly Button _offButton;
private readonly Button _scanButton;
@@ -36,9 +36,9 @@ public sealed class ParticleAcceleratorControlMenu : BaseWindow
private readonly PASegmentControl _fuelChamberTexture;
private readonly PASegmentControl _controlBoxTexture;
private readonly PASegmentControl _powerBoxTexture;
- private readonly PASegmentControl _emitterCenterTexture;
- private readonly PASegmentControl _emitterRightTexture;
- private readonly PASegmentControl _emitterLeftTexture;
+ private readonly PASegmentControl _emitterForeTexture;
+ private readonly PASegmentControl _emitterPortTexture;
+ private readonly PASegmentControl _emitterStarboardTexture;
private float _time;
private int _lastDraw;
@@ -47,14 +47,16 @@ public sealed class ParticleAcceleratorControlMenu : BaseWindow
private bool _blockSpinBox;
private bool _assembled;
private bool _shouldContinueAnimating;
+ private int _maxStrength = 3;
public ParticleAcceleratorControlMenu(ParticleAcceleratorBoundUserInterface owner)
{
SetSize = (400, 320);
_greyScaleShader = IoCManager.Resolve().Index("Greyscale").Instance();
- Owner = owner;
- _drawNoiseGenerator = new NoiseGenerator(NoiseGenerator.NoiseType.Fbm);
+ _owner = owner;
+ _drawNoiseGenerator = new();
+ _drawNoiseGenerator.SetFractalType(FastNoiseLite.FractalType.FBm);
_drawNoiseGenerator.SetFrequency(0.5f);
var resourceCache = IoCManager.Resolve();
@@ -98,7 +100,7 @@ public ParticleAcceleratorControlMenu(ParticleAcceleratorBoundUserInterface owne
MouseFilter = MouseFilterMode.Pass
});
- _stateSpinBox = new SpinBox {Value = 0, IsValid = StrengthSpinBoxValid,};
+ _stateSpinBox = new SpinBox { Value = 0, IsValid = StrengthSpinBoxValid };
_stateSpinBox.InitDefaultButtons();
_stateSpinBox.ValueChanged += PowerStateChanged;
_stateSpinBox.LineEditDisabled = true;
@@ -107,7 +109,7 @@ public ParticleAcceleratorControlMenu(ParticleAcceleratorBoundUserInterface owne
{
ToggleMode = false,
Text = Loc.GetString("particle-accelerator-control-menu-off-button"),
- StyleClasses = {StyleBase.ButtonOpenRight},
+ StyleClasses = { StyleBase.ButtonOpenRight },
};
_offButton.OnPressed += args => owner.SendEnableMessage(false);
@@ -115,13 +117,13 @@ public ParticleAcceleratorControlMenu(ParticleAcceleratorBoundUserInterface owne
{
ToggleMode = false,
Text = Loc.GetString("particle-accelerator-control-menu-on-button"),
- StyleClasses = {StyleBase.ButtonOpenLeft},
+ StyleClasses = { StyleBase.ButtonOpenLeft },
};
_onButton.OnPressed += args => owner.SendEnableMessage(true);
var closeButton = new TextureButton
{
- StyleClasses = {"windowCloseButton"},
+ StyleClasses = { "windowCloseButton" },
HorizontalAlignment = HAlignment.Right,
Margin = new Thickness(0, 0, 8, 0)
};
@@ -130,7 +132,7 @@ public ParticleAcceleratorControlMenu(ParticleAcceleratorBoundUserInterface owne
var serviceManual = new Label
{
HorizontalAlignment = HAlignment.Center,
- StyleClasses = {StyleBase.StyleClassLabelSubText},
+ StyleClasses = { StyleBase.StyleClassLabelSubText },
Text = Loc.GetString("particle-accelerator-control-menu-service-manual-reference")
};
_drawLabel = new Label();
@@ -227,7 +229,8 @@ public ParticleAcceleratorControlMenu(ParticleAcceleratorBoundUserInterface owne
Align = Label.AlignMode.Center
},
serviceManual
- }
+ },
+ Visible = false,
}),
}
},
@@ -267,9 +270,9 @@ public ParticleAcceleratorControlMenu(ParticleAcceleratorBoundUserInterface owne
new Control {MinSize = imgSize},
(_powerBoxTexture = Segment("power_box")),
new Control {MinSize = imgSize},
- (_emitterLeftTexture = Segment("emitter_left")),
- (_emitterCenterTexture = Segment("emitter_center")),
- (_emitterRightTexture = Segment("emitter_right")),
+ (_emitterStarboardTexture = Segment("emitter_starboard")),
+ (_emitterForeTexture = Segment("emitter_fore")),
+ (_emitterPortTexture = Segment("emitter_port")),
}
}
}
@@ -312,7 +315,7 @@ public ParticleAcceleratorControlMenu(ParticleAcceleratorBoundUserInterface owne
}
});
- _scanButton.OnPressed += args => Owner.SendScanPartsMessage();
+ _scanButton.OnPressed += args => _owner.SendScanPartsMessage();
_alarmControl.AnimationCompleted += s =>
{
@@ -336,7 +339,7 @@ PASegmentControl Segment(string name)
private bool StrengthSpinBoxValid(int n)
{
- return (n >= 0 && n <= 3 && !_blockSpinBox);
+ return n >= 0 && n <= _maxStrength && !_blockSpinBox;
}
private void PowerStateChanged(ValueChangedEventArgs e)
@@ -356,16 +359,15 @@ private void PowerStateChanged(ValueChangedEventArgs e)
case 3:
newState = ParticleAcceleratorPowerState.Level2;
break;
- // They can't reach this level anyway and I just want to fix the bugginess for now.
- //case 4:
- // newState = ParticleAcceleratorPowerState.Level3;
- // break;
+ case 4:
+ newState = ParticleAcceleratorPowerState.Level3;
+ break;
default:
return;
}
_stateSpinBox.SetButtonDisabled(true);
- Owner.SendPowerStateMessage(newState);
+ _owner.SendPowerStateMessage(newState);
}
protected override DragMode GetDragModeFor(Vector2 relativeMousePos)
@@ -402,14 +404,15 @@ private void UpdatePowerState(ParticleAcceleratorPowerState state, bool enabled,
});
- _shouldContinueAnimating = false;
- _alarmControl.StopAnimation("warningAnim");
- _alarmControl.Visible = false;
- if (maxState == ParticleAcceleratorPowerState.Level3 && enabled && assembled)
+ _maxStrength = maxState == ParticleAcceleratorPowerState.Level3 ? 4 : 3;
+ if (_maxStrength > 3 && enabled && assembled)
{
_shouldContinueAnimating = true;
- _alarmControl.PlayAnimation(_alarmControlAnimation, "warningAnim");
+ if (!_alarmControl.Visible)
+ _alarmControl.PlayAnimation(_alarmControlAnimation, "warningAnim");
}
+ else
+ _shouldContinueAnimating = false;
}
private void UpdateUI(bool assembled, bool blocked, bool enabled, bool powerBlock)
@@ -430,12 +433,12 @@ private void UpdateUI(bool assembled, bool blocked, bool enabled, bool powerBloc
private void UpdatePreview(ParticleAcceleratorUIState updateMessage)
{
_endCapTexture.SetPowerState(updateMessage, updateMessage.EndCapExists);
- _fuelChamberTexture.SetPowerState(updateMessage, updateMessage.FuelChamberExists);
_controlBoxTexture.SetPowerState(updateMessage, true);
+ _fuelChamberTexture.SetPowerState(updateMessage, updateMessage.FuelChamberExists);
_powerBoxTexture.SetPowerState(updateMessage, updateMessage.PowerBoxExists);
- _emitterCenterTexture.SetPowerState(updateMessage, updateMessage.EmitterCenterExists);
- _emitterLeftTexture.SetPowerState(updateMessage, updateMessage.EmitterLeftExists);
- _emitterRightTexture.SetPowerState(updateMessage, updateMessage.EmitterRightExists);
+ _emitterStarboardTexture.SetPowerState(updateMessage, updateMessage.EmitterStarboardExists);
+ _emitterForeTexture.SetPowerState(updateMessage, updateMessage.EmitterForeExists);
+ _emitterPortTexture.SetPowerState(updateMessage, updateMessage.EmitterPortExists);
}
protected override void FrameUpdate(FrameEventArgs args)
@@ -453,7 +456,7 @@ protected override void FrameUpdate(FrameEventArgs args)
var watts = 0;
if (_lastDraw != 0)
{
- var val = _drawNoiseGenerator.GetNoise(_time);
+ var val = _drawNoiseGenerator.GetNoise(_time, 0f);
watts = (int) (_lastDraw + val * 5);
}
@@ -476,7 +479,7 @@ public PASegmentControl(ParticleAcceleratorControlMenu menu, IResourceCache cach
_baseState = name;
_rsi = cache.GetResource($"/Textures/Structures/Power/Generation/PA/{name}.rsi").RSI;
- AddChild(_base = new TextureRect {Texture = _rsi[$"completed"].Frame0});
+ AddChild(_base = new TextureRect { Texture = _rsi[$"completed"].Frame0 });
AddChild(_unlit = new TextureRect());
MinSize = _rsi.Size;
}
diff --git a/Content.Client/Physics/JointVisualsOverlay.cs b/Content.Client/Physics/JointVisualsOverlay.cs
new file mode 100644
index 00000000000000..5362a848d54050
--- /dev/null
+++ b/Content.Client/Physics/JointVisualsOverlay.cs
@@ -0,0 +1,74 @@
+using Content.Shared.Physics;
+using Robust.Client.GameObjects;
+using Robust.Client.Graphics;
+using Robust.Shared.Enums;
+using Robust.Shared.Physics;
+using Robust.Shared.Physics.Dynamics.Joints;
+
+namespace Content.Client.Physics;
+
+///
+/// Draws a texture on top of a joint.
+///
+public sealed class JointVisualsOverlay : Overlay
+{
+ public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowFOV;
+
+ private IEntityManager _entManager;
+
+ private HashSet _drawn = new();
+
+ public JointVisualsOverlay(IEntityManager entManager)
+ {
+ _entManager = entManager;
+ }
+
+ protected override void Draw(in OverlayDrawArgs args)
+ {
+ _drawn.Clear();
+ var worldHandle = args.WorldHandle;
+
+ var spriteSystem = _entManager.System();
+ var xformSystem = _entManager.System();
+ var joints = _entManager.EntityQueryEnumerator();
+ var xformQuery = _entManager.GetEntityQuery();
+
+ while (joints.MoveNext(out var visuals, out var xform))
+ {
+ if (xform.MapID != args.MapId)
+ continue;
+
+ var other = visuals.Target;
+
+ if (!xformQuery.TryGetComponent(other, out var otherXform))
+ continue;
+
+ if (xform.MapID != otherXform.MapID)
+ continue;
+
+ var texture = spriteSystem.Frame0(visuals.Sprite);
+ var width = texture.Width / (float) EyeManager.PixelsPerMeter;
+
+ var coordsA = xform.Coordinates;
+ var coordsB = otherXform.Coordinates;
+
+ var rotA = xform.LocalRotation;
+ var rotB = otherXform.LocalRotation;
+
+ coordsA = coordsA.Offset(rotA.RotateVec(visuals.OffsetA));
+ coordsB = coordsB.Offset(rotB.RotateVec(visuals.OffsetB));
+
+ var posA = coordsA.ToMapPos(_entManager, xformSystem);
+ var posB = coordsB.ToMapPos(_entManager, xformSystem);
+ var diff = (posB - posA);
+ var length = diff.Length;
+
+ var midPoint = diff / 2f + posA;
+ var angle = (posB - posA).ToWorldAngle();
+ var box = new Box2(-width / 2f, -length / 2f, width / 2f, length / 2f);
+ var rotate = new Box2Rotated(box.Translated(midPoint), angle, midPoint);
+
+ worldHandle.DrawTextureRect(texture, rotate);
+ }
+ }
+}
diff --git a/Content.Client/Physics/JointVisualsSystem.cs b/Content.Client/Physics/JointVisualsSystem.cs
new file mode 100644
index 00000000000000..2e4e2e4c616eba
--- /dev/null
+++ b/Content.Client/Physics/JointVisualsSystem.cs
@@ -0,0 +1,20 @@
+using Robust.Client.Graphics;
+
+namespace Content.Client.Physics;
+
+public sealed class JointVisualsSystem : EntitySystem
+{
+ [Dependency] private readonly IOverlayManager _overlay = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+ _overlay.AddOverlay(new JointVisualsOverlay(EntityManager));
+ }
+
+ public override void Shutdown()
+ {
+ base.Shutdown();
+ _overlay.RemoveOverlay();
+ }
+}
diff --git a/Content.Client/Pinpointer/UI/NavMapControl.cs b/Content.Client/Pinpointer/UI/NavMapControl.cs
index 723030b7688fe4..2e63ef0eceef73 100644
--- a/Content.Client/Pinpointer/UI/NavMapControl.cs
+++ b/Content.Client/Pinpointer/UI/NavMapControl.cs
@@ -95,6 +95,15 @@ public NavMapControl() : base(8f, 128f, 48f)
};
}
+ public void CenterToCoordinates(EntityCoordinates coordinates)
+ {
+ if (_entManager.TryGetComponent(MapUid, out var physics))
+ {
+ _offset = new Vector2(coordinates.X, coordinates.Y) - physics.LocalCenter;
+ }
+ _recenter.Disabled = false;
+ }
+
protected override void KeyBindDown(GUIBoundKeyEventArgs args)
{
base.KeyBindDown(args);
diff --git a/Content.Client/Players/PlayTimeTracking/PlayTimeTrackingManager.cs b/Content.Client/Players/PlayTimeTracking/JobRequirementsManager.cs
similarity index 75%
rename from Content.Client/Players/PlayTimeTracking/PlayTimeTrackingManager.cs
rename to Content.Client/Players/PlayTimeTracking/JobRequirementsManager.cs
index cc4dd7e6d65e8b..56e18fc157b099 100644
--- a/Content.Client/Players/PlayTimeTracking/PlayTimeTrackingManager.cs
+++ b/Content.Client/Players/PlayTimeTracking/JobRequirementsManager.cs
@@ -1,6 +1,8 @@
using System.Diagnostics.CodeAnalysis;
+using System.Linq;
using System.Text;
using Content.Shared.CCVar;
+using Content.Shared.Players;
using Content.Shared.Players.PlayTimeTracking;
using Content.Shared.Roles;
using Robust.Client;
@@ -11,7 +13,7 @@
namespace Content.Client.Players.PlayTimeTracking;
-public sealed class PlayTimeTrackingManager
+public sealed class JobRequirementsManager
{
[Dependency] private readonly IBaseClient _client = default!;
[Dependency] private readonly IClientNetManager _net = default!;
@@ -20,9 +22,18 @@ public sealed class PlayTimeTrackingManager
[Dependency] private readonly IPrototypeManager _prototypes = default!;
private readonly Dictionary _roles = new();
+ private readonly List _roleBans = new();
+
+ private ISawmill _sawmill = default!;
+
+ public event Action? Updated;
public void Initialize()
{
+ _sawmill = Logger.GetSawmill("job_requirements");
+
+ // Yeah the client manager handles role bans and playtime but the server ones are separate DEAL.
+ _net.RegisterNetMessage(RxRoleBans);
_net.RegisterNetMessage(RxPlayTime);
_client.RunLevelChanged += ClientOnRunLevelChanged;
@@ -37,6 +48,18 @@ private void ClientOnRunLevelChanged(object? sender, RunLevelChangedEventArgs e)
}
}
+ private void RxRoleBans(MsgRoleBans message)
+ {
+ _sawmill.Debug($"Received roleban info containing {message.Bans.Count} entries.");
+
+ if (_roleBans.Equals(message.Bans))
+ return;
+
+ _roleBans.Clear();
+ _roleBans.AddRange(message.Bans);
+ Updated?.Invoke();
+ }
+
private void RxPlayTime(MsgPlayTime message)
{
_roles.Clear();
@@ -52,6 +75,7 @@ private void RxPlayTime(MsgPlayTime message)
{
sawmill.Info($"{tracker}: {time}");
}*/
+ Updated?.Invoke();
}
public bool IsAllowed(AntagPrototype antag, [NotNullWhen(false)] out string? reason)
@@ -91,21 +115,29 @@ public bool IsAllowed(JobPrototype job, [NotNullWhen(false)] out string? reason)
{
reason = null;
+ if (_roleBans.Contains($"Job:{job.ID}"))
+ {
+ reason = Loc.GetString("role-ban");
+ return false;
+ }
+
if (job.Requirements == null ||
!_cfg.GetCVar(CCVars.GameRoleTimers))
+ {
return true;
+ }
var player = _playerManager.LocalPlayer?.Session;
- if (player == null) return true;
+ if (player == null)
+ return true;
- var roles = _roles;
var reasonBuilder = new StringBuilder();
var first = true;
foreach (var requirement in job.Requirements)
{
- if (JobRequirements.TryRequirementMet(requirement, roles, out reason, _prototypes))
+ if (JobRequirements.TryRequirementMet(requirement, _roles, out reason, _prototypes))
continue;
if (!first)
diff --git a/Content.Client/Popups/PopupSystem.cs b/Content.Client/Popups/PopupSystem.cs
index 58e304459161b3..be0966674d951a 100644
--- a/Content.Client/Popups/PopupSystem.cs
+++ b/Content.Client/Popups/PopupSystem.cs
@@ -11,6 +11,7 @@
using Robust.Shared.Player;
using Robust.Shared.Players;
using Robust.Shared.Prototypes;
+using Robust.Shared.Replays;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
@@ -26,6 +27,7 @@ public sealed class PopupSystem : SharedPopupSystem
[Dependency] private readonly IResourceCache _resource = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly IUserInterfaceManager _uiManager = default!;
+ [Dependency] private readonly IReplayRecordingManager _replayRecording = default!;
public IReadOnlyList WorldLabels => _aliveWorldLabels;
public IReadOnlyList CursorLabels => _aliveCursorLabels;
@@ -52,8 +54,16 @@ public override void Shutdown()
.RemoveOverlay();
}
- private void PopupMessage(string message, PopupType type, EntityCoordinates coordinates, EntityUid? entity = null)
+ private void PopupMessage(string message, PopupType type, EntityCoordinates coordinates, EntityUid? entity, bool recordReplay)
{
+ if (recordReplay && _replayRecording.IsRecording)
+ {
+ if (entity != null)
+ _replayRecording.RecordClientMessage(new PopupEntityEvent(message, type, entity.Value));
+ else
+ _replayRecording.RecordClientMessage(new PopupCoordinatesEvent(message, type, coordinates));
+ }
+
var label = new WorldPopupLabel(coordinates)
{
Text = message,
@@ -66,23 +76,26 @@ private void PopupMessage(string message, PopupType type, EntityCoordinates coor
#region Abstract Method Implementations
public override void PopupCoordinates(string message, EntityCoordinates coordinates, PopupType type = PopupType.Small)
{
- PopupMessage(message, type, coordinates, null);
+ PopupMessage(message, type, coordinates, null, true);
}
public override void PopupCoordinates(string message, EntityCoordinates coordinates, ICommonSession recipient, PopupType type = PopupType.Small)
{
if (_playerManager.LocalPlayer?.Session == recipient)
- PopupMessage(message, type, coordinates, null);
+ PopupMessage(message, type, coordinates, null, true);
}
public override void PopupCoordinates(string message, EntityCoordinates coordinates, EntityUid recipient, PopupType type = PopupType.Small)
{
if (_playerManager.LocalPlayer?.ControlledEntity == recipient)
- PopupMessage(message, type, coordinates, null);
+ PopupMessage(message, type, coordinates, null, true);
}
- public override void PopupCursor(string message, PopupType type = PopupType.Small)
+ private void PopupCursorInternal(string message, PopupType type, bool recordReplay)
{
+ if (recordReplay && _replayRecording.IsRecording)
+ _replayRecording.RecordClientMessage(new PopupCursorEvent(message, type));
+
var label = new CursorPopupLabel(_inputManager.MouseScreenPosition)
{
Text = message,
@@ -92,6 +105,9 @@ public override void PopupCursor(string message, PopupType type = PopupType.Smal
_aliveCursorLabels.Add(label);
}
+ public override void PopupCursor(string message, PopupType type = PopupType.Small)
+ => PopupCursorInternal(message, type, true);
+
public override void PopupCursor(string message, ICommonSession recipient, PopupType type = PopupType.Small)
{
if (_playerManager.LocalPlayer?.Session == recipient)
@@ -137,12 +153,8 @@ public override void PopupClient(string message, EntityUid uid, EntityUid recipi
public override void PopupEntity(string message, EntityUid uid, PopupType type = PopupType.Small)
{
- if (!EntityManager.EntityExists(uid))
- return;
-
- var transform = EntityManager.GetComponent(uid);
-
- PopupMessage(message, type, transform.Coordinates, uid);
+ if (TryComp(uid, out TransformComponent? transform))
+ PopupMessage(message, type, transform.Coordinates, uid, true);
}
#endregion
@@ -151,17 +163,18 @@ public override void PopupEntity(string message, EntityUid uid, PopupType type =
private void OnPopupCursorEvent(PopupCursorEvent ev)
{
- PopupCursor(ev.Message, ev.Type);
+ PopupCursorInternal(ev.Message, ev.Type, false);
}
private void OnPopupCoordinatesEvent(PopupCoordinatesEvent ev)
{
- PopupCoordinates(ev.Message, ev.Coordinates, ev.Type);
+ PopupMessage(ev.Message, ev.Type, ev.Coordinates, null, false);
}
private void OnPopupEntityEvent(PopupEntityEvent ev)
{
- PopupEntity(ev.Message, ev.Uid, ev.Type);
+ if (TryComp(ev.Uid, out TransformComponent? transform))
+ PopupMessage(ev.Message, ev.Type, transform.Coordinates, ev.Uid, false);
}
private void OnRoundRestart(RoundRestartCleanupEvent ev)
diff --git a/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml.cs b/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml.cs
index e89eb84ba1aa3f..37b1d7b0b424a2 100644
--- a/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml.cs
+++ b/Content.Client/Preferences/UI/HumanoidProfileEditor.xaml.cs
@@ -53,6 +53,7 @@ public sealed partial class HumanoidProfileEditor : Control
private readonly IEntityManager _entMan;
private readonly IConfigurationManager _configurationManager;
private readonly MarkingManager _markingManager;
+ private readonly JobRequirementsManager _requirements;
private LineEdit _ageEdit => CAgeEdit;
private LineEdit _nameEdit => CNameEdit;
@@ -377,96 +378,9 @@ public HumanoidProfileEditor(IClientPreferencesManager preferencesManager, IProt
_jobPriorities = new List();
_jobCategories = new Dictionary();
-
- var firstCategory = true;
- var playTime = IoCManager.Resolve();
-
- foreach (var department in _prototypeManager.EnumeratePrototypes())
- {
- var departmentName = Loc.GetString($"department-{department.ID}");
-
- if (!_jobCategories.TryGetValue(department.ID, out var category))
- {
- category = new BoxContainer
- {
- Orientation = LayoutOrientation.Vertical,
- Name = department.ID,
- ToolTip = Loc.GetString("humanoid-profile-editor-jobs-amount-in-department-tooltip",
- ("departmentName", departmentName))
- };
-
- if (firstCategory)
- {
- firstCategory = false;
- }
- else
- {
- category.AddChild(new Control
- {
- MinSize = new Vector2(0, 23),
- });
- }
-
- category.AddChild(new PanelContainer
- {
- PanelOverride = new StyleBoxFlat {BackgroundColor = Color.FromHex("#464966")},
- Children =
- {
- new Label
- {
- Text = Loc.GetString("humanoid-profile-editor-department-jobs-label",
- ("departmentName", departmentName)),
- Margin = new Thickness(5f, 0, 0, 0)
- }
- }
- });
-
- _jobCategories[department.ID] = category;
- _jobList.AddChild(category);
- }
-
- var jobs = department.Roles.Select(o => _prototypeManager.Index(o)).Where(o => o.SetPreference).ToList();
- jobs.Sort((x, y) => -string.Compare(x.LocalizedName, y.LocalizedName, StringComparison.CurrentCultureIgnoreCase));
-
- foreach (var job in jobs)
- {
- var selector = new JobPrioritySelector(job);
-
- if (!playTime.IsAllowed(job, out var reason))
- {
- selector.LockRequirements(reason);
- }
-
- category.AddChild(selector);
- _jobPriorities.Add(selector);
-
- selector.PriorityChanged += priority =>
- {
- Profile = Profile?.WithJobPriority(job.ID, priority);
- IsDirty = true;
-
- foreach (var jobSelector in _jobPriorities)
- {
- // Sync other selectors with the same job in case of multiple department jobs
- if (jobSelector.Job == selector.Job)
- {
- jobSelector.Priority = priority;
- }
-
- // Lower any other high priorities to medium.
- if (priority == JobPriority.High)
- {
- if (jobSelector.Job != selector.Job && jobSelector.Priority == JobPriority.High)
- {
- jobSelector.Priority = JobPriority.Medium;
- Profile = Profile?.WithJobPriority(jobSelector.Job.ID, JobPriority.Medium);
- }
- }
- }
- };
-
- }
- }
+ _requirements = IoCManager.Resolve();
+ _requirements.Updated += UpdateRoleRequirements;
+ UpdateRoleRequirements();
#endregion Jobs
@@ -480,7 +394,7 @@ public HumanoidProfileEditor(IClientPreferencesManager preferencesManager, IProt
{
var selector = new AntagPreferenceSelector(antag);
- if (!playTime.IsAllowed(antag, out var reason))
+ if (!_requirements.IsAllowed(antag, out var reason))
{
selector.LockRequirements(antag, reason);
}
@@ -612,6 +526,101 @@ public HumanoidProfileEditor(IClientPreferencesManager preferencesManager, IProt
IsDirty = false;
}
+ private void UpdateRoleRequirements()
+ {
+ _jobList.DisposeAllChildren();
+ _jobPriorities.Clear();
+ _jobCategories.Clear();
+ var firstCategory = true;
+
+ foreach (var department in _prototypeManager.EnumeratePrototypes())
+ {
+ var departmentName = Loc.GetString($"department-{department.ID}");
+
+ if (!_jobCategories.TryGetValue(department.ID, out var category))
+ {
+ category = new BoxContainer
+ {
+ Orientation = LayoutOrientation.Vertical,
+ Name = department.ID,
+ ToolTip = Loc.GetString("humanoid-profile-editor-jobs-amount-in-department-tooltip",
+ ("departmentName", departmentName))
+ };
+
+ if (firstCategory)
+ {
+ firstCategory = false;
+ }
+ else
+ {
+ category.AddChild(new Control
+ {
+ MinSize = new Vector2(0, 23),
+ });
+ }
+
+ category.AddChild(new PanelContainer
+ {
+ PanelOverride = new StyleBoxFlat {BackgroundColor = Color.FromHex("#464966")},
+ Children =
+ {
+ new Label
+ {
+ Text = Loc.GetString("humanoid-profile-editor-department-jobs-label",
+ ("departmentName", departmentName)),
+ Margin = new Thickness(5f, 0, 0, 0)
+ }
+ }
+ });
+
+ _jobCategories[department.ID] = category;
+ _jobList.AddChild(category);
+ }
+
+ var jobs = department.Roles.Select(o => _prototypeManager.Index(o)).Where(o => o.SetPreference).ToList();
+ jobs.Sort((x, y) => -string.Compare(x.LocalizedName, y.LocalizedName, StringComparison.CurrentCultureIgnoreCase));
+
+ foreach (var job in jobs)
+ {
+ var selector = new JobPrioritySelector(job);
+
+ if (!_requirements.IsAllowed(job, out var reason))
+ {
+ selector.LockRequirements(reason);
+ }
+
+ category.AddChild(selector);
+ _jobPriorities.Add(selector);
+
+ selector.PriorityChanged += priority =>
+ {
+ Profile = Profile?.WithJobPriority(job.ID, priority);
+ IsDirty = true;
+
+ foreach (var jobSelector in _jobPriorities)
+ {
+ // Sync other selectors with the same job in case of multiple department jobs
+ if (jobSelector.Job == selector.Job)
+ {
+ jobSelector.Priority = priority;
+ }
+
+ // Lower any other high priorities to medium.
+ if (priority == JobPriority.High)
+ {
+ if (jobSelector.Job != selector.Job && jobSelector.Priority == JobPriority.High)
+ {
+ jobSelector.Priority = JobPriority.Medium;
+ Profile = Profile?.WithJobPriority(jobSelector.Job.ID, JobPriority.Medium);
+ }
+ }
+ }
+ };
+
+ }
+ }
+ }
+
private void OnFlavorTextChange(string content)
{
if (Profile is null)
@@ -703,6 +712,7 @@ protected override void Dispose(bool disposing)
if (_previewDummy != null)
_entMan.DeleteEntity(_previewDummy.Value);
+ _requirements.Updated -= UpdateRoleRequirements;
_preferencesManager.OnServerDataLoaded -= LoadServerData;
}
diff --git a/Content.Client/Replay/ContentReplayPlaybackManager.cs b/Content.Client/Replay/ContentReplayPlaybackManager.cs
new file mode 100644
index 00000000000000..e400958bca5496
--- /dev/null
+++ b/Content.Client/Replay/ContentReplayPlaybackManager.cs
@@ -0,0 +1,145 @@
+using Content.Client.Administration.Managers;
+using Content.Client.Launcher;
+using Content.Client.MainMenu;
+using Content.Client.Replay.UI.Loading;
+using Content.Client.UserInterface.Systems.Chat;
+using Content.Shared.Chat;
+using Content.Shared.GameTicking;
+using Content.Shared.Hands;
+using Content.Shared.Instruments;
+using Content.Shared.Popups;
+using Content.Shared.Projectiles;
+using Content.Shared.Weapons.Melee;
+using Content.Shared.Weapons.Melee.Events;
+using Content.Shared.Weapons.Ranged.Events;
+using Content.Shared.Weapons.Ranged.Systems;
+using Robust.Client;
+using Robust.Client.Console;
+using Robust.Client.GameObjects;
+using Robust.Client.Replays.Loading;
+using Robust.Client.Replays.Playback;
+using Robust.Client.State;
+using Robust.Client.Timing;
+using Robust.Client.UserInterface;
+using Robust.Shared.ContentPack;
+using Robust.Shared.Serialization.Markdown.Mapping;
+using Robust.Shared.Utility;
+
+namespace Content.Client.Replay;
+
+public sealed class ContentReplayPlaybackManager
+{
+ [Dependency] private readonly IStateManager _stateMan = default!;
+ [Dependency] private readonly IClientGameTiming _timing = default!;
+ [Dependency] private readonly IReplayLoadManager _loadMan = default!;
+ [Dependency] private readonly IGameController _controller = default!;
+ [Dependency] private readonly IClientEntityManager _entMan = default!;
+ [Dependency] private readonly IUserInterfaceManager _uiMan = default!;
+ [Dependency] private readonly IReplayPlaybackManager _playback = default!;
+ [Dependency] private readonly IClientConGroupController _conGrp = default!;
+ [Dependency] private readonly IClientAdminManager _adminMan = default!;
+
+ ///
+ /// UI state to return to when stopping a replay or loading fails.
+ ///
+ public Type? DefaultState;
+
+ private bool _initialized;
+
+ public void Initialize()
+ {
+ if (_initialized)
+ return;
+
+ _initialized = true;
+ _playback.HandleReplayMessage += OnHandleReplayMessage;
+ _playback.ReplayPlaybackStopped += OnReplayPlaybackStopped;
+ _playback.ReplayPlaybackStarted += OnReplayPlaybackStarted;
+ _playback.ReplayCheckpointReset += OnCheckpointReset;
+ _loadMan.LoadOverride += LoadOverride;
+ }
+
+ private void LoadOverride(IWritableDirProvider dir, ResPath resPath)
+ {
+ var screen = _stateMan.RequestStateChange>();
+ screen.Job = new ContentLoadReplayJob(1/60f, dir, resPath, _loadMan, screen);
+ screen.OnJobFinished += (_, e) => OnFinishedLoading(e);
+ }
+
+ private void OnFinishedLoading(Exception? exception)
+ {
+ if (exception != null)
+ {
+ ReturnToDefaultState();
+ _uiMan.Popup(Loc.GetString("replay-loading-failed", ("reason", exception)));
+ }
+ }
+
+ public void ReturnToDefaultState()
+ {
+ if (DefaultState != null)
+ _stateMan.RequestStateChange(DefaultState);
+ else if (_controller.LaunchState.FromLauncher)
+ _stateMan.RequestStateChange().SetDisconnected();
+ else
+ _stateMan.RequestStateChange();
+ }
+
+ private void OnCheckpointReset()
+ {
+ // This function removes future chat messages when rewinding time.
+
+ // TODO REPLAYS add chat messages when jumping forward in time.
+ // Need to allow content to add data to checkpoint states.
+
+ _uiMan.GetUIController().History.RemoveAll(x => x.Item1 > _timing.CurTick);
+ _uiMan.GetUIController().Repopulate();
+ }
+
+ private bool OnHandleReplayMessage(object message, bool skipEffects)
+ {
+ switch (message)
+ {
+ case BoundUserInterfaceMessage:
+ break; // TODO REPLAYS refactor BUIs
+ case ChatMessage chat:
+ // Just pass on the chat message to the UI controller, but skip speech-bubbles if we are fast-forwarding.
+ _uiMan.GetUIController().ProcessChatMessage(chat, speechBubble: !skipEffects);
+ return true;
+ // TODO REPLAYS figure out a cleaner way of doing this. This sucks.
+ // Next: we want to avoid spamming animations, sounds, and pop-ups while scrubbing or rewinding time
+ // (e.g., to rewind 1 tick, we really rewind ~60 and then fast forward 59). Currently, this is
+ // effectively an EntityEvent blacklist. But this is kinda shit and should be done differently somehow.
+ // The unifying aspect of these events is that they trigger pop-ups, UI changes, spawn client-side
+ // entities or start animations.
+ case RoundEndMessageEvent:
+ case PopupEvent:
+ case AudioMessage:
+ case PickupAnimationEvent:
+ case MeleeLungeEvent:
+ case SharedGunSystem.HitscanEvent:
+ case ImpactEffectEvent:
+ case MuzzleFlashEvent:
+ case DamageEffectEvent:
+ case InstrumentStartMidiEvent:
+ case InstrumentMidiEventEvent:
+ case InstrumentStopMidiEvent:
+ if (!skipEffects)
+ _entMan.DispatchReceivedNetworkMsg((EntityEventArgs)message);
+ return true;
+ }
+
+ return false;
+ }
+
+ private void OnReplayPlaybackStarted(MappingDataNode metadata, List