diff --git a/OpenRA.Mods.Cnc/Traits/Buildings/ProductionAirdrop.cs b/OpenRA.Mods.Cnc/Traits/Buildings/ProductionAirdrop.cs index a83600a465..38825f4281 100644 --- a/OpenRA.Mods.Cnc/Traits/Buildings/ProductionAirdrop.cs +++ b/OpenRA.Mods.Cnc/Traits/Buildings/ProductionAirdrop.cs @@ -41,6 +41,9 @@ namespace OpenRA.Mods.Cnc.Traits public override bool Produce(Actor self, ActorInfo producee, string productionType, TypeDictionary inits) { + if (IsTraitDisabled || IsTraitPaused) + return false; + var owner = self.Owner; var aircraftInfo = self.World.Map.Rules.Actors[info.ActorType].TraitInfo(); diff --git a/OpenRA.Mods.Common/Traits/Player/ClassicProductionQueue.cs b/OpenRA.Mods.Common/Traits/Player/ClassicProductionQueue.cs index dbe8b5d74b..95d3cff86a 100644 --- a/OpenRA.Mods.Common/Traits/Player/ClassicProductionQueue.cs +++ b/OpenRA.Mods.Common/Traits/Player/ClassicProductionQueue.cs @@ -46,42 +46,50 @@ namespace OpenRA.Mods.Common.Traits this.info = info; } - [Sync] bool isActive = false; - protected override void Tick(Actor self) { // PERF: Avoid LINQ. - isActive = false; + Enabled = false; + var isActive = false; foreach (var x in self.World.ActorsWithTrait()) { - if (x.Actor.Owner == self.Owner && x.Trait.Info.Produces.Contains(Info.Type)) - { - isActive = true; - break; - } + if (x.Trait.IsTraitDisabled) + continue; + + if (x.Actor.Owner != self.Owner || !x.Trait.Info.Produces.Contains(Info.Type)) + continue; + + Enabled |= IsValidFaction; + isActive |= !x.Trait.IsTraitPaused; } - base.Tick(self); + if (!Enabled) + ClearQueue(); + + TickInner(self, !isActive); } public override IEnumerable AllItems() { - return isActive ? base.AllItems() : NoItems; + return Enabled ? base.AllItems() : NoItems; } public override IEnumerable BuildableItems() { - return isActive ? base.BuildableItems() : NoItems; + return Enabled ? base.BuildableItems() : NoItems; } public override TraitPair MostLikelyProducer() { - return self.World.ActorsWithTrait() + var productionActors = self.World.ActorsWithTrait() .Where(x => x.Actor.Owner == self.Owner - && x.Trait.Info.Produces.Contains(Info.Type)) + && !x.Trait.IsTraitDisabled && x.Trait.Info.Produces.Contains(Info.Type)) .OrderByDescending(x => x.Actor.IsPrimaryBuilding()) .ThenByDescending(x => x.Actor.ActorID) - .FirstOrDefault(); + .ToList(); + + var unpaused = productionActors.FirstOrDefault(a => !a.Trait.IsTraitPaused); + return unpaused.Trait != null ? unpaused : productionActors.FirstOrDefault(); } protected override bool BuildUnit(ActorInfo unit) @@ -94,6 +102,7 @@ namespace OpenRA.Mods.Common.Traits var producers = self.World.ActorsWithTrait() .Where(x => x.Actor.Owner == self.Owner + && !x.Trait.IsTraitDisabled && x.Trait.Info.Produces.Contains(type)) .OrderByDescending(x => x.Actor.IsPrimaryBuilding()) .ThenByDescending(x => x.Actor.ActorID); @@ -104,8 +113,11 @@ namespace OpenRA.Mods.Common.Traits return false; } - foreach (var p in producers.Where(p => !p.Actor.IsDisabled())) + foreach (var p in producers) { + if (p.Trait.IsTraitPaused) + continue; + var inits = new TypeDictionary { new OwnerInit(self.Owner), @@ -134,7 +146,7 @@ namespace OpenRA.Mods.Common.Traits var type = bi.BuildAtProductionType ?? info.Type; var selfsameProductionsCount = self.World.ActorsWithTrait() - .Count(p => p.Actor.Owner == self.Owner && p.Trait.Info.Produces.Contains(type)); + .Count(p => !p.Trait.IsTraitDisabled && !p.Trait.IsTraitPaused && p.Actor.Owner == self.Owner && p.Trait.Info.Produces.Contains(type)); var speedModifier = selfsameProductionsCount.Clamp(1, info.BuildTimeSpeedReduction.Length) - 1; time = (time * info.BuildTimeSpeedReduction[speedModifier]) / 100; diff --git a/OpenRA.Mods.Common/Traits/Player/ProductionQueue.cs b/OpenRA.Mods.Common/Traits/Player/ProductionQueue.cs index eb589be86e..f70297e56f 100644 --- a/OpenRA.Mods.Common/Traits/Player/ProductionQueue.cs +++ b/OpenRA.Mods.Common/Traits/Player/ProductionQueue.cs @@ -65,7 +65,7 @@ namespace OpenRA.Mods.Common.Traits public virtual object Create(ActorInitializer init) { return new ProductionQueue(init, init.Self.Owner.PlayerActor, this); } } - public class ProductionQueue : IResolveOrder, ITick, ITechTreeElement, INotifyOwnerChanged, INotifyKilled, INotifySold, ISync, INotifyTransform + public class ProductionQueue : IResolveOrder, ITick, ITechTreeElement, INotifyOwnerChanged, INotifyKilled, INotifySold, ISync, INotifyTransform, INotifyCreated { public readonly ProductionQueueInfo Info; readonly Actor self; @@ -76,6 +76,8 @@ namespace OpenRA.Mods.Common.Traits readonly IEnumerable allProducibles; readonly IEnumerable buildableProducibles; + Production[] productionTraits; + // Will change if the owner changes PowerManager playerPower; PlayerResources playerResources; @@ -89,9 +91,10 @@ namespace OpenRA.Mods.Common.Traits [Sync] public int CurrentSlowdown { get { return QueueLength == 0 ? 0 : queue[0].Slowdown; } } [Sync] public bool CurrentPaused { get { return QueueLength != 0 && queue[0].Paused; } } [Sync] public bool CurrentDone { get { return QueueLength != 0 && queue[0].Done; } } - [Sync] public bool Enabled { get; private set; } + [Sync] public bool Enabled { get; protected set; } public string Faction { get; private set; } + [Sync] public bool IsValidFaction { get; private set; } public ProductionQueue(ActorInitializer init, Actor playerActor, ProductionQueueInfo info) { @@ -102,14 +105,20 @@ namespace OpenRA.Mods.Common.Traits developerMode = playerActor.Trait(); Faction = init.Contains() ? init.Get() : self.Owner.Faction.InternalName; - Enabled = !info.Factions.Any() || info.Factions.Contains(Faction); + IsValidFaction = !info.Factions.Any() || info.Factions.Contains(Faction); + Enabled = IsValidFaction; CacheProducibles(playerActor); allProducibles = producible.Where(a => a.Value.Buildable || a.Value.Visible).Select(a => a.Key); buildableProducibles = producible.Where(a => a.Value.Buildable).Select(a => a.Key); } - void ClearQueue() + void INotifyCreated.Created(Actor self) + { + productionTraits = self.TraitsImplementing().ToArray(); + } + + protected void ClearQueue() { if (queue.Count == 0) return; @@ -130,7 +139,7 @@ namespace OpenRA.Mods.Common.Traits if (!Info.Sticky) { Faction = self.Owner.Faction.InternalName; - Enabled = !Info.Factions.Any() || Info.Factions.Contains(Faction); + IsValidFaction = !Info.Factions.Any() || Info.Factions.Contains(Faction); } // Regenerate the producibles and tech tree state @@ -236,6 +245,25 @@ namespace OpenRA.Mods.Common.Traits } protected virtual void Tick(Actor self) + { + // PERF: Avoid LINQ when checking whether all production traits are disabled/paused + var anyEnabledProduction = false; + var anyUnpausedProduction = false; + foreach (var p in productionTraits) + { + anyEnabledProduction |= !p.IsTraitDisabled; + anyUnpausedProduction |= !p.IsTraitPaused; + } + + if (!anyEnabledProduction) + ClearQueue(); + + Enabled = IsValidFaction && anyEnabledProduction; + + TickInner(self, !anyUnpausedProduction); + } + + protected virtual void TickInner(Actor self, bool allProductionPaused) { while (queue.Count > 0 && BuildableItems().All(b => b.Name != queue[0].Item)) { @@ -244,7 +272,7 @@ namespace OpenRA.Mods.Common.Traits FinishProduction(); } - if (queue.Count > 0) + if (queue.Count > 0 && !allProductionPaused) queue[0].Tick(playerResources); } @@ -367,16 +395,19 @@ namespace OpenRA.Mods.Common.Traits // Returns the actor/trait that is most likely (but not necessarily guaranteed) to produce something in this queue public virtual TraitPair MostLikelyProducer() { - var trait = self.TraitsImplementing().FirstOrDefault(p => p.Info.Produces.Contains(Info.Type)); - return new TraitPair(self, trait); + var traits = productionTraits.Where(p => !p.IsTraitDisabled && p.Info.Produces.Contains(Info.Type)); + var unpaused = traits.FirstOrDefault(a => !a.IsTraitPaused); + return new TraitPair(self, unpaused != null ? unpaused : traits.FirstOrDefault()); } // Builds a unit from the actor that holds this queue (1 queue per building) // Returns false if the unit can't be built protected virtual bool BuildUnit(ActorInfo unit) { - // Cannot produce if I'm dead - if (!self.IsInWorld || self.IsDead) + var mostLikelyProducerTrait = MostLikelyProducer().Trait; + + // Cannot produce if I'm dead or trait is disabled + if (!self.IsInWorld || self.IsDead || mostLikelyProducerTrait == null) { CancelProduction(unit.Name, 1); return false; @@ -388,8 +419,7 @@ namespace OpenRA.Mods.Common.Traits new FactionInit(BuildableInfo.GetInitialFaction(unit, Faction)) }; - var sp = self.TraitsImplementing().FirstOrDefault(p => p.Info.Produces.Contains(Info.Type)); - if (sp != null && !self.IsDisabled() && sp.Produce(self, unit, developerMode.AllTech ? null : Info.Type, inits)) + if (!mostLikelyProducerTrait.IsTraitPaused && mostLikelyProducerTrait.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 d924de9743..3e6c89d993 100644 --- a/OpenRA.Mods.Common/Traits/Production.cs +++ b/OpenRA.Mods.Common/Traits/Production.cs @@ -19,27 +19,26 @@ using OpenRA.Traits; namespace OpenRA.Mods.Common.Traits { [Desc("This unit has access to build queues.")] - public class ProductionInfo : ITraitInfo + public class ProductionInfo : PausableConditionalTraitInfo { [FieldLoader.Require] [Desc("e.g. Infantry, Vehicles, Aircraft, Buildings")] public readonly string[] Produces = { }; - public virtual object Create(ActorInitializer init) { return new Production(init, this); } + public override object Create(ActorInitializer init) { return new Production(init, this); } } - public class Production : INotifyCreated + public class Production : PausableConditionalTrait, INotifyCreated { readonly Lazy rp; - public readonly ProductionInfo Info; public string Faction { get; private set; } Building building; public Production(ActorInitializer init, ProductionInfo info) + : base(info) { - Info = info; rp = Exts.Lazy(() => init.Self.IsDead ? null : init.Self.TraitOrDefault()); Faction = init.Contains() ? init.Get() : init.Self.Owner.Faction.InternalName; } @@ -132,7 +131,7 @@ namespace OpenRA.Mods.Common.Traits public virtual bool Produce(Actor self, ActorInfo producee, string productionType, TypeDictionary inits) { - if (Reservable.IsReserved(self) || (building != null && building.Locked)) + if (IsTraitDisabled || IsTraitPaused || Reservable.IsReserved(self) || (building != null && building.Locked)) return false; // Pick a spawn/exit point pair diff --git a/OpenRA.Mods.Common/Traits/ProductionFromMapEdge.cs b/OpenRA.Mods.Common/Traits/ProductionFromMapEdge.cs index c167c5179c..98dda716c8 100644 --- a/OpenRA.Mods.Common/Traits/ProductionFromMapEdge.cs +++ b/OpenRA.Mods.Common/Traits/ProductionFromMapEdge.cs @@ -42,6 +42,9 @@ namespace OpenRA.Mods.Common.Traits public override bool Produce(Actor self, ActorInfo producee, string productionType, TypeDictionary inits) { + if (IsTraitDisabled || IsTraitPaused) + return false; + var aircraftInfo = producee.TraitInfoOrDefault(); var mobileInfo = producee.TraitInfoOrDefault(); diff --git a/OpenRA.Mods.Common/Traits/ProductionParadrop.cs b/OpenRA.Mods.Common/Traits/ProductionParadrop.cs index 7d08b051d7..17ed5cd10f 100644 --- a/OpenRA.Mods.Common/Traits/ProductionParadrop.cs +++ b/OpenRA.Mods.Common/Traits/ProductionParadrop.cs @@ -45,6 +45,9 @@ namespace OpenRA.Mods.Common.Traits public override bool Produce(Actor self, ActorInfo producee, string productionType, TypeDictionary inits) { + if (IsTraitDisabled || IsTraitPaused) + return false; + var owner = self.Owner; var exit = SelectExit(self, producee, productionType); diff --git a/mods/ts/rules/gdi-structures.yaml b/mods/ts/rules/gdi-structures.yaml index 93a1530ab5..c375cb1be1 100644 --- a/mods/ts/rules/gdi-structures.yaml +++ b/mods/ts/rules/gdi-structures.yaml @@ -100,6 +100,7 @@ GAPILE: ExitsDebugOverlay: Production: Produces: Infantry + PauseOnCondition: empdisable PrimaryBuilding: PrimaryCondition: primary ProductionBar: @@ -159,6 +160,7 @@ GAWEAP: ExitsDebugOverlay: Production: Produces: Vehicle + PauseOnCondition: empdisable PrimaryBuilding: PrimaryCondition: primary ProductionBar: @@ -213,6 +215,7 @@ GAHPAD: IsPlayerPalette: false Production: Produces: Air + PauseOnCondition: empdisable PrimaryBuilding: PrimaryCondition: primary Reservable: @@ -442,6 +445,7 @@ GAPLUG: ChargeTime: 720 Production: Produces: HunterSeeker + PauseOnCondition: empdisable Exit@1: ExitsDebugOverlay: SupportPowerChargeBar: diff --git a/mods/ts/rules/nod-structures.yaml b/mods/ts/rules/nod-structures.yaml index fc67482561..b8c3068934 100644 --- a/mods/ts/rules/nod-structures.yaml +++ b/mods/ts/rules/nod-structures.yaml @@ -114,6 +114,7 @@ NAHAND: IsPlayerPalette: false Production: Produces: Infantry + PauseOnCondition: empdisable PrimaryBuilding: PrimaryCondition: primary ProductionBar: @@ -171,6 +172,7 @@ NAWEAP: ExitsDebugOverlay: Production: Produces: Vehicle + PauseOnCondition: empdisable PrimaryBuilding: PrimaryCondition: primary ProductionBar: @@ -221,6 +223,7 @@ NAHPAD: IsPlayerPalette: false Production: Produces: Air + PauseOnCondition: empdisable PrimaryBuilding: PrimaryCondition: primary Reservable: @@ -378,5 +381,6 @@ NATMPL: ChargeTime: 720 Production: Produces: HunterSeeker + PauseOnCondition: empdisable Exit@1: ExitsDebugOverlay: diff --git a/mods/ts/rules/shared-structures.yaml b/mods/ts/rules/shared-structures.yaml index 501891b457..b86f5b0ef7 100644 --- a/mods/ts/rules/shared-structures.yaml +++ b/mods/ts/rules/shared-structures.yaml @@ -19,6 +19,7 @@ GACNST: MaxHeightDelta: 3 Production: Produces: Building,Defense + PauseOnCondition: empdisable Valued: Cost: 2500 Tooltip: