2213 results

global using static Sandbox.Internal.GlobalGameNamespace;
global using Microsoft.AspNetCore.Components;
global using Microsoft.AspNetCore.Components.Rendering;
[assembly: global::System.Reflection.AssemblyMetadata( "AddonTitle", "Debug Assertions" )]
[assembly: global::System.Reflection.AssemblyMetadata( "AddonIdent", "debugassertions" )]
[assembly: global::System.Reflection.AssemblyMetadata( "OrgIdent", "quality" )]
[assembly: global::System.Reflection.AssemblyMetadata( "Ident", "quality.debugassertions" )]
[assembly: global::System.Reflection.AssemblyMetadata( "CompileTime", "2/10/2026 8:52:37 AM" )]
[assembly: global::System.Reflection.AssemblyMetadata( "EngineVersion", "24" )]
[assembly: global::System.Reflection.AssemblyMetadata( "EngineMinorVersion", "1" )]

[assembly: System.Runtime.Versioning.TargetFramework( ".NETCoreApp,Version=v9.0", FrameworkDisplayName = ".NET 9.0" )]
[assembly: global::System.Reflection.AssemblyVersion("0.0.117.0")]
[assembly: global::System.Reflection.AssemblyFileVersion("0.0.117.0")]
using System;

namespace Sandbox;
[Title("Particle Ring Emitter Even")]
[Category( "Particles" )]	
[Description("Let's you ensure that the particles are placed evenly around the ring when emitted. Simply check the \"Even Angle\" checkbox and you are set !")]
public class ParticleRingEmitterEven : ParticleEmitter
{
	[Property] public ParticleFloat Radius { get; set; } = 50.0f;
	[Property] public ParticleFloat Thickness { get; set; } = 10.0f;
	[Property, Range( 0, 360 )] public ParticleFloat AngleStart { get; set; } = 0.0f;
	[Property, Range( 0, 360 )] public ParticleFloat Angle { get; set; } = 360.0f;
	[Property, Range( 0, 1 )] public ParticleFloat Flatness { get; set; } = 0.0f;
	[Property, Range( -100, 100 )] public ParticleFloat VelocityFromCenter { get; set; } = 0.0f;
	[Property, Range( -100, 100 )] public ParticleFloat VelocityFromRing { get; set; } = 0.0f;
	[Property] public bool EvenAngle { get; set; } = false;

	private float _angleStep = 0.0f;

	protected override void OnUpdate()
	{

		
	}
	
	public override bool Emit( ParticleEffect target )
	{
		
		if ( target.Particles.Count == 0 )
		{
			_angleStep = 0.0f;
		}
		
		var angle = 0f;
		if ( !EvenAngle )
		{
			angle = Random.Shared.Float( 0, Angle.Evaluate( Delta, EmitRandom ).DegreeToRadian() );
			angle += AngleStart.Evaluate( Delta, EmitRandom ).DegreeToRadian();
		}
		else
		{
			angle = _angleStep;
			AngleStepBurst();
		}

		var x = MathF.Sin( angle );
		var y = MathF.Cos( angle );

		var size = new Vector3( x, y, 0 ) * Radius.Evaluate( Delta, 0 );
		var ringOffset = Vector3.Zero;

		var thickness = Thickness.Evaluate( Delta, EmitRandom );

		if ( thickness > 0 )
		{
			ringOffset = Vector3.Random * thickness;
			ringOffset.z *= (1 - Flatness.Evaluate( Delta, EmitRandom ));

			size += ringOffset;
		}

		size = (size * WorldScale) * WorldRotation;

		var p = target.Emit( WorldPosition + size, Delta );
		if ( p is not null )
		{
			var velFromCenter = VelocityFromCenter.Evaluate( Delta, EmitRandom );
			if ( velFromCenter != 0 )
			{
				p.Velocity += (size.Normal * velFromCenter);
			}

			var velFromRing = VelocityFromRing.Evaluate( Delta, EmitRandom );
			if ( velFromRing != 0 )
			{
				ringOffset = (ringOffset * WorldScale) * WorldRotation;
				p.Velocity += (ringOffset.Normal * velFromRing);
			}
		}

		return true;
	}

	private void AngleStepBurst()
	{
		
		
		_angleStep += Angle.Evaluate( Delta, EmitRandom ).DegreeToRadian() / Burst ;
		
	}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json.Nodes;
using System.Text.Json.Serialization;
using Sandbox;

namespace Sandbox;

[Title( "Shader Particle Model Renderer" )]
[Category( "Particles" )]
[Description(
	"Adds the \"Particle Shader\" feature which let's you set shader parameters on the particles model using the particle system !" )]
public class ShaderParticleModelRenderer : ParticleController, Component.ExecuteInEditor
{
	private ParticleModelRenderer _particleModelRenderer { get; set; } = new ParticleModelRenderer();


	/**
	 *  These are the dictionaries used to set the values for the render attributes in the particle system
	 */
	[Property, FeatureEnabled("ParticleShader")]
	public bool ParticleShaderEnabled { get; set; } = true;
	
	
	[Property, Group( "ColorParameters" ), Feature( "ParticleShader" )]
	public Dictionary<String, ParticleGradient> Colors { get; set; }

	[Property, Group( "FloatParameters" ), Feature( "ParticleShader" )]
	public Dictionary<String, ParticleFloat> Floats { get; set; }

	[Property, Group( "Float2Parameters" ), Feature( "ParticleShader" )]
	public Dictionary<String, Vector2> Floats2 { get; set; }

	[Property, Group( "Float4Parameters" ), Feature( "ParticleShader" )]
	public Dictionary<String, Vector4> Floats4 { get; set; }

	[Property, Group( "Textures" ), Feature( "ParticleShader" )]
	public Dictionary<String, Texture> Textures { get; set; }

	[Property, Group( "DynamicCombos" ), Feature( "ParticleShader" )]
	public Dictionary<String, int> DynamicCombos { get; set; }
	

	
	

	[RequireComponent] public new ParticleEffect ParticleEffect { get; set; }

	[Property, Order( -100 ), InlineEditor( Label = false ), Group( "Advanced Rendering", StartFolded = true )]
	public RenderOptions RenderOptions => _particleModelRenderer.RenderOptions;
	
	
	protected override void OnStart()
	{
		Floats = Floats == null ? new Dictionary<string, ParticleFloat>() : Floats;
		Floats2 = Floats2 == null ? new Dictionary<string, Vector2>() : Floats2;
		Floats4 = Floats4 == null ? new Dictionary<string, Vector4>() : Floats4;
		Textures = Textures == null ? new Dictionary<string, Texture>() : Textures;
		Colors = Colors == null ? new Dictionary<String, ParticleGradient>() : Colors;
		DynamicCombos = DynamicCombos == null ? new Dictionary<string, int>() : DynamicCombos;
		
		if( ParticleShaderEnabled ) ReadAttributes();
	}
	
	[Button, Feature("ParticleShader")]
	private void ReadAttributes()
	{
		if ( MaterialOverride == null || !FileSystem.Mounted.FileExists( MaterialOverride.Shader.ResourcePath )) return;
		AttributesParser<ParticleFloat, ParticleGradient> parser = new AttributesParser<ParticleFloat, ParticleGradient>(new ParticleAttributeTypeSet());
		parser.Floats = Floats;
		parser.Floats2 = Floats2;
		parser.Floats4 = Floats4;
		parser.Textures = Textures;
		parser.DynamicCombos = DynamicCombos;
		parser.Colors = Colors;
		parser.ParseAttributes( MaterialOverride.Shader.ResourcePath );
		
	}

	public sealed class ModelEntry
	{
		private Model _model;

		[KeyProperty]
		public Model Model
		{
			get => _model;
			set
			{
				if ( _model == value )
					return;

				_model = value;

				MaterialGroup = default;
				BodyGroups = _model?.DefaultBodyGroupMask ?? default;
			}
		}

		[Model.MaterialGroup, ShowIf( nameof(HasMaterialGroups), true )]
		public string MaterialGroup { get; set; }

		[Model.BodyGroupMask, ShowIf( nameof(HasBodyGroups), true )]
		public ulong BodyGroups { get; set; }

		[Hide, JsonIgnore] public bool HasMaterialGroups => Model?.MaterialGroupCount > 0;

		[Hide, JsonIgnore] public bool HasBodyGroups => Model?.BodyParts.Sum( x => x.Choices.Count ) > 1;

		public static implicit operator ModelEntry( Model model ) => new() { Model = model };
	}

	[Hide, Obsolete( "Use Choices" )] public List<Model> Models { get; set; } = new();

	[Property] public List<ModelEntry> Choices { get; set; } = new List<ModelEntry> { Model.Cube };

	[Property] public Material MaterialOverride { get; set; }

	[Property, Feature( "ScaleXYZ" )] public ParticleFloat ScaleX { get; set; } = 1;
	[Property, Feature( "ScaleXYZ" )] public ParticleFloat ScaleY { get; set; } = 1;
	[Property, Feature( "ScaleXYZ" )] public ParticleFloat ScaleZ { get; set; } = 1;

	[Property, FeatureEnabled( "ScaleXYZ" )]
	public bool ApplyScaleXYZ { get; set; } = true;

	[Property] public float Scale { get; set; } = 1;
	
	[Property] public bool CastShadows { get; set; } = true;

	[Property] public Allignement Allignement { get; set; }

	protected override void OnParticleCreated( Particle p )
	{
		var particleModel = new CustomParticleModel( this );
		p.AddListener( particleModel, this );
		
	}

	public override int ComponentVersion => 1;

	[JsonUpgrader( typeof(ParticleModelRenderer), 1 )]
	static void Upgrader_v1( JsonObject obj )
	{
		if ( obj.TryGetPropertyValue( "Models", out var node ) )
		{
			var choices = new JsonArray();

			foreach ( var model in node.AsArray() )
			{
				if ( model is null )
					continue;

				choices.Add( new JsonObject { ["Model"] = model.ToString() } );
			}

			obj["Choices"] = choices;
			obj.Remove( "Models" );
		}
	}
	
	
}

public enum Allignement
{
	SimulationSpace,
	FaceCamera,
	FaceVelocity,
	
}





public class CustomParticleModel : Particle.BaseListener
{
	public ShaderParticleModelRenderer Renderer;

	public SceneObject so;

	private ParticleAttributesSetter _particleAttributesSetter;

	public CustomParticleModel( ShaderParticleModelRenderer renderer )
	{
		Renderer = renderer;
	}

	public override void OnEnabled( Particle p )
	{
		var entry = Random.Shared.FromList( Renderer.Choices );
		var model = entry?.Model;
		so = new SceneObject( Renderer.Scene.SceneWorld, model ?? Model.Cube );
		so.Batchable = false;
		if ( model is not null )
		{
			so.MeshGroupMask = entry.BodyGroups;
			so.SetMaterialGroup( entry.MaterialGroup );
		}

		if ( !Renderer.ParticleShaderEnabled ) return;
		_particleAttributesSetter = new ParticleAttributesSetter( so.Attributes, p );
		SetRenderAttributes();
	}

	public override void OnDisabled( Particle p )
	{
		if ( !so.IsValid() ) return;
		so.Delete();
	}

	public override void OnUpdate( Particle p, float dt )
	{
		if ( !so.IsValid() ) return;


		var angles = ComputeRotation( p );
		
		var scale = p.Size * Renderer.WorldScale;
		if ( Renderer.ApplyScaleXYZ )
		{
			scale *= EvaluateScale( p );
		}

		so.Transform = new Transform( p.Position, angles, scale * Renderer.Scale );
		so.ColorTint = p.Color.WithAlphaMultiplied( p.Alpha );
		so.Flags.CastShadows = Renderer.CastShadows;
		so.SetMaterialOverride( Renderer.MaterialOverride );
		
		if(Renderer.ParticleShaderEnabled) _particleAttributesSetter.SetAttributes();
		

		if ( Renderer.RenderOptions != null )
		{
			Renderer.RenderOptions.Apply( so );
		}
	}

	


	private Vector3 EvaluateScale( Particle p )
	{
		
		var scaleX = Renderer.ScaleX.Evaluate( p, 6211 );
		var scaleY = Renderer.ScaleY.Evaluate( p, 6211 );
		var scaleZ = Renderer.ScaleZ.Evaluate( p, 6211 );
		return new Vector3( scaleX, scaleY, scaleZ );
	}


	private Angles ComputeRotation(Particle p)
	{
		var angles = new Rotation();
		switch ( Renderer.Allignement )
		{
			case Allignement.FaceCamera :
				if ( Renderer.Scene.Camera == null ) break;
				var dir = Renderer.Scene.Camera.WorldPosition - p.Position;
				angles = Rotation.LookAt( dir, Vector3.Up ) * p.Angles.ToRotation();
				break;
			case Allignement.FaceVelocity :
				angles = Rotation.LookAt( p.Velocity.Normal, Vector3.Up ) * p.Angles.ToRotation();
				break;
			case Allignement.SimulationSpace :
				angles = Renderer.ParticleEffect.LocalSpace.Evaluate( p,65373 ) <= 1 ? Renderer.WorldRotation.Angles() : Rotation.Identity.Angles(); 
				angles *= p.Angles;
				break;
		}
		return angles;
	}
	private void SetRenderAttributes()
	{
		_particleAttributesSetter.Floats = Renderer.Floats;
		_particleAttributesSetter.Floats2 = Renderer.Floats2;
		_particleAttributesSetter.Floats4 = Renderer.Floats4;
		_particleAttributesSetter.Textures = Renderer.Textures;
		_particleAttributesSetter.Colors = Renderer.Colors;
		_particleAttributesSetter.DynamicCombos = Renderer.DynamicCombos;
		_particleAttributesSetter.SetAttributes();
	}
}
namespace Sandbox;
// The code in this file is ai generated
public class ParticleAttributeTypeSet : IAttributeTypeSet<ParticleFloat, ParticleGradient>
{
	public ParticleFloat GetDefaultFloat() => new ParticleFloat();
	public ParticleGradient GetDefaultColor() => new ParticleGradient();
}
using System;
using System.Collections.Generic;
using Sandbox;
namespace Sandbox;
[Title("ModelShaderAttributes")]
[Category("Shaders")]
[Description("Let's you set the render attributes of the scene object of a ModelRenderer")]
public sealed class ModelShaderAttributes : Component, Component.ExecuteInEditor
{
	[Property] private ModelRenderer ModelRenderer { get; set; }
	[Property, Group( "Floats1" )] public Dictionary<String, float> Floats { get; set; } 
	[Property, Group("Floats2")] public Dictionary<String, Vector2> Floats2 { get; set; }
	[Property, Group("Colors")] public Dictionary<String, Color> Colors {get; set;}
	[Property, Group("Textures")] public Dictionary<String, Texture> Textures { get; set; }
	
	[Property, Group("Floats4")] public Dictionary<String, Vector4> Floats4 { get; set; }
	
	[Property, Group("DynamicCombos")] public Dictionary<String, int> DynamicCombos { get; set; }
	
	[Property] public bool Batchable { get; set; } = true;
	
	
	protected override void OnStart()
	{
		ModelRenderer.SceneObject.Batchable = Batchable;
		Floats = Floats == null ? new Dictionary<string, float>() : Floats;
		Floats2 = Floats2 == null ? new Dictionary<string, Vector2>() : Floats2;
		Floats4 = Floats4 == null ? new Dictionary<string, Vector4>() : Floats4;
		Textures = Textures == null ? new Dictionary<string, Texture>() : Textures;
		Colors = Colors == null ? new Dictionary<String, Color>() : Colors;
		DynamicCombos = DynamicCombos == null ? new Dictionary<string, int>() : DynamicCombos;
		
	}
	[Button]
	private void ReadAttributes()
	{
		if ( ModelRenderer.MaterialOverride == null || !FileSystem.Mounted.FileExists( ModelRenderer.MaterialOverride.Shader.ResourcePath )) return;
		AttributesParser<float,Color> parser = new AttributesParser<float,Color>(new NativeAttributeTypeSet());
		parser.Floats = Floats;
		parser.Floats2 = Floats2;
		parser.Floats4 = Floats4;
		parser.Textures = Textures;
		parser.DynamicCombos = DynamicCombos;
		parser.Colors = Colors;
		parser.ParseAttributes( ModelRenderer.MaterialOverride.Shader.ResourcePath );
		
	}
	protected override void OnUpdate()
	{
		SetColorAttributes();
		SetFloatAttributes();
		SetFloat2Attributes();
		SetTexturesAttributes();
		SetFloat4Attributes();
		SetDynamicCombos();
	}

	private void SetDynamicCombos()
	{
		foreach ( var dynamicCombo in DynamicCombos )
		{
			ModelRenderer.SceneObject.Attributes.SetCombo( dynamicCombo.Key, dynamicCombo.Value );
		}
	}

	private void SetFloat4Attributes()
	{
		foreach ( var float4Attribute in Floats4 )
		{
			ModelRenderer.SceneObject.Attributes.Set( float4Attribute.Key, float4Attribute.Value );
		}
	}

	private void SetTexturesAttributes()
	{
		foreach ( var textureAttribute in Textures )
		{
			ModelRenderer.SceneObject.Attributes.Set( textureAttribute.Key, textureAttribute.Value );
		}
	}


	private void SetColorAttributes()
	{
		foreach ( var colorAttribute in Colors )
		{
			ModelRenderer.SceneObject.Attributes.Set( colorAttribute.Key, colorAttribute.Value );
		}
	}

	private void SetFloatAttributes()
	{
		foreach ( var float1 in Floats )
		{
			ModelRenderer.SceneObject.Attributes.Set( float1.Key, float1.Value );
		}
	}

	private void SetFloat2Attributes()
	{
		foreach ( var float2 in Floats2 )
		{
			ModelRenderer.SceneObject.Attributes.Set( float2.Key, float2.Value );
		}
	}
}


public sealed class MVCitizenAnimation : Component, Component.ExecuteInEditor
{
	[Property] public SkinnedModelRenderer Target { get; set; }

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

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

	[Property, Range( 0.5f, 1.5f )] public float Height { get; set; } = 1.0f;


	[Property] public GameObject IkLeftHand { get; set; }
	[Property] public GameObject IkRightHand { get; set; }
	[Property] public GameObject IkLeftFoot { get; set; }
	[Property] public GameObject IkRightFoot { get; set; }

	[Property] public HoldTypes CurrentHoldType { get; set; }

	public float SkidAmount { get; set; }

	[Property, Range( 0, 10 )] public int FacesOverride { get; set; }

	protected override void OnUpdate()
	{
		if ( LookAtObject.IsValid() )
		{
			var eyePos = GetEyeWorldTransform.Position;

			var dir = (LookAtObject.WorldPosition - eyePos).Normal;
			WithLook( dir, 1, 0.5f, 0.1f );
		}

		Target.Set( "scale_height", Height );

		// SetIk( "left_hand", ... );
		// SetIk( "right_hand", ... );

		if ( IkLeftHand.IsValid() && IkLeftHand.Active ) SetIk( "hand_left", IkLeftHand.Transform.World );
		else ClearIk( "hand_left" );

		if ( IkRightHand.IsValid() && IkRightHand.Active ) SetIk( "hand_right", IkRightHand.Transform.World );
		else ClearIk( "hand_right" );

		if ( IkLeftFoot.IsValid() && IkLeftFoot.Active ) SetIk( "foot_left", IkLeftFoot.Transform.World );
		else ClearIk( "foot_left" );

		if ( IkRightFoot.IsValid() && IkRightFoot.Active ) SetIk( "foot_right", IkRightFoot.Transform.World );
		else ClearIk( "foot_right" );

		HoldType = CurrentHoldType;

		FaceOverride = FacesOverride;
	}

	public void SetIk( string name, Transform tx )
	{
		// convert local to model
		tx = Target.Transform.World.ToLocal( tx );

		Target.Set( $"ik.{name}.enabled", true );
		Target.Set( $"ik.{name}.position", tx.Position );
		Target.Set( $"ik.{name}.rotation", tx.Rotation );
	}

	public void ClearIk( string name )
	{
		Target.Set( $"ik.{name}.enabled", false );
	}

	public Transform GetEyeWorldTransform
	{
		get
		{
			if ( EyeSource.IsValid() ) return EyeSource.Transform.World;

			return Transform.World;
		}
	}


	/// <summary>
	/// Have the player look at this point in the world
	/// </summary>
	public void WithLook( Vector3 lookDirection, float eyesWeight = 1.0f, float headWeight = 1.0f, float bodyWeight = 1.0f )
	{
		Target.SetLookDirection( "aim_eyes", lookDirection );
		Target.SetLookDirection( "aim_head", lookDirection );
		Target.SetLookDirection( "aim_body", lookDirection );

		AimEyesWeight = eyesWeight;
		AimHeadWeight = headWeight;
		AimBodyWeight = bodyWeight;
	}

	public void WithVelocity( Vector3 Velocity )
	{
		var dir = Velocity;
		var forward = Target.WorldRotation.Forward.Dot( dir );
		var sideward = Target.WorldRotation.Right.Dot( dir );

		var angle = MathF.Atan2( sideward, forward ).RadianToDegree().NormalizeDegrees();

		Target.Set( "move_direction", angle );
		Target.Set( "move_speed", Velocity.Length );
		Target.Set( "move_groundspeed", Velocity.WithZ( 0 ).Length );
		Target.Set( "move_y", sideward );
		Target.Set( "move_x", forward );
		Target.Set( "move_z", Velocity.z );
	}

	public void WithWishVelocity( Vector3 Velocity )
	{
		var dir = Velocity;
		var forward = Target.WorldRotation.Forward.Dot( dir );
		var sideward = Target.WorldRotation.Right.Dot( dir );

		var angle = MathF.Atan2( sideward, forward ).RadianToDegree().NormalizeDegrees();

		Target.Set( "wish_direction", angle );
		Target.Set( "wish_speed", Velocity.Length );
		Target.Set( "wish_groundspeed", Velocity.WithZ( 0 ).Length );
		Target.Set( "wish_y", sideward );
		Target.Set( "wish_x", forward );
		Target.Set( "wish_z", Velocity.z );
	}

	public float CheckForGroundAngle()
	{
		var trace = Scene.Trace.Ray( Target.WorldPosition + Vector3.Up * 2, Target.WorldPosition + Vector3.Down * 6 )
			.WithoutTags( "player", "collider" )
			.Radius( 8 )
			.Run();

		//	Gizmo.Draw.Color = Color.Red;
		//	Gizmo.Draw.Line( Target.WorldPosition + Vector3.Up * 2, Target.WorldPosition + Vector3.Down * 6 );

		if ( !trace.Hit )
			return 0;

		return trace.Normal.Angle( Vector3.Up );
	}

	public void SpecialMenu(bool menu)
	{
		var useidle = menu ? 1 : 0;
		Target.Set( "special_idle_states", useidle );
	}

	public Rotation AimAngle
	{
		set
		{
			value = Target.WorldRotation.Inverse * value;
			var ang = value.Angles();

			Target.Set( "aim_body_pitch", ang.pitch );
			Target.Set( "aim_body_yaw", ang.yaw );
		}
	}

	public float AimEyesWeight
	{
		get => Target.GetFloat( "aim_eyes_weight" );
		set => Target.Set( "aim_eyes_weight", value );
	}

	public float AimHeadWeight
	{
		get => Target.GetFloat( "aim_head_weight" );
		set => Target.Set( "aim_head_weight", value );
	}

	public float AimBodyWeight
	{
		get => Target.GetFloat( "aim_body_weight" );
		set => Target.Set( "aim_body_weight", value );
	}


	public float FootShuffle
	{
		get => Target.GetFloat( "move_shuffle" );
		set => Target.Set( "move_shuffle", value );
	}

	public float DuckLevel
	{
		get => Target.GetFloat( "duck" );
		set => Target.Set( "duck", value );
	}

	public float SkidLevel
	{
		get => Target.GetFloat( "skid" );
		set => Target.Set( "skid", value );
	}

	public float VoiceLevel
	{
		get => Target.GetFloat( "voice" );
		set => Target.Set( "voice", value );
	}

	public bool IsSitting
	{
		get => Target.GetBool( "b_sit" );
		set => Target.Set( "b_sit", value );
	}

	public bool IsGrounded
	{
		get => Target.GetBool( "b_grounded" );
		set => Target.Set( "b_grounded", value );
	}

	public bool IsSwimming
	{
		get => Target.GetBool( "b_swim" );
		set => Target.Set( "b_swim", value );
	}

	public bool IsClimbing
	{
		get => Target.GetBool( "b_climbing" );
		set => Target.Set( "b_climbing", value );
	}

	public bool IsNoclipping
	{
		get => Target.GetBool( "b_noclip" );
		set => Target.Set( "b_noclip", value );
	}

	public bool IsWeaponLowered
	{
		get => Target.GetBool( "b_weapon_lower" );
		set => Target.Set( "b_weapon_lower", value );
	}

	public enum HoldTypes
	{
		None,
		Pistol,
		Rifle,
		Shotgun,
		HoldItem,
		Punch,
		Swing,
		RPG
	}

	public HoldTypes HoldType
	{
		get => (HoldTypes)Target.GetInt( "holdtype" );
		set => Target.Set( "holdtype", (int)value );
	}

	public enum Hand
	{
		Both,
		Right,
		Left
	}

	public Hand Handedness
	{
		get => (Hand)Target.GetInt( "holdtype_handedness" );
		set => Target.Set( "holdtype_handedness", (int)value );
	}

	public void TriggerJump()
	{
		Target.Set( "b_jump", true );
	}

	public void TriggerDeploy()
	{
		Target.Set( "b_deploy", true );
	}

	public enum MoveStyles
	{
		Auto,
		Walk,
		Run
	}

	/// <summary>
	/// We can force the model to walk or run, or let it decide based on the speed.
	/// </summary>
	public MoveStyles MoveStyle
	{
		get => (MoveStyles)Target.GetInt( "move_style" );
		set => Target.Set( "move_style", (int)value );
	}

	public enum SpecialMoveStyle
	{
		None,
		LedgeGrab,
		Roll,
		Slide
	}

	public SpecialMoveStyle SpecialMove
	{
		get => (SpecialMoveStyle)Target.GetInt( "special_movement_states" );
		set => Target.Set( "special_movement_states", (int)value );
	}

	public int FaceOverride
	{
		get => Target.GetInt( "face_override" );
		set => Target.Set( "face_override", value );
	}
}
using Sandbox;
using Editor;
using static Sandbox.ClothingContainer;

[Title( "Clothing Dresser" )]
[Category( "Clothing" )]
[Icon( "checkroom", "blue", "white" )]
public sealed class ModelViewerClothingDresser : Component
{
	[Property] SkinnedModelRenderer Source { get; set; }
	[Property] List<Clothing> ClothingList { get ; set; } = new();
	ClothingContainer Container { get; set; } = new ClothingContainer();
	public List<SceneModel> Dressed { get; private set; }

