using System.Numerics; using Content.Shared.Construction.Components; using Content.Shared.Weapons.Melee.Components; using Content.Shared.Weapons.Melee.Events; using Robust.Shared.Physics; using Robust.Shared.Physics.Components; using Robust.Shared.Physics.Events; using Robust.Shared.Physics.Systems; using Robust.Shared.Timing; namespace Content.Shared.Weapons.Melee; /// /// This handles /// public sealed class MeleeThrowOnHitSystem : EntitySystem { [Dependency] private readonly IGameTiming _timing = default!; [Dependency] private readonly SharedTransformSystem _transform = default!; [Dependency] private readonly SharedPhysicsSystem _physics = default!; /// public override void Initialize() { SubscribeLocalEvent(OnMeleeHit); SubscribeLocalEvent(OnThrownStartup); SubscribeLocalEvent(OnThrownShutdown); SubscribeLocalEvent(OnStartCollide); } private void OnMeleeHit(Entity ent, ref MeleeHitEvent args) { var (_, comp) = ent; if (!args.IsHit) return; var mapPos = _transform.GetMapCoordinates(args.User).Position; foreach (var hit in args.HitEntities) { var hitPos = _transform.GetMapCoordinates(hit).Position; var angle = args.Direction ?? hitPos - mapPos; if (angle == Vector2.Zero) continue; if (!CanThrowOnHit(ent, hit)) continue; if (comp.UnanchorOnHit && HasComp(hit)) { _transform.Unanchor(hit, Transform(hit)); } RemComp(hit); var ev = new MeleeThrowOnHitStartEvent(args.User, ent); RaiseLocalEvent(hit, ref ev); var thrownComp = new MeleeThrownComponent { Velocity = angle.Normalized() * comp.Speed, Lifetime = comp.Lifetime, MinLifetime = comp.MinLifetime }; AddComp(hit, thrownComp); } } private void OnThrownStartup(Entity ent, ref ComponentStartup args) { var (_, comp) = ent; if (!TryComp(ent, out var body) || (body.BodyType & (BodyType.Dynamic | BodyType.KinematicController)) == 0x0) return; comp.PreviousStatus = body.BodyStatus; comp.ThrownEndTime = _timing.CurTime + TimeSpan.FromSeconds(comp.Lifetime); comp.MinLifetimeTime = _timing.CurTime + TimeSpan.FromSeconds(comp.MinLifetime); _physics.SetBodyStatus(ent, body, BodyStatus.InAir); _physics.SetLinearVelocity(ent, Vector2.Zero, body: body); _physics.ApplyLinearImpulse(ent, comp.Velocity * body.Mass, body: body); Dirty(ent, ent.Comp); } private void OnThrownShutdown(Entity ent, ref ComponentShutdown args) { if (TryComp(ent, out var body)) _physics.SetBodyStatus(ent, body, ent.Comp.PreviousStatus); var ev = new MeleeThrowOnHitEndEvent(); RaiseLocalEvent(ent, ref ev); } private void OnStartCollide(Entity ent, ref StartCollideEvent args) { var (_, comp) = ent; if (!args.OtherFixture.Hard || !args.OtherBody.CanCollide || !args.OurFixture.Hard || !args.OurBody.CanCollide) return; if (_timing.CurTime < comp.MinLifetimeTime) return; RemCompDeferred(ent, ent.Comp); } public bool CanThrowOnHit(Entity ent, EntityUid target) { var (uid, comp) = ent; var ev = new AttemptMeleeThrowOnHitEvent(target); RaiseLocalEvent(uid, ref ev); if (ev.Handled) return !ev.Cancelled; return comp.Enabled; } public override void Update(float frameTime) { base.Update(frameTime); var query = EntityQueryEnumerator(); while (query.MoveNext(out var uid, out var comp)) { if (_timing.CurTime > comp.ThrownEndTime) RemCompDeferred(uid, comp); } } }