From af2b32e7baeb08fcd896e085111ac16c2481042b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matthias=20Mail=C3=A4nder?= Date: Thu, 23 Mar 2023 10:50:14 +0100 Subject: [PATCH] Add particle smoke effects. --- OpenRA.Mods.Common/Effects/FloatingSprite.cs | 94 +++++++++++++++ .../Traits/Render/FloatingSpriteEmitter.cs | 111 ++++++++++++++++++ OpenRA.Mods.Common/Util.cs | 25 ++++ mods/d2k/rules/defaults.yaml | 13 ++ mods/d2k/rules/palettes.yaml | 4 + mods/d2k/rules/vehicles.yaml | 1 - mods/d2k/sequences/misc.yaml | 10 ++ 7 files changed, 257 insertions(+), 1 deletion(-) create mode 100644 OpenRA.Mods.Common/Effects/FloatingSprite.cs create mode 100644 OpenRA.Mods.Common/Traits/Render/FloatingSpriteEmitter.cs diff --git a/OpenRA.Mods.Common/Effects/FloatingSprite.cs b/OpenRA.Mods.Common/Effects/FloatingSprite.cs new file mode 100644 index 0000000000..e9593404f2 --- /dev/null +++ b/OpenRA.Mods.Common/Effects/FloatingSprite.cs @@ -0,0 +1,94 @@ +#region Copyright & License Information +/* + * Copyright (c) The OpenRA Developers and Contributors + * This file is part of OpenRA, which is free software. It is made + * available to you under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. For more + * information, see COPYING. + */ +#endregion + +using System.Collections.Generic; +using OpenRA.Effects; +using OpenRA.Graphics; + +namespace OpenRA.Mods.Common.Effects +{ + class FloatingSprite : IEffect, ISpatiallyPartitionable + { + readonly WDist[] speed; + readonly WDist[] gravity; + readonly Animation anim; + + readonly bool visibleThroughFog; + readonly int turnRate; + readonly int randomRate; + readonly string palette; + + WPos pos; + WVec offset; + int lifetime; + int ticks; + WAngle facing; + + public FloatingSprite(Actor emitter, string image, string[] sequences, string palette, bool isPlayerPalette, + int[] lifetime, WDist[] speed, WDist[] gravity, int turnRate, int randomRate, WPos pos, WAngle facing, + bool visibleThroughFog = false) + { + var world = emitter.World; + this.pos = pos; + this.turnRate = turnRate; + this.randomRate = randomRate; + this.speed = speed; + this.gravity = gravity; + this.visibleThroughFog = visibleThroughFog; + this.facing = facing; + + anim = new Animation(world, image, () => facing); + anim.PlayRepeating(sequences.Random(world.LocalRandom)); + world.ScreenMap.Add(this, pos, anim.Image); + this.lifetime = Util.RandomInRange(world.LocalRandom, lifetime); + + this.palette = isPlayerPalette ? palette + emitter.Owner.InternalName : palette; + } + + public void Tick(World world) + { + if (--lifetime < 0) + { + world.AddFrameEndTask(w => { w.Remove(this); w.ScreenMap.Remove(this); }); + return; + } + + if (--ticks < 0) + { + var forward = Util.RandomDistance(world.LocalRandom, speed).Length; + var height = Util.RandomDistance(world.LocalRandom, gravity).Length; + + offset = new WVec(forward, 0, height); + + if (turnRate > 0) + facing = WAngle.FromFacing(Util.NormalizeFacing(facing.Facing + world.LocalRandom.Next(-turnRate, turnRate))); + + offset = offset.Rotate(WRot.FromYaw(facing)); + + ticks = randomRate; + } + + anim.Tick(); + + pos += offset; + + world.ScreenMap.Update(this, pos, anim.Image); + } + + public IEnumerable Render(WorldRenderer wr) + { + if (!visibleThroughFog && wr.World.FogObscures(pos)) + return SpriteRenderable.None; + + return anim.Render(pos, wr.Palette(palette)); + } + } +} diff --git a/OpenRA.Mods.Common/Traits/Render/FloatingSpriteEmitter.cs b/OpenRA.Mods.Common/Traits/Render/FloatingSpriteEmitter.cs new file mode 100644 index 0000000000..6417977951 --- /dev/null +++ b/OpenRA.Mods.Common/Traits/Render/FloatingSpriteEmitter.cs @@ -0,0 +1,111 @@ +#region Copyright & License Information +/* + * Copyright (c) The OpenRA Developers and Contributors + * This file is part of OpenRA, which is free software. It is made + * available to you under the terms of the GNU General Public License + * as published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. For more + * information, see COPYING. + */ +#endregion + +using OpenRA.Mods.Common.Effects; +using OpenRA.Traits; + +namespace OpenRA.Mods.Common.Traits +{ + [Desc("Spawns moving sprite effects.")] + public class FloatingSpriteEmitterInfo : ConditionalTraitInfo, IRulesetLoaded + { + [FieldLoader.Require] + [Desc("The time between individual particle creation. Two values mean actual lifetime will vary between them.")] + public readonly int[] Lifetime; + + [FieldLoader.Require] + [Desc("The time in ticks until stop spawning. -1 means forever.")] + public readonly int Duration = -1; + + [Desc("Randomised offset for the particle emitter.")] + public readonly WVec[] Offset = { WVec.Zero }; + + [Desc("Randomized particle forward movement.")] + public readonly WDist[] Speed = { WDist.Zero }; + + [Desc("Randomized particle gravity.")] + public readonly WDist[] Gravity = { WDist.Zero }; + + [Desc("Randomize particle facing.")] + public readonly bool RandomFacing = true; + + [Desc("Randomize particle turnrate.")] + public readonly int TurnRate = 0; + + [Desc("The rate at which particle movement properties are reset.")] + public readonly int RandomRate = 4; + + [Desc("How many particles should spawn. Two values for a random range.")] + public readonly int[] SpawnFrequency = { 1 }; + + [Desc("Which image to use.")] + public readonly string Image = "smoke"; + + [Desc("Which sequence to use.")] + [SequenceReference(nameof(Image))] + public readonly string[] Sequences = { "particles" }; + + [Desc("Which palette to use.")] + [PaletteReference(nameof(IsPlayerPalette))] + public readonly string Palette = "effect"; + + public readonly bool IsPlayerPalette = false; + + public override object Create(ActorInitializer init) { return new FloatingSpriteEmitter(init.Self, this); } + } + + public class FloatingSpriteEmitter : ConditionalTrait, ITick + { + readonly WVec offset; + + IFacing facing; + int ticks; + int duration; + + public FloatingSpriteEmitter(Actor self, FloatingSpriteEmitterInfo info) + : base(info) + { + offset = Util.RandomVector(self.World.SharedRandom, Info.Offset); + } + + protected override void Created(Actor self) + { + facing = self.TraitOrDefault(); + + base.Created(self); + } + + protected override void TraitEnabled(Actor self) + { + base.TraitEnabled(self); + + duration = Info.Duration; + } + + void ITick.Tick(Actor self) + { + if (IsTraitDisabled) + return; + + if (Info.Duration > 0 && --duration < 0) + return; + + if (--ticks < 0) + { + ticks = Util.RandomInRange(self.World.LocalRandom, Info.SpawnFrequency); + + var spawnFacing = (!Info.RandomFacing && facing != null) ? facing.Facing : WAngle.FromFacing(self.World.LocalRandom.Next(256)); + self.World.AddFrameEndTask(w => w.Add(new FloatingSprite(self, Info.Image, Info.Sequences, Info.Palette, Info.IsPlayerPalette, + Info.Lifetime, Info.Speed, Info.Gravity, Info.TurnRate, Info.RandomRate, self.CenterPosition + offset, spawnFacing))); + } + } + } +} diff --git a/OpenRA.Mods.Common/Util.cs b/OpenRA.Mods.Common/Util.cs index 8774e23e2b..025bb1554d 100644 --- a/OpenRA.Mods.Common/Util.cs +++ b/OpenRA.Mods.Common/Util.cs @@ -233,6 +233,31 @@ namespace OpenRA.Mods.Common : t.Name; } + public static WDist RandomDistance(MersenneTwister random, WDist[] distance) + { + if (distance.Length == 0) + return WDist.Zero; + + if (distance.Length == 1) + return distance[0]; + + return new WDist(random.Next(distance[0].Length, distance[1].Length)); + } + + public static WVec RandomVector(MersenneTwister random, WVec[] vector) + { + if (vector.Length == 0) + return WVec.Zero; + + if (vector.Length == 1) + return vector[0]; + + var x = random.Next(vector[0].X, vector[1].X); + var y = random.Next(vector[0].Y, vector[1].Y); + var z = random.Next(vector[0].Z, vector[1].Z); + return new WVec(x, y, z); + } + public static string FriendlyTypeName(Type t) { if (t.IsEnum) diff --git a/mods/d2k/rules/defaults.yaml b/mods/d2k/rules/defaults.yaml index a04a48b6e4..9415fed25c 100644 --- a/mods/d2k/rules/defaults.yaml +++ b/mods/d2k/rules/defaults.yaml @@ -256,6 +256,19 @@ SpeedMultiplier@HEAVYDAMAGE: RequiresCondition: heavy-damage Modifier: 75 + FloatingSpriteEmitter@SMOKE: + RequiresCondition: heavy-damage + Palette: smoke3 + Image: smoke3 + Lifetime: 15, 20 + Speed: 3 + Gravity: 50 + SpawnFrequency: 5, 10 + RandomFacing: true + RandomRate: 4 + Offset: 0, 0, 200 + TurnRate: 3 + Duration: 500 ^Tank: Inherits: ^Vehicle diff --git a/mods/d2k/rules/palettes.yaml b/mods/d2k/rules/palettes.yaml index 3cafaf843e..dc7e858c9e 100644 --- a/mods/d2k/rules/palettes.yaml +++ b/mods/d2k/rules/palettes.yaml @@ -49,6 +49,10 @@ Name: shroud Filename: DATA.R8 Frame: 38 + PaletteFromEmbeddedSpritePalette@smoke3: + Name: smoke3 + Filename: DATA.R8 + Frame: 3747 D2kFogPalette@fog: Name: fog BasePalette: shroud diff --git a/mods/d2k/rules/vehicles.yaml b/mods/d2k/rules/vehicles.yaml index c9ca5ed45a..7e1c824969 100644 --- a/mods/d2k/rules/vehicles.yaml +++ b/mods/d2k/rules/vehicles.yaml @@ -111,7 +111,6 @@ harvester: Margin: 1, 4 RequiresSelection: true PipCount: 7 - -GrantConditionOnDamageState@HEAVY: -SpeedMultiplier@HEAVYDAMAGE: trike: diff --git a/mods/d2k/sequences/misc.yaml b/mods/d2k/sequences/misc.yaml index 816c9ce734..63ce0b2061 100644 --- a/mods/d2k/sequences/misc.yaml +++ b/mods/d2k/sequences/misc.yaml @@ -382,6 +382,16 @@ smoke_m: Length: 3 BlendMode: Additive +smoke3: + particles: + Filename: DATA.R8 + ZOffset: 511 + Start: 3747 + Length: 7 + Tick: 120 + BlendMode: Subtractive + HasEmbeddedPalette: True + bombs: idle: Filename: DATA.R8