diff --git a/OpenRA.Mods.RA/Buildings/BaseProvider.cs b/OpenRA.Mods.RA/Buildings/BaseProvider.cs new file mode 100755 index 0000000000..8278d74744 --- /dev/null +++ b/OpenRA.Mods.RA/Buildings/BaseProvider.cs @@ -0,0 +1,88 @@ +#region Copyright & License Information +/* + * Copyright 2007-2011 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; +using System.Drawing; +using OpenRA.Graphics; +using OpenRA.Traits; + +namespace OpenRA.Mods.RA.Buildings +{ + public class BaseProviderInfo : ITraitInfo + { + public readonly float Range = 10; + public readonly int Cooldown = 0; + public readonly int InitialDelay = 0; + + public object Create(ActorInitializer init) { return new BaseProvider(init.self, this); } + } + + public class BaseProvider : ITick, IPreRenderSelection, ISelectionBar + { + public readonly BaseProviderInfo Info; + DeveloperMode devMode; + Actor self; + int total; + int progress; + + public BaseProvider(Actor self, BaseProviderInfo info) + { + Info = info; + this.self = self; + devMode = self.Owner.PlayerActor.Trait(); + progress = total = info.InitialDelay; + } + + public void Tick(Actor self) + { + if (progress > 0) + progress--; + } + + public void BeginCooldown() + { + progress = total = Info.Cooldown; + } + + public bool Ready() + { + return devMode.FastBuild || progress == 0; + } + + // Range circle + public void RenderBeforeWorld(WorldRenderer wr, Actor self) + { + // Visible to player and allies + if (self.World.RenderedPlayer != null && self.Owner.Stances[self.World.RenderedPlayer] != Stance.Ally) + return; + + wr.DrawRangeCircleWithContrast( + Color.FromArgb(128, Ready() ? Color.White : Color.Red), + self.CenterLocation.ToFloat2(), Info.Range, + Color.FromArgb(96, Color.Black), 1); + } + + // Selection bar + public float GetValue() + { + // Visible to player and allies + if (self.World.RenderedPlayer != null && self.Owner.Stances[self.World.RenderedPlayer] != Stance.Ally) + return 0f; + + // Ready or delay disabled + if (progress == 0 || total == 0 || devMode.FastBuild) + return 0f; + + return (float)progress / total; + } + + public Color GetColor() { return Color.Purple; } + } +} diff --git a/OpenRA.Mods.RA/Buildings/Building.cs b/OpenRA.Mods.RA/Buildings/Building.cs index 572f836d0d..f4904e8b82 100755 --- a/OpenRA.Mods.RA/Buildings/Building.cs +++ b/OpenRA.Mods.RA/Buildings/Building.cs @@ -31,17 +31,40 @@ namespace OpenRA.Mods.RA.Buildings [Desc("x means space it blocks, _ is a part that is passable by actors.")] public readonly string Footprint = "x"; public readonly int2 Dimensions = new int2(1, 1); + public readonly bool RequiresBaseProvider = false; public readonly string[] BuildSounds = {"placbldg.aud", "build5.aud"}; public readonly string[] SellSounds = {"cashturn.aud"}; public object Create(ActorInitializer init) { return new Building(init, this); } + public PPos CenterLocation(CPos topLeft) + { + return (PPos)((2 * topLeft.ToInt2() + Dimensions) * Game.CellSize / 2); + } + + bool HasBaseProvider(World world, Player p, CPos topLeft) + { + var center = CenterLocation(topLeft); + foreach (var bp in world.ActorsWithTrait()) + { + if (bp.Actor.Owner.Stances[p] != Stance.Ally || !bp.Trait.Ready()) + continue; + + if (Combat.IsInRange(center, bp.Trait.Info.Range, bp.Actor.CenterLocation)) + return true; + } + return false; + } + public bool IsCloseEnoughToBase(World world, Player p, string buildingName, CPos topLeft) { if (p.PlayerActor.Trait().BuildAnywhere) return true; + if (RequiresBaseProvider && !HasBaseProvider(world, p, topLeft)) + return false; + var buildingMaxBounds = (CVec)Dimensions; if (Rules.Info[buildingName].Traits.Contains()) buildingMaxBounds += new CVec(0, 1); @@ -103,7 +126,7 @@ namespace OpenRA.Mods.RA.Buildings occupiedCells = FootprintUtils.UnpathableTiles( self.Info.Name, Info, TopLeft ) .Select(c => Pair.New(c, SubCell.FullCell)).ToArray(); - pxPosition = (PPos) (( 2 * topLeft.ToInt2() + Info.Dimensions ) * Game.CellSize / 2); + pxPosition = Info.CenterLocation(topLeft); } public int GetPowerUsage() diff --git a/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj b/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj index db3abb3163..4ee2c5f1d8 100644 --- a/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj +++ b/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj @@ -422,6 +422,7 @@ + diff --git a/OpenRA.Mods.RA/Player/PlaceBuilding.cs b/OpenRA.Mods.RA/Player/PlaceBuilding.cs index 4405d6a8d8..9adbef1df0 100755 --- a/OpenRA.Mods.RA/Player/PlaceBuilding.cs +++ b/OpenRA.Mods.RA/Player/PlaceBuilding.cs @@ -80,6 +80,22 @@ namespace OpenRA.Mods.RA queue.FinishProduction(); + if (buildingInfo.RequiresBaseProvider) + { + var center = buildingInfo.CenterLocation(order.TargetLocation); + foreach (var bp in w.ActorsWithTrait()) + { + if (bp.Actor.Owner.Stances[self.Owner] != Stance.Ally || !bp.Trait.Ready()) + continue; + + if (Combat.IsInRange(center, bp.Trait.Info.Range, bp.Actor.CenterLocation)) + { + bp.Trait.BeginCooldown(); + break; + } + } + } + if (GetNumBuildables(self.Owner) > prevItems) w.Add(new DelayedAction(10, () => Sound.PlayNotification(order.Player, "Speech", "NewOptions", order.Player.Country.Race))); diff --git a/OpenRA.Mods.RA/Render/RenderBuilding.cs b/OpenRA.Mods.RA/Render/RenderBuilding.cs index ce7711bd7c..f1e969fe16 100755 --- a/OpenRA.Mods.RA/Render/RenderBuilding.cs +++ b/OpenRA.Mods.RA/Render/RenderBuilding.cs @@ -19,7 +19,7 @@ using OpenRA.Mods.RA.Activities; namespace OpenRA.Mods.RA.Render { - public class RenderBuildingInfo : RenderSimpleInfo + public class RenderBuildingInfo : RenderSimpleInfo, Requires, IPlaceBuildingDecoration { public readonly bool HasMakeAnimation = true; public readonly float2 Origin = float2.Zero; @@ -30,6 +30,15 @@ namespace OpenRA.Mods.RA.Render return base.RenderPreview(building, pr) .Select(a => a.WithPos(a.Pos + building.Traits.Get().Origin)); } + + public void Render(WorldRenderer wr, World w, ActorInfo ai, PPos centerLocation) + { + if (!ai.Traits.Get().RequiresBaseProvider) + return; + + foreach (var a in w.ActorsWithTrait()) + a.Trait.RenderBeforeWorld(wr, a.Actor); + } } public class RenderBuilding : RenderSimple, INotifyDamageStateChanged, IRenderModifier diff --git a/mods/cnc/rules/defaults.yaml b/mods/cnc/rules/defaults.yaml index 9a3ef74237..b78b77373e 100644 --- a/mods/cnc/rules/defaults.yaml +++ b/mods/cnc/rules/defaults.yaml @@ -232,6 +232,7 @@ RepairPercent: 40 RepairStep: 14 Building: + RequiresBaseProvider: true Dimensions: 1,1 Footprint: x BuildSounds: constru2.aud, hvydoor1.aud diff --git a/mods/cnc/rules/structures.yaml b/mods/cnc/rules/structures.yaml index c85b90e388..38302258fa 100644 --- a/mods/cnc/rules/structures.yaml +++ b/mods/cnc/rules/structures.yaml @@ -45,6 +45,8 @@ FACT: ReadyAudio: ConstructionComplete BaseBuilding: ProductionBar: + BaseProvider: + Cooldown: 200 NUKE: Inherits: ^Building