From: Princess Cheeseballs <66055347+Pronana@users.noreply.github.com> Date: Fri, 2 May 2025 08:18:08 +0000 (-0700) Subject: Mob Movement Major Refactor (#36847) X-Git-Url: https://git.smokeofanarchy.ru/gitweb.cgi?a=commitdiff_plain;h=36030ef154272c53790bf0043cda8279a7912157;p=space-station-14.git Mob Movement Major Refactor (#36847) * 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 Co-authored-by: metalgearsloth <31366439+metalgearsloth@users.noreply.github.com> --- diff --git a/Content.Client/Eye/EyeLerpingSystem.cs b/Content.Client/Eye/EyeLerpingSystem.cs index ac32299dca..c0a7c01696 100644 --- a/Content.Client/Eye/EyeLerpingSystem.cs +++ b/Content.Client/Eye/EyeLerpingSystem.cs @@ -34,7 +34,7 @@ public sealed class EyeLerpingSystem : EntitySystem SubscribeLocalEvent(OnDetached); UpdatesAfter.Add(typeof(TransformSystem)); - UpdatesAfter.Add(typeof(PhysicsSystem)); + UpdatesAfter.Add(typeof(Robust.Client.Physics.PhysicsSystem)); UpdatesBefore.Add(typeof(SharedEyeSystem)); UpdatesOutsidePrediction = true; } diff --git a/Content.Client/NPC/NPCSteeringSystem.cs b/Content.Client/NPC/NPCSteeringSystem.cs index eda3d74cf7..9ca3ec1a7e 100644 --- a/Content.Client/NPC/NPCSteeringSystem.cs +++ b/Content.Client/NPC/NPCSteeringSystem.cs @@ -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; diff --git a/Content.Client/Physics/Controllers/MoverController.cs b/Content.Client/Physics/Controllers/MoverController.cs index 37e3d83ddb..ca04c93e74 100644 --- a/Content.Client/Physics/Controllers/MoverController.cs +++ b/Content.Client/Physics/Controllers/MoverController.cs @@ -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() diff --git a/Content.Server/Fluids/EntitySystems/PuddleSystem.cs b/Content.Server/Fluids/EntitySystems/PuddleSystem.cs index 8b66853210..94951b0113 100644 --- a/Content.Server/Fluids/EntitySystems/PuddleSystem.cs +++ b/Content.Server/Fluids/EntitySystems/PuddleSystem.cs @@ -386,6 +386,9 @@ public sealed partial class PuddleSystem : SharedPuddleSystem if (!TryComp(entity, out var comp)) return; + // Ensure we actually have the component + EnsureComp(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(uid); var speed = 1 - maxViscosity; - _speedModContacts.ChangeModifiers(uid, speed, comp); + _speedModContacts.ChangeSpeedModifiers(uid, speed, comp); } else { diff --git a/Content.Server/Physics/Controllers/MoverController.cs b/Content.Server/Physics/Controllers/MoverController.cs index d0605e916e..26684b8882 100644 --- a/Content.Server/Physics/Controllers/MoverController.cs +++ b/Content.Server/Physics/Controllers/MoverController.cs @@ -57,48 +57,43 @@ public sealed class MoverController : SharedMoverController return true; } + private HashSet _moverAdded = new(); + private List> _movers = new(); + + private void InsertMover(Entity 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(); + // 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); diff --git a/Content.Shared/CCVar/CCVars.Physics.cs b/Content.Shared/CCVar/CCVars.Physics.cs index 32f81f023d..88aa479ec1 100644 --- a/Content.Shared/CCVar/CCVars.Physics.cs +++ b/Content.Shared/CCVar/CCVars.Physics.cs @@ -10,8 +10,17 @@ public sealed partial class CCVars public static readonly CVarDef RelativeMovement = CVarDef.Create("physics.relative_movement", true, CVar.ARCHIVE | CVar.REPLICATED | CVar.SERVER); + public static readonly CVarDef MinFriction = + CVarDef.Create("physics.min_friction", 0.0f, CVar.ARCHIVE | CVar.REPLICATED | CVar.SERVER); + + public static readonly CVarDef AirFriction = + CVarDef.Create("physics.air_friction", 0.2f, CVar.ARCHIVE | CVar.REPLICATED | CVar.SERVER); + + public static readonly CVarDef OffgridFriction = + CVarDef.Create("physics.offgrid_friction", 0.05f, CVar.ARCHIVE | CVar.REPLICATED | CVar.SERVER); + public static readonly CVarDef 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 StopSpeed = CVarDef.Create("physics.stop_speed", 0.1f, CVar.ARCHIVE | CVar.REPLICATED | CVar.SERVER); diff --git a/Content.Shared/Chemistry/Reagent/ReagentPrototype.cs b/Content.Shared/Chemistry/Reagent/ReagentPrototype.cs index b4fed5fc03..72fc768bc1 100644 --- a/Content.Shared/Chemistry/Reagent/ReagentPrototype.cs +++ b/Content.Shared/Chemistry/Reagent/ReagentPrototype.cs @@ -100,7 +100,6 @@ namespace Content.Shared.Chemistry.Reagent [DataField] public bool MetamorphicChangeColor { get; private set; } = true; - /// /// If not null, makes something slippery. Also defines slippery interactions like stun time and launch mult. /// diff --git a/Content.Shared/Clothing/Components/SkatesComponent.cs b/Content.Shared/Clothing/Components/SkatesComponent.cs index 04b4c722ec..50ddc2fa58 100644 --- a/Content.Shared/Clothing/Components/SkatesComponent.cs +++ b/Content.Shared/Clothing/Components/SkatesComponent.cs @@ -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 /// /// the levels of friction the wearer is subected to, higher the number the more friction. /// - [DataField, ViewVariables(VVAccess.ReadWrite)] - public float Friction = 2.5f; + [DataField] + public float Friction = 0.125f; /// /// Determines the turning ability of the wearer, Higher the number the less control of their turning ability. /// - [DataField, ViewVariables(VVAccess.ReadWrite)] - public float? FrictionNoInput = 2.5f; + [DataField] + public float FrictionNoInput = 0.125f; /// /// Sets the speed in which the wearer accelerates to full speed, higher the number the quicker the acceleration. /// - [DataField, ViewVariables(VVAccess.ReadWrite)] - public float Acceleration = 5f; + [DataField] + public float Acceleration = 0.25f; /// /// The minimum speed the wearer needs to be traveling to take damage from collision. /// - [DataField, ViewVariables(VVAccess.ReadWrite)] + [DataField] public float MinimumSpeed = 3f; /// /// The length of time the wearer is stunned for on collision. /// - [DataField, ViewVariables(VVAccess.ReadWrite)] + [DataField] public float StunSeconds = 3f; /// /// The time duration before another collision can take place. /// - [DataField, ViewVariables(VVAccess.ReadWrite)] + [DataField] public float DamageCooldown = 2f; /// /// The damage per increment of speed on collision. /// - [DataField, ViewVariables(VVAccess.ReadWrite)] + [DataField] public float SpeedDamage = 1f; diff --git a/Content.Shared/Clothing/EntitySystems/SkatesSystem.cs b/Content.Shared/Clothing/EntitySystems/SkatesSystem.cs index bbb640bd98..2158bce163 100644 --- a/Content.Shared/Clothing/EntitySystems/SkatesSystem.cs +++ b/Content.Shared/Clothing/EntitySystems/SkatesSystem.cs @@ -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; /// /// 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(OnGotEquipped); SubscribeLocalEvent(OnGotUnequipped); + SubscribeLocalEvent>(OnRefreshFrictionModifiers); } /// /// When item is unequipped from the shoe slot, friction, aceleration and collide on impact return to default settings. /// - public void OnGotUnequipped(EntityUid uid, SkatesComponent component, ClothingGotUnequippedEvent args) + private void OnGotUnequipped(Entity 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); } /// /// When item is equipped into the shoe slot, friction, acceleration and collide on impact are adjusted. /// - private void OnGotEquipped(EntityUid uid, SkatesComponent component, ClothingGotEquippedEvent args) + private void OnGotEquipped(Entity 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 ent, + ref InventoryRelayedEvent 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); } } diff --git a/Content.Shared/Damage/Systems/DamageOnHighSpeedImpactSystem.cs b/Content.Shared/Damage/Systems/DamageOnHighSpeedImpactSystem.cs index 6768048def..c35e95ecb8 100644 --- a/Content.Shared/Damage/Systems/DamageOnHighSpeedImpactSystem.cs +++ b/Content.Shared/Damage/Systems/DamageOnHighSpeedImpactSystem.cs @@ -33,6 +33,7 @@ public sealed class DamageOnHighSpeedImpactSystem : EntitySystem if (!EntityManager.HasComponent(uid)) return; + //TODO: This should solve after physics solves var speed = args.OurBody.LinearVelocity.Length(); if (speed < component.MinimumSpeed) diff --git a/Content.Shared/Friction/TileFrictionController.cs b/Content.Shared/Friction/TileFrictionController.cs index eb109caa42..1ebf2478f9 100644 --- a/Content.Shared/Friction/TileFrictionController.cs +++ b/Content.Shared/Friction/TileFrictionController.cs @@ -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 _frictionQuery; @@ -30,16 +31,19 @@ namespace Content.Shared.Friction private EntityQuery _pullableQuery; private EntityQuery _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(); _xformQuery = GetEntityQuery(); _pullerQuery = GetEntityQuery(); @@ -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(xform.GridUid) && (!TryComp(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) diff --git a/Content.Shared/Inventory/InventorySystem.Relay.cs b/Content.Shared/Inventory/InventorySystem.Relay.cs index 68d067efc9..1ab9478a8d 100644 --- a/Content.Shared/Inventory/InventorySystem.Relay.cs +++ b/Content.Shared/Inventory/InventorySystem.Relay.cs @@ -50,6 +50,7 @@ public partial class InventorySystem SubscribeLocalEvent(RelayInventoryEvent); // by-ref events + SubscribeLocalEvent(RefRelayInventoryEvent); SubscribeLocalEvent(RefRelayInventoryEvent); SubscribeLocalEvent(RefRelayInventoryEvent); SubscribeLocalEvent(RefRelayInventoryEvent); diff --git a/Content.Shared/Maps/ContentTileDefinition.cs b/Content.Shared/Maps/ContentTileDefinition.cs index de5d684c25..f1b67fc6c8 100644 --- a/Content.Shared/Maps/ContentTileDefinition.cs +++ b/Content.Shared/Maps/ContentTileDefinition.cs @@ -62,7 +62,10 @@ namespace Content.Shared.Maps /// [DataField("barestepSounds")] public SoundSpecifier? BarestepSounds { get; private set; } = new SoundCollectionSpecifier("BarestepHard"); - [DataField("friction")] public float Friction { get; set; } = 0.2f; + /// + /// Base friction modifier for this tile. + /// + [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; } - /// - /// No-input friction override for mob mover in - /// - [DataField("mobFrictionNoInput")] - public float? MobFrictionNoInput { get; private set; } - /// /// Accel override for mob mover in /// diff --git a/Content.Shared/Movement/Components/FrictionContactsComponent.cs b/Content.Shared/Movement/Components/FrictionContactsComponent.cs index 693ada49f7..204e35a348 100644 --- a/Content.Shared/Movement/Components/FrictionContactsComponent.cs +++ b/Content.Shared/Movement/Components/FrictionContactsComponent.cs @@ -13,19 +13,19 @@ public sealed partial class FrictionContactsComponent : Component /// [DataField, ViewVariables(VVAccess.ReadWrite)] [AutoNetworkedField] - public float MobFriction = 0.5f; + public float MobFriction = 0.05f; /// /// Modified mob friction without input while on FrictionContactsComponent /// [AutoNetworkedField] [DataField, ViewVariables(VVAccess.ReadWrite)] - public float MobFrictionNoInput = 0.05f; + public float? MobFrictionNoInput = 0.05f; /// /// Modified mob acceleration while on FrictionContactsComponent /// [AutoNetworkedField] [DataField, ViewVariables(VVAccess.ReadWrite)] - public float MobAcceleration = 2.0f; + public float MobAcceleration = 0.1f; } diff --git a/Content.Shared/Movement/Components/InputMoverComponent.cs b/Content.Shared/Movement/Components/InputMoverComponent.cs index 7c3b8b431a..f03619ccc6 100644 --- a/Content.Shared/Movement/Components/InputMoverComponent.cs +++ b/Content.Shared/Movement/Components/InputMoverComponent.cs @@ -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) - /// - /// Should our velocity be applied to our parent? - /// - [DataField] - public bool ToParent = false; - public GameTick LastInputTick; public ushort LastInputSubTick; diff --git a/Content.Shared/Movement/Components/JetpackComponent.cs b/Content.Shared/Movement/Components/JetpackComponent.cs index 336f4a353c..a84de7f196 100644 --- a/Content.Shared/Movement/Components/JetpackComponent.cs +++ b/Content.Shared/Movement/Components/JetpackComponent.cs @@ -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; diff --git a/Content.Shared/Movement/Components/JetpackUserComponent.cs b/Content.Shared/Movement/Components/JetpackUserComponent.cs index 972f8c69c6..ac8f3e78b7 100644 --- a/Content.Shared/Movement/Components/JetpackUserComponent.cs +++ b/Content.Shared/Movement/Components/JetpackUserComponent.cs @@ -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; } diff --git a/Content.Shared/Movement/Components/SpeedModifiedByContactComponent.cs b/Content.Shared/Movement/Components/MovementModifiedByContactComponent.cs similarity index 72% rename from Content.Shared/Movement/Components/SpeedModifiedByContactComponent.cs rename to Content.Shared/Movement/Components/MovementModifiedByContactComponent.cs index 4f791e13ca..1a809ecd2a 100644 --- a/Content.Shared/Movement/Components/SpeedModifiedByContactComponent.cs +++ b/Content.Shared/Movement/Components/MovementModifiedByContactComponent.cs @@ -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 +{ +} diff --git a/Content.Shared/Movement/Components/MovementRelayTargetComponent.cs b/Content.Shared/Movement/Components/MovementRelayTargetComponent.cs index dbe5d23f1c..d6093203a2 100644 --- a/Content.Shared/Movement/Components/MovementRelayTargetComponent.cs +++ b/Content.Shared/Movement/Components/MovementRelayTargetComponent.cs @@ -10,6 +10,6 @@ public sealed partial class MovementRelayTargetComponent : Component /// /// The entity that is relaying to this entity. /// - [ViewVariables, AutoNetworkedField] + [DataField, AutoNetworkedField] public EntityUid Source; } diff --git a/Content.Shared/Movement/Components/MovementSpeedModifierComponent.cs b/Content.Shared/Movement/Components/MovementSpeedModifierComponent.cs index 6a39aa44b6..f7334dfb4d 100644 --- a/Content.Shared/Movement/Components/MovementSpeedModifierComponent.cs +++ b/Content.Shared/Movement/Components/MovementSpeedModifierComponent.cs @@ -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 + + /// + /// These base values should be defined in yaml and rarely if ever modified directly. + /// + [DataField, AutoNetworkedField] + public float BaseWalkSpeed = DefaultBaseWalkSpeed; + + [DataField, AutoNetworkedField] + public float BaseSprintSpeed = DefaultBaseSprintSpeed; + + /// + /// The acceleration applied to mobs when moving. If this is ever less than Friction the mob will be slower. + /// + [AutoNetworkedField, DataField] + public float BaseAcceleration = DefaultAcceleration; + + /// + /// The body's base friction modifier that is applied in *all* circumstances. + /// + [AutoNetworkedField, DataField] + public float BaseFriction = DefaultFriction; /// /// Minimum speed a mob has to be moving before applying movement friction. /// - [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; + /// - /// 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. /// - [ViewVariables(VVAccess.ReadWrite), DataField] - public float WeightlessFriction = DefaultWeightlessFriction; + [AutoNetworkedField, DataField] + public float Acceleration; /// - /// 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. /// - [ViewVariables(VVAccess.ReadWrite), DataField] - public float WeightlessFrictionNoInput = DefaultWeightlessFrictionNoInput; + [AutoNetworkedField, DataField] + public float Friction; /// - /// The negative velocity applied for friction when weightless and not standing on a grid or mapgrid + /// The negative velocity applied for friction. /// - [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 /// - /// 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. /// - [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; /// /// The acceleration applied to mobs when moving and weightless. /// - [ViewVariables(VVAccess.ReadWrite), DataField] - public float WeightlessAcceleration = DefaultWeightlessAcceleration; + [AutoNetworkedField, DataField] + public float WeightlessAcceleration; /// - /// The acceleration applied to mobs when moving. + /// The movement speed modifier applied to a mob's total input velocity when weightless. /// - [AutoNetworkedField, ViewVariables(VVAccess.ReadWrite), DataField] - public float Acceleration = DefaultAcceleration; + [AutoNetworkedField, DataField] + public float WeightlessModifier; /// - /// The negative velocity applied for friction. + /// The negative velocity applied for friction when weightless and providing inputs. /// - [AutoNetworkedField, ViewVariables(VVAccess.ReadWrite), DataField] - public float Friction = DefaultFriction; + [AutoNetworkedField, DataField] + public float WeightlessFriction; /// - /// The negative velocity applied for friction. + /// The negative velocity applied for friction when weightless and not providing inputs. /// - [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; + /// + /// The negative velocity applied for friction when weightless and not standing on a grid or mapgrid + /// + [AutoNetworkedField, DataField] + public float? OffGridFriction; - [ViewVariables] - public float CurrentWalkSpeed => WalkSpeedModifier * BaseWalkSpeed; - [ViewVariables] - public float CurrentSprintSpeed => SprintSpeedModifier * BaseSprintSpeed; + #endregion } } diff --git a/Content.Shared/Movement/Components/RelayInputMoverComponent.cs b/Content.Shared/Movement/Components/RelayInputMoverComponent.cs index c783165cbe..24fedf6d6f 100644 --- a/Content.Shared/Movement/Components/RelayInputMoverComponent.cs +++ b/Content.Shared/Movement/Components/RelayInputMoverComponent.cs @@ -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; } diff --git a/Content.Shared/Movement/Systems/FrictionContactsSystem.cs b/Content.Shared/Movement/Systems/FrictionContactsSystem.cs index b086bc0e05..4dd679c37c 100644 --- a/Content.Shared/Movement/Systems/FrictionContactsSystem.cs +++ b/Content.Shared/Movement/Systems/FrictionContactsSystem.cs @@ -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 _toUpdate = new(); + private readonly HashSet _toUpdate = new(); + private readonly HashSet _toRemove = new(); public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnEntityEnter); SubscribeLocalEvent(OnEntityExit); + SubscribeLocalEvent(OnRefreshFrictionModifiers); SubscribeLocalEvent(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(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 entity, ref RefreshFrictionModifiersEvent args) { - if (!EntityManager.TryGetComponent(uid, out var physicsComponent)) - return; - - if (!TryComp(uid, out MovementSpeedModifierComponent? speedModifier)) + if (!EntityManager.TryGetComponent(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(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(uid)) + return; - return null; + EnsureComp(uid); + _toUpdate.Add(uid); } } diff --git a/Content.Shared/Movement/Systems/MovementSpeedModifierSystem.cs b/Content.Shared/Movement/Systems/MovementSpeedModifierSystem.cs index 8e89e4b62b..b5bb633491 100644 --- a/Content.Shared/Movement/Systems/MovementSpeedModifierSystem.cs +++ b/Content.Shared/Movement/Systems/MovementSpeedModifierSystem.cs @@ -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(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 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; + } } diff --git a/Content.Shared/Movement/Systems/SharedJetpackSystem.cs b/Content.Shared/Movement/Systems/SharedJetpackSystem.cs index 548594c01f..72f321d8f5 100644 --- a/Content.Shared/Movement/Systems/SharedJetpackSystem.cs +++ b/Content.Shared/Movement/Systems/SharedJetpackSystem.cs @@ -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(OnJetpackGetAction); SubscribeLocalEvent(OnJetpackDropped); SubscribeLocalEvent(OnJetpackToggle); - SubscribeLocalEvent(OnJetpackCanWeightlessMove); + SubscribeLocalEvent(OnJetpackUserWeightlessMovement); SubscribeLocalEvent(OnJetpackUserCanWeightless); SubscribeLocalEvent(OnJetpackUserEntParentChanged); + SubscribeLocalEvent(OnJetpackMoved); SubscribeLocalEvent(OnJetpackUserGravityChanged); SubscribeLocalEvent(OnMapInit); } - private void OnMapInit(EntityUid uid, JetpackComponent component, MapInitEvent args) + private void OnJetpackUserWeightlessMovement(Entity 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 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(user); - _mover.SetRelay(user, jetpackUid); + EnsureComp(user, out var userComp); + component.JetpackUser = user; if (TryComp(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(uid)) return; + component.JetpackUser = null; + if (TryComp(uid, out var physics)) _physics.SetBodyStatus(uid, physics, BodyStatus.OnGround); - RemComp(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(uid); } else { + RemoveUser(user.Value, component); RemComp(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); diff --git a/Content.Shared/Movement/Systems/SharedMoverController.Input.cs b/Content.Shared/Movement/Systems/SharedMoverController.Input.cs index 87849e8f12..464c4c3758 100644 --- a/Content.Shared/Movement/Systems/SharedMoverController.Input.cs +++ b/Content.Shared/Movement/Systems/SharedMoverController.Input.cs @@ -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(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(relative) && HasComp(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(relative) && HasComp(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(entity, out var relayMover)) { DebugTools.Assert(relayMover.RelayEntity != entity); diff --git a/Content.Shared/Movement/Systems/SharedMoverController.cs b/Content.Shared/Movement/Systems/SharedMoverController.cs index a98fc633d0..317bfdabbe 100644 --- a/Content.Shared/Movement/Systems/SharedMoverController.cs +++ b/Content.Shared/Movement/Systems/SharedMoverController.cs @@ -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 FootstepSoundTag = "FootstepSound"; - /// - /// - /// - private float _stopSpeed; - private bool _relativeMovement; + private float _minDamping; + private float _airDamping; + private float _offGridDamping; /// /// Cache the mob movement calculation to re-use elsewhere. @@ -90,10 +90,14 @@ public abstract partial class SharedMoverController : VirtualController FootstepModifierQuery = GetEntityQuery(); MapGridQuery = GetEntityQuery(); + SubscribeLocalEvent(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. /// protected void HandleMobMovement( - EntityUid uid, - InputMoverComponent mover, - EntityUid physicsUid, - PhysicsComponent physicsComponent, - TransformComponent xform, + Entity 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(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(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; } /// @@ -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 ent, ref TileFrictionEvent args) + { + if (!TryComp(ent, out var physicsComponent) || !XformQuery.TryComp(ent, out var xform)) + return; + + // TODO: Make IsWeightless event based!!! + if (physicsComponent.BodyStatus != BodyStatus.OnGround || _gravity.IsWeightless(ent, physicsComponent, xform)) + args.Modifier *= ent.Comp.BaseWeightlessFriction; + else + args.Modifier *= ent.Comp.BaseFriction; + } } diff --git a/Content.Shared/Movement/Systems/SpeedModifierContactsSystem.cs b/Content.Shared/Movement/Systems/SpeedModifierContactsSystem.cs index e53a4351d1..9c96a2b7dc 100644 --- a/Content.Shared/Movement/Systems/SpeedModifierContactsSystem.cs +++ b/Content.Shared/Movement/Systems/SpeedModifierContactsSystem.cs @@ -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 _toUpdate = new(); - private HashSet _toRemove = new(); + private readonly HashSet _toUpdate = new(); + private readonly HashSet _toRemove = new(); public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnEntityEnter); SubscribeLocalEvent(OnEntityExit); - SubscribeLocalEvent(MovementSpeedCheck); + SubscribeLocalEvent(OnRefreshMovementSpeedModifiers); SubscribeLocalEvent(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(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(ent, out var slipperyComponent) && speedModified == false) + if (HasComp(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; diff --git a/Content.Shared/Slippery/SlipperySystem.cs b/Content.Shared/Slippery/SlipperySystem.cs index 4da347acbd..bedf05536b 100644 --- a/Content.Shared/Slippery/SlipperySystem.cs +++ b/Content.Shared/Slippery/SlipperySystem.cs @@ -119,6 +119,7 @@ public sealed class SlipperySystem : EntitySystem { var sliding = EnsureComp(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)); } } diff --git a/Content.Shared/Stunnable/SharedStunSystem.cs b/Content.Shared/Stunnable/SharedStunSystem.cs index 17115d4aa6..1866ad39ef 100644 --- a/Content.Shared/Stunnable/SharedStunSystem.cs +++ b/Content.Shared/Stunnable/SharedStunSystem.cs @@ -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. /// - public const float KnockDownModifier = 0.4f; + public const float KnockDownModifier = 0.2f; public override void Initialize() { diff --git a/Content.Shared/Throwing/ThrowingSystem.cs b/Content.Shared/Throwing/ThrowingSystem.cs index c37f235698..567c969b0f 100644 --- a/Content.Shared/Throwing/ThrowingSystem.cs +++ b/Content.Shared/Throwing/ThrowingSystem.cs @@ -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); diff --git a/Resources/Prototypes/Entities/Effects/puddle.yml b/Resources/Prototypes/Entities/Effects/puddle.yml index f89e850193..49962ec189 100644 --- a/Resources/Prototypes/Entities/Effects/puddle.yml +++ b/Resources/Prototypes/Entities/Effects/puddle.yml @@ -113,8 +113,6 @@ components: - type: Clickable - type: Slippery - - type: TileFrictionModifier - modifier: 0.3 - type: Transform noRot: true anchored: true diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml index 22f818d56b..23b855c18f 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/animals.yml @@ -517,7 +517,7 @@ baseSprintSpeed : 4 weightlessAcceleration: 1.5 weightlessFriction: 1 - weightlessModifier: 1 + baseWeightlessModifier: 1 - type: Damageable damageContainer: Biological damageModifierSet: Moth diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/behonker.yml b/Resources/Prototypes/Entities/Mobs/NPCs/behonker.yml index 6a20a50315..8229b103e7 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/behonker.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/behonker.yml @@ -49,7 +49,7 @@ - type: MovementSpeedModifier baseWalkSpeed: 3 baseSprintSpeed: 5 - weightlessModifier: 1.5 + baseWeightlessModifier: 1.5 - type: Sprite sprite: Mobs/Demons/behonker.rsi layers: diff --git a/Resources/Prototypes/Entities/Mobs/NPCs/flying_animals.yml b/Resources/Prototypes/Entities/Mobs/NPCs/flying_animals.yml index c6846b8ef4..0347d394b7 100644 --- a/Resources/Prototypes/Entities/Mobs/NPCs/flying_animals.yml +++ b/Resources/Prototypes/Entities/Mobs/NPCs/flying_animals.yml @@ -9,3 +9,4 @@ - type: NoSlip - type: MovementAlwaysTouching - type: CanMoveInAir + - type: MovementSpeedModifier diff --git a/Resources/Prototypes/Entities/Mobs/Player/dragon.yml b/Resources/Prototypes/Entities/Mobs/Player/dragon.yml index 2721c6dfd1..0eafd75754 100644 --- a/Resources/Prototypes/Entities/Mobs/Player/dragon.yml +++ b/Resources/Prototypes/Entities/Mobs/Player/dragon.yml @@ -41,7 +41,7 @@ - type: MovementSpeedModifier baseWalkSpeed: 3 baseSprintSpeed: 5 - weightlessModifier: 1.5 + baseWeightlessModifier: 1.5 - type: RandomSprite available: - enum.DamageStateVisualLayers.Base: diff --git a/Resources/Prototypes/Entities/Mobs/Species/moth.yml b/Resources/Prototypes/Entities/Mobs/Species/moth.yml index 59e3d6a7a5..2bdd103dd9 100644 --- a/Resources/Prototypes/Entities/Mobs/Species/moth.yml +++ b/Resources/Prototypes/Entities/Mobs/Species/moth.yml @@ -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: @@ -118,7 +118,7 @@ state: "creampie_moth" visible: false - type: Inventory - speciesId: moth + speciesId: moth femaleDisplacements: jumpsuit: sizeMaps: @@ -135,7 +135,7 @@ - type: HumanoidAppearance species: Moth - type: Inventory - speciesId: moth + speciesId: moth femaleDisplacements: jumpsuit: sizeMaps: diff --git a/Resources/Prototypes/Entities/Objects/Fun/immovable_rod.yml b/Resources/Prototypes/Entities/Objects/Fun/immovable_rod.yml index 324502c3d1..9a7a1ab4fb 100644 --- a/Resources/Prototypes/Entities/Objects/Fun/immovable_rod.yml +++ b/Resources/Prototypes/Entities/Objects/Fun/immovable_rod.yml @@ -14,6 +14,8 @@ - type: Physics bodyType: Dynamic linearDamping: 0 + - type: TileFrictionModifier + modifier: 0 - type: PointLight radius: 3 color: red @@ -80,11 +82,11 @@ 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 diff --git a/Resources/Prototypes/Entities/Objects/Misc/ice_crust.yml b/Resources/Prototypes/Entities/Objects/Misc/ice_crust.yml index 43c4568e70..8262aab461 100644 --- a/Resources/Prototypes/Entities/Objects/Misc/ice_crust.yml +++ b/Resources/Prototypes/Entities/Objects/Misc/ice_crust.yml @@ -50,3 +50,5 @@ coldDamage: {} coldDamageThreshold: 0 - type: FrictionContacts + - type: TileFrictionModifier + modifier: 0.05 diff --git a/Resources/Prototypes/Entities/Objects/Tools/jetpacks.yml b/Resources/Prototypes/Entities/Objects/Tools/jetpacks.yml index abbf68a27e..a9dde098f9 100644 --- a/Resources/Prototypes/Entities/Objects/Tools/jetpacks.yml +++ b/Resources/Prototypes/Entities/Objects/Tools/jetpacks.yml @@ -25,13 +25,6 @@ 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 diff --git a/Resources/Prototypes/Tiles/floors.yml b/Resources/Prototypes/Tiles/floors.yml index 1431fab3c0..39c109207d 100644 --- a/Resources/Prototypes/Tiles/floors.yml +++ b/Resources/Prototypes/Tiles/floors.yml @@ -1059,7 +1059,7 @@ collection: FootstepCarpet barestepSounds: collection: BarestepCarpet - friction: 0.25 + friction: 1.25 itemDrop: FloorTileItemArcadeBlue heatCapacity: 10000 @@ -1074,7 +1074,7 @@ collection: FootstepCarpet barestepSounds: collection: BarestepCarpet - friction: 0.25 + friction: 1.25 itemDrop: FloorTileItemArcadeBlue2 heatCapacity: 10000 @@ -1089,7 +1089,7 @@ collection: FootstepCarpet barestepSounds: collection: BarestepCarpet - friction: 0.25 + friction: 1.25 itemDrop: FloorTileItemArcadeRed heatCapacity: 10000 @@ -1104,7 +1104,7 @@ collection: FootstepCarpet barestepSounds: collection: BarestepCarpet - friction: 0.25 + friction: 1.25 itemDrop: FloorTileItemEighties heatCapacity: 10000 @@ -1119,7 +1119,7 @@ collection: FootstepCarpet barestepSounds: collection: BarestepCarpet - friction: 0.25 + friction: 1.25 itemDrop: FloorTileItemCarpetClown heatCapacity: 10000 @@ -1134,7 +1134,7 @@ collection: FootstepCarpet barestepSounds: collection: BarestepCarpet - friction: 0.25 + friction: 1.25 itemDrop: FloorTileItemCarpetOffice heatCapacity: 10000 @@ -1153,7 +1153,7 @@ deconstructTools: [ Prying ] footstepSounds: collection: FootstepFloor - friction: 0.25 + friction: 1.25 itemDrop: FloorTileItemBoxing heatCapacity: 10000 @@ -1172,7 +1172,7 @@ deconstructTools: [ Prying ] footstepSounds: collection: FootstepFloor - friction: 0.25 + friction: 1.25 itemDrop: FloorTileItemGym heatCapacity: 10000 @@ -1789,7 +1789,7 @@ footstepSounds: collection: FootstepBlood itemDrop: FloorTileItemFlesh - friction: 0.05 #slippy + friction: 0.25 #slippy heatCapacity: 10000 - type: tile @@ -1991,9 +1991,8 @@ 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 diff --git a/Resources/Prototypes/Tiles/planet.yml b/Resources/Prototypes/Tiles/planet.yml index ccb3fb61da..f478f4e5a4 100644 --- a/Resources/Prototypes/Tiles/planet.yml +++ b/Resources/Prototypes/Tiles/planet.yml @@ -138,9 +138,7 @@ friction: 0.05 heatCapacity: 10000 weather: true - mobFriction: 0.5 - mobFrictionNoInput: 0.05 - mobAcceleration: 2 + mobAcceleration: 0.1 indestructible: true # Dug snow diff --git a/Resources/Prototypes/Tiles/plating.yml b/Resources/Prototypes/Tiles/plating.yml index 1f6579c47e..e3f4c89d94 100644 --- a/Resources/Prototypes/Tiles/plating.yml +++ b/Resources/Prototypes/Tiles/plating.yml @@ -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