🔍 s&box Package Code Search

Search C# source code, UI razor templates, shaders, and configs across s&box packages.

Showing code results for query: * (154 total matches found)
facepunch.sbdm / Items/Pickups/ArmourPickup.cs
Game game
/// <summary>
/// A pickup that gives the player some armour.
/// </summary>
public sealed class ArmourPickup : BasePickup
{
	/// <summary>
	/// How much armour to give to the player
	/// </summary>
	[Property, Group( "Armour" )] float ArmourGive { get; set; } = 0;

	public override bool CanPickup( Player player, PlayerInventory inventory )
	{
		if ( player.Armour >= player.MaxArmour && ArmourGive > 0 )
			return false;

		return true;
	}

	protected override bool OnPickup( Player player, PlayerInventory inventory )
	{
		player.Armour = (player.Armour + ArmourGive).Clamp( 0, player.MaxArmour );
		player.PlayerData.AddStat( $"pickup.armor" );

		return true;
	}
}
facepunch.sbdm / Map/BaseToggle.cs
Game game
public abstract class BaseToggle : Component
{
	public delegate Task StateChangedDelegate( bool state );

	/// <summary>
	/// The toggle state has changed
	/// </summary>
	[Property] public StateChangedDelegate OnStateChanged { get; set; }

	bool _state;

	[Property, Sync]
	public bool State
	{
		get => _state;
		set
		{
			if ( _state == value ) return;

			_state = value;
			StateHasChanged( _state );

		}
	}

	/// <summary>
	/// The toggle state has changed
	/// </summary>
	protected virtual void StateHasChanged( bool newState )
	{
		OnStateChanged?.Invoke( _state );
	}
}
facepunch.sbdm / Player/GrabAction.cs
Game game
/// <summary>
/// Put this component on something that is pressable, and the player will grab it with a certain style animation.
/// </summary>
public partial class GrabAction : Component
{
	public enum GrabStyle
	{
		None,
		SweepDown,
		SweepRight,
		SweepLeft,
		PressButton
	}

	[Property]
	public GrabStyle Style { get; set; } = GrabStyle.SweepDown;
}
facepunch.sbdm / UI/Inventory.razor
Game game
@using Sandbox;
@using Sandbox.UI;
@inherits PanelComponent
@implements ILocalPlayerEvent

<root>
	@for (int i = 0; i < 5; i++)
	{
        @if ( ItemsInSlot(i).Count() < 1 )
        {
            continue;
        }

        <InventorySlot Index=@i Inventory=@inventory Hovered=@hovered Active=@active></InventorySlot>
	}
</root>

@code
{
    [Property] public SoundEvent SwitchSound { get; set; }
    [Property] public SoundEvent SelectSound { get; set; }
    [Property] public SoundEvent CancelSound { get; set; }

    PlayerInventory inventory => Player.Local.IsValid() ? Player.Local.Inventory : null;
    Carryable hovered;
    Carryable active;
    Carryable prev;

    protected override int BuildHash() => HashCode.Combine(inventory, hovered, active);

    IEnumerable<Carryable> ItemsInSlot( int slot )
    {
        return inventory.IsValid() ? inventory.Carryables.Where(x => x.PreferredSlot == slot) : [];
    }

    void ILocalPlayerEvent.OnPickup( BaseInventoryItem weapon )
    {
        StateHasChanged();
    }

    protected override void OnUpdate()
    {
        DoInventoryInput();
	}

	RealTimeSince timeSinceInteraction;

	void DoInventoryInput()
	{
		if (inventory is null)
			return;

		if (GameManager.Current.CurrentGameStage != GameManager.GameStage.Game)
		{
			if (hovered is not null)
			{
				hovered = null;
				active = null;
				prev = null;
			}

			return;
		}

		MoveSlot(-(int)Input.MouseWheel.y);

		if ( Input.Pressed( "Menu" ) && prev.IsValid() )
		{
			var weapon = prev;
            prev = inventory.Current;
			inventory.SwitchWeapon( weapon );
		}

		if (Input.Pressed("SlotNext")) MoveSlot(1);
		if (Input.Pressed("SlotPrev")) MoveSlot(-1);

		if (Input.Pressed("Slot1")) IterateSlot(0);
		if (Input.Pressed("Slot2")) IterateSlot(1);
		if (Input.Pressed("Slot3")) IterateSlot(2);
		if (Input.Pressed("Slot4")) IterateSlot(3);
		if (Input.Pressed("Slot5")) IterateSlot(4);

		if (hovered is null)
			return;

		if (Input.Pressed("Attack1"))
		{
			Input.ReleaseAction("Attack1");
			Input.SetAction("Attack1", false);

			prev = inventory.Current;
			inventory.SwitchWeapon(hovered);
			active = hovered;
			hovered = null;
			Sound.Play(SelectSound);
		}

		if (Input.Pressed("Attack2") || timeSinceInteraction > 2f)
		{
			Input.ReleaseAction("Attack2");
			Input.SetAction("Attack2", false);
			active = null;
			hovered = null;
			Sound.Play(CancelSound);
		}
	}

	public void MoveSlot( int delta )
	{
		if ( delta == 0 )
			return;

		var weapons = inventory.Carryables.Where( x => x.CanSwitch() ).ToList();

		if ( weapons.Count == 0 )
			return;

        var currentHover = hovered ?? active ?? inventory.Current ?? weapons.FirstOrDefault();

		int currentIndex = weapons.IndexOf( currentHover );

		currentIndex += delta;
		currentIndex %= weapons.Count;
		if ( currentIndex < 0 )
			currentIndex = weapons.Count + currentIndex;

		active = null;
		if ( GamePreferences.FastSwitch )
		{
			inventory.SwitchWeapon( weapons[currentIndex] );
		}
		else
		{
			hovered = weapons[currentIndex];
			timeSinceInteraction = 0;
		}

		Sound.Play( SwitchSound );
	}

	public void IterateSlot( int slot )
	{
		var slotWeapons = inventory.Carryables.Where( x => x.PreferredSlot == slot ).Where( x => x.CanSwitch() ).OrderBy( x => x.SlotOrder ).ToList();
		if ( slotWeapons.Count == 0 )
			return;

		// if inventory wasn't open, and current weapon is in a different slot, currentIndex will be -1 (incremented to 0), and first weapon in target slot will be selected
        var currentHover = hovered ?? active ?? inventory.Current ?? slotWeapons.FirstOrDefault();

		int currentIndex = slotWeapons.IndexOf( currentHover );

		currentIndex += 1;
		currentIndex %= slotWeapons.Count;

		if ( currentIndex < 0 )
			currentIndex = slotWeapons.Count + currentIndex;

		active = null;
		if ( GamePreferences.FastSwitch )
		{
			inventory.SwitchWeapon( slotWeapons[currentIndex] );
		}
		else
		{
			hovered = slotWeapons[currentIndex];
			timeSinceInteraction = 0;
		}

		Sound.Play( SwitchSound );
    }
}
facepunch.sbdm / Utility/CameraNoise/CameraNoiseSystem.cs
Game game
namespace Sandbox.CameraNoise;

public class CameraNoiseSystem : GameObjectSystem<CameraNoiseSystem>, ICameraSetup
{
	List<BaseCameraNoise> _all = new();

	public CameraNoiseSystem( Scene scene ) : base( scene )
	{
	}

	void ICameraSetup.PreSetup( Sandbox.CameraComponent cc )
	{
		foreach ( var effect in _all )
		{
			effect.Update();
		}

		_all.RemoveAll( x => x.IsDone );
	}

	void ICameraSetup.PostSetup( CameraComponent cc )
	{
		foreach ( var effect in _all )
		{
			effect.ModifyCamera( cc );
		}
	}

	public void Add( BaseCameraNoise noise )
	{
		_all.Add( noise );
	}
}

public abstract class BaseCameraNoise
{
	public float LifeTime { get; protected set; }
	public float CurrentTime { get; protected set; }
	public float Delta => CurrentTime.LerpInverse( 0, LifeTime, true );
	public float DeltaInverse => 1 - Delta;

	public BaseCameraNoise()
	{
		CameraNoiseSystem.Current.Add( this );
	}

	public virtual bool IsDone => CurrentTime > LifeTime;

	public virtual void Update()
	{
		CurrentTime += Time.Delta;
	}

	public virtual void ModifyCamera( CameraComponent cc ) { }
}
facepunch.sbdm / Weapons/CrossbowProjectile.cs
Game game
namespace Sandbox;

internal class CrossbowProjectile : Projectile, IKillIcon
{
	[Property]
	public Curve Curve { get; set; }

	[Property]
	public float ExplosiveDamage { get; set; } = 70f;

	[Property]
	public float Radius { get; set; } = 128.0f;

	[Property]
	Texture IKillIcon.DisplayIcon { get; set; }

	protected override void OnHit( Collision collision = default )
	{
		CreateEffects( collision.Contact.Point + (collision.Contact.Speed.Normal * 32) );

		// Direct hit, just kill the player
		if ( collision.Other.GameObject.GetComponentInParent<Player>() is Player player && player.IsValid() )
		{
			player.OnDamage( new DamageInfo( 200, Instigator?.Player?.GameObject, GameObject ) );
		}
		else
		{
			// Explode in radius
			Damage.Radius( collision.Contact.Point, Radius, ExplosiveDamage, [DamageTags.Explosion], GameObject, GameObject, Curve, Instigator );
		}

		GameObject.Destroy();
	}

	[Rpc.Broadcast( NetFlags.HostOnly )]
	private void CreateEffects( Vector3 position )
	{
		if ( Application.IsDedicatedServer ) return;

		Effects.SpawnExplosionSmall( position );
	}
}
facepunch.sbdm / Weapons/Item.cs
Game game
/// <summary>
/// Base class for a passive thing a player carries in their inventory and stores in a coffin - it is
/// never equipped. Anything that can be selected and held derives from <see cref="Carryable"/> instead.
/// </summary>
public abstract partial class Item : BaseInventoryItem
{
	/// <summary>
	/// The player that owns this item
	/// </summary>
	public Player Owner => GetComponentInParent<Player>( true );

	/// <summary>
	/// Passive items are never deployed, so the inventory must never select one.
	/// </summary>
	protected override bool OnCanSwitchTo() => false;

	/// <summary>
	/// Called when the owner dies
	/// </summary>
	public virtual void OnPlayerDeath( IPlayerEvent.DiedParams args )
	{
	}
}
facepunch.sbdm / ui/controls/color/coloralphacontrol.cs.scss
Game game
ColorAlphaControl
{
	gap: 0.5rem;
	flex-grow: 1;
	pointer-events: all;
	background: linear-gradient( to right, black, white );
	border-radius: 4px;
	padding: 2px;
	height: 12px;
	position: relative;
	cursor: pointer;
	border: 1px solid #333;

	&:hover
	{
		border: 1px solid #08f;
	}

	&:active
	{
		border: 1px solid #fff;
	}

	.handle
	{
		top: -5px;
		bottom: -5px;
		aspect-ratio: 1;
		border-radius: 100px;
		border: 2px solid #444;
		position: absolute;
		background-color: white;
		box-shadow: 2px 2px 16px #000a;
		transform: translateX( -50% );
		pointer-events: none;
	}
}
facepunch.sbdm / .obj/__compiler_extra.cs
Game game
global using static Sandbox.Internal.GlobalGameNamespace;
global using Microsoft.AspNetCore.Components;
global using Microsoft.AspNetCore.Components.Rendering;
[assembly: global::System.Reflection.AssemblyMetadata( "AddonTitle", "Deathmatch" )]
[assembly: global::System.Reflection.AssemblyMetadata( "AddonIdent", "sbdm" )]
[assembly: global::System.Reflection.AssemblyMetadata( "OrgIdent", "facepunch" )]
[assembly: global::System.Reflection.AssemblyMetadata( "Ident", "facepunch.sbdm" )]
[assembly: global::System.Reflection.AssemblyMetadata( "EngineVersion", "28" )]
[assembly: global::System.Reflection.AssemblyMetadata( "EngineMinorVersion", "1" )]

[assembly: System.Runtime.Versioning.TargetFramework( ".NETCoreApp,Version=v9.0", FrameworkDisplayName = ".NET 9.0" )]
[assembly: global::System.Reflection.AssemblyMetadata( "CompileTime", "2026-07-29T09:34:51.9024540Z" )]
[assembly: global::System.Reflection.AssemblyVersion("0.0.111.0")]
[assembly: global::System.Reflection.AssemblyFileVersion("0.0.111.0")]
facepunch.sbdm / AI/Rat.cs
Game game
/// <summary>
/// A rat NPC
/// </summary>
public sealed class Rat : Component, Component.IDamageable, IKillIcon, Component.ICollisionListener, IInstigator
{
	/// <summary>
	/// The <see cref="NavMeshAgent"/> which lets the rat traverse the navmesh
	/// </summary>
	[RequireComponent] public NavMeshAgent Agent { get; set; }

	/// <summary>
	/// The <see cref="Rigidbody"/> which we switch out to when jumping off of the navmesh
	/// </summary>
	[RequireComponent] public Rigidbody Rigidbody { get; set; }

	/// <summary>
	/// What should we spawn when the Rat dies?
	/// </summary>
	[Property] public GameObject DeathEffects { get; set; }

	/// <summary>
	/// What sound should we play when the Rat dies?
	/// </summary>
	[Property] public SoundEvent AttackSound { get; set; }

	/// <summary>
	/// The mouth of the rat, used for attack range
	/// </summary>
	[Property] public GameObject Mouth { get; set; }

	/// <summary>
	/// An icon to show on the kill feed when the rat kills someone
	/// </summary>
	[Property] Texture IKillIcon.DisplayIcon { get; set; }

	/// <summary>
	/// How far (sq units) away should the rat be before it starts attacking people?
	/// </summary>
	[Property, Feature( "Balance" )] public float AttackDistance { get; set; } = 50000f;

	/// <summary>
	/// How long until the rat implodes
	/// </summary>
	[Property, Feature( "Balance" )] public float Lifetime { get; set; } = 30f;

	/// <summary>
	/// How frequently should the rat choose a new roam location
	/// </summary>
	[Property, Feature( "Balance" )] public float RoamFrequency { get; set; } = 5f;

	/// <summary>
	/// How far away can the rat roam
	/// </summary>
	[Property, Feature( "Balance" )] public float RoamRadius { get; set; } = 1024f;

	/// <summary>
	/// Damage dealt when the rat bites a target
	/// </summary>
	[Property, Feature( "Balance" )] public float BiteDamage { get; set; } = 10f;

	/// <summary>
	/// Time (in seconds) between bite attacks
	/// </summary>
	[Property, Feature( "Balance" )] public float BiteCooldown { get; set; } = 1f;

	/// <summary>
	/// Time (in seconds) between lunge attacks
	/// </summary>
	[Property, Feature( "Balance" )] public float LungeCooldown { get; set; } = 3f;

	/// <summary>
	/// How often the rat thinks/updates its behavior (in seconds)
	/// </summary>
	[Property, Feature( "Balance" )] public float ThinkInterval { get; set; } = 0.1f;

	/// <summary>
	/// Distance squared for bite attack range
	/// </summary>
	[Property, Feature( "Balance" )] public float BiteRangeSquared { get; set; } = 5000f;

