]> git.smokeofanarchy.ru Git - space-station-14.git/commitdiff
Mob Movement Major Refactor (#36847)
authorPrincess Cheeseballs <66055347+Pronana@users.noreply.github.com>
Fri, 2 May 2025 08:18:08 +0000 (01:18 -0700)
committerGitHub <noreply@github.com>
Fri, 2 May 2025 08:18:08 +0000 (18:18 +1000)
* Conveyor optimisations

- Optimise movement for moving stuff. Better flags + less resolves + slapped parallelrobustjob on it.
- Sleeping for entities getting conveyed into walls.

* Blocker version

* Finish

* Final

* Fix conveyor power mispredict

* Bagel save

* Revert "Bagel save"

This reverts commit 1b93fda81fb852d89b89b0beae0b80f8a61165f2.

* Conveyor resave

* Init Commit

* windows yelling at me to update commit

* working commit, need prediciton and more dehardcoding

* Project 0 warnings

* Working Commit (Near Final)

* ryder got confused commit

* I love Merge Conflicts :)

* Working commit, no prediction

* Forgot the yaml changes

* Comments and typos

* Apparently while the reduced launch mult of lube was initialized it was never used so I revered back to default

* Fixed an incorrect divisor

* bit of cleanup

* Prediciton fixed, and puddles now affect all entities

* FORGOT TO RENAME A VERY IMPORTANT VARIABLE OOPS

* Really big I forgor moment

* Even bigger I forgor moment

* four more merge conflicts to fix four more oopsies

* fixed actual divide by zero moment and also im very dumb

* Even bigger I forgor moment

* four more merge conflicts to fix four more oopsies

* fixed actual divide by zero moment and also im very dumb

* Fix all test fails

* code cleanup

* Webedit whitespace

* Code cleaup

* whitespace webedit

* whitespace webedit

* whitespace webedit

* whitespace removal

* Comments and cleanup

* Re-Added 20 warnings as per Ork's request

* Cleanups

* Spacing fix

* bugfixes and cleanup

* Small bugfix

* Fix prediction

* Mob movement rewrite

* Bandaid

* Working version

* Tentatively working

* Friction to fix cornering

* More fixes

* Refactor mob movement

Trying to cleanup relay ordering / tryupdaterelative being cooked, purge ToParent, and fix all the eye rotation shenanigans.

* Building

* Re-implement jetpacks

* Reorganise weightless movement

* More work

* Fix camera

* reh

* Revert bagel

* Revert this

* Revert held move buttons

* Puddles work but are unpredicted and unoptimized

* Fixes

* Puddle code...

* Actually dirty the slipComp for real

* Sliding component done plus an extra suggestion from ArtisticRoomba

* Atomized Commit

* Added Friction field to Reagent Prototype per design discussion

* Cleaned up Working Commit

* a

* Delete stinkers

* Fix this code smell

* Reviewed

* Funky re-save

* Our conveyance

* Better conveyor sleeping

* Remove this

* Revert "Better conveyor sleeping"

This reverts commit f5281f64bbae95b7b9feb56295c5cf931f9fb2e1.

* Revert that

Way too janky

* Also this

* a

* Working Commit - Still a lot to do

* Acceleration refactor

* Minor jetpack cleanup

* frictionnomovement no longer nullable

* Shared Mover Feels 99% done

* OffGrid/Weightless/Throwing Friction saved

* Fix merge conflicts

* Fix a debug assert

* Final Commit for today

* Some fixes

* Actually use those CCVars Properly

* Need to fix throwing

* Second to last Commit for real

* Jetpack bug fixed

* Jetpack bug fixed

* Test fail patch

* Small patch

* Skates Component cleanup + Bring Accel back to 5 (oops)

* Fix test fail oops

* yaml cleanup make dragons not fat

---------

Co-authored-by: metalgearsloth <comedian_vs_clown@hotmail.com>
Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com>
42 files changed:
Content.Client/Eye/EyeLerpingSystem.cs
Content.Client/NPC/NPCSteeringSystem.cs
Content.Client/Physics/Controllers/MoverController.cs
Content.Server/Fluids/EntitySystems/PuddleSystem.cs
Content.Server/Physics/Controllers/MoverController.cs
Content.Shared/CCVar/CCVars.Physics.cs
Content.Shared/Chemistry/Reagent/ReagentPrototype.cs
Content.Shared/Clothing/Components/SkatesComponent.cs
Content.Shared/Clothing/EntitySystems/SkatesSystem.cs
Content.Shared/Damage/Systems/DamageOnHighSpeedImpactSystem.cs
Content.Shared/Friction/TileFrictionController.cs
Content.Shared/Inventory/InventorySystem.Relay.cs
Content.Shared/Maps/ContentTileDefinition.cs
Content.Shared/Movement/Components/FrictionContactsComponent.cs
Content.Shared/Movement/Components/InputMoverComponent.cs
Content.Shared/Movement/Components/JetpackComponent.cs
Content.Shared/Movement/Components/JetpackUserComponent.cs
Content.Shared/Movement/Components/MovementModifiedByContactComponent.cs [moved from Content.Shared/Movement/Components/SpeedModifiedByContactComponent.cs with 72% similarity]
Content.Shared/Movement/Components/MovementRelayTargetComponent.cs
Content.Shared/Movement/Components/MovementSpeedModifierComponent.cs
Content.Shared/Movement/Components/RelayInputMoverComponent.cs
Content.Shared/Movement/Systems/FrictionContactsSystem.cs
Content.Shared/Movement/Systems/MovementSpeedModifierSystem.cs
Content.Shared/Movement/Systems/SharedJetpackSystem.cs
Content.Shared/Movement/Systems/SharedMoverController.Input.cs
Content.Shared/Movement/Systems/SharedMoverController.cs
Content.Shared/Movement/Systems/SpeedModifierContactsSystem.cs
Content.Shared/Slippery/SlipperySystem.cs
Content.Shared/Stunnable/SharedStunSystem.cs
Content.Shared/Throwing/ThrowingSystem.cs
Resources/Prototypes/Entities/Effects/puddle.yml
Resources/Prototypes/Entities/Mobs/NPCs/animals.yml
Resources/Prototypes/Entities/Mobs/NPCs/behonker.yml
Resources/Prototypes/Entities/Mobs/NPCs/flying_animals.yml
Resources/Prototypes/Entities/Mobs/Player/dragon.yml
Resources/Prototypes/Entities/Mobs/Species/moth.yml
Resources/Prototypes/Entities/Objects/Fun/immovable_rod.yml
Resources/Prototypes/Entities/Objects/Misc/ice_crust.yml
Resources/Prototypes/Entities/Objects/Tools/jetpacks.yml
Resources/Prototypes/Tiles/floors.yml
Resources/Prototypes/Tiles/planet.yml
Resources/Prototypes/Tiles/plating.yml

index ac32299dca7dc7aa2312e25ee07f8f92c7b45b35..c0a7c016966e8471198e5c208a2f6a9ad123586c 100644 (file)
@@ -34,7 +34,7 @@ public sealed class EyeLerpingSystem : EntitySystem
         SubscribeLocalEvent<LerpingEyeComponent, LocalPlayerDetachedEvent>(OnDetached);
 
         UpdatesAfter.Add(typeof(TransformSystem));
-        UpdatesAfter.Add(typeof(PhysicsSystem));
+        UpdatesAfter.Add(typeof(Robust.Client.Physics.PhysicsSystem));
         UpdatesBefore.Add(typeof(SharedEyeSystem));
         UpdatesOutsidePrediction = true;
     }
index eda3d74cf7151b9003be5fe9bf1778843d9c9174..9ca3ec1a7ea12e77bae3ef9b7d2be2a1cc88d958 100644 (file)
@@ -1,5 +1,6 @@
 using System.Numerics;
 using Content.Client.Physics.Controllers;
+using Content.Client.PhysicsSystem.Controllers;
 using Content.Shared.Movement.Components;
 using Content.Shared.NPC;
 using Content.Shared.NPC.Events;
index 37e3d83ddbb6558dd870102db2c1bc7eea48013f..ca04c93e74d4949e0022ef3c7264ee939f3f40dd 100644 (file)
@@ -3,15 +3,13 @@ using Content.Shared.CCVar;
 using Content.Shared.Movement.Components;
 using Content.Shared.Movement.Pulling.Components;
 using Content.Shared.Movement.Systems;
-using Robust.Client.GameObjects;
 using Robust.Client.Physics;
 using Robust.Client.Player;
 using Robust.Shared.Configuration;
-using Robust.Shared.Physics.Components;
 using Robust.Shared.Player;
 using Robust.Shared.Timing;
 
-namespace Content.Client.Physics.Controllers;
+namespace Content.Client.PhysicsSystem.Controllers;
 
 public sealed class MoverController : SharedMoverController
 {
@@ -101,39 +99,13 @@ public sealed class MoverController : SharedMoverController
 
     private void HandleClientsideMovement(EntityUid player, float frameTime)
     {
-        if (!MoverQuery.TryGetComponent(player, out var mover) ||
-            !XformQuery.TryGetComponent(player, out var xform))
-        {
-            return;
-        }
-
-        var physicsUid = player;
-        PhysicsComponent? body;
-        var xformMover = xform;
-
-        if (mover.ToParent && RelayQuery.HasComponent(xform.ParentUid))
-        {
-            if (!PhysicsQuery.TryGetComponent(xform.ParentUid, out body) ||
-                !XformQuery.TryGetComponent(xform.ParentUid, out xformMover))
-            {
-                return;
-            }
-
-            physicsUid = xform.ParentUid;
-        }
-        else if (!PhysicsQuery.TryGetComponent(player, out body))
+        if (!MoverQuery.TryGetComponent(player, out var mover))
         {
             return;
         }
 
         // Server-side should just be handled on its own so we'll just do this shizznit
-        HandleMobMovement(
-            player,
-            mover,
-            physicsUid,
-            body,
-            xformMover,
-            frameTime);
+        HandleMobMovement((player, mover), frameTime);
     }
 
     protected override bool CanSound()
index 8b668532109d59f94b486077e3301ea9d0b67a9c..94951b01136e53fe283d2713839e2b48b85346b7 100644 (file)
@@ -386,6 +386,9 @@ public sealed partial class PuddleSystem : SharedPuddleSystem
         if (!TryComp<StepTriggerComponent>(entity, out var comp))
             return;
 
+        // Ensure we actually have the component
+        EnsureComp<TileFrictionModifierComponent>(entity);
+
         // This is the base amount of reagent needed before a puddle can be considered slippery. Is defined based on
         // the sprite threshold for a puddle larger than 5 pixels.
         var smallPuddleThreshold = FixedPoint2.New(entity.Comp.OverflowVolume.Float() * LowThreshold);
@@ -409,7 +412,7 @@ public sealed partial class PuddleSystem : SharedPuddleSystem
         if (solution.Volume <= smallPuddleThreshold)
         {
             _stepTrigger.SetActive(entity, false, comp);
-            _tile.SetModifier(entity, TileFrictionController.DefaultFriction);
+            _tile.SetModifier(entity, 1f);
             return;
         }
 
@@ -461,7 +464,7 @@ public sealed partial class PuddleSystem : SharedPuddleSystem
 
         // Lower tile friction based on how slippery it is, lets items slide across a puddle of lube
         slipComp.SlipData.SlipFriction = (float)(puddleFriction/solution.Volume);
-        _tile.SetModifier(entity, TileFrictionController.DefaultFriction * slipComp.SlipData.SlipFriction);
+        _tile.SetModifier(entity, slipComp.SlipData.SlipFriction);
 
         Dirty(entity, slipComp);
     }
@@ -479,7 +482,7 @@ public sealed partial class PuddleSystem : SharedPuddleSystem
         {
             var comp = EnsureComp<SpeedModifierContactsComponent>(uid);
             var speed = 1 - maxViscosity;
-            _speedModContacts.ChangeModifiers(uid, speed, comp);
+            _speedModContacts.ChangeSpeedModifiers(uid, speed, comp);
         }
         else
         {
index d0605e916e72a6370a2a8421bdafb3abfa7fe7da..26684b88822c5b77ff99ac6eb15e21590cd6cfa2 100644 (file)
@@ -57,48 +57,43 @@ public sealed class MoverController : SharedMoverController
         return true;
     }
 
