Files
wwdpublic/Content.Shared/Projectiles/SharedProjectileSystem.cs
VMSolidus 7dc84aaae4 More Random Code Optimizations (#2339)
These are all essentially just random systems that scaled directly with
player count (or were on my list of the top 50 anyways). So just a
couple systems that had very inefficient enumerators being swapped out
with significantly more efficient ones, plus a few swaps from O(n) to
O(m) m << n. A big one was DrainSystem, which was querrying all possible
entities, rather than doing so from the "set of all static objects",
which is significantly smaller. Puddles are always static objects, so it
doesn't make sense to have the drains check for anything other than
static.

We can also use DirtyField to save on performance costs of Dirty(uid,
component) in cases where the Dirty is only networking a single
component field.

no CL this isn't player facing.
2025-07-12 02:24:22 +10:00

327 lines
12 KiB
C#

using System.Linq;
using System.Numerics;
using Content.Shared.Damage;
using Content.Shared.DoAfter;
using Content.Shared.Examine;
using Content.Shared.IdentityManagement;
using Content.Shared.Hands.EntitySystems;
using Content.Shared.Interaction;
using Content.Shared.Physics;
using Content.Shared.Popups;
using Content.Shared._Shitmed.Targeting;
using Content.Shared._White.Penetrated;
using Content.Shared._White.Projectile;
using Content.Shared.Item.ItemToggle;
using Content.Shared.Throwing;
using Content.Shared.UserInterface;
using Robust.Shared.Audio.Systems;
using Robust.Shared.Map;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Events;
using Robust.Shared.Physics.Systems;
using Robust.Shared.Serialization;
using Robust.Shared.Timing;
using Content.Shared.Standing;
using Robust.Shared.Network;
namespace Content.Shared.Projectiles;
public abstract partial class SharedProjectileSystem : EntitySystem
{
public const string ProjectileFixture = "projectile";
[Dependency] private readonly INetManager _netManager = default!;
[Dependency] private readonly SharedAudioSystem _audio = default!;
[Dependency] private readonly SharedDoAfterSystem _doAfter = default!;
[Dependency] private readonly SharedHandsSystem _hands = default!;
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
[Dependency] private readonly SharedTransformSystem _transform = default!;
[Dependency] private readonly IGameTiming _timing = default!;
[Dependency] private readonly SharedPopupSystem _popup = default!;
[Dependency] private readonly StandingStateSystem _standing = default!;
[Dependency] private readonly PenetratedSystem _penetrated = default!; // WD EDIT
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<ProjectileComponent, PreventCollideEvent>(PreventCollision);
SubscribeLocalEvent<EmbeddableProjectileComponent, ProjectileHitEvent>(OnEmbedProjectileHit);
SubscribeLocalEvent<EmbeddableProjectileComponent, ThrowDoHitEvent>(OnEmbedThrowDoHit);
SubscribeLocalEvent<EmbeddableProjectileComponent, ActivateInWorldEvent>(OnEmbedActivate, before: new[] { typeof(ActivatableUISystem), typeof(ItemToggleSystem), });
SubscribeLocalEvent<EmbeddableProjectileComponent, RemoveEmbeddedProjectileEvent>(OnEmbedRemove);
SubscribeLocalEvent<EmbeddableProjectileComponent, ExaminedEvent>(OnExamined);
SubscribeLocalEvent<EmbeddableProjectileComponent, PreventCollideEvent>(OnPreventCollision); // WD EDIT
}
// TODO: rename Embedded to Target in every context
public override void Update(float frameTime)
{
base.Update(frameTime);
var query = EntityQueryEnumerator<ActiveEmbeddableProjectileComponent>();
var curTime = _timing.CurTime;
while (query.MoveNext(out var uid, out var _))
{
if (!TryComp(uid, out EmbeddableProjectileComponent? comp))
{
RemCompDeferred<ActiveEmbeddableProjectileComponent>(uid);
continue;
}
if (comp.AutoRemoveTime == null || comp.AutoRemoveTime > curTime)
continue;
if (comp.Target is { } targetUid)
_popup.PopupClient(Loc.GetString("throwing-embed-falloff", ("item", uid)), targetUid, targetUid);
RemoveEmbed(uid, comp);
}
}
private void OnEmbedActivate(EntityUid uid, EmbeddableProjectileComponent component, ActivateInWorldEvent args)
{
// Nuh uh
if (component.RemovalTime == null)
return;
if (args.Handled || !args.Complex || !TryComp<PhysicsComponent>(uid, out var physics) || physics.BodyType != BodyType.Static)
return;
args.Handled = true;
if (component.Target is {} targetUid)
_popup.PopupClient(Loc.GetString("throwing-embed-remove-alert-owner", ("item", uid), ("other", args.User)),
args.User, targetUid);
_doAfter.TryStartDoAfter(new DoAfterArgs(EntityManager, args.User, component.RemovalTime.Value,
new RemoveEmbeddedProjectileEvent(), eventTarget: uid, target: uid)
{
DistanceThreshold = SharedInteractionSystem.InteractionRange,
BreakOnMove = true,
NeedHand = true,
});
}
private void OnEmbedRemove(EntityUid uid, EmbeddableProjectileComponent component, RemoveEmbeddedProjectileEvent args)
{
if (args.Cancelled)
return;
RemoveEmbed(uid, component, args.User);
}
public void RemoveEmbed(EntityUid uid, EmbeddableProjectileComponent component, EntityUid? remover = null)
{
component.AutoRemoveTime = null;
component.Target = null;
component.TargetBodyPart = null;
RemCompDeferred<ActiveEmbeddableProjectileComponent>(uid);
var ev = new RemoveEmbedEvent(remover);
RaiseLocalEvent(uid, ref ev);
if (component.DeleteOnRemove)
{
QueueDel(uid);
FreePenetrated(uid); // WD EDIT
return;
}
if (!TryComp(uid, out PhysicsComponent? physics))
return;
var xform = Transform(uid);
_physics.SetBodyType(uid, BodyType.Dynamic, body: physics, xform: xform);
_transform.AttachToGridOrMap(uid, xform);
// Reset whether the projectile has damaged anything if it successfully was removed
if (TryComp<ProjectileComponent>(uid, out var projectile))
{
projectile.Shooter = null;
projectile.Weapon = null;
projectile.DamagedEntity = false;
}
FreePenetrated(uid); // WD EDIT
// Land it just coz uhhh yeah
var landEv = new LandEvent(remover, true);
RaiseLocalEvent(uid, ref landEv);
_physics.WakeBody(uid, body: physics);
// try place it in the user's hand
if (remover is { } removerUid)
_hands.TryPickupAnyHand(removerUid, uid);
Dirty(uid, component);
}
private void OnEmbedThrowDoHit(EntityUid uid, EmbeddableProjectileComponent component, ThrowDoHitEvent args)
{
if (!component.EmbedOnThrow
|| HasComp<ThrownItemImmuneComponent>(args.Target))
// || _standing.IsDown(args.Target)) // WWDP edit; hit prone targets
return;
TryEmbed(uid, args.Target, null, component, args.TargetPart);
}
private void OnEmbedProjectileHit(EntityUid uid, EmbeddableProjectileComponent component, ref ProjectileHitEvent args)
{
if (!(args.Target is { }) // || _standing.IsDown(args.Target) // WWDP
|| !TryComp(uid, out ProjectileComponent? projectile)
|| !TryEmbed(uid, args.Target, args.Shooter, component))
return;
// Raise a specific event for projectiles.
var ev = new ProjectileEmbedEvent(projectile.Shooter, projectile.Weapon!.Value, args.Target);
RaiseLocalEvent(uid, ref ev);
}
public bool TryEmbed(EntityUid uid, EntityUid target, EntityUid? user, EmbeddableProjectileComponent component, TargetBodyPart? targetPart = null, bool raiseEvent = true)
{
// WD EDIT START
if (!TryComp<PhysicsComponent>(uid, out var physics)
|| TerminatingOrDeleted(target)
|| physics.LinearVelocity.Length() < component.MinimumSpeed
|| _netManager.IsClient)
return false;
var attemptEmbedEvent = new AttemptEmbedEvent(user, target);
RaiseLocalEvent(uid, ref attemptEmbedEvent);
var xform = Transform(uid);
if (!TryComp<PenetratedProjectileComponent>(uid, out var penetratedProjectile)
|| !penetratedProjectile.PenetratedUid.HasValue
|| (penetratedProjectile.PenetratedUid != target
&& !HasComp<PenetratedComponent>(target)))
{
EnsureComp<ActiveEmbeddableProjectileComponent>(uid);
_physics.SetLinearVelocity(uid, Vector2.Zero, body: physics);
_physics.SetBodyType(uid, BodyType.Static, body: physics);
_transform.SetParent(uid, xform, target);
}
// WD EDIT END
if (component.Offset != Vector2.Zero)
{
_transform.SetLocalPosition(uid, xform.LocalPosition + xform.LocalRotation.RotateVec(component.Offset),
xform);
}
// WD EDIT START
if (!raiseEvent)
return true;
// WD EDIT END
_audio.PlayPredicted(component.Sound, uid, null);
component.TargetBodyPart = targetPart;
var ev = new EmbedEvent(user, target, targetPart);
RaiseLocalEvent(uid, ref ev);
if (component.AutoRemoveDuration != 0)
component.AutoRemoveTime = _timing.CurTime + TimeSpan.FromSeconds(component.AutoRemoveDuration);
component.Target = target;
Dirty(uid, component);
return true;
}
private void PreventCollision(EntityUid uid, ProjectileComponent component, ref PreventCollideEvent args)
{
if (component.IgnoreShooter && (args.OtherEntity == component.Shooter || args.OtherEntity == component.Weapon))
args.Cancelled = true;
}
public void SetShooter(EntityUid id, ProjectileComponent component, EntityUid shooterId)
{
if (component.Shooter == shooterId)
return;
component.Shooter = shooterId;
Dirty(id, component);
}
private void OnExamined(EntityUid uid, EmbeddableProjectileComponent component, ExaminedEvent args)
{
if (!(component.Target is { } target))
return;
var targetIdentity = Identity.Entity(target, EntityManager);
var loc = component.TargetBodyPart == null
? Loc.GetString("throwing-examine-embedded",
("embedded", uid),
("target", targetIdentity))
: Loc.GetString("throwing-examine-embedded-part",
("embedded", uid),
("target", targetIdentity),
("targetName", Name(targetIdentity)), // WWDP
("targetPart", Loc.GetString($"body-part-{component.TargetBodyPart.ToString()}")));
args.PushMarkup(loc);
}
// WD EDIT START
private void OnPreventCollision(EntityUid uid, EmbeddableProjectileComponent component, ref PreventCollideEvent args)
{
// Opaque collision mask doesn't work for EmbeddableProjectileComponent
if (component.PreventCollide && TryComp(args.OtherEntity, out FixturesComponent? fixtures) &&
fixtures.Fixtures.All(fix => (fix.Value.CollisionLayer & (int) CollisionGroup.Opaque) == 0))
args.Cancelled = true;
}
private void FreePenetrated(EntityUid uid, PenetratedProjectileComponent? penetratedProjectile = null)
{
if (!Resolve(uid, ref penetratedProjectile, false)
|| !penetratedProjectile.PenetratedUid.HasValue)
return;
_penetrated.FreePenetrated(penetratedProjectile.PenetratedUid.Value);
}
// WD EDIT END
[Serializable, NetSerializable]
private sealed partial class RemoveEmbeddedProjectileEvent : DoAfterEvent
{
public override DoAfterEvent Clone() => this;
}
}
[Serializable, NetSerializable]
public sealed class ImpactEffectEvent : EntityEventArgs
{
public string Prototype;
public NetCoordinates Coordinates;
public ImpactEffectEvent(string prototype, NetCoordinates coordinates)
{
Prototype = prototype;
Coordinates = coordinates;
}
}
/// <summary>
/// Raised when an entity is just about to be hit with a projectile but can reflect it
/// </summary>
[ByRefEvent]
public record struct ProjectileReflectAttemptEvent(EntityUid ProjUid, ProjectileComponent Component, bool Cancelled);
/// <summary>
/// Raised when a projectile hits an entity
/// </summary>
[ByRefEvent]
public record struct ProjectileHitEvent(DamageSpecifier Damage, EntityUid Target, EntityUid? Shooter = null);
/// <summary>
/// Raised after a projectile has dealt it's damage.
/// </summary>
[ByRefEvent]
public record struct AfterProjectileHitEvent(DamageSpecifier Damage, EntityUid Target);