	/// <summary>
	/// Distance squared for target detection range
	/// </summary>
	[Property, Feature( "Balance" )] public float TargetDetectionRangeSquared { get; set; } = 262144f;

	/// <summary>
	/// Distance threshold for reaching roam target
	/// </summary>
	[Property, Feature( "Balance" )] public float RoamTargetThreshold { get; set; } = 32f;

	/// <summary>
	/// Velocity when lunging at target (X = horizontal, Y = vertical)
	/// </summary>
	[Property, Feature( "Balance" )] public Vector2 LungeVelocity { get; set; } = new Vector2( 512f, 256f );

	/// <summary>
	/// Velocity when thrown (X = horizontal, Y = vertical)
	/// </summary>
	[Property, Feature( "Balance" )] public Vector2 ThrowVelocity { get; set; } = new Vector2( 1024f, 50f );

	/// <summary>
	/// Initial think timer delay on start
	/// </summary>
	[Property, Feature( "Balance" )] public float InitialThinkDelay { get; set; } = 5f;

	/// <summary>
	/// Lunge timer when a new target is acquired
	/// </summary>
	[Property, Feature( "Balance" )] public float NewTargetLungeDelay { get; set; } = 1.5f;

	/// <summary>
	/// Height for roam area bounding box
	/// </summary>
	[Property, Feature( "Balance" )] public float RoamAreaHeight { get; set; } = 128f;

	/// <summary>
	/// Ground check distance (up and down)
	/// </summary>
	[Property, Feature( "Balance" )] public float GroundCheckDistance { get; set; } = 8f;

	/// <summary>
	/// Who's the rat's friend (the person who threw them.. not much of a friend are they)
	/// </summary>
	[Sync]
	public PlayerData Instigator { get; set; }

	/// <summary>
	/// Are we on the ground?
	/// </summary>
	bool IsOnGround = true;

	/// <summary>
	/// The current target of the rat
	/// </summary>
	Player target;

	/// <summary>
	/// How many seconds has it been since the rat bit someone?
	/// </summary>
	TimeSince TimeSinceBitten = 0;

	/// <summary>
	/// How many seconds has it been since we last LUNGED
	/// </summary>
	TimeSince LungeTimer = 0;

	/// <summary>
	/// How long has the rat been alive?
	/// </summary>
	TimeSince TimeSinceCreated = 0;

	/// <summary>
	/// Time since we picked a new target
	/// </summary>
	TimeSince ThinkTimer = 0;

	/// <summary>
	/// The roam target
	/// </summary>
	Vector3 RoamTarget;

	protected override void OnStart()
	{
		TimeSinceCreated = 0;
		ThinkTimer = InitialThinkDelay;
	}

	/// <summary>
	/// Trace down, see if we're hitting the ground
	/// </summary>
	/// <returns></returns>
	private void UpdateGrounded()
	{
		var tr = Scene.Trace.Ray( WorldPosition + Vector3.Up * GroundCheckDistance, WorldPosition + Vector3.Down * GroundCheckDistance )
			.IgnoreGameObjectHierarchy( GameObject.Root )
			.Run();

		IsOnGround = tr.Hit;
	}

	/// <summary>
	/// Throws a rat in a set direction
	/// </summary>
	/// <param name="rotation"></param>
	public void Throw( Rotation rotation )
	{
		var direction = rotation.Forward;
		Agent.UpdatePosition = false;
		WorldRotation = Rotation.LookAt( direction, Vector3.Up );
		Rigidbody.Velocity = WorldRotation.Forward * ThrowVelocity.x + Vector3.Up * ThrowVelocity.y;
	}

	/// <summary>
	/// Called when the rat either lifts off the ground, or lands
	/// </summary>
	void OnGroundedChanged( bool before, bool after )
	{
		Agent.SetAgentPosition( WorldPosition );
	}


	private bool hasLanded = false;
	void ICollisionListener.OnCollisionStart( Collision collision )
	{
		// if we're thrown somewhere, hit the ground running
		if ( !hasLanded )
		{
			Rigidbody.Velocity = Vector3.Zero;
			Agent.SetAgentPosition( WorldPosition );
			hasLanded = true;
		}
	}

	void Bite( Player target )
	{
		TimeSinceBitten = 0;

		DoAttackEffects();

		var dmg = new DamageInfo( BiteDamage, Instigator?.Player?.GameObject, GameObject );

		target.OnDamage( dmg );
	}

	protected override void OnUpdate()
	{
		if ( IsProxy )
			return;

		if ( TimeSinceCreated > Lifetime )
		{
			Die();
			return;
		}

		// We're lunging, and we have a target
		if ( !IsOnGround && target.IsValid() && TimeSinceBitten > BiteCooldown )
		{
			// Bite the target if they're close enough
			if ( target.WorldPosition.DistanceSquared( Mouth.WorldPosition ) < BiteRangeSquared )
				Bite( target );
		}

		var prevOnGround = IsOnGround;
		UpdateGrounded();

		if ( prevOnGround != IsOnGround )
			OnGroundedChanged( prevOnGround, IsOnGround );

		if ( !IsOnGround )
			return;

		if ( !Agent.Enabled )
			return;

		if ( target.IsValid() )
		{
			// If we haven't attacked in a while, move towards the enemy and try to jump them
			if ( ThinkTimer > ThinkInterval )
			{
				ThinkTimer = 0;
				if ( WorldPosition.DistanceSquared( target.WorldPosition ) < AttackDistance && LungeTimer > LungeCooldown )
				{
					Attack();
					return;
				}

				Agent.UpdatePosition = true;
				Agent.UpdateRotation = true;
				Agent.MoveTo( target.WorldPosition );
			}
		}
		else // roaming
		{
			if ( ThinkTimer > RoamFrequency || Vector3.DistanceBetween( WorldPosition, RoamTarget ) <= RoamTargetThreshold )
			{
				ThinkTimer = 0;

				var target = GetRoamPoint();
				if ( target.HasValue )
				{
					RoamTarget = target.Value;

					Agent.UpdatePosition = true;
					Agent.UpdateRotation = true;
					Agent.MoveTo( RoamTarget );
				}
			}

			LookForTarget();

			if ( target.IsValid() )
			{
				// attack soon if we've just got a target
				LungeTimer = NewTargetLungeDelay;
			}
		}
	}

	private Vector3? GetRoamPoint()
	{
		var roamSize = new Vector3( RoamRadius, RoamRadius, RoamAreaHeight );
		var bbox = BBox.FromPositionAndSize( WorldPosition, new Vector3( RoamRadius, RoamRadius, RoamAreaHeight ) );

		// vaguely prefer moving in the direction we're looking
		bbox += ((roamSize.WithZ( 0 ) * WorldRotation.Forward) * 0.4f);

		for ( int i = 0; i < 10; i++ )
		{
			var p = Scene.NavMesh.GetClosestPoint( bbox.RandomPointInside );
			if ( !p.HasValue || p.Value.Distance( WorldPosition ) > RoamRadius )
				continue;

			// check if navable
			var path = Scene.NavMesh.CalculatePath( new Sandbox.Navigation.CalculatePathRequest()
			{
				Start = WorldPosition,
				Target = p.Value
			} );

			if ( path.Status != Sandbox.Navigation.NavMeshPathStatus.Partial && path.Status != Sandbox.Navigation.NavMeshPathStatus.Complete )
				continue;

			return p.Value;
		}

		return default;
	}

	/// <summary>
	/// Looks for a target, closest non-friend player is the target
	/// </summary>
	private void LookForTarget( bool withRange = true )
	{
		var allPlayers = Scene.GetAllComponents<Player>()
			.Where( x => Instigator.IsValid() ? x.PlayerData != Instigator : true );

		if ( withRange )
			allPlayers = allPlayers.Where( x => x.WorldPosition.DistanceSquared( WorldPosition ) < TargetDetectionRangeSquared );

		allPlayers = allPlayers.OrderBy( x => x.WorldPosition.DistanceSquared( WorldPosition ) );

		if ( allPlayers.Any() ) target = allPlayers.First();
	}

	void Attack()
	{
		// wait the full attack length
		LungeTimer = 0;

		DoAttackEffects();

		// We want to take over the rat movement by enabling a rigidbody and just flinging them at a player
		Agent.UpdatePosition = false;
		Agent.UpdateRotation = false;

		var dir = (target.WorldPosition - WorldPosition).WithZ( 0 ).Normal;

		IsOnGround = false;
		Rigidbody.Velocity = (dir * LungeVelocity.x) + (Vector3.Up * LungeVelocity.y);
		WorldRotation = Rotation.LookAt( dir );

		target = null;
	}

	[Rpc.Broadcast]
	void DoDeathEffects()
	{
		if ( Application.IsDedicatedServer ) return;

		DeathEffects?.Clone( WorldPosition );
	}

	[Rpc.Broadcast]
	void DoAttackEffects()
	{
		if ( Application.IsDedicatedServer ) return;

		Sound.Play( AttackSound, WorldPosition );
	}

	void IDamageable.OnDamage( in DamageInfo damage )
	{
		Die();
	}

	void Die()
	{
		DoDeathEffects();
		GameObject.Destroy();
	}
}
facepunch.sbdm / Items/Pickups/InventoryPickup.cs
Game game
/// <summary>
/// A pickup that gives an inventory item, like a weapon
/// </summary>
public sealed class InventoryPickup : BasePickup
{
	/// <summary>
	/// A list of prefabs (that have to be inventory items) that are given to the player
	/// </summary>
	[Property, Group( "Inventory" )] public List<GameObject> Items { get; set; }

	protected override bool OnPickup( Player player, PlayerInventory inventory )
	{
		if ( Items == null ) return false;

		bool consumed = false;
		foreach ( var prefab in Items )
		{
			if ( inventory.Pickup( prefab ).IsValid() )
			{
				consumed = true;
				player.PlayerData.AddStat( $"pickup.inventory.{prefab.Name}" );
			}
		}

		return consumed;
	}
}
facepunch.sbdm / Map/TriggerTeleport.cs
Game game
public sealed class TriggerTeleport : Component, Component.ITriggerListener
{
	/// <summary>
	/// If not empty, the target must have one of these tags
	/// </summary>
	[Property, Group( "Target" )] public TagSet Include { get; set; } = new();

	/// <summary>
	/// If not empty, the target must not have one of these tags
	/// </summary>
	[Property, Group( "Target" )] public TagSet Exclude { get; set; } = new();

	[Property] public GameObject Target { get; set; }
	[Property] public Action<GameObject> OnTeleported { get; set; }

	protected override void DrawGizmos()
	{
		if ( !Target.IsValid() )
			return;

		Gizmo.Draw.Arrow( 0, WorldTransform.PointToLocal( Target.WorldPosition ) );
	}

	void ITriggerListener.OnTriggerEnter( Collider other )
	{
		var go = other.GameObject;

		if ( !IsValidTarget( ref go ) ) return;

		go.WorldPosition = Target.WorldPosition;
		go.Transform.ClearInterpolation();

		DoTeleportedEvent( go );
	}

	bool IsValidTarget( ref GameObject go )
	{
		go = go.Root;
		if ( go.IsProxy ) return false;

		if ( !Exclude.IsEmpty && go.Tags.HasAny( Exclude ) )
			return false;

		if ( !Include.IsEmpty && !go.Tags.HasAny( Include ) )
			return false;

		return true;
	}

	[Rpc.Broadcast]
	void DoTeleportedEvent( GameObject obj )
	{
		OnTeleported?.Invoke( obj );
	}
}
facepunch.sbdm / Player/PlayerObserver.cs
Game game
/// <summary>
/// Dead players become these. They try to observe their last corpse. 
/// </summary>
public sealed class PlayerObserver : Component
{
	Angles EyeAngles;
	TimeSince timeSinceStarted;

	protected override void OnEnabled()
	{
		base.OnEnabled();

		EyeAngles = Scene.Camera.WorldRotation;
		timeSinceStarted = 0;
	}

	protected override void OnUpdate()
	{
		if ( IsProxy ) return;

		var corpse = Scene.GetAllComponents<DeathCameraTarget>()
					.Where( x => x.Connection == Network.Owner )
					.OrderByDescending( x => x.Created )
					.FirstOrDefault();

		if ( corpse.IsValid() )
		{
			RotateAround( corpse );
		}

		// Don't allow immediate respawn
		if ( timeSinceStarted < 1 )
			return;

		// If pressed a button, or has been too long
		if ( Input.Pressed( "attack1" ) || Input.Pressed( "jump" ) || timeSinceStarted > 4f )
		{
			Respawn();
			GameObject.Destroy();
		}
	}

	[Rpc.Host( NetFlags.OwnerOnly | NetFlags.Reliable )]
	public void Respawn()
	{
		GameManager.Current.SpawnPlayer( Network.Owner );
		GameObject.Destroy();
	}

	private void RotateAround( Component target )
	{
		// Find the corpse eyes
		if ( !target.Components.Get<SkinnedModelRenderer>().TryGetBoneTransform( "head", out var tx ) )
		{
			tx.Position = target.GameObject.GetBounds().Center + Vector3.Up * 25f;
		}

		var e = EyeAngles;
		e += Input.AnalogLook;
		e.pitch = e.pitch.Clamp( -90, 90 );
		e.roll = 0.0f;
		EyeAngles = e;

		var center = tx.Position;
		var targetPos = center - EyeAngles.Forward * 150f;

		var tr = Scene.Trace.FromTo( center, targetPos ).Radius( 1.0f ).WithoutTags( "ragdoll", "effect" ).Run();

		Scene.Camera.WorldPosition = Vector3.Lerp( Scene.Camera.WorldPosition, tr.EndPosition, timeSinceStarted, true );
		Scene.Camera.WorldRotation = EyeAngles;
	}
}
facepunch.sbdm / UI/InventorySlot.razor
Game game
@using Sandbox;
@using Sandbox.UI;
@inherits Panel

@if ( GetWeapons().Count() < 1 )
{
    return;
}

<root>
    <div class="index">
        <label>@(Index+1)</label>
    </div>

    <div class="list">
    @foreach ( var weapon in GetWeapons() )
    {
        <InventoryWeapon Carryable="@weapon" CanSwitch=@weapon.CanSwitch() IsHovered="@(weapon == Hovered)" />
    }
    </div>

</root>

@code
{
    public PlayerInventory Inventory { get; set; }
    public Carryable Active { get; set; }
    public Carryable Hovered { get; set; }

    public int Index { get; set; }

    IEnumerable<Carryable> GetWeapons()
    {
        if (!Inventory.IsValid()) return Enumerable.Empty<Carryable>();

        return Inventory.Carryables.Where(x => x.PreferredSlot == Index).OrderBy(x => x.SlotOrder);
    }

    public override void Tick()
    {
        base.Tick();

        SetClass("active", Hovered?.PreferredSlot == Index || Active?.PreferredSlot == Index);
    }

	protected override int BuildHash() => HashCode.Combine( GetWeapons().Count() );

}
facepunch.sbdm / Weapons/BaseWeapon/BaseWeapon.cs
Game game
using Sandbox.Rendering;