+    private HashSet<EntityUid> _moverAdded = new();
+    private List<Entity<InputMoverComponent>> _movers = new();
+
+    private void InsertMover(Entity<InputMoverComponent> source)
+    {
+        if (TryComp(source, out MovementRelayTargetComponent? relay))
+        {
+            if (TryComp(relay.Source, out InputMoverComponent? relayMover))
+            {
+                InsertMover((relay.Source, relayMover));
+            }
+        }
+
+        // Already added
+        if (!_moverAdded.Add(source.Owner))
+            return;
+
+        _movers.Add(source);
+    }
+
     public override void UpdateBeforeSolve(bool prediction, float frameTime)
     {
         base.UpdateBeforeSolve(prediction, frameTime);
 
+        _moverAdded.Clear();
+        _movers.Clear();
         var inputQueryEnumerator = AllEntityQuery<InputMoverComponent>();
 
+        // Need to order mob movement so that movers don't run before their relays.
         while (inputQueryEnumerator.MoveNext(out var uid, out var mover))
         {
-            var physicsUid = uid;
-
-            if (RelayQuery.HasComponent(uid))
-                continue;
-
-            if (!XformQuery.TryGetComponent(uid, out var xform))
-            {
-                continue;
-            }
-
-            PhysicsComponent? body;
-            var xformMover = xform;
-
-            if (mover.ToParent && RelayQuery.HasComponent(xform.ParentUid))
-            {
-                if (!PhysicsQuery.TryGetComponent(xform.ParentUid, out body) ||
-                    !XformQuery.TryGetComponent(xform.ParentUid, out xformMover))
-                {
-                    continue;
-                }
-
-                physicsUid = xform.ParentUid;
-            }
-            else if (!PhysicsQuery.TryGetComponent(uid, out body))
-            {
-                continue;
-            }
+            InsertMover((uid, mover));
+        }
 
-            HandleMobMovement(uid,
-                mover,
-                physicsUid,
-                body,
-                xformMover,
-                frameTime);
+        foreach (var mover in _movers)
+        {
+            HandleMobMovement(mover, frameTime);
         }
 
         HandleShuttleMovement(frameTime);
index 32f81f023d4adf1673cfd7dd093626cb4c30f885..88aa479ec1635a3869140afe41223774fa629972 100644 (file)
@@ -10,8 +10,17 @@ public sealed partial class CCVars
     public static readonly CVarDef<bool> RelativeMovement =
         CVarDef.Create("physics.relative_movement", true, CVar.ARCHIVE | CVar.REPLICATED | CVar.SERVER);
 
+    public static readonly CVarDef<float> MinFriction =
+        CVarDef.Create("physics.min_friction", 0.0f, CVar.ARCHIVE | CVar.REPLICATED | CVar.SERVER);
+
+    public static readonly CVarDef<float> AirFriction =
+        CVarDef.Create("physics.air_friction", 0.2f, CVar.ARCHIVE | CVar.REPLICATED | CVar.SERVER);
+
+    public static readonly CVarDef<float> OffgridFriction =
+        CVarDef.Create("physics.offgrid_friction", 0.05f, CVar.ARCHIVE | CVar.REPLICATED | CVar.SERVER);
+
     public static readonly CVarDef<float> TileFrictionModifier =
-        CVarDef.Create("physics.tile_friction", 40.0f, CVar.ARCHIVE | CVar.REPLICATED | CVar.SERVER);
+        CVarDef.Create("physics.tile_friction", 8.0f, CVar.ARCHIVE | CVar.REPLICATED | CVar.SERVER);
 
     public static readonly CVarDef<float> StopSpeed =
         CVarDef.Create("physics.stop_speed", 0.1f, CVar.ARCHIVE | CVar.REPLICATED | CVar.SERVER);
index b4fed5fc0371be2eac45b1acb091ac9a826ce380..72fc768bc1a103108b5e6f7e935e07ed9989ed88 100644 (file)
@@ -100,7 +100,6 @@ namespace Content.Shared.Chemistry.Reagent
         [DataField]
         public bool MetamorphicChangeColor { get; private set; } = true;
 
-
         /// <summary>
         /// If not null, makes something slippery. Also defines slippery interactions like stun time and launch mult.
         /// </summary>
index 04b4c722ec17a735e93df30b8d45fc3859bd7f26..50ddc2fa58280f9e62da1f7a954621e97d5cb3a0 100644 (file)
@@ -1,7 +1,7 @@
 using Robust.Shared.GameStates;
 using Content.Shared.Clothing.EntitySystems;
 
-namespace Content.Shared.Clothing;
+namespace Content.Shared.Clothing.Components;
 
 [RegisterComponent]
 [NetworkedComponent]
@@ -11,44 +11,44 @@ public sealed partial class SkatesComponent : Component
     /// <summary>
     /// the levels of friction the wearer is subected to, higher the number the more friction.
     /// </summary>
-    [DataField, ViewVariables(VVAccess.ReadWrite)]
-    public float Friction = 2.5f;
+    [DataField]
+    public float Friction = 0.125f;
 
     /// <summary>
     /// Determines the turning ability of the wearer, Higher the number the less control of their turning ability.
     /// </summary>
-    [DataField, ViewVariables(VVAccess.ReadWrite)]
-    public float? FrictionNoInput = 2.5f;
+    [DataField]
+    public float FrictionNoInput = 0.125f;
 
     /// <summary>
     /// Sets the speed in which the wearer accelerates to full speed, higher the number the quicker the acceleration.
     /// </summary>
-    [DataField, ViewVariables(VVAccess.ReadWrite)]
-    public float Acceleration = 5f;
+    [DataField]
+    public float Acceleration = 0.25f;
 
     /// <summary>
     /// The minimum speed the wearer needs to be traveling to take damage from collision.
     /// </summary>
-    [DataField, ViewVariables(VVAccess.ReadWrite)]
+    [DataField]
     public float MinimumSpeed = 3f;
 
     /// <summary>
     /// The length of time the wearer is stunned for on collision.
     /// </summary>
-    [DataField, ViewVariables(VVAccess.ReadWrite)]
+    [DataField]
     public float StunSeconds = 3f;
 
 
     /// <summary>
     /// The time duration before another collision can take place.
     /// </summary>
-    [DataField, ViewVariables(VVAccess.ReadWrite)]
+    [DataField]
     public float DamageCooldown = 2f;
 
     /// <summary>
     /// The damage per increment of speed on collision.
     /// </summary>
-    [DataField, ViewVariables(VVAccess.ReadWrite)]
+    [DataField]
     public float SpeedDamage = 1f;
 
 
index bbb640bd986b4ec7b1e13f3d1cb61b3a10ff4d5e..2158bce163df324e3d01c9f5f98096381e977fd4 100644 (file)
@@ -1,9 +1,9 @@
-using Content.Shared.Inventory.Events;
 using Content.Shared.Movement.Systems;
 using Content.Shared.Damage.Systems;
-using Content.Shared.Movement.Components;
+using Content.Shared.Inventory;
+using Content.Shared.Clothing.Components;
 
-namespace Content.Shared.Clothing;
+namespace Content.Shared.Clothing.EntitySystems;
 
 /// <summary>
 /// Changes the friction and acceleration of the wearer and also the damage on impact variables of thew wearer when hitting a static object.
@@ -19,26 +19,31 @@ public sealed class SkatesSystem : EntitySystem
 
         SubscribeLocalEvent<SkatesComponent, ClothingGotEquippedEvent>(OnGotEquipped);
         SubscribeLocalEvent<SkatesComponent, ClothingGotUnequippedEvent>(OnGotUnequipped);
+        SubscribeLocalEvent<SkatesComponent, InventoryRelayedEvent<RefreshFrictionModifiersEvent>>(OnRefreshFrictionModifiers);
     }
 
     /// <summary>
     /// When item is unequipped from the shoe slot, friction, aceleration and collide on impact return to default settings.
     /// </summary>
-    public void OnGotUnequipped(EntityUid uid, SkatesComponent component, ClothingGotUnequippedEvent args)
+    private void OnGotUnequipped(Entity<SkatesComponent> entity, ref ClothingGotUnequippedEvent args)
     {
-        if (!TryComp(args.Wearer, out MovementSpeedModifierComponent? speedModifier))
-            return;
-
-        _move.ChangeFriction(args.Wearer, MovementSpeedModifierComponent.DefaultFriction, MovementSpeedModifierComponent.DefaultFrictionNoInput, MovementSpeedModifierComponent.DefaultAcceleration, speedModifier);
-        _impact.ChangeCollide(args.Wearer, component.DefaultMinimumSpeed, component.DefaultStunSeconds, component.DefaultDamageCooldown, component.DefaultSpeedDamage);
+        _move.RefreshFrictionModifiers(args.Wearer);
+        _impact.ChangeCollide(args.Wearer, entity.Comp.DefaultMinimumSpeed, entity.Comp.DefaultStunSeconds, entity.Comp.DefaultDamageCooldown, entity.Comp.DefaultSpeedDamage);
     }
 
     /// <summary>
     /// When item is equipped into the shoe slot, friction, acceleration and collide on impact are adjusted.
     /// </summary>
-    private void OnGotEquipped(EntityUid uid, SkatesComponent component, ClothingGotEquippedEvent args)
+    private void OnGotEquipped(Entity<SkatesComponent> entity, ref ClothingGotEquippedEvent args)
+    {
+        _move.RefreshFrictionModifiers(args.Wearer);
+        _impact.ChangeCollide(args.Wearer, entity.Comp.MinimumSpeed, entity.Comp.StunSeconds, entity.Comp.DamageCooldown, entity.Comp.SpeedDamage);
+    }
+
+    private void OnRefreshFrictionModifiers(Entity<SkatesComponent> ent,
+        ref InventoryRelayedEvent<RefreshFrictionModifiersEvent> args)
     {
-        _move.ChangeFriction(args.Wearer, component.Friction, component.FrictionNoInput, component.Acceleration);
-        _impact.ChangeCollide(args.Wearer, component.MinimumSpeed, component.StunSeconds, component.DamageCooldown, component.SpeedDamage);
+        args.Args.ModifyFriction(ent.Comp.Friction, ent.Comp.FrictionNoInput);
+        args.Args.ModifyAcceleration(ent.Comp.Acceleration);
     }
 }
index 6768048defe16e3071aef04976e4022d41734335..c35e95ecb8bacb853307539aa84d628ac0956892 100644 (file)
@@ -33,6 +33,7 @@ public sealed class DamageOnHighSpeedImpactSystem : EntitySystem
         if (!EntityManager.HasComponent<DamageableComponent>(uid))
             return;
 
+        //TODO: This should solve after physics solves
         var speed = args.OurBody.LinearVelocity.Length();
 
         if (speed < component.MinimumSpeed)
index eb109caa42600af71716849bd76a54ba87dfd36e..1ebf2478f9a54fbc0fe5ba85b229cefdaabf9702 100644 (file)
@@ -1,6 +1,7 @@
 using System.Numerics;
 using Content.Shared.CCVar;
 using Content.Shared.Gravity;
+using Content.Shared.Interaction.Events;
 using Content.Shared.Movement.Events;
 using Content.Shared.Movement.Pulling.Components;
 using Content.Shared.Movement.Systems;
@@ -8,6 +9,7 @@ using JetBrains.Annotations;
 using Robust.Shared.Configuration;
 using Robust.Shared.Map;
 using Robust.Shared.Map.Components;
+using Robust.Shared.Physics;
 using Robust.Shared.Physics.Components;
 using Robust.Shared.Physics.Controllers;
 using Robust.Shared.Physics.Dynamics;
@@ -21,7 +23,6 @@ namespace Content.Shared.Friction
         [Dependency] private readonly ITileDefinitionManager _tileDefinitionManager = default!;
         [Dependency] private readonly SharedGravitySystem _gravity = default!;
         [Dependency] private readonly SharedMoverController _mover = default!;
-        [Dependency] private readonly SharedPhysicsSystem _physics = default!;
         [Dependency] private readonly SharedMapSystem _map = default!;
 
         private EntityQuery<TileFrictionModifierComponent> _frictionQuery;
@@ -30,16 +31,19 @@ namespace Content.Shared.Friction
         private EntityQuery<PullableComponent> _pullableQuery;
         private EntityQuery<MapGridComponent> _gridQuery;
 
-        private float _stopSpeed;
         private float _frictionModifier;
-        public const float DefaultFriction = 0.3f;
+        private float _minDamping;
+        private float _airDamping;
+        private float _offGridDamping;
 
         public override void Initialize()
         {
             base.Initialize();
 
             Subs.CVar(_configManager, CCVars.TileFrictionModifier, value => _frictionModifier = value, true);
-            Subs.CVar(_configManager, CCVars.StopSpeed, value => _stopSpeed = value, true);
+            Subs.CVar(_configManager, CCVars.MinFriction, value => _minDamping = value, true);
+            Subs.CVar(_configManager, CCVars.AirFriction, value => _airDamping = value, true);
+            Subs.CVar(_configManager, CCVars.OffgridFriction, value => _offGridDamping = value, true);
             _frictionQuery = GetEntityQuery<TileFrictionModifierComponent>();
             _xformQuery = GetEntityQuery<TransformComponent>();
             _pullerQuery = GetEntityQuery<PullerComponent>();
@@ -56,12 +60,9 @@ namespace Content.Shared.Friction
                 var uid = body.Owner;
 
                 // Only apply friction when it's not a mob (or the mob doesn't have control)
-                if (prediction && !body.Predict ||
-                    body.BodyStatus == BodyStatus.InAir ||
-                    _mover.UseMobMovement(uid))
-                {
+                // We may want to instead only apply friction to dynamic entities and not mobs ever.
+                if (prediction && !body.Predict || _mover.UseMobMovement(uid))
                     continue;
-                }
 
                 if (body.LinearVelocity.Equals(Vector2.Zero) && body.AngularVelocity.Equals(0f))
                     continue;
@@ -72,7 +73,15 @@ namespace Content.Shared.Friction
                     continue;
                 }
 
-                var surfaceFriction = GetTileFriction(uid, body, xform);
+                float 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))
+                    friction = xform.GridUid == null || !_gridQuery.HasComp(xform.GridUid) ? _offGridDamping : _airDamping;
+                else
+                    friction = _frictionModifier * GetTileFriction(uid, body, xform);
+
                 var bodyModifier = 1f;
 
                 if (_frictionQuery.TryGetComponent(uid, out var frictionComp))
@@ -94,75 +103,24 @@ namespace Content.Shared.Friction
                     bodyModifier *= 0.2f;
                 }
 
-                var friction = _frictionModifier * surfaceFriction * bodyModifier;
+                friction *= bodyModifier;
 
-                ReduceLinearVelocity(uid, prediction, body, friction, frameTime);
-                ReduceAngularVelocity(uid, prediction, body, friction, frameTime);
-            }
-        }
+                friction = Math.Max(_minDamping, friction);
 
