Skip to content

Commit

Permalink
Melee Pt2 (#693)
Browse files Browse the repository at this point in the history
# PT2 of Melee Weapons The Numbers Don't Lie

This is part 2 of the ongoing work of Solid and myself going through and
touching up the melee combat in the game. In this part I rebalance all
of the melee weapons to generally do less damage, more stamina damage,
and be more unique in regards to slight range changes, attack speed
adjustments, along with every weapon getting slightly adjusted heavy
swing changes ranging from attack rates, damage, range, angle, and how
many targets you can hit.

Majority of weapons will hit the standard amount of targets of 5(the old
norm), but a few are lowered to be single target hits. These are usually
tightened in the angle that they attack in(old angle range was 60).
Similarly all melee weapons have individual stamina costs on their heavy
swings, most of these are in the range of 5 or 10, and following this PR
the new standard should be 10 as the outliers that would abuse this have
been addressed in this PR.

---

# Changelog

Normally I would do a changelog but this took awhile and I forgo. 

:cl: ODJ
- tweak: Melee Weapons now feel different across the board, from the
Wrench to the Chainsaw, try out their normal swings and their heavy
attacks!

---------

Co-authored-by: VMSolidus <[email protected]>
Co-authored-by: jcsmithing <[email protected]>
  • Loading branch information
3 people authored Aug 10, 2024
1 parent 05364c5 commit c8c859a
Show file tree
Hide file tree
Showing 39 changed files with 675 additions and 212 deletions.
5 changes: 2 additions & 3 deletions Content.Client/Weapons/Melee/MeleeWeaponSystem.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Linq;
using Content.Client.Gameplay;
using Content.Shared.CCVar;
using Content.Shared.CombatMode;
using Content.Shared.Effects;
using Content.Shared.Hands.Components;
Expand All @@ -16,8 +17,6 @@
using Robust.Shared.Input;
using Robust.Shared.Map;
using Robust.Shared.Player;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;

namespace Content.Client.Weapons.Melee;

Expand Down Expand Up @@ -228,7 +227,7 @@ private void ClientHeavyAttack(EntityUid user, EntityCoordinates coordinates, En
// This should really be improved. GetEntitiesInArc uses pos instead of bounding boxes.
// Server will validate it with InRangeUnobstructed.
var entities = GetNetEntityList(ArcRayCast(userPos, direction.ToWorldAngle(), component.Angle, distance, userXform.MapID, user).ToList());
RaisePredictiveEvent(new HeavyAttackEvent(GetNetEntity(meleeUid), entities.GetRange(0, Math.Min(MaxTargets, entities.Count)), GetNetCoordinates(coordinates)));
RaisePredictiveEvent(new HeavyAttackEvent(GetNetEntity(meleeUid), entities.GetRange(0, Math.Min(component.MaxTargets, entities.Count)), GetNetCoordinates(coordinates)));
}

private void OnMeleeLunge(MeleeLungeEvent ev)
Expand Down
2 changes: 1 addition & 1 deletion Content.Server/Execution/ExecutionSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ private void OnDoafterMelee(EntityUid uid, SharpComponent component, DoAfterEven
return;

_damageableSystem.TryChangeDamage(victim, melee.Damage * DamageModifier, true);
_audioSystem.PlayEntity(melee.HitSound, Filter.Pvs(weapon), weapon, true, AudioParams.Default);
_audioSystem.PlayEntity(melee.SoundHit, Filter.Pvs(weapon), weapon, true, AudioParams.Default);

if (attacker == victim)
{
Expand Down
11 changes: 9 additions & 2 deletions Content.Server/Weapons/Melee/MeleeWeaponSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ private void OnMeleeExamineDamage(EntityUid uid, MeleeWeaponComponent component,
return;

_damageExamine.AddDamageExamine(args.Message, damageSpec, Loc.GetString("damage-melee"));

if (damageSpec * component.HeavyDamageBaseModifier != damageSpec)
_damageExamine.AddDamageExamine(args.Message, damageSpec * component.HeavyDamageBaseModifier, Loc.GetString("damage-melee-heavy"));
}

protected override bool ArcRaySuccessful(EntityUid targetUid, Vector2 position, Angle angle, Angle arcWidth, float range, MapId mapId,
Expand Down Expand Up @@ -132,7 +135,7 @@ protected override bool DoDisarm(EntityUid user, DisarmAttackEvent ev, EntityUid
if (attemptEvent.Cancelled)
return false;

var chance = CalculateDisarmChance(user, target, inTargetHand, combatMode) * _contests.MassContest(user, target);
var chance = CalculateDisarmChance(user, target, inTargetHand, combatMode);

if (_random.Prob(chance))
{
Expand Down Expand Up @@ -212,7 +215,11 @@ private float CalculateDisarmChance(EntityUid disarmer, EntityUid disarmed, Enti
chance += malus.Malus;
}

return Math.Clamp(chance, 0f, 1f);
return Math.Clamp(chance
* _contests.MassContest(disarmer, disarmed, false, 0.5f)
* _contests.StaminaContest(disarmer, disarmed, false, 0.5f)
* _contests.HealthContest(disarmer, disarmed, false, 0.5f),
0f, 1f);
}

public override void DoLunge(EntityUid user, EntityUid weapon, Angle angle, Vector2 localPos, string? animation, bool predicted = true)
Expand Down
2 changes: 1 addition & 1 deletion Content.Server/Zombies/ZombieSystem.Transform.cs
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ public void ZombifyEntity(EntityUid target, MobStateComponent? mobState = null)
melee.AltDisarm = false;
melee.Range = 1.2f;
melee.Angle = 0.0f;
melee.HitSound = zombiecomp.BiteSound;
melee.SoundHit = zombiecomp.BiteSound;

if (mobState.CurrentState == MobState.Alive)
{
Expand Down
3 changes: 2 additions & 1 deletion Content.Shared/Damage/Systems/StaminaSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,8 @@ public bool TryTakeStamina(EntityUid uid, float value, StaminaComponent? compone
public void TakeStaminaDamage(EntityUid uid, float value, StaminaComponent? component = null,
EntityUid? source = null, EntityUid? with = null, bool visual = true, SoundSpecifier? sound = null)
{
if (!Resolve(uid, ref component, false))
if (!Resolve(uid, ref component, false)
|| value == 0)
return;

var ev = new BeforeStaminaDamageEvent(value);
Expand Down
6 changes: 3 additions & 3 deletions Content.Shared/Weapons/Melee/MeleeSoundSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public sealed class MeleeSoundSystem : EntitySystem
/// </summary>
public void PlaySwingSound(EntityUid userUid, EntityUid weaponUid, MeleeWeaponComponent weaponComponent)
{
_audio.PlayPredicted(weaponComponent.SwingSound, weaponUid, userUid);
_audio.PlayPredicted(weaponComponent.SoundSwing, weaponUid, userUid);
}

/// <summary>
Expand All @@ -32,8 +32,8 @@ public void PlaySwingSound(EntityUid userUid, EntityUid weaponUid, MeleeWeaponCo
/// <param name="hitSoundOverride"> A sound can be supplied by the <see cref="MeleeHitEvent"/> itself to override everything else </param>
public void PlayHitSound(EntityUid targetUid, EntityUid? userUid, string? damageType, SoundSpecifier? hitSoundOverride, MeleeWeaponComponent weaponComponent)
{
var hitSound = weaponComponent.HitSound;
var noDamageSound = weaponComponent.NoDamageSound;
var hitSound = weaponComponent.SoundHit;
var noDamageSound = weaponComponent.SoundNoDamage;

var playedSound = false;

Expand Down
71 changes: 44 additions & 27 deletions Content.Shared/Weapons/Melee/MeleeWeaponComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,28 +18,26 @@ public sealed partial class MeleeWeaponComponent : Component
/// <summary>
/// Does this entity do a disarm on alt attack.
/// </summary>
[DataField, ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
[DataField, AutoNetworkedField]
public bool AltDisarm = true;

/// <summary>
/// Should the melee weapon's damage stats be examinable.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[DataField]
public bool Hidden;

/// <summary>
/// Next time this component is allowed to light attack. Heavy attacks are wound up and never have a cooldown.
/// </summary>
[DataField(customTypeSerializer: typeof(TimeOffsetSerializer)), AutoNetworkedField]
[ViewVariables(VVAccess.ReadWrite)]
[AutoPausedField]
public TimeSpan NextAttack;

/// <summary>
/// Starts attack cooldown when equipped if true.
/// </summary>
[ViewVariables(VVAccess.ReadWrite), DataField]
[DataField]
public bool ResetOnHandSelected = true;

/*
Expand All @@ -51,77 +49,98 @@ public sealed partial class MeleeWeaponComponent : Component
/// <summary>
/// How many times we can attack per second.
/// </summary>
[ViewVariables(VVAccess.ReadWrite), DataField, AutoNetworkedField]
[DataField, AutoNetworkedField]
public float AttackRate = 1f;

/// <summary>
/// When power attacking, the swing speed (in attacks per second) is multiplied by this amount
/// </summary>
[DataField, AutoNetworkedField]
public float HeavyRateModifier = 0.8f;
/// <summary>
/// Are we currently holding down the mouse for an attack.
/// Used so we can't just hold the mouse button and attack constantly.
/// </summary>
[ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
[AutoNetworkedField]
public bool Attacking = false;

/// <summary>
/// If true, attacks will be repeated automatically without requiring the mouse button to be lifted.
/// </summary>
[DataField, ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
[DataField, AutoNetworkedField]
public bool AutoAttack;

/// <summary>
/// Base damage for this weapon. Can be modified via heavy damage or other means.
/// </summary>
[DataField(required: true)]
[ViewVariables(VVAccess.ReadWrite), AutoNetworkedField]
[AutoNetworkedField]
public DamageSpecifier Damage = default!;

[DataField]
[ViewVariables(VVAccess.ReadWrite)]
public FixedPoint2 BluntStaminaDamageFactor = FixedPoint2.New(0.5f);
[DataField, AutoNetworkedField]
public FixedPoint2 BluntStaminaDamageFactor = FixedPoint2.New(1f);

/// <summary>
/// Multiplies damage by this amount for single-target attacks.
/// </summary>
[ViewVariables(VVAccess.ReadWrite), DataField]
[DataField, AutoNetworkedField]
public FixedPoint2 ClickDamageModifier = FixedPoint2.New(1);

// TODO: Temporarily 1.5 until interactionoutline is adjusted to use melee, then probably drop to 1.2
/// <summary>
/// Nearest edge range to hit an entity.
/// </summary>
[ViewVariables(VVAccess.ReadWrite), DataField, AutoNetworkedField]
[DataField, AutoNetworkedField]
public float Range = 1.5f;

/// <summary>
/// Attack range for heavy swings
/// </summary>
[DataField, AutoNetworkedField]
public float HeavyRangeModifier = 1f;

/// <summary>
/// Weapon damage is multiplied by this amount for heavy swings
/// </summary>
[DataField, AutoNetworkedField]
public float HeavyDamageBaseModifier = 1.2f;

/// <summary>
/// Total width of the angle for wide attacks.
/// </summary>
[ViewVariables(VVAccess.ReadWrite), DataField]
[DataField, AutoNetworkedField]
public Angle Angle = Angle.FromDegrees(60);

[ViewVariables(VVAccess.ReadWrite), DataField, AutoNetworkedField]
[DataField, AutoNetworkedField]
public EntProtoId Animation = "WeaponArcPunch";

[ViewVariables(VVAccess.ReadWrite), DataField, AutoNetworkedField]
[DataField, AutoNetworkedField]
public EntProtoId WideAnimation = "WeaponArcSlash";

/// <summary>
/// Rotation of the animation.
/// 0 degrees means the top faces the attacker.
/// </summary>
[ViewVariables(VVAccess.ReadWrite), DataField]
[DataField, AutoNetworkedField]
public Angle WideAnimationRotation = Angle.Zero;

[ViewVariables(VVAccess.ReadWrite), DataField]
[DataField]
public bool SwingLeft;

[DataField, AutoNetworkedField]
public float HeavyStaminaCost = 20f;

[DataField, AutoNetworkedField]
public int MaxTargets = 5;


// Sounds

/// <summary>
/// This gets played whenever a melee attack is done. This is predicted by the client.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[DataField("soundSwing"), AutoNetworkedField]
public SoundSpecifier SwingSound { get; set; } = new SoundPathSpecifier("/Audio/Weapons/punchmiss.ogg")
[DataField, AutoNetworkedField]
public SoundSpecifier SoundSwing { get; set; } = new SoundPathSpecifier("/Audio/Weapons/punchmiss.ogg")
{
Params = AudioParams.Default.WithVolume(-3f).WithVariation(0.025f),
};
Expand All @@ -130,16 +149,14 @@ public sealed partial class MeleeWeaponComponent : Component
// then a player may doubt if the target actually took damage or not.
// If overwatch and apex do this then we probably should too.

[ViewVariables(VVAccess.ReadWrite)]
[DataField("soundHit"), AutoNetworkedField]
public SoundSpecifier? HitSound;
[DataField, AutoNetworkedField]
public SoundSpecifier? SoundHit;

/// <summary>
/// Plays if no damage is done to the target entity.
/// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[DataField("soundNoDamage"), AutoNetworkedField]
public SoundSpecifier NoDamageSound { get; set; } = new SoundCollectionSpecifier("WeakHit");
[DataField, AutoNetworkedField]
public SoundSpecifier SoundNoDamage { get; set; } = new SoundCollectionSpecifier("WeakHit");
}

/// <summary>
Expand Down
Loading

0 comments on commit c8c859a

Please sign in to comment.