mirror of
https://github.com/space-syndicate/space-station-14.git
synced 2026-10-09 10:21:18 +03:00
Hotfix: Fix melee wideswing damage exploit (#46299)
* Initial commit * TrimExcess to Take
This commit is contained in:
parent
4afa06d5b4
commit
94087a918a
9 changed files with 46 additions and 56 deletions
|
|
@ -159,10 +159,10 @@ public sealed partial class MeleeWeaponSystem : SharedMeleeWeaponSystem
|
|||
return Interaction.InRangeUnobstructed(user, target, targetCoordinates, targetLocalAngle, range, overlapCheck: false);
|
||||
}
|
||||
|
||||
protected override void DoDamageEffect(List<EntityUid> targets, EntityUid? user, TransformComponent targetXform)
|
||||
protected override void DoDamageEffect(HashSet<EntityUid> targets, EntityUid? user, TransformComponent targetXform)
|
||||
{
|
||||
// Server never sends the event to us for predictiveeevent.
|
||||
_color.RaiseEffect(Color.Red, targets, Filter.Local());
|
||||
_color.RaiseEffect(Color.Red, targets.ToList(), Filter.Local());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -189,8 +189,9 @@ public sealed partial class MeleeWeaponSystem : SharedMeleeWeaponSystem
|
|||
|
||||
// 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)));
|
||||
var entities = GetNetEntitySet(ArcRayCast(userPos, direction.ToWorldAngle(), component.Angle, distance, userXform.MapID, user));
|
||||
entities = entities.Take(Math.Min(MaxTargets, entities.Count)).ToHashSet();
|
||||
RaisePredictiveEvent(new HeavyAttackEvent(GetNetEntity(meleeUid), entities, GetNetCoordinates(coordinates)));
|
||||
}
|
||||
|
||||
private void ClientDisarm(EntityUid attacker, MapCoordinates mousePos, EntityCoordinates coordinates)
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ public sealed partial class SolutionInjectOnCollideSystem : EntitySystem
|
|||
|
||||
private void DoInjection(Entity<BaseSolutionInjectOnEventComponent> injectorEntity, EntityUid target, EntityUid? source = null)
|
||||
{
|
||||
TryInjectTargets(injectorEntity, [target], source);
|
||||
TryInjectTargets(injectorEntity, new HashSet<EntityUid>() { target }, source);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -79,7 +79,7 @@ public sealed partial class SolutionInjectOnCollideSystem : EntitySystem
|
|||
/// </list>
|
||||
/// </remarks>
|
||||
/// <returns>true if at least one target was successfully injected, otherwise false</returns>
|
||||
private bool TryInjectTargets(Entity<BaseSolutionInjectOnEventComponent> injector, IReadOnlyList<EntityUid> targets, EntityUid? user = null)
|
||||
private bool TryInjectTargets(Entity<BaseSolutionInjectOnEventComponent> injector, IReadOnlySet<EntityUid> targets, EntityUid? user = null)
|
||||
{
|
||||
// Make sure we have at least one target
|
||||
if (targets.Count == 0)
|
||||
|
|
|
|||
|
|
@ -68,10 +68,10 @@ public sealed partial class MeleeWeaponSystem : SharedMeleeWeaponSystem
|
|||
return Interaction.InRangeUnobstructed(user, target, range);
|
||||
}
|
||||
|
||||
protected override void DoDamageEffect(List<EntityUid> targets, EntityUid? user, TransformComponent targetXform)
|
||||
protected override void DoDamageEffect(HashSet<EntityUid> targets, EntityUid? user, TransformComponent targetXform)
|
||||
{
|
||||
var filter = Filter.Pvs(targetXform.Coordinates, entityMan: EntityManager).RemoveWhereAttachedEntity(o => o == user);
|
||||
_color.RaiseEffect(Color.Red, targets, filter);
|
||||
_color.RaiseEffect(Color.Red, targets.ToList(), filter);
|
||||
}
|
||||
|
||||
public override void DoLunge(EntityUid user, EntityUid weapon, Angle angle, Vector2 localPos, string? animation, bool predicted = true)
|
||||
|
|
|
|||
|
|
@ -100,10 +100,10 @@ public sealed partial class InjectorSystem : EntitySystem
|
|||
|
||||
private void OnAttack(Entity<InjectorComponent> injector, ref MeleeHitEvent args)
|
||||
{
|
||||
if (args.HitEntities is [])
|
||||
if (args.HitEntities.Count == 0)
|
||||
return;
|
||||
|
||||
TryMobsDoAfter(injector, args.User, args.HitEntities[0]);
|
||||
TryMobsDoAfter(injector, args.User, args.HitEntities.First());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ public abstract partial class SharedFlashSystem : EntitySystem
|
|||
Flash(target, args.User, ent.Owner, ent.Comp.MeleeDuration, ent.Comp.SlowTo, melee: true, stunDuration: ent.Comp.MeleeStunDuration);
|
||||
}
|
||||
|
||||
EntityUid? firstTarget = args.HitEntities.Count > 0 ? args.HitEntities[0] : null; // Just pick the first hit entity.
|
||||
EntityUid? firstTarget = args.HitEntities.Count > 0 ? args.HitEntities.First() : null; // Just pick the first hit entity.
|
||||
var ev = new AfterFlashActivatedEvent(firstTarget, args.User);
|
||||
RaiseLocalEvent(ent, ref ev);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
using System.Linq;
|
||||
using Content.Shared.Trigger.Components.Triggers;
|
||||
using Content.Shared.Weapons.Melee.Events;
|
||||
|
||||
|
|
@ -30,7 +31,7 @@ public sealed partial class TriggerOnMeleeTriggerSystem : TriggerOnXSystem
|
|||
if (args.HitEntities.Count == 0)
|
||||
target = ent.Comp.TargetIsUser ? null : args.User;
|
||||
else
|
||||
target = ent.Comp.TargetIsUser ? args.HitEntities[0] : args.User;
|
||||
target = ent.Comp.TargetIsUser ? args.HitEntities.First() : args.User;
|
||||
|
||||
Trigger.Trigger(ent.Owner, target, ent.Comp.KeyOut);
|
||||
}
|
||||
|
|
@ -42,7 +43,7 @@ public sealed partial class TriggerOnMeleeTriggerSystem : TriggerOnXSystem
|
|||
|
||||
if (!ent.Comp.TriggerEveryHit)
|
||||
{
|
||||
var target = ent.Comp.TargetIsUser ? args.HitEntities[0] : args.User;
|
||||
var target = ent.Comp.TargetIsUser ? args.HitEntities.First() : args.User;
|
||||
Trigger.Trigger(ent.Owner, target, ent.Comp.KeyOut);
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,9 +14,9 @@ public sealed class HeavyAttackEvent : AttackEvent
|
|||
/// <summary>
|
||||
/// As what the client swung at will not match server we'll have them tell us what they hit so we can verify.
|
||||
/// </summary>
|
||||
public List<NetEntity> Entities;
|
||||
public HashSet<NetEntity> Entities;
|
||||
|
||||
public HeavyAttackEvent(NetEntity weapon, List<NetEntity> entities, NetCoordinates coordinates) : base(coordinates)
|
||||
public HeavyAttackEvent(NetEntity weapon, HashSet<NetEntity> entities, NetCoordinates coordinates) : base(coordinates)
|
||||
{
|
||||
Weapon = weapon;
|
||||
Entities = entities;
|
||||
|
|
|
|||
|
|
@ -31,9 +31,9 @@ public sealed class MeleeHitEvent : HandledEntityEventArgs
|
|||
public DamageSpecifier BonusDamage = new();
|
||||
|
||||
/// <summary>
|
||||
/// A list containing every hit entity. Can be zero.
|
||||
/// A set containing every hit entity. Can be zero.
|
||||
/// </summary>
|
||||
public IReadOnlyList<EntityUid> HitEntities;
|
||||
public IReadOnlySet<EntityUid> HitEntities;
|
||||
|
||||
/// <summary>
|
||||
/// Used to define a new hit sound in case you want to override the default GenericHit.
|
||||
|
|
@ -66,7 +66,7 @@ public sealed class MeleeHitEvent : HandledEntityEventArgs
|
|||
/// </remarks>
|
||||
public bool IsHit = true;
|
||||
|
||||
public MeleeHitEvent(List<EntityUid> hitEntities, EntityUid user, EntityUid weapon, DamageSpecifier baseDamage, Vector2? direction)
|
||||
public MeleeHitEvent(HashSet<EntityUid> hitEntities, EntityUid user, EntityUid weapon, DamageSpecifier baseDamage, Vector2? direction)
|
||||
{
|
||||
HitEntities = hitEntities;
|
||||
User = user;
|
||||
|
|
|
|||
|
|
@ -527,7 +527,7 @@ public abstract partial class SharedMeleeWeaponSystem : EntitySystem
|
|||
LogImpact.Low,
|
||||
$"{ToPrettyString(user):actor} melee attacked (light) using {ToPrettyString(meleeUid):tool} and missed");
|
||||
}
|
||||
var missEvent = new MeleeHitEvent(new List<EntityUid>(), user, meleeUid, damage, null);
|
||||
var missEvent = new MeleeHitEvent(new HashSet<EntityUid>(), user, meleeUid, damage, null);
|
||||
RaiseLocalEvent(meleeUid, missEvent);
|
||||
_meleeSound.PlaySwingSound(user, meleeUid, component);
|
||||
return;
|
||||
|
|
@ -536,15 +536,15 @@ public abstract partial class SharedMeleeWeaponSystem : EntitySystem
|
|||
// Sawmill.Debug($"Melee damage is {damage.Total} out of {component.Damage.Total}");
|
||||
|
||||
// Raise event before doing damage so we can cancel damage if the event is handled
|
||||
var hitEvent = new MeleeHitEvent(new List<EntityUid> { target.Value }, user, meleeUid, damage, null);
|
||||
var hitEvent = new MeleeHitEvent(new HashSet<EntityUid> { target.Value }, user, meleeUid, damage, null);
|
||||
RaiseLocalEvent(meleeUid, hitEvent);
|
||||
|
||||
if (hitEvent.Handled)
|
||||
return;
|
||||
|
||||
var targets = new List<EntityUid>(1)
|
||||
var targets = new HashSet<EntityUid>(1)
|
||||
{
|
||||
target.Value
|
||||
target.Value,
|
||||
};
|
||||
|
||||
var weapon = GetEntity(ev.Weapon);
|
||||
|
|
@ -601,7 +601,7 @@ public abstract partial class SharedMeleeWeaponSystem : EntitySystem
|
|||
}
|
||||
}
|
||||
|
||||
protected abstract void DoDamageEffect(List<EntityUid> targets, EntityUid? user, TransformComponent targetXform);
|
||||
protected abstract void DoDamageEffect(HashSet<EntityUid> targets, EntityUid? user, TransformComponent targetXform);
|
||||
|
||||
private bool DoHeavyAttack(EntityUid user, HeavyAttackEvent ev, EntityUid meleeUid, MeleeWeaponComponent component, ICommonSession? session)
|
||||
{
|
||||
|
|
@ -620,7 +620,7 @@ public abstract partial class SharedMeleeWeaponSystem : EntitySystem
|
|||
|
||||
var damage = GetDamage(meleeUid, user, component);
|
||||
var resistanceBypass = GetResistanceBypass(meleeUid, user, component);
|
||||
var entities = GetEntityList(ev.Entities);
|
||||
var entities = GetEntitySet(ev.Entities);
|
||||
|
||||
if (entities.Count == 0)
|
||||
{
|
||||
|
|
@ -636,7 +636,7 @@ public abstract partial class SharedMeleeWeaponSystem : EntitySystem
|
|||
LogImpact.Low,
|
||||
$"{ToPrettyString(user):actor} melee attacked (heavy) using {ToPrettyString(meleeUid):tool} and missed");
|
||||
}
|
||||
var missEvent = new MeleeHitEvent(new List<EntityUid>(), user, meleeUid, damage, direction);
|
||||
var missEvent = new MeleeHitEvent(new HashSet<EntityUid>(), user, meleeUid, damage, direction);
|
||||
RaiseLocalEvent(meleeUid, missEvent);
|
||||
|
||||
// immediate audio feedback
|
||||
|
|
@ -648,35 +648,21 @@ public abstract partial class SharedMeleeWeaponSystem : EntitySystem
|
|||
// Naughty input
|
||||
if (entities.Count > MaxTargets)
|
||||
{
|
||||
entities.RemoveRange(MaxTargets, entities.Count - MaxTargets);
|
||||
entities = entities.Take(MaxTargets).ToHashSet();
|
||||
}
|
||||
|
||||
// Validate client
|
||||
for (var i = entities.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var entity = entities[i];
|
||||
entities.RemoveWhere(entity => TerminatingOrDeleted(entity) ||
|
||||
!ArcRaySuccessful(entity,
|
||||
userPos,
|
||||
direction.ToWorldAngle(),
|
||||
component.Angle,
|
||||
distance,
|
||||
userXform.MapID,
|
||||
user,
|
||||
session));
|
||||
|
||||
if (TerminatingOrDeleted(entity))
|
||||
{
|
||||
entities.RemoveAt(i);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!ArcRaySuccessful(entity,
|
||||
userPos,
|
||||
direction.ToWorldAngle(),
|
||||
component.Angle,
|
||||
distance,
|
||||
userXform.MapID,
|
||||
user,
|
||||
session))
|
||||
{
|
||||
// Bad input
|
||||
entities.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
var targets = new List<EntityUid>();
|
||||
var targets = new HashSet<EntityUid>();
|
||||
foreach (var entity in entities)
|
||||
{
|
||||
if (entity == user ||
|
||||
|
|
@ -710,17 +696,17 @@ public abstract partial class SharedMeleeWeaponSystem : EntitySystem
|
|||
}
|
||||
|
||||
var appliedDamage = new DamageSpecifier();
|
||||
var filteredTargets = new HashSet<EntityUid>();
|
||||
|
||||
for (var i = targets.Count - 1; i >= 0; i--)
|
||||
foreach (var entity in targets)
|
||||
{
|
||||
var entity = targets[i];
|
||||
// We raise an attack attempt here as well,
|
||||
// primarily because this was an untargeted wideswing: if a subscriber to that event cared about
|
||||
// the potential target (such as for pacifism), they need to be made aware of the target here.
|
||||
// In that case, just continue.
|
||||
if (!Blocker.CanAttack(user, entity, (weapon, component)))
|
||||
{
|
||||
targets.RemoveAt(i);
|
||||
filteredTargets.Add(entity);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -755,9 +741,11 @@ public abstract partial class SharedMeleeWeaponSystem : EntitySystem
|
|||
}
|
||||
|
||||
if (TerminatingOrDeleted(entity))
|
||||
targets.RemoveAt(i);
|
||||
filteredTargets.Add(entity);
|
||||
}
|
||||
|
||||
targets.ExceptWith(filteredTargets);
|
||||
|
||||
if (entities.Count != 0)
|
||||
{
|
||||
var target = entities.First();
|
||||
|
|
@ -768,12 +756,12 @@ public abstract partial class SharedMeleeWeaponSystem : EntitySystem
|
|||
{
|
||||
if (appliedDamage.GetTotal() > FixedPoint2.Zero)
|
||||
{
|
||||
DoDamageEffect(targets, user, Transform(targets[0]));
|
||||
DoDamageEffect(targets, user, Transform(targets.First()));
|
||||
ResetUndamagedSwingsCount((meleeUid, component));
|
||||
}
|
||||
else
|
||||
{
|
||||
UndamagedAttack((meleeUid, component), targets[0], user);
|
||||
UndamagedAttack((meleeUid, component), targets.First(), user);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue