Add files via upload

This commit is contained in:
celisej567
2021-08-05 17:54:51 +03:00
committed by GitHub
parent d58ad89d11
commit 76b55e2a87
57 changed files with 5632 additions and 0 deletions

31
code/Carriable.cs Normal file
View File

@@ -0,0 +1,31 @@
using Sandbox;
public partial class Carriable : BaseCarriable, IUse
{
public override void CreateViewModel()
{
Host.AssertClient();
if ( string.IsNullOrEmpty( ViewModelPath ) )
return;
ViewModelEntity = new ViewModel
{
Position = Position,
Owner = Owner,
EnableViewmodelRendering = true
};
ViewModelEntity.SetModel( ViewModelPath );
}
public bool OnUse( Entity user )
{
return false;
}
public virtual bool IsUsable( Entity user )
{
return Owner == null;
}
}

97
code/Game.cs Normal file
View File

@@ -0,0 +1,97 @@
using Sandbox;
[Library( "sandbox", Title = "Sandbox" )]
partial class SandboxGame : Game
{
public SandboxGame()
{
if ( IsServer )
{
// Create the HUD
_ = new SandboxHud();
}
}
public override void ClientJoined( Client cl )
{
base.ClientJoined( cl );
var player = new SandboxPlayer();
player.Respawn();
cl.Pawn = player;
}
protected override void OnDestroy()
{
base.OnDestroy();
}
[ServerCmd( "spawn" )]
public static void Spawn( string modelname )
{
var owner = ConsoleSystem.Caller?.Pawn;
if ( ConsoleSystem.Caller == null )
return;
var tr = Trace.Ray( owner.EyePos, owner.EyePos + owner.EyeRot.Forward * 500 )
.UseHitboxes()
.Ignore( owner )
.Run();
var ent = new Prop();
ent.Position = tr.EndPos;
ent.Rotation = Rotation.From( new Angles( 0, owner.EyeRot.Angles().yaw, 0 ) ) * Rotation.FromAxis( Vector3.Up, 180 );
ent.SetModel( modelname );
ent.Position = tr.EndPos - Vector3.Up * ent.CollisionBounds.Mins.z;
}
[ServerCmd( "spawn_entity" )]
public static void SpawnEntity( string entName )
{
var owner = ConsoleSystem.Caller.Pawn;
if ( owner == null )
return;
var attribute = Library.GetAttribute( entName );
if ( attribute == null || !attribute.Spawnable )
return;
var tr = Trace.Ray( owner.EyePos, owner.EyePos + owner.EyeRot.Forward * 200 )
.UseHitboxes()
.Ignore( owner )
.Size( 2 )
.Run();
var ent = Library.Create<Entity>( entName );
if ( ent is BaseCarriable && owner.Inventory != null )
{
if ( owner.Inventory.Add( ent, true ) )
return;
}
ent.Position = tr.EndPos;
ent.Rotation = Rotation.From( new Angles( 0, owner.EyeRot.Angles().yaw, 0 ) );
//Log.Info( $"ent: {ent}" );
}
public override void DoPlayerNoclip( Client player )
{
if ( player.Pawn is Player basePlayer )
{
if ( basePlayer.DevController is NoclipController )
{
Log.Info( "Noclip Mode Off" );
basePlayer.DevController = null;
}
else
{
Log.Info( "Noclip Mode On" );
basePlayer.DevController = new NoclipController();
}
}
}
}

50
code/Inventory.cs Normal file
View File

@@ -0,0 +1,50 @@
using Sandbox;
using System;
using System.Linq;
partial class Inventory : BaseInventory
{
public Inventory( Player player ) : base( player )
{
}
public override bool CanAdd( Entity entity )
{
if ( !entity.IsValid() )
return false;
if ( !base.CanAdd( entity ) )
return false;
return !IsCarryingType( entity.GetType() );
}
public override bool Add( Entity entity, bool makeActive = false )
{
if ( !entity.IsValid() )
return false;
if ( IsCarryingType( entity.GetType() ) )
return false;
return base.Add( entity, makeActive );
}
public bool IsCarryingType( Type t )
{
return List.Any( x => x?.GetType() == t );
}
public override bool Drop( Entity ent )
{
if ( !Host.IsServer )
return false;
if ( !Contains( ent ) )
return false;
ent.OnCarryDrop( Owner );
return ent.Parent == null;
}
}

113
code/Player.Clothes.cs Normal file
View File

@@ -0,0 +1,113 @@
using Sandbox;
partial class SandboxPlayer
{
ModelEntity pants;
ModelEntity jacket;
ModelEntity shoes;
ModelEntity hat;
bool dressed = false;
public void Dress()
{
if ( dressed ) return;
dressed = true;
if ( true )
{
var model = Rand.FromArray( new[]
{
"models/citizen_clothes/trousers/trousers.jeans.vmdl",
"models/citizen_clothes/trousers/trousers.lab.vmdl",
"models/citizen_clothes/trousers/trousers.police.vmdl",
"models/citizen_clothes/trousers/trousers.smart.vmdl",
"models/citizen_clothes/trousers/trousers.smarttan.vmdl",
"models/citizen/clothes/trousers_tracksuit.vmdl",
"models/citizen_clothes/trousers/trousers_tracksuitblue.vmdl",
"models/citizen_clothes/trousers/trousers_tracksuit.vmdl",
"models/citizen_clothes/shoes/shorts.cargo.vmdl",
} );
pants = new ModelEntity();
pants.SetModel( model );
pants.SetParent( this, true );
pants.EnableShadowInFirstPerson = true;
pants.EnableHideInFirstPerson = true;
SetBodyGroup( "Legs", 1 );
}
if ( true )
{
var model = Rand.FromArray( new[]
{
"models/citizen_clothes/jacket/labcoat.vmdl",
"models/citizen_clothes/jacket/jacket.red.vmdl",
"models/citizen_clothes/jacket/jacket.tuxedo.vmdl",
"models/citizen_clothes/jacket/jacket_heavy.vmdl",
} );
jacket = new ModelEntity();
jacket.SetModel( model );
jacket.SetParent( this, true );
jacket.EnableShadowInFirstPerson = true;
jacket.EnableHideInFirstPerson = true;
var propInfo = jacket.GetModel().GetPropData();
if ( propInfo.ParentBodyGroupName != null )
{
SetBodyGroup( propInfo.ParentBodyGroupName, propInfo.ParentBodyGroupValue );
}
else
{
SetBodyGroup( "Chest", 0 );
}
}
if ( true )
{
var model = Rand.FromArray( new[]
{
"models/citizen_clothes/shoes/trainers.vmdl",
"models/citizen_clothes/shoes/shoes.workboots.vmdl"
} );
shoes = new ModelEntity();
shoes.SetModel( model );
shoes.SetParent( this, true );
shoes.EnableShadowInFirstPerson = true;
shoes.EnableHideInFirstPerson = true;
SetBodyGroup( "Feet", 1 );
}
if ( true )
{
var model = Rand.FromArray( new[]
{
"models/citizen_clothes/hat/hat_hardhat.vmdl",
"models/citizen_clothes/hat/hat_woolly.vmdl",
"models/citizen_clothes/hat/hat_securityhelmet.vmdl",
"models/citizen_clothes/hair/hair_malestyle02.vmdl",
"models/citizen_clothes/hair/hair_femalebun.black.vmdl",
"models/citizen_clothes/hat/hat_beret.red.vmdl",
"models/citizen_clothes/hat/hat.tophat.vmdl",
"models/citizen_clothes/hat/hat_beret.black.vmdl",
"models/citizen_clothes/hat/hat_cap.vmdl",
"models/citizen_clothes/hat/hat_leathercap.vmdl",
"models/citizen_clothes/hat/hat_leathercapnobadge.vmdl",
"models/citizen_clothes/hat/hat_securityhelmetnostrap.vmdl",
"models/citizen_clothes/hat/hat_service.vmdl",
"models/citizen_clothes/hat/hat_uniform.police.vmdl",
"models/citizen_clothes/hat/hat_woollybobble.vmdl",
} );
hat = new ModelEntity();
hat.SetModel( model );
hat.SetParent( this, true );
hat.EnableShadowInFirstPerson = true;
hat.EnableHideInFirstPerson = true;
}
}
}

85
code/Player.Ragdoll.cs Normal file
View File

@@ -0,0 +1,85 @@
using Sandbox;
partial class SandboxPlayer
{
[ClientRpc]
private void BecomeRagdollOnClient( Vector3 velocity, DamageFlags damageFlags, Vector3 forcePos, Vector3 force, int bone )
{
var ent = new ModelEntity();
ent.Position = Position;
ent.Rotation = Rotation;
ent.Scale = Scale;
ent.MoveType = MoveType.Physics;
ent.UsePhysicsCollision = true;
ent.EnableAllCollisions = true;
ent.CollisionGroup = CollisionGroup.Debris;
ent.SetModel( GetModelName() );
ent.CopyBonesFrom( this );
ent.CopyBodyGroups( this );
ent.CopyMaterialGroup( this );
ent.TakeDecalsFrom( this );
ent.EnableHitboxes = true;
ent.EnableAllCollisions = true;
ent.SurroundingBoundsMode = SurroundingBoundsType.Physics;
ent.RenderColorAndAlpha = RenderColorAndAlpha;
ent.PhysicsGroup.Velocity = velocity;
if ( Local.Pawn == this )
{
//ent.EnableDrawing = false; wtf
}
ent.SetInteractsAs( CollisionLayer.Debris );
ent.SetInteractsWith( CollisionLayer.WORLD_GEOMETRY );
ent.SetInteractsExclude( CollisionLayer.Player | CollisionLayer.Debris );
foreach ( var child in Children )
{
if ( child is ModelEntity e )
{
var model = e.GetModelName();
if ( model != null && !model.Contains( "clothes" ) )
continue;
var clothing = new ModelEntity();
clothing.SetModel( model );
clothing.SetParent( ent, true );
clothing.RenderColorAndAlpha = e.RenderColorAndAlpha;
if ( Local.Pawn == this )
{
// clothing.EnableDrawing = false; wtf
}
}
}
if ( damageFlags.HasFlag( DamageFlags.Bullet ) ||
damageFlags.HasFlag( DamageFlags.PhysicsImpact ) )
{
PhysicsBody body = bone > 0 ? ent.GetBonePhysicsBody( bone ) : null;
if ( body != null )
{
body.ApplyImpulseAt( forcePos, force * body.Mass );
}
else
{
ent.PhysicsGroup.ApplyImpulse( force );
}
}
if ( damageFlags.HasFlag( DamageFlags.Blast ) )
{
if ( ent.PhysicsGroup != null )
{
ent.PhysicsGroup.AddVelocity( (Position - (forcePos + Vector3.Down * 100.0f)).Normal * (force.Length * 0.2f) );
var angularDir = (Rotation.FromYaw( 90 ) * force.WithZ( 0 ).Normal).Normal;
ent.PhysicsGroup.AddAngularVelocity( angularDir * (force.Length * 0.02f) );
}
}
Corpse = ent;
ent.DeleteAsync( 10.0f );
}
}

35
code/Player.Use.cs Normal file
View File

@@ -0,0 +1,35 @@
using Sandbox;
partial class SandboxPlayer
{
public bool IsUseDisabled()
{
return ActiveChild is IUse use && use.IsUsable( this );
}
protected override Entity FindUsable()
{
if ( IsUseDisabled() )
return null;
var tr = Trace.Ray( EyePos, EyePos + EyeRot.Forward * (85 * Scale) )
.Radius( 2 )
.HitLayer( CollisionLayer.Debris )
.Ignore( this )
.Run();
if ( tr.Entity == null ) return null;
if ( tr.Entity is not IUse use ) return null;
if ( !use.IsUsable( this ) ) return null;
return tr.Entity;
}
protected override void UseFail()
{
if ( IsUseDisabled() )
return;
base.UseFail();
}
}

240
code/Player.cs Normal file
View File

@@ -0,0 +1,240 @@
using Sandbox;
partial class SandboxPlayer : Player
{
private TimeSince timeSinceDropped;
private TimeSince timeSinceJumpReleased;
private DamageInfo lastDamage;
[Net] public PawnController VehicleController { get; set; }
[Net] public PawnAnimator VehicleAnimator { get; set; }
[Net, Predicted] public ICamera VehicleCamera { get; set; }
[Net, Predicted] public Entity Vehicle { get; set; }
[Net, Predicted] public ICamera MainCamera { get; set; }
public ICamera LastCamera { get; set; }
public SandboxPlayer()
{
Inventory = new Inventory( this );
}
public override void Spawn()
{
MainCamera = new FirstPersonCamera();
LastCamera = MainCamera;
base.Spawn();
}
public override void Respawn()
{
SetModel( "models/citizen/citizen.vmdl" );
Controller = new WalkController();
Animator = new StandardPlayerAnimator();
MainCamera = LastCamera;
Camera = MainCamera;
if ( DevController is NoclipController )
{
DevController = null;
}
EnableAllCollisions = true;
EnableDrawing = true;
EnableHideInFirstPerson = true;
EnableShadowInFirstPerson = true;
Dress();
Inventory.Add( new PhysGun(), true );
Inventory.Add( new GravGun() );
Inventory.Add( new Tool() );
Inventory.Add( new Pistol() );
Inventory.Add( new Flashlight() );
base.Respawn();
}
public override void OnKilled()
{
base.OnKilled();
if ( lastDamage.Flags.HasFlag( DamageFlags.Vehicle ) )
{
Particles.Create( "particles/impact.flesh.bloodpuff-big.vpcf", lastDamage.Position );
Particles.Create( "particles/impact.flesh-big.vpcf", lastDamage.Position );
PlaySound( "kersplat" );
}
VehicleController = null;
VehicleAnimator = null;
VehicleCamera = null;
Vehicle = null;
BecomeRagdollOnClient( Velocity, lastDamage.Flags, lastDamage.Position, lastDamage.Force, GetHitboxBone( lastDamage.HitboxIndex ) );
LastCamera = MainCamera;
MainCamera = new SpectateRagdollCamera();
Camera = MainCamera;
Controller = null;
EnableAllCollisions = false;
EnableDrawing = false;
Inventory.DropActive();
Inventory.DeleteContents();
}
public override void TakeDamage( DamageInfo info )
{
if ( GetHitboxGroup( info.HitboxIndex ) == 1 )
{
info.Damage *= 10.0f;
}
lastDamage = info;
TookDamage( lastDamage.Flags, lastDamage.Position, lastDamage.Force );
base.TakeDamage( info );
}
[ClientRpc]
public void TookDamage( DamageFlags damageFlags, Vector3 forcePos, Vector3 force )
{
}
public override PawnController GetActiveController()
{
if ( VehicleController != null ) return VehicleController;
if ( DevController != null ) return DevController;
return base.GetActiveController();
}
public override PawnAnimator GetActiveAnimator()
{
if ( VehicleAnimator != null ) return VehicleAnimator;
return base.GetActiveAnimator();
}
public ICamera GetActiveCamera()
{
if ( VehicleCamera != null ) return VehicleCamera;
return MainCamera;
}
public override void Simulate( Client cl )
{
base.Simulate( cl );
if ( Input.ActiveChild != null )
{
ActiveChild = Input.ActiveChild;
}
if ( LifeState != LifeState.Alive )
return;
if ( VehicleController != null && DevController is NoclipController )
{
DevController = null;
}
var controller = GetActiveController();
if ( controller != null )
EnableSolidCollisions = !controller.HasTag( "noclip" );
TickPlayerUse();
SimulateActiveChild( cl, ActiveChild );
if ( Input.Pressed( InputButton.View ) )
{
if ( MainCamera is not FirstPersonCamera )
{
MainCamera = new FirstPersonCamera();
}
else
{
MainCamera = new ThirdPersonCamera();
}
}
Camera = GetActiveCamera();
if ( Input.Pressed( InputButton.Drop ) )
{
var dropped = Inventory.DropActive();
if ( dropped != null )
{
dropped.PhysicsGroup.ApplyImpulse( Velocity + EyeRot.Forward * 500.0f + Vector3.Up * 100.0f, true );
dropped.PhysicsGroup.ApplyAngularImpulse( Vector3.Random * 100.0f, true );
timeSinceDropped = 0;
}
}
if ( Input.Released( InputButton.Jump ) )
{
if ( timeSinceJumpReleased < 0.3f )
{
Game.Current?.DoPlayerNoclip( cl );
}
timeSinceJumpReleased = 0;
}
if ( Input.Left != 0 || Input.Forward != 0 )
{
timeSinceJumpReleased = 1;
}
}
public override void StartTouch( Entity other )
{
if ( timeSinceDropped < 1 ) return;
base.StartTouch( other );
}
[ServerCmd( "inventory_current" )]
public static void SetInventoryCurrent( string entName )
{
var target = ConsoleSystem.Caller.Pawn;
if ( target == null ) return;
var inventory = target.Inventory;
if ( inventory == null )
return;
for ( int i = 0; i < inventory.Count(); ++i )
{
var slot = inventory.GetSlot( i );
if ( !slot.IsValid() )
continue;
if ( !slot.ClassInfo.IsNamed( entName ) )
continue;
inventory.SetActiveSlot( i, false );
break;
}
}
// TODO
//public override bool HasPermission( string mode )
//{
// if ( mode == "noclip" ) return true;
// if ( mode == "devcam" ) return true;
// if ( mode == "suicide" ) return true;
//
// return base.HasPermission( mode );
// }
}

39
code/PreviewEntity.cs Normal file
View File

@@ -0,0 +1,39 @@

namespace Sandbox.Tools
{
public class PreviewEntity : ModelEntity
{
public bool RelativeToNormal { get; set; } = true;
public bool OffsetBounds { get; set; } = false;
public Rotation RotationOffset { get; set; } = Rotation.Identity;
public Vector3 PositionOffset { get; set; } = Vector3.Zero;
internal bool UpdateFromTrace( TraceResult tr )
{
if ( !IsTraceValid( tr ) )
{
return false;
}
if ( RelativeToNormal )
{
Rotation = Rotation.LookAt( tr.Normal, tr.Direction ) * RotationOffset;
Position = tr.EndPos + Rotation * PositionOffset;
}
else
{
Rotation = Rotation.Identity * RotationOffset;
Position = tr.EndPos + PositionOffset;
}
if ( OffsetBounds )
{
Position += tr.Normal * CollisionBounds.Size * 0.5f;
}
return true;
}
protected virtual bool IsTraceValid( TraceResult tr ) => tr.Hit;
}
}

14
code/Tool.Effects.cs Normal file
View File

@@ -0,0 +1,14 @@
using Sandbox;
public partial class Tool
{
[ClientRpc]
public void CreateHitEffects( Vector3 hitPos )
{
var particle = Particles.Create( "particles/tool_hit.vpcf", hitPos );
particle.SetPosition( 0, hitPos );
particle.Destroy( false );
PlaySound( "balloon_pop_cute" );
}
}

