Player gun control component. Handles aiming, shooting hitscan and rockets, spawning effects and decals, damaging enemy AI and ragdoll impulses, playing sounds, showing UI overlay and handling death/leaderboard callbacks.
using Sandbox;
using Sandbox.Audio;
using Sandbox.Modals;
using Sandbox.ModelEditor.Nodes;
using System;
using System.Diagnostics;
using System.Net.Quic;
using System.Numerics;
using System.Threading.Tasks;
using static HealthSystem;
using static LeaderboardDisplay;
using static Sandbox.ModelPhysics;
public sealed class GunControl : Component, HealthSystem.IHealthEvent, LeaderboardDisplay.ILeaderboardEvent
{
SceneLoader SceneLoader;
HighscoreManager HighscoreManager;
[Property] Rigidbody CarBody;
[Property] GameObject Turret;
[Property] GameObject Muzzle;
[Property] CameraComponent MainCamera;
[Property] GameObject Crosshair;
[Property] BeamEffect ShootBeam;
[Property] BeamEffect LaserBeam;
[Property] GameObject BulletHole;
[Property] GameObject BulletSpark;
[Property] GameObject BulletSparkEnemy;
[Property] GameObject BulletSparkEnemyGhost;
[Property] SpriteRenderer MuzzleFlashSprite;
[Property] ParticleEffect MuzzleFlashEffect;
[Property] ParticleSphereEmitter MuzzleFlashEmitter;
[Property] float aimRange = 15000f;
[Property] float aimWidth = 2000f;
[Property] float ShootCooldown = 0.2f;
[Property] float Inaccuracy = 0.015f;
TimeUntil NextShot;
[Property] GameObject Rocket;
[Property] float RocketSpeed = 10000f;
[Property] public float StartRockets = 15;
public float CurrentRockets;
SceneTraceResult SightlineTrace;
bool IsAiming;
public bool IsTargetingEnemy;
public bool IsHitmarkerActive = false;
public bool HasShot = false;
GameObject newBulletHole;
GameObject newBulletSpark;
GameObject newBulletSparkEnemy;
public TimeSince SinceShot = 10;
TimeSince SinceHit = 10;
TimeSince SinceAim;
TimeSince SinceGunAnimationStart;
Vector3 originalGunScale;
Game.Overlay overlay = new Game.Overlay();
Random random = new Random();
void IHealthEvent.OnDeath()
{
Log.Info( "PLAYER DIED" );
Sound.Play( "sounds/player-dead-8bit.sound", CarBody.WorldPosition );
HighscoreManager.WriteToLeaderboard();
// HighscoreManager.ResetScore();
DeathVignette();
Scene.TimeScale = 0.3f;
SceneLoader.SceneLoadOptions.SetScene( SceneLoader.LobbyScene );
SceneLoader.StartCountdown( 0, 5 );
}
void ILeaderboardEvent.OnGlobalHit()
{
// Nothing to do
}
void ILeaderboardEvent.OnFriendsHit()
{
// Nothing to do
}
void ILeaderboardEvent.OnCenterMeHit()
{
// Nothing to do
}
protected override void OnStart()
{
SceneLoader = Scene.Get<SceneLoader>();
HighscoreManager = Scene.Get<HighscoreManager>();
originalGunScale = Turret.WorldScale;
MuzzleFlashSprite.Enabled = false;
CurrentRockets = StartRockets;
}
protected override void OnUpdate()
{
// Wo man hinaimed
Ray TurretRay = new Ray( MainCamera.WorldPosition + MainCamera.WorldRotation.Forward * 250, MainCamera.WorldRotation.Forward );
SightlineTrace = Scene.Trace.Sphere( 25f, TurretRay, MainCamera.ZFar )
.WithoutTags("player", "dead")
.IgnoreGameObjectHierarchy( GameObject )
.Run();
// DebugOverlay.Trace( SightlineTrace );
if ( SightlineTrace.Hit && SightlineTrace.GameObject.Tags.Has("enemy") && !SightlineTrace.GameObject.Tags.Has("dead"))
{
IsTargetingEnemy = true;
}
else
{
IsTargetingEnemy = false;
}
if ( SinceHit < 0.2f )
{
IsHitmarkerActive = true;
}
else
{
IsHitmarkerActive = false;
}
// Crosshair.WorldPosition = MainCamera.WorldPosition + MainCamera.WorldRotation.Forward * aimRange / 10;
LaserBeam.TargetPosition = SightlineTrace.EndPosition;
// Turret Yaw mit Camera Yaw mitdrehen
Turret.WorldRotation = Rotation.LookAt( MainCamera.WorldRotation.Backward, CarBody.WorldRotation.Up );
if ( Input.Down( "attack1" ) && NextShot )
{
// Log.Info( "Shooting" );
Sound.Play( "sounds/bullet-ricochet.sound", Muzzle.WorldPosition );
MuzzleFlashEmitter.Emit( MuzzleFlashEffect );
FlashMuzzleSprite( 0.05f );
AnimateGun( 0.03f);
SinceShot = 0;
if ( SightlineTrace.Hit )
{
if ( SightlineTrace.GameObject.Tags.Has( "button-global" ) )
{
ILeaderboardEvent.PostToGameObject( SightlineTrace.GameObject.Parent, x => x.OnGlobalHit() );
}
else if ( SightlineTrace.GameObject.Tags.Has( "button-friends" ) )
{
ILeaderboardEvent.PostToGameObject( SightlineTrace.GameObject.Parent, x => x.OnFriendsHit() );
}
else if ( SightlineTrace.GameObject.Tags.Has( "button-centerme" ) )
{
ILeaderboardEvent.PostToGameObject( SightlineTrace.GameObject.Parent, x => x.OnCenterMeHit() );
}
newBulletHole = BulletHole.Clone( SightlineTrace.HitPosition, Rotation.LookAt( SightlineTrace.Normal, Vector3.Up ) );
if ( SightlineTrace.GameObject.IsValid()) newBulletHole.SetParent( SightlineTrace.GameObject );
if ( SightlineTrace.GameObject.Tags.Has( "enemy" ) && !SightlineTrace.GameObject.Tags.Has("dead") )
{
SinceHit = 0;
if ( SightlineTrace.GameObject.GetComponent<HealthSystem>().IsValid() ) SightlineTrace.GameObject.GetComponent<HealthSystem>().Damage( 50f );
if ( SightlineTrace.GameObject.GetComponent<ZombieBrain>() != null )
{
newBulletSparkEnemy = BulletSparkEnemy.Clone( SightlineTrace.HitPosition, Rotation.LookAt( SightlineTrace.Normal, Vector3.Up ) );
SightlineTrace.GameObject.GetComponent<ZombieBrain>().CurrentState = ZombieState.Staggered;
SightlineTrace.GameObject.GetComponent<ZombieBrain>().KnockBack = Math.Max( 0.1f, SightlineTrace.GameObject.GetComponent<ZombieBrain>().KnockBack + 0.1f );
SightlineTrace.GameObject.GetComponent<ZombieBrain>().AnimateHit();
}
else if ( SightlineTrace.GameObject.GetComponent<VampireBrain>() != null )
{
newBulletSparkEnemy = BulletSparkEnemy.Clone( SightlineTrace.HitPosition, Rotation.LookAt( SightlineTrace.Normal, Vector3.Up ) );
SightlineTrace.GameObject.GetComponent<VampireBrain>().CurrentState = VampireState.Staggered;
SightlineTrace.GameObject.GetComponent<VampireBrain>().UntilKnockBack = Math.Max( 0.1f, SightlineTrace.GameObject.GetComponent<VampireBrain>().UntilKnockBack + 0.1f );
SightlineTrace.GameObject.WorldRotation = SightlineTrace.GameObject.WorldRotation.Angles().WithYaw( SightlineTrace.GameObject.WorldRotation.Yaw() + random.Float( -10, 10 ) );
SightlineTrace.GameObject.GetComponent<VampireBrain>().TargetPosition += SightlineTrace.Direction * 200;
SightlineTrace.GameObject.GetComponent<VampireBrain>().AnimateHit();
// Rotation?
}
else if ( SightlineTrace.GameObject.GetComponent<GhostBrain>() != null )
{
newBulletSparkEnemy = BulletSparkEnemyGhost.Clone( SightlineTrace.HitPosition, Rotation.LookAt( SightlineTrace.Normal, Vector3.Up ) );
SightlineTrace.GameObject.GetComponent<GhostBrain>().CurrentState = GhostState.Staggered;
SightlineTrace.GameObject.GetComponent<GhostBrain>().UntilKnockBack = Math.Max( 0f, SightlineTrace.GameObject.GetComponent<GhostBrain>().UntilKnockBack + 0f ); // 0f = disabled
SightlineTrace.GameObject.GetComponent<GhostBrain>().AnimateHit();
// ShootTrace.GameObject.GetComponent<GhostBrain>().TargetPosition += ShootTrace.Direction * 200;
// Rotation?
}
if ( SightlineTrace.GameObject.GetComponent<Rigidbody>() != null )
{
Rigidbody rb = SightlineTrace.GameObject.GetComponent<Rigidbody>();
rb.GravityScale = 1;
rb.SmoothRotate( rb.WorldRotation.Angles().WithPitch( random.FromArray<int>( new int[] { -10, -15 } ) )
.WithYaw( rb.WorldRotation.Yaw() + random.FromArray<int>( new int[] { -15, 15 } ) ), 0.001f, 1f );
// ShootTrace.GameObject.GetComponent<Rigidbody>().ApplyTorque( ShootTrace.GameObject.WorldRotation.Up * 100000 * ShootTrace.GameObject.GetComponent<Rigidbody>().Mass );
SightlineTrace.GameObject.GetComponent<Rigidbody>().ApplyImpulse( (SightlineTrace.Direction + Vector3.Up) * 100 * SightlineTrace.GameObject.GetComponent<Rigidbody>().Mass );
}
// Partikel und Sound und bullethole für Gegner
newBulletHole.GetComponent<Decal>().ColorTint = Color.Red * random.Float(3, 5); newBulletHole.WorldScale = (newBulletHole.WorldScale * 3).WithX( 4 );
Sound.Play( "sounds/bullet-impact-flesh.sound", SightlineTrace.HitPosition );
}
else
{
// normaler Partikel
newBulletSpark = BulletSpark.Clone( SightlineTrace.HitPosition, Rotation.LookAt( SightlineTrace.Normal, Vector3.Up ) );
}
}
ShootBeam.TargetPosition = SightlineTrace.EndPosition;
ShootBeam.SpawnBeam();
NextShot = ShootCooldown;
}
if ( Input.Pressed( "attack2" ) )
{
// Log.Info( "Rocket" );
// Rocket
LaunchRocket();
}
if ( overlay.IsOpen )
{
Scene.TimeScale = 0;
Mixer.Master.Volume = 0f;
}
else if ( GetComponent<HealthSystem>().CurrentHealth > 0 && Scene.TimeScale == 0 )
{
Scene.TimeScale = 1;
Mixer.Master.Volume = 1.0f;
}
}
async Task FlashMuzzleSprite(float time)
{
MuzzleFlashSprite.WorldRotation = MuzzleFlashSprite.WorldRotation.Angles().WithRoll( random.Int(-45, 45) );
MuzzleFlashSprite.Enabled = true;
await Task.DelayRealtimeSeconds( time );
MuzzleFlashSprite.Enabled = false;
}
async Task DeathVignette()
{
while ( MainCamera.GetComponent<Vignette>().Intensity < 2f )
{
MainCamera.GetComponent<Vignette>().Intensity += Time.Delta * 3f;
await Task.FrameEnd();
}
}
void LaunchRocket()
{
if ( CurrentRockets <= 0 ) return; // Sound
AnimateGun( 0.3f );
Sound.Play( "sounds/grenadelauncher.sound", Muzzle.WorldPosition );
GameObject newRocket = Rocket.Clone( Muzzle.WorldPosition, Rotation.LookAt( MainCamera.WorldRotation.Forward, Vector3.Up ) );
newRocket.GetComponentInChildren<Rigidbody>().Velocity = (SightlineTrace.EndPosition - Muzzle.WorldPosition).Normal * RocketSpeed;
CurrentRockets--;
}
public async Task AnimateGun(float strength)
{
float duration = 0.1f;
SinceGunAnimationStart = 0;
Turret.WorldScale = originalGunScale.z * (1f + strength);
while ( SinceGunAnimationStart < duration )
{
Turret.WorldScale = Turret.WorldScale.z.LerpTo( originalGunScale.z, 0.1f );
await Task.FrameEnd();
}
if ( !this.IsValid() ) return;
Turret.WorldScale = originalGunScale;
}
/*
void Ramming()
{
if (CarBody.Velocity.WithZ(0).Length > 500)
{
Capsule ramCapsule = new Capsule( CarBody.WorldPosition + CarBody.WorldRotation.Up * 40 + ( CarBody.WorldRotation.Backward + CarBody.WorldRotation.Left) * 80,
CarBody.WorldPosition + CarBody.WorldRotation.Up * 40 + ( CarBody.WorldRotation.Backward + CarBody.WorldRotation.Right ) * 80, 50);
DebugOverlay.Capsule( ramCapsule, Color.Red );
var ramTrace = Scene.Trace.Capsule( ramCapsule )
.IgnoreGameObjectHierarchy( GameObject )
.WithTag( "enemy" )
.RunAll();
foreach ( var hit in ramTrace )
{
if ( hit.GameObject.GetComponent<HealthSystem>() != null )
{
hit.GameObject.GetComponent<HealthSystem>().Damage( 10 );
Sound.Play( "sounds/bullet-impact-flesh.sound", hit.HitPosition );
}
}
}
}*/
}