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

@@ -21,6 +21,7 @@ namespace Content.IntegrationTests.Tests.Doors
components: components:
- type: Physics - type: Physics
bodyType: Dynamic bodyType: Dynamic
- type: GravityAffected
- type: Fixtures - type: Fixtures
fixtures: fixtures:
fix1: fix1:

View File

@@ -19,6 +19,7 @@ namespace Content.IntegrationTests.Tests.Gravity
- type: Alerts - type: Alerts
- type: Physics - type: Physics
bodyType: Dynamic bodyType: Dynamic
- type: GravityAffected
- type: entity - type: entity
name: WeightlessGravityGeneratorDummy name: WeightlessGravityGeneratorDummy

View File

@@ -76,8 +76,8 @@ namespace Content.IntegrationTests.Tests
Assert.Multiple(() => Assert.Multiple(() =>
{ {
Assert.That(generatorComponent.GravityActive, Is.True); Assert.That(generatorComponent.GravityActive, Is.True);
Assert.That(!entityMan.GetComponent<GravityComponent>(grid1).EnabledVV); Assert.That(!entityMan.GetComponent<GravityComponent>(grid1).Enabled);
Assert.That(entityMan.GetComponent<GravityComponent>(grid2).EnabledVV); Assert.That(entityMan.GetComponent<GravityComponent>(grid2).Enabled);
}); });
// Re-enable needs power so it turns off again. // Re-enable needs power so it turns off again.
@@ -94,7 +94,7 @@ namespace Content.IntegrationTests.Tests
Assert.Multiple(() => Assert.Multiple(() =>
{ {
Assert.That(generatorComponent.GravityActive, Is.False); Assert.That(generatorComponent.GravityActive, Is.False);
Assert.That(entityMan.GetComponent<GravityComponent>(grid2).EnabledVV, Is.False); Assert.That(entityMan.GetComponent<GravityComponent>(grid2).Enabled, Is.False);
}); });
}); });

View File

@@ -144,6 +144,7 @@ public abstract partial class InteractionTest
- type: Stripping - type: Stripping
- type: Puller - type: Puller
- type: Physics - type: Physics
- type: GravityAffected
- type: Tag - type: Tag
tags: tags:
- CanPilot - CanPilot

View File

@@ -29,6 +29,7 @@ using Content.Shared.Damage;
using Content.Shared.Damage.Systems; using Content.Shared.Damage.Systems;
using Content.Shared.Database; using Content.Shared.Database;
using Content.Shared.Electrocution; using Content.Shared.Electrocution;
using Content.Shared.Gravity;
using Content.Shared.Interaction.Components; using Content.Shared.Interaction.Components;
using Content.Shared.Inventory; using Content.Shared.Inventory;
using Content.Shared.Mobs; using Content.Shared.Mobs;
@@ -675,6 +676,11 @@ public sealed partial class AdminVerbSystem
grav.Weightless = true; grav.Weightless = true;
Dirty(args.Target, grav); Dirty(args.Target, grav);
EnsureComp<GravityAffectedComponent>(args.Target, out var weightless);
weightless.Weightless = true;
Dirty(args.Target, weightless);
}, },
Impact = LogImpact.Extreme, Impact = LogImpact.Extreme,
Message = string.Join(": ", noGravityName, Loc.GetString("admin-smite-remove-gravity-description")) Message = string.Join(": ", noGravityName, Loc.GetString("admin-smite-remove-gravity-description"))

View File

