2022-07-25 15:10:23 +10:00
using Content.Shared.Alert ;
2022-07-18 19:49:52 +10:00
using Content.Shared.Inventory ;
2025-08-19 11:35:09 -07:00
using Content.Shared.Throwing ;
using Content.Shared.Weapons.Ranged.Systems ;
using Robust.Shared.Map ;
2022-07-18 19:49:52 +10:00
using Robust.Shared.Physics ;
2022-09-14 17:26:26 +10:00
using Robust.Shared.Physics.Components ;
2025-08-19 11:35:09 -07:00
using Robust.Shared.Physics.Events ;
2025-07-07 15:57:05 -04:00
using Robust.Shared.Prototypes ;
2022-07-25 15:10:23 +10:00
using Robust.Shared.Serialization ;
2022-12-18 16:03:23 +11:00
using Robust.Shared.Timing ;
2022-07-18 19:49:52 +10:00
2025-08-19 11:35:09 -07:00
namespace Content.Shared.Gravity ;
public abstract partial class SharedGravitySystem : EntitySystem
2021-06-27 07:43:39 +02:00
{
2025-08-19 11:35:09 -07:00
[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 ( )
2021-06-27 07:43:39 +02:00
{
2025-08-19 11:35:09 -07:00
base . Initialize ( ) ;
// Grid Gravity
SubscribeLocalEvent < GridInitializeEvent > ( OnGridInit ) ;
SubscribeLocalEvent < GravityChangedEvent > ( OnGravityChange ) ;
// Weightlessness
SubscribeLocalEvent < GravityAffectedComponent , MapInitEvent > ( OnMapInit ) ;
SubscribeLocalEvent < GravityAffectedComponent , EntParentChangedMessage > ( OnEntParentChanged ) ;
SubscribeLocalEvent < GravityAffectedComponent , PhysicsBodyTypeChangedEvent > ( OnBodyTypeChanged ) ;
// Alerts
SubscribeLocalEvent < AlertSyncEvent > ( OnAlertsSync ) ;
SubscribeLocalEvent < AlertsComponent , WeightlessnessChangedEvent > ( OnWeightlessnessChanged ) ;
SubscribeLocalEvent < AlertsComponent , EntParentChangedMessage > ( OnAlertsParentChange ) ;
// Impulse
SubscribeLocalEvent < GravityAffectedComponent , ShooterImpulseEvent > ( OnShooterImpulse ) ;
SubscribeLocalEvent < GravityAffectedComponent , ThrowerImpulseEvent > ( OnThrowerImpulse ) ;
GravityQuery = GetEntityQuery < GravityComponent > ( ) ;
_weightlessQuery = GetEntityQuery < GravityAffectedComponent > ( ) ;
_physicsQuery = GetEntityQuery < PhysicsComponent > ( ) ;
}
2022-07-18 19:49:52 +10:00
2025-08-19 11:35:09 -07:00
public override void Update ( float frameTime )
{
base . Update ( frameTime ) ;
UpdateShake ( ) ;
}
2024-05-23 22:43:04 -04:00
2025-08-19 11:35:09 -07:00
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 ;
}
2024-06-15 23:38:17 -04:00
2025-08-19 11:35:09 -07:00
private bool GetWeightless ( Entity < GravityAffectedComponent , PhysicsComponent ? > entity )
{
if ( ! _physicsQuery . Resolve ( entity , ref entity . Comp2 , false ) )
return false ;
2022-07-18 19:49:52 +10:00
2025-08-19 11:35:09 -07:00
if ( entity . Comp2 . BodyType is BodyType . Static or BodyType . Kinematic )
return false ;
2022-07-18 19:49:52 +10:00
2025-08-19 11:35:09 -07:00
// 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 ;
2022-07-18 19:49:52 +10:00
2025-08-19 11:35:09 -07:00
return ! EntityGridOrMapHaveGravity ( entity . Owner ) ;
}
2024-06-14 23:43:23 -04:00
2025-08-19 11:35:09 -07:00
/// <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 ;
2022-07-21 11:48:49 +10:00
2025-08-19 11:35:09 -07:00
UpdateWeightless ( entity ! ) ;
}
2022-07-18 19:49:52 +10:00
2025-08-19 11:35:09 -07:00
/// <summary>
/// Overload of <see cref="RefreshWeightless(Entity{GravityAffectedComponent?})"/> which also takes a bool for the weightlessness value we want to change to.
2025-08-27 07:26:32 -07:00
/// This method should only be called if there is no chance something can override the weightless value you're trying to change to.
/// This is really only the case if you're applying a weightless value that overrides non-conditionally from events or are a grid with the gravity component.
2025-08-19 11:35:09 -07:00
/// </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 ;
2022-07-18 19:49:52 +10:00
2025-08-19 11:35:09 -07:00
// Only update if we're changing our weightless status
if ( entity . Comp . Weightless = = weightless )
return ;
2024-06-15 23:38:17 -04:00
2025-08-19 11:35:09 -07:00
UpdateWeightless ( entity ! ) ;
}
2024-06-15 23:38:17 -04:00
2025-08-19 11:35:09 -07:00
private void UpdateWeightless ( Entity < GravityAffectedComponent > entity )
{
var newWeightless = GetWeightless ( entity ) ;
2024-06-15 23:38:17 -04:00
2025-08-19 11:35:09 -07:00
// Don't network or raise events if it's not changing
if ( newWeightless = = entity . Comp . Weightless )
return ;
2024-06-15 23:38:17 -04:00
2025-08-19 11:35:09 -07:00
entity . Comp . Weightless = newWeightless ;
Dirty ( entity ) ;
2024-06-15 23:38:17 -04:00
2025-08-19 11:35:09 -07:00
var ev = new WeightlessnessChangedEvent ( entity . Comp . Weightless ) ;
RaiseLocalEvent ( entity , ref ev ) ;
}
2022-12-18 16:03:23 +11:00
2025-08-19 11:35:09 -07:00
private void OnMapInit ( Entity < GravityAffectedComponent > entity , ref MapInitEvent args )
{
RefreshWeightless ( ( entity . Owner , entity . Comp ) ) ;
}
2022-07-25 15:10:23 +10:00
2025-08-19 11:35:09 -07:00
private void OnWeightlessnessChanged ( Entity < AlertsComponent > entity , ref WeightlessnessChangedEvent args )
{
if ( args . Weightless )
2025-09-05 02:45:48 -07:00
_alerts . ShowAlert ( entity . AsNullable ( ) , WeightlessAlert ) ;
2025-08-19 11:35:09 -07:00
else
2025-09-05 02:45:48 -07:00
_alerts . ClearAlert ( entity . AsNullable ( ) , WeightlessAlert ) ;
2025-08-19 11:35:09 -07:00
}
2022-07-25 15:10:23 +10:00
2025-08-19 11:35:09 -07:00
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 ;
2022-07-25 15:10:23 +10:00
2025-08-27 07:26:32 -07:00
RefreshWeightless ( ( entity . Owner , entity . Comp ) ) ;
2025-08-19 11:35:09 -07:00
}
2022-07-25 15:10:23 +10:00
2025-08-19 11:35:09 -07:00
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 ;
2022-12-18 16:03:23 +11:00
2025-08-19 11:35:09 -07:00
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 ;
2021-06-27 07:43:39 +02:00
2025-08-19 11:35:09 -07:00
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 ) )
2021-06-27 07:43:39 +02:00
{
2025-08-19 11:35:09 -07:00
if ( xform . GridUid ! = args . ChangedGridIndex )
continue ;
RefreshWeightless ( ( uid , weightless ) , ! args . HasGravity ) ;
2021-06-27 07:43:39 +02:00
}
2025-08-19 11:35:09 -07:00
}
2022-07-25 15:10:23 +10:00
2025-08-19 11:35:09 -07:00
private void OnAlertsSync ( AlertSyncEvent ev )
{
if ( IsWeightless ( ev . Euid ) )
_alerts . ShowAlert ( ev . Euid , WeightlessAlert ) ;
else
_alerts . ClearAlert ( ev . Euid , WeightlessAlert ) ;
}
2025-09-05 02:45:48 -07:00
private void OnAlertsParentChange ( Entity < AlertsComponent > entity , ref EntParentChangedMessage args )
2025-08-19 11:35:09 -07:00
{
2025-09-05 02:45:48 -07:00
if ( IsWeightless ( entity . Owner ) )
_alerts . ShowAlert ( entity . AsNullable ( ) , WeightlessAlert ) ;
2025-08-19 11:35:09 -07:00
else
2025-09-05 02:45:48 -07:00
_alerts . ClearAlert ( entity . AsNullable ( ) , WeightlessAlert ) ;
2025-08-19 11:35:09 -07:00
}
2022-07-25 15:10:23 +10:00
2025-08-19 11:35:09 -07:00
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 ;
2022-07-25 15:10:23 +10:00
}
2021-06-27 07:43:39 +02:00
}
2024-06-14 23:43:23 -04:00
2025-08-19 11:35:09 -07:00
private void OnThrowerImpulse ( Entity < GravityAffectedComponent > entity , ref ThrowerImpulseEvent args )
{
args . Push = true ;
}
private void OnShooterImpulse ( Entity < GravityAffectedComponent > entity , ref ShooterImpulseEvent args )
2024-06-14 23:43:23 -04:00
{
2025-08-19 11:35:09 -07:00
args . Push = true ;
2024-06-14 23:43:23 -04:00
}
2021-06-27 07:43:39 +02:00
}
2025-08-19 11:35:09 -07:00
/// <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 ) ;