using System.Numerics; using Content.Client.Animations; using Content.Client.Weapons.Melee.Components; using Content.Shared.Weapons.Melee; using Robust.Client.Animations; using Robust.Client.GameObjects; using Robust.Shared.Animations; using Robust.Shared.Map; namespace Content.Client.Weapons.Melee; public sealed partial class MeleeWeaponSystem { private const string FadeAnimationKey = "melee-fade"; private const string SlashAnimationKey = "melee-slash"; private const string ThrustAnimationKey = "melee-thrust"; /// /// 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, EntityUid weapon, Angle angle, Vector2 localPos, string? animation, bool predicted = true) { if (!Timing.IsFirstTimePredicted) return; var lunge = GetLungeAnimation(localPos); // Stop any existing lunges on the user. _animation.Stop(user, MeleeLungeKey); _animation.Play(user, lunge, MeleeLungeKey); if (localPos == Vector2.Zero || animation == null) return; if (!_xformQuery.TryGetComponent(user, out var userXform) || userXform.MapID == MapId.Nullspace) return; var animationUid = Spawn(animation, userXform.Coordinates); if (!TryComp(animationUid, out var sprite) || !TryComp(animationUid, out var arcComponent)) { return; } var spriteRotation = Angle.Zero; if (arcComponent.Animation != WeaponArcAnimation.None && TryComp(weapon, out MeleeWeaponComponent? meleeWeaponComponent)) { if (user != weapon && TryComp(weapon, out SpriteComponent? weaponSpriteComponent)) _sprite.CopySprite((weapon, weaponSpriteComponent), (animationUid, sprite)); spriteRotation = meleeWeaponComponent.WideAnimationRotation; if (meleeWeaponComponent.SwingLeft) angle *= -1; } _sprite.SetRotation((animationUid, sprite), localPos.ToWorldAngle()); var distance = Math.Clamp(localPos.Length() / 2f, 0.2f, 1f); var xform = _xformQuery.GetComponent(animationUid); TrackUserComponent track; switch (arcComponent.Animation) { case WeaponArcAnimation.Slash: track = EnsureComp(animationUid); track.User = user; _animation.Play(animationUid, GetSlashAnimation(sprite, angle, spriteRotation), SlashAnimationKey); if (arcComponent.Fadeout) _animation.Play(animationUid, GetFadeAnimation(sprite, 0.065f, 0.065f + 0.05f), FadeAnimationKey); break; case WeaponArcAnimation.Thrust: track = EnsureComp(animationUid); track.User = user; _animation.Play(animationUid, GetThrustAnimation((animationUid, sprite), distance, spriteRotation), ThrustAnimationKey); if (arcComponent.Fadeout) _animation.Play(animationUid, GetFadeAnimation(sprite, 0.05f, 0.15f), FadeAnimationKey); break; case WeaponArcAnimation.None: var (mapPos, mapRot) = TransformSystem.GetWorldPositionRotation(userXform); var worldPos = mapPos + (mapRot - userXform.LocalRotation).RotateVec(localPos); var newLocalPos = Vector2.Transform(worldPos, TransformSystem.GetInvWorldMatrix(xform.ParentUid)); TransformSystem.SetLocalPositionNoLerp(animationUid, newLocalPos, xform); if (arcComponent.Fadeout) _animation.Play(animationUid, GetFadeAnimation(sprite, 0f, 0.15f), FadeAnimationKey); break; } } private Animation GetSlashAnimation(SpriteComponent sprite, Angle arc, Angle spriteRotation) { 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; var startRotationOffset = startRotation.RotateVec(new Vector2(0f, -1f)); var endRotationOffset = endRotation.RotateVec(new Vector2(0f, -1f)); startRotation += spriteRotation; endRotation += spriteRotation; 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(startRotationOffset, 0f), new AnimationTrackProperty.KeyFrame(startRotationOffset, slashStart), new AnimationTrackProperty.KeyFrame(endRotationOffset, slashEnd) } }, } }; } private Animation GetThrustAnimation(Entity sprite, float distance, Angle spriteRotation) { const float thrustEnd = 0.05f; const float length = 0.15f; var startOffset = sprite.Comp.Rotation.RotateVec(new Vector2(0f, -distance / 5f)); var endOffset = sprite.Comp.Rotation.RotateVec(new Vector2(0f, -distance)); _sprite.SetRotation(sprite.AsNullable(), sprite.Comp.Rotation + spriteRotation); return new Animation() { Length = TimeSpan.FromSeconds(length), AnimationTracks = { new AnimationTrackComponentProperty() { ComponentType = typeof(SpriteComponent), Property = nameof(SpriteComponent.Offset), KeyFrames = { new AnimationTrackProperty.KeyFrame(startOffset, 0f), new AnimationTrackProperty.KeyFrame(endOffset, thrustEnd), new AnimationTrackProperty.KeyFrame(endOffset, 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) } } } }; } /// /// Updates the effect positions to follow the user /// private void UpdateEffects() { var query = EntityQueryEnumerator(); while (query.MoveNext(out var uid, out var arcComponent, out var xform)) { if (arcComponent.User == null || EntityManager.Deleted(arcComponent.User)) continue; Vector2 targetPos = TransformSystem.GetWorldPosition(arcComponent.User.Value); if (arcComponent.Offset != Vector2.Zero) { var entRotation = TransformSystem.GetWorldRotation(xform); targetPos += entRotation.RotateVec(arcComponent.Offset); } TransformSystem.SetWorldPosition(uid, targetPos); } } }