Merge branch 'master' into air-alarm-fixup
This commit is contained in:
@@ -11,7 +11,7 @@ namespace Content.Client.AME.UI
|
|||||||
{
|
{
|
||||||
private AMEWindow? _window;
|
private AMEWindow? _window;
|
||||||
|
|
||||||
public AMEControllerBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
|
public AMEControllerBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ namespace Content.Client.Access.UI
|
|||||||
{
|
{
|
||||||
private AgentIDCardWindow? _window;
|
private AgentIDCardWindow? _window;
|
||||||
|
|
||||||
public AgentIDCardBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
|
public AgentIDCardBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ namespace Content.Client.Access.UI
|
|||||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||||
|
|
||||||
public IdCardConsoleBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
|
public IdCardConsoleBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,7 +59,7 @@ namespace Content.Client.Access.UI
|
|||||||
_window?.UpdateState(castState);
|
_window?.UpdateState(castState);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SubmitData(string newFullName, string newJobTitle, List<string> newAccessList)
|
public void SubmitData(string newFullName, string newJobTitle, List<string> newAccessList, string newJobPrototype)
|
||||||
{
|
{
|
||||||
if (newFullName.Length > MaxFullNameLength)
|
if (newFullName.Length > MaxFullNameLength)
|
||||||
newFullName = newFullName[..MaxFullNameLength];
|
newFullName = newFullName[..MaxFullNameLength];
|
||||||
@@ -70,7 +70,8 @@ namespace Content.Client.Access.UI
|
|||||||
SendMessage(new WriteToTargetIdMessage(
|
SendMessage(new WriteToTargetIdMessage(
|
||||||
newFullName,
|
newFullName,
|
||||||
newJobTitle,
|
newJobTitle,
|
||||||
newAccessList));
|
newAccessList,
|
||||||
|
newJobPrototype));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,8 +26,10 @@ namespace Content.Client.Access.UI
|
|||||||
|
|
||||||
private string? _lastFullName;
|
private string? _lastFullName;
|
||||||
private string? _lastJobTitle;
|
private string? _lastJobTitle;
|
||||||
|
private string? _lastJobProto;
|
||||||
|
|
||||||
public IdCardConsoleWindow(IdCardConsoleBoundUserInterface owner, IPrototypeManager prototypeManager, List<string> accessLevels)
|
public IdCardConsoleWindow(IdCardConsoleBoundUserInterface owner, IPrototypeManager prototypeManager,
|
||||||
|
List<string> accessLevels)
|
||||||
{
|
{
|
||||||
RobustXamlLoader.Load(this);
|
RobustXamlLoader.Load(this);
|
||||||
IoCManager.InjectDependencies(this);
|
IoCManager.InjectDependencies(this);
|
||||||
@@ -101,6 +103,7 @@ namespace Content.Client.Access.UI
|
|||||||
}
|
}
|
||||||
|
|
||||||
JobTitleLineEdit.Text = Loc.GetString(job.Name);
|
JobTitleLineEdit.Text = Loc.GetString(job.Name);
|
||||||
|
args.Button.SelectId(args.Id);
|
||||||
|
|
||||||
ClearAllAccess();
|
ClearAllAccess();
|
||||||
|
|
||||||
@@ -181,17 +184,29 @@ namespace Content.Client.Access.UI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var jobIndex = _jobPrototypeIds.IndexOf(state.TargetIdJobPrototype);
|
||||||
|
if (jobIndex >= 0)
|
||||||
|
{
|
||||||
|
JobPresetOptionButton.SelectId(jobIndex);
|
||||||
|
}
|
||||||
|
|
||||||
_lastFullName = state.TargetIdFullName;
|
_lastFullName = state.TargetIdFullName;
|
||||||
_lastJobTitle = state.TargetIdJobTitle;
|
_lastJobTitle = state.TargetIdJobTitle;
|
||||||
|
_lastJobProto = state.TargetIdJobPrototype;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SubmitData()
|
private void SubmitData()
|
||||||
{
|
{
|
||||||
|
// Don't send this if it isn't dirty.
|
||||||
|
var jobProtoDirty = _lastJobProto != null &&
|
||||||
|
_jobPrototypeIds[JobPresetOptionButton.SelectedId] != _lastJobProto;
|
||||||
|
|
||||||
_owner.SubmitData(
|
_owner.SubmitData(
|
||||||
FullNameLineEdit.Text,
|
FullNameLineEdit.Text,
|
||||||
JobTitleLineEdit.Text,
|
JobTitleLineEdit.Text,
|
||||||
// Iterate over the buttons dictionary, filter by `Pressed`, only get key from the key/value pair
|
// Iterate over the buttons dictionary, filter by `Pressed`, only get key from the key/value pair
|
||||||
_accessButtons.Where(x => x.Value.Pressed).Select(x => x.Key).ToList());
|
_accessButtons.Where(x => x.Value.Pressed).Select(x => x.Key).ToList(),
|
||||||
|
jobProtoDirty ? _jobPrototypeIds[JobPresetOptionButton.SelectedId] : string.Empty);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ namespace Content.Client.AirlockPainter.UI
|
|||||||
private AirlockPainterWindow? _window;
|
private AirlockPainterWindow? _window;
|
||||||
public List<string> Styles = new();
|
public List<string> Styles = new();
|
||||||
|
|
||||||
public AirlockPainterBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
|
public AirlockPainterBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ namespace Content.Client.Arcade.UI
|
|||||||
{
|
{
|
||||||
private BlockGameMenu? _menu;
|
private BlockGameMenu? _menu;
|
||||||
|
|
||||||
public BlockGameBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
|
public BlockGameBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ namespace Content.Client.Arcade.UI
|
|||||||
|
|
||||||
//public SharedSpaceVillainArcadeComponent SpaceVillainArcade;
|
//public SharedSpaceVillainArcadeComponent SpaceVillainArcade;
|
||||||
|
|
||||||
public SpaceVillainArcadeBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
|
public SpaceVillainArcadeBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey)
|
||||||
{
|
{
|
||||||
SendAction(PlayerAction.RequestData);
|
SendAction(PlayerAction.RequestData);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ namespace Content.Client.Atmos.UI
|
|||||||
{
|
{
|
||||||
public sealed class GasAnalyzerBoundUserInterface : BoundUserInterface
|
public sealed class GasAnalyzerBoundUserInterface : BoundUserInterface
|
||||||
{
|
{
|
||||||
public GasAnalyzerBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
|
public GasAnalyzerBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ namespace Content.Client.Atmos.UI
|
|||||||
|
|
||||||
private GasCanisterWindow? _window;
|
private GasCanisterWindow? _window;
|
||||||
|
|
||||||
public GasCanisterBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
|
public GasCanisterBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ namespace Content.Client.Atmos.UI
|
|||||||
private GasFilterWindow? _window;
|
private GasFilterWindow? _window;
|
||||||
private const float MaxTransferRate = Atmospherics.MaxTransferRate;
|
private const float MaxTransferRate = Atmospherics.MaxTransferRate;
|
||||||
|
|
||||||
public GasFilterBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
|
public GasFilterBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ namespace Content.Client.Atmos.UI
|
|||||||
private GasMixerWindow? _window;
|
private GasMixerWindow? _window;
|
||||||
private const float MaxPressure = Atmospherics.MaxOutputPressure;
|
private const float MaxPressure = Atmospherics.MaxOutputPressure;
|
||||||
|
|
||||||
public GasMixerBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
|
public GasMixerBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ namespace Content.Client.Atmos.UI
|
|||||||
private GasPressurePumpWindow? _window;
|
private GasPressurePumpWindow? _window;
|
||||||
private const float MaxPressure = Atmospherics.MaxOutputPressure;
|
private const float MaxPressure = Atmospherics.MaxOutputPressure;
|
||||||
|
|
||||||
public GasPressurePumpBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
|
public GasPressurePumpBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ namespace Content.Client.Atmos.UI
|
|||||||
private float _minTemp = 0.0f;
|
private float _minTemp = 0.0f;
|
||||||
private float _maxTemp = 0.0f;
|
private float _maxTemp = 0.0f;
|
||||||
|
|
||||||
public GasThermomachineBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
|
public GasThermomachineBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ namespace Content.Client.Atmos.UI
|
|||||||
private GasVolumePumpWindow? _window;
|
private GasVolumePumpWindow? _window;
|
||||||
private const float MaxTransferRate = Atmospherics.MaxTransferRate;
|
private const float MaxTransferRate = Atmospherics.MaxTransferRate;
|
||||||
|
|
||||||
public GasVolumePumpBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
|
public GasVolumePumpBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ using Robust.Shared.Prototypes;
|
|||||||
using Robust.Shared.Random;
|
using Robust.Shared.Random;
|
||||||
using Robust.Shared.Timing;
|
using Robust.Shared.Timing;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using Robust.Client.GameObjects;
|
||||||
|
using Robust.Client.ResourceManagement;
|
||||||
using Timer = Robust.Shared.Timing.Timer;
|
using Timer = Robust.Shared.Timing.Timer;
|
||||||
|
|
||||||
namespace Content.Client.Audio
|
namespace Content.Client.Audio
|
||||||
@@ -44,7 +46,7 @@ namespace Content.Client.Audio
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// What the ambience has been set to.
|
/// What the ambience has been set to.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private SoundCollectionPrototype _currentCollection = default!;
|
private SoundCollectionPrototype? _currentCollection;
|
||||||
private CancellationTokenSource _timerCancelTokenSource = new();
|
private CancellationTokenSource _timerCancelTokenSource = new();
|
||||||
|
|
||||||
private SoundCollectionPrototype _spaceAmbience = default!;
|
private SoundCollectionPrototype _spaceAmbience = default!;
|
||||||
@@ -58,12 +60,22 @@ namespace Content.Client.Audio
|
|||||||
_spaceAmbience = _prototypeManager.Index<SoundCollectionPrototype>("SpaceAmbienceBase");
|
_spaceAmbience = _prototypeManager.Index<SoundCollectionPrototype>("SpaceAmbienceBase");
|
||||||
_currentCollection = _stationAmbience;
|
_currentCollection = _stationAmbience;
|
||||||
|
|
||||||
|
// TOOD: Ideally audio loading streamed better / we have more robust audio but this is quite annoying
|
||||||
|
var cache = IoCManager.Resolve<IResourceCache>();
|
||||||
|
|
||||||
|
foreach (var audio in _spaceAmbience.PickFiles)
|
||||||
|
{
|
||||||
|
cache.GetResource<AudioResource>(audio.ToString());
|
||||||
|
}
|
||||||
|
|
||||||
_configManager.OnValueChanged(CCVars.AmbienceVolume, AmbienceCVarChanged);
|
_configManager.OnValueChanged(CCVars.AmbienceVolume, AmbienceCVarChanged);
|
||||||
_configManager.OnValueChanged(CCVars.LobbyMusicEnabled, LobbyMusicCVarChanged);
|
_configManager.OnValueChanged(CCVars.LobbyMusicEnabled, LobbyMusicCVarChanged);
|
||||||
_configManager.OnValueChanged(CCVars.StationAmbienceEnabled, StationAmbienceCVarChanged);
|
_configManager.OnValueChanged(CCVars.StationAmbienceEnabled, StationAmbienceCVarChanged);
|
||||||
_configManager.OnValueChanged(CCVars.SpaceAmbienceEnabled, SpaceAmbienceCVarChanged);
|
_configManager.OnValueChanged(CCVars.SpaceAmbienceEnabled, SpaceAmbienceCVarChanged);
|
||||||
|
|
||||||
|
SubscribeLocalEvent<PlayerAttachedEvent>(OnPlayerAttached);
|
||||||
SubscribeLocalEvent<EntParentChangedMessage>(EntParentChanged);
|
SubscribeLocalEvent<EntParentChangedMessage>(EntParentChanged);
|
||||||
|
SubscribeLocalEvent<PlayerDetachedEvent>(OnPlayerDetached);
|
||||||
|
|
||||||
_stateManager.OnStateChanged += StateManagerOnStateChanged;
|
_stateManager.OnStateChanged += StateManagerOnStateChanged;
|
||||||
|
|
||||||
@@ -73,10 +85,28 @@ namespace Content.Client.Audio
|
|||||||
_gameTicker.LobbyStatusUpdated += LobbySongReceived;
|
_gameTicker.LobbyStatusUpdated += LobbySongReceived;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnPlayerAttached(PlayerAttachedEvent ev)
|
||||||
|
{
|
||||||
|
if (!TryComp<TransformComponent>(ev.Entity, out var xform))
|
||||||
|
return;
|
||||||
|
|
||||||
|
CheckAmbience(xform);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPlayerDetached(PlayerDetachedEvent ev)
|
||||||
|
{
|
||||||
|
EndAmbience();
|
||||||
|
}
|
||||||
|
|
||||||
public override void Shutdown()
|
public override void Shutdown()
|
||||||
{
|
{
|
||||||
base.Shutdown();
|
base.Shutdown();
|
||||||
|
|
||||||
|
_configManager.UnsubValueChanged(CCVars.AmbienceVolume, AmbienceCVarChanged);
|
||||||
|
_configManager.UnsubValueChanged(CCVars.LobbyMusicEnabled, LobbyMusicCVarChanged);
|
||||||
|
_configManager.UnsubValueChanged(CCVars.StationAmbienceEnabled, StationAmbienceCVarChanged);
|
||||||
|
_configManager.UnsubValueChanged(CCVars.SpaceAmbienceEnabled, SpaceAmbienceCVarChanged);
|
||||||
|
|
||||||
_stateManager.OnStateChanged -= StateManagerOnStateChanged;
|
_stateManager.OnStateChanged -= StateManagerOnStateChanged;
|
||||||
|
|
||||||
_client.PlayerJoinedServer -= OnJoin;
|
_client.PlayerJoinedServer -= OnJoin;
|
||||||
@@ -88,15 +118,12 @@ namespace Content.Client.Audio
|
|||||||
EndLobbyMusic();
|
EndLobbyMusic();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void EntParentChanged(ref EntParentChangedMessage message)
|
private void CheckAmbience(TransformComponent xform)
|
||||||
{
|
{
|
||||||
if(_playMan.LocalPlayer is null || _playMan.LocalPlayer.ControlledEntity != message.Entity ||
|
if (xform.GridUid != null)
|
||||||
!_timing.IsFirstTimePredicted) return;
|
|
||||||
|
|
||||||
// Check if we traversed to grid.
|
|
||||||
if (message.Transform.GridUid != null)
|
|
||||||
{
|
{
|
||||||
if (_currentCollection == _stationAmbience) return;
|
if (_currentCollection == _stationAmbience)
|
||||||
|
return;
|
||||||
ChangeAmbience(_stationAmbience);
|
ChangeAmbience(_stationAmbience);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -105,6 +132,15 @@ namespace Content.Client.Audio
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void EntParentChanged(ref EntParentChangedMessage message)
|
||||||
|
{
|
||||||
|
if(_playMan.LocalPlayer is null || _playMan.LocalPlayer.ControlledEntity != message.Entity ||
|
||||||
|
!_timing.IsFirstTimePredicted) return;
|
||||||
|
|
||||||
|
// Check if we traversed to grid.
|
||||||
|
CheckAmbience(message.Transform);
|
||||||
|
}
|
||||||
|
|
||||||
private void ChangeAmbience(SoundCollectionPrototype newAmbience)
|
private void ChangeAmbience(SoundCollectionPrototype newAmbience)
|
||||||
{
|
{
|
||||||
if (_currentCollection == newAmbience) return;
|
if (_currentCollection == newAmbience) return;
|
||||||
@@ -172,7 +208,8 @@ namespace Content.Client.Audio
|
|||||||
private void StartAmbience()
|
private void StartAmbience()
|
||||||
{
|
{
|
||||||
EndAmbience();
|
EndAmbience();
|
||||||
if (!CanPlayCollection(_currentCollection)) return;
|
if (_currentCollection == null || !CanPlayCollection(_currentCollection))
|
||||||
|
return;
|
||||||
_playingCollection = _currentCollection;
|
_playingCollection = _currentCollection;
|
||||||
var file = _robustRandom.Pick(_currentCollection.PickFiles).ToString();
|
var file = _robustRandom.Pick(_currentCollection.PickFiles).ToString();
|
||||||
_ambientStream = SoundSystem.Play(file, Filter.Local(), _ambientParams.WithVolume(_ambientParams.Volume + _configManager.GetCVar(CCVars.AmbienceVolume)));
|
_ambientStream = SoundSystem.Play(file, Filter.Local(), _ambientParams.WithVolume(_ambientParams.Volume + _configManager.GetCVar(CCVars.AmbienceVolume)));
|
||||||
@@ -197,6 +234,9 @@ namespace Content.Client.Audio
|
|||||||
|
|
||||||
private void StationAmbienceCVarChanged(bool enabled)
|
private void StationAmbienceCVarChanged(bool enabled)
|
||||||
{
|
{
|
||||||
|
if (_currentCollection == null)
|
||||||
|
return;
|
||||||
|
|
||||||
if (enabled && _stateManager.CurrentState is GameScreen && _currentCollection.ID == _stationAmbience.ID)
|
if (enabled && _stateManager.CurrentState is GameScreen && _currentCollection.ID == _stationAmbience.ID)
|
||||||
{
|
{
|
||||||
StartAmbience();
|
StartAmbience();
|
||||||
@@ -209,6 +249,9 @@ namespace Content.Client.Audio
|
|||||||
|
|
||||||
private void SpaceAmbienceCVarChanged(bool enabled)
|
private void SpaceAmbienceCVarChanged(bool enabled)
|
||||||
{
|
{
|
||||||
|
if (_currentCollection == null)
|
||||||
|
return;
|
||||||
|
|
||||||
if (enabled && _stateManager.CurrentState is GameScreen && _currentCollection.ID == _spaceAmbience.ID)
|
if (enabled && _stateManager.CurrentState is GameScreen && _currentCollection.ID == _spaceAmbience.ID)
|
||||||
{
|
{
|
||||||
StartAmbience();
|
StartAmbience();
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ namespace Content.Client.Body.UI
|
|||||||
[ViewVariables]
|
[ViewVariables]
|
||||||
private BodyScannerDisplay? _display;
|
private BodyScannerDisplay? _display;
|
||||||
|
|
||||||
public BodyScannerBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey) { }
|
public BodyScannerBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey) { }
|
||||||
|
|
||||||
protected override void Open()
|
protected override void Open()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ namespace Content.Client.Cargo.BUI
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private CargoProductPrototype? _product;
|
private CargoProductPrototype? _product;
|
||||||
|
|
||||||
public CargoOrderConsoleBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
|
public CargoOrderConsoleBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ public sealed class CargoShuttleConsoleBoundUserInterface : BoundUserInterface
|
|||||||
{
|
{
|
||||||
private CargoShuttleMenu? _menu;
|
private CargoShuttleMenu? _menu;
|
||||||
|
|
||||||
public CargoShuttleConsoleBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey) {}
|
public CargoShuttleConsoleBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey) {}
|
||||||
|
|
||||||
protected override void Open()
|
protected override void Open()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ namespace Content.Client.CharacterAppearance
|
|||||||
{
|
{
|
||||||
private MagicMirrorWindow? _window;
|
private MagicMirrorWindow? _window;
|
||||||
|
|
||||||
public MagicMirrorBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
|
public MagicMirrorBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ namespace Content.Client.Chemistry.UI
|
|||||||
{
|
{
|
||||||
private ChemMasterWindow? _window;
|
private ChemMasterWindow? _window;
|
||||||
|
|
||||||
public ChemMasterBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
|
public ChemMasterBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ namespace Content.Client.Chemistry.UI
|
|||||||
private ReagentDispenserWindow? _window;
|
private ReagentDispenserWindow? _window;
|
||||||
private ReagentDispenserBoundUserInterfaceState? _lastState;
|
private ReagentDispenserBoundUserInterfaceState? _lastState;
|
||||||
|
|
||||||
public ReagentDispenserBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
|
public ReagentDispenserBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ namespace Content.Client.Chemistry.UI
|
|||||||
_window.OpenCentered();
|
_window.OpenCentered();
|
||||||
}
|
}
|
||||||
|
|
||||||
public TransferAmountBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
|
public TransferAmountBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ namespace Content.Client.CloningConsole.UI
|
|||||||
{
|
{
|
||||||
private CloningConsoleWindow? _window;
|
private CloningConsoleWindow? _window;
|
||||||
|
|
||||||
public CloningConsoleBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
|
public CloningConsoleBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ namespace Content.Client.Communications.UI
|
|||||||
public int Countdown => _expectedCountdownTime == null ? 0 : Math.Max((int)_expectedCountdownTime.Value.Subtract(_gameTiming.CurTime).TotalSeconds, 0);
|
public int Countdown => _expectedCountdownTime == null ? 0 : Math.Max((int)_expectedCountdownTime.Value.Subtract(_gameTiming.CurTime).TotalSeconds, 0);
|
||||||
private TimeSpan? _expectedCountdownTime;
|
private TimeSpan? _expectedCountdownTime;
|
||||||
|
|
||||||
public CommunicationsConsoleBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
|
public CommunicationsConsoleBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ namespace Content.Client.Computer
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Alas, this constructor has to be copied to the subclass. :(
|
// Alas, this constructor has to be copied to the subclass. :(
|
||||||
public ComputerBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey) {}
|
public ComputerBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey) {}
|
||||||
|
|
||||||
protected override void UpdateState(BoundUserInterfaceState state)
|
protected override void UpdateState(BoundUserInterfaceState state)
|
||||||
{
|
{
|
||||||
@@ -58,7 +58,7 @@ namespace Content.Client.Computer
|
|||||||
[Virtual]
|
[Virtual]
|
||||||
public class ComputerBoundUserInterfaceBase : BoundUserInterface
|
public class ComputerBoundUserInterfaceBase : BoundUserInterface
|
||||||
{
|
{
|
||||||
public ComputerBoundUserInterfaceBase(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey) {}
|
public ComputerBoundUserInterfaceBase(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey) {}
|
||||||
|
|
||||||
public new void SendMessage(BoundUserInterfaceMessage msg)
|
public new void SendMessage(BoundUserInterfaceMessage msg)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ namespace Content.Client.Configurable.UI
|
|||||||
{
|
{
|
||||||
public Regex? Validation { get; internal set; }
|
public Regex? Validation { get; internal set; }
|
||||||
|
|
||||||
public ConfigurationBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
|
public ConfigurationBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -167,7 +167,7 @@ namespace Content.Client.Construction.UI
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(category) && category != Loc.GetString("construction-presenter-category-all"))
|
if (!string.IsNullOrEmpty(category) && category != Loc.GetString("construction-category-all"))
|
||||||
{
|
{
|
||||||
if (recipe.Category != category)
|
if (recipe.Category != category)
|
||||||
continue;
|
continue;
|
||||||
@@ -191,11 +191,11 @@ namespace Content.Client.Construction.UI
|
|||||||
var uniqueCategories = new HashSet<string>();
|
var uniqueCategories = new HashSet<string>();
|
||||||
|
|
||||||
// hard-coded to show all recipes
|
// hard-coded to show all recipes
|
||||||
uniqueCategories.Add(Loc.GetString("construction-presenter-category-all"));
|
uniqueCategories.Add(Loc.GetString("construction-category-all"));
|
||||||
|
|
||||||
foreach (var prototype in _prototypeManager.EnumeratePrototypes<ConstructionPrototype>())
|
foreach (var prototype in _prototypeManager.EnumeratePrototypes<ConstructionPrototype>())
|
||||||
{
|
{
|
||||||
var category = Loc.GetString(prototype.Category);
|
var category = prototype.Category;
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(category))
|
if (!string.IsNullOrEmpty(category))
|
||||||
uniqueCategories.Add(category);
|
uniqueCategories.Add(category);
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ namespace Content.Client.Crayon.UI
|
|||||||
{
|
{
|
||||||
public sealed class CrayonBoundUserInterface : BoundUserInterface
|
public sealed class CrayonBoundUserInterface : BoundUserInterface
|
||||||
{
|
{
|
||||||
public CrayonBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
|
public CrayonBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -131,11 +131,11 @@ public sealed partial class CrewManifestUi : DefaultWindow
|
|||||||
|
|
||||||
foreach (var entry in entries)
|
foreach (var entry in entries)
|
||||||
{
|
{
|
||||||
var name = new Label()
|
var name = new RichTextLabel()
|
||||||
{
|
{
|
||||||
HorizontalExpand = true,
|
HorizontalExpand = true,
|
||||||
Text = entry.Name
|
|
||||||
};
|
};
|
||||||
|
name.SetMessage(entry.Name);
|
||||||
|
|
||||||
var titleContainer = new BoxContainer()
|
var titleContainer = new BoxContainer()
|
||||||
{
|
{
|
||||||
@@ -143,10 +143,8 @@ public sealed partial class CrewManifestUi : DefaultWindow
|
|||||||
HorizontalExpand = true
|
HorizontalExpand = true
|
||||||
};
|
};
|
||||||
|
|
||||||
var title = new Label()
|
var title = new RichTextLabel();
|
||||||
{
|
title.SetMessage(Loc.GetString(entry.JobTitle));
|
||||||
Text = Loc.GetString(entry.JobTitle)
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
if (rsi != null)
|
if (rsi != null)
|
||||||
|
|||||||
@@ -1,857 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using Content.Shared.Damage;
|
|
||||||
using Content.Shared.Damage.Prototypes;
|
|
||||||
using Content.Shared.FixedPoint;
|
|
||||||
using Robust.Client.GameObjects;
|
|
||||||
using Robust.Shared.GameObjects;
|
|
||||||
using Robust.Shared.IoC;
|
|
||||||
using Robust.Shared.Log;
|
|
||||||
using Robust.Shared.Maths;
|
|
||||||
using Robust.Shared.Prototypes;
|
|
||||||
using Robust.Shared.Reflection;
|
|
||||||
using Robust.Shared.Serialization.Manager.Attributes;
|
|
||||||
using Robust.Shared.Utility;
|
|
||||||
|
|
||||||
// apologies in advance for all the != null checks,
|
|
||||||
// my IDE wouldn't stop complaining about these
|
|
||||||
|
|
||||||
namespace Content.Client.Damage
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// A simple visualizer for any entity with a DamageableComponent
|
|
||||||
/// to display the status of how damaged it is.
|
|
||||||
///
|
|
||||||
/// Can either be an overlay for an entity, or target multiple
|
|
||||||
/// layers on the same entity.
|
|
||||||
///
|
|
||||||
/// This can be disabled dynamically by passing into SetData,
|
|
||||||
/// key DamageVisualizerKeys.Disabled, value bool
|
|
||||||
/// (DamageVisualizerKeys lives in Content.Shared.Damage)
|
|
||||||
///
|
|
||||||
/// Damage layers, if targetting layers, can also be dynamically
|
|
||||||
/// disabled if needed by passing into SetData, the name/enum
|
|
||||||
/// of the sprite layer, and then passing in a bool value
|
|
||||||
/// (true to enable, false to disable).
|
|
||||||
/// </summary>
|
|
||||||
public sealed class DamageVisualizer : AppearanceVisualizer
|
|
||||||
{
|
|
||||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
|
||||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
|
||||||
|
|
||||||
private const string _name = "DamageVisualizer";
|
|
||||||
/// <summary>
|
|
||||||
/// Damage thresholds between damage state changes.
|
|
||||||
///
|
|
||||||
/// If there are any negative thresholds, or there is
|
|
||||||
/// less than one threshold, the visualizer is marked
|
|
||||||
/// as invalid.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// A 'zeroth' threshold is automatically added,
|
|
||||||
/// and this list is automatically sorted for
|
|
||||||
/// efficiency beforehand. As such, the zeroth
|
|
||||||
/// threshold is not required - and negative
|
|
||||||
/// thresholds are automatically caught as
|
|
||||||
/// invalid. The zeroth threshold automatically
|
|
||||||
/// sets all layers to invisible, so a sprite
|
|
||||||
/// isn't required for it.
|
|
||||||
/// </remarks>
|
|
||||||
[DataField("thresholds", required: true)]
|
|
||||||
private List<FixedPoint2> _thresholds = new();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Layers to target, by layerMapKey.
|
|
||||||
/// If a target layer map key is invalid
|
|
||||||
/// (in essence, undefined), then the target
|
|
||||||
/// layer is removed from the list for efficiency.
|
|
||||||
///
|
|
||||||
/// If no layers are valid, then the visualizer
|
|
||||||
/// is marked as invalid.
|
|
||||||
///
|
|
||||||
/// If this is not defined, however, the visualizer
|
|
||||||
/// instead adds an overlay to the sprite.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// Layers can be disabled here by passing
|
|
||||||
/// the layer's name as a key to SetData,
|
|
||||||
/// and passing in a bool set to either 'false'
|
|
||||||
/// to disable it, or 'true' to enable it.
|
|
||||||
/// Setting the layer as disabled will make it
|
|
||||||
/// completely invisible.
|
|
||||||
/// </remarks>
|
|
||||||
[DataField("targetLayers")]
|
|
||||||
private List<Enum>? _targetLayers;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The actual sprites for every damage group
|
|
||||||
/// that the entity should display visually.
|
|
||||||
///
|
|
||||||
/// This is keyed by a damage group identifier
|
|
||||||
/// (for example, Brute), and has a value
|
|
||||||
/// of a DamageVisualizerSprite (see below)
|
|
||||||
/// </summary>
|
|
||||||
[DataField("damageOverlayGroups")]
|
|
||||||
private readonly Dictionary<string, DamageVisualizerSprite>? _damageOverlayGroups;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Sets if you want sprites to overlay the
|
|
||||||
/// entity when damaged, or if you would
|
|
||||||
/// rather have each target layer's state
|
|
||||||
/// replaced by a different state
|
|
||||||
/// within its RSI.
|
|
||||||
///
|
|
||||||
/// This cannot be set to false if:
|
|
||||||
/// - There are no target layers
|
|
||||||
/// - There is no damage group
|
|
||||||
/// </summary>
|
|
||||||
[DataField("overlay")]
|
|
||||||
private readonly bool _overlay = true;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A single damage group to target.
|
|
||||||
/// This should only be defined if
|
|
||||||
/// overlay is set to false.
|
|
||||||
/// If this is defined with damageSprites,
|
|
||||||
/// this will be ignored.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// This is here because otherwise,
|
|
||||||
/// you would need several permutations
|
|
||||||
/// of group sprites depending on
|
|
||||||
/// what kind of damage combination
|
|
||||||
/// you would want, on which threshold.
|
|
||||||
/// </remarks>
|
|
||||||
[DataField("damageGroup")]
|
|
||||||
private readonly string? _damageGroup;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Set this if you want incoming damage to be
|
|
||||||
/// divided.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// This is more useful if you have similar
|
|
||||||
/// damage sprites inbetween entities,
|
|
||||||
/// but with different damage thresholds
|
|
||||||
/// and you want to avoid duplicating
|
|
||||||
/// these sprites.
|
|
||||||
/// </remarks>
|
|
||||||
[DataField("damageDivisor")]
|
|
||||||
private float _divisor = 1;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Set this to track all damage, instead of specific groups.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// This will only work if you have damageOverlay
|
|
||||||
/// defined - otherwise, it will not work.
|
|
||||||
/// </remarks>
|
|
||||||
[DataField("trackAllDamage")]
|
|
||||||
private readonly bool _trackAllDamage = false;
|
|
||||||
/// <summary>
|
|
||||||
/// This is the overlay sprite used, if _trackAllDamage is
|
|
||||||
/// enabled. Supports no complex per-group layering,
|
|
||||||
/// just an actually simple damage overlay. See
|
|
||||||
/// DamageVisualizerSprite for more information.
|
|
||||||
/// </summary>
|
|
||||||
[DataField("damageOverlay")]
|
|
||||||
private readonly DamageVisualizerSprite? _damageOverlay;
|
|
||||||
|
|
||||||
// deals with the edge case of human damage visuals not
|
|
||||||
// being in color without making a Dict<Dict<Dict<Dict<Dict<Dict...
|
|
||||||
[DataDefinition]
|
|
||||||
internal sealed class DamageVisualizerSprite
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// The RSI path for the damage visualizer
|
|
||||||
/// group overlay.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// States in here will require one of four
|
|
||||||
/// forms:
|
|
||||||
///
|
|
||||||
/// If tracking damage groups:
|
|
||||||
/// - {base_state}_{group}_{threshold} if targetting
|
|
||||||
/// a static layer on a sprite (either as an
|
|
||||||
/// overlay or as a state change)
|
|
||||||
/// - DamageOverlay_{group}_{threshold} if not
|
|
||||||
/// targetting a layer on a sprite.
|
|
||||||
///
|
|
||||||
/// If not tracking damage groups:
|
|
||||||
/// - {base_state}_{threshold} if it is targetting
|
|
||||||
/// a layer
|
|
||||||
/// - DamageOverlay_{threshold} if not targetting
|
|
||||||
/// a layer.
|
|
||||||
/// </remarks>
|
|
||||||
[DataField("sprite", required: true)]
|
|
||||||
public readonly string Sprite = default!;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The color of this sprite overlay.
|
|
||||||
/// Supports only hexadecimal format.
|
|
||||||
/// </summary>
|
|
||||||
[DataField("color")]
|
|
||||||
public readonly string? Color;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes an entity to be managed by this appearance controller.
|
|
||||||
/// DO NOT assume this is your only entity. Visualizers are shared.
|
|
||||||
/// </summary>
|
|
||||||
[Obsolete("Subscribe to your component being initialised instead.")]
|
|
||||||
public override void InitializeEntity(EntityUid entity)
|
|
||||||
{
|
|
||||||
base.InitializeEntity(entity);
|
|
||||||
|
|
||||||
IoCManager.InjectDependencies(this);
|
|
||||||
|
|
||||||
var damageData = _entityManager.EnsureComponent<DamageVisualizerDataComponent>(entity);
|
|
||||||
VerifyVisualizerSetup(entity, damageData);
|
|
||||||
if (damageData.Valid)
|
|
||||||
InitializeVisualizer(entity, damageData);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void VerifyVisualizerSetup(EntityUid entity, DamageVisualizerDataComponent damageData)
|
|
||||||
{
|
|
||||||
if (_thresholds.Count < 1)
|
|
||||||
{
|
|
||||||
Logger.ErrorS(_name, $"Thresholds were invalid for entity {entity}. Thresholds: {_thresholds}");
|
|
||||||
damageData.Valid = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_divisor == 0)
|
|
||||||
{
|
|
||||||
Logger.ErrorS(_name, $"Divisor for {entity} is set to zero.");
|
|
||||||
damageData.Valid = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_overlay)
|
|
||||||
{
|
|
||||||
if (_damageOverlayGroups == null && _damageOverlay == null)
|
|
||||||
{
|
|
||||||
Logger.ErrorS(_name, $"Enabled overlay without defined damage overlay sprites on {entity}.");
|
|
||||||
damageData.Valid = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_trackAllDamage && _damageOverlay == null)
|
|
||||||
{
|
|
||||||
Logger.ErrorS(_name, $"Enabled all damage tracking without a damage overlay sprite on {entity}.");
|
|
||||||
damageData.Valid = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!_trackAllDamage && _damageOverlay != null)
|
|
||||||
{
|
|
||||||
Logger.WarningS(_name, $"Disabled all damage tracking with a damage overlay sprite on {entity}.");
|
|
||||||
damageData.Valid = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (_trackAllDamage && _damageOverlayGroups != null)
|
|
||||||
{
|
|
||||||
Logger.WarningS(_name, $"Enabled all damage tracking with damage overlay groups on {entity}.");
|
|
||||||
damageData.Valid = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (!_overlay)
|
|
||||||
{
|
|
||||||
if (_targetLayers == null)
|
|
||||||
{
|
|
||||||
Logger.ErrorS(_name, $"Disabled overlay without target layers on {entity}.");
|
|
||||||
damageData.Valid = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_damageOverlayGroups != null || _damageOverlay != null)
|
|
||||||
{
|
|
||||||
Logger.ErrorS(_name, $"Disabled overlay with defined damage overlay sprites on {entity}.");
|
|
||||||
damageData.Valid = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_damageGroup == null)
|
|
||||||
{
|
|
||||||
Logger.ErrorS(_name, $"Disabled overlay without defined damage group on {entity}.");
|
|
||||||
damageData.Valid = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_damageOverlayGroups != null && _damageGroup != null)
|
|
||||||
{
|
|
||||||
Logger.WarningS(_name, $"Damage overlay sprites and damage group are both defined on {entity}.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_damageOverlay != null && _damageGroup != null)
|
|
||||||
{
|
|
||||||
Logger.WarningS(_name, $"Damage overlay sprites and damage group are both defined on {entity}.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void InitializeVisualizer(EntityUid entity, DamageVisualizerDataComponent damageData)
|
|
||||||
{
|
|
||||||
if (!_entityManager.TryGetComponent(entity, out SpriteComponent? spriteComponent)
|
|
||||||
|| !_entityManager.TryGetComponent<DamageableComponent?>(entity, out var damageComponent)
|
|
||||||
|| !_entityManager.HasComponent<AppearanceComponent>(entity))
|
|
||||||
return;
|
|
||||||
|
|
||||||
_thresholds.Add(FixedPoint2.Zero);
|
|
||||||
_thresholds.Sort();
|
|
||||||
|
|
||||||
if (_thresholds[0] != 0)
|
|
||||||
{
|
|
||||||
Logger.ErrorS(_name, $"Thresholds were invalid for entity {entity}. Thresholds: {_thresholds}");
|
|
||||||
damageData.Valid = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the damage container on our entity's DamageableComponent
|
|
||||||
// is not null, we can try to check through its groups.
|
|
||||||
if (damageComponent.DamageContainerID != null
|
|
||||||
&& _prototypeManager.TryIndex<DamageContainerPrototype>(damageComponent.DamageContainerID, out var damageContainer))
|
|
||||||
{
|
|
||||||
// Are we using damage overlay sprites by group?
|
|
||||||
// Check if the container matches the supported groups,
|
|
||||||
// and start cacheing the last threshold.
|
|
||||||
if (_damageOverlayGroups != null)
|
|
||||||
{
|
|
||||||
foreach (string damageType in _damageOverlayGroups.Keys)
|
|
||||||
{
|
|
||||||
if (!damageContainer.SupportedGroups.Contains(damageType))
|
|
||||||
{
|
|
||||||
Logger.ErrorS(_name, $"Damage key {damageType} was invalid for entity {entity}.");
|
|
||||||
damageData.Valid = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
damageData.LastThresholdPerGroup.Add(damageType, FixedPoint2.Zero);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Are we tracking a single damage group without overlay instead?
|
|
||||||
// See if that group is in our entity's damage container.
|
|
||||||
else if (!_overlay && _damageGroup != null)
|
|
||||||
{
|
|
||||||
if (!damageContainer.SupportedGroups.Contains(_damageGroup))
|
|
||||||
{
|
|
||||||
Logger.ErrorS(_name, $"Damage keys were invalid for entity {entity}.");
|
|
||||||
damageData.Valid = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
damageData.LastThresholdPerGroup.Add(_damageGroup, FixedPoint2.Zero);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Ditto above, but instead we go through every group.
|
|
||||||
else // oh boy! time to enumerate through every single group!
|
|
||||||
{
|
|
||||||
var damagePrototypeIdList = _prototypeManager.EnumeratePrototypes<DamageGroupPrototype>()
|
|
||||||
.Select((p, _) => p.ID)
|
|
||||||
.ToList();
|
|
||||||
if (_damageOverlayGroups != null)
|
|
||||||
foreach (string damageType in _damageOverlayGroups.Keys)
|
|
||||||
{
|
|
||||||
if (!damagePrototypeIdList.Contains(damageType))
|
|
||||||
{
|
|
||||||
Logger.ErrorS(_name, $"Damage keys were invalid for entity {entity}.");
|
|
||||||
damageData.Valid = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
damageData.LastThresholdPerGroup.Add(damageType, FixedPoint2.Zero);
|
|
||||||
}
|
|
||||||
else if (_damageGroup != null)
|
|
||||||
{
|
|
||||||
if (!damagePrototypeIdList.Contains(_damageGroup))
|
|
||||||
{
|
|
||||||
Logger.ErrorS(_name, $"Damage keys were invalid for entity {entity}.");
|
|
||||||
damageData.Valid = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
damageData.LastThresholdPerGroup.Add(_damageGroup, FixedPoint2.Zero);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we're targetting any layers, and the amount of
|
|
||||||
// layers is greater than zero, we start reserving
|
|
||||||
// all the layers needed to track damage groups
|
|
||||||
// on the entity.
|
|
||||||
if (_targetLayers != null && _targetLayers.Count > 0)
|
|
||||||
{
|
|
||||||
// This should ensure that the layers we're targetting
|
|
||||||
// are valid for the visualizer's use.
|
|
||||||
//
|
|
||||||
// If the layer doesn't have a base state, or
|
|
||||||
// the layer key just doesn't exist, we skip it.
|
|
||||||
foreach (var key in _targetLayers)
|
|
||||||
{
|
|
||||||
if (!spriteComponent.LayerMapTryGet(key, out int index)
|
|
||||||
|| spriteComponent.LayerGetState(index).ToString() == null)
|
|
||||||
{
|
|
||||||
Logger.WarningS(_name, $"Layer at key {key} was invalid for entity {entity}.");
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
damageData.TargetLayerMapKeys.Add(key);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Similar to damage overlay groups, if none of the targetted
|
|
||||||
// sprite layers could be used, we display an error and
|
|
||||||
// invalidate the visualizer without crashing.
|
|
||||||
if (damageData.TargetLayerMapKeys.Count == 0)
|
|
||||||
{
|
|
||||||
Logger.ErrorS(_name, $"Target layers were invalid for entity {entity}.");
|
|
||||||
damageData.Valid = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise, we start reserving layers. Since the filtering
|
|
||||||
// loop above ensures that all of these layers are not null,
|
|
||||||
// and have valid state IDs, there should be no issues.
|
|
||||||
foreach (object layer in damageData.TargetLayerMapKeys)
|
|
||||||
{
|
|
||||||
int layerCount = spriteComponent.AllLayers.Count();
|
|
||||||
int index = spriteComponent.LayerMapGet(layer);
|
|
||||||
string layerState = spriteComponent.LayerGetState(index)!.ToString()!;
|
|
||||||
|
|
||||||
if (index + 1 != layerCount)
|
|
||||||
{
|
|
||||||
index += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
damageData.LayerMapKeyStates.Add(layer, layerState);
|
|
||||||
|
|
||||||
// If we're an overlay, and we're targetting groups,
|
|
||||||
// we reserve layers per damage group.
|
|
||||||
if (_overlay && _damageOverlayGroups != null)
|
|
||||||
{
|
|
||||||
foreach (var (group, sprite) in _damageOverlayGroups)
|
|
||||||
{
|
|
||||||
AddDamageLayerToSprite(spriteComponent,
|
|
||||||
sprite,
|
|
||||||
$"{layerState}_{group}_{_thresholds[1]}",
|
|
||||||
$"{layer}{group}",
|
|
||||||
index);
|
|
||||||
}
|
|
||||||
damageData.DisabledLayers.Add(layer, false);
|
|
||||||
}
|
|
||||||
// If we're not targetting groups, and we're still
|
|
||||||
// using an overlay, we instead just add a general
|
|
||||||
// overlay that reflects on how much damage
|
|
||||||
// was taken.
|
|
||||||
else if (_damageOverlay != null)
|
|
||||||
{
|
|
||||||
AddDamageLayerToSprite(spriteComponent,
|
|
||||||
_damageOverlay,
|
|
||||||
$"{layerState}_{_thresholds[1]}",
|
|
||||||
$"{layer}trackDamage",
|
|
||||||
index);
|
|
||||||
damageData.DisabledLayers.Add(layer, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// If we're not targetting layers, however,
|
|
||||||
// we should ensure that we instead
|
|
||||||
// reserve it as an overlay.
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (_damageOverlayGroups != null)
|
|
||||||
{
|
|
||||||
foreach (var (group, sprite) in _damageOverlayGroups)
|
|
||||||
{
|
|
||||||
AddDamageLayerToSprite(spriteComponent,
|
|
||||||
sprite,
|
|
||||||
$"DamageOverlay_{group}_{_thresholds[1]}",
|
|
||||||
$"DamageOverlay{group}");
|
|
||||||
damageData.TopMostLayerKey = $"DamageOverlay{group}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (_damageOverlay != null)
|
|
||||||
{
|
|
||||||
AddDamageLayerToSprite(spriteComponent,
|
|
||||||
_damageOverlay,
|
|
||||||
$"DamageOverlay_{_thresholds[1]}",
|
|
||||||
"DamageOverlay");
|
|
||||||
damageData.TopMostLayerKey = $"DamageOverlay";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Adds a damage tracking layer to a given sprite component.
|
|
||||||
/// </summary>
|
|
||||||
private void AddDamageLayerToSprite(SpriteComponent spriteComponent, DamageVisualizerSprite sprite, string state, string mapKey, int? index = null)
|
|
||||||
{
|
|
||||||
int newLayer = spriteComponent.AddLayer(
|
|
||||||
new SpriteSpecifier.Rsi(
|
|
||||||
new ResourcePath(sprite.Sprite), state
|
|
||||||
), index);
|
|
||||||
spriteComponent.LayerMapSet(mapKey, newLayer);
|
|
||||||
if (sprite.Color != null)
|
|
||||||
spriteComponent.LayerSetColor(newLayer, Color.FromHex(sprite.Color));
|
|
||||||
spriteComponent.LayerSetVisible(newLayer, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Obsolete("Subscribe to AppearanceChangeEvent instead.")]
|
|
||||||
public override void OnChangeData(AppearanceComponent component)
|
|
||||||
{
|
|
||||||
var entities = _entityManager;
|
|
||||||
if (!entities.TryGetComponent(component.Owner, out DamageVisualizerDataComponent? damageData))
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (!damageData.Valid)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// If this was passed into the component, we update
|
|
||||||
// the data to ensure that the current disabled
|
|
||||||
// bool matches.
|
|
||||||
if (component.TryGetData<bool>(DamageVisualizerKeys.Disabled, out var disabledStatus))
|
|
||||||
if (disabledStatus != damageData.Disabled)
|
|
||||||
damageData.Disabled = disabledStatus;
|
|
||||||
|
|
||||||
if (damageData.Disabled)
|
|
||||||
return;
|
|
||||||
|
|
||||||
HandleDamage(component, damageData);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void HandleDamage(AppearanceComponent component, DamageVisualizerDataComponent damageData)
|
|
||||||
{
|
|
||||||
var entities = _entityManager;
|
|
||||||
if (!entities.TryGetComponent(component.Owner, out SpriteComponent? spriteComponent)
|
|
||||||
|| !entities.TryGetComponent(component.Owner, out DamageableComponent? damageComponent))
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (_targetLayers != null && _damageOverlayGroups != null)
|
|
||||||
UpdateDisabledLayers(spriteComponent, component, damageData);
|
|
||||||
|
|
||||||
if (_overlay && _damageOverlayGroups != null && _targetLayers == null)
|
|
||||||
CheckOverlayOrdering(spriteComponent, damageData);
|
|
||||||
|
|
||||||
if (component.TryGetData<bool>(DamageVisualizerKeys.ForceUpdate, out bool update)
|
|
||||||
&& update)
|
|
||||||
{
|
|
||||||
ForceUpdateLayers(damageComponent, spriteComponent, damageData);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_trackAllDamage)
|
|
||||||
{
|
|
||||||
UpdateDamageVisuals(damageComponent, spriteComponent, damageData);
|
|
||||||
}
|
|
||||||
else if (component.TryGetData(DamageVisualizerKeys.DamageUpdateGroups, out DamageVisualizerGroupData data))
|
|
||||||
{
|
|
||||||
UpdateDamageVisuals(data.GroupList, damageComponent, spriteComponent, damageData);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Checks if any layers were disabled in the last
|
|
||||||
/// data update. Disabled layers mean that the
|
|
||||||
/// layer will no longer be visible, or obtain
|
|
||||||
/// any damage updates.
|
|
||||||
/// </summary>
|
|
||||||
private void UpdateDisabledLayers(SpriteComponent spriteComponent, AppearanceComponent component, DamageVisualizerDataComponent damageData)
|
|
||||||
{
|
|
||||||
foreach (var layer in damageData.TargetLayerMapKeys)
|
|
||||||
{
|
|
||||||
bool? layerStatus = null;
|
|
||||||
if (component.TryGetData<bool>(layer, out var layerStateEnum))
|
|
||||||
layerStatus = layerStateEnum;
|
|
||||||
|
|
||||||
if (layerStatus == null)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (damageData.DisabledLayers[layer] != (bool) layerStatus)
|
|
||||||
{
|
|
||||||
damageData.DisabledLayers[layer] = (bool) layerStatus;
|
|
||||||
if (!_trackAllDamage && _damageOverlayGroups != null)
|
|
||||||
foreach (string damageGroup in _damageOverlayGroups!.Keys)
|
|
||||||
spriteComponent.LayerSetVisible($"{layer}{damageGroup}", damageData.DisabledLayers[layer]);
|
|
||||||
else if (_trackAllDamage)
|
|
||||||
spriteComponent.LayerSetVisible($"{layer}trackDamage", damageData.DisabledLayers[layer]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Checks the overlay ordering on the current
|
|
||||||
/// sprite component, compared to the
|
|
||||||
/// data for the visualizer. If the top
|
|
||||||
/// most layer doesn't match, the sprite
|
|
||||||
/// layers are recreated and placed on top.
|
|
||||||
/// </summary>
|
|
||||||
private void CheckOverlayOrdering(SpriteComponent spriteComponent, DamageVisualizerDataComponent damageData)
|
|
||||||
{
|
|
||||||
if (spriteComponent[damageData.TopMostLayerKey] != spriteComponent[spriteComponent.AllLayers.Count() - 1])
|
|
||||||
{
|
|
||||||
if (!_trackAllDamage && _damageOverlayGroups != null)
|
|
||||||
{
|
|
||||||
foreach (var (damageGroup, sprite) in _damageOverlayGroups)
|
|
||||||
{
|
|
||||||
FixedPoint2 threshold = damageData.LastThresholdPerGroup[damageGroup];
|
|
||||||
ReorderOverlaySprite(spriteComponent,
|
|
||||||
damageData,
|
|
||||||
sprite,
|
|
||||||
$"DamageOverlay{damageGroup}",
|
|
||||||
$"DamageOverlay_{damageGroup}",
|
|
||||||
threshold);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (_trackAllDamage && _damageOverlay != null)
|
|
||||||
{
|
|
||||||
ReorderOverlaySprite(spriteComponent,
|
|
||||||
damageData,
|
|
||||||
_damageOverlay,
|
|
||||||
$"DamageOverlay",
|
|
||||||
$"DamageOverlay",
|
|
||||||
damageData.LastDamageThreshold);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ReorderOverlaySprite(SpriteComponent spriteComponent, DamageVisualizerDataComponent damageData, DamageVisualizerSprite sprite, string key, string statePrefix, FixedPoint2 threshold)
|
|
||||||
{
|
|
||||||
spriteComponent.LayerMapTryGet(key, out int spriteLayer);
|
|
||||||
bool visibility = spriteComponent[spriteLayer].Visible;
|
|
||||||
spriteComponent.RemoveLayer(spriteLayer);
|
|
||||||
if (threshold == FixedPoint2.Zero) // these should automatically be invisible
|
|
||||||
threshold = _thresholds[1];
|
|
||||||
spriteLayer = spriteComponent.AddLayer(
|
|
||||||
new SpriteSpecifier.Rsi(
|
|
||||||
new ResourcePath(sprite.Sprite),
|
|
||||||
$"{statePrefix}_{threshold}"
|
|
||||||
),
|
|
||||||
spriteLayer);
|
|
||||||
spriteComponent.LayerMapSet(key, spriteLayer);
|
|
||||||
spriteComponent.LayerSetVisible(spriteLayer, visibility);
|
|
||||||
// this is somewhat iffy since it constantly reallocates
|
|
||||||
damageData.TopMostLayerKey = key;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Updates damage visuals without tracking
|
|
||||||
/// any damage groups.
|
|
||||||
/// </summary>
|
|
||||||
private void UpdateDamageVisuals(DamageableComponent damageComponent, SpriteComponent spriteComponent, DamageVisualizerDataComponent damageData)
|
|
||||||
{
|
|
||||||
if (!CheckThresholdBoundary(damageComponent.TotalDamage, damageData.LastDamageThreshold, out FixedPoint2 threshold))
|
|
||||||
return;
|
|
||||||
|
|
||||||
damageData.LastDamageThreshold = threshold;
|
|
||||||
|
|
||||||
if (_targetLayers != null)
|
|
||||||
{
|
|
||||||
foreach (var layerMapKey in damageData.TargetLayerMapKeys)
|
|
||||||
UpdateTargetLayer(spriteComponent, damageData, layerMapKey, threshold);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
UpdateOverlay(spriteComponent, threshold);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Updates damage visuals by damage group,
|
|
||||||
/// according to the list of damage groups
|
|
||||||
/// passed into it.
|
|
||||||
/// </summary>
|
|
||||||
private void UpdateDamageVisuals(List<string> delta, DamageableComponent damageComponent, SpriteComponent spriteComponent, DamageVisualizerDataComponent damageData)
|
|
||||||
{
|
|
||||||
foreach (var damageGroup in delta)
|
|
||||||
{
|
|
||||||
if (!_overlay && damageGroup != _damageGroup)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (!_prototypeManager.TryIndex<DamageGroupPrototype>(damageGroup, out var damageGroupPrototype)
|
|
||||||
|| !damageComponent.Damage.TryGetDamageInGroup(damageGroupPrototype, out FixedPoint2 damageTotal))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (!damageData.LastThresholdPerGroup.TryGetValue(damageGroup, out FixedPoint2 lastThreshold)
|
|
||||||
|| !CheckThresholdBoundary(damageTotal, lastThreshold, out FixedPoint2 threshold))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
damageData.LastThresholdPerGroup[damageGroup] = threshold;
|
|
||||||
|
|
||||||
if (_targetLayers != null)
|
|
||||||
{
|
|
||||||
foreach (var layerMapKey in damageData.TargetLayerMapKeys)
|
|
||||||
UpdateTargetLayer(spriteComponent, damageData, layerMapKey, damageGroup, threshold);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
UpdateOverlay(spriteComponent, damageGroup, threshold);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Checks if a threshold boundary was passed.
|
|
||||||
/// </summary>
|
|
||||||
private bool CheckThresholdBoundary(FixedPoint2 damageTotal, FixedPoint2 lastThreshold, out FixedPoint2 threshold)
|
|
||||||
{
|
|
||||||
threshold = FixedPoint2.Zero;
|
|
||||||
damageTotal = damageTotal / _divisor;
|
|
||||||
int thresholdIndex = _thresholds.BinarySearch(damageTotal);
|
|
||||||
|
|
||||||
if (thresholdIndex < 0)
|
|
||||||
{
|
|
||||||
thresholdIndex = ~thresholdIndex;
|
|
||||||
threshold = _thresholds[thresholdIndex - 1];
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
threshold = _thresholds[thresholdIndex];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (threshold == lastThreshold)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This is the entry point for
|
|
||||||
/// forcing an update on all damage layers.
|
|
||||||
/// Does different things depending on
|
|
||||||
/// the configuration of the visualizer.
|
|
||||||
/// </summary>
|
|
||||||
private void ForceUpdateLayers(DamageableComponent damageComponent, SpriteComponent spriteComponent, DamageVisualizerDataComponent damageData)
|
|
||||||
{
|
|
||||||
if (_damageOverlayGroups != null)
|
|
||||||
{
|
|
||||||
UpdateDamageVisuals(_damageOverlayGroups.Keys.ToList(), damageComponent, spriteComponent, damageData);
|
|
||||||
}
|
|
||||||
else if (_damageGroup != null)
|
|
||||||
{
|
|
||||||
UpdateDamageVisuals(new List<string>(){ _damageGroup }, damageComponent, spriteComponent, damageData);
|
|
||||||
}
|
|
||||||
else if (_damageOverlay != null)
|
|
||||||
{
|
|
||||||
UpdateDamageVisuals(damageComponent, spriteComponent, damageData);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Updates a target layer. Without a damage group passed in,
|
|
||||||
/// it assumes you're updating a layer that is tracking all
|
|
||||||
/// damage.
|
|
||||||
/// </summary>
|
|
||||||
private void UpdateTargetLayer(SpriteComponent spriteComponent, DamageVisualizerDataComponent damageData, object layerMapKey, FixedPoint2 threshold)
|
|
||||||
{
|
|
||||||
if (_overlay && _damageOverlayGroups != null)
|
|
||||||
{
|
|
||||||
if (!damageData.DisabledLayers[layerMapKey])
|
|
||||||
{
|
|
||||||
string layerState = damageData.LayerMapKeyStates[layerMapKey];
|
|
||||||
spriteComponent.LayerMapTryGet($"{layerMapKey}trackDamage", out int spriteLayer);
|
|
||||||
|
|
||||||
UpdateDamageLayerState(spriteComponent,
|
|
||||||
spriteLayer,
|
|
||||||
$"{layerState}",
|
|
||||||
threshold);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (!_overlay)
|
|
||||||
{
|
|
||||||
string layerState = damageData.LayerMapKeyStates[layerMapKey];
|
|
||||||
spriteComponent.LayerMapTryGet(layerMapKey, out int spriteLayer);
|
|
||||||
|
|
||||||
UpdateDamageLayerState(spriteComponent,
|
|
||||||
spriteLayer,
|
|
||||||
$"{layerState}",
|
|
||||||
threshold);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Updates a target layer by damage group.
|
|
||||||
/// </summary>
|
|
||||||
private void UpdateTargetLayer(SpriteComponent spriteComponent, DamageVisualizerDataComponent damageData, object layerMapKey, string damageGroup, FixedPoint2 threshold)
|
|
||||||
{
|
|
||||||
if (_overlay && _damageOverlayGroups != null)
|
|
||||||
{
|
|
||||||
if (_damageOverlayGroups.ContainsKey(damageGroup) && !damageData.DisabledLayers[layerMapKey])
|
|
||||||
{
|
|
||||||
string layerState = damageData.LayerMapKeyStates[layerMapKey];
|
|
||||||
spriteComponent.LayerMapTryGet($"{layerMapKey}{damageGroup}", out int spriteLayer);
|
|
||||||
|
|
||||||
UpdateDamageLayerState(spriteComponent,
|
|
||||||
spriteLayer,
|
|
||||||
$"{layerState}_{damageGroup}",
|
|
||||||
threshold);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (!_overlay)
|
|
||||||
{
|
|
||||||
string layerState = damageData.LayerMapKeyStates[layerMapKey];
|
|
||||||
spriteComponent.LayerMapTryGet(layerMapKey, out int spriteLayer);
|
|
||||||
|
|
||||||
UpdateDamageLayerState(spriteComponent,
|
|
||||||
spriteLayer,
|
|
||||||
$"{layerState}_{damageGroup}",
|
|
||||||
threshold);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Updates an overlay that is tracking all damage.
|
|
||||||
/// </summary>
|
|
||||||
private void UpdateOverlay(SpriteComponent spriteComponent, FixedPoint2 threshold)
|
|
||||||
{
|
|
||||||
spriteComponent.LayerMapTryGet($"DamageOverlay", out int spriteLayer);
|
|
||||||
|
|
||||||
UpdateDamageLayerState(spriteComponent,
|
|
||||||
spriteLayer,
|
|
||||||
$"DamageOverlay",
|
|
||||||
threshold);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Updates an overlay based on damage group.
|
|
||||||
/// </summary>
|
|
||||||
private void UpdateOverlay(SpriteComponent spriteComponent, string damageGroup, FixedPoint2 threshold)
|
|
||||||
{
|
|
||||||
if (_damageOverlayGroups != null)
|
|
||||||
{
|
|
||||||
if (_damageOverlayGroups.ContainsKey(damageGroup))
|
|
||||||
{
|
|
||||||
spriteComponent.LayerMapTryGet($"DamageOverlay{damageGroup}", out int spriteLayer);
|
|
||||||
|
|
||||||
UpdateDamageLayerState(spriteComponent,
|
|
||||||
spriteLayer,
|
|
||||||
$"DamageOverlay_{damageGroup}",
|
|
||||||
threshold);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Updates a layer on the sprite by what
|
|
||||||
/// prefix it has (calculated by whatever
|
|
||||||
/// function calls it), and what threshold
|
|
||||||
/// was passed into it.
|
|
||||||
/// </summary>
|
|
||||||
private void UpdateDamageLayerState(SpriteComponent spriteComponent, int spriteLayer, string statePrefix, FixedPoint2 threshold)
|
|
||||||
{
|
|
||||||
if (threshold == 0)
|
|
||||||
{
|
|
||||||
spriteComponent.LayerSetVisible(spriteLayer, false);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (!spriteComponent[spriteLayer].Visible)
|
|
||||||
{
|
|
||||||
spriteComponent.LayerSetVisible(spriteLayer, true);
|
|
||||||
}
|
|
||||||
spriteComponent.LayerSetState(spriteLayer, $"{statePrefix}_{threshold}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using Content.Shared.FixedPoint;
|
|
||||||
using Robust.Shared.GameObjects;
|
|
||||||
|
|
||||||
namespace Content.Client.Damage
|
|
||||||
{
|
|
||||||
// Stores all the data for a DamageVisualizer.
|
|
||||||
//
|
|
||||||
// Storing it inside of the AppearanceComponent's data
|
|
||||||
// dictionary was too messy, but at least we can
|
|
||||||
// store it in the entity itself as a separate,
|
|
||||||
// dynamically added component.
|
|
||||||
[RegisterComponent]
|
|
||||||
public sealed class DamageVisualizerDataComponent : Component
|
|
||||||
{
|
|
||||||
public List<Enum> TargetLayerMapKeys = new();
|
|
||||||
public bool Disabled = false;
|
|
||||||
public bool Valid = true;
|
|
||||||
public FixedPoint2 LastDamageThreshold = FixedPoint2.Zero;
|
|
||||||
public Dictionary<object, bool> DisabledLayers = new();
|
|
||||||
public Dictionary<object, string> LayerMapKeyStates = new();
|
|
||||||
public Dictionary<string, FixedPoint2> LastThresholdPerGroup = new();
|
|
||||||
public string TopMostLayerKey = default!;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
161
Content.Client/Damage/DamageVisualsComponent.cs
Normal file
161
Content.Client/Damage/DamageVisualsComponent.cs
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
using Content.Shared.FixedPoint;
|
||||||
|
|
||||||
|
namespace Content.Client.Damage;
|
||||||
|
|
||||||
|
[RegisterComponent]
|
||||||
|
public sealed class DamageVisualsComponent : Component
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Damage thresholds between damage state changes.
|
||||||
|
///
|
||||||
|
/// If there are any negative thresholds, or there is
|
||||||
|
/// less than one threshold, the visualizer is marked
|
||||||
|
/// as invalid.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// A 'zeroth' threshold is automatically added,
|
||||||
|
/// and this list is automatically sorted for
|
||||||
|
/// efficiency beforehand. As such, the zeroth
|
||||||
|
/// threshold is not required - and negative
|
||||||
|
/// thresholds are automatically caught as
|
||||||
|
/// invalid. The zeroth threshold automatically
|
||||||
|
/// sets all layers to invisible, so a sprite
|
||||||
|
/// isn't required for it.
|
||||||
|
/// </remarks>
|
||||||
|
[DataField("thresholds", required: true)]
|
||||||
|
public List<FixedPoint2> Thresholds = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Layers to target, by layerMapKey.
|
||||||
|
/// If a target layer map key is invalid
|
||||||
|
/// (in essence, undefined), then the target
|
||||||
|
/// layer is removed from the list for efficiency.
|
||||||
|
///
|
||||||
|
/// If no layers are valid, then the visualizer
|
||||||
|
/// is marked as invalid.
|
||||||
|
///
|
||||||
|
/// If this is not defined, however, the visualizer
|
||||||
|
/// instead adds an overlay to the sprite.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Layers can be disabled here by passing
|
||||||
|
/// the layer's name as a key to SetData,
|
||||||
|
/// and passing in a bool set to either 'false'
|
||||||
|
/// to disable it, or 'true' to enable it.
|
||||||
|
/// Setting the layer as disabled will make it
|
||||||
|
/// completely invisible.
|
||||||
|
/// </remarks>
|
||||||
|
[DataField("targetLayers")] public List<Enum>? TargetLayers;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The actual sprites for every damage group
|
||||||
|
/// that the entity should display visually.
|
||||||
|
///
|
||||||
|
/// This is keyed by a damage group identifier
|
||||||
|
/// (for example, Brute), and has a value
|
||||||
|
/// of a DamageVisualizerSprite (see below)
|
||||||
|
/// </summary>
|
||||||
|
[DataField("damageOverlayGroups")] public readonly Dictionary<string, DamageVisualizerSprite>? DamageOverlayGroups;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets if you want sprites to overlay the
|
||||||
|
/// entity when damaged, or if you would
|
||||||
|
/// rather have each target layer's state
|
||||||
|
/// replaced by a different state
|
||||||
|
/// within its RSI.
|
||||||
|
///
|
||||||
|
/// This cannot be set to false if:
|
||||||
|
/// - There are no target layers
|
||||||
|
/// - There is no damage group
|
||||||
|
/// </summary>
|
||||||
|
[DataField("overlay")] public readonly bool Overlay = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A single damage group to target.
|
||||||
|
/// This should only be defined if
|
||||||
|
/// overlay is set to false.
|
||||||
|
/// If this is defined with damageSprites,
|
||||||
|
/// this will be ignored.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This is here because otherwise,
|
||||||
|
/// you would need several permutations
|
||||||
|
/// of group sprites depending on
|
||||||
|
/// what kind of damage combination
|
||||||
|
/// you would want, on which threshold.
|
||||||
|
/// </remarks>
|
||||||
|
[DataField("damageGroup")] public readonly string? DamageGroup;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set this if you want incoming damage to be
|
||||||
|
/// divided.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This is more useful if you have similar
|
||||||
|
/// damage sprites in between entities,
|
||||||
|
/// but with different damage thresholds
|
||||||
|
/// and you want to avoid duplicating
|
||||||
|
/// these sprites.
|
||||||
|
/// </remarks>
|
||||||
|
[DataField("damageDivisor")] public float Divisor = 1;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set this to track all damage, instead of specific groups.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This will only work if you have damageOverlay
|
||||||
|
/// defined - otherwise, it will not work.
|
||||||
|
/// </remarks>
|
||||||
|
[DataField("trackAllDamage")] public readonly bool TrackAllDamage;
|
||||||
|
/// <summary>
|
||||||
|
/// This is the overlay sprite used, if _trackAllDamage is
|
||||||
|
/// enabled. Supports no complex per-group layering,
|
||||||
|
/// just an actually simple damage overlay. See
|
||||||
|
/// DamageVisualizerSprite for more information.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("damageOverlay")] public readonly DamageVisualizerSprite? DamageOverlay;
|
||||||
|
|
||||||
|
public readonly List<Enum> TargetLayerMapKeys = new();
|
||||||
|
public bool Disabled = false;
|
||||||
|
public bool Valid = true;
|
||||||
|
public FixedPoint2 LastDamageThreshold = FixedPoint2.Zero;
|
||||||
|
public readonly Dictionary<object, bool> DisabledLayers = new();
|
||||||
|
public readonly Dictionary<object, string> LayerMapKeyStates = new();
|
||||||
|
public readonly Dictionary<string, FixedPoint2> LastThresholdPerGroup = new();
|
||||||
|
public string TopMostLayerKey = default!;
|
||||||
|
}
|
||||||
|
|
||||||
|
// deals with the edge case of human damage visuals not
|
||||||
|
// being in color without making a Dict<Dict<Dict<Dict<Dict<Dict...
|
||||||
|
[DataDefinition]
|
||||||
|
public sealed class DamageVisualizerSprite
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The RSI path for the damage visualizer
|
||||||
|
/// group overlay.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// States in here will require one of four
|
||||||
|
/// forms:
|
||||||
|
///
|
||||||
|
/// If tracking damage groups:
|
||||||
|
/// - {base_state}_{group}_{threshold} if targeting
|
||||||
|
/// a static layer on a sprite (either as an
|
||||||
|
/// overlay or as a state change)
|
||||||
|
/// - DamageOverlay_{group}_{threshold} if not
|
||||||
|
/// targeting a layer on a sprite.
|
||||||
|
///
|
||||||
|
/// If not tracking damage groups:
|
||||||
|
/// - {base_state}_{threshold} if it is targeting
|
||||||
|
/// a layer
|
||||||
|
/// - DamageOverlay_{threshold} if not targeting
|
||||||
|
/// a layer.
|
||||||
|
/// </remarks>
|
||||||
|
[DataField("sprite", required: true)] public readonly string Sprite = default!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The color of this sprite overlay.
|
||||||
|
/// Supports only hexadecimal format.
|
||||||
|
/// </summary>
|
||||||
|
[DataField("color")] public readonly string? Color;
|
||||||
|
}
|
||||||
698
Content.Client/Damage/DamageVisualsSystem.cs
Normal file
698
Content.Client/Damage/DamageVisualsSystem.cs
Normal file
@@ -0,0 +1,698 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using Content.Shared.Damage;
|
||||||
|
using Content.Shared.Damage.Prototypes;
|
||||||
|
using Content.Shared.FixedPoint;
|
||||||
|
using Robust.Client.GameObjects;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
using Robust.Shared.Utility;
|
||||||
|
|
||||||
|
namespace Content.Client.Damage;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A simple visualizer for any entity with a DamageableComponent
|
||||||
|
/// to display the status of how damaged it is.
|
||||||
|
///
|
||||||
|
/// Can either be an overlay for an entity, or target multiple
|
||||||
|
/// layers on the same entity.
|
||||||
|
///
|
||||||
|
/// This can be disabled dynamically by passing into SetData,
|
||||||
|
/// key DamageVisualizerKeys.Disabled, value bool
|
||||||
|
/// (DamageVisualizerKeys lives in Content.Shared.Damage)
|
||||||
|
///
|
||||||
|
/// Damage layers, if targeting layers, can also be dynamically
|
||||||
|
/// disabled if needed by passing into SetData, the name/enum
|
||||||
|
/// of the sprite layer, and then passing in a bool value
|
||||||
|
/// (true to enable, false to disable).
|
||||||
|
/// </summary>
|
||||||
|
public sealed class DamageVisualsSystem : VisualizerSystem<DamageVisualsComponent>
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||||
|
|
||||||
|
private const string SawmillName = "DamageVisuals";
|
||||||
|
|
||||||
|
public override void Initialize()
|
||||||
|
{
|
||||||
|
base.Initialize();
|
||||||
|
|
||||||
|
SubscribeLocalEvent<DamageVisualsComponent, ComponentInit>(InitializeEntity);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeEntity(EntityUid entity, DamageVisualsComponent comp, ComponentInit args)
|
||||||
|
{
|
||||||
|
VerifyVisualizerSetup(entity, comp);
|
||||||
|
|
||||||
|
if (!comp.Valid)
|
||||||
|
{
|
||||||
|
RemCompDeferred<DamageVisualsComponent>(entity);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
InitializeVisualizer(entity, comp);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void VerifyVisualizerSetup(EntityUid entity, DamageVisualsComponent damageVisComp)
|
||||||
|
{
|
||||||
|
if (damageVisComp.Thresholds.Count < 1)
|
||||||
|
{
|
||||||
|
Logger.ErrorS(SawmillName, $"Thresholds were invalid for entity {entity}. Thresholds: {damageVisComp.Thresholds}");
|
||||||
|
damageVisComp.Valid = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (damageVisComp.Divisor == 0)
|
||||||
|
{
|
||||||
|
Logger.ErrorS(SawmillName, $"Divisor for {entity} is set to zero.");
|
||||||
|
damageVisComp.Valid = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (damageVisComp.Overlay)
|
||||||
|
{
|
||||||
|
if (damageVisComp.DamageOverlayGroups == null && damageVisComp.DamageOverlay == null)
|
||||||
|
{
|
||||||
|
Logger.ErrorS(SawmillName, $"Enabled overlay without defined damage overlay sprites on {entity}.");
|
||||||
|
damageVisComp.Valid = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (damageVisComp.TrackAllDamage && damageVisComp.DamageOverlay == null)
|
||||||
|
{
|
||||||
|
Logger.ErrorS(SawmillName, $"Enabled all damage tracking without a damage overlay sprite on {entity}.");
|
||||||
|
damageVisComp.Valid = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!damageVisComp.TrackAllDamage && damageVisComp.DamageOverlay != null)
|
||||||
|
{
|
||||||
|
Logger.WarningS(SawmillName, $"Disabled all damage tracking with a damage overlay sprite on {entity}.");
|
||||||
|
damageVisComp.Valid = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (damageVisComp.TrackAllDamage && damageVisComp.DamageOverlayGroups != null)
|
||||||
|
{
|
||||||
|
Logger.WarningS(SawmillName, $"Enabled all damage tracking with damage overlay groups on {entity}.");
|
||||||
|
damageVisComp.Valid = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (!damageVisComp.Overlay)
|
||||||
|
{
|
||||||
|
if (damageVisComp.TargetLayers == null)
|
||||||
|
{
|
||||||
|
Logger.ErrorS(SawmillName, $"Disabled overlay without target layers on {entity}.");
|
||||||
|
damageVisComp.Valid = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (damageVisComp.DamageOverlayGroups != null || damageVisComp.DamageOverlay != null)
|
||||||
|
{
|
||||||
|
Logger.ErrorS(SawmillName, $"Disabled overlay with defined damage overlay sprites on {entity}.");
|
||||||
|
damageVisComp.Valid = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (damageVisComp.DamageGroup == null)
|
||||||
|
{
|
||||||
|
Logger.ErrorS(SawmillName, $"Disabled overlay without defined damage group on {entity}.");
|
||||||
|
damageVisComp.Valid = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (damageVisComp.DamageOverlayGroups != null && damageVisComp.DamageGroup != null)
|
||||||
|
{
|
||||||
|
Logger.WarningS(SawmillName, $"Damage overlay sprites and damage group are both defined on {entity}.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (damageVisComp.DamageOverlay != null && damageVisComp.DamageGroup != null)
|
||||||
|
{
|
||||||
|
Logger.WarningS(SawmillName, $"Damage overlay sprites and damage group are both defined on {entity}.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeVisualizer(EntityUid entity, DamageVisualsComponent damageVisComp)
|
||||||
|
{
|
||||||
|
if (!TryComp(entity, out SpriteComponent? spriteComponent)
|
||||||
|
|| !TryComp<DamageableComponent?>(entity, out var damageComponent)
|
||||||
|
|| !HasComp<AppearanceComponent>(entity))
|
||||||
|
return;
|
||||||
|
|
||||||
|
damageVisComp.Thresholds.Add(FixedPoint2.Zero);
|
||||||
|
damageVisComp.Thresholds.Sort();
|
||||||
|
|
||||||
|
if (damageVisComp.Thresholds[0] != 0)
|
||||||
|
{
|
||||||
|
Logger.ErrorS(SawmillName, $"Thresholds were invalid for entity {entity}. Thresholds: {damageVisComp.Thresholds}");
|
||||||
|
damageVisComp.Valid = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the damage container on our entity's DamageableComponent
|
||||||
|
// is not null, we can try to check through its groups.
|
||||||
|
if (damageComponent.DamageContainerID != null
|
||||||
|
&& _prototypeManager.TryIndex<DamageContainerPrototype>(damageComponent.DamageContainerID, out var damageContainer))
|
||||||
|
{
|
||||||
|
// Are we using damage overlay sprites by group?
|
||||||
|
// Check if the container matches the supported groups,
|
||||||
|
// and start caching the last threshold.
|
||||||
|
if (damageVisComp.DamageOverlayGroups != null)
|
||||||
|
{
|
||||||
|
foreach (var damageType in damageVisComp.DamageOverlayGroups.Keys)
|
||||||
|
{
|
||||||
|
if (!damageContainer.SupportedGroups.Contains(damageType))
|
||||||
|
{
|
||||||
|
Logger.ErrorS(SawmillName, $"Damage key {damageType} was invalid for entity {entity}.");
|
||||||
|
damageVisComp.Valid = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
damageVisComp.LastThresholdPerGroup.Add(damageType, FixedPoint2.Zero);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Are we tracking a single damage group without overlay instead?
|
||||||
|
// See if that group is in our entity's damage container.
|
||||||
|
else if (!damageVisComp.Overlay && damageVisComp.DamageGroup != null)
|
||||||
|
{
|
||||||
|
if (!damageContainer.SupportedGroups.Contains(damageVisComp.DamageGroup))
|
||||||
|
{
|
||||||
|
Logger.ErrorS(SawmillName, $"Damage keys were invalid for entity {entity}.");
|
||||||
|
damageVisComp.Valid = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
damageVisComp.LastThresholdPerGroup.Add(damageVisComp.DamageGroup, FixedPoint2.Zero);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Ditto above, but instead we go through every group.
|
||||||
|
else // oh boy! time to enumerate through every single group!
|
||||||
|
{
|
||||||
|
var damagePrototypeIdList = _prototypeManager.EnumeratePrototypes<DamageGroupPrototype>()
|
||||||
|
.Select((p, _) => p.ID)
|
||||||
|
.ToList();
|
||||||
|
if (damageVisComp.DamageOverlayGroups != null)
|
||||||
|
{
|
||||||
|
foreach (var damageType in damageVisComp.DamageOverlayGroups.Keys)
|
||||||
|
{
|
||||||
|
if (!damagePrototypeIdList.Contains(damageType))
|
||||||
|
{
|
||||||
|
Logger.ErrorS(SawmillName, $"Damage keys were invalid for entity {entity}.");
|
||||||
|
damageVisComp.Valid = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
damageVisComp.LastThresholdPerGroup.Add(damageType, FixedPoint2.Zero);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (damageVisComp.DamageGroup != null)
|
||||||
|
{
|
||||||
|
if (!damagePrototypeIdList.Contains(damageVisComp.DamageGroup))
|
||||||
|
{
|
||||||
|
Logger.ErrorS(SawmillName, $"Damage keys were invalid for entity {entity}.");
|
||||||
|
damageVisComp.Valid = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
damageVisComp.LastThresholdPerGroup.Add(damageVisComp.DamageGroup, FixedPoint2.Zero);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we're targeting any layers, and the amount of
|
||||||
|
// layers is greater than zero, we start reserving
|
||||||
|
// all the layers needed to track damage groups
|
||||||
|
// on the entity.
|
||||||
|
if (damageVisComp.TargetLayers is { Count: > 0 })
|
||||||
|
{
|
||||||
|
// This should ensure that the layers we're targeting
|
||||||
|
// are valid for the visualizer's use.
|
||||||
|
//
|
||||||
|
// If the layer doesn't have a base state, or
|
||||||
|
// the layer key just doesn't exist, we skip it.
|
||||||
|
foreach (var key in damageVisComp.TargetLayers)
|
||||||
|
{
|
||||||
|
if (!spriteComponent.LayerMapTryGet(key, out var index)
|
||||||
|
|| spriteComponent.LayerGetState(index).ToString() == null)
|
||||||
|
{
|
||||||
|
Logger.WarningS(SawmillName, $"Layer at key {key} was invalid for entity {entity}.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
damageVisComp.TargetLayerMapKeys.Add(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Similar to damage overlay groups, if none of the targeted
|
||||||
|
// sprite layers could be used, we display an error and
|
||||||
|
// invalidate the visualizer without crashing.
|
||||||
|
if (damageVisComp.TargetLayerMapKeys.Count == 0)
|
||||||
|
{
|
||||||
|
Logger.ErrorS(SawmillName, $"Target layers were invalid for entity {entity}.");
|
||||||
|
damageVisComp.Valid = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, we start reserving layers. Since the filtering
|
||||||
|
// loop above ensures that all of these layers are not null,
|
||||||
|
// and have valid state IDs, there should be no issues.
|
||||||
|
foreach (object layer in damageVisComp.TargetLayerMapKeys)
|
||||||
|
{
|
||||||
|
var layerCount = spriteComponent.AllLayers.Count();
|
||||||
|
var index = spriteComponent.LayerMapGet(layer);
|
||||||
|
var layerState = spriteComponent.LayerGetState(index).ToString()!;
|
||||||
|
|
||||||
|
if (index + 1 != layerCount)
|
||||||
|
{
|
||||||
|
index += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
damageVisComp.LayerMapKeyStates.Add(layer, layerState);
|
||||||
|
|
||||||
|
// If we're an overlay, and we're targeting groups,
|
||||||
|
// we reserve layers per damage group.
|
||||||
|
if (damageVisComp.Overlay && damageVisComp.DamageOverlayGroups != null)
|
||||||
|
{
|
||||||
|
foreach (var (group, sprite) in damageVisComp.DamageOverlayGroups)
|
||||||
|
{
|
||||||
|
AddDamageLayerToSprite(spriteComponent,
|
||||||
|
sprite,
|
||||||
|
$"{layerState}_{group}_{damageVisComp.Thresholds[1]}",
|
||||||
|
$"{layer}{group}",
|
||||||
|
index);
|
||||||
|
}
|
||||||
|
damageVisComp.DisabledLayers.Add(layer, false);
|
||||||
|
}
|
||||||
|
// If we're not targeting groups, and we're still
|
||||||
|
// using an overlay, we instead just add a general
|
||||||
|
// overlay that reflects on how much damage
|
||||||
|
// was taken.
|
||||||
|
else if (damageVisComp.DamageOverlay != null)
|
||||||
|
{
|
||||||
|
AddDamageLayerToSprite(spriteComponent,
|
||||||
|
damageVisComp.DamageOverlay,
|
||||||
|
$"{layerState}_{damageVisComp.Thresholds[1]}",
|
||||||
|
$"{layer}trackDamage",
|
||||||
|
index);
|
||||||
|
damageVisComp.DisabledLayers.Add(layer, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// If we're not targeting layers, however,
|
||||||
|
// we should ensure that we instead
|
||||||
|
// reserve it as an overlay.
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (damageVisComp.DamageOverlayGroups != null)
|
||||||
|
{
|
||||||
|
foreach (var (group, sprite) in damageVisComp.DamageOverlayGroups)
|
||||||
|
{
|
||||||
|
AddDamageLayerToSprite(spriteComponent,
|
||||||
|
sprite,
|
||||||
|
$"DamageOverlay_{group}_{damageVisComp.Thresholds[1]}",
|
||||||
|
$"DamageOverlay{group}");
|
||||||
|
damageVisComp.TopMostLayerKey = $"DamageOverlay{group}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (damageVisComp.DamageOverlay != null)
|
||||||
|
{
|
||||||
|
AddDamageLayerToSprite(spriteComponent,
|
||||||
|
damageVisComp.DamageOverlay,
|
||||||
|
$"DamageOverlay_{damageVisComp.Thresholds[1]}",
|
||||||
|
"DamageOverlay");
|
||||||
|
damageVisComp.TopMostLayerKey = $"DamageOverlay";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a damage tracking layer to a given sprite component.
|
||||||
|
/// </summary>
|
||||||
|
private void AddDamageLayerToSprite(SpriteComponent spriteComponent, DamageVisualizerSprite sprite, string state, string mapKey, int? index = null)
|
||||||
|
{
|
||||||
|
var newLayer = spriteComponent.AddLayer(
|
||||||
|
new SpriteSpecifier.Rsi(
|
||||||
|
new ResourcePath(sprite.Sprite), state
|
||||||
|
), index);
|
||||||
|
spriteComponent.LayerMapSet(mapKey, newLayer);
|
||||||
|
if (sprite.Color != null)
|
||||||
|
spriteComponent.LayerSetColor(newLayer, Color.FromHex(sprite.Color));
|
||||||
|
spriteComponent.LayerSetVisible(newLayer, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnAppearanceChange(EntityUid uid, DamageVisualsComponent damageVisComp, ref AppearanceChangeEvent args)
|
||||||
|
{
|
||||||
|
// how is this still here?
|
||||||
|
if (!damageVisComp.Valid)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// If this was passed into the component, we update
|
||||||
|
// the data to ensure that the current disabled
|
||||||
|
// bool matches.
|
||||||
|
if (args.Component.TryGetData<bool>(DamageVisualizerKeys.Disabled, out var disabledStatus))
|
||||||
|
damageVisComp.Disabled = disabledStatus;
|
||||||
|
|
||||||
|
if (damageVisComp.Disabled)
|
||||||
|
return;
|
||||||
|
|
||||||
|
HandleDamage(args.Component, damageVisComp);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleDamage(AppearanceComponent component, DamageVisualsComponent damageVisComp)
|
||||||
|
{
|
||||||
|
if (!TryComp(component.Owner, out SpriteComponent? spriteComponent)
|
||||||
|
|| !TryComp(component.Owner, out DamageableComponent? damageComponent))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (damageVisComp.TargetLayers != null && damageVisComp.DamageOverlayGroups != null)
|
||||||
|
UpdateDisabledLayers(spriteComponent, component, damageVisComp);
|
||||||
|
|
||||||
|
if (damageVisComp.Overlay && damageVisComp.DamageOverlayGroups != null && damageVisComp.TargetLayers == null)
|
||||||
|
CheckOverlayOrdering(spriteComponent, damageVisComp);
|
||||||
|
|
||||||
|
if (component.TryGetData<bool>(DamageVisualizerKeys.ForceUpdate, out var update)
|
||||||
|
&& update)
|
||||||
|
{
|
||||||
|
ForceUpdateLayers(damageComponent, spriteComponent, damageVisComp);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (damageVisComp.TrackAllDamage)
|
||||||
|
{
|
||||||
|
UpdateDamageVisuals(damageComponent, spriteComponent, damageVisComp);
|
||||||
|
}
|
||||||
|
else if (component.TryGetData(DamageVisualizerKeys.DamageUpdateGroups, out DamageVisualizerGroupData data))
|
||||||
|
{
|
||||||
|
UpdateDamageVisuals(data.GroupList, damageComponent, spriteComponent, damageVisComp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if any layers were disabled in the last
|
||||||
|
/// data update. Disabled layers mean that the
|
||||||
|
/// layer will no longer be visible, or obtain
|
||||||
|
/// any damage updates.
|
||||||
|
/// </summary>
|
||||||
|
private void UpdateDisabledLayers(SpriteComponent spriteComponent, AppearanceComponent component, DamageVisualsComponent damageVisComp)
|
||||||
|
{
|
||||||
|
foreach (var layer in damageVisComp.TargetLayerMapKeys)
|
||||||
|
{
|
||||||
|
bool? layerStatus = null;
|
||||||
|
if (component.TryGetData<bool>(layer, out var layerStateEnum))
|
||||||
|
layerStatus = layerStateEnum;
|
||||||
|
|
||||||
|
if (layerStatus == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (damageVisComp.DisabledLayers[layer] != (bool) layerStatus)
|
||||||
|
{
|
||||||
|
damageVisComp.DisabledLayers[layer] = (bool) layerStatus;
|
||||||
|
if (!damageVisComp.TrackAllDamage && damageVisComp.DamageOverlayGroups != null)
|
||||||
|
{
|
||||||
|
foreach (var damageGroup in damageVisComp.DamageOverlayGroups!.Keys)
|
||||||
|
{
|
||||||
|
spriteComponent.LayerSetVisible($"{layer}{damageGroup}", damageVisComp.DisabledLayers[layer]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (damageVisComp.TrackAllDamage)
|
||||||
|
spriteComponent.LayerSetVisible($"{layer}trackDamage", damageVisComp.DisabledLayers[layer]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks the overlay ordering on the current
|
||||||
|
/// sprite component, compared to the
|
||||||
|
/// data for the visualizer. If the top
|
||||||
|
/// most layer doesn't match, the sprite
|
||||||
|
/// layers are recreated and placed on top.
|
||||||
|
/// </summary>
|
||||||
|
private void CheckOverlayOrdering(SpriteComponent spriteComponent, DamageVisualsComponent damageVisComp)
|
||||||
|
{
|
||||||
|
if (spriteComponent[damageVisComp.TopMostLayerKey] != spriteComponent[spriteComponent.AllLayers.Count() - 1])
|
||||||
|
{
|
||||||
|
if (!damageVisComp.TrackAllDamage && damageVisComp.DamageOverlayGroups != null)
|
||||||
|
{
|
||||||
|
foreach (var (damageGroup, sprite) in damageVisComp.DamageOverlayGroups)
|
||||||
|
{
|
||||||
|
var threshold = damageVisComp.LastThresholdPerGroup[damageGroup];
|
||||||
|
ReorderOverlaySprite(spriteComponent,
|
||||||
|
damageVisComp,
|
||||||
|
sprite,
|
||||||
|
$"DamageOverlay{damageGroup}",
|
||||||
|
$"DamageOverlay_{damageGroup}",
|
||||||
|
threshold);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (damageVisComp.TrackAllDamage && damageVisComp.DamageOverlay != null)
|
||||||
|
{
|
||||||
|
ReorderOverlaySprite(spriteComponent,
|
||||||
|
damageVisComp,
|
||||||
|
damageVisComp.DamageOverlay,
|
||||||
|
$"DamageOverlay",
|
||||||
|
$"DamageOverlay",
|
||||||
|
damageVisComp.LastDamageThreshold);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ReorderOverlaySprite(SpriteComponent spriteComponent, DamageVisualsComponent damageVisComp, DamageVisualizerSprite sprite, string key, string statePrefix, FixedPoint2 threshold)
|
||||||
|
{
|
||||||
|
spriteComponent.LayerMapTryGet(key, out var spriteLayer);
|
||||||
|
var visibility = spriteComponent[spriteLayer].Visible;
|
||||||
|
spriteComponent.RemoveLayer(spriteLayer);
|
||||||
|
if (threshold == FixedPoint2.Zero) // these should automatically be invisible
|
||||||
|
threshold = damageVisComp.Thresholds[1];
|
||||||
|
spriteLayer = spriteComponent.AddLayer(
|
||||||
|
new SpriteSpecifier.Rsi(
|
||||||
|
new ResourcePath(sprite.Sprite),
|
||||||
|
$"{statePrefix}_{threshold}"
|
||||||
|
),
|
||||||
|
spriteLayer);
|
||||||
|
spriteComponent.LayerMapSet(key, spriteLayer);
|
||||||
|
spriteComponent.LayerSetVisible(spriteLayer, visibility);
|
||||||
|
// this is somewhat iffy since it constantly reallocates
|
||||||
|
damageVisComp.TopMostLayerKey = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates damage visuals without tracking
|
||||||
|
/// any damage groups.
|
||||||
|
/// </summary>
|
||||||
|
private void UpdateDamageVisuals(DamageableComponent damageComponent, SpriteComponent spriteComponent, DamageVisualsComponent damageVisComp)
|
||||||
|
{
|
||||||
|
if (!CheckThresholdBoundary(damageComponent.TotalDamage, damageVisComp.LastDamageThreshold, damageVisComp, out var threshold))
|
||||||
|
return;
|
||||||
|
|
||||||
|
damageVisComp.LastDamageThreshold = threshold;
|
||||||
|
|
||||||
|
if (damageVisComp.TargetLayers != null)
|
||||||
|
{
|
||||||
|
foreach (var layerMapKey in damageVisComp.TargetLayerMapKeys)
|
||||||
|
{
|
||||||
|
UpdateTargetLayer(spriteComponent, damageVisComp, layerMapKey, threshold);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UpdateOverlay(spriteComponent, threshold);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates damage visuals by damage group,
|
||||||
|
/// according to the list of damage groups
|
||||||
|
/// passed into it.
|
||||||
|
/// </summary>
|
||||||
|
private void UpdateDamageVisuals(List<string> delta, DamageableComponent damageComponent, SpriteComponent spriteComponent, DamageVisualsComponent damageVisComp)
|
||||||
|
{
|
||||||
|
foreach (var damageGroup in delta)
|
||||||
|
{
|
||||||
|
if (!damageVisComp.Overlay && damageGroup != damageVisComp.DamageGroup)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (!_prototypeManager.TryIndex<DamageGroupPrototype>(damageGroup, out var damageGroupPrototype)
|
||||||
|
|| !damageComponent.Damage.TryGetDamageInGroup(damageGroupPrototype, out var damageTotal))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (!damageVisComp.LastThresholdPerGroup.TryGetValue(damageGroup, out var lastThreshold)
|
||||||
|
|| !CheckThresholdBoundary(damageTotal, lastThreshold, damageVisComp, out var threshold))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
damageVisComp.LastThresholdPerGroup[damageGroup] = threshold;
|
||||||
|
|
||||||
|
if (damageVisComp.TargetLayers != null)
|
||||||
|
{
|
||||||
|
foreach (var layerMapKey in damageVisComp.TargetLayerMapKeys)
|
||||||
|
{
|
||||||
|
UpdateTargetLayer(spriteComponent, damageVisComp, layerMapKey, damageGroup, threshold);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UpdateOverlay(spriteComponent, damageVisComp, damageGroup, threshold);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if a threshold boundary was passed.
|
||||||
|
/// </summary>
|
||||||
|
private bool CheckThresholdBoundary(FixedPoint2 damageTotal, FixedPoint2 lastThreshold, DamageVisualsComponent damageVisComp, out FixedPoint2 threshold)
|
||||||
|
{
|
||||||
|
threshold = FixedPoint2.Zero;
|
||||||
|
damageTotal = damageTotal / damageVisComp.Divisor;
|
||||||
|
var thresholdIndex = damageVisComp.Thresholds.BinarySearch(damageTotal);
|
||||||
|
|
||||||
|
if (thresholdIndex < 0)
|
||||||
|
{
|
||||||
|
thresholdIndex = ~thresholdIndex;
|
||||||
|
threshold = damageVisComp.Thresholds[thresholdIndex - 1];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
threshold = damageVisComp.Thresholds[thresholdIndex];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (threshold == lastThreshold)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This is the entry point for
|
||||||
|
/// forcing an update on all damage layers.
|
||||||
|
/// Does different things depending on
|
||||||
|
/// the configuration of the visualizer.
|
||||||
|
/// </summary>
|
||||||
|
private void ForceUpdateLayers(DamageableComponent damageComponent, SpriteComponent spriteComponent, DamageVisualsComponent damageVisComp)
|
||||||
|
{
|
||||||
|
if (damageVisComp.DamageOverlayGroups != null)
|
||||||
|
{
|
||||||
|
UpdateDamageVisuals(damageVisComp.DamageOverlayGroups.Keys.ToList(), damageComponent, spriteComponent, damageVisComp);
|
||||||
|
}
|
||||||
|
else if (damageVisComp.DamageGroup != null)
|
||||||
|
{
|
||||||
|
UpdateDamageVisuals(new List<string>(){ damageVisComp.DamageGroup }, damageComponent, spriteComponent, damageVisComp);
|
||||||
|
}
|
||||||
|
else if (damageVisComp.DamageOverlay != null)
|
||||||
|
{
|
||||||
|
UpdateDamageVisuals(damageComponent, spriteComponent, damageVisComp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates a target layer. Without a damage group passed in,
|
||||||
|
/// it assumes you're updating a layer that is tracking all
|
||||||
|
/// damage.
|
||||||
|
/// </summary>
|
||||||
|
private void UpdateTargetLayer(SpriteComponent spriteComponent, DamageVisualsComponent damageVisComp, object layerMapKey, FixedPoint2 threshold)
|
||||||
|
{
|
||||||
|
if (damageVisComp.Overlay && damageVisComp.DamageOverlayGroups != null)
|
||||||
|
{
|
||||||
|
if (!damageVisComp.DisabledLayers[layerMapKey])
|
||||||
|
{
|
||||||
|
var layerState = damageVisComp.LayerMapKeyStates[layerMapKey];
|
||||||
|
spriteComponent.LayerMapTryGet($"{layerMapKey}trackDamage", out var spriteLayer);
|
||||||
|
|
||||||
|
UpdateDamageLayerState(spriteComponent,
|
||||||
|
spriteLayer,
|
||||||
|
$"{layerState}",
|
||||||
|
threshold);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (!damageVisComp.Overlay)
|
||||||
|
{
|
||||||
|
var layerState = damageVisComp.LayerMapKeyStates[layerMapKey];
|
||||||
|
spriteComponent.LayerMapTryGet(layerMapKey, out var spriteLayer);
|
||||||
|
|
||||||
|
UpdateDamageLayerState(spriteComponent,
|
||||||
|
spriteLayer,
|
||||||
|
$"{layerState}",
|
||||||
|
threshold);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates a target layer by damage group.
|
||||||
|
/// </summary>
|
||||||
|
private void UpdateTargetLayer(SpriteComponent spriteComponent, DamageVisualsComponent damageVisComp, object layerMapKey, string damageGroup, FixedPoint2 threshold)
|
||||||
|
{
|
||||||
|
if (damageVisComp.Overlay && damageVisComp.DamageOverlayGroups != null)
|
||||||
|
{
|
||||||
|
if (damageVisComp.DamageOverlayGroups.ContainsKey(damageGroup) && !damageVisComp.DisabledLayers[layerMapKey])
|
||||||
|
{
|
||||||
|
var layerState = damageVisComp.LayerMapKeyStates[layerMapKey];
|
||||||
|
spriteComponent.LayerMapTryGet($"{layerMapKey}{damageGroup}", out var spriteLayer);
|
||||||
|
|
||||||
|
UpdateDamageLayerState(spriteComponent,
|
||||||
|
spriteLayer,
|
||||||
|
$"{layerState}_{damageGroup}",
|
||||||
|
threshold);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (!damageVisComp.Overlay)
|
||||||
|
{
|
||||||
|
var layerState = damageVisComp.LayerMapKeyStates[layerMapKey];
|
||||||
|
spriteComponent.LayerMapTryGet(layerMapKey, out var spriteLayer);
|
||||||
|
|
||||||
|
UpdateDamageLayerState(spriteComponent,
|
||||||
|
spriteLayer,
|
||||||
|
$"{layerState}_{damageGroup}",
|
||||||
|
threshold);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates an overlay that is tracking all damage.
|
||||||
|
/// </summary>
|
||||||
|
private void UpdateOverlay(SpriteComponent spriteComponent, FixedPoint2 threshold)
|
||||||
|
{
|
||||||
|
spriteComponent.LayerMapTryGet($"DamageOverlay", out var spriteLayer);
|
||||||
|
|
||||||
|
UpdateDamageLayerState(spriteComponent,
|
||||||
|
spriteLayer,
|
||||||
|
$"DamageOverlay",
|
||||||
|
threshold);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates an overlay based on damage group.
|
||||||
|
/// </summary>
|
||||||
|
private void UpdateOverlay(SpriteComponent spriteComponent, DamageVisualsComponent damageVisComp, string damageGroup, FixedPoint2 threshold)
|
||||||
|
{
|
||||||
|
if (damageVisComp.DamageOverlayGroups != null)
|
||||||
|
{
|
||||||
|
if (damageVisComp.DamageOverlayGroups.ContainsKey(damageGroup))
|
||||||
|
{
|
||||||
|
spriteComponent.LayerMapTryGet($"DamageOverlay{damageGroup}", out var spriteLayer);
|
||||||
|
|
||||||
|
UpdateDamageLayerState(spriteComponent,
|
||||||
|
spriteLayer,
|
||||||
|
$"DamageOverlay_{damageGroup}",
|
||||||
|
threshold);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates a layer on the sprite by what
|
||||||
|
/// prefix it has (calculated by whatever
|
||||||
|
/// function calls it), and what threshold
|
||||||
|
/// was passed into it.
|
||||||
|
/// </summary>
|
||||||
|
private void UpdateDamageLayerState(SpriteComponent spriteComponent, int spriteLayer, string statePrefix, FixedPoint2 threshold)
|
||||||
|
{
|
||||||
|
if (threshold == 0)
|
||||||
|
{
|
||||||
|
spriteComponent.LayerSetVisible(spriteLayer, false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!spriteComponent[spriteLayer].Visible)
|
||||||
|
{
|
||||||
|
spriteComponent.LayerSetVisible(spriteLayer, true);
|
||||||
|
}
|
||||||
|
spriteComponent.LayerSetState(spriteLayer, $"{statePrefix}_{threshold}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,7 +13,7 @@ namespace Content.Client.Disposal.UI
|
|||||||
{
|
{
|
||||||
private DisposalRouterWindow? _window;
|
private DisposalRouterWindow? _window;
|
||||||
|
|
||||||
public DisposalRouterBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
|
public DisposalRouterBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ namespace Content.Client.Disposal.UI
|
|||||||
{
|
{
|
||||||
private DisposalTaggerWindow? _window;
|
private DisposalTaggerWindow? _window;
|
||||||
|
|
||||||
public DisposalTaggerBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
|
public DisposalTaggerBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ namespace Content.Client.Disposal.UI
|
|||||||
public MailingUnitWindow? MailingUnitWindow;
|
public MailingUnitWindow? MailingUnitWindow;
|
||||||
public DisposalUnitWindow? DisposalUnitWindow;
|
public DisposalUnitWindow? DisposalUnitWindow;
|
||||||
|
|
||||||
public DisposalUnitBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
|
public DisposalUnitBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ namespace Content.Client.Doors
|
|||||||
{
|
{
|
||||||
var flickMaintenancePanel = new AnimationTrackSpriteFlick();
|
var flickMaintenancePanel = new AnimationTrackSpriteFlick();
|
||||||
CloseAnimation.AnimationTracks.Add(flickMaintenancePanel);
|
CloseAnimation.AnimationTracks.Add(flickMaintenancePanel);
|
||||||
flickMaintenancePanel.LayerKey = WiresVisualizer.WiresVisualLayers.MaintenancePanel;
|
flickMaintenancePanel.LayerKey = WiresVisualLayers.MaintenancePanel;
|
||||||
flickMaintenancePanel.KeyFrames.Add(new AnimationTrackSpriteFlick.KeyFrame("panel_closing", 0f));
|
flickMaintenancePanel.KeyFrames.Add(new AnimationTrackSpriteFlick.KeyFrame("panel_closing", 0f));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -109,7 +109,7 @@ namespace Content.Client.Doors
|
|||||||
{
|
{
|
||||||
var flickMaintenancePanel = new AnimationTrackSpriteFlick();
|
var flickMaintenancePanel = new AnimationTrackSpriteFlick();
|
||||||
OpenAnimation.AnimationTracks.Add(flickMaintenancePanel);
|
OpenAnimation.AnimationTracks.Add(flickMaintenancePanel);
|
||||||
flickMaintenancePanel.LayerKey = WiresVisualizer.WiresVisualLayers.MaintenancePanel;
|
flickMaintenancePanel.LayerKey = WiresVisualLayers.MaintenancePanel;
|
||||||
flickMaintenancePanel.KeyFrames.Add(new AnimationTrackSpriteFlick.KeyFrame("panel_opening", 0f));
|
flickMaintenancePanel.KeyFrames.Add(new AnimationTrackSpriteFlick.KeyFrame("panel_opening", 0f));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,9 +27,9 @@ public sealed class DoorSystem : SharedDoorSystem
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TODO AUDIO PREDICT see comments in server-side PlaySound()
|
// TODO AUDIO PREDICT see comments in server-side PlaySound()
|
||||||
protected override void PlaySound(EntityUid uid, string sound, AudioParams audioParams, EntityUid? predictingPlayer, bool predicted)
|
protected override void PlaySound(EntityUid uid, SoundSpecifier soundSpecifier, AudioParams audioParams, EntityUid? predictingPlayer, bool predicted)
|
||||||
{
|
{
|
||||||
if (GameTiming.InPrediction && GameTiming.IsFirstTimePredicted)
|
if (GameTiming.InPrediction && GameTiming.IsFirstTimePredicted)
|
||||||
SoundSystem.Play(sound, Filter.Local(), uid, audioParams);
|
Audio.Play(soundSpecifier, Filter.Local(), uid, audioParams);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
using System;
|
|
||||||
using Content.Client.Administration.Managers;
|
using Content.Client.Administration.Managers;
|
||||||
using Content.Client.Changelog;
|
using Content.Client.Changelog;
|
||||||
using Content.Client.CharacterInterface;
|
using Content.Client.CharacterInterface;
|
||||||
using Content.Client.Chat.Managers;
|
using Content.Client.Chat.Managers;
|
||||||
using Content.Client.EscapeMenu;
|
using Content.Client.EscapeMenu;
|
||||||
using Content.Client.Eui;
|
using Content.Client.Eui;
|
||||||
using Content.Client.Eye.Blinding;
|
|
||||||
using Content.Client.Flash;
|
using Content.Client.Flash;
|
||||||
using Content.Client.GhostKick;
|
using Content.Client.GhostKick;
|
||||||
using Content.Client.HUD;
|
using Content.Client.HUD;
|
||||||
@@ -14,40 +12,33 @@ using Content.Client.Input;
|
|||||||
using Content.Client.IoC;
|
using Content.Client.IoC;
|
||||||
using Content.Client.Launcher;
|
using Content.Client.Launcher;
|
||||||
using Content.Client.MainMenu;
|
using Content.Client.MainMenu;
|
||||||
using Content.Client.MobState.Overlays;
|
|
||||||
using Content.Client.Parallax;
|
|
||||||
using Content.Client.Parallax.Managers;
|
using Content.Client.Parallax.Managers;
|
||||||
using Content.Client.Players.PlayTimeTracking;
|
using Content.Client.Players.PlayTimeTracking;
|
||||||
using Content.Client.Preferences;
|
using Content.Client.Preferences;
|
||||||
using Content.Client.Radiation;
|
using Content.Client.Radiation;
|
||||||
using Content.Client.Sandbox;
|
|
||||||
using Content.Client.Screenshot;
|
using Content.Client.Screenshot;
|
||||||
using Content.Client.Singularity;
|
using Content.Client.Singularity;
|
||||||
using Content.Client.Stylesheets;
|
using Content.Client.Stylesheets;
|
||||||
using Content.Client.Viewport;
|
using Content.Client.Viewport;
|
||||||
using Content.Client.Voting;
|
using Content.Client.Voting;
|
||||||
using Content.Shared.Actions;
|
|
||||||
using Content.Shared.Administration;
|
using Content.Shared.Administration;
|
||||||
using Content.Shared.Alert;
|
|
||||||
using Content.Shared.AME;
|
using Content.Shared.AME;
|
||||||
using Content.Shared.Cargo.Components;
|
|
||||||
using Content.Shared.Chemistry.Components;
|
using Content.Shared.Chemistry.Components;
|
||||||
using Content.Shared.Chemistry.Dispenser;
|
using Content.Shared.Chemistry.Dispenser;
|
||||||
using Content.Shared.Gravity;
|
using Content.Shared.Gravity;
|
||||||
using Content.Shared.Lathe;
|
using Content.Shared.Lathe;
|
||||||
using Content.Shared.Markers;
|
using Content.Shared.Markers;
|
||||||
using Content.Shared.Research.Components;
|
|
||||||
using Content.Shared.VendingMachines;
|
|
||||||
using Content.Shared.Wires;
|
|
||||||
using Robust.Client;
|
using Robust.Client;
|
||||||
using Robust.Client.Graphics;
|
using Robust.Client.Graphics;
|
||||||
using Robust.Client.Input;
|
using Robust.Client.Input;
|
||||||
using Robust.Client.Player;
|
using Robust.Client.Player;
|
||||||
using Robust.Client.State;
|
using Robust.Client.State;
|
||||||
using Robust.Client.UserInterface;
|
using Robust.Client.UserInterface;
|
||||||
|
#if FULL_RELEASE
|
||||||
|
using Robust.Shared;
|
||||||
|
using Robust.Shared.Configuration;
|
||||||
|
#endif
|
||||||
using Robust.Shared.ContentPack;
|
using Robust.Shared.ContentPack;
|
||||||
using Robust.Shared.GameObjects;
|
|
||||||
using Robust.Shared.IoC;
|
|
||||||
using Robust.Shared.Map;
|
using Robust.Shared.Map;
|
||||||
using Robust.Shared.Prototypes;
|
using Robust.Shared.Prototypes;
|
||||||
using Robust.Shared.Timing;
|
using Robust.Shared.Timing;
|
||||||
@@ -130,6 +121,11 @@ namespace Content.Client.Entry
|
|||||||
|
|
||||||
IoCManager.InjectDependencies(this);
|
IoCManager.InjectDependencies(this);
|
||||||
|
|
||||||
|
#if FULL_RELEASE
|
||||||
|
// if FULL_RELEASE, because otherwise this breaks some integration tests.
|
||||||
|
IoCManager.Resolve<IConfigurationManager>().OverrideDefault(CVars.NetBufferSize, 2);
|
||||||
|
#endif
|
||||||
|
|
||||||
_escapeMenuOwner.Initialize();
|
_escapeMenuOwner.Initialize();
|
||||||
|
|
||||||
_baseClient.PlayerJoinedServer += (_, _) =>
|
_baseClient.PlayerJoinedServer += (_, _) =>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<Label Text="{Loc 'ui-options-net-interp-ratio'}" />
|
<Label Text="{Loc 'ui-options-net-interp-ratio'}" />
|
||||||
<Control MinSize="8 0" />
|
<Control MinSize="8 0" />
|
||||||
<Slider Name="NetInterpRatioSlider"
|
<Slider Name="NetInterpRatioSlider"
|
||||||
MinValue="0"
|
ToolTip="{Loc 'ui-options-net-interp-ratio-tooltip'}"
|
||||||
MaxValue="6"
|
MaxValue="6"
|
||||||
HorizontalExpand="True"
|
HorizontalExpand="True"
|
||||||
MinSize="80 0"
|
MinSize="80 0"
|
||||||
|
|||||||
@@ -1,15 +1,10 @@
|
|||||||
using System;
|
|
||||||
using Content.Shared.CCVar;
|
|
||||||
using Robust.Client.AutoGenerated;
|
using Robust.Client.AutoGenerated;
|
||||||
using Robust.Client.Graphics;
|
|
||||||
using Robust.Client.UserInterface;
|
using Robust.Client.UserInterface;
|
||||||
using Robust.Client.UserInterface.Controls;
|
using Robust.Client.UserInterface.Controls;
|
||||||
using Robust.Client.UserInterface.XAML;
|
using Robust.Client.UserInterface.XAML;
|
||||||
using Robust.Shared;
|
using Robust.Shared;
|
||||||
using Robust.Shared.Maths;
|
|
||||||
using Robust.Shared.Configuration;
|
using Robust.Shared.Configuration;
|
||||||
using Robust.Shared.IoC;
|
using Robust.Client.GameStates;
|
||||||
using Robust.Shared.Localization;
|
|
||||||
|
|
||||||
namespace Content.Client.EscapeMenu.UI.Tabs
|
namespace Content.Client.EscapeMenu.UI.Tabs
|
||||||
{
|
{
|
||||||
@@ -17,6 +12,7 @@ namespace Content.Client.EscapeMenu.UI.Tabs
|
|||||||
public sealed partial class NetworkTab : Control
|
public sealed partial class NetworkTab : Control
|
||||||
{
|
{
|
||||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||||
|
[Dependency] private readonly IClientGameStateManager _stateMan = default!;
|
||||||
|
|
||||||
public NetworkTab()
|
public NetworkTab()
|
||||||
{
|
{
|
||||||
@@ -26,6 +22,7 @@ namespace Content.Client.EscapeMenu.UI.Tabs
|
|||||||
ApplyButton.OnPressed += OnApplyButtonPressed;
|
ApplyButton.OnPressed += OnApplyButtonPressed;
|
||||||
ResetButton.OnPressed += OnResetButtonPressed;
|
ResetButton.OnPressed += OnResetButtonPressed;
|
||||||
NetInterpRatioSlider.OnValueChanged += OnNetInterpRatioSliderChanged;
|
NetInterpRatioSlider.OnValueChanged += OnNetInterpRatioSliderChanged;
|
||||||
|
NetInterpRatioSlider.MinValue = _stateMan.MinBufferSize;
|
||||||
|
|
||||||
Reset();
|
Reset();
|
||||||
}
|
}
|
||||||
@@ -45,7 +42,7 @@ namespace Content.Client.EscapeMenu.UI.Tabs
|
|||||||
|
|
||||||
private void OnApplyButtonPressed(BaseButton.ButtonEventArgs args)
|
private void OnApplyButtonPressed(BaseButton.ButtonEventArgs args)
|
||||||
{
|
{
|
||||||
_cfg.SetCVar(CVars.NetInterpRatio, (int) NetInterpRatioSlider.Value);
|
_cfg.SetCVar(CVars.NetBufferSize, (int) NetInterpRatioSlider.Value - _stateMan.MinBufferSize);
|
||||||
_cfg.SaveToFile();
|
_cfg.SaveToFile();
|
||||||
UpdateChanges();
|
UpdateChanges();
|
||||||
}
|
}
|
||||||
@@ -57,13 +54,13 @@ namespace Content.Client.EscapeMenu.UI.Tabs
|
|||||||
|
|
||||||
private void Reset()
|
private void Reset()
|
||||||
{
|
{
|
||||||
NetInterpRatioSlider.Value = _cfg.GetCVar(CVars.NetInterpRatio);
|
NetInterpRatioSlider.Value = _cfg.GetCVar(CVars.NetBufferSize) + _stateMan.MinBufferSize;
|
||||||
UpdateChanges();
|
UpdateChanges();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateChanges()
|
private void UpdateChanges()
|
||||||
{
|
{
|
||||||
var isEverythingSame = NetInterpRatioSlider.Value == _cfg.GetCVar(CVars.NetInterpRatio);
|
var isEverythingSame = NetInterpRatioSlider.Value == _cfg.GetCVar(CVars.NetBufferSize) + _stateMan.MinBufferSize;
|
||||||
ApplyButton.Disabled = isEverythingSame;
|
ApplyButton.Disabled = isEverythingSame;
|
||||||
ResetButton.Disabled = isEverythingSame;
|
ResetButton.Disabled = isEverythingSame;
|
||||||
NetInterpRatioLabel.Text = NetInterpRatioSlider.Value.ToString();
|
NetInterpRatioLabel.Text = NetInterpRatioSlider.Value.ToString();
|
||||||
|
|||||||
@@ -14,15 +14,13 @@ namespace Content.Client.Eye.Blinding
|
|||||||
|
|
||||||
public override bool RequestScreenTexture => true;
|
public override bool RequestScreenTexture => true;
|
||||||
public override OverlaySpace Space => OverlaySpace.WorldSpace;
|
public override OverlaySpace Space => OverlaySpace.WorldSpace;
|
||||||
private readonly ShaderInstance _blurryVisionXShader;
|
private readonly ShaderInstance _dim;
|
||||||
private readonly ShaderInstance _blurryVisionYShader;
|
|
||||||
private BlurryVisionComponent _blurryVisionComponent = default!;
|
private BlurryVisionComponent _blurryVisionComponent = default!;
|
||||||
|
|
||||||
public BlurryVisionOverlay()
|
public BlurryVisionOverlay()
|
||||||
{
|
{
|
||||||
IoCManager.InjectDependencies(this);
|
IoCManager.InjectDependencies(this);
|
||||||
_blurryVisionXShader = _prototypeManager.Index<ShaderPrototype>("BlurryVisionX").InstanceUnique();
|
_dim = _prototypeManager.Index<ShaderPrototype>("Dim").InstanceUnique();
|
||||||
_blurryVisionYShader = _prototypeManager.Index<ShaderPrototype>("BlurryVisionY").InstanceUnique();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool BeforeDraw(in OverlayDrawArgs args)
|
protected override bool BeforeDraw(in OverlayDrawArgs args)
|
||||||
@@ -51,18 +49,17 @@ namespace Content.Client.Eye.Blinding
|
|||||||
if (ScreenTexture == null)
|
if (ScreenTexture == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
_blurryVisionXShader?.SetParameter("SCREEN_TEXTURE", ScreenTexture);
|
var opacity = -(_blurryVisionComponent.Magnitude / 15) + 0.9f;
|
||||||
_blurryVisionXShader?.SetParameter("BLUR_AMOUNT", (_blurryVisionComponent.Magnitude / 10));
|
|
||||||
_blurryVisionYShader?.SetParameter("SCREEN_TEXTURE", ScreenTexture);
|
_dim.SetParameter("DAMAGE_AMOUNT", opacity);
|
||||||
_blurryVisionYShader?.SetParameter("BLUR_AMOUNT", (_blurryVisionComponent.Magnitude / 10));
|
|
||||||
|
|
||||||
var worldHandle = args.WorldHandle;
|
var worldHandle = args.WorldHandle;
|
||||||
var viewport = args.WorldBounds;
|
var viewport = args.WorldBounds;
|
||||||
|
|
||||||
|
worldHandle.UseShader(_dim);
|
||||||
worldHandle.SetTransform(Matrix3.Identity);
|
worldHandle.SetTransform(Matrix3.Identity);
|
||||||
worldHandle.UseShader(_blurryVisionXShader);
|
worldHandle.DrawRect(viewport, Color.Black);
|
||||||
worldHandle.DrawRect(viewport, Color.White);
|
worldHandle.UseShader(null);
|
||||||
worldHandle.UseShader(_blurryVisionYShader);
|
|
||||||
worldHandle.DrawRect(viewport, Color.White);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ namespace Content.Client.Forensics
|
|||||||
{
|
{
|
||||||
private ForensicScannerMenu? _window;
|
private ForensicScannerMenu? _window;
|
||||||
|
|
||||||
public ForensicScannerBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
|
public ForensicScannerBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ namespace Content.Client.Gravity.UI
|
|||||||
{
|
{
|
||||||
private GravityGeneratorWindow? _window;
|
private GravityGeneratorWindow? _window;
|
||||||
|
|
||||||
public GravityGeneratorBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base (owner, uiKey)
|
public GravityGeneratorBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base (owner, uiKey)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ namespace Content.Client.HealthAnalyzer.UI
|
|||||||
{
|
{
|
||||||
private HealthAnalyzerWindow? _window;
|
private HealthAnalyzerWindow? _window;
|
||||||
|
|
||||||
public HealthAnalyzerBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
|
public HealthAnalyzerBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ namespace Content.Client.Instruments.UI
|
|||||||
|
|
||||||
public InstrumentComponent? Instrument { get; set; }
|
public InstrumentComponent? Instrument { get; set; }
|
||||||
|
|
||||||
public InstrumentBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
|
public InstrumentBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ namespace Content.Client.Inventory
|
|||||||
[ViewVariables]
|
[ViewVariables]
|
||||||
private StrippingMenu? _strippingMenu;
|
private StrippingMenu? _strippingMenu;
|
||||||
|
|
||||||
public StrippableBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
|
public StrippableBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ namespace Content.Client.Kitchen.UI
|
|||||||
private readonly Dictionary<int, EntityUid> _solids = new();
|
private readonly Dictionary<int, EntityUid> _solids = new();
|
||||||
private readonly Dictionary<int, Solution.ReagentQuantity> _reagents =new();
|
private readonly Dictionary<int, Solution.ReagentQuantity> _reagents =new();
|
||||||
|
|
||||||
public MicrowaveBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner,uiKey)
|
public MicrowaveBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner,uiKey)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ namespace Content.Client.Kitchen.UI
|
|||||||
|
|
||||||
private GrinderMenu? _menu;
|
private GrinderMenu? _menu;
|
||||||
|
|
||||||
public ReagentGrinderBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey) { }
|
public ReagentGrinderBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey) { }
|
||||||
|
|
||||||
protected override void Open()
|
protected override void Open()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ namespace Content.Client.Labels.UI
|
|||||||
{
|
{
|
||||||
private HandLabelerWindow? _window;
|
private HandLabelerWindow? _window;
|
||||||
|
|
||||||
public HandLabelerBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
|
public HandLabelerBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -247,7 +247,7 @@ namespace Content.Client.LateJoin
|
|||||||
|
|
||||||
string? reason = null;
|
string? reason = null;
|
||||||
|
|
||||||
if (value == 0 || !tracker.IsAllowed(prototype, out reason))
|
if (!tracker.IsAllowed(prototype, out reason))
|
||||||
{
|
{
|
||||||
jobButton.Disabled = true;
|
jobButton.Disabled = true;
|
||||||
|
|
||||||
@@ -256,6 +256,10 @@ namespace Content.Client.LateJoin
|
|||||||
jobButton.ToolTip = reason;
|
jobButton.ToolTip = reason;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (value == 0)
|
||||||
|
{
|
||||||
|
jobButton.Disabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
_jobButtons[id][prototype.ID] = jobButton;
|
_jobButtons[id][prototype.ID] = jobButton;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,6 @@ using Robust.Client.GameObjects;
|
|||||||
using Content.Shared.Lathe;
|
using Content.Shared.Lathe;
|
||||||
using Content.Shared.Power;
|
using Content.Shared.Power;
|
||||||
using Content.Client.Power;
|
using Content.Client.Power;
|
||||||
using Content.Client.Wires.Visualizers;
|
|
||||||
using Content.Shared.Wires;
|
|
||||||
|
|
||||||
namespace Content.Client.Lathe
|
namespace Content.Client.Lathe
|
||||||
{
|
{
|
||||||
@@ -20,12 +18,6 @@ namespace Content.Client.Lathe
|
|||||||
args.Sprite.LayerSetVisible(PowerDeviceVisualLayers.Powered, powered);
|
args.Sprite.LayerSetVisible(PowerDeviceVisualLayers.Powered, powered);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (args.Component.TryGetData(WiresVisuals.MaintenancePanelState, out bool panel)
|
|
||||||
&& args.Sprite.LayerMapTryGet(WiresVisualizer.WiresVisualLayers.MaintenancePanel, out _))
|
|
||||||
{
|
|
||||||
args.Sprite.LayerSetVisible(WiresVisualizer.WiresVisualLayers.MaintenancePanel, panel);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lathe specific stuff
|
// Lathe specific stuff
|
||||||
if (args.Component.TryGetData(LatheVisuals.IsRunning, out bool isRunning))
|
if (args.Component.TryGetData(LatheVisuals.IsRunning, out bool isRunning))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ namespace Content.Client.Lathe.UI
|
|||||||
public Queue<LatheRecipePrototype> QueuedRecipes => _queuedRecipes;
|
public Queue<LatheRecipePrototype> QueuedRecipes => _queuedRecipes;
|
||||||
private readonly Queue<LatheRecipePrototype> _queuedRecipes = new();
|
private readonly Queue<LatheRecipePrototype> _queuedRecipes = new();
|
||||||
|
|
||||||
public LatheBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
|
public LatheBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey)
|
||||||
{
|
{
|
||||||
SendMessage(new LatheSyncRequestMessage());
|
SendMessage(new LatheSyncRequestMessage());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ namespace Content.Client.MachineLinking.UI
|
|||||||
private string? _selectedTransmitterPort;
|
private string? _selectedTransmitterPort;
|
||||||
private string? _selectedReceiverPort;
|
private string? _selectedReceiverPort;
|
||||||
|
|
||||||
public SignalPortSelectorBoundUserInterface([NotNull] ClientUserInterfaceComponent owner, [NotNull] object uiKey) : base(owner, uiKey) { }
|
public SignalPortSelectorBoundUserInterface([NotNull] ClientUserInterfaceComponent owner, [NotNull] Enum uiKey) : base(owner, uiKey) { }
|
||||||
|
|
||||||
protected override void Open()
|
protected override void Open()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ namespace Content.Client.Medical.CrewMonitoring
|
|||||||
{
|
{
|
||||||
private CrewMonitoringWindow? _menu;
|
private CrewMonitoringWindow? _menu;
|
||||||
|
|
||||||
public CrewMonitoringBoundUserInterface([NotNull] ClientUserInterfaceComponent owner, [NotNull] object uiKey) : base(owner, uiKey)
|
public CrewMonitoringBoundUserInterface([NotNull] ClientUserInterfaceComponent owner, [NotNull] Enum uiKey) : base(owner, uiKey)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ public sealed class NetworkConfiguratorBoundUserInterface : BoundUserInterface
|
|||||||
private NetworkConfiguratorListMenu? _listMenu;
|
private NetworkConfiguratorListMenu? _listMenu;
|
||||||
private NetworkConfiguratorConfigurationMenu? _configurationMenu;
|
private NetworkConfiguratorConfigurationMenu? _configurationMenu;
|
||||||
|
|
||||||
public NetworkConfiguratorBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
|
public NetworkConfiguratorBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ namespace Content.Client.Nuke
|
|||||||
{
|
{
|
||||||
private NukeMenu? _menu;
|
private NukeMenu? _menu;
|
||||||
|
|
||||||
public NukeBoundUserInterface([NotNull] ClientUserInterfaceComponent owner, [NotNull] object uiKey) : base(owner, uiKey)
|
public NukeBoundUserInterface([NotNull] ClientUserInterfaceComponent owner, [NotNull] Enum uiKey) : base(owner, uiKey)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ namespace Content.Client.PDA
|
|||||||
|
|
||||||
private PDAMenu? _menu;
|
private PDAMenu? _menu;
|
||||||
|
|
||||||
public PDABoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
|
public PDABoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey)
|
||||||
{
|
{
|
||||||
IoCManager.InjectDependencies(this);
|
IoCManager.InjectDependencies(this);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ namespace Content.Client.PDA.Ringer
|
|||||||
{
|
{
|
||||||
private RingtoneMenu? _menu;
|
private RingtoneMenu? _menu;
|
||||||
|
|
||||||
public RingerBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
|
public RingerBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ namespace Content.Client.Paper.UI
|
|||||||
{
|
{
|
||||||
private PaperWindow? _window;
|
private PaperWindow? _window;
|
||||||
|
|
||||||
public PaperBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
|
public PaperBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ namespace Content.Client.ParticleAccelerator.UI
|
|||||||
{
|
{
|
||||||
private ParticleAcceleratorControlMenu? _menu;
|
private ParticleAcceleratorControlMenu? _menu;
|
||||||
|
|
||||||
public ParticleAcceleratorBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
|
public ParticleAcceleratorBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ namespace Content.Client.Power.APC
|
|||||||
_menu.OpenCentered();
|
_menu.OpenCentered();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ApcBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
|
public ApcBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -80,6 +80,6 @@ public sealed partial class PowerMonitoringWindow : DefaultWindow, IComputerWind
|
|||||||
[UsedImplicitly]
|
[UsedImplicitly]
|
||||||
public sealed class PowerMonitoringConsoleBoundUserInterface : ComputerBoundUserInterface<PowerMonitoringWindow, PowerMonitoringConsoleBoundInterfaceState>
|
public sealed class PowerMonitoringConsoleBoundUserInterface : ComputerBoundUserInterface<PowerMonitoringWindow, PowerMonitoringConsoleBoundInterfaceState>
|
||||||
{
|
{
|
||||||
public PowerMonitoringConsoleBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey) {}
|
public PowerMonitoringConsoleBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -161,6 +161,6 @@ namespace Content.Client.Power
|
|||||||
[UsedImplicitly]
|
[UsedImplicitly]
|
||||||
public sealed class SolarControlConsoleBoundUserInterface : ComputerBoundUserInterface<SolarControlWindow, SolarControlConsoleBoundInterfaceState>
|
public sealed class SolarControlConsoleBoundUserInterface : ComputerBoundUserInterface<SolarControlWindow, SolarControlConsoleBoundInterfaceState>
|
||||||
{
|
{
|
||||||
public SolarControlConsoleBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey) {}
|
public SolarControlConsoleBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey) {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ namespace Content.Client.Research.UI
|
|||||||
{
|
{
|
||||||
private ResearchClientServerSelectionMenu? _menu;
|
private ResearchClientServerSelectionMenu? _menu;
|
||||||
|
|
||||||
public ResearchClientBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
|
public ResearchClientBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey)
|
||||||
{
|
{
|
||||||
SendMessage(new ResearchClientSyncMessage());
|
SendMessage(new ResearchClientSyncMessage());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ namespace Content.Client.Research.UI
|
|||||||
private ResearchConsoleMenu? _consoleMenu;
|
private ResearchConsoleMenu? _consoleMenu;
|
||||||
private TechnologyDatabaseComponent? _technologyDatabase;
|
private TechnologyDatabaseComponent? _technologyDatabase;
|
||||||
|
|
||||||
public ResearchConsoleBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
|
public ResearchConsoleBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey)
|
||||||
{
|
{
|
||||||
SendMessage(new ConsoleServerSyncMessage());
|
SendMessage(new ConsoleServerSyncMessage());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,56 +0,0 @@
|
|||||||
using Content.Client.Traitor.Uplink;
|
|
||||||
using Content.Shared.Revenant;
|
|
||||||
using Content.Shared.Traitor.Uplink;
|
|
||||||
using JetBrains.Annotations;
|
|
||||||
using Robust.Client.GameObjects;
|
|
||||||
|
|
||||||
namespace Content.Client.Revenant.Ui;
|
|
||||||
|
|
||||||
[UsedImplicitly]
|
|
||||||
public sealed class RevenantBoundUserInterface : BoundUserInterface
|
|
||||||
{
|
|
||||||
private RevenantMenu? _menu;
|
|
||||||
|
|
||||||
public RevenantBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Open()
|
|
||||||
{
|
|
||||||
_menu = new();
|
|
||||||
_menu.OpenCentered();
|
|
||||||
_menu.OnClose += Close;
|
|
||||||
|
|
||||||
_menu.OnListingButtonPressed += (_, listing) =>
|
|
||||||
{
|
|
||||||
SendMessage(new RevenantBuyListingMessage(listing));
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void UpdateState(BoundUserInterfaceState state)
|
|
||||||
{
|
|
||||||
base.UpdateState(state);
|
|
||||||
|
|
||||||
if (_menu == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
switch (state)
|
|
||||||
{
|
|
||||||
case RevenantUpdateState msg:
|
|
||||||
_menu.UpdateEssence(msg.Essence);
|
|
||||||
_menu.UpdateListing(msg.Listings);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Dispose(bool disposing)
|
|
||||||
{
|
|
||||||
base.Dispose(disposing);
|
|
||||||
if (!disposing)
|
|
||||||
return;
|
|
||||||
|
|
||||||
_menu?.Close();
|
|
||||||
_menu?.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
using Robust.Client.AutoGenerated;
|
|
||||||
using Robust.Client.Graphics;
|
|
||||||
using Robust.Client.UserInterface;
|
|
||||||
using Robust.Client.UserInterface.Controls;
|
|
||||||
using Robust.Client.UserInterface.XAML;
|
|
||||||
using Robust.Shared.Maths;
|
|
||||||
|
|
||||||
namespace Content.Client.Revenant.Ui;
|
|
||||||
|
|
||||||
[GenerateTypedNameReferences]
|
|
||||||
public sealed partial class RevenantListingControl : Control
|
|
||||||
{
|
|
||||||
public RevenantListingControl(string itemName, string itemDescription,
|
|
||||||
int itemPrice, bool canBuy, Texture? texture = null)
|
|
||||||
{
|
|
||||||
RobustXamlLoader.Load(this);
|
|
||||||
|
|
||||||
RevenantItemName.Text = itemName;
|
|
||||||
RevenantItemDescription.SetMessage(itemDescription);
|
|
||||||
|
|
||||||
RevenantItemBuyButton.Text = Loc.GetString("revenant-user-interface-cost", ("price", itemPrice));
|
|
||||||
RevenantItemBuyButton.Disabled = !canBuy;
|
|
||||||
|
|
||||||
RevenantItemTexture.Texture = texture;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
using Content.Client.Message;
|
|
||||||
using Content.Shared.FixedPoint;
|
|
||||||
using Content.Shared.Revenant;
|
|
||||||
using Robust.Client.AutoGenerated;
|
|
||||||
using Robust.Client.UserInterface.Controls;
|
|
||||||
using Robust.Client.UserInterface.CustomControls;
|
|
||||||
using Robust.Client.UserInterface.XAML;
|
|
||||||
using Robust.Client.Utility;
|
|
||||||
|
|
||||||
namespace Content.Client.Revenant.Ui;
|
|
||||||
|
|
||||||
[GenerateTypedNameReferences]
|
|
||||||
public sealed partial class RevenantMenu : DefaultWindow
|
|
||||||
{
|
|
||||||
private FixedPoint2 _essence = 0f;
|
|
||||||
|
|
||||||
public event Action<BaseButton.ButtonEventArgs, RevenantStoreListingPrototype>? OnListingButtonPressed;
|
|
||||||
|
|
||||||
public RevenantMenu()
|
|
||||||
{
|
|
||||||
RobustXamlLoader.Load(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void UpdateEssence(float essence)
|
|
||||||
{
|
|
||||||
// update balance label
|
|
||||||
_essence = essence;
|
|
||||||
var balanceStr = Loc.GetString("revenant-user-interface-essence-amount", ("amount", Math.Round(_essence.Float())));
|
|
||||||
BalanceInfo.SetMarkup(balanceStr);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void UpdateListing(List<RevenantStoreListingPrototype> listings)
|
|
||||||
{
|
|
||||||
// should probably chunk these out instead. to-do if this clogs the internet tubes.
|
|
||||||
// maybe read clients prototypes instead?
|
|
||||||
ClearListings();
|
|
||||||
foreach (var item in listings)
|
|
||||||
{
|
|
||||||
AddListingGui(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void AddListingGui(RevenantStoreListingPrototype listing)
|
|
||||||
{
|
|
||||||
var listingName = listing.ListingName;
|
|
||||||
var listingDesc = listing.Description;
|
|
||||||
var listingPrice = listing.Price;
|
|
||||||
var canBuy = _essence > listing.Price;
|
|
||||||
var texture = listing.Icon?.Frame0();
|
|
||||||
|
|
||||||
var newListing = new RevenantListingControl(listingName, listingDesc, listingPrice, canBuy, texture);
|
|
||||||
newListing.RevenantItemBuyButton.OnButtonDown += args
|
|
||||||
=> OnListingButtonPressed?.Invoke(args, listing);
|
|
||||||
|
|
||||||
RevenantListingsContainer.AddChild(newListing);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ClearListings()
|
|
||||||
{
|
|
||||||
RevenantListingsContainer.Children.Clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -9,5 +9,5 @@ namespace Content.Client.Shuttles.BUI;
|
|||||||
[UsedImplicitly]
|
[UsedImplicitly]
|
||||||
public sealed class EmergencyConsoleBoundUserInterface : ComputerBoundUserInterface<EmergencyConsoleWindow, EmergencyConsoleBoundUserInterfaceState>
|
public sealed class EmergencyConsoleBoundUserInterface : ComputerBoundUserInterface<EmergencyConsoleWindow, EmergencyConsoleBoundUserInterfaceState>
|
||||||
{
|
{
|
||||||
public EmergencyConsoleBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey) {}
|
public EmergencyConsoleBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey) {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ public sealed class IFFConsoleBoundUserInterface : BoundUserInterface
|
|||||||
{
|
{
|
||||||
private IFFConsoleWindow? _window;
|
private IFFConsoleWindow? _window;
|
||||||
|
|
||||||
public IFFConsoleBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
|
public IFFConsoleBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ public sealed class RadarConsoleBoundUserInterface : BoundUserInterface
|
|||||||
{
|
{
|
||||||
private RadarConsoleWindow? _window;
|
private RadarConsoleWindow? _window;
|
||||||
|
|
||||||
public RadarConsoleBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey) {}
|
public RadarConsoleBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey) {}
|
||||||
|
|
||||||
protected override void Open()
|
protected override void Open()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ public sealed class ShuttleConsoleBoundUserInterface : BoundUserInterface
|
|||||||
{
|
{
|
||||||
private ShuttleConsoleWindow? _window;
|
private ShuttleConsoleWindow? _window;
|
||||||
|
|
||||||
public ShuttleConsoleBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey) {}
|
public ShuttleConsoleBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey) {}
|
||||||
|
|
||||||
protected override void Open()
|
protected override void Open()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ public sealed class GeneralStationRecordConsoleBoundUserInterface : BoundUserInt
|
|||||||
{
|
{
|
||||||
private GeneralStationRecordConsoleWindow? _window = default!;
|
private GeneralStationRecordConsoleWindow? _window = default!;
|
||||||
|
|
||||||
public GeneralStationRecordConsoleBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
|
public GeneralStationRecordConsoleBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
protected override void Open()
|
protected override void Open()
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ namespace Content.Client.Storage
|
|||||||
{
|
{
|
||||||
[ViewVariables] private StorageWindow? _window;
|
[ViewVariables] private StorageWindow? _window;
|
||||||
|
|
||||||
public StorageBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
|
public StorageBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ namespace Content.Client.Storage.UI
|
|||||||
new Label
|
new Label
|
||||||
{
|
{
|
||||||
Align = Label.AlignMode.Right,
|
Align = Label.AlignMode.Right,
|
||||||
Text = item?.Size.ToString() ?? Loc.GetString("no-item-size")
|
Text = item?.Size.ToString() ?? Loc.GetString("comp-storage-no-item-size"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
79
Content.Client/Store/Ui/StoreBoundUserInterface.cs
Normal file
79
Content.Client/Store/Ui/StoreBoundUserInterface.cs
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
using Content.Shared.Store;
|
||||||
|
using JetBrains.Annotations;
|
||||||
|
using Robust.Client.GameObjects;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Content.Client.Store.Ui;
|
||||||
|
|
||||||
|
[UsedImplicitly]
|
||||||
|
public sealed class StoreBoundUserInterface : BoundUserInterface
|
||||||
|
{
|
||||||
|
private StoreMenu? _menu;
|
||||||
|
|
||||||
|
private string _windowName = Loc.GetString("store-ui-default-title");
|
||||||
|
|
||||||
|
public StoreBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Open()
|
||||||
|
{
|
||||||
|
_menu = new StoreMenu(_windowName);
|
||||||
|
|
||||||
|
_menu.OpenCentered();
|
||||||
|
_menu.OnClose += Close;
|
||||||
|
|
||||||
|
_menu.OnListingButtonPressed += (_, listing) =>
|
||||||
|
{
|
||||||
|
if (_menu.CurrentBuyer != null)
|
||||||
|
SendMessage(new StoreBuyListingMessage(_menu.CurrentBuyer.Value, listing));
|
||||||
|
};
|
||||||
|
|
||||||
|
_menu.OnCategoryButtonPressed += (_, category) =>
|
||||||
|
{
|
||||||
|
_menu.CurrentCategory = category;
|
||||||
|
if (_menu.CurrentBuyer != null)
|
||||||
|
SendMessage(new StoreRequestUpdateInterfaceMessage(_menu.CurrentBuyer.Value));
|
||||||
|
};
|
||||||
|
|
||||||
|
_menu.OnWithdrawAttempt += (_, type, amount) =>
|
||||||
|
{
|
||||||
|
if (_menu.CurrentBuyer != null)
|
||||||
|
SendMessage(new StoreRequestWithdrawMessage(_menu.CurrentBuyer.Value, type, amount));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
protected override void UpdateState(BoundUserInterfaceState state)
|
||||||
|
{
|
||||||
|
base.UpdateState(state);
|
||||||
|
|
||||||
|
if (_menu == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
switch (state)
|
||||||
|
{
|
||||||
|
case StoreUpdateState msg:
|
||||||
|
if (msg.Buyer != null)
|
||||||
|
_menu.CurrentBuyer = msg.Buyer;
|
||||||
|
_menu.UpdateBalance(msg.Balance);
|
||||||
|
_menu.PopulateStoreCategoryButtons(msg.Listings);
|
||||||
|
_menu.UpdateListing(msg.Listings.ToList());
|
||||||
|
break;
|
||||||
|
case StoreInitializeState msg:
|
||||||
|
_windowName = msg.Name;
|
||||||
|
if (_menu != null && _menu.Window != null)
|
||||||
|
_menu.Window.Title = msg.Name;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
base.Dispose(disposing);
|
||||||
|
if (!disposing)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_menu?.Close();
|
||||||
|
_menu?.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
<Control xmlns="https://spacestation14.io">
|
<Control xmlns="https://spacestation14.io">
|
||||||
<BoxContainer Margin="8,8,8,8" Orientation="Vertical">
|
<BoxContainer Margin="8,8,8,8" Orientation="Vertical">
|
||||||
<BoxContainer Orientation="Horizontal">
|
<BoxContainer Orientation="Horizontal">
|
||||||
<Label Name="RevenantItemName" HorizontalExpand="True" />
|
<Label Name="StoreItemName" HorizontalExpand="True" />
|
||||||
<Button
|
<Button
|
||||||
Name="RevenantItemBuyButton"
|
Name="StoreItemBuyButton"
|
||||||
MinWidth="64"
|
MinWidth="64"
|
||||||
HorizontalAlignment="Right"
|
HorizontalAlignment="Right"
|
||||||
Access="Public" />
|
Access="Public" />
|
||||||
@@ -11,11 +11,11 @@
|
|||||||
<PanelContainer StyleClasses="HighDivider" />
|
<PanelContainer StyleClasses="HighDivider" />
|
||||||
<BoxContainer HorizontalExpand="True" Orientation="Horizontal">
|
<BoxContainer HorizontalExpand="True" Orientation="Horizontal">
|
||||||
<TextureRect
|
<TextureRect
|
||||||
Name="RevenantItemTexture"
|
Name="StoreItemTexture"
|
||||||
Margin="0,0,4,0"
|
Margin="0,0,4,0"
|
||||||
MinSize="48 48"
|
MinSize="48 48"
|
||||||
Stretch="KeepAspectCentered" />
|
Stretch="KeepAspectCentered" />
|
||||||
<RichTextLabel Name="RevenantItemDescription" />
|
<RichTextLabel Name="StoreItemDescription" />
|
||||||
</BoxContainer>
|
</BoxContainer>
|
||||||
</BoxContainer>
|
</BoxContainer>
|
||||||
</Control>
|
</Control>
|
||||||
24
Content.Client/Store/Ui/StoreListingControl.xaml.cs
Normal file
24
Content.Client/Store/Ui/StoreListingControl.xaml.cs
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
using Robust.Client.AutoGenerated;
|
||||||
|
using Robust.Client.Graphics;
|
||||||
|
using Robust.Client.UserInterface;
|
||||||
|
using Robust.Client.UserInterface.XAML;
|
||||||
|
|
||||||
|
namespace Content.Client.Store.Ui;
|
||||||
|
|
||||||
|
[GenerateTypedNameReferences]
|
||||||
|
public sealed partial class StoreListingControl : Control
|
||||||
|
{
|
||||||
|
public StoreListingControl(string itemName, string itemDescription,
|
||||||
|
string price, bool canBuy, Texture? texture = null)
|
||||||
|
{
|
||||||
|
RobustXamlLoader.Load(this);
|
||||||
|
|
||||||
|
StoreItemName.Text = itemName;
|
||||||
|
StoreItemDescription.SetMessage(itemDescription);
|
||||||
|
|
||||||
|
StoreItemBuyButton.Text = price;
|
||||||
|
StoreItemBuyButton.Disabled = !canBuy;
|
||||||
|
|
||||||
|
StoreItemTexture.Texture = texture;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
<DefaultWindow
|
<DefaultWindow
|
||||||
xmlns="https://spacestation14.io"
|
xmlns="https://spacestation14.io"
|
||||||
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
|
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
|
||||||
Title="{Loc 'revenant-user-interface-title'}"
|
Title="{Loc 'store-ui-default-title'}"
|
||||||
MinSize="512 512"
|
MinSize="512 512"
|
||||||
SetSize="512 512">
|
SetSize="512 512">
|
||||||
<BoxContainer Orientation="Vertical">
|
<BoxContainer Orientation="Vertical">
|
||||||
@@ -12,21 +12,34 @@
|
|||||||
HorizontalAlignment="Left"
|
HorizontalAlignment="Left"
|
||||||
Access="Public"
|
Access="Public"
|
||||||
HorizontalExpand="True" />
|
HorizontalExpand="True" />
|
||||||
|
<Button
|
||||||
|
Name="WithdrawButton"
|
||||||
|
MinWidth="64"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
Text="{Loc 'store-ui-default-withdraw-text'}" />
|
||||||
</BoxContainer>
|
</BoxContainer>
|
||||||
<PanelContainer VerticalExpand="True">
|
<PanelContainer VerticalExpand="True">
|
||||||
<PanelContainer.PanelOverride>
|
<PanelContainer.PanelOverride>
|
||||||
<gfx:StyleBoxFlat BackgroundColor="#000000FF" />
|
<gfx:StyleBoxFlat BackgroundColor="#000000FF" />
|
||||||
</PanelContainer.PanelOverride>
|
</PanelContainer.PanelOverride>
|
||||||
<BoxContainer Orientation="Horizontal" VerticalExpand="True">
|
<BoxContainer Orientation="Horizontal" VerticalExpand="True">
|
||||||
|
<PanelContainer VerticalExpand="True">
|
||||||
|
<PanelContainer.PanelOverride>
|
||||||
|
<gfx:StyleBoxFlat BackgroundColor="#80808005" />
|
||||||
|
</PanelContainer.PanelOverride>
|
||||||
|
<BoxContainer Name="CategoryListContainer" Orientation="Vertical">
|
||||||
|
<!-- Category buttons are added here by code -->
|
||||||
|
</BoxContainer>
|
||||||
|
</PanelContainer>
|
||||||
<ScrollContainer
|
<ScrollContainer
|
||||||
Name="RevenantListingsScroll"
|
Name="StoreListingsScroll"
|
||||||
HScrollEnabled="False"
|
HScrollEnabled="False"
|
||||||
HorizontalExpand="True"
|
HorizontalExpand="True"
|
||||||
MinSize="100 256"
|
MinSize="100 256"
|
||||||
SizeFlagsStretchRatio="2"
|
SizeFlagsStretchRatio="2"
|
||||||
VerticalExpand="True">
|
VerticalExpand="True">
|
||||||
<BoxContainer
|
<BoxContainer
|
||||||
Name="RevenantListingsContainer"
|
Name="StoreListingsContainer"
|
||||||
MinSize="100 256"
|
MinSize="100 256"
|
||||||
Orientation="Vertical"
|
Orientation="Vertical"
|
||||||
SizeFlagsStretchRatio="2"
|
SizeFlagsStretchRatio="2"
|
||||||
222
Content.Client/Store/Ui/StoreMenu.xaml.cs
Normal file
222
Content.Client/Store/Ui/StoreMenu.xaml.cs
Normal file
@@ -0,0 +1,222 @@
|
|||||||
|
using Content.Client.Message;
|
||||||
|
using Content.Shared.Store;
|
||||||
|
using Robust.Client.AutoGenerated;
|
||||||
|
using Robust.Client.GameObjects;
|
||||||
|
using Robust.Client.UserInterface.Controls;
|
||||||
|
using Robust.Client.UserInterface.CustomControls;
|
||||||
|
using Robust.Client.UserInterface.XAML;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
using Robust.Client.Graphics;
|
||||||
|
using Content.Shared.Actions.ActionTypes;
|
||||||
|
using System.Linq;
|
||||||
|
using Content.Shared.FixedPoint;
|
||||||
|
|
||||||
|
namespace Content.Client.Store.Ui;
|
||||||
|
|
||||||
|
[GenerateTypedNameReferences]
|
||||||
|
public sealed partial class StoreMenu : DefaultWindow
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||||
|
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||||
|
|
||||||
|
private StoreWithdrawWindow? _withdrawWindow;
|
||||||
|
|
||||||
|
public event Action<BaseButton.ButtonEventArgs, ListingData>? OnListingButtonPressed;
|
||||||
|
public event Action<BaseButton.ButtonEventArgs, string>? OnCategoryButtonPressed;
|
||||||
|
public event Action<BaseButton.ButtonEventArgs, string, int>? OnWithdrawAttempt;
|
||||||
|
|
||||||
|
public EntityUid? CurrentBuyer;
|
||||||
|
public Dictionary<string, FixedPoint2> Balance = new();
|
||||||
|
public string CurrentCategory = string.Empty;
|
||||||
|
|
||||||
|
public StoreMenu(string name)
|
||||||
|
{
|
||||||
|
RobustXamlLoader.Load(this);
|
||||||
|
IoCManager.InjectDependencies(this);
|
||||||
|
|
||||||
|
WithdrawButton.OnButtonDown += OnWithdrawButtonDown;
|
||||||
|
if (Window != null)
|
||||||
|
Window.Title = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateBalance(Dictionary<string, FixedPoint2> balance)
|
||||||
|
{
|
||||||
|
Balance = balance;
|
||||||
|
|
||||||
|
var currency = new Dictionary<(string, FixedPoint2), CurrencyPrototype>();
|
||||||
|
foreach (var type in balance)
|
||||||
|
{
|
||||||
|
currency.Add((type.Key, type.Value), _prototypeManager.Index<CurrencyPrototype>(type.Key));
|
||||||
|
}
|
||||||
|
|
||||||
|
var balanceStr = string.Empty;
|
||||||
|
foreach (var type in currency)
|
||||||
|
{
|
||||||
|
balanceStr += $"{Loc.GetString(type.Value.BalanceDisplay, ("amount", type.Key.Item2))}\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
BalanceInfo.SetMarkup(balanceStr.TrimEnd());
|
||||||
|
|
||||||
|
var disabled = true;
|
||||||
|
foreach (var type in currency)
|
||||||
|
{
|
||||||
|
if (type.Value.CanWithdraw && type.Value.EntityId != null && type.Key.Item2 > 0)
|
||||||
|
disabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
WithdrawButton.Disabled = disabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateListing(List<ListingData> listings)
|
||||||
|
{
|
||||||
|
var sorted = listings.OrderBy(l => l.Priority).ThenBy(l => l.Cost.Values.Sum());
|
||||||
|
|
||||||
|
// should probably chunk these out instead. to-do if this clogs the internet tubes.
|
||||||
|
// maybe read clients prototypes instead?
|
||||||
|
ClearListings();
|
||||||
|
foreach (var item in sorted)
|
||||||
|
{
|
||||||
|
AddListingGui(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnWithdrawButtonDown(BaseButton.ButtonEventArgs args)
|
||||||
|
{
|
||||||
|
// check if window is already open
|
||||||
|
if (_withdrawWindow != null && _withdrawWindow.IsOpen)
|
||||||
|
{
|
||||||
|
_withdrawWindow.MoveToFront();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// open a new one
|
||||||
|
_withdrawWindow = new StoreWithdrawWindow();
|
||||||
|
_withdrawWindow.OpenCentered();
|
||||||
|
|
||||||
|
_withdrawWindow.CreateCurrencyButtons(Balance);
|
||||||
|
_withdrawWindow.OnWithdrawAttempt += OnWithdrawAttempt;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddListingGui(ListingData listing)
|
||||||
|
{
|
||||||
|
if (!listing.Categories.Contains(CurrentCategory))
|
||||||
|
return;
|
||||||
|
|
||||||
|
string listingName = new (listing.Name);
|
||||||
|
string listingDesc = new (listing.Description);
|
||||||
|
var listingPrice = listing.Cost;
|
||||||
|
var canBuy = CanBuyListing(Balance, listingPrice);
|
||||||
|
|
||||||
|
var spriteSys = _entityManager.EntitySysManager.GetEntitySystem<SpriteSystem>();
|
||||||
|
|
||||||
|
Texture? texture = null;
|
||||||
|
if (listing.Icon != null)
|
||||||
|
texture = spriteSys.Frame0(listing.Icon);
|
||||||
|
|
||||||
|
if (listing.ProductEntity != null)
|
||||||
|
{
|
||||||
|
if (texture == null)
|
||||||
|
texture = spriteSys.GetPrototypeIcon(listing.ProductEntity).Default;
|
||||||
|
|
||||||
|
var proto = _prototypeManager.Index<EntityPrototype>(listing.ProductEntity);
|
||||||
|
if (listingName == string.Empty)
|
||||||
|
listingName = proto.Name;
|
||||||
|
if (listingDesc == string.Empty)
|
||||||
|
listingDesc = proto.Description;
|
||||||
|
}
|
||||||
|
else if (listing.ProductAction != null)
|
||||||
|
{
|
||||||
|
var action = _prototypeManager.Index<InstantActionPrototype>(listing.ProductAction);
|
||||||
|
if (action.Icon != null)
|
||||||
|
texture = spriteSys.Frame0(action.Icon);
|
||||||
|
}
|
||||||
|
|
||||||
|
var newListing = new StoreListingControl(listingName, listingDesc, GetListingPriceString(listing), canBuy, texture);
|
||||||
|
newListing.StoreItemBuyButton.OnButtonDown += args
|
||||||
|
=> OnListingButtonPressed?.Invoke(args, listing);
|
||||||
|
|
||||||
|
StoreListingsContainer.AddChild(newListing);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool CanBuyListing(Dictionary<string, FixedPoint2> currency, Dictionary<string, FixedPoint2> price)
|
||||||
|
{
|
||||||
|
foreach (var type in price)
|
||||||
|
{
|
||||||
|
if (!currency.ContainsKey(type.Key))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (currency[type.Key] < type.Value)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetListingPriceString(ListingData listing)
|
||||||
|
{
|
||||||
|
var text = string.Empty;
|
||||||
|
|
||||||
|
foreach (var type in listing.Cost)
|
||||||
|
{
|
||||||
|
var currency = _prototypeManager.Index<CurrencyPrototype>(type.Key);
|
||||||
|
text += $"{Loc.GetString(currency.PriceDisplay, ("amount", type.Value))}\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (listing.Cost.Count < 1)
|
||||||
|
text = Loc.GetString("store-currency-free");
|
||||||
|
|
||||||
|
return text.TrimEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ClearListings()
|
||||||
|
{
|
||||||
|
StoreListingsContainer.Children.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void PopulateStoreCategoryButtons(HashSet<ListingData> listings)
|
||||||
|
{
|
||||||
|
var allCategories = new List<StoreCategoryPrototype>();
|
||||||
|
foreach (var listing in listings)
|
||||||
|
{
|
||||||
|
foreach (var cat in listing.Categories)
|
||||||
|
{
|
||||||
|
var proto = _prototypeManager.Index<StoreCategoryPrototype>(cat);
|
||||||
|
if (!allCategories.Contains(proto))
|
||||||
|
allCategories.Add(proto);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
allCategories = allCategories.OrderBy(c => c.Priority).ToList();
|
||||||
|
|
||||||
|
if (CurrentCategory == string.Empty && allCategories.Count > 0)
|
||||||
|
CurrentCategory = allCategories.First().ID;
|
||||||
|
|
||||||
|
if (allCategories.Count <= 1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
CategoryListContainer.Children.Clear();
|
||||||
|
|
||||||
|
foreach (var proto in allCategories)
|
||||||
|
{
|
||||||
|
var catButton = new StoreCategoryButton
|
||||||
|
{
|
||||||
|
Text = Loc.GetString(proto.Name),
|
||||||
|
Id = proto.ID
|
||||||
|
};
|
||||||
|
|
||||||
|
catButton.OnPressed += args => OnCategoryButtonPressed?.Invoke(args, catButton.Id);
|
||||||
|
CategoryListContainer.AddChild(catButton);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Close()
|
||||||
|
{
|
||||||
|
base.Close();
|
||||||
|
CurrentBuyer = null;
|
||||||
|
_withdrawWindow?.Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class StoreCategoryButton : Button
|
||||||
|
{
|
||||||
|
public string? Id;
|
||||||
|
}
|
||||||
|
}
|
||||||
16
Content.Client/Store/Ui/StoreWithdrawWindow.xaml
Normal file
16
Content.Client/Store/Ui/StoreWithdrawWindow.xaml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<DefaultWindow
|
||||||
|
xmlns="https://spacestation14.io"
|
||||||
|
Title="{Loc 'store-ui-default-withdraw-text'}"
|
||||||
|
MinSize="256 128">
|
||||||
|
<BoxContainer
|
||||||
|
HorizontalExpand="True"
|
||||||
|
Orientation="Vertical"
|
||||||
|
VerticalExpand="True">
|
||||||
|
<SliderIntInput Name="WithdrawSlider" HorizontalExpand="True" />
|
||||||
|
<BoxContainer
|
||||||
|
Name="ButtonContainer"
|
||||||
|
VerticalAlignment="Bottom"
|
||||||
|
Orientation="Vertical"
|
||||||
|
VerticalExpand="True" />
|
||||||
|
</BoxContainer>
|
||||||
|
</DefaultWindow>
|
||||||
102
Content.Client/Store/Ui/StoreWithdrawWindow.xaml.cs
Normal file
102
Content.Client/Store/Ui/StoreWithdrawWindow.xaml.cs
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using Robust.Client.AutoGenerated;
|
||||||
|
using Robust.Client.UserInterface.CustomControls;
|
||||||
|
using Robust.Client.UserInterface.XAML;
|
||||||
|
using Robust.Shared.Localization;
|
||||||
|
using Content.Shared.FixedPoint;
|
||||||
|
using Content.Shared.Store;
|
||||||
|
using Robust.Client.UserInterface;
|
||||||
|
using Robust.Client.AutoGenerated;
|
||||||
|
using Robust.Client.GameObjects;
|
||||||
|
using Robust.Client.UserInterface.Controls;
|
||||||
|
using Robust.Client.UserInterface.CustomControls;
|
||||||
|
using Robust.Client.UserInterface.XAML;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
using Robust.Client.Graphics;
|
||||||
|
using Content.Shared.Actions.ActionTypes;
|
||||||
|
using Robust.Client.UserInterface.Controls;
|
||||||
|
using Robust.Shared.Prototypes;
|
||||||
|
|
||||||
|
namespace Content.Client.Store.Ui;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Window to select amount TC to withdraw from Uplink account
|
||||||
|
/// Used as sub-window in Uplink UI
|
||||||
|
/// </summary>
|
||||||
|
[GenerateTypedNameReferences]
|
||||||
|
public sealed partial class StoreWithdrawWindow : DefaultWindow
|
||||||
|
{
|
||||||
|
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
||||||
|
|
||||||
|
private Dictionary<FixedPoint2, CurrencyPrototype> _validCurrencies = new();
|
||||||
|
private HashSet<CurrencyWithdrawButton> _buttons = new();
|
||||||
|
public event Action<BaseButton.ButtonEventArgs, string, int>? OnWithdrawAttempt;
|
||||||
|
|
||||||
|
public StoreWithdrawWindow()
|
||||||
|
{
|
||||||
|
RobustXamlLoader.Load(this);
|
||||||
|
IoCManager.InjectDependencies(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CreateCurrencyButtons(Dictionary<string, FixedPoint2> balance)
|
||||||
|
{
|
||||||
|
_validCurrencies.Clear();
|
||||||
|
foreach (var currency in balance)
|
||||||
|
{
|
||||||
|
if (!_prototypeManager.TryIndex<CurrencyPrototype>(currency.Key, out var proto))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
_validCurrencies.Add(currency.Value, proto);
|
||||||
|
}
|
||||||
|
|
||||||
|
//this shouldn't ever happen but w/e
|
||||||
|
if (_validCurrencies.Count < 1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ButtonContainer.Children.Clear();
|
||||||
|
_buttons.Clear();
|
||||||
|
foreach (var currency in _validCurrencies)
|
||||||
|
{
|
||||||
|
Logger.Debug((currency.Value.PriceDisplay));
|
||||||
|
var button = new CurrencyWithdrawButton()
|
||||||
|
{
|
||||||
|
Id = currency.Value.ID,
|
||||||
|
Amount = currency.Key,
|
||||||
|
MinHeight = 20,
|
||||||
|
Text = Loc.GetString("store-withdraw-button-ui", ("currency",Loc.GetString(currency.Value.PriceDisplay))),
|
||||||
|
};
|
||||||
|
button.Disabled = false;
|
||||||
|
button.OnPressed += args =>
|
||||||
|
{
|
||||||
|
OnWithdrawAttempt?.Invoke(args, button.Id, WithdrawSlider.Value);
|
||||||
|
Close();
|
||||||
|
};
|
||||||
|
|
||||||
|
_buttons.Add(button);
|
||||||
|
ButtonContainer.AddChild(button);
|
||||||
|
}
|
||||||
|
|
||||||
|
var maxWithdrawAmount = _validCurrencies.Keys.Max().Int();
|
||||||
|
|
||||||
|
// setup withdraw slider
|
||||||
|
WithdrawSlider.MinValue = 1;
|
||||||
|
WithdrawSlider.MaxValue = maxWithdrawAmount;
|
||||||
|
|
||||||
|
WithdrawSlider.OnValueChanged += OnValueChanged;
|
||||||
|
OnValueChanged(WithdrawSlider.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnValueChanged(int i)
|
||||||
|
{
|
||||||
|
foreach (var button in _buttons)
|
||||||
|
{
|
||||||
|
button.Disabled = button.Amount < WithdrawSlider.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class CurrencyWithdrawButton : Button
|
||||||
|
{
|
||||||
|
public string? Id;
|
||||||
|
public FixedPoint2 Amount = FixedPoint2.Zero;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,7 +13,7 @@ public sealed class SurveillanceCameraMonitorBoundUserInterface : BoundUserInter
|
|||||||
private SurveillanceCameraMonitorWindow? _window;
|
private SurveillanceCameraMonitorWindow? _window;
|
||||||
private EntityUid? _currentCamera;
|
private EntityUid? _currentCamera;
|
||||||
|
|
||||||
public SurveillanceCameraMonitorBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
|
public SurveillanceCameraMonitorBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey)
|
||||||
{
|
{
|
||||||
IoCManager.InjectDependencies(this);
|
IoCManager.InjectDependencies(this);
|
||||||
_eyeLerpingSystem = _entityManager.EntitySysManager.GetEntitySystem<EyeLerpingSystem>();
|
_eyeLerpingSystem = _entityManager.EntitySysManager.GetEntitySystem<EyeLerpingSystem>();
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ public sealed class SurveillanceCameraSetupBoundUi : BoundUserInterface
|
|||||||
private SurveillanceCameraSetupWindow? _window;
|
private SurveillanceCameraSetupWindow? _window;
|
||||||
private SurveillanceCameraSetupUiKey _type;
|
private SurveillanceCameraSetupUiKey _type;
|
||||||
|
|
||||||
public SurveillanceCameraSetupBoundUi(ClientUserInterfaceComponent component, object uiKey) : base(component, uiKey)
|
public SurveillanceCameraSetupBoundUi(ClientUserInterfaceComponent component, Enum uiKey) : base(component, uiKey)
|
||||||
{
|
{
|
||||||
if (uiKey is not SurveillanceCameraSetupUiKey key)
|
if (uiKey is not SurveillanceCameraSetupUiKey key)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,72 +1,50 @@
|
|||||||
using Content.Client.Items.Components;
|
|
||||||
using Content.Client.Message;
|
using Content.Client.Message;
|
||||||
using Content.Client.Stylesheets;
|
using Content.Client.Stylesheets;
|
||||||
using Content.Shared.Tools.Components;
|
using Content.Shared.Tools.Components;
|
||||||
using Robust.Client.UserInterface;
|
using Robust.Client.UserInterface;
|
||||||
using Robust.Client.UserInterface.Controls;
|
using Robust.Client.UserInterface.Controls;
|
||||||
using Robust.Shared.GameObjects;
|
|
||||||
using Robust.Shared.GameStates;
|
|
||||||
using Robust.Shared.Serialization.Manager.Attributes;
|
|
||||||
using Robust.Shared.Timing;
|
using Robust.Shared.Timing;
|
||||||
using Robust.Shared.ViewVariables;
|
|
||||||
|
|
||||||
namespace Content.Client.Tools.Components
|
namespace Content.Client.Tools.Components
|
||||||
{
|
{
|
||||||
[RegisterComponent]
|
[RegisterComponent]
|
||||||
public sealed class MultipleToolComponent : SharedMultipleToolComponent, IItemStatus
|
[ComponentReference(typeof(SharedMultipleToolComponent))]
|
||||||
|
public sealed class MultipleToolComponent : SharedMultipleToolComponent
|
||||||
{
|
{
|
||||||
private string? _behavior;
|
[ViewVariables(VVAccess.ReadWrite)]
|
||||||
|
public bool UiUpdateNeeded;
|
||||||
|
|
||||||
[DataField("statusShowBehavior")]
|
[DataField("statusShowBehavior")]
|
||||||
private bool _statusShowBehavior = true;
|
public bool StatusShowBehavior = true;
|
||||||
|
}
|
||||||
|
|
||||||
[ViewVariables(VVAccess.ReadWrite)] private bool _uiUpdateNeeded;
|
public sealed class MultipleToolStatusControl : Control
|
||||||
[ViewVariables] public bool StatusShowBehavior => _statusShowBehavior;
|
{
|
||||||
[ViewVariables] public string? Behavior => _behavior;
|
private readonly MultipleToolComponent _parent;
|
||||||
|
private readonly RichTextLabel _label;
|
||||||
|
|
||||||
public override void HandleComponentState(ComponentState? curState, ComponentState? nextState)
|
public MultipleToolStatusControl(MultipleToolComponent parent)
|
||||||
{
|
{
|
||||||
base.HandleComponentState(curState, nextState);
|
_parent = parent;
|
||||||
|
_label = new RichTextLabel { StyleClasses = { StyleNano.StyleClassItemStatus } };
|
||||||
if (curState is not MultipleToolComponentState tool) return;
|
_label.SetMarkup(_parent.StatusShowBehavior ? _parent.CurrentQualityName : string.Empty);
|
||||||
|
AddChild(_label);
|
||||||
_behavior = tool.QualityName;
|
|
||||||
_uiUpdateNeeded = true;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Control MakeControl() => new StatusControl(this);
|
protected override void FrameUpdate(FrameEventArgs args)
|
||||||
|
|
||||||
private sealed class StatusControl : Control
|
|
||||||
{
|
{
|
||||||
private readonly MultipleToolComponent _parent;
|
base.FrameUpdate(args);
|
||||||
private readonly RichTextLabel _label;
|
|
||||||
|
|
||||||
public StatusControl(MultipleToolComponent parent)
|
if (_parent.UiUpdateNeeded)
|
||||||
{
|
{
|
||||||
_parent = parent;
|
_parent.UiUpdateNeeded = false;
|
||||||
_label = new RichTextLabel {StyleClasses = {StyleNano.StyleClassItemStatus}};
|
|
||||||
AddChild(_label);
|
|
||||||
|
|
||||||
UpdateDraw();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void FrameUpdate(FrameEventArgs args)
|
|
||||||
{
|
|
||||||
base.FrameUpdate(args);
|
|
||||||
|
|
||||||
if (!_parent._uiUpdateNeeded)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Update();
|
Update();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void Update()
|
public void Update()
|
||||||
{
|
{
|
||||||
_parent._uiUpdateNeeded = false;
|
_label.SetMarkup(_parent.StatusShowBehavior ? _parent.CurrentQualityName : string.Empty);
|
||||||
|
|
||||||
_label.SetMarkup(_parent.StatusShowBehavior ? _parent.Behavior ?? string.Empty : string.Empty);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,48 @@
|
|||||||
|
using Content.Client.Items;
|
||||||
using Content.Client.Tools.Components;
|
using Content.Client.Tools.Components;
|
||||||
|
using Content.Shared.Tools;
|
||||||
using Content.Shared.Tools.Components;
|
using Content.Shared.Tools.Components;
|
||||||
using Robust.Shared.GameObjects;
|
using Robust.Client.GameObjects;
|
||||||
using Robust.Shared.GameStates;
|
using Robust.Shared.GameStates;
|
||||||
|
|
||||||
namespace Content.Client.Tools
|
namespace Content.Client.Tools
|
||||||
{
|
{
|
||||||
public sealed class ToolSystem : EntitySystem
|
public sealed class ToolSystem : SharedToolSystem
|
||||||
{
|
{
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
base.Initialize();
|
base.Initialize();
|
||||||
|
|
||||||
SubscribeLocalEvent<WelderComponent, ComponentHandleState>(OnWelderHandleState);
|
SubscribeLocalEvent<WelderComponent, ComponentHandleState>(OnWelderHandleState);
|
||||||
|
SubscribeLocalEvent<MultipleToolComponent, ItemStatusCollectMessage>(OnGetStatusMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void SetMultipleTool(EntityUid uid,
|
||||||
|
SharedMultipleToolComponent? multiple = null,
|
||||||
|
ToolComponent? tool = null,
|
||||||
|
bool playSound = false,
|
||||||
|
EntityUid? user = null)
|
||||||
|
{
|
||||||
|
if (!Resolve(uid, ref multiple))
|
||||||
|
return;
|
||||||
|
|
||||||
|
base.SetMultipleTool(uid, multiple, tool, playSound, user);
|
||||||
|
((MultipleToolComponent)multiple).UiUpdateNeeded = true;
|
||||||
|
|
||||||
|
// TODO replace this with appearance + visualizer
|
||||||
|
// in order to convert this to a specifier, the manner in which the sprite is specified in yaml needs to be updated.
|
||||||
|
|
||||||
|
if (multiple.Entries.Length > multiple.CurrentEntry && TryComp(uid, out SpriteComponent? sprite))
|
||||||
|
{
|
||||||
|
var current = multiple.Entries[multiple.CurrentEntry];
|
||||||
|
if (current.Sprite != null)
|
||||||
|
sprite.LayerSetSprite(0, current.Sprite);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnGetStatusMessage(EntityUid uid, MultipleToolComponent welder, ItemStatusCollectMessage args)
|
||||||
|
{
|
||||||
|
args.Controls.Add(new MultipleToolStatusControl(welder));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnWelderHandleState(EntityUid uid, WelderComponent welder, ref ComponentHandleState args)
|
private void OnWelderHandleState(EntityUid uid, WelderComponent welder, ref ComponentHandleState args)
|
||||||
|
|||||||
@@ -1,65 +0,0 @@
|
|||||||
using Content.Shared.Traitor.Uplink;
|
|
||||||
using JetBrains.Annotations;
|
|
||||||
using Robust.Client.GameObjects;
|
|
||||||
|
|
||||||
namespace Content.Client.Traitor.Uplink
|
|
||||||
{
|
|
||||||
[UsedImplicitly]
|
|
||||||
public sealed class UplinkBoundUserInterface : BoundUserInterface
|
|
||||||
{
|
|
||||||
private UplinkMenu? _menu;
|
|
||||||
|
|
||||||
public UplinkBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Open()
|
|
||||||
{
|
|
||||||
_menu = new UplinkMenu();
|
|
||||||
_menu.OpenCentered();
|
|
||||||
_menu.OnClose += Close;
|
|
||||||
|
|
||||||
_menu.OnListingButtonPressed += (_, listing) =>
|
|
||||||
{
|
|
||||||
SendMessage(new UplinkBuyListingMessage(listing.ItemId));
|
|
||||||
};
|
|
||||||
|
|
||||||
_menu.OnCategoryButtonPressed += (_, category) =>
|
|
||||||
{
|
|
||||||
_menu.CurrentFilterCategory = category;
|
|
||||||
SendMessage(new UplinkRequestUpdateInterfaceMessage());
|
|
||||||
};
|
|
||||||
|
|
||||||
_menu.OnWithdrawAttempt += (tc) =>
|
|
||||||
{
|
|
||||||
SendMessage(new UplinkTryWithdrawTC(tc));
|
|
||||||
};
|
|
||||||
}
|
|
||||||
protected override void UpdateState(BoundUserInterfaceState state)
|
|
||||||
{
|
|
||||||
base.UpdateState(state);
|
|
||||||
|
|
||||||
if (_menu == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
switch (state)
|
|
||||||
{
|
|
||||||
case UplinkUpdateState msg:
|
|
||||||
_menu.UpdateAccount(msg.Account);
|
|
||||||
_menu.UpdateListing(msg.Listings);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Dispose(bool disposing)
|
|
||||||
{
|
|
||||||
base.Dispose(disposing);
|
|
||||||
if (!disposing)
|
|
||||||
return;
|
|
||||||
|
|
||||||
_menu?.Close();
|
|
||||||
_menu?.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
<Control xmlns="https://spacestation14.io">
|
|
||||||
<BoxContainer Orientation="Vertical"
|
|
||||||
Margin="8 8 8 8">
|
|
||||||
<BoxContainer Orientation="Horizontal">
|
|
||||||
<Label Name="UplinkItemName"
|
|
||||||
HorizontalExpand="True"/>
|
|
||||||
<Button Name="UplinkItemBuyButton"
|
|
||||||
Access="Public"
|
|
||||||
HorizontalAlignment="Right"
|
|
||||||
MinWidth="64"/>
|
|
||||||
</BoxContainer>
|
|
||||||
<PanelContainer StyleClasses="HighDivider" />
|
|
||||||
<BoxContainer Orientation="Horizontal"
|
|
||||||
HorizontalExpand="True">
|
|
||||||
<TextureRect Name="UplinkItemTexture"
|
|
||||||
MinSize="48 48"
|
|
||||||
Margin="0 0 4 0"
|
|
||||||
Stretch="KeepAspectCentered"/>
|
|
||||||
<RichTextLabel Name="UplinkItemDescription" />
|
|
||||||
</BoxContainer>
|
|
||||||
</BoxContainer>
|
|
||||||
</Control>
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
using Robust.Client.AutoGenerated;
|
|
||||||
using Robust.Client.Graphics;
|
|
||||||
using Robust.Client.UserInterface;
|
|
||||||
using Robust.Client.UserInterface.Controls;
|
|
||||||
using Robust.Client.UserInterface.XAML;
|
|
||||||
using Robust.Shared.Maths;
|
|
||||||
|
|
||||||
namespace Content.Client.Traitor.Uplink
|
|
||||||
{
|
|
||||||
[GenerateTypedNameReferences]
|
|
||||||
public sealed partial class UplinkListingControl : Control
|
|
||||||
{
|
|
||||||
|
|
||||||
public UplinkListingControl(string itemName, string itemDescription,
|
|
||||||
int itemPrice, bool canBuy, Texture? texture = null)
|
|
||||||
{
|
|
||||||
RobustXamlLoader.Load(this);
|
|
||||||
|
|
||||||
UplinkItemName.Text = itemName;
|
|
||||||
UplinkItemDescription.SetMessage(itemDescription);
|
|
||||||
|
|
||||||
UplinkItemBuyButton.Text = $"{itemPrice} TC";
|
|
||||||
UplinkItemBuyButton.Disabled = !canBuy;
|
|
||||||
|
|
||||||
UplinkItemTexture.Texture = texture;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
<DefaultWindow xmlns="https://spacestation14.io"
|
|
||||||
xmlns:gfx="clr-namespace:Robust.Client.Graphics;assembly=Robust.Client"
|
|
||||||
Title="{Loc 'uplink-user-interface-title'}"
|
|
||||||
MinSize="512 512"
|
|
||||||
SetSize="512 512">
|
|
||||||
<BoxContainer Orientation="Vertical">
|
|
||||||
<BoxContainer Orientation="Vertical"
|
|
||||||
VerticalExpand="True">
|
|
||||||
<BoxContainer Orientation="Horizontal"
|
|
||||||
Margin="4 4 4 4">
|
|
||||||
<RichTextLabel Name="BalanceInfo"
|
|
||||||
Access="Public"
|
|
||||||
HorizontalExpand="True"
|
|
||||||
HorizontalAlignment="Left" />
|
|
||||||
<Button Name="WithdrawButton"
|
|
||||||
Text="{Loc 'uplink-user-interface-withdraw-button'}"
|
|
||||||
HorizontalAlignment="Right"
|
|
||||||
MinWidth="64"/>
|
|
||||||
</BoxContainer>
|
|
||||||
<PanelContainer VerticalExpand="True">
|
|
||||||
<PanelContainer.PanelOverride>
|
|
||||||
<gfx:StyleBoxFlat BackgroundColor="#000000FF" />
|
|
||||||
</PanelContainer.PanelOverride>
|
|
||||||
<BoxContainer Orientation="Horizontal"
|
|
||||||
VerticalExpand="True">
|
|
||||||
<PanelContainer VerticalExpand="True">
|
|
||||||
<PanelContainer.PanelOverride>
|
|
||||||
<gfx:StyleBoxFlat BackgroundColor="#80808005" />
|
|
||||||
</PanelContainer.PanelOverride>
|
|
||||||
<BoxContainer Name="CategoryListContainer"
|
|
||||||
Orientation="Vertical">
|
|
||||||
<!-- Category buttons are added here by code -->
|
|
||||||
</BoxContainer>
|
|
||||||
</PanelContainer>
|
|
||||||
<ScrollContainer Name="UplinkListingsScroll"
|
|
||||||
HorizontalExpand="True"
|
|
||||||
VerticalExpand="True"
|
|
||||||
SizeFlagsStretchRatio="2"
|
|
||||||
HScrollEnabled="False"
|
|
||||||
MinSize="100 256">
|
|
||||||
<BoxContainer Name="UplinkListingsContainer"
|
|
||||||
Orientation="Vertical"
|
|
||||||
VerticalExpand="True"
|
|
||||||
SizeFlagsStretchRatio="2"
|
|
||||||
MinSize="100 256">
|
|
||||||
<!-- Listings are added here by code -->
|
|
||||||
</BoxContainer>
|
|
||||||
</ScrollContainer>
|
|
||||||
</BoxContainer>
|
|
||||||
</PanelContainer>
|
|
||||||
</BoxContainer>
|
|
||||||
</BoxContainer>
|
|
||||||
</DefaultWindow>
|
|
||||||
@@ -1,163 +0,0 @@
|
|||||||
using Content.Client.Message;
|
|
||||||
using Content.Shared.PDA;
|
|
||||||
using Content.Shared.Traitor.Uplink;
|
|
||||||
using Robust.Client.AutoGenerated;
|
|
||||||
using Robust.Client.GameObjects;
|
|
||||||
using Robust.Client.ResourceManagement;
|
|
||||||
using Robust.Client.UserInterface.Controls;
|
|
||||||
using Robust.Client.UserInterface.CustomControls;
|
|
||||||
using Robust.Client.UserInterface.XAML;
|
|
||||||
using Robust.Client.Utility;
|
|
||||||
using Robust.Shared.Prototypes;
|
|
||||||
|
|
||||||
namespace Content.Client.Traitor.Uplink
|
|
||||||
{
|
|
||||||
[GenerateTypedNameReferences]
|
|
||||||
public sealed partial class UplinkMenu : DefaultWindow
|
|
||||||
{
|
|
||||||
[Dependency] private readonly IPrototypeManager _prototypeManager = default!;
|
|
||||||
[Dependency] private readonly IResourceCache _resourceCache = default!;
|
|
||||||
|
|
||||||
private UplinkWithdrawWindow? _withdrawWindow;
|
|
||||||
|
|
||||||
public event Action<BaseButton.ButtonEventArgs, UplinkListingData>? OnListingButtonPressed;
|
|
||||||
public event Action<BaseButton.ButtonEventArgs, UplinkCategory>? OnCategoryButtonPressed;
|
|
||||||
public event Action<int>? OnWithdrawAttempt;
|
|
||||||
|
|
||||||
private UplinkCategory _currentFilter;
|
|
||||||
private UplinkAccountData? _loggedInUplinkAccount;
|
|
||||||
|
|
||||||
public UplinkMenu()
|
|
||||||
{
|
|
||||||
RobustXamlLoader.Load(this);
|
|
||||||
IoCManager.InjectDependencies(this);
|
|
||||||
|
|
||||||
PopulateUplinkCategoryButtons();
|
|
||||||
WithdrawButton.OnButtonDown += OnWithdrawButtonDown;
|
|
||||||
}
|
|
||||||
|
|
||||||
public UplinkCategory CurrentFilterCategory
|
|
||||||
{
|
|
||||||
get => _currentFilter;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (value.GetType() != typeof(UplinkCategory))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_currentFilter = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void UpdateAccount(UplinkAccountData account)
|
|
||||||
{
|
|
||||||
_loggedInUplinkAccount = account;
|
|
||||||
|
|
||||||
// update balance label
|
|
||||||
var balance = account.DataBalance;
|
|
||||||
var weightedColor = balance switch
|
|
||||||
{
|
|
||||||
<= 0 => "gray",
|
|
||||||
<= 5 => "green",
|
|
||||||
<= 20 => "yellow",
|
|
||||||
<= 50 => "purple",
|
|
||||||
_ => "gray"
|
|
||||||
};
|
|
||||||
var balanceStr = Loc.GetString("uplink-bound-user-interface-tc-balance-popup",
|
|
||||||
("weightedColor", weightedColor),
|
|
||||||
("balance", balance));
|
|
||||||
BalanceInfo.SetMarkup(balanceStr);
|
|
||||||
|
|
||||||
// you can't withdraw if you don't have TC
|
|
||||||
WithdrawButton.Disabled = balance <= 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void UpdateListing(UplinkListingData[] listings)
|
|
||||||
{
|
|
||||||
// should probably chunk these out instead. to-do if this clogs the internet tubes.
|
|
||||||
// maybe read clients prototypes instead?
|
|
||||||
ClearListings();
|
|
||||||
foreach (var item in listings)
|
|
||||||
{
|
|
||||||
AddListingGui(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnWithdrawButtonDown(BaseButton.ButtonEventArgs args)
|
|
||||||
{
|
|
||||||
if (_loggedInUplinkAccount == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// check if window is already open
|
|
||||||
if (_withdrawWindow != null && _withdrawWindow.IsOpen)
|
|
||||||
{
|
|
||||||
_withdrawWindow.MoveToFront();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// open a new one
|
|
||||||
_withdrawWindow = new UplinkWithdrawWindow(_loggedInUplinkAccount.DataBalance);
|
|
||||||
_withdrawWindow.OpenCentered();
|
|
||||||
|
|
||||||
_withdrawWindow.OnWithdrawAttempt += OnWithdrawAttempt;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void AddListingGui(UplinkListingData listing)
|
|
||||||
{
|
|
||||||
if (!_prototypeManager.TryIndex(listing.ItemId, out EntityPrototype? prototype) || listing.Category != CurrentFilterCategory)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var listingName = listing.ListingName == string.Empty ? prototype.Name : listing.ListingName;
|
|
||||||
var listingDesc = listing.Description == string.Empty ? prototype.Description : listing.Description;
|
|
||||||
var listingPrice = listing.Price;
|
|
||||||
var canBuy = _loggedInUplinkAccount?.DataBalance >= listing.Price;
|
|
||||||
|
|
||||||
var texture = listing.Icon?.Frame0();
|
|
||||||
if (texture == null)
|
|
||||||
texture = SpriteComponent.GetPrototypeIcon(prototype, _resourceCache).Default;
|
|
||||||
|
|
||||||
var newListing = new UplinkListingControl(listingName, listingDesc, listingPrice, canBuy, texture);
|
|
||||||
newListing.UplinkItemBuyButton.OnButtonDown += args
|
|
||||||
=> OnListingButtonPressed?.Invoke(args, listing);
|
|
||||||
|
|
||||||
UplinkListingsContainer.AddChild(newListing);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ClearListings()
|
|
||||||
{
|
|
||||||
UplinkListingsContainer.Children.Clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void PopulateUplinkCategoryButtons()
|
|
||||||
{
|
|
||||||
foreach (UplinkCategory cat in Enum.GetValues(typeof(UplinkCategory)))
|
|
||||||
{
|
|
||||||
var catButton = new PDAUplinkCategoryButton
|
|
||||||
{
|
|
||||||
Text = Loc.GetString(cat.ToString()),
|
|
||||||
ButtonCategory = cat
|
|
||||||
};
|
|
||||||
//It'd be neat if it could play a cool tech ping sound when you switch categories,
|
|
||||||
//but right now there doesn't seem to be an easy way to do client-side audio without still having to round trip to the server and
|
|
||||||
//send to a specific client INetChannel.
|
|
||||||
catButton.OnPressed += args => OnCategoryButtonPressed?.Invoke(args, catButton.ButtonCategory);
|
|
||||||
|
|
||||||
CategoryListContainer.AddChild(catButton);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Close()
|
|
||||||
{
|
|
||||||
base.Close();
|
|
||||||
_withdrawWindow?.Close();
|
|
||||||
}
|
|
||||||
|
|
||||||
private sealed class PDAUplinkCategoryButton : Button
|
|
||||||
{
|
|
||||||
public UplinkCategory ButtonCategory;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
<DefaultWindow xmlns="https://spacestation14.io"
|
|
||||||
Title="{Loc 'uplink-user-interface-withdraw-title'}"
|
|
||||||
MinSize="256 128">
|
|
||||||
<BoxContainer Orientation="Vertical"
|
|
||||||
HorizontalExpand="True"
|
|
||||||
VerticalExpand="True">
|
|
||||||
<SliderIntInput Name="WithdrawSlider"
|
|
||||||
HorizontalExpand="True"/>
|
|
||||||
<BoxContainer Orientation="Horizontal"
|
|
||||||
VerticalExpand="True"
|
|
||||||
VerticalAlignment="Bottom">
|
|
||||||
<Button Name="ApplyButton"
|
|
||||||
Text="{Loc 'uplink-user-interface-withdraw-withdraw-button'}"
|
|
||||||
HorizontalAlignment="Left"
|
|
||||||
HorizontalExpand="True"/>
|
|
||||||
<Button Name="CancelButton"
|
|
||||||
Text="{Loc 'uplink-user-interface-withdraw-cancel-button'}"
|
|
||||||
HorizontalAlignment="Right"
|
|
||||||
HorizontalExpand="True"/>
|
|
||||||
</BoxContainer>
|
|
||||||
</BoxContainer>
|
|
||||||
</DefaultWindow>
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
using Robust.Client.AutoGenerated;
|
|
||||||
using Robust.Client.UserInterface.CustomControls;
|
|
||||||
using Robust.Client.UserInterface.XAML;
|
|
||||||
using Robust.Shared.Localization;
|
|
||||||
|
|
||||||
namespace Content.Client.Traitor.Uplink
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Window to select amount TC to withdraw from Uplink account
|
|
||||||
/// Used as sub-window in Uplink UI
|
|
||||||
/// </summary>
|
|
||||||
[GenerateTypedNameReferences]
|
|
||||||
public sealed partial class UplinkWithdrawWindow : DefaultWindow
|
|
||||||
{
|
|
||||||
public event System.Action<int>? OnWithdrawAttempt;
|
|
||||||
|
|
||||||
public UplinkWithdrawWindow(int tcCount)
|
|
||||||
{
|
|
||||||
RobustXamlLoader.Load(this);
|
|
||||||
|
|
||||||
// setup withdraw slider
|
|
||||||
WithdrawSlider.MinValue = 1;
|
|
||||||
WithdrawSlider.MaxValue = tcCount;
|
|
||||||
|
|
||||||
// and buttons
|
|
||||||
ApplyButton.OnButtonDown += _ =>
|
|
||||||
{
|
|
||||||
OnWithdrawAttempt?.Invoke(WithdrawSlider.Value);
|
|
||||||
Close();
|
|
||||||
};
|
|
||||||
CancelButton.OnButtonDown += _ => Close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -9,7 +9,7 @@ namespace Content.Client.UserInterface.Atmos.GasTank
|
|||||||
public sealed class GasTankBoundUserInterface
|
public sealed class GasTankBoundUserInterface
|
||||||
: BoundUserInterface
|
: BoundUserInterface
|
||||||
{
|
{
|
||||||
public GasTankBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) :
|
public GasTankBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) :
|
||||||
base(owner, uiKey)
|
base(owner, uiKey)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ namespace Content.Client.VendingMachines
|
|||||||
|
|
||||||
public SharedVendingMachineComponent? VendingMachine { get; private set; }
|
public SharedVendingMachineComponent? VendingMachine { get; private set; }
|
||||||
|
|
||||||
public VendingMachineBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
|
public VendingMachineBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey)
|
||||||
{
|
{
|
||||||
SendMessage(new InventorySyncRequestMessage());
|
SendMessage(new InventorySyncRequestMessage());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ public sealed class FlyBySoundSystem : SharedFlyBySoundSystem
|
|||||||
{
|
{
|
||||||
[Dependency] private readonly IPlayerManager _player = default!;
|
[Dependency] private readonly IPlayerManager _player = default!;
|
||||||
[Dependency] private readonly IRobustRandom _random = default!;
|
[Dependency] private readonly IRobustRandom _random = default!;
|
||||||
|
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||||
|
|
||||||
public override void Initialize()
|
public override void Initialize()
|
||||||
{
|
{
|
||||||
@@ -28,11 +29,18 @@ public sealed class FlyBySoundSystem : SharedFlyBySoundSystem
|
|||||||
if (attachedEnt == null ||
|
if (attachedEnt == null ||
|
||||||
args.OtherFixture.Body.Owner != attachedEnt ||
|
args.OtherFixture.Body.Owner != attachedEnt ||
|
||||||
TryComp<ProjectileComponent>(args.OurFixture.Body.Owner, out var projectile) &&
|
TryComp<ProjectileComponent>(args.OurFixture.Body.Owner, out var projectile) &&
|
||||||
projectile.Shooter == attachedEnt) return;
|
projectile.Shooter == attachedEnt)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (args.OurFixture.ID != FlyByFixture ||
|
if (args.OurFixture.ID != FlyByFixture ||
|
||||||
!_random.Prob(component.Prob)) return;
|
!_random.Prob(component.Prob))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
SoundSystem.Play(component.Sound.GetSound(), Filter.Local(), uid, component.Sound.Params);
|
// Play attached to our entity because the projectile may immediately delete or the likes.
|
||||||
|
_audio.Play(component.Sound, Filter.Local(), attachedEnt.Value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ namespace Content.Client.Wires.UI
|
|||||||
{
|
{
|
||||||
private WiresMenu? _menu;
|
private WiresMenu? _menu;
|
||||||
|
|
||||||
public WiresBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
|
public WiresBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,31 +0,0 @@
|
|||||||
using Content.Shared.Wires;
|
|
||||||
using Robust.Client.GameObjects;
|
|
||||||
using Robust.Shared.GameObjects;
|
|
||||||
using Robust.Shared.IoC;
|
|
||||||
|
|
||||||
namespace Content.Client.Wires.Visualizers
|
|
||||||
{
|
|
||||||
public sealed class WiresVisualizer : AppearanceVisualizer
|
|
||||||
{
|
|
||||||
[Obsolete("Subscribe to AppearanceChangeEvent instead.")]
|
|
||||||
public override void OnChangeData(AppearanceComponent component)
|
|
||||||
{
|
|
||||||
base.OnChangeData(component);
|
|
||||||
var sprite = IoCManager.Resolve<IEntityManager>().GetComponent<ISpriteComponent>(component.Owner);
|
|
||||||
if (component.TryGetData<bool>(WiresVisuals.MaintenancePanelState, out var state))
|
|
||||||
{
|
|
||||||
sprite.LayerSetVisible(WiresVisualLayers.MaintenancePanel, state);
|
|
||||||
}
|
|
||||||
// Mainly for spawn window
|
|
||||||
else
|
|
||||||
{
|
|
||||||
sprite.LayerSetVisible(WiresVisualLayers.MaintenancePanel, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum WiresVisualLayers : byte
|
|
||||||
{
|
|
||||||
MaintenancePanel,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
32
Content.Client/Wires/Visualizers/WiresVisualizerSystem.cs
Normal file
32
Content.Client/Wires/Visualizers/WiresVisualizerSystem.cs
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
using Content.Shared.Wires;
|
||||||
|
using Robust.Client.GameObjects;
|
||||||
|
|
||||||
|
namespace Content.Client.Wires.Visualizers
|
||||||
|
{
|
||||||
|
public sealed class WiresVisualizerSystem : VisualizerSystem<WiresVisualsComponent>
|
||||||
|
{
|
||||||
|
protected override void OnAppearanceChange(EntityUid uid, WiresVisualsComponent component, ref AppearanceChangeEvent args)
|
||||||
|
{
|
||||||
|
if (args.Sprite == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var layer = args.Sprite.LayerMapReserveBlank(WiresVisualLayers.MaintenancePanel);
|
||||||
|
|
||||||
|
if(args.AppearanceData.TryGetValue(WiresVisuals.MaintenancePanelState, out var panelStateObject) &&
|
||||||
|
panelStateObject is bool panelState)
|
||||||
|
{
|
||||||
|
args.Sprite.LayerSetVisible(layer, panelState);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//Mainly for spawn window
|
||||||
|
args.Sprite.LayerSetVisible(layer, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum WiresVisualLayers : byte
|
||||||
|
{
|
||||||
|
MaintenancePanel
|
||||||
|
}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user