	//Hair Tint
	[Property] Gradient HairTintGradient { get; set; } = new Gradient( new Gradient.ColorFrame( 0.0f, Color.White ), new Gradient.ColorFrame( 0.16f, "#FCC88C" ), new Gradient.ColorFrame(0.34f, "#A57E6A" ), new Gradient.ColorFrame( 0.53f, "#A33900" ), new Gradient.ColorFrame( 0.75f, "#3A271D" ), new Gradient.ColorFrame( 1.0f, "#000000" ) );
	[Property] Color HairTint { get; set; }
	[Property, Range(0,1)] float HairTintValue { get; set; } = 0.4f;

	//Beard Tint
	[Property] Gradient BeardTintGradient { get; set; } = new Gradient( new Gradient.ColorFrame( 0.0f, Color.White ), new Gradient.ColorFrame( 0.16f, "#FCC88C" ), new Gradient.ColorFrame( 0.34f, "#A57E6A" ), new Gradient.ColorFrame( 0.53f, "#A33900" ), new Gradient.ColorFrame( 0.75f, "#3A271D" ), new Gradient.ColorFrame( 1.0f, "#000000" ) );
	[Property] Color BeardTint { get; set; } = Color.White;
	[Property, Range(0,1)] float BeardTintValue { get; set; } = 0.4f;

	protected override void OnStart()
	{
		if ( Source is null )
			return;

		if ( ClothingList is null )
			return;
	
		foreach ( var clothing in ClothingList )
		{
			if ( clothing is null )
				continue;
			var entry = new ClothingEntry( clothing );
			if ( Container.Clothing.Contains( entry ) )
				continue;
			
			Container.Clothing.Add( entry );
		}

		Container.Apply( Source );

		//Find the hair model
		foreach ( var model in GameObject.Children )
		{			
			var mod = model.Components.Get<SkinnedModelRenderer>();

			if( mod is null )
				continue;
			
			if ( model.Name.Contains( "hair" ) || mod.Model.ResourcePath.Contains("hair") && mod.Model.MorphCount <= 1 )
			{
				var hair = model.Components.Get<SkinnedModelRenderer>();
				hair.Tint = HairTint;
			}

			if ( mod.Model.MorphCount >= 1 )
			{
				var beard = model.Components.Get<SkinnedModelRenderer>();
				beard.Tint = BeardTint;
				Log.Info( "Beard Tint: " );
			}
		}
	}
}
using Sandbox;
using Editor;
using static Sandbox.ClothingContainer;

[Title( "Clothing Dresser" )]
[Category( "Clothing" )]
[Icon( "checkroom", "blue", "white" )]
public sealed class ModelViewerClothingDresser : Component
{
	[Property] SkinnedModelRenderer Source { get; set; }
	[Property] List<Clothing> ClothingList { get ; set; } = new();
	ClothingContainer Container { get; set; } = new ClothingContainer();
	public List<SceneModel> Dressed { get; private set; }

	//Hair Tint
	[Property] Gradient HairTintGradient { get; set; } = new Gradient( new Gradient.ColorFrame( 0.0f, Color.White ), new Gradient.ColorFrame( 0.16f, "#FCC88C" ), new Gradient.ColorFrame(0.34f, "#A57E6A" ), new Gradient.ColorFrame( 0.53f, "#A33900" ), new Gradient.ColorFrame( 0.75f, "#3A271D" ), new Gradient.ColorFrame( 1.0f, "#000000" ) );
	[Property] Color HairTint { get; set; }
	[Property, Range(0,1)] float HairTintValue { get; set; } = 0.4f;

	//Beard Tint
	[Property] Gradient BeardTintGradient { get; set; } = new Gradient( new Gradient.ColorFrame( 0.0f, Color.White ), new Gradient.ColorFrame( 0.16f, "#FCC88C" ), new Gradient.ColorFrame( 0.34f, "#A57E6A" ), new Gradient.ColorFrame( 0.53f, "#A33900" ), new Gradient.ColorFrame( 0.75f, "#3A271D" ), new Gradient.ColorFrame( 1.0f, "#000000" ) );
	[Property] Color BeardTint { get; set; } = Color.White;
	[Property, Range(0,1)] float BeardTintValue { get; set; } = 0.4f;

	protected override void OnStart()
	{
		if ( Source is null )
			return;

		if ( ClothingList is null )
			return;
	
		foreach ( var clothing in ClothingList )
		{
			if ( clothing is null )
				continue;
			var entry = new ClothingEntry( clothing );
			if ( Container.Clothing.Contains( entry ) )
				continue;
			
			Container.Clothing.Add( entry );
		}

		Container.Apply( Source );

		//Find the hair model
		foreach ( var model in GameObject.Children )
		{			
			var mod = model.Components.Get<SkinnedModelRenderer>();

			if( mod is null )
				continue;
			
			if ( model.Name.Contains( "hair" ) || mod.Model.ResourcePath.Contains("hair") && mod.Model.MorphCount <= 1 )
			{
				var hair = model.Components.Get<SkinnedModelRenderer>();
				hair.Tint = HairTint;
			}

			if ( mod.Model.MorphCount >= 1 )
			{
				var beard = model.Components.Get<SkinnedModelRenderer>();
				beard.Tint = BeardTint;
				Log.Info( "Beard Tint: " );
			}
		}
	}
}


public sealed class MVCitizenAnimation : Component, Component.ExecuteInEditor
{
	[Property] public SkinnedModelRenderer Target { get; set; }

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

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

	[Property, Range( 0.5f, 1.5f )] public float Height { get; set; } = 1.0f;


	[Property] public GameObject IkLeftHand { get; set; }
	[Property] public GameObject IkRightHand { get; set; }
	[Property] public GameObject IkLeftFoot { get; set; }
	[Property] public GameObject IkRightFoot { get; set; }

	[Property] public HoldTypes CurrentHoldType { get; set; }

	public float SkidAmount { get; set; }

	[Property, Range( 0, 10 )] public int FacesOverride { get; set; }

	protected override void OnUpdate()
	{
		if ( LookAtObject.IsValid() )
		{
			var eyePos = GetEyeWorldTransform.Position;

			var dir = (LookAtObject.WorldPosition - eyePos).Normal;
			WithLook( dir, 1, 0.5f, 0.1f );
		}

		Target.Set( "scale_height", Height );

		// SetIk( "left_hand", ... );
		// SetIk( "right_hand", ... );

		if ( IkLeftHand.IsValid() && IkLeftHand.Active ) SetIk( "hand_left", IkLeftHand.Transform.World );
		else ClearIk( "hand_left" );

		if ( IkRightHand.IsValid() && IkRightHand.Active ) SetIk( "hand_right", IkRightHand.Transform.World );
		else ClearIk( "hand_right" );

		if ( IkLeftFoot.IsValid() && IkLeftFoot.Active ) SetIk( "foot_left", IkLeftFoot.Transform.World );
		else ClearIk( "foot_left" );

		if ( IkRightFoot.IsValid() && IkRightFoot.Active ) SetIk( "foot_right", IkRightFoot.Transform.World );
		else ClearIk( "foot_right" );

		HoldType = CurrentHoldType;

		FaceOverride = FacesOverride;
	}

	public void SetIk( string name, Transform tx )
	{
		// convert local to model
		tx = Target.Transform.World.ToLocal( tx );

		Target.Set( $"ik.{name}.enabled", true );
		Target.Set( $"ik.{name}.position", tx.Position );
		Target.Set( $"ik.{name}.rotation", tx.Rotation );
	}

	public void ClearIk( string name )
	{
		Target.Set( $"ik.{name}.enabled", false );
	}

	public Transform GetEyeWorldTransform
	{
		get
		{
			if ( EyeSource.IsValid() ) return EyeSource.Transform.World;

			return Transform.World;
		}
	}


	/// <summary>
	/// Have the player look at this point in the world
	/// </summary>
	public void WithLook( Vector3 lookDirection, float eyesWeight = 1.0f, float headWeight = 1.0f, float bodyWeight = 1.0f )
	{
		Target.SetLookDirection( "aim_eyes", lookDirection );
		Target.SetLookDirection( "aim_head", lookDirection );
		Target.SetLookDirection( "aim_body", lookDirection );

		AimEyesWeight = eyesWeight;
		AimHeadWeight = headWeight;
		AimBodyWeight = bodyWeight;
	}

	public void WithVelocity( Vector3 Velocity )
	{
		var dir = Velocity;
		var forward = Target.WorldRotation.Forward.Dot( dir );
		var sideward = Target.WorldRotation.Right.Dot( dir );

		var angle = MathF.Atan2( sideward, forward ).RadianToDegree().NormalizeDegrees();

		Target.Set( "move_direction", angle );
		Target.Set( "move_speed", Velocity.Length );
		Target.Set( "move_groundspeed", Velocity.WithZ( 0 ).Length );
		Target.Set( "move_y", sideward );
		Target.Set( "move_x", forward );
		Target.Set( "move_z", Velocity.z );
	}

	public void WithWishVelocity( Vector3 Velocity )
	{
		var dir = Velocity;
		var forward = Target.WorldRotation.Forward.Dot( dir );
		var sideward = Target.WorldRotation.Right.Dot( dir );

		var angle = MathF.Atan2( sideward, forward ).RadianToDegree().NormalizeDegrees();

		Target.Set( "wish_direction", angle );
		Target.Set( "wish_speed", Velocity.Length );
		Target.Set( "wish_groundspeed", Velocity.WithZ( 0 ).Length );
		Target.Set( "wish_y", sideward );
		Target.Set( "wish_x", forward );
		Target.Set( "wish_z", Velocity.z );
	}

	public float CheckForGroundAngle()
	{
		var trace = Scene.Trace.Ray( Target.WorldPosition + Vector3.Up * 2, Target.WorldPosition + Vector3.Down * 6 )
			.WithoutTags( "player", "collider" )
			.Radius( 8 )
			.Run();

		//	Gizmo.Draw.Color = Color.Red;
		//	Gizmo.Draw.Line( Target.WorldPosition + Vector3.Up * 2, Target.WorldPosition + Vector3.Down * 6 );

		if ( !trace.Hit )
			return 0;

		return trace.Normal.Angle( Vector3.Up );
	}

	public void SpecialMenu(bool menu)
	{
		var useidle = menu ? 1 : 0;
		Target.Set( "special_idle_states", useidle );
	}

	public Rotation AimAngle
	{
		set
		{
			value = Target.WorldRotation.Inverse * value;
			var ang = value.Angles();

			Target.Set( "aim_body_pitch", ang.pitch );
			Target.Set( "aim_body_yaw", ang.yaw );
		}
	}

	public float AimEyesWeight
	{
		get => Target.GetFloat( "aim_eyes_weight" );
		set => Target.Set( "aim_eyes_weight", value );
	}

	public float AimHeadWeight
	{
		get => Target.GetFloat( "aim_head_weight" );
		set => Target.Set( "aim_head_weight", value );
	}

	public float AimBodyWeight
	{
		get => Target.GetFloat( "aim_body_weight" );
		set => Target.Set( "aim_body_weight", value );
	}


	public float FootShuffle
	{
		get => Target.GetFloat( "move_shuffle" );
		set => Target.Set( "move_shuffle", value );
	}

	public float DuckLevel
	{
		get => Target.GetFloat( "duck" );
		set => Target.Set( "duck", value );
	}

	public float SkidLevel
	{
		get => Target.GetFloat( "skid" );
		set => Target.Set( "skid", value );
	}

	public float VoiceLevel
	{
		get => Target.GetFloat( "voice" );
		set => Target.Set( "voice", value );
	}

	public bool IsSitting
	{
		get => Target.GetBool( "b_sit" );
		set => Target.Set( "b_sit", value );
	}

	public bool IsGrounded
	{
		get => Target.GetBool( "b_grounded" );
		set => Target.Set( "b_grounded", value );
	}

	public bool IsSwimming
	{
		get => Target.GetBool( "b_swim" );
		set => Target.Set( "b_swim", value );
	}

	public bool IsClimbing
	{
		get => Target.GetBool( "b_climbing" );
		set => Target.Set( "b_climbing", value );
	}

	public bool IsNoclipping
	{
		get => Target.GetBool( "b_noclip" );
		set => Target.Set( "b_noclip", value );
	}

	public bool IsWeaponLowered
	{
		get => Target.GetBool( "b_weapon_lower" );
		set => Target.Set( "b_weapon_lower", value );
	}

	public enum HoldTypes
	{
		None,
		Pistol,
		Rifle,
		Shotgun,
		HoldItem,
		Punch,
		Swing,
		RPG
	}

	public HoldTypes HoldType
	{
		get => (HoldTypes)Target.GetInt( "holdtype" );
		set => Target.Set( "holdtype", (int)value );
	}

	public enum Hand
	{
		Both,
		Right,
		Left
	}

	public Hand Handedness
	{
		get => (Hand)Target.GetInt( "holdtype_handedness" );
		set => Target.Set( "holdtype_handedness", (int)value );
	}

	public void TriggerJump()
	{
		Target.Set( "b_jump", true );
	}

	public void TriggerDeploy()
	{
		Target.Set( "b_deploy", true );
	}

	public enum MoveStyles
	{
		Auto,
		Walk,
		Run
	}

	/// <summary>
	/// We can force the model to walk or run, or let it decide based on the speed.
	/// </summary>
	public MoveStyles MoveStyle
	{
		get => (MoveStyles)Target.GetInt( "move_style" );
		set => Target.Set( "move_style", (int)value );
	}

	public enum SpecialMoveStyle
	{
		None,
		LedgeGrab,
		Roll,
		Slide
	}

	public SpecialMoveStyle SpecialMove
	{
		get => (SpecialMoveStyle)Target.GetInt( "special_movement_states" );
		set => Target.Set( "special_movement_states", (int)value );
	}

	public int FaceOverride
	{
		get => Target.GetInt( "face_override" );
		set => Target.Set( "face_override", value );
	}
}
using Sandbox;
using System.Collections.Generic;
using static Sandbox.ClothingContainer;

[Icon( "checkroom", "blue", "white" )]
[EditorHandle( "editor/citizenhead.png" )]
public sealed class ClothingFileDresser : Component
{
	// New struct to hold clothing and source together
	public struct ClothingSet
	{
		public List<Clothing> Clothes { get; set; }
		public SkinnedModelRenderer Source { get; set; }
		public bool IsHuman { get; set; }
	}

	[Property, InlineEditor] List<ClothingSet> Sets { get; set; } = new();

	[Button( "Dress" )]
	void DressCitizen()
	{
		foreach ( var set in Sets )
		{
			if ( set.Source == null || !set.Source.IsValid() )
				continue;

			var container = new ClothingContainer();
			container.PrefersHuman = set.IsHuman;
			container.Reset( set.Source );
			container.Clothing.Clear();

			foreach ( var clothing in set.Clothes )
			{
				if ( clothing is null )
					continue;

				var entry = new ClothingEntry( clothing );
				if ( container.Clothing.Contains( entry ) )
					continue;

				container.Clothing.Add( entry );
			}

			container.Normalize();
			container.Apply( set.Source );
		}
	}

	[Button( "UnDress" )]
	void UnDressCitizen()
	{
		foreach ( var set in Sets )
		{
			if ( set.Source == null || !set.Source.IsValid() )
				continue;

			var container = new ClothingContainer();
			container.Reset( set.Source );
			container.Clothing.Clear();
		}
	}
}
namespace Sandbox;

public readonly struct MeshSliceRegion
{
	public readonly float StartX { get; }
	public readonly float StartY { get; }
	public readonly float EndX { get; }
	public readonly float EndY { get; }

	public static MeshSliceRegion Full { get; } = new( 0.0f, 0.0f, 1.0f, 1.0f );

	public MeshSliceRegion( float startX, float startY, float endX, float endY )
	{
		StartX = startX;
		StartY = startY;
		EndX = endX;
		EndY = endY;
	}

	public Vector2 Map( Vector2 uv )
	{
		return Map( uv.x, uv.y );
	}

	public Vector2 Map( float x, float y )
	{
		return new Vector2(
			MapRange( x, 0.0f, 1.0f, StartX, EndX ),
			MapRange( y, 0.0f, 1.0f, StartY, EndY ) );
	}

	private static float MapRange( float value, float sourceMin, float sourceMax, float targetMin, float targetMax )
	{
		return (value - sourceMin) * (targetMax - targetMin) / (sourceMax - sourceMin) + targetMin;
	}
}
namespace Sandbox;

public sealed class MeshSliceResult
{
	public Model UpperModel { get; }
	public Model LowerModel { get; }

	internal MeshSliceResult( Model upperModel, Model lowerModel )
	{
		UpperModel = upperModel;
		LowerModel = lowerModel;
	}

	public GameObject CreateUpperHull( GameObject source, string name = "Upper_Hull" )
	{
		return CreateUpperHull( source, name, autoCopyPhysics: false );
	}

	public GameObject CreateUpperHull( GameObject source, string name = "Upper_Hull", bool autoCopyPhysics = false )
	{
		return CreateHullObject( source, UpperModel, name, autoCopyPhysics );
	}

	public GameObject CreateLowerHull( GameObject source, string name = "Lower_Hull" )
	{
		return CreateLowerHull( source, name, autoCopyPhysics: false );
	}

	public GameObject CreateLowerHull( GameObject source, string name = "Lower_Hull", bool autoCopyPhysics = false )
	{
		return CreateHullObject( source, LowerModel, name, autoCopyPhysics );
	}

	public GameObject[] CreateHulls( GameObject source, bool disableSource = false, bool autoCopyPhysics = false )
	{
		var upper = CreateUpperHull( source, autoCopyPhysics: autoCopyPhysics );
		var lower = CreateLowerHull( source, autoCopyPhysics: autoCopyPhysics );

		if ( disableSource && source is not null )
		{
			source.Enabled = false;
		}

		if ( upper is not null && lower is not null )
			return new[] { upper, lower };

		if ( upper is not null )
			return new[] { upper };

		if ( lower is not null )
			return new[] { lower };

		return null;
	}

	private static GameObject CreateHullObject( GameObject source, Model model, string name, bool autoCopyPhysics )
	{
		if ( source is null || model is null )
			return null;

		var newObject = new GameObject( source.Parent, source.Enabled, name );
		newObject.WorldTransform = source.WorldTransform;
		newObject.Tags.SetFrom( source.Tags );

		var sourceRenderer = source.GetComponent<ModelRenderer>( true );
		var renderer = newObject.GetOrAddComponent<ModelRenderer>();
		renderer.Model = model;

		if ( sourceRenderer is not null )
		{
			renderer.Tint = sourceRenderer.Tint;
			renderer.RenderType = sourceRenderer.RenderType;
		}

		if ( autoCopyPhysics )
		{
			CopyPhysicsFromSource( source, newObject, model );
		}

		return newObject;
	}

	private static void CopyPhysicsFromSource( GameObject source, GameObject destination, Model hullModel )
	{
		var sourceBody = source.GetComponent<Rigidbody>( true );
		if ( sourceBody is not null )
		{
			var destinationBody = destination.AddComponent<Rigidbody>( false );
			CopyRigidbody( sourceBody, destinationBody );
			destinationBody.Enabled = sourceBody.Enabled;
		}

		Collider sourceColliderTemplate = null;
		foreach ( var sourceCollider in source.GetComponents<Collider>( true ) )
		{
			sourceColliderTemplate = sourceCollider;
			break;
		}

		if ( sourceColliderTemplate is not null && hullModel is not null )
		{
			var destinationCollider = destination.AddComponent<ModelCollider>( false );
			CopyCollider( sourceColliderTemplate, destinationCollider );
			destinationCollider.Model = hullModel;
			destinationCollider.Enabled = sourceColliderTemplate.Enabled;
		}
		else if ( sourceBody is not null && hullModel is not null )
		{
			var fallbackCollider = destination.AddComponent<ModelCollider>( false );
			fallbackCollider.Model = hullModel;
			fallbackCollider.Enabled = sourceBody.Enabled;
		}
	}

	private static void CopyCollider( Collider source, Collider destination )
	{
		destination.Static = source.Static;
		destination.IsTrigger = source.IsTrigger;
		destination.Surface = source.Surface;
		destination.SurfaceVelocity = source.SurfaceVelocity;
		destination.Friction = source.Friction;
		destination.Elasticity = source.Elasticity;
		destination.RollingResistance = source.RollingResistance;
		destination.ColliderFlags = source.ColliderFlags;
	}

	private static void CopyRigidbody( Rigidbody source, Rigidbody destination )
	{
		destination.Gravity = source.Gravity;
		destination.GravityScale = source.GravityScale;
		destination.LinearDamping = source.LinearDamping;
		destination.AngularDamping = source.AngularDamping;
		destination.MassOverride = source.MassOverride;
		destination.OverrideMassCenter = source.OverrideMassCenter;
		destination.MassCenterOverride = source.MassCenterOverride;
		destination.Locking = source.Locking;
		destination.StartAsleep = source.StartAsleep;
		destination.RigidbodyFlags = source.RigidbodyFlags;
		destination.EnableImpactDamage = source.EnableImpactDamage;
		destination.MinImpactDamageSpeed = source.MinImpactDamageSpeed;
		destination.ImpactDamage = source.ImpactDamage;
		destination.MotionEnabled = source.MotionEnabled;
		destination.CollisionEventsEnabled = source.CollisionEventsEnabled;
		destination.CollisionUpdateEventsEnabled = source.CollisionUpdateEventsEnabled;
		destination.EnhancedCcd = source.EnhancedCcd;
		destination.Velocity = source.Velocity;
		destination.AngularVelocity = source.AngularVelocity;
	}
}
using Sandbox.Sboku;
using Sandbox.Sboku.Shared;

namespace Sandbox.AI.Default;
internal class ReloadState : StateBase, ICombatState
{
    public ReloadState(SbokuBase bot) : base(bot)
    {
    }
    public override void OnSet()
    {
        Bot.IsReloading = true;
    }

    public override void OnUnset()
    {
    }

    public void OnReloadFinish()
    {
        Bot.SetCombatState<ShootState>();
        Bot.IsReloading = false;
    }
}
using Sandbox.Sboku;
using Sandbox.Sboku.Shared;

namespace Sandbox.AI.Default;
public class SbokuParent
{
    protected SbokuBase Bot { get; }
    protected Scene Scene => Bot.Scene;
    protected SbokuSettings Settings => Bot.Settings;
    protected ISbokuTarget Target => Bot.Target;
    protected ISbokuWeapon Weapon => Bot.Weapon;

    /// <summary>
    /// Get squared distance to target. If turget is null, we'll get NRE.
    /// </summary>
    protected float SquaredDistanceToTarget => Bot.WorldPosition.DistanceSquared(Target.GameObject.WorldPosition);

    protected SbokuParent(SbokuBase bot)
    {
        Bot = bot;
    }
}
using Sandbox.Sboku;

namespace Sandbox.AI.Default;
internal class ShootState : StateBase, ICombatState
{
    private Burst burst;
    public ShootState(SbokuBase bot) : base(bot)
    {
    }

    private record Burst
    {
        public float Period;
        public TimeSince Timer = new TimeSince();
        public Burst(float period)
        {
            Period = period;
            Timer = 0;
        }
        public bool CanFire()
            => Timer < Period;
        public bool ShouldStop()
            => Timer > Period;
        public bool CanContinue()
            => Timer > Period * 2;
    }

    public override void Think()
    {
        if (Weapon.HasAmmo())
        {
            if (burst?.CanFire() ?? true)
            {
                Bot.IsShooting = Scene.Trace.Ray(Bot.EyePos, Target.GameObject.WorldPosition + Bot.HeightToAimAt)
                                            .IgnoreGameObjectHierarchy(Bot.GameObject)
                                            .Run().GameObject?.Parent == Target.GameObject;
            }
            else if (burst != null)
            {
                if (burst.CanContinue())
                    burst = null;
                else if (burst.ShouldStop())
                    Bot.IsShooting = false;
            }
            
            if (Bot.IsShooting && burst == null)
            {
                burst = new(Bot.BurstPeriod);
            }
        }
        else
        {
            OnReload();
        }
    }

    public override void OnUnset()
    {
        Bot.IsShooting = false;
    }

    public void OnReload()
    {
        lock (this)
        {
            Bot.IsShooting = false;
            Bot.SetCombatState<ReloadState>();
        }
    }
}
using Sandbox.Navigation;
using System;
using System.Collections.Generic;

namespace Sandbox.Sboku;
internal static class Extensions
{
    // Sometimes I get `Failed to compare two elements in the array.` for some reason
    public static List<Vector3> GetSimplePathSafe(this NavMesh mesh, Vector3 from, Vector3 to)
    {
        try
        {
            return mesh.GetSimplePath(from, to);
        }
        catch (Exception e)
        {
            var ex = new Exception("NavMesh fail: " + e.Message, e.InnerException);
            Log.Error(ex);
            return new();
        }
    }
}
namespace Sandbox.Shared;
public interface ISbokuCondition
{
    bool If();
    void Then();
    /// <summary>
    /// Should we stop evaluating other conditions if the condition is true
    /// </summary>
    /// <returns></returns>
    bool IsTerminal();
}
using System;
using System.Collections.Generic;
using Sandbox.Sboku;
using Sandbox.Shared;

namespace Sandbox.AI.Default;
public class Conditions
{
    private abstract class SimpleCondition : SbokuParent, ISbokuCondition
    {
        public SimpleCondition(SbokuBase bot) : base(bot)
        {
        }

        public abstract bool If();
        public abstract void Then();

        public bool IsTerminal()
            => false;

    }
    private class StopCondion : SimpleCondition
    {
        public StopCondion(SbokuBase bot) : base(bot)
        {
        }
        public override bool If()
                => !(Bot.IsActiveActionState<IdleActionState>() && Bot.IsActiveCombatState<IdleCombatState>())
                   && (Weapon == null
                   ||  Target == null
                   || !Target.IsValid
                   || !Target.IsAlive
                   || SquaredDistanceToTarget > MathF.Pow(Bot.SearchRange, 2));
        public override void Then()
            => Bot.ResetState();
    }
    private class ChaseCondition : SimpleCondition
    {
        public ChaseCondition(SbokuBase bot) : base(bot)
        {
        }
        public override bool If()
                => Bot.Target != null && SquaredDistanceToTarget > MathF.Pow(Bot.MaxFightRange, 2);
        public override void Then()
            => Bot.SetActionState<ChaseState>();
    }


