CGTownBuilding.cpp 15 KB

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