@@ -166,7 +166,7 @@ public sealed class SpraySystem : EntitySystem
if (TryComp<PhysicsComponent>(user, out var body)) if (TryComp<PhysicsComponent>(user, out var body))
{ {
if (_gravity.IsWeightless(user, body)) if (_gravity.IsWeightless(user))
{ {
// push back the player // push back the player
_physics.ApplyLinearImpulse(user, -impulseDirection * entity.Comp.PushbackAmount, body: body); _physics.ApplyLinearImpulse(user, -impulseDirection * entity.Comp.PushbackAmount, body: body);

View File

@@ -18,7 +18,7 @@ namespace Content.Server.Gravity
/// </summary> /// </summary>
public void RefreshGravity(EntityUid uid, GravityComponent? gravity = null) public void RefreshGravity(EntityUid uid, GravityComponent? gravity = null)
{ {
if (!Resolve(uid, ref gravity)) if (!GravityQuery.Resolve(uid, ref gravity))
return; return;
if (gravity.Inherent) if (gravity.Inherent)
@@ -61,7 +61,7 @@ namespace Content.Server.Gravity
/// </summary> /// </summary>
public void EnableGravity(EntityUid uid, GravityComponent? gravity = null) public void EnableGravity(EntityUid uid, GravityComponent? gravity = null)
{ {
if (!Resolve(uid, ref gravity)) if (!GravityQuery.Resolve(uid, ref gravity))
return; return;
if (gravity.Enabled || gravity.Inherent) if (gravity.Enabled || gravity.Inherent)

View File

@@ -6,10 +6,13 @@ namespace Content.Shared.Clothing.EntitySystems;
public sealed class AntiGravityClothingSystem : EntitySystem public sealed class AntiGravityClothingSystem : EntitySystem
{ {
[Dependency] SharedGravitySystem _gravity = default!;
/// <inheritdoc/> /// <inheritdoc/>
public override void Initialize() public override void Initialize()
{ {
SubscribeLocalEvent<AntiGravityClothingComponent, InventoryRelayedEvent<IsWeightlessEvent>>(OnIsWeightless); SubscribeLocalEvent<AntiGravityClothingComponent, InventoryRelayedEvent<IsWeightlessEvent>>(OnIsWeightless);
SubscribeLocalEvent<AntiGravityClothingComponent, ClothingGotEquippedEvent>(OnEquipped);
SubscribeLocalEvent<AntiGravityClothingComponent, ClothingGotUnequippedEvent>(OnUnequipped);
} }
private void OnIsWeightless(Entity<AntiGravityClothingComponent> ent, ref InventoryRelayedEvent<IsWeightlessEvent> args) private void OnIsWeightless(Entity<AntiGravityClothingComponent> ent, ref InventoryRelayedEvent<IsWeightlessEvent> args)
@@ -20,4 +23,14 @@ public sealed class AntiGravityClothingSystem : EntitySystem
args.Args.Handled = true; args.Args.Handled = true;
args.Args.IsWeightless = true; args.Args.IsWeightless = true;
} }
private void OnEquipped(Entity<AntiGravityClothingComponent> entity, ref ClothingGotEquippedEvent args)
{
_gravity.RefreshWeightless(args.Wearer, true);
}
private void OnUnequipped(Entity<AntiGravityClothingComponent> entity, ref ClothingGotUnequippedEvent args)
{
_gravity.RefreshWeightless(args.Wearer, false);
}
} }

View File

@@ -1,4 +1,5 @@
using Content.Shared.Alert; using Content.Shared.Alert;
using Content.Shared.Inventory;
using Robust.Shared.GameStates; using Robust.Shared.GameStates;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
@@ -16,10 +17,4 @@ public sealed partial class MagbootsComponent : Component
/// </summary> /// </summary>
[DataField] [DataField]
public bool RequiresGrid = true; public bool RequiresGrid = true;
/// <summary>
/// Slot the clothing has to be worn in to work.
/// </summary>
[DataField]
public string Slot = "shoes";
} }

View File

@@ -32,14 +32,8 @@ public sealed class SharedMagbootsSystem : EntitySystem
private void OnToggled(Entity<MagbootsComponent> ent, ref ItemToggledEvent args) private void OnToggled(Entity<MagbootsComponent> ent, ref ItemToggledEvent args)
{ {
var (uid, comp) = ent; if (_container.TryGetContainingContainer((ent.Owner, null, null), out var container))
// only stick to the floor if being worn in the correct slot
if (_container.TryGetContainingContainer((uid, null, null), out var container) &&
_inventory.TryGetSlotEntity(container.Owner, comp.Slot, out var worn)
&& uid == worn)
{
UpdateMagbootEffects(container.Owner, ent, args.Activated); UpdateMagbootEffects(container.Owner, ent, args.Activated);
}
} }
private void OnGotUnequipped(Entity<MagbootsComponent> ent, ref ClothingGotUnequippedEvent args) private void OnGotUnequipped(Entity<MagbootsComponent> ent, ref ClothingGotUnequippedEvent args)
@@ -58,6 +52,8 @@ public sealed class SharedMagbootsSystem : EntitySystem
if (TryComp<MovedByPressureComponent>(user, out var moved)) if (TryComp<MovedByPressureComponent>(user, out var moved))
moved.Enabled = !state; moved.Enabled = !state;
_gravity.RefreshWeightless(user, !state);
if (state) if (state)
_alerts.ShowAlert(user, ent.Comp.MagbootsAlert); _alerts.ShowAlert(user, ent.Comp.MagbootsAlert);
else else

View File

@@ -164,12 +164,11 @@ public abstract partial class SharedDoAfterSystem : EntitySystem
if (args.Target is { } target && !xformQuery.TryGetComponent(target, out targetXform)) if (args.Target is { } target && !xformQuery.TryGetComponent(target, out targetXform))
return true; return true;
TransformComponent? usedXform = null; if (args.Used is { } @using && !xformQuery.HasComp(@using))
if (args.Used is { } @using && !xformQuery.TryGetComponent(@using, out usedXform))
return true; return true;
// TODO: Re-use existing xform query for these calculations. // TODO: Re-use existing xform query for these calculations.
if (args.BreakOnMove && !(!args.BreakOnWeightlessMove && _gravity.IsWeightless(args.User, xform: userXform))) if (args.BreakOnMove && !(!args.BreakOnWeightlessMove && _gravity.IsWeightless(args.User)))
{ {
// Whether the user has moved too much from their original position. // Whether the user has moved too much from their original position.
if (!_transform.InRange(userXform.Coordinates, doAfter.UserPosition, args.MovementThreshold)) if (!_transform.InRange(userXform.Coordinates, doAfter.UserPosition, args.MovementThreshold))

View File

@@ -73,7 +73,7 @@ namespace Content.Shared.Friction
// If we're not touching the ground, don't use tileFriction. // If we're not touching the ground, don't use tileFriction.
// TODO: Make IsWeightless event-based; we already have grid traversals tracked so just raise events // TODO: Make IsWeightless event-based; we already have grid traversals tracked so just raise events
if (body.BodyStatus == BodyStatus.InAir || _gravity.IsWeightless(uid, body, xform) || !xform.Coordinates.IsValid(EntityManager)) if (body.BodyStatus == BodyStatus.InAir || _gravity.IsWeightless(uid) || !xform.Coordinates.IsValid(EntityManager))
friction = xform.GridUid == null || !_gridQuery.HasComp(xform.GridUid) ? _offGridDamping : _airDamping; friction = xform.GridUid == null || !_gridQuery.HasComp(xform.GridUid) ? _offGridDamping : _airDamping;
else else
friction = _frictionModifier * GetTileFriction(uid, body, xform); friction = _frictionModifier * GetTileFriction(uid, body, xform);

View File

@@ -10,20 +10,17 @@ public sealed partial class FloatingVisualsComponent : Component
/// <summary> /// <summary>
/// How long it takes to go from the bottom of the animation to the top. /// How long it takes to go from the bottom of the animation to the top.
/// </summary> /// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[DataField, AutoNetworkedField] [DataField, AutoNetworkedField]
public float AnimationTime = 2f; public float AnimationTime = 2f;
/// <summary> /// <summary>
/// How far it goes in any direction. /// How far it goes in any direction.
/// </summary> /// </summary>
[ViewVariables(VVAccess.ReadWrite)]
[DataField, AutoNetworkedField] [DataField, AutoNetworkedField]
public Vector2 Offset = new(0, 0.2f); public Vector2 Offset = new(0, 0.2f);
[ViewVariables(VVAccess.ReadWrite)] [DataField, AutoNetworkedField]
[AutoNetworkedField] public bool CanFloat;
public bool CanFloat = false;
public readonly string AnimationKey = "gravity"; public readonly string AnimationKey = "gravity";
} }

View File

@@ -0,0 +1,17 @@
using Robust.Shared.GameStates;
namespace Content.Shared.Gravity;
/// <summary>
/// This Component allows a target to be considered "weightless" when Weightless is true. Without this component, the
/// target will never be weightless.
/// </summary>
[RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class GravityAffectedComponent : Component
{
/// <summary>
/// If true, this entity will be considered "weightless"
/// </summary>
[DataField, AutoNetworkedField]
public bool Weightless = true;
}

View File

@@ -5,34 +5,20 @@ using Robust.Shared.Serialization;
namespace Content.Shared.Gravity namespace Content.Shared.Gravity
{ {
[RegisterComponent] [RegisterComponent]
[AutoGenerateComponentState]
[NetworkedComponent] [NetworkedComponent]
public sealed partial class GravityComponent : Component public sealed partial class GravityComponent : Component
{ {
[DataField("gravityShakeSound")] [DataField, AutoNetworkedField]
public SoundSpecifier GravityShakeSound { get; set; } = new SoundPathSpecifier("/Audio/Effects/alert.ogg"); public SoundSpecifier GravityShakeSound { get; set; } = new SoundPathSpecifier("/Audio/Effects/alert.ogg");
[ViewVariables(VVAccess.ReadWrite)] [DataField, AutoNetworkedField]
public bool EnabledVV
{
get => Enabled;
set
{
if (Enabled == value) return;
Enabled = value;
var ev = new GravityChangedEvent(Owner, value);
IoCManager.Resolve<IEntityManager>().EventBus.RaiseLocalEvent(Owner, ref ev);
Dirty();
}
}
[DataField("enabled")]
public bool Enabled; public bool Enabled;
/// <summary> /// <summary>
/// Inherent gravity ensures GravitySystem won't change Enabled according to the gravity generators attached to this entity. /// Inherent gravity ensures GravitySystem won't change Enabled according to the gravity generators attached to this entity.
/// </summary> /// </summary>
[ViewVariables(VVAccess.ReadWrite)] [DataField, AutoNetworkedField]
[DataField("inherent")]
public bool Inherent; public bool Inherent;
} }
} }

View File

@@ -8,15 +8,14 @@ namespace Content.Shared.Gravity;
/// </summary> /// </summary>
public abstract class SharedFloatingVisualizerSystem : EntitySystem public abstract class SharedFloatingVisualizerSystem : EntitySystem
{ {
[Dependency] private readonly SharedGravitySystem GravitySystem = default!; [Dependency] private readonly SharedGravitySystem _gravity = default!;
public override void Initialize() public override void Initialize()
{ {
base.Initialize(); base.Initialize();
SubscribeLocalEvent<FloatingVisualsComponent, ComponentStartup>(OnComponentStartup); SubscribeLocalEvent<FloatingVisualsComponent, ComponentStartup>(OnComponentStartup);
SubscribeLocalEvent<GravityChangedEvent>(OnGravityChanged); SubscribeLocalEvent<FloatingVisualsComponent, WeightlessnessChangedEvent>(OnWeightlessnessChanged);
SubscribeLocalEvent<FloatingVisualsComponent, EntParentChangedMessage>(OnEntParentChanged);
} }
/// <summary> /// <summary>
@@ -24,48 +23,28 @@ public abstract class SharedFloatingVisualizerSystem : EntitySystem
/// </summary> /// </summary>
public virtual void FloatAnimation(EntityUid uid, Vector2 offset, string animationKey, float animationTime, bool stop = false) { } public virtual void FloatAnimation(EntityUid uid, Vector2 offset, string animationKey, float animationTime, bool stop = false) { }
protected bool CanFloat(EntityUid uid, FloatingVisualsComponent component, TransformComponent? transform = null) protected bool CanFloat(Entity<FloatingVisualsComponent> entity)
{ {
if (!Resolve(uid, ref transform)) entity.Comp.CanFloat = _gravity.IsWeightless(entity.Owner);
return false; Dirty(entity);
return entity.Comp.CanFloat;
if (transform.MapID == MapId.Nullspace)
return false;
component.CanFloat = GravitySystem.IsWeightless(uid, xform: transform);
Dirty(uid, component);
return component.CanFloat;
} }
private void OnComponentStartup(EntityUid uid, FloatingVisualsComponent component, ComponentStartup args) private void OnComponentStartup(Entity<FloatingVisualsComponent> entity, ref ComponentStartup args)
{ {
if (CanFloat(uid, component)) if (CanFloat(entity))
FloatAnimation(uid, component.Offset, component.AnimationKey, component.AnimationTime); FloatAnimation(entity, entity.Comp.Offset, entity.Comp.AnimationKey, entity.Comp.AnimationTime);
} }
private void OnGravityChanged(ref GravityChangedEvent args) private void OnWeightlessnessChanged(Entity<FloatingVisualsComponent> entity, ref WeightlessnessChangedEvent args)
{ {
var query = EntityQueryEnumerator<FloatingVisualsComponent, TransformComponent>(); if (entity.Comp.CanFloat == args.Weightless)
while (query.MoveNext(out var uid, out var floating, out var transform)) return;
{
if (transform.MapID == MapId.Nullspace)
continue;
if (transform.GridUid != args.ChangedGridIndex) entity.Comp.CanFloat = CanFloat(entity);
continue; Dirty(entity);
floating.CanFloat = !args.HasGravity; if (args.Weightless)
Dirty(uid, floating); FloatAnimation(entity, entity.Comp.Offset, entity.Comp.AnimationKey, entity.Comp.AnimationTime);
if (!args.HasGravity)
FloatAnimation(uid, floating.Offset, floating.AnimationKey, floating.AnimationTime);
}
}
private void OnEntParentChanged(EntityUid uid, FloatingVisualsComponent component, ref EntParentChangedMessage args)
{
var transform = args.Transform;
if (CanFloat(uid, component, transform))
FloatAnimation(uid, component.Offset, component.AnimationKey, component.AnimationTime);
} }
} }

View File

@@ -1,171 +1,254 @@
using Content.Shared.Alert; using Content.Shared.Alert;
using Content.Shared.Inventory; using Content.Shared.Inventory;
using Content.Shared.Movement.Components; using Content.Shared.Throwing;
using Robust.Shared.GameStates; using Content.Shared.Weapons.Ranged.Systems;
using Robust.Shared.Map;
using Robust.Shared.Physics; using Robust.Shared.Physics;
using Robust.Shared.Physics.Components; using Robust.Shared.Physics.Components;
using Robust.Shared.Physics.Events;
using Robust.Shared.Prototypes; using Robust.Shared.Prototypes;
using Robust.Shared.Serialization; using Robust.Shared.Serialization;
using Robust.Shared.Timing; 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!; base.Initialize();
[Dependency] private readonly AlertsSystem _alerts = default!; // 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) // Impulse
{ SubscribeLocalEvent<GravityAffectedComponent, ShooterImpulseEvent>(OnShooterImpulse);
Resolve(uid, ref body, false); SubscribeLocalEvent<GravityAffectedComponent, ThrowerImpulseEvent>(OnThrowerImpulse);
if ((body?.BodyType & (BodyType.Static | BodyType.Kinematic)) != 0) GravityQuery = GetEntityQuery<GravityComponent>();
return false; _weightlessQuery = GetEntityQuery<GravityAffectedComponent>();
_physicsQuery = GetEntityQuery<PhysicsComponent>();
}
if (TryComp<MovementIgnoreGravityComponent>(uid, out var ignoreGravityComponent)) public override void Update(float frameTime)
return ignoreGravityComponent.Weightless; {
base.Update(frameTime);
UpdateShake();
}
var ev = new IsWeightlessEvent(uid); public bool IsWeightless(Entity<GravityAffectedComponent?> entity)
RaiseLocalEvent(uid, ref ev); {
if (ev.Handled) // If we can be weightless and are weightless, return true, otherwise return false
return ev.IsWeightless; return _weightlessQuery.Resolve(entity, ref entity.Comp, false) && entity.Comp.Weightless;
}
if (!Resolve(uid, ref xform)) private bool GetWeightless(Entity<GravityAffectedComponent, PhysicsComponent?> entity)
return true; {
if (!_physicsQuery.Resolve(entity, ref entity.Comp2, false))
return false;
// If grid / map has gravity if (entity.Comp2.BodyType is BodyType.Static or BodyType.Kinematic)
if (EntityGridOrMapHaveGravity((uid, xform))) return false;
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; return true;
}
/// <summary> return GravityQuery.TryComp(entity.Comp.GridUid, out var gravity) && gravity.Enabled ||
/// Checks if a given entity is currently standing on a grid or map that supports having gravity at all. GravityQuery.TryComp(entity.Comp.MapUid, out var mapGravity) && mapGravity.Enabled;
/// </summary> }
public bool EntityOnGravitySupportingGridOrMap(Entity<TransformComponent?> entity)
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) || RefreshWeightless((uid, weightless), !args.HasGravity);
_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;
}
} }
} }
[ByRefEvent] private void OnAlertsSync(AlertSyncEvent ev)
public record struct IsWeightlessEvent(EntityUid Entity, bool IsWeightless = false, bool Handled = false) : IInventoryRelayEvent
{ {
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);

View File

@@ -1,34 +1,17 @@
using Content.Shared.Clothing;
using Content.Shared.Gravity;
using Content.Shared.Inventory;
using Robust.Shared.GameStates; using Robust.Shared.GameStates;
using Robust.Shared.Map;
using Robust.Shared.Physics;
using Robust.Shared.Physics.Components;
using Robust.Shared.Serialization;
namespace Content.Shared.Movement.Components namespace Content.Shared.Movement.Components
{ {
/// <summary> /// <summary>
/// Ignores gravity entirely. /// Ignores gravity entirely.
/// </summary> /// </summary>
[RegisterComponent, NetworkedComponent] [RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
public sealed partial class MovementIgnoreGravityComponent : Component public sealed partial class MovementIgnoreGravityComponent : Component
{ {
/// <summary> /// <summary>
/// Whether or not gravity is on or off for this object. /// Whether gravity is on or off for this object. This will always override the current Gravity State.
/// </summary> /// </summary>
[DataField("gravityState")] public bool Weightless = false; [DataField, AutoNetworkedField]
}
[NetSerializable, Serializable]
public sealed class MovementIgnoreGravityComponentState : ComponentState
{
public bool Weightless; public bool Weightless;
public MovementIgnoreGravityComponentState(MovementIgnoreGravityComponent component)
{
Weightless = component.Weightless;
}
} }
} }

View File

@@ -84,7 +84,7 @@ public sealed class FrictionContactsSystem : EntitySystem
var frictionNoInput = 0.0f; var frictionNoInput = 0.0f;
var acceleration = 0.0f; var acceleration = 0.0f;
var isAirborne = physicsComponent.BodyStatus == BodyStatus.InAir || _gravity.IsWeightless(entity, physicsComponent); var isAirborne = physicsComponent.BodyStatus == BodyStatus.InAir || _gravity.IsWeightless(entity.Owner);
var remove = true; var remove = true;
var entries = 0; var entries = 0;

View File

@@ -1,33 +1,35 @@
using Content.Shared.Movement.Components; using Content.Shared.Gravity;
using Content.Shared.Movement.Components;
using Content.Shared.Movement.Events; using Content.Shared.Movement.Events;
using Robust.Shared.GameStates;
namespace Content.Shared.Movement.Systems; namespace Content.Shared.Movement.Systems;
public sealed class MovementIgnoreGravitySystem : EntitySystem public sealed class MovementIgnoreGravitySystem : EntitySystem
{ {
[Dependency] SharedGravitySystem _gravity = default!;
public override void Initialize() public override void Initialize()
{ {
SubscribeLocalEvent<MovementIgnoreGravityComponent, ComponentGetState>(GetState);
SubscribeLocalEvent<MovementIgnoreGravityComponent, ComponentHandleState>(HandleState);
SubscribeLocalEvent<MovementAlwaysTouchingComponent, CanWeightlessMoveEvent>(OnWeightless); SubscribeLocalEvent<MovementAlwaysTouchingComponent, CanWeightlessMoveEvent>(OnWeightless);
SubscribeLocalEvent<MovementIgnoreGravityComponent, IsWeightlessEvent>(OnIsWeightless);
SubscribeLocalEvent<MovementIgnoreGravityComponent, ComponentStartup>(OnComponentStartup);
} }
private void OnWeightless(EntityUid uid, MovementAlwaysTouchingComponent component, ref CanWeightlessMoveEvent args) private void OnWeightless(Entity<MovementAlwaysTouchingComponent> entity, ref CanWeightlessMoveEvent args)
{ {
args.CanMove = true; args.CanMove = true;
} }
private void HandleState(EntityUid uid, MovementIgnoreGravityComponent component, ref ComponentHandleState args) private void OnIsWeightless(Entity<MovementIgnoreGravityComponent> entity, ref IsWeightlessEvent args)
{ {
if (args.Next is null) // We don't check if the event has been handled as this component takes precedent over other things.
return;
component.Weightless = ((MovementIgnoreGravityComponentState) args.Next).Weightless; args.IsWeightless = entity.Comp.Weightless;
args.Handled = true;
} }
private void GetState(EntityUid uid, MovementIgnoreGravityComponent component, ref ComponentGetState args) private void OnComponentStartup(Entity<MovementIgnoreGravityComponent> entity, ref ComponentStartup args)
{ {
args.State = new MovementIgnoreGravityComponentState(component); EnsureComp<GravityAffectedComponent>(entity);
_gravity.RefreshWeightless(entity.Owner, entity.Comp.Weightless);
} }
} }

View File

@@ -195,8 +195,7 @@ public abstract partial class SharedMoverController : VirtualController
} }
// If the body is in air but isn't weightless then it can't move // If the body is in air but isn't weightless then it can't move
// TODO: MAKE ISWEIGHTLESS EVENT BASED var weightless = _gravity.IsWeightless(uid);
var weightless = _gravity.IsWeightless(uid, physicsComponent, xform);
var inAirHelpless = false; var inAirHelpless = false;
if (physicsComponent.BodyStatus != BodyStatus.OnGround && !CanMoveInAirQuery.HasComponent(uid)) if (physicsComponent.BodyStatus != BodyStatus.OnGround && !CanMoveInAirQuery.HasComponent(uid))
@@ -624,8 +623,7 @@ public abstract partial class SharedMoverController : VirtualController
if (!TryComp<PhysicsComponent>(ent, out var physicsComponent) || !XformQuery.TryComp(ent, out var xform)) if (!TryComp<PhysicsComponent>(ent, out var physicsComponent) || !XformQuery.TryComp(ent, out var xform))
return; return;
// TODO: Make IsWeightless event based!!! if (physicsComponent.BodyStatus != BodyStatus.OnGround || _gravity.IsWeightless(ent.Owner))
if (physicsComponent.BodyStatus != BodyStatus.OnGround || _gravity.IsWeightless(ent, physicsComponent, xform))
args.Modifier *= ent.Comp.BaseWeightlessFriction; args.Modifier *= ent.Comp.BaseWeightlessFriction;
else else
args.Modifier *= ent.Comp.BaseFriction; args.Modifier *= ent.Comp.BaseFriction;