92
code/Tool.Preview.cs Normal file
View File

@@ -0,0 +1,92 @@
using System.Collections.Generic;
namespace Sandbox.Tools
{
public partial class BaseTool
{
internal List<PreviewEntity> Previews;
protected virtual bool IsPreviewTraceValid( TraceResult tr )
{
if ( !tr.Hit )
return false;
if ( !tr.Entity.IsValid() )
return false;
return true;
}
public virtual void CreatePreviews()
{
// Nothing
}
public virtual void DeletePreviews()
{
if ( Previews == null || Previews.Count == 0 )
return;
foreach ( var preview in Previews )
{
preview.Delete();
}
Previews.Clear();
}
public virtual bool TryCreatePreview( ref PreviewEntity ent, string model )
{
if ( !ent.IsValid() )
{
ent = new PreviewEntity();
ent.SetModel( model );
}
if ( Previews == null )
{
Previews = new List<PreviewEntity>();
}
if ( !Previews.Contains( ent ) )
{
Previews.Add( ent );
}
return ent.IsValid();
}
private void UpdatePreviews()
{
if ( Previews == null || Previews.Count == 0 )
return;
if ( !Owner.IsValid() )
return;
var startPos = Owner.EyePos;
var dir = Owner.EyeRot.Forward;
var tr = Trace.Ray( startPos, startPos + dir * 10000.0f )
.Ignore( Owner )
.Run();
foreach ( var preview in Previews )
{
if ( !preview.IsValid() )
continue;
if ( IsPreviewTraceValid( tr ) && preview.UpdateFromTrace( tr ) )
{
preview.RenderAlpha = 0.5f;
}
else
{
preview.RenderAlpha = 0.0f;
}
}
}
}
}

124
code/Tool.cs Normal file
View File

@@ -0,0 +1,124 @@
using Sandbox;
using Sandbox.Tools;
[Library( "weapon_tool", Title = "Toolgun" )]
partial class Tool : Carriable
{
[ConVar.ClientData( "tool_current" )]
public static string UserToolCurrent { get; set; } = "tool_boxgun";
public override string ViewModelPath => "weapons/rust_pistol/v_rust_pistol.vmdl";
[Net, Predicted]
public BaseTool CurrentTool { get; set; }
public override void Spawn()
{
base.Spawn();
SetModel( "weapons/rust_pistol/rust_pistol.vmdl" );
}
public override void Simulate( Client owner )
{
UpdateCurrentTool( owner );
CurrentTool?.Simulate();
}
private void UpdateCurrentTool( Client owner )
{
var toolName = owner.GetUserString( "tool_current", "tool_boxgun" );
if ( toolName == null )
return;
// Already the right tool
if ( CurrentTool != null && CurrentTool.Parent == this && CurrentTool.Owner == owner.Pawn && CurrentTool.ClassInfo.IsNamed( toolName ) )
return;
if ( CurrentTool != null )
{
CurrentTool?.Deactivate();
CurrentTool = null;
}
CurrentTool = Library.Create<BaseTool>( toolName, false );
if ( CurrentTool != null )
{
CurrentTool.Parent = this;
CurrentTool.Owner = owner.Pawn as Player;
CurrentTool.Activate();
}
}
public override void ActiveStart( Entity ent )
{
base.ActiveStart( ent );
CurrentTool?.Activate();
}
public override void ActiveEnd( Entity ent, bool dropped )
{
base.ActiveEnd( ent, dropped );
CurrentTool?.Deactivate();
}
protected override void OnDestroy()
{
base.OnDestroy();
CurrentTool?.Deactivate();
CurrentTool = null;
}
public override void OnCarryDrop( Entity dropper )
{
}
[Event.Frame]
public void OnFrame()
{
if ( !IsActiveChild() ) return;
CurrentTool?.OnFrame();
}
}
namespace Sandbox.Tools
{
public partial class BaseTool : NetworkComponent
{
public Tool Parent { get; set; }
public Player Owner { get; set; }
protected virtual float MaxTraceDistance => 10000.0f;
public virtual void Activate()
{
CreatePreviews();
}
public virtual void Deactivate()
{
DeletePreviews();
}
public virtual void Simulate()
{
}
public virtual void OnFrame()
{
UpdatePreviews();
}
public virtual void CreateHitEffects( Vector3 pos )
{
Parent?.CreateHitEffects( pos );
}
}
}

102
code/ViewModel.cs Normal file
View File

@@ -0,0 +1,102 @@
using Sandbox;
public class ViewModel : BaseViewModel
{
protected float SwingInfluence => 0.05f;
protected float ReturnSpeed => 5.0f;
protected float MaxOffsetLength => 10.0f;
protected float BobCycleTime => 7;
protected Vector3 BobDirection => new Vector3( 0.0f, 1.0f, 0.5f );
private Vector3 swingOffset;
private float lastPitch;
private float lastYaw;
private float bobAnim;
private bool activated = false;
public override void PostCameraSetup( ref CameraSetup camSetup )
{
base.PostCameraSetup( ref camSetup );
if ( !Local.Pawn.IsValid() )
return;
if ( !activated )
{
lastPitch = camSetup.Rotation.Pitch();
lastYaw = camSetup.Rotation.Yaw();
activated = true;
}
Position = camSetup.Position;
Rotation = camSetup.Rotation;
camSetup.ViewModel.FieldOfView = FieldOfView;
var playerVelocity = Local.Pawn.Velocity;
if ( Local.Pawn is Player player )
{
var controller = player.GetActiveController();
if ( controller != null && controller.HasTag( "noclip" ) )
{
playerVelocity = Vector3.Zero;
}
}
var newPitch = Rotation.Pitch();
var newYaw = Rotation.Yaw();
var pitchDelta = Angles.NormalizeAngle( newPitch - lastPitch );
var yawDelta = Angles.NormalizeAngle( lastYaw - newYaw );
var verticalDelta = playerVelocity.z * Time.Delta;
var viewDown = Rotation.FromPitch( newPitch ).Up * -1.0f;
verticalDelta *= (1.0f - System.MathF.Abs( viewDown.Cross( Vector3.Down ).y ));
pitchDelta -= verticalDelta * 1;
var offset = CalcSwingOffset( pitchDelta, yawDelta );
offset += CalcBobbingOffset( playerVelocity );
Position += Rotation * offset;
lastPitch = newPitch;
lastYaw = newYaw;
}
protected Vector3 CalcSwingOffset( float pitchDelta, float yawDelta )
{
Vector3 swingVelocity = new Vector3( 0, yawDelta, pitchDelta );
swingOffset -= swingOffset * ReturnSpeed * Time.Delta;
swingOffset += (swingVelocity * SwingInfluence);
if ( swingOffset.Length > MaxOffsetLength )
{
swingOffset = swingOffset.Normal * MaxOffsetLength;
}
return swingOffset;
}
protected Vector3 CalcBobbingOffset( Vector3 velocity )
{
bobAnim += Time.Delta * BobCycleTime;
var twoPI = System.MathF.PI * 2.0f;
if ( bobAnim > twoPI )
{
bobAnim -= twoPI;
}
var speed = new Vector2( velocity.x, velocity.y ).Length;
speed = speed > 10.0 ? speed : 0.0f;
var offset = BobDirection * (speed * 0.005f) * System.MathF.Cos( bobAnim );
offset = offset.WithZ( -System.MathF.Abs( offset.z ) );
return offset;
}
}

202
code/Weapon.cs Normal file
View File

@@ -0,0 +1,202 @@
using Sandbox;
public partial class Weapon : BaseWeapon, IUse
{
public virtual float ReloadTime => 3.0f;
public PickupTrigger PickupTrigger { get; protected set; }
[Net, Predicted]
public TimeSince TimeSinceReload { get; set; }
[Net, Predicted]
public bool IsReloading { get; set; }
[Net, Predicted]
public TimeSince TimeSinceDeployed { get; set; }
public override void Spawn()
{
base.Spawn();
PickupTrigger = new PickupTrigger
{
Parent = this,
Position = Position,
EnableTouch = true,
EnableSelfCollisions = false
};
PickupTrigger.PhysicsBody.EnableAutoSleeping = false;
}
public override void ActiveStart( Entity ent )
{
base.ActiveStart( ent );
TimeSinceDeployed = 0;
}
public override void Reload()
{
if ( IsReloading )
return;
TimeSinceReload = 0;
IsReloading = true;
(Owner as AnimEntity)?.SetAnimBool( "b_reload", true );
StartReloadEffects();
}
public override void Simulate( Client owner )
{
if ( TimeSinceDeployed < 0.6f )
return;
if ( !IsReloading )
{
base.Simulate( owner );
}
if ( IsReloading && TimeSinceReload > ReloadTime )
{
OnReloadFinish();
}
}
public virtual void OnReloadFinish()
{
IsReloading = false;
}
[ClientRpc]
public virtual void StartReloadEffects()
{
ViewModelEntity?.SetAnimBool( "reload", true );
// TODO - player third person model reload
}
public override void CreateViewModel()
{
Host.AssertClient();
if ( string.IsNullOrEmpty( ViewModelPath ) )
return;
ViewModelEntity = new ViewModel
{
Position = Position,
Owner = Owner,
EnableViewmodelRendering = true
};
ViewModelEntity.SetModel( ViewModelPath );
}
public bool OnUse( Entity user )
{
if ( Owner != null )
return false;
if ( !user.IsValid() )
return false;
user.StartTouch( this );
return false;
}
public virtual bool IsUsable( Entity user )
{
if ( Owner != null ) return false;
if ( user.Inventory is Inventory inventory )
{
return inventory.CanAdd( this );
}
return true;
}
public void Remove()
{
PhysicsGroup?.Wake();
Delete();
}
[ClientRpc]
protected virtual void ShootEffects()
{
Host.AssertClient();
Particles.Create( "particles/pistol_muzzleflash.vpcf", EffectEntity, "muzzle" );
if ( IsLocalPawn )
{
_ = new Sandbox.ScreenShake.Perlin();
}
ViewModelEntity?.SetAnimBool( "fire", true );
CrosshairPanel?.CreateEvent( "fire" );
}
/// <summary>
/// Shoot a single bullet
/// </summary>
public virtual void ShootBullet( Vector3 pos, Vector3 dir, float spread, float force, float damage, float bulletSize )
{
var forward = dir;
forward += (Vector3.Random + Vector3.Random + Vector3.Random + Vector3.Random) * spread * 0.25f;
forward = forward.Normal;
//
// ShootBullet is coded in a way where we can have bullets pass through shit
// or bounce off shit, in which case it'll return multiple results
//
foreach ( var tr in TraceBullet( pos, pos + forward * 5000, bulletSize ) )
{
tr.Surface.DoBulletImpact( tr );
if ( !IsServer ) continue;
if ( !tr.Entity.IsValid() ) continue;
//
// We turn predictiuon off for this, so any exploding effects don't get culled etc
//
using ( Prediction.Off() )
{
var damageInfo = DamageInfo.FromBullet( tr.EndPos, forward * 100 * force, damage )
.UsingTraceResult( tr )
.WithAttacker( Owner )
.WithWeapon( this );
tr.Entity.TakeDamage( damageInfo );
}
}
}
/// <summary>
/// Shoot a single bullet from owners view point
/// </summary>
public virtual void ShootBullet( float spread, float force, float damage, float bulletSize )
{
ShootBullet( Owner.EyePos, Owner.EyeRot.Forward, spread, force, damage, bulletSize );
}
/// <summary>
/// Shoot a multiple bullets from owners view point
/// </summary>
public virtual void ShootBullets( int numBullets, float spread, float force, float damage, float bulletSize )
{
var pos = Owner.EyePos;
var dir = Owner.EyeRot.Forward;
for ( int i = 0; i < numBullets; i++ )
{
ShootBullet( pos, dir, spread, force / numBullets, damage, bulletSize );
}
}
}

View File

@@ -0,0 +1,37 @@
using Sandbox;
[Library( "ent_balloon", Title = "Balloon", Spawnable = true )]
public partial class BalloonEntity : Prop
{
private static float GravityScale => -0.2f;
public override void Spawn()
{
base.Spawn();
SetModel( "models/citizen_props/balloonregular01.vmdl" );
SetupPhysicsFromModel( PhysicsMotionType.Dynamic, false );
PhysicsBody.GravityScale = GravityScale;
RenderColor = Color.Random.ToColor32();
}
public override void OnKilled()
{
base.OnKilled();
PlaySound( "balloon_pop_cute" );
}
[Event.Physics.PostStep]
public void OnPostPhysicsStep()
{
if ( !this.IsValid() )
return;
var body = PhysicsBody;
if ( !body.IsValid() )
return;
body.GravityScale = GravityScale;
}
}

View File

@@ -0,0 +1,43 @@
using Sandbox;
using System;
[Library( "ent_bouncyball", Title = "Bouncy Ball", Spawnable = true )]
public partial class BouncyBallEntity : Prop, IUse
{
public float MaxSpeed { get; set; } = 1000.0f;
public float SpeedMul { get; set; } = 1.2f;
public override void Spawn()
{
base.Spawn();
SetModel( "models/ball/ball.vmdl" );
SetupPhysicsFromModel( PhysicsMotionType.Dynamic, false );
Scale = Rand.Float( 0.5f, 2.0f );
RenderColor = Color.Random.ToColor32();
}
protected override void OnPhysicsCollision( CollisionEventData eventData )
{
var speed = eventData.PreVelocity.Length;
var direction = Vector3.Reflect( eventData.PreVelocity.Normal, eventData.Normal.Normal ).Normal;
Velocity = direction * MathF.Min( speed * SpeedMul, MaxSpeed );
}
public bool IsUsable( Entity user )
{
return true;
}
public bool OnUse( Entity user )
{
if ( user is Player player )
{
player.Health += 10;
Delete();
}
return false;
}
}

View File

@@ -0,0 +1,64 @@
using Sandbox;
using System.Linq;
[Library( "directional_gravity", Title = "Directional Gravity", Spawnable = true )]
public partial class DirectionalGravity : Prop
{
bool enabled = false;
public override void Spawn()
{
base.Spawn();
DeleteOthers();
SetModel( "models/arrow.vmdl" );
SetupPhysicsFromModel( PhysicsMotionType.Dynamic, false );
enabled = true;
}
private void DeleteOthers()
{
// Only allow one of these to be spawned at a time
foreach ( var ent in All.OfType<DirectionalGravity>()
.Where( x => x.IsValid() && x != this ) )
{
ent.Delete();
}
}
protected override void OnDestroy()
{
base.OnDestroy();
if ( IsServer )
{
PhysicsWorld.UseDefaultGravity();
PhysicsWorld.WakeAllBodies();
}
enabled = false;
}
[Event.Physics.PostStep]
public void OnPostPhysicsStep()
{
if ( !IsServer )
return;
if ( !enabled )
return;
if ( !this.IsValid() )
return;
var gravity = Rotation.Down * 800.0f;
if ( gravity != PhysicsWorld.Gravity )
{
PhysicsWorld.Gravity = gravity;
PhysicsWorld.WakeAllBodies();
}
}
}

View File

@@ -0,0 +1,146 @@
using Sandbox;
using System;
[Library( "ent_drone", Title = "Drone", Spawnable = true )]
public partial class DroneEntity : Prop
{
public virtual float altitudeAcceleration => 2000;
public virtual float movementAcceleration => 5000;
public virtual float yawSpeed => 150;
public virtual float uprightSpeed => 5000;
public virtual float uprightDot => 0.5f;
public virtual float leanWeight => 0.5f;
public virtual float leanMaxVelocity => 1000;
private struct DroneInputState
{
public Vector3 movement;
public float throttle;
public float pitch;
public float yaw;
public void Reset()
{
movement = Vector3.Zero;
pitch = 0;
yaw = 0;
}
}
private DroneInputState currentInput;
public override void Spawn()
{
base.Spawn();
SetModel( "entities/drone/drone.vmdl" );
SetupPhysicsFromModel( PhysicsMotionType.Dynamic, false );
}
[Event.Physics.PostStep]
public void OnPostPhysicsStep()
{
if ( !PhysicsBody.IsValid() )
{
return;
}
var body = PhysicsBody;
var transform = Transform;
body.LinearDrag = 1.0f;
body.AngularDrag = 1.0f;
body.LinearDamping = 4.0f;
body.AngularDamping = 4.0f;
var yawRot = Rotation.From( new Angles( 0, Rotation.Angles().yaw, 0 ) );
var worldMovement = yawRot * currentInput.movement;
var velocityDirection = body.Velocity.WithZ( 0 );
var velocityMagnitude = velocityDirection.Length;
velocityDirection = velocityDirection.Normal;
var velocityScale = (velocityMagnitude / leanMaxVelocity).Clamp( 0, 1 );
var leanDirection = worldMovement.LengthSquared == 0.0f
? -velocityScale * velocityDirection
: worldMovement;
var targetUp = (Vector3.Up + leanDirection * leanWeight * velocityScale).Normal;
var currentUp = transform.NormalToWorld( Vector3.Up );
var alignment = Math.Max( Vector3.Dot( targetUp, currentUp ), 0 );
bool hasCollision = false;
bool isGrounded = false;
if ( !hasCollision || isGrounded )
{
var hoverForce = isGrounded && currentInput.throttle <= 0 ? Vector3.Zero : -1 * transform.NormalToWorld( Vector3.Up ) * -800.0f;
var movementForce = isGrounded ? Vector3.Zero : worldMovement * movementAcceleration;
var altitudeForce = transform.NormalToWorld( Vector3.Up ) * currentInput.throttle * altitudeAcceleration;
var totalForce = hoverForce + movementForce + altitudeForce;
body.ApplyForce( (totalForce * alignment) * body.Mass );
}
if ( !hasCollision && !isGrounded )
{
var spinTorque = Transform.NormalToWorld( new Vector3( 0, 0, currentInput.yaw * yawSpeed ) );
var uprightTorque = Vector3.Cross( currentUp, targetUp ) * uprightSpeed;
var uprightAlignment = alignment < uprightDot ? 0 : alignment;
var totalTorque = spinTorque * alignment + uprightTorque * uprightAlignment;
body.ApplyTorque( (totalTorque * alignment) * body.Mass );
}
}
public override void Simulate( Client owner )
{
if ( owner == null ) return;
if ( !IsServer ) return;
using ( Prediction.Off() )
{
currentInput.Reset();
var x = (Input.Down( InputButton.Forward ) ? -1 : 0) + (Input.Down( InputButton.Back ) ? 1 : 0);
var y = (Input.Down( InputButton.Right ) ? 1 : 0) + (Input.Down( InputButton.Left ) ? -1 : 0);
currentInput.movement = new Vector3( x, y, 0 ).Normal;
currentInput.throttle = (Input.Down( InputButton.Run ) ? 1 : 0) + (Input.Down( InputButton.Duck ) ? -1 : 0);
currentInput.yaw = -Input.MouseDelta.x;
}
}
public void ResetInput()
{
currentInput.Reset();
}
private readonly Vector3[] turbinePositions = new Vector3[]
{
new Vector3( -35.37f, 35.37f, 10.0f ),
new Vector3( 35.37f, 35.37f, 10.0f ),
new Vector3( 35.37f, -35.37f, 10.0f ),
new Vector3( -35.37f, -35.37f, 10.0f )
};
public override void OnNewModel( Model model )
{
base.OnNewModel( model );
if ( IsClient )
{
}
}
private float spinAngle;
[Event.Frame]
public void OnFrame()
{
spinAngle += 10000.0f * Time.Delta;
spinAngle %= 360.0f;
for ( int i = 0; i < turbinePositions.Length; ++i )
{
var transform = Transform.ToWorld( new Transform( turbinePositions[i] * Scale, Rotation.From( new Angles( 0, spinAngle, 0 ) ) ) );
transform.Scale = Scale;
SetBoneTransform( i, transform );
}
}
}

