From b6e8c9c9eae3e5f2309f502865eb9a4924eb418d Mon Sep 17 00:00:00 2001 From: Scott_NZ Date: Tue, 27 Nov 2012 02:48:20 +1300 Subject: [PATCH] Start implementing graphs for the stats panel --- OpenRA.Mods.RA/OpenRA.Mods.RA.csproj | 1 + OpenRA.Mods.RA/Player/PlayerStatistics.cs | 33 ++++++- .../Widgets/Logic/ObserverStatsLogic.cs | 58 ++++++++---- .../Widgets/ObserverProductionIconsWidget.cs | 2 +- .../Widgets/ObserverStatsGraphWidget.cs | 91 +++++++++++++++++++ .../ObserverSupportPowerIconsWidget.cs | 2 +- mods/ra/chrome/ingame.yaml | 65 ++++++++++--- 7 files changed, 215 insertions(+), 37 deletions(-) create mode 100644 OpenRA.Mods.RA/Widgets/ObserverStatsGraphWidget.cs diff --git a/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj b/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj index cf1c60aa84..8a6771b0fc 100644 --- a/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj +++ b/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj @@ -384,6 +384,7 @@ + diff --git a/OpenRA.Mods.RA/Player/PlayerStatistics.cs b/OpenRA.Mods.RA/Player/PlayerStatistics.cs index 1d9d43fc20..2d12cb745c 100644 --- a/OpenRA.Mods.RA/Player/PlayerStatistics.cs +++ b/OpenRA.Mods.RA/Player/PlayerStatistics.cs @@ -8,6 +8,7 @@ */ #endregion +using System.Collections.Generic; using System.Linq; using OpenRA.Mods.RA.Buildings; using OpenRA.Traits; @@ -23,12 +24,26 @@ namespace OpenRA.Mods.RA { World world; Player player; + public double MapControl; public int OrderCount; + + public int EarnedThisMinute + { + get + { + return player.PlayerActor.Trait().Earned - earnedAtBeginningOfMinute; + } + } + public Queue EarnedSamples = new Queue(100); + int earnedAtBeginningOfMinute; + public int KillsCost; public int DeathsCost; + public int UnitsKilled; public int UnitsDead; + public int BuildingsKilled; public int BuildingsDead; @@ -48,9 +63,23 @@ namespace OpenRA.Mods.RA .Count() / total; } + void UpdateEarnedThisMinute() + { + EarnedSamples.Enqueue(EarnedThisMinute); + earnedAtBeginningOfMinute = player.PlayerActor.Trait().Earned; + if (EarnedSamples.Count > 100) + { + EarnedSamples.Dequeue(); + } + } + public void Tick(Actor self) { - if (self.World.FrameNumber % 250 == 1) + if (self.World.FrameNumber % 1500 == 0) + { + UpdateEarnedThisMinute(); + } + if (self.World.FrameNumber % 250 == 0) { UpdateMapControl(); } @@ -92,7 +121,7 @@ namespace OpenRA.Mods.RA attackerStats.BuildingsKilled++; defenderStats.BuildingsDead++; } - if (self.HasTrait()) + else if (self.HasTrait()) { attackerStats.UnitsKilled++; defenderStats.UnitsDead++; diff --git a/OpenRA.Mods.RA/Widgets/Logic/ObserverStatsLogic.cs b/OpenRA.Mods.RA/Widgets/Logic/ObserverStatsLogic.cs index 3f8597d8bd..8e55229c54 100644 --- a/OpenRA.Mods.RA/Widgets/Logic/ObserverStatsLogic.cs +++ b/OpenRA.Mods.RA/Widgets/Logic/ObserverStatsLogic.cs @@ -12,6 +12,7 @@ using System; using System.Collections.Generic; using System.Drawing; using System.Linq; +using OpenRA.FileFormats; using OpenRA.Mods.RA.Buildings; using OpenRA.Network; using OpenRA.Traits; @@ -25,11 +26,13 @@ namespace OpenRA.Mods.RA.Widgets.Logic ContainerWidget economicStatsHeaders; ContainerWidget productionStatsHeaders; ContainerWidget combatStatsHeaders; + ContainerWidget earnedThisMinuteGraphHeaders; ScrollPanelWidget playerStatsPanel; ScrollItemWidget basicPlayerTemplate; ScrollItemWidget economicPlayerTemplate; ScrollItemWidget productionPlayerTemplate; ScrollItemWidget combatPlayerTemplate; + ContainerWidget earnedThisMinuteGraphTemplate; ScrollItemWidget teamTemplate; DropDownButtonWidget statsDropDown; IEnumerable players; @@ -45,6 +48,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic economicStatsHeaders = widget.Get("ECONOMIC_STATS_HEADERS"); productionStatsHeaders = widget.Get("PRODUCTION_STATS_HEADERS"); combatStatsHeaders = widget.Get("COMBAT_STATS_HEADERS"); + earnedThisMinuteGraphHeaders = widget.Get("EARNED_THIS_MIN_GRAPH_HEADERS"); playerStatsPanel = widget.Get("PLAYER_STATS_PANEL"); playerStatsPanel.Layout = new GridLayout(playerStatsPanel); @@ -53,6 +57,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic economicPlayerTemplate = playerStatsPanel.Get("ECONOMIC_PLAYER_TEMPLATE"); productionPlayerTemplate = playerStatsPanel.Get("PRODUCTION_PLAYER_TEMPLATE"); combatPlayerTemplate = playerStatsPanel.Get("COMBAT_PLAYER_TEMPLATE"); + earnedThisMinuteGraphTemplate = playerStatsPanel.Get("EARNED_THIS_MIN_GRAPH_TEMPLATE"); teamTemplate = playerStatsPanel.Get("TEAM_TEMPLATE"); @@ -105,6 +110,17 @@ namespace OpenRA.Mods.RA.Widgets.Logic statsDropDown.GetText = () => "Combat"; DisplayStats(CombatStats); } + }, + new StatsDropDownOption + { + Title = "Earnings (graph)", + IsSelected = () => earnedThisMinuteGraphHeaders.Visible, + OnClick = () => + { + ClearStats(); + statsDropDown.GetText = () => "Earnings (graph)"; + EarnedThisMinuteGraph(); + } } }; Func setupItem = (option, template) => @@ -116,9 +132,6 @@ namespace OpenRA.Mods.RA.Widgets.Logic statsDropDown.ShowDropDown("LABEL_DROPDOWN_TEMPLATE", 150, options, setupItem); }; - widget.Height = (200 + (Math.Min(8, players.Count()) * 25)).ToString(); - InitializeWidgets(widget, widget.Get("BACKGROUND"), widget.Get("PLAYER_STATS_PANEL")); - ClearStats(); DisplayStats(BasicStats); } @@ -130,9 +143,23 @@ namespace OpenRA.Mods.RA.Widgets.Logic economicStatsHeaders.Visible = false; productionStatsHeaders.Visible = false; combatStatsHeaders.Visible = false; + earnedThisMinuteGraphHeaders.Visible = false; } - void DisplayStats(Func forEachPlayer) + void EarnedThisMinuteGraph() + { + earnedThisMinuteGraphHeaders.Visible = true; + var template = earnedThisMinuteGraphTemplate.Clone(); + + var graph = template.Get("EARNED_THIS_MIN_GRAPH"); + graph.GetDataSource = () => players.Select(p => Pair.New(p, p.PlayerActor.Trait().EarnedSamples.Select(s => (float)s))); + graph.GetDataScale = () => 1 / 100f; + graph.GetLastValueFormat = () => "${0}"; + + playerStatsPanel.AddChild(template); + } + + void DisplayStats(Func createItem) { var teams = players.GroupBy(p => (world.LobbyInfo.ClientWithIndex(p.ClientIndex) ?? new Session.Client()).Team).OrderBy(g => g.Key); foreach (var t in teams) @@ -145,7 +172,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic foreach (var p in team) { var player = p; - playerStatsPanel.AddChild(forEachPlayer(player)); + playerStatsPanel.AddChild(createItem(player)); } } } @@ -190,9 +217,11 @@ namespace OpenRA.Mods.RA.Widgets.Logic AddPlayerFlagAndName(template, player); var res = player.PlayerActor.Trait(); + var stats = player.PlayerActor.Trait(); template.Get("CASH").GetText = () => "$" + (res.DisplayCash + res.DisplayOre); - template.Get("EARNED_MIN").GetText = () => EarnedPerMinute(res.Earned); + template.Get("EARNED_MIN").GetText = () => AverageEarnedPerMinute(res.Earned); + template.Get("EARNED_THIS_MIN").GetText = () => "$" + stats.EarnedThisMinute; template.Get("EARNED").GetText = () => "$" + res.Earned; template.Get("SPENT").GetText = () => "$" + res.Spent; @@ -216,7 +245,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic var res = player.PlayerActor.Trait(); template.Get("CASH").GetText = () => "$" + (res.DisplayCash + res.DisplayOre); - template.Get("EARNED_MIN").GetText = () => EarnedPerMinute(res.Earned); + template.Get("EARNED_MIN").GetText = () => AverageEarnedPerMinute(res.Earned); var powerRes = player.PlayerActor.Trait(); var power = template.Get("POWER"); @@ -227,7 +256,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic template.Get("DEATHS").GetText = () => player.Deaths.ToString(); var stats = player.PlayerActor.Trait(); - template.Get("ACTIONS_MIN").GetText = () => OrdersPerMinute(stats.OrderCount); + template.Get("ACTIONS_MIN").GetText = () => AverageOrdersPerMinute(stats.OrderCount); return template; } @@ -249,12 +278,12 @@ namespace OpenRA.Mods.RA.Widgets.Logic return (control * 100).ToString("F1") + "%"; } - string OrdersPerMinute(double orders) + string AverageOrdersPerMinute(double orders) { return (world.FrameNumber == 0 ? 0 : orders / (world.FrameNumber / 1500.0)).ToString("F1"); } - string EarnedPerMinute(double earned) + string AverageEarnedPerMinute(double earned) { return "$" + (world.FrameNumber == 0 ? 0 : earned / (world.FrameNumber / 1500.0)).ToString("F2"); } @@ -270,15 +299,6 @@ namespace OpenRA.Mods.RA.Widgets.Logic playerName.GetColor = () => player.ColorRamp.GetColor(0); } - static void InitializeWidgets(params Widget[] widgets) - { - var args = new WidgetArgs(); - foreach (var widget in widgets) - { - widget.Initialize(args); - } - } - static Color GetPowerColor(PowerState state) { if (state == PowerState.Critical) return Color.Red; diff --git a/OpenRA.Mods.RA/Widgets/ObserverProductionIconsWidget.cs b/OpenRA.Mods.RA/Widgets/ObserverProductionIconsWidget.cs index 68b78a7b20..3aa47af80a 100644 --- a/OpenRA.Mods.RA/Widgets/ObserverProductionIconsWidget.cs +++ b/OpenRA.Mods.RA/Widgets/ObserverProductionIconsWidget.cs @@ -10,10 +10,10 @@ using System; using System.Collections.Generic; +using System.Drawing; using System.Linq; using OpenRA.Graphics; using OpenRA.Widgets; -using System.Drawing; namespace OpenRA.Mods.RA.Widgets { diff --git a/OpenRA.Mods.RA/Widgets/ObserverStatsGraphWidget.cs b/OpenRA.Mods.RA/Widgets/ObserverStatsGraphWidget.cs new file mode 100644 index 0000000000..1360a9217b --- /dev/null +++ b/OpenRA.Mods.RA/Widgets/ObserverStatsGraphWidget.cs @@ -0,0 +1,91 @@ +#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; + +namespace OpenRA.Widgets +{ + public class ObserverStatsGraphWidget : Widget + { + public Func>>> GetDataSource = () => null; + public Func GetDataScale = () => 1.0f; + public Func GetLastValueFormat = () => "{0}"; + public Func GetNodeCount = () => 20; + public Func GetNodeStep = () => 5; + + public ObserverStatsGraphWidget() : base() { } + + protected ObserverStatsGraphWidget(ObserverStatsGraphWidget other) + : base(other) + { + GetDataSource = other.GetDataSource; + } + + public override void Draw() + { + var rect = RenderBounds; + var origin = new float2(rect.Left, rect.Bottom); + var basis = new float2(rect.Width / 100, rect.Height / 100); + + Game.Renderer.LineRenderer.DrawLine(origin, origin + new float2(100, 0) * basis, Color.White, Color.White); + Game.Renderer.LineRenderer.DrawLine(origin, origin - new float2(0, 100) * basis, Color.White, Color.White); + Game.Renderer.LineRenderer.DrawLine(origin + new float2(100, 0) * basis, origin + new float2(100, -100) * basis, Color.White, Color.White); + + var tinyBold = Game.Renderer.Fonts["TinyBold"]; + + var i = 0; + foreach (var pair in GetDataSource()) + { + var player = pair.First; + var data = pair.Second.Reverse().Take(GetNodeCount()).Reverse(); + var color = player.ColorRamp.GetColor(0); + if (data.Any()) + { + var scale = GetDataScale(); + var scaledData = data.Select(d => d * scale); + var n = 0; + var step = GetNodeStep(); + scaledData.Aggregate((a, b) => + { + Game.Renderer.LineRenderer.DrawLine( + origin + new float2(n, -a) * basis, + origin + new float2(n + step, -b) * basis, + color, color); + n += step; + return b; + }); + + var lastValue = data.Last(); + if (lastValue != 0) + { + var scaledLastValue = lastValue * scale; + var lastValueFormat = GetLastValueFormat(); + if (lastValueFormat != null) + { + tinyBold.DrawText(lastValueFormat.F(lastValue), origin + new float2(n, -scaledLastValue - 2) * basis, color); + } + } + } + + tinyBold.DrawText(player.PlayerName, new float2(rect.Left, rect.Top) + new float2(5, 10 * i - 3), color); + i++; + } + } + + public override Widget Clone() + { + return new ObserverStatsGraphWidget(this); + } + } +} diff --git a/OpenRA.Mods.RA/Widgets/ObserverSupportPowerIconsWidget.cs b/OpenRA.Mods.RA/Widgets/ObserverSupportPowerIconsWidget.cs index fba606a789..c20c8c69e6 100644 --- a/OpenRA.Mods.RA/Widgets/ObserverSupportPowerIconsWidget.cs +++ b/OpenRA.Mods.RA/Widgets/ObserverSupportPowerIconsWidget.cs @@ -10,10 +10,10 @@ using System; using System.Collections.Generic; +using System.Drawing; using System.Linq; using OpenRA.Graphics; using OpenRA.Widgets; -using System.Drawing; namespace OpenRA.Mods.RA.Widgets { diff --git a/mods/ra/chrome/ingame.yaml b/mods/ra/chrome/ingame.yaml index b784b4fd61..2bdaf44fe2 100644 --- a/mods/ra/chrome/ingame.yaml +++ b/mods/ra/chrome/ingame.yaml @@ -396,7 +396,7 @@ Container@OBSERVER_ROOT: X:25 Y:50 Width:950 - Height:400 + Height:500 Visible:false Children: Background@BACKGROUND: @@ -413,9 +413,9 @@ Container@OBSERVER_ROOT: Align:Center Text:Statistics DropDownButton@STATS_DROPDOWN: - X:PARENT_RIGHT-140 + X:PARENT_RIGHT-200 Y:15 - Width:125 + Width:185 Height:25 Font:Bold Container@BASIC_STATS_HEADERS: @@ -503,29 +503,36 @@ Container@OBSERVER_ROOT: Height:25 Font:Bold Text:Earned/min - Label@ASSETS_HEADER: + Label@EARNED_THIS_MIN_HEADER: X:425 Y:40 Width:60 Height:25 Font:Bold + Text:Earned this min + Label@ASSETS_HEADER: + X:565 + Y:40 + Width:60 + Height:25 + Font:Bold Text:Assets Label@EARNED_HEADER: - X:505 + X:645 Y:40 Width:60 Height:25 Font:Bold Text:Earned Label@SPENT_HEADER: - X:585 + X:725 Y:40 Width:60 Height:25 Font:Bold Text:Spent Label@HARVESTERS_HEADER: - X:665 + X:805 Y:40 Width:60 Height:25 @@ -607,7 +614,7 @@ Container@OBSERVER_ROOT: Width:40 Height:25 Font:Bold - Text:Units Dead + Text:Units Lost Align:Right Label@BUILDINGS_KILLED_HEADER: X:725 @@ -615,7 +622,7 @@ Container@OBSERVER_ROOT: Width:40 Height:25 Font:Bold - Text:Bld Destroyed + Text:Bldg Killed Align:Right Label@BUILDINGS_DEAD_HEADER: X:825 @@ -623,8 +630,22 @@ Container@OBSERVER_ROOT: Width:40 Height:25 Font:Bold - Text:Bld Lost + Text:Bldg Lost Align:Right + Container@EARNED_THIS_MIN_GRAPH_HEADERS: + X:0 + Y:0 + Width:PARENT_RIGHT + Height:PARENT_BOTTOM + Children: + Label@EARNED_THIS_MIN_HEADER: + X:0 + Y:40 + Width:PARENT_RIGHT + Height:25 + Font:Bold + Text:Earnings received each minute + Align:Center ScrollPanel@PLAYER_STATS_PANEL: X:25 Y:70 @@ -725,23 +746,28 @@ Container@OBSERVER_ROOT: Y:0 Width:60 Height:PARENT_BOTTOM - Label@ASSETS: + Label@EARNED_THIS_MIN: X:395 Y:0 Width:60 Height:PARENT_BOTTOM + Label@ASSETS: + X:535 + Y:0 + Width:60 + Height:PARENT_BOTTOM Label@EARNED: - X:475 + X:615 Y:0 Width:60 Height:PARENT_BOTTOM Label@SPENT: - X:555 + X:695 Y:0 Width:60 Height:PARENT_BOTTOM Label@HARVESTERS: - X:635 + X:775 Y:0 Width:60 Height:PARENT_BOTTOM @@ -833,6 +859,17 @@ Container@OBSERVER_ROOT: Width:40 Height:PARENT_BOTTOM Align:Right + Container@EARNED_THIS_MIN_GRAPH_TEMPLATE: + X:0 + Y:0 + Width:PARENT_RIGHT-35 + Height:300 + Children: + ObserverStatsGraph@EARNED_THIS_MIN_GRAPH: + X:0 + Y:0 + Width:800 + Height:PARENT_BOTTOM Background@FMVPLAYER: Width:WINDOW_RIGHT Height:WINDOW_BOTTOM