Code/ParticleValues.cs
using System;
using Sandbox;
using System.Linq;
namespace fxbox;
public class FXParticleFloat
{
[Property] public bool UseParameter { get; set; } = false;
[Property, ShowIf(nameof(UseParameter), false)]
public ParticleFloat Value { get; set; }
[Property, ShowIf(nameof(UseParameter), true)]
[Description("Select a parameter from the system")]
public string ParameterName { get; set; }
[Property, ShowIf(nameof(UseParameter), true), Range(0f, 10f)]
[Description("Multiplier applied to the parameter value")]
public float Multiplier { get; set; } = 1.0f;
public float GetValue(FXBoxNativeParticleSystem systemComponent = null)
{
if (UseParameter && !string.IsNullOrEmpty(ParameterName) && systemComponent != null)
{
return systemComponent.GetFloatParameter(ParameterName) * Multiplier;
}
var result = Value.Evaluate(Random.Shared.Float(), 3f);
return result;
}
public ParticleFloat ToParticleFloat(FXBoxNativeParticleSystem systemComponent = null)
{
if (UseParameter && !string.IsNullOrEmpty(ParameterName) && systemComponent != null)
{
// Get the instance-specific value (override or default)
float value = systemComponent.GetFloatParameter(ParameterName) * Multiplier;
return new ParticleFloat()
{
Type = ParticleFloat.ValueType.Constant,
ConstantValue = value,
Evaluation = ParticleFloat.EvaluationType.Seed
};
}
return Value;
}
// ==================== OPERATORS ====================
public static FXParticleFloat operator *(float a, FXParticleFloat b)
{
return b * a; // Commutative
}
// Division operators
public static FXParticleFloat operator /(FXParticleFloat a, float b)
{
if (a == null)
{
Log.Warning("Division: a is null");
return null;
}
if (b == 0 || MathF.Abs(b) < 0.0001f)
{
Log.Warning($"Division by zero or very small number ({b}) in FXParticleFloat");
return a;
}
var result = a * (1.0f / b);
return result;
}
public static FXParticleFloat operator *(FXParticleFloat a, float b)
{
if (a == null)
{
Log.Warning("Multiplication: a is null");
return null;
}
var result = new FXParticleFloat();
if (a.UseParameter)
{
result.UseParameter = true;
result.ParameterName = a.ParameterName;
result.Multiplier = a.Multiplier * b;
result.Value = new ParticleFloat()
{
Type = ParticleFloat.ValueType.Constant,
ConstantValue = 0f,
Evaluation = ParticleFloat.EvaluationType.Seed
};
}
else
{
result.UseParameter = false;
result.Value = ScaleParticleFloat(a.Value, b);
}
return result;
}
private static ParticleFloat ScaleParticleFloat(ParticleFloat pf, float scale)
{
var result = new ParticleFloat();
result.Type = pf.Type;
result.Evaluation = pf.Evaluation;
// Copy Constants FIRST, before setting individual values
// (or don't copy it at all since we're setting the values manually)
// result.Constants = pf.Constants;
switch (pf.Type)
{
case ParticleFloat.ValueType.Constant:
result.ConstantValue = pf.ConstantValue * scale;
break;
case ParticleFloat.ValueType.Range:
result.ConstantA = pf.ConstantA * scale;
result.ConstantB = pf.ConstantB * scale;
break;
case ParticleFloat.ValueType.Curve:
result.CurveA = ScaleCurve(pf.CurveA, scale);
result.CurveB = ScaleCurve(pf.CurveB, scale);
break;
case ParticleFloat.ValueType.CurveRange:
result.CurveRange = ScaleCurveRange(pf.CurveRange, scale);
break;
}
// DON'T copy Constants here - it overwrites our values!
// result.Constants = pf.Constants;
return result;
}
private static ParticleFloat OffsetParticleFloat(ParticleFloat pf, float offset)
{
var result = new ParticleFloat();
result.Type = pf.Type;
result.Evaluation = pf.Evaluation;
// DON'T copy Constants - it will overwrite our values
// result.Constants = pf.Constants;
switch (pf.Type)
{
case ParticleFloat.ValueType.Constant:
result.ConstantValue = pf.ConstantValue + offset;
break;
case ParticleFloat.ValueType.Range:
result.ConstantA = pf.ConstantA + offset;
result.ConstantB = pf.ConstantB + offset;
break;
case ParticleFloat.ValueType.Curve:
result.CurveA = OffsetCurve(pf.CurveA, offset);
result.CurveB = OffsetCurve(pf.CurveB, offset);
break;
case ParticleFloat.ValueType.CurveRange:
result.CurveRange = OffsetCurveRange(pf.CurveRange, offset);
break;
}
// DON'T copy Constants here!
// result.Constants = pf.Constants;
return result;
}
private static Curve ScaleCurve(Curve curve, float scale)
{
var newFrames = curve.Frames.Select(frame =>
new Curve.Frame(frame.Time, frame.Value * scale)).ToArray();
return new Curve(newFrames);
}
private static Curve OffsetCurve(Curve curve, float offset)
{
var newFrames = curve.Frames.Select(frame =>
new Curve.Frame(frame.Time, frame.Value + offset)).ToArray();
return new Curve(newFrames);
}
private static CurveRange ScaleCurveRange(CurveRange range, float scale)
{
return new CurveRange
(
ScaleCurve(range.A, scale),
ScaleCurve(range.B, scale)
);
}
private static CurveRange OffsetCurveRange(CurveRange range, float offset)
{
return new CurveRange
(
OffsetCurve(range.A, offset),
OffsetCurve(range.B, offset)
);
}
// ==================== IMPLICIT CONVERSIONS ====================
public static implicit operator FXParticleFloat(float v)
{
var fxParticle = new FXParticleFloat();
fxParticle.Value = new ParticleFloat()
{
Type = ParticleFloat.ValueType.Constant,
ConstantValue = v,
Evaluation = ParticleFloat.EvaluationType.Seed
};
return fxParticle;
}
// ==================== CONSTRUCTORS ====================
public FXParticleFloat()
{
var particleFloat = new ParticleFloat();
particleFloat.Type = ParticleFloat.ValueType.Constant;
particleFloat.ConstantValue = 0.0f;
particleFloat.Evaluation = ParticleFloat.EvaluationType.Seed;
particleFloat.CurveA = new Curve();
particleFloat.CurveB = new Curve();
particleFloat.Constants = new Vector4();
this.Value = particleFloat;
}
public FXParticleFloat(float a, float b)
{
var particleFloat = new ParticleFloat();
particleFloat.Type = ParticleFloat.ValueType.Range;
particleFloat.ConstantA = a;
particleFloat.ConstantB = b;
particleFloat.Evaluation = ParticleFloat.EvaluationType.Seed;
particleFloat.CurveA = new Curve();
particleFloat.CurveB = new Curve();
particleFloat.Constants = new Vector4();
this.Value = particleFloat;
}
}
public class FXParticleVector
{
[Property] public bool UseParameter { get; set; } = false;
[Property, ShowIf(nameof(UseParameter), false)]
public ParticleVector3 Value { get; set; } = Vector3.Zero;
[Property, ShowIf(nameof(UseParameter), true)]
[Description("Select a vector parameter from the system")]
public string ParameterName { get; set; }
[Property, ShowIf(nameof(UseParameter), true), Range(0f, 10f)]
[Description("Multiplier applied to the parameter value")]
public float Multiplier { get; set; } = 1.0f;
public Vector3 GetValue(Particle particle,FXBoxNativeParticleSystem systemComponent = null)
{
if (UseParameter && !string.IsNullOrEmpty(ParameterName) && systemComponent != null)
{
return systemComponent.GetVectorParameter(ParameterName) * Multiplier;
}
if ( particle == null )
{
return Value.Evaluate( Time.Delta, 0, 0, 0 );
}
return Value.Evaluate( Time.Delta,particle.Rand(1),particle.Rand(2),particle.Rand(3) );
}
public static implicit operator FXParticleVector(Vector3 v)
{
return new FXParticleVector { Value = v };
}
public FXParticleVector()
{
Value = Vector3.Zero;
}
public FXParticleVector(Vector3 value)
{
Value = value;
}
public FXParticleVector(float x, float y, float z)
{
Value = new Vector3(x, y, z);
}
}
public class FXParticleColor
{
[Property] public bool UseParameter { get; set; } = false;
[Property, ShowIf(nameof(UseParameter), false)]
public ParticleGradient Value { get; set; } = Color.White;
[Property, ShowIf(nameof(UseParameter), true)]
[Description("Select a color parameter from the system")]
public string ParameterName { get; set; }
[Property, ShowIf(nameof(UseParameter), true), Range(0f, 10f)]
[Description("Multiplier applied to the parameter value (affects RGB)")]
public float Multiplier { get; set; } = 1.0f;
public ParticleGradient GetValue(FXBoxNativeParticleSystem systemComponent = null)
{
if (UseParameter && !string.IsNullOrEmpty(ParameterName) && systemComponent != null)
{
var color = systemComponent.GetColorParameter(ParameterName);
// Apply multiplier to RGB components
if (Multiplier != 1.0f)
{
return color;
}
return color;
}
return Value;
}
public static implicit operator FXParticleColor(Color c)
{
return new FXParticleColor { Value = c };
}
public FXParticleColor()
{
Value = Color.White;
}
public FXParticleColor(Color value)
{
Value = value;
}
public FXParticleColor(float r, float g, float b, float a = 1.0f)
{
Value = new Color(r, g, b, a);
}
}