diff --git a/OpenRA.Mods.Cnc/OpenRA.Mods.Cnc.csproj b/OpenRA.Mods.Cnc/OpenRA.Mods.Cnc.csproj index def4794ec6..f4e04c13f8 100644 --- a/OpenRA.Mods.Cnc/OpenRA.Mods.Cnc.csproj +++ b/OpenRA.Mods.Cnc/OpenRA.Mods.Cnc.csproj @@ -92,6 +92,8 @@ + + diff --git a/OpenRA.Mods.Cnc/Widgets/Logic/CncIngameChromeLogic.cs b/OpenRA.Mods.Cnc/Widgets/Logic/CncIngameChromeLogic.cs index 124d46d08b..362cbaeddb 100644 --- a/OpenRA.Mods.Cnc/Widgets/Logic/CncIngameChromeLogic.cs +++ b/OpenRA.Mods.Cnc/Widgets/Logic/CncIngameChromeLogic.cs @@ -12,6 +12,7 @@ using System.Drawing; using System.Linq; using OpenRA.Traits; using OpenRA.Widgets; +using OpenRA.Mods.RA; namespace OpenRA.Mods.Cnc.Widgets.Logic { @@ -33,6 +34,13 @@ namespace OpenRA.Mods.Cnc.Widgets.Logic Game.BeforeGameStart -= UnregisterEvents; } + ProductionQueue QueueForType(World world, string type) + { + return world.ActorsWithTrait() + .Where(p => p.Actor.Owner == world.LocalPlayer) + .Select(p => p.Trait).FirstOrDefault(p => p.Info.Type == type); + } + [ObjectCreator.UseCtor] public CncIngameChromeLogic([ObjectCreator.Param] Widget widget, [ObjectCreator.Param] World world ) @@ -54,6 +62,15 @@ namespace OpenRA.Mods.Cnc.Widgets.Logic var playerResources = world.LocalPlayer.PlayerActor.Trait(); sidebarRoot.GetWidget("CASH_DISPLAY").GetText = () => "${0}".F(playerResources.DisplayCash + playerResources.DisplayOre); + + var buildPalette = playerWidgets.GetWidget("PRODUCTION_PALETTE"); + var queueTabs = playerWidgets.GetWidget("PRODUCTION_TABS"); + var queueTypes = sidebarRoot.GetWidget("PRODUCTION_TYPES"); + queueTypes.GetWidget("BUILDING").OnClick = () => queueTabs.QueueType = "Building"; + queueTypes.GetWidget("DEFENSE").OnClick = () => queueTabs.QueueType = "Defense"; + queueTypes.GetWidget("INFANTRY").OnClick = () => queueTabs.QueueType = "Infantry"; + queueTypes.GetWidget("VEHICLE").OnClick = () => queueTabs.QueueType = "Vehicle"; + queueTypes.GetWidget("AIRCRAFT").OnClick = () => queueTabs.QueueType = "Aircraft"; } ingameRoot.GetWidget("OPTIONS_BUTTON").OnClick = () => { diff --git a/OpenRA.Mods.Cnc/Widgets/ProductionPaletteWidget.cs b/OpenRA.Mods.Cnc/Widgets/ProductionPaletteWidget.cs new file mode 100755 index 0000000000..3263ff4d4c --- /dev/null +++ b/OpenRA.Mods.Cnc/Widgets/ProductionPaletteWidget.cs @@ -0,0 +1,323 @@ +#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.Collections.Generic; +using System.Drawing; +using System.Linq; +using OpenRA.FileFormats; +using OpenRA.Graphics; +using OpenRA.Mods.RA; +using OpenRA.Mods.RA.Buildings; +using OpenRA.Mods.RA.Orders; +using OpenRA.Traits; +using OpenRA.Widgets; + +namespace OpenRA.Mods.Cnc.Widgets +{ + class ProductionPaletteWidget : Widget + { + public readonly int Columns = 3; + public readonly string BuildPaletteOpen = "appear1.aud"; + public readonly string BuildPaletteClose = "appear1.aud"; + public readonly string TabClick = "button.aud"; + public ProductionQueue CurrentQueue = null; + + Dictionary iconSprites; + Animation cantBuild; + Animation ready; + Animation clock; + List>> buttons = new List>>(); + readonly WorldRenderer worldRenderer; + readonly World world; + [ObjectCreator.UseCtor] + public ProductionPaletteWidget( [ObjectCreator.Param] World world, [ObjectCreator.Param] WorldRenderer worldRenderer ) + { + this.world = world; + this.worldRenderer = worldRenderer; + + cantBuild = new Animation("clock"); + cantBuild.PlayFetchIndex("idle", () => 0); + ready = new Animation("pips"); + ready.PlayRepeating("ready"); + clock = new Animation("clock"); + + iconSprites = Rules.Info.Values + .Where(u => u.Traits.Contains() && u.Name[0] != '^') + .ToDictionary( + u => u.Name, + u => Game.modData.SpriteLoader.LoadAllSprites( + u.Traits.Get().Icon ?? (u.Name + "icon"))[0]); + } + + public override void Tick() + { + if (CurrentQueue != null && CurrentQueue.self.Destroyed) + CurrentQueue = null; + + base.Tick(); + } + + public override Rectangle EventBounds + { + get { return (buttons.Count == 0) ? Rectangle.Empty : buttons.Select(kv => kv.First).Aggregate(Rectangle.Union); } + } + + + // TODO: BuildPaletteWidget doesn't support delegate methods for mouse input + public override bool HandleMouseInput(MouseInput mi) + { + if (mi.Event != MouseInputEvent.Down) + return false; + + var action = buttons.Where(a => a.First.Contains(mi.Location)) + .Select(a => a.Second).FirstOrDefault(); + + action(mi); + return true; + } + + public override void DrawInner() + { + if (!IsVisible()) return; + buttons.Clear(); + + var x = 0; + var y = 0; + + if (CurrentQueue != null) + { + var buildableItems = CurrentQueue.BuildableItems().OrderBy(a => a.Traits.Get().BuildPaletteOrder); + var allBuildables = CurrentQueue.AllItems().OrderBy(a => a.Traits.Get().BuildPaletteOrder); + var overlayBits = new List>(); + + // Icons + string tooltipItem = null; + var isBuildingSomething = CurrentQueue.CurrentItem() != null; + foreach (var item in allBuildables) + { + var rect = new RectangleF(RenderOrigin.X + x * 64, RenderOrigin.Y + 48 * y, 64, 48); + var drawPos = new float2(rect.Location); + WidgetUtils.DrawSHP(iconSprites[item.Name], drawPos, worldRenderer); + + + if (rect.Contains(Viewport.LastMousePos)) + tooltipItem = item.Name; + + var overlayPos = drawPos + new float2((64 - ready.Image.size.X) / 2, 2); + + // Build progress + var firstOfThis = CurrentQueue.AllQueued().FirstOrDefault(a => a.Item == item.Name); + if (firstOfThis != null) + { + clock.PlayFetchIndex("idle", + () => (firstOfThis.TotalTime - firstOfThis.RemainingTime) + * (clock.CurrentSequence.Length - 1) / firstOfThis.TotalTime); + clock.Tick(); + WidgetUtils.DrawSHP(clock.Image, drawPos, worldRenderer); + + if (firstOfThis.Done) + { + ready.Play("ready"); + overlayBits.Add(Pair.New(ready.Image, overlayPos)); + } + else if (firstOfThis.Paused) + { + ready.Play("hold"); + overlayBits.Add(Pair.New(ready.Image, overlayPos)); + } + + var repeats = CurrentQueue.AllQueued().Count(a => a.Item == item.Name); + if (repeats > 1 || CurrentQueue.CurrentItem() != firstOfThis) + { + var offset = -22; + var digits = repeats.ToString(); + foreach (var d in digits) + { + ready.PlayFetchIndex("groups", () => d - '0'); + ready.Tick(); + overlayBits.Add(Pair.New(ready.Image, overlayPos + new float2(offset, 0))); + offset += 6; + } + } + } + else + if (!buildableItems.Any(a => a.Name == item.Name) || isBuildingSomething) + overlayBits.Add(Pair.New(cantBuild.Image, drawPos)); + + var closureName = buildableItems.Any(a => a.Name == item.Name) ? item.Name : null; + buttons.Add(Pair.New(new Rectangle((int)rect.X, (int)rect.Y, (int)rect.Width, (int)rect.Height), HandleClick(closureName, world))); + + if (++x == Columns) { x = 0; y++; } + } + if (x != 0) y++; + + foreach (var ob in overlayBits) + WidgetUtils.DrawSHP(ob.First, ob.Second, worldRenderer); + + // Tooltip + if (tooltipItem != null) + DrawProductionTooltip(world, tooltipItem, + new float2(Game.viewport.Width, Game.viewport.Height - 100).ToInt2()); + } + } + + Action HandleClick(string name, World world) + { + return mi => { + Sound.Play(TabClick); + + if (name != null) + HandleBuildPalette(world, name, (mi.Button == MouseButton.Left)); + }; + } + + static string Description( string a ) + { + // hack hack hack - going to die soon anyway + if (a == "barracks") + return "Infantry production"; + if (a == "vehicleproduction") + return "Vehicle production"; + if (a == "techcenter") + return "Tech Center"; + if (a == "anypower") + return "Power Plant"; + + ActorInfo ai; + Rules.Info.TryGetValue(a.ToLowerInvariant(), out ai); + if (ai != null && ai.Traits.Contains()) + return ai.Traits.Get().Name; + + return a; + } + + void HandleBuildPalette( World world, string item, bool isLmb ) + { + var unit = Rules.Info[item]; + var producing = CurrentQueue.AllQueued().FirstOrDefault( a => a.Item == item ); + + if (isLmb) + { + if (producing != null && producing == CurrentQueue.CurrentItem()) + { + if (producing.Done) + { + if (unit.Traits.Contains()) + world.OrderGenerator = new PlaceBuildingOrderGenerator(CurrentQueue.self, item); + else + StartProduction( world, item ); + return; + } + + if (producing.Paused) + { + world.IssueOrder(Order.PauseProduction(CurrentQueue.self, item, false)); + return; + } + } + + StartProduction(world, item); + } + else + { + if (producing != null) + { + // instant cancel of things we havent really started yet, and things that are finished + if (producing.Paused || producing.Done || producing.TotalCost == producing.RemainingCost) + { + Sound.Play(CurrentQueue.Info.CancelledAudio); + int numberToCancel = Game.GetModifierKeys().HasModifier(Modifiers.Shift) ? 5 : 1; + if (Game.GetModifierKeys().HasModifier(Modifiers.Shift) && + Game.GetModifierKeys().HasModifier(Modifiers.Ctrl)) + { + numberToCancel = -1; //cancel all + } + world.IssueOrder(Order.CancelProduction(CurrentQueue.self, item, numberToCancel)); + } + else + { + Sound.Play(CurrentQueue.Info.OnHoldAudio); + world.IssueOrder(Order.PauseProduction(CurrentQueue.self, item, true)); + } + } + } + } + + void StartProduction( World world, string item ) + { + Sound.Play(CurrentQueue.Info.QueuedAudio); + world.IssueOrder(Order.StartProduction(CurrentQueue.self, item, + Game.GetModifierKeys().HasModifier(Modifiers.Shift) ? 5 : 1)); + } + + void DrawRightAligned(string text, int2 pos, Color c) + { + Game.Renderer.Fonts["Bold"].DrawText(text, + pos - new int2(Game.Renderer.Fonts["Bold"].Measure(text).X, 0), c); + } + + void DrawProductionTooltip(World world, string unit, int2 pos) + { + pos.Y += 15; + + var pl = world.LocalPlayer; + var p = pos.ToFloat2() - new float2(297, -3); + + var info = Rules.Info[unit]; + var tooltip = info.Traits.Get(); + var buildable = info.Traits.Get(); + var cost = info.Traits.Get().Cost; + var canBuildThis = CurrentQueue.CanBuild(info); + + var longDescSize = Game.Renderer.Fonts["Regular"].Measure(tooltip.Description.Replace("\\n", "\n")).Y; + if (!canBuildThis) longDescSize += 8; + + WidgetUtils.DrawPanel("dialog4", new Rectangle(Game.viewport.Width - 300, pos.Y, 300, longDescSize + 65)); + + Game.Renderer.Fonts["Bold"].DrawText( + tooltip.Name + ((buildable.Hotkey != null)? " ({0})".F(buildable.Hotkey.ToUpper()) : ""), + p.ToInt2() + new int2(5, 5), Color.White); + + var resources = pl.PlayerActor.Trait(); + var power = pl.PlayerActor.Trait(); + + DrawRightAligned("${0}".F(cost), pos + new int2(-5, 5), + (resources.DisplayCash + resources.DisplayOre >= cost ? Color.White : Color.Red )); + + var lowpower = power.PowerState != PowerState.Normal; + var time = CurrentQueue.GetBuildTime(info.Name) + * ((lowpower)? CurrentQueue.Info.LowPowerSlowdown : 1); + DrawRightAligned(WidgetUtils.FormatTime(time), pos + new int2(-5, 35), lowpower ? Color.Red: Color.White); + + var bi = info.Traits.GetOrDefault(); + if (bi != null) + DrawRightAligned("{1}{0}".F(bi.Power, bi.Power > 0 ? "+" : ""), pos + new int2(-5, 20), + ((power.PowerProvided - power.PowerDrained) >= -bi.Power || bi.Power > 0)? Color.White: Color.Red); + + p += new int2(5, 35); + if (!canBuildThis) + { + var prereqs = buildable.Prerequisites + .Select( a => Description( a ) ); + Game.Renderer.Fonts["Regular"].DrawText( + "Requires {0}".F(string.Join(", ", prereqs.ToArray())), + p.ToInt2(), + Color.White); + + p += new int2(0, 8); + } + + p += new int2(0, 15); + Game.Renderer.Fonts["Regular"].DrawText(tooltip.Description.Replace("\\n", "\n"), + p.ToInt2(), Color.White); + } + } +} diff --git a/OpenRA.Mods.Cnc/Widgets/ProductionTabsWidget.cs b/OpenRA.Mods.Cnc/Widgets/ProductionTabsWidget.cs new file mode 100755 index 0000000000..0bf27db972 --- /dev/null +++ b/OpenRA.Mods.Cnc/Widgets/ProductionTabsWidget.cs @@ -0,0 +1,96 @@ +#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.Collections.Generic; +using System.Drawing; +using System.Linq; +using OpenRA.FileFormats; +using OpenRA.Graphics; +using OpenRA.Mods.RA; +using OpenRA.Widgets; + +namespace OpenRA.Mods.Cnc.Widgets +{ + class ProductionTabsWidget : Widget + { + public string QueueType = null; + string cachedQueueType = null; + public string PaletteWidget = null; + + List> buttons = new List>(); + List VisibleQueues = new List(); + + readonly World world; + + [ObjectCreator.UseCtor] + public ProductionTabsWidget( [ObjectCreator.Param] World world ) + { + this.world = world; + } + + public override void Tick() + { + VisibleQueues.Clear(); + + VisibleQueues = world.ActorsWithTrait() + .Where(p => p.Actor.Owner == world.LocalPlayer && p.Trait.Info.Type == QueueType) + .Select(p => p.Trait).ToList(); + + var palette = Widget.RootWidget.GetWidget(PaletteWidget); + if (VisibleQueues.Count() == 0) + palette.CurrentQueue = null; + else if (palette.CurrentQueue == null || cachedQueueType != QueueType) + { + palette.CurrentQueue = VisibleQueues.First(); + cachedQueueType = QueueType; + } + base.Tick(); + } + + public override bool HandleMouseInput(MouseInput mi) + { + if (mi.Event != MouseInputEvent.Down) + return false; + + var queue = buttons.Where(a => a.First.Contains(mi.Location)) + .Select(a => a.Second).FirstOrDefault(); + if (queue == null) + return true; + + var palette = Widget.RootWidget.GetWidget(PaletteWidget); + + palette.CurrentQueue = queue; + return true; + } + + public override void DrawInner() + { + if (!IsVisible()) return; + buttons.Clear(); + int x = 0; + var palette = Widget.RootWidget.GetWidget(PaletteWidget); + + // Giant hack + var width = 30; + + foreach (var queue in VisibleQueues) + { + var foo = queue; + var rect = new Rectangle(RenderBounds.X + x,RenderBounds.Y,width, RenderBounds.Height); + var state = palette.CurrentQueue == queue ? 2 : + rect.Contains(Viewport.LastMousePos) ? 1 : 0; + x += width; + + WidgetUtils.DrawRGBA(ChromeProvider.GetImage("button", "background"), new int2(rect.Location)); + buttons.Add(Pair.New(rect, foo)); + } + } + } +} diff --git a/mods/cnc/chrome/ingame.yaml b/mods/cnc/chrome/ingame.yaml index 34cc162fd5..fc887f1d54 100644 --- a/mods/cnc/chrome/ingame.yaml +++ b/mods/cnc/chrome/ingame.yaml @@ -173,69 +173,54 @@ Container@INGAME_ROOT: Height:25 Align:Center Font:Bold - Container@PRODUCTION: + Container@PRODUCTION_TYPES: + Id:PRODUCTION_TYPES X:10 Y:190 Width:170 Height:30 Children: - Button@BUILDINGS: + Button@BUILDING: + Id:BUILDING Width:30 Height:30 Text: B - Button@DEFENCE: + Button@DEFENSE: + Id:DEFENSE X:35 Width:30 Height:30 Text: D Button@INFANTRY: + Id:INFANTRY X:70 Width:30 Height:30 Text: I - Button@VEHICLES: + Button@VEHICLE: + Id:VEHICLE X:105 Width:30 Height:30 Text: V Button@AIRCRAFT: + Id:AIRCRAFT X:140 Width:30 Height:30 Text: H - BuildPalette@INGAME_BUILD_PALETTE: - Id:INGAME_BUILD_PALETTE - X:WINDOW_RIGHT - 250 - Y:600 - Width:250 - Height:500 + ProductionTabs: + Id:PRODUCTION_TABS + PaletteWidget:PRODUCTION_PALETTE + X:WINDOW_RIGHT - 200 + Y:260 + Width:192 + Height:15 + ProductionPalette: + Id:PRODUCTION_PALETTE + X:WINDOW_RIGHT - 200 + Y:275 TabClick: button.aud - BuildPaletteOpen: appear1.aud - BuildPaletteClose: appear1.aud -# RadarBin@INGAME_RADAR_BIN: -# Id:INGAME_RADAR_BIN -# WorldInteractionController:INTERACTION_CONTROLLER -# PowerBin@INGAME_POWER_BIN: -# Id:INGAME_POWER_BIN -# MoneyBin@INGAME_MONEY_BIN: -# Id:INGAME_MONEY_BIN -# X:WINDOW_RIGHT - WIDTH -# Y:0 -# Width:320 -# Height: 32 -# SplitOreAndCash: yes -# Children: -# -# OrderButton@REPAIR: -# Id:REPAIR -# Logic:OrderButtonsChromeLogic -# X:75 -# Y:0 -# Width:30 -# Height:30 -# Image:repair -# Description:Repair -# LongDesc:Repair damaged buildings Background@FMVPLAYER: Id:FMVPLAYER Width:WINDOW_RIGHT diff --git a/mods/cnc/rules/system.yaml b/mods/cnc/rules/system.yaml index d9f8615106..fed991ce4e 100644 --- a/mods/cnc/rules/system.yaml +++ b/mods/cnc/rules/system.yaml @@ -94,7 +94,7 @@ World: Country@nod: Name: Nod Race: nod - ChooseBuildTabOnSelect: +# ChooseBuildTabOnSelect: BibLayer: ResourceLayer: ResourceType@green-tib: