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().Any( x => x?.HeldBody?.PhysicsGroup == body?.PhysicsGroup ) ) return true; if ( All.OfType().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(); } }