From fc9991cff242c71c9c217c892f2f2e5dea18985f Mon Sep 17 00:00:00 2001 From: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> Date: Wed, 9 Nov 2022 07:28:49 +1100 Subject: [PATCH] Polish melee effects (#11653) * Polish melee effects * adjustments * Animation changes * Fix fist --- .../Components/WeaponArcVisualsComponent.cs | 3 + .../Melee/MeleeWeaponSystem.Effects.cs | 181 ++++++++++++++++- .../Weapons/Melee/MeleeWeaponSystem.cs | 190 +----------------- .../Zombies/ZombifyOnDeathSystem.cs | 3 +- .../Weapons/Melee/MeleeWeaponComponent.cs | 5 +- .../Weapons/Melee/SharedMeleeWeaponSystem.cs | 17 +- .../Entities/Effects/weapon_arc.yml | 70 ++++++- .../Prototypes/Entities/Mobs/NPCs/xeno.yml | 2 +- .../Entities/Mobs/Species/reptilian.yml | 2 +- Resources/Textures/Effects/arcs.rsi/fist.png | Bin 188 -> 5103 bytes Resources/Textures/Effects/arcs.rsi/meta.json | 2 +- Resources/Textures/Effects/arcs.rsi/slash.png | Bin 202 -> 4947 bytes Resources/Textures/Effects/arcs.rsi/spear.png | Bin 206 -> 4936 bytes 13 files changed, 272 insertions(+), 203 deletions(-) diff --git a/Content.Client/Weapons/Melee/Components/WeaponArcVisualsComponent.cs b/Content.Client/Weapons/Melee/Components/WeaponArcVisualsComponent.cs index 0c8d6bb9ba..f12bbc76ef 100644 --- a/Content.Client/Weapons/Melee/Components/WeaponArcVisualsComponent.cs +++ b/Content.Client/Weapons/Melee/Components/WeaponArcVisualsComponent.cs @@ -8,6 +8,9 @@ public sealed class WeaponArcVisualsComponent : Component { [ViewVariables, DataField("animation")] public WeaponArcAnimation Animation = WeaponArcAnimation.None; + + [ViewVariables(VVAccess.ReadWrite), DataField("fadeOut")] + public bool Fadeout = true; } public enum WeaponArcAnimation : byte diff --git a/Content.Client/Weapons/Melee/MeleeWeaponSystem.Effects.cs b/Content.Client/Weapons/Melee/MeleeWeaponSystem.Effects.cs index d90cb171cd..b640428a2b 100644 --- a/Content.Client/Weapons/Melee/MeleeWeaponSystem.Effects.cs +++ b/Content.Client/Weapons/Melee/MeleeWeaponSystem.Effects.cs @@ -1,9 +1,10 @@ +using Content.Client.Weapons.Melee.Components; using Content.Shared.Weapons; using Content.Shared.Weapons.Melee; using Robust.Client.Animations; using Robust.Client.GameObjects; using Robust.Shared.Animations; -using Robust.Shared.Utility; +using Robust.Shared.Map; namespace Content.Client.Weapons.Melee; @@ -13,7 +14,12 @@ public sealed partial class MeleeWeaponSystem /// It's a little on the long side but given we use multiple colours denoting what happened it makes it easier to register. /// private const float DamageAnimationLength = 0.30f; + + private const string AnimationKey = "melee-animation"; private const string DamageAnimationKey = "damage-effect"; + private const string FadeAnimationKey = "melee-fade"; + private const string SlashAnimationKey = "melee-slash"; + private const string ThrustAnimationKey = "melee-thrust"; private void InitializeEffect() { @@ -54,7 +60,7 @@ public sealed partial class MeleeWeaponSystem InterpolationMode = AnimationInterpolationMode.Linear, KeyFrames = { - new AnimationTrackProperty.KeyFrame(color * sprite.Color, 0f), + new AnimationTrackProperty.KeyFrame(color, 0f), new AnimationTrackProperty.KeyFrame(sprite.Color, DamageAnimationLength) } } @@ -104,4 +110,175 @@ public sealed partial class MeleeWeaponSystem _animation.Play(player, animation, DamageAnimationKey); } } + + /// + /// Does all of the melee effects for a player that are predicted, i.e. character lunge and weapon animation. + /// + public override void DoLunge(EntityUid user, Angle angle, Vector2 localPos, string? animation) + { + if (!Timing.IsFirstTimePredicted) + return; + + var lunge = GetLungeAnimation(localPos); + + // Stop any existing lunges on the user. + _animation.Stop(user, MeleeLungeKey); + _animation.Play(user, lunge, MeleeLungeKey); + + // Clientside entity to spawn + if (animation != null) + { + if (!TryComp(user, out var userXform)) + return; + + var coords = userXform.Coordinates; + var animationUid = Spawn(animation, coords); + + if (localPos != Vector2.Zero && TryComp(animationUid, out var sprite)) + { + if (TryComp(animationUid, out var arcComponent)) + { + sprite.NoRotation = true; + sprite.Rotation = localPos.ToWorldAngle(); + + var distance = Math.Clamp(localPos.Length / 2f, 0.2f, 1f); + + switch (arcComponent.Animation) + { + case WeaponArcAnimation.Slash: + _animation.Play(animationUid, GetSlashAnimation(sprite, angle), SlashAnimationKey); + if (arcComponent.Fadeout) + _animation.Play(animationUid, GetFadeAnimation(sprite, 0.065f, 0.065f + 0.05f), FadeAnimationKey); + break; + case WeaponArcAnimation.Thrust: + _animation.Play(animationUid, GetThrustAnimation(sprite, distance), ThrustAnimationKey); + if (arcComponent.Fadeout) + _animation.Play(animationUid, GetFadeAnimation(sprite, 0.05f, 0.15f), FadeAnimationKey); + break; + case WeaponArcAnimation.None: + var mapPos = userXform.WorldPosition; + var xform = Transform(animationUid); + xform.AttachToGridOrMap(); + xform.WorldPosition = mapPos + (userXform.WorldRotation - userXform.LocalRotation).RotateVec(localPos); + if (arcComponent.Fadeout) + _animation.Play(animationUid, GetFadeAnimation(sprite, 0f, 0.15f), FadeAnimationKey); + break; + } + } + } + } + } + + private Animation GetSlashAnimation(SpriteComponent sprite, Angle arc) + { + const float slashStart = 0.03f; + const float slashEnd = 0.065f; + const float length = slashEnd + 0.05f; + var startRotation = sprite.Rotation - arc / 2; + var endRotation = sprite.Rotation + arc / 2; + sprite.NoRotation = true; + + return new Animation() + { + Length = TimeSpan.FromSeconds(length), + AnimationTracks = + { + new AnimationTrackComponentProperty() + { + ComponentType = typeof(SpriteComponent), + Property = nameof(SpriteComponent.Rotation), + KeyFrames = + { + new AnimationTrackProperty.KeyFrame(startRotation, 0f), + new AnimationTrackProperty.KeyFrame(startRotation, slashStart), + new AnimationTrackProperty.KeyFrame(endRotation, slashEnd) + } + }, + new AnimationTrackComponentProperty() + { + ComponentType = typeof(SpriteComponent), + Property = nameof(SpriteComponent.Offset), + KeyFrames = + { + new AnimationTrackProperty.KeyFrame(startRotation.RotateVec(new Vector2(0f, -1f)), 0f), + new AnimationTrackProperty.KeyFrame(startRotation.RotateVec(new Vector2(0f, -1f)), slashStart), + new AnimationTrackProperty.KeyFrame(endRotation.RotateVec(new Vector2(0f, -1f)), slashEnd) + } + }, + } + }; + } + + private Animation GetThrustAnimation(SpriteComponent sprite, float distance) + { + const float thrustEnd = 0.05f; + const float length = 0.15f; + + return new Animation() + { + Length = TimeSpan.FromSeconds(length), + AnimationTracks = + { + new AnimationTrackComponentProperty() + { + ComponentType = typeof(SpriteComponent), + Property = nameof(SpriteComponent.Offset), + KeyFrames = + { + new AnimationTrackProperty.KeyFrame(sprite.Rotation.RotateVec(new Vector2(0f, -distance / 5f)), 0f), + new AnimationTrackProperty.KeyFrame(sprite.Rotation.RotateVec(new Vector2(0f, -distance)), thrustEnd), + new AnimationTrackProperty.KeyFrame(sprite.Rotation.RotateVec(new Vector2(0f, -distance)), length), + } + }, + } + }; + } + + private Animation GetFadeAnimation(SpriteComponent sprite, float start, float end) + { + return new Animation + { + Length = TimeSpan.FromSeconds(end), + AnimationTracks = + { + new AnimationTrackComponentProperty() + { + ComponentType = typeof(SpriteComponent), + Property = nameof(SpriteComponent.Color), + KeyFrames = + { + new AnimationTrackProperty.KeyFrame(sprite.Color, start), + new AnimationTrackProperty.KeyFrame(sprite.Color.WithAlpha(0f), end) + } + } + } + }; + } + + /// + /// Get the sprite offset animation to use for mob lunges. + /// + private Animation GetLungeAnimation(Vector2 direction) + { + const float length = 0.1f; + + return new Animation + { + Length = TimeSpan.FromSeconds(length), + AnimationTracks = + { + new AnimationTrackComponentProperty() + { + ComponentType = typeof(SpriteComponent), + Property = nameof(SpriteComponent.Offset), + InterpolationMode = AnimationInterpolationMode.Linear, + KeyFrames = + { + new AnimationTrackProperty.KeyFrame(direction.Normalized * 0.15f, 0f), + new AnimationTrackProperty.KeyFrame(Vector2.Zero, length) + } + } + } + }; + } } diff --git a/Content.Client/Weapons/Melee/MeleeWeaponSystem.cs b/Content.Client/Weapons/Melee/MeleeWeaponSystem.cs index 98279528ac..eaca65a2a1 100644 --- a/Content.Client/Weapons/Melee/MeleeWeaponSystem.cs +++ b/Content.Client/Weapons/Melee/MeleeWeaponSystem.cs @@ -197,14 +197,13 @@ public sealed partial class MeleeWeaponSystem : SharedMeleeWeaponSystem target = screen.GetEntityUnderPosition(mousePos); } - EntityManager.RaisePredictiveEvent(new LightAttackEvent(target, weapon.Owner, coordinates)); - + RaisePredictiveEvent(new LightAttackEvent(target, weapon.Owner, coordinates)); return; } if (weapon.Attacking) { - EntityManager.RaisePredictiveEvent(new StopAttackEvent(weapon.Owner)); + RaisePredictiveEvent(new StopAttackEvent(weapon.Owner)); } } @@ -245,189 +244,4 @@ public sealed partial class MeleeWeaponSystem : SharedMeleeWeaponSystem if (Exists(ev.Entity)) DoLunge(ev.Entity, ev.Angle, ev.LocalPos, ev.Animation); } - - /// - /// Does all of the melee effects for a player that are predicted, i.e. character lunge and weapon animation. - /// - public override void DoLunge(EntityUid user, Angle angle, Vector2 localPos, string? animation) - { - if (!Timing.IsFirstTimePredicted) - return; - - var lunge = GetLungeAnimation(localPos); - - // Stop any existing lunges on the user. - _animation.Stop(user, MeleeLungeKey); - _animation.Play(user, lunge, MeleeLungeKey); - - // Clientside entity to spawn - if (animation != null) - { - var animationUid = Spawn(animation, new EntityCoordinates(user, Vector2.Zero)); - - if (localPos != Vector2.Zero && TryComp(animationUid, out var sprite)) - { - sprite[0].AutoAnimated = false; - - if (TryComp(animationUid, out var arcComponent)) - { - sprite.NoRotation = true; - sprite.Rotation = localPos.ToWorldAngle(); - var distance = Math.Clamp(localPos.Length / 2f, 0.2f, 1f); - - switch (arcComponent.Animation) - { - case WeaponArcAnimation.Slash: - _animation.Play(animationUid, GetSlashAnimation(sprite, angle), "melee-slash"); - break; - case WeaponArcAnimation.Thrust: - _animation.Play(animationUid, GetThrustAnimation(sprite, distance), "melee-thrust"); - break; - case WeaponArcAnimation.None: - sprite.Offset = localPos.Normalized * distance; - _animation.Play(animationUid, GetStaticAnimation(sprite), "melee-fade"); - break; - } - } - } - } - } - - private Animation GetSlashAnimation(SpriteComponent sprite, Angle arc) - { - var slashStart = 0.03f; - var slashEnd = 0.065f; - var length = slashEnd + 0.05f; - var startRotation = sprite.Rotation - arc / 2; - var endRotation = sprite.Rotation + arc / 2; - sprite.NoRotation = true; - - return new Animation() - { - Length = TimeSpan.FromSeconds(length), - AnimationTracks = - { - new AnimationTrackComponentProperty() - { - ComponentType = typeof(SpriteComponent), - Property = nameof(SpriteComponent.Rotation), - KeyFrames = - { - new AnimationTrackProperty.KeyFrame(startRotation, 0f), - new AnimationTrackProperty.KeyFrame(startRotation, slashStart), - new AnimationTrackProperty.KeyFrame(endRotation, slashEnd) - } - }, - new AnimationTrackComponentProperty() - { - ComponentType = typeof(SpriteComponent), - Property = nameof(SpriteComponent.Offset), - KeyFrames = - { - new AnimationTrackProperty.KeyFrame(startRotation.RotateVec(new Vector2(0f, -1f)), 0f), - new AnimationTrackProperty.KeyFrame(startRotation.RotateVec(new Vector2(0f, -1f)), slashStart), - new AnimationTrackProperty.KeyFrame(endRotation.RotateVec(new Vector2(0f, -1f)), slashEnd) - } - }, - new AnimationTrackComponentProperty() - { - ComponentType = typeof(SpriteComponent), - Property = nameof(SpriteComponent.Color), - KeyFrames = - { - new AnimationTrackProperty.KeyFrame(sprite.Color, slashEnd), - new AnimationTrackProperty.KeyFrame(sprite.Color.WithAlpha(0f), length), - } - } - } - }; - } - - private Animation GetThrustAnimation(SpriteComponent sprite, float distance) - { - var length = 0.15f; - var thrustEnd = 0.05f; - - return new Animation() - { - Length = TimeSpan.FromSeconds(length), - AnimationTracks = - { - new AnimationTrackComponentProperty() - { - ComponentType = typeof(SpriteComponent), - Property = nameof(SpriteComponent.Offset), - KeyFrames = - { - new AnimationTrackProperty.KeyFrame(sprite.Rotation.RotateVec(new Vector2(0f, -distance / 5f)), 0f), - new AnimationTrackProperty.KeyFrame(sprite.Rotation.RotateVec(new Vector2(0f, -distance)), thrustEnd), - new AnimationTrackProperty.KeyFrame(sprite.Rotation.RotateVec(new Vector2(0f, -distance)), length), - } - }, - new AnimationTrackComponentProperty() - { - ComponentType = typeof(SpriteComponent), - Property = nameof(SpriteComponent.Color), - KeyFrames = - { - new AnimationTrackProperty.KeyFrame(sprite.Color, thrustEnd), - new AnimationTrackProperty.KeyFrame(sprite.Color.WithAlpha(0f), length), - } - } - } - }; - } - - /// - /// Get the fadeout for static weapon arcs. - /// - private Animation GetStaticAnimation(SpriteComponent sprite) - { - var length = 0.15f; - - return new() - { - Length = TimeSpan.FromSeconds(length), - AnimationTracks = - { - new AnimationTrackComponentProperty() - { - ComponentType = typeof(SpriteComponent), - Property = nameof(SpriteComponent.Color), - KeyFrames = - { - new AnimationTrackProperty.KeyFrame(sprite.Color, 0f), - new AnimationTrackProperty.KeyFrame(sprite.Color.WithAlpha(0f), length) - } - } - } - }; - } - - /// - /// Get the sprite offset animation to use for mob lunges. - /// - private Animation GetLungeAnimation(Vector2 direction) - { - var length = 0.1f; - - return new Animation - { - Length = TimeSpan.FromSeconds(length), - AnimationTracks = - { - new AnimationTrackComponentProperty() - { - ComponentType = typeof(SpriteComponent), - Property = nameof(SpriteComponent.Offset), - InterpolationMode = AnimationInterpolationMode.Linear, - KeyFrames = - { - new AnimationTrackProperty.KeyFrame(direction.Normalized * 0.15f, 0f), - new AnimationTrackProperty.KeyFrame(Vector2.Zero, length) - } - } - } - }; - } } diff --git a/Content.Server/Zombies/ZombifyOnDeathSystem.cs b/Content.Server/Zombies/ZombifyOnDeathSystem.cs index 3548b0506c..d0d38136e5 100644 --- a/Content.Server/Zombies/ZombifyOnDeathSystem.cs +++ b/Content.Server/Zombies/ZombifyOnDeathSystem.cs @@ -119,7 +119,8 @@ namespace Content.Server.Zombies //This is the actual damage of the zombie. We assign the visual appearance //and range here because of stuff we'll find out later var melee = EnsureComp(target); - melee.Animation = zombiecomp.AttackAnimation; + melee.ClickAnimation = zombiecomp.AttackAnimation; + melee.WideAnimation = zombiecomp.AttackAnimation; melee.Range = 0.75f; //We have specific stuff for humanoid zombies because they matter more diff --git a/Content.Shared/Weapons/Melee/MeleeWeaponComponent.cs b/Content.Shared/Weapons/Melee/MeleeWeaponComponent.cs index 5a4ce906bd..ed52667ea6 100644 --- a/Content.Shared/Weapons/Melee/MeleeWeaponComponent.cs +++ b/Content.Shared/Weapons/Melee/MeleeWeaponComponent.cs @@ -97,7 +97,10 @@ public sealed class MeleeWeaponComponent : Component public Angle Angle = Angle.FromDegrees(60); [ViewVariables(VVAccess.ReadWrite), DataField("animation", customTypeSerializer: typeof(PrototypeIdSerializer))] - public string Animation = "WeaponArcSlash"; + public string ClickAnimation = "WeaponArcPunch"; + + [ViewVariables(VVAccess.ReadWrite), DataField("wideAnimation", customTypeSerializer: typeof(PrototypeIdSerializer))] + public string WideAnimation = "WeaponArcSlash"; // Sounds diff --git a/Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs b/Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs index bea4ea0fed..e9ddca6638 100644 --- a/Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs +++ b/Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs @@ -8,7 +8,6 @@ using Content.Shared.Inventory; using Content.Shared.Inventory.Events; using Content.Shared.Popups; using Content.Shared.Weapons.Melee.Events; -using JetBrains.Annotations; using Robust.Shared.GameStates; using Robust.Shared.Map; using Robust.Shared.Players; @@ -272,19 +271,23 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem weapon.NextAttack += TimeSpan.FromSeconds(1f / weapon.AttackRate); // Attack confirmed + string animation; switch (attack) { case LightAttackEvent light: DoLightAttack(user, light, weapon, session); + animation = weapon.ClickAnimation; break; case DisarmAttackEvent disarm: if (!DoDisarm(user, disarm, weapon, session)) return; + animation = weapon.ClickAnimation; break; case HeavyAttackEvent heavy: DoHeavyAttack(user, heavy, weapon, session); + animation = weapon.WideAnimation; break; default: throw new NotImplementedException(); @@ -293,7 +296,7 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem // Play a sound to give instant feedback; same with playing the animations Audio.PlayPredicted(weapon.SwingSound, weapon.Owner, user); - DoLungeAnimation(user, weapon.Angle, attack.Coordinates.ToMap(EntityManager), weapon.Animation); + DoLungeAnimation(user, weapon.Angle, attack.Coordinates.ToMap(EntityManager), weapon.Range, animation); weapon.Attacking = true; Dirty(weapon); } @@ -351,7 +354,7 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem return true; } - private void DoLungeAnimation(EntityUid user, Angle angle, MapCoordinates coordinates, string? animation) + private void DoLungeAnimation(EntityUid user, Angle angle, MapCoordinates coordinates, float length, string? animation) { // TODO: Assert that offset eyes are still okay. if (!TryComp(user, out var userXform)) @@ -364,6 +367,14 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem return; localPos = userXform.LocalRotation.RotateVec(localPos); + + // We'll play the effect just short visually so it doesn't look like we should be hitting but actually aren't. + const float BufferLength = 0.2f; + var visualLength = length - BufferLength; + + if (localPos.Length > visualLength) + localPos = localPos.Normalized * visualLength; + DoLunge(user, angle, localPos, animation); } diff --git a/Resources/Prototypes/Entities/Effects/weapon_arc.yml b/Resources/Prototypes/Entities/Effects/weapon_arc.yml index 254377142b..37b180b66f 100644 --- a/Resources/Prototypes/Entities/Effects/weapon_arc.yml +++ b/Resources/Prototypes/Entities/Effects/weapon_arc.yml @@ -11,8 +11,22 @@ - type: EffectVisuals - type: WeaponArcVisuals -# TODO: Camera recoil (try it as a shake, i.e. zoom out and then rotate slightly maybe) -# See https://github.com/gasgiant/Camera-Shake +- type: entity + # Plays the state animation then disappears with no fade or swing + id: WeaponArcAnimated + noSpawn: true + components: + - type: Sprite + sprite: Effects/arcs.rsi + state: disarm + netsync: false + drawdepth: Effects + - type: EffectVisuals + - type: WeaponArcVisuals + fadeOut: false + + +# Uses TimedDespawn instead of EffectVisuals because auto animation is easier but doesn't raise an animation complete event. - type: entity id: WeaponArcThrust @@ -22,8 +36,6 @@ - type: WeaponArcVisuals animation: Thrust - # TODO: Hold for 0.1, thrust out n distance, then fade out - - type: entity id: WeaponArcSlash parent: WeaponArcStatic @@ -38,24 +50,36 @@ parent: WeaponArcStatic noSpawn: true components: + - type: WeaponArcVisuals + fadeOut: false - type: Sprite state: bite + - type: TimedDespawn + lifetime: 0.399 - type: entity id: WeaponArcClaw parent: WeaponArcStatic noSpawn: true components: + - type: WeaponArcVisuals + fadeOut: false - type: Sprite state: claw + - type: TimedDespawn + lifetime: 0.399 - type: entity id: WeaponArcDisarm - parent: WeaponArcStatic + parent: WeaponArcAnimated noSpawn: true components: + - type: WeaponArcVisuals + fadeOut: false - type: Sprite state: disarm + - type: TimedDespawn + lifetime: 0.299 - type: entity id: WeaponArcFist @@ -64,3 +88,39 @@ components: - type: Sprite state: fist + +- type: entity + id: WeaponArcPunch + parent: WeaponArcStatic + noSpawn: true + components: + - type: WeaponArcVisuals + fadeOut: false + - type: Sprite + state: punch + - type: TimedDespawn + lifetime: 0.499 + +- type: entity + id: WeaponArcKick + parent: WeaponArcStatic + noSpawn: true + components: + - type: WeaponArcVisuals + fadeOut: false + - type: Sprite + state: kick + - type: TimedDespawn + lifetime: 0.299 + +- type: entity + id: WeaponArcSmash + parent: WeaponArcStatic + noSpawn: true + components: + - type: WeaponArcVisuals + fadeOut: false + - type: Sprite + state: smash + - type: TimedDespawn + lifetime: 0.299 diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml b/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml index 8fe666334a..0946d11c6e 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml @@ -72,7 +72,7 @@ angle: 0 soundHit: collection: AlienClaw - animation: WeaponArcClaw + animation: WeaponArcBite damage: groups: Brute: 12 diff --git a/Resources/Prototypes/Entities/Mobs/Species/reptilian.yml b/Resources/Prototypes/Entities/Mobs/Species/reptilian.yml index f282651851..ceae93a83f 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/reptilian.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/reptilian.yml @@ -27,7 +27,7 @@ soundHit: path: /Audio/Weapons/pierce.ogg angle: 30 - animation: WeaponArcFist + animation: WeaponArcPunch damage: types: Piercing: 5 diff --git a/Resources/Textures/Effects/arcs.rsi/fist.png b/Resources/Textures/Effects/arcs.rsi/fist.png index 528c2b8d2a4f1ad8e2d4158652423e9437ce5405..2ec1fae39aaf17e6d9e30e8a1a3d3191f66f2295 100644 GIT binary patch literal 5103 zcmeHKdsGu=77tYc0REQe{G;zbWz>|Yb$&Ltm#5Dg7=N{AoMYq7yNs(hqZdZyCW!VdS`b`* zaK>I{bt%<<_v9_{za38JKepS_@x#1I|E<3$);$tz$u9T0eKTz5k+%NgtdC<4wft-E zv5g+|{hO1cc9d`OYPoNJS+YC3=7jG@pE!EHQ{on$W>ZYF7;5dg>X5-P9orJjxSl+B%%r3+h5K8}n>wzX%yh{+LeTxOzjjXUEz`d)qhX*w(r?wZHRO=v3EjvxHCF_SL!Z`g4>^j-LB6 zQL*^S{vS?{o3gP?oj;cm?ln^`ziqI1^&k`ygRnHQ*<2WXxFm0<~1LV zWZ15`1LX%#Pv6YA9NsHwD_rNV{o`n7T*v_ZMzzlhA(InfBb$` zS@0rHvA@_<^)~P5lk&@FhZq$_snR1(Lu~4LXAQqp7jk>^H=e7?OV@p@-#R$K+|}`L z>*fkC8qH=cDUn3TB$DTQ4(znTRa?ZNweNYGWWI-SPJYbKBF}yX9fOoL=OiCS>087+!vN*VN_2?O2x85l2P;vyu}((20BfEv*gRV{#81$=9yVmc&;C{gCX{*W2B5|F&QGvn<;7GgRd=6m|u}aexFv zFk1e;2-j(tm`bN4m}ZS03`rU-AkeJG@MOXWDTxG9E2j6Is-Z)qN=#qMle6V|36V&K zW>Q2{X1D^+OvZ&Odf+^V0J8`HXb2+)nKda|gUBqVTX99;-XdnvA*+fpSxk?XM?exC zML=98m&t~uW-=Y2&vSqRD3w|i85BGM0iMM4M59qJVzEpn6Vv3!)KLj6j!-CMu@M%6 zz(4~wWN3|;8P*znED*yOL4*OPNWGENX(0nVCDR&=m=-52Pyn1s0vwe9MFeam7giG}8|DhRTv&;y*svNE@=%P6urao96vQHm z1YL=xjE>3zr2hRO>CP*QzOAusjToHy3J27DCgv#m&*bOh?iI_Hl z06To7rJl>lKPeZ&CvX%~5-=A>LB;u?a6%kKU=>G*`5^>~AnIX`U!WUwYNH9Gi1`U1 zQ;-d4A!|0!yCY=!z8Z}wk+3ku2K$@ChB*QShc80dB9K1ouTM{f^D$I~D1mDPhq)>N z1}jw@4vY%a2uko#g01Gi8t(s=9>P;_P!S>&vC-%01+Xk5>v=H)SpQY-VTDmM4$v|z z1G5v1V%CdUJi?cyL;k{Rq|N?94*>d&lh@++4P9^OdMyTCOZiQAy`k&17KX@i^R`az@-T=;dw#u-OAli`SRea{)HXs?V4~;R|MRe``ybm1CLQay(9;GlNhhO7>r;NF>VetImR4&=t zTHvy~yGLpY>`S1P-9CP6e4bnUY?dFbIIt4$c3L^UxeVLh?qoM7tDK8BA*+st)*^l` z!Sv6&hR*Jea8pi}m19+zNfYKQjj;FryGuX?W0)1@UYC1C~XI A1ONa4 delta 161 zcmV;S0ABy^C%gfWBYyw^b5ch_0Itp)=>Px#c1c7*R9J=WluHhPFbG5k3Dpjs%Ph$taqK_uH`A&CUU1R-J+1Qf+>NG1@-MlvB03R*u`sv;_g zh)NLlo_op@{Tu1!n(1BbJ0OpA_17r=UgTBDN6rA7#d zK_4$;Puqp#W(IJ@F;gT{{EF%{|Hy>m58L@Jp0lkm1`*% z1yjDVD^YlqpRsK1>;LnM#74gjS6*a%zRNW8zjts?EUvMRPL+xd9E!Mo^hy0(^!&b< z=Vimt%J zV&6)e>MZBHC6n&gP5osoK1X`hCo87og!7A*ysQ=7R^|mV(Cb#EEqc zTM;*xHbfc!V%2m8TbM_-bD1a^AN%~$EV^QO!QNKqJu@!4+&S(^3tG`$8RS}P9MxRx zy{M*6F0tG&ErHoygx|XFCe=3Z+Mm#}(RSA2R%4j;Nu7P9Wk#VDw0<1h`t+U2B{xmD zBK%>?n(Z?!%4dJ!)3PJq$#GLOJ{mV^#=7awMGj{zz0z~=id`gT=8nWF&C2{qJ?w@e zK@B;)>C5o1WAEA=Iq<+Gx!lS&aGe9+&Q0SJS)A>8Ilp*P)>PpDdA)PgoC1xn?{Cb+ zn@$~jM$Gxu_tx#sq$7uue*B){ci>@M;>pr7(wUZX z;q}j(#q*b?_adb(;j%xIW4j6#E*o58o!92z{}+DBHU|12a^9`f#eLVDDw>bE%`a`B z?W?$0(BinZhizLn!KyZHU6BE@JyF!kh}-o;Lex);I7qz6%KFO60v%LWRJ3AqZ4rM) z{odlT_H0yp^1zx^a9sbe``;cI*{Yl_aXCveO0C9&UJ=;>WzT4C8} ztadFwrgPrAi3gubUiNB0M>UOQ?4IeAWzkCOUpKI9ZZGdM#n*m^@2*_c`P{|VG|yB^^7vg50Yo z-FRiyP|u&(OHwa?*zM$R!|WZia(P$xwI74Gp0Alu^@Gv9eg3&~t*=shx}oud*SEK) zyFvnQzD+r@YII(DqwkBaq;IxQGgW37r#crl_5OC_Yg@zPyOZm0^bx8Em6a*%YF%%3 z^T}-Y_E&3@K951q2)7ys`m;LrJ$m_|@QN6zzVH(1ymR%n)f@Y)Bd@(Y(96ehIE!^M zK0iXp=f7=j(9ne$IlQpineKHfqQXm~1#Q8XJo%3$!O@o@D`KteOOG~H{O4-vil!1z zVM>bgQv0!)ehVipE!64ME>;&mcL??N4jDhKf3@t}kS#X#uMGOW6w_FTf|#;`45u{} zi)V8k$7`R*G{4EhZRq6VBRB6!gDUgaKYmuVapQ44ZU3*Cy^XXY=egDf!F^8I#Bu%B z{S9TM+PmIkD^Ha;9B|5>hz8#eJ$7A@*|k&?+qnmxvUk7tSf_7FC+DT-=Z?uW6tIiS z-F~${$C_SvhW*_w?&J2I)zsLgh0xMNoGh!n?E*WZYo^>2uTv#CY9^JL3ug(}$yk_}x>;5$tJ*f+edvv??CezcWeS7Au4odz~Q?jeEZVnj1t}-xcV?}ehVwHjnOH}a) z*`QE^QH;X{1R2z@I2l0+@koM9$s_iiuO<>?5*~38Q$!W1`ADKHEKP$%rOk^LrzMLy z5@JxGU4VfL02Bxc6ATKuQp+{)h-O?axW>d3BEhVJCi94~q6h+Cr9lV`GJ{Mdg&1V1 z5HZk>5TKDrxeEldhatcnkC=#}YA%JM*Xzl8I$5PjpwKuR4uuL)AczDsNZM6O6gH5Q z+9?>s5QYHJiZwDdDpM&57$zLA(xE&e5v&v5#-~t=MDO60+F=#|9~1+urqIY#ib6pd zX`w|!QUSui=jRxOXlm&=ZrPGL!kW@s8P8kUy5x=un>ojt6IubDjks}JAss&zY zAGiz^iXz@wU=$?C6l${-Ao~MNR3?2-)(5d+E9P`Y1_I3A;eMd~Hg~f!&=QHb0+m>Y zg(np7h?sw_L?xCK2C5R4kBka~ zk^m@3%Aj*tOa{rHM#rG2EE0zugtaJ!5LrKKk*(!|!2HPo9zzGONtxPa4 zV1#pnBZNF6M5ex%M95)O3JgFG$dnS5Ui*G2TBbmvP#EKr#$qu!Yz7r#Fj-8dKj*#D zLPVnlwTPk8sN~^%u#Mq@bO5z5R;d8MYyq<2@-+yIsx;9mm7GV!f+Ap+<^m@K3~fu8 zObawtVKqNo^-)O5(A`iI$Ytgw0>NCkTv$BhL<^@P60;*rHlT#e z*${k&$(;6KH2OpYV~Ps;n?@zk{G(|sE=1)*3?gMXSqj$EZ?g@c{Ed$Qv%-iW0PKcj zV7P!;MR_-@hWWxM{1=bmefSqW0O(InK8oK@bbX@hqZs%oj3RoDNG zF1z=SJBSke0@8zrnI_|`4DcXi9UmSlz`emPx#gh@m}R9J=W*Gme(APfZ1*qi0#bF}GJ zXl=;{wI#C@)Mrppkw5?t03%}@k$GwFltW+WTyZHC_pNpHT!M+uL=~P0LuXY<^VkBJr&?tX(%dS6jfadn3De8*Z&PY d@4?CzIRVaRFjWE37e@d9002ovPDHLkV1g*CMOXj; diff --git a/Resources/Textures/Effects/arcs.rsi/spear.png b/Resources/Textures/Effects/arcs.rsi/spear.png index 80322b475144935739f4c20152e41b5f6f6334ce..aa3a070b774e731b6386643bb9622b1b88b18407 100644 GIT binary patch literal 4936 zcmeHKYg7|w6As?XO)QFvwhaM6EV*Ad5<&?)C%%!ATMp90|UO_&I6>xEj;adI*G}m1)tjrbyUKG+=rSnurihsRo3ISWpdt zV7c{hha&6#RL77PiV&*ZT8ZD9s@$v7t!+14Z(hzWVHA)`PTvZEy%lNJA(vS{FMHwq zcr?Y^tyR1wTXp0_dhbq$Y04Y&_N}G)^{L$ft9s>Uppg^iI~UEBu08dv@sr|}^0C*x z|J)P(E3opznv6awcSCQDY#dtIwc+>9nr`CGWQBB~w>bqKma}=FCmvUO2ypQ4>`hwDOpOvuKkwmFXB$cl+ig zncbii5t-|sLr~EW+wQe}+Vy63#PV|QkpXX)PV@B;M0ibie2#&eEd zLsAq&CUr2*{vHtb%!?W;o^s-zcoe;m=&|Pg`91XW6QZt(4|?tRPU2C$ZA)S9I$rnt zUg7U=_M2i%;jzRUAfy)B@mcopJcm0J+SDQ}x#>pSqSCL!FCAt1<+smGI9TLOK7Qro zsSS_6`Z2Gf=k~=Jld`U~AOE(RtUc6rz|rsMFLjSR*YmDy+8hdf{V=wxbNv2B*_S@P z>{&kSo_(ehM1kxx#!wn3FVNq2h+jgCCZ=Xd40fST^F!Gv$IYUh$&pH;mD@tI#>HF4 z$T58qvvy%p>)k2wW1BPYcb*g!T>pc4&HLa{2lm(3;XXKh>-Uj8D)XG2e5da|ecojq z&2yLkv|{h%1GgGfPFbtS^N!p1-dUhX|4BHe`tRrIF{MrNNzcF7KmX%t^|IOLK4`f- zCvOvSXVzoii#MnIs#UbR_QaY}FfW(s|} zaGNS2^+?kVV^O2|(XVojkl?@9CEy6Z%$Y#2TZM|n5fZWZWg~<3&0V@#5Oywba`nQf znMKjTw?yYX#1GV>=<~BnVuv{t9cU<7SzENQq0mE;lr(mpgL8&o%m?#wlameOhEDe&mnOEatAHK4+I4`M#W`uFFdlGx%f18=uM>U#ugn2E{ac?anmD z;q1>JJgHc-rrb==Ys=`Yr|%g%YlJ1Z1==^st$Rdw-M%8@9UtezhYDTtT{liLiCRL- zE^0Gc=V7t;cEX;!@_d|KGmAdVT9UopX}cwdSFqo`&EX{HKA_0W zN%R@sy1c9Un6O~^vhfM*#J~Q!d2FuF^1g{Du2ek#)HVB=S7l=pDfH|;jv=YJ>Av6| zHgZD7l__G(iz?k4JM2!qb=U8KR<}{5N0&V4Xzj79iJWthd$!7h7E{oXyu;kr*W25* zQ?^XF*c&U{R$mH z*QqlkBB~aU=CGwSsX>e+pkZkk5|t*4R;4XgL28mPz~0}&2LM{c1QRXVM4gdu5s+-S zd~lD8sU)IJ#k5#Jij_tX#d-`OvM4MHjT~Y@Qy8QGd!j$4*6<^PXAD4qCjlwJWHRun zRI}MkF*7N8ES^e-Ac#t1P#Fv|&>$OAbtc$C))_r{>x=^|06wS|*g&OIXjHA1I@rQ!3P}MV0|EW3g)tiZK2akPqdpl^ zAt5P<&g3~5LalmbZ%D=xZRx00R3s760#zgMN`J#;s6-m^$^xe#9@QFbR)Fj`G)<`H zHCb=OhOgMt85{^Oe}(&o_RHLD%0NphEOG$>9Y7QAv zav_$6&El#wkPS+$;?K}yS{Q66s)gebszDcTTfhnDiy|Zf5`#i}Es02kO&VYTdH~g_ z^=9MirD#-(M44cmPdbOghImW}qH$Rq4tn9Sy| z)EbDV)X*8KH_?rHjmZpSh$tRp3bFwuWXpy)b%0FYH}%a42+kA@^f#SGrgNj|96lZ5 zvuPyiK(bW4r(b64PyHW0{A~(@h5)eZmx18|W)=0-uo~bCr|>^K2KM1U^Z=mWIe9C7 z-_iAsuD4>~t(4zY*E_o2ih;LMepg-pH@fU!zwRJ9@DIogUS5Cg*EB$RWjy)st`oNr)`CEGX`?e*orTWN82Z delta 179 zcmV;k08IbLCe8tnBYyw^b5ch_0Itp)=>Px#h)G02R9J=WmC*_S5fDXZw*4@^eEiw$ z6M3+7wHxZ5QkudE7Znh9Fa4B~i-?2}G}lzMG3uQQ75EwoP=EqAIw0q~eh=z8%JLnc z00ou{z}Ydg@&(tC%wKbi5t?&a;Emq}dJ^d3(*iAtnZ>tY`79Bf7sJdJcfibi6w49t h5x9G4y&56p7#=?qL6dYAKGXmJ002ovPDHLkV1mkkOdbFL