    public static List<ISbokuCondition> Get(SbokuBase bot) =>
        new List<ISbokuCondition>()
        {
            new StopCondion(bot),
            new ChaseCondition(bot)
        };
}
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text.Json;
using System.Text.Json.Nodes;
using Sandbox;

namespace SpriteTools;

public partial class SpriteResource
{
    public override int ResourceVersion => 1;

    [JsonUpgrader(typeof(SpriteResource), 1)]
    static void Upgrader_v1(JsonObject json)
    {
        if (json.ContainsKey("Looping"))
        {
            var wasLooping = json["Looping"].GetValue<bool>();
            json["LoopMode"] = (int)(wasLooping ? SpriteResource.LoopMode.Forward : SpriteResource.LoopMode.None);
        }
    }
}
using Sandbox;
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json.Serialization;

namespace SpriteTools;

[Category( "2D" )]
[Title( "2D Tileset" )]
[Icon( "calendar_view_month" )]
[Tint( EditorTint.Yellow )]
public partial class TilesetComponent : Component, Component.ExecuteInEditor
{
	/// <summary>
	/// The Layers within the TilesetComponent
	/// </summary>
	[Property, Group( "Layers" )]
	public List<Layer> Layers
	{
		get => _layers;
		set
		{
			_layers = value;
			foreach ( var layer in _layers )
			{
				layer.TilesetComponent = this;
			}
		}
	}
	List<Layer> _layers;

	[Property, WideMode( HasLabel = false )]
	ComponentControls InternalControls { get; set; }

	/// <summary>
	/// Whether or not the component should generate a collider based on the specified Collision Layer
	/// </summary>
	[Property, FeatureEnabled( "Collision" )]
	public bool HasCollider
	{
		get => _hasCollider;
		set
		{
			if ( value == _hasCollider ) return;
			_hasCollider = value;
			if ( value ) CreateCollider();
			else DestroyCollider();
		}
	}
	bool _hasCollider;

	/// <inheritdoc cref="Collider.Static" />
	[Property, Feature( "Collision" )]
	public bool Static
	{
		get => _static;
		set
		{
			if ( value == _static ) return;
			_static = value;
			if ( Collider.IsValid() ) Collider.Static = value;
		}
	}
	private bool _static = true;

	/// <inheritdoc cref="Collider.IsTrigger" />
	[Property, Feature( "Collision" )]
	public bool IsTrigger
	{
		get => _isTrigger;
		set
		{
			if ( value == _isTrigger ) return;
			_isTrigger = value;
			if ( Collider.IsValid() ) Collider.IsTrigger = value;
		}
	}
	private bool _isTrigger = false;

	/// <summary>
	/// The width of the generated collider
	/// </summary>
	[Property, Feature( "Collision" )]
	public float ColliderWidth
	{
		get => _colliderWidth;
		set
		{
			if ( value < 0f ) _colliderWidth = 0f;
			else if ( value == _colliderWidth ) return;
			_colliderWidth = value;
			Collider?.RebuildMesh();
		}
	}
	float _colliderWidth;

	/// <inheritdoc cref="Collider.Friction" />
	[Property, Feature( "Collision" ), Group( "Surface Properties" )]
	[Range( 0f, 1f, true, true ), Step( 0.01f )]
	public float? Friction
	{
		get => _friction;
		set
		{
			if ( value == _friction ) return;
			_friction = value;
			if ( Collider.IsValid() ) Collider.Friction = value;
		}
	}
	private float? _friction;

	/// <inheritdoc cref="Collider.Surface" />
	[Property, Feature( "Collision" ), Group( "Surface Properties" )]
	public Surface Surface
	{
		get => _surface;
		set
		{
			if ( value == _surface ) return;
			_surface = value;
			if ( Collider.IsValid() ) Collider.Surface = value;
		}
	}
	private Surface _surface;

	/// <inheritdoc cref="Collider.SurfaceVelocity" />
	[Property, Feature( "Collision" ), Group( "Surface Properties" )]
	public Vector3 SurfaceVelocity
	{
		get => _surfaceVelocity;
		set
		{
			if ( value == _surfaceVelocity ) return;
			_surfaceVelocity = value;
			if ( Collider.IsValid() ) Collider.SurfaceVelocity = value;
		}
	}
	private Vector3 _surfaceVelocity;

	[Property, Feature( "Collision" ), Group( "Trigger Actions" ), ShowIf( nameof( IsTrigger ), true )]
	public Action<Collider> OnTriggerEnter { get; set; }

	[Property, Feature( "Collision" ), Group( "Trigger Actions" ), ShowIf( nameof( IsTrigger ), true )]
	public Action<Collider> OnTriggerExit { get; set; }

	/// <summary>
	/// Whether or not the associated Collider is dirty. Setting this to true will rebuild the Collider on the next frame.
	/// </summary>
	public bool IsDirty
	{
		get => Collider?.IsDirty ?? false;
		set
		{
			if ( !Collider.IsValid() ) return;
			Collider.IsDirty = value;
		}
	}
	TilesetCollider Collider;
	internal List<TilesetSceneObject> _sos = new();

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

		CreateCollider();

		if ( Layers is null ) return;
		foreach ( var layer in Layers )
		{
			layer.TilesetComponent = this;
		}
	}

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

		DestroyCollider();

		foreach ( var _so in _sos )
		{
			_so.Delete();
		}
		_sos.Clear();
	}

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

		_sos ??= new();
		Layers ??= new();
		var _newSos = new List<TilesetSceneObject>();
		foreach ( var sos in _sos )
		{
			if ( sos is not null || sos.IsValid() )
			{
				_newSos.Add( sos );
			}
			else
			{
				sos?.Delete();
			}
		}
		_sos = _newSos;
		if ( Layers.Count != _sos.Count )
		{
			RebuildSceneObjects();
		}
	}

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

		foreach ( var _so in _sos )
			_so?.Tags.SetFrom( Tags );
	}

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

		if ( Layers is null ) return;
		if ( Layers.Count == 0 )
		{
			return;
		}

		foreach ( var _so in _sos )
		{
			if ( !_so.IsValid() ) continue;
			_so.RenderingEnabled = true;
			_so.Transform = Transform.World;
			_so.Flags.CastShadows = false;
			_so.Flags.IsOpaque = false;
			_so.Flags.IsTranslucent = true;
		}
	}

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

		var bounds = GetBounds();
		Gizmo.Hitbox.BBox( bounds );

		if ( !Gizmo.IsSelected ) return;

		using ( Gizmo.Scope( "tileset", new Transform( 0, WorldRotation.Inverse, 1 ) ) )
		{
			Gizmo.Draw.Color = Color.Yellow;
			Gizmo.Draw.LineThickness = 1f;
			Gizmo.Draw.LineBBox( bounds );
		}
	}

	public BBox GetBounds ()
	{
		var bounds = BBox.FromPositionAndSize( 0, 0 );
		foreach ( var _so in _sos )
		{
			if ( !_so.IsValid() ) continue;

			var boundSize = _so.Bounds.Size;
			if ( ( boundSize.x + boundSize.y + boundSize.z ) > ( bounds.Size.x + bounds.Size.y + bounds.Size.z ) )
			{
				bounds = _so.Bounds.Translate( -_so.Position );
			}
		}

		return bounds;
	}

	void RebuildSceneObjects ()
	{
		foreach ( var _so in _sos )
		{
			_so.Delete();
		}

		_sos = new List<TilesetSceneObject>();
		for ( int i = 0; i < Layers.Count; i++ )
		{
			_sos.Add( new TilesetSceneObject( this, Scene.SceneWorld, i ) );
		}
	}

	void CreateCollider ()
	{
		if ( !HasCollider ) return;
		if ( Collider.IsValid() ) return;
		Collider = AddComponent<TilesetCollider>();
		Collider.Flags |= ComponentFlags.Hidden | ComponentFlags.NotSaved;
		Collider.Tileset = this;
		Collider.Static = Static;
		Collider.IsTrigger = IsTrigger;
		Collider.Friction = Friction;
		Collider.Surface = Surface;
		Collider.SurfaceVelocity = SurfaceVelocity;
		Collider.OnTriggerEnter += OnTriggerEnter;
		Collider.OnTriggerExit += OnTriggerExit;
	}

	void DestroyCollider ()
	{
		if ( Collider.IsValid() )
			Collider.Destroy();
		Collider = null;
	}

	/// <summary>
	/// Returns the Layer with the specified name
	/// </summary>
	/// <param name="name"></param>
	/// <returns></returns>
	public Layer GetLayerFromName ( string name )
	{
		return Layers.FirstOrDefault( x => x.Name == name );
	}

	/// <summary>
	/// Returns the Layer at the specified index
	/// </summary>
	/// <param name="index"></param>
	/// <returns></returns>
	public Layer GetLayerFromIndex ( int index )
	{
		if ( index < 0 || index >= Layers.Count ) return null;
		return Layers[index];
	}

	public class Layer
	{
		/// <summary>
		/// The name of the Layer
		/// </summary>
		public string Name { get; set; }

		/// <summary>
		/// Whether or not this Layer is currently being rendered
		/// </summary>
		public bool IsVisible { get; set; }

		/// <summary>
		/// Whether or not this Layer is locked. Locked Layers will ignore any attempted changes
		/// </summary>
		public bool IsLocked { get; set; }

		/// <summary>
		/// The Tileset that this Layer uses
		/// </summary>
		[Property, Group( "Selected Layer" )] public TilesetResource TilesetResource { get; set; }

		/// <summary>
		/// The height of the Layer
		/// </summary>
		[Property, Group( "Selected Layer" )] public float? Height { get; set; } = null;

		/// <summary>
		/// Whether or not this Layer dictates the collision mesh
		/// </summary>
		[Group( "Selected Layer" ), Title( "Has Collisions" )] public bool IsCollisionLayer { get; set; }

		/// <summary>
		/// A dictionary of all Tiles in the layer by their position.
		/// </summary>
		public Dictionary<Vector2Int, Tile> Tiles { get; set; }

		/// <summary>
		/// A dictionary containing a list of positions for each Autotile Brush by their ID.
		/// </summary>
		public Dictionary<Guid, List<AutotilePosition>> Autotiles { get; set; }

		/// <summary>
		/// The TilesetComponent that this Layer belongs to
		/// </summary>
		[JsonIgnore, Hide] public TilesetComponent TilesetComponent { get; set; }

		public Layer ( string name = "Untitled Layer" )
		{
			Name = name;
			IsVisible = true;
			IsLocked = false;
			Tiles = new();
		}

		/// <summary>
		/// Returns an exact copy of the Layer
		/// </summary>
		/// <returns></returns>
		public Layer Copy ()
		{
			var layer = new Layer( Name )
			{
				IsVisible = IsVisible,
				IsLocked = IsLocked,
				Tiles = new(),
				IsCollisionLayer = false,
				TilesetComponent = TilesetComponent,
			};

			foreach ( var tile in Tiles )
			{
				layer.Tiles[tile.Key] = tile.Value.Copy();
			}

			return layer;
		}

		/// <summary>
		/// Set a tile at the specified position. Will fail if IsLocked is true.
		/// </summary>
		/// <param name="position"></param>
		/// <param name="tileId"></param>
		/// <param name="cellPosition"></param>
		/// <param name="angle"></param>
		/// <param name="flipX"></param>
		/// <param name="flipY"></param>
		/// <param name="rebuild"></param>
		public void SetTile ( Vector2Int position, Guid tileId, Vector2Int cellPosition = default, int angle = 0, bool flipX = false, bool flipY = false, bool rebuild = true, bool removeAutotile = true )
		{
			if ( IsLocked ) return;
			var tile = new Tile( tileId, cellPosition, angle, flipX, flipY );
			Tiles[position] = tile;
			if ( rebuild && TilesetComponent.IsValid() )
				TilesetComponent.IsDirty = true;

			if ( removeAutotile && Autotiles is not null )
			{
				foreach ( var group in Autotiles )
				{
					foreach ( var autotile in group.Value )
					{
						if ( autotile.Position == position )
						{
							Autotiles[group.Key].Remove( autotile );
							break;
						}
					}
				}
			}
		}

		/// <summary>
		/// Get the Tile at the specified position
		/// </summary>
		/// <param name="position"></param>
		/// <returns></returns>
		public Tile GetTile ( Vector2Int position )
		{
			return Tiles[position];
		}

		/// <summary>
		/// Get the Tile at the specified position
		/// </summary>
		/// <param name="position"></param>
		/// <returns></returns>
		public Tile GetTile ( Vector3 position )
		{
			return Tiles[new Vector2Int( (int)position.x, (int)position.y )];
		}

		/// <summary>
		/// Remove the Tile at the specified position. Will fail if IsLocked is true.
		/// </summary>
		/// <param name="position"></param>
		public void RemoveTile ( Vector2Int position )
		{
			if ( IsLocked ) return;
			Tiles.Remove( position );

			if ( Autotiles is not null )
			{
				foreach ( var group in Autotiles )
				{
					foreach ( var autotile in group.Value )
					{
						if ( autotile.Position == position )
						{
							Autotiles[group.Key].Remove( autotile );
							break;
						}
					}
				}
			}
		}

		/// <summary>
		/// Set an Autotile at the specified position. Will fail if IsLocked is true.
		/// </summary>
		/// <param name="autotileBrush"></param>
		/// <param name="position"></param>
		/// <param name="enabled"></param>
		///	<param name="update"></param>
		/// <param name="isMerging"></param>
		public void SetAutotile ( AutotileBrush autotileBrush, Vector2Int position, bool enabled = true, bool update = true, bool isMerging = false )
		{
			SetAutotile( autotileBrush.Id, position, enabled, update, isMerging );
		}

		/// <summary>
		/// Set an Autotile at the specified position. Will fail if IsLocked is true.
		/// </summary>
		/// <param name="autotileId"></param>
		/// <param name="position"></param>
		/// <param name="enabled"></param>
		/// <param name="update"></param>
		/// <param name="isMerging"></param>
		public void SetAutotile ( Guid autotileId, Vector2Int position, bool enabled = true, bool update = true, bool isMerging = false )
		{
			if ( IsLocked ) return;
			Autotiles ??= new();

			foreach ( var group in Autotiles )
			{
				if ( group.Key == autotileId ) continue;
				foreach ( var autotile in group.Value )
				{
					if ( autotile.Position == position )
					{
						Autotiles[group.Key].Remove( autotile );
						break;
					}
				}
			}

			if ( !Autotiles.ContainsKey( autotileId ) )
				Autotiles[autotileId] = new List<AutotilePosition>();

			bool shouldUpdate = false;
			if ( enabled )
			{
				if ( !Autotiles[autotileId].Any( x => x.Position == position ) )
				{
					Autotiles[autotileId].Add( new( position, isMerging ) );
					shouldUpdate = true;
				}
			}
			else
			{
				var foundPos = Autotiles[autotileId].FirstOrDefault( x => x.Position == position );
				if ( foundPos is not null )
				{
					Tiles.Remove( position );
					Autotiles[autotileId].Remove( foundPos );
					shouldUpdate = true;
				}
				else
				{
					RemoveTile( position );
				}
			}

			if ( update && shouldUpdate )
			{
				UpdateAutotile( autotileId, position, !enabled, shouldMerge: isMerging );
			}
		}

		/// <summary>
		/// Update the Autotile at the specified position. Used when manually modifying the placed autotiles.
		/// </summary>
		/// <param name="autotileId"></param>
		/// <param name="position"></param>
		/// <param name="checkErased"></param>
		/// <param name="updateSurrounding"></param>
		/// <param name="shouldMerge"></param>
		public void UpdateAutotile ( Guid autotileId, Vector2Int position, bool checkErased, bool updateSurrounding = true, bool shouldMerge = false )
		{
			if ( !Autotiles.ContainsKey( autotileId ) ) return;

			var brush = TilesetResource.AutotileBrushes.FirstOrDefault( x => x.Id == autotileId );
			var autotile = Autotiles[autotileId].FirstOrDefault( x => x.Position == position );
			if ( autotile is not null )
			{
				if ( shouldMerge ) autotile.ShouldMerge = true;
				if ( autotile.ShouldMerge ) shouldMerge = true;

				var bitmask = GetAutotileBitmask( autotileId, position, shouldMerge );
				if ( bitmask == -1 )
				{
					if ( checkErased ) RemoveTile( position );
				}
				else
				{
					if ( brush is not null )
					{
						var tile = brush.GetTileFromBitmask( bitmask );
						if ( tile is not null )
						{
							SetTile( position, tile.Id, Vector2Int.Zero, 0, false, false, false, removeAutotile: false );
						}
						else
						{
							Log.Warning( $"Tile not found for bitmask {bitmask} in AutotileBrush {brush.Name}" );
						}
					}
				}
			}

			if ( updateSurrounding )
			{
				var up = position.WithY( position.y + 1 );
				var down = position.WithY( position.y - 1 );
				var left = position.WithX( position.x - 1 );
				var right = position.WithX( position.x + 1 );
				var upLeft = up.WithX( left.x );
				var upRight = up.WithX( right.x );
				var downLeft = down.WithX( left.x );
				var downRight = down.WithX( right.x );

				if ( brush is not null && brush.AutotileType == AutotileType.Bitmask2x2Edge )
				{
					ClearInvalidAutotile( autotileId, up );
					ClearInvalidAutotile( autotileId, down );
					ClearInvalidAutotile( autotileId, left );
					ClearInvalidAutotile( autotileId, right );
					ClearInvalidAutotile( autotileId, upLeft );
					ClearInvalidAutotile( autotileId, upRight );
					ClearInvalidAutotile( autotileId, downLeft );
					ClearInvalidAutotile( autotileId, downRight );
				}

				UpdateAutotile( autotileId, up, checkErased, false, shouldMerge );
				UpdateAutotile( autotileId, down, checkErased, false, shouldMerge );
				UpdateAutotile( autotileId, left, checkErased, false, shouldMerge );
				UpdateAutotile( autotileId, right, checkErased, false, shouldMerge );
				UpdateAutotile( autotileId, upLeft, checkErased, false, shouldMerge );
				UpdateAutotile( autotileId, upRight, checkErased, false, shouldMerge );
				UpdateAutotile( autotileId, downLeft, checkErased, false, shouldMerge );
				UpdateAutotile( autotileId, downRight, checkErased, false, shouldMerge );
			}
		}

		void ClearInvalidAutotile ( Guid autotileId, Vector2Int position )
		{
			if ( !Tiles.TryGetValue( position, out var tile ) ) return;

			var brush = TilesetResource.AutotileBrushes.FirstOrDefault( x => x.Id == autotileId );

			if ( brush is null ) return;
			if ( brush.AutotileType != AutotileType.Bitmask2x2Edge ) return;
			if ( !brush.Tiles.Any( x => x.Tiles.Any( y => y.Id == tile.TileId ) ) ) return;
			if ( GetAutotileBitmask( autotileId, position ) != -1 ) return;

			RemoveTile( position );
		}


		public int GetAutotileBitmask ( Guid autotileId, Vector2Int position, bool mergeAll = false )
		{
			if ( Autotiles is null || ( !mergeAll && !Autotiles.ContainsKey( autotileId ) ) ) return -1;

			List<AutotilePosition> positions = new();
			if ( mergeAll )
			{
				foreach ( var kvp in Autotiles )
				{
					positions.AddRange( kvp.Value );
				}
			}
			else
			{
				positions = Autotiles[autotileId];
			}
			int value = 0;

			var up = position.WithY( position.y + 1 );
			var down = position.WithY( position.y - 1 );
			var left = position.WithX( position.x - 1 );
			var right = position.WithX( position.x + 1 );

			var brush = TilesetResource.AutotileBrushes.FirstOrDefault( x => x.Id == autotileId );
			if ( brush is null ) return 0;

			bool is2x2 = brush.AutotileType == AutotileType.Bitmask2x2Edge;
			if ( is2x2 )
			{
				foreach ( var pos in positions )
				{
					if ( pos.Position == up ) value += 1;
					if ( pos.Position == left ) value += 2;
					if ( pos.Position == right ) value += 4;
					if ( pos.Position == down ) value += 8;
				}
				switch ( value )
				{
					case 0:
					case 1:
					case 2:
					case 4:
					case 8:
					case 9:
					case 6:
						return -1;
				}
				value = 0;
			}

			var upLeft = up.WithX( left.x );
			var upRight = up.WithX( right.x );
			var downLeft = down.WithX( left.x );
			var downRight = down.WithX( right.x );

			foreach ( var thing in positions )
			{
				var pos = thing.Position;
				if ( pos == upLeft ) value += 1;
				if ( pos == up ) value += 2;
				if ( pos == upRight ) value += 4;
				if ( pos == left ) value += 8;
				if ( pos == right ) value += 16;
				if ( pos == downLeft ) value += 32;
				if ( pos == down ) value += 64;
				if ( pos == downRight ) value += 128;
			}

			if ( is2x2 )
			{
				switch ( value )
				{
					case 46:
					case 116:
					case 147:
					case 201:
						return -1;
				}
			}

			return value;
		}

		public int GetAutotileBitmask ( Guid autotileId, Vector2Int position, Dictionary<Vector2Int, bool> overrides, bool mergeAll = false )
		{
			if ( Autotiles is null ) return -1;

			var positions = new List<Vector2Int>();
			foreach ( var thing in Autotiles )
			{
				if ( !mergeAll && thing.Key != autotileId ) continue;
				foreach ( var pos in thing.Value )
				{
					if ( !positions.Contains( pos.Position ) )
						positions.Add( pos.Position );
				}
			}
			int value = 0;

			foreach ( var ride in overrides )
			{
				if ( ride.Value )
				{
					if ( !positions.Contains( ride.Key ) )
					{
						positions.Add( ride.Key );
					}
				}
				else
				{
					if ( positions.Contains( ride.Key ) )
					{
						positions.Remove( ride.Key );
					}
				}
			}

			var up = position.WithY( position.y + 1 );
			var down = position.WithY( position.y - 1 );
			var left = position.WithX( position.x - 1 );
			var right = position.WithX( position.x + 1 );
			var upLeft = up.WithX( left.x );
			var upRight = up.WithX( right.x );
			var downLeft = down.WithX( left.x );
			var downRight = down.WithX( right.x );

			foreach ( var pos in positions )
			{
				if ( pos == upLeft ) value += 1;
				if ( pos == up ) value += 2;
				if ( pos == upRight ) value += 4;
				if ( pos == left ) value += 8;
				if ( pos == right ) value += 16;
				if ( pos == downLeft ) value += 32;
				if ( pos == down ) value += 64;
				if ( pos == downRight ) value += 128;
			}

			return value;
		}

		public class AutotilePosition
		{
			public Vector2Int Position { get; set; }
			public bool ShouldMerge { get; set; } = false;

			public AutotilePosition ( Vector2Int position, bool shouldMerge = false )
			{
				Position = position;
				ShouldMerge = shouldMerge;
			}
		}
	}

	public class Tile
	{
		public Guid TileId { get; set; } = Guid.NewGuid();
		public Vector2Int CellPosition { get; set; }
		public bool HorizontalFlip { get; set; }
		public bool VerticalFlip { get; set; }
		public int Rotation { get; set; }
		public Vector2Int BakedPosition { get; set; }

		public Tile () { }

		public Tile ( Guid tileId, Vector2Int cellPosition, int rotation, bool flipX, bool flipY )
		{
			TileId = tileId;
			CellPosition = cellPosition;
			HorizontalFlip = flipX;
			VerticalFlip = flipY;
			Rotation = rotation;
		}

		public Tile Copy ()
		{
			return new Tile( TileId, CellPosition, Rotation, HorizontalFlip, VerticalFlip );
		}
	}

	public class ComponentControls { }

}

internal sealed class TilesetSceneObject : SceneCustomObject
{
	TilesetComponent Component;
	Dictionary<TilesetResource, (TileAtlas, Material)> Materials = new();
	Material MissingMaterial;
	int LayerIndex;

	public TilesetSceneObject ( TilesetComponent component, SceneWorld world, int layerIndex ) : base( world )
	{
		Component = component;
		LayerIndex = layerIndex;

		MissingMaterial = Material.Load( "materials/sprite_2d.vmat" ).CreateCopy();
		MissingMaterial.Set( "Texture", Texture.Load( "images/missing-tile.png" ) );
		Tags.SetFrom( Component.Tags );
	}