View File

@@ -0,0 +1,33 @@
using Sandbox;
[Library( "ent_lamp", Title = "Lamp", Spawnable = true )]
public partial class LampEntity : SpotLightEntity, IUse
{
public override void Spawn()
{
base.Spawn();
SetModel( "models/torch/torch.vmdl" );
SetupPhysicsFromModel( PhysicsMotionType.Dynamic, false );
}
public bool IsUsable( Entity user )
{
return true;
}
public bool OnUse( Entity user )
{
Enabled = !Enabled;
PlaySound( Enabled ? "flashlight-on" : "flashlight-off" );
return false;
}
public void Remove()
{
PhysicsGroup?.Wake();
Delete();
}
}

View File

@@ -0,0 +1,33 @@
using Sandbox;
[Library( "ent_light", Title = "Light", Spawnable = true )]
public partial class LightEntity : PointLightEntity, IUse
{
public override void Spawn()
{
base.Spawn();
SetModel( "models/light/light_tubular.vmdl" );
SetupPhysicsFromModel( PhysicsMotionType.Dynamic, false );
}
public bool IsUsable( Entity user )
{
return true;
}
public bool OnUse( Entity user )
{
Enabled = !Enabled;
PlaySound( Enabled ? "flashlight-on" : "flashlight-off" );
return false;
}
public void Remove()
{
PhysicsGroup?.Wake();
Delete();
}
}

103
code/entities/NoiseTest.cs Normal file
View File

@@ -0,0 +1,103 @@
using Sandbox;
[Library( "noise_test", Title = "Noise Test", Spawnable = true )]
public partial class NoiseTest : Prop
{
public override void Spawn()
{
base.Spawn();
SetModel( "models/citizen_props/balloonregular01.vmdl" );
SetupPhysicsFromModel( PhysicsMotionType.Dynamic, false );
}
[Event.Frame]
public void OnFrame()
{
var pos = Position;
var right = Rotation.Right * 4;
var forward = Rotation.Forward * 4;
var up = Rotation.Up * 50;
var offset = Time.Now * 2.0f;
var offsetz = Time.Now * 0.1f;
var mode = (int)((Time.Now * 0.3f) % 5);
switch ( mode )
{
case 0:
{
DebugOverlay.Text( pos, "Perlin" );
break;
}
case 1:
{
DebugOverlay.Text( pos, "SparseConvolution" );
break;
}
case 2:
{
DebugOverlay.Text( pos, "SparseConvolutionNormalized" );
break;
}
case 3:
{
DebugOverlay.Text( pos, "Turbulence" );
break;
}
case 4:
{
DebugOverlay.Text( pos, "Fractal" );
break;
}
}
var size = 100;
pos -= right * size * 0.5f;
pos -= forward * size * 0.5f;
for ( float x = 0; x < size; x++ )
for ( float y = 0; y < size; y++ )
{
float val = 0;
switch ( mode )
{
case 0:
{
val = Noise.Perlin( x * 0.1f + offset, y * 0.1f, offsetz ) * 0.5f;
break;
}
case 1:
{
val = Noise.SparseConvolution( x * 0.1f + offset, y * 0.1f, offsetz ) * 0.5f;
break;
}
case 2:
{
val = Noise.SparseConvolutionNormalized( x * 0.1f + offset, y * 0.1f, offsetz ) * 0.5f;
break;
}
case 3:
{
val = Noise.Turbulence( 2, x * 0.1f + offset, y * 0.1f, offsetz ) * 0.5f;
break;
}
case 4:
{
val = Noise.Fractal( 2, x * 0.1f + offset, y * 0.1f, offsetz ) * 0.5f;
break;
}
}
var start = pos + x * right + y * forward;
DebugOverlay.Line( start, start + up * val, Color.Lerp( Color.Red, Color.Green, (val + 1.0f) / 2.0f ) );
}
}
}

View File

@@ -0,0 +1,46 @@
using Sandbox;
public partial class ThrusterEntity
{
private Particles effects;
[Event.Frame]
public void OnFrame()
{
UpdateEffects();
}
protected void CreateEffects()
{
if ( effects != null )
return;
effects = Particles.Create( "particles/physgun_end_nohit.vpcf" );
}
protected virtual void KillEffects()
{
if ( effects == null )
return;
effects.Destroy( false );
effects = null;
}
protected virtual void UpdateEffects()
{
if ( Enabled )
{
CreateEffects();
}
else
{
KillEffects();
}
if ( effects == null )
return;
effects.SetPosition( 0, Position + Rotation.Up * 20 );
}
}

View File

@@ -0,0 +1,50 @@
using Sandbox;
[Library( "ent_thruster" )]
public partial class ThrusterEntity : Prop, IUse
{
public float Force = 1000.0f;
public bool Massless = false;
public PhysicsBody TargetBody;
[Net]
public bool Enabled { get; set; } = true;
[Event.Physics.PostStep]
public virtual void OnPostPhysicsStep()
{
if ( IsServer && Enabled )
{
if ( TargetBody.IsValid() )
{
TargetBody.ApplyForceAt( Position, Rotation.Down * (Massless ? Force * TargetBody.Mass : Force) );
}
else if ( PhysicsBody.IsValid() )
{
PhysicsBody.ApplyForce( Rotation.Down * (Massless ? Force * PhysicsBody.Mass : Force) );
}
}
}
public bool IsUsable( Entity user )
{
return true;
}
public bool OnUse( Entity user )
{
Enabled = !Enabled;
return false;
}
protected override void OnDestroy()
{
base.OnDestroy();
if ( IsClient )
{
KillEffects();
}
}
}

View File

@@ -0,0 +1,25 @@
using Sandbox;
using Sandbox.Joints;
[Library( "ent_wheel" )]
public partial class WheelEntity : Prop
{
public RevoluteJoint Joint;
protected override void OnDestroy()
{
base.OnDestroy();
if ( Joint.IsValid )
{
Joint.Remove();
}
}
protected override void UpdatePropData( Model model )
{
base.UpdatePropData( model );
Health = -1;
}
}

View File

@@ -0,0 +1,34 @@

namespace Sandbox
{
public class CarAnimator : PawnAnimator
{
public override void Simulate()
{
ResetParams();
SetParam( "b_grounded", true );
SetParam( "b_sit", true );
var eyeAngles = (Pawn.Rotation.Inverse * Pawn.EyeRot).Angles();
eyeAngles.pitch = eyeAngles.pitch.Clamp( -25, 70 );
eyeAngles.yaw = eyeAngles.yaw.Clamp( -90, 90 );
var aimPos = Pawn.EyePos + (Pawn.Rotation * Rotation.From( eyeAngles )).Forward * 200;
SetLookAt( "aim_eyes", aimPos );
SetLookAt( "aim_head", aimPos );
SetLookAt( "aim_body", aimPos );
if ( Pawn.ActiveChild is BaseCarriable carry )
{
carry.SimulateAnimator( this );
}
else
{
SetParam( "holdtype", 0 );
SetParam( "aim_body_weight", 0.5f );
}
}
}
}

View File

@@ -0,0 +1,228 @@
using Sandbox;
using System;
public class CarCamera : Camera
{
protected virtual float MinFov => 80.0f;
protected virtual float MaxFov => 100.0f;
protected virtual float MaxFovSpeed => 1000.0f;
protected virtual float FovSmoothingSpeed => 4.0f;
protected virtual float OrbitCooldown => 0.6f;
protected virtual float OrbitSmoothingSpeed => 25.0f;
protected virtual float OrbitReturnSmoothingSpeed => 4.0f;
protected virtual float MinOrbitPitch => -25.0f;
protected virtual float MaxOrbitPitch => 70.0f;
protected virtual float FixedOrbitPitch => 10.0f;
protected virtual float OrbitHeight => 35.0f;
protected virtual float OrbitDistance => 150.0f;
protected virtual float MaxOrbitReturnSpeed => 100.0f;
protected virtual float MinCarPitch => -60.0f;
protected virtual float MaxCarPitch => 60.0f;
protected virtual float FirstPersonPitch => 10.0f;
protected virtual float CarPitchSmoothingSpeed => 1.0f;
protected virtual float CollisionRadius => 8.0f;
protected virtual float ShakeSpeed => 10.0f;
protected virtual float ShakeSpeedThreshold => 1500.0f;
protected virtual float ShakeMaxSpeed => 2500.0f;
protected virtual float ShakeMaxLength => 1.0f;
private bool orbitEnabled;
private TimeSince timeSinceOrbit;
private Angles orbitAngles;
private Rotation orbitYawRot;
private Rotation orbitPitchRot;
private float currentFov;
private float carPitch;
private bool firstPerson;
public override void Activated()
{
var pawn = Local.Pawn;
if ( pawn == null ) return;
orbitEnabled = false;
timeSinceOrbit = 0.0f;
orbitAngles = Angles.Zero;
orbitYawRot = Rotation.Identity;
orbitPitchRot = Rotation.Identity;
currentFov = MinFov;
carPitch = 0;
firstPerson = false;
var car = (pawn as SandboxPlayer)?.Vehicle as CarEntity;
if ( !car.IsValid() ) return;
orbitYawRot = firstPerson ? Rotation.Identity : Rotation.FromYaw( car.Rotation.Yaw() );
orbitPitchRot = firstPerson ? Rotation.FromPitch( FirstPersonPitch ) : Rotation.Identity;
orbitAngles = (orbitYawRot * orbitPitchRot).Angles();
}
public override void Update()
{
var pawn = Local.Pawn;
if ( pawn == null ) return;
var car = (pawn as SandboxPlayer)?.Vehicle as CarEntity;
if ( !car.IsValid() ) return;
var body = car.PhysicsBody;
if ( !body.IsValid() )
return;
var speed = car.MovementSpeed;
var speedAbs = Math.Abs( speed );
if ( orbitEnabled && timeSinceOrbit > OrbitCooldown )
orbitEnabled = false;
var carRot = car.Rotation;
carPitch = carPitch.LerpTo( car.Grounded ? carRot.Pitch().Clamp( MinCarPitch, MaxCarPitch ) * (speed < 0.0f ? -1.0f : 1.0f) : 0.0f, Time.Delta * CarPitchSmoothingSpeed );
if ( orbitEnabled )
{
var slerpAmount = Time.Delta * OrbitSmoothingSpeed;
orbitYawRot = Rotation.Slerp( orbitYawRot, Rotation.From( 0.0f, orbitAngles.yaw, 0.0f ), slerpAmount );
orbitPitchRot = Rotation.Slerp( orbitPitchRot, Rotation.From( orbitAngles.pitch + carPitch, 0.0f, 0.0f ), slerpAmount );
}
else
{
if ( firstPerson )
{
var targetYaw = 0;
var targetPitch = FirstPersonPitch;
var slerpAmount = Time.Delta * OrbitReturnSmoothingSpeed;
orbitYawRot = Rotation.Slerp( orbitYawRot, Rotation.FromYaw( targetYaw ), slerpAmount );
orbitPitchRot = Rotation.Slerp( orbitPitchRot, Rotation.FromPitch( targetPitch ), slerpAmount );
}
else
{
var targetPitch = FixedOrbitPitch.Clamp( MinOrbitPitch, MaxOrbitPitch );
var targetYaw = !firstPerson && speed < 0.0f ? carRot.Yaw() + 180.0f : carRot.Yaw();
var slerpAmount = MaxOrbitReturnSpeed > 0.0f ? Time.Delta * (speedAbs / MaxOrbitReturnSpeed).Clamp( 0.0f, OrbitReturnSmoothingSpeed ) : 1.0f;
orbitYawRot = Rotation.Slerp( orbitYawRot, Rotation.FromYaw( targetYaw ), slerpAmount );
orbitPitchRot = Rotation.Slerp( orbitPitchRot, Rotation.FromPitch( targetPitch + carPitch ), slerpAmount );
}
orbitAngles.pitch = orbitPitchRot.Pitch();
orbitAngles.yaw = orbitYawRot.Yaw();
orbitAngles = orbitAngles.Normal;
}
if ( firstPerson )
{
DoFirstPerson();
}
else
{
DoThirdPerson( car, body );
}
currentFov = MaxFovSpeed > 0.0f ? currentFov.LerpTo( MinFov.LerpTo( MaxFov, speedAbs / MaxFovSpeed ), Time.Delta * FovSmoothingSpeed ) : MaxFov;
FieldOfView = currentFov;
ApplyShake( speedAbs );
}
private void DoFirstPerson()
{
var pawn = Local.Pawn;
if ( pawn == null ) return;
Pos = pawn.EyePos;
Rot = pawn.Rotation * (orbitYawRot * orbitPitchRot);
Viewer = pawn;
}
private void DoThirdPerson( CarEntity car, PhysicsBody body )
{
Rot = orbitYawRot * orbitPitchRot;
var carPos = car.Position + car.Rotation * (body.LocalMassCenter * car.Scale);
var startPos = carPos;
var targetPos = startPos + Rot.Backward * (OrbitDistance * car.Scale) + (Vector3.Up * (OrbitHeight * car.Scale));
var tr = Trace.Ray( startPos, targetPos )
.Ignore( car )
.Radius( Math.Clamp( CollisionRadius * car.Scale, 2.0f, 10.0f ) )
.WorldOnly()
.Run();
Pos = tr.EndPos;
Viewer = null;
}
public override void BuildInput( InputBuilder input )
{
base.BuildInput( input );
var pawn = Local.Pawn;
if ( pawn == null ) return;
var car = (pawn as SandboxPlayer)?.Vehicle as CarEntity;
if ( !car.IsValid() ) return;
if ( input.Pressed( InputButton.View ) )
{
firstPerson = !firstPerson;
orbitYawRot = firstPerson ? Rotation.Identity : Rotation.FromYaw( car.Rotation.Yaw() );
orbitPitchRot = firstPerson ? Rotation.FromPitch( FirstPersonPitch ) : Rotation.Identity;
orbitAngles = (orbitYawRot * orbitPitchRot).Angles();
orbitEnabled = false;
timeSinceOrbit = 0.0f;
}
if ( (Math.Abs( input.AnalogLook.pitch ) + Math.Abs( input.AnalogLook.yaw )) > 0.0f )
{
if ( !orbitEnabled )
{
orbitAngles = (orbitYawRot * orbitPitchRot).Angles();
orbitAngles = orbitAngles.Normal;
orbitYawRot = Rotation.From( 0.0f, orbitAngles.yaw, 0.0f );
orbitPitchRot = Rotation.From( orbitAngles.pitch, 0.0f, 0.0f );
}
orbitEnabled = true;
timeSinceOrbit = 0.0f;
orbitAngles.yaw += input.AnalogLook.yaw;
orbitAngles.pitch += input.AnalogLook.pitch;
orbitAngles = orbitAngles.Normal;
orbitAngles.pitch = orbitAngles.pitch.Clamp( MinOrbitPitch, MaxOrbitPitch );
}
if ( firstPerson )
{
input.ViewAngles = (car.Rotation * Rotation.From( orbitAngles )).Angles();
}
else
{
input.ViewAngles = orbitEnabled ? orbitAngles : car.Rotation.Angles();
}
input.ViewAngles = input.ViewAngles.Normal;
}
private void ApplyShake( float speed )
{
if ( speed < ShakeSpeedThreshold )
return;
var pos = (Time.Now % MathF.PI) * ShakeSpeed;
var length = (speed - ShakeSpeedThreshold) / (ShakeMaxSpeed - ShakeSpeedThreshold);
length = length.Clamp( 0, ShakeMaxLength );
float x = Noise.Perlin( pos, 0, 0 ) * length;
float y = Noise.Perlin( pos, 5.0f, 0 ) * length;
Pos += Rot.Right * x + Rot.Up * y;
Rot *= Rotation.FromAxis( Vector3.Up, x );
Rot *= Rotation.FromAxis( Vector3.Right, y );
}
}

View File

@@ -0,0 +1,37 @@
using Sandbox;
[Library]
public class CarController : PawnController
{
public override void FrameSimulate()
{
base.FrameSimulate();
Simulate();
}
public override void Simulate()
{
var player = Pawn as SandboxPlayer;
if ( !player.IsValid() ) return;
var car = player.Vehicle as CarEntity;
if ( !car.IsValid() ) return;
car.Simulate( Client );
if ( player.Vehicle == null )
{
Position = car.Position + car.Rotation.Up * (100 * car.Scale);
Velocity += car.Rotation.Right * (200 * car.Scale);
return;
}
EyeRot = Input.Rotation;
EyePosLocal = Vector3.Up * (64 - 10) * car.Scale;
Velocity = car.Velocity;
SetTag( "noclip" );
SetTag( "sitting" );
}
}

View File

