From 9de76e70c71097241b3b2a2720eef0c1d34aba89 Mon Sep 17 00:00:00 2001 From: Princess Cheeseballs <66055347+Princess-Cheeseballs@users.noreply.github.com> Date: Tue, 19 Aug 2025 11:35:09 -0700 Subject: [PATCH] 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> --- .../Tests/Doors/AirlockTest.cs | 1 + .../Tests/Gravity/WeightlessStatusTests.cs | 1 + .../Tests/GravityGridTest.cs | 6 +- .../Tests/Interaction/InteractionTest.cs | 1 + .../Systems/AdminVerbSystem.Smites.cs | 6 + .../Fluids/EntitySystems/SpraySystem.cs | 2 +- Content.Server/Gravity/GravitySystem.cs | 4 +- .../AntiGravityClothingSystem.cs | 13 + Content.Shared/Clothing/MagbootsComponent.cs | 7 +- Content.Shared/Clothing/MagbootsSystem.cs | 10 +- .../DoAfter/SharedDoAfterSystem.Update.cs | 5 +- .../Friction/TileFrictionController.cs | 2 +- .../Gravity/FloatingVisualsComponent.cs | 7 +- .../Gravity/GravityAffectedComponent.cs | 17 + Content.Shared/Gravity/GravityComponent.cs | 22 +- .../Gravity/SharedFloatingVisualizerSystem.cs | 53 +-- Content.Shared/Gravity/SharedGravitySystem.cs | 367 +++++++++++------- .../MovementIgnoreGravityComponent.cs | 23 +- .../Systems/FrictionContactsSystem.cs | 2 +- .../Systems/MovementIgnoreGravitySystem.cs | 24 +- .../Movement/Systems/SharedMoverController.cs | 6 +- .../Systems/SpeedModifierContactsSystem.cs | 2 +- .../Controllers/SharedConveyorController.cs | 2 +- .../StepTrigger/Systems/StepTriggerSystem.cs | 2 +- Content.Shared/Throwing/ThrowingSystem.cs | 2 +- .../Weapons/Ranged/Systems/SharedGunSystem.cs | 2 +- Resources/Prototypes/Entities/Mobs/base.yml | 1 + .../Entities/Objects/Fun/immovable_rod.yml | 4 +- .../Entities/Objects/Specific/Mech/mechs.yml | 1 + .../Prototypes/Entities/Objects/base_item.yml | 1 + .../Entities/Structures/base_structure.yml | 1 + 31 files changed, 329 insertions(+), 268 deletions(-) create mode 100644 Content.Shared/Gravity/GravityAffectedComponent.cs diff --git a/Content.IntegrationTests/Tests/Doors/AirlockTest.cs b/Content.IntegrationTests/Tests/Doors/AirlockTest.cs index e47c73611a..69fe66039b 100644 --- a/Content.IntegrationTests/Tests/Doors/AirlockTest.cs +++ b/Content.IntegrationTests/Tests/Doors/AirlockTest.cs @@ -21,6 +21,7 @@ namespace Content.IntegrationTests.Tests.Doors components: - type: Physics bodyType: Dynamic + - type: GravityAffected - type: Fixtures fixtures: fix1: diff --git a/Content.IntegrationTests/Tests/Gravity/WeightlessStatusTests.cs b/Content.IntegrationTests/Tests/Gravity/WeightlessStatusTests.cs index 6aa2763888..0951e7e260 100644 --- a/Content.IntegrationTests/Tests/Gravity/WeightlessStatusTests.cs +++ b/Content.IntegrationTests/Tests/Gravity/WeightlessStatusTests.cs @@ -19,6 +19,7 @@ namespace Content.IntegrationTests.Tests.Gravity - type: Alerts - type: Physics bodyType: Dynamic + - type: GravityAffected - type: entity name: WeightlessGravityGeneratorDummy diff --git a/Content.IntegrationTests/Tests/GravityGridTest.cs b/Content.IntegrationTests/Tests/GravityGridTest.cs index b32d6c2b8d..8257035de6 100644 --- a/Content.IntegrationTests/Tests/GravityGridTest.cs +++ b/Content.IntegrationTests/Tests/GravityGridTest.cs @@ -76,8 +76,8 @@ namespace Content.IntegrationTests.Tests Assert.Multiple(() => { Assert.That(generatorComponent.GravityActive, Is.True); - Assert.That(!entityMan.GetComponent(grid1).EnabledVV); - Assert.That(entityMan.GetComponent(grid2).EnabledVV); + Assert.That(!entityMan.GetComponent(grid1).Enabled); + Assert.That(entityMan.GetComponent(grid2).Enabled); }); // Re-enable needs power so it turns off again. @@ -94,7 +94,7 @@ namespace Content.IntegrationTests.Tests Assert.Multiple(() => { Assert.That(generatorComponent.GravityActive, Is.False); - Assert.That(entityMan.GetComponent(grid2).EnabledVV, Is.False); + Assert.That(entityMan.GetComponent(grid2).Enabled, Is.False); }); }); diff --git a/Content.IntegrationTests/Tests/Interaction/InteractionTest.cs b/Content.IntegrationTests/Tests/Interaction/InteractionTest.cs index 79756ea5b4..0ed42d3476 100644 --- a/Content.IntegrationTests/Tests/Interaction/InteractionTest.cs +++ b/Content.IntegrationTests/Tests/Interaction/InteractionTest.cs @@ -144,6 +144,7 @@ public abstract partial class InteractionTest - type: Stripping - type: Puller - type: Physics + - type: GravityAffected - type: Tag tags: - CanPilot diff --git a/Content.Server/Administration/Systems/AdminVerbSystem.Smites.cs b/Content.Server/Administration/Systems/AdminVerbSystem.Smites.cs index 1bc4b65999..3703c8c1ac 100644 --- a/Content.Server/Administration/Systems/AdminVerbSystem.Smites.cs +++ b/Content.Server/Administration/Systems/AdminVerbSystem.Smites.cs @@ -29,6 +29,7 @@ using Content.Shared.Damage; using Content.Shared.Damage.Systems; using Content.Shared.Database; using Content.Shared.Electrocution; +using Content.Shared.Gravity; using Content.Shared.Interaction.Components; using Content.Shared.Inventory; using Content.Shared.Mobs; @@ -675,6 +676,11 @@ public sealed partial class AdminVerbSystem grav.Weightless = true; Dirty(args.Target, grav); + + EnsureComp(args.Target, out var weightless); + weightless.Weightless = true; + + Dirty(args.Target, weightless); }, Impact = LogImpact.Extreme, Message = string.Join(": ", noGravityName, Loc.GetString("admin-smite-remove-gravity-description")) diff --git a/Content.Server/Fluids/EntitySystems/SpraySystem.cs b/Content.Server/Fluids/EntitySystems/SpraySystem.cs index d8da0cde3d..2a6b0644be 100644 --- a/Content.Server/Fluids/EntitySystems/SpraySystem.cs +++ b/Content.Server/Fluids/EntitySystems/SpraySystem.cs @@ -166,7 +166,7 @@ public sealed class SpraySystem : EntitySystem if (TryComp(user, out var body)) { - if (_gravity.IsWeightless(user, body)) + if (_gravity.IsWeightless(user)) { // push back the player _physics.ApplyLinearImpulse(user, -impulseDirection * entity.Comp.PushbackAmount, body: body); diff --git a/Content.Server/Gravity/GravitySystem.cs b/Content.Server/Gravity/GravitySystem.cs index 6807b9df4a..7958071a31 100644 --- a/Content.Server/Gravity/GravitySystem.cs +++ b/Content.Server/Gravity/GravitySystem.cs @@ -18,7 +18,7 @@ namespace Content.Server.Gravity /// public void RefreshGravity(EntityUid uid, GravityComponent? gravity = null) { - if (!Resolve(uid, ref gravity)) + if (!GravityQuery.Resolve(uid, ref gravity)) return; if (gravity.Inherent) @@ -61,7 +61,7 @@ namespace Content.Server.Gravity /// public void EnableGravity(EntityUid uid, GravityComponent? gravity = null) { - if (!Resolve(uid, ref gravity)) + if (!GravityQuery.Resolve(uid, ref gravity)) return; if (gravity.Enabled || gravity.Inherent) diff --git a/Content.Shared/Clothing/EntitySystems/AntiGravityClothingSystem.cs b/Content.Shared/Clothing/EntitySystems/AntiGravityClothingSystem.cs index c5b2ee3dfc..636a21533e 100644 --- a/Content.Shared/Clothing/EntitySystems/AntiGravityClothingSystem.cs +++ b/Content.Shared/Clothing/EntitySystems/AntiGravityClothingSystem.cs @@ -6,10 +6,13 @@ namespace Content.Shared.Clothing.EntitySystems; public sealed class AntiGravityClothingSystem : EntitySystem { + [Dependency] SharedGravitySystem _gravity = default!; /// public override void Initialize() { SubscribeLocalEvent>(OnIsWeightless); + SubscribeLocalEvent(OnEquipped); + SubscribeLocalEvent(OnUnequipped); } private void OnIsWeightless(Entity ent, ref InventoryRelayedEvent args) @@ -20,4 +23,14 @@ public sealed class AntiGravityClothingSystem : EntitySystem args.Args.Handled = true; args.Args.IsWeightless = true; } + + private void OnEquipped(Entity entity, ref ClothingGotEquippedEvent args) + { + _gravity.RefreshWeightless(args.Wearer, true); + } + + private void OnUnequipped(Entity entity, ref ClothingGotUnequippedEvent args) + { + _gravity.RefreshWeightless(args.Wearer, false); + } } diff --git a/Content.Shared/Clothing/MagbootsComponent.cs b/Content.Shared/Clothing/MagbootsComponent.cs index 4bef74fd33..c0a8b40866 100644 --- a/Content.Shared/Clothing/MagbootsComponent.cs +++ b/Content.Shared/Clothing/MagbootsComponent.cs @@ -1,4 +1,5 @@ using Content.Shared.Alert; +using Content.Shared.Inventory; using Robust.Shared.GameStates; using Robust.Shared.Prototypes; @@ -16,10 +17,4 @@ public sealed partial class MagbootsComponent : Component /// [DataField] public bool RequiresGrid = true; - - /// - /// Slot the clothing has to be worn in to work. - /// - [DataField] - public string Slot = "shoes"; } diff --git a/Content.Shared/Clothing/MagbootsSystem.cs b/Content.Shared/Clothing/MagbootsSystem.cs index fd5a2cc336..58bc2f0796 100644 --- a/Content.Shared/Clothing/MagbootsSystem.cs +++ b/Content.Shared/Clothing/MagbootsSystem.cs @@ -32,14 +32,8 @@ public sealed class SharedMagbootsSystem : EntitySystem private void OnToggled(Entity ent, ref ItemToggledEvent args) { - var (uid, comp) = ent; - // 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) - { + if (_container.TryGetContainingContainer((ent.Owner, null, null), out var container)) UpdateMagbootEffects(container.Owner, ent, args.Activated); - } } private void OnGotUnequipped(Entity ent, ref ClothingGotUnequippedEvent args) @@ -58,6 +52,8 @@ public sealed class SharedMagbootsSystem : EntitySystem if (TryComp(user, out var moved)) moved.Enabled = !state; + _gravity.RefreshWeightless(user, !state); + if (state) _alerts.ShowAlert(user, ent.Comp.MagbootsAlert); else diff --git a/Content.Shared/DoAfter/SharedDoAfterSystem.Update.cs b/Content.Shared/DoAfter/SharedDoAfterSystem.Update.cs index 31ff034809..c70c7ab61e 100644 --- a/Content.Shared/DoAfter/SharedDoAfterSystem.Update.cs +++ b/Content.Shared/DoAfter/SharedDoAfterSystem.Update.cs @@ -164,12 +164,11 @@ public abstract partial class SharedDoAfterSystem : EntitySystem if (args.Target is { } target && !xformQuery.TryGetComponent(target, out targetXform)) return true; - TransformComponent? usedXform = null; - if (args.Used is { } @using && !xformQuery.TryGetComponent(@using, out usedXform)) + if (args.Used is { } @using && !xformQuery.HasComp(@using)) return true; // 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. if (!_transform.InRange(userXform.Coordinates, doAfter.UserPosition, args.MovementThreshold)) diff --git a/Content.Shared/Friction/TileFrictionController.cs b/Content.Shared/Friction/TileFrictionController.cs index 36e971862a..4b29b9a9de 100644 --- a/Content.Shared/Friction/TileFrictionController.cs +++ b/Content.Shared/Friction/TileFrictionController.cs @@ -73,7 +73,7 @@ namespace Content.Shared.Friction // 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 - 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; else friction = _frictionModifier * GetTileFriction(uid, body, xform); diff --git a/Content.Shared/Gravity/FloatingVisualsComponent.cs b/Content.Shared/Gravity/FloatingVisualsComponent.cs index 67650baecd..c0064ebf78 100644 --- a/Content.Shared/Gravity/FloatingVisualsComponent.cs +++ b/Content.Shared/Gravity/FloatingVisualsComponent.cs @@ -10,20 +10,17 @@ public sealed partial class FloatingVisualsComponent : Component /// /// How long it takes to go from the bottom of the animation to the top. /// - [ViewVariables(VVAccess.ReadWrite)] [DataField, AutoNetworkedField] public float AnimationTime = 2f; /// /// How far it goes in any direction. /// - [ViewVariables(VVAccess.ReadWrite)] [DataField, AutoNetworkedField] public Vector2 Offset = new(0, 0.2f); - [ViewVariables(VVAccess.ReadWrite)] - [AutoNetworkedField] - public bool CanFloat = false; + [DataField, AutoNetworkedField] + public bool CanFloat; public readonly string AnimationKey = "gravity"; } diff --git a/Content.Shared/Gravity/GravityAffectedComponent.cs b/Content.Shared/Gravity/GravityAffectedComponent.cs new file mode 100644 index 0000000000..2da7458b07 --- /dev/null +++ b/Content.Shared/Gravity/GravityAffectedComponent.cs @@ -0,0 +1,17 @@ +using Robust.Shared.GameStates; + +namespace Content.Shared.Gravity; + +/// +/// This Component allows a target to be considered "weightless" when Weightless is true. Without this component, the +/// target will never be weightless. +/// +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState] +public sealed partial class GravityAffectedComponent : Component +{ + /// + /// If true, this entity will be considered "weightless" + /// + [DataField, AutoNetworkedField] + public bool Weightless = true; +} diff --git a/Content.Shared/Gravity/GravityComponent.cs b/Content.Shared/Gravity/GravityComponent.cs index bbe3de69ea..ede8f74c7a 100644 --- a/Content.Shared/Gravity/GravityComponent.cs +++ b/Content.Shared/Gravity/GravityComponent.cs @@ -5,34 +5,20 @@ using Robust.Shared.Serialization; namespace Content.Shared.Gravity { [RegisterComponent] + [AutoGenerateComponentState] [NetworkedComponent] public sealed partial class GravityComponent : Component { - [DataField("gravityShakeSound")] + [DataField, AutoNetworkedField] public SoundSpecifier GravityShakeSound { get; set; } = new SoundPathSpecifier("/Audio/Effects/alert.ogg"); - [ViewVariables(VVAccess.ReadWrite)] - public bool EnabledVV - { - get => Enabled; - set - { - if (Enabled == value) return; - Enabled = value; - var ev = new GravityChangedEvent(Owner, value); - IoCManager.Resolve().EventBus.RaiseLocalEvent(Owner, ref ev); - Dirty(); - } - } - - [DataField("enabled")] + [DataField, AutoNetworkedField] public bool Enabled; /// /// Inherent gravity ensures GravitySystem won't change Enabled according to the gravity generators attached to this entity. /// - [ViewVariables(VVAccess.ReadWrite)] - [DataField("inherent")] + [DataField, AutoNetworkedField] public bool Inherent; } } diff --git a/Content.Shared/Gravity/SharedFloatingVisualizerSystem.cs b/Content.Shared/Gravity/SharedFloatingVisualizerSystem.cs index 6ca974f2ed..ae5c73b498 100644 --- a/Content.Shared/Gravity/SharedFloatingVisualizerSystem.cs +++ b/Content.Shared/Gravity/SharedFloatingVisualizerSystem.cs @@ -8,15 +8,14 @@ namespace Content.Shared.Gravity; /// public abstract class SharedFloatingVisualizerSystem : EntitySystem { - [Dependency] private readonly SharedGravitySystem GravitySystem = default!; + [Dependency] private readonly SharedGravitySystem _gravity = default!; public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnComponentStartup); - SubscribeLocalEvent(OnGravityChanged); - SubscribeLocalEvent(OnEntParentChanged); + SubscribeLocalEvent(OnWeightlessnessChanged); } /// @@ -24,48 +23,28 @@ public abstract class SharedFloatingVisualizerSystem : EntitySystem /// 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 entity) { - if (!Resolve(uid, ref transform)) - return false; - - if (transform.MapID == MapId.Nullspace) - return false; - - component.CanFloat = GravitySystem.IsWeightless(uid, xform: transform); - Dirty(uid, component); - return component.CanFloat; + entity.Comp.CanFloat = _gravity.IsWeightless(entity.Owner); + Dirty(entity); + return entity.Comp.CanFloat; } - private void OnComponentStartup(EntityUid uid, FloatingVisualsComponent component, ComponentStartup args) + private void OnComponentStartup(Entity entity, ref ComponentStartup args) { - if (CanFloat(uid, component)) - FloatAnimation(uid, component.Offset, component.AnimationKey, component.AnimationTime); + if (CanFloat(entity)) + FloatAnimation(entity, entity.Comp.Offset, entity.Comp.AnimationKey, entity.Comp.AnimationTime); } - private void OnGravityChanged(ref GravityChangedEvent args) + private void OnWeightlessnessChanged(Entity entity, ref WeightlessnessChangedEvent args) { - var query = EntityQueryEnumerator(); - while (query.MoveNext(out var uid, out var floating, out var transform)) - { - if (transform.MapID == MapId.Nullspace) - continue; + if (entity.Comp.CanFloat == args.Weightless) + return; - if (transform.GridUid != args.ChangedGridIndex) - continue; + entity.Comp.CanFloat = CanFloat(entity); + Dirty(entity); - floating.CanFloat = !args.HasGravity; - Dirty(uid, floating); - - 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); + if (args.Weightless) + FloatAnimation(entity, entity.Comp.Offset, entity.Comp.AnimationKey, entity.Comp.AnimationTime); } } diff --git a/Content.Shared/Gravity/SharedGravitySystem.cs b/Content.Shared/Gravity/SharedGravitySystem.cs index e20771d603..b54f9b21c8 100644 --- a/Content.Shared/Gravity/SharedGravitySystem.cs +++ b/Content.Shared/Gravity/SharedGravitySystem.cs @@ -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 WeightlessAlert = "Weightless"; + + protected EntityQuery GravityQuery; + private EntityQuery _weightlessQuery; + private EntityQuery _physicsQuery; + + public override void Initialize() { - [Dependency] protected readonly IGameTiming Timing = default!; - [Dependency] private readonly AlertsSystem _alerts = default!; + base.Initialize(); + // Grid Gravity + SubscribeLocalEvent(OnGridInit); + SubscribeLocalEvent(OnGravityChange); - public static readonly ProtoId WeightlessAlert = "Weightless"; + // Weightlessness + SubscribeLocalEvent(OnMapInit); + SubscribeLocalEvent(OnEntParentChanged); + SubscribeLocalEvent(OnBodyTypeChanged); - private EntityQuery _gravityQuery; + // Alerts + SubscribeLocalEvent(OnAlertsSync); + SubscribeLocalEvent(OnWeightlessnessChanged); + SubscribeLocalEvent(OnAlertsParentChange); - public bool IsWeightless(EntityUid uid, PhysicsComponent? body = null, TransformComponent? xform = null) - { - Resolve(uid, ref body, false); + // Impulse + SubscribeLocalEvent(OnShooterImpulse); + SubscribeLocalEvent(OnThrowerImpulse); - if ((body?.BodyType & (BodyType.Static | BodyType.Kinematic)) != 0) - return false; + GravityQuery = GetEntityQuery(); + _weightlessQuery = GetEntityQuery(); + _physicsQuery = GetEntityQuery(); + } - if (TryComp(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 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 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); + } + + /// + /// Refreshes weightlessness status, needs to be called anytime it would change. + /// + /// The entity we are updating the weightless status of + public void RefreshWeightless(Entity entity) + { + if (!_weightlessQuery.Resolve(entity, ref entity.Comp)) + return; + + UpdateWeightless(entity!); + } + + /// + /// Overload of 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. + /// + /// The entity we are updating the weightless status of + /// The weightless value we are trying to change to, helps avoid needless networking + public void RefreshWeightless(Entity 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 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 entity, ref MapInitEvent args) + { + RefreshWeightless((entity.Owner, entity.Comp)); + } + + private void OnWeightlessnessChanged(Entity entity, ref WeightlessnessChangedEvent args) + { + if (args.Weightless) + _alerts.ShowAlert(entity, WeightlessAlert); + else + _alerts.ClearAlert(entity, WeightlessAlert); + } + + private void OnEntParentChanged(Entity 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 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)); + } + + /// + /// Checks if a given entity is currently standing on a grid or map that supports having gravity at all. + /// + public bool EntityOnGravitySupportingGridOrMap(Entity entity) + { + entity.Comp ??= Transform(entity); + + return GravityQuery.HasComp(entity.Comp.GridUid) || + GravityQuery.HasComp(entity.Comp.MapUid); + } + + /// + /// Checks if a given entity is currently standing on a grid or map that has gravity of some kind. + /// + public bool EntityGridOrMapHaveGravity(Entity 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; - } - /// - /// Checks if a given entity is currently standing on a grid or map that supports having gravity at all. - /// - public bool EntityOnGravitySupportingGridOrMap(Entity 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(); + 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); - } - - - /// - /// Checks if a given entity is currently standing on a grid or map that has gravity of some kind. - /// - public bool EntityGridOrMapHaveGravity(Entity 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(OnGridInit); - SubscribeLocalEvent(OnAlertsSync); - SubscribeLocalEvent(OnAlertsParentChange); - SubscribeLocalEvent(OnGravityChange); - SubscribeLocalEvent(OnGetState); - SubscribeLocalEvent(OnHandleState); - - _gravityQuery = GetEntityQuery(); - } - - 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(); - 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(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(ev.EntityUid); + } + + [Serializable, NetSerializable] + private sealed class GravityComponentState : ComponentState + { + public bool Enabled { get; } + + public GravityComponentState(bool enabled) + { + Enabled = enabled; + } + } + + private void OnThrowerImpulse(Entity entity, ref ThrowerImpulseEvent args) + { + args.Push = true; + } + + private void OnShooterImpulse(Entity entity, ref ShooterImpulseEvent args) + { + args.Push = true; } } + +/// +/// Raised to determine if an entity's weightlessness is being overwritten by a component or item with a component. +/// +/// Whether we should be weightless +/// Whether something is trying to override our weightlessness +[ByRefEvent] +public record struct IsWeightlessEvent(bool IsWeightless = false, bool Handled = false) : IInventoryRelayEvent +{ + SlotFlags IInventoryRelayEvent.TargetSlots => ~SlotFlags.POCKET; +} + +/// +/// Raised on an entity when their weightless status changes. +/// +[ByRefEvent] +public readonly record struct WeightlessnessChangedEvent(bool Weightless); diff --git a/Content.Shared/Movement/Components/MovementIgnoreGravityComponent.cs b/Content.Shared/Movement/Components/MovementIgnoreGravityComponent.cs index 77c468e871..1723600ad1 100644 --- a/Content.Shared/Movement/Components/MovementIgnoreGravityComponent.cs +++ b/Content.Shared/Movement/Components/MovementIgnoreGravityComponent.cs @@ -1,34 +1,17 @@ -using Content.Shared.Clothing; -using Content.Shared.Gravity; -using Content.Shared.Inventory; 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 { /// /// Ignores gravity entirely. /// - [RegisterComponent, NetworkedComponent] + [RegisterComponent, NetworkedComponent, AutoGenerateComponentState] public sealed partial class MovementIgnoreGravityComponent : Component { /// - /// 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. /// - [DataField("gravityState")] public bool Weightless = false; - } - - [NetSerializable, Serializable] - public sealed class MovementIgnoreGravityComponentState : ComponentState - { + [DataField, AutoNetworkedField] public bool Weightless; - - public MovementIgnoreGravityComponentState(MovementIgnoreGravityComponent component) - { - Weightless = component.Weightless; - } } } diff --git a/Content.Shared/Movement/Systems/FrictionContactsSystem.cs b/Content.Shared/Movement/Systems/FrictionContactsSystem.cs index 44fc3933e3..3ffdd6ec26 100644 --- a/Content.Shared/Movement/Systems/FrictionContactsSystem.cs +++ b/Content.Shared/Movement/Systems/FrictionContactsSystem.cs @@ -84,7 +84,7 @@ public sealed class FrictionContactsSystem : EntitySystem var frictionNoInput = 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 entries = 0; diff --git a/Content.Shared/Movement/Systems/MovementIgnoreGravitySystem.cs b/Content.Shared/Movement/Systems/MovementIgnoreGravitySystem.cs index 52cdf219e5..93f6650fa9 100644 --- a/Content.Shared/Movement/Systems/MovementIgnoreGravitySystem.cs +++ b/Content.Shared/Movement/Systems/MovementIgnoreGravitySystem.cs @@ -1,33 +1,35 @@ -using Content.Shared.Movement.Components; +using Content.Shared.Gravity; +using Content.Shared.Movement.Components; using Content.Shared.Movement.Events; -using Robust.Shared.GameStates; namespace Content.Shared.Movement.Systems; public sealed class MovementIgnoreGravitySystem : EntitySystem { + [Dependency] SharedGravitySystem _gravity = default!; public override void Initialize() { - SubscribeLocalEvent(GetState); - SubscribeLocalEvent(HandleState); SubscribeLocalEvent(OnWeightless); + SubscribeLocalEvent(OnIsWeightless); + SubscribeLocalEvent(OnComponentStartup); } - private void OnWeightless(EntityUid uid, MovementAlwaysTouchingComponent component, ref CanWeightlessMoveEvent args) + private void OnWeightless(Entity entity, ref CanWeightlessMoveEvent args) { args.CanMove = true; } - private void HandleState(EntityUid uid, MovementIgnoreGravityComponent component, ref ComponentHandleState args) + private void OnIsWeightless(Entity entity, ref IsWeightlessEvent args) { - if (args.Next is null) - return; + // We don't check if the event has been handled as this component takes precedent over other things. - 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 entity, ref ComponentStartup args) { - args.State = new MovementIgnoreGravityComponentState(component); + EnsureComp(entity); + _gravity.RefreshWeightless(entity.Owner, entity.Comp.Weightless); } } diff --git a/Content.Shared/Movement/Systems/SharedMoverController.cs b/Content.Shared/Movement/Systems/SharedMoverController.cs index f8495fcd18..b3c84aed4d 100644 --- a/Content.Shared/Movement/Systems/SharedMoverController.cs +++ b/Content.Shared/Movement/Systems/SharedMoverController.cs @@ -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 - // TODO: MAKE ISWEIGHTLESS EVENT BASED - var weightless = _gravity.IsWeightless(uid, physicsComponent, xform); + var weightless = _gravity.IsWeightless(uid); var inAirHelpless = false; if (physicsComponent.BodyStatus != BodyStatus.OnGround && !CanMoveInAirQuery.HasComponent(uid)) @@ -624,8 +623,7 @@ public abstract partial class SharedMoverController : VirtualController if (!TryComp(ent, out var physicsComponent) || !XformQuery.TryComp(ent, out var xform)) return; - // TODO: Make IsWeightless event based!!! - if (physicsComponent.BodyStatus != BodyStatus.OnGround || _gravity.IsWeightless(ent, physicsComponent, xform)) + if (physicsComponent.BodyStatus != BodyStatus.OnGround || _gravity.IsWeightless(ent.Owner)) args.Modifier *= ent.Comp.BaseWeightlessFriction; else args.Modifier *= ent.Comp.BaseFriction; diff --git a/Content.Shared/Movement/Systems/SpeedModifierContactsSystem.cs b/Content.Shared/Movement/Systems/SpeedModifierContactsSystem.cs index 8a0b085a83..2dda244163 100644 --- a/Content.Shared/Movement/Systems/SpeedModifierContactsSystem.cs +++ b/Content.Shared/Movement/Systems/SpeedModifierContactsSystem.cs @@ -85,7 +85,7 @@ public sealed class SpeedModifierContactsSystem : EntitySystem 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. - var isAirborne = physicsComponent.BodyStatus == BodyStatus.InAir || _gravity.IsWeightless(uid, physicsComponent); + var isAirborne = physicsComponent.BodyStatus == BodyStatus.InAir || _gravity.IsWeightless(uid); bool remove = true; var entries = 0; diff --git a/Content.Shared/Physics/Controllers/SharedConveyorController.cs b/Content.Shared/Physics/Controllers/SharedConveyorController.cs index c2c88b2742..b1ccb3be2a 100644 --- a/Content.Shared/Physics/Controllers/SharedConveyorController.cs +++ b/Content.Shared/Physics/Controllers/SharedConveyorController.cs @@ -216,7 +216,7 @@ public abstract class SharedConveyorController : VirtualController return true; if (physics.BodyStatus == BodyStatus.InAir || - _gravity.IsWeightless(entity, physics, xform)) + _gravity.IsWeightless(entity.Owner)) { return true; } diff --git a/Content.Shared/StepTrigger/Systems/StepTriggerSystem.cs b/Content.Shared/StepTrigger/Systems/StepTriggerSystem.cs index 14703f3177..d1443e5da2 100644 --- a/Content.Shared/StepTrigger/Systems/StepTriggerSystem.cs +++ b/Content.Shared/StepTrigger/Systems/StepTriggerSystem.cs @@ -139,7 +139,7 @@ public sealed class StepTriggerSystem : EntitySystem // and the entity is flying or currently weightless // Makes sense simulation wise to have this be part of steptrigger directly IMO if (!component.IgnoreWeightless && TryComp(otherUid, out var physics) && - (physics.BodyStatus == BodyStatus.InAir || _gravity.IsWeightless(otherUid, physics))) + (physics.BodyStatus == BodyStatus.InAir || _gravity.IsWeightless(otherUid))) return false; var msg = new StepTriggerAttemptEvent { Source = uid, Tripper = otherUid }; diff --git a/Content.Shared/Throwing/ThrowingSystem.cs b/Content.Shared/Throwing/ThrowingSystem.cs index 4e44901c57..db68c3517c 100644 --- a/Content.Shared/Throwing/ThrowingSystem.cs +++ b/Content.Shared/Throwing/ThrowingSystem.cs @@ -242,7 +242,7 @@ public sealed class ThrowingSystem : EntitySystem RaiseLocalEvent(user.Value, ref pushEv); 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); } } diff --git a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs index 61ee8cdada..31f86d0236 100644 --- a/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs +++ b/Content.Shared/Weapons/Ranged/Systems/SharedGunSystem.cs @@ -391,7 +391,7 @@ public abstract partial class SharedGunSystem : EntitySystem var shooterEv = new ShooterImpulseEvent(); RaiseLocalEvent(user, ref shooterEv); - if (shooterEv.Push || _gravity.IsWeightless(user, userPhysics)) + if (shooterEv.Push) CauseImpulse(fromCoordinates, toCoordinates.Value, user, userPhysics); } diff --git a/Resources/Prototypes/Entities/Mobs/base.yml b/Resources/Prototypes/Entities/Mobs/base.yml index cfd9c13631..90bb90e663 100644 --- a/Resources/Prototypes/Entities/Mobs/base.yml +++ b/Resources/Prototypes/Entities/Mobs/base.yml @@ -9,6 +9,7 @@ noRot: true drawdepth: Mobs - type: MobCollision + - type: GravityAffected - type: Physics bodyType: KinematicController - type: Fixtures diff --git a/Resources/Prototypes/Entities/Objects/Fun/immovable_rod.yml b/Resources/Prototypes/Entities/Objects/Fun/immovable_rod.yml index e57223e1b4..15f67245f1 100644 --- a/Resources/Prototypes/Entities/Objects/Fun/immovable_rod.yml +++ b/Resources/Prototypes/Entities/Objects/Fun/immovable_rod.yml @@ -6,11 +6,13 @@ - type: Clickable - type: InteractionOutline - type: MovementIgnoreGravity + weightless: true - type: Sprite sprite: Objects/Fun/immovable_rod.rsi state: icon noRot: false - type: ImmovableRod + - type: GravityAffected - type: Physics bodyType: Dynamic linearDamping: 0 @@ -78,8 +80,6 @@ damage: types: Blunt: 190 - - type: MovementIgnoreGravity - gravityState: true - type: InputMover - type: MovementSpeedModifier baseWeightlessAcceleration: 5 diff --git a/Resources/Prototypes/Entities/Objects/Specific/Mech/mechs.yml b/Resources/Prototypes/Entities/Objects/Specific/Mech/mechs.yml index 4c4e44c28c..bda5901378 100644 --- a/Resources/Prototypes/Entities/Objects/Specific/Mech/mechs.yml +++ b/Resources/Prototypes/Entities/Objects/Specific/Mech/mechs.yml @@ -64,6 +64,7 @@ - type: Pullable - type: Physics bodyType: KinematicController + - type: GravityAffected - type: Clickable - type: WiresPanel - type: Fixtures diff --git a/Resources/Prototypes/Entities/Objects/base_item.yml b/Resources/Prototypes/Entities/Objects/base_item.yml index bb67d3f4cf..8490ba0042 100644 --- a/Resources/Prototypes/Entities/Objects/base_item.yml +++ b/Resources/Prototypes/Entities/Objects/base_item.yml @@ -24,6 +24,7 @@ soundHit: collection: MetalThud - type: CollisionWake + - type: GravityAffected - type: Physics bodyType: Dynamic fixedRotation: false diff --git a/Resources/Prototypes/Entities/Structures/base_structure.yml b/Resources/Prototypes/Entities/Structures/base_structure.yml index 71971a6624..d4936c859c 100644 --- a/Resources/Prototypes/Entities/Structures/base_structure.yml +++ b/Resources/Prototypes/Entities/Structures/base_structure.yml @@ -7,6 +7,7 @@ - type: Transform anchored: true - type: Clickable + - type: GravityAffected - type: Physics bodyType: Static - type: Fixtures