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; using Robust.Shared.Physics; namespace Content.Shared.Singularity.EntitySystems; /// /// The entity system primarily responsible for managing s. /// public abstract class SharedEventHorizonSystem : EntitySystem { [Dependency] private readonly FixtureSystem _fixtures = default!; [Dependency] private readonly SharedPhysicsSystem _physics = default!; [Dependency] protected readonly IViewVariablesManager Vvm = default!; 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.ColliderFixtureId), (_, comp) => comp.ColliderFixtureId, (uid, value, comp) => SetColliderFixtureId(uid, value, eventHorizon: comp)); vvHandle.AddPath(nameof(EventHorizonComponent.ConsumerFixtureId), (_, comp) => comp.ConsumerFixtureId, (uid, value, comp) => SetConsumerFixtureId(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.ColliderFixtureId)); vvHandle.RemovePath(nameof(EventHorizonComponent.ConsumerFixtureId)); 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; Dirty(uid, 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; Dirty(uid, 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 SetColliderFixtureId(EntityUid uid, string? value, bool updateFixture = true, EventHorizonComponent? eventHorizon = null) { if (!Resolve(uid, ref eventHorizon)) return; var oldValue = eventHorizon.ColliderFixtureId; if (value == oldValue) return; eventHorizon.ColliderFixtureId = value; Dirty(uid, 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 SetConsumerFixtureId(EntityUid uid, string? value, bool updateFixture = true, EventHorizonComponent? eventHorizon = null) { if (!Resolve(uid, ref eventHorizon)) return; var oldValue = eventHorizon.ConsumerFixtureId; if (value == oldValue) return; eventHorizon.ConsumerFixtureId = value; Dirty(uid, 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 fixture manager component containing the fixture to update. /// The state of the event horizon associated with the fixture to update. public void UpdateEventHorizonFixture(EntityUid uid, FixturesComponent? fixtures = null, EventHorizonComponent? eventHorizon = null) { if (!Resolve(uid, ref eventHorizon)) return; var consumerId = eventHorizon.ConsumerFixtureId; var colliderId = eventHorizon.ColliderFixtureId; if (consumerId == null || colliderId == null || !Resolve(uid, ref fixtures, logMissing: false)) return; // Update both fixtures the event horizon is associated with: var consumer = _fixtures.GetFixtureOrNull(uid, consumerId, fixtures); if (consumer != null) { _physics.SetRadius(uid, consumerId, consumer, consumer.Shape, eventHorizon.Radius, fixtures); _physics.SetHard(uid, consumer, false, fixtures); } var collider = _fixtures.GetFixtureOrNull(uid, colliderId, fixtures); if (collider != null) { _physics.SetRadius(uid, colliderId, collider, collider.Shape, eventHorizon.Radius, fixtures); _physics.SetHard(uid, collider, true, fixtures); } EntityManager.Dirty(uid, 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.OtherEntity; // For prediction reasons always want the client to ignore these. if (HasComp(otherUid) || HasComp(otherUid)) { args.Cancelled = true; return true; } // If we can, breach containment // otherwise, check if it's containment and just keep the collision if (HasComp(otherUid) || HasComp(otherUid)) { if (comp.CanBreachContainment) args.Cancelled = true; return true; } return false; } #endregion EventHandlers }