GameStatistics.cpp 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
  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 "CGameState.h"
  15. #include "TerrainHandler.h"
  16. #include "CHeroHandler.h"
  17. #include "StartInfo.h"
  18. #include "HighScore.h"
  19. #include "../mapObjects/CGHeroInstance.h"
  20. #include "../mapObjects/CGTownInstance.h"
  21. #include "../mapObjects/CGObjectInstance.h"
  22. #include "../mapObjects/MiscObjects.h"
  23. #include "../mapping/CMap.h"
  24. VCMI_LIB_NAMESPACE_BEGIN
  25. void StatisticDataSet::add(StatisticDataSetEntry entry)
  26. {
  27. data.push_back(entry);
  28. }
  29. StatisticDataSetEntry StatisticDataSet::createEntry(const PlayerState * ps, const CGameState * gs)
  30. {
  31. StatisticDataSetEntry data;
  32. HighScoreParameter param = HighScore::prepareHighScores(gs, ps->color, false);
  33. HighScoreCalculation scenarioHighScores;
  34. scenarioHighScores.parameters.push_back(param);
  35. scenarioHighScores.isCampaign = false;
  36. data.day = gs->getDate(Date::DAY);
  37. data.player = ps->color;
  38. data.team = ps->team;
  39. data.isHuman = ps->isHuman();
  40. data.status = ps->status;
  41. data.resources = ps->resources;
  42. data.numberHeroes = ps->heroes.size();
  43. data.numberTowns = gs->howManyTowns(ps->color);
  44. data.numberArtifacts = Statistic::getNumberOfArts(ps);
  45. data.armyStrength = Statistic::getArmyStrength(ps, true);
  46. data.income = Statistic::getIncome(gs, ps);
  47. data.mapVisitedRatio = Statistic::getMapVisitedRatio(gs, ps->color);
  48. data.obeliskVisited = Statistic::getObeliskVisited(gs, ps->team);
  49. data.mightMagicRatio = Statistic::getMightMagicRatio(ps);
  50. data.numMines = Statistic::getNumMines(gs, ps);
  51. data.score = scenarioHighScores.calculate().total;
  52. data.maxHeroLevel = Statistic::findBestHero(gs, ps->color)->level;
  53. data.numBattlesNeutral = gs->statistic.values.numBattlesNeutral.count(ps->color) ? gs->statistic.values.numBattlesNeutral.at(ps->color) : 0;
  54. data.numBattlesPlayer = gs->statistic.values.numBattlesPlayer.count(ps->color) ? gs->statistic.values.numBattlesPlayer.at(ps->color) : 0;
  55. data.numWinBattlesNeutral = gs->statistic.values.numWinBattlesNeutral.count(ps->color) ? gs->statistic.values.numWinBattlesNeutral.at(ps->color) : 0;
  56. data.numWinBattlesPlayer = gs->statistic.values.numWinBattlesPlayer.count(ps->color) ? gs->statistic.values.numWinBattlesPlayer.at(ps->color) : 0;
  57. return data;
  58. }
  59. std::string StatisticDataSet::toCsv()
  60. {
  61. std::stringstream ss;
  62. auto resources = std::vector<EGameResID>{EGameResID::GOLD, EGameResID::WOOD, EGameResID::MERCURY, EGameResID::ORE, EGameResID::SULFUR, EGameResID::CRYSTAL, EGameResID::GEMS};
  63. ss << "Day" << ";";
  64. ss << "Player" << ";";
  65. ss << "Team" << ";";
  66. ss << "IsHuman" << ";";
  67. ss << "Status" << ";";
  68. ss << "NumberHeroes" << ";";
  69. ss << "NumberTowns" << ";";
  70. ss << "NumberArtifacts" << ";";
  71. ss << "ArmyStrength" << ";";
  72. ss << "Income" << ";";
  73. ss << "MapVisitedRatio" << ";";
  74. ss << "ObeliskVisited" << ";";
  75. ss << "MightMagicRatio" << ";";
  76. ss << "Score" << ";";
  77. ss << "MaxHeroLevel" << ";";
  78. ss << "NumBattlesNeutral" << ";";
  79. ss << "NumBattlesPlayer" << ";";
  80. ss << "NumWinBattlesNeutral" << ";";
  81. ss << "NumWinBattlesPlayer";
  82. for(auto & resource : resources)
  83. ss << ";" << GameConstants::RESOURCE_NAMES[resource];
  84. for(auto & resource : resources)
  85. ss << ";" << GameConstants::RESOURCE_NAMES[resource] + "Mines";
  86. ss << "\r\n";
  87. for(auto & entry : data)
  88. {
  89. ss << entry.day << ";";
  90. ss << GameConstants::PLAYER_COLOR_NAMES[entry.player] << ";";
  91. ss << entry.team.getNum() << ";";
  92. ss << entry.isHuman << ";";
  93. ss << (int)entry.status << ";";
  94. ss << entry.numberHeroes << ";";
  95. ss << entry.numberTowns << ";";
  96. ss << entry.numberArtifacts << ";";
  97. ss << entry.armyStrength << ";";
  98. ss << entry.income << ";";
  99. ss << entry.mapVisitedRatio << ";";
  100. ss << entry.obeliskVisited << ";";
  101. ss << entry.mightMagicRatio << ";";
  102. ss << entry.score << ";";
  103. ss << entry.maxHeroLevel << ";";
  104. ss << entry.numBattlesNeutral << ";";
  105. ss << entry.numBattlesPlayer << ";";
  106. ss << entry.numWinBattlesNeutral << ";";
  107. ss << entry.numWinBattlesPlayer;
  108. for(auto & resource : resources)
  109. ss << ";" << entry.resources[resource];
  110. for(auto & resource : resources)
  111. ss << ";" << entry.numMines[resource];
  112. ss << "\r\n";
  113. }
  114. return ss.str();
  115. }
  116. std::vector<const CGMine *> Statistic::getMines(const CGameState * gs, const PlayerState * ps)
  117. {
  118. std::vector<const CGMine *> tmp;
  119. /// FIXME: Dirty dirty hack
  120. /// Stats helper need some access to gamestate.
  121. std::vector<const CGObjectInstance *> ownedObjects;
  122. for(const CGObjectInstance * obj : gs->map->objects)
  123. {
  124. if(obj && obj->tempOwner == ps->color)
  125. ownedObjects.push_back(obj);
  126. }
  127. /// This is code from CPlayerSpecificInfoCallback::getMyObjects
  128. /// I'm really need to find out about callback interface design...
  129. for(const auto * object : ownedObjects)
  130. {
  131. //Mines
  132. if ( object->ID == Obj::MINE )
  133. {
  134. const auto * mine = dynamic_cast<const CGMine *>(object);
  135. assert(mine);
  136. tmp.push_back(mine);
  137. }
  138. }
  139. return tmp;
  140. }
  141. //calculates total number of artifacts that belong to given player
  142. int Statistic::getNumberOfArts(const PlayerState * ps)
  143. {
  144. int ret = 0;
  145. for(auto h : ps->heroes)
  146. {
  147. ret += (int)h->artifactsInBackpack.size() + (int)h->artifactsWorn.size();
  148. }
  149. return ret;
  150. }
  151. // get total strength of player army
  152. si64 Statistic::getArmyStrength(const PlayerState * ps, bool withTownGarrison)
  153. {
  154. si64 str = 0;
  155. for(auto h : ps->heroes)
  156. {
  157. if(!h->inTownGarrison || withTownGarrison) //original h3 behavior
  158. str += h->getArmyStrength();
  159. }
  160. return str;
  161. }
  162. // get total gold income
  163. int Statistic::getIncome(const CGameState * gs, const PlayerState * ps)
  164. {
  165. int percentIncome = gs->getStartInfo()->getIthPlayersSettings(ps->color).handicap.percentIncome;
  166. int totalIncome = 0;
  167. const CGObjectInstance * heroOrTown = nullptr;
  168. //Heroes can produce gold as well - skill, specialty or arts
  169. for(const auto & h : ps->heroes)
  170. {
  171. totalIncome += h->valOfBonuses(Selector::typeSubtype(BonusType::GENERATE_RESOURCE, BonusSubtypeID(GameResID(GameResID::GOLD)))) * percentIncome / 100;
  172. if(!heroOrTown)
  173. heroOrTown = h;
  174. }
  175. //Add town income of all towns
  176. for(const auto & t : ps->towns)
  177. {
  178. totalIncome += t->dailyIncome()[EGameResID::GOLD];
  179. if(!heroOrTown)
  180. heroOrTown = t;
  181. }
  182. for(const CGMine * mine : getMines(gs, ps))
  183. {
  184. if (mine->producedResource == EGameResID::GOLD)
  185. totalIncome += mine->getProducedQuantity();
  186. }
  187. return totalIncome;
  188. }
  189. double Statistic::getMapVisitedRatio(const CGameState * gs, PlayerColor player)
  190. {
  191. double visible = 0.0;
  192. double numTiles = 0.0;
  193. for(int layer = 0; layer < (gs->map->twoLevel ? 2 : 1); layer++)
  194. for(int y = 0; y < gs->map->height; ++y)
  195. for(int x = 0; x < gs->map->width; ++x)
  196. {
  197. TerrainTile tile = gs->map->getTile(int3(x, y, layer));
  198. if(tile.blocked && (!tile.visitable))
  199. continue;
  200. if(gs->isVisible(int3(x, y, layer), player))
  201. visible++;
  202. numTiles++;
  203. }
  204. return visible / numTiles;
  205. }
  206. const CGHeroInstance * Statistic::findBestHero(const CGameState * gs, const PlayerColor & color)
  207. {
  208. auto &h = gs->players.at(color).heroes;
  209. if(h.empty())
  210. return nullptr;
  211. //best hero will be that with highest exp
  212. int best = 0;
  213. for(int b=1; b<h.size(); ++b)
  214. {
  215. if(h[b]->exp > h[best]->exp)
  216. {
  217. best = b;
  218. }
  219. }
  220. return h[best];
  221. }
  222. std::vector<std::vector<PlayerColor>> Statistic::getRank(std::vector<TStat> stats)
  223. {
  224. std::sort(stats.begin(), stats.end(), [](const TStat & a, const TStat & b) { return a.second > b.second; });
  225. //put first element
  226. std::vector< std::vector<PlayerColor> > ret;
  227. std::vector<PlayerColor> tmp;
  228. tmp.push_back( stats[0].first );
  229. ret.push_back( tmp );
  230. //the rest of elements
  231. for(int g=1; g<stats.size(); ++g)
  232. {
  233. if(stats[g].second == stats[g-1].second)
  234. {
  235. (ret.end()-1)->push_back( stats[g].first );
  236. }
  237. else
  238. {
  239. //create next occupied rank
  240. std::vector<PlayerColor> tmp;
  241. tmp.push_back(stats[g].first);
  242. ret.push_back(tmp);
  243. }
  244. }
  245. return ret;
  246. }
  247. int Statistic::getObeliskVisited(const CGameState * gs, const TeamID & t)
  248. {
  249. if(gs->map->obelisksVisited.count(t))
  250. return gs->map->obelisksVisited.at(t);
  251. else
  252. return 0;
  253. }
  254. double Statistic::getMightMagicRatio(const PlayerState * ps)
  255. {
  256. double numMight = 0;
  257. for(auto h : ps->heroes)
  258. if(h->type->heroClass->affinity == CHeroClass::EClassAffinity::MIGHT)
  259. numMight++;
  260. return numMight / ps->heroes.size();
  261. }
  262. std::map<EGameResID, int> Statistic::getNumMines(const CGameState * gs, const PlayerState * ps)
  263. {
  264. std::map<EGameResID, int> tmp;
  265. for(auto & res : EGameResID::ALL_RESOURCES())
  266. tmp[res] = 0;
  267. for(const CGMine * mine : getMines(gs, ps))
  268. tmp[mine->producedResource]++;
  269. return tmp;
  270. }
  271. VCMI_LIB_NAMESPACE_END