diff --git a/OpenRA.Mods.Cnc/Widgets/ProductionPaletteWidget.cs b/OpenRA.Mods.Cnc/Widgets/ProductionPaletteWidget.cs index 3263ff4d4c..86bbdd2dec 100755 --- a/OpenRA.Mods.Cnc/Widgets/ProductionPaletteWidget.cs +++ b/OpenRA.Mods.Cnc/Widgets/ProductionPaletteWidget.cs @@ -22,31 +22,49 @@ using OpenRA.Widgets; namespace OpenRA.Mods.Cnc.Widgets { - class ProductionPaletteWidget : Widget + public class ProductionIcon + { + public string Name; + public Sprite Sprite; + public List Queued; + } + + public 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; + ProductionQueue currentQueue; + public ProductionQueue CurrentQueue + { + get + { + return currentQueue; + } + set + { + currentQueue = value; + RefreshIcons(); + } + } + + public override Rectangle EventBounds { get { return eventBounds; } } + Dictionary Icons = new Dictionary(); Dictionary iconSprites; - Animation cantBuild; - Animation ready; - Animation clock; - List>> buttons = new List>>(); + Animation cantBuild, clock; + Rectangle eventBounds = Rectangle.Empty; readonly WorldRenderer worldRenderer; readonly World world; + [ObjectCreator.UseCtor] - public ProductionPaletteWidget( [ObjectCreator.Param] World world, [ObjectCreator.Param] WorldRenderer worldRenderer ) + 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 @@ -59,196 +77,64 @@ namespace OpenRA.Mods.Cnc.Widgets public override void Tick() { - if (CurrentQueue != null && CurrentQueue.self.Destroyed) + if (CurrentQueue != null && !CurrentQueue.self.IsInWorld) CurrentQueue = null; - + + if (CurrentQueue != null) + RefreshIcons(); + 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(); + var clicked = Icons.Where(i => i.Key.Contains(mi.Location)) + .Select(i => i.Value).FirstOrDefault(); - action(mi); - return true; - } - - public override void DrawInner() - { - if (!IsVisible()) return; - buttons.Clear(); + if (clicked == null) + return true; - var x = 0; - var y = 0; + var actor = Rules.Info[clicked.Name]; + var first = clicked.Queued.FirstOrDefault(); - if (CurrentQueue != null) + if (mi.Button == MouseButton.Left) { - 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; - } + // Pick up a completed building + if (first != null && first.Done && actor.Traits.Contains()) + world.OrderGenerator = new PlaceBuildingOrderGenerator(CurrentQueue.self, clicked.Name); - if (producing.Paused) - { - world.IssueOrder(Order.PauseProduction(CurrentQueue.self, item, false)); - return; - } - } + // Resume a paused item + else if (first != null && first.Paused) + world.IssueOrder(Order.PauseProduction(CurrentQueue.self, clicked.Name, false)); - StartProduction(world, item); + // Queue a new item + else StartProduction(world, clicked.Name); } - else + + // Hold/Cancel an existing item + else if (mi.Button == MouseButton.Right && first != null) { - if (producing != null) + Sound.Play(TabClick); + + // instant cancel of things we havent started yet and things that are finished + if (first.Paused || first.Done || first.TotalCost == first.RemainingCost) { - // 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)); - } + Sound.Play(CurrentQueue.Info.CancelledAudio); + world.IssueOrder(Order.CancelProduction(CurrentQueue.self, clicked.Name, + Game.GetModifierKeys().HasModifier(Modifiers.Shift) ? 5 : 1)); + } + else + { + Sound.Play(CurrentQueue.Info.OnHoldAudio); + world.IssueOrder(Order.PauseProduction(CurrentQueue.self, clicked.Name, true)); } } + return true; } void StartProduction( World world, string item ) @@ -258,66 +144,84 @@ namespace OpenRA.Mods.Cnc.Widgets Game.GetModifierKeys().HasModifier(Modifiers.Shift) ? 5 : 1)); } - void DrawRightAligned(string text, int2 pos, Color c) + public void RefreshIcons() { - Game.Renderer.Fonts["Bold"].DrawText(text, - pos - new int2(Game.Renderer.Fonts["Bold"].Measure(text).X, 0), c); - } + Icons = new Dictionary(); + if (CurrentQueue == null) + return; - 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 allBuildables = CurrentQueue.AllItems().OrderBy(a => a.Traits.Get().BuildPaletteOrder); + var i = 0; + var rb = RenderBounds; + foreach (var item in allBuildables) { - 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); + var x = i % Columns; + var y = i / Columns; + var rect = new Rectangle(rb.X + x * 64 + 1, rb.Y + y * 48 + 1, 64, 48); + var pi = new ProductionIcon() + { + Sprite = iconSprites[item.Name], + Queued = CurrentQueue.AllQueued().Where(a => a.Item == item.Name).ToList(), + Name = item.Name + }; + Icons.Add(rect, pi); + i++; } - p += new int2(0, 15); - Game.Renderer.Fonts["Regular"].DrawText(tooltip.Description.Replace("\\n", "\n"), - p.ToInt2(), Color.White); + eventBounds = Icons.Keys.Aggregate(Rectangle.Union); + } + + public override void DrawInner() + { + if (CurrentQueue == null) + return; + + var isBuildingSomething = CurrentQueue.CurrentItem() != null; + var buildableItems = CurrentQueue.BuildableItems().OrderBy(a => a.Traits.Get().BuildPaletteOrder); + + var overlayFont = Game.Renderer.Fonts["TinyBold"]; + var holdOffset = new float2(32,24) - overlayFont.Measure("On Hold") / 2; + var readyOffset = new float2(32,24) - overlayFont.Measure("Ready") / 2; + var queuedOffset = new float2(4,2); + + foreach (var kv in Icons) + WidgetUtils.DrawPanel("panel-black", kv.Key.InflateBy(1,1,1,1)); + + foreach (var kv in Icons) + { + var rect = kv.Key; + var icon = kv.Value; + var drawPos = new float2(rect.Location); + WidgetUtils.DrawSHP(icon.Sprite, drawPos, worldRenderer); + + // Build progress + var total = icon.Queued.Count; + if (total > 0) + { + var first = icon.Queued[0]; + clock.PlayFetchIndex("idle", + () => (first.TotalTime - first.RemainingTime) + * (clock.CurrentSequence.Length - 1) / first.TotalTime); + clock.Tick(); + WidgetUtils.DrawSHP(clock.Image, drawPos, worldRenderer); + + if (first.Done) + overlayFont.DrawTextWithContrast("Ready", + drawPos + readyOffset, + Color.White, Color.Black, 1); + else if (first.Paused) + overlayFont.DrawTextWithContrast("On Hold", + drawPos + holdOffset, + Color.White, Color.Black, 1); + + if (total > 1 || (first != CurrentQueue.CurrentItem() && !first.Done)) + overlayFont.DrawTextWithContrast(total.ToString(), + drawPos + queuedOffset, + Color.White, Color.Black, 1); + } + else if (isBuildingSomething || !buildableItems.Any(a => a.Name == icon.Name)) + WidgetUtils.DrawSHP(cantBuild.Image, drawPos, worldRenderer); + } } } } diff --git a/mods/cnc/chrome/ingame.yaml b/mods/cnc/chrome/ingame.yaml index efb3538c67..3110a192b7 100644 --- a/mods/cnc/chrome/ingame.yaml +++ b/mods/cnc/chrome/ingame.yaml @@ -87,16 +87,16 @@ Container@INGAME_ROOT: Y:50 Background@SIDEBAR_BACKGROUND: Id:SIDEBAR_BACKGROUND - X:WINDOW_RIGHT - 200 + X:WINDOW_RIGHT - 204 Y:30 - Width:190 - Height:230 + Width:194 + Height:240 Background:panel-black Children: Button@OPTIONS_BUTTON: Id:OPTIONS_BUTTON Key:escape - X:38 + X:40 Y:0-24 Width:35 Height:25 @@ -104,7 +104,7 @@ Container@INGAME_ROOT: Font:Bold Button@SELL: Id:SELL - X:78 + X:80 Y:0-24 Width:35 Height:25 @@ -112,17 +112,17 @@ Container@INGAME_ROOT: Font:Bold Button@REPAIR: Id:SELL - X:118 + X:120 Y:0-24 Width:35 Height:25 Text:R Font:Bold Background@RADAR: - X:19 - Y:11 - Width:152 - Height:152 + X:13 + Y:5 + Width:168 + Height:168 Background:panel-gray Children: Radar: @@ -133,10 +133,10 @@ Container@INGAME_ROOT: Height:PARENT_BOTTOM-2 WorldInteractionController:INTERACTION_CONTROLLER Background@POWERBAR: - X:10 - Y:11 + X:4 + Y:5 Width:10 - Height:152 + Height:168 Background:panel-black Children: PowerBar: @@ -145,15 +145,15 @@ Container@INGAME_ROOT: Width:PARENT_RIGHT-2 Height:PARENT_BOTTOM-2 Label@POWERICON: - X:10 - Y:170 + X:4 + Y:180 Text:P Font:Bold Background@SILOBAR: - X:170 - Y:11 + X:180 + Y:5 Width:10 - Height:152 + Height:168 Background:panel-black Children: SiloBar: @@ -162,21 +162,21 @@ Container@INGAME_ROOT: Width:PARENT_RIGHT-2 Height:PARENT_BOTTOM-2 Label@SILOICON: - X:171 - Y:170 + X:181 + Y:180 Text:T Font:Bold Label@CASH: Id:CASH_DISPLAY - Y:160 + Y:170 Width:PARENT_RIGHT Height:25 Align:Center Font:Bold Container@PRODUCTION_TYPES: Id:PRODUCTION_TYPES - X:10 - Y:190 + X:12 + Y:197 Width:170 Height:30 Children: @@ -212,14 +212,14 @@ Container@INGAME_ROOT: ProductionTabs: Id:PRODUCTION_TABS PaletteWidget:PRODUCTION_PALETTE - X:WINDOW_RIGHT - 200 - Y:259 - Width:190 + X:WINDOW_RIGHT - 204 + Y:268 + Width:194 Height:20 ProductionPalette: Id:PRODUCTION_PALETTE - X:WINDOW_RIGHT - 200 - Y:280 + X:WINDOW_RIGHT - 204 + Y:287 TabClick: button.aud Background@FMVPLAYER: Id:FMVPLAYER