-        private void ReduceLinearVelocity(EntityUid uid, bool prediction, PhysicsComponent body, float friction, float frameTime)
-        {
-            var speed = body.LinearVelocity.Length();
+                PhysicsSystem.SetLinearDamping(uid, body, friction);
+                PhysicsSystem.SetAngularDamping(uid, body, friction);
 
-            if (speed <= 0.0f)
-                return;
+                if (body.BodyType != BodyType.KinematicController)
+                    return;
 
-            // This is the *actual* amount that speed will drop by, we just do some multiplication around it to be easier.
-            var drop = 0.0f;
-            float control;
-
-            if (friction > 0.0f)
-            {
-                // TBH I can't really tell if this makes a difference.
-                if (!prediction)
-                {
-                    control = speed < _stopSpeed ? _stopSpeed : speed;
-                }
-                else
-                {
-                    control = speed;
-                }
-
-                drop += control * friction * frameTime;
+                // Physics engine doesn't apply damping to Kinematic Controllers so we have to do it here.
+                // BEWARE YE TRAVELLER:
+                // You may think you can just pass the body.LinearVelocity to the Friction function and edit it there!
+                // But doing so is unpredicted! And you will doom yourself to 1000 years of rubber banding!
+                var velocity = body.LinearVelocity;
+                _mover.Friction(0f, frameTime, friction, ref velocity);
+                PhysicsSystem.SetLinearVelocity(uid, velocity, body: body);
             }
-
-            var newSpeed = MathF.Max(0.0f, speed - drop);
-
-            newSpeed /= speed;
-            _physics.SetLinearVelocity(uid, body.LinearVelocity * newSpeed, body: body);
-        }
-
-        private void ReduceAngularVelocity(EntityUid uid, bool prediction, PhysicsComponent body, float friction, float frameTime)
-        {
-            var speed = MathF.Abs(body.AngularVelocity);
-
-            if (speed <= 0.0f)
-                return;
-
-            // This is the *actual* amount that speed will drop by, we just do some multiplication around it to be easier.
-            var drop = 0.0f;
-            float control;
-
-            if (friction > 0.0f)
-            {
-                // TBH I can't really tell if this makes a difference.
-                if (!prediction)
-                {
-                    control = speed < _stopSpeed ? _stopSpeed : speed;
-                }
-                else
-                {
-                    control = speed;
-                }
-
-                drop += control * friction * frameTime;
-            }
-
-            var newSpeed = MathF.Max(0.0f, speed - drop);
-
-            newSpeed /= speed;
-            _physics.SetAngularVelocity(uid, body.AngularVelocity * newSpeed, body: body);
         }
 
         [Pure]
@@ -171,19 +129,13 @@ namespace Content.Shared.Friction
             PhysicsComponent body,
             TransformComponent xform)
         {
-            // TODO: Make IsWeightless event-based; we already have grid traversals tracked so just raise events
-            if (_gravity.IsWeightless(uid, body, xform))
-                return 0.0f;
-
-            if (!xform.Coordinates.IsValid(EntityManager))
-                return 0.0f;
-
-            // If not on a grid then return the map's friction.
+            var tileModifier = 1f;
+            // If not on a grid and not in the air then return the map's friction.
             if (!_gridQuery.TryGetComponent(xform.GridUid, out var grid))
             {
                 return _frictionQuery.TryGetComponent(xform.MapUid, out var friction)
                     ? friction.Modifier
-                    : DefaultFriction;
+                    : tileModifier;
             }
 
             var tile = _map.GetTileRef(xform.GridUid.Value, grid, xform.Coordinates);
@@ -192,21 +144,18 @@ namespace Content.Shared.Friction
             if (tile.Tile.IsEmpty &&
                 HasComp<MapComponent>(xform.GridUid) &&
                 (!TryComp<GravityComponent>(xform.GridUid, out var gravity) || gravity.Enabled))
-            {
-                return DefaultFriction;
-            }
-
-            // If there's an anchored ent that modifies friction then fallback to that instead.
-            var anc = grid.GetAnchoredEntitiesEnumerator(tile.GridIndices);
+                return tileModifier;
 
+            // Check for anchored ents that modify friction
+            var anc = _map.GetAnchoredEntitiesEnumerator(xform.GridUid.Value, grid, tile.GridIndices);
             while (anc.MoveNext(out var tileEnt))
             {
                 if (_frictionQuery.TryGetComponent(tileEnt, out var friction))
-                    return friction.Modifier;
+                    tileModifier *= friction.Modifier;
             }
 
             var tileDef = _tileDefinitionManager[tile.Tile.TypeId];
-            return tileDef.Friction;
+            return tileDef.Friction * tileModifier;
         }
 
         public void SetModifier(EntityUid entityUid, float value, TileFrictionModifierComponent? friction = null)
index 68d067efc94a3f565bb3db3b77437922de09d1d6..1ab9478a8de3d470548a8921b30e979b97910e1b 100644 (file)
@@ -50,6 +50,7 @@ public partial class InventorySystem
         SubscribeLocalEvent<InventoryComponent, ZombificationResistanceQueryEvent>(RelayInventoryEvent);
 
         // by-ref events
+        SubscribeLocalEvent<InventoryComponent, RefreshFrictionModifiersEvent>(RefRelayInventoryEvent);
         SubscribeLocalEvent<InventoryComponent, BeforeStaminaDamageEvent>(RefRelayInventoryEvent);
         SubscribeLocalEvent<InventoryComponent, GetExplosionResistanceEvent>(RefRelayInventoryEvent);
         SubscribeLocalEvent<InventoryComponent, IsWeightlessEvent>(RefRelayInventoryEvent);
index de5d684c2555da712788689cba20a791a28ffeaa..f1b67fc6c839a92555c8b73a81689074ee6b554f 100644 (file)
@@ -62,7 +62,10 @@ namespace Content.Shared.Maps
         /// </summary>
         [DataField("barestepSounds")] public SoundSpecifier? BarestepSounds { get; private set; } = new SoundCollectionSpecifier("BarestepHard");
 
-        [DataField("friction")] public float Friction { get; set; } = 0.2f;
+        /// <summary>
+        /// Base friction modifier for this tile.
+        /// </summary>
+        [DataField("friction")] public float Friction { get; set; } = 1f;
 
         [DataField("variants")] public byte Variants { get; set; } = 1;
 
@@ -91,12 +94,6 @@ namespace Content.Shared.Maps
         [DataField("mobFriction")]
         public float? MobFriction { get; private set; }
 
-        /// <summary>
-        ///     No-input friction override for mob mover in <see cref="SharedMoverController"/>
-        /// </summary>
-        [DataField("mobFrictionNoInput")]
-        public float? MobFrictionNoInput { get; private set; }
-
         /// <summary>
         ///     Accel override for mob mover in <see cref="SharedMoverController"/>
         /// </summary>
index 693ada49f77ae6ed85f5e7234ce092942c6d473b..204e35a34875345a3c3f39219ff236819bdec761 100644 (file)
@@ -13,19 +13,19 @@ public sealed partial class FrictionContactsComponent : Component
     /// </summary>
     [DataField, ViewVariables(VVAccess.ReadWrite)]
     [AutoNetworkedField]
-    public float MobFriction = 0.5f;
+    public float MobFriction = 0.05f;
 
     /// <summary>
     /// Modified mob friction without input while on FrictionContactsComponent
     /// </summary>
     [AutoNetworkedField]
     [DataField, ViewVariables(VVAccess.ReadWrite)]
-    public float MobFrictionNoInput = 0.05f;
+    public float? MobFrictionNoInput = 0.05f;
 
     /// <summary>
     /// Modified mob acceleration while on FrictionContactsComponent
     /// </summary>
     [AutoNetworkedField]
     [DataField, ViewVariables(VVAccess.ReadWrite)]
-    public float MobAcceleration = 2.0f;
+    public float MobAcceleration = 0.1f;
 }
index 7c3b8b431a37fb7a7d622f317fc007cb71fcdfc3..f03619ccc675bc23c4b31673ed1dd642f1b867d4 100644 (file)
@@ -29,12 +29,6 @@ namespace Content.Shared.Movement.Components
         //   (well maybe we do but the code is designed such that MoverSystem applies movement speed)
         //   (and I'm not changing that)
 
-        /// <summary>
-        /// Should our velocity be applied to our parent?
-        /// </summary>
-        [DataField]
-        public bool ToParent = false;
-
         public GameTick LastInputTick;
         public ushort LastInputSubTick;
 
index 336f4a353c208622714640006e5ea39769e92ffb..a84de7f196d71cb07085fbd4368a1646f2c47d77 100644 (file)
@@ -7,6 +7,9 @@ namespace Content.Shared.Movement.Components;
 [RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
 public sealed partial class JetpackComponent : Component
 {
+    [DataField, AutoNetworkedField]
+    public EntityUid? JetpackUser;
+
     [ViewVariables(VVAccess.ReadWrite), DataField("moleUsage")]
     public float MoleUsage = 0.012f;
 
@@ -18,7 +21,7 @@ public sealed partial class JetpackComponent : Component
     public float Acceleration = 1f;
 
     [ViewVariables(VVAccess.ReadWrite), DataField("friction")]
-    public float Friction = 0.3f;
+    public float Friction = 0.25f; // same as off-grid friction
 
     [ViewVariables(VVAccess.ReadWrite), DataField("weightlessModifier")]
     public float WeightlessModifier = 1.2f;
index 972f8c69c6a27f525587b04d5adc11438ed2a575..ac8f3e78b7214f20cecde54508c55fb61deea0b6 100644 (file)
@@ -8,6 +8,18 @@ namespace Content.Shared.Movement.Components;
 [RegisterComponent, NetworkedComponent, AutoGenerateComponentState]
 public sealed partial class JetpackUserComponent : Component
 {
-    [AutoNetworkedField]
+    [DataField, AutoNetworkedField]
     public EntityUid Jetpack;
+
+    [DataField, AutoNetworkedField]
+    public float WeightlessAcceleration;
+
+    [DataField, AutoNetworkedField]
+    public float WeightlessFriction;
+
+    [DataField, AutoNetworkedField]
+    public float WeightlessFrictionNoInput;
+
+    [DataField, AutoNetworkedField]
+    public float WeightlessModifier;
 }
similarity index 72%
rename from Content.Shared/Movement/Components/SpeedModifiedByContactComponent.cs
rename to Content.Shared/Movement/Components/MovementModifiedByContactComponent.cs
index 4f791e13cab0fda5fc9521a5dc28c9410568b930..1a809ecd2abe10dee22dac2258f1b235736f3ade 100644 (file)
@@ -10,3 +10,8 @@ namespace Content.Shared.Movement.Components;
 public sealed partial class SpeedModifiedByContactComponent : Component
 {
 }
+
+[NetworkedComponent, RegisterComponent] // ditto but for friction
+public sealed partial class FrictionModifiedByContactComponent : Component
+{
+}
index dbe5d23f1c6d5e2cae902b256597a992c24c9300..d6093203a291de980c06fb5d3d4a3b3630a67417 100644 (file)
@@ -10,6 +10,6 @@ public sealed partial class MovementRelayTargetComponent : Component
     /// <summary>
     /// The entity that is relaying to this entity.
     /// </summary>
-    [ViewVariables, AutoNetworkedField]
+    [DataField, AutoNetworkedField]
     public EntityUid Source;
 }
index 6a39aa44b6582dedbf2acb9e04a37c49d7f1c483..f7334dfb4d57e089af9f548e65eea1512b0412da 100644 (file)
@@ -11,91 +11,146 @@ namespace Content.Shared.Movement.Components
     [Access(typeof(MovementSpeedModifierSystem))]
     public sealed partial class MovementSpeedModifierComponent : Component
     {
-        // Weightless
-        public const float DefaultMinimumFrictionSpeed = 0.005f;
+        #region defaults
+
+        // weightless
         public const float DefaultWeightlessFriction = 1f;
-        public const float DefaultWeightlessFrictionNoInput = 0.2f;
-        public const float DefaultOffGridFriction = 0.05f;
         public const float DefaultWeightlessModifier = 0.7f;
         public const float DefaultWeightlessAcceleration = 1f;
 
+        // friction
         public const float DefaultAcceleration = 20f;
-        public const float DefaultFriction = 20f;
-        public const float DefaultFrictionNoInput = 20f;
+        public const float DefaultFriction = 2.5f;
+        public const float DefaultFrictionNoInput = 2.5f;
+        public const float DefaultMinimumFrictionSpeed = 0.005f;
 
+        // movement
         public const float DefaultBaseWalkSpeed = 2.5f;
         public const float DefaultBaseSprintSpeed = 4.5f;
 
-        [AutoNetworkedField, ViewVariables]
-        public float WalkSpeedModifier = 1.0f;
+        #endregion
 
-        [AutoNetworkedField, ViewVariables]
-        public float SprintSpeedModifier = 1.0f;
+        #region base values
+
+        /// <summary>
+        /// These base values should be defined in yaml and rarely if ever modified directly.
+        /// </summary>
+        [DataField, AutoNetworkedField]
+        public float BaseWalkSpeed = DefaultBaseWalkSpeed;
+
+        [DataField, AutoNetworkedField]
+        public float BaseSprintSpeed = DefaultBaseSprintSpeed;
+
+        /// <summary>
+        /// The acceleration applied to mobs when moving. If this is ever less than Friction the mob will be slower.
+        /// </summary>
+        [AutoNetworkedField, DataField]
+        public float BaseAcceleration = DefaultAcceleration;
+
+        /// <summary>
+        /// The body's base friction modifier that is applied in *all* circumstances.
+        /// </summary>
+        [AutoNetworkedField, DataField]
+        public float BaseFriction = DefaultFriction;
 
         /// <summary>
         /// Minimum speed a mob has to be moving before applying movement friction.
         /// </summary>
-        [ViewVariables(VVAccess.ReadWrite), DataField]
+        [DataField]
         public float MinimumFrictionSpeed = DefaultMinimumFrictionSpeed;
 
+        #endregion
+
+        #region calculated values
+
+        [ViewVariables]
+        public float CurrentWalkSpeed => WalkSpeedModifier * BaseWalkSpeed;
+        [ViewVariables]
+        public float CurrentSprintSpeed => SprintSpeedModifier * BaseSprintSpeed;
+
         /// <summary>
-        /// The negative velocity applied for friction when weightless and providing inputs.
+        /// The acceleration applied to mobs when moving. If this is ever less than Friction the mob will be slower.
         /// </summary>
-        [ViewVariables(VVAccess.ReadWrite), DataField]
-        public float WeightlessFriction = DefaultWeightlessFriction;
+        [AutoNetworkedField, DataField]
+        public float Acceleration;
 
         /// <summary>
-        /// The negative velocity applied for friction when weightless and not providing inputs.
-        /// This is essentially how much their speed decreases per second.
+        /// Modifier to the negative velocity applied for friction.
         /// </summary>
-        [ViewVariables(VVAccess.ReadWrite), DataField]
-        public float WeightlessFrictionNoInput = DefaultWeightlessFrictionNoInput;
+        [AutoNetworkedField, DataField]
+        public float Friction;
 
         /// <summary>
-        /// The negative velocity applied for friction when weightless and not standing on a grid or mapgrid
+        /// The negative velocity applied for friction.
         /// </summary>
-        [ViewVariables(VVAccess.ReadWrite), DataField]
-        public float OffGridFriction = DefaultOffGridFriction;
+        [AutoNetworkedField, DataField]
+        public float FrictionNoInput;
+
+        #endregion
+
+        #region movement modifiers
+
+        [AutoNetworkedField, ViewVariables]
+        public float WalkSpeedModifier = 1.0f;
+
+        [AutoNetworkedField, ViewVariables]
+        public float SprintSpeedModifier = 1.0f;
+
+        #endregion
+
+        #region Weightless
 
         /// <summary>
-        /// The movement speed modifier applied to a mob's total input velocity when weightless.
+        /// These base values should be defined in yaml and rarely if ever modified directly.
         /// </summary>
-        [ViewVariables(VVAccess.ReadWrite), DataField]
-        public float WeightlessModifier = DefaultWeightlessModifier;
+        [AutoNetworkedField, DataField]
+        public float BaseWeightlessFriction = DefaultWeightlessFriction;
+
+        [AutoNetworkedField, DataField]
+        public float BaseWeightlessModifier = DefaultWeightlessModifier;
+
+        [AutoNetworkedField, DataField]
+        public float BaseWeightlessAcceleration = DefaultWeightlessAcceleration;
+
+        /*
+         * Final values
+         */
+
+        [ViewVariables]
+        public float WeightlessWalkSpeed => WeightlessModifier * BaseWalkSpeed;
+        [ViewVariables]
+        public float WeightlessSprintSpeed => WeightlessModifier * BaseSprintSpeed;
 
         /// <summary>
         /// The acceleration applied to mobs when moving and weightless.
         /// </summary>
-        [ViewVariables(VVAccess.ReadWrite), DataField]
-        public float WeightlessAcceleration = DefaultWeightlessAcceleration;
+        [AutoNetworkedField, DataField]
+        public float WeightlessAcceleration;
 
         /// <summary>
-        /// The acceleration applied to mobs when moving.
+        /// The movement speed modifier applied to a mob's total input velocity when weightless.
         /// </summary>
-        [AutoNetworkedField, ViewVariables(VVAccess.ReadWrite), DataField]
-        public float Acceleration = DefaultAcceleration;
+        [AutoNetworkedField, DataField]
+        public float WeightlessModifier;
 
         /// <summary>
-        /// The negative velocity applied for friction.
+        /// The negative velocity applied for friction when weightless and providing inputs.
         /// </summary>
-        [AutoNetworkedField, ViewVariables(VVAccess.ReadWrite), DataField]
-        public float Friction = DefaultFriction;
+        [AutoNetworkedField, DataField]
+        public float WeightlessFriction;
 
         /// <summary>
-        /// The negative velocity applied for friction.
+        /// The negative velocity applied for friction when weightless and not providing inputs.
         /// </summary>
-        [AutoNetworkedField, ViewVariables(VVAccess.ReadWrite), DataField]
-        public float? FrictionNoInput;
-
-        [ViewVariables(VVAccess.ReadWrite), DataField, AutoNetworkedField]
-        public float BaseWalkSpeed { get; set; } = DefaultBaseWalkSpeed;
+        [AutoNetworkedField, DataField]
+        public float WeightlessFrictionNoInput;
 
-        [ViewVariables(VVAccess.ReadWrite), DataField, AutoNetworkedField]
-        public float BaseSprintSpeed { get; set; } = DefaultBaseSprintSpeed;
+        /// <summary>
+        /// The negative velocity applied for friction when weightless and not standing on a grid or mapgrid
+        /// </summary>
+        [AutoNetworkedField, DataField]
+        public float? OffGridFriction;
 
-        [ViewVariables]
-        public float CurrentWalkSpeed => WalkSpeedModifier * BaseWalkSpeed;
-        [ViewVariables]
-        public float CurrentSprintSpeed => SprintSpeedModifier * BaseSprintSpeed;
+        #endregion
     }
 }
