Merge branch 'master' into 2020-08-19-firelocks
# Conflicts: # Content.Server/GameObjects/Components/Doors/ServerDoorComponent.cs # Content.Shared/Maps/TurfHelpers.cs # SpaceStation14.sln.DotSettings
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
using Content.Shared.GameObjects.Components;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects.Components.UserInterface;
|
||||
|
||||
namespace Content.Client.GameObjects.Components
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public class AcceptCloningBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
|
||||
public AcceptCloningBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
|
||||
{
|
||||
}
|
||||
|
||||
private AcceptCloningWindow _window;
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
|
||||
_window = new AcceptCloningWindow();
|
||||
_window.OnClose += Close;
|
||||
_window.DenyButton.OnPressed += _ => _window.Close();
|
||||
_window.ConfirmButton.OnPressed += _ =>
|
||||
{
|
||||
SendMessage(
|
||||
new SharedAcceptCloningComponent.UiButtonPressedMessage(
|
||||
SharedAcceptCloningComponent.UiButton.Accept));
|
||||
_window.Close();
|
||||
};
|
||||
_window.OpenCentered();
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
_window?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
50
Content.Client/GameObjects/Components/AcceptCloningWindow.cs
Normal file
50
Content.Client/GameObjects/Components/AcceptCloningWindow.cs
Normal file
@@ -0,0 +1,50 @@
|
||||
#nullable enable
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Shared.Localization;
|
||||
|
||||
namespace Content.Client.GameObjects.Components
|
||||
{
|
||||
public sealed class AcceptCloningWindow : SS14Window
|
||||
{
|
||||
public readonly Button DenyButton;
|
||||
public readonly Button ConfirmButton;
|
||||
|
||||
public AcceptCloningWindow()
|
||||
{
|
||||
|
||||
Title = Loc.GetString("Cloning Machine");
|
||||
|
||||
Contents.AddChild(new VBoxContainer
|
||||
{
|
||||
Children =
|
||||
{
|
||||
new VBoxContainer
|
||||
{
|
||||
Children =
|
||||
{
|
||||
(new Label
|
||||
{
|
||||
Text = Loc.GetString("You are being cloned! Transfer your soul to the clone body?")
|
||||
}),
|
||||
new HBoxContainer
|
||||
{
|
||||
Children =
|
||||
{
|
||||
(ConfirmButton = new Button
|
||||
{
|
||||
Text = Loc.GetString("Yes"),
|
||||
}),
|
||||
(DenyButton = new Button
|
||||
{
|
||||
Text = Loc.GetString("No"),
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Linq;
|
||||
using Content.Client.GameObjects.Components.Mobs;
|
||||
using Content.Client.UserInterface;
|
||||
using Content.Shared.GameObjects.Components.Mobs;
|
||||
using Content.Shared.Input;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Interfaces.Input;
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
using Content.Shared.GameObjects.Components.Atmos;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Interfaces.GameObjects.Components;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Interfaces.ResourceManagement;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.GameObjects.Components.Renderable;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Utility;
|
||||
using System;
|
||||
using YamlDotNet.RepresentationModel;
|
||||
|
||||
namespace Content.Client.GameObjects.Components.Atmos
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public class PipeVisualizer : AppearanceVisualizer
|
||||
{
|
||||
private RSI _pipeRSI;
|
||||
|
||||
public override void LoadData(YamlMappingNode node)
|
||||
{
|
||||
base.LoadData(node);
|
||||
|
||||
var rsiString = node.GetNode("pipeRSI").ToString();
|
||||
var rsiPath = SharedSpriteComponent.TextureRoot / rsiString;
|
||||
try
|
||||
{
|
||||
var resourceCache = IoCManager.Resolve<IResourceCache>();
|
||||
var resource = resourceCache.GetResource<RSIResource>(rsiPath);
|
||||
_pipeRSI = resource.RSI;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.ErrorS("go.pipevisualizer", "Unable to load RSI '{0}'. Trace:\n{1}", rsiPath, e);
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnChangeData(AppearanceComponent component)
|
||||
{
|
||||
base.OnChangeData(component);
|
||||
|
||||
if (!component.Owner.TryGetComponent(out ISpriteComponent sprite))
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (!component.TryGetData(PipeVisuals.VisualState, out PipeVisualStateSet pipeVisualStateSet))
|
||||
{
|
||||
return;
|
||||
}
|
||||
for (var i = 0; i < pipeVisualStateSet.PipeVisualStates.Length; i++)
|
||||
{
|
||||
var pipeVisualState = pipeVisualStateSet.PipeVisualStates[i];
|
||||
var rsiState = "pipe";
|
||||
rsiState += pipeVisualState.PipeDirection.ToString();
|
||||
rsiState += ((int) pipeVisualState.ConduitLayer).ToString();
|
||||
|
||||
var pipeLayerKey = "pipeLayer" + i.ToString();
|
||||
sprite.LayerMapReserveBlank(pipeLayerKey);
|
||||
var currentPipeLayer = sprite.LayerMapGet(pipeLayerKey);
|
||||
sprite.LayerSetRSI(currentPipeLayer, _pipeRSI);
|
||||
sprite.LayerSetState(currentPipeLayer, rsiState);
|
||||
sprite.LayerSetVisible(currentPipeLayer, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
using Content.Shared.GameObjects.Atmos;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Interfaces.GameObjects.Components;
|
||||
using Robust.Client.Interfaces.ResourceManagement;
|
||||
using Robust.Client.ResourceManagement;
|
||||
using Robust.Shared.GameObjects.Components.Renderable;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Utility;
|
||||
using System;
|
||||
using YamlDotNet.RepresentationModel;
|
||||
|
||||
namespace Content.Client.GameObjects.Components.Atmos
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public class PumpVisualizer : AppearanceVisualizer
|
||||
{
|
||||
private RSI _pumpRSI;
|
||||
|
||||
public override void LoadData(YamlMappingNode node)
|
||||
{
|
||||
base.LoadData(node);
|
||||
|
||||
var rsiString = node.GetNode("pumpRSI").ToString();
|
||||
var rsiPath = SharedSpriteComponent.TextureRoot / rsiString;
|
||||
try
|
||||
{
|
||||
var resourceCache = IoCManager.Resolve<IResourceCache>();
|
||||
var resource = resourceCache.GetResource<RSIResource>(rsiPath);
|
||||
_pumpRSI = resource.RSI;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Logger.ErrorS("go.pumpvisualizer", "Unable to load RSI '{0}'. Trace:\n{1}", rsiPath, e);
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnChangeData(AppearanceComponent component)
|
||||
{
|
||||
base.OnChangeData(component);
|
||||
|
||||
if (!component.Owner.TryGetComponent(out ISpriteComponent sprite))
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (!component.TryGetData(PumpVisuals.VisualState, out PumpVisualState pumpVisualState))
|
||||
{
|
||||
return;
|
||||
}
|
||||
var pumpBaseState = "pump";
|
||||
pumpBaseState += pumpVisualState.InletDirection.ToString();
|
||||
pumpBaseState += ((int) pumpVisualState.InletConduitLayer).ToString();
|
||||
pumpBaseState += pumpVisualState.OutletDirection.ToString();
|
||||
pumpBaseState += ((int) pumpVisualState.OutletConduitLayer).ToString();
|
||||
|
||||
sprite.LayerMapReserveBlank(Layer.PumpBase);
|
||||
var basePumpLayer = sprite.LayerMapGet(Layer.PumpBase);
|
||||
sprite.LayerSetRSI(basePumpLayer, _pumpRSI);
|
||||
sprite.LayerSetState(basePumpLayer, pumpBaseState);
|
||||
sprite.LayerSetVisible(basePumpLayer, true);
|
||||
|
||||
|
||||
|
||||
var pumpEnabledAnimationState = "pumpEnabled";
|
||||
pumpEnabledAnimationState += pumpVisualState.InletDirection.ToString();
|
||||
pumpEnabledAnimationState += ((int) pumpVisualState.InletConduitLayer).ToString();
|
||||
pumpEnabledAnimationState += pumpVisualState.OutletDirection.ToString();
|
||||
pumpEnabledAnimationState += ((int) pumpVisualState.OutletConduitLayer).ToString();
|
||||
|
||||
sprite.LayerMapReserveBlank(Layer.PumpEnabled);
|
||||
var pumpEnabledAnimationLayer = sprite.LayerMapGet(Layer.PumpEnabled);
|
||||
sprite.LayerSetRSI(pumpEnabledAnimationLayer, _pumpRSI);
|
||||
sprite.LayerSetState(pumpEnabledAnimationLayer, pumpEnabledAnimationState);
|
||||
sprite.LayerSetVisible(pumpEnabledAnimationLayer, pumpVisualState.PumpEnabled);
|
||||
}
|
||||
|
||||
private enum Layer
|
||||
{
|
||||
PumpBase,
|
||||
PumpEnabled,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
#nullable enable
|
||||
using Content.Client.GameObjects.Components.Disposal;
|
||||
using Content.Client.GameObjects.Components.MedicalScanner;
|
||||
using Content.Client.Interfaces.GameObjects.Components.Interaction;
|
||||
using Content.Shared.GameObjects.Components.Body;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.Components.Medical;
|
||||
using Robust.Client.Interfaces.GameObjects.Components;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
@@ -14,14 +16,20 @@ namespace Content.Client.GameObjects.Components.Body
|
||||
{
|
||||
[RegisterComponent]
|
||||
[ComponentReference(typeof(IDamageableComponent))]
|
||||
[ComponentReference(typeof(IBodyManagerComponent))]
|
||||
[ComponentReference(typeof(ISharedBodyManagerComponent))]
|
||||
public class BodyManagerComponent : SharedBodyManagerComponent, IClientDraggable
|
||||
{
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
|
||||
public bool ClientCanDropOn(CanDropEventArgs eventArgs)
|
||||
{
|
||||
return eventArgs.Target.HasComponent<DisposalUnitComponent>();
|
||||
if (
|
||||
eventArgs.Target.HasComponent<DisposalUnitComponent>()||
|
||||
eventArgs.Target.HasComponent<MedicalScannerComponent>())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool ClientCanDrag(CanDragEventArgs eventArgs)
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Content.Shared.GameObjects.Components.Medical;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects.Components.UserInterface;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameObjects.Components.UserInterface;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using static Content.Shared.GameObjects.Components.Medical.SharedCloningPodComponent;
|
||||
|
||||
namespace Content.Client.GameObjects.Components.CloningPod
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public class CloningPodBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
public CloningPodBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
|
||||
{
|
||||
}
|
||||
|
||||
private CloningPodWindow _window;
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
|
||||
|
||||
_window = new CloningPodWindow(new Dictionary<int, string>());
|
||||
_window.OnClose += Close;
|
||||
_window.CloneButton.OnPressed += _ =>
|
||||
{
|
||||
if (_window.SelectedScan != null)
|
||||
{
|
||||
SendMessage(new CloningPodUiButtonPressedMessage(UiButton.Clone, (int) _window.SelectedScan));
|
||||
}
|
||||
};
|
||||
_window.EjectButton.OnPressed += _ =>
|
||||
{
|
||||
SendMessage(new CloningPodUiButtonPressedMessage(UiButton.Eject, null));
|
||||
};
|
||||
_window.OpenCentered();
|
||||
}
|
||||
|
||||
protected override void UpdateState(BoundUserInterfaceState state)
|
||||
{
|
||||
base.UpdateState(state);
|
||||
_window.Populate((CloningPodBoundUserInterfaceState) state);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
_window?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
using System;
|
||||
using Content.Shared.GameObjects.Components.Medical;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Interfaces.GameObjects.Components;
|
||||
using static Content.Shared.GameObjects.Components.Medical.SharedCloningPodComponent;
|
||||
using static Content.Shared.GameObjects.Components.Medical.SharedCloningPodComponent.CloningPodStatus;
|
||||
|
||||
namespace Content.Client.GameObjects.Components.CloningPod
|
||||
{
|
||||
public class CloningPodVisualizer : AppearanceVisualizer
|
||||
{
|
||||
public override void OnChangeData(AppearanceComponent component)
|
||||
{
|
||||
base.OnChangeData(component);
|
||||
|
||||
var sprite = component.Owner.GetComponent<ISpriteComponent>();
|
||||
if (!component.TryGetData(CloningPodVisuals.Status, out CloningPodStatus status)) return;
|
||||
sprite.LayerSetState(CloningPodVisualLayers.Machine, StatusToMachineStateId(status));
|
||||
}
|
||||
|
||||
private string StatusToMachineStateId(CloningPodStatus status)
|
||||
{
|
||||
//TODO: implement NoMind for if the mind is not yet in the body
|
||||
//TODO: Find a use for GORE POD
|
||||
switch (status)
|
||||
{
|
||||
case Cloning: return "pod_1";
|
||||
case NoMind: return "pod_e";
|
||||
case Gore: return "pod_g";
|
||||
case Idle: return "pod_0";
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(status), status, "unknown CloningPodStatus");
|
||||
}
|
||||
}
|
||||
|
||||
public enum CloningPodVisualLayers
|
||||
{
|
||||
Machine,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,442 @@
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.Localization;
|
||||
using static Content.Shared.GameObjects.Components.Medical.SharedCloningPodComponent;
|
||||
|
||||
namespace Content.Client.GameObjects.Components.CloningPod
|
||||
{
|
||||
public sealed class CloningPodWindow : SS14Window
|
||||
{
|
||||
private Dictionary<int, string> _scanManager;
|
||||
|
||||
private readonly VBoxContainer _mainVBox;
|
||||
private readonly ScanListContainer _scanList;
|
||||
private readonly LineEdit _searchBar;
|
||||
private readonly Button _clearButton;
|
||||
public readonly Button CloneButton;
|
||||
public readonly Button EjectButton;
|
||||
private readonly CloningScanButton _measureButton;
|
||||
private CloningScanButton? _selectedButton;
|
||||
private Label _progressLabel;
|
||||
private readonly ProgressBar _cloningProgressBar;
|
||||
private Label _mindState;
|
||||
|
||||
protected override Vector2 ContentsMinimumSize => _mainVBox?.CombinedMinimumSize ?? Vector2.Zero;
|
||||
private CloningPodBoundUserInterfaceState _lastUpdate = null!;
|
||||
|
||||
// List of scans that are visible based on current filter criteria.
|
||||
private readonly Dictionary<int, string> _filteredScans = new Dictionary<int, string>();
|
||||
|
||||
// The indices of the visible scans last time UpdateVisibleScans was ran.
|
||||
// This is inclusive, so end is the index of the last scan, not right after it.
|
||||
private (int start, int end) _lastScanIndices;
|
||||
|
||||
public int? SelectedScan;
|
||||
|
||||
protected override Vector2? CustomSize => (250, 300);
|
||||
|
||||
public CloningPodWindow(
|
||||
Dictionary<int, string> scanManager)
|
||||
{
|
||||
_scanManager = scanManager;
|
||||
|
||||
|
||||
Title = Loc.GetString("Cloning Machine");
|
||||
|
||||
Contents.AddChild(_mainVBox = new VBoxContainer
|
||||
{
|
||||
Children =
|
||||
{
|
||||
new HBoxContainer
|
||||
{
|
||||
Children =
|
||||
{
|
||||
(_searchBar = new LineEdit
|
||||
{
|
||||
SizeFlagsHorizontal = SizeFlags.FillExpand,
|
||||
PlaceHolder = Loc.GetString("Search")
|
||||
}),
|
||||
|
||||
(_clearButton = new Button
|
||||
{
|
||||
Disabled = true,
|
||||
Text = Loc.GetString("Clear"),
|
||||
})
|
||||
}
|
||||
},
|
||||
new ScrollContainer
|
||||
{
|
||||
CustomMinimumSize = new Vector2(200.0f, 0.0f),
|
||||
SizeFlagsVertical = SizeFlags.FillExpand,
|
||||
Children =
|
||||
{
|
||||
(_scanList = new ScanListContainer())
|
||||
}
|
||||
},
|
||||
new VBoxContainer
|
||||
{
|
||||
Children =
|
||||
{
|
||||
(CloneButton = new Button
|
||||
{
|
||||
Text = Loc.GetString("Clone")
|
||||
})
|
||||
}
|
||||
},
|
||||
(_measureButton = new CloningScanButton {Visible = false}),
|
||||
(_cloningProgressBar = new ProgressBar
|
||||
{
|
||||
CustomMinimumSize = (200, 20),
|
||||
SizeFlagsHorizontal = SizeFlags.Fill,
|
||||
MinValue = 0,
|
||||
MaxValue = 10,
|
||||
Page = 0,
|
||||
Value = 0.5f,
|
||||
Children =
|
||||
{
|
||||
(_progressLabel = new Label())
|
||||
}
|
||||
}),
|
||||
(EjectButton = new Button
|
||||
{
|
||||
Text = Loc.GetString("Eject Body")
|
||||
}),
|
||||
new HBoxContainer
|
||||
{
|
||||
Children =
|
||||
{
|
||||
new Label()
|
||||
{
|
||||
Text = Loc.GetString("Neural Interface: ")
|
||||
},
|
||||
(_mindState = new Label()
|
||||
{
|
||||
Text = Loc.GetString("No Activity"),
|
||||
FontColorOverride = Color.Red
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
_searchBar.OnTextChanged += OnSearchBarTextChanged;
|
||||
_clearButton.OnPressed += OnClearButtonPressed;
|
||||
|
||||
BuildEntityList();
|
||||
|
||||
_searchBar.GrabKeyboardFocus();
|
||||
}
|
||||
|
||||
public void Populate(CloningPodBoundUserInterfaceState state)
|
||||
{
|
||||
//Ignore useless updates or we can't interact with the UI
|
||||
//TODO: come up with a better comparision, probably write a comparator because '.Equals' doesn't work
|
||||
if (_lastUpdate == null || _lastUpdate.MindIdName.Count != state.MindIdName.Count)
|
||||
{
|
||||
_scanManager = state.MindIdName;
|
||||
BuildEntityList();
|
||||
_lastUpdate = state;
|
||||
}
|
||||
|
||||
var percentage = state.Progress / _cloningProgressBar.MaxValue * 100;
|
||||
_progressLabel.Text = $"{percentage:0}%";
|
||||
|
||||
_cloningProgressBar.Value = state.Progress;
|
||||
_mindState.Text = Loc.GetString(state.MindPresent ? "Consciousness Detected" : "No Activity");
|
||||
_mindState.FontColorOverride = state.MindPresent ? Color.Green : Color.Red;
|
||||
}
|
||||
|
||||
private void OnSearchBarTextChanged(LineEdit.LineEditEventArgs args)
|
||||
{
|
||||
BuildEntityList(args.Text);
|
||||
_clearButton.Disabled = string.IsNullOrEmpty(args.Text);
|
||||
}
|
||||
|
||||
private void OnClearButtonPressed(BaseButton.ButtonEventArgs args)
|
||||
{
|
||||
_searchBar.Clear();
|
||||
BuildEntityList("");
|
||||
}
|
||||
|
||||
|
||||
private void BuildEntityList(string? searchStr = null)
|
||||
{
|
||||
_filteredScans.Clear();
|
||||
_scanList.RemoveAllChildren();
|
||||
// Reset last scan indices so it automatically updates the entire list.
|
||||
_lastScanIndices = (0, -1);
|
||||
_scanList.RemoveAllChildren();
|
||||
_selectedButton = null;
|
||||
searchStr = searchStr?.ToLowerInvariant();
|
||||
|
||||
foreach (var scan in _scanManager)
|
||||
{
|
||||
if (searchStr != null && !_doesScanMatchSearch(scan.Value, searchStr))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
_filteredScans.Add(scan.Key, scan.Value);
|
||||
}
|
||||
|
||||
//TODO: set up sort
|
||||
//_filteredScans.Sort((a, b) => string.Compare(a.ToString(), b.ToString(), StringComparison.Ordinal));
|
||||
|
||||
_scanList.TotalItemCount = _filteredScans.Count;
|
||||
}
|
||||
|
||||
private void UpdateVisibleScans()
|
||||
{
|
||||
// Update visible buttons in the scan list.
|
||||
|
||||
// Calculate index of first scan to render based on current scroll.
|
||||
var height = _measureButton.CombinedMinimumSize.Y + ScanListContainer.Separation;
|
||||
var offset = -_scanList.Position.Y;
|
||||
var startIndex = (int) Math.Floor(offset / height);
|
||||
_scanList.ItemOffset = startIndex;
|
||||
|
||||
var (prevStart, prevEnd) = _lastScanIndices;
|
||||
|
||||
// Calculate index of final one.
|
||||
var endIndex = startIndex - 1;
|
||||
var spaceUsed = -height; // -height instead of 0 because else it cuts off the last button.
|
||||
|
||||
while (spaceUsed < _scanList.Parent!.Height)
|
||||
{
|
||||
spaceUsed += height;
|
||||
endIndex += 1;
|
||||
}
|
||||
|
||||
endIndex = Math.Min(endIndex, _filteredScans.Count - 1);
|
||||
|
||||
if (endIndex == prevEnd && startIndex == prevStart)
|
||||
{
|
||||
// Nothing changed so bye.
|
||||
return;
|
||||
}
|
||||
|
||||
_lastScanIndices = (startIndex, endIndex);
|
||||
|
||||
// Delete buttons at the start of the list that are no longer visible (scrolling down).
|
||||
for (var i = prevStart; i < startIndex && i <= prevEnd; i++)
|
||||
{
|
||||
var control = (CloningScanButton) _scanList.GetChild(0);
|
||||
DebugTools.Assert(control.Index == i);
|
||||
_scanList.RemoveChild(control);
|
||||
}
|
||||
|
||||
// Delete buttons at the end of the list that are no longer visible (scrolling up).
|
||||
for (var i = prevEnd; i > endIndex && i >= prevStart; i--)
|
||||
{
|
||||
var control = (CloningScanButton) _scanList.GetChild(_scanList.ChildCount - 1);
|
||||
DebugTools.Assert(control.Index == i);
|
||||
_scanList.RemoveChild(control);
|
||||
}
|
||||
|
||||
var array = _filteredScans.ToArray();
|
||||
|
||||
// Create buttons at the start of the list that are now visible (scrolling up).
|
||||
for (var i = Math.Min(prevStart - 1, endIndex); i >= startIndex; i--)
|
||||
{
|
||||
InsertEntityButton(array[i], true, i);
|
||||
}
|
||||
|
||||
// Create buttons at the end of the list that are now visible (scrolling down).
|
||||
for (var i = Math.Max(prevEnd + 1, startIndex); i <= endIndex; i++)
|
||||
{
|
||||
InsertEntityButton(array[i], false, i);
|
||||
}
|
||||
}
|
||||
|
||||
// Create a spawn button and insert it into the start or end of the list.
|
||||
private void InsertEntityButton(KeyValuePair<int, string> scan, bool insertFirst, int index)
|
||||
{
|
||||
var button = new CloningScanButton
|
||||
{
|
||||
Scan = scan.Value,
|
||||
Id = scan.Key,
|
||||
Index = index // We track this index purely for debugging.
|
||||
};
|
||||
button.ActualButton.OnToggled += OnItemButtonToggled;
|
||||
var entityLabelText = scan.Value;
|
||||
|
||||
button.EntityLabel.Text = entityLabelText;
|
||||
|
||||
if (scan.Key == SelectedScan)
|
||||
{
|
||||
_selectedButton = button;
|
||||
_selectedButton.ActualButton.Pressed = true;
|
||||
}
|
||||
|
||||
//TODO: replace with body's face
|
||||
/*var tex = IconComponent.GetScanIcon(scan, resourceCache);
|
||||
var rect = button.EntityTextureRect;
|
||||
if (tex != null)
|
||||
{
|
||||
rect.Texture = tex.Default;
|
||||
}
|
||||
else
|
||||
{
|
||||
rect.Dispose();
|
||||
}
|
||||
|
||||
rect.Dispose();
|
||||
*/
|
||||
|
||||
_scanList.AddChild(button);
|
||||
if (insertFirst)
|
||||
{
|
||||
button.SetPositionInParent(0);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool _doesScanMatchSearch(string scan, string searchStr)
|
||||
{
|
||||
return scan.ToLowerInvariant().Contains(searchStr);
|
||||
}
|
||||
|
||||
private void OnItemButtonToggled(BaseButton.ButtonToggledEventArgs args)
|
||||
{
|
||||
var item = (CloningScanButton) args.Button.Parent!;
|
||||
if (_selectedButton == item)
|
||||
{
|
||||
_selectedButton = null;
|
||||
SelectedScan = null;
|
||||
return;
|
||||
}
|
||||
else if (_selectedButton != null)
|
||||
{
|
||||
_selectedButton.ActualButton.Pressed = false;
|
||||
}
|
||||
|
||||
_selectedButton = null;
|
||||
SelectedScan = null;
|
||||
|
||||
_selectedButton = item;
|
||||
SelectedScan = item.Id;
|
||||
}
|
||||
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
base.FrameUpdate(args);
|
||||
UpdateVisibleScans();
|
||||
}
|
||||
|
||||
private class ScanListContainer : Container
|
||||
{
|
||||
// Quick and dirty container to do virtualization of the list.
|
||||
// Basically, get total item count and offset to put the current buttons at.
|
||||
// Get a constant minimum height and move the buttons in the list up to match the scrollbar.
|
||||
private int _totalItemCount;
|
||||
private int _itemOffset;
|
||||
|
||||
public int TotalItemCount
|
||||
{
|
||||
get => _totalItemCount;
|
||||
set
|
||||
{
|
||||
_totalItemCount = value;
|
||||
MinimumSizeChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public int ItemOffset
|
||||
{
|
||||
get => _itemOffset;
|
||||
set
|
||||
{
|
||||
_itemOffset = value;
|
||||
UpdateLayout();
|
||||
}
|
||||
}
|
||||
|
||||
public const float Separation = 2;
|
||||
|
||||
protected override Vector2 CalculateMinimumSize()
|
||||
{
|
||||
if (ChildCount == 0)
|
||||
{
|
||||
return Vector2.Zero;
|
||||
}
|
||||
|
||||
var first = GetChild(0);
|
||||
|
||||
var (minX, minY) = first.CombinedMinimumSize;
|
||||
|
||||
return (minX, minY * TotalItemCount + (TotalItemCount - 1) * Separation);
|
||||
}
|
||||
|
||||
protected override void LayoutUpdateOverride()
|
||||
{
|
||||
if (ChildCount == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var first = GetChild(0);
|
||||
|
||||
var height = first.CombinedMinimumSize.Y;
|
||||
var offset = ItemOffset * height + (ItemOffset - 1) * Separation;
|
||||
|
||||
foreach (var child in Children)
|
||||
{
|
||||
FitChildInBox(child, UIBox2.FromDimensions(0, offset, Width, height));
|
||||
offset += Separation + height;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[DebuggerDisplay("cloningbutton {" + nameof(Index) + "}")]
|
||||
private class CloningScanButton : Control
|
||||
{
|
||||
public string Scan { get; set; } = default!;
|
||||
public int Id { get; set; }
|
||||
public Button ActualButton { get; private set; }
|
||||
public Label EntityLabel { get; private set; }
|
||||
public TextureRect EntityTextureRect { get; private set; }
|
||||
public int Index { get; set; }
|
||||
|
||||
public CloningScanButton()
|
||||
{
|
||||
AddChild(ActualButton = new Button
|
||||
{
|
||||
SizeFlagsHorizontal = SizeFlags.FillExpand,
|
||||
SizeFlagsVertical = SizeFlags.FillExpand,
|
||||
ToggleMode = true,
|
||||
});
|
||||
|
||||
AddChild(new HBoxContainer
|
||||
{
|
||||
Children =
|
||||
{
|
||||
(EntityTextureRect = new TextureRect
|
||||
{
|
||||
CustomMinimumSize = (32, 32),
|
||||
SizeFlagsHorizontal = SizeFlags.ShrinkCenter,
|
||||
SizeFlagsVertical = SizeFlags.ShrinkCenter,
|
||||
Stretch = TextureRect.StretchMode.KeepAspectCentered,
|
||||
CanShrink = true
|
||||
}),
|
||||
(EntityLabel = new Label
|
||||
{
|
||||
SizeFlagsVertical = SizeFlags.ShrinkCenter,
|
||||
SizeFlagsHorizontal = SizeFlags.FillExpand,
|
||||
Text = "",
|
||||
ClipText = true
|
||||
})
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -115,6 +115,7 @@ namespace Content.Client.GameObjects.Components.Doors
|
||||
|
||||
var unlitVisible = true;
|
||||
var boltedVisible = false;
|
||||
var weldedVisible = false;
|
||||
switch (state)
|
||||
{
|
||||
case DoorVisualState.Closed:
|
||||
@@ -145,6 +146,9 @@ namespace Content.Client.GameObjects.Components.Doors
|
||||
animPlayer.Play(DenyAnimation, AnimationKey);
|
||||
}
|
||||
break;
|
||||
case DoorVisualState.Welded:
|
||||
weldedVisible = true;
|
||||
break;
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
@@ -159,6 +163,7 @@ namespace Content.Client.GameObjects.Components.Doors
|
||||
}
|
||||
|
||||
sprite.LayerSetVisible(DoorVisualLayers.BaseUnlit, unlitVisible);
|
||||
sprite.LayerSetVisible(DoorVisualLayers.BaseWelded, weldedVisible);
|
||||
sprite.LayerSetVisible(DoorVisualLayers.BaseBolted, unlitVisible && boltedVisible);
|
||||
}
|
||||
}
|
||||
@@ -167,6 +172,7 @@ namespace Content.Client.GameObjects.Components.Doors
|
||||
{
|
||||
Base,
|
||||
BaseUnlit,
|
||||
BaseBolted
|
||||
BaseWelded,
|
||||
BaseBolted,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
|
||||
using Content.Shared.GameObjects.Components;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
using System;
|
||||
|
||||
namespace Content.Client.GameObjects.Components
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public class ExpendableLightVisualizer : AppearanceVisualizer
|
||||
{
|
||||
public override void OnChangeData(AppearanceComponent component)
|
||||
{
|
||||
base.OnChangeData(component);
|
||||
|
||||
if (component.Deleted)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (component.TryGetData(ExpendableLightVisuals.State, out string lightBehaviourID))
|
||||
{
|
||||
if (component.Owner.TryGetComponent<LightBehaviourComponent>(out var lightBehaviour))
|
||||
{
|
||||
lightBehaviour.StopLightBehaviour();
|
||||
|
||||
if (lightBehaviourID != string.Empty)
|
||||
{
|
||||
lightBehaviour.StartLightBehaviour(lightBehaviourID);
|
||||
}
|
||||
else if (component.Owner.TryGetComponent<PointLightComponent>(out var light))
|
||||
{
|
||||
light.Enabled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
using Content.Shared.GameObjects.Components;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Interfaces.GameObjects.Components;
|
||||
|
||||
namespace Content.Client.GameObjects.Components
|
||||
{
|
||||
public class ExtinguisherCabinetVisualizer : AppearanceVisualizer
|
||||
{
|
||||
private string _prefix;
|
||||
|
||||
public override void OnChangeData(AppearanceComponent component)
|
||||
{
|
||||
base.OnChangeData(component);
|
||||
|
||||
var sprite = component.Owner.GetComponent<ISpriteComponent>();
|
||||
|
||||
if (component.TryGetData(ExtinguisherCabinetVisuals.IsOpen, out bool isOpen))
|
||||
{
|
||||
if (isOpen)
|
||||
{
|
||||
if (component.TryGetData(ExtinguisherCabinetVisuals.ContainsExtinguisher, out bool contains))
|
||||
{
|
||||
if (contains)
|
||||
{
|
||||
sprite.LayerSetState(0, "extinguisher_full");
|
||||
}
|
||||
else
|
||||
{
|
||||
sprite.LayerSetState(0, "extinguisher_empty");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
sprite.LayerSetState(0, "extinguisher_closed");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
|
||||
using Content.Shared.GameObjects.Components;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Client.GameObjects;
|
||||
|
||||
namespace Content.Client.GameObjects.Components.Interactable
|
||||
{
|
||||
/// <summary>
|
||||
/// Component that represents a handheld expendable light which can be activated and eventually dies over time.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public class ExpendableLightComponent : SharedExpendableLightComponent
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
539
Content.Client/GameObjects/Components/LightBehaviourComponent.cs
Normal file
539
Content.Client/GameObjects/Components/LightBehaviourComponent.cs
Normal file
@@ -0,0 +1,539 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.Animations;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Interfaces.Random;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using Content.Shared.GameObjects.Components;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Interfaces.Serialization;
|
||||
using Robust.Client.Animations;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
using Robust.Client.GameObjects.Components.Animations;
|
||||
using System.Linq;
|
||||
|
||||
namespace Content.Client.GameObjects.Components
|
||||
{
|
||||
#region LIGHT_BEHAVIOURS
|
||||
/// <summary>
|
||||
/// Base class for all light behaviours to derive from.
|
||||
/// This AnimationTrack derivative does not rely on keyframes since it often needs to have a randomized duration.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public abstract class LightBehaviourAnimationTrack : AnimationTrackProperty, IExposeData
|
||||
{
|
||||
[ViewVariables] public string ID { get; set; }
|
||||
[ViewVariables] public string Property { get; protected set; }
|
||||
[ViewVariables] public bool IsLooped { get; set; }
|
||||
[ViewVariables] public bool Enabled { get; set; }
|
||||
[ViewVariables] public float StartValue { get; set; }
|
||||
[ViewVariables] public float EndValue { get; set; }
|
||||
[ViewVariables] public float MinDuration { get; set; }
|
||||
[ViewVariables] public float MaxDuration { get; set; }
|
||||
[ViewVariables] public AnimationInterpolationMode InterpolateMode { get; set; }
|
||||
|
||||
[ViewVariables] protected float MaxTime { get; set; }
|
||||
protected PointLightComponent Light = default;
|
||||
protected IRobustRandom RobustRandom = default;
|
||||
|
||||
private float _maxTime = default;
|
||||
|
||||
public virtual void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
serializer.DataField(this, x => x.ID, "id", string.Empty);
|
||||
serializer.DataField(this, x => x.IsLooped, "isLooped", false);
|
||||
serializer.DataField(this, x => x.Enabled, "enabled", false);
|
||||
serializer.DataField(this, x => x.StartValue, "startValue", 0f);
|
||||
serializer.DataField(this, x => x.EndValue, "endValue", 2f);
|
||||
serializer.DataField(this, x => x.MinDuration, "minDuration", -1f);
|
||||
serializer.DataField(this, x => x.MaxDuration, "maxDuration", 2f);
|
||||
serializer.DataField(this, x => x.Property, "property", "Radius");
|
||||
serializer.DataField(this, x => x.InterpolateMode, "interpolate", AnimationInterpolationMode.Linear);
|
||||
}
|
||||
|
||||
public void Initialize(PointLightComponent light)
|
||||
{
|
||||
Light = light;
|
||||
RobustRandom = IoCManager.Resolve<IRobustRandom>();
|
||||
|
||||
if (Enabled)
|
||||
{
|
||||
Light.Enabled = true;
|
||||
}
|
||||
|
||||
OnInitialize();
|
||||
}
|
||||
|
||||
public void UpdatePlaybackValues(Animation owner)
|
||||
{
|
||||
Light.Enabled = true;
|
||||
|
||||
if (MinDuration > 0)
|
||||
{
|
||||
MaxTime = (float) RobustRandom.NextDouble() * (MaxDuration - MinDuration) + MinDuration;
|
||||
}
|
||||
else
|
||||
{
|
||||
MaxTime = MaxDuration;
|
||||
}
|
||||
|
||||
owner.Length = TimeSpan.FromSeconds(MaxTime);
|
||||
}
|
||||
|
||||
public override (int KeyFrameIndex, float FramePlayingTime) InitPlayback()
|
||||
{
|
||||
OnStart();
|
||||
|
||||
return (-1, _maxTime);
|
||||
}
|
||||
|
||||
protected void ApplyProperty(object value)
|
||||
{
|
||||
if (Property == null)
|
||||
{
|
||||
throw new InvalidOperationException("Property parameter is null! Check the prototype!");
|
||||
}
|
||||
|
||||
if (Light is IAnimationProperties properties)
|
||||
{
|
||||
properties.SetAnimatableProperty(Property, value);
|
||||
}
|
||||
else
|
||||
{
|
||||
AnimationHelper.SetAnimatableProperty(Light, Property, value);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void ApplyProperty(object context, object value)
|
||||
{
|
||||
ApplyProperty(value);
|
||||
}
|
||||
|
||||
public virtual void OnInitialize() { }
|
||||
public virtual void OnStart() { }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A light behaviour that alternates between StartValue and EndValue
|
||||
/// </summary>
|
||||
public class PulseBehaviour: LightBehaviourAnimationTrack
|
||||
{
|
||||
public override (int KeyFrameIndex, float FramePlayingTime) AdvancePlayback(
|
||||
object context, int prevKeyFrameIndex, float prevPlayingTime, float frameTime)
|
||||
{
|
||||
var playingTime = prevPlayingTime + frameTime;
|
||||
var interpolateValue = playingTime / MaxTime;
|
||||
|
||||
if (Property == "Enabled") // special case for boolean
|
||||
{
|
||||
ApplyProperty(interpolateValue < 0.5f? true : false);
|
||||
return (-1, playingTime);
|
||||
}
|
||||
|
||||
if (interpolateValue < 0.5f)
|
||||
{
|
||||
switch (InterpolateMode)
|
||||
{
|
||||
case AnimationInterpolationMode.Linear:
|
||||
ApplyProperty(InterpolateLinear(StartValue, EndValue, interpolateValue * 2f));
|
||||
break;
|
||||
case AnimationInterpolationMode.Cubic:
|
||||
ApplyProperty(InterpolateCubic(EndValue, StartValue, EndValue, StartValue, interpolateValue * 2f));
|
||||
break;
|
||||
default:
|
||||
case AnimationInterpolationMode.Nearest:
|
||||
ApplyProperty(StartValue);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (InterpolateMode)
|
||||
{
|
||||
case AnimationInterpolationMode.Linear:
|
||||
ApplyProperty(InterpolateLinear(EndValue, StartValue, (interpolateValue - 0.5f) * 2f));
|
||||
break;
|
||||
case AnimationInterpolationMode.Cubic:
|
||||
ApplyProperty(InterpolateCubic(StartValue, EndValue, StartValue, EndValue, (interpolateValue - 0.5f) * 2f));
|
||||
break;
|
||||
default:
|
||||
case AnimationInterpolationMode.Nearest:
|
||||
ApplyProperty(EndValue);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return (-1, playingTime);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A light behaviour that interpolates from StartValue to EndValue
|
||||
/// </summary>
|
||||
public class FadeBehaviour : LightBehaviourAnimationTrack
|
||||
{
|
||||
public override (int KeyFrameIndex, float FramePlayingTime) AdvancePlayback(
|
||||
object context, int prevKeyFrameIndex, float prevPlayingTime, float frameTime)
|
||||
{
|
||||
var playingTime = prevPlayingTime + frameTime;
|
||||
var interpolateValue = playingTime / MaxTime;
|
||||
|
||||
if (Property == "Enabled") // special case for boolean
|
||||
{
|
||||
ApplyProperty(interpolateValue < EndValue? true : false);
|
||||
return (-1, playingTime);
|
||||
}
|
||||
|
||||
switch (InterpolateMode)
|
||||
{
|
||||
case AnimationInterpolationMode.Linear:
|
||||
ApplyProperty(InterpolateLinear(StartValue, EndValue, interpolateValue));
|
||||
break;
|
||||
case AnimationInterpolationMode.Cubic:
|
||||
ApplyProperty(InterpolateCubic(EndValue, StartValue, EndValue, StartValue, interpolateValue));
|
||||
break;
|
||||
default:
|
||||
case AnimationInterpolationMode.Nearest:
|
||||
ApplyProperty(interpolateValue < 0.5f ? StartValue : EndValue);
|
||||
break;
|
||||
}
|
||||
|
||||
return (-1, playingTime);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A light behaviour that interpolates using random values chosen between StartValue and EndValue.
|
||||
/// </summary>
|
||||
public class RandomizeBehaviour : LightBehaviourAnimationTrack
|
||||
{
|
||||
private object _randomValue1 = default;
|
||||
private object _randomValue2 = default;
|
||||
private object _randomValue3 = default;
|
||||
private object _randomValue4 = default;
|
||||
|
||||
public override void OnInitialize()
|
||||
{
|
||||
_randomValue2 = InterpolateLinear(StartValue, EndValue, (float) RobustRandom.NextDouble());
|
||||
_randomValue3 = InterpolateLinear(StartValue, EndValue, (float) RobustRandom.NextDouble());
|
||||
_randomValue4 = InterpolateLinear(StartValue, EndValue, (float) RobustRandom.NextDouble());
|
||||
}
|
||||
|
||||
public override void OnStart()
|
||||
{
|
||||
if (Property == "Enabled") // special case for boolean, we randomize it
|
||||
{
|
||||
ApplyProperty(RobustRandom.NextDouble() < 0.5 ? true : false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (InterpolateMode == AnimationInterpolationMode.Cubic)
|
||||
{
|
||||
_randomValue1 = _randomValue2;
|
||||
_randomValue2 = _randomValue3;
|
||||
}
|
||||
|
||||
_randomValue3 = _randomValue4;
|
||||
_randomValue4 = InterpolateLinear(StartValue, EndValue, (float) RobustRandom.NextDouble());
|
||||
}
|
||||
|
||||
public override (int KeyFrameIndex, float FramePlayingTime) AdvancePlayback(
|
||||
object context, int prevKeyFrameIndex, float prevPlayingTime, float frameTime)
|
||||
{
|
||||
var playingTime = prevPlayingTime + frameTime;
|
||||
var interpolateValue = playingTime / MaxTime;
|
||||
|
||||
if (Property == "Enabled")
|
||||
{
|
||||
return (-1, playingTime);
|
||||
}
|
||||
|
||||
switch (InterpolateMode)
|
||||
{
|
||||
case AnimationInterpolationMode.Linear:
|
||||
ApplyProperty(InterpolateLinear(_randomValue3, _randomValue4, interpolateValue));
|
||||
break;
|
||||
case AnimationInterpolationMode.Cubic:
|
||||
ApplyProperty(InterpolateCubic(_randomValue1, _randomValue2, _randomValue3, _randomValue4, interpolateValue));
|
||||
break;
|
||||
default:
|
||||
case AnimationInterpolationMode.Nearest:
|
||||
ApplyProperty(interpolateValue < 0.5f ? _randomValue3 : _randomValue4);
|
||||
break;
|
||||
}
|
||||
|
||||
return (-1, playingTime);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A light behaviour that cycles through a list of colors.
|
||||
/// </summary>
|
||||
public class ColorCycleBehaviour : LightBehaviourAnimationTrack
|
||||
{
|
||||
public List<Color> ColorsToCycle { get; set; }
|
||||
|
||||
private int _colorIndex = 0;
|
||||
|
||||
public override void OnStart()
|
||||
{
|
||||
_colorIndex++;
|
||||
|
||||
if (_colorIndex > ColorsToCycle.Count - 1)
|
||||
{
|
||||
_colorIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public override (int KeyFrameIndex, float FramePlayingTime) AdvancePlayback(
|
||||
object context, int prevKeyFrameIndex, float prevPlayingTime, float frameTime)
|
||||
{
|
||||
var playingTime = prevPlayingTime + frameTime;
|
||||
var interpolateValue = playingTime / MaxTime;
|
||||
|
||||
switch (InterpolateMode)
|
||||
{
|
||||
case AnimationInterpolationMode.Linear:
|
||||
ApplyProperty(InterpolateLinear(ColorsToCycle[(_colorIndex - 1) % ColorsToCycle.Count],
|
||||
ColorsToCycle[_colorIndex],
|
||||
interpolateValue));
|
||||
break;
|
||||
case AnimationInterpolationMode.Cubic:
|
||||
ApplyProperty(InterpolateCubic(ColorsToCycle[_colorIndex],
|
||||
ColorsToCycle[(_colorIndex + 1) % ColorsToCycle.Count],
|
||||
ColorsToCycle[(_colorIndex + 2) % ColorsToCycle.Count],
|
||||
ColorsToCycle[(_colorIndex + 3) % ColorsToCycle.Count],
|
||||
interpolateValue));
|
||||
break;
|
||||
default:
|
||||
case AnimationInterpolationMode.Nearest:
|
||||
ApplyProperty(ColorsToCycle[_colorIndex]);
|
||||
break;
|
||||
}
|
||||
|
||||
return (-1, playingTime);
|
||||
}
|
||||
|
||||
public override void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
serializer.DataField(this, x => x.ID, "id", string.Empty);
|
||||
serializer.DataField(this, x => x.IsLooped, "isLooped", false);
|
||||
serializer.DataField(this, x => x.Enabled, "enabled", false);
|
||||
serializer.DataField(this, x => x.MinDuration, "minDuration", -1f);
|
||||
serializer.DataField(this, x => x.MaxDuration, "maxDuration", 2f);
|
||||
serializer.DataField(this, x => x.InterpolateMode, "interpolate", AnimationInterpolationMode.Linear);
|
||||
ColorsToCycle = serializer.ReadDataField("colors", new List<Color>());
|
||||
Property = "Color";
|
||||
|
||||
if (ColorsToCycle.Count < 2)
|
||||
{
|
||||
throw new InvalidOperationException($"{nameof(ColorCycleBehaviour)} has less than 2 colors to cycle");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// A component which applies a specific behaviour to a PointLightComponent on its owner.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public class LightBehaviourComponent : SharedLightBehaviourComponent
|
||||
{
|
||||
private const string KeyPrefix = nameof(LightBehaviourComponent);
|
||||
|
||||
private class AnimationContainer
|
||||
{
|
||||
public AnimationContainer(int key, Animation animation, LightBehaviourAnimationTrack track)
|
||||
{
|
||||
Key = key;
|
||||
Animation = animation;
|
||||
LightBehaviour = track;
|
||||
}
|
||||
|
||||
public string FullKey => KeyPrefix + Key;
|
||||
public int Key { get; set; }
|
||||
public Animation Animation { get; set; }
|
||||
public LightBehaviourAnimationTrack LightBehaviour { get; set; }
|
||||
}
|
||||
|
||||
[ViewVariables(VVAccess.ReadOnly)]
|
||||
private List<AnimationContainer> _animations = new List<AnimationContainer>();
|
||||
|
||||
private float _originalRadius = default;
|
||||
private float _originalEnergy = default;
|
||||
private Angle _originalRotation = default;
|
||||
private Color _originalColor = default;
|
||||
private bool _originalEnabled = default;
|
||||
private PointLightComponent _lightComponent = default;
|
||||
private AnimationPlayerComponent _animationPlayer = default;
|
||||
|
||||
protected override void Startup()
|
||||
{
|
||||
base.Startup();
|
||||
|
||||
CopyLightSettings();
|
||||
_animationPlayer = Owner.EnsureComponent<AnimationPlayerComponent>();
|
||||
_animationPlayer.AnimationCompleted += s => OnAnimationCompleted(s);
|
||||
|
||||
foreach (var container in _animations)
|
||||
{
|
||||
container.LightBehaviour.Initialize(_lightComponent);
|
||||
}
|
||||
|
||||
// we need to initialize all behaviours before starting any
|
||||
foreach (var container in _animations)
|
||||
{
|
||||
if (container.LightBehaviour.Enabled)
|
||||
{
|
||||
StartLightBehaviour(container.LightBehaviour.ID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAnimationCompleted(string key)
|
||||
{
|
||||
var container = _animations.FirstOrDefault(x => x.FullKey == key);
|
||||
|
||||
if (container.LightBehaviour.IsLooped)
|
||||
{
|
||||
container.LightBehaviour.UpdatePlaybackValues(container.Animation);
|
||||
_animationPlayer.Play(container.Animation, container.FullKey);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If we disable all the light behaviours we want to be able to revert the light to its original state.
|
||||
/// </summary>
|
||||
private void CopyLightSettings()
|
||||
{
|
||||
if (Owner.TryGetComponent(out _lightComponent))
|
||||
{
|
||||
_originalColor = _lightComponent.Color;
|
||||
_originalEnabled = _lightComponent.Enabled;
|
||||
_originalEnergy = _lightComponent.Energy;
|
||||
_originalRadius = _lightComponent.Radius;
|
||||
_originalRotation = _lightComponent.Rotation;
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Warning($"{Owner.Name} has a {nameof(LightBehaviourComponent)} but it has no {nameof(PointLightComponent)}! Check the prototype!");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Start animating a light behaviour with the specified ID. If the specified ID is empty, it will start animating all light behaviour entries.
|
||||
/// If specified light behaviours are already animating, calling this does nothing.
|
||||
/// Multiple light behaviours can have the same ID.
|
||||
/// </summary>
|
||||
public void StartLightBehaviour(string id = "")
|
||||
{
|
||||
foreach (var container in _animations)
|
||||
{
|
||||
if (container.LightBehaviour.ID == id || id == string.Empty)
|
||||
{
|
||||
if (!_animationPlayer.HasRunningAnimation(KeyPrefix + container.Key))
|
||||
{
|
||||
container.LightBehaviour.UpdatePlaybackValues(container.Animation);
|
||||
_animationPlayer.Play(container.Animation, KeyPrefix + container.Key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If any light behaviour with the specified ID is animating, then stop it.
|
||||
/// If no ID is specified then all light behaviours will be stopped.
|
||||
/// Multiple light behaviours can have the same ID.
|
||||
/// </summary>
|
||||
/// <param name="id"></param>
|
||||
/// <param name="removeBehaviour">Should the behaviour(s) also be removed permanently?</param>
|
||||
/// <param name="resetToOriginalSettings">Should the light have its original settings applied?</param>
|
||||
public void StopLightBehaviour(string id = "", bool removeBehaviour = false, bool resetToOriginalSettings = false)
|
||||
{
|
||||
var toRemove = new List<AnimationContainer>();
|
||||
|
||||
foreach (var container in _animations)
|
||||
{
|
||||
if (container.LightBehaviour.ID == id || id == string.Empty)
|
||||
{
|
||||
if (_animationPlayer.HasRunningAnimation(KeyPrefix + container.Key))
|
||||
{
|
||||
_animationPlayer.Stop(KeyPrefix + container.Key);
|
||||
}
|
||||
|
||||
if (removeBehaviour)
|
||||
{
|
||||
toRemove.Add(container);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var container in toRemove)
|
||||
{
|
||||
_animations.Remove(container);
|
||||
}
|
||||
|
||||
if (resetToOriginalSettings)
|
||||
{
|
||||
_lightComponent.Color = _originalColor;
|
||||
_lightComponent.Enabled = _originalEnabled;
|
||||
_lightComponent.Energy = _originalEnergy;
|
||||
_lightComponent.Radius = _originalRadius;
|
||||
_lightComponent.Rotation = _originalRotation;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a new light behaviour to the component and start it immediately unless otherwise specified.
|
||||
/// </summary>
|
||||
public void AddNewLightBehaviour(LightBehaviourAnimationTrack behaviour, bool playImmediately = true)
|
||||
{
|
||||
int key = 0;
|
||||
|
||||
while (_animations.Any(x => x.Key == key))
|
||||
{
|
||||
key++;
|
||||
}
|
||||
|
||||
var animation = new Animation()
|
||||
{
|
||||
AnimationTracks = { behaviour }
|
||||
};
|
||||
|
||||
behaviour.Initialize(_lightComponent);
|
||||
var container = new AnimationContainer(key, animation, behaviour);
|
||||
_animations.Add(container);
|
||||
|
||||
if (playImmediately)
|
||||
{
|
||||
StartLightBehaviour(behaviour.ID);
|
||||
}
|
||||
}
|
||||
|
||||
public override void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
base.ExposeData(serializer);
|
||||
|
||||
var behaviours = serializer.ReadDataField("behaviours", new List<LightBehaviourAnimationTrack>());
|
||||
var key = 0;
|
||||
|
||||
foreach (LightBehaviourAnimationTrack behaviour in behaviours)
|
||||
{
|
||||
var animation = new Animation()
|
||||
{
|
||||
AnimationTracks = { behaviour }
|
||||
};
|
||||
|
||||
_animations.Add(new AnimationContainer(key, animation, behaviour));
|
||||
key++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
using Content.Shared.GameObjects.Components.Medical;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.Client.GameObjects.Components.MedicalScanner
|
||||
{
|
||||
|
||||
[RegisterComponent]
|
||||
[ComponentReference(typeof(SharedMedicalScannerComponent))]
|
||||
public class MedicalScannerComponent : SharedMedicalScannerComponent
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,11 @@ namespace Content.Client.GameObjects.Components.MedicalScanner
|
||||
{
|
||||
base.OnChangeData(component);
|
||||
|
||||
if (component.Owner.Deleted)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var sprite = component.Owner.GetComponent<ISpriteComponent>();
|
||||
if (!component.TryGetData(MedicalScannerVisuals.Status, out MedicalScannerStatus status)) return;
|
||||
sprite.LayerSetState(MedicalScannerVisualLayers.Machine, StatusToMachineStateId(status));
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace Content.Client.GameObjects.Components.MedicalScanner
|
||||
{
|
||||
(ScanButton = new Button
|
||||
{
|
||||
Text = "Scan and Save DNA"
|
||||
Text = Loc.GetString("Scan and Save DNA")
|
||||
}),
|
||||
(_diagnostics = new Label
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Content.Client.UserInterface;
|
||||
@@ -100,7 +100,10 @@ namespace Content.Client.GameObjects.Components.Mobs
|
||||
foreach (var (key, effect) in _status.OrderBy(x => (int) x.Key))
|
||||
{
|
||||
var texture = _resourceCache.GetTexture(effect.Icon);
|
||||
var status = new StatusControl(key, texture);
|
||||
var status = new StatusControl(key, texture)
|
||||
{
|
||||
ToolTip = key.ToString()
|
||||
};
|
||||
|
||||
if (effect.Cooldown.HasValue)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
using Content.Client.GameObjects.EntitySystems;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.Components.Mobs;
|
||||
using Content.Shared.GameObjects.Components.Mobs.State;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.GameObjects.Systems;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
|
||||
namespace Content.Client.GameObjects.Components.Mobs.State
|
||||
{
|
||||
public class CriticalState : SharedCriticalState
|
||||
{
|
||||
public override void EnterState(IEntity entity)
|
||||
{
|
||||
if (entity.TryGetComponent(out AppearanceComponent appearance))
|
||||
{
|
||||
appearance.SetData(DamageStateVisuals.State, DamageState.Critical);
|
||||
}
|
||||
|
||||
EntitySystem.Get<StandingStateSystem>().Down(entity);
|
||||
}
|
||||
|
||||
public override void ExitState(IEntity entity)
|
||||
{
|
||||
EntitySystem.Get<StandingStateSystem>().Standing(entity);
|
||||
}
|
||||
|
||||
public override void UpdateState(IEntity entity) { }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
using Content.Client.GameObjects.EntitySystems;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.Components.Mobs;
|
||||
using Content.Shared.GameObjects.Components.Mobs.State;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.GameObjects.Components;
|
||||
using Robust.Shared.GameObjects.Systems;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
|
||||
namespace Content.Client.GameObjects.Components.Mobs.State
|
||||
{
|
||||
public class DeadState : SharedDeadState
|
||||
{
|
||||
public override void EnterState(IEntity entity)
|
||||
{
|
||||
if (entity.TryGetComponent(out AppearanceComponent appearance))
|
||||
{
|
||||
appearance.SetData(DamageStateVisuals.State, DamageState.Dead);
|
||||
}
|
||||
|
||||
EntitySystem.Get<StandingStateSystem>().Down(entity);
|
||||
|
||||
if (entity.TryGetComponent(out CollidableComponent collidable))
|
||||
{
|
||||
collidable.CanCollide = false;
|
||||
}
|
||||
}
|
||||
|
||||
public override void ExitState(IEntity entity)
|
||||
{
|
||||
EntitySystem.Get<StandingStateSystem>().Standing(entity);
|
||||
|
||||
if (entity.TryGetComponent(out CollidableComponent collidable))
|
||||
{
|
||||
collidable.CanCollide = true;
|
||||
}
|
||||
}
|
||||
|
||||
public override void UpdateState(IEntity entity) { }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
#nullable enable
|
||||
using System.Collections.Generic;
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.Components.Mobs.State;
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.Client.GameObjects.Components.Mobs.State
|
||||
{
|
||||
[RegisterComponent]
|
||||
[ComponentReference(typeof(SharedMobStateManagerComponent))]
|
||||
public class MobStateManagerComponent : SharedMobStateManagerComponent
|
||||
{
|
||||
private readonly Dictionary<DamageState, IMobState> _behavior = new Dictionary<DamageState, IMobState>
|
||||
{
|
||||
{DamageState.Alive, new NormalState()},
|
||||
{DamageState.Critical, new CriticalState()},
|
||||
{DamageState.Dead, new DeadState()}
|
||||
};
|
||||
|
||||
private DamageState _currentDamageState;
|
||||
|
||||
protected override IReadOnlyDictionary<DamageState, IMobState> Behavior => _behavior;
|
||||
|
||||
public override DamageState CurrentDamageState
|
||||
{
|
||||
get => _currentDamageState;
|
||||
protected set
|
||||
{
|
||||
if (_currentDamageState == value)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_currentDamageState != DamageState.Invalid)
|
||||
{
|
||||
CurrentMobState.ExitState(Owner);
|
||||
}
|
||||
|
||||
_currentDamageState = value;
|
||||
CurrentMobState = Behavior[CurrentDamageState];
|
||||
CurrentMobState.EnterState(Owner);
|
||||
|
||||
Dirty();
|
||||
}
|
||||
}
|
||||
|
||||
public override void HandleComponentState(ComponentState? curState, ComponentState? nextState)
|
||||
{
|
||||
base.HandleComponentState(curState, nextState);
|
||||
|
||||
if (!(curState is MobStateManagerComponentState state))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_currentDamageState = state.DamageState;
|
||||
CurrentMobState?.ExitState(Owner);
|
||||
CurrentMobState = Behavior[CurrentDamageState];
|
||||
CurrentMobState.EnterState(Owner);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using Content.Shared.GameObjects.Components.Damage;
|
||||
using Content.Shared.GameObjects.Components.Mobs;
|
||||
using Content.Shared.GameObjects.Components.Mobs.State;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
|
||||
namespace Content.Client.GameObjects.Components.Mobs.State
|
||||
{
|
||||
public class NormalState : SharedNormalState
|
||||
{
|
||||
public override void EnterState(IEntity entity)
|
||||
{
|
||||
if (entity.TryGetComponent(out AppearanceComponent appearance))
|
||||
{
|
||||
appearance.SetData(DamageStateVisuals.State, DamageState.Alive);
|
||||
}
|
||||
|
||||
UpdateState(entity);
|
||||
}
|
||||
|
||||
public override void ExitState(IEntity entity) { }
|
||||
|
||||
public override void UpdateState(IEntity entity) { }
|
||||
}
|
||||
}
|
||||
@@ -18,8 +18,7 @@ namespace Content.Client.GameObjects.Components.Observer
|
||||
|
||||
private GhostGui _gui;
|
||||
|
||||
[ViewVariables(VVAccess.ReadOnly)]
|
||||
public bool CanReturnToBody { get; private set; } = true;
|
||||
[ViewVariables(VVAccess.ReadOnly)] public bool CanReturnToBody { get; private set; } = true;
|
||||
|
||||
private bool _isAttached;
|
||||
|
||||
@@ -51,7 +50,8 @@ namespace Content.Client.GameObjects.Components.Observer
|
||||
base.Initialize();
|
||||
|
||||
if (Owner.TryGetComponent(out SpriteComponent component))
|
||||
component.Visible = _playerManager.LocalPlayer.ControlledEntity?.HasComponent<GhostComponent>() ?? false;
|
||||
component.Visible =
|
||||
_playerManager.LocalPlayer.ControlledEntity?.HasComponent<GhostComponent>() ?? false;
|
||||
}
|
||||
|
||||
public override void HandleMessage(ComponentMessage message, IComponent component)
|
||||
@@ -98,7 +98,6 @@ namespace Content.Client.GameObjects.Components.Observer
|
||||
{
|
||||
_gui?.Update();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
using Robust.Client.GameObjects.Components.UserInterface;
|
||||
using Robust.Shared.GameObjects.Components.UserInterface;
|
||||
using static Content.Shared.GameObjects.Components.Power.AME.SharedAMEControllerComponent;
|
||||
|
||||
namespace Content.Client.GameObjects.Components.Power.AME
|
||||
{
|
||||
public class AMEControllerBoundUserInterface : BoundUserInterface
|
||||
{
|
||||
private AMEWindow _window;
|
||||
|
||||
public AMEControllerBoundUserInterface(ClientUserInterfaceComponent owner, object uiKey) : base(owner, uiKey)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected override void Open()
|
||||
{
|
||||
base.Open();
|
||||
|
||||
_window = new AMEWindow();
|
||||
_window.OnClose += Close;
|
||||
_window.OpenCentered();
|
||||
|
||||
_window.EjectButton.OnPressed += _ => ButtonPressed(UiButton.Eject);
|
||||
_window.ToggleInjection.OnPressed += _ => ButtonPressed(UiButton.ToggleInjection);
|
||||
_window.IncreaseFuelButton.OnPressed += _ => ButtonPressed(UiButton.IncreaseFuel);
|
||||
_window.DecreaseFuelButton.OnPressed += _ => ButtonPressed(UiButton.DecreaseFuel);
|
||||
_window.RefreshPartsButton.OnPressed += _ => ButtonPressed(UiButton.RefreshParts);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Update the ui each time new state data is sent from the server.
|
||||
/// </summary>
|
||||
/// <param name="state">
|
||||
/// Data of the <see cref="SharedReagentDispenserComponent"/> that this ui represents.
|
||||
/// Sent from the server.
|
||||
/// </param>
|
||||
protected override void UpdateState(BoundUserInterfaceState state)
|
||||
{
|
||||
base.UpdateState(state);
|
||||
|
||||
var castState = (AMEControllerBoundUserInterfaceState) state;
|
||||
_window?.UpdateState(castState); //Update window state
|
||||
}
|
||||
|
||||
private void ButtonPressed(UiButton button, int dispenseIndex = -1)
|
||||
{
|
||||
SendMessage(new UiButtonPressedMessage(button));
|
||||
}
|
||||
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
|
||||
if (disposing)
|
||||
{
|
||||
_window.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Interfaces.GameObjects.Components;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using static Content.Shared.GameObjects.Components.Power.AME.SharedAMEControllerComponent;
|
||||
|
||||
namespace Content.Client.GameObjects.Components.Power.AME
|
||||
{
|
||||
public class AMEControllerVisualizer : AppearanceVisualizer
|
||||
{
|
||||
public override void InitializeEntity(IEntity entity)
|
||||
{
|
||||
base.InitializeEntity(entity);
|
||||
var sprite = entity.GetComponent<ISpriteComponent>();
|
||||
|
||||
sprite.LayerMapSet(Layers.Display, sprite.AddLayerState("control_on"));
|
||||
sprite.LayerSetVisible(Layers.Display, false);
|
||||
}
|
||||
|
||||
public override void OnChangeData(AppearanceComponent component)
|
||||
{
|
||||
base.OnChangeData(component);
|
||||
var sprite = component.Owner.GetComponent<ISpriteComponent>();
|
||||
if (component.TryGetData<string>(AMEControllerVisuals.DisplayState, out var state))
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case "on":
|
||||
sprite.LayerSetState(Layers.Display, "control_on");
|
||||
sprite.LayerSetVisible(Layers.Display, true);
|
||||
break;
|
||||
case "critical":
|
||||
sprite.LayerSetState(Layers.Display, "control_critical");
|
||||
sprite.LayerSetVisible(Layers.Display, true);
|
||||
break;
|
||||
case "fuck":
|
||||
sprite.LayerSetState(Layers.Display, "control_fuck");
|
||||
sprite.LayerSetVisible(Layers.Display, true);
|
||||
break;
|
||||
case "off":
|
||||
sprite.LayerSetVisible(Layers.Display, false);
|
||||
break;
|
||||
default:
|
||||
sprite.LayerSetVisible(Layers.Display, false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum Layers
|
||||
{
|
||||
Display,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Client.Interfaces.GameObjects.Components;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using static Content.Shared.GameObjects.Components.Power.AME.SharedAMEShieldComponent;
|
||||
|
||||
namespace Content.Client.GameObjects.Components.Power.AME
|
||||
{
|
||||
public class AMEVisualizer : AppearanceVisualizer
|
||||
{
|
||||
public override void InitializeEntity(IEntity entity)
|
||||
{
|
||||
base.InitializeEntity(entity);
|
||||
var sprite = entity.GetComponent<ISpriteComponent>();
|
||||
sprite.LayerMapSet(Layers.Core, sprite.AddLayerState("core"));
|
||||
sprite.LayerSetVisible(Layers.Core, false);
|
||||
sprite.LayerMapSet(Layers.CoreState, sprite.AddLayerState("core_weak"));
|
||||
sprite.LayerSetVisible(Layers.CoreState, false);
|
||||
}
|
||||
|
||||
public override void OnChangeData(AppearanceComponent component)
|
||||
{
|
||||
base.OnChangeData(component);
|
||||
var sprite = component.Owner.GetComponent<ISpriteComponent>();
|
||||
if (component.TryGetData<string>(AMEShieldVisuals.Core, out var core))
|
||||
{
|
||||
if (core == "isCore")
|
||||
{
|
||||
sprite.LayerSetState(Layers.Core, "core");
|
||||
sprite.LayerSetVisible(Layers.Core, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
sprite.LayerSetVisible(Layers.Core, false);
|
||||
}
|
||||
}
|
||||
|
||||
if (component.TryGetData<string>(AMEShieldVisuals.CoreState, out var coreState))
|
||||
switch (coreState)
|
||||
{
|
||||
case "weak":
|
||||
sprite.LayerSetState(Layers.CoreState, "core_weak");
|
||||
sprite.LayerSetVisible(Layers.CoreState, true);
|
||||
break;
|
||||
case "strong":
|
||||
sprite.LayerSetState(Layers.CoreState, "core_strong");
|
||||
sprite.LayerSetVisible(Layers.CoreState, true);
|
||||
break;
|
||||
case "off":
|
||||
sprite.LayerSetVisible(Layers.CoreState, false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum Layers
|
||||
{
|
||||
Core,
|
||||
CoreState,
|
||||
}
|
||||
}
|
||||
162
Content.Client/GameObjects/Components/Power/AME/AMEWindow.cs
Normal file
162
Content.Client/GameObjects/Components/Power/AME/AMEWindow.cs
Normal file
@@ -0,0 +1,162 @@
|
||||
using Content.Client.UserInterface.Stylesheets;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Client.UserInterface.CustomControls;
|
||||
using Robust.Shared.GameObjects.Components.UserInterface;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using static Content.Shared.GameObjects.Components.Power.AME.SharedAMEControllerComponent;
|
||||
|
||||
namespace Content.Client.GameObjects.Components.Power.AME
|
||||
{
|
||||
public class AMEWindow : SS14Window
|
||||
{
|
||||
public Label InjectionStatus { get; set; }
|
||||
public Button EjectButton { get; set; }
|
||||
public Button ToggleInjection { get; set; }
|
||||
public Button IncreaseFuelButton { get; set; }
|
||||
public Button DecreaseFuelButton { get; set; }
|
||||
public Button RefreshPartsButton { get; set; }
|
||||
public ProgressBar FuelMeter { get; set; }
|
||||
public Label FuelAmount { get; set; }
|
||||
public Label InjectionAmount { get; set; }
|
||||
public Label CoreCount { get; set; }
|
||||
|
||||
|
||||
public AMEWindow()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
|
||||
Title = "Antimatter Control Unit";
|
||||
Contents.AddChild(new VBoxContainer
|
||||
{
|
||||
Children =
|
||||
{
|
||||
new HBoxContainer
|
||||
{
|
||||
Children =
|
||||
{
|
||||
new Label {Text = Loc.GetString("Engine Status") + ": "},
|
||||
(InjectionStatus = new Label {Text = "Not Injecting"})
|
||||
}
|
||||
},
|
||||
new HBoxContainer
|
||||
{
|
||||
Children =
|
||||
{
|
||||
(ToggleInjection = new Button {Text = "Toggle Injection", StyleClasses = {StyleBase.ButtonOpenBoth}, Disabled = true}),
|
||||
}
|
||||
},
|
||||
new HBoxContainer
|
||||
{
|
||||
Children =
|
||||
{
|
||||
new Label {Text = Loc.GetString("Fuel Status") + ": "},
|
||||
(FuelAmount = new Label {Text = "No fuel inserted"})
|
||||
}
|
||||
},
|
||||
new HBoxContainer
|
||||
{
|
||||
Children =
|
||||
{
|
||||
(EjectButton = new Button {Text = "Eject", StyleClasses = {StyleBase.ButtonOpenBoth}, Disabled = true}),
|
||||
}
|
||||
},
|
||||
new HBoxContainer
|
||||
{
|
||||
Children =
|
||||
{
|
||||
new Label {Text = Loc.GetString("Injection amount") + ": "},
|
||||
(InjectionAmount = new Label {Text = "0"})
|
||||
}
|
||||
},
|
||||
new HBoxContainer
|
||||
{
|
||||
Children =
|
||||
{
|
||||
(IncreaseFuelButton = new Button {Text = "Increase", StyleClasses = {StyleBase.ButtonOpenRight}}),
|
||||
(DecreaseFuelButton = new Button {Text = "Decrease", StyleClasses = {StyleBase.ButtonOpenLeft}}),
|
||||
}
|
||||
},
|
||||
new HBoxContainer
|
||||
{
|
||||
Children =
|
||||
{
|
||||
(RefreshPartsButton = new Button {Text = "Refresh Parts", StyleClasses = {StyleBase.ButtonOpenBoth }, Disabled = true }),
|
||||
new Label { Text = Loc.GetString("Core count") + ": "},
|
||||
(CoreCount = new Label { Text = "0"}),
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// This searches recursively through all the children of "parent"
|
||||
/// and sets the Disabled value of any buttons found to "val"
|
||||
/// </summary>
|
||||
/// <param name="parent">The control which childrens get searched</param>
|
||||
/// <param name="val">The value to which disabled gets set</param>
|
||||
private void SetButtonDisabledRecursive(Control parent, bool val)
|
||||
{
|
||||
foreach (var child in parent.Children)
|
||||
{
|
||||
if (child is Button but)
|
||||
{
|
||||
but.Disabled = val;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (child.Children != null)
|
||||
{
|
||||
SetButtonDisabledRecursive(child, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the UI state when new state data is received from the server.
|
||||
/// </summary>
|
||||
/// <param name="state">State data sent by the server.</param>
|
||||
public void UpdateState(BoundUserInterfaceState state)
|
||||
{
|
||||
var castState = (AMEControllerBoundUserInterfaceState) state;
|
||||
|
||||
// Disable all buttons if not powered
|
||||
if (Contents.Children != null)
|
||||
{
|
||||
SetButtonDisabledRecursive(Contents, !castState.HasPower);
|
||||
EjectButton.Disabled = false;
|
||||
}
|
||||
|
||||
if(!castState.HasFuelJar)
|
||||
{
|
||||
EjectButton.Disabled = true;
|
||||
ToggleInjection.Disabled = true;
|
||||
FuelAmount.Text = Loc.GetString("No fuel inserted");
|
||||
}
|
||||
else
|
||||
{
|
||||
EjectButton.Disabled = false;
|
||||
ToggleInjection.Disabled = false;
|
||||
FuelAmount.Text = $"{castState.FuelAmount}";
|
||||
}
|
||||
|
||||
if(!castState.IsMaster)
|
||||
{
|
||||
ToggleInjection.Disabled = true;
|
||||
}
|
||||
|
||||
RefreshPartsButton.Disabled = castState.Injecting;
|
||||
|
||||
CoreCount.Text = $"{castState.CoreCount}";
|
||||
InjectionAmount.Text = $"{castState.InjectionAmount}";
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user