diff --git a/OpenRA.Mods.Cnc/Traits/Buildings/ClonesProducedUnits.cs b/OpenRA.Mods.Cnc/Traits/Buildings/ClonesProducedUnits.cs index d6c6699a28..ded611f53e 100644 --- a/OpenRA.Mods.Cnc/Traits/Buildings/ClonesProducedUnits.cs +++ b/OpenRA.Mods.Cnc/Traits/Buildings/ClonesProducedUnits.cs @@ -40,7 +40,7 @@ namespace OpenRA.Mods.Cnc.Traits faction = init.Contains() ? init.Get() : init.Self.Owner.Faction.InternalName; } - public void UnitProducedByOther(Actor self, Actor producer, Actor produced) + public void UnitProducedByOther(Actor self, Actor producer, Actor produced, string productionType) { // No recursive cloning! if (producer.Owner != self.Owner || producer.Info.HasTraitInfo()) @@ -56,7 +56,7 @@ namespace OpenRA.Mods.Cnc.Traits new FactionInit(BuildableInfo.GetInitialFaction(produced.Info, faction)) }; - production.Produce(self, produced.Info, inits); + production.Produce(self, produced.Info, productionType, inits); } } } diff --git a/OpenRA.Mods.Cnc/Traits/Buildings/ProductionAirdrop.cs b/OpenRA.Mods.Cnc/Traits/Buildings/ProductionAirdrop.cs index 3d5c582985..a83600a465 100644 --- a/OpenRA.Mods.Cnc/Traits/Buildings/ProductionAirdrop.cs +++ b/OpenRA.Mods.Cnc/Traits/Buildings/ProductionAirdrop.cs @@ -9,7 +9,7 @@ */ #endregion -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using OpenRA.Activities; using OpenRA.Mods.Common; @@ -39,7 +39,7 @@ namespace OpenRA.Mods.Cnc.Traits this.info = info; } - public override bool Produce(Actor self, ActorInfo producee, TypeDictionary inits) + public override bool Produce(Actor self, ActorInfo producee, string productionType, TypeDictionary inits) { var owner = self.Owner; var aircraftInfo = self.World.Map.Rules.Actors[info.ActorType].TraitInfo(); @@ -80,7 +80,7 @@ namespace OpenRA.Mods.Cnc.Traits foreach (var cargo in self.TraitsImplementing()) cargo.Delivered(self); - self.World.AddFrameEndTask(ww => DoProduction(self, producee, exit, inits)); + self.World.AddFrameEndTask(ww => DoProduction(self, producee, exit, productionType, inits)); Game.Sound.PlayNotification(self.World.Map.Rules, self.Owner, "Speech", info.ReadyAudio, self.Owner.Faction.InternalName); })); diff --git a/OpenRA.Mods.Common/Activities/Air/HeliReturnToBase.cs b/OpenRA.Mods.Common/Activities/Air/HeliReturnToBase.cs index 638987fcf4..6e53e6f283 100644 --- a/OpenRA.Mods.Common/Activities/Air/HeliReturnToBase.cs +++ b/OpenRA.Mods.Common/Activities/Air/HeliReturnToBase.cs @@ -80,7 +80,7 @@ namespace OpenRA.Mods.Common.Activities } } - var exit = dest.Info.TraitInfos().FirstOrDefault(); + var exit = dest.Info.FirstExitOrDefault(null); var offset = (exit != null) ? exit.SpawnOffset : WVec.Zero; if (ShouldLandAtBuilding(self, dest)) diff --git a/OpenRA.Mods.Common/Scripting/Properties/ProductionProperties.cs b/OpenRA.Mods.Common/Scripting/Properties/ProductionProperties.cs index 17985e4327..2ab2a54fe4 100644 --- a/OpenRA.Mods.Common/Scripting/Properties/ProductionProperties.cs +++ b/OpenRA.Mods.Common/Scripting/Properties/ProductionProperties.cs @@ -33,8 +33,9 @@ namespace OpenRA.Mods.Common.Scripting } [ScriptActorPropertyActivity] - [Desc("Build a unit, ignoring the production queue. The activity will wait if the exit is blocked.")] - public void Produce(string actorType, string factionVariant = null) + [Desc("Build a unit, ignoring the production queue. The activity will wait if the exit is blocked.", + "If productionType is nil or unavailable, then an exit will be selected based on Buildable info.")] + public void Produce(string actorType, string factionVariant = null, string productionType = null) { ActorInfo actorInfo; if (!Self.World.Map.Rules.Actors.TryGetValue(actorType, out actorInfo)) @@ -47,7 +48,7 @@ namespace OpenRA.Mods.Common.Scripting new FactionInit(faction) }; - Self.QueueActivity(new WaitFor(() => p.Produce(Self, actorInfo, inits))); + Self.QueueActivity(new WaitFor(() => p.Produce(Self, actorInfo, productionType, inits))); } } diff --git a/OpenRA.Mods.Common/Scripting/ScriptTriggers.cs b/OpenRA.Mods.Common/Scripting/ScriptTriggers.cs index ac7039abcc..0188a6b45d 100644 --- a/OpenRA.Mods.Common/Scripting/ScriptTriggers.cs +++ b/OpenRA.Mods.Common/Scripting/ScriptTriggers.cs @@ -404,7 +404,7 @@ namespace OpenRA.Mods.Common.Scripting } } - public void UnitProducedByOther(Actor self, Actor producee, Actor produced) + public void UnitProducedByOther(Actor self, Actor producee, Actor produced, string productionType) { if (world.Disposing) return; diff --git a/OpenRA.Mods.Common/Traits/Air/Aircraft.cs b/OpenRA.Mods.Common/Traits/Air/Aircraft.cs index 40c39f963d..12576ea3f2 100644 --- a/OpenRA.Mods.Common/Traits/Air/Aircraft.cs +++ b/OpenRA.Mods.Common/Traits/Air/Aircraft.cs @@ -731,7 +731,7 @@ namespace OpenRA.Mods.Common.Traits Action enter = () => { - var exit = order.TargetActor.Info.TraitInfos().FirstOrDefault(); + var exit = order.TargetActor.Info.FirstExitOrDefault(null); var offset = (exit != null) ? exit.SpawnOffset : WVec.Zero; self.QueueActivity(new HeliFly(self, Target.FromPos(order.TargetActor.CenterPosition + offset))); diff --git a/OpenRA.Mods.Common/Traits/Buildings/Exit.cs b/OpenRA.Mods.Common/Traits/Buildings/Exit.cs index dfc7084472..f903125ee9 100644 --- a/OpenRA.Mods.Common/Traits/Buildings/Exit.cs +++ b/OpenRA.Mods.Common/Traits/Buildings/Exit.cs @@ -9,6 +9,9 @@ */ #endregion +using System; +using System.Collections.Generic; +using System.Linq; using OpenRA.Traits; namespace OpenRA.Mods.Common.Traits @@ -23,6 +26,9 @@ namespace OpenRA.Mods.Common.Traits public readonly CVec ExitCell = CVec.Zero; public readonly int Facing = -1; + [Desc("Type tags on this exit.")] + public readonly HashSet ProductionTypes = new HashSet(); + [Desc("AttackMove to a RallyPoint or stay where you are spawned.")] public readonly bool MoveIntoWorld = true; @@ -31,4 +37,38 @@ namespace OpenRA.Mods.Common.Traits } public class Exit { } + + public static class ExitExts + { + public static ExitInfo FirstExitOrDefault(this ActorInfo info, string productionType = null) + { + var all = info.TraitInfos(); + if (string.IsNullOrEmpty(productionType)) + return all.FirstOrDefault(e => e.ProductionTypes.Count == 0); + return all.FirstOrDefault(e => e.ProductionTypes.Count == 0 || e.ProductionTypes.Contains(productionType)); + } + + public static IEnumerable Exits(this ActorInfo info, string productionType = null) + { + var all = info.TraitInfos(); + if (string.IsNullOrEmpty(productionType)) + return all.Where(e => e.ProductionTypes.Count == 0); + return all.Where(e => e.ProductionTypes.Count == 0 || e.ProductionTypes.Contains(productionType)); + } + + public static ExitInfo RandomExitOrDefault(this ActorInfo info, World world, string productionType, Func p = null) + { + var allOfType = Exits(info, productionType); + if (!allOfType.Any()) + return null; + + var shuffled = allOfType.Shuffle(world.SharedRandom); + return p != null ? shuffled.FirstOrDefault(p) : shuffled.First(); + } + + public static ExitInfo RandomExitOrDefault(this Actor self, string productionType, Func p = null) + { + return RandomExitOrDefault(self.Info, self.World, productionType, p); + } + } } diff --git a/OpenRA.Mods.Common/Traits/Conditions/ProximityExternalCondition.cs b/OpenRA.Mods.Common/Traits/Conditions/ProximityExternalCondition.cs index e4df55ab67..8aa94d95db 100644 --- a/OpenRA.Mods.Common/Traits/Conditions/ProximityExternalCondition.cs +++ b/OpenRA.Mods.Common/Traits/Conditions/ProximityExternalCondition.cs @@ -120,7 +120,7 @@ namespace OpenRA.Mods.Common.Traits tokens[a] = external.GrantCondition(a, self); } - public void UnitProducedByOther(Actor self, Actor producer, Actor produced) + public void UnitProducedByOther(Actor self, Actor producer, Actor produced, string productionType) { // If the produced Actor doesn't occupy space, it can't be in range if (produced.OccupiesSpace == null) diff --git a/OpenRA.Mods.Common/Traits/Player/ClassicProductionQueue.cs b/OpenRA.Mods.Common/Traits/Player/ClassicProductionQueue.cs index e74f4d73a9..dbe8b5d74b 100644 --- a/OpenRA.Mods.Common/Traits/Player/ClassicProductionQueue.cs +++ b/OpenRA.Mods.Common/Traits/Player/ClassicProductionQueue.cs @@ -90,7 +90,7 @@ namespace OpenRA.Mods.Common.Traits var bi = unit.TraitInfo(); // Some units may request a specific production type, which is ignored if the AllTech cheat is enabled - var type = developerMode.AllTech ? Info.Type : bi.BuildAtProductionType ?? Info.Type; + var type = developerMode.AllTech ? Info.Type : (bi.BuildAtProductionType ?? Info.Type); var producers = self.World.ActorsWithTrait() .Where(x => x.Actor.Owner == self.Owner @@ -112,7 +112,7 @@ namespace OpenRA.Mods.Common.Traits new FactionInit(BuildableInfo.GetInitialFaction(unit, p.Trait.Faction)) }; - if (p.Trait.Produce(p.Actor, unit, inits)) + if (p.Trait.Produce(p.Actor, unit, type, inits)) { FinishProduction(); return true; diff --git a/OpenRA.Mods.Common/Traits/Player/ProductionQueue.cs b/OpenRA.Mods.Common/Traits/Player/ProductionQueue.cs index 4d4b5383b9..eb589be86e 100644 --- a/OpenRA.Mods.Common/Traits/Player/ProductionQueue.cs +++ b/OpenRA.Mods.Common/Traits/Player/ProductionQueue.cs @@ -389,7 +389,7 @@ namespace OpenRA.Mods.Common.Traits }; var sp = self.TraitsImplementing().FirstOrDefault(p => p.Info.Produces.Contains(Info.Type)); - if (sp != null && !self.IsDisabled() && sp.Produce(self, unit, inits)) + if (sp != null && !self.IsDisabled() && sp.Produce(self, unit, developerMode.AllTech ? null : Info.Type, inits)) { FinishProduction(); return true; diff --git a/OpenRA.Mods.Common/Traits/Production.cs b/OpenRA.Mods.Common/Traits/Production.cs index 0ca1190ae5..d924de9743 100644 --- a/OpenRA.Mods.Common/Traits/Production.cs +++ b/OpenRA.Mods.Common/Traits/Production.cs @@ -49,7 +49,7 @@ namespace OpenRA.Mods.Common.Traits building = self.TraitOrDefault(); } - public virtual void DoProduction(Actor self, ActorInfo producee, ExitInfo exitinfo, TypeDictionary inits) + public virtual void DoProduction(Actor self, ActorInfo producee, ExitInfo exitinfo, string productionType, TypeDictionary inits) { var exit = CPos.Zero; var exitLocation = CPos.Zero; @@ -113,25 +113,35 @@ namespace OpenRA.Mods.Common.Traits var notifyOthers = self.World.ActorsWithTrait(); foreach (var notify in notifyOthers) - notify.Trait.UnitProducedByOther(notify.Actor, self, newUnit); + notify.Trait.UnitProducedByOther(notify.Actor, self, newUnit, productionType); foreach (var t in newUnit.TraitsImplementing()) t.BuildingComplete(newUnit); }); } - public virtual bool Produce(Actor self, ActorInfo producee, TypeDictionary inits) + protected virtual ExitInfo SelectExit(Actor self, ActorInfo producee, string productionType, Func p) + { + return self.RandomExitOrDefault(productionType, p); + } + + protected ExitInfo SelectExit(Actor self, ActorInfo producee, string productionType) + { + return SelectExit(self, producee, productionType, e => CanUseExit(self, producee, e)); + } + + public virtual bool Produce(Actor self, ActorInfo producee, string productionType, TypeDictionary inits) { if (Reservable.IsReserved(self) || (building != null && building.Locked)) return false; // Pick a spawn/exit point pair - var exit = self.Info.TraitInfos().Shuffle(self.World.SharedRandom) - .FirstOrDefault(e => CanUseExit(self, producee, e)); + var exit = SelectExit(self, producee, productionType); if (exit != null || self.OccupiesSpace == null) { - DoProduction(self, producee, exit, inits); + DoProduction(self, producee, exit, productionType, inits); + return true; } diff --git a/OpenRA.Mods.Common/Traits/ProductionFromMapEdge.cs b/OpenRA.Mods.Common/Traits/ProductionFromMapEdge.cs index 1df656037c..c167c5179c 100644 --- a/OpenRA.Mods.Common/Traits/ProductionFromMapEdge.cs +++ b/OpenRA.Mods.Common/Traits/ProductionFromMapEdge.cs @@ -10,7 +10,6 @@ #endregion using System.Drawing; -using OpenRA; using OpenRA.Primitives; using OpenRA.Traits; @@ -41,7 +40,7 @@ namespace OpenRA.Mods.Common.Traits rp = self.TraitOrDefault(); } - public override bool Produce(Actor self, ActorInfo producee, TypeDictionary inits) + public override bool Produce(Actor self, ActorInfo producee, string productionType, TypeDictionary inits) { var aircraftInfo = producee.TraitInfoOrDefault(); var mobileInfo = producee.TraitInfoOrDefault(); @@ -96,7 +95,7 @@ namespace OpenRA.Mods.Common.Traits var notifyOthers = self.World.ActorsWithTrait(); foreach (var notify in notifyOthers) - notify.Trait.UnitProducedByOther(notify.Actor, self, newUnit); + notify.Trait.UnitProducedByOther(notify.Actor, self, newUnit, productionType); foreach (var t in newUnit.TraitsImplementing()) t.BuildingComplete(newUnit); diff --git a/OpenRA.Mods.Common/Traits/ProductionParadrop.cs b/OpenRA.Mods.Common/Traits/ProductionParadrop.cs index 2f8c2e249a..7d08b051d7 100644 --- a/OpenRA.Mods.Common/Traits/ProductionParadrop.cs +++ b/OpenRA.Mods.Common/Traits/ProductionParadrop.cs @@ -11,7 +11,6 @@ using System; using System.Drawing; -using System.Linq; using OpenRA.Activities; using OpenRA.Mods.Common.Activities; using OpenRA.Primitives; @@ -44,16 +43,15 @@ namespace OpenRA.Mods.Common.Traits rp = Exts.Lazy(() => init.Self.IsDead ? null : init.Self.TraitOrDefault()); } - public override bool Produce(Actor self, ActorInfo producee, TypeDictionary inits) + public override bool Produce(Actor self, ActorInfo producee, string productionType, TypeDictionary inits) { var owner = self.Owner; - // Assume a single exit point for simplicity - var exit = self.Info.TraitInfos().First(); + var exit = SelectExit(self, producee, productionType); // Start a fixed distance away: the width of the map. // This makes the production timing independent of spawnpoint - var dropPos = self.Location + exit.ExitCell; + var dropPos = exit != null ? self.Location + exit.ExitCell : self.Location; var startPos = dropPos + new CVec(owner.World.Map.Bounds.Width, 0); var endPos = new CPos(owner.World.Map.Bounds.Left - 5, dropPos.Y); @@ -85,7 +83,7 @@ namespace OpenRA.Mods.Common.Traits foreach (var cargo in self.TraitsImplementing()) cargo.Delivered(self); - self.World.AddFrameEndTask(ww => DoProduction(self, producee, exit, inits)); + self.World.AddFrameEndTask(ww => DoProduction(self, producee, exit, productionType, inits)); Game.Sound.Play(SoundType.World, info.ChuteSound, self.CenterPosition); Game.Sound.PlayNotification(self.World.Map.Rules, self.Owner, "Speech", info.ReadyAudio, self.Owner.Faction.InternalName); })); @@ -97,7 +95,7 @@ namespace OpenRA.Mods.Common.Traits return true; } - public override void DoProduction(Actor self, ActorInfo producee, ExitInfo exitinfo, TypeDictionary inits) + public override void DoProduction(Actor self, ActorInfo producee, ExitInfo exitinfo, string productionType, TypeDictionary inits) { var exit = CPos.Zero; var exitLocation = CPos.Zero; @@ -155,7 +153,7 @@ namespace OpenRA.Mods.Common.Traits var notifyOthers = self.World.ActorsWithTrait(); foreach (var notify in notifyOthers) - notify.Trait.UnitProducedByOther(notify.Actor, self, newUnit); + notify.Trait.UnitProducedByOther(notify.Actor, self, newUnit, productionType); foreach (var t in newUnit.TraitsImplementing()) t.BuildingComplete(newUnit); diff --git a/OpenRA.Mods.Common/Traits/SupportPowers/ProduceActorPower.cs b/OpenRA.Mods.Common/Traits/SupportPowers/ProduceActorPower.cs index fb5b6233f5..938756fe6e 100644 --- a/OpenRA.Mods.Common/Traits/SupportPowers/ProduceActorPower.cs +++ b/OpenRA.Mods.Common/Traits/SupportPowers/ProduceActorPower.cs @@ -75,7 +75,7 @@ namespace OpenRA.Mods.Common.Traits new FactionInit(BuildableInfo.GetInitialFaction(ai, faction)) }; - activated |= sp.Produce(self, ai, inits); + activated |= sp.Produce(self, ai, info.Type, inits); } } diff --git a/OpenRA.Mods.Common/TraitsInterfaces.cs b/OpenRA.Mods.Common/TraitsInterfaces.cs index d51bdee682..199735b594 100644 --- a/OpenRA.Mods.Common/TraitsInterfaces.cs +++ b/OpenRA.Mods.Common/TraitsInterfaces.cs @@ -103,7 +103,7 @@ namespace OpenRA.Mods.Common.Traits public interface INotifyBurstComplete { void FiredBurst(Actor self, Target target, Armament a); } public interface INotifyChat { bool OnChat(string from, string message); } public interface INotifyProduction { void UnitProduced(Actor self, Actor other, CPos exit); } - public interface INotifyOtherProduction { void UnitProducedByOther(Actor self, Actor producer, Actor produced); } + public interface INotifyOtherProduction { void UnitProducedByOther(Actor self, Actor producer, Actor produced, string productionType); } public interface INotifyDelivery { void IncomingDelivery(Actor self); void Delivered(Actor self); } public interface INotifyDocking { void Docked(Actor self, Actor harvester); void Undocked(Actor self, Actor harvester); } public interface INotifyParachute { void OnParachute(Actor self); void OnLanded(Actor self, Actor ignore); } diff --git a/mods/ra/rules/structures.yaml b/mods/ra/rules/structures.yaml index 13fe21ab73..22e99e8f43 100644 --- a/mods/ra/rules/structures.yaml +++ b/mods/ra/rules/structures.yaml @@ -144,18 +144,42 @@ SPEN: SpawnOffset: 0,-213,0 Facing: 96 ExitCell: -1,2 + ProductionTypes: Submarine Exit@2: SpawnOffset: 0,-213,0 Facing: 160 ExitCell: 3,2 + ProductionTypes: Submarine Exit@3: SpawnOffset: 0,0,0 Facing: 32 ExitCell: 0,0 + ProductionTypes: Submarine Exit@4: SpawnOffset: 0,0,0 Facing: 224 ExitCell: 2,0 + ProductionTypes: Submarine + Exit@b1: + SpawnOffset: -1024,1024,0 + Facing: 160 + ExitCell: 0,2 + ProductionTypes: Ship + Exit@b2: + SpawnOffset: 1024,1024,0 + Facing: 224 + ExitCell: 2,2 + ProductionTypes: Ship + Exit@b3: + SpawnOffset: -1024,-1024,0 + Facing: 96 + ExitCell: 0,0 + ProductionTypes: Ship + Exit@b4: + SpawnOffset: 1024,-1024,0 + Facing: 32 + ExitCell: 2,0 + ProductionTypes: Ship Production: Produces: Ship, Submarine PrimaryBuilding: @@ -248,18 +272,22 @@ SYRD: SpawnOffset: -1024,1024,0 Facing: 160 ExitCell: 0,2 + ProductionTypes: Ship Exit@2: SpawnOffset: 1024,1024,0 Facing: 224 ExitCell: 2,2 + ProductionTypes: Ship Exit@3: SpawnOffset: -1024,-1024,0 Facing: 96 ExitCell: 0,0 + ProductionTypes: Ship Exit@4: SpawnOffset: 1024,-1024,0 Facing: 32 ExitCell: 2,0 + ProductionTypes: Ship Production: Produces: Ship, Boat PrimaryBuilding: @@ -1557,9 +1585,11 @@ BARR: Exit@1: SpawnOffset: -170,810,0 ExitCell: 1,2 + ProductionTypes: Soldier, Infantry Exit@2: SpawnOffset: -725,640,0 ExitCell: 0,2 + ProductionTypes: Soldier, Infantry Production: Produces: Infantry, Soldier PrimaryBuilding: @@ -1631,9 +1661,11 @@ KENN: Exit@1: SpawnOffset: -280,400,0 ExitCell: 0,1 + ProductionTypes: Dog, Infantry Exit@2: SpawnOffset: -280,400,0 ExitCell: -1,0 + ProductionTypes: Dog, Infantry Production: Produces: Infantry, Dog PrimaryBuilding: @@ -1684,9 +1716,11 @@ TENT: Exit@1: SpawnOffset: -42,810,0 ExitCell: 1,2 + ProductionTypes: Soldier, Infantry Exit@2: SpawnOffset: -725,640,0 ExitCell: 0,2 + ProductionTypes: Soldier, Infantry Production: Produces: Infantry, Soldier PrimaryBuilding: