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/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/DMCompiler/DMStandard/_Standard.dm b/DMCompiler/DMStandard/_Standard.dm index 4795053892..0ecd420fc7 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/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/OpenDreamClient/Rendering/ClientDreamParticlesSystem.cs b/OpenDreamClient/Rendering/ClientDreamParticlesSystem.cs new file mode 100644 index 0000000000..457a8752fc --- /dev/null +++ b/OpenDreamClient/Rendering/ClientDreamParticlesSystem.cs @@ -0,0 +1,114 @@ +using JetBrains.Annotations; +using OpenDreamShared.Rendering; +using Robust.Client.Graphics; +using Robust.Shared.Random; +using Robust.Shared.Timing; +using Vector3 = Robust.Shared.Maths.Vector3; + +namespace OpenDreamClient.Rendering; + +[UsedImplicitly] +public sealed class ClientDreamParticlesSystem : SharedDreamParticlesSystem +{ + [Dependency] private readonly ParticlesManager _particlesManager = 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(HandleComponentRemove); + RenderTargetPool = new(_clyde); + } + + private void OnDreamParticlesComponentChange(EntityUid uid, DreamParticlesComponent component, ref AfterAutoHandleStateEvent args) + { + if(_particlesManager.TryGetParticleSystem(uid, out var system)) + system.UpdateSystem(GetParticleSystemArgs(component)); + else + _particlesManager.CreateParticleSystem(uid, GetParticleSystemArgs(component)); + } + private void HandleComponentRemove(EntityUid uid, DreamParticlesComponent component, ref ComponentRemove args) + { + _particlesManager.DestroyParticleSystem(uid); + } + + private ParticleSystemArgs GetParticleSystemArgs(DreamParticlesComponent component){ + Func textureFunc; + if(component.TextureList is null || component.TextureList.Length == 0) + textureFunc = () => Texture.White; + 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) ?? 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(); + 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.Gradient.Length > 0) + result.Color = (float lifetime) => { + 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; + 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) => { + 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); + }; + result.BaseTransform = Matrix3x2.Identity; + + 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/DreamViewOverlay.cs b/OpenDreamClient/Rendering/DreamViewOverlay.cs index ead729a933..1a3e82381d 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!; @@ -373,7 +375,10 @@ 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 - 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) { @@ -462,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)); + } } /// @@ -799,6 +809,7 @@ internal sealed class RendererMetaData : IComparable { public Texture? TextureOverride; public string? Maptext; public Vector2i? MaptextSize; + public ParticleSystem? Particles; public bool IsPlaneMaster => (AppearanceFlags & AppearanceFlags.PlaneMaster) != 0; public bool HasRenderSource => !string.IsNullOrEmpty(RenderSource); @@ -830,6 +841,7 @@ public void Reset() { TextureOverride = null; Maptext = null; MaptextSize = null; + Particles = null; } public Texture? GetTexture(DreamViewOverlay viewOverlay, DrawingHandleWorld handle) { 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 2fdd1a785a..e3967e976a 100644 --- a/OpenDreamRuntime/Objects/DreamObjectTree.cs +++ b/OpenDreamRuntime/Objects/DreamObjectTree.cs @@ -49,6 +49,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; @@ -70,6 +72,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(); @@ -83,6 +86,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(); @@ -172,6 +176,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)) @@ -307,6 +315,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); @@ -331,7 +341,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/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/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 new file mode 100644 index 0000000000..cab470b207 --- /dev/null +++ b/OpenDreamRuntime/Objects/Types/DreamObjectParticles.cs @@ -0,0 +1,304 @@ +using OpenDreamRuntime.Resources; +using OpenDreamShared.Dream; +using OpenDreamShared.Rendering; +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) { + 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")) + SetVar(kv.Key, kv.Value); + } + } + + 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(DreamResourceManager.TryLoadIcon(iconValue, out var iconRsc)) { + MutableAppearance iconAppearance = MutableAppearance.Get(); + iconAppearance.Icon = iconRsc.Id; + _icons.Add(iconAppearance); + } + } + } 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){ + 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 + 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 + 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 + 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": //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)) { + ParticlesComponent.SpawnPositionHigh = GetGeneratorValueAsVector3(dreamObjectGenerator.B); + ParticlesComponent.SpawnPositionLow = GetGeneratorValueAsVector3(dreamObjectGenerator.A); + ParticlesComponent.SpawnPositionType = ParticlePropertyType.RandomUniform; //TODO all the other distributions + } + break; + 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)) { + ParticlesComponent.SpawnVelocityHigh = GetGeneratorValueAsVector3(dreamObjectGenerator.B); + ParticlesComponent.SpawnVelocityLow = GetGeneratorValueAsVector3(dreamObjectGenerator.A); + ParticlesComponent.SpawnVelocityType = ParticlePropertyType.RandomUniform; //TODO all the other distributions + } + break; + case "scale": //num, list, vector, or generator + 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)) { + ParticlesComponent.ScaleHigh = GetGeneratorValueAsVector2(dreamObjectGenerator.B); + ParticlesComponent.ScaleLow = GetGeneratorValueAsVector2(dreamObjectGenerator.A); + ParticlesComponent.ScaleType = ParticlePropertyType.RandomUniform; //TODO all the other distributions + } + break; + case "grow": //num, list, vector, or generator + 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)) { + ParticlesComponent.GrowthHigh = GetGeneratorValueAsVector2(dreamObjectGenerator.B); + ParticlesComponent.GrowthLow = GetGeneratorValueAsVector2(dreamObjectGenerator.A); + ParticlesComponent.GrowthType = ParticlePropertyType.RandomUniform; //TODO all the other distributions + } + break; + case "rotation": //num or generator + 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 + 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, vector, or generator + 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)) { + ParticlesComponent.FrictionHigh = GetGeneratorValueAsVector3(dreamObjectGenerator.B); + ParticlesComponent.FrictionLow = GetGeneratorValueAsVector3(dreamObjectGenerator.A); + ParticlesComponent.FrictionType = ParticlePropertyType.RandomUniform; //TODO all the other distributions + } + break; + case "drift": //num, vector, or generator + 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)) { + ParticlesComponent.DriftHigh = GetGeneratorValueAsVector3(dreamObjectGenerator.B); + ParticlesComponent.DriftLow = GetGeneratorValueAsVector3(dreamObjectGenerator.A); + ParticlesComponent.DriftType = ParticlePropertyType.RandomUniform; //TODO all the other distributions + } + break; + } + 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"); + } +} diff --git a/OpenDreamRuntime/Procs/Native/DreamProcNative.cs b/OpenDreamRuntime/Procs/Native/DreamProcNative.cs index 7c28ec7f0e..542b9323e0 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 5aa3761ff9..36c9c0a8bc 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, 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"); + 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)] diff --git a/OpenDreamRuntime/Rendering/ServerDreamParticleSystem.cs b/OpenDreamRuntime/Rendering/ServerDreamParticleSystem.cs new file mode 100644 index 0000000000..8a2edd5274 --- /dev/null +++ b/OpenDreamRuntime/Rendering/ServerDreamParticleSystem.cs @@ -0,0 +1,9 @@ +using OpenDreamShared.Rendering; + +namespace OpenDreamRuntime.Rendering; + +public sealed class ServerDreamParticlesSystem : SharedDreamParticlesSystem { + public void MarkDirty(Entity ent){ + Dirty(ent, ent.Comp); + } +} diff --git a/OpenDreamShared/Rendering/DreamParticlesComponent.cs b/OpenDreamShared/Rendering/DreamParticlesComponent.cs new file mode 100644 index 0000000000..bed9ae1f10 --- /dev/null +++ b/OpenDreamShared/Rendering/DreamParticlesComponent.cs @@ -0,0 +1,68 @@ + +using System.Numerics; +using OpenDreamShared.Dream; +using Robust.Shared.Analyzers; +using Robust.Shared.GameObjects; +using Robust.Shared.GameStates; +using Robust.Shared.Maths; +using Robust.Shared.ViewVariables; +using Vector3 = Robust.Shared.Maths.Vector3; + +namespace OpenDreamShared.Rendering; + +[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; + [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), 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), 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), 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), 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), 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), 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), 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 new file mode 100644 index 0000000000..a729c301af --- /dev/null +++ b/OpenDreamShared/Rendering/SharedDreamParticleSystem.cs @@ -0,0 +1,8 @@ + +using Robust.Shared.GameObjects; +using Robust.Shared.GameStates; +using static OpenDreamShared.Rendering.DreamParticlesComponent; + +namespace OpenDreamShared.Rendering; + +public abstract class SharedDreamParticlesSystem : EntitySystem {} diff --git a/RobustToolbox b/RobustToolbox index fea592e1d5..0c0d878777 160000 --- a/RobustToolbox +++ b/RobustToolbox @@ -1 +1 @@ -Subproject commit fea592e1d53b043bda73a66248576caa6e3f65dc +Subproject commit 0c0d8787776ae1a0c5c3c00cf04fceaa53d910f3 diff --git a/TestGame/code.dm b/TestGame/code.dm index d1b94607d4..2635d26cd1 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 + New() ..() @@ -71,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"))]" diff --git a/TestGame/icons/bee.dmi b/TestGame/icons/bee.dmi new file mode 100644 index 0000000000..e7e5289162 Binary files /dev/null and b/TestGame/icons/bee.dmi differ