From 71db52c5443a7bf82d9a23a770994a42b043be04 Mon Sep 17 00:00:00 2001 From: Leonardo Bishop Date: Thu, 27 Jul 2023 22:11:31 +0100 Subject: Initial commit --- code/pawn/Player.cs | 218 ++++++++++++++++++++++++++++++++++++++++++ code/pawn/PlayerAnimator.cs | 21 ++++ code/pawn/PlayerController.cs | 177 ++++++++++++++++++++++++++++++++++ code/pawn/PlayerInventory.cs | 126 ++++++++++++++++++++++++ code/pawn/Ragdoll.cs | 13 +++ 5 files changed, 555 insertions(+) create mode 100644 code/pawn/Player.cs create mode 100644 code/pawn/PlayerAnimator.cs create mode 100644 code/pawn/PlayerController.cs create mode 100644 code/pawn/PlayerInventory.cs create mode 100644 code/pawn/Ragdoll.cs (limited to 'code/pawn') diff --git a/code/pawn/Player.cs b/code/pawn/Player.cs new file mode 100644 index 0000000..a1b126a --- /dev/null +++ b/code/pawn/Player.cs @@ -0,0 +1,218 @@ +using Sandbox; +using Sandbox.UI; +using System.ComponentModel; + +namespace MurderGame; + +public partial class Player : AnimatedEntity +{ + [ClientInput] + public Vector3 InputDirection { get; set; } + + [ClientInput] + public Angles ViewAngles { get; set; } + + [Browsable( false )] + public Vector3 EyePosition + { + get => Transform.PointToWorld( EyeLocalPosition ); + set => EyeLocalPosition = Transform.PointToLocal( value ); + } + + [Net, Predicted, Browsable( false )] + public Vector3 EyeLocalPosition { get; set; } + + [Browsable( false )] + public Rotation EyeRotation + { + get => Transform.RotationToWorld( EyeLocalRotation ); + set => EyeLocalRotation = Transform.RotationToLocal( value ); + } + + [Net, Predicted, Browsable( false )] + public Rotation EyeLocalRotation { get; set; } + + public BBox Hull + { + get => new + ( + new Vector3( -12, -12, 0 ), + new Vector3( 12, 12, 64 ) + ); + } + + [Net] + public Team CurrentTeam { get; set; } + + [BindComponent] public PlayerController Controller { get; } + [BindComponent] public PlayerAnimator Animator { get; } + [BindComponent] public PlayerInventory Inventory { get; } + + public Ragdoll PlayerRagdoll { get; set; } + public ClothingContainer PlayerClothingContainer { get; set; } + + public Vector3 LastAttackForce { get; set; } + public int LastHitBone { get; set; } + + public override Ray AimRay => new Ray( EyePosition, EyeRotation.Forward ); + + public override void Spawn() + { + SetModel( "models/citizen/citizen.vmdl" ); + + Tags.Add( "player" ); + EnableHitboxes = true; + EnableDrawing = false; + EnableHideInFirstPerson = true; + EnableShadowInFirstPerson = true; + SetupPhysicsFromAABB( PhysicsMotionType.Keyframed, Hull.Mins, Hull.Maxs ); + EnableSolidCollisions = false; + } + + public void Respawn() + { + Tags.Add( "livingplayer" ); + EnableAllCollisions = true; + EnableDrawing = true; + Components.Create(); + Components.Create(); + Components.Create(); + Health = 100f; + DeleteRagdoll(); + } + + public void Cleanup() + { + DisablePlayer(); + DeleteRagdoll(); + } + + public void DeleteRagdoll() + { + if (PlayerRagdoll != null) + { + PlayerRagdoll.Delete(); + PlayerRagdoll = null; + } + } + + public void DisablePlayer() + { + EnableAllCollisions = false; + LifeState = LifeState.Dead; + Tags.Remove( "livingplayer" ); + Inventory?.Clear(); + Components.RemoveAll(); + EnableDrawing = false; + } + + public override void OnKilled() + { + Inventory?.SpillContents(EyePosition, new Vector3(0,0,0)); + DisablePlayer(); + Event.Run( MurderEvent.Kill, LastAttacker, this ); + var ragdoll = new Ragdoll(); + ragdoll.Position = Position; + ragdoll.Rotation = Rotation; + ragdoll.CopyFrom(this); + ragdoll.PhysicsGroup.AddVelocity(LastAttackForce / 100); + PlayerClothingContainer.DressEntity( ragdoll ); + PlayerRagdoll = ragdoll; + } + + public override void TakeDamage( DamageInfo info ) + { + LastAttacker = info.Attacker; + LastAttackerWeapon = info.Weapon; + LastAttackForce = info.Force; + LastHitBone = info.BoneIndex; + if (Game.IsServer && Health > 0f && LifeState == LifeState.Alive) + { + Health -= info.Damage; + if (Health <= 0f) + { + Health = 0f; + OnKilled(); + } + } + } + + public void DressFromClient( IClient cl ) + { + PlayerClothingContainer = new ClothingContainer(); + PlayerClothingContainer.LoadFromClient( cl ); + PlayerClothingContainer.DressEntity( this ); + } + + public override void Simulate( IClient cl ) + { + SimulateRotation(); + Controller?.Simulate( cl ); + Animator?.Simulate(); + Inventory?.Simulate( cl ); + EyeLocalPosition = Vector3.Up * (64f * Scale); + } + + 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; + } + + bool IsThirdPerson { get; set; } = false; + + public override void FrameSimulate( IClient cl ) + { + SimulateRotation(); + + Camera.Rotation = ViewAngles.ToRotation(); + Camera.FieldOfView = Screen.CreateVerticalFieldOfView( Game.Preferences.FieldOfView ); + + Camera.FirstPersonViewer = this; + Camera.Position = EyePosition; + } + + public TraceResult TraceBBox( Vector3 start, Vector3 end, float liftFeet = 0.0f ) + { + return TraceBBox( start, end, Hull.Mins, Hull.Maxs, liftFeet ); + } + + public TraceResult TraceBBox( Vector3 start, Vector3 end, Vector3 mins, Vector3 maxs, float liftFeet = 0.0f ) + { + if ( liftFeet > 0 ) + { + start += Vector3.Up * liftFeet; + maxs = maxs.WithZ( maxs.z - liftFeet ); + } + + var tr = Trace.Ray( start, end ) + .Size( mins, maxs ) + .WithAnyTags( "solid", "player", "passbullets" ) + .Ignore( this ) + .Run(); + + return tr; + } + + protected void SimulateRotation() + { + EyeRotation = ViewAngles.ToRotation(); + Rotation = ViewAngles.WithPitch( 0f ).ToRotation(); + } + +} diff --git a/code/pawn/PlayerAnimator.cs b/code/pawn/PlayerAnimator.cs new file mode 100644 index 0000000..4cd4e3f --- /dev/null +++ b/code/pawn/PlayerAnimator.cs @@ -0,0 +1,21 @@ +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/PlayerController.cs b/code/pawn/PlayerController.cs new file mode 100644 index 0000000..d638bb0 --- /dev/null +++ b/code/pawn/PlayerController.cs @@ -0,0 +1,177 @@ +using Sandbox; +using System; +using System.Collections.Generic; + +namespace MurderGame; + +public class PlayerController : EntityComponent +{ + public int StepSize => 12; + public int GroundAngle => 45; + public int JumpSpeed => 300; + public float Gravity => 800f; + public float SpeedMultiplier { get; set; } = 1; + + HashSet ControllerEvents = new( StringComparer.OrdinalIgnoreCase ); + + bool Grounded => Entity.GroundEntity.IsValid(); + + public void Simulate( IClient cl ) + { + 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; + + if ( groundEntity.IsValid() ) + { + if ( !Grounded ) + { + Entity.Velocity = Entity.Velocity.WithZ( 0 ); + AddEvent( "grounded" ); + } + var sprintMultiplier = TeamOperations.CanSprint( team ) ? (Input.Down( "run" ) ? 2.5f : 1f) : 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); + } + + if ( Input.Pressed( "jump" ) ) + { + DoJump(); + } + + var mh = new MoveHelper( Entity.Position, Entity.Velocity ); + mh.Trace = mh.Trace.Size( Entity.Hull ).Ignore( Entity ); + + 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" ); + } + } + + 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/PlayerInventory.cs b/code/pawn/PlayerInventory.cs new file mode 100644 index 0000000..55fa3ed --- /dev/null +++ b/code/pawn/PlayerInventory.cs @@ -0,0 +1,126 @@ +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/Ragdoll.cs b/code/pawn/Ragdoll.cs new file mode 100644 index 0000000..9948946 --- /dev/null +++ b/code/pawn/Ragdoll.cs @@ -0,0 +1,13 @@ +using Sandbox; + +public partial class Ragdoll : AnimatedEntity +{ + public Ragdoll() + { + Tags.Add( "ragdoll" ); + PhysicsEnabled = true; + UsePhysicsCollision = true; + EnableSelfCollisions = true; + EnableSolidCollisions = true; + } +} -- cgit v1.2.3-70-g09d2