@@ -0,0 +1,561 @@
using Sandbox;
using System;
using System.Collections.Generic;
[Library( "ent_car", Title = "Car", Spawnable = true )]
public partial class CarEntity : Prop, IUse
{
[ConVar.Replicated( "debug_car" )]
public static bool debug_car { get; set; } = false;
[ConVar.Replicated( "car_accelspeed" )]
public static float car_accelspeed { get; set; } = 500.0f;
private CarWheel frontLeft;
private CarWheel frontRight;
private CarWheel backLeft;
private CarWheel backRight;
private float frontLeftDistance;
private float frontRightDistance;
private float backLeftDistance;
private float backRightDistance;
private bool frontWheelsOnGround;
private bool backWheelsOnGround;
private float accelerateDirection;
private float airRoll;
private float airTilt;
private float grip;
private TimeSince timeSinceDriverLeft;
[Net] private float WheelSpeed { get; set; }
[Net] private float TurnDirection { get; set; }
[Net] private float AccelerationTilt { get; set; }
[Net] private float TurnLean { get; set; }
[Net] public float MovementSpeed { get; private set; }
[Net] public bool Grounded { get; private set; }
private struct InputState
{
public float throttle;
public float turning;
public float breaking;
public float tilt;
public float roll;
public void Reset()
{
throttle = 0;
turning = 0;
breaking = 0;
tilt = 0;
roll = 0;
}
}
private InputState currentInput;
public CarEntity()
{
frontLeft = new CarWheel( this );
frontRight = new CarWheel( this );
backLeft = new CarWheel( this );
backRight = new CarWheel( this );
}
[Net] public Player driver { get; private set; }
private ModelEntity chassis_axle_rear;
private ModelEntity chassis_axle_front;
private ModelEntity wheel0;
private ModelEntity wheel1;
private ModelEntity wheel2;
private ModelEntity wheel3;
public override void Spawn()
{
base.Spawn();
var modelName = "models/car/car.vmdl";
SetModel( modelName );
SetupPhysicsFromModel( PhysicsMotionType.Dynamic, false );
SetInteractsExclude( CollisionLayer.Player );
EnableSelfCollisions = false;
var trigger = new ModelEntity
{
Parent = this,
Position = Position,
Rotation = Rotation,
EnableTouch = true,
CollisionGroup = CollisionGroup.Trigger,
Transmit = TransmitType.Never,
EnableSelfCollisions = false,
};
trigger.SetModel( modelName );
trigger.SetupPhysicsFromModel( PhysicsMotionType.Keyframed, false );
}
public override void ClientSpawn()
{
base.ClientSpawn();
{
var vehicle_fuel_tank = new ModelEntity();
vehicle_fuel_tank.SetModel( "entities/modular_vehicle/vehicle_fuel_tank.vmdl" );
vehicle_fuel_tank.Transform = Transform;
vehicle_fuel_tank.Parent = this;
vehicle_fuel_tank.LocalPosition = new Vector3( 0.75f, 0, 0 ) * 40.0f;
}
{
chassis_axle_front = new ModelEntity();
chassis_axle_front.SetModel( "entities/modular_vehicle/chassis_axle_front.vmdl" );
chassis_axle_front.Transform = Transform;
chassis_axle_front.Parent = this;
chassis_axle_front.LocalPosition = new Vector3( 1.05f, 0, 0.35f ) * 40.0f;
{
wheel0 = new ModelEntity();
wheel0.SetModel( "entities/modular_vehicle/wheel_a.vmdl" );
wheel0.SetParent( chassis_axle_front, "Wheel_Steer_R", new Transform( Vector3.Zero, Rotation.From( 0, 180, 0 ) ) );
}
{
wheel1 = new ModelEntity();
wheel1.SetModel( "entities/modular_vehicle/wheel_a.vmdl" );
wheel1.SetParent( chassis_axle_front, "Wheel_Steer_L", new Transform( Vector3.Zero, Rotation.From( 0, 0, 0 ) ) );
}
{
var chassis_steering = new ModelEntity();
chassis_steering.SetModel( "entities/modular_vehicle/chassis_steering.vmdl" );
chassis_steering.SetParent( chassis_axle_front, "Axle_front_Center", new Transform( Vector3.Zero, Rotation.From( -90, 180, 0 ) ) );
}
}
{
chassis_axle_rear = new ModelEntity();
chassis_axle_rear.SetModel( "entities/modular_vehicle/chassis_axle_rear.vmdl" );
chassis_axle_rear.Transform = Transform;
chassis_axle_rear.Parent = this;
chassis_axle_rear.LocalPosition = new Vector3( -1.05f, 0, 0.35f ) * 40.0f;
{
var chassis_transmission = new ModelEntity();
chassis_transmission.SetModel( "entities/modular_vehicle/chassis_transmission.vmdl" );
chassis_transmission.SetParent( chassis_axle_rear, "Axle_Rear_Center", new Transform( Vector3.Zero, Rotation.From( -90, 180, 0 ) ) );
}
{
wheel2 = new ModelEntity();
wheel2.SetModel( "entities/modular_vehicle/wheel_a.vmdl" );
wheel2.SetParent( chassis_axle_rear, "Axle_Rear_Center", new Transform( Vector3.Left * (0.7f * 40), Rotation.From( 0, 90, 0 ) ) );
}
{
wheel3 = new ModelEntity();
wheel3.SetModel( "entities/modular_vehicle/wheel_a.vmdl" );
wheel3.SetParent( chassis_axle_rear, "Axle_Rear_Center", new Transform( Vector3.Right * (0.7f * 40), Rotation.From( 0, -90, 0 ) ) );
}
}
}
protected override void OnDestroy()
{
base.OnDestroy();
if ( driver is SandboxPlayer player )
{
RemoveDriver( player );
}
}
public void ResetInput()
{
currentInput.Reset();
}
[Event.Tick.Server]
protected void Tick()
{
if ( driver is SandboxPlayer player )
{
if ( player.LifeState != LifeState.Alive || player.Vehicle != this )
{
RemoveDriver( player );
}
}
}
public override void Simulate( Client owner )
{
if ( owner == null ) return;
if ( !IsServer ) return;
using ( Prediction.Off() )
{
currentInput.Reset();
if ( Input.Pressed( InputButton.Use ) )
{
if ( owner.Pawn is SandboxPlayer player && !player.IsUseDisabled() )
{
RemoveDriver( player );
return;
}
}
currentInput.throttle = (Input.Down( InputButton.Forward ) ? 1 : 0) + (Input.Down( InputButton.Back ) ? -1 : 0);
currentInput.turning = (Input.Down( InputButton.Left ) ? 1 : 0) + (Input.Down( InputButton.Right ) ? -1 : 0);
currentInput.breaking = (Input.Down( InputButton.Jump ) ? 1 : 0);
currentInput.tilt = (Input.Down( InputButton.Run ) ? 1 : 0) + (Input.Down( InputButton.Duck ) ? -1 : 0);
currentInput.roll = (Input.Down( InputButton.Left ) ? 1 : 0) + (Input.Down( InputButton.Right ) ? -1 : 0);
}
}
[Event.Physics.PreStep]
public void OnPrePhysicsStep()
{
if ( !IsServer )
return;
var selfBody = PhysicsBody;
if ( !selfBody.IsValid() )
return;
var body = selfBody.SelfOrParent;
if ( !body.IsValid() )
return;
var dt = Time.Delta;
body.DragEnabled = false;
var rotation = selfBody.Rotation;
accelerateDirection = currentInput.throttle.Clamp( -1, 1 ) * (1.0f - currentInput.breaking);
TurnDirection = TurnDirection.LerpTo( currentInput.turning.Clamp( -1, 1 ), 1.0f - MathF.Pow( 0.001f, dt ) );
airRoll = airRoll.LerpTo( currentInput.roll.Clamp( -1, 1 ), 1.0f - MathF.Pow( 0.0001f, dt ) );
airTilt = airTilt.LerpTo( currentInput.tilt.Clamp( -1, 1 ), 1.0f - MathF.Pow( 0.0001f, dt ) );
float targetTilt = 0;
float targetLean = 0;
var localVelocity = rotation.Inverse * body.Velocity;
if ( backWheelsOnGround || frontWheelsOnGround )
{
var forwardSpeed = MathF.Abs( localVelocity.x );
var speedFraction = MathF.Min( forwardSpeed / 500.0f, 1 );
targetTilt = accelerateDirection.Clamp( -1.0f, 1.0f );
targetLean = speedFraction * TurnDirection;
}
AccelerationTilt = AccelerationTilt.LerpTo( targetTilt, 1.0f - MathF.Pow( 0.01f, dt ) );
TurnLean = TurnLean.LerpTo( targetLean, 1.0f - MathF.Pow( 0.01f, dt ) );
if ( backWheelsOnGround )
{
var forwardSpeed = MathF.Abs( localVelocity.x );
var speedFactor = 1.0f - (forwardSpeed / 5000.0f).Clamp( 0.0f, 1.0f );
var acceleration = speedFactor * (accelerateDirection < 0.0f ? car_accelspeed * 0.5f : car_accelspeed) * accelerateDirection * dt;
var impulse = rotation * new Vector3( acceleration, 0, 0 );
body.Velocity += impulse;
}
RaycastWheels( rotation, true, out frontWheelsOnGround, out backWheelsOnGround, dt );
var onGround = frontWheelsOnGround || backWheelsOnGround;
var fullyGrounded = (frontWheelsOnGround && backWheelsOnGround);
Grounded = onGround;
if ( fullyGrounded )
{
body.Velocity += PhysicsWorld.Gravity * dt;
}
body.GravityScale = fullyGrounded ? 0 : 1;
bool canAirControl = false;
var v = rotation * localVelocity.WithZ( 0 );
var vDelta = MathF.Pow((v.Length / 1000.0f).Clamp( 0, 1 ), 5.0f).Clamp(0, 1);
if ( vDelta < 0.01f ) vDelta = 0;
if ( debug_car )
{
DebugOverlay.Line( body.MassCenter, body.MassCenter + rotation.Forward.Normal * 100, Color.White, 0, false );
DebugOverlay.Line( body.MassCenter, body.MassCenter + v.Normal * 100, Color.Green, 0, false );
}
var angle = ( rotation.Forward.Normal * MathF.Sign( localVelocity.x )).Normal.Dot( v.Normal ).Clamp( 0.0f, 1.0f );
angle = angle.LerpTo( 1.0f, 1.0f - vDelta );
grip = grip.LerpTo( angle, 1.0f - MathF.Pow( 0.001f, dt ) );
if ( debug_car )
{
DebugOverlay.ScreenText( new Vector2( 200, 200 ), $"{grip}" );
}
var angularDamping = 0.0f;
angularDamping = angularDamping.LerpTo( 5.0f, grip );
body.LinearDamping = 0.0f;
body.AngularDamping = fullyGrounded ? angularDamping : 0.5f;
if ( onGround )
{
localVelocity = rotation.Inverse * body.Velocity;
WheelSpeed = localVelocity.x;
var turnAmount = frontWheelsOnGround ? (MathF.Sign( localVelocity.x ) * 25.0f * CalculateTurnFactor( TurnDirection, MathF.Abs( localVelocity.x ) ) * dt) : 0.0f;
body.AngularVelocity += rotation * new Vector3( 0, 0, turnAmount );
airRoll = 0;
airTilt = 0;
var forwardGrip = 0.1f;
forwardGrip = forwardGrip.LerpTo( 0.9f, currentInput.breaking );
body.Velocity = VelocityDamping( Velocity, rotation, new Vector3( forwardGrip, grip, 0 ), dt );
}
else
{
var s = selfBody.Position + (rotation * selfBody.LocalMassCenter);
var tr = Trace.Ray( s, s + rotation.Down * 50 )
.Ignore( this )
.Run();
if ( debug_car )
DebugOverlay.Line( tr.StartPos, tr.EndPos, tr.Hit ? Color.Red : Color.Green );
canAirControl = !tr.Hit;
}
if ( canAirControl && (airRoll != 0 || airTilt != 0) )
{
var offset = 50 * Scale;
var s = selfBody.Position + (rotation * selfBody.LocalMassCenter) + (rotation.Right * airRoll * offset) + (rotation.Down * (10 * Scale));
var tr = Trace.Ray( s, s + rotation.Up * (25 * Scale) )
.Ignore( this )
.Run();
if ( debug_car )
DebugOverlay.Line( tr.StartPos, tr.EndPos );
bool dampen = false;
if ( currentInput.roll.Clamp( -1, 1 ) != 0 )
{
var force = tr.Hit ? 400.0f : 100.0f;
var roll = tr.Hit ? currentInput.roll.Clamp( -1, 1 ) : airRoll;
body.ApplyForceAt( selfBody.MassCenter + rotation.Left * (offset * roll), (rotation.Down * roll) * (roll * (body.Mass * force)) );
if ( debug_car )
DebugOverlay.Sphere( selfBody.MassCenter + rotation.Left * (offset * roll), 8, Color.Red );
dampen = true;
}
if ( !tr.Hit && currentInput.tilt.Clamp( -1, 1 ) != 0 )
{
var force = 200.0f;
body.ApplyForceAt( selfBody.MassCenter + rotation.Forward * (offset * airTilt), (rotation.Down * airTilt) * (airTilt * (body.Mass * force)) );
if ( debug_car )
DebugOverlay.Sphere( selfBody.MassCenter + rotation.Forward * (offset * airTilt), 8, Color.Green );
dampen = true;
}
if ( dampen )
body.AngularVelocity = VelocityDamping( body.AngularVelocity, rotation, 0.95f, dt );
}
localVelocity = rotation.Inverse * body.Velocity;
MovementSpeed = localVelocity.x;
}
private static float CalculateTurnFactor( float direction, float speed )
{
var turnFactor = MathF.Min( speed / 500.0f, 1 );
var yawSpeedFactor = 1.0f - (speed / 1000.0f).Clamp( 0, 0.6f );
return direction * turnFactor * yawSpeedFactor;
}
private static Vector3 VelocityDamping( Vector3 velocity, Rotation rotation, Vector3 damping, float dt )
{
var localVelocity = rotation.Inverse * velocity;
var dampingPow = new Vector3( MathF.Pow( 1.0f - damping.x, dt ), MathF.Pow( 1.0f - damping.y, dt ), MathF.Pow( 1.0f - damping.z, dt ) );
return rotation * (localVelocity * dampingPow);
}
private void RaycastWheels( Rotation rotation, bool doPhysics, out bool frontWheels, out bool backWheels, float dt )
{
float forward = 42;
float right = 32;
var frontLeftPos = rotation.Forward * forward + rotation.Right * right + rotation.Up * 20;
var frontRightPos = rotation.Forward * forward - rotation.Right * right + rotation.Up * 20;
var backLeftPos = -rotation.Forward * forward + rotation.Right * right + rotation.Up * 20;
var backRightPos = -rotation.Forward * forward - rotation.Right * right + rotation.Up * 20;
var tiltAmount = AccelerationTilt * 2.5f;
var leanAmount = TurnLean * 2.5f;
float length = 20.0f;
frontWheels =
frontLeft.Raycast( length + tiltAmount - leanAmount, doPhysics, frontLeftPos * Scale, ref frontLeftDistance, dt ) |
frontRight.Raycast( length + tiltAmount + leanAmount, doPhysics, frontRightPos * Scale, ref frontRightDistance, dt );
backWheels =
backLeft.Raycast( length - tiltAmount - leanAmount, doPhysics, backLeftPos * Scale, ref backLeftDistance, dt ) |
backRight.Raycast( length - tiltAmount + leanAmount, doPhysics, backRightPos * Scale, ref backRightDistance, dt );
}
float wheelAngle = 0.0f;
float wheelRevolute = 0.0f;
[Event.Frame]
public void OnFrame()
{
wheelAngle = wheelAngle.LerpTo( TurnDirection * 25, 1.0f - MathF.Pow( 0.001f, Time.Delta ) );
wheelRevolute += (WheelSpeed / (14.0f * Scale)).RadianToDegree() * Time.Delta;
var wheelRotRight = Rotation.From( -wheelAngle, 180, -wheelRevolute );
var wheelRotLeft = Rotation.From( wheelAngle, 0, wheelRevolute );
var wheelRotBackRight = Rotation.From( 0, 90, -wheelRevolute );
var wheelRotBackLeft = Rotation.From( 0, -90, wheelRevolute );
RaycastWheels( Rotation, false, out _, out _, Time.Delta );
float frontOffset = 20.0f - Math.Min( frontLeftDistance, frontRightDistance );
float backOffset = 20.0f - Math.Min( backLeftDistance, backRightDistance );
chassis_axle_front.SetBoneTransform( "Axle_front_Center", new Transform( Vector3.Up * frontOffset ), false );
chassis_axle_rear.SetBoneTransform( "Axle_Rear_Center", new Transform( Vector3.Up * backOffset ), false );
wheel0.LocalRotation = wheelRotRight;
wheel1.LocalRotation = wheelRotLeft;
wheel2.LocalRotation = wheelRotBackRight;
wheel3.LocalRotation = wheelRotBackLeft;
}
private void RemoveDriver( SandboxPlayer player )
{
driver = null;
player.Vehicle = null;
player.VehicleController = null;
player.VehicleAnimator = null;
player.VehicleCamera = null;
player.Parent = null;
player.PhysicsBody.Enabled = true;
player.PhysicsBody.Position = player.Position;
timeSinceDriverLeft = 0;
ResetInput();
}
public bool OnUse( Entity user )
{
if ( user is SandboxPlayer player && player.Vehicle == null && timeSinceDriverLeft > 1.0f )
{
player.Vehicle = this;
player.VehicleController = new CarController();
player.VehicleAnimator = new CarAnimator();
player.VehicleCamera = new CarCamera();
player.Parent = this;
player.LocalPosition = Vector3.Up * 10;
player.LocalRotation = Rotation.Identity;
player.LocalScale = 1;
player.PhysicsBody.Enabled = false;
driver = player;
}
return true;
}
public bool IsUsable( Entity user )
{
return driver == null;
}
public override void StartTouch( Entity other )
{
base.StartTouch( other );
if ( !IsServer )
return;
var body = PhysicsBody;
if ( !body.IsValid() )
return;
body = body.SelfOrParent;
if ( !body.IsValid() )
return;
if ( other is SandboxPlayer player && player.Vehicle == null )
{
var speed = body.Velocity.Length;
var forceOrigin = Position + Rotation.Down * Rand.Float( 20, 30 );
var velocity = (player.Position - forceOrigin).Normal * speed;
var angularVelocity = body.AngularVelocity;
OnPhysicsCollision( new CollisionEventData
{
Entity = player,
Pos = player.Position + Vector3.Up * 50,
Velocity = velocity,
PreVelocity = velocity,
PostVelocity = velocity,
PreAngularVelocity = angularVelocity,
Speed = speed,
} );
}
}
protected override void OnPhysicsCollision( CollisionEventData eventData )
{
if ( !IsServer )
return;
if ( eventData.Entity is SandboxPlayer player && player.Vehicle != null )
{
return;
}
var propData = GetModelPropData();
var minImpactSpeed = propData.MinImpactDamageSpeed;
if ( minImpactSpeed <= 0.0f ) minImpactSpeed = 500;
var impactDmg = propData.ImpactDamage;
if ( impactDmg <= 0.0f ) impactDmg = 10;
var speed = eventData.Speed;
if ( speed > minImpactSpeed )
{
if ( eventData.Entity.IsValid() && eventData.Entity != this )
{
var damage = speed / minImpactSpeed * impactDmg * 1.2f;
eventData.Entity.TakeDamage( DamageInfo.Generic( damage )
.WithFlag( DamageFlags.PhysicsImpact )
.WithFlag( DamageFlags.Vehicle )
.WithAttacker( driver != null ? driver : this, driver != null ? this : null )
.WithPosition( eventData.Pos )
.WithForce( eventData.PreVelocity ) );
}
}
}
}

