ResourceTrader.cpp 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. /*
  2. * ResourceTrader.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. #include "StdInc.h"
  10. #include "ResourceTrader.h"
  11. namespace NK2AI
  12. {
  13. bool ResourceTrader::trade(BuildAnalyzer & buildAnalyzer, CCallback & cc, const TResources & freeResources)
  14. {
  15. bool haveTraded = false;
  16. ObjectInstanceID marketId;
  17. // TODO: Mircea: What about outside town markets that have better rates than a single town for example?
  18. // Are those used anywhere? To inspect.
  19. for(const auto * const town : cc.getTownsInfo())
  20. {
  21. if(town->hasBuiltResourceMarketplace())
  22. {
  23. marketId = town->id;
  24. break;
  25. }
  26. }
  27. if(!marketId.hasValue())
  28. return false;
  29. const CGObjectInstance * obj = cc.getObj(marketId, false);
  30. assert(obj);
  31. // if (!obj)
  32. // return false;
  33. const auto * market = dynamic_cast<const IMarket *>(obj);
  34. assert(market);
  35. // if (!market)
  36. // return false;
  37. bool shouldTryToTrade = true;
  38. while(shouldTryToTrade)
  39. {
  40. shouldTryToTrade = false;
  41. buildAnalyzer.update();
  42. // if we favor getResourcesRequiredNow is better on short term, if we favor getTotalResourcesRequired is better on long term
  43. TResources missingNow = buildAnalyzer.getMissingResourcesNow(ARMY_GOLD_RATIO_PER_MAKE_TURN_PASS);
  44. if(missingNow.empty())
  45. break;
  46. const TResources income = buildAnalyzer.getDailyIncome();
  47. // We don't want to sell something that's necessary later on, though that could make short term a bit harder sometimes
  48. // TODO: Mircea: Consider allowing the sale of just a few resources even if necessary long term if critical short term
  49. // to buy a capitol for example
  50. TResources freeAfterMissingTotal = buildAnalyzer.getFreeResourcesAfterMissingTotal(ARMY_GOLD_RATIO_PER_MAKE_TURN_PASS);
  51. logAi->info(
  52. "ResourceTrader: Free %s. FreeAfterMissingTotal %s. MissingNow %s",
  53. freeResources.toString(),
  54. freeAfterMissingTotal.toString(),
  55. missingNow.toString()
  56. );
  57. if(tradeHelper(EXPENDABLE_BULK_RATIO, *market, missingNow, income, freeAfterMissingTotal, buildAnalyzer, cc))
  58. {
  59. haveTraded = true;
  60. shouldTryToTrade = true;
  61. }
  62. }
  63. return haveTraded;
  64. }
  65. bool ResourceTrader::tradeHelper(
  66. const float expendableBulkRatio,
  67. const IMarket & market,
  68. TResources missingNow,
  69. TResources income,
  70. TResources freeAfterMissingTotal,
  71. const BuildAnalyzer & buildAnalyzer,
  72. CCallback & cc
  73. )
  74. {
  75. constexpr int EMPTY = -1;
  76. int mostWanted = EMPTY;
  77. TResource mostWantedScoreNeg = std::numeric_limits<TResource>::max();
  78. int mostExpendable = EMPTY;
  79. TResource mostExpendableAmountPos = 0;
  80. // Find the most wanted resource
  81. for(int i = 0; i < missingNow.size(); ++i)
  82. {
  83. if(missingNow[i] == 0)
  84. continue;
  85. TResource score = income[i] - missingNow[i];
  86. if(i != GameResID::GOLD)
  87. {
  88. int givenPerUnit;
  89. int receivedPerUnit;
  90. market.getOffer(i, GameResID::GOLD, givenPerUnit, receivedPerUnit, EMarketMode::RESOURCE_RESOURCE);
  91. score *= receivedPerUnit;
  92. logAi->trace("ResourceTrader: mostWantedScoreNeg %d for %d with market receivedPerUnit %d", mostWantedScoreNeg, i, receivedPerUnit);
  93. }
  94. if(score < mostWantedScoreNeg)
  95. {
  96. mostWanted = i;
  97. mostWantedScoreNeg = score;
  98. }
  99. }
  100. // Find the most expendable resource
  101. for(int i = 0; i < missingNow.size(); ++i)
  102. {
  103. const TResource amountToSell = freeAfterMissingTotal[i];
  104. if(amountToSell == 0)
  105. continue;
  106. bool okToSell = false;
  107. if(i == GameResID::GOLD)
  108. {
  109. // TODO: Mircea: Check if we should negate isGoldPressureOverMax() instead
  110. if(income[GameResID::GOLD] > 0 && !buildAnalyzer.isGoldPressureOverMax())
  111. okToSell = true;
  112. }
  113. else
  114. {
  115. okToSell = true;
  116. }
  117. if(okToSell && amountToSell > mostExpendableAmountPos)
  118. {
  119. mostExpendable = i;
  120. mostExpendableAmountPos = amountToSell;
  121. }
  122. }
  123. logAi->trace(
  124. "ResourceTrader: mostWanted: %d, mostWantedScoreNeg %d, mostExpendable: %d, mostExpendableAmountPos %d",
  125. mostWanted,
  126. mostWantedScoreNeg,
  127. mostExpendable,
  128. mostExpendableAmountPos
  129. );
  130. if(mostExpendable == mostWanted || mostWanted == EMPTY || mostExpendable == EMPTY)
  131. return false;
  132. int givenPerUnit;
  133. int receivedPerUnit;
  134. market.getOffer(mostExpendable, mostWanted, givenPerUnit, receivedPerUnit, EMarketMode::RESOURCE_RESOURCE);
  135. if(!givenPerUnit || !receivedPerUnit)
  136. {
  137. logGlobal->error(
  138. "ResourceTrader: No offer for %d of %d, given %d, received %d. Should never happen", mostExpendable, mostWanted, givenPerUnit, receivedPerUnit
  139. );
  140. return false;
  141. }
  142. // TODO: Mircea: if 15 wood and 14 gems, gems can be used a lot more for buying other things
  143. if(givenPerUnit > mostExpendableAmountPos)
  144. return false;
  145. TResource multiplier = std::min(
  146. static_cast<int>(mostExpendableAmountPos * expendableBulkRatio / givenPerUnit),
  147. missingNow[mostWanted] / receivedPerUnit
  148. ); // for gold we have to / receivedUnits, because 1 ore gives many gold units
  149. if(multiplier == 0) // could happen for very small values due to EXPENDABLE_BULK_RATIO
  150. multiplier = 1;
  151. const TResource givenMultiplied = givenPerUnit * multiplier;
  152. if(givenMultiplied > freeAfterMissingTotal[mostExpendable])
  153. {
  154. logGlobal->error("ResourceTrader: Something went wrong with the multiplier %d", multiplier);
  155. return false;
  156. }
  157. cc.trade(market.getObjInstanceID(), EMarketMode::RESOURCE_RESOURCE, GameResID(mostExpendable), GameResID(mostWanted), givenMultiplied);
  158. logAi->info("ResourceTrader: Traded %d of %s for %d receivedPerUnit of %s", givenMultiplied, mostExpendable, receivedPerUnit, mostWanted);
  159. return true;
  160. }
  161. }