├── 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 | } --------------------------------------------------------------------------------