CBank.cpp 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379
  1. /*
  2. * CBank.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 "CBank.h"
  12. #include <vcmi/spells/Spell.h>
  13. #include <vcmi/spells/Service.h>
  14. #include "../NetPacks.h"
  15. #include "../CGeneralTextHandler.h"
  16. #include "../CSoundBase.h"
  17. #include "../GameSettings.h"
  18. #include "../mapObjectConstructors/CObjectClassesHandler.h"
  19. #include "../mapObjectConstructors/CBankInstanceConstructor.h"
  20. #include "../IGameCallback.h"
  21. #include "../gameState/CGameState.h"
  22. VCMI_LIB_NAMESPACE_BEGIN
  23. ///helpers
  24. static std::string visitedTxt(const bool visited)
  25. {
  26. int id = visited ? 352 : 353;
  27. return VLC->generaltexth->allTexts[id];
  28. }
  29. //must be instantiated in .cpp file for access to complete types of all member fields
  30. CBank::CBank() = default;
  31. //must be instantiated in .cpp file for access to complete types of all member fields
  32. CBank::~CBank() = default;
  33. void CBank::initObj(CRandomGenerator & rand)
  34. {
  35. daycounter = 0;
  36. resetDuration = 0;
  37. VLC->objtypeh->getHandlerFor(ID, subID)->configureObject(this, rand);
  38. }
  39. bool CBank::isCoastVisitable() const
  40. {
  41. return coastVisitable;
  42. }
  43. std::string CBank::getHoverText(PlayerColor player) const
  44. {
  45. // TODO: record visited players
  46. return getObjectName() + " " + visitedTxt(bc == nullptr);
  47. }
  48. void CBank::setConfig(const BankConfig & config)
  49. {
  50. bc = std::make_unique<BankConfig>(config);
  51. clear(); // remove all stacks, if any
  52. for(const auto & stack : config.guards)
  53. setCreature (SlotID(stacksCount()), stack.type->getId(), stack.count);
  54. }
  55. void CBank::setPropertyDer (ui8 what, ui32 val)
  56. {
  57. switch (what)
  58. {
  59. case ObjProperty::BANK_DAYCOUNTER: //daycounter
  60. daycounter+=val;
  61. break;
  62. case ObjProperty::BANK_RESET:
  63. // FIXME: Object reset must be done by separate netpack from server
  64. initObj(cb->gameState()->getRandomGenerator());
  65. daycounter = 1; //yes, 1 since "today" daycounter won't be incremented
  66. break;
  67. case ObjProperty::BANK_CLEAR:
  68. bc.reset();
  69. break;
  70. }
  71. }
  72. void CBank::newTurn(CRandomGenerator & rand) const
  73. {
  74. if (bc == nullptr)
  75. {
  76. if (resetDuration != 0)
  77. {
  78. if (daycounter >= resetDuration)
  79. cb->setObjProperty (id, ObjProperty::BANK_RESET, 0); //daycounter 0
  80. else
  81. cb->setObjProperty (id, ObjProperty::BANK_DAYCOUNTER, 1); //daycounter++
  82. }
  83. }
  84. }
  85. bool CBank::wasVisited (PlayerColor player) const
  86. {
  87. return !bc; //FIXME: player A should not know about visit done by player B
  88. }
  89. void CBank::onHeroVisit(const CGHeroInstance * h) const
  90. {
  91. int banktext = 0;
  92. switch (ID)
  93. {
  94. case Obj::DERELICT_SHIP:
  95. banktext = 41;
  96. break;
  97. case Obj::DRAGON_UTOPIA:
  98. banktext = 47;
  99. break;
  100. case Obj::CRYPT:
  101. banktext = 119;
  102. break;
  103. case Obj::SHIPWRECK:
  104. banktext = 122;
  105. break;
  106. case Obj::PYRAMID:
  107. banktext = 105;
  108. break;
  109. case Obj::CREATURE_BANK:
  110. default:
  111. banktext = 32;
  112. break;
  113. }
  114. BlockingDialog bd(true, false);
  115. bd.player = h->getOwner();
  116. bd.soundID = soundBase::invalid; // Sound is handled in json files, else two sounds are played
  117. bd.text.appendLocalString(EMetaText::ADVOB_TXT, banktext);
  118. if (banktext == 32)
  119. bd.text.replaceRawString(getObjectName());
  120. if (VLC->settings()->getBoolean(EGameSettings::BANKS_SHOW_GUARDS_COMPOSITION))
  121. {
  122. std::map<CreatureID, int> guardsAmounts;
  123. for (auto const & guard : bc->guards)
  124. guardsAmounts[guard.getType()->getId()] += guard.getCount();
  125. for (auto const & guard : guardsAmounts)
  126. {
  127. Component comp;
  128. comp.id = Component::EComponentType::CREATURE;
  129. comp.subtype = guard.first.getNum();
  130. comp.val = guard.second;
  131. bd.components.push_back(comp);
  132. }
  133. }
  134. cb->showBlockingDialog(&bd);
  135. }
  136. void CBank::doVisit(const CGHeroInstance * hero) const
  137. {
  138. int textID = -1;
  139. InfoWindow iw;
  140. iw.type = EInfoWindowMode::AUTO;
  141. iw.player = hero->getOwner();
  142. MetaString loot;
  143. if (bc)
  144. {
  145. switch (ID)
  146. {
  147. case Obj::DERELICT_SHIP:
  148. textID = 43;
  149. break;
  150. case Obj::CRYPT:
  151. textID = 121;
  152. break;
  153. case Obj::SHIPWRECK:
  154. textID = 124;
  155. break;
  156. case Obj::PYRAMID:
  157. textID = 106;
  158. break;
  159. case Obj::CREATURE_BANK:
  160. case Obj::DRAGON_UTOPIA:
  161. default:
  162. textID = 34;
  163. break;
  164. }
  165. }
  166. else
  167. {
  168. switch (ID)
  169. {
  170. case Obj::SHIPWRECK:
  171. case Obj::DERELICT_SHIP:
  172. case Obj::CRYPT:
  173. {
  174. GiveBonus gbonus;
  175. gbonus.id = hero->id.getNum();
  176. gbonus.bonus.duration = BonusDuration::ONE_BATTLE;
  177. gbonus.bonus.source = BonusSource::OBJECT;
  178. gbonus.bonus.sid = ID;
  179. gbonus.bonus.type = BonusType::MORALE;
  180. gbonus.bonus.val = -1;
  181. switch (ID)
  182. {
  183. case Obj::SHIPWRECK:
  184. textID = 123;
  185. gbonus.bdescr.appendRawString(VLC->generaltexth->arraytxt[99]);
  186. break;
  187. case Obj::DERELICT_SHIP:
  188. textID = 42;
  189. gbonus.bdescr.appendRawString(VLC->generaltexth->arraytxt[101]);
  190. break;
  191. case Obj::CRYPT:
  192. textID = 120;
  193. gbonus.bdescr.appendRawString(VLC->generaltexth->arraytxt[98]);
  194. break;
  195. }
  196. cb->giveHeroBonus(&gbonus);
  197. iw.components.emplace_back(Component::EComponentType::MORALE, 0, -1, 0);
  198. iw.soundID = soundBase::GRAVEYARD;
  199. break;
  200. }
  201. case Obj::PYRAMID:
  202. {
  203. GiveBonus gb;
  204. gb.bonus = Bonus(BonusDuration::ONE_BATTLE, BonusType::LUCK, BonusSource::OBJECT, -2, id.getNum(), VLC->generaltexth->arraytxt[70]);
  205. gb.id = hero->id.getNum();
  206. cb->giveHeroBonus(&gb);
  207. textID = 107;
  208. iw.components.emplace_back(Component::EComponentType::LUCK, 0, -2, 0);
  209. break;
  210. }
  211. case Obj::CREATURE_BANK:
  212. case Obj::DRAGON_UTOPIA:
  213. default:
  214. iw.text.appendRawString(VLC->generaltexth->advobtxt[33]);// This was X, now is completely empty
  215. iw.text.replaceRawString(getObjectName());
  216. }
  217. if(textID != -1)
  218. {
  219. iw.text.appendLocalString(EMetaText::ADVOB_TXT, textID);
  220. }
  221. cb->showInfoDialog(&iw);
  222. }
  223. //grant resources
  224. if (bc)
  225. {
  226. for (int it = 0; it < bc->resources.size(); it++)
  227. {
  228. if (bc->resources[it] != 0)
  229. {
  230. iw.components.emplace_back(Component::EComponentType::RESOURCE, it, bc->resources[it], 0);
  231. loot.appendRawString("%d %s");
  232. loot.replaceNumber(iw.components.back().val);
  233. loot.replaceLocalString(EMetaText::RES_NAMES, iw.components.back().subtype);
  234. cb->giveResource(hero->getOwner(), static_cast<EGameResID>(it), bc->resources[it]);
  235. }
  236. }
  237. //grant artifacts
  238. for (auto & elem : bc->artifacts)
  239. {
  240. iw.components.emplace_back(Component::EComponentType::ARTIFACT, elem, 0, 0);
  241. loot.appendRawString("%s");
  242. loot.replaceLocalString(EMetaText::ART_NAMES, elem);
  243. cb->giveHeroNewArtifact(hero, VLC->arth->objects[elem], ArtifactPosition::FIRST_AVAILABLE);
  244. }
  245. //display loot
  246. if (!iw.components.empty())
  247. {
  248. iw.text.appendLocalString(EMetaText::ADVOB_TXT, textID);
  249. if (textID == 34)
  250. {
  251. const auto * strongest = boost::range::max_element(bc->guards, [](const CStackBasicDescriptor & a, const CStackBasicDescriptor & b)
  252. {
  253. return a.type->getFightValue() < b.type->getFightValue();
  254. })->type;
  255. iw.text.replaceLocalString(EMetaText::CRE_PL_NAMES, strongest->getId());
  256. iw.text.replaceRawString(loot.buildList());
  257. }
  258. cb->showInfoDialog(&iw);
  259. }
  260. loot.clear();
  261. iw.components.clear();
  262. iw.text.clear();
  263. if (!bc->spells.empty())
  264. {
  265. std::set<SpellID> spells;
  266. bool noWisdom = false;
  267. if(textID == 106)
  268. {
  269. iw.text.appendLocalString(EMetaText::ADVOB_TXT, textID); //pyramid
  270. }
  271. for(const SpellID & spellId : bc->spells)
  272. {
  273. const auto * spell = spellId.toSpell(VLC->spells());
  274. iw.text.appendLocalString(EMetaText::SPELL_NAME, spellId);
  275. if(spell->getLevel() <= hero->maxSpellLevel())
  276. {
  277. if(hero->canLearnSpell(spell))
  278. {
  279. spells.insert(spellId);
  280. iw.components.emplace_back(Component::EComponentType::SPELL, spellId, 0, 0);
  281. }
  282. }
  283. else
  284. noWisdom = true;
  285. }
  286. if (!hero->getArt(ArtifactPosition::SPELLBOOK))
  287. iw.text.appendLocalString(EMetaText::ADVOB_TXT, 109); //no spellbook
  288. else if(noWisdom)
  289. iw.text.appendLocalString(EMetaText::ADVOB_TXT, 108); //no expert Wisdom
  290. if(!iw.components.empty() || !iw.text.toString().empty())
  291. cb->showInfoDialog(&iw);
  292. if(!spells.empty())
  293. cb->changeSpells(hero, true, spells);
  294. }
  295. iw.components.clear();
  296. iw.text.clear();
  297. //grant creatures
  298. CCreatureSet ourArmy;
  299. for(const auto & slot : bc->creatures)
  300. {
  301. ourArmy.addToSlot(ourArmy.getSlotFor(slot.type->getId()), slot.type->getId(), slot.count);
  302. }
  303. for(const auto & elem : ourArmy.Slots())
  304. {
  305. iw.components.emplace_back(*elem.second);
  306. loot.appendRawString("%s");
  307. loot.replaceCreatureName(*elem.second);
  308. }
  309. if(ourArmy.stacksCount())
  310. {
  311. if(ourArmy.stacksCount() == 1 && ourArmy.Slots().begin()->second->count == 1)
  312. iw.text.appendLocalString(EMetaText::ADVOB_TXT, 185);
  313. else
  314. iw.text.appendLocalString(EMetaText::ADVOB_TXT, 186);
  315. iw.text.replaceRawString(loot.buildList());
  316. iw.text.replaceRawString(hero->getNameTranslated());
  317. cb->showInfoDialog(&iw);
  318. cb->giveCreatures(this, hero, ourArmy, false);
  319. }
  320. cb->setObjProperty(id, ObjProperty::BANK_CLEAR, 0); //bc = nullptr
  321. }
  322. }
  323. void CBank::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const
  324. {
  325. if (result.winner == 0)
  326. {
  327. doVisit(hero);
  328. }
  329. }
  330. void CBank::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const
  331. {
  332. if (answer)
  333. {
  334. if (bc) // not looted bank
  335. cb->startBattleI(hero, this, true);
  336. else
  337. doVisit(hero);
  338. }
  339. }
  340. VCMI_LIB_NAMESPACE_END