index c783165cbe0ade6c9e6f5ece7877b5d23b7a2026..24fedf6d6f6b91656535b57926d3ffcd33fa4865 100644 (file)
@@ -10,6 +10,6 @@ namespace Content.Shared.Movement.Components;
 [Access(typeof(SharedMoverController))]
 public sealed partial class RelayInputMoverComponent : Component
 {
-    [ViewVariables, AutoNetworkedField]
+    [DataField, AutoNetworkedField]
     public EntityUid RelayEntity;
 }
index b086bc0e05f5753a7386d9fbbec4a7456d5d3a65..4dd679c37c9c1b155571f970363a13fdde8823be 100644 (file)
@@ -11,38 +11,57 @@ public sealed class FrictionContactsSystem : EntitySystem
     [Dependency] private readonly MovementSpeedModifierSystem _speedModifierSystem = default!;
 
     // Comment copied from "original" SlowContactsSystem.cs (now SpeedModifierContactsSystem.cs)
-    // TODO full-game-save 
+    // TODO full-game-save
     // Either these need to be processed before a map is saved, or slowed/slowing entities need to update on init.
-    private HashSet<EntityUid> _toUpdate = new();
+    private readonly HashSet<EntityUid> _toUpdate = new();
+    private readonly HashSet<EntityUid> _toRemove = new();
 
     public override void Initialize()
     {
         base.Initialize();
         SubscribeLocalEvent<FrictionContactsComponent, StartCollideEvent>(OnEntityEnter);
         SubscribeLocalEvent<FrictionContactsComponent, EndCollideEvent>(OnEntityExit);
+        SubscribeLocalEvent<FrictionModifiedByContactComponent, RefreshFrictionModifiersEvent>(OnRefreshFrictionModifiers);
         SubscribeLocalEvent<FrictionContactsComponent, ComponentShutdown>(OnShutdown);
 
         UpdatesAfter.Add(typeof(SharedPhysicsSystem));
     }
 
-    private void OnEntityEnter(EntityUid uid, FrictionContactsComponent component, ref StartCollideEvent args)
+    public override void Update(float frameTime)
     {
-        var otherUid = args.OtherEntity;
+        base.Update(frameTime);
 
-        if (!HasComp(otherUid, typeof(MovementSpeedModifierComponent)))
-            return;
+        _toRemove.Clear();
 
-        _toUpdate.Add(otherUid);
+        foreach (var ent in _toUpdate)
+        {
+            _speedModifierSystem.RefreshFrictionModifiers(ent);
+        }
+
+        foreach (var ent in _toRemove)
+        {
+            RemComp<FrictionModifiedByContactComponent>(ent);
+        }
+
+        _toUpdate.Clear();
     }
 
-    private void OnEntityExit(EntityUid uid, FrictionContactsComponent component, ref EndCollideEvent args)
+    public void ChangeFrictionModifiers(EntityUid uid, float friction, FrictionContactsComponent? component = null)
     {
-        var otherUid = args.OtherEntity;
+        ChangeFrictionModifiers(uid, friction, null, null, component);
+    }
 
-        if (!HasComp(otherUid, typeof(MovementSpeedModifierComponent)))
+    public void ChangeFrictionModifiers(EntityUid uid, float mobFriction, float? mobFrictionNoInput, float? acceleration, FrictionContactsComponent? component = null)
+    {
+        if (!Resolve(uid, ref component))
             return;
 
-        _toUpdate.Add(otherUid);
+        component.MobFriction = mobFriction;
+        component.MobFrictionNoInput = mobFrictionNoInput;
+        if (acceleration.HasValue)
+            component.MobAcceleration = acceleration.Value;
+        Dirty(uid, component);
+        _toUpdate.UnionWith(_physics.GetContactingEntities(uid));
     }
 
     private void OnShutdown(EntityUid uid, FrictionContactsComponent component, ComponentShutdown args)
@@ -50,51 +69,70 @@ public sealed class FrictionContactsSystem : EntitySystem
         if (!TryComp(uid, out PhysicsComponent? phys))
             return;
 
+        // Note that the entity may not be getting deleted here. E.g., glue puddles.
         _toUpdate.UnionWith(_physics.GetContactingEntities(uid, phys));
     }
 
-    public override void Update(float frameTime)
-    {
-        base.Update(frameTime);
-
-        foreach (var uid in _toUpdate)
-        {
-            ApplyFrictionChange(uid);
-        }
-
-        _toUpdate.Clear();
-    }
-
-    private void ApplyFrictionChange(EntityUid uid)
+    private void OnRefreshFrictionModifiers(Entity<FrictionModifiedByContactComponent> entity, ref RefreshFrictionModifiersEvent args)
     {
-        if (!EntityManager.TryGetComponent<PhysicsComponent>(uid, out var physicsComponent))
-            return;
-
-        if (!TryComp(uid, out MovementSpeedModifierComponent? speedModifier))
+        if (!EntityManager.TryGetComponent<PhysicsComponent>(entity, out var physicsComponent))
             return;
 
-        FrictionContactsComponent? frictionComponent = TouchesFrictionContactsComponent(uid, physicsComponent);
+        var friction = 0.0f;
+        var frictionNoInput = 0.0f;
+        var acceleration = 0.0f;
 
-        if (frictionComponent == null)
+        var remove = true;
+        var entries = 0;
+        foreach (var ent in _physics.GetContactingEntities(entity, physicsComponent))
         {
-            _speedModifierSystem.ChangeFriction(uid, MovementSpeedModifierComponent.DefaultFriction, null, MovementSpeedModifierComponent.DefaultAcceleration, speedModifier);
+            if (!TryComp<FrictionContactsComponent>(ent, out var contacts))
+                continue;
+            friction += contacts.MobFriction;
+            frictionNoInput += contacts.MobFrictionNoInput ?? contacts.MobFriction;
+            acceleration += contacts.MobAcceleration;
+            remove = false;
+            entries++;
         }
-        else
+
+        if (entries > 0)
         {
-            _speedModifierSystem.ChangeFriction(uid, frictionComponent.MobFriction, frictionComponent.MobFrictionNoInput, frictionComponent.MobAcceleration, speedModifier);
+            if (!MathHelper.CloseTo(friction, entries) || !MathHelper.CloseTo(frictionNoInput, entries))
+            {
+                friction /= entries;
+                frictionNoInput /= entries;
+                args.ModifyFriction(friction, frictionNoInput);
+            }
+
+            if (!MathHelper.CloseTo(acceleration, entries))
+            {
+                acceleration /= entries;
+                args.ModifyAcceleration(acceleration);
+            }
         }
+
+        // no longer colliding with anything
+        if (remove)
+            _toRemove.Add(entity);
     }
 
-    private FrictionContactsComponent? TouchesFrictionContactsComponent(EntityUid uid, PhysicsComponent physicsComponent)
+    private void OnEntityExit(EntityUid uid, FrictionContactsComponent component, ref EndCollideEvent args)
     {
-        foreach (var ent in _physics.GetContactingEntities(uid, physicsComponent))
-        {
-            if (!TryComp(ent, out FrictionContactsComponent? frictionContacts))
-                continue;
+        var otherUid = args.OtherEntity;
+        _toUpdate.Add(otherUid);
+    }
 
-            return frictionContacts;
-        }
+    private void OnEntityEnter(EntityUid uid, FrictionContactsComponent component, ref StartCollideEvent args)
+    {
+        AddModifiedEntity(args.OtherEntity);
+    }
+
+    public void AddModifiedEntity(EntityUid uid)
+    {
+        if (!HasComp<MovementSpeedModifierComponent>(uid))
+            return;
 
-        return null;
+        EnsureComp<FrictionModifiedByContactComponent>(uid);
+        _toUpdate.Add(uid);
     }
 }
index 8e89e4b62b3762bb9af64697f620ab79c2f258a4..b5bb633491b0a7e20ce1d0adadbca881f1ef0faa 100644 (file)
@@ -1,5 +1,9 @@
+using System.Text.Json.Serialization.Metadata;
+using Content.Shared.CCVar;
 using Content.Shared.Inventory;
 using Content.Shared.Movement.Components;
+using Content.Shared.Movement.Events;
+using Robust.Shared.Configuration;
 using Robust.Shared.Timing;
 
 namespace Content.Shared.Movement.Systems
