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