{"TotalCount":6,"Files":[{"Ident":"duccsoft.libfreecam","Path":"Code/Freecam.Events.cs","FileName":"Freecam.Events.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":56341,"Code":"using System;\r\n\r\nnamespace Duccsoft;\r\n\r\npublic partial class Freecam\r\n{\r\n\t/// \u003Csummary\u003E\r\n\t/// Invoked whenever a freecam is enabled. The argument is the freecam that was enabled.\r\n\t/// \u003C/summary\u003E\r\n\tpublic static event Action\u003CFreecam\u003E OnFreecamStart;\r\n\t/// \u003Csummary\u003E\r\n\t/// Invoked whenever a freecam is disabled. The argument is the freecam that was disabled.\r\n\t/// \u003C/summary\u003E\r\n\tpublic static event Action\u003CFreecam\u003E OnFreecamEnd;\r\n\r\n\t/// \u003Csummary\u003E\r\n\t/// Removes all listeners from the static events of this class. Used by\r\n\t/// \u003Csee cref=\u0022CameraEventCleanupSystem\u0022/\u003E to tidy things up between play sessions.\r\n\t/// \u003C/summary\u003E\r\n\tpublic static void ClearInvocationLists()\r\n\t{\r\n\t\tOnFreecamStart = null;\r\n\t\tOnFreecamEnd = null;\r\n\t}\r\n}\r\n"},{"Ident":"duccsoft.libfreecam","Path":"Code/Assembly.cs","FileName":"Assembly.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":56341,"Code":"global using Sandbox;\r\nglobal using System.Collections.Generic;\r\nglobal using System.Linq;\r\n"},{"Ident":"duccsoft.libfreecam","Path":"Code/Freecam.cs","FileName":"Freecam.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":56341,"Code":"namespace Duccsoft;\r\n\r\n/// \u003Csummary\u003E\r\n/// Enables the use of AnalogMove and AnalogLook to control the position and rotation \r\n/// of the main camera of the scene.\r\n/// \u003C/summary\u003E\r\n[Title( \u0022Freecam\u0022 )]\r\n[Category( \u0022Camera\u0022 )]\r\n[Icon( \u0022control_camera\u0022 )]\r\npublic sealed partial class Freecam : Component\r\n{\r\n\t/// \u003Csummary\u003E\r\n\t/// How many units per second the camera will move at normal speed.\r\n\t/// \u003C/summary\u003E\r\n\t[Property] public float Speed { get; set; } = 300f;\r\n\t/// \u003Csummary\u003E\r\n\t/// A factor applied to movement speed whenever the crouch button is held.\r\n\t/// \u003C/summary\u003E\r\n\t[Property] public float LowSpeedFactor { get; set; } = 0.25f;\r\n\t/// \u003Csummary\u003E\r\n\t/// A factor applied to movement speed whenever the run button is held.\r\n\t/// \u003C/summary\u003E\r\n\t[Property] public float HighSpeedFactor { get; set; } = 2.5f;\r\n\t/// \u003Csummary\u003E\r\n\t/// If true, prevents the player from looking higher than directly up or lower\r\n\t/// than directly down. Prevents the camera from going upside-down and doing loop-de-loops.\r\n\t/// \u003C/summary\u003E\r\n\t[Property] public bool ClampPitch { get; set; } = true;\r\n\t/// \u003Csummary\u003E\r\n\t/// If true, the freecam will use a \u003Csee cref=\u0022CharacterController\u0022/\u003E to handle collisions.\r\n\t/// If none exists already, one will be created.\r\n\t/// \u003C/summary\u003E\r\n\t[Property] public bool UseCollision { get; set; } = true;\r\n\r\n\t/// \u003Csummary\u003E\r\n\t/// The main scene camera. Will be refreshed each update.\r\n\t/// \u003C/summary\u003E\r\n\tprivate CameraComponent _camera;\r\n\t/// \u003Csummary\u003E\r\n\t/// The current angle/rotation that we are looking at.\r\n\t/// \u003C/summary\u003E\r\n\tprivate Angles _lookAngle;\r\n\t/// \u003Csummary\u003E\r\n\t/// If \u003Csee cref=\u0022UseCollision\u0022/\u003E is true, this will be an instance of a \u003Csee cref=\u0022CharacterController\u0022/\u003E\r\n\t/// on the same GameObject as this component.\r\n\t/// \u003C/summary\u003E\r\n\tprivate CharacterController _controller;\r\n\r\n\tprotected override void OnEnabled()\r\n\t{\r\n\t\tOnFreecamStart?.Invoke( this );\r\n\t}\r\n\r\n\tprotected override void OnDisabled()\r\n\t{\r\n\t\tOnFreecamEnd?.Invoke( this );\r\n\t\t_controller?.Destroy();\r\n\t\t_controller = null;\r\n\t}\r\n\r\n\tprotected override void OnUpdate()\r\n\t{\r\n\t\tif ( _camera is null || !_camera.IsMainCamera )\r\n\t\t{\r\n\t\t\t_camera = Scene.Camera;\r\n\t\t}\r\n\t\tif ( !_camera.IsValid() )\r\n\t\t\treturn;\r\n\r\n\t\tRotateMainCamera();\r\n\t\tMoveMainCamera();\r\n\t}\r\n\r\n\tprotected override void OnFixedUpdate()\r\n\t{\r\n\t\tUpdatePosition();\r\n\t}\r\n\r\n\tprivate void RotateMainCamera()\r\n\t{\r\n\t\t_lookAngle \u002B= Input.AnalogLook;\r\n\t\tif ( ClampPitch )\r\n\t\t{\r\n\t\t\t_lookAngle.pitch = _lookAngle.pitch.Clamp( -89f, 89f );\r\n\t\t}\r\n\t\t_camera.Transform.Rotation = _lookAngle;\r\n\t}\r\n\r\n\t/// \u003Csummary\u003E\r\n\t/// Move the main scene camera to roughly the position of this GameObject.\r\n\t/// \u003C/summary\u003E\r\n\tprivate void MoveMainCamera()\r\n\t{\r\n\t\tif ( UseCollision )\r\n\t\t{\r\n\t\t\t// Put the camera up in to the center of the CharacterController\u0027s collision cube.\r\n\t\t\t_camera.Transform.Position = Transform.Position \u002B Vector3.Up * 8f;\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\t_camera.Transform.Position = Transform.Position;\r\n\t\t}\r\n\t}\r\n\r\n\t/// \u003Csummary\u003E\r\n\t/// Use input to move this GameObject. If \u003Csee cref=\u0022UseCollision\u0022/\u003E is true, a \u003Csee cref=\u0022CharacterController\u0022/\u003E\r\n\t/// will be used to ensure that this GameObject doesn\u0027t clip through anything it shouldn\u0027t.\r\n\t/// \u003C/summary\u003E\r\n\tprivate void UpdatePosition()\r\n\t{\r\n\t\tEnsureCollision();\r\n\t\t// Move using WASD or left thumbstick\r\n\t\tvar movement = Input.AnalogMove * Speed * GetSpeedFactor();\r\n\t\t// Move relative to the direction the camera is facing.\r\n\t\tmovement *= _lookAngle;\r\n\t\tif ( UseCollision )\r\n\t\t{\r\n\t\t\t_controller.Velocity = movement;\r\n\t\t\t_controller.Move();\r\n\t\t\t_controller.IsOnGround = false;\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\tTransform.Position \u002B= movement * Time.Delta;\r\n\t\t}\r\n\t}\r\n\r\n\tprivate void EnsureCollision()\r\n\t{\r\n\t\tif ( !UseCollision )\r\n\t\t\treturn;\r\n\r\n\t\t_controller ??= Components.GetOrCreate\u003CCharacterController\u003E();\r\n\t\t_controller.Radius = 8f;\r\n\t\t_controller.Height = 16f;\r\n\t}\r\n\r\n\tprivate float GetSpeedFactor()\r\n\t{\r\n\t\tvar speedFactor = 1f;\r\n\t\tif ( Input.Down( CameraActions.MoveSlow ) )\r\n\t\t{\r\n\t\t\tspeedFactor = LowSpeedFactor;\r\n\t\t}\r\n\t\telse if ( Input.Down( CameraActions.MoveFast ) )\r\n\t\t{\r\n\t\t\tspeedFactor = HighSpeedFactor;\r\n\t\t}\r\n\t\treturn speedFactor;\r\n\t}\r\n}\r\n"},{"Ident":"duccsoft.libfreecam","Path":"Code/CameraEventCleanupSystem.cs","FileName":"CameraEventCleanupSystem.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":56341,"Code":"namespace Duccsoft;\r\n\r\n/// \u003Csummary\u003E\r\n/// On game shutdown, clears the invocation lists of the static events of \u003Csee cref=\u0022Freecam\u0022/\u003E.\r\n/// This is to prevent duplicate event subscribers from piling up between play sessions.\r\n/// \u003C/summary\u003E\r\npublic class CameraEventCleanupSystem : GameObjectSystem\r\n{\r\n\tpublic CameraEventCleanupSystem( Scene scene ) : base( scene ) \r\n\t{ \r\n\t\r\n\t} \r\n\r\n\tpublic override void Dispose()\r\n\t{\r\n\t\tbase.Dispose();\r\n\r\n\t\tFreecam.ClearInvocationLists();\r\n\t}\r\n}\r\n"},{"Ident":"duccsoft.libfreecam","Path":"Code/Freecam.Commands.cs","FileName":"Freecam.Commands.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":56341,"Code":"namespace Duccsoft;\r\n\r\npublic sealed partial class Freecam\r\n{\r\n\t/// \u003Csummary\u003E\r\n\t/// Holds whatever instance of \u003Csee cref=\u0022Freecam\u0022/\u003E may have been created by invoking \u003Csee cref=\u0022Toggle\u0022/\u003E.\r\n\t/// \u003C/summary\u003E\r\n\tprivate static Freecam _conCmdInstance;\r\n\t/// \u003Csummary\u003E\r\n\t/// Holds references to whatever \u003Csee cref=\u0022Freecam\u0022/\u003E instances were disabled by \u003Csee cref=\u0022Toggle\u0022/\u003E.\r\n\t/// \u003C/summary\u003E\r\n\tprivate static HashSet\u003CFreecam\u003E _disabledFreecams = new();\r\n\r\n\t[ConCmd(\u0022freecam\u0022, Help = \u0022Toggle a mode where the camera can be moved and rotated freely.\u0022)]\r\n\tpublic static void Toggle()\r\n\t{\r\n\t\tvar camera = Game.ActiveScene?.Camera;\r\n\t\tif ( !camera.IsValid() )\r\n\t\t\treturn;\r\n\r\n\t\t// If we were already using the freecam ConCmd...\r\n\t\tif ( _conCmdInstance?.Active == true )\r\n\t\t{\r\n\t\t\t// ...toggle it off by destroying its GameObject.\r\n\t\t\t_conCmdInstance?.GameObject?.Destroy();\r\n\t\t\t_conCmdInstance = null;\r\n\t\t\t// Reenable whatever non-ConCmd freecam we might have previously disabled.\r\n\t\t\tReenablePreviousFreecam();\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\t// If there is an active freecam not created by this ConCmd, disable it.\r\n\t\tDisableActiveFreecams();\r\n\t\t// Make a new GameObject with a Freecam component.\r\n\t\tCreateFreecam();\r\n\t}\r\n\r\n\tprivate static void DisableActiveFreecams()\r\n\t{\r\n\t\t// Find any freecams that are currently active...\r\n\t\tvar freecams = Game.ActiveScene.GetAllComponents\u003CFreecam\u003E();\r\n\t\tif ( !freecams.Any() )\r\n\t\t\treturn;\r\n\r\n\t\tforeach ( var freecam in freecams )\r\n\t\t{\r\n\t\t\t// ...and turn them off.\r\n\t\t\tfreecam.Enabled = false;\r\n\t\t\t_disabledFreecams.Add( freecam );\r\n\t\t}\r\n\t\treturn;\r\n\t}\r\n\r\n\tprivate static void ReenablePreviousFreecam()\r\n\t{\r\n\t\t// Clean out any invalid freecams (e.g. GameObject or component was deleted)\r\n\t\tforeach( var freecam in _disabledFreecams.ToList() )\r\n\t\t{\r\n\t\t\tif ( !freecam.IsValid() )\r\n\t\t\t{\r\n\t\t\t\t_disabledFreecams.Remove( freecam );\r\n\t\t\t}\r\n\t\t}\r\n\t\t// Find the first valid freecam we had previously disabled\r\n\t\tvar firstFreecam = _disabledFreecams.FirstOrDefault();\r\n\t\tif ( firstFreecam is null )\r\n\t\t\treturn;\r\n\r\n\t\tfirstFreecam.Enabled = true;\r\n\t\t_disabledFreecams.Remove( firstFreecam );\r\n\t}\r\n\r\n\tprivate static void CreateFreecam()\r\n\t{\r\n\t\t// There were no active freecams, so create one.\r\n\t\tvar freecamGo = new GameObject( true, \u0022ConCmd Freecam\u0022 );\r\n\t\tvar camTx = Game.ActiveScene.Camera.Transform;\r\n\t\tfreecamGo.Transform.Position = camTx.Position;\r\n\t\t_conCmdInstance = freecamGo.Components.Create\u003CFreecam\u003E();\r\n\t\t_conCmdInstance._lookAngle = camTx.Rotation;\r\n\t\t// Assume that if someone uses a ConCmd to freecam, they also want to noclip.\r\n\t\t// Colliding with walls is more of an official \u0022photo mode\u0022 or \u0022spectator mode\u0022 kind of thing.\r\n\t\t_conCmdInstance.UseCollision = false;\r\n\t}\r\n}\r\n"},{"Ident":"duccsoft.libfreecam","Path":"Code/CameraActions.cs","FileName":"CameraActions.cs","PackageType":"library","CodeKind":"Game","AssetVersionId":56341,"Code":"namespace Duccsoft;\r\n\r\n/// \u003Csummary\u003E\r\n/// Defines the action names that are used to control the \u003Csee cref=\u0022Freecam\u0022/\u003E.\r\n/// \u003C/summary\u003E\r\npublic static class CameraActions\r\n{\r\n\tpublic const string MoveFast = \u0022run\u0022;\r\n\tpublic const string MoveSlow = \u0022duck\u0022;\r\n}\r\n"}]}