@@ -7,6 +11,71 @@ namespace Content.Shared.Movement.Systems
     public sealed class MovementSpeedModifierSystem : EntitySystem
     {
         [Dependency] private readonly IGameTiming _timing = default!;
+        [Dependency] private   readonly IConfigurationManager _configManager = default!;
+
+        private float _frictionModifier;
+        private float _airDamping;
+        private float _offGridDamping;
+
+        public override void Initialize()
+        {
+            base.Initialize();
+            SubscribeLocalEvent<MovementSpeedModifierComponent, MapInitEvent>(OnModMapInit);
+
+            Subs.CVar(_configManager, CCVars.TileFrictionModifier, value => _frictionModifier = value, true);
+            Subs.CVar(_configManager, CCVars.AirFriction, value => _airDamping = value, true);
+            Subs.CVar(_configManager, CCVars.OffgridFriction, value => _offGridDamping = value, true);
+        }
+
+        private void OnModMapInit(Entity<MovementSpeedModifierComponent> ent, ref MapInitEvent args)
+        {
+            // TODO: Dirty these smarter.
+            ent.Comp.WeightlessAcceleration = ent.Comp.BaseWeightlessAcceleration;
+            ent.Comp.WeightlessModifier = ent.Comp.BaseWeightlessModifier;
+            ent.Comp.WeightlessFriction = _airDamping * ent.Comp.BaseWeightlessFriction;
+            ent.Comp.WeightlessFrictionNoInput = _airDamping * ent.Comp.BaseWeightlessFriction;
+            ent.Comp.OffGridFriction = _offGridDamping * ent.Comp.BaseWeightlessFriction;
+            ent.Comp.Acceleration = ent.Comp.BaseAcceleration;
+            ent.Comp.Friction = _frictionModifier * ent.Comp.BaseFriction;
+            ent.Comp.FrictionNoInput = _frictionModifier * ent.Comp.BaseFriction;
+            Dirty(ent);
+        }
+
+        public void RefreshWeightlessModifiers(EntityUid uid, MovementSpeedModifierComponent? move = null)
+        {
+            if (!Resolve(uid, ref move, false))
+                return;
+
+            if (_timing.ApplyingState)
+                return;
+
+            var ev = new RefreshWeightlessModifiersEvent()
+            {
+                WeightlessAcceleration = move.BaseWeightlessAcceleration,
+                WeightlessAccelerationMod = 1.0f,
+                WeightlessModifier = move.BaseWeightlessModifier,
+                WeightlessFriction = move.BaseWeightlessFriction,
+                WeightlessFrictionMod = 1.0f,
+                WeightlessFrictionNoInput = move.BaseWeightlessFriction,
+                WeightlessFrictionNoInputMod = 1.0f,
+            };
+
+            RaiseLocalEvent(uid, ref ev);
+
+            if (MathHelper.CloseTo(ev.WeightlessAcceleration, move.WeightlessAcceleration) &&
+                MathHelper.CloseTo(ev.WeightlessModifier, move.WeightlessModifier) &&
+                MathHelper.CloseTo(ev.WeightlessFriction, move.WeightlessFriction) &&
+                MathHelper.CloseTo(ev.WeightlessFrictionNoInput, move.WeightlessFrictionNoInput))
+            {
+                return;
+            }
+
+            move.WeightlessAcceleration = ev.WeightlessAcceleration * ev.WeightlessAccelerationMod;
+            move.WeightlessModifier = ev.WeightlessModifier;
+            move.WeightlessFriction = _airDamping * ev.WeightlessFriction * ev.WeightlessFrictionMod;
+            move.WeightlessFrictionNoInput = _airDamping * ev.WeightlessFrictionNoInput * ev.WeightlessFrictionNoInputMod;
+            Dirty(uid, move);
+        }
 
         public void RefreshMovementSpeedModifiers(EntityUid uid, MovementSpeedModifierComponent? move = null)
         {
@@ -39,15 +108,42 @@ namespace Content.Shared.Movement.Systems
             Dirty(uid, move);
         }
 
-        // We might want to create separate RefreshMovementFrictionModifiersEvent and RefreshMovementFrictionModifiers function that will call it
-        public void ChangeFriction(EntityUid uid, float friction, float? frictionNoInput, float acceleration, MovementSpeedModifierComponent? move = null)
+        public void RefreshFrictionModifiers(EntityUid uid, MovementSpeedModifierComponent? move = null)
+        {
+            if (!Resolve(uid, ref move, false))
+                return;
+
+            if (_timing.ApplyingState)
+                return;
+
+            var ev = new RefreshFrictionModifiersEvent()
+            {
+                Friction = move.BaseFriction,
+                FrictionNoInput = move.BaseFriction,
+                Acceleration = move.BaseAcceleration,
+            };
+            RaiseLocalEvent(uid, ref ev);
+
+            if (MathHelper.CloseTo(ev.Friction, move.Friction)
+                && MathHelper.CloseTo(ev.FrictionNoInput, move.FrictionNoInput)
+                && MathHelper.CloseTo(ev.Acceleration, move.Acceleration))
+                return;
+
+            move.Friction = _frictionModifier * ev.Friction;
+            move.FrictionNoInput = _frictionModifier * ev.FrictionNoInput;
+            move.Acceleration = ev.Acceleration;
+
+            Dirty(uid, move);
+        }
+
+        public void ChangeBaseFriction(EntityUid uid, float friction, float frictionNoInput, float acceleration, MovementSpeedModifierComponent? move = null)
         {
             if (!Resolve(uid, ref move, false))
                 return;
 
-            move.Friction = friction;
+            move.BaseFriction = friction;
             move.FrictionNoInput = frictionNoInput;
-            move.Acceleration = acceleration;
+            move.BaseAcceleration = acceleration;
             Dirty(uid, move);
         }
     }
@@ -75,4 +171,65 @@ namespace Content.Shared.Movement.Systems
             ModifySpeed(mod, mod);
         }
     }
+
+    [ByRefEvent]
+    public record struct RefreshWeightlessModifiersEvent
+    {
+        public float WeightlessAcceleration;
+        public float WeightlessAccelerationMod;
+
+        public float WeightlessModifier;
+
+        public float WeightlessFriction;
+        public float WeightlessFrictionMod;
+
+        public float WeightlessFrictionNoInput;
+        public float WeightlessFrictionNoInputMod;
+
+        public void ModifyFriction(float friction, float noInput)
+        {
+            WeightlessFrictionMod *= friction;
+            WeightlessFrictionNoInput *= noInput;
+        }
+
+        public void ModifyFriction(float friction)
+        {
+            ModifyFriction(friction, friction);
+        }
+
+        public void ModifyAcceleration(float acceleration, float modifier)
+        {
+            WeightlessAcceleration *= acceleration;
+            WeightlessModifier *= modifier;
+        }
+
+        public void ModifyAcceleration(float modifier)
+        {
+            ModifyAcceleration(modifier, modifier);
+        }
+    }
+    [ByRefEvent]
+    public record struct RefreshFrictionModifiersEvent : IInventoryRelayEvent
+    {
+        public float Friction;
+        public float FrictionNoInput;
+        public float Acceleration;
+
+        public void ModifyFriction(float friction, float noInput)
+        {
+            Friction *= friction;
+            FrictionNoInput *= noInput;
+        }
+
+        public void ModifyFriction(float friction)
+        {
+            ModifyFriction(friction, friction);
+        }
+
+        public void ModifyAcceleration(float acceleration)
+        {
+            Acceleration *= acceleration;
+        }
+        SlotFlags IInventoryRelayEvent.TargetSlots =>  ~SlotFlags.POCKET;
+    }
 }
index 548594c01f7ec960bf8f2e2efc3f237fe335a5c7..72f321d8f55f7ca30451ab11e1ae0062a6b02db2 100644 (file)
@@ -16,7 +16,6 @@ public abstract class SharedJetpackSystem : EntitySystem
     [Dependency] private readonly MovementSpeedModifierSystem _movementSpeedModifier = default!;
     [Dependency] protected readonly SharedAppearanceSystem Appearance = default!;
     [Dependency] protected readonly SharedContainerSystem Container = default!;
-    [Dependency] private readonly SharedMoverController _mover = default!;
     [Dependency] private readonly SharedPopupSystem _popup = default!;
     [Dependency] private readonly SharedPhysicsSystem _physics = default!;
     [Dependency] private readonly ActionContainerSystem _actionContainer = default!;
@@ -27,24 +26,29 @@ public abstract class SharedJetpackSystem : EntitySystem
         SubscribeLocalEvent<JetpackComponent, GetItemActionsEvent>(OnJetpackGetAction);
         SubscribeLocalEvent<JetpackComponent, DroppedEvent>(OnJetpackDropped);
         SubscribeLocalEvent<JetpackComponent, ToggleJetpackEvent>(OnJetpackToggle);
-        SubscribeLocalEvent<JetpackComponent, CanWeightlessMoveEvent>(OnJetpackCanWeightlessMove);
 
+        SubscribeLocalEvent<JetpackUserComponent, RefreshWeightlessModifiersEvent>(OnJetpackUserWeightlessMovement);
         SubscribeLocalEvent<JetpackUserComponent, CanWeightlessMoveEvent>(OnJetpackUserCanWeightless);
         SubscribeLocalEvent<JetpackUserComponent, EntParentChangedMessage>(OnJetpackUserEntParentChanged);
+        SubscribeLocalEvent<JetpackComponent, EntGotInsertedIntoContainerMessage>(OnJetpackMoved);
 
         SubscribeLocalEvent<GravityChangedEvent>(OnJetpackUserGravityChanged);
         SubscribeLocalEvent<JetpackComponent, MapInitEvent>(OnMapInit);
     }
 
-    private void OnMapInit(EntityUid uid, JetpackComponent component, MapInitEvent args)
+    private void OnJetpackUserWeightlessMovement(Entity<JetpackUserComponent> ent, ref RefreshWeightlessModifiersEvent args)
     {
-        _actionContainer.EnsureAction(uid, ref component.ToggleActionEntity, component.ToggleAction);
-        Dirty(uid, component);
+        // Yes this bulldozes the values but primarily for backwards compat atm.
+        args.WeightlessAcceleration = ent.Comp.WeightlessAcceleration;
+        args.WeightlessModifier = ent.Comp.WeightlessModifier;
+        args.WeightlessFriction = ent.Comp.WeightlessFriction;
+        args.WeightlessFrictionNoInput = ent.Comp.WeightlessFrictionNoInput;
     }
 
-    private void OnJetpackCanWeightlessMove(EntityUid uid, JetpackComponent component, ref CanWeightlessMoveEvent args)
+    private void OnMapInit(EntityUid uid, JetpackComponent component, MapInitEvent args)
     {
-        args.CanMove = true;
+        _actionContainer.EnsureAction(uid, ref component.ToggleActionEntity, component.ToggleAction);
+        Dirty(uid, component);
     }
 
     private void OnJetpackUserGravityChanged(ref GravityChangedEvent ev)
@@ -70,6 +74,12 @@ public abstract class SharedJetpackSystem : EntitySystem
         SetEnabled(uid, component, false, args.User);
     }
 
+    private void OnJetpackMoved(Entity<JetpackComponent> ent, ref EntGotInsertedIntoContainerMessage args)
+    {
+        if (args.Container.Owner != ent.Comp.JetpackUser)
+            SetEnabled(ent, ent.Comp, false, ent.Comp.JetpackUser);
+    }
+
     private void OnJetpackUserCanWeightless(EntityUid uid, JetpackUserComponent component, ref CanWeightlessMoveEvent args)
     {
         args.CanMove = true;
@@ -86,26 +96,33 @@ public abstract class SharedJetpackSystem : EntitySystem
         }
     }
 
-    private void SetupUser(EntityUid user, EntityUid jetpackUid)
+    private void SetupUser(EntityUid user, EntityUid jetpackUid, JetpackComponent component)
     {
-        var userComp = EnsureComp<JetpackUserComponent>(user);
-        _mover.SetRelay(user, jetpackUid);
+        EnsureComp<JetpackUserComponent>(user, out var userComp);
+        component.JetpackUser = user;
 
         if (TryComp<PhysicsComponent>(user, out var physics))
             _physics.SetBodyStatus(user, physics, BodyStatus.InAir);
 
         userComp.Jetpack = jetpackUid;
+        userComp.WeightlessAcceleration = component.Acceleration;
+        userComp.WeightlessModifier = component.WeightlessModifier;
+        userComp.WeightlessFriction = component.Friction;
+        userComp.WeightlessFrictionNoInput = component.Friction;
+        _movementSpeedModifier.RefreshWeightlessModifiers(user);
     }
 
-    private void RemoveUser(EntityUid uid)
+    private void RemoveUser(EntityUid uid, JetpackComponent component)
     {
         if (!RemComp<JetpackUserComponent>(uid))
             return;
 
+        component.JetpackUser = null;
+
         if (TryComp<PhysicsComponent>(uid, out var physics))
             _physics.SetBodyStatus(uid, physics, BodyStatus.OnGround);
 
-        RemComp<RelayInputMoverComponent>(uid);
+        _movementSpeedModifier.RefreshWeightlessModifiers(uid);
     }
 
     private void OnJetpackToggle(EntityUid uid, JetpackComponent component, ToggleJetpackEvent args)
