TreasurePlacer.cpp 30 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009
  1. /*
  2. * TreasurePlacer.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 "TreasurePlacer.h"
  12. #include "../CMapGenerator.h"
  13. #include "../Functions.h"
  14. #include "ObjectManager.h"
  15. #include "RoadPlacer.h"
  16. #include "ConnectionsPlacer.h"
  17. #include "../RmgMap.h"
  18. #include "../TileInfo.h"
  19. #include "../CZonePlacer.h"
  20. #include "PrisonHeroPlacer.h"
  21. #include "QuestArtifactPlacer.h"
  22. #include "../../ArtifactUtils.h"
  23. #include "../../mapObjectConstructors/AObjectTypeHandler.h"
  24. #include "../../mapObjectConstructors/CObjectClassesHandler.h"
  25. #include "../../mapObjectConstructors/DwellingInstanceConstructor.h"
  26. #include "../../mapObjects/CGHeroInstance.h"
  27. #include "../../mapObjects/CGPandoraBox.h"
  28. #include "../../CCreatureHandler.h"
  29. #include "../../spells/CSpellHandler.h" //for choosing random spells
  30. #include "../../mapping/CMap.h"
  31. #include "../../mapping/CMapEditManager.h"
  32. VCMI_LIB_NAMESPACE_BEGIN
  33. ObjectInfo::ObjectInfo():
  34. destroyObject([](CGObjectInstance * obj){})
  35. {
  36. }
  37. void TreasurePlacer::process()
  38. {
  39. addAllPossibleObjects();
  40. auto * m = zone.getModificator<ObjectManager>();
  41. if(m)
  42. createTreasures(*m);
  43. }
  44. void TreasurePlacer::init()
  45. {
  46. maxPrisons = 0; //Should be in the constructor, but we use macro for that
  47. DEPENDENCY(ObjectManager);
  48. DEPENDENCY(ConnectionsPlacer);
  49. DEPENDENCY_ALL(PrisonHeroPlacer);
  50. POSTFUNCTION(RoadPlacer);
  51. }
  52. void TreasurePlacer::addObjectToRandomPool(const ObjectInfo& oi)
  53. {
  54. RecursiveLock lock(externalAccessMutex);
  55. possibleObjects.push_back(oi);
  56. }
  57. void TreasurePlacer::addAllPossibleObjects()
  58. {
  59. ObjectInfo oi;
  60. for(auto primaryID : VLC->objtypeh->knownObjects())
  61. {
  62. for(auto secondaryID : VLC->objtypeh->knownSubObjects(primaryID))
  63. {
  64. auto handler = VLC->objtypeh->getHandlerFor(primaryID, secondaryID);
  65. if(!handler->isStaticObject() && handler->getRMGInfo().value)
  66. {
  67. auto rmgInfo = handler->getRMGInfo();
  68. if (rmgInfo.mapLimit || rmgInfo.value > zone.getMaxTreasureValue())
  69. {
  70. //Skip objects with per-map limit here
  71. continue;
  72. }
  73. oi.generateObject = [primaryID, secondaryID]() -> CGObjectInstance *
  74. {
  75. return VLC->objtypeh->getHandlerFor(primaryID, secondaryID)->create();
  76. };
  77. oi.value = rmgInfo.value;
  78. oi.probability = rmgInfo.rarity;
  79. oi.setTemplates(primaryID, secondaryID, zone.getTerrainType());
  80. oi.maxPerZone = rmgInfo.zoneLimit;
  81. if(!oi.templates.empty())
  82. addObjectToRandomPool(oi);
  83. }
  84. }
  85. }
  86. //Generate Prison on water only if it has a template
  87. auto prisonTemplates = VLC->objtypeh->getHandlerFor(Obj::PRISON, 0)->getTemplates(zone.getTerrainType());
  88. if (!prisonTemplates.empty())
  89. {
  90. PrisonHeroPlacer * prisonHeroPlacer = nullptr;
  91. for(auto & z : map.getZones())
  92. {
  93. prisonHeroPlacer = z.second->getModificator<PrisonHeroPlacer>();
  94. if (prisonHeroPlacer)
  95. {
  96. break;
  97. }
  98. }
  99. //prisons
  100. //levels 1, 5, 10, 20, 30
  101. static int prisonsLevels = std::min(generator.getConfig().prisonExperience.size(), generator.getConfig().prisonValues.size());
  102. size_t prisonsLeft = getMaxPrisons();
  103. for (int i = prisonsLevels - 1; i >= 0; i--)
  104. {
  105. ObjectInfo oi; // Create new instance which will hold destructor operation
  106. oi.value = generator.getConfig().prisonValues[i];
  107. if (oi.value > zone.getMaxTreasureValue())
  108. {
  109. continue;
  110. }
  111. oi.generateObject = [i, this, prisonHeroPlacer]() -> CGObjectInstance*
  112. {
  113. HeroTypeID hid = prisonHeroPlacer->drawRandomHero();
  114. auto factory = VLC->objtypeh->getHandlerFor(Obj::PRISON, 0);
  115. auto* obj = dynamic_cast<CGHeroInstance*>(factory->create());
  116. obj->setHeroType(hid); //will be initialized later
  117. obj->exp = generator.getConfig().prisonExperience[i];
  118. obj->setOwner(PlayerColor::NEUTRAL);
  119. return obj;
  120. };
  121. oi.destroyObject = [prisonHeroPlacer](CGObjectInstance* obj)
  122. {
  123. // Hero can be used again
  124. auto* hero = dynamic_cast<CGHeroInstance*>(obj);
  125. prisonHeroPlacer->restoreDrawnHero(hero->getHeroType());
  126. };
  127. oi.setTemplates(Obj::PRISON, 0, zone.getTerrainType());
  128. oi.value = generator.getConfig().prisonValues[i];
  129. oi.probability = 30;
  130. //Distribute all allowed prisons, starting from the most valuable
  131. oi.maxPerZone = (std::ceil((float)prisonsLeft / (i + 1)));
  132. prisonsLeft -= oi.maxPerZone;
  133. if(!oi.templates.empty())
  134. addObjectToRandomPool(oi);
  135. }
  136. }
  137. if(zone.getType() == ETemplateZoneType::WATER)
  138. return;
  139. //all following objects are unlimited
  140. oi.maxPerZone = std::numeric_limits<ui32>::max();
  141. std::vector<CCreature *> creatures; //native creatures for this zone
  142. for(auto cre : VLC->creh->objects)
  143. {
  144. if(!cre->special && cre->getFaction() == zone.getTownType())
  145. {
  146. creatures.push_back(cre);
  147. }
  148. }
  149. //dwellings
  150. auto dwellingTypes = {Obj::CREATURE_GENERATOR1, Obj::CREATURE_GENERATOR4};
  151. for(auto dwellingType : dwellingTypes)
  152. {
  153. auto subObjects = VLC->objtypeh->knownSubObjects(dwellingType);
  154. if(dwellingType == Obj::CREATURE_GENERATOR1)
  155. {
  156. //don't spawn original "neutral" dwellings that got replaced by Conflux dwellings in AB
  157. static MapObjectSubID elementalConfluxROE[] = {7, 13, 16, 47};
  158. for(auto const & i : elementalConfluxROE)
  159. vstd::erase_if_present(subObjects, i);
  160. }
  161. for(auto secondaryID : subObjects)
  162. {
  163. const auto * dwellingHandler = dynamic_cast<const DwellingInstanceConstructor *>(VLC->objtypeh->getHandlerFor(dwellingType, secondaryID).get());
  164. auto creatures = dwellingHandler->getProducedCreatures();
  165. if(creatures.empty())
  166. continue;
  167. const auto * cre = creatures.front();
  168. if(cre->getFaction() == zone.getTownType())
  169. {
  170. auto nativeZonesCount = static_cast<float>(map.getZoneCount(cre->getFaction()));
  171. oi.value = static_cast<ui32>(cre->getAIValue() * cre->getGrowth() * (1 + (nativeZonesCount / map.getTotalZoneCount()) + (nativeZonesCount / 2)));
  172. oi.probability = 40;
  173. oi.generateObject = [secondaryID, dwellingType]() -> CGObjectInstance *
  174. {
  175. auto * obj = VLC->objtypeh->getHandlerFor(dwellingType, secondaryID)->create();
  176. obj->tempOwner = PlayerColor::NEUTRAL;
  177. return obj;
  178. };
  179. oi.setTemplates(dwellingType, secondaryID, zone.getTerrainType());
  180. if(!oi.templates.empty())
  181. addObjectToRandomPool(oi);
  182. }
  183. }
  184. }
  185. for(int i = 0; i < generator.getConfig().scrollValues.size(); i++)
  186. {
  187. oi.generateObject = [i, this]() -> CGObjectInstance *
  188. {
  189. auto factory = VLC->objtypeh->getHandlerFor(Obj::SPELL_SCROLL, 0);
  190. auto * obj = dynamic_cast<CGArtifact *>(factory->create());
  191. std::vector<SpellID> out;
  192. for(auto spell : VLC->spellh->objects) //spellh size appears to be greater (?)
  193. {
  194. if(map.isAllowedSpell(spell->id) && spell->getLevel() == i + 1)
  195. {
  196. out.push_back(spell->id);
  197. }
  198. }
  199. auto * a = ArtifactUtils::createScroll(*RandomGeneratorUtil::nextItem(out, zone.getRand()));
  200. obj->storedArtifact = a;
  201. return obj;
  202. };
  203. oi.setTemplates(Obj::SPELL_SCROLL, 0, zone.getTerrainType());
  204. oi.value = generator.getConfig().scrollValues[i];
  205. oi.probability = 30;
  206. if(!oi.templates.empty())
  207. addObjectToRandomPool(oi);
  208. }
  209. //pandora box with gold
  210. for(int i = 1; i < 5; i++)
  211. {
  212. oi.generateObject = [i]() -> CGObjectInstance *
  213. {
  214. auto factory = VLC->objtypeh->getHandlerFor(Obj::PANDORAS_BOX, 0);
  215. auto * obj = dynamic_cast<CGPandoraBox *>(factory->create());
  216. Rewardable::VisitInfo reward;
  217. reward.reward.resources[EGameResID::GOLD] = i * 5000;
  218. reward.visitType = Rewardable::EEventType::EVENT_FIRST_VISIT;
  219. obj->configuration.info.push_back(reward);
  220. return obj;
  221. };
  222. oi.setTemplates(Obj::PANDORAS_BOX, 0, zone.getTerrainType());
  223. oi.value = i * generator.getConfig().pandoraMultiplierGold;
  224. oi.probability = 5;
  225. if(!oi.templates.empty())
  226. addObjectToRandomPool(oi);
  227. }
  228. //pandora box with experience
  229. for(int i = 1; i < 5; i++)
  230. {
  231. oi.generateObject = [i]() -> CGObjectInstance *
  232. {
  233. auto factory = VLC->objtypeh->getHandlerFor(Obj::PANDORAS_BOX, 0);
  234. auto * obj = dynamic_cast<CGPandoraBox *>(factory->create());
  235. Rewardable::VisitInfo reward;
  236. reward.reward.heroExperience = i * 5000;
  237. reward.visitType = Rewardable::EEventType::EVENT_FIRST_VISIT;
  238. obj->configuration.info.push_back(reward);
  239. return obj;
  240. };
  241. oi.setTemplates(Obj::PANDORAS_BOX, 0, zone.getTerrainType());
  242. oi.value = i * generator.getConfig().pandoraMultiplierExperience;
  243. oi.probability = 20;
  244. if(!oi.templates.empty())
  245. addObjectToRandomPool(oi);
  246. }
  247. //pandora box with creatures
  248. const std::vector<int> & tierValues = generator.getConfig().pandoraCreatureValues;
  249. auto creatureToCount = [tierValues](CCreature * creature) -> int
  250. {
  251. if(!creature->getAIValue() || tierValues.empty()) //bug #2681
  252. return 0; //this box won't be generated
  253. //Follow the rules from https://heroes.thelazy.net/index.php/Pandora%27s_Box
  254. int actualTier = creature->getLevel() > tierValues.size() ?
  255. tierValues.size() - 1 :
  256. creature->getLevel() - 1;
  257. float creaturesAmount = std::floor((static_cast<float>(tierValues[actualTier])) / creature->getAIValue());
  258. if (creaturesAmount < 1)
  259. {
  260. return 0;
  261. }
  262. else if(creaturesAmount <= 5)
  263. {
  264. //No change
  265. }
  266. else if(creaturesAmount <= 12)
  267. {
  268. creaturesAmount = std::ceil(creaturesAmount / 2) * 2;
  269. }
  270. else if(creaturesAmount <= 50)
  271. {
  272. creaturesAmount = std::round(creaturesAmount / 5) * 5;
  273. }
  274. else
  275. {
  276. creaturesAmount = std::round(creaturesAmount / 10) * 10;
  277. }
  278. return static_cast<int>(creaturesAmount);
  279. };
  280. for(auto * creature : creatures)
  281. {
  282. int creaturesAmount = creatureToCount(creature);
  283. if(!creaturesAmount)
  284. continue;
  285. oi.generateObject = [creature, creaturesAmount]() -> CGObjectInstance *
  286. {
  287. auto factory = VLC->objtypeh->getHandlerFor(Obj::PANDORAS_BOX, 0);
  288. auto * obj = dynamic_cast<CGPandoraBox *>(factory->create());
  289. Rewardable::VisitInfo reward;
  290. reward.reward.creatures.emplace_back(creature, creaturesAmount);
  291. reward.visitType = Rewardable::EEventType::EVENT_FIRST_VISIT;
  292. obj->configuration.info.push_back(reward);
  293. return obj;
  294. };
  295. oi.setTemplates(Obj::PANDORAS_BOX, 0, zone.getTerrainType());
  296. oi.value = static_cast<ui32>((2 * (creature->getAIValue()) * creaturesAmount * (1 + static_cast<float>(map.getZoneCount(creature->getFaction())) / map.getTotalZoneCount())) / 3);
  297. oi.probability = 3;
  298. if(!oi.templates.empty())
  299. addObjectToRandomPool(oi);
  300. }
  301. //Pandora with 12 spells of certain level
  302. for(int i = 1; i <= GameConstants::SPELL_LEVELS; i++)
  303. {
  304. oi.generateObject = [i, this]() -> CGObjectInstance *
  305. {
  306. auto factory = VLC->objtypeh->getHandlerFor(Obj::PANDORAS_BOX, 0);
  307. auto * obj = dynamic_cast<CGPandoraBox *>(factory->create());
  308. std::vector <CSpell *> spells;
  309. for(auto spell : VLC->spellh->objects)
  310. {
  311. if(map.isAllowedSpell(spell->id) && spell->getLevel() == i)
  312. spells.push_back(spell);
  313. }
  314. RandomGeneratorUtil::randomShuffle(spells, zone.getRand());
  315. Rewardable::VisitInfo reward;
  316. for(int j = 0; j < std::min(12, static_cast<int>(spells.size())); j++)
  317. {
  318. reward.reward.spells.push_back(spells[j]->id);
  319. }
  320. reward.visitType = Rewardable::EEventType::EVENT_FIRST_VISIT;
  321. obj->configuration.info.push_back(reward);
  322. return obj;
  323. };
  324. oi.setTemplates(Obj::PANDORAS_BOX, 0, zone.getTerrainType());
  325. oi.value = (i + 1) * generator.getConfig().pandoraMultiplierSpells; //5000 - 15000
  326. oi.probability = 2;
  327. if(!oi.templates.empty())
  328. addObjectToRandomPool(oi);
  329. }
  330. //Pandora with 15 spells of certain school
  331. for(int i = 0; i < 4; i++)
  332. {
  333. oi.generateObject = [i, this]() -> CGObjectInstance *
  334. {
  335. auto factory = VLC->objtypeh->getHandlerFor(Obj::PANDORAS_BOX, 0);
  336. auto * obj = dynamic_cast<CGPandoraBox *>(factory->create());
  337. std::vector <CSpell *> spells;
  338. for(auto spell : VLC->spellh->objects)
  339. {
  340. if(map.isAllowedSpell(spell->id) && spell->school[SpellSchool(i)])
  341. spells.push_back(spell);
  342. }
  343. RandomGeneratorUtil::randomShuffle(spells, zone.getRand());
  344. Rewardable::VisitInfo reward;
  345. for(int j = 0; j < std::min(15, static_cast<int>(spells.size())); j++)
  346. {
  347. reward.reward.spells.push_back(spells[j]->id);
  348. }
  349. reward.visitType = Rewardable::EEventType::EVENT_FIRST_VISIT;
  350. obj->configuration.info.push_back(reward);
  351. return obj;
  352. };
  353. oi.setTemplates(Obj::PANDORAS_BOX, 0, zone.getTerrainType());
  354. oi.value = generator.getConfig().pandoraSpellSchool;
  355. oi.probability = 2;
  356. if(!oi.templates.empty())
  357. addObjectToRandomPool(oi);
  358. }
  359. // Pandora box with 60 random spells
  360. oi.generateObject = [this]() -> CGObjectInstance *
  361. {
  362. auto factory = VLC->objtypeh->getHandlerFor(Obj::PANDORAS_BOX, 0);
  363. auto * obj = dynamic_cast<CGPandoraBox *>(factory->create());
  364. std::vector <CSpell *> spells;
  365. for(auto spell : VLC->spellh->objects)
  366. {
  367. if(map.isAllowedSpell(spell->id))
  368. spells.push_back(spell);
  369. }
  370. RandomGeneratorUtil::randomShuffle(spells, zone.getRand());
  371. Rewardable::VisitInfo reward;
  372. for(int j = 0; j < std::min(60, static_cast<int>(spells.size())); j++)
  373. {
  374. reward.reward.spells.push_back(spells[j]->id);
  375. }
  376. reward.visitType = Rewardable::EEventType::EVENT_FIRST_VISIT;
  377. obj->configuration.info.push_back(reward);
  378. return obj;
  379. };
  380. oi.setTemplates(Obj::PANDORAS_BOX, 0, zone.getTerrainType());
  381. oi.value = generator.getConfig().pandoraSpell60;
  382. oi.probability = 2;
  383. if(!oi.templates.empty())
  384. addObjectToRandomPool(oi);
  385. //Seer huts with creatures or generic rewards
  386. if(zone.getConnectedZoneIds().size()) //Unlikely, but...
  387. {
  388. auto * qap = zone.getModificator<QuestArtifactPlacer>();
  389. if(!qap)
  390. {
  391. return; //TODO: throw?
  392. }
  393. const int questArtsRemaining = qap->getMaxQuestArtifactCount();
  394. if (!questArtsRemaining)
  395. {
  396. return;
  397. }
  398. //Generate Seer Hut one by one. Duplicated oi possible and should work fine.
  399. oi.maxPerZone = 1;
  400. std::vector<ObjectInfo> possibleSeerHuts;
  401. //14 creatures per town + 4 for each of gold / exp reward
  402. possibleSeerHuts.reserve(14 + 4 + 4);
  403. RandomGeneratorUtil::randomShuffle(creatures, zone.getRand());
  404. auto setRandomArtifact = [qap](CGSeerHut * obj)
  405. {
  406. ArtifactID artid = qap->drawRandomArtifact();
  407. obj->quest->mission.artifacts.push_back(artid);
  408. qap->addQuestArtifact(artid);
  409. };
  410. auto destroyObject = [qap](CGObjectInstance * obj)
  411. {
  412. auto * seer = dynamic_cast<CGSeerHut *>(obj);
  413. // Artifact can be used again
  414. ArtifactID artid = seer->quest->mission.artifacts.front();
  415. qap->addRandomArtifact(artid);
  416. qap->removeQuestArtifact(artid);
  417. };
  418. for(int i = 0; i < static_cast<int>(creatures.size()); i++)
  419. {
  420. auto * creature = creatures[i];
  421. int creaturesAmount = creatureToCount(creature);
  422. if(!creaturesAmount)
  423. continue;
  424. int randomAppearance = chooseRandomAppearance(zone.getRand(), Obj::SEER_HUT, zone.getTerrainType());
  425. // FIXME: Remove duplicated code for gold, exp and creaure reward
  426. oi.generateObject = [creature, creaturesAmount, randomAppearance, setRandomArtifact]() -> CGObjectInstance *
  427. {
  428. auto factory = VLC->objtypeh->getHandlerFor(Obj::SEER_HUT, randomAppearance);
  429. auto * obj = dynamic_cast<CGSeerHut *>(factory->create());
  430. Rewardable::VisitInfo reward;
  431. reward.reward.creatures.emplace_back(creature->getId(), creaturesAmount);
  432. reward.visitType = Rewardable::EEventType::EVENT_FIRST_VISIT;
  433. obj->configuration.info.push_back(reward);
  434. setRandomArtifact(obj);
  435. return obj;
  436. };
  437. oi.destroyObject = destroyObject;
  438. oi.probability = 3;
  439. oi.setTemplates(Obj::SEER_HUT, randomAppearance, zone.getTerrainType());
  440. oi.value = static_cast<ui32>(((2 * (creature->getAIValue()) * creaturesAmount * (1 + static_cast<float>(map.getZoneCount(creature->getFaction())) / map.getTotalZoneCount())) - 4000) / 3);
  441. if (oi.value > zone.getMaxTreasureValue())
  442. {
  443. continue;
  444. }
  445. else
  446. {
  447. if(!oi.templates.empty())
  448. possibleSeerHuts.push_back(oi);
  449. }
  450. }
  451. static int seerLevels = std::min(generator.getConfig().questValues.size(), generator.getConfig().questRewardValues.size());
  452. for(int i = 0; i < seerLevels; i++) //seems that code for exp and gold reward is similiar
  453. {
  454. int randomAppearance = chooseRandomAppearance(zone.getRand(), Obj::SEER_HUT, zone.getTerrainType());
  455. oi.setTemplates(Obj::SEER_HUT, randomAppearance, zone.getTerrainType());
  456. oi.value = generator.getConfig().questValues[i];
  457. if (oi.value > zone.getMaxTreasureValue())
  458. {
  459. //Both variants have same value
  460. continue;
  461. }
  462. oi.probability = 10;
  463. oi.maxPerZone = 1;
  464. oi.generateObject = [i, randomAppearance, this, setRandomArtifact]() -> CGObjectInstance *
  465. {
  466. auto factory = VLC->objtypeh->getHandlerFor(Obj::SEER_HUT, randomAppearance);
  467. auto * obj = dynamic_cast<CGSeerHut *>(factory->create());
  468. Rewardable::VisitInfo reward;
  469. reward.reward.heroExperience = generator.getConfig().questRewardValues[i];
  470. reward.visitType = Rewardable::EEventType::EVENT_FIRST_VISIT;
  471. obj->configuration.info.push_back(reward);
  472. setRandomArtifact(obj);
  473. return obj;
  474. };
  475. oi.destroyObject = destroyObject;
  476. if(!oi.templates.empty())
  477. possibleSeerHuts.push_back(oi);
  478. oi.generateObject = [i, randomAppearance, this, setRandomArtifact]() -> CGObjectInstance *
  479. {
  480. auto factory = VLC->objtypeh->getHandlerFor(Obj::SEER_HUT, randomAppearance);
  481. auto * obj = dynamic_cast<CGSeerHut *>(factory->create());
  482. Rewardable::VisitInfo reward;
  483. reward.reward.resources[EGameResID::GOLD] = generator.getConfig().questRewardValues[i];
  484. reward.visitType = Rewardable::EEventType::EVENT_FIRST_VISIT;
  485. obj->configuration.info.push_back(reward);
  486. setRandomArtifact(obj);
  487. return obj;
  488. };
  489. oi.destroyObject = destroyObject;
  490. if(!oi.templates.empty())
  491. possibleSeerHuts.push_back(oi);
  492. }
  493. if (possibleSeerHuts.empty())
  494. {
  495. return;
  496. }
  497. for (size_t i = 0; i < questArtsRemaining; i++)
  498. {
  499. addObjectToRandomPool(*RandomGeneratorUtil::nextItem(possibleSeerHuts, zone.getRand()));
  500. }
  501. }
  502. }
  503. size_t TreasurePlacer::getPossibleObjectsSize() const
  504. {
  505. RecursiveLock lock(externalAccessMutex);
  506. return possibleObjects.size();
  507. }
  508. void TreasurePlacer::setMaxPrisons(size_t count)
  509. {
  510. RecursiveLock lock(externalAccessMutex);
  511. maxPrisons = count;
  512. }
  513. size_t TreasurePlacer::getMaxPrisons() const
  514. {
  515. RecursiveLock lock(externalAccessMutex);
  516. return maxPrisons;
  517. }
  518. bool TreasurePlacer::isGuardNeededForTreasure(int value)
  519. {// no guard in a zone with "monsters: none" and for small treasures; water zones cen get monster strength ZONE_NONE elsewhere if needed
  520. return zone.monsterStrength != EMonsterStrength::ZONE_NONE && value > minGuardedValue;
  521. }
  522. std::vector<ObjectInfo*> TreasurePlacer::prepareTreasurePile(const CTreasureInfo& treasureInfo)
  523. {
  524. std::vector<ObjectInfo*> objectInfos;
  525. int maxValue = treasureInfo.max;
  526. int minValue = treasureInfo.min;
  527. const ui32 desiredValue = zone.getRand().nextInt(minValue, maxValue);
  528. int currentValue = 0;
  529. bool hasLargeObject = false;
  530. while(currentValue <= static_cast<int>(desiredValue) - 100) //no objects with value below 100 are available
  531. {
  532. auto * oi = getRandomObject(desiredValue, currentValue, !hasLargeObject);
  533. if(!oi) //fail
  534. break;
  535. bool visitableFromTop = true;
  536. for(auto & t : oi->templates)
  537. if(!t->isVisitableFromTop())
  538. visitableFromTop = false;
  539. if(visitableFromTop)
  540. {
  541. objectInfos.push_back(oi);
  542. }
  543. else
  544. {
  545. objectInfos.insert(objectInfos.begin(), oi); //large object shall at first place
  546. hasLargeObject = true;
  547. }
  548. //remove from possible objects
  549. assert(oi->maxPerZone);
  550. oi->maxPerZone--;
  551. currentValue += oi->value;
  552. if (currentValue >= minValue)
  553. {
  554. // 50% chance to end right here
  555. if (zone.getRand().nextInt() & 1)
  556. break;
  557. }
  558. }
  559. return objectInfos;
  560. }
  561. rmg::Object TreasurePlacer::constructTreasurePile(const std::vector<ObjectInfo*> & treasureInfos, bool densePlacement)
  562. {
  563. rmg::Object rmgObject;
  564. for(const auto & oi : treasureInfos)
  565. {
  566. auto blockedArea = rmgObject.getArea();
  567. auto entrableArea = rmgObject.getEntrableArea();
  568. auto accessibleArea = rmgObject.getAccessibleArea();
  569. if(rmgObject.instances().empty())
  570. {
  571. accessibleArea.add(int3());
  572. }
  573. auto * object = oi->generateObject();
  574. if(oi->templates.empty())
  575. {
  576. logGlobal->warn("Deleting randomized object with no templates: %s", object->getObjectName());
  577. oi->destroyObject(object);
  578. delete object;
  579. continue;
  580. }
  581. auto templates = object->getObjectHandler()->getMostSpecificTemplates(zone.getTerrainType());
  582. if (templates.empty())
  583. {
  584. throw rmgException(boost::str(boost::format("Did not find template for object (%d,%d) at %s") % object->ID % object->subID % zone.getTerrainType().encode(zone.getTerrainType())));
  585. }
  586. object->appearance = *RandomGeneratorUtil::nextItem(templates, zone.getRand());
  587. //Put object in accessible area next to entrable area (excluding blockvis tiles)
  588. if (!entrableArea.empty())
  589. {
  590. auto entrableBorder = entrableArea.getBorderOutside();
  591. accessibleArea.erase_if([&](const int3 & tile)
  592. {
  593. return !entrableBorder.count(tile);
  594. });
  595. }
  596. auto & instance = rmgObject.addInstance(*object);
  597. instance.onCleared = oi->destroyObject;
  598. do
  599. {
  600. if(accessibleArea.empty())
  601. {
  602. //fail - fallback
  603. rmgObject.clear();
  604. return rmgObject;
  605. }
  606. std::vector<int3> bestPositions;
  607. if(densePlacement && !entrableArea.empty())
  608. {
  609. // Choose positon which has access to as many entrable tiles as possible
  610. int bestPositionsWeight = std::numeric_limits<int>::max();
  611. for(const auto & t : accessibleArea.getTilesVector())
  612. {
  613. instance.setPosition(t);
  614. auto currentAccessibleArea = rmgObject.getAccessibleArea();
  615. auto currentEntrableBorder = rmgObject.getEntrableArea().getBorderOutside();
  616. currentAccessibleArea.erase_if([&](const int3 & tile)
  617. {
  618. return !currentEntrableBorder.count(tile);
  619. });
  620. size_t w = currentAccessibleArea.getTilesVector().size();
  621. if(w > bestPositionsWeight)
  622. {
  623. // Minimum 1 position must be entrable
  624. bestPositions.clear();
  625. bestPositions.push_back(t);
  626. bestPositionsWeight = w;
  627. }
  628. else if(w == bestPositionsWeight)
  629. {
  630. bestPositions.push_back(t);
  631. }
  632. }
  633. }
  634. if (bestPositions.empty())
  635. {
  636. bestPositions = accessibleArea.getTilesVector();
  637. }
  638. int3 nextPos = *RandomGeneratorUtil::nextItem(bestPositions, zone.getRand());
  639. instance.setPosition(nextPos - rmgObject.getPosition());
  640. auto instanceAccessibleArea = instance.getAccessibleArea();
  641. if(instance.getBlockedArea().getTilesVector().size() == 1)
  642. {
  643. if(instance.object().appearance->isVisitableFromTop() && !instance.object().isBlockedVisitable())
  644. instanceAccessibleArea.add(instance.getVisitablePosition());
  645. }
  646. //Do not clean up after first object
  647. if(rmgObject.instances().size() == 1)
  648. break;
  649. if(!blockedArea.overlap(instance.getBlockedArea()) && accessibleArea.overlap(instanceAccessibleArea))
  650. break;
  651. //fail - new position
  652. accessibleArea.erase(nextPos);
  653. } while(true);
  654. }
  655. return rmgObject;
  656. }
  657. ObjectInfo * TreasurePlacer::getRandomObject(ui32 desiredValue, ui32 currentValue, bool allowLargeObjects)
  658. {
  659. std::vector<std::pair<ui32, ObjectInfo*>> thresholds; //handle complex object via pointer
  660. ui32 total = 0;
  661. //calculate actual treasure value range based on remaining value
  662. ui32 maxVal = desiredValue - currentValue;
  663. ui32 minValue = static_cast<ui32>(0.25f * (desiredValue - currentValue));
  664. for(ObjectInfo & oi : possibleObjects) //copy constructor turned out to be costly
  665. {
  666. if(oi.value > maxVal)
  667. break; //this assumes values are sorted in ascending order
  668. bool visitableFromTop = true;
  669. for(auto & t : oi.templates)
  670. if(!t->isVisitableFromTop())
  671. visitableFromTop = false;
  672. if(!visitableFromTop && !allowLargeObjects)
  673. continue;
  674. if(oi.value >= minValue && oi.maxPerZone > 0)
  675. {
  676. total += oi.probability;
  677. thresholds.emplace_back(total, &oi);
  678. }
  679. }
  680. if(thresholds.empty())
  681. {
  682. return nullptr;
  683. }
  684. else
  685. {
  686. int r = zone.getRand().nextInt(1, total);
  687. auto sorter = [](const std::pair<ui32, ObjectInfo *> & rhs, const ui32 lhs) -> bool
  688. {
  689. return static_cast<int>(rhs.first) < lhs;
  690. };
  691. //binary search = fastest
  692. auto it = std::lower_bound(thresholds.begin(), thresholds.end(), r, sorter);
  693. return it->second;
  694. }
  695. }
  696. void TreasurePlacer::createTreasures(ObjectManager& manager)
  697. {
  698. const int maxAttempts = 2;
  699. int mapMonsterStrength = map.getMapGenOptions().getMonsterStrength();
  700. int monsterStrength = (zone.monsterStrength == EMonsterStrength::ZONE_NONE ? 0 : zone.monsterStrength + mapMonsterStrength - 1); //array index from 0 to 4; pick any correct value for ZONE_NONE, minGuardedValue won't be used in this case anyway
  701. static int minGuardedValues[] = { 6500, 4167, 3000, 1833, 1333 };
  702. minGuardedValue = minGuardedValues[monsterStrength];
  703. auto valueComparator = [](const CTreasureInfo& lhs, const CTreasureInfo& rhs) -> bool
  704. {
  705. return lhs.max > rhs.max;
  706. };
  707. auto restoreZoneLimits = [](const std::vector<ObjectInfo*>& treasurePile)
  708. {
  709. for (auto* oi : treasurePile)
  710. {
  711. oi->maxPerZone++;
  712. }
  713. };
  714. //place biggest treasures first at large distance, place smaller ones inbetween
  715. auto treasureInfo = zone.getTreasureInfo();
  716. boost::sort(treasureInfo, valueComparator);
  717. //sort treasures by ascending value so we can stop checking treasures with too high value
  718. boost::sort(possibleObjects, [](const ObjectInfo& oi1, const ObjectInfo& oi2) -> bool
  719. {
  720. return oi1.value < oi2.value;
  721. });
  722. size_t size = 0;
  723. {
  724. Zone::Lock lock(zone.areaMutex);
  725. size = zone.getArea().getTilesVector().size();
  726. }
  727. int totalDensity = 0;
  728. for (auto t = treasureInfo.begin(); t != treasureInfo.end(); t++)
  729. {
  730. std::vector<rmg::Object> treasures;
  731. //discard objects with too high value to be ever placed
  732. vstd::erase_if(possibleObjects, [t](const ObjectInfo& oi) -> bool
  733. {
  734. return oi.value > t->max;
  735. });
  736. totalDensity += t->density;
  737. const int DENSITY_CONSTANT = 300;
  738. size_t count = (size * t->density) / DENSITY_CONSTANT;
  739. //Assure space for lesser treasures, if there are any left
  740. const int averageValue = (t->min + t->max) / 2;
  741. if (t != (treasureInfo.end() - 1))
  742. {
  743. if (averageValue > 10000)
  744. {
  745. //Will surely be guarded => larger piles => less space inbetween
  746. vstd::amin(count, size * (10.f / DENSITY_CONSTANT) / (std::sqrt((float)averageValue / 10000)));
  747. }
  748. }
  749. //this is squared distance for optimization purposes
  750. const float minDistance = std::max<float>((125.f / totalDensity), 1.0f);
  751. size_t emergencyLoopFinish = 0;
  752. while(treasures.size() < count && emergencyLoopFinish < count)
  753. {
  754. auto treasurePileInfos = prepareTreasurePile(*t);
  755. if (treasurePileInfos.empty())
  756. {
  757. emergencyLoopFinish++; //Exit potentially infinite loop for bad settings
  758. continue;
  759. }
  760. int value = std::accumulate(treasurePileInfos.begin(), treasurePileInfos.end(), 0, [](int v, const ObjectInfo* oi) {return v + oi->value; });
  761. const ui32 maxPileGenerationAttemps = 2;
  762. for (ui32 attempt = 0; attempt < maxPileGenerationAttemps; attempt++)
  763. {
  764. auto rmgObject = constructTreasurePile(treasurePileInfos, attempt == maxAttempts);
  765. if (rmgObject.instances().empty())
  766. {
  767. // Restore once if all attemps failed
  768. if (attempt == (maxPileGenerationAttemps - 1))
  769. {
  770. restoreZoneLimits(treasurePileInfos);
  771. }
  772. continue;
  773. }
  774. //guard treasure pile
  775. bool guarded = isGuardNeededForTreasure(value);
  776. if (guarded)
  777. guarded = manager.addGuard(rmgObject, value);
  778. treasures.push_back(rmgObject);
  779. break;
  780. }
  781. }
  782. for (auto& rmgObject : treasures)
  783. {
  784. const bool guarded = rmgObject.isGuarded();
  785. auto path = rmg::Path::invalid();
  786. Zone::Lock lock(zone.areaMutex); //We are going to subtract this area
  787. auto possibleArea = zone.areaPossible();
  788. possibleArea.erase_if([this, &minDistance](const int3& tile) -> bool
  789. {
  790. auto ti = map.getTileInfo(tile);
  791. return (ti.getNearestObjectDistance() < minDistance);
  792. });
  793. if (guarded)
  794. {
  795. path = manager.placeAndConnectObject(possibleArea, rmgObject, [this, &rmgObject, &minDistance, &manager](const int3& tile)
  796. {
  797. float bestDistance = 10e9;
  798. for (const auto& t : rmgObject.getArea().getTilesVector())
  799. {
  800. float distance = map.getTileInfo(t).getNearestObjectDistance();
  801. if (distance < minDistance)
  802. return -1.f;
  803. else
  804. vstd::amin(bestDistance, distance);
  805. }
  806. const auto & guardedArea = rmgObject.instances().back()->getAccessibleArea();
  807. const auto areaToBlock = rmgObject.getAccessibleArea(true) - guardedArea;
  808. if (zone.freePaths().overlap(areaToBlock) || manager.getVisitableArea().overlap(areaToBlock))
  809. return -1.f;
  810. return bestDistance;
  811. }, guarded, false, ObjectManager::OptimizeType::BOTH);
  812. }
  813. else
  814. {
  815. path = manager.placeAndConnectObject(possibleArea, rmgObject, minDistance, guarded, false, ObjectManager::OptimizeType::DISTANCE);
  816. }
  817. lock.unlock();
  818. if (path.valid())
  819. {
  820. //debug purposes
  821. treasureArea.unite(rmgObject.getArea());
  822. if (guarded)
  823. {
  824. guards.unite(rmgObject.instances().back()->getBlockedArea());
  825. auto guardedArea = rmgObject.instances().back()->getAccessibleArea();
  826. auto areaToBlock = rmgObject.getAccessibleArea(true) - guardedArea;
  827. treasureBlockArea.unite(areaToBlock);
  828. }
  829. zone.connectPath(path);
  830. manager.placeObject(rmgObject, guarded, true);
  831. }
  832. }
  833. }
  834. }
  835. char TreasurePlacer::dump(const int3 & t)
  836. {
  837. if(guards.contains(t))
  838. return '!';
  839. if(treasureArea.contains(t))
  840. return '$';
  841. if(treasureBlockArea.contains(t))
  842. return '*';
  843. return Modificator::dump(t);
  844. }
  845. void ObjectInfo::setTemplates(MapObjectID type, MapObjectSubID subtype, TerrainId terrainType)
  846. {
  847. auto templHandler = VLC->objtypeh->getHandlerFor(type, subtype);
  848. if(!templHandler)
  849. return;
  850. templates = templHandler->getTemplates(terrainType);
  851. }
  852. VCMI_LIB_NAMESPACE_END