From 914512435d37d9f1e1ea4c045afd4ec5612b7534 Mon Sep 17 00:00:00 2001 From: Leonardo Bishop Date: Sun, 30 Jul 2023 02:45:36 +0100 Subject: Replace movement controllers --- code/entity/DroppedWeapon.cs | 2 +- code/pawn/Player.cs | 74 +- code/pawn/component/AnimatorComponent.cs | 26 + code/pawn/component/FallDamageComponent.cs | 23 + code/pawn/component/InventoryComponent.cs | 126 ++ code/pawn/component/PlayerAnimator.cs | 21 - code/pawn/component/PlayerInventory.cs | 126 -- code/pawn/component/PlayerSpectator.cs | 48 - code/pawn/component/camera/BaseCameraComponent.cs | 20 + .../pawn/component/camera/PlayerCameraComponent.cs | 48 + .../component/camera/SpectatorCameraComponent.cs | 41 + code/pawn/component/movement/BaseController.cs | 14 - .../component/movement/BaseControllerComponent.cs | 104 ++ code/pawn/component/movement/PlayerController.cs | 281 ---- .../component/movement/WalkControllerComponent.cs | 1368 ++++++++++++++++++++ code/phase/PlayPhase.cs | 10 +- code/phase/WaitPhase.cs | 2 +- code/team/Team.cs | 74 +- code/ui/health/Health.razor | 2 +- code/ui/overlay/DeathOverlay.razor | 33 +- code/ui/spectator/Spectator.razor | 14 +- 21 files changed, 1850 insertions(+), 607 deletions(-) create mode 100644 code/pawn/component/AnimatorComponent.cs create mode 100644 code/pawn/component/FallDamageComponent.cs create mode 100644 code/pawn/component/InventoryComponent.cs delete mode 100644 code/pawn/component/PlayerAnimator.cs delete mode 100644 code/pawn/component/PlayerInventory.cs delete mode 100644 code/pawn/component/PlayerSpectator.cs create mode 100644 code/pawn/component/camera/BaseCameraComponent.cs create mode 100644 code/pawn/component/camera/PlayerCameraComponent.cs create mode 100644 code/pawn/component/camera/SpectatorCameraComponent.cs delete mode 100644 code/pawn/component/movement/BaseController.cs create mode 100644 code/pawn/component/movement/BaseControllerComponent.cs delete mode 100644 code/pawn/component/movement/PlayerController.cs create mode 100644 code/pawn/component/movement/WalkControllerComponent.cs (limited to 'code') diff --git a/code/entity/DroppedWeapon.cs b/code/entity/DroppedWeapon.cs index 0cea7da..a58bdb1 100644 --- a/code/entity/DroppedWeapon.cs +++ b/code/entity/DroppedWeapon.cs @@ -29,7 +29,7 @@ public partial class DroppedWeapon : AnimatedEntity MurderGame.Player player = (MurderGame.Player)other; - if ( player.Inventory == null || player.Inventory.PrimaryWeapon != null || !player.Inventory.AllowPickup) return; + if ( player.Inventory== null || player.Inventory.PrimaryWeapon != null || !player.Inventory.AllowPickup) return; Weapon instance = TypeLibrary.Create( WeaponType ); instance.Ammo = Ammo; diff --git a/code/pawn/Player.cs b/code/pawn/Player.cs index cd2f83a..7462c6e 100644 --- a/code/pawn/Player.cs +++ b/code/pawn/Player.cs @@ -44,10 +44,11 @@ public partial class Player : AnimatedEntity [Net] public Team CurrentTeam { get; set; } - [BindComponent] public PlayerController Controller { get; } - [BindComponent] public PlayerAnimator Animator { get; } - [BindComponent] public PlayerInventory Inventory { get; } - [BindComponent] public PlayerSpectator Spectator { get; } + public BaseCameraComponent Camera => Components.Get(); + public BaseControllerComponent Controller => Components.Get(); + [BindComponent] public AnimatorComponent Animator { get; } + [BindComponent] public InventoryComponent Inventory { get; } + [BindComponent] public FallDamageComponent FallDamage { get; } [Net] public Ragdoll PlayerRagdoll { get; set; } @@ -87,9 +88,11 @@ public partial class Player : AnimatedEntity EnableDrawing = true; Components.RemoveAll(); - Components.Create(); - Components.Create(); - Components.Create(); + Components.Create(); + Components.Create(); + Components.Create(); + Components.Create(); + Components.Create(); Health = 100f; LifeState = LifeState.Alive; @@ -119,7 +122,6 @@ public partial class Player : AnimatedEntity EnableDrawing = false; Inventory?.Clear(); - Components.RemoveAll(); LifeState = LifeState.Dead; } @@ -174,67 +176,31 @@ public partial class Player : AnimatedEntity SimulateRotation(); TickPlayerUse(); - Controller?.Simulate( this ); + Controller?.Simulate( cl ); + Camera?.Simulate( cl ); Animator?.Simulate(); Inventory?.Simulate( cl ); - Spectator?.Simulate(); - - EyeLocalPosition = Vector3.Up * (64f * Scale); + FallDamage?.Simulate( cl ); - if (Game.IsServer && Spectator == null && LifeState == LifeState.Dead && TimeSinceDeath > 3) + if (Game.IsServer && Camera is not SpectatorCameraComponent && LifeState == LifeState.Dead && TimeSinceDeath > 3.5) { - Log.Info( "Spectator created" ); DeathOverlay.Hide( To.Single( Client ) ); - Components.Create(); + Components.RemoveAll(); + Components.Create(); } } public override void BuildInput() { - InputDirection = Input.AnalogMove; - - if ( Input.StopProcessing ) - return; - - var look = Input.AnalogLook; - - if ( ViewAngles.pitch > 90f || ViewAngles.pitch < -90f ) - { - look = look.WithYaw( look.yaw * -1f ); - } - - var viewAngles = ViewAngles; - viewAngles += look; - viewAngles.pitch = viewAngles.pitch.Clamp( -89f, 89f ); - viewAngles.roll = 0f; - ViewAngles = viewAngles.Normal; + Controller?.BuildInput(); + Camera?.BuildInput(); } public override void FrameSimulate( IClient cl ) { - if (Spectator != null) - { - Spectator.FrameSimulate(this); - return; - } - else if (Controller != null) - { - //TOOD move below logic to controller - } - SimulateRotation(); - - Camera.Rotation = ViewAngles.ToRotation(); - Camera.FieldOfView = Screen.CreateVerticalFieldOfView( Game.Preferences.FieldOfView ); - - Camera.FirstPersonViewer = this; - if (PlayerRagdoll != null && PlayerRagdoll.IsValid) - { - Camera.Position = PlayerRagdoll.Position; - } else - { - Camera.Position = EyePosition; - } + Controller?.FrameSimulate( cl ); + Camera?.FrameSimulate( cl ); } public TraceResult TraceBBox( Vector3 start, Vector3 end, float liftFeet = 0.0f ) diff --git a/code/pawn/component/AnimatorComponent.cs b/code/pawn/component/AnimatorComponent.cs new file mode 100644 index 0000000..6b27b15 --- /dev/null +++ b/code/pawn/component/AnimatorComponent.cs @@ -0,0 +1,26 @@ +using Sandbox; + +namespace MurderGame; + +public class AnimatorComponent : EntityComponent, ISingletonComponent +{ + public void Simulate() + { + var helper = new CitizenAnimationHelper( Entity ); + var player = Entity; + + helper.WithVelocity( Entity.Velocity ); + helper.WithLookAt( Entity.EyePosition + Entity.EyeRotation.Forward * 100 ); + helper.HoldType = CitizenAnimationHelper.HoldTypes.None; + helper.IsGrounded = Entity.GroundEntity.IsValid(); + helper.DuckLevel = MathX.Lerp( helper.DuckLevel, player.Controller.HasTag( "ducked" ) ? 1 : 0, Time.Delta * 10.0f ); + helper.VoiceLevel = (Game.IsClient && player.Client.IsValid()) ? player.Client.Voice.LastHeard < 0.5f ? player.Client.Voice.CurrentLevel : 0.0f : 0.0f; + helper.IsClimbing = player.Controller.HasTag( "climbing" ); + helper.IsSwimming = player.GetWaterLevel() >= 0.5f; + + if ( Entity.Controller.HasEvent( "jump" ) ) + { + helper.TriggerJump(); + } + } +} diff --git a/code/pawn/component/FallDamageComponent.cs b/code/pawn/component/FallDamageComponent.cs new file mode 100644 index 0000000..59e75d3 --- /dev/null +++ b/code/pawn/component/FallDamageComponent.cs @@ -0,0 +1,23 @@ +using Sandbox; + +namespace MurderGame; + +public partial class FallDamageComponent : EntityComponent, ISingletonComponent +{ + float PreviousZVelocity = 0; + const float LethalFallSpeed = 1024; + const float SafeFallSpeed = 580; + const float DamageForSpeed = (float)100 / (LethalFallSpeed - SafeFallSpeed); // damage per unit per second. + public void Simulate( IClient cl ) + { + var FallSpeed = -PreviousZVelocity; + if ( FallSpeed > (SafeFallSpeed * Entity.Scale) && Entity.GroundEntity != null ) + { + var FallDamage = (FallSpeed - (SafeFallSpeed * Entity.Scale)) * (DamageForSpeed * Entity.Scale); + var info = DamageInfo.Generic( FallDamage ).WithTag( "fall" ); + Entity.TakeDamage( info ); + Entity.PlaySound( "fall" ); + } + PreviousZVelocity = Entity.Velocity.z; + } +} diff --git a/code/pawn/component/InventoryComponent.cs b/code/pawn/component/InventoryComponent.cs new file mode 100644 index 0000000..6383aa8 --- /dev/null +++ b/code/pawn/component/InventoryComponent.cs @@ -0,0 +1,126 @@ +using Sandbox; +using System; + +namespace MurderGame; + +public partial class InventoryComponent : EntityComponent, ISingletonComponent +{ + const int MIN_SLOT = 1; + const int MAX_SLOT = 2; + + const int UNARMED_SLOT = 1; + const int PRIMARY_SLOT = 2; + + [Net] + public Weapon PrimaryWeapon { get; private set; } + + [Net] + public int ActiveSlot { get; set; } + + [Net] + public bool AllowPickup { get; set; } = true; + + public Weapon GetCurrentWeapon() + { + return ActiveSlot switch + { + PRIMARY_SLOT => PrimaryWeapon, + _ => null, + }; + } + + public void SetPrimaryWeapon( Weapon weapon ) + { + PrimaryWeapon?.OnHolster(); + PrimaryWeapon?.Delete(); + PrimaryWeapon = weapon; + if (weapon != null) + { + weapon.ChangeOwner( Entity ); + } + if (ActiveSlot == PRIMARY_SLOT) + { + weapon?.OnEquip( Entity ); + } + } + + private void PrevSlot() + { + if (ActiveSlot > MIN_SLOT) + { + --ActiveSlot; + } + else + { + ActiveSlot = MAX_SLOT; + } + } + private void NextSlot() + { + if (ActiveSlot < MAX_SLOT) + { + ++ActiveSlot; + } + else + { + ActiveSlot = MIN_SLOT; + } + } + + public void Simulate(IClient cl) + { + var currentWeapon = GetCurrentWeapon(); + var currentSlot = ActiveSlot; + + if (Input.Released("SlotPrev")) + { + PrevSlot(); + } + else if (Input.Released("SlotNext")) + { + NextSlot(); + } + else if (Input.Down("Slot1")) + { + ActiveSlot = 1; + } + else if (Input.Down("Slot2")) + { + ActiveSlot = 2; + } + + if (ActiveSlot != currentSlot) + { + currentWeapon?.OnHolster(); + GetCurrentWeapon()?.OnEquip( Entity ); + } + GetCurrentWeapon()?.Simulate( cl ); + } + + public void Holster() + { + Weapon weapon = GetCurrentWeapon(); + weapon?.OnHolster(); + } + + public void Clear() + { + Holster(); + SetPrimaryWeapon( null ); + } + + public void SpillContents(Vector3 location, Vector3 velocity) + { + Holster(); + if (PrimaryWeapon is not null and Pistol ) + { + PrimaryWeapon.ChangeOwner( null ); + DroppedWeapon droppedWeapon = new( (Pistol)PrimaryWeapon ); + droppedWeapon.CopyFrom( PrimaryWeapon ); + droppedWeapon.Position = location; + droppedWeapon.Velocity = velocity; + } + Clear(); + } + +} diff --git a/code/pawn/component/PlayerAnimator.cs b/code/pawn/component/PlayerAnimator.cs deleted file mode 100644 index 4cd4e3f..0000000 --- a/code/pawn/component/PlayerAnimator.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Sandbox; -using System; - -namespace MurderGame; - -public class PlayerAnimator : EntityComponent, ISingletonComponent -{ - public void Simulate() - { - var helper = new CitizenAnimationHelper( Entity ); - helper.WithVelocity( Entity.Velocity ); - helper.WithLookAt( Entity.EyePosition + Entity.EyeRotation.Forward * 100 ); - helper.HoldType = CitizenAnimationHelper.HoldTypes.None; - helper.IsGrounded = Entity.GroundEntity.IsValid(); - - if ( Entity.Controller.HasEvent( "jump" ) ) - { - helper.TriggerJump(); - } - } -} diff --git a/code/pawn/component/PlayerInventory.cs b/code/pawn/component/PlayerInventory.cs deleted file mode 100644 index 55fa3ed..0000000 --- a/code/pawn/component/PlayerInventory.cs +++ /dev/null @@ -1,126 +0,0 @@ -using Sandbox; -using System; - -namespace MurderGame; - -public partial class PlayerInventory : EntityComponent -{ - const int MIN_SLOT = 1; - const int MAX_SLOT = 2; - - const int UNARMED_SLOT = 1; - const int PRIMARY_SLOT = 2; - - [Net] - public Weapon PrimaryWeapon { get; private set; } - - [Net] - public int ActiveSlot { get; set; } - - [Net] - public bool AllowPickup { get; set; } = true; - - public Weapon GetCurrentWeapon() - { - return ActiveSlot switch - { - PRIMARY_SLOT => PrimaryWeapon, - _ => null, - }; - } - - public void SetPrimaryWeapon( Weapon weapon ) - { - PrimaryWeapon?.OnHolster(); - PrimaryWeapon?.Delete(); - PrimaryWeapon = weapon; - if (weapon != null) - { - weapon.ChangeOwner( Entity ); - } - if (ActiveSlot == PRIMARY_SLOT) - { - weapon?.OnEquip( Entity ); - } - } - - private void PrevSlot() - { - if (ActiveSlot > MIN_SLOT) - { - --ActiveSlot; - } - else - { - ActiveSlot = MAX_SLOT; - } - } - private void NextSlot() - { - if (ActiveSlot < MAX_SLOT) - { - ++ActiveSlot; - } - else - { - ActiveSlot = MIN_SLOT; - } - } - - public void Simulate(IClient cl) - { - var currentWeapon = GetCurrentWeapon(); - var currentSlot = ActiveSlot; - - if (Input.Released("SlotPrev")) - { - PrevSlot(); - } - else if (Input.Released("SlotNext")) - { - NextSlot(); - } - else if (Input.Down("Slot1")) - { - ActiveSlot = 1; - } - else if (Input.Down("Slot2")) - { - ActiveSlot = 2; - } - - if (ActiveSlot != currentSlot) - { - currentWeapon?.OnHolster(); - GetCurrentWeapon()?.OnEquip( Entity ); - } - GetCurrentWeapon()?.Simulate( cl ); - } - - public void Holster() - { - Weapon weapon = GetCurrentWeapon(); - weapon?.OnHolster(); - } - - public void Clear() - { - Holster(); - SetPrimaryWeapon( null ); - } - - public void SpillContents(Vector3 location, Vector3 velocity) - { - Holster(); - if (PrimaryWeapon is not null and Pistol ) - { - PrimaryWeapon.ChangeOwner( null ); - DroppedWeapon droppedWeapon = new( (Pistol)PrimaryWeapon ); - droppedWeapon.CopyFrom( PrimaryWeapon ); - droppedWeapon.Position = location; - droppedWeapon.Velocity = velocity; - } - Clear(); - } - -} diff --git a/code/pawn/component/PlayerSpectator.cs b/code/pawn/component/PlayerSpectator.cs deleted file mode 100644 index c468de0..0000000 --- a/code/pawn/component/PlayerSpectator.cs +++ /dev/null @@ -1,48 +0,0 @@ -using Sandbox; -using System.Collections.Generic; -using System.Linq; - -namespace MurderGame; - -public class PlayerSpectator : EntityComponent -{ - public Player Target { get; set; } - - public void Simulate() - { - if (Target == null || !Target.IsValid() || Target.LifeState == LifeState.Dead) - { - var targets = GetTargets(); - if ( targets.Count == 0 ) - { - Target = null; - return; - } - var nextTarget = targets.First(); - Target = (Player)nextTarget.Pawn; - } - } - - public void FrameSimulate( Player player ) - { - if ( Target == null || !Target.IsValid() || Target.LifeState == LifeState.Dead ) return; - - // SimulateRotation(player); - Camera.Rotation = Target.EyeRotation; - Camera.FieldOfView = Screen.CreateVerticalFieldOfView( Game.Preferences.FieldOfView ); - - Camera.FirstPersonViewer = Target; - Camera.Position = Target.EyePosition; - } - - protected void SimulateRotation(Player player) - { - player.EyeRotation = Target.ViewAngles.ToRotation(); - player.Rotation = Target.ViewAngles.WithPitch( 0f ).ToRotation(); - } - - public List GetTargets() - { - return Game.Clients.Where(c => c.Pawn is Player player && player.CurrentTeam != Team.Spectator && player.LifeState == LifeState.Alive).ToList(); - } -} diff --git a/code/pawn/component/camera/BaseCameraComponent.cs b/code/pawn/component/camera/BaseCameraComponent.cs new file mode 100644 index 0000000..be310c8 --- /dev/null +++ b/code/pawn/component/camera/BaseCameraComponent.cs @@ -0,0 +1,20 @@ +using Sandbox; + +namespace MurderGame; + +public class BaseCameraComponent : EntityComponent, ISingletonComponent +{ + + public virtual void Simulate( IClient cl ) + { + + } + public virtual void FrameSimulate( IClient cl ) + { + + } + public virtual void BuildInput() + { + + } +} diff --git a/code/pawn/component/camera/PlayerCameraComponent.cs b/code/pawn/component/camera/PlayerCameraComponent.cs new file mode 100644 index 0000000..5011c67 --- /dev/null +++ b/code/pawn/component/camera/PlayerCameraComponent.cs @@ -0,0 +1,48 @@ +using Sandbox; + +namespace MurderGame; + +public class PlayerCameraComponent : BaseCameraComponent +{ + protected override void OnActivate() + { + base.OnActivate(); + // Set field of view to whatever the user chose in options + Camera.FieldOfView = Screen.CreateVerticalFieldOfView( Game.Preferences.FieldOfView ); + } + public override void FrameSimulate( IClient cl ) + { + + var pl = Entity as Player; + // Update rotation every frame, to keep things smooth + + if (pl.PlayerRagdoll != null && pl.LifeState == LifeState.Dead) + { + Camera.Position = pl.PlayerRagdoll.Position; + Camera.FirstPersonViewer = pl.PlayerRagdoll; + return; + } + + pl.EyeRotation = pl.ViewAngles.ToRotation(); + + Camera.Position = pl.EyePosition; + Camera.Rotation = pl.ViewAngles.ToRotation(); + + Camera.Main.SetViewModelCamera( Screen.CreateVerticalFieldOfView( Game.Preferences.FieldOfView ) ); + + // Set the first person viewer to this, so it won't render our model + Camera.FirstPersonViewer = Entity; + + Camera.ZNear = 8 * pl.Scale; + } + public override void BuildInput() + { + if ( Game.LocalClient.Components.TryGet( out var _ ) ) + return; + + var pl = Entity as Player; + var viewAngles = (pl.ViewAngles + Input.AnalogLook).Normal; + pl.ViewAngles = viewAngles.WithPitch( viewAngles.pitch.Clamp( -89f, 89f ) ); + return; + } +} diff --git a/code/pawn/component/camera/SpectatorCameraComponent.cs b/code/pawn/component/camera/SpectatorCameraComponent.cs new file mode 100644 index 0000000..f81e186 --- /dev/null +++ b/code/pawn/component/camera/SpectatorCameraComponent.cs @@ -0,0 +1,41 @@ +using Sandbox; +using System.Collections.Generic; +using System.Linq; + +namespace MurderGame; + +public class SpectatorCameraComponent : BaseCameraComponent +{ + public Player Target { get; set; } + + public override void Simulate( IClient cl ) + { + if (Target == null || !Target.IsValid() || Target.LifeState == LifeState.Dead) + { + var targets = GetTargets(); + if ( targets.Count == 0 ) + { + Target = null; + return; + } + var nextTarget = targets.First(); + Target = (Player)nextTarget.Pawn; + } + } + + public override void FrameSimulate( IClient cl ) + { + if ( Target == null || !Target.IsValid() || Target.LifeState == LifeState.Dead ) return; + + Camera.Rotation = Target.EyeRotation; + Camera.FieldOfView = Screen.CreateVerticalFieldOfView( Game.Preferences.FieldOfView ); + + Camera.FirstPersonViewer = Target; + Camera.Position = Target.EyePosition; + } + + private List GetTargets() + { + return Game.Clients.Where(c => c.Pawn is Player player && player.CurrentTeam != Team.Spectator && player.LifeState == LifeState.Alive).ToList(); + } +} diff --git a/code/pawn/component/movement/BaseController.cs b/code/pawn/component/movement/BaseController.cs deleted file mode 100644 index 02c0be1..0000000 --- a/code/pawn/component/movement/BaseController.cs +++ /dev/null @@ -1,14 +0,0 @@ -using Sandbox; - -namespace MurderGame; - -//TODO make spectatro a controller -public abstract class BaseController -{ - public virtual float SpeedMultiplier { get; set; } = 1; - - public abstract void Simulate(Player player); - - public abstract bool HasEvent(string eventName); - -} diff --git a/code/pawn/component/movement/BaseControllerComponent.cs b/code/pawn/component/movement/BaseControllerComponent.cs new file mode 100644 index 0000000..d9b24c6 --- /dev/null +++ b/code/pawn/component/movement/BaseControllerComponent.cs @@ -0,0 +1,104 @@ +using Sandbox; +using System; +using System.Collections.Generic; + +namespace MurderGame; + +/// +/// Component designed for movement, only 1 per pawn. +/// +public class BaseControllerComponent : EntityComponent, ISingletonComponent +{ + + public virtual void Simulate( IClient cl ) + { + + } + public virtual void FrameSimulate( IClient cl ) + { + + } + public virtual void BuildInput() + { + + } + public Vector3 WishVelocity { get; set; } + + internal HashSet Events = new( StringComparer.OrdinalIgnoreCase ); + + internal HashSet Tags; + /// + /// Call OnEvent for each event + /// + public virtual void RunEvents( BaseControllerComponent additionalController ) + { + if ( Events == null ) return; + + foreach ( var e in Events ) + { + OnEvent( e ); + additionalController?.OnEvent( e ); + } + } + + /// + /// An event has been triggered - maybe handle it + /// + public virtual void OnEvent( string name ) + { + + } + + /// + /// Returns true if we have this event + /// + public bool HasEvent( string eventName ) + { + if ( Events == null ) return false; + return Events.Contains( eventName ); + } + + /// + /// + public bool HasTag( string tagName ) + { + if ( Tags == null ) return false; + return Tags.Contains( tagName ); + } + + + /// + /// Allows the controller to pass events to other systems + /// while staying abstracted. + /// For example, it could pass a "jump" event, which could then + /// be picked up by the playeranimator to trigger a jump animation, + /// and picked up by the player to play a jump sound. + /// + public void AddEvent( string eventName ) + { + // TODO - shall we allow passing data with the event? + + if ( Events == null ) Events = new HashSet(); + + if ( Events.Contains( eventName ) ) + return; + + Events.Add( eventName ); + } + + + /// + /// + public void SetTag( string tagName ) + { + // TODO - shall we allow passing data with the event? + + Tags ??= new HashSet(); + + if ( Tags.Contains( tagName ) ) + return; + + Tags.Add( tagName ); + } +} + diff --git a/code/pawn/component/movement/PlayerController.cs b/code/pawn/component/movement/PlayerController.cs deleted file mode 100644 index f2658af..0000000 --- a/code/pawn/component/movement/PlayerController.cs +++ /dev/null @@ -1,281 +0,0 @@ -using Sandbox; -using System; -using System.Collections.Generic; - -namespace MurderGame; - -public class PlayerController : EntityComponent -{ - public int StepSize => 24; - public int GroundAngle => 45; - public int JumpSpeed => 300; - public float Gravity => 800f; - public float SpeedMultiplier { get; set; } = 1; - - HashSet ControllerEvents = new( StringComparer.OrdinalIgnoreCase ); - - bool IsTouchingLadder = false; - - Vector3 LadderNormal; - - TimeSince TimeSinceFootstep; - bool FootstepFoot; - - bool Grounded => Entity.GroundEntity.IsValid(); - - public void Simulate( Player player ) - { - ControllerEvents.Clear(); - - var movement = Entity.InputDirection.Normal; - var angles = Entity.ViewAngles.WithPitch( 0 ); - var moveVector = Rotation.From( angles ) * movement * 320f; - var groundEntity = CheckForGround(); - var team = Entity.CurrentTeam; - - // wasd -- start - - if ( groundEntity.IsValid() ) - { - if ( !Grounded ) - { - var zVel = Entity.Velocity.z; - AddEvent( "grounded" ); - Entity.Velocity = Entity.Velocity.WithZ( 0 ); - - if (zVel < -500) - { - var damageInfo = DamageInfo.Generic( Math.Abs((float)(zVel * 0.05)) ); - Entity.PlaySound( "fall" ); - var trace = Trace.Ray( Entity.Position, Entity.Position + Vector3.Down * 20f ) - .Radius( 1f ) - .Ignore( Entity ) - .Run(); - if (trace.Hit) - { - trace.Surface.DoFootstep( Entity, trace, FootstepFoot ? 1 : 0, 70f ); - } - Entity.TakeDamage( damageInfo ); - return; - } - - } - var isSprinting = TeamOperations.CanSprint( team ) && Input.Down( "run" ); - var footstepTimeThreshold = isSprinting ? 0.2 : 0.3; - - if (moveVector.Length > 0 && TimeSinceFootstep > footstepTimeThreshold) - { - var trace = Trace.Ray( Entity.Position, Entity.Position + Vector3.Down * 20f ) - .Radius( 1f ) - .Ignore( Entity ) - .Run(); - - if (trace.Hit) - { - FootstepFoot = !FootstepFoot; - - trace.Surface.DoFootstep( Entity, trace, FootstepFoot ? 1 : 0, 40f ); - - TimeSinceFootstep = 0; - } - } - var sprintMultiplier = (isSprinting ? 2.5f : 1f); - - Entity.Velocity = Accelerate( Entity.Velocity, moveVector.Normal, moveVector.Length, SpeedMultiplier * 200.0f * sprintMultiplier, 7.5f ); - Entity.Velocity = ApplyFriction( Entity.Velocity, 4.0f ); - } - else - { - Entity.Velocity = Accelerate( Entity.Velocity, moveVector.Normal, moveVector.Length, SpeedMultiplier * 100, 20f ); - Entity.Velocity += Vector3.Down * Gravity * Time.Delta * (1/SpeedMultiplier); - } - - // wasd -- end - - // ladder -- start - - if (CheckLadder()) - { - float normalDot = moveVector.Dot(LadderNormal); - var cross = LadderNormal * normalDot; - Entity.Velocity = (moveVector - cross) + (-normalDot * LadderNormal.Cross(Vector3.Up.Cross(LadderNormal).Normal) * 0.5f); - } - - // ladder -- end - - if ( Input.Pressed( "jump" ) ) - { - DoJump(); - } - - // actually do move -- - - var mh = new MoveHelper( Entity.Position, Entity.Velocity ); - mh.Trace = mh.Trace.Size( Entity.Hull ).Ignore( Entity ); - - if (mh.TryUnstuck()) - { - Entity.Position = mh.Position; - Entity.Velocity = mh.Velocity; - } - - if ( mh.TryMoveWithStep( Time.Delta, StepSize ) > 0 ) - { - if ( Grounded ) - { - mh.Position = StayOnGround( mh.Position ); - } - Entity.Position = mh.Position; - Entity.Velocity = mh.Velocity; - } - Entity.GroundEntity = groundEntity; - } - - void DoJump() - { - if ( Grounded ) - { - Entity.Velocity = ApplyJump( Entity.Velocity, "jump" ); - } - } - - public bool CheckLadder() - { - var wishvel = new Vector3( Entity.InputDirection.x.Clamp( -1f, 1f ), Entity.InputDirection.y.Clamp( -1f, 1f ), 0); - wishvel *= Entity.ViewAngles.WithPitch(0).ToRotation(); - wishvel = wishvel.Normal; - - if (IsTouchingLadder) - { - if (Input.Pressed("jump")) - { - Entity.Velocity = LadderNormal * 100.0f; - return false; - - } - else if (Entity.GroundEntity != null && LadderNormal.Dot(wishvel) > 0) - { - return false; - } - } - - const float ladderDistance = 1.0f; - var start = Entity.Position; - Vector3 end = start + (IsTouchingLadder ? (LadderNormal * -1.0f) : wishvel) * ladderDistance; - - var pm = Trace.Ray(start, end) - .Size(Entity.Hull.Mins, Entity.Hull.Maxs) - .WithTag("ladder") - .Ignore(Entity) - .Run(); - - if (pm.Hit) - { - LadderNormal = pm.Normal; - return true; - } - return false; - } - - Entity CheckForGround() - { - if ( Entity.Velocity.z > 100f ) - return null; - - var trace = Entity.TraceBBox( Entity.Position, Entity.Position + Vector3.Down, 2f ); - - if ( !trace.Hit ) - return null; - - if ( trace.Normal.Angle( Vector3.Up ) > GroundAngle ) - return null; - - return trace.Entity; - } - - Vector3 ApplyFriction( Vector3 input, float frictionAmount ) - { - float StopSpeed = 100.0f; - - var speed = input.Length; - if ( speed < 0.1f ) return input; - - // Bleed off some speed, but if we have less than the bleed - // threshold, bleed the threshold amount. - float control = (speed < StopSpeed) ? StopSpeed : speed; - - // Add the amount to the drop amount. - var drop = control * Time.Delta * frictionAmount; - - // scale the velocity - float newspeed = speed - drop; - if ( newspeed < 0 ) newspeed = 0; - if ( newspeed == speed ) return input; - - newspeed /= speed; - input *= newspeed; - - return input; - } - - Vector3 Accelerate( Vector3 input, Vector3 wishdir, float wishspeed, float speedLimit, float acceleration ) - { - if ( speedLimit > 0 && wishspeed > speedLimit ) - wishspeed = speedLimit; - - var currentspeed = input.Dot( wishdir ); - var addspeed = wishspeed - currentspeed; - - if ( addspeed <= 0 ) - return input; - - var accelspeed = acceleration * Time.Delta * wishspeed; - - if ( accelspeed > addspeed ) - accelspeed = addspeed; - - input += wishdir * accelspeed; - - return input; - } - - Vector3 ApplyJump( Vector3 input, string jumpType ) - { - AddEvent( jumpType ); - - return input + Vector3.Up * JumpSpeed; - } - - Vector3 StayOnGround( Vector3 position ) - { - var start = position + Vector3.Up * 2; - var end = position + Vector3.Down * StepSize; - - // See how far up we can go without getting stuck - var trace = Entity.TraceBBox( position, start ); - start = trace.EndPosition; - - // Now trace down from a known safe position - trace = Entity.TraceBBox( start, end ); - - if ( trace.Fraction <= 0 ) return position; - if ( trace.Fraction >= 1 ) return position; - if ( trace.StartedSolid ) return position; - if ( Vector3.GetAngle( Vector3.Up, trace.Normal ) > GroundAngle ) return position; - - return trace.EndPosition; - } - - public bool HasEvent( string eventName ) - { - return ControllerEvents.Contains( eventName ); - } - - void AddEvent( string eventName ) - { - if ( HasEvent( eventName ) ) - return; - - ControllerEvents.Add( eventName ); - } -} diff --git a/code/pawn/component/movement/WalkControllerComponent.cs b/code/pawn/component/movement/WalkControllerComponent.cs new file mode 100644 index 0000000..765437c --- /dev/null +++ b/code/pawn/component/movement/WalkControllerComponent.cs @@ -0,0 +1,1368 @@ +using Sandbox; +using System; +using System.Collections.Generic; + +namespace MurderGame; + +public partial class WalkControllerComponent : BaseControllerComponent +{ + public bool CanSprint + { + get + { + return TeamCapabilities.CanSprint( Entity.CurrentTeam ); + } + } + [Net] public float SprintSpeed { get; set; } = 320.0f; + [Net] public float WalkSpeed { get; set; } = 150.0f; + [Net] public float CrouchSpeed { get; set; } = 80.0f; + [Net] public float DefaultSpeed { get; set; } = 190.0f; + [Net] public float Acceleration { get; set; } = 10.0f; + [Net] public float AirAcceleration { get; set; } = 50.0f; + [Net] public float FallSoundZ { get; set; } = -30.0f; + [Net] public float GroundFriction { get; set; } = 4.0f; + [Net] public float StopSpeed { get; set; } = 100.0f; + [Net] public float Size { get; set; } = 20.0f; + [Net] public float DistEpsilon { get; set; } = 0.03125f; + [Net] public float GroundAngle { get; set; } = 46.0f; + [Net] public float Bounce { get; set; } = 0.0f; + [Net] public float MoveFriction { get; set; } = 1.0f; + [Net] public float StepSize { get; set; } = 18.0f; + [Net] public float MaxNonJumpVelocity { get; set; } = 140.0f; + [Net] public float BodyGirth { get; set; } = 32.0f; + [Net] public float BodyHeight { get; set; } = 72.0f; + [Net] public float EyeHeight { get; set; } = 64.0f; + [Net] public float DuckHeight { get; set; } = 32.0f; + [Net] public float DuckEyeHeight { get; set; } = 24.0f; + [Net] public float Gravity { get; set; } = 800.0f; + [Net] public float AirControl { get; set; } = 30.0f; + [Net] public float SpeedMultiplier { get; set; } = 1f; + [ConVar.Replicated( "walkcontroller_showbbox" )] public bool ShowBBox { get; set; } = false; + public bool Swimming { get; set; } = false; + [Net] public bool AutoJump { get; set; } = false; + public TimeSince TimeSinceFootstep { get; set; } + public bool FootstepFoot { get; set; } + + public WalkControllerComponent() + { + } + + /// + /// This is temporary, get the hull size for the player's collision + /// + public BBox GetHull() + { + var girth = BodyGirth * 0.5f; + var height = BodyHeight; + if ( IsDucking ) height = 32; + var mins = new Vector3( -girth, -girth, 0 ); + var maxs = new Vector3( +girth, +girth, BodyHeight ); + + return new BBox( mins, maxs ); + } + + + // Duck body height 32 + // Eye Height 64 + // Duck Eye Height 28 + + public Vector3 mins; + public Vector3 maxs; + + /// + /// Any bbox traces we do will be offset by this amount. + /// todo: this needs to be predicted + /// + public Vector3 TraceOffset; + + public virtual void SetBBox( Vector3 mins, Vector3 maxs ) + { + if ( this.mins == mins && this.maxs == maxs ) + return; + + this.mins = mins; + this.maxs = maxs; + } + + /// + /// Update the size of the bbox. We should really trigger some shit if this changes. + /// + public virtual void UpdateBBox( int forceduck = 0 ) + { + var girth = BodyGirth * 0.5f; + var height = (EyeHeight + DuckAmount) + (BodyHeight - EyeHeight); + if ( forceduck == 1 ) height = DuckHeight; + if ( forceduck == -1 ) height = BodyHeight; + + var mins = new Vector3( -girth, -girth, 0 ) * Entity.Scale; + var maxs = new Vector3( +girth, +girth, height ) * Entity.Scale; + + SetBBox( mins, maxs ); + } + + protected float SurfaceFriction; + + + public override void FrameSimulate( IClient cl ) + { + if ( ShowBBox ) DebugOverlay.Box( Entity.Position, mins, maxs, Color.Yellow ); + RestoreGroundAngles(); + var pl = Entity as Player; + SaveGroundAngles(); + DuckFrameSimulate(); + } + + public override void BuildInput() + { + + var pl = Entity as Player; + pl.InputDirection = Input.AnalogMove; + } + public override void Simulate( IClient cl ) + { + var pl = Entity as Player; + + Events?.Clear(); + Tags?.Clear(); + + pl.EyeLocalPosition = Vector3.Up * EyeHeight; + pl.EyeRotation = pl.ViewAngles.ToRotation(); + + + CheckDuck(); + UpdateBBox(); + + + RestoreGroundPos(); + //Entity.Velocity += Entity.BaseVelocity * (1 + Time.Delta * 0.5f); + //Entity.BaseVelocity = Vector3.Zero; + + //Rot = Rotation.LookAt( Input.Rotation.Forward.WithZ( 0 ), Vector3.Up ); + + + // Check Stuck + // Unstuck - or return if stuck + + // Set Ground Entity to null if falling faster then 250 + + // store water level to compare later + + // if not on ground, store fall velocity + + // player->UpdateStepSound( player->m_pSurfaceData, mv->GetAbsOrigin(), mv->m_vecVelocity ) + + + // RunLadderMode + + CheckLadder(); + Swimming = Entity.GetWaterLevel() > 0.5f; + + // + // Start Gravity + // + if ( !Swimming && !IsTouchingLadder ) + { + Entity.Velocity -= new Vector3( 0, 0, (Gravity * Entity.Scale) * 0.5f ) * Time.Delta; + Entity.Velocity += new Vector3( 0, 0, Entity.BaseVelocity.z ) * Time.Delta; + + Entity.BaseVelocity = Entity.BaseVelocity.WithZ( 0 ); + } + + + /* + if (player->m_flWaterJumpTime) + { + WaterJump(); + TryPlayerMove(); + // See if we are still in water? + CheckWater(); + return; + } + */ + + // if ( underwater ) do underwater movement + if ( Entity.GetWaterLevel() > 0.1 ) + { + WaterSimulate(); + } + else + if ( AutoJump ? Input.Down( "Jump" ) : Input.Pressed( "Jump" ) ) + { + CheckJumpButton(); + } + + // Fricion is handled before we add in any base velocity. That way, if we are on a conveyor, + // we don't slow when standing still, relative to the conveyor. + bool bStartOnGround = Entity.GroundEntity != null; + //bool bDropSound = false; + if ( bStartOnGround ) + { + //if ( Velocity.z < FallSoundZ ) bDropSound = true; + + Entity.Velocity = Entity.Velocity.WithZ( 0 ); + //player->m_Local.m_flFallVelocity = 0.0f; + + if ( Entity.GroundEntity != null ) + { + ApplyFriction( GroundFriction * SurfaceFriction ); + } + } + + // + // Work out wish velocity.. just take input, rotate it to view, clamp to -1, 1 + // + WishVelocity = new Vector3( pl.InputDirection.x.Clamp( -1f, 1f ), pl.InputDirection.y.Clamp( -1f, 1f ), 0 ); + var inSpeed = WishVelocity.Length.Clamp( 0, 1 ); + + if ( Swimming || IsTouchingLadder ) + { + + WishVelocity *= pl.ViewAngles.ToRotation(); + } + else + { + + WishVelocity *= pl.ViewAngles.WithPitch( 0 ).ToRotation(); + } + + + if ( !Swimming && !IsTouchingLadder ) + { + WishVelocity = WishVelocity.WithZ( 0 ); + } + + WishVelocity = WishVelocity.Normal * inSpeed; + WishVelocity *= GetWishSpeed(); + WishVelocity *= Entity.Scale; + WishVelocity *= SpeedMultiplier; + + + bool bStayOnGround = false; + if ( Swimming ) + { + ApplyFriction( 1 ); + WaterMove(); + } + else if ( IsTouchingLadder ) + { + Entity.Tags.Add( "climbing" ); + LadderMove(); + } + else if ( Entity.GroundEntity != null ) + { + bStayOnGround = true; + WalkMove(); + } + else + { + AirMove(); + } + + CategorizePosition( bStayOnGround ); + + // FinishGravity + if ( !Swimming && !IsTouchingLadder ) + { + Entity.Velocity -= new Vector3( 0, 0, (Gravity * Entity.Scale) * 0.5f ) * Time.Delta; + } + + + if ( Entity.GroundEntity != null ) + { + Entity.Velocity = Entity.Velocity.WithZ( 0 ); + } + DoPushingStuff(); + if ( Entity == null ) return; + + // CheckFalling(); // fall damage etc + + // Land Sound + // Swim Sounds + + SaveGroundPos(); + LatchOntoLadder(); + PreviousGroundEntity = Entity.GroundEntity; + + bool Debug = false; + if ( Debug ) + { + DebugOverlay.Box( Entity.Position + TraceOffset, mins, maxs, Color.Red ); + DebugOverlay.Box( Entity.Position, mins, maxs, Color.Blue ); + + var lineOffset = 0; + if ( Game.IsServer ) lineOffset = 10; + + DebugOverlay.ScreenText( $" Position: {Entity.Position}", lineOffset + 0 ); + DebugOverlay.ScreenText( $" Velocity: {Entity.Velocity}", lineOffset + 1 ); + DebugOverlay.ScreenText( $" BaseVelocity: {Entity.BaseVelocity}", lineOffset + 2 ); + DebugOverlay.ScreenText( $" GroundEntity: {Entity.GroundEntity} [{Entity.GroundEntity?.Velocity}]", lineOffset + 3 ); + DebugOverlay.ScreenText( $" SurfaceFriction: {SurfaceFriction}", lineOffset + 4 ); + DebugOverlay.ScreenText( $" WishVelocity: {WishVelocity}", lineOffset + 5 ); + DebugOverlay.ScreenText( $" Speed: {Entity.Velocity.Length}", lineOffset + 6 ); + } + + } + + public virtual float GetWishSpeed() + { + var ws = -1;// Duck.GetWishSpeed(); + if ( ws >= 0 ) return ws; + + if ( Input.Down( "Duck" ) || IsDucking ) return CrouchSpeed; + if ( Input.Down( "Run" ) && CanSprint ) return SprintSpeed; + if ( Input.Down( "Walk" ) ) return WalkSpeed; + + return DefaultSpeed; + } + + public virtual void WalkMove() + { + var wishdir = WishVelocity.Normal; + var wishspeed = WishVelocity.Length; + + WishVelocity = WishVelocity.WithZ( 0 ); + WishVelocity = WishVelocity.Normal * wishspeed; + + Entity.Velocity = Entity.Velocity.WithZ( 0 ); + Accelerate( wishdir, wishspeed, 0, Acceleration ); + Entity.Velocity = Entity.Velocity.WithZ( 0 ); + + // Player.SetAnimParam( "forward", Input.Forward ); + // Player.SetAnimParam( "sideward", Input.Right ); + // Player.SetAnimParam( "wishspeed", wishspeed ); + // Player.SetAnimParam( "walkspeed_scale", 2.0f / 190.0f ); + // Player.SetAnimParam( "runspeed_scale", 2.0f / 320.0f ); + + // DebugOverlay.Text( 0, Pos + Vector3.Up * 100, $"forward: {Input.Forward}\nsideward: {Input.Right}" ); + + // Add in any base velocity to the current velocity. + if (Entity.Velocity.Length != 0) + { + var footstepTimeThreshold = 60f / Entity.Velocity.Length; + + if (TimeSinceFootstep > footstepTimeThreshold) + { + var trace = Trace.Ray( Entity.Position, Entity.Position + Vector3.Down * 20f ) + .Radius( 1f ) + .Ignore( Entity ) + .Run(); + + if (trace.Hit) + { + FootstepFoot = !FootstepFoot; + + trace.Surface.DoFootstep( Entity, trace, FootstepFoot ? 1 : 0, 35f * (Entity.Velocity.Length / SprintSpeed) ); + + TimeSinceFootstep = 0; + } + } + } + + Entity.Velocity += Entity.BaseVelocity; + try + { + if ( Entity.Velocity.Length < 1.0f ) + { + Entity.Velocity = Vector3.Zero; + return; + } + + // first try just moving to the destination + var dest = (Entity.Position + Entity.Velocity * Time.Delta).WithZ( Entity.Position.z ); + + var pm = TraceBBox( Entity.Position, dest ); + + if ( pm.Fraction == 1 ) + { + Entity.Position = pm.EndPosition; + StayOnGround(); + return; + } + + StepMove(); + + } + finally + { + + // Now pull the base velocity back out. Base velocity is set if you are on a moving object, like a conveyor (or maybe another monster?) + Entity.Velocity -= Entity.BaseVelocity; + } + + StayOnGround(); + + Entity.Velocity = Entity.Velocity.Normal * MathF.Min( Entity.Velocity.Length, (GetWishSpeed() * Entity.Scale) ); + } + + public virtual void StepMove() + { + MoveHelper mover = new MoveHelper( Entity.Position, Entity.Velocity ); + mover.Trace = mover.Trace.Size( mins, maxs ).Ignore( Entity ); + mover.MaxStandableAngle = GroundAngle; + + mover.TryMoveWithStep( Time.Delta, StepSize * Entity.Scale ); + + Entity.Position = mover.Position; + Entity.Velocity = mover.Velocity; + } + + public virtual void Move() + { + MoveHelper mover = new MoveHelper( Entity.Position, Entity.Velocity ); + mover.Trace = mover.Trace.Size( mins, maxs ).Ignore( Entity ); + mover.MaxStandableAngle = GroundAngle; + + mover.TryMove( Time.Delta ); + + Entity.Position = mover.Position; + Entity.Velocity = mover.Velocity; + } + + /// + /// Add our wish direction and speed onto our velocity + /// + public virtual void Accelerate( Vector3 wishdir, float wishspeed, float speedLimit, float acceleration ) + { + // This gets overridden because some games (CSPort) want to allow dead (observer) players + // to be able to move around. + // if ( !CanAccelerate() ) + // return; + speedLimit *= Entity.Scale; + acceleration /= Entity.Scale; + if ( speedLimit > 0 && wishspeed > speedLimit ) + wishspeed = speedLimit; + + // See if we are changing direction a bit + var currentspeed = Entity.Velocity.Dot( wishdir ); + + // Reduce wishspeed by the amount of veer. + var addspeed = wishspeed - currentspeed; + + // If not going to add any speed, done. + if ( addspeed <= 0 ) + return; + + // Determine amount of acceleration. + var accelspeed = (acceleration * Entity.Scale) * Time.Delta * wishspeed * SurfaceFriction; + + // Cap at addspeed + if ( accelspeed > addspeed ) + accelspeed = addspeed; + + Entity.Velocity += wishdir * accelspeed; + } + + /// + /// Remove ground friction from velocity + /// + public virtual void ApplyFriction( float frictionAmount = 1.0f ) + { + // If we are in water jump cycle, don't apply friction + //if ( player->m_flWaterJumpTime ) + // return; + // Not on ground - no friction + // Calculate speed + var speed = Entity.Velocity.Length; + if ( speed < 0.1f ) return; + + // Bleed off some speed, but if we have less than the bleed + // threshold, bleed the threshold amount. + float control = (speed < StopSpeed * Entity.Scale) ? (StopSpeed * Entity.Scale) : speed; + + // Add the amount to the drop amount. + var drop = control * Time.Delta * frictionAmount; + + // scale the velocity + float newspeed = speed - drop; + if ( newspeed < 0 ) newspeed = 0; + + if ( newspeed != speed ) + { + newspeed /= speed; + Entity.Velocity *= newspeed; + } + + // mv->m_outWishVel -= (1.f-newspeed) * mv->m_vecVelocity; + } + + [Net, Predicted] public bool IsDucking { get; set; } // replicate + [Net, Predicted] public float DuckAmount { get; set; } = 0; + public virtual void CheckDuck() + { + var pl = Entity as Player; + bool wants = Input.Down( "Duck" ); + + if ( wants != IsDucking ) + { + if ( wants ) TryDuck(); + else TryUnDuck(); + } + + if ( IsDucking ) + { + var delta = DuckAmount; + DuckAmount = DuckAmount.LerpTo( (EyeHeight - DuckEyeHeight) * -1, 8 * Time.Delta ); + delta -= DuckAmount; + SetTag( "ducked" ); + if ( pl.GroundEntity is null ) + { + pl.Position += Vector3.Up * (delta * Entity.Scale); + var pm = TraceBBox( Entity.Position, Entity.Position ); + if ( pm.StartedSolid ) + { + pl.Position -= Vector3.Up * (delta * Entity.Scale); + } + } + FixPlayerCrouchStuck( true ); + CategorizePosition( false ); + } + else + { + var delta = DuckAmount; + DuckAmount = DuckAmount.LerpTo( 0, 8 * Time.Delta ); + delta -= DuckAmount; + + if ( pl.GroundEntity is null ) + { + pl.Position += Vector3.Up * (delta * Entity.Scale); + + var pm = TraceBBox( Entity.Position, Entity.Position ); + if ( pm.StartedSolid ) + { + pl.Position -= Vector3.Up * (delta * Entity.Scale); + } + } + CategorizePosition( false ); + } + pl.EyeLocalPosition = pl.EyeLocalPosition.WithZ( EyeHeight + (DuckAmount) ); + + } + public float LocalDuckAmount { get; set; } = 0; + void DuckFrameSimulate() + { + + var pl = Entity as Player; + if ( IsDucking ) + { + LocalDuckAmount = LocalDuckAmount.LerpTo( (EyeHeight - DuckEyeHeight) * -1, 8 * Time.Delta ); + } + else + { + LocalDuckAmount = LocalDuckAmount.LerpTo( 0, 8 * Time.Delta ); + } + pl.EyeLocalPosition = pl.EyeLocalPosition.WithZ( EyeHeight + LocalDuckAmount ); + } + public virtual void TryDuck() + { + IsDucking = true; + } + + public virtual void TryUnDuck() + { + UpdateBBox( -1 ); + var pm = TraceBBox( Entity.Position, Entity.Position, DuckHeight - 4 ); + if ( pm.StartedSolid ) + { + UpdateBBox(); + return; + } + IsDucking = false; + } + + public virtual void FixPlayerCrouchStuck( bool upward ) + { + int direction = upward ? 1 : 0; + + var trace = TraceBBox( Entity.Position, Entity.Position ); + if ( trace.Entity == null ) + return; + + var test = Entity.Position; + for ( int i = 0; i < (DuckHeight - 4); i++ ) + { + var org = Entity.Position; + org.z += direction; + + Entity.Position = org; + trace = TraceBBox( Entity.Position, Entity.Position ); + if ( trace.Entity == null ) + return; + } + + Entity.Position = test; + } + public virtual void CheckJumpButton() + { + //if ( !player->CanJump() ) + // return false; + + + /* + if ( player->m_flWaterJumpTime ) + { + player->m_flWaterJumpTime -= gpGlobals->frametime(); + if ( player->m_flWaterJumpTime < 0 ) + player->m_flWaterJumpTime = 0; + + return false; + }*/ + + + + // If we are in the water most of the way... + if ( Swimming ) + { + // swimming, not jumping + ClearGroundEntity(); + + Entity.Velocity = Entity.Velocity.WithZ( 100 ); + + // play swimming sound + // if ( player->m_flSwimSoundTime <= 0 ) + { + // Don't play sound again for 1 second + // player->m_flSwimSoundTime = 1000; + // PlaySwimSound(); + } + + return; + } + + if ( Entity.GroundEntity == null ) + return; + + /* + if ( player->m_Local.m_bDucking && (player->GetFlags() & FL_DUCKING) ) + return false; + */ + + /* + // Still updating the eye position. + if ( player->m_Local.m_nDuckJumpTimeMsecs > 0u ) + return false; + */ + + ClearGroundEntity(); + + // player->PlayStepSound( (Vector &)mv->GetAbsOrigin(), player->m_pSurfaceData, 1.0, true ); + + // MoveHelper()->PlayerSetAnimation( PLAYER_JUMP ); + + float flGroundFactor = 1.0f; + //if ( player->m_pSurfaceData ) + { + // flGroundFactor = g_pPhysicsQuery->GetGameSurfaceproperties( player->m_pSurfaceData )->m_flJumpFactor; + } + + float flMul = (268.3281572999747f * Entity.Scale) * 1.2f; + float startz = Entity.Velocity.z; + + Entity.Velocity = Entity.Velocity.WithZ( startz + flMul * flGroundFactor ); + + Entity.Velocity -= new Vector3( 0, 0, (Gravity * Entity.Scale) * 0.5f ) * Time.Delta; + + // mv->m_outJumpVel.z += mv->m_vecVelocity[2] - startz; + // mv->m_outStepHeight += 0.15f; + + // don't jump again until released + //mv->m_nOldButtons |= IN_JUMP; + + AddEvent( "jump" ); + + } + public virtual void WaterSimulate() + { + if ( Entity.GetWaterLevel() > 0.4 ) + { + CheckWaterJump(); + } + + // If we are falling again, then we must not trying to jump out of water any more. + if ( (Entity.Velocity.z < 0.0f) && IsJumpingFromWater ) + { + WaterJumpTime = 0.0f; + } + + // Was jump button pressed? + if ( Input.Down( "Jump" ) ) + { + CheckJumpButton(); + } + SetTag( "swimming" ); + } + protected float WaterJumpTime { get; set; } + protected Vector3 WaterJumpVelocity { get; set; } + protected bool IsJumpingFromWater => WaterJumpTime > 0; + protected TimeSince TimeSinceSwimSound { get; set; } + protected float LastWaterLevel { get; set; } + + public virtual float WaterJumpHeight => 8; + protected void CheckWaterJump() + { + // Already water jumping. + if ( IsJumpingFromWater ) + return; + + // Don't hop out if we just jumped in + // only hop out if we are moving up + if ( Entity.Velocity.z < -180 ) + return; + + // See if we are backing up + var flatvelocity = Entity.Velocity.WithZ( 0 ); + + // Must be moving + var curspeed = flatvelocity.Length; + flatvelocity = flatvelocity.Normal; + + // see if near an edge + var flatforward = Entity.Rotation.Forward.WithZ( 0 ).Normal; + + // Are we backing into water from steps or something? If so, don't pop forward + if ( curspeed != 0 && Vector3.Dot( flatvelocity, flatforward ) < 0 ) + return; + + var vecStart = Entity.Position + (mins + maxs) * .5f; + var vecEnd = vecStart + flatforward * 24; + + var tr = TraceBBox( vecStart, vecEnd ); + if ( tr.Fraction == 1 ) + return; + + vecStart.z = Entity.Position.z + EyeHeight + WaterJumpHeight; + vecEnd = vecStart + flatforward * 24; + WaterJumpVelocity = tr.Normal * -50; + + tr = TraceBBox( vecStart, vecEnd ); + if ( tr.Fraction < 1.0 ) + return; + + // Now trace down to see if we would actually land on a standable surface. + vecStart = vecEnd; + vecEnd.z -= 1024; + + tr = TraceBBox( vecStart, vecEnd ); + if ( tr.Fraction < 1 && tr.Normal.z >= 0.7f ) + { + Entity.Velocity = Entity.Velocity.WithZ( 256 * Entity.Scale ); + Entity.Tags.Add( "waterjump" ); + WaterJumpTime = 2000; + } + } + public virtual void AirMove() + { + var wishdir = WishVelocity.Normal; + var wishspeed = WishVelocity.Length; + + Accelerate( wishdir, wishspeed, AirControl, AirAcceleration ); + + Entity.Velocity += Entity.BaseVelocity; + + Move(); + + Entity.Velocity -= Entity.BaseVelocity; + } + + public virtual void WaterMove() + { + var wishdir = WishVelocity.Normal; + var wishspeed = WishVelocity.Length; + + wishspeed *= 0.8f; + + Accelerate( wishdir, wishspeed, 100 * Entity.Scale, Acceleration ); + + Entity.Velocity += Entity.BaseVelocity; + + Move(); + + Entity.Velocity -= Entity.BaseVelocity; + } + + bool IsTouchingLadder = false; + Vector3 LadderNormal; + + Vector3 LastNonZeroWishLadderVelocity; + public virtual void CheckLadder() + { + var pl = Entity as Player; + + var wishvel = new Vector3( pl.InputDirection.x.Clamp( -1f, 1f ), pl.InputDirection.y.Clamp( -1f, 1f ), 0 ); + if ( wishvel.Length > 0 ) + { + LastNonZeroWishLadderVelocity = wishvel; + } + if ( TryLatchNextTickCounter > 0 ) wishvel = LastNonZeroWishLadderVelocity * -1; + wishvel *= pl.ViewAngles.WithPitch( 0 ).ToRotation(); + wishvel = wishvel.Normal; + + + if ( IsTouchingLadder ) + { + if ( Input.Pressed( "Jump" ) ) + { + var sidem = (Math.Abs( Entity.ViewAngles.ToRotation().Forward.Abs().z - 1 ) * 3).Clamp( 0, 1 ); + var upm = Entity.ViewAngles.ToRotation().Forward.z; + + var Eject = new Vector3(); + + Eject.x = LadderNormal.x * sidem; + Eject.y = LadderNormal.y * sidem; + Eject.z = (3 * upm).Clamp( 0, 1 ); + + Entity.Velocity += (Eject * 180.0f) * Entity.Scale; + IsTouchingLadder = false; + + return; + + } + else if ( Entity.GroundEntity != null && LadderNormal.Dot( wishvel ) > 0 ) + { + IsTouchingLadder = false; + + return; + } + } + + const float ladderDistance = 1.0f; + var start = Entity.Position; + Vector3 end = start + (IsTouchingLadder ? (LadderNormal * -1.0f) : wishvel) * ladderDistance; + + var pm = Trace.Ray( start, end ) + .Size( mins, maxs ) + .WithTag( "ladder" ) + .Ignore( Entity ) + .Run(); + + IsTouchingLadder = false; + + if ( pm.Hit ) + { + IsTouchingLadder = true; + LadderNormal = pm.Normal; + } + } + public virtual void LadderMove() + { + var velocity = WishVelocity; + float normalDot = velocity.Dot( LadderNormal ); + + var cross = LadderNormal * normalDot; + Entity.Velocity = (velocity - cross) + (-normalDot * LadderNormal.Cross( Vector3.Up.Cross( LadderNormal ).Normal )); + + Move(); + } + + Entity PreviousGroundEntity; + int TryLatchNextTickCounter = 0; + Vector3 LastNonZeroWishVelocity; + [ConVar.Replicated( "sv_ladderlatchdebug" )] + public static bool LatchDebug { get; set; } = false; + public virtual void LatchOntoLadder() + { + if ( !WishVelocity.Normal.IsNearlyZero( 0.001f ) ) + { + LastNonZeroWishVelocity = WishVelocity; + } + if ( TryLatchNextTickCounter > 0 ) + { + + Entity.Velocity = (LastNonZeroWishVelocity.Normal * -100).WithZ( Entity.Velocity.z ); + TryLatchNextTickCounter++; + } + if ( TryLatchNextTickCounter >= 10 ) + { + TryLatchNextTickCounter = 0; + } + + if ( Entity.GroundEntity != null ) return; + if ( PreviousGroundEntity == null ) return; + var pos = Entity.Position + (Vector3.Down * 16); + //var tr = TraceBBox( pos, pos ); + + var tr = Trace.Ray( pos, pos - (LastNonZeroWishVelocity.Normal * 8) ) + .Size( mins, maxs ) + .WithTag( "ladder" ) + .Ignore( Entity ) + .Run(); + + if ( LatchDebug ) DebugOverlay.Line( Entity.Position, pos, 10 ); + if ( LatchDebug ) DebugOverlay.Line( tr.StartPosition, tr.EndPosition, 10 ); + if ( tr.Hit ) + { + Entity.Velocity = Vector3.Zero.WithZ( Entity.Velocity.z ); + TryLatchNextTickCounter++; + } + + } + + + public virtual void CategorizePosition( bool bStayOnGround ) + { + SurfaceFriction = 1.0f; + + // Doing this before we move may introduce a potential latency in water detection, but + // doing it after can get us stuck on the bottom in water if the amount we move up + // is less than the 1 pixel 'threshold' we're about to snap to. Also, we'll call + // this several times per frame, so we really need to avoid sticking to the bottom of + // water on each call, and the converse case will correct itself if called twice. + //CheckWater(); + + var point = Entity.Position - Vector3.Up * (2 * Entity.Scale); + var vBumpOrigin = Entity.Position; + + // + // Shooting up really fast. Definitely not on ground trimed until ladder shit + // + bool bMovingUpRapidly = Entity.Velocity.z > MaxNonJumpVelocity; + bool bMovingUp = Entity.Velocity.z > 0; + + bool bMoveToEndPos = false; + + if ( Entity.GroundEntity != null ) // and not underwater + { + bMoveToEndPos = true; + point.z -= StepSize * Entity.Scale; + } + else if ( bStayOnGround ) + { + bMoveToEndPos = true; + point.z -= StepSize * Entity.Scale; + } + + if ( bMovingUpRapidly || Swimming ) // or ladder and moving up + { + ClearGroundEntity(); + return; + } + + var pm = TraceBBox( vBumpOrigin, point, 4.0f ); + + if ( pm.Entity == null || Vector3.GetAngle( Vector3.Up, pm.Normal ) > GroundAngle ) + { + ClearGroundEntity(); + bMoveToEndPos = false; + + if ( Entity.Velocity.z > 0 ) + SurfaceFriction = 0.25f; + } + else + { + UpdateGroundEntity( pm ); + } + + if ( bMoveToEndPos && !pm.StartedSolid && pm.Fraction > 0.0f && pm.Fraction < 1.0f ) + { + Entity.Position = pm.EndPosition; + } + + } + + public Vector3 GroundNormal { get; set; } + /// + /// We have a new ground entity + /// + public virtual void UpdateGroundEntity( TraceResult tr ) + { + GroundNormal = tr.Normal; + + // VALVE HACKHACK: Scale this to fudge the relationship between vphysics friction values and player friction values. + // A value of 0.8f feels pretty normal for vphysics, whereas 1.0f is normal for players. + // This scaling trivially makes them equivalent. REVISIT if this affects low friction surfaces too much. + SurfaceFriction = tr.Surface.Friction * 1.25f; + if ( SurfaceFriction > 1 ) SurfaceFriction = 1; + + //if ( tr.Entity == GroundEntity ) return; + + Vector3 oldGroundVelocity = default; + if ( Entity.GroundEntity != null ) oldGroundVelocity = Entity.GroundEntity.Velocity; + + bool wasOffGround = Entity.GroundEntity == null; + + Entity.GroundEntity = tr.Entity; + + if ( Entity.GroundEntity != null ) + { + Entity.BaseVelocity = Entity.GroundEntity.Velocity; + } + } + + /// + /// We're no longer on the ground, remove it + /// + public virtual void ClearGroundEntity() + { + if ( Entity.GroundEntity == null ) return; + + Entity.GroundEntity = null; + GroundNormal = Vector3.Up; + SurfaceFriction = 1.0f; + } + + /// + /// Traces the current bbox and returns the result. + /// liftFeet will move the start position up by this amount, while keeping the top of the bbox at the same + /// position. This is good when tracing down because you won't be tracing through the ceiling above. + /// + public virtual TraceResult TraceBBox( Vector3 start, Vector3 end, float liftFeet = 0.0f ) + { + return TraceBBox( start, end, mins, maxs, liftFeet ); + } + + /// + /// Traces the bbox and returns the trace result. + /// LiftFeet will move the start position up by this amount, while keeping the top of the bbox at the same + /// position. This is good when tracing down because you won't be tracing through the ceiling above. + /// + public virtual TraceResult TraceBBox( Vector3 start, Vector3 end, Vector3 mins, Vector3 maxs, float liftFeet = 0.0f ) + { + if ( liftFeet > 0 ) + { + liftFeet *= Entity.Scale; + start += Vector3.Up * liftFeet; + maxs = maxs.WithZ( maxs.z - liftFeet ); + } + + var tr = Trace.Ray( start + TraceOffset, end + TraceOffset ) + .Size( mins, maxs ) + .WithAnyTags( "solid", "playerclip", "passbullets", "player" ) + .Ignore( Entity ) + .Run(); + + tr.EndPosition -= TraceOffset; + return tr; + } + + /// + /// Try to keep a walking player on the ground when running down slopes etc + /// + public virtual void StayOnGround() + { + var start = Entity.Position + Vector3.Up * (2 * Entity.Scale); + var end = Entity.Position + Vector3.Down * (StepSize * Entity.Scale); + + // See how far up we can go without getting stuck + var trace = TraceBBox( Entity.Position, start ); + start = trace.EndPosition; + + // Now trace down from a known safe position + trace = TraceBBox( start, end ); + + if ( trace.Fraction <= 0 ) return; + if ( trace.Fraction >= 1 ) return; + if ( trace.StartedSolid ) return; + if ( Vector3.GetAngle( Vector3.Up, trace.Normal ) > GroundAngle ) return; + + // This is incredibly hacky. The real problem is that trace returning that strange value we can't network over. + // float flDelta = fabs( mv->GetAbsOrigin().z - trace.m_vEndPos.z ); + // if ( flDelta > 0.5f * DIST_EPSILON ) + + Entity.Position = trace.EndPosition; + } + + public Transform? GroundTransform; + public Sandbox.Entity? OldGroundEntity; + void RestoreGroundPos() + { + if ( Entity.GroundEntity == null || Entity.GroundEntity.IsWorld || GroundTransform == null || Entity.GroundEntity != OldGroundEntity ) + return; + + var worldTrns = Entity.GroundEntity.Transform.ToWorld( GroundTransform.Value ); + if ( Prediction.FirstTime ) + { + Entity.BaseVelocity = ((Entity.Position - worldTrns.Position) * -1) / Time.Delta; + } + //Entity.Position = (Entity.Position.WithZ( worldTrns.Position.z )); + } + + void SaveGroundPos() + { + var ply = Entity as Player; + if ( Entity.GroundEntity == null || Entity.GroundEntity.IsWorld ) + { + OldGroundEntity = null; + GroundTransform = null; + return; + } + + if ( Entity.GroundEntity == OldGroundEntity ) + { + GroundTransform = Entity.GroundEntity.Transform.ToLocal( Entity.Transform ); + } + else + { + GroundTransform = null; + GroundTransformViewAngles = null; + } + OldGroundEntity = Entity.GroundEntity; + } + + public Transform? GroundTransformViewAngles; + public Angles? PreviousViewAngles; + void RestoreGroundAngles() + { + if ( Entity.GroundEntity == null || Entity.GroundEntity.IsWorld || GroundTransformViewAngles == null || PreviousViewAngles == null ) + return; + + var ply = Entity as Player; + var worldTrnsView = Entity.GroundEntity.Transform.ToWorld( GroundTransformViewAngles.Value ); + ply.ViewAngles -= (PreviousViewAngles.Value.ToRotation() * worldTrnsView.Rotation.Inverse).Angles().WithPitch( 0 ).WithRoll( 0 ); + } + void SaveGroundAngles() + { + + if ( Entity.GroundEntity == null || Entity.GroundEntity.IsWorld ) + { + GroundTransformViewAngles = null; + return; + } + + var ply = Entity as Player; + GroundTransformViewAngles = Entity.GroundEntity.Transform.ToLocal( new Transform( Vector3.Zero, ply.ViewAngles.ToRotation() ) ); + PreviousViewAngles = ply.ViewAngles; + } + bool PushDebug = false; + [SkipHotload] Dictionary OldTransforms; + Transform OldTransform; + void DoPushingStuff() + { + var tr = TraceBBox( Entity.Position, Entity.Position ); + if ( tr.StartedSolid + && tr.Entity != null + && tr.Entity != OldGroundEntity + && tr.Entity != Entity.GroundEntity + && !tr.Entity.IsWorld + && OldTransforms != null + && OldTransforms.TryGetValue( tr.Entity.NetworkIdent, out var oldTransform ) ) + { + if ( tr.Entity is BasePhysics ) return; + var oldPosition = Entity.Position; + var oldTransformLocal = oldTransform.ToLocal( Entity.Transform ); + var newTransform = tr.Entity.Transform.ToWorld( oldTransformLocal ); + + // this used to be just the direction of the tr delta however pushing outwards a llittle seems more appropriate + var direction = ((Entity.Position - newTransform.Position) * -1); + direction += (Entity.Position - tr.Entity.Position).Normal.WithZ( 0 ) * 0.8f; + + FindIdealMovementDirection( newTransform.Position, direction, out var outOffset, out var outDirection ); + + + var newPosition = newTransform.Position + (outDirection * outOffset) + (outDirection * 0.1f); + + // Check if we're being crushed, if not we set our position. + if ( IsBeingCrushed( newPosition ) ) + { + OnCrushed( tr.Entity ); + } + else + { + Entity.Velocity += (outDirection / Time.Delta); + + // insurance we dont instantly get stuck again add a little extra. + Entity.Position = newPosition; + + } + } + + // In order to get the delta of transforms we have not yet touched, we grab the transforms of EVERYTHING with in a radius. + // The radius we look in is determined by player speed and velocity + GetPossibleTransforms(); + } + + void FindIdealMovementDirection( Vector3 Position, Vector3 Direction, out float OutOffset, out Vector3 OutDirection ) + { + OutDirection = Direction; + OutOffset = 0; + // ------------------------ shit ------------------------ + // look into doing this nicer at somepoint + // ------------------------------------------------------ + // brute force our way into finding how much extra we need to be pushed in the case of AABB edges being still inside of the object + for ( int i = 0; i < 512; i++ ) + { + var possibleoffset = (float)(i) / 16f; + var pos = Position + (Direction * possibleoffset); + + var offsettr = TraceBBox( pos, pos ); + + if ( !offsettr.StartedSolid ) + { + if ( PushDebug ) DebugOverlay.Line( Entity.Position, pos, Color.Green, 5 ); + OutDirection = Direction; + OutOffset = possibleoffset; + break; + } + + //sidewards test, for things moving sideways and upwards or downwards + var posside = Position + (Direction.WithZ( 0 ) * possibleoffset); + var offsettrside = TraceBBox( posside, posside ); + + if ( !offsettrside.StartedSolid ) + { + if ( PushDebug ) DebugOverlay.Line( Entity.Position, pos, Color.Green, 5 ); + OutDirection = Direction.WithZ( 0 ); + OutOffset = possibleoffset; + + break; + } + + if ( PushDebug ) DebugOverlay.Line( Entity.Position, pos, Color.Red, 5 ); + } + // ------------------------------------------------------ + } + + bool IsBeingCrushed( Vector3 NewPosition ) + { + //do a trace that will decide whether or not we're being crushed + var crushtrace = Trace.Ray( Entity.Position, NewPosition ) + .Ignore( Entity ) + .Run(); + + if ( PushDebug ) DebugOverlay.Line( crushtrace.StartPosition, crushtrace.EndPosition, Color.Blue, 5 ); + + return crushtrace.Fraction != 1; + } + + void OnCrushed( Entity CurshingEntity ) + { + // deal crush damage! + if ( !Game.IsServer ) return; + //if ( CurshingEntity is DoorEntity || CurshingEntity is PlatformEntity || CurshingEntity is PathPlatformEntity ) + //{ + // Entity.TakeDamage( DamageInfo.Generic( 5 ).WithTag( "crush" ) ); + //} + + // if we get crushed by a door, change its direction. + if ( CurshingEntity is DoorEntity door ) + { + if ( door.State == DoorEntity.DoorState.Opening ) + { + door.Close(); + door.Close(); + } + else if ( door.State == DoorEntity.DoorState.Closing ) + { + door.Open(); + door.Open(); + } + } + } + /*void DoPushingStuff() + { + var tr = TraceBBox( Entity.Position, Entity.Position ); + if ( tr.StartedSolid && tr.Entity != null && !tr.Entity.IsWorld && tr.Entity != OldGroundEntity && tr.Entity != Entity.GroundEntity ) + { + var tf = new Transform(); + if ( OldTransforms != null && OldTransforms.TryGetValue( tr.Entity.NetworkIdent, out tf ) ) + { + var x = tf.ToLocal( Entity.Transform ); + var x2 = tr.Entity.Transform.ToWorld( x ); + + // grab the delta between the last ticks position and this ticks position + var x3 = ((Entity.Position - x2.Position) * -1);//.WithZ( 0 ); + var x4 = ((Entity.Position - x2.Position) * -1).WithZ( 0 ); + + // grab the delta between the last ticks position and this ticks position + var oldpos = Entity.Position; + var oldvel = Entity.Velocity; + bool unstuck = false; + for ( int i = 0; i < 2048; i++ ) + { + var ch = (x3 * (i / 16.0f)); + var pos = Entity.Position + ch; + + + tr.Entity.ResetInterpolation(); + Entity.ResetInterpolation(); + var tr2 = TraceBBox( pos, pos ); + + if ( !tr2.StartedSolid ) + { + var x5 = ((oldpos - pos) * -1); + var tr3 = Trace.Ray( Entity.Position, pos + x3 ).Ignore( tr.Entity ).Ignore( Entity ).Run(); + + + if ( tr3.Fraction != 1 ) + { + var ch2 = (x4 * (i / 16.0f)); + var pos2 = Entity.Position + ch2; + var tr4 = TraceBBox( pos2, pos2 ); + var tr5 = Trace.Ray( Entity.Position, pos2 + x4 ).Ignore( tr.Entity ).Ignore( Entity ).Run(); + if ( !tr4.StartedSolid && tr5.Fraction == 1 ) + { + x3 = x4; + ch = ch2; + pos = pos2; + } + else + { + //crush / stuck + if ( tr.Entity is DoorEntity door ) + { + if ( Game.IsServer ) + { + if ( door.State == DoorEntity.DoorState.Opening ) + door.Close(); + if ( door.State == DoorEntity.DoorState.Closing ) + door.Open(); + + Entity.TakeDamage( DamageInfo.Generic( 10 ).WithTag( "crush" ) ); + } + GetPossibleTransforms(); + OldTransforms.Remove( tr.Entity.NetworkIdent ); + if ( PushDebug ) DebugOverlay.Line( Entity.Position, pos, Color.Red, 5 ); + return; + } + else + { + if ( Game.IsServer ) + { + Entity.TakeDamage( DamageInfo.Generic( 10 ).WithTag( "crush" ) ); + } + if ( PushDebug ) DebugOverlay.Line( Entity.Position, pos, Color.Red, 5 ); + continue; + } + } + } + if ( PushDebug ) DebugOverlay.Line( Entity.Position, pos, Color.Green, 5 ); + if ( !(x3 / Time.Delta).AlmostEqual( Vector3.Zero, 0.01f ) ) + { + Entity.Velocity += (x3 / Time.Delta); + Entity.Position = pos + x3; + } + //Entity.Velocity = (x5 / Time.Delta); + unstuck = true; + break; + } + else + { + if ( PushDebug ) DebugOverlay.Line( Entity.Position, pos, Color.Red, 5 ); + } + } + if ( unstuck ) + { + if ( PushDebug ) DebugOverlay.Text( "unstuck", Entity.Position, Color.Green ); + } + else + { + + if ( PushDebug ) DebugOverlay.Text( "failed", Entity.Position, Color.Red ); + } + } + } + GetPossibleTransforms(); + }*/ + + void GetPossibleTransforms() + { + + var a = Sandbox.Entity.FindInSphere( Entity.Position, 512 + Entity.Velocity.Length ); + var b = new Dictionary(); + foreach ( var i in a ) + { + b.Add( i.NetworkIdent, i.Transform ); + } + OldTransforms = b; + OldTransform = Entity.Transform; + } +} + diff --git a/code/phase/PlayPhase.cs b/code/phase/PlayPhase.cs index 8b4ff7f..2a28fda 100644 --- a/code/phase/PlayPhase.cs +++ b/code/phase/PlayPhase.cs @@ -25,7 +25,7 @@ public class PlayPhase : BasePhase if (pawn.CurrentTeam != Team.Spectator) { pawn.Respawn(); - TeamOperations.GiveLoadouts( pawn ); + TeamCapabilities.GiveLoadouts( pawn ); } } } @@ -50,8 +50,8 @@ public class PlayPhase : BasePhase DeathOverlay.Hide( To.Single( entity ) ); if (entity is Player pawn && pawn.IsValid() ) { - if (pawn.Controller != null) pawn.Controller.SpeedMultiplier = 1; - if (pawn.Inventory != null) pawn.Inventory.AllowPickup = true; + if (pawn.Controller is WalkControllerComponent controller) controller.SpeedMultiplier = 1; + if (pawn.Inventory!= null) pawn.Inventory.AllowPickup = true; } } @@ -126,14 +126,14 @@ public class PlayPhase : BasePhase BlindedOverlay.Show( To.Single( killer ) ); - if (killerPlayer.Controller != null) killerPlayer.Controller.SpeedMultiplier = 0.3f; + if (killerPlayer.Controller is WalkControllerComponent controller) controller.SpeedMultiplier = 0.3f; if (killerPlayer.Inventory != null) { killerPlayer.Inventory.AllowPickup = false; killerPlayer.Inventory.SpillContents(killerPlayer.EyePosition, killerPlayer.AimRay.Forward); } - Blinded[killer] = 20 * Game.TickRate; + Blinded[killer] = 30 * Game.TickRate; } else if (victimTeam == Team.Murderer ) { diff --git a/code/phase/WaitPhase.cs b/code/phase/WaitPhase.cs index 6014467..c3d0e16 100644 --- a/code/phase/WaitPhase.cs +++ b/code/phase/WaitPhase.cs @@ -57,7 +57,7 @@ public class WaitPhase : BasePhase } else { var pawn = (Player)client.Pawn; - if (pawn.LifeState == LifeState.Dead && pawn.TimeSinceDeath > 3) + if (pawn.LifeState == LifeState.Dead && pawn.TimeSinceDeath > 5) { RespawnPlayer( pawn ); } diff --git a/code/team/Team.cs b/code/team/Team.cs index ccca6eb..8c9a7d7 100644 --- a/code/team/Team.cs +++ b/code/team/Team.cs @@ -15,43 +15,8 @@ public enum Team : ushort } // why are c# enums so bad -public static class TeamOperations +public static class TeamCapabilities { - - public static string GetTeamName(Team team) - { - return team switch - { - Team.Detective => "Detective", - Team.Murderer => "Murderer", - Team.Bystander => "Bystander", - Team.Spectator => "Spectator", - _ => "None", - }; - } - - public static string GetTeamColour(Team team) - { - return team switch - { - Team.Detective => "#33A0FF", - Team.Murderer => "#FF4136", - Team.Bystander => "#33A0FF", - _ => "#AAAAAA", - }; - } - - public static string GetTeamDescription(Team team) - { - return team switch - { - Team.Detective => "There is a murderer on the loose! Find out who they are and shoot them before they kill everybody else.", - Team.Murderer => "Kill everybody else in time and avoid detection. At least one other player is armed.", - Team.Bystander => "There is a murderer on the loose! Avoid getting killed and work with others to establish who the murderer is.", - _ => "None", - }; - } - public static bool CanSprint(Team team) { return team switch @@ -89,3 +54,40 @@ public static class TeamOperations pawn.Inventory.SetPrimaryWeapon( new Knife() ); } } +public static class TeamOperations +{ + + public static string GetTeamName(Team team) + { + return team switch + { + Team.Detective => "Detective", + Team.Murderer => "Murderer", + Team.Bystander => "Bystander", + Team.Spectator => "Spectator", + _ => "None", + }; + } + + public static string GetTeamColour(Team team) + { + return team switch + { + Team.Detective => "#33A0FF", + Team.Murderer => "#FF4136", + Team.Bystander => "#33A0FF", + _ => "#AAAAAA", + }; + } + + public static string GetTeamDescription(Team team) + { + return team switch + { + Team.Detective => "There is a murderer on the loose! Find out who they are and shoot them before they kill everybody else.", + Team.Murderer => "Kill everybody else in time and avoid detection. At least one other player is armed.", + Team.Bystander => "There is a murderer on the loose! Avoid getting killed and work with others to establish who the murderer is.", + _ => "None", + }; + } +} diff --git a/code/ui/health/Health.razor b/code/ui/health/Health.razor index c3c6660..8961f63 100644 --- a/code/ui/health/Health.razor +++ b/code/ui/health/Health.razor @@ -21,7 +21,7 @@ Health { left: 0; } -
+
@code diff --git a/code/ui/overlay/DeathOverlay.razor b/code/ui/overlay/DeathOverlay.razor index 15848f6..23c3a17 100644 --- a/code/ui/overlay/DeathOverlay.razor +++ b/code/ui/overlay/DeathOverlay.razor @@ -10,6 +10,11 @@ 0% { opacity: 0; } 100% { opacity: 1; } } +@@keyframes blurIn +{ + 0% { opacity: 0; } + 100% { opacity: 1; } +} deathoverlay { } .overlay { @@ -18,11 +23,22 @@ deathoverlay { top: 0; width: 100vw; height: 100vh; - - background-color: rgba(80, 1, 1, 0.90); - backdrop-filter-blur: 64px; +} +.fade { + opacity: 0; + background-color: rgb(0, 0, 0); animation-name: fadeIn; - animation-duration: 2.5s; + animation-duration: 0.5s; + animation-delay: 1.5s; + animation-iteration-count: 1; + animation-timing-function: ease-in; + animation-fill-mode: forwards; +} +.blur { + opacity: 0; + backdrop-filter-blur: 32px; + animation-name: fadeIn; + animation-duration: 1.5s; animation-iteration-count: 1; animation-timing-function: ease-in; animation-fill-mode: forwards; @@ -56,7 +72,9 @@ deathoverlay { @if (ShowOverlay) { -
+
+
+
@@ -78,11 +96,6 @@ deathoverlay { protected override int BuildHash() { - var clientPawn = Game.LocalPawn; - if (clientPawn is Player player) - { - ShowOverlay = player.LifeState == LifeState.Dead && player.Spectator == null; - } return ShowOverlay.GetHashCode(); } diff --git a/code/ui/spectator/Spectator.razor b/code/ui/spectator/Spectator.razor index f6fe8af..fe0d662 100644 --- a/code/ui/spectator/Spectator.razor +++ b/code/ui/spectator/Spectator.razor @@ -43,16 +43,12 @@ spectator { protected override int BuildHash() { var localPawn = Game.LocalPawn; - if (localPawn is Player player) + if (localPawn is Player player && player.Camera is SpectatorCameraComponent spectator) { - var spectator = player.Spectator; - if (spectator != null) - { - var target = spectator.Target; - Spectating = true; - TargetName = (target != null && target.IsValid() && target.LifeState == LifeState.Alive) ? target.Client.Name : ""; - return HashCode.Combine(Spectating.GetHashCode(), TargetName.GetHashCode()); - } + var target = spectator.Target; + Spectating = true; + TargetName = (target != null && target.IsValid() && target.LifeState == LifeState.Alive) ? target.Client.Name : ""; + return HashCode.Combine(Spectating.GetHashCode(), TargetName.GetHashCode()); } if (Spectating) { -- cgit v1.2.3-70-g09d2