From 6c43d005e0804fe29007371a46a56e27bccbd4f8 Mon Sep 17 00:00:00 2001 From: Skubman Date: Fri, 17 Jan 2025 11:16:13 +0800 Subject: [PATCH] Martial Artist Rework + Fix MeleeWeapon Wizmerge Bugs (#1560) # Description Reworks the Martial Artist trait, making it a more impactful and visually distinct trait. - Left-clicks are now single-target power attacks, requiring less aim than before. - **50%** damage bonus reduced to **20%** damage bonus (same overall DPS with next change). - Gain **25%** attack rate bonus. - The attack rate bonus helps make the trait feel and look distinct from non-Martial Artist melee attacks. - **50%** range bonus reduced to **10%** range bonus. - The damage bonus is now also applied to Asphyxiation and Poison damage, which Lamia unarmed attacks deal. - Trait cost increased from **-3** points to **-5** points. - Striking Calluses (which requires Martial Artist) cost reduced from **-4** points to **-3** points to prevent the Martial Artist/Striking Calluses combo from being too expensive. The combo used to cost **-7** points, now it is **-8** points. The reworked Martial Artist trait is also given to the Boxers, Martial Artists and Gladiators. Also reverted some wizmerge messery that messed up the melee attack rate **again**, and messed up pistol whipping by making the cooldowns of gunshots and melee attacks intertwined. Also while we're at it, Natural Weapons Removal has been disabled for all species whose damage is pure Blunt, including Diona, Dwarf, Arachne and IPC. (IPC have 6 blunt so the trait would literally be a 0-point negative trait for them) ## Technical Details A new trait function has been added for Martial Artist: `TraitModifyUnarmed`, which modifies the player entity's `MeleeWeaponComponent`. The Claws, Talons, Natural Weapon Removal, and Striking Calluses traits have also been refactored under the hood to use `TraitModifyUnarmed`, instead of replacing `MeleeWeaponComponent` which would wipe out all the changes made by the Martial Artist trait. ## Media ### New Description ![martialartist](https://github.com/user-attachments/assets/de51530f-5f2a-41a8-b686-c12f746b5e4a) ### Martial Artist In Action https://github.com/user-attachments/assets/7890aac2-2c74-4abd-be9f-b28c2922c865 ### Striking Calluses New Description ![strikingcalluses](https://github.com/user-attachments/assets/bc15425e-3460-49f2-88f5-840c686e0053) ### Natural Weapons Removal New Description ![naturalweaponsremoval](https://github.com/user-attachments/assets/583adaa4-48c0-46e3-882b-1bb0f470018c) # Changelog :cl: Skubman - add: Martial Artist Rework: Martial Artist now costs 5 points, but it turns all unarmed melee attacks into single-target power attacks, with 20% bonus damage, 25% bonus attack rate and 10% bonus attack range. - tweak: The reworked Martial Artist trait is now given for free to Boxers, Martial Artists, and Gladiators. - tweak: Martial Artists (the job) and Gladiators can now select the Striking Calluses trait. - tweak: The Martial Artist trait now applies bonus damage to the Lamiae's unarmed Asphyxiation and Poison damage. - tweak: The cost of Striking Calluses has been reduced from 4 points to 3 points. - fix: Fixed a bug where slow weapons were fast and fast weapons were slow. - fix: You can pistol whip (right-click melee) immediately after firing a gun again, and the cooldown on firing the gun after pistol whipping is always 0.528 seconds again. - fix: Prevented Dionas, Arachnae and IPCs, who all have pure Blunt damage from selecting the redundant Natural Weapons Removal trait. --- .../Weapons/Melee/MeleeWeaponSystem.cs | 9 ++ .../Abilities/Boxer/Boxer/BoxerComponent.cs | 2 +- .../Traits/TraitSystem.Functions.cs | 90 ++++++++++++++++ .../Weapons/Melee/MeleeWeaponComponent.cs | 6 ++ .../Weapons/Melee/SharedMeleeWeaponSystem.cs | 43 +++----- Resources/Locale/en-US/traits/traits.ftl | 8 +- .../Entities/Mobs/Player/silicon_base.yml | 5 + .../Prototypes/Entities/Mobs/Species/base.yml | 5 + .../Roles/Jobs/Wildcards/gladiator.yml | 11 +- .../Roles/Jobs/Wildcards/martialartist.yml | 11 +- .../Prototypes/Roles/Jobs/Wildcards/boxer.yml | 11 +- Resources/Prototypes/Traits/physical.yml | 100 +++++++++--------- 12 files changed, 199 insertions(+), 102 deletions(-) diff --git a/Content.Client/Weapons/Melee/MeleeWeaponSystem.cs b/Content.Client/Weapons/Melee/MeleeWeaponSystem.cs index d86172de718..6b5ddedd59e 100644 --- a/Content.Client/Weapons/Melee/MeleeWeaponSystem.cs +++ b/Content.Client/Weapons/Melee/MeleeWeaponSystem.cs @@ -151,6 +151,9 @@ public override void Update(float frameTime) if (mousePos.MapId != attackerPos.MapId || (attackerPos.Position - mousePos.Position).Length() > weapon.Range) { + if (weapon.HeavyOnLightMiss) + ClientHeavyAttack(entity, coordinates, weaponUid, weapon); + return; } @@ -165,6 +168,12 @@ public override void Update(float frameTime) if (Interaction.CombatModeCanHandInteract(entity, target)) return; + if (weapon.HeavyOnLightMiss && !CanDoLightAttack(entity, target, weapon, out _)) + { + ClientHeavyAttack(entity, coordinates, weaponUid, weapon); + return; + } + RaisePredictiveEvent(new LightAttackEvent(GetNetEntity(target), GetNetEntity(weaponUid), GetNetCoordinates(coordinates))); } } diff --git a/Content.Server/Nyanotrasen/Abilities/Boxer/Boxer/BoxerComponent.cs b/Content.Server/Nyanotrasen/Abilities/Boxer/Boxer/BoxerComponent.cs index b844e5e8f5a..41adfab116a 100644 --- a/Content.Server/Nyanotrasen/Abilities/Boxer/Boxer/BoxerComponent.cs +++ b/Content.Server/Nyanotrasen/Abilities/Boxer/Boxer/BoxerComponent.cs @@ -12,7 +12,7 @@ public sealed partial class BoxerComponent : Component public DamageModifierSet UnarmedModifiers = default!; [DataField("rangeBonus")] - public float RangeBonus = 1.5f; + public float RangeBonus = 1.0f; /// /// Damage modifier with boxing glove stam damage. diff --git a/Content.Server/Traits/TraitSystem.Functions.cs b/Content.Server/Traits/TraitSystem.Functions.cs index 29ab1512ae6..8306105b936 100644 --- a/Content.Server/Traits/TraitSystem.Functions.cs +++ b/Content.Server/Traits/TraitSystem.Functions.cs @@ -20,6 +20,8 @@ using Content.Shared.Mobs; using Content.Shared.Damage.Components; using Content.Shared.NPC.Systems; +using Content.Shared.Weapons.Melee; +using Robust.Shared.Audio; namespace Content.Server.Traits; @@ -585,3 +587,91 @@ public override void OnPlayerSpawn(EntityUid uid, slowOnDamage.SpeedModifierThresholds = newSpeedModifierThresholds; } } + +/// +/// Used for traits that modify unarmed damage on MeleeWeaponComponent. +/// +[UsedImplicitly] +public sealed partial class TraitModifyUnarmed : TraitFunction +{ + // + // The sound played on hitting targets. + // + [DataField, AlwaysPushInheritance] + public SoundSpecifier? SoundHit; + + // + // The animation to play on hit, for both light and power attacks. + // + [DataField, AlwaysPushInheritance] + public EntProtoId? Animation; + + // + // Whether to set the power attack animation to be the same as the light attack. + // + [DataField, AlwaysPushInheritance] + public bool HeavyAnimationFromLight = true; + + // + // The damage values of unarmed damage. + // + [DataField, AlwaysPushInheritance] + public DamageSpecifier? Damage; + + // + // Additional damage added to the existing damage. + // + [DataField, AlwaysPushInheritance] + public DamageSpecifier? FlatDamageIncrease; + + /// + /// Turns the left click into a power attack when the light attack misses. + /// + [DataField] + public bool? HeavyOnLightMiss; + + // + // What to multiply the melee weapon range by. + // + [DataField, AlwaysPushInheritance] + public float? RangeModifier; + + // + // What to multiply the attack rate by. + // + [DataField, AlwaysPushInheritance] + public float? AttackRateModifier; + + public override void OnPlayerSpawn(EntityUid uid, + IComponentFactory factory, + IEntityManager entityManager, + ISerializationManager serializationManager) + { + if (!entityManager.TryGetComponent(uid, out var melee)) + return; + + if (SoundHit != null) + melee.SoundHit = SoundHit; + + if (Animation != null) + melee.Animation = Animation.Value; + + if (HeavyAnimationFromLight) + melee.WideAnimation = melee.Animation; + + if (Damage != null) + melee.Damage = Damage; + + if (FlatDamageIncrease != null) + melee.Damage += FlatDamageIncrease; + + if (HeavyOnLightMiss != null) + melee.HeavyOnLightMiss = HeavyOnLightMiss.Value; + + if (RangeModifier != null) + melee.Range *= RangeModifier.Value; + + if (AttackRateModifier != null) + melee.AttackRate *= AttackRateModifier.Value; + } +} diff --git a/Content.Shared/Weapons/Melee/MeleeWeaponComponent.cs b/Content.Shared/Weapons/Melee/MeleeWeaponComponent.cs index 851699c9815..e25328ada61 100644 --- a/Content.Shared/Weapons/Melee/MeleeWeaponComponent.cs +++ b/Content.Shared/Weapons/Melee/MeleeWeaponComponent.cs @@ -61,6 +61,12 @@ public sealed partial class MeleeWeaponComponent : Component [DataField] public bool DisableClick = false; + /// + /// If true, when a light attack misses, the weapon will perform a power attack instead. + /// + [DataField, AutoNetworkedField] + public bool HeavyOnLightMiss = false; + /* * Melee combat works based around 2 types of attacks: * 1. Click attacks with left-click. This attacks whatever is under your mnouse diff --git a/Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs b/Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs index edca18e5f76..201269fcc86 100644 --- a/Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs +++ b/Content.Shared/Weapons/Melee/SharedMeleeWeaponSystem.cs @@ -68,8 +68,6 @@ public override void Initialize() base.Initialize(); SubscribeLocalEvent(OnMeleeSelected); - SubscribeLocalEvent(OnMeleeShotAttempted); - SubscribeLocalEvent(OnMeleeShot); SubscribeLocalEvent(OnGetBonusMeleeDamage); SubscribeLocalEvent(OnGetBonusHeavyDamageModifier); SubscribeLocalEvent(OnGetBonusMeleeAttackRate); @@ -93,24 +91,6 @@ private void OnMapInit(EntityUid uid, MeleeWeaponComponent component, MapInitEve #endif } - private void OnMeleeShotAttempted(EntityUid uid, MeleeWeaponComponent comp, ref ShotAttemptedEvent args) - { - if (comp.NextAttack > Timing.CurTime) - args.Cancel(); - } - - private void OnMeleeShot(EntityUid uid, MeleeWeaponComponent component, ref GunShotEvent args) - { - if (!TryComp(uid, out var gun)) - return; - - if (gun.NextFire > component.NextAttack) - { - component.NextAttack = gun.NextFire; - DirtyField(uid, component, nameof(MeleeWeaponComponent.NextAttack)); - } - } - private void OnMeleeSelected(EntityUid uid, MeleeWeaponComponent component, HandSelectedEvent args) { var attackRate = GetAttackRate(uid, args.User, component); @@ -351,6 +331,8 @@ private bool AttemptAttack(EntityUid user, EntityUid weaponUid, MeleeWeaponCompo if (!CombatMode.IsInCombatMode(user)) return false; + var fireRateSwingModifier = 1f; + EntityUid? target = null; switch (attack) { @@ -368,6 +350,9 @@ private bool AttemptAttack(EntityUid user, EntityUid weaponUid, MeleeWeaponCompo if (weaponUid == target) return false; + break; + case HeavyAttackEvent: + fireRateSwingModifier = weapon.HeavyRateModifier; break; case DisarmAttackEvent disarm: if (disarm.Target != null && !TryGetEntity(disarm.Target, out target)) @@ -386,7 +371,7 @@ private bool AttemptAttack(EntityUid user, EntityUid weaponUid, MeleeWeaponCompo } // Windup time checked elsewhere. - var fireRate = TimeSpan.FromSeconds(1f / GetAttackRate(weaponUid, user, weapon)); + var fireRate = TimeSpan.FromSeconds(GetAttackRate(weaponUid, user, weapon) * fireRateSwingModifier); var swings = 0; // TODO: If we get autoattacks then probably need a shotcounter like guns so we can do timing properly. @@ -457,6 +442,16 @@ private bool AttemptAttack(EntityUid user, EntityUid weaponUid, MeleeWeaponCompo protected abstract bool InRange(EntityUid user, EntityUid target, float range, ICommonSession? session); + protected bool CanDoLightAttack(EntityUid user, [NotNullWhen(true)] EntityUid? target, MeleeWeaponComponent component, [NotNullWhen(true)] out TransformComponent? targetXform, ICommonSession? session = null) + { + targetXform = null; + return !Deleted(target) && + HasComp(target) && + TryComp(target, out targetXform) && + // Not in LOS. + InRange(user, target.Value, component.Range, session); + } + protected virtual void DoLightAttack(EntityUid user, LightAttackEvent ev, EntityUid meleeUid, MeleeWeaponComponent component, ICommonSession? session) { // If I do not come back later to fix Light Attacks being Heavy Attacks you can throw me in the spider pit -Errant @@ -465,11 +460,7 @@ protected virtual void DoLightAttack(EntityUid user, LightAttackEvent ev, Entity var resistanceBypass = GetResistanceBypass(meleeUid, user, component); // For consistency with wide attacks stuff needs damageable. - if (Deleted(target) || - !HasComp(target) || - !TryComp(target, out TransformComponent? targetXform) || - // Not in LOS. - !InRange(user, target.Value, component.Range, session)) + if (!CanDoLightAttack(user, target, component, out var targetXform, session)) { // Leave IsHit set to true, because the only time it's set to false // is when a melee weapon is examined. Misses are inferred from an diff --git a/Resources/Locale/en-US/traits/traits.ftl b/Resources/Locale/en-US/traits/traits.ftl index 592e8b32bcf..7dc82335181 100644 --- a/Resources/Locale/en-US/traits/traits.ftl +++ b/Resources/Locale/en-US/traits/traits.ftl @@ -179,9 +179,11 @@ trait-description-Feeble = trait-name-MartialArtist = Martial Artist trait-description-MartialArtist = - You have received formal training in unarmed combat, whether with Fists, Feet, or Claws. - Your unarmed melee attacks have a small range increase, and deal 50% more damage. - This does not apply to any form of armed melee, only the weapons you were naturally born with. + You have received formal training in unarmed combat, whether with fists, claws, feet, or teeth. + Your unarmed melee attack is now considered a single-target [color=orange]Power Attack[/color], requiring less precision. + Additionally, your unarmed melee attacks deal [color=yellow]20%[/color] more damage, attack [color=yellow]25%[/color] faster, and have [color=yellow]10%[/color] increased range. + This has no effect on damage dealt with any form of armed melee. + The [color=#9FED58]Boxer[/color], [color=#9FED58]Martial Artist[/color], and [color=#9FED58]Gladiator[/color] jobs start with this trait by default. trait-name-Vigor = Vigor trait-description-Vigor = diff --git a/Resources/Prototypes/Entities/Mobs/Player/silicon_base.yml b/Resources/Prototypes/Entities/Mobs/Player/silicon_base.yml index 532c28b0116..7cdc46f350c 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/silicon_base.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/silicon_base.yml @@ -256,6 +256,11 @@ damage: types: Blunt: 6 # It's tough. + heavyRateModifier: 1 + heavyDamageBaseModifier: 1 + heavyPartDamageMultiplier: 1 + heavyStaminaCost: 0 + maxTargets: 1 - type: MobPrice price: 1500 # Kidnapping a living person and selling them for cred is a good move. deathPenalty: 0.01 # However they really ought to be living and intact, otherwise they're worth 100x less. diff --git a/Resources/Prototypes/Entities/Mobs/Species/base.yml b/Resources/Prototypes/Entities/Mobs/Species/base.yml index 46224a3cb33..bb27e46ba47 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/base.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/base.yml @@ -179,6 +179,11 @@ damage: types: Blunt: 5 + heavyRateModifier: 1 + heavyDamageBaseModifier: 1 + heavyPartDamageMultiplier: 1 + heavyStaminaCost: 0 + maxTargets: 1 - type: SleepEmitSound - type: SSDIndicator - type: StandingState diff --git a/Resources/Prototypes/Nyanotrasen/Roles/Jobs/Wildcards/gladiator.yml b/Resources/Prototypes/Nyanotrasen/Roles/Jobs/Wildcards/gladiator.yml index 3651d223d77..79ae853f9f1 100644 --- a/Resources/Prototypes/Nyanotrasen/Roles/Jobs/Wildcards/gladiator.yml +++ b/Resources/Prototypes/Nyanotrasen/Roles/Jobs/Wildcards/gladiator.yml @@ -15,14 +15,9 @@ department: Security min: 21600 special: - - !type:AddComponentSpecial - components: - - type: Boxer - modifiers: - coefficients: # These only apply to unarmed - Blunt: 1.5 - Slash: 1.5 - Piercing: 1.5 + - !type:AddTraitSpecial + traits: + - MartialArtist - type: startingGear id: NyanoGladiatorGear diff --git a/Resources/Prototypes/Nyanotrasen/Roles/Jobs/Wildcards/martialartist.yml b/Resources/Prototypes/Nyanotrasen/Roles/Jobs/Wildcards/martialartist.yml index 8c3c80c72fd..de6212dacf9 100644 --- a/Resources/Prototypes/Nyanotrasen/Roles/Jobs/Wildcards/martialartist.yml +++ b/Resources/Prototypes/Nyanotrasen/Roles/Jobs/Wildcards/martialartist.yml @@ -16,14 +16,9 @@ - Theatre # DeltaV - Add Theatre access - Boxer # DeltaV - Add Boxer access special: - - !type:AddComponentSpecial - components: - - type: Boxer - modifiers: - coefficients: # These only apply to unarmed - Blunt: 1.5 - Slash: 1.5 - Piercing: 1.5 + - !type:AddTraitSpecial + traits: + - MartialArtist - type: startingGear id: MartialArtistGear diff --git a/Resources/Prototypes/Roles/Jobs/Wildcards/boxer.yml b/Resources/Prototypes/Roles/Jobs/Wildcards/boxer.yml index 33def38bb08..65739809da0 100644 --- a/Resources/Prototypes/Roles/Jobs/Wildcards/boxer.yml +++ b/Resources/Prototypes/Roles/Jobs/Wildcards/boxer.yml @@ -14,14 +14,9 @@ - Theatre # DeltaV - Add Theatre access - Boxer # DeltaV - Add Boxer access special: # Nyanotrasen - BoxerComponent, see Content.Server/Nyanotrasen/Abilities/Boxer/Boxer/BoxerComponent.cs - - !type:AddComponentSpecial - components: - - type: Boxer - modifiers: - coefficients: # These only apply to unarmed - Blunt: 1.5 - Slash: 1.5 - Piercing: 1.5 + - !type:AddTraitSpecial + traits: + - MartialArtist - type: startingGear id: BoxerGear diff --git a/Resources/Prototypes/Traits/physical.yml b/Resources/Prototypes/Traits/physical.yml index 81ae6715d77..77ea7871f50 100644 --- a/Resources/Prototypes/Traits/physical.yml +++ b/Resources/Prototypes/Traits/physical.yml @@ -284,7 +284,7 @@ - type: trait id: MartialArtist category: Physical - points: -3 + points: -5 requirements: - !type:CharacterJobRequirement inverted: true @@ -292,15 +292,26 @@ - Borg - MedicalBorg - Boxer + - MartialArtist + - Gladiator functions: + - !type:TraitModifyUnarmed + heavyOnLightMiss: true + attackRateModifier: 0.8 + rangeModifier: 1.1 - !type:TraitReplaceComponent - components: - - type: Boxer + components: # Keep BoxerComponent for now until we have After/Before on traits prototypes + - type: Boxer # for potential conflicts with other traits that replace unarmed damage modifiers: coefficients: - Blunt: 1.5 - Slash: 1.5 - Piercing: 1.5 + Blunt: 1.2 + Slash: 1.2 + Piercing: 1.2 + Poison: 1.2 + Asphyxiation: 1.2 + # An attack rate of 1.25 hits per second (1 / 0.8 = 1.25) multiplied by 20% extra damage + # effectively means 50% more overall DPS, same DPS bonus as before (1 * 1.25 * 1.2 = 1.5) + # but the extra attack rate makes it visually apparent that it's Martial Artist. - type: trait id: Small @@ -358,16 +369,14 @@ traits: - Claws functions: - - !type:TraitReplaceComponent - components: - - type: MeleeWeapon - soundHit: - collection: AlienClaw - animation: WeaponArcClaw - damage: - types: - Piercing: 5 # No, this isn't "OP", this is literally the worst brute damage type in the game. - # Same deal as Slash, except that a majority of all armor provides Piercing resistance. + - !type:TraitModifyUnarmed + soundHit: + collection: AlienClaw + animation: WeaponArcClaw + damage: + types: + Piercing: 5 # No, this isn't "OP", this is literally the worst brute damage type in the game. + # Same deal as Slash, except that a majority of all armor provides Piercing resistance. - type: trait id: Claws @@ -386,17 +395,14 @@ traits: - Talons functions: - - !type:TraitReplaceComponent - components: - - type: MeleeWeapon - soundHit: - collection: AlienClaw - angle: 30 - animation: WeaponArcClaw - damage: - types: - Slash: 5 # Trade stamina damage on hit for a very minor amount of extra bleed. - # Blunt also deals bleed damage, so this is more of a sidegrade. + - !type:TraitModifyUnarmed + soundHit: + collection: AlienClaw + animation: WeaponArcClaw + damage: + types: + Slash: 5 # Trade stamina damage on hit for a very minor amount of extra bleed. + # Blunt also deals bleed damage, so this is more of a sidegrade. - type: trait id: NaturalWeaponRemoval @@ -409,27 +415,29 @@ - Human - Oni - SlimePerson + - Diona + - Dwarf + - Arachne + - IPC # Other species could justify getting this trait for Blunt stamina damage, + # but 6 blunt -> 5 blunt is a straight up downgrade. - !type:CharacterTraitRequirement inverted: true traits: - Talons - Claws functions: - - !type:TraitReplaceComponent - components: - - type: MeleeWeapon - soundHit: - collection: Punch - angle: 30 - animation: WeaponArcFist - damage: - types: - Blunt: 5 + - !type:TraitModifyUnarmed + soundHit: + collection: Punch + animation: WeaponArcFist + damage: + types: + Blunt: 5 - type: trait id: StrikingCalluses category: Physical - points: -4 + points: -3 requirements: - !type:CharacterJobRequirement inverted: true @@ -452,17 +460,13 @@ - !type:CharacterJobRequirement jobs: - Boxer + - MartialArtist + - Gladiator functions: - - !type:TraitReplaceComponent - components: - - type: MeleeWeapon - soundHit: - collection: Punch - angle: 30 - animation: WeaponArcFist - damage: - types: - Blunt: 6 + - !type:TraitModifyUnarmed + flatDamageIncrease: + types: + Blunt: 1 - type: trait id: Spinarette