View File

@@ -85,7 +85,7 @@ public sealed class SpeedModifierContactsSystem : EntitySystem
var sprintSpeed = 0.0f; var sprintSpeed = 0.0f;
// Cache the result of the airborne check, as it's expensive and independent of contacting entities, hence need only be done once. // Cache the result of the airborne check, as it's expensive and independent of contacting entities, hence need only be done once.
var isAirborne = physicsComponent.BodyStatus == BodyStatus.InAir || _gravity.IsWeightless(uid, physicsComponent); var isAirborne = physicsComponent.BodyStatus == BodyStatus.InAir || _gravity.IsWeightless(uid);
bool remove = true; bool remove = true;
var entries = 0; var entries = 0;

View File

@@ -216,7 +216,7 @@ public abstract class SharedConveyorController : VirtualController
return true; return true;
if (physics.BodyStatus == BodyStatus.InAir || if (physics.BodyStatus == BodyStatus.InAir ||
_gravity.IsWeightless(entity, physics, xform)) _gravity.IsWeightless(entity.Owner))
{ {
return true; return true;
} }

View File

@@ -139,7 +139,7 @@ public sealed class StepTriggerSystem : EntitySystem
// and the entity is flying or currently weightless // and the entity is flying or currently weightless
// Makes sense simulation wise to have this be part of steptrigger directly IMO // Makes sense simulation wise to have this be part of steptrigger directly IMO
if (!component.IgnoreWeightless && TryComp<PhysicsComponent>(otherUid, out var physics) && if (!component.IgnoreWeightless && TryComp<PhysicsComponent>(otherUid, out var physics) &&
(physics.BodyStatus == BodyStatus.InAir || _gravity.IsWeightless(otherUid, physics))) (physics.BodyStatus == BodyStatus.InAir || _gravity.IsWeightless(otherUid)))
return false; return false;
var msg = new StepTriggerAttemptEvent { Source = uid, Tripper = otherUid }; var msg = new StepTriggerAttemptEvent { Source = uid, Tripper = otherUid };