View File

@@ -0,0 +1,75 @@
using Sandbox;
using System;
struct CarWheel
{
private readonly CarEntity parent;
private float _previousLength;
private float _currentLength;
public CarWheel( CarEntity parent )
{
this.parent = parent;
_previousLength = 0;
_currentLength = 0;
}
public bool Raycast( float length, bool doPhysics, Vector3 offset, ref float wheel, float dt )
{
var position = parent.Position;
var rotation = parent.Rotation;
var wheelAttachPos = position + offset;
var wheelExtend = wheelAttachPos - rotation.Up * (length * parent.Scale);
var tr = Trace.Ray( wheelAttachPos, wheelExtend )
.Ignore( parent )
.Ignore( parent.driver )
.Run();
wheel = length * tr.Fraction;
var wheelRadius = (14 * parent.Scale);
if ( !doPhysics && CarEntity.debug_car )
{
var wheelPosition = tr.Hit ? tr.EndPos : wheelExtend;
wheelPosition += rotation.Up * wheelRadius;
if ( tr.Hit )
{
DebugOverlay.Circle( wheelPosition, rotation * Rotation.FromYaw( 90 ), wheelRadius, Color.Red.WithAlpha( 0.5f ), false );
DebugOverlay.Line( tr.StartPos, tr.EndPos, Color.Red, 0, false );
}
else
{
DebugOverlay.Circle( wheelPosition, rotation * Rotation.FromYaw( 90 ), wheelRadius, Color.Green.WithAlpha( 0.5f ), false );
DebugOverlay.Line( wheelAttachPos, wheelExtend, Color.Green, 0, false );
}
}
if ( !tr.Hit || !doPhysics )
{
return tr.Hit;
}
var body = parent.PhysicsBody.SelfOrParent;
_previousLength = _currentLength;
_currentLength = (length * parent.Scale) - tr.Distance;
var springVelocity = (_currentLength - _previousLength) / dt;
var springForce = body.Mass * 50.0f * _currentLength;
var damperForce = body.Mass * (1.5f + (1.0f - tr.Fraction) * 3.0f) * springVelocity;
var velocity = body.GetVelocityAtPoint( wheelAttachPos );
var speed = velocity.Length;
var speedDot = MathF.Abs( speed ) > 0.0f ? MathF.Abs( MathF.Min( Vector3.Dot( velocity, rotation.Up.Normal ) / speed, 0.0f ) ) : 0.0f;
var speedAlongNormal = speedDot * speed;
var correctionMultiplier = (1.0f - tr.Fraction) * (speedAlongNormal / 1000.0f);
var correctionForce = correctionMultiplier * 50.0f * speedAlongNormal / dt;
body.ApplyImpulseAt( wheelAttachPos, tr.Normal * (springForce + damperForce + correctionForce) * dt );
return true;
}
}

123
code/tools/Balloon.cs Normal file
View File

@@ -0,0 +1,123 @@
namespace Sandbox.Tools
{
[Library( "tool_balloon", Title = "Balloons", Description = "Create Balloons!", Group = "construction" )]
public partial class BalloonTool : BaseTool
{
[Net]
public Color32 Tint { get; set; }
PreviewEntity previewModel;
public override void Activate()
{
base.Activate();
if ( Host.IsServer )
{
Tint = Color.Random.ToColor32();
}
}
protected override bool IsPreviewTraceValid( TraceResult tr )
{
if ( !base.IsPreviewTraceValid( tr ) )
return false;
if ( tr.Entity is BalloonEntity )
return false;
return true;
}
public override void CreatePreviews()
{
if ( TryCreatePreview( ref previewModel, "models/citizen_props/balloonregular01.vmdl" ) )
{
previewModel.RelativeToNormal = false;
}
}
public override void Simulate()
{
if ( previewModel.IsValid() )
{
previewModel.RenderColor = Tint;
}
if ( !Host.IsServer )
return;
using ( Prediction.Off() )
{
bool useRope = Input.Pressed( InputButton.Attack1 );
if ( !useRope && !Input.Pressed( InputButton.Attack2 ) )
return;
var startPos = Owner.EyePos;
var dir = Owner.EyeRot.Forward;
var tr = Trace.Ray( startPos, startPos + dir * MaxTraceDistance )
.Ignore( Owner )
.Run();
if ( !tr.Hit )
return;
if ( !tr.Entity.IsValid() )
return;
CreateHitEffects( tr.EndPos );
if ( tr.Entity is BalloonEntity )
return;
var ent = new BalloonEntity
{
Position = tr.EndPos,
};
ent.SetModel( "models/citizen_props/balloonregular01.vmdl" );
ent.PhysicsBody.GravityScale = -0.2f;
ent.RenderColor = Tint;
Tint = Color.Random.ToColor32();
if ( !useRope )
return;
var rope = Particles.Create( "particles/rope.vpcf" );
rope.SetEntity( 0, ent );
var attachEnt = tr.Body.IsValid() ? tr.Body.Entity : tr.Entity;
var attachLocalPos = tr.Body.Transform.PointToLocal( tr.EndPos ) * (1.0f / tr.Entity.Scale);
if ( attachEnt.IsWorld )
{
rope.SetPosition( 1, attachLocalPos );
}
else
{
rope.SetEntityBone( 1, attachEnt, tr.Bone, new Transform( attachLocalPos ) );
}
var spring = PhysicsJoint.Spring
.From( ent.PhysicsBody )
.To( tr.Body, tr.Body.Transform.PointToLocal( tr.EndPos ) )
.WithFrequency( 5.0f )
.WithDampingRatio( 0.7f )
.WithReferenceMass( ent.PhysicsBody.Mass )
.WithMinRestLength( 0 )
.WithMaxRestLength( 100 )
.WithCollisionsEnabled()
.Create();
spring.EnableAngularConstraint = false;
spring.OnBreak( () =>
{
rope?.Destroy( true );
spring.Remove();
} );
}
}
}
}

38
code/tools/BoxShooter.cs Normal file
View File

@@ -0,0 +1,38 @@
namespace Sandbox.Tools
{
[Library( "tool_boxgun", Title = "Box Shooter", Description = "Shoot boxes", Group = "fun" )]
public class BoxShooter : BaseTool
{
TimeSince timeSinceShoot;
public override void Simulate()
{
if ( Host.IsServer )
{
if ( Input.Pressed( InputButton.Attack1 ) )
{
ShootBox();
}
if ( Input.Down( InputButton.Attack2 ) && timeSinceShoot > 0.05f )
{
timeSinceShoot = 0;
ShootBox();
}
}
}
void ShootBox()
{
var ent = new Prop
{
Position = Owner.EyePos + Owner.EyeRot.Forward * 50,
Rotation = Owner.EyeRot
};
ent.SetModel( "models/citizen_props/crate01.vmdl" );
ent.Velocity = Owner.EyeRot.Forward * 1000;
}
}
}

38
code/tools/Color.cs Normal file
View File

@@ -0,0 +1,38 @@
using System;
namespace Sandbox.Tools
{
[Library( "tool_color", Title = "Color", Description = "Change render color and alpha of entities", Group = "construction" )]
public partial class ColorTool : BaseTool
{
public override void Simulate()
{
if ( !Host.IsServer )
return;
using ( Prediction.Off() )
{
var startPos = Owner.EyePos;
var dir = Owner.EyeRot.Forward;
if ( !Input.Pressed( InputButton.Attack1 ) ) return;
var tr = Trace.Ray( startPos, startPos + dir * MaxTraceDistance )
.Ignore( Owner )
.UseHitboxes()
.HitLayer( CollisionLayer.Debris )
.Run();
if ( !tr.Hit || !tr.Entity.IsValid() )
return;
if ( tr.Entity is not ModelEntity modelEnt )
return;
modelEnt.RenderColor = Color.Random.ToColor32();
CreateHitEffects( tr.EndPos );
}
}
}
}

280
code/tools/GravGun.cs Normal file
View File

@@ -0,0 +1,280 @@
using Sandbox;
using Sandbox.Joints;
using System;
using System.Linq;
[Library( "gravgun" )]
public partial class GravGun : Carriable
{
public override string ViewModelPath => "weapons/rust_pistol/v_rust_pistol.vmdl";
private PhysicsBody holdBody;
private WeldJoint holdJoint;
public PhysicsBody HeldBody { get; private set; }
public Rotation HeldRot { get; private set; }
public ModelEntity HeldEntity { get; private set; }
protected virtual float MaxPullDistance => 2000.0f;
protected virtual float MaxPushDistance => 500.0f;
protected virtual float LinearFrequency => 10.0f;
protected virtual float LinearDampingRatio => 1.0f;
protected virtual float AngularFrequency => 10.0f;
protected virtual float AngularDampingRatio => 1.0f;
protected virtual float PullForce => 20.0f;
protected virtual float PushForce => 1000.0f;
protected virtual float ThrowForce => 2000.0f;
protected virtual float HoldDistance => 100.0f;
protected virtual float AttachDistance => 150.0f;
protected virtual float DropCooldown => 0.5f;
protected virtual float BreakLinearForce => 2000.0f;
private TimeSince timeSinceDrop;
public override void Spawn()
{
base.Spawn();
SetModel( "weapons/rust_pistol/rust_pistol.vmdl" );
CollisionGroup = CollisionGroup.Weapon;
SetInteractsAs( CollisionLayer.Debris );
}
public override void Simulate( Client client )
{
if ( Owner is not Player owner ) return;
if ( !IsServer )
return;
using ( Prediction.Off() )
{
var eyePos = owner.EyePos;
var eyeRot = owner.EyeRot;
var eyeDir = owner.EyeRot.Forward;
if ( HeldBody.IsValid() && HeldBody.PhysicsGroup != null )
{
if ( holdJoint.IsValid && !holdJoint.IsActive )
{
GrabEnd();
}
else if ( Input.Pressed( InputButton.Attack1 ) )
{
if ( HeldBody.PhysicsGroup.BodyCount > 1 )
{
// Don't throw ragdolls as hard
HeldBody.PhysicsGroup.ApplyImpulse( eyeDir * (ThrowForce * 0.5f), true );
HeldBody.PhysicsGroup.ApplyAngularImpulse( Vector3.Random * ThrowForce, true );
}
else
{
HeldBody.ApplyImpulse( eyeDir * (HeldBody.Mass * ThrowForce) );
HeldBody.ApplyAngularImpulse( Vector3.Random * (HeldBody.Mass * ThrowForce) );
}
GrabEnd();
}
else if ( Input.Pressed( InputButton.Attack2 ) )
{
timeSinceDrop = 0;
GrabEnd();
}
else
{
GrabMove( eyePos, eyeDir, eyeRot );
}
return;
}
if ( timeSinceDrop < DropCooldown )
return;
var tr = Trace.Ray( eyePos, eyePos + eyeDir * MaxPullDistance )
.UseHitboxes()
.Ignore( owner, false )
.Radius( 2.0f )
.HitLayer( CollisionLayer.Debris )
.Run();
if ( !tr.Hit || !tr.Body.IsValid() || !tr.Entity.IsValid() || tr.Entity.IsWorld )
return;
if ( tr.Entity.PhysicsGroup == null )
return;
var modelEnt = tr.Entity as ModelEntity;
if ( !modelEnt.IsValid() )
return;
var body = tr.Body;
if ( Input.Pressed( InputButton.Attack1 ) )
{
if ( tr.Distance < MaxPushDistance && !IsBodyGrabbed( body ) )
{
var pushScale = 1.0f - Math.Clamp( tr.Distance / MaxPushDistance, 0.0f, 1.0f );
body.ApplyImpulseAt( tr.EndPos, eyeDir * (body.Mass * (PushForce * pushScale)) );
}
}
else if ( Input.Down( InputButton.Attack2 ) )
{
var physicsGroup = tr.Entity.PhysicsGroup;
if ( physicsGroup.BodyCount > 1 )
{
body = modelEnt.PhysicsBody;
if ( !body.IsValid() )
return;
}
if ( eyePos.Distance( body.Position ) <= AttachDistance )
{
GrabStart( modelEnt, body, eyePos + eyeDir * HoldDistance, eyeRot );
}
else if ( !IsBodyGrabbed( body ) )
{
physicsGroup.ApplyImpulse( eyeDir * -PullForce, true );
}
}
}
}
private void Activate()
{
if ( !holdBody.IsValid() )
{
holdBody = new PhysicsBody
{
BodyType = PhysicsBodyType.Keyframed
};
}
}
private void Deactivate()
{
GrabEnd();
holdBody?.Remove();
holdBody = null;
}
public override void ActiveStart( Entity ent )
{
base.ActiveStart( ent );
if ( IsServer )
{
Activate();
}
}
public override void ActiveEnd( Entity ent, bool dropped )
{
base.ActiveEnd( ent, dropped );
if ( IsServer )
{
Deactivate();
}
}
protected override void OnDestroy()
{
base.OnDestroy();
if ( IsServer )
{
Deactivate();
}
}
public override void OnCarryDrop( Entity dropper )
{
}
private static bool IsBodyGrabbed( PhysicsBody body )
{
// There for sure is a better way to deal with this
if ( All.OfType<PhysGun>().Any( x => x?.HeldBody?.PhysicsGroup == body?.PhysicsGroup ) ) return true;
if ( All.OfType<GravGun>().Any( x => x?.HeldBody?.PhysicsGroup == body?.PhysicsGroup ) ) return true;
return false;
}
private void GrabStart( ModelEntity entity, PhysicsBody body, Vector3 grabPos, Rotation grabRot )
{
if ( !body.IsValid() )
return;
if ( body.PhysicsGroup == null )
return;
if ( IsBodyGrabbed( body ) )
return;
GrabEnd();
HeldBody = body;
HeldRot = grabRot.Inverse * HeldBody.Rotation;
holdBody.Position = grabPos;
holdBody.Rotation = HeldBody.Rotation;
HeldBody.Wake();
HeldBody.EnableAutoSleeping = false;
holdJoint = PhysicsJoint.Weld
.From( holdBody )
.To( HeldBody, HeldBody.LocalMassCenter )
.WithLinearSpring( LinearFrequency, LinearDampingRatio, 0.0f )
.WithAngularSpring( AngularFrequency, AngularDampingRatio, 0.0f )
.Breakable( HeldBody.Mass * BreakLinearForce, 0 )
.Create();
HeldEntity = entity;
var client = GetClientOwner();
client?.Pvs.Add( HeldEntity );
}
private void GrabEnd()
{
if ( holdJoint.IsValid )
{
holdJoint.Remove();
}
if ( HeldBody.IsValid() )
{
HeldBody.EnableAutoSleeping = true;
}
if ( HeldEntity.IsValid() )
{
var client = GetClientOwner();
client?.Pvs.Remove( HeldEntity );
}
HeldBody = null;
HeldRot = Rotation.Identity;
HeldEntity = null;
}
private void GrabMove( Vector3 startPos, Vector3 dir, Rotation rot )
{
if ( !HeldBody.IsValid() )
return;
holdBody.Position = startPos + dir * HoldDistance;
holdBody.Rotation = rot * HeldRot;
}
public override bool IsUsable( Entity user )
{
return Owner == null || HeldBody.IsValid();
}
}

83
code/tools/Lamp.cs Normal file
View File

@@ -0,0 +1,83 @@
namespace Sandbox.Tools
{
[Library( "tool_lamp", Title = "Lamps", Description = "Directional light source that casts shadows", Group = "construction" )]
public partial class LampTool : BaseTool
{
PreviewEntity previewModel;
private string Model => "models/torch/torch.vmdl";
protected override bool IsPreviewTraceValid( TraceResult tr )
{
if ( !base.IsPreviewTraceValid( tr ) )
return false;
if ( tr.Entity is LampEntity )
return false;
return true;
}
public override void CreatePreviews()
{
if ( TryCreatePreview( ref previewModel, Model ) )
{
previewModel.RelativeToNormal = false;
previewModel.OffsetBounds = true;
previewModel.PositionOffset = -previewModel.CollisionBounds.Center;
}
}
public override void Simulate()
{
if ( !Host.IsServer )
return;
using ( Prediction.Off() )
{
if ( !Input.Pressed( InputButton.Attack1 ) )
return;
var startPos = Owner.EyePos;
var dir = Owner.EyeRot.Forward;
var tr = Trace.Ray( startPos, startPos + dir * MaxTraceDistance )
.Ignore( Owner )
.Run();
if ( !tr.Hit || !tr.Entity.IsValid() )
return;
CreateHitEffects( tr.EndPos );
if ( tr.Entity is LampEntity lamp )
{
// TODO: Set properties
lamp.Flicker = !lamp.Flicker;
return;
}
lamp = new LampEntity
{
Enabled = true,
DynamicShadows = true,
Range = 512,
Falloff = 1.0f,
LinearAttenuation = 0.0f,
QuadraticAttenuation = 1.0f,
InnerConeAngle = 25,
OuterConeAngle = 45,
Brightness = 10,
Color = Color.Random,
Rotation = Rotation.Identity
};
lamp.SetModel( Model );
lamp.SetupPhysicsFromModel( PhysicsMotionType.Dynamic, false );
lamp.Position = tr.EndPos + -lamp.CollisionBounds.Center + tr.Normal * lamp.CollisionBounds.Size * 0.5f;
}
}
}
}

57
code/tools/LeafBlower.cs Normal file
View File

@@ -0,0 +1,57 @@
namespace Sandbox.Tools
{
[Library( "tool_leafblower", Title = "Leaf Blower", Description = "Blow me", Group = "fun" )]
public partial class LeafBlowerTool : BaseTool
{
protected virtual float Force => 128;
protected virtual float MaxDistance => 512;
protected virtual bool Massless => true;
public override void Simulate()
{
if ( !Host.IsServer )
return;
using ( Prediction.Off() )
{
bool push = Input.Down( InputButton.Attack1 );
if ( !push && !Input.Down( InputButton.Attack2 ) )
return;
var startPos = Owner.EyePos;
var dir = Owner.EyeRot.Forward;
var tr = Trace.Ray( startPos, startPos + dir * MaxTraceDistance )
.Ignore( Owner )
.HitLayer( CollisionLayer.Debris )
.Run();
if ( !tr.Hit )
return;
if ( !tr.Entity.IsValid() )
return;
if ( tr.Entity.IsWorld )
return;
var body = tr.Body;
if ( !body.IsValid() )
return;
var direction = tr.EndPos - tr.StartPos;
var distance = direction.Length;
var ratio = (1.0f - (distance / MaxDistance)).Clamp( 0, 1 ) * (push ? 1.0f : -1.0f);
var force = direction * (Force * ratio);
if ( Massless )
{
force *= body.Mass;
}
body.ApplyForceAt( tr.EndPos, force );
}
}
}
}

116
code/tools/Light.cs Normal file
View File

