2
0

CQuest.cpp 29 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174
  1. /*
  2. * CQuest.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 "CQuest.h"
  12. #include <vcmi/spells/Spell.h>
  13. #include "../NetPacks.h"
  14. #include "../CSoundBase.h"
  15. #include "../CGeneralTextHandler.h"
  16. #include "../CHeroHandler.h"
  17. #include "CObjectClassesHandler.h"
  18. #include "MiscObjects.h"
  19. #include "../IGameCallback.h"
  20. #include "../CGameState.h"
  21. #include "../serializer/JsonSerializeFormat.h"
  22. #include "../CModHandler.h"
  23. #include "../GameConstants.h"
  24. #include "../StringConstants.h"
  25. #include "../CSkillHandler.h"
  26. #include "../mapping/CMap.h"
  27. std::map <PlayerColor, std::set <ui8> > CGKeys::playerKeyMap;
  28. CQuest::CQuest()
  29. : qid(-1), missionType(MISSION_NONE), progress(NOT_ACTIVE), lastDay(-1), m13489val(0),
  30. textOption(0), completedOption(0), stackDirection(0), heroPortrait(-1),
  31. isCustomFirst(false), isCustomNext(false), isCustomComplete(false)
  32. {
  33. }
  34. ///helpers
  35. static void showInfoDialog(const PlayerColor playerID, const ui32 txtID, const ui16 soundID = 0)
  36. {
  37. InfoWindow iw;
  38. iw.soundID = soundID;
  39. iw.player = playerID;
  40. iw.text.addTxt(MetaString::ADVOB_TXT,txtID);
  41. IObjectInterface::cb->sendAndApply(&iw);
  42. }
  43. static void showInfoDialog(const CGHeroInstance* h, const ui32 txtID, const ui16 soundID = 0)
  44. {
  45. const PlayerColor playerID = h->getOwner();
  46. showInfoDialog(playerID,txtID,soundID);
  47. }
  48. static std::string & visitedTxt(const bool visited)
  49. {
  50. int id = visited ? 352 : 353;
  51. return VLC->generaltexth->allTexts[id];
  52. }
  53. bool CQuest::checkMissionArmy(const CQuest * q, const CCreatureSet * army)
  54. {
  55. std::vector<CStackBasicDescriptor>::const_iterator cre;
  56. TSlots::const_iterator it;
  57. ui32 count;
  58. ui32 slotsCount = 0;
  59. bool hasExtraCreatures = false;
  60. for(cre = q->m6creatures.begin(); cre != q->m6creatures.end(); ++cre)
  61. {
  62. for(count = 0, it = army->Slots().begin(); it != army->Slots().end(); ++it)
  63. {
  64. if(it->second->type == cre->type)
  65. {
  66. count += it->second->count;
  67. slotsCount++;
  68. }
  69. }
  70. if((TQuantity)count < cre->count) //not enough creatures of this kind
  71. return false;
  72. hasExtraCreatures |= (TQuantity)count > cre->count;
  73. }
  74. return hasExtraCreatures || slotsCount < army->Slots().size();
  75. }
  76. bool CQuest::checkQuest(const CGHeroInstance * h) const
  77. {
  78. switch (missionType)
  79. {
  80. case MISSION_NONE:
  81. return true;
  82. case MISSION_LEVEL:
  83. if(m13489val <= h->level)
  84. return true;
  85. return false;
  86. case MISSION_PRIMARY_STAT:
  87. for(int i = 0; i < GameConstants::PRIMARY_SKILLS; ++i)
  88. {
  89. if(h->getPrimSkillLevel(static_cast<PrimarySkill::PrimarySkill>(i)) < (int)m2stats[i])
  90. return false;
  91. }
  92. return true;
  93. case MISSION_KILL_HERO:
  94. case MISSION_KILL_CREATURE:
  95. if (!h->cb->getObjByQuestIdentifier(m13489val))
  96. return true;
  97. return false;
  98. case MISSION_ART:
  99. for(auto & elem : m5arts)
  100. {
  101. if(h->hasArt(elem, false, true))
  102. continue;
  103. return false; //if the artifact was not found
  104. }
  105. return true;
  106. case MISSION_ARMY:
  107. return checkMissionArmy(this, h);
  108. case MISSION_RESOURCES:
  109. for(Res::ERes i = Res::WOOD; i <= Res::GOLD; vstd::advance(i, +1)) //including Mithril ?
  110. { //Quest has no direct access to callback
  111. if(h->cb->getResource (h->tempOwner, i) < (int)m7resources[i])
  112. return false;
  113. }
  114. return true;
  115. case MISSION_HERO:
  116. if(m13489val == h->type->ID.getNum())
  117. return true;
  118. return false;
  119. case MISSION_PLAYER:
  120. if(m13489val == h->getOwner().getNum())
  121. return true;
  122. return false;
  123. default:
  124. return false;
  125. }
  126. }
  127. void CQuest::getVisitText(MetaString &iwText, std::vector<Component> &components, bool isCustom, bool firstVisit, const CGHeroInstance * h) const
  128. {
  129. std::string text;
  130. bool failRequirements = (h ? !checkQuest(h) : true);
  131. if(firstVisit)
  132. {
  133. isCustom = isCustomFirst;
  134. iwText << (text = firstVisitText);
  135. }
  136. else if(failRequirements)
  137. {
  138. isCustom = isCustomNext;
  139. iwText << (text = nextVisitText);
  140. }
  141. switch (missionType)
  142. {
  143. case MISSION_LEVEL:
  144. components.push_back(Component(Component::EXPERIENCE, 0, m13489val, 0));
  145. if(!isCustom)
  146. iwText.addReplacement(m13489val);
  147. break;
  148. case MISSION_PRIMARY_STAT:
  149. {
  150. MetaString loot;
  151. for(int i = 0; i < 4; ++i)
  152. {
  153. if(m2stats[i])
  154. {
  155. components.push_back(Component(Component::PRIM_SKILL, i, m2stats[i], 0));
  156. loot << "%d %s";
  157. loot.addReplacement(m2stats[i]);
  158. loot.addReplacement(VLC->generaltexth->primarySkillNames[i]);
  159. }
  160. }
  161. if (!isCustom)
  162. iwText.addReplacement(loot.buildList());
  163. }
  164. break;
  165. case MISSION_KILL_HERO:
  166. components.push_back(Component(Component::HERO_PORTRAIT, heroPortrait, 0, 0));
  167. if(!isCustom)
  168. addReplacements(iwText, text);
  169. break;
  170. case MISSION_HERO:
  171. //FIXME: portrait may not match hero, if custom portrait was set in map editor
  172. components.push_back(Component(Component::HERO_PORTRAIT, VLC->heroh->objects[m13489val]->imageIndex, 0, 0));
  173. if(!isCustom)
  174. iwText.addReplacement(VLC->heroh->objects[m13489val]->name);
  175. break;
  176. case MISSION_KILL_CREATURE:
  177. {
  178. components.push_back(Component(stackToKill));
  179. if(!isCustom)
  180. {
  181. addReplacements(iwText, text);
  182. }
  183. }
  184. break;
  185. case MISSION_ART:
  186. {
  187. MetaString loot;
  188. for(auto & elem : m5arts)
  189. {
  190. components.push_back(Component(Component::ARTIFACT, elem, 0, 0));
  191. loot << "%s";
  192. loot.addReplacement(MetaString::ART_NAMES, elem);
  193. }
  194. if(!isCustom)
  195. iwText.addReplacement(loot.buildList());
  196. }
  197. break;
  198. case MISSION_ARMY:
  199. {
  200. MetaString loot;
  201. for(auto & elem : m6creatures)
  202. {
  203. components.push_back(Component(elem));
  204. loot << "%s";
  205. loot.addReplacement(elem);
  206. }
  207. if(!isCustom)
  208. iwText.addReplacement(loot.buildList());
  209. }
  210. break;
  211. case MISSION_RESOURCES:
  212. {
  213. MetaString loot;
  214. for(int i = 0; i < 7; ++i)
  215. {
  216. if(m7resources[i])
  217. {
  218. components.push_back(Component (Component::RESOURCE, i, m7resources[i], 0));
  219. loot << "%d %s";
  220. loot.addReplacement(m7resources[i]);
  221. loot.addReplacement(MetaString::RES_NAMES, i);
  222. }
  223. }
  224. if(!isCustom)
  225. iwText.addReplacement(loot.buildList());
  226. }
  227. break;
  228. case MISSION_PLAYER:
  229. components.push_back(Component (Component::FLAG, m13489val, 0, 0));
  230. if(!isCustom)
  231. iwText.addReplacement(VLC->generaltexth->colors[m13489val]);
  232. break;
  233. }
  234. }
  235. void CQuest::getRolloverText(MetaString &ms, bool onHover) const
  236. {
  237. // Quests with MISSION_NONE type don't have a text for them
  238. assert(missionType != MISSION_NONE);
  239. if(onHover)
  240. ms << "\n\n";
  241. ms << VLC->generaltexth->quests[missionType-1][onHover ? 3 : 4][textOption];
  242. switch(missionType)
  243. {
  244. case MISSION_LEVEL:
  245. ms.addReplacement(m13489val);
  246. break;
  247. case MISSION_PRIMARY_STAT:
  248. {
  249. MetaString loot;
  250. for (int i = 0; i < 4; ++i)
  251. {
  252. if (m2stats[i])
  253. {
  254. loot << "%d %s";
  255. loot.addReplacement(m2stats[i]);
  256. loot.addReplacement(VLC->generaltexth->primarySkillNames[i]);
  257. }
  258. }
  259. ms.addReplacement(loot.buildList());
  260. }
  261. break;
  262. case MISSION_KILL_HERO:
  263. ms.addReplacement(heroName);
  264. break;
  265. case MISSION_KILL_CREATURE:
  266. ms.addReplacement(stackToKill);
  267. break;
  268. case MISSION_ART:
  269. {
  270. MetaString loot;
  271. for (auto & elem : m5arts)
  272. {
  273. loot << "%s";
  274. loot.addReplacement(MetaString::ART_NAMES, elem);
  275. }
  276. ms.addReplacement(loot.buildList());
  277. }
  278. break;
  279. case MISSION_ARMY:
  280. {
  281. MetaString loot;
  282. for (auto & elem : m6creatures)
  283. {
  284. loot << "%s";
  285. loot.addReplacement(elem);
  286. }
  287. ms.addReplacement(loot.buildList());
  288. }
  289. break;
  290. case MISSION_RESOURCES:
  291. {
  292. MetaString loot;
  293. for (int i = 0; i < 7; ++i)
  294. {
  295. if (m7resources[i])
  296. {
  297. loot << "%d %s";
  298. loot.addReplacement(m7resources[i]);
  299. loot.addReplacement(MetaString::RES_NAMES, i);
  300. }
  301. }
  302. ms.addReplacement(loot.buildList());
  303. }
  304. break;
  305. case MISSION_HERO:
  306. ms.addReplacement(VLC->heroh->objects[m13489val]->name);
  307. break;
  308. case MISSION_PLAYER:
  309. ms.addReplacement(VLC->generaltexth->colors[m13489val]);
  310. break;
  311. default:
  312. break;
  313. }
  314. }
  315. void CQuest::getCompletionText(MetaString &iwText, std::vector<Component> &components, bool isCustom, const CGHeroInstance * h) const
  316. {
  317. iwText << completedText;
  318. switch(missionType)
  319. {
  320. case CQuest::MISSION_LEVEL:
  321. if (!isCustomComplete)
  322. iwText.addReplacement(m13489val);
  323. break;
  324. case CQuest::MISSION_PRIMARY_STAT:
  325. if (vstd::contains (completedText,'%')) //there's one case when there's nothing to replace
  326. {
  327. MetaString loot;
  328. for (int i = 0; i < 4; ++i)
  329. {
  330. if (m2stats[i])
  331. {
  332. loot << "%d %s";
  333. loot.addReplacement(m2stats[i]);
  334. loot.addReplacement(VLC->generaltexth->primarySkillNames[i]);
  335. }
  336. }
  337. if (!isCustomComplete)
  338. iwText.addReplacement(loot.buildList());
  339. }
  340. break;
  341. case CQuest::MISSION_ART:
  342. {
  343. MetaString loot;
  344. for (auto & elem : m5arts)
  345. {
  346. loot << "%s";
  347. loot.addReplacement(MetaString::ART_NAMES, elem);
  348. }
  349. if (!isCustomComplete)
  350. iwText.addReplacement(loot.buildList());
  351. }
  352. break;
  353. case CQuest::MISSION_ARMY:
  354. {
  355. MetaString loot;
  356. for (auto & elem : m6creatures)
  357. {
  358. loot << "%s";
  359. loot.addReplacement(elem);
  360. }
  361. if (!isCustomComplete)
  362. iwText.addReplacement(loot.buildList());
  363. }
  364. break;
  365. case CQuest::MISSION_RESOURCES:
  366. {
  367. MetaString loot;
  368. for (int i = 0; i < 7; ++i)
  369. {
  370. if (m7resources[i])
  371. {
  372. loot << "%d %s";
  373. loot.addReplacement(m7resources[i]);
  374. loot.addReplacement(MetaString::RES_NAMES, i);
  375. }
  376. }
  377. if (!isCustomComplete)
  378. iwText.addReplacement(loot.buildList());
  379. }
  380. break;
  381. case MISSION_KILL_HERO:
  382. case MISSION_KILL_CREATURE:
  383. if (!isCustomComplete)
  384. addReplacements(iwText, completedText);
  385. break;
  386. case MISSION_HERO:
  387. if (!isCustomComplete)
  388. iwText.addReplacement(VLC->heroh->objects[m13489val]->name);
  389. break;
  390. case MISSION_PLAYER:
  391. if (!isCustomComplete)
  392. iwText.addReplacement(VLC->generaltexth->colors[m13489val]);
  393. break;
  394. }
  395. }
  396. void CQuest::serializeJson(JsonSerializeFormat & handler, const std::string & fieldName)
  397. {
  398. auto q = handler.enterStruct(fieldName);
  399. handler.serializeString("firstVisitText", firstVisitText);
  400. handler.serializeString("nextVisitText", nextVisitText);
  401. handler.serializeString("completedText", completedText);
  402. if(!handler.saving)
  403. {
  404. isCustomFirst = firstVisitText.size() > 0;
  405. isCustomNext = nextVisitText.size() > 0;
  406. isCustomComplete = completedText.size() > 0;
  407. }
  408. static const std::vector<std::string> MISSION_TYPE_JSON =
  409. {
  410. "None", "Level", "PrimaryStat", "KillHero", "KillCreature", "Artifact", "Army", "Resources", "Hero", "Player"
  411. };
  412. handler.serializeEnum("missionType", missionType, Emission::MISSION_NONE, MISSION_TYPE_JSON);
  413. handler.serializeInt("timeLimit", lastDay, -1);
  414. switch (missionType)
  415. {
  416. case MISSION_NONE:
  417. break;
  418. case MISSION_LEVEL:
  419. handler.serializeInt("heroLevel", m13489val, -1);
  420. break;
  421. case MISSION_PRIMARY_STAT:
  422. {
  423. auto primarySkills = handler.enterStruct("primarySkills");
  424. if(!handler.saving)
  425. m2stats.resize(GameConstants::PRIMARY_SKILLS);
  426. for(int i = 0; i < GameConstants::PRIMARY_SKILLS; ++i)
  427. handler.serializeInt(PrimarySkill::names[i], m2stats[i], 0);
  428. }
  429. break;
  430. case MISSION_KILL_HERO:
  431. case MISSION_KILL_CREATURE:
  432. handler.serializeInstance<ui32>("killTarget", m13489val, ui32(-1));
  433. break;
  434. case MISSION_ART:
  435. //todo: ban artifacts
  436. handler.serializeIdArray<ui16, ArtifactID>("artifacts", m5arts);
  437. break;
  438. case MISSION_ARMY:
  439. {
  440. auto a = handler.enterArray("creatures");
  441. a.serializeStruct(m6creatures);
  442. }
  443. break;
  444. case MISSION_RESOURCES:
  445. {
  446. auto r = handler.enterStruct("resources");
  447. if(!handler.saving)
  448. m7resources.resize(GameConstants::RESOURCE_QUANTITY-1);
  449. for(size_t idx = 0; idx < (GameConstants::RESOURCE_QUANTITY - 1); idx++)
  450. {
  451. handler.serializeInt(GameConstants::RESOURCE_NAMES[idx], m7resources[idx], 0);
  452. }
  453. }
  454. break;
  455. case MISSION_HERO:
  456. handler.serializeId<ui32, ui32, HeroTypeID>("hero", m13489val, 0);
  457. break;
  458. case MISSION_PLAYER:
  459. handler.serializeEnum("player", m13489val, PlayerColor::CANNOT_DETERMINE.getNum(), GameConstants::PLAYER_COLOR_NAMES);
  460. break;
  461. default:
  462. logGlobal->error("Invalid quest mission type");
  463. break;
  464. }
  465. }
  466. CGSeerHut::CGSeerHut() : IQuestObject(),
  467. rewardType(NOTHING), rID(-1), rVal(-1)
  468. {
  469. quest->lastDay = -1;
  470. quest->isCustomFirst = false;
  471. quest->isCustomNext = false;
  472. quest->isCustomComplete = false;
  473. }
  474. void CGSeerHut::setObjToKill()
  475. {
  476. if(quest->missionType == CQuest::MISSION_KILL_CREATURE)
  477. {
  478. quest->stackToKill = getCreatureToKill(false)->getStack(SlotID(0)); //FIXME: stacks tend to disappear (desync?) on server :?
  479. assert(quest->stackToKill.type);
  480. quest->stackToKill.count = 0; //no count in info window
  481. quest->stackDirection = checkDirection();
  482. }
  483. else if(quest->missionType == CQuest::MISSION_KILL_HERO)
  484. {
  485. quest->heroName = getHeroToKill(false)->name;
  486. quest->heroPortrait = getHeroToKill(false)->portrait;
  487. }
  488. }
  489. void CGSeerHut::init(CRandomGenerator & rand)
  490. {
  491. seerName = *RandomGeneratorUtil::nextItem(VLC->generaltexth->seerNames, rand);
  492. quest->textOption = rand.nextInt(2);
  493. quest->completedOption = rand.nextInt(1, 3);
  494. }
  495. void CGSeerHut::initObj(CRandomGenerator & rand)
  496. {
  497. init(rand);
  498. quest->progress = CQuest::NOT_ACTIVE;
  499. if(quest->missionType)
  500. {
  501. if(!quest->isCustomFirst)
  502. quest->firstVisitText = VLC->generaltexth->quests[quest->missionType-1][0][quest->textOption];
  503. if(!quest->isCustomNext)
  504. quest->nextVisitText = VLC->generaltexth->quests[quest->missionType-1][1][quest->textOption];
  505. if(!quest->isCustomComplete)
  506. quest->completedText = VLC->generaltexth->quests[quest->missionType-1][2][quest->textOption];
  507. }
  508. else
  509. {
  510. quest->progress = CQuest::COMPLETE;
  511. quest->firstVisitText = VLC->generaltexth->seerEmpty[quest->completedOption];
  512. }
  513. }
  514. void CGSeerHut::getRolloverText(MetaString &text, bool onHover) const
  515. {
  516. quest->getRolloverText (text, onHover);//TODO: simplify?
  517. if(!onHover)
  518. text.addReplacement(seerName);
  519. }
  520. std::string CGSeerHut::getHoverText(PlayerColor player) const
  521. {
  522. std::string hoverName = getObjectName();
  523. if(ID == Obj::SEER_HUT && quest->progress != CQuest::NOT_ACTIVE)
  524. {
  525. hoverName = VLC->generaltexth->allTexts[347];
  526. boost::algorithm::replace_first(hoverName, "%s", seerName);
  527. }
  528. if(quest->progress & quest->missionType) //rollover when the quest is active
  529. {
  530. MetaString ms;
  531. getRolloverText (ms, true);
  532. hoverName += ms.toString();
  533. }
  534. return hoverName;
  535. }
  536. void CQuest::addReplacements(MetaString &out, const std::string &base) const
  537. {
  538. switch(missionType)
  539. {
  540. case MISSION_KILL_CREATURE:
  541. out.addReplacement(stackToKill);
  542. if (std::count(base.begin(), base.end(), '%') == 2) //say where is placed monster
  543. {
  544. out.addReplacement(VLC->generaltexth->arraytxt[147+stackDirection]);
  545. }
  546. break;
  547. case MISSION_KILL_HERO:
  548. out.addReplacement(heroName);
  549. break;
  550. }
  551. }
  552. IQuestObject::IQuestObject():
  553. quest(new CQuest())
  554. {
  555. }
  556. IQuestObject::~IQuestObject()
  557. {
  558. ///Information about quest should remain accessible even if IQuestObject removed from map
  559. ///All CQuest objects are freed in CMap destructor
  560. //delete quest;
  561. }
  562. bool IQuestObject::checkQuest(const CGHeroInstance* h) const
  563. {
  564. return quest->checkQuest(h);
  565. }
  566. void IQuestObject::getVisitText (MetaString &text, std::vector<Component> &components, bool isCustom, bool FirstVisit, const CGHeroInstance * h) const
  567. {
  568. quest->getVisitText (text,components, isCustom, FirstVisit, h);
  569. }
  570. void IQuestObject::afterAddToMapCommon(CMap * map)
  571. {
  572. map->addNewQuestInstance(quest);
  573. }
  574. void CGSeerHut::getCompletionText(MetaString &text, std::vector<Component> &components, bool isCustom, const CGHeroInstance * h) const
  575. {
  576. quest->getCompletionText (text, components, isCustom, h);
  577. switch(rewardType)
  578. {
  579. case EXPERIENCE: components.push_back(Component (Component::EXPERIENCE, 0, (si32)h->calculateXp(rVal), 0));
  580. break;
  581. case MANA_POINTS: components.push_back(Component (Component::PRIM_SKILL, 5, rVal, 0));
  582. break;
  583. case MORALE_BONUS: components.push_back(Component (Component::MORALE, 0, rVal, 0));
  584. break;
  585. case LUCK_BONUS: components.push_back(Component (Component::LUCK, 0, rVal, 0));
  586. break;
  587. case RESOURCES: components.push_back(Component (Component::RESOURCE, rID, rVal, 0));
  588. break;
  589. case PRIMARY_SKILL: components.push_back(Component (Component::PRIM_SKILL, rID, rVal, 0));
  590. break;
  591. case SECONDARY_SKILL: components.push_back(Component (Component::SEC_SKILL, rID, rVal, 0));
  592. break;
  593. case ARTIFACT: components.push_back(Component (Component::ARTIFACT, rID, 0, 0));
  594. break;
  595. case SPELL: components.push_back(Component (Component::SPELL, rID, 0, 0));
  596. break;
  597. case CREATURE: components.push_back(Component (Component::CREATURE, rID, rVal, 0));
  598. break;
  599. }
  600. }
  601. void CGSeerHut::setPropertyDer (ui8 what, ui32 val)
  602. {
  603. switch(what)
  604. {
  605. case 10:
  606. quest->progress = static_cast<CQuest::Eprogress>(val);
  607. break;
  608. }
  609. }
  610. void CGSeerHut::newTurn(CRandomGenerator & rand) const
  611. {
  612. if(quest->lastDay >= 0 && quest->lastDay <= cb->getDate() - 1) //time is up
  613. {
  614. cb->setObjProperty (id, CGSeerHut::OBJPROP_VISITED, CQuest::COMPLETE);
  615. }
  616. }
  617. void CGSeerHut::onHeroVisit(const CGHeroInstance * h) const
  618. {
  619. InfoWindow iw;
  620. iw.player = h->getOwner();
  621. if(quest->progress < CQuest::COMPLETE)
  622. {
  623. bool firstVisit = !quest->progress;
  624. bool failRequirements = !checkQuest(h);
  625. bool isCustom = false;
  626. if(firstVisit)
  627. {
  628. isCustom = quest->isCustomFirst;
  629. cb->setObjProperty(id, CGSeerHut::OBJPROP_VISITED, CQuest::IN_PROGRESS);
  630. AddQuest aq;
  631. aq.quest = QuestInfo (quest, this, visitablePos());
  632. aq.player = h->tempOwner;
  633. cb->sendAndApply(&aq); //TODO: merge with setObjProperty?
  634. }
  635. else if(failRequirements)
  636. {
  637. isCustom = quest->isCustomNext;
  638. }
  639. if(firstVisit || failRequirements)
  640. {
  641. getVisitText (iw.text, iw.components, isCustom, firstVisit, h);
  642. cb->showInfoDialog(&iw);
  643. }
  644. if(!failRequirements) // propose completion, also on first visit
  645. {
  646. BlockingDialog bd (true, false);
  647. bd.player = h->getOwner();
  648. getCompletionText (bd.text, bd.components, isCustom, h);
  649. cb->showBlockingDialog (&bd);
  650. return;
  651. }
  652. }
  653. else
  654. {
  655. iw.text << VLC->generaltexth->seerEmpty[quest->completedOption];
  656. if (ID == Obj::SEER_HUT)
  657. iw.text.addReplacement(seerName);
  658. cb->showInfoDialog(&iw);
  659. }
  660. }
  661. int CGSeerHut::checkDirection() const
  662. {
  663. int3 cord = getCreatureToKill()->pos;
  664. if ((double)cord.x/(double)cb->getMapSize().x < 0.34) //north
  665. {
  666. if ((double)cord.y/(double)cb->getMapSize().y < 0.34) //northwest
  667. return 8;
  668. else if ((double)cord.y/(double)cb->getMapSize().y < 0.67) //north
  669. return 1;
  670. else //northeast
  671. return 2;
  672. }
  673. else if ((double)cord.x/(double)cb->getMapSize().x < 0.67) //horizontal
  674. {
  675. if ((double)cord.y/(double)cb->getMapSize().y < 0.34) //west
  676. return 7;
  677. else if ((double)cord.y/(double)cb->getMapSize().y < 0.67) //central
  678. return 9;
  679. else //east
  680. return 3;
  681. }
  682. else //south
  683. {
  684. if ((double)cord.y/(double)cb->getMapSize().y < 0.34) //southwest
  685. return 6;
  686. else if ((double)cord.y/(double)cb->getMapSize().y < 0.67) //south
  687. return 5;
  688. else //southeast
  689. return 4;
  690. }
  691. }
  692. void CGSeerHut::finishQuest(const CGHeroInstance * h, ui32 accept) const
  693. {
  694. if (accept)
  695. {
  696. switch (quest->missionType)
  697. {
  698. case CQuest::MISSION_ART:
  699. for (auto & elem : quest->m5arts)
  700. {
  701. if(!h->hasArt(elem))
  702. {
  703. // first we need to disassemble this backpack artifact
  704. auto assembly = h->getAssemblyByConstituent(elem);
  705. assert(assembly);
  706. for(auto & ci : assembly->constituentsInfo)
  707. {
  708. cb->giveHeroNewArtifact(h, ci.art->artType, ArtifactPosition::PRE_FIRST);
  709. }
  710. // remove the assembly
  711. cb->removeArtifact(ArtifactLocation(h, h->getArtPos(assembly)));
  712. }
  713. cb->removeArtifact(ArtifactLocation(h, h->getArtPos(elem, false)));
  714. }
  715. break;
  716. case CQuest::MISSION_ARMY:
  717. cb->takeCreatures(h->id, quest->m6creatures);
  718. break;
  719. case CQuest::MISSION_RESOURCES:
  720. for (int i = 0; i < 7; ++i)
  721. {
  722. cb->giveResource(h->getOwner(), static_cast<Res::ERes>(i), -(int)quest->m7resources[i]);
  723. }
  724. break;
  725. default:
  726. break;
  727. }
  728. cb->setObjProperty (id, CGSeerHut::OBJPROP_VISITED, CQuest::COMPLETE); //mission complete
  729. completeQuest(h); //make sure to remove QuestGuard at the very end
  730. }
  731. }
  732. void CGSeerHut::completeQuest (const CGHeroInstance * h) const //reward
  733. {
  734. switch (rewardType)
  735. {
  736. case EXPERIENCE:
  737. {
  738. TExpType expVal = h->calculateXp(rVal);
  739. cb->changePrimSkill(h, PrimarySkill::EXPERIENCE, expVal, false);
  740. break;
  741. }
  742. case MANA_POINTS:
  743. {
  744. cb->setManaPoints(h->id, h->mana+rVal);
  745. break;
  746. }
  747. case MORALE_BONUS: case LUCK_BONUS:
  748. {
  749. Bonus hb(Bonus::ONE_WEEK, (rewardType == 3 ? Bonus::MORALE : Bonus::LUCK),
  750. Bonus::OBJECT, rVal, h->id.getNum(), "", -1);
  751. GiveBonus gb;
  752. gb.id = h->id.getNum();
  753. gb.bonus = hb;
  754. cb->giveHeroBonus(&gb);
  755. }
  756. break;
  757. case RESOURCES:
  758. cb->giveResource(h->getOwner(), static_cast<Res::ERes>(rID), rVal);
  759. break;
  760. case PRIMARY_SKILL:
  761. cb->changePrimSkill(h, static_cast<PrimarySkill::PrimarySkill>(rID), rVal, false);
  762. break;
  763. case SECONDARY_SKILL:
  764. cb->changeSecSkill(h, SecondarySkill(rID), rVal, false);
  765. break;
  766. case ARTIFACT:
  767. cb->giveHeroNewArtifact(h, VLC->arth->objects[rID],ArtifactPosition::FIRST_AVAILABLE);
  768. break;
  769. case SPELL:
  770. {
  771. std::set<SpellID> spell;
  772. spell.insert (SpellID(rID));
  773. cb->changeSpells(h, true, spell);
  774. }
  775. break;
  776. case CREATURE:
  777. {
  778. CCreatureSet creatures;
  779. creatures.setCreature(SlotID(0), CreatureID(rID), rVal);
  780. cb->giveCreatures(this, h, creatures, false);
  781. }
  782. break;
  783. default:
  784. break;
  785. }
  786. }
  787. const CGHeroInstance * CGSeerHut::getHeroToKill(bool allowNull) const
  788. {
  789. const CGObjectInstance *o = cb->getObjByQuestIdentifier(quest->m13489val);
  790. if(allowNull && !o)
  791. return nullptr;
  792. assert(o && (o->ID == Obj::HERO || o->ID == Obj::PRISON));
  793. return static_cast<const CGHeroInstance*>(o);
  794. }
  795. const CGCreature * CGSeerHut::getCreatureToKill(bool allowNull) const
  796. {
  797. const CGObjectInstance *o = cb->getObjByQuestIdentifier(quest->m13489val);
  798. if(allowNull && !o)
  799. return nullptr;
  800. assert(o && o->ID == Obj::MONSTER);
  801. return static_cast<const CGCreature*>(o);
  802. }
  803. void CGSeerHut::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const
  804. {
  805. finishQuest(hero, answer);
  806. }
  807. void CGSeerHut::afterAddToMap(CMap* map)
  808. {
  809. IQuestObject::afterAddToMapCommon(map);
  810. }
  811. void CGSeerHut::serializeJsonOptions(JsonSerializeFormat & handler)
  812. {
  813. static const std::map<ERewardType, std::string> REWARD_MAP =
  814. {
  815. {NOTHING, ""},
  816. {EXPERIENCE, "experience"},
  817. {MANA_POINTS, "mana"},
  818. {MORALE_BONUS, "morale"},
  819. {LUCK_BONUS, "luck"},
  820. {RESOURCES, "resource"},
  821. {PRIMARY_SKILL, "primarySkill"},
  822. {SECONDARY_SKILL,"secondarySkill"},
  823. {ARTIFACT, "artifact"},
  824. {SPELL, "spell"},
  825. {CREATURE, "creature"}
  826. };
  827. static const std::map<std::string, ERewardType> REWARD_RMAP =
  828. {
  829. {"experience", EXPERIENCE},
  830. {"mana", MANA_POINTS},
  831. {"morale", MORALE_BONUS},
  832. {"luck", LUCK_BONUS},
  833. {"resource", RESOURCES},
  834. {"primarySkill", PRIMARY_SKILL},
  835. {"secondarySkill",SECONDARY_SKILL},
  836. {"artifact", ARTIFACT},
  837. {"spell", SPELL},
  838. {"creature", CREATURE}
  839. };
  840. //quest and reward
  841. quest->serializeJson(handler, "quest");
  842. //only one reward is supported
  843. //todo: full reward format support after CRewardInfo integration
  844. auto s = handler.enterStruct("reward");
  845. std::string fullIdentifier, metaTypeName, scope, identifier;
  846. if(handler.saving)
  847. {
  848. si32 amount = rVal;
  849. metaTypeName = REWARD_MAP.at(rewardType);
  850. switch (rewardType)
  851. {
  852. case NOTHING:
  853. break;
  854. case EXPERIENCE:
  855. case MANA_POINTS:
  856. case MORALE_BONUS:
  857. case LUCK_BONUS:
  858. identifier = "";
  859. break;
  860. case RESOURCES:
  861. identifier = GameConstants::RESOURCE_NAMES[rID];
  862. break;
  863. case PRIMARY_SKILL:
  864. identifier = PrimarySkill::names[rID];
  865. break;
  866. case SECONDARY_SKILL:
  867. identifier = CSkillHandler::encodeSkill(rID);
  868. break;
  869. case ARTIFACT:
  870. identifier = ArtifactID(rID).toArtifact(VLC->artifacts())->getJsonKey();
  871. amount = 1;
  872. break;
  873. case SPELL:
  874. identifier = SpellID(rID).toSpell(VLC->spells())->getJsonKey();
  875. amount = 1;
  876. break;
  877. case CREATURE:
  878. identifier = CreatureID(rID).toCreature(VLC->creatures())->getJsonKey();
  879. break;
  880. default:
  881. assert(false);
  882. break;
  883. }
  884. if(rewardType != NOTHING)
  885. {
  886. fullIdentifier = CModHandler::makeFullIdentifier(scope, metaTypeName, identifier);
  887. handler.serializeInt(fullIdentifier, amount);
  888. }
  889. }
  890. else
  891. {
  892. rewardType = NOTHING;
  893. const JsonNode & rewardsJson = handler.getCurrent();
  894. fullIdentifier = "";
  895. if(rewardsJson.Struct().empty())
  896. return;
  897. else
  898. {
  899. auto iter = rewardsJson.Struct().begin();
  900. fullIdentifier = iter->first;
  901. }
  902. CModHandler::parseIdentifier(fullIdentifier, scope, metaTypeName, identifier);
  903. auto it = REWARD_RMAP.find(metaTypeName);
  904. if(it == REWARD_RMAP.end())
  905. {
  906. logGlobal->error("%s: invalid metatype in reward item %s", instanceName, fullIdentifier);
  907. return;
  908. }
  909. else
  910. {
  911. rewardType = it->second;
  912. }
  913. bool doRequest = false;
  914. switch (rewardType)
  915. {
  916. case NOTHING:
  917. return;
  918. case EXPERIENCE:
  919. case MANA_POINTS:
  920. case MORALE_BONUS:
  921. case LUCK_BONUS:
  922. break;
  923. case PRIMARY_SKILL:
  924. doRequest = true;
  925. break;
  926. case RESOURCES:
  927. case SECONDARY_SKILL:
  928. case ARTIFACT:
  929. case SPELL:
  930. case CREATURE:
  931. doRequest = true;
  932. break;
  933. default:
  934. assert(false);
  935. break;
  936. }
  937. if(doRequest)
  938. {
  939. auto rawId = VLC->modh->identifiers.getIdentifier("core", fullIdentifier, false);
  940. if(rawId)
  941. {
  942. rID = rawId.get();
  943. }
  944. else
  945. {
  946. rewardType = NOTHING;//fallback in case of error
  947. return;
  948. }
  949. }
  950. handler.serializeInt(fullIdentifier, rVal);
  951. }
  952. }
  953. void CGQuestGuard::init(CRandomGenerator & rand)
  954. {
  955. blockVisit = true;
  956. quest->textOption = rand.nextInt(3, 5);
  957. quest->completedOption = rand.nextInt(4, 5);
  958. }
  959. void CGQuestGuard::completeQuest(const CGHeroInstance *h) const
  960. {
  961. cb->removeObject(this);
  962. }
  963. void CGQuestGuard::serializeJsonOptions(JsonSerializeFormat & handler)
  964. {
  965. //quest only, do not call base class
  966. quest->serializeJson(handler, "quest");
  967. }
  968. void CGKeys::reset()
  969. {
  970. playerKeyMap.clear();
  971. }
  972. void CGKeys::setPropertyDer (ui8 what, ui32 val) //101-108 - enable key for player 1-8
  973. {
  974. if (what >= 101 && what <= (100 + PlayerColor::PLAYER_LIMIT_I))
  975. {
  976. PlayerColor player(what-101);
  977. playerKeyMap[player].insert((ui8)val);
  978. }
  979. else
  980. logGlobal->error("Unexpected properties requested to set: what=%d, val=%d", (int)what, val);
  981. }
  982. bool CGKeys::wasMyColorVisited (PlayerColor player) const
  983. {
  984. if(playerKeyMap.count(player) && vstd::contains(playerKeyMap[player], subID))
  985. return true;
  986. else
  987. return false;
  988. }
  989. std::string CGKeys::getHoverText(PlayerColor player) const
  990. {
  991. return getObjectName() + "\n" + visitedTxt(wasMyColorVisited(player));
  992. }
  993. std::string CGKeys::getObjectName() const
  994. {
  995. return VLC->generaltexth->tentColors[subID] + " " + CGObjectInstance::getObjectName();
  996. }
  997. bool CGKeymasterTent::wasVisited (PlayerColor player) const
  998. {
  999. return wasMyColorVisited (player);
  1000. }
  1001. void CGKeymasterTent::onHeroVisit( const CGHeroInstance * h ) const
  1002. {
  1003. int txt_id;
  1004. if (!wasMyColorVisited (h->getOwner()) )
  1005. {
  1006. cb->setObjProperty(id, h->tempOwner.getNum()+101, subID);
  1007. txt_id=19;
  1008. }
  1009. else
  1010. txt_id=20;
  1011. showInfoDialog(h, txt_id);
  1012. }
  1013. void CGBorderGuard::initObj(CRandomGenerator & rand)
  1014. {
  1015. //ui32 m13489val = subID; //store color as quest info
  1016. blockVisit = true;
  1017. }
  1018. void CGBorderGuard::getVisitText (MetaString &text, std::vector<Component> &components, bool isCustom, bool FirstVisit, const CGHeroInstance * h) const
  1019. {
  1020. text << std::pair<ui8,ui32>(11,18);
  1021. }
  1022. void CGBorderGuard::getRolloverText (MetaString &text, bool onHover) const
  1023. {
  1024. if (!onHover)
  1025. text << VLC->generaltexth->tentColors[subID] << " " << VLC->objtypeh->getObjectName(Obj::KEYMASTER);
  1026. }
  1027. bool CGBorderGuard::checkQuest(const CGHeroInstance * h) const
  1028. {
  1029. return wasMyColorVisited (h->tempOwner);
  1030. }
  1031. void CGBorderGuard::onHeroVisit(const CGHeroInstance * h) const
  1032. {
  1033. if (wasMyColorVisited (h->getOwner()) )
  1034. {
  1035. BlockingDialog bd (true, false);
  1036. bd.player = h->getOwner();
  1037. bd.text.addTxt (MetaString::ADVOB_TXT, 17);
  1038. cb->showBlockingDialog (&bd);
  1039. }
  1040. else
  1041. {
  1042. showInfoDialog(h, 18);
  1043. AddQuest aq;
  1044. aq.quest = QuestInfo (quest, this, visitablePos());
  1045. aq.player = h->tempOwner;
  1046. cb->sendAndApply (&aq);
  1047. //TODO: add this quest only once OR check for multiple instances later
  1048. }
  1049. }
  1050. void CGBorderGuard::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const
  1051. {
  1052. if (answer)
  1053. cb->removeObject(this);
  1054. }
  1055. void CGBorderGuard::afterAddToMap(CMap * map)
  1056. {
  1057. IQuestObject::afterAddToMapCommon(map);
  1058. }
  1059. void CGBorderGate::onHeroVisit(const CGHeroInstance * h) const //TODO: passability
  1060. {
  1061. if (!wasMyColorVisited (h->getOwner()) )
  1062. {
  1063. showInfoDialog(h,18,0);
  1064. AddQuest aq;
  1065. aq.quest = QuestInfo (quest, this, visitablePos());
  1066. aq.player = h->tempOwner;
  1067. cb->sendAndApply (&aq);
  1068. }
  1069. }
  1070. bool CGBorderGate::passableFor(PlayerColor color) const
  1071. {
  1072. return wasMyColorVisited(color);
  1073. }