View File

@@ -242,7 +242,7 @@ public sealed class ThrowingSystem : EntitySystem
RaiseLocalEvent(user.Value, ref pushEv); RaiseLocalEvent(user.Value, ref pushEv);
const float massLimit = 5f; const float massLimit = 5f;
if (pushEv.Push || _gravity.IsWeightless(user.Value)) if (pushEv.Push)
_physics.ApplyLinearImpulse(user.Value, -impulseVector / physics.Mass * pushbackRatio * MathF.Min(massLimit, physics.Mass), body: userPhysics); _physics.ApplyLinearImpulse(user.Value, -impulseVector / physics.Mass * pushbackRatio * MathF.Min(massLimit, physics.Mass), body: userPhysics);
} }
} }

View File

@@ -391,7 +391,7 @@ public abstract partial class SharedGunSystem : EntitySystem
var shooterEv = new ShooterImpulseEvent(); var shooterEv = new ShooterImpulseEvent();
RaiseLocalEvent(user, ref shooterEv); RaiseLocalEvent(user, ref shooterEv);
if (shooterEv.Push || _gravity.IsWeightless(user, userPhysics)) if (shooterEv.Push)
CauseImpulse(fromCoordinates, toCoordinates.Value, user, userPhysics); CauseImpulse(fromCoordinates, toCoordinates.Value, user, userPhysics);
} }

