diff --git a/OpenRA.Mods.RA/Widgets/Logic/ObserverStatsLogic.cs b/OpenRA.Mods.RA/Widgets/Logic/ObserverStatsLogic.cs index f7eb4f9851..170eddab3b 100644 --- a/OpenRA.Mods.RA/Widgets/Logic/ObserverStatsLogic.cs +++ b/OpenRA.Mods.RA/Widgets/Logic/ObserverStatsLogic.cs @@ -9,6 +9,7 @@ #endregion using System; +using System.Collections.Generic; using System.Drawing; using System.Linq; using OpenRA.Mods.RA.Buildings; @@ -20,26 +21,96 @@ namespace OpenRA.Mods.RA.Widgets.Logic { public class ObserverStatsLogic { - ContainerWidget basicStats; - ScrollPanelWidget playersPanel; + class StatsDropDownOption + { + public string Title; + public Func IsSelected; + public Action OnClick; + } + + ContainerWidget basicStatsHeaders; + ContainerWidget economicStatsHeaders; + ScrollPanelWidget playerStatsPanel; ScrollItemWidget basicPlayerTemplate; + ScrollItemWidget economicPlayerTemplate; ScrollItemWidget teamTemplate; + DropDownButtonWidget statsDropDown; + LabelWidget title; + IEnumerable players; + World world; [ObjectCreator.UseCtor] public ObserverStatsLogic(World world, Widget widget) { - basicStats = widget.Get("BASIC_STATS"); - playersPanel = widget.Get("PLAYERS_PANEL"); - basicPlayerTemplate = playersPanel.Get("BASIC_PLAYER_TEMPLATE"); - teamTemplate = playersPanel.Get("TEAM_TEMPLATE"); - playersPanel.RemoveChildren(); - playersPanel.Layout = new GridLayout(playersPanel); + this.world = world; + players = world.Players.Where(p => !p.NonCombatant); - var players = world.Players.Where(p => !p.NonCombatant); + title = widget.Get("TITLE"); + + basicStatsHeaders = widget.Get("BASIC_STATS_HEADERS"); + economicStatsHeaders = widget.Get("ECONOMIC_STATS_HEADERS"); + + playerStatsPanel = widget.Get("PLAYER_STATS_PANEL"); + playerStatsPanel.Layout = new GridLayout(playerStatsPanel); + + basicPlayerTemplate = playerStatsPanel.Get("BASIC_PLAYER_TEMPLATE"); + economicPlayerTemplate = playerStatsPanel.Get("ECONOMIC_PLAYER_TEMPLATE"); + teamTemplate = playerStatsPanel.Get("TEAM_TEMPLATE"); + + statsDropDown = widget.Get("STATS_DROPDOWN"); + statsDropDown.GetText = () => "Basic"; + statsDropDown.OnMouseDown = _ => + { + var options = new List + { + new StatsDropDownOption + { + Title = "Basic", + IsSelected = () => basicStatsHeaders.Visible, + OnClick = () => + { + ClearStats(); + statsDropDown.GetText = () => "Basic"; + LoadStats(BasicStats); + } + }, + new StatsDropDownOption + { + Title = "Economic", + IsSelected = () => economicStatsHeaders.Visible, + OnClick = () => + { + ClearStats(); + statsDropDown.GetText = () => "Economic"; + LoadStats(EconomicStats); + } + } + }; + Func setupItem = (option, template) => + { + var item = ScrollItemWidget.Setup(template, option.IsSelected, option.OnClick); + item.Get("LABEL").GetText = () => option.Title; + return item; + }; + statsDropDown.ShowDropDown("LABEL_DROPDOWN_TEMPLATE", 100, options, setupItem); + }; widget.Height = (200 + (Math.Min(8, players.Count()) * 25)).ToString(); - Initialize(widget, widget.Get("BACKGROUND"), widget.Get("PLAYERS_PANEL")); + InitializeWidgets(widget, widget.Get("BACKGROUND"), widget.Get("PLAYER_STATS_PANEL")); + ClearStats(); + LoadStats(BasicStats); + } + + void ClearStats() + { + playerStatsPanel.Children.Clear(); + basicStatsHeaders.Visible = false; + economicStatsHeaders.Visible = false; + } + + void LoadStats(Action forEachPlayer) + { var teams = players.GroupBy(p => (world.LobbyInfo.ClientWithIndex(p.ClientIndex) ?? new Session.Client()).Team).OrderBy(g => g.Key); foreach (var t in teams) { @@ -47,56 +118,95 @@ namespace OpenRA.Mods.RA.Widgets.Logic var tt = ScrollItemWidget.Setup(teamTemplate, () => false, () => { }); tt.IgnoreMouseOver = true; tt.Get("TEAM").GetText = () => team.Key == 0 ? "No team" : "Team " + team.Key; - playersPanel.AddChild(tt); + playerStatsPanel.AddChild(tt); foreach (var p in team) { var player = p; - var template = ScrollItemWidget.Setup(basicPlayerTemplate, () => false, () => - { - var playerBase = world.Actors.FirstOrDefault(a => !a.IsDead() && a.HasTrait() && a.Owner == player); - if (playerBase != null) - { - Game.MoveViewport(playerBase.Location.ToFloat2()); - } - }); - - var flag = template.Get("FACTION_FLAG"); - flag.GetImageName = () => player.Country.Race; - flag.GetImageCollection = () => "flags"; - - var playerName = template.Get("PLAYER"); - playerName.GetText = () => player.PlayerName + (player.WinState == WinState.Undefined ? "" : " (" + player.WinState + ")"); - playerName.GetColor = () => player.ColorRamp.GetColor(0); - - var res = player.PlayerActor.Trait(); - template.Get("CASH").GetText = () => "$" + (res.DisplayCash + res.DisplayOre); - template.Get("INCOME").GetText = () => "$" + res.IncomePerMin; - var change = template.Get("INCOME_CHANGE"); - change.GetText = () => Math.Round(res.IncomeChange * 100, 1, MidpointRounding.AwayFromZero) + "%"; - change.GetColor = () => - { - if (res.IncomeChange < 0) return Color.Red; - if (res.IncomeChange > 0) return Color.LimeGreen; - else return Color.White; - }; - - var powerRes = player.PlayerActor.Trait(); - var power = template.Get("POWER"); - power.GetText = () => powerRes.PowerDrained + "/" + powerRes.PowerProvided; - power.GetColor = () => GetPowerColor(powerRes.PowerState); - - template.Get("KILLS").GetText = () => player.Kills.ToString(); - template.Get("DEATHS").GetText = () => player.Deaths.ToString(); - - var production = template.Get("PRODUCTION_ICONS"); - production.GetPlayer = () => player; - - playersPanel.AddChild(template); + forEachPlayer(player); } } } - static void Initialize(params Widget[] widgets) + void EconomicStats(Player player) + { + economicStatsHeaders.Visible = true; + var template = SetupPlayerScrollItemWidget(economicPlayerTemplate, player); + + var flag = template.Get("FACTION_FLAG"); + flag.GetImageName = () => player.Country.Race; + flag.GetImageCollection = () => "flags"; + + var playerName = template.Get("PLAYER"); + playerName.GetText = () => player.PlayerName + (player.WinState == WinState.Undefined ? "" : " (" + player.WinState + ")"); + playerName.GetColor = () => player.ColorRamp.GetColor(0); + + var res = player.PlayerActor.Trait(); + template.Get("CASH").GetText = () => "$" + (res.DisplayCash + res.DisplayOre); + template.Get("INCOME").GetText = () => "$" + res.IncomePerMin; + var change = template.Get("INCOME_CHANGE"); + change.GetText = () => Math.Round(res.IncomeChange * 100, 1, MidpointRounding.AwayFromZero) + "%"; + change.GetColor = () => + { + if (res.IncomeChange < 0) return Color.Red; + if (res.IncomeChange > 0) return Color.LimeGreen; + else return Color.White; + }; + + var assets = template.Get("TOTAL_ASSETS"); + assets.GetText = () => "$" + world.Actors + .Where(a => a.Owner == player && !a.IsDead() && a.Info.Traits.WithInterface().Any()) + .Sum(a => a.Info.Traits.WithInterface().First().Cost); + + var numHarvesters = template.Get("NUMBER_HARVESTERS"); + numHarvesters.GetText = () => world.Actors.Count(a => a.Owner == player && !a.IsDead() && a.HasTrait()).ToString(); + + playerStatsPanel.AddChild(template); + } + + void BasicStats(Player player) + { + basicStatsHeaders.Visible = true; + var template = SetupPlayerScrollItemWidget(basicPlayerTemplate, player); + + var flag = template.Get("FACTION_FLAG"); + flag.GetImageName = () => player.Country.Race; + flag.GetImageCollection = () => "flags"; + + var playerName = template.Get("PLAYER"); + playerName.GetText = () => player.PlayerName + (player.WinState == WinState.Undefined ? "" : " (" + player.WinState + ")"); + playerName.GetColor = () => player.ColorRamp.GetColor(0); + + var res = player.PlayerActor.Trait(); + template.Get("CASH").GetText = () => "$" + (res.DisplayCash + res.DisplayOre); + template.Get("INCOME").GetText = () => "$" + res.IncomePerMin; + + var powerRes = player.PlayerActor.Trait(); + var power = template.Get("POWER"); + power.GetText = () => powerRes.PowerDrained + "/" + powerRes.PowerProvided; + power.GetColor = () => GetPowerColor(powerRes.PowerState); + + template.Get("KILLS").GetText = () => player.Kills.ToString(); + template.Get("DEATHS").GetText = () => player.Deaths.ToString(); + + var production = template.Get("PRODUCTION_ICONS"); + production.GetPlayer = () => player; + + playerStatsPanel.AddChild(template); + } + + ScrollItemWidget SetupPlayerScrollItemWidget(ScrollItemWidget template, Player player) + { + return ScrollItemWidget.Setup(template, () => false, () => + { + var playerBase = world.Actors.FirstOrDefault(a => !a.IsDead() && a.HasTrait() && a.Owner == player); + if (playerBase != null) + { + Game.MoveViewport(playerBase.Location.ToFloat2()); + } + }); + } + + static void InitializeWidgets(params Widget[] widgets) { var args = new WidgetArgs(); foreach (var widget in widgets) diff --git a/mods/ra/chrome/ingame.yaml b/mods/ra/chrome/ingame.yaml index 66879f0c9d..e7f855d18f 100644 --- a/mods/ra/chrome/ingame.yaml +++ b/mods/ra/chrome/ingame.yaml @@ -292,7 +292,7 @@ Container@OBSERVER_ROOT: Y:0 Width:160 Height:25 - Text:Players + Text:Statistics Font:Bold Background@RADAR_BG: X:WINDOW_RIGHT-255 @@ -411,8 +411,14 @@ Container@OBSERVER_ROOT: Height:25 Font:Bold Align:Center - Text:Players - Container@BASIC_STATS: + Text:Statistics + DropDownButton@STATS_DROPDOWN: + X:PARENT_RIGHT-140 + Y:15 + Width:125 + Height:25 + Font:Bold + Container@BASIC_STATS_HEADERS: X:0 Y:0 Width:PARENT_RIGHT @@ -435,19 +441,19 @@ Container@OBSERVER_ROOT: Label@INCOME_HEADER: X:325 Y:40 - Width:160 + Width:60 Height:25 Font:Bold Text:Income Label@POWER_HEADER - X:445 + X:405 Y:40 Width:80 Height:25 Font:Bold Text:Power Label@KILLS_HEADER: - X:525 + X:485 Y:40 Width:40 Height:25 @@ -455,7 +461,7 @@ Container@OBSERVER_ROOT: Text:Kills Align:Right Label@DEATHS_HEADER: - X:585 + X:545 Y:40 Width:40 Height:25 @@ -463,14 +469,56 @@ Container@OBSERVER_ROOT: Text:Deaths Align:Right Label@PRODUCTION_HEADER - X:625 + X:585 Y:40 Width:PARENT_RIGHT-625 Height:25 Font:Bold Text:Production Align:Center - ScrollPanel@PLAYERS_PANEL: + Container@ECONOMIC_STATS_HEADERS: + X:0 + Y:0 + Width:PARENT_RIGHT + Height:PARENT_BOTTOM + Children: + Label@PLAYER_HEADER: + X:85 + Y:40 + Width:160 + Height:25 + Font:Bold + Text:Player + Label@CASH_HEADER: + X:245 + Y:40 + Width:80 + Height:25 + Font:Bold + Text:Cash + Label@INCOME_HEADER: + X:325 + Y:40 + Width:120 + Height:25 + Font:Bold + Text:Income + Label@TOTAL_ASSETS_HEADER + X:445 + Y:40 + Width:60 + Height:25 + Font:Bold + Text:Assets + Label@NUMBER_HARVESTERS_HEADER + X:525 + Y:40 + Width:60 + Height:25 + Font:Bold + Text:Harvesters + Align:Right + ScrollPanel@PLAYER_STATS_PANEL: X:25 Y:70 Width:PARENT_RIGHT-50 @@ -518,33 +566,73 @@ Container@OBSERVER_ROOT: Y:0 Width:60 Height:PARENT_BOTTOM - Label@INCOME_CHANGE - X:355 - Y:0 - Width:40 - Height:PARENT_BOTTOM Label@POWER: - X:415 + X:375 Y:0 Width:80 Height:PARENT_BOTTOM Label@KILLS: - X:495 + X:455 Y:0 Width:40 Height:PARENT_BOTTOM Align:Right Label@DEATHS: - X:555 + X:515 Y:0 Width:40 Height:PARENT_BOTTOM Align:Right ObserverBuildIcons@PRODUCTION_ICONS: - X:615 + X:575 Y:0 Width:240 Height:PARENT_BOTTOM + ScrollItem@ECONOMIC_PLAYER_TEMPLATE: + X:0 + Y:0 + Width:PARENT_RIGHT-35 + Height:25 + Children: + Image@FACTION_FLAG: + X:20 + Y:5 + Width:35 + Height:PARENT_BOTTOM-5 + ImageName:random + ImageCollection:flags + Label@PLAYER: + X:55 + Y:0 + Width:160 + Height:PARENT_BOTTOM + Font:Bold + Label@CASH: + X:215 + Y:0 + Width:80 + Height:PARENT_BOTTOM + Label@INCOME: + X:295 + Y:0 + Width:60 + Height:PARENT_BOTTOM + Label@INCOME_CHANGE: + X:355 + Y:0 + Width:60 + Height:PARENT_BOTTOM + Label@TOTAL_ASSETS: + X:415 + Y:0 + Width:60 + Height:PARENT_BOTTOM + Label@NUMBER_HARVESTERS: + X:495 + Y:0 + Width:60 + Height:PARENT_BOTTOM + Align:Right Background@FMVPLAYER: Width:WINDOW_RIGHT Height:WINDOW_BOTTOM