mirror of
https://github.com/celisej567/gm_construct_port.git
synced 2026-09-13 20:12:39 +03:00
Add files via upload
This commit is contained in:
31
code/Carriable.cs
Normal file
31
code/Carriable.cs
Normal 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
97
code/Game.cs
Normal 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
50
code/Inventory.cs
Normal 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
113
code/Player.Clothes.cs
Normal 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
85
code/Player.Ragdoll.cs
Normal 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
35
code/Player.Use.cs
Normal 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
240
code/Player.cs
Normal 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
39
code/PreviewEntity.cs
Normal 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
14
code/Tool.Effects.cs
Normal 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
92
code/Tool.Preview.cs
Normal 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
124
code/Tool.cs
Normal 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
102
code/ViewModel.cs
Normal 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
202
code/Weapon.cs
Normal 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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
37
code/entities/BalloonEntity.cs
Normal file
37
code/entities/BalloonEntity.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
43
code/entities/BouncyBall.cs
Normal file
43
code/entities/BouncyBall.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
64
code/entities/DirectionalGravity.cs
Normal file
64
code/entities/DirectionalGravity.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
146
code/entities/DroneEntity.cs
Normal file
146
code/entities/DroneEntity.cs
Normal 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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
33
code/entities/LampEntity.cs
Normal file
33
code/entities/LampEntity.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
33
code/entities/LightEntity.cs
Normal file
33
code/entities/LightEntity.cs
Normal 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
103
code/entities/NoiseTest.cs
Normal 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 ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
46
code/entities/ThrusterEntity.Effects.cs
Normal file
46
code/entities/ThrusterEntity.Effects.cs
Normal 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 );
|
||||
}
|
||||
}
|
||||
50
code/entities/ThrusterEntity.cs
Normal file
50
code/entities/ThrusterEntity.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
25
code/entities/WheelEntity.cs
Normal file
25
code/entities/WheelEntity.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
34
code/entities/car/CarAnimator.cs
Normal file
34
code/entities/car/CarAnimator.cs
Normal 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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
228
code/entities/car/CarCamera.cs
Normal file
228
code/entities/car/CarCamera.cs
Normal 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 );
|
||||
}
|
||||
}
|
||||
|
||||
37
code/entities/car/CarController.cs
Normal file
37
code/entities/car/CarController.cs
Normal 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" );
|
||||
}
|
||||
}
|
||||
561
code/entities/car/CarEntity.cs
Normal file
561
code/entities/car/CarEntity.cs
Normal 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 ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
75
code/entities/car/CarWheel.cs
Normal file
75
code/entities/car/CarWheel.cs
Normal 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
123
code/tools/Balloon.cs
Normal 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
38
code/tools/BoxShooter.cs
Normal 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
38
code/tools/Color.cs
Normal 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
280
code/tools/GravGun.cs
Normal 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
83
code/tools/Lamp.cs
Normal 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
57
code/tools/LeafBlower.cs
Normal 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
116
code/tools/Light.cs
Normal 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();
|
||||
} );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
124
code/tools/PhysGun.Effects.cs
Normal file
124
code/tools/PhysGun.Effects.cs
Normal 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
419
code/tools/PhysGun.cs
Normal 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
42
code/tools/Remover.cs
Normal 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
54
code/tools/Resizer.cs
Normal 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
122
code/tools/Rope.cs
Normal 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
89
code/tools/Thruster.cs
Normal 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
96
code/tools/Weld.cs
Normal 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
83
code/tools/Wheel.cs
Normal 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
41
code/ui/CurrentTool.cs
Normal 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
21
code/ui/Health.cs
Normal 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
103
code/ui/InventoryBar.cs
Normal 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
24
code/ui/InventoryIcon.cs
Normal 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
25
code/ui/SandboxHud.cs
Normal 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
135
code/ui/SandboxHud.scss
Normal 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
78
code/ui/SpawnMenu.cs
Normal 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
177
code/ui/SpawnMenu.scss
Normal 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 {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
38
code/ui/left/EntityList.cs
Normal file
38
code/ui/left/EntityList.cs
Normal 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
37
code/ui/left/SpawnList.cs
Normal 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
211
code/weapons/Flashlight.cs
Normal 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
62
code/weapons/Pistol.cs
Normal 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
66
code/weapons/SMG.cs
Normal 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
111
code/weapons/Shotgun.cs
Normal 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 );
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user