GameStatistics.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390
  1. /*
  2. * GameStatistics.cpp, part of VCMI engine
  3. *
  4. * Authors: listed in file AUTHORS in main folder
  5. *
  6. * License: GNU General Public License v2.0 or later
  7. * Full text of license available in license.txt file, in main folder
  8. *
  9. */
  10. #include "StdInc.h"
  11. #include "GameStatistics.h"
  12. #include "../CPlayerState.h"
  13. #include "../constants/StringConstants.h"
  14. #include "../VCMIDirs.h"
  15. #include "CGameState.h"
  16. #include "TerrainHandler.h"
  17. #include "CHeroHandler.h"
  18. #include "StartInfo.h"
  19. #include "HighScore.h"
  20. #include "../mapObjects/CGHeroInstance.h"
  21. #include "../mapObjects/CGTownInstance.h"
  22. #include "../mapObjects/CGObjectInstance.h"
  23. #include "../mapObjects/MiscObjects.h"
  24. #include "../mapping/CMap.h"
  25. #include "../entities/building/CBuilding.h"
  26. VCMI_LIB_NAMESPACE_BEGIN
  27. void StatisticDataSet::add(StatisticDataSetEntry entry)
  28. {
  29. data.push_back(entry);
  30. }
  31. StatisticDataSetEntry StatisticDataSet::createEntry(const PlayerState * ps, const CGameState * gs)
  32. {
  33. StatisticDataSetEntry data;
  34. HighScoreParameter param = HighScore::prepareHighScores(gs, ps->color, false);
  35. HighScoreCalculation scenarioHighScores;
  36. scenarioHighScores.parameters.push_back(param);
  37. scenarioHighScores.isCampaign = false;
  38. data.map = gs->map->name.toString();
  39. data.timestamp = std::time(nullptr);
  40. data.day = gs->getDate(Date::DAY);
  41. data.player = ps->color;
  42. data.playerName = gs->getStartInfo()->playerInfos.at(ps->color).name;
  43. data.team = ps->team;
  44. data.isHuman = ps->isHuman();
  45. data.status = ps->status;
  46. data.resources = ps->resources;
  47. data.numberHeroes = ps->heroes.size();
  48. data.numberTowns = gs->howManyTowns(ps->color);
  49. data.numberArtifacts = Statistic::getNumberOfArts(ps);
  50. data.numberDwellings = gs->getPlayerState(ps->color)->dwellings.size();
  51. data.armyStrength = Statistic::getArmyStrength(ps, true);
  52. data.totalExperience = Statistic::getTotalExperience(ps);
  53. data.income = Statistic::getIncome(gs, ps);
  54. data.mapExploredRatio = Statistic::getMapExploredRatio(gs, ps->color);
  55. data.obeliskVisitedRatio = Statistic::getObeliskVisitedRatio(gs, ps->team);
  56. data.townBuiltRatio = Statistic::getTownBuiltRatio(ps);
  57. data.hasGrail = param.hasGrail;
  58. data.numMines = Statistic::getNumMines(gs, ps);
  59. data.score = scenarioHighScores.calculate().total;
  60. data.maxHeroLevel = Statistic::findBestHero(gs, ps->color) ? Statistic::findBestHero(gs, ps->color)->level : 0;
  61. data.numBattlesNeutral = gs->statistic.accumulatedValues.count(ps->color) ? gs->statistic.accumulatedValues.at(ps->color).numBattlesNeutral : 0;
  62. data.numBattlesPlayer = gs->statistic.accumulatedValues.count(ps->color) ? gs->statistic.accumulatedValues.at(ps->color).numBattlesPlayer : 0;
  63. data.numWinBattlesNeutral = gs->statistic.accumulatedValues.count(ps->color) ? gs->statistic.accumulatedValues.at(ps->color).numWinBattlesNeutral : 0;
  64. data.numWinBattlesPlayer = gs->statistic.accumulatedValues.count(ps->color) ? gs->statistic.accumulatedValues.at(ps->color).numWinBattlesPlayer : 0;
  65. data.numHeroSurrendered = gs->statistic.accumulatedValues.count(ps->color) ? gs->statistic.accumulatedValues.at(ps->color).numHeroSurrendered : 0;
  66. data.numHeroEscaped = gs->statistic.accumulatedValues.count(ps->color) ? gs->statistic.accumulatedValues.at(ps->color).numHeroEscaped : 0;
  67. data.spentResourcesForArmy = gs->statistic.accumulatedValues.count(ps->color) ? gs->statistic.accumulatedValues.at(ps->color).spentResourcesForArmy : TResources();
  68. data.spentResourcesForBuildings = gs->statistic.accumulatedValues.count(ps->color) ? gs->statistic.accumulatedValues.at(ps->color).spentResourcesForBuildings : TResources();
  69. data.tradeVolume = gs->statistic.accumulatedValues.count(ps->color) ? gs->statistic.accumulatedValues.at(ps->color).tradeVolume : TResources();
  70. data.eventCapturedTown = gs->statistic.accumulatedValues.count(ps->color) ? gs->statistic.accumulatedValues.at(ps->color).lastCapturedTownDay == gs->getDate(Date::DAY) : false;
  71. data.eventDefeatedStrongestHero = gs->statistic.accumulatedValues.count(ps->color) ? gs->statistic.accumulatedValues.at(ps->color).lastDefeatedStrongestHeroDay == gs->getDate(Date::DAY) : false;
  72. data.eventRansackingDragonUtopia = gs->statistic.accumulatedValues.count(ps->color) ? gs->statistic.accumulatedValues.at(ps->color).lastRansackingDragonUtopiaDay == gs->getDate(Date::DAY) : false;
  73. data.movementPointsUsed = gs->statistic.accumulatedValues.count(ps->color) ? gs->statistic.accumulatedValues.at(ps->color).movementPointsUsed : 0;
  74. return data;
  75. }
  76. std::string StatisticDataSet::toCsv(std::string sep)
  77. {
  78. std::stringstream ss;
  79. auto resources = std::vector<EGameResID>{EGameResID::GOLD, EGameResID::WOOD, EGameResID::MERCURY, EGameResID::ORE, EGameResID::SULFUR, EGameResID::CRYSTAL, EGameResID::GEMS};
  80. ss << "Map" << sep;
  81. ss << "Timestamp" << sep;
  82. ss << "Day" << sep;
  83. ss << "Player" << sep;
  84. ss << "PlayerName" << sep;
  85. ss << "Team" << sep;
  86. ss << "IsHuman" << sep;
  87. ss << "Status" << sep;
  88. ss << "NumberHeroes" << sep;
  89. ss << "NumberTowns" << sep;
  90. ss << "NumberArtifacts" << sep;
  91. ss << "NumberDwellings" << sep;
  92. ss << "ArmyStrength" << sep;
  93. ss << "TotalExperience" << sep;
  94. ss << "Income" << sep;
  95. ss << "MapExploredRatio" << sep;
  96. ss << "ObeliskVisitedRatio" << sep;
  97. ss << "TownBuiltRatio" << sep;
  98. ss << "HasGrail" << sep;
  99. ss << "Score" << sep;
  100. ss << "MaxHeroLevel" << sep;
  101. ss << "NumBattlesNeutral" << sep;
  102. ss << "NumBattlesPlayer" << sep;
  103. ss << "NumWinBattlesNeutral" << sep;
  104. ss << "NumWinBattlesPlayer" << sep;
  105. ss << "NumHeroSurrendered" << sep;
  106. ss << "NumHeroEscaped" << sep;
  107. ss << "EventCapturedTown" << sep;
  108. ss << "EventDefeatedStrongestHero" << sep;
  109. ss << "EventRansackingDragonUtopia" << sep;
  110. ss << "MovementPointsUsed";
  111. for(auto & resource : resources)
  112. ss << sep << GameConstants::RESOURCE_NAMES[resource];
  113. for(auto & resource : resources)
  114. ss << sep << GameConstants::RESOURCE_NAMES[resource] + "Mines";
  115. for(auto & resource : resources)
  116. ss << sep << GameConstants::RESOURCE_NAMES[resource] + "SpentResourcesForArmy";
  117. for(auto & resource : resources)
  118. ss << sep << GameConstants::RESOURCE_NAMES[resource] + "SpentResourcesForBuildings";
  119. for(auto & resource : resources)
  120. ss << sep << GameConstants::RESOURCE_NAMES[resource] + "TradeVolume";
  121. ss << "\r\n";
  122. for(auto & entry : data)
  123. {
  124. ss << entry.map << sep;
  125. ss << vstd::getFormattedDateTime(entry.timestamp, "%Y-%m-%dT%H:%M:%S") << sep;
  126. ss << entry.day << sep;
  127. ss << GameConstants::PLAYER_COLOR_NAMES[entry.player] << sep;
  128. ss << entry.playerName << sep;
  129. ss << entry.team.getNum() << sep;
  130. ss << entry.isHuman << sep;
  131. ss << static_cast<int>(entry.status) << sep;
  132. ss << entry.numberHeroes << sep;
  133. ss << entry.numberTowns << sep;
  134. ss << entry.numberArtifacts << sep;
  135. ss << entry.numberDwellings << sep;
  136. ss << entry.armyStrength << sep;
  137. ss << entry.totalExperience << sep;
  138. ss << entry.income << sep;
  139. ss << entry.mapExploredRatio << sep;
  140. ss << entry.obeliskVisitedRatio << sep;
  141. ss << entry.townBuiltRatio << sep;
  142. ss << entry.hasGrail << sep;
  143. ss << entry.score << sep;
  144. ss << entry.maxHeroLevel << sep;
  145. ss << entry.numBattlesNeutral << sep;
  146. ss << entry.numBattlesPlayer << sep;
  147. ss << entry.numWinBattlesNeutral << sep;
  148. ss << entry.numWinBattlesPlayer << sep;
  149. ss << entry.numHeroSurrendered << sep;
  150. ss << entry.numHeroEscaped << sep;
  151. ss << entry.eventCapturedTown << sep;
  152. ss << entry.eventDefeatedStrongestHero << sep;
  153. ss << entry.eventRansackingDragonUtopia << sep;
  154. ss << entry.movementPointsUsed;
  155. for(auto & resource : resources)
  156. ss << sep << entry.resources[resource];
  157. for(auto & resource : resources)
  158. ss << sep << entry.numMines[resource];
  159. for(auto & resource : resources)
  160. ss << sep << entry.spentResourcesForArmy[resource];
  161. for(auto & resource : resources)
  162. ss << sep << entry.spentResourcesForBuildings[resource];
  163. for(auto & resource : resources)
  164. ss << sep << entry.tradeVolume[resource];
  165. ss << "\r\n";
  166. }
  167. return ss.str();
  168. }
  169. std::string StatisticDataSet::writeCsv()
  170. {
  171. const boost::filesystem::path outPath = VCMIDirs::get().userCachePath() / "statistic";
  172. boost::filesystem::create_directories(outPath);
  173. const boost::filesystem::path filePath = outPath / (vstd::getDateTimeISO8601Basic(std::time(nullptr)) + ".csv");
  174. std::ofstream file(filePath.c_str());
  175. std::string csv = toCsv(";");
  176. file << csv;
  177. return filePath.string();
  178. }
  179. std::vector<const CGMine *> Statistic::getMines(const CGameState * gs, const PlayerState * ps)
  180. {
  181. std::vector<const CGMine *> tmp;
  182. std::vector<const CGObjectInstance *> ownedObjects;
  183. for(const CGObjectInstance * obj : gs->map->objects)
  184. {
  185. if(obj && obj->tempOwner == ps->color)
  186. ownedObjects.push_back(obj);
  187. }
  188. /// This is code from CPlayerSpecificInfoCallback::getMyObjects
  189. /// I'm really need to find out about callback interface design...
  190. for(const auto * object : ownedObjects)
  191. {
  192. //Mines
  193. if ( object->ID == Obj::MINE )
  194. {
  195. const auto * mine = dynamic_cast<const CGMine *>(object);
  196. assert(mine);
  197. tmp.push_back(mine);
  198. }
  199. }
  200. return tmp;
  201. }
  202. //calculates total number of artifacts that belong to given player
  203. int Statistic::getNumberOfArts(const PlayerState * ps)
  204. {
  205. int ret = 0;
  206. for(auto h : ps->heroes)
  207. {
  208. ret += h->artifactsInBackpack.size() + h->artifactsWorn.size();
  209. }
  210. return ret;
  211. }
  212. // get total strength of player army
  213. si64 Statistic::getArmyStrength(const PlayerState * ps, bool withTownGarrison)
  214. {
  215. si64 str = 0;
  216. for(auto h : ps->heroes)
  217. {
  218. if(!h->inTownGarrison || withTownGarrison) //original h3 behavior
  219. str += h->getArmyStrength();
  220. }
  221. return str;
  222. }
  223. // get total experience of all heroes
  224. si64 Statistic::getTotalExperience(const PlayerState * ps)
  225. {
  226. si64 tmp = 0;
  227. for(auto h : ps->heroes)
  228. tmp += h->exp;
  229. return tmp;
  230. }
  231. // get total gold income
  232. int Statistic::getIncome(const CGameState * gs, const PlayerState * ps)
  233. {
  234. int percentIncome = gs->getStartInfo()->getIthPlayersSettings(ps->color).handicap.percentIncome;
  235. int totalIncome = 0;
  236. //Heroes can produce gold as well - skill, specialty or arts
  237. for(const auto & h : ps->heroes)
  238. totalIncome += h->valOfBonuses(Selector::typeSubtype(BonusType::GENERATE_RESOURCE, BonusSubtypeID(GameResID(GameResID::GOLD)))) * percentIncome / 100;
  239. //Add town income of all towns
  240. for(const auto & t : ps->towns)
  241. totalIncome += t->dailyIncome()[EGameResID::GOLD];
  242. for(const CGMine * mine : getMines(gs, ps))
  243. if(mine->producedResource == EGameResID::GOLD)
  244. totalIncome += mine->getProducedQuantity();
  245. return totalIncome;
  246. }
  247. float Statistic::getMapExploredRatio(const CGameState * gs, PlayerColor player)
  248. {
  249. float visible = 0.0;
  250. float numTiles = 0.0;
  251. for(int layer = 0; layer < (gs->map->twoLevel ? 2 : 1); layer++)
  252. for(int y = 0; y < gs->map->height; ++y)
  253. for(int x = 0; x < gs->map->width; ++x)
  254. {
  255. TerrainTile tile = gs->map->getTile(int3(x, y, layer));
  256. if(tile.blocked && (!tile.visitable))
  257. continue;
  258. if(gs->isVisible(int3(x, y, layer), player))
  259. visible++;
  260. numTiles++;
  261. }
  262. return visible / numTiles;
  263. }
  264. const CGHeroInstance * Statistic::findBestHero(const CGameState * gs, const PlayerColor & color)
  265. {
  266. auto &h = gs->players.at(color).heroes;
  267. if(h.empty())
  268. return nullptr;
  269. //best hero will be that with highest exp
  270. int best = 0;
  271. for(int b=1; b<h.size(); ++b)
  272. {
  273. if(h[b]->exp > h[best]->exp)
  274. {
  275. best = b;
  276. }
  277. }
  278. return h[best];
  279. }
  280. std::vector<std::vector<PlayerColor>> Statistic::getRank(std::vector<std::pair<PlayerColor, si64>> stats)
  281. {
  282. std::sort(stats.begin(), stats.end(), [](const std::pair<PlayerColor, si64> & a, const std::pair<PlayerColor, si64> & b) { return a.second > b.second; });
  283. //put first element
  284. std::vector< std::vector<PlayerColor> > ret;
  285. ret.push_back( { stats[0].first } );
  286. //the rest of elements
  287. for(int g=1; g<stats.size(); ++g)
  288. {
  289. if(stats[g].second == stats[g-1].second)
  290. {
  291. (ret.end()-1)->push_back( stats[g].first );
  292. }
  293. else
  294. {
  295. //create next occupied rank
  296. ret.push_back( { stats[g].first });
  297. }
  298. }
  299. return ret;
  300. }
  301. int Statistic::getObeliskVisited(const CGameState * gs, const TeamID & t)
  302. {
  303. if(gs->map->obelisksVisited.count(t))
  304. return gs->map->obelisksVisited.at(t);
  305. else
  306. return 0;
  307. }
  308. float Statistic::getObeliskVisitedRatio(const CGameState * gs, const TeamID & t)
  309. {
  310. if(!gs->map->obeliskCount)
  311. return 0;
  312. return static_cast<float>(getObeliskVisited(gs, t)) / gs->map->obeliskCount;
  313. }
  314. std::map<EGameResID, int> Statistic::getNumMines(const CGameState * gs, const PlayerState * ps)
  315. {
  316. std::map<EGameResID, int> tmp;
  317. for(auto & res : EGameResID::ALL_RESOURCES())
  318. tmp[res] = 0;
  319. for(const CGMine * mine : getMines(gs, ps))
  320. tmp[mine->producedResource]++;
  321. return tmp;
  322. }
  323. float Statistic::getTownBuiltRatio(const PlayerState * ps)
  324. {
  325. float built = 0.0;
  326. float total = 0.0;
  327. for(const auto & t : ps->towns)
  328. {
  329. built += t->builtBuildings.size();
  330. for(const auto & b : t->town->buildings)
  331. if(!t->forbiddenBuildings.count(b.first))
  332. total += 1;
  333. }
  334. if(total < 1)
  335. return 0;
  336. return built / total;
  337. }
  338. VCMI_LIB_NAMESPACE_END