diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index 2e90f0eed03..f248264219c 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -3,18 +3,36 @@
# C# code
/Content.*/ @DeltaV-Station/maintainers
+# Any assets
+/Resources/ @DeltaV-Station/maintainers
+
+# Server config files
+/Resources/ConfigPresets/ @MilonPL
+
# YML files
/Resources/*.yml @DeltaV-Station/yaml-maintainers
/Resources/**/*.yml @DeltaV-Station/yaml-maintainers
# Sprites
-/Resources/Textures/ @IamVelcroboy
+/Resources/Textures/ @DeltaV-Station/direction
# Lobby art and music - automatically direction issues since its immediately visible to players
-/Resources/Audio/Lobby/ @DeltaV-Station/game-directors
-/Resources/Textures/LobbyScreens/ @DeltaV-Station/game-directors
+/Resources/Audio/Lobby/ @DeltaV-Station/direction
+/Resources/Textures/LobbyScreens/ @DeltaV-Station/direction
# Maps
/Resources/Maps/ @DeltaV-Station/maptainers
/Resources/Prototypes/Maps/ @DeltaV-Station/maptainers
/Content.IntegrationTests/Tests/PostMapInitTest.cs @DeltaV-Station/maptainers
+
+# Server rules
+/Resources/ServerInfo/Guidebook/DeltaV/Rules/ @DeltaV-Station/head-administrators
+
+# Tools and scripts
+/Tools/ @deltanedas @MilonPL
+
+# Workflows, codeowners, templates, etc.
+/.github/ @deltanedas @MilonPL
+
+# Standalone files in the root repo
+/* @deltanedas
diff --git a/Content.Client/Access/UI/AgentIDCardBoundUserInterface.cs b/Content.Client/Access/UI/AgentIDCardBoundUserInterface.cs
index 050756fcd14..93ce5538aa1 100644
--- a/Content.Client/Access/UI/AgentIDCardBoundUserInterface.cs
+++ b/Content.Client/Access/UI/AgentIDCardBoundUserInterface.cs
@@ -26,6 +26,13 @@ protected override void Open()
_window.OnNameChanged += OnNameChanged;
_window.OnJobChanged += OnJobChanged;
_window.OnJobIconChanged += OnJobIconChanged;
+ _window.OnNumberChanged += OnNumberChanged; // DeltaV
+ }
+
+ // DeltaV - Add number change handler
+ private void OnNumberChanged(uint newNumber)
+ {
+ SendMessage(new AgentIDCardNumberChangedMessage(newNumber));
}
private void OnNameChanged(string newName)
@@ -56,6 +63,7 @@ protected override void UpdateState(BoundUserInterfaceState state)
_window.SetCurrentName(cast.CurrentName);
_window.SetCurrentJob(cast.CurrentJob);
_window.SetAllowedIcons(cast.CurrentJobIconId);
+ _window.SetCurrentNumber(cast.CurrentNumber); // DeltaV
}
}
}
diff --git a/Content.Client/Access/UI/AgentIDCardWindow.xaml b/Content.Client/Access/UI/AgentIDCardWindow.xaml
index 7d091e4e165..a2ddd1c417d 100644
--- a/Content.Client/Access/UI/AgentIDCardWindow.xaml
+++ b/Content.Client/Access/UI/AgentIDCardWindow.xaml
@@ -6,6 +6,10 @@
+
+
+
+
diff --git a/Content.Client/Access/UI/AgentIDCardWindow.xaml.cs b/Content.Client/Access/UI/AgentIDCardWindow.xaml.cs
index 320bb88a67e..a342013d314 100644
--- a/Content.Client/Access/UI/AgentIDCardWindow.xaml.cs
+++ b/Content.Client/Access/UI/AgentIDCardWindow.xaml.cs
@@ -21,9 +21,13 @@ public sealed partial class AgentIDCardWindow : DefaultWindow
private const int JobIconColumnCount = 10;
+ private const int MaxNumberLength = 4; // DeltaV - Same as NewChatPopup
+
public event Action? OnNameChanged;
public event Action? OnJobChanged;
+ public event Action? OnNumberChanged; // DeltaV - Add event for number changes
+
public event Action>? OnJobIconChanged;
public AgentIDCardWindow()
@@ -37,6 +41,37 @@ public AgentIDCardWindow()
JobLineEdit.OnTextEntered += e => OnJobChanged?.Invoke(e.Text);
JobLineEdit.OnFocusExit += e => OnJobChanged?.Invoke(e.Text);
+
+ // DeltaV - Add handlers for number changes
+ NumberLineEdit.OnTextEntered += OnNumberEntered;
+ NumberLineEdit.OnFocusExit += OnNumberEntered;
+
+ // DeltaV - Filter to only allow digits
+ NumberLineEdit.OnTextChanged += args =>
+ {
+ if (args.Text.Length > MaxNumberLength)
+ {
+ NumberLineEdit.Text = args.Text[..MaxNumberLength];
+ }
+
+ // Filter to digits only
+ var newText = string.Concat(args.Text.Where(char.IsDigit));
+ if (newText != args.Text)
+ NumberLineEdit.Text = newText;
+ };
+ }
+
+ // DeltaV - Add number validation and event
+ private void OnNumberEntered(LineEdit.LineEditEventArgs args)
+ {
+ if (uint.TryParse(args.Text, out var number) && number > 0)
+ OnNumberChanged?.Invoke(number);
+ }
+
+ // DeltaV - Add setter for current number
+ public void SetCurrentNumber(uint? number)
+ {
+ NumberLineEdit.Text = number?.ToString("D4") ?? "";
}
public void SetAllowedIcons(string currentJobIconId)
diff --git a/Content.Client/Actions/ActionsSystem.cs b/Content.Client/Actions/ActionsSystem.cs
index 57c861bd20f..a00b58f54f9 100644
--- a/Content.Client/Actions/ActionsSystem.cs
+++ b/Content.Client/Actions/ActionsSystem.cs
@@ -259,13 +259,13 @@ public void UnlinkAllActions()
public void LinkAllActions(ActionsComponent? actions = null)
{
- if (_playerManager.LocalEntity is not { } user ||
- !Resolve(user, ref actions, false))
- {
- return;
- }
+ if (_playerManager.LocalEntity is not { } user ||
+ !Resolve(user, ref actions, false))
+ {
+ return;
+ }
- LinkActions?.Invoke(actions);
+ LinkActions?.Invoke(actions);
}
public override void Shutdown()
diff --git a/Content.Client/Administration/UI/Notes/NoteEdit.xaml.cs b/Content.Client/Administration/UI/Notes/NoteEdit.xaml.cs
index a412e47396b..c8e3afeb22c 100644
--- a/Content.Client/Administration/UI/Notes/NoteEdit.xaml.cs
+++ b/Content.Client/Administration/UI/Notes/NoteEdit.xaml.cs
@@ -159,6 +159,7 @@ private void OnTypeChanged(OptionButton.ItemSelectedEventArgs args)
SecretCheckBox.Pressed = false;
SeverityOption.Disabled = false;
PermanentCheckBox.Pressed = true;
+ SubmitButton.Disabled = true;
UpdatePermanentCheckboxFields();
break;
case (int) NoteType.Message: // Message: these are shown to the player when they log on
diff --git a/Content.Client/Atmos/Components/PipeColorVisualsComponent.cs b/Content.Client/Atmos/Components/PipeColorVisualsComponent.cs
index 355b10cb4a4..9b24b1adc25 100644
--- a/Content.Client/Atmos/Components/PipeColorVisualsComponent.cs
+++ b/Content.Client/Atmos/Components/PipeColorVisualsComponent.cs
@@ -1,8 +1,4 @@
-using Robust.Shared.GameObjects;
-
namespace Content.Client.Atmos.Components;
[RegisterComponent]
-public sealed partial class PipeColorVisualsComponent : Component
-{
-}
+public sealed partial class PipeColorVisualsComponent : Component;
diff --git a/Content.Client/Atmos/Consoles/AtmosAlarmEntryContainer.xaml b/Content.Client/Atmos/Consoles/AtmosAlarmEntryContainer.xaml
index 6bdfb3989f9..3dbe14e6b6d 100644
--- a/Content.Client/Atmos/Consoles/AtmosAlarmEntryContainer.xaml
+++ b/Content.Client/Atmos/Consoles/AtmosAlarmEntryContainer.xaml
@@ -1,6 +1,5 @@
@@ -62,7 +61,7 @@
-
+
diff --git a/Content.Client/Atmos/Consoles/AtmosAlarmEntryContainer.xaml.cs b/Content.Client/Atmos/Consoles/AtmosAlarmEntryContainer.xaml.cs
index 79bb66560e3..e533ef2dce0 100644
--- a/Content.Client/Atmos/Consoles/AtmosAlarmEntryContainer.xaml.cs
+++ b/Content.Client/Atmos/Consoles/AtmosAlarmEntryContainer.xaml.cs
@@ -136,8 +136,9 @@ public void UpdateEntry(AtmosAlertsComputerEntry entry, bool isFocus, AtmosAlert
GasGridContainer.RemoveAllChildren();
var gasData = focusData.Value.GasData.Where(g => g.Key != Gas.Oxygen);
+ var keyValuePairs = gasData.ToList();
- if (gasData.Count() == 0)
+ if (keyValuePairs.Count == 0)
{
// No other gases
var gasLabel = new Label()
@@ -158,13 +159,11 @@ public void UpdateEntry(AtmosAlertsComputerEntry entry, bool isFocus, AtmosAlert
else
{
// Add an entry for each gas
- foreach ((var gas, (var mol, var percent, var alert)) in gasData)
+ foreach ((var gas, (var mol, var percent, var alert)) in keyValuePairs)
{
- var gasPercent = (FixedPoint2)0f;
- gasPercent = percent * 100f;
+ FixedPoint2 gasPercent = percent * 100f;
- if (!_gasShorthands.TryGetValue(gas, out var gasShorthand))
- gasShorthand = "X";
+ var gasShorthand = _gasShorthands.GetValueOrDefault(gas, "X");
var gasLabel = new Label()
{
diff --git a/Content.Client/Atmos/Consoles/AtmosAlertsComputerBoundUserInterface.cs b/Content.Client/Atmos/Consoles/AtmosAlertsComputerBoundUserInterface.cs
index 08cae979b9b..6f0e7f80da1 100644
--- a/Content.Client/Atmos/Consoles/AtmosAlertsComputerBoundUserInterface.cs
+++ b/Content.Client/Atmos/Consoles/AtmosAlertsComputerBoundUserInterface.cs
@@ -14,8 +14,6 @@ protected override void Open()
_menu = new AtmosAlertsComputerWindow(this, Owner);
_menu.OpenCentered();
_menu.OnClose += Close;
-
- EntMan.TryGetComponent(Owner, out var xform);
}
protected override void UpdateState(BoundUserInterfaceState state)
@@ -24,9 +22,6 @@ protected override void UpdateState(BoundUserInterfaceState state)
var castState = (AtmosAlertsComputerBoundInterfaceState) state;
- if (castState == null)
- return;
-
EntMan.TryGetComponent(Owner, out var xform);
_menu?.UpdateUI(xform?.Coordinates, castState.AirAlarms, castState.FireAlarms, castState.FocusData);
}
diff --git a/Content.Client/Atmos/Consoles/AtmosAlertsComputerWindow.xaml b/Content.Client/Atmos/Consoles/AtmosAlertsComputerWindow.xaml
index 8824a776ee6..e5ede1b92e3 100644
--- a/Content.Client/Atmos/Consoles/AtmosAlertsComputerWindow.xaml
+++ b/Content.Client/Atmos/Consoles/AtmosAlertsComputerWindow.xaml
@@ -1,7 +1,6 @@
(OnInit);
- SubscribeLocalEvent(OnAppearanceChanged, after: new[] { typeof(SubFloorHideSystem) });
+ SubscribeLocalEvent(OnAppearanceChanged, after: [typeof(SubFloorHideSystem)]);
}
private void OnInit(EntityUid uid, PipeAppearanceComponent component, ComponentInit args)
@@ -84,7 +82,8 @@ private void OnAppearanceChanged(EntityUid uid, PipeAppearanceComponent componen
layer.Visible &= visible;
- if (!visible) continue;
+ if (!visible)
+ continue;
layer.Color = color;
}
diff --git a/Content.Client/Atmos/EntitySystems/GasPressurePumpSystem.cs b/Content.Client/Atmos/EntitySystems/GasPressurePumpSystem.cs
new file mode 100644
index 00000000000..54e16bc8621
--- /dev/null
+++ b/Content.Client/Atmos/EntitySystems/GasPressurePumpSystem.cs
@@ -0,0 +1,23 @@
+using Content.Client.Atmos.UI;
+using Content.Shared.Atmos.Components;
+using Content.Shared.Atmos.EntitySystems;
+using Content.Shared.Atmos.Piping.Binary.Components;
+
+namespace Content.Client.Atmos.EntitySystems;
+
+public sealed class GasPressurePumpSystem : SharedGasPressurePumpSystem
+{
+ public override void Initialize()
+ {
+ base.Initialize();
+ SubscribeLocalEvent(OnPumpUpdate);
+ }
+
+ private void OnPumpUpdate(Entity ent, ref AfterAutoHandleStateEvent args)
+ {
+ if (UserInterfaceSystem.TryGetOpenUi(ent.Owner, GasPressurePumpUiKey.Key, out var bui))
+ {
+ bui.Update();
+ }
+ }
+}
diff --git a/Content.Client/Atmos/Monitor/AtmosAlarmableVisualsSystem.cs b/Content.Client/Atmos/Monitor/AtmosAlarmableVisualsSystem.cs
index 019f25f376b..18ca2234752 100644
--- a/Content.Client/Atmos/Monitor/AtmosAlarmableVisualsSystem.cs
+++ b/Content.Client/Atmos/Monitor/AtmosAlarmableVisualsSystem.cs
@@ -1,12 +1,7 @@
-using System.Collections.Generic;
using Content.Shared.Atmos.Monitor;
using Content.Shared.Power;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
-using Robust.Shared.GameObjects;
-using Robust.Shared.IoC;
-using Robust.Shared.Maths;
-using Robust.Shared.Serialization.Manager.Attributes;
namespace Content.Client.Atmos.Monitor;
@@ -27,7 +22,7 @@ protected override void OnAppearanceChange(EntityUid uid, AtmosAlarmableVisualsC
{
foreach (var visLayer in component.HideOnDepowered)
{
- if (args.Sprite.LayerMapTryGet(visLayer, out int powerVisibilityLayer))
+ if (args.Sprite.LayerMapTryGet(visLayer, out var powerVisibilityLayer))
args.Sprite.LayerSetVisible(powerVisibilityLayer, powered);
}
}
@@ -36,7 +31,7 @@ protected override void OnAppearanceChange(EntityUid uid, AtmosAlarmableVisualsC
{
foreach (var (setLayer, powerState) in component.SetOnDepowered)
{
- if (args.Sprite.LayerMapTryGet(setLayer, out int setStateLayer))
+ if (args.Sprite.LayerMapTryGet(setLayer, out var setStateLayer))
args.Sprite.LayerSetState(setStateLayer, new RSI.StateId(powerState));
}
}
diff --git a/Content.Client/Atmos/Monitor/UI/AirAlarmBoundUserInterface.cs b/Content.Client/Atmos/Monitor/UI/AirAlarmBoundUserInterface.cs
index d9e94e373b4..650f96eec97 100644
--- a/Content.Client/Atmos/Monitor/UI/AirAlarmBoundUserInterface.cs
+++ b/Content.Client/Atmos/Monitor/UI/AirAlarmBoundUserInterface.cs
@@ -1,11 +1,7 @@
using Content.Shared.Atmos;
using Content.Shared.Atmos.Monitor;
using Content.Shared.Atmos.Monitor.Components;
-using Robust.Client.GameObjects;
using Robust.Client.UserInterface;
-using Robust.Shared.GameObjects;
-using Robust.Shared.IoC;
-using Robust.Shared.Log;
namespace Content.Client.Atmos.Monitor.UI;
@@ -78,6 +74,7 @@ protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
- if (disposing) _window?.Dispose();
+ if (disposing)
+ _window?.Dispose();
}
}
diff --git a/Content.Client/Atmos/Monitor/UI/AirAlarmWindow.xaml.cs b/Content.Client/Atmos/Monitor/UI/AirAlarmWindow.xaml.cs
index e1425ac491b..65164983865 100644
--- a/Content.Client/Atmos/Monitor/UI/AirAlarmWindow.xaml.cs
+++ b/Content.Client/Atmos/Monitor/UI/AirAlarmWindow.xaml.cs
@@ -8,7 +8,6 @@
using Content.Shared.Atmos.Piping.Unary.Components;
using Content.Shared.Temperature;
using Robust.Client.AutoGenerated;
-using Robust.Client.GameObjects;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
@@ -59,7 +58,7 @@ public AirAlarmWindow()
AirAlarmMode.Fill => "air-alarm-ui-mode-fill",
AirAlarmMode.Panic => "air-alarm-ui-mode-panic",
AirAlarmMode.None => "air-alarm-ui-mode-none",
- _ => "error"
+ _ => "error",
};
_modes.AddItem(Loc.GetString(text));
}
@@ -70,7 +69,7 @@ public AirAlarmWindow()
AirAlarmModeChanged!.Invoke((AirAlarmMode) args.Id);
};
- _autoMode.OnToggled += args =>
+ _autoMode.OnToggled += _ =>
{
AutoModeChanged!.Invoke(_autoMode.Pressed);
};
@@ -176,22 +175,18 @@ public void UpdateDeviceData(string addr, IAtmosDeviceData device)
public static Color ColorForThreshold(float amount, AtmosAlarmThreshold threshold)
{
- threshold.CheckThreshold(amount, out AtmosAlarmType curAlarm);
+ threshold.CheckThreshold(amount, out var curAlarm);
return ColorForAlarm(curAlarm);
}
public static Color ColorForAlarm(AtmosAlarmType curAlarm)
{
- if(curAlarm == AtmosAlarmType.Danger)
+ return curAlarm switch
{
- return StyleNano.DangerousRedFore;
- }
- else if(curAlarm == AtmosAlarmType.Warning)
- {
- return StyleNano.ConcerningOrangeFore;
- }
-
- return StyleNano.GoodGreenFore;
+ AtmosAlarmType.Danger => StyleNano.DangerousRedFore,
+ AtmosAlarmType.Warning => StyleNano.ConcerningOrangeFore,
+ _ => StyleNano.GoodGreenFore,
+ };
}
diff --git a/Content.Client/Atmos/Monitor/UI/Widgets/PumpControl.xaml.cs b/Content.Client/Atmos/Monitor/UI/Widgets/PumpControl.xaml.cs
index 17b03b84684..2cd51d6fc7f 100644
--- a/Content.Client/Atmos/Monitor/UI/Widgets/PumpControl.xaml.cs
+++ b/Content.Client/Atmos/Monitor/UI/Widgets/PumpControl.xaml.cs
@@ -1,12 +1,8 @@
-using System;
-using Content.Shared.Atmos.Monitor;
using Content.Shared.Atmos.Monitor.Components;
using Content.Shared.Atmos.Piping.Unary.Components;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
-using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
-using Robust.Shared.Localization;
namespace Content.Client.Atmos.Monitor.UI.Widgets;
@@ -25,7 +21,7 @@ public sealed partial class PumpControl : BoxContainer
private OptionButton _pressureCheck => CPressureCheck;
private FloatSpinBox _externalBound => CExternalBound;
private FloatSpinBox _internalBound => CInternalBound;
- private Button _copySettings => CCopySettings;
+ private Button _copySettings => CCopySettings;
public PumpControl(GasVentPumpData data, string address)
{
@@ -86,7 +82,7 @@ public PumpControl(GasVentPumpData data, string address)
_data.PressureChecks = (VentPressureBound) args.Id;
PumpDataChanged?.Invoke(_address, _data);
};
-
+
_copySettings.OnPressed += _ =>
{
PumpDataCopied?.Invoke(_data);
diff --git a/Content.Client/Atmos/Monitor/UI/Widgets/ScrubberControl.xaml.cs b/Content.Client/Atmos/Monitor/UI/Widgets/ScrubberControl.xaml.cs
index f2241bcd8da..c16ff688c93 100644
--- a/Content.Client/Atmos/Monitor/UI/Widgets/ScrubberControl.xaml.cs
+++ b/Content.Client/Atmos/Monitor/UI/Widgets/ScrubberControl.xaml.cs
@@ -1,15 +1,9 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
using Content.Shared.Atmos;
-using Content.Shared.Atmos.Monitor;
using Content.Shared.Atmos.Monitor.Components;
using Content.Shared.Atmos.Piping.Unary.Components;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
-using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
-using Robust.Shared.Localization;
namespace Content.Client.Atmos.Monitor.UI.Widgets;
@@ -27,7 +21,7 @@ public sealed partial class ScrubberControl : BoxContainer
private OptionButton _pumpDirection => CPumpDirection;
private FloatSpinBox _volumeRate => CVolumeRate;
private CheckBox _wideNet => CWideNet;
- private Button _copySettings => CCopySettings;
+ private Button _copySettings => CCopySettings;
private GridContainer _gases => CGasContainer;
private Dictionary _gasControls = new();
@@ -77,7 +71,7 @@ public ScrubberControl(GasVentScrubberData data, string address)
_data.PumpDirection = (ScrubberPumpDirection) args.Id;
ScrubberDataChanged?.Invoke(_address, _data);
};
-
+
_copySettings.OnPressed += _ =>
{
ScrubberDataCopied?.Invoke(_data);
diff --git a/Content.Client/Atmos/Monitor/UI/Widgets/SensorInfo.xaml.cs b/Content.Client/Atmos/Monitor/UI/Widgets/SensorInfo.xaml.cs
index da602cd7479..9e60b6cea62 100644
--- a/Content.Client/Atmos/Monitor/UI/Widgets/SensorInfo.xaml.cs
+++ b/Content.Client/Atmos/Monitor/UI/Widgets/SensorInfo.xaml.cs
@@ -43,7 +43,8 @@ public SensorInfo(AtmosSensorData data, string address)
var label = new RichTextLabel();
var fractionGas = amount / data.TotalMoles;
- label.SetMarkup(Loc.GetString("air-alarm-ui-gases-indicator", ("gas", $"{gas}"),
+ label.SetMarkup(Loc.GetString("air-alarm-ui-gases-indicator",
+ ("gas", $"{gas}"),
("color", AirAlarmWindow.ColorForThreshold(fractionGas, data.GasThresholds[gas])),
("amount", $"{amount:0.####}"),
("percentage", $"{(100 * fractionGas):0.##}")));
@@ -53,9 +54,9 @@ public SensorInfo(AtmosSensorData data, string address)
var threshold = data.GasThresholds[gas];
var gasThresholdControl = new ThresholdControl(Loc.GetString($"air-alarm-ui-thresholds-gas-title", ("gas", $"{gas}")), threshold, AtmosMonitorThresholdType.Gas, gas, 100);
gasThresholdControl.Margin = new Thickness(20, 2, 2, 2);
- gasThresholdControl.ThresholdDataChanged += (type, threshold, arg3) =>
+ gasThresholdControl.ThresholdDataChanged += (type, alarmThreshold, arg3) =>
{
- OnThresholdUpdate!(_address, type, threshold, arg3);
+ OnThresholdUpdate!(_address, type, alarmThreshold, arg3);
};
_gasThresholds.Add(gas, gasThresholdControl);
@@ -64,7 +65,8 @@ public SensorInfo(AtmosSensorData data, string address)
_pressureThreshold = new ThresholdControl(Loc.GetString("air-alarm-ui-thresholds-pressure-title"), data.PressureThreshold, AtmosMonitorThresholdType.Pressure);
PressureThresholdContainer.AddChild(_pressureThreshold);
- _temperatureThreshold = new ThresholdControl(Loc.GetString("air-alarm-ui-thresholds-temperature-title"), data.TemperatureThreshold,
+ _temperatureThreshold = new ThresholdControl(Loc.GetString("air-alarm-ui-thresholds-temperature-title"),
+ data.TemperatureThreshold,
AtmosMonitorThresholdType.Temperature);
TemperatureThresholdContainer.AddChild(_temperatureThreshold);
@@ -103,7 +105,8 @@ public void ChangeData(AtmosSensorData data)
}
var fractionGas = amount / data.TotalMoles;
- label.SetMarkup(Loc.GetString("air-alarm-ui-gases-indicator", ("gas", $"{gas}"),
+ label.SetMarkup(Loc.GetString("air-alarm-ui-gases-indicator",
+ ("gas", $"{gas}"),
("color", AirAlarmWindow.ColorForThreshold(fractionGas, data.GasThresholds[gas])),
("amount", $"{amount:0.####}"),
("percentage", $"{(100 * fractionGas):0.##}")));
diff --git a/Content.Client/Atmos/Monitor/UI/Widgets/ThresholdBoundControl.xaml.cs b/Content.Client/Atmos/Monitor/UI/Widgets/ThresholdBoundControl.xaml.cs
index 3612d84de4c..55f7c008987 100644
--- a/Content.Client/Atmos/Monitor/UI/Widgets/ThresholdBoundControl.xaml.cs
+++ b/Content.Client/Atmos/Monitor/UI/Widgets/ThresholdBoundControl.xaml.cs
@@ -1,7 +1,4 @@
-using Content.Client.Message;
-using Content.Shared.Atmos;
using Content.Shared.Atmos.Monitor;
-using Content.Shared.Temperature;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
diff --git a/Content.Client/Atmos/Monitor/UI/Widgets/ThresholdControl.xaml.cs b/Content.Client/Atmos/Monitor/UI/Widgets/ThresholdControl.xaml.cs
index 78c73fa573a..651620f3e25 100644
--- a/Content.Client/Atmos/Monitor/UI/Widgets/ThresholdControl.xaml.cs
+++ b/Content.Client/Atmos/Monitor/UI/Widgets/ThresholdControl.xaml.cs
@@ -1,12 +1,8 @@
-using System;
using Content.Shared.Atmos;
using Content.Shared.Atmos.Monitor;
-using Content.Shared.Atmos.Monitor.Components;
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
-using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
-using Robust.Shared.Localization;
// holy FUCK
// this technically works because some of this you can *not* do in XAML but holy FUCK
@@ -115,29 +111,38 @@ public ThresholdControl(string name, AtmosAlarmThreshold threshold, AtmosMonitor
_enabled.Pressed = !_threshold.Ignore;
}
- private String LabelForBound(string boundType) //, DebugMessage)> state) =>
{
if (_system.TileData.TryGetValue(uid, out var data))
diff --git a/Content.Client/Atmos/UI/GasPressurePumpBoundUserInterface.cs b/Content.Client/Atmos/UI/GasPressurePumpBoundUserInterface.cs
index 220fdbe875c..0c07eec4025 100644
--- a/Content.Client/Atmos/UI/GasPressurePumpBoundUserInterface.cs
+++ b/Content.Client/Atmos/UI/GasPressurePumpBoundUserInterface.cs
@@ -1,65 +1,63 @@
using Content.Shared.Atmos;
+using Content.Shared.Atmos.Components;
using Content.Shared.Atmos.Piping.Binary.Components;
+using Content.Shared.IdentityManagement;
using Content.Shared.Localizations;
using JetBrains.Annotations;
-using Robust.Client.GameObjects;
using Robust.Client.UserInterface;
-namespace Content.Client.Atmos.UI
+namespace Content.Client.Atmos.UI;
+
+///
+/// Initializes a and updates it when new server messages are received.
+///
+[UsedImplicitly]
+public sealed class GasPressurePumpBoundUserInterface : BoundUserInterface
{
- ///
- /// Initializes a and updates it when new server messages are received.
- ///
- [UsedImplicitly]
- public sealed class GasPressurePumpBoundUserInterface : BoundUserInterface
- {
- [ViewVariables]
- private const float MaxPressure = Atmospherics.MaxOutputPressure;
+ [ViewVariables]
+ private const float MaxPressure = Atmospherics.MaxOutputPressure;
+
+ [ViewVariables]
+ private GasPressurePumpWindow? _window;
- [ViewVariables]
- private GasPressurePumpWindow? _window;
+ public GasPressurePumpBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
+ {
+ }
- public GasPressurePumpBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
- {
- }
+ protected override void Open()
+ {
+ base.Open();
- protected override void Open()
- {
- base.Open();
+ _window = this.CreateWindow();
- _window = this.CreateWindow();
+ _window.ToggleStatusButtonPressed += OnToggleStatusButtonPressed;
+ _window.PumpOutputPressureChanged += OnPumpOutputPressurePressed;
+ Update();
+ }
- _window.ToggleStatusButtonPressed += OnToggleStatusButtonPressed;
- _window.PumpOutputPressureChanged += OnPumpOutputPressurePressed;
- }
+ public void Update()
+ {
+ if (_window == null)
+ return;
- private void OnToggleStatusButtonPressed()
- {
- if (_window is null) return;
- SendMessage(new GasPressurePumpToggleStatusMessage(_window.PumpStatus));
- }
+ _window.Title = Identity.Name(Owner, EntMan);
- private void OnPumpOutputPressurePressed(string value)
- {
- var pressure = UserInputParser.TryFloat(value, out var parsed) ? parsed : 0f;
- if (pressure > MaxPressure) pressure = MaxPressure;
+ if (!EntMan.TryGetComponent(Owner, out GasPressurePumpComponent? pump))
+ return;
- SendMessage(new GasPressurePumpChangeOutputPressureMessage(pressure));
- }
+ _window.SetPumpStatus(pump.Enabled);
+ _window.MaxPressure = pump.MaxTargetPressure;
+ _window.SetOutputPressure(pump.TargetPressure);
+ }
- ///
- /// Update the UI state based on server-sent info
- ///
- ///
- protected override void UpdateState(BoundUserInterfaceState state)
- {
- base.UpdateState(state);
- if (_window == null || state is not GasPressurePumpBoundUserInterfaceState cast)
- return;
+ private void OnToggleStatusButtonPressed()
+ {
+ if (_window is null) return;
+ SendPredictedMessage(new GasPressurePumpToggleStatusMessage(_window.PumpStatus));
+ }
- _window.Title = (cast.PumpLabel);
- _window.SetPumpStatus(cast.Enabled);
- _window.SetOutputPressure(cast.OutputPressure);
- }
+ private void OnPumpOutputPressurePressed(float value)
+ {
+ SendPredictedMessage(new GasPressurePumpChangeOutputPressureMessage(value));
}
}
diff --git a/Content.Client/Atmos/UI/GasPressurePumpWindow.xaml b/Content.Client/Atmos/UI/GasPressurePumpWindow.xaml
index a0896a7b41e..f2c2c7cec50 100644
--- a/Content.Client/Atmos/UI/GasPressurePumpWindow.xaml
+++ b/Content.Client/Atmos/UI/GasPressurePumpWindow.xaml
@@ -1,22 +1,18 @@
-
+ xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls"
+ SetSize="340 110" MinSize="340 110" Title="Pressure Pump">
-
-
+
+
+
+
-
-
-
-
-
-
-
-
+
-
+
diff --git a/Content.Client/Atmos/UI/GasPressurePumpWindow.xaml.cs b/Content.Client/Atmos/UI/GasPressurePumpWindow.xaml.cs
index b5ffcd10721..aa86a1aa03b 100644
--- a/Content.Client/Atmos/UI/GasPressurePumpWindow.xaml.cs
+++ b/Content.Client/Atmos/UI/GasPressurePumpWindow.xaml.cs
@@ -1,14 +1,8 @@
-using System;
-using System.Collections.Generic;
-using System.Globalization;
-using Content.Client.Atmos.EntitySystems;
+using Content.Client.UserInterface.Controls;
using Content.Shared.Atmos;
-using Content.Shared.Atmos.Prototypes;
using Robust.Client.AutoGenerated;
-using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
-using Robust.Shared.Localization;
namespace Content.Client.Atmos.UI
{
@@ -16,12 +10,25 @@ namespace Content.Client.Atmos.UI
/// Client-side UI used to control a gas pressure pump.
///
[GenerateTypedNameReferences]
- public sealed partial class GasPressurePumpWindow : DefaultWindow
+ public sealed partial class GasPressurePumpWindow : FancyWindow
{
public bool PumpStatus = true;
public event Action? ToggleStatusButtonPressed;
- public event Action? PumpOutputPressureChanged;
+ public event Action? PumpOutputPressureChanged;
+
+ public float MaxPressure
+ {
+ get => _maxPressure;
+ set
+ {
+ _maxPressure = value;
+
+ PumpPressureOutputInput.Value = MathF.Min(value, PumpPressureOutputInput.Value);
+ }
+ }
+
+ private float _maxPressure = Atmospherics.MaxOutputPressure;
public GasPressurePumpWindow()
{
@@ -30,23 +37,25 @@ public GasPressurePumpWindow()
ToggleStatusButton.OnPressed += _ => SetPumpStatus(!PumpStatus);
ToggleStatusButton.OnPressed += _ => ToggleStatusButtonPressed?.Invoke();
- PumpPressureOutputInput.OnTextChanged += _ => SetOutputPressureButton.Disabled = false;
+ PumpPressureOutputInput.OnValueChanged += _ => SetOutputPressureButton.Disabled = false;
+
SetOutputPressureButton.OnPressed += _ =>
{
- PumpOutputPressureChanged?.Invoke(PumpPressureOutputInput.Text ??= "");
+ PumpPressureOutputInput.Value = Math.Clamp(PumpPressureOutputInput.Value, 0f, _maxPressure);
+ PumpOutputPressureChanged?.Invoke(PumpPressureOutputInput.Value);
SetOutputPressureButton.Disabled = true;
};
SetMaxPressureButton.OnPressed += _ =>
{
- PumpPressureOutputInput.Text = Atmospherics.MaxOutputPressure.ToString(CultureInfo.CurrentCulture);
+ PumpPressureOutputInput.Value = _maxPressure;
SetOutputPressureButton.Disabled = false;
};
}
public void SetOutputPressure(float pressure)
{
- PumpPressureOutputInput.Text = pressure.ToString(CultureInfo.CurrentCulture);
+ PumpPressureOutputInput.Value = pressure;
}
public void SetPumpStatus(bool enabled)
diff --git a/Content.Client/Cargo/BUI/CargoBountyConsoleBoundUserInterface.cs b/Content.Client/Cargo/BUI/CargoBountyConsoleBoundUserInterface.cs
index 44c40143d83..04075000f5b 100644
--- a/Content.Client/Cargo/BUI/CargoBountyConsoleBoundUserInterface.cs
+++ b/Content.Client/Cargo/BUI/CargoBountyConsoleBoundUserInterface.cs
@@ -39,6 +39,6 @@ protected override void UpdateState(BoundUserInterfaceState message)
if (message is not CargoBountyConsoleState state)
return;
- _menu?.UpdateEntries(state.Bounties, state.UntilNextSkip);
+ _menu?.UpdateEntries(state.Bounties, state.History, state.UntilNextSkip);
}
}
diff --git a/Content.Client/Cargo/UI/BountyHistoryEntry.xaml b/Content.Client/Cargo/UI/BountyHistoryEntry.xaml
new file mode 100644
index 00000000000..eee8c5cc165
--- /dev/null
+++ b/Content.Client/Cargo/UI/BountyHistoryEntry.xaml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/Cargo/UI/BountyHistoryEntry.xaml.cs b/Content.Client/Cargo/UI/BountyHistoryEntry.xaml.cs
new file mode 100644
index 00000000000..f3c9bbfafb1
--- /dev/null
+++ b/Content.Client/Cargo/UI/BountyHistoryEntry.xaml.cs
@@ -0,0 +1,54 @@
+using Content.Client.Message;
+using Content.Shared.Cargo;
+using Content.Shared.Cargo.Prototypes;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Prototypes;
+using Robust.Shared.Timing;
+
+namespace Content.Client.Cargo.UI;
+
+[GenerateTypedNameReferences]
+public sealed partial class BountyHistoryEntry : BoxContainer
+{
+ [Dependency] private readonly IPrototypeManager _prototype = default!;
+
+ public BountyHistoryEntry(CargoBountyHistoryData bounty)
+ {
+ RobustXamlLoader.Load(this);
+ IoCManager.InjectDependencies(this);
+
+ if (!_prototype.TryIndex(bounty.Bounty, out var bountyPrototype))
+ return;
+
+ var items = new List();
+ foreach (var entry in bountyPrototype.Entries)
+ {
+ items.Add(Loc.GetString("bounty-console-manifest-entry",
+ ("amount", entry.Amount),
+ ("item", Loc.GetString(entry.Name))));
+ }
+ ManifestLabel.SetMarkup(Loc.GetString("bounty-console-manifest-label", ("item", string.Join(", ", items))));
+ RewardLabel.SetMarkup(Loc.GetString("bounty-console-reward-label", ("reward", bountyPrototype.Reward)));
+ IdLabel.SetMarkup(Loc.GetString("bounty-console-id-label", ("id", bounty.Id)));
+
+ var stationTime = bounty.Timestamp.ToString("hh\\:mm\\:ss");
+ if (bounty.ActorName == null)
+ {
+ StatusLabel.SetMarkup(Loc.GetString("bounty-console-history-completed-label"));
+ NoticeLabel.SetMarkup(Loc.GetString("bounty-console-history-notice-completed-label", ("time", stationTime)));
+ }
+ else
+ {
+ StatusLabel.SetMarkup(Loc.GetString("bounty-console-history-skipped-label"));
+ NoticeLabel.SetMarkup(Loc.GetString("bounty-console-history-notice-skipped-label",
+ ("id", bounty.ActorName),
+ ("time", stationTime)));
+ }
+ }
+ protected override void FrameUpdate(FrameEventArgs args)
+ {
+ base.FrameUpdate(args);
+ }
+}
diff --git a/Content.Client/Cargo/UI/CargoBountyMenu.xaml b/Content.Client/Cargo/UI/CargoBountyMenu.xaml
index bb263ff6c4a..0f093d5f8e7 100644
--- a/Content.Client/Cargo/UI/CargoBountyMenu.xaml
+++ b/Content.Client/Cargo/UI/CargoBountyMenu.xaml
@@ -11,15 +11,26 @@
-
-
-
-
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/Cargo/UI/CargoBountyMenu.xaml.cs b/Content.Client/Cargo/UI/CargoBountyMenu.xaml.cs
index 3767b45e4be..0717aacc5e6 100644
--- a/Content.Client/Cargo/UI/CargoBountyMenu.xaml.cs
+++ b/Content.Client/Cargo/UI/CargoBountyMenu.xaml.cs
@@ -17,8 +17,11 @@ public CargoBountyMenu()
RobustXamlLoader.Load(this);
}
- public void UpdateEntries(List bounties, TimeSpan untilNextSkip)
+ public void UpdateEntries(List bounties, List history, TimeSpan untilNextSkip)
{
+ MasterTabContainer.SetTabTitle(0, Loc.GetString("bounty-console-tab-available-label"));
+ MasterTabContainer.SetTabTitle(1, Loc.GetString("bounty-console-tab-history-label"));
+
BountyEntriesContainer.Children.Clear();
foreach (var b in bounties)
{
@@ -32,5 +35,12 @@ public void UpdateEntries(List bounties, TimeSpan untilNextSkip
{
MinHeight = 10
});
+
+ BountyHistoryContainer.Children.Clear();
+ foreach (var h in history)
+ {
+ var entry = new BountyHistoryEntry(h);
+ BountyHistoryContainer.AddChild(entry);
+ }
}
}
diff --git a/Content.Client/CartridgeLoader/Cartridges/LogProbeUi.cs b/Content.Client/CartridgeLoader/Cartridges/LogProbeUi.cs
index d28d3228c94..aaf3900beee 100644
--- a/Content.Client/CartridgeLoader/Cartridges/LogProbeUi.cs
+++ b/Content.Client/CartridgeLoader/Cartridges/LogProbeUi.cs
@@ -23,6 +23,6 @@ public override void UpdateState(BoundUserInterfaceState state)
if (state is not LogProbeUiState logProbeUiState)
return;
- _fragment?.UpdateState(logProbeUiState.PulledLogs);
+ _fragment?.UpdateState(logProbeUiState); // DeltaV - just take the state
}
}
diff --git a/Content.Client/CartridgeLoader/Cartridges/LogProbeUiFragment.xaml b/Content.Client/CartridgeLoader/Cartridges/LogProbeUiFragment.xaml
index d12fb55cdce..a0769590e91 100644
--- a/Content.Client/CartridgeLoader/Cartridges/LogProbeUiFragment.xaml
+++ b/Content.Client/CartridgeLoader/Cartridges/LogProbeUiFragment.xaml
@@ -9,10 +9,30 @@
BorderColor="#5a5a5a"
BorderThickness="0 0 0 1"/>
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/CartridgeLoader/Cartridges/LogProbeUiFragment.xaml.cs b/Content.Client/CartridgeLoader/Cartridges/LogProbeUiFragment.xaml.cs
index b22e0bc1964..5fa93bb40db 100644
--- a/Content.Client/CartridgeLoader/Cartridges/LogProbeUiFragment.xaml.cs
+++ b/Content.Client/CartridgeLoader/Cartridges/LogProbeUiFragment.xaml.cs
@@ -1,4 +1,7 @@
-using Content.Shared.CartridgeLoader.Cartridges;
+using System.Linq; // DeltaV
+using Content.Client.DeltaV.CartridgeLoader.Cartridges; // DeltaV
+using Content.Shared.CartridgeLoader.Cartridges;
+using Content.Shared.DeltaV.CartridgeLoader.Cartridges; // DeltaV
using Robust.Client.AutoGenerated;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
@@ -13,10 +16,112 @@ public LogProbeUiFragment()
RobustXamlLoader.Load(this);
}
- public void UpdateState(List logs)
+ // DeltaV begin - Update to handle both types of data
+ public void UpdateState(LogProbeUiState state)
{
ProbedDeviceContainer.RemoveAllChildren();
+ if (state.NanoChatData != null)
+ {
+ SetupNanoChatView(state.NanoChatData.Value);
+ DisplayNanoChatData(state.NanoChatData.Value);
+ }
+ else
+ {
+ SetupAccessLogView();
+ if (state.PulledLogs.Count > 0)
+ DisplayAccessLogs(state.PulledLogs);
+ }
+ }
+
+ private void SetupNanoChatView(NanoChatData data)
+ {
+ TitleLabel.Text = Loc.GetString("log-probe-header-nanochat");
+ ContentLabel.Text = Loc.GetString("log-probe-label-message");
+
+ // Show card info if available
+ var cardInfo = new List();
+ if (data.CardNumber != null)
+ cardInfo.Add(Loc.GetString("log-probe-card-number", ("number", $"#{data.CardNumber:D4}")));
+
+ // Add recipient count
+ cardInfo.Add(Loc.GetString("log-probe-recipients", ("count", data.Recipients.Count)));
+
+ CardNumberLabel.Text = string.Join(" | ", cardInfo);
+ CardNumberLabel.Visible = true;
+ }
+
+ private void SetupAccessLogView()
+ {
+ TitleLabel.Text = Loc.GetString("log-probe-header-access");
+ ContentLabel.Text = Loc.GetString("log-probe-label-accessor");
+ CardNumberLabel.Visible = false;
+ }
+
+ private void DisplayNanoChatData(NanoChatData data)
+ {
+ // First add a recipient list entry
+ var recipientsList = Loc.GetString("log-probe-recipient-list") + "\n" + string.Join("\n",
+ data.Recipients.Values
+ .OrderBy(r => r.Name)
+ .Select(r => $" {r.Name}" +
+ (string.IsNullOrEmpty(r.JobTitle) ? "" : $" ({r.JobTitle})") +
+ $" | #{r.Number:D4}"));
+
+ var recipientsEntry = new LogProbeUiEntry(0, "---", recipientsList);
+ ProbedDeviceContainer.AddChild(recipientsEntry);
+
+ var count = 1;
+ foreach (var (partnerId, messages) in data.Messages)
+ {
+ // Show only successfully delivered incoming messages
+ var incomingMessages = messages
+ .Where(msg => msg.SenderId == partnerId && !msg.DeliveryFailed)
+ .OrderByDescending(msg => msg.Timestamp);
+
+ foreach (var msg in incomingMessages)
+ {
+ var messageText = Loc.GetString("log-probe-message-format",
+ ("sender", $"#{msg.SenderId:D4}"),
+ ("recipient", $"#{data.CardNumber:D4}"),
+ ("content", msg.Content));
+
+ var entry = new NanoChatLogEntry(
+ count,
+ TimeSpan.FromSeconds(Math.Truncate(msg.Timestamp.TotalSeconds)).ToString(),
+ messageText);
+
+ ProbedDeviceContainer.AddChild(entry);
+ count++;
+ }
+
+ // Show only successfully delivered outgoing messages
+ var outgoingMessages = messages
+ .Where(msg => msg.SenderId == data.CardNumber && !msg.DeliveryFailed)
+ .OrderByDescending(msg => msg.Timestamp);
+
+ foreach (var msg in outgoingMessages)
+ {
+ var messageText = Loc.GetString("log-probe-message-format",
+ ("sender", $"#{msg.SenderId:D4}"),
+ ("recipient", $"#{partnerId:D4}"),
+ ("content", msg.Content));
+
+ var entry = new NanoChatLogEntry(
+ count,
+ TimeSpan.FromSeconds(Math.Truncate(msg.Timestamp.TotalSeconds)).ToString(),
+ messageText);
+
+ ProbedDeviceContainer.AddChild(entry);
+ count++;
+ }
+ }
+ }
+ // DeltaV end
+
+ // DeltaV - Handle this in a separate method
+ private void DisplayAccessLogs(List logs)
+ {
//Reverse the list so the oldest entries appear at the bottom
logs.Reverse();
diff --git a/Content.Client/Clothing/FlippableClothingVisualizerSystem.cs b/Content.Client/Clothing/FlippableClothingVisualizerSystem.cs
index 2c3afb0324f..1f09ae9eebb 100644
--- a/Content.Client/Clothing/FlippableClothingVisualizerSystem.cs
+++ b/Content.Client/Clothing/FlippableClothingVisualizerSystem.cs
@@ -7,7 +7,7 @@
namespace Content.Client.Clothing;
-public sealed class FlippableClothingVisualizerSystem : VisualizerSystem
+public sealed class FlippableClothingVisualizerSystem : VisualizerSystem
{
[Dependency] private readonly SharedItemSystem _itemSys = default!;
diff --git a/Content.Client/Construction/UI/ConstructionMenu.xaml b/Content.Client/Construction/UI/ConstructionMenu.xaml
index 6e4438cf6fd..a934967a533 100644
--- a/Content.Client/Construction/UI/ConstructionMenu.xaml
+++ b/Content.Client/Construction/UI/ConstructionMenu.xaml
@@ -1,15 +1,20 @@
-
+
+
+
+
-
-
+
+
+
+
+
diff --git a/Content.Client/Construction/UI/ConstructionMenu.xaml.cs b/Content.Client/Construction/UI/ConstructionMenu.xaml.cs
index f0cb8148762..9ab8a156005 100644
--- a/Content.Client/Construction/UI/ConstructionMenu.xaml.cs
+++ b/Content.Client/Construction/UI/ConstructionMenu.xaml.cs
@@ -25,11 +25,16 @@ public interface IConstructionMenuView : IDisposable
OptionButton OptionCategories { get; }
bool EraseButtonPressed { get; set; }
+ bool GridViewButtonPressed { get; set; }
bool BuildButtonPressed { get; set; }
ItemList Recipes { get; }
ItemList RecipeStepList { get; }
+
+ ScrollContainer RecipesGridScrollContainer { get; }
+ GridContainer RecipesGrid { get; }
+
event EventHandler<(string search, string catagory)> PopulateRecipes;
event EventHandler RecipeSelected;
event EventHandler RecipeFavorited;
@@ -72,9 +77,16 @@ public bool EraseButtonPressed
set => EraseButton.Pressed = value;
}
+ public bool GridViewButtonPressed
+ {
+ get => MenuGridViewButton.Pressed;
+ set => MenuGridViewButton.Pressed = value;
+ }
+
public ConstructionMenu()
{
- SetSize = MinSize = new Vector2(720, 320);
+ SetSize = new Vector2(560, 450);
+ MinSize = new Vector2(560, 320);
IoCManager.InjectDependencies(this);
RobustXamlLoader.Load(this);
@@ -102,6 +114,9 @@ public ConstructionMenu()
EraseButton.OnToggled += args => EraseButtonToggled?.Invoke(this, args.Pressed);
FavoriteButton.OnPressed += args => RecipeFavorited?.Invoke(this, EventArgs.Empty);
+
+ MenuGridViewButton.OnPressed += _ =>
+ PopulateRecipes?.Invoke(this, (SearchBar.Text, Categories[OptionCategories.SelectedId]));
}
public event EventHandler? ClearAllGhosts;
diff --git a/Content.Client/Construction/UI/ConstructionMenuPresenter.cs b/Content.Client/Construction/UI/ConstructionMenuPresenter.cs
index c315cdedb2c..d35e8fbe769 100644
--- a/Content.Client/Construction/UI/ConstructionMenuPresenter.cs
+++ b/Content.Client/Construction/UI/ConstructionMenuPresenter.cs
@@ -1,7 +1,8 @@
using System.Linq;
+using System.Numerics;
+using Content.Client.Stylesheets;
using Content.Client.UserInterface.Systems.MenuBar.Widgets;
using Content.Shared.Construction.Prototypes;
-using Content.Shared.Tag;
using Content.Shared.Whitelist;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
@@ -11,7 +12,6 @@
using Robust.Client.UserInterface.Controls;
using Robust.Client.Utility;
using Robust.Shared.Enums;
-using Robust.Shared.Graphics;
using Robust.Shared.Prototypes;
using static Robust.Client.UserInterface.Controls.BaseButton;
@@ -33,10 +33,12 @@ internal sealed class ConstructionMenuPresenter : IDisposable
private readonly IConstructionMenuView _constructionView;
private readonly EntityWhitelistSystem _whitelistSystem;
+ private readonly SpriteSystem _spriteSystem;
private ConstructionSystem? _constructionSystem;
private ConstructionPrototype? _selected;
private List _favoritedRecipes = [];
+ private Dictionary _recipeButtons = new();
private string _selectedCategory = string.Empty;
private string _favoriteCatName = "construction-category-favorites";
private string _forAllCategoryName = "construction-category-all";
@@ -85,6 +87,7 @@ public ConstructionMenuPresenter()
IoCManager.InjectDependencies(this);
_constructionView = new ConstructionMenu();
_whitelistSystem = _entManager.System();
+ _spriteSystem = _entManager.System();
// This is required so that if we load after the system is initialized, we can bind to it immediately
if (_systemManager.TryGetEntitySystem(out var constructionSystem))
@@ -150,12 +153,24 @@ private void OnViewRecipeSelected(object? sender, ItemList.Item? item)
PopulateInfo(_selected);
}
+ private void OnGridViewRecipeSelected(object? sender, ConstructionPrototype? recipe)
+ {
+ if (recipe is null)
+ {
+ _selected = null;
+ _constructionView.ClearRecipeInfo();
+ return;
+ }
+
+ _selected = recipe;
+ if (_placementManager.IsActive && !_placementManager.Eraser) UpdateGhostPlacement();
+ PopulateInfo(_selected);
+ }
+
private void OnViewPopulateRecipes(object? sender, (string search, string catagory) args)
{
var (search, category) = args;
- var recipesList = _constructionView.Recipes;
- recipesList.Clear();
var recipes = new List();
var isEmptyCategory = string.IsNullOrEmpty(category) || category == _forAllCategoryName;
@@ -201,12 +216,73 @@ private void OnViewPopulateRecipes(object? sender, (string search, string catago
recipes.Sort((a, b) => string.Compare(a.Name, b.Name, StringComparison.InvariantCulture));
- foreach (var recipe in recipes)
+ var recipesList = _constructionView.Recipes;
+ recipesList.Clear();
+
+ var recipesGrid = _constructionView.RecipesGrid;
+ recipesGrid.RemoveAllChildren();
+
+ _constructionView.RecipesGridScrollContainer.Visible = _constructionView.GridViewButtonPressed;
+ _constructionView.Recipes.Visible = !_constructionView.GridViewButtonPressed;
+
+ if (_constructionView.GridViewButtonPressed)
+ {
+ foreach (var recipe in recipes)
+ {
+ var itemButton = new TextureButton
+ {
+ TextureNormal = _spriteSystem.Frame0(recipe.Icon),
+ VerticalAlignment = Control.VAlignment.Center,
+ Name = recipe.Name,
+ ToolTip = recipe.Name,
+ Scale = new Vector2(1.35f),
+ ToggleMode = true,
+ };
+ var itemButtonPanelContainer = new PanelContainer
+ {
+ PanelOverride = new StyleBoxFlat { BackgroundColor = StyleNano.ButtonColorDefault },
+ Children = { itemButton },
+ };
+
+ itemButton.OnToggled += buttonToggledEventArgs =>
+ {
+ SelectGridButton(itemButton, buttonToggledEventArgs.Pressed);
+
+ if (buttonToggledEventArgs.Pressed &&
+ _selected != null &&
+ _recipeButtons.TryGetValue(_selected.Name, out var oldButton))
+ {
+ oldButton.Pressed = false;
+ SelectGridButton(oldButton, false);
+ }
+
+ OnGridViewRecipeSelected(this, buttonToggledEventArgs.Pressed ? recipe : null);
+ };
+
+ recipesGrid.AddChild(itemButtonPanelContainer);
+ _recipeButtons[recipe.Name] = itemButton;
+ var isCurrentButtonSelected = _selected == recipe;
+ itemButton.Pressed = isCurrentButtonSelected;
+ SelectGridButton(itemButton, isCurrentButtonSelected);
+ }
+ }
+ else
{
- recipesList.Add(GetItem(recipe, recipesList));
+ foreach (var recipe in recipes)
+ {
+ recipesList.Add(GetItem(recipe, recipesList));
+ }
}
+ }
+
+ private void SelectGridButton(TextureButton button, bool select)
+ {
+ if (button.Parent is not PanelContainer buttonPanel)
+ return;
- // There is apparently no way to set which
+ button.Modulate = select ? Color.Green : Color.White;
+ var buttonColor = select ? StyleNano.ButtonColorDefault : Color.Transparent;
+ buttonPanel.PanelOverride = new StyleBoxFlat { BackgroundColor = buttonColor };
}
private void PopulateCategories(string? selectCategory = null)
@@ -257,11 +333,10 @@ private void PopulateCategories(string? selectCategory = null)
private void PopulateInfo(ConstructionPrototype prototype)
{
- var spriteSys = _systemManager.GetEntitySystem();
_constructionView.ClearRecipeInfo();
_constructionView.SetRecipeInfo(
- prototype.Name, prototype.Description, spriteSys.Frame0(prototype.Icon),
+ prototype.Name, prototype.Description, _spriteSystem.Frame0(prototype.Icon),
prototype.Type != ConstructionType.Item,
!_favoritedRecipes.Contains(prototype));
@@ -274,7 +349,6 @@ private void GenerateStepList(ConstructionPrototype prototype, ItemList stepList
if (_constructionSystem?.GetGuide(prototype) is not { } guide)
return;
- var spriteSys = _systemManager.GetEntitySystem();
foreach (var entry in guide.Entries)
{
@@ -290,20 +364,20 @@ private void GenerateStepList(ConstructionPrototype prototype, ItemList stepList
// The padding needs to be applied regardless of text length... (See PadLeft documentation)
text = text.PadLeft(text.Length + entry.Padding);
- var icon = entry.Icon != null ? spriteSys.Frame0(entry.Icon) : Texture.Transparent;
+ var icon = entry.Icon != null ? _spriteSystem.Frame0(entry.Icon) : Texture.Transparent;
stepList.AddItem(text, icon, false);
}
}
- private static ItemList.Item GetItem(ConstructionPrototype recipe, ItemList itemList)
+ private ItemList.Item GetItem(ConstructionPrototype recipe, ItemList itemList)
{
return new(itemList)
{
Metadata = recipe,
Text = recipe.Name,
- Icon = recipe.Icon.Frame0(),
+ Icon = _spriteSystem.Frame0(recipe.Icon),
TooltipEnabled = true,
- TooltipText = recipe.Description
+ TooltipText = recipe.Description,
};
}
diff --git a/Content.Client/Crayon/UI/CrayonBoundUserInterface.cs b/Content.Client/Crayon/UI/CrayonBoundUserInterface.cs
index e5be0b1811f..44501767dd4 100644
--- a/Content.Client/Crayon/UI/CrayonBoundUserInterface.cs
+++ b/Content.Client/Crayon/UI/CrayonBoundUserInterface.cs
@@ -31,7 +31,7 @@ protected override void Open()
private void PopulateCrayons()
{
var crayonDecals = _protoManager.EnumeratePrototypes().Where(x => x.Tags.Contains("crayon"));
- _menu?.Populate(crayonDecals);
+ _menu?.Populate(crayonDecals.ToList());
}
public override void OnProtoReload(PrototypesReloadedEventArgs args)
@@ -44,6 +44,16 @@ public override void OnProtoReload(PrototypesReloadedEventArgs args)
PopulateCrayons();
}
+ protected override void ReceiveMessage(BoundUserInterfaceMessage message)
+ {
+ base.ReceiveMessage(message);
+
+ if (_menu is null || message is not CrayonUsedMessage crayonMessage)
+ return;
+
+ _menu.AdvanceState(crayonMessage.DrawnDecal);
+ }
+
protected override void UpdateState(BoundUserInterfaceState state)
{
base.UpdateState(state);
diff --git a/Content.Client/Crayon/UI/CrayonWindow.xaml b/Content.Client/Crayon/UI/CrayonWindow.xaml
index 7729318ae7f..7acb22551b7 100644
--- a/Content.Client/Crayon/UI/CrayonWindow.xaml
+++ b/Content.Client/Crayon/UI/CrayonWindow.xaml
@@ -1,14 +1,13 @@
+ MinSize="450 500"
+ SetSize="450 500">
-
+
-
-
-
+
+
diff --git a/Content.Client/Crayon/UI/CrayonWindow.xaml.cs b/Content.Client/Crayon/UI/CrayonWindow.xaml.cs
index 6ef282d219a..88475562c67 100644
--- a/Content.Client/Crayon/UI/CrayonWindow.xaml.cs
+++ b/Content.Client/Crayon/UI/CrayonWindow.xaml.cs
@@ -1,8 +1,10 @@
using System.Collections.Generic;
+using System.Linq;
using Content.Client.Stylesheets;
using Content.Shared.Crayon;
using Content.Shared.Decals;
using Robust.Client.AutoGenerated;
+using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
@@ -18,7 +20,12 @@ namespace Content.Client.Crayon.UI
[GenerateTypedNameReferences]
public sealed partial class CrayonWindow : DefaultWindow
{
- private Dictionary? _decals;
+ [Dependency] private readonly IEntitySystemManager _entitySystem = default!;
+ private readonly SpriteSystem _spriteSystem = default!;
+
+ private Dictionary>? _decals;
+ private List? _allDecals;
+ private string? _autoSelected;
private string? _selected;
private Color _color;
@@ -28,8 +35,10 @@ public sealed partial class CrayonWindow : DefaultWindow
public CrayonWindow()
{
RobustXamlLoader.Load(this);
+ IoCManager.InjectDependencies(this);
+ _spriteSystem = _entitySystem.GetEntitySystem();
- Search.OnTextChanged += _ => RefreshList();
+ Search.OnTextChanged += SearchChanged;
ColorSelector.OnColorChanged += SelectColor;
}
@@ -44,51 +53,94 @@ private void SelectColor(Color color)
private void RefreshList()
{
// Clear
- Grid.DisposeAllChildren();
- if (_decals == null)
+ Grids.DisposeAllChildren();
+
+ if (_decals == null || _allDecals == null)
return;
var filter = Search.Text;
- foreach (var (decal, tex) in _decals)
+ var comma = filter.IndexOf(',');
+ var first = (comma == -1 ? filter : filter[..comma]).Trim();
+
+ var names = _decals.Keys.ToList();
+ names.Sort((a, b) => a == "random" ? 1 : b == "random" ? -1 : a.CompareTo(b));
+
+ if (_autoSelected != null && first != _autoSelected && _allDecals.Contains(first))
+ {
+ _selected = first;
+ _autoSelected = _selected;
+ OnSelected?.Invoke(_selected);
+ }
+
+ foreach (var categoryName in names)
{
- if (!decal.Contains(filter))
+ var locName = Loc.GetString("crayon-category-" + categoryName);
+ var category = _decals[categoryName].Where(d => locName.Contains(first) || d.Name.Contains(first)).ToList();
+
+ if (category.Count == 0)
continue;
- var button = new TextureButton()
+ var label = new Label
{
- TextureNormal = tex,
- Name = decal,
- ToolTip = decal,
- Modulate = _color,
+ Text = locName
};
- button.OnPressed += ButtonOnPressed;
- if (_selected == decal)
+
+ var grid = new GridContainer
{
- var panelContainer = new PanelContainer()
+ Columns = 6,
+ Margin = new Thickness(0, 0, 0, 16)
+ };
+
+ Grids.AddChild(label);
+ Grids.AddChild(grid);
+
+ foreach (var (name, texture) in category)
+ {
+ var button = new TextureButton()
{
- PanelOverride = new StyleBoxFlat()
- {
- BackgroundColor = StyleNano.ButtonColorDefault,
- },
- Children =
- {
- button,
- },
+ TextureNormal = texture,
+ Name = name,
+ ToolTip = name,
+ Modulate = _color,
+ Scale = new System.Numerics.Vector2(2, 2)
};
- Grid.AddChild(panelContainer);
- }
- else
- {
- Grid.AddChild(button);
+ button.OnPressed += ButtonOnPressed;
+
+ if (_selected == name)
+ {
+ var panelContainer = new PanelContainer()
+ {
+ PanelOverride = new StyleBoxFlat()
+ {
+ BackgroundColor = StyleNano.ButtonColorDefault,
+ },
+ Children =
+ {
+ button,
+ },
+ };
+ grid.AddChild(panelContainer);
+ }
+ else
+ {
+ grid.AddChild(button);
+ }
}
}
}
+ private void SearchChanged(LineEdit.LineEditEventArgs obj)
+ {
+ _autoSelected = ""; // Placeholder to kick off the auto-select in refreshlist()
+ RefreshList();
+ }
+
private void ButtonOnPressed(ButtonEventArgs obj)
{
if (obj.Button.Name == null) return;
_selected = obj.Button.Name;
+ _autoSelected = null;
OnSelected?.Invoke(_selected);
RefreshList();
}
@@ -107,12 +159,38 @@ public void UpdateState(CrayonBoundUserInterfaceState state)
RefreshList();
}
- public void Populate(IEnumerable prototypes)
+ public void AdvanceState(string drawnDecal)
{
- _decals = new Dictionary();
+ var filter = Search.Text;
+ if (!filter.Contains(',') || !filter.Contains(drawnDecal))
+ return;
+
+ var first = filter[..filter.IndexOf(',')].Trim();
+
+ if (first.Equals(drawnDecal, StringComparison.InvariantCultureIgnoreCase))
+ {
+ Search.Text = filter[(filter.IndexOf(',') + 1)..].Trim();
+ _autoSelected = first;
+ }
+
+ RefreshList();
+ }
+
+ public void Populate(List prototypes)
+ {
+ _decals = [];
+ _allDecals = [];
+
+ prototypes.Sort((a, b) => a.ID.CompareTo(b.ID));
+
foreach (var decalPrototype in prototypes)
{
- _decals.Add(decalPrototype.ID, decalPrototype.Sprite.Frame0());
+ var category = "random";
+ if (decalPrototype.Tags.Count > 1 && decalPrototype.Tags[1].StartsWith("crayon-"))
+ category = decalPrototype.Tags[1].Replace("crayon-", "");
+ var list = _decals.GetOrNew(category);
+ list.Add((decalPrototype.ID, _spriteSystem.Frame0(decalPrototype.Sprite)));
+ _allDecals.Add(decalPrototype.ID);
}
RefreshList();
diff --git a/Content.Client/DeltaV/AACTablet/UI/AACBoundUserInterface.cs b/Content.Client/DeltaV/AACTablet/UI/AACBoundUserInterface.cs
index 6a9330598fa..1e47b78da89 100644
--- a/Content.Client/DeltaV/AACTablet/UI/AACBoundUserInterface.cs
+++ b/Content.Client/DeltaV/AACTablet/UI/AACBoundUserInterface.cs
@@ -1,12 +1,11 @@
using Content.Shared.DeltaV.AACTablet;
+using Content.Shared.DeltaV.QuickPhrase;
using Robust.Shared.Prototypes;
namespace Content.Client.DeltaV.AACTablet.UI;
public sealed class AACBoundUserInterface : BoundUserInterface
{
- [Dependency] private readonly IPrototypeManager _prototypeManager = default!;
-
[ViewVariables]
private AACWindow? _window;
@@ -18,14 +17,14 @@ protected override void Open()
{
base.Open();
_window?.Close();
- _window = new AACWindow(this, _prototypeManager);
+ _window = new AACWindow();
_window.OpenCentered();
_window.PhraseButtonPressed += OnPhraseButtonPressed;
_window.OnClose += Close;
}
- private void OnPhraseButtonPressed(string phraseId)
+ private void OnPhraseButtonPressed(ProtoId phraseId)
{
SendMessage(new AACTabletSendPhraseMessage(phraseId));
}
@@ -33,6 +32,6 @@ private void OnPhraseButtonPressed(string phraseId)
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
- _window?.Dispose();
+ _window?.Orphan();
}
}
diff --git a/Content.Client/DeltaV/AACTablet/UI/AACWindow.xaml b/Content.Client/DeltaV/AACTablet/UI/AACWindow.xaml
index e4183934209..6bb23b5c96a 100644
--- a/Content.Client/DeltaV/AACTablet/UI/AACWindow.xaml
+++ b/Content.Client/DeltaV/AACTablet/UI/AACWindow.xaml
@@ -6,4 +6,4 @@
MinSize="540 300">
-
\ No newline at end of file
+
diff --git a/Content.Client/DeltaV/AACTablet/UI/AACWindow.xaml.cs b/Content.Client/DeltaV/AACTablet/UI/AACWindow.xaml.cs
index c9bc457b8df..07866367786 100644
--- a/Content.Client/DeltaV/AACTablet/UI/AACWindow.xaml.cs
+++ b/Content.Client/DeltaV/AACTablet/UI/AACWindow.xaml.cs
@@ -13,20 +13,26 @@ namespace Content.Client.DeltaV.AACTablet.UI;
[GenerateTypedNameReferences]
public sealed partial class AACWindow : FancyWindow
{
- private IPrototypeManager _prototypeManager;
- public event Action? PhraseButtonPressed;
+ [Dependency] private readonly IPrototypeManager _prototype = default!;
+ public event Action>? PhraseButtonPressed;
- public AACWindow(AACBoundUserInterface ui, IPrototypeManager prototypeManager)
+ private const float SpaceWidth = 10f;
+ private const float ParentWidth = 540f;
+ private const int ColumnCount = 4;
+
+ private const int ButtonWidth =
+ (int)((ParentWidth - SpaceWidth * 2) / ColumnCount - SpaceWidth * ((ColumnCount - 1f) / ColumnCount));
+
+ public AACWindow()
{
RobustXamlLoader.Load(this);
- _prototypeManager = prototypeManager;
- PopulateGui(ui);
+ IoCManager.InjectDependencies(this);
+ PopulateGui();
}
- private void PopulateGui(AACBoundUserInterface ui)
+ private void PopulateGui()
{
- var loc = IoCManager.Resolve();
- var phrases = _prototypeManager.EnumeratePrototypes().ToList();
+ var phrases = _prototype.EnumeratePrototypes().ToList();
// take ALL phrases and turn them into tabs and groups, so the buttons are sorted and tabbed
var sortedTabs = phrases
@@ -38,7 +44,7 @@ private void PopulateGui(AACBoundUserInterface ui)
.OrderBy(gg => gg.Key)
.ToDictionary(
gg => gg.Key,
- gg => gg.OrderBy(p => loc.GetString(p.Text)).ToList()
+ gg => gg.OrderBy(p => Loc.GetString(p.Text)).ToList()
)
);
@@ -49,11 +55,10 @@ private void PopulateGui(AACBoundUserInterface ui)
private TabContainer CreateTabContainer(Dictionary>> sortedTabs)
{
var tabContainer = new TabContainer();
- var loc = IoCManager.Resolve();
foreach (var tab in sortedTabs)
{
- var tabName = loc.GetString(tab.Key);
+ var tabName = Loc.GetString(tab.Key);
var boxContainer = CreateBoxContainerForTab(tab.Value);
tabContainer.AddChild(boxContainer);
tabContainer.SetTabTitle(tabContainer.ChildCount - 1, tabName);
@@ -64,7 +69,7 @@ private TabContainer CreateTabContainer(Dictionary> groups)
{
- var boxContainer = new BoxContainer()
+ var boxContainer = new BoxContainer
{
HorizontalExpand = true,
Orientation = BoxContainer.LayoutOrientation.Vertical
@@ -81,7 +86,7 @@ private BoxContainer CreateBoxContainerForTab(Dictionary phrases)
{
- var loc = IoCManager.Resolve();
var buttonContainer = CreateButtonContainer();
foreach (var phrase in phrases)
{
- var text = loc.GetString(phrase.Text);
+ var text = Loc.GetString(phrase.Text);
var button = CreatePhraseButton(text, phrase.StyleClass);
- button.OnPressed += _ => OnPhraseButtonPressed(phrase.ID);
+ button.OnPressed += _ => OnPhraseButtonPressed(new ProtoId(phrase.ID));
buttonContainer.AddChild(button);
}
return buttonContainer;
@@ -121,11 +125,10 @@ private static GridContainer CreateButtonContainer()
private static Button CreatePhraseButton(string text, string styleClass)
{
- var buttonWidth = GetButtonWidth();
var phraseButton = new Button
{
Access = AccessLevel.Public,
- MaxSize = new Vector2(buttonWidth, buttonWidth),
+ MaxSize = new Vector2(ButtonWidth, ButtonWidth),
ClipText = false,
HorizontalExpand = true,
StyleClasses = { styleClass }
@@ -142,20 +145,7 @@ private static Button CreatePhraseButton(string text, string styleClass)
return phraseButton;
}
- private static int GetButtonWidth()
- {
- var spaceWidth = 10;
- var parentWidth = 540;
- var columnCount = 4;
-
- var paddingSize = spaceWidth * 2;
- var gutterScale = (columnCount - 1) / columnCount;
- var columnWidth = (parentWidth - paddingSize) / columnCount;
- var buttonWidth = columnWidth - spaceWidth * gutterScale;
- return buttonWidth;
- }
-
- private void OnPhraseButtonPressed(string phraseId)
+ private void OnPhraseButtonPressed(ProtoId phraseId)
{
PhraseButtonPressed?.Invoke(phraseId);
}
diff --git a/Content.Client/DeltaV/CartridgeLoader/Cartridges/NanoChatEntry.xaml b/Content.Client/DeltaV/CartridgeLoader/Cartridges/NanoChatEntry.xaml
new file mode 100644
index 00000000000..0b136133624
--- /dev/null
+++ b/Content.Client/DeltaV/CartridgeLoader/Cartridges/NanoChatEntry.xaml
@@ -0,0 +1,48 @@
+
+
+
diff --git a/Content.Client/DeltaV/CartridgeLoader/Cartridges/NanoChatEntry.xaml.cs b/Content.Client/DeltaV/CartridgeLoader/Cartridges/NanoChatEntry.xaml.cs
new file mode 100644
index 00000000000..ff4ea9ba9c1
--- /dev/null
+++ b/Content.Client/DeltaV/CartridgeLoader/Cartridges/NanoChatEntry.xaml.cs
@@ -0,0 +1,39 @@
+using Content.Shared.DeltaV.CartridgeLoader.Cartridges;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+
+namespace Content.Client.DeltaV.CartridgeLoader.Cartridges;
+
+[GenerateTypedNameReferences]
+public sealed partial class NanoChatEntry : BoxContainer
+{
+ public event Action? OnPressed;
+ private uint _number;
+ private Action? _pressHandler;
+
+ public NanoChatEntry()
+ {
+ RobustXamlLoader.Load(this);
+ }
+
+ public void SetRecipient(NanoChatRecipient recipient, uint number, bool isSelected)
+ {
+ // Remove old handler if it exists
+ if (_pressHandler != null)
+ ChatButton.OnPressed -= _pressHandler;
+
+ _number = number;
+
+ // Create and store new handler
+ _pressHandler = _ => OnPressed?.Invoke(_number);
+ ChatButton.OnPressed += _pressHandler;
+
+ NameLabel.Text = recipient.Name;
+ JobLabel.Text = recipient.JobTitle ?? "";
+ JobLabel.Visible = !string.IsNullOrEmpty(recipient.JobTitle);
+ UnreadIndicator.Visible = recipient.HasUnread;
+
+ ChatButton.ModulateSelfOverride = isSelected ? NanoChatMessageBubble.OwnMessageColor : null;
+ }
+}
diff --git a/Content.Client/DeltaV/CartridgeLoader/Cartridges/NanoChatLogEntry.xaml b/Content.Client/DeltaV/CartridgeLoader/Cartridges/NanoChatLogEntry.xaml
new file mode 100644
index 00000000000..c87478d6301
--- /dev/null
+++ b/Content.Client/DeltaV/CartridgeLoader/Cartridges/NanoChatLogEntry.xaml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
diff --git a/Content.Client/DeltaV/CartridgeLoader/Cartridges/NanoChatLogEntry.xaml.cs b/Content.Client/DeltaV/CartridgeLoader/Cartridges/NanoChatLogEntry.xaml.cs
new file mode 100644
index 00000000000..b94ea1a18aa
--- /dev/null
+++ b/Content.Client/DeltaV/CartridgeLoader/Cartridges/NanoChatLogEntry.xaml.cs
@@ -0,0 +1,17 @@
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+
+namespace Content.Client.DeltaV.CartridgeLoader.Cartridges;
+
+[GenerateTypedNameReferences]
+public sealed partial class NanoChatLogEntry : BoxContainer
+{
+ public NanoChatLogEntry(int number, string time, string message)
+ {
+ RobustXamlLoader.Load(this);
+ NumberLabel.Text = number.ToString();
+ TimeLabel.Text = time;
+ MessageLabel.Text = message;
+ }
+}
diff --git a/Content.Client/DeltaV/CartridgeLoader/Cartridges/NanoChatMessageBubble.xaml b/Content.Client/DeltaV/CartridgeLoader/Cartridges/NanoChatMessageBubble.xaml
new file mode 100644
index 00000000000..84daa2f1c59
--- /dev/null
+++ b/Content.Client/DeltaV/CartridgeLoader/Cartridges/NanoChatMessageBubble.xaml
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/DeltaV/CartridgeLoader/Cartridges/NanoChatMessageBubble.xaml.cs b/Content.Client/DeltaV/CartridgeLoader/Cartridges/NanoChatMessageBubble.xaml.cs
new file mode 100644
index 00000000000..42725bb09c5
--- /dev/null
+++ b/Content.Client/DeltaV/CartridgeLoader/Cartridges/NanoChatMessageBubble.xaml.cs
@@ -0,0 +1,62 @@
+using Content.Shared.DeltaV.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 NanoChatMessageBubble : BoxContainer
+{
+ public static readonly Color OwnMessageColor = Color.FromHex("#173717d9"); // Dark green
+ public static readonly Color OtherMessageColor = Color.FromHex("#252525d9"); // Dark gray
+ public static readonly Color BorderColor = Color.FromHex("#40404066"); // Subtle border
+ public static readonly Color TextColor = Color.FromHex("#dcdcdc"); // Slightly softened white
+ public static readonly Color ErrorColor = Color.FromHex("#cc3333"); // Red
+
+ public NanoChatMessageBubble()
+ {
+ RobustXamlLoader.Load(this);
+ }
+
+ public void SetMessage(NanoChatMessage message, bool isOwnMessage)
+ {
+ if (MessagePanel.PanelOverride is not StyleBoxFlat)
+ return;
+
+ // Configure message appearance
+ var style = (StyleBoxFlat)MessagePanel.PanelOverride;
+ style.BackgroundColor = isOwnMessage ? OwnMessageColor : OtherMessageColor;
+ style.BorderColor = BorderColor;
+
+ // Set message content
+ MessageText.Text = message.Content;
+ MessageText.Modulate = TextColor;
+
+ // Show delivery failed text if needed (only for own messages)
+ DeliveryFailedLabel.Visible = isOwnMessage && message.DeliveryFailed;
+ if (DeliveryFailedLabel.Visible)
+ DeliveryFailedLabel.Modulate = ErrorColor;
+
+ // For own messages: FlexSpace -> MessagePanel -> RightSpacer
+ // For other messages: LeftSpacer -> MessagePanel -> FlexSpace
+ MessageContainer.RemoveAllChildren();
+
+ // fuuuuuck
+ MessageBox.Parent?.RemoveChild(MessageBox);
+
+ if (isOwnMessage)
+ {
+ MessageContainer.AddChild(FlexSpace);
+ MessageContainer.AddChild(MessageBox);
+ MessageContainer.AddChild(RightSpacer);
+ }
+ else
+ {
+ MessageContainer.AddChild(LeftSpacer);
+ MessageContainer.AddChild(MessageBox);
+ MessageContainer.AddChild(FlexSpace);
+ }
+ }
+}
diff --git a/Content.Client/DeltaV/CartridgeLoader/Cartridges/NanoChatUi.cs b/Content.Client/DeltaV/CartridgeLoader/Cartridges/NanoChatUi.cs
new file mode 100644
index 00000000000..fb65b03e887
--- /dev/null
+++ b/Content.Client/DeltaV/CartridgeLoader/Cartridges/NanoChatUi.cs
@@ -0,0 +1,43 @@
+using Content.Client.UserInterface.Fragments;
+using Content.Shared.CartridgeLoader;
+using Content.Shared.DeltaV.CartridgeLoader.Cartridges;
+using Robust.Client.UserInterface;
+
+namespace Content.Client.DeltaV.CartridgeLoader.Cartridges;
+
+public sealed partial class NanoChatUi : UIFragment
+{
+ private NanoChatUiFragment? _fragment;
+
+ public override Control GetUIFragmentRoot()
+ {
+ return _fragment!;
+ }
+
+ public override void Setup(BoundUserInterface userInterface, EntityUid? fragmentOwner)
+ {
+ _fragment = new NanoChatUiFragment();
+
+ _fragment.OnMessageSent += (type, number, content, job) =>
+ {
+ SendNanoChatUiMessage(type, number, content, job, userInterface);
+ };
+ }
+
+ public override void UpdateState(BoundUserInterfaceState state)
+ {
+ if (state is NanoChatUiState cast)
+ _fragment?.UpdateState(cast);
+ }
+
+ private static void SendNanoChatUiMessage(NanoChatUiMessageType type,
+ uint? number,
+ string? content,
+ string? job,
+ BoundUserInterface userInterface)
+ {
+ var nanoChatMessage = new NanoChatUiMessageEvent(type, number, content, job);
+ var message = new CartridgeUiMessage(nanoChatMessage);
+ userInterface.SendMessage(message);
+ }
+}
diff --git a/Content.Client/DeltaV/CartridgeLoader/Cartridges/NanoChatUiFragment.xaml b/Content.Client/DeltaV/CartridgeLoader/Cartridges/NanoChatUiFragment.xaml
new file mode 100644
index 00000000000..2a39094b85d
--- /dev/null
+++ b/Content.Client/DeltaV/CartridgeLoader/Cartridges/NanoChatUiFragment.xaml
@@ -0,0 +1,167 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/DeltaV/CartridgeLoader/Cartridges/NanoChatUiFragment.xaml.cs b/Content.Client/DeltaV/CartridgeLoader/Cartridges/NanoChatUiFragment.xaml.cs
new file mode 100644
index 00000000000..159d6b1a93a
--- /dev/null
+++ b/Content.Client/DeltaV/CartridgeLoader/Cartridges/NanoChatUiFragment.xaml.cs
@@ -0,0 +1,254 @@
+using System.Linq;
+using System.Numerics;
+using Content.Shared.DeltaV.CartridgeLoader.Cartridges;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.Controls;
+using Robust.Client.UserInterface.XAML;
+using Robust.Client.UserInterface;
+using Robust.Shared.Timing;
+
+namespace Content.Client.DeltaV.CartridgeLoader.Cartridges;
+
+[GenerateTypedNameReferences]
+public sealed partial class NanoChatUiFragment : BoxContainer
+{
+ [Dependency] private readonly IGameTiming _timing = default!;
+
+ private const int MaxMessageLength = 256;
+
+ private readonly NewChatPopup _newChatPopup;
+ private uint? _currentChat;
+ private uint? _pendingChat;
+ private uint _ownNumber;
+ private bool _notificationsMuted;
+ private Dictionary _recipients = new();
+ private Dictionary> _messages = new();
+
+ public event Action? OnMessageSent;
+
+ public NanoChatUiFragment()
+ {
+ IoCManager.InjectDependencies(this);
+ RobustXamlLoader.Load(this);
+
+ _newChatPopup = new NewChatPopup();
+ SetupEventHandlers();
+ }
+
+ private void SetupEventHandlers()
+ {
+ _newChatPopup.OnChatCreated += (number, name, job) =>
+ {
+ OnMessageSent?.Invoke(NanoChatUiMessageType.NewChat, number, name, job);
+ };
+
+ NewChatButton.OnPressed += _ =>
+ {
+ _newChatPopup.ClearInputs();
+ _newChatPopup.OpenCentered();
+ };
+
+ MuteButton.OnPressed += _ =>
+ {
+ _notificationsMuted = !_notificationsMuted;
+ UpdateMuteButton();
+ OnMessageSent?.Invoke(NanoChatUiMessageType.ToggleMute, null, null, null);
+ };
+
+ MessageInput.OnTextChanged += args =>
+ {
+ var length = args.Text.Length;
+ var isValid = !string.IsNullOrWhiteSpace(args.Text) &&
+ length <= MaxMessageLength &&
+ (_currentChat != null || _pendingChat != null);
+
+ SendButton.Disabled = !isValid;
+
+ // Show character count when over limit
+ CharacterCount.Visible = length > MaxMessageLength;
+ if (length > MaxMessageLength)
+ {
+ CharacterCount.Text = Loc.GetString("nano-chat-message-too-long",
+ ("current", length),
+ ("max", MaxMessageLength));
+ CharacterCount.StyleClasses.Add("LabelDanger");
+ }
+ };
+
+ SendButton.OnPressed += _ => SendMessage();
+ DeleteChatButton.OnPressed += _ => DeleteCurrentChat();
+ }
+
+ private void SendMessage()
+ {
+ var activeChat = _pendingChat ?? _currentChat;
+ if (activeChat == null || string.IsNullOrWhiteSpace(MessageInput.Text))
+ return;
+
+ var messageContent = MessageInput.Text;
+
+ // Add predicted message
+ var predictedMessage = new NanoChatMessage(
+ _timing.CurTime,
+ messageContent,
+ _ownNumber
+ );
+
+ if (!_messages.TryGetValue(activeChat.Value, out var value))
+ {
+ value = new List();
+ _messages[activeChat.Value] = value;
+ }
+
+ value.Add(predictedMessage);
+
+ // Update UI with predicted message
+ UpdateMessages(_messages);
+
+ // Send message event
+ OnMessageSent?.Invoke(NanoChatUiMessageType.SendMessage, activeChat, messageContent, null);
+
+ // Clear input
+ MessageInput.Text = string.Empty;
+ SendButton.Disabled = true;
+ }
+
+ private void SelectChat(uint number)
+ {
+ // Don't reselect the same chat
+ if (_currentChat == number && _pendingChat == null)
+ return;
+
+ _pendingChat = number;
+
+ // Predict marking messages as read
+ if (_recipients.TryGetValue(number, out var recipient))
+ {
+ recipient.HasUnread = false;
+ _recipients[number] = recipient;
+ UpdateChatList(_recipients);
+ }
+
+ OnMessageSent?.Invoke(NanoChatUiMessageType.SelectChat, number, null, null);
+ UpdateCurrentChat();
+ }
+
+ private void DeleteCurrentChat()
+ {
+ var activeChat = _pendingChat ?? _currentChat;
+ if (activeChat == null)
+ return;
+
+ OnMessageSent?.Invoke(NanoChatUiMessageType.DeleteChat, activeChat, null, null);
+ }
+
+ private void UpdateChatList(Dictionary recipients)
+ {
+ ChatList.RemoveAllChildren();
+ _recipients = recipients;
+
+ NoChatsLabel.Visible = recipients.Count == 0;
+ if (NoChatsLabel.Parent != ChatList)
+ {
+ NoChatsLabel.Parent?.RemoveChild(NoChatsLabel);
+ ChatList.AddChild(NoChatsLabel);
+ }
+
+ foreach (var (number, recipient) in recipients.OrderBy(r => r.Value.Name))
+ {
+ var entry = new NanoChatEntry();
+ // For pending chat selection, always show it as selected even if unconfirmed
+ var isSelected = (_pendingChat == number) || (_pendingChat == null && _currentChat == number);
+ entry.SetRecipient(recipient, number, isSelected);
+ entry.OnPressed += SelectChat;
+ ChatList.AddChild(entry);
+ }
+ }
+
+ private void UpdateCurrentChat()
+ {
+ var activeChat = _pendingChat ?? _currentChat;
+ var hasActiveChat = activeChat != null;
+
+ // Update UI state
+ MessagesScroll.Visible = hasActiveChat;
+ CurrentChatName.Visible = !hasActiveChat;
+ MessageInputContainer.Visible = hasActiveChat;
+ DeleteChatButton.Visible = hasActiveChat;
+ DeleteChatButton.Disabled = !hasActiveChat;
+
+ if (activeChat != null && _recipients.TryGetValue(activeChat.Value, out var recipient))
+ {
+ CurrentChatName.Text = recipient.Name + (string.IsNullOrEmpty(recipient.JobTitle) ? "" : $" ({recipient.JobTitle})");
+ }
+ else
+ {
+ CurrentChatName.Text = Loc.GetString("nano-chat-select-chat");
+ }
+ }
+
+ private void UpdateMessages(Dictionary> messages)
+ {
+ _messages = messages;
+ MessageList.RemoveAllChildren();
+
+ var activeChat = _pendingChat ?? _currentChat;
+ if (activeChat == null || !messages.TryGetValue(activeChat.Value, out var chatMessages))
+ return;
+
+ foreach (var message in chatMessages)
+ {
+ var messageBubble = new NanoChatMessageBubble();
+ messageBubble.SetMessage(message, message.SenderId == _ownNumber);
+ MessageList.AddChild(messageBubble);
+
+ // Add spacing between messages
+ MessageList.AddChild(new Control { MinSize = new Vector2(0, 4) });
+ }
+
+ MessageList.InvalidateMeasure();
+ MessagesScroll.InvalidateMeasure();
+
+ // Scroll to bottom after messages are added
+ if (MessageList.Parent is ScrollContainer scroll)
+ scroll.SetScrollValue(new Vector2(0, float.MaxValue));
+ }
+
+ private void UpdateMuteButton()
+ {
+ if (BellMutedIcon != null)
+ BellMutedIcon.Visible = _notificationsMuted;
+ }
+
+ public void UpdateState(NanoChatUiState state)
+ {
+ _ownNumber = state.OwnNumber;
+ _notificationsMuted = state.NotificationsMuted;
+ OwnNumberLabel.Text = $"#{state.OwnNumber:D4}";
+ UpdateMuteButton();
+
+ // Update new chat button state based on recipient limit
+ var atLimit = state.Recipients.Count >= state.MaxRecipients;
+ NewChatButton.Disabled = atLimit;
+ NewChatButton.ToolTip = atLimit
+ ? Loc.GetString("nano-chat-max-recipients")
+ : Loc.GetString("nano-chat-new-chat");
+
+ // First handle pending chat resolution if we have one
+ if (_pendingChat != null)
+ {
+ if (_pendingChat == state.CurrentChat)
+ _currentChat = _pendingChat; // Server confirmed our selection
+
+ _pendingChat = null; // Clear pending either way
+ }
+
+ // No pending chat or it was just cleared, update current directly
+ if (_pendingChat == null)
+ _currentChat = state.CurrentChat;
+
+ UpdateCurrentChat();
+ UpdateChatList(state.Recipients);
+ UpdateMessages(state.Messages);
+ }
+}
diff --git a/Content.Client/DeltaV/CartridgeLoader/Cartridges/NewChatPopup.xaml b/Content.Client/DeltaV/CartridgeLoader/Cartridges/NewChatPopup.xaml
new file mode 100644
index 00000000000..20095c4fce9
--- /dev/null
+++ b/Content.Client/DeltaV/CartridgeLoader/Cartridges/NewChatPopup.xaml
@@ -0,0 +1,52 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/DeltaV/CartridgeLoader/Cartridges/NewChatPopup.xaml.cs b/Content.Client/DeltaV/CartridgeLoader/Cartridges/NewChatPopup.xaml.cs
new file mode 100644
index 00000000000..8e47e1ee5d4
--- /dev/null
+++ b/Content.Client/DeltaV/CartridgeLoader/Cartridges/NewChatPopup.xaml.cs
@@ -0,0 +1,87 @@
+using System.Linq;
+using Robust.Client.AutoGenerated;
+using Robust.Client.UserInterface.CustomControls;
+using Robust.Client.UserInterface.XAML;
+
+namespace Content.Client.DeltaV.CartridgeLoader.Cartridges;
+
+[GenerateTypedNameReferences]
+public sealed partial class NewChatPopup : DefaultWindow
+{
+ private const int MaxInputLength = 16;
+ private const int MaxNumberLength = 4; // i hardcoded it to be 4 so suffer
+
+ public event Action? OnChatCreated;
+
+ public NewChatPopup()
+ {
+ RobustXamlLoader.Load(this);
+
+ // margins trolling
+ ContentsContainer.Margin = new Thickness(3);
+
+ // Button handlers
+ CancelButton.OnPressed += _ => Close();
+ CreateButton.OnPressed += _ => CreateChat();
+
+ // Input validation
+ NumberInput.OnTextChanged += _ => ValidateInputs();
+ NameInput.OnTextChanged += _ => ValidateInputs();
+
+ // Input validation
+ NumberInput.OnTextChanged += args =>
+ {
+ if (args.Text.Length > MaxNumberLength)
+ NumberInput.Text = args.Text[..MaxNumberLength];
+
+ // Filter to digits only
+ var newText = string.Concat(NumberInput.Text.Where(char.IsDigit));
+ if (newText != NumberInput.Text)
+ NumberInput.Text = newText;
+
+ ValidateInputs();
+ };
+
+ NameInput.OnTextChanged += args =>
+ {
+ if (args.Text.Length > MaxInputLength)
+ NameInput.Text = args.Text[..MaxInputLength];
+ ValidateInputs();
+ };
+
+ JobInput.OnTextChanged += args =>
+ {
+ if (args.Text.Length > MaxInputLength)
+ JobInput.Text = args.Text[..MaxInputLength];
+ };
+ }
+
+ private void ValidateInputs()
+ {
+ var isValid = !string.IsNullOrWhiteSpace(NumberInput.Text) &&
+ !string.IsNullOrWhiteSpace(NameInput.Text) &&
+ uint.TryParse(NumberInput.Text, out _);
+
+ CreateButton.Disabled = !isValid;
+ }
+
+ private void CreateChat()
+ {
+ if (!uint.TryParse(NumberInput.Text, out var number))
+ return;
+
+ var name = NameInput.Text.Trim();
+ var job = string.IsNullOrWhiteSpace(JobInput.Text) ? null : JobInput.Text.Trim();
+
+ OnChatCreated?.Invoke(number, name, job);
+ Close();
+ }
+
+ public void ClearInputs()
+ {
+ NumberInput.Text = string.Empty;
+ NameInput.Text = string.Empty;
+ JobInput.Text = string.Empty;
+ ValidateInputs();
+ }
+}
diff --git a/Content.Client/DeltaV/NanoChat/NanoChatSystem.cs b/Content.Client/DeltaV/NanoChat/NanoChatSystem.cs
new file mode 100644
index 00000000000..242deb05b72
--- /dev/null
+++ b/Content.Client/DeltaV/NanoChat/NanoChatSystem.cs
@@ -0,0 +1,5 @@
+using Content.Shared.DeltaV.NanoChat;
+
+namespace Content.Client.DeltaV.NanoChat;
+
+public sealed class NanoChatSystem : SharedNanoChatSystem;
diff --git a/Content.Client/DeltaV/Shuttles/Systems/DockingConsoleSystem.cs b/Content.Client/DeltaV/Shuttles/Systems/DockingConsoleSystem.cs
new file mode 100644
index 00000000000..5e0df6ae5d7
--- /dev/null
+++ b/Content.Client/DeltaV/Shuttles/Systems/DockingConsoleSystem.cs
@@ -0,0 +1,5 @@
+using Content.Shared.DeltaV.Shuttles.Systems;
+
+namespace Content.Client.DeltaV.Shuttles.Systems;
+
+public sealed class DockingConsoleSystem : SharedDockingConsoleSystem;
diff --git a/Content.Client/DeltaV/Shuttles/UI/DockingConsoleBoundUserInterface.cs b/Content.Client/DeltaV/Shuttles/UI/DockingConsoleBoundUserInterface.cs
new file mode 100644
index 00000000000..450ffc04a1d
--- /dev/null
+++ b/Content.Client/DeltaV/Shuttles/UI/DockingConsoleBoundUserInterface.cs
@@ -0,0 +1,38 @@
+using Content.Shared.DeltaV.Shuttles;
+
+namespace Content.Client.DeltaV.Shuttles.UI;
+
+public sealed class DockingConsoleBoundUserInterface : BoundUserInterface
+{
+ [ViewVariables]
+ private DockingConsoleWindow? _window;
+
+ public DockingConsoleBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
+ {
+ }
+
+ protected override void Open()
+ {
+ base.Open();
+
+ _window = new DockingConsoleWindow(Owner);
+ _window.OnFTL += index => SendMessage(new DockingConsoleFTLMessage(index));
+ _window.OnClose += Close;
+ _window.OpenCentered();
+ }
+
+ protected override void UpdateState(BoundUserInterfaceState state)
+ {
+ base.UpdateState(state);
+ if (state is DockingConsoleState cast)
+ _window?.UpdateState(cast);
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ base.Dispose(disposing);
+
+ if (disposing)
+ _window?.Orphan();
+ }
+}
diff --git a/Content.Client/DeltaV/Shuttles/UI/DockingConsoleWindow.xaml b/Content.Client/DeltaV/Shuttles/UI/DockingConsoleWindow.xaml
new file mode 100644
index 00000000000..595a30be807
--- /dev/null
+++ b/Content.Client/DeltaV/Shuttles/UI/DockingConsoleWindow.xaml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Content.Client/DeltaV/Shuttles/UI/DockingConsoleWindow.xaml.cs b/Content.Client/DeltaV/Shuttles/UI/DockingConsoleWindow.xaml.cs
new file mode 100644
index 00000000000..eaa489ae868
--- /dev/null
+++ b/Content.Client/DeltaV/Shuttles/UI/DockingConsoleWindow.xaml.cs
@@ -0,0 +1,111 @@
+using Content.Client.UserInterface.Controls;
+using Content.Shared.Access.Systems;
+using Content.Shared.DeltaV.Shuttles;
+using Content.Shared.DeltaV.Shuttles.Components;
+using Content.Shared.Shuttles.Systems;
+using Content.Shared.Timing;
+using Robust.Client.AutoGenerated;
+using Robust.Client.Graphics;
+using Robust.Client.Player;
+using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Timing;
+
+namespace Content.Client.DeltaV.Shuttles.UI;
+
+[GenerateTypedNameReferences]
+public sealed partial class DockingConsoleWindow : FancyWindow
+{
+ [Dependency] private readonly IEntityManager _entMan = default!;
+ [Dependency] private readonly IGameTiming _timing = default!;
+ [Dependency] private readonly IPlayerManager _player = default!;
+ private readonly AccessReaderSystem _access;
+
+ public event Action? OnFTL;
+
+ private readonly EntityUid _owner;
+ private readonly StyleBoxFlat _ftlStyle;
+
+ private FTLState _state;
+ private int? _selected;
+ private StartEndTime _ftlTime;
+
+ public DockingConsoleWindow(EntityUid owner)
+ {
+ RobustXamlLoader.Load(this);
+ IoCManager.InjectDependencies(this);
+
+ _access = _entMan.System();
+
+ _owner = owner;
+
+ _ftlStyle = new StyleBoxFlat(Color.LimeGreen);
+ FTLBar.ForegroundStyleBoxOverride = _ftlStyle;
+
+ if (!_entMan.TryGetComponent(owner, out var comp))
+ return;
+
+ Title = Loc.GetString(comp.WindowTitle);
+
+ if (!comp.HasShuttle)
+ {
+ MapFTLState.Text = Loc.GetString("docking-console-no-shuttle");
+ _ftlStyle.BackgroundColor = Color.FromHex("#B02E26");
+ }
+
+ Destinations.OnItemSelected += args => _selected = args.ItemIndex;
+ Destinations.OnItemDeselected += _ => _selected = null;
+
+ FTLButton.OnPressed += _ =>
+ {
+ if (_selected is {} index)
+ OnFTL?.Invoke(index);
+ };
+ }
+
+ public void UpdateState(DockingConsoleState state)
+ {
+ _state = state.FTLState;
+ _ftlTime = state.FTLTime;
+
+ MapFTLState.Text = Loc.GetString($"shuttle-console-ftl-state-{_state.ToString()}");
+ _ftlStyle.BackgroundColor = Color.FromHex(_state switch
+ {
+ FTLState.Available => "#80C71F",
+ FTLState.Starting => "#169C9C",
+ FTLState.Travelling => "#8932B8",
+ FTLState.Arriving => "#F9801D",
+ _ => "#B02E26" // cooldown and fallback
+ });
+
+ UpdateButton();
+
+ if (Destinations.Count == state.Destinations.Count)
+ return;
+
+ Destinations.Clear();
+ foreach (var dest in state.Destinations)
+ {
+ Destinations.AddItem(dest.Name);
+ }
+ }
+
+ private void UpdateButton()
+ {
+ FTLButton.Disabled = _selected == null || _state != FTLState.Available || !HasAccess();
+ }
+
+ private bool HasAccess()
+ {
+ return _player.LocalSession?.AttachedEntity is {} player && _access.IsAllowed(player, _owner);
+ }
+
+ protected override void FrameUpdate(FrameEventArgs args)
+ {
+ base.FrameUpdate(args);
+
+ UpdateButton();
+
+ var progress = _ftlTime.ProgressAt(_timing.CurTime);
+ FTLBar.Value = float.IsFinite(progress) ? progress : 1;
+ }
+}
diff --git a/Content.Client/Electrocution/ElectrocutionHUDVisualizerSystem.cs b/Content.Client/Electrocution/ElectrocutionHUDVisualizerSystem.cs
new file mode 100644
index 00000000000..b95c0d585d7
--- /dev/null
+++ b/Content.Client/Electrocution/ElectrocutionHUDVisualizerSystem.cs
@@ -0,0 +1,95 @@
+using Content.Shared.Electrocution;
+using Robust.Client.GameObjects;
+using Robust.Client.Player;
+using Robust.Shared.Player;
+
+namespace Content.Client.Electrocution;
+
+///
+/// Shows the Electrocution HUD to entities with the ShowElectrocutionHUDComponent.
+///
+public sealed class ElectrocutionHUDVisualizerSystem : VisualizerSystem
+{
+ [Dependency] private readonly IPlayerManager _playerMan = default!;
+
+ public override void Initialize()
+ {
+ base.Initialize();
+
+ SubscribeLocalEvent(OnInit);
+ SubscribeLocalEvent(OnShutdown);
+ SubscribeLocalEvent(OnPlayerAttached);
+ SubscribeLocalEvent(OnPlayerDetached);
+ }
+
+ private void OnPlayerAttached(Entity ent, ref LocalPlayerAttachedEvent args)
+ {
+ ShowHUD();
+ }
+
+ private void OnPlayerDetached(Entity ent, ref LocalPlayerDetachedEvent args)
+ {
+ RemoveHUD();
+ }
+
+ private void OnInit(Entity ent, ref ComponentInit args)
+ {
+ if (_playerMan.LocalEntity == ent)
+ {
+ ShowHUD();
+ }
+ }
+
+ private void OnShutdown(Entity ent, ref ComponentShutdown args)
+ {
+ if (_playerMan.LocalEntity == ent)
+ {
+ RemoveHUD();
+ }
+ }
+
+ // Show the HUD to the client.
+ // We have to look for all current entities that can be electrified and toggle the HUD layer on if they are.
+ private void ShowHUD()
+ {
+ var electrifiedQuery = AllEntityQuery();
+ while (electrifiedQuery.MoveNext(out var uid, out var _, out var appearanceComp, out var spriteComp))
+ {
+ if (!AppearanceSystem.TryGetData(uid, ElectrifiedVisuals.IsElectrified, out var electrified, appearanceComp))
+ continue;
+
+ if (electrified)
+ spriteComp.LayerSetVisible(ElectrifiedLayers.HUD, true);
+ else
+ spriteComp.LayerSetVisible(ElectrifiedLayers.HUD, false);
+ }
+ }
+
+ // Remove the HUD from the client.
+ // Find all current entities that can be electrified and hide the HUD layer.
+ private void RemoveHUD()
+ {
+ var electrifiedQuery = AllEntityQuery();
+ while (electrifiedQuery.MoveNext(out var uid, out var _, out var appearanceComp, out var spriteComp))
+ {
+
+ spriteComp.LayerSetVisible(ElectrifiedLayers.HUD, false);
+ }
+ }
+
+ // Toggle the HUD layer if an entity becomes (de-)electrified
+ protected override void OnAppearanceChange(EntityUid uid, ElectrocutionHUDVisualsComponent comp, ref AppearanceChangeEvent args)
+ {
+ if (args.Sprite == null)
+ return;
+
+ if (!AppearanceSystem.TryGetData(uid, ElectrifiedVisuals.IsElectrified, out var electrified, args.Component))
+ return;
+
+ var player = _playerMan.LocalEntity;
+ if (electrified && HasComp(player))
+ args.Sprite.LayerSetVisible(ElectrifiedLayers.HUD, true);
+ else
+ args.Sprite.LayerSetVisible(ElectrifiedLayers.HUD, false);
+ }
+}
diff --git a/Content.Client/Eui/BaseEui.cs b/Content.Client/Eui/BaseEui.cs
index 7f86ded7e48..c11ba5a9b69 100644
--- a/Content.Client/Eui/BaseEui.cs
+++ b/Content.Client/Eui/BaseEui.cs
@@ -55,7 +55,7 @@ public virtual void HandleMessage(EuiMessageBase msg)
///
protected void SendMessage(EuiMessageBase msg)
{
- var netMsg = _netManager.CreateNetMessage();
+ var netMsg = new MsgEuiMessage();
netMsg.Id = Id;
netMsg.Message = msg;
diff --git a/Content.Client/Info/LinkBanner.cs b/Content.Client/Info/LinkBanner.cs
index a30aa413761..7366a8f8565 100644
--- a/Content.Client/Info/LinkBanner.cs
+++ b/Content.Client/Info/LinkBanner.cs
@@ -34,6 +34,7 @@ public LinkBanner()
AddInfoButton("server-info-website-button", CCVars.InfoLinksWebsite);
AddInfoButton("server-info-wiki-button", CCVars.InfoLinksWiki);
AddInfoButton("server-info-forum-button", CCVars.InfoLinksForum);
+ AddInfoButton("server-info-telegram-button", CCVars.InfoLinksTelegram);
var guidebookController = UserInterfaceManager.GetUIController();
var guidebookButton = new Button() { Text = Loc.GetString("server-info-guidebook-button") };
diff --git a/Content.Client/Inventory/StrippableBoundUserInterface.cs b/Content.Client/Inventory/StrippableBoundUserInterface.cs
index 2ce07758c96..90e52d72837 100644
--- a/Content.Client/Inventory/StrippableBoundUserInterface.cs
+++ b/Content.Client/Inventory/StrippableBoundUserInterface.cs
@@ -17,6 +17,7 @@
using Content.Shared.Strip.Components;
using JetBrains.Annotations;
using Robust.Client.GameObjects;
+using Robust.Client.Player;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Shared.Input;
@@ -29,10 +30,13 @@ namespace Content.Client.Inventory
[UsedImplicitly]
public sealed class StrippableBoundUserInterface : BoundUserInterface
{
+ [Dependency] private readonly IPlayerManager _player = default!;
[Dependency] private readonly IUserInterfaceManager _ui = default!;
+
private readonly ExamineSystem _examine;
private readonly InventorySystem _inv;
private readonly SharedCuffableSystem _cuffable;
+ private readonly StrippableSystem _strippable;
[ViewVariables]
private const int ButtonSeparation = 4;
@@ -51,6 +55,8 @@ public StrippableBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, u
_examine = EntMan.System();
_inv = EntMan.System();
_cuffable = EntMan.System();
+ _strippable = EntMan.System();
+
_virtualHiddenEntity = EntMan.SpawnEntity(HiddenPocketEntityId, MapCoordinates.Nullspace);
}
@@ -185,9 +191,15 @@ private void SlotPressed(GUIBoundKeyEventArgs ev, SlotControl slot)
return;
if (ev.Function == ContentKeyFunctions.ExamineEntity)
+ {
_examine.DoExamine(slot.Entity.Value);
+ ev.Handle();
+ }
else if (ev.Function == EngineKeyFunctions.UseSecondary)
+ {
_ui.GetUIController().OpenVerbMenu(slot.Entity.Value);
+ ev.Handle();
+ }
}
private void AddInventoryButton(EntityUid invUid, string slotId, InventoryComponent inv)
@@ -198,7 +210,8 @@ private void AddInventoryButton(EntityUid invUid, string slotId, InventoryCompon
var entity = container.ContainedEntity;
// If this is a full pocket, obscure the real entity
- if (entity != null && slotDef.StripHidden)
+ // this does not work for modified clients because they are still sent the real entity
+ if (entity != null && _strippable.IsStripHidden(slotDef, _player.LocalEntity))
entity = _virtualHiddenEntity;
var button = new SlotButton(new SlotData(slotDef, container));
diff --git a/Content.Client/Lathe/UI/LatheBoundUserInterface.cs b/Content.Client/Lathe/UI/LatheBoundUserInterface.cs
index a599f79152e..66f09aa41da 100644
--- a/Content.Client/Lathe/UI/LatheBoundUserInterface.cs
+++ b/Content.Client/Lathe/UI/LatheBoundUserInterface.cs
@@ -1,3 +1,4 @@
+using Content.Shared.DeltaV.Salvage; // DeltaV
using Content.Shared.Lathe;
using Content.Shared.Research.Components;
using JetBrains.Annotations;
@@ -31,6 +32,8 @@ protected override void Open()
{
SendMessage(new LatheQueueRecipeMessage(recipe, amount));
};
+
+ _menu.OnClaimMiningPoints += () => SendMessage(new LatheClaimMiningPointsMessage()); // DeltaV
}
protected override void UpdateState(BoundUserInterfaceState state)
diff --git a/Content.Client/Lathe/UI/LatheMenu.xaml b/Content.Client/Lathe/UI/LatheMenu.xaml
index 5b21f0bae66..d84449ce43e 100644
--- a/Content.Client/Lathe/UI/LatheMenu.xaml
+++ b/Content.Client/Lathe/UI/LatheMenu.xaml
@@ -132,6 +132,12 @@
HorizontalExpand="True">
+
+
+
+
+
+
diff --git a/Content.Client/Lathe/UI/LatheMenu.xaml.cs b/Content.Client/Lathe/UI/LatheMenu.xaml.cs
index 02464d22e12..c2289632052 100644
--- a/Content.Client/Lathe/UI/LatheMenu.xaml.cs
+++ b/Content.Client/Lathe/UI/LatheMenu.xaml.cs
@@ -1,16 +1,20 @@
using System.Linq;
using System.Text;
using Content.Client.Materials;
+using Content.Shared.DeltaV.Salvage.Components; // DeltaV
+using Content.Shared.DeltaV.Salvage.Systems; // DeltaV
using Content.Shared.Lathe;
using Content.Shared.Lathe.Prototypes;
using Content.Shared.Research.Prototypes;
using Robust.Client.AutoGenerated;
using Robust.Client.GameObjects;
+using Robust.Client.Player; // DeltaV
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.CustomControls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes;
+using Robust.Shared.Timing; // DeltaV
namespace Content.Client.Lathe.UI;
@@ -18,14 +22,17 @@ namespace Content.Client.Lathe.UI;
public sealed partial class LatheMenu : DefaultWindow
{
[Dependency] private readonly IEntityManager _entityManager = default!;
+ [Dependency] private readonly IPlayerManager _player = default!; // DeltaV
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
private readonly SpriteSystem _spriteSystem;
private readonly LatheSystem _lathe;
private readonly MaterialStorageSystem _materialStorage;
+ private readonly MiningPointsSystem _miningPoints; // DeltaV
public event Action? OnServerListButtonPressed;
public event Action? RecipeQueueAction;
+ public event Action? OnClaimMiningPoints; // DeltaV
public List> Recipes = new();
@@ -35,6 +42,8 @@ public sealed partial class LatheMenu : DefaultWindow
public EntityUid Entity;
+ private uint? _lastMiningPoints; // DeltaV: used to avoid Loc.GetString every frame
+
public LatheMenu()
{
RobustXamlLoader.Load(this);
@@ -43,6 +52,7 @@ public LatheMenu()
_spriteSystem = _entityManager.System();
_lathe = _entityManager.System();
_materialStorage = _entityManager.System();
+ _miningPoints = _entityManager.System(); // DeltaV
SearchBar.OnTextChanged += _ =>
{
@@ -70,9 +80,31 @@ public void SetEntity(EntityUid uid)
}
}
+ // Begin DeltaV Additions: Mining points UI
+ MiningPointsContainer.Visible = _entityManager.TryGetComponent(Entity, out var points);
+ MiningPointsClaimButton.OnPressed += _ => OnClaimMiningPoints?.Invoke();
+ if (points != null)
+ UpdateMiningPoints(points.Points);
+ // End DeltaV Additions
+
MaterialsList.SetOwner(Entity);
}
+ ///
+ /// DeltaV: Updates the UI elements for mining points.
+ ///
+ private void UpdateMiningPoints(uint points)
+ {
+ MiningPointsClaimButton.Disabled = points == 0 ||
+ _player.LocalSession?.AttachedEntity is not {} player ||
+ _miningPoints.TryFindIdCard(player) == null;
+ if (points == _lastMiningPoints)
+ return;
+
+ _lastMiningPoints = points;
+ MiningPointsLabel.Text = Loc.GetString("lathe-menu-mining-points", ("points", points));
+ }
+
protected override void Opened()
{
base.Opened();
@@ -83,6 +115,17 @@ protected override void Opened()
}
}
+ ///
+ /// DeltaV: Update mining points UI whenever it changes.
+ ///
+ protected override void FrameUpdate(FrameEventArgs args)
+ {
+ base.FrameUpdate(args);
+
+ if (_entityManager.TryGetComponent(Entity, out var points))
+ UpdateMiningPoints(points.Points);
+ }
+
///
/// Populates the list of all the recipes
///
diff --git a/Content.Client/Light/HandheldLightSystem.cs b/Content.Client/Light/HandheldLightSystem.cs
index ddd99c7c483..d25b28756f8 100644
--- a/Content.Client/Light/HandheldLightSystem.cs
+++ b/Content.Client/Light/HandheldLightSystem.cs
@@ -21,6 +21,22 @@ public override void Initialize()
SubscribeLocalEvent(OnAppearanceChange);
}
+ ///
+ /// TODO: Not properly predicted yet. Don't call this function if you want a the actual return value!
+ ///
+ public override bool TurnOff(Entity ent, bool makeNoise = true)
+ {
+ return true;
+ }
+
+ ///
+ /// TODO: Not properly predicted yet. Don't call this function if you want a the actual return value!
+ ///
+ public override bool TurnOn(EntityUid user, Entity uid)
+ {
+ return true;
+ }
+
private void OnAppearanceChange(EntityUid uid, HandheldLightComponent? component, ref AppearanceChangeEvent args)
{
if (!Resolve(uid, ref component))
diff --git a/Content.Client/Lobby/LobbyState.cs b/Content.Client/Lobby/LobbyState.cs
index 1aabc4ff381..1361ca57cd2 100644
--- a/Content.Client/Lobby/LobbyState.cs
+++ b/Content.Client/Lobby/LobbyState.cs
@@ -116,7 +116,7 @@ public override void FrameUpdate(FrameEventArgs e)
return;
}
- Lobby!.StationTime.Text = Loc.GetString("lobby-state-player-status-round-not-started");
+ Lobby!.StationTime.Text = Loc.GetString("lobby-state-player-status-round-not-started");
string text;
if (_gameTicker.Paused)
@@ -136,6 +136,10 @@ public override void FrameUpdate(FrameEventArgs e)
{
text = Loc.GetString(seconds < -5 ? "lobby-state-right-now-question" : "lobby-state-right-now-confirmation");
}
+ else if (difference.TotalHours >= 1)
+ {
+ text = $"{Math.Floor(difference.TotalHours)}:{difference.Minutes:D2}:{difference.Seconds:D2}";
+ }
else
{
text = $"{difference.Minutes}:{difference.Seconds:D2}";
diff --git a/Content.Client/Lobby/LobbyUIController.cs b/Content.Client/Lobby/LobbyUIController.cs
index 3cf98c98aba..50a25519988 100644
--- a/Content.Client/Lobby/LobbyUIController.cs
+++ b/Content.Client/Lobby/LobbyUIController.cs
@@ -279,7 +279,7 @@ private void OpenSavePanel()
_profileEditor.OnOpenGuidebook += _guide.OpenHelp;
- _characterSetup = new CharacterSetupGui(EntityManager, _prototypeManager, _resourceCache, _preferencesManager, _profileEditor);
+ _characterSetup = new CharacterSetupGui(_profileEditor);
_characterSetup.CloseButton.OnPressed += _ =>
{
diff --git a/Content.Client/Lobby/UI/CharacterSetupGui.xaml b/Content.Client/Lobby/UI/CharacterSetupGui.xaml
index f83be265884..c463987a1fe 100644
--- a/Content.Client/Lobby/UI/CharacterSetupGui.xaml
+++ b/Content.Client/Lobby/UI/CharacterSetupGui.xaml
@@ -2,6 +2,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
xmlns:style="clr-namespace:Content.Client.Stylesheets"
+ xmlns:cc="clr-namespace:Content.Client.Administration.UI.CustomControls"
VerticalExpand="True">
@@ -10,10 +11,15 @@
+
+
diff --git a/Content.Client/Lobby/UI/CharacterSetupGui.xaml.cs b/Content.Client/Lobby/UI/CharacterSetupGui.xaml.cs
index 777725b9eda..45799161324 100644
--- a/Content.Client/Lobby/UI/CharacterSetupGui.xaml.cs
+++ b/Content.Client/Lobby/UI/CharacterSetupGui.xaml.cs
@@ -1,6 +1,7 @@
using Content.Client.Info;
using Content.Client.Info.PlaytimeStats;
using Content.Client.Resources;
+using Content.Shared.CCVar;
using Content.Shared.Preferences;
using Robust.Client.AutoGenerated;
using Robust.Client.Graphics;
@@ -8,6 +9,7 @@
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
+using Robust.Shared.Configuration;
using Robust.Shared.Prototypes;
namespace Content.Client.Lobby.UI
@@ -18,28 +20,23 @@ namespace Content.Client.Lobby.UI
[GenerateTypedNameReferences]
public sealed partial class CharacterSetupGui : Control
{
- private readonly IClientPreferencesManager _preferencesManager;
- private readonly IEntityManager _entManager;
- private readonly IPrototypeManager _protomanager;
+ [Dependency] private readonly IClientPreferencesManager _preferencesManager = default!;
+ [Dependency] private readonly IEntityManager _entManager = default!;
+ [Dependency] private readonly IPrototypeManager _protomanager = default!;
+ [Dependency] private readonly IResourceCache _resourceCache = default!;
+ [Dependency] private readonly IConfigurationManager _cfg = default!;
private readonly Button _createNewCharacterButton;
public event Action? SelectCharacter;
public event Action? DeleteCharacter;
- public CharacterSetupGui(
- IEntityManager entManager,
- IPrototypeManager protoManager,
- IResourceCache resourceCache,
- IClientPreferencesManager preferencesManager,
- HumanoidProfileEditor profileEditor)
+ public CharacterSetupGui(HumanoidProfileEditor profileEditor)
{
RobustXamlLoader.Load(this);
- _preferencesManager = preferencesManager;
- _entManager = entManager;
- _protomanager = protoManager;
+ IoCManager.InjectDependencies(this);
- var panelTex = resourceCache.GetTexture("/Textures/Interface/Nano/button.svg.96dpi.png");
+ var panelTex = _resourceCache.GetTexture("/Textures/Interface/Nano/button.svg.96dpi.png");
var back = new StyleBoxTexture
{
Texture = panelTex,
@@ -56,7 +53,7 @@ public CharacterSetupGui(
_createNewCharacterButton.OnPressed += args =>
{
- preferencesManager.CreateCharacter(HumanoidCharacterProfile.Random());
+ _preferencesManager.CreateCharacter(HumanoidCharacterProfile.Random());
ReloadCharacterPickers();
args.Event.Handle();
};
@@ -65,6 +62,8 @@ public CharacterSetupGui(
RulesButton.OnPressed += _ => new RulesAndInfoWindow().Open();
StatsButton.OnPressed += _ => new PlaytimeStatsWindow().OpenCentered();
+
+ _cfg.OnValueChanged(CCVars.SeeOwnNotes, p => AdminRemarksButton.Visible = p, true);
}
///
diff --git a/Content.Client/Lobby/UI/LobbyGui.xaml.cs b/Content.Client/Lobby/UI/LobbyGui.xaml.cs
index 81230130a1d..6471edb6f37 100644
--- a/Content.Client/Lobby/UI/LobbyGui.xaml.cs
+++ b/Content.Client/Lobby/UI/LobbyGui.xaml.cs
@@ -2,7 +2,6 @@
using Content.Client.UserInterface.Systems.EscapeMenu;
using Robust.Client.AutoGenerated;
using Robust.Client.Console;
-using Robust.Client.State;
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.XAML;
diff --git a/Content.Client/Overlays/StencilOverlay.Weather.cs b/Content.Client/Overlays/StencilOverlay.Weather.cs
index bc514548036..ad69522dfda 100644
--- a/Content.Client/Overlays/StencilOverlay.Weather.cs
+++ b/Content.Client/Overlays/StencilOverlay.Weather.cs
@@ -35,7 +35,7 @@ private void DrawWeather(in OverlayDrawArgs args, WeatherPrototype weatherProto,
var matty = Matrix3x2.Multiply(matrix, invMatrix);
worldHandle.SetTransform(matty);
- foreach (var tile in grid.Comp.GetTilesIntersecting(worldAABB))
+ foreach (var tile in _map.GetTilesIntersecting(grid.Owner, grid, worldAABB))
{
// Ignored tiles for stencil
if (_weather.CanWeatherAffect(grid.Owner, grid, tile))
diff --git a/Content.Client/Overlays/StencilOverlay.cs b/Content.Client/Overlays/StencilOverlay.cs
index 78b1c4d2b15..eb5c27156ed 100644
--- a/Content.Client/Overlays/StencilOverlay.cs
+++ b/Content.Client/Overlays/StencilOverlay.cs
@@ -24,6 +24,7 @@ public sealed partial class StencilOverlay : Overlay
[Dependency] private readonly IPrototypeManager _protoManager = default!;
private readonly ParallaxSystem _parallax;
private readonly SharedTransformSystem _transform;
+ private readonly SharedMapSystem _map;
private readonly SpriteSystem _sprite;
private readonly WeatherSystem _weather;
@@ -33,11 +34,12 @@ public sealed partial class StencilOverlay : Overlay
private readonly ShaderInstance _shader;
- public StencilOverlay(ParallaxSystem parallax, SharedTransformSystem transform, SpriteSystem sprite, WeatherSystem weather)
+ public StencilOverlay(ParallaxSystem parallax, SharedTransformSystem transform, SharedMapSystem map, SpriteSystem sprite, WeatherSystem weather)
{
ZIndex = ParallaxSystem.ParallaxZIndex + 1;
_parallax = parallax;
_transform = transform;
+ _map = map;
_sprite = sprite;
_weather = weather;
IoCManager.InjectDependencies(this);
diff --git a/Content.Client/Overlays/StencilOverlaySystem.cs b/Content.Client/Overlays/StencilOverlaySystem.cs
index c8a9553cfdd..364ec0fddbf 100644
--- a/Content.Client/Overlays/StencilOverlaySystem.cs
+++ b/Content.Client/Overlays/StencilOverlaySystem.cs
@@ -10,13 +10,14 @@ public sealed class StencilOverlaySystem : EntitySystem
[Dependency] private readonly IOverlayManager _overlay = default!;
[Dependency] private readonly ParallaxSystem _parallax = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
+ [Dependency] private readonly SharedMapSystem _map = default!;
[Dependency] private readonly SpriteSystem _sprite = default!;
[Dependency] private readonly WeatherSystem _weather = default!;
public override void Initialize()
{
base.Initialize();
- _overlay.AddOverlay(new StencilOverlay(_parallax, _transform, _sprite, _weather));
+ _overlay.AddOverlay(new StencilOverlay(_parallax, _transform, _map, _sprite, _weather));
}
public override void Shutdown()
diff --git a/Content.Client/Salvage/UI/SalvageMagnetBoundUserInterface.cs b/Content.Client/Salvage/UI/SalvageMagnetBoundUserInterface.cs
index d691f9acef3..64fc975b98c 100644
--- a/Content.Client/Salvage/UI/SalvageMagnetBoundUserInterface.cs
+++ b/Content.Client/Salvage/UI/SalvageMagnetBoundUserInterface.cs
@@ -1,7 +1,9 @@
using System.Linq;
using Content.Client.Message;
+using Content.Shared.DeltaV.Salvage.Systems; // DeltaV
using Content.Shared.Salvage;
using Content.Shared.Salvage.Magnet;
+using Robust.Client.Player; // DeltaV
using Robust.Client.UserInterface;
using Robust.Client.UserInterface.Controls;
@@ -10,12 +12,16 @@ namespace Content.Client.Salvage.UI;
public sealed class SalvageMagnetBoundUserInterface : BoundUserInterface
{
[Dependency] private readonly IEntityManager _entManager = default!;
+ [Dependency] private readonly IPlayerManager _player = default!; // DeltaV
+
+ private readonly MiningPointsSystem _points; // DeltaV
private OfferingWindow? _window;
public SalvageMagnetBoundUserInterface(EntityUid owner, Enum uiKey) : base(owner, uiKey)
{
IoCManager.InjectDependencies(this);
+ _points = _entManager.System(); // DeltaV
}
protected override void Open()
@@ -61,6 +67,21 @@ protected override void UpdateState(BoundUserInterfaceState state)
});
};
+ // Begin DeltaV Additions: Mining points cost for wrecks
+ if (offer.Cost > 0)
+ {
+ if (_player.LocalSession?.AttachedEntity is not {} user || !_points.UserHasPoints(user, offer.Cost))
+ option.Disabled = true;
+
+ var label = new Label
+ {
+ Text = Loc.GetString("salvage-magnet-mining-points-cost", ("points", offer.Cost)),
+ HorizontalAlignment = Control.HAlignment.Center
+ };
+ option.AddContent(label);
+ }
+ // End DeltaV Additions
+
switch (offer)
{
case AsteroidOffering asteroid:
diff --git a/Content.Client/Sandbox/SandboxSystem.cs b/Content.Client/Sandbox/SandboxSystem.cs
index 8a4c93fa354..abc717642c8 100644
--- a/Content.Client/Sandbox/SandboxSystem.cs
+++ b/Content.Client/Sandbox/SandboxSystem.cs
@@ -110,7 +110,7 @@ public bool Copy(ICommonSession? session, EntityCoordinates coords, EntityUid ui
}
// Try copy tile.
-
+
if (!_map.TryFindGridAt(_transform.ToMapCoordinates(coords), out var gridUid, out var grid) || !_mapSystem.TryGetTileRef(gridUid, grid, coords, out var tileRef))
return false;
@@ -157,10 +157,5 @@ public void ShowBb()
{
_consoleHost.ExecuteCommand("physics shapes");
}
-
- public void MachineLinking()
- {
- _consoleHost.ExecuteCommand("signallink");
- }
}
}
diff --git a/Content.Client/Shuttles/UI/BaseShuttleControl.xaml.cs b/Content.Client/Shuttles/UI/BaseShuttleControl.xaml.cs
index b50d8fa6b21..a5411005391 100644
--- a/Content.Client/Shuttles/UI/BaseShuttleControl.xaml.cs
+++ b/Content.Client/Shuttles/UI/BaseShuttleControl.xaml.cs
@@ -116,7 +116,7 @@ protected void DrawCircles(DrawingHandleScreen handle)
}
}
- protected void DrawGrid(DrawingHandleScreen handle, Matrix3x2 matrix, Entity grid, Color color, float alpha = 0.01f)
+ protected void DrawGrid(DrawingHandleScreen handle, Matrix3x2 gridToView, Entity grid, Color color, float alpha = 0.01f)
{
var rator = Maps.GetAllTilesEnumerator(grid.Owner, grid.Comp);
var minimapScale = MinimapScale;
@@ -264,7 +264,7 @@ protected void DrawGrid(DrawingHandleScreen handle, Matrix3x2 matrix, Entity
private EntityCoordinates? _coordinates;
+ ///
+ /// Entity of controlling console
+ ///
+ private EntityUid? _consoleEntity;
+
private Angle? _rotation;
private Dictionary> _docks = new();
@@ -57,6 +62,11 @@ public void SetMatrix(EntityCoordinates? coordinates, Angle? angle)
_rotation = angle;
}
+ public void SetConsole(EntityUid? consoleEntity)
+ {
+ _consoleEntity = consoleEntity;
+ }
+
protected override void KeyBindUp(GUIBoundKeyEventArgs args)
{
base.KeyBindUp(args);
@@ -139,40 +149,35 @@ protected override void Draw(DrawingHandleScreen handle)
}
var mapPos = _transform.ToMapCoordinates(_coordinates.Value);
- var offset = _coordinates.Value.Position;
- var posMatrix = Matrix3Helpers.CreateTransform(offset, _rotation.Value);
+ var posMatrix = Matrix3Helpers.CreateTransform(_coordinates.Value.Position, _rotation.Value);
var ourEntRot = RotateWithEntity ? _transform.GetWorldRotation(xform) : _rotation.Value;
var ourEntMatrix = Matrix3Helpers.CreateTransform(_transform.GetWorldPosition(xform), ourEntRot);
- var ourWorldMatrix = Matrix3x2.Multiply(posMatrix, ourEntMatrix);
- Matrix3x2.Invert(ourWorldMatrix, out var ourWorldMatrixInvert);
+ var shuttleToWorld = Matrix3x2.Multiply(posMatrix, ourEntMatrix);
+ Matrix3x2.Invert(shuttleToWorld, out var worldToShuttle);
+ var shuttleToView = Matrix3x2.CreateScale(new Vector2(MinimapScale, -MinimapScale)) * Matrix3x2.CreateTranslation(MidPointVector);
// Draw our grid in detail
var ourGridId = xform.GridUid;
if (EntManager.TryGetComponent(ourGridId, out var ourGrid) &&
fixturesQuery.HasComponent(ourGridId.Value))
{
- var ourGridMatrix = _transform.GetWorldMatrix(ourGridId.Value);
- var matrix = Matrix3x2.Multiply(ourGridMatrix, ourWorldMatrixInvert);
+ var ourGridToWorld = _transform.GetWorldMatrix(ourGridId.Value);
+ var ourGridToShuttle = Matrix3x2.Multiply(ourGridToWorld, worldToShuttle);
+ var ourGridToView = ourGridToShuttle * shuttleToView;
var color = _shuttles.GetIFFColor(ourGridId.Value, self: true);
- DrawGrid(handle, matrix, (ourGridId.Value, ourGrid), color);
- DrawDocks(handle, ourGridId.Value, matrix);
+ DrawGrid(handle, ourGridToView, (ourGridId.Value, ourGrid), color);
+ DrawDocks(handle, ourGridId.Value, ourGridToView);
}
- var invertedPosition = _coordinates.Value.Position - offset;
- invertedPosition.Y = -invertedPosition.Y;
- // Don't need to transform the InvWorldMatrix again as it's already offset to its position.
-
// Draw radar position on the station
- var radarPos = invertedPosition;
const float radarVertRadius = 2f;
-
var radarPosVerts = new Vector2[]
{
- ScalePosition(radarPos + new Vector2(0f, -radarVertRadius)),
- ScalePosition(radarPos + new Vector2(radarVertRadius / 2f, 0f)),
- ScalePosition(radarPos + new Vector2(0f, radarVertRadius)),
- ScalePosition(radarPos + new Vector2(radarVertRadius / -2f, 0f)),
+ ScalePosition(new Vector2(0f, -radarVertRadius)),
+ ScalePosition(new Vector2(radarVertRadius / 2f, 0f)),
+ ScalePosition(new Vector2(0f, radarVertRadius)),
+ ScalePosition(new Vector2(radarVertRadius / -2f, 0f)),
};
handle.DrawPrimitives(DrawPrimitiveTopology.TriangleFan, radarPosVerts, Color.Lime);
@@ -197,8 +202,8 @@ protected override void Draw(DrawingHandleScreen handle)
if (!_shuttles.CanDraw(gUid, gridBody, iff))
continue;
- var gridMatrix = _transform.GetWorldMatrix(gUid);
- var matty = Matrix3x2.Multiply(gridMatrix, ourWorldMatrixInvert);
+ var curGridToWorld = _transform.GetWorldMatrix(gUid);
+ var curGridToView = curGridToWorld * worldToShuttle * shuttleToView;
var labelColor = _shuttles.GetIFFColor(grid, self: false, iff);
var coordColor = new Color(labelColor.R * 0.8f, labelColor.G * 0.8f, labelColor.B * 0.8f, 0.5f);
@@ -213,8 +218,7 @@ protected override void Draw(DrawingHandleScreen handle)
{
var gridBounds = grid.Comp.LocalAABB;
- var gridCentre = Vector2.Transform(gridBody.LocalCenter, matty);
- gridCentre.Y = -gridCentre.Y;
+ var gridCentre = Vector2.Transform(gridBody.LocalCenter, curGridToView);
var distance = gridCentre.Length();
var labelText = Loc.GetString("shuttle-console-iff-label", ("name", labelName),
@@ -230,9 +234,8 @@ protected override void Draw(DrawingHandleScreen handle)
// y-offset the control to always render below the grid (vertically)
var yOffset = Math.Max(gridBounds.Height, gridBounds.Width) * MinimapScale / 1.8f;
- // The actual position in the UI. We centre the label by offsetting the matrix position
- // by half the label's width, plus the y-offset
- var gridScaledPosition = ScalePosition(gridCentre) - new Vector2(0, -yOffset);
+ // The actual position in the UI.
+ var gridScaledPosition = gridCentre - new Vector2(0, -yOffset);
// Normalize the grid position if it exceeds the viewport bounds
// normalizing it instead of clamping it preserves the direction of the vector and prevents corner-hugging
@@ -264,18 +267,32 @@ protected override void Draw(DrawingHandleScreen handle)
}
// Detailed view
- var gridAABB = gridMatrix.TransformBox(grid.Comp.LocalAABB);
+ var gridAABB = curGridToWorld.TransformBox(grid.Comp.LocalAABB);
// Skip drawing if it's out of range.
if (!gridAABB.Intersects(viewAABB))
continue;
- DrawGrid(handle, matty, grid, labelColor);
- DrawDocks(handle, gUid, matty);
+ DrawGrid(handle, curGridToView, grid, labelColor);
+ DrawDocks(handle, gUid, curGridToView);
}
+
+ // If we've set the controlling console, and it's on a different grid
+ // to the shuttle itself, then draw an additional marker to help the
+ // player determine where they are relative to the shuttle.
+ if (_consoleEntity != null && xformQuery.TryGetComponent(_consoleEntity, out var consoleXform))
+ {
+ if (consoleXform.ParentUid != _coordinates.Value.EntityId)
+ {
+ var consolePositionWorld = _transform.GetWorldPosition((EntityUid)_consoleEntity);
+ var p = Vector2.Transform(consolePositionWorld, worldToShuttle * shuttleToView);
+ handle.DrawCircle(p, 5, Color.ToSrgb(Color.Cyan), true);
+ }
+ }
+
}
- private void DrawDocks(DrawingHandleScreen handle, EntityUid uid, Matrix3x2 matrix)
+ private void DrawDocks(DrawingHandleScreen handle, EntityUid uid, Matrix3x2 gridToView)
{
if (!ShowDocks)
return;
@@ -283,33 +300,32 @@ private void DrawDocks(DrawingHandleScreen handle, EntityUid uid, Matrix3x2 matr
const float DockScale = 0.6f;
var nent = EntManager.GetNetEntity(uid);
+ const float sqrt2 = 1.41421356f;
+ const float dockRadius = DockScale * sqrt2;
+ // Worst-case bounds used to cull a dock:
+ Box2 viewBounds = new Box2(-dockRadius, -dockRadius, Size.X + dockRadius, Size.Y + dockRadius);
if (_docks.TryGetValue(nent, out var docks))
{
foreach (var state in docks)
{
var position = state.Coordinates.Position;
- var uiPosition = Vector2.Transform(position, matrix);
- if (uiPosition.Length() > (WorldRange * 2f) - DockScale)
+ var positionInView = Vector2.Transform(position, gridToView);
+ if (!viewBounds.Contains(positionInView))
+ {
continue;
+ }
var color = Color.ToSrgb(Color.Magenta);
var verts = new[]
{
- Vector2.Transform(position + new Vector2(-DockScale, -DockScale), matrix),
- Vector2.Transform(position + new Vector2(DockScale, -DockScale), matrix),
- Vector2.Transform(position + new Vector2(DockScale, DockScale), matrix),
- Vector2.Transform(position + new Vector2(-DockScale, DockScale), matrix),
+ Vector2.Transform(position + new Vector2(-DockScale, -DockScale), gridToView),
+ Vector2.Transform(position + new Vector2(DockScale, -DockScale), gridToView),
+ Vector2.Transform(position + new Vector2(DockScale, DockScale), gridToView),
+ Vector2.Transform(position + new Vector2(-DockScale, DockScale), gridToView),
};
- for (var i = 0; i < verts.Length; i++)
- {
- var vert = verts[i];
- vert.Y = -vert.Y;
- verts[i] = ScalePosition(vert);
- }
-
handle.DrawPrimitives(DrawPrimitiveTopology.TriangleFan, verts, color.WithAlpha(0.8f));
handle.DrawPrimitives(DrawPrimitiveTopology.LineStrip, verts, color);
}
diff --git a/Content.Client/Silicons/Laws/Ui/LawDisplay.xaml.cs b/Content.Client/Silicons/Laws/Ui/LawDisplay.xaml.cs
index 4e412df8581..bb0dba2f57d 100644
--- a/Content.Client/Silicons/Laws/Ui/LawDisplay.xaml.cs
+++ b/Content.Client/Silicons/Laws/Ui/LawDisplay.xaml.cs
@@ -9,6 +9,7 @@
using Robust.Client.UserInterface.Controls;
using Robust.Client.UserInterface.XAML;
using Robust.Shared.Prototypes;
+using Robust.Shared.Timing;
using Robust.Shared.Utility;
namespace Content.Client.Silicons.Laws.Ui;
@@ -18,8 +19,13 @@ public sealed partial class LawDisplay : Control
{
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
[Dependency] private readonly IChatManager _chatManager = default!;
+ [Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly EntityManager _entityManager = default!;
+ private static readonly TimeSpan PressCooldown = TimeSpan.FromSeconds(3);
+
+ private readonly Dictionary