@@ -145,42 +162,26 @@ public abstract class SharedJetpackSystem : EntitySystem
     {
         if (IsEnabled(uid) == enabled ||
             enabled && !CanEnable(uid, component))
-        {
             return;
+
+        if (user == null)
+        {
+            if (!Container.TryGetContainingContainer((uid, null, null), out var container))
+                return;
+            user = container.Owner;
         }
 
         if (enabled)
         {
+            SetupUser(user.Value, uid, component);
             EnsureComp<ActiveJetpackComponent>(uid);
         }
         else
         {
+            RemoveUser(user.Value, component);
             RemComp<ActiveJetpackComponent>(uid);
         }
 
-        if (user == null)
-        {
-            Container.TryGetContainingContainer((uid, null, null), out var container);
-            user = container?.Owner;
-        }
-
-        // Can't activate if no one's using.
-        if (user == null && enabled)
-            return;
-
-        if (user != null)
-        {
-            if (enabled)
-            {
-                SetupUser(user.Value, uid);
-            }
-            else
-            {
-                RemoveUser(user.Value);
-            }
-
-            _movementSpeedModifier.RefreshMovementSpeedModifiers(user.Value);
-        }
 
         Appearance.SetData(uid, JetpackVisuals.Enabled, enabled);
         Dirty(uid, component);
index 87849e8f125136fb3e752fe3b86101d4fce3cfe3..464c4c3758c134b44e581b262ae2eaa7e3d12408 100644 (file)
@@ -169,7 +169,7 @@ namespace Content.Shared.Movement.Systems
             }
 
             // If we updated parent then cancel the accumulator and force it now.
-            if (!TryUpdateRelative(mover, XformQuery.GetComponent(uid)) && mover.TargetRelativeRotation.Equals(Angle.Zero))
+            if (!TryUpdateRelative(uid, mover, XformQuery.GetComponent(uid)) && mover.TargetRelativeRotation.Equals(Angle.Zero))
                 return;
 
             mover.LerpTarget = TimeSpan.Zero;
@@ -177,7 +177,7 @@ namespace Content.Shared.Movement.Systems
             Dirty(uid, mover);
         }
 
-        private bool TryUpdateRelative(InputMoverComponent mover, TransformComponent xform)
+        private bool TryUpdateRelative(EntityUid uid, InputMoverComponent mover, TransformComponent xform)
         {
             var relative = xform.GridUid;
             relative ??= xform.MapUid;
@@ -192,38 +192,42 @@ namespace Content.Shared.Movement.Systems
 
             // Okay need to get our old relative rotation with respect to our new relative rotation
             // e.g. if we were right side up on our current grid need to get what that is on our new grid.
-            var currentRotation = Angle.Zero;
-            var targetRotation = Angle.Zero;
+            var oldRelativeRot = Angle.Zero;
+            var relativeRot = Angle.Zero;
 
             // Get our current relative rotation
             if (XformQuery.TryGetComponent(mover.RelativeEntity, out var oldRelativeXform))
             {
-                currentRotation = _transform.GetWorldRotation(oldRelativeXform, XformQuery) + mover.RelativeRotation;
+                oldRelativeRot = _transform.GetWorldRotation(oldRelativeXform);
             }
 
             if (XformQuery.TryGetComponent(relative, out var relativeXform))
             {
                 // This is our current rotation relative to our new parent.
-                mover.RelativeRotation = (currentRotation - _transform.GetWorldRotation(relativeXform)).FlipPositive();
+                relativeRot = _transform.GetWorldRotation(relativeXform);
             }
 
-            // If we went from grid -> map we'll preserve our worldrotation
-            if (relative != null && HasComp<MapComponent>(relative.Value))
+            var diff = relativeRot - oldRelativeRot;
+
+            // If we're going from a grid -> map then preserve the relative rotation so it's seamless if they go into space and back.
+            if (HasComp<MapComponent>(relative) && HasComp<MapGridComponent>(mover.RelativeEntity))
             {
-                targetRotation = currentRotation.FlipPositive().Reduced();
+                mover.TargetRelativeRotation -= diff;
             }
-            // If we went from grid -> grid OR grid -> map then snap the target to cardinal and lerp there.
-            // OR just rotate to zero (depending on cvar)
-            else if (relative != null && MapGridQuery.HasComp(relative.Value))
+            // Snap to nearest cardinal if map -> grid
+            else if (HasComp<MapGridComponent>(relative) && HasComp<MapComponent>(mover.RelativeEntity))
             {
-                if (CameraRotationLocked)
-                    targetRotation = Angle.Zero;
-                else
-                    targetRotation = mover.RelativeRotation.GetCardinalDir().ToAngle().Reduced();
+                var targetDir = mover.TargetRelativeRotation - diff;
+                targetDir = targetDir.GetCardinalDir().ToAngle().Reduced();
+                mover.TargetRelativeRotation = targetDir;
             }
 
+            // Preserve target rotation in relation to the new parent.
+            // Regardless of what the target is don't want the eye to move at all (from the player's perspective).
+            mover.RelativeRotation -= diff;
+
             mover.RelativeEntity = relative;
-            mover.TargetRelativeRotation = targetRotation;
+            Dirty(uid, mover);
             return true;
         }
 
@@ -297,6 +301,7 @@ namespace Content.Shared.Movement.Systems
             // Relayed movement just uses the same keybinds given we're moving the relayed entity
             // the same as us.
 
+            // TODO: Should move this into HandleMobMovement itself.
             if (TryComp<RelayInputMoverComponent>(entity, out var relayMover))
             {
                 DebugTools.Assert(relayMover.RelayEntity != entity);
index a98fc633d0efbed3302173c8b97a2536336c3b95..317bfdabbe294d091f6cf7fe07c3834f060de300 100644 (file)
@@ -1,4 +1,5 @@
 using System.Diagnostics.CodeAnalysis;
+using System.Net;
 using System.Numerics;
 using Content.Shared.Bed.Sleep;
 using Content.Shared.CCVar;
@@ -21,6 +22,7 @@ using Robust.Shared.Physics.Components;
 using Robust.Shared.Physics.Controllers;
 using Robust.Shared.Physics.Systems;
 using Robust.Shared.Prototypes;
+using Robust.Shared.Serialization.Manager.Exceptions;
 using Robust.Shared.Timing;
 using Robust.Shared.Utility;
 using PullableComponent = Content.Shared.Movement.Pulling.Components.PullableComponent;
@@ -61,12 +63,10 @@ public abstract partial class SharedMoverController : VirtualController
 
     private static readonly ProtoId<TagPrototype> FootstepSoundTag = "FootstepSound";
 
-    /// <summary>
-    /// <see cref="CCVars.StopSpeed"/>
-    /// </summary>
-    private float _stopSpeed;
-
     private bool _relativeMovement;
+    private float _minDamping;
+    private float _airDamping;
+    private float _offGridDamping;
 
     /// <summary>
     /// Cache the mob movement calculation to re-use elsewhere.
@@ -90,10 +90,14 @@ public abstract partial class SharedMoverController : VirtualController
         FootstepModifierQuery = GetEntityQuery<FootstepModifierComponent>();
         MapGridQuery = GetEntityQuery<MapGridComponent>();
 
+        SubscribeLocalEvent<MovementSpeedModifierComponent, TileFrictionEvent>(OnTileFriction);
+
         InitializeInput();
         InitializeRelay();
         Subs.CVar(_configManager, CCVars.RelativeMovement, value => _relativeMovement = value, true);
-        Subs.CVar(_configManager, CCVars.StopSpeed, value => _stopSpeed = value, true);
+        Subs.CVar(_configManager, CCVars.MinFriction, value => _minDamping = value, true);
+        Subs.CVar(_configManager, CCVars.AirFriction, value => _airDamping = value, true);
+        Subs.CVar(_configManager, CCVars.OffgridFriction, value => _offGridDamping = value, true);
         UpdatesBefore.Add(typeof(TileFrictionController));
     }
 
@@ -121,157 +125,213 @@ public abstract partial class SharedMoverController : VirtualController
     ///     Movement while considering actionblockers, weightlessness, etc.
     /// </summary>
     protected void HandleMobMovement(
-        EntityUid uid,
-        InputMoverComponent mover,
-        EntityUid physicsUid,
-        PhysicsComponent physicsComponent,
-        TransformComponent xform,
+        Entity<InputMoverComponent> entity,
         float frameTime)
     {
-        var canMove = mover.CanMove;
-        if (RelayTargetQuery.TryGetComponent(uid, out var relayTarget))
+        var uid = entity.Owner;
+        var mover = entity.Comp;
+
+        // If we're a relay then apply all of our data to the parent instead and go next.
+        if (RelayQuery.TryComp(uid, out var relay))
         {
-            if (_mobState.IsIncapacitated(relayTarget.Source) ||
-                TryComp<SleepingComponent>(relayTarget.Source, out _) ||
-                !MoverQuery.TryGetComponent(relayTarget.Source, out var relayedMover))
+            if (!MoverQuery.TryComp(relay.RelayEntity, out var relayTargetMover))
+                return;
+
+            // Always lerp rotation so relay entities aren't cooked.
+            LerpRotation(uid, mover, frameTime);
+            var dirtied = false;
+
+            if (relayTargetMover.RelativeEntity != mover.RelativeEntity)
             {
-                canMove = false;
+                relayTargetMover.RelativeEntity = mover.RelativeEntity;
+                dirtied = true;
             }
-            else
+
+            if (relayTargetMover.RelativeRotation != mover.RelativeRotation)
+            {
+                relayTargetMover.RelativeRotation = mover.RelativeRotation;
+                dirtied = true;
+            }
+
+            if (relayTargetMover.TargetRelativeRotation != mover.TargetRelativeRotation)
             {
-                mover.RelativeEntity = relayedMover.RelativeEntity;
-                mover.RelativeRotation = relayedMover.RelativeRotation;
-                mover.TargetRelativeRotation = relayedMover.TargetRelativeRotation;
+                relayTargetMover.TargetRelativeRotation = mover.TargetRelativeRotation;
+                dirtied = true;
             }
+
+            if (relayTargetMover.CanMove != mover.CanMove)
+            {
+                relayTargetMover.CanMove = mover.CanMove;
+                dirtied = true;
+            }
+
+            if (dirtied)
+            {
+                Dirty(relay.RelayEntity, relayTargetMover);
+            }
+
+            return;
         }
 
-        // Update relative movement
-        if (mover.LerpTarget < Timing.CurTime)
+        if (!XformQuery.TryComp(entity.Owner, out var xform))
+            return;
+
+        RelayTargetQuery.TryComp(uid, out var relayTarget);
+        var relaySource = relayTarget?.Source;
+
+        // If we're not the target of a relay then handle lerp data.
+        if (relaySource == null)
         {
-            if (TryUpdateRelative(mover, xform))
+            // Update relative movement
+            if (mover.LerpTarget < Timing.CurTime)
             {
-                Dirty(uid, mover);
+                TryUpdateRelative(uid, mover, xform);
             }
-        }
 
-        LerpRotation(uid, mover, frameTime);
+            LerpRotation(uid, mover, frameTime);
+        }
 