@@ -0,0 +1,116 @@
namespace Sandbox.Tools
{
[Library( "tool_light", Title = "Lights", Description = "A dynamic point light", Group = "construction" )]
public partial class LightTool : BaseTool
{
PreviewEntity previewModel;
private string Model => "models/light/light_tubular.vmdl";
protected override bool IsPreviewTraceValid( TraceResult tr )
{
if ( !base.IsPreviewTraceValid( tr ) )
return false;
if ( tr.Entity is LightEntity )
return false;
return true;
}
public override void CreatePreviews()
{
if ( TryCreatePreview( ref previewModel, Model ) )
{
previewModel.RelativeToNormal = false;
previewModel.OffsetBounds = true;
previewModel.PositionOffset = -previewModel.CollisionBounds.Center;
}
}
public override void Simulate()
{
if ( !Host.IsServer )
return;
using ( Prediction.Off() )
{
bool useRope = Input.Pressed( InputButton.Attack1 );
if ( !useRope && !Input.Pressed( InputButton.Attack2 ) )
return;
var startPos = Owner.EyePos;
var dir = Owner.EyeRot.Forward;
var tr = Trace.Ray( startPos, startPos + dir * MaxTraceDistance )
.Ignore( Owner )
.Run();
if ( !tr.Hit || !tr.Entity.IsValid() )
return;
CreateHitEffects( tr.EndPos );
if ( tr.Entity is LightEntity )
{
// TODO: Set properties
return;
}
var light = new LightEntity
{
Enabled = true,
DynamicShadows = false,
Range = 128,
Falloff = 1.0f,
LinearAttenuation = 0.0f,
QuadraticAttenuation = 1.0f,
Brightness = 1,
Color = Color.Random,
};
light.UseFogNoShadows();
light.SetModel( Model );
light.SetupPhysicsFromModel( PhysicsMotionType.Dynamic, false );
light.Position = tr.EndPos + -light.CollisionBounds.Center + tr.Normal * light.CollisionBounds.Size * 0.5f;
if ( !useRope )
return;
var rope = Particles.Create( "particles/rope.vpcf" );
rope.SetEntity( 0, light, Vector3.Down * 6.5f ); // Should be an attachment point
var attachEnt = tr.Body.IsValid() ? tr.Body.Entity : tr.Entity;
var attachLocalPos = tr.Body.Transform.PointToLocal( tr.EndPos ) * (1.0f / tr.Entity.Scale);
if ( attachEnt.IsWorld )
{
rope.SetPosition( 1, attachLocalPos );
}
else
{
rope.SetEntityBone( 1, attachEnt, tr.Bone, new Transform( attachLocalPos ) );
}
var spring = PhysicsJoint.Spring
.From( light.PhysicsBody, Vector3.Down * 6.5f )
.To( tr.Body, tr.Body.Transform.PointToLocal( tr.EndPos ) )
.WithFrequency( 5.0f )
.WithDampingRatio( 0.7f )
.WithReferenceMass( light.PhysicsBody.Mass )
.WithMinRestLength( 0 )
.WithMaxRestLength( 100 )
.WithCollisionsEnabled()
.Create();
spring.EnableAngularConstraint = false;
spring.OnBreak( () =>
{
rope?.Destroy( true );
spring.Remove();
} );
}
}
}
}

View File

@@ -0,0 +1,124 @@
using Sandbox;
using System.Linq;
public partial class PhysGun
{
Particles Beam;
Particles EndNoHit;
Vector3 lastBeamPos;
ModelEntity lastGrabbedEntity;
[Event.Frame]
public void OnFrame()
{
UpdateEffects();
}
protected virtual void KillEffects()
{
Beam?.Destroy( true );
Beam = null;
BeamActive = false;
EndNoHit?.Destroy( false );
EndNoHit = null;
if ( lastGrabbedEntity.IsValid() )
{
foreach ( var child in lastGrabbedEntity.Children.OfType<ModelEntity>() )
{
if ( child is Player )
continue;
child.GlowActive = false;
child.GlowState = GlowStates.GlowStateOff;
}
lastGrabbedEntity.GlowActive = false;
lastGrabbedEntity.GlowState = GlowStates.GlowStateOff;
lastGrabbedEntity = null;
}
}
protected virtual void UpdateEffects()
{
var owner = Owner;
if ( owner == null || !BeamActive || !IsActiveChild() )
{
KillEffects();
return;
}
var startPos = owner.EyePos;
var dir = owner.EyeRot.Forward;
var tr = Trace.Ray( startPos, startPos + dir * MaxTargetDistance )
.UseHitboxes()
.Ignore( owner )
.Run();
if ( Beam == null )
{
Beam = Particles.Create( "particles/physgun_beam.vpcf", tr.EndPos );
}
Beam.SetEntityAttachment( 0, EffectEntity, "muzzle", true );
if ( GrabbedEntity.IsValid() && !GrabbedEntity.IsWorld )
{
var physGroup = GrabbedEntity.PhysicsGroup;
if ( physGroup != null && GrabbedBone >= 0 )
{
var physBody = physGroup.GetBody( GrabbedBone );
if ( physBody != null )
{
Beam.SetPosition( 1, physBody.Transform.PointToWorld( GrabbedPos ) );
}
}
else
{
Beam.SetEntity( 1, GrabbedEntity, GrabbedPos, true );
}
lastBeamPos = GrabbedEntity.Position + GrabbedEntity.Rotation * GrabbedPos;
EndNoHit?.Destroy( false );
EndNoHit = null;
if ( GrabbedEntity is ModelEntity modelEnt )
{
lastGrabbedEntity = modelEnt;
modelEnt.GlowState = GlowStates.GlowStateOn;
modelEnt.GlowDistanceStart = 0;
modelEnt.GlowDistanceEnd = 1000;
modelEnt.GlowColor = new Color( 0.1f, 1.0f, 1.0f, 1.0f );
modelEnt.GlowActive = true;
foreach ( var child in lastGrabbedEntity.Children.OfType<ModelEntity>() )
{
if ( child is Player )
continue;
child.GlowState = GlowStates.GlowStateOn;
child.GlowDistanceStart = 0;
child.GlowDistanceEnd = 1000;
child.GlowColor = new Color( 0.1f, 1.0f, 1.0f, 1.0f );
child.GlowActive = true;
}
}
}
else
{
lastBeamPos = tr.EndPos;// Vector3.Lerp( lastBeamPos, tr.EndPos, Time.Delta * 10 );
Beam.SetPosition( 1, lastBeamPos );
if ( EndNoHit == null )
EndNoHit = Particles.Create( "particles/physgun_end_nohit.vpcf", lastBeamPos );
EndNoHit.SetPosition( 0, lastBeamPos );
}
}
}

419
code/tools/PhysGun.cs Normal file
View File

@@ -0,0 +1,419 @@
using Sandbox;
using Sandbox.Joints;
using System;
using System.Linq;
[Library( "physgun" )]
public partial class PhysGun : Carriable
{
public override string ViewModelPath => "weapons/rust_pistol/v_rust_pistol.vmdl";
protected PhysicsBody holdBody;
protected WeldJoint holdJoint;
protected PhysicsBody heldBody;
protected Vector3 heldPos;
protected Rotation heldRot;
protected float holdDistance;
protected bool grabbing;
protected virtual float MinTargetDistance => 0.0f;
protected virtual float MaxTargetDistance => 10000.0f;
protected virtual float LinearFrequency => 20.0f;
protected virtual float LinearDampingRatio => 1.0f;
protected virtual float AngularFrequency => 20.0f;
protected virtual float AngularDampingRatio => 1.0f;
protected virtual float TargetDistanceSpeed => 50.0f;
protected virtual float RotateSpeed => 0.2f;
protected virtual float RotateSnapAt => 45.0f;
[Net] public bool BeamActive { get; set; }
[Net] public Entity GrabbedEntity { get; set; }
[Net] public int GrabbedBone { get; set; }
[Net] public Vector3 GrabbedPos { get; set; }
public PhysicsBody HeldBody => heldBody;
public override void Spawn()
{
base.Spawn();
SetModel( "weapons/rust_pistol/rust_pistol.vmdl" );
CollisionGroup = CollisionGroup.Weapon;
SetInteractsAs( CollisionLayer.Debris );
}
public override void Simulate( Client client )
{
if ( Owner is not Player owner ) return;
var eyePos = owner.EyePos;
var eyeDir = owner.EyeRot.Forward;
var eyeRot = Rotation.From( new Angles( 0.0f, owner.EyeRot.Angles().yaw, 0.0f ) );
if ( Input.Pressed( InputButton.Attack1 ) )
{
(Owner as AnimEntity)?.SetAnimBool( "b_attack", true );
if ( !grabbing )
grabbing = true;
}
bool grabEnabled = grabbing && Input.Down( InputButton.Attack1 );
bool wantsToFreeze = Input.Pressed( InputButton.Attack2 );
if ( GrabbedEntity.IsValid() && wantsToFreeze )
{
(Owner as AnimEntity)?.SetAnimBool( "b_attack", true );
}
BeamActive = grabEnabled;
if ( IsServer )
{
using ( Prediction.Off() )
{
if ( !holdBody.IsValid() )
return;
if ( grabEnabled )
{
if ( heldBody.IsValid() )
{
UpdateGrab( eyePos, eyeRot, eyeDir, wantsToFreeze );
}
else
{
TryStartGrab( owner, eyePos, eyeRot, eyeDir );
}
}
else if ( grabbing )
{
GrabEnd();
}
if ( !grabbing && Input.Pressed( InputButton.Reload ) )
{
TryUnfreezeAll( owner, eyePos, eyeRot, eyeDir );
}
}
}
if ( BeamActive )
{
Input.MouseWheel = 0;
}
}
private static bool IsBodyGrabbed( PhysicsBody body )
{
// There for sure is a better way to deal with this
if ( All.OfType<PhysGun>().Any( x => x?.HeldBody?.PhysicsGroup == body?.PhysicsGroup ) ) return true;
if ( All.OfType<GravGun>().Any( x => x?.HeldBody?.PhysicsGroup == body?.PhysicsGroup ) ) return true;
return false;
}
private void TryUnfreezeAll( Player owner, Vector3 eyePos, Rotation eyeRot, Vector3 eyeDir )
{
var tr = Trace.Ray( eyePos, eyePos + eyeDir * MaxTargetDistance )
.UseHitboxes()
.Ignore( owner, false )
.HitLayer( CollisionLayer.Debris )
.Run();
if ( !tr.Hit || !tr.Entity.IsValid() || tr.Entity.IsWorld ) return;
var rootEnt = tr.Entity.Root;
if ( !rootEnt.IsValid() ) return;
var physicsGroup = rootEnt.PhysicsGroup;
if ( physicsGroup == null ) return;
bool unfrozen = false;
for ( int i = 0; i < physicsGroup.BodyCount; ++i )
{
var body = physicsGroup.GetBody( i );
if ( !body.IsValid() ) continue;
if ( body.BodyType == PhysicsBodyType.Static )
{
body.BodyType = PhysicsBodyType.Dynamic;
unfrozen = true;
}
}
if ( unfrozen )
{
var freezeEffect = Particles.Create( "particles/physgun_freeze.vpcf" );
freezeEffect.SetPosition( 0, tr.EndPos );
}
}
private void TryStartGrab( Player owner, Vector3 eyePos, Rotation eyeRot, Vector3 eyeDir )
{
var tr = Trace.Ray( eyePos, eyePos + eyeDir * MaxTargetDistance )
.UseHitboxes()
.Ignore( owner, false )
.HitLayer( CollisionLayer.Debris )
.Run();
if ( !tr.Hit || !tr.Entity.IsValid() || !tr.Body.IsValid() || tr.Entity.IsWorld ) return;
var rootEnt = tr.Entity.Root;
var body = tr.Body;
if ( tr.Entity.Parent.IsValid() )
{
if ( rootEnt.IsValid() && rootEnt.PhysicsGroup != null )
{
body = rootEnt.PhysicsGroup.GetBody( 0 );
}
}
if ( !body.IsValid() )
return;
//
// Don't move keyframed
//
if ( body.BodyType == PhysicsBodyType.Keyframed )
return;
// Unfreeze
if ( body.BodyType == PhysicsBodyType.Static )
{
body.BodyType = PhysicsBodyType.Dynamic;
}
if ( IsBodyGrabbed( body ) )
return;
GrabInit( body, eyePos, tr.EndPos, eyeRot );
GrabbedEntity = rootEnt;
GrabbedPos = body.Transform.PointToLocal( tr.EndPos );
GrabbedBone = tr.Entity.PhysicsGroup.GetBodyIndex( body );
var client = GetClientOwner();
if ( client != null )
{
client.Pvs.Add( GrabbedEntity );
}
}
private void UpdateGrab( Vector3 eyePos, Rotation eyeRot, Vector3 eyeDir, bool wantsToFreeze )
{
if ( wantsToFreeze )
{
heldBody.BodyType = PhysicsBodyType.Static;
if ( GrabbedEntity.IsValid() )
{
var freezeEffect = Particles.Create( "particles/physgun_freeze.vpcf" );
freezeEffect.SetPosition( 0, heldBody.Transform.PointToWorld( GrabbedPos ) );
}
GrabEnd();
return;
}
MoveTargetDistance( Input.MouseWheel * TargetDistanceSpeed );
bool rotating = Input.Down( InputButton.Use );
bool snapping = false;
if ( rotating )
{
EnableAngularSpring( Input.Down( InputButton.Run ) ? 100.0f : 0.0f );
DoRotate( eyeRot, Input.MouseDelta * RotateSpeed );
snapping = Input.Down( InputButton.Run );
}
else
{
DisableAngularSpring();
}
GrabMove( eyePos, eyeDir, eyeRot, snapping );
}
private void EnableAngularSpring( float scale )
{
if ( holdJoint.IsValid )
{
holdJoint.AngularDampingRatio = AngularDampingRatio * scale;
holdJoint.AngularFrequency = AngularFrequency * scale;
}
}
private void DisableAngularSpring()
{
if ( holdJoint.IsValid )
{
holdJoint.AngularDampingRatio = 0.0f;
holdJoint.AngularFrequency = 0.0f;
}
}
private void Activate()
{
if ( !IsServer )
return;
if ( !holdBody.IsValid() )
{
holdBody = new PhysicsBody
{
BodyType = PhysicsBodyType.Keyframed
};
}
}
private void Deactivate()
{
if ( IsServer )
{
GrabEnd();
holdBody?.Remove();
holdBody = null;
}
KillEffects();
}
public override void ActiveStart( Entity ent )
{
base.ActiveStart( ent );
Activate();
}
public override void ActiveEnd( Entity ent, bool dropped )
{
base.ActiveEnd( ent, dropped );
Deactivate();
}
protected override void OnDestroy()
{
base.OnDestroy();
Deactivate();
}
public override void OnCarryDrop( Entity dropper )
{
}
private void GrabInit( PhysicsBody body, Vector3 startPos, Vector3 grabPos, Rotation rot )
{
if ( !body.IsValid() )
return;
GrabEnd();
grabbing = true;
heldBody = body;
holdDistance = Vector3.DistanceBetween( startPos, grabPos );
holdDistance = holdDistance.Clamp( MinTargetDistance, MaxTargetDistance );
heldPos = heldBody.Transform.PointToLocal( grabPos );
heldRot = rot.Inverse * heldBody.Rotation;
holdBody.Position = grabPos;
holdBody.Rotation = heldBody.Rotation;
heldBody.Wake();
heldBody.EnableAutoSleeping = false;
holdJoint = PhysicsJoint.Weld
.From( holdBody )
.To( heldBody, heldPos )
.WithLinearSpring( LinearFrequency, LinearDampingRatio, 0.0f )
.WithAngularSpring( 0.0f, 0.0f, 0.0f )
.Create();
}
private void GrabEnd()
{
if ( holdJoint.IsValid )
{
holdJoint.Remove();
}
if ( heldBody.IsValid() )
{
heldBody.EnableAutoSleeping = true;
}
var client = GetClientOwner();
if ( client != null && GrabbedEntity.IsValid() )
{
client.Pvs.Remove( GrabbedEntity );
}
heldBody = null;
GrabbedEntity = null;
grabbing = false;
}
private void GrabMove( Vector3 startPos, Vector3 dir, Rotation rot, bool snapAngles )
{
if ( !heldBody.IsValid() )
return;
holdBody.Position = startPos + dir * holdDistance;
holdBody.Rotation = rot * heldRot;
if ( snapAngles )
{
var angles = holdBody.Rotation.Angles();
holdBody.Rotation = Rotation.From(
MathF.Round( angles.pitch / RotateSnapAt ) * RotateSnapAt,
MathF.Round( angles.yaw / RotateSnapAt ) * RotateSnapAt,
MathF.Round( angles.roll / RotateSnapAt ) * RotateSnapAt
);
}
}
private void MoveTargetDistance( float distance )
{
holdDistance += distance;
holdDistance = holdDistance.Clamp( MinTargetDistance, MaxTargetDistance );
}
protected virtual void DoRotate( Rotation eye, Vector3 input )
{
var localRot = eye;
localRot *= Rotation.FromAxis( Vector3.Up, input.x );
localRot *= Rotation.FromAxis( Vector3.Right, input.y );
localRot = eye.Inverse * localRot;
heldRot = localRot * heldRot;
}
public override void BuildInput( InputBuilder owner )
{
if ( !GrabbedEntity.IsValid() )
return;
if ( !owner.Down( InputButton.Attack1 ) )
return;
if ( owner.Down( InputButton.Use ) )
{
owner.ViewAngles = owner.OriginalViewAngles;
}
}
public override bool IsUsable( Entity user )
{
return Owner == null || HeldBody.IsValid();
}
}

42
code/tools/Remover.cs Normal file
View File

@@ -0,0 +1,42 @@
namespace Sandbox.Tools
{
[Library( "tool_remover", Title = "Remover", Description = "Remove entities", Group = "construction" )]
public partial class RemoverTool : BaseTool
{
public override void Simulate()
{
if ( !Host.IsServer )
return;
using ( Prediction.Off() )
{
if ( !Input.Pressed( InputButton.Attack1 ) )
return;
var startPos = Owner.EyePos;
var dir = Owner.EyeRot.Forward;
var tr = Trace.Ray( startPos, startPos + dir * MaxTraceDistance )
.Ignore( Owner )
.HitLayer( CollisionLayer.Debris )
.Run();
if ( !tr.Hit || !tr.Entity.IsValid() )
return;
if ( tr.Entity is Player )
return;
CreateHitEffects( tr.EndPos );
if ( tr.Entity.IsWorld )
return;
tr.Entity.Delete();
var particle = Particles.Create( "particles/physgun_freeze.vpcf" );
particle.SetPosition( 0, tr.Entity.Position );
}
}
}
}

