From b59ae476e40fbb8072cfda54c6b6f2c758694f52 Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Mon, 6 May 2019 09:56:39 +0000 Subject: [PATCH] Add PlaceBuildingVariant trait. --- .../Orders/PlaceBuildingOrderGenerator.cs | 173 ++++++++++++------ .../Traits/Buildings/PlaceBuildingVariants.cs | 28 +++ .../Traits/Player/PlaceBuilding.cs | 25 ++- 3 files changed, 164 insertions(+), 62 deletions(-) create mode 100644 OpenRA.Mods.Common/Traits/Buildings/PlaceBuildingVariants.cs diff --git a/OpenRA.Mods.Common/Orders/PlaceBuildingOrderGenerator.cs b/OpenRA.Mods.Common/Orders/PlaceBuildingOrderGenerator.cs index 99eea191c9..c857b96263 100644 --- a/OpenRA.Mods.Common/Orders/PlaceBuildingOrderGenerator.cs +++ b/OpenRA.Mods.Common/Orders/PlaceBuildingOrderGenerator.cs @@ -36,15 +36,58 @@ namespace OpenRA.Mods.Common.Orders IEnumerable Render(WorldRenderer wr, CPos topLeft, Dictionary footprint); } - public class PlaceBuildingOrderGenerator : OrderGenerator + public class PlaceBuildingOrderGenerator : IOrderGenerator { + class VariantWrapper + { + public readonly ActorInfo ActorInfo; + public readonly BuildingInfo BuildingInfo; + public readonly PlugInfo PlugInfo; + public readonly LineBuildInfo LineBuildInfo; + public readonly IPlaceBuildingPreview Preview; + + public VariantWrapper(WorldRenderer wr, ProductionQueue queue, ActorInfo ai) + { + ActorInfo = ai; + BuildingInfo = ActorInfo.TraitInfo(); + PlugInfo = ActorInfo.TraitInfoOrDefault(); + LineBuildInfo = ActorInfo.TraitInfoOrDefault(); + + var previewGeneratorInfo = ActorInfo.TraitInfoOrDefault(); + if (previewGeneratorInfo != null) + { + string faction; + var buildableInfo = ActorInfo.TraitInfoOrDefault(); + if (buildableInfo != null && buildableInfo.ForceFaction != null) + faction = buildableInfo.ForceFaction; + else + { + var mostLikelyProducer = queue.MostLikelyProducer(); + faction = mostLikelyProducer.Trait != null ? mostLikelyProducer.Trait.Faction : queue.Actor.Owner.Faction.InternalName; + } + + var td = new TypeDictionary() + { + new FactionInit(faction), + new OwnerInit(queue.Actor.Owner), + }; + + foreach (var api in ActorInfo.TraitInfos()) + foreach (var o in api.ActorPreviewInits(ActorInfo, ActorPreviewType.PlaceBuilding)) + td.Add(o); + + Preview = previewGeneratorInfo.CreatePreview(wr, ActorInfo, td); + } + } + } + readonly ProductionQueue queue; readonly PlaceBuildingInfo placeBuildingInfo; readonly BuildingInfluence buildingInfluence; + readonly ResourceLayer resourceLayer; readonly Viewport viewport; - readonly ActorInfo actorInfo; - readonly BuildingInfo buildingInfo; - readonly IPlaceBuildingPreview preview; + readonly VariantWrapper[] variants; + int variant; public PlaceBuildingOrderGenerator(ProductionQueue queue, string name, WorldRenderer worldRenderer) { @@ -52,37 +95,23 @@ namespace OpenRA.Mods.Common.Orders this.queue = queue; placeBuildingInfo = queue.Actor.Owner.PlayerActor.Info.TraitInfo(); buildingInfluence = world.WorldActor.Trait(); + resourceLayer = world.WorldActor.TraitOrDefault(); viewport = worldRenderer.Viewport; // Clear selection if using Left-Click Orders if (Game.Settings.Game.UseClassicMouseStyle) world.Selection.Clear(); - actorInfo = world.Map.Rules.Actors[name]; - buildingInfo = actorInfo.TraitInfo(); - - var previewGeneratorInfo = actorInfo.TraitInfoOrDefault(); - if (previewGeneratorInfo != null) + var variants = new List() { - var faction = actorInfo.TraitInfo().ForceFaction; - if (faction == null) - { - var mostLikelyProducer = queue.MostLikelyProducer(); - faction = mostLikelyProducer.Trait != null ? mostLikelyProducer.Trait.Faction : queue.Actor.Owner.Faction.InternalName; - } + new VariantWrapper(worldRenderer, queue, world.Map.Rules.Actors[name]) + }; - var td = new TypeDictionary() - { - new FactionInit(faction), - new OwnerInit(queue.Actor.Owner), - }; + foreach (var v in variants[0].ActorInfo.TraitInfos()) + foreach (var a in v.Actors) + variants.Add(new VariantWrapper(worldRenderer, queue, world.Map.Rules.Actors[a])); - foreach (var api in actorInfo.TraitInfos()) - foreach (var o in api.ActorPreviewInits(actorInfo, ActorPreviewType.PlaceBuilding)) - td.Add(o); - - preview = previewGeneratorInfo.CreatePreview(worldRenderer, actorInfo, td); - } + this.variants = variants.ToArray(); } PlaceBuildingCellType MakeCellType(bool valid, bool lineBuild = false) @@ -94,20 +123,25 @@ namespace OpenRA.Mods.Common.Orders return cell; } - protected override IEnumerable OrderInner(World world, CPos cell, int2 worldPixel, MouseInput mi) + public IEnumerable Order(World world, CPos cell, int2 worldPixel, MouseInput mi) { - if (mi.Button == MouseButton.Right) - world.CancelInputMode(); + if ((mi.Button == MouseButton.Left && mi.Event == MouseInputEvent.Down) || (mi.Button == MouseButton.Right && mi.Event == MouseInputEvent.Up)) + { + if (mi.Button == MouseButton.Right) + world.CancelInputMode(); - var ret = InnerOrder(world, cell, mi).ToArray(); + var ret = InnerOrder(world, cell, mi).ToArray(); - // If there was a successful placement order - if (ret.Any(o => o.OrderString == "PlaceBuilding" - || o.OrderString == "LineBuild" - || o.OrderString == "PlacePlug")) - world.CancelInputMode(); + // If there was a successful placement order + if (ret.Any(o => o.OrderString == "PlaceBuilding" + || o.OrderString == "LineBuild" + || o.OrderString == "PlacePlug")) + world.CancelInputMode(); - return ret; + return ret; + } + + return Enumerable.Empty(); } CPos TopLeft @@ -115,8 +149,8 @@ namespace OpenRA.Mods.Common.Orders get { var offsetPos = Viewport.LastMousePos; - if (preview != null) - offsetPos += preview.TopLeftScreenOffset; + if (variants[variant].Preview != null) + offsetPos += variants[variant].Preview.TopLeftScreenOffset; return viewport.ViewToWorld(offsetPos); } @@ -128,12 +162,15 @@ namespace OpenRA.Mods.Common.Orders yield break; var owner = queue.Actor.Owner; + var ai = variants[variant].ActorInfo; + var bi = variants[variant].BuildingInfo; + if (mi.Button == MouseButton.Left) { var orderType = "PlaceBuilding"; var topLeft = TopLeft; - var plugInfo = actorInfo.TraitInfoOrDefault(); + var plugInfo = ai.TraitInfoOrDefault(); if (plugInfo != null) { orderType = "PlacePlug"; @@ -145,8 +182,8 @@ namespace OpenRA.Mods.Common.Orders } else { - if (!world.CanPlaceBuilding(topLeft, actorInfo, buildingInfo, null) - || !buildingInfo.IsCloseEnoughToBase(world, owner, actorInfo, topLeft)) + if (!world.CanPlaceBuilding(topLeft, ai, bi, null) + || !bi.IsCloseEnoughToBase(world, owner, ai, topLeft)) { foreach (var order in ClearBlockersOrders(world, topLeft)) yield return order; @@ -155,29 +192,31 @@ namespace OpenRA.Mods.Common.Orders yield break; } - if (actorInfo.HasTraitInfo() && !mi.Modifiers.HasModifier(Modifiers.Shift)) + if (ai.HasTraitInfo() && !mi.Modifiers.HasModifier(Modifiers.Shift)) orderType = "LineBuild"; } yield return new Order(orderType, owner.PlayerActor, Target.FromCell(world, topLeft), false) { // Building to place - TargetString = actorInfo.Name, + TargetString = variants[0].ActorInfo.Name, + + // Pack the actor to associate the placement with and the alternate actor flag together + ExtraLocation = new CPos((int)queue.Actor.ActorID, variant), - // Actor to associate the placement with - ExtraData = queue.Actor.ActorID, SuppressVisualFeedback = true }; } } - protected override void Tick(World world) + void IOrderGenerator.Tick(World world) { - if (queue.AllQueued().All(i => !i.Done || i.Item != actorInfo.Name)) + if (queue.AllQueued().All(i => !i.Done || i.Item != variants[0].ActorInfo.Name)) world.CancelInputMode(); - if (preview != null) - preview.Tick(); + foreach (var v in variants) + if (v.Preview != null) + v.Preview.Tick(); } bool AcceptsPlug(CPos cell, PlugInfo plug) @@ -190,12 +229,18 @@ namespace OpenRA.Mods.Common.Orders return host.TraitsImplementing().Any(p => location + p.Info.Offset == cell && p.AcceptsPlug(host, plug.Type)); } - protected override IEnumerable Render(WorldRenderer wr, World world) { yield break; } - protected override IEnumerable RenderAboveShroud(WorldRenderer wr, World world) + IEnumerable IOrderGenerator.Render(WorldRenderer wr, World world) { yield break; } + IEnumerable IOrderGenerator.RenderAboveShroud(WorldRenderer wr, World world) { var topLeft = TopLeft; var footprint = new Dictionary(); - var plugInfo = actorInfo.TraitInfoOrDefault(); + var activeVariant = variants[variant]; + var actorInfo = activeVariant.ActorInfo; + var buildingInfo = activeVariant.BuildingInfo; + var plugInfo = activeVariant.PlugInfo; + var lineBuildInfo = activeVariant.LineBuildInfo; + var preview = activeVariant.Preview; + if (plugInfo != null) { if (buildingInfo.Dimensions.X != 1 || buildingInfo.Dimensions.Y != 1) @@ -203,7 +248,7 @@ namespace OpenRA.Mods.Common.Orders footprint.Add(topLeft, MakeCellType(AcceptsPlug(topLeft, plugInfo))); } - else if (actorInfo.HasTraitInfo()) + else if (lineBuildInfo != null) { // Linebuild for walls. if (buildingInfo.Dimensions.X != 1 || buildingInfo.Dimensions.Y != 1) @@ -225,20 +270,34 @@ namespace OpenRA.Mods.Common.Orders } else { - var res = world.WorldActor.TraitOrDefault(); var isCloseEnough = buildingInfo.IsCloseEnoughToBase(world, world.LocalPlayer, actorInfo, topLeft); foreach (var t in buildingInfo.Tiles(topLeft)) - footprint.Add(t, MakeCellType(isCloseEnough && world.IsCellBuildable(t, actorInfo, buildingInfo) && (res == null || res.GetResource(t) == null))); + footprint.Add(t, MakeCellType(isCloseEnough && world.IsCellBuildable(t, actorInfo, buildingInfo) && (resourceLayer == null || resourceLayer.GetResource(t) == null))); } return preview != null ? preview.Render(wr, topLeft, footprint) : Enumerable.Empty(); } - protected override string GetCursor(World world, CPos cell, int2 worldPixel, MouseInput mi) { return "default"; } + string IOrderGenerator.GetCursor(World world, CPos cell, int2 worldPixel, MouseInput mi) { return "default"; } + + bool IOrderGenerator.HandleKeyPress(KeyInput e) + { + if (variants.Length > 0 && placeBuildingInfo.ToggleVariantKey.IsActivatedBy(e)) + { + if (++variant >= variants.Length) + variant = 0; + + return true; + } + + return false; + } + + void IOrderGenerator.Deactivate() { } IEnumerable ClearBlockersOrders(World world, CPos topLeft) { - var allTiles = buildingInfo.Tiles(topLeft).ToArray(); + var allTiles = variants[variant].BuildingInfo.Tiles(topLeft).ToArray(); var neightborTiles = Util.ExpandFootprint(allTiles, true).Except(allTiles) .Where(world.Map.Contains).ToList(); diff --git a/OpenRA.Mods.Common/Traits/Buildings/PlaceBuildingVariants.cs b/OpenRA.Mods.Common/Traits/Buildings/PlaceBuildingVariants.cs new file mode 100644 index 0000000000..0f875dcf30 --- /dev/null +++ b/OpenRA.Mods.Common/Traits/Buildings/PlaceBuildingVariants.cs @@ -0,0 +1,28 @@ +#region Copyright & License Information +/* + * Copyright 2007-2019 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.Common.Traits +{ + [Desc("Place a different building when PlaceBuilding's ToggleVariantKey hotkey is pressed while the PlaceBuildingOrderGenerator is active.")] + public class PlaceBuildingVariantsInfo : TraitInfo, Requires, Requires + { + [FieldLoader.Require] + [ActorReference(typeof(BuildingInfo))] + [Desc("Variant actors that can be cycled between when placing a structure.")] + public readonly string[] Actors = null; + + public override object Create(ActorInitializer init) { return new PlaceBuildingVariants(); } + } + + public class PlaceBuildingVariants { } +} diff --git a/OpenRA.Mods.Common/Traits/Player/PlaceBuilding.cs b/OpenRA.Mods.Common/Traits/Player/PlaceBuilding.cs index f59aeac706..412aa1e627 100644 --- a/OpenRA.Mods.Common/Traits/Player/PlaceBuilding.cs +++ b/OpenRA.Mods.Common/Traits/Player/PlaceBuilding.cs @@ -31,6 +31,9 @@ namespace OpenRA.Mods.Common.Traits [NotificationReference("Speech")] public readonly string CannotPlaceNotification = null; + [Desc("Hotkey to toggle between PlaceBuildingVariants when placing a structure.")] + public HotkeyReference ToggleVariantKey = new HotkeyReference(); + public object Create(ActorInitializer init) { return new PlaceBuilding(this); } } @@ -56,7 +59,7 @@ namespace OpenRA.Mods.Common.Traits self.World.AddFrameEndTask(w => { var prevItems = GetNumBuildables(self.Owner); - var targetActor = w.GetActorById(order.ExtraData); + var targetActor = w.GetActorById((uint)order.ExtraLocation.X); var targetLocation = w.Map.CellContaining(order.Target.CenterPosition); if (targetActor == null || targetActor.IsDead) @@ -75,6 +78,18 @@ namespace OpenRA.Mods.Common.Traits if (item == null) return; + // Override with the alternate actor + if (order.ExtraLocation.Y > 0) + { + var variant = actorInfo.TraitInfos() + .SelectMany(p => p.Actors) + .Skip(order.ExtraLocation.Y - 1) + .FirstOrDefault(); + + if (variant != null) + actorInfo = self.World.Map.Rules.Actors[variant]; + } + var producer = queue.MostLikelyProducer(); var faction = producer.Trait != null ? producer.Trait.Faction : self.Owner.Faction.InternalName; var buildingInfo = actorInfo.TraitInfo(); @@ -86,7 +101,7 @@ namespace OpenRA.Mods.Common.Traits if (os == "LineBuild") { // Build the parent actor first - var placed = w.CreateActor(order.TargetString, new TypeDictionary + var placed = w.CreateActor(actorInfo.Name, new TypeDictionary { new LocationInit(targetLocation), new OwnerInit(order.Player), @@ -100,14 +115,14 @@ namespace OpenRA.Mods.Common.Traits // Build the connection segments var segmentType = actorInfo.TraitInfo().SegmentType; if (string.IsNullOrEmpty(segmentType)) - segmentType = order.TargetString; + segmentType = actorInfo.Name; foreach (var t in BuildingUtils.GetLineBuildCells(w, targetLocation, actorInfo, buildingInfo)) { if (t.First == targetLocation) continue; - w.CreateActor(t.First == targetLocation ? order.TargetString : segmentType, new TypeDictionary + w.CreateActor(t.First == targetLocation ? actorInfo.Name : segmentType, new TypeDictionary { new LocationInit(t.First), new OwnerInit(order.Player), @@ -157,7 +172,7 @@ namespace OpenRA.Mods.Common.Traits } } - var building = w.CreateActor(order.TargetString, new TypeDictionary + var building = w.CreateActor(actorInfo.Name, new TypeDictionary { new LocationInit(targetLocation), new OwnerInit(order.Player),