🔍 s&box Package Code Search
Search C# source code, UI razor templates, shaders, and configs across s&box packages.
🔗 Raw Facepunch API Link: https://public.facepunch.com/sbox/code/search/1/?ident=facepunch.sbdm&take=20
Showing code results for query:
*
(154 total matches found)
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;
}
}
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 );
}
}
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;
}
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 );
}
}
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 ) { }
}
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 );
}
}
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 )
{
}
}
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;
}
}
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")]
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();
}
}
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;
}
}
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 );
}
}
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;
}
}
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() );
}
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();
}
}
}
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 );
}
}
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
{
}
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 );
}
}
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;
}
}
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"
}
]
}