diff --git a/Content.Client/Weapons/Melee/MeleeWeaponSystem.Effects.cs b/Content.Client/Weapons/Melee/MeleeWeaponSystem.Effects.cs index 7dce555817..79a6529a4d 100644 --- a/Content.Client/Weapons/Melee/MeleeWeaponSystem.Effects.cs +++ b/Content.Client/Weapons/Melee/MeleeWeaponSystem.Effects.cs @@ -30,21 +30,22 @@ public sealed partial class MeleeWeaponSystem if (localPos == Vector2.Zero || animation == null) return; - if (!TryComp(user, out var userXform) || userXform.MapID == MapId.Nullspace) + 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; + } sprite.NoRotation = true; sprite.Rotation = localPos.ToWorldAngle(); var distance = Math.Clamp(localPos.Length() / 2f, 0.2f, 1f); - var xformQuery = GetEntityQuery(); - var xform = xformQuery.GetComponent(animationUid); + var xform = _xformQuery.GetComponent(animationUid); switch (arcComponent.Animation) { @@ -61,10 +62,10 @@ public sealed partial class MeleeWeaponSystem _animation.Play(animationUid, GetFadeAnimation(sprite, 0.05f, 0.15f), FadeAnimationKey); break; case WeaponArcAnimation.None: - var (mapPos, mapRot) = TransformSystem.GetWorldPositionRotation(userXform, xformQuery); + var (mapPos, mapRot) = TransformSystem.GetWorldPositionRotation(userXform); TransformSystem.AttachToGridOrMap(animationUid, xform); var worldPos = mapPos + (mapRot - userXform.LocalRotation).RotateVec(localPos); - var newLocalPos = TransformSystem.GetInvWorldMatrix(xform.ParentUid, xformQuery).Transform(worldPos); + var newLocalPos = TransformSystem.GetInvWorldMatrix(xform.ParentUid).Transform(worldPos); TransformSystem.SetLocalPositionNoLerp(xform, newLocalPos); if (arcComponent.Fadeout) _animation.Play(animationUid, GetFadeAnimation(sprite, 0f, 0.15f), FadeAnimationKey); diff --git a/Content.Client/Weapons/Melee/MeleeWeaponSystem.cs b/Content.Client/Weapons/Melee/MeleeWeaponSystem.cs index d77537c051..48ab2fa5cf 100644 --- a/Content.Client/Weapons/Melee/MeleeWeaponSystem.cs +++ b/Content.Client/Weapons/Melee/MeleeWeaponSystem.cs @@ -26,29 +26,23 @@ public sealed partial class MeleeWeaponSystem : SharedMeleeWeaponSystem [Dependency] private readonly IEyeManager _eyeManager = default!; [Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly IInputManager _inputManager = default!; - [Dependency] private readonly IOverlayManager _overlayManager = default!; [Dependency] private readonly IPlayerManager _player = default!; - [Dependency] private readonly IPrototypeManager _protoManager = default!; [Dependency] private readonly IStateManager _stateManager = default!; [Dependency] private readonly AnimationPlayerSystem _animation = default!; [Dependency] private readonly InputSystem _inputSystem = default!; + private EntityQuery _xformQuery; + private const string MeleeLungeKey = "melee-lunge"; public override void Initialize() { base.Initialize(); - _overlayManager.AddOverlay(new MeleeWindupOverlay(EntityManager, _timing, _player, _protoManager)); + _xformQuery = GetEntityQuery(); SubscribeNetworkEvent(OnMeleeLunge); UpdatesOutsidePrediction = true; } - public override void Shutdown() - { - base.Shutdown(); - _overlayManager.RemoveOverlay(); - } - public override void Update(float frameTime) { base.Update(frameTime); @@ -69,56 +63,63 @@ public sealed partial class MeleeWeaponSystem : SharedMeleeWeaponSystem if (!CombatMode.IsInCombatMode(entity) || !Blocker.CanAttack(entity)) { weapon.Attacking = false; - if (weapon.WindUpStart != null) - { - EntityManager.RaisePredictiveEvent(new StopHeavyAttackEvent(weaponUid)); - } + return; + } + var useDown = _inputSystem.CmdStates.GetState(EngineKeyFunctions.Use); + var altDown = _inputSystem.CmdStates.GetState(EngineKeyFunctions.UseSecondary); + + if (useDown != BoundKeyState.Down && altDown != BoundKeyState.Down) + { + if (weapon.Attacking) + { + RaisePredictiveEvent(new StopAttackEvent(weaponUid)); + } + } + + if (weapon.Attacking || weapon.NextAttack > Timing.CurTime) + { return; } // TODO using targeted actions while combat mode is enabled should NOT trigger attacks. - var useDown = _inputSystem.CmdStates.GetState(EngineKeyFunctions.Use); - var altDown = _inputSystem.CmdStates.GetState(EngineKeyFunctions.UseSecondary); - var currentTime = Timing.CurTime; + // TODO: Need to make alt-fire melee its own component I guess? + // Melee and guns share a lot in the middle but share virtually nothing at the start and end so + // it's kinda tricky. + // I think as long as we make secondaries their own component it's probably fine + // as long as guncomp has an alt-use key then it shouldn't be too much of a PITA to deal with. + if (HasComp(weaponUid)) + { + return; + } + + var mousePos = _eyeManager.ScreenToMap(_inputManager.MouseScreenPosition); + + if (mousePos.MapId == MapId.Nullspace) + { + return; + } + + EntityCoordinates coordinates; + + if (MapManager.TryFindGridAt(mousePos, out var gridUid, out _)) + { + coordinates = EntityCoordinates.FromMap(gridUid, mousePos, TransformSystem, EntityManager); + } + else + { + coordinates = EntityCoordinates.FromMap(MapManager.GetMapEntityId(mousePos.MapId), mousePos, TransformSystem, EntityManager); + } // Heavy attack. if (altDown == BoundKeyState.Down) { - // TODO: Need to make alt-fire melee its own component I guess? - // Melee and guns share a lot in the middle but share virtually nothing at the start and end so - // it's kinda tricky. - // I think as long as we make secondaries their own component it's probably fine - // as long as guncomp has an alt-use key then it shouldn't be too much of a PITA to deal with. - if (HasComp(weaponUid)) - { - return; - } - - // We did the click to end the attack but haven't pulled the key up. - if (weapon.Attacking) - { - return; - } - // If it's an unarmed attack then do a disarm if (weapon.AltDisarm && weaponUid == entity) { EntityUid? target = null; - var mousePos = _eyeManager.ScreenToMap(_inputManager.MouseScreenPosition); - EntityCoordinates coordinates; - - if (MapManager.TryFindGridAt(mousePos, out var gridUid, out _)) - { - coordinates = EntityCoordinates.FromMap(gridUid, mousePos, TransformSystem, EntityManager); - } - else - { - coordinates = EntityCoordinates.FromMap(MapManager.GetMapEntityId(mousePos.MapId), mousePos, TransformSystem, EntityManager); - } - if (_stateManager.CurrentState is GameplayStateBase screen) { target = screen.GetClickedEntity(mousePos); @@ -128,52 +129,13 @@ public sealed partial class MeleeWeaponSystem : SharedMeleeWeaponSystem return; } - // Otherwise do heavy attack. - - // Start a windup - if (weapon.WindUpStart == null) - { - EntityManager.RaisePredictiveEvent(new StartHeavyAttackEvent(weaponUid)); - weapon.WindUpStart = currentTime; - } - - // Try to do a heavy attack. - if (useDown == BoundKeyState.Down) - { - var mousePos = _eyeManager.ScreenToMap(_inputManager.MouseScreenPosition); - EntityCoordinates coordinates; - - // Bro why would I want a ternary here - // ReSharper disable once ConvertIfStatementToConditionalTernaryExpression - if (MapManager.TryFindGridAt(mousePos, out var gridUid, out _)) - { - coordinates = EntityCoordinates.FromMap(gridUid, mousePos, TransformSystem, EntityManager); - } - else - { - coordinates = EntityCoordinates.FromMap(MapManager.GetMapEntityId(mousePos.MapId), mousePos, TransformSystem, EntityManager); - } - - ClientHeavyAttack(entity, coordinates, weaponUid, weapon); - } - + ClientHeavyAttack(entity, coordinates, weaponUid, weapon); return; } - if (weapon.WindUpStart != null) - { - EntityManager.RaisePredictiveEvent(new StopHeavyAttackEvent(weaponUid)); - } - // Light attack if (useDown == BoundKeyState.Down) { - if (weapon.Attacking || weapon.NextAttack > Timing.CurTime) - { - return; - } - - var mousePos = _eyeManager.ScreenToMap(_inputManager.MouseScreenPosition); var attackerPos = Transform(entity).MapPosition; if (mousePos.MapId != attackerPos.MapId || @@ -182,34 +144,14 @@ public sealed partial class MeleeWeaponSystem : SharedMeleeWeaponSystem return; } - EntityCoordinates coordinates; - - // Bro why would I want a ternary here - // ReSharper disable once ConvertIfStatementToConditionalTernaryExpression - if (MapManager.TryFindGridAt(mousePos, out var gridUid, out _)) - { - coordinates = EntityCoordinates.FromMap(gridUid, mousePos, TransformSystem, EntityManager); - } - else - { - coordinates = EntityCoordinates.FromMap(MapManager.GetMapEntityId(mousePos.MapId), mousePos, TransformSystem, EntityManager); - } - EntityUid? target = null; - // TODO: UI Refactor update I assume if (_stateManager.CurrentState is GameplayStateBase screen) { target = screen.GetClickedEntity(mousePos); } RaisePredictiveEvent(new LightAttackEvent(target, weaponUid, coordinates)); - return; - } - - if (weapon.Attacking) - { - RaisePredictiveEvent(new StopAttackEvent(weaponUid)); } } @@ -263,7 +205,7 @@ public sealed partial class MeleeWeaponSystem : SharedMeleeWeaponSystem private void ClientHeavyAttack(EntityUid user, EntityCoordinates coordinates, EntityUid meleeUid, MeleeWeaponComponent component) { // Only run on first prediction to avoid the potential raycast entities changing. - if (!TryComp(user, out var userXform) || + if (!_xformQuery.TryGetComponent(user, out var userXform) || !Timing.IsFirstTimePredicted) { return; @@ -284,14 +226,6 @@ public sealed partial class MeleeWeaponSystem : SharedMeleeWeaponSystem RaisePredictiveEvent(new HeavyAttackEvent(meleeUid, entities.GetRange(0, Math.Min(MaxTargets, entities.Count)), coordinates)); } - protected override void Popup(string message, EntityUid? uid, EntityUid? user) - { - if (!Timing.IsFirstTimePredicted || uid == null) - return; - - PopupSystem.PopupEntity(message, uid.Value); - } - private void OnMeleeLunge(MeleeLungeEvent ev) { // Entity might not have been sent by PVS. diff --git a/Content.Client/Weapons/Melee/MeleeWindupOverlay.cs b/Content.Client/Weapons/Melee/MeleeWindupOverlay.cs deleted file mode 100644 index 55e66d6c9d..0000000000 --- a/Content.Client/Weapons/Melee/MeleeWindupOverlay.cs +++ /dev/null @@ -1,146 +0,0 @@ -using System.Numerics; -using Content.Shared.Weapons.Melee; -using Robust.Client.GameObjects; -using Robust.Client.Graphics; -using Robust.Client.Player; -using Robust.Shared.Enums; -using Robust.Shared.Prototypes; -using Robust.Shared.Timing; -using Robust.Shared.Utility; - -namespace Content.Client.Weapons.Melee; - -public sealed class MeleeWindupOverlay : Overlay -{ - private readonly IEntityManager _entManager; - private readonly IGameTiming _timing; - private readonly IPlayerManager _player; - private readonly SharedMeleeWeaponSystem _melee; - private readonly SharedTransformSystem _transform; - - public override OverlaySpace Space => OverlaySpace.WorldSpaceBelowFOV; - - private readonly Texture _texture; - private readonly ShaderInstance _shader; - - public MeleeWindupOverlay(IEntityManager entManager, IGameTiming timing, IPlayerManager playerManager, IPrototypeManager protoManager) - { - _entManager = entManager; - _timing = timing; - _player = playerManager; - _melee = _entManager.EntitySysManager.GetEntitySystem(); - _transform = _entManager.EntitySysManager.GetEntitySystem(); - var sprite = new SpriteSpecifier.Rsi(new ("/Textures/Interface/Misc/progress_bar.rsi"), "icon"); - _texture = _entManager.EntitySysManager.GetEntitySystem().Frame0(sprite); - _shader = protoManager.Index("unshaded").Instance(); - } - - protected override void Draw(in OverlayDrawArgs args) - { - var owner = _player.LocalPlayer?.ControlledEntity; - - if (!_entManager.TryGetComponent(owner, out var ownerXform) || - ownerXform.MapID != args.MapId) - { - return; - } - - if (!_melee.TryGetWeapon(owner.Value, out var meleeUid, out var comp)) - return; - - var handle = args.WorldHandle; - var rotation = args.Viewport.Eye?.Rotation ?? Angle.Zero; - var spriteQuery = _entManager.GetEntityQuery(); - var xformQuery = _entManager.GetEntityQuery(); - - // If you use the display UI scale then need to set max(1f, displayscale) because 0 is valid. - const float scale = 1f; - var scaleMatrix = Matrix3.CreateScale(new Vector2(scale, scale)); - var rotationMatrix = Matrix3.CreateRotation(-rotation); - handle.UseShader(_shader); - var currentTime = _timing.CurTime; - - if (comp.WindUpStart == null || - comp.Attacking) - { - return; - } - - if (!xformQuery.TryGetComponent(meleeUid, out var xform) || - xform.MapID != args.MapId) - { - return; - } - - var worldPosition = _transform.GetWorldPosition(xform); - var worldMatrix = Matrix3.CreateTranslation(worldPosition); - Matrix3.Multiply(scaleMatrix, worldMatrix, out var scaledWorld); - Matrix3.Multiply(rotationMatrix, scaledWorld, out var matty); - - handle.SetTransform(matty); - var offset = -_texture.Height / scale; - - // Use the sprite itself if we know its bounds. This means short or tall sprites don't get overlapped - // by the bar. - float yOffset; - if (spriteQuery.TryGetComponent(meleeUid, out var sprite)) - { - yOffset = -sprite.Bounds.Height / 2f - 0.05f; - } - else - { - yOffset = -0.5f; - } - - // Position above the entity (we've already applied the matrix transform to the entity itself) - // Offset by the texture size for every do_after we have. - var position = new Vector2(-_texture.Width / 2f / EyeManager.PixelsPerMeter, - yOffset / scale + offset / EyeManager.PixelsPerMeter * scale); - - // Draw the underlying bar texture - handle.DrawTexture(_texture, position); - - // Draw the items overlapping the texture - const float startX = 2f; - const float endX = 22f; - - // Area marking where to release - var releaseWidth = 2f * SharedMeleeWeaponSystem.GracePeriod / (float) _melee.GetWindupTime(meleeUid, owner.Value, comp).TotalSeconds * EyeManager.PixelsPerMeter; - const float releaseMiddle = (endX - startX) / 2f + startX; - - var releaseBox = new Box2(new Vector2(releaseMiddle - releaseWidth / 2f, 3f) / EyeManager.PixelsPerMeter, - new Vector2(releaseMiddle + releaseWidth / 2f, 4f) / EyeManager.PixelsPerMeter); - - releaseBox = releaseBox.Translated(position); - handle.DrawRect(releaseBox, Color.LimeGreen); - - // Wraps around back to 0 - var totalDuration = _melee.GetWindupTime(meleeUid, owner.Value, comp).TotalSeconds * 2; - - var elapsed = (currentTime - comp.WindUpStart.Value).TotalSeconds % (2 * totalDuration); - var value = elapsed / totalDuration; - - if (value > 1) - { - value = 2 - value; - } - - var fraction = (float) value; - - var xPos = (endX - startX) * fraction + startX; - - // In pixels - const float width = 2f; - // If we hit the end we won't draw half the box so we need to subtract the end pos from it - var endPos = xPos + width / 2f; - - var box = new Box2(new Vector2(Math.Max(startX, endPos - width), 3f) / EyeManager.PixelsPerMeter, - new Vector2(Math.Min(endX, endPos), 4f) / EyeManager.PixelsPerMeter); - - box = box.Translated(position); - handle.DrawRect(box, Color.White); - - handle.UseShader(null); - handle.SetTransform(Matrix3.Identity); - } -} diff --git a/Content.Server/Weapons/Melee/MeleeWeaponSystem.cs b/Content.Server/Weapons/Melee/MeleeWeaponSystem.cs index 2e3104eea2..9714112895 100644 --- a/Content.Server/Weapons/Melee/MeleeWeaponSystem.cs +++ b/Content.Server/Weapons/Melee/MeleeWeaponSystem.cs @@ -104,18 +104,6 @@ public sealed class MeleeWeaponSystem : SharedMeleeWeaponSystem return true; } - - protected override void Popup(string message, EntityUid? uid, EntityUid? user) - { - if (uid == null) - return; - - if (user == null) - PopupSystem.PopupEntity(message, uid.Value); - else - PopupSystem.PopupEntity(message, uid.Value, Filter.PvsExcept(user.Value, entityManager: EntityManager), true); - } - protected override bool DoDisarm(EntityUid user, DisarmAttackEvent ev, EntityUid meleeUid, MeleeWeaponComponent component, ICommonSession? session) { if (!base.DoDisarm(user, ev, meleeUid, component, session)) diff --git a/Content.Shared/Damage/Components/StaminaComponent.cs b/Content.Shared/Damage/Components/StaminaComponent.cs index 24983ff37b..f75d4ec2fc 100644 --- a/Content.Shared/Damage/Components/StaminaComponent.cs +++ b/Content.Shared/Damage/Components/StaminaComponent.cs @@ -36,12 +36,12 @@ public sealed class StaminaComponent : Component /// /// How much stamina damage is required to entire stam crit. /// - [ViewVariables(VVAccess.ReadWrite), DataField("excess")] + [ViewVariables(VVAccess.ReadWrite), DataField("critThreshold")] public float CritThreshold = 100f; /// /// To avoid continuously updating our data we track the last time we updated so we can extrapolate our current stamina. /// - [DataField("lastUpdate", customTypeSerializer:typeof(TimeOffsetSerializer))] + [DataField("nextUpdate", customTypeSerializer:typeof(TimeOffsetSerializer))] public TimeSpan NextUpdate = TimeSpan.Zero; } diff --git a/Content.Shared/Damage/Systems/StaminaSystem.cs b/Content.Shared/Damage/Systems/StaminaSystem.cs index b0232f4be9..c0fa7a0bf6 100644 --- a/Content.Shared/Damage/Systems/StaminaSystem.cs +++ b/Content.Shared/Damage/Systems/StaminaSystem.cs @@ -227,6 +227,23 @@ public sealed class StaminaSystem : EntitySystem _alerts.ShowAlert(uid, AlertType.Stamina, (short) severity); } + /// + /// Tries to take stamina damage without raising the entity over the crit threshold. + /// + public bool TryTakeStamina(EntityUid uid, float value, StaminaComponent? component = null, EntityUid? source = null, EntityUid? with = null) + { + if (!Resolve(uid, ref component, false)) + return false; + + var oldStam = component.StaminaDamage; + + if (oldStam + value > component.CritThreshold || component.Critical) + return false; + + TakeStaminaDamage(uid, value, component, source, with); + return true; + } + public void TakeStaminaDamage(EntityUid uid, float value, StaminaComponent? component = null, EntityUid? source = null, EntityUid? with = null) { if (!Resolve(uid, ref component, false)) diff --git a/Content.Shared/Weapons/Melee/Events/MeleeHitEvent.cs b/Content.Shared/Weapons/Melee/Events/MeleeHitEvent.cs index da46446377..5c7096e117 100644 --- a/Content.Shared/Weapons/Melee/Events/MeleeHitEvent.cs +++ b/Content.Shared/Weapons/Melee/Events/MeleeHitEvent.cs @@ -85,9 +85,3 @@ public record struct GetMeleeAttackRateEvent(EntityUid Weapon, float Rate, float /// [ByRefEvent] public record struct GetHeavyDamageModifierEvent(EntityUid Weapon, FixedPoint2 DamageModifier, float Multipliers, EntityUid User); - -/// -/// Raised on a melee weapon to calculate the heavy windup modifier. -/// -[ByRefEvent] -public record struct GetHeavyWindupModifierEvent(EntityUid Weapon, float WindupModifier, float Multipliers, EntityUid User); diff --git a/Content.Shared/Weapons/Melee/Events/StartHeavyAttackEvent.cs b/Content.Shared/Weapons/Melee/Events/StartHeavyAttackEvent.cs deleted file mode 100644 index d7f220e75e..0000000000 --- a/Content.Shared/Weapons/Melee/Events/StartHeavyAttackEvent.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Robust.Shared.Serialization; - -namespace Content.Shared.Weapons.Melee.Events; - -[Serializable, NetSerializable] -public sealed class StartHeavyAttackEvent : EntityEventArgs -{ - public readonly EntityUid Weapon; - - public StartHeavyAttackEvent(EntityUid weapon) - { - Weapon = weapon; - } -} diff --git a/Content.Shared/Weapons/Melee/Events/StopHeavyAttackEvent.cs b/Content.Shared/Weapons/Melee/Events/StopHeavyAttackEvent.cs deleted file mode 100644 index 75d2ff0aa5..0000000000 --- a/Content.Shared/Weapons/Melee/Events/StopHeavyAttackEvent.cs +++ /dev/null @@ -1,17 +0,0 @@ -using Robust.Shared.Serialization; - -namespace Content.Shared.Weapons.Melee.Events; - -/// -/// Raised by the client if it pre-emptively stops a heavy attack. -/// -[Serializable, NetSerializable] -public sealed class StopHeavyAttackEvent : EntityEventArgs -{ - public readonly EntityUid Weapon; - - public StopHeavyAttackEvent(EntityUid weapon) - { - Weapon = weapon; - } -} diff --git a/Content.Shared/Weapons/Melee/MeleeWeaponComponent.cs b/Content.Shared/Weapons/Melee/MeleeWeaponComponent.cs index 866dcddb5c..34f94b41a8 100644 --- a/Content.Shared/Weapons/Melee/MeleeWeaponComponent.cs +++ b/Content.Shared/Weapons/Melee/MeleeWeaponComponent.cs @@ -61,25 +61,6 @@ public sealed class MeleeWeaponComponent : Component [ViewVariables(VVAccess.ReadWrite)] public bool Attacking = false; - /// - /// When did we start a heavy attack. - /// - /// - [ViewVariables(VVAccess.ReadWrite), DataField("windUpStart")] - public TimeSpan? WindUpStart; - - /// - /// Heavy attack windup time gets multiplied by this value and the light attack cooldown. - /// - [ViewVariables(VVAccess.ReadWrite), DataField("heavyWindupModifier")] - public float HeavyWindupModifier = 1.5f; - - /// - /// Light attacks get multiplied by this over the base value. - /// - [ViewVariables(VVAccess.ReadWrite), DataField("heavyDamageModifier")] - public FixedPoint2 HeavyDamageModifier = FixedPoint2.New(2); - /// /// Base damage for this weapon. Can be modified via heavy damage or other means. /// @@ -87,9 +68,20 @@ public sealed class MeleeWeaponComponent : Component [ViewVariables(VVAccess.ReadWrite)] public DamageSpecifier Damage = default!; - [DataField("bluntStaminaDamageFactor")] - [ViewVariables(VVAccess.ReadWrite)] - public FixedPoint2 BluntStaminaDamageFactor { get; set; } = 0.5f; + [DataField("bluntStaminaDamageFactor")] [ViewVariables(VVAccess.ReadWrite)] + public FixedPoint2 BluntStaminaDamageFactor = FixedPoint2.New(0.5f); + + /// + /// Multiplies damage by this amount for wide attacks. + /// + [ViewVariables(VVAccess.ReadWrite), DataField("heavyDamageModifier")] + public FixedPoint2 HeavyDamageModifier = FixedPoint2.New(1.25); + + /// + /// How much stamina it costs for a heavy attack. + /// + [ViewVariables(VVAccess.ReadWrite), DataField("heavyStaminaCost")] + public float HeavyStaminaCost = 15f; // TODO: Temporarily 1.5 until interactionoutline is adjusted to use melee, then probably drop to 1.2 /// @@ -155,18 +147,16 @@ public sealed class MeleeWeaponComponentState : ComponentState public float AttackRate; public bool Attacking; public TimeSpan NextAttack; - public TimeSpan? WindUpStart; public string ClickAnimation; public string WideAnimation; public float Range; - public MeleeWeaponComponentState(float attackRate, bool attacking, TimeSpan nextAttack, TimeSpan? windupStart, string clickAnimation, string wideAnimation, float range) + public MeleeWeaponComponentState(float attackRate, bool attacking, TimeSpan nextAttack, string clickAnimation, string wideAnimation, float range) { AttackRate = attackRate; Attacking = attacking; NextAttack = nextAttack; - WindUpStart = windupStart; ClickAnimation = clickAnimation; WideAnimation = wideAnimation; Range = range; diff --git a/Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs b/Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs index 6c186ada75..52c2b2e517 100644 --- a/Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs +++ b/Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs @@ -27,7 +27,6 @@ using Robust.Shared.Physics.Systems; using Robust.Shared.Players; using Robust.Shared.Prototypes; using Robust.Shared.Timing; -using Robust.Shared.Utility; namespace Content.Shared.Weapons.Melee; @@ -48,8 +47,6 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem [Dependency] protected readonly SharedTransformSystem TransformSystem = default!; [Dependency] private readonly StaminaSystem _stamina = default!; - protected ISawmill Sawmill = default!; - public const float DamagePitchVariation = 0.05f; private const int AttackMask = (int) (CollisionGroup.MobMask | CollisionGroup.Opaque); @@ -66,24 +63,19 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem public override void Initialize() { base.Initialize(); - Sawmill = Logger.GetSawmill("melee"); SubscribeLocalEvent(OnMeleeUnpaused); SubscribeLocalEvent(OnGetState); SubscribeLocalEvent(OnHandleState); - SubscribeLocalEvent(OnMeleeDropped); SubscribeLocalEvent(OnMeleeSelected); SubscribeLocalEvent(OnMeleeShotAttempted); SubscribeLocalEvent(OnMeleeShot); SubscribeLocalEvent(OnGetBonusMeleeDamage); SubscribeLocalEvent(OnGetBonusHeavyDamageModifier); SubscribeLocalEvent(OnGetBonusMeleeAttackRate); - SubscribeLocalEvent(OnGetBonusHeavyWindupModifier); SubscribeAllEvent(OnHeavyAttack); SubscribeAllEvent(OnLightAttack); - SubscribeAllEvent(OnStartHeavyAttack); - SubscribeAllEvent(OnStopHeavyAttack); SubscribeAllEvent(OnDisarmAttack); SubscribeAllEvent(OnStopAttack); @@ -144,15 +136,6 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem Dirty(component); } - private void OnMeleeDropped(EntityUid uid, MeleeWeaponComponent component, HandDeselectedEvent args) - { - if (component.WindUpStart == null) - return; - - component.WindUpStart = null; - Dirty(component); - } - private void OnGetBonusMeleeDamage(EntityUid uid, BonusMeleeDamageComponent component, ref GetMeleeDamageEvent args) { if (component.BonusDamage != null) @@ -173,12 +156,6 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem args.Multipliers *= component.Multiplier; } - private void OnGetBonusHeavyWindupModifier(EntityUid uid, BonusMeleeAttackRateComponent component, ref GetHeavyWindupModifierEvent args) - { - args.WindupModifier += component.HeavyWindupFlatModifier; - args.Multipliers *= component.HeavyWindupMultiplier; - } - private void OnStopAttack(StopAttackEvent msg, EntitySessionEventArgs args) { var user = args.SenderSession.AttachedEntity; @@ -199,26 +176,6 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem Dirty(weapon); } - private void OnStartHeavyAttack(StartHeavyAttackEvent msg, EntitySessionEventArgs args) - { - var user = args.SenderSession.AttachedEntity; - - if (user == null) - return; - - if (!TryGetWeapon(user.Value, out var weaponUid, out var weapon) || - weaponUid != msg.Weapon) - { - return; - } - - DebugTools.Assert(weapon.WindUpStart == null); - weapon.WindUpStart = Timing.CurTime; - Dirty(weapon); - } - - protected abstract void Popup(string message, EntityUid? uid, EntityUid? user); - private void OnLightAttack(LightAttackEvent msg, EntitySessionEventArgs args) { var user = args.SenderSession.AttachedEntity; @@ -235,28 +192,6 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem AttemptAttack(args.SenderSession.AttachedEntity!.Value, msg.Weapon, weapon, msg, args.SenderSession); } - private void OnStopHeavyAttack(StopHeavyAttackEvent msg, EntitySessionEventArgs args) - { - if (args.SenderSession.AttachedEntity == null) - { - return; - } - - if (!TryGetWeapon(args.SenderSession.AttachedEntity.Value, out var weaponUid, out var weapon) || - weaponUid != msg.Weapon) - { - return; - } - - if (weapon.WindUpStart.Equals(null)) - { - return; - } - - weapon.WindUpStart = null; - Dirty(weapon); - } - private void OnHeavyAttack(HeavyAttackEvent msg, EntitySessionEventArgs args) { if (args.SenderSession.AttachedEntity == null) @@ -290,8 +225,7 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem private void OnGetState(EntityUid uid, MeleeWeaponComponent component, ref ComponentGetState args) { - args.State = new MeleeWeaponComponentState(component.AttackRate, component.Attacking, component.NextAttack, - component.WindUpStart, component.ClickAnimation, component.WideAnimation, component.Range); + args.State = new MeleeWeaponComponentState(component.AttackRate, component.Attacking, component.NextAttack, component.ClickAnimation, component.WideAnimation, component.Range); } private void OnHandleState(EntityUid uid, MeleeWeaponComponent component, ref ComponentHandleState args) @@ -302,7 +236,6 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem component.Attacking = state.Attacking; component.AttackRate = state.AttackRate; component.NextAttack = state.NextAttack; - component.WindUpStart = state.WindUpStart; component.ClickAnimation = state.ClickAnimation; component.WideAnimation = state.WideAnimation; @@ -345,32 +278,6 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem return ev.DamageModifier * ev.Multipliers; } - public float GetHeavyWindupModifier(EntityUid uid, EntityUid user, MeleeWeaponComponent? component = null) - { - if (!Resolve(uid, ref component)) - return 0; - - var ev = new GetHeavyWindupModifierEvent(uid, component.HeavyWindupModifier, 1, user); - RaiseLocalEvent(uid, ref ev); - - return ev.WindupModifier * ev.Multipliers; - } - - /// - /// Gets how long it takes a heavy attack to windup. - /// - public TimeSpan GetWindupTime(EntityUid uid, EntityUid user, MeleeWeaponComponent? component = null) - { - if (!Resolve(uid, ref component)) - return TimeSpan.Zero; - - var attackRate = GetAttackRate(uid, user, component); - - return attackRate > 0 - ? TimeSpan.FromSeconds(1 / attackRate * GetHeavyWindupModifier(uid, user, component)) - : TimeSpan.Zero; - } - public bool TryGetWeapon(EntityUid entity, out EntityUid weaponUid, [NotNullWhen(true)] out MeleeWeaponComponent? melee) { weaponUid = default; @@ -525,7 +432,9 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem animation = weapon.ClickAnimation; break; case HeavyAttackEvent heavy: - DoHeavyAttack(user, heavy, weaponUid, weapon, session); + if (!DoHeavyAttack(user, heavy, weaponUid, weapon, session)) + return false; + animation = weapon.WideAnimation; break; default: @@ -539,45 +448,11 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem return true; } - /// - /// When an attack is released get the actual modifier for damage done. - /// - public float GetModifier(EntityUid uid, EntityUid user, MeleeWeaponComponent component, bool lightAttack) - { - if (lightAttack) - return 1f; - - var windup = component.WindUpStart; - if (windup == null) - return 0f; - - var releaseTime = (Timing.CurTime - windup.Value).TotalSeconds; - var windupTime = GetWindupTime(uid, user, component).TotalSeconds; - - // Wraps around back to 0 - releaseTime %= (2 * windupTime); - - var releaseDiff = Math.Abs(releaseTime - windupTime); - - if (releaseDiff < 0) - releaseDiff = Math.Min(0, releaseDiff + GracePeriod); - else - releaseDiff = Math.Max(0, releaseDiff - GracePeriod); - - var fraction = (windupTime - releaseDiff) / windupTime; - - if (fraction < 0.4) - fraction = 0; - - DebugTools.Assert(fraction <= 1); - return (float) fraction * GetHeavyDamageModifier(uid, user, component).Float(); - } - protected abstract bool InRange(EntityUid user, EntityUid target, float range, ICommonSession? session); protected virtual void DoLightAttack(EntityUid user, LightAttackEvent ev, EntityUid meleeUid, MeleeWeaponComponent component, ICommonSession? session) { - var damage = GetDamage(meleeUid, user, component) * GetModifier(meleeUid, user, component, true); + var damage = GetDamage(meleeUid, user, component); // For consistency with wide attacks stuff needs damageable. if (Deleted(ev.Target) || @@ -679,22 +554,28 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem protected abstract void DoDamageEffect(List targets, EntityUid? user, TransformComponent targetXform); - private void DoHeavyAttack(EntityUid user, HeavyAttackEvent ev, EntityUid meleeUid, MeleeWeaponComponent component, ICommonSession? session) + private bool DoHeavyAttack(EntityUid user, HeavyAttackEvent ev, EntityUid meleeUid, MeleeWeaponComponent component, ICommonSession? session) { // TODO: This is copy-paste as fuck with DoPreciseAttack if (!TryComp(user, out var userXform)) - return; + return false; var targetMap = ev.Coordinates.ToMap(EntityManager, TransformSystem); if (targetMap.MapId != userXform.MapID) - return; + return false; + + if (!_stamina.TryTakeStamina(user, component.HeavyStaminaCost)) + { + PopupSystem.PopupClient(Loc.GetString("melee-stamina"), user, user); + return false; + } var userPos = TransformSystem.GetWorldPosition(userXform); var direction = targetMap.Position - userPos; var distance = Math.Min(component.Range, direction.Length()); - var damage = GetDamage(meleeUid, user, component) * GetModifier(meleeUid, user, component, false); + var damage = GetDamage(meleeUid, user, component) * GetHeavyDamageModifier(meleeUid, user, component); var entities = ev.Entities; if (entities.Count == 0) @@ -713,7 +594,7 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem RaiseLocalEvent(meleeUid, missEvent); Audio.PlayPredicted(component.SwingSound, meleeUid, user); - return; + return true; } // Naughty input @@ -754,7 +635,7 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem RaiseLocalEvent(meleeUid, hitEvent); if (hitEvent.Handled) - return; + return true; Interaction.DoContactInteraction(user, ev.Weapon); @@ -819,6 +700,8 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem { DoDamageEffect(targets, user, Transform(targets[0])); } + + return true; } protected HashSet ArcRayCast(Vector2 position, Angle angle, Angle arcWidth, float range, MapId mapId, EntityUid ignore) diff --git a/Content.Shared/Wieldable/WieldableSystem.cs b/Content.Shared/Wieldable/WieldableSystem.cs index 0e2b52b9e2..31e462c6af 100644 --- a/Content.Shared/Wieldable/WieldableSystem.cs +++ b/Content.Shared/Wieldable/WieldableSystem.cs @@ -35,7 +35,6 @@ public sealed class WieldableSystem : EntitySystem SubscribeLocalEvent(OnItemLeaveHand); SubscribeLocalEvent(OnVirtualItemDeleted); SubscribeLocalEvent>(AddToggleWieldVerb); - SubscribeLocalEvent(OnDisarmAttemptEvent); SubscribeLocalEvent(OnMeleeAttempt); SubscribeLocalEvent(OnShootAttempt); @@ -85,12 +84,6 @@ public sealed class WieldableSystem : EntitySystem Dirty(gun); } - private void OnDisarmAttemptEvent(EntityUid uid, WieldableComponent component, DisarmAttemptEvent args) - { - if (component.Wielded) - args.Cancel(); - } - private void AddToggleWieldVerb(EntityUid uid, WieldableComponent component, GetVerbsEvent args) { if (args.Hands == null || !args.CanAccess || !args.CanInteract) diff --git a/Resources/Locale/en-US/damage/stamina.ftl b/Resources/Locale/en-US/damage/stamina.ftl index 9afe540b9d..ce45d76c69 100644 --- a/Resources/Locale/en-US/damage/stamina.ftl +++ b/Resources/Locale/en-US/damage/stamina.ftl @@ -1 +1,2 @@ -stamina-resist = Resisted \ No newline at end of file +stamina-resist = Resisted +melee-stamina = Not enough stamina diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml index 0c564c2772..725063ec93 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml @@ -96,7 +96,7 @@ 0: Alive 10: Dead - type: Stamina - excess: 10 + critThreshold: 10 - type: DamageStateVisuals states: Alive: diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/carp.yml b/Resources/Prototypes/Entities/Mobs/NPCs/carp.yml index c725a49dd5..c323c519ad 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/carp.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/carp.yml @@ -41,7 +41,7 @@ 0: Alive 100: Dead - type: Stamina - excess: 100 + critThreshold: 100 - type: MovementAlwaysTouching - type: DamageStateVisuals states: diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/flesh.yml b/Resources/Prototypes/Entities/Mobs/NPCs/flesh.yml index 2a6fd443c8..386377ee2e 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/flesh.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/flesh.yml @@ -34,7 +34,7 @@ 0: Alive 75: Dead - type: Stamina - excess: 50 + critThreshold: 50 - type: Butcherable spawned: - id: FoodMeat diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/regalrat.yml b/Resources/Prototypes/Entities/Mobs/NPCs/regalrat.yml index 8d6ec89ba7..96d2ebb293 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/regalrat.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/regalrat.yml @@ -218,7 +218,7 @@ 15: Critical 30: Dead - type: Stamina - excess: 60 + critThreshold: 60 - type: MeleeWeapon hidden: true soundHit: diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml b/Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml index 3318d0c3b5..2f260c604b 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/silicon.yml @@ -79,7 +79,7 @@ 0: Alive 120: Dead - type: Stamina - excess: 120 + critThreshold: 120 - type: Destructible thresholds: - trigger: diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/space.yml b/Resources/Prototypes/Entities/Mobs/NPCs/space.yml index 34142c8393..0fb7cb8681 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/space.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/space.yml @@ -36,7 +36,7 @@ 0: Alive 150: Dead - type: Stamina - excess: 150 + critThreshold: 150 - type: MovementAlwaysTouching - type: Bloodstream bloodMaxVolume: 300 @@ -163,7 +163,7 @@ Dead: Base: kangaroo-space-dead - type: Stamina - excess: 180 + critThreshold: 180 - type: Inventory speciesId: kangaroo templateId: spacekangaroo @@ -220,7 +220,7 @@ 0: Alive 130: Dead - type: Stamina - excess: 150 + critThreshold: 150 - type: DamageStateVisuals states: Alive: diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/spacetick.yml b/Resources/Prototypes/Entities/Mobs/NPCs/spacetick.yml index 4119eb3b01..86e52da394 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/spacetick.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/spacetick.yml @@ -43,7 +43,7 @@ 0: Alive 15: Dead - type: Stamina - excess: 15 + critThreshold: 15 - type: MovementAlwaysTouching - type: DamageStateVisuals states: diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml b/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml index ab2586670c..c744916243 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/xeno.yml @@ -61,7 +61,7 @@ 0: Alive 50: Dead - type: Stamina - excess: 200 + critThreshold: 200 - type: Bloodstream bloodReagent: FluorosulfuricAcid - type: MeleeWeapon @@ -129,7 +129,7 @@ 0: Alive 75: Dead - type: Stamina - excess: 300 + critThreshold: 300 - type: SlowOnDamage speedModifierThresholds: 50: 0.7 @@ -201,7 +201,7 @@ 0: Alive 300: Dead - type: Stamina - excess: 1500 + critThreshold: 1500 - type: MovementSpeedModifier baseWalkSpeed: 2.8 baseSprintSpeed: 3.8 @@ -246,7 +246,7 @@ 0: Alive 200: Dead - type: Stamina - excess: 550 + critThreshold: 550 - type: MovementSpeedModifier baseWalkSpeed: 2.3 baseSprintSpeed: 4.2 @@ -284,7 +284,7 @@ - map: ["enum.DamageStateVisualLayers.Base"] state: running - type: Stamina - excess: 250 + critThreshold: 250 - type: MovementSpeedModifier baseWalkSpeed: 2.7 baseSprintSpeed: 6.0 @@ -335,7 +335,7 @@ rootTask: task: SimpleRangedHostileCompound - type: Stamina - excess: 300 + critThreshold: 300 - type: RechargeBasicEntityAmmo rechargeCooldown: 0.75 - type: BasicEntityAmmoProvider diff --git a/Resources/Prototypes/Entities/Objects/Weapons/Melee/e_sword.yml b/Resources/Prototypes/Entities/Objects/Weapons/Melee/e_sword.yml index a13e2f4bd2..5c5972d839 100644 --- a/Resources/Prototypes/Entities/Objects/Weapons/Melee/e_sword.yml +++ b/Resources/Prototypes/Entities/Objects/Weapons/Melee/e_sword.yml @@ -179,10 +179,8 @@ Blunt: -4.5 litDisarmMalus: 0.7 - type: MeleeWeapon - attackRate: 1.6 + attackRate: 1.5 angle: 100 - heavyWindupModifier: .9 - heavyDamageModifier: 1.5 soundHit: path: /Audio/Weapons/eblade1.ogg damage: