🔍 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=rue.house&take=20
Showing code results for query:
*
(154 total matches found)
Game
game
using Sandbox;
namespace BrickJam;
public sealed partial class MansionGame
{
/// <summary>How fast the music fades in/out (per second). Legacy faded on the server tick.</summary>
public float MusicVolumeChangeRate => 0.5f;
/// <summary>Target music volume - background level, the tracks are mastered loud.</summary>
public float MusicVolume => 0.15f;
private SoundHandle musicHandle;
private LevelType musicLevel = LevelType.None;
private float musicVolume;
/// <summary>
/// Client-side music orchestration (every client, host included - music is local audio). Scene-System
/// port of the legacy host-side <c>ProcessMusic</c>: drive the track from the replicated
/// <see cref="CurrentLevelType"/> and crossfade when the level changes.
/// </summary>
protected override void OnUpdate()
{
var track = Level.GetMusic( CurrentLevelType );
if ( CurrentLevelType != musicLevel )
{
// Level changed: fade the old track out, then swap once it's silent.
musicVolume = musicVolume.LerpTo( 0f, MusicVolumeChangeRate * Time.Delta );
if ( musicVolume <= 0.01f )
{
musicHandle?.Stop();
musicHandle = null;
musicLevel = CurrentLevelType;
musicVolume = 0f;
}
ApplyMusicVolume();
return;
}
// Same level: keep the track playing (restart if the asset isn't looped) and fade toward target.
if ( !string.IsNullOrEmpty( track ) )
{
if ( musicHandle is null || musicHandle.IsStopped )
{
musicHandle = Sound.Play( track );
if ( musicHandle is not null )
musicHandle.Volume = musicVolume; // start at the current (faded) level, not full blast
}
musicVolume = musicVolume.LerpTo( MusicVolume, MusicVolumeChangeRate * Time.Delta );
}
else
{
musicVolume = musicVolume.LerpTo( 0f, MusicVolumeChangeRate * Time.Delta );
if ( musicVolume <= 0.01f && musicHandle is not null )
{
musicHandle.Stop();
musicHandle = null;
}
}
ApplyMusicVolume();
}
private void ApplyMusicVolume()
{
if ( musicHandle is not null && !musicHandle.IsStopped )
musicHandle.Volume = musicVolume;
}
}
Game
game
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Sandbox;
namespace GridAStar;
public struct AStarPathBuilder
{
public Grid Grid { get; private set; } = null;
public List<string> TagsToExclude { get; private set; } = new() { "occupied" };
public bool HasTagsToExlude => TagsToExclude.Count() > 0;
public bool HasOccupiedTagToExclude => HasTagsToExlude ? TagsToExclude.Contains( "occupied" ) : false;
public List<string> TagsToInclude { get; private set; } = new();
public bool HasTagsToInclude => TagsToInclude.Count() > 0;
public Dictionary<string, float> TagsToAvoid { get; private set; } = new();
public bool HasTagsToAvoid => TagsToAvoid.Count() > 0;
public bool AcceptsPartial { get; private set; } = false;
public float MaxCheckDistance { get; private set; } = float.PositiveInfinity;
public float MaxDropHeight { get; private set; } = GridSettings.DEFAULT_DROP_HEIGHT;
public Component PathCreator { get; private set; } = null;
public bool HasPathCreator => PathCreator != null;
public AStarPathBuilder() { }
public AStarPathBuilder( Grid grid ) : this()
{
Grid = grid;
}
public static AStarPathBuilder From( Grid grid ) => new AStarPathBuilder( grid );
public AStarPathBuilder WithTags( params string[] tags )
{
foreach ( var tag in tags )
{
if ( !TagsToInclude.Contains( tag ) )
TagsToInclude.Add( tag );
if ( TagsToExclude.Contains( tag ) )
TagsToExclude.Remove( tag );
}
return this;
}
public AStarPathBuilder WithoutTags( params string[] tags )
{
foreach ( var tag in tags )
{
if ( !TagsToExclude.Contains( tag ) )
TagsToExclude.Add( tag );
if ( TagsToInclude.Contains( tag ) )
TagsToInclude.Remove( tag );
}
return this;
}
/// <summary>
/// Which tags to avoid, when found it will add the malus to its total cost.
/// </summary>
public AStarPathBuilder AvoidTag( string tag, float malus )
{
malus = Math.Abs( malus );
if ( !TagsToAvoid.ContainsKey( tag ) )
TagsToAvoid.Add( tag, malus );
else
TagsToAvoid[tag] = malus;
return this;
}
public AStarPathBuilder WithMaxDistance( float maxDistance )
{
MaxCheckDistance = Math.Max( 0f, maxDistance );
return this;
}
public AStarPathBuilder WithMaxDropHeight( float maxDropHeight )
{
MaxDropHeight = Math.Min( Grid.MaxDropHeight, maxDropHeight );
return this;
}
public AStarPathBuilder WithPartialEnabled()
{
AcceptsPartial = true;
return this;
}
public AStarPathBuilder WithPathCreator( Component pathCreator )
{
PathCreator = pathCreator;
return this;
}
public AStarPath Run( Cell startingCell, Cell targetCell, bool reversed = false, bool withCellConnections = true )
{
if ( startingCell is null || targetCell is null || startingCell == targetCell ) return AStarPath.Empty();
return AStarPath.From( this, Grid.ComputePathInternal( this, startingCell, targetCell, CancellationToken.None, reversed, withCellConnections ) );
}
public AStarPath Run( Vector3 startingPosition, Cell targetCell, bool reversed = false, bool withCellConnections = true ) => Run( Grid.GetCell( startingPosition ), targetCell, reversed, withCellConnections );
public AStarPath Run( Cell startingCell, Vector3 targetPosition, bool reversed = false, bool withCellConnections = true ) => Run( startingCell, Grid.GetCell( targetPosition ), reversed, withCellConnections );
public AStarPath Run( Vector3 startingPosition, Vector3 targetPosition, bool reversed = false, bool withCellConnections = true ) => Run( Grid.GetCell( startingPosition ), Grid.GetCell( targetPosition ), reversed, withCellConnections );
internal AStarPath Run( Cell startingCell, Cell targetCell, CancellationToken token, bool reversed = false, bool withCellConnections = true )
{
if ( startingCell is null || targetCell is null || startingCell == targetCell ) return AStarPath.Empty();
return AStarPath.From( this, Grid.ComputePathInternal( this, startingCell, targetCell, token, reversed, withCellConnections ) );
}
public async Task<AStarPath> RunAsync( Cell startingCell, Cell targetCell, CancellationToken token, bool reversed = false, bool withCellConnections = true )
{
var builder = this;
return await GameTask.RunInThreadAsync( () => builder.Run( startingCell, targetCell, token, reversed, withCellConnections ) );
}
public async Task<AStarPath> RunAsync( Vector3 startingPosition, Cell targetCell, CancellationToken token, bool reversed = false, bool withCellConnections = true ) => await RunAsync( Grid.GetCell( startingPosition ), targetCell, token, reversed, withCellConnections );
public async Task<AStarPath> RunAsync( Cell startingCell, Vector3 targetPosition, CancellationToken token, bool reversed = false, bool withCellConnections = true ) => await RunAsync( startingCell, Grid.GetCell( targetPosition ), token, reversed, withCellConnections );
public async Task<AStarPath> RunAsync( Vector3 startingPosition, Vector3 targetPosition, CancellationToken token, bool reversed = false, bool withCellConnections = true ) => await RunAsync( Grid.GetCell( startingPosition ), Grid.GetCell( targetPosition ), token, reversed, withCellConnections );
public async Task<AStarPath> RunInParallel( Cell startingCell, Cell targetCell, CancellationTokenSource tokenSource, bool withCellConnections = true )
{
if ( startingCell is null || targetCell is null || startingCell == targetCell ) return AStarPath.Empty();
var fromTo = RunAsync( startingCell, targetCell, tokenSource.Token, false, withCellConnections );
var toFrom = RunAsync( targetCell, startingCell, tokenSource.Token, true, false ); // You can't reverse some cell connections, like dropping down
var pathResult = await GameTask.WhenAny( fromTo, toFrom ).Result;
// Cancel the other task that hasn't finished yet.
tokenSource.Cancel();
return pathResult;
}
public async Task<AStarPath> RunInParallel( Vector3 startingPosition, Cell targetCell, CancellationTokenSource tokenSource, bool withCellConnections = true ) => await RunInParallel( Grid.GetCell( startingPosition ), targetCell, tokenSource, withCellConnections );
public async Task<AStarPath> RunInParallel( Cell startingCell, Vector3 targetPosition, CancellationTokenSource tokenSource, bool withCellConnections = true ) => await RunInParallel( startingCell, Grid.GetCell( targetPosition ), tokenSource, withCellConnections );
public async Task<AStarPath> RunInParallel( Vector3 startingPosition, Vector3 targetPosition, CancellationTokenSource tokenSource, bool withCellConnections = true ) => await RunInParallel( Grid.GetCell( startingPosition ), Grid.GetCell( targetPosition ), tokenSource, withCellConnections );
}
Game
game
using System;
using Sandbox;
namespace GridAStar;
/// <summary>
/// Like a Vector2, but with integers instead.
/// </summary>
public struct IntVector2 : IEquatable<IntVector2>
{
public int x { get; set; }
public int y { get; set; }
public int this[int index]
{
get
{
int result = index switch
{
0 => x,
1 => y,
_ => throw new IndexOutOfRangeException(),
};
return result;
}
set
{
switch ( index )
{
case 0:
x = value;
break;
case 1:
y = value;
break;
}
}
}
public IntVector2( int x, int y )
{
this.x = x;
this.y = y;
}
public IntVector2 WithX( int x ) => new IntVector2( x, this.y );
public IntVector2 WithY( int y ) => new IntVector2( this.x, y );
public Vector2 ToVector2() => new Vector2( x, y );
public float DistanceSquared( IntVector2 other ) => ToVector2().DistanceSquared( other.ToVector2() );
public override string ToString() => $"{x},{y}";
public override bool Equals( object obj ) => obj is IntVector2 other && Equals( other );
public bool Equals( IntVector2 other ) => x == other.x && y == other.y;
public override int GetHashCode() => HashCode.Combine( x, y );
public static bool operator ==( IntVector2 left, IntVector2 right ) => left.Equals( right );
public static bool operator !=( IntVector2 left, IntVector2 right ) => !(left == right);
public static IntVector2 operator +( IntVector2 a, IntVector2 b ) => new IntVector2( a.x + b.x, a.y + b.y );
public static IntVector2 operator -( IntVector2 a, IntVector2 b ) => new IntVector2( a.x - b.x, a.y - b.y );
}
Game
game
using System;
using System.Linq;
using System.Threading.Tasks;
using System.Collections.Generic;
using Sandbox;
namespace BrickJam;
/// <summary>
/// Level orchestration. The legacy <c>Level</c> was an <c>Entity</c> (for replication); in the Scene
/// System it is a plain server-side object owned by <see cref="MansionGame"/>. The single replicated
/// fact clients need — the current level type — lives on the manager as
/// <see cref="MansionGame.CurrentLevelType"/>.
///
/// DEFERRED: grid generation (<c>GenerateGrid</c>, needs the grid package), music
/// (<c>ProcessMusic</c>), and the black-screen transition + event-log messages (UI system).
/// </summary>
public abstract partial class Level
{
public abstract LevelType Type { get; }
public string Music => GetMusic( Type );
public virtual BBox WorldBox => new( new Vector3( -100000f ), new Vector3( 100000f ) );
/// <summary>
/// Music track per level. Single source of truth so the client-side music player
/// (<see cref="MansionGame"/>) can resolve a track from the replicated <see cref="LevelType"/>
/// without a host-side <see cref="Level"/> instance.
/// </summary>
public static string GetMusic( LevelType type ) => type switch
{
LevelType.Shop => "sounds/music/scary_quest_at_midnight.sound",
LevelType.Mansion => "sounds/music/looming_trees_in_eerie_woods.sound",
LevelType.Dungeon => "sounds/music/malevolent_sightings_in_the_room.sound",
LevelType.Bathrooms => "sounds/music/depths_and_terror.sound",
_ => null,
};
public LegacyUsableComponent Exit { get; set; }
public List<NPC> Monsters { get; } = new();
public TimeSince SinceStarted { get; set; }
public static bool GameIsEnding { get; set; }
protected Scene Scene => MansionGame.Instance.Scene;
public virtual void Compute()
{
var players = Scene.GetAllComponents<Player>().ToList();
if ( players.Count > 0 && players.All( x => !x.IsAlive ) && !GameIsEnding )
{
MansionGame.RestartGame();
GameIsEnding = true;
// TODO (UI): Eventlog "Looks like everyone died, better luck next time!"
}
}
protected void RespawnAll() => MansionGame.Instance.RespawnAll();
public virtual async Task Start()
{
await GameTask.Yield();
RespawnAll();
// Companion spawning for players who bought the upgrade.
foreach ( var player in Scene.GetAllComponents<Player>().ToList() )
{
if ( player.HasUpgrade( "Cartoony Sidekick" ) )
{
var doob = NPC.Create<Doob>( player.WorldPosition, player.WorldRotation );
doob.Owner = player;
player.Doob = doob;
// Who's a Good Boy?: Doob summoned to protect this player.
player.TrackAchievement( GameStats.AchGoodBoy );
}
}
Exit?.GameObject.Destroy();
if ( Type == LevelType.Bathrooms )
{
var finalDoors = Scene.GetAllComponents<ValidFinalDoorPosition>().ToList();
var spot = MansionGame.Random.FromList( finalDoors, null );
if ( spot is not null )
Exit = FinalDoor.Create( spot.WorldPosition, spot.WorldRotation );
}
else
{
var trapdoors = Scene.GetAllComponents<ValidTrapdoorPosition>().Where( x => x.LevelType == Type ).ToList();
var spot = MansionGame.Random.FromList( trapdoors, null );
if ( spot is not null )
Exit = Trapdoor.Create( spot.WorldPosition, spot.WorldRotation );
}
foreach ( var spawner in Scene.GetAllComponents<LootSpawner>().Where( x => x.LevelType == Type ).ToList() )
spawner.SpawnLoot();
foreach ( var door in Scene.GetAllComponents<Door>().Where( x => x.LevelType == Type ).ToList() )
door.Close();
await GameTask.DelayRealtimeSeconds( 1f );
await GenerateGrid();
GameIsEnding = false;
SinceStarted = 0f; // reset the level clock (used by the Slipped on a Soap achievement)
MansionGame.Instance.TimerStart();
}
public virtual async Task End()
{
await GameTask.Yield();
MansionGame.Instance?.ShowBlackScreen( 2f, 1f, 1f );
Exit?.GameObject.Destroy();
Exit = null;
foreach ( var monster in Monsters.ToList() )
RemoveMonster( monster );
foreach ( var spawner in Scene.GetAllComponents<LootSpawner>().ToList() )
spawner.DeleteLoot();
foreach ( var door in Scene.GetAllComponents<Door>().Where( x => WorldBox.Contains( x.WorldPosition ) ).ToList() )
door.Close();
foreach ( var loot in Scene.GetAllComponents<Loot>().ToList() )
loot.GameObject.Destroy();
MansionGame.Instance.TimerStop();
foreach ( var doob in Scene.GetAllComponents<Doob>().ToList() )
{
if ( doob.Owner.IsValid() )
doob.Owner.Doob = null;
doob.GameObject.Destroy();
}
}
public virtual void RegisterMonster( NPC monster ) => Monsters.Add( monster );
public virtual void RemoveMonster( NPC monster )
{
Monsters.Remove( monster );
if ( monster.IsValid() )
monster.GameObject.Destroy();
}
public static Type GetClrType( LevelType type ) => type switch
{
LevelType.Shop => typeof( ShopLevel ),
LevelType.Mansion => typeof( MansionLevel ),
LevelType.Dungeon => typeof( DungeonLevel ),
LevelType.Bathrooms => typeof( BathroomsLevel ),
_ => null,
};
}
Game
game
using System.Linq;
using System.Threading.Tasks;
using Sandbox;
namespace BrickJam;
public sealed class ShopLevel : Level
{
public override LevelType Type => LevelType.Shop;
public override async Task Start()
{
// Shop is a safe hub - no base.Start() (no exit/monsters/loot/grid).
await GameTask.Yield();
RespawnAll();
MansionGame.Instance.TimerStop();
}
}
Game
game
using System;
using System.Linq;
using System.Threading.Tasks;
using Sandbox;
using GridAStar;
namespace BrickJam;
/// <summary>
/// Specter monster - sinks into the floor and rises elsewhere. Scene-System port of legacy <c>Specter</c>,
/// now on grid A*: it chases directly, and on a long idle teleports to a random grid cell (sink → reposition
/// → rise). The lamp light flicker (legacy CapsuleLightEntity) is deferred to the effects system.
/// </summary>
[Title( "Specter" )]
[Category( "NPC" )]
public sealed partial class Specter : NPC
{
public override string ModelPath { get; set; } = "models/specter/specter.vmdl";
public override float WalkSpeed { get; set; } = 120f;
public override float RunSpeed { get; set; } = 320f;
public override float MaxVisionAngle { get; set; } = 240f;
public override float MaxVisionRange { get; set; } = 1000f;
public override float MaxVisionRangeWhenChasing { get; set; } = 1000f;
public override float MaxVisionAngleWhenChasing { get; set; } = 180f;
public override float MaxRememberTime { get; set; } = 3f;
public override string IdleSound => "sounds/specter/spectermoan.sound";
public override float IdleVolume => 1.5f;
public override string AttackSound => "sounds/specter/spectermoan.sound";
public override float AttackVolume => 2f;
public float TimeToTeleport => 2f;
public bool IsLowering => LastTeleport <= TimeToTeleport / 2f + 0.5f;
public bool IsRising => LastTeleport <= TimeToTeleport + 1f && LastTeleport > TimeToTeleport / 2f + 0.5f;
public bool IsTeleporting => IsLowering || IsRising;
[Sync] public TimeSince LastTeleport { get; set; } = 999f;
private static readonly Color LampColor = new( 1f, 0.55f, 0.2f );
private PointLight lamp;
protected override void OnStart()
{
base.OnStart();
// Atmospheric lamp light (legacy CapsuleLightEntity). Purely visual, so each client builds its OWN
// non-networked light and flickers it locally in OnUpdate - NPC logic (Think) is host-only and the
// flicker shouldn't depend on replicated state.
var lampGo = new GameObject( true, "Lamp" ) { Parent = GameObject };
lampGo.LocalPosition = Vector3.Up * 40f;
lamp = lampGo.Components.Create<PointLight>();
lamp.LightColor = LampColor;
lamp.Radius = 350f;
lamp.Shadows = false; // atmospheric glow - skip the (6-face) shadow pass for this point light
}
protected override void OnUpdate()
{
if ( !lamp.IsValid() )
return;
// Eerie flicker; fade the lamp out while the specter is sunk into the floor (teleporting).
var t = Time.Now;
var flicker = 0.7f + 0.18f * MathF.Sin( t * 27f ) + 0.12f * MathF.Sin( t * 11.3f );
var visible = IsTeleporting ? 0f : 1f;
lamp.LightColor = LampColor * (flicker * visible * 4f);
}
public override void ComputeIdleAndSeek()
{
// While sinking/rising we don't make new decisions; ComputeMotion drives the vertical move.
if ( IsTeleporting )
return;
if ( InVision.Count > 0 )
{
Target = InVision.OrderBy( x => x.Key.WorldPosition.Distance( WorldPosition ) ).FirstOrDefault().Key;
if ( Target is Player player && player.Doob.IsValid() )
Target = player.Doob;
LastTarget = Target;
}
else
{
Target = null;
if ( !IsFollowingPath && nextIdle && CurrentGrid is not null )
{
var isLongIdle = MansionGame.Random.NextSingle() <= 0.2f;
var allCells = CurrentGrid.AllCells.ToList();
Cell chosen = null;
for ( var tried = 0; tried < 20 && allCells.Count > 0; tried++ )
{
var candidate = MansionGame.Random.FromList( allCells, null );
if ( candidate is null )
break;
var dist = candidate.Position.Distance( WorldPosition );
if ( isLongIdle ? dist >= 1000f : (dist >= 400f && dist <= 1000f) )
{
chosen = candidate;
break;
}
}
if ( chosen != null )
{
if ( isLongIdle )
Teleport( chosen.Position );
else
NavigateTo( chosen );
}
nextIdle = MansionGame.Random.NextSingle() * 1f + 1f;
LastTarget = null;
}
}
if ( Target.IsValid() && Target.WorldPosition.Distance( WorldPosition ) <= KillRange )
{
if ( Target is Player p )
_ = CatchPlayer( p );
else if ( Target is Doob d )
_ = CatchDoob( d );
}
if ( nextIdleSound && !string.IsNullOrEmpty( IdleSound ) )
{
SoundExtensions.BroadcastPlay( IdleSound, WorldPosition, IdleVolume );
nextIdleSound = MansionGame.Random.NextSingle() * 4f + 4f;
}
}
public override void ComputeMotion()
{
if ( !IsTeleporting )
{
base.ComputeMotion();
return;
}
// Sink into / rise out of the floor under manual control (off the grid).
if ( IsLowering )
WorldPosition += Vector3.Down * CollisionHeight * Time.Delta / (TimeToTeleport / 2f);
else if ( IsRising )
WorldPosition += Vector3.Up * CollisionHeight * Time.Delta / (TimeToTeleport / 2f);
}
public async void Teleport( Vector3 position )
{
LastTeleport = 0;
MansionGame.Instance?.PlayEffect( "prefabs/particles/specter_teleport.prefab", WorldPosition, Rotation.Identity );
await Task.DelayRealtimeSeconds( TimeToTeleport * 0.5f + 0.5f );
WorldPosition = position + Vector3.Down * CollisionHeight;
}
}
Game
game
@using System
@using Sandbox
@using Sandbox.UI
@namespace BrickJam.UI
@inherits PanelComponent
<root>
@if ( Loot.IsValid() )
{
<div class="container">
<span>@Loot.FullName</span>
<div class="row">
<span class="currency">$</span><span>@($"{Loot.MonetaryValue:n0}")</span>
</div>
</div>
}
</root>
@code {
private Loot loot;
private Loot Loot => loot ??= GetComponentInParent<Loot>();
protected override int BuildHash() => HashCode.Combine( Loot?.FullName, Loot?.MonetaryValue ?? 0 );
}
<style>
GroundLootPanel {
transition: transform 0.5s ease-in-out;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
.container {
padding: 10px;
padding-right: 30px;
padding-left: 30px;
font-size: 32px;
align-items: center;
font-family: "alagard";
flex-direction: column;
color: white;
text-shadow: 3px 3px 0px black;
background: linear-gradient(to left, rgba(black, 0) 0%, rgba(black, 0.5) 20%, rgba(black, 0.5) 80%, rgba(black, 0) 100%);
.currency {
padding-right: 4px;
font-size: 22px;
top: 5px;
color: rgba(50, 205, 50, 1);
}
}
&:outro {
transform: scale(0);
}
&:intro {
transform: scale(0);
}
}
</style>
Game
game
@using System
@using Sandbox
@using Sandbox.UI
@namespace BrickJam.UI
@inherits PanelComponent
<root>
@{
var player = Player.Local;
var isSpectator = player?.Spectating ?? false;
var isPlayer = player?.IsValid() ?? false;
}
<MoneyCounter />
<Eventlog />
<PlayerCounter />
<StunIndicator />
<QuickPing />
<SubtitlesList />
<Lockpicker />
<BlackScreen />
<div class="input-hints">
@if ( isSpectator )
{
<div class="hint"><inputglyph action="StopFollowing"/>Stop following</div>
<div class="hint"><inputglyph action="FollowNext"/>Next player</div>
<div class="hint"><inputglyph action="FollowPrevious"/>Previous player</div>
}
else
{
<div class="hint"><inputglyph action="inventory"/>Inventory</div>
<div class="hint"><inputglyph action="chat"/>Chat</div>
<div class="hint"><inputglyph action="ping"/>Quick Ping</div>
}
</div>
@if ( player.IsValid() )
{
<Inventory />
<Shop />
<TextInput Ghost="Say something..." class="chat-input" @ref="ChatInput" onsubmit=@OnChatSubmit />
@if ( !player.SeenTips )
{
<TutorialTips />
}
}
@if ( player.IsValid() && player.IsAlive && !LockpickerBus.IsOpen )
{
<div class="crosshair">
<div class="mark">+</div>
<RadialProgress />
<InteractionTip />
</div>
}
@if ( player.IsValid() && !player.IsAlive )
{
<div class="death">
<span class="title">YOU'RE DEAD!</span>
</div>
}
</root>
@code {
private TextInput ChatInput { get; set; }
protected override void OnUpdate()
{
if ( ChatInput is null )
return;
if ( Input.Pressed( "chat" ) )
{
ChatInput.Focus();
ChatInput.AddClass( "visible" );
}
}
private void OnChatSubmit()
{
if ( ChatInput is null )
return;
var text = ChatInput.Text;
if ( !string.IsNullOrWhiteSpace( text ) )
Player.Local?.Say( text );
ChatInput.Text = "";
ChatInput.RemoveClass( "visible" );
}
protected override int BuildHash() => HashCode.Combine(
Player.Local,
Player.Local?.IsAlive ?? false,
LockpickerBus.IsOpen );
}
<style>
Hud {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
font-family: "alagard";
justify-content: flex-start;
.crosshair {
position: absolute;
top: 0; left: 0;
width: 100%;
height: 100%;
flex-direction: column;
align-items: center;
justify-content: center;
.mark {
color: white;
font-size: 28px;
text-shadow: 2px 2px 0px black;
}
}
.input-hints {
position: absolute;
bottom: 30px;
right: 0px;
align-items: flex-end;
flex-direction: column;
z-index: 3;
.hint {
color: white;
height: 32px;
justify-content: center;
font-size: 32px;
padding-left: 50px;
padding-top: 10px;
padding-bottom: 40px;
padding-right: 10px;
background: linear-gradient(to left, rgba(black, 0.5) 0%, rgba(black, 0.2) 75%, rgba(black, 0) 100%);
margin-top: 5px;
text-shadow: 3px 3px 0px black;
InputGlyph {
width: 32px;
aspect-ratio: 1;
margin-right: 10px;
}
}
}
.chat-input {
position: absolute;
width: 450px;
min-height: 20px;
font-size: 24px;
opacity: 0;
pointer-events: all;
background-color: rgba(37, 64, 98, 1);
box-shadow: 3px 3px 0px 0px rgba(17, 44, 78, 1);
transition: opacity 0.2s ease-in-out, transform 0.2s ease-in-out;
transform: translate(-50% 200px);
text-shadow: 3px 3px 0px black;
left: 50%;
top: 50%;
margin-top: 40px;
&.visible {
opacity: 1;
transform: translate(-50% 0px);
}
}
.death {
position: absolute;
width: 100%;
height: 100%;
left: 0px;
top: 0px;
color: white;
font-size: 32px;
z-index: 2;
backdrop-filter: grayscale(100%);
justify-content: center;
transition: opacity 1s ease-in-out;
opacity: 0;
text-shadow: 4px 4px 0px black;
&.visible {
opacity: 1;
}
.container {
flex-direction: column;
align-items: center;
}
span {
background: linear-gradient(to left, rgba(black, 0) 0%, rgba(black, 0.4) 20%, rgba(black, 0.4) 80%, rgba(black, 0) 100%);
padding: 10px;
padding-left: 40px;
padding-right: 40px;
}
.title {
color: red;
font-size: 64px;
margin-top: 150px;
margin-bottom: 20px;
}
}
}
</style>
Game
game
@using System
@using Sandbox
@using Sandbox.UI
@using WorldPanel = Sandbox.WorldPanel
@namespace BrickJam.UI
@inherits PanelComponent
<root>
@if ( !IsLocal && Player.IsValid() && Player.IsAlive )
{
<div class="container">
<span>@Name</span>
</div>
}
</root>
@code {
private Player player;
private Player Player => player ??= GetComponentInParent<Player>();
// Don't draw your own nametag in first person.
private bool IsLocal => Player == Player.Local;
private string Name => Player.IsValid() && Player.Network.Owner is { } owner ? owner.DisplayName : "player";
protected override int BuildHash() => HashCode.Combine( IsLocal, Name, Player?.IsAlive ?? false );
}
<style>
Nametag {
transition: transform 0.5s ease-in-out;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
.container {
padding: 10px;
padding-right: 30px;
padding-left: 30px;
font-size: 32px;
align-items: center;
font-family: "alagard";
flex-direction: column;
color: white;
text-shadow: 3px 3px 0px black;
background: linear-gradient(to left, rgba(black, 0) 0%, rgba(black, 0.5) 20%, rgba(black, 0.5) 80%, rgba(black, 0) 100%);
}
&:outro {
transform: scale(0);
}
&:intro {
transform: scale(0);
}
}
</style>
Game
game
using System;
using Sandbox;
namespace BrickJam.UI;
/// <summary>Decouples gameplay from the Razor <c>QuickPing</c> panel (see <see cref="EventlogBus"/>).</summary>
public static class PingBus
{
public enum PingType { Enemy, Exit, Loot, Other }
public static event Action<Vector3, PingType> OnPing;
public static void Post( Vector3 worldPosition, PingType type ) => OnPing?.Invoke( worldPosition, type );
}
Game
game
@using System
@using System.Linq
@using System.Collections.Generic
@using Sandbox
@using Sandbox.UI
@namespace BrickJam.UI
@inherits Panel
<root>
@foreach ( var line in lines )
{
<div class="subtitle">
@if ( !string.IsNullOrEmpty( line.Speaker ) )
{
<span class="speaker">@line.Speaker:</span>
}
<span class="text">@line.Text</span>
</div>
}
</root>
@code {
private readonly List<(string Speaker, string Text, TimeUntil Expire)> lines = new();
public SubtitlesList()
{
SubtitleBus.OnSubtitle += Add;
}
public override void OnDeleted()
{
base.OnDeleted();
SubtitleBus.OnSubtitle -= Add;
}
private void Add( string speaker, string text, float duration )
{
lines.Add( (speaker, text, duration) );
if ( lines.Count > 3 )
lines.RemoveAt( 0 );
}
public override void Tick()
{
lines.RemoveAll( l => l.Expire );
}
protected override int BuildHash() => HashCode.Combine( lines.Count, lines.LastOrDefault().Text );
}
<style>
SubtitlesList {
position: absolute;
left: 0; right: 0;
bottom: 60px;
flex-direction: column;
align-items: center;
.subtitle {
flex-direction: row;
padding: 6px 16px;
margin-top: 4px;
background-color: rgba(0,0,0,0.6);
font-size: 26px;
text-shadow: 2px 2px 0px black;
.speaker { color: rgba(255,220,80,1); margin-right: 8px; }
.text { color: white; }
}
}
</style>
Game
game
.switchcontrol
{
flex-direction: row;
width: 100px;
min-height: 24px;
align-items: center;
cursor: pointer;
.switch-frame
{
flex-grow: 0;
flex-shrink: 1;
width: 48px;
height: 16px;
background-color: #fff1;
margin: 0px 5px;
align-items: center;
border-radius: 100px;
transition: all 0.4s linear;
.switch-inner
{
position: relative;
flex-grow: 0;
flex-shrink: 1;
background-color: #999;
width: 25px;
height: 25px;
border-radius: 100px;
left: 20%;
transform: translateX( -50% );
transition: all 0.3s ease-out;
}
}
&.active
{
.switch-frame
{
background-color: #fffa;
}
.switch-inner
{
left: 80%;
background-color: #fff;
}
}
}
Game
game
$form-control-height: 28px !default;
@import "form/_checkbox.scss";
@import "form/_switch.scss";
@import "form/_dropdown.scss";
@import "form/_coloreditor.scss";
@import "form/_colorproperty.scss";
.form
{
flex-direction: column;
align-items: stretch;
justify-content: flex-start;
overflow: scroll;
}
.field-group
{
flex-direction: column;
flex-shrink: 0;
}
.field-header
{
flex-shrink: 0;
}
.field
{
color: white;
font-size: 14px;
flex-shrink: 0;
flex-grow: 0;
> .label
{
flex-grow: 0;
flex-shrink: 0;
font-weight: 600;
opacity: 0.4;
width: 20%;
font-size: 13px;
}
> .control
{
flex-shrink: 0;
flex-grow: 1;
flex-direction: column;
}
}
.is-vertical > .field, .field.is-vertical
{
flex-direction: column;
> .label
{
width: auto;
height: auto;
}
}
Game
game
ColorPickerControl
{
flex-direction: column;
flex-shrink: 0;
gap: 0.5rem;
margin: 1rem;
}
Game
game
EnumControl
{
gap: 2px;
flex-grow: 1;
}
EnumControl DropDown,
EnumControl ButtonGroup
{
border-radius: 8px;
background-color: #000a;
flex-grow: 1;
}
EnumControl DropDown
{
flex-grow: 1;
min-height: 32px;
}
EnumControl ButtonGroup
{
border-radius: 12px;
overflow: hidden;
min-height: 32px;
Button
{
flex-grow: 1;
justify-content: center;
align-items: center;
gap: 4px;
color: #aaa;
font-size: 1rem;
cursor: pointer;
.icon
{
color: #08f;
}
&:hover
{
color: #ddd;
.icon
{
color: #3af;
}
}
&:active
{
background-color: #04a;
color: white;
transform: translateX( 1px ) translateY( 1px );
.icon
{
color: #fff;
}
}
&.active
{
background-color: #08f;
color: white;
pointer-events: none;
.icon
{
color: #fff;
}
}
}
}
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
ColorSaturationValueControl
{
width: 240px;
height: 240px;
background-color: red;
position: relative;
border-radius: 4px;
cursor: pointer;
border: 1px solid #333;
&:hover
{
border: 1px solid #08f;
}
&:active
{
border: 1px solid #fff;
}
.handle
{
width: 16px;
height: 16px;
border-radius: 100px;
border: 2px solid #444;
position: absolute;
background-color: white;
box-shadow: 2px 2px 16px #000a;
transform: translateX( -50% ) translateY( -50% );
pointer-events: none;
z-index: 100;
z-index: 100;
}
.gradient
{
position: absolute;
width: 100%;
height: 100%;
border-radius: 4px;
background: linear-gradient( to right, white, rgba( 255, 255, 255, 0 ) );
&:after
{
content: "";
position: absolute;
width: 100%;
height: 100%;
border-radius: 4px;
background: linear-gradient( to top, black, rgba( 0, 0, 0, 0 ) );
}
}
}
Game
game
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Sandbox;
namespace GridAStar;
public partial class Grid : IValid
{
public static Grid Main
{
get => Grids.GetValueOrDefault( "main" );
set
{
if ( Grids.ContainsKey( "main" ) )
Grids["main"] = value;
else
Grids.Add( "main", value );
}
}
public static Dictionary<string, Grid> Grids { get; set; } = new();
/// <summary>The scene this grid traces against. Set on creation (Scene-System port).</summary>
public Scene Scene { get; set; }
// --- Generation diagnostics ---
public static int DebugCastsHit;
public static int DebugAngleRejected;
public static int DebugOutOfBounds;
public GridBuilder Settings { get; internal set; }
public string Identifier => Settings.Identifier;
public Dictionary<IntVector2, List<Cell>> CellStacks { get; internal set; } = new();
public IEnumerable<Cell> AllCells => CellStacks.Values.SelectMany( list => list );
public Vector3 Position => Settings.Position;
public BBox Bounds => Settings.Bounds;
public BBox RotatedBounds => Bounds.GetRotatedBounds( Rotation );
public BBox WorldBounds => RotatedBounds.Translate( Position );
public Transform Transform => new Transform( WorldBounds.Center, AxisRotation );
public Rotation Rotation => Settings.Rotation;
public bool AxisAligned => Settings.AxisAligned;
public float StandableAngle => Settings.StandableAngle;
public float StepSize => Settings.StepSize;
public float CellSize => Settings.CellSize;
public float HeightClearance => Settings.HeightClearance;
public float WidthClearance => Settings.WidthClearance;
public bool GridPerfect => Settings.GridPerfect;
public bool StaticOnly => Settings.StaticOnly;
public float MaxDropHeight => Settings.MaxDropHeight;
public List<JumpDefinition> JumpDefinitions => Settings.JumpDefinitions;
public int MinNeighbourCount => Settings.MinNeighbourCount;
public bool IgnoreConnectionsForJumps => Settings.IgnoreConnectionsForJumps;
public bool IgnoreLOSForJumps => Settings.IgnoreLOSForJumps;
public bool CylinderShaped => Settings.CylinderShaped;
public float Tolerance => GridPerfect ? 0.001f : 0f;
public Rotation AxisRotation => AxisAligned ? new Rotation() : Rotation;
public int MinimumColumn => WorldBounds.Mins.ToIntVector2( CellSize ).y;
public int MaximumColumn => WorldBounds.Maxs.ToIntVector2( CellSize ).y;
public int Columns => MaximumColumn - MinimumColumn;
public int MinimumRow => WorldBounds.Mins.ToIntVector2( CellSize ).x;
public int MaximumRow => WorldBounds.Maxs.ToIntVector2( CellSize ).x;
public int Rows => MaximumRow - MinimumRow;
bool IValid.IsValid { get; }
public Grid()
{
Settings = new GridBuilder();
}
public Grid( GridBuilder settings )
{
Settings = settings;
}
public void Print( string message ) => Print( Identifier, message );
public static void Print( string identifier, string message ) => Log.Info( $"Grid '{identifier}': {message}" );
public BBox ToWorld( BBox bounds ) => bounds.GetRotatedBounds( AxisRotation ).Translate( WorldBounds.Center );
public BBox ToLocal( BBox bounds ) => bounds.GetRotatedBounds( AxisRotation.Inverse ).Translate( -WorldBounds.Center );
public IntVector2 PositionToCoordinates( Vector3 position ) => (position - WorldBounds.Mins - CellSize / 2).ToIntVector2( CellSize );
/// <summary>Find the nearest cell from a position even if outside the grid (expensive).</summary>
public Cell GetNearestCell( Vector3 position, bool onlyBelow = true, bool unoccupiedOnly = false )
{
var validCells = AllCells;
if ( unoccupiedOnly )
validCells = validCells.Where( x => !x.Occupied );
if ( onlyBelow )
validCells = validCells.Where( x => x.Vertices.Min() - Math.Max( HeightClearance, StepSize ) <= position.z );
return validCells.OrderBy( x => x.Position.DistanceSquared( position ) )
.FirstOrDefault();
}
public Cell GetCellInArea( Vector3 position, float width, bool onlyBelow = true, bool withinStepRange = true )
{
var cellsToCheck = (int)Math.Ceiling( width / CellSize ) * 2;
for ( int y = 0; y <= cellsToCheck; y++ )
{
var spiralY = MathAStar.SpiralPattern( y );
for ( int x = 0; x <= cellsToCheck; x++ )
{
var spiralX = MathAStar.SpiralPattern( x );
var cellFound = GetCell( position + AxisRotation.Forward * spiralX * CellSize + AxisRotation.Right * spiralY * CellSize + Vector3.Up * StepSize, onlyBelow );
if ( cellFound == null ) continue;
if ( withinStepRange )
if ( position.z - cellFound.Position.z <= Math.Max( HeightClearance, StepSize ) ) return cellFound; else continue;
return cellFound;
}
}
return null;
}
public Cell GetCell( Vector3 position, bool onlyBelow = true ) => GetCell( PositionToCoordinates( position ), onlyBelow ? position.z : WorldBounds.Maxs.z );
public Cell GetCell( IntVector2 coordinates, float height )
{
var cellsAtCoordinates = CellStacks.GetValueOrDefault( coordinates );
if ( cellsAtCoordinates == null ) return null;
// Return the cell CLOSEST to the query height among the candidates, not the first match. A column on
// a spiral staircase / multi-floor area stacks several cells at the same XY; the original first-match
// returned an arbitrary one (often the bottom of the spiral), so an NPC partway up got a path from the
// wrong height and couldn't follow it. Candidate window (<= height + clearance) is unchanged.
Cell best = null;
var bestDist = float.MaxValue;
foreach ( var cell in cellsAtCoordinates )
{
if ( cell.Vertices.Min() - Math.Max( HeightClearance, StepSize ) >= height )
continue;
var dist = Math.Abs( cell.Position.z - height );
if ( dist < bestDist )
{
bestDist = dist;
best = cell;
}
}
return best;
}
public void AddCell( Cell cell )
{
if ( cell == null ) return;
var coordinates = cell.GridPosition;
if ( !CellStacks.ContainsKey( coordinates ) )
CellStacks.Add( coordinates, new List<Cell>() { cell } );
else
if ( !CellStacks[coordinates].Any( x => Math.Abs( x.Position.z - cell.Position.z ) < Math.Max( HeightClearance, StepSize ) ) )
CellStacks[coordinates].Add( cell );
}
public Cell GetCellInDirection( Cell startingCell, Vector3 direction, int numOfCellsInDirection = 1 ) => GetCell( startingCell.Position + direction * CellSize * numOfCellsInDirection );
public Cell GetNeighbourInDirection( Cell cell, Vector3 direction )
{
var horizontalDirection = direction.WithZ( 0 ).Normal;
var localCoordinates = horizontalDirection.ToIntVector2();
var coordinatesToCheck = cell.GridPosition + localCoordinates;
var cellsAtCoordinates = CellStacks.GetValueOrDefault( coordinatesToCheck );
if ( cellsAtCoordinates == null ) return null;
foreach ( var cellAtCoordinate in cellsAtCoordinates )
if ( cell.IsNeighbour( cellAtCoordinate ) && cell != cellAtCoordinate )
return cellAtCoordinate;
return null;
}
/// <summary>Returns if there's a valid, unoccupied, and direct line of sight from a cell to another</summary>
public bool LineOfSight( Cell startingCell, Cell endingCell, Component pathCreator = null, bool debugShow = false )
{
var startingPosition = startingCell.Position;
var endingPosition = endingCell.Position;
var distanceInSteps = (int)Math.Ceiling( startingPosition.Distance( endingPosition ) / CellSize );
if ( pathCreator == null && startingCell.Occupied ) return false;
if ( pathCreator != null && startingCell.Occupied && startingCell.OccupyingEntity != pathCreator ) return false;
if ( pathCreator == null && endingCell.Occupied ) return false;
if ( pathCreator != null && endingCell.Occupied && endingCell.OccupyingEntity != pathCreator ) return false;
Cell lastCell = startingCell;
for ( int i = 0; i <= distanceInSteps; i++ )
{
var direction = (endingPosition - lastCell.Position).Normal;
var cellToCheck = GetNeighbourInDirection( lastCell, direction );
if ( cellToCheck == null ) return false;
if ( cellToCheck == endingCell ) return true;
if ( cellToCheck == lastCell ) continue;
if ( pathCreator == null && cellToCheck.Occupied ) return false;
if ( pathCreator != null && cellToCheck.Occupied && cellToCheck.OccupyingEntity != pathCreator ) return false;
if ( !cellToCheck.IsNeighbour( lastCell ) ) return false;
lastCell = cellToCheck;
if ( debugShow )
lastCell.Draw( 2f, false, false, false );
}
return true;
}
/// <summary>Can you roughly walk towards the cell without it being a direct line of sight</summary>
public bool IsDirectlyWalkable( Cell startingCell, Cell endingCell, float maxDistanceFromDirectPath = 150f, Component pathCreator = null, bool withConnections = true )
{
if ( startingCell == null || endingCell == null ) return false;
var currentCell = startingCell;
var directPath = new Line( startingCell.Position.WithZ( 0 ), endingCell.Position.WithZ( 0 ) );
List<Cell> cellsChecked = new();
if ( pathCreator == null && startingCell.Occupied ) return false;
if ( pathCreator != null && startingCell.Occupied && startingCell.OccupyingEntity != pathCreator ) return false;
if ( pathCreator == null && endingCell.Occupied ) return false;
if ( pathCreator != null && endingCell.Occupied && endingCell.OccupyingEntity != pathCreator ) return false;
while ( currentCell != endingCell && directPath.Distance( currentCell.Position.WithZ( 0 ) ) <= maxDistanceFromDirectPath )
{
var cellToCheck = withConnections ? currentCell.GetClosestNeighbourAndConnection( endingCell.Position ) : currentCell.GetClosestNeighbour( endingCell.Position );
if ( cellToCheck == null ) return false;
if ( cellsChecked.Contains( cellToCheck ) ) return false;
if ( pathCreator == null && cellToCheck.Occupied ) return false;
if ( pathCreator != null && cellToCheck.Occupied && cellToCheck.OccupyingEntity != pathCreator ) return false;
if ( cellToCheck == endingCell ) return true;
cellsChecked.Add( currentCell );
currentCell = cellToCheck;
}
return false;
}
public bool IsInsideBounds( Vector3 point ) => Bounds.IsRotatedPointWithinBounds( Position, point, Rotation );
public bool IsInsideCylinder( Vector3 point ) => Bounds.IsInsideSquishedRotatedCylinder( Position, point, Rotation );
public void Initialize()
{
if ( Grids.ContainsKey( Identifier ) )
{
if ( Grids[Identifier] != null )
Grids[Identifier].Delete( true );
Grids[Identifier] = this;
}
else
Grids.Add( Identifier, this );
}
public void Delete( bool deleteSave = false )
{
if ( Grids.ContainsKey( Identifier ) )
{
Grids[Identifier] = null;
Grids.Remove( Identifier );
}
}
public List<Cell> GetCellsInBBox( BBox bbox )
{
var cells = new List<Cell>();
foreach ( var cell in AllCells )
if ( bbox.Contains( cell.Position ) )
cells.Add( cell );
return cells;
}
public override int GetHashCode() => Settings.GetHashCode();
/// <summary>Gives the edge tag to all cells with less than 8 neighbours</summary>
public async Task AssignEdgeCells( int maxNeighourCount = 8, int threadsToUse = 1, bool clearTags = false, string tagToExclude = "", string tagToAssign = "edge" ) => await assignEdgeCellsInternal( AllCells.ToList(), maxNeighourCount, threadsToUse, clearTags, tagToExclude, tagToAssign );
public async Task AssignEdgeCells( BBox bounds, int maxNeighourCount = 8, int threadsToUse = 1, bool clearTags = false, string tagToExclude = "", string tagToAssign = "edge" ) => await assignEdgeCellsInternal( GetCellsInBBox( bounds ), maxNeighourCount, threadsToUse, clearTags, tagToExclude, tagToAssign );
internal async Task assignEdgeCellsInternal( List<Cell> cells, int maxNeighourCount = 8, int threadsToUse = 1, bool clearTags = false, string tagToExclude = "", string tagToAssign = "edge" )
{
var cellsCount = cells.Count();
threadsToUse = Math.Max( 1, threadsToUse );
var cellsEachThread = (int)(cellsCount / threadsToUse);
var lastThreadCount = cellsCount - (cellsEachThread * (threadsToUse - 1));
List<Task> tasks = new();
for ( int i = 0; i < threadsToUse; i++ )
{
var curentThread = i;
tasks.Add( GameTask.RunInThreadAsync( () =>
{
var cellsRange = curentThread == threadsToUse - 1 ? cellsEachThread : lastThreadCount;
var cellsToCheck = cells.Skip( cellsEachThread * curentThread ).Take( cellsRange );
foreach ( var cell in cellsToCheck )
{
if ( clearTags )
cell.Tags.Remove( tagToAssign );
var neighbours = cell.GetNeighbours();
if ( tagToExclude != "" )
neighbours = neighbours.Where( x => !x.Tags.Has( tagToExclude ) );
if ( neighbours.Count() < maxNeighourCount )
cell.Tags.Add( tagToAssign );
}
} ) );
}
await GameTask.WhenAll( tasks );
}
/// <summary>Adds the droppable connection to cells you can drop from</summary>
public async Task AssignDroppableCells( int threadsToUse = 1 ) => await internalAssignDroppableCells( CellsWithTag( "edge" ).ToList(), threadsToUse );
public async Task AssignDroppableCells( BBox bounds, int threadsToUse = 1 ) => await internalAssignDroppableCells( CellsWithTag( bounds, "edge" ).ToList(), threadsToUse );
internal async Task internalAssignDroppableCells( List<Cell> cells, int threadsToUse = 1 )
{
var allCells = cells;
var cellsCount = allCells.Count();
threadsToUse = Math.Max( 1, threadsToUse );
var cellsEachThread = (int)(cellsCount / threadsToUse);
var lastThreadCount = cellsCount - (cellsEachThread * (threadsToUse - 1));
List<Task> tasks = new();
for ( int i = 0; i < threadsToUse; i++ )
{
var curentThread = i;
tasks.Add( GameTask.RunInThreadAsync( () =>
{
var cellsRange = curentThread == threadsToUse - 1 ? cellsEachThread : lastThreadCount;
var cellsToCheck = allCells.Skip( cellsEachThread * curentThread ).Take( cellsRange );
foreach ( var cell in cellsToCheck )
{
var droppableCell = cell.GetFirstValidDroppable( maxHeightDistance: MaxDropHeight );
if ( droppableCell != null )
cell.AddConnection( droppableCell, "drop" );
}
} ) );
}
await GameTask.WhenAll( tasks );
}
public IEnumerable<Cell> JumpableCandidates()
{
var droppedCells = CellsWithConnection( "drop" ).SelectMany( cell => cell.GetConnections( "drop" ).Select( connection => connection.Current ) );
return CellsWithTag( "edge" ).Concat( droppedCells );
}
public async Task AssignJumpableCells( JumpDefinition definition, int threadsToUse = 16 ) => await internalAssignJumpableCells( JumpableCandidates().ToList(), definition, threadsToUse );
internal async Task internalAssignJumpableCells( List<Cell> cells, JumpDefinition definition, int threadsToUse = 16 )
{
var allCells = cells;
var cellsCount = allCells.Count();
threadsToUse = Math.Max( 1, threadsToUse );
var cellsEachThread = (int)(cellsCount / threadsToUse);
var lastThreadCount = cellsCount - (cellsEachThread * (threadsToUse - 1));
List<Task> tasks = new();
for ( int i = 0; i < threadsToUse; i++ )
{
var curentThread = i;
tasks.Add( GameTask.RunInThreadAsync( () =>
{
var totalFraction = 1f;
var cellsRange = curentThread == threadsToUse - 1 ? cellsEachThread : lastThreadCount;
var cellsToCheck = allCells.Skip( cellsEachThread * curentThread ).Take( cellsRange );
foreach ( var cell in cellsToCheck )
{
if ( totalFraction >= 1f )
{
List<Cell> connectedCells = new();
List<AStarNode> jumpConnections = new();
foreach ( var jumpableCell in cell.GetValidJumpables( definition, MaxDropHeight, IgnoreConnectionsForJumps, IgnoreLOSForJumps ) )
if ( jumpableCell != null )
{
jumpConnections.Add( cell.AddConnection( jumpableCell, definition.Name ) );
connectedCells.Add( jumpableCell );
}
foreach ( var jumpableConnection in connectedCells )
{
var direction = (cell.Position - jumpableConnection.Position).WithZ( 0 ).Normal;
var jumpbackCell = jumpableConnection.GetValidJumpable( definition, direction, MaxDropHeight, IgnoreConnectionsForJumps, IgnoreLOSForJumps );
if ( jumpbackCell != null )
if ( !IsDirectlyWalkable( jumpbackCell, cell ) )
{
var duplicate = false;
foreach ( var connection in jumpConnections )
if ( connection.Parent.Current == jumpbackCell && connection.MovementTag == definition.Name )
duplicate = true;
if ( !duplicate )
jumpConnections.Add( jumpableConnection.AddConnection( jumpbackCell, definition.Name ) );
}
}
foreach ( var connection in jumpConnections )
if ( LineOfSight( connection.Parent.Current, connection.Current ) )
connection.Parent.Current.RemoveConnection( connection );
totalFraction = 0f;
}
totalFraction += definition.GenerateFraction;
}
} ) );
}
await GameTask.WhenAll( tasks );
}
public Vector3 TraceParabola( Vector3 startingPosition, Vector3 horizontalVelocity, float verticalSpeed, float gravity, float maxDropHeight, int subSteps = 2 )
{
var horizontalDirection = horizontalVelocity.WithZ( 0 ).Normal;
var horizontalSpeed = horizontalVelocity.WithZ( 0 ).Length;
var maxHeight = startingPosition.z + MathAStar.ParabolaMaxHeight( verticalSpeed, gravity );
var minHeight = maxHeight - maxDropHeight;
var currentDistance = 1;
var lastPositionChecked = startingPosition;
while ( lastPositionChecked.z >= minHeight )
{
var horizontalOffset = CellSize * currentDistance / subSteps;
var verticalOffset = MathAStar.ParabolaHeight( horizontalOffset, horizontalSpeed, verticalSpeed, gravity );
var nextPositionToCheck = startingPosition + horizontalDirection * horizontalOffset + Vector3.Up * verticalOffset;
var clearanceBBox = new BBox( new Vector3( -WidthClearance / 2f, -WidthClearance / 2f, StepSize ), new Vector3( WidthClearance / 2f, WidthClearance / 2f, HeightClearance ) );
var jumpTrace = Scene.Trace.Box( clearanceBBox, lastPositionChecked, nextPositionToCheck )
.WithGridSettings( Settings )
.Run();
if ( jumpTrace.Hit )
return jumpTrace.EndPosition;
lastPositionChecked = nextPositionToCheck;
currentDistance++;
}
return lastPositionChecked;
}
public void RemoveCells( BBox bounds, bool printInfo = false )
{
var cellsToRemove = GetCellsInBBox( bounds );
var count = cellsToRemove.Count();
foreach ( var cell in cellsToRemove )
cell.Delete();
if ( printInfo )
Print( $"Removed {count} cells" );
}
public async Task GenerateCells( BBox bounds, int threadedChunkSides = 1, bool printInfo = true )
{
List<Task<List<Cell>>> tasks = new();
var totalMins = bounds.Mins;
var totalMaxs = bounds.Maxs;
var totalSize = bounds.Size;
threadedChunkSides = Math.Max( 1, threadedChunkSides );
for ( int x = 1; x <= threadedChunkSides; x++ )
{
for ( int y = 1; y <= threadedChunkSides; y++ )
{
var xOffset = totalSize.x / threadedChunkSides * x - totalSize.x / threadedChunkSides / 2;
var yOffset = totalSize.y / threadedChunkSides * y - totalSize.y / threadedChunkSides / 2;
var offset = new Vector3( xOffset, yOffset );
var chunkSize = totalSize / threadedChunkSides;
var chunkMins = totalMins + offset - chunkSize / 2;
var chunkMaxs = totalMins + offset + chunkSize / 2;
var dividedBounds = new BBox( chunkMins.WithZ( totalMins.z ), chunkMaxs.WithZ( totalMaxs.z ) );
tasks.Add( GameTask.RunInThreadAsync( () => createCells( dividedBounds, printInfo ) ) );
}
}
await GameTask.WhenAll( tasks );
foreach ( var task in tasks )
foreach ( var cell in task.Result )
AddCell( cell );
}
/// <summary>Create cells in that local bbox (Doesn't add them)</summary>
private List<Cell> createCells( BBox bounds, bool printInfo = true )
{
var generatedCells = new List<Cell>();
var minimumGrid = bounds.Mins.ToIntVector2( CellSize );
var maximumGrid = bounds.Maxs.ToIntVector2( CellSize );
var startingColumn = minimumGrid.y - MinimumColumn;
var totalColumns = maximumGrid.y - minimumGrid.y;
var endingColumn = startingColumn + totalColumns;
var startingRow = minimumGrid.x - MinimumRow;
var totalRows = maximumGrid.x - minimumGrid.x;
var endingRow = startingRow + totalRows;
for ( int column = startingColumn; column < endingColumn; column++ )
{
for ( int row = startingRow; row < endingRow; row++ )
{
var startPosition = WorldBounds.Mins.WithZ( WorldBounds.Maxs.z ) + new Vector3( row * CellSize + CellSize / 2f, column * CellSize + CellSize / 2f, Tolerance * 2f ) * AxisRotation;
var endPosition = WorldBounds.Mins + new Vector3( row * CellSize + CellSize / 2f, column * CellSize + CellSize / 2f, -Tolerance ) * AxisRotation;
var checkBBox = new BBox( new Vector3( -CellSize / 2f + Tolerance, -CellSize / 2f + Tolerance, 0f ), new Vector3( CellSize / 2f - Tolerance, CellSize / 2f - Tolerance, 0.001f ) );
var positionTrace = Scene.Trace.Box( checkBBox, startPosition, endPosition )
.WithGridSettings( Settings );
var positionResult = positionTrace.Run();
while ( positionResult.Hit && startPosition.z >= endPosition.z )
{
DebugCastsHit++;
if ( IsInsideBounds( positionResult.HitPosition ) )
{
if ( !CylinderShaped || IsInsideCylinder( positionResult.HitPosition ) )
{
var angle = Vector3.GetAngle( Vector3.Up, positionResult.Normal );
if ( angle <= StandableAngle )
{
var newCell = Cell.TryCreate( this, positionResult.HitPosition );
if ( newCell != null )
generatedCells.Add( newCell );
}
else
{
DebugAngleRejected++;
}
}
}
else
{
DebugOutOfBounds++;
}
startPosition = positionResult.HitPosition + Vector3.Down * HeightClearance;
// Scene-System port of Sandbox.Trace.TestPoint: a zero-length sphere trace reports
// StartedSolid when the point is inside geometry. Step down until we're clear.
while ( Scene.Trace.Sphere( CellSize / 2f - Tolerance, startPosition, startPosition ).Run().StartedSolid )
startPosition += Vector3.Down * HeightClearance;
positionTrace = Scene.Trace.Box( checkBBox, startPosition, endPosition )
.WithGridSettings( Settings );
positionResult = positionTrace.Run();
}
}
}
return generatedCells;
}
public IEnumerable<Cell> CellsWithTag( string tag ) => AllCells.Where( cell => cell.Tags.Has( tag ) );
public IEnumerable<Cell> CellsWithTags( params string[] tags ) => AllCells.Where( cell => cell.Tags.Has( tags ) );
public IEnumerable<Cell> CellsWithTags( List<string> tags ) => AllCells.Where( cell => cell.Tags.Has( tags ) );
public IEnumerable<Cell> CellsWithTag( BBox bounds, string tag ) => GetCellsInBBox( bounds ).Where( cell => cell.Tags.Has( tag ) );
public IEnumerable<Cell> CellsWithTags( BBox bounds, params string[] tags ) => GetCellsInBBox( bounds ).Where( cell => cell.Tags.Has( tags ) );
public IEnumerable<Cell> CellsWithTags( BBox bounds, List<string> tags ) => GetCellsInBBox( bounds ).Where( cell => cell.Tags.Has( tags ) );
public IEnumerable<Cell> CellsWithConnection( string movementTag ) => AllCells.Where( cell => cell.GetConnections( movementTag ).Count() > 0 );
public IEnumerable<Cell> CellsWithConnection( BBox bounds, string movementTag ) => GetCellsInBBox( bounds ).Where( cell => cell.GetConnections( movementTag ).Count() > 0 );
public void CheckOccupancy( string tag )
{
foreach ( var cell in AllCells )
cell.Occupied = cell.TestForOccupancy( tag );
}
}
Game
game
namespace GridAStar;
// Set STEP_SIZE or WIDTH_CLEARANCE to 0 to disable them (faster grid generation)
public static partial class GridSettings
{
public const float DEFAULT_STANDABLE_ANGLE = 40f; // How steep the terrain can be on a cell before it gets discarded
public const float DEFAULT_STEP_SIZE = 12f; // How big steps can be on a cell before it gets discarded
public const float DEFAULT_CELL_SIZE = 16f; // How large each cell will be in hammer units
public const float DEFAULT_HEIGHT_CLEARANCE = 72f; // How much vertical space there should be
public const float DEFAULT_WIDTH_CLEARANCE = 24f; // How much horizontal space there should be
public const float DEFAULT_DROP_HEIGHT = 400f; // How high you can drop down from
public const bool DEFAULT_GRID_PERFECT = false; // For grid-perfect terrain, if true it will not be checking for steps, so use ramps instead
public const bool DEFAULT_STATIC_ONLY = true; // Will it only hit world and static or also dynamic
}
Game
game
using Sandbox;
namespace GridAStar;
public static partial class TraceExtensions
{
/// <summary>
/// Apply a grid's generation filters to a scene trace. Scene-System port: legacy <c>StaticOnly()</c>
/// becomes <c>IgnoreDynamic()</c>. Tag filters are only applied when non-empty (an empty
/// <c>WithAllTags</c> would otherwise filter against nothing).
/// </summary>
public static SceneTrace WithGridSettings( this SceneTrace self, GridBuilder settings )
{
if ( settings.StaticOnly )
self = self.IgnoreDynamic();
// ANY of the include tags (not all) - the floor may be tagged "world" while props are "solid", and
// requiring both would match nothing. Legacy used WithAllTags but its maps used a single floor tag.
if ( settings.TagsToInclude.Count > 0 )
self = self.WithAnyTags( settings.TagsToInclude.ToArray() );
if ( settings.TagsToExclude.Count > 0 )
self = self.WithoutTags( settings.TagsToExclude.ToArray() );
return self;
}
}
Debug: View Raw JSON Response
{
"TotalCount": 154,
"Files": [
{
"Ident": "rue.house",
"Path": "Game/MansionGame.Music.cs",
"FileName": "MansionGame.Music.cs",
"PackageType": "game",
"CodeKind": "Game",
"AssetVersionId": 301999,
"Code": "using Sandbox;\n\nnamespace BrickJam;\n\npublic sealed partial class MansionGame\n{\n\t/// <summary>How fast the music fades in/out (per second). Legacy faded on the server tick.</summary>\n\tpublic float MusicVolumeChangeRate => 0.5f;\n\n\t/// <summary>Target music volume - background level, the tracks are mastered loud.</summary>\n\tpublic float MusicVolume => 0.15f;\n\n\tprivate SoundHandle musicHandle;\n\tprivate LevelType musicLevel = LevelType.None;\n\tprivate float musicVolume;\n\n\t/// <summary>\n\t/// Client-side music orchestration (every client, host included - music is local audio). Scene-System\n\t/// port of the legacy host-side <c>ProcessMusic</c>: drive the track from the replicated\n\t/// <see cref=\"CurrentLevelType\"/> and crossfade when the level changes.\n\t/// </summary>\n\tprotected override void OnUpdate()\n\t{\n\t\tvar track = Level.GetMusic( CurrentLevelType );\n\n\t\tif ( CurrentLevelType != musicLevel )\n\t\t{\n\t\t\t// Level changed: fade the old track out, then swap once it's silent.\n\t\t\tmusicVolume = musicVolume.LerpTo( 0f, MusicVolumeChangeRate * Time.Delta );\n\n\t\t\tif ( musicVolume <= 0.01f )\n\t\t\t{\n\t\t\t\tmusicHandle?.Stop();\n\t\t\t\tmusicHandle = null;\n\t\t\t\tmusicLevel = CurrentLevelType;\n\t\t\t\tmusicVolume = 0f;\n\t\t\t}\n\n\t\t\tApplyMusicVolume();\n\t\t\treturn;\n\t\t}\n\n\t\t// Same level: keep the track playing (restart if the asset isn't looped) and fade toward target.\n\t\tif ( !string.IsNullOrEmpty( track ) )\n\t\t{\n\t\t\tif ( musicHandle is null || musicHandle.IsStopped )\n\t\t\t{\n\t\t\t\tmusicHandle = Sound.Play( track );\n\t\t\t\tif ( musicHandle is not null )\n\t\t\t\t\tmusicHandle.Volume = musicVolume; // start at the current (faded) level, not full blast\n\t\t\t}\n\n\t\t\tmusicVolume = musicVolume.LerpTo( MusicVolume, MusicVolumeChangeRate * Time.Delta );\n\t\t}\n\t\telse\n\t\t{\n\t\t\tmusicVolume = musicVolume.LerpTo( 0f, MusicVolumeChangeRate * Time.Delta );\n\t\t\tif ( musicVolume <= 0.01f && musicHandle is not null )\n\t\t\t{\n\t\t\t\tmusicHandle.Stop();\n\t\t\t\tmusicHandle = null;\n\t\t\t}\n\t\t}\n\n\t\tApplyMusicVolume();\n\t}\n\n\tprivate void ApplyMusicVolume()\n\t{\n\t\tif ( musicHandle is not null && !musicHandle.IsStopped )\n\t\t\tmusicHandle.Volume = musicVolume;\n\t}\n}\n"
},
{
"Ident": "rue.house",
"Path": "Grid/AStarPathBuilder.cs",
"FileName": "AStarPathBuilder.cs",
"PackageType": "game",
"CodeKind": "Game",
"AssetVersionId": 301999,
"Code": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Threading;\nusing System.Threading.Tasks;\nusing Sandbox;\n\nnamespace GridAStar;\n\npublic struct AStarPathBuilder\n{\n\tpublic Grid Grid { get; private set; } = null;\n\tpublic List<string> TagsToExclude { get; private set; } = new() { \"occupied\" };\n\tpublic bool HasTagsToExlude => TagsToExclude.Count() > 0;\n\tpublic bool HasOccupiedTagToExclude => HasTagsToExlude ? TagsToExclude.Contains( \"occupied\" ) : false;\n\tpublic List<string> TagsToInclude { get; private set; } = new();\n\tpublic bool HasTagsToInclude => TagsToInclude.Count() > 0;\n\tpublic Dictionary<string, float> TagsToAvoid { get; private set; } = new();\n\tpublic bool HasTagsToAvoid => TagsToAvoid.Count() > 0;\n\tpublic bool AcceptsPartial { get; private set; } = false;\n\tpublic float MaxCheckDistance { get; private set; } = float.PositiveInfinity;\n\tpublic float MaxDropHeight { get; private set; } = GridSettings.DEFAULT_DROP_HEIGHT;\n\tpublic Component PathCreator { get; private set; } = null;\n\tpublic bool HasPathCreator => PathCreator != null;\n\n\tpublic AStarPathBuilder() { }\n\tpublic AStarPathBuilder( Grid grid ) : this()\n\t{\n\t\tGrid = grid;\n\t}\n\n\tpublic static AStarPathBuilder From( Grid grid ) => new AStarPathBuilder( grid );\n\n\tpublic AStarPathBuilder WithTags( params string[] tags )\n\t{\n\t\tforeach ( var tag in tags )\n\t\t{\n\t\t\tif ( !TagsToInclude.Contains( tag ) )\n\t\t\t\tTagsToInclude.Add( tag );\n\t\t\tif ( TagsToExclude.Contains( tag ) )\n\t\t\t\tTagsToExclude.Remove( tag );\n\t\t}\n\t\treturn this;\n\t}\n\n\tpublic AStarPathBuilder WithoutTags( params string[] tags )\n\t{\n\t\tforeach ( var tag in tags )\n\t\t{\n\t\t\tif ( !TagsToExclude.Contains( tag ) )\n\t\t\t\tTagsToExclude.Add( tag );\n\t\t\tif ( TagsToInclude.Contains( tag ) )\n\t\t\t\tTagsToInclude.Remove( tag );\n\t\t}\n\t\treturn this;\n\t}\n\n\t/// <summary>\n\t/// Which tags to avoid, when found it will add the malus to its total cost.\n\t/// </summary>\n\tpublic AStarPathBuilder AvoidTag( string tag, float malus )\n\t{\n\t\tmalus = Math.Abs( malus );\n\n\t\tif ( !TagsToAvoid.ContainsKey( tag ) )\n\t\t\tTagsToAvoid.Add( tag, malus );\n\t\telse\n\t\t\tTagsToAvoid[tag] = malus;\n\n\t\treturn this;\n\t}\n\n\tpublic AStarPathBuilder WithMaxDistance( float maxDistance )\n\t{\n\t\tMaxCheckDistance = Math.Max( 0f, maxDistance );\n\t\treturn this;\n\t}\n\n\tpublic AStarPathBuilder WithMaxDropHeight( float maxDropHeight )\n\t{\n\t\tMaxDropHeight = Math.Min( Grid.MaxDropHeight, maxDropHeight );\n\t\treturn this;\n\t}\n\n\tpublic AStarPathBuilder WithPartialEnabled()\n\t{\n\t\tAcceptsPartial = true;\n\t\treturn this;\n\t}\n\n\tpublic AStarPathBuilder WithPathCreator( Component pathCreator )\n\t{\n\t\tPathCreator = pathCreator;\n\t\treturn this;\n\t}\n\n\tpublic AStarPath Run( Cell startingCell, Cell targetCell, bool reversed = false, bool withCellConnections = true )\n\t{\n\t\tif ( startingCell is null || targetCell is null || startingCell == targetCell ) return AStarPath.Empty();\n\n\t\treturn AStarPath.From( this, Grid.ComputePathInternal( this, startingCell, targetCell, CancellationToken.None, reversed, withCellConnections ) );\n\t}\n\tpublic AStarPath Run( Vector3 startingPosition, Cell targetCell, bool reversed = false, bool withCellConnections = true ) => Run( Grid.GetCell( startingPosition ), targetCell, reversed, withCellConnections );\n\tpublic AStarPath Run( Cell startingCell, Vector3 targetPosition, bool reversed = false, bool withCellConnections = true ) => Run( startingCell, Grid.GetCell( targetPosition ), reversed, withCellConnections );\n\tpublic AStarPath Run( Vector3 startingPosition, Vector3 targetPosition, bool reversed = false, bool withCellConnections = true ) => Run( Grid.GetCell( startingPosition ), Grid.GetCell( targetPosition ), reversed, withCellConnections );\n\n\tinternal AStarPath Run( Cell startingCell, Cell targetCell, CancellationToken token, bool reversed = false, bool withCellConnections = true )\n\t{\n\t\tif ( startingCell is null || targetCell is null || startingCell == targetCell ) return AStarPath.Empty();\n\n\t\treturn AStarPath.From( this, Grid.ComputePathInternal( this, startingCell, targetCell, token, reversed, withCellConnections ) );\n\t}\n\n\tpublic async Task<AStarPath> RunAsync( Cell startingCell, Cell targetCell, CancellationToken token, bool reversed = false, bool withCellConnections = true )\n\t{\n\t\tvar builder = this;\n\n\t\treturn await GameTask.RunInThreadAsync( () => builder.Run( startingCell, targetCell, token, reversed, withCellConnections ) );\n\t}\n\tpublic async Task<AStarPath> RunAsync( Vector3 startingPosition, Cell targetCell, CancellationToken token, bool reversed = false, bool withCellConnections = true ) => await RunAsync( Grid.GetCell( startingPosition ), targetCell, token, reversed, withCellConnections );\n\tpublic async Task<AStarPath> RunAsync( Cell startingCell, Vector3 targetPosition, CancellationToken token, bool reversed = false, bool withCellConnections = true ) => await RunAsync( startingCell, Grid.GetCell( targetPosition ), token, reversed, withCellConnections );\n\tpublic async Task<AStarPath> RunAsync( Vector3 startingPosition, Vector3 targetPosition, CancellationToken token, bool reversed = false, bool withCellConnections = true ) => await RunAsync( Grid.GetCell( startingPosition ), Grid.GetCell( targetPosition ), token, reversed, withCellConnections );\n\n\tpublic async Task<AStarPath> RunInParallel( Cell startingCell, Cell targetCell, CancellationTokenSource tokenSource, bool withCellConnections = true )\n\t{\n\t\tif ( startingCell is null || targetCell is null || startingCell == targetCell ) return AStarPath.Empty();\n\n\t\tvar fromTo = RunAsync( startingCell, targetCell, tokenSource.Token, false, withCellConnections );\n\t\tvar toFrom = RunAsync( targetCell, startingCell, tokenSource.Token, true, false ); // You can't reverse some cell connections, like dropping down\n\n\t\tvar pathResult = await GameTask.WhenAny( fromTo, toFrom ).Result;\n\n\t\t// Cancel the other task that hasn't finished yet.\n\t\ttokenSource.Cancel();\n\n\t\treturn pathResult;\n\t}\n\tpublic async Task<AStarPath> RunInParallel( Vector3 startingPosition, Cell targetCell, CancellationTokenSource tokenSource, bool withCellConnections = true ) => await RunInParallel( Grid.GetCell( startingPosition ), targetCell, tokenSource, withCellConnections );\n\tpublic async Task<AStarPath> RunInParallel( Cell startingCell, Vector3 targetPosition, CancellationTokenSource tokenSource, bool withCellConnections = true ) => await RunInParallel( startingCell, Grid.GetCell( targetPosition ), tokenSource, withCellConnections );\n\tpublic async Task<AStarPath> RunInParallel( Vector3 startingPosition, Vector3 targetPosition, CancellationTokenSource tokenSource, bool withCellConnections = true ) => await RunInParallel( Grid.GetCell( startingPosition ), Grid.GetCell( targetPosition ), tokenSource, withCellConnections );\n}\n"
},
{
"Ident": "rue.house",
"Path": "Grid/IntVector2.cs",
"FileName": "IntVector2.cs",
"PackageType": "game",
"CodeKind": "Game",
"AssetVersionId": 301999,
"Code": "using System;\nusing Sandbox;\n\nnamespace GridAStar;\n\n/// <summary>\n/// Like a Vector2, but with integers instead.\n/// </summary>\npublic struct IntVector2 : IEquatable<IntVector2>\n{\n\tpublic int x { get; set; }\n\tpublic int y { get; set; }\n\n\tpublic int this[int index]\n\t{\n\t\tget\n\t\t{\n\t\t\tint result = index switch\n\t\t\t{\n\t\t\t\t0 => x,\n\t\t\t\t1 => y,\n\t\t\t\t_ => throw new IndexOutOfRangeException(),\n\t\t\t};\n\n\t\t\treturn result;\n\t\t}\n\t\tset\n\t\t{\n\t\t\tswitch ( index )\n\t\t\t{\n\t\t\t\tcase 0:\n\t\t\t\t\tx = value;\n\t\t\t\t\tbreak;\n\t\t\t\tcase 1:\n\t\t\t\t\ty = value;\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\tpublic IntVector2( int x, int y )\n\t{\n\t\tthis.x = x;\n\t\tthis.y = y;\n\t}\n\n\tpublic IntVector2 WithX( int x ) => new IntVector2( x, this.y );\n\tpublic IntVector2 WithY( int y ) => new IntVector2( this.x, y );\n\tpublic Vector2 ToVector2() => new Vector2( x, y );\n\tpublic float DistanceSquared( IntVector2 other ) => ToVector2().DistanceSquared( other.ToVector2() );\n\tpublic override string ToString() => $\"{x},{y}\";\n\tpublic override bool Equals( object obj ) => obj is IntVector2 other && Equals( other );\n\tpublic bool Equals( IntVector2 other ) => x == other.x && y == other.y;\n\tpublic override int GetHashCode() => HashCode.Combine( x, y );\n\n\tpublic static bool operator ==( IntVector2 left, IntVector2 right ) => left.Equals( right );\n\tpublic static bool operator !=( IntVector2 left, IntVector2 right ) => !(left == right);\n\tpublic static IntVector2 operator +( IntVector2 a, IntVector2 b ) => new IntVector2( a.x + b.x, a.y + b.y );\n\tpublic static IntVector2 operator -( IntVector2 a, IntVector2 b ) => new IntVector2( a.x - b.x, a.y - b.y );\n}\n"
},
{
"Ident": "rue.house",
"Path": "Levels/Level.cs",
"FileName": "Level.cs",
"PackageType": "game",
"CodeKind": "Game",
"AssetVersionId": 301999,
"Code": "using System;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing System.Collections.Generic;\nusing Sandbox;\n\nnamespace BrickJam;\n\n/// <summary>\n/// Level orchestration. The legacy <c>Level</c> was an <c>Entity</c> (for replication); in the Scene\n/// System it is a plain server-side object owned by <see cref=\"MansionGame\"/>. The single replicated\n/// fact clients need \u2014 the current level type \u2014 lives on the manager as\n/// <see cref=\"MansionGame.CurrentLevelType\"/>.\n///\n/// DEFERRED: grid generation (<c>GenerateGrid</c>, needs the grid package), music\n/// (<c>ProcessMusic</c>), and the black-screen transition + event-log messages (UI system).\n/// </summary>\npublic abstract partial class Level\n{\n\tpublic abstract LevelType Type { get; }\n\tpublic string Music => GetMusic( Type );\n\tpublic virtual BBox WorldBox => new( new Vector3( -100000f ), new Vector3( 100000f ) );\n\n\t/// <summary>\n\t/// Music track per level. Single source of truth so the client-side music player\n\t/// (<see cref=\"MansionGame\"/>) can resolve a track from the replicated <see cref=\"LevelType\"/>\n\t/// without a host-side <see cref=\"Level\"/> instance.\n\t/// </summary>\n\tpublic static string GetMusic( LevelType type ) => type switch\n\t{\n\t\tLevelType.Shop => \"sounds/music/scary_quest_at_midnight.sound\",\n\t\tLevelType.Mansion => \"sounds/music/looming_trees_in_eerie_woods.sound\",\n\t\tLevelType.Dungeon => \"sounds/music/malevolent_sightings_in_the_room.sound\",\n\t\tLevelType.Bathrooms => \"sounds/music/depths_and_terror.sound\",\n\t\t_ => null,\n\t};\n\n\tpublic LegacyUsableComponent Exit { get; set; }\n\tpublic List<NPC> Monsters { get; } = new();\n\tpublic TimeSince SinceStarted { get; set; }\n\n\tpublic static bool GameIsEnding { get; set; }\n\n\tprotected Scene Scene => MansionGame.Instance.Scene;\n\n\tpublic virtual void Compute()\n\t{\n\t\tvar players = Scene.GetAllComponents<Player>().ToList();\n\t\tif ( players.Count > 0 && players.All( x => !x.IsAlive ) && !GameIsEnding )\n\t\t{\n\t\t\tMansionGame.RestartGame();\n\t\t\tGameIsEnding = true;\n\t\t\t// TODO (UI): Eventlog \"Looks like everyone died, better luck next time!\"\n\t\t}\n\t}\n\n\tprotected void RespawnAll() => MansionGame.Instance.RespawnAll();\n\n\tpublic virtual async Task Start()\n\t{\n\t\tawait GameTask.Yield();\n\n\t\tRespawnAll();\n\n\t\t// Companion spawning for players who bought the upgrade.\n\t\tforeach ( var player in Scene.GetAllComponents<Player>().ToList() )\n\t\t{\n\t\t\tif ( player.HasUpgrade( \"Cartoony Sidekick\" ) )\n\t\t\t{\n\t\t\t\tvar doob = NPC.Create<Doob>( player.WorldPosition, player.WorldRotation );\n\t\t\t\tdoob.Owner = player;\n\t\t\t\tplayer.Doob = doob;\n\n\t\t\t\t// Who's a Good Boy?: Doob summoned to protect this player.\n\t\t\t\tplayer.TrackAchievement( GameStats.AchGoodBoy );\n\t\t\t}\n\t\t}\n\n\t\tExit?.GameObject.Destroy();\n\n\t\tif ( Type == LevelType.Bathrooms )\n\t\t{\n\t\t\tvar finalDoors = Scene.GetAllComponents<ValidFinalDoorPosition>().ToList();\n\t\t\tvar spot = MansionGame.Random.FromList( finalDoors, null );\n\t\t\tif ( spot is not null )\n\t\t\t\tExit = FinalDoor.Create( spot.WorldPosition, spot.WorldRotation );\n\t\t}\n\t\telse\n\t\t{\n\t\t\tvar trapdoors = Scene.GetAllComponents<ValidTrapdoorPosition>().Where( x => x.LevelType == Type ).ToList();\n\t\t\tvar spot = MansionGame.Random.FromList( trapdoors, null );\n\t\t\tif ( spot is not null )\n\t\t\t\tExit = Trapdoor.Create( spot.WorldPosition, spot.WorldRotation );\n\t\t}\n\n\t\tforeach ( var spawner in Scene.GetAllComponents<LootSpawner>().Where( x => x.LevelType == Type ).ToList() )\n\t\t\tspawner.SpawnLoot();\n\n\t\tforeach ( var door in Scene.GetAllComponents<Door>().Where( x => x.LevelType == Type ).ToList() )\n\t\t\tdoor.Close();\n\n\t\tawait GameTask.DelayRealtimeSeconds( 1f );\n\n\t\tawait GenerateGrid();\n\n\t\tGameIsEnding = false;\n\t\tSinceStarted = 0f; // reset the level clock (used by the Slipped on a Soap achievement)\n\t\tMansionGame.Instance.TimerStart();\n\t}\n\n\tpublic virtual async Task End()\n\t{\n\t\tawait GameTask.Yield();\n\n\t\tMansionGame.Instance?.ShowBlackScreen( 2f, 1f, 1f );\n\n\t\tExit?.GameObject.Destroy();\n\t\tExit = null;\n\n\t\tforeach ( var monster in Monsters.ToList() )\n\t\t\tRemoveMonster( monster );\n\n\t\tforeach ( var spawner in Scene.GetAllComponents<LootSpawner>().ToList() )\n\t\t\tspawner.DeleteLoot();\n\n\t\tforeach ( var door in Scene.GetAllComponents<Door>().Where( x => WorldBox.Contains( x.WorldPosition ) ).ToList() )\n\t\t\tdoor.Close();\n\n\t\tforeach ( var loot in Scene.GetAllComponents<Loot>().ToList() )\n\t\t\tloot.GameObject.Destroy();\n\n\t\tMansionGame.Instance.TimerStop();\n\n\t\tforeach ( var doob in Scene.GetAllComponents<Doob>().ToList() )\n\t\t{\n\t\t\tif ( doob.Owner.IsValid() )\n\t\t\t\tdoob.Owner.Doob = null;\n\n\t\t\tdoob.GameObject.Destroy();\n\t\t}\n\t}\n\n\tpublic virtual void RegisterMonster( NPC monster ) => Monsters.Add( monster );\n\n\tpublic virtual void RemoveMonster( NPC monster )\n\t{\n\t\tMonsters.Remove( monster );\n\t\tif ( monster.IsValid() )\n\t\t\tmonster.GameObject.Destroy();\n\t}\n\n\tpublic static Type GetClrType( LevelType type ) => type switch\n\t{\n\t\tLevelType.Shop => typeof( ShopLevel ),\n\t\tLevelType.Mansion => typeof( MansionLevel ),\n\t\tLevelType.Dungeon => typeof( DungeonLevel ),\n\t\tLevelType.Bathrooms => typeof( BathroomsLevel ),\n\t\t_ => null,\n\t};\n}\n"
},
{
"Ident": "rue.house",
"Path": "Levels/ShopLevel.cs",
"FileName": "ShopLevel.cs",
"PackageType": "game",
"CodeKind": "Game",
"AssetVersionId": 301999,
"Code": "using System.Linq;\nusing System.Threading.Tasks;\nusing Sandbox;\n\nnamespace BrickJam;\n\npublic sealed class ShopLevel : Level\n{\n\tpublic override LevelType Type => LevelType.Shop;\n\n\tpublic override async Task Start()\n\t{\n\t\t// Shop is a safe hub - no base.Start() (no exit/monsters/loot/grid).\n\t\tawait GameTask.Yield();\n\n\t\tRespawnAll();\n\t\tMansionGame.Instance.TimerStop();\n\t}\n}\n"
},
{
"Ident": "rue.house",
"Path": "NPC/Specter.cs",
"FileName": "Specter.cs",
"PackageType": "game",
"CodeKind": "Game",
"AssetVersionId": 301999,
"Code": "using System;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing Sandbox;\nusing GridAStar;\n\nnamespace BrickJam;\n\n/// <summary>\n/// Specter monster - sinks into the floor and rises elsewhere. Scene-System port of legacy <c>Specter</c>,\n/// now on grid A*: it chases directly, and on a long idle teleports to a random grid cell (sink \u2192 reposition\n/// \u2192 rise). The lamp light flicker (legacy CapsuleLightEntity) is deferred to the effects system.\n/// </summary>\n[Title( \"Specter\" )]\n[Category( \"NPC\" )]\npublic sealed partial class Specter : NPC\n{\n\tpublic override string ModelPath { get; set; } = \"models/specter/specter.vmdl\";\n\tpublic override float WalkSpeed { get; set; } = 120f;\n\tpublic override float RunSpeed { get; set; } = 320f;\n\tpublic override float MaxVisionAngle { get; set; } = 240f;\n\tpublic override float MaxVisionRange { get; set; } = 1000f;\n\tpublic override float MaxVisionRangeWhenChasing { get; set; } = 1000f;\n\tpublic override float MaxVisionAngleWhenChasing { get; set; } = 180f;\n\tpublic override float MaxRememberTime { get; set; } = 3f;\n\tpublic override string IdleSound => \"sounds/specter/spectermoan.sound\";\n\tpublic override float IdleVolume => 1.5f;\n\tpublic override string AttackSound => \"sounds/specter/spectermoan.sound\";\n\tpublic override float AttackVolume => 2f;\n\n\tpublic float TimeToTeleport => 2f;\n\tpublic bool IsLowering => LastTeleport <= TimeToTeleport / 2f + 0.5f;\n\tpublic bool IsRising => LastTeleport <= TimeToTeleport + 1f && LastTeleport > TimeToTeleport / 2f + 0.5f;\n\tpublic bool IsTeleporting => IsLowering || IsRising;\n\n\t[Sync] public TimeSince LastTeleport { get; set; } = 999f;\n\n\tprivate static readonly Color LampColor = new( 1f, 0.55f, 0.2f );\n\tprivate PointLight lamp;\n\n\tprotected override void OnStart()\n\t{\n\t\tbase.OnStart();\n\n\t\t// Atmospheric lamp light (legacy CapsuleLightEntity). Purely visual, so each client builds its OWN\n\t\t// non-networked light and flickers it locally in OnUpdate - NPC logic (Think) is host-only and the\n\t\t// flicker shouldn't depend on replicated state.\n\t\tvar lampGo = new GameObject( true, \"Lamp\" ) { Parent = GameObject };\n\t\tlampGo.LocalPosition = Vector3.Up * 40f;\n\t\tlamp = lampGo.Components.Create<PointLight>();\n\t\tlamp.LightColor = LampColor;\n\t\tlamp.Radius = 350f;\n\t\tlamp.Shadows = false; // atmospheric glow - skip the (6-face) shadow pass for this point light\n\t}\n\n\tprotected override void OnUpdate()\n\t{\n\t\tif ( !lamp.IsValid() )\n\t\t\treturn;\n\n\t\t// Eerie flicker; fade the lamp out while the specter is sunk into the floor (teleporting).\n\t\tvar t = Time.Now;\n\t\tvar flicker = 0.7f + 0.18f * MathF.Sin( t * 27f ) + 0.12f * MathF.Sin( t * 11.3f );\n\t\tvar visible = IsTeleporting ? 0f : 1f;\n\n\t\tlamp.LightColor = LampColor * (flicker * visible * 4f);\n\t}\n\n\tpublic override void ComputeIdleAndSeek()\n\t{\n\t\t// While sinking/rising we don't make new decisions; ComputeMotion drives the vertical move.\n\t\tif ( IsTeleporting )\n\t\t\treturn;\n\n\t\tif ( InVision.Count > 0 )\n\t\t{\n\t\t\tTarget = InVision.OrderBy( x => x.Key.WorldPosition.Distance( WorldPosition ) ).FirstOrDefault().Key;\n\n\t\t\tif ( Target is Player player && player.Doob.IsValid() )\n\t\t\t\tTarget = player.Doob;\n\n\t\t\tLastTarget = Target;\n\t\t}\n\t\telse\n\t\t{\n\t\t\tTarget = null;\n\n\t\t\tif ( !IsFollowingPath && nextIdle && CurrentGrid is not null )\n\t\t\t{\n\t\t\t\tvar isLongIdle = MansionGame.Random.NextSingle() <= 0.2f;\n\t\t\t\tvar allCells = CurrentGrid.AllCells.ToList();\n\n\t\t\t\tCell chosen = null;\n\t\t\t\tfor ( var tried = 0; tried < 20 && allCells.Count > 0; tried++ )\n\t\t\t\t{\n\t\t\t\t\tvar candidate = MansionGame.Random.FromList( allCells, null );\n\t\t\t\t\tif ( candidate is null )\n\t\t\t\t\t\tbreak;\n\n\t\t\t\t\tvar dist = candidate.Position.Distance( WorldPosition );\n\t\t\t\t\tif ( isLongIdle ? dist >= 1000f : (dist >= 400f && dist <= 1000f) )\n\t\t\t\t\t{\n\t\t\t\t\t\tchosen = candidate;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif ( chosen != null )\n\t\t\t\t{\n\t\t\t\t\tif ( isLongIdle )\n\t\t\t\t\t\tTeleport( chosen.Position );\n\t\t\t\t\telse\n\t\t\t\t\t\tNavigateTo( chosen );\n\t\t\t\t}\n\n\t\t\t\tnextIdle = MansionGame.Random.NextSingle() * 1f + 1f;\n\t\t\t\tLastTarget = null;\n\t\t\t}\n\t\t}\n\n\t\tif ( Target.IsValid() && Target.WorldPosition.Distance( WorldPosition ) <= KillRange )\n\t\t{\n\t\t\tif ( Target is Player p )\n\t\t\t\t_ = CatchPlayer( p );\n\t\t\telse if ( Target is Doob d )\n\t\t\t\t_ = CatchDoob( d );\n\t\t}\n\n\t\tif ( nextIdleSound && !string.IsNullOrEmpty( IdleSound ) )\n\t\t{\n\t\t\tSoundExtensions.BroadcastPlay( IdleSound, WorldPosition, IdleVolume );\n\t\t\tnextIdleSound = MansionGame.Random.NextSingle() * 4f + 4f;\n\t\t}\n\t}\n\n\tpublic override void ComputeMotion()\n\t{\n\t\tif ( !IsTeleporting )\n\t\t{\n\t\t\tbase.ComputeMotion();\n\t\t\treturn;\n\t\t}\n\n\t\t// Sink into / rise out of the floor under manual control (off the grid).\n\t\tif ( IsLowering )\n\t\t\tWorldPosition += Vector3.Down * CollisionHeight * Time.Delta / (TimeToTeleport / 2f);\n\t\telse if ( IsRising )\n\t\t\tWorldPosition += Vector3.Up * CollisionHeight * Time.Delta / (TimeToTeleport / 2f);\n\t}\n\n\tpublic async void Teleport( Vector3 position )\n\t{\n\t\tLastTeleport = 0;\n\t\tMansionGame.Instance?.PlayEffect( \"prefabs/particles/specter_teleport.prefab\", WorldPosition, Rotation.Identity );\n\n\t\tawait Task.DelayRealtimeSeconds( TimeToTeleport * 0.5f + 0.5f );\n\n\t\tWorldPosition = position + Vector3.Down * CollisionHeight;\n\t}\n}\n"
},
{
"Ident": "rue.house",
"Path": "UI/GroundLootPanel.razor",
"FileName": "GroundLootPanel.razor",
"PackageType": "game",
"CodeKind": "Game",
"AssetVersionId": 301999,
"Code": "@using System\n@using Sandbox\n@using Sandbox.UI\n@namespace BrickJam.UI\n@inherits PanelComponent\n\n<root>\n\t@if ( Loot.IsValid() )\n\t{\n\t\t<div class=\"container\">\n\t\t\t<span>@Loot.FullName</span>\n\t\t\t<div class=\"row\">\n\t\t\t\t<span class=\"currency\">$</span><span>@($\"{Loot.MonetaryValue:n0}\")</span>\n\t\t\t</div>\n\t\t</div>\n\t}\n</root>\n\n@code {\n\tprivate Loot loot;\n\tprivate Loot Loot => loot ??= GetComponentInParent<Loot>();\n\n\tprotected override int BuildHash() => HashCode.Combine( Loot?.FullName, Loot?.MonetaryValue ?? 0 );\n}\n\n<style>\n\tGroundLootPanel {\n\t\ttransition: transform 0.5s ease-in-out;\n\t\tjustify-content: center;\n\t\talign-items: center;\n\t\twidth: 100%;\n\t\theight: 100%;\n\n\t\t.container {\n\t\t\tpadding: 10px;\n\t\t\tpadding-right: 30px;\n\t\t\tpadding-left: 30px;\n\t\t\tfont-size: 32px;\n\t\t\talign-items: center;\n\t\t\tfont-family: \"alagard\";\n\t\t\tflex-direction: column;\n\t\t\tcolor: white;\n\t\t\ttext-shadow: 3px 3px 0px black;\n\t\t\tbackground: linear-gradient(to left, rgba(black, 0) 0%, rgba(black, 0.5) 20%, rgba(black, 0.5) 80%, rgba(black, 0) 100%);\n\n\t\t\t.currency {\n\t\t\t\tpadding-right: 4px;\n\t\t\t\tfont-size: 22px;\n\t\t\t\ttop: 5px;\n\t\t\t\tcolor: rgba(50, 205, 50, 1);\n\t\t\t}\n\t\t}\n\n\t\t&:outro {\n\t\t\ttransform: scale(0);\n\t\t}\n\n\t\t&:intro {\n\t\t\ttransform: scale(0);\n\t\t}\n\t}\n</style>\n"
},
{
"Ident": "rue.house",
"Path": "UI/Hud.razor",
"FileName": "Hud.razor",
"PackageType": "game",
"CodeKind": "Game",
"AssetVersionId": 301999,
"Code": "@using System\n@using Sandbox\n@using Sandbox.UI\n@namespace BrickJam.UI\n@inherits PanelComponent\n\n<root>\n\t@{\n\t\tvar player = Player.Local;\n\n\t\tvar isSpectator = player?.Spectating ?? false;\n\t\tvar isPlayer = player?.IsValid() ?? false;\n\t}\n\n\t<MoneyCounter />\n\t<Eventlog />\n\t<PlayerCounter />\n\t<StunIndicator />\n\t<QuickPing />\n\t<SubtitlesList />\n\t<Lockpicker />\n\t<BlackScreen />\n\t\n\t<div class=\"input-hints\">\n\t\t@if ( isSpectator )\n\t\t{\n\t\t\t<div class=\"hint\"><inputglyph action=\"StopFollowing\"/>Stop following</div>\n\t\t\t<div class=\"hint\"><inputglyph action=\"FollowNext\"/>Next player</div>\n\t\t\t<div class=\"hint\"><inputglyph action=\"FollowPrevious\"/>Previous player</div>\n\t\t}\n\t\telse\n\t\t{\n\t\t\t<div class=\"hint\"><inputglyph action=\"inventory\"/>Inventory</div>\n\t\t\t<div class=\"hint\"><inputglyph action=\"chat\"/>Chat</div>\n\t\t\t<div class=\"hint\"><inputglyph action=\"ping\"/>Quick Ping</div>\n\t\t}\n\t</div>\n\n\t@if ( player.IsValid() )\n\t{\n\t\t<Inventory />\n\t\t<Shop />\n\t\t<TextInput Ghost=\"Say something...\" class=\"chat-input\" @ref=\"ChatInput\" onsubmit=@OnChatSubmit />\n\n\t\t@if ( !player.SeenTips )\n\t\t{\n\t\t\t<TutorialTips />\n\t\t}\n\t}\n\n\t@if ( player.IsValid() && player.IsAlive && !LockpickerBus.IsOpen )\n\t{\n\t\t<div class=\"crosshair\">\n\t\t\t<div class=\"mark\">+</div>\n\t\t\t<RadialProgress />\n\t\t\t<InteractionTip />\n\t\t</div>\n\t}\n\n\t@if ( player.IsValid() && !player.IsAlive )\n\t{\n\t\t<div class=\"death\">\n\t\t\t<span class=\"title\">YOU'RE DEAD!</span>\n\t\t</div>\n\t}\n</root>\n\n@code {\n\tprivate TextInput ChatInput { get; set; }\n\n\tprotected override void OnUpdate()\n\t{\n\t\tif ( ChatInput is null )\n\t\t\treturn;\n\n\t\tif ( Input.Pressed( \"chat\" ) )\n\t\t{\n\t\t\tChatInput.Focus();\n\t\t\tChatInput.AddClass( \"visible\" );\n\t\t}\n\t}\n\n\tprivate void OnChatSubmit()\n\t{\n\t\tif ( ChatInput is null )\n\t\t\treturn;\n\n\t\tvar text = ChatInput.Text;\n\t\tif ( !string.IsNullOrWhiteSpace( text ) )\n\t\t\tPlayer.Local?.Say( text );\n\n\t\tChatInput.Text = \"\";\n\t\tChatInput.RemoveClass( \"visible\" );\n\t}\n\n\tprotected override int BuildHash() => HashCode.Combine(\n\t\tPlayer.Local,\n\t\tPlayer.Local?.IsAlive ?? false,\n\t\tLockpickerBus.IsOpen );\n}\n\n<style>\n\tHud {\n\t\tposition: absolute;\n\t\ttop: 0;\n\t\tleft: 0;\n\t\twidth: 100%;\n\t\theight: 100%;\n\t\tdisplay: flex;\n\t\tflex-direction: column;\n\t\talign-items: center;\n\t\tfont-family: \"alagard\";\n\t\tjustify-content: flex-start;\n\n\t\t.crosshair {\n\t\t\tposition: absolute;\n\t\t\ttop: 0; left: 0;\n\t\t\twidth: 100%;\n\t\t\theight: 100%;\n\t\t\tflex-direction: column;\n\t\t\talign-items: center;\n\t\t\tjustify-content: center;\n\n\t\t\t.mark {\n\t\t\t\tcolor: white;\n\t\t\t\tfont-size: 28px;\n\t\t\t\ttext-shadow: 2px 2px 0px black;\n\t\t\t}\n\t\t}\n\n\t\t.input-hints {\n\t\t\tposition: absolute;\n\t\t\tbottom: 30px;\n\t\t\tright: 0px;\n\t\t\talign-items: flex-end;\n\t\t\tflex-direction: column;\n\t\t\tz-index: 3;\n\n\t\t\t.hint {\n\t\t\t\tcolor: white;\n\t\t\t\theight: 32px;\n\t\t\t\tjustify-content: center;\n\t\t\t\tfont-size: 32px;\n\t\t\t\tpadding-left: 50px;\n\t\t\t\tpadding-top: 10px;\n\t\t\t\tpadding-bottom: 40px;\n\t\t\t\tpadding-right: 10px;\n\t\t\t\tbackground: linear-gradient(to left, rgba(black, 0.5) 0%, rgba(black, 0.2) 75%, rgba(black, 0) 100%);\n\t\t\t\tmargin-top: 5px;\n\t\t\t\ttext-shadow: 3px 3px 0px black;\n\n\t\t\t\tInputGlyph {\n\t\t\t\t\twidth: 32px;\n\t\t\t\t\taspect-ratio: 1;\n\t\t\t\t\tmargin-right: 10px;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t.chat-input {\n\t\t\tposition: absolute;\n\t\t\twidth: 450px;\n\t\t\tmin-height: 20px;\n\t\t\tfont-size: 24px;\n\t\t\topacity: 0;\n\t\t\tpointer-events: all;\n\t\t\tbackground-color: rgba(37, 64, 98, 1);\n\t\t\tbox-shadow: 3px 3px 0px 0px rgba(17, 44, 78, 1);\n\t\t\ttransition: opacity 0.2s ease-in-out, transform 0.2s ease-in-out;\n\t\t\ttransform: translate(-50% 200px);\n\t\t\ttext-shadow: 3px 3px 0px black;\n\t\t\tleft: 50%;\n\t\t\ttop: 50%;\n\t\t\tmargin-top: 40px;\n\n\t\t\t&.visible {\n\t\t\t\topacity: 1;\n\t\t\t\ttransform: translate(-50% 0px);\n\t\t\t}\n\t\t}\n\n\t\t.death {\n\t\t\tposition: absolute;\n\t\t\twidth: 100%;\n\t\t\theight: 100%;\n\t\t\tleft: 0px;\n\t\t\ttop: 0px;\n\t\t\tcolor: white;\n\t\t\tfont-size: 32px;\n\t\t\tz-index: 2;\n\t\t\tbackdrop-filter: grayscale(100%);\n\t\t\tjustify-content: center;\n\t\t\ttransition: opacity 1s ease-in-out;\n\t\t\topacity: 0;\n\t\t\ttext-shadow: 4px 4px 0px black;\n\n\t\t\t&.visible {\n\t\t\t\topacity: 1;\n\t\t\t}\n\n\t\t\t.container {\n\t\t\t\tflex-direction: column;\n\t\t\t\talign-items: center;\n\t\t\t}\n\n\t\t\tspan {\n\t\t\t\tbackground: linear-gradient(to left, rgba(black, 0) 0%, rgba(black, 0.4) 20%, rgba(black, 0.4) 80%, rgba(black, 0) 100%);\n\t\t\t\tpadding: 10px;\n\t\t\t\tpadding-left: 40px;\n\t\t\t\tpadding-right: 40px;\n\t\t\t}\n\n\t\t\t.title {\n\t\t\t\tcolor: red;\n\t\t\t\tfont-size: 64px;\n\t\t\t\tmargin-top: 150px;\n\t\t\t\tmargin-bottom: 20px;\n\t\t\t}\n\t\t}\n\t}\n</style>\n"
},
{
"Ident": "rue.house",
"Path": "UI/Nametag.razor",
"FileName": "Nametag.razor",
"PackageType": "game",
"CodeKind": "Game",
"AssetVersionId": 301999,
"Code": "@using System\n@using Sandbox\n@using Sandbox.UI\n@using WorldPanel = Sandbox.WorldPanel\n@namespace BrickJam.UI\n@inherits PanelComponent\n\n<root>\n\t@if ( !IsLocal && Player.IsValid() && Player.IsAlive )\n\t{\n\t\t<div class=\"container\">\n\t\t\t<span>@Name</span>\n\t\t</div>\n\t}\n</root>\n\n@code {\n\tprivate Player player;\n\tprivate Player Player => player ??= GetComponentInParent<Player>();\n\n\t// Don't draw your own nametag in first person.\n\tprivate bool IsLocal => Player == Player.Local;\n\n\tprivate string Name => Player.IsValid() && Player.Network.Owner is { } owner ? owner.DisplayName : \"player\";\n\n\tprotected override int BuildHash() => HashCode.Combine( IsLocal, Name, Player?.IsAlive ?? false );\n}\n\n<style>\n\tNametag {\n\t\ttransition: transform 0.5s ease-in-out;\n\t\tjustify-content: center;\n\t\talign-items: center;\n\t\twidth: 100%;\n\t\theight: 100%;\n\n\t\t.container {\n\t\t\tpadding: 10px;\n\t\t\tpadding-right: 30px;\n\t\t\tpadding-left: 30px;\n\t\t\tfont-size: 32px;\n\t\t\talign-items: center;\n\t\t\tfont-family: \"alagard\";\n\t\t\tflex-direction: column;\n\t\t\tcolor: white;\n\t\t\ttext-shadow: 3px 3px 0px black;\n\t\t\tbackground: linear-gradient(to left, rgba(black, 0) 0%, rgba(black, 0.5) 20%, rgba(black, 0.5) 80%, rgba(black, 0) 100%);\n\t\t}\n\n\t\t&:outro {\n\t\t\ttransform: scale(0);\n\t\t}\n\n\t\t&:intro {\n\t\t\ttransform: scale(0);\n\t\t}\n\t}\n</style>\n"
},
{
"Ident": "rue.house",
"Path": "UI/PingBus.cs",
"FileName": "PingBus.cs",
"PackageType": "game",
"CodeKind": "Game",
"AssetVersionId": 301999,
"Code": "using System;\nusing Sandbox;\n\nnamespace BrickJam.UI;\n\n/// <summary>Decouples gameplay from the Razor <c>QuickPing</c> panel (see <see cref=\"EventlogBus\"/>).</summary>\npublic static class PingBus\n{\n\tpublic enum PingType { Enemy, Exit, Loot, Other }\n\n\tpublic static event Action<Vector3, PingType> OnPing;\n\n\tpublic static void Post( Vector3 worldPosition, PingType type ) => OnPing?.Invoke( worldPosition, type );\n}\n"
},
{
"Ident": "rue.house",
"Path": "UI/SubtitlesList.razor",
"FileName": "SubtitlesList.razor",
"PackageType": "game",
"CodeKind": "Game",
"AssetVersionId": 301999,
"Code": "@using System\n@using System.Linq\n@using System.Collections.Generic\n@using Sandbox\n@using Sandbox.UI\n@namespace BrickJam.UI\n@inherits Panel\n\n<root>\n\t@foreach ( var line in lines )\n\t{\n\t\t<div class=\"subtitle\">\n\t\t\t@if ( !string.IsNullOrEmpty( line.Speaker ) )\n\t\t\t{\n\t\t\t\t<span class=\"speaker\">@line.Speaker:</span>\n\t\t\t}\n\t\t\t<span class=\"text\">@line.Text</span>\n\t\t</div>\n\t}\n</root>\n\n@code {\n\tprivate readonly List<(string Speaker, string Text, TimeUntil Expire)> lines = new();\n\n\tpublic SubtitlesList()\n\t{\n\t\tSubtitleBus.OnSubtitle += Add;\n\t}\n\n\tpublic override void OnDeleted()\n\t{\n\t\tbase.OnDeleted();\n\t\tSubtitleBus.OnSubtitle -= Add;\n\t}\n\n\tprivate void Add( string speaker, string text, float duration )\n\t{\n\t\tlines.Add( (speaker, text, duration) );\n\t\tif ( lines.Count > 3 )\n\t\t\tlines.RemoveAt( 0 );\n\t}\n\n\tpublic override void Tick()\n\t{\n\t\tlines.RemoveAll( l => l.Expire );\n\t}\n\n\tprotected override int BuildHash() => HashCode.Combine( lines.Count, lines.LastOrDefault().Text );\n}\n\n<style>\n\tSubtitlesList {\n\t\tposition: absolute;\n\t\tleft: 0; right: 0;\n\t\tbottom: 60px;\n\t\tflex-direction: column;\n\t\talign-items: center;\n\n\t\t.subtitle {\n\t\t\tflex-direction: row;\n\t\t\tpadding: 6px 16px;\n\t\t\tmargin-top: 4px;\n\t\t\tbackground-color: rgba(0,0,0,0.6);\n\t\t\tfont-size: 26px;\n\t\t\ttext-shadow: 2px 2px 0px black;\n\n\t\t\t.speaker { color: rgba(255,220,80,1); margin-right: 8px; }\n\t\t\t.text { color: white; }\n\t\t}\n\t}\n</style>\n"
},
{
"Ident": "rue.house",
"Path": "ui/controls/switchcontrol.razor.scss",
"FileName": "switchcontrol.razor.scss",
"PackageType": "game",
"CodeKind": "Game",
"AssetVersionId": 301999,
"Code": "\r\n.switchcontrol\r\n{\r\n flex-direction: row;\r\n width: 100px;\r\n min-height: 24px;\r\n align-items: center;\r\n cursor: pointer;\r\n\r\n .switch-frame\r\n {\r\n flex-grow: 0;\r\n flex-shrink: 1;\r\n width: 48px;\r\n height: 16px;\r\n background-color: #fff1;\r\n margin: 0px 5px;\r\n align-items: center;\r\n border-radius: 100px;\r\n transition: all 0.4s linear;\r\n\r\n .switch-inner\r\n {\r\n position: relative;\r\n flex-grow: 0;\r\n flex-shrink: 1;\r\n background-color: #999;\r\n width: 25px;\r\n height: 25px;\r\n border-radius: 100px;\r\n left: 20%;\r\n transform: translateX( -50% );\r\n transition: all 0.3s ease-out;\r\n }\r\n }\r\n\r\n &.active\r\n {\r\n .switch-frame\r\n {\r\n background-color: #fffa;\r\n }\r\n\r\n .switch-inner\r\n {\r\n left: 80%;\r\n background-color: #fff;\r\n }\r\n }\r\n}\r\n"
},
{
"Ident": "rue.house",
"Path": "styles/form.scss",
"FileName": "form.scss",
"PackageType": "game",
"CodeKind": "Game",
"AssetVersionId": 301999,
"Code": "\r\n$form-control-height: 28px !default;\r\n\r\n@import \"form/_checkbox.scss\";\r\n@import \"form/_switch.scss\";\r\n@import \"form/_dropdown.scss\";\r\n@import \"form/_coloreditor.scss\";\r\n@import \"form/_colorproperty.scss\";\r\n\r\n.form\r\n{\r\n\tflex-direction: column;\r\n\talign-items: stretch;\r\n\tjustify-content: flex-start;\r\n\toverflow: scroll;\r\n}\r\n\r\n.field-group\r\n{\r\n\tflex-direction: column;\r\n\tflex-shrink: 0;\r\n}\r\n\r\n.field-header\r\n{\r\n\tflex-shrink: 0;\r\n}\r\n\r\n.field\r\n{\r\n\tcolor: white;\r\n\tfont-size: 14px;\r\n\tflex-shrink: 0;\r\n\tflex-grow: 0;\r\n\r\n\t> .label\r\n\t{\r\n\t\tflex-grow: 0;\r\n\t\tflex-shrink: 0;\r\n\t\tfont-weight: 600;\r\n\t\topacity: 0.4;\r\n\t\twidth: 20%;\r\n\t\tfont-size: 13px;\r\n\t}\r\n\r\n\t> .control\r\n\t{\r\n\t\tflex-shrink: 0;\r\n\t\tflex-grow: 1;\r\n\t\tflex-direction: column;\r\n\t}\r\n}\r\n\r\n.is-vertical > .field, .field.is-vertical\r\n{\r\n\tflex-direction: column;\r\n\r\n\t> .label\r\n\t{\r\n\t\twidth: auto;\r\n\t\theight: auto;\r\n\t}\r\n}"
},
{
"Ident": "rue.house",
"Path": "ui/controls/color/colorpickercontrol.cs.scss",
"FileName": "colorpickercontrol.cs.scss",
"PackageType": "game",
"CodeKind": "Game",
"AssetVersionId": 301999,
"Code": "ColorPickerControl\r\n{\r\n\tflex-direction: column;\r\n\tflex-shrink: 0;\r\n\tgap: 0.5rem;\r\n\tmargin: 1rem;\r\n}"
},
{
"Ident": "rue.house",
"Path": "ui/controls/enumcontrol.cs.scss",
"FileName": "enumcontrol.cs.scss",
"PackageType": "game",
"CodeKind": "Game",
"AssetVersionId": 301999,
"Code": "EnumControl\r\n{\r\n\tgap: 2px;\r\n\tflex-grow: 1;\r\n}\r\n\r\nEnumControl DropDown,\r\nEnumControl ButtonGroup\r\n{\r\n\tborder-radius: 8px;\r\n\tbackground-color: #000a;\r\n\tflex-grow: 1;\r\n}\r\n\r\nEnumControl DropDown\r\n{\r\n\tflex-grow: 1;\r\n\tmin-height: 32px;\r\n}\r\n\r\nEnumControl ButtonGroup\r\n{\r\n\tborder-radius: 12px;\r\n\toverflow: hidden;\r\n\tmin-height: 32px;\r\n\r\n\tButton\r\n\t{\r\n\t\tflex-grow: 1;\r\n\t\tjustify-content: center;\r\n\t\talign-items: center;\r\n\t\tgap: 4px;\r\n\t\tcolor: #aaa;\r\n\t\tfont-size: 1rem;\r\n\t\tcursor: pointer;\r\n\r\n\t\t.icon\r\n\t\t{\r\n\t\t\tcolor: #08f;\r\n\t\t}\r\n\r\n\t\t&:hover\r\n\t\t{\r\n\t\t\tcolor: #ddd;\r\n\r\n\t\t\t.icon\r\n\t\t\t{\r\n\t\t\t\tcolor: #3af;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t&:active\r\n\t\t{\r\n\t\t\tbackground-color: #04a;\r\n\t\t\tcolor: white;\r\n\t\t\ttransform: translateX( 1px ) translateY( 1px );\r\n\r\n\t\t\t.icon\r\n\t\t\t{\r\n\t\t\t\tcolor: #fff;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t&.active\r\n\t\t{\r\n\t\t\tbackground-color: #08f;\r\n\t\t\tcolor: white;\r\n\t\t\tpointer-events: none;\r\n\r\n\t\t\t.icon\r\n\t\t\t{\r\n\t\t\t\tcolor: #fff;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n}"
},
{
"Ident": "rue.house",
"Path": "ui/controls/color/coloralphacontrol.cs.scss",
"FileName": "coloralphacontrol.cs.scss",
"PackageType": "game",
"CodeKind": "Game",
"AssetVersionId": 301999,
"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": "rue.house",
"Path": "ui/controls/color/colorsaturationvaluecontrol.cs.scss",
"FileName": "colorsaturationvaluecontrol.cs.scss",
"PackageType": "game",
"CodeKind": "Game",
"AssetVersionId": 301999,
"Code": "ColorSaturationValueControl\r\n{\r\n\twidth: 240px;\r\n\theight: 240px;\r\n\tbackground-color: red;\r\n\tposition: relative;\r\n\tborder-radius: 4px;\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\twidth: 16px;\r\n\t\theight: 16px;\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% ) translateY( -50% );\r\n\t\tpointer-events: none;\r\n\t\tz-index: 100;\r\n\t\tz-index: 100;\r\n\t}\r\n\r\n\t.gradient\r\n\t{\r\n\t\tposition: absolute;\r\n\t\twidth: 100%;\r\n\t\theight: 100%;\r\n\t\tborder-radius: 4px;\r\n\t\tbackground: linear-gradient( to right, white, rgba( 255, 255, 255, 0 ) );\r\n\r\n\t\t&:after\r\n\t\t{\r\n\t\t\tcontent: \"\";\r\n\t\t\tposition: absolute;\r\n\t\t\twidth: 100%;\r\n\t\t\theight: 100%;\r\n\t\t\tborder-radius: 4px;\r\n\t\t\tbackground: linear-gradient( to top, black, rgba( 0, 0, 0, 0 ) );\r\n\t\t}\r\n\t}\r\n}\r\n"
},
{
"Ident": "rue.house",
"Path": "Grid/Grid.cs",
"FileName": "Grid.cs",
"PackageType": "game",
"CodeKind": "Game",
"AssetVersionId": 301999,
"Code": "using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Threading.Tasks;\nusing Sandbox;\n\nnamespace GridAStar;\n\npublic partial class Grid : IValid\n{\n\tpublic static Grid Main\n\t{\n\t\tget => Grids.GetValueOrDefault( \"main\" );\n\t\tset\n\t\t{\n\t\t\tif ( Grids.ContainsKey( \"main\" ) )\n\t\t\t\tGrids[\"main\"] = value;\n\t\t\telse\n\t\t\t\tGrids.Add( \"main\", value );\n\t\t}\n\t}\n\n\tpublic static Dictionary<string, Grid> Grids { get; set; } = new();\n\n\t/// <summary>The scene this grid traces against. Set on creation (Scene-System port).</summary>\n\tpublic Scene Scene { get; set; }\n\n\t// --- Generation diagnostics ---\n\tpublic static int DebugCastsHit;\n\tpublic static int DebugAngleRejected;\n\tpublic static int DebugOutOfBounds;\n\n\tpublic GridBuilder Settings { get; internal set; }\n\tpublic string Identifier => Settings.Identifier;\n\tpublic Dictionary<IntVector2, List<Cell>> CellStacks { get; internal set; } = new();\n\tpublic IEnumerable<Cell> AllCells => CellStacks.Values.SelectMany( list => list );\n\tpublic Vector3 Position => Settings.Position;\n\tpublic BBox Bounds => Settings.Bounds;\n\tpublic BBox RotatedBounds => Bounds.GetRotatedBounds( Rotation );\n\tpublic BBox WorldBounds => RotatedBounds.Translate( Position );\n\tpublic Transform Transform => new Transform( WorldBounds.Center, AxisRotation );\n\tpublic Rotation Rotation => Settings.Rotation;\n\tpublic bool AxisAligned => Settings.AxisAligned;\n\tpublic float StandableAngle => Settings.StandableAngle;\n\tpublic float StepSize => Settings.StepSize;\n\tpublic float CellSize => Settings.CellSize;\n\tpublic float HeightClearance => Settings.HeightClearance;\n\tpublic float WidthClearance => Settings.WidthClearance;\n\tpublic bool GridPerfect => Settings.GridPerfect;\n\tpublic bool StaticOnly => Settings.StaticOnly;\n\tpublic float MaxDropHeight => Settings.MaxDropHeight;\n\tpublic List<JumpDefinition> JumpDefinitions => Settings.JumpDefinitions;\n\tpublic int MinNeighbourCount => Settings.MinNeighbourCount;\n\tpublic bool IgnoreConnectionsForJumps => Settings.IgnoreConnectionsForJumps;\n\tpublic bool IgnoreLOSForJumps => Settings.IgnoreLOSForJumps;\n\tpublic bool CylinderShaped => Settings.CylinderShaped;\n\tpublic float Tolerance => GridPerfect ? 0.001f : 0f;\n\tpublic Rotation AxisRotation => AxisAligned ? new Rotation() : Rotation;\n\tpublic int MinimumColumn => WorldBounds.Mins.ToIntVector2( CellSize ).y;\n\tpublic int MaximumColumn => WorldBounds.Maxs.ToIntVector2( CellSize ).y;\n\tpublic int Columns => MaximumColumn - MinimumColumn;\n\tpublic int MinimumRow => WorldBounds.Mins.ToIntVector2( CellSize ).x;\n\tpublic int MaximumRow => WorldBounds.Maxs.ToIntVector2( CellSize ).x;\n\tpublic int Rows => MaximumRow - MinimumRow;\n\tbool IValid.IsValid { get; }\n\n\tpublic Grid()\n\t{\n\t\tSettings = new GridBuilder();\n\t}\n\n\tpublic Grid( GridBuilder settings )\n\t{\n\t\tSettings = settings;\n\t}\n\n\tpublic void Print( string message ) => Print( Identifier, message );\n\tpublic static void Print( string identifier, string message ) => Log.Info( $\"Grid '{identifier}': {message}\" );\n\n\tpublic BBox ToWorld( BBox bounds ) => bounds.GetRotatedBounds( AxisRotation ).Translate( WorldBounds.Center );\n\tpublic BBox ToLocal( BBox bounds ) => bounds.GetRotatedBounds( AxisRotation.Inverse ).Translate( -WorldBounds.Center );\n\n\tpublic IntVector2 PositionToCoordinates( Vector3 position ) => (position - WorldBounds.Mins - CellSize / 2).ToIntVector2( CellSize );\n\n\t/// <summary>Find the nearest cell from a position even if outside the grid (expensive).</summary>\n\tpublic Cell GetNearestCell( Vector3 position, bool onlyBelow = true, bool unoccupiedOnly = false )\n\t{\n\t\tvar validCells = AllCells;\n\n\t\tif ( unoccupiedOnly )\n\t\t\tvalidCells = validCells.Where( x => !x.Occupied );\n\t\tif ( onlyBelow )\n\t\t\tvalidCells = validCells.Where( x => x.Vertices.Min() - Math.Max( HeightClearance, StepSize ) <= position.z );\n\n\t\treturn validCells.OrderBy( x => x.Position.DistanceSquared( position ) )\n\t\t\t.FirstOrDefault();\n\t}\n\n\tpublic Cell GetCellInArea( Vector3 position, float width, bool onlyBelow = true, bool withinStepRange = true )\n\t{\n\t\tvar cellsToCheck = (int)Math.Ceiling( width / CellSize ) * 2;\n\t\tfor ( int y = 0; y <= cellsToCheck; y++ )\n\t\t{\n\t\t\tvar spiralY = MathAStar.SpiralPattern( y );\n\t\t\tfor ( int x = 0; x <= cellsToCheck; x++ )\n\t\t\t{\n\t\t\t\tvar spiralX = MathAStar.SpiralPattern( x );\n\t\t\t\tvar cellFound = GetCell( position + AxisRotation.Forward * spiralX * CellSize + AxisRotation.Right * spiralY * CellSize + Vector3.Up * StepSize, onlyBelow );\n\n\t\t\t\tif ( cellFound == null ) continue;\n\n\t\t\t\tif ( withinStepRange )\n\t\t\t\t\tif ( position.z - cellFound.Position.z <= Math.Max( HeightClearance, StepSize ) ) return cellFound; else continue;\n\n\t\t\t\treturn cellFound;\n\t\t\t}\n\t\t}\n\n\t\treturn null;\n\t}\n\n\tpublic Cell GetCell( Vector3 position, bool onlyBelow = true ) => GetCell( PositionToCoordinates( position ), onlyBelow ? position.z : WorldBounds.Maxs.z );\n\n\tpublic Cell GetCell( IntVector2 coordinates, float height )\n\t{\n\t\tvar cellsAtCoordinates = CellStacks.GetValueOrDefault( coordinates );\n\n\t\tif ( cellsAtCoordinates == null ) return null;\n\n\t\t// Return the cell CLOSEST to the query height among the candidates, not the first match. A column on\n\t\t// a spiral staircase / multi-floor area stacks several cells at the same XY; the original first-match\n\t\t// returned an arbitrary one (often the bottom of the spiral), so an NPC partway up got a path from the\n\t\t// wrong height and couldn't follow it. Candidate window (<= height + clearance) is unchanged.\n\t\tCell best = null;\n\t\tvar bestDist = float.MaxValue;\n\n\t\tforeach ( var cell in cellsAtCoordinates )\n\t\t{\n\t\t\tif ( cell.Vertices.Min() - Math.Max( HeightClearance, StepSize ) >= height )\n\t\t\t\tcontinue;\n\n\t\t\tvar dist = Math.Abs( cell.Position.z - height );\n\t\t\tif ( dist < bestDist )\n\t\t\t{\n\t\t\t\tbestDist = dist;\n\t\t\t\tbest = cell;\n\t\t\t}\n\t\t}\n\n\t\treturn best;\n\t}\n\n\tpublic void AddCell( Cell cell )\n\t{\n\t\tif ( cell == null ) return;\n\t\tvar coordinates = cell.GridPosition;\n\t\tif ( !CellStacks.ContainsKey( coordinates ) )\n\t\t\tCellStacks.Add( coordinates, new List<Cell>() { cell } );\n\t\telse\n\t\t\tif ( !CellStacks[coordinates].Any( x => Math.Abs( x.Position.z - cell.Position.z ) < Math.Max( HeightClearance, StepSize ) ) )\n\t\t\tCellStacks[coordinates].Add( cell );\n\t}\n\n\tpublic Cell GetCellInDirection( Cell startingCell, Vector3 direction, int numOfCellsInDirection = 1 ) => GetCell( startingCell.Position + direction * CellSize * numOfCellsInDirection );\n\n\tpublic Cell GetNeighbourInDirection( Cell cell, Vector3 direction )\n\t{\n\t\tvar horizontalDirection = direction.WithZ( 0 ).Normal;\n\t\tvar localCoordinates = horizontalDirection.ToIntVector2();\n\t\tvar coordinatesToCheck = cell.GridPosition + localCoordinates;\n\n\t\tvar cellsAtCoordinates = CellStacks.GetValueOrDefault( coordinatesToCheck );\n\n\t\tif ( cellsAtCoordinates == null ) return null;\n\n\t\tforeach ( var cellAtCoordinate in cellsAtCoordinates )\n\t\t\tif ( cell.IsNeighbour( cellAtCoordinate ) && cell != cellAtCoordinate )\n\t\t\t\treturn cellAtCoordinate;\n\n\t\treturn null;\n\t}\n\n\t/// <summary>Returns if there's a valid, unoccupied, and direct line of sight from a cell to another</summary>\n\tpublic bool LineOfSight( Cell startingCell, Cell endingCell, Component pathCreator = null, bool debugShow = false )\n\t{\n\t\tvar startingPosition = startingCell.Position;\n\t\tvar endingPosition = endingCell.Position;\n\t\tvar distanceInSteps = (int)Math.Ceiling( startingPosition.Distance( endingPosition ) / CellSize );\n\n\t\tif ( pathCreator == null && startingCell.Occupied ) return false;\n\t\tif ( pathCreator != null && startingCell.Occupied && startingCell.OccupyingEntity != pathCreator ) return false;\n\n\t\tif ( pathCreator == null && endingCell.Occupied ) return false;\n\t\tif ( pathCreator != null && endingCell.Occupied && endingCell.OccupyingEntity != pathCreator ) return false;\n\n\t\tCell lastCell = startingCell;\n\t\tfor ( int i = 0; i <= distanceInSteps; i++ )\n\t\t{\n\t\t\tvar direction = (endingPosition - lastCell.Position).Normal;\n\t\t\tvar cellToCheck = GetNeighbourInDirection( lastCell, direction );\n\n\t\t\tif ( cellToCheck == null ) return false;\n\t\t\tif ( cellToCheck == endingCell ) return true;\n\t\t\tif ( cellToCheck == lastCell ) continue;\n\t\t\tif ( pathCreator == null && cellToCheck.Occupied ) return false;\n\t\t\tif ( pathCreator != null && cellToCheck.Occupied && cellToCheck.OccupyingEntity != pathCreator ) return false;\n\t\t\tif ( !cellToCheck.IsNeighbour( lastCell ) ) return false;\n\n\t\t\tlastCell = cellToCheck;\n\n\t\t\tif ( debugShow )\n\t\t\t\tlastCell.Draw( 2f, false, false, false );\n\t\t}\n\n\t\treturn true;\n\t}\n\n\t/// <summary>Can you roughly walk towards the cell without it being a direct line of sight</summary>\n\tpublic bool IsDirectlyWalkable( Cell startingCell, Cell endingCell, float maxDistanceFromDirectPath = 150f, Component pathCreator = null, bool withConnections = true )\n\t{\n\t\tif ( startingCell == null || endingCell == null ) return false;\n\n\t\tvar currentCell = startingCell;\n\t\tvar directPath = new Line( startingCell.Position.WithZ( 0 ), endingCell.Position.WithZ( 0 ) );\n\t\tList<Cell> cellsChecked = new();\n\n\t\tif ( pathCreator == null && startingCell.Occupied ) return false;\n\t\tif ( pathCreator != null && startingCell.Occupied && startingCell.OccupyingEntity != pathCreator ) return false;\n\n\t\tif ( pathCreator == null && endingCell.Occupied ) return false;\n\t\tif ( pathCreator != null && endingCell.Occupied && endingCell.OccupyingEntity != pathCreator ) return false;\n\n\t\twhile ( currentCell != endingCell && directPath.Distance( currentCell.Position.WithZ( 0 ) ) <= maxDistanceFromDirectPath )\n\t\t{\n\t\t\tvar cellToCheck = withConnections ? currentCell.GetClosestNeighbourAndConnection( endingCell.Position ) : currentCell.GetClosestNeighbour( endingCell.Position );\n\n\t\t\tif ( cellToCheck == null ) return false;\n\t\t\tif ( cellsChecked.Contains( cellToCheck ) ) return false;\n\t\t\tif ( pathCreator == null && cellToCheck.Occupied ) return false;\n\t\t\tif ( pathCreator != null && cellToCheck.Occupied && cellToCheck.OccupyingEntity != pathCreator ) return false;\n\n\t\t\tif ( cellToCheck == endingCell ) return true;\n\n\t\t\tcellsChecked.Add( currentCell );\n\t\t\tcurrentCell = cellToCheck;\n\t\t}\n\n\t\treturn false;\n\t}\n\n\tpublic bool IsInsideBounds( Vector3 point ) => Bounds.IsRotatedPointWithinBounds( Position, point, Rotation );\n\tpublic bool IsInsideCylinder( Vector3 point ) => Bounds.IsInsideSquishedRotatedCylinder( Position, point, Rotation );\n\n\tpublic void Initialize()\n\t{\n\t\tif ( Grids.ContainsKey( Identifier ) )\n\t\t{\n\t\t\tif ( Grids[Identifier] != null )\n\t\t\t\tGrids[Identifier].Delete( true );\n\n\t\t\tGrids[Identifier] = this;\n\t\t}\n\t\telse\n\t\t\tGrids.Add( Identifier, this );\n\t}\n\n\tpublic void Delete( bool deleteSave = false )\n\t{\n\t\tif ( Grids.ContainsKey( Identifier ) )\n\t\t{\n\t\t\tGrids[Identifier] = null;\n\t\t\tGrids.Remove( Identifier );\n\t\t}\n\t}\n\n\tpublic List<Cell> GetCellsInBBox( BBox bbox )\n\t{\n\t\tvar cells = new List<Cell>();\n\n\t\tforeach ( var cell in AllCells )\n\t\t\tif ( bbox.Contains( cell.Position ) )\n\t\t\t\tcells.Add( cell );\n\n\t\treturn cells;\n\t}\n\n\tpublic override int GetHashCode() => Settings.GetHashCode();\n\n\t/// <summary>Gives the edge tag to all cells with less than 8 neighbours</summary>\n\tpublic async Task AssignEdgeCells( int maxNeighourCount = 8, int threadsToUse = 1, bool clearTags = false, string tagToExclude = \"\", string tagToAssign = \"edge\" ) => await assignEdgeCellsInternal( AllCells.ToList(), maxNeighourCount, threadsToUse, clearTags, tagToExclude, tagToAssign );\n\n\tpublic async Task AssignEdgeCells( BBox bounds, int maxNeighourCount = 8, int threadsToUse = 1, bool clearTags = false, string tagToExclude = \"\", string tagToAssign = \"edge\" ) => await assignEdgeCellsInternal( GetCellsInBBox( bounds ), maxNeighourCount, threadsToUse, clearTags, tagToExclude, tagToAssign );\n\n\tinternal async Task assignEdgeCellsInternal( List<Cell> cells, int maxNeighourCount = 8, int threadsToUse = 1, bool clearTags = false, string tagToExclude = \"\", string tagToAssign = \"edge\" )\n\t{\n\t\tvar cellsCount = cells.Count();\n\t\tthreadsToUse = Math.Max( 1, threadsToUse );\n\t\tvar cellsEachThread = (int)(cellsCount / threadsToUse);\n\t\tvar lastThreadCount = cellsCount - (cellsEachThread * (threadsToUse - 1));\n\t\tList<Task> tasks = new();\n\n\t\tfor ( int i = 0; i < threadsToUse; i++ )\n\t\t{\n\t\t\tvar curentThread = i;\n\n\t\t\ttasks.Add( GameTask.RunInThreadAsync( () =>\n\t\t\t{\n\t\t\t\tvar cellsRange = curentThread == threadsToUse - 1 ? cellsEachThread : lastThreadCount;\n\t\t\t\tvar cellsToCheck = cells.Skip( cellsEachThread * curentThread ).Take( cellsRange );\n\n\t\t\t\tforeach ( var cell in cellsToCheck )\n\t\t\t\t{\n\t\t\t\t\tif ( clearTags )\n\t\t\t\t\t\tcell.Tags.Remove( tagToAssign );\n\n\t\t\t\t\tvar neighbours = cell.GetNeighbours();\n\n\t\t\t\t\tif ( tagToExclude != \"\" )\n\t\t\t\t\t\tneighbours = neighbours.Where( x => !x.Tags.Has( tagToExclude ) );\n\n\t\t\t\t\tif ( neighbours.Count() < maxNeighourCount )\n\t\t\t\t\t\tcell.Tags.Add( tagToAssign );\n\t\t\t\t}\n\t\t\t} ) );\n\t\t}\n\n\t\tawait GameTask.WhenAll( tasks );\n\t}\n\n\t/// <summary>Adds the droppable connection to cells you can drop from</summary>\n\tpublic async Task AssignDroppableCells( int threadsToUse = 1 ) => await internalAssignDroppableCells( CellsWithTag( \"edge\" ).ToList(), threadsToUse );\n\n\tpublic async Task AssignDroppableCells( BBox bounds, int threadsToUse = 1 ) => await internalAssignDroppableCells( CellsWithTag( bounds, \"edge\" ).ToList(), threadsToUse );\n\n\tinternal async Task internalAssignDroppableCells( List<Cell> cells, int threadsToUse = 1 )\n\t{\n\t\tvar allCells = cells;\n\t\tvar cellsCount = allCells.Count();\n\t\tthreadsToUse = Math.Max( 1, threadsToUse );\n\t\tvar cellsEachThread = (int)(cellsCount / threadsToUse);\n\t\tvar lastThreadCount = cellsCount - (cellsEachThread * (threadsToUse - 1));\n\t\tList<Task> tasks = new();\n\n\t\tfor ( int i = 0; i < threadsToUse; i++ )\n\t\t{\n\t\t\tvar curentThread = i;\n\n\t\t\ttasks.Add( GameTask.RunInThreadAsync( () =>\n\t\t\t{\n\t\t\t\tvar cellsRange = curentThread == threadsToUse - 1 ? cellsEachThread : lastThreadCount;\n\t\t\t\tvar cellsToCheck = allCells.Skip( cellsEachThread * curentThread ).Take( cellsRange );\n\n\t\t\t\tforeach ( var cell in cellsToCheck )\n\t\t\t\t{\n\t\t\t\t\tvar droppableCell = cell.GetFirstValidDroppable( maxHeightDistance: MaxDropHeight );\n\t\t\t\t\tif ( droppableCell != null )\n\t\t\t\t\t\tcell.AddConnection( droppableCell, \"drop\" );\n\t\t\t\t}\n\t\t\t} ) );\n\t\t}\n\n\t\tawait GameTask.WhenAll( tasks );\n\t}\n\n\tpublic IEnumerable<Cell> JumpableCandidates()\n\t{\n\t\tvar droppedCells = CellsWithConnection( \"drop\" ).SelectMany( cell => cell.GetConnections( \"drop\" ).Select( connection => connection.Current ) );\n\t\treturn CellsWithTag( \"edge\" ).Concat( droppedCells );\n\t}\n\n\tpublic async Task AssignJumpableCells( JumpDefinition definition, int threadsToUse = 16 ) => await internalAssignJumpableCells( JumpableCandidates().ToList(), definition, threadsToUse );\n\n\tinternal async Task internalAssignJumpableCells( List<Cell> cells, JumpDefinition definition, int threadsToUse = 16 )\n\t{\n\t\tvar allCells = cells;\n\t\tvar cellsCount = allCells.Count();\n\t\tthreadsToUse = Math.Max( 1, threadsToUse );\n\t\tvar cellsEachThread = (int)(cellsCount / threadsToUse);\n\t\tvar lastThreadCount = cellsCount - (cellsEachThread * (threadsToUse - 1));\n\t\tList<Task> tasks = new();\n\n\t\tfor ( int i = 0; i < threadsToUse; i++ )\n\t\t{\n\t\t\tvar curentThread = i;\n\n\t\t\ttasks.Add( GameTask.RunInThreadAsync( () =>\n\t\t\t{\n\t\t\t\tvar totalFraction = 1f;\n\t\t\t\tvar cellsRange = curentThread == threadsToUse - 1 ? cellsEachThread : lastThreadCount;\n\t\t\t\tvar cellsToCheck = allCells.Skip( cellsEachThread * curentThread ).Take( cellsRange );\n\n\t\t\t\tforeach ( var cell in cellsToCheck )\n\t\t\t\t{\n\t\t\t\t\tif ( totalFraction >= 1f )\n\t\t\t\t\t{\n\t\t\t\t\t\tList<Cell> connectedCells = new();\n\t\t\t\t\t\tList<AStarNode> jumpConnections = new();\n\n\t\t\t\t\t\tforeach ( var jumpableCell in cell.GetValidJumpables( definition, MaxDropHeight, IgnoreConnectionsForJumps, IgnoreLOSForJumps ) )\n\t\t\t\t\t\t\tif ( jumpableCell != null )\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tjumpConnections.Add( cell.AddConnection( jumpableCell, definition.Name ) );\n\t\t\t\t\t\t\t\tconnectedCells.Add( jumpableCell );\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\tforeach ( var jumpableConnection in connectedCells )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tvar direction = (cell.Position - jumpableConnection.Position).WithZ( 0 ).Normal;\n\t\t\t\t\t\t\tvar jumpbackCell = jumpableConnection.GetValidJumpable( definition, direction, MaxDropHeight, IgnoreConnectionsForJumps, IgnoreLOSForJumps );\n\n\t\t\t\t\t\t\tif ( jumpbackCell != null )\n\t\t\t\t\t\t\t\tif ( !IsDirectlyWalkable( jumpbackCell, cell ) )\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tvar duplicate = false;\n\t\t\t\t\t\t\t\t\tforeach ( var connection in jumpConnections )\n\t\t\t\t\t\t\t\t\t\tif ( connection.Parent.Current == jumpbackCell && connection.MovementTag == definition.Name )\n\t\t\t\t\t\t\t\t\t\t\tduplicate = true;\n\t\t\t\t\t\t\t\t\tif ( !duplicate )\n\t\t\t\t\t\t\t\t\t\tjumpConnections.Add( jumpableConnection.AddConnection( jumpbackCell, definition.Name ) );\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tforeach ( var connection in jumpConnections )\n\t\t\t\t\t\t\tif ( LineOfSight( connection.Parent.Current, connection.Current ) )\n\t\t\t\t\t\t\t\tconnection.Parent.Current.RemoveConnection( connection );\n\n\t\t\t\t\t\ttotalFraction = 0f;\n\t\t\t\t\t}\n\n\t\t\t\t\ttotalFraction += definition.GenerateFraction;\n\t\t\t\t}\n\t\t\t} ) );\n\t\t}\n\n\t\tawait GameTask.WhenAll( tasks );\n\t}\n\n\tpublic Vector3 TraceParabola( Vector3 startingPosition, Vector3 horizontalVelocity, float verticalSpeed, float gravity, float maxDropHeight, int subSteps = 2 )\n\t{\n\t\tvar horizontalDirection = horizontalVelocity.WithZ( 0 ).Normal;\n\t\tvar horizontalSpeed = horizontalVelocity.WithZ( 0 ).Length;\n\t\tvar maxHeight = startingPosition.z + MathAStar.ParabolaMaxHeight( verticalSpeed, gravity );\n\t\tvar minHeight = maxHeight - maxDropHeight;\n\t\tvar currentDistance = 1;\n\t\tvar lastPositionChecked = startingPosition;\n\n\t\twhile ( lastPositionChecked.z >= minHeight )\n\t\t{\n\t\t\tvar horizontalOffset = CellSize * currentDistance / subSteps;\n\t\t\tvar verticalOffset = MathAStar.ParabolaHeight( horizontalOffset, horizontalSpeed, verticalSpeed, gravity );\n\t\t\tvar nextPositionToCheck = startingPosition + horizontalDirection * horizontalOffset + Vector3.Up * verticalOffset;\n\n\t\t\tvar clearanceBBox = new BBox( new Vector3( -WidthClearance / 2f, -WidthClearance / 2f, StepSize ), new Vector3( WidthClearance / 2f, WidthClearance / 2f, HeightClearance ) );\n\t\t\tvar jumpTrace = Scene.Trace.Box( clearanceBBox, lastPositionChecked, nextPositionToCheck )\n\t\t\t\t.WithGridSettings( Settings )\n\t\t\t\t.Run();\n\n\t\t\tif ( jumpTrace.Hit )\n\t\t\t\treturn jumpTrace.EndPosition;\n\n\t\t\tlastPositionChecked = nextPositionToCheck;\n\t\t\tcurrentDistance++;\n\t\t}\n\n\t\treturn lastPositionChecked;\n\t}\n\n\tpublic void RemoveCells( BBox bounds, bool printInfo = false )\n\t{\n\t\tvar cellsToRemove = GetCellsInBBox( bounds );\n\t\tvar count = cellsToRemove.Count();\n\n\t\tforeach ( var cell in cellsToRemove )\n\t\t\tcell.Delete();\n\n\t\tif ( printInfo )\n\t\t\tPrint( $\"Removed {count} cells\" );\n\t}\n\n\tpublic async Task GenerateCells( BBox bounds, int threadedChunkSides = 1, bool printInfo = true )\n\t{\n\t\tList<Task<List<Cell>>> tasks = new();\n\t\tvar totalMins = bounds.Mins;\n\t\tvar totalMaxs = bounds.Maxs;\n\t\tvar totalSize = bounds.Size;\n\n\t\tthreadedChunkSides = Math.Max( 1, threadedChunkSides );\n\n\t\tfor ( int x = 1; x <= threadedChunkSides; x++ )\n\t\t{\n\t\t\tfor ( int y = 1; y <= threadedChunkSides; y++ )\n\t\t\t{\n\t\t\t\tvar xOffset = totalSize.x / threadedChunkSides * x - totalSize.x / threadedChunkSides / 2;\n\t\t\t\tvar yOffset = totalSize.y / threadedChunkSides * y - totalSize.y / threadedChunkSides / 2;\n\t\t\t\tvar offset = new Vector3( xOffset, yOffset );\n\t\t\t\tvar chunkSize = totalSize / threadedChunkSides;\n\t\t\t\tvar chunkMins = totalMins + offset - chunkSize / 2;\n\t\t\t\tvar chunkMaxs = totalMins + offset + chunkSize / 2;\n\t\t\t\tvar dividedBounds = new BBox( chunkMins.WithZ( totalMins.z ), chunkMaxs.WithZ( totalMaxs.z ) );\n\n\t\t\t\ttasks.Add( GameTask.RunInThreadAsync( () => createCells( dividedBounds, printInfo ) ) );\n\t\t\t}\n\t\t}\n\n\t\tawait GameTask.WhenAll( tasks );\n\n\t\tforeach ( var task in tasks )\n\t\t\tforeach ( var cell in task.Result )\n\t\t\t\tAddCell( cell );\n\t}\n\n\t/// <summary>Create cells in that local bbox (Doesn't add them)</summary>\n\tprivate List<Cell> createCells( BBox bounds, bool printInfo = true )\n\t{\n\t\tvar generatedCells = new List<Cell>();\n\n\t\tvar minimumGrid = bounds.Mins.ToIntVector2( CellSize );\n\t\tvar maximumGrid = bounds.Maxs.ToIntVector2( CellSize );\n\t\tvar startingColumn = minimumGrid.y - MinimumColumn;\n\t\tvar totalColumns = maximumGrid.y - minimumGrid.y;\n\t\tvar endingColumn = startingColumn + totalColumns;\n\t\tvar startingRow = minimumGrid.x - MinimumRow;\n\t\tvar totalRows = maximumGrid.x - minimumGrid.x;\n\t\tvar endingRow = startingRow + totalRows;\n\n\t\tfor ( int column = startingColumn; column < endingColumn; column++ )\n\t\t{\n\t\t\tfor ( int row = startingRow; row < endingRow; row++ )\n\t\t\t{\n\t\t\t\tvar startPosition = WorldBounds.Mins.WithZ( WorldBounds.Maxs.z ) + new Vector3( row * CellSize + CellSize / 2f, column * CellSize + CellSize / 2f, Tolerance * 2f ) * AxisRotation;\n\t\t\t\tvar endPosition = WorldBounds.Mins + new Vector3( row * CellSize + CellSize / 2f, column * CellSize + CellSize / 2f, -Tolerance ) * AxisRotation;\n\t\t\t\tvar checkBBox = new BBox( new Vector3( -CellSize / 2f + Tolerance, -CellSize / 2f + Tolerance, 0f ), new Vector3( CellSize / 2f - Tolerance, CellSize / 2f - Tolerance, 0.001f ) );\n\t\t\t\tvar positionTrace = Scene.Trace.Box( checkBBox, startPosition, endPosition )\n\t\t\t\t\t.WithGridSettings( Settings );\n\n\t\t\t\tvar positionResult = positionTrace.Run();\n\n\t\t\t\twhile ( positionResult.Hit && startPosition.z >= endPosition.z )\n\t\t\t\t{\n\t\t\t\t\tDebugCastsHit++;\n\t\t\t\t\tif ( IsInsideBounds( positionResult.HitPosition ) )\n\t\t\t\t\t{\n\t\t\t\t\t\tif ( !CylinderShaped || IsInsideCylinder( positionResult.HitPosition ) )\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tvar angle = Vector3.GetAngle( Vector3.Up, positionResult.Normal );\n\t\t\t\t\t\t\tif ( angle <= StandableAngle )\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tvar newCell = Cell.TryCreate( this, positionResult.HitPosition );\n\n\t\t\t\t\t\t\t\tif ( newCell != null )\n\t\t\t\t\t\t\t\t\tgeneratedCells.Add( newCell );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\telse\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tDebugAngleRejected++;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\telse\n\t\t\t\t\t{\n\t\t\t\t\t\tDebugOutOfBounds++;\n\t\t\t\t\t}\n\n\t\t\t\t\tstartPosition = positionResult.HitPosition + Vector3.Down * HeightClearance;\n\n\t\t\t\t\t// Scene-System port of Sandbox.Trace.TestPoint: a zero-length sphere trace reports\n\t\t\t\t\t// StartedSolid when the point is inside geometry. Step down until we're clear.\n\t\t\t\t\twhile ( Scene.Trace.Sphere( CellSize / 2f - Tolerance, startPosition, startPosition ).Run().StartedSolid )\n\t\t\t\t\t\tstartPosition += Vector3.Down * HeightClearance;\n\n\t\t\t\t\tpositionTrace = Scene.Trace.Box( checkBBox, startPosition, endPosition )\n\t\t\t\t\t\t.WithGridSettings( Settings );\n\n\t\t\t\t\tpositionResult = positionTrace.Run();\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn generatedCells;\n\t}\n\n\tpublic IEnumerable<Cell> CellsWithTag( string tag ) => AllCells.Where( cell => cell.Tags.Has( tag ) );\n\tpublic IEnumerable<Cell> CellsWithTags( params string[] tags ) => AllCells.Where( cell => cell.Tags.Has( tags ) );\n\tpublic IEnumerable<Cell> CellsWithTags( List<string> tags ) => AllCells.Where( cell => cell.Tags.Has( tags ) );\n\tpublic IEnumerable<Cell> CellsWithTag( BBox bounds, string tag ) => GetCellsInBBox( bounds ).Where( cell => cell.Tags.Has( tag ) );\n\tpublic IEnumerable<Cell> CellsWithTags( BBox bounds, params string[] tags ) => GetCellsInBBox( bounds ).Where( cell => cell.Tags.Has( tags ) );\n\tpublic IEnumerable<Cell> CellsWithTags( BBox bounds, List<string> tags ) => GetCellsInBBox( bounds ).Where( cell => cell.Tags.Has( tags ) );\n\tpublic IEnumerable<Cell> CellsWithConnection( string movementTag ) => AllCells.Where( cell => cell.GetConnections( movementTag ).Count() > 0 );\n\tpublic IEnumerable<Cell> CellsWithConnection( BBox bounds, string movementTag ) => GetCellsInBBox( bounds ).Where( cell => cell.GetConnections( movementTag ).Count() > 0 );\n\n\tpublic void CheckOccupancy( string tag )\n\t{\n\t\tforeach ( var cell in AllCells )\n\t\t\tcell.Occupied = cell.TestForOccupancy( tag );\n\t}\n}\n"
},
{
"Ident": "rue.house",
"Path": "Grid/GridSettings.cs",
"FileName": "GridSettings.cs",
"PackageType": "game",
"CodeKind": "Game",
"AssetVersionId": 301999,
"Code": "namespace GridAStar;\n\n// Set STEP_SIZE or WIDTH_CLEARANCE to 0 to disable them (faster grid generation)\npublic static partial class GridSettings\n{\n\tpublic const float DEFAULT_STANDABLE_ANGLE = 40f; // How steep the terrain can be on a cell before it gets discarded\n\tpublic const float DEFAULT_STEP_SIZE = 12f; // How big steps can be on a cell before it gets discarded\n\tpublic const float DEFAULT_CELL_SIZE = 16f; // How large each cell will be in hammer units\n\tpublic const float DEFAULT_HEIGHT_CLEARANCE = 72f; // How much vertical space there should be\n\tpublic const float DEFAULT_WIDTH_CLEARANCE = 24f; // How much horizontal space there should be\n\tpublic const float DEFAULT_DROP_HEIGHT = 400f; // How high you can drop down from\n\tpublic const bool DEFAULT_GRID_PERFECT = false; // For grid-perfect terrain, if true it will not be checking for steps, so use ramps instead\n\tpublic const bool DEFAULT_STATIC_ONLY = true; // Will it only hit world and static or also dynamic\n}\n"
},
{
"Ident": "rue.house",
"Path": "Grid/TraceExtensions.cs",
"FileName": "TraceExtensions.cs",
"PackageType": "game",
"CodeKind": "Game",
"AssetVersionId": 301999,
"Code": "using Sandbox;\n\nnamespace GridAStar;\n\npublic static partial class TraceExtensions\n{\n\t/// <summary>\n\t/// Apply a grid's generation filters to a scene trace. Scene-System port: legacy <c>StaticOnly()</c>\n\t/// becomes <c>IgnoreDynamic()</c>. Tag filters are only applied when non-empty (an empty\n\t/// <c>WithAllTags</c> would otherwise filter against nothing).\n\t/// </summary>\n\tpublic static SceneTrace WithGridSettings( this SceneTrace self, GridBuilder settings )\n\t{\n\t\tif ( settings.StaticOnly )\n\t\t\tself = self.IgnoreDynamic();\n\n\t\t// ANY of the include tags (not all) - the floor may be tagged \"world\" while props are \"solid\", and\n\t\t// requiring both would match nothing. Legacy used WithAllTags but its maps used a single floor tag.\n\t\tif ( settings.TagsToInclude.Count > 0 )\n\t\t\tself = self.WithAnyTags( settings.TagsToInclude.ToArray() );\n\n\t\tif ( settings.TagsToExclude.Count > 0 )\n\t\t\tself = self.WithoutTags( settings.TagsToExclude.ToArray() );\n\n\t\treturn self;\n\t}\n}\n"
}
]
}