{"TotalCount":19,"Files":[{"Ident":"fish.scc","Path":"code/Examples/ShrimpleFlyer.cs","FileName":"ShrimpleFlyer.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":301969,"IsPrivate":false,"Code":"using Sandbox.Citizen;\r\n\r\nnamespace ShrimpleCharacterController;\r\n\r\n[Hide]\r\npublic sealed class ShrimpleFlyer : Component\r\n{\r\n    [RequireComponent]\r\n    public ShrimpleCharacterController Controller { get; set; }\r\n    public GameObject Camera { get; set; }\r\n\r\n    [Property]\r\n    [Range(400f, 1600f)]\r\n    public float WalkSpeed { get; set; } = 800f;\r\n\r\n    [Property]\r\n    [Range(800f, 4000f)]\r\n    public float RunSpeed { get; set; } = 2400f;\r\n\r\n    public Angles EyeAngles { get; set; }\r\n\r\n    protected override void OnStart()\r\n    {\r\n        base.OnStart();\r\n\r\n        Camera = new GameObject(true, \u0022Camera\u0022);\r\n        Camera.SetParent(GameObject);\r\n        var cameraComponent = Camera.Components.Create\u003CCameraComponent\u003E();\r\n        cameraComponent.ZFar = 32768f;\r\n    }\r\n\r\n    protected override void OnFixedUpdate()\r\n    {\r\n        base.OnFixedUpdate();\r\n\r\n        var isDucking = Input.Down(\u0022Duck\u0022);\r\n        var isRunning = Input.Down(\u0022Run\u0022);\r\n        var ascending = Input.Down(\u0022Jump\u0022) ? 1f : 0f;\r\n        var descending = Input.Down(\u0022Duck\u0022) ? -1f : 0f;\r\n        var wishSpeed = isRunning ? RunSpeed : WalkSpeed;\r\n        var wishDirection = (Input.AnalogMove \u002B Vector3.Up * (ascending \u002B descending)).Normal * EyeAngles.ToRotation();\r\n\r\n        Controller.WishVelocity = wishDirection * wishSpeed;\r\n        Controller.Move();\r\n    }\r\n\r\n    protected override void OnUpdate()\r\n    {\r\n        base.OnUpdate();\r\n\r\n        EyeAngles \u002B= Input.AnalogLook;\r\n        EyeAngles = EyeAngles.WithPitch(MathX.Clamp(EyeAngles.pitch, -40f, 40f));\r\n\r\n        var cameraOffset = Vector3.Up * 70f \u002B Vector3.Backward * 760f;\r\n        Camera.WorldRotation = EyeAngles.ToRotation();\r\n        Camera.LocalPosition = cameraOffset * Camera.WorldRotation;\r\n    }\r\n}\r\n"},{"Ident":"fish.scc","Path":"code/ShrimpleCollisionResult.cs","FileName":"ShrimpleCollisionResult.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":301969,"IsPrivate":false,"Code":"namespace ShrimpleCharacterController;\r\n\r\npublic struct ShrimpleCollisionResult\r\n{\r\n    public Vector3 HitPosition;\r\n    public Vector3 HitNormal;\r\n    public Vector3 HitVelocityBefore;\r\n    public Vector3 HitVelocityAfter;\r\n    public float Angle;\r\n    public GameObject HitObject;\r\n    public Surface HitSurface;\r\n\r\n    public ShrimpleCollisionResult() { }\r\n\r\n    public ShrimpleCollisionResult(Vector3 hitPosition, Vector3 hitNormal, Vector3 hitVelocityBefore, Vector3 hitVelocityAfter, float angle, GameObject hitObject, Surface hitSurface)\r\n    {\r\n        HitPosition = hitPosition;\r\n        HitNormal = hitNormal;\r\n        HitVelocityBefore = hitVelocityBefore;\r\n        HitVelocityAfter = hitVelocityAfter;\r\n        Angle = angle;\r\n        HitObject = hitObject;\r\n        HitSurface = hitSurface;\r\n    }\r\n\r\n    public ShrimpleCollisionResult WithHitPosition(Vector3 hitPosition)\r\n    {\r\n        var result = this;\r\n        result.HitPosition = hitPosition;\r\n        return result;\r\n    }\r\n\r\n    public ShrimpleCollisionResult WithHitNormal(Vector3 hitNormal)\r\n    {\r\n        var result = this;\r\n        result.HitNormal = hitNormal;\r\n        return result;\r\n    }\r\n\r\n    public ShrimpleCollisionResult WithHitVelocityBefore(Vector3 hitVelocityBefore)\r\n    {\r\n        var result = this;\r\n        result.HitVelocityBefore = hitVelocityBefore;\r\n        return result;\r\n    }\r\n\r\n    public ShrimpleCollisionResult WithHitVelocityAfter(Vector3 hitVelocityAfter)\r\n    {\r\n        var result = this;\r\n        result.HitVelocityAfter = hitVelocityAfter;\r\n        return result;\r\n    }\r\n\r\n    public ShrimpleCollisionResult WithAngle(float angle)\r\n    {\r\n        var result = this;\r\n        result.Angle = angle;\r\n        return result;\r\n    }\r\n\r\n    public ShrimpleCollisionResult WithHitObject(GameObject hitObject)\r\n    {\r\n        var result = this;\r\n        result.HitObject = hitObject;\r\n        return result;\r\n    }\r\n\r\n    public ShrimpleCollisionResult WithHitSurface(Surface hitSurface)\r\n    {\r\n        var result = this;\r\n        result.HitSurface = hitSurface;\r\n        return result;\r\n    }\r\n}"},{"Ident":"fish.scc","Path":"code/ShrimpleCharacterController.Physical.cs","FileName":"ShrimpleCharacterController.Physical.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":301969,"IsPrivate":false,"Code":"namespace ShrimpleCharacterController;\r\n\r\npublic partial class ShrimpleCharacterController\r\n{\r\n    bool _didStep;\r\n    Vector3 _stepPosition;\r\n\r\n    // Set by Move() when ManuallyUpdate is enabled in physical mode, consumed by the next physics step\r\n    bool _physicalMovePending;\r\n    float _physicalMoveDelta;\r\n\r\n    // The delta this physics step should simulate (the manual Move() delta, otherwise the fixed step)\r\n    float ActiveDelta =\u003E ( ManuallyUpdate \u0026\u0026 _physicalMovePending ) ? _physicalMoveDelta : ActiveAutoTimeDelta;\r\n\r\n    // The solver always integrates over Time.Delta, so we scale velocity to cover ActiveDelta worth of distance\r\n    float MoveScale =\u003E Time.Delta \u003E 0f ? ActiveDelta / Time.Delta : 1f;\r\n\r\n    void IScenePhysicsEvents.PrePhysicsStep()\r\n    {\r\n        if ( !PhysicallySimulated ) return;\r\n        if ( !Body.IsValid() ) return;\r\n        if ( ManuallyUpdate \u0026\u0026 !_physicalMovePending ) return; // Wait for a manual Move() call to drive us\r\n\r\n        _didStep = false;\r\n\r\n        UpdateBodyPhysics();\r\n        UpdateMassCenter();\r\n        UpdateMovement();\r\n    }\r\n\r\n    void IScenePhysicsEvents.PostPhysicsStep()\r\n    {\r\n        if ( !Body.IsValid() )\r\n        {\r\n            WorldPosition \u002B= GroundVelocity * Time.Delta;\r\n            return;\r\n        }\r\n\r\n        if ( !PhysicallySimulated ) return;\r\n        if ( ManuallyUpdate \u0026\u0026 !_physicalMovePending ) return;\r\n\r\n        if ( _didStep )\r\n            Body.WorldPosition = _stepPosition;\r\n\r\n        CategorizePhysicalGround();\r\n        ApplyGroundVelocity();\r\n        Velocity = Body.Velocity / MoveScale; // De-scale back to logical units/second\r\n\r\n        _physicalMovePending = false;\r\n    }\r\n\r\n    private void UpdateMassCenter()\r\n    {\r\n        if ( !Body.IsValid() ) return;\r\n\r\n        var wishSpeed = WishVelocity.WithZ( 0 ).Length;\r\n        var halfHeight = AppliedHeight * 0.5f;\r\n        float massCenter = IsOnGround ? wishSpeed.Clamp( 0, halfHeight ) : halfHeight;\r\n\r\n        Body.MassCenterOverride = new Vector3( 0, 0, massCenter );\r\n        Body.OverrideMassCenter = true;\r\n    }\r\n\r\n    private void UpdateBodyPhysics()\r\n    {\r\n        if ( !Collider.IsValid() || !Body.IsValid() ) return;\r\n\r\n        var onDynamic = false;\r\n        if ( IsOnGround \u0026\u0026 GroundObject.IsValid() )\r\n        {\r\n            var groundBody = GroundObject.GetComponent\u003CRigidbody\u003E();\r\n            onDynamic = groundBody != null \u0026\u0026 groundBody.PhysicsBody.BodyType == PhysicsBodyType.Dynamic;\r\n        }\r\n\r\n        Body.Gravity = false;\r\n\r\n        var velocityHorizontal = Body.Velocity.WithZ( 0 ).Length;\r\n        bool wantsBrakes = IsOnGround \u0026\u0026 WishVelocity.Length \u003C 1f \u0026\u0026 velocityHorizontal \u003C 10f;\r\n        Body.LinearDamping = wantsBrakes ? 10f : 0.1f;\r\n        Body.AngularDamping = 1f;\r\n\r\n        float friction = 0f;\r\n        if ( IsOnGround \u0026\u0026 WishVelocity.Length \u003C 0.1f )\r\n            friction = onDynamic ? 0.5f : 1f;\r\n        Collider.Friction = friction;\r\n    }\r\n\r\n    private void AddWishVelocity()\r\n    {\r\n        var z = Body.Velocity.z;\r\n        var velocity = CalculateGoalVelocity( ActiveDelta );\r\n\r\n        if ( IsOnGround \u0026\u0026 GroundStickEnabled \u0026\u0026 !GroundNormal.IsNearlyZero( 0.01f ) )\r\n        {\r\n            if ( MathF.Abs( GroundNormal.z ) \u003E 0.01f )\r\n                velocity = velocity.WithZ( -( GroundNormal.x * velocity.x \u002B GroundNormal.y * velocity.y ) / GroundNormal.z );\r\n\r\n            var isGoingUphill = Vector3.Dot( velocity, AppliedGravity ) \u003C 0f;\r\n            var slopeMultiplier = isGoingUphill ? GetSlopeVelocityMultiplier( GroundAngle ) : 1f;\r\n            velocity *= slopeMultiplier;\r\n        }\r\n\r\n        if ( IsOnGround \u0026\u0026 !GroundStickEnabled )\r\n            velocity.z = z;\r\n\r\n        if ( GravityEnabled \u0026\u0026 ( !IsOnGround || IsSlipping || !GroundStickEnabled ) )\r\n            velocity \u002B= AppliedGravity * ActiveDelta;\r\n\r\n        Body.Velocity = velocity * MoveScale; // Scale so the solver covers ActiveDelta worth of distance over Time.Delta\r\n    }\r\n\r\n    private void UpdateMovement()\r\n    {\r\n        if ( IsOnGround \u0026\u0026 GroundStickEnabled \u0026\u0026 !IsSlipping )\r\n            StickToGround();\r\n\r\n        AddWishVelocity();\r\n\r\n        var wishHorizontal = WishVelocity.WithZ( 0 );\r\n        var bodyHorizontal = Body.Velocity.WithZ( 0 );\r\n        var moveDir = wishHorizontal.IsNearlyZero( 0.1f ) ? bodyHorizontal : wishHorizontal;\r\n        if ( moveDir.IsNearlyZero( 0.1f ) ) return;\r\n\r\n        var speed = MathF.Max( bodyHorizontal.Length * Time.Delta, StepDepth );\r\n        var vel = moveDir.Normal * speed;\r\n        var forwardTrace = BuildTrace( _shrunkenBounds, WorldPosition \u002B _offset - vel.Normal * SkinWidth, WorldPosition \u002B _offset \u002B vel );\r\n\r\n        if ( !forwardTrace.Hit || forwardTrace.StartedSolid ) return;\r\n\r\n        var forwardAngle = Vector3.GetAngle( -AppliedGravity.Normal, forwardTrace.Normal );\r\n        if ( IsAngleStandable( forwardAngle ) \u0026\u0026 Vector3.Dot( Body.Velocity, forwardTrace.Normal ) \u003C 0f )\r\n            Body.Velocity = Vector3.VectorPlaneProject( Body.Velocity, forwardTrace.Normal );\r\n\r\n        if ( StepsEnabled \u0026\u0026 IsOnGround )\r\n            TryStepUp( forwardTrace, vel );\r\n    }\r\n\r\n    private void TryStepUp( SceneTraceResult forwardTrace, Vector3 vel )\r\n    {\r\n        if ( forwardTrace.StartedSolid || !forwardTrace.Hit ) return;\r\n\r\n        var angle = Vector3.GetAngle( -GravityNormal, forwardTrace.Normal );\r\n        if ( angle \u003C 90f - StepTolerance || angle \u003E 90f \u002B StepTolerance ) return;\r\n\r\n        var stepHorizontal = Vector3.VectorPlaneProject( vel.Normal, GravityNormal ).Normal * StepDepth;\r\n        var stepVertical = -GravityNormal * ( StepHeight \u002B SkinWidth );\r\n        var stepTrace = BuildTrace( _shrunkenBounds, forwardTrace.EndPosition \u002B stepHorizontal \u002B stepVertical, forwardTrace.EndPosition \u002B stepHorizontal );\r\n\r\n        if ( stepTrace.StartedSolid || !stepTrace.Hit ) return;\r\n        if ( !IsAngleStandable( Vector3.GetAngle( stepTrace.Normal, -GravityNormal ) ) ) return;\r\n\r\n        var newFeetPos = stepTrace.EndPosition - _offset \u002B _upAxis * SkinWidth;\r\n        if ( newFeetPos.z - WorldPosition.z \u003C 0.5f ) return;\r\n\r\n        _didStep = true;\r\n        _stepPosition = newFeetPos;\r\n        Body.WorldPosition = _stepPosition;\r\n        Body.Velocity = Body.Velocity.WithZ( 0 );\r\n    }\r\n\r\n    private void ApplyGroundVelocity()\r\n    {\r\n        if ( !IsOnGround || !GroundStickEnabled || IsSlipping ) return;\r\n\r\n        var groundVelocity = PlatformVelocity * Time.Delta \u002B SurfaceVelocity * ActiveDelta;\r\n        if ( groundVelocity.IsNearZeroLength ) return;\r\n\r\n        Body.WorldPosition \u002B= groundVelocity;\r\n    }\r\n\r\n    private void StickToGround()\r\n    {\r\n        var currentPosition = WorldPosition;\r\n        var from = currentPosition \u002B _offset - GravityNormal * StepHeight;\r\n        var to = currentPosition \u002B _offset \u002B GravityNormal * ( GroundStickDistance \u002B SkinWidth );\r\n\r\n        var trace = BuildTrace( _shrunkenBounds, from, to );\r\n\r\n        if ( trace.StartedSolid ) return;\r\n\r\n        if ( trace.Hit )\r\n        {\r\n            var surfaceAngle = Vector3.GetAngle( -GravityNormal, trace.Normal );\r\n            if ( !IsAngleStandable( surfaceAngle ) ) return;\r\n\r\n            var targetFeetPos = trace.EndPosition - _offset \u002B _upAxis * SkinWidth;\r\n\r\n            Body.WorldPosition = targetFeetPos;\r\n            Body.Sleeping = false;\r\n\r\n            if ( Body.Velocity.z \u003E 0f )\r\n                Body.Velocity = Body.Velocity.WithZ( 0 );\r\n\r\n            IsOnGround = true;\r\n            GroundNormal = trace.Normal;\r\n            GroundSurface = trace.Surface;\r\n            GroundObject = trace.GameObject;\r\n            IsSlipping = false;\r\n        }\r\n    }\r\n\r\n    private void CategorizePhysicalGround()\r\n    {\r\n        var position = WorldPosition \u002B _offset;\r\n        var shortCylinderOffset = AppliedWidth / 2f;\r\n        var shortCylinderBounds = new BBox( _shrunkenBounds.Mins, new Vector3( _shrunkenBounds.Maxs.x, _shrunkenBounds.Maxs.y, _shrunkenBounds.Maxs.z - shortCylinderOffset ) );\r\n        var groundTrace = BuildTrace( shortCylinderBounds, position \u002B GravityNormal * shortCylinderOffset / 2f, position \u002B GravityNormal * ( GroundStickDistance \u002B SkinWidth ) );\r\n\r\n        if ( groundTrace.StartedSolid )\r\n        {\r\n            var stuckCheck = BuildTrace( _shrunkenBounds.Grow( -SkinWidth ), position, position, useCapsule: true );\r\n\r\n            if ( stuckCheck.StartedSolid )\r\n            {\r\n                IsStuck = true;\r\n\r\n                if ( UnstuckEnabled \u0026\u0026 TryUnstuck( position, out var unstuckResult ) )\r\n                {\r\n                    IsStuck = false;\r\n                    Body.WorldPosition = unstuckResult - _offset;\r\n                    Body.Velocity = Vector3.Zero;\r\n                }\r\n\r\n                var fallbackTrace = BuildTrace( _shrunkenBounds, position \u002B -GravityNormal * 4f, position \u002B GravityNormal * 2f );\r\n\r\n                if ( fallbackTrace.Hit \u0026\u0026 !fallbackTrace.StartedSolid \u0026\u0026 Vector3.Dot( fallbackTrace.Normal, -GravityNormal ) \u003E 0f )\r\n                {\r\n                    var standable = IsAngleStandable( Vector3.GetAngle( Vector3.Up, fallbackTrace.Normal ) );\r\n                    IsOnGround = true;\r\n                    GroundNormal = fallbackTrace.Normal;\r\n                    GroundSurface = fallbackTrace.Surface;\r\n                    GroundObject = fallbackTrace.GameObject;\r\n                    IsSlipping = !standable;\r\n                }\r\n                return;\r\n            }\r\n\r\n            groundTrace = BuildTrace( _shrunkenBounds, position, position \u002B GravityNormal * ( GroundStickDistance \u002B SkinWidth ) );\r\n        }\r\n\r\n        IsStuck = false;\r\n\r\n        if ( groundTrace.Hit )\r\n        {\r\n            var standable = IsAngleStandable( Vector3.GetAngle( Vector3.Up, groundTrace.Normal ) );\r\n            var landingAngle = Vector3.Dot( Velocity.Normal, groundTrace.Normal );\r\n            var verticalAngle = Vector3.Dot( Velocity.Normal, GravityNormal );\r\n            var goingUp = verticalAngle \u003C 0.1f;\r\n            var hasLanded = !IsOnGround \u0026\u0026 landingAngle \u003C= ( MaxGroundAngle.Max / 180f ) \u0026\u0026 groundTrace.Distance \u003C= SkinWidth * ( goingUp ? 6f : 2f ) \u002B StepHeight;\r\n\r\n            IsOnGround = IsOnGround || hasLanded;\r\n            GroundNormal = groundTrace.Normal;\r\n            GroundSurface = groundTrace.Surface;\r\n            GroundObject = groundTrace.GameObject;\r\n            IsSlipping = IsOnGround \u0026\u0026 !standable;\r\n\r\n            if ( GroundStickEnabled \u0026\u0026 !IsSlipping \u0026\u0026 IsOnGround )\r\n            {\r\n                var targetFeetPos = groundTrace.EndPosition - _offset \u002B _upAxis * ( SkinWidth \u002B shortCylinderOffset / 2f );\r\n                var delta = WorldPosition - targetFeetPos;\r\n\r\n                if ( delta.z \u003E= -0.1f \u0026\u0026 !delta.IsNearlyZero( 0.001f ) )\r\n                {\r\n                    Body.WorldPosition = targetFeetPos;\r\n                    Body.Sleeping = false;\r\n\r\n                    if ( Body.Velocity.z \u003E 0f )\r\n                        Body.Velocity = Body.Velocity.WithZ( 0 );\r\n                }\r\n            }\r\n\r\n            return;\r\n        }\r\n\r\n        IsOnGround = false;\r\n        GroundNormal = Vector3.Zero;\r\n        GroundSurface = null;\r\n        GroundObject = null;\r\n        IsSlipping = false;\r\n    }\r\n\r\n    protected void CreateBody( bool recreate = false )\r\n    {\r\n        if ( recreate )\r\n        {\r\n            if ( Collider.IsValid() ) Collider.Destroy();\r\n            if ( Body.IsValid() ) Body.Destroy();\r\n            if ( BodyObject.IsValid() ) BodyObject.Destroy();\r\n        }\r\n\r\n        CreateCollider( recreate );\r\n        CreateRigidbody( recreate );\r\n    }\r\n\r\n    protected void DestroyBody()\r\n    {\r\n        if ( Collider.IsValid() ) Collider.Destroy();\r\n        if ( Body.IsValid() ) Body.Destroy();\r\n        if ( BodyObject.IsValid() ) BodyObject.Destroy();\r\n    }\r\n\r\n    protected void CreateCollider( bool recreate = false )\r\n    {\r\n        if ( recreate \u0026\u0026 Collider.IsValid() )\r\n            Collider.Destroy();\r\n\r\n        if ( PhysicallySimulated )\r\n        {\r\n            if ( !Collider.IsValid() || Collider is not CapsuleCollider )\r\n            {\r\n                if ( Collider.IsValid() )\r\n                    Collider.Destroy();\r\n\r\n                Collider = GameObject.GetOrAddComponent\u003CCapsuleCollider\u003E();\r\n            }\r\n        }\r\n        else\r\n        {\r\n            if ( TraceShape == TraceType.Box || TraceShape == TraceType.Bounds )\r\n            {\r\n                if ( !Collider.IsValid() || Collider is not BoxCollider )\r\n                {\r\n                    if ( Collider.IsValid() )\r\n                        Collider.Destroy();\r\n\r\n                    Collider = GameObject.GetOrAddComponent\u003CBoxCollider\u003E();\r\n                }\r\n            }\r\n            else if ( TraceShape == TraceType.Cylinder )\r\n            {\r\n                if ( !Collider.IsValid() || Collider is not HullCollider )\r\n                {\r\n                    if ( Collider.IsValid() )\r\n                        Collider.Destroy();\r\n\r\n                    Collider = GameObject.GetOrAddComponent\u003CHullCollider\u003E();\r\n                }\r\n            }\r\n            else if ( TraceShape == TraceType.Sphere )\r\n            {\r\n                if ( !Collider.IsValid() || Collider is not SphereCollider )\r\n                {\r\n                    if ( Collider.IsValid() )\r\n                        Collider.Destroy();\r\n\r\n                    Collider = GameObject.GetOrAddComponent\u003CSphereCollider\u003E();\r\n                }\r\n            }\r\n        }\r\n\r\n        if ( HidePhysicalComponents )\r\n            Collider.Flags |= ComponentFlags.Hidden;\r\n        else\r\n            Collider.Flags \u0026= ~ComponentFlags.Hidden;\r\n\r\n        UpdateCollider();\r\n    }\r\n\r\n    protected void UpdateCollider()\r\n    {\r\n        if ( !Collider.IsValid() ) return;\r\n\r\n        if ( Collider is CapsuleCollider capsuleCollider )\r\n        {\r\n            var radius = ( TraceWidth - SkinWidth ) / 2f;\r\n            var height = TraceHeight - SkinWidth;\r\n            capsuleCollider.Radius = radius;\r\n            capsuleCollider.Start = Vector3.Up * radius;\r\n            capsuleCollider.End = Vector3.Up * ( height - radius );\r\n        }\r\n        else if ( Collider is BoxCollider boxCollider )\r\n        {\r\n            var bounds = BuildBounds();\r\n            boxCollider.Scale = bounds.Size - SkinWidth;\r\n            boxCollider.Center = Vector3.Up * ( TraceHeight / 2f );\r\n        }\r\n        else if ( Collider is HullCollider hullCollider )\r\n        {\r\n            hullCollider.Type = HullCollider.PrimitiveType.Cylinder;\r\n            hullCollider.Height = TraceHeight - SkinWidth;\r\n            hullCollider.Radius = ( TraceWidth - SkinWidth ) / 2f;\r\n            hullCollider.Center = Vector3.Up * ( TraceHeight / 2f );\r\n        }\r\n        else if ( Collider is SphereCollider sphereCollider )\r\n        {\r\n            sphereCollider.Radius = ( TraceWidth - SkinWidth ) / 2f;\r\n            sphereCollider.Center = Vector3.Up * sphereCollider.Radius;\r\n        }\r\n    }\r\n\r\n    protected void CreateRigidbody( bool recreate = false )\r\n    {\r\n        if ( recreate \u0026\u0026 Body.IsValid() )\r\n            Body.Destroy();\r\n\r\n        if ( !Body.IsValid() \u0026\u0026 GameObject.Components.TryGet\u003CRigidbody\u003E( out var existingBody ) )\r\n            Body = existingBody;\r\n        if ( !Body.IsValid() )\r\n            Body = GameObject.AddComponent\u003CRigidbody\u003E();\r\n\r\n        Body.Locking = new PhysicsLock() { Pitch = true, Roll = true, Yaw = true };\r\n        Body.Gravity = false;\r\n        Body.MassOverride = BodyMassOverride;\r\n        Body.RigidbodyFlags |= RigidbodyFlags.DisableCollisionSounds;\r\n        Body.EnhancedCcd = EnableCCD;\r\n\r\n        if ( HidePhysicalComponents )\r\n            Body.Flags |= ComponentFlags.Hidden;\r\n        else\r\n            Body.Flags \u0026= ~ComponentFlags.Hidden;\r\n    }\r\n\r\n    public void OnCollisionStart( Collision collision ) { }\r\n}\r\n"},{"Ident":"fish.scc","Path":"Examples/ShrimpleRoller.cs","FileName":"ShrimpleRoller.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":301969,"IsPrivate":false,"Code":"namespace ShrimpleCharacterController;\r\n\r\n[Hide]\r\npublic sealed class ShrimpleRoller : Component\r\n{\r\n    [RequireComponent]\r\n    public ShrimpleCharacterController Controller { get; set; }\r\n\r\n    public ModelRenderer Renderer { get; set; }\r\n    public GameObject Camera { get; set; }\r\n\r\n    [Property]\r\n    [Range(500f, 2000f)]\r\n    public float WalkSpeed { get; set; } = 1000f;\r\n\r\n    [Property]\r\n    [Range(1000f, 5000f)]\r\n    public float RunSpeed { get; set; } = 3000f;\r\n\r\n    [Property]\r\n    [Range(200f, 500f)]\r\n    public float JumpStrength { get; set; } = 350f;\r\n\r\n    public Angles EyeAngles { get; set; }\r\n\r\n    protected override void OnStart()\r\n    {\r\n        base.OnStart();\r\n\r\n        Renderer = Components.Get\u003CModelRenderer\u003E(FindMode.EnabledInSelfAndDescendants);\r\n\r\n        Camera = new GameObject(true, \u0022Camera\u0022);\r\n        Camera.SetParent(GameObject);\r\n        var cameraComponent = Camera.Components.Create\u003CCameraComponent\u003E();\r\n        cameraComponent.ZFar = 32768f;\r\n    }\r\n\r\n    protected override void OnFixedUpdate()\r\n    {\r\n        base.OnFixedUpdate();\r\n\r\n        var wishDirection = Input.AnalogMove.Normal * Rotation.FromYaw(EyeAngles.yaw);\r\n        var isDucking = Input.Down(\u0022Duck\u0022);\r\n        var isRunning = Input.Down(\u0022Run\u0022);\r\n        var wishSpeed = isRunning ? RunSpeed : WalkSpeed;\r\n\r\n        Controller.WishVelocity = wishDirection * wishSpeed;\r\n        Controller.Move();\r\n\r\n        if (Input.Pressed(\u0022Jump\u0022) \u0026\u0026 Controller.IsOnGround)\r\n            Controller.Punch(Vector3.Up * JumpStrength);\r\n    }\r\n\r\n    protected override void OnUpdate()\r\n    {\r\n        base.OnUpdate();\r\n\r\n        EyeAngles \u002B= Input.AnalogLook;\r\n        EyeAngles = EyeAngles.WithPitch(MathX.Clamp(EyeAngles.pitch, -10f, 40f));\r\n\r\n        float pitchRotation = Controller.Velocity.x * Time.Delta;\r\n        float rollRotation = -Controller.Velocity.y * Time.Delta;\r\n        var ballPitch = Rotation.FromPitch(pitchRotation);\r\n        var ballRoll = Rotation.FromRoll(rollRotation);\r\n        Renderer.WorldRotation = ballPitch * ballRoll * Renderer.WorldRotation;\r\n\r\n        var cameraOffset = Vector3.Up * 70f \u002B Vector3.Backward * 260f;\r\n        Camera.WorldRotation = EyeAngles.ToRotation();\r\n        Camera.LocalPosition = cameraOffset * Camera.WorldRotation;\r\n    }\r\n}\r\n"},{"Ident":"fish.scc","Path":"Examples/ShrimpleWalker.cs","FileName":"ShrimpleWalker.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":301969,"IsPrivate":false,"Code":"namespace ShrimpleCharacterController;\r\n\r\n[Hide]\r\npublic sealed class ShrimpleWalker : Component\r\n{\r\n    [RequireComponent]\r\n    public ShrimpleCharacterController Controller { get; set; }\r\n    [Property]\r\n    public SkinnedModelRenderer Renderer { get; set; }\r\n    public GameObject Camera { get; set; }\r\n\r\n    [Property]\r\n    [Range( 50f, 200f )]\r\n    public float WalkSpeed { get; set; } = 100f;\r\n\r\n    [Property]\r\n    [Range( 100f, 500f )]\r\n    public float RunSpeed { get; set; } = 300f;\r\n\r\n    [Property]\r\n    [Range( 25f, 100f )]\r\n    public float DuckSpeed { get; set; } = 50f;\r\n\r\n    [Property]\r\n    [Range( 200f, 500f )]\r\n    public float JumpStrength { get; set; } = 350f;\r\n\r\n    public Angles EyeAngles { get; set; }\r\n\r\n    protected override void OnStart()\r\n    {\r\n        base.OnStart();\r\n\r\n        Renderer = Components.Get\u003CSkinnedModelRenderer\u003E( FindMode.EverythingInSelfAndDescendants );\r\n        Camera = new GameObject( true, \u0022Camera\u0022 );\r\n        Camera.SetParent( GameObject );\r\n        var cameraComponent = Camera.Components.Create\u003CCameraComponent\u003E();\r\n        cameraComponent.ZFar = 32768f;\r\n    }\r\n\r\n    protected override void OnFixedUpdate()\r\n    {\r\n        base.OnFixedUpdate();\r\n\r\n        var wishDirection = Input.AnalogMove.Normal * Rotation.FromYaw( EyeAngles.yaw ) * GameObject.WorldRotation;\r\n        var isDucking = Input.Down( \u0022Duck\u0022 );\r\n        var isRunning = Input.Down( \u0022Run\u0022 );\r\n        var wishSpeed = isDucking ? DuckSpeed :\r\n            isRunning ? RunSpeed : WalkSpeed;\r\n\r\n        Controller.WishVelocity = wishDirection * wishSpeed;\r\n\r\n        if ( Input.Pressed( \u0022Jump\u0022 ) \u0026\u0026 Controller.IsOnGround )\r\n        {\r\n            Controller.Punch( -Controller.AppliedGravity.Normal * JumpStrength );\r\n            Renderer?.Set( \u0022b_jump\u0022, true );\r\n        }\r\n\r\n        if ( !Renderer.IsValid() ) return;\r\n\r\n        var oldX = Renderer.GetFloat( \u0022move_x\u0022 );\r\n        var oldY = Renderer.GetFloat( \u0022move_y\u0022 );\r\n\r\n        var velocity = Controller.Velocity;\r\n        var runScale = 1f / Renderer.Transform.World.UniformScale;\r\n        var moveX = Vector3.Dot( velocity, Renderer.WorldRotation.Forward ) * runScale;\r\n        var moveY = Vector3.Dot( velocity, Renderer.WorldRotation.Right ) * runScale;\r\n\r\n        var newX = MathX.Lerp( oldX, moveX, Time.Delta * 10f );\r\n        var newY = MathX.Lerp( oldY, moveY, Time.Delta * 10f );\r\n\r\n        Renderer.Set( \u0022move_x\u0022, newX );\r\n        Renderer.Set( \u0022move_y\u0022, newY );\r\n\r\n        Renderer.Set( \u0022b_grounded\u0022, Controller.IsOnGround );\r\n        Renderer.Set( \u0022duck\u0022, isDucking ? 1f : 0f );\r\n    }\r\n\r\n    protected override void OnUpdate()\r\n    {\r\n        base.OnUpdate();\r\n\r\n        EyeAngles \u002B= Input.AnalogLook;\r\n        EyeAngles = EyeAngles.WithPitch( MathX.Clamp( EyeAngles.pitch, -10f, 40f ) );\r\n        Renderer.LocalRotation = Rotation.FromYaw( EyeAngles.yaw );\r\n\r\n        var cameraOffset = Vector3.Up * 70f \u002B Vector3.Backward * 280f;\r\n        Camera.LocalRotation = EyeAngles.ToRotation();\r\n        Camera.LocalPosition = cameraOffset * Camera.LocalRotation;\r\n    }\r\n}\r\n"},{"Ident":"fish.scc","Path":"code/Assembly.cs","FileName":"Assembly.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":301969,"IsPrivate":false,"Code":"global using Sandbox;\r\nglobal using System;\r\nglobal using System.Collections.Generic;\r\nglobal using System.Linq;\r\nnamespace ShrimpleCharacterController;\r\n"},{"Ident":"fish.scc","Path":"code/PlatformTest.cs","FileName":"PlatformTest.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":301969,"IsPrivate":false,"Code":"using Sandbox;\r\n\r\n[Hide]\r\npublic sealed class PlatformTest : Component\r\n{\r\n    protected override void OnFixedUpdate()\r\n    {\r\n        WorldPosition \u002B= Vector3.Right * (float)Math.Sin(Time.Now * 2f) * 400f * Time.Delta;\r\n        //WorldPosition \u002B= Vector3.Up * (float)Math.Sin(Time.Now * 2f) * 200f * Time.Delta;\r\n    }\r\n}\r\n"},{"Ident":"fish.scc","Path":"code/Util/Extensions/Vector3Extensions.cs","FileName":"Vector3Extensions.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":301969,"IsPrivate":false,"Code":"namespace ShrimpleCharacterController;\r\n\r\npublic static class Vector3Extensions\r\n{\r\n    /// \u003Csummary\u003E\r\n    /// Move a vector3 towards a goal by a fixed distance\r\n    /// \u003C/summary\u003E\r\n    /// \u003Cparam name=\u0022value\u0022\u003E\u003C/param\u003E\r\n    /// \u003Cparam name=\u0022target\u0022\u003E\u003C/param\u003E\r\n    /// \u003Cparam name=\u0022travelSpeed\u0022\u003E\u003C/param\u003E\r\n    /// \u003Creturns\u003E\u003C/returns\u003E\r\n    public static Vector3 MoveTowards(this Vector3 value, Vector3 target, float travelSpeed)\r\n    {\r\n        var difference = target - value;\r\n        var distance = difference.Length;\r\n        var normal = difference.Normal;\r\n\r\n        if (distance \u003C= travelSpeed || distance == 0f)\r\n        {\r\n            return target;\r\n        }\r\n\r\n        return value \u002B normal * travelSpeed;\r\n    }\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Project a vector along a plane (normal) and scale it back to its original length\r\n    /// \u003C/summary\u003E\r\n    /// \u003Cparam name=\u0022value\u0022\u003E\u003C/param\u003E\r\n    /// \u003Cparam name=\u0022normal\u0022\u003E\u003C/param\u003E\r\n    /// \u003Creturns\u003E\u003C/returns\u003E\r\n    public static Vector3 ProjectAndScale(this Vector3 value, Vector3 normal)\r\n    {\r\n        var length = value.Length;\r\n        value = Vector3.VectorPlaneProject(value, normal).Normal;\r\n        value *= length;\r\n\r\n        return value;\r\n    }\r\n}\r\n"},{"Ident":"fish.scc","Path":"Examples/ShrimpleFlyer.cs","FileName":"ShrimpleFlyer.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":301969,"IsPrivate":false,"Code":"using Sandbox.Citizen;\r\n\r\nnamespace ShrimpleCharacterController;\r\n\r\n[Hide]\r\npublic sealed class ShrimpleFlyer : Component\r\n{\r\n    [RequireComponent]\r\n    public ShrimpleCharacterController Controller { get; set; }\r\n    public GameObject Camera { get; set; }\r\n\r\n    [Property]\r\n    [Range(400f, 1600f)]\r\n    public float WalkSpeed { get; set; } = 800f;\r\n\r\n    [Property]\r\n    [Range(800f, 4000f)]\r\n    public float RunSpeed { get; set; } = 2400f;\r\n\r\n    public Angles EyeAngles { get; set; }\r\n\r\n    protected override void OnStart()\r\n    {\r\n        base.OnStart();\r\n\r\n        Camera = new GameObject(true, \u0022Camera\u0022);\r\n        Camera.SetParent(GameObject);\r\n        var cameraComponent = Camera.Components.Create\u003CCameraComponent\u003E();\r\n        cameraComponent.ZFar = 32768f;\r\n    }\r\n\r\n    protected override void OnFixedUpdate()\r\n    {\r\n        base.OnFixedUpdate();\r\n\r\n        var isDucking = Input.Down(\u0022Duck\u0022);\r\n        var isRunning = Input.Down(\u0022Run\u0022);\r\n        var ascending = Input.Down(\u0022Jump\u0022) ? 1f : 0f;\r\n        var descending = Input.Down(\u0022Duck\u0022) ? -1f : 0f;\r\n        var wishSpeed = isRunning ? RunSpeed : WalkSpeed;\r\n        var wishDirection = (Input.AnalogMove \u002B Vector3.Up * (ascending \u002B descending)).Normal * EyeAngles.ToRotation();\r\n\r\n        Controller.WishVelocity = wishDirection * wishSpeed;\r\n        Controller.Move();\r\n    }\r\n\r\n    protected override void OnUpdate()\r\n    {\r\n        base.OnUpdate();\r\n\r\n        EyeAngles \u002B= Input.AnalogLook;\r\n        EyeAngles = EyeAngles.WithPitch(MathX.Clamp(EyeAngles.pitch, -40f, 40f));\r\n\r\n        var cameraOffset = Vector3.Up * 70f \u002B Vector3.Backward * 760f;\r\n        Camera.WorldRotation = EyeAngles.ToRotation();\r\n        Camera.LocalPosition = cameraOffset * Camera.WorldRotation;\r\n    }\r\n}\r\n"},{"Ident":"fish.scc","Path":"PlatformTest.cs","FileName":"PlatformTest.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":301969,"IsPrivate":false,"Code":"using Sandbox;\r\n\r\n[Hide]\r\npublic sealed class PlatformTest : Component\r\n{\r\n    protected override void OnFixedUpdate()\r\n    {\r\n        WorldPosition \u002B= Vector3.Right * (float)Math.Sin(Time.Now * 2f) * 400f * Time.Delta;\r\n        //WorldPosition \u002B= Vector3.Up * (float)Math.Sin(Time.Now * 2f) * 200f * Time.Delta;\r\n    }\r\n}\r\n"},{"Ident":"fish.scc","Path":"ShrimpleCollisionResult.cs","FileName":"ShrimpleCollisionResult.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":301969,"IsPrivate":false,"Code":"namespace ShrimpleCharacterController;\r\n\r\npublic struct ShrimpleCollisionResult\r\n{\r\n    public Vector3 HitPosition;\r\n    public Vector3 HitNormal;\r\n    public Vector3 HitVelocityBefore;\r\n    public Vector3 HitVelocityAfter;\r\n    public float Angle;\r\n    public GameObject HitObject;\r\n    public Surface HitSurface;\r\n\r\n    public ShrimpleCollisionResult() { }\r\n\r\n    public ShrimpleCollisionResult(Vector3 hitPosition, Vector3 hitNormal, Vector3 hitVelocityBefore, Vector3 hitVelocityAfter, float angle, GameObject hitObject, Surface hitSurface)\r\n    {\r\n        HitPosition = hitPosition;\r\n        HitNormal = hitNormal;\r\n        HitVelocityBefore = hitVelocityBefore;\r\n        HitVelocityAfter = hitVelocityAfter;\r\n        Angle = angle;\r\n        HitObject = hitObject;\r\n        HitSurface = hitSurface;\r\n    }\r\n\r\n    public ShrimpleCollisionResult WithHitPosition(Vector3 hitPosition)\r\n    {\r\n        var result = this;\r\n        result.HitPosition = hitPosition;\r\n        return result;\r\n    }\r\n\r\n    public ShrimpleCollisionResult WithHitNormal(Vector3 hitNormal)\r\n    {\r\n        var result = this;\r\n        result.HitNormal = hitNormal;\r\n        return result;\r\n    }\r\n\r\n    public ShrimpleCollisionResult WithHitVelocityBefore(Vector3 hitVelocityBefore)\r\n    {\r\n        var result = this;\r\n        result.HitVelocityBefore = hitVelocityBefore;\r\n        return result;\r\n    }\r\n\r\n    public ShrimpleCollisionResult WithHitVelocityAfter(Vector3 hitVelocityAfter)\r\n    {\r\n        var result = this;\r\n        result.HitVelocityAfter = hitVelocityAfter;\r\n        return result;\r\n    }\r\n\r\n    public ShrimpleCollisionResult WithAngle(float angle)\r\n    {\r\n        var result = this;\r\n        result.Angle = angle;\r\n        return result;\r\n    }\r\n\r\n    public ShrimpleCollisionResult WithHitObject(GameObject hitObject)\r\n    {\r\n        var result = this;\r\n        result.HitObject = hitObject;\r\n        return result;\r\n    }\r\n\r\n    public ShrimpleCollisionResult WithHitSurface(Surface hitSurface)\r\n    {\r\n        var result = this;\r\n        result.HitSurface = hitSurface;\r\n        return result;\r\n    }\r\n}"},{"Ident":"fish.scc","Path":"code/Examples/ShrimpleRoller.cs","FileName":"ShrimpleRoller.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":301969,"IsPrivate":false,"Code":"namespace ShrimpleCharacterController;\r\n\r\n[Hide]\r\npublic sealed class ShrimpleRoller : Component\r\n{\r\n    [RequireComponent]\r\n    public ShrimpleCharacterController Controller { get; set; }\r\n\r\n    public ModelRenderer Renderer { get; set; }\r\n    public GameObject Camera { get; set; }\r\n\r\n    [Property]\r\n    [Range(500f, 2000f)]\r\n    public float WalkSpeed { get; set; } = 1000f;\r\n\r\n    [Property]\r\n    [Range(1000f, 5000f)]\r\n    public float RunSpeed { get; set; } = 3000f;\r\n\r\n    [Property]\r\n    [Range(200f, 500f)]\r\n    public float JumpStrength { get; set; } = 350f;\r\n\r\n    public Angles EyeAngles { get; set; }\r\n\r\n    protected override void OnStart()\r\n    {\r\n        base.OnStart();\r\n\r\n        Renderer = Components.Get\u003CModelRenderer\u003E(FindMode.EnabledInSelfAndDescendants);\r\n\r\n        Camera = new GameObject(true, \u0022Camera\u0022);\r\n        Camera.SetParent(GameObject);\r\n        var cameraComponent = Camera.Components.Create\u003CCameraComponent\u003E();\r\n        cameraComponent.ZFar = 32768f;\r\n    }\r\n\r\n    protected override void OnFixedUpdate()\r\n    {\r\n        base.OnFixedUpdate();\r\n\r\n        var wishDirection = Input.AnalogMove.Normal * Rotation.FromYaw(EyeAngles.yaw);\r\n        var isDucking = Input.Down(\u0022Duck\u0022);\r\n        var isRunning = Input.Down(\u0022Run\u0022);\r\n        var wishSpeed = isRunning ? RunSpeed : WalkSpeed;\r\n\r\n        Controller.WishVelocity = wishDirection * wishSpeed;\r\n        Controller.Move();\r\n\r\n        if (Input.Pressed(\u0022Jump\u0022) \u0026\u0026 Controller.IsOnGround)\r\n            Controller.Punch(Vector3.Up * JumpStrength);\r\n    }\r\n\r\n    protected override void OnUpdate()\r\n    {\r\n        base.OnUpdate();\r\n\r\n        EyeAngles \u002B= Input.AnalogLook;\r\n        EyeAngles = EyeAngles.WithPitch(MathX.Clamp(EyeAngles.pitch, -10f, 40f));\r\n\r\n        float pitchRotation = Controller.Velocity.x * Time.Delta;\r\n        float rollRotation = -Controller.Velocity.y * Time.Delta;\r\n        var ballPitch = Rotation.FromPitch(pitchRotation);\r\n        var ballRoll = Rotation.FromRoll(rollRotation);\r\n        Renderer.WorldRotation = ballPitch * ballRoll * Renderer.WorldRotation;\r\n\r\n        var cameraOffset = Vector3.Up * 70f \u002B Vector3.Backward * 260f;\r\n        Camera.WorldRotation = EyeAngles.ToRotation();\r\n        Camera.LocalPosition = cameraOffset * Camera.WorldRotation;\r\n    }\r\n}\r\n"},{"Ident":"fish.scc","Path":"Util/Extensions/Vector3Extensions.cs","FileName":"Vector3Extensions.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":301969,"IsPrivate":false,"Code":"namespace ShrimpleCharacterController;\r\n\r\npublic static class Vector3Extensions\r\n{\r\n    /// \u003Csummary\u003E\r\n    /// Move a vector3 towards a goal by a fixed distance\r\n    /// \u003C/summary\u003E\r\n    /// \u003Cparam name=\u0022value\u0022\u003E\u003C/param\u003E\r\n    /// \u003Cparam name=\u0022target\u0022\u003E\u003C/param\u003E\r\n    /// \u003Cparam name=\u0022travelSpeed\u0022\u003E\u003C/param\u003E\r\n    /// \u003Creturns\u003E\u003C/returns\u003E\r\n    public static Vector3 MoveTowards(this Vector3 value, Vector3 target, float travelSpeed)\r\n    {\r\n        var difference = target - value;\r\n        var distance = difference.Length;\r\n        var normal = difference.Normal;\r\n\r\n        if (distance \u003C= travelSpeed || distance == 0f)\r\n        {\r\n            return target;\r\n        }\r\n\r\n        return value \u002B normal * travelSpeed;\r\n    }\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Project a vector along a plane (normal) and scale it back to its original length\r\n    /// \u003C/summary\u003E\r\n    /// \u003Cparam name=\u0022value\u0022\u003E\u003C/param\u003E\r\n    /// \u003Cparam name=\u0022normal\u0022\u003E\u003C/param\u003E\r\n    /// \u003Creturns\u003E\u003C/returns\u003E\r\n    public static Vector3 ProjectAndScale(this Vector3 value, Vector3 normal)\r\n    {\r\n        var length = value.Length;\r\n        value = Vector3.VectorPlaneProject(value, normal).Normal;\r\n        value *= length;\r\n\r\n        return value;\r\n    }\r\n}\r\n"},{"Ident":"fish.scc","Path":"ShrimpleCharacterController.Physical.cs","FileName":"ShrimpleCharacterController.Physical.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":301969,"IsPrivate":false,"Code":"namespace ShrimpleCharacterController;\r\n\r\npublic partial class ShrimpleCharacterController\r\n{\r\n    bool _didStep;\r\n    Vector3 _stepPosition;\r\n\r\n    // Set by Move() when ManuallyUpdate is enabled in physical mode, consumed by the next physics step\r\n    bool _physicalMovePending;\r\n    float _physicalMoveDelta;\r\n\r\n    // The delta this physics step should simulate (the manual Move() delta, otherwise the fixed step)\r\n    float ActiveDelta =\u003E ( ManuallyUpdate \u0026\u0026 _physicalMovePending ) ? _physicalMoveDelta : ActiveAutoTimeDelta;\r\n\r\n    // The solver always integrates over Time.Delta, so we scale velocity to cover ActiveDelta worth of distance\r\n    float MoveScale =\u003E Time.Delta \u003E 0f ? ActiveDelta / Time.Delta : 1f;\r\n\r\n    void IScenePhysicsEvents.PrePhysicsStep()\r\n    {\r\n        if ( !PhysicallySimulated ) return;\r\n        if ( !Body.IsValid() ) return;\r\n        if ( ManuallyUpdate \u0026\u0026 !_physicalMovePending ) return; // Wait for a manual Move() call to drive us\r\n\r\n        _didStep = false;\r\n\r\n        UpdateBodyPhysics();\r\n        UpdateMassCenter();\r\n        UpdateMovement();\r\n    }\r\n\r\n    void IScenePhysicsEvents.PostPhysicsStep()\r\n    {\r\n        if ( !Body.IsValid() )\r\n        {\r\n            WorldPosition \u002B= GroundVelocity * Time.Delta;\r\n            return;\r\n        }\r\n\r\n        if ( !PhysicallySimulated ) return;\r\n        if ( ManuallyUpdate \u0026\u0026 !_physicalMovePending ) return;\r\n\r\n        if ( _didStep )\r\n            Body.WorldPosition = _stepPosition;\r\n\r\n        CategorizePhysicalGround();\r\n        ApplyGroundVelocity();\r\n        Velocity = Body.Velocity / MoveScale; // De-scale back to logical units/second\r\n\r\n        _physicalMovePending = false;\r\n    }\r\n\r\n    private void UpdateMassCenter()\r\n    {\r\n        if ( !Body.IsValid() ) return;\r\n\r\n        var wishSpeed = WishVelocity.WithZ( 0 ).Length;\r\n        var halfHeight = AppliedHeight * 0.5f;\r\n        float massCenter = IsOnGround ? wishSpeed.Clamp( 0, halfHeight ) : halfHeight;\r\n\r\n        Body.MassCenterOverride = new Vector3( 0, 0, massCenter );\r\n        Body.OverrideMassCenter = true;\r\n    }\r\n\r\n    private void UpdateBodyPhysics()\r\n    {\r\n        if ( !Collider.IsValid() || !Body.IsValid() ) return;\r\n\r\n        var onDynamic = false;\r\n        if ( IsOnGround \u0026\u0026 GroundObject.IsValid() )\r\n        {\r\n            var groundBody = GroundObject.GetComponent\u003CRigidbody\u003E();\r\n            onDynamic = groundBody != null \u0026\u0026 groundBody.PhysicsBody.BodyType == PhysicsBodyType.Dynamic;\r\n        }\r\n\r\n        Body.Gravity = false;\r\n\r\n        var velocityHorizontal = Body.Velocity.WithZ( 0 ).Length;\r\n        bool wantsBrakes = IsOnGround \u0026\u0026 WishVelocity.Length \u003C 1f \u0026\u0026 velocityHorizontal \u003C 10f;\r\n        Body.LinearDamping = wantsBrakes ? 10f : 0.1f;\r\n        Body.AngularDamping = 1f;\r\n\r\n        float friction = 0f;\r\n        if ( IsOnGround \u0026\u0026 WishVelocity.Length \u003C 0.1f )\r\n            friction = onDynamic ? 0.5f : 1f;\r\n        Collider.Friction = friction;\r\n    }\r\n\r\n    private void AddWishVelocity()\r\n    {\r\n        var z = Body.Velocity.z;\r\n        var velocity = CalculateGoalVelocity( ActiveDelta );\r\n\r\n        if ( IsOnGround \u0026\u0026 GroundStickEnabled \u0026\u0026 !GroundNormal.IsNearlyZero( 0.01f ) )\r\n        {\r\n            if ( MathF.Abs( GroundNormal.z ) \u003E 0.01f )\r\n                velocity = velocity.WithZ( -( GroundNormal.x * velocity.x \u002B GroundNormal.y * velocity.y ) / GroundNormal.z );\r\n\r\n            var isGoingUphill = Vector3.Dot( velocity, AppliedGravity ) \u003C 0f;\r\n            var slopeMultiplier = isGoingUphill ? GetSlopeVelocityMultiplier( GroundAngle ) : 1f;\r\n            velocity *= slopeMultiplier;\r\n        }\r\n\r\n        if ( IsOnGround \u0026\u0026 !GroundStickEnabled )\r\n            velocity.z = z;\r\n\r\n        if ( GravityEnabled \u0026\u0026 ( !IsOnGround || IsSlipping || !GroundStickEnabled ) )\r\n            velocity \u002B= AppliedGravity * ActiveDelta;\r\n\r\n        Body.Velocity = velocity * MoveScale; // Scale so the solver covers ActiveDelta worth of distance over Time.Delta\r\n    }\r\n\r\n    private void UpdateMovement()\r\n    {\r\n        if ( IsOnGround \u0026\u0026 GroundStickEnabled \u0026\u0026 !IsSlipping )\r\n            StickToGround();\r\n\r\n        AddWishVelocity();\r\n\r\n        var wishHorizontal = WishVelocity.WithZ( 0 );\r\n        var bodyHorizontal = Body.Velocity.WithZ( 0 );\r\n        var moveDir = wishHorizontal.IsNearlyZero( 0.1f ) ? bodyHorizontal : wishHorizontal;\r\n        if ( moveDir.IsNearlyZero( 0.1f ) ) return;\r\n\r\n        var speed = MathF.Max( bodyHorizontal.Length * Time.Delta, StepDepth );\r\n        var vel = moveDir.Normal * speed;\r\n        var forwardTrace = BuildTrace( _shrunkenBounds, WorldPosition \u002B _offset - vel.Normal * SkinWidth, WorldPosition \u002B _offset \u002B vel );\r\n\r\n        if ( !forwardTrace.Hit || forwardTrace.StartedSolid ) return;\r\n\r\n        var forwardAngle = Vector3.GetAngle( -AppliedGravity.Normal, forwardTrace.Normal );\r\n        if ( IsAngleStandable( forwardAngle ) \u0026\u0026 Vector3.Dot( Body.Velocity, forwardTrace.Normal ) \u003C 0f )\r\n            Body.Velocity = Vector3.VectorPlaneProject( Body.Velocity, forwardTrace.Normal );\r\n\r\n        if ( StepsEnabled \u0026\u0026 IsOnGround )\r\n            TryStepUp( forwardTrace, vel );\r\n    }\r\n\r\n    private void TryStepUp( SceneTraceResult forwardTrace, Vector3 vel )\r\n    {\r\n        if ( forwardTrace.StartedSolid || !forwardTrace.Hit ) return;\r\n\r\n        var angle = Vector3.GetAngle( -GravityNormal, forwardTrace.Normal );\r\n        if ( angle \u003C 90f - StepTolerance || angle \u003E 90f \u002B StepTolerance ) return;\r\n\r\n        var stepHorizontal = Vector3.VectorPlaneProject( vel.Normal, GravityNormal ).Normal * StepDepth;\r\n        var stepVertical = -GravityNormal * ( StepHeight \u002B SkinWidth );\r\n        var stepTrace = BuildTrace( _shrunkenBounds, forwardTrace.EndPosition \u002B stepHorizontal \u002B stepVertical, forwardTrace.EndPosition \u002B stepHorizontal );\r\n\r\n        if ( stepTrace.StartedSolid || !stepTrace.Hit ) return;\r\n        if ( !IsAngleStandable( Vector3.GetAngle( stepTrace.Normal, -GravityNormal ) ) ) return;\r\n\r\n        var newFeetPos = stepTrace.EndPosition - _offset \u002B _upAxis * SkinWidth;\r\n        if ( newFeetPos.z - WorldPosition.z \u003C 0.5f ) return;\r\n\r\n        _didStep = true;\r\n        _stepPosition = newFeetPos;\r\n        Body.WorldPosition = _stepPosition;\r\n        Body.Velocity = Body.Velocity.WithZ( 0 );\r\n    }\r\n\r\n    private void ApplyGroundVelocity()\r\n    {\r\n        if ( !IsOnGround || !GroundStickEnabled || IsSlipping ) return;\r\n\r\n        var groundVelocity = PlatformVelocity * Time.Delta \u002B SurfaceVelocity * ActiveDelta;\r\n        if ( groundVelocity.IsNearZeroLength ) return;\r\n\r\n        Body.WorldPosition \u002B= groundVelocity;\r\n    }\r\n\r\n    private void StickToGround()\r\n    {\r\n        var currentPosition = WorldPosition;\r\n        var from = currentPosition \u002B _offset - GravityNormal * StepHeight;\r\n        var to = currentPosition \u002B _offset \u002B GravityNormal * ( GroundStickDistance \u002B SkinWidth );\r\n\r\n        var trace = BuildTrace( _shrunkenBounds, from, to );\r\n\r\n        if ( trace.StartedSolid ) return;\r\n\r\n        if ( trace.Hit )\r\n        {\r\n            var surfaceAngle = Vector3.GetAngle( -GravityNormal, trace.Normal );\r\n            if ( !IsAngleStandable( surfaceAngle ) ) return;\r\n\r\n            var targetFeetPos = trace.EndPosition - _offset \u002B _upAxis * SkinWidth;\r\n\r\n            Body.WorldPosition = targetFeetPos;\r\n            Body.Sleeping = false;\r\n\r\n            if ( Body.Velocity.z \u003E 0f )\r\n                Body.Velocity = Body.Velocity.WithZ( 0 );\r\n\r\n            IsOnGround = true;\r\n            GroundNormal = trace.Normal;\r\n            GroundSurface = trace.Surface;\r\n            GroundObject = trace.GameObject;\r\n            IsSlipping = false;\r\n        }\r\n    }\r\n\r\n    private void CategorizePhysicalGround()\r\n    {\r\n        var position = WorldPosition \u002B _offset;\r\n        var shortCylinderOffset = AppliedWidth / 2f;\r\n        var shortCylinderBounds = new BBox( _shrunkenBounds.Mins, new Vector3( _shrunkenBounds.Maxs.x, _shrunkenBounds.Maxs.y, _shrunkenBounds.Maxs.z - shortCylinderOffset ) );\r\n        var groundTrace = BuildTrace( shortCylinderBounds, position \u002B GravityNormal * shortCylinderOffset / 2f, position \u002B GravityNormal * ( GroundStickDistance \u002B SkinWidth ) );\r\n\r\n        if ( groundTrace.StartedSolid )\r\n        {\r\n            var stuckCheck = BuildTrace( _shrunkenBounds.Grow( -SkinWidth ), position, position, useCapsule: true );\r\n\r\n            if ( stuckCheck.StartedSolid )\r\n            {\r\n                IsStuck = true;\r\n\r\n                if ( UnstuckEnabled \u0026\u0026 TryUnstuck( position, out var unstuckResult ) )\r\n                {\r\n                    IsStuck = false;\r\n                    Body.WorldPosition = unstuckResult - _offset;\r\n                    Body.Velocity = Vector3.Zero;\r\n                }\r\n\r\n                var fallbackTrace = BuildTrace( _shrunkenBounds, position \u002B -GravityNormal * 4f, position \u002B GravityNormal * 2f );\r\n\r\n                if ( fallbackTrace.Hit \u0026\u0026 !fallbackTrace.StartedSolid \u0026\u0026 Vector3.Dot( fallbackTrace.Normal, -GravityNormal ) \u003E 0f )\r\n                {\r\n                    var standable = IsAngleStandable( Vector3.GetAngle( Vector3.Up, fallbackTrace.Normal ) );\r\n                    IsOnGround = true;\r\n                    GroundNormal = fallbackTrace.Normal;\r\n                    GroundSurface = fallbackTrace.Surface;\r\n                    GroundObject = fallbackTrace.GameObject;\r\n                    IsSlipping = !standable;\r\n                }\r\n                return;\r\n            }\r\n\r\n            groundTrace = BuildTrace( _shrunkenBounds, position, position \u002B GravityNormal * ( GroundStickDistance \u002B SkinWidth ) );\r\n        }\r\n\r\n        IsStuck = false;\r\n\r\n        if ( groundTrace.Hit )\r\n        {\r\n            var standable = IsAngleStandable( Vector3.GetAngle( Vector3.Up, groundTrace.Normal ) );\r\n            var landingAngle = Vector3.Dot( Velocity.Normal, groundTrace.Normal );\r\n            var verticalAngle = Vector3.Dot( Velocity.Normal, GravityNormal );\r\n            var goingUp = verticalAngle \u003C 0.1f;\r\n            var hasLanded = !IsOnGround \u0026\u0026 landingAngle \u003C= ( MaxGroundAngle.Max / 180f ) \u0026\u0026 groundTrace.Distance \u003C= SkinWidth * ( goingUp ? 6f : 2f ) \u002B StepHeight;\r\n\r\n            IsOnGround = IsOnGround || hasLanded;\r\n            GroundNormal = groundTrace.Normal;\r\n            GroundSurface = groundTrace.Surface;\r\n            GroundObject = groundTrace.GameObject;\r\n            IsSlipping = IsOnGround \u0026\u0026 !standable;\r\n\r\n            if ( GroundStickEnabled \u0026\u0026 !IsSlipping \u0026\u0026 IsOnGround )\r\n            {\r\n                var targetFeetPos = groundTrace.EndPosition - _offset \u002B _upAxis * ( SkinWidth \u002B shortCylinderOffset / 2f );\r\n                var delta = WorldPosition - targetFeetPos;\r\n\r\n                if ( delta.z \u003E= -0.1f \u0026\u0026 !delta.IsNearlyZero( 0.001f ) )\r\n                {\r\n                    Body.WorldPosition = targetFeetPos;\r\n                    Body.Sleeping = false;\r\n\r\n                    if ( Body.Velocity.z \u003E 0f )\r\n                        Body.Velocity = Body.Velocity.WithZ( 0 );\r\n                }\r\n            }\r\n\r\n            return;\r\n        }\r\n\r\n        IsOnGround = false;\r\n        GroundNormal = Vector3.Zero;\r\n        GroundSurface = null;\r\n        GroundObject = null;\r\n        IsSlipping = false;\r\n    }\r\n\r\n    protected void CreateBody( bool recreate = false )\r\n    {\r\n        if ( recreate )\r\n        {\r\n            if ( Collider.IsValid() ) Collider.Destroy();\r\n            if ( Body.IsValid() ) Body.Destroy();\r\n            if ( BodyObject.IsValid() ) BodyObject.Destroy();\r\n        }\r\n\r\n        CreateCollider( recreate );\r\n        CreateRigidbody( recreate );\r\n    }\r\n\r\n    protected void DestroyBody()\r\n    {\r\n        if ( Collider.IsValid() ) Collider.Destroy();\r\n        if ( Body.IsValid() ) Body.Destroy();\r\n        if ( BodyObject.IsValid() ) BodyObject.Destroy();\r\n    }\r\n\r\n    protected void CreateCollider( bool recreate = false )\r\n    {\r\n        if ( recreate \u0026\u0026 Collider.IsValid() )\r\n            Collider.Destroy();\r\n\r\n        if ( PhysicallySimulated )\r\n        {\r\n            if ( !Collider.IsValid() || Collider is not CapsuleCollider )\r\n            {\r\n                if ( Collider.IsValid() )\r\n                    Collider.Destroy();\r\n\r\n                Collider = GameObject.GetOrAddComponent\u003CCapsuleCollider\u003E();\r\n            }\r\n        }\r\n        else\r\n        {\r\n            if ( TraceShape == TraceType.Box || TraceShape == TraceType.Bounds )\r\n            {\r\n                if ( !Collider.IsValid() || Collider is not BoxCollider )\r\n                {\r\n                    if ( Collider.IsValid() )\r\n                        Collider.Destroy();\r\n\r\n                    Collider = GameObject.GetOrAddComponent\u003CBoxCollider\u003E();\r\n                }\r\n            }\r\n            else if ( TraceShape == TraceType.Cylinder )\r\n            {\r\n                if ( !Collider.IsValid() || Collider is not HullCollider )\r\n                {\r\n                    if ( Collider.IsValid() )\r\n                        Collider.Destroy();\r\n\r\n                    Collider = GameObject.GetOrAddComponent\u003CHullCollider\u003E();\r\n                }\r\n            }\r\n            else if ( TraceShape == TraceType.Sphere )\r\n            {\r\n                if ( !Collider.IsValid() || Collider is not SphereCollider )\r\n                {\r\n                    if ( Collider.IsValid() )\r\n                        Collider.Destroy();\r\n\r\n                    Collider = GameObject.GetOrAddComponent\u003CSphereCollider\u003E();\r\n                }\r\n            }\r\n        }\r\n\r\n        if ( HidePhysicalComponents )\r\n            Collider.Flags |= ComponentFlags.Hidden;\r\n        else\r\n            Collider.Flags \u0026= ~ComponentFlags.Hidden;\r\n\r\n        UpdateCollider();\r\n    }\r\n\r\n    protected void UpdateCollider()\r\n    {\r\n        if ( !Collider.IsValid() ) return;\r\n\r\n        if ( Collider is CapsuleCollider capsuleCollider )\r\n        {\r\n            var radius = ( TraceWidth - SkinWidth ) / 2f;\r\n            var height = TraceHeight - SkinWidth;\r\n            capsuleCollider.Radius = radius;\r\n            capsuleCollider.Start = Vector3.Up * radius;\r\n            capsuleCollider.End = Vector3.Up * ( height - radius );\r\n        }\r\n        else if ( Collider is BoxCollider boxCollider )\r\n        {\r\n            var bounds = BuildBounds();\r\n            boxCollider.Scale = bounds.Size - SkinWidth;\r\n            boxCollider.Center = Vector3.Up * ( TraceHeight / 2f );\r\n        }\r\n        else if ( Collider is HullCollider hullCollider )\r\n        {\r\n            hullCollider.Type = HullCollider.PrimitiveType.Cylinder;\r\n            hullCollider.Height = TraceHeight - SkinWidth;\r\n            hullCollider.Radius = ( TraceWidth - SkinWidth ) / 2f;\r\n            hullCollider.Center = Vector3.Up * ( TraceHeight / 2f );\r\n        }\r\n        else if ( Collider is SphereCollider sphereCollider )\r\n        {\r\n            sphereCollider.Radius = ( TraceWidth - SkinWidth ) / 2f;\r\n            sphereCollider.Center = Vector3.Up * sphereCollider.Radius;\r\n        }\r\n    }\r\n\r\n    protected void CreateRigidbody( bool recreate = false )\r\n    {\r\n        if ( recreate \u0026\u0026 Body.IsValid() )\r\n            Body.Destroy();\r\n\r\n        if ( !Body.IsValid() \u0026\u0026 GameObject.Components.TryGet\u003CRigidbody\u003E( out var existingBody ) )\r\n            Body = existingBody;\r\n        if ( !Body.IsValid() )\r\n            Body = GameObject.AddComponent\u003CRigidbody\u003E();\r\n\r\n        Body.Locking = new PhysicsLock() { Pitch = true, Roll = true, Yaw = true };\r\n        Body.Gravity = false;\r\n        Body.MassOverride = BodyMassOverride;\r\n        Body.RigidbodyFlags |= RigidbodyFlags.DisableCollisionSounds;\r\n        Body.EnhancedCcd = EnableCCD;\r\n\r\n        if ( HidePhysicalComponents )\r\n            Body.Flags |= ComponentFlags.Hidden;\r\n        else\r\n            Body.Flags \u0026= ~ComponentFlags.Hidden;\r\n    }\r\n\r\n    public void OnCollisionStart( Collision collision ) { }\r\n}\r\n"},{"Ident":"fish.scc","Path":"Assembly.cs","FileName":"Assembly.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":301969,"IsPrivate":false,"Code":"global using Sandbox;\r\nglobal using System;\r\nglobal using System.Collections.Generic;\r\nglobal using System.Linq;\r\nnamespace ShrimpleCharacterController;\r\n"},{"Ident":"fish.scc","Path":"code/Examples/ShrimpleWalker.cs","FileName":"ShrimpleWalker.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":301969,"IsPrivate":false,"Code":"namespace ShrimpleCharacterController;\r\n\r\n[Hide]\r\npublic sealed class ShrimpleWalker : Component\r\n{\r\n    [RequireComponent]\r\n    public ShrimpleCharacterController Controller { get; set; }\r\n    [Property]\r\n    public SkinnedModelRenderer Renderer { get; set; }\r\n    public GameObject Camera { get; set; }\r\n\r\n    [Property]\r\n    [Range( 50f, 200f )]\r\n    public float WalkSpeed { get; set; } = 100f;\r\n\r\n    [Property]\r\n    [Range( 100f, 500f )]\r\n    public float RunSpeed { get; set; } = 300f;\r\n\r\n    [Property]\r\n    [Range( 25f, 100f )]\r\n    public float DuckSpeed { get; set; } = 50f;\r\n\r\n    [Property]\r\n    [Range( 200f, 500f )]\r\n    public float JumpStrength { get; set; } = 350f;\r\n\r\n    public Angles EyeAngles { get; set; }\r\n\r\n    protected override void OnStart()\r\n    {\r\n        base.OnStart();\r\n\r\n        Renderer = Components.Get\u003CSkinnedModelRenderer\u003E( FindMode.EverythingInSelfAndDescendants );\r\n        Camera = new GameObject( true, \u0022Camera\u0022 );\r\n        Camera.SetParent( GameObject );\r\n        var cameraComponent = Camera.Components.Create\u003CCameraComponent\u003E();\r\n        cameraComponent.ZFar = 32768f;\r\n    }\r\n\r\n    protected override void OnFixedUpdate()\r\n    {\r\n        base.OnFixedUpdate();\r\n\r\n        var wishDirection = Input.AnalogMove.Normal * Rotation.FromYaw( EyeAngles.yaw ) * GameObject.WorldRotation;\r\n        var isDucking = Input.Down( \u0022Duck\u0022 );\r\n        var isRunning = Input.Down( \u0022Run\u0022 );\r\n        var wishSpeed = isDucking ? DuckSpeed :\r\n            isRunning ? RunSpeed : WalkSpeed;\r\n\r\n        Controller.WishVelocity = wishDirection * wishSpeed;\r\n\r\n        if ( Input.Pressed( \u0022Jump\u0022 ) \u0026\u0026 Controller.IsOnGround )\r\n        {\r\n            Controller.Punch( -Controller.AppliedGravity.Normal * JumpStrength );\r\n            Renderer?.Set( \u0022b_jump\u0022, true );\r\n        }\r\n\r\n        if ( !Renderer.IsValid() ) return;\r\n\r\n        var oldX = Renderer.GetFloat( \u0022move_x\u0022 );\r\n        var oldY = Renderer.GetFloat( \u0022move_y\u0022 );\r\n\r\n        var velocity = Controller.Velocity;\r\n        var runScale = 1f / Renderer.Transform.World.UniformScale;\r\n        var moveX = Vector3.Dot( velocity, Renderer.WorldRotation.Forward ) * runScale;\r\n        var moveY = Vector3.Dot( velocity, Renderer.WorldRotation.Right ) * runScale;\r\n\r\n        var newX = MathX.Lerp( oldX, moveX, Time.Delta * 10f );\r\n        var newY = MathX.Lerp( oldY, moveY, Time.Delta * 10f );\r\n\r\n        Renderer.Set( \u0022move_x\u0022, newX );\r\n        Renderer.Set( \u0022move_y\u0022, newY );\r\n\r\n        Renderer.Set( \u0022b_grounded\u0022, Controller.IsOnGround );\r\n        Renderer.Set( \u0022duck\u0022, isDucking ? 1f : 0f );\r\n    }\r\n\r\n    protected override void OnUpdate()\r\n    {\r\n        base.OnUpdate();\r\n\r\n        EyeAngles \u002B= Input.AnalogLook;\r\n        EyeAngles = EyeAngles.WithPitch( MathX.Clamp( EyeAngles.pitch, -10f, 40f ) );\r\n        Renderer.LocalRotation = Rotation.FromYaw( EyeAngles.yaw );\r\n\r\n        var cameraOffset = Vector3.Up * 70f \u002B Vector3.Backward * 280f;\r\n        Camera.LocalRotation = EyeAngles.ToRotation();\r\n        Camera.LocalPosition = cameraOffset * Camera.LocalRotation;\r\n    }\r\n}\r\n"},{"Ident":"fish.scc","Path":"ShrimpleCharacterController.cs","FileName":"ShrimpleCharacterController.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":301969,"IsPrivate":false,"Code":"using System.Text.Json.Nodes;\r\n\r\nnamespace ShrimpleCharacterController;\r\n\r\n[Icon( \u0022nordic_walking\u0022 )]\r\npublic partial class ShrimpleCharacterController : Component, IScenePhysicsEvents, ISceneEvent\u003CIScenePhysicsEvents\u003E, Component.ICollisionListener\r\n{\r\n    /// \u003Csummary\u003E\r\n    /// Manually update this by calling Move() or let it always be simulated.\r\n    /// In physical mode, Move() drives the next physics step instead of running the trace-based path.\r\n    /// \u003C/summary\u003E\r\n    [Property]\r\n    [Group( \u0022Options\u0022 )]\r\n    public bool ManuallyUpdate { get; set; } = false;\r\n\r\n    [Property]\r\n    [FeatureEnabled( \u0022Physical\u0022 )]\r\n    public bool PhysicallySimulated\r\n    {\r\n        get;\r\n        set\r\n        {\r\n            field = value;\r\n\r\n            if ( value )\r\n                CreateBody();\r\n            else\r\n                DestroyBody();\r\n        }\r\n    }\r\n\r\n    private bool isPhysical( object _ ) =\u003E !PhysicallySimulated;\r\n\r\n    [Property]\r\n    [Hide]\r\n    public GameObject BodyObject { get; protected set; }\r\n\r\n    [Property]\r\n    [Feature( \u0022Physical\u0022 )]\r\n    [Header( \u0022Collider\u0022 )]\r\n    [Validate( nameof( isPhysical ), \u0022Physical mode enabled - controller can interact with and be pushed by physics objects\u0022, LogLevel.Info )]\r\n    public Surface ColliderSurface\r\n    {\r\n        get =\u003E Collider.IsValid() ? Collider.Surface : null;\r\n        set\r\n        {\r\n            if ( Collider.IsValid() )\r\n                Collider.Surface = value;\r\n        }\r\n    }\r\n\r\n    [Property]\r\n    [Feature( \u0022Physical\u0022 )]\r\n    public float? ColliderElasticity\r\n    {\r\n        get =\u003E Collider.IsValid() ? Collider.Elasticity : null;\r\n        set\r\n        {\r\n            if ( Collider.IsValid() )\r\n                Collider.Elasticity = value;\r\n        }\r\n    }\r\n\r\n    [Property]\r\n    [Feature( \u0022Physical\u0022 )]\r\n    [ShowIf( \u0022HidePhysicalComponents\u0022, false )]\r\n    public Collider Collider { get; protected set; }\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Kilos, tweak based on your surroundings, default boxes are like 600kg!\r\n    /// \u003C/summary\u003E\r\n    [Property]\r\n    [Feature( \u0022Physical\u0022 )]\r\n    [Header( \u0022Rigid Body\u0022 )]\r\n    public float BodyMassOverride\r\n    {\r\n        get;\r\n        set\r\n        {\r\n            field = value;\r\n\r\n            if ( Body.IsValid() )\r\n                Body.MassOverride = value;\r\n        }\r\n    } = 100f;\r\n\r\n    [Property]\r\n    [Feature( \u0022Physical\u0022 )]\r\n    public RigidbodyFlags BodyRigidbodyFlags\r\n    {\r\n        get =\u003E Body.IsValid() ? Body.RigidbodyFlags : 0;\r\n        set\r\n        {\r\n            if ( Body.IsValid() )\r\n                Body.RigidbodyFlags = value;\r\n        }\r\n    }\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Enable Continuous Collision Detection (CCD) for the physics body.\r\n    /// Useful for fast-moving characters to prevent tunneling through thin objects.\r\n    /// \u003C/summary\u003E\r\n    [Property]\r\n    [Feature( \u0022Physical\u0022 )]\r\n    [Title( \u0022Enable CCD\u0022 )]\r\n    public bool EnableCCD\r\n    {\r\n        get;\r\n        set\r\n        {\r\n            field = value;\r\n            if ( Body.IsValid() )\r\n                Body.EnhancedCcd = value;\r\n        }\r\n    }\r\n\r\n    [Property]\r\n    [Feature( \u0022Physical\u0022 )]\r\n    [ShowIf( \u0022HidePhysicalComponents\u0022, false )]\r\n    public Rigidbody Body { get; protected set; }\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Hide the \u003Csee cref=\u0022Body\u0022/\u003E and \u003Csee cref=\u0022Collider\u0022/\u003E components in the inspector\r\n    /// \u003C/summary\u003E\r\n    [Property]\r\n    [Feature( \u0022Physical\u0022 )]\r\n    [Title( \u0022Hide Components\u0022 )]\r\n    [Space]\r\n    [Validate( nameof( isPhysical ), \u0022Make sure to go over the other features to see any warning regarding physical simulation!\u0022, LogLevel.Warn )]\r\n    public bool HidePhysicalComponents\r\n    {\r\n        get;\r\n        protected set\r\n        {\r\n            field = value;\r\n\r\n            if ( value )\r\n            {\r\n                if ( Body.IsValid() )\r\n                    Body.Flags |= ComponentFlags.Hidden;\r\n                if ( Collider.IsValid() )\r\n                    Collider.Flags |= ComponentFlags.Hidden;\r\n            }\r\n            else\r\n            {\r\n                if ( Body.IsValid() )\r\n                    Body.Flags \u0026= ~ComponentFlags.Hidden;\r\n                if ( Collider.IsValid() )\r\n                    Collider.Flags \u0026= ~ComponentFlags.Hidden;\r\n            }\r\n        }\r\n    } = true;\r\n\r\n    [Feature( \u0022Physical\u0022 )]\r\n    [Button( \u0022Recreate Components\u0022, \u0022sync\u0022 )]\r\n    public void RefreshBody() =\u003E CreateBody( true );\r\n\r\n    /// \u003Csummary\u003E\r\n    /// If pushing against a wall, scale the velocity based on the wall\u0027s angle (False is useful for NPCs that get stuck on corners)\r\n    /// \u003C/summary\u003E\r\n    [Property]\r\n    [Group( \u0022Options\u0022 )]\r\n    public bool ScaleAgainstWalls { get; set; } = true;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Rotate the trace with the gameobject\r\n    /// \u003C/summary\u003E\r\n    [Property]\r\n    [Group( \u0022Trace\u0022 )]\r\n    [Validate( nameof( isPhysical ), \u0022Physical mode uses a capsule collider to prevent snagging on terrain.\u0022, LogLevel.Info )]\r\n    public bool RotateWithGameObject { get; set; } = true;\r\n\r\n    public enum TraceType\r\n    {\r\n        /// \u003Csummary\u003E\r\n        /// This has no drawback, enjoy!\r\n        /// \u003C/summary\u003E\r\n        [Icon( \u0022\uD83D\uDCE6\u0022 )]\r\n        Box,\r\n        /// \u003Csummary\u003E\r\n        /// This is a PHYSICAL TRACE, so it\u0027s more expensive than the normal box or sphere trace\r\n        /// \u003C/summary\u003E\r\n        [Icon( \u0022\uD83D\uDEE2\u0022 )]\r\n        Cylinder,\r\n        /// \u003Csummary\u003E\r\n        /// This will disable \u003Csee cref=\u0022StepsEnabled\u0022/\u003E as it\u0027s impossible to get the angle reliably\r\n        /// \u003C/summary\u003E\r\n        [Icon( \u0022\uD83C\uDFD0\u0022 )]\r\n        Sphere,\r\n        /// \u003Csummary\u003E\r\n        /// No drawbacks, box but you can define exact bounds\r\n        /// \u003C/summary\u003E\r\n        [Icon( \u0022\uD83E\uDDCA\u0022 )]\r\n        Bounds\r\n    }\r\n\r\n    [Property]\r\n    [Group( \u0022Trace\u0022 )]\r\n    [HideIf( nameof( PhysicallySimulated ), true )]\r\n    public TraceType TraceShape\r\n    {\r\n        get;\r\n        set\r\n        {\r\n            field = value;\r\n            RebuildBounds();\r\n            CreateCollider();\r\n        }\r\n    } = TraceType.Box;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Width of our trace\r\n    /// \u003C/summary\u003E\r\n    [Property]\r\n    [Group( \u0022Trace\u0022 )]\r\n    [ShowIf( nameof( _showWidthHeight ), true )]\r\n    [Range( 1f, 128f, false, true )]\r\n    [Sync]\r\n    public float TraceWidth\r\n    {\r\n        get;\r\n        set\r\n        {\r\n            field = value;\r\n            RebuildBounds();\r\n            UpdateCollider();\r\n        }\r\n    } = 16f;\r\n\r\n    // Show width/height if Physical mode OR if not using Bounds shape\r\n    bool _showWidthHeight =\u003E PhysicallySimulated || TraceShape != TraceType.Bounds;\r\n    // Show height if Physical mode OR if using Box/Cylinder shape\r\n    bool _showHeight =\u003E PhysicallySimulated || TraceShape == TraceType.Box || TraceShape == TraceType.Cylinder;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Height of our trace\r\n    /// \u003C/summary\u003E\r\n    [Property]\r\n    [Group( \u0022Trace\u0022 )]\r\n    [ShowIf( nameof( _showHeight ), true )]\r\n    [Range( 1f, 256f, false, true )]\r\n    [Sync]\r\n    public float TraceHeight\r\n    {\r\n        get;\r\n        set\r\n        {\r\n            field = value;\r\n            RebuildBounds();\r\n            UpdateCollider();\r\n        }\r\n    } = 72f;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Bounds of our trace\r\n    /// \u003C/summary\u003E\r\n    [Property]\r\n    [Group( \u0022Trace\u0022 )]\r\n    [ShowIf( \u0022TraceShape\u0022, TraceType.Bounds )]\r\n    [Sync]\r\n    public BBox TraceBounds\r\n    {\r\n        get =\u003E TraceShape == TraceType.Bounds ? field : Bounds;\r\n        set\r\n        {\r\n            field = value;\r\n            RebuildBounds();\r\n            UpdateCollider();\r\n        }\r\n    } = BBox.FromPositionAndSize( Vector3.Up * 36f, new Vector3( 32f, 32f, 72f ) );\r\n\r\n    private bool ignoreTagsAndPhysical( TagSet tagSet ) =\u003E tagSet.IsEmpty || !PhysicallySimulated;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Which tags it should ignore\r\n    /// \u003C/summary\u003E\r\n    [Property]\r\n    [Group( \u0022Trace\u0022 )]\r\n    [Validate( nameof( ignoreTagsAndPhysical ), \u0022Contoller is physical! Ignore tags must be set on the collision matrix\u0022, LogLevel.Warn )]\r\n    public TagSet IgnoreTags { get; set; } = new TagSet();\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Max amount of trace calls whenever the simulation doesn\u0027t reach its target (Slide and collide bounces)\r\n    /// \u003C/summary\u003E\r\n    [Property]\r\n    [Group( \u0022Trace\u0022 )]\r\n    [Range( 1, 20, true, true )]\r\n    public int MaxBounces { get; set; } = 5;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Acceleration and Deceleration\r\n    /// \u003C/summary\u003E\r\n    [FeatureEnabled( \u0022Acceleration\u0022 )]\r\n    [Property]\r\n    public bool AccelerationEnabled { get; set; } = true;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// How fast you accelerate while on the ground (Units per second)\r\n    /// \u003C/summary\u003E\r\n    [Property]\r\n    [Feature( \u0022Acceleration\u0022 )]\r\n    [Range( 0f, 3000f, false )]\r\n    public float GroundAcceleration { get; set; } = 1000f;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// How fast you accelerate while in the air (Units per second)\r\n    /// \u003C/summary\u003E\r\n    [Property]\r\n    [Feature( \u0022Acceleration\u0022 )]\r\n    [Range( 0f, 3000f, false )]\r\n    public float AirAcceleration { get; set; } = 300f;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Use the fixed acceleration value instead of curves\r\n    /// \u003C/summary\u003E\r\n    [Property]\r\n    [Feature( \u0022Acceleration\u0022 )]\r\n    public bool FixedAcceleration { get; set; } = true;\r\n\r\n    protected static readonly Curve DefaultAcceleration = new Curve( new List\u003CCurve.Frame\u003E\r\n    {\r\n        new Curve.Frame(0f, 0.2f, 0f, 1.5f),\r\n        new Curve.Frame(1f, 1f, 0f, 0f)\r\n    } )\r\n    {\r\n        TimeRange = new Vector2( 0f, 500f ),\r\n        ValueRange = new Vector2( 0f, 1f ),\r\n    };\r\n\r\n    /// \u003Csummary\u003E\r\n    /// How much acceleration based on the current velocity\u003Cbr/\u003E\r\n    /// X axis = Current Velocity (Maxes at 500 by default but you can modify)\u003Cbr/\u003E\r\n    /// Y axis *= Acceleration/\u003E\u003Cbr/\u003E\r\n    /// \u003C/summary\u003E\r\n    [Property]\r\n    [Feature( \u0022Acceleration\u0022 )]\r\n    [ValueRange( 0f, 1f, false )]\r\n    [HideIf( \u0022FixedAcceleration\u0022, true )]\r\n    public Curve AccelerationCurve { get; set; } = DefaultAcceleration;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// How fast you decelerate while on the ground (Units per second)\r\n    /// \u003C/summary\u003E\r\n    [Property]\r\n    [Feature( \u0022Acceleration\u0022 )]\r\n    [Range( 0f, 3000f, false )]\r\n    public float GroundDeceleration { get; set; } = 1500f;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// How fast you decelerate while in the air (Units per second)\r\n    /// \u003C/summary\u003E\r\n    [Property]\r\n    [Feature( \u0022Acceleration\u0022 )]\r\n    [Range( 0f, 3000f, false )]\r\n    public float AirDeceleration { get; set; } = 0f;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Use the fixed deceleration value instead of curves\r\n    /// \u003C/summary\u003E\r\n    [Property]\r\n    [Feature( \u0022Acceleration\u0022 )]\r\n    public bool FixedDeceleration { get; set; } = true;\r\n\r\n    protected static readonly Curve DefaultDeceleration = new Curve( new List\u003CCurve.Frame\u003E\r\n    {\r\n        new Curve.Frame(0f, 1f, 0f, -2.5f),\r\n        new Curve.Frame(1f, 0.2f, 0f, 0f)\r\n    } )\r\n    {\r\n        TimeRange = new Vector2( 0f, 500f ),\r\n        ValueRange = new Vector2( 0f, 1f ),\r\n    };\r\n\r\n    /// \u003Csummary\u003E\r\n    /// How much deceleration based on the current velocity\u003Cbr/\u003E\r\n    /// X axis = Current Velocity (Maxes at 500 by default but you can modify)\u003Cbr/\u003E\r\n    /// Y axis *= Deceleration/\u003E\u003Cbr/\u003E\r\n    /// \u003C/summary\u003E\r\n    [Property]\r\n    [Feature( \u0022Acceleration\u0022 )]\r\n    [ValueRange( 0f, 1f, false )]\r\n    [HideIf( \u0022FixedDeceleration\u0022, true )]\r\n    public Curve DecelerationCurve { get; set; } = DefaultDeceleration;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Do we ignore the friction of the surface you\u0027re standing on or not?\r\n    /// \u003C/summary\u003E\r\n    [Property]\r\n    [Feature( \u0022Acceleration\u0022 )]\r\n    public bool IgnoreGroundSurface { get; set; } = false;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Use Time.Delta as the time step when automatically simulating (disable to set a fixed TimeDelta)\r\n    /// \u003C/summary\u003E\r\n    [Property]\r\n    [Group( \u0022Options\u0022 )]\r\n    [HideIf( nameof( ManuallyUpdate ), true )]\r\n    public bool UseTimeDelta { get; set; } = true;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Fixed time step used when automatically simulating and UseTimeDelta is disabled\r\n    /// \u003C/summary\u003E\r\n    [Property]\r\n    [Group( \u0022Options\u0022 )]\r\n    [ShowIf( nameof( _showTimeDelta ), true )]\r\n    public float TimeDelta { get; set; } = 1f / 60f;\r\n\r\n    private bool _showTimeDelta =\u003E !ManuallyUpdate \u0026\u0026 !UseTimeDelta;\r\n    private float ActiveAutoTimeDelta =\u003E UseTimeDelta ? Time.Delta : TimeDelta;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Is this MoveHelper meant for horizontal grounded movement? (false = For flying or noclip)\r\n    /// \u003C/summary\u003E\r\n    [Property]\r\n    [Group( \u0022Movement\u0022 )]\r\n    public bool IgnoreZ { get; set; } = true;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Do we ignore Z when it\u0027s near 0 (So that gravity affects you when not moving)\r\n    /// \u003C/summary\u003E\r\n    [Property]\r\n    [Title( \u0022Ignore Z When Zero\u0022 )]\r\n    [Group( \u0022Movement\u0022 )]\r\n    [HideIf( \u0022IgnoreZ\u0022, true )]\r\n    public bool IgnoreZWhenZero { get; set; } = true;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Tolerance from a 90\u00B0 surface before it\u0027s considered a wall (Ex. Tolerance 1 = Between 89\u00B0 and 91\u00B0 can be a wall, 0.1 = 89.9\u00B0 to 90.1\u00B0)\r\n    /// \u003C/summary\u003E\r\n    [Group( \u0022Movement\u0022 )]\r\n    [Property]\r\n    [Range( 0f, 10f, false )]\r\n    public float WallTolerance { get; set; } = 1f;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Player feels like it\u0027s gripping walls too much? Try more Grip Factor Reduction!\r\n    /// \u003C/summary\u003E\r\n    [Group( \u0022Movement\u0022 )]\r\n    [Property]\r\n    [Range( 1f, 10f, true )]\r\n    [Validate( nameof( isPhysical ), \u0022Physical mode uses collider friction for wall interaction\u0022, LogLevel.Info )]\r\n    public float GripFactorReduction { get; set; } = 1f;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Bouncing off walls and ground on collision\r\n    /// \u003C/summary\u003E\r\n    [FeatureEnabled( \u0022Bouncing\u0022 )]\r\n    [Property]\r\n    public bool BouncingEnabled { get; set; } = false;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// How much the MoveHelper will \u0022bounce\u0022 off walls when colliding with them\u003Cbr/\u003E\r\n    /// (0f = No bounce, 1f = Full bounce)\u003Cbr/\u003E\r\n    /// \u003C/summary\u003E\r\n    [Property]\r\n    [Feature( \u0022Bouncing\u0022 )]\r\n    [Range( 0f, 1f, true )]\r\n    [Validate( nameof( isPhysical ), \u0022Contoller is physical! Elasticity must be done through the collider\u0022, LogLevel.Error )]\r\n    public float HorizontalElasticity { get; set; } = 0f;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// How much the MoveHelper will \u0022bounce\u0022 off the ground when colliding with it\u003Cbr/\u003E\r\n    /// (0f = No bounce, 1f = Full bounce)\u003Cbr/\u003E\r\n    /// \u003Csee cref=\u0022GroundStickEnabled\u0022/\u003E will override this value when sticking to the ground\u003Cbr/\u003E\r\n    /// \u003C/summary\u003E\r\n    [Property]\r\n    [Feature( \u0022Bouncing\u0022 )]\r\n    [Range( 0f, 1f, true )]\r\n    public float VerticalElasticity { get; set; } = 0f;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Include the ground elasticity when bouncing off the ground\u003Cbr/\u003E\r\n    /// Make sure the ground surface doesn\u0027t have a very low elasticity!\r\n    /// \u003C/summary\u003E\r\n    [Property]\r\n    [Feature( \u0022Bouncing\u0022 )]\r\n    public bool IncludeGroundElasticity { get; set; } = false;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Minimum velocity to allow bouncing\r\n    /// \u003C/summary\u003E\r\n    [Property]\r\n    [Feature( \u0022Bouncing\u0022 )]\r\n    [Range( 0f, 100f, true )]\r\n    public float ElasticityThreshold { get; set; } = 30f;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Stick the MoveHelper to the ground (IsOnGround will default to false if disabled)\r\n    /// \u003C/summary\u003E\r\n    [FeatureEnabled( \u0022GroundStick\u0022 )]\r\n    [Property]\r\n    public bool GroundStickEnabled { get; set; } = true;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// How steep terrain can be for you to stand on without slipping.\u003Cbr/\u003E\r\n    /// Min = Angle where velocity starts to drop off\u003Cbr/\u003E\r\n    /// Max = Max angle where velocity is fully blocked (you can still slide down with gravity)\r\n    /// \u003C/summary\u003E\r\n    [Property, Step( 0.1f )]\r\n    [Feature( \u0022GroundStick\u0022 )]\r\n    public RangedFloat MaxGroundAngle\r\n    {\r\n        get;\r\n        set\r\n        {\r\n            if ( value.Range == RangedFloat.RangeType.Between )\r\n            {\r\n                var min = MathX.Clamp( value.Min, 0f, 89f );\r\n                var max = MathX.Clamp( value.Max, 0f, 89f );\r\n\r\n                field = new RangedFloat( MathF.Min( min, max ), MathF.Max( min, max ) );\r\n            }\r\n            else\r\n            {\r\n                field = new RangedFloat( MathX.Clamp( value.FixedValue, 0f, 89f ) );\r\n            }\r\n        }\r\n    } = new RangedFloat( 30f, 60f );\r\n\r\n    /// \u003Csummary\u003E\r\n    /// How far from the ground the MoveHelper is going to stick (Useful for going down stairs!)\r\n    /// \u003C/summary\u003E\r\n    [Property]\r\n    [Feature( \u0022GroundStick\u0022 )]\r\n    [Range( 1f, 32f, false )]\r\n    public float GroundStickDistance { get; set; } = 12f;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Stick to any \u003Csee cref=\u0022GroundObject\u0022/\u003E when standing on it\r\n    /// \u003C/summary\u003E\r\n    [Property]\r\n    [Feature( \u0022GroundStick\u0022 )]\r\n    public bool StickToPlatforms { get; set; } = true;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// When the surface you\u0027re on has a surface velocity, apply it to the controller\r\n    /// \u003C/summary\u003E\r\n    [Property]\r\n    [Feature( \u0022GroundStick\u0022 )]\r\n    public bool ApplySurfaceVelocity { get; set; } = true;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// When the surface you\u0027re on is slippery, scale the max ground angled relative to the friction\u003Cbr/\u003E\r\n    /// Example: Ice with friction 0.5f, MaxGroundAngle will be 30f instead of 60f\r\n    /// \u003C/summary\u003E\r\n    [Property]\r\n    [Feature( \u0022GroundStick\u0022 )]\r\n    public bool ScaleGroundAngleBySurfaceFriction { get; set; } = true;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Enable steps climbing (\u002B1 Trace call)\r\n    /// \u003C/summary\u003E\r\n    [FeatureEnabled( \u0022Steps\u0022 )]\r\n    [Property]\r\n    public bool StepsEnabled { get; set; } = true;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// How high steps can be for you to climb on\r\n    /// \u003C/summary\u003E\r\n    [Feature( \u0022Steps\u0022 )]\r\n    [Property]\r\n    [Range( 1f, 32f, false )]\r\n    public float StepHeight { get; set; } = 12f;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// How deep it checks for steps (Minimum depth)\r\n    /// \u003C/summary\u003E\r\n    [Feature( \u0022Steps\u0022 )]\r\n    [Property]\r\n    [Range( 0.1f, 8f, false )]\r\n    public float StepDepth { get; set; } = 2f;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Tolerance from a 90\u00B0 surface before it\u0027s considered a valid step (Ex. Tolerance 1 = Between 89\u00B0 and 91\u00B0 can be a step, 0.1 = 89.9\u00B0 to 90.1\u00B0)\r\n    /// \u003C/summary\u003E\r\n    [Feature( \u0022Steps\u0022 )]\r\n    [Property]\r\n    [Range( 0f, 10f, false )]\r\n    public float StepTolerance { get; set; } = 1f;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Enable to ability to walk on a surface that\u0027s too steep if it\u0027s equal or smaller than a step (\u002B1 Trace call when on steep terrain)\r\n    /// \u003C/summary\u003E\r\n    [Feature( \u0022Steps\u0022 )]\r\n    [Property]\r\n    public bool PseudoStepsEnabled { get; set; } = true;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Instead of colliding with these tags the MoveHelper will be pushed away (Make sure the tags are in IgnoreTags as well!)\r\n    /// \u003C/summary\u003E\r\n    [FeatureEnabled( \u0022Push\u0022 )]\r\n    [Property]\r\n    public bool PushEnabled { get; set; } = false;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Which tags will push this MoveHelper away and with how much force (Make sure they are also included in IgnoreTags!) (\u002B1 Trace call)\r\n    /// \u003C/summary\u003E\r\n    [Property]\r\n    [Feature( \u0022Push\u0022 )]\r\n    [Validate( nameof( isPhysical ), \u0022Contoller is physical! Make sure the tags are ignored on the collision matrix\u0022, LogLevel.Warn )]\r\n    [Sync]\r\n    public Dictionary\u003Cstring, float\u003E PushTagsWeight\r\n    {\r\n        get;\r\n        set\r\n        {\r\n            field = value;\r\n            _pushTags = BuildPushTags();\r\n        }\r\n    } = new Dictionary\u003Cstring, float\u003E() { { \u0022player\u0022, 1f } };\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Apply gravity to this MoveHelper when not on the ground\r\n    /// \u003C/summary\u003E\r\n    [FeatureEnabled( \u0022Gravity\u0022 )]\r\n    [Property]\r\n    public bool GravityEnabled { get; set; } = true;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Use the scene\u0027s gravity or our own\r\n    /// \u003C/summary\u003E\r\n    [Property]\r\n    [Feature( \u0022Gravity\u0022 )]\r\n    public bool UseSceneGravity\r\n    {\r\n        get;\r\n        set\r\n        {\r\n            field = value;\r\n            BuildGravity();\r\n        }\r\n    } = true;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Use a Vector3 gravity instead of a single float (Use this if you want to use a custom gravity)\r\n    /// \u003C/summary\u003E\r\n    [Property]\r\n    [Feature( \u0022Gravity\u0022 )]\r\n    [HideIf( \u0022UseSceneGravity\u0022, true )]\r\n    public bool UseVectorGravity\r\n    {\r\n        get;\r\n        set\r\n        {\r\n            field = value;\r\n            BuildGravity();\r\n        }\r\n    }\r\n\r\n    private bool _usingFloatGravity =\u003E !UseVectorGravity \u0026\u0026 !UseSceneGravity;\r\n    private bool _usingVectorGravity =\u003E UseVectorGravity \u0026\u0026 !UseSceneGravity;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Units per second squared (Default is -850f)\r\n    /// \u003C/summary\u003E\r\n    [Property]\r\n    [Feature( \u0022Gravity\u0022 )]\r\n    [Range( -2000, 2000, false )]\r\n    [ShowIf( \u0022_usingFloatGravity\u0022, true )]\r\n    public float Gravity\r\n    {\r\n        get;\r\n        set\r\n        {\r\n            field = value;\r\n            BuildGravity();\r\n        }\r\n    } = -850f;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Units per second squared (Default is 0f, 0f, -850f)\u003Cbr/\u003E\r\n    /// Changes which way \u003Csee cref=\u0022GroundStickEnabled\u0022/\u003E sticks to the ground\r\n    /// \u003C/summary\u003E\r\n    [Property]\r\n    [Feature( \u0022Gravity\u0022 )]\r\n    [ShowIf( \u0022_usingVectorGravity\u0022, true )]\r\n    public Vector3 VectorGravity\r\n    {\r\n        get;\r\n        set\r\n        {\r\n            field = value;\r\n            BuildGravity();\r\n        }\r\n    } = new Vector3( 0f, 0f, -850f );\r\n\r\n    private Vector3 _appliedGravity;\r\n    public Vector3 GravityNormal { get; private set; }\r\n    public Vector3 AppliedGravity =\u003E _appliedGravity;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Check if the MoveHelper is stuck and try to get it to unstuck (\u002BTrace calls if stuck)\r\n    /// \u003C/summary\u003E\r\n    [FeatureEnabled( \u0022Unstuck\u0022 )]\r\n    [Property]\r\n    public bool UnstuckEnabled { get; set; } = true;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// How many trace calls it will attempt to get the MoveHelper unstuck\r\n    /// \u003C/summary\u003E\r\n    [Property]\r\n    [Feature( \u0022Unstuck\u0022 )]\r\n    [Range( 1, 100, false )]\r\n    [Step( 1f )]\r\n    public int MaxUnstuckTries { get; set; } = 40;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// The simulated target velocity for our MoveHelper (Units per second, we apply Time.Delta inside)\r\n    /// \u003C/summary\u003E\r\n    [Sync] public Vector3 WishVelocity { get; set; }\r\n\r\n    /// \u003Csummary\u003E\r\n    /// The resulting velocity after the simulation is done (Units per second)\r\n    /// \u003C/summary\u003E\r\n    [Sync]\r\n    public Vector3 Velocity\r\n    {\r\n        get;\r\n        set\r\n        {\r\n            if ( PhysicallySimulated \u0026\u0026 Body.IsValid() )\r\n                Body.Velocity = value;\r\n\r\n            field = value;\r\n        }\r\n    }\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Velocity controlled by outside factors, such as knockback, rootmotion, etc.\r\n    /// It is only applied to our final position and doesn\u0027t affect our Velocity.\r\n    /// It should be handled outside the controller.\r\n    /// \u003C/summary\u003E\r\n    [Sync] public Vector3 ExternalVelocity { get; set; }\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Is the MoveHelper currently touching the ground\r\n    /// \u003C/summary\u003E\r\n    [Sync] public bool IsOnGround { get; set; }\r\n\r\n    /// \u003Csummary\u003E\r\n    /// The current ground normal you\u0027re standing on (Always Vector3.Zero if IsOnGround false)\r\n    /// \u003C/summary\u003E\r\n    public Vector3 GroundNormal { get; private set; } = Vector3.Zero;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// The current ground angle you\u0027re standing on (Always 0f if IsOnGround false)\r\n    /// \u003C/summary\u003E\r\n    public float GroundAngle =\u003E Vector3.GetAngle( GroundNormal, -GravityNormal );\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Gets the velocity multiplier for a given slope angle based on MaxGroundAngle range.\u003Cbr/\u003E\r\n    /// Returns 1.0 below the start angle, 0.0 at/above the max angle, and lerps between.\r\n    /// \u003C/summary\u003E\r\n    public float GetSlopeVelocityMultiplier( float angle )\r\n    {\r\n        if ( angle \u003C= MaxGroundAngle.Min ) return 1f;\r\n        if ( angle \u003E= MaxGroundAngle.Max ) return 0f;\r\n        return 1f - MathX.LerpInverse( angle, MaxGroundAngle.Min, MaxGroundAngle.Max );\r\n    }\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Whether the given angle is considered standable (below max ground angle)\r\n    /// \u003C/summary\u003E\r\n    public bool IsAngleStandable( float angle ) =\u003E angle \u003C= MaxGroundAngle.Max * ( ScaleGroundAngleBySurfaceFriction ? GroundSurface?.Friction ?? 1f : 1f );\r\n\r\n    /// \u003Csummary\u003E\r\n    /// The current surface you\u0027re standing on\r\n    /// \u003C/summary\u003E\r\n    public Surface GroundSurface { get; private set; }\r\n\r\n    /// \u003Csummary\u003E\r\n    /// The gameobject you\u0027re currently standing on\r\n    /// \u003C/summary\u003E\r\n    public GameObject GroundObject { get; set; }\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Is the MoveHelper currently pushing against a wall\r\n    /// \u003C/summary\u003E\r\n    public bool IsPushingAgainstWall { get; private set; }\r\n\r\n    /// \u003Csummary\u003E\r\n    /// The current wall normal you\u0027re pushing against (Always Vector3.Zero if IsPushingAgainstWall false)\r\n    /// \u003C/summary\u003E\r\n    public Vector3 WallNormal { get; private set; } = Vector3.Zero;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// The gameobject you\u0027re currently pushing on\r\n    /// \u003C/summary\u003E\r\n    public GameObject WallObject { get; set; }\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Is the MoveHelper standing on a terrain too steep to stand on (Always false if IsOnGround false)\r\n    /// \u003C/summary\u003E\r\n    [Sync] public bool IsSlipping { get; private set; } // TODO IMPLEMENT\r\n\r\n    /// \u003Csummary\u003E\r\n    /// The MoveHelper is stuck and we can\u0027t get it out\r\n    /// \u003C/summary\u003E\r\n    [Sync] public bool IsStuck { get; private set; }\r\n\r\n    /// \u003Csummary\u003E\r\n    /// To avoid getting stuck due to imprecision we shrink the bounds before checking and compensate for it later\r\n    /// \u003C/summary\u003E\r\n    public float SkinWidth =\u003E Math.Min( Math.Max( 0.1f, TraceWidth * 0.05f ), GroundStickDistance );\r\n\r\n    public float AppliedWidth =\u003E TraceWidth * WorldScale.x; // The width of the MoveHelper in world units\r\n    public float AppliedDepth =\u003E TraceWidth * WorldScale.y; // The depth of the MoveHelper in world units\r\n    public float AppliedHeight =\u003E TraceShape == TraceType.Sphere ? AppliedWidth :\r\n        TraceShape == TraceType.Bounds ? TraceBounds.Size.z * WorldScale.z : TraceHeight * WorldScale.z; // The height of the MoveHelper in world units\r\n    private Vector3 _upAxis =\u003E RotateWithGameObject ? WorldRotation.Up : Vector3.Up;\r\n    private Vector3 _offset =\u003E ( RotateWithGameObject ? WorldRotation.Up : Vector3.Up ) * ( TraceShape == TraceType.Sphere || TraceShape == TraceType.Bounds ? 0f : AppliedHeight / 2f ); // The position of the MoveHelper in world units\r\n\r\n    /// \u003Csummary\u003E\r\n    /// The bounds of this MoveHelper generated from the TraceWidth and TraceHeight\r\n    /// \u003C/summary\u003E\r\n    public BBox Bounds { get; set; }\r\n    private BBox _shrunkenBounds;\r\n    private string[] _pushTags;\r\n    private Vector3 _lastVelocity;\r\n    private float _minimumTolerance =\u003E MathX.Clamp( Time.Delta / 2f, 0.005f, 0.1f ); // Floating precision tolerance, too high if used inside of OnUpdate so tied to update rate\r\n\r\n    /// \u003Csummary\u003E\r\n    /// If another MoveHelper moved at the same time and they\u0027re stuck, let this one know that the other already unstuck for us\r\n    /// \u003C/summary\u003E\r\n    public ShrimpleCharacterController UnstuckTarget;\r\n\r\n    public Action\u003CShrimpleCollisionResult\u003E OnCollide { get; set; }\r\n    public bool IsOnPlatform =\u003E IsOnGround \u0026\u0026 GroundStickEnabled \u0026\u0026 !IsSlipping \u0026\u0026 StickToPlatforms \u0026\u0026 GroundObject.IsValid();\r\n    public Vector3 PlatformVelocity =\u003E IsOnPlatform ? GroundObject.GetComponent\u003CCollider\u003E()?.GetVelocityAtPoint( WorldPosition ) ?? Vector3.Zero : Vector3.Zero;\r\n    public bool IsOnSurfaceWithVelocity =\u003E IsOnGround \u0026\u0026 GroundStickEnabled \u0026\u0026 !IsSlipping \u0026\u0026 ApplySurfaceVelocity \u0026\u0026 GroundObject.IsValid();\r\n    public Vector3 SurfaceVelocity =\u003E IsOnSurfaceWithVelocity ? GroundObject.GetComponent\u003CCollider\u003E()?.SurfaceVelocity * GroundObject.WorldRotation ?? Vector3.Zero : Vector3.Zero;\r\n    public Vector3 GroundVelocity =\u003E PlatformVelocity \u002B SurfaceVelocity;\r\n\r\n    protected override void OnStart()\r\n    {\r\n        RebuildBounds();\r\n        BuildGravity();\r\n        _pushTags = BuildPushTags();\r\n    }\r\n\r\n    protected override void DrawGizmos()\r\n    {\r\n        if ( Gizmo.IsSelected )\r\n        {\r\n            Gizmo.GizmoDraw draw = Gizmo.Draw;\r\n            draw.Color = Color.Blue;\r\n\r\n            if ( TraceShape == TraceType.Box )\r\n                draw.LineBBox( Bounds.Translate( Vector3.Up * Bounds.Size.z / 2f ) );\r\n            if ( TraceShape == TraceType.Cylinder )\r\n                draw.LineCylinder( Vector3.Zero, Vector3.Up * ( Bounds.Maxs.z - Bounds.Mins.z ), Bounds.Maxs.x, Bounds.Maxs.x, 24 );\r\n            if ( TraceShape == TraceType.Sphere )\r\n                draw.LineSphere( Vector3.Up * Bounds.Maxs.x, Bounds.Maxs.x );\r\n            if ( TraceShape == TraceType.Bounds )\r\n                draw.LineBBox( TraceBounds );\r\n\r\n            if ( !UseSceneGravity \u0026\u0026 UseVectorGravity )\r\n            {\r\n                draw.Color = Color.Red;\r\n                draw.LineThickness = 5f;\r\n                draw.IgnoreDepth = true;\r\n                draw.Arrow( Vector3.Zero, GravityNormal * 50f );\r\n            }\r\n        }\r\n    }\r\n\r\n    private BBox BuildBounds()\r\n    {\r\n        if ( TraceShape == TraceType.Bounds )\r\n            return new BBox( TraceBounds.Mins * GameObject.WorldScale, TraceBounds.Maxs * GameObject.WorldScale );\r\n\r\n        var x = GameObject.WorldScale.x;\r\n        var y = GameObject.WorldScale.y;\r\n        var z = GameObject.WorldScale.z;\r\n\r\n        var width = TraceWidth / 2f * x;\r\n        var depth = TraceWidth / 2f * y;\r\n        var halfHeight = TraceHeight / 2f * z;\r\n\r\n        // For cylinders we want the bounds to start at Z = 0 and extend up to the full height\r\n        if ( TraceShape == TraceType.Cylinder )\r\n            return new BBox( new Vector3( -width, -depth, 0f ), new Vector3( width, depth, halfHeight * 2f ) );\r\n\r\n        return new BBox( new Vector3( -width, -depth, -halfHeight ), new Vector3( width, depth, halfHeight ) );\r\n    }\r\n\r\n    private void RebuildBounds()\r\n    {\r\n        Bounds = BuildBounds();\r\n        _shrunkenBounds = Bounds.Grow( -SkinWidth );\r\n    }\r\n\r\n    private void BuildGravity()\r\n    {\r\n        _appliedGravity = UseSceneGravity ? Scene.PhysicsWorld.Gravity : UseVectorGravity ? VectorGravity : new Vector3( 0f, 0f, Gravity );\r\n        GravityNormal = _appliedGravity.Normal;\r\n    }\r\n\r\n    private string[] BuildPushTags()\r\n    {\r\n        return PushTagsWeight.Keys.ToArray();\r\n    }\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Casts the current bounds from to and returns the scene trace result\r\n    /// \u003C/summary\u003E\r\n    /// \u003Cparam name=\u0022bounds\u0022\u003E\u003C/param\u003E\r\n    /// \u003Cparam name=\u0022from\u0022\u003E\u003C/param\u003E\r\n    /// \u003Cparam name=\u0022to\u0022\u003E\u003C/param\u003E\r\n    /// \u003Cparam name=\u0022useCapsule\u0022\u003EUse a capsule shape instead of the defaults\u003C/param\u003E\r\n    /// \u003Creturns\u003E\u003C/returns\u003E\r\n    public SceneTraceResult BuildTrace( BBox bounds, Vector3 from, Vector3 to, bool useCapsule = false )\r\n    {\r\n        SceneTrace builder = new SceneTrace(); // Empty trace builder\r\n\r\n        if ( useCapsule )\r\n        {\r\n            var radius = bounds.Maxs.x;\r\n            var halfHeight = ( bounds.Maxs.z - bounds.Mins.z ) / 2f;\r\n            var hemiOffset = MathF.Max( halfHeight - radius, 0f );\r\n            var capsule = new Capsule( Vector3.Down * hemiOffset, Vector3.Up * hemiOffset, radius ); // Do not rotate Vector3, it\u0027s rotated 17 lines down!\r\n            builder = Game.SceneTrace.Capsule( capsule, from, to );\r\n        }\r\n        else\r\n        {\r\n            if ( TraceShape == TraceType.Box || TraceShape == TraceType.Bounds )\r\n                builder = Game.SceneTrace.Box( bounds, from, to );\r\n            if ( TraceShape == TraceType.Cylinder )\r\n                builder = Game.SceneTrace.Cylinder( bounds.Maxs.z - bounds.Mins.z, bounds.Maxs.x, from, to );\r\n            if ( TraceShape == TraceType.Sphere )\r\n                builder = Game.SceneTrace.Sphere( bounds.Maxs.x, from \u002B Vector3.Up * bounds.Maxs.x, to \u002B Vector3.Up * bounds.Maxs.x );\r\n        }\r\n\r\n        builder = builder\r\n            .IgnoreGameObjectHierarchy( GameObject )\r\n            .WithoutTags( IgnoreTags );\r\n\r\n        if ( RotateWithGameObject )\r\n            builder = builder.Rotated( PhysicallySimulated ? Collider.WorldRotation : GameObject.WorldRotation );\r\n\r\n        return builder.Run();\r\n    }\r\n\r\n    private SceneTraceResult BuildPushTrace( BBox bounds, Vector3 from, Vector3 to )\r\n    {\r\n        SceneTrace builder = new SceneTrace(); // Empty trace builder\r\n\r\n        if ( TraceShape == TraceType.Box || TraceShape == TraceType.Bounds )\r\n            builder = Game.SceneTrace.Box( bounds, from, to );\r\n        if ( TraceShape == TraceType.Cylinder )\r\n            builder = Game.SceneTrace.Cylinder( bounds.Maxs.z - bounds.Mins.z, bounds.Maxs.x, from, to );\r\n        if ( TraceShape == TraceType.Sphere )\r\n            builder = Game.SceneTrace.Sphere( bounds.Maxs.x, from, to );\r\n\r\n        builder = builder\r\n            .IgnoreGameObjectHierarchy( GameObject )\r\n            .WithAnyTags( _pushTags ); // Check for only the push tags\r\n\r\n        if ( RotateWithGameObject )\r\n            builder = builder.Rotated( GameObject.WorldRotation );\r\n\r\n        return builder.Run();\r\n    }\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Detach the MoveHelper from the ground and launch it somewhere (Units per second)\r\n    /// \u003C/summary\u003E\r\n    /// \u003Cparam name=\u0022amount\u0022\u003E\u003C/param\u003E\r\n    public void Punch( in Vector3 amount )\r\n    {\r\n        var groundVel = GroundVelocity; // Capture before ungrounding\r\n        IsOnGround = false;\r\n\r\n        if ( PhysicallySimulated \u0026\u0026 Body.IsValid() )\r\n            Body.Velocity = Body.Velocity \u002B amount \u002B groundVel;\r\n\r\n        Velocity \u002B= amount \u002B groundVel;\r\n    }\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Apply the WishVelocity, update the Velocity and the Position of the GameObject by simulating the MoveHelper\r\n    /// \u003C/summary\u003E\r\n    /// \u003Cparam name=\u0022manualUpdate\u0022\u003EJust calculate but don\u0027t update position\u003C/param\u003E\r\n    public MoveHelperResult Move( bool manualUpdate = false ) =\u003E Move( Time.Delta, manualUpdate );\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Apply the WishVelocity, update the Velocity and the Position of the GameObject by simulating the MoveHelper\r\n    /// \u003C/summary\u003E\r\n    /// \u003Cparam name=\u0022delta\u0022\u003EThe time step\u003C/param\u003E\r\n    /// \u003Cparam name=\u0022manualUpdate\u0022\u003EJust calculate but don\u0027t update position\u003C/param\u003E\r\n    public MoveHelperResult Move( float delta, bool manualUpdate = false )\r\n    {\r\n        // In physical mode the simulation runs inside the physics step callbacks.\r\n        // Queue this call so the next PrePhysicsStep/PostPhysicsStep drives the body with the requested delta.\r\n        if ( PhysicallySimulated )\r\n        {\r\n            if ( Body.IsValid() \u0026\u0026 !manualUpdate )\r\n            {\r\n                _physicalMoveDelta = delta;\r\n                _physicalMovePending = true;\r\n            }\r\n\r\n            return new MoveHelperResult( WorldPosition, Velocity );\r\n        }\r\n\r\n        var goalVelocity = CalculateGoalVelocity( delta ); // Calculate the goal velocity using our Acceleration and Deceleration values\r\n\r\n        // SIMULATE PUSH FORCES //\r\n        if ( PushEnabled )\r\n        {\r\n            var pushTrace = BuildPushTrace( Bounds, WorldPosition \u002B _offset, WorldPosition ); // Build a trace but using the Push tags instead of the Ignore tags\r\n\r\n            if ( pushTrace.Hit ) // We\u0027re inside any of the push tags\r\n            {\r\n                foreach ( var tag in pushTrace.GameObject.Tags )\r\n                {\r\n                    if ( PushTagsWeight.TryGetValue( tag, out var tagWeight ) )\r\n                    {\r\n                        var otherPosition = pushTrace.GameObject.WorldPosition.WithZ( WorldPosition.z ); // Only horizontal pushing\r\n                        var pushDirection = ( otherPosition - WorldPosition ).Normal;\r\n                        var pushVelocity = pushDirection * tagWeight * 50f; // I find 50 u/s to be a good amount to push if the weight is 1.0 (!!!)\r\n\r\n                        goalVelocity -= pushVelocity;\r\n                    }\r\n                }\r\n            }\r\n        }\r\n\r\n        var finalPosition = WorldPosition;\r\n\r\n        var moveHelperResult = CollideAndSlide( goalVelocity, finalPosition \u002B _offset, delta ); // Simulate the MoveHelper\r\n\r\n        finalPosition = moveHelperResult.Position;\r\n        var finalVelocity = moveHelperResult.Velocity;\r\n        // SIMULATE GRAVITY //\r\n        if ( GravityEnabled \u0026\u0026 Gravity != 0f )\r\n        {\r\n            if ( !IsOnGround || IsSlipping || !GroundStickEnabled )\r\n            {\r\n                var gravity = AppliedGravity * delta;\r\n                var gravityResult = CollideAndSlide( gravity, moveHelperResult.Position, delta, gravityPass: true ); // Apply and simulate the gravity step\r\n\r\n                finalPosition = gravityResult.Position;\r\n                finalVelocity \u002B= gravityResult.Velocity;\r\n            }\r\n        }\r\n\r\n        if ( !ExternalVelocity.IsNearZeroLength )\r\n        {\r\n            moveHelperResult = CollideAndSlide( ExternalVelocity, finalPosition, delta );\r\n        }\r\n\r\n        finalPosition = moveHelperResult.Position - _offset; // Compensate for the offset we added at the beginning\r\n        _lastVelocity = Velocity * delta;\r\n\r\n        if ( !manualUpdate )\r\n        {\r\n            Velocity = finalVelocity;\r\n            WorldPosition = finalPosition \u002B SurfaceVelocity * delta; // Actually updating the position is \u0022expensive\u0022 so we only do it once at the end\r\n        }\r\n\r\n        return new MoveHelperResult( finalPosition, finalVelocity, moveHelperResult.Offset );\r\n    }\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Sometimes we have to update only the position but not the velocity (Like when climbing steps or getting unstuck) so we can\u0027t have Position rely only on Velocity\r\n    /// \u003C/summary\u003E\r\n    public struct MoveHelperResult\r\n    {\r\n        public Vector3 Position;\r\n        public Vector3 Velocity;\r\n        internal Vector3 Offset;\r\n        internal float Leftover = 1f;\r\n\r\n        public MoveHelperResult( Vector3 position, Vector3 velocity )\r\n        {\r\n            Position = position;\r\n            Velocity = velocity;\r\n        }\r\n\r\n        internal MoveHelperResult( Vector3 position, Vector3 velocity, Vector3 offset, float leftover = 1f )\r\n        {\r\n            Position = position;\r\n            Velocity = velocity;\r\n            Offset = offset;\r\n            Leftover = leftover;\r\n        }\r\n    }\r\n\r\n    bool _bounced = false;\r\n\r\n    private MoveHelperResult CollideAndSlide( Vector3 velocity, Vector3 position, float delta, int depth = 0, bool gravityPass = false ) =\u003E\r\n        CollideAndSlide( new MoveHelperResult( position, velocity ), delta, depth, gravityPass );\r\n\r\n    private MoveHelperResult CollideAndSlide( MoveHelperResult current, float delta, int depth = 0, bool gravityPass = false )\r\n    {\r\n        if ( depth \u003E= MaxBounces )\r\n            return current;\r\n\r\n        var velocity = current.Velocity * delta; // I like to set Velocity as units/second but we have to deal with units/tick here\r\n        var position = current.Position;\r\n\r\n        // GROUND AND UNSTUCK CHECK //\r\n        if ( depth == 0 ) // Only check for the first step since it\u0027s impossible to get stuck on other steps\r\n        {\r\n            var groundTrace = BuildTrace( _shrunkenBounds, position, position \u002B AppliedGravity.Normal * ( GroundStickDistance \u002B SkinWidth * 1.1f ) ); // Compensate for floating inaccuracy\r\n\r\n            if ( groundTrace.StartedSolid )\r\n            {\r\n                IsStuck = true;\r\n                if ( UnstuckEnabled )\r\n                {\r\n                    if ( UnstuckTarget == null )\r\n                    {\r\n                        IsStuck = !TryUnstuck( position, out var result );\r\n\r\n                        if ( !IsStuck )\r\n                        {\r\n                            position = result; // Update the new position\r\n\r\n                            if ( PhysicallySimulated \u0026\u0026 Body.IsValid() )\r\n                            {\r\n                                Body.WorldPosition = result;\r\n                                Body.Velocity = Vector3.Zero;\r\n                            }\r\n\r\n                            if ( groundTrace.GameObject != null )\r\n                                if ( groundTrace.GameObject.Components.TryGet\u003CShrimpleCharacterController\u003E( out var otherHelper ) )\r\n                                    otherHelper.UnstuckTarget = this; // We already solved this, no need to unstuck the other helper\r\n                        }\r\n                        else\r\n                        {\r\n                            return new MoveHelperResult( position, Vector3.Zero ); // Mission failed, bail out!\r\n                        }\r\n                    }\r\n                    else\r\n                    {\r\n                        UnstuckTarget = null; // Alright the other MoveHelper got us unstuck so just do nothing\r\n                    }\r\n                }\r\n            }\r\n            else\r\n            {\r\n                var hasLanded = !IsOnGround \u0026\u0026 Vector3.Dot( Velocity, AppliedGravity ) \u003E= 0f \u0026\u0026 groundTrace.Hit \u0026\u0026 groundTrace.Distance \u003C= SkinWidth * 2f; // Wasn\u0027t on the ground and now is\r\n                var isGrounded = IsOnGround \u0026\u0026 groundTrace.Hit; // Was already on the ground and still is, this helps stick when going down stairs\r\n\r\n                IsOnGround = hasLanded || isGrounded;\r\n                GroundSurface = IsOnGround ? groundTrace.Surface : null;\r\n                GroundNormal = IsOnGround ? groundTrace.Normal : -AppliedGravity.Normal;\r\n                GroundObject = IsOnGround ? groundTrace.GameObject : null;\r\n                IsSlipping = IsOnGround \u0026\u0026 !IsAngleStandable( GroundAngle );\r\n\r\n                if ( IsSlipping \u0026\u0026 !gravityPass \u0026\u0026 Vector3.Dot( velocity, AppliedGravity ) \u003C 0f )\r\n                    velocity = velocity.WithZ( 0f ); // If we\u0027re slipping ignore any extra velocity we had\r\n\r\n                // FIX: Clamp velocity when slipping to prevent buildup\r\n                // Project velocity onto the slope and limit accumulation\r\n                if ( IsSlipping )\r\n                {\r\n                    var slopeDir = Vector3.VectorPlaneProject( AppliedGravity.Normal, GroundNormal ).Normal;\r\n                    var slopeSpeed = Vector3.Dot( velocity, slopeDir );\r\n                    var maxSlipSpeed = AppliedGravity.Length * delta * 2f; // Reasonable max slip speed per frame\r\n\r\n                    if ( slopeSpeed \u003E maxSlipSpeed )\r\n                    {\r\n                        // Clamp the velocity component along the slope\r\n                        velocity = velocity - slopeDir * ( slopeSpeed - maxSlipSpeed );\r\n                    }\r\n                }\r\n\r\n                if ( IsOnGround \u0026\u0026 GroundStickEnabled \u0026\u0026 !IsSlipping )\r\n                {\r\n                    position = groundTrace.EndPosition \u002B -AppliedGravity.Normal * SkinWidth; // Place on the ground\r\n                    velocity = Vector3.VectorPlaneProject( velocity, GroundNormal ); // Follow the ground you\u0027re on without projecting Z\r\n                }\r\n\r\n                IsStuck = false;\r\n            }\r\n        }\r\n\r\n        if ( velocity.IsNearlyZero( _minimumTolerance ) ) // Not worth continuing, reduces small stutter\r\n        {\r\n            return new MoveHelperResult( position, Vector3.Zero, current.Offset );\r\n        }\r\n\r\n        var toTravel = velocity.Length * current.Leftover \u002B SkinWidth;\r\n        var targetPosition = position \u002B velocity.Normal * toTravel;\r\n        var travelTrace = BuildTrace( _shrunkenBounds, position, targetPosition );\r\n\r\n        if ( travelTrace.Hit )\r\n        {\r\n            var travelled = velocity.Normal * Math.Max( travelTrace.Distance - SkinWidth, 0f );\r\n            var leftover = velocity - travelled; // How much leftover velocity still needs to be simulated\r\n            var speed = leftover.Length;\r\n            var angle = Vector3.GetAngle( -AppliedGravity.Normal, travelTrace.Normal );\r\n\r\n            if ( toTravel \u003E= SkinWidth \u0026\u0026 travelTrace.Distance \u003C SkinWidth )\r\n                travelled = Vector3.Zero;\r\n\r\n            var elasticityDirection = MathX.Lerp( HorizontalElasticity, VerticalElasticity, angle / 90f );\r\n            var elasticity = PhysicallySimulated ? 0f : BouncingEnabled ?\r\n                velocity.Length / delta \u003C= ElasticityThreshold ?\r\n                    0f : elasticityDirection * ( IncludeGroundElasticity ? GroundSurface?.Elasticity ?? 1f : 1f )\r\n                : 0f; // Allah help me format this better\r\n\r\n            if ( IsAngleStandable( angle ) ) // Terrain we can walk on\r\n            {\r\n                var projectedLeftover = leftover;\r\n\r\n                if ( gravityPass || !IsOnGround )\r\n                    projectedLeftover = Vector3.VectorPlaneProject( projectedLeftover, travelTrace.Normal ); // Don\u0027t project the vertical velocity after landing else it boosts your horizontal velocity\r\n                else\r\n                    projectedLeftover = projectedLeftover.ProjectAndScale( travelTrace.Normal ); // Project the velocity along the terrain\r\n\r\n                // Apply slope velocity reduction only when going uphill (against gravity)\r\n                var isGoingUphill = Vector3.Dot( projectedLeftover, AppliedGravity ) \u003C 0f;\r\n                if ( isGoingUphill )\r\n                {\r\n                    var slopeMultiplier = GetSlopeVelocityMultiplier( angle );\r\n                    projectedLeftover *= slopeMultiplier;\r\n                }\r\n\r\n                if ( elasticity \u003E 0 )\r\n                    leftover = Vector3.Lerp( projectedLeftover, Vector3.Reflect( leftover, travelTrace.Normal ), elasticity, false );\r\n                else\r\n                    leftover = projectedLeftover;\r\n\r\n                IsPushingAgainstWall = false;\r\n                WallObject = null;\r\n            }\r\n            else\r\n            {\r\n                var climbedStair = false;\r\n\r\n                if ( angle \u003E= 90f - WallTolerance \u0026\u0026 angle \u003C= 90f \u002B WallTolerance ) // Check for walls\r\n                    IsPushingAgainstWall = true; // We\u0027re pushing against a wall\r\n\r\n                if ( StepsEnabled )\r\n                {\r\n                    var isStep = angle \u003E= 90f - StepTolerance \u0026\u0026 angle \u003C= 90f \u002B StepTolerance;\r\n\r\n                    if ( isStep || PseudoStepsEnabled ) // Check for steps\r\n                    {\r\n                        if ( IsOnGround ) // Stairs VVV\r\n                        {\r\n                            var stepHorizontal = Vector3.VectorPlaneProject( velocity.Normal, AppliedGravity.Normal ).Normal * StepDepth; // How far in front we\u0027re looking for steps\r\n                            var stepVertical = -AppliedGravity.Normal * ( StepHeight \u002B SkinWidth ); // How high we\u0027re looking for steps \u002B Some to compensate for floating inaccuracy\r\n                            var stepTrace = BuildTrace( _shrunkenBounds, travelTrace.EndPosition \u002B stepHorizontal \u002B stepVertical, travelTrace.EndPosition \u002B stepHorizontal );\r\n                            var stepAngle = Vector3.GetAngle( stepTrace.Normal, -AppliedGravity.Normal );\r\n\r\n                            if ( !stepTrace.StartedSolid \u0026\u0026 stepTrace.Hit \u0026\u0026 IsAngleStandable( stepAngle ) ) // We found a step!\r\n                            {\r\n                                if ( isStep || !IsSlipping \u0026\u0026 PseudoStepsEnabled )\r\n                                {\r\n                                    var stepDistance = stepTrace.EndPosition - travelTrace.EndPosition;\r\n                                    var stepTravelled = -AppliedGravity.Normal * stepDistance;\r\n                                    position \u002B= stepTravelled; // Offset our position by the height of the step climbed\r\n                                    climbedStair = true;\r\n\r\n                                    IsPushingAgainstWall = false; // Nevermind, we\u0027re not against a wall, we climbed a step!\r\n                                    WallObject = null;\r\n                                }\r\n                            }\r\n                        }\r\n                    }\r\n                }\r\n\r\n                if ( IsPushingAgainstWall )\r\n                {\r\n                    // Scale our leftover velocity based on the angle of approach relative to the wall\r\n                    // (Perpendicular = 0%, Parallel = 100%)\r\n                    var scale = ScaleAgainstWalls ? 1f - Vector3.Dot( -travelTrace.Normal.Normal / GripFactorReduction, velocity.Normal ) : 1f;\r\n                    var wallLeftover = ScaleAgainstWalls ? Vector3.VectorPlaneProject( leftover, travelTrace.Normal.Normal ) : leftover.ProjectAndScale( travelTrace.Normal.Normal );\r\n\r\n                    if ( elasticity \u003E 0 )\r\n                        leftover = Vector3.Lerp( wallLeftover, Vector3.Reflect( leftover, travelTrace.Normal ), elasticity, false );\r\n                    else\r\n                        leftover = ( wallLeftover * scale ).WithZ( wallLeftover.z );\r\n\r\n                    WallObject = travelTrace.GameObject;\r\n                    WallNormal = travelTrace.Normal;\r\n                }\r\n                else\r\n                {\r\n                    if ( !climbedStair )\r\n                    {\r\n                        var scale = IsSlipping ? 1f : 1f - Vector3.Dot( -travelTrace.Normal / GripFactorReduction, velocity.Normal );\r\n                        var wallLeftover = ScaleAgainstWalls ? Vector3.VectorPlaneProject( leftover, travelTrace.Normal ) * scale : leftover.ProjectAndScale( travelTrace.Normal );\r\n\r\n                        if ( elasticity \u003E 0 )\r\n                            leftover = Vector3.Lerp( wallLeftover, Vector3.Reflect( leftover, travelTrace.Normal ), elasticity, false );\r\n                        else\r\n                            leftover = wallLeftover;\r\n                    }\r\n\r\n                }\r\n\r\n\r\n            }\r\n\r\n            var previousVelocity = velocity;\r\n            var leftoverSpeed = leftover.Length / speed; // How much speed we had left after the collision\r\n            velocity = leftover.Normal * velocity.Length * leftoverSpeed;\r\n\r\n            if ( travelled.Length \u003C= _minimumTolerance \u0026\u0026 leftover.Length \u003C= _minimumTolerance )\r\n                return new MoveHelperResult( position \u002B travelled, velocity / delta, current.Offset );\r\n\r\n            if ( elasticity \u003E 0 )\r\n                _bounced = true;\r\n\r\n            var offset = position - current.Position;\r\n            var newResult = CollideAndSlide( new MoveHelperResult( position \u002B travelled, velocity / delta, offset, leftover.Length / velocity.Length ), delta, depth \u002B 1, gravityPass ); // Simulate another bounce for the leftover velocity from the latest position\r\n\r\n            if ( depth == 0 \u0026\u0026 !gravityPass )\r\n            {\r\n                var collision = new ShrimpleCollisionResult()\r\n                    .WithHitNormal( travelTrace.Normal )\r\n                    .WithHitPosition( travelTrace.HitPosition )\r\n                    .WithAngle( angle )\r\n                    .WithHitObject( travelTrace.GameObject )\r\n                    .WithHitSurface( travelTrace.Surface )\r\n                    .WithHitVelocityBefore( previousVelocity / delta )\r\n                    .WithHitVelocityAfter( newResult.Velocity );\r\n\r\n                OnCollide?.Invoke( collision );\r\n            }\r\n\r\n            return newResult;\r\n        }\r\n\r\n        if ( depth == 0 \u0026\u0026 !gravityPass )\r\n        {\r\n            IsPushingAgainstWall = false;\r\n            WallObject = null;\r\n        }\r\n\r\n        if ( gravityPass \u0026\u0026 _bounced )\r\n        {\r\n            _bounced = false;\r\n            velocity -= AppliedGravity * delta * delta * 1.4f; // Evil hack so it doesn\u0027t lose velocity due to gravity passes after each bounce\r\n        }\r\n\r\n        return new MoveHelperResult( position \u002B velocity, velocity / delta, current.Offset ); // We didn\u0027t hit anything? Ok just keep going then :-)\r\n    }\r\n\r\n    private float CalculateGoalSpeed( Vector3 wishVelocity, Vector3 velocity, bool isAccelerating, float delta )\r\n    {\r\n        if ( !AccelerationEnabled ) return 999999999f; // Should be enough for the freaks that don\u0027t want acceleration\r\n\r\n        float goalSpeed;\r\n\r\n        var isSameDirection = velocity.IsNearlyZero( 1f ) || Vector3.Dot( wishVelocity.WithZ( 0f ).Normal, velocity.WithZ( 0f ).Normal ) \u003E= 0f; // Is our wishVelocity roughly moving towards our velocity already?\r\n        var currentSpeed = Math.Max( Velocity.WithZ( 0f ).Length, 10f );\r\n        var acceleration = ( IsOnGround ? GroundAcceleration : AirAcceleration ) * ( FixedAcceleration ? 1f : AccelerationCurve.Evaluate( currentSpeed ) );\r\n        var deceleration = ( IsOnGround ? GroundDeceleration : AirDeceleration ) * ( FixedDeceleration ? 1f : DecelerationCurve.Evaluate( currentSpeed ) );\r\n\r\n        if ( isAccelerating )\r\n            goalSpeed = acceleration;\r\n        else\r\n            goalSpeed = !isSameDirection ? Math.Max( acceleration, deceleration ) : deceleration; // Makes movement more responsive especially for flying or rolling\r\n\r\n        if ( !IgnoreGroundSurface \u0026\u0026 GroundSurface != null )\r\n            goalSpeed *= GroundSurface.Friction; // Take into account the ground\u0027s friction\r\n\r\n        goalSpeed *= delta;\r\n\r\n        return goalSpeed;\r\n    }\r\n\r\n    private Vector3 CalculateGoalVelocity( float delta )\r\n    {\r\n        bool shouldIgnoreZ = IgnoreZ || ( IgnoreZWhenZero \u0026\u0026 WishVelocity.z.AlmostEqual( 0f ) );\r\n        var wishVelocity = shouldIgnoreZ ? ( WishVelocity.Normal * WishVelocity.Length ).WithZ( Velocity.z ) : WishVelocity;\r\n        var isAccelerating = shouldIgnoreZ ? wishVelocity.WithZ( 0f ).Length \u003E= Velocity.WithZ( 0f ).Length : wishVelocity.Length \u003E= Velocity.Length;\r\n\r\n        var goalSpeed = CalculateGoalSpeed( wishVelocity, Velocity, isAccelerating, delta );\r\n        var goalVelocity = Velocity.MoveTowards( wishVelocity, goalSpeed );\r\n\r\n        return shouldIgnoreZ ? goalVelocity.WithZ( Velocity.z ) : goalVelocity;\r\n    }\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Attempts to return the nearest standable position until you\u0027re not longer inside other colliders\u003Cbr/\u003E\r\n    /// If it fails too frequently, consider increasing \u003Csee cref=\u0022MaxUnstuckTries\u0022/\u003E\r\n    /// \u003C/summary\u003E\r\n    /// \u003Cparam name=\u0022position\u0022\u003EThe start position\u003C/param\u003E\r\n    /// \u003Cparam name=\u0022result\u0022\u003EThe final standable position\u003C/param\u003E\r\n    /// \u003Creturns\u003ETrue if it found one, false if it failed.\u003C/returns\u003E\r\n    public bool TryUnstuck( Vector3 position, out Vector3 result )\r\n    {\r\n        if ( _lastVelocity == Vector3.Zero )\r\n            _lastVelocity = -AppliedGravity.Normal;\r\n\r\n        var velocityLength = _lastVelocity.Length \u002B SkinWidth;\r\n        var startPos = position - _lastVelocity.Normal * velocityLength; // Try undoing the last velocity 1st\r\n        var endPos = position;\r\n\r\n        for ( int i = 0; i \u003C MaxUnstuckTries \u002B 1; i\u002B\u002B )\r\n        {\r\n            if ( i == 1 )\r\n                startPos = position \u002B -AppliedGravity.Normal * 2f; // Try going up 2nd\r\n\r\n            if ( i \u003E 1 \u0026\u0026 i \u003C MaxUnstuckTries / 2f )\r\n                startPos = position \u002B Vector3.Random.Normal * ( (float)i / 2f ); // Start randomly checking 3rd\r\n\r\n            if ( i \u003E= MaxUnstuckTries / 2f )\r\n                startPos = position \u002B Vector3.Random.Normal * i; // Ok at this point start checking further\r\n\r\n            if ( startPos - endPos == Vector3.Zero ) // No difference!\r\n                continue;\r\n\r\n            var unstuckTrace = BuildTrace( _shrunkenBounds, startPos, endPos, PhysicallySimulated );\r\n\r\n            if ( !unstuckTrace.StartedSolid )\r\n            {\r\n                result = unstuckTrace.EndPosition - _lastVelocity.Normal * SkinWidth / 4f;\r\n                _lastVelocity = Vector3.Zero;\r\n                return true;\r\n            }\r\n        }\r\n\r\n        result = position;\r\n        return false;\r\n    }\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Debug don\u0027t use\r\n    /// \u003C/summary\u003E\r\n    /// \u003Cparam name=\u0022position\u0022\u003E\u003C/param\u003E\r\n    /// \u003Cparam name=\u0022title\u0022\u003E\u003C/param\u003E\r\n    /// \u003Creturns\u003E\u003C/returns\u003E\r\n    private bool TestPosition( Vector3 position, string title )\r\n    {\r\n        var testTrace = BuildTrace( _shrunkenBounds, position, position );\r\n\r\n        if ( testTrace.StartedSolid )\r\n        {\r\n            Log.Info( $\u0022[{RealTime.Now}]{title} {GameObject.Name} started solid at {position} against {testTrace.GameObject}\u0022 );\r\n            return true;\r\n        }\r\n\r\n        return false;\r\n    }\r\n\r\n    protected override void OnFixedUpdate()\r\n    {\r\n        base.OnFixedUpdate();\r\n\r\n        if ( !ManuallyUpdate \u0026\u0026 Active \u0026\u0026 !PhysicallySimulated ) // If we\u0027re not physically simulated we Move inside of OnFixedUpdate\r\n            Move( ActiveAutoTimeDelta );\r\n    }\r\n\r\n    protected override void OnDestroy()\r\n    {\r\n        base.OnDestroy();\r\n\r\n        Collider?.Destroy();\r\n        Body?.Destroy();\r\n    }\r\n\r\n    protected override void OnDisabled()\r\n    {\r\n        base.OnDisabled();\r\n\r\n        Collider?.Enabled = false;\r\n        Body?.Enabled = false;\r\n    }\r\n\r\n    protected override void OnEnabled()\r\n    {\r\n        base.OnEnabled();\r\n\r\n        Collider?.Enabled = true;\r\n        Body?.Enabled = true;\r\n    }\r\n\r\n    public override int ComponentVersion =\u003E 4;\r\n\r\n    [JsonUpgrader( typeof( ShrimpleCharacterController ), 3 )]\r\n    private static void UpdateTraceShape( JsonObject json )\r\n    {\r\n        if ( json.Remove( \u0022CylinderTrace\u0022, out var newNode ) \u0026\u0026 newNode is JsonValue boolNode \u0026\u0026 boolNode.TryGetValue\u003Cbool\u003E( out var isCylinder ) )\r\n            json[\u0022TraceShape\u0022] = isCylinder ? \u0022Cylinder\u0022 : \u0022Box\u0022;\r\n        else\r\n            json[\u0022TraceShape\u0022] = \u0022Box\u0022;\r\n    }\r\n\r\n    [JsonUpgrader( typeof( ShrimpleCharacterController ), 4 )]\r\n    private static void UpdateMaxGroundAngle( JsonObject json )\r\n    {\r\n        // Old default was 60f, new default is (30f, 60f)\r\n        if ( json.TryGetPropertyValue( \u0022MaxGroundAngle\u0022, out var oldValue ) \u0026\u0026 oldValue is JsonValue floatValue \u0026\u0026 floatValue.TryGetValue\u003Cfloat\u003E( out var oldAngle ) )\r\n        {\r\n            json.Remove( \u0022MaxGroundAngle\u0022 );\r\n            var startAngle = oldAngle * 0.75f;\r\n            json[\u0022MaxGroundAngle\u0022] = $\u0022{startAngle:F2} {oldAngle:F2}\u0022;\r\n        }\r\n    }\r\n}\r\n"},{"Ident":"fish.scc","Path":".obj/__compiler_extra.cs","FileName":"__compiler_extra.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":301969,"IsPrivate":false,"Code":"global using static Sandbox.Internal.GlobalGameNamespace;\r\nglobal using Microsoft.AspNetCore.Components;\r\nglobal using Microsoft.AspNetCore.Components.Rendering;\r\n[assembly: global::System.Reflection.AssemblyMetadata( \u0022AddonTitle\u0022, \u0022Shrimple Character Controller\u0022 )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \u0022AddonIdent\u0022, \u0022scc\u0022 )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \u0022OrgIdent\u0022, \u0022fish\u0022 )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \u0022Ident\u0022, \u0022fish.scc\u0022 )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \u0022EngineVersion\u0022, \u002226\u0022 )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \u0022EngineMinorVersion\u0022, \u00221\u0022 )]\r\n\r\n[assembly: System.Runtime.Versioning.TargetFramework( \u0022.NETCoreApp,Version=v9.0\u0022, FrameworkDisplayName = \u0022.NET 9.0\u0022 )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \u0022CompileTime\u0022, \u00222026-06-27T12:22:40.9761893Z\u0022 )]\r\n[assembly: global::System.Reflection.AssemblyVersion(\u00220.0.111.0\u0022)]\r\n[assembly: global::System.Reflection.AssemblyFileVersion(\u00220.0.111.0\u0022)]"},{"Ident":"fish.scc","Path":"code/ShrimpleCharacterController.cs","FileName":"ShrimpleCharacterController.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":301969,"IsPrivate":false,"Code":"using System.Text.Json.Nodes;\r\n\r\nnamespace ShrimpleCharacterController;\r\n\r\n[Icon( \u0022nordic_walking\u0022 )]\r\npublic partial class ShrimpleCharacterController : Component, IScenePhysicsEvents, ISceneEvent\u003CIScenePhysicsEvents\u003E, Component.ICollisionListener\r\n{\r\n    /// \u003Csummary\u003E\r\n    /// Manually update this by calling Move() or let it always be simulated.\r\n    /// In physical mode, Move() drives the next physics step instead of running the trace-based path.\r\n    /// \u003C/summary\u003E\r\n    [Property]\r\n    [Group( \u0022Options\u0022 )]\r\n    public bool ManuallyUpdate { get; set; } = false;\r\n\r\n    [Property]\r\n    [FeatureEnabled( \u0022Physical\u0022 )]\r\n    public bool PhysicallySimulated\r\n    {\r\n        get;\r\n        set\r\n        {\r\n            field = value;\r\n\r\n            if ( value )\r\n                CreateBody();\r\n            else\r\n                DestroyBody();\r\n        }\r\n    }\r\n\r\n    private bool isPhysical( object _ ) =\u003E !PhysicallySimulated;\r\n\r\n    [Property]\r\n    [Hide]\r\n    public GameObject BodyObject { get; protected set; }\r\n\r\n    [Property]\r\n    [Feature( \u0022Physical\u0022 )]\r\n    [Header( \u0022Collider\u0022 )]\r\n    [Validate( nameof( isPhysical ), \u0022Physical mode enabled - controller can interact with and be pushed by physics objects\u0022, LogLevel.Info )]\r\n    public Surface ColliderSurface\r\n    {\r\n        get =\u003E Collider.IsValid() ? Collider.Surface : null;\r\n        set\r\n        {\r\n            if ( Collider.IsValid() )\r\n                Collider.Surface = value;\r\n        }\r\n    }\r\n\r\n    [Property]\r\n    [Feature( \u0022Physical\u0022 )]\r\n    public float? ColliderElasticity\r\n    {\r\n        get =\u003E Collider.IsValid() ? Collider.Elasticity : null;\r\n        set\r\n        {\r\n            if ( Collider.IsValid() )\r\n                Collider.Elasticity = value;\r\n        }\r\n    }\r\n\r\n    [Property]\r\n    [Feature( \u0022Physical\u0022 )]\r\n    [ShowIf( \u0022HidePhysicalComponents\u0022, false )]\r\n    public Collider Collider { get; protected set; }\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Kilos, tweak based on your surroundings, default boxes are like 600kg!\r\n    /// \u003C/summary\u003E\r\n    [Property]\r\n    [Feature( \u0022Physical\u0022 )]\r\n    [Header( \u0022Rigid Body\u0022 )]\r\n    public float BodyMassOverride\r\n    {\r\n        get;\r\n        set\r\n        {\r\n            field = value;\r\n\r\n            if ( Body.IsValid() )\r\n                Body.MassOverride = value;\r\n        }\r\n    } = 100f;\r\n\r\n    [Property]\r\n    [Feature( \u0022Physical\u0022 )]\r\n    public RigidbodyFlags BodyRigidbodyFlags\r\n    {\r\n        get =\u003E Body.IsValid() ? Body.RigidbodyFlags : 0;\r\n        set\r\n        {\r\n            if ( Body.IsValid() )\r\n                Body.RigidbodyFlags = value;\r\n        }\r\n    }\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Enable Continuous Collision Detection (CCD) for the physics body.\r\n    /// Useful for fast-moving characters to prevent tunneling through thin objects.\r\n    /// \u003C/summary\u003E\r\n    [Property]\r\n    [Feature( \u0022Physical\u0022 )]\r\n    [Title( \u0022Enable CCD\u0022 )]\r\n    public bool EnableCCD\r\n    {\r\n        get;\r\n        set\r\n        {\r\n            field = value;\r\n            if ( Body.IsValid() )\r\n                Body.EnhancedCcd = value;\r\n        }\r\n    }\r\n\r\n    [Property]\r\n    [Feature( \u0022Physical\u0022 )]\r\n    [ShowIf( \u0022HidePhysicalComponents\u0022, false )]\r\n    public Rigidbody Body { get; protected set; }\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Hide the \u003Csee cref=\u0022Body\u0022/\u003E and \u003Csee cref=\u0022Collider\u0022/\u003E components in the inspector\r\n    /// \u003C/summary\u003E\r\n    [Property]\r\n    [Feature( \u0022Physical\u0022 )]\r\n    [Title( \u0022Hide Components\u0022 )]\r\n    [Space]\r\n    [Validate( nameof( isPhysical ), \u0022Make sure to go over the other features to see any warning regarding physical simulation!\u0022, LogLevel.Warn )]\r\n    public bool HidePhysicalComponents\r\n    {\r\n        get;\r\n        protected set\r\n        {\r\n            field = value;\r\n\r\n            if ( value )\r\n            {\r\n                if ( Body.IsValid() )\r\n                    Body.Flags |= ComponentFlags.Hidden;\r\n                if ( Collider.IsValid() )\r\n                    Collider.Flags |= ComponentFlags.Hidden;\r\n            }\r\n            else\r\n            {\r\n                if ( Body.IsValid() )\r\n                    Body.Flags \u0026= ~ComponentFlags.Hidden;\r\n                if ( Collider.IsValid() )\r\n                    Collider.Flags \u0026= ~ComponentFlags.Hidden;\r\n            }\r\n        }\r\n    } = true;\r\n\r\n    [Feature( \u0022Physical\u0022 )]\r\n    [Button( \u0022Recreate Components\u0022, \u0022sync\u0022 )]\r\n    public void RefreshBody() =\u003E CreateBody( true );\r\n\r\n    /// \u003Csummary\u003E\r\n    /// If pushing against a wall, scale the velocity based on the wall\u0027s angle (False is useful for NPCs that get stuck on corners)\r\n    /// \u003C/summary\u003E\r\n    [Property]\r\n    [Group( \u0022Options\u0022 )]\r\n    public bool ScaleAgainstWalls { get; set; } = true;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Rotate the trace with the gameobject\r\n    /// \u003C/summary\u003E\r\n    [Property]\r\n    [Group( \u0022Trace\u0022 )]\r\n    [Validate( nameof( isPhysical ), \u0022Physical mode uses a capsule collider to prevent snagging on terrain.\u0022, LogLevel.Info )]\r\n    public bool RotateWithGameObject { get; set; } = true;\r\n\r\n    public enum TraceType\r\n    {\r\n        /// \u003Csummary\u003E\r\n        /// This has no drawback, enjoy!\r\n        /// \u003C/summary\u003E\r\n        [Icon( \u0022\uD83D\uDCE6\u0022 )]\r\n        Box,\r\n        /// \u003Csummary\u003E\r\n        /// This is a PHYSICAL TRACE, so it\u0027s more expensive than the normal box or sphere trace\r\n        /// \u003C/summary\u003E\r\n        [Icon( \u0022\uD83D\uDEE2\u0022 )]\r\n        Cylinder,\r\n        /// \u003Csummary\u003E\r\n        /// This will disable \u003Csee cref=\u0022StepsEnabled\u0022/\u003E as it\u0027s impossible to get the angle reliably\r\n        /// \u003C/summary\u003E\r\n        [Icon( \u0022\uD83C\uDFD0\u0022 )]\r\n        Sphere,\r\n        /// \u003Csummary\u003E\r\n        /// No drawbacks, box but you can define exact bounds\r\n        /// \u003C/summary\u003E\r\n        [Icon( \u0022\uD83E\uDDCA\u0022 )]\r\n        Bounds\r\n    }\r\n\r\n    [Property]\r\n    [Group( \u0022Trace\u0022 )]\r\n    [HideIf( nameof( PhysicallySimulated ), true )]\r\n    public TraceType TraceShape\r\n    {\r\n        get;\r\n        set\r\n        {\r\n            field = value;\r\n            RebuildBounds();\r\n            CreateCollider();\r\n        }\r\n    } = TraceType.Box;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Width of our trace\r\n    /// \u003C/summary\u003E\r\n    [Property]\r\n    [Group( \u0022Trace\u0022 )]\r\n    [ShowIf( nameof( _showWidthHeight ), true )]\r\n    [Range( 1f, 128f, false, true )]\r\n    [Sync]\r\n    public float TraceWidth\r\n    {\r\n        get;\r\n        set\r\n        {\r\n            field = value;\r\n            RebuildBounds();\r\n            UpdateCollider();\r\n        }\r\n    } = 16f;\r\n\r\n    // Show width/height if Physical mode OR if not using Bounds shape\r\n    bool _showWidthHeight =\u003E PhysicallySimulated || TraceShape != TraceType.Bounds;\r\n    // Show height if Physical mode OR if using Box/Cylinder shape\r\n    bool _showHeight =\u003E PhysicallySimulated || TraceShape == TraceType.Box || TraceShape == TraceType.Cylinder;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Height of our trace\r\n    /// \u003C/summary\u003E\r\n    [Property]\r\n    [Group( \u0022Trace\u0022 )]\r\n    [ShowIf( nameof( _showHeight ), true )]\r\n    [Range( 1f, 256f, false, true )]\r\n    [Sync]\r\n    public float TraceHeight\r\n    {\r\n        get;\r\n        set\r\n        {\r\n            field = value;\r\n            RebuildBounds();\r\n            UpdateCollider();\r\n        }\r\n    } = 72f;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Bounds of our trace\r\n    /// \u003C/summary\u003E\r\n    [Property]\r\n    [Group( \u0022Trace\u0022 )]\r\n    [ShowIf( \u0022TraceShape\u0022, TraceType.Bounds )]\r\n    [Sync]\r\n    public BBox TraceBounds\r\n    {\r\n        get =\u003E TraceShape == TraceType.Bounds ? field : Bounds;\r\n        set\r\n        {\r\n            field = value;\r\n            RebuildBounds();\r\n            UpdateCollider();\r\n        }\r\n    } = BBox.FromPositionAndSize( Vector3.Up * 36f, new Vector3( 32f, 32f, 72f ) );\r\n\r\n    private bool ignoreTagsAndPhysical( TagSet tagSet ) =\u003E tagSet.IsEmpty || !PhysicallySimulated;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Which tags it should ignore\r\n    /// \u003C/summary\u003E\r\n    [Property]\r\n    [Group( \u0022Trace\u0022 )]\r\n    [Validate( nameof( ignoreTagsAndPhysical ), \u0022Contoller is physical! Ignore tags must be set on the collision matrix\u0022, LogLevel.Warn )]\r\n    public TagSet IgnoreTags { get; set; } = new TagSet();\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Max amount of trace calls whenever the simulation doesn\u0027t reach its target (Slide and collide bounces)\r\n    /// \u003C/summary\u003E\r\n    [Property]\r\n    [Group( \u0022Trace\u0022 )]\r\n    [Range( 1, 20, true, true )]\r\n    public int MaxBounces { get; set; } = 5;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Acceleration and Deceleration\r\n    /// \u003C/summary\u003E\r\n    [FeatureEnabled( \u0022Acceleration\u0022 )]\r\n    [Property]\r\n    public bool AccelerationEnabled { get; set; } = true;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// How fast you accelerate while on the ground (Units per second)\r\n    /// \u003C/summary\u003E\r\n    [Property]\r\n    [Feature( \u0022Acceleration\u0022 )]\r\n    [Range( 0f, 3000f, false )]\r\n    public float GroundAcceleration { get; set; } = 1000f;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// How fast you accelerate while in the air (Units per second)\r\n    /// \u003C/summary\u003E\r\n    [Property]\r\n    [Feature( \u0022Acceleration\u0022 )]\r\n    [Range( 0f, 3000f, false )]\r\n    public float AirAcceleration { get; set; } = 300f;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Use the fixed acceleration value instead of curves\r\n    /// \u003C/summary\u003E\r\n    [Property]\r\n    [Feature( \u0022Acceleration\u0022 )]\r\n    public bool FixedAcceleration { get; set; } = true;\r\n\r\n    protected static readonly Curve DefaultAcceleration = new Curve( new List\u003CCurve.Frame\u003E\r\n    {\r\n        new Curve.Frame(0f, 0.2f, 0f, 1.5f),\r\n        new Curve.Frame(1f, 1f, 0f, 0f)\r\n    } )\r\n    {\r\n        TimeRange = new Vector2( 0f, 500f ),\r\n        ValueRange = new Vector2( 0f, 1f ),\r\n    };\r\n\r\n    /// \u003Csummary\u003E\r\n    /// How much acceleration based on the current velocity\u003Cbr/\u003E\r\n    /// X axis = Current Velocity (Maxes at 500 by default but you can modify)\u003Cbr/\u003E\r\n    /// Y axis *= Acceleration/\u003E\u003Cbr/\u003E\r\n    /// \u003C/summary\u003E\r\n    [Property]\r\n    [Feature( \u0022Acceleration\u0022 )]\r\n    [ValueRange( 0f, 1f, false )]\r\n    [HideIf( \u0022FixedAcceleration\u0022, true )]\r\n    public Curve AccelerationCurve { get; set; } = DefaultAcceleration;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// How fast you decelerate while on the ground (Units per second)\r\n    /// \u003C/summary\u003E\r\n    [Property]\r\n    [Feature( \u0022Acceleration\u0022 )]\r\n    [Range( 0f, 3000f, false )]\r\n    public float GroundDeceleration { get; set; } = 1500f;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// How fast you decelerate while in the air (Units per second)\r\n    /// \u003C/summary\u003E\r\n    [Property]\r\n    [Feature( \u0022Acceleration\u0022 )]\r\n    [Range( 0f, 3000f, false )]\r\n    public float AirDeceleration { get; set; } = 0f;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Use the fixed deceleration value instead of curves\r\n    /// \u003C/summary\u003E\r\n    [Property]\r\n    [Feature( \u0022Acceleration\u0022 )]\r\n    public bool FixedDeceleration { get; set; } = true;\r\n\r\n    protected static readonly Curve DefaultDeceleration = new Curve( new List\u003CCurve.Frame\u003E\r\n    {\r\n        new Curve.Frame(0f, 1f, 0f, -2.5f),\r\n        new Curve.Frame(1f, 0.2f, 0f, 0f)\r\n    } )\r\n    {\r\n        TimeRange = new Vector2( 0f, 500f ),\r\n        ValueRange = new Vector2( 0f, 1f ),\r\n    };\r\n\r\n    /// \u003Csummary\u003E\r\n    /// How much deceleration based on the current velocity\u003Cbr/\u003E\r\n    /// X axis = Current Velocity (Maxes at 500 by default but you can modify)\u003Cbr/\u003E\r\n    /// Y axis *= Deceleration/\u003E\u003Cbr/\u003E\r\n    /// \u003C/summary\u003E\r\n    [Property]\r\n    [Feature( \u0022Acceleration\u0022 )]\r\n    [ValueRange( 0f, 1f, false )]\r\n    [HideIf( \u0022FixedDeceleration\u0022, true )]\r\n    public Curve DecelerationCurve { get; set; } = DefaultDeceleration;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Do we ignore the friction of the surface you\u0027re standing on or not?\r\n    /// \u003C/summary\u003E\r\n    [Property]\r\n    [Feature( \u0022Acceleration\u0022 )]\r\n    public bool IgnoreGroundSurface { get; set; } = false;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Use Time.Delta as the time step when automatically simulating (disable to set a fixed TimeDelta)\r\n    /// \u003C/summary\u003E\r\n    [Property]\r\n    [Group( \u0022Options\u0022 )]\r\n    [HideIf( nameof( ManuallyUpdate ), true )]\r\n    public bool UseTimeDelta { get; set; } = true;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Fixed time step used when automatically simulating and UseTimeDelta is disabled\r\n    /// \u003C/summary\u003E\r\n    [Property]\r\n    [Group( \u0022Options\u0022 )]\r\n    [ShowIf( nameof( _showTimeDelta ), true )]\r\n    public float TimeDelta { get; set; } = 1f / 60f;\r\n\r\n    private bool _showTimeDelta =\u003E !ManuallyUpdate \u0026\u0026 !UseTimeDelta;\r\n    private float ActiveAutoTimeDelta =\u003E UseTimeDelta ? Time.Delta : TimeDelta;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Is this MoveHelper meant for horizontal grounded movement? (false = For flying or noclip)\r\n    /// \u003C/summary\u003E\r\n    [Property]\r\n    [Group( \u0022Movement\u0022 )]\r\n    public bool IgnoreZ { get; set; } = true;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Do we ignore Z when it\u0027s near 0 (So that gravity affects you when not moving)\r\n    /// \u003C/summary\u003E\r\n    [Property]\r\n    [Title( \u0022Ignore Z When Zero\u0022 )]\r\n    [Group( \u0022Movement\u0022 )]\r\n    [HideIf( \u0022IgnoreZ\u0022, true )]\r\n    public bool IgnoreZWhenZero { get; set; } = true;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Tolerance from a 90\u00B0 surface before it\u0027s considered a wall (Ex. Tolerance 1 = Between 89\u00B0 and 91\u00B0 can be a wall, 0.1 = 89.9\u00B0 to 90.1\u00B0)\r\n    /// \u003C/summary\u003E\r\n    [Group( \u0022Movement\u0022 )]\r\n    [Property]\r\n    [Range( 0f, 10f, false )]\r\n    public float WallTolerance { get; set; } = 1f;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Player feels like it\u0027s gripping walls too much? Try more Grip Factor Reduction!\r\n    /// \u003C/summary\u003E\r\n    [Group( \u0022Movement\u0022 )]\r\n    [Property]\r\n    [Range( 1f, 10f, true )]\r\n    [Validate( nameof( isPhysical ), \u0022Physical mode uses collider friction for wall interaction\u0022, LogLevel.Info )]\r\n    public float GripFactorReduction { get; set; } = 1f;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Bouncing off walls and ground on collision\r\n    /// \u003C/summary\u003E\r\n    [FeatureEnabled( \u0022Bouncing\u0022 )]\r\n    [Property]\r\n    public bool BouncingEnabled { get; set; } = false;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// How much the MoveHelper will \u0022bounce\u0022 off walls when colliding with them\u003Cbr/\u003E\r\n    /// (0f = No bounce, 1f = Full bounce)\u003Cbr/\u003E\r\n    /// \u003C/summary\u003E\r\n    [Property]\r\n    [Feature( \u0022Bouncing\u0022 )]\r\n    [Range( 0f, 1f, true )]\r\n    [Validate( nameof( isPhysical ), \u0022Contoller is physical! Elasticity must be done through the collider\u0022, LogLevel.Error )]\r\n    public float HorizontalElasticity { get; set; } = 0f;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// How much the MoveHelper will \u0022bounce\u0022 off the ground when colliding with it\u003Cbr/\u003E\r\n    /// (0f = No bounce, 1f = Full bounce)\u003Cbr/\u003E\r\n    /// \u003Csee cref=\u0022GroundStickEnabled\u0022/\u003E will override this value when sticking to the ground\u003Cbr/\u003E\r\n    /// \u003C/summary\u003E\r\n    [Property]\r\n    [Feature( \u0022Bouncing\u0022 )]\r\n    [Range( 0f, 1f, true )]\r\n    public float VerticalElasticity { get; set; } = 0f;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Include the ground elasticity when bouncing off the ground\u003Cbr/\u003E\r\n    /// Make sure the ground surface doesn\u0027t have a very low elasticity!\r\n    /// \u003C/summary\u003E\r\n    [Property]\r\n    [Feature( \u0022Bouncing\u0022 )]\r\n    public bool IncludeGroundElasticity { get; set; } = false;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Minimum velocity to allow bouncing\r\n    /// \u003C/summary\u003E\r\n    [Property]\r\n    [Feature( \u0022Bouncing\u0022 )]\r\n    [Range( 0f, 100f, true )]\r\n    public float ElasticityThreshold { get; set; } = 30f;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Stick the MoveHelper to the ground (IsOnGround will default to false if disabled)\r\n    /// \u003C/summary\u003E\r\n    [FeatureEnabled( \u0022GroundStick\u0022 )]\r\n    [Property]\r\n    public bool GroundStickEnabled { get; set; } = true;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// How steep terrain can be for you to stand on without slipping.\u003Cbr/\u003E\r\n    /// Min = Angle where velocity starts to drop off\u003Cbr/\u003E\r\n    /// Max = Max angle where velocity is fully blocked (you can still slide down with gravity)\r\n    /// \u003C/summary\u003E\r\n    [Property, Step( 0.1f )]\r\n    [Feature( \u0022GroundStick\u0022 )]\r\n    public RangedFloat MaxGroundAngle\r\n    {\r\n        get;\r\n        set\r\n        {\r\n            if ( value.Range == RangedFloat.RangeType.Between )\r\n            {\r\n                var min = MathX.Clamp( value.Min, 0f, 89f );\r\n                var max = MathX.Clamp( value.Max, 0f, 89f );\r\n\r\n                field = new RangedFloat( MathF.Min( min, max ), MathF.Max( min, max ) );\r\n            }\r\n            else\r\n            {\r\n                field = new RangedFloat( MathX.Clamp( value.FixedValue, 0f, 89f ) );\r\n            }\r\n        }\r\n    } = new RangedFloat( 30f, 60f );\r\n\r\n    /// \u003Csummary\u003E\r\n    /// How far from the ground the MoveHelper is going to stick (Useful for going down stairs!)\r\n    /// \u003C/summary\u003E\r\n    [Property]\r\n    [Feature( \u0022GroundStick\u0022 )]\r\n    [Range( 1f, 32f, false )]\r\n    public float GroundStickDistance { get; set; } = 12f;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Stick to any \u003Csee cref=\u0022GroundObject\u0022/\u003E when standing on it\r\n    /// \u003C/summary\u003E\r\n    [Property]\r\n    [Feature( \u0022GroundStick\u0022 )]\r\n    public bool StickToPlatforms { get; set; } = true;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// When the surface you\u0027re on has a surface velocity, apply it to the controller\r\n    /// \u003C/summary\u003E\r\n    [Property]\r\n    [Feature( \u0022GroundStick\u0022 )]\r\n    public bool ApplySurfaceVelocity { get; set; } = true;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// When the surface you\u0027re on is slippery, scale the max ground angled relative to the friction\u003Cbr/\u003E\r\n    /// Example: Ice with friction 0.5f, MaxGroundAngle will be 30f instead of 60f\r\n    /// \u003C/summary\u003E\r\n    [Property]\r\n    [Feature( \u0022GroundStick\u0022 )]\r\n    public bool ScaleGroundAngleBySurfaceFriction { get; set; } = true;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Enable steps climbing (\u002B1 Trace call)\r\n    /// \u003C/summary\u003E\r\n    [FeatureEnabled( \u0022Steps\u0022 )]\r\n    [Property]\r\n    public bool StepsEnabled { get; set; } = true;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// How high steps can be for you to climb on\r\n    /// \u003C/summary\u003E\r\n    [Feature( \u0022Steps\u0022 )]\r\n    [Property]\r\n    [Range( 1f, 32f, false )]\r\n    public float StepHeight { get; set; } = 12f;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// How deep it checks for steps (Minimum depth)\r\n    /// \u003C/summary\u003E\r\n    [Feature( \u0022Steps\u0022 )]\r\n    [Property]\r\n    [Range( 0.1f, 8f, false )]\r\n    public float StepDepth { get; set; } = 2f;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Tolerance from a 90\u00B0 surface before it\u0027s considered a valid step (Ex. Tolerance 1 = Between 89\u00B0 and 91\u00B0 can be a step, 0.1 = 89.9\u00B0 to 90.1\u00B0)\r\n    /// \u003C/summary\u003E\r\n    [Feature( \u0022Steps\u0022 )]\r\n    [Property]\r\n    [Range( 0f, 10f, false )]\r\n    public float StepTolerance { get; set; } = 1f;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Enable to ability to walk on a surface that\u0027s too steep if it\u0027s equal or smaller than a step (\u002B1 Trace call when on steep terrain)\r\n    /// \u003C/summary\u003E\r\n    [Feature( \u0022Steps\u0022 )]\r\n    [Property]\r\n    public bool PseudoStepsEnabled { get; set; } = true;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Instead of colliding with these tags the MoveHelper will be pushed away (Make sure the tags are in IgnoreTags as well!)\r\n    /// \u003C/summary\u003E\r\n    [FeatureEnabled( \u0022Push\u0022 )]\r\n    [Property]\r\n    public bool PushEnabled { get; set; } = false;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Which tags will push this MoveHelper away and with how much force (Make sure they are also included in IgnoreTags!) (\u002B1 Trace call)\r\n    /// \u003C/summary\u003E\r\n    [Property]\r\n    [Feature( \u0022Push\u0022 )]\r\n    [Validate( nameof( isPhysical ), \u0022Contoller is physical! Make sure the tags are ignored on the collision matrix\u0022, LogLevel.Warn )]\r\n    [Sync]\r\n    public Dictionary\u003Cstring, float\u003E PushTagsWeight\r\n    {\r\n        get;\r\n        set\r\n        {\r\n            field = value;\r\n            _pushTags = BuildPushTags();\r\n        }\r\n    } = new Dictionary\u003Cstring, float\u003E() { { \u0022player\u0022, 1f } };\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Apply gravity to this MoveHelper when not on the ground\r\n    /// \u003C/summary\u003E\r\n    [FeatureEnabled( \u0022Gravity\u0022 )]\r\n    [Property]\r\n    public bool GravityEnabled { get; set; } = true;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Use the scene\u0027s gravity or our own\r\n    /// \u003C/summary\u003E\r\n    [Property]\r\n    [Feature( \u0022Gravity\u0022 )]\r\n    public bool UseSceneGravity\r\n    {\r\n        get;\r\n        set\r\n        {\r\n            field = value;\r\n            BuildGravity();\r\n        }\r\n    } = true;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Use a Vector3 gravity instead of a single float (Use this if you want to use a custom gravity)\r\n    /// \u003C/summary\u003E\r\n    [Property]\r\n    [Feature( \u0022Gravity\u0022 )]\r\n    [HideIf( \u0022UseSceneGravity\u0022, true )]\r\n    public bool UseVectorGravity\r\n    {\r\n        get;\r\n        set\r\n        {\r\n            field = value;\r\n            BuildGravity();\r\n        }\r\n    }\r\n\r\n    private bool _usingFloatGravity =\u003E !UseVectorGravity \u0026\u0026 !UseSceneGravity;\r\n    private bool _usingVectorGravity =\u003E UseVectorGravity \u0026\u0026 !UseSceneGravity;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Units per second squared (Default is -850f)\r\n    /// \u003C/summary\u003E\r\n    [Property]\r\n    [Feature( \u0022Gravity\u0022 )]\r\n    [Range( -2000, 2000, false )]\r\n    [ShowIf( \u0022_usingFloatGravity\u0022, true )]\r\n    public float Gravity\r\n    {\r\n        get;\r\n        set\r\n        {\r\n            field = value;\r\n            BuildGravity();\r\n        }\r\n    } = -850f;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Units per second squared (Default is 0f, 0f, -850f)\u003Cbr/\u003E\r\n    /// Changes which way \u003Csee cref=\u0022GroundStickEnabled\u0022/\u003E sticks to the ground\r\n    /// \u003C/summary\u003E\r\n    [Property]\r\n    [Feature( \u0022Gravity\u0022 )]\r\n    [ShowIf( \u0022_usingVectorGravity\u0022, true )]\r\n    public Vector3 VectorGravity\r\n    {\r\n        get;\r\n        set\r\n        {\r\n            field = value;\r\n            BuildGravity();\r\n        }\r\n    } = new Vector3( 0f, 0f, -850f );\r\n\r\n    private Vector3 _appliedGravity;\r\n    public Vector3 GravityNormal { get; private set; }\r\n    public Vector3 AppliedGravity =\u003E _appliedGravity;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Check if the MoveHelper is stuck and try to get it to unstuck (\u002BTrace calls if stuck)\r\n    /// \u003C/summary\u003E\r\n    [FeatureEnabled( \u0022Unstuck\u0022 )]\r\n    [Property]\r\n    public bool UnstuckEnabled { get; set; } = true;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// How many trace calls it will attempt to get the MoveHelper unstuck\r\n    /// \u003C/summary\u003E\r\n    [Property]\r\n    [Feature( \u0022Unstuck\u0022 )]\r\n    [Range( 1, 100, false )]\r\n    [Step( 1f )]\r\n    public int MaxUnstuckTries { get; set; } = 40;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// The simulated target velocity for our MoveHelper (Units per second, we apply Time.Delta inside)\r\n    /// \u003C/summary\u003E\r\n    [Sync] public Vector3 WishVelocity { get; set; }\r\n\r\n    /// \u003Csummary\u003E\r\n    /// The resulting velocity after the simulation is done (Units per second)\r\n    /// \u003C/summary\u003E\r\n    [Sync]\r\n    public Vector3 Velocity\r\n    {\r\n        get;\r\n        set\r\n        {\r\n            if ( PhysicallySimulated \u0026\u0026 Body.IsValid() )\r\n                Body.Velocity = value;\r\n\r\n            field = value;\r\n        }\r\n    }\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Velocity controlled by outside factors, such as knockback, rootmotion, etc.\r\n    /// It is only applied to our final position and doesn\u0027t affect our Velocity.\r\n    /// It should be handled outside the controller.\r\n    /// \u003C/summary\u003E\r\n    [Sync] public Vector3 ExternalVelocity { get; set; }\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Is the MoveHelper currently touching the ground\r\n    /// \u003C/summary\u003E\r\n    [Sync] public bool IsOnGround { get; set; }\r\n\r\n    /// \u003Csummary\u003E\r\n    /// The current ground normal you\u0027re standing on (Always Vector3.Zero if IsOnGround false)\r\n    /// \u003C/summary\u003E\r\n    public Vector3 GroundNormal { get; private set; } = Vector3.Zero;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// The current ground angle you\u0027re standing on (Always 0f if IsOnGround false)\r\n    /// \u003C/summary\u003E\r\n    public float GroundAngle =\u003E Vector3.GetAngle( GroundNormal, -GravityNormal );\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Gets the velocity multiplier for a given slope angle based on MaxGroundAngle range.\u003Cbr/\u003E\r\n    /// Returns 1.0 below the start angle, 0.0 at/above the max angle, and lerps between.\r\n    /// \u003C/summary\u003E\r\n    public float GetSlopeVelocityMultiplier( float angle )\r\n    {\r\n        if ( angle \u003C= MaxGroundAngle.Min ) return 1f;\r\n        if ( angle \u003E= MaxGroundAngle.Max ) return 0f;\r\n        return 1f - MathX.LerpInverse( angle, MaxGroundAngle.Min, MaxGroundAngle.Max );\r\n    }\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Whether the given angle is considered standable (below max ground angle)\r\n    /// \u003C/summary\u003E\r\n    public bool IsAngleStandable( float angle ) =\u003E angle \u003C= MaxGroundAngle.Max * ( ScaleGroundAngleBySurfaceFriction ? GroundSurface?.Friction ?? 1f : 1f );\r\n\r\n    /// \u003Csummary\u003E\r\n    /// The current surface you\u0027re standing on\r\n    /// \u003C/summary\u003E\r\n    public Surface GroundSurface { get; private set; }\r\n\r\n    /// \u003Csummary\u003E\r\n    /// The gameobject you\u0027re currently standing on\r\n    /// \u003C/summary\u003E\r\n    public GameObject GroundObject { get; set; }\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Is the MoveHelper currently pushing against a wall\r\n    /// \u003C/summary\u003E\r\n    public bool IsPushingAgainstWall { get; private set; }\r\n\r\n    /// \u003Csummary\u003E\r\n    /// The current wall normal you\u0027re pushing against (Always Vector3.Zero if IsPushingAgainstWall false)\r\n    /// \u003C/summary\u003E\r\n    public Vector3 WallNormal { get; private set; } = Vector3.Zero;\r\n\r\n    /// \u003Csummary\u003E\r\n    /// The gameobject you\u0027re currently pushing on\r\n    /// \u003C/summary\u003E\r\n    public GameObject WallObject { get; set; }\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Is the MoveHelper standing on a terrain too steep to stand on (Always false if IsOnGround false)\r\n    /// \u003C/summary\u003E\r\n    [Sync] public bool IsSlipping { get; private set; } // TODO IMPLEMENT\r\n\r\n    /// \u003Csummary\u003E\r\n    /// The MoveHelper is stuck and we can\u0027t get it out\r\n    /// \u003C/summary\u003E\r\n    [Sync] public bool IsStuck { get; private set; }\r\n\r\n    /// \u003Csummary\u003E\r\n    /// To avoid getting stuck due to imprecision we shrink the bounds before checking and compensate for it later\r\n    /// \u003C/summary\u003E\r\n    public float SkinWidth =\u003E Math.Min( Math.Max( 0.1f, TraceWidth * 0.05f ), GroundStickDistance );\r\n\r\n    public float AppliedWidth =\u003E TraceWidth * WorldScale.x; // The width of the MoveHelper in world units\r\n    public float AppliedDepth =\u003E TraceWidth * WorldScale.y; // The depth of the MoveHelper in world units\r\n    public float AppliedHeight =\u003E TraceShape == TraceType.Sphere ? AppliedWidth :\r\n        TraceShape == TraceType.Bounds ? TraceBounds.Size.z * WorldScale.z : TraceHeight * WorldScale.z; // The height of the MoveHelper in world units\r\n    private Vector3 _upAxis =\u003E RotateWithGameObject ? WorldRotation.Up : Vector3.Up;\r\n    private Vector3 _offset =\u003E ( RotateWithGameObject ? WorldRotation.Up : Vector3.Up ) * ( TraceShape == TraceType.Sphere || TraceShape == TraceType.Bounds ? 0f : AppliedHeight / 2f ); // The position of the MoveHelper in world units\r\n\r\n    /// \u003Csummary\u003E\r\n    /// The bounds of this MoveHelper generated from the TraceWidth and TraceHeight\r\n    /// \u003C/summary\u003E\r\n    public BBox Bounds { get; set; }\r\n    private BBox _shrunkenBounds;\r\n    private string[] _pushTags;\r\n    private Vector3 _lastVelocity;\r\n    private float _minimumTolerance =\u003E MathX.Clamp( Time.Delta / 2f, 0.005f, 0.1f ); // Floating precision tolerance, too high if used inside of OnUpdate so tied to update rate\r\n\r\n    /// \u003Csummary\u003E\r\n    /// If another MoveHelper moved at the same time and they\u0027re stuck, let this one know that the other already unstuck for us\r\n    /// \u003C/summary\u003E\r\n    public ShrimpleCharacterController UnstuckTarget;\r\n\r\n    public Action\u003CShrimpleCollisionResult\u003E OnCollide { get; set; }\r\n    public bool IsOnPlatform =\u003E IsOnGround \u0026\u0026 GroundStickEnabled \u0026\u0026 !IsSlipping \u0026\u0026 StickToPlatforms \u0026\u0026 GroundObject.IsValid();\r\n    public Vector3 PlatformVelocity =\u003E IsOnPlatform ? GroundObject.GetComponent\u003CCollider\u003E()?.GetVelocityAtPoint( WorldPosition ) ?? Vector3.Zero : Vector3.Zero;\r\n    public bool IsOnSurfaceWithVelocity =\u003E IsOnGround \u0026\u0026 GroundStickEnabled \u0026\u0026 !IsSlipping \u0026\u0026 ApplySurfaceVelocity \u0026\u0026 GroundObject.IsValid();\r\n    public Vector3 SurfaceVelocity =\u003E IsOnSurfaceWithVelocity ? GroundObject.GetComponent\u003CCollider\u003E()?.SurfaceVelocity * GroundObject.WorldRotation ?? Vector3.Zero : Vector3.Zero;\r\n    public Vector3 GroundVelocity =\u003E PlatformVelocity \u002B SurfaceVelocity;\r\n\r\n    protected override void OnStart()\r\n    {\r\n        RebuildBounds();\r\n        BuildGravity();\r\n        _pushTags = BuildPushTags();\r\n    }\r\n\r\n    protected override void DrawGizmos()\r\n    {\r\n        if ( Gizmo.IsSelected )\r\n        {\r\n            Gizmo.GizmoDraw draw = Gizmo.Draw;\r\n            draw.Color = Color.Blue;\r\n\r\n            if ( TraceShape == TraceType.Box )\r\n                draw.LineBBox( Bounds.Translate( Vector3.Up * Bounds.Size.z / 2f ) );\r\n            if ( TraceShape == TraceType.Cylinder )\r\n                draw.LineCylinder( Vector3.Zero, Vector3.Up * ( Bounds.Maxs.z - Bounds.Mins.z ), Bounds.Maxs.x, Bounds.Maxs.x, 24 );\r\n            if ( TraceShape == TraceType.Sphere )\r\n                draw.LineSphere( Vector3.Up * Bounds.Maxs.x, Bounds.Maxs.x );\r\n            if ( TraceShape == TraceType.Bounds )\r\n                draw.LineBBox( TraceBounds );\r\n\r\n            if ( !UseSceneGravity \u0026\u0026 UseVectorGravity )\r\n            {\r\n                draw.Color = Color.Red;\r\n                draw.LineThickness = 5f;\r\n                draw.IgnoreDepth = true;\r\n                draw.Arrow( Vector3.Zero, GravityNormal * 50f );\r\n            }\r\n        }\r\n    }\r\n\r\n    private BBox BuildBounds()\r\n    {\r\n        if ( TraceShape == TraceType.Bounds )\r\n            return new BBox( TraceBounds.Mins * GameObject.WorldScale, TraceBounds.Maxs * GameObject.WorldScale );\r\n\r\n        var x = GameObject.WorldScale.x;\r\n        var y = GameObject.WorldScale.y;\r\n        var z = GameObject.WorldScale.z;\r\n\r\n        var width = TraceWidth / 2f * x;\r\n        var depth = TraceWidth / 2f * y;\r\n        var halfHeight = TraceHeight / 2f * z;\r\n\r\n        // For cylinders we want the bounds to start at Z = 0 and extend up to the full height\r\n        if ( TraceShape == TraceType.Cylinder )\r\n            return new BBox( new Vector3( -width, -depth, 0f ), new Vector3( width, depth, halfHeight * 2f ) );\r\n\r\n        return new BBox( new Vector3( -width, -depth, -halfHeight ), new Vector3( width, depth, halfHeight ) );\r\n    }\r\n\r\n    private void RebuildBounds()\r\n    {\r\n        Bounds = BuildBounds();\r\n        _shrunkenBounds = Bounds.Grow( -SkinWidth );\r\n    }\r\n\r\n    private void BuildGravity()\r\n    {\r\n        _appliedGravity = UseSceneGravity ? Scene.PhysicsWorld.Gravity : UseVectorGravity ? VectorGravity : new Vector3( 0f, 0f, Gravity );\r\n        GravityNormal = _appliedGravity.Normal;\r\n    }\r\n\r\n    private string[] BuildPushTags()\r\n    {\r\n        return PushTagsWeight.Keys.ToArray();\r\n    }\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Casts the current bounds from to and returns the scene trace result\r\n    /// \u003C/summary\u003E\r\n    /// \u003Cparam name=\u0022bounds\u0022\u003E\u003C/param\u003E\r\n    /// \u003Cparam name=\u0022from\u0022\u003E\u003C/param\u003E\r\n    /// \u003Cparam name=\u0022to\u0022\u003E\u003C/param\u003E\r\n    /// \u003Cparam name=\u0022useCapsule\u0022\u003EUse a capsule shape instead of the defaults\u003C/param\u003E\r\n    /// \u003Creturns\u003E\u003C/returns\u003E\r\n    public SceneTraceResult BuildTrace( BBox bounds, Vector3 from, Vector3 to, bool useCapsule = false )\r\n    {\r\n        SceneTrace builder = new SceneTrace(); // Empty trace builder\r\n\r\n        if ( useCapsule )\r\n        {\r\n            var radius = bounds.Maxs.x;\r\n            var halfHeight = ( bounds.Maxs.z - bounds.Mins.z ) / 2f;\r\n            var hemiOffset = MathF.Max( halfHeight - radius, 0f );\r\n            var capsule = new Capsule( Vector3.Down * hemiOffset, Vector3.Up * hemiOffset, radius ); // Do not rotate Vector3, it\u0027s rotated 17 lines down!\r\n            builder = Game.SceneTrace.Capsule( capsule, from, to );\r\n        }\r\n        else\r\n        {\r\n            if ( TraceShape == TraceType.Box || TraceShape == TraceType.Bounds )\r\n                builder = Game.SceneTrace.Box( bounds, from, to );\r\n            if ( TraceShape == TraceType.Cylinder )\r\n                builder = Game.SceneTrace.Cylinder( bounds.Maxs.z - bounds.Mins.z, bounds.Maxs.x, from, to );\r\n            if ( TraceShape == TraceType.Sphere )\r\n                builder = Game.SceneTrace.Sphere( bounds.Maxs.x, from \u002B Vector3.Up * bounds.Maxs.x, to \u002B Vector3.Up * bounds.Maxs.x );\r\n        }\r\n\r\n        builder = builder\r\n            .IgnoreGameObjectHierarchy( GameObject )\r\n            .WithoutTags( IgnoreTags );\r\n\r\n        if ( RotateWithGameObject )\r\n            builder = builder.Rotated( PhysicallySimulated ? Collider.WorldRotation : GameObject.WorldRotation );\r\n\r\n        return builder.Run();\r\n    }\r\n\r\n    private SceneTraceResult BuildPushTrace( BBox bounds, Vector3 from, Vector3 to )\r\n    {\r\n        SceneTrace builder = new SceneTrace(); // Empty trace builder\r\n\r\n        if ( TraceShape == TraceType.Box || TraceShape == TraceType.Bounds )\r\n            builder = Game.SceneTrace.Box( bounds, from, to );\r\n        if ( TraceShape == TraceType.Cylinder )\r\n            builder = Game.SceneTrace.Cylinder( bounds.Maxs.z - bounds.Mins.z, bounds.Maxs.x, from, to );\r\n        if ( TraceShape == TraceType.Sphere )\r\n            builder = Game.SceneTrace.Sphere( bounds.Maxs.x, from, to );\r\n\r\n        builder = builder\r\n            .IgnoreGameObjectHierarchy( GameObject )\r\n            .WithAnyTags( _pushTags ); // Check for only the push tags\r\n\r\n        if ( RotateWithGameObject )\r\n            builder = builder.Rotated( GameObject.WorldRotation );\r\n\r\n        return builder.Run();\r\n    }\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Detach the MoveHelper from the ground and launch it somewhere (Units per second)\r\n    /// \u003C/summary\u003E\r\n    /// \u003Cparam name=\u0022amount\u0022\u003E\u003C/param\u003E\r\n    public void Punch( in Vector3 amount )\r\n    {\r\n        var groundVel = GroundVelocity; // Capture before ungrounding\r\n        IsOnGround = false;\r\n\r\n        if ( PhysicallySimulated \u0026\u0026 Body.IsValid() )\r\n            Body.Velocity = Body.Velocity \u002B amount \u002B groundVel;\r\n\r\n        Velocity \u002B= amount \u002B groundVel;\r\n    }\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Apply the WishVelocity, update the Velocity and the Position of the GameObject by simulating the MoveHelper\r\n    /// \u003C/summary\u003E\r\n    /// \u003Cparam name=\u0022manualUpdate\u0022\u003EJust calculate but don\u0027t update position\u003C/param\u003E\r\n    public MoveHelperResult Move( bool manualUpdate = false ) =\u003E Move( Time.Delta, manualUpdate );\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Apply the WishVelocity, update the Velocity and the Position of the GameObject by simulating the MoveHelper\r\n    /// \u003C/summary\u003E\r\n    /// \u003Cparam name=\u0022delta\u0022\u003EThe time step\u003C/param\u003E\r\n    /// \u003Cparam name=\u0022manualUpdate\u0022\u003EJust calculate but don\u0027t update position\u003C/param\u003E\r\n    public MoveHelperResult Move( float delta, bool manualUpdate = false )\r\n    {\r\n        // In physical mode the simulation runs inside the physics step callbacks.\r\n        // Queue this call so the next PrePhysicsStep/PostPhysicsStep drives the body with the requested delta.\r\n        if ( PhysicallySimulated )\r\n        {\r\n            if ( Body.IsValid() \u0026\u0026 !manualUpdate )\r\n            {\r\n                _physicalMoveDelta = delta;\r\n                _physicalMovePending = true;\r\n            }\r\n\r\n            return new MoveHelperResult( WorldPosition, Velocity );\r\n        }\r\n\r\n        var goalVelocity = CalculateGoalVelocity( delta ); // Calculate the goal velocity using our Acceleration and Deceleration values\r\n\r\n        // SIMULATE PUSH FORCES //\r\n        if ( PushEnabled )\r\n        {\r\n            var pushTrace = BuildPushTrace( Bounds, WorldPosition \u002B _offset, WorldPosition ); // Build a trace but using the Push tags instead of the Ignore tags\r\n\r\n            if ( pushTrace.Hit ) // We\u0027re inside any of the push tags\r\n            {\r\n                foreach ( var tag in pushTrace.GameObject.Tags )\r\n                {\r\n                    if ( PushTagsWeight.TryGetValue( tag, out var tagWeight ) )\r\n                    {\r\n                        var otherPosition = pushTrace.GameObject.WorldPosition.WithZ( WorldPosition.z ); // Only horizontal pushing\r\n                        var pushDirection = ( otherPosition - WorldPosition ).Normal;\r\n                        var pushVelocity = pushDirection * tagWeight * 50f; // I find 50 u/s to be a good amount to push if the weight is 1.0 (!!!)\r\n\r\n                        goalVelocity -= pushVelocity;\r\n                    }\r\n                }\r\n            }\r\n        }\r\n\r\n        var finalPosition = WorldPosition;\r\n\r\n        var moveHelperResult = CollideAndSlide( goalVelocity, finalPosition \u002B _offset, delta ); // Simulate the MoveHelper\r\n\r\n        finalPosition = moveHelperResult.Position;\r\n        var finalVelocity = moveHelperResult.Velocity;\r\n        // SIMULATE GRAVITY //\r\n        if ( GravityEnabled \u0026\u0026 Gravity != 0f )\r\n        {\r\n            if ( !IsOnGround || IsSlipping || !GroundStickEnabled )\r\n            {\r\n                var gravity = AppliedGravity * delta;\r\n                var gravityResult = CollideAndSlide( gravity, moveHelperResult.Position, delta, gravityPass: true ); // Apply and simulate the gravity step\r\n\r\n                finalPosition = gravityResult.Position;\r\n                finalVelocity \u002B= gravityResult.Velocity;\r\n            }\r\n        }\r\n\r\n        if ( !ExternalVelocity.IsNearZeroLength )\r\n        {\r\n            moveHelperResult = CollideAndSlide( ExternalVelocity, finalPosition, delta );\r\n        }\r\n\r\n        finalPosition = moveHelperResult.Position - _offset; // Compensate for the offset we added at the beginning\r\n        _lastVelocity = Velocity * delta;\r\n\r\n        if ( !manualUpdate )\r\n        {\r\n            Velocity = finalVelocity;\r\n            WorldPosition = finalPosition \u002B SurfaceVelocity * delta; // Actually updating the position is \u0022expensive\u0022 so we only do it once at the end\r\n        }\r\n\r\n        return new MoveHelperResult( finalPosition, finalVelocity, moveHelperResult.Offset );\r\n    }\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Sometimes we have to update only the position but not the velocity (Like when climbing steps or getting unstuck) so we can\u0027t have Position rely only on Velocity\r\n    /// \u003C/summary\u003E\r\n    public struct MoveHelperResult\r\n    {\r\n        public Vector3 Position;\r\n        public Vector3 Velocity;\r\n        internal Vector3 Offset;\r\n        internal float Leftover = 1f;\r\n\r\n        public MoveHelperResult( Vector3 position, Vector3 velocity )\r\n        {\r\n            Position = position;\r\n            Velocity = velocity;\r\n        }\r\n\r\n        internal MoveHelperResult( Vector3 position, Vector3 velocity, Vector3 offset, float leftover = 1f )\r\n        {\r\n            Position = position;\r\n            Velocity = velocity;\r\n            Offset = offset;\r\n            Leftover = leftover;\r\n        }\r\n    }\r\n\r\n    bool _bounced = false;\r\n\r\n    private MoveHelperResult CollideAndSlide( Vector3 velocity, Vector3 position, float delta, int depth = 0, bool gravityPass = false ) =\u003E\r\n        CollideAndSlide( new MoveHelperResult( position, velocity ), delta, depth, gravityPass );\r\n\r\n    private MoveHelperResult CollideAndSlide( MoveHelperResult current, float delta, int depth = 0, bool gravityPass = false )\r\n    {\r\n        if ( depth \u003E= MaxBounces )\r\n            return current;\r\n\r\n        var velocity = current.Velocity * delta; // I like to set Velocity as units/second but we have to deal with units/tick here\r\n        var position = current.Position;\r\n\r\n        // GROUND AND UNSTUCK CHECK //\r\n        if ( depth == 0 ) // Only check for the first step since it\u0027s impossible to get stuck on other steps\r\n        {\r\n            var groundTrace = BuildTrace( _shrunkenBounds, position, position \u002B AppliedGravity.Normal * ( GroundStickDistance \u002B SkinWidth * 1.1f ) ); // Compensate for floating inaccuracy\r\n\r\n            if ( groundTrace.StartedSolid )\r\n            {\r\n                IsStuck = true;\r\n                if ( UnstuckEnabled )\r\n                {\r\n                    if ( UnstuckTarget == null )\r\n                    {\r\n                        IsStuck = !TryUnstuck( position, out var result );\r\n\r\n                        if ( !IsStuck )\r\n                        {\r\n                            position = result; // Update the new position\r\n\r\n                            if ( PhysicallySimulated \u0026\u0026 Body.IsValid() )\r\n                            {\r\n                                Body.WorldPosition = result;\r\n                                Body.Velocity = Vector3.Zero;\r\n                            }\r\n\r\n                            if ( groundTrace.GameObject != null )\r\n                                if ( groundTrace.GameObject.Components.TryGet\u003CShrimpleCharacterController\u003E( out var otherHelper ) )\r\n                                    otherHelper.UnstuckTarget = this; // We already solved this, no need to unstuck the other helper\r\n                        }\r\n                        else\r\n                        {\r\n                            return new MoveHelperResult( position, Vector3.Zero ); // Mission failed, bail out!\r\n                        }\r\n                    }\r\n                    else\r\n                    {\r\n                        UnstuckTarget = null; // Alright the other MoveHelper got us unstuck so just do nothing\r\n                    }\r\n                }\r\n            }\r\n            else\r\n            {\r\n                var hasLanded = !IsOnGround \u0026\u0026 Vector3.Dot( Velocity, AppliedGravity ) \u003E= 0f \u0026\u0026 groundTrace.Hit \u0026\u0026 groundTrace.Distance \u003C= SkinWidth * 2f; // Wasn\u0027t on the ground and now is\r\n                var isGrounded = IsOnGround \u0026\u0026 groundTrace.Hit; // Was already on the ground and still is, this helps stick when going down stairs\r\n\r\n                IsOnGround = hasLanded || isGrounded;\r\n                GroundSurface = IsOnGround ? groundTrace.Surface : null;\r\n                GroundNormal = IsOnGround ? groundTrace.Normal : -AppliedGravity.Normal;\r\n                GroundObject = IsOnGround ? groundTrace.GameObject : null;\r\n                IsSlipping = IsOnGround \u0026\u0026 !IsAngleStandable( GroundAngle );\r\n\r\n                if ( IsSlipping \u0026\u0026 !gravityPass \u0026\u0026 Vector3.Dot( velocity, AppliedGravity ) \u003C 0f )\r\n                    velocity = velocity.WithZ( 0f ); // If we\u0027re slipping ignore any extra velocity we had\r\n\r\n                // FIX: Clamp velocity when slipping to prevent buildup\r\n                // Project velocity onto the slope and limit accumulation\r\n                if ( IsSlipping )\r\n                {\r\n                    var slopeDir = Vector3.VectorPlaneProject( AppliedGravity.Normal, GroundNormal ).Normal;\r\n                    var slopeSpeed = Vector3.Dot( velocity, slopeDir );\r\n                    var maxSlipSpeed = AppliedGravity.Length * delta * 2f; // Reasonable max slip speed per frame\r\n\r\n                    if ( slopeSpeed \u003E maxSlipSpeed )\r\n                    {\r\n                        // Clamp the velocity component along the slope\r\n                        velocity = velocity - slopeDir * ( slopeSpeed - maxSlipSpeed );\r\n                    }\r\n                }\r\n\r\n                if ( IsOnGround \u0026\u0026 GroundStickEnabled \u0026\u0026 !IsSlipping )\r\n                {\r\n                    position = groundTrace.EndPosition \u002B -AppliedGravity.Normal * SkinWidth; // Place on the ground\r\n                    velocity = Vector3.VectorPlaneProject( velocity, GroundNormal ); // Follow the ground you\u0027re on without projecting Z\r\n                }\r\n\r\n                IsStuck = false;\r\n            }\r\n        }\r\n\r\n        if ( velocity.IsNearlyZero( _minimumTolerance ) ) // Not worth continuing, reduces small stutter\r\n        {\r\n            return new MoveHelperResult( position, Vector3.Zero, current.Offset );\r\n        }\r\n\r\n        var toTravel = velocity.Length * current.Leftover \u002B SkinWidth;\r\n        var targetPosition = position \u002B velocity.Normal * toTravel;\r\n        var travelTrace = BuildTrace( _shrunkenBounds, position, targetPosition );\r\n\r\n        if ( travelTrace.Hit )\r\n        {\r\n            var travelled = velocity.Normal * Math.Max( travelTrace.Distance - SkinWidth, 0f );\r\n            var leftover = velocity - travelled; // How much leftover velocity still needs to be simulated\r\n            var speed = leftover.Length;\r\n            var angle = Vector3.GetAngle( -AppliedGravity.Normal, travelTrace.Normal );\r\n\r\n            if ( toTravel \u003E= SkinWidth \u0026\u0026 travelTrace.Distance \u003C SkinWidth )\r\n                travelled = Vector3.Zero;\r\n\r\n            var elasticityDirection = MathX.Lerp( HorizontalElasticity, VerticalElasticity, angle / 90f );\r\n            var elasticity = PhysicallySimulated ? 0f : BouncingEnabled ?\r\n                velocity.Length / delta \u003C= ElasticityThreshold ?\r\n                    0f : elasticityDirection * ( IncludeGroundElasticity ? GroundSurface?.Elasticity ?? 1f : 1f )\r\n                : 0f; // Allah help me format this better\r\n\r\n            if ( IsAngleStandable( angle ) ) // Terrain we can walk on\r\n            {\r\n                var projectedLeftover = leftover;\r\n\r\n                if ( gravityPass || !IsOnGround )\r\n                    projectedLeftover = Vector3.VectorPlaneProject( projectedLeftover, travelTrace.Normal ); // Don\u0027t project the vertical velocity after landing else it boosts your horizontal velocity\r\n                else\r\n                    projectedLeftover = projectedLeftover.ProjectAndScale( travelTrace.Normal ); // Project the velocity along the terrain\r\n\r\n                // Apply slope velocity reduction only when going uphill (against gravity)\r\n                var isGoingUphill = Vector3.Dot( projectedLeftover, AppliedGravity ) \u003C 0f;\r\n                if ( isGoingUphill )\r\n                {\r\n                    var slopeMultiplier = GetSlopeVelocityMultiplier( angle );\r\n                    projectedLeftover *= slopeMultiplier;\r\n                }\r\n\r\n                if ( elasticity \u003E 0 )\r\n                    leftover = Vector3.Lerp( projectedLeftover, Vector3.Reflect( leftover, travelTrace.Normal ), elasticity, false );\r\n                else\r\n                    leftover = projectedLeftover;\r\n\r\n                IsPushingAgainstWall = false;\r\n                WallObject = null;\r\n            }\r\n            else\r\n            {\r\n                var climbedStair = false;\r\n\r\n                if ( angle \u003E= 90f - WallTolerance \u0026\u0026 angle \u003C= 90f \u002B WallTolerance ) // Check for walls\r\n                    IsPushingAgainstWall = true; // We\u0027re pushing against a wall\r\n\r\n                if ( StepsEnabled )\r\n                {\r\n                    var isStep = angle \u003E= 90f - StepTolerance \u0026\u0026 angle \u003C= 90f \u002B StepTolerance;\r\n\r\n                    if ( isStep || PseudoStepsEnabled ) // Check for steps\r\n                    {\r\n                        if ( IsOnGround ) // Stairs VVV\r\n                        {\r\n                            var stepHorizontal = Vector3.VectorPlaneProject( velocity.Normal, AppliedGravity.Normal ).Normal * StepDepth; // How far in front we\u0027re looking for steps\r\n                            var stepVertical = -AppliedGravity.Normal * ( StepHeight \u002B SkinWidth ); // How high we\u0027re looking for steps \u002B Some to compensate for floating inaccuracy\r\n                            var stepTrace = BuildTrace( _shrunkenBounds, travelTrace.EndPosition \u002B stepHorizontal \u002B stepVertical, travelTrace.EndPosition \u002B stepHorizontal );\r\n                            var stepAngle = Vector3.GetAngle( stepTrace.Normal, -AppliedGravity.Normal );\r\n\r\n                            if ( !stepTrace.StartedSolid \u0026\u0026 stepTrace.Hit \u0026\u0026 IsAngleStandable( stepAngle ) ) // We found a step!\r\n                            {\r\n                                if ( isStep || !IsSlipping \u0026\u0026 PseudoStepsEnabled )\r\n                                {\r\n                                    var stepDistance = stepTrace.EndPosition - travelTrace.EndPosition;\r\n                                    var stepTravelled = -AppliedGravity.Normal * stepDistance;\r\n                                    position \u002B= stepTravelled; // Offset our position by the height of the step climbed\r\n                                    climbedStair = true;\r\n\r\n                                    IsPushingAgainstWall = false; // Nevermind, we\u0027re not against a wall, we climbed a step!\r\n                                    WallObject = null;\r\n                                }\r\n                            }\r\n                        }\r\n                    }\r\n                }\r\n\r\n                if ( IsPushingAgainstWall )\r\n                {\r\n                    // Scale our leftover velocity based on the angle of approach relative to the wall\r\n                    // (Perpendicular = 0%, Parallel = 100%)\r\n                    var scale = ScaleAgainstWalls ? 1f - Vector3.Dot( -travelTrace.Normal.Normal / GripFactorReduction, velocity.Normal ) : 1f;\r\n                    var wallLeftover = ScaleAgainstWalls ? Vector3.VectorPlaneProject( leftover, travelTrace.Normal.Normal ) : leftover.ProjectAndScale( travelTrace.Normal.Normal );\r\n\r\n                    if ( elasticity \u003E 0 )\r\n                        leftover = Vector3.Lerp( wallLeftover, Vector3.Reflect( leftover, travelTrace.Normal ), elasticity, false );\r\n                    else\r\n                        leftover = ( wallLeftover * scale ).WithZ( wallLeftover.z );\r\n\r\n                    WallObject = travelTrace.GameObject;\r\n                    WallNormal = travelTrace.Normal;\r\n                }\r\n                else\r\n                {\r\n                    if ( !climbedStair )\r\n                    {\r\n                        var scale = IsSlipping ? 1f : 1f - Vector3.Dot( -travelTrace.Normal / GripFactorReduction, velocity.Normal );\r\n                        var wallLeftover = ScaleAgainstWalls ? Vector3.VectorPlaneProject( leftover, travelTrace.Normal ) * scale : leftover.ProjectAndScale( travelTrace.Normal );\r\n\r\n                        if ( elasticity \u003E 0 )\r\n                            leftover = Vector3.Lerp( wallLeftover, Vector3.Reflect( leftover, travelTrace.Normal ), elasticity, false );\r\n                        else\r\n                            leftover = wallLeftover;\r\n                    }\r\n\r\n                }\r\n\r\n\r\n            }\r\n\r\n            var previousVelocity = velocity;\r\n            var leftoverSpeed = leftover.Length / speed; // How much speed we had left after the collision\r\n            velocity = leftover.Normal * velocity.Length * leftoverSpeed;\r\n\r\n            if ( travelled.Length \u003C= _minimumTolerance \u0026\u0026 leftover.Length \u003C= _minimumTolerance )\r\n                return new MoveHelperResult( position \u002B travelled, velocity / delta, current.Offset );\r\n\r\n            if ( elasticity \u003E 0 )\r\n                _bounced = true;\r\n\r\n            var offset = position - current.Position;\r\n            var newResult = CollideAndSlide( new MoveHelperResult( position \u002B travelled, velocity / delta, offset, leftover.Length / velocity.Length ), delta, depth \u002B 1, gravityPass ); // Simulate another bounce for the leftover velocity from the latest position\r\n\r\n            if ( depth == 0 \u0026\u0026 !gravityPass )\r\n            {\r\n                var collision = new ShrimpleCollisionResult()\r\n                    .WithHitNormal( travelTrace.Normal )\r\n                    .WithHitPosition( travelTrace.HitPosition )\r\n                    .WithAngle( angle )\r\n                    .WithHitObject( travelTrace.GameObject )\r\n                    .WithHitSurface( travelTrace.Surface )\r\n                    .WithHitVelocityBefore( previousVelocity / delta )\r\n                    .WithHitVelocityAfter( newResult.Velocity );\r\n\r\n                OnCollide?.Invoke( collision );\r\n            }\r\n\r\n            return newResult;\r\n        }\r\n\r\n        if ( depth == 0 \u0026\u0026 !gravityPass )\r\n        {\r\n            IsPushingAgainstWall = false;\r\n            WallObject = null;\r\n        }\r\n\r\n        if ( gravityPass \u0026\u0026 _bounced )\r\n        {\r\n            _bounced = false;\r\n            velocity -= AppliedGravity * delta * delta * 1.4f; // Evil hack so it doesn\u0027t lose velocity due to gravity passes after each bounce\r\n        }\r\n\r\n        return new MoveHelperResult( position \u002B velocity, velocity / delta, current.Offset ); // We didn\u0027t hit anything? Ok just keep going then :-)\r\n    }\r\n\r\n    private float CalculateGoalSpeed( Vector3 wishVelocity, Vector3 velocity, bool isAccelerating, float delta )\r\n    {\r\n        if ( !AccelerationEnabled ) return 999999999f; // Should be enough for the freaks that don\u0027t want acceleration\r\n\r\n        float goalSpeed;\r\n\r\n        var isSameDirection = velocity.IsNearlyZero( 1f ) || Vector3.Dot( wishVelocity.WithZ( 0f ).Normal, velocity.WithZ( 0f ).Normal ) \u003E= 0f; // Is our wishVelocity roughly moving towards our velocity already?\r\n        var currentSpeed = Math.Max( Velocity.WithZ( 0f ).Length, 10f );\r\n        var acceleration = ( IsOnGround ? GroundAcceleration : AirAcceleration ) * ( FixedAcceleration ? 1f : AccelerationCurve.Evaluate( currentSpeed ) );\r\n        var deceleration = ( IsOnGround ? GroundDeceleration : AirDeceleration ) * ( FixedDeceleration ? 1f : DecelerationCurve.Evaluate( currentSpeed ) );\r\n\r\n        if ( isAccelerating )\r\n            goalSpeed = acceleration;\r\n        else\r\n            goalSpeed = !isSameDirection ? Math.Max( acceleration, deceleration ) : deceleration; // Makes movement more responsive especially for flying or rolling\r\n\r\n        if ( !IgnoreGroundSurface \u0026\u0026 GroundSurface != null )\r\n            goalSpeed *= GroundSurface.Friction; // Take into account the ground\u0027s friction\r\n\r\n        goalSpeed *= delta;\r\n\r\n        return goalSpeed;\r\n    }\r\n\r\n    private Vector3 CalculateGoalVelocity( float delta )\r\n    {\r\n        bool shouldIgnoreZ = IgnoreZ || ( IgnoreZWhenZero \u0026\u0026 WishVelocity.z.AlmostEqual( 0f ) );\r\n        var wishVelocity = shouldIgnoreZ ? ( WishVelocity.Normal * WishVelocity.Length ).WithZ( Velocity.z ) : WishVelocity;\r\n        var isAccelerating = shouldIgnoreZ ? wishVelocity.WithZ( 0f ).Length \u003E= Velocity.WithZ( 0f ).Length : wishVelocity.Length \u003E= Velocity.Length;\r\n\r\n        var goalSpeed = CalculateGoalSpeed( wishVelocity, Velocity, isAccelerating, delta );\r\n        var goalVelocity = Velocity.MoveTowards( wishVelocity, goalSpeed );\r\n\r\n        return shouldIgnoreZ ? goalVelocity.WithZ( Velocity.z ) : goalVelocity;\r\n    }\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Attempts to return the nearest standable position until you\u0027re not longer inside other colliders\u003Cbr/\u003E\r\n    /// If it fails too frequently, consider increasing \u003Csee cref=\u0022MaxUnstuckTries\u0022/\u003E\r\n    /// \u003C/summary\u003E\r\n    /// \u003Cparam name=\u0022position\u0022\u003EThe start position\u003C/param\u003E\r\n    /// \u003Cparam name=\u0022result\u0022\u003EThe final standable position\u003C/param\u003E\r\n    /// \u003Creturns\u003ETrue if it found one, false if it failed.\u003C/returns\u003E\r\n    public bool TryUnstuck( Vector3 position, out Vector3 result )\r\n    {\r\n        if ( _lastVelocity == Vector3.Zero )\r\n            _lastVelocity = -AppliedGravity.Normal;\r\n\r\n        var velocityLength = _lastVelocity.Length \u002B SkinWidth;\r\n        var startPos = position - _lastVelocity.Normal * velocityLength; // Try undoing the last velocity 1st\r\n        var endPos = position;\r\n\r\n        for ( int i = 0; i \u003C MaxUnstuckTries \u002B 1; i\u002B\u002B )\r\n        {\r\n            if ( i == 1 )\r\n                startPos = position \u002B -AppliedGravity.Normal * 2f; // Try going up 2nd\r\n\r\n            if ( i \u003E 1 \u0026\u0026 i \u003C MaxUnstuckTries / 2f )\r\n                startPos = position \u002B Vector3.Random.Normal * ( (float)i / 2f ); // Start randomly checking 3rd\r\n\r\n            if ( i \u003E= MaxUnstuckTries / 2f )\r\n                startPos = position \u002B Vector3.Random.Normal * i; // Ok at this point start checking further\r\n\r\n            if ( startPos - endPos == Vector3.Zero ) // No difference!\r\n                continue;\r\n\r\n            var unstuckTrace = BuildTrace( _shrunkenBounds, startPos, endPos, PhysicallySimulated );\r\n\r\n            if ( !unstuckTrace.StartedSolid )\r\n            {\r\n                result = unstuckTrace.EndPosition - _lastVelocity.Normal * SkinWidth / 4f;\r\n                _lastVelocity = Vector3.Zero;\r\n                return true;\r\n            }\r\n        }\r\n\r\n        result = position;\r\n        return false;\r\n    }\r\n\r\n    /// \u003Csummary\u003E\r\n    /// Debug don\u0027t use\r\n    /// \u003C/summary\u003E\r\n    /// \u003Cparam name=\u0022position\u0022\u003E\u003C/param\u003E\r\n    /// \u003Cparam name=\u0022title\u0022\u003E\u003C/param\u003E\r\n    /// \u003Creturns\u003E\u003C/returns\u003E\r\n    private bool TestPosition( Vector3 position, string title )\r\n    {\r\n        var testTrace = BuildTrace( _shrunkenBounds, position, position );\r\n\r\n        if ( testTrace.StartedSolid )\r\n        {\r\n            Log.Info( $\u0022[{RealTime.Now}]{title} {GameObject.Name} started solid at {position} against {testTrace.GameObject}\u0022 );\r\n            return true;\r\n        }\r\n\r\n        return false;\r\n    }\r\n\r\n    protected override void OnFixedUpdate()\r\n    {\r\n        base.OnFixedUpdate();\r\n\r\n        if ( !ManuallyUpdate \u0026\u0026 Active \u0026\u0026 !PhysicallySimulated ) // If we\u0027re not physically simulated we Move inside of OnFixedUpdate\r\n            Move( ActiveAutoTimeDelta );\r\n    }\r\n\r\n    protected override void OnDestroy()\r\n    {\r\n        base.OnDestroy();\r\n\r\n        Collider?.Destroy();\r\n        Body?.Destroy();\r\n    }\r\n\r\n    protected override void OnDisabled()\r\n    {\r\n        base.OnDisabled();\r\n\r\n        Collider?.Enabled = false;\r\n        Body?.Enabled = false;\r\n    }\r\n\r\n    protected override void OnEnabled()\r\n    {\r\n        base.OnEnabled();\r\n\r\n        Collider?.Enabled = true;\r\n        Body?.Enabled = true;\r\n    }\r\n\r\n    public override int ComponentVersion =\u003E 4;\r\n\r\n    [JsonUpgrader( typeof( ShrimpleCharacterController ), 3 )]\r\n    private static void UpdateTraceShape( JsonObject json )\r\n    {\r\n        if ( json.Remove( \u0022CylinderTrace\u0022, out var newNode ) \u0026\u0026 newNode is JsonValue boolNode \u0026\u0026 boolNode.TryGetValue\u003Cbool\u003E( out var isCylinder ) )\r\n            json[\u0022TraceShape\u0022] = isCylinder ? \u0022Cylinder\u0022 : \u0022Box\u0022;\r\n        else\r\n            json[\u0022TraceShape\u0022] = \u0022Box\u0022;\r\n    }\r\n\r\n    [JsonUpgrader( typeof( ShrimpleCharacterController ), 4 )]\r\n    private static void UpdateMaxGroundAngle( JsonObject json )\r\n    {\r\n        // Old default was 60f, new default is (30f, 60f)\r\n        if ( json.TryGetPropertyValue( \u0022MaxGroundAngle\u0022, out var oldValue ) \u0026\u0026 oldValue is JsonValue floatValue \u0026\u0026 floatValue.TryGetValue\u003Cfloat\u003E( out var oldAngle ) )\r\n        {\r\n            json.Remove( \u0022MaxGroundAngle\u0022 );\r\n            var startAngle = oldAngle * 0.75f;\r\n            json[\u0022MaxGroundAngle\u0022] = $\u0022{startAngle:F2} {oldAngle:F2}\u0022;\r\n        }\r\n    }\r\n}\r\n"}]}