diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index 4029b093ccb..b85b383ee3a 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -1,4 +1,4 @@
-
+
## About the PR
@@ -10,23 +10,22 @@
## Media
-
+
## Requirements
-- [ ] I have read and am following the [Pull Request and Changelog Guidelines](https://docs.spacestation14.com/en/general-development/codebase-info/pull-request-guidelines.html).
+- [ ] I have tested all added content and changes.
- [ ] I have added media to this PR or it does not require an ingame showcase.
## Breaking changes
-
+
**Changelog**
+Changelogs must have a :cl: symbol, so the bot recognizes the changes and adds them to the game's changelog. -->
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/DeltaV/CartridgeLoader/Cartridges/PriceHistoryTable.xaml.cs b/Content.Client/DeltaV/CartridgeLoader/Cartridges/PriceHistoryTable.xaml.cs
new file mode 100644
index 00000000000..f5798f44c42
--- /dev/null
+++ b/Content.Client/DeltaV/CartridgeLoader/Cartridges/PriceHistoryTable.xaml.cs
@@ -0,0 +1,75 @@
+using System.Linq;
+using Robust.Client.AutoGenerated;
+using Robust.Client.Graphics;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+
+namespace Content.Client.DeltaV.CartridgeLoader.Cartridges;
+
+[GenerateTypedNameReferences]
+public sealed partial class PriceHistoryTable : BoxContainer
+{
+ public PriceHistoryTable()
+ {
+ RobustXamlLoader.Load(this);
+
+ // Create the stylebox here so we can use the colors from StockTradingUi
+ var styleBox = new StyleBoxFlat
+ {
+ BackgroundColor = StockTradingUiFragment.PriceBackgroundColor,
+ ContentMarginLeftOverride = 6,
+ ContentMarginRightOverride = 6,
+ ContentMarginTopOverride = 4,
+ ContentMarginBottomOverride = 4,
+ BorderColor = StockTradingUiFragment.BorderColor,
+ BorderThickness = new Thickness(1),
+ };
+
+ HistoryPanel.PanelOverride = styleBox;
+ }
+
+ public void Update(List priceHistory)
+ {
+ PriceGrid.RemoveAllChildren();
+
+ // Take last 5 prices
+ var lastFivePrices = priceHistory.TakeLast(5).ToList();
+
+ for (var i = 0; i < lastFivePrices.Count; i++)
+ {
+ var price = lastFivePrices[i];
+ var previousPrice = i > 0 ? lastFivePrices[i - 1] : price;
+ var priceChange = ((price - previousPrice) / previousPrice) * 100;
+
+ var entryContainer = new BoxContainer
+ {
+ Orientation = LayoutOrientation.Vertical,
+ MinWidth = 80,
+ HorizontalAlignment = HAlignment.Center,
+ };
+
+ var priceLabel = new Label
+ {
+ Text = $"${price:F2}",
+ HorizontalAlignment = HAlignment.Center,
+ };
+
+ var changeLabel = new Label
+ {
+ Text = $"{(priceChange >= 0 ? "+" : "")}{priceChange:F2}%",
+ HorizontalAlignment = HAlignment.Center,
+ StyleClasses = { "LabelSubText" },
+ Modulate = priceChange switch
+ {
+ > 0 => StockTradingUiFragment.PositiveColor,
+ < 0 => StockTradingUiFragment.NegativeColor,
+ _ => StockTradingUiFragment.NeutralColor,
+ }
+ };
+
+ entryContainer.AddChild(priceLabel);
+ entryContainer.AddChild(changeLabel);
+ PriceGrid.AddChild(entryContainer);
+ }
+ }
+}
diff --git a/Content.Client/DeltaV/CartridgeLoader/Cartridges/StockTradingUi.cs b/Content.Client/DeltaV/CartridgeLoader/Cartridges/StockTradingUi.cs
new file mode 100644
index 00000000000..45704ee2349
--- /dev/null
+++ b/Content.Client/DeltaV/CartridgeLoader/Cartridges/StockTradingUi.cs
@@ -0,0 +1,45 @@
+using Robust.Client.UserInterface;
+using Content.Client.UserInterface.Fragments;
+using Content.Shared.CartridgeLoader;
+using Content.Shared.CartridgeLoader.Cartridges;
+
+namespace Content.Client.DeltaV.CartridgeLoader.Cartridges;
+
+public sealed partial class StockTradingUi : UIFragment
+{
+ private StockTradingUiFragment? _fragment;
+
+ public override Control GetUIFragmentRoot()
+ {
+ return _fragment!;
+ }
+
+ public override void Setup(BoundUserInterface userInterface, EntityUid? fragmentOwner)
+ {
+ _fragment = new StockTradingUiFragment();
+
+ _fragment.OnBuyButtonPressed += (company, amount) =>
+ {
+ SendStockTradingUiMessage(StockTradingUiAction.Buy, company, amount, userInterface);
+ };
+ _fragment.OnSellButtonPressed += (company, amount) =>
+ {
+ SendStockTradingUiMessage(StockTradingUiAction.Sell, company, amount, userInterface);
+ };
+ }
+
+ public override void UpdateState(BoundUserInterfaceState state)
+ {
+ if (state is StockTradingUiState cast)
+ {
+ _fragment?.UpdateState(cast);
+ }
+ }
+
+ private static void SendStockTradingUiMessage(StockTradingUiAction action, int company, float amount, BoundUserInterface userInterface)
+ {
+ var newsMessage = new StockTradingUiMessageEvent(action, company, amount);
+ var message = new CartridgeUiMessage(newsMessage);
+ userInterface.SendMessage(message);
+ }
+}
diff --git a/Content.Client/DeltaV/CartridgeLoader/Cartridges/StockTradingUiFragment.xaml b/Content.Client/DeltaV/CartridgeLoader/Cartridges/StockTradingUiFragment.xaml
new file mode 100644
index 00000000000..00b45584cc4
--- /dev/null
+++ b/Content.Client/DeltaV/CartridgeLoader/Cartridges/StockTradingUiFragment.xaml
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/DeltaV/CartridgeLoader/Cartridges/StockTradingUiFragment.xaml.cs b/Content.Client/DeltaV/CartridgeLoader/Cartridges/StockTradingUiFragment.xaml.cs
new file mode 100644
index 00000000000..b44e8f44c70
--- /dev/null
+++ b/Content.Client/DeltaV/CartridgeLoader/Cartridges/StockTradingUiFragment.xaml.cs
@@ -0,0 +1,269 @@
+using System.Linq;
+using Content.Client.Administration.UI.CustomControls;
+using Content.Shared.CartridgeLoader.Cartridges;
+using Robust.Client.AutoGenerated;
+using Robust.Client.Graphics;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+
+namespace Content.Client.DeltaV.CartridgeLoader.Cartridges;
+
+[GenerateTypedNameReferences]
+public sealed partial class StockTradingUiFragment : BoxContainer
+{
+ private readonly Dictionary _companyEntries = new();
+
+ // Event handlers for the parent UI
+ public event Action? OnBuyButtonPressed;
+ public event Action? OnSellButtonPressed;
+
+ // Define colors
+ public static readonly Color PositiveColor = Color.FromHex("#00ff00"); // Green
+ public static readonly Color NegativeColor = Color.FromHex("#ff0000"); // Red
+ public static readonly Color NeutralColor = Color.FromHex("#ffffff"); // White
+ public static readonly Color BackgroundColor = Color.FromHex("#25252a"); // Dark grey
+ public static readonly Color PriceBackgroundColor = Color.FromHex("#1a1a1a"); // Darker grey
+ public static readonly Color BorderColor = Color.FromHex("#404040"); // Light grey
+
+ public StockTradingUiFragment()
+ {
+ RobustXamlLoader.Load(this);
+ }
+
+ public void UpdateState(StockTradingUiState state)
+ {
+ NoEntries.Visible = state.Entries.Count == 0;
+ Balance.Text = Loc.GetString("stock-trading-balance", ("balance", state.Balance));
+
+ // Clear all existing entries
+ foreach (var entry in _companyEntries.Values)
+ {
+ entry.Container.RemoveAllChildren();
+ }
+ _companyEntries.Clear();
+ Entries.RemoveAllChildren();
+
+ // Add new entries
+ for (var i = 0; i < state.Entries.Count; i++)
+ {
+ var company = state.Entries[i];
+ var entry = new CompanyEntry(i, company.LocalizedDisplayName, OnBuyButtonPressed, OnSellButtonPressed);
+ _companyEntries[i] = entry;
+ Entries.AddChild(entry.Container);
+
+ var ownedStocks = state.OwnedStocks.GetValueOrDefault(i, 0);
+ entry.Update(company, ownedStocks);
+ }
+ }
+
+ private sealed class CompanyEntry
+ {
+ public readonly BoxContainer Container;
+ private readonly Label _nameLabel;
+ private readonly Label _priceLabel;
+ private readonly Label _changeLabel;
+ private readonly Button _sellButton;
+ private readonly Button _buyButton;
+ private readonly Label _sharesLabel;
+ private readonly LineEdit _amountEdit;
+ private readonly PriceHistoryTable _priceHistory;
+
+ public CompanyEntry(int companyIndex,
+ string displayName,
+ Action? onBuyPressed,
+ Action? onSellPressed)
+ {
+ Container = new BoxContainer
+ {
+ Orientation = LayoutOrientation.Vertical,
+ HorizontalExpand = true,
+ Margin = new Thickness(0, 0, 0, 2),
+ };
+
+ // Company info panel
+ var companyPanel = new PanelContainer();
+
+ var mainContent = new BoxContainer
+ {
+ Orientation = LayoutOrientation.Vertical,
+ HorizontalExpand = true,
+ Margin = new Thickness(8),
+ };
+
+ // Top row with company name and price info
+ var topRow = new BoxContainer
+ {
+ Orientation = LayoutOrientation.Horizontal,
+ HorizontalExpand = true,
+ };
+
+ _nameLabel = new Label
+ {
+ HorizontalExpand = true,
+ Text = displayName,
+ };
+
+ // Create a panel for price and change
+ var pricePanel = new PanelContainer
+ {
+ HorizontalAlignment = HAlignment.Right,
+ };
+
+ // Style the price panel
+ var priceStyleBox = new StyleBoxFlat
+ {
+ BackgroundColor = BackgroundColor,
+ ContentMarginLeftOverride = 8,
+ ContentMarginRightOverride = 8,
+ ContentMarginTopOverride = 4,
+ ContentMarginBottomOverride = 4,
+ BorderColor = BorderColor,
+ BorderThickness = new Thickness(1),
+ };
+
+ pricePanel.PanelOverride = priceStyleBox;
+
+ // Container for price and change labels
+ var priceContainer = new BoxContainer
+ {
+ Orientation = LayoutOrientation.Horizontal,
+ };
+
+ _priceLabel = new Label();
+
+ _changeLabel = new Label
+ {
+ HorizontalAlignment = HAlignment.Right,
+ Modulate = NeutralColor,
+ Margin = new Thickness(15, 0, 0, 0),
+ };
+
+ priceContainer.AddChild(_priceLabel);
+ priceContainer.AddChild(_changeLabel);
+ pricePanel.AddChild(priceContainer);
+
+ topRow.AddChild(_nameLabel);
+ topRow.AddChild(pricePanel);
+
+ // Add the top row
+ mainContent.AddChild(topRow);
+
+ // Add the price history table between top and bottom rows
+ _priceHistory = new PriceHistoryTable();
+ mainContent.AddChild(_priceHistory);
+
+ // Trading controls (bottom row)
+ var bottomRow = new BoxContainer
+ {
+ Orientation = LayoutOrientation.Horizontal,
+ HorizontalExpand = true,
+ Margin = new Thickness(0, 5, 0, 0),
+ };
+
+ _sharesLabel = new Label
+ {
+ Text = Loc.GetString("stock-trading-owned-shares"),
+ MinWidth = 100,
+ };
+
+ _amountEdit = new LineEdit
+ {
+ PlaceHolder = Loc.GetString("stock-trading-amount-placeholder"),
+ HorizontalExpand = true,
+ MinWidth = 80,
+ };
+
+ var buttonContainer = new BoxContainer
+ {
+ Orientation = LayoutOrientation.Horizontal,
+ HorizontalAlignment = HAlignment.Right,
+ MinWidth = 140,
+ };
+
+ _buyButton = new Button
+ {
+ Text = Loc.GetString("stock-trading-buy-button"),
+ MinWidth = 65,
+ Margin = new Thickness(3, 0, 3, 0),
+ };
+
+ _sellButton = new Button
+ {
+ Text = Loc.GetString("stock-trading-sell-button"),
+ MinWidth = 65,
+ };
+
+ buttonContainer.AddChild(_buyButton);
+ buttonContainer.AddChild(_sellButton);
+
+ bottomRow.AddChild(_sharesLabel);
+ bottomRow.AddChild(_amountEdit);
+ bottomRow.AddChild(buttonContainer);
+
+ // Add the bottom row last
+ mainContent.AddChild(bottomRow);
+
+ companyPanel.AddChild(mainContent);
+ Container.AddChild(companyPanel);
+
+ // Add horizontal separator after the panel
+ var separator = new HSeparator
+ {
+ Margin = new Thickness(5, 3, 5, 5),
+ };
+ Container.AddChild(separator);
+
+ // Button click events
+ _buyButton.OnPressed += _ =>
+ {
+ if (float.TryParse(_amountEdit.Text, out var amount) && amount > 0)
+ onBuyPressed?.Invoke(companyIndex, amount);
+ };
+
+ _sellButton.OnPressed += _ =>
+ {
+ if (float.TryParse(_amountEdit.Text, out var amount) && amount > 0)
+ onSellPressed?.Invoke(companyIndex, amount);
+ };
+
+ // There has to be a better way of doing this
+ _amountEdit.OnTextChanged += args =>
+ {
+ var newText = string.Concat(args.Text.Where(char.IsDigit));
+ if (newText != args.Text)
+ _amountEdit.Text = newText;
+ };
+ }
+
+ public void Update(StockCompanyStruct company, int ownedStocks)
+ {
+ _nameLabel.Text = company.LocalizedDisplayName;
+ _priceLabel.Text = $"${company.CurrentPrice:F2}";
+ _sharesLabel.Text = Loc.GetString("stock-trading-owned-shares", ("shares", ownedStocks));
+
+ var priceChange = 0f;
+ if (company.PriceHistory is { Count: > 0 })
+ {
+ var previousPrice = company.PriceHistory[^1];
+ priceChange = (company.CurrentPrice - previousPrice) / previousPrice * 100;
+ }
+
+ _changeLabel.Text = $"{(priceChange >= 0 ? "+" : "")}{priceChange:F2}%";
+
+ // Update color based on price change
+ _changeLabel.Modulate = priceChange switch
+ {
+ > 0 => PositiveColor,
+ < 0 => NegativeColor,
+ _ => NeutralColor,
+ };
+
+ // Update the price history table if not null
+ if (company.PriceHistory != null)
+ _priceHistory.Update(company.PriceHistory);
+
+ // Disable sell button if no shares owned
+ _sellButton.Disabled = ownedStocks <= 0;
+ }
+ }
+}
diff --git a/Content.Client/DeltaV/Chapel/SacrificialAltarSystem.cs b/Content.Client/DeltaV/Chapel/SacrificialAltarSystem.cs
new file mode 100644
index 00000000000..7b9b3757e32
--- /dev/null
+++ b/Content.Client/DeltaV/Chapel/SacrificialAltarSystem.cs
@@ -0,0 +1,5 @@
+using Content.Shared.DeltaV.Chapel;
+
+namespace Content.Client.DeltaV.Chapel;
+
+public sealed class SacrificialAltarSystem : SharedSacrificialAltarSystem;
diff --git a/Content.Client/Nyanotrasen/Mail/MailComponent.cs b/Content.Client/DeltaV/Mail/MailComponent.cs
similarity index 53%
rename from Content.Client/Nyanotrasen/Mail/MailComponent.cs
rename to Content.Client/DeltaV/Mail/MailComponent.cs
index 4f9b6e36892..1603cf7d663 100644
--- a/Content.Client/Nyanotrasen/Mail/MailComponent.cs
+++ b/Content.Client/DeltaV/Mail/MailComponent.cs
@@ -1,8 +1,9 @@
-using Content.Shared.Mail;
+using Content.Shared.DeltaV.Mail;
-namespace Content.Client.Mail
+namespace Content.Client.DeltaV.Mail
{
[RegisterComponent]
public sealed partial class MailComponent : SharedMailComponent
- {}
+ {
+ }
}
diff --git a/Content.Client/Nyanotrasen/Mail/MailSystem.cs b/Content.Client/DeltaV/Mail/MailSystem.cs
similarity index 93%
rename from Content.Client/Nyanotrasen/Mail/MailSystem.cs
rename to Content.Client/DeltaV/Mail/MailSystem.cs
index 38e575b4150..b215192140f 100644
--- a/Content.Client/Nyanotrasen/Mail/MailSystem.cs
+++ b/Content.Client/DeltaV/Mail/MailSystem.cs
@@ -1,9 +1,9 @@
-using Content.Shared.Mail;
+using Content.Shared.DeltaV.Mail;
using Content.Shared.StatusIcon;
using Robust.Client.GameObjects;
using Robust.Shared.Prototypes;
-namespace Content.Client.Mail;
+namespace Content.Client.DeltaV.Mail;
///
/// Display a cool stamp on the parcel based on the job of the recipient.
@@ -27,7 +27,6 @@ public sealed class MailJobVisualizerSystem : VisualizerSystem
{
[Dependency] private readonly SharedAppearanceSystem _appearance = default!;
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
- [Dependency] private readonly SpriteSystem _stateManager = default!;
[Dependency] private readonly SpriteSystem _spriteSystem = default!;
protected override void OnAppearanceChange(EntityUid uid, MailComponent component, ref AppearanceChangeEvent args)
diff --git a/Content.Client/DeltaV/RoundEnd/NoEorgPopup.xaml b/Content.Client/DeltaV/RoundEnd/NoEorgPopup.xaml
new file mode 100644
index 00000000000..31c3b74ea0b
--- /dev/null
+++ b/Content.Client/DeltaV/RoundEnd/NoEorgPopup.xaml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/DeltaV/RoundEnd/NoEorgPopup.xaml.cs b/Content.Client/DeltaV/RoundEnd/NoEorgPopup.xaml.cs
new file mode 100644
index 00000000000..18e89bb15c5
--- /dev/null
+++ b/Content.Client/DeltaV/RoundEnd/NoEorgPopup.xaml.cs
@@ -0,0 +1,91 @@
+using Content.Client.UserInterface.Controls;
+using Content.Shared.DeltaV.CCVars;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Configuration;
+using Robust.Shared.Timing;
+using Robust.Shared.Utility;
+
+namespace Content.Client.DeltaV.RoundEnd;
+
+[GenerateTypedNameReferences]
+public sealed partial class NoEorgPopup : FancyWindow
+{
+ [Dependency] private readonly IConfigurationManager _cfg = default!;
+
+ private float _remainingTime;
+ private bool _initialSkipState;
+
+ public NoEorgPopup()
+ {
+ IoCManager.InjectDependencies(this);
+ RobustXamlLoader.Load(this);
+
+ InitializeUI();
+ InitializeEvents();
+ ResetTimer();
+ }
+
+ private void InitializeUI()
+ {
+ TitleLabel.Text = Loc.GetString("no-eorg-popup-label");
+ MessageLabel.SetMessage(FormattedMessage.FromMarkupOrThrow(Loc.GetString("no-eorg-popup-message")));
+ RuleLabel.SetMessage(FormattedMessage.FromMarkupOrThrow(Loc.GetString("no-eorg-popup-rule")));
+ RuleTextLabel.SetMessage(FormattedMessage.FromMarkupOrThrow(Loc.GetString("no-eorg-popup-rule-text")));
+
+ _initialSkipState =
+ _cfg.GetCVar(DCCVars.SkipRoundEndNoEorgPopup); // Store the initial CVar value to compare against
+ SkipCheckBox.Pressed = _initialSkipState;
+ NoEorgCloseButton.Disabled = true;
+
+ UpdateCloseButtonText();
+ }
+
+ private void InitializeEvents()
+ {
+ OnClose += SaveSkipState; // Only change the CVar once the close button is pressed
+ NoEorgCloseButton.OnPressed += OnClosePressed;
+ }
+
+ private void ResetTimer()
+ {
+ _remainingTime = _cfg.GetCVar(DCCVars.RoundEndNoEorgPopupTime); // Set how long to show the popup for
+ UpdateCloseButtonText();
+ }
+
+ private void SaveSkipState()
+ {
+ if (SkipCheckBox.Pressed == _initialSkipState)
+ return;
+
+ _cfg.SetCVar(DCCVars.SkipRoundEndNoEorgPopup, SkipCheckBox.Pressed);
+ _cfg.SaveToFile();
+ }
+
+ private void OnClosePressed(BaseButton.ButtonEventArgs args)
+ {
+ Close();
+ }
+
+ private void UpdateCloseButtonText()
+ {
+ var isWaiting = _remainingTime > 0f;
+ NoEorgCloseButton.Text = isWaiting
+ ? Loc.GetString("no-eorg-popup-close-button-wait", ("time", (int)MathF.Ceiling(_remainingTime)))
+ : Loc.GetString("no-eorg-popup-close-button");
+ NoEorgCloseButton.Disabled = isWaiting;
+ }
+
+ protected override void FrameUpdate(FrameEventArgs args)
+ {
+ base.FrameUpdate(args);
+
+ if (!NoEorgCloseButton.Disabled)
+ return;
+
+ _remainingTime = MathF.Max(0f, _remainingTime - args.DeltaSeconds);
+ UpdateCloseButtonText();
+ }
+}
+
diff --git a/Content.Client/DeltaV/RoundEnd/NoEorgPopupSystem.cs b/Content.Client/DeltaV/RoundEnd/NoEorgPopupSystem.cs
new file mode 100644
index 00000000000..40341b9ae89
--- /dev/null
+++ b/Content.Client/DeltaV/RoundEnd/NoEorgPopupSystem.cs
@@ -0,0 +1,36 @@
+using Content.Shared.GameTicking;
+using Content.Shared.DeltaV.CCVars;
+using Robust.Shared.Configuration;
+
+namespace Content.Client.DeltaV.RoundEnd;
+
+public sealed class NoEorgPopupSystem : EntitySystem
+{
+ [Dependency] private readonly IConfigurationManager _cfg = default!;
+
+ private NoEorgPopup? _window;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+ SubscribeNetworkEvent(OnRoundEnd);
+ }
+
+ private void OnRoundEnd(RoundEndMessageEvent ev)
+ {
+ if (_cfg.GetCVar(DCCVars.SkipRoundEndNoEorgPopup) || _cfg.GetCVar(DCCVars.RoundEndNoEorgPopup) == false)
+ return;
+
+ OpenNoEorgPopup();
+ }
+
+ private void OpenNoEorgPopup()
+ {
+ if (_window != null)
+ return;
+
+ _window = new NoEorgPopup();
+ _window.OpenCentered();
+ _window.OnClose += () => _window = null;
+ }
+}
diff --git a/Content.Client/Ensnaring/EnsnareableSystem.cs b/Content.Client/Ensnaring/EnsnareableSystem.cs
index b7a5a45ca0c..6861bd8f09a 100644
--- a/Content.Client/Ensnaring/EnsnareableSystem.cs
+++ b/Content.Client/Ensnaring/EnsnareableSystem.cs
@@ -2,7 +2,7 @@
using Content.Shared.Ensnaring.Components;
using Robust.Client.GameObjects;
-namespace Content.Client.Ensnaring.Visualizers;
+namespace Content.Client.Ensnaring;
public sealed class EnsnareableSystem : SharedEnsnareableSystem
{
@@ -12,13 +12,14 @@ public override void Initialize()
{
base.Initialize();
- SubscribeLocalEvent(OnComponentInit);
SubscribeLocalEvent(OnAppearanceChange);
}
- private void OnComponentInit(EntityUid uid, EnsnareableComponent component, ComponentInit args)
+ protected override void OnEnsnareInit(Entity ent, ref ComponentInit args)
{
- if(!TryComp(uid, out var sprite))
+ base.OnEnsnareInit(ent, ref args);
+
+ if(!TryComp(ent.Owner, out var sprite))
return;
// TODO remove this, this should just be in yaml.
diff --git a/Content.Client/Entry/EntryPoint.cs b/Content.Client/Entry/EntryPoint.cs
index 4fb2eba7228..602c13149b1 100644
--- a/Content.Client/Entry/EntryPoint.cs
+++ b/Content.Client/Entry/EntryPoint.cs
@@ -4,6 +4,7 @@
using Content.Client.DebugMon;
using Content.Client.Eui;
using Content.Client.Fullscreen;
+using Content.Client.GameTicking.Managers;
using Content.Client.GhostKick;
using Content.Client.Guidebook;
using Content.Client.Input;
@@ -69,8 +70,8 @@ public sealed class EntryPoint : GameClient
[Dependency] private readonly IResourceManager _resourceManager = default!;
[Dependency] private readonly IReplayLoadManager _replayLoad = default!;
[Dependency] private readonly ILogManager _logManager = default!;
- [Dependency] private readonly ContentReplayPlaybackManager _replayMan = default!;
[Dependency] private readonly DebugMonitorManager _debugMonitorManager = default!;
+ [Dependency] private readonly TitleWindowManager _titleWindowManager = default!;
public override void Init()
{
@@ -141,6 +142,12 @@ public override void Init()
_configManager.SetCVar("interface.resolutionAutoScaleMinimum", 0.5f);
}
+ public override void Shutdown()
+ {
+ base.Shutdown();
+ _titleWindowManager.Shutdown();
+ }
+
public override void PostInit()
{
base.PostInit();
@@ -161,6 +168,7 @@ public override void PostInit()
_userInterfaceManager.SetDefaultTheme("SS14DefaultTheme");
_userInterfaceManager.SetActiveTheme(_configManager.GetCVar(CVars.InterfaceTheme));
_documentParsingManager.Initialize();
+ _titleWindowManager.Initialize();
_baseClient.RunLevelChanged += (_, args) =>
{
@@ -192,7 +200,7 @@ private void SwitchToDefaultState(bool disconnected = false)
_resourceManager,
ReplayConstants.ReplayZipFolder.ToRootedPath());
- _replayMan.LastLoad = (null, ReplayConstants.ReplayZipFolder.ToRootedPath());
+ _playbackMan.LastLoad = (null, ReplayConstants.ReplayZipFolder.ToRootedPath());
_replayLoad.LoadAndStartReplay(reader);
}
else if (_gameController.LaunchState.FromLauncher)
diff --git a/Content.Client/GPS/Components/HandheldGPSComponent.cs b/Content.Client/GPS/Components/HandheldGPSComponent.cs
deleted file mode 100644
index 0f5271fd80c..00000000000
--- a/Content.Client/GPS/Components/HandheldGPSComponent.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-using Content.Shared.GPS;
-
-namespace Content.Client.GPS.Components
-{
- [RegisterComponent]
- public sealed partial class HandheldGPSComponent : SharedHandheldGPSComponent
- {
- }
-}
diff --git a/Content.Client/GPS/Systems/HandheldGpsSystem.cs b/Content.Client/GPS/Systems/HandheldGpsSystem.cs
index cc2b888da37..3f38a3b6952 100644
--- a/Content.Client/GPS/Systems/HandheldGpsSystem.cs
+++ b/Content.Client/GPS/Systems/HandheldGpsSystem.cs
@@ -1,4 +1,4 @@
-using Content.Client.GPS.Components;
+using Content.Shared.GPS.Components;
using Content.Client.GPS.UI;
using Content.Client.Items;
diff --git a/Content.Client/GPS/UI/HandheldGpsStatusControl.cs b/Content.Client/GPS/UI/HandheldGpsStatusControl.cs
index 7dcf3f29c51..57645e386e7 100644
--- a/Content.Client/GPS/UI/HandheldGpsStatusControl.cs
+++ b/Content.Client/GPS/UI/HandheldGpsStatusControl.cs
@@ -1,4 +1,4 @@
-using Content.Client.GPS.Components;
+using Content.Shared.GPS.Components;
using Content.Client.Message;
using Content.Client.Stylesheets;
using Robust.Client.GameObjects;
@@ -30,6 +30,13 @@ protected override void FrameUpdate(FrameEventArgs args)
{
base.FrameUpdate(args);
+ // don't display the label if the gps component is being removed
+ if (_parent.Comp.LifeStage > ComponentLifeStage.Running)
+ {
+ _label.Visible = false;
+ return;
+ }
+
_updateDif += args.DeltaSeconds;
if (_updateDif < _parent.Comp.UpdateRate)
return;
@@ -44,9 +51,9 @@ private void UpdateGpsDetails()
var posText = "Error";
if (_entMan.TryGetComponent(_parent, out TransformComponent? transComp))
{
- var pos = _transform.GetMapCoordinates(_parent.Owner, xform: transComp);
- var x = (int) pos.X;
- var y = (int) pos.Y;
+ var pos = _transform.GetMapCoordinates(_parent.Owner, xform: transComp);
+ var x = (int)pos.X;
+ var y = (int)pos.Y;
posText = $"({x}, {y})";
}
_label.SetMarkup(Loc.GetString("handheld-gps-coordinates-title", ("coordinates", posText)));
diff --git a/Content.Client/GameTicking/Managers/TitleWindowManager.cs b/Content.Client/GameTicking/Managers/TitleWindowManager.cs
new file mode 100644
index 00000000000..18ce16f634c
--- /dev/null
+++ b/Content.Client/GameTicking/Managers/TitleWindowManager.cs
@@ -0,0 +1,62 @@
+using Content.Shared.CCVar;
+using Robust.Client;
+using Robust.Client.Graphics;
+using Robust.Shared;
+using Robust.Shared.Configuration;
+
+namespace Content.Client.GameTicking.Managers;
+
+public sealed class TitleWindowManager
+{
+ [Dependency] private readonly IBaseClient _client = default!;
+ [Dependency] private readonly IClyde _clyde = default!;
+ [Dependency] private readonly IConfigurationManager _cfg = default!;
+ [Dependency] private readonly IGameController _gameController = default!;
+
+ public void Initialize()
+ {
+ _cfg.OnValueChanged(CVars.GameHostName, OnHostnameChange, true);
+ _cfg.OnValueChanged(CCVars.GameHostnameInTitlebar, OnHostnameTitleChange, true);
+
+ _client.RunLevelChanged += OnRunLevelChangedChange;
+ }
+
+ public void Shutdown()
+ {
+ _cfg.UnsubValueChanged(CVars.GameHostName, OnHostnameChange);
+ _cfg.UnsubValueChanged(CCVars.GameHostnameInTitlebar, OnHostnameTitleChange);
+ }
+
+ private void OnHostnameChange(string hostname)
+ {
+ var defaultWindowTitle = _gameController.GameTitle();
+
+ // Since the game assumes the server name is MyServer and that GameHostnameInTitlebar CCVar is true by default
+ // Lets just... not show anything. This also is used to revert back to just the game title on disconnect.
+ if (_client.RunLevel == ClientRunLevel.Initialize)
+ {
+ _clyde.SetWindowTitle(defaultWindowTitle);
+ return;
+ }
+
+ if (_cfg.GetCVar(CCVars.GameHostnameInTitlebar))
+ // If you really dislike the dash I guess change it here
+ _clyde.SetWindowTitle(hostname + " - " + defaultWindowTitle);
+ else
+ _clyde.SetWindowTitle(defaultWindowTitle);
+ }
+
+ // Clients by default assume game.hostname_in_titlebar is true
+ // but we need to clear it as soon as we join and actually receive the servers preference on this.
+ // This will ensure we rerun OnHostnameChange and set the correct title bar name.
+ private void OnHostnameTitleChange(bool colonthree)
+ {
+ OnHostnameChange(_cfg.GetCVar(CVars.GameHostName));
+ }
+
+ // This is just used we can rerun the hostname change function when we disconnect to revert back to just the games title.
+ private void OnRunLevelChangedChange(object? sender, RunLevelChangedEventArgs runLevelChangedEventArgs)
+ {
+ OnHostnameChange(_cfg.GetCVar(CVars.GameHostName));
+ }
+}
diff --git a/Content.Client/Hands/Systems/HandsSystem.cs b/Content.Client/Hands/Systems/HandsSystem.cs
index ffa6dfd29d6..68800a2afe5 100644
--- a/Content.Client/Hands/Systems/HandsSystem.cs
+++ b/Content.Client/Hands/Systems/HandsSystem.cs
@@ -130,9 +130,9 @@ public void ReloadHandButtons()
OnPlayerHandsAdded?.Invoke(hands);
}
- public override void DoDrop(EntityUid uid, Hand hand, bool doDropInteraction = true, HandsComponent? hands = null)
+ public override void DoDrop(EntityUid uid, Hand hand, bool doDropInteraction = true, HandsComponent? hands = null, bool log = true)
{
- base.DoDrop(uid, hand, doDropInteraction, hands);
+ base.DoDrop(uid, hand, doDropInteraction, hands, log);
if (TryComp(hand.HeldEntity, out SpriteComponent? sprite))
sprite.RenderOrder = EntityManager.CurrentTick.Value;
diff --git a/Content.Client/Info/PlaytimeStats/PlaytimeStatsEntry.cs b/Content.Client/Info/PlaytimeStats/PlaytimeStatsEntry.cs
index aff01800f94..632ad8de4ac 100644
--- a/Content.Client/Info/PlaytimeStats/PlaytimeStatsEntry.cs
+++ b/Content.Client/Info/PlaytimeStats/PlaytimeStatsEntry.cs
@@ -1,3 +1,4 @@
+using Content.Shared.Localizations;
using Robust.Client.AutoGenerated;
using Robust.Client.Graphics;
using Robust.Client.UserInterface.Controls;
@@ -16,19 +17,10 @@ public PlaytimeStatsEntry(string role, TimeSpan playtime, StyleBox styleBox)
RoleLabel.Text = role;
Playtime = playtime; // store the TimeSpan value directly
- PlaytimeLabel.Text = ConvertTimeSpanToHoursMinutes(playtime); // convert to string for display
+ PlaytimeLabel.Text = ContentLocalizationManager.FormatPlaytime(playtime); // convert to string for display
BackgroundColorPanel.PanelOverride = styleBox;
}
- private static string ConvertTimeSpanToHoursMinutes(TimeSpan timeSpan)
- {
- var hours = (int)timeSpan.TotalHours;
- var minutes = timeSpan.Minutes;
-
- var formattedTimeLoc = Loc.GetString("ui-playtime-time-format", ("hours", hours), ("minutes", minutes));
- return formattedTimeLoc;
- }
-
public void UpdateShading(StyleBoxFlat styleBox)
{
BackgroundColorPanel.PanelOverride = styleBox;
diff --git a/Content.Client/Info/PlaytimeStats/PlaytimeStatsWindow.cs b/Content.Client/Info/PlaytimeStats/PlaytimeStatsWindow.cs
index 3b54bf82daf..98241b2ccab 100644
--- a/Content.Client/Info/PlaytimeStats/PlaytimeStatsWindow.cs
+++ b/Content.Client/Info/PlaytimeStats/PlaytimeStatsWindow.cs
@@ -104,8 +104,7 @@ private void PopulatePlaytimeData()
{
var overallPlaytime = _jobRequirementsManager.FetchOverallPlaytime();
- var formattedPlaytime = ConvertTimeSpanToHoursMinutes(overallPlaytime);
- OverallPlaytimeLabel.Text = Loc.GetString("ui-playtime-overall", ("time", formattedPlaytime));
+ OverallPlaytimeLabel.Text = Loc.GetString("ui-playtime-overall", ("time", overallPlaytime));
var rolePlaytimes = _jobRequirementsManager.FetchPlaytimeByRoles();
@@ -134,13 +133,4 @@ private void AddRolePlaytimeEntryToTable(string role, string playtimeString)
_sawmill.Error($"The provided playtime string '{playtimeString}' is not in the correct format.");
}
}
-
- private static string ConvertTimeSpanToHoursMinutes(TimeSpan timeSpan)
- {
- var hours = (int) timeSpan.TotalHours;
- var minutes = timeSpan.Minutes;
-
- var formattedTimeLoc = Loc.GetString("ui-playtime-time-format", ("hours", hours), ("minutes", minutes));
- return formattedTimeLoc;
- }
}
diff --git a/Content.Client/Inventory/StrippableBoundUserInterface.cs b/Content.Client/Inventory/StrippableBoundUserInterface.cs
index 132c5ed654c..2ce07758c96 100644
--- a/Content.Client/Inventory/StrippableBoundUserInterface.cs
+++ b/Content.Client/Inventory/StrippableBoundUserInterface.cs
@@ -98,7 +98,7 @@ public void UpdateMenu()
}
}
- if (EntMan.TryGetComponent(Owner, out var handsComp))
+ if (EntMan.TryGetComponent(Owner, out var handsComp) && handsComp.CanBeStripped)
{
// good ol hands shit code. there is a GuiHands comparer that does the same thing... but these are hands
// and not gui hands... which are different...
@@ -136,7 +136,7 @@ public void UpdateMenu()
StyleClasses = { StyleBase.ButtonOpenRight }
};
- button.OnPressed += (_) => SendMessage(new StrippingEnsnareButtonPressed());
+ button.OnPressed += (_) => SendPredictedMessage(new StrippingEnsnareButtonPressed());
_strippingMenu.SnareContainer.AddChild(button);
}
@@ -177,7 +177,7 @@ private void SlotPressed(GUIBoundKeyEventArgs ev, SlotControl slot)
// So for now: only stripping & examining
if (ev.Function == EngineKeyFunctions.Use)
{
- SendMessage(new StrippingSlotButtonPressed(slot.SlotName, slot is HandButton));
+ SendPredictedMessage(new StrippingSlotButtonPressed(slot.SlotName, slot is HandButton));
return;
}
diff --git a/Content.Client/IoC/ClientContentIoC.cs b/Content.Client/IoC/ClientContentIoC.cs
index 1fd237cf3e3..370188e3c61 100644
--- a/Content.Client/IoC/ClientContentIoC.cs
+++ b/Content.Client/IoC/ClientContentIoC.cs
@@ -5,6 +5,7 @@
using Content.Client.DebugMon;
using Content.Client.Eui;
using Content.Client.Fullscreen;
+using Content.Client.GameTicking.Managers;
using Content.Client.GhostKick;
using Content.Client.Guidebook;
using Content.Client.Launcher;
@@ -18,8 +19,11 @@
using Content.Client.Voting;
using Content.Shared.Administration.Logs;
using Content.Client.Lobby;
+using Content.Client.Players.RateLimiting;
using Content.Shared.Administration.Managers;
+using Content.Shared.Chat;
using Content.Shared.Players.PlayTimeTracking;
+using Content.Shared.Players.RateLimiting;
namespace Content.Client.IoC
{
@@ -31,6 +35,7 @@ public static void Register()
collection.Register();
collection.Register();
+ collection.Register();
collection.Register();
collection.Register();
collection.Register();
@@ -47,10 +52,13 @@ public static void Register()
collection.Register();
collection.Register();
collection.Register();
- collection.Register();
+ collection.Register();
collection.Register();
collection.Register();
collection.Register();
+ collection.Register();
+ collection.Register();
+ collection.Register();
}
}
}
diff --git a/Content.Client/Paper/UI/PaperBoundUserInterface.cs b/Content.Client/Paper/UI/PaperBoundUserInterface.cs
index 63645bc01e9..ec417f749b9 100644
--- a/Content.Client/Paper/UI/PaperBoundUserInterface.cs
+++ b/Content.Client/Paper/UI/PaperBoundUserInterface.cs
@@ -2,6 +2,7 @@
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Utility;
+using Content.Shared.Paper;
using static Content.Shared.Paper.PaperComponent;
namespace Content.Client.Paper.UI;
@@ -23,6 +24,10 @@ protected override void Open()
_window = this.CreateWindow();
_window.OnSaved += InputOnTextEntered;
+ if (EntMan.TryGetComponent(Owner, out var paper))
+ {
+ _window.MaxInputLength = paper.ContentSize;
+ }
if (EntMan.TryGetComponent(Owner, out var visuals))
{
_window.InitVisuals(Owner, visuals);
diff --git a/Content.Client/Paper/UI/PaperWindow.xaml b/Content.Client/Paper/UI/PaperWindow.xaml
index 2344afd5ef7..503ae928a3e 100644
--- a/Content.Client/Paper/UI/PaperWindow.xaml
+++ b/Content.Client/Paper/UI/PaperWindow.xaml
@@ -15,10 +15,13 @@
-
-
-
+
+
+
+
+
+
diff --git a/Content.Client/Paper/UI/PaperWindow.xaml.cs b/Content.Client/Paper/UI/PaperWindow.xaml.cs
index 81b831068c3..3522aabc66a 100644
--- a/Content.Client/Paper/UI/PaperWindow.xaml.cs
+++ b/Content.Client/Paper/UI/PaperWindow.xaml.cs
@@ -48,6 +48,20 @@ public sealed partial class PaperWindow : BaseWindow
public event Action? OnSaved;
+ private int _MaxInputLength = -1;
+ public int MaxInputLength
+ {
+ get
+ {
+ return _MaxInputLength;
+ }
+ set
+ {
+ _MaxInputLength = value;
+ UpdateFillState();
+ }
+ }
+
public PaperWindow()
{
IoCManager.InjectDependencies(this);
@@ -63,11 +77,21 @@ public PaperWindow()
{
if (args.Function == EngineKeyFunctions.MultilineTextSubmit)
{
- RunOnSaved();
- args.Handle();
+ // SaveButton is disabled when we hit the max input limit. Just check
+ // that flag instead of trying to calculate the input length again
+ if (!SaveButton.Disabled)
+ {
+ RunOnSaved();
+ args.Handle();
+ }
}
};
+ Input.OnTextChanged += args =>
+ {
+ UpdateFillState();
+ };
+
SaveButton.OnPressed += _ =>
{
RunOnSaved();
@@ -126,6 +150,7 @@ public void InitVisuals(EntityUid entity, PaperVisualsComponent visuals)
PaperContent.ModulateSelfOverride = visuals.ContentImageModulate;
WrittenTextLabel.ModulateSelfOverride = visuals.FontAccentColor;
+ FillStatus.ModulateSelfOverride = visuals.FontAccentColor;
var contentImage = visuals.ContentImagePath != null ? _resCache.GetResource(visuals.ContentImagePath) : null;
if (contentImage != null)
@@ -294,7 +319,29 @@ protected override DragMode GetDragModeFor(Vector2 relativeMousePos)
private void RunOnSaved()
{
+ // Prevent further saving while text processing still in
+ SaveButton.Disabled = true;
OnSaved?.Invoke(Rope.Collapse(Input.TextRope));
}
+
+ private void UpdateFillState()
+ {
+ if (MaxInputLength != -1)
+ {
+ var inputLength = Input.TextLength;
+
+ FillStatus.Text = Loc.GetString("paper-ui-fill-level",
+ ("currentLength", inputLength),
+ ("maxLength", MaxInputLength));
+
+ // Disable the save button if we've gone over the limit
+ SaveButton.Disabled = inputLength > MaxInputLength;
+ }
+ else
+ {
+ FillStatus.Text = "";
+ SaveButton.Disabled = false;
+ }
+ }
}
}
diff --git a/Content.Client/Pinpointer/NavMapSystem.Regions.cs b/Content.Client/Pinpointer/NavMapSystem.Regions.cs
new file mode 100644
index 00000000000..4cc775418ec
--- /dev/null
+++ b/Content.Client/Pinpointer/NavMapSystem.Regions.cs
@@ -0,0 +1,303 @@
+using Content.Shared.Atmos;
+using Content.Shared.Pinpointer;
+using System.Linq;
+
+namespace Content.Client.Pinpointer;
+
+public sealed partial class NavMapSystem
+{
+ private (AtmosDirection, Vector2i, AtmosDirection)[] _regionPropagationTable =
+ {
+ (AtmosDirection.East, new Vector2i(1, 0), AtmosDirection.West),
+ (AtmosDirection.West, new Vector2i(-1, 0), AtmosDirection.East),
+ (AtmosDirection.North, new Vector2i(0, 1), AtmosDirection.South),
+ (AtmosDirection.South, new Vector2i(0, -1), AtmosDirection.North),
+ };
+
+ public override void Update(float frameTime)
+ {
+ // To prevent compute spikes, only one region is flood filled per frame
+ var query = AllEntityQuery();
+
+ while (query.MoveNext(out var ent, out var entNavMapRegions))
+ FloodFillNextEnqueuedRegion(ent, entNavMapRegions);
+ }
+
+ private void FloodFillNextEnqueuedRegion(EntityUid uid, NavMapComponent component)
+ {
+ if (!component.QueuedRegionsToFlood.Any())
+ return;
+
+ var regionOwner = component.QueuedRegionsToFlood.Dequeue();
+
+ // If the region is no longer valid, flood the next one in the queue
+ if (!component.RegionProperties.TryGetValue(regionOwner, out var regionProperties) ||
+ !regionProperties.Seeds.Any())
+ {
+ FloodFillNextEnqueuedRegion(uid, component);
+ return;
+ }
+
+ // Flood fill the region, using the region seeds as starting points
+ var (floodedTiles, floodedChunks) = FloodFillRegion(uid, component, regionProperties);
+
+ // Combine the flooded tiles into larger rectangles
+ var gridCoords = GetMergedRegionTiles(floodedTiles);
+
+ // Create and assign the new region overlay
+ var regionOverlay = new NavMapRegionOverlay(regionProperties.UiKey, gridCoords)
+ {
+ Color = regionProperties.Color
+ };
+
+ component.RegionOverlays[regionOwner] = regionOverlay;
+
+ // To reduce unnecessary future flood fills, we will track which chunks have been flooded by a region owner
+
+ // First remove an old assignments
+ if (component.RegionOwnerToChunkTable.TryGetValue(regionOwner, out var oldChunks))
+ {
+ foreach (var chunk in oldChunks)
+ {
+ if (component.ChunkToRegionOwnerTable.TryGetValue(chunk, out var oldOwners))
+ {
+ oldOwners.Remove(regionOwner);
+ component.ChunkToRegionOwnerTable[chunk] = oldOwners;
+ }
+ }
+ }
+
+ // Now update with the new assignments
+ component.RegionOwnerToChunkTable[regionOwner] = floodedChunks;
+
+ foreach (var chunk in floodedChunks)
+ {
+ if (!component.ChunkToRegionOwnerTable.TryGetValue(chunk, out var owners))
+ owners = new();
+
+ owners.Add(regionOwner);
+ component.ChunkToRegionOwnerTable[chunk] = owners;
+ }
+ }
+
+ private (HashSet, HashSet) FloodFillRegion(EntityUid uid, NavMapComponent component, NavMapRegionProperties regionProperties)
+ {
+ if (!regionProperties.Seeds.Any())
+ return (new(), new());
+
+ var visitedChunks = new HashSet();
+ var visitedTiles = new HashSet();
+ var tilesToVisit = new Stack();
+
+ foreach (var regionSeed in regionProperties.Seeds)
+ {
+ tilesToVisit.Push(regionSeed);
+
+ while (tilesToVisit.Count > 0)
+ {
+ // If the max region area is hit, exit
+ if (visitedTiles.Count > regionProperties.MaxArea)
+ return (new(), new());
+
+ // Pop the top tile from the stack
+ var current = tilesToVisit.Pop();
+
+ // If the current tile position has already been visited,
+ // or is too far away from the seed, continue
+ if ((regionSeed - current).Length > regionProperties.MaxRadius)
+ continue;
+
+ if (visitedTiles.Contains(current))
+ continue;
+
+ // Determine the tile's chunk index
+ var chunkOrigin = SharedMapSystem.GetChunkIndices(current, ChunkSize);
+ var relative = SharedMapSystem.GetChunkRelative(current, ChunkSize);
+ var idx = GetTileIndex(relative);
+
+ // Extract the tile data
+ if (!component.Chunks.TryGetValue(chunkOrigin, out var chunk))
+ continue;
+
+ var flag = chunk.TileData[idx];
+
+ // If the current tile is entirely occupied, continue
+ if ((FloorMask & flag) == 0)
+ continue;
+
+ if ((WallMask & flag) == WallMask)
+ continue;
+
+ if ((AirlockMask & flag) == AirlockMask)
+ continue;
+
+ // Otherwise the tile can be added to this region
+ visitedTiles.Add(current);
+ visitedChunks.Add(chunkOrigin);
+
+ // Determine if we can propagate the region into its cardinally adjacent neighbors
+ // To propagate to a neighbor, movement into the neighbors closest edge must not be
+ // blocked, and vice versa
+
+ foreach (var (direction, tileOffset, reverseDirection) in _regionPropagationTable)
+ {
+ if (!RegionCanPropagateInDirection(chunk, current, direction))
+ continue;
+
+ var neighbor = current + tileOffset;
+ var neighborOrigin = SharedMapSystem.GetChunkIndices(neighbor, ChunkSize);
+
+ if (!component.Chunks.TryGetValue(neighborOrigin, out var neighborChunk))
+ continue;
+
+ visitedChunks.Add(neighborOrigin);
+
+ if (!RegionCanPropagateInDirection(neighborChunk, neighbor, reverseDirection))
+ continue;
+
+ tilesToVisit.Push(neighbor);
+ }
+ }
+ }
+
+ return (visitedTiles, visitedChunks);
+ }
+
+ private bool RegionCanPropagateInDirection(NavMapChunk chunk, Vector2i tile, AtmosDirection direction)
+ {
+ var relative = SharedMapSystem.GetChunkRelative(tile, ChunkSize);
+ var idx = GetTileIndex(relative);
+ var flag = chunk.TileData[idx];
+
+ if ((FloorMask & flag) == 0)
+ return false;
+
+ var directionMask = 1 << (int)direction;
+ var wallMask = (int)direction << (int)NavMapChunkType.Wall;
+ var airlockMask = (int)direction << (int)NavMapChunkType.Airlock;
+
+ if ((wallMask & flag) > 0)
+ return false;
+
+ if ((airlockMask & flag) > 0)
+ return false;
+
+ return true;
+ }
+
+ private List<(Vector2i, Vector2i)> GetMergedRegionTiles(HashSet tiles)
+ {
+ if (!tiles.Any())
+ return new();
+
+ var x = tiles.Select(t => t.X);
+ var minX = x.Min();
+ var maxX = x.Max();
+
+ var y = tiles.Select(t => t.Y);
+ var minY = y.Min();
+ var maxY = y.Max();
+
+ var matrix = new int[maxX - minX + 1, maxY - minY + 1];
+
+ foreach (var tile in tiles)
+ {
+ var a = tile.X - minX;
+ var b = tile.Y - minY;
+
+ matrix[a, b] = 1;
+ }
+
+ return GetMergedRegionTiles(matrix, new Vector2i(minX, minY));
+ }
+
+ private List<(Vector2i, Vector2i)> GetMergedRegionTiles(int[,] matrix, Vector2i offset)
+ {
+ var output = new List<(Vector2i, Vector2i)>();
+
+ var rows = matrix.GetLength(0);
+ var cols = matrix.GetLength(1);
+
+ var dp = new int[rows, cols];
+ var coords = (new Vector2i(), new Vector2i());
+ var maxArea = 0;
+
+ var count = 0;
+
+ while (!IsArrayEmpty(matrix))
+ {
+ count++;
+
+ if (count > rows * cols)
+ break;
+
+ // Clear old values
+ dp = new int[rows, cols];
+ coords = (new Vector2i(), new Vector2i());
+ maxArea = 0;
+
+ // Initialize the first row of dp
+ for (int j = 0; j < cols; j++)
+ {
+ dp[0, j] = matrix[0, j];
+ }
+
+ // Calculate dp values for remaining rows
+ for (int i = 1; i < rows; i++)
+ {
+ for (int j = 0; j < cols; j++)
+ dp[i, j] = matrix[i, j] == 1 ? dp[i - 1, j] + 1 : 0;
+ }
+
+ // Find the largest rectangular area seeded for each position in the matrix
+ for (int i = 0; i < rows; i++)
+ {
+ for (int j = 0; j < cols; j++)
+ {
+ int minWidth = dp[i, j];
+
+ for (int k = j; k >= 0; k--)
+ {
+ if (dp[i, k] <= 0)
+ break;
+
+ minWidth = Math.Min(minWidth, dp[i, k]);
+ var currArea = Math.Max(maxArea, minWidth * (j - k + 1));
+
+ if (currArea > maxArea)
+ {
+ maxArea = currArea;
+ coords = (new Vector2i(i - minWidth + 1, k), new Vector2i(i, j));
+ }
+ }
+ }
+ }
+
+ // Save the recorded rectangle vertices
+ output.Add((coords.Item1 + offset, coords.Item2 + offset));
+
+ // Removed the tiles covered by the rectangle from matrix
+ for (int i = coords.Item1.X; i <= coords.Item2.X; i++)
+ {
+ for (int j = coords.Item1.Y; j <= coords.Item2.Y; j++)
+ matrix[i, j] = 0;
+ }
+ }
+
+ return output;
+ }
+
+ private bool IsArrayEmpty(int[,] matrix)
+ {
+ for (int i = 0; i < matrix.GetLength(0); i++)
+ {
+ for (int j = 0; j < matrix.GetLength(1); j++)
+ {
+ if (matrix[i, j] == 1)
+ return false;
+ }
+ }
+
+ return true;
+ }
+}
diff --git a/Content.Client/Pinpointer/NavMapSystem.cs b/Content.Client/Pinpointer/NavMapSystem.cs
index 9aeb792a429..47469d4ea79 100644
--- a/Content.Client/Pinpointer/NavMapSystem.cs
+++ b/Content.Client/Pinpointer/NavMapSystem.cs
@@ -1,3 +1,4 @@
+using System.Linq;
using Content.Shared.Pinpointer;
using Robust.Shared.GameStates;
@@ -16,6 +17,7 @@ private void OnHandleState(EntityUid uid, NavMapComponent component, ref Compone
{
Dictionary modifiedChunks;
Dictionary beacons;
+ Dictionary regions;
switch (args.Current)
{
@@ -23,6 +25,8 @@ private void OnHandleState(EntityUid uid, NavMapComponent component, ref Compone
{
modifiedChunks = delta.ModifiedChunks;
beacons = delta.Beacons;
+ regions = delta.Regions;
+
foreach (var index in component.Chunks.Keys)
{
if (!delta.AllChunks!.Contains(index))
@@ -35,6 +39,8 @@ private void OnHandleState(EntityUid uid, NavMapComponent component, ref Compone
{
modifiedChunks = state.Chunks;
beacons = state.Beacons;
+ regions = state.Regions;
+
foreach (var index in component.Chunks.Keys)
{
if (!state.Chunks.ContainsKey(index))
@@ -47,13 +53,54 @@ private void OnHandleState(EntityUid uid, NavMapComponent component, ref Compone
return;
}
+ // Update region data and queue new regions for flooding
+ var prevRegionOwners = component.RegionProperties.Keys.ToList();
+ var validRegionOwners = new List();
+
+ component.RegionProperties.Clear();
+
+ foreach (var (regionOwner, regionData) in regions)
+ {
+ if (!regionData.Seeds.Any())
+ continue;
+
+ component.RegionProperties[regionOwner] = regionData;
+ validRegionOwners.Add(regionOwner);
+
+ if (component.RegionOverlays.ContainsKey(regionOwner))
+ continue;
+
+ if (component.QueuedRegionsToFlood.Contains(regionOwner))
+ continue;
+
+ component.QueuedRegionsToFlood.Enqueue(regionOwner);
+ }
+
+ // Remove stale region owners
+ var regionOwnersToRemove = prevRegionOwners.Except(validRegionOwners);
+
+ foreach (var regionOwnerRemoved in regionOwnersToRemove)
+ RemoveNavMapRegion(uid, component, regionOwnerRemoved);
+
+ // Modify chunks
foreach (var (origin, chunk) in modifiedChunks)
{
var newChunk = new NavMapChunk(origin);
Array.Copy(chunk, newChunk.TileData, chunk.Length);
component.Chunks[origin] = newChunk;
+
+ // If the affected chunk intersects one or more regions, re-flood them
+ if (!component.ChunkToRegionOwnerTable.TryGetValue(origin, out var affectedOwners))
+ continue;
+
+ foreach (var affectedOwner in affectedOwners)
+ {
+ if (!component.QueuedRegionsToFlood.Contains(affectedOwner))
+ component.QueuedRegionsToFlood.Enqueue(affectedOwner);
+ }
}
+ // Refresh beacons
component.Beacons.Clear();
foreach (var (nuid, beacon) in beacons)
{
diff --git a/Content.Client/Pinpointer/UI/NavMapControl.cs b/Content.Client/Pinpointer/UI/NavMapControl.cs
index 413b41c36a6..90c2680c4a7 100644
--- a/Content.Client/Pinpointer/UI/NavMapControl.cs
+++ b/Content.Client/Pinpointer/UI/NavMapControl.cs
@@ -48,6 +48,7 @@ public partial class NavMapControl : MapGridControl
public List<(Vector2, Vector2)> TileLines = new();
public List<(Vector2, Vector2)> TileRects = new();
public List<(Vector2[], Color)> TilePolygons = new();
+ public List RegionOverlays = new();
// Default colors
public Color WallColor = new(102, 217, 102);
@@ -228,7 +229,7 @@ protected override void KeyBindUp(GUIBoundKeyEventArgs args)
{
if (!blip.Selectable)
continue;
-
+
var currentDistance = (_transformSystem.ToMapCoordinates(blip.Coordinates).Position - worldPosition).Length();
if (closestDistance < currentDistance || currentDistance * MinimapScale > MaxSelectableDistance)
@@ -319,6 +320,22 @@ protected override void Draw(DrawingHandleScreen handle)
}
}
+ // Draw region overlays
+ if (_grid != null)
+ {
+ foreach (var regionOverlay in RegionOverlays)
+ {
+ foreach (var gridCoords in regionOverlay.GridCoords)
+ {
+ var positionTopLeft = ScalePosition(new Vector2(gridCoords.Item1.X, -gridCoords.Item1.Y) - new Vector2(offset.X, -offset.Y));
+ var positionBottomRight = ScalePosition(new Vector2(gridCoords.Item2.X + _grid.TileSize, -gridCoords.Item2.Y - _grid.TileSize) - new Vector2(offset.X, -offset.Y));
+
+ var box = new UIBox2(positionTopLeft, positionBottomRight);
+ handle.DrawRect(box, regionOverlay.Color);
+ }
+ }
+ }
+
// Draw map lines
if (TileLines.Any())
{
diff --git a/Content.Client/Players/PlayTimeTracking/JobRequirementsManager.cs b/Content.Client/Players/PlayTimeTracking/JobRequirementsManager.cs
index 7056a15e0e4..faf8aa28d57 100644
--- a/Content.Client/Players/PlayTimeTracking/JobRequirementsManager.cs
+++ b/Content.Client/Players/PlayTimeTracking/JobRequirementsManager.cs
@@ -52,6 +52,8 @@ private void ClientOnRunLevelChanged(object? sender, RunLevelChangedEventArgs e)
{
// Reset on disconnect, just in case.
_roles.Clear();
+ _jobWhitelists.Clear();
+ _roleBans.Clear();
}
}
@@ -59,9 +61,6 @@ 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();
diff --git a/Content.Client/Players/RateLimiting/PlayerRateLimitManager.cs b/Content.Client/Players/RateLimiting/PlayerRateLimitManager.cs
new file mode 100644
index 00000000000..e79eadd92b1
--- /dev/null
+++ b/Content.Client/Players/RateLimiting/PlayerRateLimitManager.cs
@@ -0,0 +1,23 @@
+using Content.Shared.Players.RateLimiting;
+using Robust.Shared.Player;
+
+namespace Content.Client.Players.RateLimiting;
+
+public sealed class PlayerRateLimitManager : SharedPlayerRateLimitManager
+{
+ public override RateLimitStatus CountAction(ICommonSession player, string key)
+ {
+ // TODO Rate-Limit
+ // Add support for rate limit prediction
+ // I.e., dont mis-predict just because somebody is clicking too quickly.
+ return RateLimitStatus.Allowed;
+ }
+
+ public override void Register(string key, RateLimitRegistration registration)
+ {
+ }
+
+ public override void Initialize()
+ {
+ }
+}
diff --git a/Content.Client/Popups/PopupSystem.cs b/Content.Client/Popups/PopupSystem.cs
index 700f6b6d26f..a249c9251bf 100644
--- a/Content.Client/Popups/PopupSystem.cs
+++ b/Content.Client/Popups/PopupSystem.cs
@@ -148,7 +148,12 @@ private void PopupCursorInternal(string? message, PopupType type, bool recordRep
}
public override void PopupCursor(string? message, PopupType type = PopupType.Small)
- => PopupCursorInternal(message, type, true);
+ {
+ if (!_timing.IsFirstTimePredicted)
+ return;
+
+ PopupCursorInternal(message, type, true);
+ }
public override void PopupCursor(string? message, ICommonSession recipient, PopupType type = PopupType.Small)
{
diff --git a/Content.Client/Power/APC/ApcBoundUserInterface.cs b/Content.Client/Power/APC/ApcBoundUserInterface.cs
index a790c5d984a..5c4036a9159 100644
--- a/Content.Client/Power/APC/ApcBoundUserInterface.cs
+++ b/Content.Client/Power/APC/ApcBoundUserInterface.cs
@@ -1,8 +1,9 @@
-using Content.Client.Power.APC.UI;
+using Content.Client.Power.APC.UI;
+using Content.Shared.Access.Systems;
using Content.Shared.APC;
using JetBrains.Annotations;
-using Robust.Client.GameObjects;
using Robust.Client.UserInterface;
+using Robust.Shared.Player;
namespace Content.Client.Power.APC
{
@@ -22,6 +23,14 @@ protected override void Open()
_menu = this.CreateWindow();
_menu.SetEntity(Owner);
_menu.OnBreaker += BreakerPressed;
+
+ var hasAccess = false;
+ if (PlayerManager.LocalEntity != null)
+ {
+ var accessReader = EntMan.System();
+ hasAccess = accessReader.IsAllowed((EntityUid)PlayerManager.LocalEntity, Owner);
+ }
+ _menu?.SetAccessEnabled(hasAccess);
}
protected override void UpdateState(BoundUserInterfaceState state)
diff --git a/Content.Client/Power/APC/UI/ApcMenu.xaml.cs b/Content.Client/Power/APC/UI/ApcMenu.xaml.cs
index 2f61ea63a86..25e885b3c7a 100644
--- a/Content.Client/Power/APC/UI/ApcMenu.xaml.cs
+++ b/Content.Client/Power/APC/UI/ApcMenu.xaml.cs
@@ -1,4 +1,4 @@
-using Robust.Client.AutoGenerated;
+using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.XAML;
using Robust.Client.GameObjects;
using Robust.Shared.IoC;
@@ -36,19 +36,9 @@ public void UpdateState(BoundUserInterfaceState state)
{
var castState = (ApcBoundInterfaceState) state;
- if (BreakerButton != null)
+ if (!BreakerButton.Disabled)
{
- if(castState.HasAccess == false)
- {
- BreakerButton.Disabled = true;
- BreakerButton.ToolTip = Loc.GetString("apc-component-insufficient-access");
- }
- else
- {
- BreakerButton.Disabled = false;
- BreakerButton.ToolTip = null;
- BreakerButton.Pressed = castState.MainBreaker;
- }
+ BreakerButton.Pressed = castState.MainBreaker;
}
if (PowerLabel != null)
@@ -86,6 +76,20 @@ public void UpdateState(BoundUserInterfaceState state)
}
}
+ public void SetAccessEnabled(bool hasAccess)
+ {
+ if(hasAccess)
+ {
+ BreakerButton.Disabled = false;
+ BreakerButton.ToolTip = null;
+ }
+ else
+ {
+ BreakerButton.Disabled = true;
+ BreakerButton.ToolTip = Loc.GetString("apc-component-insufficient-access");
+ }
+ }
+
private void UpdateChargeBarColor(float charge)
{
if (ChargeBar == null)
diff --git a/Content.Client/Silicons/StationAi/StationAiSystem.Airlock.cs b/Content.Client/Silicons/StationAi/StationAiSystem.Airlock.cs
index bf6b65a9697..d5bc764b348 100644
--- a/Content.Client/Silicons/StationAi/StationAiSystem.Airlock.cs
+++ b/Content.Client/Silicons/StationAi/StationAiSystem.Airlock.cs
@@ -1,4 +1,5 @@
using Content.Shared.Doors.Components;
+using Content.Shared.Electrocution;
using Content.Shared.Silicons.StationAi;
using Robust.Shared.Utility;
@@ -6,25 +7,69 @@ namespace Content.Client.Silicons.StationAi;
public sealed partial class StationAiSystem
{
+ private readonly ResPath _aiActionsRsi = new ResPath("/Textures/Interface/Actions/actions_ai.rsi");
+
private void InitializeAirlock()
{
SubscribeLocalEvent(OnDoorBoltGetRadial);
+ SubscribeLocalEvent(OnEmergencyAccessGetRadial);
+ SubscribeLocalEvent(OnDoorElectrifiedGetRadial);
}
private void OnDoorBoltGetRadial(Entity ent, ref GetStationAiRadialEvent args)
{
- args.Actions.Add(new StationAiRadial()
- {
- Sprite = ent.Comp.BoltsDown ?
- new SpriteSpecifier.Rsi(
- new ResPath("/Textures/Structures/Doors/Airlocks/Standard/basic.rsi"), "open") :
- new SpriteSpecifier.Rsi(
- new ResPath("/Textures/Structures/Doors/Airlocks/Standard/basic.rsi"), "closed"),
- Tooltip = ent.Comp.BoltsDown ? Loc.GetString("bolt-open") : Loc.GetString("bolt-close"),
- Event = new StationAiBoltEvent()
+ args.Actions.Add(
+ new StationAiRadial
+ {
+ Sprite = ent.Comp.BoltsDown
+ ? new SpriteSpecifier.Rsi(_aiActionsRsi, "unbolt_door")
+ : new SpriteSpecifier.Rsi(_aiActionsRsi, "bolt_door"),
+ Tooltip = ent.Comp.BoltsDown
+ ? Loc.GetString("bolt-open")
+ : Loc.GetString("bolt-close"),
+ Event = new StationAiBoltEvent
+ {
+ Bolted = !ent.Comp.BoltsDown,
+ }
+ }
+ );
+ }
+
+ private void OnEmergencyAccessGetRadial(Entity ent, ref GetStationAiRadialEvent args)
+ {
+ args.Actions.Add(
+ new StationAiRadial
+ {
+ Sprite = ent.Comp.EmergencyAccess
+ ? new SpriteSpecifier.Rsi(_aiActionsRsi, "emergency_off")
+ : new SpriteSpecifier.Rsi(_aiActionsRsi, "emergency_on"),
+ Tooltip = ent.Comp.EmergencyAccess
+ ? Loc.GetString("emergency-access-off")
+ : Loc.GetString("emergency-access-on"),
+ Event = new StationAiEmergencyAccessEvent
+ {
+ EmergencyAccess = !ent.Comp.EmergencyAccess,
+ }
+ }
+ );
+ }
+
+ private void OnDoorElectrifiedGetRadial(Entity ent, ref GetStationAiRadialEvent args)
+ {
+ args.Actions.Add(
+ new StationAiRadial
{
- Bolted = !ent.Comp.BoltsDown,
+ Sprite = ent.Comp.Enabled
+ ? new SpriteSpecifier.Rsi(_aiActionsRsi, "door_overcharge_off")
+ : new SpriteSpecifier.Rsi(_aiActionsRsi, "door_overcharge_on"),
+ Tooltip = ent.Comp.Enabled
+ ? Loc.GetString("electrify-door-off")
+ : Loc.GetString("electrify-door-on"),
+ Event = new StationAiElectrifiedEvent
+ {
+ Electrified = !ent.Comp.Enabled,
+ }
}
- });
+ );
}
}
diff --git a/Content.Client/UserInterface/Systems/Actions/ActionUIController.cs b/Content.Client/UserInterface/Systems/Actions/ActionUIController.cs
index 1dffeb8d2d4..a6c1cfc94f8 100644
--- a/Content.Client/UserInterface/Systems/Actions/ActionUIController.cs
+++ b/Content.Client/UserInterface/Systems/Actions/ActionUIController.cs
@@ -398,10 +398,6 @@ private void OnActionsUpdated()
{
QueueWindowUpdate();
- // TODO ACTIONS allow buttons to persist across state applications
- // Then we don't have to interrupt drags any time the buttons get rebuilt.
- _menuDragHelper.EndDrag();
-
if (_actionsSystem != null)
_container?.SetActionData(_actionsSystem, _actions.ToArray());
}
diff --git a/Content.Client/UserInterface/Systems/Actions/Controls/ActionButtonContainer.cs b/Content.Client/UserInterface/Systems/Actions/Controls/ActionButtonContainer.cs
index 38c08dc4721..67b96d03307 100644
--- a/Content.Client/UserInterface/Systems/Actions/Controls/ActionButtonContainer.cs
+++ b/Content.Client/UserInterface/Systems/Actions/Controls/ActionButtonContainer.cs
@@ -28,14 +28,26 @@ public ActionButton this[int index]
get => (ActionButton) GetChild(index);
}
- private void BuildActionButtons(int count)
+ public void SetActionData(ActionsSystem system, params EntityUid?[] actionTypes)
{
+ var uniqueCount = Math.Min(system.GetClientActions().Count(), actionTypes.Length + 1);
var keys = ContentKeyFunctions.GetHotbarBoundKeys();
- Children.Clear();
- for (var index = 0; index < count; index++)
+ for (var i = 0; i < uniqueCount; i++)
+ {
+ if (i >= ChildCount)
+ {
+ AddChild(MakeButton(i));
+ }
+
+ if (!actionTypes.TryGetValue(i, out var action))
+ action = null;
+ ((ActionButton) GetChild(i)).UpdateData(action, system);
+ }
+
+ for (var i = ChildCount - 1; i >= uniqueCount; i--)
{
- Children.Add(MakeButton(index));
+ RemoveChild(GetChild(i));
}
ActionButton MakeButton(int index)
@@ -55,20 +67,6 @@ ActionButton MakeButton(int index)
}
}
- public void SetActionData(ActionsSystem system, params EntityUid?[] actionTypes)
- {
- var uniqueCount = Math.Min(system.GetClientActions().Count(), actionTypes.Length + 1);
- if (ChildCount != uniqueCount)
- BuildActionButtons(uniqueCount);
-
- for (var i = 0; i < uniqueCount; i++)
- {
- if (!actionTypes.TryGetValue(i, out var action))
- action = null;
- ((ActionButton) GetChild(i)).UpdateData(action, system);
- }
- }
-
public void ClearActionData()
{
foreach (var button in Children)
diff --git a/Content.Client/VendingMachines/UI/VendingMachineMenu.xaml b/Content.Client/VendingMachines/UI/VendingMachineMenu.xaml
index 913b07a8f65..44b1ff95e7f 100644
--- a/Content.Client/VendingMachines/UI/VendingMachineMenu.xaml
+++ b/Content.Client/VendingMachines/UI/VendingMachineMenu.xaml
@@ -2,7 +2,8 @@
xmlns="https://spacestation14.io"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
- xmlns:co="clr-namespace:Content.Client.UserInterface.Controls">
+ xmlns:co="clr-namespace:Content.Client.UserInterface.Controls"
+ MinHeight="210">
diff --git a/Content.Client/VendingMachines/VendingMachineBoundUserInterface.cs b/Content.Client/VendingMachines/VendingMachineBoundUserInterface.cs
index f8313882a2f..28b1b25adef 100644
--- a/Content.Client/VendingMachines/VendingMachineBoundUserInterface.cs
+++ b/Content.Client/VendingMachines/VendingMachineBoundUserInterface.cs
@@ -23,29 +23,17 @@ protected override void Open()
{
base.Open();
- var vendingMachineSys = EntMan.System();
-
- _cachedInventory = vendingMachineSys.GetAllInventory(Owner);
-
_menu = this.CreateWindow();
_menu.OpenCenteredLeft();
_menu.Title = EntMan.GetComponent(Owner).EntityName;
-
_menu.OnItemSelected += OnItemSelected;
-
- _menu.Populate(_cachedInventory);
-
- _menu.OpenCenteredLeft();
+ Refresh();
}
- protected override void UpdateState(BoundUserInterfaceState state)
+ public void Refresh()
{
- base.UpdateState(state);
-
- if (state is not VendingMachineInterfaceState newState)
- return;
-
- _cachedInventory = newState.Inventory;
+ var system = EntMan.System();
+ _cachedInventory = system.GetAllInventory(Owner);
_menu?.Populate(_cachedInventory);
}
diff --git a/Content.Client/VendingMachines/VendingMachineSystem.cs b/Content.Client/VendingMachines/VendingMachineSystem.cs
index 922a75d24a2..1b1dde2b67e 100644
--- a/Content.Client/VendingMachines/VendingMachineSystem.cs
+++ b/Content.Client/VendingMachines/VendingMachineSystem.cs
@@ -8,6 +8,7 @@ public sealed class VendingMachineSystem : SharedVendingMachineSystem
{
[Dependency] private readonly AnimationPlayerSystem _animationPlayer = default!;
[Dependency] private readonly SharedAppearanceSystem _appearanceSystem = default!;
+ [Dependency] private readonly SharedUserInterfaceSystem _uiSystem = default!;
public override void Initialize()
{
@@ -15,6 +16,15 @@ public override void Initialize()
SubscribeLocalEvent(OnAppearanceChange);
SubscribeLocalEvent(OnAnimationCompleted);
+ SubscribeLocalEvent(OnVendingAfterState);
+ }
+
+ private void OnVendingAfterState(EntityUid uid, VendingMachineComponent component, ref AfterAutoHandleStateEvent args)
+ {
+ if (_uiSystem.TryGetOpenUi(uid, VendingMachineUiKey.Key, out var bui))
+ {
+ bui.Refresh();
+ }
}
private void OnAnimationCompleted(EntityUid uid, VendingMachineComponent component, AnimationCompletedEvent args)
diff --git a/Content.Client/Verbs/VerbSystem.cs b/Content.Client/Verbs/VerbSystem.cs
index f990c83d7c2..f592303d281 100644
--- a/Content.Client/Verbs/VerbSystem.cs
+++ b/Content.Client/Verbs/VerbSystem.cs
@@ -1,9 +1,9 @@
using System.Diagnostics.CodeAnalysis;
-using System.Linq;
using System.Numerics;
using Content.Client.Examine;
using Content.Client.Gameplay;
using Content.Client.Popups;
+using Content.Shared.CCVar;
using Content.Shared.Examine;
using Content.Shared.Tag;
using Content.Shared.Verbs;
@@ -13,6 +13,8 @@
using Robust.Client.Graphics;
using Robust.Client.Player;
using Robust.Client.State;
+using Robust.Shared.Configuration;
+using Robust.Shared.Containers;
using Robust.Shared.Map;
using Robust.Shared.Utility;
@@ -28,11 +30,11 @@ public sealed class VerbSystem : SharedVerbSystem
[Dependency] private readonly IStateManager _stateManager = default!;
[Dependency] private readonly IEyeManager _eyeManager = default!;
[Dependency] private readonly IPlayerManager _playerManager = default!;
+ [Dependency] private readonly SharedContainerSystem _containers = default!;
+ [Dependency] private readonly IConfigurationManager _cfg = default!;
+ [Dependency] private readonly EntityLookupSystem _lookup = default!;
- ///
- /// When a user right clicks somewhere, how large is the box we use to get entities for the context menu?
- ///
- public const float EntityMenuLookupSize = 0.25f;
+ private float _lookupSize;
///
/// These flags determine what entities the user can see on the context menu.
@@ -41,114 +43,127 @@ public sealed class VerbSystem : SharedVerbSystem
public Action? OnVerbsResponse;
- private List _entities = new();
-
public override void Initialize()
{
base.Initialize();
SubscribeNetworkEvent(HandleVerbResponse);
+ Subs.CVar(_cfg, CCVars.GameEntityMenuLookup, OnLookupChanged, true);
+ }
+
+ private void OnLookupChanged(float val)
+ {
+ _lookupSize = val;
}
///
- /// Get all of the entities in an area for displaying on the context menu.
+ /// Get all of the entities in an area for displaying on the context menu.
///
- public bool TryGetEntityMenuEntities(MapCoordinates targetPos, [NotNullWhen(true)] out List? result)
+ /// True if any entities were found.
+ public bool TryGetEntityMenuEntities(MapCoordinates targetPos, [NotNullWhen(true)] out List? entities)
{
- result = null;
+ entities = null;
- if (_stateManager.CurrentState is not GameplayStateBase gameScreenBase)
+ if (_stateManager.CurrentState is not GameplayStateBase)
return false;
- var player = _playerManager.LocalEntity;
- if (player == null)
+ if (_playerManager.LocalEntity is not { } player)
return false;
// If FOV drawing is disabled, we will modify the visibility option to ignore visiblity checks.
- var visibility = _eyeManager.CurrentEye.DrawFov
- ? Visibility
- : Visibility | MenuVisibility.NoFov;
+ var visibility = _eyeManager.CurrentEye.DrawFov ? Visibility : Visibility | MenuVisibility.NoFov;
- var ev = new MenuVisibilityEvent()
+ var ev = new MenuVisibilityEvent
{
TargetPos = targetPos,
Visibility = visibility,
};
- RaiseLocalEvent(player.Value, ref ev);
+ RaiseLocalEvent(player, ref ev);
visibility = ev.Visibility;
- // Get entities
- _entities.Clear();
- var entitiesUnderMouse = _tree.QueryAabb(targetPos.MapId, Box2.CenteredAround(targetPos.Position, new Vector2(EntityMenuLookupSize, EntityMenuLookupSize)));
-
- // Do we have to do FoV checks?
- if ((visibility & MenuVisibility.NoFov) == 0)
+ // Initially, we include all entities returned by a sprite area lookup
+ var box = Box2.CenteredAround(targetPos.Position, new Vector2(_lookupSize, _lookupSize));
+ var queryResult = _tree.QueryAabb(targetPos.MapId, box);
+ entities = new List(queryResult.Count);
+ foreach (var ent in queryResult)
{
- bool Predicate(EntityUid e) => e == player;
-
- TryComp(player.Value, out ExaminerComponent? examiner);
-
- foreach (var ent in entitiesUnderMouse)
- {
- if (_examine.CanExamine(player.Value, targetPos, Predicate, ent.Uid, examiner))
- _entities.Add(ent.Uid);
- }
+ entities.Add(ent.Uid);
}
- else
+
+ // If we're in a container list all other entities in it.
+ // E.g., allow players in lockers to examine / interact with other entities in the same locker
+ if (_containers.TryGetContainingContainer((player, null), out var container))
{
- foreach (var ent in entitiesUnderMouse)
+ // Only include the container contents when clicking near it.
+ if (entities.Contains(container.Owner)
+ || _containers.TryGetOuterContainer(container.Owner, Transform(container.Owner), out var outer)
+ && entities.Contains(outer.Owner))
{
- _entities.Add(ent.Uid);
+ // The container itself might be in some other container, so it might not have been added by the
+ // sprite tree lookup.
+ if (!entities.Contains(container.Owner))
+ entities.Add(container.Owner);
+
+ // TODO Context Menu
+ // This might miss entities in some situations. E.g., one of the contained entities entity in it, that
+ // itself has another entity attached to it, then we should be able to "see" that entity.
+ // E.g., if a security guard is on a segway and gets thrown in a locker, this wouldn't let you see the guard.
+ foreach (var ent in container.ContainedEntities)
+ {
+ if (!entities.Contains(ent))
+ entities.Add(ent);
+ }
}
}
- if (_entities.Count == 0)
- return false;
-
- if (visibility == MenuVisibility.All)
+ if ((visibility & MenuVisibility.InContainer) != 0)
{
- result = new (_entities);
- return true;
+ // This is inefficient, but I'm lazy and CBF implementing my own recursive container method. Note that
+ // this might actually fail to add the contained children of some entities in the menu. E.g., an entity
+ // with a large sprite aabb, but small broadphase might appear in the menu, but have its children added
+ // by this.
+ var flags = LookupFlags.All & ~LookupFlags.Sensors;
+ foreach (var e in _lookup.GetEntitiesInRange(targetPos, _lookupSize, flags: flags))
+ {
+ if (!entities.Contains(e))
+ entities.Add(e);
+ }
}
- // remove any entities in containers
- if ((visibility & MenuVisibility.InContainer) == 0)
+ // Do we have to do FoV checks?
+ if ((visibility & MenuVisibility.NoFov) == 0)
{
- for (var i = _entities.Count - 1; i >= 0; i--)
+ TryComp(player, out ExaminerComponent? examiner);
+ for (var i = entities.Count - 1; i >= 0; i--)
{
- var entity = _entities[i];
-
- if (ContainerSystem.IsInSameOrTransparentContainer(player.Value, entity))
- continue;
-
- _entities.RemoveSwap(i);
+ if (!_examine.CanExamine(player, targetPos, e => e == player, entities[i], examiner))
+ entities.RemoveSwap(i);
}
}
- // remove any invisible entities
- if ((visibility & MenuVisibility.Invisible) == 0)
+ if ((visibility & MenuVisibility.Invisible) != 0)
+ return entities.Count != 0;
+
+ for (var i = entities.Count - 1; i >= 0; i--)
{
- var spriteQuery = GetEntityQuery();
+ if (_tagSystem.HasTag(entities[i], "HideContextMenu"))
+ entities.RemoveSwap(i);
+ }
- for (var i = _entities.Count - 1; i >= 0; i--)
- {
- var entity = _entities[i];
+ // Unless we added entities in containers, every entity should already have a visible sprite due to
+ // the fact that we used the sprite tree query.
+ if (container == null && (visibility & MenuVisibility.InContainer) == 0)
+ return entities.Count != 0;
- if (!spriteQuery.TryGetComponent(entity, out var spriteComponent) ||
- !spriteComponent.Visible ||
- _tagSystem.HasTag(entity, "HideContextMenu"))
- {
- _entities.RemoveSwap(i);
- }
- }
+ var spriteQuery = GetEntityQuery();
+ for (var i = entities.Count - 1; i >= 0; i--)
+ {
+ if (!spriteQuery.TryGetComponent(entities[i], out var spriteComponent) || !spriteComponent.Visible)
+ entities.RemoveSwap(i);
}
- if (_entities.Count == 0)
- return false;
-
- result = new(_entities);
- return true;
+ return entities.Count != 0;
}
///
diff --git a/Content.Client/VoiceMask/VoiceMaskBoundUserInterface.cs b/Content.Client/VoiceMask/VoiceMaskBoundUserInterface.cs
index 891804674d3..e76ca1cf8f7 100644
--- a/Content.Client/VoiceMask/VoiceMaskBoundUserInterface.cs
+++ b/Content.Client/VoiceMask/VoiceMaskBoundUserInterface.cs
@@ -22,6 +22,7 @@ protected override void Open()
_window = this.CreateWindow();
_window.ReloadVerbs(_protomanager);
+ _window.AddVerbs();
_window.OnNameChange += OnNameSelected;
_window.OnVerbChange += verb => SendMessage(new VoiceMaskChangeVerbMessage(verb));
diff --git a/Content.Client/VoiceMask/VoiceMaskNameChangeWindow.xaml.cs b/Content.Client/VoiceMask/VoiceMaskNameChangeWindow.xaml.cs
index 0dc41f807ab..7ca4dd4b957 100644
--- a/Content.Client/VoiceMask/VoiceMaskNameChangeWindow.xaml.cs
+++ b/Content.Client/VoiceMask/VoiceMaskNameChangeWindow.xaml.cs
@@ -31,8 +31,6 @@ public VoiceMaskNameChangeWindow()
OnVerbChange?.Invoke((string?) args.Button.GetItemMetadata(args.Id));
SpeechVerbSelector.SelectId(args.Id);
};
-
- AddVerbs();
}
public void ReloadVerbs(IPrototypeManager proto)
@@ -44,7 +42,7 @@ public void ReloadVerbs(IPrototypeManager proto)
_verbs.Sort((a, b) => a.Item1.CompareTo(b.Item1));
}
- private void AddVerbs()
+ public void AddVerbs()
{
SpeechVerbSelector.Clear();
diff --git a/Content.Client/Voting/UI/VoteCallMenu.xaml b/Content.Client/Voting/UI/VoteCallMenu.xaml
index cb03dd6bb88..caca4fd553d 100644
--- a/Content.Client/Voting/UI/VoteCallMenu.xaml
+++ b/Content.Client/Voting/UI/VoteCallMenu.xaml
@@ -1,7 +1,7 @@
-
+ MouseFilter="Stop" MinSize="350 200">
@@ -13,16 +13,18 @@
-
-
-
-
-
+
+
+
+
+
+
+
-
+
-
-
+
+
diff --git a/Content.Client/Voting/UI/VoteCallMenu.xaml.cs b/Content.Client/Voting/UI/VoteCallMenu.xaml.cs
index 0eede4c4804..c5746c24d79 100644
--- a/Content.Client/Voting/UI/VoteCallMenu.xaml.cs
+++ b/Content.Client/Voting/UI/VoteCallMenu.xaml.cs
@@ -1,7 +1,9 @@
-using System;
+using System.Linq;
using System.Numerics;
using Content.Client.Stylesheets;
using Content.Shared.Administration;
+using Content.Shared.CCVar;
+using Content.Shared.Ghost;
using Content.Shared.Voting;
using JetBrains.Annotations;
using Robust.Client.AutoGenerated;
@@ -9,10 +11,8 @@
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Configuration;
using Robust.Shared.Console;
-using Robust.Shared.IoC;
-using Robust.Shared.Localization;
-using Robust.Shared.Maths;
using Robust.Shared.Network;
using Robust.Shared.Timing;
@@ -25,32 +25,54 @@ public sealed partial class VoteCallMenu : BaseWindow
[Dependency] private readonly IVoteManager _voteManager = default!;
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly IClientNetManager _netManager = default!;
+ [Dependency] private readonly IEntityManager _entityManager = default!;
+ [Dependency] private readonly IEntityNetworkManager _entNetManager = default!;
+ [Dependency] private readonly IConfigurationManager _cfg = default!;
- public static readonly (string name, StandardVoteType type, (string name, string id)[]? secondaries)[]
- AvailableVoteTypes =
- {
- ("ui-vote-type-restart", StandardVoteType.Restart, null),
- ("ui-vote-type-gamemode", StandardVoteType.Preset, null),
- ("ui-vote-type-map", StandardVoteType.Map, null)
- };
+ private VotingSystem _votingSystem;
+
+ public StandardVoteType Type;
+
+ public Dictionary AvailableVoteOptions = new Dictionary()
+ {
+ { StandardVoteType.Restart, new CreateVoteOption("ui-vote-type-restart", new(), false, null) },
+ { StandardVoteType.Preset, new CreateVoteOption("ui-vote-type-gamemode", new(), false, null) },
+ { StandardVoteType.Map, new CreateVoteOption("ui-vote-type-map", new(), false, null) },
+ { StandardVoteType.Votekick, new CreateVoteOption("ui-vote-type-votekick", new(), true, 0) }
+ };
+
+ public Dictionary VotekickReasons = new Dictionary()
+ {
+ { VotekickReasonType.Raiding.ToString(), Loc.GetString("ui-vote-votekick-type-raiding") },
+ { VotekickReasonType.Cheating.ToString(), Loc.GetString("ui-vote-votekick-type-cheating") },
+ { VotekickReasonType.Spam.ToString(), Loc.GetString("ui-vote-votekick-type-spamming") }
+ };
+
+ public Dictionary PlayerList = new();
+
+ public OptionButton? _followDropdown = null;
+
+ public bool IsAllowedVotekick = false;
public VoteCallMenu()
{
IoCManager.InjectDependencies(this);
RobustXamlLoader.Load(this);
+ _votingSystem = _entityManager.System();
Stylesheet = IoCManager.Resolve().SheetSpace;
CloseButton.OnPressed += _ => Close();
+ VoteNotTrustedLabel.Text = Loc.GetString("ui-vote-trusted-users-notice", ("timeReq", _cfg.GetCVar(CCVars.VotekickEligibleVoterDeathtime) / 60));
- for (var i = 0; i < AvailableVoteTypes.Length; i++)
+ foreach (StandardVoteType voteType in Enum.GetValues())
{
- var (text, _, _) = AvailableVoteTypes[i];
- VoteTypeButton.AddItem(Loc.GetString(text), i);
+ var option = AvailableVoteOptions[voteType];
+ VoteTypeButton.AddItem(Loc.GetString(option.Name), (int)voteType);
}
VoteTypeButton.OnItemSelected += VoteTypeSelected;
- VoteSecondButton.OnItemSelected += VoteSecondSelected;
CreateButton.OnPressed += CreatePressed;
+ FollowButton.OnPressed += FollowSelected;
}
protected override void Opened()
@@ -60,6 +82,8 @@ protected override void Opened()
_netManager.ClientSendMessage(new MsgVoteMenu());
_voteManager.CanCallVoteChanged += CanCallVoteChanged;
+ _votingSystem.VotePlayerListResponse += UpdateVotePlayerList;
+ _votingSystem.RequestVotePlayerList();
}
public override void Close()
@@ -67,6 +91,7 @@ public override void Close()
base.Close();
_voteManager.CanCallVoteChanged -= CanCallVoteChanged;
+ _votingSystem.VotePlayerListResponse -= UpdateVotePlayerList;
}
protected override void FrameUpdate(FrameEventArgs args)
@@ -82,21 +107,50 @@ private void CanCallVoteChanged(bool obj)
Close();
}
+ private void UpdateVotePlayerList(VotePlayerListResponseEvent msg)
+ {
+ Dictionary optionsList = new();
+ Dictionary playerList = new();
+ foreach ((NetUserId, NetEntity, string) player in msg.Players)
+ {
+ optionsList.Add(player.Item1.ToString(), player.Item3);
+ playerList.Add(player.Item1, (player.Item2, player.Item3));
+ }
+ if (optionsList.Count == 0)
+ optionsList.Add(" ", " ");
+
+ PlayerList = playerList;
+
+ IsAllowedVotekick = !msg.Denied;
+
+ var updatedDropdownOption = AvailableVoteOptions[StandardVoteType.Votekick];
+ updatedDropdownOption.Dropdowns = new List>() { optionsList, VotekickReasons };
+ AvailableVoteOptions[StandardVoteType.Votekick] = updatedDropdownOption;
+ }
+
private void CreatePressed(BaseButton.ButtonEventArgs obj)
{
var typeId = VoteTypeButton.SelectedId;
- var (_, typeKey, secondaries) = AvailableVoteTypes[typeId];
+ var voteType = AvailableVoteOptions[(StandardVoteType)typeId];
- if (secondaries != null)
- {
- var secondaryId = VoteSecondButton.SelectedId;
- var (_, secondKey) = secondaries[secondaryId];
+ var commandArgs = "";
- _consoleHost.LocalShell.RemoteExecuteCommand($"createvote {typeKey} {secondKey}");
+ if (voteType.Dropdowns == null || voteType.Dropdowns.Count == 0)
+ {
+ _consoleHost.LocalShell.RemoteExecuteCommand($"createvote {((StandardVoteType)typeId).ToString()}");
}
else
{
- _consoleHost.LocalShell.RemoteExecuteCommand($"createvote {typeKey}");
+ int i = 0;
+ foreach(var dropdowns in VoteOptionsButtonContainer.Children)
+ {
+ if (dropdowns is OptionButton optionButton && AvailableVoteOptions[(StandardVoteType)typeId].Dropdowns != null)
+ {
+ commandArgs += AvailableVoteOptions[(StandardVoteType)typeId].Dropdowns[i].ElementAt(optionButton.SelectedId).Key + " ";
+ i++;
+ }
+ }
+ _consoleHost.LocalShell.RemoteExecuteCommand($"createvote {((StandardVoteType)typeId).ToString()} {commandArgs}");
}
Close();
@@ -104,9 +158,16 @@ private void CreatePressed(BaseButton.ButtonEventArgs obj)
private void UpdateVoteTimeout()
{
- var (_, typeKey, _) = AvailableVoteTypes[VoteTypeButton.SelectedId];
+ var typeKey = (StandardVoteType)VoteTypeButton.SelectedId;
var isAvailable = _voteManager.CanCallStandardVote(typeKey, out var timeout);
- CreateButton.Disabled = !isAvailable;
+ if (typeKey == StandardVoteType.Votekick && !IsAllowedVotekick)
+ {
+ CreateButton.Disabled = true;
+ }
+ else
+ {
+ CreateButton.Disabled = !isAvailable;
+ }
VoteTypeTimeoutLabel.Visible = !isAvailable;
if (!isAvailable)
@@ -123,29 +184,73 @@ private void UpdateVoteTimeout()
}
}
- private static void VoteSecondSelected(OptionButton.ItemSelectedEventArgs obj)
+ private static void ButtonSelected(OptionButton.ItemSelectedEventArgs obj)
{
obj.Button.SelectId(obj.Id);
}
+ private void FollowSelected(Button.ButtonEventArgs obj)
+ {
+ if (_followDropdown == null)
+ return;
+
+ if (_followDropdown.SelectedId >= PlayerList.Count)
+ return;
+
+ var netEntity = PlayerList.ElementAt(_followDropdown.SelectedId).Value.Item1;
+
+ var msg = new GhostWarpToTargetRequestEvent(netEntity);
+ _entNetManager.SendSystemNetworkMessage(msg);
+ }
+
private void VoteTypeSelected(OptionButton.ItemSelectedEventArgs obj)
{
VoteTypeButton.SelectId(obj.Id);
- var (_, _, options) = AvailableVoteTypes[obj.Id];
- if (options == null)
+ VoteNotTrustedLabel.Visible = false;
+ if ((StandardVoteType)obj.Id == StandardVoteType.Votekick)
{
- VoteSecondButton.Visible = false;
+ if (!IsAllowedVotekick)
+ {
+ VoteNotTrustedLabel.Visible = true;
+ var updatedDropdownOption = AvailableVoteOptions[StandardVoteType.Votekick];
+ updatedDropdownOption.Dropdowns = new List>();
+ AvailableVoteOptions[StandardVoteType.Votekick] = updatedDropdownOption;
+ }
+ else
+ {
+ _votingSystem.RequestVotePlayerList();
+ }
}
- else
- {
- VoteSecondButton.Visible = true;
- VoteSecondButton.Clear();
- for (var i = 0; i < options.Length; i++)
+ VoteWarningLabel.Visible = AvailableVoteOptions[(StandardVoteType)obj.Id].EnableVoteWarning;
+ FollowButton.Visible = false;
+
+ var voteList = AvailableVoteOptions[(StandardVoteType)obj.Id].Dropdowns;
+
+ VoteOptionsButtonContainer.RemoveAllChildren();
+ if (voteList != null)
+ {
+ int i = 0;
+ foreach (var voteDropdown in voteList)
{
- var (text, _) = options[i];
- VoteSecondButton.AddItem(Loc.GetString(text), i);
+ var optionButton = new OptionButton();
+ int j = 0;
+ foreach (var (key, value) in voteDropdown)
+ {
+ optionButton.AddItem(Loc.GetString(value), j);
+ j++;
+ }
+ VoteOptionsButtonContainer.AddChild(optionButton);
+ optionButton.Visible = true;
+ optionButton.OnItemSelected += ButtonSelected;
+ optionButton.Margin = new Thickness(2, 1);
+ if (AvailableVoteOptions[(StandardVoteType)obj.Id].FollowDropdownId != null && AvailableVoteOptions[(StandardVoteType)obj.Id].FollowDropdownId == i)
+ {
+ _followDropdown = optionButton;
+ FollowButton.Visible = true;
+ }
+ i++;
}
}
}
@@ -168,4 +273,20 @@ public void Execute(IConsoleShell shell, string argStr, string[] args)
new VoteCallMenu().OpenCentered();
}
}
+
+ public record struct CreateVoteOption
+ {
+ public string Name;
+ public List> Dropdowns;
+ public bool EnableVoteWarning;
+ public int? FollowDropdownId; // If set, this will enable the Follow button and use the dropdown matching the ID as input.
+
+ public CreateVoteOption(string name, List> dropdowns, bool enableVoteWarning, int? followDropdownId)
+ {
+ Name = name;
+ Dropdowns = dropdowns;
+ EnableVoteWarning = enableVoteWarning;
+ FollowDropdownId = followDropdownId;
+ }
+ }
}
diff --git a/Content.Client/Voting/UI/VotePopup.xaml b/Content.Client/Voting/UI/VotePopup.xaml
index fd40d7b790c..aacefd33a8a 100644
--- a/Content.Client/Voting/UI/VotePopup.xaml
+++ b/Content.Client/Voting/UI/VotePopup.xaml
@@ -1,10 +1,11 @@
-
+
-
-
+
+
+
diff --git a/Content.Client/Voting/UI/VotePopup.xaml.cs b/Content.Client/Voting/UI/VotePopup.xaml.cs
index 6bcd18165a1..2a9a6b31f89 100644
--- a/Content.Client/Voting/UI/VotePopup.xaml.cs
+++ b/Content.Client/Voting/UI/VotePopup.xaml.cs
@@ -1,12 +1,10 @@
-using System;
+using System;
using Content.Client.Stylesheets;
+using Content.Shared.Ghost;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
-using Robust.Shared.IoC;
-using Robust.Shared.Localization;
-using Robust.Shared.Maths;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
@@ -17,9 +15,11 @@ public sealed partial class VotePopup : Control
{
[Dependency] private readonly IGameTiming _gameTiming = default!;
[Dependency] private readonly IVoteManager _voteManager = default!;
+ [Dependency] private readonly IEntityNetworkManager _net = default!;
private readonly VoteManager.ActiveVote _vote;
private readonly Button[] _voteButtons;
+ private readonly NetEntity? _targetEntity;
public VotePopup(VoteManager.ActiveVote vote)
{
@@ -29,6 +29,13 @@ public VotePopup(VoteManager.ActiveVote vote)
Stylesheet = IoCManager.Resolve().SheetSpace;
+ if (_vote.TargetEntity != null && _vote.TargetEntity != 0)
+ {
+ _targetEntity = new NetEntity(_vote.TargetEntity.Value);
+ FollowVoteTarget.Visible = true;
+ FollowVoteTarget.OnPressed += _ => AttemptFollowVoteEntity();
+ }
+
Modulate = Color.White.WithAlpha(0.75f);
_voteButtons = new Button[vote.Entries.Length];
var group = new ButtonGroup();
@@ -55,13 +62,29 @@ public void UpdateData()
for (var i = 0; i < _voteButtons.Length; i++)
{
var entry = _vote.Entries[i];
- _voteButtons[i].Text = Loc.GetString("ui-vote-button", ("text", entry.Text), ("votes", entry.Votes));
+ if (_vote.DisplayVotes)
+ {
+ _voteButtons[i].Text = Loc.GetString("ui-vote-button", ("text", entry.Text), ("votes", entry.Votes));
+ }
+ else
+ {
+ _voteButtons[i].Text = Loc.GetString("ui-vote-button-no-votes", ("text", entry.Text));
+ }
if (_vote.OurVote == i)
_voteButtons[i].Pressed = true;
}
}
+ private void AttemptFollowVoteEntity()
+ {
+ if (_targetEntity != null)
+ {
+ var msg = new GhostWarpToTargetRequestEvent(_targetEntity.Value);
+ _net.SendSystemNetworkMessage(msg);
+ }
+ }
+
protected override void FrameUpdate(FrameEventArgs args)
{
// Logger.Debug($"{_gameTiming.ServerTime}, {_vote.StartTime}, {_vote.EndTime}");
diff --git a/Content.Client/Voting/VoteManager.cs b/Content.Client/Voting/VoteManager.cs
index a7c799b58fe..629adb36aa5 100644
--- a/Content.Client/Voting/VoteManager.cs
+++ b/Content.Client/Voting/VoteManager.cs
@@ -1,4 +1,4 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Linq;
using Content.Shared.Voting;
@@ -184,6 +184,8 @@ private void ReceiveVoteData(MsgVoteData message)
existingVote.Title = message.VoteTitle;
existingVote.StartTime = _gameTiming.RealServerToLocal(message.StartTime);
existingVote.EndTime = _gameTiming.RealServerToLocal(message.EndTime);
+ existingVote.DisplayVotes = message.DisplayVotes;
+ existingVote.TargetEntity = message.TargetEntity;
// Logger.Debug($"{existingVote.StartTime}, {existingVote.EndTime}, {_gameTiming.RealTime}");
@@ -245,7 +247,8 @@ public sealed class ActiveVote
public string Initiator = "";
public int? OurVote;
public int Id;
-
+ public bool DisplayVotes;
+ public int? TargetEntity; // NetEntity
public ActiveVote(int voteId)
{
Id = voteId;
diff --git a/Content.Client/Voting/VotingSystem.cs b/Content.Client/Voting/VotingSystem.cs
new file mode 100644
index 00000000000..d2049174605
--- /dev/null
+++ b/Content.Client/Voting/VotingSystem.cs
@@ -0,0 +1,34 @@
+using Content.Client.Ghost;
+using Content.Shared.Voting;
+
+namespace Content.Client.Voting;
+
+public sealed class VotingSystem : EntitySystem
+{
+
+ public event Action? VotePlayerListResponse; //Provides a list of players elligble for vote actions
+
+ [Dependency] private readonly GhostSystem _ghostSystem = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeNetworkEvent(OnVotePlayerListResponseEvent);
+ }
+
+ private void OnVotePlayerListResponseEvent(VotePlayerListResponseEvent msg)
+ {
+ if (!_ghostSystem.IsGhost)
+ {
+ return;
+ }
+
+ VotePlayerListResponse?.Invoke(msg);
+ }
+
+ public void RequestVotePlayerList()
+ {
+ RaiseNetworkEvent(new VotePlayerListRequestEvent());
+ }
+}
diff --git a/Content.Client/Weapons/Melee/MeleeWeaponSystem.cs b/Content.Client/Weapons/Melee/MeleeWeaponSystem.cs
index 7604d5f8808..26ec75f9957 100644
--- a/Content.Client/Weapons/Melee/MeleeWeaponSystem.cs
+++ b/Content.Client/Weapons/Melee/MeleeWeaponSystem.cs
@@ -28,6 +28,7 @@ public sealed partial class MeleeWeaponSystem : SharedMeleeWeaponSystem
[Dependency] private readonly AnimationPlayerSystem _animation = default!;
[Dependency] private readonly InputSystem _inputSystem = default!;
[Dependency] private readonly SharedColorFlashEffectSystem _color = default!;
+ [Dependency] private readonly MapSystem _map = default!;
private EntityQuery _xformQuery;
@@ -109,11 +110,11 @@ public override void Update(float frameTime)
if (MapManager.TryFindGridAt(mousePos, out var gridUid, out _))
{
- coordinates = EntityCoordinates.FromMap(gridUid, mousePos, TransformSystem, EntityManager);
+ coordinates = TransformSystem.ToCoordinates(gridUid, mousePos);
}
else
{
- coordinates = EntityCoordinates.FromMap(MapManager.GetMapEntityId(mousePos.MapId), mousePos, TransformSystem, EntityManager);
+ coordinates = TransformSystem.ToCoordinates(_map.GetMap(mousePos.MapId), mousePos);
}
// Heavy attack.
diff --git a/Content.IntegrationTests/Pair/TestPair.Helpers.cs b/Content.IntegrationTests/Pair/TestPair.Helpers.cs
index 588cf0d80e0..1b4825cc9c7 100644
--- a/Content.IntegrationTests/Pair/TestPair.Helpers.cs
+++ b/Content.IntegrationTests/Pair/TestPair.Helpers.cs
@@ -35,9 +35,9 @@ await Server.WaitPost(() =>
mapData.GridCoords = new EntityCoordinates(mapData.Grid, 0, 0);
var plating = tileDefinitionManager[tile];
var platingTile = new Tile(plating.TileId);
- mapData.Grid.Comp.SetTile(mapData.GridCoords, platingTile);
+ Server.System().SetTile(mapData.Grid.Owner, mapData.Grid.Comp, mapData.GridCoords, platingTile);
mapData.MapCoords = new MapCoordinates(0, 0, mapData.MapId);
- mapData.Tile = mapData.Grid.Comp.GetAllTiles().First();
+ mapData.Tile = Server.System().GetAllTiles(mapData.Grid.Owner, mapData.Grid.Comp).First();
});
TestMap = mapData;
@@ -107,13 +107,41 @@ public async Task WaitClientCommand(string cmd, int numTicks = 10)
///
/// Retrieve all entity prototypes that have some component.
///
- public List GetPrototypesWithComponent(
+ public List<(EntityPrototype, T)> GetPrototypesWithComponent(
HashSet? ignored = null,
bool ignoreAbstract = true,
bool ignoreTestPrototypes = true)
where T : IComponent
{
var id = Server.ResolveDependency().GetComponentName(typeof(T));
+ var list = new List<(EntityPrototype, T)>();
+ foreach (var proto in Server.ProtoMan.EnumeratePrototypes())
+ {
+ if (ignored != null && ignored.Contains(proto.ID))
+ continue;
+
+ if (ignoreAbstract && proto.Abstract)
+ continue;
+
+ if (ignoreTestPrototypes && IsTestPrototype(proto))
+ continue;
+
+ if (proto.Components.TryGetComponent(id, out var cmp))
+ list.Add((proto, (T)cmp));
+ }
+
+ return list;
+ }
+
+ ///
+ /// Retrieve all entity prototypes that have some component.
+ ///
+ public List GetPrototypesWithComponent(Type type,
+ HashSet? ignored = null,
+ bool ignoreAbstract = true,
+ bool ignoreTestPrototypes = true)
+ {
+ var id = Server.ResolveDependency().GetComponentName(type);
var list = new List();
foreach (var proto in Server.ProtoMan.EnumeratePrototypes())
{
@@ -127,7 +155,7 @@ public List GetPrototypesWithComponent(
continue;
if (proto.Components.ContainsKey(id))
- list.Add(proto);
+ list.Add((proto));
}
return list;
diff --git a/Content.IntegrationTests/PoolManager.Cvars.cs b/Content.IntegrationTests/PoolManager.Cvars.cs
index bcd48f82380..23f0ded7df2 100644
--- a/Content.IntegrationTests/PoolManager.Cvars.cs
+++ b/Content.IntegrationTests/PoolManager.Cvars.cs
@@ -36,7 +36,9 @@ private static readonly (string cvar, string value)[] TestCvars =
(CCVars.ConfigPresetDevelopment.Name, "false"),
(CCVars.AdminLogsEnabled.Name, "false"),
(CCVars.AutosaveEnabled.Name, "false"),
- (CVars.NetBufferSize.Name, "0")
+ (CVars.NetBufferSize.Name, "0"),
+ (CCVars.InteractionRateLimitCount.Name, "9999999"),
+ (CCVars.InteractionRateLimitPeriod.Name, "0.1"),
};
public static async Task SetupCVars(RobustIntegrationTest.IntegrationInstance instance, PoolSettings settings)
diff --git a/Content.IntegrationTests/Tests/Construction/Interaction/ComputerContruction.cs b/Content.IntegrationTests/Tests/Construction/Interaction/ComputerContruction.cs
index 8af5edaf316..9a819b257bc 100644
--- a/Content.IntegrationTests/Tests/Construction/Interaction/ComputerContruction.cs
+++ b/Content.IntegrationTests/Tests/Construction/Interaction/ComputerContruction.cs
@@ -39,7 +39,7 @@ public async Task DeconstructComputer()
await StartDeconstruction(ComputerId);
// Initial interaction turns id computer into generic computer
- await InteractUsing(Screw);
+ await InteractUsing(Pry);
AssertPrototype(ComputerFrame);
// Perform deconstruction steps
@@ -69,7 +69,7 @@ public async Task ChangeComputer()
await SpawnTarget(ComputerId);
// Initial interaction turns id computer into generic computer
- await InteractUsing(Screw);
+ await InteractUsing(Pry);
AssertPrototype(ComputerFrame);
// Perform partial deconstruction steps
diff --git a/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs b/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs
index a4563aa37e6..039c0c7b184 100644
--- a/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs
+++ b/Content.IntegrationTests/Tests/GameRules/NukeOpsTest.cs
@@ -1,4 +1,5 @@
#nullable enable
+using System.Collections.Generic;
using System.Linq;
using Content.Server.Body.Components;
using Content.Server.GameTicking;
@@ -120,8 +121,8 @@ public async Task TryStopNukeOpsFromConstantlyFailing()
Assert.That(roleSys.MindHasRole(mind));
Assert.That(factionSys.IsMember(player, "Syndicate"), Is.True);
Assert.That(factionSys.IsMember(player, "NanoTrasen"), Is.False);
- var roles = roleSys.MindGetAllRoles(mind);
- var cmdRoles = roles.Where(x => x.Prototype == "NukeopsCommander" && x.Component is NukeopsRoleComponent);
+ var roles = roleSys.MindGetAllRoleInfo(mind);
+ var cmdRoles = roles.Where(x => x.Prototype == "NukeopsCommander");
Assert.That(cmdRoles.Count(), Is.EqualTo(1));
// The second dummy player should be a medic
@@ -131,8 +132,8 @@ public async Task TryStopNukeOpsFromConstantlyFailing()
Assert.That(roleSys.MindHasRole(dummyMind));
Assert.That(factionSys.IsMember(dummyEnts[1], "Syndicate"), Is.True);
Assert.That(factionSys.IsMember(dummyEnts[1], "NanoTrasen"), Is.False);
- roles = roleSys.MindGetAllRoles(dummyMind);
- cmdRoles = roles.Where(x => x.Prototype == "NukeopsMedic" && x.Component is NukeopsRoleComponent);
+ roles = roleSys.MindGetAllRoleInfo(dummyMind);
+ cmdRoles = roles.Where(x => x.Prototype == "NukeopsMedic");
Assert.That(cmdRoles.Count(), Is.EqualTo(1));
// The other two players should have just spawned in as normal.
@@ -141,13 +142,14 @@ public async Task TryStopNukeOpsFromConstantlyFailing()
void CheckDummy(int i)
{
var ent = dummyEnts[i];
- var mind = mindSys.GetMind(ent)!.Value;
+ var mindCrew = mindSys.GetMind(ent)!.Value;
Assert.That(entMan.HasComponent(ent), Is.False);
- Assert.That(roleSys.MindIsAntagonist(mind), Is.False);
- Assert.That(roleSys.MindHasRole(mind), Is.False);
+ Assert.That(roleSys.MindIsAntagonist(mindCrew), Is.False);
+ Assert.That(roleSys.MindHasRole(mindCrew), Is.False);
Assert.That(factionSys.IsMember(ent, "Syndicate"), Is.False);
Assert.That(factionSys.IsMember(ent, "NanoTrasen"), Is.True);
- Assert.That(roleSys.MindGetAllRoles(mind).Any(x => x.Component is NukeopsRoleComponent), Is.False);
+ var nukeroles = new List() { "Nukeops", "NukeopsMedic", "NukeopsCommander" };
+ Assert.That(roleSys.MindGetAllRoleInfo(mindCrew).Any(x => nukeroles.Contains(x.Prototype)), Is.False);
}
// The game rule exists, and all the stations/shuttles/maps are properly initialized
@@ -238,7 +240,8 @@ await server.WaitAssertion(() =>
for (var i = 0; i < nukies.Length - 1; i++)
{
entMan.DeleteEntity(nukies[i]);
- Assert.That(roundEndSys.IsRoundEndRequested, Is.False,
+ Assert.That(roundEndSys.IsRoundEndRequested,
+ Is.False,
$"The round ended, but {nukies.Length - i - 1} nukies are still alive!");
}
// Delete the last nukie and make sure the round ends.
diff --git a/Content.IntegrationTests/Tests/Internals/AutoInternalsTests.cs b/Content.IntegrationTests/Tests/Internals/AutoInternalsTests.cs
index dbd612c7101..d1535368736 100644
--- a/Content.IntegrationTests/Tests/Internals/AutoInternalsTests.cs
+++ b/Content.IntegrationTests/Tests/Internals/AutoInternalsTests.cs
@@ -2,7 +2,6 @@
using Content.Server.Body.Systems;
using Content.Server.Station.Systems;
using Content.Shared.Preferences;
-using Content.Shared.Roles.Jobs;
namespace Content.IntegrationTests.Tests.Internals;
@@ -25,10 +24,7 @@ public async Task TestInternalsAutoActivateInSpaceForStationSpawn()
await server.WaitAssertion(() =>
{
var profile = new HumanoidCharacterProfile();
- var dummy = stationSpawning.SpawnPlayerMob(testMap.GridCoords, new JobComponent()
- {
- Prototype = "TestInternalsDummy"
- }, profile, station: null);
+ var dummy = stationSpawning.SpawnPlayerMob(testMap.GridCoords, "TestInternalsDummy", profile, station: null);
Assert.That(atmos.HasAtmosphere(testMap.Grid), Is.False, "Test map has atmosphere - test needs adjustment!");
Assert.That(internals.AreInternalsWorking(dummy), "Internals did not automatically connect!");
diff --git a/Content.IntegrationTests/Tests/Minds/MindTests.cs b/Content.IntegrationTests/Tests/Minds/MindTests.cs
index 3b7ecf0135f..d4d551f4e0a 100644
--- a/Content.IntegrationTests/Tests/Minds/MindTests.cs
+++ b/Content.IntegrationTests/Tests/Minds/MindTests.cs
@@ -1,10 +1,8 @@
#nullable enable
using System.Linq;
-using Content.Server.Ghost;
using Content.Server.Ghost.Roles;
using Content.Server.Ghost.Roles.Components;
using Content.Server.Mind.Commands;
-using Content.Server.Players;
using Content.Server.Roles;
using Content.Shared.Damage;
using Content.Shared.Damage.Prototypes;
@@ -18,7 +16,6 @@
using Robust.Server.GameObjects;
using Robust.Server.Player;
using Robust.Shared.GameObjects;
-using Robust.Shared.IoC;
using Robust.Shared.Map;
using Robust.Shared.Prototypes;
@@ -287,27 +284,27 @@ await server.WaitAssertion(() =>
Assert.Multiple(() =>
{
Assert.That(roleSystem.MindHasRole(mindId), Is.False);
- Assert.That(roleSystem.MindHasRole(mindId), Is.False);
+ Assert.That(roleSystem.MindHasRole(mindId), Is.False);
});
- var traitorRole = new TraitorRoleComponent();
+ var traitorRole = "MindRoleTraitor";
roleSystem.MindAddRole(mindId, traitorRole);
Assert.Multiple(() =>
{
Assert.That(roleSystem.MindHasRole(mindId));
- Assert.That(roleSystem.MindHasRole(mindId), Is.False);
+ Assert.That(roleSystem.MindHasRole(mindId), Is.False);
});
- var jobRole = new JobComponent();
+ var jobRole = "";
- roleSystem.MindAddRole(mindId, jobRole);
+ roleSystem.MindAddJobRole(mindId, jobPrototype:jobRole);
Assert.Multiple(() =>
{
Assert.That(roleSystem.MindHasRole(mindId));
- Assert.That(roleSystem.MindHasRole(mindId));
+ Assert.That(roleSystem.MindHasRole(mindId));
});
roleSystem.MindRemoveRole(mindId);
@@ -315,15 +312,15 @@ await server.WaitAssertion(() =>
Assert.Multiple(() =>
{
Assert.That(roleSystem.MindHasRole(mindId), Is.False);
- Assert.That(roleSystem.MindHasRole(mindId));
+ Assert.That(roleSystem.MindHasRole(mindId));
});
- roleSystem.MindRemoveRole(mindId);
+ roleSystem.MindRemoveRole(mindId);
Assert.Multiple(() =>
{
Assert.That(roleSystem.MindHasRole(mindId), Is.False);
- Assert.That(roleSystem.MindHasRole(mindId), Is.False);
+ Assert.That(roleSystem.MindHasRole(mindId), Is.False);
});
});
diff --git a/Content.IntegrationTests/Tests/Minds/RoleTests.cs b/Content.IntegrationTests/Tests/Minds/RoleTests.cs
new file mode 100644
index 00000000000..fcfe1236cfc
--- /dev/null
+++ b/Content.IntegrationTests/Tests/Minds/RoleTests.cs
@@ -0,0 +1,95 @@
+using System.Linq;
+using Content.Server.Roles;
+using Content.Shared.Roles;
+using Content.Shared.Roles.Jobs;
+using Robust.Shared.GameObjects;
+using Robust.Shared.Reflection;
+
+namespace Content.IntegrationTests.Tests.Minds;
+
+[TestFixture]
+public sealed class RoleTests
+{
+ ///
+ /// Check that any prototype with a is properly configured
+ ///
+ [Test]
+ public async Task ValidateRolePrototypes()
+ {
+ await using var pair = await PoolManager.GetServerClient();
+
+ var jobComp = pair.Server.ResolveDependency().GetComponentName(typeof(JobRoleComponent));
+
+ Assert.Multiple(() =>
+ {
+ foreach (var (proto, comp) in pair.GetPrototypesWithComponent())
+ {
+ Assert.That(comp.AntagPrototype == null || comp.JobPrototype == null, $"Role {proto.ID} has both a job and antag prototype.");
+ Assert.That(!comp.ExclusiveAntag || comp.Antag, $"Role {proto.ID} is marked as an exclusive antag, despite not being an antag.");
+ Assert.That(comp.Antag || comp.AntagPrototype == null, $"Role {proto.ID} has an antag prototype, despite not being an antag.");
+
+ if (comp.JobPrototype != null)
+ Assert.That(proto.Components.ContainsKey(jobComp), $"Role {proto.ID} is a job, despite not having a job prototype.");
+
+ // It is possible that this is meant to be supported? Though I would assume that it would be for
+ // admin / prototype uploads, and that pre-defined roles should still check this.
+ Assert.That(!comp.Antag || comp.AntagPrototype != null , $"Role {proto.ID} is an antag, despite not having a antag prototype.");
+ }
+ });
+
+ await pair.CleanReturnAsync();
+ }
+
+ ///
+ /// Check that any prototype with a also has a properly configured
+ ///
+ ///
+ [Test]
+ public async Task ValidateJobPrototypes()
+ {
+ await using var pair = await PoolManager.GetServerClient();
+
+ var mindCompId = pair.Server.ResolveDependency().GetComponentName(typeof(MindRoleComponent));
+
+ Assert.Multiple(() =>
+ {
+ foreach (var (proto, comp) in pair.GetPrototypesWithComponent())
+ {
+ if (proto.Components.TryGetComponent(mindCompId, out var mindComp))
+ Assert.That(((MindRoleComponent)mindComp).JobPrototype, Is.Not.Null);
+ }
+ });
+
+ await pair.CleanReturnAsync();
+ }
+
+ ///
+ /// Check that any prototype with a component that inherits from also has a
+ ///
+ ///
+ [Test]
+ public async Task ValidateRolesHaveMindRoleComp()
+ {
+ await using var pair = await PoolManager.GetServerClient();
+
+ var refMan = pair.Server.ResolveDependency();
+ var mindCompId = pair.Server.ResolveDependency().GetComponentName(typeof(MindRoleComponent));
+
+ var compTypes = refMan.GetAllChildren(typeof(BaseMindRoleComponent))
+ .Append(typeof(RoleBriefingComponent))
+ .Where(x => !x.IsAbstract);
+
+ Assert.Multiple(() =>
+ {
+ foreach (var comp in compTypes)
+ {
+ foreach (var proto in pair.GetPrototypesWithComponent(comp))
+ {
+ Assert.That(proto.Components.ContainsKey(mindCompId), $"Role {proto.ID} does not have a {nameof(MindRoleComponent)} despite having a {comp.Name}");
+ }
+ }
+ });
+
+ await pair.CleanReturnAsync();
+ }
+}
diff --git a/Content.IntegrationTests/Tests/Nyanotrasen/DeepFryerTest.cs b/Content.IntegrationTests/Tests/Nyanotrasen/DeepFryerTest.cs
deleted file mode 100644
index 1181f3a169e..00000000000
--- a/Content.IntegrationTests/Tests/Nyanotrasen/DeepFryerTest.cs
+++ /dev/null
@@ -1,60 +0,0 @@
-#nullable enable annotations
-using Content.Server.Kitchen.Components;
-using Content.Server.Kitchen.EntitySystems;
-using Content.Server.Nyanotrasen.Kitchen.Components;
-using Content.Server.Nyanotrasen.Kitchen.EntitySystems;
-using Robust.Shared.GameObjects;
-using Robust.Shared.Reflection;
-
-namespace Content.IntegrationTests.Tests.DeepFryer
-{
- [TestFixture]
- [TestOf(typeof(DeepFriedComponent))]
- [TestOf(typeof(DeepFryerSystem))]
- [TestOf(typeof(DeepFryerComponent))]
- public sealed class DeepFryerTest
- {
-
- [TestPrototypes]
- private const string Prototypes = @"
-- type: entity
- name: DeepFryerDummy
- id: DeepFryerDummy
- components:
- - type: DeepFryer
- entryDelay: 0
- draggedEntryDelay: 0
- flushTime: 0
- - type: Anchorable
- - type: ApcPowerReceiver
- - type: Physics
- bodyType: Static
- - type: Fixtures
- fixtures:
- fix1:
- shape:
- !type:PhysShapeCircle
- radius: 0.35
-";
-
- [Test]
- public async Task Test()
- {
- await using var pair = await PoolManager.GetServerClient();
- var server = pair.Server;
-
- var testMap = await pair.CreateTestMap();
-
- EntityUid unitUid = default;
-
- var entityManager = server.ResolveDependency();
- var xformSystem = entityManager.System();
- var deepFryerSystem = entityManager.System();
- await server.WaitAssertion(() =>
- {
- Assert.That(deepFryerSystem, Is.Not.Null);
- });
- await pair.CleanReturnAsync();
- }
- }
-}
diff --git a/Content.IntegrationTests/Tests/Preferences/LoadoutTests.cs b/Content.IntegrationTests/Tests/Preferences/LoadoutTests.cs
index 6746d6d5a94..267b3637e0a 100644
--- a/Content.IntegrationTests/Tests/Preferences/LoadoutTests.cs
+++ b/Content.IntegrationTests/Tests/Preferences/LoadoutTests.cs
@@ -3,7 +3,6 @@
using Content.Shared.Inventory;
using Content.Shared.Preferences;
using Content.Shared.Preferences.Loadouts;
-using Content.Shared.Roles.Jobs;
using Robust.Shared.GameObjects;
using Robust.Shared.Prototypes;
@@ -68,10 +67,7 @@ await server.WaitAssertion(() =>
profile.SetLoadout(new RoleLoadout("LoadoutTester"));
- var tester = stationSystem.SpawnPlayerMob(testMap.GridCoords, job: new JobComponent()
- {
- Prototype = "LoadoutTester"
- }, profile, station: null);
+ var tester = stationSystem.SpawnPlayerMob(testMap.GridCoords, job: "LoadoutTester", profile, station: null);
var slotQuery = inventorySystem.GetSlotEnumerator(tester);
var checkedCount = 0;
diff --git a/Content.IntegrationTests/Tests/Roles/StartingGearStorageTests.cs b/Content.IntegrationTests/Tests/Roles/StartingGearStorageTests.cs
index f8060edb2b4..3b2935258a7 100644
--- a/Content.IntegrationTests/Tests/Roles/StartingGearStorageTests.cs
+++ b/Content.IntegrationTests/Tests/Roles/StartingGearStorageTests.cs
@@ -35,15 +35,16 @@ await server.WaitAssertion(() =>
{
foreach (var gearProto in protos)
{
- var backpackProto = ((IEquipmentLoadout) gearProto).GetGear("back");
- if (backpackProto == string.Empty)
- continue;
-
- var bag = server.EntMan.SpawnEntity(backpackProto, coords);
var ents = new ValueList();
foreach (var (slot, entProtos) in gearProto.Storage)
{
+ ents.Clear();
+ var storageProto = ((IEquipmentLoadout)gearProto).GetGear(slot);
+ if (storageProto == string.Empty)
+ continue;
+
+ var bag = server.EntMan.SpawnEntity(storageProto, coords);
if (entProtos.Count == 0)
continue;
@@ -59,9 +60,8 @@ await server.WaitAssertion(() =>
server.EntMan.DeleteEntity(ent);
}
+ server.EntMan.DeleteEntity(bag);
}
-
- server.EntMan.DeleteEntity(bag);
}
mapManager.DeleteMap(testMap.MapId);
diff --git a/Content.IntegrationTests/Tests/Sprite/ItemSpriteTest.cs b/Content.IntegrationTests/Tests/Sprite/ItemSpriteTest.cs
index bf75188f029..da7e1e8e9b0 100644
--- a/Content.IntegrationTests/Tests/Sprite/ItemSpriteTest.cs
+++ b/Content.IntegrationTests/Tests/Sprite/ItemSpriteTest.cs
@@ -40,7 +40,7 @@ public async Task AllItemsHaveSpritesTest()
await pair.Client.WaitPost(() =>
{
- foreach (var proto in pair.GetPrototypesWithComponent(Ignored))
+ foreach (var (proto, _) in pair.GetPrototypesWithComponent(Ignored))
{
var dummy = pair.Client.EntMan.Spawn(proto.ID);
pair.Client.EntMan.RunMapInit(dummy, pair.Client.MetaData(dummy));
diff --git a/Content.IntegrationTests/Tests/StorageTest.cs b/Content.IntegrationTests/Tests/StorageTest.cs
index 2d28534347d..983ec709362 100644
--- a/Content.IntegrationTests/Tests/StorageTest.cs
+++ b/Content.IntegrationTests/Tests/StorageTest.cs
@@ -94,14 +94,13 @@ public async Task TestSufficientSpaceForFill()
await Assert.MultipleAsync(async () =>
{
- foreach (var proto in pair.GetPrototypesWithComponent())
+ foreach (var (proto, fill) in pair.GetPrototypesWithComponent())
{
if (proto.HasComponent(compFact))
continue;
StorageComponent? storage = null;
ItemComponent? item = null;
- StorageFillComponent fill = default!;
var size = 0;
await server.WaitAssertion(() =>
{
@@ -112,7 +111,6 @@ await server.WaitAssertion(() =>
}
proto.TryGetComponent("Item", out item);
- fill = (StorageFillComponent) proto.Components[id].Component;
size = GetFillSize(fill, false, protoMan, itemSys);
});
@@ -179,7 +177,7 @@ public async Task TestSufficientSpaceForEntityStorageFill()
var itemSys = entMan.System();
- foreach (var proto in pair.GetPrototypesWithComponent())
+ foreach (var (proto, fill) in pair.GetPrototypesWithComponent())
{
if (proto.HasComponent(compFact))
continue;
@@ -192,7 +190,6 @@ await server.WaitAssertion(() =>
if (entStorage == null)
return;
- var fill = (StorageFillComponent) proto.Components[id].Component;
var size = GetFillSize(fill, true, protoMan, itemSys);
Assert.That(size, Is.LessThanOrEqualTo(entStorage.Capacity),
$"{proto.ID} storage fill is too large.");
diff --git a/Content.Server/Access/Systems/AgentIDCardSystem.cs b/Content.Server/Access/Systems/AgentIDCardSystem.cs
index 8f1d0d4f820..a38aefce935 100644
--- a/Content.Server/Access/Systems/AgentIDCardSystem.cs
+++ b/Content.Server/Access/Systems/AgentIDCardSystem.cs
@@ -32,7 +32,7 @@ public override void Initialize()
private void OnAfterInteract(EntityUid uid, AgentIDCardComponent component, AfterInteractEvent args)
{
- if (!TryComp(args.Target, out var targetAccess) || !HasComp(args.Target) || args.Target == null)
+ if (args.Target == null || !args.CanReach || !TryComp(args.Target, out var targetAccess) || !HasComp(args.Target))
return;
if (!TryComp(uid, out var access) || !HasComp(uid))
@@ -67,7 +67,7 @@ private void AfterUIOpen(EntityUid uid, AgentIDCardComponent component, AfterAct
if (!TryComp(uid, out var idCard))
return;
- var state = new AgentIDCardBoundUserInterfaceState(idCard.FullName ?? "", idCard.JobTitle ?? "", idCard.JobIcon);
+ var state = new AgentIDCardBoundUserInterfaceState(idCard.FullName ?? "", idCard.LocalizedJobTitle ?? "", idCard.JobIcon);
_uiSystem.SetUiState(uid, AgentIDCardUiKey.Key, state);
}
diff --git a/Content.Server/Access/Systems/IdCardConsoleSystem.cs b/Content.Server/Access/Systems/IdCardConsoleSystem.cs
index e02664f2bbd..a9e5d9a6d3e 100644
--- a/Content.Server/Access/Systems/IdCardConsoleSystem.cs
+++ b/Content.Server/Access/Systems/IdCardConsoleSystem.cs
@@ -96,7 +96,7 @@ private void UpdateUserInterface(EntityUid uid, IdCardConsoleComponent component
PrivilegedIdIsAuthorized(uid, component),
true,
targetIdComponent.FullName,
- targetIdComponent.JobTitle,
+ targetIdComponent.LocalizedJobTitle,
targetAccessComponent.Tags.ToList(),
possibleAccess,
jobProto,
diff --git a/Content.Server/Administration/Managers/BanManager.cs b/Content.Server/Administration/Managers/BanManager.cs
index 946770d6aaf..1cdfb822242 100644
--- a/Content.Server/Administration/Managers/BanManager.cs
+++ b/Content.Server/Administration/Managers/BanManager.cs
@@ -14,13 +14,13 @@
using Content.Shared.Roles;
using Robust.Server.Player;
using Robust.Shared.Asynchronous;
+using Robust.Shared.Collections;
using Robust.Shared.Configuration;
using Robust.Shared.Enums;
using Robust.Shared.Network;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
-using Robust.Shared.Utility;
namespace Content.Server.Administration.Managers;
@@ -45,14 +45,12 @@ public sealed partial class BanManager : IBanManager, IPostInjectInit
public const string SawmillId = "admin.bans";
public const string JobPrefix = "Job:";
- private readonly Dictionary> _cachedRoleBans = new();
+ private readonly Dictionary> _cachedRoleBans = new();
// Cached ban exemption flags are used to handle
private readonly Dictionary _cachedBanExemptions = new();
public void Initialize()
{
- _playerManager.PlayerStatusChanged += OnPlayerStatusChanged;
-
_netManager.RegisterNetMessage();
_db.SubscribeToNotifications(OnDatabaseNotification);
@@ -63,12 +61,23 @@ public void Initialize()
private async Task CachePlayerData(ICommonSession player, CancellationToken cancel)
{
- // Yeah so role ban loading code isn't integrated with exempt flag loading code.
- // Have you seen how garbage role ban code code is? I don't feel like refactoring it right now.
-
var flags = await _db.GetBanExemption(player.UserId, cancel);
+
+ var netChannel = player.Channel;
+ ImmutableArray? hwId = netChannel.UserData.HWId.Length == 0 ? null : netChannel.UserData.HWId;
+ var roleBans = await _db.GetServerRoleBansAsync(netChannel.RemoteEndPoint.Address, player.UserId, hwId, false);
+
+ var userRoleBans = new List();
+ foreach (var ban in roleBans)
+ {
+ userRoleBans.Add(ban);
+ }
+
cancel.ThrowIfCancellationRequested();
_cachedBanExemptions[player] = flags;
+ _cachedRoleBans[player] = userRoleBans;
+
+ SendRoleBans(player);
}
private void ClearPlayerData(ICommonSession player)
@@ -76,25 +85,15 @@ private void ClearPlayerData(ICommonSession player)
_cachedBanExemptions.Remove(player);
}
- private async void OnPlayerStatusChanged(object? sender, SessionStatusEventArgs e)
- {
- if (e.NewStatus != SessionStatus.Connected || _cachedRoleBans.ContainsKey(e.Session.UserId))
- return;
-
- var netChannel = e.Session.Channel;
- ImmutableArray? hwId = netChannel.UserData.HWId.Length == 0 ? null : netChannel.UserData.HWId;
- await CacheDbRoleBans(e.Session.UserId, netChannel.RemoteEndPoint.Address, hwId);
-
- SendRoleBans(e.Session);
- }
-
private async Task AddRoleBan(ServerRoleBanDef banDef)
{
banDef = await _db.AddServerRoleBanAsync(banDef);
- if (banDef.UserId != null)
+ if (banDef.UserId != null
+ && _playerManager.TryGetSessionById(banDef.UserId, out var player)
+ && _cachedRoleBans.TryGetValue(player, out var cachedBans))
{
- _cachedRoleBans.GetOrNew(banDef.UserId.Value).Add(banDef);
+ cachedBans.Add(banDef);
}
return true;
@@ -102,31 +101,21 @@ private async Task AddRoleBan(ServerRoleBanDef banDef)
public HashSet? GetRoleBans(NetUserId playerUserId)
{
- return _cachedRoleBans.TryGetValue(playerUserId, out var roleBans)
+ if (!_playerManager.TryGetSessionById(playerUserId, out var session))
+ return null;
+
+ return _cachedRoleBans.TryGetValue(session, out var roleBans)
? roleBans.Select(banDef => banDef.Role).ToHashSet()
: null;
}
- private async Task CacheDbRoleBans(NetUserId userId, IPAddress? address = null, ImmutableArray? hwId = null)
- {
- var roleBans = await _db.GetServerRoleBansAsync(address, userId, hwId, false);
-
- var userRoleBans = new HashSet();
- foreach (var ban in roleBans)
- {
- userRoleBans.Add(ban);
- }
-
- _cachedRoleBans[userId] = userRoleBans;
- }
-
public void Restart()
{
// Clear out players that have disconnected.
- var toRemove = new List();
+ var toRemove = new ValueList();
foreach (var player in _cachedRoleBans.Keys)
{
- if (!_playerManager.TryGetSessionById(player, out _))
+ if (player.Status == SessionStatus.Disconnected)
toRemove.Add(player);
}
@@ -138,7 +127,7 @@ public void Restart()
// Check for expired bans
foreach (var roleBans in _cachedRoleBans.Values)
{
- roleBans.RemoveWhere(ban => DateTimeOffset.Now > ban.ExpirationTime);
+ roleBans.RemoveAll(ban => DateTimeOffset.Now > ban.ExpirationTime);
}
}
@@ -281,9 +270,9 @@ public async void CreateRoleBan(NetUserId? target, string? targetUsername, NetUs
var length = expires == null ? Loc.GetString("cmd-roleban-inf") : Loc.GetString("cmd-roleban-until", ("expires", expires));
_chat.SendAdminAlert(Loc.GetString("cmd-roleban-success", ("target", targetUsername ?? "null"), ("role", role), ("reason", reason), ("length", length)));
- if (target != null)
+ if (target != null && _playerManager.TryGetSessionById(target.Value, out var session))
{
- SendRoleBans(target.Value);
+ SendRoleBans(session);
}
}
@@ -311,10 +300,12 @@ public async Task PardonRoleBan(int banId, NetUserId? unbanningAdmin, Da
await _db.AddServerRoleUnbanAsync(new ServerRoleUnbanDef(banId, unbanningAdmin, DateTimeOffset.Now));
- if (ban.UserId is { } player && _cachedRoleBans.TryGetValue(player, out var roleBans))
+ if (ban.UserId is { } player
+ && _playerManager.TryGetSessionById(player, out var session)
+ && _cachedRoleBans.TryGetValue(session, out var roleBans))
{
- roleBans.RemoveWhere(roleBan => roleBan.Id == ban.Id);
- SendRoleBans(player);
+ roleBans.RemoveAll(roleBan => roleBan.Id == ban.Id);
+ SendRoleBans(session);
}
return $"Pardoned ban with id {banId}";
@@ -322,8 +313,12 @@ public async Task PardonRoleBan(int banId, NetUserId? unbanningAdmin, Da
public HashSet>? GetJobBans(NetUserId playerUserId)
{
- if (!_cachedRoleBans.TryGetValue(playerUserId, out var roleBans))
+ if (!_playerManager.TryGetSessionById(playerUserId, out var session))
+ return null;
+
+ if (!_cachedRoleBans.TryGetValue(session, out var roleBans))
return null;
+
return roleBans
.Where(ban => ban.Role.StartsWith(JobPrefix, StringComparison.Ordinal))
.Select(ban => new ProtoId(ban.Role[JobPrefix.Length..]))
@@ -331,19 +326,9 @@ public async Task PardonRoleBan(int banId, NetUserId? unbanningAdmin, Da
}
#endregion
- public void SendRoleBans(NetUserId userId)
- {
- if (!_playerManager.TryGetSessionById(userId, out var player))
- {
- return;
- }
-
- SendRoleBans(player);
- }
-
public void SendRoleBans(ICommonSession pSession)
{
- var roleBans = _cachedRoleBans.GetValueOrDefault(pSession.UserId) ?? new HashSet();
+ var roleBans = _cachedRoleBans.GetValueOrDefault(pSession) ?? new List();
var bans = new MsgRoleBans()
{
Bans = roleBans.Select(o => o.Role).ToList()
diff --git a/Content.Server/Administration/Managers/IBanManager.cs b/Content.Server/Administration/Managers/IBanManager.cs
index b60e0a25351..c11e310a825 100644
--- a/Content.Server/Administration/Managers/IBanManager.cs
+++ b/Content.Server/Administration/Managers/IBanManager.cs
@@ -47,12 +47,6 @@ public interface IBanManager
/// The time at which this role ban was pardoned.
public Task PardonRoleBan(int banId, NetUserId? unbanningAdmin, DateTimeOffset unbanTime);
- ///
- /// Sends role bans to the target
- ///
- /// Player's user ID
- public void SendRoleBans(NetUserId userId);
-
///
/// Sends role bans to the target
///
diff --git a/Content.Server/Administration/Systems/AdminVerbSystem.Smites.cs b/Content.Server/Administration/Systems/AdminVerbSystem.Smites.cs
index d108c29975a..78362390811 100644
--- a/Content.Server/Administration/Systems/AdminVerbSystem.Smites.cs
+++ b/Content.Server/Administration/Systems/AdminVerbSystem.Smites.cs
@@ -98,9 +98,10 @@ private void AddSmiteVerbs(GetVerbsEvent args)
if (HasComp(args.Target) || HasComp(args.Target))
return;
+ var explodeName = Loc.GetString("admin-smite-explode-name").ToLowerInvariant();
Verb explode = new()
{
- Text = "admin-smite-explode-name",
+ Text = explodeName,
Category = VerbCategory.Smite,
Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/VerbIcons/smite.svg.192dpi.png")),
Act = () =>
@@ -114,13 +115,14 @@ private void AddSmiteVerbs(GetVerbsEvent args)
_bodySystem.GibBody(args.Target);
},
Impact = LogImpact.Extreme,
- Message = Loc.GetString("admin-smite-explode-description")
+ Message = string.Join(": ", explodeName, Loc.GetString("admin-smite-explode-description")) // we do this so the description tells admins the Text to run it via console.
};
args.Verbs.Add(explode);
+ var chessName = Loc.GetString("admin-smite-chess-dimension-name").ToLowerInvariant();
Verb chess = new()
{
- Text = "admin-smite-chess-dimension-name",
+ Text = chessName,
Category = VerbCategory.Smite,
Icon = new SpriteSpecifier.Rsi(new ("/Textures/Objects/Fun/Tabletop/chessboard.rsi"), "chessboard"),
Act = () =>
@@ -140,12 +142,13 @@ private void AddSmiteVerbs(GetVerbsEvent args)
xform.WorldRotation = Angle.Zero;
},
Impact = LogImpact.Extreme,
- Message = Loc.GetString("admin-smite-chess-dimension-description")
+ Message = string.Join(": ", chessName, Loc.GetString("admin-smite-chess-dimension-description"))
};
args.Verbs.Add(chess);
if (TryComp(args.Target, out var flammable))
{
+ var flamesName = Loc.GetString("admin-smite-set-alight-name").ToLowerInvariant();
Verb flames = new()
{
Text = "admin-smite-set-alight-name",
@@ -163,14 +166,15 @@ private void AddSmiteVerbs(GetVerbsEvent args)
Filter.PvsExcept(args.Target), true, PopupType.MediumCaution);
},
Impact = LogImpact.Extreme,
- Message = Loc.GetString("admin-smite-set-alight-description")
+ Message = string.Join(": ", flamesName, Loc.GetString("admin-smite-set-alight-description"))
};
args.Verbs.Add(flames);
}
+ var monkeyName = Loc.GetString("admin-smite-monkeyify-name").ToLowerInvariant();
Verb monkey = new()
{
- Text = "admin-smite-monkeyify-name",
+ Text = monkeyName,
Category = VerbCategory.Smite,
Icon = new SpriteSpecifier.Rsi(new ("/Textures/Mobs/Animals/monkey.rsi"), "monkey"),
Act = () =>
@@ -178,13 +182,14 @@ private void AddSmiteVerbs(GetVerbsEvent args)
_polymorphSystem.PolymorphEntity(args.Target, "AdminMonkeySmite");
},
Impact = LogImpact.Extreme,
- Message = Loc.GetString("admin-smite-monkeyify-description")
+ Message = string.Join(": ", monkeyName, Loc.GetString("admin-smite-monkeyify-description"))
};
args.Verbs.Add(monkey);
+ var disposalBinName = Loc.GetString("admin-smite-garbage-can-name").ToLowerInvariant();
Verb disposalBin = new()
{
- Text = "admin-smite-electrocute-name",
+ Text = disposalBinName,
Category = VerbCategory.Smite,
Icon = new SpriteSpecifier.Rsi(new ("/Textures/Structures/Piping/disposal.rsi"), "disposal"),
Act = () =>
@@ -192,16 +197,17 @@ private void AddSmiteVerbs(GetVerbsEvent args)
_polymorphSystem.PolymorphEntity(args.Target, "AdminDisposalsSmite");
},
Impact = LogImpact.Extreme,
- Message = Loc.GetString("admin-smite-garbage-can-description")
+ Message = string.Join(": ", disposalBinName, Loc.GetString("admin-smite-garbage-can-description"))
};
args.Verbs.Add(disposalBin);
if (TryComp(args.Target, out var damageable) &&
HasComp(args.Target))
{
+ var hardElectrocuteName = Loc.GetString("admin-smite-electrocute-name").ToLowerInvariant();
Verb hardElectrocute = new()
{
- Text = "admin-smite-creampie-name",
+ Text = hardElectrocuteName,
Category = VerbCategory.Smite,
Icon = new SpriteSpecifier.Rsi(new ("/Textures/Clothing/Hands/Gloves/Color/yellow.rsi"), "icon"),
Act = () =>
@@ -237,16 +243,17 @@ private void AddSmiteVerbs(GetVerbsEvent args)
TimeSpan.FromSeconds(30), refresh: true, ignoreInsulation: true);
},
Impact = LogImpact.Extreme,
- Message = Loc.GetString("admin-smite-electrocute-description")
+ Message = string.Join(": ", hardElectrocuteName, Loc.GetString("admin-smite-electrocute-description"))
};
args.Verbs.Add(hardElectrocute);
}
if (TryComp(args.Target, out var creamPied))
{
+ var creamPieName = Loc.GetString("admin-smite-creampie-name").ToLowerInvariant();
Verb creamPie = new()
{
- Text = "admin-smite-remove-blood-name",
+ Text = creamPieName,
Category = VerbCategory.Smite,
Icon = new SpriteSpecifier.Rsi(new ("/Textures/Objects/Consumable/Food/Baked/pie.rsi"), "plain-slice"),
Act = () =>
@@ -254,16 +261,17 @@ private void AddSmiteVerbs(GetVerbsEvent args)
_creamPieSystem.SetCreamPied(args.Target, creamPied, true);
},
Impact = LogImpact.Extreme,
- Message = Loc.GetString("admin-smite-creampie-description")
+ Message = string.Join(": ", creamPieName, Loc.GetString("admin-smite-creampie-description"))
};
args.Verbs.Add(creamPie);
}
if (TryComp(args.Target, out var bloodstream))
{
+ var bloodRemovalName = Loc.GetString("admin-smite-remove-blood-name").ToLowerInvariant();
Verb bloodRemoval = new()
{
- Text = "admin-smite-vomit-organs-name",
+ Text = bloodRemovalName,
Category = VerbCategory.Smite,
Icon = new SpriteSpecifier.Rsi(new ("/Textures/Fluids/tomato_splat.rsi"), "puddle-1"),
Act = () =>
@@ -276,7 +284,7 @@ private void AddSmiteVerbs(GetVerbsEvent args)
Filter.PvsExcept(args.Target), true, PopupType.MediumCaution);
},
Impact = LogImpact.Extreme,
- Message = Loc.GetString("admin-smite-remove-blood-description")
+ Message = string.Join(": ", bloodRemovalName, Loc.GetString("admin-smite-remove-blood-description"))
};
args.Verbs.Add(bloodRemoval);
}
@@ -284,9 +292,10 @@ private void AddSmiteVerbs(GetVerbsEvent args)
// bobby...
if (TryComp(args.Target, out var body))
{
+ var vomitOrgansName = Loc.GetString("admin-smite-vomit-organs-name").ToLowerInvariant();
Verb vomitOrgans = new()
{
- Text = "admin-smite-remove-hands-name",
+ Text = vomitOrgansName,
Category = VerbCategory.Smite,
Icon = new SpriteSpecifier.Rsi(new("/Textures/Fluids/vomit_toxin.rsi"), "vomit_toxin-1"),
Act = () =>
@@ -308,13 +317,14 @@ private void AddSmiteVerbs(GetVerbsEvent args)
Filter.PvsExcept(args.Target), true, PopupType.MediumCaution);
},
Impact = LogImpact.Extreme,
- Message = Loc.GetString("admin-smite-vomit-organs-description")
+ Message = string.Join(": ", vomitOrgansName, Loc.GetString("admin-smite-vomit-organs-description"))
};
args.Verbs.Add(vomitOrgans);
+ var handsRemovalName = Loc.GetString("admin-smite-remove-hands-name").ToLowerInvariant();
Verb handsRemoval = new()
{
- Text = "admin-smite-remove-hand-name",
+ Text = handsRemovalName,
Category = VerbCategory.Smite,
Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/AdminActions/remove-hands.png")),
Act = () =>
@@ -330,13 +340,14 @@ private void AddSmiteVerbs(GetVerbsEvent args)
Filter.PvsExcept(args.Target), true, PopupType.Medium);
},
Impact = LogImpact.Extreme,
- Message = Loc.GetString("admin-smite-remove-hands-description")
+ Message = string.Join(": ", handsRemovalName, Loc.GetString("admin-smite-remove-hands-description"))
};
args.Verbs.Add(handsRemoval);
+ var handRemovalName = Loc.GetString("admin-smite-remove-hand-name").ToLowerInvariant();
Verb handRemoval = new()
{
- Text = "admin-smite-pinball-name",
+ Text = handRemovalName,
Category = VerbCategory.Smite,
Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/AdminActions/remove-hand.png")),
Act = () =>
@@ -353,13 +364,14 @@ private void AddSmiteVerbs(GetVerbsEvent args)
Filter.PvsExcept(args.Target), true, PopupType.Medium);
},
Impact = LogImpact.Extreme,
- Message = Loc.GetString("admin-smite-remove-hand-description")
+ Message = string.Join(": ", handRemovalName, Loc.GetString("admin-smite-remove-hand-description"))
};
args.Verbs.Add(handRemoval);
+ var stomachRemovalName = Loc.GetString("admin-smite-stomach-removal-name").ToLowerInvariant();
Verb stomachRemoval = new()
{
- Text = "admin-smite-yeet-name",
+ Text = stomachRemovalName,
Category = VerbCategory.Smite,
Icon = new SpriteSpecifier.Rsi(new ("/Textures/Mobs/Species/Human/organs.rsi"), "stomach"),
Act = () =>
@@ -373,13 +385,14 @@ private void AddSmiteVerbs(GetVerbsEvent args)
args.Target, PopupType.LargeCaution);
},
Impact = LogImpact.Extreme,
- Message = Loc.GetString("admin-smite-stomach-removal-description"),
+ Message = string.Join(": ", stomachRemovalName, Loc.GetString("admin-smite-stomach-removal-description"))
};
args.Verbs.Add(stomachRemoval);
+ var lungRemovalName = Loc.GetString("admin-smite-lung-removal-name").ToLowerInvariant();
Verb lungRemoval = new()
{
- Text = "admin-smite-become-bread-name",
+ Text = lungRemovalName,
Category = VerbCategory.Smite,
Icon = new SpriteSpecifier.Rsi(new ("/Textures/Mobs/Species/Human/organs.rsi"), "lung-r"),
Act = () =>
@@ -393,16 +406,17 @@ private void AddSmiteVerbs(GetVerbsEvent args)
args.Target, PopupType.LargeCaution);
},
Impact = LogImpact.Extreme,
- Message = Loc.GetString("admin-smite-lung-removal-description"),
+ Message = string.Join(": ", lungRemovalName, Loc.GetString("admin-smite-lung-removal-description"))
};
args.Verbs.Add(lungRemoval);
}
if (TryComp(args.Target, out var physics))
{
+ var pinballName = Loc.GetString("admin-smite-pinball-name").ToLowerInvariant();
Verb pinball = new()
{
- Text = "admin-smite-ghostkick-name",
+ Text = pinballName,
Category = VerbCategory.Smite,
Icon = new SpriteSpecifier.Rsi(new ("/Textures/Objects/Fun/toys.rsi"), "basketball"),
Act = () =>
@@ -430,13 +444,14 @@ private void AddSmiteVerbs(GetVerbsEvent args)
_physics.SetAngularDamping(args.Target, physics, 0f);
},
Impact = LogImpact.Extreme,
- Message = Loc.GetString("admin-smite-pinball-description")
+ Message = string.Join(": ", pinballName, Loc.GetString("admin-smite-pinball-description"))
};
args.Verbs.Add(pinball);
+ var yeetName = Loc.GetString("admin-smite-yeet-name").ToLowerInvariant();
Verb yeet = new()
{
- Text = "admin-smite-nyanify-name",
+ Text = yeetName,
Category = VerbCategory.Smite,
Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/VerbIcons/eject.svg.192dpi.png")),
Act = () =>
@@ -460,11 +475,12 @@ private void AddSmiteVerbs(GetVerbsEvent args)
_physics.SetAngularDamping(args.Target, physics, 0f);
},
Impact = LogImpact.Extreme,
- Message = Loc.GetString("admin-smite-yeet-description")
+ Message = string.Join(": ", yeetName, Loc.GetString("admin-smite-yeet-description"))
};
args.Verbs.Add(yeet);
}
+ var breadName = Loc.GetString("admin-smite-become-bread-name").ToLowerInvariant(); // Will I get cancelled for breadName-ing you?
Verb bread = new()
{
Text = "admin-smite-kill-sign-name",
@@ -475,10 +491,11 @@ private void AddSmiteVerbs(GetVerbsEvent args)
_polymorphSystem.PolymorphEntity(args.Target, "AdminBreadSmite");
},
Impact = LogImpact.Extreme,
- Message = Loc.GetString("admin-smite-become-bread-description")
+ Message = string.Join(": ", breadName, Loc.GetString("admin-smite-become-bread-description"))
};
args.Verbs.Add(bread);
+ var mouseName = Loc.GetString("admin-smite-become-mouse-name").ToLowerInvariant();
Verb mouse = new()
{
Text = "admin-smite-cluwne-name",
@@ -489,15 +506,16 @@ private void AddSmiteVerbs(GetVerbsEvent args)
_polymorphSystem.PolymorphEntity(args.Target, "AdminMouseSmite");
},
Impact = LogImpact.Extreme,
- Message = Loc.GetString("admin-smite-become-mouse-description")
+ Message = string.Join(": ", mouseName, Loc.GetString("admin-smite-become-mouse-description"))
};
args.Verbs.Add(mouse);
if (TryComp(args.Target, out var actorComponent))
{
+ var ghostKickName = Loc.GetString("admin-smite-ghostkick-name").ToLowerInvariant();
Verb ghostKick = new()
{
- Text = "admin-smite-anger-pointing-arrows-name",
+ Text = ghostKickName,
Category = VerbCategory.Smite,
Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/gavel.svg.192dpi.png")),
Act = () =>
@@ -505,15 +523,18 @@ private void AddSmiteVerbs(GetVerbsEvent args)
_ghostKickManager.DoDisconnect(actorComponent.PlayerSession.Channel, "Smitten.");
},
Impact = LogImpact.Extreme,
- Message = Loc.GetString("admin-smite-ghostkick-description")
+ Message = string.Join(": ", ghostKickName, Loc.GetString("admin-smite-ghostkick-description"))
+
};
args.Verbs.Add(ghostKick);
}
- if (TryComp(args.Target, out var inventory)) {
+ if (TryComp(args.Target, out var inventory))
+ {
+ var nyanifyName = Loc.GetString("admin-smite-nyanify-name").ToLowerInvariant();
Verb nyanify = new()
{
- Text = "admin-smite-dust-name",
+ Text = nyanifyName,
Category = VerbCategory.Smite,
Icon = new SpriteSpecifier.Rsi(new ("/Textures/Clothing/Head/Hats/catears.rsi"), "icon"),
Act = () =>
@@ -524,13 +545,14 @@ private void AddSmiteVerbs(GetVerbsEvent args)
_inventorySystem.TryEquip(args.Target, ears, "head", true, true, false, inventory);
},
Impact = LogImpact.Extreme,
- Message = Loc.GetString("admin-smite-nyanify-description")
+ Message = string.Join(": ", nyanifyName, Loc.GetString("admin-smite-nyanify-description"))
};
args.Verbs.Add(nyanify);
+ var killSignName = Loc.GetString("admin-smite-kill-sign-name").ToLowerInvariant();
Verb killSign = new()
{
- Text = "admin-smite-buffering-name",
+ Text = killSignName,
Category = VerbCategory.Smite,
Icon = new SpriteSpecifier.Rsi(new ("/Textures/Objects/Misc/killsign.rsi"), "icon"),
Act = () =>
@@ -538,13 +560,14 @@ private void AddSmiteVerbs(GetVerbsEvent args)
EnsureComp(args.Target);
},
Impact = LogImpact.Extreme,
- Message = Loc.GetString("admin-smite-kill-sign-description")
+ Message = string.Join(": ", killSignName, Loc.GetString("admin-smite-kill-sign-description"))
};
args.Verbs.Add(killSign);
+ var cluwneName = Loc.GetString("admin-smite-cluwne-name").ToLowerInvariant();
Verb cluwne = new()
{
- Text = "admin-smite-become-instrument-name",
+ Text = cluwneName,
Category = VerbCategory.Smite,
Icon = new SpriteSpecifier.Rsi(new ("/Textures/Clothing/Mask/cluwne.rsi"), "icon"),
@@ -554,13 +577,14 @@ private void AddSmiteVerbs(GetVerbsEvent args)
EnsureComp(args.Target);
},
Impact = LogImpact.Extreme,
- Message = Loc.GetString("admin-smite-cluwne-description")
+ Message = string.Join(": ", cluwneName, Loc.GetString("admin-smite-cluwne-description"))
};
args.Verbs.Add(cluwne);
+ var maidenName = Loc.GetString("admin-smite-maid-name").ToLowerInvariant();
Verb maiden = new()
{
- Text = "admin-smite-remove-gravity-name",
+ Text = maidenName,
Category = VerbCategory.Smite,
Icon = new SpriteSpecifier.Rsi(new ("/Textures/Clothing/Uniforms/Jumpskirt/janimaid.rsi"), "icon"),
Act = () =>
@@ -573,14 +597,15 @@ private void AddSmiteVerbs(GetVerbsEvent args)
});
},
Impact = LogImpact.Extreme,
- Message = Loc.GetString("admin-smite-maid-description")
+ Message = string.Join(": ", maidenName, Loc.GetString("admin-smite-maid-description"))
};
args.Verbs.Add(maiden);
}
+ var angerPointingArrowsName = Loc.GetString("admin-smite-anger-pointing-arrows-name").ToLowerInvariant();
Verb angerPointingArrows = new()
{
- Text = "admin-smite-reptilian-species-swap-name",
+ Text = angerPointingArrowsName,
Category = VerbCategory.Smite,
Icon = new SpriteSpecifier.Rsi(new ("/Textures/Interface/Misc/pointing.rsi"), "pointing"),
Act = () =>
@@ -588,13 +613,14 @@ private void AddSmiteVerbs(GetVerbsEvent args)
EnsureComp(args.Target);
},
Impact = LogImpact.Extreme,
- Message = Loc.GetString("admin-smite-anger-pointing-arrows-description")
+ Message = string.Join(": ", angerPointingArrowsName, Loc.GetString("admin-smite-anger-pointing-arrows-description"))
};
args.Verbs.Add(angerPointingArrows);
+ var dustName = Loc.GetString("admin-smite-dust-name").ToLowerInvariant();
Verb dust = new()
{
- Text = "admin-smite-locker-stuff-name",
+ Text = dustName,
Category = VerbCategory.Smite,
Icon = new SpriteSpecifier.Rsi(new ("/Textures/Objects/Materials/materials.rsi"), "ash"),
Act = () =>
@@ -604,13 +630,14 @@ private void AddSmiteVerbs(GetVerbsEvent args)
_popupSystem.PopupEntity(Loc.GetString("admin-smite-turned-ash-other", ("name", args.Target)), args.Target, PopupType.LargeCaution);
},
Impact = LogImpact.Extreme,
- Message = Loc.GetString("admin-smite-dust-description"),
+ Message = string.Join(": ", dustName, Loc.GetString("admin-smite-dust-description"))
};
args.Verbs.Add(dust);
+ var youtubeVideoSimulationName = Loc.GetString("admin-smite-buffering-name").ToLowerInvariant();
Verb youtubeVideoSimulation = new()
{
- Text = "admin-smite-headstand-name",
+ Text = youtubeVideoSimulationName,
Category = VerbCategory.Smite,
Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/Misc/buffering_smite_icon.png")),
Act = () =>
@@ -618,10 +645,11 @@ private void AddSmiteVerbs(GetVerbsEvent args)
EnsureComp(args.Target);
},
Impact = LogImpact.Extreme,
- Message = Loc.GetString("admin-smite-buffering-description"),
+ Message = string.Join(": ", youtubeVideoSimulationName, Loc.GetString("admin-smite-buffering-description"))
};
args.Verbs.Add(youtubeVideoSimulation);
+ var instrumentationName = Loc.GetString("admin-smite-become-instrument-name").ToLowerInvariant();
Verb instrumentation = new()
{
Text = "admin-smite-become-mouse-name",
@@ -632,13 +660,14 @@ private void AddSmiteVerbs(GetVerbsEvent args)
_polymorphSystem.PolymorphEntity(args.Target, "AdminInstrumentSmite");
},
Impact = LogImpact.Extreme,
- Message = Loc.GetString("admin-smite-become-instrument-description"),
+ Message = string.Join(": ", instrumentationName, Loc.GetString("admin-smite-become-instrument-description"))
};
args.Verbs.Add(instrumentation);
+ var noGravityName = Loc.GetString("admin-smite-remove-gravity-name").ToLowerInvariant();
Verb noGravity = new()
{
- Text = "admin-smite-maid-name",
+ Text = noGravityName,
Category = VerbCategory.Smite,
Icon = new SpriteSpecifier.Rsi(new("/Textures/Structures/Machines/gravity_generator.rsi"), "off"),
Act = () =>
@@ -649,13 +678,14 @@ private void AddSmiteVerbs(GetVerbsEvent args)
Dirty(args.Target, grav);
},
Impact = LogImpact.Extreme,
- Message = Loc.GetString("admin-smite-remove-gravity-description"),
+ Message = string.Join(": ", noGravityName, Loc.GetString("admin-smite-remove-gravity-description"))
};
args.Verbs.Add(noGravity);
+ var reptilianName = Loc.GetString("admin-smite-reptilian-species-swap-name").ToLowerInvariant();
Verb reptilian = new()
{
- Text = "admin-smite-zoom-in-name",
+ Text = reptilianName,
Category = VerbCategory.Smite,
Icon = new SpriteSpecifier.Rsi(new ("/Textures/Objects/Fun/toys.rsi"), "plushie_lizard"),
Act = () =>
@@ -663,13 +693,14 @@ private void AddSmiteVerbs(GetVerbsEvent args)
_polymorphSystem.PolymorphEntity(args.Target, "AdminLizardSmite");
},
Impact = LogImpact.Extreme,
- Message = Loc.GetString("admin-smite-reptilian-species-swap-description"),
+ Message = string.Join(": ", reptilianName, Loc.GetString("admin-smite-reptilian-species-swap-description"))
};
args.Verbs.Add(reptilian);
+ var lockerName = Loc.GetString("admin-smite-locker-stuff-name").ToLowerInvariant();
Verb locker = new()
{
- Text = "admin-smite-flip-eye-name",
+ Text = lockerName,
Category = VerbCategory.Smite,
Icon = new SpriteSpecifier.Rsi(new ("/Textures/Structures/Storage/closet.rsi"), "generic"),
Act = () =>
@@ -685,10 +716,11 @@ private void AddSmiteVerbs(GetVerbsEvent args)
_weldableSystem.SetWeldedState(locker, true);
},
Impact = LogImpact.Extreme,
- Message = Loc.GetString("admin-smite-locker-stuff-description"),
+ Message = string.Join(": ", lockerName, Loc.GetString("admin-smite-locker-stuff-description"))
};
args.Verbs.Add(locker);
+ var headstandName = Loc.GetString("admin-smite-headstand-name").ToLowerInvariant();
Verb headstand = new()
{
Text = "admin-smite-run-walk-swap-name",
@@ -699,13 +731,14 @@ private void AddSmiteVerbs(GetVerbsEvent args)
EnsureComp(args.Target);
},
Impact = LogImpact.Extreme,
- Message = Loc.GetString("admin-smite-headstand-description"),
+ Message = string.Join(": ", headstandName, Loc.GetString("admin-smite-headstand-description"))
};
args.Verbs.Add(headstand);
+ var zoomInName = Loc.GetString("admin-smite-zoom-in-name").ToLowerInvariant();
Verb zoomIn = new()
{
- Text = "admin-smite-super-speed-name",
+ Text = zoomInName,
Category = VerbCategory.Smite,
Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/AdminActions/zoom.png")),
Act = () =>
@@ -714,13 +747,14 @@ private void AddSmiteVerbs(GetVerbsEvent args)
_eyeSystem.SetZoom(args.Target, eye.TargetZoom * 0.2f, ignoreLimits: true);
},
Impact = LogImpact.Extreme,
- Message = Loc.GetString("admin-smite-zoom-in-description"),
+ Message = string.Join(": ", zoomInName, Loc.GetString("admin-smite-zoom-in-description"))
};
args.Verbs.Add(zoomIn);
+ var flipEyeName = Loc.GetString("admin-smite-flip-eye-name").ToLowerInvariant();
Verb flipEye = new()
{
- Text = "admin-smite-stomach-removal-name",
+ Text = flipEyeName,
Category = VerbCategory.Smite,
Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/AdminActions/flip.png")),
Act = () =>
@@ -729,13 +763,14 @@ private void AddSmiteVerbs(GetVerbsEvent args)
_eyeSystem.SetZoom(args.Target, eye.TargetZoom * -1, ignoreLimits: true);
},
Impact = LogImpact.Extreme,
- Message = Loc.GetString("admin-smite-flip-eye-description"),
+ Message = string.Join(": ", flipEyeName, Loc.GetString("admin-smite-flip-eye-description"))
};
args.Verbs.Add(flipEye);
+ var runWalkSwapName = Loc.GetString("admin-smite-run-walk-swap-name").ToLowerInvariant();
Verb runWalkSwap = new()
{
- Text = "admin-smite-speak-backwards-name",
+ Text = runWalkSwapName,
Category = VerbCategory.Smite,
Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/AdminActions/run-walk-swap.png")),
Act = () =>
@@ -749,13 +784,14 @@ private void AddSmiteVerbs(GetVerbsEvent args)
args.Target, PopupType.LargeCaution);
},
Impact = LogImpact.Extreme,
- Message = Loc.GetString("admin-smite-run-walk-swap-description"),
+ Message = string.Join(": ", runWalkSwapName, Loc.GetString("admin-smite-run-walk-swap-description"))
};
args.Verbs.Add(runWalkSwap);
+ var backwardsAccentName = Loc.GetString("admin-smite-speak-backwards-name").ToLowerInvariant();
Verb backwardsAccent = new()
{
- Text = "admin-smite-lung-removal-name",
+ Text = backwardsAccentName,
Category = VerbCategory.Smite,
Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/AdminActions/help-backwards.png")),
Act = () =>
@@ -763,13 +799,14 @@ private void AddSmiteVerbs(GetVerbsEvent args)
EnsureComp(args.Target);
},
Impact = LogImpact.Extreme,
- Message = Loc.GetString("admin-smite-speak-backwards-description"),
+ Message = string.Join(": ", backwardsAccentName, Loc.GetString("admin-smite-speak-backwards-description"))
};
args.Verbs.Add(backwardsAccent);
+ var disarmProneName = Loc.GetString("admin-smite-disarm-prone-name").ToLowerInvariant();
Verb disarmProne = new()
{
- Text = "admin-smite-disarm-prone-name",
+ Text = disarmProneName,
Category = VerbCategory.Smite,
Icon = new SpriteSpecifier.Texture(new ("/Textures/Interface/Actions/disarm.png")),
Act = () =>
@@ -777,10 +814,11 @@ private void AddSmiteVerbs(GetVerbsEvent args)
EnsureComp(args.Target);
},
Impact = LogImpact.Extreme,
- Message = Loc.GetString("admin-smite-disarm-prone-description"),
+ Message = string.Join(": ", disarmProneName, Loc.GetString("admin-smite-disarm-prone-description"))
};
args.Verbs.Add(disarmProne);
+ var superSpeedName = Loc.GetString("admin-smite-super-speed-name").ToLowerInvariant();
Verb superSpeed = new()
{
Text = "admin-smite-garbage-can-name",
@@ -795,41 +833,45 @@ private void AddSmiteVerbs(GetVerbsEvent args)
args.Target, PopupType.LargeCaution);
},
Impact = LogImpact.Extreme,
- Message = Loc.GetString("admin-smite-super-speed-description"),
+ Message = string.Join(": ", superSpeedName, Loc.GetString("admin-smite-super-speed-description"))
};
args.Verbs.Add(superSpeed);
//Bonk
+ var superBonkLiteName = Loc.GetString("admin-smite-super-bonk-lite-name").ToLowerInvariant();
Verb superBonkLite = new()
{
- Text = "admin-smite-super-bonk-name",
+ Text = superBonkLiteName,
Category = VerbCategory.Smite,
Icon = new SpriteSpecifier.Rsi(new("Structures/Furniture/Tables/glass.rsi"), "full"),
Act = () =>
{
_superBonkSystem.StartSuperBonk(args.Target, stopWhenDead: true);
},
- Message = Loc.GetString("admin-smite-super-bonk-lite-description"),
Impact = LogImpact.Extreme,
+ Message = string.Join(": ", superBonkLiteName, Loc.GetString("admin-smite-super-bonk-lite-description"))
};
args.Verbs.Add(superBonkLite);
+
+ var superBonkName = Loc.GetString("admin-smite-super-bonk-name").ToLowerInvariant();
Verb superBonk= new()
{
- Text = "admin-smite-super-bonk-lite-name",
+ Text = superBonkName,
Category = VerbCategory.Smite,
Icon = new SpriteSpecifier.Rsi(new("Structures/Furniture/Tables/generic.rsi"), "full"),
Act = () =>
{
_superBonkSystem.StartSuperBonk(args.Target);
},
- Message = Loc.GetString("admin-smite-super-bonk-description"),
Impact = LogImpact.Extreme,
+ Message = string.Join(": ", superBonkName, Loc.GetString("admin-smite-super-bonk-description"))
};
args.Verbs.Add(superBonk);
+ var superslipName = Loc.GetString("admin-smite-super-slip-name").ToLowerInvariant();
Verb superslip = new()
{
- Text = "admin-smite-super-slip-name",
+ Text = superslipName,
Category = VerbCategory.Smite,
Icon = new SpriteSpecifier.Rsi(new("Objects/Specific/Janitorial/soap.rsi"), "omega-4"),
Act = () =>
@@ -849,7 +891,7 @@ private void AddSmiteVerbs(GetVerbsEvent args)
}
},
Impact = LogImpact.Extreme,
- Message = Loc.GetString("admin-smite-super-slip-description")
+ Message = string.Join(": ", superslipName, Loc.GetString("admin-smite-super-slip-description"))
};
args.Verbs.Add(superslip);
}
diff --git a/Content.Server/Administration/Systems/AdminVerbSystem.Tools.cs b/Content.Server/Administration/Systems/AdminVerbSystem.Tools.cs
index fef8a031d9d..70fcfccc4eb 100644
--- a/Content.Server/Administration/Systems/AdminVerbSystem.Tools.cs
+++ b/Content.Server/Administration/Systems/AdminVerbSystem.Tools.cs
@@ -52,7 +52,6 @@ public sealed partial class AdminVerbSystem
[Dependency] private readonly StationJobsSystem _stationJobsSystem = default!;
[Dependency] private readonly JointSystem _jointSystem = default!;
[Dependency] private readonly BatterySystem _batterySystem = default!;
- [Dependency] private readonly SharedTransformSystem _xformSystem = default!;
[Dependency] private readonly MetaDataSystem _metaSystem = default!;
[Dependency] private readonly GunSystem _gun = default!;
@@ -90,22 +89,22 @@ private void AddTricksVerbs(GetVerbsEvent args)
args.Verbs.Add(bolt);
}
- if (TryComp(args.Target, out var airlock))
+ if (TryComp(args.Target, out var airlockComp))
{
Verb emergencyAccess = new()
{
- Text = airlock.EmergencyAccess ? "Emergency Access Off" : "Emergency Access On",
+ Text = airlockComp.EmergencyAccess ? "Emergency Access Off" : "Emergency Access On",
Category = VerbCategory.Tricks,
Icon = new SpriteSpecifier.Texture(new("/Textures/Interface/AdminActions/emergency_access.png")),
Act = () =>
{
- _airlockSystem.ToggleEmergencyAccess(args.Target, airlock);
+ _airlockSystem.SetEmergencyAccess((args.Target, airlockComp), !airlockComp.EmergencyAccess);
},
Impact = LogImpact.Medium,
- Message = Loc.GetString(airlock.EmergencyAccess
+ Message = Loc.GetString(airlockComp.EmergencyAccess
? "admin-trick-emergency-access-off-description"
: "admin-trick-emergency-access-on-description"),
- Priority = (int) (airlock.EmergencyAccess ? TricksVerbPriorities.EmergencyAccessOff : TricksVerbPriorities.EmergencyAccessOn),
+ Priority = (int) (airlockComp.EmergencyAccess ? TricksVerbPriorities.EmergencyAccessOff : TricksVerbPriorities.EmergencyAccessOn),
};
args.Verbs.Add(emergencyAccess);
}
@@ -327,7 +326,7 @@ private void AddTricksVerbs(GetVerbsEvent args)
Act = () =>
{
var (mapUid, gridUid) = _adminTestArenaSystem.AssertArenaLoaded(player);
- _xformSystem.SetCoordinates(args.Target, new EntityCoordinates(gridUid ?? mapUid, Vector2.One));
+ _transformSystem.SetCoordinates(args.Target, new EntityCoordinates(gridUid ?? mapUid, Vector2.One));
},
Impact = LogImpact.Medium,
Message = Loc.GetString("admin-trick-send-to-test-arena-description"),
@@ -533,7 +532,7 @@ private void AddTricksVerbs(GetVerbsEvent args)
if (shuttle is null)
return;
- _xformSystem.SetCoordinates(args.User, new EntityCoordinates(shuttle.Value, Vector2.Zero));
+ _transformSystem.SetCoordinates(args.User, new EntityCoordinates(shuttle.Value, Vector2.Zero));
},
Impact = LogImpact.Low,
Message = Loc.GetString("admin-trick-locate-cargo-shuttle-description"),
diff --git a/Content.Server/Administration/Systems/AdminVerbSystem.cs b/Content.Server/Administration/Systems/AdminVerbSystem.cs
index 5aa05ce28b7..2ab27e4388e 100644
--- a/Content.Server/Administration/Systems/AdminVerbSystem.cs
+++ b/Content.Server/Administration/Systems/AdminVerbSystem.cs
@@ -35,10 +35,8 @@
using Robust.Shared.Utility;
using System.Linq;
using Content.Server.Silicons.Laws;
-using Content.Shared.Silicons.Laws;
using Content.Shared.Silicons.Laws.Components;
using Robust.Server.Player;
-using Content.Shared.Mind;
using Robust.Shared.Physics.Components;
using static Content.Shared.Configurable.ConfigurationComponent;
@@ -63,7 +61,6 @@ public sealed partial class AdminVerbSystem : EntitySystem
[Dependency] private readonly ArtifactSystem _artifactSystem = default!;
[Dependency] private readonly UserInterfaceSystem _uiSystem = default!;
[Dependency] private readonly PrayerSystem _prayerSystem = default!;
- [Dependency] private readonly EuiManager _eui = default!;
[Dependency] private readonly MindSystem _mindSystem = default!;
[Dependency] private readonly ToolshedManager _toolshed = default!;
[Dependency] private readonly RejuvenateSystem _rejuvenate = default!;
@@ -294,7 +291,7 @@ private void AddAdminVerbs(GetVerbsEvent