diff --git a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj index b4ca63d1aa..3bb01cbe2c 100644 --- a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj +++ b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj @@ -619,6 +619,8 @@ + + diff --git a/OpenRA.Mods.Common/Orders/PlaceBuildingOrderGenerator.cs b/OpenRA.Mods.Common/Orders/PlaceBuildingOrderGenerator.cs index 1de95bee5c..41faa7cf3f 100644 --- a/OpenRA.Mods.Common/Orders/PlaceBuildingOrderGenerator.cs +++ b/OpenRA.Mods.Common/Orders/PlaceBuildingOrderGenerator.cs @@ -24,6 +24,7 @@ namespace OpenRA.Mods.Common.Orders readonly string building; readonly BuildingInfo buildingInfo; readonly PlaceBuildingInfo placeBuildingInfo; + readonly BuildingInfluence buildingInfluence; readonly string race; readonly Sprite buildOk; readonly Sprite buildBlocked; @@ -52,6 +53,8 @@ namespace OpenRA.Mods.Common.Orders buildOk = map.SequenceProvider.GetSequence("overlay", "build-valid-{0}".F(tileset)).GetSprite(0); buildBlocked = map.SequenceProvider.GetSequence("overlay", "build-invalid").GetSprite(0); + + buildingInfluence = producer.World.WorldActor.Trait(); } public IEnumerable Order(World world, CPos xy, MouseInput mi) @@ -73,16 +76,33 @@ namespace OpenRA.Mods.Common.Orders if (mi.Button == MouseButton.Left) { + var orderType = "PlaceBuilding"; var topLeft = xy - FootprintUtils.AdjustForBuildingSize(buildingInfo); - if (!world.CanPlaceBuilding(building, buildingInfo, topLeft, null) - || !buildingInfo.IsCloseEnoughToBase(world, producer.Owner, building, topLeft)) + + var plugInfo = world.Map.Rules.Actors[building].Traits.GetOrDefault(); + if (plugInfo != null) { - Sound.PlayNotification(world.Map.Rules, producer.Owner, "Speech", "BuildingCannotPlaceAudio", producer.Owner.Country.Race); - yield break; + orderType = "PlacePlug"; + if (!AcceptsPlug(topLeft, plugInfo)) + { + Sound.PlayNotification(world.Map.Rules, producer.Owner, "Speech", "BuildingCannotPlaceAudio", producer.Owner.Country.Race); + yield break; + } + } + else + { + if (!world.CanPlaceBuilding(building, buildingInfo, topLeft, null) + || !buildingInfo.IsCloseEnoughToBase(world, producer.Owner, building, topLeft)) + { + Sound.PlayNotification(world.Map.Rules, producer.Owner, "Speech", "BuildingCannotPlaceAudio", producer.Owner.Country.Race); + yield break; + } + + if (world.Map.Rules.Actors[building].Traits.Contains()) + orderType = "LineBuild"; } - var isLineBuild = world.Map.Rules.Actors[building].Traits.Contains(); - yield return new Order(isLineBuild ? "LineBuild" : "PlaceBuilding", producer.Owner.PlayerActor, false) + yield return new Order(orderType, producer.Owner.PlayerActor, false) { TargetLocation = topLeft, TargetActor = producer, @@ -101,6 +121,16 @@ namespace OpenRA.Mods.Common.Orders p.Tick(); } + bool AcceptsPlug(CPos cell, PlugInfo plug) + { + var host = buildingInfluence.GetBuildingAt(cell); + if (host == null) + return false; + + var location = host.Location; + return host.TraitsImplementing().Any(p => location + p.Info.Offset == cell && p.AcceptsPlug(host, plug.Type)); + } + public IEnumerable Render(WorldRenderer wr, World world) { yield break; } public IEnumerable RenderAfterWorld(WorldRenderer wr, World world) { @@ -116,10 +146,17 @@ namespace OpenRA.Mods.Common.Orders var cells = new Dictionary(); - // Linebuild for walls. - // Requires a 1x1 footprint - if (rules.Actors[building].Traits.Contains()) + var plugInfo = rules.Actors[building].Traits.GetOrDefault(); + if (plugInfo != null) { + if (buildingInfo.Dimensions.X != 1 || buildingInfo.Dimensions.Y != 1) + throw new InvalidOperationException("Plug requires a 1x1 sized Building"); + + cells.Add(topLeft, AcceptsPlug(topLeft, plugInfo)); + } + else if (rules.Actors[building].Traits.Contains()) + { + // Linebuild for walls. if (buildingInfo.Dimensions.X != 1 || buildingInfo.Dimensions.Y != 1) throw new InvalidOperationException("LineBuild requires a 1x1 sized Building"); diff --git a/OpenRA.Mods.Common/Traits/Player/PlaceBuilding.cs b/OpenRA.Mods.Common/Traits/Player/PlaceBuilding.cs index 91d118f0de..2c435d9196 100644 --- a/OpenRA.Mods.Common/Traits/Player/PlaceBuilding.cs +++ b/OpenRA.Mods.Common/Traits/Player/PlaceBuilding.cs @@ -26,7 +26,7 @@ namespace OpenRA.Mods.Common.Traits { public void ResolveOrder(Actor self, Order order) { - if (order.OrderString == "PlaceBuilding" || order.OrderString == "LineBuild") + if (order.OrderString == "PlaceBuilding" || order.OrderString == "LineBuild" || order.OrderString == "PlacePlug") { self.World.AddFrameEndTask(w => { @@ -69,6 +69,27 @@ namespace OpenRA.Mods.Common.Traits playSounds = false; } } + else if (order.OrderString == "PlacePlug") + { + var host = self.World.WorldActor.Trait().GetBuildingAt(order.TargetLocation); + if (host == null) + return; + + var plugInfo = unit.Traits.GetOrDefault(); + if (plugInfo == null) + return; + + var location = host.Location; + var pluggable = host.TraitsImplementing() + .FirstOrDefault(p => location + p.Info.Offset == order.TargetLocation && p.AcceptsPlug(host, plugInfo.Type)); + + if (pluggable == null) + return; + + pluggable.EnablePlug(host, plugInfo.Type); + foreach (var s in buildingInfo.BuildSounds) + Sound.PlayToPlayer(order.Player, s, host.CenterPosition); + } else { if (!self.World.CanPlaceBuilding(order.TargetString, buildingInfo, order.TargetLocation, null) diff --git a/OpenRA.Mods.Common/Traits/Plug.cs b/OpenRA.Mods.Common/Traits/Plug.cs new file mode 100644 index 0000000000..625d8b4389 --- /dev/null +++ b/OpenRA.Mods.Common/Traits/Plug.cs @@ -0,0 +1,24 @@ +#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.Collections.Generic; +using System.Linq; +using OpenRA.Traits; + +namespace OpenRA.Mods.Common.Traits +{ + public class PlugInfo : TraitInfo + { + [Desc("Plug type (matched against Upgrades in Pluggable)")] + public readonly string Type = null; + } + + public class Plug { } +} diff --git a/OpenRA.Mods.Common/Traits/Pluggable.cs b/OpenRA.Mods.Common/Traits/Pluggable.cs new file mode 100644 index 0000000000..34804b99d3 --- /dev/null +++ b/OpenRA.Mods.Common/Traits/Pluggable.cs @@ -0,0 +1,79 @@ +#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.Collections.Generic; +using System.Linq; +using OpenRA.Traits; + +namespace OpenRA.Mods.Common.Traits +{ + public class PluggableInfo : ITraitInfo, Requires + { + [Desc("Footprint cell offset where a plug can be placed.")] + public readonly CVec Offset = CVec.Zero; + + [FieldLoader.LoadUsing("LoadUpgrades")] + [Desc("Upgrades to grant for each accepted plug type.")] + public readonly Dictionary Upgrades = null; + + static object LoadUpgrades(MiniYaml y) + { + MiniYaml upgrades; + + if (!y.ToDictionary().TryGetValue("Upgrades", out upgrades)) + return new Dictionary(); + + return upgrades.Nodes.ToDictionary( + kv => kv.Key, + kv => FieldLoader.GetValue("(value)", kv.Value.Value)); + } + + public object Create(ActorInitializer init) { return new Pluggable(init.Self, this); } + } + + public class Pluggable + { + public readonly PluggableInfo Info; + readonly UpgradeManager upgradeManager; + string active; + + public Pluggable(Actor self, PluggableInfo info) + { + Info = info; + upgradeManager = self.Trait(); + } + + public bool AcceptsPlug(Actor self, string type) + { + return active == null && Info.Upgrades.ContainsKey(type); + } + + public void EnablePlug(Actor self, string type) + { + string[] upgrades; + if (!Info.Upgrades.TryGetValue(type, out upgrades)) + return; + + foreach (var u in upgrades) + upgradeManager.GrantUpgrade(self, u, this); + + active = type; + } + + public void DisablePlug(Actor self, string type) + { + if (type != active) + return; + + foreach (var u in Info.Upgrades[type]) + upgradeManager.RevokeUpgrade(self, u, this); + } + } +}