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);
	}
}