From 841bb101c5005bd8f124d0630bab193e8a15d390 Mon Sep 17 00:00:00 2001 From: Pieter-Jan Briers Date: Tue, 12 Nov 2019 01:43:11 +0100 Subject: [PATCH] Visualize melee weapon cooldowns in HUD. --- Content.Client/AssemblyInfo.cs | 3 + Content.Client/UserInterface/HandsGui.cs | 97 ++++++++++++++++-- .../Weapon/Melee/MeleeWeaponComponent.cs | 10 +- .../Components/Items/ItemCooldownComponent.cs | 83 +++++++++++++++ Content.Shared/GameObjects/ContentNetIDs.cs | 1 + .../Client/UserInterface/HandsGuiTest.cs | 17 +++ .../Prototypes/Entities/items/toolbox.yml | 1 + Resources/Prototypes/Entities/items/tools.yml | 5 + .../Entities/items/weapons/kitchen.yml | 1 + .../Entities/items/weapons/mining.yml | 1 + .../Entities/items/weapons/spear.yml | 2 + .../UserInterface/Inventory/cooldown-0.png | Bin 0 -> 456 bytes .../UserInterface/Inventory/cooldown-1.png | Bin 0 -> 472 bytes .../UserInterface/Inventory/cooldown-2.png | Bin 0 -> 427 bytes .../UserInterface/Inventory/cooldown-3.png | Bin 0 -> 432 bytes .../UserInterface/Inventory/cooldown-4.png | Bin 0 -> 365 bytes .../UserInterface/Inventory/cooldown-5.png | Bin 0 -> 367 bytes .../UserInterface/Inventory/cooldown-6.png | Bin 0 -> 278 bytes .../UserInterface/Inventory/cooldown-7.png | Bin 0 -> 276 bytes .../UserInterface/Inventory/cooldown.svg | 68 ++++++++++++ 20 files changed, 279 insertions(+), 10 deletions(-) create mode 100644 Content.Client/AssemblyInfo.cs create mode 100644 Content.Shared/GameObjects/Components/Items/ItemCooldownComponent.cs create mode 100644 Content.Tests/Client/UserInterface/HandsGuiTest.cs create mode 100644 Resources/Textures/UserInterface/Inventory/cooldown-0.png create mode 100644 Resources/Textures/UserInterface/Inventory/cooldown-1.png create mode 100644 Resources/Textures/UserInterface/Inventory/cooldown-2.png create mode 100644 Resources/Textures/UserInterface/Inventory/cooldown-3.png create mode 100644 Resources/Textures/UserInterface/Inventory/cooldown-4.png create mode 100644 Resources/Textures/UserInterface/Inventory/cooldown-5.png create mode 100644 Resources/Textures/UserInterface/Inventory/cooldown-6.png create mode 100644 Resources/Textures/UserInterface/Inventory/cooldown-7.png create mode 100644 Resources/Textures/UserInterface/Inventory/cooldown.svg 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 0000000000000000000000000000000000000000..9f31aa430422f0bce3c83f440a0acbaf593958a3 GIT binary patch literal 456 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM3?#3wJbMaAF&8^|hH!9j+Bu6$SW& zxB_W6w^%>xBtO3-PtRyi&s-Omv;c<$AJ=p*=Tr}mBoB{FU$-<55Ifb!J=w=S!^K-3P=-1B!dQrhB@B7(kUEH9$e2lskl*wxRLoT%fIL zB|(0{4BWiDe0=-@s%q-$8m6XZX66>2KE8f_{(+&;Oa%8sfzfNO@aH(?+6Ei&c<}KsW_2r#s!ph;xv{3VrW3YpFaPzEX zx?L{L&l|XT-5EZgurKV|p8AMC_VHnD_bbZU%IuH0_MTjryllH>Tg+9Ki5K3;?2F?% W>TrPJRd+Seg$$mqelF{r5}E+<5SoVo literal 0 HcmV?d00001 diff --git a/Resources/Textures/UserInterface/Inventory/cooldown-1.png b/Resources/Textures/UserInterface/Inventory/cooldown-1.png new file mode 100644 index 0000000000000000000000000000000000000000..965b4b777dc09aaf77064ba7f5448734575f092b GIT binary patch literal 472 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM3?#3wJbMaAF&8^|hH!9j+Bu6$SW& zxB_W6w^%>xBtO3-PtRyi&s-OmG#{r7AJ=p*=Tr}mBoB{FU$-<55Ifb!J=w=S!^K-3LUb0R_EX(>=iqph}Pe*ECPpRBv~nVjwq7{KelC zpsi{pL4Lsu+`PPeeEia?YU=75rlw|Q<`xc~KE8hbfl<*hIk|b2Rdsds4N*Iylz}>y zdAc};NJ!=$Y;#oO<=c$~Cf~>wDIrAdy-`+F5=O5$!LW|jLx0lrbUCiL=>gTe~DWM4fuga&{ literal 0 HcmV?d00001 diff --git a/Resources/Textures/UserInterface/Inventory/cooldown-2.png b/Resources/Textures/UserInterface/Inventory/cooldown-2.png new file mode 100644 index 0000000000000000000000000000000000000000..2e23286978c443e8080a6e1149b28185c68fc2d4 GIT binary patch literal 427 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM3?#3wJbMaAF&8^|hH!9j+Bur3Lte zxB_W6w^%>xBu~$1PtRNzmoy*ObT8*r504}dk4#^;G!GCv)yF;A$34T#EzQ#<#nUa- z%PrN@E#2ES)yqBI#}&v<^>$77bWa0IrGaE!(>z^Mz1@K-f!ws(X;V%BZBi-;@(X6* z=H=t#S5;G2*Dy6TGc&jF^zrrc4~&k<$<3>*s;jGS*kZ8^1N`#onLwW>~08|8=bp0&tFu{{?BuFBu~$1PtRNzmoy)z3?J8Y506Y=w=^%;bRYL*ANLF|w=_?e6d#WaZ}(JB zHy}#)c1`tiPxo?919H7x)4g2NJi%KZ0yX66jVd26PYZ%y=OoVuZF39a@zu-LKL(zk6PA3&!v~Yhq$L!=@uTWB?UE1?Fp+C;= w%@)J>`*T9<mdKI;Vst05RN^#sB~S literal 0 HcmV?d00001 diff --git a/Resources/Textures/UserInterface/Inventory/cooldown-4.png b/Resources/Textures/UserInterface/Inventory/cooldown-4.png new file mode 100644 index 0000000000000000000000000000000000000000..dbd129d7d5202ed8e2cd635f1570c7f6c97f74f9 GIT binary patch literal 365 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM3?#3wJbMaAF&8^|hH!9j+Bu1qS$p zxB_W6w^&clXiv{v7nd|2*K`k$OkcM&ANOP*_Y5z$G*6clPq$Q0w{&mUR4?~*AlutD z-P1h{$N2zJC7EF*&(; zl~r|h^$pxC-H(CF(mh=qLp07$y<{oWV8FxdQ0Tm4y}+0M?Lh`24WTYew|%I{dEoc% z+0n<-xA3d{e68&tbzz>#!m3~VDf7&FwkYy5$*`W`GGn!qy7uBBZ?gE21qydr7jQ5= pII^&zNW5W*bq>R(s;d3xSzoRcYu`TQ9VlQKJYD@<);T3K0RXh1cSisK literal 0 HcmV?d00001 diff --git a/Resources/Textures/UserInterface/Inventory/cooldown-5.png b/Resources/Textures/UserInterface/Inventory/cooldown-5.png new file mode 100644 index 0000000000000000000000000000000000000000..3af065ecb110e4abd4449230ff780774e16fcb28 GIT binary patch literal 367 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM3?#3wJbMaAF&8^|hH!9j+Bu1qS$p zxB_WEza&r3Xiv{v7nd|2rwn)ZWM8*5ANOQ0w=_?e6c3jaACC+#w^UEJbT9XGPq$QW z*K|+!G;bi+Ee#~@n&#=6>g}Ei=B6DzSN#!asB}q?UoZnNA0NN8iiVlFg@c2SuYYt* zPHtXhRb5?uLm7`V8&FxMr;B5V#`&!mEcqG?cw7RR8wI{?XnF8kUc|eD$!J=}kEQoR z)OH!2JKs3zlj5c+cXAWlF39j4y!TyPq|vaa=b#I#jP?W52vZLE1+@nj-O1eb{_bl< u(|f1g%XUn!+A)0-@4Ka1+gh|=-uIL85V$GTmU0GYDTAl0pUXO@geCw0sC*It literal 0 HcmV?d00001 diff --git a/Resources/Textures/UserInterface/Inventory/cooldown-6.png b/Resources/Textures/UserInterface/Inventory/cooldown-6.png new file mode 100644 index 0000000000000000000000000000000000000000..4d6097c60c994931d4ec812dc9e69aa608354719 GIT binary patch literal 278 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaN3?zjj6;1;w=3*z$5DpHG+YkL80J)q69+AZi z417mGm~pB$pEOWVvcxr_Bsf2(yEr+qAXP8FD1G)j8!4b7g8-ip zS0L@_8SUwr>*A8;>z3x_mgeb_;^~&|<(}^Cn(pZi7I97Uc25OL0nypo3!i{`1WJPZ zf*JVu_%+NeeEnl`@~Z0U8`!jtXaVJHJY5_^BrYc>06|~OVFu=&t^fZYwzT^%D6{Rm zhs^%}9DJ86RrrqAtMus_smskYmY=yRL&S3j3^P6(yEr+qAXP8FD1G)j8!4b7g8-ip zS0L@_8SUwr>*JK+?w;)9p6ub0=Hrp!>6Y&8n(pbI=H-^==?X-tKn4))%KH}s)FV(5 z+2ett3<3hK2fK^IEI60kP?_~Y z?a;47-3kBL6uzIF=yt@nd-^7o9mh + + + + + + + + + image/svg+xml + + + + + + + + +