diff --git a/OpenRA.Game/Traits/Util.cs b/OpenRA.Game/Traits/Util.cs index 3ac2693589..3757c69dda 100644 --- a/OpenRA.Game/Traits/Util.cs +++ b/OpenRA.Game/Traits/Util.cs @@ -149,5 +149,20 @@ namespace OpenRA.Traits return (int)a; } + + public static IEnumerable RandomWalk(CPos p, MersenneTwister r) + { + for (;;) + { + var dx = r.Next(-1, 2); + var dy = r.Next(-1, 2); + + if (dx == 0 && dy == 0) + continue; + + p += new CVec(dx, dy); + yield return p; + } + } } } diff --git a/OpenRA.Mods.Common/Traits/SeedsResource.cs b/OpenRA.Mods.Common/Traits/SeedsResource.cs index 0f15622957..3eb3bbd703 100644 --- a/OpenRA.Mods.Common/Traits/SeedsResource.cs +++ b/OpenRA.Mods.Common/Traits/SeedsResource.cs @@ -63,7 +63,7 @@ namespace OpenRA.Mods.Common.Traits public void Seed(Actor self) { - var cell = RandomWalk(self.Location, self.World.SharedRandom) + var cell = Util.RandomWalk(self.Location, self.World.SharedRandom) .Take(info.MaxRange) .SkipWhile(p => resLayer.GetResource(p) == resourceType && resLayer.IsFull(p)) .Cast().FirstOrDefault(); @@ -71,20 +71,5 @@ namespace OpenRA.Mods.Common.Traits if (cell != null && resLayer.CanSpawnResourceAt(resourceType, cell.Value)) resLayer.AddResource(resourceType, cell.Value, 1); } - - static IEnumerable RandomWalk(CPos p, MersenneTwister r) - { - for (;;) - { - var dx = r.Next(-1, 2); - var dy = r.Next(-1, 2); - - if (dx == 0 && dy == 0) - continue; - - p += new CVec(dx, dy); - yield return p; - } - } } } diff --git a/OpenRA.Mods.Common/Traits/ThrowsShrapnel.cs b/OpenRA.Mods.Common/Traits/ThrowsShrapnel.cs index bd01530ed0..f0ea965dd9 100644 --- a/OpenRA.Mods.Common/Traits/ThrowsShrapnel.cs +++ b/OpenRA.Mods.Common/Traits/ThrowsShrapnel.cs @@ -18,9 +18,14 @@ namespace OpenRA.Mods.Common.Traits public class ThrowsShrapnelInfo : ITraitInfo, IRulesetLoaded { [WeaponReference, FieldLoader.Require] - public string[] Weapons = { }; - public int[] Pieces = { 3, 10 }; - public WDist[] Range = { WDist.FromCells(2), WDist.FromCells(5) }; + [Desc("The weapons used for shrapnel.")] + public readonly string[] Weapons = { }; + + [Desc("The amount of pieces of shrapnel to expel. Two values indicate a range.")] + public readonly int[] Pieces = { 3, 10 }; + + [Desc("The minimum and maximum distances the shrapnel may travel.")] + public readonly WDist[] Range = { WDist.FromCells(2), WDist.FromCells(5) }; public WeaponInfo[] WeaponInfos { get; private set; } diff --git a/OpenRA.Mods.D2k/OpenRA.Mods.D2k.csproj b/OpenRA.Mods.D2k/OpenRA.Mods.D2k.csproj index 39acd08bc9..0a64324e53 100644 --- a/OpenRA.Mods.D2k/OpenRA.Mods.D2k.csproj +++ b/OpenRA.Mods.D2k/OpenRA.Mods.D2k.csproj @@ -92,6 +92,7 @@ + diff --git a/OpenRA.Mods.D2k/Traits/SpiceBloom.cs b/OpenRA.Mods.D2k/Traits/SpiceBloom.cs new file mode 100644 index 0000000000..aec763cc9d --- /dev/null +++ b/OpenRA.Mods.D2k/Traits/SpiceBloom.cs @@ -0,0 +1,148 @@ +#region Copyright & License Information +/* + * Copyright 2007-2015 The OpenRA Developers (see AUTHORS) + * 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. For more information, + * see COPYING. + */ +#endregion + +using System; +using System.Collections.Generic; +using System.Linq; +using OpenRA.Effects; +using OpenRA.GameRules; +using OpenRA.Graphics; +using OpenRA.Mods.Common.Traits; +using OpenRA.Primitives; +using OpenRA.Traits; + +namespace OpenRA.Mods.D2k.Traits +{ + [Desc("Seeds resources by explosive eruptions after accumulation times.")] + public class SpiceBloomInfo : ITraitInfo, Requires + { + [SequenceReference] + public readonly string[] GrowthSequences = { "grow1", "grow2", "grow3" }; + + [Desc("The range of time (in ticks) that the spicebloom will take to respawn.")] + public readonly int[] RespawnDelay = { 1500, 2500 }; + + [Desc("The range of time (in ticks) that the spicebloom will take to grow.")] + public readonly int[] GrowthDelay = { 1000, 1500 }; + + public readonly string ResourceType = "Spice"; + + [Desc("The weapon to use for spice creation.")] + [WeaponReference] + public readonly string Weapon = "SpiceExplosion"; + + [Desc("The amount of spice to expel.")] + public readonly int[] Pieces = { 3, 10 }; + + [Desc("The maximum distance in cells that spice may be expelled.")] + public readonly int Range = 5; + + public object Create(ActorInitializer init) { return new SpiceBloom(init, this); } + } + + public class SpiceBloom : ITick, INotifyKilled + { + readonly Actor self; + readonly SpiceBloomInfo info; + readonly ResourceType resType; + readonly ResourceLayer resLayer; + readonly AnimationWithOffset anim; + + readonly int respawnTicks; + readonly int growTicks; + int ticks; + + public SpiceBloom(ActorInitializer init, SpiceBloomInfo info) + { + this.info = info; + self = init.Self; + + resLayer = self.World.WorldActor.Trait(); + resType = self.World.WorldActor.TraitsImplementing().First(t => t.Info.Name == info.ResourceType); + + var render = self.Trait(); + anim = new AnimationWithOffset(new Animation(init.Self.World, render.GetImage(self)), null, () => self.IsDead); + render.Add(anim); + + respawnTicks = self.World.SharedRandom.Next(info.RespawnDelay[0], info.RespawnDelay[1]); + growTicks = self.World.SharedRandom.Next(info.GrowthDelay[0], info.GrowthDelay[1]); + anim.Animation.Play(info.GrowthSequences[0]); + } + + public void Tick(Actor self) + { + ticks++; + + if (ticks >= growTicks) + self.Kill(self); + else + { + var index = info.GrowthSequences.Length * ticks / growTicks; + anim.Animation.Play(info.GrowthSequences[index]); + } + } + + public void Killed(Actor self, AttackInfo e) + { + var args = new ProjectileArgs + { + Weapon = self.World.Map.Rules.Weapons[info.Weapon.ToLowerInvariant()], + Facing = 0, + + DamageModifiers = self.TraitsImplementing() + .Select(a => a.GetFirepowerModifier()).ToArray(), + + InaccuracyModifiers = self.TraitsImplementing() + .Select(a => a.GetInaccuracyModifier()).ToArray(), + + Source = self.CenterPosition, + SourceActor = self, + }; + + var pieces = self.World.SharedRandom.Next(info.Pieces[0], info.Pieces[1]) * ticks / growTicks; + for (var i = 0; pieces > i; i++) + { + var cells = OpenRA.Traits.Util.RandomWalk(self.Location, self.World.SharedRandom); + var cell = cells.Take(info.Range).SkipWhile(p => resLayer.GetResource(p) == resType && resLayer.IsFull(p)).Cast().RandomOrDefault(self.World.SharedRandom); + if (cell == null) + cell = cells.Take(info.Range).Random(self.World.SharedRandom); + + args.PassiveTarget = self.World.Map.CenterOfCell(cell.Value); + + self.World.AddFrameEndTask(_ => + { + if (args.Weapon.Projectile != null) + { + var projectile = args.Weapon.Projectile.Create(args); + if (projectile != null) + self.World.Add(projectile); + + if (args.Weapon.Report != null && args.Weapon.Report.Any()) + Game.Sound.Play(args.Weapon.Report.Random(self.World.SharedRandom), self.CenterPosition); + } + }); + } + + self.World.AddFrameEndTask(t => t.Add(new DelayedAction(respawnTicks, () => + { + var td = new TypeDictionary + { + new ParentActorInit(self), + new LocationInit(self.Location), + new CenterPositionInit(self.CenterPosition), + new OwnerInit(self.Owner), + new FactionInit(self.Owner.Faction.InternalName), + new SkipMakeAnimsInit() + }; + self.World.CreateActor(self.Info.Name, td); + }))); + } + } +} diff --git a/mods/d2k/bits/spicebloom.shp b/mods/d2k/bits/spicebloom.shp deleted file mode 100644 index 0e0bb76d3f..0000000000 Binary files a/mods/d2k/bits/spicebloom.shp and /dev/null differ diff --git a/mods/d2k/rules/arrakis.yaml b/mods/d2k/rules/arrakis.yaml index 623591415b..31db59991c 100644 --- a/mods/d2k/rules/arrakis.yaml +++ b/mods/d2k/rules/arrakis.yaml @@ -1,20 +1,29 @@ spicebloom: - Inherits@1: ^SpriteActor HiddenUnderShroud: - WithSpriteBody: - Building: - Footprint: x - Dimensions: 1,1 + BodyOrientation: + QuantizedFacings: 1 + AutoSelectionSize: + RenderSprites: AppearsOnRadar: + UseLocation: yes Tooltip: Name: Spice Bloom - SeedsResource: - ResourceType: Spice - Interval: 75 - WithActiveAnimation: + SpiceBloom: + Weapon: SpiceExplosion + Crushable: + CrushClasses: spicebloom + CrushedByFriendlies: true RadarColorFromTerrain: Terrain: Spice - WithMakeAnimation: + Immobile: + Health: + HP: 1 + Radius: 512 + Targetable: + TargetTypes: Ground + RequiresForceFire: yes + Armor: + Type: None sandworm: Inherits@1: ^SpriteActor diff --git a/mods/d2k/rules/defaults.yaml b/mods/d2k/rules/defaults.yaml index 9850182443..ff890fae5e 100644 --- a/mods/d2k/rules/defaults.yaml +++ b/mods/d2k/rules/defaults.yaml @@ -58,7 +58,7 @@ Inherits@2: ^GainsExperience Inherits@3: ^SpriteActor Mobile: - Crushes: crate + Crushes: crate, spicebloom TerrainSpeeds: Sand: 100 Rock: 100 @@ -99,7 +99,7 @@ ^Tank: Inherits: ^Vehicle Mobile: - Crushes: crate, infantry + Crushes: crate, infantry, spicebloom ^Husk: Inherits@1: ^SpriteActor @@ -165,7 +165,7 @@ RevealsShroud: Range: 6c0 Mobile: - Crushes: crate + Crushes: crate, spicebloom SharesCell: true TerrainSpeeds: Sand: 100 diff --git a/mods/d2k/rules/structures.yaml b/mods/d2k/rules/structures.yaml index 899e8be2e2..02914895b3 100644 --- a/mods/d2k/rules/structures.yaml +++ b/mods/d2k/rules/structures.yaml @@ -528,7 +528,7 @@ wall: Armor: Type: none Crushable: - CrushClasses: Concretewall + CrushClasses: wall BlocksProjectiles: LineBuild: Range: 5 diff --git a/mods/d2k/rules/vehicles.yaml b/mods/d2k/rules/vehicles.yaml index 06302ee3d3..0bf412ec36 100644 --- a/mods/d2k/rules/vehicles.yaml +++ b/mods/d2k/rules/vehicles.yaml @@ -20,7 +20,7 @@ mcv: Type: light Mobile: Speed: 31 - Crushes: crate, infantry + Crushes: crate, infantry, spicebloom RevealsShroud: Range: 8c0 MustBeDestroyed: @@ -77,7 +77,7 @@ harvester: Type: harvester Mobile: Speed: 43 - Crushes: crate, infantry + Crushes: crate, infantry, spicebloom RevealsShroud: Range: 4c0 Explodes: @@ -192,7 +192,6 @@ siege_tank: Mobile: Speed: 43 ROT: 3 - Crushes: crate, infantry RevealsShroud: Range: 8c0 Turreted: @@ -312,7 +311,7 @@ devastator: Mobile: ROT: 3 Speed: 31 - Crushes: crate, infantry + Crushes: crate, infantry, spicebloom, wall RevealsShroud: Range: 7c0 Armament: @@ -452,7 +451,6 @@ deviator: Mobile: Speed: 75 ROT: 5 - Crushes: crate, infantry RevealsShroud: Range: 8c0 Turreted: @@ -511,7 +509,6 @@ combat_tank_o: Mobile: Speed: 85 ROT: 5 - Crushes: crate, infantry Health: HP: 1800 SpawnActorOnDeath: diff --git a/mods/d2k/sequences/misc.yaml b/mods/d2k/sequences/misc.yaml index 4a284643a2..756bf5dba3 100644 --- a/mods/d2k/sequences/misc.yaml +++ b/mods/d2k/sequences/misc.yaml @@ -327,19 +327,20 @@ crate: ZOffset: -511 Offset: -16,-16 -# TODO: keep the redundant spicebloom.shp for now for the awful WinForms maps editor spicebloom: - make: DATA.R8 + grow1: DATA.R8 Start: 107 - Length: 3 + Length: 1 ZOffset: -1023 Offset: -16,-16 - active: DATA.R8 - Start: 109 + grow2: DATA.R8 + Start: 108 + Length: 1 ZOffset: -1023 Offset: -16,-16 - idle: DATA.R8 + grow3: DATA.R8 Start: 109 + Length: 1 ZOffset: -1023 Offset: -16,-16 @@ -400,3 +401,6 @@ sandcraters: Length: 16 Offset: -16,-16 +null: + idle: DATA.R8 + Start: 3304 diff --git a/mods/d2k/weapons.yaml b/mods/d2k/weapons.yaml index d68c5ad5f8..40ccd18b2a 100644 --- a/mods/d2k/weapons.yaml +++ b/mods/d2k/weapons.yaml @@ -829,6 +829,13 @@ SardDeath: ImpactSound: EXPLSML2.WAV SpiceExplosion: + Report: EXPLMD1.WAV + Projectile: Bullet + Speed: 50, 75 + High: true + Angle: 91, 264 + Trail: large_trail + Image: null Warhead@1Dam: SpreadDamage Spread: 480 Falloff: 100, 100, 100, 95, 60, 25, 0 @@ -845,6 +852,7 @@ SpiceExplosion: cy: 20 harvester: 25 DamageTypes: Prone50Percent, TriggerProne, ExplosionDeath + AffectsParent: true Warhead@2Res: CreateResource AddsResourceType: Spice Size: 2,2