	public override void RenderSceneObject ()
	{
		if ( Component?.Layers is null ) return;
		var Layer = Component.Layers.ElementAtOrDefault( LayerIndex );
		if ( Layer is null )
		{
			return;
		}

		var layers = Component.Layers.ToList();
		layers.Reverse();
		if ( layers.Count == 0 ) return;

		Dictionary<Vector2Int, TilesetComponent.Tile> missingTiles = new();

		if ( Layer?.IsVisible != true ) return;

		int i = 0;
		int layerIndex = layers.IndexOf( Layer );

		{
			var tileset = Layer.TilesetResource;
			if ( tileset is null ) return;
			var tilemap = tileset.TileMap;

			var combo = GetMaterial( tileset );
			if ( combo.Item1 is null || combo.Item2 is null ) return;

			var tiling = combo.Item1.GetTiling();
			var totalTiles = Layer.Tiles.Where( x => x.Value.TileId == default || tilemap.ContainsKey( x.Value.TileId ) );
			var vertex = ArrayPool<Vertex>.Shared.Rent( totalTiles.Count() * 6 );

			var minPosition = new Vector3( int.MaxValue, int.MaxValue, int.MaxValue );
			var maxPosition = new Vector3( int.MinValue, int.MinValue, int.MinValue );

			foreach ( var tile in Layer.Tiles )
			{
				var pos = tile.Key;
				Vector2Int offsetPos = Vector2Int.Zero;
				if ( tile.Value.TileId == default ) offsetPos = tile.Value.BakedPosition;
				else
				{
					if ( !tilemap.ContainsKey( tile.Value.TileId ) )
					{
						missingTiles[pos] = tile.Value;
						continue;
					}
					offsetPos = tilemap[tile.Value.TileId].Position;
				}
				var offset = combo.Item1.GetOffset( offsetPos + tile.Value.CellPosition );
				if ( tile.Value.HorizontalFlip )
					offset.x = -offset.x - tiling.x;
				if ( !tile.Value.VerticalFlip )
					offset.y = -offset.y - tiling.y;


				var size = tileset.GetTileSize();
				var position = new Vector3( pos.x, pos.y, Layer.Height ?? ( Component.Layers.Count - Component.Layers.IndexOf( Layer ) ) ) * new Vector3( size.x, size.y, 1 );

				minPosition = Vector3.Min( minPosition, position );
				maxPosition = Vector3.Max( maxPosition, position );

				var topLeft = new Vector3( position.x, position.y, position.z );
				var topRight = new Vector3( position.x + size.x, position.y, position.z );
				var bottomRight = new Vector3( position.x + size.x, position.y + size.y, position.z );
				var bottomLeft = new Vector3( position.x, position.y + size.y, position.z );

				var uvTopLeft = new Vector2( offset.x, offset.y );
				var uvTopRight = new Vector2( offset.x + tiling.x, offset.y );
				var uvBottomRight = new Vector2( offset.x + tiling.x, offset.y + tiling.y );
				var uvBottomLeft = new Vector2( offset.x, offset.y + tiling.y );

				if ( tile.Value.Rotation == 90 )
				{
					var tempUv = uvTopLeft;
					uvTopLeft = uvBottomLeft;
					uvBottomLeft = uvBottomRight;
					uvBottomRight = uvTopRight;
					uvTopRight = tempUv;
				}
				else if ( tile.Value.Rotation == 180 )
				{
					var tempUv = uvTopLeft;
					uvTopLeft = uvBottomRight;
					uvBottomRight = tempUv;
					tempUv = uvTopRight;
					uvTopRight = uvBottomLeft;
					uvBottomLeft = tempUv;
				}
				else if ( tile.Value.Rotation == 270 )
				{
					var tempUv = uvTopLeft;
					uvTopLeft = uvTopRight;
					uvTopRight = uvBottomRight;
					uvBottomRight = uvBottomLeft;
					uvBottomLeft = tempUv;
				}

				vertex[i] = new Vertex( topLeft );
				vertex[i].TexCoord0 = uvTopLeft;
				vertex[i].Normal = Vector3.Up;
				i++;

				vertex[i] = new Vertex( topRight );
				vertex[i].TexCoord0 = uvTopRight;
				vertex[i].Normal = Vector3.Up;
				i++;

				vertex[i] = new Vertex( bottomRight );
				vertex[i].TexCoord0 = uvBottomRight;
				vertex[i].Normal = Vector3.Up;
				i++;

				vertex[i] = new Vertex( topLeft );
				vertex[i].TexCoord0 = uvTopLeft;
				vertex[i].Normal = Vector3.Up;
				i++;

				vertex[i] = new Vertex( bottomRight );
				vertex[i].TexCoord0 = uvBottomRight;
				vertex[i].Normal = Vector3.Up;
				i++;

				vertex[i] = new Vertex( bottomLeft );
				vertex[i].TexCoord0 = uvBottomLeft;
				vertex[i].Normal = Vector3.Up;
				i++;
			}

			Graphics.Draw( vertex, totalTiles.Count() * 6, combo.Item2, Attributes );
			ArrayPool<Vertex>.Shared.Return( vertex );

			var siz = tileset.GetTileSize();
			maxPosition += new Vector3( siz.x, siz.y, 0 );
			Bounds = new BBox( minPosition, maxPosition + Vector3.Down * 0.01f ).Rotate( Rotation ).Translate( Position );


		}

		if ( missingTiles.Count > 0 )
		{
			var uvTopLeft = new Vector2( 0, 0 );
			var uvTopRight = new Vector2( 1, 0 );
			var uvBottomRight = new Vector2( 1, 1 );
			var uvBottomLeft = new Vector2( 0, 1 );

			foreach ( var tile in missingTiles )
			{
				var material = MissingMaterial;
				var pos = tile.Key;
				var size = Component.Layers[0].TilesetResource.TileSize;
				var position = new Vector3( pos.x, pos.y, 0 ) * new Vector3( size.x, size.y, 1 );

				var topLeft = new Vector3( position.x, position.y, position.z );
				var topRight = new Vector3( position.x + size.x, position.y, position.z );
				var bottomRight = new Vector3( position.x + size.x, position.y + size.y, position.z );
				var bottomLeft = new Vector3( position.x, position.y + size.y, position.z );

				var vertex = new Vertex[]
				{
				new Vertex(topLeft) { TexCoord0 = uvTopLeft, Normal = Vector3.Up },
				new Vertex(topRight) { TexCoord0 = uvTopRight, Normal = Vector3.Up },
				new Vertex(bottomRight) { TexCoord0 = uvBottomRight, Normal = Vector3.Up },
				new Vertex(topLeft) { TexCoord0 = uvTopLeft, Normal = Vector3.Up },
				new Vertex(bottomRight) { TexCoord0 = uvBottomRight, Normal = Vector3.Up },
				new Vertex(bottomLeft) { TexCoord0 = uvBottomLeft, Normal = Vector3.Up },
				};

				Graphics.Draw( vertex, 6, material, Attributes );
			}
		}
	}

	(TileAtlas, Material) GetMaterial ( TilesetResource resource )
	{
		var texture = TileAtlas.FromTileset( resource );

		if ( Materials.TryGetValue( resource, out var combo ) )
		{
			combo.Item1 = texture;
			combo.Item2.Set( "Texture", texture );
		}
		else
		{
			var material = Material.Load( "materials/sprite_2d.vmat" ).CreateCopy();
			material.Set( "Texture", texture );
			combo.Item1 = texture;
			combo.Item2 = material;
			Materials.Add( resource, combo );
		}

		return combo;
	}
}
using Sandbox;
using System;
using System.Collections.Generic;
using System.Linq;

namespace SpriteTools;

/// <summary>
/// A class that re-packs a tileset with 1px borders to avoid bleeding.
/// </summary>
public class TileAtlas
{
	Texture Texture;
	Vector2 OriginalTileSize;
	Vector2Int TileSize;
	Vector2Int TileCounts;
	Dictionary<Vector2Int, Texture> TileCache = new();


	public static Dictionary<TilesetResource, TileAtlas> Cache = new();

	public Vector2 GetTiling ()
	{
		return (Vector2)OriginalTileSize / Texture.Size;
	}

	public Vector2 GetOffset ( Vector2Int cellPosition )
	{
		return new Vector2( cellPosition.x * TileSize.x + 1, cellPosition.y * TileSize.y + 1 ) / Texture.Size;
	}

	public static TileAtlas FromTileset ( TilesetResource tilesetResource )
	{
		if ( tilesetResource is null ) return null;

		if ( Cache?.ContainsKey( tilesetResource ) ?? false )
		{
			return Cache[tilesetResource];
		}

		if ( tilesetResource.Tiles.Count() == 0 )
		{
			return null;
		}

		if ( tilesetResource.Tiles.Any( x => x?.Tileset is null ) )
		{
			return null;
		}

		var path = tilesetResource.FilePath;
		if ( !FileSystem.Mounted.FileExists( path ) )
		{
			Log.Error( $"Tileset texture file {path} does not exist." );
			return null;
		}
		var texture = Texture.LoadFromFileSystem( path, FileSystem.Mounted );
		var atlas = new TileAtlas();

		var tileSize = tilesetResource.TileSize;
		atlas.TileSize = tileSize + Vector2Int.One * 2;
		atlas.OriginalTileSize = tileSize;

		var hTiles = tilesetResource.Tiles.Max( x => x.Position.x + x.Size.x );
		var vTiles = tilesetResource.Tiles.Max( x => x.Position.y + x.Size.y );
		atlas.TileCounts = new Vector2Int( hTiles, vTiles );

		var textureSize = new Vector2Int( hTiles * ( tileSize.x + 2 ), vTiles * ( tileSize.y + 2 ) );

		byte[] textureData = new byte[textureSize.x * textureSize.y * 4];
		for ( int i = 0; i < textureSize.x; i++ )
		{
			for ( int j = 0; j < textureSize.y; j++ )
			{
				var ind = ( j * textureSize.x + i ) * 4;
				textureData[ind] = 0;
				textureData[ind + 1] = 0;
				textureData[ind + 2] = 0;
				textureData[ind + 3] = 0;
			}
		}

		var pixels = texture.GetPixels();

		foreach ( var tile in tilesetResource.Tiles )
		{
			for ( int n = 0; n < tile.Size.x; n++ )
			{
				for ( int m = 0; m < tile.Size.y; m++ )
				{
					var cellPos = tile.Position + new Vector2Int( n, m );

					var tSize = tileSize * tile.Size;
					var tPos = cellPos * atlas.TileSize + Vector2Int.One;
					var sampleX = cellPos.x * tileSize.x;
					var sampleY = cellPos.y * tileSize.y;
					for ( int i = -1; i <= tSize.x; i++ )
					{
						for ( int j = -1; j <= tSize.y; j++ )
						{
							var sampleInd = (int)( ( sampleY + Math.Clamp( j, 0, tSize.y - 1 ) ) * texture.Size.x + sampleX + Math.Clamp( i, 0, tSize.x - 1 ) );
							var color = pixels[sampleInd];
							var ind = ( ( tPos.y + j ) * textureSize.x + tPos.x + i ) * 4;
							if ( ind < 0 || ind >= textureData.Length ) continue;
							textureData[ind + 0] = color.r;
							textureData[ind + 1] = color.g;
							textureData[ind + 2] = color.b;
							textureData[ind + 3] = color.a;
						}
					}


				}
			}

		}

		var builder = Texture.Create( textureSize.x, textureSize.y );
		builder.WithData( textureData );
		builder.WithMips( 0 );
		atlas.Texture = builder.Finish();

		Cache[tilesetResource] = atlas;

		return atlas;
	}

	public Texture GetTextureFromCell ( Vector2Int cellPosition )
	{
		if ( TileCache.ContainsKey( cellPosition ) )
		{
			return TileCache[cellPosition];
		}

		int x = cellPosition.x * TileSize.x + 1;
		int y = cellPosition.y * TileSize.y + 1;
		int outputSizeX = TileSize.x - 2;
		int outputSizeY = TileSize.y - 2;
		byte[] textureData = new byte[outputSizeX * outputSizeY * 4];
		var pixels = Texture.GetPixels();
		for ( int i = 0; i < outputSizeX; i++ )
		{
			for ( int j = 0; j < outputSizeY; j++ )
			{
				int ind = ( i + j * outputSizeX ) * 4;
				int sampleIndex = (int)( x + i + ( y + j ) * Texture.Size.x );
				var color = pixels[sampleIndex];
				textureData[ind + 0] = color.r;
				textureData[ind + 1] = color.g;
				textureData[ind + 2] = color.b;
				textureData[ind + 3] = color.a;
			}
		}

		var builder = Texture.Create( outputSizeX, outputSizeY );
		builder.WithData( textureData );
		builder.WithMips( 0 );
		var texture = builder.Finish();
		TileCache[cellPosition] = texture;
		return texture;
	}

	// Cast to texture
	public static implicit operator Texture ( TileAtlas atlas )
	{
		return atlas?.Texture ?? null;
	}

	public static void ClearCache ( string path = "" )
	{
		if ( path.StartsWith( "/" ) ) path = path.Substring( 1 );
		if ( string.IsNullOrEmpty( path ) )
		{
			Cache.Clear();
		}
		else
		{
			Cache = Cache.Where( x => x.Key.FilePath != path ).ToDictionary( x => x.Key, x => x.Value );
		}
	}

	public static void ClearCache ( TilesetResource tileset )
	{
		Cache.Remove( tileset );
	}
}
using System.Collections.Generic;

namespace Sandbox;

public static class TraceExtensions
{
	extension( SceneTrace trace )
	{
		/// <summary>
		/// Performs a ray trace along the given ray for the specified distance,
		/// returning only the first hit.
		/// </summary>
		/// <param name="ray">The ray to trace along.</param>
		/// <param name="distance">How far the ray should travel.</param>
		/// <param name="withTags">Optional tags to restrict the trace to objects that have any of these tags.</param>
		/// <returns>Result of the trace.</returns>
		public SceneTraceResult RunRayTrace( Ray ray, float distance = 100f, params string[] withTags )
		{
			return trace.Ray( ray, distance )
				.WithAnyTags( withTags )
				.Run();
		}
		
		/// <summary>
		/// Performs a ray trace along the given ray for the specified distance,
		/// returning all hits along the ray.
		/// </summary>
		/// <param name="ray">The ray to trace along.</param>
		/// <param name="distance">How far the ray should travel.</param>
		/// <param name="withTags">Optional tags to restrict the trace to objects that have any of these tags.</param>
		/// <returns>All results of the trace in order along the ray.</returns>
		public IEnumerable<SceneTraceResult> RunAllRayTrace( Ray ray, float distance = 100f, params string[] withTags )
		{
			return trace.Ray( ray, distance )
				.WithAnyTags( withTags )
				.RunAll();
		}
	}
}
using System.Collections.Generic;
using Sandbox;
using Sandbox.Diagnostics;

namespace WackyLib.Patterns;

/// <summary>
/// Represents a Singleton component. Which is a component that we can access through an instance, and only one can be allowed in a scene at once.
/// In-game, duplicate instances are automatically destroyed. In the editor (with ExecuteInEditor), multiple instances are permitted to coexist
/// for tooling purposes, but a warning is logged, as only one instance will survive when the game runs (the latest awakened component).
/// </summary>
public abstract class Singleton<T> : Component, IHotloadManaged where T : Singleton<T>
{
#nullable enable
#pragma warning disable SB3000
	// ReSharper disable once MemberCanBePrivate.Global
	public static T? Instance { get; private set; }
#pragma warning restore SB3000
	
	private readonly Logger Log = new( "Singleton" );
	
	protected override void OnAwake()
	{
		// We're running ExecuteInEditor, which means we should ignore instances.
		if ( ExecutingInEditor() )
		{
			Log.Info( $"OnAwake called in editor with ExecuteInEditor, creating once." );
			
			if ( Active )
			{
				if ( Instance.IsValid() && Instance != this )
				{
					Log.Warning( $"Multiple {typeof(T)} instances detected in the scene! Only one will be used in-game." );
				}
				
				Instance = (T)this;
			}
			
			return;
		}
		
		if ( Instance.IsValid() )
		{
			Log.Warning( $"Singleton tried to initialize another {typeof(T)}!" );
			Destroy();
			return;
		}
		
		if ( Active )
		{
			Instance = (T)this;
		}
	}
	
	protected override void OnDestroy()
	{
		if ( Instance == this )
		{
			Instance = null;
		}
	}
	
	void IHotloadManaged.Destroyed( Dictionary<string, object> state )
	{
		state["IsActive"] = Instance == this;
	}
	
	void IHotloadManaged.Created( IReadOnlyDictionary<string, object> state )
	{
		if ( state.GetValueOrDefault( "IsActive" ) is true )
		{
			Instance = (T)this;
		}
	}
	
	private bool ExecutingInEditor()
	{
		if ( !Game.IsEditor )
		{
			return false;
		}
		
		var type = GetType();
		return typeof(ExecuteInEditor).IsAssignableFrom( type );
	}
}
using Sandbox;
using System;

[Hide]
internal sealed class Dummy : Component, Component.ExecuteInEditor
{
  
}
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Sandbox;

namespace Iconify;

/// <summary>
/// Fetches icons from the Iconify API and caches them as textures.
/// Handles disk cache and in-memory cache with proper null safety.
/// </summary>
public static class IconCache
{
	private static readonly Dictionary<string, Texture> MemoryCache = new();
	private static BaseFileSystem? _diskCache;
	private static bool _diskCacheReady;

	private static BaseFileSystem DiskCache
	{
		get
		{
			if ( !_diskCacheReady )
			{
				_diskCacheReady = true;
				try
				{
					if ( FileSystem.Data is not null )
					{
						FileSystem.Data.CreateDirectory( "iconify_cache" );
						_diskCache = FileSystem.Data.CreateSubSystem( "iconify_cache" );
					}
				}
				catch ( Exception e )
				{
					Log.Warning( $"[Iconify] Could not create disk cache: {e.Message}" );
					_diskCache = null;
				}
			}
			return _diskCache;
		}
	}

	/// <summary>
	/// Get an icon texture from cache or fetch from the Iconify API.
	/// </summary>
	public static async Task<Texture?> GetOrFetch( string prefix, string name, string color, int size )
	{
		var cacheKey = $"{prefix}_{name}_{color}_{size}";

		// Memory cache
		if ( MemoryCache.TryGetValue( cacheKey, out var cached ) && cached.IsValid() )
			return cached;

		// Disk cache
		var diskPath = $"{cacheKey}.svg";
		if ( DiskCache is not null && DiskCache.FileExists( diskPath ) )
		{
			try
			{
				var svgString = DiskCache.ReadAllText( diskPath );
				var tex = Texture.CreateFromSvgSource( svgString, size, size, null );
				if ( tex is not null && tex.IsValid() )
				{
					MemoryCache[cacheKey] = tex;
					return tex;
				}
			}
			catch { /* disk cache corrupted, re-fetch */ }
		}

		// Fetch from API
		var texture = await FetchFromApi( prefix, name, color, size, cacheKey );
		if ( texture is not null )
		{
			MemoryCache[cacheKey] = texture;
		}

		return texture;
	}

	private static async Task<Texture?> FetchFromApi( string prefix, string name, string color, int size, string cacheKey )
	{
		var encodedColor = Uri.EscapeDataString( color ?? "white" );
		var url = $"https://api.iconify.design/{prefix}/{name}.svg?color={encodedColor}&width={size}&height={size}";

		try
		{
			var svgString = await Http.RequestStringAsync( url );

			if ( string.IsNullOrEmpty( svgString ) )
			{
				Log.Warning( $"[Iconify] Empty response for {prefix}:{name}" );
				return null;
			}

			// Create texture directly from SVG string with size
			var texture = Texture.CreateFromSvgSource( svgString, size, size, null );

			if ( texture is not null && texture.IsValid() )
			{
				SaveToDiskCache( $"{cacheKey}.svg", System.Text.Encoding.UTF8.GetBytes( svgString ) );
			}

			return texture;
		}
		catch ( Exception e )
		{
			Log.Warning( $"[Iconify] API fetch failed for {prefix}:{name}: {e.Message}" );
			return null;
		}
	}

	private static void SaveToDiskCache( string path, byte[] data )
	{
		try
		{
			DiskCache?.WriteAllBytes( path, data );
		}
		catch { /* non-critical, just won't be cached */ }
	}
}
// Generated by Haxe 4.3.7

#pragma warning disable 109, 114, 219, 429, 168, 162
public class IntIterator : global::haxe.lang.HxObject {
	
	public IntIterator(global::haxe.lang.EmptyObject empty) {
	}
	
	
	public IntIterator(int min, int max) {
		global::IntIterator.__hx_ctor__IntIterator(this, min, max);
	}
	
	
	protected static void __hx_ctor__IntIterator(global::IntIterator __hx_this, int min, int max) {
		__hx_this.min = min;
		__hx_this.max = max;
	}
	
	
	public int min;
	
	public int max;
	
	public bool hasNext() {
		return ( this.min < this.max );
	}
	
	
	public int next() {
		return this.min++;
	}
	
	
	public override double __hx_setField_f(string field, int hash, double @value, bool handleProperties) {
		unchecked {
			switch (hash) {
				case 5442212:
				{
					this.max = ((int) (@value) );
					return @value;
				}
				
				
				case 5443986:
				{
					this.min = ((int) (@value) );
					return @value;
				}
				
				
				default:
				{
					return base.__hx_setField_f(field, hash, @value, handleProperties);
				}
				
			}
			
		}
	}
	
	
	public override object __hx_setField(string field, int hash, object @value, bool handleProperties) {
		unchecked {
			switch (hash) {
				case 5442212:
				{
					this.max = ((int) (global::haxe.lang.Runtime.toInt(@value)) );
					return @value;
				}
				
				
				case 5443986:
				{
					this.min = ((int) (global::haxe.lang.Runtime.toInt(@value)) );
					return @value;
				}
				
				
				default:
				{
					return base.__hx_setField(field, hash, @value, handleProperties);
				}
				
			}
			
		}
	}
	
	
	public override object __hx_getField(string field, int hash, bool throwErrors, bool isCheck, bool handleProperties) {
		unchecked {
			switch (hash) {
				case 1224901875:
				{
					return ((global::haxe.lang.Function) (new global::haxe.lang.Closure(this, "next", 1224901875)) );
				}
				
				
				case 407283053:
				{
					return ((global::haxe.lang.Function) (new global::haxe.lang.Closure(this, "hasNext", 407283053)) );
				}
				
				
				case 5442212:
				{
					return this.max;
				}
				
				
				case 5443986:
				{
					return this.min;
				}
				
				
				default:
				{
					return base.__hx_getField(field, hash, throwErrors, isCheck, handleProperties);
				}
				
			}
			
		}
	}
	
	
	public override double __hx_getField_f(string field, int hash, bool throwErrors, bool handleProperties) {
		unchecked {
			switch (hash) {
				case 5442212:
				{
					return ((double) (this.max) );
				}
				
				
				case 5443986:
				{
					return ((double) (this.min) );
				}
				
				
				default:
				{
					return base.__hx_getField_f(field, hash, throwErrors, handleProperties);
				}
				
			}
			
		}
	}
	
	
	public override object __hx_invokeField(string field, int hash, object[] dynargs) {
		unchecked {
			switch (hash) {
				case 1224901875:
				{
					return this.next();
				}
				
				
				case 407283053:
				{
					return this.hasNext();
				}
				
				
				default:
				{
					return base.__hx_invokeField(field, hash, dynargs);
				}
				
			}
			
		}
	}
	
	
	public override void __hx_getFields(global::Array<string> baseArr) {
		baseArr.push("max");
		baseArr.push("min");
		base.__hx_getFields(baseArr);
	}
	
	
}


// Generated by Haxe 4.3.7

#pragma warning disable 109, 114, 219, 429, 168, 162
public sealed class EReg : global::haxe.lang.HxObject {
	
	public EReg(global::haxe.lang.EmptyObject empty) {
	}
	
	
	public EReg(string r, string opt) {
		global::EReg.__hx_ctor__EReg(this, r, opt);
	}
	
	
	private static void __hx_ctor__EReg(global::EReg __hx_this, string r, string opt) {
		unchecked {
			int opts = ((int) (global::haxe.lang.Runtime.toInt(((object) (global::System.Text.RegularExpressions.RegexOptions.CultureInvariant) ))) );
			{
				int _g = 0;
				int _g1 = opt.Length;
				while (( _g < _g1 )) {
					int i = _g++;
					switch (((int) (opt[i]) )) {
						case 99:
						{
							opts |= ((int) (global::haxe.lang.Runtime.toInt(((object) (global::System.Text.RegularExpressions.RegexOptions.Compiled) ))) );
							break;
						}
						
						
						case 103:
						{
							__hx_this.isGlobal = true;
							break;
						}
						
						
						case 105:
						{
							opts |= ((int) (global::haxe.lang.Runtime.toInt(((object) (global::System.Text.RegularExpressions.RegexOptions.IgnoreCase) ))) );
							break;
						}
						
						
						case 109:
						{
							opts |= ((int) (global::haxe.lang.Runtime.toInt(((object) (global::System.Text.RegularExpressions.RegexOptions.Multiline) ))) );
							break;
						}
						
						
					}
					
				}
				
			}
			
			__hx_this.regex = new global::System.Text.RegularExpressions.Regex(((string) (r) ), ((global::System.Text.RegularExpressions.RegexOptions) (((object) (opts) )) ));
		}
	}
	
	
	public static string escape(string s) {
		return global::System.Text.RegularExpressions.Regex.Escape(((string) (s) ));
	}
	
	
	public global::System.Text.RegularExpressions.Regex regex;
	
	public global::System.Text.RegularExpressions.Match m;
	
	public bool isGlobal;
	
	public string cur;
	
