From b38e410ec0b30688757c6e136be9c544a9477429 Mon Sep 17 00:00:00 2001 From: amy Date: Wed, 5 Feb 2025 00:08:18 +0000 Subject: [PATCH 01/29] hacky but kinda working --- OpenDreamClient/Rendering/DreamViewOverlay.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/OpenDreamClient/Rendering/DreamViewOverlay.cs b/OpenDreamClient/Rendering/DreamViewOverlay.cs index ead729a933..a705968d9a 100644 --- a/OpenDreamClient/Rendering/DreamViewOverlay.cs +++ b/OpenDreamClient/Rendering/DreamViewOverlay.cs @@ -57,6 +57,7 @@ internal sealed partial class DreamViewOverlay : Overlay { private readonly ClientImagesSystem _clientImagesSystem; private readonly EntityQuery _spriteQuery; + private readonly EntityQuery _particlesQuery; private readonly EntityQuery _xformQuery; private readonly EntityQuery _mobSightQuery; @@ -88,6 +89,7 @@ public DreamViewOverlay(RenderTargetPool renderTargetPool, TransformSystem trans _clientImagesSystem = clientImagesSystem; _spriteQuery = _entityManager.GetEntityQuery(); + _particlesQuery = _entityManager.GetEntityQuery(); _xformQuery = _entityManager.GetEntityQuery(); _mobSightQuery = _entityManager.GetEntityQuery(); @@ -374,6 +376,12 @@ private void ProcessIconComponents(DreamIcon icon, Vector2 position, EntityUid u } //TODO particles - colour and transform don't apply? + //query entity for particles component + //if it has one, add it to the result list + if(_particlesQuery.TryGetComponent(current.Uid, out ParticlesComponent? particlesComponent) && particlesComponent.particlesSystem is not null){ + current.Particles ??= new(); + current.Particles.Add(particlesComponent.particlesSystem); + } //flatten KeepTogetherGroup. Done here so we get implicit recursive iteration down the tree. if (current.KeepTogetherGroup?.Count > 0) { @@ -451,6 +459,14 @@ public void DrawIcon(DrawingHandleWorld handle, Vector2i renderTargetSize, Rende var frame = iconMetaData.GetTexture(this, handle); var pixelPosition = (iconMetaData.Position + positionOffset) * EyeManager.PixelsPerMeter; + if(iconMetaData.Particles is not null) { + foreach(var particleSystem in iconMetaData.Particles){ + handle.UseShader(GetBlendAndColorShader(iconMetaData, ignoreColor: true)); + + handle.SetTransform(CalculateDrawingMatrix(iconMetaData.TransformToApply, pixelPosition, new Vector2i(200,200), renderTargetSize)); + particleSystem.Draw(handle); + } + } //if frame is null, this doesn't require a draw, so return NOP if (frame == null) return; @@ -799,6 +815,7 @@ internal sealed class RendererMetaData : IComparable { public Texture? TextureOverride; public string? Maptext; public Vector2i? MaptextSize; + public List? Particles; public bool IsPlaneMaster => (AppearanceFlags & AppearanceFlags.PlaneMaster) != 0; public bool HasRenderSource => !string.IsNullOrEmpty(RenderSource); @@ -830,6 +847,7 @@ public void Reset() { TextureOverride = null; Maptext = null; MaptextSize = null; + Particles = null; } public Texture? GetTexture(DreamViewOverlay viewOverlay, DrawingHandleWorld handle) { From 1abb28f3b53df1c5d273dccfc000476f070a91e1 Mon Sep 17 00:00:00 2001 From: amy Date: Thu, 6 Feb 2025 21:48:45 +0000 Subject: [PATCH 02/29] render target --- OpenDreamClient/Rendering/DreamViewOverlay.cs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/OpenDreamClient/Rendering/DreamViewOverlay.cs b/OpenDreamClient/Rendering/DreamViewOverlay.cs index a705968d9a..75914669d9 100644 --- a/OpenDreamClient/Rendering/DreamViewOverlay.cs +++ b/OpenDreamClient/Rendering/DreamViewOverlay.cs @@ -460,11 +460,24 @@ public void DrawIcon(DrawingHandleWorld handle, Vector2i renderTargetSize, Rende var pixelPosition = (iconMetaData.Position + positionOffset) * EyeManager.PixelsPerMeter; if(iconMetaData.Particles is not null) { + foreach(var particleSystem in iconMetaData.Particles){ + var particleRenderTarget = _renderTargetPool.Rent(particleSystem.RenderSize); + handle.UseShader(GetBlendAndColorShader(iconMetaData, ignoreColor: true)); + + //handle.SetTransform(CalculateDrawingMatrix(iconMetaData.TransformToApply, pixelPosition-new Vector2(-200,-200), new Vector2i(200,200), renderTargetSize)); + handle.RenderInRenderTarget(particleRenderTarget, + () => { + particleSystem.Draw(handle); + }, + Color.Transparent + ); handle.UseShader(GetBlendAndColorShader(iconMetaData, ignoreColor: true)); - handle.SetTransform(CalculateDrawingMatrix(iconMetaData.TransformToApply, pixelPosition, new Vector2i(200,200), renderTargetSize)); - particleSystem.Draw(handle); + handle.SetTransform(CalculateDrawingMatrix(iconMetaData.TransformToApply, pixelPosition, particleSystem.RenderSize, renderTargetSize)); + handle.DrawTextureRect(particleRenderTarget.Texture, Box2.FromDimensions(Vector2.Zero, particleSystem.RenderSize)); + + _renderTargetPool.ReturnAtEndOfFrame(particleRenderTarget); } } //if frame is null, this doesn't require a draw, so return NOP From b697f68efc4342ffc791d6fbd380e99aba79fb14 Mon Sep 17 00:00:00 2001 From: amy Date: Thu, 6 Feb 2025 21:52:41 +0000 Subject: [PATCH 03/29] fix offset --- OpenDreamClient/Rendering/DreamViewOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenDreamClient/Rendering/DreamViewOverlay.cs b/OpenDreamClient/Rendering/DreamViewOverlay.cs index 75914669d9..b30da22373 100644 --- a/OpenDreamClient/Rendering/DreamViewOverlay.cs +++ b/OpenDreamClient/Rendering/DreamViewOverlay.cs @@ -474,7 +474,7 @@ public void DrawIcon(DrawingHandleWorld handle, Vector2i renderTargetSize, Rende ); handle.UseShader(GetBlendAndColorShader(iconMetaData, ignoreColor: true)); - handle.SetTransform(CalculateDrawingMatrix(iconMetaData.TransformToApply, pixelPosition, particleSystem.RenderSize, renderTargetSize)); + handle.SetTransform(CalculateDrawingMatrix(iconMetaData.TransformToApply, pixelPosition-particleSystem.RenderSize/2, particleSystem.RenderSize, renderTargetSize)); handle.DrawTextureRect(particleRenderTarget.Texture, Box2.FromDimensions(Vector2.Zero, particleSystem.RenderSize)); _renderTargetPool.ReturnAtEndOfFrame(particleRenderTarget); From 6db920288763055b2805746fdca782f82bc445bc Mon Sep 17 00:00:00 2001 From: amy Date: Mon, 17 Feb 2025 21:53:47 +0000 Subject: [PATCH 04/29] manager rendering --- OpenDreamClient/Rendering/DreamViewOverlay.cs | 22 ++++++------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/OpenDreamClient/Rendering/DreamViewOverlay.cs b/OpenDreamClient/Rendering/DreamViewOverlay.cs index b30da22373..5caa652ae2 100644 --- a/OpenDreamClient/Rendering/DreamViewOverlay.cs +++ b/OpenDreamClient/Rendering/DreamViewOverlay.cs @@ -40,6 +40,8 @@ internal sealed partial class DreamViewOverlay : Overlay { [Dependency] private readonly IDreamInterfaceManager _interfaceManager = default!; [Dependency] private readonly IPlayerManager _playerManager = default!; [Dependency] private readonly IEntityManager _entityManager = default!; + [Dependency] private readonly ParticlesManager _particlesManager = default!; + [Dependency] private readonly IMapManager _mapManager = default!; [Dependency] private readonly IClyde _clyde = default!; [Dependency] private readonly IPrototypeManager _protoManager = default!; @@ -57,7 +59,7 @@ internal sealed partial class DreamViewOverlay : Overlay { private readonly ClientImagesSystem _clientImagesSystem; private readonly EntityQuery _spriteQuery; - private readonly EntityQuery _particlesQuery; + private readonly EntityQuery _particlesQuery; private readonly EntityQuery _xformQuery; private readonly EntityQuery _mobSightQuery; @@ -89,7 +91,7 @@ public DreamViewOverlay(RenderTargetPool renderTargetPool, TransformSystem trans _clientImagesSystem = clientImagesSystem; _spriteQuery = _entityManager.GetEntityQuery(); - _particlesQuery = _entityManager.GetEntityQuery(); + _particlesQuery = _entityManager.GetEntityQuery(); _xformQuery = _entityManager.GetEntityQuery(); _mobSightQuery = _entityManager.GetEntityQuery(); @@ -375,12 +377,11 @@ private void ProcessIconComponents(DreamIcon icon, Vector2 position, EntityUid u result.Add(maptext); } - //TODO particles - colour and transform don't apply? //query entity for particles component //if it has one, add it to the result list - if(_particlesQuery.TryGetComponent(current.Uid, out ParticlesComponent? particlesComponent) && particlesComponent.particlesSystem is not null){ + if(_particlesManager.TryGetParticleSystem(current.Uid, out var particlesSystem)){ current.Particles ??= new(); - current.Particles.Add(particlesComponent.particlesSystem); + current.Particles.Add(particlesSystem); } //flatten KeepTogetherGroup. Done here so we get implicit recursive iteration down the tree. @@ -464,16 +465,7 @@ public void DrawIcon(DrawingHandleWorld handle, Vector2i renderTargetSize, Rende foreach(var particleSystem in iconMetaData.Particles){ var particleRenderTarget = _renderTargetPool.Rent(particleSystem.RenderSize); handle.UseShader(GetBlendAndColorShader(iconMetaData, ignoreColor: true)); - - //handle.SetTransform(CalculateDrawingMatrix(iconMetaData.TransformToApply, pixelPosition-new Vector2(-200,-200), new Vector2i(200,200), renderTargetSize)); - handle.RenderInRenderTarget(particleRenderTarget, - () => { - particleSystem.Draw(handle); - }, - Color.Transparent - ); - handle.UseShader(GetBlendAndColorShader(iconMetaData, ignoreColor: true)); - + particleSystem.Draw(handle, pixelPosition); handle.SetTransform(CalculateDrawingMatrix(iconMetaData.TransformToApply, pixelPosition-particleSystem.RenderSize/2, particleSystem.RenderSize, renderTargetSize)); handle.DrawTextureRect(particleRenderTarget.Texture, Box2.FromDimensions(Vector2.Zero, particleSystem.RenderSize)); From 01299249385e450cdb543b74240c8257c85045d1 Mon Sep 17 00:00:00 2001 From: amy Date: Mon, 17 Feb 2025 22:14:59 +0000 Subject: [PATCH 05/29] Component system --- .../Rendering/ClientDreamParticlesSystem.cs | 111 ++++++++++++++++++ .../Rendering/DreamParticlesComponent.cs | 10 ++ .../Rendering/DreamParticlesComponent.cs | 7 ++ .../Rendering/ServerDreamParticleSystem.cs | 5 + .../Rendering/DreamParticlesComponent.cs | 68 +++++++++++ .../Rendering/SharedDreamParticleSystem.cs | 6 + 6 files changed, 207 insertions(+) create mode 100644 OpenDreamClient/Rendering/ClientDreamParticlesSystem.cs create mode 100644 OpenDreamClient/Rendering/DreamParticlesComponent.cs create mode 100644 OpenDreamRuntime/Rendering/DreamParticlesComponent.cs create mode 100644 OpenDreamRuntime/Rendering/ServerDreamParticleSystem.cs create mode 100644 OpenDreamShared/Rendering/DreamParticlesComponent.cs create mode 100644 OpenDreamShared/Rendering/SharedDreamParticleSystem.cs diff --git a/OpenDreamClient/Rendering/ClientDreamParticlesSystem.cs b/OpenDreamClient/Rendering/ClientDreamParticlesSystem.cs new file mode 100644 index 0000000000..bcf23e4f56 --- /dev/null +++ b/OpenDreamClient/Rendering/ClientDreamParticlesSystem.cs @@ -0,0 +1,111 @@ +using JetBrains.Annotations; +using OpenDreamShared.Rendering; +using Robust.Client.Graphics; +using Robust.Client.ResourceManagement; +using Robust.Shared.Prototypes; +using Robust.Shared.Random; +using Vector3 = Robust.Shared.Maths.Vector3; + +namespace OpenDreamClient.Rendering; + +[UsedImplicitly] +public sealed class ClientDreamParticlesSystem : SharedDreamParticlesSystem +{ + [Dependency] private readonly ParticlesManager _particlesManager = default!; + [Dependency] private readonly IResourceCache _resourceCache = default!; + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + private Random random = new(); + + public override void Initialize() { + base.Initialize(); + SubscribeLocalEvent(OnDynamicParticlesComponentChange); + SubscribeLocalEvent(HandleComponentAdd); + SubscribeLocalEvent(HandleComponentRemove); + } + + private void OnDynamicParticlesComponentChange(EntityUid uid, DynamicParticlesComponent component, ref ComponentChange args) + { + if(_particlesManager.TryGetParticleSystem(uid, out var system)) + system.UpdateSystem(GetParticleSystemArgs(component)); + } + + private void HandleComponentAdd(EntityUid uid, DynamicParticlesComponent component, ref ComponentAdd args) + { + component.particlesSystem = _particlesManager.CreateParticleSystem(uid, GetParticleSystemArgs(component)); + } + + private void HandleComponentRemove(EntityUid uid, DynamicParticlesComponent component, ref ComponentRemove args) + { + component.particlesSystem = null; + _particlesManager.DestroyParticleSystem(uid); + } + + private ParticleSystemArgs GetParticleSystemArgs(DynamicParticlesComponent component){ + Func textureFunc; + if(component.TextureList is null || component.TextureList.Length == 0) + textureFunc = () => Texture.White; + else + textureFunc = () => _resourceCache.GetResource(new Random().Pick(component.TextureList)); //TODO + + var result = new ParticleSystemArgs(textureFunc, new Vector2i(component.Width, component.Height), (uint)component.Count, component.Spawning); + + GeneratorFloat lifespan = new(); + result.Lifespan = GetGeneratorFloat(component.LifespanLow, component.LifespanHigh, component.LifespanType); + result.Fadein = GetGeneratorFloat(component.FadeInLow, component.FadeInHigh, component.FadeInType); + result.Fadeout = GetGeneratorFloat(component.FadeOutLow, component.FadeOutHigh, component.FadeOutType); + if(component.ColorList.Length > 0) + result.Color = (float lifetime) => { + var colorIndex = (int)(lifetime * component.ColorList.Length); + colorIndex = Math.Clamp(colorIndex, 0, component.ColorList.Length - 1); + return component.ColorList[colorIndex]; + }; + else + result.Color = (float lifetime) => System.Drawing.Color.White; + result.Acceleration = (float _ ) => GetGeneratorVector3(component.AccelerationLow, component.AccelerationHigh, component.AccelerationType)(); + result.SpawnPosition = GetGeneratorVector3(component.SpawnPositionLow, component.SpawnPositionHigh, component.SpawnPositionType); + result.SpawnVelocity = GetGeneratorVector3(component.SpawnVelocityLow, component.SpawnVelocityHigh, component.SpawnVelocityType); + result.Transform = (float lifetime) => { + var scale = GetGeneratorVector2(component.ScaleLow, component.ScaleHigh, component.ScaleType)(); + var rotation = GetGeneratorFloat(component.RotationLow, component.RotationHigh, component.RotationType)(); + var growth = GetGeneratorVector2(component.GrowthLow, component.GrowthHigh, component.GrowthType)(); + var spin = GetGeneratorFloat(component.SpinLow, component.SpinHigh, component.SpinType)(); + return Matrix3x2.CreateScale(scale.X + growth.X, scale.Y + growth.Y) * + Matrix3x2.CreateRotation(rotation + spin); + }; + + return result; + } + + private Func GetGeneratorFloat(float low, float high, ParticlePropertyType type){ + switch (type) { + case ParticlePropertyType.HighValue: + return () => high; + case ParticlePropertyType.RandomUniform: + return () => random.NextFloat(low, high); + default: + throw new NotImplementedException(); + } + } + + private Func GetGeneratorVector2(Vector2 low, Vector2 high, ParticlePropertyType type){ + switch (type) { + case ParticlePropertyType.HighValue: + return () => high; + case ParticlePropertyType.RandomUniform: + return () => new Vector2(random.NextFloat(low.X, high.X), random.NextFloat(low.Y, high.Y)); + default: + throw new NotImplementedException(); + } + } + + private Func GetGeneratorVector3(Vector3 low, Vector3 high, ParticlePropertyType type){ + switch (type) { + case ParticlePropertyType.HighValue: + return () => high; + case ParticlePropertyType.RandomUniform: + return () => new Vector3(random.NextFloat(low.X, high.X), random.NextFloat(low.Y, high.Y), random.NextFloat(low.Z, high.Z)); + default: + throw new NotImplementedException(); + } + } +} diff --git a/OpenDreamClient/Rendering/DreamParticlesComponent.cs b/OpenDreamClient/Rendering/DreamParticlesComponent.cs new file mode 100644 index 0000000000..9cf5d70d3a --- /dev/null +++ b/OpenDreamClient/Rendering/DreamParticlesComponent.cs @@ -0,0 +1,10 @@ + +using OpenDreamShared.Rendering; +using Robust.Client.Graphics; + +namespace OpenDreamClient.Rendering; + +[RegisterComponent] +public sealed partial class DreamParticlesComponent : SharedDreamParticlesComponent { + public ParticleSystem? particlesSystem; +} diff --git a/OpenDreamRuntime/Rendering/DreamParticlesComponent.cs b/OpenDreamRuntime/Rendering/DreamParticlesComponent.cs new file mode 100644 index 0000000000..41e748ff2e --- /dev/null +++ b/OpenDreamRuntime/Rendering/DreamParticlesComponent.cs @@ -0,0 +1,7 @@ +using OpenDreamShared.Rendering; +using Robust.Shared.GameObjects; + +namespace OpenDreamShared.Rendering; + +[RegisterComponent] +public sealed partial class DynamicParticlesComponent : SharedDreamParticlesComponent {} diff --git a/OpenDreamRuntime/Rendering/ServerDreamParticleSystem.cs b/OpenDreamRuntime/Rendering/ServerDreamParticleSystem.cs new file mode 100644 index 0000000000..d530f7cdb2 --- /dev/null +++ b/OpenDreamRuntime/Rendering/ServerDreamParticleSystem.cs @@ -0,0 +1,5 @@ +using OpenDreamShared.Rendering; + +namespace OpenDreamRuntime.Rendering; + +public sealed class ServerDreamParticlesSystem : SharedDreamParticlesSystem {} diff --git a/OpenDreamShared/Rendering/DreamParticlesComponent.cs b/OpenDreamShared/Rendering/DreamParticlesComponent.cs new file mode 100644 index 0000000000..6264c0538e --- /dev/null +++ b/OpenDreamShared/Rendering/DreamParticlesComponent.cs @@ -0,0 +1,68 @@ + +using System.Numerics; +using Robust.Shared.GameObjects; +using Robust.Shared.GameStates; +using Robust.Shared.Utility; +using Robust.Shared.ViewVariables; +using Color = System.Drawing.Color; +using Vector3 = Robust.Shared.Maths.Vector3; + +namespace OpenDreamShared.Rendering; + +[NetworkedComponent] +public abstract partial class SharedDreamParticlesComponent : Component { + //The width and height of the drawing surface for the particles - this is the size of the texture that the particles are drawn on. + [ViewVariables(VVAccess.ReadWrite)] public int Width = 640; + [ViewVariables(VVAccess.ReadWrite)] public int Height = 480; + //Maximum number of particles in this system + [ViewVariables(VVAccess.ReadWrite)] public int Count = 1000; + //Maximum number of particles spawned per second + [ViewVariables(VVAccess.ReadWrite)] public float Spawning = 100; + //Texture that the particles have. This is a list of paths to textures, and the particle system will randomly choose one of them for each particle. If blank, the particle will be a 1px white dot. + [ViewVariables(VVAccess.ReadWrite)] public ResPath[] TextureList = []; + //Maximum lifespan of the partcles in seconds + [ViewVariables(VVAccess.ReadWrite)] public float LifespanHigh; + [ViewVariables(VVAccess.ReadWrite)] public float LifespanLow; + [ViewVariables(VVAccess.ReadWrite)] public ParticlePropertyType LifespanType; + [ViewVariables(VVAccess.ReadWrite)] public int FadeInHigh = 0; + [ViewVariables(VVAccess.ReadWrite)] public int FadeInLow = 0; + [ViewVariables(VVAccess.ReadWrite)] public ParticlePropertyType FadeInType; + [ViewVariables(VVAccess.ReadWrite)] public int FadeOutHigh = 0; + [ViewVariables(VVAccess.ReadWrite)] public int FadeOutLow = 0; + [ViewVariables(VVAccess.ReadWrite)] public ParticlePropertyType FadeOutType; + //Color of the particles. This can either be a list of a colours, or a gradient that the particles will interpolate between over their lifespan. + [ViewVariables(VVAccess.ReadWrite)] public Color[] ColorList = []; + //Starting position of the particles. X,Y,Z. The Z co-ordinate determines layering order. + [ViewVariables(VVAccess.ReadWrite)] public Vector3 SpawnPositionHigh; + [ViewVariables(VVAccess.ReadWrite)] public Vector3 SpawnPositionLow; + [ViewVariables(VVAccess.ReadWrite)] public ParticlePropertyType SpawnPositionType; + //Starting velocity of the particles + [ViewVariables(VVAccess.ReadWrite)] public Vector3 SpawnVelocityHigh; + [ViewVariables(VVAccess.ReadWrite)] public Vector3 SpawnVelocityLow; + [ViewVariables(VVAccess.ReadWrite)] public ParticlePropertyType SpawnVelocityType; + //Acceleration applied to the particles per second + [ViewVariables(VVAccess.ReadWrite)] public Vector3 AccelerationHigh; + [ViewVariables(VVAccess.ReadWrite)] public Vector3 AccelerationLow; + [ViewVariables(VVAccess.ReadWrite)] public ParticlePropertyType AccelerationType; + //Scaling applied to the particles in (x,y) + [ViewVariables(VVAccess.ReadWrite)] public Vector2 ScaleHigh; + [ViewVariables(VVAccess.ReadWrite)] public Vector2 ScaleLow; + [ViewVariables(VVAccess.ReadWrite)] public ParticlePropertyType ScaleType; + //Rotation applied to the particles in degrees + [ViewVariables(VVAccess.ReadWrite)] public float RotationHigh = 0; + [ViewVariables(VVAccess.ReadWrite)] public float RotationLow = 0; + [ViewVariables(VVAccess.ReadWrite)] public ParticlePropertyType RotationType; + //Increase in scale per second + [ViewVariables(VVAccess.ReadWrite)] public Vector2 GrowthHigh; + [ViewVariables(VVAccess.ReadWrite)] public Vector2 GrowthLow; + [ViewVariables(VVAccess.ReadWrite)] public ParticlePropertyType GrowthType; + //Change in rotation per second + [ViewVariables(VVAccess.ReadWrite)] public float SpinHigh = 0; + [ViewVariables(VVAccess.ReadWrite)] public float SpinLow = 0; + [ViewVariables(VVAccess.ReadWrite)] public ParticlePropertyType SpinType; +} + +public enum ParticlePropertyType { + HighValue, + RandomUniform +} diff --git a/OpenDreamShared/Rendering/SharedDreamParticleSystem.cs b/OpenDreamShared/Rendering/SharedDreamParticleSystem.cs new file mode 100644 index 0000000000..2a51e7c465 --- /dev/null +++ b/OpenDreamShared/Rendering/SharedDreamParticleSystem.cs @@ -0,0 +1,6 @@ + +using Robust.Shared.GameObjects; + +namespace OpenDreamShared.Rendering; + +public abstract class SharedDreamParticlesSystem : EntitySystem {} From 9138e9c3eaa743a726ba28922e87a70995203f2e Mon Sep 17 00:00:00 2001 From: amy Date: Mon, 17 Feb 2025 22:47:30 +0000 Subject: [PATCH 06/29] sandbox --- .../Rendering/ClientDreamParticlesSystem.cs | 18 ++++++++---------- .../Rendering/DreamParticlesComponent.cs | 2 +- .../Rendering/DreamParticlesComponent.cs | 7 +------ 3 files changed, 10 insertions(+), 17 deletions(-) diff --git a/OpenDreamClient/Rendering/ClientDreamParticlesSystem.cs b/OpenDreamClient/Rendering/ClientDreamParticlesSystem.cs index bcf23e4f56..7792795299 100644 --- a/OpenDreamClient/Rendering/ClientDreamParticlesSystem.cs +++ b/OpenDreamClient/Rendering/ClientDreamParticlesSystem.cs @@ -2,7 +2,6 @@ using OpenDreamShared.Rendering; using Robust.Client.Graphics; using Robust.Client.ResourceManagement; -using Robust.Shared.Prototypes; using Robust.Shared.Random; using Vector3 = Robust.Shared.Maths.Vector3; @@ -13,34 +12,33 @@ public sealed class ClientDreamParticlesSystem : SharedDreamParticlesSystem { [Dependency] private readonly ParticlesManager _particlesManager = default!; [Dependency] private readonly IResourceCache _resourceCache = default!; - [Dependency] private readonly IPrototypeManager _prototypeManager = default!; private Random random = new(); public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnDynamicParticlesComponentChange); - SubscribeLocalEvent(HandleComponentAdd); - SubscribeLocalEvent(HandleComponentRemove); + SubscribeLocalEvent(OnDreamParticlesComponentChange); + SubscribeLocalEvent(HandleComponentAdd); + SubscribeLocalEvent(HandleComponentRemove); } - private void OnDynamicParticlesComponentChange(EntityUid uid, DynamicParticlesComponent component, ref ComponentChange args) + private void OnDreamParticlesComponentChange(EntityUid uid, DreamParticlesComponent component, ref ComponentChange args) { if(_particlesManager.TryGetParticleSystem(uid, out var system)) system.UpdateSystem(GetParticleSystemArgs(component)); } - private void HandleComponentAdd(EntityUid uid, DynamicParticlesComponent component, ref ComponentAdd args) + private void HandleComponentAdd(EntityUid uid, DreamParticlesComponent component, ref ComponentAdd args) { component.particlesSystem = _particlesManager.CreateParticleSystem(uid, GetParticleSystemArgs(component)); } - private void HandleComponentRemove(EntityUid uid, DynamicParticlesComponent component, ref ComponentRemove args) + private void HandleComponentRemove(EntityUid uid, DreamParticlesComponent component, ref ComponentRemove args) { component.particlesSystem = null; _particlesManager.DestroyParticleSystem(uid); } - private ParticleSystemArgs GetParticleSystemArgs(DynamicParticlesComponent component){ + private ParticleSystemArgs GetParticleSystemArgs(DreamParticlesComponent component){ Func textureFunc; if(component.TextureList is null || component.TextureList.Length == 0) textureFunc = () => Texture.White; @@ -60,7 +58,7 @@ private ParticleSystemArgs GetParticleSystemArgs(DynamicParticlesComponent compo return component.ColorList[colorIndex]; }; else - result.Color = (float lifetime) => System.Drawing.Color.White; + result.Color = (float lifetime) => Color.White; result.Acceleration = (float _ ) => GetGeneratorVector3(component.AccelerationLow, component.AccelerationHigh, component.AccelerationType)(); result.SpawnPosition = GetGeneratorVector3(component.SpawnPositionLow, component.SpawnPositionHigh, component.SpawnPositionType); result.SpawnVelocity = GetGeneratorVector3(component.SpawnVelocityLow, component.SpawnVelocityHigh, component.SpawnVelocityType); diff --git a/OpenDreamRuntime/Rendering/DreamParticlesComponent.cs b/OpenDreamRuntime/Rendering/DreamParticlesComponent.cs index 41e748ff2e..7cfbe703a3 100644 --- a/OpenDreamRuntime/Rendering/DreamParticlesComponent.cs +++ b/OpenDreamRuntime/Rendering/DreamParticlesComponent.cs @@ -4,4 +4,4 @@ namespace OpenDreamShared.Rendering; [RegisterComponent] -public sealed partial class DynamicParticlesComponent : SharedDreamParticlesComponent {} +public sealed partial class DreamParticlesComponent : SharedDreamParticlesComponent {} diff --git a/OpenDreamShared/Rendering/DreamParticlesComponent.cs b/OpenDreamShared/Rendering/DreamParticlesComponent.cs index 6264c0538e..34c2c8f1bc 100644 --- a/OpenDreamShared/Rendering/DreamParticlesComponent.cs +++ b/OpenDreamShared/Rendering/DreamParticlesComponent.cs @@ -2,9 +2,9 @@ using System.Numerics; using Robust.Shared.GameObjects; using Robust.Shared.GameStates; +using Robust.Shared.Maths; using Robust.Shared.Utility; using Robust.Shared.ViewVariables; -using Color = System.Drawing.Color; using Vector3 = Robust.Shared.Maths.Vector3; namespace OpenDreamShared.Rendering; @@ -61,8 +61,3 @@ public abstract partial class SharedDreamParticlesComponent : Component { [ViewVariables(VVAccess.ReadWrite)] public float SpinLow = 0; [ViewVariables(VVAccess.ReadWrite)] public ParticlePropertyType SpinType; } - -public enum ParticlePropertyType { - HighValue, - RandomUniform -} From 3d371935fc985a3178d4ac1fa877f8561179cadb Mon Sep 17 00:00:00 2001 From: amy Date: Mon, 17 Feb 2025 23:59:09 +0000 Subject: [PATCH 07/29] TODO: stop taking on enormous projects when tired --- .../Objects/Types/DreamObjectParticles.cs | 25 +++++++++++++++++++ .../Rendering/DreamParticlesComponent.cs | 20 ++++++++------- 2 files changed, 36 insertions(+), 9 deletions(-) create mode 100644 OpenDreamRuntime/Objects/Types/DreamObjectParticles.cs diff --git a/OpenDreamRuntime/Objects/Types/DreamObjectParticles.cs b/OpenDreamRuntime/Objects/Types/DreamObjectParticles.cs new file mode 100644 index 0000000000..5b01f07e84 --- /dev/null +++ b/OpenDreamRuntime/Objects/Types/DreamObjectParticles.cs @@ -0,0 +1,25 @@ +using OpenDreamRuntime.Procs; +using OpenDreamRuntime.Rendering; +using OpenDreamShared.Dream; +using OpenDreamShared.Rendering; +using Robust.Shared.Map; + +namespace OpenDreamRuntime.Objects.Types; + +public sealed class DreamObjectParticles : DreamObject { + public EntityUid Entity = EntityUid.Invalid; + public DreamParticlesComponent ParticlesComponent; + + public DreamObjectParticles(DreamObjectDefinition objectDefinition) : base(objectDefinition) { + Entity = EntityManager.SpawnEntity(null, new MapCoordinates(0, 0, MapId.Nullspace)); //spawning an entity in nullspace means it never actually gets sent to any clients until it's placed on the map, or it gets a PVS override + ParticlesComponent = EntityManager.AddComponent(Entity); + //populate component with settings from type + //do set/get var to grab those also + //check if I need to manually send update events to the component? + //add entity array to appearance objects + //collect entities client-side for the rendermetadata + //idk I guess bodge generators right now? + //set up a special list type on /atom for /particles + + } +} diff --git a/OpenDreamShared/Rendering/DreamParticlesComponent.cs b/OpenDreamShared/Rendering/DreamParticlesComponent.cs index 34c2c8f1bc..d83fbb8217 100644 --- a/OpenDreamShared/Rendering/DreamParticlesComponent.cs +++ b/OpenDreamShared/Rendering/DreamParticlesComponent.cs @@ -1,5 +1,6 @@ using System.Numerics; +using OpenDreamShared.Dream; using Robust.Shared.GameObjects; using Robust.Shared.GameStates; using Robust.Shared.Maths; @@ -11,16 +12,16 @@ namespace OpenDreamShared.Rendering; [NetworkedComponent] public abstract partial class SharedDreamParticlesComponent : Component { - //The width and height of the drawing surface for the particles - this is the size of the texture that the particles are drawn on. [ViewVariables(VVAccess.ReadWrite)] public int Width = 640; [ViewVariables(VVAccess.ReadWrite)] public int Height = 480; - //Maximum number of particles in this system [ViewVariables(VVAccess.ReadWrite)] public int Count = 1000; - //Maximum number of particles spawned per second [ViewVariables(VVAccess.ReadWrite)] public float Spawning = 100; - //Texture that the particles have. This is a list of paths to textures, and the particle system will randomly choose one of them for each particle. If blank, the particle will be a 1px white dot. - [ViewVariables(VVAccess.ReadWrite)] public ResPath[] TextureList = []; - //Maximum lifespan of the partcles in seconds + [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 float LifespanHigh; [ViewVariables(VVAccess.ReadWrite)] public float LifespanLow; [ViewVariables(VVAccess.ReadWrite)] public ParticlePropertyType LifespanType; @@ -30,9 +31,7 @@ public abstract partial class SharedDreamParticlesComponent : Component { [ViewVariables(VVAccess.ReadWrite)] public int FadeOutHigh = 0; [ViewVariables(VVAccess.ReadWrite)] public int FadeOutLow = 0; [ViewVariables(VVAccess.ReadWrite)] public ParticlePropertyType FadeOutType; - //Color of the particles. This can either be a list of a colours, or a gradient that the particles will interpolate between over their lifespan. - [ViewVariables(VVAccess.ReadWrite)] public Color[] ColorList = []; - //Starting position of the particles. X,Y,Z. The Z co-ordinate determines layering order. + [ViewVariables(VVAccess.ReadWrite)] public Vector3 SpawnPositionHigh; [ViewVariables(VVAccess.ReadWrite)] public Vector3 SpawnPositionLow; [ViewVariables(VVAccess.ReadWrite)] public ParticlePropertyType SpawnPositionType; @@ -44,6 +43,9 @@ public abstract partial class SharedDreamParticlesComponent : Component { [ViewVariables(VVAccess.ReadWrite)] public Vector3 AccelerationHigh; [ViewVariables(VVAccess.ReadWrite)] public Vector3 AccelerationLow; [ViewVariables(VVAccess.ReadWrite)] public ParticlePropertyType AccelerationType; + [ViewVariables(VVAccess.ReadWrite)] public Vector3 FrictionHigh; + [ViewVariables(VVAccess.ReadWrite)] public Vector3 FrictionLow; + [ViewVariables(VVAccess.ReadWrite)] public ParticlePropertyType FrictionType; //Scaling applied to the particles in (x,y) [ViewVariables(VVAccess.ReadWrite)] public Vector2 ScaleHigh; [ViewVariables(VVAccess.ReadWrite)] public Vector2 ScaleLow; From 13342413f1c8cf09e4556f31dd5bdf15e70a6b82 Mon Sep 17 00:00:00 2001 From: amy Date: Tue, 18 Feb 2025 22:04:27 +0000 Subject: [PATCH 08/29] jesus christ this is so much shit --- .../Objects/Types/DreamObjectParticles.cs | 158 ++++++++++++++++++ .../Rendering/DreamParticlesComponent.cs | 3 + 2 files changed, 161 insertions(+) diff --git a/OpenDreamRuntime/Objects/Types/DreamObjectParticles.cs b/OpenDreamRuntime/Objects/Types/DreamObjectParticles.cs index 5b01f07e84..230df1723e 100644 --- a/OpenDreamRuntime/Objects/Types/DreamObjectParticles.cs +++ b/OpenDreamRuntime/Objects/Types/DreamObjectParticles.cs @@ -1,15 +1,21 @@ using OpenDreamRuntime.Procs; using OpenDreamRuntime.Rendering; +using OpenDreamRuntime.Resources; using OpenDreamShared.Dream; using OpenDreamShared.Rendering; using Robust.Shared.Map; +using Vector3 = Robust.Shared.Maths.Vector3; namespace OpenDreamRuntime.Objects.Types; public sealed class DreamObjectParticles : DreamObject { + private static readonly DreamResourceManager _resourceManager = IoCManager.Resolve(); public EntityUid Entity = EntityUid.Invalid; public DreamParticlesComponent ParticlesComponent; + private List _icons = new(); + private List _iconStates = new(); + public DreamObjectParticles(DreamObjectDefinition objectDefinition) : base(objectDefinition) { Entity = EntityManager.SpawnEntity(null, new MapCoordinates(0, 0, MapId.Nullspace)); //spawning an entity in nullspace means it never actually gets sent to any clients until it's placed on the map, or it gets a PVS override ParticlesComponent = EntityManager.AddComponent(Entity); @@ -22,4 +28,156 @@ public DreamObjectParticles(DreamObjectDefinition objectDefinition) : base(objec //set up a special list type on /atom for /particles } + + 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 = value.MustGetValueAsInteger(); + break; + case "height": //num + ParticlesComponent.Height = value.MustGetValueAsInteger(); + break; + case "count": //num + ParticlesComponent.Count = value.MustGetValueAsInteger(); + break; + case "spawning": //num + ParticlesComponent.Spawning = value.MustGetValueAsFloat(); + break; + case "bound1": //list or vector + if(value.TryGetValueAsDreamList(out var bound1List) && bound1List.GetLength() >= 3) { + List dreamValues = bound1List.GetValues(); + ParticlesComponent.Bound1 = new Vector3(dreamValues[0].MustGetValueAsFloat(), dreamValues[1].MustGetValueAsFloat(), dreamValues[2].MustGetValueAsFloat()); + } //else if vector + break; + case "bound2": //list or vector + if(value.TryGetValueAsDreamList(out var bound2List) && bound2List.GetLength() >= 3) { + List dreamValues = bound2List.GetValues(); + ParticlesComponent.Bound2 = new Vector3(dreamValues[0].MustGetValueAsFloat(), dreamValues[1].MustGetValueAsFloat(), dreamValues[2].MustGetValueAsFloat()); + } //else if vector + break; + case "gravity": //list or vector + if(value.TryGetValueAsDreamList(out var gravityList) && gravityList.GetLength() >= 3) { + List dreamValues = gravityList.GetValues(); + ParticlesComponent.Gravity = new Vector3(dreamValues[0].MustGetValueAsFloat(), dreamValues[1].MustGetValueAsFloat(), dreamValues[2].MustGetValueAsFloat()); + } //else if vector + break; + case "gradient": //color gradient list + if(value.TryGetValueAsDreamList(out var colorList)){ + List grad = new(colorList.GetLength()); + foreach(DreamValue colorValue in colorList.GetValues()){ + if (ColorHelpers.TryParseColor(colorValue.MustGetValueAsString(), out var c, defaultAlpha: string.Empty)) + grad.Add(c); + } + ParticlesComponent.Gradient = grad.ToArray(); + } + 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]); + } + break; + case "icon": //list or icon + _icons.Clear(); + if(value.TryGetValueAsDreamList(out var iconList)){ + foreach(DreamValue iconValue in iconList.GetValues()){ + if(iconValue.TryGetValueAsDreamObject(out var icon)){ + _icons.Add(AtomManager.MustGetAppearance(icon).ToMutable()); + } + } + } else if(value.TryGetValueAsDreamObject(out var dreamObjectIcon)) { + _icons.Add(AtomManager.MustGetAppearance(dreamObjectIcon).ToMutable()); + } + List immutableAppearances = new(); + foreach(var icon in _icons){ + foreach(var iconState in _iconStates){ + MutableAppearance iconCombo = MutableAppearance.GetCopy(icon); + iconCombo.IconState = iconState; + immutableAppearances.Add(AppearanceSystem!.AddAppearance(iconCombo)); + } + } + ParticlesComponent.TextureList = immutableAppearances.ToArray(); + break; + case "icon_state": //list or string + _iconStates.Clear(); + if(value.TryGetValueAsDreamList(out var iconStateList)){ + foreach(DreamValue iconValue in iconStateList.GetValues()){ + if(iconValue.TryGetValueAsString(out var iconState)){ + _iconStates.Add(iconState); + } + } + } else if(value.TryGetValueAsString(out var iconState)) { + _iconStates.Add(iconState); + } + immutableAppearances = new(); + foreach(var icon in _icons){ + foreach(var iconState in _iconStates){ + MutableAppearance iconCombo = MutableAppearance.GetCopy(icon); + iconCombo.IconState = iconState; + immutableAppearances.Add(AppearanceSystem!.AddAppearance(iconCombo)); + } + } + ParticlesComponent.TextureList = immutableAppearances.ToArray(); + break; + case "lifespan": //num or generator + ParticlesComponent.LifespanHigh = value.GetValueAsFloat(); + ParticlesComponent.LifespanLow = value.GetValueAsFloat(); + ParticlesComponent.LifespanType = value.GetValueAsParticlePropertyType(); + break; + case "fadein": //num or generator + ParticlesComponent.FadeInHigh = value.GetValueAsInteger(); + ParticlesComponent.FadeInLow = value.GetValueAsInteger(); + ParticlesComponent.FadeInType = value.GetValueAsParticlePropertyType(); + break; + case "fade": //num or generator + ParticlesComponent.FadeOutHigh = value.GetValueAsInteger(); + ParticlesComponent.FadeOutLow = value.GetValueAsInteger(); + ParticlesComponent.FadeOutType = value.GetValueAsParticlePropertyType(); + break; + case "position": //list, vector, or generator + ParticlesComponent.SpawnPositionHigh = value.GetValueAsVector3(); + ParticlesComponent.SpawnPositionLow = value.GetValueAsVector3(); + ParticlesComponent.SpawnPositionType = value.GetValueAsParticlePropertyType(); + break; + case "velocity": //list, vector, or generator + ParticlesComponent.SpawnPositionHigh = value.GetValueAsVector3(); + ParticlesComponent.SpawnPositionLow = value.GetValueAsVector3(); + ParticlesComponent.SpawnPositionType = value.GetValueAsParticlePropertyType(); + break; + case "scale": //num, list, vector, or generator + ParticlesComponent.ScaleHigh = value.GetValueAsVector2(); + ParticlesComponent.ScaleLow = value.GetValueAsVector2(); + ParticlesComponent.ScaleType = value.GetValueAsParticlePropertyType(); + break; + case "grow": //num, list, vector, or generator + ParticlesComponent.GrowthHigh = value.GetValueAsFloat(); + ParticlesComponent.GrowthLow = value.GetValueAsFloat(); + ParticlesComponent.GrowthType = value.GetValueAsParticlePropertyType(); + break; + case "rotation": //num or generator + ParticlesComponent.RotationHigh = value.GetValueAsFloat(); + ParticlesComponent.RotationLow = value.GetValueAsFloat(); + ParticlesComponent.RotationType = value.GetValueAsParticlePropertyType(); + break; + case "spin": //num or generator + ParticlesComponent.SpinHigh = value.GetValueAsFloat(); + ParticlesComponent.SpinLow = value.GetValueAsFloat(); + ParticlesComponent.SpinType = value.GetValueAsParticlePropertyType(); + break; + case "friction": //num or generator + ParticlesComponent.FrictionHigh = value.GetValueAsFloat(); + ParticlesComponent.FrictionLow = value.GetValueAsFloat(); + ParticlesComponent.FrictionType = value.GetValueAsParticlePropertyType(); + break; + case "drift": //num or generator + ParticlesComponent.DriftHigh = value.GetValueAsVector3(); + ParticlesComponent.DriftLow = value.GetValueAsVector3(); + ParticlesComponent.DriftType = value.GetValueAsParticlePropertyType(); + break; + default: + base.SetVar(varName, value); + break; + } + } } diff --git a/OpenDreamShared/Rendering/DreamParticlesComponent.cs b/OpenDreamShared/Rendering/DreamParticlesComponent.cs index d83fbb8217..8202bfcba3 100644 --- a/OpenDreamShared/Rendering/DreamParticlesComponent.cs +++ b/OpenDreamShared/Rendering/DreamParticlesComponent.cs @@ -62,4 +62,7 @@ public abstract partial class SharedDreamParticlesComponent : Component { [ViewVariables(VVAccess.ReadWrite)] public float SpinHigh = 0; [ViewVariables(VVAccess.ReadWrite)] public float SpinLow = 0; [ViewVariables(VVAccess.ReadWrite)] public ParticlePropertyType SpinType; + [ViewVariables(VVAccess.ReadWrite)] public Vector3 DriftHigh; + [ViewVariables(VVAccess.ReadWrite)] public Vector3 DriftLow; + [ViewVariables(VVAccess.ReadWrite)] public ParticlePropertyType DriftType; } From 82b6b866217878764ae0f7b1c881884fceb4d91c Mon Sep 17 00:00:00 2001 From: amy Date: Tue, 18 Feb 2025 23:09:46 +0000 Subject: [PATCH 09/29] generator --- DMCompiler/DMStandard/UnsortedAdditions.dm | 2 - OpenDreamRuntime/Objects/DreamObjectTree.cs | 8 +++ .../Objects/Types/DreamObjectGenerator.cs | 40 ++++++++++++ .../Procs/Native/DreamProcNativeRoot.cs | 62 +++++++++++++++++++ 4 files changed, 110 insertions(+), 2 deletions(-) create mode 100644 OpenDreamRuntime/Objects/Types/DreamObjectGenerator.cs diff --git a/DMCompiler/DMStandard/UnsortedAdditions.dm b/DMCompiler/DMStandard/UnsortedAdditions.dm index c2542bd97c..5c83a94531 100644 --- a/DMCompiler/DMStandard/UnsortedAdditions.dm +++ b/DMCompiler/DMStandard/UnsortedAdditions.dm @@ -16,8 +16,6 @@ set opendream_unimplemented = TRUE /proc/findtextEx_char(Haystack,Needle,Start=1,End=0) set opendream_unimplemented = TRUE -/proc/generator(type, A, B, rand) - set opendream_unimplemented = TRUE /proc/load_resource(File) set opendream_unimplemented = TRUE proc/missile(Type, Start, End) diff --git a/OpenDreamRuntime/Objects/DreamObjectTree.cs b/OpenDreamRuntime/Objects/DreamObjectTree.cs index 668b00cab0..ee9e2394d3 100644 --- a/OpenDreamRuntime/Objects/DreamObjectTree.cs +++ b/OpenDreamRuntime/Objects/DreamObjectTree.cs @@ -48,6 +48,8 @@ public sealed class DreamObjectTree { public TreeEntry Movable { get; private set; } public TreeEntry Obj { get; private set; } public TreeEntry Mob { get; private set; } + public TreeEntry Generator { get; private set; } + public TreeEntry Particles { get; private set; } private FrozenDictionary _pathToType = FrozenDictionary.Empty; private FrozenDictionary _globalProcIds = FrozenDictionary.Empty; @@ -171,6 +173,10 @@ public DreamObject CreateObject(TreeEntry type) { return new DreamObjectArea(type.ObjectDefinition); if (type.ObjectDefinition.IsSubtypeOf(Atom)) return new DreamObjectAtom(type.ObjectDefinition); + if (type.ObjectDefinition.IsSubtypeOf(Generator)) + throw new Exception("Cannot create objects of type /generator with the generator() proc"); + if (type.ObjectDefinition.IsSubtypeOf(Particles)) + return new DreamObjectParticles(type.ObjectDefinition); if (type.ObjectDefinition.IsSubtypeOf(Client)) throw new Exception("Cannot create objects of type /client"); if (type.ObjectDefinition.IsSubtypeOf(Turf)) @@ -303,6 +309,8 @@ private void LoadTypesFromJson(DreamTypeJson[] types, ProcDefinitionJson[]? proc Movable = GetTreeEntry("/atom/movable"); Obj = GetTreeEntry("/obj"); Mob = GetTreeEntry("/mob"); + Particles = GetTreeEntry("/particles"); + Generator = GetTreeEntry("/generator"); // Load procs first so types can set their init proc's super proc LoadProcsFromJson(procs, globalProcs); diff --git a/OpenDreamRuntime/Objects/Types/DreamObjectGenerator.cs b/OpenDreamRuntime/Objects/Types/DreamObjectGenerator.cs new file mode 100644 index 0000000000..1583a9b569 --- /dev/null +++ b/OpenDreamRuntime/Objects/Types/DreamObjectGenerator.cs @@ -0,0 +1,40 @@ +using OpenDreamRuntime.Procs; + +namespace OpenDreamRuntime.Objects.Types; + +public sealed class DreamObjectGenerator : DreamObject { + + public DreamValue A { get; private set; } + public DreamValue B { get; private set; } + public GeneratorOutputType OutputType { get; private set; } + public GeneratorDistribution Distribution { get; private set; } + + + public DreamObjectGenerator(DreamObjectDefinition objectDefinition, DreamValue A, DreamValue B, GeneratorOutputType outputType, GeneratorDistribution dist) : base(objectDefinition) { + this.A = A; + this.B = B; + this.OutputType = outputType; + this.Distribution = dist; + } + public override void Initialize(DreamProcArguments args) { + base.Initialize(args); + } +} + +public enum GeneratorOutputType { + Num, + Vector, + Box, + Color, + Circle, + Sphere, + Square, + Cube +} + +public enum GeneratorDistribution { + Uniform, + Normal, + Linear, + Square +} diff --git a/OpenDreamRuntime/Procs/Native/DreamProcNativeRoot.cs b/OpenDreamRuntime/Procs/Native/DreamProcNativeRoot.cs index e8c0511734..436a9c6d95 100644 --- a/OpenDreamRuntime/Procs/Native/DreamProcNativeRoot.cs +++ b/OpenDreamRuntime/Procs/Native/DreamProcNativeRoot.cs @@ -23,6 +23,7 @@ using Robust.Server; using Robust.Shared.Asynchronous; using Vector4 = Robust.Shared.Maths.Vector4; +using System.ComponentModel; namespace OpenDreamRuntime.Procs.Native; /// @@ -1092,6 +1093,67 @@ public static DreamValue NativeProc_get_steps_to(NativeProc.Bundle bundle, Dream return new(result.GetLength() > 0 ? result : null); } + [DreamProc("generator")] + [DreamProcParameter("type", Type = DreamValueTypeFlag.String)] + [DreamProcParameter("A", Type = DreamValueTypeFlag.DreamObject)] + [DreamProcParameter("B", Type = DreamValueTypeFlag.DreamObject)] + [DreamProcParameter("rand", Type = DreamValueTypeFlag.Float)] + public static DreamValue NativeProc_generator(NativeProc.Bundle bundle, DreamObject? src, DreamObject? usr) { + string outputTypeString = bundle.GetArgument(0, "type").MustGetValueAsString(); + var A = bundle.GetArgument(1, "A"); + var B = bundle.GetArgument(2, "B"); + var distNum = bundle.GetArgument(3, "rand").MustGetValueAsInteger(); + + GeneratorOutputType outputType; + GeneratorDistribution distribution; + switch(outputTypeString){ + case "num": + outputType = GeneratorOutputType.Num; + break; + case "vector": + outputType = GeneratorOutputType.Vector; + break; + case "box": + outputType = GeneratorOutputType.Box; + break; + case "color": + outputType = GeneratorOutputType.Color; + break; + case "circle": + outputType = GeneratorOutputType.Circle; + break; + case "sphere": + outputType = GeneratorOutputType.Sphere; + break; + case "square": + outputType = GeneratorOutputType.Square; + break; + case "cube": + outputType = GeneratorOutputType.Cube; + break; + default: + throw new InvalidEnumArgumentException("Invalid output type specified in generator()"); + } + switch(distNum){ + case 0: + distribution = GeneratorDistribution.Uniform; + break; + case 1: + distribution = GeneratorDistribution.Normal; + break; + case 2: + distribution = GeneratorDistribution.Linear; + break; + case 3: + distribution = GeneratorDistribution.Square; + break; + default: + throw new InvalidEnumArgumentException("Invalid distribution type specified in generator()"); + } + return new(new DreamObjectGenerator(bundle.ObjectTree.Generator.ObjectDefinition, A, B, outputType, distribution)); + + } + [DreamProc("hascall")] [DreamProcParameter("Object", Type = DreamValueTypeFlag.DreamObject)] [DreamProcParameter("ProcName", Type = DreamValueTypeFlag.String)] From b9b914254fe1838d7de6d2275bb6eeccecbfb75f Mon Sep 17 00:00:00 2001 From: amy Date: Tue, 18 Feb 2025 23:30:47 +0000 Subject: [PATCH 10/29] more --- .../Objects/Types/DreamObjectParticles.cs | 64 ++++++++++++++----- 1 file changed, 47 insertions(+), 17 deletions(-) diff --git a/OpenDreamRuntime/Objects/Types/DreamObjectParticles.cs b/OpenDreamRuntime/Objects/Types/DreamObjectParticles.cs index 230df1723e..310574c0b6 100644 --- a/OpenDreamRuntime/Objects/Types/DreamObjectParticles.cs +++ b/OpenDreamRuntime/Objects/Types/DreamObjectParticles.cs @@ -121,19 +121,37 @@ protected override void SetVar(string varName, DreamValue value) { ParticlesComponent.TextureList = immutableAppearances.ToArray(); break; case "lifespan": //num or generator - ParticlesComponent.LifespanHigh = value.GetValueAsFloat(); - ParticlesComponent.LifespanLow = value.GetValueAsFloat(); - ParticlesComponent.LifespanType = value.GetValueAsParticlePropertyType(); + if(value.TryGetValueAsFloat(out float floatValue)){ + ParticlesComponent.LifespanHigh = floatValue; + ParticlesComponent.LifespanLow = floatValue; + ParticlesComponent.LifespanType = ParticlePropertyType.HighValue; + } else if(value.TryGetValueAsDreamObject(out var dreamObjectGenerator)) { + ParticlesComponent.LifespanHigh = dreamObjectGenerator.B.MustGetValueAsFloat(); + ParticlesComponent.LifespanLow = dreamObjectGenerator.A.MustGetValueAsFloat(); + ParticlesComponent.LifespanType = ParticlePropertyType.RandomUniform; //TODO all the other distributions + } break; case "fadein": //num or generator - ParticlesComponent.FadeInHigh = value.GetValueAsInteger(); - ParticlesComponent.FadeInLow = value.GetValueAsInteger(); - ParticlesComponent.FadeInType = value.GetValueAsParticlePropertyType(); + if(value.TryGetValueAsInteger(out int intValue)){ + ParticlesComponent.FadeInHigh = intValue; + ParticlesComponent.FadeInLow = intValue; + ParticlesComponent.FadeInType = ParticlePropertyType.HighValue; + } else if(value.TryGetValueAsDreamObject(out var dreamObjectGenerator)) { + ParticlesComponent.FadeInHigh = dreamObjectGenerator.B.MustGetValueAsInteger(); + ParticlesComponent.FadeInLow = dreamObjectGenerator.A.MustGetValueAsInteger(); + ParticlesComponent.FadeInType = ParticlePropertyType.RandomUniform; //TODO all the other distributions + } break; case "fade": //num or generator - ParticlesComponent.FadeOutHigh = value.GetValueAsInteger(); - ParticlesComponent.FadeOutLow = value.GetValueAsInteger(); - ParticlesComponent.FadeOutType = value.GetValueAsParticlePropertyType(); + if(value.TryGetValueAsInteger(out intValue)){ + ParticlesComponent.FadeOutHigh = intValue; + ParticlesComponent.FadeOutLow = intValue; + ParticlesComponent.FadeOutType = ParticlePropertyType.HighValue; + } else if(value.TryGetValueAsDreamObject(out var dreamObjectGenerator)) { + ParticlesComponent.FadeOutHigh = dreamObjectGenerator.B.MustGetValueAsInteger(); + ParticlesComponent.FadeOutLow = dreamObjectGenerator.A.MustGetValueAsInteger(); + ParticlesComponent.FadeOutType = ParticlePropertyType.RandomUniform; //TODO all the other distributions + } break; case "position": //list, vector, or generator ParticlesComponent.SpawnPositionHigh = value.GetValueAsVector3(); @@ -156,21 +174,33 @@ protected override void SetVar(string varName, DreamValue value) { ParticlesComponent.GrowthType = value.GetValueAsParticlePropertyType(); break; case "rotation": //num or generator - ParticlesComponent.RotationHigh = value.GetValueAsFloat(); - ParticlesComponent.RotationLow = value.GetValueAsFloat(); - ParticlesComponent.RotationType = value.GetValueAsParticlePropertyType(); + if(value.TryGetValueAsFloat(out floatValue)){ + ParticlesComponent.RotationHigh = floatValue; + ParticlesComponent.RotationLow = floatValue; + ParticlesComponent.RotationType = ParticlePropertyType.HighValue; + } else if(value.TryGetValueAsDreamObject(out var dreamObjectGenerator)) { + ParticlesComponent.RotationHigh = dreamObjectGenerator.B.MustGetValueAsFloat(); + ParticlesComponent.RotationLow = dreamObjectGenerator.A.MustGetValueAsFloat(); + ParticlesComponent.RotationType = ParticlePropertyType.RandomUniform; //TODO all the other distributions + } break; case "spin": //num or generator - ParticlesComponent.SpinHigh = value.GetValueAsFloat(); - ParticlesComponent.SpinLow = value.GetValueAsFloat(); - ParticlesComponent.SpinType = value.GetValueAsParticlePropertyType(); + if(value.TryGetValueAsFloat(out floatValue)){ + ParticlesComponent.SpinHigh = floatValue; + ParticlesComponent.SpinLow = floatValue; + ParticlesComponent.SpinType = ParticlePropertyType.HighValue; + } else if(value.TryGetValueAsDreamObject(out var dreamObjectGenerator)) { + ParticlesComponent.SpinHigh = dreamObjectGenerator.B.MustGetValueAsFloat(); + ParticlesComponent.SpinLow = dreamObjectGenerator.A.MustGetValueAsFloat(); + ParticlesComponent.SpinType = ParticlePropertyType.RandomUniform; //TODO all the other distributions + } break; - case "friction": //num or generator + case "friction": //num, vector, or generator ParticlesComponent.FrictionHigh = value.GetValueAsFloat(); ParticlesComponent.FrictionLow = value.GetValueAsFloat(); ParticlesComponent.FrictionType = value.GetValueAsParticlePropertyType(); break; - case "drift": //num or generator + case "drift": //num, vector, or generator ParticlesComponent.DriftHigh = value.GetValueAsVector3(); ParticlesComponent.DriftLow = value.GetValueAsVector3(); ParticlesComponent.DriftType = value.GetValueAsParticlePropertyType(); From 582f7d52763fa3ab6043db9a0684992ffad3b9ae Mon Sep 17 00:00:00 2001 From: amy Date: Wed, 19 Feb 2025 00:08:52 +0000 Subject: [PATCH 11/29] done! with this part. --- .../Objects/Types/DreamObjectParticles.cs | 127 +++++++++++++++--- 1 file changed, 105 insertions(+), 22 deletions(-) diff --git a/OpenDreamRuntime/Objects/Types/DreamObjectParticles.cs b/OpenDreamRuntime/Objects/Types/DreamObjectParticles.cs index 310574c0b6..d57f031743 100644 --- a/OpenDreamRuntime/Objects/Types/DreamObjectParticles.cs +++ b/OpenDreamRuntime/Objects/Types/DreamObjectParticles.cs @@ -24,9 +24,7 @@ public DreamObjectParticles(DreamObjectDefinition objectDefinition) : base(objec //check if I need to manually send update events to the component? //add entity array to appearance objects //collect entities client-side for the rendermetadata - //idk I guess bodge generators right now? //set up a special list type on /atom for /particles - } protected override void SetVar(string varName, DreamValue value) { @@ -153,25 +151,82 @@ protected override void SetVar(string varName, DreamValue value) { ParticlesComponent.FadeOutType = ParticlePropertyType.RandomUniform; //TODO all the other distributions } break; - case "position": //list, vector, or generator - ParticlesComponent.SpawnPositionHigh = value.GetValueAsVector3(); - ParticlesComponent.SpawnPositionLow = value.GetValueAsVector3(); - ParticlesComponent.SpawnPositionType = value.GetValueAsParticlePropertyType(); + case "position": //num, list, vector, or generator + if(value.TryGetValueAsFloat(out floatValue)){ + ParticlesComponent.SpawnPositionHigh = new Vector3(floatValue); + ParticlesComponent.SpawnPositionLow = new Vector3(floatValue); + ParticlesComponent.SpawnPositionType = ParticlePropertyType.HighValue; + } + if(value.TryGetValueAsDreamList(out var vectorList) && vectorList.GetLength() >= 3){ + List dreamValues = vectorList.GetValues(); + ParticlesComponent.SpawnPositionHigh = new Vector3(dreamValues[0].MustGetValueAsFloat(), dreamValues[1].MustGetValueAsFloat(), dreamValues[2].MustGetValueAsFloat()); + ParticlesComponent.SpawnPositionLow = new Vector3(dreamValues[0].MustGetValueAsFloat(), dreamValues[1].MustGetValueAsFloat(), dreamValues[2].MustGetValueAsFloat()); + ParticlesComponent.SpawnPositionType = ParticlePropertyType.HighValue; + } else if(value.TryGetValueAsDreamObject(out var dreamObjectGenerator)) { + List dreamValues = dreamObjectGenerator.B.MustGetValueAsDreamList().GetValues(); + ParticlesComponent.SpawnPositionHigh = new Vector3(dreamValues[0].MustGetValueAsFloat(), dreamValues[1].MustGetValueAsFloat(), dreamValues[2].MustGetValueAsFloat()); + dreamValues = dreamObjectGenerator.A.MustGetValueAsDreamList().GetValues(); + ParticlesComponent.SpawnPositionLow = new Vector3(dreamValues[0].MustGetValueAsFloat(), dreamValues[1].MustGetValueAsFloat(), dreamValues[2].MustGetValueAsFloat()); + ParticlesComponent.RotationLow = dreamObjectGenerator.A.MustGetValueAsFloat(); + ParticlesComponent.RotationType = ParticlePropertyType.RandomUniform; //TODO all the other distributions + } break; - case "velocity": //list, vector, or generator - ParticlesComponent.SpawnPositionHigh = value.GetValueAsVector3(); - ParticlesComponent.SpawnPositionLow = value.GetValueAsVector3(); - ParticlesComponent.SpawnPositionType = value.GetValueAsParticlePropertyType(); + case "velocity": //num, list, vector, or generator + if(value.TryGetValueAsFloat(out floatValue)){ + ParticlesComponent.SpawnVelocityHigh = new Vector3(floatValue); + ParticlesComponent.SpawnVelocityLow = new Vector3(floatValue); + ParticlesComponent.SpawnVelocityType = ParticlePropertyType.HighValue; + } + if(value.TryGetValueAsDreamList(out vectorList) && vectorList.GetLength() >= 3){ + List dreamValues = vectorList.GetValues(); + ParticlesComponent.SpawnVelocityHigh = new Vector3(dreamValues[0].MustGetValueAsFloat(), dreamValues[1].MustGetValueAsFloat(), dreamValues[2].MustGetValueAsFloat()); + ParticlesComponent.SpawnVelocityLow = new Vector3(dreamValues[0].MustGetValueAsFloat(), dreamValues[1].MustGetValueAsFloat(), dreamValues[2].MustGetValueAsFloat()); + ParticlesComponent.SpawnVelocityType = ParticlePropertyType.HighValue; + } else if(value.TryGetValueAsDreamObject(out var dreamObjectGenerator)) { + List dreamValues = dreamObjectGenerator.B.MustGetValueAsDreamList().GetValues(); + ParticlesComponent.SpawnVelocityHigh = new Vector3(dreamValues[0].MustGetValueAsFloat(), dreamValues[1].MustGetValueAsFloat(), dreamValues[2].MustGetValueAsFloat()); + dreamValues = dreamObjectGenerator.A.MustGetValueAsDreamList().GetValues(); + ParticlesComponent.SpawnVelocityLow = new Vector3(dreamValues[0].MustGetValueAsFloat(), dreamValues[1].MustGetValueAsFloat(), dreamValues[2].MustGetValueAsFloat()); + ParticlesComponent.SpawnVelocityType = ParticlePropertyType.RandomUniform; //TODO all the other distributions + } break; case "scale": //num, list, vector, or generator - ParticlesComponent.ScaleHigh = value.GetValueAsVector2(); - ParticlesComponent.ScaleLow = value.GetValueAsVector2(); - ParticlesComponent.ScaleType = value.GetValueAsParticlePropertyType(); + if(value.TryGetValueAsFloat(out floatValue)){ + ParticlesComponent.ScaleHigh = new Vector2(floatValue); + ParticlesComponent.ScaleLow = new Vector2(floatValue); + ParticlesComponent.ScaleType = ParticlePropertyType.HighValue; + } + if(value.TryGetValueAsDreamList(out vectorList) && vectorList.GetLength() >= 2){ + List dreamValues = vectorList.GetValues(); + ParticlesComponent.ScaleHigh = new Vector2(dreamValues[0].MustGetValueAsFloat(), dreamValues[1].MustGetValueAsFloat()); + ParticlesComponent.ScaleLow = new Vector2(dreamValues[0].MustGetValueAsFloat(), dreamValues[1].MustGetValueAsFloat()); + ParticlesComponent.ScaleType = ParticlePropertyType.HighValue; + } else if(value.TryGetValueAsDreamObject(out var dreamObjectGenerator)) { + List dreamValues = dreamObjectGenerator.B.MustGetValueAsDreamList().GetValues(); + ParticlesComponent.ScaleHigh = new Vector2(dreamValues[0].MustGetValueAsFloat(), dreamValues[1].MustGetValueAsFloat()); + dreamValues = dreamObjectGenerator.A.MustGetValueAsDreamList().GetValues(); + ParticlesComponent.ScaleLow = new Vector2(dreamValues[0].MustGetValueAsFloat(), dreamValues[1].MustGetValueAsFloat()); + ParticlesComponent.ScaleType = ParticlePropertyType.RandomUniform; //TODO all the other distributions + } break; case "grow": //num, list, vector, or generator - ParticlesComponent.GrowthHigh = value.GetValueAsFloat(); - ParticlesComponent.GrowthLow = value.GetValueAsFloat(); - ParticlesComponent.GrowthType = value.GetValueAsParticlePropertyType(); + if(value.TryGetValueAsFloat(out floatValue)){ + ParticlesComponent.GrowthHigh = new Vector2(floatValue); + ParticlesComponent.GrowthLow = new Vector2(floatValue); + ParticlesComponent.GrowthType = ParticlePropertyType.HighValue; + } + if(value.TryGetValueAsDreamList(out vectorList) && vectorList.GetLength() >= 2){ + List dreamValues = vectorList.GetValues(); + ParticlesComponent.GrowthHigh = new Vector2(dreamValues[0].MustGetValueAsFloat(), dreamValues[1].MustGetValueAsFloat()); + ParticlesComponent.GrowthLow = new Vector2(dreamValues[0].MustGetValueAsFloat(), dreamValues[1].MustGetValueAsFloat()); + ParticlesComponent.GrowthType = ParticlePropertyType.HighValue; + } else if(value.TryGetValueAsDreamObject(out var dreamObjectGenerator)) { + List dreamValues = dreamObjectGenerator.B.MustGetValueAsDreamList().GetValues(); + ParticlesComponent.GrowthHigh = new Vector2(dreamValues[0].MustGetValueAsFloat(), dreamValues[1].MustGetValueAsFloat()); + dreamValues = dreamObjectGenerator.A.MustGetValueAsDreamList().GetValues(); + ParticlesComponent.GrowthLow = new Vector2(dreamValues[0].MustGetValueAsFloat(), dreamValues[1].MustGetValueAsFloat()); + ParticlesComponent.GrowthType = ParticlePropertyType.RandomUniform; //TODO all the other distributions + } break; case "rotation": //num or generator if(value.TryGetValueAsFloat(out floatValue)){ @@ -196,14 +251,42 @@ protected override void SetVar(string varName, DreamValue value) { } break; case "friction": //num, vector, or generator - ParticlesComponent.FrictionHigh = value.GetValueAsFloat(); - ParticlesComponent.FrictionLow = value.GetValueAsFloat(); - ParticlesComponent.FrictionType = value.GetValueAsParticlePropertyType(); + if(value.TryGetValueAsFloat(out floatValue)){ + ParticlesComponent.FrictionHigh = new Vector3(floatValue); + ParticlesComponent.FrictionLow = new Vector3(floatValue); + ParticlesComponent.FrictionType = ParticlePropertyType.HighValue; + } + if(value.TryGetValueAsDreamList(out vectorList) && vectorList.GetLength() >= 3){ + List dreamValues = vectorList.GetValues(); + ParticlesComponent.FrictionHigh = new Vector3(dreamValues[0].MustGetValueAsFloat(), dreamValues[1].MustGetValueAsFloat(), dreamValues[2].MustGetValueAsFloat()); + ParticlesComponent.FrictionLow = new Vector3(dreamValues[0].MustGetValueAsFloat(), dreamValues[1].MustGetValueAsFloat(), dreamValues[2].MustGetValueAsFloat()); + ParticlesComponent.FrictionType = ParticlePropertyType.HighValue; + } else if(value.TryGetValueAsDreamObject(out var dreamObjectGenerator)) { + List dreamValues = dreamObjectGenerator.B.MustGetValueAsDreamList().GetValues(); + ParticlesComponent.FrictionHigh = new Vector3(dreamValues[0].MustGetValueAsFloat(), dreamValues[1].MustGetValueAsFloat(), dreamValues[2].MustGetValueAsFloat()); + dreamValues = dreamObjectGenerator.A.MustGetValueAsDreamList().GetValues(); + ParticlesComponent.FrictionLow = new Vector3(dreamValues[0].MustGetValueAsFloat(), dreamValues[1].MustGetValueAsFloat(), dreamValues[2].MustGetValueAsFloat()); + ParticlesComponent.FrictionType = ParticlePropertyType.RandomUniform; //TODO all the other distributions + } break; case "drift": //num, vector, or generator - ParticlesComponent.DriftHigh = value.GetValueAsVector3(); - ParticlesComponent.DriftLow = value.GetValueAsVector3(); - ParticlesComponent.DriftType = value.GetValueAsParticlePropertyType(); + if(value.TryGetValueAsFloat(out floatValue)){ + ParticlesComponent.DriftHigh = new Vector3(floatValue); + ParticlesComponent.DriftLow = new Vector3(floatValue); + ParticlesComponent.DriftType = ParticlePropertyType.HighValue; + } + if(value.TryGetValueAsDreamList(out vectorList) && vectorList.GetLength() >= 3){ + List dreamValues = vectorList.GetValues(); + ParticlesComponent.DriftHigh = new Vector3(dreamValues[0].MustGetValueAsFloat(), dreamValues[1].MustGetValueAsFloat(), dreamValues[2].MustGetValueAsFloat()); + ParticlesComponent.DriftLow = new Vector3(dreamValues[0].MustGetValueAsFloat(), dreamValues[1].MustGetValueAsFloat(), dreamValues[2].MustGetValueAsFloat()); + ParticlesComponent.DriftType = ParticlePropertyType.HighValue; + } else if(value.TryGetValueAsDreamObject(out var dreamObjectGenerator)) { + List dreamValues = dreamObjectGenerator.B.MustGetValueAsDreamList().GetValues(); + ParticlesComponent.DriftHigh = new Vector3(dreamValues[0].MustGetValueAsFloat(), dreamValues[1].MustGetValueAsFloat(), dreamValues[2].MustGetValueAsFloat()); + dreamValues = dreamObjectGenerator.A.MustGetValueAsDreamList().GetValues(); + ParticlesComponent.DriftLow = new Vector3(dreamValues[0].MustGetValueAsFloat(), dreamValues[1].MustGetValueAsFloat(), dreamValues[2].MustGetValueAsFloat()); + ParticlesComponent.DriftType = ParticlePropertyType.RandomUniform; //TODO all the other distributions + } break; default: base.SetVar(varName, value); From dda9e5e9c743def0d9da1002ae1a02232803ec53 Mon Sep 17 00:00:00 2001 From: amy Date: Wed, 19 Feb 2025 20:57:44 +0000 Subject: [PATCH 12/29] closee --- .../Rendering/ClientDreamParticlesSystem.cs | 29 +++++++++++++----- TestGame/code.dm | 20 ++++++++++++ TestGame/icons/bee.dmi | Bin 0 -> 77831 bytes 3 files changed, 41 insertions(+), 8 deletions(-) create mode 100644 TestGame/icons/bee.dmi diff --git a/OpenDreamClient/Rendering/ClientDreamParticlesSystem.cs b/OpenDreamClient/Rendering/ClientDreamParticlesSystem.cs index 7792795299..3805f2a53d 100644 --- a/OpenDreamClient/Rendering/ClientDreamParticlesSystem.cs +++ b/OpenDreamClient/Rendering/ClientDreamParticlesSystem.cs @@ -1,8 +1,10 @@ using JetBrains.Annotations; using OpenDreamShared.Rendering; +using Pidgin; using Robust.Client.Graphics; using Robust.Client.ResourceManagement; using Robust.Shared.Random; +using Robust.Shared.Timing; using Vector3 = Robust.Shared.Maths.Vector3; namespace OpenDreamClient.Rendering; @@ -12,13 +14,19 @@ public sealed class ClientDreamParticlesSystem : SharedDreamParticlesSystem { [Dependency] private readonly ParticlesManager _particlesManager = default!; [Dependency] private readonly IResourceCache _resourceCache = default!; + [Dependency] private readonly IGameTiming _gameTiming = default!; + [Dependency] private readonly ClientAppearanceSystem _appearanceSystem = default!; + [Dependency] private readonly IClyde _clyde = default!; + public RenderTargetPool RenderTargetPool = default!; private Random random = new(); + private RendererMetaData defaultRenderMetaData = new(); //used for icon GetTexture(), never needs anything but default settings public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnDreamParticlesComponentChange); SubscribeLocalEvent(HandleComponentAdd); SubscribeLocalEvent(HandleComponentRemove); + RenderTargetPool = new(_clyde); } private void OnDreamParticlesComponentChange(EntityUid uid, DreamParticlesComponent component, ref ComponentChange args) @@ -42,20 +50,25 @@ private ParticleSystemArgs GetParticleSystemArgs(DreamParticlesComponent compone Func textureFunc; if(component.TextureList is null || component.TextureList.Length == 0) textureFunc = () => Texture.White; - else - textureFunc = () => _resourceCache.GetResource(new Random().Pick(component.TextureList)); //TODO - + else{ + List icons = new(component.TextureList.Length); + foreach(var appearance in component.TextureList){ + DreamIcon icon = new DreamIcon(RenderTargetPool, _gameTiming, _clyde, _appearanceSystem); + icon.SetAppearance(appearance.MustGetId()); + icons.Add(icon); + } + textureFunc = () => random.Pick(icons).GetTexture(null!, null!, defaultRenderMetaData, null)!; //oh god, so hacky + } var result = new ParticleSystemArgs(textureFunc, new Vector2i(component.Width, component.Height), (uint)component.Count, component.Spawning); - GeneratorFloat lifespan = new(); result.Lifespan = GetGeneratorFloat(component.LifespanLow, component.LifespanHigh, component.LifespanType); result.Fadein = GetGeneratorFloat(component.FadeInLow, component.FadeInHigh, component.FadeInType); result.Fadeout = GetGeneratorFloat(component.FadeOutLow, component.FadeOutHigh, component.FadeOutType); - if(component.ColorList.Length > 0) + if(component.Gradient.Length > 0) result.Color = (float lifetime) => { - var colorIndex = (int)(lifetime * component.ColorList.Length); - colorIndex = Math.Clamp(colorIndex, 0, component.ColorList.Length - 1); - return component.ColorList[colorIndex]; + var colorIndex = (int)(lifetime * component.Gradient.Length); + colorIndex = Math.Clamp(colorIndex, 0, component.Gradient.Length - 1); + return component.Gradient[colorIndex]; }; else result.Color = (float lifetime) => Color.White; diff --git a/TestGame/code.dm b/TestGame/code.dm index d1b94607d4..2f4a238ed3 100644 --- a/TestGame/code.dm +++ b/TestGame/code.dm @@ -52,6 +52,25 @@ spawn(20) toggleBlink() +/particles/swarm/bees + icon = 'icons/bee.dmi' + icon_state = list("mini-bee"=1, "mini-bee2"=1) + friction = 0.1 + count = 10 + spawning = 0.35 + fade = 5 + fadein = 5 + lifespan = generator("num", 50, 80, LINEAR_RAND) + width = 64 + position = generator("box", list(-10,-10,0), list(10,10,50)) + bound1 = list(-32, -32, -100) + bound2 = list(32, 32, 100) + gravity = list(0, -0.1) + drift = generator("box", list(-0.4, -0.1, 0), list(0.4, 0.15, 0)) + velocity = generator("box", list(-2, -0.1, 0), list(2, 0.5, 0)) + height = 64 + + /mob icon = 'icons/mob.dmi' icon_state = "mob" @@ -62,6 +81,7 @@ desc = "Such a beautiful smile." gender = MALE see_invisible = 101 + particles = new /particles/swarm/bees New() ..() diff --git a/TestGame/icons/bee.dmi b/TestGame/icons/bee.dmi new file mode 100644 index 0000000000000000000000000000000000000000..e7e528916259f07e595fca896e1f8d4d198c7463 GIT binary patch literal 77831 zcmb@t2{=^m|35s#2&pU;?L!Df+L4$^Nu?Tv?52GoRI-mbQz=SC5wZ18wYY9lYp${^r%|x2}45A`t$GB?&E_y@o69 zbv0U}raG6c)4E#uq9vtHHGF&vs`#MgI_p=eU-l$sppR-?bofFmM=egf@`xC^m;0EoPv1m*L9EfWVCBs@^=BtlN1nN?>7OVnj7f|gJAbP6RP^!U z_LYlEw_bVnNiOqD)AjUKO}C9?cSWz5k5pRK2))+>ep8talThF@Bo*P**`Gg!#Z z7CF~(w!2Q6Y|7o?m}=xbw|ps{`Q&O~0sTQm%f{qkLd1##v^tv)s&%i4DFs=~Z-uKa zAwoei0!s2#sQsFj@}m9A-J6vm5vP4VDrVm>_oC+_ve}iEy9O zp$8OSty^oD96pPF6(_dJH?T!*Enq#TT@D9 z*RL*dzI9Ib{uX)j*|P^0nO$Es23-i?*SidasdQ#JFc)_AYVY=f^e28f}74X8mXbw?&>@ z`|-q*X$9jAPKU46IWcDQkH2}AeE9xMLIoO}YD#M|d_(s>FkU-rIy-leyia}r zx#VMI8dCjAL&;H@>pC7KYBLX92(?R|*JnqsGoUAL+CHai`b#zA%}mv;GWL_r$DW-Z zyJy)A=P4$?1)^??*_dEwXC6b3psYHzf7RudpDm2X5LLp>?R?=Rb^d z0yW~Y3guk_4!?Klm|AnNY&w#sY4QAc<-n7<_V$tB-qv3fRGU+R>{}o=kl^xIxO&f_ zie)3_S7W%ODZ=xrBYUoU>cr0aHcGqrPzC;P>d(!!*h203>C4@INU-8Sv% zc3V)CDo5o>cQ?Lo@O7C^_QYxV{iv2Ij>+cpD z+#Wc0Pa`tmSLc?4i@&_v`&OFckjfA1HeZl7KYRdt@`ItqUNgd_!h?%mGJeo`JWsg^ zF74*EF;rmwhZ<9%KvX0`BOytUA?a16FYuSa`TqYlNca&xpI?&SOHqqrGV`Sis~1VU2!MaV<#Oxq(wdu_dpXI{ zFPplv&$h?3QaNA(mCyafb$mGYXdrX|B7g#sG@_P|_b3n&prOYep((yH2}DF*Vsf?Z zkbutz6M*f|#C{qOv1ZSXKxj*8M-`D+0U;2EP|giAP(l>3^z8;T8XX~>GHc&uIJa(D zsGk$o=JW`;v)#Rwk+Fcm(1k73!pr(86E}zuo1X~+Czy{+~7HWq(SEVDEVrf^DxgDJ|-2rwK&=L04gyZf&Z zQy@gn))Y@2%mDOiFHd-jmx>=k*w|G|o(a^;c;U3WWB)cX`WKa-0H^}<5<83_8(4f& z-8U|i4lyg_db6)KFz9X&QuPfXP*9Ji=M!ZFG%8>xO}Sd~=uuN|PS vqCJ?0;@q6 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 literal 0 HcmV?d00001 From 2b51722e4b213cffb1156887642a11dbe55e9827 Mon Sep 17 00:00:00 2001 From: amy Date: Wed, 19 Feb 2025 22:10:49 +0000 Subject: [PATCH 13/29] list, appearance, bugfixes --- DMCompiler/DMStandard/Types/Atoms/Movable.dm | 2 +- DMCompiler/DMStandard/Types/Particles.dm | 48 +++++----- DMCompiler/DMStandard/_Standard.dm | 1 + OpenDreamRuntime/Objects/Types/DreamList.cs | 89 +++++++++++++++++++ .../Objects/Types/DreamObjectAtom.cs | 19 ++++ .../Objects/Types/DreamObjectParticles.cs | 10 +-- .../Procs/Native/DreamProcNative.cs | 1 + .../Procs/Native/DreamProcNativeRoot.cs | 2 +- OpenDreamShared/Dream/ImmutableAppearance.cs | 35 +++++++- OpenDreamShared/Dream/MutableAppearance.cs | 13 +++ 10 files changed, 185 insertions(+), 35 deletions(-) diff --git a/DMCompiler/DMStandard/Types/Atoms/Movable.dm b/DMCompiler/DMStandard/Types/Atoms/Movable.dm index 185e87280f..ff0a4b589e 100644 --- a/DMCompiler/DMStandard/Types/Atoms/Movable.dm +++ b/DMCompiler/DMStandard/Types/Atoms/Movable.dm @@ -13,7 +13,7 @@ //Undocumented var. "[x],[y]" or "[x],[y] to [x2],[y2]" based on bound_* vars var/bounds as opendream_unimplemented - var/particles/particles as opendream_unimplemented + var/particles/particles proc/Bump(atom/Obstacle) diff --git a/DMCompiler/DMStandard/Types/Particles.dm b/DMCompiler/DMStandard/Types/Particles.dm index 3b7a64ca4d..d57e4b2103 100644 --- a/DMCompiler/DMStandard/Types/Particles.dm +++ b/DMCompiler/DMStandard/Types/Particles.dm @@ -3,34 +3,34 @@ /particles parent_type = /datum //Particle vars that affect the entire set (generators are not allowed for these) - var/width = 100 as opendream_unimplemented //null defaults to 0. width is the size of the particle "image" ie particles within this width image will be rendered, if they are partially in they get partially cut. if they reenter this area after leaving it they reapper. image is centered on particle owner. - var/height = 100 as opendream_unimplemented //ditto - var/count = 100 as opendream_unimplemented // if null, uses the last set value. is checked BEFORE lifespan so (count 10, lifespan 10, spawning 1) will skip a pixel every 10 pixels - var/spawning = 1 as opendream_unimplemented // null is treated as 0 - var/bound1 = -1000 as opendream_unimplemented // Usually list but if a number treated as list(bound1, bound1, bound1). if particles go above/below bound they will get immediately deleted regardless of lifespan. null is treated as the default value (-1000 and 1000)(this could be treated as infinity as well but 1000 is so large its hard to tell) - var/bound2 = 1000 as opendream_unimplemented // Ditto! - var/gravity as opendream_unimplemented // Usually list but if a number treated as list(gravity, gravity, gravity). - var/list/gradient = null as opendream_unimplemented // not cast as a list on byond as of 514.1580 despite only being able to be a list - var/transform as opendream_unimplemented // matrix or list. list can be simple matrix, complex matrix or projection matrix. thus: list(a, b, c, d, e, f) OR list(xx,xy,xz, yx,yy,yz, zx,zy,zz) OR list(xx,xy,xz, yx,yy,yz, zx,zy,zz, cx,cy,cz) OR list(xx,xy,xz,xw, yx,yy,yz,yw, zx,zy,zz,zw, wx,wy,wz,ww) + var/width = 100 //null defaults to 0. width is the size of the particle "image" ie particles within this width image will be rendered, if they are partially in they get partially cut. if they reenter this area after leaving it they reapper. image is centered on particle owner. + var/height = 100 //ditto + var/count = 100 // if null, uses the last set value. is checked BEFORE lifespan so (count 10, lifespan 10, spawning 1) will skip a pixel every 10 pixels + var/spawning = 1 // null is treated as 0 + var/bound1 = -1000 // Usually list but if a number treated as list(bound1, bound1, bound1). if particles go above/below bound they will get immediately deleted regardless of lifespan. null is treated as the default value (-1000 and 1000)(this could be treated as infinity as well but 1000 is so large its hard to tell) + var/bound2 = 1000 // Ditto! + var/gravity // Usually list but if a number treated as list(gravity, gravity, gravity). + var/list/gradient = null // not cast as a list on byond as of 514.1580 despite only being able to be a list + var/transform // matrix or list. list can be simple matrix, complex matrix or projection matrix. thus: list(a, b, c, d, e, f) OR list(xx,xy,xz, yx,yy,yz, zx,zy,zz) OR list(xx,xy,xz, yx,yy,yz, zx,zy,zz, cx,cy,cz) OR list(xx,xy,xz,xw, yx,yy,yz,yw, zx,zy,zz,zw, wx,wy,wz,ww) //Vars that apply when a particle spawns - var/lifespan as opendream_unimplemented // actual time a particle exists is fadein + lifespan + fade. thus this just the time it spends fully faded in. null is treated as - var/fade as opendream_unimplemented // null treated as 0 - var/fadein as opendream_unimplemented // null treated as 0 - var/icon as opendream_unimplemented // either icon or list(icon = weightofthisicon, icon = weightofthisicon) if null defaults to a 1x1 white pixel - var/icon_state as opendream_unimplemented // either string or list(string = weightofthisiconstate, string = weightofthisiconstate) if null defaults to a 1x1 white pixel - var/color as opendream_unimplemented // null treated as 0 - var/color_change as opendream_unimplemented // null treated as 0 - var/position as opendream_unimplemented // Usually list but if a number treated as list(position, position, position). null is treated as 0 - var/velocity as opendream_unimplemented // Usually list but if a number treated as list(velocity, velocity, velocity). null is treated as 0 - var/scale as opendream_unimplemented // if null defaults to 1, if number treated as list(scale, scale) - var/grow as opendream_unimplemented // if null defaults to 0, if number treated as list(grow, grow) - var/rotation as opendream_unimplemented // null treated as 0 - var/spin as opendream_unimplemented // null treated as 0 - var/friction as opendream_unimplemented // null treated as 0, numbers below 0 treated as 0 + var/lifespan // actual time a particle exists is fadein + lifespan + fade. thus this just the time it spends fully faded in. null is treated as + var/fade // null treated as 0 + var/fadein // null treated as 0 + var/icon // either icon or list(icon = weightofthisicon, icon = weightofthisicon) if null defaults to a 1x1 white pixel + var/icon_state // either string or list(string = weightofthisiconstate, string = weightofthisiconstate) if null defaults to a 1x1 white pixel + var/color // null treated as 0 + var/color_change // null treated as 0 + var/position // Usually list but if a number treated as list(position, position, position). null is treated as 0 + var/velocity // Usually list but if a number treated as list(velocity, velocity, velocity). null is treated as 0 + var/scale // if null defaults to 1, if number treated as list(scale, scale) + var/grow // if null defaults to 0, if number treated as list(grow, grow) + var/rotation // null treated as 0 + var/spin // null treated as 0 + var/friction // null treated as 0, numbers below 0 treated as 0 //Vars that are evaluated every tick - var/drift as opendream_unimplemented // Usually list but if a number treated as list(drift, drift, drift) + var/drift // Usually list but if a number treated as list(drift, drift, drift) //misc notes // particle image height/width is not considered for TILE_BOUND-less atoms diff --git a/DMCompiler/DMStandard/_Standard.dm b/DMCompiler/DMStandard/_Standard.dm index 410b1cd950..e36c673aa6 100644 --- a/DMCompiler/DMStandard/_Standard.dm +++ b/DMCompiler/DMStandard/_Standard.dm @@ -29,6 +29,7 @@ proc/flist(Path) as /list proc/floor(A) as num proc/fract(n) as num proc/ftime(File, IsCreationTime = 0) as num +proc/generator(type, A, B, rand) as /generator proc/get_step_to(Ref, Trg, Min=0) as num proc/get_steps_to(Ref, Trg, Min=0) as /list proc/gradient(A, index) diff --git a/OpenDreamRuntime/Objects/Types/DreamList.cs b/OpenDreamRuntime/Objects/Types/DreamList.cs index 0689448131..c4bb8500c6 100644 --- a/OpenDreamRuntime/Objects/Types/DreamList.cs +++ b/OpenDreamRuntime/Objects/Types/DreamList.cs @@ -858,6 +858,95 @@ public override int GetLength() { } } +// atom.particles list +// Operates on an atom's appearance +public sealed class DreamParticlesList : DreamList { + [Dependency] private readonly AtomManager _atomManager = default!; + [Dependency] private readonly IEntityManager _entityManager = default!; + private readonly PvsOverrideSystem? _pvsOverrideSystem; + + private readonly List _particles = new(); + private readonly DreamObject _atom; + + public DreamParticlesList(DreamObjectDefinition listDef, PvsOverrideSystem? pvsOverrideSystem, DreamObject atom) : base(listDef, 0) { + IoCManager.InjectDependencies(this); + + _pvsOverrideSystem = pvsOverrideSystem; + _atom = atom; + } + + public override List GetValues() { + var values = new List(_particles.Count); + + foreach (var particlesObject in _particles) { + values.Add(new(particlesObject)); + } + + return values; + } + + public override void Cut(int start = 1, int end = 0) { + int count = _particles.Count + 1; + if (end == 0 || end > count) end = count; + + _particles.RemoveRange(start - 1, end - start); + _atomManager.UpdateAppearance(_atom, appearance => { + appearance.Particles.RemoveRange(start - 1, end - start); + }); + } + + public override DreamValue GetValue(DreamValue key) { + if (!key.TryGetValueAsInteger(out var particlesIndex) || particlesIndex < 1) + throw new Exception($"Invalid index into particles list: {key}"); + if (particlesIndex > _particles.Count) + throw new Exception($"Atom only has {_particles.Count} particles element(s), cannot index {particlesIndex}"); + + return new DreamValue(_particles[particlesIndex - 1]); + } + + public override void SetValue(DreamValue key, DreamValue value, bool allowGrowth = false) { + throw new Exception("Cannot write to an index of a particles list"); + } + + public override void AddValue(DreamValue value) { + EntityUid entity; + if (value.TryGetValueAsDreamObject(out var particles)) { + if (_particles.Contains(particles)) + return; // particles cannot contain duplicates + _particles.Add(particles); + entity = particles.Entity; + } else if (value == DreamValue.Null) { + return; // particles cannot contain nulls + } else { + throw new Exception($"Cannot add {value} to a particles list"); + } + + // TODO: Only override the entity's visibility if its parent atom is visible + if (entity != EntityUid.Invalid) + _pvsOverrideSystem?.AddGlobalOverride(entity); + + _atomManager.UpdateAppearance(_atom, appearance => { + // Add even an invalid UID to keep this and _visContents in sync + appearance.Particles.Add(_entityManager.GetNetEntity(entity)); + }); + } + + public override void RemoveValue(DreamValue value) { + if (!value.TryGetValueAsDreamObject(out var particles)) + return; + + _particles.Remove(particles); + _atomManager.UpdateAppearance(_atom, appearance => { + appearance.Particles.Remove(_entityManager.GetNetEntity(particles.Entity)); + }); + } + + public override int GetLength() { + return _particles.Count; + } +} + + // atom.filters list // Operates on an object's appearance public sealed class DreamFilterList : DreamList { diff --git a/OpenDreamRuntime/Objects/Types/DreamObjectAtom.cs b/OpenDreamRuntime/Objects/Types/DreamObjectAtom.cs index 20cba60f33..1d9f5b3abf 100644 --- a/OpenDreamRuntime/Objects/Types/DreamObjectAtom.cs +++ b/OpenDreamRuntime/Objects/Types/DreamObjectAtom.cs @@ -7,6 +7,7 @@ public class DreamObjectAtom : DreamObject { public readonly DreamOverlaysList Overlays; public readonly DreamOverlaysList Underlays; public readonly DreamVisContentsList VisContents; + public readonly DreamParticlesList Particles; public readonly DreamFilterList Filters; public DreamList? VisLocs; // TODO: Implement @@ -14,6 +15,7 @@ public DreamObjectAtom(DreamObjectDefinition objectDefinition) : base(objectDefi Overlays = new(ObjectTree.List.ObjectDefinition, this, AppearanceSystem, false); Underlays = new(ObjectTree.List.ObjectDefinition, this, AppearanceSystem, true); VisContents = new(ObjectTree.List.ObjectDefinition, PvsOverrideSystem, this); + Particles = new(ObjectTree.List.ObjectDefinition, PvsOverrideSystem, this); Filters = new(ObjectTree.List.ObjectDefinition, this); AtomManager.AddAtom(this); @@ -73,6 +75,9 @@ protected override bool TryGetVar(string varName, out DreamValue value) { case "vis_contents": value = new(VisContents); return true; + case "particles": + value = new(Particles); + return true; default: if (AtomManager.IsValidAppearanceVar(varName)) { @@ -146,6 +151,20 @@ protected override void SetVar(string varName, DreamValue value) { break; } + case "particles": { + Particles.Cut(); + + if (value.TryGetValueAsDreamList(out var valueList)) { + // TODO: This should postpone UpdateAppearance until after everything is added + foreach (DreamValue particlesValue in valueList.GetValues()) { + Particles.AddValue(particlesValue); + } + } else if (!value.IsNull) { + Particles.AddValue(value); + } + + break; + } case "filters": { Filters.Cut(); diff --git a/OpenDreamRuntime/Objects/Types/DreamObjectParticles.cs b/OpenDreamRuntime/Objects/Types/DreamObjectParticles.cs index d57f031743..d9b1fc5d62 100644 --- a/OpenDreamRuntime/Objects/Types/DreamObjectParticles.cs +++ b/OpenDreamRuntime/Objects/Types/DreamObjectParticles.cs @@ -20,11 +20,8 @@ public DreamObjectParticles(DreamObjectDefinition objectDefinition) : base(objec Entity = EntityManager.SpawnEntity(null, new MapCoordinates(0, 0, MapId.Nullspace)); //spawning an entity in nullspace means it never actually gets sent to any clients until it's placed on the map, or it gets a PVS override ParticlesComponent = EntityManager.AddComponent(Entity); //populate component with settings from type - //do set/get var to grab those also //check if I need to manually send update events to the component? - //add entity array to appearance objects //collect entities client-side for the rendermetadata - //set up a special list type on /atom for /particles } protected override void SetVar(string varName, DreamValue value) { @@ -167,8 +164,7 @@ protected override void SetVar(string varName, DreamValue value) { ParticlesComponent.SpawnPositionHigh = new Vector3(dreamValues[0].MustGetValueAsFloat(), dreamValues[1].MustGetValueAsFloat(), dreamValues[2].MustGetValueAsFloat()); dreamValues = dreamObjectGenerator.A.MustGetValueAsDreamList().GetValues(); ParticlesComponent.SpawnPositionLow = new Vector3(dreamValues[0].MustGetValueAsFloat(), dreamValues[1].MustGetValueAsFloat(), dreamValues[2].MustGetValueAsFloat()); - ParticlesComponent.RotationLow = dreamObjectGenerator.A.MustGetValueAsFloat(); - ParticlesComponent.RotationType = ParticlePropertyType.RandomUniform; //TODO all the other distributions + ParticlesComponent.SpawnPositionType = ParticlePropertyType.RandomUniform; //TODO all the other distributions } break; case "velocity": //num, list, vector, or generator @@ -288,9 +284,7 @@ protected override void SetVar(string varName, DreamValue value) { ParticlesComponent.DriftType = ParticlePropertyType.RandomUniform; //TODO all the other distributions } break; - default: - base.SetVar(varName, value); - break; } + base.SetVar(varName, value); //all calls should set the internal vars, so GetVar() can just be default also } } diff --git a/OpenDreamRuntime/Procs/Native/DreamProcNative.cs b/OpenDreamRuntime/Procs/Native/DreamProcNative.cs index acaaac09e2..53c9d38603 100644 --- a/OpenDreamRuntime/Procs/Native/DreamProcNative.cs +++ b/OpenDreamRuntime/Procs/Native/DreamProcNative.cs @@ -33,6 +33,7 @@ public static void SetupNativeProcs(DreamObjectTree objectTree) { objectTree.SetGlobalNativeProc(DreamProcNativeRoot.NativeProc_floor); objectTree.SetGlobalNativeProc(DreamProcNativeRoot.NativeProc_fract); objectTree.SetGlobalNativeProc(DreamProcNativeRoot.NativeProc_ftime); + objectTree.SetGlobalNativeProc(DreamProcNativeRoot.NativeProc_generator); objectTree.SetGlobalNativeProc(DreamProcNativeRoot.NativeProc_get_step_to); objectTree.SetGlobalNativeProc(DreamProcNativeRoot.NativeProc_get_steps_to); objectTree.SetGlobalNativeProc(DreamProcNativeRoot.NativeProc_hascall); diff --git a/OpenDreamRuntime/Procs/Native/DreamProcNativeRoot.cs b/OpenDreamRuntime/Procs/Native/DreamProcNativeRoot.cs index 436a9c6d95..d2af41d246 100644 --- a/OpenDreamRuntime/Procs/Native/DreamProcNativeRoot.cs +++ b/OpenDreamRuntime/Procs/Native/DreamProcNativeRoot.cs @@ -1097,7 +1097,7 @@ public static DreamValue NativeProc_get_steps_to(NativeProc.Bundle bundle, Dream [DreamProcParameter("type", Type = DreamValueTypeFlag.String)] [DreamProcParameter("A", Type = DreamValueTypeFlag.DreamObject)] [DreamProcParameter("B", Type = DreamValueTypeFlag.DreamObject)] - [DreamProcParameter("rand", Type = DreamValueTypeFlag.Float)] + [DreamProcParameter("rand", Type = DreamValueTypeFlag.Float, DefaultValue = 0)] public static DreamValue NativeProc_generator(NativeProc.Bundle bundle, DreamObject? src, DreamObject? usr) { string outputTypeString = bundle.GetArgument(0, "type").MustGetValueAsString(); var A = bundle.GetArgument(1, "A"); diff --git a/OpenDreamShared/Dream/ImmutableAppearance.cs b/OpenDreamShared/Dream/ImmutableAppearance.cs index 97bffc6d38..a8929c2505 100644 --- a/OpenDreamShared/Dream/ImmutableAppearance.cs +++ b/OpenDreamShared/Dream/ImmutableAppearance.cs @@ -51,6 +51,7 @@ public sealed class ImmutableAppearance : IEquatable { [ViewVariables] public readonly ImmutableAppearance[] Overlays; [ViewVariables] public readonly ImmutableAppearance[] Underlays; [ViewVariables] public readonly Robust.Shared.GameObjects.NetEntity[] VisContents; + [ViewVariables] public readonly Robust.Shared.GameObjects.NetEntity[] Particles; [ViewVariables] public readonly DreamFilter[] Filters; [ViewVariables] public readonly int[] Verbs; [ViewVariables] public readonly ColorMatrix ColorMatrix = ColorMatrix.Identity; @@ -106,6 +107,7 @@ public ImmutableAppearance(MutableAppearance appearance, SharedAppearanceSystem? Underlays = appearance.Underlays.ToArray(); VisContents = appearance.VisContents.ToArray(); + Particles = appearance.Particles.ToArray(); Filters = appearance.Filters.ToArray(); Verbs = appearance.Verbs.ToArray(); Override = appearance.Override; @@ -168,6 +170,7 @@ public bool Equals(ImmutableAppearance? immutableAppearance) { if (immutableAppearance.Overlays.Length != Overlays.Length) return false; if (immutableAppearance.Underlays.Length != Underlays.Length) return false; if (immutableAppearance.VisContents.Length != VisContents.Length) return false; + if (immutableAppearance.Particles.Length != Particles.Length) return false; if (immutableAppearance.Filters.Length != Filters.Length) return false; if (immutableAppearance.Verbs.Length != Verbs.Length) return false; if (immutableAppearance.Override != Override) return false; @@ -195,6 +198,10 @@ public bool Equals(ImmutableAppearance? immutableAppearance) { if (immutableAppearance.Verbs[i] != Verbs[i]) return false; } + for (int i = 0; i < Particles.Length; i++) { + if (immutableAppearance.Particles[i] != Particles[i]) return false; + } + for (int i = 0; i < 6; i++) { if (!immutableAppearance.Transform[i].Equals(Transform[i])) return false; } @@ -258,6 +265,10 @@ public override int GetHashCode() { hashCode.Add(visContent); } + foreach (int particlesObject in Particles) { + hashCode.Add(particlesObject); + } + foreach (DreamFilter filter in Filters) { hashCode.Add(filter); } @@ -279,7 +290,8 @@ public ImmutableAppearance(NetIncomingMessage buffer, IRobustSerializer serializ Underlays = []; VisContents = []; Filters = []; - Verbs =[]; + Verbs = []; + Particles = []; var property = (IconAppearanceProperty)buffer.ReadByte(); while (property != IconAppearanceProperty.End) { @@ -392,6 +404,16 @@ public ImmutableAppearance(NetIncomingMessage buffer, IRobustSerializer serializ break; } + case IconAppearanceProperty.Particles: { + var particlesCount = buffer.ReadVariableInt32(); + + Particles = new Robust.Shared.GameObjects.NetEntity[particlesCount]; + for (int particlesI = 0; particlesI < particlesCount; particlesI++) { + Particles[particlesI] = buffer.ReadNetEntity(); + } + + break; + } case IconAppearanceProperty.Filters: { var filtersCount = buffer.ReadInt32(); @@ -485,11 +507,13 @@ public MutableAppearance ToMutable() { result.VisContents.EnsureCapacity(VisContents.Length); result.Filters.EnsureCapacity(Filters.Length); result.Verbs.EnsureCapacity(Verbs.Length); + result.Particles.EnsureCapacity(Particles.Length); result.Overlays.AddRange(Overlays); result.Underlays.AddRange(Underlays); result.VisContents.AddRange(VisContents); result.Filters.AddRange(Filters); result.Verbs.AddRange(Verbs); + result.Particles.AddRange(Particles); Array.Copy(Transform, result.Transform, 6); return result; @@ -639,6 +663,15 @@ public void WriteToBuffer(NetOutgoingMessage buffer, IRobustSerializer serialize } } + if (Particles.Length != 0) { + buffer.Write((byte)IconAppearanceProperty.Particles); + + buffer.WriteVariableInt32(Particles.Length); + foreach (var item in Particles) { + buffer.Write(item); + } + } + if (Filters.Length != 0) { buffer.Write((byte)IconAppearanceProperty.Filters); diff --git a/OpenDreamShared/Dream/MutableAppearance.cs b/OpenDreamShared/Dream/MutableAppearance.cs index 76c853cb03..805819eaa9 100644 --- a/OpenDreamShared/Dream/MutableAppearance.cs +++ b/OpenDreamShared/Dream/MutableAppearance.cs @@ -49,6 +49,7 @@ public sealed class MutableAppearance : IEquatable, IDisposab [ViewVariables] public List Overlays; [ViewVariables] public List Underlays; [ViewVariables] public List VisContents; + [ViewVariables] public List Particles; [ViewVariables] public List Filters; [ViewVariables] public List Verbs; [ViewVariables] public Vector2i MaptextSize = new(32,32); @@ -84,6 +85,7 @@ private MutableAppearance() { VisContents = []; Filters = []; Verbs = []; + Particles = []; } public void Dispose() { @@ -142,6 +144,7 @@ public void CopyFrom(MutableAppearance appearance) { VisContents.AddRange(appearance.VisContents); Filters.AddRange(appearance.Filters); Verbs.AddRange(appearance.Verbs); + Particles.AddRange(appearance.Particles); Array.Copy(appearance.Transform, Transform, 6); } @@ -176,6 +179,7 @@ public bool Equals(MutableAppearance? appearance) { if (appearance.VisContents.Count != VisContents.Count) return false; if (appearance.Filters.Count != Filters.Count) return false; if (appearance.Verbs.Count != Verbs.Count) return false; + if (appearance.Particles.Count != Particles.Count) return false; if (appearance.Override != Override) return false; if (appearance.Maptext != Maptext) return false; if (appearance.MaptextSize != MaptextSize) return false; @@ -201,6 +205,10 @@ public bool Equals(MutableAppearance? appearance) { if (appearance.Verbs[i] != Verbs[i]) return false; } + for (int i = 0; i < Particles.Count; i++) { + if (appearance.Particles[i] != Particles[i]) return false; + } + for (int i = 0; i < 6; i++) { if (!appearance.Transform[i].Equals(Transform[i])) return false; } @@ -282,6 +290,10 @@ public override int GetHashCode() { hashCode.Add(visContent); } + foreach (int particlesObject in Particles) { + hashCode.Add(particlesObject); + } + foreach (DreamFilter filter in Filters) { hashCode.Add(filter); } @@ -404,6 +416,7 @@ public enum IconAppearanceProperty : byte { Overlays, Underlays, VisContents, + Particles, Filters, Verbs, Transform, From a08e3b1be3a52364fb2ae4215181991cb352a440 Mon Sep 17 00:00:00 2001 From: amy Date: Wed, 19 Feb 2025 22:56:03 +0000 Subject: [PATCH 14/29] so close --- OpenDreamClient/Rendering/DreamViewOverlay.cs | 14 ++++------- .../Objects/Types/DreamObjectParticles.cs | 8 +++++-- .../Rendering/DreamParticlesComponent.cs | 24 +++++++++---------- 3 files changed, 23 insertions(+), 23 deletions(-) diff --git a/OpenDreamClient/Rendering/DreamViewOverlay.cs b/OpenDreamClient/Rendering/DreamViewOverlay.cs index 5caa652ae2..e3b3ea3726 100644 --- a/OpenDreamClient/Rendering/DreamViewOverlay.cs +++ b/OpenDreamClient/Rendering/DreamViewOverlay.cs @@ -379,10 +379,11 @@ private void ProcessIconComponents(DreamIcon icon, Vector2 position, EntityUid u //query entity for particles component //if it has one, add it to the result list - if(_particlesManager.TryGetParticleSystem(current.Uid, out var particlesSystem)){ - current.Particles ??= new(); - current.Particles.Add(particlesSystem); - } + foreach(var particleNetEntity in icon.Appearance.Particles) + if(_particlesManager.TryGetParticleSystem(_entityManager.GetEntity(particleNetEntity), out var particlesSystem)){ + current.Particles ??= new(); + current.Particles.Add(particlesSystem); + } //flatten KeepTogetherGroup. Done here so we get implicit recursive iteration down the tree. if (current.KeepTogetherGroup?.Count > 0) { @@ -461,15 +462,10 @@ public void DrawIcon(DrawingHandleWorld handle, Vector2i renderTargetSize, Rende var pixelPosition = (iconMetaData.Position + positionOffset) * EyeManager.PixelsPerMeter; if(iconMetaData.Particles is not null) { - foreach(var particleSystem in iconMetaData.Particles){ - var particleRenderTarget = _renderTargetPool.Rent(particleSystem.RenderSize); handle.UseShader(GetBlendAndColorShader(iconMetaData, ignoreColor: true)); particleSystem.Draw(handle, pixelPosition); handle.SetTransform(CalculateDrawingMatrix(iconMetaData.TransformToApply, pixelPosition-particleSystem.RenderSize/2, particleSystem.RenderSize, renderTargetSize)); - handle.DrawTextureRect(particleRenderTarget.Texture, Box2.FromDimensions(Vector2.Zero, particleSystem.RenderSize)); - - _renderTargetPool.ReturnAtEndOfFrame(particleRenderTarget); } } //if frame is null, this doesn't require a draw, so return NOP diff --git a/OpenDreamRuntime/Objects/Types/DreamObjectParticles.cs b/OpenDreamRuntime/Objects/Types/DreamObjectParticles.cs index d9b1fc5d62..83718d9d58 100644 --- a/OpenDreamRuntime/Objects/Types/DreamObjectParticles.cs +++ b/OpenDreamRuntime/Objects/Types/DreamObjectParticles.cs @@ -17,11 +17,14 @@ public sealed class DreamObjectParticles : DreamObject { private List _iconStates = new(); public DreamObjectParticles(DreamObjectDefinition objectDefinition) : base(objectDefinition) { - Entity = EntityManager.SpawnEntity(null, new MapCoordinates(0, 0, MapId.Nullspace)); //spawning an entity in nullspace means it never actually gets sent to any clients until it's placed on the map, or it gets a PVS override + Entity = EntityManager.SpawnEntity(null, new MapCoordinates(0, 0, MapId.Nullspace)); //spawning an entity in nullspace means it never actually gets sent to any clients until it's put in the particles list on an atom, when PVS override happens ParticlesComponent = EntityManager.AddComponent(Entity); //populate component with settings from type + foreach(KeyValuePair kv in objectDefinition.Variables){ + if(objectDefinition.ConstVariables is not null && !objectDefinition.ConstVariables.Contains(kv.Key)) + SetVar(kv.Key, kv.Value); + } //check if I need to manually send update events to the component? - //collect entities client-side for the rendermetadata } protected override void SetVar(string varName, DreamValue value) { @@ -285,6 +288,7 @@ protected override void SetVar(string varName, DreamValue value) { } 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 8202bfcba3..5da24de3e9 100644 --- a/OpenDreamShared/Rendering/DreamParticlesComponent.cs +++ b/OpenDreamShared/Rendering/DreamParticlesComponent.cs @@ -12,10 +12,10 @@ namespace OpenDreamShared.Rendering; [NetworkedComponent] public abstract partial class SharedDreamParticlesComponent : Component { - [ViewVariables(VVAccess.ReadWrite)] public int Width = 640; - [ViewVariables(VVAccess.ReadWrite)] public int Height = 480; - [ViewVariables(VVAccess.ReadWrite)] public int Count = 1000; - [ViewVariables(VVAccess.ReadWrite)] public float Spawning = 100; + [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; @@ -25,11 +25,11 @@ public abstract partial class SharedDreamParticlesComponent : Component { [ViewVariables(VVAccess.ReadWrite)] public float LifespanHigh; [ViewVariables(VVAccess.ReadWrite)] public float LifespanLow; [ViewVariables(VVAccess.ReadWrite)] public ParticlePropertyType LifespanType; - [ViewVariables(VVAccess.ReadWrite)] public int FadeInHigh = 0; - [ViewVariables(VVAccess.ReadWrite)] public int FadeInLow = 0; + [ViewVariables(VVAccess.ReadWrite)] public int FadeInHigh; + [ViewVariables(VVAccess.ReadWrite)] public int FadeInLow; [ViewVariables(VVAccess.ReadWrite)] public ParticlePropertyType FadeInType; - [ViewVariables(VVAccess.ReadWrite)] public int FadeOutHigh = 0; - [ViewVariables(VVAccess.ReadWrite)] public int FadeOutLow = 0; + [ViewVariables(VVAccess.ReadWrite)] public int FadeOutHigh; + [ViewVariables(VVAccess.ReadWrite)] public int FadeOutLow; [ViewVariables(VVAccess.ReadWrite)] public ParticlePropertyType FadeOutType; [ViewVariables(VVAccess.ReadWrite)] public Vector3 SpawnPositionHigh; @@ -51,16 +51,16 @@ public abstract partial class SharedDreamParticlesComponent : Component { [ViewVariables(VVAccess.ReadWrite)] public Vector2 ScaleLow; [ViewVariables(VVAccess.ReadWrite)] public ParticlePropertyType ScaleType; //Rotation applied to the particles in degrees - [ViewVariables(VVAccess.ReadWrite)] public float RotationHigh = 0; - [ViewVariables(VVAccess.ReadWrite)] public float RotationLow = 0; + [ViewVariables(VVAccess.ReadWrite)] public float RotationHigh; + [ViewVariables(VVAccess.ReadWrite)] public float RotationLow; [ViewVariables(VVAccess.ReadWrite)] public ParticlePropertyType RotationType; //Increase in scale per second [ViewVariables(VVAccess.ReadWrite)] public Vector2 GrowthHigh; [ViewVariables(VVAccess.ReadWrite)] public Vector2 GrowthLow; [ViewVariables(VVAccess.ReadWrite)] public ParticlePropertyType GrowthType; //Change in rotation per second - [ViewVariables(VVAccess.ReadWrite)] public float SpinHigh = 0; - [ViewVariables(VVAccess.ReadWrite)] public float SpinLow = 0; + [ViewVariables(VVAccess.ReadWrite)] public float SpinHigh; + [ViewVariables(VVAccess.ReadWrite)] public float SpinLow; [ViewVariables(VVAccess.ReadWrite)] public ParticlePropertyType SpinType; [ViewVariables(VVAccess.ReadWrite)] public Vector3 DriftHigh; [ViewVariables(VVAccess.ReadWrite)] public Vector3 DriftLow; From 608cfc33a1870602248a2694550c80c430c2145c Mon Sep 17 00:00:00 2001 From: amy Date: Wed, 19 Feb 2025 23:14:17 +0000 Subject: [PATCH 15/29] mark component as dirty --- OpenDreamRuntime/Objects/DreamObject.cs | 1 + OpenDreamRuntime/Objects/DreamObjectDefinition.cs | 5 ++++- OpenDreamRuntime/Objects/DreamObjectTree.cs | 4 +++- OpenDreamRuntime/Objects/Types/DreamObjectParticles.cs | 2 +- OpenDreamRuntime/Rendering/ServerDreamParticleSystem.cs | 6 +++++- 5 files changed, 14 insertions(+), 4 deletions(-) diff --git a/OpenDreamRuntime/Objects/DreamObject.cs b/OpenDreamRuntime/Objects/DreamObject.cs index e3e4389248..d09c91d3ff 100644 --- a/OpenDreamRuntime/Objects/DreamObject.cs +++ b/OpenDreamRuntime/Objects/DreamObject.cs @@ -44,6 +44,7 @@ public class DreamObject { protected PvsOverrideSystem? PvsOverrideSystem => ObjectDefinition.PvsOverrideSystem; protected MetaDataSystem? MetaDataSystem => ObjectDefinition.MetaDataSystem; protected ServerVerbSystem? VerbSystem => ObjectDefinition.VerbSystem; + protected ServerDreamParticlesSystem? ParticlesSystem => ObjectDefinition.ParticlesSystem; protected Dictionary? Variables; //handle to the list of vars on this object so that it's only created once and refs to object.vars are consistent diff --git a/OpenDreamRuntime/Objects/DreamObjectDefinition.cs b/OpenDreamRuntime/Objects/DreamObjectDefinition.cs index 9c89943200..a77432c0fe 100644 --- a/OpenDreamRuntime/Objects/DreamObjectDefinition.cs +++ b/OpenDreamRuntime/Objects/DreamObjectDefinition.cs @@ -30,6 +30,7 @@ public sealed class DreamObjectDefinition { public readonly PvsOverrideSystem? PvsOverrideSystem; public readonly MetaDataSystem? MetaDataSystem; public readonly ServerVerbSystem? VerbSystem; + public readonly ServerDreamParticlesSystem? ParticlesSystem; public readonly TreeEntry TreeEntry; public string Type => TreeEntry.Path; @@ -74,6 +75,7 @@ public DreamObjectDefinition(DreamObjectDefinition copyFrom) { PvsOverrideSystem = copyFrom.PvsOverrideSystem; MetaDataSystem = copyFrom.MetaDataSystem; VerbSystem = copyFrom.VerbSystem; + ParticlesSystem = copyFrom.ParticlesSystem; TreeEntry = copyFrom.TreeEntry; InitializationProc = copyFrom.InitializationProc; @@ -88,7 +90,7 @@ public DreamObjectDefinition(DreamObjectDefinition copyFrom) { Verbs = new List(copyFrom.Verbs); } - public DreamObjectDefinition(DreamManager dreamManager, DreamObjectTree objectTree, AtomManager atomManager, IDreamMapManager dreamMapManager, IMapManager mapManager, DreamResourceManager dreamResourceManager, WalkManager walkManager, IEntityManager entityManager, IPlayerManager playerManager, ISerializationManager serializationManager, ServerAppearanceSystem? appearanceSystem, TransformSystem? transformSystem, PvsOverrideSystem? pvsOverrideSystem, MetaDataSystem? metaDataSystem, ServerVerbSystem? verbSystem, TreeEntry? treeEntry) { + public DreamObjectDefinition(DreamManager dreamManager, DreamObjectTree objectTree, AtomManager atomManager, IDreamMapManager dreamMapManager, IMapManager mapManager, DreamResourceManager dreamResourceManager, WalkManager walkManager, IEntityManager entityManager, IPlayerManager playerManager, ISerializationManager serializationManager, ServerAppearanceSystem? appearanceSystem, TransformSystem? transformSystem, PvsOverrideSystem? pvsOverrideSystem, MetaDataSystem? metaDataSystem, ServerVerbSystem? verbSystem, ServerDreamParticlesSystem? particlesSystem, TreeEntry? treeEntry) { DreamManager = dreamManager; ObjectTree = objectTree; AtomManager = atomManager; @@ -104,6 +106,7 @@ public DreamObjectDefinition(DreamManager dreamManager, DreamObjectTree objectTr PvsOverrideSystem = pvsOverrideSystem; MetaDataSystem = metaDataSystem; VerbSystem = verbSystem; + ParticlesSystem = particlesSystem; TreeEntry = treeEntry; diff --git a/OpenDreamRuntime/Objects/DreamObjectTree.cs b/OpenDreamRuntime/Objects/DreamObjectTree.cs index ee9e2394d3..2fe25b5491 100644 --- a/OpenDreamRuntime/Objects/DreamObjectTree.cs +++ b/OpenDreamRuntime/Objects/DreamObjectTree.cs @@ -71,6 +71,7 @@ public sealed class DreamObjectTree { private PvsOverrideSystem? _pvsOverrideSystem; private MetaDataSystem? _metaDataSystem; private ServerVerbSystem? _verbSystem; + private ServerDreamParticlesSystem? _particlesSystem; public void LoadJson(DreamCompiledJson json) { var types = json.Types ?? Array.Empty(); @@ -84,6 +85,7 @@ public void LoadJson(DreamCompiledJson json) { _entitySystemManager.TryGetEntitySystem(out _pvsOverrideSystem); _entitySystemManager.TryGetEntitySystem(out _metaDataSystem); _entitySystemManager.TryGetEntitySystem(out _verbSystem); + _entitySystemManager.TryGetEntitySystem(out _particlesSystem); Strings = json.Strings ?? new(); @@ -335,7 +337,7 @@ private void LoadTypesFromJson(DreamTypeJson[] types, ProcDefinitionJson[]? proc foreach (TreeEntry type in GetAllDescendants(Root)) { int typeId = type.Id; DreamTypeJson jsonType = types[typeId]; - var definition = new DreamObjectDefinition(_dreamManager, this, _atomManager, _dreamMapManager, _mapManager, _dreamResourceManager, _walkManager, _entityManager, _playerManager, _serializationManager, _appearanceSystem, _transformSystem, _pvsOverrideSystem, _metaDataSystem, _verbSystem, type); + var definition = new DreamObjectDefinition(_dreamManager, this, _atomManager, _dreamMapManager, _mapManager, _dreamResourceManager, _walkManager, _entityManager, _playerManager, _serializationManager, _appearanceSystem, _transformSystem, _pvsOverrideSystem, _metaDataSystem, _verbSystem, _particlesSystem, type); type.ObjectDefinition = definition; type.TreeIndex = treeIndex++; diff --git a/OpenDreamRuntime/Objects/Types/DreamObjectParticles.cs b/OpenDreamRuntime/Objects/Types/DreamObjectParticles.cs index 83718d9d58..5f74e76035 100644 --- a/OpenDreamRuntime/Objects/Types/DreamObjectParticles.cs +++ b/OpenDreamRuntime/Objects/Types/DreamObjectParticles.cs @@ -288,7 +288,7 @@ protected override void SetVar(string varName, DreamValue value) { } break; } - + ParticlesSystem!.MarkDirty((Entity, ParticlesComponent)); base.SetVar(varName, value); //all calls should set the internal vars, so GetVar() can just be default also } } diff --git a/OpenDreamRuntime/Rendering/ServerDreamParticleSystem.cs b/OpenDreamRuntime/Rendering/ServerDreamParticleSystem.cs index d530f7cdb2..8a2edd5274 100644 --- a/OpenDreamRuntime/Rendering/ServerDreamParticleSystem.cs +++ b/OpenDreamRuntime/Rendering/ServerDreamParticleSystem.cs @@ -2,4 +2,8 @@ namespace OpenDreamRuntime.Rendering; -public sealed class ServerDreamParticlesSystem : SharedDreamParticlesSystem {} +public sealed class ServerDreamParticlesSystem : SharedDreamParticlesSystem { + public void MarkDirty(Entity ent){ + Dirty(ent, ent.Comp); + } +} From 0311905b0dba803e6565ba7dbb8d9df7bdec4258 Mon Sep 17 00:00:00 2001 From: amy Date: Wed, 19 Feb 2025 23:41:52 +0000 Subject: [PATCH 16/29] I'm tired and it doesn't work and I don't know why --- .../Rendering/ClientDreamParticlesSystem.cs | 4 +- .../Rendering/DreamParticlesComponent.cs | 3 +- .../Rendering/DreamParticlesComponent.cs | 3 +- .../Rendering/DreamParticlesComponent.cs | 95 ++++++++++--------- 4 files changed, 54 insertions(+), 51 deletions(-) diff --git a/OpenDreamClient/Rendering/ClientDreamParticlesSystem.cs b/OpenDreamClient/Rendering/ClientDreamParticlesSystem.cs index 3805f2a53d..929af157c0 100644 --- a/OpenDreamClient/Rendering/ClientDreamParticlesSystem.cs +++ b/OpenDreamClient/Rendering/ClientDreamParticlesSystem.cs @@ -23,13 +23,13 @@ public sealed class ClientDreamParticlesSystem : SharedDreamParticlesSystem public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnDreamParticlesComponentChange); + SubscribeLocalEvent(OnDreamParticlesComponentChange); SubscribeLocalEvent(HandleComponentAdd); SubscribeLocalEvent(HandleComponentRemove); RenderTargetPool = new(_clyde); } - private void OnDreamParticlesComponentChange(EntityUid uid, DreamParticlesComponent component, ref ComponentChange args) + private void OnDreamParticlesComponentChange(EntityUid uid, DreamParticlesComponent component, ref AfterAutoHandleStateEvent args) { if(_particlesManager.TryGetParticleSystem(uid, out var system)) system.UpdateSystem(GetParticleSystemArgs(component)); diff --git a/OpenDreamClient/Rendering/DreamParticlesComponent.cs b/OpenDreamClient/Rendering/DreamParticlesComponent.cs index 9cf5d70d3a..8d5ac27bd8 100644 --- a/OpenDreamClient/Rendering/DreamParticlesComponent.cs +++ b/OpenDreamClient/Rendering/DreamParticlesComponent.cs @@ -1,10 +1,11 @@ using OpenDreamShared.Rendering; using Robust.Client.Graphics; +using Robust.Shared.GameStates; namespace OpenDreamClient.Rendering; -[RegisterComponent] +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true)] public sealed partial class DreamParticlesComponent : SharedDreamParticlesComponent { public ParticleSystem? particlesSystem; } diff --git a/OpenDreamRuntime/Rendering/DreamParticlesComponent.cs b/OpenDreamRuntime/Rendering/DreamParticlesComponent.cs index 7cfbe703a3..f5a213f0bc 100644 --- a/OpenDreamRuntime/Rendering/DreamParticlesComponent.cs +++ b/OpenDreamRuntime/Rendering/DreamParticlesComponent.cs @@ -1,7 +1,8 @@ using OpenDreamShared.Rendering; using Robust.Shared.GameObjects; +using Robust.Shared.GameStates; namespace OpenDreamShared.Rendering; -[RegisterComponent] +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true)] public sealed partial class DreamParticlesComponent : SharedDreamParticlesComponent {} diff --git a/OpenDreamShared/Rendering/DreamParticlesComponent.cs b/OpenDreamShared/Rendering/DreamParticlesComponent.cs index 5da24de3e9..b8c02e8f8b 100644 --- a/OpenDreamShared/Rendering/DreamParticlesComponent.cs +++ b/OpenDreamShared/Rendering/DreamParticlesComponent.cs @@ -1,6 +1,7 @@ using System.Numerics; using OpenDreamShared.Dream; +using Robust.Shared.Analyzers; using Robust.Shared.GameObjects; using Robust.Shared.GameStates; using Robust.Shared.Maths; @@ -10,59 +11,59 @@ namespace OpenDreamShared.Rendering; -[NetworkedComponent] +[NetworkedComponent, AutoGenerateComponentState(true)] public abstract partial class SharedDreamParticlesComponent : Component { - [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 float LifespanHigh; - [ViewVariables(VVAccess.ReadWrite)] public float LifespanLow; - [ViewVariables(VVAccess.ReadWrite)] public ParticlePropertyType LifespanType; - [ViewVariables(VVAccess.ReadWrite)] public int FadeInHigh; - [ViewVariables(VVAccess.ReadWrite)] public int FadeInLow; - [ViewVariables(VVAccess.ReadWrite)] public ParticlePropertyType FadeInType; - [ViewVariables(VVAccess.ReadWrite)] public int FadeOutHigh; - [ViewVariables(VVAccess.ReadWrite)] public int FadeOutLow; - [ViewVariables(VVAccess.ReadWrite)] public ParticlePropertyType FadeOutType; + [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 float LifespanHigh; + [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public float LifespanLow; + [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public ParticlePropertyType LifespanType; + [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public int FadeInHigh; + [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public int FadeInLow; + [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public ParticlePropertyType FadeInType; + [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public int FadeOutHigh; + [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public int FadeOutLow; + [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public ParticlePropertyType FadeOutType; - [ViewVariables(VVAccess.ReadWrite)] public Vector3 SpawnPositionHigh; - [ViewVariables(VVAccess.ReadWrite)] public Vector3 SpawnPositionLow; - [ViewVariables(VVAccess.ReadWrite)] public ParticlePropertyType SpawnPositionType; + [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public Vector3 SpawnPositionHigh; + [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public Vector3 SpawnPositionLow; + [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public ParticlePropertyType SpawnPositionType; //Starting velocity of the particles - [ViewVariables(VVAccess.ReadWrite)] public Vector3 SpawnVelocityHigh; - [ViewVariables(VVAccess.ReadWrite)] public Vector3 SpawnVelocityLow; - [ViewVariables(VVAccess.ReadWrite)] public ParticlePropertyType SpawnVelocityType; + [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public Vector3 SpawnVelocityHigh; + [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public Vector3 SpawnVelocityLow; + [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public ParticlePropertyType SpawnVelocityType; //Acceleration applied to the particles per second - [ViewVariables(VVAccess.ReadWrite)] public Vector3 AccelerationHigh; - [ViewVariables(VVAccess.ReadWrite)] public Vector3 AccelerationLow; - [ViewVariables(VVAccess.ReadWrite)] public ParticlePropertyType AccelerationType; - [ViewVariables(VVAccess.ReadWrite)] public Vector3 FrictionHigh; - [ViewVariables(VVAccess.ReadWrite)] public Vector3 FrictionLow; - [ViewVariables(VVAccess.ReadWrite)] public ParticlePropertyType FrictionType; + [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public Vector3 AccelerationHigh; + [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public Vector3 AccelerationLow; + [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public ParticlePropertyType AccelerationType; + [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public Vector3 FrictionHigh; + [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public Vector3 FrictionLow; + [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public ParticlePropertyType FrictionType; //Scaling applied to the particles in (x,y) - [ViewVariables(VVAccess.ReadWrite)] public Vector2 ScaleHigh; - [ViewVariables(VVAccess.ReadWrite)] public Vector2 ScaleLow; - [ViewVariables(VVAccess.ReadWrite)] public ParticlePropertyType ScaleType; + [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public Vector2 ScaleHigh; + [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public Vector2 ScaleLow; + [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public ParticlePropertyType ScaleType; //Rotation applied to the particles in degrees - [ViewVariables(VVAccess.ReadWrite)] public float RotationHigh; - [ViewVariables(VVAccess.ReadWrite)] public float RotationLow; - [ViewVariables(VVAccess.ReadWrite)] public ParticlePropertyType RotationType; + [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public float RotationHigh; + [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public float RotationLow; + [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public ParticlePropertyType RotationType; //Increase in scale per second - [ViewVariables(VVAccess.ReadWrite)] public Vector2 GrowthHigh; - [ViewVariables(VVAccess.ReadWrite)] public Vector2 GrowthLow; - [ViewVariables(VVAccess.ReadWrite)] public ParticlePropertyType GrowthType; + [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public Vector2 GrowthHigh; + [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public Vector2 GrowthLow; + [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public ParticlePropertyType GrowthType; //Change in rotation per second - [ViewVariables(VVAccess.ReadWrite)] public float SpinHigh; - [ViewVariables(VVAccess.ReadWrite)] public float SpinLow; - [ViewVariables(VVAccess.ReadWrite)] public ParticlePropertyType SpinType; - [ViewVariables(VVAccess.ReadWrite)] public Vector3 DriftHigh; - [ViewVariables(VVAccess.ReadWrite)] public Vector3 DriftLow; - [ViewVariables(VVAccess.ReadWrite)] public ParticlePropertyType DriftType; + [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public float SpinHigh; + [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public float SpinLow; + [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public ParticlePropertyType SpinType; + [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public Vector3 DriftHigh; + [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public Vector3 DriftLow; + [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public ParticlePropertyType DriftType; } From ee38d3446d272e8f5cd2d149d260e0867d389081 Mon Sep 17 00:00:00 2001 From: amylizzle Date: Thu, 20 Feb 2025 10:06:51 +0000 Subject: [PATCH 17/29] split components bad --- .../Rendering/ClientDreamParticlesSystem.cs | 3 +-- OpenDreamClient/Rendering/DreamParticlesComponent.cs | 11 ----------- OpenDreamClient/Rendering/DreamViewOverlay.cs | 2 -- OpenDreamRuntime/Rendering/DreamParticlesComponent.cs | 8 -------- OpenDreamShared/Rendering/DreamParticlesComponent.cs | 4 ++-- 5 files changed, 3 insertions(+), 25 deletions(-) delete mode 100644 OpenDreamClient/Rendering/DreamParticlesComponent.cs delete mode 100644 OpenDreamRuntime/Rendering/DreamParticlesComponent.cs diff --git a/OpenDreamClient/Rendering/ClientDreamParticlesSystem.cs b/OpenDreamClient/Rendering/ClientDreamParticlesSystem.cs index 929af157c0..1484e026a0 100644 --- a/OpenDreamClient/Rendering/ClientDreamParticlesSystem.cs +++ b/OpenDreamClient/Rendering/ClientDreamParticlesSystem.cs @@ -37,12 +37,11 @@ private void OnDreamParticlesComponentChange(EntityUid uid, DreamParticlesCompon private void HandleComponentAdd(EntityUid uid, DreamParticlesComponent component, ref ComponentAdd args) { - component.particlesSystem = _particlesManager.CreateParticleSystem(uid, GetParticleSystemArgs(component)); + _particlesManager.CreateParticleSystem(uid, GetParticleSystemArgs(component)); } private void HandleComponentRemove(EntityUid uid, DreamParticlesComponent component, ref ComponentRemove args) { - component.particlesSystem = null; _particlesManager.DestroyParticleSystem(uid); } diff --git a/OpenDreamClient/Rendering/DreamParticlesComponent.cs b/OpenDreamClient/Rendering/DreamParticlesComponent.cs deleted file mode 100644 index 8d5ac27bd8..0000000000 --- a/OpenDreamClient/Rendering/DreamParticlesComponent.cs +++ /dev/null @@ -1,11 +0,0 @@ - -using OpenDreamShared.Rendering; -using Robust.Client.Graphics; -using Robust.Shared.GameStates; - -namespace OpenDreamClient.Rendering; - -[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true)] -public sealed partial class DreamParticlesComponent : SharedDreamParticlesComponent { - public ParticleSystem? particlesSystem; -} diff --git a/OpenDreamClient/Rendering/DreamViewOverlay.cs b/OpenDreamClient/Rendering/DreamViewOverlay.cs index e3b3ea3726..d5e7e0172a 100644 --- a/OpenDreamClient/Rendering/DreamViewOverlay.cs +++ b/OpenDreamClient/Rendering/DreamViewOverlay.cs @@ -59,7 +59,6 @@ internal sealed partial class DreamViewOverlay : Overlay { private readonly ClientImagesSystem _clientImagesSystem; private readonly EntityQuery _spriteQuery; - private readonly EntityQuery _particlesQuery; private readonly EntityQuery _xformQuery; private readonly EntityQuery _mobSightQuery; @@ -91,7 +90,6 @@ public DreamViewOverlay(RenderTargetPool renderTargetPool, TransformSystem trans _clientImagesSystem = clientImagesSystem; _spriteQuery = _entityManager.GetEntityQuery(); - _particlesQuery = _entityManager.GetEntityQuery(); _xformQuery = _entityManager.GetEntityQuery(); _mobSightQuery = _entityManager.GetEntityQuery(); diff --git a/OpenDreamRuntime/Rendering/DreamParticlesComponent.cs b/OpenDreamRuntime/Rendering/DreamParticlesComponent.cs deleted file mode 100644 index f5a213f0bc..0000000000 --- a/OpenDreamRuntime/Rendering/DreamParticlesComponent.cs +++ /dev/null @@ -1,8 +0,0 @@ -using OpenDreamShared.Rendering; -using Robust.Shared.GameObjects; -using Robust.Shared.GameStates; - -namespace OpenDreamShared.Rendering; - -[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true)] -public sealed partial class DreamParticlesComponent : SharedDreamParticlesComponent {} diff --git a/OpenDreamShared/Rendering/DreamParticlesComponent.cs b/OpenDreamShared/Rendering/DreamParticlesComponent.cs index b8c02e8f8b..7dce99ea3c 100644 --- a/OpenDreamShared/Rendering/DreamParticlesComponent.cs +++ b/OpenDreamShared/Rendering/DreamParticlesComponent.cs @@ -11,8 +11,8 @@ namespace OpenDreamShared.Rendering; -[NetworkedComponent, AutoGenerateComponentState(true)] -public abstract partial class SharedDreamParticlesComponent : Component { +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(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; From 47dc47140689abd5779d2db34cb082f9492a6f60 Mon Sep 17 00:00:00 2001 From: amylizzle Date: Thu, 20 Feb 2025 10:49:24 +0000 Subject: [PATCH 18/29] it *still* doesn't work and I *still* have no idea why --- OpenDreamClient/Rendering/ClientDreamParticlesSystem.cs | 1 - OpenDreamRuntime/Objects/Types/DreamObjectParticles.cs | 2 +- TestGame/code.dm | 6 +++++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/OpenDreamClient/Rendering/ClientDreamParticlesSystem.cs b/OpenDreamClient/Rendering/ClientDreamParticlesSystem.cs index 1484e026a0..0763d3f7f1 100644 --- a/OpenDreamClient/Rendering/ClientDreamParticlesSystem.cs +++ b/OpenDreamClient/Rendering/ClientDreamParticlesSystem.cs @@ -13,7 +13,6 @@ namespace OpenDreamClient.Rendering; public sealed class ClientDreamParticlesSystem : SharedDreamParticlesSystem { [Dependency] private readonly ParticlesManager _particlesManager = default!; - [Dependency] private readonly IResourceCache _resourceCache = default!; [Dependency] private readonly IGameTiming _gameTiming = default!; [Dependency] private readonly ClientAppearanceSystem _appearanceSystem = default!; [Dependency] private readonly IClyde _clyde = default!; diff --git a/OpenDreamRuntime/Objects/Types/DreamObjectParticles.cs b/OpenDreamRuntime/Objects/Types/DreamObjectParticles.cs index 5f74e76035..ebdadfceff 100644 --- a/OpenDreamRuntime/Objects/Types/DreamObjectParticles.cs +++ b/OpenDreamRuntime/Objects/Types/DreamObjectParticles.cs @@ -21,7 +21,7 @@ public DreamObjectParticles(DreamObjectDefinition objectDefinition) : base(objec ParticlesComponent = EntityManager.AddComponent(Entity); //populate component with settings from type foreach(KeyValuePair kv in objectDefinition.Variables){ - if(objectDefinition.ConstVariables is not null && !objectDefinition.ConstVariables.Contains(kv.Key)) + if(!(kv.Key == "parent_type" || kv.Key == "type" || kv.Key == "vars")) SetVar(kv.Key, kv.Value); } //check if I need to manually send update events to the component? diff --git a/TestGame/code.dm b/TestGame/code.dm index 2f4a238ed3..2635d26cd1 100644 --- a/TestGame/code.dm +++ b/TestGame/code.dm @@ -81,7 +81,7 @@ desc = "Such a beautiful smile." gender = MALE see_invisible = 101 - particles = new /particles/swarm/bees + New() ..() @@ -91,6 +91,10 @@ world.log << "login ran" src.client.screen += new /obj/order_test_item/plane_master //used for render tests + verb/add_particles() + particles = new /particles/swarm/bees + usr << "not the bees!" + verb/winget_test() usr << "windows: [json_encode(winget(usr, null, "windows"))]" usr << "panes: [json_encode(winget(usr, null, "panes"))]" From 32f9e1f428426f5e3302fad9a88bfe50a1994216 Mon Sep 17 00:00:00 2001 From: amylizzle Date: Thu, 20 Feb 2025 12:53:21 +0000 Subject: [PATCH 19/29] manual state --- .../Rendering/ClientDreamParticlesSystem.cs | 55 ++++++- .../Rendering/DreamParticlesComponent.cs | 154 ++++++++++++------ .../Rendering/SharedDreamParticleSystem.cs | 62 ++++++- RobustToolbox | 2 +- 4 files changed, 221 insertions(+), 52 deletions(-) diff --git a/OpenDreamClient/Rendering/ClientDreamParticlesSystem.cs b/OpenDreamClient/Rendering/ClientDreamParticlesSystem.cs index 0763d3f7f1..dd7d1e31f8 100644 --- a/OpenDreamClient/Rendering/ClientDreamParticlesSystem.cs +++ b/OpenDreamClient/Rendering/ClientDreamParticlesSystem.cs @@ -3,8 +3,10 @@ using Pidgin; using Robust.Client.Graphics; using Robust.Client.ResourceManagement; +using Robust.Shared.GameStates; using Robust.Shared.Random; using Robust.Shared.Timing; +using static OpenDreamShared.Rendering.DreamParticlesComponent; using Vector3 = Robust.Shared.Maths.Vector3; namespace OpenDreamClient.Rendering; @@ -22,14 +24,63 @@ public sealed class ClientDreamParticlesSystem : SharedDreamParticlesSystem public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnDreamParticlesComponentChange); + SubscribeLocalEvent(OnDreamParticlesComponentChange); SubscribeLocalEvent(HandleComponentAdd); SubscribeLocalEvent(HandleComponentRemove); RenderTargetPool = new(_clyde); } - private void OnDreamParticlesComponentChange(EntityUid uid, DreamParticlesComponent component, ref AfterAutoHandleStateEvent args) + private void OnDreamParticlesComponentChange(EntityUid uid, DreamParticlesComponent component, ref ComponentHandleState args) { + if (args.Current is not DreamParticlesComponentState state) + return; + component.Width = state.Width; + component.Width = state.Width; + component.Height = state.Height; + component.Count = state.Count; + component.Spawning = state.Spawning; + component.Bound1 = state.Bound1; + component.Bound2 = state.Bound2; + component.Gravity = state.Gravity; + component.Gradient = state.Gradient; + component.Transform = state.Transform; + component.TextureList = state.TextureList; + component.LifespanHigh = state.LifespanHigh; + component.LifespanLow = state.LifespanLow; + component.LifespanType = state.LifespanType; + component.FadeInHigh = state.FadeInHigh; + component.FadeInLow = state.FadeInLow; + component.FadeInType = state.FadeInType; + component.FadeOutHigh = state.FadeOutHigh; + component.FadeOutLow = state.FadeOutLow; + component.FadeOutType = state.FadeOutType; + component.SpawnPositionHigh = state.SpawnPositionHigh; + component.SpawnPositionLow = state.SpawnPositionLow; + component.SpawnPositionType = state.SpawnPositionType; + component.SpawnVelocityHigh = state.SpawnVelocityHigh; + component.SpawnVelocityLow = state.SpawnVelocityLow; + component.SpawnVelocityType = state.SpawnVelocityType; + component.AccelerationHigh = state.AccelerationHigh; + component.AccelerationLow = state.AccelerationLow; + component.AccelerationType = state.AccelerationType; + component.FrictionHigh = state.FrictionHigh; + component.FrictionLow = state.FrictionLow; + component.FrictionType = state.FrictionType; + component.ScaleHigh = state.ScaleHigh; + component.ScaleLow = state.ScaleLow; + component.ScaleType = state.ScaleType; + component.RotationHigh = state.RotationHigh; + component.RotationLow = state.RotationLow; + component.RotationType = state.RotationType; + component.GrowthHigh = state.GrowthHigh; + component.GrowthLow = state.GrowthLow; + component.GrowthType = state.GrowthType; + component.SpinHigh = state.SpinHigh; + component.SpinLow = state.SpinLow; + component.SpinType = state.SpinType; + component.DriftHigh = state.DriftHigh; + component.DriftLow = state.DriftLow; + component.DriftType = state.DriftType; if(_particlesManager.TryGetParticleSystem(uid, out var system)) system.UpdateSystem(GetParticleSystemArgs(component)); } diff --git a/OpenDreamShared/Rendering/DreamParticlesComponent.cs b/OpenDreamShared/Rendering/DreamParticlesComponent.cs index 7dce99ea3c..eec30c14af 100644 --- a/OpenDreamShared/Rendering/DreamParticlesComponent.cs +++ b/OpenDreamShared/Rendering/DreamParticlesComponent.cs @@ -5,65 +5,123 @@ using Robust.Shared.GameObjects; using Robust.Shared.GameStates; using Robust.Shared.Maths; -using Robust.Shared.Utility; +using Robust.Shared.Serialization; +using System; using Robust.Shared.ViewVariables; using Vector3 = Robust.Shared.Maths.Vector3; namespace OpenDreamShared.Rendering; -[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true)] +[RegisterComponent, NetworkedComponent] 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 float LifespanHigh; - [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public float LifespanLow; - [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public ParticlePropertyType LifespanType; - [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public int FadeInHigh; - [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public int FadeInLow; - [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public ParticlePropertyType FadeInType; - [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public int FadeOutHigh; - [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public int FadeOutLow; - [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public ParticlePropertyType FadeOutType; + [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 float LifespanHigh; + [ViewVariables(VVAccess.ReadWrite)] public float LifespanLow; + [ViewVariables(VVAccess.ReadWrite)] public ParticlePropertyType LifespanType; + [ViewVariables(VVAccess.ReadWrite)] public int FadeInHigh; + [ViewVariables(VVAccess.ReadWrite)] public int FadeInLow; + [ViewVariables(VVAccess.ReadWrite)] public ParticlePropertyType FadeInType; + [ViewVariables(VVAccess.ReadWrite)] public int FadeOutHigh; + [ViewVariables(VVAccess.ReadWrite)] public int FadeOutLow; + [ViewVariables(VVAccess.ReadWrite)] public ParticlePropertyType FadeOutType; - [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public Vector3 SpawnPositionHigh; - [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public Vector3 SpawnPositionLow; - [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public ParticlePropertyType SpawnPositionType; + [ViewVariables(VVAccess.ReadWrite)] public Vector3 SpawnPositionHigh; + [ViewVariables(VVAccess.ReadWrite)] public Vector3 SpawnPositionLow; + [ViewVariables(VVAccess.ReadWrite)] public ParticlePropertyType SpawnPositionType; //Starting velocity of the particles - [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public Vector3 SpawnVelocityHigh; - [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public Vector3 SpawnVelocityLow; - [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public ParticlePropertyType SpawnVelocityType; + [ViewVariables(VVAccess.ReadWrite)] public Vector3 SpawnVelocityHigh; + [ViewVariables(VVAccess.ReadWrite)] public Vector3 SpawnVelocityLow; + [ViewVariables(VVAccess.ReadWrite)] public ParticlePropertyType SpawnVelocityType; //Acceleration applied to the particles per second - [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public Vector3 AccelerationHigh; - [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public Vector3 AccelerationLow; - [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public ParticlePropertyType AccelerationType; - [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public Vector3 FrictionHigh; - [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public Vector3 FrictionLow; - [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public ParticlePropertyType FrictionType; + [ViewVariables(VVAccess.ReadWrite)] public Vector3 AccelerationHigh; + [ViewVariables(VVAccess.ReadWrite)] public Vector3 AccelerationLow; + [ViewVariables(VVAccess.ReadWrite)] public ParticlePropertyType AccelerationType; + [ViewVariables(VVAccess.ReadWrite)] public Vector3 FrictionHigh; + [ViewVariables(VVAccess.ReadWrite)] public Vector3 FrictionLow; + [ViewVariables(VVAccess.ReadWrite)] public ParticlePropertyType FrictionType; //Scaling applied to the particles in (x,y) - [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public Vector2 ScaleHigh; - [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public Vector2 ScaleLow; - [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public ParticlePropertyType ScaleType; + [ViewVariables(VVAccess.ReadWrite)] public Vector2 ScaleHigh; + [ViewVariables(VVAccess.ReadWrite)] public Vector2 ScaleLow; + [ViewVariables(VVAccess.ReadWrite)] public ParticlePropertyType ScaleType; //Rotation applied to the particles in degrees - [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public float RotationHigh; - [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public float RotationLow; - [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public ParticlePropertyType RotationType; + [ViewVariables(VVAccess.ReadWrite)] public float RotationHigh; + [ViewVariables(VVAccess.ReadWrite)] public float RotationLow; + [ViewVariables(VVAccess.ReadWrite)] public ParticlePropertyType RotationType; //Increase in scale per second - [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public Vector2 GrowthHigh; - [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public Vector2 GrowthLow; - [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public ParticlePropertyType GrowthType; + [ViewVariables(VVAccess.ReadWrite)] public Vector2 GrowthHigh; + [ViewVariables(VVAccess.ReadWrite)] public Vector2 GrowthLow; + [ViewVariables(VVAccess.ReadWrite)] public ParticlePropertyType GrowthType; //Change in rotation per second - [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public float SpinHigh; - [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public float SpinLow; - [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public ParticlePropertyType SpinType; - [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public Vector3 DriftHigh; - [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public Vector3 DriftLow; - [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public ParticlePropertyType DriftType; + [ViewVariables(VVAccess.ReadWrite)] public float SpinHigh; + [ViewVariables(VVAccess.ReadWrite)] public float SpinLow; + [ViewVariables(VVAccess.ReadWrite)] public ParticlePropertyType SpinType; + [ViewVariables(VVAccess.ReadWrite)] public Vector3 DriftHigh; + [ViewVariables(VVAccess.ReadWrite)] public Vector3 DriftLow; + [ViewVariables(VVAccess.ReadWrite)] public ParticlePropertyType DriftType; + [Serializable, NetSerializable] + public sealed class DreamParticlesComponentState : ComponentState + { + public int Width; + public int Height; + public int Count; + public float Spawning; + public Vector3 Bound1; + public Vector3 Bound2; + public Vector3 Gravity; + public Color[] Gradient = []; + public Matrix3x2 Transform; + public ImmutableAppearance[] TextureList = []; + public float LifespanHigh; + public float LifespanLow; + public ParticlePropertyType LifespanType; + public int FadeInHigh; + public int FadeInLow; + public ParticlePropertyType FadeInType; + public int FadeOutHigh; + public int FadeOutLow; + public ParticlePropertyType FadeOutType; + + public Vector3 SpawnPositionHigh; + public Vector3 SpawnPositionLow; + public ParticlePropertyType SpawnPositionType; + //Starting velocity of the particles + public Vector3 SpawnVelocityHigh; + public Vector3 SpawnVelocityLow; + public ParticlePropertyType SpawnVelocityType; + //Acceleration applied to the particles per second + public Vector3 AccelerationHigh; + public Vector3 AccelerationLow; + public ParticlePropertyType AccelerationType; + public Vector3 FrictionHigh; + public Vector3 FrictionLow; + public ParticlePropertyType FrictionType; + //Scaling applied to the particles in (x,y) + public Vector2 ScaleHigh; + public Vector2 ScaleLow; + public ParticlePropertyType ScaleType; + //Rotation applied to the particles in degrees + public float RotationHigh; + public float RotationLow; + public ParticlePropertyType RotationType; + //Increase in scale per second + public Vector2 GrowthHigh; + public Vector2 GrowthLow; + public ParticlePropertyType GrowthType; + //Change in rotation per second + public float SpinHigh; + public float SpinLow; + public ParticlePropertyType SpinType; + public Vector3 DriftHigh; + public Vector3 DriftLow; + public ParticlePropertyType DriftType; + } } diff --git a/OpenDreamShared/Rendering/SharedDreamParticleSystem.cs b/OpenDreamShared/Rendering/SharedDreamParticleSystem.cs index 2a51e7c465..e79e0cc838 100644 --- a/OpenDreamShared/Rendering/SharedDreamParticleSystem.cs +++ b/OpenDreamShared/Rendering/SharedDreamParticleSystem.cs @@ -1,6 +1,66 @@ using Robust.Shared.GameObjects; +using Robust.Shared.GameStates; +using static OpenDreamShared.Rendering.DreamParticlesComponent; namespace OpenDreamShared.Rendering; -public abstract class SharedDreamParticlesSystem : EntitySystem {} +public abstract class SharedDreamParticlesSystem : EntitySystem { + public override void Initialize() + { + base.Initialize(); + SubscribeLocalEvent(GetCompState); + } + private void GetCompState(Entity ent, ref ComponentGetState args) + { + args.State = new DreamParticlesComponentState + { + Width = ent.Comp.Width, + Height = ent.Comp.Height, + Count = ent.Comp.Count, + Spawning = ent.Comp.Spawning, + Bound1 = ent.Comp.Bound1, + Bound2 = ent.Comp.Bound2, + Gravity = ent.Comp.Gravity, + Gradient = ent.Comp.Gradient, + Transform = ent.Comp.Transform, + TextureList = ent.Comp.TextureList, + LifespanHigh = ent.Comp.LifespanHigh, + LifespanLow = ent.Comp.LifespanLow, + LifespanType = ent.Comp.LifespanType, + FadeInHigh = ent.Comp.FadeInHigh, + FadeInLow = ent.Comp.FadeInLow, + FadeInType = ent.Comp.FadeInType, + FadeOutHigh = ent.Comp.FadeOutHigh, + FadeOutLow = ent.Comp.FadeOutLow, + FadeOutType = ent.Comp.FadeOutType, + SpawnPositionHigh = ent.Comp.SpawnPositionHigh, + SpawnPositionLow = ent.Comp.SpawnPositionLow, + SpawnPositionType = ent.Comp.SpawnPositionType, + SpawnVelocityHigh = ent.Comp.SpawnVelocityHigh, + SpawnVelocityLow = ent.Comp.SpawnVelocityLow, + SpawnVelocityType = ent.Comp.SpawnVelocityType, + AccelerationHigh = ent.Comp.AccelerationHigh, + AccelerationLow = ent.Comp.AccelerationLow, + AccelerationType = ent.Comp.AccelerationType, + FrictionHigh = ent.Comp.FrictionHigh, + FrictionLow = ent.Comp.FrictionLow, + FrictionType = ent.Comp.FrictionType, + ScaleHigh = ent.Comp.ScaleHigh, + ScaleLow = ent.Comp.ScaleLow, + ScaleType = ent.Comp.ScaleType, + RotationHigh = ent.Comp.RotationHigh, + RotationLow = ent.Comp.RotationLow, + RotationType = ent.Comp.RotationType, + GrowthHigh = ent.Comp.GrowthHigh, + GrowthLow = ent.Comp.GrowthLow, + GrowthType = ent.Comp.GrowthType, + SpinHigh = ent.Comp.SpinHigh, + SpinLow = ent.Comp.SpinLow, + SpinType = ent.Comp.SpinType, + DriftHigh = ent.Comp.DriftHigh, + DriftLow = ent.Comp.DriftLow, + DriftType = ent.Comp.DriftType + }; + } +} diff --git a/RobustToolbox b/RobustToolbox index e4190f4f29..cf5a4d0ee9 160000 --- a/RobustToolbox +++ b/RobustToolbox @@ -1 +1 @@ -Subproject commit e4190f4f2900634e332208a77cd6df9cef75c29a +Subproject commit cf5a4d0ee9c82199e35d945d5e517c2cc65648fb From 2aebbc94816f7650a0c0a24e3a34aa074fc177f1 Mon Sep 17 00:00:00 2001 From: amylizzle Date: Thu, 20 Feb 2025 13:58:12 +0000 Subject: [PATCH 20/29] drawing --- .../Rendering/ClientDreamParticlesSystem.cs | 11 +++-------- OpenDreamClient/Rendering/DreamViewOverlay.cs | 3 ++- .../Objects/Types/DreamObjectParticles.cs | 13 ++++++++----- 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/OpenDreamClient/Rendering/ClientDreamParticlesSystem.cs b/OpenDreamClient/Rendering/ClientDreamParticlesSystem.cs index dd7d1e31f8..57444ffaab 100644 --- a/OpenDreamClient/Rendering/ClientDreamParticlesSystem.cs +++ b/OpenDreamClient/Rendering/ClientDreamParticlesSystem.cs @@ -25,7 +25,6 @@ public sealed class ClientDreamParticlesSystem : SharedDreamParticlesSystem public override void Initialize() { base.Initialize(); SubscribeLocalEvent(OnDreamParticlesComponentChange); - SubscribeLocalEvent(HandleComponentAdd); SubscribeLocalEvent(HandleComponentRemove); RenderTargetPool = new(_clyde); } @@ -83,13 +82,9 @@ private void OnDreamParticlesComponentChange(EntityUid uid, DreamParticlesCompon component.DriftType = state.DriftType; if(_particlesManager.TryGetParticleSystem(uid, out var system)) system.UpdateSystem(GetParticleSystemArgs(component)); + else + _particlesManager.CreateParticleSystem(uid, GetParticleSystemArgs(component)); } - - private void HandleComponentAdd(EntityUid uid, DreamParticlesComponent component, ref ComponentAdd args) - { - _particlesManager.CreateParticleSystem(uid, GetParticleSystemArgs(component)); - } - private void HandleComponentRemove(EntityUid uid, DreamParticlesComponent component, ref ComponentRemove args) { _particlesManager.DestroyParticleSystem(uid); @@ -106,7 +101,7 @@ private ParticleSystemArgs GetParticleSystemArgs(DreamParticlesComponent compone icon.SetAppearance(appearance.MustGetId()); icons.Add(icon); } - textureFunc = () => random.Pick(icons).GetTexture(null!, null!, defaultRenderMetaData, null)!; //oh god, so hacky + textureFunc = () => random.Pick(icons).GetTexture(null!, null!, defaultRenderMetaData, null) ?? Texture.White; //oh god, so hacky } var result = new ParticleSystemArgs(textureFunc, new Vector2i(component.Width, component.Height), (uint)component.Count, component.Spawning); GeneratorFloat lifespan = new(); diff --git a/OpenDreamClient/Rendering/DreamViewOverlay.cs b/OpenDreamClient/Rendering/DreamViewOverlay.cs index d5e7e0172a..37249d2ead 100644 --- a/OpenDreamClient/Rendering/DreamViewOverlay.cs +++ b/OpenDreamClient/Rendering/DreamViewOverlay.cs @@ -462,8 +462,9 @@ public void DrawIcon(DrawingHandleWorld handle, Vector2i renderTargetSize, Rende if(iconMetaData.Particles is not null) { foreach(var particleSystem in iconMetaData.Particles){ handle.UseShader(GetBlendAndColorShader(iconMetaData, ignoreColor: true)); - particleSystem.Draw(handle, pixelPosition); handle.SetTransform(CalculateDrawingMatrix(iconMetaData.TransformToApply, pixelPosition-particleSystem.RenderSize/2, particleSystem.RenderSize, renderTargetSize)); + particleSystem.Draw(handle, pixelPosition); + } } //if frame is null, this doesn't require a draw, so return NOP diff --git a/OpenDreamRuntime/Objects/Types/DreamObjectParticles.cs b/OpenDreamRuntime/Objects/Types/DreamObjectParticles.cs index ebdadfceff..7197ff4826 100644 --- a/OpenDreamRuntime/Objects/Types/DreamObjectParticles.cs +++ b/OpenDreamRuntime/Objects/Types/DreamObjectParticles.cs @@ -24,7 +24,6 @@ public DreamObjectParticles(DreamObjectDefinition objectDefinition) : base(objec if(!(kv.Key == "parent_type" || kv.Key == "type" || kv.Key == "vars")) SetVar(kv.Key, kv.Value); } - //check if I need to manually send update events to the component? } protected override void SetVar(string varName, DreamValue value) { @@ -80,12 +79,16 @@ protected override void SetVar(string varName, DreamValue value) { _icons.Clear(); if(value.TryGetValueAsDreamList(out var iconList)){ foreach(DreamValue iconValue in iconList.GetValues()){ - if(iconValue.TryGetValueAsDreamObject(out var icon)){ - _icons.Add(AtomManager.MustGetAppearance(icon).ToMutable()); + if(DreamResourceManager.TryLoadIcon(iconValue, out var iconRsc)) { + MutableAppearance iconAppearance = MutableAppearance.Get(); + iconAppearance.Icon = iconRsc.Id; + _icons.Add(iconAppearance); } } - } else if(value.TryGetValueAsDreamObject(out var dreamObjectIcon)) { - _icons.Add(AtomManager.MustGetAppearance(dreamObjectIcon).ToMutable()); + } else if(DreamResourceManager.TryLoadIcon(value, out var iconRsc)) { + MutableAppearance iconAppearance = MutableAppearance.Get(); + iconAppearance.Icon = iconRsc.Id; + _icons.Add(iconAppearance); } List immutableAppearances = new(); foreach(var icon in _icons){ From 572791b720bfed6d4056486e0a9b60822530df84 Mon Sep 17 00:00:00 2001 From: amylizzle Date: Thu, 20 Feb 2025 14:05:43 +0000 Subject: [PATCH 21/29] whoops --- OpenDreamShared/Dream/MutableAppearance.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/OpenDreamShared/Dream/MutableAppearance.cs b/OpenDreamShared/Dream/MutableAppearance.cs index 805819eaa9..a50da73443 100644 --- a/OpenDreamShared/Dream/MutableAppearance.cs +++ b/OpenDreamShared/Dream/MutableAppearance.cs @@ -139,6 +139,7 @@ public void CopyFrom(MutableAppearance appearance) { VisContents.Clear(); Filters.Clear(); Verbs.Clear(); + Particles.Clear(); Overlays.AddRange(appearance.Overlays); Underlays.AddRange(appearance.Underlays); VisContents.AddRange(appearance.VisContents); From fb0298517df2159a4f47e04719e2911a6366432a Mon Sep 17 00:00:00 2001 From: amylizzle Date: Thu, 20 Feb 2025 16:06:04 +0000 Subject: [PATCH 22/29] working!!!! --- OpenDreamClient/Rendering/ClientDreamParticlesSystem.cs | 1 + OpenDreamClient/Rendering/DreamViewOverlay.cs | 6 +++--- OpenDreamShared/Rendering/DreamParticlesComponent.cs | 4 ++-- RobustToolbox | 2 +- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/OpenDreamClient/Rendering/ClientDreamParticlesSystem.cs b/OpenDreamClient/Rendering/ClientDreamParticlesSystem.cs index 57444ffaab..5f8f48d24e 100644 --- a/OpenDreamClient/Rendering/ClientDreamParticlesSystem.cs +++ b/OpenDreamClient/Rendering/ClientDreamParticlesSystem.cs @@ -127,6 +127,7 @@ private ParticleSystemArgs GetParticleSystemArgs(DreamParticlesComponent compone return Matrix3x2.CreateScale(scale.X + growth.X, scale.Y + growth.Y) * Matrix3x2.CreateRotation(rotation + spin); }; + result.BaseTransform = Matrix3x2.Identity; return result; } diff --git a/OpenDreamClient/Rendering/DreamViewOverlay.cs b/OpenDreamClient/Rendering/DreamViewOverlay.cs index 37249d2ead..ecbb8c99d0 100644 --- a/OpenDreamClient/Rendering/DreamViewOverlay.cs +++ b/OpenDreamClient/Rendering/DreamViewOverlay.cs @@ -461,10 +461,10 @@ public void DrawIcon(DrawingHandleWorld handle, Vector2i renderTargetSize, Rende if(iconMetaData.Particles is not null) { foreach(var particleSystem in iconMetaData.Particles){ - handle.UseShader(GetBlendAndColorShader(iconMetaData, ignoreColor: true)); - handle.SetTransform(CalculateDrawingMatrix(iconMetaData.TransformToApply, pixelPosition-particleSystem.RenderSize/2, particleSystem.RenderSize, renderTargetSize)); - particleSystem.Draw(handle, pixelPosition); + var renderTarget = _renderTargetPool.Rent(particleSystem.RenderSize); + handle.UseShader(GetBlendAndColorShader(iconMetaData, ignoreColor: true)); + particleSystem.Draw(handle, CalculateDrawingMatrix(iconMetaData.TransformToApply, pixelPosition, particleSystem.RenderSize, renderTargetSize)); } } //if frame is null, this doesn't require a draw, so return NOP diff --git a/OpenDreamShared/Rendering/DreamParticlesComponent.cs b/OpenDreamShared/Rendering/DreamParticlesComponent.cs index eec30c14af..921532580b 100644 --- a/OpenDreamShared/Rendering/DreamParticlesComponent.cs +++ b/OpenDreamShared/Rendering/DreamParticlesComponent.cs @@ -49,8 +49,8 @@ public sealed partial class DreamParticlesComponent : Component { [ViewVariables(VVAccess.ReadWrite)] public Vector3 FrictionLow; [ViewVariables(VVAccess.ReadWrite)] public ParticlePropertyType FrictionType; //Scaling applied to the particles in (x,y) - [ViewVariables(VVAccess.ReadWrite)] public Vector2 ScaleHigh; - [ViewVariables(VVAccess.ReadWrite)] public Vector2 ScaleLow; + [ViewVariables(VVAccess.ReadWrite)] public Vector2 ScaleHigh = Vector2.One; + [ViewVariables(VVAccess.ReadWrite)] public Vector2 ScaleLow = Vector2.One; [ViewVariables(VVAccess.ReadWrite)] public ParticlePropertyType ScaleType; //Rotation applied to the particles in degrees [ViewVariables(VVAccess.ReadWrite)] public float RotationHigh; diff --git a/RobustToolbox b/RobustToolbox index cf5a4d0ee9..00ebe3158f 160000 --- a/RobustToolbox +++ b/RobustToolbox @@ -1 +1 @@ -Subproject commit cf5a4d0ee9c82199e35d945d5e517c2cc65648fb +Subproject commit 00ebe3158f3c539cf4ae6be84e6e175100664a45 From 4f2e9a295a231b17d146e0273e7645c1f94124d2 Mon Sep 17 00:00:00 2001 From: amylizzle Date: Thu, 20 Feb 2025 16:56:15 +0000 Subject: [PATCH 23/29] clean up generator --- .../Objects/Types/DreamObjectParticles.cs | 60 +++++++++++-------- 1 file changed, 36 insertions(+), 24 deletions(-) diff --git a/OpenDreamRuntime/Objects/Types/DreamObjectParticles.cs b/OpenDreamRuntime/Objects/Types/DreamObjectParticles.cs index 7197ff4826..53aeb75135 100644 --- a/OpenDreamRuntime/Objects/Types/DreamObjectParticles.cs +++ b/OpenDreamRuntime/Objects/Types/DreamObjectParticles.cs @@ -166,10 +166,8 @@ protected override void SetVar(string varName, DreamValue value) { ParticlesComponent.SpawnPositionLow = new Vector3(dreamValues[0].MustGetValueAsFloat(), dreamValues[1].MustGetValueAsFloat(), dreamValues[2].MustGetValueAsFloat()); ParticlesComponent.SpawnPositionType = ParticlePropertyType.HighValue; } else if(value.TryGetValueAsDreamObject(out var dreamObjectGenerator)) { - List dreamValues = dreamObjectGenerator.B.MustGetValueAsDreamList().GetValues(); - ParticlesComponent.SpawnPositionHigh = new Vector3(dreamValues[0].MustGetValueAsFloat(), dreamValues[1].MustGetValueAsFloat(), dreamValues[2].MustGetValueAsFloat()); - dreamValues = dreamObjectGenerator.A.MustGetValueAsDreamList().GetValues(); - ParticlesComponent.SpawnPositionLow = new Vector3(dreamValues[0].MustGetValueAsFloat(), dreamValues[1].MustGetValueAsFloat(), dreamValues[2].MustGetValueAsFloat()); + ParticlesComponent.SpawnPositionHigh = GetGeneratorValueAsVector3(dreamObjectGenerator.B); + ParticlesComponent.SpawnPositionLow = GetGeneratorValueAsVector3(dreamObjectGenerator.A); ParticlesComponent.SpawnPositionType = ParticlePropertyType.RandomUniform; //TODO all the other distributions } break; @@ -185,10 +183,8 @@ protected override void SetVar(string varName, DreamValue value) { ParticlesComponent.SpawnVelocityLow = new Vector3(dreamValues[0].MustGetValueAsFloat(), dreamValues[1].MustGetValueAsFloat(), dreamValues[2].MustGetValueAsFloat()); ParticlesComponent.SpawnVelocityType = ParticlePropertyType.HighValue; } else if(value.TryGetValueAsDreamObject(out var dreamObjectGenerator)) { - List dreamValues = dreamObjectGenerator.B.MustGetValueAsDreamList().GetValues(); - ParticlesComponent.SpawnVelocityHigh = new Vector3(dreamValues[0].MustGetValueAsFloat(), dreamValues[1].MustGetValueAsFloat(), dreamValues[2].MustGetValueAsFloat()); - dreamValues = dreamObjectGenerator.A.MustGetValueAsDreamList().GetValues(); - ParticlesComponent.SpawnVelocityLow = new Vector3(dreamValues[0].MustGetValueAsFloat(), dreamValues[1].MustGetValueAsFloat(), dreamValues[2].MustGetValueAsFloat()); + ParticlesComponent.SpawnVelocityHigh = GetGeneratorValueAsVector3(dreamObjectGenerator.B); + ParticlesComponent.SpawnVelocityLow = GetGeneratorValueAsVector3(dreamObjectGenerator.A); ParticlesComponent.SpawnVelocityType = ParticlePropertyType.RandomUniform; //TODO all the other distributions } break; @@ -204,10 +200,8 @@ protected override void SetVar(string varName, DreamValue value) { ParticlesComponent.ScaleLow = new Vector2(dreamValues[0].MustGetValueAsFloat(), dreamValues[1].MustGetValueAsFloat()); ParticlesComponent.ScaleType = ParticlePropertyType.HighValue; } else if(value.TryGetValueAsDreamObject(out var dreamObjectGenerator)) { - List dreamValues = dreamObjectGenerator.B.MustGetValueAsDreamList().GetValues(); - ParticlesComponent.ScaleHigh = new Vector2(dreamValues[0].MustGetValueAsFloat(), dreamValues[1].MustGetValueAsFloat()); - dreamValues = dreamObjectGenerator.A.MustGetValueAsDreamList().GetValues(); - ParticlesComponent.ScaleLow = new Vector2(dreamValues[0].MustGetValueAsFloat(), dreamValues[1].MustGetValueAsFloat()); + ParticlesComponent.ScaleHigh = GetGeneratorValueAsVector2(dreamObjectGenerator.B); + ParticlesComponent.ScaleLow = GetGeneratorValueAsVector2(dreamObjectGenerator.A); ParticlesComponent.ScaleType = ParticlePropertyType.RandomUniform; //TODO all the other distributions } break; @@ -223,10 +217,8 @@ protected override void SetVar(string varName, DreamValue value) { ParticlesComponent.GrowthLow = new Vector2(dreamValues[0].MustGetValueAsFloat(), dreamValues[1].MustGetValueAsFloat()); ParticlesComponent.GrowthType = ParticlePropertyType.HighValue; } else if(value.TryGetValueAsDreamObject(out var dreamObjectGenerator)) { - List dreamValues = dreamObjectGenerator.B.MustGetValueAsDreamList().GetValues(); - ParticlesComponent.GrowthHigh = new Vector2(dreamValues[0].MustGetValueAsFloat(), dreamValues[1].MustGetValueAsFloat()); - dreamValues = dreamObjectGenerator.A.MustGetValueAsDreamList().GetValues(); - ParticlesComponent.GrowthLow = new Vector2(dreamValues[0].MustGetValueAsFloat(), dreamValues[1].MustGetValueAsFloat()); + ParticlesComponent.GrowthHigh = GetGeneratorValueAsVector2(dreamObjectGenerator.B); + ParticlesComponent.GrowthLow = GetGeneratorValueAsVector2(dreamObjectGenerator.A); ParticlesComponent.GrowthType = ParticlePropertyType.RandomUniform; //TODO all the other distributions } break; @@ -264,10 +256,8 @@ protected override void SetVar(string varName, DreamValue value) { ParticlesComponent.FrictionLow = new Vector3(dreamValues[0].MustGetValueAsFloat(), dreamValues[1].MustGetValueAsFloat(), dreamValues[2].MustGetValueAsFloat()); ParticlesComponent.FrictionType = ParticlePropertyType.HighValue; } else if(value.TryGetValueAsDreamObject(out var dreamObjectGenerator)) { - List dreamValues = dreamObjectGenerator.B.MustGetValueAsDreamList().GetValues(); - ParticlesComponent.FrictionHigh = new Vector3(dreamValues[0].MustGetValueAsFloat(), dreamValues[1].MustGetValueAsFloat(), dreamValues[2].MustGetValueAsFloat()); - dreamValues = dreamObjectGenerator.A.MustGetValueAsDreamList().GetValues(); - ParticlesComponent.FrictionLow = new Vector3(dreamValues[0].MustGetValueAsFloat(), dreamValues[1].MustGetValueAsFloat(), dreamValues[2].MustGetValueAsFloat()); + ParticlesComponent.FrictionHigh = GetGeneratorValueAsVector3(dreamObjectGenerator.B); + ParticlesComponent.FrictionLow = GetGeneratorValueAsVector3(dreamObjectGenerator.A); ParticlesComponent.FrictionType = ParticlePropertyType.RandomUniform; //TODO all the other distributions } break; @@ -283,10 +273,8 @@ protected override void SetVar(string varName, DreamValue value) { ParticlesComponent.DriftLow = new Vector3(dreamValues[0].MustGetValueAsFloat(), dreamValues[1].MustGetValueAsFloat(), dreamValues[2].MustGetValueAsFloat()); ParticlesComponent.DriftType = ParticlePropertyType.HighValue; } else if(value.TryGetValueAsDreamObject(out var dreamObjectGenerator)) { - List dreamValues = dreamObjectGenerator.B.MustGetValueAsDreamList().GetValues(); - ParticlesComponent.DriftHigh = new Vector3(dreamValues[0].MustGetValueAsFloat(), dreamValues[1].MustGetValueAsFloat(), dreamValues[2].MustGetValueAsFloat()); - dreamValues = dreamObjectGenerator.A.MustGetValueAsDreamList().GetValues(); - ParticlesComponent.DriftLow = new Vector3(dreamValues[0].MustGetValueAsFloat(), dreamValues[1].MustGetValueAsFloat(), dreamValues[2].MustGetValueAsFloat()); + ParticlesComponent.DriftHigh = GetGeneratorValueAsVector3(dreamObjectGenerator.B); + ParticlesComponent.DriftLow = GetGeneratorValueAsVector3(dreamObjectGenerator.A); ParticlesComponent.DriftType = ParticlePropertyType.RandomUniform; //TODO all the other distributions } break; @@ -294,4 +282,28 @@ protected override void SetVar(string varName, DreamValue value) { ParticlesSystem!.MarkDirty((Entity, ParticlesComponent)); base.SetVar(varName, value); //all calls should set the internal vars, so GetVar() can just be default also } + + private Vector2 GetGeneratorValueAsVector2(DreamValue value){ + if(value.TryGetValueAsFloat(out float floatValue)){ + return new Vector2(floatValue); + } //else vector + //else list + if(value.TryGetValueAsDreamList(out var valueList) && valueList.GetLength() >= 2){ + List dreamValues = valueList.GetValues(); + return new Vector2(dreamValues[0].MustGetValueAsFloat(), dreamValues[1].MustGetValueAsFloat()); + } + throw new InvalidCastException("Expected a float, list, or vector"); + } + + private Vector3 GetGeneratorValueAsVector3(DreamValue value){ + if(value.TryGetValueAsFloat(out float floatValue)){ + return new Vector3(floatValue); + } //else vector + //else list + if(value.TryGetValueAsDreamList(out var valueList) && valueList.GetLength() >= 3){ + List dreamValues = valueList.GetValues(); + return new Vector3(dreamValues[0].MustGetValueAsFloat(), dreamValues[1].MustGetValueAsFloat(), dreamValues[2].MustGetValueAsFloat()); + } + throw new InvalidCastException("Expected a float, list, or vector"); + } } From 58950e82e98cbef3352c2a9dfac889e013d24326 Mon Sep 17 00:00:00 2001 From: amylizzle Date: Thu, 20 Feb 2025 17:28:02 +0000 Subject: [PATCH 24/29] only one particle --- OpenDreamClient/Rendering/DreamViewOverlay.cs | 22 ++--- OpenDreamRuntime/Objects/Types/DreamList.cs | 87 ------------------- .../Objects/Types/DreamObjectAtom.cs | 21 ----- .../Objects/Types/DreamObjectMovable.cs | 21 +++++ .../Objects/Types/DreamObjectParticles.cs | 4 +- 5 files changed, 30 insertions(+), 125 deletions(-) diff --git a/OpenDreamClient/Rendering/DreamViewOverlay.cs b/OpenDreamClient/Rendering/DreamViewOverlay.cs index ecbb8c99d0..f4a2e14185 100644 --- a/OpenDreamClient/Rendering/DreamViewOverlay.cs +++ b/OpenDreamClient/Rendering/DreamViewOverlay.cs @@ -375,13 +375,10 @@ private void ProcessIconComponents(DreamIcon icon, Vector2 position, EntityUid u result.Add(maptext); } - //query entity for particles component - //if it has one, add it to the result list - foreach(var particleNetEntity in icon.Appearance.Particles) - if(_particlesManager.TryGetParticleSystem(_entityManager.GetEntity(particleNetEntity), out var particlesSystem)){ - current.Particles ??= new(); - current.Particles.Add(particlesSystem); - } + //query entity for particles component - check for parent to make sure this is the top level entity + if(parentIcon is null && _particlesManager.TryGetParticleSystem(uid, out var particlesSystem)){ + current.Particles = particlesSystem; + } //flatten KeepTogetherGroup. Done here so we get implicit recursive iteration down the tree. if (current.KeepTogetherGroup?.Count > 0) { @@ -460,13 +457,10 @@ public void DrawIcon(DrawingHandleWorld handle, Vector2i renderTargetSize, Rende var pixelPosition = (iconMetaData.Position + positionOffset) * EyeManager.PixelsPerMeter; if(iconMetaData.Particles is not null) { - foreach(var particleSystem in iconMetaData.Particles){ - var renderTarget = _renderTargetPool.Rent(particleSystem.RenderSize); - - handle.UseShader(GetBlendAndColorShader(iconMetaData, ignoreColor: true)); - particleSystem.Draw(handle, CalculateDrawingMatrix(iconMetaData.TransformToApply, pixelPosition, particleSystem.RenderSize, renderTargetSize)); - } + handle.UseShader(GetBlendAndColorShader(iconMetaData, ignoreColor: true)); + iconMetaData.Particles.Draw(handle, CalculateDrawingMatrix(iconMetaData.TransformToApply, pixelPosition, iconMetaData.Particles.RenderSize, renderTargetSize)); } + //if frame is null, this doesn't require a draw, so return NOP if (frame == null) return; @@ -815,7 +809,7 @@ internal sealed class RendererMetaData : IComparable { public Texture? TextureOverride; public string? Maptext; public Vector2i? MaptextSize; - public List? Particles; + public ParticleSystem? Particles; public bool IsPlaneMaster => (AppearanceFlags & AppearanceFlags.PlaneMaster) != 0; public bool HasRenderSource => !string.IsNullOrEmpty(RenderSource); diff --git a/OpenDreamRuntime/Objects/Types/DreamList.cs b/OpenDreamRuntime/Objects/Types/DreamList.cs index c4bb8500c6..7980a2c142 100644 --- a/OpenDreamRuntime/Objects/Types/DreamList.cs +++ b/OpenDreamRuntime/Objects/Types/DreamList.cs @@ -858,93 +858,6 @@ public override int GetLength() { } } -// atom.particles list -// Operates on an atom's appearance -public sealed class DreamParticlesList : DreamList { - [Dependency] private readonly AtomManager _atomManager = default!; - [Dependency] private readonly IEntityManager _entityManager = default!; - private readonly PvsOverrideSystem? _pvsOverrideSystem; - - private readonly List _particles = new(); - private readonly DreamObject _atom; - - public DreamParticlesList(DreamObjectDefinition listDef, PvsOverrideSystem? pvsOverrideSystem, DreamObject atom) : base(listDef, 0) { - IoCManager.InjectDependencies(this); - - _pvsOverrideSystem = pvsOverrideSystem; - _atom = atom; - } - - public override List GetValues() { - var values = new List(_particles.Count); - - foreach (var particlesObject in _particles) { - values.Add(new(particlesObject)); - } - - return values; - } - - public override void Cut(int start = 1, int end = 0) { - int count = _particles.Count + 1; - if (end == 0 || end > count) end = count; - - _particles.RemoveRange(start - 1, end - start); - _atomManager.UpdateAppearance(_atom, appearance => { - appearance.Particles.RemoveRange(start - 1, end - start); - }); - } - - public override DreamValue GetValue(DreamValue key) { - if (!key.TryGetValueAsInteger(out var particlesIndex) || particlesIndex < 1) - throw new Exception($"Invalid index into particles list: {key}"); - if (particlesIndex > _particles.Count) - throw new Exception($"Atom only has {_particles.Count} particles element(s), cannot index {particlesIndex}"); - - return new DreamValue(_particles[particlesIndex - 1]); - } - - public override void SetValue(DreamValue key, DreamValue value, bool allowGrowth = false) { - throw new Exception("Cannot write to an index of a particles list"); - } - - public override void AddValue(DreamValue value) { - EntityUid entity; - if (value.TryGetValueAsDreamObject(out var particles)) { - if (_particles.Contains(particles)) - return; // particles cannot contain duplicates - _particles.Add(particles); - entity = particles.Entity; - } else if (value == DreamValue.Null) { - return; // particles cannot contain nulls - } else { - throw new Exception($"Cannot add {value} to a particles list"); - } - - // TODO: Only override the entity's visibility if its parent atom is visible - if (entity != EntityUid.Invalid) - _pvsOverrideSystem?.AddGlobalOverride(entity); - - _atomManager.UpdateAppearance(_atom, appearance => { - // Add even an invalid UID to keep this and _visContents in sync - appearance.Particles.Add(_entityManager.GetNetEntity(entity)); - }); - } - - public override void RemoveValue(DreamValue value) { - if (!value.TryGetValueAsDreamObject(out var particles)) - return; - - _particles.Remove(particles); - _atomManager.UpdateAppearance(_atom, appearance => { - appearance.Particles.Remove(_entityManager.GetNetEntity(particles.Entity)); - }); - } - - public override int GetLength() { - return _particles.Count; - } -} // atom.filters list diff --git a/OpenDreamRuntime/Objects/Types/DreamObjectAtom.cs b/OpenDreamRuntime/Objects/Types/DreamObjectAtom.cs index 1d9f5b3abf..0a7577f065 100644 --- a/OpenDreamRuntime/Objects/Types/DreamObjectAtom.cs +++ b/OpenDreamRuntime/Objects/Types/DreamObjectAtom.cs @@ -7,15 +7,12 @@ public class DreamObjectAtom : DreamObject { public readonly DreamOverlaysList Overlays; public readonly DreamOverlaysList Underlays; public readonly DreamVisContentsList VisContents; - public readonly DreamParticlesList Particles; public readonly DreamFilterList Filters; public DreamList? VisLocs; // TODO: Implement - public DreamObjectAtom(DreamObjectDefinition objectDefinition) : base(objectDefinition) { Overlays = new(ObjectTree.List.ObjectDefinition, this, AppearanceSystem, false); Underlays = new(ObjectTree.List.ObjectDefinition, this, AppearanceSystem, true); VisContents = new(ObjectTree.List.ObjectDefinition, PvsOverrideSystem, this); - Particles = new(ObjectTree.List.ObjectDefinition, PvsOverrideSystem, this); Filters = new(ObjectTree.List.ObjectDefinition, this); AtomManager.AddAtom(this); @@ -75,10 +72,6 @@ protected override bool TryGetVar(string varName, out DreamValue value) { case "vis_contents": value = new(VisContents); return true; - case "particles": - value = new(Particles); - return true; - default: if (AtomManager.IsValidAppearanceVar(varName)) { var appearance = AtomManager.MustGetAppearance(this); @@ -151,20 +144,6 @@ protected override void SetVar(string varName, DreamValue value) { break; } - case "particles": { - Particles.Cut(); - - if (value.TryGetValueAsDreamList(out var valueList)) { - // TODO: This should postpone UpdateAppearance until after everything is added - foreach (DreamValue particlesValue in valueList.GetValues()) { - Particles.AddValue(particlesValue); - } - } else if (!value.IsNull) { - Particles.AddValue(value); - } - - break; - } case "filters": { Filters.Cut(); diff --git a/OpenDreamRuntime/Objects/Types/DreamObjectMovable.cs b/OpenDreamRuntime/Objects/Types/DreamObjectMovable.cs index 685aeced7f..015c27740e 100644 --- a/OpenDreamRuntime/Objects/Types/DreamObjectMovable.cs +++ b/OpenDreamRuntime/Objects/Types/DreamObjectMovable.cs @@ -20,6 +20,7 @@ public class DreamObjectMovable : DreamObjectAtom { private readonly TransformComponent _transformComponent; private readonly MovableContentsList _contents; private string? _screenLoc; + private DreamObjectParticles? _particles; private string? ScreenLoc { get => _screenLoc; @@ -94,6 +95,9 @@ protected override bool TryGetVar(string varName, out DreamValue value) { value = new DreamValue(locs); return true; + case "particles": + value = new(_particles); + return true; default: return base.TryGetVar(varName, out value); } @@ -138,6 +142,23 @@ protected override void SetVar(string varName, DreamValue value) { ScreenLoc = screenLoc; break; + case "particles": + if (value.TryGetValueAsDreamObject(out var particles)) { + if(particles == _particles){ + ParticlesSystem!.MarkDirty((Entity, _particles.ParticlesComponent)); + return; + } + if (_particles != null) + EntityManager.RemoveComponent(Entity, _particles.ParticlesComponent); + _particles = particles; + EntityManager.AddComponent(Entity, _particles.ParticlesComponent); + ParticlesSystem!.MarkDirty((Entity, _particles.ParticlesComponent)); + } else { + _particles = null; + if (_particles != null) + EntityManager.RemoveComponent(Entity, _particles.ParticlesComponent); + } + break; default: base.SetVar(varName, value); break; diff --git a/OpenDreamRuntime/Objects/Types/DreamObjectParticles.cs b/OpenDreamRuntime/Objects/Types/DreamObjectParticles.cs index 53aeb75135..10f499c109 100644 --- a/OpenDreamRuntime/Objects/Types/DreamObjectParticles.cs +++ b/OpenDreamRuntime/Objects/Types/DreamObjectParticles.cs @@ -17,8 +17,7 @@ public sealed class DreamObjectParticles : DreamObject { private List _iconStates = new(); public DreamObjectParticles(DreamObjectDefinition objectDefinition) : base(objectDefinition) { - Entity = EntityManager.SpawnEntity(null, new MapCoordinates(0, 0, MapId.Nullspace)); //spawning an entity in nullspace means it never actually gets sent to any clients until it's put in the particles list on an atom, when PVS override happens - ParticlesComponent = EntityManager.AddComponent(Entity); + ParticlesComponent = new DreamParticlesComponent(); //populate component with settings from type foreach(KeyValuePair kv in objectDefinition.Variables){ if(!(kv.Key == "parent_type" || kv.Key == "type" || kv.Key == "vars")) @@ -279,7 +278,6 @@ protected override void SetVar(string varName, DreamValue value) { } break; } - ParticlesSystem!.MarkDirty((Entity, ParticlesComponent)); base.SetVar(varName, value); //all calls should set the internal vars, so GetVar() can just be default also } From 22c4f3d22105891ad331d4727835da8987800254 Mon Sep 17 00:00:00 2001 From: amy Date: Thu, 20 Feb 2025 18:18:34 +0000 Subject: [PATCH 25/29] Fix autogenerated component state stuff --- .../Rendering/ClientDreamParticlesSystem.cs | 53 +----- OpenDreamShared/OpenDreamShared.csproj | 1 + .../Rendering/DreamParticlesComponent.cs | 151 ++++++------------ .../Rendering/SharedDreamParticleSystem.cs | 60 +------ 4 files changed, 51 insertions(+), 214 deletions(-) diff --git a/OpenDreamClient/Rendering/ClientDreamParticlesSystem.cs b/OpenDreamClient/Rendering/ClientDreamParticlesSystem.cs index 5f8f48d24e..cfc0665501 100644 --- a/OpenDreamClient/Rendering/ClientDreamParticlesSystem.cs +++ b/OpenDreamClient/Rendering/ClientDreamParticlesSystem.cs @@ -24,62 +24,13 @@ public sealed class ClientDreamParticlesSystem : SharedDreamParticlesSystem public override void Initialize() { base.Initialize(); - SubscribeLocalEvent(OnDreamParticlesComponentChange); + SubscribeLocalEvent(OnDreamParticlesComponentChange); SubscribeLocalEvent(HandleComponentRemove); RenderTargetPool = new(_clyde); } - private void OnDreamParticlesComponentChange(EntityUid uid, DreamParticlesComponent component, ref ComponentHandleState args) + private void OnDreamParticlesComponentChange(EntityUid uid, DreamParticlesComponent component, ref AfterAutoHandleStateEvent args) { - if (args.Current is not DreamParticlesComponentState state) - return; - component.Width = state.Width; - component.Width = state.Width; - component.Height = state.Height; - component.Count = state.Count; - component.Spawning = state.Spawning; - component.Bound1 = state.Bound1; - component.Bound2 = state.Bound2; - component.Gravity = state.Gravity; - component.Gradient = state.Gradient; - component.Transform = state.Transform; - component.TextureList = state.TextureList; - component.LifespanHigh = state.LifespanHigh; - component.LifespanLow = state.LifespanLow; - component.LifespanType = state.LifespanType; - component.FadeInHigh = state.FadeInHigh; - component.FadeInLow = state.FadeInLow; - component.FadeInType = state.FadeInType; - component.FadeOutHigh = state.FadeOutHigh; - component.FadeOutLow = state.FadeOutLow; - component.FadeOutType = state.FadeOutType; - component.SpawnPositionHigh = state.SpawnPositionHigh; - component.SpawnPositionLow = state.SpawnPositionLow; - component.SpawnPositionType = state.SpawnPositionType; - component.SpawnVelocityHigh = state.SpawnVelocityHigh; - component.SpawnVelocityLow = state.SpawnVelocityLow; - component.SpawnVelocityType = state.SpawnVelocityType; - component.AccelerationHigh = state.AccelerationHigh; - component.AccelerationLow = state.AccelerationLow; - component.AccelerationType = state.AccelerationType; - component.FrictionHigh = state.FrictionHigh; - component.FrictionLow = state.FrictionLow; - component.FrictionType = state.FrictionType; - component.ScaleHigh = state.ScaleHigh; - component.ScaleLow = state.ScaleLow; - component.ScaleType = state.ScaleType; - component.RotationHigh = state.RotationHigh; - component.RotationLow = state.RotationLow; - component.RotationType = state.RotationType; - component.GrowthHigh = state.GrowthHigh; - component.GrowthLow = state.GrowthLow; - component.GrowthType = state.GrowthType; - component.SpinHigh = state.SpinHigh; - component.SpinLow = state.SpinLow; - component.SpinType = state.SpinType; - component.DriftHigh = state.DriftHigh; - component.DriftLow = state.DriftLow; - component.DriftType = state.DriftType; if(_particlesManager.TryGetParticleSystem(uid, out var system)) system.UpdateSystem(GetParticleSystemArgs(component)); else diff --git a/OpenDreamShared/OpenDreamShared.csproj b/OpenDreamShared/OpenDreamShared.csproj index 2b9f66a9b7..c69bb75e29 100644 --- a/OpenDreamShared/OpenDreamShared.csproj +++ b/OpenDreamShared/OpenDreamShared.csproj @@ -18,4 +18,5 @@ + diff --git a/OpenDreamShared/Rendering/DreamParticlesComponent.cs b/OpenDreamShared/Rendering/DreamParticlesComponent.cs index 921532580b..766fe46b4d 100644 --- a/OpenDreamShared/Rendering/DreamParticlesComponent.cs +++ b/OpenDreamShared/Rendering/DreamParticlesComponent.cs @@ -12,116 +12,59 @@ namespace OpenDreamShared.Rendering; -[RegisterComponent, NetworkedComponent] +[RegisterComponent, NetworkedComponent, AutoGenerateComponentState(true)] public sealed partial class DreamParticlesComponent : Component { - [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 float LifespanHigh; - [ViewVariables(VVAccess.ReadWrite)] public float LifespanLow; - [ViewVariables(VVAccess.ReadWrite)] public ParticlePropertyType LifespanType; - [ViewVariables(VVAccess.ReadWrite)] public int FadeInHigh; - [ViewVariables(VVAccess.ReadWrite)] public int FadeInLow; - [ViewVariables(VVAccess.ReadWrite)] public ParticlePropertyType FadeInType; - [ViewVariables(VVAccess.ReadWrite)] public int FadeOutHigh; - [ViewVariables(VVAccess.ReadWrite)] public int FadeOutLow; - [ViewVariables(VVAccess.ReadWrite)] public ParticlePropertyType FadeOutType; + [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 float LifespanHigh; + [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public float LifespanLow; + [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public ParticlePropertyType LifespanType; + [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public int FadeInHigh; + [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public int FadeInLow; + [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public ParticlePropertyType FadeInType; + [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public int FadeOutHigh; + [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public int FadeOutLow; + [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public ParticlePropertyType FadeOutType; - [ViewVariables(VVAccess.ReadWrite)] public Vector3 SpawnPositionHigh; - [ViewVariables(VVAccess.ReadWrite)] public Vector3 SpawnPositionLow; - [ViewVariables(VVAccess.ReadWrite)] public ParticlePropertyType SpawnPositionType; + [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public Vector3 SpawnPositionHigh; + [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public Vector3 SpawnPositionLow; + [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public ParticlePropertyType SpawnPositionType; //Starting velocity of the particles - [ViewVariables(VVAccess.ReadWrite)] public Vector3 SpawnVelocityHigh; - [ViewVariables(VVAccess.ReadWrite)] public Vector3 SpawnVelocityLow; - [ViewVariables(VVAccess.ReadWrite)] public ParticlePropertyType SpawnVelocityType; + [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public Vector3 SpawnVelocityHigh; + [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public Vector3 SpawnVelocityLow; + [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public ParticlePropertyType SpawnVelocityType; //Acceleration applied to the particles per second - [ViewVariables(VVAccess.ReadWrite)] public Vector3 AccelerationHigh; - [ViewVariables(VVAccess.ReadWrite)] public Vector3 AccelerationLow; - [ViewVariables(VVAccess.ReadWrite)] public ParticlePropertyType AccelerationType; - [ViewVariables(VVAccess.ReadWrite)] public Vector3 FrictionHigh; - [ViewVariables(VVAccess.ReadWrite)] public Vector3 FrictionLow; - [ViewVariables(VVAccess.ReadWrite)] public ParticlePropertyType FrictionType; + [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public Vector3 AccelerationHigh; + [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public Vector3 AccelerationLow; + [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public ParticlePropertyType AccelerationType; + [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public Vector3 FrictionHigh; + [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public Vector3 FrictionLow; + [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public ParticlePropertyType FrictionType; //Scaling applied to the particles in (x,y) - [ViewVariables(VVAccess.ReadWrite)] public Vector2 ScaleHigh = Vector2.One; - [ViewVariables(VVAccess.ReadWrite)] public Vector2 ScaleLow = Vector2.One; - [ViewVariables(VVAccess.ReadWrite)] public ParticlePropertyType ScaleType; + [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public Vector2 ScaleHigh = Vector2.One; + [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public Vector2 ScaleLow = Vector2.One; + [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public ParticlePropertyType ScaleType; //Rotation applied to the particles in degrees - [ViewVariables(VVAccess.ReadWrite)] public float RotationHigh; - [ViewVariables(VVAccess.ReadWrite)] public float RotationLow; - [ViewVariables(VVAccess.ReadWrite)] public ParticlePropertyType RotationType; + [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public float RotationHigh; + [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public float RotationLow; + [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public ParticlePropertyType RotationType; //Increase in scale per second - [ViewVariables(VVAccess.ReadWrite)] public Vector2 GrowthHigh; - [ViewVariables(VVAccess.ReadWrite)] public Vector2 GrowthLow; - [ViewVariables(VVAccess.ReadWrite)] public ParticlePropertyType GrowthType; + [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public Vector2 GrowthHigh; + [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public Vector2 GrowthLow; + [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public ParticlePropertyType GrowthType; //Change in rotation per second - [ViewVariables(VVAccess.ReadWrite)] public float SpinHigh; - [ViewVariables(VVAccess.ReadWrite)] public float SpinLow; - [ViewVariables(VVAccess.ReadWrite)] public ParticlePropertyType SpinType; - [ViewVariables(VVAccess.ReadWrite)] public Vector3 DriftHigh; - [ViewVariables(VVAccess.ReadWrite)] public Vector3 DriftLow; - [ViewVariables(VVAccess.ReadWrite)] public ParticlePropertyType DriftType; - [Serializable, NetSerializable] - public sealed class DreamParticlesComponentState : ComponentState - { - public int Width; - public int Height; - public int Count; - public float Spawning; - public Vector3 Bound1; - public Vector3 Bound2; - public Vector3 Gravity; - public Color[] Gradient = []; - public Matrix3x2 Transform; - public ImmutableAppearance[] TextureList = []; - public float LifespanHigh; - public float LifespanLow; - public ParticlePropertyType LifespanType; - public int FadeInHigh; - public int FadeInLow; - public ParticlePropertyType FadeInType; - public int FadeOutHigh; - public int FadeOutLow; - public ParticlePropertyType FadeOutType; - - public Vector3 SpawnPositionHigh; - public Vector3 SpawnPositionLow; - public ParticlePropertyType SpawnPositionType; - //Starting velocity of the particles - public Vector3 SpawnVelocityHigh; - public Vector3 SpawnVelocityLow; - public ParticlePropertyType SpawnVelocityType; - //Acceleration applied to the particles per second - public Vector3 AccelerationHigh; - public Vector3 AccelerationLow; - public ParticlePropertyType AccelerationType; - public Vector3 FrictionHigh; - public Vector3 FrictionLow; - public ParticlePropertyType FrictionType; - //Scaling applied to the particles in (x,y) - public Vector2 ScaleHigh; - public Vector2 ScaleLow; - public ParticlePropertyType ScaleType; - //Rotation applied to the particles in degrees - public float RotationHigh; - public float RotationLow; - public ParticlePropertyType RotationType; - //Increase in scale per second - public Vector2 GrowthHigh; - public Vector2 GrowthLow; - public ParticlePropertyType GrowthType; - //Change in rotation per second - public float SpinHigh; - public float SpinLow; - public ParticlePropertyType SpinType; - public Vector3 DriftHigh; - public Vector3 DriftLow; - public ParticlePropertyType DriftType; - } + [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public float SpinHigh; + [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public float SpinLow; + [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public ParticlePropertyType SpinType; + [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public Vector3 DriftHigh; + [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public Vector3 DriftLow; + [ViewVariables(VVAccess.ReadWrite), AutoNetworkedField] public ParticlePropertyType DriftType; } diff --git a/OpenDreamShared/Rendering/SharedDreamParticleSystem.cs b/OpenDreamShared/Rendering/SharedDreamParticleSystem.cs index e79e0cc838..a729c301af 100644 --- a/OpenDreamShared/Rendering/SharedDreamParticleSystem.cs +++ b/OpenDreamShared/Rendering/SharedDreamParticleSystem.cs @@ -5,62 +5,4 @@ namespace OpenDreamShared.Rendering; -public abstract class SharedDreamParticlesSystem : EntitySystem { - public override void Initialize() - { - base.Initialize(); - SubscribeLocalEvent(GetCompState); - } - private void GetCompState(Entity ent, ref ComponentGetState args) - { - args.State = new DreamParticlesComponentState - { - Width = ent.Comp.Width, - Height = ent.Comp.Height, - Count = ent.Comp.Count, - Spawning = ent.Comp.Spawning, - Bound1 = ent.Comp.Bound1, - Bound2 = ent.Comp.Bound2, - Gravity = ent.Comp.Gravity, - Gradient = ent.Comp.Gradient, - Transform = ent.Comp.Transform, - TextureList = ent.Comp.TextureList, - LifespanHigh = ent.Comp.LifespanHigh, - LifespanLow = ent.Comp.LifespanLow, - LifespanType = ent.Comp.LifespanType, - FadeInHigh = ent.Comp.FadeInHigh, - FadeInLow = ent.Comp.FadeInLow, - FadeInType = ent.Comp.FadeInType, - FadeOutHigh = ent.Comp.FadeOutHigh, - FadeOutLow = ent.Comp.FadeOutLow, - FadeOutType = ent.Comp.FadeOutType, - SpawnPositionHigh = ent.Comp.SpawnPositionHigh, - SpawnPositionLow = ent.Comp.SpawnPositionLow, - SpawnPositionType = ent.Comp.SpawnPositionType, - SpawnVelocityHigh = ent.Comp.SpawnVelocityHigh, - SpawnVelocityLow = ent.Comp.SpawnVelocityLow, - SpawnVelocityType = ent.Comp.SpawnVelocityType, - AccelerationHigh = ent.Comp.AccelerationHigh, - AccelerationLow = ent.Comp.AccelerationLow, - AccelerationType = ent.Comp.AccelerationType, - FrictionHigh = ent.Comp.FrictionHigh, - FrictionLow = ent.Comp.FrictionLow, - FrictionType = ent.Comp.FrictionType, - ScaleHigh = ent.Comp.ScaleHigh, - ScaleLow = ent.Comp.ScaleLow, - ScaleType = ent.Comp.ScaleType, - RotationHigh = ent.Comp.RotationHigh, - RotationLow = ent.Comp.RotationLow, - RotationType = ent.Comp.RotationType, - GrowthHigh = ent.Comp.GrowthHigh, - GrowthLow = ent.Comp.GrowthLow, - GrowthType = ent.Comp.GrowthType, - SpinHigh = ent.Comp.SpinHigh, - SpinLow = ent.Comp.SpinLow, - SpinType = ent.Comp.SpinType, - DriftHigh = ent.Comp.DriftHigh, - DriftLow = ent.Comp.DriftLow, - DriftType = ent.Comp.DriftType - }; - } -} +public abstract class SharedDreamParticlesSystem : EntitySystem {} From de1ba9fa794f19be67eea60a9cd119cecd8e1767 Mon Sep 17 00:00:00 2001 From: amy Date: Thu, 20 Feb 2025 19:31:16 +0000 Subject: [PATCH 26/29] friction and drift --- OpenDreamClient/Rendering/ClientDreamParticlesSystem.cs | 2 +- OpenDreamShared/Rendering/DreamParticlesComponent.cs | 2 -- RobustToolbox | 2 +- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/OpenDreamClient/Rendering/ClientDreamParticlesSystem.cs b/OpenDreamClient/Rendering/ClientDreamParticlesSystem.cs index cfc0665501..788adccc2f 100644 --- a/OpenDreamClient/Rendering/ClientDreamParticlesSystem.cs +++ b/OpenDreamClient/Rendering/ClientDreamParticlesSystem.cs @@ -67,7 +67,7 @@ private ParticleSystemArgs GetParticleSystemArgs(DreamParticlesComponent compone }; else result.Color = (float lifetime) => Color.White; - result.Acceleration = (float _ ) => GetGeneratorVector3(component.AccelerationLow, component.AccelerationHigh, component.AccelerationType)(); + result.Acceleration = (float _ , Vector3 velocity) => GetGeneratorVector3(component.AccelerationLow, component.AccelerationHigh, component.AccelerationType)() + GetGeneratorVector3(component.DriftLow, component.DriftHigh, component.DriftType)() - velocity*GetGeneratorVector3(component.FrictionLow, component.FrictionHigh, component.FrictionType)(); result.SpawnPosition = GetGeneratorVector3(component.SpawnPositionLow, component.SpawnPositionHigh, component.SpawnPositionType); result.SpawnVelocity = GetGeneratorVector3(component.SpawnVelocityLow, component.SpawnVelocityHigh, component.SpawnVelocityType); result.Transform = (float lifetime) => { diff --git a/OpenDreamShared/Rendering/DreamParticlesComponent.cs b/OpenDreamShared/Rendering/DreamParticlesComponent.cs index 766fe46b4d..bed9ae1f10 100644 --- a/OpenDreamShared/Rendering/DreamParticlesComponent.cs +++ b/OpenDreamShared/Rendering/DreamParticlesComponent.cs @@ -5,8 +5,6 @@ using Robust.Shared.GameObjects; using Robust.Shared.GameStates; using Robust.Shared.Maths; -using Robust.Shared.Serialization; -using System; using Robust.Shared.ViewVariables; using Vector3 = Robust.Shared.Maths.Vector3; diff --git a/RobustToolbox b/RobustToolbox index 00ebe3158f..a3a90aa380 160000 --- a/RobustToolbox +++ b/RobustToolbox @@ -1 +1 @@ -Subproject commit 00ebe3158f3c539cf4ae6be84e6e175100664a45 +Subproject commit a3a90aa3804a72555255c39772d6dac0bdd3ea64 From e5c568b3aedfc7fee1cb3b1f33f47ce9163282bd Mon Sep 17 00:00:00 2001 From: amy Date: Thu, 20 Feb 2025 19:55:55 +0000 Subject: [PATCH 27/29] render above --- OpenDreamClient/Rendering/DreamViewOverlay.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/OpenDreamClient/Rendering/DreamViewOverlay.cs b/OpenDreamClient/Rendering/DreamViewOverlay.cs index f4a2e14185..1a3e82381d 100644 --- a/OpenDreamClient/Rendering/DreamViewOverlay.cs +++ b/OpenDreamClient/Rendering/DreamViewOverlay.cs @@ -456,11 +456,6 @@ public void DrawIcon(DrawingHandleWorld handle, Vector2i renderTargetSize, Rende var frame = iconMetaData.GetTexture(this, handle); var pixelPosition = (iconMetaData.Position + positionOffset) * EyeManager.PixelsPerMeter; - if(iconMetaData.Particles is not null) { - handle.UseShader(GetBlendAndColorShader(iconMetaData, ignoreColor: true)); - iconMetaData.Particles.Draw(handle, CalculateDrawingMatrix(iconMetaData.TransformToApply, pixelPosition, iconMetaData.Particles.RenderSize, renderTargetSize)); - } - //if frame is null, this doesn't require a draw, so return NOP if (frame == null) return; @@ -472,6 +467,11 @@ public void DrawIcon(DrawingHandleWorld handle, Vector2i renderTargetSize, Rende handle.SetTransform(CalculateDrawingMatrix(iconMetaData.TransformToApply, pixelPosition, frame.Size, renderTargetSize)); handle.DrawTextureRect(frame, Box2.FromDimensions(Vector2.Zero, frame.Size)); + + if(iconMetaData.Particles is not null) { + handle.UseShader(GetBlendAndColorShader(iconMetaData, ignoreColor: true)); + iconMetaData.Particles.Draw(handle, CalculateDrawingMatrix(iconMetaData.TransformToApply, pixelPosition, iconMetaData.Particles.RenderSize, renderTargetSize)); + } } /// From 45e7f16eb56437872ba7f851d38e843712a033d4 Mon Sep 17 00:00:00 2001 From: amy Date: Thu, 20 Feb 2025 21:22:22 +0000 Subject: [PATCH 28/29] cleanup --- .../Rendering/ClientDreamParticlesSystem.cs | 4 --- OpenDreamRuntime/Objects/Types/DreamList.cs | 2 -- .../Objects/Types/DreamObjectAtom.cs | 2 ++ .../Objects/Types/DreamObjectParticles.cs | 3 -- OpenDreamShared/Dream/ImmutableAppearance.cs | 35 +------------------ OpenDreamShared/Dream/MutableAppearance.cs | 14 -------- RobustToolbox | 2 +- 7 files changed, 4 insertions(+), 58 deletions(-) diff --git a/OpenDreamClient/Rendering/ClientDreamParticlesSystem.cs b/OpenDreamClient/Rendering/ClientDreamParticlesSystem.cs index 788adccc2f..457a8752fc 100644 --- a/OpenDreamClient/Rendering/ClientDreamParticlesSystem.cs +++ b/OpenDreamClient/Rendering/ClientDreamParticlesSystem.cs @@ -1,12 +1,8 @@ using JetBrains.Annotations; using OpenDreamShared.Rendering; -using Pidgin; using Robust.Client.Graphics; -using Robust.Client.ResourceManagement; -using Robust.Shared.GameStates; using Robust.Shared.Random; using Robust.Shared.Timing; -using static OpenDreamShared.Rendering.DreamParticlesComponent; using Vector3 = Robust.Shared.Maths.Vector3; namespace OpenDreamClient.Rendering; diff --git a/OpenDreamRuntime/Objects/Types/DreamList.cs b/OpenDreamRuntime/Objects/Types/DreamList.cs index 7980a2c142..0689448131 100644 --- a/OpenDreamRuntime/Objects/Types/DreamList.cs +++ b/OpenDreamRuntime/Objects/Types/DreamList.cs @@ -858,8 +858,6 @@ public override int GetLength() { } } - - // atom.filters list // Operates on an object's appearance public sealed class DreamFilterList : DreamList { diff --git a/OpenDreamRuntime/Objects/Types/DreamObjectAtom.cs b/OpenDreamRuntime/Objects/Types/DreamObjectAtom.cs index 0a7577f065..20cba60f33 100644 --- a/OpenDreamRuntime/Objects/Types/DreamObjectAtom.cs +++ b/OpenDreamRuntime/Objects/Types/DreamObjectAtom.cs @@ -9,6 +9,7 @@ public class DreamObjectAtom : DreamObject { public readonly DreamVisContentsList VisContents; public readonly DreamFilterList Filters; public DreamList? VisLocs; // TODO: Implement + public DreamObjectAtom(DreamObjectDefinition objectDefinition) : base(objectDefinition) { Overlays = new(ObjectTree.List.ObjectDefinition, this, AppearanceSystem, false); Underlays = new(ObjectTree.List.ObjectDefinition, this, AppearanceSystem, true); @@ -72,6 +73,7 @@ protected override bool TryGetVar(string varName, out DreamValue value) { case "vis_contents": value = new(VisContents); return true; + default: if (AtomManager.IsValidAppearanceVar(varName)) { var appearance = AtomManager.MustGetAppearance(this); diff --git a/OpenDreamRuntime/Objects/Types/DreamObjectParticles.cs b/OpenDreamRuntime/Objects/Types/DreamObjectParticles.cs index 10f499c109..cab470b207 100644 --- a/OpenDreamRuntime/Objects/Types/DreamObjectParticles.cs +++ b/OpenDreamRuntime/Objects/Types/DreamObjectParticles.cs @@ -1,9 +1,6 @@ -using OpenDreamRuntime.Procs; -using OpenDreamRuntime.Rendering; using OpenDreamRuntime.Resources; using OpenDreamShared.Dream; using OpenDreamShared.Rendering; -using Robust.Shared.Map; using Vector3 = Robust.Shared.Maths.Vector3; namespace OpenDreamRuntime.Objects.Types; diff --git a/OpenDreamShared/Dream/ImmutableAppearance.cs b/OpenDreamShared/Dream/ImmutableAppearance.cs index a8929c2505..97bffc6d38 100644 --- a/OpenDreamShared/Dream/ImmutableAppearance.cs +++ b/OpenDreamShared/Dream/ImmutableAppearance.cs @@ -51,7 +51,6 @@ public sealed class ImmutableAppearance : IEquatable { [ViewVariables] public readonly ImmutableAppearance[] Overlays; [ViewVariables] public readonly ImmutableAppearance[] Underlays; [ViewVariables] public readonly Robust.Shared.GameObjects.NetEntity[] VisContents; - [ViewVariables] public readonly Robust.Shared.GameObjects.NetEntity[] Particles; [ViewVariables] public readonly DreamFilter[] Filters; [ViewVariables] public readonly int[] Verbs; [ViewVariables] public readonly ColorMatrix ColorMatrix = ColorMatrix.Identity; @@ -107,7 +106,6 @@ public ImmutableAppearance(MutableAppearance appearance, SharedAppearanceSystem? Underlays = appearance.Underlays.ToArray(); VisContents = appearance.VisContents.ToArray(); - Particles = appearance.Particles.ToArray(); Filters = appearance.Filters.ToArray(); Verbs = appearance.Verbs.ToArray(); Override = appearance.Override; @@ -170,7 +168,6 @@ public bool Equals(ImmutableAppearance? immutableAppearance) { if (immutableAppearance.Overlays.Length != Overlays.Length) return false; if (immutableAppearance.Underlays.Length != Underlays.Length) return false; if (immutableAppearance.VisContents.Length != VisContents.Length) return false; - if (immutableAppearance.Particles.Length != Particles.Length) return false; if (immutableAppearance.Filters.Length != Filters.Length) return false; if (immutableAppearance.Verbs.Length != Verbs.Length) return false; if (immutableAppearance.Override != Override) return false; @@ -198,10 +195,6 @@ public bool Equals(ImmutableAppearance? immutableAppearance) { if (immutableAppearance.Verbs[i] != Verbs[i]) return false; } - for (int i = 0; i < Particles.Length; i++) { - if (immutableAppearance.Particles[i] != Particles[i]) return false; - } - for (int i = 0; i < 6; i++) { if (!immutableAppearance.Transform[i].Equals(Transform[i])) return false; } @@ -265,10 +258,6 @@ public override int GetHashCode() { hashCode.Add(visContent); } - foreach (int particlesObject in Particles) { - hashCode.Add(particlesObject); - } - foreach (DreamFilter filter in Filters) { hashCode.Add(filter); } @@ -290,8 +279,7 @@ public ImmutableAppearance(NetIncomingMessage buffer, IRobustSerializer serializ Underlays = []; VisContents = []; Filters = []; - Verbs = []; - Particles = []; + Verbs =[]; var property = (IconAppearanceProperty)buffer.ReadByte(); while (property != IconAppearanceProperty.End) { @@ -404,16 +392,6 @@ public ImmutableAppearance(NetIncomingMessage buffer, IRobustSerializer serializ break; } - case IconAppearanceProperty.Particles: { - var particlesCount = buffer.ReadVariableInt32(); - - Particles = new Robust.Shared.GameObjects.NetEntity[particlesCount]; - for (int particlesI = 0; particlesI < particlesCount; particlesI++) { - Particles[particlesI] = buffer.ReadNetEntity(); - } - - break; - } case IconAppearanceProperty.Filters: { var filtersCount = buffer.ReadInt32(); @@ -507,13 +485,11 @@ public MutableAppearance ToMutable() { result.VisContents.EnsureCapacity(VisContents.Length); result.Filters.EnsureCapacity(Filters.Length); result.Verbs.EnsureCapacity(Verbs.Length); - result.Particles.EnsureCapacity(Particles.Length); result.Overlays.AddRange(Overlays); result.Underlays.AddRange(Underlays); result.VisContents.AddRange(VisContents); result.Filters.AddRange(Filters); result.Verbs.AddRange(Verbs); - result.Particles.AddRange(Particles); Array.Copy(Transform, result.Transform, 6); return result; @@ -663,15 +639,6 @@ public void WriteToBuffer(NetOutgoingMessage buffer, IRobustSerializer serialize } } - if (Particles.Length != 0) { - buffer.Write((byte)IconAppearanceProperty.Particles); - - buffer.WriteVariableInt32(Particles.Length); - foreach (var item in Particles) { - buffer.Write(item); - } - } - if (Filters.Length != 0) { buffer.Write((byte)IconAppearanceProperty.Filters); diff --git a/OpenDreamShared/Dream/MutableAppearance.cs b/OpenDreamShared/Dream/MutableAppearance.cs index a50da73443..76c853cb03 100644 --- a/OpenDreamShared/Dream/MutableAppearance.cs +++ b/OpenDreamShared/Dream/MutableAppearance.cs @@ -49,7 +49,6 @@ public sealed class MutableAppearance : IEquatable, IDisposab [ViewVariables] public List Overlays; [ViewVariables] public List Underlays; [ViewVariables] public List VisContents; - [ViewVariables] public List Particles; [ViewVariables] public List Filters; [ViewVariables] public List Verbs; [ViewVariables] public Vector2i MaptextSize = new(32,32); @@ -85,7 +84,6 @@ private MutableAppearance() { VisContents = []; Filters = []; Verbs = []; - Particles = []; } public void Dispose() { @@ -139,13 +137,11 @@ public void CopyFrom(MutableAppearance appearance) { VisContents.Clear(); Filters.Clear(); Verbs.Clear(); - Particles.Clear(); Overlays.AddRange(appearance.Overlays); Underlays.AddRange(appearance.Underlays); VisContents.AddRange(appearance.VisContents); Filters.AddRange(appearance.Filters); Verbs.AddRange(appearance.Verbs); - Particles.AddRange(appearance.Particles); Array.Copy(appearance.Transform, Transform, 6); } @@ -180,7 +176,6 @@ public bool Equals(MutableAppearance? appearance) { if (appearance.VisContents.Count != VisContents.Count) return false; if (appearance.Filters.Count != Filters.Count) return false; if (appearance.Verbs.Count != Verbs.Count) return false; - if (appearance.Particles.Count != Particles.Count) return false; if (appearance.Override != Override) return false; if (appearance.Maptext != Maptext) return false; if (appearance.MaptextSize != MaptextSize) return false; @@ -206,10 +201,6 @@ public bool Equals(MutableAppearance? appearance) { if (appearance.Verbs[i] != Verbs[i]) return false; } - for (int i = 0; i < Particles.Count; i++) { - if (appearance.Particles[i] != Particles[i]) return false; - } - for (int i = 0; i < 6; i++) { if (!appearance.Transform[i].Equals(Transform[i])) return false; } @@ -291,10 +282,6 @@ public override int GetHashCode() { hashCode.Add(visContent); } - foreach (int particlesObject in Particles) { - hashCode.Add(particlesObject); - } - foreach (DreamFilter filter in Filters) { hashCode.Add(filter); } @@ -417,7 +404,6 @@ public enum IconAppearanceProperty : byte { Overlays, Underlays, VisContents, - Particles, Filters, Verbs, Transform, diff --git a/RobustToolbox b/RobustToolbox index a3a90aa380..0c0d878777 160000 --- a/RobustToolbox +++ b/RobustToolbox @@ -1 +1 @@ -Subproject commit a3a90aa3804a72555255c39772d6dac0bdd3ea64 +Subproject commit 0c0d8787776ae1a0c5c3c00cf04fceaa53d910f3 From 007360d6a9e13d3181467e9dfeb46b0abce366f0 Mon Sep 17 00:00:00 2001 From: amy Date: Sun, 23 Feb 2025 18:51:32 +0000 Subject: [PATCH 29/29] viewport changes --- OpenDreamClient/Interface/Controls/UI/ScalingViewport.cs | 4 ++-- RobustToolbox | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/OpenDreamClient/Interface/Controls/UI/ScalingViewport.cs b/OpenDreamClient/Interface/Controls/UI/ScalingViewport.cs index a5f7395e5a..7ba057e5a2 100644 --- a/OpenDreamClient/Interface/Controls/UI/ScalingViewport.cs +++ b/OpenDreamClient/Interface/Controls/UI/ScalingViewport.cs @@ -111,7 +111,7 @@ protected override void KeyBindUp(GUIBoundKeyEventArgs args) { _inputManager.ViewportKeyEvent(this, args); } - protected override void Draw(IRenderHandle handle) { + protected override void Draw(DrawingHandleScreen handle) { EnsureViewportCreated(); DebugTools.AssertNotNull(_viewport); @@ -133,7 +133,7 @@ protected override void Draw(IRenderHandle handle) { var drawBox = GetDrawBox(); var drawBoxGlobal = drawBox.Translated(GlobalPixelPosition); _viewport.RenderScreenOverlaysBelow(handle, this, drawBoxGlobal); - handle.DrawingHandleScreen.DrawTextureRect(_viewport.RenderTarget.Texture, drawBox); + handle.DrawTextureRect(_viewport.RenderTarget.Texture, drawBox); _viewport.RenderScreenOverlaysAbove(handle, this, drawBoxGlobal); } diff --git a/RobustToolbox b/RobustToolbox index 7104a4f459..0c0d878777 160000 --- a/RobustToolbox +++ b/RobustToolbox @@ -1 +1 @@ -Subproject commit 7104a4f4594149f5c09aeefc34275b1cbac4e297 +Subproject commit 0c0d8787776ae1a0c5c3c00cf04fceaa53d910f3