-        if (!canMove
-            || physicsComponent.BodyStatus != BodyStatus.OnGround && !CanMoveInAirQuery.HasComponent(uid)
+        // If we can't move then just use tile-friction / no movement handling.
+        if (!mover.CanMove
+            || !PhysicsQuery.TryComp(uid, out var physicsComponent)
             || PullableQuery.TryGetComponent(uid, out var pullable) && pullable.BeingPulled)
         {
             UsedMobMovement[uid] = false;
             return;
         }
 
-        UsedMobMovement[uid] = true;
-        // Specifically don't use mover.Owner because that may be different to the actual physics body being moved.
-        var weightless = _gravity.IsWeightless(physicsUid, physicsComponent, xform);
-        var (walkDir, sprintDir) = GetVelocityInput(mover);
-        var touching = false;
+        // 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 inAirHelpless = false;
 
-        // Handle wall-pushes.
-        if (weightless)
+        if (physicsComponent.BodyStatus != BodyStatus.OnGround && !CanMoveInAirQuery.HasComponent(uid))
         {
-            if (xform.GridUid != null)
-                touching = true;
-
-            if (!touching)
+            if (!weightless)
             {
-                var ev = new CanWeightlessMoveEvent(uid);
-                RaiseLocalEvent(uid, ref ev, true);
-                // No gravity: is our entity touching anything?
-                touching = ev.CanMove;
-
-                if (!touching && TryComp<MobMoverComponent>(uid, out var mobMover))
-                    touching |= IsAroundCollider(PhysicsSystem, xform, mobMover, physicsUid, physicsComponent);
+                UsedMobMovement[uid] = false;
+                return;
             }
+            inAirHelpless = true;
         }
 
+        UsedMobMovement[uid] = true;
+
+        var moveSpeedComponent = ModifierQuery.CompOrNull(uid);
+
+        float friction;
+        float accel;
+        Vector2 wishDir;
+        var velocity = physicsComponent.LinearVelocity;
+
         // Get current tile def for things like speed/friction mods
         ContentTileDefinition? tileDef = null;
 
-        // Don't bother getting the tiledef here if we're weightless or in-air
-        // since no tile-based modifiers should be applying in that situation
-        if (MapGridQuery.TryComp(xform.GridUid, out var gridComp)
-            && _mapSystem.TryGetTileRef(xform.GridUid.Value, gridComp, xform.Coordinates, out var tile)
-            && !(weightless || physicsComponent.BodyStatus == BodyStatus.InAir))
+        var touching = false;
+        // Whether we use tilefriction or not
+        if (weightless || inAirHelpless)
         {
-            tileDef = (ContentTileDefinition) _tileDefinitionManager[tile.Tile.TypeId];
-        }
+            // Find the speed we should be moving at and make sure we're not trying to move faster than that
+            var walkSpeed = moveSpeedComponent?.WeightlessWalkSpeed ?? MovementSpeedModifierComponent.DefaultBaseWalkSpeed;
+            var sprintSpeed = moveSpeedComponent?.WeightlessSprintSpeed ?? MovementSpeedModifierComponent.DefaultBaseSprintSpeed;
 
-        // Regular movement.
-        // Target velocity.
-        // This is relative to the map / grid we're on.
-        var moveSpeedComponent = ModifierQuery.CompOrNull(uid);
-
-        var walkSpeed = moveSpeedComponent?.CurrentWalkSpeed ?? MovementSpeedModifierComponent.DefaultBaseWalkSpeed;
-        var sprintSpeed = moveSpeedComponent?.CurrentSprintSpeed ?? MovementSpeedModifierComponent.DefaultBaseSprintSpeed;
+            wishDir = AssertValidWish(mover, walkSpeed, sprintSpeed);
 
-        var total = walkDir * walkSpeed + sprintDir * sprintSpeed;
+            var ev = new CanWeightlessMoveEvent(uid);
+            RaiseLocalEvent(uid, ref ev, true);
 
-        var parentRotation = GetParentGridAngle(mover);
-        var wishDir = _relativeMovement ? parentRotation.RotateVec(total) : total;
+            touching = ev.CanMove || xform.GridUid != null || MapGridQuery.HasComp(xform.GridUid);
 
-        DebugTools.Assert(MathHelper.CloseToPercent(total.Length(), wishDir.Length()));
+            // If we're not on a grid, and not able to move in space check if we're close enough to a grid to touch.
+            if (!touching && MobMoverQuery.TryComp(uid, out var mobMover))
+                touching |= IsAroundCollider(PhysicsSystem, xform, mobMover, uid, physicsComponent);
 
-        float friction;
-        float weightlessModifier;
-        float accel;
-        var velocity = physicsComponent.LinearVelocity;
-
-        // Whether we use weightless friction or not.
-        if (weightless)
-        {
-            if (gridComp == null && !MapGridQuery.HasComp(xform.GridUid))
-                friction = moveSpeedComponent?.OffGridFriction ?? MovementSpeedModifierComponent.DefaultOffGridFriction;
-            else if (wishDir != Vector2.Zero && touching)
-                friction = moveSpeedComponent?.WeightlessFriction ?? MovementSpeedModifierComponent.DefaultWeightlessFriction;
+            // If we're touching then use the weightless values
+            if (touching)
+            {
+                touching = true;
+                if (wishDir != Vector2.Zero)
+                    friction = moveSpeedComponent?.WeightlessFriction ?? _airDamping;
+                else
+                    friction = moveSpeedComponent?.WeightlessFrictionNoInput ?? _airDamping;
+            }
+            // Otherwise use the off-grid values.
             else
-                friction = moveSpeedComponent?.WeightlessFrictionNoInput ?? MovementSpeedModifierComponent.DefaultWeightlessFrictionNoInput;
+            {
+                friction = moveSpeedComponent?.OffGridFriction ?? _offGridDamping;
+            }
 
-            weightlessModifier = moveSpeedComponent?.WeightlessModifier ?? MovementSpeedModifierComponent.DefaultWeightlessModifier;
             accel = moveSpeedComponent?.WeightlessAcceleration ?? MovementSpeedModifierComponent.DefaultWeightlessAcceleration;
         }
         else
         {
-            if (wishDir != Vector2.Zero || moveSpeedComponent?.FrictionNoInput == null)
+            if (MapGridQuery.TryComp(xform.GridUid, out var gridComp)
+                && _mapSystem.TryGetTileRef(xform.GridUid.Value, gridComp, xform.Coordinates, out var tile))
+                tileDef = (ContentTileDefinition) _tileDefinitionManager[tile.Tile.TypeId];
+
+            var walkSpeed = moveSpeedComponent?.CurrentWalkSpeed ?? MovementSpeedModifierComponent.DefaultBaseWalkSpeed;
+            var sprintSpeed = moveSpeedComponent?.CurrentSprintSpeed ?? MovementSpeedModifierComponent.DefaultBaseSprintSpeed;
+
+            wishDir = AssertValidWish(mover, walkSpeed, sprintSpeed);
+
+            if (wishDir != Vector2.Zero)
             {
-                friction = tileDef?.MobFriction ?? moveSpeedComponent?.Friction ?? MovementSpeedModifierComponent.DefaultFriction;
+                friction = moveSpeedComponent?.Friction ?? MovementSpeedModifierComponent.DefaultFriction;
+                friction *= tileDef?.MobFriction ?? tileDef?.Friction ?? 1f;
             }
             else
             {
-                friction = tileDef?.MobFrictionNoInput ?? moveSpeedComponent.FrictionNoInput ?? MovementSpeedModifierComponent.DefaultFrictionNoInput;
+                friction = moveSpeedComponent?.FrictionNoInput ?? MovementSpeedModifierComponent.DefaultFrictionNoInput;
+                friction *= tileDef?.Friction ?? 1f;
             }
 
-            weightlessModifier = 1f;
-            accel = tileDef?.MobAcceleration ?? moveSpeedComponent?.Acceleration ?? MovementSpeedModifierComponent.DefaultAcceleration;
+            accel = moveSpeedComponent?.Acceleration ?? MovementSpeedModifierComponent.DefaultAcceleration;
+            accel *= tileDef?.MobAcceleration ?? 1f;
         }
 
+        // This way friction never exceeds acceleration when you're trying to move.
+        // If you want to slow down an entity with "friction" you shouldn't be using this system.
+        if (wishDir != Vector2.Zero)
+            friction = Math.Min(friction, accel);
+        friction = Math.Max(friction, _minDamping);
         var minimumFrictionSpeed = moveSpeedComponent?.MinimumFrictionSpeed ?? MovementSpeedModifierComponent.DefaultMinimumFrictionSpeed;
         Friction(minimumFrictionSpeed, frameTime, friction, ref velocity);
 
-        wishDir *= weightlessModifier;
-
         if (!weightless || touching)
             Accelerate(ref velocity, in wishDir, accel, frameTime);
 
         SetWishDir((uid, mover), wishDir);
 
-        PhysicsSystem.SetLinearVelocity(physicsUid, velocity, body: physicsComponent);
+        /*
+         * SNAKING!!! >-( 0 ================>
+         * Snaking is a feature where you can move faster by strafing in a direction perpendicular to the
+         * direction you intend to move while still holding the movement key for the direction you're trying to move.
+         * Snaking only works if acceleration exceeds friction, and it's effectiveness scales as acceleration continues
+         * to exceed friction.
+         * Snaking works because friction is applied first in the direction of our current velocity, while acceleration
+         * is applied after in our "Wish Direction" and is capped by the dot of our wish direction and current direction.
+         * This means when you change direction, you're technically able to accelerate more than what the velocity cap
+         * allows, but friction normally eats up the extra movement you gain.
+         * By strafing as stated above you can increase your speed by about 1.4 (square root of 2).
+         * This only works if friction is low enough so be sure that anytime you are letting a mob move in a low friction
+         * environment you take into account the fact they can snake! Also be sure to lower acceleration as well to
+         * prevent jerky movement!
+         */
+        PhysicsSystem.SetLinearVelocity(uid, velocity, body: physicsComponent);
 
         // Ensures that players do not spiiiiiiin
-        PhysicsSystem.SetAngularVelocity(physicsUid, 0, body: physicsComponent);
+        PhysicsSystem.SetAngularVelocity(uid, 0, body: physicsComponent);
 
         // Handle footsteps at the end
-        if (total != Vector2.Zero)
+        if (wishDir != Vector2.Zero)
         {
             if (!NoRotateQuery.HasComponent(uid))
             {
                 // TODO apparently this results in a duplicate move event because "This should have its event run during
                 // island solver"??. So maybe SetRotation needs an argument to avoid raising an event?
                 var worldRot = _transform.GetWorldRotation(xform);
-                _transform.SetLocalRotation(xform, xform.LocalRotation + wishDir.ToWorldAngle() - worldRot);
+
+                _transform.SetLocalRotation(uid, xform.LocalRotation + wishDir.ToWorldAngle() - worldRot, xform);
             }
 
             if (!weightless && MobMoverQuery.TryGetComponent(uid, out var mobMover) &&
@@ -284,9 +344,9 @@ public abstract partial class SharedMoverController : VirtualController
                     .WithVariation(sound.Params.Variation ?? mobMover.FootstepVariation);
 
                 // If we're a relay target then predict the sound for all relays.
-                if (relayTarget != null)
+                if (relaySource != null)
                 {
-                    _audio.PlayPredicted(sound, uid, relayTarget.Source, audioParams);
+                    _audio.PlayPredicted(sound, uid, relaySource.Value, audioParams);
                 }
                 else
                 {
@@ -334,14 +394,12 @@ public abstract partial class SharedMoverController : VirtualController
                 adjustment = Math.Clamp(adjustment, -angleDiff, angleDiff);
             }
 
-            mover.RelativeRotation += adjustment;
-            mover.RelativeRotation.FlipPositive();
+            mover.RelativeRotation = (mover.RelativeRotation + adjustment).FlipPositive();
             Dirty(uid, mover);
         }
         else if (!angleDiff.Equals(Angle.Zero))
         {
-            mover.TargetRelativeRotation.FlipPositive();
-            mover.RelativeRotation = mover.TargetRelativeRotation;
+            mover.RelativeRotation = mover.TargetRelativeRotation.FlipPositive();
             Dirty(uid, mover);
         }
     }
@@ -353,18 +411,10 @@ public abstract partial class SharedMoverController : VirtualController
         if (speed < minimumFrictionSpeed)
             return;
 
-        var drop = 0f;
+        // This equation is lifted from the Physics Island solver.
+        // We re-use it here because Kinematic Controllers can't/shouldn't use the Physics Friction
+        velocity *= Math.Clamp(1.0f - frameTime * friction, 0.0f, 1.0f);
 
-        var control = MathF.Max(_stopSpeed, speed);
-        drop += control * friction * frameTime;
-
-        var newSpeed = MathF.Max(0f, speed - drop);
-
-        if (newSpeed.Equals(speed))
-            return;
-
-        newSpeed /= speed;
-        velocity *= newSpeed;
     }
 
     /// <summary>
@@ -542,4 +592,30 @@ public abstract partial class SharedMoverController : VirtualController
         sound = haveShoes ? tileDef.FootstepSounds : tileDef.BarestepSounds;
         return sound != null;
     }
+
+    private Vector2 AssertValidWish(InputMoverComponent mover, float walkSpeed, float sprintSpeed)
+    {
+        var (walkDir, sprintDir) = GetVelocityInput(mover);
+
+        var total = walkDir * walkSpeed + sprintDir * sprintSpeed;
+
+        var parentRotation = GetParentGridAngle(mover);
+        var wishDir = _relativeMovement ? parentRotation.RotateVec(total) : total;
+
+        DebugTools.Assert(MathHelper.CloseToPercent(total.Length(), wishDir.Length()));
+
+        return wishDir;
+    }
+
+    private void OnTileFriction(Entity<MovementSpeedModifierComponent> ent, ref TileFrictionEvent args)
+    {
+        if (!TryComp<PhysicsComponent>(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))
+            args.Modifier *= ent.Comp.BaseWeightlessFriction;
+        else
+            args.Modifier *= ent.Comp.BaseFriction;
+    }
 }
index e53a4351d195732f9a69c88045cea1761efedf9a..9c96a2b7dc2f0324142d348a5488934739f95457 100644 (file)
@@ -1,4 +1,3 @@
-using Content.Shared.Inventory;
 using Content.Shared.Movement.Components;
 using Content.Shared.Movement.Events;
 using Content.Shared.Gravity;
@@ -19,15 +18,15 @@ public sealed class SpeedModifierContactsSystem : EntitySystem
 
     // TODO full-game-save
     // Either these need to be processed before a map is saved, or slowed/slowing entities need to update on init.
-    private HashSet<EntityUid> _toUpdate = new();
-    private HashSet<EntityUid> _toRemove = new();
+    private readonly HashSet<EntityUid> _toUpdate = new();
+    private readonly HashSet<EntityUid> _toRemove = new();
 
     public override void Initialize()
     {
         base.Initialize();
         SubscribeLocalEvent<SpeedModifierContactsComponent, StartCollideEvent>(OnEntityEnter);
         SubscribeLocalEvent<SpeedModifierContactsComponent, EndCollideEvent>(OnEntityExit);
-        SubscribeLocalEvent<SpeedModifiedByContactComponent, RefreshMovementSpeedModifiersEvent>(MovementSpeedCheck);
+        SubscribeLocalEvent<SpeedModifiedByContactComponent, RefreshMovementSpeedModifiersEvent>(OnRefreshMovementSpeedModifiers);
         SubscribeLocalEvent<SpeedModifierContactsComponent, ComponentShutdown>(OnShutdown);
 
         UpdatesAfter.Add(typeof(SharedPhysicsSystem));
@@ -52,17 +51,16 @@ public sealed class SpeedModifierContactsSystem : EntitySystem
         _toUpdate.Clear();
     }
 
-    public void ChangeModifiers(EntityUid uid, float speed, SpeedModifierContactsComponent? component = null)
+    public void ChangeSpeedModifiers(EntityUid uid, float speed, SpeedModifierContactsComponent? component = null)
     {
-        ChangeModifiers(uid, speed, speed, component);
+        ChangeSpeedModifiers(uid, speed, speed, component);
     }
 
-    public void ChangeModifiers(EntityUid uid, float walkSpeed, float sprintSpeed, SpeedModifierContactsComponent? component = null)
+    public void ChangeSpeedModifiers(EntityUid uid, float walkSpeed, float sprintSpeed, SpeedModifierContactsComponent? component = null)
     {
         if (!Resolve(uid, ref component))
-        {
             return;
-        }
+
         component.WalkSpeedModifier = walkSpeed;
         component.SprintSpeedModifier = sprintSpeed;
         Dirty(uid, component);
@@ -78,7 +76,7 @@ public sealed class SpeedModifierContactsSystem : EntitySystem
         _toUpdate.UnionWith(_physics.GetContactingEntities(uid, phys));
     }
 