	public bool match(string s) {
		this.m = this.regex.Match(((string) (s) ));
		this.cur = s;
		return ( this.m as global::System.Text.RegularExpressions.Group ).Success;
	}
	
	
	public string matched(int n) {
		if (( ( this.m == null ) || ( ((uint) (n) ) > this.m.Groups.Count ) )) {
			throw ((global::System.Exception) (global::haxe.Exception.thrown("EReg::matched")) );
		}
		
		if ( ! (this.m.Groups[n].Success) ) {
			return null;
		}
		
		return ( this.m.Groups[n] as global::System.Text.RegularExpressions.Capture ).Value;
	}
	
	
	public string matchedLeft() {
		return this.cur.Substring(((int) (0) ), ((int) (( this.m as global::System.Text.RegularExpressions.Capture ).Index) ));
	}
	
	
	public string matchedRight() {
		return this.cur.Substring(((int) (( ( this.m as global::System.Text.RegularExpressions.Capture ).Index + ( this.m as global::System.Text.RegularExpressions.Capture ).Length )) ));
	}
	
	
	public object matchedPos() {
		int tmp = ( this.m as global::System.Text.RegularExpressions.Capture ).Index;
		{
			int __temp_odecl1 = ( this.m as global::System.Text.RegularExpressions.Capture ).Length;
			return new global::haxe.lang.DynamicObject(new int[]{}, new object[]{}, new int[]{5393365, 5594516}, new double[]{((double) (__temp_odecl1) ), ((double) (tmp) )});
		}
		
	}
	
	
	public bool matchSub(string s, int pos, global::haxe.lang.Null<int> len) {
		unchecked {
			int len1 = ( ( ! (len.hasValue) ) ? (-1) : ((len).@value) );
			this.m = ( (( len1 < 0 )) ? (this.regex.Match(((string) (s) ), ((int) (pos) ))) : (this.regex.Match(((string) (s) ), ((int) (pos) ), ((int) (len1) ))) );
			this.cur = s;
			return ( this.m as global::System.Text.RegularExpressions.Group ).Success;
		}
	}
	
	
	public global::Array<string> split(string s) {
		if (this.isGlobal) {
			return global::cs.Lib.array<string>(((string[]) (this.regex.Split(((string) (s) ))) ));
		}
		
		global::System.Text.RegularExpressions.Match m = this.regex.Match(((string) (s) ));
		if ( ! (( m as global::System.Text.RegularExpressions.Group ).Success) ) {
			return new global::Array<string>(new string[]{s});
		}
		
		return new global::Array<string>(new string[]{s.Substring(((int) (0) ), ((int) (( m as global::System.Text.RegularExpressions.Capture ).Index) )), s.Substring(((int) (( ( m as global::System.Text.RegularExpressions.Capture ).Index + ( m as global::System.Text.RegularExpressions.Capture ).Length )) ))});
	}
	
	
	public int start(int @group) {
		return ( this.m.Groups[@group] as global::System.Text.RegularExpressions.Capture ).Index;
	}
	
	
	public int len(int @group) {
		return ( this.m.Groups[@group] as global::System.Text.RegularExpressions.Capture ).Length;
	}
	
	
	public string replace(string s, string @by) {
		unchecked {
			if (this.isGlobal) {
				return this.regex.Replace(((string) (s) ), ((string) (@by) ));
			}
			else {
				return this.regex.Replace(((string) (s) ), ((string) (@by) ), ((int) (1) ));
			}
			
		}
	}
	
	
	public string map(string s, global::haxe.lang.Function f) {
		unchecked {
			int offset = 0;
			global::StringBuf buf = new global::StringBuf();
			do {
				if (( offset >= s.Length )) {
					break;
				}
				else if ( ! (this.matchSub(s, offset, default(global::haxe.lang.Null<int>))) ) {
					buf.@add<string>(((string) (global::haxe.lang.StringExt.substr(s, offset, default(global::haxe.lang.Null<int>))) ));
					break;
				}
				
				object p = this.matchedPos();
				buf.@add<string>(((string) (global::haxe.lang.StringExt.substr(s, offset, new global::haxe.lang.Null<int>(( ((int) (global::haxe.lang.Runtime.getField_f(p, "pos", 5594516, true)) ) - ((int) (offset) ) ), true))) ));
				buf.@add<string>(global::haxe.lang.Runtime.toString(f.__hx_invoke1_o(default(double), this)));
				if (( ((int) (global::haxe.lang.Runtime.getField_f(p, "len", 5393365, true)) ) == 0 )) {
					buf.@add<string>(((string) (global::haxe.lang.StringExt.substr(s, ((int) (global::haxe.lang.Runtime.getField_f(p, "pos", 5594516, true)) ), new global::haxe.lang.Null<int>(1, true))) ));
					offset = ( ((int) (global::haxe.lang.Runtime.getField_f(p, "pos", 5594516, true)) ) + 1 );
				}
				else {
					offset = ( ((int) (global::haxe.lang.Runtime.getField_f(p, "pos", 5594516, true)) ) + ((int) (global::haxe.lang.Runtime.getField_f(p, "len", 5393365, true)) ) );
				}
				
			}
			while (this.isGlobal);
			if (( (  ! (this.isGlobal)  && ( offset > 0 ) ) && ( offset < s.Length ) )) {
				buf.@add<string>(((string) (global::haxe.lang.StringExt.substr(s, offset, default(global::haxe.lang.Null<int>))) ));
			}
			
			return buf.toString();
		}
	}
	
	
	public override object __hx_setField(string field, int hash, object @value, bool handleProperties) {
		unchecked {
			switch (hash) {
				case 4949376:
				{
					this.cur = global::haxe.lang.Runtime.toString(@value);
					return @value;
				}
				
				
				case 1821933:
				{
					this.isGlobal = global::haxe.lang.Runtime.toBool(@value);
					return @value;
				}
				
				
				case 109:
				{
					this.m = ((global::System.Text.RegularExpressions.Match) (@value) );
					return @value;
				}
				
				
				case 1723805383:
				{
					this.regex = ((global::System.Text.RegularExpressions.Regex) (@value) );
					return @value;
				}
				
				
				default:
				{
					return base.__hx_setField(field, hash, @value, handleProperties);
				}
				
			}
			
		}
	}
	
	
	public override object __hx_getField(string field, int hash, bool throwErrors, bool isCheck, bool handleProperties) {
		unchecked {
			switch (hash) {
				case 5442204:
				{
					return ((global::haxe.lang.Function) (new global::haxe.lang.Closure(this, "map", 5442204)) );
				}
				
				
				case 724060212:
				{
					return ((global::haxe.lang.Function) (new global::haxe.lang.Closure(this, "replace", 724060212)) );
				}
				
				
				case 5393365:
				{
					return ((global::haxe.lang.Function) (new global::haxe.lang.Closure(this, "len", 5393365)) );
				}
				
				
				case 67859554:
				{
					return ((global::haxe.lang.Function) (new global::haxe.lang.Closure(this, "start", 67859554)) );
				}
				
				
				case 24046298:
				{
					return ((global::haxe.lang.Function) (new global::haxe.lang.Closure(this, "split", 24046298)) );
				}
				
				
				case 1126920507:
				{
					return ((global::haxe.lang.Function) (new global::haxe.lang.Closure(this, "matchSub", 1126920507)) );
				}
				
				
				case 1271070480:
				{
					return ((global::haxe.lang.Function) (new global::haxe.lang.Closure(this, "matchedPos", 1271070480)) );
				}
				
				
				case 614073432:
				{
					return ((global::haxe.lang.Function) (new global::haxe.lang.Closure(this, "matchedRight", 614073432)) );
				}
				
				
				case 2083500811:
				{
					return ((global::haxe.lang.Function) (new global::haxe.lang.Closure(this, "matchedLeft", 2083500811)) );
				}
				
				
				case 159136996:
				{
					return ((global::haxe.lang.Function) (new global::haxe.lang.Closure(this, "matched", 159136996)) );
				}
				
				
				case 52644165:
				{
					return ((global::haxe.lang.Function) (new global::haxe.lang.Closure(this, "match", 52644165)) );
				}
				
				
				case 4949376:
				{
					return this.cur;
				}
				
				
				case 1821933:
				{
					return this.isGlobal;
				}
				
				
				case 109:
				{
					return this.m;
				}
				
				
				case 1723805383:
				{
					return this.regex;
				}
				
				
				default:
				{
					return base.__hx_getField(field, hash, throwErrors, isCheck, handleProperties);
				}
				
			}
			
		}
	}
	
	
	public override object __hx_invokeField(string field, int hash, object[] dynargs) {
		unchecked {
			switch (hash) {
				case 5442204:
				{
					return this.map(global::haxe.lang.Runtime.toString(dynargs[0]), ((global::haxe.lang.Function) (dynargs[1]) ));
				}
				
				
				case 724060212:
				{
					return this.replace(global::haxe.lang.Runtime.toString(dynargs[0]), global::haxe.lang.Runtime.toString(dynargs[1]));
				}
				
				
				case 5393365:
				{
					return this.len(((int) (global::haxe.lang.Runtime.toInt(dynargs[0])) ));
				}
				
				
				case 67859554:
				{
					return this.start(((int) (global::haxe.lang.Runtime.toInt(dynargs[0])) ));
				}
				
				
				case 24046298:
				{
					return this.split(global::haxe.lang.Runtime.toString(dynargs[0]));
				}
				
				
				case 1126920507:
				{
					return this.matchSub(global::haxe.lang.Runtime.toString(dynargs[0]), ((int) (global::haxe.lang.Runtime.toInt(dynargs[1])) ), global::haxe.lang.Null<object>.ofDynamic<int>(( (( dynargs.Length > 2 )) ? (dynargs[2]) : (null) )));
				}
				
				
				case 1271070480:
				{
					return this.matchedPos();
				}
				
				
				case 614073432:
				{
					return this.matchedRight();
				}
				
				
				case 2083500811:
				{
					return this.matchedLeft();
				}
				
				
				case 159136996:
				{
					return this.matched(((int) (global::haxe.lang.Runtime.toInt(dynargs[0])) ));
				}
				
				
				case 52644165:
				{
					return this.match(global::haxe.lang.Runtime.toString(dynargs[0]));
				}
				
				
				default:
				{
					return base.__hx_invokeField(field, hash, dynargs);
				}
				
			}
			
		}
	}
	
	
	public override void __hx_getFields(global::Array<string> baseArr) {
		baseArr.push("cur");
		baseArr.push("isGlobal");
		baseArr.push("m");
		baseArr.push("regex");
		base.__hx_getFields(baseArr);
	}
	
	
}


// Generated by Haxe 4.3.7

#pragma warning disable 109, 114, 219, 429, 168, 162
public class Lambda : global::haxe.lang.HxObject {
	
	public Lambda(global::haxe.lang.EmptyObject empty) {
	}
	
	
	public Lambda() {
		global::Lambda.__hx_ctor__Lambda(this);
	}
	
	
	protected static void __hx_ctor__Lambda(global::Lambda __hx_this) {
	}
	
	
	public static global::Array<A> array<A>(object it) {
		global::Array<A> a = new global::Array<A>();
		{
			object i = ((object) (global::haxe.lang.Runtime.callField(it, "iterator", 328878574, null)) );
			while (global::haxe.lang.Runtime.toBool(global::haxe.lang.Runtime.callField(i, "hasNext", 407283053, null))) {
				A i1 = global::haxe.lang.Runtime.genericCast<A>(global::haxe.lang.Runtime.callField(i, "next", 1224901875, null));
				a.push(i1);
			}
			
		}
		
		return a;
	}
	
	
	public static global::haxe.ds.List<A> list<A>(object it) {
		global::haxe.ds.List<A> l = new global::haxe.ds.List<A>();
		{
			object i = ((object) (global::haxe.lang.Runtime.callField(it, "iterator", 328878574, null)) );
			while (global::haxe.lang.Runtime.toBool(global::haxe.lang.Runtime.callField(i, "hasNext", 407283053, null))) {
				A i1 = global::haxe.lang.Runtime.genericCast<A>(global::haxe.lang.Runtime.callField(i, "next", 1224901875, null));
				l.@add(i1);
			}
			
		}
		
		return l;
	}
	
	
	public static global::Array<B> map<A, B>(object it, global::haxe.lang.Function f) {
		global::Array<B> _g = new global::Array<B>(new B[]{});
		{
			object x = ((object) (global::haxe.lang.Runtime.callField(it, "iterator", 328878574, null)) );
			while (global::haxe.lang.Runtime.toBool(global::haxe.lang.Runtime.callField(x, "hasNext", 407283053, null))) {
				A x1 = global::haxe.lang.Runtime.genericCast<A>(global::haxe.lang.Runtime.callField(x, "next", 1224901875, null));
				_g.push(global::haxe.lang.Runtime.genericCast<B>(f.__hx_invoke1_o(default(double), x1)));
			}
			
		}
		
		return _g;
	}
	
	
	public static global::Array<B> mapi<A, B>(object it, global::haxe.lang.Function f) {
		int i = 0;
		global::Array<B> _g = new global::Array<B>(new B[]{});
		{
			object x = ((object) (global::haxe.lang.Runtime.callField(it, "iterator", 328878574, null)) );
			while (global::haxe.lang.Runtime.toBool(global::haxe.lang.Runtime.callField(x, "hasNext", 407283053, null))) {
				A x1 = global::haxe.lang.Runtime.genericCast<A>(global::haxe.lang.Runtime.callField(x, "next", 1224901875, null));
				_g.push(global::haxe.lang.Runtime.genericCast<B>(f.__hx_invoke2_o(((double) (i++) ), global::haxe.lang.Runtime.undefined, default(double), x1)));
			}
			
		}
		
		return _g;
	}
	
	
	public static global::Array<A> flatten<A>(object it) {
		global::Array<A> _g = new global::Array<A>(new A[]{});
		{
			object e = ((object) (global::haxe.lang.Runtime.callField(it, "iterator", 328878574, null)) );
			while (global::haxe.lang.Runtime.toBool(global::haxe.lang.Runtime.callField(e, "hasNext", 407283053, null))) {
				object e1 = ((object) (global::haxe.lang.Runtime.callField(e, "next", 1224901875, null)) );
				{
					object x = ((object) (global::haxe.lang.Runtime.callField(e1, "iterator", 328878574, null)) );
					while (global::haxe.lang.Runtime.toBool(global::haxe.lang.Runtime.callField(x, "hasNext", 407283053, null))) {
						A x1 = global::haxe.lang.Runtime.genericCast<A>(global::haxe.lang.Runtime.callField(x, "next", 1224901875, null));
						_g.push(x1);
					}
					
				}
				
			}
			
		}
		
		return _g;
	}
	
	
	public static global::Array<B> flatMap<A, B>(object it, global::haxe.lang.Function f) {
		return global::Lambda.flatten<B>(((object) (global::Lambda.map<A, object>(((object) (it) ), ((global::haxe.lang.Function) (f) ))) ));
	}
	
	
	public static bool has<A>(object it, A elt) {
		{
			object x = ((object) (global::haxe.lang.Runtime.callField(it, "iterator", 328878574, null)) );
			while (global::haxe.lang.Runtime.toBool(global::haxe.lang.Runtime.callField(x, "hasNext", 407283053, null))) {
				A x1 = global::haxe.lang.Runtime.genericCast<A>(global::haxe.lang.Runtime.callField(x, "next", 1224901875, null));
				if (global::haxe.lang.Runtime.eq(x1, elt)) {
					return true;
				}
				
			}
			
		}
		
		return false;
	}
	
	
	public static bool exists<A>(object it, global::haxe.lang.Function f) {
		{
			object x = ((object) (global::haxe.lang.Runtime.callField(it, "iterator", 328878574, null)) );
			while (global::haxe.lang.Runtime.toBool(global::haxe.lang.Runtime.callField(x, "hasNext", 407283053, null))) {
				A x1 = global::haxe.lang.Runtime.genericCast<A>(global::haxe.lang.Runtime.callField(x, "next", 1224901875, null));
				if (global::haxe.lang.Runtime.toBool(f.__hx_invoke1_o(default(double), x1))) {
					return true;
				}
				
			}
			
		}
		
		return false;
	}
	
	
	public static bool @foreach<A>(object it, global::haxe.lang.Function f) {
		{
			object x = ((object) (global::haxe.lang.Runtime.callField(it, "iterator", 328878574, null)) );
			while (global::haxe.lang.Runtime.toBool(global::haxe.lang.Runtime.callField(x, "hasNext", 407283053, null))) {
				A x1 = global::haxe.lang.Runtime.genericCast<A>(global::haxe.lang.Runtime.callField(x, "next", 1224901875, null));
				if ( ! (global::haxe.lang.Runtime.toBool(f.__hx_invoke1_o(default(double), x1))) ) {
					return false;
				}
				
			}
			
		}
		
		return true;
	}
	
	
	public static void iter<A>(object it, global::haxe.lang.Function f) {
		object x = ((object) (global::haxe.lang.Runtime.callField(it, "iterator", 328878574, null)) );
		while (global::haxe.lang.Runtime.toBool(global::haxe.lang.Runtime.callField(x, "hasNext", 407283053, null))) {
			A x1 = global::haxe.lang.Runtime.genericCast<A>(global::haxe.lang.Runtime.callField(x, "next", 1224901875, null));
			f.__hx_invoke1_o(default(double), x1);
		}
		
	}
	
	
	public static global::Array<A> filter<A>(object it, global::haxe.lang.Function f) {
		global::Array<A> _g = new global::Array<A>(new A[]{});
		{
			object x = ((object) (global::haxe.lang.Runtime.callField(it, "iterator", 328878574, null)) );
			while (global::haxe.lang.Runtime.toBool(global::haxe.lang.Runtime.callField(x, "hasNext", 407283053, null))) {
				A x1 = global::haxe.lang.Runtime.genericCast<A>(global::haxe.lang.Runtime.callField(x, "next", 1224901875, null));
				if (global::haxe.lang.Runtime.toBool(f.__hx_invoke1_o(default(double), x1))) {
					_g.push(x1);
				}
				
			}
			
		}
		
		return _g;
	}
	
	
	public static B fold<A, B>(object it, global::haxe.lang.Function f, B first) {
		{
			object x = ((object) (global::haxe.lang.Runtime.callField(it, "iterator", 328878574, null)) );
			while (global::haxe.lang.Runtime.toBool(global::haxe.lang.Runtime.callField(x, "hasNext", 407283053, null))) {
				A x1 = global::haxe.lang.Runtime.genericCast<A>(global::haxe.lang.Runtime.callField(x, "next", 1224901875, null));
				first = global::haxe.lang.Runtime.genericCast<B>(f.__hx_invoke2_o(default(double), x1, default(double), first));
			}
			
		}
		
		return first;
	}
	
	
	public static B foldi<A, B>(object it, global::haxe.lang.Function f, B first) {
		int i = 0;
		{
			object x = ((object) (global::haxe.lang.Runtime.callField(it, "iterator", 328878574, null)) );
			while (global::haxe.lang.Runtime.toBool(global::haxe.lang.Runtime.callField(x, "hasNext", 407283053, null))) {
				A x1 = global::haxe.lang.Runtime.genericCast<A>(global::haxe.lang.Runtime.callField(x, "next", 1224901875, null));
				first = global::haxe.lang.Runtime.genericCast<B>(f.__hx_invoke3_o(default(double), x1, default(double), first, ((double) (i) ), global::haxe.lang.Runtime.undefined));
				 ++ i;
			}
			
		}
		
		return first;
	}
	
	
	public static int count<A>(object it, global::haxe.lang.Function pred) {
		int n = 0;
		if (( pred == null )) {
			object _ = ((object) (global::haxe.lang.Runtime.callField(it, "iterator", 328878574, null)) );
			while (global::haxe.lang.Runtime.toBool(global::haxe.lang.Runtime.callField(_, "hasNext", 407283053, null))) {
				A _1 = global::haxe.lang.Runtime.genericCast<A>(global::haxe.lang.Runtime.callField(_, "next", 1224901875, null));
				 ++ n;
			}
			
		}
		else {
			object x = ((object) (global::haxe.lang.Runtime.callField(it, "iterator", 328878574, null)) );
			while (global::haxe.lang.Runtime.toBool(global::haxe.lang.Runtime.callField(x, "hasNext", 407283053, null))) {
				A x1 = global::haxe.lang.Runtime.genericCast<A>(global::haxe.lang.Runtime.callField(x, "next", 1224901875, null));
				if (global::haxe.lang.Runtime.toBool(((global::haxe.lang.Function) (pred) ).__hx_invoke1_o(default(double), x1))) {
					 ++ n;
				}
				
			}
			
		}
		
		return n;
	}
	
	
	public static bool empty<T>(object it) {
		return  ! (global::haxe.lang.Runtime.toBool(global::haxe.lang.Runtime.callField(((object) (global::haxe.lang.Runtime.callField(it, "iterator", 328878574, null)) ), "hasNext", 407283053, null))) ;
	}
	
	
	public static int indexOf<T>(object it, T v) {
		unchecked {
			int i = 0;
			{
				object v2 = ((object) (global::haxe.lang.Runtime.callField(it, "iterator", 328878574, null)) );
				while (global::haxe.lang.Runtime.toBool(global::haxe.lang.Runtime.callField(v2, "hasNext", 407283053, null))) {
					T v21 = global::haxe.lang.Runtime.genericCast<T>(global::haxe.lang.Runtime.callField(v2, "next", 1224901875, null));
					if (global::haxe.lang.Runtime.eq(v, v21)) {
						return i;
					}
					
					 ++ i;
				}
				
			}
			
			return -1;
		}
	}
	
	
	public static global::haxe.lang.Null<T> find<T>(object it, global::haxe.lang.Function f) {
		{
			object v = ((object) (global::haxe.lang.Runtime.callField(it, "iterator", 328878574, null)) );
			while (global::haxe.lang.Runtime.toBool(global::haxe.lang.Runtime.callField(v, "hasNext", 407283053, null))) {
				T v1 = global::haxe.lang.Runtime.genericCast<T>(global::haxe.lang.Runtime.callField(v, "next", 1224901875, null));
				if (global::haxe.lang.Runtime.toBool(f.__hx_invoke1_o(default(double), v1))) {
					return new global::haxe.lang.Null<T>(v1, true);
				}
				
			}
			
		}
		
		return default(global::haxe.lang.Null<T>);
	}
	
	
	public static int findIndex<T>(object it, global::haxe.lang.Function f) {
		unchecked {
			int i = 0;
			{
				object v = ((object) (global::haxe.lang.Runtime.callField(it, "iterator", 328878574, null)) );
				while (global::haxe.lang.Runtime.toBool(global::haxe.lang.Runtime.callField(v, "hasNext", 407283053, null))) {
					T v1 = global::haxe.lang.Runtime.genericCast<T>(global::haxe.lang.Runtime.callField(v, "next", 1224901875, null));
					if (global::haxe.lang.Runtime.toBool(f.__hx_invoke1_o(default(double), v1))) {
						return i;
					}
					
					 ++ i;
				}
				
			}
			
			return -1;
		}
	}
	
	
	public static global::Array<T> concat<T>(object a, object b) {
		global::Array<T> l = new global::Array<T>();
		{
			object x = ((object) (global::haxe.lang.Runtime.callField(a, "iterator", 328878574, null)) );
			while (global::haxe.lang.Runtime.toBool(global::haxe.lang.Runtime.callField(x, "hasNext", 407283053, null))) {
				T x1 = global::haxe.lang.Runtime.genericCast<T>(global::haxe.lang.Runtime.callField(x, "next", 1224901875, null));
				l.push(x1);
			}
			
		}
		
		{
			object x2 = ((object) (global::haxe.lang.Runtime.callField(b, "iterator", 328878574, null)) );
			while (global::haxe.lang.Runtime.toBool(global::haxe.lang.Runtime.callField(x2, "hasNext", 407283053, null))) {
				T x3 = global::haxe.lang.Runtime.genericCast<T>(global::haxe.lang.Runtime.callField(x2, "next", 1224901875, null));
				l.push(x3);
			}
			
		}
		
		return l;
	}
	
	
}


// Generated by Haxe 4.3.7

#pragma warning disable 109, 114, 219, 429, 168, 162
namespace haxe {
	public class SysTools : global::haxe.lang.HxObject {
		
		static SysTools() {
			unchecked{
				global::haxe.SysTools.winMetaCharacters = ((global::Array<int>) (new global::Array<int>(new int[]{32, 40, 41, 37, 33, 94, 34, 60, 62, 38, 124, 10, 13, 44, 59})) );
			}
		}
		
		
		public SysTools(global::haxe.lang.EmptyObject empty) {
		}
		
		
		public SysTools() {
			global::haxe.SysTools.__hx_ctor_haxe_SysTools(this);
		}
		
		
		protected static void __hx_ctor_haxe_SysTools(global::haxe.SysTools __hx_this) {
		}
		
		
		public static global::Array<int> winMetaCharacters;
		
		public static string quoteUnixArg(string argument) {
			if (( argument == "" )) {
				return "\'\'";
			}
			
			if ( ! (new global::EReg(((string) ("[^a-zA-Z0-9_@%+=:,./-]") ), ((string) ("") )).match(argument)) ) {
				return argument;
			}
			
			return global::haxe.lang.Runtime.concat(global::haxe.lang.Runtime.concat("\'", global::StringTools.replace(argument, "\'", "\'\"\'\"\'")), "\'");
		}
		
		
		public static string quoteWinArg(string argument, bool escapeMetaCharacters) {
			unchecked {
				if ( ! (new global::EReg(((string) ("^(/)?[^ \t/\\\\\"]+$") ), ((string) ("") )).match(argument)) ) {
					global::StringBuf result = new global::StringBuf();
					bool needquote = ( ( ( ( global::haxe.lang.StringExt.indexOf(argument, " ", default(global::haxe.lang.Null<int>)) != -1 ) || ( global::haxe.lang.StringExt.indexOf(argument, "\t", default(global::haxe.lang.Null<int>)) != -1 ) ) || ( argument == "" ) ) || ( global::haxe.lang.StringExt.indexOf(argument, "/", default(global::haxe.lang.Null<int>)) > 0 ) );
					if (needquote) {
						result.@add<string>(((string) ("\"") ));
					}
					
					global::StringBuf bs_buf = new global::StringBuf();
					{
						int _g = 0;
						int _g1 = argument.Length;
						while (( _g < _g1 )) {
							int i = _g++;
							{
								global::haxe.lang.Null<int> _g2 = global::haxe.lang.StringExt.charCodeAt(argument, i);
								if ( ! (_g2.hasValue) ) {
									global::haxe.lang.Null<int> c = _g2;
									{
										if (( bs_buf.get_length() > 0 )) {
											result.@add<string>(((string) (bs_buf.toString()) ));
											bs_buf = new global::StringBuf();
										}
										
										result.addChar((c).@value);
									}
									
								}
								else {
									switch (((_g2)).@value) {
										case 34:
										{
											string bs = bs_buf.toString();
											result.@add<string>(((string) (bs) ));
											result.@add<string>(((string) (bs) ));
											bs_buf = new global::StringBuf();
											result.@add<string>(((string) ("\\\"") ));
											break;
										}
										
										
										case 92:
										{
											bs_buf.@add<string>(((string) ("\\") ));
											break;
										}
										
										
										default:
										{
											global::haxe.lang.Null<int> c1 = _g2;
											{
												if (( bs_buf.get_length() > 0 )) {
													result.@add<string>(((string) (bs_buf.toString()) ));
													bs_buf = new global::StringBuf();
												}
												
												result.addChar((c1).@value);
											}
											
											break;
										}
										
									}
									
								}
								
							}
							
						}
						
					}
					
					result.@add<string>(((string) (bs_buf.toString()) ));
					if (needquote) {
						result.@add<string>(((string) (bs_buf.toString()) ));
						result.@add<string>(((string) ("\"") ));
					}
					
					argument = result.toString();
				}
				
				if (escapeMetaCharacters) {
					global::StringBuf result1 = new global::StringBuf();
					{
						int _g3 = 0;
						int _g4 = argument.Length;
						while (( _g3 < _g4 )) {
							int i1 = _g3++;
							global::haxe.lang.Null<int> c2 = global::haxe.lang.StringExt.charCodeAt(argument, i1);
							if (( global::haxe.SysTools.winMetaCharacters.indexOf((c2).@value, default(global::haxe.lang.Null<int>)) >= 0 )) {
								result1.addChar(94);
							}
							
							result1.addChar((c2).@value);
						}
						
					}
					
					return result1.toString();
				}
				else {
					return argument;
				}
				
			}
		}
		
		
	}
}


// Generated by Haxe 4.3.7

#pragma warning disable 109, 114, 219, 429, 168, 162
namespace haxe.io {
	public class Error : global::haxe.lang.Enum {
		
		protected Error(int index) : base(index) {
		}
		
		
		public static readonly global::haxe.io.Error Blocked = new global::haxe.io.Error_Blocked();
		
		public static readonly global::haxe.io.Error Overflow = new global::haxe.io.Error_Overflow();
		
		public static readonly global::haxe.io.Error OutsideBounds = new global::haxe.io.Error_OutsideBounds();
		
		public static global::haxe.io.Error Custom(object e) {
			return new global::haxe.io.Error_Custom(e);
		}
		
		
		protected static readonly string[] __hx_constructs = new string[]{"Blocked", "Overflow", "OutsideBounds", "Custom"};
		
	}
}



#pragma warning disable 109, 114, 219, 429, 168, 162
namespace haxe.io {
	public sealed class Error_Blocked : global::haxe.io.Error {
		
		public Error_Blocked() : base(0) {
		}
		
		
		public override string getTag() {
			return "Blocked";
		}
		
		
	}
}



#pragma warning disable 109, 114, 219, 429, 168, 162
namespace haxe.io {
	public sealed class Error_Overflow : global::haxe.io.Error {
		
