1
0

BeamSystem.cs 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. using System.Numerics;
  2. using Content.Server.Beam.Components;
  3. using Content.Shared.Beam;
  4. using Content.Shared.Beam.Components;
  5. using Content.Shared.Physics;
  6. using Robust.Server.GameObjects;
  7. using Robust.Shared.Audio;
  8. using Robust.Shared.Audio.Systems;
  9. using Robust.Shared.Map;
  10. using Robust.Shared.Physics;
  11. using Robust.Shared.Physics.Collision.Shapes;
  12. using Robust.Shared.Physics.Components;
  13. using Robust.Shared.Physics.Systems;
  14. namespace Content.Server.Beam;
  15. public sealed class BeamSystem : SharedBeamSystem
  16. {
  17. [Dependency] private readonly FixtureSystem _fixture = default!;
  18. [Dependency] private readonly TransformSystem _transform = default!;
  19. [Dependency] private readonly SharedAudioSystem _audio = default!;
  20. [Dependency] private readonly SharedBroadphaseSystem _broadphase = default!;
  21. [Dependency] private readonly SharedPhysicsSystem _physics = default!;
  22. public override void Initialize()
  23. {
  24. base.Initialize();
  25. SubscribeLocalEvent<BeamComponent, CreateBeamSuccessEvent>(OnBeamCreationSuccess);
  26. SubscribeLocalEvent<BeamComponent, BeamControllerCreatedEvent>(OnControllerCreated);
  27. SubscribeLocalEvent<BeamComponent, BeamFiredEvent>(OnBeamFired);
  28. SubscribeLocalEvent<BeamComponent, ComponentRemove>(OnRemove);
  29. }
  30. private void OnBeamCreationSuccess(EntityUid uid, BeamComponent component, CreateBeamSuccessEvent args)
  31. {
  32. component.BeamShooter = args.User;
  33. }
  34. private void OnControllerCreated(EntityUid uid, BeamComponent component, BeamControllerCreatedEvent args)
  35. {
  36. component.OriginBeam = args.OriginBeam;
  37. }
  38. private void OnBeamFired(EntityUid uid, BeamComponent component, BeamFiredEvent args)
  39. {
  40. component.CreatedBeams.Add(args.CreatedBeam);
  41. }
  42. private void OnRemove(EntityUid uid, BeamComponent component, ComponentRemove args)
  43. {
  44. if (component.VirtualBeamController == null)
  45. return;
  46. if (component.CreatedBeams.Count == 0 && component.VirtualBeamController.Value.Valid)
  47. QueueDel(component.VirtualBeamController.Value);
  48. }
  49. /// <summary>
  50. /// If <see cref="TryCreateBeam"/> is successful, it spawns a beam from the user to the target.
  51. /// </summary>
  52. /// <param name="prototype">The prototype used to make the beam</param>
  53. /// <param name="userAngle">Angle of the user firing the beam</param>
  54. /// <param name="calculatedDistance">The calculated distance from the user to the target.</param>
  55. /// <param name="beamStartPos">Where the beam will spawn in</param>
  56. /// <param name="distanceCorrection">Calculated correction so the <see cref="EdgeShape"/> can be properly dynamically created</param>
  57. /// <param name="controller"> The virtual beam controller that this beam will use. If one doesn't exist it will be created here.</param>
  58. /// <param name="bodyState">Optional sprite state for the <see cref="prototype"/> if it needs a dynamic one</param>
  59. /// <param name="shader">Optional shader for the <see cref="prototype"/> and <see cref="bodyState"/> if it needs something other than default</param>
  60. private void CreateBeam(string prototype,
  61. Angle userAngle,
  62. Vector2 calculatedDistance,
  63. MapCoordinates beamStartPos,
  64. Vector2 distanceCorrection,
  65. EntityUid? controller,
  66. string? bodyState = null,
  67. string shader = "unshaded")
  68. {
  69. var beamSpawnPos = beamStartPos;
  70. var ent = Spawn(prototype, beamSpawnPos);
  71. var shape = new EdgeShape(distanceCorrection, new Vector2(0,0));
  72. if (!TryComp<PhysicsComponent>(ent, out var physics) || !TryComp<BeamComponent>(ent, out var beam))
  73. return;
  74. FixturesComponent? manager = null;
  75. _fixture.TryCreateFixture(
  76. ent,
  77. shape,
  78. "BeamBody",
  79. hard: false,
  80. collisionMask: (int)CollisionGroup.ItemMask,
  81. collisionLayer: (int)CollisionGroup.MobLayer,
  82. manager: manager,
  83. body: physics);
  84. _physics.SetBodyType(ent, BodyType.Dynamic, manager: manager, body: physics);
  85. _physics.SetCanCollide(ent, true, manager: manager, body: physics);
  86. _broadphase.RegenerateContacts((ent, physics, manager));
  87. var distanceLength = distanceCorrection.Length();
  88. var beamVisualizerEvent = new BeamVisualizerEvent(GetNetEntity(ent), distanceLength, userAngle, bodyState, shader);
  89. RaiseNetworkEvent(beamVisualizerEvent);
  90. if (controller != null)
  91. beam.VirtualBeamController = controller;
  92. else
  93. {
  94. var controllerEnt = Spawn("VirtualBeamEntityController", beamSpawnPos);
  95. beam.VirtualBeamController = controllerEnt;
  96. _audio.PlayPvs(beam.Sound, ent);
  97. var beamControllerCreatedEvent = new BeamControllerCreatedEvent(ent, controllerEnt);
  98. RaiseLocalEvent(controllerEnt, beamControllerCreatedEvent);
  99. }
  100. //Create the rest of the beam, sprites handled through the BeamVisualizerEvent
  101. for (var i = 0; i < distanceLength-1; i++)
  102. {
  103. beamSpawnPos = beamSpawnPos.Offset(calculatedDistance.Normalized());
  104. var newEnt = Spawn(prototype, beamSpawnPos);
  105. var ev = new BeamVisualizerEvent(GetNetEntity(newEnt), distanceLength, userAngle, bodyState, shader);
  106. RaiseNetworkEvent(ev);
  107. }
  108. var beamFiredEvent = new BeamFiredEvent(ent);
  109. RaiseLocalEvent(beam.VirtualBeamController.Value, beamFiredEvent);
  110. }
  111. /// <summary>
  112. /// Called where you want an entity to create a beam from one target to another.
  113. /// Tries to create the beam and does calculations like the distance, angle, and offset.
  114. /// </summary>
  115. /// <param name="user">The entity that's firing off the beam</param>
  116. /// <param name="target">The entity that's being targeted by the user</param>
  117. /// <param name="bodyPrototype">The prototype spawned when this beam is created</param>
  118. /// <param name="bodyState">Optional sprite state for the <see cref="bodyPrototype"/> if a default one is not given</param>
  119. /// <param name="shader">Optional shader for the <see cref="bodyPrototype"/> if a default one is not given</param>
  120. /// <param name="controller"></param>
  121. public void TryCreateBeam(EntityUid user, EntityUid target, string bodyPrototype, string? bodyState = null, string shader = "unshaded", EntityUid? controller = null)
  122. {
  123. if (Deleted(user) || Deleted(target))
  124. return;
  125. var userMapPos = _transform.GetMapCoordinates(user);
  126. var targetMapPos = _transform.GetMapCoordinates(target);
  127. //The distance between the target and the user.
  128. var calculatedDistance = targetMapPos.Position - userMapPos.Position;
  129. var userAngle = calculatedDistance.ToWorldAngle();
  130. if (userMapPos.MapId != targetMapPos.MapId)
  131. return;
  132. //Where the start of the beam will spawn
  133. var beamStartPos = userMapPos.Offset(calculatedDistance.Normalized());
  134. //Don't divide by zero
  135. if (calculatedDistance.Length() == 0)
  136. return;
  137. if (controller != null && TryComp<BeamComponent>(controller, out var controllerBeamComp))
  138. {
  139. controllerBeamComp.HitTargets.Add(user);
  140. controllerBeamComp.HitTargets.Add(target);
  141. }
  142. var distanceCorrection = calculatedDistance - calculatedDistance.Normalized();
  143. CreateBeam(bodyPrototype, userAngle, calculatedDistance, beamStartPos, distanceCorrection, controller, bodyState, shader);
  144. var ev = new CreateBeamSuccessEvent(user, target);
  145. RaiseLocalEvent(user, ev);
  146. }
  147. }