From 87ee44964cb1443dd2245857b2fbcf930a639931 Mon Sep 17 00:00:00 2001 From: Lukas Werling Date: Fri, 17 Feb 2017 23:04:16 +0100 Subject: [PATCH] Send script-defined statistics to the masterserver After GameOver(), the global function CollectStatistics() is called which in turn calls CollectStats() on all definitions and the Scenario. The results are collected into a proplist and sent to the masterserver as JSON. The intended purpose is to collect statistics like weapon kill counts and evaluate them across all online games to improve balancing. --- planet/System.ocg/CollectStatistics.c | 28 +++++++++++++++++++++++++++ src/control/C4RoundResults.cpp | 14 ++++++++++++++ src/control/C4RoundResults.h | 3 +++ src/game/C4GameScript.h | 2 ++ src/network/C4Network2Reference.cpp | 2 ++ src/network/C4Network2Reference.h | 1 + 6 files changed, 50 insertions(+) create mode 100644 planet/System.ocg/CollectStatistics.c diff --git a/planet/System.ocg/CollectStatistics.c b/planet/System.ocg/CollectStatistics.c new file mode 100644 index 000000000..553126e4e --- /dev/null +++ b/planet/System.ocg/CollectStatistics.c @@ -0,0 +1,28 @@ + +/*-- + CollectStatistics.c + Authors: Luchs + + Global entry point for statistics collection for the masterserver. +--*/ + + +// This function is called after the round ends. The return value is passed to +// the masterserver. +global func CollectStatistics() +{ + var result = {}; + var i = 0, def, stats; + while (def = GetDefinition(i++)) + { + stats = def->~CollectStats(); + if (stats != nil) + result[def->GetName(true)] = stats; + } + stats = Scenario->~CollectStats(); + if (stats != nil) + result.Scenario = stats; + if (GetLength(GetProperties(result))) + return result; + return nil; +} diff --git a/src/control/C4RoundResults.cpp b/src/control/C4RoundResults.cpp index 1fd755629..818e2c107 100644 --- a/src/control/C4RoundResults.cpp +++ b/src/control/C4RoundResults.cpp @@ -249,6 +249,7 @@ void C4RoundResults::Clear() sNetResult.Clear(); eNetResult = NR_None; fHideSettlementScore=false; + Statistics.Clear(); } void C4RoundResults::Init() @@ -308,6 +309,19 @@ void C4RoundResults::EvaluateGame() int32_t iFirstLocalPlayer = pFirstLocalPlayer ? pFirstLocalPlayer->Number : NO_OWNER; EvaluateGoals(Goals, FulfilledGoals, iFirstLocalPlayer); iPlayingTime = Game.Time; + + // collect statistics + try + { + C4AulParSet Pars; + auto stats = ::ScriptEngine.GetPropList()->Call(PSF_CollectStatistics, &Pars); + if (stats != C4VNull) + Statistics = stats.ToJSON(); + } + catch (C4JSONSerializationError& e) + { + DebugLogF("ERROR: cannot serialize CollectStatistics() result: %s", e.what()); + } } void C4RoundResults::EvaluateNetwork(C4RoundResults::NetResult eNetResult, const char *szResultMsg) diff --git a/src/control/C4RoundResults.h b/src/control/C4RoundResults.h index b21c8147c..7b5808cef 100644 --- a/src/control/C4RoundResults.h +++ b/src/control/C4RoundResults.h @@ -149,6 +149,7 @@ private: // scenario-specific StdCopyStrBuf sCustomEvaluationStrings; + StdCopyStrBuf Statistics; // stats collected by script at the end of the round public: C4RoundResults() : iPlayingTime(0) {} @@ -190,6 +191,8 @@ public: void SetLeaguePerformance(int32_t iNewPerf, int32_t idPlayer = 0); int32_t GetLeaguePerformance(int32_t idPlayer = 0) const; + StdCopyStrBuf GetStatistics() const { return Statistics; } + const C4RoundResultsPlayers &GetPlayers() const { return Players; } const char *GetCustomEvaluationStrings() const { return sCustomEvaluationStrings.getData(); } NetResult GetNetResult() const { return eNetResult; } diff --git a/src/game/C4GameScript.h b/src/game/C4GameScript.h index 734fe24a6..d3171bc97 100644 --- a/src/game/C4GameScript.h +++ b/src/game/C4GameScript.h @@ -102,6 +102,8 @@ bool C4ValueToMatrix(const C4ValueArray& array, StdMeshMatrix* matrix); #define PSF_CommandFailure "~CommandFailure" // string command, pTarget, iTx, iTy, pTarget2, iData #define PSF_OnCompletionChange "~OnCompletionChange" // int old_con, int new_con +#define PSF_CollectStatistics "CollectStatistics" + // Effect callbacks #define PSF_FxStart "Fx%sStart" // C4Object *pTarget, int iEffectNumber, int iTemp, C4Value vVar1, C4Value vVar2, C4Value vVar3, C4Value vVar4 diff --git a/src/network/C4Network2Reference.cpp b/src/network/C4Network2Reference.cpp index 0a78809e7..01cb6ca5f 100644 --- a/src/network/C4Network2Reference.cpp +++ b/src/network/C4Network2Reference.cpp @@ -80,6 +80,7 @@ void C4Network2Reference::InitLocal() IsEditor = !!::Application.isEditor; NetpuncherGameID = ::Network.getNetpuncherGameID(); NetpuncherAddr = ::Network.getNetpuncherAddr(); + Statistics = ::Game.RoundResults.GetStatistics(); Game.Set(); // Addresses @@ -129,6 +130,7 @@ void C4Network2Reference::CompileFunc(StdCompiler *pComp) pComp->Value(mkNamingAdapt(OfficialServer, "OfficialServer", false)); pComp->Value(mkNamingAdapt(NetpuncherGameID, "NetpuncherID", C4NetpuncherID(), false, false)); pComp->Value(mkNamingAdapt(NetpuncherAddr, "NetpuncherAddr", "", false, false)); + pComp->Value(mkNamingAdapt(Statistics, "Statistics", "", false, false)); pComp->Value(Parameters); } diff --git a/src/network/C4Network2Reference.h b/src/network/C4Network2Reference.h index 12a3cee35..d807e419d 100644 --- a/src/network/C4Network2Reference.h +++ b/src/network/C4Network2Reference.h @@ -54,6 +54,7 @@ private: bool IsEditor; C4NetpuncherID NetpuncherGameID; StdCopyStrBuf NetpuncherAddr; + StdCopyStrBuf Statistics; // Engine information C4GameVersion Game;