Visualize melee weapon cooldowns in HUD.
3
Content.Client/AssemblyInfo.cs
Normal file
@@ -0,0 +1,3 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("Content.Tests")]
|
||||
@@ -1,10 +1,11 @@
|
||||
using Content.Client.GameObjects;
|
||||
using System;
|
||||
using Content.Client.GameObjects;
|
||||
using Content.Client.GameObjects.EntitySystems;
|
||||
using Content.Client.Interfaces.GameObjects;
|
||||
using Content.Client.Utility;
|
||||
using Content.Shared.GameObjects.Components.Items;
|
||||
using Content.Shared.Input;
|
||||
using Robust.Client.Graphics;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Client.Interfaces.GameObjects.Components;
|
||||
using Robust.Client.Interfaces.ResourceManagement;
|
||||
using Robust.Client.Player;
|
||||
@@ -12,15 +13,18 @@ using Robust.Client.UserInterface;
|
||||
using Robust.Client.UserInterface.Controls;
|
||||
using Robust.Shared.Input;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Interfaces.Timing;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Localization;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Timing;
|
||||
|
||||
namespace Content.Client.UserInterface
|
||||
{
|
||||
public class HandsGui : Control
|
||||
{
|
||||
private const int CooldownLevels = 8;
|
||||
private const int BoxSpacing = 0;
|
||||
private const int BoxSize = 64;
|
||||
|
||||
@@ -28,11 +32,13 @@ namespace Content.Client.UserInterface
|
||||
[Dependency] private readonly IPlayerManager _playerManager;
|
||||
[Dependency] private readonly IResourceCache _resourceCache;
|
||||
[Dependency] private readonly ILocalizationManager _loc;
|
||||
[Dependency] private readonly IGameTiming _gameTiming;
|
||||
#pragma warning restore 0649
|
||||
|
||||
private Texture TextureHandLeft;
|
||||
private Texture TextureHandRight;
|
||||
private Texture TextureHandActive;
|
||||
private readonly Texture TextureHandLeft;
|
||||
private readonly Texture TextureHandRight;
|
||||
private readonly Texture TextureHandActive;
|
||||
private readonly Texture[] TexturesCooldownOverlay;
|
||||
|
||||
private IEntity LeftHand;
|
||||
private IEntity RightHand;
|
||||
@@ -43,6 +49,9 @@ namespace Content.Client.UserInterface
|
||||
private readonly SpriteView RightSpriteView;
|
||||
private readonly TextureRect ActiveHandRect;
|
||||
|
||||
private readonly TextureRect CooldownCircleLeft;
|
||||
private readonly TextureRect CooldownCircleRight;
|
||||
|
||||
public HandsGui()
|
||||
{
|
||||
IoCManager.InjectDependencies(this);
|
||||
@@ -58,6 +67,13 @@ namespace Content.Client.UserInterface
|
||||
TextureHandRight = _resourceCache.GetTexture("/Textures/UserInterface/Inventory/hand_r.png");
|
||||
TextureHandActive = _resourceCache.GetTexture("/Textures/UserInterface/Inventory/hand_active.png");
|
||||
|
||||
TexturesCooldownOverlay = new Texture[CooldownLevels];
|
||||
for (var i = 0; i < CooldownLevels; i++)
|
||||
{
|
||||
TexturesCooldownOverlay[i] =
|
||||
_resourceCache.GetTexture($"/Textures/UserInterface/Inventory/cooldown-{i}.png");
|
||||
}
|
||||
|
||||
AddChild(new TextureRect
|
||||
{
|
||||
MouseFilter = MouseFilterMode.Ignore,
|
||||
@@ -102,6 +118,24 @@ namespace Content.Client.UserInterface
|
||||
AddChild(RightSpriteView);
|
||||
RightSpriteView.Size = _handR.Size;
|
||||
RightSpriteView.Position = _handR.TopLeft;
|
||||
|
||||
// Cooldown circles.
|
||||
AddChild(CooldownCircleLeft = new TextureRect
|
||||
{
|
||||
MouseFilter = MouseFilterMode.Ignore,
|
||||
Position = _handL.TopLeft + (8, 8),
|
||||
TextureScale = (2, 2),
|
||||
Visible = false,
|
||||
|
||||
});
|
||||
|
||||
AddChild(CooldownCircleRight = new TextureRect
|
||||
{
|
||||
MouseFilter = MouseFilterMode.Ignore,
|
||||
Position = _handR.TopLeft + (8, 8),
|
||||
TextureScale = (2, 2),
|
||||
Visible = false
|
||||
});
|
||||
}
|
||||
|
||||
protected override Vector2 CalculateMinimumSize()
|
||||
@@ -210,8 +244,8 @@ namespace Content.Client.UserInterface
|
||||
return;
|
||||
}
|
||||
|
||||
var leftHandContains = _handL.Contains((Vector2i)args.RelativePosition);
|
||||
var rightHandContains = _handR.Contains((Vector2i)args.RelativePosition);
|
||||
var leftHandContains = _handL.Contains((Vector2i) args.RelativePosition);
|
||||
var rightHandContains = _handR.Contains((Vector2i) args.RelativePosition);
|
||||
|
||||
string handIndex;
|
||||
if (leftHandContains)
|
||||
@@ -262,8 +296,55 @@ namespace Content.Client.UserInterface
|
||||
}
|
||||
|
||||
var esm = IoCManager.Resolve<IEntitySystemManager>();
|
||||
esm.GetEntitySystem<VerbSystem>().OpenContextMenu(entity, new ScreenCoordinates(args.PointerLocation.Position));
|
||||
esm.GetEntitySystem<VerbSystem>()
|
||||
.OpenContextMenu(entity, new ScreenCoordinates(args.PointerLocation.Position));
|
||||
}
|
||||
}
|
||||
|
||||
protected override void FrameUpdate(FrameEventArgs args)
|
||||
{
|
||||
base.FrameUpdate(args);
|
||||
|
||||
UpdateCooldown(CooldownCircleLeft, LeftHand);
|
||||
UpdateCooldown(CooldownCircleRight, RightHand);
|
||||
}
|
||||
|
||||
private void UpdateCooldown(TextureRect cooldownTexture, IEntity entity)
|
||||
{
|
||||
if (entity != null
|
||||
&& entity.TryGetComponent(out ItemCooldownComponent cooldown)
|
||||
&& cooldown.CooldownStart.HasValue
|
||||
&& cooldown.CooldownEnd.HasValue)
|
||||
{
|
||||
var start = cooldown.CooldownStart.Value;
|
||||
var end = cooldown.CooldownEnd.Value;
|
||||
|
||||
var length = (end - start).TotalSeconds;
|
||||
var progress = (_gameTiming.CurTime - start).TotalSeconds;
|
||||
var ratio = (float)(progress / length);
|
||||
|
||||
var textureIndex = CalculateCooldownLevel(ratio);
|
||||
if (textureIndex == CooldownLevels)
|
||||
{
|
||||
cooldownTexture.Visible = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
cooldownTexture.Visible = true;
|
||||
cooldownTexture.Texture = TexturesCooldownOverlay[textureIndex];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
cooldownTexture.Visible = false;
|
||||
}
|
||||
}
|
||||
|
||||
internal static int CalculateCooldownLevel(float cooldownValue)
|
||||
{
|
||||
var val = cooldownValue.Clamp(0, 1);
|
||||
val *= CooldownLevels;
|
||||
return (int) Math.Floor(val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Content.Server.GameObjects.Components.Mobs;
|
||||
using Content.Server.GameObjects.EntitySystems;
|
||||
using Content.Shared.GameObjects;
|
||||
using Content.Shared.GameObjects.Components.Items;
|
||||
using Robust.Server.GameObjects.EntitySystems;
|
||||
using Robust.Server.Interfaces.GameObjects;
|
||||
using Robust.Shared.GameObjects;
|
||||
@@ -112,7 +111,14 @@ namespace Content.Server.GameObjects.Components.Weapon.Melee
|
||||
var sys = _entitySystemManager.GetEntitySystem<MeleeWeaponSystem>();
|
||||
sys.SendAnimation(Arc, angle, eventArgs.User, hitEntities);
|
||||
}
|
||||
|
||||
_lastAttackTime = IoCManager.Resolve<IGameTiming>().CurTime;
|
||||
|
||||
if (Owner.TryGetComponent(out ItemCooldownComponent cooldown))
|
||||
{
|
||||
cooldown.CooldownStart = _lastAttackTime;
|
||||
cooldown.CooldownEnd = _lastAttackTime + TimeSpan.FromSeconds(_cooldownTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
using System;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Content.Shared.GameObjects.Components.Items
|
||||
{
|
||||
/// <summary>
|
||||
/// Stores a visual "cooldown" for items, that gets displayed in the hands GUI.
|
||||
/// </summary>
|
||||
[RegisterComponent]
|
||||
public sealed class ItemCooldownComponent : Component
|
||||
{
|
||||
public override string Name => "ItemCooldown";
|
||||
public override Type StateType => typeof(ItemCooldownComponentState);
|
||||
public override uint? NetID => ContentNetIDs.ITEMCOOLDOWN;
|
||||
|
||||
private TimeSpan? _cooldownEnd;
|
||||
private TimeSpan? _cooldownStart;
|
||||
|
||||
/// <summary>
|
||||
/// The time when this cooldown ends.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If null, no cooldown is displayed.
|
||||
/// </remarks>
|
||||
[ViewVariables]
|
||||
public TimeSpan? CooldownEnd
|
||||
{
|
||||
get => _cooldownEnd;
|
||||
set
|
||||
{
|
||||
_cooldownEnd = value;
|
||||
Dirty();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The time when this cooldown started.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// If null, no cooldown is displayed.
|
||||
/// </remarks>
|
||||
[ViewVariables]
|
||||
public TimeSpan? CooldownStart
|
||||
{
|
||||
get => _cooldownStart;
|
||||
set
|
||||
{
|
||||
_cooldownStart = value;
|
||||
Dirty();
|
||||
}
|
||||
}
|
||||
|
||||
public override ComponentState GetComponentState()
|
||||
{
|
||||
return new ItemCooldownComponentState
|
||||
{
|
||||
CooldownEnd = CooldownEnd,
|
||||
CooldownStart = CooldownStart
|
||||
};
|
||||
}
|
||||
|
||||
public override void HandleComponentState(ComponentState curState, ComponentState nextState)
|
||||
{
|
||||
var cast = (ItemCooldownComponentState) curState;
|
||||
|
||||
CooldownStart = cast.CooldownStart;
|
||||
CooldownEnd = cast.CooldownEnd;
|
||||
}
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
private sealed class ItemCooldownComponentState : ComponentState
|
||||
{
|
||||
public TimeSpan? CooldownStart { get; set; }
|
||||
public TimeSpan? CooldownEnd { get; set; }
|
||||
|
||||
public ItemCooldownComponentState() : base(ContentNetIDs.ITEMCOOLDOWN)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,5 +31,6 @@
|
||||
public const uint STATUSEFFECTS = 1026;
|
||||
public const uint OVERLAYEFFECTS = 1027;
|
||||
public const uint STOMACH = 1028;
|
||||
public const uint ITEMCOOLDOWN = 1029;
|
||||
}
|
||||
}
|
||||
|
||||
17
Content.Tests/Client/UserInterface/HandsGuiTest.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using Content.Client.UserInterface;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace Content.Tests.Client.UserInterface
|
||||
{
|
||||
[TestFixture]
|
||||
public class HandsGuiTest
|
||||
{
|
||||
[Test]
|
||||
public void TestCalculateCooldownLevel()
|
||||
{
|
||||
Assert.AreEqual(HandsGui.CalculateCooldownLevel(0.5f), 4);
|
||||
Assert.AreEqual(HandsGui.CalculateCooldownLevel(1), 8);
|
||||
Assert.AreEqual(HandsGui.CalculateCooldownLevel(0), 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@
|
||||
Capacity: 60
|
||||
- type: Item
|
||||
Size: 9999
|
||||
- type: ItemCooldown
|
||||
- type: MeleeWeapon
|
||||
hitSound: "/Audio/weapons/smash.ogg"
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
texture: Objects/Tools/wirecutter.png
|
||||
- type: Icon
|
||||
texture: Objects/Tools/wirecutter.png
|
||||
- type: ItemCooldown
|
||||
- type: MeleeWeapon
|
||||
|
||||
- type: entity
|
||||
@@ -22,6 +23,7 @@
|
||||
texture: Objects/Tools/screwdriver.png
|
||||
- type: Icon
|
||||
texture: Objects/Tools/screwdriver.png
|
||||
- type: ItemCooldown
|
||||
- type: MeleeWeapon
|
||||
|
||||
- type: entity
|
||||
@@ -42,6 +44,7 @@
|
||||
- type: Icon
|
||||
sprite: Objects/Tools/welder.rsi
|
||||
state: welder
|
||||
- type: ItemCooldown
|
||||
- type: MeleeWeapon
|
||||
|
||||
- type: entity
|
||||
@@ -55,6 +58,7 @@
|
||||
texture: Objects/Tools/wrench.png
|
||||
- type: Icon
|
||||
texture: Objects/Tools/wrench.png
|
||||
- type: ItemCooldown
|
||||
- type: MeleeWeapon
|
||||
|
||||
- type: entity
|
||||
@@ -68,6 +72,7 @@
|
||||
texture: Objects/Tools/crowbar.png
|
||||
- type: Icon
|
||||
texture: Objects/Tools/crowbar.png
|
||||
- type: ItemCooldown
|
||||
- type: MeleeWeapon
|
||||
|
||||
- type: entity
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
sprite: Objects/Melee/cleaver.rsi
|
||||
state: butch
|
||||
|
||||
- type: ItemCooldown
|
||||
- type: MeleeWeapon
|
||||
- type: Item
|
||||
Size: 10
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
sprite: Objects/Melee/pickaxe.rsi
|
||||
state: pickaxe
|
||||
- type: Pickaxe
|
||||
- type: ItemCooldown
|
||||
- type: MeleeWeapon
|
||||
damage: 25
|
||||
- type: Item
|
||||
|
||||
@@ -21,6 +21,8 @@
|
||||
sprite: Objects/Melee/spear.rsi
|
||||
prefix: inhand
|
||||
|
||||
- type: ItemCooldown
|
||||
|
||||
- type: MeleeWeaponAnimation
|
||||
id: spear
|
||||
state: spear
|
||||
|
||||
BIN
Resources/Textures/UserInterface/Inventory/cooldown-0.png
Normal file
|
After Width: | Height: | Size: 456 B |
BIN
Resources/Textures/UserInterface/Inventory/cooldown-1.png
Normal file
|
After Width: | Height: | Size: 472 B |
BIN
Resources/Textures/UserInterface/Inventory/cooldown-2.png
Normal file
|
After Width: | Height: | Size: 427 B |
BIN
Resources/Textures/UserInterface/Inventory/cooldown-3.png
Normal file
|
After Width: | Height: | Size: 432 B |
BIN
Resources/Textures/UserInterface/Inventory/cooldown-4.png
Normal file
|
After Width: | Height: | Size: 365 B |
BIN
Resources/Textures/UserInterface/Inventory/cooldown-5.png
Normal file
|
After Width: | Height: | Size: 367 B |
BIN
Resources/Textures/UserInterface/Inventory/cooldown-6.png
Normal file
|
After Width: | Height: | Size: 278 B |
BIN
Resources/Textures/UserInterface/Inventory/cooldown-7.png
Normal file
|
After Width: | Height: | Size: 276 B |
68
Resources/Textures/UserInterface/Inventory/cooldown.svg
Normal file
@@ -0,0 +1,68 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 6.3499999 6.3500002"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
inkscape:version="0.92.4 5da689c313, 2019-01-14"
|
||||
sodipodi:docname="cooldown.svg"
|
||||
inkscape:export-filename="/ssdhome/pj/Projects/space-station-14-content/Resources/Textures/UserInterface/Inventory/cooldown-7.png"
|
||||
inkscape:export-xdpi="96"
|
||||
inkscape:export-ydpi="96">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="15.839192"
|
||||
inkscape:cx="8.9236949"
|
||||
inkscape:cy="12.417176"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
inkscape:pagecheckerboard="true"
|
||||
units="px"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1043"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1" />
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="circle"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-290.64999)">
|
||||
<path
|
||||
style="opacity:1;vector-effect:none;fill:#4a4666;fill-opacity:0.50196078;fill-rule:nonzero;stroke:none;stroke-width:1.99293923;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
|
||||
d="M 12,0 A 12,12 0 0 0 3.5195312,3.5195312 L 12,12 Z"
|
||||
transform="matrix(0.26458334,0,0,0.26458334,0,290.64999)"
|
||||
id="circle"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.3 KiB |