using System.Numerics; using Content.Server.Beam.Components; using Content.Shared.Beam; using Content.Shared.Beam.Components; using Content.Shared.Physics; using Robust.Server.GameObjects; using Robust.Shared.Audio; using Robust.Shared.Audio.Systems; using Robust.Shared.Map; using Robust.Shared.Physics; using Robust.Shared.Physics.Collision.Shapes; using Robust.Shared.Physics.Components; using Robust.Shared.Physics.Systems; namespace Content.Server.Beam; public sealed class BeamSystem : SharedBeamSystem { [Dependency] private readonly FixtureSystem _fixture = default!; [Dependency] private readonly TransformSystem _transform = default!; [Dependency] private readonly SharedAudioSystem _audio = default!; [Dependency] private readonly SharedBroadphaseSystem _broadphase = default!; [Dependency] private readonly SharedPhysicsSystem _physics = default!; public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnBeamCreationSuccess); SubscribeLocalEvent(OnControllerCreated); SubscribeLocalEvent(OnBeamFired); SubscribeLocalEvent(OnRemove); } private void OnBeamCreationSuccess(EntityUid uid, BeamComponent component, CreateBeamSuccessEvent args) { component.BeamShooter = args.User; } private void OnControllerCreated(EntityUid uid, BeamComponent component, BeamControllerCreatedEvent args) { component.OriginBeam = args.OriginBeam; } private void OnBeamFired(EntityUid uid, BeamComponent component, BeamFiredEvent args) { component.CreatedBeams.Add(args.CreatedBeam); } private void OnRemove(EntityUid uid, BeamComponent component, ComponentRemove args) { if (component.VirtualBeamController == null) return; if (component.CreatedBeams.Count == 0 && component.VirtualBeamController.Value.Valid) QueueDel(component.VirtualBeamController.Value); } /// /// If is successful, it spawns a beam from the user to the target. /// /// The prototype used to make the beam /// Angle of the user firing the beam /// The calculated distance from the user to the target. /// Where the beam will spawn in /// Calculated correction so the can be properly dynamically created /// The virtual beam controller that this beam will use. If one doesn't exist it will be created here. /// Optional sprite state for the if it needs a dynamic one /// Optional shader for the and if it needs something other than default private void CreateBeam(string prototype, Angle userAngle, Vector2 calculatedDistance, MapCoordinates beamStartPos, Vector2 distanceCorrection, EntityUid? controller, string? bodyState = null, string shader = "unshaded") { var beamSpawnPos = beamStartPos; var ent = Spawn(prototype, beamSpawnPos); var shape = new EdgeShape(distanceCorrection, new Vector2(0,0)); if (!TryComp(ent, out var physics) || !TryComp(ent, out var beam)) return; FixturesComponent? manager = null; _fixture.TryCreateFixture( ent, shape, "BeamBody", hard: false, collisionMask: (int)CollisionGroup.ItemMask, collisionLayer: (int)CollisionGroup.MobLayer, manager: manager, body: physics); _physics.SetBodyType(ent, BodyType.Dynamic, manager: manager, body: physics); _physics.SetCanCollide(ent, true, manager: manager, body: physics); _broadphase.RegenerateContacts((ent, physics, manager)); var distanceLength = distanceCorrection.Length(); var beamVisualizerEvent = new BeamVisualizerEvent(GetNetEntity(ent), distanceLength, userAngle, bodyState, shader); RaiseNetworkEvent(beamVisualizerEvent); if (controller != null) beam.VirtualBeamController = controller; else { var controllerEnt = Spawn("VirtualBeamEntityController", beamSpawnPos); beam.VirtualBeamController = controllerEnt; _audio.PlayPvs(beam.Sound, ent); var beamControllerCreatedEvent = new BeamControllerCreatedEvent(ent, controllerEnt); RaiseLocalEvent(controllerEnt, beamControllerCreatedEvent); } //Create the rest of the beam, sprites handled through the BeamVisualizerEvent for (var i = 0; i < distanceLength-1; i++) { beamSpawnPos = beamSpawnPos.Offset(calculatedDistance.Normalized()); var newEnt = Spawn(prototype, beamSpawnPos); var ev = new BeamVisualizerEvent(GetNetEntity(newEnt), distanceLength, userAngle, bodyState, shader); RaiseNetworkEvent(ev); } var beamFiredEvent = new BeamFiredEvent(ent); RaiseLocalEvent(beam.VirtualBeamController.Value, beamFiredEvent); } /// /// Called where you want an entity to create a beam from one target to another. /// Tries to create the beam and does calculations like the distance, angle, and offset. /// /// The entity that's firing off the beam /// The entity that's being targeted by the user /// The prototype spawned when this beam is created /// Optional sprite state for the if a default one is not given /// Optional shader for the if a default one is not given /// public void TryCreateBeam(EntityUid user, EntityUid target, string bodyPrototype, string? bodyState = null, string shader = "unshaded", EntityUid? controller = null) { if (Deleted(user) || Deleted(target)) return; var userMapPos = _transform.GetMapCoordinates(user); var targetMapPos = _transform.GetMapCoordinates(target); //The distance between the target and the user. var calculatedDistance = targetMapPos.Position - userMapPos.Position; var userAngle = calculatedDistance.ToWorldAngle(); if (userMapPos.MapId != targetMapPos.MapId) return; //Where the start of the beam will spawn var beamStartPos = userMapPos.Offset(calculatedDistance.Normalized()); //Don't divide by zero if (calculatedDistance.Length() == 0) return; if (controller != null && TryComp(controller, out var controllerBeamComp)) { controllerBeamComp.HitTargets.Add(user); controllerBeamComp.HitTargets.Add(target); } var distanceCorrection = calculatedDistance - calculatedDistance.Normalized(); CreateBeam(bodyPrototype, userAngle, calculatedDistance, beamStartPos, distanceCorrection, controller, bodyState, shader); var ev = new CreateBeamSuccessEvent(user, target); RaiseLocalEvent(user, ev); } }