EVENT BASED WEIGHTLESSNESS (#37971)

* Init Commit

* Typos

* Commit 2

* Save Interaction Test Mob from failing

* ssss

* Confident I've gotten all the correct prototypes

* Whoops forgot to edit those

* aaaaa

* Better solution

* Test fail fixes

* Yaml fix

* THE FINAL TEST FIX

* Final fix(?)

* whoops

* Added a WeightlessnessChangedEvent

* Check out this diff

* Wait I'm dumb

* Final optimization and don't duplicate code

* Death to IsWeightless

* File scoped namespaces

* REVIEW

* Fix test fails

* FIX TEST FAILS REAL

* A

* Commit of doom

* borgar

* We don't need to specify on map init apparently

* Fuck it

* LOAD BEARING COMMENT

---------

Co-authored-by: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com>
This commit is contained in:
Princess Cheeseballs
2025-08-19 11:35:09 -07:00
committed by GitHub
parent 5cd9ba6016
commit 9de76e70c7
31 changed files with 329 additions and 268 deletions

View File

@@ -1,171 +1,254 @@
using Content.Shared.Alert;
using Content.Shared.Inventory;
using Content.Shared.Movement.Components;
using Robust.Shared.GameStates;
using Content.Shared.Throwing;
using Content.Shared.Weapons.Ranged.Systems;
using Robust.Shared.Map;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Events;
using Robust.Shared.Prototypes;
using Robust.Shared.Serialization;
using Robust.Shared.Timing;
namespace Content.Shared.Gravity
namespace Content.Shared.Gravity;
public abstract partial class SharedGravitySystem : EntitySystem
{
public abstract partial class SharedGravitySystem : EntitySystem
[Dependency] protected readonly IGameTiming Timing = default!;
[Dependency] private readonly AlertsSystem _alerts = default!;
public static readonly ProtoId<AlertPrototype> WeightlessAlert = "Weightless";
protected EntityQuery<GravityComponent> GravityQuery;
private EntityQuery<GravityAffectedComponent> _weightlessQuery;
private EntityQuery<PhysicsComponent> _physicsQuery;
public override void Initialize()
{
[Dependency] protected readonly IGameTiming Timing = default!;
[Dependency] private readonly AlertsSystem _alerts = default!;
base.Initialize();
// Grid Gravity
SubscribeLocalEvent<GridInitializeEvent>(OnGridInit);
SubscribeLocalEvent<GravityChangedEvent>(OnGravityChange);
public static readonly ProtoId<AlertPrototype> WeightlessAlert = "Weightless";
// Weightlessness
SubscribeLocalEvent<GravityAffectedComponent, MapInitEvent>(OnMapInit);
SubscribeLocalEvent<GravityAffectedComponent, EntParentChangedMessage>(OnEntParentChanged);
SubscribeLocalEvent<GravityAffectedComponent, PhysicsBodyTypeChangedEvent>(OnBodyTypeChanged);
private EntityQuery<GravityComponent> _gravityQuery;
// Alerts
SubscribeLocalEvent<AlertSyncEvent>(OnAlertsSync);
SubscribeLocalEvent<AlertsComponent, WeightlessnessChangedEvent>(OnWeightlessnessChanged);
SubscribeLocalEvent<AlertsComponent, EntParentChangedMessage>(OnAlertsParentChange);
public bool IsWeightless(EntityUid uid, PhysicsComponent? body = null, TransformComponent? xform = null)
{
Resolve(uid, ref body, false);
// Impulse
SubscribeLocalEvent<GravityAffectedComponent, ShooterImpulseEvent>(OnShooterImpulse);
SubscribeLocalEvent<GravityAffectedComponent, ThrowerImpulseEvent>(OnThrowerImpulse);
if ((body?.BodyType & (BodyType.Static | BodyType.Kinematic)) != 0)
return false;
GravityQuery = GetEntityQuery<GravityComponent>();
_weightlessQuery = GetEntityQuery<GravityAffectedComponent>();
_physicsQuery = GetEntityQuery<PhysicsComponent>();
}
if (TryComp<MovementIgnoreGravityComponent>(uid, out var ignoreGravityComponent))
return ignoreGravityComponent.Weightless;
public override void Update(float frameTime)
{
base.Update(frameTime);
UpdateShake();
}
var ev = new IsWeightlessEvent(uid);
RaiseLocalEvent(uid, ref ev);
if (ev.Handled)
return ev.IsWeightless;
public bool IsWeightless(Entity<GravityAffectedComponent?> entity)
{
// If we can be weightless and are weightless, return true, otherwise return false
return _weightlessQuery.Resolve(entity, ref entity.Comp, false) && entity.Comp.Weightless;
}
if (!Resolve(uid, ref xform))
return true;
private bool GetWeightless(Entity<GravityAffectedComponent, PhysicsComponent?> entity)
{
if (!_physicsQuery.Resolve(entity, ref entity.Comp2, false))
return false;
// If grid / map has gravity
if (EntityGridOrMapHaveGravity((uid, xform)))
return false;
if (entity.Comp2.BodyType is BodyType.Static or BodyType.Kinematic)
return false;
// Check if something other than the grid or map is overriding our gravity
var ev = new IsWeightlessEvent();
RaiseLocalEvent(entity, ref ev);
if (ev.Handled)
return ev.IsWeightless;
return !EntityGridOrMapHaveGravity(entity.Owner);
}
/// <summary>
/// Refreshes weightlessness status, needs to be called anytime it would change.
/// </summary>
/// <param name="entity">The entity we are updating the weightless status of</param>
public void RefreshWeightless(Entity<GravityAffectedComponent?> entity)
{
if (!_weightlessQuery.Resolve(entity, ref entity.Comp))
return;
UpdateWeightless(entity!);
}
/// <summary>
/// Overload of <see cref="RefreshWeightless(Entity{GravityAffectedComponent?})"/> which also takes a bool for the weightlessness value we want to change to.
/// This method is LOAD BEARING for UninitializedSaveTest. DO NOT REMOVE IT.
/// </summary>
/// <param name="entity">The entity we are updating the weightless status of</param>
/// <param name="weightless">The weightless value we are trying to change to, helps avoid needless networking</param>
public void RefreshWeightless(Entity<GravityAffectedComponent?> entity, bool weightless)
{
if (!_weightlessQuery.Resolve(entity, ref entity.Comp))
return;
// Only update if we're changing our weightless status
if (entity.Comp.Weightless == weightless)
return;
UpdateWeightless(entity!);
}
private void UpdateWeightless(Entity<GravityAffectedComponent> entity)
{
var newWeightless = GetWeightless(entity);
// Don't network or raise events if it's not changing
if (newWeightless == entity.Comp.Weightless)
return;
entity.Comp.Weightless = newWeightless;
Dirty(entity);
var ev = new WeightlessnessChangedEvent(entity.Comp.Weightless);
RaiseLocalEvent(entity, ref ev);
}
private void OnMapInit(Entity<GravityAffectedComponent> entity, ref MapInitEvent args)
{
RefreshWeightless((entity.Owner, entity.Comp));
}
private void OnWeightlessnessChanged(Entity<AlertsComponent> entity, ref WeightlessnessChangedEvent args)
{
if (args.Weightless)
_alerts.ShowAlert(entity, WeightlessAlert);
else
_alerts.ClearAlert(entity, WeightlessAlert);
}
private void OnEntParentChanged(Entity<GravityAffectedComponent> entity, ref EntParentChangedMessage args)
{
// If we've moved but are still on the same grid, then don't do anything.
if (args.OldParent == args.Transform.GridUid)
return;
RefreshWeightless((entity.Owner, entity.Comp), !EntityGridOrMapHaveGravity((entity, args.Transform)));
}
private void OnBodyTypeChanged(Entity<GravityAffectedComponent> entity, ref PhysicsBodyTypeChangedEvent args)
{
// No need to update weightlessness if we're not weightless and we're a body type that can't be weightless
if (args.New is BodyType.Static or BodyType.Kinematic && entity.Comp.Weightless == false)
return;
RefreshWeightless((entity.Owner, entity.Comp));
}
/// <summary>
/// Checks if a given entity is currently standing on a grid or map that supports having gravity at all.
/// </summary>
public bool EntityOnGravitySupportingGridOrMap(Entity<TransformComponent?> entity)
{
entity.Comp ??= Transform(entity);
return GravityQuery.HasComp(entity.Comp.GridUid) ||
GravityQuery.HasComp(entity.Comp.MapUid);
}
/// <summary>
/// Checks if a given entity is currently standing on a grid or map that has gravity of some kind.
/// </summary>
public bool EntityGridOrMapHaveGravity(Entity<TransformComponent?> entity)
{
entity.Comp ??= Transform(entity);
// DO NOT SET TO WEIGHTLESS IF THEY'RE IN NULL-SPACE
// TODO: If entities actually properly pause when leaving PVS rather than entering null-space this can probably go.
if (entity.Comp.MapID == MapId.Nullspace)
return true;
}
/// <summary>
/// Checks if a given entity is currently standing on a grid or map that supports having gravity at all.
/// </summary>
public bool EntityOnGravitySupportingGridOrMap(Entity<TransformComponent?> entity)
return GravityQuery.TryComp(entity.Comp.GridUid, out var gravity) && gravity.Enabled ||
GravityQuery.TryComp(entity.Comp.MapUid, out var mapGravity) && mapGravity.Enabled;
}
private void OnGravityChange(ref GravityChangedEvent args)
{
var gravity = AllEntityQuery<GravityAffectedComponent, TransformComponent>();
while(gravity.MoveNext(out var uid, out var weightless, out var xform))
{
entity.Comp ??= Transform(entity);
if (xform.GridUid != args.ChangedGridIndex)
continue;
return _gravityQuery.HasComp(entity.Comp.GridUid) ||
_gravityQuery.HasComp(entity.Comp.MapUid);
}
/// <summary>
/// Checks if a given entity is currently standing on a grid or map that has gravity of some kind.
/// </summary>
public bool EntityGridOrMapHaveGravity(Entity<TransformComponent?> entity)
{
entity.Comp ??= Transform(entity);
return _gravityQuery.TryComp(entity.Comp.GridUid, out var gravity) && gravity.Enabled ||
_gravityQuery.TryComp(entity.Comp.MapUid, out var mapGravity) && mapGravity.Enabled;
}
public override void Initialize()
{
base.Initialize();
SubscribeLocalEvent<GridInitializeEvent>(OnGridInit);
SubscribeLocalEvent<AlertSyncEvent>(OnAlertsSync);
SubscribeLocalEvent<AlertsComponent, EntParentChangedMessage>(OnAlertsParentChange);
SubscribeLocalEvent<GravityChangedEvent>(OnGravityChange);
SubscribeLocalEvent<GravityComponent, ComponentGetState>(OnGetState);
SubscribeLocalEvent<GravityComponent, ComponentHandleState>(OnHandleState);
_gravityQuery = GetEntityQuery<GravityComponent>();
}
public override void Update(float frameTime)
{
base.Update(frameTime);
UpdateShake();
}
private void OnHandleState(EntityUid uid, GravityComponent component, ref ComponentHandleState args)
{
if (args.Current is not GravityComponentState state)
return;
if (component.EnabledVV == state.Enabled)
return;
component.EnabledVV = state.Enabled;
var ev = new GravityChangedEvent(uid, component.EnabledVV);
RaiseLocalEvent(uid, ref ev, true);
}
private void OnGetState(EntityUid uid, GravityComponent component, ref ComponentGetState args)
{
args.State = new GravityComponentState(component.EnabledVV);
}
private void OnGravityChange(ref GravityChangedEvent ev)
{
var alerts = AllEntityQuery<AlertsComponent, TransformComponent>();
while(alerts.MoveNext(out var uid, out _, out var xform))
{
if (xform.GridUid != ev.ChangedGridIndex)
continue;
if (!ev.HasGravity)
{
_alerts.ShowAlert(uid, WeightlessAlert);
}
else
{
_alerts.ClearAlert(uid, WeightlessAlert);
}
}
}
private void OnAlertsSync(AlertSyncEvent ev)
{
if (IsWeightless(ev.Euid))
{
_alerts.ShowAlert(ev.Euid, WeightlessAlert);
}
else
{
_alerts.ClearAlert(ev.Euid, WeightlessAlert);
}
}
private void OnAlertsParentChange(EntityUid uid, AlertsComponent component, ref EntParentChangedMessage args)
{
if (IsWeightless(uid))
{
_alerts.ShowAlert(uid, WeightlessAlert);
}
else
{
_alerts.ClearAlert(uid, WeightlessAlert);
}
}
private void OnGridInit(GridInitializeEvent ev)
{
EnsureComp<GravityComponent>(ev.EntityUid);
}
[Serializable, NetSerializable]
private sealed class GravityComponentState : ComponentState
{
public bool Enabled { get; }
public GravityComponentState(bool enabled)
{
Enabled = enabled;
}
RefreshWeightless((uid, weightless), !args.HasGravity);
}
}
[ByRefEvent]
public record struct IsWeightlessEvent(EntityUid Entity, bool IsWeightless = false, bool Handled = false) : IInventoryRelayEvent
private void OnAlertsSync(AlertSyncEvent ev)
{
SlotFlags IInventoryRelayEvent.TargetSlots => ~SlotFlags.POCKET;
if (IsWeightless(ev.Euid))
_alerts.ShowAlert(ev.Euid, WeightlessAlert);
else
_alerts.ClearAlert(ev.Euid, WeightlessAlert);
}
private void OnAlertsParentChange(EntityUid uid, AlertsComponent component, ref EntParentChangedMessage args)
{
if (IsWeightless(uid))
_alerts.ShowAlert(uid, WeightlessAlert);
else
_alerts.ClearAlert(uid, WeightlessAlert);
}
private void OnGridInit(GridInitializeEvent ev)
{
EnsureComp<GravityComponent>(ev.EntityUid);
}
[Serializable, NetSerializable]
private sealed class GravityComponentState : ComponentState
{
public bool Enabled { get; }
public GravityComponentState(bool enabled)
{
Enabled = enabled;
}
}
private void OnThrowerImpulse(Entity<GravityAffectedComponent> entity, ref ThrowerImpulseEvent args)
{
args.Push = true;
}
private void OnShooterImpulse(Entity<GravityAffectedComponent> entity, ref ShooterImpulseEvent args)
{
args.Push = true;
}
}
/// <summary>
/// Raised to determine if an entity's weightlessness is being overwritten by a component or item with a component.
/// </summary>
/// <param name="IsWeightless">Whether we should be weightless</param>
/// <param name="Handled">Whether something is trying to override our weightlessness</param>
[ByRefEvent]
public record struct IsWeightlessEvent(bool IsWeightless = false, bool Handled = false) : IInventoryRelayEvent
{
SlotFlags IInventoryRelayEvent.TargetSlots => ~SlotFlags.POCKET;
}
/// <summary>
/// Raised on an entity when their weightless status changes.
/// </summary>
[ByRefEvent]
public readonly record struct WeightlessnessChangedEvent(bool Weightless);