Content update for NetEntities (#18935)

This commit is contained in:
metalgearsloth
2023-09-11 09:42:41 +10:00
committed by GitHub
parent 389c8d1a2c
commit 5a0fc68be2
526 changed files with 3058 additions and 2215 deletions

View File

@@ -10,9 +10,9 @@ namespace Content.Shared.Weapons.Melee.Events
/// <summary>
/// Coordinates being attacked.
/// </summary>
public readonly EntityCoordinates Coordinates;
public readonly NetCoordinates Coordinates;
protected AttackEvent(EntityCoordinates coordinates)
protected AttackEvent(NetCoordinates coordinates)
{
Coordinates = coordinates;
}

View File

@@ -6,9 +6,9 @@ namespace Content.Shared.Weapons.Melee.Events;
[Serializable, NetSerializable]
public sealed class DisarmAttackEvent : AttackEvent
{
public EntityUid? Target;
public NetEntity? Target;
public DisarmAttackEvent(EntityUid? target, EntityCoordinates coordinates) : base(coordinates)
public DisarmAttackEvent(NetEntity? target, NetCoordinates coordinates) : base(coordinates)
{
Target = target;
}

View File

@@ -9,14 +9,14 @@ namespace Content.Shared.Weapons.Melee.Events;
[Serializable, NetSerializable]
public sealed class HeavyAttackEvent : AttackEvent
{
public readonly EntityUid Weapon;
public readonly NetEntity Weapon;
/// <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<EntityUid> Entities;
public List<NetEntity> Entities;
public HeavyAttackEvent(EntityUid weapon, List<EntityUid> entities, EntityCoordinates coordinates) : base(coordinates)
public HeavyAttackEvent(NetEntity weapon, List<NetEntity> entities, NetCoordinates coordinates) : base(coordinates)
{
Weapon = weapon;
Entities = entities;

View File

@@ -9,10 +9,10 @@ namespace Content.Shared.Weapons.Melee.Events;
[Serializable, NetSerializable]
public sealed class LightAttackEvent : AttackEvent
{
public readonly EntityUid? Target;
public readonly EntityUid Weapon;
public readonly NetEntity? Target;
public readonly NetEntity Weapon;
public LightAttackEvent(EntityUid? target, EntityUid weapon, EntityCoordinates coordinates) : base(coordinates)
public LightAttackEvent(NetEntity? target, NetEntity weapon, NetCoordinates coordinates) : base(coordinates)
{
Target = target;
Weapon = weapon;

View File

@@ -9,7 +9,7 @@ namespace Content.Shared.Weapons.Melee.Events;
[Serializable, NetSerializable]
public sealed class MeleeLungeEvent : EntityEventArgs
{
public EntityUid Entity;
public NetEntity Entity;
/// <summary>
/// Width of the attack angle.
@@ -26,9 +26,9 @@ public sealed class MeleeLungeEvent : EntityEventArgs
/// </summary>
public string? Animation;
public MeleeLungeEvent(EntityUid uid, Angle angle, Vector2 localPos, string? animation)
public MeleeLungeEvent(NetEntity entity, Angle angle, Vector2 localPos, string? animation)
{
Entity = uid;
Entity = entity;
Angle = angle;
LocalPos = localPos;
Animation = animation;

View File

@@ -5,9 +5,9 @@ namespace Content.Shared.Weapons.Melee.Events;
[Serializable, NetSerializable]
public sealed class StopAttackEvent : EntityEventArgs
{
public readonly EntityUid Weapon;
public readonly NetEntity Weapon;
public StopAttackEvent(EntityUid weapon)
public StopAttackEvent(NetEntity weapon)
{
Weapon = weapon;
}

View File

@@ -164,7 +164,7 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
return;
if (!TryGetWeapon(user.Value, out var weaponUid, out var weapon) ||
weaponUid != msg.Weapon)
weaponUid != GetEntity(msg.Weapon))
{
return;
}
@@ -184,12 +184,12 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
return;
if (!TryGetWeapon(user.Value, out var weaponUid, out var weapon) ||
weaponUid != msg.Weapon)
weaponUid != GetEntity(msg.Weapon))
{
return;
}
AttemptAttack(args.SenderSession.AttachedEntity!.Value, msg.Weapon, weapon, msg, args.SenderSession);
AttemptAttack(args.SenderSession.AttachedEntity!.Value, weaponUid, weapon, msg, args.SenderSession);
}
private void OnHeavyAttack(HeavyAttackEvent msg, EntitySessionEventArgs args)
@@ -200,12 +200,12 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
}
if (!TryGetWeapon(args.SenderSession.AttachedEntity.Value, out var weaponUid, out var weapon) ||
weaponUid != msg.Weapon)
weaponUid != GetEntity(msg.Weapon))
{
return;
}
AttemptAttack(args.SenderSession.AttachedEntity.Value, msg.Weapon, weapon, msg, args.SenderSession);
AttemptAttack(args.SenderSession.AttachedEntity.Value, weaponUid, weapon, msg, args.SenderSession);
}
private void OnDisarmAttack(DisarmAttackEvent msg, EntitySessionEventArgs args)
@@ -330,7 +330,7 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
public void AttemptLightAttackMiss(EntityUid user, EntityUid weaponUid, MeleeWeaponComponent weapon, EntityCoordinates coordinates)
{
AttemptAttack(user, weaponUid, weapon, new LightAttackEvent(null, weaponUid, coordinates), null);
AttemptAttack(user, weaponUid, weapon, new LightAttackEvent(null, GetNetEntity(weaponUid), GetNetCoordinates(coordinates)), null);
}
public bool AttemptLightAttack(EntityUid user, EntityUid weaponUid, MeleeWeaponComponent weapon, EntityUid target)
@@ -338,7 +338,7 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
if (!TryComp<TransformComponent>(target, out var targetXform))
return false;
return AttemptAttack(user, weaponUid, weapon, new LightAttackEvent(target, weaponUid, targetXform.Coordinates), null);
return AttemptAttack(user, weaponUid, weapon, new LightAttackEvent(GetNetEntity(target), GetNetEntity(weaponUid), GetNetCoordinates(targetXform.Coordinates)), null);
}
public bool AttemptDisarmAttack(EntityUid user, EntityUid weaponUid, MeleeWeaponComponent weapon, EntityUid target)
@@ -346,7 +346,7 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
if (!TryComp<TransformComponent>(target, out var targetXform))
return false;
return AttemptAttack(user, weaponUid, weapon, new DisarmAttackEvent(target, targetXform.Coordinates), null);
return AttemptAttack(user, weaponUid, weapon, new DisarmAttackEvent(GetNetEntity(target), GetNetCoordinates(targetXform.Coordinates)), null);
}
/// <summary>
@@ -366,16 +366,20 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
switch (attack)
{
case LightAttackEvent light:
if (!Blocker.CanAttack(user, light.Target))
var lightTarget = GetEntity(light.Target);
if (!Blocker.CanAttack(user, lightTarget))
return false;
// Can't self-attack if you're the weapon
if (weaponUid == light.Target)
if (weaponUid == lightTarget)
return false;
break;
case DisarmAttackEvent disarm:
if (!Blocker.CanAttack(user, disarm.Target))
var disarmTarget = GetEntity(disarm.Target);
if (!Blocker.CanAttack(user, disarmTarget))
return false;
break;
default:
@@ -441,7 +445,7 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
throw new NotImplementedException();
}
DoLungeAnimation(user, weapon.Angle, attack.Coordinates.ToMap(EntityManager, TransformSystem), weapon.Range, animation);
DoLungeAnimation(user, weapon.Angle, GetCoordinates(attack.Coordinates).ToMap(EntityManager, TransformSystem), weapon.Range, animation);
}
weapon.Attacking = true;
@@ -454,13 +458,14 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
{
// If I do not come back later to fix Light Attacks being Heavy Attacks you can throw me in the spider pit -Errant
var damage = GetDamage(meleeUid, user, component) * GetHeavyDamageModifier(meleeUid, user, component);
var target = GetEntity(ev.Target);
// For consistency with wide attacks stuff needs damageable.
if (Deleted(ev.Target) ||
!HasComp<DamageableComponent>(ev.Target) ||
!TryComp<TransformComponent>(ev.Target, out var targetXform) ||
if (Deleted(target) ||
!HasComp<DamageableComponent>(target) ||
!TryComp<TransformComponent>(target, out var targetXform) ||
// Not in LOS.
!InRange(user, ev.Target.Value, component.Range, session))
!InRange(user, target.Value, component.Range, 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
@@ -485,7 +490,7 @@ public abstract 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> { ev.Target.Value }, user, meleeUid, damage);
var hitEvent = new MeleeHitEvent(new List<EntityUid> { target.Value }, user, meleeUid, damage);
RaiseLocalEvent(meleeUid, hitEvent);
if (hitEvent.Handled)
@@ -493,43 +498,45 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
var targets = new List<EntityUid>(1)
{
ev.Target.Value
target.Value
};
Interaction.DoContactInteraction(ev.Weapon, ev.Target);
Interaction.DoContactInteraction(user, ev.Weapon);
var weapon = GetEntity(ev.Weapon);
Interaction.DoContactInteraction(weapon, target);
Interaction.DoContactInteraction(user, weapon);
// If the user is using a long-range weapon, this probably shouldn't be happening? But I'll interpret melee as a
// somewhat messy scuffle. See also, heavy attacks.
Interaction.DoContactInteraction(user, ev.Target);
Interaction.DoContactInteraction(user, target);
// For stuff that cares about it being attacked.
var attackedEvent = new AttackedEvent(meleeUid, user, targetXform.Coordinates);
RaiseLocalEvent(ev.Target.Value, attackedEvent);
RaiseLocalEvent(target.Value, attackedEvent);
var modifiedDamage = DamageSpecifier.ApplyModifierSets(damage + hitEvent.BonusDamage + attackedEvent.BonusDamage, hitEvent.ModifiersList);
var damageResult = Damageable.TryChangeDamage(ev.Target, modifiedDamage, origin:user);
var damageResult = Damageable.TryChangeDamage(target, modifiedDamage, origin:user);
if (damageResult != null && damageResult.Total > FixedPoint2.Zero)
{
// If the target has stamina and is taking blunt damage, they should also take stamina damage based on their blunt to stamina factor
if (damageResult.DamageDict.TryGetValue("Blunt", out var bluntDamage))
{
_stamina.TakeStaminaDamage(ev.Target.Value, (bluntDamage * component.BluntStaminaDamageFactor).Float(), visual: false, source: user, with: meleeUid == user ? null : meleeUid);
_stamina.TakeStaminaDamage(target.Value, (bluntDamage * component.BluntStaminaDamageFactor).Float(), visual: false, source: user, with: meleeUid == user ? null : meleeUid);
}
if (meleeUid == user)
{
AdminLogger.Add(LogType.MeleeHit, LogImpact.Medium,
$"{ToPrettyString(user):actor} melee attacked (light) {ToPrettyString(ev.Target.Value):subject} using their hands and dealt {damageResult.Total:damage} damage");
$"{ToPrettyString(user):actor} melee attacked (light) {ToPrettyString(target.Value):subject} using their hands and dealt {damageResult.Total:damage} damage");
}
else
{
AdminLogger.Add(LogType.MeleeHit, LogImpact.Medium,
$"{ToPrettyString(user):actor} melee attacked (light) {ToPrettyString(ev.Target.Value):subject} using {ToPrettyString(meleeUid):tool} and dealt {damageResult.Total:damage} damage");
$"{ToPrettyString(user):actor} melee attacked (light) {ToPrettyString(target.Value):subject} using {ToPrettyString(meleeUid):tool} and dealt {damageResult.Total:damage} damage");
}
PlayHitSound(ev.Target.Value, user, GetHighestDamageSound(modifiedDamage, _protoManager), hitEvent.HitSoundOverride, component.HitSound);
PlayHitSound(target.Value, user, GetHighestDamageSound(modifiedDamage, _protoManager), hitEvent.HitSoundOverride, component.HitSound);
}
else
{
@@ -561,7 +568,7 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
if (!TryComp<TransformComponent>(user, out var userXform))
return false;
var targetMap = ev.Coordinates.ToMap(EntityManager, TransformSystem);
var targetMap = GetCoordinates(ev.Coordinates).ToMap(EntityManager, TransformSystem);
if (targetMap.MapId != userXform.MapID)
return false;
@@ -571,7 +578,7 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
var distance = Math.Min(component.Range, direction.Length());
var damage = GetDamage(meleeUid, user, component);
var entities = ev.Entities;
var entities = GetEntityList(ev.Entities);
if (entities.Count == 0)
{
@@ -632,12 +639,14 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
if (hitEvent.Handled)
return true;
Interaction.DoContactInteraction(user, ev.Weapon);
var weapon = GetEntity(ev.Weapon);
Interaction.DoContactInteraction(user, weapon);
// For stuff that cares about it being attacked.
foreach (var target in targets)
{
Interaction.DoContactInteraction(ev.Weapon, target);
Interaction.DoContactInteraction(weapon, target);
// If the user is using a long-range weapon, this probably shouldn't be happening? But I'll interpret melee as a
// somewhat messy scuffle. See also, light attacks.
@@ -648,7 +657,7 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
foreach (var entity in targets)
{
var attackedEvent = new AttackedEvent(meleeUid, user, ev.Coordinates);
var attackedEvent = new AttackedEvent(meleeUid, user, GetCoordinates(ev.Coordinates));
RaiseLocalEvent(entity, attackedEvent);
var modifiedDamage = DamageSpecifier.ApplyModifierSets(damage + hitEvent.BonusDamage + attackedEvent.BonusDamage, hitEvent.ModifiersList);
@@ -820,9 +829,13 @@ public abstract class SharedMeleeWeaponSystem : EntitySystem
protected virtual bool DoDisarm(EntityUid user, DisarmAttackEvent ev, EntityUid meleeUid, MeleeWeaponComponent component, ICommonSession? session)
{
if (Deleted(ev.Target) ||
user == ev.Target)
var target = GetEntity(ev.Target);
if (Deleted(target) ||
user == target)
{
return false;
}
// Play a sound to give instant feedback; same with playing the animations
Audio.PlayPredicted(component.SwingSound, meleeUid, user);

View File

@@ -112,14 +112,16 @@ public abstract partial class SharedTetherGunSystem : EntitySystem
return;
}
if (!msg.Coordinates.TryDistance(EntityManager, TransformSystem, Transform(gunUid.Value).Coordinates,
var coords = GetCoordinates(msg.Coordinates);
if (!coords.TryDistance(EntityManager, TransformSystem, Transform(gunUid.Value).Coordinates,
out var distance) ||
distance > gun.MaxDistance)
{
return;
}
TransformSystem.SetCoordinates(gun.TetherEntity.Value, msg.Coordinates);
TransformSystem.SetCoordinates(gun.TetherEntity.Value, coords);
}
private void OnTetherRanged(EntityUid uid, TetherGunComponent component, AfterInteractEvent args)
@@ -283,7 +285,7 @@ public abstract partial class SharedTetherGunSystem : EntitySystem
[Serializable, NetSerializable]
protected sealed class RequestTetherMoveEvent : EntityEventArgs
{
public EntityCoordinates Coordinates;
public NetCoordinates Coordinates;
}
[Serializable, NetSerializable]

View File

@@ -7,7 +7,7 @@ using Robust.Shared.Serialization.TypeSerializers.Implementations.Custom.Prototy
namespace Content.Shared.Weapons.Ranged.Components;
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
[RegisterComponent, NetworkedComponent]
public sealed partial class BallisticAmmoProviderComponent : Component
{
[ViewVariables(VVAccess.ReadWrite), DataField("soundRack")]
@@ -25,7 +25,6 @@ public sealed partial class BallisticAmmoProviderComponent : Component
public int Count => UnspawnedCount + Container.ContainedEntities.Count;
[ViewVariables(VVAccess.ReadWrite), DataField("unspawnedCount")]
[AutoNetworkedField]
public int UnspawnedCount;
[ViewVariables(VVAccess.ReadWrite), DataField("whitelist")]
@@ -35,7 +34,6 @@ public sealed partial class BallisticAmmoProviderComponent : Component
// TODO: Make this use stacks when the typeserializer is done.
[DataField("entities")]
[AutoNetworkedField(true)]
public List<EntityUid> Entities = new();
/// <summary>
@@ -45,7 +43,6 @@ public sealed partial class BallisticAmmoProviderComponent : Component
/// Set to false for entities like turrets to avoid users being able to cycle them.
/// </remarks>
[ViewVariables(VVAccess.ReadWrite), DataField("cycleable")]
[AutoNetworkedField]
public bool Cycleable = true;
/// <summary>

View File

@@ -8,7 +8,7 @@ namespace Content.Shared.Weapons.Ranged.Events;
[Serializable, NetSerializable]
public sealed class MuzzleFlashEvent : EntityEventArgs
{
public EntityUid Uid;
public NetEntity Uid;
public string Prototype;
/// <summary>
@@ -16,7 +16,7 @@ public sealed class MuzzleFlashEvent : EntityEventArgs
/// </summary>
public bool MatchRotation;
public MuzzleFlashEvent(EntityUid uid, string prototype, bool matchRotation = false)
public MuzzleFlashEvent(NetEntity uid, string prototype, bool matchRotation = false)
{
Uid = uid;
Prototype = prototype;

View File

@@ -9,6 +9,6 @@ namespace Content.Shared.Weapons.Ranged.Events;
[Serializable, NetSerializable]
public sealed class RequestShootEvent : EntityEventArgs
{
public EntityUid Gun;
public EntityCoordinates Coordinates;
}
public NetEntity Gun;
public NetCoordinates Coordinates;
}

View File

@@ -8,5 +8,5 @@ namespace Content.Shared.Weapons.Ranged.Events;
[Serializable, NetSerializable]
public sealed class RequestStopShootEvent : EntityEventArgs
{
public EntityUid Gun;
}
public NetEntity Gun;
}

View File

@@ -29,6 +29,29 @@ public abstract partial class SharedGunSystem
SubscribeLocalEvent<BallisticAmmoProviderComponent, AfterInteractEvent>(OnBallisticAfterInteract);
SubscribeLocalEvent<BallisticAmmoProviderComponent, AmmoFillDoAfterEvent>(OnBallisticAmmoFillDoAfter);
SubscribeLocalEvent<BallisticAmmoProviderComponent, UseInHandEvent>(OnBallisticUse);
SubscribeLocalEvent<BallisticAmmoProviderComponent, ComponentGetState>(OnBallisticGetState);
SubscribeLocalEvent<BallisticAmmoProviderComponent, ComponentHandleState>(OnBallisticHandleState);
}
private void OnBallisticGetState(EntityUid uid, BallisticAmmoProviderComponent component, ref ComponentGetState args)
{
args.State = new BallisticAmmoProviderComponentState()
{
UnspawnedCount = component.UnspawnedCount,
Cycleable = component.Cycleable,
Entities = GetNetEntityList(component.Entities),
};
}
private void OnBallisticHandleState(EntityUid uid, BallisticAmmoProviderComponent component, ref ComponentHandleState args)
{
if (args.Current is not BallisticAmmoProviderComponentState state)
return;
component.UnspawnedCount = state.UnspawnedCount;
component.Cycleable = state.Cycleable;
component.Entities = EnsureEntityList<BallisticAmmoProviderComponent>(state.Entities, uid);
}
private void OnBallisticUse(EntityUid uid, BallisticAmmoProviderComponent component, UseInHandEvent args)
@@ -73,7 +96,7 @@ public abstract partial class SharedGunSystem
args.Handled = true;
_doAfter.TryStartDoAfter(new DoAfterArgs(args.User, component.FillDelay, new AmmoFillDoAfterEvent(), used: uid, target: args.Target, eventTarget: uid)
_doAfter.TryStartDoAfter(new DoAfterArgs(EntityManager, args.User, component.FillDelay, new AmmoFillDoAfterEvent(), used: uid, target: args.Target, eventTarget: uid)
{
BreakOnTargetMove = true,
BreakOnUserMove = true,
@@ -141,7 +164,7 @@ public abstract partial class SharedGunSystem
SimulateInsertAmmo(ent.Value, args.Target.Value, Transform(args.Target.Value).Coordinates);
}
if (ent.Value.IsClientSide())
if (IsClientSide(ent.Value))
Del(ent.Value);
}
@@ -269,6 +292,14 @@ public abstract partial class SharedGunSystem
Appearance.SetData(uid, AmmoVisuals.AmmoCount, GetBallisticShots(component), appearance);
Appearance.SetData(uid, AmmoVisuals.AmmoMax, component.Capacity, appearance);
}
[Serializable, NetSerializable]
private sealed class BallisticAmmoProviderComponentState : ComponentState
{
public int UnspawnedCount;
public List<NetEntity> Entities = new();
public bool Cycleable = true;
}
}
/// <summary>

View File

@@ -47,7 +47,7 @@ public partial class SharedGunSystem
args.State = new RevolverAmmoProviderComponentState
{
CurrentIndex = component.CurrentIndex,
AmmoSlots = component.AmmoSlots,
AmmoSlots = GetNetEntityList(component.AmmoSlots),
Chambers = component.Chambers,
};
}
@@ -64,7 +64,7 @@ public partial class SharedGunSystem
// Need to copy across the state rather than the ref.
for (var i = 0; i < component.AmmoSlots.Count; i++)
{
component.AmmoSlots[i] = state.AmmoSlots[i];
component.AmmoSlots[i] = EnsureEntity<RevolverAmmoProviderComponent>(state.AmmoSlots[i], uid);
component.Chambers[i] = state.Chambers[i];
}
@@ -416,7 +416,7 @@ public partial class SharedGunSystem
protected sealed class RevolverAmmoProviderComponentState : ComponentState
{
public int CurrentIndex;
public List<EntityUid?> AmmoSlots = default!;
public List<NetEntity?> AmmoSlots = default!;
public bool?[] Chambers = default!;
}

View File

@@ -131,18 +131,20 @@ public abstract partial class SharedGunSystem : EntitySystem
return;
}
if (ent != msg.Gun)
if (ent != GetEntity(msg.Gun))
return;
gun.ShootCoordinates = msg.Coordinates;
gun.ShootCoordinates = GetCoordinates(msg.Coordinates);
Log.Debug($"Set shoot coordinates to {gun.ShootCoordinates}");
AttemptShoot(user.Value, ent, gun);
}
private void OnStopShootRequest(RequestStopShootEvent ev, EntitySessionEventArgs args)
{
var gunUid = GetEntity(ev.Gun);
if (args.SenderSession.AttachedEntity == null ||
!TryComp<GunComponent>(ev.Gun, out var gun) ||
!TryComp<GunComponent>(gunUid, out var gun) ||
!TryGetGun(args.SenderSession.AttachedEntity.Value, out _, out var userGun))
{
return;
@@ -151,7 +153,7 @@ public abstract partial class SharedGunSystem : EntitySystem
if (userGun != gun)
return;
StopShooting(ev.Gun, gun);
StopShooting(gunUid, gun);
}
public bool CanShoot(GunComponent component)
@@ -432,7 +434,7 @@ public abstract partial class SharedGunSystem : EntitySystem
if (sprite == null)
return;
var ev = new MuzzleFlashEvent(gun, sprite, user == gun);
var ev = new MuzzleFlashEvent(GetNetEntity(gun), sprite, user == gun);
CreateEffect(gun, ev, user);
}
@@ -454,7 +456,7 @@ public abstract partial class SharedGunSystem : EntitySystem
[Serializable, NetSerializable]
public sealed class HitscanEvent : EntityEventArgs
{
public List<(EntityCoordinates coordinates, Angle angle, SpriteSpecifier Sprite, float Distance)> Sprites = new();
public List<(NetCoordinates coordinates, Angle angle, SpriteSpecifier Sprite, float Distance)> Sprites = new();
}
}