/// <summary>
/// A <see cref="Carryable"/> that shoots. Magazines, reserve ammo, reloading and fire timing all live
/// on <see cref="BaseCombatWeapon"/> now - this keeps the deathmatch-facing names and the dry-fire /
/// auto-reload behaviour our weapons are written against.
/// </summary>
public partial class BaseWeapon : Carryable
{
	/// <summary>
	/// Rounds in the magazine. Read-only alias for the engine's <see cref="BaseCombatWeapon.Clip1"/>,
	/// which is host owned - spend it through <see cref="TakeAmmo(int)"/> so the host sees the spend,
	/// rather than assigning, which a client couldn't make stick.
	/// </summary>
	public int ClipContents => Clip1;

	/// <summary>
	/// Adds a delay, making it so we can't shoot for the specified time
	/// </summary>
	/// <param name="seconds"></param>
	public void AddShootDelay( float seconds )
	{
		SetNextFire( seconds );
	}

	/// <summary>
	/// The dry fire sound if we have no ammo
	/// </summary>
	private static SoundEvent DefaultDryFireSound = new SoundEvent( "audio/sounds/dry_fire.sound" );

	/// <summary>
	/// Play a dry fire sound. You should only call this on weapons that can't auto reload - if they can, use <see cref="TryAutoReload"/> instead.
	/// </summary>
	public override void DryFire()
	{
		if ( HasAmmo() )
			return;

		if ( IsReloading )
			return;

		if ( NextPrimaryFire > 0 )
			return;

		GameObject.PlaySound( DryFireSound ?? DefaultDryFireSound );
	}

	/// <summary>
	/// Player has fired an empty gun - play dry fire sound and start reloading. You should only call this on weapons that can reload - if they can't, use <see cref="DryFire"/> instead.
	/// </summary>
	public virtual void TryAutoReload()
	{
		if ( HasAmmo() )
			return;

		if ( IsReloading )
			return;

		if ( NextPrimaryFire > 0 )
			return;

		DryFire();

		AddShootDelay( 0.1f );

		if ( CanReload() )
			Reload();
		else
			SwitchAway();
	}

	/// <summary>
	/// Are we allowed to shoot this weapon? Can be overriden per-weapon
	/// </summary>
	/// <returns></returns>
	public virtual bool CanShoot()
	{
		if ( !HasAmmo() ) return false;
		if ( IsReloading ) return false;
		if ( NextPrimaryFire > 0 ) return false;

		return true;
	}

	public override void DrawHud( HudPainter painter, Vector2 crosshair )
	{
		DrawCrosshair( painter, crosshair );
	}

	public override void DrawCrosshair( HudPainter hud, Vector2 center )
	{
		Color color = Color.Red;

		hud.DrawLine( center + Vector2.Left * 32, center + Vector2.Left * 15, 3, color );
		hud.DrawLine( center - Vector2.Left * 32, center - Vector2.Left * 15, 3, color );
		hud.DrawLine( center + Vector2.Up * 32, center + Vector2.Up * 15, 3, color );
		hud.DrawLine( center - Vector2.Up * 32, center - Vector2.Up * 15, 3, color );
	}

	public override void OnControl( Player player )
	{
		base.OnControl( player );

		bool wantsToCancelReload = Input.Pressed( "Attack1" ) || Input.Pressed( "Attack2" );
		if ( CanCancelReload && IsReloading && wantsToCancelReload && HasAmmo() )
		{
			CancelReload();
		}

		if ( CanReload() && Input.Pressed( "reload" ) )
		{
			Reload();
		}
	}
}
facepunch.sbdm / Weapons/GaussGun/GaussWeapon.cs
Game game
using Sandbox.Rendering;
using Sandbox.Utility;

public class GaussWeapon : BaseBulletWeapon
{
	/// <summary>
	/// Having this as its own event separate from weapon events until we have a reason to use it for another weapon
	/// </summary>
	public interface IGaussWeaponEvents : ISceneEvent<IGaussWeaponEvents>
	{
		/// <summary>
		/// Called when we consume a bit of ammo
		/// </summary>
		void OnConsumedAmmo();
	}

	/// <summary>
	/// Shot frequency delay
	/// </summary>
	[Property] public float TimeBetweenShots { get; set; } = 0.1f;

	/// <summary>
	/// How much damage
	/// </summary>
	[Property] public float Damage { get; set; } = 12.0f;

	/// <summary>
	/// How many units deep can geometry be for us to penetrate directly through it.
	/// </summary>
	[Property] public float PenetrationThickness { get; set; } = 32f;

	[Property] public GameObject ImpactEffectPrefab { get; set; }

	[Property] public GameObject LargeImpactEffect { get; set; }

	/// <summary>
	/// What looping sound should we play while charging the gun
	/// </summary>
	[Property, Feature( "Charge" )]
	public SoundEvent ChargeSoundEvent { get; set; }

	/// <summary>
	/// A curve to get a damage value directly from ChargePower 
	/// </summary>
	[Property, Feature( "Charge" )]
	public Curve ChargeDamage { get; set; }

	/// <summary>
	/// How much force is applied when we shoot a charged shot
	/// </summary>
	[Property, Feature( "Charge" )]
	public Curve ChargeForce { get; set; }

	/// <summary>
	/// How frequently does the ammo drain whilst charging the gauss gun
	/// </summary>
	[Property, Feature( "Charge" )]
	public float ChargeAmmoDrainFrequency { get; set; } = 0.2f;

	/// <summary>
	/// How long do we charge for until the gun overloads
	/// </summary>
	[Property, Feature( "Overload" )]
	public float OverloadTime { get; set; } = 5f;

	/// <summary>
	/// How much damage to inflict on the player if the gun overloads
	/// </summary>
	[Property, Feature( "Overload" )]
	public float OverloadDamage { get; set; } = 20f;

	[Property, Feature( "Overload" )]
	public SoundEvent OverloadSound { get; set; }

	/// <summary>
	/// The charge loop sound handle, we handle its lifetime.
	/// </summary>
	SoundHandle chargeHandle;

	/// <summary>
	/// Normalized charge power between 0 and 1
	/// </summary>
	float chargePower = 0;

	/// <summary>
	/// Is this gun charging up to shoot?
	/// </summary>
	bool isCharging = false;

	/// <summary>
	/// How long has it been since we started charging?
	/// </summary>
	TimeSince timeSinceChargeStart = 0;

	/// <summary>
	/// How long since we drained ammo while charged?
	/// </summary>
	TimeSince timeSinceAmmoTick;

	protected override void OnDisabled()
	{
		StopCharge();
		base.OnDisabled();
	}

	[Rpc.Host]
	private void HurtSelf()
	{
		if ( !Owner.IsValid() ) return;

		Owner.OnDamage( new DamageInfo( OverloadDamage, Owner.GameObject, GameObject ) );
		Sound.Play( OverloadSound, WorldPosition );
	}

	public override void OnControl( Player player )
	{
		base.OnControl( player );

		if ( isCharging )
		{
			if ( chargeHandle is not null )
				chargeHandle.Position = WorldPosition;

			if ( timeSinceChargeStart > OverloadTime )
			{
				StopCharge();
				HurtSelf();

				if ( !player.Controller.ThirdPerson && player.IsLocalPlayer )
				{
					var target = new Vector3( Random.Shared.Float( -10, -15 ), Random.Shared.Float( -25, 0 ), 0 );

					new Sandbox.CameraNoise.Punch( target, 1.0f, 3, 0.5f );
					new Sandbox.CameraNoise.Shake( 0.3f, 1.2f );
				}

				return;
			}

			if ( chargePower < 1f && timeSinceAmmoTick > ChargeAmmoDrainFrequency )
			{
				timeSinceAmmoTick = 0;

				if ( !TakeAmmo( 1 ) )
				{
					ShootCharged( player );
					return;
				}

				IGaussWeaponEvents.PostToGameObject( GameObject.Root, x => x.OnConsumedAmmo() );
			}

			chargePower += 0.5f * Time.Delta;
			chargePower = chargePower.Clamp( 0, 1 );
			chargeHandle.Pitch = chargePower.Remap( 0, 1, 0.5f, 1.2f );

			if ( !player.Controller.ThirdPerson && player.IsLocalPlayer )
			{
				new Sandbox.CameraNoise.Shake( 0.3f * chargePower.Remap( 0, 1, 0.5f, 3f ), Time.Delta );
			}
		}

		if ( !isCharging && Input.Down( "attack1" ) )
		{
			Shoot( player );
		}

		if ( Input.Pressed( "Attack2" ) )
		{
			if ( !TakeAmmo( 1 ) )
			{
				TryAutoReload();
				return;
			}
			else if ( CanShoot() )
			{
				timeSinceChargeStart = 0;
				isCharging = true;
				chargeHandle?.Stop();
				chargeHandle = Sound.Play( ChargeSoundEvent );

				StartAttack();
			}
		}

		if ( Input.Released( "Attack2" ) && isCharging )
		{
			StopAttack();
			ShootCharged( player );
		}
	}

	public override bool IsInUse() => isCharging;

	/// <summary>
	/// Shoot a charged shot
	/// </summary>
	/// <param name="player"></param>
	void ShootCharged( Player player )
	{
		ShootBullet( player, ChargeDamage.Evaluate( chargePower ), true, true );

		// Fling the player 
		var controller = player.GetComponent<PlayerController>();

		// Should expose all these to properties
		controller.Jump( -player.EyeTransform.ForwardRay.Forward * ChargeForce.Evaluate( chargePower ) );

		StopCharge();
	}

	/// <summary>
	/// Stop charging the gun
	/// </summary>
	void StopCharge()
	{
		isCharging = false;
		chargeHandle?.Stop();
		chargePower = 0;
	}

	/// <summary>
	/// Constructs a trace and returns it, doesn't run it yet!
	/// </summary>
	SceneTrace GetBulletTrace( Player player, Vector3 start, Vector3 end, float radius )
	{
		return Scene.Trace.Ray( start, end )
			.IgnoreGameObjectHierarchy( player.GameObject )
			.WithCollisionRules( "bullet" )
			.UseHitboxes()
			.Size( radius );
	}

	/// <summary>
	/// Runs a trace with all the data we have supplied it, and returns the result
	/// </summary>
	IEnumerable<SceneTraceResult> GetShootTraceResults( Player player )
	{
		var hits = new List<SceneTraceResult>();

		var start = player.EyeTransform.Position;
		var rot = Rotation.LookAt( player.EyeTransform.Forward );

		var forward = rot.Forward.WithAimCone( 2 );

		var original = GetBulletTrace( player, start, player.EyeTransform.Position + forward * 4096f, 2f )
						.RunAll();


		if ( original.Count() < 1 )
		{
			hits.Add( GetBulletTrace( player, start, player.EyeTransform.Position + forward * 4096f, 2f ).Run() );
			return hits;
		}

		// Run through and fix the start positions for the traces
		// By using the last end position as the start
		var startPos = original.ElementAt( 0 ).StartPosition;
		List<SceneTraceResult> fixedPath = new();
		for ( int i = 0; i < original.Count(); i++ )
		{
			var el = original.ElementAt( i );

			fixedPath.Add( el with { StartPosition = startPos } );
			startPos = el.EndPosition;
		}

		var entries = new List<(SceneTraceResult Trace, float Thickness)>();

		// Then, trace backwards from the end so we can get exit points and thickness
		for ( int i = fixedPath.Count - 1; i >= 0; i-- )
		{
			var el = fixedPath.ElementAt( i );

			// Do a trace back, from the end position to the start, this'll give us the LAST entry's exit point.
			var backTrace = GetBulletTrace( player, el.EndPosition, el.StartPosition, 2f )
							.Run();

			var impact = backTrace.EndPosition;

			// From that, we can calculate the surface thickness
			float thickness = (el.StartPosition - impact).Length;

			// Return the element starting at the exit point, it's more useful that way.
			el = el with { StartPosition = impact };
			entries.Insert( 0, (el, thickness) );
		}

		// Thickness detection
		{
			var thickness = 0f;
			foreach ( var el in entries )
			{
				thickness += el.Thickness;
				if ( thickness >= PenetrationThickness )
					break;

				hits.Add( el.Trace );
			}
		}

		return hits
			.Where( x => x.Hit && x.Distance > 0f );
	}

	void Shoot( Player player )
	{
		if ( !CanShoot() || !TakeAmmo( 1 ) )
		{
			TryAutoReload();
			return;
		}

		ShootBullet( player, Damage );
	}

	public void ShootBullet( Player player, float damage, bool isCharged = false, bool shouldRicochet = true )
	{
		AddShootDelay( TimeBetweenShots );

		bool shouldPenetrate = isCharged;

		IGaussWeaponEvents.PostToGameObject( GameObject.Root, x => x.OnConsumedAmmo() );

		int count = 0;
		var traces = GetShootTraceResults( player );

		foreach ( var tr in traces )
		{
			ShootEffects( tr.EndPosition, false, tr.Normal, tr.GameObject, tr.Surface, count > 0 ? tr.StartPosition : null );
			TraceAttack( TraceAttackInfo.From( tr, damage, damage > 70 ? [DamageTags.GibAlways] : null ) );
			count++;

			if ( player.IsLocalPlayer )
			{
				HitMarker.CreateFromTrace( tr );
			}

			if ( tr.Hit )
			{
				SpecialImpactEffects( tr.EndPosition, tr.Normal, tr.GameObject, tr.Surface );
			}

			if ( !shouldPenetrate ) break;
		}

		TimeSinceShoot = 0;

		if ( shouldRicochet && traces.Any() )
		{
			// Grab the last sufficient trace, we only want to ricochet at the end
			var tr = traces.Last();

			var reflectDir = Vector3.Reflect( tr.Direction, tr.Normal ).Normal;
			var angle = reflectDir.Angle( tr.Direction );

			// Some acute angle
			if ( angle < 45f )
			{
				tr = GetBulletTrace( player, tr.EndPosition, tr.EndPosition + (reflectDir * 4096), 2f ).Run();

				ShootEffects( tr.EndPosition, false, tr.Normal, tr.GameObject, tr.Surface, count > 0 ? tr.StartPosition : null );
				TraceAttack( TraceAttackInfo.From( tr, damage ) );

				if ( player.IsLocalPlayer )
				{
					HitMarker.CreateFromTrace( tr );
				}

				if ( tr.Hit )
				{
					SpecialImpactEffects( tr.EndPosition, tr.Normal, tr.GameObject, tr.Surface );
				}
			}
		}

		player.Controller.EyeAngles += new Angles( Random.Shared.Float( -0.2f, -0.3f ), Random.Shared.Float( -0.1f, 0.1f ), 0 );

		if ( !player.Controller.ThirdPerson && player.IsLocalPlayer )
		{
			var target = new Vector3( Random.Shared.Float( -10, -15 ), Random.Shared.Float( -10, 0 ), 0 );
			target *= chargePower.Remap( 0, 1, 1, 10 );

			new Sandbox.CameraNoise.Punch( target, 1.0f, 3, 0.5f );
			new Sandbox.CameraNoise.Shake( 0.3f, 1.2f );
		}
	}

