diff --git a/Content.Client/AssemblyInfo.cs b/Content.Client/AssemblyInfo.cs new file mode 100644 index 0000000000..54b2cd50ac --- /dev/null +++ b/Content.Client/AssemblyInfo.cs @@ -0,0 +1,3 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Content.Tests")] diff --git a/Content.Client/UserInterface/HandsGui.cs b/Content.Client/UserInterface/HandsGui.cs index d474a87275..87b0b050ad 100644 --- a/Content.Client/UserInterface/HandsGui.cs +++ b/Content.Client/UserInterface/HandsGui.cs @@ -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(); - esm.GetEntitySystem().OpenContextMenu(entity, new ScreenCoordinates(args.PointerLocation.Position)); + esm.GetEntitySystem() + .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); + } } } diff --git a/Content.Server/GameObjects/Components/Weapon/Melee/MeleeWeaponComponent.cs b/Content.Server/GameObjects/Components/Weapon/Melee/MeleeWeaponComponent.cs index 74740cf264..42eabe55c4 100644 --- a/Content.Server/GameObjects/Components/Weapon/Melee/MeleeWeaponComponent.cs +++ b/Content.Server/GameObjects/Components/Weapon/Melee/MeleeWeaponComponent.cs @@ -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(); sys.SendAnimation(Arc, angle, eventArgs.User, hitEntities); } + _lastAttackTime = IoCManager.Resolve().CurTime; + + if (Owner.TryGetComponent(out ItemCooldownComponent cooldown)) + { + cooldown.CooldownStart = _lastAttackTime; + cooldown.CooldownEnd = _lastAttackTime + TimeSpan.FromSeconds(_cooldownTime); + } } } } diff --git a/Content.Shared/GameObjects/Components/Items/ItemCooldownComponent.cs b/Content.Shared/GameObjects/Components/Items/ItemCooldownComponent.cs new file mode 100644 index 0000000000..1cfd8021b0 --- /dev/null +++ b/Content.Shared/GameObjects/Components/Items/ItemCooldownComponent.cs @@ -0,0 +1,83 @@ +using System; +using Robust.Shared.GameObjects; +using Robust.Shared.Serialization; +using Robust.Shared.ViewVariables; + +namespace Content.Shared.GameObjects.Components.Items +{ + /// + /// Stores a visual "cooldown" for items, that gets displayed in the hands GUI. + /// + [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; + + /// + /// The time when this cooldown ends. + /// + /// + /// If null, no cooldown is displayed. + /// + [ViewVariables] + public TimeSpan? CooldownEnd + { + get => _cooldownEnd; + set + { + _cooldownEnd = value; + Dirty(); + } + } + + /// + /// The time when this cooldown started. + /// + /// + /// If null, no cooldown is displayed. + /// + [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) + { + } + } + } +} diff --git a/Content.Shared/GameObjects/ContentNetIDs.cs b/Content.Shared/GameObjects/ContentNetIDs.cs index c3f0f1e4d3..33f0c986e5 100644 --- a/Content.Shared/GameObjects/ContentNetIDs.cs +++ b/Content.Shared/GameObjects/ContentNetIDs.cs @@ -31,5 +31,6 @@ public const uint STATUSEFFECTS = 1026; public const uint OVERLAYEFFECTS = 1027; public const uint STOMACH = 1028; + public const uint ITEMCOOLDOWN = 1029; } } diff --git a/Content.Tests/Client/UserInterface/HandsGuiTest.cs b/Content.Tests/Client/UserInterface/HandsGuiTest.cs new file mode 100644 index 0000000000..154de537ba --- /dev/null +++ b/Content.Tests/Client/UserInterface/HandsGuiTest.cs @@ -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); + } + } +} diff --git a/Resources/Prototypes/Entities/items/toolbox.yml b/Resources/Prototypes/Entities/items/toolbox.yml index 8cd4b41ce0..b24918463b 100644 --- a/Resources/Prototypes/Entities/items/toolbox.yml +++ b/Resources/Prototypes/Entities/items/toolbox.yml @@ -6,6 +6,7 @@ Capacity: 60 - type: Item Size: 9999 + - type: ItemCooldown - type: MeleeWeapon hitSound: "/Audio/weapons/smash.ogg" diff --git a/Resources/Prototypes/Entities/items/tools.yml b/Resources/Prototypes/Entities/items/tools.yml index 3fcf365ccf..4e23db703a 100644 --- a/Resources/Prototypes/Entities/items/tools.yml +++ b/Resources/Prototypes/Entities/items/tools.yml @@ -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 diff --git a/Resources/Prototypes/Entities/items/weapons/kitchen.yml b/Resources/Prototypes/Entities/items/weapons/kitchen.yml index 25995043f1..bc40643057 100644 --- a/Resources/Prototypes/Entities/items/weapons/kitchen.yml +++ b/Resources/Prototypes/Entities/items/weapons/kitchen.yml @@ -13,6 +13,7 @@ sprite: Objects/Melee/cleaver.rsi state: butch + - type: ItemCooldown - type: MeleeWeapon - type: Item Size: 10 diff --git a/Resources/Prototypes/Entities/items/weapons/mining.yml b/Resources/Prototypes/Entities/items/weapons/mining.yml index 5349a7b471..cb0a460380 100644 --- a/Resources/Prototypes/Entities/items/weapons/mining.yml +++ b/Resources/Prototypes/Entities/items/weapons/mining.yml @@ -11,6 +11,7 @@ sprite: Objects/Melee/pickaxe.rsi state: pickaxe - type: Pickaxe + - type: ItemCooldown - type: MeleeWeapon damage: 25 - type: Item diff --git a/Resources/Prototypes/Entities/items/weapons/spear.yml b/Resources/Prototypes/Entities/items/weapons/spear.yml index 5595c69dea..b03cf7bba0 100644 --- a/Resources/Prototypes/Entities/items/weapons/spear.yml +++ b/Resources/Prototypes/Entities/items/weapons/spear.yml @@ -21,6 +21,8 @@ sprite: Objects/Melee/spear.rsi prefix: inhand + - type: ItemCooldown + - type: MeleeWeaponAnimation id: spear state: spear diff --git a/Resources/Textures/UserInterface/Inventory/cooldown-0.png b/Resources/Textures/UserInterface/Inventory/cooldown-0.png new file mode 100644 index 0000000000..9f31aa4304 Binary files /dev/null and b/Resources/Textures/UserInterface/Inventory/cooldown-0.png differ diff --git a/Resources/Textures/UserInterface/Inventory/cooldown-1.png b/Resources/Textures/UserInterface/Inventory/cooldown-1.png new file mode 100644 index 0000000000..965b4b777d Binary files /dev/null and b/Resources/Textures/UserInterface/Inventory/cooldown-1.png differ diff --git a/Resources/Textures/UserInterface/Inventory/cooldown-2.png b/Resources/Textures/UserInterface/Inventory/cooldown-2.png new file mode 100644 index 0000000000..2e23286978 Binary files /dev/null and b/Resources/Textures/UserInterface/Inventory/cooldown-2.png differ diff --git a/Resources/Textures/UserInterface/Inventory/cooldown-3.png b/Resources/Textures/UserInterface/Inventory/cooldown-3.png new file mode 100644 index 0000000000..4d196d64b1 Binary files /dev/null and b/Resources/Textures/UserInterface/Inventory/cooldown-3.png differ diff --git a/Resources/Textures/UserInterface/Inventory/cooldown-4.png b/Resources/Textures/UserInterface/Inventory/cooldown-4.png new file mode 100644 index 0000000000..dbd129d7d5 Binary files /dev/null and b/Resources/Textures/UserInterface/Inventory/cooldown-4.png differ diff --git a/Resources/Textures/UserInterface/Inventory/cooldown-5.png b/Resources/Textures/UserInterface/Inventory/cooldown-5.png new file mode 100644 index 0000000000..3af065ecb1 Binary files /dev/null and b/Resources/Textures/UserInterface/Inventory/cooldown-5.png differ diff --git a/Resources/Textures/UserInterface/Inventory/cooldown-6.png b/Resources/Textures/UserInterface/Inventory/cooldown-6.png new file mode 100644 index 0000000000..4d6097c60c Binary files /dev/null and b/Resources/Textures/UserInterface/Inventory/cooldown-6.png differ diff --git a/Resources/Textures/UserInterface/Inventory/cooldown-7.png b/Resources/Textures/UserInterface/Inventory/cooldown-7.png new file mode 100644 index 0000000000..fe3154f2b2 Binary files /dev/null and b/Resources/Textures/UserInterface/Inventory/cooldown-7.png differ diff --git a/Resources/Textures/UserInterface/Inventory/cooldown.svg b/Resources/Textures/UserInterface/Inventory/cooldown.svg new file mode 100644 index 0000000000..3248c426a0 --- /dev/null +++ b/Resources/Textures/UserInterface/Inventory/cooldown.svg @@ -0,0 +1,68 @@ + + + + + + + + + + image/svg+xml + + + + + + + + +