Merge branch 'master' into air-alarm-fixup
This commit is contained in:
@@ -11,7 +11,7 @@ namespace Content.Client.AME.UI
|
||||
{
|
||||
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;
|
||||
|
||||
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 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);
|
||||
}
|
||||
|
||||
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)
|
||||
newFullName = newFullName[..MaxFullNameLength];
|
||||
@@ -70,7 +70,8 @@ namespace Content.Client.Access.UI
|
||||
SendMessage(new WriteToTargetIdMessage(
|
||||
newFullName,
|
||||
newJobTitle,
|
||||
newAccessList));
|
||||
newAccessList,
|
||||
newJobPrototype));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,8 +26,10 @@ namespace Content.Client.Access.UI
|
||||
|
||||
private string? _lastFullName;
|
||||
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);
|
||||
IoCManager.InjectDependencies(this);
|
||||
@@ -101,6 +103,7 @@ namespace Content.Client.Access.UI
|
||||
}
|
||||
|
||||
JobTitleLineEdit.Text = Loc.GetString(job.Name);
|
||||
args.Button.SelectId(args.Id);
|
||||
|
||||
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;
|
||||
_lastJobTitle = state.TargetIdJobTitle;
|
||||
_lastJobProto = state.TargetIdJobPrototype;
|
||||
}
|
||||
|
||||
private void SubmitData()
|
||||
{
|
||||
// Don't send this if it isn't dirty.
|
||||
var jobProtoDirty = _lastJobProto != null &&
|
||||
_jobPrototypeIds[JobPresetOptionButton.SelectedId] != _lastJobProto;
|
||||
|
||||
_owner.SubmitData(
|
||||
FullNameLineEdit.Text,
|
||||
JobTitleLineEdit.Text,
|
||||
// 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;
|
||||
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;
|
||||
|
||||
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 SpaceVillainArcadeBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
|
||||
public SpaceVillainArcadeBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey)
|
||||
{
|
||||
SendAction(PlayerAction.RequestData);
|
||||
}
|
||||
|
||||
@@ -18,9 +18,9 @@ public sealed class AirAlarmBoundUserInterface : BoundUserInterface
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
|
||||
|
||||
_window = new AirAlarmWindow();
|
||||
|
||||
|
||||
if (State != null) UpdateState(State);
|
||||
|
||||
_window.OpenCentered();
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace Content.Client.Atmos.UI
|
||||
{
|
||||
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;
|
||||
|
||||
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 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 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 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 _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 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.Timing;
|
||||
using System.Threading;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Timer = Robust.Shared.Timing.Timer;
|
||||
|
||||
namespace Content.Client.Audio
|
||||
@@ -44,7 +46,7 @@ namespace Content.Client.Audio
|
||||
/// <summary>
|
||||
/// What the ambience has been set to.
|
||||
/// </summary>
|
||||
private SoundCollectionPrototype _currentCollection = default!;
|
||||
private SoundCollectionPrototype? _currentCollection;
|
||||
private CancellationTokenSource _timerCancelTokenSource = new();
|
||||
|
||||
private SoundCollectionPrototype _spaceAmbience = default!;
|
||||
@@ -58,12 +60,22 @@ namespace Content.Client.Audio
|
||||
_spaceAmbience = _prototypeManager.Index<SoundCollectionPrototype>("SpaceAmbienceBase");
|
||||
_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.LobbyMusicEnabled, LobbyMusicCVarChanged);
|
||||
_configManager.OnValueChanged(CCVars.StationAmbienceEnabled, StationAmbienceCVarChanged);
|
||||
_configManager.OnValueChanged(CCVars.SpaceAmbienceEnabled, SpaceAmbienceCVarChanged);
|
||||
|
||||
SubscribeLocalEvent<PlayerAttachedEvent>(OnPlayerAttached);
|
||||
SubscribeLocalEvent<EntParentChangedMessage>(EntParentChanged);
|
||||
SubscribeLocalEvent<PlayerDetachedEvent>(OnPlayerDetached);
|
||||
|
||||
_stateManager.OnStateChanged += StateManagerOnStateChanged;
|
||||
|
||||
@@ -73,10 +85,28 @@ namespace Content.Client.Audio
|
||||
_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()
|
||||
{
|
||||
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;
|
||||
|
||||
_client.PlayerJoinedServer -= OnJoin;
|
||||
@@ -88,15 +118,12 @@ namespace Content.Client.Audio
|
||||
EndLobbyMusic();
|
||||
}
|
||||
|
||||
private void EntParentChanged(ref EntParentChangedMessage message)
|
||||
private void CheckAmbience(TransformComponent xform)
|
||||
{
|
||||
if(_playMan.LocalPlayer is null || _playMan.LocalPlayer.ControlledEntity != message.Entity ||
|
||||
!_timing.IsFirstTimePredicted) return;
|
||||
|
||||
// Check if we traversed to grid.
|
||||
if (message.Transform.GridUid != null)
|
||||
if (xform.GridUid != null)
|
||||
{
|
||||
if (_currentCollection == _stationAmbience) return;
|
||||
if (_currentCollection == _stationAmbience)
|
||||
return;
|
||||
ChangeAmbience(_stationAmbience);
|
||||
}
|
||||
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)
|
||||
{
|
||||
if (_currentCollection == newAmbience) return;
|
||||
@@ -172,7 +208,8 @@ namespace Content.Client.Audio
|
||||
private void StartAmbience()
|
||||
{
|
||||
EndAmbience();
|
||||
if (!CanPlayCollection(_currentCollection)) return;
|
||||
if (_currentCollection == null || !CanPlayCollection(_currentCollection))
|
||||
return;
|
||||
_playingCollection = _currentCollection;
|
||||
var file = _robustRandom.Pick(_currentCollection.PickFiles).ToString();
|
||||
_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)
|
||||
{
|
||||
if (_currentCollection == null)
|
||||
return;
|
||||
|
||||
if (enabled && _stateManager.CurrentState is GameScreen && _currentCollection.ID == _stationAmbience.ID)
|
||||
{
|
||||
StartAmbience();
|
||||
@@ -209,6 +249,9 @@ namespace Content.Client.Audio
|
||||
|
||||
private void SpaceAmbienceCVarChanged(bool enabled)
|
||||
{
|
||||
if (_currentCollection == null)
|
||||
return;
|
||||
|
||||
if (enabled && _stateManager.CurrentState is GameScreen && _currentCollection.ID == _spaceAmbience.ID)
|
||||
{
|
||||
StartAmbience();
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace Content.Client.Body.UI
|
||||
[ViewVariables]
|
||||
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()
|
||||
{
|
||||
|
||||
@@ -39,7 +39,7 @@ namespace Content.Client.Cargo.BUI
|
||||
/// </summary>
|
||||
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;
|
||||
|
||||
public CargoShuttleConsoleBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey) {}
|
||||
public CargoShuttleConsoleBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey) {}
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace Content.Client.CharacterAppearance
|
||||
{
|
||||
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;
|
||||
|
||||
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 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();
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
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);
|
||||
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. :(
|
||||
public ComputerBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey) {}
|
||||
public ComputerBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey) {}
|
||||
|
||||
protected override void UpdateState(BoundUserInterfaceState state)
|
||||
{
|
||||
@@ -58,7 +58,7 @@ namespace Content.Client.Computer
|
||||
[Virtual]
|
||||
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)
|
||||
{
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace Content.Client.Configurable.UI
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
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)
|
||||
continue;
|
||||
@@ -191,11 +191,11 @@ namespace Content.Client.Construction.UI
|
||||
var uniqueCategories = new HashSet<string>();
|
||||
|
||||
// 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>())
|
||||
{
|
||||
var category = Loc.GetString(prototype.Category);
|
||||
var category = prototype.Category;
|
||||
|
||||
if (!string.IsNullOrEmpty(category))
|
||||
uniqueCategories.Add(category);
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace Content.Client.Crayon.UI
|
||||
{
|
||||
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)
|
||||
{
|
||||
var name = new Label()
|
||||
var name = new RichTextLabel()
|
||||
{
|
||||
HorizontalExpand = true,
|
||||
Text = entry.Name
|
||||
};
|
||||
name.SetMessage(entry.Name);
|
||||
|
||||
var titleContainer = new BoxContainer()
|
||||
{
|
||||
@@ -143,10 +143,8 @@ public sealed partial class CrewManifestUi : DefaultWindow
|
||||
HorizontalExpand = true
|
||||
};
|
||||
|
||||
var title = new Label()
|
||||
{
|
||||
Text = Loc.GetString(entry.JobTitle)
|
||||
};
|
||||
var title = new RichTextLabel();
|
||||
title.SetMessage(Loc.GetString(entry.JobTitle));
|
||||
|
||||
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
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 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();
|
||||
CloseAnimation.AnimationTracks.Add(flickMaintenancePanel);
|
||||
flickMaintenancePanel.LayerKey = WiresVisualizer.WiresVisualLayers.MaintenancePanel;
|
||||
flickMaintenancePanel.LayerKey = WiresVisualLayers.MaintenancePanel;
|
||||
flickMaintenancePanel.KeyFrames.Add(new AnimationTrackSpriteFlick.KeyFrame("panel_closing", 0f));
|
||||
}
|
||||
}
|
||||
@@ -109,7 +109,7 @@ namespace Content.Client.Doors
|
||||
{
|
||||
var flickMaintenancePanel = new AnimationTrackSpriteFlick();
|
||||
OpenAnimation.AnimationTracks.Add(flickMaintenancePanel);
|
||||
flickMaintenancePanel.LayerKey = WiresVisualizer.WiresVisualLayers.MaintenancePanel;
|
||||
flickMaintenancePanel.LayerKey = WiresVisualLayers.MaintenancePanel;
|
||||
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()
|
||||
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)
|
||||
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.Changelog;
|
||||
using Content.Client.CharacterInterface;
|
||||
using Content.Client.Chat.Managers;
|
||||
using Content.Client.EscapeMenu;
|
||||
using Content.Client.Eui;
|
||||
using Content.Client.Eye.Blinding;
|
||||
using Content.Client.Flash;
|
||||
using Content.Client.GhostKick;
|
||||
using Content.Client.HUD;
|
||||
@@ -14,40 +12,33 @@ using Content.Client.Input;
|
||||
using Content.Client.IoC;
|
||||
using Content.Client.Launcher;
|
||||
using Content.Client.MainMenu;
|
||||
using Content.Client.MobState.Overlays;
|
||||
using Content.Client.Parallax;
|
||||
using Content.Client.Parallax.Managers;
|
||||
using Content.Client.Players.PlayTimeTracking;
|
||||
using Content.Client.Preferences;
|
||||
using Content.Client.Radiation;
|
||||
using Content.Client.Sandbox;
|
||||
using Content.Client.Screenshot;
|
||||
using Content.Client.Singularity;
|
||||
using Content.Client.Stylesheets;
|
||||
using Content.Client.Viewport;
|
||||
using Content.Client.Voting;
|
||||
using Content.Shared.Actions;
|
||||
using Content.Shared.Administration;
|
||||
using Content.Shared.Alert;
|
||||
using Content.Shared.AME;
|
||||
using Content.Shared.Cargo.Components;
|
||||
using Content.Shared.Chemistry.Components;
|
||||
using Content.Shared.Chemistry.Dispenser;
|
||||
using Content.Shared.Gravity;
|
||||
using Content.Shared.Lathe;
|
||||
using Content.Shared.Markers;
|
||||
using Content.Shared.Research.Components;
|
||||
using Content.Shared.VendingMachines;
|
||||
using Content.Shared.Wires;
|
||||
using Robust.Client;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Client.Player;
|
||||
using Robust.Client.State;
|
||||
using Robust.Client.UserInterface;
|
||||
#if FULL_RELEASE
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Configuration;
|
||||
#endif
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Prototypes;
|
||||
using Robust.Shared.Timing;
|
||||
@@ -130,6 +121,11 @@ namespace Content.Client.Entry
|
||||
|
||||
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();
|
||||
|
||||
_baseClient.PlayerJoinedServer += (_, _) =>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
<Label Text="{Loc 'ui-options-net-interp-ratio'}" />
|
||||
<Control MinSize="8 0" />
|
||||
<Slider Name="NetInterpRatioSlider"
|
||||
MinValue="0"
|
||||
ToolTip="{Loc 'ui-options-net-interp-ratio-tooltip'}"
|
||||
MaxValue="6"
|
||||
HorizontalExpand="True"
|
||||
MinSize="80 0"
|
||||
|
||||
@@ -1,15 +1,10 @@
|
||||
using System;
|
||||
using Content.Shared.CCVar;
|
||||
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;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Client.GameStates;
|
||||
|
||||
namespace Content.Client.EscapeMenu.UI.Tabs
|
||||
{
|
||||
@@ -17,6 +12,7 @@ namespace Content.Client.EscapeMenu.UI.Tabs
|
||||
public sealed partial class NetworkTab : Control
|
||||
{
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
[Dependency] private readonly IClientGameStateManager _stateMan = default!;
|
||||
|
||||
public NetworkTab()
|
||||
{
|
||||
@@ -26,6 +22,7 @@ namespace Content.Client.EscapeMenu.UI.Tabs
|
||||
ApplyButton.OnPressed += OnApplyButtonPressed;
|
||||
ResetButton.OnPressed += OnResetButtonPressed;
|
||||
NetInterpRatioSlider.OnValueChanged += OnNetInterpRatioSliderChanged;
|
||||
NetInterpRatioSlider.MinValue = _stateMan.MinBufferSize;
|
||||
|
||||
Reset();
|
||||
}
|
||||
@@ -45,7 +42,7 @@ namespace Content.Client.EscapeMenu.UI.Tabs
|
||||
|
||||
private void OnApplyButtonPressed(BaseButton.ButtonEventArgs args)
|
||||
{
|
||||
_cfg.SetCVar(CVars.NetInterpRatio, (int) NetInterpRatioSlider.Value);
|
||||
_cfg.SetCVar(CVars.NetBufferSize, (int) NetInterpRatioSlider.Value - _stateMan.MinBufferSize);
|
||||
_cfg.SaveToFile();
|
||||
UpdateChanges();
|
||||
}
|
||||
@@ -57,13 +54,13 @@ namespace Content.Client.EscapeMenu.UI.Tabs
|
||||
|
||||
private void Reset()
|
||||
{
|
||||
NetInterpRatioSlider.Value = _cfg.GetCVar(CVars.NetInterpRatio);
|
||||
NetInterpRatioSlider.Value = _cfg.GetCVar(CVars.NetBufferSize) + _stateMan.MinBufferSize;
|
||||
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;
|
||||
ResetButton.Disabled = isEverythingSame;
|
||||
NetInterpRatioLabel.Text = NetInterpRatioSlider.Value.ToString();
|
||||
|
||||
@@ -14,15 +14,13 @@ namespace Content.Client.Eye.Blinding
|
||||
|
||||
public override bool RequestScreenTexture => true;
|
||||
public override OverlaySpace Space => OverlaySpace.WorldSpace;
|
||||
private readonly ShaderInstance _blurryVisionXShader;
|
||||
private readonly ShaderInstance _blurryVisionYShader;
|
||||
private readonly ShaderInstance _dim;
|
||||
private BlurryVisionComponent _blurryVisionComponent = default!;
|
||||
|
||||
public BlurryVisionOverlay()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
_blurryVisionXShader = _prototypeManager.Index<ShaderPrototype>("BlurryVisionX").InstanceUnique();
|
||||
_blurryVisionYShader = _prototypeManager.Index<ShaderPrototype>("BlurryVisionY").InstanceUnique();
|
||||
_dim = _prototypeManager.Index<ShaderPrototype>("Dim").InstanceUnique();
|
||||
}
|
||||
|
||||
protected override bool BeforeDraw(in OverlayDrawArgs args)
|
||||
@@ -51,18 +49,17 @@ namespace Content.Client.Eye.Blinding
|
||||
if (ScreenTexture == null)
|
||||
return;
|
||||
|
||||
_blurryVisionXShader?.SetParameter("SCREEN_TEXTURE", ScreenTexture);
|
||||
_blurryVisionXShader?.SetParameter("BLUR_AMOUNT", (_blurryVisionComponent.Magnitude / 10));
|
||||
_blurryVisionYShader?.SetParameter("SCREEN_TEXTURE", ScreenTexture);
|
||||
_blurryVisionYShader?.SetParameter("BLUR_AMOUNT", (_blurryVisionComponent.Magnitude / 10));
|
||||
var opacity = -(_blurryVisionComponent.Magnitude / 15) + 0.9f;
|
||||
|
||||
_dim.SetParameter("DAMAGE_AMOUNT", opacity);
|
||||
|
||||
var worldHandle = args.WorldHandle;
|
||||
var viewport = args.WorldBounds;
|
||||
|
||||
worldHandle.UseShader(_dim);
|
||||
worldHandle.SetTransform(Matrix3.Identity);
|
||||
worldHandle.UseShader(_blurryVisionXShader);
|
||||
worldHandle.DrawRect(viewport, Color.White);
|
||||
worldHandle.UseShader(_blurryVisionYShader);
|
||||
worldHandle.DrawRect(viewport, Color.White);
|
||||
worldHandle.DrawRect(viewport, Color.Black);
|
||||
worldHandle.UseShader(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace Content.Client.Forensics
|
||||
{
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
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 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]
|
||||
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, 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;
|
||||
|
||||
public ReagentGrinderBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey) { }
|
||||
public ReagentGrinderBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey) { }
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Content.Client.Labels.UI
|
||||
{
|
||||
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;
|
||||
|
||||
if (value == 0 || !tracker.IsAllowed(prototype, out reason))
|
||||
if (!tracker.IsAllowed(prototype, out reason))
|
||||
{
|
||||
jobButton.Disabled = true;
|
||||
|
||||
@@ -256,6 +256,10 @@ namespace Content.Client.LateJoin
|
||||
jobButton.ToolTip = reason;
|
||||
}
|
||||
}
|
||||
else if (value == 0)
|
||||
{
|
||||
jobButton.Disabled = true;
|
||||
}
|
||||
|
||||
_jobButtons[id][prototype.ID] = jobButton;
|
||||
}
|
||||
|
||||
@@ -2,8 +2,6 @@ using Robust.Client.GameObjects;
|
||||
using Content.Shared.Lathe;
|
||||
using Content.Shared.Power;
|
||||
using Content.Client.Power;
|
||||
using Content.Client.Wires.Visualizers;
|
||||
using Content.Shared.Wires;
|
||||
|
||||
namespace Content.Client.Lathe
|
||||
{
|
||||
@@ -20,12 +18,6 @@ namespace Content.Client.Lathe
|
||||
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
|
||||
if (args.Component.TryGetData(LatheVisuals.IsRunning, out bool isRunning))
|
||||
{
|
||||
|
||||
@@ -29,7 +29,7 @@ namespace Content.Client.Lathe.UI
|
||||
public Queue<LatheRecipePrototype> QueuedRecipes => _queuedRecipes;
|
||||
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());
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace Content.Client.MachineLinking.UI
|
||||
private string? _selectedTransmitterPort;
|
||||
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()
|
||||
{
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace Content.Client.Medical.CrewMonitoring
|
||||
{
|
||||
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 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;
|
||||
|
||||
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;
|
||||
|
||||
public PDABoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
|
||||
public PDABoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey)
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ namespace Content.Client.PDA.Ringer
|
||||
{
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
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]
|
||||
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]
|
||||
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;
|
||||
|
||||
public ResearchClientBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
|
||||
public ResearchClientBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey)
|
||||
{
|
||||
SendMessage(new ResearchClientSyncMessage());
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace Content.Client.Research.UI
|
||||
private ResearchConsoleMenu? _consoleMenu;
|
||||
private TechnologyDatabaseComponent? _technologyDatabase;
|
||||
|
||||
public ResearchConsoleBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
|
||||
public ResearchConsoleBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey)
|
||||
{
|
||||
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]
|
||||
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;
|
||||
|
||||
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;
|
||||
|
||||
public RadarConsoleBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey) {}
|
||||
public RadarConsoleBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey) {}
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
|
||||
@@ -12,7 +12,7 @@ public sealed class ShuttleConsoleBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
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()
|
||||
{
|
||||
|
||||
@@ -7,7 +7,7 @@ public sealed class GeneralStationRecordConsoleBoundUserInterface : BoundUserInt
|
||||
{
|
||||
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()
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace Content.Client.Storage
|
||||
{
|
||||
[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
|
||||
{
|
||||
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">
|
||||
<BoxContainer Margin="8,8,8,8" Orientation="Vertical">
|
||||
<BoxContainer Orientation="Horizontal">
|
||||
<Label Name="RevenantItemName" HorizontalExpand="True" />
|
||||
<Label Name="StoreItemName" HorizontalExpand="True" />
|
||||
<Button
|
||||
Name="RevenantItemBuyButton"
|
||||
Name="StoreItemBuyButton"
|
||||
MinWidth="64"
|
||||
HorizontalAlignment="Right"
|
||||
Access="Public" />
|
||||
@@ -11,11 +11,11 @@
|
||||
<PanelContainer StyleClasses="HighDivider" />
|
||||
<BoxContainer HorizontalExpand="True" Orientation="Horizontal">
|
||||
<TextureRect
|
||||
Name="RevenantItemTexture"
|
||||
Name="StoreItemTexture"
|
||||
Margin="0,0,4,0"
|
||||
MinSize="48 48"
|
||||
Stretch="KeepAspectCentered" />
|
||||
<RichTextLabel Name="RevenantItemDescription" />
|
||||
<RichTextLabel Name="StoreItemDescription" />
|
||||
</BoxContainer>
|
||||
</BoxContainer>
|
||||
</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
|
||||
xmlns="https://spacestation14.io"
|
||||
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"
|
||||
SetSize="512 512">
|
||||
<BoxContainer Orientation="Vertical">
|
||||
@@ -12,21 +12,34 @@
|
||||
HorizontalAlignment="Left"
|
||||
Access="Public"
|
||||
HorizontalExpand="True" />
|
||||
<Button
|
||||
Name="WithdrawButton"
|
||||
MinWidth="64"
|
||||
HorizontalAlignment="Right"
|
||||
Text="{Loc 'store-ui-default-withdraw-text'}" />
|
||||
</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="RevenantListingsScroll"
|
||||
Name="StoreListingsScroll"
|
||||
HScrollEnabled="False"
|
||||
HorizontalExpand="True"
|
||||
MinSize="100 256"
|
||||
SizeFlagsStretchRatio="2"
|
||||
VerticalExpand="True">
|
||||
<BoxContainer
|
||||
Name="RevenantListingsContainer"
|
||||
Name="StoreListingsContainer"
|
||||
MinSize="100 256"
|
||||
Orientation="Vertical"
|
||||
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 EntityUid? _currentCamera;
|
||||
|
||||
public SurveillanceCameraMonitorBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
|
||||
public SurveillanceCameraMonitorBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) : base(owner, uiKey)
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
_eyeLerpingSystem = _entityManager.EntitySysManager.GetEntitySystem<EyeLerpingSystem>();
|
||||
|
||||
@@ -8,7 +8,7 @@ public sealed class SurveillanceCameraSetupBoundUi : BoundUserInterface
|
||||
private SurveillanceCameraSetupWindow? _window;
|
||||
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)
|
||||
{
|
||||
|
||||
@@ -1,72 +1,50 @@
|
||||
using Content.Client.Items.Components;
|
||||
using Content.Client.Message;
|
||||
using Content.Client.Stylesheets;
|
||||
using Content.Shared.Tools.Components;
|
||||
using Robust.Client.UserInterface;
|
||||
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.ViewVariables;
|
||||
|
||||
namespace Content.Client.Tools.Components
|
||||
{
|
||||
[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")]
|
||||
private bool _statusShowBehavior = true;
|
||||
public bool StatusShowBehavior = true;
|
||||
}
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)] private bool _uiUpdateNeeded;
|
||||
[ViewVariables] public bool StatusShowBehavior => _statusShowBehavior;
|
||||
[ViewVariables] public string? Behavior => _behavior;
|
||||
public sealed class MultipleToolStatusControl : Control
|
||||
{
|
||||
private readonly MultipleToolComponent _parent;
|
||||
private readonly RichTextLabel _label;
|
||||
|
||||
public override void HandleComponentState(ComponentState? curState, ComponentState? nextState)
|
||||
public MultipleToolStatusControl(MultipleToolComponent parent)
|
||||
{
|
||||
base.HandleComponentState(curState, nextState);
|
||||
|
||||
if (curState is not MultipleToolComponentState tool) return;
|
||||
|
||||
_behavior = tool.QualityName;
|
||||
_uiUpdateNeeded = true;
|
||||
|
||||
_parent = parent;
|
||||
_label = new RichTextLabel { StyleClasses = { StyleNano.StyleClassItemStatus } };
|
||||
_label.SetMarkup(_parent.StatusShowBehavior ? _parent.CurrentQualityName : string.Empty);
|
||||
AddChild(_label);
|
||||
}
|
||||
|
||||
public Control MakeControl() => new StatusControl(this);
|
||||
|
||||
private sealed class StatusControl : Control
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
private readonly MultipleToolComponent _parent;
|
||||
private readonly RichTextLabel _label;
|
||||
base.FrameUpdate(args);
|
||||
|
||||
public StatusControl(MultipleToolComponent parent)
|
||||
if (_parent.UiUpdateNeeded)
|
||||
{
|
||||
_parent = parent;
|
||||
_label = new RichTextLabel {StyleClasses = {StyleNano.StyleClassItemStatus}};
|
||||
AddChild(_label);
|
||||
|
||||
UpdateDraw();
|
||||
}
|
||||
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
base.FrameUpdate(args);
|
||||
|
||||
if (!_parent._uiUpdateNeeded)
|
||||
{
|
||||
return;
|
||||
}
|
||||
_parent.UiUpdateNeeded = false;
|
||||
Update();
|
||||
}
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
_parent._uiUpdateNeeded = false;
|
||||
|
||||
_label.SetMarkup(_parent.StatusShowBehavior ? _parent.Behavior ?? string.Empty : string.Empty);
|
||||
}
|
||||
public void Update()
|
||||
{
|
||||
_label.SetMarkup(_parent.StatusShowBehavior ? _parent.CurrentQualityName : string.Empty);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,48 @@
|
||||
using Content.Client.Items;
|
||||
using Content.Client.Tools.Components;
|
||||
using Content.Shared.Tools;
|
||||
using Content.Shared.Tools.Components;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.GameStates;
|
||||
|
||||
namespace Content.Client.Tools
|
||||
{
|
||||
public sealed class ToolSystem : EntitySystem
|
||||
public sealed class ToolSystem : SharedToolSystem
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
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)
|
||||
|
||||
@@ -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
|
||||
: BoundUserInterface
|
||||
{
|
||||
public GasTankBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) :
|
||||
public GasTankBoundUserInterface(ClientUserInterfaceComponent owner, Enum uiKey) :
|
||||
base(owner, uiKey)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace Content.Client.VendingMachines
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ public sealed class FlyBySoundSystem : SharedFlyBySoundSystem
|
||||
{
|
||||
[Dependency] private readonly IPlayerManager _player = default!;
|
||||
[Dependency] private readonly IRobustRandom _random = default!;
|
||||
[Dependency] private readonly SharedAudioSystem _audio = default!;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
@@ -28,11 +29,18 @@ public sealed class FlyBySoundSystem : SharedFlyBySoundSystem
|
||||
if (attachedEnt == null ||
|
||||
args.OtherFixture.Body.Owner != attachedEnt ||
|
||||
TryComp<ProjectileComponent>(args.OurFixture.Body.Owner, out var projectile) &&
|
||||
projectile.Shooter == attachedEnt) return;
|
||||
projectile.Shooter == attachedEnt)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user