-    private void MovementSpeedCheck(EntityUid uid, SpeedModifiedByContactComponent component, RefreshMovementSpeedModifiersEvent args)
+    private void OnRefreshMovementSpeedModifiers(EntityUid uid, SpeedModifiedByContactComponent component, RefreshMovementSpeedModifiersEvent args)
     {
         if (!EntityManager.TryGetComponent<PhysicsComponent>(uid, out var physicsComponent))
             return;
@@ -110,12 +108,12 @@ public sealed class SpeedModifierContactsSystem : EntitySystem
             }
 
             // SpeedModifierContactsComponent takes priority over SlowedOverSlipperyComponent, effectively overriding the slippery slow.
-            if (TryComp<SlipperyComponent>(ent, out var slipperyComponent) && speedModified == false)
+            if (HasComp<SlipperyComponent>(ent) && speedModified == false)
             {
                 var evSlippery = new GetSlowedOverSlipperyModifierEvent();
                 RaiseLocalEvent(uid, ref evSlippery);
 
-                if (evSlippery.SlowdownModifier != 1)
+                if (MathHelper.CloseTo(evSlippery.SlowdownModifier, 1))
                 {
                     walkSpeed += evSlippery.SlowdownModifier;
                     sprintSpeed += evSlippery.SlowdownModifier;
@@ -130,7 +128,7 @@ public sealed class SpeedModifierContactsSystem : EntitySystem
             }
         }
 
-        if (entries > 0)
+        if (entries > 0 && (!MathHelper.CloseTo(walkSpeed, entries) || !MathHelper.CloseTo(sprintSpeed, entries)))
         {
             walkSpeed /= entries;
             sprintSpeed /= entries;
index 4da347acbdac6c49509468ff5f82705f347d7ed3..bedf05536b8bee5b130fe778870ef683f493ca18 100644 (file)
@@ -119,6 +119,7 @@ public sealed class SlipperySystem : EntitySystem
             {
                 var sliding = EnsureComp<SlidingComponent>(other);
                 sliding.CollidingEntities.Add(uid);
+                // Why the fuck does this assertion stack overflow every once in a while
                 DebugTools.Assert(_physics.GetContactingEntities(other, physics).Contains(uid));
             }
         }
index 17115d4aa6e9112628a34503f1850e1de1dd92dc..1866ad39ef4a5c59728c89cb52808afb5de2ea92 100644 (file)
@@ -38,7 +38,7 @@ public abstract class SharedStunSystem : EntitySystem
     /// Friction modifier for knocked down players.
     /// Doesn't make them faster but makes them slow down... slower.
     /// </summary>
-    public const float KnockDownModifier = 0.4f;
+    public const float KnockDownModifier = 0.2f;
 
     public override void Initialize()
     {
index c37f2356989fc2ad895348f8f68d4b20acc0e478..567c969b0fd7a140bf637b95fdc88b330d963f89 100644 (file)
@@ -29,7 +29,10 @@ public sealed class ThrowingSystem : EntitySystem
 
     public const float FlyTimePercentage = 0.8f;
 
+    private const float TileFrictionMod = 1.5f;
+
     private float _frictionModifier;
+    private float _airDamping;
 
     [Dependency] private readonly IGameTiming _gameTiming = default!;
     [Dependency] private readonly SharedGravitySystem _gravity = default!;
@@ -45,6 +48,7 @@ public sealed class ThrowingSystem : EntitySystem
         base.Initialize();
 
         Subs.CVar(_configManager, CCVars.TileFrictionModifier, value => _frictionModifier = value, true);
+        Subs.CVar(_configManager, CCVars.AirFriction, value => _airDamping = value, true);
     }
 
     public void TryThrow(
@@ -159,7 +163,7 @@ public sealed class ThrowingSystem : EntitySystem
         };
 
         // if not given, get the default friction value for distance calculation
-        var tileFriction = friction ?? _frictionModifier * TileFrictionController.DefaultFriction;
+        var tileFriction = friction ?? _frictionModifier * TileFrictionMod;
 
         if (tileFriction == 0f)
             compensateFriction = false; // cannot calculate this if there is no friction
@@ -202,6 +206,7 @@ public sealed class ThrowingSystem : EntitySystem
         // else let the item land on the cursor and from where it slides a little further.
         // This is an exact formula we get from exponentially decaying velocity after landing.
         // If someone changes how tile friction works at some point, this will have to be adjusted.
+        // This doesn't actually compensate for air friction, but it's low enough it shouldn't matter.
         var throwSpeed = compensateFriction ? direction.Length() / (flyTime + 1 / tileFriction) : baseThrowSpeed;
         var impulseVector = direction.Normalized() * throwSpeed * physics.Mass;
         _physics.ApplyLinearImpulse(uid, impulseVector, body: physics);
index f89e850193e9173a876e66ac102c010658cc2a61..49962ec189aac97d61f8b474a6c2ff942700c2a8 100644 (file)
   components:
   - type: Clickable
   - type: Slippery
-  - type: TileFrictionModifier
-    modifier: 0.3
   - type: Transform
     noRot: true
     anchored: true
index 22f818d56b8935a5c02fab8bde57ce83e0338c9b..23b855c18f90a30165af9c24ef514f6d4f847b0f 100644 (file)
     baseSprintSpeed : 4
     weightlessAcceleration: 1.5
     weightlessFriction: 1
-    weightlessModifier: 1
+    baseWeightlessModifier: 1
   - type: Damageable
     damageContainer: Biological
     damageModifierSet: Moth
index 6a20a503153b72cfec9b29043c80f03421561830..8229b103e7d36c6e66b01e371b4bcd70f16a1e26 100644 (file)
@@ -49,7 +49,7 @@
     - type: MovementSpeedModifier
       baseWalkSpeed: 3
       baseSprintSpeed: 5
-      weightlessModifier: 1.5
+      baseWeightlessModifier: 1.5
     - type: Sprite
       sprite: Mobs/Demons/behonker.rsi
       layers:
index c6846b8ef4b762365c5042d3a918aa1d5ff60847..0347d394b7460c0c0d609c1a21c0546db32520b0 100644 (file)
@@ -9,3 +9,4 @@
   - type: NoSlip
   - type: MovementAlwaysTouching
   - type: CanMoveInAir
+  - type: MovementSpeedModifier
index 2721c6dfd1b8c1ad8bedced766a179689dcb5dba..0eafd75754bd76931b916b4e8650626a477d8052 100644 (file)
@@ -41,7 +41,7 @@
   - type: MovementSpeedModifier
     baseWalkSpeed: 3
     baseSprintSpeed: 5
-    weightlessModifier: 1.5
+    baseWeightlessModifier: 1.5
   - type: RandomSprite
     available:
     - enum.DamageStateVisualLayers.Base:
index 59e3d6a7a5ece1ddb7d6b57996ffefc6fe49690d..2bdd103dd9532f857e085296e8ac8e8a0c617360 100644 (file)
@@ -50,7 +50,7 @@
   - type: MovementSpeedModifier
     weightlessAcceleration: 1.5 # Move around more easily in space.
     weightlessFriction: 1
-    weightlessModifier: 1
+    baseWeightlessModifier: 1
   - type: Flammable
     damage:
       types:
         state: "creampie_moth"
         visible: false
   - type: Inventory
-    speciesId: moth 
+    speciesId: moth
     femaleDisplacements:
       jumpsuit:
         sizeMaps:
   - type: HumanoidAppearance
     species: Moth
   - type: Inventory
-    speciesId: moth 
+    speciesId: moth
     femaleDisplacements:
       jumpsuit:
         sizeMaps:
index 324502c3d1f732b52999c30ac867b30fdc33ce9b..9a7a1ab4fb7adf7fa7f9c9b6cd6287175cc6b700 100644 (file)
@@ -14,6 +14,8 @@
   - type: Physics
     bodyType: Dynamic
     linearDamping: 0
+  - type: TileFrictionModifier
+    modifier: 0
   - type: PointLight
     radius: 3
     color: red
     gravityState: true
   - type: InputMover
   - type: MovementSpeedModifier
-    weightlessAcceleration: 5
-    weightlessModifier: 2
-    weightlessFriction: 0
-    friction: 0
-    frictionNoInput: 0
+    baseWeightlessAcceleration: 5
+    baseWeightlessModifier: 2
+    baseWeightlessFriction: 0
+    baseFriction: 0
+    offGridFriction: 0
   - type: CanMoveInAir
   - type: MovementAlwaysTouching
   - type: NoSlip
index 43c4568e70a52d5adfd8556d7420f8db5f450867..8262aab46144b89ffb12ce2e3a8466c2b463f483 100644 (file)
@@ -50,3 +50,5 @@
       coldDamage: {}
       coldDamageThreshold: 0
     - type: FrictionContacts
+    - type: TileFrictionModifier
+      modifier: 0.05
index abbf68a27eeaeaec10171d76aba9398bc9e56753..a9dde098f934a3f3693d527f2161a0aae9726450 100644 (file)
   name: jetpack
   description: It's a jetpack. It can hold 5 L of gas.
   components:
-    - type: InputMover
-      toParent: true
-    - type: MovementSpeedModifier
-      weightlessAcceleration: 1
-      weightlessFriction: 0.3
-      weightlessModifier: 1.2
-    - type: CanMoveInAir
     - type: Sprite
       sprite: Objects/Tanks/Jetpacks/blue.rsi
       state: icon
index 1431fab3c000e1da8f5885f6d6452fb1c90d1d7e..39c109207dff90ab64df5307b77813820c4297f6 100644 (file)
     collection: FootstepCarpet
   barestepSounds:
     collection: BarestepCarpet
-  friction: 0.25
+  friction: 1.25
   itemDrop: FloorTileItemArcadeBlue
   heatCapacity: 10000
 
     collection: FootstepCarpet
   barestepSounds:
     collection: BarestepCarpet
-  friction: 0.25
+  friction: 1.25
   itemDrop: FloorTileItemArcadeBlue2
   heatCapacity: 10000
 
     collection: FootstepCarpet
   barestepSounds:
     collection: BarestepCarpet
-  friction: 0.25
+  friction: 1.25
   itemDrop: FloorTileItemArcadeRed
   heatCapacity: 10000
 
     collection: FootstepCarpet
   barestepSounds:
     collection: BarestepCarpet
-  friction: 0.25
+  friction: 1.25
   itemDrop: FloorTileItemEighties
   heatCapacity: 10000
 
     collection: FootstepCarpet
   barestepSounds:
     collection: BarestepCarpet
-  friction: 0.25
+  friction: 1.25
   itemDrop: FloorTileItemCarpetClown
   heatCapacity: 10000
 
     collection: FootstepCarpet
   barestepSounds:
     collection: BarestepCarpet
-  friction: 0.25
+  friction: 1.25
   itemDrop: FloorTileItemCarpetOffice
   heatCapacity: 10000
 
   deconstructTools: [ Prying ]
   footstepSounds:
     collection: FootstepFloor
-  friction: 0.25
+  friction: 1.25
   itemDrop: FloorTileItemBoxing
   heatCapacity: 10000
 
   deconstructTools: [ Prying ]
   footstepSounds:
     collection: FootstepFloor
-  friction: 0.25
+  friction: 1.25
   itemDrop: FloorTileItemGym
   heatCapacity: 10000
 
   footstepSounds:
     collection: FootstepBlood
   itemDrop: FloorTileItemFlesh
-  friction: 0.05 #slippy
+  friction: 0.25 #slippy
   heatCapacity: 10000
 
 - type: tile
   deconstructTools: [ Prying ]
   friction: 0.05
   heatCapacity: 10000
-  mobFriction: 0.5
-  mobFrictionNoInput: 0.05
-  mobAcceleration: 2
+  mobFriction: 0.05
+  mobAcceleration: 0.1
   itemDrop: FloorTileItemAstroIce
 
 - type: tile
index ccb3fb61dacdc966e298e39282846a363aeede00..f478f4e5a43d1bac4fb0ed6e01f5d7f72c643679 100644 (file)
   friction: 0.05
   heatCapacity: 10000
   weather: true
-  mobFriction: 0.5
-  mobFrictionNoInput: 0.05
-  mobAcceleration: 2
+  mobAcceleration: 0.1
   indestructible: true
 
 # Dug snow
index 1f6579c47e9390dfa5077d99ffdb698d7610bfd0..e3f4c89d94b9c4591b759a12a1eaa7a5b2610c5f 100644 (file)
@@ -6,7 +6,7 @@
   isSubfloor: true
   footstepSounds:
     collection: FootstepPlating
-  friction: 0.3
+  friction: 1.5
   heatCapacity: 10000
 
 - type: tile
@@ -22,7 +22,7 @@
   isSubfloor: true
   footstepSounds:
     collection: FootstepPlating
-  friction: 0.3
+  friction: 1.5
   heatCapacity: 10000
 
 - type: tile
@@ -33,7 +33,7 @@
   isSubfloor: true
   footstepSounds:
     collection: FootstepPlating
-  friction: 0.3
+  friction: 1.5
   heatCapacity: 10000
 
 - type: tile
@@ -44,7 +44,7 @@
   isSubfloor: true
   footstepSounds:
     collection: FootstepPlating
-  friction: 0.3
+  friction: 1.5
   heatCapacity: 10000
 
 - type: tile
@@ -55,7 +55,7 @@
   isSubfloor: true
   footstepSounds:
     collection: FootstepPlating
-  friction: 0.15 #a little less then actual snow
+  friction: 0.75 #a little less then actual snow
   heatCapacity: 10000
 
 - type: tile
@@ -68,7 +68,7 @@
   weather: true
   footstepSounds:
     collection: FootstepCatwalk
-  friction: 0.3
+  friction: 1.5
   isSpace: true
   itemDrop: PartRodMetal1
   heatCapacity: 10000
@@ -83,7 +83,7 @@
   weather: true
   footstepSounds:
     collection: FootstepPlating
-  friction: 0.3
+  friction: 1.5
   isSpace: true
   itemDrop: PartRodMetal1
   heatCapacity: 10000