Search the source of every open source package.
5305 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;
}
}
}
}