	public override void DrawCrosshair( HudPainter hud, Vector2 center )
	{
		var tss = TimeSinceShoot.Relative.Remap( 0, 0.2f, 1, 0 );

		var gap = 6 + Easing.EaseOut( tss ) * 32;
		var len = 6;
		var w = 2;

		Color color = !CanShoot() ? UI.CrosshairInactive : UI.CrosshairActive;

		hud.DrawLine( center + Vector2.Left * (len + gap) * 2, center + Vector2.Left * gap * 2, w, color );
		hud.DrawLine( center - Vector2.Left * (len + gap) * 2, center - Vector2.Left * gap * 2, w, color );
		hud.DrawLine( center + Vector2.Up * (len + gap), center + Vector2.Up * gap, w, color );
		hud.DrawLine( center - Vector2.Up * (len + gap), center - Vector2.Up * gap, w, color );
	}

	[Rpc.Broadcast]
	public void SpecialImpactEffects( Vector3 hitpoint, Vector3 normal, GameObject hitObject, Surface hitSurface )
	{
		if ( Application.IsDedicatedServer ) return;

		if ( ImpactEffectPrefab is null )
			return;

		var impact = ImpactEffectPrefab.Clone();
		impact.WorldPosition = hitpoint + normal;
		impact.WorldRotation = Rotation.LookAt( normal ) * new Angles( 90, 0, 0 );
		impact.SetParent( hitObject, true );
	}
}
facepunch.sbdm / Weapons/WeaponModel/WeaponModel.cs
Game game
/// <summary>
/// Base for our view and world model components. <see cref="BaseWeaponModel"/> supplies the muzzle
/// and shell-eject attachments, the effect prefabs and the deploy/attack/reload presentation hooks -
/// this exists so the deathmatch view and world models share a type of their own.
/// </summary>
public abstract class WeaponModel : BaseWeaponModel
{
}
facepunch.sbdm / Weapons/WeaponModel/WorldModel.cs
Game game
using static BaseWeapon;

public sealed class WorldModel : WeaponModel, IWeaponEvent
{
	void IWeaponEvent.OnAttack( IWeaponEvent.AttackEvent e )
	{
		Renderer?.Set( "b_attack", true );

		if ( e.isFirstPerson )
			return;

		DoMuzzleEffect();
		DoEjectBrass();
	}

	void IWeaponEvent.CreateRangedEffects( BaseWeapon weapon, Vector3 hitPoint, Vector3? origin )
	{
		if ( weapon.ViewModel.IsValid() )
			return;

		DoTracerEffect( hitPoint, origin );
	}
}
facepunch.sbdm / styles/base.scss
Game game
@import "base/_splitcontainer.scss";
@import "base/_navigator.scss";

button
{
	cursor: pointer;
}

IconPanel
{
	font-family: Material Icons;
}

.is-half
{
	width: 50%;
}

.is-third
{
	width: 33%;
}

.is-quarter
{
	width: 25%;
}