		public Error_Overflow() : base(1) {
		}
		
		
		public override string getTag() {
			return "Overflow";
		}
		
		
	}
}



#pragma warning disable 109, 114, 219, 429, 168, 162
namespace haxe.io {
	public sealed class Error_OutsideBounds : global::haxe.io.Error {
		
		public Error_OutsideBounds() : base(2) {
		}
		
		
		public override string getTag() {
			return "OutsideBounds";
		}
		
		
	}
}



#pragma warning disable 109, 114, 219, 429, 168, 162
namespace haxe.io {
	public sealed class Error_Custom : global::haxe.io.Error {
		
		public Error_Custom(object e) : base(3) {
			this.e = e;
		}
		
		
		public override global::Array<object> getParams() {
			return new global::Array<object>(new object[]{this.e});
		}
		
		
		public override string getTag() {
			return "Custom";
		}
		
		
		public override int GetHashCode() {
			unchecked {
				return global::haxe.lang.Enum.paramsGetHashCode(3, new object[]{this.e});
			}
		}
		
		
		public override bool Equals(object other) {
			if (global::System.Object.ReferenceEquals(((object) (this) ), ((object) (other) ))) {
				return true;
			}
			
			global::haxe.io.Error_Custom en = ( other as global::haxe.io.Error_Custom );
			if (( en == null )) {
				return false;
			}
			
			if ( ! (global::Type.enumEq<object>(((object) (this.e) ), ((object) (en.e) ))) ) {
				return false;
			}
			
			return true;
		}
		
		
		public override string toString() {
			return global::haxe.lang.Enum.paramsToString("Custom", new object[]{this.e});
		}
		
		
		public readonly object e;
		
	}
}


// Generated by Haxe 4.3.7

#pragma warning disable 109, 114, 219, 429, 168, 162
namespace haxe.iterators {
	public class StringIteratorUnicode : global::haxe.lang.HxObject {
		
		public StringIteratorUnicode(global::haxe.lang.EmptyObject empty) {
		}
		
		
		public StringIteratorUnicode(string s) {
			global::haxe.iterators.StringIteratorUnicode.__hx_ctor_haxe_iterators_StringIteratorUnicode(this, s);
		}
		
		
		protected static void __hx_ctor_haxe_iterators_StringIteratorUnicode(global::haxe.iterators.StringIteratorUnicode __hx_this, string s) {
			__hx_this.offset = 0;
			{
				__hx_this.s = s;
			}
			
		}
		
		
		public static global::haxe.iterators.StringIteratorUnicode unicodeIterator(string s) {
			return new global::haxe.iterators.StringIteratorUnicode(((string) (s) ));
		}
		
		
		public int offset;
		
		public string s;
		
		public bool hasNext() {
			return ( this.offset < this.s.Length );
		}
		
		
		public int next() {
			unchecked {
				int c = global::StringTools.utf16CodePointAt(this.s, this.offset++);
				if (( c >= 65536 )) {
					this.offset++;
				}
				
				return c;
			}
		}
		
		
		public override double __hx_setField_f(string field, int hash, double @value, bool handleProperties) {
			unchecked {
				switch (hash) {
					case 1614780307:
					{
						this.offset = ((int) (@value) );
						return @value;
					}
					
					
					default:
					{
						return base.__hx_setField_f(field, hash, @value, handleProperties);
					}
					
				}
				
			}
		}
		
		
		public override object __hx_setField(string field, int hash, object @value, bool handleProperties) {
			unchecked {
				switch (hash) {
					case 115:
					{
						this.s = global::haxe.lang.Runtime.toString(@value);
						return @value;
					}
					
					
					case 1614780307:
					{
						this.offset = ((int) (global::haxe.lang.Runtime.toInt(@value)) );
						return @value;
					}
					
					
					default:
					{
						return base.__hx_setField(field, hash, @value, handleProperties);
					}
					
				}
				
			}
		}
		
		
		public override object __hx_getField(string field, int hash, bool throwErrors, bool isCheck, bool handleProperties) {
			unchecked {
				switch (hash) {
					case 1224901875:
					{
						return ((global::haxe.lang.Function) (new global::haxe.lang.Closure(this, "next", 1224901875)) );
					}
					
					
					case 407283053:
					{
						return ((global::haxe.lang.Function) (new global::haxe.lang.Closure(this, "hasNext", 407283053)) );
					}
					
					
					case 115:
					{
						return this.s;
					}
					
					
					case 1614780307:
					{
						return this.offset;
					}
					
					
					default:
					{
						return base.__hx_getField(field, hash, throwErrors, isCheck, handleProperties);
					}
					
				}
				
			}
		}
		
		
		public override double __hx_getField_f(string field, int hash, bool throwErrors, bool handleProperties) {
			unchecked {
				switch (hash) {
					case 1614780307:
					{
						return ((double) (this.offset) );
					}
					
					
					default:
					{
						return base.__hx_getField_f(field, hash, throwErrors, handleProperties);
					}
					
				}
				
			}
		}
		
		
		public override object __hx_invokeField(string field, int hash, object[] dynargs) {
			unchecked {
				switch (hash) {
					case 1224901875:
					{
						return this.next();
					}
					
					
					case 407283053:
					{
						return this.hasNext();
					}
					
					
					default:
					{
						return base.__hx_invokeField(field, hash, dynargs);
					}
					
				}
				
			}
		}
		
		
		public override void __hx_getFields(global::Array<string> baseArr) {
			baseArr.push("s");
			baseArr.push("offset");
			base.__hx_getFields(baseArr);
		}
		
		
	}
}


// Generated by Haxe 4.3.7

#pragma warning disable 109, 114, 219, 429, 168, 162
namespace sys.thread {
	public class NoEventLoopException : global::haxe.Exception {
		
		public NoEventLoopException(global::haxe.lang.EmptyObject empty) : base(global::haxe.lang.EmptyObject.EMPTY) {
		}
		
		
		public NoEventLoopException(string msg, global::haxe.Exception previous) : base(((string) (( (( msg == null )) ? ("Event loop is not available. Refer to sys.thread.Thread.runWithEventLoop.") : (msg) )) ), ((global::haxe.Exception) (( (( previous == null )) ? (null) : (previous) )) ), default(object)) {
			{
				if (( msg == null )) {
					msg = "Event loop is not available. Refer to sys.thread.Thread.runWithEventLoop.";
				}
				
			}
			
			this.__shiftStack();
		}
		
		
	}
}


global using static Sandbox.Internal.GlobalGameNamespace;
global using Microsoft.AspNetCore.Components;
global using Microsoft.AspNetCore.Components.Rendering;
[assembly: global::System.Reflection.AssemblyMetadata( "AddonTitle", "Reactivity" )]
[assembly: global::System.Reflection.AssemblyMetadata( "AddonIdent", "reactivity" )]
[assembly: global::System.Reflection.AssemblyMetadata( "OrgIdent", "igor" )]
[assembly: global::System.Reflection.AssemblyMetadata( "Ident", "igor.reactivity" )]
[assembly: global::System.Reflection.AssemblyMetadata( "CompileTime", "2026-03-29 03:35:54" )]
[assembly: global::System.Reflection.AssemblyMetadata( "EngineVersion", "25" )]
[assembly: global::System.Reflection.AssemblyMetadata( "EngineMinorVersion", "1" )]

[assembly: System.Runtime.Versioning.TargetFramework( ".NETCoreApp,Version=v9.0", FrameworkDisplayName = ".NET 9.0" )]
[assembly: global::System.Reflection.AssemblyVersion("0.0.110.0")]
[assembly: global::System.Reflection.AssemblyFileVersion("0.0.110.0")]
#if JETBRAINS_ANNOTATIONS
#endif

namespace Sandbox.Reactivity;

/// <summary>
/// An object that contains a reactive value. Reading the value inside an effect will cause it to re-run when it
/// changes.
/// </summary>
/// <typeparam name="T">The type of value this object contains.</typeparam>
/// <remarks>
/// This can be used to abstract over a <see cref="State{T}" /> or <see cref="Derived{T}" /> as needed.
/// </remarks>
#if JETBRAINS_ANNOTATIONS
#endif
public interface IState<T>
{
	/// <summary>
	/// The current value.
	/// </summary>
	T Value { get; set; }
}

/// <inheritdoc cref="IState{T}" />
#if JETBRAINS_ANNOTATIONS
#endif
public interface IReadOnlyState<out T>
{
	/// <inheritdoc cref="IState{T}.Value" />
	T Value { get; }
}
#if SANDBOX
namespace Sandbox.Reactivity.Internals;

/// <summary>
/// Maintains a list of types that are assignable to the given type.
/// </summary>
internal static class TypeHierarchy<T>
{
	/// <summary>
	/// All types that are assignable to <typeparamref name="T"/>.
	/// </summary>
	[SkipHotload]
	// ReSharper disable once StaticMemberInGenericType
	public static readonly IEnumerable<Type> Types;

	static TypeHierarchy()
	{
		// since this is most likely going to be used for simple event types, we're going to assume that the hierarchy
		// won't be very large and that checking a list would be faster than hashing for a set
		var next = typeof(T);
		var hierarchy = new List<Type>();

		while (next != null)
		{
			hierarchy.Add(next);

			foreach (var type in next.GetInterfaces())
			{
				if (!hierarchy.Contains(type))
				{
					hierarchy.Add(type);
				}
			}

			next = next.BaseType;

			if (next == typeof(object))
			{
				break;
			}
		}

		Types = hierarchy;
	}
}
#endif
#if SANDBOX
using Sandbox.Reactivity.Internals;
#if JETBRAINS_ANNOTATIONS
using JetBrains.Annotations;
#endif

// we can't wrap the BuildRenderTree method for razor components, so we need something that can set up the proper
// scope inside the markup itself

namespace Sandbox.Reactivity;

/// <summary>
/// A disposable that's used to enable reactivity for a <see cref="ReactivePanelComponent" /> or
/// <see cref="ReactivePanel" /> during rendering.
/// </summary>
#if JETBRAINS_ANNOTATIONS
[PublicAPI]
#endif
public readonly ref struct ReactivePanelScope : IDisposable
{
	private readonly Effect.ExecutionScope _executionScope;

	internal ReactivePanelScope(IReactivePanel panel)
	{
		if (panel.RenderEffectRoot is { } previousRoot)
		{
			// don't teardown previous root since we're already building the render tree by this point
			previousRoot.Dispose(false);
		}

		// nested panels don't render immediately when a containing panel's tree is rendering, so the parent is
		// always null anyway
		var effectRoot = new Effect(null, null, true, () => panel.Version++);
		effectRoot.SetDebugInfo(panel.GetType().ToSimpleString(false) + " (Render)",
			panel is ReactivePanel ? "view_quilt" : "monitor",
			new CallLocation(2),
			panel is ReactivePanel reactive ? reactive.GameObject?.GetComponent<IReactivePanel>() : panel);

		panel.RenderEffectRoot = effectRoot;
		_executionScope = new Effect.ExecutionScope(effectRoot);
	}

	public void Dispose()
	{
		_executionScope.Dispose();
	}
}
#endif
#if SANDBOX
using System.Diagnostics;
using Sandbox.Reactivity.Internals;
using Sandbox.UI;
using static Sandbox.Reactivity.Reactive;
#if JETBRAINS_ANNOTATIONS
using JetBrains.Annotations;
#endif

namespace Sandbox.Reactivity;

/// <summary>
/// The reactive counterpart to <see cref="Panel" /> that allows usage of reactive properties.
/// </summary>
/// <remarks>
/// Make sure you set up an effect root using <see cref="PanelRoot" /> at the top of your razor markup:
/// <code>
/// @{ using var _ = PanelRoot(); }
/// </code>
/// Engine limitations prevent this from being done automatically.
/// </remarks>
#if JETBRAINS_ANNOTATIONS
[PublicAPI]
#endif
public class ReactivePanel : Panel, IReactivePropertyContainer, IReactivePanel
{
	private Effect? _effectRoot;

	private Effect? _renderEffectRoot;

	private int _version;

	public ReactivePanel()
	{
		var parent = Runtime.CurrentEffect;

		_effectRoot = new Effect([StackTraceHidden] [DebuggerStepThrough]() =>
			{
				OnActivate();
				return null;
			},
			parent,
			false);

		_effectRoot.SetDebugInfo(DisplayInfo.For(this).Name,
			DisplayInfo.For(this).Icon,
			new CallLocation(GetType(), nameof(OnActivate)),
			parent ?? (object?)this);

		_effectRoot.Run();
	}

	Effect? IReactivePanel.RenderEffectRoot
	{
		get => _renderEffectRoot;
		set => _renderEffectRoot = value;
	}

	int IReactivePanel.Version
	{
		get => _version;
		set => _version = value;
	}

	Dictionary<int, IProducer> IReactivePropertyContainer.Producers { get; } = [];

	protected ReactivePanelScope PanelRoot()
	{
		return new ReactivePanelScope(this);
	}

	public sealed override void Delete(bool immediate = false)
	{
		_renderEffectRoot?.Dispose();
		_renderEffectRoot = null;

		_effectRoot?.Dispose();
		_effectRoot = null;

		base.Delete(immediate);
	}

	protected sealed override int BuildHash()
	{
		return _version;
	}

	/// <summary>
	/// Called inside an effect root when this panel is instantiated, allowing for effects to be created. When this
	/// panel is deleted, the effect root (and all of its descendants) are disposed.
	/// </summary>
	protected virtual void OnActivate()
	{
	}
}
#endif
using System.Runtime.CompilerServices;

namespace Sandbox.Reactivity.Internals.Runtimes;

internal sealed class Runtime : IDisposable
{
	/// <summary>
	/// Effects that are waiting to run due to reactivity changes.
	/// </summary>
	private readonly Queue<Effect> _pendingEffects = new(16);

	/// <summary>
	/// How many effects have run during a flush operation.
	/// </summary>
	private uint _flushDepth;

	/// <summary>
	/// Whether pending effects are currently being run.
	/// </summary>
	private bool _isFlushing;

	/// <summary>
	/// The currently executing effect.
	/// </summary>
	public Effect? CurrentEffect { get; set; }

	/// <summary>
	/// The currently executing reaction.
	/// </summary>
	public IReaction? CurrentReaction { get; set; }

	/// <summary>
	/// A monotonically increasing counter that's incremented when an <see cref="IProducer" /> updates its current
	/// value.
	/// </summary>
	public uint Version { get; set; } = 1;

	/// <summary>
	/// Whether dependency tracking is currently disabled.
	/// </summary>
	public bool IsUntracking { get; set; }

	/// <summary>
	/// Whether a flush was scheduled to run at the end of the frame.
	/// </summary>
	public bool IsFlushScheduled { get; private set; }

	/// <summary>
	/// Whether an effect is currently executing its teardown function. This is used by producers to skip any
	/// recomputation when accessed to ensure the previous value is returned.
	/// </summary>
	public bool IsRunningTeardown { get; set; }

	public void Dispose()
	{
		_pendingEffects.Clear();
		CurrentEffect = null;
		CurrentReaction = null;
		Version = uint.MaxValue;
		IsUntracking = true;
		IsFlushScheduled = false;
		_isFlushing = false;
	}

	/// <summary>
	/// Returns the currently executing effect.
	/// </summary>
	/// <exception cref="InvalidOperationException">
	/// Thrown if there is no effect that is currently executing.
	/// </exception>
	public Effect EnsureCurrentEffect([CallerMemberName] string name = "Effect")
	{
		return CurrentEffect ?? throw new InvalidOperationException(name + " must be created in an effect root");
	}

	public void ScheduleEffect(Effect effect)
	{
		_pendingEffects.Enqueue(effect);

		if (!IsFlushScheduled && !_isFlushing)
		{
			IsFlushScheduled = true;
		}
	}

	/// <summary>
	/// Empties the queue of effects that are scheduled to run due to one of their dependencies changing. This should
	/// only be run when you want an effect to re-run immediately after changing a reactive value.
	/// </summary>
	public void Flush()
	{
		if (_isFlushing)
		{
			return;
		}

		_isFlushing = true;
		IsFlushScheduled = false;

		try
		{
			while (_pendingEffects.TryDequeue(out var effect))
			{
				if (_flushDepth++ > 1000)
				{
					_pendingEffects.Clear();
					_pendingEffects.TrimExcess(16);

#if DEBUG
					var exception = new InfiniteLoopException(_effectExecutions);
					OnFlushInfiniteLoop?.Invoke(exception);

					throw exception;
#else
					throw new InfiniteLoopException();
#endif
				}

				if (effect.ShouldRun)
				{
					effect.Run();
#if DEBUG
					_effectExecutions[effect] = _effectExecutions.GetValueOrDefault(effect) + 1;
#endif
				}
			}
		}
		finally
		{
			_flushDepth = 0;
			_isFlushing = false;
#if DEBUG
			_effectExecutions.Clear();
#endif
		}
	}

#if DEBUG
	/// <summary>
	/// Which effects have executing during the current flush, and how many times they've executed.
	/// </summary>
	private readonly Dictionary<Effect, int> _effectExecutions = [];

	/// <summary>
	/// Called when an infinite loop occurred during a flush.
	/// </summary>
	public static event Action<InfiniteLoopException>? OnFlushInfiniteLoop;
#endif
}
using System.Threading.Tasks;
using SandboxModelContextProtocol.Server.Services.Models;

namespace SandboxModelContextProtocol.Server.Services.Interfaces;

public interface IResourceService
{
	Task<CallResourceResponse> GetResource( CallResourceRequest request );
	void HandleResponse( string message );
}
using System.Collections.Generic;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace SandboxModelContextProtocol.Server.Services.Models;

public class CallResourceResponse
{
	[JsonPropertyName( "type" )]
	public string Type { get; } = "resource";

	[JsonPropertyName( "id" )]
	public required string Id { get; set; }

	[JsonPropertyName( "name" )]
	public required string Name { get; set; }

	[JsonPropertyName( "content" )]
	public List<JsonElement> Content { get; set; } = [];

	[JsonPropertyName( "isError" )]
	public bool IsError { get; set; }
}
using System.Collections.Generic;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace SandboxModelContextProtocol.Server.Services.Models;

public class CallToolResponse
{
	[JsonIgnore]
	public string Type { get; set; } = "tool";

	[JsonPropertyName( "id" )]
	public required string Id { get; set; }

	[JsonPropertyName( "name" )]
	public required string Name { get; set; }

	[JsonPropertyName( "content" )]
	public List<JsonElement> Content { get; set; } = [];

	[JsonPropertyName( "isError" )]
	public bool IsError { get; set; }
}
global using static Sandbox.Internal.GlobalGameNamespace;
global using Microsoft.AspNetCore.Components;
global using Microsoft.AspNetCore.Components.Rendering;
[assembly: global::System.Reflection.AssemblyMetadata( "AddonTitle", "Teleport Trigger" )]
[assembly: global::System.Reflection.AssemblyMetadata( "AddonIdent", "teleport_trigger" )]
[assembly: global::System.Reflection.AssemblyMetadata( "OrgIdent", "kido" )]
[assembly: global::System.Reflection.AssemblyMetadata( "Ident", "kido.teleport_trigger" )]
[assembly: global::System.Reflection.AssemblyMetadata( "CompileTime", "4/9/2026 5:05:24 PM" )]
[assembly: global::System.Reflection.AssemblyMetadata( "EngineVersion", "25" )]
[assembly: global::System.Reflection.AssemblyMetadata( "EngineMinorVersion", "1" )]

[assembly: System.Runtime.Versioning.TargetFramework( ".NETCoreApp,Version=v9.0", FrameworkDisplayName = ".NET 9.0" )]
[assembly: global::System.Reflection.AssemblyVersion("0.0.128.0")]
[assembly: global::System.Reflection.AssemblyFileVersion("0.0.128.0")]
namespace Sandbox
{
	public class WirePortData
	{
		public bool inputsInitialized = false;
		public Dictionary<string, Action<object>> inputHandlers { get; } = [];
		public Dictionary<string, WireInput> inputs = [];
		public Dictionary<string, WireOutput> outputs = [];
	}

	public interface IWireComponent
	{
		public WirePortData WirePorts { get; }

		public virtual string GetOverlayText() { return ""; }

		public static object GetDefaultValueFromType( string type )
		{
			if ( type == "bool" )
				return false;
			else if ( type == "int" )
				return 0;
			else if ( type == "float" )
				return 0.0f;
			else if ( type == "string" )
				return "";
			else if ( type == "vector3" )
				return Vector3.Zero;
			else if ( type == "angle" )
				return Angles.Zero;
			else if ( type == "rotation" )
				return Rotation.Identity;
			else if ( type == "gameobject" )
			{
				return 0; // this... isn't great, but null's are worse (eg. TriggerOutput's `output.value.Equals( value )` check errors)
			}

			return false;
		}
	}
	public class BaseWireComponent : Component, IWireComponent
	{
		public WirePortData WirePorts { get; } = new();

		[Rpc.Broadcast( NetFlags.Reliable )]
		public void WireConnect( GameObject inputEnt, string outputName, string inputName )
		{
			((IWireOutputComponent)this)._WireConnect( inputEnt.GetComponent<IWireInputComponent>(), outputName, inputName );
		}
		[Rpc.Broadcast( NetFlags.Reliable )]
		public void DisconnectInput( string inputName, bool destroyRope = true )
		{
			((IWireInputComponent)this)._DisconnectInput( inputName, destroyRope );
		}
	}
}
using System;
using Sandbox;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace fxbox;

/// <summary>
/// Stage when a particle module executes
/// </summary>
public enum ModuleStage
{
    Spawn,      // Controls when/how particles spawn
    Initialize, // Runs once when particle is created
    Update,     // Runs every frame for each particle
    Render      // Controls how particles are rendered
}

/// <summary>
/// Context passed to particle modules during execution
/// </summary>
public class ParticleExecutionContext
{
	public Particle Particle;
	public ParticleEffect Effect;
	public Sandbox.ParticleEmitter Emitter;
	public FXBoxNativeParticleSystem SystemComponent; // Changed from Resource to SystemComponent
}

// ==================== SPAWN MODULES ====================

/// <summary>
/// Controls spawn rate over time
/// </summary>
[Title("Spawn Rate"), Category("Spawn"), Icon("speed")]
public partial class SpawnRateModule : ParticleModule
{
    [Hide]
    public override ModuleStage Stage => ModuleStage.Spawn;
    
    [Property, Range(0.1f, 1000f)] 
    public FXParticleFloat SpawnRate { get; set; } = 10.0f;
    
    public override void Execute(ParticleExecutionContext context)
    {
	    var rate = SpawnRate;
        context.Emitter.Rate = rate.GetValue( context.SystemComponent );
    }
    
    public override void Initialize( ParticleExecutionContext context )
    {
	    context.Emitter.Rate = SpawnRate.GetValue( context.SystemComponent );
    }
}
/// <summary>
/// Sets initial particle stretch
/// </summary>
[Title("Particle Stretch"), Category("Initialize"), Icon("photo_size_select_small")]
public partial class ParticleStretchModule : ParticleModule
{
	[Hide]
	public override ModuleStage Stage => ModuleStage.Initialize;
	
    
	[Property, Range(0.1f, 100f)]
	public FXParticleFloat Size { get; set; } = 1.0f;
	public override void Initialize( ParticleExecutionContext context )
	{
		context.Effect.ApplyShape = true;
		context.Effect.Stretch = Size.ToParticleFloat( context.SystemComponent );
	}
	public override void Execute(ParticleExecutionContext context)
	{
       
	}
}
/// <summary>
/// Controls spawn rate per unit
/// </summary>
[Title("Spawn Rate Over Distance"), Category("Spawn"), Icon("speed")]
public partial class SpawnRateOverDistanceModule : ParticleModule
{
	[Hide]
	public override ModuleStage Stage => ModuleStage.Spawn;
    
	[Property, Range(0.1f, 1000f)] 
	public FXParticleFloat SpawnRate { get; set; } = 10.0f;
    
	public override void Execute(ParticleExecutionContext context)
	{
		context.Emitter.RateOverDistance = SpawnRate.GetValue( context.SystemComponent );
	}
    
	public override void Initialize( ParticleExecutionContext context )
	{
		context.Emitter.RateOverDistance = SpawnRate.GetValue( context.SystemComponent );
	}
}
/// <summary>
/// Spawns particles in a burst
/// </summary>
[Title("Spawn Burst"), Category("Spawn"), Icon("auto_awesome")]
public partial class SpawnBurstModule : ParticleModule
{
    [Hide]
    public override ModuleStage Stage => ModuleStage.Spawn;
    
    [Property, Range(1, 1000)]
    public int ParticleCount { get; set; } = 50;
    
    public override void Initialize( ParticleExecutionContext context )
    {
	    context.Emitter.Burst = ParticleCount;
    }
    public override void Execute(ParticleExecutionContext context)
    {
	    context.Emitter.Burst = ParticleCount;
    }
}

// ==================== INITIALIZE MODULES ====================

/// <summary>
/// Sets initial position based on shape
/// </summary>
[Title("Initialize Position"), Category("Initialize"), Icon("place")]
public partial class InitializePositionModule : ParticleModule, IParticleComponentCreator
{
    [Hide]
    public override ModuleStage Stage => ModuleStage.Initialize;

    public override void Execute( ParticleExecutionContext context )
    {
	    
    }

    public enum SpawnShape { Point, Sphere, Box, Cone, Circle, Line }
    
    [Property]
    public FXCopyFlags CopyFlags { get; set; } = FXCopyFlags.Rotation | FXCopyFlags.Scale;
    
    [Property]
    public SpawnShape Shape { get; set; } = SpawnShape.Sphere;
    
    [Property, Range(0f, 1000f), ShowIf(nameof(ShowRadius), true)]
    public float Radius { get; set; } = 50.0f;
    
    [Property, ShowIf(nameof(ShowBoxSize), true)]
    public Vector3 BoxSize { get; set; } = new Vector3(100, 100, 100);
    
    [Property, Range(0f, 180f), ShowIf(nameof(ShowConeAngle), true)]
    public float ConeAngle { get; set; } = 45.0f;
    
    [Property]
    public bool EmitFromShell { get; set; } = false;
    
    [Property, ShowIf(nameof(ShowLine), true)]
    public Vector3 LineStart { get; set; } = Vector3.Zero;
    
    [Property, ShowIf(nameof(ShowLine), true)]
    public Vector3 LineEnd { get; set; } = Vector3.Up * 100;

