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