using Content.Shared.Physics; using Content.Shared.Projectiles; using Robust.Shared.Physics.Events; namespace Content.Shared._RMC14.Projectiles.Penetration; public sealed class RMCPenetratingProjectileSystem : EntitySystem { private const int HardCollisionGroup = (int)(CollisionGroup.HighImpassable | CollisionGroup.Impassable); [Dependency] private readonly SharedTransformSystem _transform = default!; public override void Initialize() { SubscribeLocalEvent(OnMapInit); SubscribeLocalEvent(OnPreventCollide); SubscribeLocalEvent(OnStartCollide, after: [typeof(SharedProjectileSystem)]); SubscribeLocalEvent(OnProjectileHit); SubscribeLocalEvent(OnAllowAdditionalHits); } /// /// Store the coordinates the projectile was shot from. /// private void OnMapInit(Entity ent, ref MapInitEvent args) { ent.Comp.ShotFrom = _transform.GetMoverCoordinates(ent); Dirty(ent); } /// /// Prevent collision with an already hit entity. /// private void OnPreventCollide(Entity ent, ref PreventCollideEvent args) { if (!ent.Comp.HitTargets.Contains(args.OtherEntity)) return; args.Cancelled = true; } /// /// Add the hit target to a list of hit targets that won't be hit another time. /// private void OnProjectileHit(Entity ent, ref ProjectileHitEvent args) { if (ent.Comp.HitTargets.Contains(args.Target)) { args.Handled = true; return; } ent.Comp.HitTargets.Add(args.Target); Dirty(ent); } /// /// Reduce the projectile damage and range based on what kind of target the projectile is colliding with. /// private void OnStartCollide(Entity ent, ref StartCollideEvent args) { if (!TryComp(ent, out ProjectileComponent? projectile) || ent.Comp.ShotFrom == null) return; var rangeLoss = ent.Comp.RangeLossPerHit; var damageLoss = ent.Comp.DamageMultiplierLossPerHit; // Apply damage and range loss multipliers depending on target hit. if ((args.OtherFixture.CollisionLayer & HardCollisionGroup) != 0) { // Thick Membranes have a lower multiplier. if (TryComp(args.OtherEntity, out OccluderComponent? occluder) && !occluder.Enabled) { rangeLoss *= ent.Comp.ThickMembraneMultiplier; damageLoss *= ent.Comp.ThickMembraneMultiplier; } else { rangeLoss *= ent.Comp.WallMultiplier; damageLoss *= ent.Comp.WallMultiplier; } } ent.Comp.Range -= rangeLoss; Dirty(ent); projectile.Damage *= 1 - damageLoss; Dirty(ent, projectile); } /// /// Make sure additional hits are allowed if range is still above 0. /// private void OnAllowAdditionalHits(Entity ent, ref AfterProjectileHitEvent args) { if (ent.Comp.ShotFrom == null) return; var distanceTravelled = (_transform.GetMoverCoordinates(ent).Position - ent.Comp.ShotFrom.Value.Position).Length(); var range = ent.Comp.Range - distanceTravelled; ent.Comp.HitTargets.Add(args.Target); Dirty(ent); if (range < 0) return; args.Projectile.Comp.ProjectileSpent = false; Dirty(args.Projectile); } } /// /// Raised on a projectile after it has hit an entity. /// [ByRefEvent] public record struct AfterProjectileHitEvent(Entity Projectile, EntityUid Target);