    [Hide] public bool ShowRadius => Shape == SpawnShape.Sphere || Shape == SpawnShape.Circle || Shape == SpawnShape.Cone;
    [Hide] public bool ShowBoxSize => Shape == SpawnShape.Box;
    [Hide] public bool ShowConeAngle => Shape == SpawnShape.Cone;
    [Hide] public bool ShowLine => Shape == SpawnShape.Line;
    
    public override void Initialize( ParticleExecutionContext context )
    {
	    if ( context.Particle != null )
	    {
		    
		    var pos = Shape switch
		    {
			    SpawnShape.Point => Vector3.Zero,
			    SpawnShape.Sphere => GetSpherePosition(),
			    SpawnShape.Box => GetBoxPosition(),
			    SpawnShape.Cone => GetConePosition(),
			    SpawnShape.Circle => GetCirclePosition(),
			    SpawnShape.Line => GetLinePosition(),
			    _ => Vector3.Zero
		    };

		    if ( CopyFlags.HasFlag( FXCopyFlags.Scale ) )
		    {
			    pos = pos * context.Emitter.WorldScale;
		    }

		    if ( CopyFlags.HasFlag( FXCopyFlags.Rotation ) )
		    {
			    pos = pos.RotateAround( 0, context.SystemComponent.WorldRotation );
		    }

	
		    context.Particle.Position += pos;
	    }
    }
    public void CreateComponent(GameObject go)
    {
	    var pointEmitter = go.AddComponent<ParticleSphereEmitter>();
	    pointEmitter.Radius = 0;
	    pointEmitter.Velocity = 0;
	    pointEmitter.Burst = 0;
	    pointEmitter.Rate = 0;
    }

    private Vector3 GetSpherePosition()
    {
        var direction = Random.Shared.VectorInSphere().Normal;
        var radius = EmitFromShell ? Radius : Random.Shared.Float(0, Radius);
        
        return direction * radius;
    }

    private Vector3 GetBoxPosition()
    {
        if (EmitFromShell)
        {
            var face = Random.Shared.Int(0, 5);
            var u = Random.Shared.Float(0, 1);
            var v = Random.Shared.Float(0, 1);
            var halfSize = BoxSize / 2f;
            
            return face switch
            {
                0 => new Vector3(-halfSize.x, MathX.Lerp(-halfSize.y, halfSize.y, u), MathX.Lerp(-halfSize.z, halfSize.z, v)),
                1 => new Vector3(halfSize.x, MathX.Lerp(-halfSize.y, halfSize.y, u), MathX.Lerp(-halfSize.z, halfSize.z, v)),
                2 => new Vector3(MathX.Lerp(-halfSize.x, halfSize.x, u), -halfSize.y, MathX.Lerp(-halfSize.z, halfSize.z, v)),
                3 => new Vector3(MathX.Lerp(-halfSize.x, halfSize.x, u), halfSize.y, MathX.Lerp(-halfSize.z, halfSize.z, v)),
                4 => new Vector3(MathX.Lerp(-halfSize.x, halfSize.x, u), MathX.Lerp(-halfSize.y, halfSize.y, v), -halfSize.z),
                _ => new Vector3(MathX.Lerp(-halfSize.x, halfSize.x, u), MathX.Lerp(-halfSize.y, halfSize.y, v), halfSize.z)
            };
        }
        
        return new Vector3(
            Random.Shared.Float(-BoxSize.x / 2, BoxSize.x / 2),
            Random.Shared.Float(-BoxSize.y / 2, BoxSize.y / 2),
            Random.Shared.Float(-BoxSize.z / 2, BoxSize.z / 2)
        );
    }

    private Vector3 GetConePosition()
    {
        var angle = Random.Shared.Float(0, 360);
        var distance = Random.Shared.Float(0, Radius);
        var coneRadius = MathF.Tan(ConeAngle.DegreeToRadian()) * distance;
        var radius = EmitFromShell ? coneRadius : Random.Shared.Float(0, coneRadius);
        
        return new Vector3(
            MathF.Cos(angle.DegreeToRadian()) * radius,
            MathF.Sin(angle.DegreeToRadian()) * radius,
            distance
        );
    }

    private Vector3 GetCirclePosition()
    {
        var angle = Random.Shared.Float(0, 360);
        var radius = EmitFromShell ? Radius : Random.Shared.Float(0, Radius);
        return new Vector3(
            MathF.Cos(angle.DegreeToRadian()) * radius,
            MathF.Sin(angle.DegreeToRadian()) * radius,
            0
        );
    }

    private Vector3 GetLinePosition()
    {
        return Vector3.Lerp(LineStart, LineEnd, Random.Shared.Float(0, 1));
    }
}

/// <summary>
/// Sets initial velocity
/// </summary>
[Title("Initialize Velocity"), Category("Initialize"), Icon("air")]
public partial class InitializeVelocityModule : ParticleModule
{
    [Hide]
    public override ModuleStage Stage => ModuleStage.Initialize;
    
    [Property]
    public FXParticleVector Velocity { get; set; } = Vector3.Up * 100;

    [Property] public bool LocalSpace { get; set; } = false;
    
    [Property]
    public bool InheritEmitterVelocity { get; set; } = false;
    
    
    [Property,ShowIf("InheritEmitterVelocity",true)] public float EmitterVelocityScale { get; set; } = 1.0f;
    public override void Initialize( ParticleExecutionContext context )
    {
	    var startVelocity = Velocity;
	    if ( LocalSpace )
	    {
		    startVelocity = startVelocity.GetValue( context.Particle,context.SystemComponent ).RotateAround( 0,context.Emitter.WorldRotation );
	    }
	    context.Effect.InitialVelocity = startVelocity.GetValue( context.Particle,context.SystemComponent );
	    if ( InheritEmitterVelocity )
	    {
		    context.Effect.InitialVelocity = (context.SystemComponent.Velocity*EmitterVelocityScale) + startVelocity.GetValue( context.Particle,context.SystemComponent );
	    }
	    
    }
    public override void Execute(ParticleExecutionContext context)
    {

    }
}
/// <summary>
/// Sets initial color
/// </summary>
[Title("Collision"), Category("Initialize"), Icon("palette")]
public partial class ParticleCollisionModule : ParticleModule
{

	[Property] public TagSet CollisionIgnore { get; set; } = new TagSet();
	[Property] public List<GameObject> CollisionPrefabs { get; set; } = new List<GameObject>();
	[Property] public FXParticleFloat CollisionRadius { get; set; } = 5;
	[Property] public FXParticleFloat CollisionPrefabChance { get; set; } = 1;
	[Property] public FXParticleFloat CollisionPrefabRotation { get; set; } = 0;
	[Property] public FXParticleFloat DieOnCollisionChance { get; set; } = 0;
	[Property] public bool CollisionPrefabAlign { get; set; } = false;
	[Hide]
	public override ModuleStage Stage => ModuleStage.Initialize;

	public override void Execute(ParticleExecutionContext context)
	{
        
	}
    
	public override void Initialize( ParticleExecutionContext context )
	{
		context.Effect.Collision = true;
		context.Effect.CollisionIgnore = CollisionIgnore;
		context.Effect.CollisionRadius = CollisionRadius.GetValue( context.SystemComponent );
		context.Effect.CollisionPrefabChance = CollisionPrefabChance.GetValue( context.SystemComponent );
		context.Effect.CollisionPrefabRotation = CollisionPrefabRotation.ToParticleFloat( context.SystemComponent );
		if ( CollisionPrefabs.Any() )
		{
			context.Effect.UsePrefabFeature = true;
		}
		context.Effect.CollisionPrefab = CollisionPrefabs;
		context.Effect.DieOnCollisionChance = DieOnCollisionChance.GetValue( context.SystemComponent );
		context.Effect.CollisionPrefabAlign = CollisionPrefabAlign;
	}
}
/// <summary>
/// Sets initial velocity
/// </summary>
[Title("Initialize Rotation"), Category("Initialize"), Icon("air")]
public partial class InitializeRotationModule : ParticleModule
{
	[Hide]
	public override ModuleStage Stage => ModuleStage.Initialize;
    
	[Property]
	public ParticleVector3 InitialRotation { get; set; } = Vector3.Up;
	public override void Initialize( ParticleExecutionContext context )
	{
		if ( context.Particle != null )
		{
			context.Particle.Angles = new Angles( InitialRotation.Evaluate( Time.Delta,context.Particle.Rand(  ),context.Particle.Rand(  ),context.Particle.Rand(  ) ) );
		}
	    
	}
	public override void Execute(ParticleExecutionContext context)
	{

	}
}

/// <summary>
/// Sets initial lifetime
/// </summary>
[Title("Initialize Lifetime"), Category("Initialize"), Icon("schedule")]
public partial class InitializeLifetimeModule : ParticleModule
{
	[Hide]
	public override ModuleStage Stage => ModuleStage.Initialize;
    
	[Property, Range(0.1f, 100f)]
	public FXParticleFloat Lifetime { get; set; } = 2.0f;
    
	public override void Initialize(ParticleExecutionContext context)
	{
		context.Effect.Lifetime = Lifetime.ToParticleFloat( context.SystemComponent );
	}
    
	public override void Execute(ParticleExecutionContext context)
	{
		// Not used
	}
}

/// <summary>
/// Sets initial size
/// </summary>
[Title("Initialize Size"), Category("Initialize"), Icon("photo_size_select_small")]
public partial class InitializeSizeModule : ParticleModule
{
    [Hide]
    public override ModuleStage Stage => ModuleStage.Initialize;

    [Property] public bool InheritEmitterScale { get; set; } = true;
    
    [Property, Range(0.1f, 100f)]
    public FXParticleFloat Size { get; set; } = 10.0f;
    public override void Initialize( ParticleExecutionContext context )
    {
	    context.Effect.ApplyShape = true;
	    if ( InheritEmitterScale )
	    {
		    context.Effect.Scale = Size.ToParticleFloat( context.SystemComponent );
	    }
	    else
	    {
		     context.Effect.Scale = (Size / context.SystemComponent.WorldScale.x).ToParticleFloat( context.SystemComponent );
	    }
	    
	    
	  
    }
    public override void Execute(ParticleExecutionContext context)
    {
       
    }
}

/// <summary>
/// Sets initial color
/// </summary>
[Title("Initialize Color"), Category("Initialize"), Icon("palette")]
public partial class InitializeColorModule : ParticleModule
{
    [Hide]
    public override ModuleStage Stage => ModuleStage.Initialize;

    [Property] public FXParticleColor Color { get; set; } = global::Color.Red;

    public override void Execute(ParticleExecutionContext context)
    {
        
    }
    
    public override void Initialize( ParticleExecutionContext context )
    {
	    context.Effect.ApplyColor = true;
	    context.Effect.ApplyAlpha = true;
	    if ( Color != null )
	    {
		    context.Effect.Gradient = Color.GetValue( context.SystemComponent );
	    }
	    else
	    {
		    Color = new FXParticleColor( global::Color.Red );
	    }
	    
    }
}

/// <summary>
/// Sets initial color
/// </summary>
[Title("Sprite Flipbook"), Category("Initialize"), Icon("palette")]
public partial class SpriteFlipbookModule : ParticleModule
{
	[Property] public FXParticleFloat SequenceTime { get; set; } = 0;
	[Property] public FXParticleFloat SequenceSpeed { get; set; } = 1;
	[Property] public int SequenceId { get; set; } = 0;

	[Hide]
	public override ModuleStage Stage => ModuleStage.Initialize;

	public override void Execute(ParticleExecutionContext context)
	{
        
	}
    
	public override void Initialize( ParticleExecutionContext context )
	{
		context.Effect.SheetSequence = true;
		context.Effect.SequenceId = SequenceId;
		context.Effect.SequenceSpeed = SequenceSpeed.ToParticleFloat( context.SystemComponent );
		context.Effect.SequenceTime = SequenceTime.ToParticleFloat( context.SystemComponent );

	}
}

/// <summary>
/// Randomly Kill a particle to spawn less
/// </summary>
[Title("RandomKill"), Category("Initialize"), Icon("arrow_downward")]
public partial class RandomKill : ParticleModule
{
	[Hide]
	public override ModuleStage Stage => ModuleStage.Initialize;
	
	[Property]
	public float Chance { get; set; } = 0.5f;
	

	public override void Execute(ParticleExecutionContext context)
	{
        
	}
	public override void Initialize( ParticleExecutionContext context )
	{
		if ( context.Particle == null ) return;
		if ( Random.Shared.Float( 0, 1 ) < Chance )
		{
			context.Particle.Age = 100000;
		}
	}
	
}


// ==================== UPDATE MODULES ====================

/// <summary>
/// Applies gravity force
/// </summary>
[Title("Gravity Force"), Category("Update"), Icon("arrow_downward")]
public partial class GravityForceModule : ParticleModule, IParticleUpdater
{
    [Hide]
    public override ModuleStage Stage => ModuleStage.Update;
    
    [Property]
    public FXParticleVector Force { get; set; } = new Vector3(0, 0, -980);

    public override void Execute(ParticleExecutionContext context)
    {
        
    }
    public override void Initialize( ParticleExecutionContext context )
    {
	    
    }
    public void UpdateParticle(ParticleExecutionContext context, float delta)
    {
        context.Particle.Velocity += Force.GetValue(context.Particle, context.SystemComponent ) * Time.Delta;
    }
}

/// <summary>
/// Make a mesh follow it's velocity
/// </summary>
[Title("Follow Velocity"), Category("Update"), Icon("arrow_downward")]
public partial class FollowVelocity : ParticleModule, IParticleUpdater
{
	[Hide]
	public override ModuleStage Stage => ModuleStage.Update;
	

	public override void Execute(ParticleExecutionContext context)
	{
        
	}
	public override void Initialize( ParticleExecutionContext context )
	{
	    
	}
	public void UpdateParticle(ParticleExecutionContext context, float delta)
	{
		context.Particle.Angles = Rotation.LookAt( context.Particle.Velocity ).Angles();
	}
}
/// <summary>
/// Applies drag/air resistance
/// </summary>
[Title("Drag Force"), Category("Update"), Icon("air")]
public partial class DragForceModule : ParticleModule, IParticleUpdater
{
    [Hide]
    public override ModuleStage Stage => ModuleStage.Update;
    
    [Property, Range(0f, 10f)]
    public float Damping { get; set; } = 0.1f;

    public override void Execute(ParticleExecutionContext context)
    {
        
    }
    public override void Initialize( ParticleExecutionContext context )
    {
	    
    }
    public void UpdateParticle(ParticleExecutionContext context, float delta)
    {
        context.Particle.Velocity *= (1.0f - Damping * Time.Delta);
    }
}

/// <summary>
/// Makes particles rotate
/// </summary>
[Title("Rotation"), Category("Update"), Icon("rotate_right")]
public partial class RotationModule : ParticleModule, IParticleUpdater
{
    [Hide]
    public override ModuleStage Stage => ModuleStage.Update;
    
    [Property, Range(-360f, 360f)]
    public FXParticleFloat RotationSpeed { get; set; } = 90.0f;

    public override void Execute(ParticleExecutionContext context)
    {
        /*context.Particle.Rotation += RotationSpeed * context.DeltaTime;*/
    }
    public override void Initialize( ParticleExecutionContext context )
    {
	    
    }
    public void UpdateParticle(ParticleExecutionContext context, float delta)
    {
        context.Particle.Angles += RotationSpeed.GetValue( context.SystemComponent ) * Time.Delta;
    }
}

public enum PositionType
{
	Local,
	World
}

/// <summary>
/// Attracts particles to a point. Full strength inside AttractorSize, falling off beyond it.
/// </summary>
[Title("Point Attractor"), Category("Update"), Icon("my_location")]
public partial class PointAttractorModule : ParticleModule, IParticleUpdater
{
	[Hide]
	public override ModuleStage Stage => ModuleStage.Update;
	[Property] public PositionType PositionType { get; set; } = PositionType.Local;

	[Property]
	public FXParticleVector AttractorPosition { get; set; } = Vector3.Zero;

	[Property, Range(0f, 10000f)]
	public FXParticleFloat Strength { get; set; } = 500.0f;

	[Property, Range(0.01f, 10000f)]
	public float AttractorSize { get; set; } = 50.0f;

	[Property] public bool Invert { get; set; } = false;

	/// <summary>
	/// How quickly strength falls off beyond AttractorSize.
	/// 1 = linear, 2 = inverse square, higher = sharper falloff.
	/// </summary>
	[Property, Range(0.1f, 8f)]
	public float Falloff { get; set; } = 2.0f;

	public override void Execute(ParticleExecutionContext context) { }
	public override void Initialize(ParticleExecutionContext context) { }

	public void UpdateParticle(ParticleExecutionContext context, float delta)
	{
		var attractorPos = PositionType == PositionType.Local ? AttractorPosition.GetValue( context.Particle, context.SystemComponent ) + context.Emitter.WorldPosition : AttractorPosition.GetValue( context.Particle, context.SystemComponent );
		
		
		var toAttractor = attractorPos - context.Particle.Position;
		var distance = toAttractor.Length;

		if (distance < 0.01f) return;

		// Inside the attractor: full strength.
		// Outside: strength falls off based on normalised excess distance.
		float strengthMultiplier;
		if (distance <= AttractorSize)
		{
			strengthMultiplier = 1f;
		}
		else
		{
			// How many radii past the edge are we? 0 at the surface, grows outward.
			var excess = (distance - AttractorSize) / AttractorSize;
			strengthMultiplier = 1f / MathF.Pow(1f + excess, Falloff);
		}
		
		if ( Invert )
		{
			strengthMultiplier = 1 - strengthMultiplier;
		}

		context.Particle.Velocity += toAttractor.Normal * Strength.GetValue( context.SystemComponent ) * strengthMultiplier * Time.Delta;
	}
}
/// <summary>
/// Creates orbital motion
/// </summary>
[Title("Vortex Force"), Category("Update"), Icon("cyclone")]
public partial class VortexForceModule : ParticleModule, IParticleUpdater
{
    [Hide]
    public override ModuleStage Stage => ModuleStage.Update;
    
    [Property]
    public Vector3 Center { get; set; } = Vector3.Zero;
    
    [Property]
    public FXParticleVector Axis { get; set; } = Vector3.Up;
    
    [Property, Range(0f, 1000f)]
    public float Strength { get; set; } = 100.0f;

    public override void Execute(ParticleExecutionContext context)
    {
        
    }
    public override void Initialize( ParticleExecutionContext context )
    {
	    
    }
    public void UpdateParticle(ParticleExecutionContext context, float delta)
    {
        var toCenter = context.Particle.Position - (Center + context.Emitter.WorldPosition);
        var distance = toCenter.Length;
        
        
        if (distance > 0.01f)
        {
            var tangent = Vector3.Cross(Axis.GetValue( context.Particle,context.SystemComponent ).Normal, toCenter.Normal);
            var force = tangent * (Strength / distance);
            context.Particle.Velocity += force * Time.Delta * 10000;
        }
    }
}

// ==================== RENDER MODULES ====================

/// <summary>
/// Basic sprite renderer
/// </summary>
[Title("Sprite Renderer"), Category("Render"), Icon("image")]
public partial class SpriteRendererModule : ParticleModule, IParticleComponentCreator
{
    [Hide] 
    public override ModuleStage Stage => ModuleStage.Render;

    [Property] 
    public Sprite Sprite { get; set; }

    [Property]
    public ParticleSpriteRenderer.BillboardAlignment Alignment { get; set; } =
	    ParticleSpriteRenderer.BillboardAlignment.LookAtCamera;

    [Property] public bool FaceVelocity { get; set; } = false;

    [Property] public bool Additive { get; set; } = false;

    public override void Execute(ParticleExecutionContext context)
    {
    }
    public override void Initialize( ParticleExecutionContext context )
    {
	    
    }
    public void CreateComponent(GameObject go)
    {
        var renderer = go.AddComponent<ParticleSpriteRenderer>();

        renderer.Alignment = Alignment;
        renderer.FaceVelocity = FaceVelocity;
        renderer.Sprite = Sprite;
		renderer.Additive = Additive;
    }
}

/// <summary>
/// Basic light renderer
/// </summary>
[Title("Light Renderer"), Category("Render"), Icon("image")]
public partial class LightRendererModule : ParticleModule, IParticleComponentCreator
{
	[Hide] 
	public override ModuleStage Stage => ModuleStage.Render;
	
	[Property] public FXParticleColor LightColor { get; set; } = new FXParticleColor( Color.White );
	[Property] public FXParticleFloat Brightness { get; set; } = 10f;
	[Property] public FXParticleFloat MaxLights { get; set; } = 10f;
	[Property] public FXParticleFloat LightSize { get; set; } = 10f;
	[Property] public FXParticleFloat Attenuation { get; set; } = 1;
	[Property] public bool CastShadows { get; set; } = false;
	[Property] public FXParticleFloat Ratio { get; set; } = 1;
	public override void Execute(ParticleExecutionContext context)
	{
		// Rendering is handled externally, this just stores render properties
	}
	public override void Initialize( ParticleExecutionContext context )
	{
	    
	}
	public void CreateComponent(GameObject go)
	{
		var renderer = go.AddComponent<ParticleLightRenderer>();
		
		var fxbox=go.GetComponentInParent<FXBoxNativeParticleSystem>(  );
		renderer.LightColor = LightColor.GetValue( fxbox );
		renderer.Brightness = Brightness.GetValue( fxbox );
		
		renderer.MaximumLights = (int)MaxLights.GetValue( fxbox );
		renderer.Scale = LightSize.GetValue( fxbox );
		renderer.Attenuation = Attenuation.GetValue( fxbox );
		renderer.Ratio = Ratio.GetValue( go.GetComponentInParent<FXBoxNativeParticleSystem>() );
		renderer.CastShadows = CastShadows;
	}
}

/// <summary>
/// Basic model renderer
/// </summary>
[Title("Model Renderer"), Category("Render"), Icon("image")]
public partial class ModelRendererModule : ParticleModule, IParticleComponentCreator
{
	[Hide] 
	public override ModuleStage Stage => ModuleStage.Render;

	[Property] 
	public List<ParticleModelRenderer.ModelEntry> Models { get; set; }

	[Property] 
	public bool FaceCamera { get; set; } = true;

	public override void Execute(ParticleExecutionContext context)
	{
		// Rendering is handled externally, this just stores render properties
	}
	public override void Initialize( ParticleExecutionContext context )
	{
	    
	}
	public void CreateComponent(GameObject go)
	{
		var renderer = go.AddComponent<ParticleModelRenderer>();

		renderer.Choices = Models;

	}
}

/// <summary>
/// Basic Trail Renderer
/// </summary>
[Title( "Trail Renderer" ), Category( "Render" ), Icon( "image" )]
public partial class TrailRendererModule : ParticleModule, IParticleComponentCreator
{
	[Hide] public override ModuleStage Stage => ModuleStage.Render;

	[Property] public bool Game { get; set; } = true;
	[Property] public bool Overlay { get; set; } = false;
	[Property] public bool Bloom { get; set; } = false;
	[Property] public bool AfterUi { get; set; } = false;
	[Property] public Material Material { get; set; }
	[Property] public FXParticleFloat UnitsPerTexture { get; set; } = 10f;
	[Property] public FXParticleFloat Scroll { get; set; } = 0f;
	[Property] public FXParticleFloat Width { get; set; } = 1f;
	[Property] public bool Opaque { get; set; } = true;
	[Property, ShowIf( "Opaque", false )] public BlendMode BlendMode { get; set; } = BlendMode.Normal;
	[Property] public int MaxPoints { get; set; } = 32;
	[Property] public float PointDistance { get; set; } = 8;
	[Property] public float LifeTime { get; set; } = 2f;
	[Property] public FXParticleColor Color { get; set; } = new FXParticleColor(  );

public override void Execute(ParticleExecutionContext context)
	{
		// Rendering is handled externally, this just stores render properties
	}
	public override void Initialize( ParticleExecutionContext context )
	{
	    
	}
	public void CreateComponent(GameObject go)
	{
		var renderer = go.AddComponent<ParticleTrailRenderer>();

		var appearance = renderer.Texturing;
		appearance.Material = Material;
		appearance.UnitsPerTexture = UnitsPerTexture.GetValue(  );
		appearance.Scroll = Scroll.GetValue();

		var widthCurve = Width.ToParticleFloat();

		if ( widthCurve.Type == ParticleFloat.ValueType.Curve )
		{
			renderer.Width = widthCurve.CurveA;
		} else if ( widthCurve.Type == ParticleFloat.ValueType.Range )
		{
			var point1 = new Curve.Frame( 0, widthCurve.ConstantA );
			var point2 = new Curve.Frame( 1, widthCurve.ConstantB );
			renderer.Width = new Curve( point1, point2 );
		} else if ( widthCurve.Type == ParticleFloat.ValueType.Constant )
		{
			renderer.Width = widthCurve.ConstantA;
		}
		else
		{
			renderer.Width = widthCurve.CurveA;
		}
		
		renderer.Opaque = Opaque;
		renderer.BlendMode = BlendMode;
		renderer.MaxPoints = MaxPoints;
		renderer.PointDistance = PointDistance;
		renderer.LifeTime = LifeTime;
		var colorParam = Color.GetValue();
		if ( colorParam.Type == ParticleGradient.ValueType.Constant )
		{
			renderer.Color = colorParam.ConstantA;
		} else if ( colorParam.Type == ParticleGradient.ValueType.Range )
		{
			var point1 = new Gradient.ColorFrame( 0, colorParam.ConstantA );
			var point2 = new Gradient.ColorFrame( 1, colorParam.ConstantB );
			renderer.Color = new Gradient( point1, point2 );
		} else if ( colorParam.Type == ParticleGradient.ValueType.Gradient )
		{
			renderer.Color = colorParam.GradientA;
		}

		renderer.RenderOptions.Game = Game;
		renderer.RenderOptions.Overlay = Overlay;
		renderer.RenderOptions.Bloom = Bloom;
		renderer.RenderOptions.AfterUI = AfterUi;

		renderer.Texturing = appearance;

	}
}
/// <summary>
/// Applies curl noise force for organic, swirling motion
/// </summary>
[Title("Curl Noise"), Category("Update"), Icon("air")]
public partial class CurlNoiseModule : ParticleModule, IParticleUpdater
{
    [Hide]
    public override ModuleStage Stage => ModuleStage.Update;
    
    [Property, Range(0f, 1000f)]
    [Description("Strength of the curl noise effect")]
    public FXParticleFloat Strength { get; set; } = 1.0f;
    
    [Property, Range(0.01f, 10f)]
    [Description("Scale of the noise pattern - smaller values create tighter curls")]
    public FXParticleFloat Scale { get; set; } = 1.0f;
    
    [Property, Range(0f, 10f)]
    [Description("Speed at which the noise pattern evolves over time")]
    public FXParticleFloat TimeScale { get; set; } = 1.0f;
    
