diff --git a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj
index 8767e17ce7..a78dc915c3 100644
--- a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj
+++ b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj
@@ -552,6 +552,8 @@
+
+
diff --git a/OpenRA.Mods.Common/Traits/ActorSpawner.cs b/OpenRA.Mods.Common/Traits/ActorSpawner.cs
new file mode 100644
index 0000000000..ea1c8b5723
--- /dev/null
+++ b/OpenRA.Mods.Common/Traits/ActorSpawner.cs
@@ -0,0 +1,34 @@
+#region Copyright & License Information
+/*
+ * Copyright 2007-2018 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, either version 3 of
+ * the License, or (at your option) any later version. For more
+ * information, see COPYING.
+ */
+#endregion
+
+using System;
+using System.Collections.Generic;
+using OpenRA.Traits;
+
+namespace OpenRA.Mods.Common.Traits
+{
+ [Desc("An actor with this trait indicates a valid spawn point for actors of ActorSpawnManager.")]
+ public class ActorSpawnerInfo : ConditionalTraitInfo
+ {
+ [Desc("Type of ActorSpawner with which it connects.")]
+ public readonly HashSet Types = new HashSet() { };
+
+ public override object Create(ActorInitializer init) { return new ActorSpawner(this); }
+ }
+
+ public class ActorSpawner : ConditionalTrait
+ {
+ public ActorSpawner(ActorSpawnerInfo info)
+ : base(info) { }
+
+ public HashSet Types { get { return Info.Types; } }
+ }
+}
diff --git a/OpenRA.Mods.Common/Traits/World/ActorSpawnManager.cs b/OpenRA.Mods.Common/Traits/World/ActorSpawnManager.cs
new file mode 100644
index 0000000000..e10249e896
--- /dev/null
+++ b/OpenRA.Mods.Common/Traits/World/ActorSpawnManager.cs
@@ -0,0 +1,116 @@
+#region Copyright & License Information
+/*
+ * Copyright 2007-2018 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, either version 3 of
+ * the License, or (at your option) any later version. For more
+ * information, see COPYING.
+ */
+#endregion
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using OpenRA.Primitives;
+using OpenRA.Traits;
+
+namespace OpenRA.Mods.Common.Traits
+{
+ [Desc("Controls the spawning of specified actor types. Attach this to the world actor.")]
+ public class ActorSpawnManagerInfo : ConditionalTraitInfo, Requires
+ {
+ [Desc("Minimum number of actors.")]
+ public readonly int Minimum = 0;
+
+ [Desc("Maximum number of actors.")]
+ public readonly int Maximum = 4;
+
+ [Desc("Time (in ticks) between actor spawn.")]
+ public readonly int SpawnInterval = 6000;
+
+ [FieldLoader.Require]
+ [ActorReference]
+ [Desc("Name of the actor that will be randomly picked to spawn.")]
+ public readonly string[] Actors = { };
+
+ public readonly string Owner = "Creeps";
+
+ [Desc("Type of ActorSpawner with which it connects.")]
+ public readonly HashSet Types = new HashSet() { };
+
+ public override object Create(ActorInitializer init) { return new ActorSpawnManager(init.Self, this); }
+ }
+
+ public class ActorSpawnManager : ConditionalTrait, ITick, INotifyCreated
+ {
+ readonly ActorSpawnManagerInfo info;
+ TraitPair[] spawnPointActors;
+
+ bool enabled;
+ int spawnCountdown;
+ int actorsPresent;
+
+ public ActorSpawnManager(Actor self, ActorSpawnManagerInfo info) : base(info)
+ {
+ this.info = info;
+ }
+
+ void INotifyCreated.Created(Actor self)
+ {
+ self.World.AddFrameEndTask(w =>
+ {
+ spawnPointActors = w.ActorsWithTrait()
+ .Where(x => info.Types.Overlaps(x.Trait.Types) || !x.Trait.Types.Any())
+ .ToArray();
+
+ enabled = self.Trait().Enabled && spawnPointActors.Any();
+ });
+ }
+
+ void ITick.Tick(Actor self)
+ {
+ if (IsTraitDisabled || !enabled)
+ return;
+
+ if (info.Maximum < 1 || actorsPresent >= info.Maximum)
+ return;
+
+ if (--spawnCountdown > 0 && actorsPresent >= info.Minimum)
+ return;
+
+ spawnCountdown = info.SpawnInterval;
+
+ do
+ {
+ // Always spawn at least one actor, plus
+ // however many needed to reach the minimum.
+ SpawnActor(self);
+ } while (actorsPresent < info.Minimum);
+ }
+
+ WPos SpawnActor(Actor self)
+ {
+ var spawnPoint = GetRandomSpawnPoint(self.World.SharedRandom);
+ self.World.AddFrameEndTask(w => w.CreateActor(info.Actors.Random(self.World.SharedRandom), new TypeDictionary
+ {
+ new OwnerInit(w.Players.First(x => x.PlayerName == info.Owner)),
+ new LocationInit(spawnPoint.Location)
+ }));
+
+ actorsPresent++;
+
+ return spawnPoint.CenterPosition;
+ }
+
+ Actor GetRandomSpawnPoint(Support.MersenneTwister random)
+ {
+ return spawnPointActors.Random(random).Actor;
+ }
+
+ public void DecreaseActorCount()
+ {
+ actorsPresent--;
+ }
+ }
+}
diff --git a/OpenRA.Mods.Common/UtilityCommands/UpgradeRules.cs b/OpenRA.Mods.Common/UtilityCommands/UpgradeRules.cs
index 3d40f70088..8d9c2156bb 100644
--- a/OpenRA.Mods.Common/UtilityCommands/UpgradeRules.cs
+++ b/OpenRA.Mods.Common/UtilityCommands/UpgradeRules.cs
@@ -1635,6 +1635,21 @@ namespace OpenRA.Mods.Common.UtilityCommands
}
}
+ if (engineVersion < 20180225)
+ {
+ if (node.Key == "WormSpawner")
+ RenameNodeKey(node, "ActorSpawner");
+
+ if (node.Key == "WormManager")
+ {
+ RenameNodeKey(node, "ActorSpawnManager");
+
+ var wormSignature = node.Value.Nodes.FirstOrDefault(n => n.Key == "WormSignature");
+ if (wormSignature != null)
+ wormSignature.Key = "Actors";
+ }
+ }
+
UpgradeActorRules(modData, engineVersion, ref node.Value.Nodes, node, depth + 1);
}
diff --git a/OpenRA.Mods.D2k/OpenRA.Mods.D2k.csproj b/OpenRA.Mods.D2k/OpenRA.Mods.D2k.csproj
index bfed6c8eec..3f2340997f 100644
--- a/OpenRA.Mods.D2k/OpenRA.Mods.D2k.csproj
+++ b/OpenRA.Mods.D2k/OpenRA.Mods.D2k.csproj
@@ -80,9 +80,7 @@
-
-
diff --git a/OpenRA.Mods.D2k/Traits/Sandworm.cs b/OpenRA.Mods.D2k/Traits/Sandworm.cs
index 5a21607921..30f310293b 100644
--- a/OpenRA.Mods.D2k/Traits/Sandworm.cs
+++ b/OpenRA.Mods.D2k/Traits/Sandworm.cs
@@ -37,7 +37,7 @@ namespace OpenRA.Mods.D2k.Traits
{
public readonly SandwormInfo WormInfo;
- readonly WormManager manager;
+ readonly ActorSpawnManager manager;
readonly Mobile mobile;
readonly AttackBase attackTrait;
@@ -54,7 +54,7 @@ namespace OpenRA.Mods.D2k.Traits
WormInfo = info;
mobile = self.Trait();
attackTrait = self.Trait();
- manager = self.World.WorldActor.Trait();
+ manager = self.World.WorldActor.Trait();
}
public override void DoAction(Actor self, CPos targetCell)
@@ -140,7 +140,7 @@ namespace OpenRA.Mods.D2k.Traits
if (disposed)
return;
- manager.DecreaseWormCount();
+ manager.DecreaseActorCount();
disposed = true;
}
}
diff --git a/OpenRA.Mods.D2k/Traits/World/WormManager.cs b/OpenRA.Mods.D2k/Traits/World/WormManager.cs
deleted file mode 100644
index 8044624ef9..0000000000
--- a/OpenRA.Mods.D2k/Traits/World/WormManager.cs
+++ /dev/null
@@ -1,113 +0,0 @@
-#region Copyright & License Information
-/*
- * Copyright 2007-2018 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, either version 3 of
- * the License, or (at your option) any later version. For more
- * information, see COPYING.
- */
-#endregion
-
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using OpenRA.Mods.Common.Traits;
-using OpenRA.Primitives;
-using OpenRA.Traits;
-
-namespace OpenRA.Mods.D2k.Traits
-{
- [Desc("Controls the spawning of sandworms. Attach this to the world actor.")]
- class WormManagerInfo : ITraitInfo, Requires
- {
- [Desc("Minimum number of worms")]
- public readonly int Minimum = 0;
-
- [Desc("Maximum number of worms")]
- public readonly int Maximum = 4;
-
- [Desc("Time (in ticks) between worm spawn.")]
- public readonly int SpawnInterval = 6000;
-
- [Desc("Name of the actor that will be spawned.")]
- public readonly string WormSignature = "sandworm";
-
- public readonly string WormSignNotification = "WormSign";
- public readonly string WormOwnerPlayer = "Creeps";
-
- public object Create(ActorInitializer init) { return new WormManager(init.Self, this); }
- }
-
- class WormManager : ITick, INotifyCreated
- {
- readonly WormManagerInfo info;
- readonly Lazy spawnPointActors;
-
- bool enabled;
- int spawnCountdown;
- int wormsPresent;
-
- public WormManager(Actor self, WormManagerInfo info)
- {
- this.info = info;
- spawnPointActors = Exts.Lazy(() => self.World.ActorsHavingTrait().ToArray());
- }
-
- void INotifyCreated.Created(Actor self)
- {
- enabled = self.Trait().Enabled;
- }
-
- void ITick.Tick(Actor self)
- {
- if (!enabled)
- return;
-
- if (!spawnPointActors.Value.Any())
- return;
-
- // Apparently someone doesn't want worms or the maximum number of worms has been reached
- if (info.Maximum < 1 || wormsPresent >= info.Maximum)
- return;
-
- if (--spawnCountdown > 0 && wormsPresent >= info.Minimum)
- return;
-
- spawnCountdown = info.SpawnInterval;
-
- var wormLocations = new List();
-
- do
- {
- // Always spawn at least one worm, plus however many
- // more we need to reach the defined minimum count.
- wormLocations.Add(SpawnWorm(self));
- } while (wormsPresent < info.Minimum);
- }
-
- WPos SpawnWorm(Actor self)
- {
- var spawnPoint = GetRandomSpawnPoint(self);
- self.World.AddFrameEndTask(w => w.CreateActor(info.WormSignature, new TypeDictionary
- {
- new OwnerInit(w.Players.First(x => x.PlayerName == info.WormOwnerPlayer)),
- new LocationInit(spawnPoint.Location)
- }));
-
- wormsPresent++;
-
- return spawnPoint.CenterPosition;
- }
-
- Actor GetRandomSpawnPoint(Actor self)
- {
- return spawnPointActors.Value.Random(self.World.SharedRandom);
- }
-
- public void DecreaseWormCount()
- {
- wormsPresent--;
- }
- }
-}
diff --git a/OpenRA.Mods.D2k/Traits/WormSpawner.cs b/OpenRA.Mods.D2k/Traits/WormSpawner.cs
deleted file mode 100644
index 499e1a0f3d..0000000000
--- a/OpenRA.Mods.D2k/Traits/WormSpawner.cs
+++ /dev/null
@@ -1,19 +0,0 @@
-#region Copyright & License Information
-/*
- * Copyright 2007-2018 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, either version 3 of
- * the License, or (at your option) any later version. For more
- * information, see COPYING.
- */
-#endregion
-
-using OpenRA.Traits;
-
-namespace OpenRA.Mods.D2k.Traits
-{
- [Desc("An actor with this trait indicates a valid spawn point for sandworms.")]
- class WormSpawnerInfo : TraitInfo { }
- class WormSpawner { }
-}
\ No newline at end of file
diff --git a/mods/d2k/maps/mount-idaho/rules.yaml b/mods/d2k/maps/mount-idaho/rules.yaml
index 114a8bf4e9..eb556d164f 100644
--- a/mods/d2k/maps/mount-idaho/rules.yaml
+++ b/mods/d2k/maps/mount-idaho/rules.yaml
@@ -1,4 +1,4 @@
World:
- WormManager:
+ ActorSpawnManager:
Minimum: 1
Maximum: 2
diff --git a/mods/d2k/maps/oasis-conquest/rules.yaml b/mods/d2k/maps/oasis-conquest/rules.yaml
index 363d4add29..93bcc8dd13 100644
--- a/mods/d2k/maps/oasis-conquest/rules.yaml
+++ b/mods/d2k/maps/oasis-conquest/rules.yaml
@@ -1,4 +1,4 @@
World:
- WormManager:
+ ActorSpawnManager:
Minimum: 3
Maximum: 6
diff --git a/mods/d2k/maps/pasty-mesa/rules.yaml b/mods/d2k/maps/pasty-mesa/rules.yaml
index 114a8bf4e9..eb556d164f 100644
--- a/mods/d2k/maps/pasty-mesa/rules.yaml
+++ b/mods/d2k/maps/pasty-mesa/rules.yaml
@@ -1,4 +1,4 @@
World:
- WormManager:
+ ActorSpawnManager:
Minimum: 1
Maximum: 2
diff --git a/mods/d2k/maps/shellmap/rules.yaml b/mods/d2k/maps/shellmap/rules.yaml
index 402728dfb1..9579520ef8 100644
--- a/mods/d2k/maps/shellmap/rules.yaml
+++ b/mods/d2k/maps/shellmap/rules.yaml
@@ -8,7 +8,7 @@ World:
-MPStartLocations:
ResourceType@Spice:
ValuePerUnit: 0
- WormManager:
+ ActorSpawnManager:
Minimum: 1
Maximum: 3
MusicPlaylist:
diff --git a/mods/d2k/rules/campaign-rules.yaml b/mods/d2k/rules/campaign-rules.yaml
index 38ee587331..7807fed2d2 100644
--- a/mods/d2k/rules/campaign-rules.yaml
+++ b/mods/d2k/rules/campaign-rules.yaml
@@ -16,7 +16,7 @@ World:
-MPStartLocations:
ObjectivesPanel:
PanelName: MISSION_OBJECTIVES
- WormManager:
+ ActorSpawnManager:
Minimum: 1
Maximum: 1
MapCreeps:
diff --git a/mods/d2k/rules/misc.yaml b/mods/d2k/rules/misc.yaml
index 2bc5abd3da..e2eefdc98b 100644
--- a/mods/d2k/rules/misc.yaml
+++ b/mods/d2k/rules/misc.yaml
@@ -204,7 +204,7 @@ wormspawner:
WithSpriteBody:
BodyOrientation:
QuantizedFacings: 1
- WormSpawner:
+ ActorSpawner:
EditorTilesetFilter:
Categories: System
diff --git a/mods/d2k/rules/world.yaml b/mods/d2k/rules/world.yaml
index 427a864fbf..03067b25c7 100644
--- a/mods/d2k/rules/world.yaml
+++ b/mods/d2k/rules/world.yaml
@@ -72,7 +72,8 @@ World:
BuildingInfluence:
ProductionQueueFromSelection:
ProductionPaletteWidget: PRODUCTION_PALETTE
- WormManager:
+ ActorSpawnManager:
+ Actors: sandworm
CrateSpawner:
Minimum: 0
Maximum: 2