From 37d30568099259b7790ce72071c0fd3d755d78f6 Mon Sep 17 00:00:00 2001 From: Nemanja <98561806+EmoGarbage404@users.noreply.github.com> Date: Wed, 7 Jun 2023 16:26:45 -0400 Subject: [PATCH] Events all over melee (#16997) --- .../Weapons/Melee/MeleeWindupOverlay.cs | 4 +- .../Electrocution/ElectrocutionSystem.cs | 2 +- .../NPC/Systems/NPCCombatSystem.Melee.cs | 2 +- .../Weapons/Melee/MeleeWeaponSystem.cs | 2 +- .../BonusMeleeAttackRateComponent.cs | 31 ++++++ .../Components/BonusMeleeDamageComponent.cs | 28 ++++- .../Weapons/Melee/Events/MeleeHitEvent.cs | 22 +++- .../Weapons/Melee/MeleeWeaponComponent.cs | 10 +- .../Weapons/Melee/SharedMeleeWeaponSystem.cs | 103 +++++++++++++++--- 9 files changed, 170 insertions(+), 34 deletions(-) create mode 100644 Content.Shared/Weapons/Melee/Components/BonusMeleeAttackRateComponent.cs diff --git a/Content.Client/Weapons/Melee/MeleeWindupOverlay.cs b/Content.Client/Weapons/Melee/MeleeWindupOverlay.cs index 72439de236..9f5193a3ca 100644 --- a/Content.Client/Weapons/Melee/MeleeWindupOverlay.cs +++ b/Content.Client/Weapons/Melee/MeleeWindupOverlay.cs @@ -104,7 +104,7 @@ public sealed class MeleeWindupOverlay : Overlay const float endX = 22f; // Area marking where to release - var releaseWidth = 2f * SharedMeleeWeaponSystem.GracePeriod / (float) comp.WindupTime.TotalSeconds * EyeManager.PixelsPerMeter; + 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, @@ -114,7 +114,7 @@ public sealed class MeleeWindupOverlay : Overlay handle.DrawRect(releaseBox, Color.LimeGreen); // Wraps around back to 0 - var totalDuration = comp.WindupTime.TotalSeconds * 2; + var totalDuration = _melee.GetWindupTime(meleeUid, owner.Value, comp).TotalSeconds * 2; var elapsed = (currentTime - comp.WindUpStart.Value).TotalSeconds % (2 * totalDuration); var value = elapsed / totalDuration; diff --git a/Content.Server/Electrocution/ElectrocutionSystem.cs b/Content.Server/Electrocution/ElectrocutionSystem.cs index 1caffd1fd5..b73228a139 100644 --- a/Content.Server/Electrocution/ElectrocutionSystem.cs +++ b/Content.Server/Electrocution/ElectrocutionSystem.cs @@ -163,7 +163,7 @@ public sealed class ElectrocutionSystem : SharedElectrocutionSystem if (!electrified.OnAttacked) return; - if (_meleeWeapon.GetDamage(args.Used).Total == 0) + if (_meleeWeapon.GetDamage(args.Used, args.User).Total == 0) return; TryDoElectrifiedAct(uid, args.User, 1, electrified); diff --git a/Content.Server/NPC/Systems/NPCCombatSystem.Melee.cs b/Content.Server/NPC/Systems/NPCCombatSystem.Melee.cs index 27b7156ad2..a4ece57beb 100644 --- a/Content.Server/NPC/Systems/NPCCombatSystem.Melee.cs +++ b/Content.Server/NPC/Systems/NPCCombatSystem.Melee.cs @@ -29,7 +29,7 @@ public sealed partial class NPCCombatSystem var cdRemaining = weapon.NextAttack - _timing.CurTime; // If CD remaining then backup. - if (cdRemaining < TimeSpan.FromSeconds(1f / weapon.AttackRate) * 0.5f) + if (cdRemaining < TimeSpan.FromSeconds(1f / _melee.GetAttackRate(component.Weapon, uid, weapon)) * 0.5f) return; if (!_physics.TryGetNearestPoints(uid, component.Target, out var pointA, out var pointB)) diff --git a/Content.Server/Weapons/Melee/MeleeWeaponSystem.cs b/Content.Server/Weapons/Melee/MeleeWeaponSystem.cs index 9c995d2283..6f93edb13d 100644 --- a/Content.Server/Weapons/Melee/MeleeWeaponSystem.cs +++ b/Content.Server/Weapons/Melee/MeleeWeaponSystem.cs @@ -62,7 +62,7 @@ public sealed class MeleeWeaponSystem : SharedMeleeWeaponSystem if (!args.CanInteract || !args.CanAccess || component.HideFromExamine) return; - var damageSpec = GetDamage(uid, component); + var damageSpec = GetDamage(uid, args.User, component); if (damageSpec.Total == FixedPoint2.Zero) return; diff --git a/Content.Shared/Weapons/Melee/Components/BonusMeleeAttackRateComponent.cs b/Content.Shared/Weapons/Melee/Components/BonusMeleeAttackRateComponent.cs new file mode 100644 index 0000000000..a47ed503bf --- /dev/null +++ b/Content.Shared/Weapons/Melee/Components/BonusMeleeAttackRateComponent.cs @@ -0,0 +1,31 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Weapons.Melee.Components; + +[RegisterComponent, NetworkedComponent, Access(typeof(SharedMeleeWeaponSystem))] +public sealed class BonusMeleeAttackRateComponent : Component +{ + /// + /// The value added onto the attack rate of a melee weapon + /// + [DataField("flatModifier"), ViewVariables(VVAccess.ReadWrite)] + public float FlatModifier; + + /// + /// A value that is multiplied by the attack rate of a melee weapon + /// + [DataField("multiplier"), ViewVariables(VVAccess.ReadWrite)] + public float Multiplier = 1; + + /// + /// A value that is added on to a weapon's heavy windup time. + /// + [DataField("heavyWindupFlatModifier"), ViewVariables(VVAccess.ReadWrite)] + public float HeavyWindupFlatModifier; + + /// + /// A value that is multiplied by a weapon's heavy windup time + /// + [DataField("heavyWindupMultiplier"), ViewVariables(VVAccess.ReadWrite)] + public float HeavyWindupMultiplier = 1; +} diff --git a/Content.Shared/Weapons/Melee/Components/BonusMeleeDamageComponent.cs b/Content.Shared/Weapons/Melee/Components/BonusMeleeDamageComponent.cs index ea937b2582..06762d57ce 100644 --- a/Content.Shared/Weapons/Melee/Components/BonusMeleeDamageComponent.cs +++ b/Content.Shared/Weapons/Melee/Components/BonusMeleeDamageComponent.cs @@ -1,4 +1,6 @@ using Content.Shared.Damage; +using Content.Shared.FixedPoint; +using Content.Shared.Weapons.Melee.Events; using Robust.Shared.GameStates; namespace Content.Shared.Weapons.Melee.Components; @@ -7,12 +9,30 @@ namespace Content.Shared.Weapons.Melee.Components; /// This is used for adding in bonus damage via /// This exists only for event relays and doing entity shenanigans. /// -[RegisterComponent, NetworkedComponent] +[RegisterComponent, NetworkedComponent, Access(typeof(SharedMeleeWeaponSystem))] public sealed class BonusMeleeDamageComponent : Component { /// - /// The damage that will be applied. + /// The damage that will be added. /// - [DataField("bonusDamage", required: true)] - public DamageSpecifier BonusDamage = default!; + [DataField("bonusDamage")] + public DamageSpecifier? BonusDamage; + + /// + /// A modifier set for the damage that will be dealt. + /// + [DataField("damageModifierSet")] + public DamageModifierSet? DamageModifierSet; + + /// + /// A flat damage increase added to + /// + [DataField("heavyDamageFlatModifier"), ViewVariables(VVAccess.ReadWrite)] + public FixedPoint2 HeavyDamageFlatModifier; + + /// + /// A value multiplier by the value of + /// + [DataField("heavyDamageMultiplier"), ViewVariables(VVAccess.ReadWrite)] + public float HeavyDamageMultiplier = 1; } diff --git a/Content.Shared/Weapons/Melee/Events/MeleeHitEvent.cs b/Content.Shared/Weapons/Melee/Events/MeleeHitEvent.cs index cb7e28890b..da46446377 100644 --- a/Content.Shared/Weapons/Melee/Events/MeleeHitEvent.cs +++ b/Content.Shared/Weapons/Melee/Events/MeleeHitEvent.cs @@ -1,4 +1,5 @@ using Content.Shared.Damage; +using Content.Shared.FixedPoint; using Robust.Shared.Audio; namespace Content.Shared.Weapons.Melee.Events; @@ -70,6 +71,23 @@ public sealed class MeleeHitEvent : HandledEntityEventArgs /// /// Raised on a melee weapon to calculate potential damage bonuses or decreases. /// -/// [ByRefEvent] -public record struct GetMeleeDamageEvent(DamageSpecifier Damage); +public record struct GetMeleeDamageEvent(EntityUid Weapon, DamageSpecifier Damage, List Modifiers, EntityUid User); + +/// +/// Raised on a melee weapon to calculate the attack rate. +/// +[ByRefEvent] +public record struct GetMeleeAttackRateEvent(EntityUid Weapon, float Rate, float Multipliers, EntityUid User); + +/// +/// Raised on a melee weapon to calculate the heavy damage modifier. +/// +[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/MeleeWeaponComponent.cs b/Content.Shared/Weapons/Melee/MeleeWeaponComponent.cs index 2ee18740fd..4bb83425e2 100644 --- a/Content.Shared/Weapons/Melee/MeleeWeaponComponent.cs +++ b/Content.Shared/Weapons/Melee/MeleeWeaponComponent.cs @@ -1,6 +1,5 @@ using Content.Shared.Damage; using Content.Shared.FixedPoint; -using Content.Shared.Interaction; using Robust.Shared.Audio; using Robust.Shared.GameStates; using Robust.Shared.Prototypes; @@ -21,7 +20,7 @@ public sealed class MeleeWeaponComponent : Component /// [ViewVariables(VVAccess.ReadWrite)] [DataField("hidden")] - public bool HideFromExamine { get; set; } = false; + public bool HideFromExamine; /// /// Next time this component is allowed to light attack. Heavy attacks are wound up and never have a cooldown. @@ -53,7 +52,6 @@ public sealed class MeleeWeaponComponent : Component /// [ViewVariables(VVAccess.ReadWrite)] public bool Attacking = false; - /// /// When did we start a heavy attack. @@ -62,12 +60,6 @@ public sealed class MeleeWeaponComponent : Component [ViewVariables(VVAccess.ReadWrite), DataField("windUpStart")] public TimeSpan? WindUpStart; - /// - /// How long it takes a heavy attack to windup. - /// - [ViewVariables] - public TimeSpan WindupTime => AttackRate > 0 ? TimeSpan.FromSeconds(1 / AttackRate * HeavyWindupModifier) : TimeSpan.Zero; - /// /// Heavy attack windup time gets multiplied by this value and the light attack cooldown. /// diff --git a/Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs b/Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs index f1a4ae111c..37b11ada7e 100644 --- a/Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs +++ b/Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs @@ -18,7 +18,6 @@ using Content.Shared.Weapons.Melee.Events; using Content.Shared.Weapons.Ranged.Components; using Content.Shared.Weapons.Ranged.Systems; using Robust.Shared.Audio; -using Robust.Shared.Collections; using Robust.Shared.GameStates; using Robust.Shared.Map; using Robust.Shared.Physics; @@ -74,6 +73,9 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem SubscribeLocalEvent(OnMeleeSelected); SubscribeLocalEvent(OnMeleeShot); SubscribeLocalEvent(OnGetBonusMeleeDamage); + SubscribeLocalEvent(OnGetBonusHeavyDamageModifier); + SubscribeLocalEvent(OnGetBonusMeleeAttackRate); + SubscribeLocalEvent(OnGetBonusHeavyWindupModifier); SubscribeAllEvent(OnHeavyAttack); SubscribeAllEvent(OnLightAttack); @@ -112,7 +114,8 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem private void OnMeleeSelected(EntityUid uid, MeleeWeaponComponent component, HandSelectedEvent args) { - if (component.AttackRate.Equals(0f)) + var attackRate = GetAttackRate(uid, args.User, component); + if (attackRate.Equals(0f)) return; if (!component.ResetOnHandSelected) @@ -123,7 +126,7 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem // If someone swaps to this weapon then reset its cd. var curTime = Timing.CurTime; - var minimum = curTime + TimeSpan.FromSeconds(1 / component.AttackRate); + var minimum = curTime + TimeSpan.FromSeconds(1 / attackRate); if (minimum < component.NextAttack) return; @@ -143,7 +146,28 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem private void OnGetBonusMeleeDamage(EntityUid uid, BonusMeleeDamageComponent component, ref GetMeleeDamageEvent args) { - args.Damage += component.BonusDamage; + if (component.BonusDamage != null) + args.Damage += component.BonusDamage; + if (component.DamageModifierSet != null) + args.Modifiers.Add(component.DamageModifierSet); + } + + private void OnGetBonusHeavyDamageModifier(EntityUid uid, BonusMeleeDamageComponent component, ref GetHeavyDamageModifierEvent args) + { + args.DamageModifier += component.HeavyDamageFlatModifier; + args.Multipliers *= component.HeavyDamageMultiplier; + } + + private void OnGetBonusMeleeAttackRate(EntityUid uid, BonusMeleeAttackRateComponent component, ref GetMeleeAttackRateEvent args) + { + args.Rate += component.FlatModifier; + 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) @@ -276,15 +300,66 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem component.Range = state.Range; } - public DamageSpecifier GetDamage(EntityUid uid, MeleeWeaponComponent? component = null) + /// + /// Gets the total damage a weapon does, including modifiers like wielding and enablind/disabling + /// + public DamageSpecifier GetDamage(EntityUid uid, EntityUid user, MeleeWeaponComponent? component = null) { if (!Resolve(uid, ref component, false)) return new DamageSpecifier(); - var ev = new GetMeleeDamageEvent(new (component.Damage)); + var ev = new GetMeleeDamageEvent(uid, new (component.Damage), new(), user); RaiseLocalEvent(uid, ref ev); - return ev.Damage; + return DamageSpecifier.ApplyModifierSets(ev.Damage, ev.Modifiers); + } + + public float GetAttackRate(EntityUid uid, EntityUid user, MeleeWeaponComponent? component = null) + { + if (!Resolve(uid, ref component)) + return 0; + + var ev = new GetMeleeAttackRateEvent(uid, component.AttackRate, 1, user); + RaiseLocalEvent(uid, ref ev); + + return ev.Rate * ev.Multipliers; + } + + public FixedPoint2 GetHeavyDamageModifier(EntityUid uid, EntityUid user, MeleeWeaponComponent? component = null) + { + if (!Resolve(uid, ref component)) + return FixedPoint2.Zero; + + var ev = new GetHeavyDamageModifierEvent(uid, component.HeavyDamageModifier, 1, user); + RaiseLocalEvent(uid, ref ev); + + 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) @@ -388,7 +463,7 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem } // Windup time checked elsewhere. - var fireRate = TimeSpan.FromSeconds(1f / weapon.AttackRate); + var fireRate = TimeSpan.FromSeconds(1f / GetAttackRate(weaponUid, user, weapon)); var swings = 0; // TODO: If we get autoattacks then probably need a shotcounter like guns so we can do timing properly. @@ -451,7 +526,7 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem /// /// When an attack is released get the actual modifier for damage done. /// - public float GetModifier(MeleeWeaponComponent component, bool lightAttack) + public float GetModifier(EntityUid uid, EntityUid user, MeleeWeaponComponent component, bool lightAttack) { if (lightAttack) return 1f; @@ -461,7 +536,7 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem return 0f; var releaseTime = (Timing.CurTime - windup.Value).TotalSeconds; - var windupTime = component.WindupTime.TotalSeconds; + var windupTime = GetWindupTime(uid, user, component).TotalSeconds; // Wraps around back to 0 releaseTime %= (2 * windupTime); @@ -479,14 +554,14 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem fraction = 0; DebugTools.Assert(fraction <= 1); - return (float) fraction * component.HeavyDamageModifier.Float(); + 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, component) * GetModifier(component, true); + var damage = GetDamage(meleeUid, user, component) * GetModifier(meleeUid, user, component, true); // For consistency with wide attacks stuff needs damageable. if (Deleted(ev.Target) || @@ -560,7 +635,7 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem { Audio.PlayPredicted(hitEvent.HitSoundOverride, meleeUid, user); } - else if (GetDamage(meleeUid, component).Total.Equals(FixedPoint2.Zero) && component.HitSound != null) + else if (GetDamage(meleeUid, user, component).Total.Equals(FixedPoint2.Zero) && component.HitSound != null) { Audio.PlayPredicted(component.HitSound, meleeUid, user); } @@ -593,7 +668,7 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem var direction = targetMap.Position - userPos; var distance = Math.Min(component.Range, direction.Length); - var damage = GetDamage(meleeUid, component) * GetModifier(component, false); + var damage = GetDamage(meleeUid, user, component) * GetModifier(meleeUid, user, component, false); var entities = ev.Entities; if (entities.Count == 0)