button.has-subtitle
{
	position: relative;
	flex-direction: column;
	justify-content: flex-start;
	align-items: flex-start;
	padding-left: 40px; // icon space

	.iconpanel
	{
		position: absolute;
		left: 5px;
		top: 0;
		bottom: 0;
		align-items: center;
	}

	.button-label
	{
		font-weight: bold;
	}

	.button-subtitle
	{
		font-size: 12px;
		opacity: 0.5;
		mix-blend-mode: lighten;
	}
}
facepunch.sbdm / ui/dropdown.cs.scss
Game game
.dropdown
{
	gap: 2px;
	flex-grow: 1;
	cursor: pointer;
	justify-content: flex-end;
	align-items: center;
	padding: 0px 12px;

	.button-right-column
	{
		flex-grow: 1;
	}
}
Debug: View Raw JSON Response
{
    "TotalCount": 154,
    "Files": [
        {
            "Ident": "facepunch.sbdm",
            "Path": "Items/Pickups/ArmourPickup.cs",
            "FileName": "ArmourPickup.cs",
            "PackageType": "game",
            "CodeKind": "Game",
            "AssetVersionId": 336963,
            "Code": "/// <summary>\r\n/// A pickup that gives the player some armour.\r\n/// </summary>\r\npublic sealed class ArmourPickup : BasePickup\r\n{\r\n\t/// <summary>\r\n\t/// How much armour to give to the player\r\n\t/// </summary>\r\n\t[Property, Group( \"Armour\" )] float ArmourGive { get; set; } = 0;\r\n\r\n\tpublic override bool CanPickup( Player player, PlayerInventory inventory )\r\n\t{\r\n\t\tif ( player.Armour >= player.MaxArmour && ArmourGive > 0 )\r\n\t\t\treturn false;\r\n\r\n\t\treturn true;\r\n\t}\r\n\r\n\tprotected override bool OnPickup( Player player, PlayerInventory inventory )\r\n\t{\r\n\t\tplayer.Armour = (player.Armour + ArmourGive).Clamp( 0, player.MaxArmour );\r\n\t\tplayer.PlayerData.AddStat( $\"pickup.armor\" );\r\n\r\n\t\treturn true;\r\n\t}\r\n}\r\n"
        },
        {
            "Ident": "facepunch.sbdm",
            "Path": "Map/BaseToggle.cs",
            "FileName": "BaseToggle.cs",
            "PackageType": "game",
            "CodeKind": "Game",
            "AssetVersionId": 336963,
            "Code": "public abstract class BaseToggle : Component\r\n{\r\n\tpublic delegate Task StateChangedDelegate( bool state );\r\n\r\n\t/// <summary>\r\n\t/// The toggle state has changed\r\n\t/// </summary>\r\n\t[Property] public StateChangedDelegate OnStateChanged { get; set; }\r\n\r\n\tbool _state;\r\n\r\n\t[Property, Sync]\r\n\tpublic bool State\r\n\t{\r\n\t\tget => _state;\r\n\t\tset\r\n\t\t{\r\n\t\t\tif ( _state == value ) return;\r\n\r\n\t\t\t_state = value;\r\n\t\t\tStateHasChanged( _state );\r\n\r\n\t\t}\r\n\t}\r\n\r\n\t/// <summary>\r\n\t/// The toggle state has changed\r\n\t/// </summary>\r\n\tprotected virtual void StateHasChanged( bool newState )\r\n\t{\r\n\t\tOnStateChanged?.Invoke( _state );\r\n\t}\r\n}\r\n"
        },
        {
            "Ident": "facepunch.sbdm",
            "Path": "Player/GrabAction.cs",
            "FileName": "GrabAction.cs",
            "PackageType": "game",
            "CodeKind": "Game",
            "AssetVersionId": 336963,
            "Code": "/// <summary>\r\n/// Put this component on something that is pressable, and the player will grab it with a certain style animation.\r\n/// </summary>\r\npublic partial class GrabAction : Component\r\n{\r\n\tpublic enum GrabStyle\r\n\t{\r\n\t\tNone,\r\n\t\tSweepDown,\r\n\t\tSweepRight,\r\n\t\tSweepLeft,\r\n\t\tPressButton\r\n\t}\r\n\r\n\t[Property]\r\n\tpublic GrabStyle Style { get; set; } = GrabStyle.SweepDown;\r\n}\r\n"
        },
        {
            "Ident": "facepunch.sbdm",
            "Path": "UI/Inventory.razor",
            "FileName": "Inventory.razor",
            "PackageType": "game",
            "CodeKind": "Game",
            "AssetVersionId": 336963,
            "Code": "@using Sandbox;\r\n@using Sandbox.UI;\r\n@inherits PanelComponent\r\n@implements ILocalPlayerEvent\r\n\r\n<root>\r\n\t@for (int i = 0; i < 5; i++)\r\n\t{\r\n        @if ( ItemsInSlot(i).Count() < 1 )\r\n        {\r\n            continue;\r\n        }\r\n\r\n        <InventorySlot Index=@i Inventory=@inventory Hovered=@hovered Active=@active></InventorySlot>\r\n\t}\r\n</root>\r\n\r\n@code\r\n{\r\n    [Property] public SoundEvent SwitchSound { get; set; }\r\n    [Property] public SoundEvent SelectSound { get; set; }\r\n    [Property] public SoundEvent CancelSound { get; set; }\r\n\r\n    PlayerInventory inventory => Player.Local.IsValid() ? Player.Local.Inventory : null;\r\n    Carryable hovered;\r\n    Carryable active;\r\n    Carryable prev;\r\n\r\n    protected override int BuildHash() => HashCode.Combine(inventory, hovered, active);\r\n\r\n    IEnumerable<Carryable> ItemsInSlot( int slot )\r\n    {\r\n        return inventory.IsValid() ? inventory.Carryables.Where(x => x.PreferredSlot == slot) : [];\r\n    }\r\n\r\n    void ILocalPlayerEvent.OnPickup( BaseInventoryItem weapon )\r\n    {\r\n        StateHasChanged();\r\n    }\r\n\r\n    protected override void OnUpdate()\r\n    {\r\n        DoInventoryInput();\r\n\t}\r\n\r\n\tRealTimeSince timeSinceInteraction;\r\n\r\n\tvoid DoInventoryInput()\r\n\t{\r\n\t\tif (inventory is null)\r\n\t\t\treturn;\r\n\r\n\t\tif (GameManager.Current.CurrentGameStage != GameManager.GameStage.Game)\r\n\t\t{\r\n\t\t\tif (hovered is not null)\r\n\t\t\t{\r\n\t\t\t\thovered = null;\r\n\t\t\t\tactive = null;\r\n\t\t\t\tprev = null;\r\n\t\t\t}\r\n\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tMoveSlot(-(int)Input.MouseWheel.y);\r\n\r\n\t\tif ( Input.Pressed( \"Menu\" ) && prev.IsValid() )\r\n\t\t{\r\n\t\t\tvar weapon = prev;\r\n            prev = inventory.Current;\r\n\t\t\tinventory.SwitchWeapon( weapon );\r\n\t\t}\r\n\r\n\t\tif (Input.Pressed(\"SlotNext\")) MoveSlot(1);\r\n\t\tif (Input.Pressed(\"SlotPrev\")) MoveSlot(-1);\r\n\r\n\t\tif (Input.Pressed(\"Slot1\")) IterateSlot(0);\r\n\t\tif (Input.Pressed(\"Slot2\")) IterateSlot(1);\r\n\t\tif (Input.Pressed(\"Slot3\")) IterateSlot(2);\r\n\t\tif (Input.Pressed(\"Slot4\")) IterateSlot(3);\r\n\t\tif (Input.Pressed(\"Slot5\")) IterateSlot(4);\r\n\r\n\t\tif (hovered is null)\r\n\t\t\treturn;\r\n\r\n\t\tif (Input.Pressed(\"Attack1\"))\r\n\t\t{\r\n\t\t\tInput.ReleaseAction(\"Attack1\");\r\n\t\t\tInput.SetAction(\"Attack1\", false);\r\n\r\n\t\t\tprev = inventory.Current;\r\n\t\t\tinventory.SwitchWeapon(hovered);\r\n\t\t\tactive = hovered;\r\n\t\t\thovered = null;\r\n\t\t\tSound.Play(SelectSound);\r\n\t\t}\r\n\r\n\t\tif (Input.Pressed(\"Attack2\") || timeSinceInteraction > 2f)\r\n\t\t{\r\n\t\t\tInput.ReleaseAction(\"Attack2\");\r\n\t\t\tInput.SetAction(\"Attack2\", false);\r\n\t\t\tactive = null;\r\n\t\t\thovered = null;\r\n\t\t\tSound.Play(CancelSound);\r\n\t\t}\r\n\t}\r\n\r\n\tpublic void MoveSlot( int delta )\r\n\t{\r\n\t\tif ( delta == 0 )\r\n\t\t\treturn;\r\n\r\n\t\tvar weapons = inventory.Carryables.Where( x => x.CanSwitch() ).ToList();\r\n\r\n\t\tif ( weapons.Count == 0 )\r\n\t\t\treturn;\r\n\r\n        var currentHover = hovered ?? active ?? inventory.Current ?? weapons.FirstOrDefault();\r\n\r\n\t\tint currentIndex = weapons.IndexOf( currentHover );\r\n\r\n\t\tcurrentIndex += delta;\r\n\t\tcurrentIndex %= weapons.Count;\r\n\t\tif ( currentIndex < 0 )\r\n\t\t\tcurrentIndex = weapons.Count + currentIndex;\r\n\r\n\t\tactive = null;\r\n\t\tif ( GamePreferences.FastSwitch )\r\n\t\t{\r\n\t\t\tinventory.SwitchWeapon( weapons[currentIndex] );\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\thovered = weapons[currentIndex];\r\n\t\t\ttimeSinceInteraction = 0;\r\n\t\t}\r\n\r\n\t\tSound.Play( SwitchSound );\r\n\t}\r\n\r\n\tpublic void IterateSlot( int slot )\r\n\t{\r\n\t\tvar slotWeapons = inventory.Carryables.Where( x => x.PreferredSlot == slot ).Where( x => x.CanSwitch() ).OrderBy( x => x.SlotOrder ).ToList();\r\n\t\tif ( slotWeapons.Count == 0 )\r\n\t\t\treturn;\r\n\r\n\t\t// if inventory wasn't open, and current weapon is in a different slot, currentIndex will be -1 (incremented to 0), and first weapon in target slot will be selected\r\n        var currentHover = hovered ?? active ?? inventory.Current ?? slotWeapons.FirstOrDefault();\r\n\r\n\t\tint currentIndex = slotWeapons.IndexOf( currentHover );\r\n\r\n\t\tcurrentIndex += 1;\r\n\t\tcurrentIndex %= slotWeapons.Count;\r\n\r\n\t\tif ( currentIndex < 0 )\r\n\t\t\tcurrentIndex = slotWeapons.Count + currentIndex;\r\n\r\n\t\tactive = null;\r\n\t\tif ( GamePreferences.FastSwitch )\r\n\t\t{\r\n\t\t\tinventory.SwitchWeapon( slotWeapons[currentIndex] );\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\thovered = slotWeapons[currentIndex];\r\n\t\t\ttimeSinceInteraction = 0;\r\n\t\t}\r\n\r\n\t\tSound.Play( SwitchSound );\r\n    }\r\n}\r\n"
        },
        {
            "Ident": "facepunch.sbdm",
            "Path": "Utility/CameraNoise/CameraNoiseSystem.cs",
            "FileName": "CameraNoiseSystem.cs",
            "PackageType": "game",
            "CodeKind": "Game",
            "AssetVersionId": 336963,
            "Code": "namespace Sandbox.CameraNoise;\r\n\r\npublic class CameraNoiseSystem : GameObjectSystem<CameraNoiseSystem>, ICameraSetup\r\n{\r\n\tList<BaseCameraNoise> _all = new();\r\n\r\n\tpublic CameraNoiseSystem( Scene scene ) : base( scene )\r\n\t{\r\n\t}\r\n\r\n\tvoid ICameraSetup.PreSetup( Sandbox.CameraComponent cc )\r\n\t{\r\n\t\tforeach ( var effect in _all )\r\n\t\t{\r\n\t\t\teffect.Update();\r\n\t\t}\r\n\r\n\t\t_all.RemoveAll( x => x.IsDone );\r\n\t}\r\n\r\n\tvoid ICameraSetup.PostSetup( CameraComponent cc )\r\n\t{\r\n\t\tforeach ( var effect in _all )\r\n\t\t{\r\n\t\t\teffect.ModifyCamera( cc );\r\n\t\t}\r\n\t}\r\n\r\n\tpublic void Add( BaseCameraNoise noise )\r\n\t{\r\n\t\t_all.Add( noise );\r\n\t}\r\n}\r\n\r\npublic abstract class BaseCameraNoise\r\n{\r\n\tpublic float LifeTime { get; protected set; }\r\n\tpublic float CurrentTime { get; protected set; }\r\n\tpublic float Delta => CurrentTime.LerpInverse( 0, LifeTime, true );\r\n\tpublic float DeltaInverse => 1 - Delta;\r\n\r\n\tpublic BaseCameraNoise()\r\n\t{\r\n\t\tCameraNoiseSystem.Current.Add( this );\r\n\t}\r\n\r\n\tpublic virtual bool IsDone => CurrentTime > LifeTime;\r\n\r\n\tpublic virtual void Update()\r\n\t{\r\n\t\tCurrentTime += Time.Delta;\r\n\t}\r\n\r\n\tpublic virtual void ModifyCamera( CameraComponent cc ) { }\r\n}\r\n"
        },
        {
            "Ident": "facepunch.sbdm",
            "Path": "Weapons/CrossbowProjectile.cs",
            "FileName": "CrossbowProjectile.cs",
            "PackageType": "game",
            "CodeKind": "Game",
            "AssetVersionId": 336963,
            "Code": "namespace Sandbox;\r\n\r\ninternal class CrossbowProjectile : Projectile, IKillIcon\r\n{\r\n\t[Property]\r\n\tpublic Curve Curve { get; set; }\r\n\r\n\t[Property]\r\n\tpublic float ExplosiveDamage { get; set; } = 70f;\r\n\r\n\t[Property]\r\n\tpublic float Radius { get; set; } = 128.0f;\r\n\r\n\t[Property]\r\n\tTexture IKillIcon.DisplayIcon { get; set; }\r\n\r\n\tprotected override void OnHit( Collision collision = default )\r\n\t{\r\n\t\tCreateEffects( collision.Contact.Point + (collision.Contact.Speed.Normal * 32) );\r\n\r\n\t\t// Direct hit, just kill the player\r\n\t\tif ( collision.Other.GameObject.GetComponentInParent<Player>() is Player player && player.IsValid() )\r\n\t\t{\r\n\t\t\tplayer.OnDamage( new DamageInfo( 200, Instigator?.Player?.GameObject, GameObject ) );\r\n\t\t}\r\n\t\telse\r\n\t\t{\r\n\t\t\t// Explode in radius\r\n\t\t\tDamage.Radius( collision.Contact.Point, Radius, ExplosiveDamage, [DamageTags.Explosion], GameObject, GameObject, Curve, Instigator );\r\n\t\t}\r\n\r\n\t\tGameObject.Destroy();\r\n\t}\r\n\r\n\t[Rpc.Broadcast( NetFlags.HostOnly )]\r\n\tprivate void CreateEffects( Vector3 position )\r\n\t{\r\n\t\tif ( Application.IsDedicatedServer ) return;\r\n\r\n\t\tEffects.SpawnExplosionSmall( position );\r\n\t}\r\n}\r\n"
        },
        {
            "Ident": "facepunch.sbdm",
            "Path": "Weapons/Item.cs",
            "FileName": "Item.cs",
            "PackageType": "game",
            "CodeKind": "Game",
            "AssetVersionId": 336963,
            "Code": "/// <summary>\n/// Base class for a passive thing a player carries in their inventory and stores in a coffin - it is\n/// never equipped. Anything that can be selected and held derives from <see cref=\"Carryable\"/> instead.\n/// </summary>\npublic abstract partial class Item : BaseInventoryItem\n{\n\t/// <summary>\n\t/// The player that owns this item\n\t/// </summary>\n\tpublic Player Owner => GetComponentInParent<Player>( true );\n\n\t/// <summary>\n\t/// Passive items are never deployed, so the inventory must never select one.\n\t/// </summary>\n\tprotected override bool OnCanSwitchTo() => false;\n\n\t/// <summary>\n\t/// Called when the owner dies\n\t/// </summary>\n\tpublic virtual void OnPlayerDeath( IPlayerEvent.DiedParams args )\n\t{\n\t}\n}\n"
        },
        {
            "Ident": "facepunch.sbdm",
            "Path": "ui/controls/color/coloralphacontrol.cs.scss",
            "FileName": "coloralphacontrol.cs.scss",
            "PackageType": "game",
            "CodeKind": "Game",
            "AssetVersionId": 336963,
            "Code": "ColorAlphaControl\r\n{\r\n\tgap: 0.5rem;\r\n\tflex-grow: 1;\r\n\tpointer-events: all;\r\n\tbackground: linear-gradient( to right, black, white );\r\n\tborder-radius: 4px;\r\n\tpadding: 2px;\r\n\theight: 12px;\r\n\tposition: relative;\r\n\tcursor: pointer;\r\n\tborder: 1px solid #333;\r\n\r\n\t&:hover\r\n\t{\r\n\t\tborder: 1px solid #08f;\r\n\t}\r\n\r\n\t&:active\r\n\t{\r\n\t\tborder: 1px solid #fff;\r\n\t}\r\n\r\n\t.handle\r\n\t{\r\n\t\ttop: -5px;\r\n\t\tbottom: -5px;\r\n\t\taspect-ratio: 1;\r\n\t\tborder-radius: 100px;\r\n\t\tborder: 2px solid #444;\r\n\t\tposition: absolute;\r\n\t\tbackground-color: white;\r\n\t\tbox-shadow: 2px 2px 16px #000a;\r\n\t\ttransform: translateX( -50% );\r\n\t\tpointer-events: none;\r\n\t}\r\n}\r\n"
        },
        {
            "Ident": "facepunch.sbdm",
            "Path": ".obj/__compiler_extra.cs",
            "FileName": "__compiler_extra.cs",
            "PackageType": "game",
            "CodeKind": "Game",
            "AssetVersionId": 336963,
            "Code": "global using static Sandbox.Internal.GlobalGameNamespace;\r\nglobal using Microsoft.AspNetCore.Components;\r\nglobal using Microsoft.AspNetCore.Components.Rendering;\r\n[assembly: global::System.Reflection.AssemblyMetadata( \"AddonTitle\", \"Deathmatch\" )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \"AddonIdent\", \"sbdm\" )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \"OrgIdent\", \"facepunch\" )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \"Ident\", \"facepunch.sbdm\" )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \"EngineVersion\", \"28\" )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \"EngineMinorVersion\", \"1\" )]\r\n\r\n[assembly: System.Runtime.Versioning.TargetFramework( \".NETCoreApp,Version=v9.0\", FrameworkDisplayName = \".NET 9.0\" )]\r\n[assembly: global::System.Reflection.AssemblyMetadata( \"CompileTime\", \"2026-07-29T09:34:51.9024540Z\" )]\r\n[assembly: global::System.Reflection.AssemblyVersion(\"0.0.111.0\")]\r\n[assembly: global::System.Reflection.AssemblyFileVersion(\"0.0.111.0\")]"
        },
        {
            "Ident": "facepunch.sbdm",
            "Path": "AI/Rat.cs",
            "FileName": "Rat.cs",
            "PackageType": "game",
            "CodeKind": "Game",
            "AssetVersionId": 336963,
            "Code": "/// <summary>\r\n/// A rat NPC\r\n/// </summary>\r\npublic sealed class Rat : Component, Component.IDamageable, IKillIcon, Component.ICollisionListener, IInstigator\r\n{\r\n\t/// <summary>\r\n\t/// The <see cref=\"NavMeshAgent\"/> which lets the rat traverse the navmesh\r\n\t/// </summary>\r\n\t[RequireComponent] public NavMeshAgent Agent { get; set; }\r\n\r\n\t/// <summary>\r\n\t/// The <see cref=\"Rigidbody\"/> which we switch out to when jumping off of the navmesh\r\n\t/// </summary>\r\n\t[RequireComponent] public Rigidbody Rigidbody { get; set; }\r\n\r\n\t/// <summary>\r\n\t/// What should we spawn when the Rat dies?\r\n\t/// </summary>\r\n\t[Property] public GameObject DeathEffects { get; set; }\r\n\r\n\t/// <summary>\r\n\t/// What sound should we play when the Rat dies?\r\n\t/// </summary>\r\n\t[Property] public SoundEvent AttackSound { get; set; }\r\n\r\n\t/// <summary>\r\n\t/// The mouth of the rat, used for attack range\r\n\t/// </summary>\r\n\t[Property] public GameObject Mouth { get; set; }\r\n\r\n\t/// <summary>\r\n\t/// An icon to show on the kill feed when the rat kills someone\r\n\t/// </summary>\r\n\t[Property] Texture IKillIcon.DisplayIcon { get; set; }\r\n\r\n\t/// <summary>\r\n\t/// How far (sq units) away should the rat be before it starts attacking people?\r\n\t/// </summary>\r\n\t[Property, Feature( \"Balance\" )] public float AttackDistance { get; set; } = 50000f;\r\n\r\n\t/// <summary>\r\n\t/// How long until the rat implodes\r\n\t/// </summary>\r\n\t[Property, Feature( \"Balance\" )] public float Lifetime { get; set; } = 30f;\r\n\r\n\t/// <summary>\r\n\t/// How frequently should the rat choose a new roam location\r\n\t/// </summary>\r\n\t[Property, Feature( \"Balance\" )] public float RoamFrequency { get; set; } = 5f;\r\n\r\n\t/// <summary>\r\n\t/// How far away can the rat roam\r\n\t/// </summary>\r\n\t[Property, Feature( \"Balance\" )] public float RoamRadius { get; set; } = 1024f;\r\n\r\n\t/// <summary>\r\n\t/// Damage dealt when the rat bites a target\r\n\t/// </summary>\r\n\t[Property, Feature( \"Balance\" )] public float BiteDamage { get; set; } = 10f;\r\n\r\n\t/// <summary>\r\n\t/// Time (in seconds) between bite attacks\r\n\t/// </summary>\r\n\t[Property, Feature( \"Balance\" )] public float BiteCooldown { get; set; } = 1f;\r\n\r\n\t/// <summary>\r\n\t/// Time (in seconds) between lunge attacks\r\n\t/// </summary>\r\n\t[Property, Feature( \"Balance\" )] public float LungeCooldown { get; set; } = 3f;\r\n\r\n\t/// <summary>\r\n\t/// How often the rat thinks/updates its behavior (in seconds)\r\n\t/// </summary>\r\n\t[Property, Feature( \"Balance\" )] public float ThinkInterval { get; set; } = 0.1f;\r\n\r\n\t/// <summary>\r\n\t/// Distance squared for bite attack range\r\n\t/// </summary>\r\n\t[Property, Feature( \"Balance\" )] public float BiteRangeSquared { get; set; } = 5000f;\r\n\r\n\t/// <summary>\r\n\t/// Distance squared for target detection range\r\n\t/// </summary>\r\n\t[Property, Feature( \"Balance\" )] public float TargetDetectionRangeSquared { get; set; } = 262144f;\r\n\r\n\t/// <summary>\r\n\t/// Distance threshold for reaching roam target\r\n\t/// </summary>\r\n\t[Property, Feature( \"Balance\" )] public float RoamTargetThreshold { get; set; } = 32f;\r\n\r\n\t/// <summary>\r\n\t/// Velocity when lunging at target (X = horizontal, Y = vertical)\r\n\t/// </summary>\r\n\t[Property, Feature( \"Balance\" )] public Vector2 LungeVelocity { get; set; } = new Vector2( 512f, 256f );\r\n\r\n\t/// <summary>\r\n\t/// Velocity when thrown (X = horizontal, Y = vertical)\r\n\t/// </summary>\r\n\t[Property, Feature( \"Balance\" )] public Vector2 ThrowVelocity { get; set; } = new Vector2( 1024f, 50f );\r\n\r\n\t/// <summary>\r\n\t/// Initial think timer delay on start\r\n\t/// </summary>\r\n\t[Property, Feature( \"Balance\" )] public float InitialThinkDelay { get; set; } = 5f;\r\n\r\n\t/// <summary>\r\n\t/// Lunge timer when a new target is acquired\r\n\t/// </summary>\r\n\t[Property, Feature( \"Balance\" )] public float NewTargetLungeDelay { get; set; } = 1.5f;\r\n\r\n\t/// <summary>\r\n\t/// Height for roam area bounding box\r\n\t/// </summary>\r\n\t[Property, Feature( \"Balance\" )] public float RoamAreaHeight { get; set; } = 128f;\r\n\r\n\t/// <summary>\r\n\t/// Ground check distance (up and down)\r\n\t/// </summary>\r\n\t[Property, Feature( \"Balance\" )] public float GroundCheckDistance { get; set; } = 8f;\r\n\r\n\t/// <summary>\r\n\t/// Who's the rat's friend (the person who threw them.. not much of a friend are they)\r\n\t/// </summary>\r\n\t[Sync]\r\n\tpublic PlayerData Instigator { get; set; }\r\n\r\n\t/// <summary>\r\n\t/// Are we on the ground?\r\n\t/// </summary>\r\n\tbool IsOnGround = true;\r\n\r\n\t/// <summary>\r\n\t/// The current target of the rat\r\n\t/// </summary>\r\n\tPlayer target;\r\n\r\n\t/// <summary>\r\n\t/// How many seconds has it been since the rat bit someone?\r\n\t/// </summary>\r\n\tTimeSince TimeSinceBitten = 0;\r\n\r\n\t/// <summary>\r\n\t/// How many seconds has it been since we last LUNGED\r\n\t/// </summary>\r\n\tTimeSince LungeTimer = 0;\r\n\r\n\t/// <summary>\r\n\t/// How long has the rat been alive?\r\n\t/// </summary>\r\n\tTimeSince TimeSinceCreated = 0;\r\n\r\n\t/// <summary>\r\n\t/// Time since we picked a new target\r\n\t/// </summary>\r\n\tTimeSince ThinkTimer = 0;\r\n\r\n\t/// <summary>\r\n\t/// The roam target\r\n\t/// </summary>\r\n\tVector3 RoamTarget;\r\n\r\n\tprotected override void OnStart()\r\n\t{\r\n\t\tTimeSinceCreated = 0;\r\n\t\tThinkTimer = InitialThinkDelay;\r\n\t}\r\n\r\n\t/// <summary>\r\n\t/// Trace down, see if we're hitting the ground\r\n\t/// </summary>\r\n\t/// <returns></returns>\r\n\tprivate void UpdateGrounded()\r\n\t{\r\n\t\tvar tr = Scene.Trace.Ray( WorldPosition + Vector3.Up * GroundCheckDistance, WorldPosition + Vector3.Down * GroundCheckDistance )\r\n\t\t\t.IgnoreGameObjectHierarchy( GameObject.Root )\r\n\t\t\t.Run();\r\n\r\n\t\tIsOnGround = tr.Hit;\r\n\t}\r\n\r\n\t/// <summary>\r\n\t/// Throws a rat in a set direction\r\n\t/// </summary>\r\n\t/// <param name=\"rotation\"></param>\r\n\tpublic void Throw( Rotation rotation )\r\n\t{\r\n\t\tvar direction = rotation.Forward;\r\n\t\tAgent.UpdatePosition = false;\r\n\t\tWorldRotation = Rotation.LookAt( direction, Vector3.Up );\r\n\t\tRigidbody.Velocity = WorldRotation.Forward * ThrowVelocity.x + Vector3.Up * ThrowVelocity.y;\r\n\t}\r\n\r\n\t/// <summary>\r\n\t/// Called when the rat either lifts off the ground, or lands\r\n\t/// </summary>\r\n\tvoid OnGroundedChanged( bool before, bool after )\r\n\t{\r\n\t\tAgent.SetAgentPosition( WorldPosition );\r\n\t}\r\n\r\n\r\n\tprivate bool hasLanded = false;\r\n\tvoid ICollisionListener.OnCollisionStart( Collision collision )\r\n\t{\r\n\t\t// if we're thrown somewhere, hit the ground running\r\n\t\tif ( !hasLanded )\r\n\t\t{\r\n\t\t\tRigidbody.Velocity = Vector3.Zero;\r\n\t\t\tAgent.SetAgentPosition( WorldPosition );\r\n\t\t\thasLanded = true;\r\n\t\t}\r\n\t}\r\n\r\n\tvoid Bite( Player target )\r\n\t{\r\n\t\tTimeSinceBitten = 0;\r\n\r\n\t\tDoAttackEffects();\r\n\r\n\t\tvar dmg = new DamageInfo( BiteDamage, Instigator?.Player?.GameObject, GameObject );\r\n\r\n\t\ttarget.OnDamage( dmg );\r\n\t}\r\n\r\n\tprotected override void OnUpdate()\r\n\t{\r\n\t\tif ( IsProxy )\r\n\t\t\treturn;\r\n\r\n\t\tif ( TimeSinceCreated > Lifetime )\r\n\t\t{\r\n\t\t\tDie();\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\t// We're lunging, and we have a target\r\n\t\tif ( !IsOnGround && target.IsValid() && TimeSinceBitten > BiteCooldown )\r\n\t\t{\r\n\t\t\t// Bite the target if they're close enough\r\n\t\t\tif ( target.WorldPosition.DistanceSquared( Mouth.WorldPosition ) < BiteRangeSquared )\r\n\t\t\t\tBite( target );\r\n\t\t}\r\n\r\n\t\tvar prevOnGround = IsOnGround;\r\n\t\tUpdateGrounded();\r\n\r\n\t\tif ( prevOnGround != IsOnGround )\r\n\t\t\tOnGroundedChanged( prevOnGround, IsOnGround );\r\n\r\n\t\tif ( !IsOnGround )\r\n\t\t\treturn;\r\n\r\n\t\tif ( !Agent.Enabled )\r\n\t\t\treturn;\r\n\r\n\t\tif ( target.IsValid() )\r\n\t\t{\r\n\t\t\t// If we haven't attacked in a while, move towards the enemy and try to jump them\r\n\t\t\tif ( ThinkTimer > ThinkInterval )\r\n\t\t\t{\r\n\t\t\t\tThinkTimer = 0;\r\n\t\t\t\tif ( WorldPosition.DistanceSquared( target.WorldPosition ) < AttackDistance && LungeTimer > LungeCooldown )\r\n\t\t\t\t{\r\n\t\t\t\t\tAttack();\r\n\t\t\t\t\treturn;\r\n\t\t\t\t}\r\n\r\n\t\t\t\tAgent.UpdatePosition = true;\r\n\t\t\t\tAgent.UpdateRotation = true;\r\n\t\t\t\tAgent.MoveTo( target.WorldPosition );\r\n\t\t\t}\r\n\t\t}\r\n\t\telse // roaming\r\n\t\t{\r\n\t\t\tif ( ThinkTimer > RoamFrequency || Vector3.DistanceBetween( WorldPosition, RoamTarget ) <= RoamTargetThreshold )\r\n\t\t\t{\r\n\t\t\t\tThinkTimer = 0;\r\n\r\n\t\t\t\tvar target = GetRoamPoint();\r\n\t\t\t\tif ( target.HasValue )\r\n\t\t\t\t{\r\n\t\t\t\t\tRoamTarget = target.Value;\r\n\r\n\t\t\t\t\tAgent.UpdatePosition = true;\r\n\t\t\t\t\tAgent.UpdateRotation = true;\r\n\t\t\t\t\tAgent.MoveTo( RoamTarget );\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\tLookForTarget();\r\n\r\n\t\t\tif ( target.IsValid() )\r\n\t\t\t{\r\n\t\t\t\t// attack soon if we've just got a target\r\n\t\t\t\tLungeTimer = NewTargetLungeDelay;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\tprivate Vector3? GetRoamPoint()\r\n\t{\r\n\t\tvar roamSize = new Vector3( RoamRadius, RoamRadius, RoamAreaHeight );\r\n\t\tvar bbox = BBox.FromPositionAndSize( WorldPosition, new Vector3( RoamRadius, RoamRadius, RoamAreaHeight ) );\r\n\r\n\t\t// vaguely prefer moving in the direction we're looking\r\n\t\tbbox += ((roamSize.WithZ( 0 ) * WorldRotation.Forward) * 0.4f);\r\n\r\n\t\tfor ( int i = 0; i < 10; i++ )\r\n\t\t{\r\n\t\t\tvar p = Scene.NavMesh.GetClosestPoint( bbox.RandomPointInside );\r\n\t\t\tif ( !p.HasValue || p.Value.Distance( WorldPosition ) > RoamRadius )\r\n\t\t\t\tcontinue;\r\n\r\n\t\t\t// check if navable\r\n\t\t\tvar path = Scene.NavMesh.CalculatePath( new Sandbox.Navigation.CalculatePathRequest()\r\n\t\t\t{\r\n\t\t\t\tStart = WorldPosition,\r\n\t\t\t\tTarget = p.Value\r\n\t\t\t} );\r\n\r\n\t\t\tif ( path.Status != Sandbox.Navigation.NavMeshPathStatus.Partial && path.Status != Sandbox.Navigation.NavMeshPathStatus.Complete )\r\n\t\t\t\tcontinue;\r\n\r\n\t\t\treturn p.Value;\r\n\t\t}\r\n\r\n\t\treturn default;\r\n\t}\r\n\r\n\t/// <summary>\r\n\t/// Looks for a target, closest non-friend player is the target\r\n\t/// </summary>\r\n\tprivate void LookForTarget( bool withRange = true )\r\n\t{\r\n\t\tvar allPlayers = Scene.GetAllComponents<Player>()\r\n\t\t\t.Where( x => Instigator.IsValid() ? x.PlayerData != Instigator : true );\r\n\r\n\t\tif ( withRange )\r\n\t\t\tallPlayers = allPlayers.Where( x => x.WorldPosition.DistanceSquared( WorldPosition ) < TargetDetectionRangeSquared );\r\n\r\n\t\tallPlayers = allPlayers.OrderBy( x => x.WorldPosition.DistanceSquared( WorldPosition ) );\r\n\r\n\t\tif ( allPlayers.Any() ) target = allPlayers.First();\r\n\t}\r\n\r\n\tvoid Attack()\r\n\t{\r\n\t\t// wait the full attack length\r\n\t\tLungeTimer = 0;\r\n\r\n\t\tDoAttackEffects();\r\n\r\n\t\t// We want to take over the rat movement by enabling a rigidbody and just flinging them at a player\r\n\t\tAgent.UpdatePosition = false;\r\n\t\tAgent.UpdateRotation = false;\r\n\r\n\t\tvar dir = (target.WorldPosition - WorldPosition).WithZ( 0 ).Normal;\r\n\r\n\t\tIsOnGround = false;\r\n\t\tRigidbody.Velocity = (dir * LungeVelocity.x) + (Vector3.Up * LungeVelocity.y);\r\n\t\tWorldRotation = Rotation.LookAt( dir );\r\n\r\n\t\ttarget = null;\r\n\t}\r\n\r\n\t[Rpc.Broadcast]\r\n\tvoid DoDeathEffects()\r\n\t{\r\n\t\tif ( Application.IsDedicatedServer ) return;\r\n\r\n\t\tDeathEffects?.Clone( WorldPosition );\r\n\t}\r\n\r\n\t[Rpc.Broadcast]\r\n\tvoid DoAttackEffects()\r\n\t{\r\n\t\tif ( Application.IsDedicatedServer ) return;\r\n\r\n\t\tSound.Play( AttackSound, WorldPosition );\r\n\t}\r\n\r\n\tvoid IDamageable.OnDamage( in DamageInfo damage )\r\n\t{\r\n\t\tDie();\r\n\t}\r\n\r\n\tvoid Die()\r\n\t{\r\n\t\tDoDeathEffects();\r\n\t\tGameObject.Destroy();\r\n\t}\r\n}\r\n"
        },
        {
            "Ident": "facepunch.sbdm",
            "Path": "Items/Pickups/InventoryPickup.cs",
            "FileName": "InventoryPickup.cs",
            "PackageType": "game",
            "CodeKind": "Game",
            "AssetVersionId": 336963,
            "Code": "/// <summary>\r\n/// A pickup that gives an inventory item, like a weapon\r\n/// </summary>\r\npublic sealed class InventoryPickup : BasePickup\r\n{\r\n\t/// <summary>\r\n\t/// A list of prefabs (that have to be inventory items) that are given to the player\r\n\t/// </summary>\r\n\t[Property, Group( \"Inventory\" )] public List<GameObject> Items { get; set; }\r\n\r\n\tprotected override bool OnPickup( Player player, PlayerInventory inventory )\r\n\t{\r\n\t\tif ( Items == null ) return false;\r\n\r\n\t\tbool consumed = false;\r\n\t\tforeach ( var prefab in Items )\r\n\t\t{\r\n\t\t\tif ( inventory.Pickup( prefab ).IsValid() )\r\n\t\t\t{\r\n\t\t\t\tconsumed = true;\r\n\t\t\t\tplayer.PlayerData.AddStat( $\"pickup.inventory.{prefab.Name}\" );\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\treturn consumed;\r\n\t}\r\n}\r\n"
        },
        {
            "Ident": "facepunch.sbdm",
            "Path": "Map/TriggerTeleport.cs",
            "FileName": "TriggerTeleport.cs",
            "PackageType": "game",
            "CodeKind": "Game",
            "AssetVersionId": 336963,
            "Code": "public sealed class TriggerTeleport : Component, Component.ITriggerListener\r\n{\r\n\t/// <summary>\r\n\t/// If not empty, the target must have one of these tags\r\n\t/// </summary>\r\n\t[Property, Group( \"Target\" )] public TagSet Include { get; set; } = new();\r\n\r\n\t/// <summary>\r\n\t/// If not empty, the target must not have one of these tags\r\n\t/// </summary>\r\n\t[Property, Group( \"Target\" )] public TagSet Exclude { get; set; } = new();\r\n\r\n\t[Property] public GameObject Target { get; set; }\r\n\t[Property] public Action<GameObject> OnTeleported { get; set; }\r\n\r\n\tprotected override void DrawGizmos()\r\n\t{\r\n\t\tif ( !Target.IsValid() )\r\n\t\t\treturn;\r\n\r\n\t\tGizmo.Draw.Arrow( 0, WorldTransform.PointToLocal( Target.WorldPosition ) );\r\n\t}\r\n\r\n\tvoid ITriggerListener.OnTriggerEnter( Collider other )\r\n\t{\r\n\t\tvar go = other.GameObject;\r\n\r\n\t\tif ( !IsValidTarget( ref go ) ) return;\r\n\r\n\t\tgo.WorldPosition = Target.WorldPosition;\r\n\t\tgo.Transform.ClearInterpolation();\r\n\r\n\t\tDoTeleportedEvent( go );\r\n\t}\r\n\r\n\tbool IsValidTarget( ref GameObject go )\r\n\t{\r\n\t\tgo = go.Root;\r\n\t\tif ( go.IsProxy ) return false;\r\n\r\n\t\tif ( !Exclude.IsEmpty && go.Tags.HasAny( Exclude ) )\r\n\t\t\treturn false;\r\n\r\n\t\tif ( !Include.IsEmpty && !go.Tags.HasAny( Include ) )\r\n\t\t\treturn false;\r\n\r\n\t\treturn true;\r\n\t}\r\n\r\n\t[Rpc.Broadcast]\r\n\tvoid DoTeleportedEvent( GameObject obj )\r\n\t{\r\n\t\tOnTeleported?.Invoke( obj );\r\n\t}\r\n}\r\n"
        },
        {
            "Ident": "facepunch.sbdm",
            "Path": "Player/PlayerObserver.cs",
            "FileName": "PlayerObserver.cs",
            "PackageType": "game",
            "CodeKind": "Game",
            "AssetVersionId": 336963,
            "Code": "/// <summary>\r\n/// Dead players become these. They try to observe their last corpse. \r\n/// </summary>\r\npublic sealed class PlayerObserver : Component\r\n{\r\n\tAngles EyeAngles;\r\n\tTimeSince timeSinceStarted;\r\n\r\n\tprotected override void OnEnabled()\r\n\t{\r\n\t\tbase.OnEnabled();\r\n\r\n\t\tEyeAngles = Scene.Camera.WorldRotation;\r\n\t\ttimeSinceStarted = 0;\r\n\t}\r\n\r\n\tprotected override void OnUpdate()\r\n\t{\r\n\t\tif ( IsProxy ) return;\r\n\r\n\t\tvar corpse = Scene.GetAllComponents<DeathCameraTarget>()\r\n\t\t\t\t\t.Where( x => x.Connection == Network.Owner )\r\n\t\t\t\t\t.OrderByDescending( x => x.Created )\r\n\t\t\t\t\t.FirstOrDefault();\r\n\r\n\t\tif ( corpse.IsValid() )\r\n\t\t{\r\n\t\t\tRotateAround( corpse );\r\n\t\t}\r\n\r\n\t\t// Don't allow immediate respawn\r\n\t\tif ( timeSinceStarted < 1 )\r\n\t\t\treturn;\r\n\r\n\t\t// If pressed a button, or has been too long\r\n\t\tif ( Input.Pressed( \"attack1\" ) || Input.Pressed( \"jump\" ) || timeSinceStarted > 4f )\r\n\t\t{\r\n\t\t\tRespawn();\r\n\t\t\tGameObject.Destroy();\r\n\t\t}\r\n\t}\r\n\r\n\t[Rpc.Host( NetFlags.OwnerOnly | NetFlags.Reliable )]\r\n\tpublic void Respawn()\r\n\t{\r\n\t\tGameManager.Current.SpawnPlayer( Network.Owner );\r\n\t\tGameObject.Destroy();\r\n\t}\r\n\r\n\tprivate void RotateAround( Component target )\r\n\t{\r\n\t\t// Find the corpse eyes\r\n\t\tif ( !target.Components.Get<SkinnedModelRenderer>().TryGetBoneTransform( \"head\", out var tx ) )\r\n\t\t{\r\n\t\t\ttx.Position = target.GameObject.GetBounds().Center + Vector3.Up * 25f;\r\n\t\t}\r\n\r\n\t\tvar e = EyeAngles;\r\n\t\te += Input.AnalogLook;\r\n\t\te.pitch = e.pitch.Clamp( -90, 90 );\r\n\t\te.roll = 0.0f;\r\n\t\tEyeAngles = e;\r\n\r\n\t\tvar center = tx.Position;\r\n\t\tvar targetPos = center - EyeAngles.Forward * 150f;\r\n\r\n\t\tvar tr = Scene.Trace.FromTo( center, targetPos ).Radius( 1.0f ).WithoutTags( \"ragdoll\", \"effect\" ).Run();\r\n\r\n\t\tScene.Camera.WorldPosition = Vector3.Lerp( Scene.Camera.WorldPosition, tr.EndPosition, timeSinceStarted, true );\r\n\t\tScene.Camera.WorldRotation = EyeAngles;\r\n\t}\r\n}\r\n"
        },
        {
            "Ident": "facepunch.sbdm",
            "Path": "UI/InventorySlot.razor",
            "FileName": "InventorySlot.razor",
            "PackageType": "game",
            "CodeKind": "Game",
            "AssetVersionId": 336963,
            "Code": "@using Sandbox;\r\n@using Sandbox.UI;\r\n@inherits Panel\r\n\r\n@if ( GetWeapons().Count() < 1 )\r\n{\r\n    return;\r\n}\r\n\r\n<root>\r\n    <div class=\"index\">\r\n        <label>@(Index+1)</label>\r\n    </div>\r\n\r\n    <div class=\"list\">\r\n    @foreach ( var weapon in GetWeapons() )\r\n    {\r\n        <InventoryWeapon Carryable=\"@weapon\" CanSwitch=@weapon.CanSwitch() IsHovered=\"@(weapon == Hovered)\" />\r\n    }\r\n    </div>\r\n\r\n</root>\r\n\r\n@code\r\n{\r\n    public PlayerInventory Inventory { get; set; }\r\n    public Carryable Active { get; set; }\r\n    public Carryable Hovered { get; set; }\r\n\r\n    public int Index { get; set; }\r\n\r\n    IEnumerable<Carryable> GetWeapons()\r\n    {\r\n        if (!Inventory.IsValid()) return Enumerable.Empty<Carryable>();\r\n\r\n        return Inventory.Carryables.Where(x => x.PreferredSlot == Index).OrderBy(x => x.SlotOrder);\r\n    }\r\n\r\n    public override void Tick()\r\n    {\r\n        base.Tick();\r\n\r\n        SetClass(\"active\", Hovered?.PreferredSlot == Index || Active?.PreferredSlot == Index);\r\n    }\r\n\r\n\tprotected override int BuildHash() => HashCode.Combine( GetWeapons().Count() );\r\n\r\n}\r\n"
        },
        {
            "Ident": "facepunch.sbdm",
            "Path": "Weapons/BaseWeapon/BaseWeapon.cs",
            "FileName": "BaseWeapon.cs",
            "PackageType": "game",
            "CodeKind": "Game",
            "AssetVersionId": 336963,
            "Code": "using Sandbox.Rendering;\n\n/// <summary>\n/// A <see cref=\"Carryable\"/> that shoots. Magazines, reserve ammo, reloading and fire timing all live\n/// on <see cref=\"BaseCombatWeapon\"/> now - this keeps the deathmatch-facing names and the dry-fire /\n/// auto-reload behaviour our weapons are written against.\n/// </summary>\npublic partial class BaseWeapon : Carryable\n{\n\t/// <summary>\n\t/// Rounds in the magazine. Read-only alias for the engine's <see cref=\"BaseCombatWeapon.Clip1\"/>,\n\t/// which is host owned - spend it through <see cref=\"TakeAmmo(int)\"/> so the host sees the spend,\n\t/// rather than assigning, which a client couldn't make stick.\n\t/// </summary>\n\tpublic int ClipContents => Clip1;\n\n\t/// <summary>\n\t/// Adds a delay, making it so we can't shoot for the specified time\n\t/// </summary>\n\t/// <param name=\"seconds\"></param>\n\tpublic void AddShootDelay( float seconds )\n\t{\n\t\tSetNextFire( seconds );\n\t}\n\n\t/// <summary>\n\t/// The dry fire sound if we have no ammo\n\t/// </summary>\n\tprivate static SoundEvent DefaultDryFireSound = new SoundEvent( \"audio/sounds/dry_fire.sound\" );\n\n\t/// <summary>\n\t/// Play a dry fire sound. You should only call this on weapons that can't auto reload - if they can, use <see cref=\"TryAutoReload\"/> instead.\n\t/// </summary>\n\tpublic override void DryFire()\n\t{\n\t\tif ( HasAmmo() )\n\t\t\treturn;\n\n\t\tif ( IsReloading )\n\t\t\treturn;\n\n\t\tif ( NextPrimaryFire > 0 )\n\t\t\treturn;\n\n\t\tGameObject.PlaySound( DryFireSound ?? DefaultDryFireSound );\n\t}\n\n\t/// <summary>\n\t/// Player has fired an empty gun - play dry fire sound and start reloading. You should only call this on weapons that can reload - if they can't, use <see cref=\"DryFire\"/> instead.\n\t/// </summary>\n\tpublic virtual void TryAutoReload()\n\t{\n\t\tif ( HasAmmo() )\n\t\t\treturn;\n\n\t\tif ( IsReloading )\n\t\t\treturn;\n\n\t\tif ( NextPrimaryFire > 0 )\n\t\t\treturn;\n\n\t\tDryFire();\n\n\t\tAddShootDelay( 0.1f );\n\n\t\tif ( CanReload() )\n\t\t\tReload();\n\t\telse\n\t\t\tSwitchAway();\n\t}\n\n\t/// <summary>\n\t/// Are we allowed to shoot this weapon? Can be overriden per-weapon\n\t/// </summary>\n\t/// <returns></returns>\n\tpublic virtual bool CanShoot()\n\t{\n\t\tif ( !HasAmmo() ) return false;\n\t\tif ( IsReloading ) return false;\n\t\tif ( NextPrimaryFire > 0 ) return false;\n\n\t\treturn true;\n\t}\n\n\tpublic override void DrawHud( HudPainter painter, Vector2 crosshair )\n\t{\n\t\tDrawCrosshair( painter, crosshair );\n\t}\n\n\tpublic override void DrawCrosshair( HudPainter hud, Vector2 center )\n\t{\n\t\tColor color = Color.Red;\n\n\t\thud.DrawLine( center + Vector2.Left * 32, center + Vector2.Left * 15, 3, color );\n\t\thud.DrawLine( center - Vector2.Left * 32, center - Vector2.Left * 15, 3, color );\n\t\thud.DrawLine( center + Vector2.Up * 32, center + Vector2.Up * 15, 3, color );\n\t\thud.DrawLine( center - Vector2.Up * 32, center - Vector2.Up * 15, 3, color );\n\t}\n\n\tpublic override void OnControl( Player player )\n\t{\n\t\tbase.OnControl( player );\n\n\t\tbool wantsToCancelReload = Input.Pressed( \"Attack1\" ) || Input.Pressed( \"Attack2\" );\n\t\tif ( CanCancelReload && IsReloading && wantsToCancelReload && HasAmmo() )\n\t\t{\n\t\t\tCancelReload();\n\t\t}\n\n\t\tif ( CanReload() && Input.Pressed( \"reload\" ) )\n\t\t{\n\t\t\tReload();\n\t\t}\n\t}\n}\n"
        },
        {
            "Ident": "facepunch.sbdm",
            "Path": "Weapons/GaussGun/GaussWeapon.cs",
            "FileName": "GaussWeapon.cs",
            "PackageType": "game",
            "CodeKind": "Game",
            "AssetVersionId": 336963,
            "Code": "using Sandbox.Rendering;\r\nusing Sandbox.Utility;\r\n\r\npublic class GaussWeapon : BaseBulletWeapon\r\n{\r\n\t/// <summary>\r\n\t/// Having this as its own event separate from weapon events until we have a reason to use it for another weapon\r\n\t/// </summary>\r\n\tpublic interface IGaussWeaponEvents : ISceneEvent<IGaussWeaponEvents>\r\n\t{\r\n\t\t/// <summary>\r\n\t\t/// Called when we consume a bit of ammo\r\n\t\t/// </summary>\r\n\t\tvoid OnConsumedAmmo();\r\n\t}\r\n\r\n\t/// <summary>\r\n\t/// Shot frequency delay\r\n\t/// </summary>\r\n\t[Property] public float TimeBetweenShots { get; set; } = 0.1f;\r\n\r\n\t/// <summary>\r\n\t/// How much damage\r\n\t/// </summary>\r\n\t[Property] public float Damage { get; set; } = 12.0f;\r\n\r\n\t/// <summary>\r\n\t/// How many units deep can geometry be for us to penetrate directly through it.\r\n\t/// </summary>\r\n\t[Property] public float PenetrationThickness { get; set; } = 32f;\r\n\r\n\t[Property] public GameObject ImpactEffectPrefab { get; set; }\r\n\r\n\t[Property] public GameObject LargeImpactEffect { get; set; }\r\n\r\n\t/// <summary>\r\n\t/// What looping sound should we play while charging the gun\r\n\t/// </summary>\r\n\t[Property, Feature( \"Charge\" )]\r\n\tpublic SoundEvent ChargeSoundEvent { get; set; }\r\n\r\n\t/// <summary>\r\n\t/// A curve to get a damage value directly from ChargePower \r\n\t/// </summary>\r\n\t[Property, Feature( \"Charge\" )]\r\n\tpublic Curve ChargeDamage { get; set; }\r\n\r\n\t/// <summary>\r\n\t/// How much force is applied when we shoot a charged shot\r\n\t/// </summary>\r\n\t[Property, Feature( \"Charge\" )]\r\n\tpublic Curve ChargeForce { get; set; }\r\n\r\n\t/// <summary>\r\n\t/// How frequently does the ammo drain whilst charging the gauss gun\r\n\t/// </summary>\r\n\t[Property, Feature( \"Charge\" )]\r\n\tpublic float ChargeAmmoDrainFrequency { get; set; } = 0.2f;\r\n\r\n\t/// <summary>\r\n\t/// How long do we charge for until the gun overloads\r\n\t/// </summary>\r\n\t[Property, Feature( \"Overload\" )]\r\n\tpublic float OverloadTime { get; set; } = 5f;\r\n\r\n\t/// <summary>\r\n\t/// How much damage to inflict on the player if the gun overloads\r\n\t/// </summary>\r\n\t[Property, Feature( \"Overload\" )]\r\n\tpublic float OverloadDamage { get; set; } = 20f;\r\n\r\n\t[Property, Feature( \"Overload\" )]\r\n\tpublic SoundEvent OverloadSound { get; set; }\r\n\r\n\t/// <summary>\r\n\t/// The charge loop sound handle, we handle its lifetime.\r\n\t/// </summary>\r\n\tSoundHandle chargeHandle;\r\n\r\n\t/// <summary>\r\n\t/// Normalized charge power between 0 and 1\r\n\t/// </summary>\r\n\tfloat chargePower = 0;\r\n\r\n\t/// <summary>\r\n\t/// Is this gun charging up to shoot?\r\n\t/// </summary>\r\n\tbool isCharging = false;\r\n\r\n\t/// <summary>\r\n\t/// How long has it been since we started charging?\r\n\t/// </summary>\r\n\tTimeSince timeSinceChargeStart = 0;\r\n\r\n\t/// <summary>\r\n\t/// How long since we drained ammo while charged?\r\n\t/// </summary>\r\n\tTimeSince timeSinceAmmoTick;\r\n\r\n\tprotected override void OnDisabled()\r\n\t{\r\n\t\tStopCharge();\r\n\t\tbase.OnDisabled();\r\n\t}\r\n\r\n\t[Rpc.Host]\r\n\tprivate void HurtSelf()\r\n\t{\r\n\t\tif ( !Owner.IsValid() ) return;\r\n\r\n\t\tOwner.OnDamage( new DamageInfo( OverloadDamage, Owner.GameObject, GameObject ) );\r\n\t\tSound.Play( OverloadSound, WorldPosition );\r\n\t}\r\n\r\n\tpublic override void OnControl( Player player )\r\n\t{\r\n\t\tbase.OnControl( player );\r\n\r\n\t\tif ( isCharging )\r\n\t\t{\r\n\t\t\tif ( chargeHandle is not null )\r\n\t\t\t\tchargeHandle.Position = WorldPosition;\r\n\r\n\t\t\tif ( timeSinceChargeStart > OverloadTime )\r\n\t\t\t{\r\n\t\t\t\tStopCharge();\r\n\t\t\t\tHurtSelf();\r\n\r\n\t\t\t\tif ( !player.Controller.ThirdPerson && player.IsLocalPlayer )\r\n\t\t\t\t{\r\n\t\t\t\t\tvar target = new Vector3( Random.Shared.Float( -10, -15 ), Random.Shared.Float( -25, 0 ), 0 );\r\n\r\n\t\t\t\t\tnew Sandbox.CameraNoise.Punch( target, 1.0f, 3, 0.5f );\r\n\t\t\t\t\tnew Sandbox.CameraNoise.Shake( 0.3f, 1.2f );\r\n\t\t\t\t}\r\n\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\r\n\t\t\tif ( chargePower < 1f && timeSinceAmmoTick > ChargeAmmoDrainFrequency )\r\n\t\t\t{\r\n\t\t\t\ttimeSinceAmmoTick = 0;\r\n\r\n\t\t\t\tif ( !TakeAmmo( 1 ) )\r\n\t\t\t\t{\r\n\t\t\t\t\tShootCharged( player );\r\n\t\t\t\t\treturn;\r\n\t\t\t\t}\r\n\r\n\t\t\t\tIGaussWeaponEvents.PostToGameObject( GameObject.Root, x => x.OnConsumedAmmo() );\r\n\t\t\t}\r\n\r\n\t\t\tchargePower += 0.5f * Time.Delta;\r\n\t\t\tchargePower = chargePower.Clamp( 0, 1 );\r\n\t\t\tchargeHandle.Pitch = chargePower.Remap( 0, 1, 0.5f, 1.2f );\r\n\r\n\t\t\tif ( !player.Controller.ThirdPerson && player.IsLocalPlayer )\r\n\t\t\t{\r\n\t\t\t\tnew Sandbox.CameraNoise.Shake( 0.3f * chargePower.Remap( 0, 1, 0.5f, 3f ), Time.Delta );\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tif ( !isCharging && Input.Down( \"attack1\" ) )\r\n\t\t{\r\n\t\t\tShoot( player );\r\n\t\t}\r\n\r\n\t\tif ( Input.Pressed( \"Attack2\" ) )\r\n\t\t{\r\n\t\t\tif ( !TakeAmmo( 1 ) )\r\n\t\t\t{\r\n\t\t\t\tTryAutoReload();\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\t\t\telse if ( CanShoot() )\r\n\t\t\t{\r\n\t\t\t\ttimeSinceChargeStart = 0;\r\n\t\t\t\tisCharging = true;\r\n\t\t\t\tchargeHandle?.Stop();\r\n\t\t\t\tchargeHandle = Sound.Play( ChargeSoundEvent );\r\n\r\n\t\t\t\tStartAttack();\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tif ( Input.Released( \"Attack2\" ) && isCharging )\r\n\t\t{\r\n\t\t\tStopAttack();\r\n\t\t\tShootCharged( player );\r\n\t\t}\r\n\t}\r\n\r\n\tpublic override bool IsInUse() => isCharging;\r\n\r\n\t/// <summary>\r\n\t/// Shoot a charged shot\r\n\t/// </summary>\r\n\t/// <param name=\"player\"></param>\r\n\tvoid ShootCharged( Player player )\r\n\t{\r\n\t\tShootBullet( player, ChargeDamage.Evaluate( chargePower ), true, true );\r\n\r\n\t\t// Fling the player \r\n\t\tvar controller = player.GetComponent<PlayerController>();\r\n\r\n\t\t// Should expose all these to properties\r\n\t\tcontroller.Jump( -player.EyeTransform.ForwardRay.Forward * ChargeForce.Evaluate( chargePower ) );\r\n\r\n\t\tStopCharge();\r\n\t}\r\n\r\n\t/// <summary>\r\n\t/// Stop charging the gun\r\n\t/// </summary>\r\n\tvoid StopCharge()\r\n\t{\r\n\t\tisCharging = false;\r\n\t\tchargeHandle?.Stop();\r\n\t\tchargePower = 0;\r\n\t}\r\n\r\n\t/// <summary>\r\n\t/// Constructs a trace and returns it, doesn't run it yet!\r\n\t/// </summary>\r\n\tSceneTrace GetBulletTrace( Player player, Vector3 start, Vector3 end, float radius )\r\n\t{\r\n\t\treturn Scene.Trace.Ray( start, end )\r\n\t\t\t.IgnoreGameObjectHierarchy( player.GameObject )\r\n\t\t\t.WithCollisionRules( \"bullet\" )\r\n\t\t\t.UseHitboxes()\r\n\t\t\t.Size( radius );\r\n\t}\r\n\r\n\t/// <summary>\r\n\t/// Runs a trace with all the data we have supplied it, and returns the result\r\n\t/// </summary>\r\n\tIEnumerable<SceneTraceResult> GetShootTraceResults( Player player )\r\n\t{\r\n\t\tvar hits = new List<SceneTraceResult>();\r\n\r\n\t\tvar start = player.EyeTransform.Position;\r\n\t\tvar rot = Rotation.LookAt( player.EyeTransform.Forward );\r\n\r\n\t\tvar forward = rot.Forward.WithAimCone( 2 );\r\n\r\n\t\tvar original = GetBulletTrace( player, start, player.EyeTransform.Position + forward * 4096f, 2f )\r\n\t\t\t\t\t\t.RunAll();\r\n\r\n\r\n\t\tif ( original.Count() < 1 )\r\n\t\t{\r\n\t\t\thits.Add( GetBulletTrace( player, start, player.EyeTransform.Position + forward * 4096f, 2f ).Run() );\r\n\t\t\treturn hits;\r\n\t\t}\r\n\r\n\t\t// Run through and fix the start positions for the traces\r\n\t\t// By using the last end position as the start\r\n\t\tvar startPos = original.ElementAt( 0 ).StartPosition;\r\n\t\tList<SceneTraceResult> fixedPath = new();\r\n\t\tfor ( int i = 0; i < original.Count(); i++ )\r\n\t\t{\r\n\t\t\tvar el = original.ElementAt( i );\r\n\r\n\t\t\tfixedPath.Add( el with { StartPosition = startPos } );\r\n\t\t\tstartPos = el.EndPosition;\r\n\t\t}\r\n\r\n\t\tvar entries = new List<(SceneTraceResult Trace, float Thickness)>();\r\n\r\n\t\t// Then, trace backwards from the end so we can get exit points and thickness\r\n\t\tfor ( int i = fixedPath.Count - 1; i >= 0; i-- )\r\n\t\t{\r\n\t\t\tvar el = fixedPath.ElementAt( i );\r\n\r\n\t\t\t// Do a trace back, from the end position to the start, this'll give us the LAST entry's exit point.\r\n\t\t\tvar backTrace = GetBulletTrace( player, el.EndPosition, el.StartPosition, 2f )\r\n\t\t\t\t\t\t\t.Run();\r\n\r\n\t\t\tvar impact = backTrace.EndPosition;\r\n\r\n\t\t\t// From that, we can calculate the surface thickness\r\n\t\t\tfloat thickness = (el.StartPosition - impact).Length;\r\n\r\n\t\t\t// Return the element starting at the exit point, it's more useful that way.\r\n\t\t\tel = el with { StartPosition = impact };\r\n\t\t\tentries.Insert( 0, (el, thickness) );\r\n\t\t}\r\n\r\n\t\t// Thickness detection\r\n\t\t{\r\n\t\t\tvar thickness = 0f;\r\n\t\t\tforeach ( var el in entries )\r\n\t\t\t{\r\n\t\t\t\tthickness += el.Thickness;\r\n\t\t\t\tif ( thickness >= PenetrationThickness )\r\n\t\t\t\t\tbreak;\r\n\r\n\t\t\t\thits.Add( el.Trace );\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\treturn hits\r\n\t\t\t.Where( x => x.Hit && x.Distance > 0f );\r\n\t}\r\n\r\n\tvoid Shoot( Player player )\r\n\t{\r\n\t\tif ( !CanShoot() || !TakeAmmo( 1 ) )\r\n\t\t{\r\n\t\t\tTryAutoReload();\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tShootBullet( player, Damage );\r\n\t}\r\n\r\n\tpublic void ShootBullet( Player player, float damage, bool isCharged = false, bool shouldRicochet = true )\r\n\t{\r\n\t\tAddShootDelay( TimeBetweenShots );\r\n\r\n\t\tbool shouldPenetrate = isCharged;\r\n\r\n\t\tIGaussWeaponEvents.PostToGameObject( GameObject.Root, x => x.OnConsumedAmmo() );\r\n\r\n\t\tint count = 0;\r\n\t\tvar traces = GetShootTraceResults( player );\r\n\r\n\t\tforeach ( var tr in traces )\r\n\t\t{\r\n\t\t\tShootEffects( tr.EndPosition, false, tr.Normal, tr.GameObject, tr.Surface, count > 0 ? tr.StartPosition : null );\r\n\t\t\tTraceAttack( TraceAttackInfo.From( tr, damage, damage > 70 ? [DamageTags.GibAlways] : null ) );\r\n\t\t\tcount++;\r\n\r\n\t\t\tif ( player.IsLocalPlayer )\r\n\t\t\t{\r\n\t\t\t\tHitMarker.CreateFromTrace( tr );\r\n\t\t\t}\r\n\r\n\t\t\tif ( tr.Hit )\r\n\t\t\t{\r\n\t\t\t\tSpecialImpactEffects( tr.EndPosition, tr.Normal, tr.GameObject, tr.Surface );\r\n\t\t\t}\r\n\r\n\t\t\tif ( !shouldPenetrate ) break;\r\n\t\t}\r\n\r\n\t\tTimeSinceShoot = 0;\r\n\r\n\t\tif ( shouldRicochet && traces.Any() )\r\n\t\t{\r\n\t\t\t// Grab the last sufficient trace, we only want to ricochet at the end\r\n\t\t\tvar tr = traces.Last();\r\n\r\n\t\t\tvar reflectDir = Vector3.Reflect( tr.Direction, tr.Normal ).Normal;\r\n\t\t\tvar angle = reflectDir.Angle( tr.Direction );\r\n\r\n\t\t\t// Some acute angle\r\n\t\t\tif ( angle < 45f )\r\n\t\t\t{\r\n\t\t\t\ttr = GetBulletTrace( player, tr.EndPosition, tr.EndPosition + (reflectDir * 4096), 2f ).Run();\r\n\r\n\t\t\t\tShootEffects( tr.EndPosition, false, tr.Normal, tr.GameObject, tr.Surface, count > 0 ? tr.StartPosition : null );\r\n\t\t\t\tTraceAttack( TraceAttackInfo.From( tr, damage ) );\r\n\r\n\t\t\t\tif ( player.IsLocalPlayer )\r\n\t\t\t\t{\r\n\t\t\t\t\tHitMarker.CreateFromTrace( tr );\r\n\t\t\t\t}\r\n\r\n\t\t\t\tif ( tr.Hit )\r\n\t\t\t\t{\r\n\t\t\t\t\tSpecialImpactEffects( tr.EndPosition, tr.Normal, tr.GameObject, tr.Surface );\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tplayer.Controller.EyeAngles += new Angles( Random.Shared.Float( -0.2f, -0.3f ), Random.Shared.Float( -0.1f, 0.1f ), 0 );\r\n\r\n\t\tif ( !player.Controller.ThirdPerson && player.IsLocalPlayer )\r\n\t\t{\r\n\t\t\tvar target = new Vector3( Random.Shared.Float( -10, -15 ), Random.Shared.Float( -10, 0 ), 0 );\r\n\t\t\ttarget *= chargePower.Remap( 0, 1, 1, 10 );\r\n\r\n\t\t\tnew Sandbox.CameraNoise.Punch( target, 1.0f, 3, 0.5f );\r\n\t\t\tnew Sandbox.CameraNoise.Shake( 0.3f, 1.2f );\r\n\t\t}\r\n\t}\r\n\r\n\tpublic override void DrawCrosshair( HudPainter hud, Vector2 center )\r\n\t{\r\n\t\tvar tss = TimeSinceShoot.Relative.Remap( 0, 0.2f, 1, 0 );\r\n\r\n\t\tvar gap = 6 + Easing.EaseOut( tss ) * 32;\r\n\t\tvar len = 6;\r\n\t\tvar w = 2;\r\n\r\n\t\tColor color = !CanShoot() ? UI.CrosshairInactive : UI.CrosshairActive;\r\n\r\n\t\thud.DrawLine( center + Vector2.Left * (len + gap) * 2, center + Vector2.Left * gap * 2, w, color );\r\n\t\thud.DrawLine( center - Vector2.Left * (len + gap) * 2, center - Vector2.Left * gap * 2, w, color );\r\n\t\thud.DrawLine( center + Vector2.Up * (len + gap), center + Vector2.Up * gap, w, color );\r\n\t\thud.DrawLine( center - Vector2.Up * (len + gap), center - Vector2.Up * gap, w, color );\r\n\t}\r\n\r\n\t[Rpc.Broadcast]\r\n\tpublic void SpecialImpactEffects( Vector3 hitpoint, Vector3 normal, GameObject hitObject, Surface hitSurface )\r\n\t{\r\n\t\tif ( Application.IsDedicatedServer ) return;\r\n\r\n\t\tif ( ImpactEffectPrefab is null )\r\n\t\t\treturn;\r\n\r\n\t\tvar impact = ImpactEffectPrefab.Clone();\r\n\t\timpact.WorldPosition = hitpoint + normal;\r\n\t\timpact.WorldRotation = Rotation.LookAt( normal ) * new Angles( 90, 0, 0 );\r\n\t\timpact.SetParent( hitObject, true );\r\n\t}\r\n}\r\n"
        },
        {
            "Ident": "facepunch.sbdm",
            "Path": "Weapons/WeaponModel/WeaponModel.cs",
            "FileName": "WeaponModel.cs",
            "PackageType": "game",
            "CodeKind": "Game",
            "AssetVersionId": 336963,
            "Code": "/// <summary>\n/// Base for our view and world model components. <see cref=\"BaseWeaponModel\"/> supplies the muzzle\n/// and shell-eject attachments, the effect prefabs and the deploy/attack/reload presentation hooks -\n/// this exists so the deathmatch view and world models share a type of their own.\n/// </summary>\npublic abstract class WeaponModel : BaseWeaponModel\n{\n}\n"
        },
        {
            "Ident": "facepunch.sbdm",
            "Path": "Weapons/WeaponModel/WorldModel.cs",
            "FileName": "WorldModel.cs",
            "PackageType": "game",
            "CodeKind": "Game",
            "AssetVersionId": 336963,
            "Code": "using static BaseWeapon;\r\n\r\npublic sealed class WorldModel : WeaponModel, IWeaponEvent\r\n{\r\n\tvoid IWeaponEvent.OnAttack( IWeaponEvent.AttackEvent e )\r\n\t{\r\n\t\tRenderer?.Set( \"b_attack\", true );\r\n\r\n\t\tif ( e.isFirstPerson )\r\n\t\t\treturn;\r\n\r\n\t\tDoMuzzleEffect();\r\n\t\tDoEjectBrass();\r\n\t}\r\n\r\n\tvoid IWeaponEvent.CreateRangedEffects( BaseWeapon weapon, Vector3 hitPoint, Vector3? origin )\r\n\t{\r\n\t\tif ( weapon.ViewModel.IsValid() )\r\n\t\t\treturn;\r\n\r\n\t\tDoTracerEffect( hitPoint, origin );\r\n\t}\r\n}\r\n"
        },
        {
            "Ident": "facepunch.sbdm",
            "Path": "styles/base.scss",
            "FileName": "base.scss",
            "PackageType": "game",
            "CodeKind": "Game",
            "AssetVersionId": 336963,
            "Code": "@import \"base/_splitcontainer.scss\";\r\n@import \"base/_navigator.scss\";\r\n\r\nbutton\r\n{\r\n\tcursor: pointer;\r\n}\r\n\r\nIconPanel\r\n{\r\n\tfont-family: Material Icons;\r\n}\r\n\r\n.is-half\r\n{\r\n\twidth: 50%;\r\n}\r\n\r\n.is-third\r\n{\r\n\twidth: 33%;\r\n}\r\n\r\n.is-quarter\r\n{\r\n\twidth: 25%;\r\n}\r\n\r\nbutton.has-subtitle\r\n{\r\n\tposition: relative;\r\n\tflex-direction: column;\r\n\tjustify-content: flex-start;\r\n\talign-items: flex-start;\r\n\tpadding-left: 40px; // icon space\r\n\r\n\t.iconpanel\r\n\t{\r\n\t\tposition: absolute;\r\n\t\tleft: 5px;\r\n\t\ttop: 0;\r\n\t\tbottom: 0;\r\n\t\talign-items: center;\r\n\t}\r\n\r\n\t.button-label\r\n\t{\r\n\t\tfont-weight: bold;\r\n\t}\r\n\r\n\t.button-subtitle\r\n\t{\r\n\t\tfont-size: 12px;\r\n\t\topacity: 0.5;\r\n\t\tmix-blend-mode: lighten;\r\n\t}\r\n}"
        },
        {
            "Ident": "facepunch.sbdm",
            "Path": "ui/dropdown.cs.scss",
            "FileName": "dropdown.cs.scss",
            "PackageType": "game",
            "CodeKind": "Game",
            "AssetVersionId": 336963,
            "Code": ".dropdown\r\n{\r\n\tgap: 2px;\r\n\tflex-grow: 1;\r\n\tcursor: pointer;\r\n\tjustify-content: flex-end;\r\n\talign-items: center;\r\n\tpadding: 0px 12px;\r\n\r\n\t.button-right-column\r\n\t{\r\n\t\tflex-grow: 1;\r\n\t}\r\n}\r\n"
        }
    ]
}