2
0

CObjectWithReward.cpp 26 KB

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