54
code/tools/Resizer.cs Normal file
View File

@@ -0,0 +1,54 @@
using System;
namespace Sandbox.Tools
{
[Library( "tool_resizer", Title = "Resizer", Description = "Change the scale of things", Group = "construction" )]
public partial class ResizerTool : BaseTool
{
public override void Simulate()
{
if ( !Host.IsServer )
return;
using ( Prediction.Off() )
{
var startPos = Owner.EyePos;
var dir = Owner.EyeRot.Forward;
int resizeDir = 0;
var reset = false;
if ( Input.Down( InputButton.Attack1 ) ) resizeDir = 1;
else if ( Input.Down( InputButton.Attack2 ) ) resizeDir = -1;
else if ( Input.Pressed( InputButton.Reload ) ) reset = true;
else return;
var tr = Trace.Ray( startPos, startPos + dir * MaxTraceDistance )
.Ignore( Owner )
.UseHitboxes()
.HitLayer( CollisionLayer.Debris )
.Run();
if ( !tr.Hit || !tr.Entity.IsValid() || tr.Entity.PhysicsGroup == null )
return;
// Disable resizing lights for now
if ( tr.Entity is LightEntity || tr.Entity is LampEntity )
return;
var scale = reset ? 1.0f : Math.Clamp( tr.Entity.Scale + ((0.5f * Time.Delta) * resizeDir), 0.4f, 4.0f );
if ( tr.Entity.Scale != scale )
{
tr.Entity.Scale = scale;
tr.Entity.PhysicsGroup.RebuildMass();
tr.Entity.PhysicsGroup.Wake();
}
if ( Input.Pressed( InputButton.Attack1 ) || Input.Pressed( InputButton.Attack2 ) || reset )
{
CreateHitEffects( tr.EndPos );
}
}
}
}
}

122
code/tools/Rope.cs Normal file
View File

@@ -0,0 +1,122 @@
namespace Sandbox.Tools
{
[Library( "tool_rope", Title = "Rope", Description = "Join two things together with a rope", Group = "construction" )]
public partial class RopeTool : BaseTool
{
private PhysicsBody targetBody;
private int targetBone;
private Vector3 localOrigin1;
private Vector3 globalOrigin1;
public override void Simulate()
{
if ( !Host.IsServer )
return;
using ( Prediction.Off() )
{
if ( !Input.Pressed( InputButton.Attack1 ) )
return;
var startPos = Owner.EyePos;
var dir = Owner.EyeRot.Forward;
var tr = Trace.Ray( startPos, startPos + dir * MaxTraceDistance )
.Ignore( Owner )
.Run();
if ( !tr.Hit )
return;
if ( !tr.Body.IsValid() )
return;
if ( !tr.Entity.IsValid() )
return;
if ( tr.Entity is not ModelEntity )
return;
if ( !targetBody.IsValid() )
{
targetBody = tr.Body;
targetBone = tr.Bone;
globalOrigin1 = tr.EndPos;
localOrigin1 = tr.Body.Transform.PointToLocal( globalOrigin1 );
CreateHitEffects( tr.EndPos );
return;
}
if ( targetBody == tr.Body )
return;
var rope = Particles.Create( "particles/rope.vpcf" );
if ( targetBody.Entity.IsWorld )
{
rope.SetPosition( 0, localOrigin1 );
}
else
{
rope.SetEntityBone( 0, targetBody.Entity, targetBone, new Transform( localOrigin1 * (1.0f / targetBody.Entity.Scale) ) );
}
var localOrigin2 = tr.Body.Transform.PointToLocal( tr.EndPos );
if ( tr.Entity.IsWorld )
{
rope.SetPosition( 1, localOrigin2 );
}
else
{
rope.SetEntityBone( 1, tr.Entity, tr.Bone, new Transform( localOrigin2 * (1.0f / tr.Entity.Scale) ) );
}
var spring = PhysicsJoint.Spring
.From( targetBody, localOrigin1 )
.To( tr.Body, localOrigin2 )
.WithFrequency( 5.0f )
.WithDampingRatio( 0.7f )
.WithReferenceMass( targetBody.Mass )
.WithMinRestLength( 0 )
.WithMaxRestLength( tr.EndPos.Distance( globalOrigin1 ) )
.WithCollisionsEnabled()
.Create();
spring.EnableAngularConstraint = false;
spring.OnBreak( () =>
{
rope?.Destroy( true );
spring.Remove();
} );
CreateHitEffects( tr.EndPos );
Reset();
}
}
private void Reset()
{
targetBody = null;
targetBone = -1;
localOrigin1 = default;
}
public override void Activate()
{
base.Activate();
Reset();
}
public override void Deactivate()
{
base.Deactivate();
Reset();
}
}
}

89
code/tools/Thruster.cs Normal file
View File

@@ -0,0 +1,89 @@
namespace Sandbox.Tools
{
[Library( "tool_thruster", Title = "Thruster", Description = "A rocket type thing that can push forwards and backward", Group = "construction" )]
public partial class ThrusterTool : BaseTool
{
PreviewEntity previewModel;
bool massless = true;
public override void CreatePreviews()
{
if ( TryCreatePreview( ref previewModel, "models/thruster/thrusterprojector.vmdl" ) )
{
previewModel.RotationOffset = Rotation.FromAxis( Vector3.Right, -90 );
}
}
protected override bool IsPreviewTraceValid( TraceResult tr )
{
if ( !base.IsPreviewTraceValid( tr ) )
return false;
if ( tr.Entity is ThrusterEntity )
return false;
return true;
}
public override void Simulate()
{
if ( !Host.IsServer )
return;
using ( Prediction.Off() )
{
if ( Input.Pressed( InputButton.Attack2 ) )
{
massless = !massless;
}
if ( !Input.Pressed( InputButton.Attack1 ) )
return;
var startPos = Owner.EyePos;
var dir = Owner.EyeRot.Forward;
var tr = Trace.Ray( startPos, startPos + dir * MaxTraceDistance )
.Ignore( Owner )
.Run();
if ( !tr.Hit )
return;
if ( !tr.Entity.IsValid() )
return;
var attached = !tr.Entity.IsWorld && tr.Body.IsValid() && tr.Body.PhysicsGroup != null && tr.Body.Entity.IsValid();
if ( attached && tr.Entity is not Prop )
return;
CreateHitEffects( tr.EndPos );
if ( tr.Entity is ThrusterEntity )
{
// TODO: Set properties
return;
}
var ent = new ThrusterEntity
{
Position = tr.EndPos,
Rotation = Rotation.LookAt( tr.Normal, dir ) * Rotation.From( new Angles( 90, 0, 0 ) ),
PhysicsEnabled = !attached,
EnableSolidCollisions = !attached,
TargetBody = attached ? tr.Body : null,
Massless = massless
};
if ( attached )
{
ent.SetParent( tr.Body.Entity, tr.Body.PhysicsGroup.GetBodyBoneName( tr.Body ) );
}
ent.SetModel( "models/thruster/thrusterprojector.vmdl" );
}
}
}
}

96
code/tools/Weld.cs Normal file
View File

@@ -0,0 +1,96 @@
namespace Sandbox.Tools
{
[Library( "tool_weld", Title = "Weld", Description = "Weld stuff together", Group = "construction" )]
public partial class WeldTool : BaseTool
{
private Prop target;
public override void Simulate()
{
if ( !Host.IsServer )
return;
using ( Prediction.Off() )
{
var startPos = Owner.EyePos;
var dir = Owner.EyeRot.Forward;
var tr = Trace.Ray( startPos, startPos + dir * MaxTraceDistance )
.Ignore( Owner )
.Run();
if ( !tr.Hit || !tr.Body.IsValid() || !tr.Entity.IsValid() || tr.Entity.IsWorld )
return;
if ( tr.Entity.PhysicsGroup == null || tr.Entity.PhysicsGroup.BodyCount > 1 )
return;
if ( tr.Entity is not Prop prop )
return;
if ( Input.Pressed( InputButton.Attack1 ) )
{
if ( prop.Root is not Prop rootProp )
{
return;
}
if ( target == rootProp )
return;
if ( !target.IsValid() )
{
target = rootProp;
}
else
{
target.Weld( rootProp );
target = null;
}
}
else if ( Input.Pressed( InputButton.Attack2 ) )
{
prop.Unweld( true );
Reset();
}
else if ( Input.Pressed( InputButton.Reload ) )
{
if ( prop.Root is not Prop rootProp )
{
return;
}
rootProp.Unweld();
Reset();
}
else
{
return;
}
CreateHitEffects( tr.EndPos );
}
}
private void Reset()
{
target = null;
}
public override void Activate()
{
base.Activate();
Reset();
}
public override void Deactivate()
{
base.Deactivate();
Reset();
}
}
}

83
code/tools/Wheel.cs Normal file
View File

@@ -0,0 +1,83 @@
namespace Sandbox.Tools
{
[Library( "tool_wheel", Title = "Wheel", Description = "A wheel that you can turn on and off (but actually can't yet)", Group = "construction" )]
public partial class WheelTool : BaseTool
{
PreviewEntity previewModel;
protected override bool IsPreviewTraceValid( TraceResult tr )
{
if ( !base.IsPreviewTraceValid( tr ) )
return false;
if ( tr.Entity is WheelEntity )
return false;
return true;
}
public override void CreatePreviews()
{
if ( TryCreatePreview( ref previewModel, "models/citizen_props/wheel01.vmdl" ) )
{
previewModel.RotationOffset = Rotation.FromAxis( Vector3.Up, 90 );
}
}
public override void Simulate()
{
if ( !Host.IsServer )
return;
using ( Prediction.Off() )
{
if ( !Input.Pressed( InputButton.Attack1 ) )
return;
var startPos = Owner.EyePos;
var dir = Owner.EyeRot.Forward;
var tr = Trace.Ray( startPos, startPos + dir * MaxTraceDistance )
.Ignore( Owner )
.Run();
if ( !tr.Hit )
return;
if ( !tr.Entity.IsValid() )
return;
var attached = !tr.Entity.IsWorld && tr.Body.IsValid() && tr.Body.PhysicsGroup != null && tr.Body.Entity.IsValid();
if ( attached && tr.Entity is not Prop )
return;
CreateHitEffects( tr.EndPos );
if ( tr.Entity is WheelEntity )
{
// TODO: Set properties
return;
}
var ent = new WheelEntity
{
Position = tr.EndPos,
Rotation = Rotation.LookAt( tr.Normal ) * Rotation.From( new Angles( 0, 90, 0 ) ),
};
ent.SetModel( "models/citizen_props/wheel01.vmdl" );
ent.PhysicsBody.Mass = tr.Body.Mass;
ent.Joint = PhysicsJoint.Revolute
.From( ent.PhysicsBody )
.To( tr.Body )
.WithPivot( tr.EndPos )
.WithBasis( Rotation.LookAt( tr.Normal ) * Rotation.From( new Angles( 90, 0, 0 ) ) )
.Create();
}
}
}
}

41
code/ui/CurrentTool.cs Normal file
View File

@@ -0,0 +1,41 @@
using Sandbox;
using Sandbox.Tools;
using Sandbox.UI;
using Sandbox.UI.Construct;
public class CurrentTool : Panel
{
public Label Title;
public Label Description;
public CurrentTool()
{
Title = Add.Label( "Tool", "title" );
Description = Add.Label( "This is a tool", "description" );
}
public override void Tick()
{
var tool = GetCurrentTool();
SetClass( "active", tool != null );
if ( tool != null )
{
Title.SetText( tool.ClassInfo.Title );
Description.SetText( tool.ClassInfo.Description );
}
}
BaseTool GetCurrentTool()
{
var player = Local.Pawn;
if ( player == null ) return null;
var inventory = player.Inventory;
if ( inventory == null ) return null;
if ( inventory.Active is not Tool tool ) return null;
return tool?.CurrentTool;
}
}

21
code/ui/Health.cs Normal file
View File

@@ -0,0 +1,21 @@
using Sandbox;
using Sandbox.UI;
using Sandbox.UI.Construct;
public class Health : Panel
{
public Label Label;
public Health()
{
Label = Add.Label( "100", "value" );
}
public override void Tick()
{
var player = Local.Pawn;
if ( player == null ) return;
Label.Text = $"{player.Health.CeilToInt()}";
}
}

103
code/ui/InventoryBar.cs Normal file
View File

@@ -0,0 +1,103 @@
using Sandbox;
using Sandbox.UI;
using System.Collections.Generic;
public class InventoryBar : Panel
{
readonly List<InventoryIcon> slots = new();
public InventoryBar()
{
for ( int i = 0; i < 9; i++ )
{
var icon = new InventoryIcon( i + 1, this );
slots.Add( icon );
}
}
public override void Tick()
{
base.Tick();
var player = Local.Pawn;
if ( player == null ) return;
if ( player.Inventory == null ) return;
for ( int i = 0; i < slots.Count; i++ )
{
UpdateIcon( player.Inventory.GetSlot( i ), slots[i], i );
}
}
private static void UpdateIcon( Entity ent, InventoryIcon inventoryIcon, int i )
{
if ( ent == null )
{
inventoryIcon.Clear();
return;
}
inventoryIcon.TargetEnt = ent;
inventoryIcon.Label.Text = ent.ClassInfo.Title;
inventoryIcon.SetClass( "active", ent.IsActiveChild() );
}
[Event( "buildinput" )]
public void ProcessClientInput( InputBuilder input )
{
var player = Local.Pawn as Player;
if ( player == null )
return;
var inventory = player.Inventory;
if ( inventory == null )
return;
if ( player.ActiveChild is PhysGun physgun && physgun.BeamActive )
{
return;
}
if ( input.Pressed( InputButton.Slot1 ) ) SetActiveSlot( input, inventory, 0 );
if ( input.Pressed( InputButton.Slot2 ) ) SetActiveSlot( input, inventory, 1 );
if ( input.Pressed( InputButton.Slot3 ) ) SetActiveSlot( input, inventory, 2 );
if ( input.Pressed( InputButton.Slot4 ) ) SetActiveSlot( input, inventory, 3 );
if ( input.Pressed( InputButton.Slot5 ) ) SetActiveSlot( input, inventory, 4 );
if ( input.Pressed( InputButton.Slot6 ) ) SetActiveSlot( input, inventory, 5 );
if ( input.Pressed( InputButton.Slot7 ) ) SetActiveSlot( input, inventory, 6 );
if ( input.Pressed( InputButton.Slot8 ) ) SetActiveSlot( input, inventory, 7 );
if ( input.Pressed( InputButton.Slot9 ) ) SetActiveSlot( input, inventory, 8 );
if ( input.MouseWheel != 0 ) SwitchActiveSlot( input, inventory, -input.MouseWheel );
}
private static void SetActiveSlot( InputBuilder input, IBaseInventory inventory, int i )
{
var player = Local.Pawn;
if ( player == null )
return;
var ent = inventory.GetSlot( i );
if ( player.ActiveChild == ent )
return;
if ( ent == null )
return;
input.ActiveChild = ent;
}
private static void SwitchActiveSlot( InputBuilder input, IBaseInventory inventory, int idelta )
{
var count = inventory.Count();
if ( count == 0 ) return;
var slot = inventory.GetActiveSlot();
var nextSlot = slot + idelta;
while ( nextSlot < 0 ) nextSlot += count;
while ( nextSlot >= count ) nextSlot -= count;
SetActiveSlot( input, inventory, nextSlot );
}
}

24
code/ui/InventoryIcon.cs Normal file
View File

@@ -0,0 +1,24 @@

using Sandbox;
using Sandbox.UI;
using Sandbox.UI.Construct;
public class InventoryIcon : Panel
{
public Entity TargetEnt;
public Label Label;
public Label Number;
public InventoryIcon( int i, Panel parent )
{
Parent = parent;
Label = Add.Label( "empty", "item-name" );
Number = Add.Label( $"{i}", "slot-number" );
}
public void Clear()
{
Label.Text = "";
SetClass( "active", false );
}
}

25
code/ui/SandboxHud.cs Normal file
View File

@@ -0,0 +1,25 @@
using Sandbox;
using Sandbox.UI;
[Library]
public partial class SandboxHud : HudEntity<RootPanel>
{
public SandboxHud()
{
if ( !IsClient )
return;
RootPanel.StyleSheet.Load( "/ui/SandboxHud.scss" );
RootPanel.AddChild<NameTags>();
RootPanel.AddChild<CrosshairCanvas>();
RootPanel.AddChild<ChatBox>();
RootPanel.AddChild<VoiceList>();
RootPanel.AddChild<KillFeed>();
RootPanel.AddChild<Scoreboard<ScoreboardEntry>>();
RootPanel.AddChild<Health>();
RootPanel.AddChild<InventoryBar>();
RootPanel.AddChild<CurrentTool>();
RootPanel.AddChild<SpawnMenu>();
}
}

135
code/ui/SandboxHud.scss Normal file
View File