View File

@@ -9,6 +9,7 @@
noRot: true noRot: true
drawdepth: Mobs drawdepth: Mobs
- type: MobCollision - type: MobCollision
- type: GravityAffected
- type: Physics - type: Physics
bodyType: KinematicController bodyType: KinematicController
- type: Fixtures - type: Fixtures

View File

@@ -6,11 +6,13 @@
- type: Clickable - type: Clickable
- type: InteractionOutline - type: InteractionOutline
- type: MovementIgnoreGravity - type: MovementIgnoreGravity
weightless: true
- type: Sprite - type: Sprite
sprite: Objects/Fun/immovable_rod.rsi sprite: Objects/Fun/immovable_rod.rsi
state: icon state: icon
noRot: false noRot: false
- type: ImmovableRod - type: ImmovableRod
- type: GravityAffected
- type: Physics - type: Physics
bodyType: Dynamic bodyType: Dynamic
linearDamping: 0 linearDamping: 0
@@ -78,8 +80,6 @@
damage: damage:
types: types:
Blunt: 190 Blunt: 190
- type: MovementIgnoreGravity
gravityState: true
- type: InputMover - type: InputMover
- type: MovementSpeedModifier - type: MovementSpeedModifier
baseWeightlessAcceleration: 5 baseWeightlessAcceleration: 5

View File

@@ -64,6 +64,7 @@
- type: Pullable - type: Pullable
- type: Physics - type: Physics
bodyType: KinematicController bodyType: KinematicController
- type: GravityAffected
- type: Clickable - type: Clickable
- type: WiresPanel - type: WiresPanel
- type: Fixtures - type: Fixtures

View File

@@ -24,6 +24,7 @@
soundHit: soundHit:
collection: MetalThud collection: MetalThud
- type: CollisionWake - type: CollisionWake
- type: GravityAffected
- type: Physics - type: Physics
bodyType: Dynamic bodyType: Dynamic
fixedRotation: false fixedRotation: false

View File

@@ -7,6 +7,7 @@
- type: Transform - type: Transform
anchored: true anchored: true
- type: Clickable - type: Clickable
- type: GravityAffected
- type: Physics - type: Physics
bodyType: Static bodyType: Static
- type: Fixtures - type: Fixtures