using Robust.Shared.Map.Components; using Robust.Shared.Physics.Collision.Shapes; using Robust.Shared.Physics.Components; using Robust.Shared.Physics.Events; using Robust.Shared.Physics.Systems; using Content.Shared.Ghost; using Content.Shared.Singularity.Components; namespace Content.Shared.Singularity.EntitySystems; /// /// The entity system primarily responsible for managing s. /// public abstract class SharedEventHorizonSystem : EntitySystem { #region Dependencies [Dependency] private readonly FixtureSystem _fixtures = default!; [Dependency] protected readonly IViewVariablesManager Vvm = default!; #endregion Dependencies public override void Initialize() { base.Initialize(); // Allows for predicted collisions with singularities. SubscribeLocalEvent(OnEventHorizonStartup); SubscribeLocalEvent(OnPreventCollide); var vvHandle = Vvm.GetTypeHandler(); vvHandle.AddPath(nameof(EventHorizonComponent.Radius), (_, comp) => comp.Radius, (uid, value, comp) => SetRadius(uid, value, eventHorizon: comp)); vvHandle.AddPath(nameof(EventHorizonComponent.CanBreachContainment), (_, comp) => comp.CanBreachContainment, (uid, value, comp) => SetCanBreachContainment(uid, value, eventHorizon: comp)); vvHandle.AddPath(nameof(EventHorizonComponent.HorizonFixtureId), (_, comp) => comp.HorizonFixtureId, (uid, value, comp) => SetHorizonFixtureId(uid, value, eventHorizon: comp)); } public override void Shutdown() { var vvHandle = Vvm.GetTypeHandler(); vvHandle.RemovePath(nameof(EventHorizonComponent.Radius)); vvHandle.RemovePath(nameof(EventHorizonComponent.CanBreachContainment)); vvHandle.RemovePath(nameof(EventHorizonComponent.HorizonFixtureId)); base.Shutdown(); } #region Getters/Setters /// /// Setter for /// May also update the fixture associated with the event horizon. /// /// The uid of the event horizon to change the radius of. /// The new radius of the event horizon. /// Whether to update the associated fixture upon changing the radius of the event horizon. /// The state of the event horizon to change the radius of. public void SetRadius(EntityUid uid, float value, bool updateFixture = true, EventHorizonComponent? eventHorizon = null) { if(!Resolve(uid, ref eventHorizon)) return; var oldValue = eventHorizon.Radius; if (value == oldValue) return; eventHorizon.Radius = value; EntityManager.Dirty(eventHorizon); if (updateFixture) UpdateEventHorizonFixture(uid, eventHorizon: eventHorizon); } /// /// Setter for /// May also update the fixture associated with the event horizon. /// /// The uid of the event horizon to make (in)capable of breaching containment. /// Whether the event horizon should be able to breach containment. /// Whether to update the associated fixture upon changing whether the event horizon can breach containment. /// The state of the event horizon to make (in)capable of breaching containment. public void SetCanBreachContainment(EntityUid uid, bool value, bool updateFixture = true, EventHorizonComponent? eventHorizon = null) { if(!Resolve(uid, ref eventHorizon)) return; var oldValue = eventHorizon.CanBreachContainment; if (value == oldValue) return; eventHorizon.CanBreachContainment = value; EntityManager.Dirty(eventHorizon); if (updateFixture) UpdateEventHorizonFixture(uid, eventHorizon: eventHorizon); } /// /// Setter for /// May also update the fixture associated with the event horizon. /// /// The uid of the event horizon with the fixture ID to change. /// The new fixture ID to associate the event horizon with. /// Whether to update the associated fixture upon changing whether the event horizon can breach containment. /// The state of the event horizon with the fixture ID to change. public void SetHorizonFixtureId(EntityUid uid, string? value, bool updateFixture = true, EventHorizonComponent? eventHorizon = null) { if(!Resolve(uid, ref eventHorizon)) return; var oldValue = eventHorizon.HorizonFixtureId; if (value == oldValue) return; eventHorizon.HorizonFixtureId = value; EntityManager.Dirty(eventHorizon); if (updateFixture) UpdateEventHorizonFixture(uid, eventHorizon: eventHorizon); } /// /// Updates the state of the fixture associated with the event horizon. /// /// The uid of the event horizon associated with the fixture to update. /// The physics component containing the fixture to update. /// The state of the event horizon associated with the fixture to update. public void UpdateEventHorizonFixture(EntityUid uid, PhysicsComponent? fixtures = null, EventHorizonComponent? eventHorizon = null) { if(!Resolve(uid, ref eventHorizon)) return; var fixtureId = eventHorizon.HorizonFixtureId; if (fixtureId == null || !Resolve(eventHorizon.Owner, ref fixtures, logMissing: false)) return; var fixture = _fixtures.GetFixtureOrNull(fixtures, fixtureId); if (fixture == null) return; var shape = (PhysShapeCircle)fixture.Shape; shape.Radius = eventHorizon.Radius; fixture.Hard = !eventHorizon.CanBreachContainment; EntityManager.Dirty(fixtures); } #endregion Getters/Setters #region EventHandlers /// /// Syncs the state of the fixture associated with the event horizon upon startup. /// /// The entity that has just gained an event horizon component. /// The event horizon component that is starting up. /// The event arguments. private void OnEventHorizonStartup(EntityUid uid, EventHorizonComponent comp, ComponentStartup args) { UpdateEventHorizonFixture(uid, eventHorizon: comp); } /// /// Prevents the event horizon from colliding with anything it cannot consume. /// Most notably map grids and ghosts. /// Also makes event horizons phase through containment if it can breach. /// /// The entity that is trying to collide with another entity. /// The event horizon of the former. /// The event arguments. private void OnPreventCollide(EntityUid uid, EventHorizonComponent comp, ref PreventCollideEvent args) { if(!args.Cancelled) PreventCollide(uid, comp, ref args); } /// /// The actual, functional part of SharedEventHorizonSystem.OnPreventCollide. /// The return value allows for overrides to early return if the base successfully handles collision prevention. /// /// The entity that is trying to collide with another entity. /// The event horizon of the former. /// The event arguments. /// A bool indicating whether the collision prevention has been handled. protected virtual bool PreventCollide(EntityUid uid, EventHorizonComponent comp, ref PreventCollideEvent args) { var otherUid = args.BodyB.Owner; // For prediction reasons always want the client to ignore these. if (EntityManager.HasComponent(otherUid) || EntityManager.HasComponent(otherUid)) { args.Cancelled = true; return true; } // If we can, breach containment // otherwise, check if it's containment and just keep the collision if (EntityManager.HasComponent(otherUid) || EntityManager.HasComponent(otherUid)) { if (comp.CanBreachContainment) args.Cancelled = true; return true; } return false; } #endregion EventHandlers }