CObjectWithReward.cpp 26 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006
  1. /*
  2. * CObjectWithReward.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 "CObjectWithReward.h"
  12. #include "CHeroHandler.h"
  13. #include "CGeneralTextHandler.h"
  14. #include "../client/CSoundBase.h"
  15. #include "NetPacks.h"
  16. bool CRewardLimiter::heroAllowed(const CGHeroInstance * hero) const
  17. {
  18. if (dayOfWeek != 0)
  19. {
  20. if (IObjectInterface::cb->getDate(Date::DAY_OF_WEEK) != dayOfWeek)
  21. return false;
  22. }
  23. for (auto & reqStack : creatures)
  24. {
  25. size_t count = 0;
  26. for (auto slot : hero->Slots())
  27. {
  28. const CStackInstance * heroStack = slot.second;
  29. if (heroStack->type == reqStack.type)
  30. count += heroStack->count;
  31. }
  32. if (count < reqStack.count) //not enough creatures of this kind
  33. return false;
  34. }
  35. if (!IObjectInterface::cb->getPlayer(hero->tempOwner)->resources.canAfford(resources))
  36. return false;
  37. if (hero->level < minLevel)
  38. return false;
  39. for (size_t i=0; i<primary.size(); i++)
  40. {
  41. if (primary[i] < hero->getPrimSkillLevel(PrimarySkill::PrimarySkill(i)))
  42. return false;
  43. }
  44. for (auto & skill : secondary)
  45. {
  46. if (skill.second < hero->getSecSkillLevel(skill.first))
  47. return false;
  48. }
  49. for (auto & art : artifacts)
  50. {
  51. if (!hero->hasArt(art))
  52. return false;
  53. }
  54. return true;
  55. }
  56. std::vector<ui32> CObjectWithReward::getAvailableRewards(const CGHeroInstance * hero) const
  57. {
  58. std::vector<ui32> ret;
  59. for (size_t i=0; i<info.size(); i++)
  60. {
  61. const CVisitInfo & visit = info[i];
  62. if (visit.numOfGrants < visit.limiter.numOfGrants && visit.limiter.heroAllowed(hero))
  63. {
  64. ret.push_back(i);
  65. }
  66. }
  67. return ret;
  68. }
  69. void CObjectWithReward::onHeroVisit(const CGHeroInstance *h) const
  70. {
  71. if (!wasVisited(h))
  72. {
  73. auto rewards = getAvailableRewards(h);
  74. switch (rewards.size())
  75. {
  76. case 0: // no rewards, e.g. empty flotsam
  77. {
  78. InfoWindow iw;
  79. iw.player = h->tempOwner;
  80. iw.soundID = soundID;
  81. iw.text = onEmpty;
  82. cb->showInfoDialog(&iw);
  83. onRewardGiven(h);
  84. break;
  85. }
  86. case 1: // one reward. Just give it with message
  87. {
  88. grantReward(info[rewards[0]], h);
  89. InfoWindow iw;
  90. iw.player = h->tempOwner;
  91. iw.soundID = soundID;
  92. iw.text = onGrant;
  93. info[rewards[0]].reward.loadComponents(iw.components);
  94. cb->showInfoDialog(&iw);
  95. onRewardGiven(h);
  96. break;
  97. }
  98. default: // multiple rewards. Let player select
  99. {
  100. BlockingDialog sd(false,true);
  101. sd.player = h->tempOwner;
  102. sd.soundID = soundID;
  103. sd.text = onGrant;
  104. for (auto index : rewards)
  105. sd.components.push_back(info[index].reward.getDisplayedComponent());
  106. cb->showBlockingDialog(&sd);
  107. return;
  108. }
  109. }
  110. }
  111. else
  112. {
  113. InfoWindow iw;
  114. iw.player = h->tempOwner;
  115. iw.soundID = soundID;
  116. iw.text = onVisited;
  117. cb->showInfoDialog(&iw);
  118. }
  119. }
  120. void CObjectWithReward::heroLevelUpDone(const CGHeroInstance *hero) const
  121. {
  122. grantRewardAfterLevelup(info[selectedReward], hero);
  123. }
  124. void CObjectWithReward::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const
  125. {
  126. if (answer > 0 && answer-1 < info.size())
  127. {
  128. auto list = getAvailableRewards(hero);
  129. grantReward(info[list[answer - 1]], hero);
  130. }
  131. else
  132. {
  133. throw std::runtime_error("Unhandled choice");
  134. }
  135. }
  136. void CObjectWithReward::onRewardGiven(const CGHeroInstance * hero) const
  137. {
  138. // no implementation, virtual function for overrides
  139. }
  140. void CObjectWithReward::grantReward(const CVisitInfo & info, const CGHeroInstance * hero) const
  141. {
  142. assert(hero);
  143. assert(hero->tempOwner.isValidPlayer());
  144. assert(stacks.empty());
  145. assert(info.reward.creatures.size() <= GameConstants::ARMY_SIZE);
  146. assert(!cb->isVisitCoveredByAnotherQuery(this, hero));
  147. cb->giveResources(hero->tempOwner, info.reward.resources);
  148. for (auto & entry : info.reward.secondary)
  149. {
  150. int current = hero->getSecSkillLevel(entry.first);
  151. if( (current != 0 && current < entry.second) ||
  152. (hero->canLearnSkill() ))
  153. {
  154. cb->changeSecSkill(hero, entry.first, entry.second);
  155. }
  156. }
  157. for(int i=0; i< info.reward.primary.size(); i++)
  158. if(info.reward.primary[i] > 0)
  159. cb->changePrimSkill(hero, static_cast<PrimarySkill::PrimarySkill>(i), info.reward.primary[i], false);
  160. si64 expToGive = 0;
  161. expToGive += VLC->heroh->reqExp(hero->level+info.reward.gainedLevels) - VLC->heroh->reqExp(hero->level);
  162. expToGive += hero->calculateXp(info.reward.gainedExp);
  163. if (expToGive)
  164. {
  165. cb->changePrimSkill(hero, PrimarySkill::EXPERIENCE, expToGive);
  166. }
  167. else
  168. {
  169. grantRewardAfterLevelup(info, hero);
  170. }
  171. }
  172. void CObjectWithReward::grantRewardAfterLevelup(const CVisitInfo & info, const CGHeroInstance * hero) const
  173. {
  174. if (info.reward.manaDiff || info.reward.manaPercentage >= 0)
  175. {
  176. si32 mana = hero->mana;
  177. if (info.reward.manaPercentage >= 0)
  178. mana = hero->manaLimit() * info.reward.manaPercentage / 100;
  179. cb->setManaPoints(hero->id, mana + info.reward.manaDiff);
  180. }
  181. if(info.reward.movePoints || info.reward.movePercentage >= 0)
  182. {
  183. SetMovePoints smp;
  184. smp.hid = hero->id;
  185. smp.val = hero->movement;
  186. if (info.reward.movePercentage >= 0) // percent from max
  187. smp.val = hero->maxMovePoints(hero->boat != nullptr) * info.reward.movePercentage / 100;
  188. smp.val = std::max<si32>(0, smp.val + info.reward.movePoints);
  189. cb->setMovePoints(&smp);
  190. }
  191. for (const Bonus & bonus : info.reward.bonuses)
  192. {
  193. GiveBonus gb;
  194. gb.bonus = bonus;
  195. gb.id = hero->id.getNum();
  196. cb->giveHeroBonus(&gb);
  197. }
  198. for (ArtifactID art : info.reward.artifacts)
  199. cb->giveHeroNewArtifact(hero, VLC->arth->artifacts[art],ArtifactPosition::FIRST_AVAILABLE);
  200. if (!info.reward.spells.empty())
  201. {
  202. std::set<SpellID> spellsToGive(info.reward.spells.begin(), info.reward.spells.end());
  203. cb->changeSpells(hero, true, spellsToGive);
  204. }
  205. if (!info.reward.creatures.empty())
  206. {
  207. CCreatureSet creatures;
  208. for (auto & crea : info.reward.creatures)
  209. creatures.addToSlot(creatures.getFreeSlot(), new CStackInstance(crea.type, crea.count));
  210. cb->giveCreatures(this, hero, creatures, false);
  211. }
  212. onRewardGiven(hero);
  213. }
  214. bool CObjectWithReward::wasVisited (PlayerColor player) const
  215. {
  216. switch (visitMode)
  217. {
  218. case VISIT_UNLIMITED:
  219. return false;
  220. case VISIT_ONCE:
  221. for (auto & visit : info)
  222. {
  223. if (visit.numOfGrants != 0)
  224. return true;
  225. }
  226. case VISIT_HERO:
  227. return false;
  228. case VISIT_PLAYER:
  229. return vstd::contains(cb->getPlayer(player)->visitedObjects, ObjectInstanceID(ID));
  230. default:
  231. return false;
  232. }
  233. }
  234. bool CObjectWithReward::wasVisited (const CGHeroInstance * h) const
  235. {
  236. switch (visitMode)
  237. {
  238. case VISIT_HERO:
  239. return vstd::contains(h->visitedObjects, ObjectInstanceID(ID));
  240. default:
  241. return wasVisited(h->tempOwner);
  242. }
  243. }
  244. void CRewardInfo::loadComponents(std::vector<Component> & comps) const
  245. {
  246. for (size_t i=0; i<resources.size(); i++)
  247. {
  248. if (resources[i] !=0)
  249. comps.push_back(Component(Component::RESOURCE, i, resources[i], 0));
  250. }
  251. if (gainedExp) comps.push_back(Component(Component::EXPERIENCE, 0, gainedExp, 0));
  252. if (gainedLevels) comps.push_back(Component(Component::EXPERIENCE, 0, gainedLevels, 0));
  253. if (manaDiff) comps.push_back(Component(Component::PRIM_SKILL, 5, manaDiff, 0));
  254. for (size_t i=0; i<primary.size(); i++)
  255. {
  256. if (primary[i] !=0)
  257. comps.push_back(Component(Component::PRIM_SKILL, i, primary[i], 0));
  258. }
  259. for (auto & entry : secondary)
  260. comps.push_back(Component(Component::SEC_SKILL, entry.first, entry.second, 0));
  261. for (auto & entry : artifacts)
  262. comps.push_back(Component(Component::ARTIFACT, entry, 1, 0));
  263. for (auto & entry : spells)
  264. comps.push_back(Component(Component::SPELL, entry, 1, 0));
  265. for (auto & entry : creatures)
  266. comps.push_back(Component(Component::CREATURE, entry.type->idNumber, entry.count, 0));
  267. }
  268. Component CRewardInfo::getDisplayedComponent() const
  269. {
  270. std::vector<Component> comps;
  271. loadComponents(comps);
  272. assert(!comps.empty());
  273. return comps.front();
  274. }
  275. // FIXME: copy-pasted from CObjectHandler
  276. static std::string & visitedTxt(const bool visited)
  277. {
  278. int id = visited ? 352 : 353;
  279. return VLC->generaltexth->allTexts[id];
  280. }
  281. const std::string & CObjectWithReward::getHoverText() const
  282. {
  283. const CGHeroInstance *h = cb->getSelectedHero(cb->getCurrentPlayer());
  284. hoverName = VLC->generaltexth->names[ID];
  285. if(visitMode != VISIT_UNLIMITED)
  286. {
  287. bool visited = wasVisited(cb->getCurrentPlayer());
  288. if (h)
  289. visited |= wasVisited(h) || h->hasBonusFrom(Bonus::OBJECT,ID);
  290. hoverName += " " + visitedTxt(visited);
  291. }
  292. return hoverName;
  293. }
  294. void CObjectWithReward::setPropertyDer(ui8 what, ui32 val)
  295. {
  296. switch (what)
  297. {
  298. case ObjProperty::REWARD_RESET:
  299. for (auto & visit : info)
  300. visit.numOfGrants = 0;
  301. break;
  302. case ObjProperty::REWARD_SELECT:
  303. selectedReward = val;
  304. break;
  305. case ObjProperty::REWARD_ADD_VISITOR:
  306. {
  307. //FIXME: Not sure if modifying another object here is a good idea
  308. CGHeroInstance * hero = cb->gameState()->getHero(ObjectInstanceID(val));
  309. assert(hero && hero->tempOwner.isValidPlayer());
  310. hero->visitedObjects.insert(ObjectInstanceID(ID));
  311. cb->gameState()->getPlayer(hero->tempOwner)->visitedObjects.insert(ObjectInstanceID(ID));
  312. break;
  313. }
  314. }
  315. }
  316. void CObjectWithReward::newTurn() const
  317. {
  318. if (resetDuration != 0 && cb->getDate(Date::DAY) % resetDuration == 0)
  319. cb->setObjProperty(id, ObjProperty::REWARD_RESET, 0);
  320. }
  321. CObjectWithReward::CObjectWithReward():
  322. soundID(soundBase::invalid),
  323. selectMode(0),
  324. selectedReward(0),
  325. resetDuration(0)
  326. {}
  327. ///////////////////////////////////////////////////////////////////////////////////////////////////
  328. /// END OF CODE FOR COBJECTWITHREWARD AND RELATED CLASSES ///
  329. ///////////////////////////////////////////////////////////////////////////////////////////////////
  330. /// Helper, selects random art class based on weights
  331. static int selectRandomArtClass(int treasure, int minor, int major, int relic)
  332. {
  333. int total = treasure + minor + major + relic;
  334. assert(total != 0);
  335. int hlp = IObjectInterface::cb->gameState()->getRandomGenerator().nextInt(total - 1);
  336. if(hlp < treasure)
  337. return CArtifact::ART_TREASURE;
  338. if(hlp < treasure + minor)
  339. return CArtifact::ART_MINOR;
  340. if(hlp < treasure + minor + major)
  341. return CArtifact::ART_MAJOR;
  342. return CArtifact::ART_RELIC;
  343. }
  344. /// Helper, adds random artifact to reward selecting class based on weights
  345. static void loadRandomArtifact(CVisitInfo & info, int treasure, int minor, int major, int relic)
  346. {
  347. int artClass = selectRandomArtClass(treasure, minor, major, relic);
  348. ArtifactID artID = VLC->arth->pickRandomArtifact(IObjectInterface::cb->gameState()->getRandomGenerator(), artClass);
  349. info.reward.artifacts.push_back(artID);
  350. }
  351. CGPickable::CGPickable()
  352. {
  353. visitMode = VISIT_ONCE;
  354. selectMode = SELECT_PLAYER;
  355. }
  356. void CGPickable::initObj()
  357. {
  358. blockVisit = true;
  359. switch(ID)
  360. {
  361. case Obj::CAMPFIRE:
  362. {
  363. soundID = soundBase::experience;
  364. onGrant.addTxt(MetaString::ADVOB_TXT,23);
  365. int givenRes = cb->gameState()->getRandomGenerator().nextInt(5);
  366. int givenAmm = cb->gameState()->getRandomGenerator().nextInt(4, 6);
  367. info.resize(1);
  368. info[0].reward.resources[givenRes] = givenAmm;
  369. info[0].reward.resources[Res::GOLD]= givenAmm * 100;
  370. break;
  371. }
  372. case Obj::FLOTSAM:
  373. {
  374. int type = cb->gameState()->getRandomGenerator().nextInt(3);
  375. soundID = soundBase::GENIE;
  376. if (type == 0)
  377. onEmpty.addTxt(MetaString::ADVOB_TXT, 51+type);
  378. else
  379. onGrant.addTxt(MetaString::ADVOB_TXT, 51+type);
  380. switch(type)
  381. {
  382. //case 0:
  383. case 1:
  384. {
  385. info.resize(1);
  386. info[0].reward.resources[Res::WOOD] = 5;
  387. break;
  388. }
  389. case 2:
  390. {
  391. info.resize(1);
  392. info[0].reward.resources[Res::WOOD] = 5;
  393. info[0].reward.resources[Res::GOLD] = 200;
  394. break;
  395. }
  396. case 3:
  397. {
  398. info.resize(1);
  399. info[0].reward.resources[Res::WOOD] = 10;
  400. info[0].reward.resources[Res::GOLD] = 500;
  401. break;
  402. }
  403. }
  404. break;
  405. }
  406. case Obj::SEA_CHEST:
  407. {
  408. soundID = soundBase::chest;
  409. int hlp = cb->gameState()->getRandomGenerator().nextInt(99);
  410. if(hlp < 20)
  411. {
  412. onGrant.addTxt(MetaString::ADVOB_TXT, 116);
  413. }
  414. else if(hlp < 90)
  415. {
  416. info.resize(1);
  417. info[0].reward.resources[Res::GOLD] = 1500;
  418. onGrant.addTxt(MetaString::ADVOB_TXT, 118);
  419. }
  420. else
  421. {
  422. info.resize(1);
  423. loadRandomArtifact(info[0], 100, 0, 0, 0);
  424. info[0].reward.resources[Res::GOLD] = 1000;
  425. onGrant.addTxt(MetaString::ADVOB_TXT, 117);
  426. onGrant.addReplacement(MetaString::ART_NAMES, info[0].reward.artifacts.back());
  427. }
  428. }
  429. break;
  430. case Obj::SHIPWRECK_SURVIVOR:
  431. {
  432. soundID = soundBase::experience;
  433. info.resize(1);
  434. loadRandomArtifact(info[0], 55, 20, 20, 5);
  435. onGrant.addTxt(MetaString::ADVOB_TXT, 125);
  436. onGrant.addReplacement(MetaString::ART_NAMES, info[0].reward.artifacts.back());
  437. }
  438. break;
  439. case Obj::TREASURE_CHEST:
  440. {
  441. int hlp = cb->gameState()->getRandomGenerator().nextInt(99);
  442. if(hlp >= 95)
  443. {
  444. soundID = soundBase::treasure;
  445. info.resize(1);
  446. loadRandomArtifact(info[0], 100, 0, 0, 0);
  447. onGrant.addTxt(MetaString::ADVOB_TXT,145);
  448. onGrant.addReplacement(MetaString::ART_NAMES, info[0].reward.artifacts.back());
  449. return;
  450. }
  451. else if (hlp >= 65)
  452. {
  453. soundID = soundBase::chest;
  454. onGrant.addTxt(MetaString::ADVOB_TXT,146);
  455. info.resize(2);
  456. info[0].reward.resources[Res::GOLD] = 2000;
  457. info[1].reward.gainedExp = 1500;
  458. }
  459. else if(hlp >= 33)
  460. {
  461. soundID = soundBase::chest;
  462. onGrant.addTxt(MetaString::ADVOB_TXT,146);
  463. info.resize(2);
  464. info[0].reward.resources[Res::GOLD] = 1500;
  465. info[1].reward.gainedExp = 1000;
  466. }
  467. else
  468. {
  469. soundID = soundBase::chest;
  470. onGrant.addTxt(MetaString::ADVOB_TXT,146);
  471. info.resize(2);
  472. info[0].reward.resources[Res::GOLD] = 1000;
  473. info[1].reward.gainedExp = 500;
  474. }
  475. }
  476. break;
  477. }
  478. }
  479. void CGPickable::onRewardGiven(const CGHeroInstance * hero) const
  480. {
  481. cb->removeObject(this);
  482. }
  483. ///////////////////////////////////////////////////////////////////////////////////////////////////
  484. CGBonusingObject::CGBonusingObject()
  485. {
  486. visitMode = VISIT_UNLIMITED;
  487. selectMode = SELECT_FIRST;
  488. }
  489. void CGBonusingObject::initObj()
  490. {
  491. auto configureBonusDuration = [&](CVisitInfo & visit, Bonus::BonusDuration duration, Bonus::BonusType type, si32 value, si32 descrID)
  492. {
  493. Bonus b(duration, type, Bonus::OBJECT, value, ID, descrID != 0 ? VLC->generaltexth->advobtxt[descrID] : "");
  494. visit.reward.bonuses.push_back(b);
  495. };
  496. auto configureBonus = [&](CVisitInfo & visit, Bonus::BonusType type, si32 value, si32 descrID)
  497. {
  498. configureBonusDuration(visit, Bonus::ONE_BATTLE, type, value, descrID);
  499. };
  500. auto configureMessage = [&](int onGrantID, int onVisitedID, soundBase::soundID sound)
  501. {
  502. onGrant.addTxt(MetaString::ADVOB_TXT, onGrantID);
  503. onVisited.addTxt(MetaString::ADVOB_TXT, onVisitedID);
  504. soundID = sound;
  505. };
  506. if(ID == Obj::BUOY || ID == Obj::MERMAID)
  507. blockVisit = true;
  508. info.resize(1);
  509. CVisitInfo & visit = info[0];
  510. switch(ID)
  511. {
  512. case Obj::BUOY:
  513. configureMessage(21, 22, soundBase::MORALE);
  514. configureBonus(visit, Bonus::MORALE, +1, 94);
  515. break;
  516. case Obj::SWAN_POND:
  517. configureMessage(29, 30, soundBase::LUCK);
  518. configureBonus(visit, Bonus::LUCK, 2, 67);
  519. visit.reward.movePercentage = 0;
  520. break;
  521. case Obj::FAERIE_RING:
  522. configureMessage(49, 50, soundBase::LUCK);
  523. configureBonus(visit, Bonus::LUCK, 2, 71);
  524. break;
  525. case Obj::FOUNTAIN_OF_FORTUNE:
  526. selectMode = SELECT_RANDOM;
  527. configureMessage(55, 56, soundBase::LUCK);
  528. info.resize(5);
  529. for (int i=0; i<5; i++)
  530. configureBonus(info[i], Bonus::LUCK, i-1, 69); //NOTE: description have %d that should be replaced with value
  531. break;
  532. case Obj::IDOL_OF_FORTUNE:
  533. configureMessage(62, 63, soundBase::experience);
  534. info.resize(7);
  535. for (int i=0; i<6; i++)
  536. {
  537. info[i].limiter.dayOfWeek = i+1;
  538. configureBonus(info[i], i%2 ? Bonus::MORALE : Bonus::LUCK, 1, 68);
  539. }
  540. info.back().limiter.dayOfWeek = 7;
  541. configureBonus(info.back(), Bonus::MORALE, 1, 68); // on last day of week
  542. configureBonus(info.back(), Bonus::LUCK, 1, 68);
  543. break;
  544. case Obj::MERMAID:
  545. configureMessage(83, 82, soundBase::LUCK);
  546. configureBonus(visit, Bonus::LUCK, 1, 72);
  547. break;
  548. case Obj::RALLY_FLAG:
  549. configureMessage(111, 110, soundBase::MORALE);
  550. configureBonus(visit, Bonus::MORALE, 1, 102);
  551. configureBonus(visit, Bonus::LUCK, 1, 102);
  552. visit.reward.movePoints = 400;
  553. break;
  554. case Obj::OASIS:
  555. configureMessage(95, 94, soundBase::MORALE);
  556. onGrant.addTxt(MetaString::ADVOB_TXT, 95);
  557. configureBonus(visit, Bonus::MORALE, 1, 95);
  558. visit.reward.movePoints = 800;
  559. break;
  560. case Obj::TEMPLE:
  561. configureMessage(140, 141, soundBase::temple);
  562. info[0].limiter.dayOfWeek = 7;
  563. info.resize(2);
  564. configureBonus(info[0], Bonus::MORALE, 2, 96);
  565. configureBonus(info[1], Bonus::MORALE, 1, 97);
  566. break;
  567. case Obj::WATERING_HOLE:
  568. configureMessage(166, 167, soundBase::MORALE);
  569. configureBonus(visit, Bonus::MORALE, 1, 100);
  570. visit.reward.movePoints = 400;
  571. break;
  572. case Obj::FOUNTAIN_OF_YOUTH:
  573. configureMessage(57, 58, soundBase::MORALE);
  574. configureBonus(visit, Bonus::MORALE, 1, 103);
  575. visit.reward.movePoints = 400;
  576. break;
  577. case Obj::STABLES:
  578. configureMessage(137, 136, soundBase::STORE);
  579. configureBonusDuration(visit, Bonus::ONE_WEEK, Bonus::LAND_MOVEMENT, 600, 0);
  580. visit.reward.movePoints = 600;
  581. //TODO: upgrade champions to cavaliers
  582. /*
  583. bool someUpgradeDone = false;
  584. for (auto i = h->Slots().begin(); i != h->Slots().end(); ++i)
  585. {
  586. if(i->second->type->idNumber == CreatureID::CAVALIER)
  587. {
  588. cb->changeStackType(StackLocation(h, i->first), VLC->creh->creatures[CreatureID::CHAMPION]);
  589. someUpgradeDone = true;
  590. }
  591. }
  592. if (someUpgradeDone)
  593. {
  594. grantMessage.addTxt(MetaString::ADVOB_TXT, 138);
  595. iw.components.push_back(Component(Component::CREATURE,11,0,1));
  596. }*/
  597. break;
  598. }
  599. }
  600. ///////////////////////////////////////////////////////////////////////////////////////////////////
  601. CGOnceVisitable::CGOnceVisitable()
  602. {
  603. visitMode = VISIT_ONCE;
  604. selectMode = SELECT_FIRST;
  605. }
  606. void CGOnceVisitable::initObj()
  607. {
  608. switch(ID)
  609. {
  610. case Obj::CORPSE:
  611. {
  612. onGrant.addTxt(MetaString::ADVOB_TXT, 37);
  613. onEmpty.addTxt(MetaString::ADVOB_TXT, 38);
  614. soundID = soundBase::MYSTERY;
  615. blockVisit = true;
  616. if(cb->gameState()->getRandomGenerator().nextInt(99) < 20)
  617. {
  618. info.resize(1);
  619. loadRandomArtifact(info[0], 10, 10, 10, 0);
  620. }
  621. }
  622. break;
  623. case Obj::LEAN_TO:
  624. {
  625. soundID = soundBase::GENIE;
  626. onGrant.addTxt(MetaString::ADVOB_TXT, 64);
  627. onEmpty.addTxt(MetaString::ADVOB_TXT, 65);
  628. info.resize(1);
  629. int type = cb->gameState()->getRandomGenerator().nextInt(5); //any basic resource without gold
  630. int value = cb->gameState()->getRandomGenerator().nextInt(1, 4);
  631. info[0].reward.resources[type] = value;
  632. }
  633. break;
  634. case Obj::WARRIORS_TOMB:
  635. {
  636. // TODO: line 161 - ask if player wants to search the Tomb
  637. soundID = soundBase::GRAVEYARD;
  638. onGrant.addTxt(MetaString::ADVOB_TXT, 162);
  639. onVisited.addTxt(MetaString::ADVOB_TXT, 163);
  640. info.resize(2);
  641. loadRandomArtifact(info[0], 30, 50, 25, 5);
  642. Bonus bonus(Bonus::ONE_BATTLE, Bonus::MORALE, Bonus::OBJECT, -3, ID);
  643. info[0].reward.bonuses.push_back(bonus);
  644. info[1].reward.bonuses.push_back(bonus);
  645. }
  646. break;
  647. case Obj::WAGON:
  648. {
  649. soundID = soundBase::GENIE;
  650. onVisited.addTxt(MetaString::ADVOB_TXT, 156);
  651. int hlp = cb->gameState()->getRandomGenerator().nextInt(99);
  652. if(hlp < 40) //minor or treasure art
  653. {
  654. onGrant.addTxt(MetaString::ADVOB_TXT, 155);
  655. info.resize(1);
  656. loadRandomArtifact(info[0], 10, 10, 0, 0);
  657. }
  658. else if(hlp < 90) //2 - 5 of non-gold resource
  659. {
  660. onGrant.addTxt(MetaString::ADVOB_TXT, 154);
  661. info.resize(1);
  662. int type = cb->gameState()->getRandomGenerator().nextInt(5);
  663. int value = cb->gameState()->getRandomGenerator().nextInt(2, 5);
  664. info[0].reward.resources[type] = value;
  665. }
  666. // or nothing
  667. }
  668. break;
  669. }
  670. }
  671. ///////////////////////////////////////////////////////////////////////////////////////////////////
  672. CGVisitableOPH::CGVisitableOPH()
  673. {
  674. visitMode = VISIT_HERO;
  675. selectMode = SELECT_PLAYER;
  676. }
  677. void CGVisitableOPH::initObj()
  678. {
  679. switch(ID)
  680. {
  681. case Obj::ARENA:
  682. soundID = soundBase::NOMAD;
  683. onGrant.addTxt(MetaString::ADVOB_TXT, 0);
  684. info.resize(2);
  685. info[0].reward.primary[PrimarySkill::ATTACK] = 2;
  686. info[1].reward.primary[PrimarySkill::DEFENSE] = 2;
  687. break;
  688. case Obj::MERCENARY_CAMP:
  689. info.resize(1);
  690. info[0].reward.primary[PrimarySkill::ATTACK] = 1;
  691. soundID = soundBase::NOMAD;
  692. onGrant.addTxt(MetaString::ADVOB_TXT, 80);
  693. break;
  694. case Obj::MARLETTO_TOWER:
  695. info.resize(1);
  696. info[0].reward.primary[PrimarySkill::DEFENSE] = 1;
  697. soundID = soundBase::NOMAD;
  698. onGrant.addTxt(MetaString::ADVOB_TXT, 39);
  699. break;
  700. case Obj::STAR_AXIS:
  701. info.resize(1);
  702. info[0].reward.primary[PrimarySkill::SPELL_POWER] = 1;
  703. soundID = soundBase::gazebo;
  704. onGrant.addTxt(MetaString::ADVOB_TXT, 100);
  705. break;
  706. case Obj::GARDEN_OF_REVELATION:
  707. info.resize(1);
  708. info[0].reward.primary[PrimarySkill::KNOWLEDGE] = 1;
  709. soundID = soundBase::GETPROTECTION;
  710. onGrant.addTxt(MetaString::ADVOB_TXT, 59);
  711. break;
  712. case Obj::LEARNING_STONE:
  713. info.resize(1);
  714. info[0].reward.gainedExp = 1000;
  715. soundID = soundBase::gazebo;
  716. onGrant.addTxt(MetaString::ADVOB_TXT, 143);
  717. break;
  718. case Obj::TREE_OF_KNOWLEDGE:
  719. soundID = soundBase::gazebo;
  720. info.resize(1);
  721. info[0].reward.gainedLevels = 1;
  722. info.resize(1);
  723. switch (cb->gameState()->getRandomGenerator().nextInt(2))
  724. {
  725. case 0: // free
  726. break;
  727. case 1:
  728. info[0].limiter.resources[Res::GOLD] = 2000;
  729. info[0].reward.resources[Res::GOLD] = -2000;
  730. break;
  731. case 2:
  732. info[0].limiter.resources[Res::GEMS] = 10;
  733. info[0].reward.resources[Res::GEMS] = -10;
  734. break;
  735. }
  736. break;
  737. case Obj::LIBRARY_OF_ENLIGHTENMENT:
  738. {
  739. onGrant.addTxt(MetaString::ADVOB_TXT, 66);
  740. onVisited.addTxt(MetaString::ADVOB_TXT, 67);
  741. onEmpty.addTxt(MetaString::ADVOB_TXT, 68);
  742. // Don't like this one but don't see any easier approach
  743. CVisitInfo visit;
  744. visit.reward.primary[PrimarySkill::ATTACK] = 2;
  745. visit.reward.primary[PrimarySkill::DEFENSE] = 2;
  746. visit.reward.primary[PrimarySkill::KNOWLEDGE] = 2;
  747. visit.reward.primary[PrimarySkill::SPELL_POWER] = 2;
  748. static_assert(SecSkillLevel::LEVELS_SIZE == 4, "Behavior of Library of Enlignment may not be correct");
  749. for (int i=0; i<SecSkillLevel::LEVELS_SIZE; i++)
  750. {
  751. visit.limiter.minLevel = 10 - i * 2;
  752. visit.limiter.secondary[SecondarySkill::DIPLOMACY] = i;
  753. info.push_back(visit);
  754. }
  755. soundID = soundBase::gazebo;
  756. break;
  757. }
  758. case Obj::SCHOOL_OF_MAGIC:
  759. info.resize(2);
  760. info[0].reward.primary[PrimarySkill::SPELL_POWER] = 1;
  761. info[1].reward.primary[PrimarySkill::KNOWLEDGE] = 1;
  762. soundID = soundBase::faerie;
  763. onGrant.addTxt(MetaString::ADVOB_TXT, 71);
  764. break;
  765. case Obj::SCHOOL_OF_WAR:
  766. info.resize(2);
  767. info[0].reward.primary[PrimarySkill::ATTACK] = 1;
  768. info[1].reward.primary[PrimarySkill::DEFENSE] = 1;
  769. soundID = soundBase::MILITARY;
  770. onGrant.addTxt(MetaString::ADVOB_TXT, 158);
  771. break;
  772. }
  773. }
  774. /*
  775. const std::string & CGVisitableOPH::getHoverText() const
  776. {
  777. int pom = -1;
  778. switch(ID)
  779. {
  780. case Obj::ARENA:
  781. pom = -1;
  782. break;
  783. case Obj::MERCENARY_CAMP:
  784. pom = 8;
  785. break;
  786. case Obj::MARLETTO_TOWER:
  787. pom = 7;
  788. break;
  789. case Obj::STAR_AXIS:
  790. pom = 11;
  791. break;
  792. case Obj::GARDEN_OF_REVELATION:
  793. pom = 4;
  794. break;
  795. case Obj::LEARNING_STONE:
  796. pom = 5;
  797. break;
  798. case Obj::TREE_OF_KNOWLEDGE:
  799. pom = 18;
  800. break;
  801. case Obj::LIBRARY_OF_ENLIGHTENMENT:
  802. break;
  803. case Obj::SCHOOL_OF_MAGIC:
  804. pom = 9;
  805. break;
  806. case Obj::SCHOOL_OF_WAR:
  807. pom = 10;
  808. break;
  809. default:
  810. throw std::runtime_error("Wrong CGVisitableOPH object ID!\n");
  811. }
  812. hoverName = VLC->generaltexth->names[ID];
  813. if(pom >= 0)
  814. hoverName += ("\n" + VLC->generaltexth->xtrainfo[pom]);
  815. const CGHeroInstance *h = cb->getSelectedHero (cb->getCurrentPlayer());
  816. if(h)
  817. {
  818. hoverName += "\n\n";
  819. bool visited = vstd::contains (visitors, h->id);
  820. hoverName += visitedTxt (visited);
  821. }
  822. return hoverName;
  823. }
  824. */
  825. ///////////////////////////////////////////////////////////////////////////////////////////////////
  826. CGVisitableOPW::CGVisitableOPW()
  827. {
  828. visitMode = VISIT_ONCE;
  829. selectMode = SELECT_RANDOM;
  830. resetDuration = 7;
  831. }
  832. void CGVisitableOPW::initObj()
  833. {
  834. switch (ID)
  835. {
  836. case Obj::MYSTICAL_GARDEN:
  837. soundID = soundBase::experience;
  838. onGrant.addTxt(MetaString::ADVOB_TXT, 92);
  839. onEmpty.addTxt(MetaString::ADVOB_TXT, 93);
  840. info.resize(2);
  841. info[0].reward.resources[Res::GEMS] = 5;
  842. info[1].reward.resources[Res::GOLD] = 500;
  843. break;
  844. case Obj::WINDMILL:
  845. soundID = soundBase::GENIE;
  846. onGrant.addTxt(MetaString::ADVOB_TXT, 170);
  847. onEmpty.addTxt(MetaString::ADVOB_TXT, 169);
  848. // 3-6 of any resource but wood and gold
  849. // this is UGLY. TODO: find better way to describe this
  850. for (int resID = Res::MERCURY; resID < Res::GOLD; resID++)
  851. {
  852. for (int val = 3; val <=6; val++)
  853. {
  854. CVisitInfo visit;
  855. visit.reward.resources[resID] = val;
  856. info.push_back(visit);
  857. }
  858. }
  859. break;
  860. case Obj::WATER_WHEEL:
  861. soundID = soundBase::GENIE;
  862. onGrant.addTxt(MetaString::ADVOB_TXT, 164);
  863. onEmpty.addTxt(MetaString::ADVOB_TXT, 165);
  864. info.resize(2);
  865. info[0].limiter.dayOfWeek = 7; // double amount on sunday
  866. info[0].reward.resources[Res::GOLD] = 1000;
  867. info[1].reward.resources[Res::GOLD] = 500;
  868. break;
  869. }
  870. }
  871. ///////////////////////////////////////////////////////////////////////////////////////////////////
  872. std::vector<int3> CGMagicSpring::getVisitableOffsets() const
  873. {
  874. std::vector <int3> visitableTiles;
  875. for(int y = 0; y < 6; y++)
  876. for (int x = 0; x < 8; x++) //starting from left
  877. if (appearance.isVisitableAt(x, y))
  878. visitableTiles.push_back (int3(x, y , 0));
  879. return visitableTiles;
  880. }
  881. int3 CGMagicSpring::getVisitableOffset() const
  882. {
  883. auto visitableTiles = getVisitableOffsets();
  884. if (visitableTiles.size() != info.size())
  885. {
  886. logGlobal->warnStream() << "Unexpected number of visitable tiles of Magic Spring at " << pos << "!";
  887. return int3(-1,-1,-1);
  888. }
  889. for (size_t i=0; i<visitableTiles.size(); i++)
  890. {
  891. if (info[i].numOfGrants == 0)
  892. return visitableTiles[i];
  893. }
  894. return visitableTiles[0]; // return *something*. This is valid visitable tile but already used
  895. }
  896. std::vector<ui32> CGMagicSpring::getAvailableRewards(const CGHeroInstance * hero) const
  897. {
  898. auto tiles = getVisitableOffsets();
  899. for (size_t i=0; i<tiles.size(); i++)
  900. {
  901. if (pos - tiles[i] == hero->getPosition() && info[i].numOfGrants == 0)
  902. {
  903. return std::vector<ui32>(1, i);
  904. }
  905. }
  906. // hero is either not on visitable tile (should not happen) or tile is already used
  907. return std::vector<ui32>();
  908. }