CGTownBuilding.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536
  1. /*
  2. * CGTownBuilding.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 "CGTownBuilding.h"
  12. #include "CGTownInstance.h"
  13. #include "../texts/CGeneralTextHandler.h"
  14. #include "../IGameCallback.h"
  15. #include "../gameState/CGameState.h"
  16. #include "../mapObjects/CGHeroInstance.h"
  17. #include "../networkPacks/PacksForClient.h"
  18. #include "../entities/building/CBuilding.h"
  19. #include <vstd/RNG.h>
  20. VCMI_LIB_NAMESPACE_BEGIN
  21. CGTownBuilding::CGTownBuilding(IGameCallback * cb)
  22. : IObjectInterface(cb)
  23. , town(nullptr)
  24. {}
  25. CGTownBuilding::CGTownBuilding(CGTownInstance * town)
  26. : IObjectInterface(town->cb)
  27. , town(town)
  28. {}
  29. PlayerColor CGTownBuilding::getOwner() const
  30. {
  31. return town->getOwner();
  32. }
  33. MapObjectID CGTownBuilding::getObjGroupIndex() const
  34. {
  35. return -1;
  36. }
  37. MapObjectSubID CGTownBuilding::getObjTypeIndex() const
  38. {
  39. return 0;
  40. }
  41. int3 CGTownBuilding::visitablePos() const
  42. {
  43. return town->visitablePos();
  44. }
  45. int3 CGTownBuilding::getPosition() const
  46. {
  47. return town->getPosition();
  48. }
  49. std::string CGTownBuilding::getVisitingBonusGreeting() const
  50. {
  51. auto bonusGreeting = town->getTown()->getGreeting(bType);
  52. if(!bonusGreeting.empty())
  53. return bonusGreeting;
  54. switch(bType)
  55. {
  56. case BuildingSubID::MANA_VORTEX:
  57. bonusGreeting = std::string(VLC->generaltexth->translate("vcmi.townHall.greetingManaVortex"));
  58. break;
  59. case BuildingSubID::KNOWLEDGE_VISITING_BONUS:
  60. bonusGreeting = std::string(VLC->generaltexth->translate("vcmi.townHall.greetingKnowledge"));
  61. break;
  62. case BuildingSubID::SPELL_POWER_VISITING_BONUS:
  63. bonusGreeting = std::string(VLC->generaltexth->translate("vcmi.townHall.greetingSpellPower"));
  64. break;
  65. case BuildingSubID::ATTACK_VISITING_BONUS:
  66. bonusGreeting = std::string(VLC->generaltexth->translate("vcmi.townHall.greetingAttack"));
  67. break;
  68. case BuildingSubID::EXPERIENCE_VISITING_BONUS:
  69. bonusGreeting = std::string(VLC->generaltexth->translate("vcmi.townHall.greetingExperience"));
  70. break;
  71. case BuildingSubID::DEFENSE_VISITING_BONUS:
  72. bonusGreeting = std::string(VLC->generaltexth->translate("vcmi.townHall.greetingDefence"));
  73. break;
  74. }
  75. auto buildingName = town->getTown()->getSpecialBuilding(bType)->getNameTranslated();
  76. if(bonusGreeting.empty())
  77. {
  78. bonusGreeting = "Error: Bonus greeting for '%s' is not localized.";
  79. logGlobal->error("'%s' building of '%s' faction has not localized bonus greeting.", buildingName, town->getTown()->faction->getNameTranslated());
  80. }
  81. boost::algorithm::replace_first(bonusGreeting, "%s", buildingName);
  82. town->getTown()->setGreeting(bType, bonusGreeting);
  83. return bonusGreeting;
  84. }
  85. std::string CGTownBuilding::getCustomBonusGreeting(const Bonus & bonus) const
  86. {
  87. if(bonus.type == BonusType::TOWN_MAGIC_WELL)
  88. {
  89. MetaString wellGreeting = MetaString::createFromTextID("vcmi.townHall.greetingInTownMagicWell");
  90. wellGreeting.replaceTextID(town->getTown()->getSpecialBuilding(bType)->getNameTextID());
  91. return wellGreeting.toString();
  92. }
  93. MetaString greeting = MetaString::createFromTextID("vcmi.townHall.greetingCustomBonus");
  94. std::string paramTextID;
  95. std::string until;
  96. if(bonus.type == BonusType::MORALE)
  97. paramTextID = "core.genrltxt.384"; // Morale
  98. if(bonus.type == BonusType::LUCK)
  99. paramTextID = "core.genrltxt.385"; // Luck
  100. greeting.replaceTextID(town->getTown()->getSpecialBuilding(bType)->getNameTextID());
  101. greeting.replaceNumber(bonus.val);
  102. greeting.replaceTextID(paramTextID);
  103. if (bonus.duration == BonusDuration::ONE_BATTLE)
  104. greeting.replaceTextID("vcmi.townHall.greetingCustomUntil");
  105. else
  106. greeting.replaceRawString(".");
  107. return greeting.toString();
  108. }
  109. COPWBonus::COPWBonus(IGameCallback *cb)
  110. : CGTownBuilding(cb)
  111. {}
  112. COPWBonus::COPWBonus(const BuildingID & bid, BuildingSubID::EBuildingSubID subId, CGTownInstance * cgTown)
  113. : CGTownBuilding(cgTown)
  114. {
  115. bID = bid;
  116. bType = subId;
  117. indexOnTV = static_cast<si32>(town->bonusingBuildings.size());
  118. }
  119. void COPWBonus::setProperty(ObjProperty what, ObjPropertyID identifier)
  120. {
  121. switch (what)
  122. {
  123. case ObjProperty::VISITORS:
  124. visitors.insert(identifier.as<ObjectInstanceID>());
  125. break;
  126. case ObjProperty::STRUCTURE_CLEAR_VISITORS:
  127. visitors.clear();
  128. break;
  129. }
  130. }
  131. void COPWBonus::onHeroVisit (const CGHeroInstance * h) const
  132. {
  133. ObjectInstanceID heroID = h->id;
  134. if(town->hasBuilt(bID))
  135. {
  136. InfoWindow iw;
  137. iw.player = h->tempOwner;
  138. switch (this->bType)
  139. {
  140. case BuildingSubID::STABLES:
  141. if(!h->hasBonusFrom(BonusSource::OBJECT_TYPE, BonusSourceID(Obj(Obj::STABLES)))) //does not stack with advMap Stables
  142. {
  143. GiveBonus gb;
  144. gb.bonus = Bonus(BonusDuration::ONE_WEEK, BonusType::MOVEMENT, BonusSource::OBJECT_TYPE, 600, BonusSourceID(Obj(Obj::STABLES)), BonusCustomSubtype::heroMovementLand);
  145. gb.id = heroID;
  146. cb->giveHeroBonus(&gb);
  147. cb->setMovePoints(heroID, 600, false);
  148. iw.text.appendRawString(VLC->generaltexth->allTexts[580]);
  149. cb->showInfoDialog(&iw);
  150. }
  151. break;
  152. case BuildingSubID::MANA_VORTEX:
  153. if(visitors.empty())
  154. {
  155. if(h->mana < h->manaLimit() * 2)
  156. {
  157. cb->setManaPoints (heroID, 2 * h->manaLimit());
  158. //TODO: investigate line below
  159. //cb->setObjProperty (town->id, ObjProperty::VISITED, true);
  160. iw.text.appendRawString(getVisitingBonusGreeting());
  161. cb->showInfoDialog(&iw);
  162. town->addHeroToStructureVisitors(h, indexOnTV);
  163. }
  164. }
  165. break;
  166. }
  167. }
  168. }
  169. CTownBonus::CTownBonus(IGameCallback *cb)
  170. : CGTownBuilding(cb)
  171. {}
  172. CTownBonus::CTownBonus(const BuildingID & index, BuildingSubID::EBuildingSubID subId, CGTownInstance * cgTown)
  173. : CGTownBuilding(cgTown)
  174. {
  175. bID = index;
  176. bType = subId;
  177. indexOnTV = static_cast<si32>(town->bonusingBuildings.size());
  178. }
  179. void CTownBonus::setProperty(ObjProperty what, ObjPropertyID identifier)
  180. {
  181. if(what == ObjProperty::VISITORS)
  182. visitors.insert(identifier.as<ObjectInstanceID>());
  183. }
  184. void CTownBonus::onHeroVisit (const CGHeroInstance * h) const
  185. {
  186. ObjectInstanceID heroID = h->id;
  187. if(town->hasBuilt(bID) && visitors.find(heroID) == visitors.end())
  188. {
  189. si64 val = 0;
  190. InfoWindow iw;
  191. PrimarySkill what = PrimarySkill::NONE;
  192. switch(bType)
  193. {
  194. case BuildingSubID::KNOWLEDGE_VISITING_BONUS: //wall of knowledge
  195. what = PrimarySkill::KNOWLEDGE;
  196. val = 1;
  197. iw.components.emplace_back(ComponentType::PRIM_SKILL, PrimarySkill::KNOWLEDGE, 1);
  198. break;
  199. case BuildingSubID::SPELL_POWER_VISITING_BONUS: //order of fire
  200. what = PrimarySkill::SPELL_POWER;
  201. val = 1;
  202. iw.components.emplace_back(ComponentType::PRIM_SKILL, PrimarySkill::SPELL_POWER, 1);
  203. break;
  204. case BuildingSubID::ATTACK_VISITING_BONUS: //hall of Valhalla
  205. what = PrimarySkill::ATTACK;
  206. val = 1;
  207. iw.components.emplace_back(ComponentType::PRIM_SKILL, PrimarySkill::ATTACK, 1);
  208. break;
  209. case BuildingSubID::EXPERIENCE_VISITING_BONUS: //academy of battle scholars
  210. what = PrimarySkill::EXPERIENCE;
  211. val = static_cast<int>(h->calculateXp(1000));
  212. iw.components.emplace_back(ComponentType::EXPERIENCE, val);
  213. break;
  214. case BuildingSubID::DEFENSE_VISITING_BONUS: //cage of warlords
  215. what = PrimarySkill::DEFENSE;
  216. val = 1;
  217. iw.components.emplace_back(ComponentType::PRIM_SKILL, PrimarySkill::DEFENSE, 1);
  218. break;
  219. case BuildingSubID::CUSTOM_VISITING_BONUS:
  220. const auto building = town->getTown()->buildings.at(bID);
  221. if(!h->hasBonusFrom(BonusSource::TOWN_STRUCTURE, BonusSourceID(building->getUniqueTypeID())))
  222. {
  223. const auto & bonuses = building->onVisitBonuses;
  224. applyBonuses(const_cast<CGHeroInstance *>(h), bonuses);
  225. }
  226. break;
  227. }
  228. if(what != PrimarySkill::NONE)
  229. {
  230. iw.player = cb->getOwner(heroID);
  231. iw.text.appendRawString(getVisitingBonusGreeting());
  232. cb->showInfoDialog(&iw);
  233. if (what == PrimarySkill::EXPERIENCE)
  234. cb->giveExperience(cb->getHero(heroID), val);
  235. else
  236. cb->changePrimSkill(cb->getHero(heroID), what, val);
  237. town->addHeroToStructureVisitors(h, indexOnTV);
  238. }
  239. }
  240. }
  241. void CTownBonus::applyBonuses(CGHeroInstance * h, const BonusList & bonuses) const
  242. {
  243. auto addToVisitors = false;
  244. for(const auto & bonus : bonuses)
  245. {
  246. GiveBonus gb;
  247. InfoWindow iw;
  248. if(bonus->type == BonusType::TOWN_MAGIC_WELL)
  249. {
  250. if(h->mana >= h->manaLimit())
  251. return;
  252. cb->setManaPoints(h->id, h->manaLimit());
  253. bonus->duration = BonusDuration::ONE_DAY;
  254. }
  255. gb.bonus = * bonus;
  256. gb.id = h->id;
  257. cb->giveHeroBonus(&gb);
  258. if(bonus->duration == BonusDuration::PERMANENT)
  259. addToVisitors = true;
  260. iw.player = cb->getOwner(h->id);
  261. iw.text.appendRawString(getCustomBonusGreeting(gb.bonus));
  262. cb->showInfoDialog(&iw);
  263. }
  264. if(addToVisitors)
  265. town->addHeroToStructureVisitors(h, indexOnTV);
  266. }
  267. CTownRewardableBuilding::CTownRewardableBuilding(IGameCallback *cb)
  268. : CGTownBuilding(cb)
  269. {}
  270. CTownRewardableBuilding::CTownRewardableBuilding(const BuildingID & index, BuildingSubID::EBuildingSubID subId, CGTownInstance * cgTown, vstd::RNG & rand)
  271. : CGTownBuilding(cgTown)
  272. {
  273. bID = index;
  274. bType = subId;
  275. indexOnTV = static_cast<si32>(town->bonusingBuildings.size());
  276. initObj(rand);
  277. }
  278. void CTownRewardableBuilding::initObj(vstd::RNG & rand)
  279. {
  280. assert(town && town->town);
  281. configuration = generateConfiguration(rand);
  282. }
  283. Rewardable::Configuration CTownRewardableBuilding::generateConfiguration(vstd::RNG & rand) const
  284. {
  285. Rewardable::Configuration result;
  286. auto building = town->town->buildings.at(bID);
  287. building->rewardableObjectInfo.configureObject(result, rand, cb);
  288. for(auto & rewardInfo : result.info)
  289. {
  290. for (auto & bonus : rewardInfo.reward.bonuses)
  291. {
  292. bonus.source = BonusSource::TOWN_STRUCTURE;
  293. bonus.sid = BonusSourceID(building->getUniqueTypeID());
  294. }
  295. }
  296. return result;
  297. }
  298. void CTownRewardableBuilding::newTurn(vstd::RNG & rand) const
  299. {
  300. if (configuration.resetParameters.period != 0 && cb->getDate(Date::DAY) > 1 && ((cb->getDate(Date::DAY)-1) % configuration.resetParameters.period) == 0)
  301. {
  302. auto newConfiguration = generateConfiguration(rand);
  303. cb->setRewardableObjectConfiguration(town->id, bID, newConfiguration);
  304. if(configuration.resetParameters.visitors)
  305. {
  306. cb->setObjPropertyValue(town->id, ObjProperty::STRUCTURE_CLEAR_VISITORS, indexOnTV);
  307. }
  308. }
  309. }
  310. void CTownRewardableBuilding::setProperty(ObjProperty what, ObjPropertyID identifier)
  311. {
  312. switch (what)
  313. {
  314. case ObjProperty::VISITORS:
  315. visitors.insert(identifier.as<ObjectInstanceID>());
  316. break;
  317. case ObjProperty::STRUCTURE_CLEAR_VISITORS:
  318. visitors.clear();
  319. break;
  320. case ObjProperty::REWARD_SELECT:
  321. selectedReward = identifier.getNum();
  322. break;
  323. }
  324. }
  325. void CTownRewardableBuilding::heroLevelUpDone(const CGHeroInstance *hero) const
  326. {
  327. grantRewardAfterLevelup(cb, configuration.info.at(selectedReward), town, hero);
  328. }
  329. void CTownRewardableBuilding::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const
  330. {
  331. if(answer == 0)
  332. return; // player refused
  333. if(visitors.find(hero->id) != visitors.end())
  334. return; // query not for this building
  335. if(answer > 0 && answer-1 < configuration.info.size())
  336. {
  337. auto list = getAvailableRewards(hero, Rewardable::EEventType::EVENT_FIRST_VISIT);
  338. grantReward(list[answer - 1], hero);
  339. }
  340. else
  341. {
  342. throw std::runtime_error("Unhandled choice");
  343. }
  344. }
  345. void CTownRewardableBuilding::grantReward(ui32 rewardID, const CGHeroInstance * hero) const
  346. {
  347. town->addHeroToStructureVisitors(hero, indexOnTV);
  348. grantRewardBeforeLevelup(cb, configuration.info.at(rewardID), hero);
  349. // hero is not blocked by levelup dialog - grant remainder immediately
  350. if(!cb->isVisitCoveredByAnotherQuery(town, hero))
  351. {
  352. grantRewardAfterLevelup(cb, configuration.info.at(rewardID), town, hero);
  353. }
  354. }
  355. bool CTownRewardableBuilding::wasVisitedBefore(const CGHeroInstance * contextHero) const
  356. {
  357. switch (configuration.visitMode)
  358. {
  359. case Rewardable::VISIT_UNLIMITED:
  360. return false;
  361. case Rewardable::VISIT_ONCE:
  362. return !visitors.empty();
  363. case Rewardable::VISIT_PLAYER:
  364. return false; //not supported
  365. case Rewardable::VISIT_BONUS:
  366. {
  367. const auto building = town->getTown()->buildings.at(bID);
  368. return contextHero->hasBonusFrom(BonusSource::TOWN_STRUCTURE, BonusSourceID(building->getUniqueTypeID()));
  369. }
  370. case Rewardable::VISIT_HERO:
  371. return visitors.find(contextHero->id) != visitors.end();
  372. case Rewardable::VISIT_LIMITER:
  373. return configuration.visitLimiter.heroAllowed(contextHero);
  374. default:
  375. return false;
  376. }
  377. }
  378. void CTownRewardableBuilding::onHeroVisit(const CGHeroInstance *h) const
  379. {
  380. auto grantRewardWithMessage = [&](int index) -> void
  381. {
  382. auto vi = configuration.info.at(index);
  383. logGlobal->debug("Granting reward %d. Message says: %s", index, vi.message.toString());
  384. town->addHeroToStructureVisitors(h, indexOnTV); //adding to visitors
  385. InfoWindow iw;
  386. iw.player = h->tempOwner;
  387. iw.text = vi.message;
  388. vi.reward.loadComponents(iw.components, h);
  389. iw.type = EInfoWindowMode::MODAL;
  390. if(!iw.components.empty() || !iw.text.toString().empty())
  391. cb->showInfoDialog(&iw);
  392. grantReward(index, h);
  393. };
  394. auto selectRewardsMessage = [&](const std::vector<ui32> & rewards, const MetaString & dialog) -> void
  395. {
  396. BlockingDialog sd(configuration.canRefuse, rewards.size() > 1);
  397. sd.player = h->tempOwner;
  398. sd.text = dialog;
  399. if (rewards.size() > 1)
  400. for (auto index : rewards)
  401. sd.components.push_back(configuration.info.at(index).reward.getDisplayedComponent(h));
  402. if (rewards.size() == 1)
  403. configuration.info.at(rewards.front()).reward.loadComponents(sd.components, h);
  404. cb->showBlockingDialog(&sd);
  405. };
  406. if(!town->hasBuilt(bID) || cb->isVisitCoveredByAnotherQuery(town, h))
  407. return;
  408. if(!wasVisitedBefore(h))
  409. {
  410. auto rewards = getAvailableRewards(h, Rewardable::EEventType::EVENT_FIRST_VISIT);
  411. logGlobal->debug("Visiting object with %d possible rewards", rewards.size());
  412. switch (rewards.size())
  413. {
  414. case 0: // no available rewards, e.g. visiting School of War without gold
  415. {
  416. auto emptyRewards = getAvailableRewards(h, Rewardable::EEventType::EVENT_NOT_AVAILABLE);
  417. if (!emptyRewards.empty())
  418. grantRewardWithMessage(emptyRewards[0]);
  419. else
  420. logMod->warn("No applicable message for visiting empty object!");
  421. break;
  422. }
  423. case 1: // one reward. Just give it with message
  424. {
  425. if (configuration.canRefuse)
  426. selectRewardsMessage(rewards, configuration.info.at(rewards.front()).message);
  427. else
  428. grantRewardWithMessage(rewards.front());
  429. break;
  430. }
  431. default: // multiple rewards. Act according to select mode
  432. {
  433. switch (configuration.selectMode) {
  434. case Rewardable::SELECT_PLAYER: // player must select
  435. selectRewardsMessage(rewards, configuration.onSelect);
  436. break;
  437. case Rewardable::SELECT_FIRST: // give first available
  438. grantRewardWithMessage(rewards.front());
  439. break;
  440. case Rewardable::SELECT_RANDOM: // give random
  441. grantRewardWithMessage(*RandomGeneratorUtil::nextItem(rewards, cb->gameState()->getRandomGenerator()));
  442. break;
  443. }
  444. break;
  445. }
  446. }
  447. }
  448. else
  449. {
  450. logGlobal->debug("Revisiting already visited object");
  451. auto visitedRewards = getAvailableRewards(h, Rewardable::EEventType::EVENT_ALREADY_VISITED);
  452. if (!visitedRewards.empty())
  453. grantRewardWithMessage(visitedRewards[0]);
  454. else
  455. logMod->debug("No applicable message for visiting already visited object!");
  456. }
  457. }
  458. VCMI_LIB_NAMESPACE_END