Item status!
This commit is contained in:
106
Content.Client/GameObjects/Components/HandheldLightComponent.cs
Normal file
106
Content.Client/GameObjects/Components/HandheldLightComponent.cs
Normal file
@@ -0,0 +1,106 @@
|
||||
using System;
|
||||
using Content.Shared.GameObjects.Components;
|
||||
using Robust.Client.Graphics.Drawing;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Client.GameObjects.Components
|
||||
{
|
||||
[RegisterComponent]
|
||||
public sealed class HandheldLightComponent : SharedHandheldLightComponent, IItemStatus
|
||||
{
|
||||
[ViewVariables] public float? Charge { get; private set; }
|
||||
|
||||
public Control MakeControl()
|
||||
{
|
||||
return new StatusControl(this);
|
||||
}
|
||||
|
||||
public override void HandleComponentState(ComponentState curState, ComponentState nextState)
|
||||
{
|
||||
var cast = (HandheldLightComponentState) curState;
|
||||
|
||||
Charge = cast.Charge;
|
||||
}
|
||||
|
||||
private sealed class StatusControl : Control
|
||||
{
|
||||
private const float TimerCycle = 1;
|
||||
|
||||
private readonly HandheldLightComponent _parent;
|
||||
private readonly PanelContainer[] _sections = new PanelContainer[5];
|
||||
|
||||
private float _timer;
|
||||
|
||||
private static readonly StyleBoxFlat _styleBoxLit = new StyleBoxFlat
|
||||
{
|
||||
BackgroundColor = Color.Green
|
||||
};
|
||||
|
||||
private static readonly StyleBoxFlat _styleBoxUnlit = new StyleBoxFlat
|
||||
{
|
||||
BackgroundColor = Color.Black
|
||||
};
|
||||
|
||||
public StatusControl(HandheldLightComponent parent)
|
||||
{
|
||||
_parent = parent;
|
||||
|
||||
var wrapper = new HBoxContainer
|
||||
{
|
||||
SeparationOverride = 4,
|
||||
SizeFlagsHorizontal = SizeFlags.ShrinkCenter
|
||||
};
|
||||
|
||||
AddChild(wrapper);
|
||||
|
||||
for (var i = 0; i < _sections.Length; i++)
|
||||
{
|
||||
var panel = new PanelContainer {CustomMinimumSize = (20, 20)};
|
||||
wrapper.AddChild(panel);
|
||||
_sections[i] = panel;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Update(FrameEventArgs args)
|
||||
{
|
||||
base.Update(args);
|
||||
|
||||
_timer += args.DeltaSeconds;
|
||||
_timer %= TimerCycle;
|
||||
|
||||
var charge = _parent.Charge ?? 0;
|
||||
|
||||
int level;
|
||||
|
||||
if (FloatMath.CloseTo(charge, 0))
|
||||
{
|
||||
level = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
level = 1 + (int) MathF.Round(charge * 6);
|
||||
}
|
||||
|
||||
if (level == 1)
|
||||
{
|
||||
// Flash the last light.
|
||||
_sections[0].PanelOverride = _timer > TimerCycle / 2 ? _styleBoxLit : _styleBoxUnlit;
|
||||
}
|
||||
else
|
||||
{
|
||||
_sections[0].PanelOverride = level > 2 ? _styleBoxLit : _styleBoxUnlit;
|
||||
}
|
||||
|
||||
_sections[1].PanelOverride = level > 3 ? _styleBoxLit : _styleBoxUnlit;
|
||||
_sections[2].PanelOverride = level > 4 ? _styleBoxLit : _styleBoxUnlit;
|
||||
_sections[3].PanelOverride = level > 5 ? _styleBoxLit : _styleBoxUnlit;
|
||||
_sections[4].PanelOverride = level > 6 ? _styleBoxLit : _styleBoxUnlit;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
33
Content.Client/GameObjects/Components/IItemStatus.cs
Normal file
33
Content.Client/GameObjects/Components/IItemStatus.cs
Normal file
@@ -0,0 +1,33 @@
|
||||
using Robust.Client.UserInterface;
|
||||
|
||||
namespace Content.Client.GameObjects.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Allows a component to provide status tooltips next to the hands interface.
|
||||
/// </summary>
|
||||
public interface IItemStatus
|
||||
{
|
||||
/// <summary>
|
||||
/// Called to get a control that represents the status for this component.
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// The control to render as status.
|
||||
/// </returns>
|
||||
public Control MakeControl();
|
||||
|
||||
/// <summary>
|
||||
/// Called when the item no longer needs this status (say, dropped from hand)
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Useful to allow you to drop the control for the GC, if you need to.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Note that this may be called after a second invocation of <see cref="MakeControl"/> (for example if the user switches the item between two hands).
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public void DestroyControl(Control control)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
|
||||
namespace Content.Client.GameObjects
|
||||
{
|
||||
[RegisterComponent]
|
||||
public class ItemStatusComponent : Component
|
||||
{
|
||||
public override string Name => "ItemStatus";
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
60
Content.Client/GameObjects/Components/StackComponent.cs
Normal file
60
Content.Client/GameObjects/Components/StackComponent.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
using Content.Client.UserInterface;
|
||||
using Content.Client.Utility;
|
||||
using Content.Shared.GameObjects.Components;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Client.GameObjects.Components
|
||||
{
|
||||
[RegisterComponent]
|
||||
public class StackComponent : SharedStackComponent, IItemStatus
|
||||
{
|
||||
[ViewVariables] public int Count { get; private set; }
|
||||
[ViewVariables] public int MaxCount { get; private set; }
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)] private bool _uiUpdateNeeded;
|
||||
|
||||
public Control MakeControl() => new StatusControl(this);
|
||||
|
||||
public override void HandleComponentState(ComponentState curState, ComponentState nextState)
|
||||
{
|
||||
var cast = (StackComponentState) curState;
|
||||
|
||||
Count = cast.Count;
|
||||
MaxCount = cast.MaxCount;
|
||||
}
|
||||
|
||||
private sealed class StatusControl : Control
|
||||
{
|
||||
private readonly StackComponent _parent;
|
||||
private readonly RichTextLabel _label;
|
||||
|
||||
public StatusControl(StackComponent parent)
|
||||
{
|
||||
_parent = parent;
|
||||
_label = new RichTextLabel {StyleClasses = {NanoStyle.StyleClassItemStatus}};
|
||||
AddChild(_label);
|
||||
|
||||
parent._uiUpdateNeeded = true;
|
||||
}
|
||||
|
||||
protected override void Update(FrameEventArgs args)
|
||||
{
|
||||
base.Update(args);
|
||||
|
||||
if (!_parent._uiUpdateNeeded)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_parent._uiUpdateNeeded = false;
|
||||
|
||||
_label.SetMarkup(Loc.GetString("Count: [color=white]{0}[/color]", _parent.Count));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,284 @@
|
||||
using System;
|
||||
using Content.Client.Animations;
|
||||
using Content.Client.UserInterface;
|
||||
using Content.Client.Utility;
|
||||
using Content.Shared.GameObjects;
|
||||
using Content.Shared.GameObjects.Components.Weapons.Ranged;
|
||||
using Robust.Client.Animations;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.Animations;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Interfaces.Network;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using static Content.Client.StaticIoC;
|
||||
|
||||
namespace Content.Client.GameObjects.Components.Weapons.Ranged
|
||||
{
|
||||
[RegisterComponent]
|
||||
public class BallisticMagazineWeaponComponent : Component, IItemStatus
|
||||
{
|
||||
private static readonly Animation AlarmAnimationSmg = new Animation
|
||||
{
|
||||
Length = TimeSpan.FromSeconds(1.4),
|
||||
AnimationTracks =
|
||||
{
|
||||
new AnimationTrackControlProperty
|
||||
{
|
||||
// These timings match the SMG audio file.
|
||||
Property = nameof(Label.FontColorOverride),
|
||||
InterpolationMode = AnimationInterpolationMode.Previous,
|
||||
KeyFrames =
|
||||
{
|
||||
new AnimationTrackProperty.KeyFrame(Color.Red, 0.1f),
|
||||
new AnimationTrackProperty.KeyFrame(null, 0.3f),
|
||||
new AnimationTrackProperty.KeyFrame(Color.Red, 0.2f),
|
||||
new AnimationTrackProperty.KeyFrame(null, 0.3f),
|
||||
new AnimationTrackProperty.KeyFrame(Color.Red, 0.2f),
|
||||
new AnimationTrackProperty.KeyFrame(null, 0.3f),
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private static readonly Animation AlarmAnimationLmg = new Animation
|
||||
{
|
||||
Length = TimeSpan.FromSeconds(0.75),
|
||||
AnimationTracks =
|
||||
{
|
||||
new AnimationTrackControlProperty
|
||||
{
|
||||
// These timings match the SMG audio file.
|
||||
Property = nameof(Label.FontColorOverride),
|
||||
InterpolationMode = AnimationInterpolationMode.Previous,
|
||||
KeyFrames =
|
||||
{
|
||||
new AnimationTrackProperty.KeyFrame(Color.Red, 0.0f),
|
||||
new AnimationTrackProperty.KeyFrame(null, 0.15f),
|
||||
new AnimationTrackProperty.KeyFrame(Color.Red, 0.15f),
|
||||
new AnimationTrackProperty.KeyFrame(null, 0.15f),
|
||||
new AnimationTrackProperty.KeyFrame(Color.Red, 0.15f),
|
||||
new AnimationTrackProperty.KeyFrame(null, 0.15f),
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public override string Name => "BallisticMagazineWeapon";
|
||||
public override uint? NetID => ContentNetIDs.BALLISTIC_MAGAZINE_WEAPON;
|
||||
public override Type StateType => typeof(BallisticMagazineWeaponComponentState);
|
||||
|
||||
private StatusControl _statusControl;
|
||||
|
||||
/// <summary>
|
||||
/// True if a bullet is chambered.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public bool Chambered { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Count of bullets in the magazine.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Null if no magazine is inserted.
|
||||
/// </remarks>
|
||||
[ViewVariables]
|
||||
public (int count, int max)? MagazineCount { get; private set; }
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)] private bool _isLmgAlarmAnimation;
|
||||
|
||||
public override void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
base.ExposeData(serializer);
|
||||
|
||||
serializer.DataField(ref _isLmgAlarmAnimation, "lmg_alarm_animation", false);
|
||||
}
|
||||
|
||||
public override void HandleComponentState(ComponentState curState, ComponentState nextState)
|
||||
{
|
||||
var cast = (BallisticMagazineWeaponComponentState) curState;
|
||||
|
||||
Chambered = cast.Chambered;
|
||||
MagazineCount = cast.MagazineCount;
|
||||
_statusControl?.Update();
|
||||
}
|
||||
|
||||
public override void HandleMessage(ComponentMessage message, INetChannel netChannel = null,
|
||||
IComponent component = null)
|
||||
{
|
||||
switch (message)
|
||||
{
|
||||
case BmwComponentAutoEjectedMessage _:
|
||||
_statusControl?.PlayAlarmAnimation();
|
||||
return;
|
||||
}
|
||||
|
||||
base.HandleMessage(message, netChannel, component);
|
||||
}
|
||||
|
||||
public Control MakeControl()
|
||||
{
|
||||
_statusControl = new StatusControl(this);
|
||||
_statusControl.Update();
|
||||
return _statusControl;
|
||||
}
|
||||
|
||||
public void DestroyControl(Control control)
|
||||
{
|
||||
if (_statusControl == control)
|
||||
{
|
||||
_statusControl = null;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class StatusControl : Control
|
||||
{
|
||||
private readonly BallisticMagazineWeaponComponent _parent;
|
||||
private readonly HBoxContainer _bulletsListTop;
|
||||
private readonly HBoxContainer _bulletsListBottom;
|
||||
private readonly TextureRect _chamberedBullet;
|
||||
private readonly Label _noMagazineLabel;
|
||||
|
||||
public StatusControl(BallisticMagazineWeaponComponent parent)
|
||||
{
|
||||
_parent = parent;
|
||||
SizeFlagsHorizontal = SizeFlags.FillExpand;
|
||||
SizeFlagsVertical = SizeFlags.ShrinkCenter;
|
||||
AddChild(new VBoxContainer
|
||||
{
|
||||
SizeFlagsHorizontal = SizeFlags.FillExpand,
|
||||
SizeFlagsVertical = SizeFlags.ShrinkCenter,
|
||||
SeparationOverride = 0,
|
||||
Children =
|
||||
{
|
||||
(_bulletsListTop = new HBoxContainer {SeparationOverride = 0}),
|
||||
new HBoxContainer
|
||||
{
|
||||
SizeFlagsHorizontal = SizeFlags.FillExpand,
|
||||
Children =
|
||||
{
|
||||
new Control
|
||||
{
|
||||
SizeFlagsHorizontal = SizeFlags.FillExpand,
|
||||
Children =
|
||||
{
|
||||
(_bulletsListBottom = new HBoxContainer
|
||||
{
|
||||
SizeFlagsVertical = SizeFlags.ShrinkCenter,
|
||||
SeparationOverride = 0
|
||||
}),
|
||||
(_noMagazineLabel = new Label
|
||||
{
|
||||
Text = "No Magazine!",
|
||||
StyleClasses = {NanoStyle.StyleClassItemStatus}
|
||||
})
|
||||
}
|
||||
},
|
||||
(_chamberedBullet = new TextureRect
|
||||
{
|
||||
Texture = ResC.GetTexture("/Textures/UserInterface/status/bullets/chambered.png"),
|
||||
SizeFlagsVertical = SizeFlags.ShrinkCenter,
|
||||
SizeFlagsHorizontal = SizeFlags.ShrinkEnd | SizeFlags.Fill,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void Update()
|
||||
{
|
||||
_chamberedBullet.ModulateSelfOverride =
|
||||
_parent.Chambered ? Color.FromHex("#d7df60") : Color.Black;
|
||||
|
||||
_bulletsListTop.RemoveAllChildren();
|
||||
_bulletsListBottom.RemoveAllChildren();
|
||||
|
||||
if (_parent.MagazineCount == null)
|
||||
{
|
||||
_noMagazineLabel.Visible = true;
|
||||
return;
|
||||
}
|
||||
|
||||
var (count, capacity) = _parent.MagazineCount.Value;
|
||||
|
||||
_noMagazineLabel.Visible = false;
|
||||
|
||||
string texturePath;
|
||||
if (capacity <= 20)
|
||||
{
|
||||
texturePath = "/Textures/UserInterface/status/bullets/normal.png";
|
||||
}
|
||||
else if (capacity <= 30)
|
||||
{
|
||||
texturePath = "/Textures/UserInterface/status/bullets/small.png";
|
||||
}
|
||||
else
|
||||
{
|
||||
texturePath = "/Textures/UserInterface/status/bullets/tiny.png";
|
||||
}
|
||||
|
||||
var texture = ResC.GetTexture(texturePath);
|
||||
|
||||
const int tinyMaxRow = 60;
|
||||
|
||||
if (capacity > tinyMaxRow)
|
||||
{
|
||||
FillBulletRow(_bulletsListBottom, Math.Min(tinyMaxRow, count), tinyMaxRow, texture);
|
||||
FillBulletRow(_bulletsListTop, Math.Max(0, count - tinyMaxRow), capacity - tinyMaxRow, texture);
|
||||
}
|
||||
else
|
||||
{
|
||||
FillBulletRow(_bulletsListBottom, count, capacity, texture);
|
||||
}
|
||||
}
|
||||
|
||||
private static void FillBulletRow(Control container, int count, int capacity, Texture texture)
|
||||
{
|
||||
var colorA = Color.FromHex("#b68f0e");
|
||||
var colorB = Color.FromHex("#d7df60");
|
||||
var colorGoneA = Color.FromHex("#000000");
|
||||
var colorGoneB = Color.FromHex("#222222");
|
||||
|
||||
var altColor = false;
|
||||
|
||||
for (var i = count; i < capacity; i++)
|
||||
{
|
||||
container.AddChild(new TextureRect
|
||||
{
|
||||
Texture = texture,
|
||||
ModulateSelfOverride = altColor ? colorGoneA : colorGoneB
|
||||
});
|
||||
|
||||
altColor ^= true;
|
||||
}
|
||||
|
||||
for (var i = 0; i < count; i++)
|
||||
{
|
||||
container.AddChild(new TextureRect
|
||||
{
|
||||
Texture = texture,
|
||||
ModulateSelfOverride = altColor ? colorA : colorB
|
||||
});
|
||||
|
||||
altColor ^= true;
|
||||
}
|
||||
}
|
||||
|
||||
protected override Vector2 CalculateMinimumSize()
|
||||
{
|
||||
return Vector2.ComponentMax((0, 15), base.CalculateMinimumSize());
|
||||
}
|
||||
|
||||
public void PlayAlarmAnimation()
|
||||
{
|
||||
var animation = _parent._isLmgAlarmAnimation ? AlarmAnimationLmg : AlarmAnimationSmg;
|
||||
_noMagazineLabel.PlayAnimation(animation, "alarm");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
74
Content.Client/GameObjects/Components/WelderComponent.cs
Normal file
74
Content.Client/GameObjects/Components/WelderComponent.cs
Normal file
@@ -0,0 +1,74 @@
|
||||
using System;
|
||||
using Content.Client.UserInterface;
|
||||
using Content.Client.Utility;
|
||||
using Content.Shared.GameObjects;
|
||||
using Content.Shared.GameObjects.Components;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Client.GameObjects.Components
|
||||
{
|
||||
[RegisterComponent]
|
||||
public class WelderComponent : Component, IItemStatus
|
||||
{
|
||||
public override string Name => "Welder";
|
||||
public override uint? NetID => ContentNetIDs.WELDER;
|
||||
public override Type StateType => typeof(WelderComponentState);
|
||||
|
||||
[ViewVariables] public float FuelCapacity { get; private set; }
|
||||
[ViewVariables] public float Fuel { get; private set; }
|
||||
[ViewVariables] public bool Activated { get; private set; }
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)] private bool _uiUpdateNeeded;
|
||||
|
||||
public override void HandleComponentState(ComponentState curState, ComponentState nextState)
|
||||
{
|
||||
var cast = (WelderComponentState) curState;
|
||||
|
||||
FuelCapacity = cast.FuelCapacity;
|
||||
Fuel = cast.Fuel;
|
||||
Activated = cast.Activated;
|
||||
|
||||
_uiUpdateNeeded = true;
|
||||
}
|
||||
|
||||
public Control MakeControl() => new StatusControl(this);
|
||||
|
||||
private sealed class StatusControl : Control
|
||||
{
|
||||
private readonly WelderComponent _parent;
|
||||
private readonly RichTextLabel _label;
|
||||
|
||||
public StatusControl(WelderComponent parent)
|
||||
{
|
||||
_parent = parent;
|
||||
_label = new RichTextLabel {StyleClasses = {NanoStyle.StyleClassItemStatus}};
|
||||
AddChild(_label);
|
||||
|
||||
parent._uiUpdateNeeded = true;
|
||||
}
|
||||
|
||||
protected override void Update(FrameEventArgs args)
|
||||
{
|
||||
base.Update(args);
|
||||
|
||||
if (!_parent._uiUpdateNeeded)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_parent._uiUpdateNeeded = false;
|
||||
|
||||
var fuelCap = _parent.FuelCapacity;
|
||||
var fuel = _parent.Fuel;
|
||||
|
||||
_label.SetMarkup(Loc.GetString("Fuel: [color={0}]{1}/{2}[/color]",
|
||||
fuel < fuelCap / 4f ? "darkorange" : "orange", Math.Round(fuel), fuelCap));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user