├── Libraries
├── alex.iconify
│ ├── .version
│ ├── .bin
│ │ ├── manifest.json
│ │ ├── package.alex.iconify.dll
│ │ └── package.alex.iconify.xml
│ ├── readme.md
│ └── license.md
├── fish.debugoverlay
│ ├── .version
│ ├── README.md
│ ├── debugoverlay.sbproj
│ └── Code
│ │ ├── DebugOverlay.Base.cs
│ │ └── DebugOverlay.cs
└── facepunch.playercontroller
│ ├── .version
│ ├── Code
│ ├── Assembly.cs
│ ├── PlayerPusher.cs
│ ├── PlayerFootsteps.cs
│ └── PlayerController.cs
│ ├── UnitTests
│ ├── UnitTest.cs
│ └── LibraryTest.cs
│ ├── Editor
│ └── MyEditorMenu.cs
│ └── playercontroller.sbproj
├── code
├── Assembly.cs
├── Game
│ ├── PlayerSpawner.cs
│ ├── RodentPathNode.cs
│ └── RodentPathFollower.cs
└── Paths
│ ├── PathTerminator.cs
│ ├── StraightLinePath.cs
│ ├── CirclePath.cs
│ ├── QuadraticBezierPath.cs
│ └── PathComponent.cs
├── .gitignore
├── ProjectSettings
└── Collision.config
├── editor
├── PathEditorTool.cs
└── PathEditorWidgetWindow.cs
├── .sbproj
├── .editorconfig
└── Assets
└── scenes
├── minimal.scene
└── gameplay_test.scene
/Libraries/alex.iconify/.version:
--------------------------------------------------------------------------------
1 | 1.0.44428
--------------------------------------------------------------------------------
/Libraries/fish.debugoverlay/.version:
--------------------------------------------------------------------------------
1 | 1.0.60155
--------------------------------------------------------------------------------
/Libraries/facepunch.playercontroller/.version:
--------------------------------------------------------------------------------
1 | 1.0.56200
--------------------------------------------------------------------------------
/Libraries/alex.iconify/.bin/manifest.json:
--------------------------------------------------------------------------------
1 | [
2 | "package.base",
3 | "package.alex.iconify"
4 | ]
--------------------------------------------------------------------------------
/code/Assembly.cs:
--------------------------------------------------------------------------------
1 | global using Sandbox;
2 | global using System.Collections.Generic;
3 | global using System.Linq;
4 |
--------------------------------------------------------------------------------
/code/Game/PlayerSpawner.cs:
--------------------------------------------------------------------------------
1 | namespace Sandbox;
2 |
3 | public sealed class PlayerSpawner : Component
4 | {
5 | }
6 |
--------------------------------------------------------------------------------
/Libraries/alex.iconify/.bin/package.alex.iconify.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rndtrash/rodent-racing-league/master/Libraries/alex.iconify/.bin/package.alex.iconify.dll
--------------------------------------------------------------------------------
/Libraries/fish.debugoverlay/README.md:
--------------------------------------------------------------------------------
1 | ### DebugOverlay
2 |
3 | A shrimple implementation of the old DebugOverlay from the Entity System era...
4 |
5 | [**sbox.game**](https://sbox.game/fish/debugoverlay)
6 |
--------------------------------------------------------------------------------
/Libraries/alex.iconify/.bin/package.alex.iconify.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | package.alex.iconify
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/Libraries/facepunch.playercontroller/Code/Assembly.cs:
--------------------------------------------------------------------------------
1 | global using System;
2 | global using Sandbox;
3 | global using System.Linq;
4 | global using System.Threading.Tasks;
5 | global using System.Collections.Generic;
6 |
--------------------------------------------------------------------------------
/Libraries/facepunch.playercontroller/UnitTests/UnitTest.cs:
--------------------------------------------------------------------------------
1 | global using Microsoft.VisualStudio.TestTools.UnitTesting;
2 |
3 | [TestClass]
4 | public class TestInit
5 | {
6 | [AssemblyInitialize]
7 | public static void ClassInitialize( TestContext context )
8 | {
9 | Sandbox.Application.InitUnitTest();
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/Libraries/facepunch.playercontroller/Editor/MyEditorMenu.cs:
--------------------------------------------------------------------------------
1 | using Editor;
2 |
3 | public static class MyEditorMenu
4 | {
5 | [Menu( "Editor", "playercontroller/My Menu Option" )]
6 | public static void OpenMyMenu()
7 | {
8 | EditorUtility.DisplayDialog( "It worked!", "This is being called from your library's editor code!" );
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/Libraries/fish.debugoverlay/debugoverlay.sbproj:
--------------------------------------------------------------------------------
1 | {
2 | "Title": "DebugOverlay",
3 | "Type": "library",
4 | "Org": "fish",
5 | "Ident": "debugoverlay",
6 | "Schema": 1,
7 | "IncludeSourceFiles": false,
8 | "Resources": null,
9 | "PackageReferences": [],
10 | "EditorReferences": null,
11 | "Metadata": {
12 | "CsProjName": ""
13 | }
14 | }
--------------------------------------------------------------------------------
/code/Paths/PathTerminator.cs:
--------------------------------------------------------------------------------
1 | using Sandbox.Diagnostics;
2 |
3 | namespace TTP.Paths;
4 |
5 | [Category( "Paths" )]
6 | public sealed class PathTerminator : PathComponent
7 | {
8 | public override IList Sample( int resolution )
9 | {
10 | Assert.True( resolution > 0 );
11 |
12 | return new List { new(Transform.World, Width) };
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/Libraries/facepunch.playercontroller/UnitTests/LibraryTest.cs:
--------------------------------------------------------------------------------
1 | using Sandbox;
2 |
3 | [TestClass]
4 | public partial class LibraryTests
5 | {
6 | [TestMethod]
7 | public void SceneTest()
8 | {
9 | var scene = new Scene();
10 | using ( scene.Push() )
11 | {
12 | var go = new GameObject();
13 |
14 | Assert.AreEqual( 1, scene.Directory.GameObjectCount );
15 | }
16 | }
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # This file describes files and paths that should not be tracked by Git version control
3 | # https://git-scm.com/docs/gitignore
4 |
5 | # Auto-generated code editor files
6 | .vs/*
7 | .vscode/*
8 | *.csproj
9 | obj
10 | bin
11 | Properties/*
12 | code/obj/*
13 | code/Properties/*
14 |
15 | # Auto-generated asset related files
16 | *.generated.*
17 | *.*_c
18 | !*.shader_c
19 | *.los
20 | *.vpk
21 | *launchSettings.json
22 | *.sln
23 |
24 | *idea
--------------------------------------------------------------------------------
/Libraries/alex.iconify/readme.md:
--------------------------------------------------------------------------------
1 | # sbox-iconify
2 |
3 | 😀 A simple way to use many different icon packs in a s&box project.
4 |
5 | ## Usage
6 |
7 | ```html
8 |
9 | ```
10 |
11 | Full list of icons at [icones](https://icones.js.org/).
12 |
13 | ## More Info
14 |
15 | Icons are fetched at runtime from the [iconify](https://iconify.design/) API.
16 | These get cached in `FileSystem.Data` (JSON data alongside the SVG) so we don't have to keep requesting it.
17 |
18 |
--------------------------------------------------------------------------------
/Libraries/facepunch.playercontroller/playercontroller.sbproj:
--------------------------------------------------------------------------------
1 | {
2 | "Title": "playercontroller",
3 | "Type": "library",
4 | "Org": "facepunch",
5 | "Ident": "playercontroller",
6 | "Tags": null,
7 | "Schema": 1,
8 | "Resources": null,
9 | "PackageReferences": [],
10 | "EditorReferences": null,
11 | "Metadata": {
12 | "CsProjName": "",
13 | "Compiler": {
14 | "RootNamespace": "Sandbox",
15 | "DefineConstants": "SANDBOX;ADDON;DEBUG",
16 | "NoWarn": "1701;1702;1591;",
17 | "WarningsAsErrors": "",
18 | "Whitelist": true,
19 | "AssemblyReferences": [],
20 | "IgnoreFolders": [],
21 | "DistinctAssemblyReferences": []
22 | }
23 | }
24 | }
--------------------------------------------------------------------------------
/code/Paths/StraightLinePath.cs:
--------------------------------------------------------------------------------
1 | using Sandbox.Diagnostics;
2 |
3 | namespace TTP.Paths;
4 |
5 | [Category( "Paths" )]
6 | public sealed class StraightLinePath : PathComponent
7 | {
8 | public override IList Sample( int resolution )
9 | {
10 | Assert.True( resolution > 0 );
11 | Assert.IsValid( Next );
12 |
13 | var samples = new List( resolution );
14 | for ( var i = 0; i < resolution; i++ )
15 | {
16 | var fraction = (float)i / resolution;
17 | samples.Add( new PathSample(
18 | global::Transform.Lerp( Transform.World, Next.Transform.World, fraction, false ),
19 | MathX.Lerp( Width, Next.Width, fraction ) ) );
20 | }
21 |
22 | return samples;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/ProjectSettings/Collision.config:
--------------------------------------------------------------------------------
1 | {
2 | "Defaults": {
3 | "solid": "Collide",
4 | "trigger": "Trigger",
5 | "ladder": "Ignore",
6 | "water": "Trigger",
7 | "player": "Collide"
8 | },
9 | "Pairs": [
10 | {
11 | "a": "solid",
12 | "b": "solid",
13 | "r": "Collide"
14 | },
15 | {
16 | "a": "trigger",
17 | "b": "playerclip",
18 | "r": "Ignore"
19 | },
20 | {
21 | "a": "trigger",
22 | "b": "solid",
23 | "r": "Trigger"
24 | },
25 | {
26 | "a": "playerclip",
27 | "b": "solid",
28 | "r": "Collide"
29 | }
30 | ],
31 | "__guid": "71963170-dc68-418f-beeb-8fe4f94da370",
32 | "__schema": "configdata",
33 | "__type": "CollisionRules",
34 | "__version": 1
35 | }
--------------------------------------------------------------------------------
/code/Game/RodentPathNode.cs:
--------------------------------------------------------------------------------
1 | using TTP.Paths;
2 |
3 | namespace Sandbox;
4 |
5 | public sealed class RodentPathNode : Component
6 | {
7 | [RequireComponent] public PathComponent Node { get; set; }
8 |
9 | [Property] public float Height { get; set; } = 100;
10 | [Range( 1, 500 )] [Property] public int Resolution { get; set; } = 100;
11 |
12 | public RodentPathNode Next { get; private set; }
13 | public RodentPathNode Previous { get; private set; }
14 | public PathComponent.PathSample[] Samples { get; private set; }
15 |
16 | protected override void OnAwake()
17 | {
18 | Next = Node.Next.Components.Get();
19 | Previous = Node.Previous.Components.Get();
20 | Samples = Node.Sample( Resolution ).ToArray();
21 | }
22 |
23 | protected override void OnStart()
24 | {
25 | if ( Game.IsPlaying && Node.IsValid() )
26 | {
27 | Node.Destroy();
28 | Node = null;
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/code/Paths/CirclePath.cs:
--------------------------------------------------------------------------------
1 | using Sandbox.Diagnostics;
2 |
3 | namespace TTP.Paths;
4 |
5 | [Category( "Paths" )]
6 | public sealed class CirclePath : PathComponent
7 | {
8 | [Property] public GameObject ControlPoint { get; set; }
9 |
10 | public override IList Sample( int resolution )
11 | {
12 | // TODO:
13 | Assert.True( resolution > 0 );
14 | Assert.IsValid( Next );
15 | Assert.IsValid( ControlPoint );
16 |
17 | var samples = new List( resolution );
18 | for ( var i = 0; i < resolution; i++ )
19 | {
20 | var fraction = (float)i / resolution;
21 | var first = global::Transform.Lerp( Transform.World, ControlPoint.Transform.World, fraction, false );
22 | var second = global::Transform.Lerp( ControlPoint.Transform.World, Next.Transform.World, fraction, false );
23 | var final = global::Transform.Lerp( first, second, fraction, false );
24 | samples.Add( new PathSample( final, MathX.Lerp( Width, Next.Width, fraction ) ) );
25 | }
26 |
27 | return samples;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/editor/PathEditorTool.cs:
--------------------------------------------------------------------------------
1 | using Editor;
2 | using TTP.Paths;
3 |
4 | namespace Sandbox;
5 |
6 | ///
7 | /// Modify paths
8 | ///
9 | [EditorTool]
10 | [Title( "Paths" )]
11 | [Icon( "route" )]
12 | [Alias( "paths" )]
13 | [Group( "9" )]
14 | public sealed class PathEditorTool : EditorTool
15 | {
16 | private PathEditorWidgetWindow PathEditor;
17 | private PathEditorContext Context = new();
18 |
19 | public override void OnEnabled()
20 | {
21 | if ( GetSelectedComponent() is not { } node )
22 | {
23 | Selection.Clear();
24 | }
25 | else
26 | {
27 | Context.SelectedNode = node;
28 | }
29 |
30 | PathEditor = new PathEditorWidgetWindow( SceneOverlay, Context );
31 | AddOverlay( PathEditor, TextFlag.RightBottom );
32 | }
33 |
34 | public override void OnUpdate()
35 | {
36 | Context.SelectedNode = GetSelectedComponent();
37 | PathEditor.Visible = Context.SelectedNode is not null;
38 |
39 | if ( Context.SelectedNode is null )
40 | return;
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/Libraries/facepunch.playercontroller/Code/PlayerPusher.cs:
--------------------------------------------------------------------------------
1 |
2 | public sealed class PlayerPusher : Component
3 | {
4 | [Property] public float Radius { get; set; } = 100;
5 |
6 | protected override void DrawGizmos()
7 | {
8 | base.DrawGizmos();
9 |
10 | Gizmo.Draw.LineSphere( Vector3.Zero, Radius );
11 | }
12 |
13 | public static Vector3 GetPushVector( in Vector3 position, Scene scene, GameObject ignore )
14 | {
15 | Vector3 vec = default;
16 |
17 | foreach ( var pusher in scene.GetAllComponents() )
18 | {
19 | if ( pusher.GameObject.IsAncestor( ignore ) )
20 | continue;
21 |
22 | pusher.Collect( position, ref vec );
23 | }
24 |
25 | return vec;
26 | }
27 |
28 | private void Collect( Vector3 position, ref Vector3 output )
29 | {
30 | var delta = (position - Transform.Position);
31 | if ( delta.Length > Radius ) return;
32 |
33 | delta.z = 0; // ignore z
34 |
35 | var distanceDelta = (delta.Length / Radius);
36 |
37 | output += delta.Normal * (1.0f - distanceDelta);
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/.sbproj:
--------------------------------------------------------------------------------
1 | {
2 | "Title": "Rodent Racing League",
3 | "Type": "game",
4 | "Org": "local",
5 | "Ident": "rodent_racing_league",
6 | "Schema": 1,
7 | "IncludeSourceFiles": false,
8 | "Resources": null,
9 | "PackageReferences": [],
10 | "EditorReferences": null,
11 | "Metadata": {
12 | "MaxPlayers": 64,
13 | "MinPlayers": 1,
14 | "TickRate": 50,
15 | "GameNetworkType": "Multiplayer",
16 | "MapSelect": "Unrestricted",
17 | "MapList": [
18 | "facepunch.flatgrass"
19 | ],
20 | "RankType": "None",
21 | "PerMapRanking": false,
22 | "LeaderboardType": "None",
23 | "CsProjName": "",
24 | "StartupScene": "scenes/minimal.scene",
25 | "Compiler": {
26 | "RootNamespace": "RRL",
27 | "DefineConstants": "SANDBOX;ADDON;DEBUG",
28 | "NoWarn": "1701;1702;1591;",
29 | "WarningsAsErrors": "",
30 | "Whitelist": true,
31 | "AssemblyReferences": [],
32 | "IgnoreFolders": [
33 | "editor",
34 | "unittest"
35 | ],
36 | "DistinctAssemblyReferences": []
37 | }
38 | }
39 | }
--------------------------------------------------------------------------------
/Libraries/alex.iconify/license.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Facepunch
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/Libraries/facepunch.playercontroller/Code/PlayerFootsteps.cs:
--------------------------------------------------------------------------------
1 |
2 | public sealed class PlayerFootsteps : Component
3 | {
4 | [Property] SkinnedModelRenderer Source { get; set; }
5 |
6 | protected override void OnEnabled()
7 | {
8 | if ( Source is null )
9 | return;
10 |
11 | Source.OnFootstepEvent += OnEvent;
12 | }
13 |
14 | protected override void OnDisabled()
15 | {
16 | if ( Source is null )
17 | return;
18 |
19 | Source.OnFootstepEvent -= OnEvent;
20 | }
21 |
22 | TimeSince timeSinceStep;
23 |
24 | private void OnEvent( SceneModel.FootstepEvent e )
25 | {
26 | if ( timeSinceStep < 0.2f )
27 | return;
28 |
29 | var tr = Scene.Trace
30 | .Ray( e.Transform.Position + Vector3.Up * 20, e.Transform.Position + Vector3.Up * -20 )
31 | .Run();
32 |
33 | if ( !tr.Hit )
34 | return;
35 |
36 | if ( tr.Surface is null )
37 | return;
38 |
39 | timeSinceStep = 0;
40 |
41 | var sound = e.FootId == 0 ? tr.Surface.Sounds.FootLeft : tr.Surface.Sounds.FootRight;
42 | if ( sound is null ) return;
43 |
44 | var handle = Sound.Play( sound, tr.HitPosition + tr.Normal * 5 );
45 | handle.Volume *= e.Volume;
46 | handle.Update();
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/code/Paths/QuadraticBezierPath.cs:
--------------------------------------------------------------------------------
1 | using Sandbox.Diagnostics;
2 |
3 | namespace TTP.Paths;
4 |
5 | [Category( "Paths" )]
6 | public sealed class QuadraticBezierPath : PathComponent
7 | {
8 | [Property] public GameObject ControlPoint { get; set; }
9 |
10 | public override IList Sample( int resolution )
11 | {
12 | Assert.True( resolution > 0 );
13 | Assert.IsValid( Next );
14 | Assert.IsValid( ControlPoint );
15 |
16 | var samples = new List( resolution );
17 | for ( var i = 0; i < resolution; i++ )
18 | {
19 | var fraction = (float)i / resolution;
20 | // var first = global::Transform.Lerp( Transform.World, ControlPoint.Transform.World, fraction, false );
21 | // var second = global::Transform.Lerp( ControlPoint.Transform.World, Next.Transform.World, fraction, false );
22 | // var final = global::Transform.Lerp( first, second, fraction, false );
23 | // samples.Add( new PathSample( final, MathX.Lerp( Width, Next.Width, fraction ) ) );
24 | var first = Vector3.Lerp( Transform.World.Position, ControlPoint.Transform.World.Position, fraction,
25 | false );
26 | var second = Vector3.Lerp( ControlPoint.Transform.World.Position, Next.Transform.World.Position, fraction,
27 | false );
28 | var final = new Transform( Vector3.Lerp( first, second, fraction, false ),
29 | Rotation.Slerp( Transform.World.Rotation, Next.Transform.World.Rotation, fraction ) );
30 | samples.Add( new PathSample( final, MathX.Lerp( Width, Next.Width, fraction ) ) );
31 | }
32 |
33 | return samples;
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/Libraries/fish.debugoverlay/Code/DebugOverlay.Base.cs:
--------------------------------------------------------------------------------
1 | global using Sandbox;
2 | global using System;
3 | global using System.Collections.Generic;
4 |
5 | ///
6 | /// Badass library for rendering debug information.
7 | ///
8 | public sealed partial class DebugOverlay : GameObjectSystem
9 | {
10 | private static List<(
11 | Action Callback,
12 | TimeSince SinceCreated,
13 | float Duration
14 | )> _queue = new();
15 |
16 | public DebugOverlay( Scene scene ) : base( scene )
17 | {
18 | _queue.Clear();
19 | Listen( Stage.FinishUpdate, 0, RenderQueue, "DebugOverlay" );
20 | }
21 |
22 | private static void RenderQueue()
23 | {
24 | var tx = Gizmo.Settings == null ? Transform.Zero : Gizmo.Transform;
25 | if ( Gizmo.Settings != null ) Gizmo.Transform = Transform.Zero;
26 |
27 | for ( int i = 0; i < _queue.Count; i++ )
28 | {
29 | var obj = _queue[i];
30 | if ( obj.SinceCreated >= obj.Duration )
31 | {
32 | _queue.RemoveAt( i );
33 | continue;
34 | }
35 |
36 | obj.Callback();
37 | }
38 |
39 | if ( Gizmo.Settings != null ) Gizmo.Transform = tx;
40 | }
41 |
42 | private static void AddToQueue( Action callback, float time )
43 | {
44 | // ? XD
45 | if ( Gizmo.Draw == null )
46 | return;
47 |
48 | if ( time <= 0f )
49 | {
50 | var tx = Gizmo.Settings == null ? Transform.Zero : Gizmo.Transform;
51 | if ( Gizmo.Settings != null ) Gizmo.Transform = Transform.Zero;
52 | callback();
53 | if ( Gizmo.Settings != null ) Gizmo.Transform = tx;
54 | return;
55 | }
56 |
57 | _queue.Add( (
58 | Callback: callback,
59 | SinceCreated: 0f,
60 | Duration: time
61 | ) );
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/code/Paths/PathComponent.cs:
--------------------------------------------------------------------------------
1 | namespace TTP.Paths;
2 |
3 | [Category( "Paths" )]
4 | public abstract class PathComponent : Component
5 | {
6 | [Property] public PathComponent Previous { get; set; }
7 | [Property] public PathComponent Next { get; set; }
8 | [Property] public float Width { get; set; }
9 | [Property] [Range( 1, 100 )] public int Resolution { get; set; } = 10;
10 |
11 | protected override void OnDestroy()
12 | {
13 | if ( Previous.IsValid() && Next.IsValid() )
14 | {
15 | Previous.Next = Next;
16 | Next.Previous = Previous;
17 | }
18 | }
19 |
20 | protected override void OnUpdate()
21 | {
22 | if ( !Next.IsValid() && this is not PathTerminator )
23 | {
24 | var terminator = Components.Create();
25 | terminator.Previous = Previous;
26 | terminator.Width = Width;
27 | Destroy();
28 | return;
29 | }
30 | }
31 |
32 | protected override void DrawGizmos()
33 | {
34 | if ( Scene.IsEditor && !Game.IsPlaying )
35 | {
36 | var color = Gizmo.IsSelected ? Color.Yellow : Color.Green;
37 |
38 | if ( !Previous.IsValid() || !Next.IsValid() )
39 | {
40 | DebugOverlay.Sphere( new Sphere( Transform.Position, 10 ), color: color );
41 | }
42 |
43 | if ( Width > 0 )
44 | {
45 | var left = Transform.Rotation.Left * Width * 0.5f;
46 | DebugOverlay.Line( Transform.Position + left, Transform.Position - left, color );
47 | }
48 |
49 | if ( Next.IsValid() )
50 | {
51 | DebugOverlay.Line( Transform.Position, Next.Transform.Position );
52 |
53 | var samples = Sample();
54 | if ( samples.Count == 0 )
55 | return;
56 |
57 | if ( Width > 0 )
58 | {
59 | for ( var i = 1; i < samples.Count; i++ )
60 | {
61 | var prevSample = samples[i - 1];
62 | var sample = samples[i];
63 | var prevLeft = prevSample.Transform.Left * prevSample.Width * 0.5f;
64 | var left = sample.Transform.Left * sample.Width * 0.5f;
65 | DebugOverlay.Line( prevSample.Transform.Position + prevLeft, sample.Transform.Position + left,
66 | color );
67 | DebugOverlay.Line( prevSample.Transform.Position - prevLeft, sample.Transform.Position - left,
68 | color );
69 | DebugOverlay.Line( sample.Transform.Position + left,
70 | sample.Transform.Position - left, color );
71 | }
72 |
73 | var lastSample = samples[^1];
74 | var lastLeft = lastSample.Transform.Left * lastSample.Width * 0.5f;
75 | var nextLeft = Next.Transform.World.Left * Next.Width * 0.5f;
76 | DebugOverlay.Line( lastSample.Transform.Position + lastLeft,
77 | Next.Transform.World.Position + nextLeft,
78 | color );
79 | DebugOverlay.Line( lastSample.Transform.Position - lastLeft,
80 | Next.Transform.World.Position - nextLeft,
81 | color );
82 | }
83 | else
84 | {
85 | foreach ( var sample in samples )
86 | {
87 | DebugOverlay.Sphere( new Sphere( sample.Transform.Position, 2.5f ), color: color );
88 | DebugOverlay.Line( sample.Transform.Position,
89 | sample.Transform.Position + sample.Transform.Forward * 10, Color.White );
90 | }
91 | }
92 | }
93 | }
94 | }
95 |
96 | public record PathSample( Transform Transform, float Width );
97 |
98 | ///
99 | /// Split the path node into samples.
100 | ///
101 | /// Amount of sample points, > 0
102 | /// A list of path samples with `resolution` items
103 | public abstract IList Sample( int resolution );
104 |
105 | public IList Sample() => Sample( Resolution );
106 | }
107 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # Remove the line below if you want to inherit .editorconfig settings from higher directories
2 | root = true
3 |
4 | # C# files
5 | [*.{cs,razor}]
6 | indent_style = tab
7 | indent_size = 4
8 | tab_size = 4
9 |
10 | # New line preferences
11 | end_of_line = crlf
12 | insert_final_newline = true
13 |
14 |
15 | #### C# Coding Conventions ####
16 |
17 | # Expression-bodied members
18 | csharp_style_expression_bodied_accessors = true:silent
19 | csharp_style_expression_bodied_constructors = false:silent
20 | csharp_style_expression_bodied_indexers = true:silent
21 | csharp_style_expression_bodied_lambdas = true:silent
22 | csharp_style_expression_bodied_local_functions = false:silent
23 | csharp_style_expression_bodied_methods = false:silent
24 | csharp_style_expression_bodied_operators = false:silent
25 | csharp_style_expression_bodied_properties = true:silent
26 |
27 | # Pattern matching preferences
28 | csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
29 | csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
30 | csharp_style_prefer_not_pattern = true:suggestion
31 | csharp_style_prefer_pattern_matching = true:silent
32 | csharp_style_prefer_switch_expression = true:suggestion
33 |
34 | # Null-checking preferences
35 | csharp_style_conditional_delegate_call = true:suggestion
36 |
37 | # Code-block preferences
38 | csharp_prefer_braces = true:silent
39 |
40 | # Expression-level preferences
41 | csharp_prefer_simple_default_expression = true:suggestion
42 | csharp_style_deconstructed_variable_declaration = true:suggestion
43 | csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion
44 | csharp_style_inlined_variable_declaration = true:suggestion
45 | csharp_style_pattern_local_over_anonymous_function = true:suggestion
46 | csharp_style_prefer_index_operator = true:suggestion
47 | csharp_style_prefer_range_operator = true:suggestion
48 | csharp_style_throw_expression = true:suggestion
49 | csharp_style_unused_value_assignment_preference = discard_variable:suggestion
50 | csharp_style_unused_value_expression_statement_preference = discard_variable:silent
51 |
52 | # 'using' directive preferences
53 | csharp_using_directive_placement = outside_namespace:silent
54 |
55 | #### C# Formatting Rules ####
56 |
57 | # New line preferences
58 | csharp_new_line_before_catch = true
59 | csharp_new_line_before_else = true
60 | csharp_new_line_before_finally = true
61 | csharp_new_line_before_members_in_anonymous_types = true
62 | csharp_new_line_before_members_in_object_initializers = true
63 | csharp_new_line_before_open_brace = all
64 | csharp_new_line_between_query_expression_clauses = true
65 |
66 | # Indentation preferences
67 | csharp_indent_block_contents = true
68 | csharp_indent_braces = false
69 | csharp_indent_case_contents = true
70 | csharp_indent_case_contents_when_block = true
71 | csharp_indent_labels = no_change
72 | csharp_indent_switch_labels = true
73 |
74 | # Space preferences
75 | csharp_space_after_cast = false
76 | csharp_space_after_colon_in_inheritance_clause = true
77 | csharp_space_after_comma = true
78 | csharp_space_after_dot = false
79 | csharp_space_after_keywords_in_control_flow_statements = true
80 | csharp_space_after_semicolon_in_for_statement = true
81 | csharp_space_around_binary_operators = before_and_after
82 | csharp_space_around_declaration_statements = false
83 | csharp_space_before_colon_in_inheritance_clause = true
84 | csharp_space_before_comma = false
85 | csharp_space_before_dot = false
86 | csharp_space_before_open_square_brackets = false
87 | csharp_space_before_semicolon_in_for_statement = false
88 | csharp_space_between_empty_square_brackets = false
89 | csharp_space_between_method_call_empty_parameter_list_parentheses = false
90 | csharp_space_between_method_call_name_and_opening_parenthesis = false
91 | csharp_space_between_method_call_parameter_list_parentheses = true
92 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
93 | csharp_space_between_method_declaration_name_and_open_parenthesis = false
94 | csharp_space_between_method_declaration_parameter_list_parentheses = true
95 | csharp_space_between_parentheses = control_flow_statements
96 | csharp_space_between_square_brackets = false
97 |
98 | # Wrapping preferences
99 | csharp_preserve_single_line_blocks = true
100 | csharp_preserve_single_line_statements = true
--------------------------------------------------------------------------------
/code/Game/RodentPathFollower.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using TTP.Paths;
3 |
4 | namespace Sandbox;
5 |
6 | public sealed class RodentPathFollower : Component
7 | {
8 | [Property] public RodentPathNode First { get; set; }
9 | [Property] public float Speed { get; set; } = 100;
10 |
11 | [Category("Debug")]
12 | [ReadOnly]
13 | [Property]
14 | private RodentPathNode Current { get; set; }
15 |
16 | [Category("Debug")]
17 | [ReadOnly]
18 | [Property]
19 | private int CurrentSampleIndex { get; set; }
20 |
21 | ///
22 | /// A fraction between the current sample and the next one
23 | ///
24 | [Category("Debug")]
25 | [ReadOnly]
26 | [Property]
27 | private float CurrentSampleFraction { get; set; }
28 |
29 | protected override void OnStart()
30 | {
31 | Current = First;
32 | CurrentSampleIndex = 0;
33 | CurrentSampleFraction = 0;
34 | Transform.World = Current.Samples[CurrentSampleIndex].Transform;
35 | }
36 |
37 | protected override void OnUpdate()
38 | {
39 | var sample = Current.Samples[CurrentSampleIndex];
40 | var left = sample.Transform.Left * sample.Width * 0.5f;
41 | DebugOverlay.Line( sample.Transform.Position + left, sample.Transform.Position - left, Color.Cyan );
42 | }
43 |
44 | protected override void OnFixedUpdate()
45 | {
46 | if ( !Game.IsPlaying )
47 | return;
48 |
49 | Advance( Time.Delta * Speed );
50 | }
51 |
52 | public void Advance( float distance )
53 | {
54 | if ( !Current.IsValid() || !Current.Next.IsValid() )
55 | return;
56 |
57 | var isForward = true;
58 | if ( distance < 0 )
59 | {
60 | isForward = false;
61 | distance = Math.Abs( distance );
62 | }
63 |
64 | float currentDistance = 0;
65 |
66 | // First step: travel through the current sample
67 | var current = Current.Samples[CurrentSampleIndex];
68 | var next = GetNextSample();
69 | var sampleDistance = current.Transform.Position.Distance( next.Transform.Position );
70 | var sampleBorderDistance = sampleDistance * (isForward ? 1 - CurrentSampleFraction : CurrentSampleFraction);
71 | if ( distance <= sampleBorderDistance )
72 | {
73 | if ( isForward )
74 | CurrentSampleFraction += distance / sampleDistance;
75 | else
76 | CurrentSampleFraction -= distance / sampleDistance;
77 |
78 | currentDistance = distance;
79 | }
80 | else
81 | {
82 | currentDistance += sampleBorderDistance;
83 |
84 | if ( isForward )
85 | {
86 | IncrementSampleIndex();
87 | }
88 | else
89 | {
90 | DecrementSampleIndex();
91 | }
92 | }
93 |
94 | // Second step: if it wasn't enough, travel through all the samples
95 | while ( currentDistance < distance )
96 | {
97 | current = Current.Samples[CurrentSampleIndex];
98 | next = GetNextSample();
99 |
100 | sampleDistance = current.Transform.Position.Distance( next.Transform.Position );
101 | var distanceLeft = distance - currentDistance;
102 | if ( sampleDistance >= distanceLeft )
103 | {
104 | CurrentSampleFraction = distanceLeft / sampleDistance;
105 | if ( !isForward )
106 | {
107 | CurrentSampleFraction = 1 - CurrentSampleFraction;
108 | }
109 |
110 | currentDistance = distance;
111 | }
112 | else
113 | {
114 | if ( isForward )
115 | {
116 | IncrementSampleIndex();
117 | }
118 | else
119 | {
120 | DecrementSampleIndex();
121 | }
122 |
123 | currentDistance += sampleDistance;
124 | }
125 | }
126 |
127 | // Third step: set the position
128 | Transform.World = global::Transform.Lerp( current.Transform, next.Transform,
129 | CurrentSampleFraction, true );
130 | Transform.ClearInterpolation();
131 | }
132 |
133 | private PathComponent.PathSample GetNextSample() =>
134 | CurrentSampleIndex + 1 < Current.Samples.Length
135 | ? Current.Samples[CurrentSampleIndex + 1]
136 | : Current.Next.Samples[0];
137 |
138 | private PathComponent.PathSample GetPreviousSample() =>
139 | CurrentSampleIndex - 1 >= 0
140 | ? Current.Samples[CurrentSampleIndex - 1]
141 | : Current.Previous.Samples[^1];
142 |
143 | private void IncrementSampleIndex()
144 | {
145 | CurrentSampleIndex++;
146 | if ( CurrentSampleIndex >= Current.Samples.Length )
147 | {
148 | Current = Current.Next;
149 | CurrentSampleIndex = 0;
150 | }
151 | }
152 |
153 | private void DecrementSampleIndex()
154 | {
155 | CurrentSampleIndex--;
156 | if ( CurrentSampleIndex < 0 )
157 | {
158 | Current = Current.Previous;
159 | CurrentSampleIndex = Current.Samples.Length - 1;
160 | }
161 | }
162 | }
163 |
--------------------------------------------------------------------------------
/Libraries/fish.debugoverlay/Code/DebugOverlay.cs:
--------------------------------------------------------------------------------
1 | public enum DebugStyle : byte
2 | {
3 | Line,
4 | Solid
5 | }
6 |
7 | public partial class DebugOverlay
8 | {
9 | ///
10 | /// Renders a BBox for a specific amount of time.
11 | ///
12 | ///
13 | ///
14 | ///
15 | ///
16 | public static void BBox( BBox bbox, DebugStyle style = DebugStyle.Line, Color? color = null, float time = 0f )
17 | => AddToQueue( () =>
18 | {
19 | Gizmo.Draw.Color = color ?? Color.Yellow;
20 | switch ( style )
21 | {
22 | case DebugStyle.Line: Gizmo.Draw.LineBBox( bbox ); break;
23 | case DebugStyle.Solid: Gizmo.Draw.SolidBox( bbox ); break;
24 | }
25 | }, time );
26 |
27 | ///
28 | /// Renders a Sphere for a specific amount of time.
29 | ///
30 | ///
31 | ///
32 | ///
33 | ///
34 | public static void Sphere( Sphere sphere, DebugStyle style = DebugStyle.Line, Color? color = null, float time = 0f )
35 | => AddToQueue( () =>
36 | {
37 | Gizmo.Draw.Color = color ?? Color.Yellow;
38 | switch ( style )
39 | {
40 | case DebugStyle.Line: Gizmo.Draw.LineSphere( sphere ); break;
41 | case DebugStyle.Solid: Gizmo.Draw.SolidSphere( sphere.Center, sphere.Radius ); break;
42 | }
43 | }, time );
44 |
45 | ///
46 | /// Renders a line for a specific amount of time.
47 | ///
48 | ///
49 | ///
50 | ///
51 | ///
52 | public static void Line( Vector3 a, Vector3 b, Color? color = null, float time = 0f )
53 | => AddToQueue( () =>
54 | {
55 | Gizmo.Draw.Color = color ?? Color.Yellow;
56 | Gizmo.Draw.Line( a, b );
57 | }, time );
58 |
59 | ///
60 | /// Renders screenspace text at a 3D-position for a specific amount of time.
61 | ///
62 | ///
63 | ///
64 | ///
65 | ///
66 | ///
67 | ///
68 | ///
69 | public static void Text( string text, Vector3 pos, string font = "Consolas", float size = 18, TextFlag flags = TextFlag.LeftTop, Color? color = null, float time = 0f )
70 | => AddToQueue( () =>
71 | {
72 | var position = Game.ActiveScene.Camera.PointToScreenPixels( pos );
73 | Gizmo.Draw.Color = color ?? Color.Yellow;
74 | Gizmo.Draw.ScreenText( text, position, font, size, flags );
75 | }, time );
76 |
77 | ///
78 | /// Renders the result of a SceneTrace for a specific amount of time.
79 | ///
80 | ///
81 | ///
82 | public static void Trace( SceneTraceResult tr, float time = 0f )
83 | => AddToQueue( () =>
84 | {
85 | Gizmo.Draw.Color = Color.Yellow;
86 | Gizmo.Draw.Line( tr.StartPosition, tr.EndPosition );
87 |
88 | Gizmo.Draw.Color = tr.Hit ? Color.Blue : Color.Red;
89 | Gizmo.Draw.LineSphere( new Sphere( tr.EndPosition, 2f ) );
90 |
91 | // If trace is hit.
92 | if ( tr.GameObject.IsValid() )
93 | {
94 | var position = Game.ActiveScene.Camera.PointToScreenPixels( tr.EndPosition ) + Vector2.Left * 30f;
95 | var text = $"{tr.GameObject.Name}\n{tr.Component}\n{tr.GameObject.Id}";
96 | Gizmo.Draw.Color = Color.Yellow;
97 | Gizmo.Draw.ScreenText( text, position, "Consolas", 18 );
98 | }
99 | }, time );
100 |
101 | ///
102 | /// Renders a Model for a specific amount of time.
103 | ///
104 | ///
105 | ///
106 | ///
107 | public static void Model( Model model, Transform transform, float time = 0f )
108 | => AddToQueue( () =>
109 | {
110 | Gizmo.Draw.Model( model, transform );
111 | }, time );
112 |
113 | ///
114 | /// Renders a Texture for a specific amount of time.
115 | ///
116 | ///
117 | ///
118 | ///
119 | ///
120 | ///
121 | public static void Sprite( Texture texture, Vector3 position, float size, Color? color = null, float time = 0f )
122 | => AddToQueue( () =>
123 | {
124 | Gizmo.Draw.Color = color ?? Color.White;
125 | Gizmo.Draw.Sprite( position, size, texture );
126 | }, time );
127 | }
128 |
--------------------------------------------------------------------------------
/editor/PathEditorWidgetWindow.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using Editor;
4 | using Sandbox.Diagnostics;
5 | using TTP.Paths;
6 |
7 | namespace Sandbox;
8 |
9 | public sealed class PathEditorContext
10 | {
11 | public PathComponent SelectedNode { get; set; }
12 | }
13 |
14 | public sealed class PathEditorWidgetWindow : WidgetWindow
15 | {
16 | public delegate PathComponent AddPathComponent( GameObject nodeObject, GameObject nextObject );
17 |
18 | public record PathNodeType( string Name, string Icon, AddPathComponent Make );
19 |
20 | // TODO: make it static readonly later because the static variables are hotloaded poorly
21 | private List PathNodeTypes = new()
22 | {
23 | new PathNodeType( "Straight", "timeline", ( nodeObject, nextObject ) =>
24 | {
25 | var path = nodeObject.Components.Create();
26 |
27 | return path;
28 | } ),
29 | new PathNodeType( "Quadratic Bézier curve", "line_curve", ( nodeObject, nextObject ) =>
30 | {
31 | var path = nodeObject.Components.Create();
32 | var controlPointObject = SceneEditorSession.Active.Scene.CreateObject();
33 | controlPointObject.Name = "Control Point";
34 | controlPointObject.Transform.World =
35 | Transform.Lerp( nodeObject.Transform.World, nextObject.Transform.World, 0.5f, true );
36 | controlPointObject.SetParent( nodeObject );
37 | path.ControlPoint = controlPointObject;
38 |
39 | return path;
40 | } ),
41 | new PathNodeType( "Circle", "circle", ( nodeObject, nextObject ) =>
42 | {
43 | Log.Error( "TODO: Circle" );
44 | return null;
45 | } ),
46 | new PathNodeType( "Crossroad Side", "line_end_circle", ( nodeObject, nextObject ) =>
47 | {
48 | Log.Error( "TODO: Crossroad Side" );
49 | return null;
50 | } ),
51 | new PathNodeType( "Crossroad Center", "hub", ( nodeObject, nextObject ) =>
52 | {
53 | Log.Error( "TODO: Crossroad Center" );
54 | return null;
55 | } ),
56 | };
57 |
58 | private PathEditorContext Context;
59 |
60 | public PathEditorWidgetWindow( Widget parent, PathEditorContext context ) : base( parent, "Path Editor" )
61 | {
62 | Context = context;
63 |
64 | Layout = Layout.Column();
65 | Layout.Margin = 8;
66 | Layout.Spacing = 8;
67 |
68 | var pathTypeSelect = new SegmentedControl();
69 | foreach ( var pathNodeType in PathNodeTypes )
70 | {
71 | pathTypeSelect.AddOption( pathNodeType.Name, pathNodeType.Icon );
72 | }
73 |
74 | pathTypeSelect.OnSelectedChanged += s =>
75 | {
76 | Log.Info( s );
77 | };
78 |
79 | Layout.Add( pathTypeSelect );
80 |
81 | var actionButtonsRow = Layout.Row();
82 | actionButtonsRow.Spacing = 8;
83 | {
84 | var addButton = new Button.Primary( "Add", "add" );
85 | addButton.Clicked += () => AddNewNode( pathTypeSelect.SelectedIndex );
86 | actionButtonsRow.Add( addButton );
87 |
88 | actionButtonsRow.Add( new Button( "Replace", "refresh" ) );
89 |
90 | var deleteButton = new Button( "Delete", "delete" );
91 | deleteButton.Clicked += () => DeleteSelectedNode();
92 | actionButtonsRow.Add( deleteButton );
93 | }
94 | Layout.Add( actionButtonsRow );
95 | }
96 |
97 | private void AddNewNode( int index )
98 | {
99 | Assert.True( index >= 0 && index < PathNodeTypes.Count );
100 |
101 | GameObject lastGameObject;
102 | PathComponent previous = null;
103 | float width = 10;
104 |
105 | if ( Context.SelectedNode.IsValid() )
106 | {
107 | // TODO: detect infinite loops
108 | // Find the last node in a path
109 | var currentPathTerminator = Context.SelectedNode;
110 | while ( currentPathTerminator is not PathTerminator && currentPathTerminator.Next.IsValid() )
111 | currentPathTerminator = currentPathTerminator.Next;
112 |
113 | lastGameObject = currentPathTerminator.GameObject;
114 | previous = currentPathTerminator.Previous;
115 | width = currentPathTerminator.Width;
116 | currentPathTerminator.Destroy();
117 | }
118 | else
119 | {
120 | lastGameObject = SceneEditorSession.Active.Scene.CreateObject();
121 | lastGameObject.Transform.Position = Gizmo.CameraTransform.Position + Gizmo.CameraTransform.Forward * 100;
122 | lastGameObject.Transform.Rotation = Gizmo.CameraTransform.Rotation.Angles().WithPitch( 0 );
123 | }
124 |
125 | var terminatorGameObject = SceneEditorSession.Active.Scene.CreateObject();
126 | terminatorGameObject.Parent = lastGameObject.Parent;
127 | terminatorGameObject.Transform.Position =
128 | lastGameObject.Transform.Position + lastGameObject.Transform.Rotation.Forward * 100;
129 | terminatorGameObject.Transform.Rotation = lastGameObject.Transform.Rotation;
130 |
131 | var terminator = terminatorGameObject.Components.Create();
132 | terminator.Width = width;
133 |
134 | var newComponent = PathNodeTypes[index].Make( lastGameObject, terminatorGameObject );
135 | newComponent.Width = width;
136 |
137 | terminator.Previous = newComponent;
138 | newComponent.Previous = previous;
139 | newComponent.Next = terminator;
140 | if ( previous.IsValid() )
141 | previous.Next = newComponent;
142 | }
143 |
144 | private void DeleteSelectedNode()
145 | {
146 | if ( !Context.SelectedNode.IsValid() )
147 | return;
148 |
149 | var next = Context.SelectedNode.Next;
150 | var previous = Context.SelectedNode.Previous;
151 | if ( previous.IsValid() )
152 | previous.Next = next;
153 | if ( next.IsValid() )
154 | next.Previous = previous;
155 |
156 | Context.SelectedNode.Destroy();
157 | }
158 | }
159 |
--------------------------------------------------------------------------------
/Libraries/facepunch.playercontroller/Code/PlayerController.cs:
--------------------------------------------------------------------------------
1 | using Sandbox.Citizen;
2 |
3 | [Group( "Walker" )]
4 | [Title( "Walker - Player Controller" )]
5 | public sealed class PlayerController : Component
6 | {
7 | [Property] public CharacterController CharacterController { get; set; }
8 | [Property] public float CrouchMoveSpeed { get; set; } = 64.0f;
9 | [Property] public float WalkMoveSpeed { get; set; } = 190.0f;
10 | [Property] public float RunMoveSpeed { get; set; } = 190.0f;
11 | [Property] public float SprintMoveSpeed { get; set; } = 320.0f;
12 |
13 | [Property] public CitizenAnimationHelper AnimationHelper { get; set; }
14 |
15 | [Sync] public bool Crouching { get; set; }
16 | [Sync] public Angles EyeAngles { get; set; }
17 | [Sync] public Vector3 WishVelocity { get; set; }
18 |
19 | public bool WishCrouch;
20 | public float EyeHeight = 64;
21 |
22 | protected override void OnUpdate()
23 | {
24 | if ( !IsProxy )
25 | {
26 | MouseInput();
27 | Transform.Rotation = new Angles( 0, EyeAngles.yaw, 0 );
28 | }
29 |
30 | UpdateAnimation();
31 | }
32 |
33 | protected override void OnFixedUpdate()
34 | {
35 | if ( IsProxy )
36 | return;
37 |
38 | CrouchingInput();
39 | MovementInput();
40 | }
41 |
42 | private void MouseInput()
43 | {
44 | var e = EyeAngles;
45 | e += Input.AnalogLook;
46 | e.pitch = e.pitch.Clamp( -90, 90 );
47 | e.roll = 0.0f;
48 | EyeAngles = e;
49 | }
50 |
51 | float CurrentMoveSpeed
52 | {
53 | get
54 | {
55 | if ( Crouching ) return CrouchMoveSpeed;
56 | if ( Input.Down( "run" ) ) return SprintMoveSpeed;
57 | if ( Input.Down( "walk" ) ) return WalkMoveSpeed;
58 |
59 | return RunMoveSpeed;
60 | }
61 | }
62 |
63 | RealTimeSince lastGrounded;
64 | RealTimeSince lastUngrounded;
65 | RealTimeSince lastJump;
66 |
67 | float GetFriction()
68 | {
69 | if ( CharacterController.IsOnGround ) return 6.0f;
70 |
71 | // air friction
72 | return 0.2f;
73 | }
74 |
75 | private void MovementInput()
76 | {
77 | if ( CharacterController is null )
78 | return;
79 |
80 | var cc = CharacterController;
81 |
82 | Vector3 halfGravity = Scene.PhysicsWorld.Gravity * Time.Delta * 0.5f;
83 |
84 | WishVelocity = Input.AnalogMove;
85 |
86 | if ( lastGrounded < 0.2f && lastJump > 0.3f && Input.Pressed( "jump" ) )
87 | {
88 | lastJump = 0;
89 | cc.Punch( Vector3.Up * 300 );
90 | }
91 |
92 | if ( !WishVelocity.IsNearlyZero() )
93 | {
94 | WishVelocity = new Angles( 0, EyeAngles.yaw, 0 ).ToRotation() * WishVelocity;
95 | WishVelocity = WishVelocity.WithZ( 0 );
96 | WishVelocity = WishVelocity.ClampLength( 1 );
97 | WishVelocity *= CurrentMoveSpeed;
98 |
99 | if ( !cc.IsOnGround )
100 | {
101 | WishVelocity = WishVelocity.ClampLength( 50 );
102 | }
103 | }
104 |
105 |
106 | cc.ApplyFriction( GetFriction() );
107 |
108 | if ( cc.IsOnGround )
109 | {
110 | cc.Accelerate( WishVelocity );
111 | cc.Velocity = CharacterController.Velocity.WithZ( 0 );
112 | }
113 | else
114 | {
115 | cc.Velocity += halfGravity;
116 | cc.Accelerate( WishVelocity );
117 |
118 | }
119 |
120 | //
121 | // Don't walk through other players, let them push you out of the way
122 | //
123 | var pushVelocity = PlayerPusher.GetPushVector( Transform.Position + Vector3.Up * 40.0f, Scene, GameObject );
124 | if ( !pushVelocity.IsNearlyZero() )
125 | {
126 | var travelDot = cc.Velocity.Dot( pushVelocity.Normal );
127 | if ( travelDot < 0 )
128 | {
129 | cc.Velocity -= pushVelocity.Normal * travelDot * 0.6f;
130 | }
131 |
132 | cc.Velocity += pushVelocity * 128.0f;
133 | }
134 |
135 | cc.Move();
136 |
137 | if ( !cc.IsOnGround )
138 | {
139 | cc.Velocity += halfGravity;
140 | }
141 | else
142 | {
143 | cc.Velocity = cc.Velocity.WithZ( 0 );
144 | }
145 |
146 | if ( cc.IsOnGround )
147 | {
148 | lastGrounded = 0;
149 | }
150 | else
151 | {
152 | lastUngrounded = 0;
153 | }
154 | }
155 | float DuckHeight = (64 - 36);
156 |
157 | bool CanUncrouch()
158 | {
159 | if ( !Crouching ) return true;
160 | if ( lastUngrounded < 0.2f ) return false;
161 |
162 | var tr = CharacterController.TraceDirection( Vector3.Up * DuckHeight );
163 | return !tr.Hit; // hit nothing - we can!
164 | }
165 |
166 | public void CrouchingInput()
167 | {
168 | WishCrouch = Input.Down( "duck" );
169 |
170 | if ( WishCrouch == Crouching )
171 | return;
172 |
173 | // crouch
174 | if ( WishCrouch )
175 | {
176 | CharacterController.Height = 36;
177 | Crouching = WishCrouch;
178 |
179 | // if we're not on the ground, slide up our bbox so when we crouch
180 | // the bottom shrinks, instead of the top, which will mean we can reach
181 | // places by crouch jumping that we couldn't.
182 | if ( !CharacterController.IsOnGround )
183 | {
184 | CharacterController.MoveTo( Transform.Position += Vector3.Up * DuckHeight, false );
185 | Transform.ClearLerp();
186 | EyeHeight -= DuckHeight;
187 | }
188 |
189 | return;
190 | }
191 |
192 | // uncrouch
193 | if ( !WishCrouch )
194 | {
195 | if ( !CanUncrouch() ) return;
196 |
197 | CharacterController.Height = 64;
198 | Crouching = WishCrouch;
199 | return;
200 | }
201 |
202 |
203 | }
204 |
205 | private void UpdateCamera()
206 | {
207 | var camera = Scene.GetAllComponents().Where( x => x.IsMainCamera ).FirstOrDefault();
208 | if ( camera is null ) return;
209 |
210 | var targetEyeHeight = Crouching ? 28 : 64;
211 | EyeHeight = EyeHeight.LerpTo( targetEyeHeight, RealTime.Delta * 10.0f );
212 |
213 | var targetCameraPos = Transform.Position + new Vector3( 0, 0, EyeHeight );
214 |
215 | // smooth view z, so when going up and down stairs or ducking, it's smooth af
216 | if ( lastUngrounded > 0.2f )
217 | {
218 | targetCameraPos.z = camera.Transform.Position.z.LerpTo( targetCameraPos.z, RealTime.Delta * 25.0f );
219 | }
220 |
221 | camera.Transform.Position = targetCameraPos;
222 | camera.Transform.Rotation = EyeAngles;
223 | camera.FieldOfView = Preferences.FieldOfView;
224 | }
225 |
226 | protected override void OnPreRender()
227 | {
228 | UpdateBodyVisibility();
229 |
230 | if ( IsProxy )
231 | return;
232 |
233 | UpdateCamera();
234 | }
235 |
236 | private void UpdateAnimation()
237 | {
238 | if ( AnimationHelper is null ) return;
239 |
240 | var wv = WishVelocity.Length;
241 |
242 | AnimationHelper.WithWishVelocity( WishVelocity );
243 | AnimationHelper.WithVelocity( CharacterController.Velocity );
244 | AnimationHelper.IsGrounded = CharacterController.IsOnGround;
245 | AnimationHelper.DuckLevel = Crouching ? 1.0f : 0.0f;
246 |
247 | AnimationHelper.MoveStyle = wv < 160f ? CitizenAnimationHelper.MoveStyles.Walk : CitizenAnimationHelper.MoveStyles.Run;
248 |
249 | var lookDir = EyeAngles.ToRotation().Forward * 1024;
250 | AnimationHelper.WithLook( lookDir, 1, 0.5f, 0.25f );
251 | }
252 |
253 | private void UpdateBodyVisibility()
254 | {
255 | if ( AnimationHelper is null )
256 | return;
257 |
258 | var renderMode = ModelRenderer.ShadowRenderType.On;
259 | if ( !IsProxy ) renderMode = ModelRenderer.ShadowRenderType.ShadowsOnly;
260 |
261 | AnimationHelper.Target.RenderType = renderMode;
262 |
263 | foreach ( var clothing in AnimationHelper.Target.Components.GetAll( FindMode.InChildren ) )
264 | {
265 | if ( !clothing.Tags.Has( "clothing" ) )
266 | continue;
267 |
268 | clothing.RenderType = renderMode;
269 | }
270 | }
271 |
272 | }
273 |
--------------------------------------------------------------------------------
/Assets/scenes/minimal.scene:
--------------------------------------------------------------------------------
1 | {
2 | "Id": "9f832399-4887-46b0-8f21-2ee284f538e2",
3 | "GameObjects": [
4 | {
5 | "Id": "bfc59c12-1ed2-4f91-8956-a95a315eac3c",
6 | "Name": "Sun",
7 | "Rotation": "-0.0729315,0.4822396,0.1305433,0.8631827",
8 | "Enabled": true,
9 | "Components": [
10 | {
11 | "__type": "DirectionalLight",
12 | "FogMode": "Enabled",
13 | "FogStrength": 1,
14 | "LightColor": "0.94419,0.97767,1,1",
15 | "Shadows": true,
16 | "SkyColor": "0.2532,0.32006,0.35349,1"
17 | }
18 | ]
19 | },
20 | {
21 | "Id": "00344a8c-fa5e-45ae-b12a-10bb781a1dc3",
22 | "Name": "2D Skybox",
23 | "Enabled": true,
24 | "Components": [
25 | {
26 | "__type": "SkyBox2D",
27 | "SkyMaterial": "materials/skybox/light_test_sky_sunny03.vmat",
28 | "Tint": "1,1,1,1"
29 | },
30 | {
31 | "__type": "EnvmapProbe",
32 | "Bounds": {
33 | "Mins": "-512,-512,-512",
34 | "Maxs": "512,512,512"
35 | },
36 | "Feathering": 0.02,
37 | "Projection": "Sphere",
38 | "Texture": "textures/cubemaps/default2.vtex",
39 | "TintColor": "1,1,1,1"
40 | }
41 | ]
42 | },
43 | {
44 | "Id": "6ad70641-3c6c-4402-9c85-9a4969af4764",
45 | "Name": "Plane",
46 | "Scale": "5,5,5",
47 | "Enabled": true,
48 | "Components": [
49 | {
50 | "__type": "ModelRenderer",
51 | "BodyGroups": 18446744073709551615,
52 | "MaterialGroup": null,
53 | "MaterialOverride": "materials/default.vmat",
54 | "Model": "models/dev/plane.vmdl",
55 | "RenderType": "On",
56 | "Tint": "0.39546,0.51163,0.27128,1"
57 | },
58 | {
59 | "__type": "BoxCollider",
60 | "Center": "0,0,-5",
61 | "IsTrigger": false,
62 | "Scale": "100,100,10",
63 | "Static": false,
64 | "Surface": null
65 | }
66 | ]
67 | },
68 | {
69 | "Id": "3c2490ef-54a0-49bb-8f13-490e40aa51d1",
70 | "Name": "Cube",
71 | "Position": "21.41682,74.1244,14.40159",
72 | "Rotation": "0.00000001819328,-0.00000000000000008235059,0.3052325,0.952278",
73 | "Scale": "0.5632889,0.5632889,0.5632889",
74 | "Enabled": true,
75 | "Components": [
76 | {
77 | "__type": "ModelRenderer",
78 | "BodyGroups": 18446744073709551615,
79 | "MaterialGroup": null,
80 | "MaterialOverride": "materials/default.vmat",
81 | "Model": "models/dev/box.vmdl",
82 | "RenderType": "On",
83 | "Tint": "1,0,0.93333,1"
84 | },
85 | {
86 | "__type": "BoxCollider",
87 | "Center": "0,0,0",
88 | "IsTrigger": false,
89 | "Scale": "50,50,50",
90 | "Static": false,
91 | "Surface": null
92 | },
93 | {
94 | "__type": "Rigidbody",
95 | "AngularDamping": 0,
96 | "Gravity": true,
97 | "LinearDamping": 0,
98 | "Locking": {},
99 | "RigidbodyFlags": 0,
100 | "StartAsleep": false
101 | }
102 | ]
103 | },
104 | {
105 | "Id": "523e3e8f-a4ec-4ec1-af9a-d86ffc9c17e1",
106 | "Name": "Cube (1)",
107 | "Position": "40.81348,46.97572,14.40159",
108 | "Rotation": "0.00000001819328,-0.00000000000000008235059,0.3052325,0.952278",
109 | "Scale": "0.5632889,0.5632889,0.5632889",
110 | "Enabled": true,
111 | "Components": [
112 | {
113 | "__type": "ModelRenderer",
114 | "BodyGroups": 18446744073709551615,
115 | "MaterialGroup": null,
116 | "MaterialOverride": "materials/default.vmat",
117 | "Model": "models/dev/box.vmdl",
118 | "RenderType": "On",
119 | "Tint": "1,0,0.93333,1"
120 | },
121 | {
122 | "__type": "BoxCollider",
123 | "Center": "0,0,0",
124 | "IsTrigger": false,
125 | "Scale": "50,50,50",
126 | "Static": false,
127 | "Surface": null
128 | },
129 | {
130 | "__type": "Rigidbody",
131 | "AngularDamping": 0,
132 | "Gravity": true,
133 | "LinearDamping": 0,
134 | "Locking": {},
135 | "RigidbodyFlags": 0,
136 | "StartAsleep": false
137 | }
138 | ]
139 | },
140 | {
141 | "Id": "5b483a09-bbf2-4949-98c7-a73b789d0ee7",
142 | "Name": "Cube (2)",
143 | "Position": "49.53707,34.08896,43.67614",
144 | "Rotation": "0.00000001819328,-0.00000000000000008235059,0.3052325,0.952278",
145 | "Scale": "0.5632889,0.5632889,0.5632889",
146 | "Enabled": true,
147 | "Components": [
148 | {
149 | "__type": "ModelRenderer",
150 | "BodyGroups": 18446744073709551615,
151 | "MaterialGroup": null,
152 | "MaterialOverride": "materials/default.vmat",
153 | "Model": "models/dev/box.vmdl",
154 | "RenderType": "On",
155 | "Tint": "1,0,0.93333,1"
156 | },
157 | {
158 | "__type": "BoxCollider",
159 | "Center": "0,0,0",
160 | "IsTrigger": false,
161 | "Scale": "50,50,50",
162 | "Static": false,
163 | "Surface": null
164 | },
165 | {
166 | "__type": "Rigidbody",
167 | "AngularDamping": 0,
168 | "Gravity": true,
169 | "LinearDamping": 0,
170 | "Locking": {},
171 | "RigidbodyFlags": 0,
172 | "StartAsleep": false
173 | }
174 | ]
175 | },
176 | {
177 | "Id": "3ee1c9f4-07be-4e0b-8b23-67bee2d8ec8a",
178 | "Name": "Camera",
179 | "Position": "-267.452,-379.653,297.7903",
180 | "Rotation": "-0.1448582,0.2860239,0.4279631,0.8450171",
181 | "Enabled": true,
182 | "Components": [
183 | {
184 | "__type": "CameraComponent",
185 | "BackgroundColor": "0.33333,0.46275,0.52157,1",
186 | "ClearFlags": "All",
187 | "FieldOfView": 60,
188 | "IsMainCamera": true,
189 | "Orthographic": false,
190 | "OrthographicHeight": 1204,
191 | "Priority": 1,
192 | "RenderExcludeTags": "",
193 | "RenderTags": "",
194 | "TargetEye": "None",
195 | "Viewport": "0,0,1,1",
196 | "ZFar": 10000,
197 | "ZNear": 10
198 | },
199 | {
200 | "__type": "Bloom",
201 | "BloomColor": {
202 | "color": [
203 | {
204 | "c": "1,1,1,1"
205 | },
206 | {
207 | "t": 1,
208 | "c": "1,1,1,1"
209 | }
210 | ]
211 | },
212 | "BloomCurve": [
213 | {
214 | "y": 0.5
215 | },
216 | {
217 | "x": 1,
218 | "y": 1
219 | }
220 | ],
221 | "Mode": "Additive",
222 | "Strength": 0.5,
223 | "Threshold": 0.5,
224 | "ThresholdWidth": 0.5
225 | },
226 | {
227 | "__type": "Tonemapping",
228 | "ExposureCompensation": 0,
229 | "MaximumExposure": 2,
230 | "MinimumExposure": 1,
231 | "Rate": 1
232 | },
233 | {
234 | "__type": "Sharpen",
235 | "Scale": 0.2
236 | }
237 | ]
238 | }
239 | ],
240 | "SceneProperties": {
241 | "FixedUpdateFrequency": 50,
242 | "Lerping": true,
243 | "MaxFixedUpdates": 5,
244 | "NetworkFrequency": 60,
245 | "ThreadedAnimation": true,
246 | "TimeScale": 1,
247 | "UseFixedUpdate": true,
248 | "NavMesh": {
249 | "Enabled": false,
250 | "IncludeStaticBodies": true,
251 | "IncludeKeyframedBodies": true,
252 | "EditorAutoUpdate": true,
253 | "AgentHeight": 64,
254 | "AgentRadius": 16,
255 | "AgentStepSize": 18,
256 | "AgentMaxSlope": 40,
257 | "ExcludedBodies": "",
258 | "IncludedBodies": ""
259 | }
260 | },
261 | "Title": "minimal",
262 | "Description": "",
263 | "LastSaved": "2024-02-08T13:32:32.3548959\u002B00:00",
264 | "__references": []
265 | }
--------------------------------------------------------------------------------
/Assets/scenes/gameplay_test.scene:
--------------------------------------------------------------------------------
1 | {
2 | "__guid": "bfb616df-2d67-4128-8fa7-6a0060f5d362",
3 | "GameObjects": [
4 | {
5 | "__guid": "871cd8e9-7e28-413d-8195-a61abc8ffa74",
6 | "Flags": 0,
7 | "Name": "Plane",
8 | "Scale": "10,10,1",
9 | "Tags": "solid",
10 | "Enabled": true,
11 | "Components": [
12 | {
13 | "__type": "Sandbox.ModelRenderer",
14 | "__guid": "09302c8a-ee6c-4bf2-8772-70917bb5d527",
15 | "BodyGroups": 18446744073709551615,
16 | "Model": "models/dev/plane.vmdl",
17 | "RenderType": "On",
18 | "Tint": "1,1,1,1"
19 | },
20 | {
21 | "__type": "Sandbox.PlaneCollider",
22 | "__guid": "92058802-a6a9-4774-a2a0-5a053702c388",
23 | "Center": "0,0,0",
24 | "IsTrigger": false,
25 | "Scale": "100,100",
26 | "Static": true
27 | }
28 | ]
29 | },
30 | {
31 | "__guid": "ff5f4aaa-8b73-44e4-b8d5-92466854e6a5",
32 | "Flags": 0,
33 | "Name": "Sun",
34 | "Position": "0,0,100",
35 | "Rotation": "0,0.6087614,0,0.7933533",
36 | "Tags": "light_directional,light",
37 | "Enabled": true,
38 | "Components": [
39 | {
40 | "__type": "Sandbox.DirectionalLight",
41 | "__guid": "bddfdfac-caa7-436f-b2b1-cd4f31c762af",
42 | "FogMode": "Enabled",
43 | "FogStrength": 1,
44 | "LightColor": "0.91373,0.98039,1,1",
45 | "Shadows": true,
46 | "SkyColor": "0.05882,0.07451,0.08235,1"
47 | }
48 | ]
49 | },
50 | {
51 | "__guid": "52c617d7-9e98-4aab-b46b-d3f5f690d82d",
52 | "Flags": 0,
53 | "Name": "2D Skybox",
54 | "Tags": "skybox",
55 | "Enabled": true,
56 | "Components": [
57 | {
58 | "__type": "Sandbox.SkyBox2D",
59 | "__guid": "495f30b6-0981-423d-86de-06cb2bc000c8",
60 | "SkyMaterial": "materials/skybox/skybox_day_01.vmat",
61 | "Tint": "1,1,1,1"
62 | }
63 | ]
64 | },
65 | {
66 | "__guid": "d22292a5-ff41-4d3d-8f72-ecb6bb6ed6e2",
67 | "Flags": 0,
68 | "Name": "Test Path",
69 | "Enabled": true,
70 | "Children": [
71 | {
72 | "__guid": "aac50103-b31d-44ac-b57b-32c375304592",
73 | "Flags": 0,
74 | "Name": "Start",
75 | "Position": "-200,0,73.59998",
76 | "Enabled": true,
77 | "Components": [
78 | {
79 | "__type": "TTP.Paths.StraightLinePath",
80 | "__guid": "d8db4273-64ec-44a3-9fe2-dc6b4e48714f",
81 | "Next": {
82 | "_type": "component",
83 | "component_id": "acbe1612-b44e-4222-ad31-bc72a4b4b5b3",
84 | "go": "30bc5ee2-c136-4f29-a479-bc73c2bf6dd8",
85 | "component_type": "QuadraticBezierPath"
86 | },
87 | "Previous": {
88 | "_type": "component",
89 | "component_id": "f792cfab-8cbf-4d0b-9cca-80ef2c80c64f",
90 | "go": "9f2b8311-a9dd-42e1-8a5a-d592e258f1de",
91 | "component_type": "QuadraticBezierPath"
92 | },
93 | "Resolution": 10,
94 | "Width": 100
95 | },
96 | {
97 | "__type": "Sandbox.RodentPathNode",
98 | "__guid": "ebdbe21e-1f36-41e7-a665-05f85595b48e",
99 | "Height": 100,
100 | "Resolution": 100
101 | }
102 | ]
103 | },
104 | {
105 | "__guid": "30bc5ee2-c136-4f29-a479-bc73c2bf6dd8",
106 | "Flags": 0,
107 | "Name": "GameObject",
108 | "Position": "200,0,73.59998",
109 | "Enabled": true,
110 | "Components": [
111 | {
112 | "__type": "TTP.Paths.QuadraticBezierPath",
113 | "__guid": "acbe1612-b44e-4222-ad31-bc72a4b4b5b3",
114 | "ControlPoint": {
115 | "_type": "gameobject",
116 | "go": "14d4f3de-dcaf-46a0-b343-18187531da62"
117 | },
118 | "Next": {
119 | "_type": "component",
120 | "component_id": "800c10ef-101d-4ade-9488-88d6a6d03e67",
121 | "go": "d449402b-d2a0-4774-9bca-079bce9953a0",
122 | "component_type": "QuadraticBezierPath"
123 | },
124 | "Previous": {
125 | "_type": "component",
126 | "component_id": "d8db4273-64ec-44a3-9fe2-dc6b4e48714f",
127 | "go": "aac50103-b31d-44ac-b57b-32c375304592",
128 | "component_type": "StraightLinePath"
129 | },
130 | "Resolution": 10,
131 | "Width": 100
132 | },
133 | {
134 | "__type": "Sandbox.RodentPathNode",
135 | "__guid": "2e425396-35d2-4c25-ba97-021ff336bb84",
136 | "Height": 100,
137 | "Resolution": 200
138 | }
139 | ],
140 | "Children": [
141 | {
142 | "__guid": "14d4f3de-dcaf-46a0-b343-18187531da62",
143 | "Flags": 0,
144 | "Name": "Control Point",
145 | "Position": "200,0,0",
146 | "Enabled": true
147 | }
148 | ]
149 | },
150 | {
151 | "__guid": "d449402b-d2a0-4774-9bca-079bce9953a0",
152 | "Flags": 0,
153 | "Name": "GameObject",
154 | "Position": "400,200,73.59998",
155 | "Rotation": "0,0,0.7071068,0.7071068",
156 | "Enabled": true,
157 | "Components": [
158 | {
159 | "__type": "TTP.Paths.QuadraticBezierPath",
160 | "__guid": "800c10ef-101d-4ade-9488-88d6a6d03e67",
161 | "ControlPoint": {
162 | "_type": "gameobject",
163 | "go": "77743011-2c45-4443-ab3f-bb31f99587de"
164 | },
165 | "Next": {
166 | "_type": "component",
167 | "component_id": "da3c948d-8f39-4c81-b1a5-253a352d309c",
168 | "go": "dc85fd3a-ba59-42aa-8aeb-ef0d53453501",
169 | "component_type": "StraightLinePath"
170 | },
171 | "Previous": {
172 | "_type": "component",
173 | "component_id": "acbe1612-b44e-4222-ad31-bc72a4b4b5b3",
174 | "go": "30bc5ee2-c136-4f29-a479-bc73c2bf6dd8",
175 | "component_type": "QuadraticBezierPath"
176 | },
177 | "Resolution": 10,
178 | "Width": 100
179 | },
180 | {
181 | "__type": "Sandbox.RodentPathNode",
182 | "__guid": "15dab635-01aa-4397-aaf6-cac3f3dc1099",
183 | "Height": 200,
184 | "Resolution": 200
185 | }
186 | ],
187 | "Children": [
188 | {
189 | "__guid": "77743011-2c45-4443-ab3f-bb31f99587de",
190 | "Flags": 0,
191 | "Name": "Control Point",
192 | "Position": "200,0,0",
193 | "Enabled": true
194 | }
195 | ]
196 | },
197 | {
198 | "__guid": "dc85fd3a-ba59-42aa-8aeb-ef0d53453501",
199 | "Flags": 0,
200 | "Name": "GameObject",
201 | "Position": "200,400,73.59998",
202 | "Rotation": "0,0,1,-0.00000004371139",
203 | "Enabled": true,
204 | "Components": [
205 | {
206 | "__type": "TTP.Paths.StraightLinePath",
207 | "__guid": "da3c948d-8f39-4c81-b1a5-253a352d309c",
208 | "Next": {
209 | "_type": "component",
210 | "component_id": "b69e1ea4-5ba4-4275-9c4f-8494e8cec4c3",
211 | "go": "04a1b00e-f3ff-4686-91b5-5bb2dbfedb06",
212 | "component_type": "QuadraticBezierPath"
213 | },
214 | "Previous": {
215 | "_type": "component",
216 | "component_id": "800c10ef-101d-4ade-9488-88d6a6d03e67",
217 | "go": "d449402b-d2a0-4774-9bca-079bce9953a0",
218 | "component_type": "QuadraticBezierPath"
219 | },
220 | "Resolution": 10,
221 | "Width": 100
222 | },
223 | {
224 | "__type": "Sandbox.RodentPathNode",
225 | "__guid": "c7d88fb9-32c7-4813-b644-16d729e714d1",
226 | "Height": 100,
227 | "Resolution": 100
228 | }
229 | ]
230 | },
231 | {
232 | "__guid": "04a1b00e-f3ff-4686-91b5-5bb2dbfedb06",
233 | "Flags": 0,
234 | "Name": "GameObject",
235 | "Position": "-200,400,73.59998",
236 | "Rotation": "0,0,1,-0.00000004371139",
237 | "Enabled": true,
238 | "Components": [
239 | {
240 | "__type": "TTP.Paths.QuadraticBezierPath",
241 | "__guid": "b69e1ea4-5ba4-4275-9c4f-8494e8cec4c3",
242 | "ControlPoint": {
243 | "_type": "gameobject",
244 | "go": "32cbf9f1-8fcd-4cf2-9ad5-5421bad27592"
245 | },
246 | "Next": {
247 | "_type": "component",
248 | "component_id": "f792cfab-8cbf-4d0b-9cca-80ef2c80c64f",
249 | "go": "9f2b8311-a9dd-42e1-8a5a-d592e258f1de",
250 | "component_type": "QuadraticBezierPath"
251 | },
252 | "Previous": {
253 | "_type": "component",
254 | "component_id": "da3c948d-8f39-4c81-b1a5-253a352d309c",
255 | "go": "dc85fd3a-ba59-42aa-8aeb-ef0d53453501",
256 | "component_type": "StraightLinePath"
257 | },
258 | "Resolution": 10,
259 | "Width": 100
260 | },
261 | {
262 | "__type": "Sandbox.RodentPathNode",
263 | "__guid": "bff7d1b6-59e0-434f-92a3-7ae3bbf04564",
264 | "Height": 100,
265 | "Resolution": 200
266 | }
267 | ],
268 | "Children": [
269 | {
270 | "__guid": "32cbf9f1-8fcd-4cf2-9ad5-5421bad27592",
271 | "Flags": 0,
272 | "Name": "Control Point",
273 | "Position": "200,-0.000004371139,0",
274 | "Enabled": true
275 | }
276 | ]
277 | },
278 | {
279 | "__guid": "9f2b8311-a9dd-42e1-8a5a-d592e258f1de",
280 | "Flags": 0,
281 | "Name": "GameObject",
282 | "Position": "-400,200,73.59998",
283 | "Rotation": "0,-0,-0.7071068,0.7071068",
284 | "Enabled": true,
285 | "Components": [
286 | {
287 | "__type": "TTP.Paths.QuadraticBezierPath",
288 | "__guid": "f792cfab-8cbf-4d0b-9cca-80ef2c80c64f",
289 | "ControlPoint": {
290 | "_type": "gameobject",
291 | "go": "4fcf156a-41a9-4be3-bd12-80cffb79407a"
292 | },
293 | "Next": {
294 | "_type": "component",
295 | "component_id": "d8db4273-64ec-44a3-9fe2-dc6b4e48714f",
296 | "go": "aac50103-b31d-44ac-b57b-32c375304592",
297 | "component_type": "StraightLinePath"
298 | },
299 | "Previous": {
300 | "_type": "component",
301 | "component_id": "b69e1ea4-5ba4-4275-9c4f-8494e8cec4c3",
302 | "go": "04a1b00e-f3ff-4686-91b5-5bb2dbfedb06",
303 | "component_type": "QuadraticBezierPath"
304 | },
305 | "Resolution": 10,
306 | "Width": 100
307 | },
308 | {
309 | "__type": "Sandbox.RodentPathNode",
310 | "__guid": "00496950-8e69-472d-9d30-da3d7fe2fe16",
311 | "Height": 100,
312 | "Resolution": 200
313 | }
314 | ],
315 | "Children": [
316 | {
317 | "__guid": "4fcf156a-41a9-4be3-bd12-80cffb79407a",
318 | "Flags": 0,
319 | "Name": "Control Point",
320 | "Position": "200,-0.000004371139,0",
321 | "Enabled": true
322 | }
323 | ]
324 | }
325 | ]
326 | },
327 | {
328 | "__guid": "fd13f2a8-8949-441d-bd69-9215d1f0369b",
329 | "Flags": 0,
330 | "Name": "Camera",
331 | "Position": "-592.5724,-564.6034,534.353",
332 | "Rotation": "-0.1195137,0.2455831,0.4209494,0.8649896",
333 | "Tags": "maincamera",
334 | "Enabled": true,
335 | "Components": [
336 | {
337 | "__type": "Sandbox.CameraComponent",
338 | "__guid": "f2413dda-e461-43ea-8990-be67e6447485",
339 | "BackgroundColor": "0.33333,0.46275,0.52157,1",
340 | "ClearFlags": "All",
341 | "FieldOfView": 60,
342 | "IsMainCamera": true,
343 | "Orthographic": false,
344 | "OrthographicHeight": 1204,
345 | "Priority": 1,
346 | "RenderExcludeTags": "",
347 | "RenderTags": "",
348 | "TargetEye": "None",
349 | "Viewport": "0,0,1,1",
350 | "ZFar": 10000,
351 | "ZNear": 10
352 | }
353 | ]
354 | },
355 | {
356 | "__guid": "a3519583-c469-4e68-8323-d07561daa019",
357 | "Flags": 0,
358 | "Name": "Path Follower",
359 | "Position": "0,0,74.42735",
360 | "Components": [
361 | {
362 | "__type": "Sandbox.RodentPathFollower",
363 | "__guid": "8314965e-fe27-4ff9-9f10-fac191ab3837",
364 | "First": {
365 | "_type": "component",
366 | "component_id": "ebdbe21e-1f36-41e7-a665-05f85595b48e",
367 | "go": "aac50103-b31d-44ac-b57b-32c375304592",
368 | "component_type": "RodentPathNode"
369 | },
370 | "Speed": 50
371 | }
372 | ],
373 | "Children": [
374 | {
375 | "__guid": "9094da9f-1c33-46de-8057-8af6b2d92993",
376 | "Flags": 0,
377 | "Name": "hampter",
378 | "Position": "0,0,0",
379 | "Enabled": true,
380 | "Components": [
381 | {
382 | "__type": "Sandbox.SkinnedModelRenderer",
383 | "__guid": "d40881cc-dc2c-4066-91da-9d72510a9ff1",
384 | "BodyGroups": 18446744073709551615,
385 | "CreateBoneObjects": false,
386 | "Model": "models/hampter/hampter.vmdl",
387 | "RenderType": "On",
388 | "Tint": "1,1,1,1",
389 | "UseAnimGraph": true
390 | }
391 | ]
392 | }
393 | ]
394 | }
395 | ],
396 | "SceneProperties": {
397 | "FixedUpdateFrequency": 50,
398 | "MaxFixedUpdates": 5,
399 | "NetworkFrequency": 30,
400 | "NetworkInterpolation": true,
401 | "ThreadedAnimation": true,
402 | "TimeScale": 1,
403 | "UseFixedUpdate": true,
404 | "NavMesh": {
405 | "Enabled": false,
406 | "IncludeStaticBodies": true,
407 | "IncludeKeyframedBodies": true,
408 | "EditorAutoUpdate": true,
409 | "AgentHeight": 64,
410 | "AgentRadius": 16,
411 | "AgentStepSize": 18,
412 | "AgentMaxSlope": 40,
413 | "ExcludedBodies": "",
414 | "IncludedBodies": ""
415 | }
416 | },
417 | "Title": "gameplay_test",
418 | "Description": "",
419 | "ResourceVersion": 1,
420 | "__references": [
421 | "fish.hs_hampter"
422 | ],
423 | "__version": 1
424 | }
--------------------------------------------------------------------------------