    [Property]
    [Description("Offset in the noise field")]
    public Vector3 Offset { get; set; } = Vector3.Zero;

    public override void Execute(ParticleExecutionContext context)
    {
        // Not used - handled in UpdateParticle
    }

    public override void Initialize(ParticleExecutionContext context)
    {
        // No initialization needed
    }

    public void UpdateParticle(ParticleExecutionContext context, float delta)
    {
        var particle = context.Particle;
        
        // Sample position in noise field
        var samplePos = (particle.Position + Offset) * Scale.GetValue( context.SystemComponent );
        var time = context.Particle.Age * TimeScale;
        
        // Calculate curl noise using the curl of a 3D noise field
        var curl = CalculateCurl(samplePos, time.GetValue( context.SystemComponent ));
        
        // Apply force
        particle.Velocity += curl * Strength.GetValue( context.SystemComponent ) * delta * 10;
    }

    /// <summary>
    /// Calculate curl noise by taking the curl of a potential field
    /// This creates divergence-free flow fields that look organic
    /// </summary>
    private Vector3 CalculateCurl(Vector3 pos, float time)
    {
        const float epsilon = 0.001f;
        
        // Sample the potential field at offset positions
        // We need 6 samples to calculate the curl (derivatives in all directions)
        
        // dPz/dy - dPy/dz
        float curlX = 
            (SamplePotential(pos + new Vector3(0, epsilon, 0), time).z - 
             SamplePotential(pos - new Vector3(0, epsilon, 0), time).z) -
            (SamplePotential(pos + new Vector3(0, 0, epsilon), time).y - 
             SamplePotential(pos - new Vector3(0, 0, epsilon), time).y);
        
        // dPx/dz - dPz/dx
        float curlY = 
            (SamplePotential(pos + new Vector3(0, 0, epsilon), time).x - 
             SamplePotential(pos - new Vector3(0, 0, epsilon), time).x) -
            (SamplePotential(pos + new Vector3(epsilon, 0, 0), time).z - 
             SamplePotential(pos - new Vector3(epsilon, 0, 0), time).z);
        
        // dPy/dx - dPx/dy
        float curlZ = 
            (SamplePotential(pos + new Vector3(epsilon, 0, 0), time).y - 
             SamplePotential(pos - new Vector3(epsilon, 0, 0), time).y) -
            (SamplePotential(pos + new Vector3(0, epsilon, 0), time).x - 
             SamplePotential(pos - new Vector3(0, epsilon, 0), time).x);
        
        return new Vector3(curlX, curlY, curlZ) / (2.0f * epsilon);
    }

    /// <summary>
    /// Sample a 3D potential field using Perlin-like noise
    /// </summary>
    private Vector3 SamplePotential(Vector3 pos, float time)
    {
        // Create three offset noise samples for each component
        // This creates a vector field from scalar noise functions
        return new Vector3(
            Noise3D(pos + new Vector3(0, 0, 0), time),
            Noise3D(pos + new Vector3(31.416f, -47.853f, 12.793f), time),
            Noise3D(pos + new Vector3(-17.737f, 86.214f, -59.482f), time)
        );
    }

    /// <summary>
    /// Simple 3D noise function using sine waves
    /// You could replace this with proper Perlin/Simplex noise for better results
    /// </summary>
    private float Noise3D(Vector3 pos, float time)
    {
        // Combine multiple sine waves at different frequencies for pseudo-noise
        var p = pos + new Vector3(time, time * 0.7f, time * 0.5f);
        
        float noise = 0;
        noise += MathF.Sin(p.x * 1.0f + p.y * 1.3f) * 0.5f;
        noise += MathF.Sin(p.y * 1.7f + p.z * 0.9f) * 0.3f;
        noise += MathF.Sin(p.z * 2.1f + p.x * 1.1f) * 0.2f;
        noise += MathF.Sin(p.x * 3.7f + p.y * 2.3f + p.z * 1.9f) * 0.15f;
        
        return noise;
    }
}
using System;
using System.Linq;
using Sandbox.UI;
using PaneOS.InteractiveComputer;

namespace PaneOS.InteractiveComputer.Apps;

[ComputerApp( "system.notepad", "Notepad", Icon = "NP", SortOrder = 10 )]
public sealed class NotepadApp : IComputerApp
{
	public ComputerAppSession Run( ComputerAppContext context )
	{
		return new ComputerAppSession
		{
			Title = "Untitled - Notepad",
			Icon = "NP",
			Content = new NotepadPanel( context )
		};
	}
}

[StyleSheet( "InteractiveComputerApps.scss" )]
public sealed class NotepadPanel : ComputerWarmupPanel
{
	private readonly ComputerAppContext context;
	private ComputerInputAwareTextEntry textEntry = null!;
	private string currentFilePath;

	public NotepadPanel( ComputerAppContext context )
	{
		this.context = context;
		AddClass( "notepad-app" );
		BuildUi();
	}

	protected override void WarmupRefresh()
	{
		BuildUi();
	}

	private void BuildUi()
	{
		var currentText = textEntry?.Text;
		var caretPosition = textEntry?.CaretPosition ?? 0;
		DeleteChildren( true );

		var toolbar = new Panel { Parent = this };
		toolbar.AddClass( "notepad-toolbar" );

		var openButton = new Button( "Open" ) { Parent = toolbar };
		openButton.AddClass( "notepad-toolbar-button" );
		openButton.AddEventListener( "onclick", OpenFile );

		var saveButton = new Button( "Save" ) { Parent = toolbar };
		saveButton.AddClass( "notepad-toolbar-button" );
		saveButton.AddEventListener( "onclick", SaveFile );

		var saveAsButton = new Button( "Save As" ) { Parent = toolbar };
		saveAsButton.AddClass( "notepad-toolbar-button" );
		saveAsButton.AddEventListener( "onclick", SaveFileAs );

		textEntry = new ComputerInputAwareTextEntry( ShouldSuppressInput )
		{
			Parent = this,
			Text = currentText ?? "",
			Multiline = true,
			Placeholder = ""
		};
		textEntry.AddClass( "notepad-text" );

		if ( currentText is null )
		{
			LoadInitialDocument();
		}
		else
		{
			textEntry.CaretPosition = Math.Clamp( caretPosition, 0, textEntry.TextLength );
		}
	}

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

		if ( context.LoadValue( "text" ) == textEntry.Text )
			return;

		context.SaveValue( "text", textEntry.Text );
	}

	private void LoadInitialDocument()
	{
		currentFilePath = context.LoadValue( "file_path" ) ?? "";
		textEntry.Text = string.IsNullOrWhiteSpace( currentFilePath )
			? context.LoadValue( "text" ) ?? ""
			: context.ReadTextFile( currentFilePath );
		textEntry.CaretPosition = textEntry.TextLength;
		textEntry.Focus();
	}

	private bool ShouldSuppressInput()
	{
		return context.Runtime.ShouldBlockInput( context.State.InstanceId ) ||
			context.State.IsMinimized ||
			context.Runtime.FocusedApp?.State.InstanceId != context.State.InstanceId;
	}

	private void OpenFile()
	{
		context.ShowOpenFileDialog(
			new ComputerFileDialogOptions
			{
				Title = "Open Text File",
				InitialPath = context.GetDefaultDocumentsPath(),
				AllowedExtensions = new[] { "txt" },
				ConfirmButtonText = "Open"
			},
			result =>
			{
				if ( !result.Confirmed )
					return;

				currentFilePath = result.VirtualPath;
				context.SaveValue( "file_path", currentFilePath );
				textEntry.Text = context.ReadTextFile( currentFilePath );
				textEntry.CaretPosition = textEntry.TextLength;
				textEntry.Focus();
			} );
	}

	private void SaveFile()
	{
		if ( string.IsNullOrWhiteSpace( currentFilePath ) )
		{
			SaveFileAs();
			return;
		}

		context.WriteTextFile( currentFilePath, textEntry.Text );
		context.SaveValue( "file_path", currentFilePath );
	}

	private void SaveFileAs()
	{
		context.ShowSaveFileDialog(
			new ComputerFileDialogOptions
			{
				Title = "Save Text File",
				InitialPath = context.GetDefaultDocumentsPath(),
				DefaultFileName = string.IsNullOrWhiteSpace( currentFilePath ) ? "Untitled.txt" : currentFilePath.Split( '/' ).Last(),
				AllowedExtensions = new[] { "txt" },
				ConfirmButtonText = "Save"
			},
			result =>
			{
				if ( !result.Confirmed )
					return;

				currentFilePath = EnsureTxtExtension( result.VirtualPath );
				context.WriteTextFile( currentFilePath, textEntry.Text );
				context.SaveValue( "file_path", currentFilePath );
				textEntry.Focus();
			} );
	}

	private static string EnsureTxtExtension( string virtualPath )
	{
		return virtualPath.EndsWith( ".txt", StringComparison.OrdinalIgnoreCase )
			? virtualPath
			: $"{virtualPath}.txt";
	}
}
using System;
using System.Collections.Generic;
using System.Linq;
using Sandbox;
using Sandbox.UI;
using PaneOS.InteractiveComputer.Core;

namespace PaneOS.InteractiveComputer.Apps;

[ComputerApp( "system.ridge", "Ridge", Icon = "RG", SortOrder = 15 )]
public sealed class RidgeBrowserApp : IComputerApp
{
	public ComputerAppSession Run( ComputerAppContext context )
	{
		return new ComputerAppSession
		{
			Title = "Ridge",
			Icon = "RG",
			Content = new RidgeBrowserPanel( context )
		};
	}
}

[StyleSheet( "InteractiveComputerApps.scss" )]
public sealed class RidgeBrowserPanel : ComputerWarmupPanel
{
	private readonly ComputerAppContext context;
	private ComputerInputAwareTextEntry addressBar = null!;
	private Panel contentHost = null!;
	private Label statusLabel = null!;
	private string currentUrl;
	private RidgePolicyResult pageState = new();

	public RidgeBrowserPanel( ComputerAppContext context )
	{
		this.context = context;
		AddClass( "ridge-app" );

		currentUrl = context.LoadValue( "url" ) ?? context.LoadSetting( "home_url" ) ?? "paneos://default";
		BuildUi();
	}

	protected override void WarmupRefresh()
	{
		MarkRenderDirty();
	}

	private void BuildUi()
	{
		DeleteChildren( true );

		var toolbar = new Panel { Parent = this };
		toolbar.AddClass( "ridge-toolbar" );

		var homeButton = new Button( "Home" ) { Parent = toolbar };
		homeButton.AddClass( "ridge-button" );
		homeButton.AddEventListener( "onclick", GoHome );

		addressBar = new ComputerInputAwareTextEntry( ShouldSuppressInput )
		{
			Parent = toolbar,
			Text = currentUrl
		};
		addressBar.AddClass( "ridge-address" );

		var goButton = new Button( "Go" ) { Parent = toolbar };
		goButton.AddClass( "ridge-button ridge-go" );
		goButton.AddEventListener( "onclick", NavigateFromAddressBar );

		statusLabel = new Label { Parent = this };
		statusLabel.AddClass( "ridge-status" );

		contentHost = new Panel { Parent = this };
		contentHost.AddClass( "ridge-content" );

		Navigate( currentUrl );
	}

	private void GoHome()
	{
		Navigate( context.LoadSetting( "home_url" ) ?? "paneos://default" );
	}

	private void NavigateFromAddressBar()
	{
		Navigate( addressBar.Text );
	}

	private void Navigate( string rawUrl )
	{
		pageState = ResolvePageState( rawUrl );
		currentUrl = pageState.NormalizedUrl;
		addressBar.Text = currentUrl;
		context.SaveValue( "url", currentUrl );
		context.SaveValue( "last_visited_at", DateTime.UtcNow.ToString( "O" ) );
		RenderPage();
	}

	private void RenderPage()
	{
		contentHost.DeleteChildren( true );
		statusLabel.Text = pageState.Status;

		if ( IsLocalDefaultPage( currentUrl ) )
		{
			var localPage = new PoodleSearchPanel( context, OnPoodleSearch )
			{
				Parent = contentHost
			};
			localPage.AddClass( "ridge-local-page" );
			statusLabel.Text = "Poodle search ready";
			return;
		}

		if ( !IsNetworkingAvailable() )
		{
			RenderNetworkMissingPage();
			return;
		}

		if ( pageState.CanRenderWebPanel )
		{
			var webPanel = new WebPanel
			{
				Parent = contentHost,
				Url = currentUrl
			};
			webPanel.AddClass( "ridge-webpanel" );
			return;
		}

		var message = new Panel { Parent = contentHost };
		message.AddClass( $"ridge-message {pageState.MessageClass}" );

		new Label( pageState.Title ) { Parent = message }.AddClass( "ridge-message-title" );
		new Label( pageState.Body ) { Parent = message }.AddClass( "ridge-message-body" );

		if ( pageState.AllowedHosts.Count > 0 )
		{
			var list = new Panel { Parent = message };
			list.AddClass( "ridge-allow-list" );
			new Label( "Allowed hosts" ) { Parent = list }.AddClass( "ridge-allow-title" );

			foreach ( var host in pageState.AllowedHosts )
			{
				new Label( host ) { Parent = list }.AddClass( "ridge-allow-host" );
			}
		}
	}

	private void OnPoodleSearch( string query )
	{
		context.SaveValue( "poodle_query", query );
		statusLabel.Text = string.IsNullOrWhiteSpace( query )
			? "Poodle search ready"
			: $"Poodle sniffed out results for \"{query}\"";
	}

	private void RenderNetworkMissingPage()
	{
		statusLabel.Text = "404 Not Found";
		var message = new Panel { Parent = contentHost };
		message.AddClass( "ridge-message blocked ridge-404-page" );
		new Label( "404 Not Found" ) { Parent = message }.AddClass( "ridge-message-title" );
		new Label( "Networking.exe is not running, so Ridge cannot reach the outside kennel." ) { Parent = message }.AddClass( "ridge-message-body" );
		new Label( "TODO: tiny pixel mouse platformer goes here." ) { Parent = message }.AddClass( "ridge-message-body" );
	}

	private bool IsNetworkingAvailable()
	{
		var networking = context.Runtime.OpenApps.FirstOrDefault( x => x.State.AppId.Equals( "system.networking", StringComparison.OrdinalIgnoreCase ) );
		return networking is not null &&
			context.Runtime.GetEffectiveStatus( networking.State.InstanceId ) == ComputerProcessStatus.Running;
	}

	private bool ShouldSuppressInput()
	{
		return context.Runtime.ShouldBlockInput( context.State.InstanceId ) ||
			context.State.IsMinimized ||
			context.Runtime.FocusedApp?.State.InstanceId != context.State.InstanceId;
	}

	private RidgePolicyResult ResolvePageState( string url )
	{
		var normalized = RidgeBrowserPolicy.NormalizeUrl( string.IsNullOrWhiteSpace( url ) ? "paneos://default" : url );
		if ( IsLocalDefaultPage( normalized ) )
		{
			return new RidgePolicyResult
			{
				NormalizedUrl = "paneos://default",
				Status = "Poodle search ready",
				Title = "Poodle",
				Body = "A local search page"
			};
		}

		return RidgeBrowserPolicy.Evaluate(
			normalized,
			context.LoadSetting( "web_rendering_enabled" ),
			context.LoadSetting( "allowed_hosts" ) );
	}

	private static bool IsLocalDefaultPage( string url )
	{
		return url.Equals( "paneos://default", StringComparison.OrdinalIgnoreCase )
			|| url.Equals( "paneos://home", StringComparison.OrdinalIgnoreCase )
			|| url.Equals( "paneos://poodle", StringComparison.OrdinalIgnoreCase );
	}
}

public sealed class PoodleSearchPanel : ComputerWarmupPanel
{
	private readonly ComputerAppContext context;
	private readonly Action<string> onSearch;
	private ComputerInputAwareTextEntry searchEntry = null!;
	private Panel resultsHost = null!;

	public PoodleSearchPanel( ComputerAppContext context, Action<string> onSearch )
	{
		this.context = context;
		this.onSearch = onSearch;
		AddClass( "poodle-page" );
		BuildUi();
	}

	protected override void WarmupRefresh()
	{
		BuildUi();
	}

	private void BuildUi()
	{
		var currentQuery = searchEntry?.Text ?? context.LoadValue( "poodle_query" ) ?? "";
		DeleteChildren( true );

		var logo = new Label( "Poodle" ) { Parent = this };
		logo.AddClass( "poodle-logo" );

		var tagline = new Label( "Search the local kennel." ) { Parent = this };
		tagline.AddClass( "poodle-tagline" );

		var searchRow = new Panel { Parent = this };
		searchRow.AddClass( "poodle-search-row" );

		searchEntry = new ComputerInputAwareTextEntry( ShouldSuppressInput )
		{
			Parent = searchRow,
			Text = currentQuery,
			Placeholder = "Search PaneOS"
		};
		searchEntry.AddClass( "poodle-input" );

		var searchButton = new Button( "Let loose the dogs!" ) { Parent = searchRow };
		searchButton.AddClass( "poodle-button" );
		searchButton.AddEventListener( "onclick", Search );

		resultsHost = new Panel { Parent = this };
		resultsHost.AddClass( "poodle-results" );

		RenderResults();
	}

	private void Search()
	{
		context.SaveValue( "poodle_query", searchEntry.Text );
		onSearch( searchEntry.Text );
		RenderResults();
	}

	private bool ShouldSuppressInput()
	{
		return context.Runtime.ShouldBlockInput( context.State.InstanceId ) ||
			context.State.IsMinimized ||
			context.Runtime.FocusedApp?.State.InstanceId != context.State.InstanceId;
	}

	private void RenderResults()
	{
		resultsHost.DeleteChildren( true );
		var query = context.LoadValue( "poodle_query" ) ?? "";

		if ( string.IsNullOrWhiteSpace( query ) )
		{
			new Label( "Poodle is waiting for a scent." ) { Parent = resultsHost }.AddClass( "poodle-empty" );
			return;
		}

		foreach ( var result in BuildResults( query ) )
		{
			var row = new Panel { Parent = resultsHost };
			row.AddClass( "poodle-result" );
			new Label( result.Title ) { Parent = row }.AddClass( "poodle-result-title" );
			new Label( result.Url ) { Parent = row }.AddClass( "poodle-result-url" );
			new Label( result.Body ) { Parent = row }.AddClass( "poodle-result-body" );
		}
	}

	private static IReadOnlyList<(string Title, string Url, string Body)> BuildResults( string query )
	{
		return new[]
		{
			($"Poodle result for {query}", $"paneos://search/{query.Replace( ' ', '-' ).ToLowerInvariant() }", $"The local dogs found the strongest scent trail for {query}."),
			($"Best of {query}", "paneos://apps", $"Try checking your apps, notes, and documents for {query}."),
			($"PaneOS knowledge: {query}", "paneos://help", $"No internet needed. Poodle keeps things local and playful.")
		};
	}
}
using System;
using System.Collections.Generic;

namespace PaneOS.InteractiveComputer.Core;

public enum ComputerMediaRepeatMode
{
	Playlist,
	Single,
	None
}

public static class ComputerMediaPlaylistPolicy
{
	public static int ResolveNextIndex( int currentIndex, int playlistCount, ComputerMediaRepeatMode repeatMode )
	{
		if ( playlistCount <= 0 )
			return -1;

		if ( repeatMode == ComputerMediaRepeatMode.Single )
			return Math.Clamp( currentIndex, 0, playlistCount - 1 );

		var nextIndex = currentIndex + 1;
		if ( nextIndex < playlistCount )
			return nextIndex;

		return repeatMode == ComputerMediaRepeatMode.Playlist ? 0 : playlistCount - 1;
	}

	public static IReadOnlyList<string> Shuffle( IReadOnlyList<string> source, int seed )
	{
		var list = new List<string>( source );
		var random = new Random( seed );
		for ( var index = list.Count - 1; index > 0; index-- )
		{
			var swapIndex = random.Next( index + 1 );
			(list[index], list[swapIndex]) = (list[swapIndex], list[index]);
		}

		return list;
	}
}
using System;
using System.Text;

#if PANEOS_UNIT_TESTS
#else
using Sandbox;
#endif

namespace PaneOS.InteractiveComputer.Core;

internal static class ComputerSandboxStorage
{
	public static string ResolveArchiveStoragePath( string computerId, string? configuredPath )
	{
		if ( !string.IsNullOrWhiteSpace( configuredPath ) )
		{
			if ( IsSandboxRelativePath( configuredPath ) )
				return NormalizePath( configuredPath );

			return $"/paneos/imported/{EncodeOpaqueName( configuredPath )}.datc";
		}

		return $"/paneos/saves/{SanitizeSegment( computerId )}.datc";
	}

	public static string ResolveArchiveUserNameStoragePath( string archivePath )
	{
		return $"{NormalizePath( archivePath )}.user.txt";
	}

	public static bool FileExists( string path )
	{
#if PANEOS_UNIT_TESTS
#else
		return FileSystem.Data.FileExists( NormalizePath( path ) );
#endif
	}

	public static byte[] ReadAllBytes( string path )
	{
#if PANEOS_UNIT_TESTS
#else
		var normalizedPath = NormalizePath( path );
		return FileSystem.Data.FileExists( normalizedPath ) ? FileSystem.Data.ReadAllBytes( normalizedPath ).ToArray() : Array.Empty<byte>();
#endif
	}

	public static void WriteAllBytes( string path, byte[] bytes )
	{
#if PANEOS_UNIT_TESTS
#else
		var normalizedPath = NormalizePath( path );
		EnsureParentDirectory( normalizedPath );
		FileSystem.Data.WriteAllBytes( normalizedPath, bytes );
#endif
	}

	public static string ReadAllText( string path )
	{
#if PANEOS_UNIT_TESTS
#else
		var normalizedPath = NormalizePath( path );
		return FileSystem.Data.FileExists( normalizedPath ) ? FileSystem.Data.ReadAllText( normalizedPath ) : "";
#endif
	}

	public static void WriteAllText( string path, string content )
	{
#if PANEOS_UNIT_TESTS
#else
		var normalizedPath = NormalizePath( path );
		EnsureParentDirectory( normalizedPath );
		FileSystem.Data.WriteAllText( normalizedPath, content );
#endif
	}

	public static string GetLocalUserNameFallback()
	{
#if PANEOS_UNIT_TESTS
#else
		return Sandbox.Utility.Steam.PersonaName ?? "";
#endif
	}

#if !PANEOS_UNIT_TESTS
	private static void EnsureParentDirectory( string normalizedPath )
	{
		var lastSlash = normalizedPath.LastIndexOf( '/' );
		if ( lastSlash <= 0 )
			return;

		FileSystem.Data.CreateDirectory( normalizedPath[..lastSlash] );
	}
#endif

	private static string NormalizePath( string path )
	{
		var normalized = path.Replace( '\\', '/' ).Trim();
		if ( string.IsNullOrWhiteSpace( normalized ) )
			return "/paneos/saves/default.datc";

		if ( !normalized.StartsWith( "/", StringComparison.Ordinal ) )
			normalized = "/" + normalized.TrimStart( '/' );

		return normalized;
	}

	private static bool IsSandboxRelativePath( string configuredPath )
	{
		return configuredPath.StartsWith( "/", StringComparison.Ordinal ) &&
			!configuredPath.Contains( ":", StringComparison.Ordinal );
	}

	private static string SanitizeSegment( string value )
	{
		var source = string.IsNullOrWhiteSpace( value ) ? "computer" : value.Trim();
		var builder = new StringBuilder( source.Length );
		foreach ( var character in source )
		{
			builder.Append( char.IsLetterOrDigit( character ) || character is '-' or '_' ? character : '_' );
		}

		return builder.Length == 0 ? "computer" : builder.ToString();
	}

	private static string EncodeOpaqueName( string value )
	{
		var bytes = Encoding.UTF8.GetBytes( value.Trim() );
		var builder = new StringBuilder( bytes.Length * 2 );
		foreach ( var currentByte in bytes )
			builder.Append( currentByte.ToString( "x2" ) );

		return builder.ToString();
	}

#if PANEOS_UNIT_TESTS
#endif
}
using System;

namespace PaneOS.InteractiveComputer.Core;

public static class ComputerWallpaperPolicy
{
	public static string GetBackgroundStyle( string? wallpaper )
	{
		return Normalize( wallpaper ) switch
		{
			"blue" => "background: linear-gradient(180deg, #8ec5ff 0%, #3876d6 45%, #123a7e 100%);",
			"sunset" => "background: linear-gradient(180deg, #ffd7a6 0%, #f58d66 46%, #81386f 100%);",
			_ => "background-color: #2c7cb7;"
		};
	}

	public static string Normalize( string? wallpaper )
	{
		if ( string.IsNullOrWhiteSpace( wallpaper ) )
			return "default";

		var normalized = wallpaper.Trim().ToLowerInvariant();
		return normalized is "blue" or "sunset" ? normalized : "default";
	}
}
using Sandbox;

namespace PaneOS.InteractiveComputer;

/// <summary>
/// Optional player-side helper that disables assigned movement/look components
/// while that player is interacting with a PaneOS computer.
/// </summary>
public sealed class ComputerInteractionPlayerLock : Component
{
	[Property] public Component? MovementComponent { get; set; }
	[Property] public Component? RotationComponent { get; set; }

	private bool? originalMovementEnabled;
	private bool? originalRotationEnabled;
	private bool wasLocked;

	protected override void OnUpdate()
	{
		var shouldLock = InteractiveComputerComponent.GetActiveComputerForPlayer( GameObject ) is not null;
		if ( shouldLock == wasLocked )
			return;

		ApplyLockState( shouldLock );
		wasLocked = shouldLock;
	}

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

		if ( wasLocked )
		{
			ApplyLockState( false );
			wasLocked = false;
		}
	}

	private void ApplyLockState( bool shouldLock )
	{
		if ( MovementComponent is not null )
		{
			if ( shouldLock )
			{
				originalMovementEnabled ??= MovementComponent.Enabled;
				MovementComponent.Enabled = false;
			}
			else if ( originalMovementEnabled.HasValue )
			{
				MovementComponent.Enabled = originalMovementEnabled.Value;
				originalMovementEnabled = null;
			}
		}

		if ( RotationComponent is not null )
		{
			if ( shouldLock )
			{
				originalRotationEnabled ??= RotationComponent.Enabled;
				RotationComponent.Enabled = false;
			}
			else if ( originalRotationEnabled.HasValue )
			{
				RotationComponent.Enabled = originalRotationEnabled.Value;
				originalRotationEnabled = null;
			}
		}
	}
}