aboutsummaryrefslogtreecommitdiffstats
path: root/code/pawn
diff options
context:
space:
mode:
Diffstat (limited to 'code/pawn')
-rw-r--r--code/pawn/Player.cs218
-rw-r--r--code/pawn/PlayerAnimator.cs21
-rw-r--r--code/pawn/PlayerController.cs177
-rw-r--r--code/pawn/PlayerInventory.cs126
-rw-r--r--code/pawn/Ragdoll.cs13
5 files changed, 555 insertions, 0 deletions
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<PlayerController>();
+ Components.Create<PlayerAnimator>();
+ Components.Create<PlayerInventory>();
+ 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<Player>, 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<Player>
+{
+ public int StepSize => 12;
+ public int GroundAngle => 45;
+ public int JumpSpeed => 300;
+ public float Gravity => 800f;
+ public float SpeedMultiplier { get; set; } = 1;
+
+ HashSet<string> 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<Player>
+{
+ 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;
+ }
+}