diff --git a/OpenDreamClient/Rendering/ClientDreamParticlesSystem.cs b/OpenDreamClient/Rendering/ClientDreamParticlesSystem.cs index 4574bf6b66..e6e1b6f02a 100644 --- a/OpenDreamClient/Rendering/ClientDreamParticlesSystem.cs +++ b/OpenDreamClient/Rendering/ClientDreamParticlesSystem.cs @@ -40,13 +40,14 @@ private void HandleComponentRemove(EntityUid uid, DreamParticlesComponent compon _particlesManager.DestroyParticleSystem(uid); } - private ParticleSystemArgs GetParticleSystemArgs(DreamParticlesComponent component) { + private ParticleSystemArgs GetParticleSystemArgs(DreamParticlesComponent comp) { + DreamParticlesComponent.ParticleData data = comp.Data; Func textureFunc; - if (component.TextureList.Length == 0) + if (data.TextureList.Length == 0) textureFunc = () => Texture.White; else { - List icons = new(component.TextureList.Length); - foreach (var appearance in component.TextureList) { + List icons = new(data.TextureList.Length); + foreach (var appearance in data.TextureList) { DreamIcon icon = new(_renderTargetPool, _dreamInterfaceManager, _gameTiming, _clyde, _appearanceSystem); icon.SetAppearance(appearance.MustGetId()); icons.Add(icon); @@ -57,30 +58,30 @@ private ParticleSystemArgs GetParticleSystemArgs(DreamParticlesComponent compone } var perTick = (1f / 10f); // "Tick" refers to a BYOND standard tick of 0.1s. --DM Reference - var result = new ParticleSystemArgs(textureFunc, new Vector2i(component.Width, component.Height), (uint)component.Count, component.Spawning / perTick) { - Lifespan = () => (component.Lifespan?.Generate(_random) ?? 1f) * perTick, - Fadein = () => (component.FadeIn?.Generate(_random) ?? 0f) * perTick, - Fadeout = () => (component.FadeOut?.Generate(_random) ?? 0f) * perTick, - Color = component.Gradient.Length > 0 + var result = new ParticleSystemArgs(textureFunc, new Vector2i(data.Width, data.Height), (uint)data.Count, data.Spawning / perTick) { + Lifespan = () => (data.Lifespan?.Generate(_random) ?? 1f) * perTick, + Fadein = () => (data.FadeIn?.Generate(_random) ?? 0f) * perTick, + Fadeout = () => (data.FadeOut?.Generate(_random) ?? 0f) * perTick, + Color = data.Gradient.Length > 0 ? lifetime => { - var colorIndex = (int)(lifetime * component.Gradient.Length); - colorIndex = Math.Clamp(colorIndex, 0, component.Gradient.Length - 1); - return component.Gradient[colorIndex]; + var colorIndex = (int)(lifetime * data.Gradient.Length); + colorIndex = Math.Clamp(colorIndex, 0, data.Gradient.Length - 1); + return data.Gradient[colorIndex]; } : _ => Color.White, Acceleration = (_, velocity) => { // TODO: Acceleration needs to only update every tick - var drift = (component.Drift?.GenerateVector3(_random) ?? Vector3.Zero); - var friction = (component.Friction?.GenerateVector3(_random) ?? Vector3.Zero); // TODO: Only calculated once per particle + var drift = (data.Drift?.GenerateVector3(_random) ?? Vector3.Zero); + var friction = (data.Friction?.GenerateVector3(_random) ?? Vector3.Zero); // TODO: Only calculated once per particle return drift - (velocity * friction); }, - SpawnPosition = () => component.SpawnPosition?.GenerateVector3(_random) ?? Vector3.Zero, - SpawnVelocity = () => component.SpawnVelocity?.GenerateVector3(_random) ?? Vector3.Zero, + SpawnPosition = () => data.SpawnPosition?.GenerateVector3(_random) ?? Vector3.Zero, + SpawnVelocity = () => data.SpawnVelocity?.GenerateVector3(_random) ?? Vector3.Zero, Transform = _ => { // TODO: Needs to only be performed every tick - var scale = component.Scale.GenerateVector2(_random); - var rotation = component.Rotation?.Generate(_random) ?? 0f; - var growth = component.Growth?.GenerateVector2(_random) ?? Vector2.Zero; - var spin = component.Spin?.Generate(_random) ?? 0f; + var scale = data.Scale.GenerateVector2(_random); + var rotation = data.Rotation?.Generate(_random) ?? 0f; + var growth = data.Growth?.GenerateVector2(_random) ?? Vector2.Zero; + var spin = data.Spin?.Generate(_random) ?? 0f; return Matrix3x2.CreateScale(scale.X + growth.X, scale.Y + growth.Y) * Matrix3x2.CreateRotation(rotation + spin); }, diff --git a/OpenDreamRuntime/Objects/Types/DreamObjectMovable.cs b/OpenDreamRuntime/Objects/Types/DreamObjectMovable.cs index 10e510dd54..d15aa31f56 100644 --- a/OpenDreamRuntime/Objects/Types/DreamObjectMovable.cs +++ b/OpenDreamRuntime/Objects/Types/DreamObjectMovable.cs @@ -20,13 +20,32 @@ public class DreamObjectMovable : DreamObjectAtom { private readonly TransformComponent _transformComponent; private readonly MovableContentsList _contents; private string? _screenLoc; - private DreamObjectParticles? _particles; private string? ScreenLoc { get => _screenLoc; set => SetScreenLoc(value); } + public DreamObjectParticles? Particles { + get; + set { + if(field == value) + return; + + if(field is not null) { + field.DecRef(); + field.RemoveOwner(this); + } + + field = value; + + if(value is not null) { + value.AddOwner(this); + value.IncRef(); + } + } + } + public DreamObjectMovable(DreamObjectDefinition objectDefinition) : base(objectDefinition) { Entity = AtomManager.CreateMovableEntity(this); SpriteComponent = EntityManager.GetComponent(Entity); @@ -54,8 +73,7 @@ public override void Initialize(DreamProcArguments args) { protected override void HandleDeletion() { SetLoc(null); WalkManager.StopWalks(this); - _particles?.Delete(); - _particles = null; + Particles = null; AtomManager.DeleteMovableEntity(this); _contents.DecRef(); @@ -97,8 +115,8 @@ protected override bool TryGetVar(string varName, out DreamValue value) { value = new DreamValue(locs); return true; case "particles": - _particles?.IncRef(); - value = new(_particles); + Particles?.IncRef(); + value = new(Particles); return true; default: return base.TryGetVar(varName, out value); @@ -145,21 +163,9 @@ protected override void SetVar(string varName, DreamValue value) { ScreenLoc = screenLoc; break; case "particles": - if (value.TryGetValueAsDreamObject(out var particles)) { - if (_particles == particles) - break; - - _particles?.Owner = null; - particles.IncRef(); - _particles?.DecRef(); - _particles = particles; - _particles.Owner = this; - } else { - _particles?.Owner = null; - _particles?.DecRef(); - _particles = null; - } + value.TryGetValueAsDreamObject(out var particles); + Particles = particles; break; default: base.SetVar(varName, value); diff --git a/OpenDreamRuntime/Objects/Types/DreamObjectParticles.cs b/OpenDreamRuntime/Objects/Types/DreamObjectParticles.cs index a42abc0ec5..96c2e797ca 100644 --- a/OpenDreamRuntime/Objects/Types/DreamObjectParticles.cs +++ b/OpenDreamRuntime/Objects/Types/DreamObjectParticles.cs @@ -4,23 +4,14 @@ namespace OpenDreamRuntime.Objects.Types; public sealed class DreamObjectParticles : DreamObject { - public DreamObjectMovable? Owner { - set { - if (field != null) - EntityManager.RemoveComponent(field.Entity, _particlesComponent); - - field = value; - if (field != null) - EntityManager.AddComponent(field.Entity, _particlesComponent); - } - } - - private DreamParticlesComponent _particlesComponent; + private DreamParticlesComponent.ParticleData _particlesData; private List _icons = new(); private List _iconStates = new(); + private Dictionary _owners; public DreamObjectParticles(DreamObjectDefinition objectDefinition) : base(objectDefinition) { - _particlesComponent = new DreamParticlesComponent(); + _owners = []; + _particlesData = new(); //populate component with settings from type foreach (KeyValuePair kv in ObjectDefinition.Variables) { @@ -30,31 +21,72 @@ public DreamObjectParticles(DreamObjectDefinition objectDefinition) : base(objec } protected override void HandleDeletion() { - Owner = null; + foreach(var owner in _owners.Keys) { + if(owner.Particles != this) + // This should never happen, but leaving things in an invalid state would be even worse + RemoveOwner(owner); + else + owner.Particles = null; // calls RemoveOwner + } + _icons = null!; _iconStates = null!; - _particlesComponent = null!; + _particlesData = null!; + _owners = null!; base.HandleDeletion(); } + public void AddOwner(DreamObjectMovable movable) { + if(Deleting || Deleted) + throw new InvalidOperationException($"{movable} tried to add new owner to deleted particles"); + if(movable.Deleting || movable.Deleted) + throw new InvalidOperationException($"deleted movable tried to own particles {this}"); + if(_owners.ContainsKey(movable)) + throw new ArgumentException($"{movable} tried to own {this} while already owning"); + + DreamParticlesComponent component = new() { Data = _particlesData }; + EntityManager.AddComponent(movable.Entity, component); + _owners.Add(movable, component); + } + + public void RemoveOwner(DreamObjectMovable movable) { + if(!_owners.TryGetValue(movable, out var component)) { + throw new ArgumentException($"{movable} tried to remove {this} but doesn't own it"); + } + + EntityManager.RemoveComponent(movable.Entity, component); + component.Data = null!; + _owners.Remove(movable); + } + + private void MarkDirty() { + foreach(var (movable, component) in _owners) { + EntityManager.Dirty(movable.Entity, component); + } + } + protected override void SetVar(string varName, DreamValue value) { //good news, these only update on assignment, so we don't need to track the generator, list, or matrix objects switch (varName) { case "width": //num - _particlesComponent.Width = (int)value.UnsafeGetValueAsFloat(); + _particlesData.Width = (int)value.UnsafeGetValueAsFloat(); + MarkDirty(); break; case "height": //num - _particlesComponent.Height = (int)value.UnsafeGetValueAsFloat(); + _particlesData.Height = (int)value.UnsafeGetValueAsFloat(); + MarkDirty(); break; case "count": //num if (!value.TryGetValueAsInteger(out var count)) break; - _particlesComponent.Count = count; + _particlesData.Count = count; + MarkDirty(); break; case "spawning": //num - _particlesComponent.Spawning = value.UnsafeGetValueAsFloat(); + _particlesData.Spawning = value.UnsafeGetValueAsFloat(); + MarkDirty(); break; case "bound1": //list or vector if (value.TryGetValueAsDreamList(out var bound1List) && bound1List.GetLength() >= 3) { @@ -65,7 +97,8 @@ protected override void SetVar(string varName, DreamValue value) { var boundY = boundYValue.UnsafeGetValueAsFloat(); var boundZ = boundZValue.UnsafeGetValueAsFloat(); - _particlesComponent.Bound1 = new Vector3(boundX, boundY, boundZ); + _particlesData.Bound1 = new Vector3(boundX, boundY, boundZ); + MarkDirty(); } //else if vector break; @@ -78,7 +111,8 @@ protected override void SetVar(string varName, DreamValue value) { var boundY = boundYValue.UnsafeGetValueAsFloat(); var boundZ = boundZValue.UnsafeGetValueAsFloat(); - _particlesComponent.Bound2 = new Vector3(boundX, boundY, boundZ); + _particlesData.Bound2 = new Vector3(boundX, boundY, boundZ); + MarkDirty(); } //else if vector break; @@ -91,7 +125,8 @@ protected override void SetVar(string varName, DreamValue value) { var gravityY = gravityYValue.UnsafeGetValueAsFloat(); var gravityZ = gravityZValue.UnsafeGetValueAsFloat(); - _particlesComponent.Gravity = new Vector3(gravityX, gravityY, gravityZ); + _particlesData.Gravity = new Vector3(gravityX, gravityY, gravityZ); + MarkDirty(); } //else if vector break; @@ -108,14 +143,16 @@ protected override void SetVar(string varName, DreamValue value) { grad[i++] = c; } - _particlesComponent.Gradient = grad; + _particlesData.Gradient = grad; + MarkDirty(); } break; case "transform": //matrix if (value.TryGetValueAsDreamObject(out var matrix)) { float[] m = DreamObjectMatrix.MatrixToTransformFloatArray(matrix); - _particlesComponent.Transform = new(m[0], m[1], m[2], m[3], m[4], m[5]); + _particlesData.Transform = new(m[0], m[1], m[2], m[3], m[4], m[5]); + MarkDirty(); } break; @@ -145,7 +182,8 @@ protected override void SetVar(string varName, DreamValue value) { } } - _particlesComponent.TextureList = immutableAppearances.ToArray(); + _particlesData.TextureList = immutableAppearances.ToArray(); + MarkDirty(); break; case "icon_state": //list or string _iconStates.Clear(); @@ -169,128 +207,163 @@ protected override void SetVar(string varName, DreamValue value) { } } - _particlesComponent.TextureList = immutableAppearances.ToArray(); + _particlesData.TextureList = immutableAppearances.ToArray(); + MarkDirty(); break; case "lifespan": //num or generator if (value.TryGetValueAsFloat(out float floatValue)) { - _particlesComponent.Lifespan = new GeneratorNum(floatValue); + _particlesData.Lifespan = new GeneratorNum(floatValue); + MarkDirty(); } else if (value.TryGetValueAsDreamObject(out var generator)) { - _particlesComponent.Lifespan = generator.RequireType(); + _particlesData.Lifespan = generator.RequireType(); + MarkDirty(); } break; case "fadein": //num or generator if (value.TryGetValueAsInteger(out int intValue)) { - _particlesComponent.FadeIn = new GeneratorNum(intValue); + _particlesData.FadeIn = new GeneratorNum(intValue); + MarkDirty(); } else if (value.TryGetValueAsDreamObject(out var generator)) { - _particlesComponent.FadeIn = generator.RequireType(); + _particlesData.FadeIn = generator.RequireType(); + MarkDirty(); } break; case "fade": //num or generator if (value.TryGetValueAsInteger(out intValue)) { - _particlesComponent.FadeOut = new GeneratorNum(intValue); + _particlesData.FadeOut = new GeneratorNum(intValue); + MarkDirty(); } else if (value.TryGetValueAsDreamObject(out var generator)) { - _particlesComponent.FadeOut = generator.RequireType(); + _particlesData.FadeOut = generator.RequireType(); + MarkDirty(); } break; case "position": //num, list, vector, or generator if (value.TryGetValueAsFloat(out floatValue)) { - _particlesComponent.SpawnPosition = new GeneratorNum(floatValue); + _particlesData.SpawnPosition = new GeneratorNum(floatValue); + MarkDirty(); } else if (value.TryGetValueAsDreamObject(out var generator)) { - _particlesComponent.SpawnPosition = generator.RequireType(); + _particlesData.SpawnPosition = generator.RequireType(); + MarkDirty(); } else if (DreamObjectVector.TryCreateFromValue(value, ObjectTree, out var vector)) { - _particlesComponent.SpawnPosition = new GeneratorVector2(vector.AsVector2); + _particlesData.SpawnPosition = new GeneratorVector2(vector.AsVector2); + MarkDirty(); vector.DecRef(); } else { - _particlesComponent.SpawnPosition = new GeneratorVector2(Vector2.Zero); + _particlesData.SpawnPosition = new GeneratorVector2(Vector2.Zero); + MarkDirty(); } break; case "velocity": //num, list, vector, or generator if (value.TryGetValueAsFloat(out floatValue)) { - _particlesComponent.SpawnVelocity = new GeneratorNum(floatValue); + _particlesData.SpawnVelocity = new GeneratorNum(floatValue); + MarkDirty(); } else if (value.TryGetValueAsDreamObject(out var generator)) { - _particlesComponent.SpawnVelocity = generator.RequireType(); + _particlesData.SpawnVelocity = generator.RequireType(); + MarkDirty(); } else if (DreamObjectVector.TryCreateFromValue(value, ObjectTree, out var vector)) { - _particlesComponent.SpawnVelocity = new GeneratorVector2(vector.AsVector2); + _particlesData.SpawnVelocity = new GeneratorVector2(vector.AsVector2); + MarkDirty(); vector.DecRef(); } else { - _particlesComponent.SpawnVelocity = new GeneratorVector2(Vector2.Zero); + _particlesData.SpawnVelocity = new GeneratorVector2(Vector2.Zero); + MarkDirty(); } break; case "scale": //num, list, vector, or generator if (value.TryGetValueAsFloat(out floatValue)) { - _particlesComponent.Scale = new GeneratorNum(floatValue); + _particlesData.Scale = new GeneratorNum(floatValue); + MarkDirty(); } else if (value.TryGetValueAsDreamObject(out var generator)) { - _particlesComponent.Scale = generator.RequireType(); + _particlesData.Scale = generator.RequireType(); + MarkDirty(); } else if (DreamObjectVector.TryCreateFromValue(value, ObjectTree, out var vector)) { - _particlesComponent.Scale = new GeneratorVector2(vector.AsVector2); + _particlesData.Scale = new GeneratorVector2(vector.AsVector2); + MarkDirty(); vector.DecRef(); } else { - _particlesComponent.Scale = new GeneratorVector2(Vector2.One); + _particlesData.Scale = new GeneratorVector2(Vector2.One); + MarkDirty(); } break; case "grow": //num, list, vector, or generator if (value.TryGetValueAsFloat(out floatValue)) { - _particlesComponent.Growth = new GeneratorNum(floatValue); + _particlesData.Growth = new GeneratorNum(floatValue); + MarkDirty(); } else if (value.TryGetValueAsDreamObject(out var generator)) { - _particlesComponent.Growth = generator.RequireType(); + _particlesData.Growth = generator.RequireType(); + MarkDirty(); } else if (DreamObjectVector.TryCreateFromValue(value, ObjectTree, out var vector)) { - _particlesComponent.Growth = new GeneratorVector2(vector.AsVector2); + _particlesData.Growth = new GeneratorVector2(vector.AsVector2); + MarkDirty(); vector.DecRef(); } else { - _particlesComponent.Growth = new GeneratorVector2(Vector2.Zero); + _particlesData.Growth = new GeneratorVector2(Vector2.Zero); + MarkDirty(); } break; case "rotation": //num or generator if (value.TryGetValueAsFloat(out floatValue)) { - _particlesComponent.Rotation = new GeneratorNum(floatValue); + _particlesData.Rotation = new GeneratorNum(floatValue); + MarkDirty(); } else if (value.TryGetValueAsDreamObject(out var generator)) { - _particlesComponent.Rotation = generator.RequireType(); + _particlesData.Rotation = generator.RequireType(); + MarkDirty(); } break; case "spin": //num or generator if (value.TryGetValueAsFloat(out floatValue)) { - _particlesComponent.Spin = new GeneratorNum(floatValue); + _particlesData.Spin = new GeneratorNum(floatValue); + MarkDirty(); } else if (value.TryGetValueAsDreamObject(out var generator)) { - _particlesComponent.Spin = generator.RequireType(); + _particlesData.Spin = generator.RequireType(); + MarkDirty(); } break; case "friction": //num, vector, or generator if (value.TryGetValueAsFloat(out floatValue)) { - _particlesComponent.Friction = new GeneratorNum(floatValue); + _particlesData.Friction = new GeneratorNum(floatValue); + MarkDirty(); } else if (value.TryGetValueAsDreamObject(out var generator)) { - _particlesComponent.Friction = generator.RequireType(); + _particlesData.Friction = generator.RequireType(); + MarkDirty(); } else if (DreamObjectVector.TryCreateFromValue(value, ObjectTree, out var vector)) { - _particlesComponent.Friction = new GeneratorVector2(vector.AsVector2); + _particlesData.Friction = new GeneratorVector2(vector.AsVector2); + MarkDirty(); vector.DecRef(); } else { - _particlesComponent.Friction = new GeneratorVector2(Vector2.Zero); + _particlesData.Friction = new GeneratorVector2(Vector2.Zero); + MarkDirty(); } break; case "drift": //num, vector, or generator if (value.TryGetValueAsFloat(out floatValue)) { - _particlesComponent.Drift = new GeneratorNum(floatValue); + _particlesData.Drift = new GeneratorNum(floatValue); + MarkDirty(); } else if (value.TryGetValueAsDreamObject(out var generator)) { - _particlesComponent.Drift = generator.RequireType(); + _particlesData.Drift = generator.RequireType(); + MarkDirty(); } else if (DreamObjectVector.TryCreateFromValue(value, ObjectTree, out var vector)) { - _particlesComponent.Drift = new GeneratorVector2(vector.AsVector2); + _particlesData.Drift = new GeneratorVector2(vector.AsVector2); + MarkDirty(); vector.DecRef(); } else { - _particlesComponent.Drift = new GeneratorVector2(Vector2.Zero); + _particlesData.Drift = new GeneratorVector2(Vector2.Zero); + MarkDirty(); } break; } base.SetVar(varName, value); //all calls should set the internal vars, so GetVar() can just be default also - } + } } diff --git a/OpenDreamShared/Rendering/DreamParticlesComponent.cs b/OpenDreamShared/Rendering/DreamParticlesComponent.cs index 3880f07796..177edc4b7e 100644 --- a/OpenDreamShared/Rendering/DreamParticlesComponent.cs +++ b/OpenDreamShared/Rendering/DreamParticlesComponent.cs @@ -1,47 +1,54 @@ +using System; using System.Numerics; using OpenDreamShared.Dream; using Robust.Shared.Analyzers; using Robust.Shared.GameObjects; using Robust.Shared.GameStates; using Robust.Shared.Maths; +using Robust.Shared.Serialization; using Robust.Shared.ViewVariables; namespace OpenDreamShared.Rendering; [RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true, fieldDeltas: true)] public sealed partial class DreamParticlesComponent : Component { - [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public int Width; - [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public int Height; - [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public int Count; - [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public float Spawning; - [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public Vector3 Bound1; - [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public Vector3 Bound2; - [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public Vector3 Gravity; - [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public Color[] Gradient = []; - [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public Matrix3x2 Transform; - [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public ImmutableAppearance[] TextureList = []; - [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public IGeneratorNum? Lifespan; - [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public IGeneratorNum? FadeIn; - [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public IGeneratorNum? FadeOut; - - [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public IGeneratorVector? SpawnPosition; - - //Starting velocity of the particles - [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public IGeneratorVector? SpawnVelocity; - - //Acceleration applied to the particles per second - [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public IGeneratorVector? Friction; - - //Scaling applied to the particles in (x,y) - [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public IGeneratorVector Scale = new GeneratorNum(1); - - //Rotation applied to the particles in degrees - [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public IGeneratorNum? Rotation; - - //Increase in scale per second - [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public IGeneratorVector? Growth; - - //Change in rotation per second - [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public IGeneratorNum? Spin; - [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public IGeneratorVector? Drift; + [ViewVariables(VVAccess.ReadOnly), AutoNetworkedField] public ParticleData Data; + + [Serializable, NetSerializable] + public sealed class ParticleData { + [ViewVariables(VVAccess.ReadWrite)] public int Width; + [ViewVariables(VVAccess.ReadWrite)] public int Height; + [ViewVariables(VVAccess.ReadWrite)] public int Count; + [ViewVariables(VVAccess.ReadWrite)] public float Spawning; + [ViewVariables(VVAccess.ReadWrite)] public Vector3 Bound1; + [ViewVariables(VVAccess.ReadWrite)] public Vector3 Bound2; + [ViewVariables(VVAccess.ReadWrite)] public Vector3 Gravity; + [ViewVariables(VVAccess.ReadWrite)] public Color[] Gradient = []; + [ViewVariables(VVAccess.ReadWrite)] public Matrix3x2 Transform; + [ViewVariables(VVAccess.ReadWrite)] public ImmutableAppearance[] TextureList = []; + [ViewVariables(VVAccess.ReadWrite)] public IGeneratorNum? Lifespan; + [ViewVariables(VVAccess.ReadWrite)] public IGeneratorNum? FadeIn; + [ViewVariables(VVAccess.ReadWrite)] public IGeneratorNum? FadeOut; + + [ViewVariables(VVAccess.ReadWrite)] public IGeneratorVector? SpawnPosition; + + //Starting velocity of the particles + [ViewVariables(VVAccess.ReadWrite)] public IGeneratorVector? SpawnVelocity; + + //Acceleration applied to the particles per second + [ViewVariables(VVAccess.ReadWrite)] public IGeneratorVector? Friction; + + //Scaling applied to the particles in (x,y) + [ViewVariables(VVAccess.ReadWrite)] public IGeneratorVector Scale = new GeneratorNum(1); + + //Rotation applied to the particles in degrees + [ViewVariables(VVAccess.ReadWrite)] public IGeneratorNum? Rotation; + + //Increase in scale per second + [ViewVariables(VVAccess.ReadWrite)] public IGeneratorVector? Growth; + + //Change in rotation per second + [ViewVariables(VVAccess.ReadWrite)] public IGeneratorNum? Spin; + [ViewVariables(VVAccess.ReadWrite)] public IGeneratorVector? Drift; + } }