@@ -0,0 +1,135 @@
Health
{
position: absolute;
background-color: rgba( black, 0.5 );
right: 100px;
bottom: 48px;
font-size: 40px;
font-weight: bold;
color: white;
height: 80px;
padding: 0 20px;
align-items: center;
}
CurrentTool
{
position: absolute;
background: linear-gradient(to right, rgba(black, 0.5), rgba(black, 0.0));
top: 100px;
left: 0px;
padding: 30px 70px;
flex-direction: column;
width: 560px;
opacity: 0.0;
&.active
{
opacity: 1.0;
}
.title
{
font-size: 50px;
font-weight: bold;
text-shadow: 4px 4px 10px rgba( black, 0.3 );
color: white;
}
.description
{
padding-top: 5px;
padding-left: 10px;
font-size: 18px;
font-weight: normal;
text-shadow: 2px 2px 5px rgba( black, 0.3 );
color: white;
opacity: 0.8;
}
}
rootpanel
{
background-color: rgba( #333, 0 );
transition: background-color 0.2s ease-in;
&.spawnmenuopen
{
transition: background-color 0.3s ease-out;
background-color: rgba( #333, 0.9 );
}
&.devcamera
{
display: none;
}
}
InventoryBar
{
position: absolute;
width: 100%;
bottom: 48px;
justify-content: center;
align-items: flex-end;
}
InventoryIcon
{
width: 80px;
height: 80px;
background-color: rgba( #222, 0.3 );
margin: 0px 2px;
position: relative;
transition: all 0.2s ease-in-out;
margin-top: 10px;
font-family: poppins;
.item-name
{
padding: 5px;
color: white;
text-align: center;
left: 0;
right: 0;
font-size: 10px;
position: absolute;
bottom: 0;
opacity: 0.7;
transition: all 0.5s ease-in-out;
text-shadow: 1px 1px 5px black;
}
.slot-number
{
font-size: 25px;
color: black;
font-weight: 900;
opacity: 0.3;
position: absolute;
top: 1px;
right: 5px;
}
&.active
{
background-color: rgba( #0094ff, 0.5 );
transition: all 0.1s ease-out;
width: 100px;
height: 100px;
.slot-number
{
color: #275576;
}
.item-name
{
opacity: 1;
transition: all 0.1s ease-out;
font-size: 13px;
}
}
}

78
code/ui/SpawnMenu.cs Normal file
View File

@@ -0,0 +1,78 @@

using Sandbox;
using Sandbox.UI;
using Sandbox.UI.Construct;
using System;
using System.Reflection.Metadata;
using System.Threading.Tasks;
[Library]
public partial class SpawnMenu : Panel
{
public static SpawnMenu Instance;
public SpawnMenu()
{
Instance = this;
StyleSheet.Load( "/ui/SpawnMenu.scss" );
var left = Add.Panel( "left" );
{
var tabs = left.AddChild<ButtonGroup>();
tabs.AddClass( "tabs" );
var body = left.Add.Panel( "body" );
{
var props = body.AddChild<SpawnList>();
tabs.SelectedButton = tabs.AddButtonActive( "Props", ( b ) => props.SetClass( "active", b ) );
var ents = body.AddChild<EntityList>();
tabs.AddButtonActive( "Entities", ( b ) => ents.SetClass( "active", b ) );
}
}
var right = Add.Panel( "right" );
{
var tabs = right.Add.Panel( "tabs" );
{
tabs.Add.Button( "Tools" ).AddClass( "active" );
tabs.Add.Button( "Utility" );
}
var body = right.Add.Panel( "body" );
{
var list = body.Add.Panel( "toollist" );
{
foreach ( var entry in Library.GetAllAttributes<Sandbox.Tools.BaseTool>() )
{
if ( entry.Title == "BaseTool" )
continue;
var button = list.Add.Button( entry.Title );
button.SetClass( "active", entry.Name == ConsoleSystem.GetValue( "tool_current" ) );
button.AddEventListener( "onclick", () =>
{
ConsoleSystem.Run( "tool_current", entry.Name );
ConsoleSystem.Run( "inventory_current", "weapon_tool" );
foreach ( var child in list.Children )
child.SetClass( "active", child == button );
} );
}
}
body.Add.Panel( "inspector" );
}
}
}
public override void Tick()
{
base.Tick();
Parent.SetClass( "spawnmenuopen", Input.Down( InputButton.Menu ) );
}
}

177
code/ui/SpawnMenu.scss Normal file
View File

@@ -0,0 +1,177 @@
spawnmenu {
left: 0px;
right: 0px;
top: 0px;
bottom: 0px;
margin: 100px;
margin-bottom: 200px;
border-radius: 5px;
pointer-events: none;
transition: all 0.1s ease-out;
position: absolute;
font-family: poppins;
opacity: 0;
.tabs {
font-size: 16px;
flex-shrink: 0;
margin-bottom: 4px;
padding-left: 10px;
button {
padding: 6px 5px;
margin-right: 10px;
font-size: 13px;
align-items: center;
font-weight: 600;
opacity: 0.2;
color: #fff;
cursor: pointer;
//border-radius: 5px;
label {
text-shadow: 1px 1px 4px rgba( black, 0.2 );
}
&:hover {
opacity: 1;
}
&.active {
transform: scale( 1 );
opacity: 1;
color: #fff;
// background-color: #fff;//( #3273eb, 0.2 );
//color: #3273eb;
}
}
}
.left {
flex-grow: 1;
flex-direction: column;
position: relative;
left: -500px;
transition: all 0.3s ease-in;
.body {
border-radius: 10px;
background-color: rgba( #111, 0.7 );
flex-grow: 1;
}
}
.right {
margin-left: 10px;
flex-direction: column;
min-width: 450px;
flex-shrink: 0;
left: 500px;
transition: all 0.3s ease-in;
.body {
// background-color: rgba( #333, 0.95 );
flex-grow: 1;
border-radius: 10px;
background-color: rgba( #111, 0.7 );
padding: 10px;
.toollist {
flex-direction: column;
width: 150px;
color: #aaa;
button {
padding: 3px 10px;
font-size: 11px;
transition: all 0.2s ease-out;
cursor: pointer;
&:hover {
//border-left: 3px solid #3273eb;
color: white;
}
&.active {
//border-left: 5px solid #3273eb;
color: white;
}
}
}
.inspector {
//background-color: rgba( #111, 0.7 );
flex-grow: 1;
margin-left: 5px;
border-radius: 0 3px 3px 0;
}
}
}
}
.spawnmenuopen spawnmenu {
transition: all 0.2s ease-out;
pointer-events: all;
opacity: 1;
transform: scale( 1 );
.left, .right {
left: 0;
transition: all 0.1s ease-out;
}
}
.spawnpage {
display: none;
&.active {
display: flex;
opacity: 1;
}
.canvas {
overflow: scroll;
width: 100%;
flex-grow: 1;
flex-wrap: wrap;
justify-content: space-between;
margin: 10px;
border-radius: 5px;
.cell {
padding: 5px;
}
.icon {
border-radius: 16px;
color: rgba( #fff, 0.5 );
font-size: 12px;
text-align: center;
cursor: pointer;
width: 100%;
height: 100%;
background-position: center;
background-size: cover;
background-color: rgba(black, 0.1);
background-image: url( /entity/spawnicon.png );
label {
font-size: 9px;
position: absolute;
bottom: -17px;
left: 0;
right: 0;
pointer-events: none;
}
&:hover {
color: #fff;
background-color: rgba(black, 0.4);
}
&:active {
}
}
}
}

View File

@@ -0,0 +1,38 @@
using Sandbox;
using Sandbox.UI;
using Sandbox.UI.Construct;
using Sandbox.UI.Tests;
using System.Linq;
[Library]
public partial class EntityList : Panel
{
VirtualScrollPanel Canvas;
public EntityList()
{
AddClass( "spawnpage" );
AddChild( out Canvas, "canvas" );
Canvas.Layout.AutoColumns = true;
Canvas.Layout.ItemSize = new Vector2( 100, 100 );
Canvas.OnCreateCell = ( cell, data ) =>
{
var entry = (LibraryAttribute)data;
var btn = cell.Add.Button( entry.Title );
btn.AddClass( "icon" );
btn.AddEventListener( "onclick", () => ConsoleSystem.Run( "spawn_entity", entry.Name ) );
btn.Style.Background = new PanelBackground
{
Texture = Texture.Load( $"/entity/{entry.Name}.png", false )
};
};
var ents = Library.GetAllAttributes<Entity>().Where( x => x.Spawnable ).OrderBy( x => x.Title ).ToArray();
foreach ( var entry in ents )
{
Canvas.AddItem( entry );
}
}
}

37
code/ui/left/SpawnList.cs Normal file
View File

@@ -0,0 +1,37 @@
using Sandbox;
using Sandbox.UI;
using Sandbox.UI.Tests;
[Library]
public partial class SpawnList : Panel
{
VirtualScrollPanel Canvas;
public SpawnList()
{
AddClass( "spawnpage" );
AddChild( out Canvas, "canvas" );
Canvas.Layout.AutoColumns = true;
Canvas.Layout.ItemSize = new Vector2( 100, 100 );
Canvas.OnCreateCell = ( cell, data ) =>
{
var file = (string)data;
var panel = cell.Add.Panel( "icon" );
panel.AddEventListener( "onclick", () => ConsoleSystem.Run( "spawn", "models/" + file ) );
panel.Style.Background = new PanelBackground
{
Texture = Texture.Load( $"/models/{file}_c.png", false )
};
};
foreach ( var file in FileSystem.Mounted.FindFile( "models", "*.vmdl_c.png", true ) )
{
if ( string.IsNullOrWhiteSpace( file ) ) continue;
if ( file.Contains( "_lod0" ) ) continue;
if ( file.Contains( "clothes" ) ) continue;
Canvas.AddItem( file.Remove( file.Length - 6 ) );
}
}
}

211
code/weapons/Flashlight.cs Normal file
View File

@@ -0,0 +1,211 @@
using Sandbox;
[Library( "weapon_flashlight", Title = "Flashlight", Spawnable = true )]
partial class Flashlight : Weapon
{
public override string ViewModelPath => "weapons/rust_flashlight/v_rust_flashlight.vmdl";
public override float SecondaryRate => 2.0f;
protected virtual Vector3 LightOffset => Vector3.Forward * 10;
private SpotLightEntity worldLight;
private SpotLightEntity viewLight;
[Net, Local, Predicted]
private bool LightEnabled { get; set; } = true;
TimeSince timeSinceLightToggled;
public override void Spawn()
{
base.Spawn();
SetModel( "weapons/rust_pistol/rust_pistol.vmdl" );
worldLight = CreateLight();
worldLight.SetParent( this, "slide", new Transform( LightOffset ) );
worldLight.EnableHideInFirstPerson = true;
worldLight.Enabled = false;
}
public override void CreateViewModel()
{
base.CreateViewModel();
viewLight = CreateLight();
viewLight.SetParent( ViewModelEntity, "light", new Transform( LightOffset ) );
viewLight.EnableViewmodelRendering = true;
viewLight.Enabled = LightEnabled;
}
private SpotLightEntity CreateLight()
{
var light = new SpotLightEntity
{
Enabled = true,
DynamicShadows = true,
Range = 512,
Falloff = 1.0f,
LinearAttenuation = 0.0f,
QuadraticAttenuation = 1.0f,
Brightness = 2,
Color = Color.White,
InnerConeAngle = 20,
OuterConeAngle = 40,
FogStength = 1.0f,
Owner = Owner,
};
light.UseFog();
return light;
}
public override void Simulate( Client cl )
{
if ( cl == null )
return;
base.Simulate( cl );
bool toggle = Input.Pressed( InputButton.Flashlight ) || Input.Pressed( InputButton.Attack1 );
if ( timeSinceLightToggled > 0.1f && toggle )
{
LightEnabled = !LightEnabled;
PlaySound( LightEnabled ? "flashlight-on" : "flashlight-off" );
if ( worldLight.IsValid() )
{
worldLight.Enabled = LightEnabled;
}
if ( viewLight.IsValid() )
{
viewLight.Enabled = LightEnabled;
}
timeSinceLightToggled = 0;
}
}
public override bool CanReload()
{
return false;
}
public override void AttackSecondary()
{
if ( MeleeAttack() )
{
OnMeleeHit();
}
else
{
OnMeleeMiss();
}
PlaySound( "rust_flashlight.attack" );
}
private bool MeleeAttack()
{
var forward = Owner.EyeRot.Forward;
forward = forward.Normal;
bool hit = false;
foreach ( var tr in TraceBullet( Owner.EyePos, Owner.EyePos + forward * 80, 20.0f ) )
{
if ( !tr.Entity.IsValid() ) continue;
tr.Surface.DoBulletImpact( tr );
hit = true;
if ( !IsServer ) continue;
using ( Prediction.Off() )
{
var damageInfo = DamageInfo.FromBullet( tr.EndPos, forward * 100, 25 )
.UsingTraceResult( tr )
.WithAttacker( Owner )
.WithWeapon( this );
tr.Entity.TakeDamage( damageInfo );
}
}
return hit;
}
[ClientRpc]
private void OnMeleeMiss()
{
Host.AssertClient();
if ( IsLocalPawn )
{
_ = new Sandbox.ScreenShake.Perlin();
}
ViewModelEntity?.SetAnimBool( "attack", true );
}
[ClientRpc]
private void OnMeleeHit()
{
Host.AssertClient();
if ( IsLocalPawn )
{
_ = new Sandbox.ScreenShake.Perlin( 1.0f, 1.0f, 3.0f );
}
ViewModelEntity?.SetAnimBool( "attack_hit", true );
}
private void Activate()
{
if ( worldLight.IsValid() )
{
worldLight.Enabled = LightEnabled;
}
}
private void Deactivate()
{
if ( worldLight.IsValid() )
{
worldLight.Enabled = false;
}
}
public override void ActiveStart( Entity ent )
{
base.ActiveStart( ent );
if ( IsServer )
{
Activate();
}
}
public override void ActiveEnd( Entity ent, bool dropped )
{
base.ActiveEnd( ent, dropped );
if ( IsServer )
{
if ( dropped )
{
Activate();
}
else
{
Deactivate();
}
}
}
}

62
code/weapons/Pistol.cs Normal file
View File

@@ -0,0 +1,62 @@
using Sandbox;
[Library( "weapon_pistol", Title = "Pistol", Spawnable = true )]
partial class Pistol : Weapon
{
public override string ViewModelPath => "weapons/rust_pistol/v_rust_pistol.vmdl";
public override float PrimaryRate => 15.0f;
public override float SecondaryRate => 1.0f;
public TimeSince TimeSinceDischarge { get; set; }
public override void Spawn()
{
base.Spawn();
SetModel( "weapons/rust_pistol/rust_pistol.vmdl" );
}
public override bool CanPrimaryAttack()
{
return base.CanPrimaryAttack() && Input.Pressed( InputButton.Attack1 );
}
public override void AttackPrimary()
{
TimeSincePrimaryAttack = 0;
TimeSinceSecondaryAttack = 0;
(Owner as AnimEntity)?.SetAnimBool( "b_attack", true );
ShootEffects();
PlaySound( "rust_pistol.shoot" );
ShootBullet( 0.05f, 1.5f, 9.0f, 3.0f );
}
private void Discharge()
{
if ( TimeSinceDischarge < 0.5f )
return;
TimeSinceDischarge = 0;
var muzzle = GetAttachment( "muzzle" ) ?? default;
var pos = muzzle.Position;
var rot = muzzle.Rotation;
ShootEffects();
PlaySound( "rust_pistol.shoot" );
ShootBullet( pos, rot.Forward, 0.05f, 1.5f, 9.0f, 3.0f );
ApplyAbsoluteImpulse( rot.Backward * 200.0f );
}
protected override void OnPhysicsCollision( CollisionEventData eventData )
{
if ( eventData.Speed > 500.0f )
{
Discharge();
}
}
}

66
code/weapons/SMG.cs Normal file
View File

@@ -0,0 +1,66 @@
using Sandbox;
[Library( "weapon_smg", Title = "SMG", Spawnable = true )]
partial class SMG : Weapon
{
public override string ViewModelPath => "weapons/rust_smg/v_rust_smg.vmdl";
public override float PrimaryRate => 15.0f;
public override float SecondaryRate => 1.0f;
public override float ReloadTime => 5.0f;
public override void Spawn()
{
base.Spawn();
SetModel( "weapons/rust_smg/rust_smg.vmdl" );
}
public override void AttackPrimary()
{
TimeSincePrimaryAttack = 0;
TimeSinceSecondaryAttack = 0;
(Owner as AnimEntity)?.SetAnimBool( "b_attack", true );
//
// Tell the clients to play the shoot effects
//
ShootEffects();
PlaySound( "rust_smg.shoot" );
//
// Shoot the bullets
//
ShootBullet( 0.1f, 1.5f, 5.0f, 3.0f );
}
public override void AttackSecondary()
{
// Grenade lob
}
[ClientRpc]
protected override void ShootEffects()
{
Host.AssertClient();
Particles.Create( "particles/pistol_muzzleflash.vpcf", EffectEntity, "muzzle" );
Particles.Create( "particles/pistol_ejectbrass.vpcf", EffectEntity, "ejection_point" );
if ( Owner == Local.Pawn )
{
new Sandbox.ScreenShake.Perlin( 0.5f, 4.0f, 1.0f, 0.5f );
}
ViewModelEntity?.SetAnimBool( "fire", true );
CrosshairPanel?.CreateEvent( "fire" );
}
public override void SimulateAnimator( PawnAnimator anim )
{
anim.SetParam( "holdtype", 2 ); // TODO this is shit
anim.SetParam( "aimat_weight", 1.0f );
}
}

111
code/weapons/Shotgun.cs Normal file
View File

@@ -0,0 +1,111 @@
using Sandbox;
[Library( "weapon_shotgun", Title = "Shotgun", Spawnable = true )]
partial class Shotgun : Weapon
{
public override string ViewModelPath => "weapons/rust_pumpshotgun/v_rust_pumpshotgun.vmdl";
public override float PrimaryRate => 1;
public override float SecondaryRate => 1;
public override float ReloadTime => 0.5f;
public override void Spawn()
{
base.Spawn();
SetModel( "weapons/rust_pumpshotgun/rust_pumpshotgun.vmdl" );
}
public override void AttackPrimary()
{
TimeSincePrimaryAttack = 0;
TimeSinceSecondaryAttack = 0;
(Owner as AnimEntity)?.SetAnimBool( "b_attack", true );
//
// Tell the clients to play the shoot effects
//
ShootEffects();
PlaySound( "rust_pumpshotgun.shoot" );
//
// Shoot the bullets
//
ShootBullets( 10, 0.1f, 10.0f, 9.0f, 3.0f );
}
public override void AttackSecondary()
{
TimeSincePrimaryAttack = -0.5f;
TimeSinceSecondaryAttack = -0.5f;
(Owner as AnimEntity)?.SetAnimBool( "b_attack", true );
//
// Tell the clients to play the shoot effects
//
DoubleShootEffects();
PlaySound( "rust_pumpshotgun.shootdouble" );
//
// Shoot the bullets
//
ShootBullets( 20, 0.4f, 20.0f, 8.0f, 3.0f );
}
[ClientRpc]
protected override void ShootEffects()
{
Host.AssertClient();
Particles.Create( "particles/pistol_muzzleflash.vpcf", EffectEntity, "muzzle" );
Particles.Create( "particles/pistol_ejectbrass.vpcf", EffectEntity, "ejection_point" );
ViewModelEntity?.SetAnimBool( "fire", true );
if ( IsLocalPawn )
{
new Sandbox.ScreenShake.Perlin( 1.0f, 1.5f, 2.0f );
}
CrosshairPanel?.CreateEvent( "fire" );
}
[ClientRpc]
protected virtual void DoubleShootEffects()
{
Host.AssertClient();
Particles.Create( "particles/pistol_muzzleflash.vpcf", EffectEntity, "muzzle" );
ViewModelEntity?.SetAnimBool( "fire_double", true );
CrosshairPanel?.CreateEvent( "fire" );
if ( IsLocalPawn )
{
new Sandbox.ScreenShake.Perlin( 3.0f, 3.0f, 3.0f );
}
}
public override void OnReloadFinish()
{
IsReloading = false;
TimeSincePrimaryAttack = 0;
TimeSinceSecondaryAttack = 0;
FinishReload();
}
[ClientRpc]
protected virtual void FinishReload()
{
ViewModelEntity?.SetAnimBool( "reload_finished", true );
}
public override void SimulateAnimator( PawnAnimator anim )
{
anim.SetParam( "holdtype", 3 ); // TODO this is shit
anim.SetParam( "aimat_weight", 1.0f );
}
}