mapsettings.cpp 32 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018
  1. /*
  2. * mapsettings.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 "mapsettings.h"
  11. #include "ui_mapsettings.h"
  12. #include "mainwindow.h"
  13. #include "../lib/CSkillHandler.h"
  14. #include "../lib/spells/CSpellHandler.h"
  15. #include "../lib/CArtHandler.h"
  16. #include "../lib/CHeroHandler.h"
  17. #include "../lib/CGeneralTextHandler.h"
  18. #include "../lib/CModHandler.h"
  19. #include "../lib/mapObjects/CGHeroInstance.h"
  20. #include "../lib/mapObjects/CGCreature.h"
  21. #include "../lib/mapping/CMapService.h"
  22. #include "../lib/StringConstants.h"
  23. #include "inspector/townbulidingswidget.h" //to convert BuildingID to string
  24. //parses date for lose condition (1m 1w 1d)
  25. int expiredDate(const QString & date)
  26. {
  27. int result = 0;
  28. for(auto component : date.split(" "))
  29. {
  30. int days = component.left(component.lastIndexOf('d')).toInt();
  31. int weeks = component.left(component.lastIndexOf('w')).toInt();
  32. int months = component.left(component.lastIndexOf('m')).toInt();
  33. result += days > 0 ? days - 1 : 0;
  34. result += (weeks > 0 ? weeks - 1 : 0) * 7;
  35. result += (months > 0 ? months - 1 : 0) * 28;
  36. }
  37. return result;
  38. }
  39. QString expiredDate(int date)
  40. {
  41. QString result;
  42. int m = date / 28;
  43. int w = (date % 28) / 7;
  44. int d = date % 7;
  45. if(m)
  46. result += QString::number(m) + "m";
  47. if(w)
  48. {
  49. if(!result.isEmpty())
  50. result += " ";
  51. result += QString::number(w) + "w";
  52. }
  53. if(d)
  54. {
  55. if(!result.isEmpty())
  56. result += " ";
  57. result += QString::number(d) + "d";
  58. }
  59. return result;
  60. }
  61. int3 posFromJson(const JsonNode & json)
  62. {
  63. return int3(json.Vector()[0].Integer(), json.Vector()[1].Integer(), json.Vector()[2].Integer());
  64. }
  65. std::vector<JsonNode> linearJsonArray(const JsonNode & json)
  66. {
  67. std::vector<JsonNode> result;
  68. if(json.getType() == JsonNode::JsonType::DATA_STRUCT)
  69. result.push_back(json);
  70. if(json.getType() == JsonNode::JsonType::DATA_VECTOR)
  71. {
  72. for(auto & node : json.Vector())
  73. {
  74. auto subvector = linearJsonArray(node);
  75. result.insert(result.end(), subvector.begin(), subvector.end());
  76. }
  77. }
  78. return result;
  79. }
  80. void traverseNode(QTreeWidgetItem * item, std::function<void(QTreeWidgetItem*)> action)
  81. {
  82. // Do something with item
  83. action(item);
  84. for (int i = 0; i < item->childCount(); ++i)
  85. traverseNode(item->child(i), action);
  86. }
  87. MapSettings::MapSettings(MapController & ctrl, QWidget *parent) :
  88. QDialog(parent),
  89. ui(new Ui::MapSettings),
  90. controller(ctrl)
  91. {
  92. ui->setupUi(this);
  93. assert(controller.map());
  94. ui->mapNameEdit->setText(tr(controller.map()->name.c_str()));
  95. ui->mapDescriptionEdit->setPlainText(tr(controller.map()->description.c_str()));
  96. ui->heroLevelLimit->setValue(controller.map()->levelLimit);
  97. ui->heroLevelLimitCheck->setChecked(controller.map()->levelLimit);
  98. show();
  99. for(int i = 0; i < controller.map()->allowedAbilities.size(); ++i)
  100. {
  101. auto * item = new QListWidgetItem(QString::fromStdString(VLC->skillh->objects[i]->getNameTranslated()));
  102. item->setData(Qt::UserRole, QVariant::fromValue(i));
  103. item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
  104. item->setCheckState(controller.map()->allowedAbilities[i] ? Qt::Checked : Qt::Unchecked);
  105. ui->listAbilities->addItem(item);
  106. }
  107. for(int i = 0; i < controller.map()->allowedSpell.size(); ++i)
  108. {
  109. auto * item = new QListWidgetItem(QString::fromStdString(VLC->spellh->objects[i]->getNameTranslated()));
  110. item->setData(Qt::UserRole, QVariant::fromValue(i));
  111. item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
  112. item->setCheckState(controller.map()->allowedSpell[i] ? Qt::Checked : Qt::Unchecked);
  113. ui->listSpells->addItem(item);
  114. }
  115. for(int i = 0; i < controller.map()->allowedArtifact.size(); ++i)
  116. {
  117. auto * item = new QListWidgetItem(QString::fromStdString(VLC->arth->objects[i]->getNameTranslated()));
  118. item->setData(Qt::UserRole, QVariant::fromValue(i));
  119. item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
  120. item->setCheckState(controller.map()->allowedArtifact[i] ? Qt::Checked : Qt::Unchecked);
  121. ui->listArts->addItem(item);
  122. }
  123. for(int i = 0; i < controller.map()->allowedHeroes.size(); ++i)
  124. {
  125. auto * item = new QListWidgetItem(QString::fromStdString(VLC->heroh->objects[i]->getNameTranslated()));
  126. item->setData(Qt::UserRole, QVariant::fromValue(i));
  127. item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
  128. item->setCheckState(controller.map()->allowedHeroes[i] ? Qt::Checked : Qt::Unchecked);
  129. ui->listHeroes->addItem(item);
  130. }
  131. //set difficulty
  132. switch(controller.map()->difficulty)
  133. {
  134. case 0:
  135. ui->diffRadio1->setChecked(true);
  136. break;
  137. case 1:
  138. ui->diffRadio2->setChecked(true);
  139. break;
  140. case 2:
  141. ui->diffRadio3->setChecked(true);
  142. break;
  143. case 3:
  144. ui->diffRadio4->setChecked(true);
  145. break;
  146. case 4:
  147. ui->diffRadio5->setChecked(true);
  148. break;
  149. };
  150. //victory & loss messages
  151. ui->victoryMessageEdit->setText(QString::fromStdString(controller.map()->victoryMessage.toString()));
  152. ui->defeatMessageEdit->setText(QString::fromStdString(controller.map()->defeatMessage.toString()));
  153. //victory & loss conditions
  154. const std::array<std::string, 8> conditionStringsWin = {
  155. QT_TR_NOOP("No special victory"),
  156. QT_TR_NOOP("Capture artifact"),
  157. QT_TR_NOOP("Hire creatures"),
  158. QT_TR_NOOP("Accumulate resources"),
  159. QT_TR_NOOP("Construct building"),
  160. QT_TR_NOOP("Capture town"),
  161. QT_TR_NOOP("Defeat hero"),
  162. QT_TR_NOOP("Transport artifact")
  163. };
  164. const std::array<std::string, 5> conditionStringsLose = {
  165. QT_TR_NOOP("No special loss"),
  166. QT_TR_NOOP("Lose castle"),
  167. QT_TR_NOOP("Lose hero"),
  168. QT_TR_NOOP("Time expired"),
  169. QT_TR_NOOP("Days without town")
  170. };
  171. for(auto & s : conditionStringsWin)
  172. {
  173. ui->victoryComboBox->addItem(QString::fromStdString(s));
  174. }
  175. ui->standardVictoryCheck->setChecked(false);
  176. ui->onlyForHumansCheck->setChecked(false);
  177. for(auto & s : conditionStringsLose)
  178. {
  179. ui->loseComboBox->addItem(QString::fromStdString(s));
  180. }
  181. ui->standardLoseCheck->setChecked(false);
  182. auto conditionToJson = [](const EventCondition & event) -> JsonNode
  183. {
  184. JsonNode result;
  185. result["condition"].Integer() = event.condition;
  186. result["value"].Integer() = event.value;
  187. result["objectType"].Integer() = event.objectType;
  188. result["objectSubytype"].Integer() = event.objectSubtype;
  189. result["objectInstanceName"].String() = event.objectInstanceName;
  190. result["metaType"].Integer() = (ui8)event.metaType;
  191. {
  192. auto & position = result["position"].Vector();
  193. position.resize(3);
  194. position[0].Float() = event.position.x;
  195. position[1].Float() = event.position.y;
  196. position[2].Float() = event.position.z;
  197. }
  198. return result;
  199. };
  200. for(auto & ev : controller.map()->triggeredEvents)
  201. {
  202. if(ev.effect.type == EventEffect::VICTORY)
  203. {
  204. if(ev.identifier == "standardVictory")
  205. ui->standardVictoryCheck->setChecked(true);
  206. if(ev.identifier == "specialVictory")
  207. {
  208. auto readjson = ev.trigger.toJson(conditionToJson);
  209. auto linearNodes = linearJsonArray(readjson);
  210. for(auto & json : linearNodes)
  211. {
  212. switch(json["condition"].Integer())
  213. {
  214. case EventCondition::HAVE_ARTIFACT: {
  215. ui->victoryComboBox->setCurrentIndex(1);
  216. assert(victoryTypeWidget);
  217. victoryTypeWidget->setCurrentIndex(json["objectType"].Integer());
  218. break;
  219. }
  220. case EventCondition::HAVE_CREATURES: {
  221. ui->victoryComboBox->setCurrentIndex(2);
  222. assert(victoryTypeWidget);
  223. assert(victoryValueWidget);
  224. auto idx = victoryTypeWidget->findData(int(json["objectType"].Integer()));
  225. victoryTypeWidget->setCurrentIndex(idx);
  226. victoryValueWidget->setText(QString::number(json["value"].Integer()));
  227. break;
  228. }
  229. case EventCondition::HAVE_RESOURCES: {
  230. ui->victoryComboBox->setCurrentIndex(3);
  231. assert(victoryTypeWidget);
  232. assert(victoryValueWidget);
  233. auto idx = victoryTypeWidget->findData(int(json["objectType"].Integer()));
  234. victoryTypeWidget->setCurrentIndex(idx);
  235. victoryValueWidget->setText(QString::number(json["value"].Integer()));
  236. break;
  237. }
  238. case EventCondition::HAVE_BUILDING: {
  239. ui->victoryComboBox->setCurrentIndex(4);
  240. assert(victoryTypeWidget);
  241. assert(victorySelectWidget);
  242. auto idx = victoryTypeWidget->findData(int(json["objectType"].Integer()));
  243. victoryTypeWidget->setCurrentIndex(idx);
  244. int townIdx = getObjectByPos<CGTownInstance>(posFromJson(json["position"]));
  245. if(townIdx >= 0)
  246. {
  247. auto idx = victorySelectWidget->findData(townIdx);
  248. victorySelectWidget->setCurrentIndex(idx);
  249. }
  250. break;
  251. }
  252. case EventCondition::CONTROL: {
  253. ui->victoryComboBox->setCurrentIndex(5);
  254. assert(victoryTypeWidget);
  255. if(json["objectType"].Integer() == Obj::TOWN)
  256. {
  257. int townIdx = getObjectByPos<CGTownInstance>(posFromJson(json["position"]));
  258. if(townIdx >= 0)
  259. {
  260. auto idx = victoryTypeWidget->findData(townIdx);
  261. victoryTypeWidget->setCurrentIndex(idx);
  262. }
  263. }
  264. //TODO: support control other objects (dwellings, mines)
  265. break;
  266. }
  267. case EventCondition::DESTROY: {
  268. ui->victoryComboBox->setCurrentIndex(6);
  269. assert(victoryTypeWidget);
  270. if(json["objectType"].Integer() == Obj::HERO)
  271. {
  272. int heroIdx = getObjectByPos<CGHeroInstance>(posFromJson(json["position"]));
  273. if(heroIdx >= 0)
  274. {
  275. auto idx = victoryTypeWidget->findData(heroIdx);
  276. victoryTypeWidget->setCurrentIndex(idx);
  277. }
  278. }
  279. //TODO: support control other objects (monsters)
  280. break;
  281. }
  282. case EventCondition::TRANSPORT: {
  283. ui->victoryComboBox->setCurrentIndex(7);
  284. assert(victoryTypeWidget);
  285. assert(victorySelectWidget);
  286. victoryTypeWidget->setCurrentIndex(json["objectType"].Integer());
  287. int townIdx = getObjectByPos<CGTownInstance>(posFromJson(json["position"]));
  288. if(townIdx >= 0)
  289. {
  290. auto idx = victorySelectWidget->findData(townIdx);
  291. victorySelectWidget->setCurrentIndex(idx);
  292. }
  293. break;
  294. }
  295. case EventCondition::IS_HUMAN: {
  296. ui->onlyForHumansCheck->setChecked(true);
  297. break;
  298. }
  299. };
  300. }
  301. }
  302. }
  303. if(ev.effect.type == EventEffect::DEFEAT)
  304. {
  305. if(ev.identifier == "standardDefeat")
  306. ui->standardLoseCheck->setChecked(true);
  307. if(ev.identifier == "specialDefeat")
  308. {
  309. auto readjson = ev.trigger.toJson(conditionToJson);
  310. auto linearNodes = linearJsonArray(readjson);
  311. for(auto & json : linearNodes)
  312. {
  313. switch(json["condition"].Integer())
  314. {
  315. case EventCondition::CONTROL: {
  316. if(json["objectType"].Integer() == Obj::TOWN)
  317. {
  318. ui->loseComboBox->setCurrentIndex(1);
  319. assert(loseTypeWidget);
  320. int townIdx = getObjectByPos<CGTownInstance>(posFromJson(json["position"]));
  321. if(townIdx >= 0)
  322. {
  323. auto idx = loseTypeWidget->findData(townIdx);
  324. loseTypeWidget->setCurrentIndex(idx);
  325. }
  326. }
  327. if(json["objectType"].Integer() == Obj::HERO)
  328. {
  329. ui->loseComboBox->setCurrentIndex(2);
  330. assert(loseTypeWidget);
  331. int heroIdx = getObjectByPos<CGHeroInstance>(posFromJson(json["position"]));
  332. if(heroIdx >= 0)
  333. {
  334. auto idx = loseTypeWidget->findData(heroIdx);
  335. loseTypeWidget->setCurrentIndex(idx);
  336. }
  337. }
  338. break;
  339. }
  340. case EventCondition::DAYS_PASSED: {
  341. ui->loseComboBox->setCurrentIndex(3);
  342. assert(loseValueWidget);
  343. loseValueWidget->setText(expiredDate(json["value"].Integer()));
  344. break;
  345. }
  346. case EventCondition::DAYS_WITHOUT_TOWN: {
  347. ui->loseComboBox->setCurrentIndex(4);
  348. assert(loseValueWidget);
  349. loseValueWidget->setText(QString::number(json["value"].Integer()));
  350. break;
  351. case EventCondition::IS_HUMAN:
  352. break; //ignore because always applicable for defeat conditions
  353. }
  354. };
  355. }
  356. }
  357. }
  358. }
  359. //mods management
  360. //collect all active mods
  361. QMap<QString, QTreeWidgetItem*> addedMods;
  362. QSet<QString> modsToProcess;
  363. ui->treeMods->blockSignals(true);
  364. auto createModTreeWidgetItem = [&](QTreeWidgetItem * parent, const CModInfo & modInfo)
  365. {
  366. auto item = new QTreeWidgetItem(parent, {QString::fromStdString(modInfo.name), QString::fromStdString(modInfo.version.toString())});
  367. item->setData(0, Qt::UserRole, QVariant(QString::fromStdString(modInfo.identifier)));
  368. item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
  369. item->setCheckState(0, controller.map()->mods.count(modInfo.identifier) ? Qt::Checked : Qt::Unchecked);
  370. //set parent check
  371. if(parent && item->checkState(0) == Qt::Checked)
  372. parent->setCheckState(0, Qt::Checked);
  373. return item;
  374. };
  375. for(const auto & modName : VLC->modh->getActiveMods())
  376. {
  377. QString qmodName = QString::fromStdString(modName);
  378. if(qmodName.split(".").size() == 1)
  379. {
  380. const auto & modInfo = VLC->modh->getModInfo(modName);
  381. addedMods[qmodName] = createModTreeWidgetItem(nullptr, modInfo);
  382. ui->treeMods->addTopLevelItem(addedMods[qmodName]);
  383. }
  384. else
  385. {
  386. modsToProcess.insert(qmodName);
  387. }
  388. }
  389. for(auto qmodIter = modsToProcess.begin(); qmodIter != modsToProcess.end();)
  390. {
  391. auto qmodName = *qmodIter;
  392. auto pieces = qmodName.split(".");
  393. assert(pieces.size() > 1);
  394. QString qs;
  395. for(int i = 0; i < pieces.size() - 1; ++i)
  396. qs += pieces[i];
  397. if(addedMods.count(qs))
  398. {
  399. const auto & modInfo = VLC->modh->getModInfo(qmodName.toStdString());
  400. addedMods[qmodName] = createModTreeWidgetItem(addedMods[qs], modInfo);
  401. modsToProcess.erase(qmodIter);
  402. qmodIter = modsToProcess.begin();
  403. }
  404. else
  405. ++qmodIter;
  406. }
  407. ui->treeMods->blockSignals(false);
  408. }
  409. MapSettings::~MapSettings()
  410. {
  411. delete ui;
  412. }
  413. std::string MapSettings::getTownName(int townObjectIdx)
  414. {
  415. std::string name;
  416. if(auto town = dynamic_cast<CGTownInstance*>(controller.map()->objects[townObjectIdx].get()))
  417. {
  418. auto * ctown = town->town;
  419. if(!ctown)
  420. ctown = VLC->townh->randomTown;
  421. name = ctown->faction ? town->getObjectName() : town->getNameTranslated() + ", (random)";
  422. }
  423. return name;
  424. }
  425. std::string MapSettings::getHeroName(int townObjectIdx)
  426. {
  427. std::string name;
  428. if(auto hero = dynamic_cast<CGHeroInstance*>(controller.map()->objects[townObjectIdx].get()))
  429. {
  430. name = hero->getNameTranslated();
  431. }
  432. return name;
  433. }
  434. std::string MapSettings::getMonsterName(int monsterObjectIdx)
  435. {
  436. std::string name;
  437. [[maybe_unused]] auto monster = dynamic_cast<CGCreature*>(controller.map()->objects[monsterObjectIdx].get());
  438. if(monster)
  439. {
  440. //TODO: get proper name
  441. //name = hero->name;
  442. }
  443. return name;
  444. }
  445. void MapSettings::updateModWidgetBasedOnMods(const ModCompatibilityInfo & mods)
  446. {
  447. //Mod management
  448. auto widgetAction = [&](QTreeWidgetItem * item)
  449. {
  450. auto modName = item->data(0, Qt::UserRole).toString().toStdString();
  451. item->setCheckState(0, mods.count(modName) ? Qt::Checked : Qt::Unchecked);
  452. };
  453. for (int i = 0; i < ui->treeMods->topLevelItemCount(); ++i)
  454. {
  455. QTreeWidgetItem *item = ui->treeMods->topLevelItem(i);
  456. traverseNode(item, widgetAction);
  457. }
  458. }
  459. void MapSettings::on_pushButton_clicked()
  460. {
  461. controller.map()->name = ui->mapNameEdit->text().toStdString();
  462. controller.map()->description = ui->mapDescriptionEdit->toPlainText().toStdString();
  463. if(ui->heroLevelLimitCheck->isChecked())
  464. controller.map()->levelLimit = ui->heroLevelLimit->value();
  465. else
  466. controller.map()->levelLimit = 0;
  467. controller.commitChangeWithoutRedraw();
  468. for(int i = 0; i < controller.map()->allowedAbilities.size(); ++i)
  469. {
  470. auto * item = ui->listAbilities->item(i);
  471. controller.map()->allowedAbilities[i] = item->checkState() == Qt::Checked;
  472. }
  473. for(int i = 0; i < controller.map()->allowedSpell.size(); ++i)
  474. {
  475. auto * item = ui->listSpells->item(i);
  476. controller.map()->allowedSpell[i] = item->checkState() == Qt::Checked;
  477. }
  478. for(int i = 0; i < controller.map()->allowedArtifact.size(); ++i)
  479. {
  480. auto * item = ui->listArts->item(i);
  481. controller.map()->allowedArtifact[i] = item->checkState() == Qt::Checked;
  482. }
  483. for(int i = 0; i < controller.map()->allowedHeroes.size(); ++i)
  484. {
  485. auto * item = ui->listHeroes->item(i);
  486. controller.map()->allowedHeroes[i] = item->checkState() == Qt::Checked;
  487. }
  488. //set difficulty
  489. if(ui->diffRadio1->isChecked()) controller.map()->difficulty = 0;
  490. if(ui->diffRadio2->isChecked()) controller.map()->difficulty = 1;
  491. if(ui->diffRadio3->isChecked()) controller.map()->difficulty = 2;
  492. if(ui->diffRadio4->isChecked()) controller.map()->difficulty = 3;
  493. if(ui->diffRadio5->isChecked()) controller.map()->difficulty = 4;
  494. //victory & loss messages
  495. controller.map()->victoryMessage = MetaString::createFromRawString(ui->victoryMessageEdit->text().toStdString());
  496. controller.map()->defeatMessage = MetaString::createFromRawString(ui->defeatMessageEdit->text().toStdString());
  497. //victory & loss conditions
  498. EventCondition victoryCondition(EventCondition::STANDARD_WIN);
  499. EventCondition defeatCondition(EventCondition::DAYS_WITHOUT_TOWN);
  500. defeatCondition.value = 7;
  501. //Victory condition - defeat all
  502. TriggeredEvent standardVictory;
  503. standardVictory.effect.type = EventEffect::VICTORY;
  504. standardVictory.effect.toOtherMessage.appendTextID("core.genrltxt.5");
  505. standardVictory.identifier = "standardVictory";
  506. standardVictory.description.clear(); // TODO: display in quest window
  507. standardVictory.onFulfill.appendTextID("core.genrltxt.659");
  508. standardVictory.trigger = EventExpression(victoryCondition);
  509. //Loss condition - 7 days without town
  510. TriggeredEvent standardDefeat;
  511. standardDefeat.effect.type = EventEffect::DEFEAT;
  512. standardDefeat.effect.toOtherMessage.appendTextID("core.genrltxt.8");
  513. standardDefeat.identifier = "standardDefeat";
  514. standardDefeat.description.clear(); // TODO: display in quest window
  515. standardDefeat.onFulfill.appendTextID("core.genrltxt.7");
  516. standardDefeat.trigger = EventExpression(defeatCondition);
  517. controller.map()->triggeredEvents.clear();
  518. //VICTORY
  519. if(ui->victoryComboBox->currentIndex() == 0)
  520. {
  521. controller.map()->triggeredEvents.push_back(standardVictory);
  522. controller.map()->victoryIconIndex = 11;
  523. controller.map()->victoryMessage.appendTextID(VLC->generaltexth->victoryConditions[0]);
  524. }
  525. else
  526. {
  527. int vicCondition = ui->victoryComboBox->currentIndex() - 1;
  528. TriggeredEvent specialVictory;
  529. specialVictory.effect.type = EventEffect::VICTORY;
  530. specialVictory.identifier = "specialVictory";
  531. specialVictory.description.clear(); // TODO: display in quest window
  532. controller.map()->victoryIconIndex = vicCondition;
  533. controller.map()->victoryMessage.appendTextID(VLC->generaltexth->victoryConditions[size_t(vicCondition) + 1]);
  534. switch(vicCondition)
  535. {
  536. case 0: {
  537. EventCondition cond(EventCondition::HAVE_ARTIFACT);
  538. assert(victoryTypeWidget);
  539. cond.objectType = victoryTypeWidget->currentData().toInt();
  540. specialVictory.effect.toOtherMessage.appendTextID("core.genrltxt.281");
  541. specialVictory.onFulfill.appendTextID("core.genrltxt.280");
  542. specialVictory.trigger = EventExpression(cond);
  543. break;
  544. }
  545. case 1: {
  546. EventCondition cond(EventCondition::HAVE_CREATURES);
  547. assert(victoryTypeWidget);
  548. cond.objectType = victoryTypeWidget->currentData().toInt();
  549. cond.value = victoryValueWidget->text().toInt();
  550. specialVictory.effect.toOtherMessage.appendTextID("core.genrltxt.277");
  551. specialVictory.onFulfill.appendTextID("core.genrltxt.276");
  552. specialVictory.trigger = EventExpression(cond);
  553. break;
  554. }
  555. case 2: {
  556. EventCondition cond(EventCondition::HAVE_RESOURCES);
  557. assert(victoryTypeWidget);
  558. cond.objectType = victoryTypeWidget->currentData().toInt();
  559. cond.value = victoryValueWidget->text().toInt();
  560. specialVictory.effect.toOtherMessage.appendTextID("core.genrltxt.279");
  561. specialVictory.onFulfill.appendTextID("core.genrltxt.278");
  562. specialVictory.trigger = EventExpression(cond);
  563. break;
  564. }
  565. case 3: {
  566. EventCondition cond(EventCondition::HAVE_BUILDING);
  567. assert(victoryTypeWidget);
  568. cond.objectType = victoryTypeWidget->currentData().toInt();
  569. int townIdx = victorySelectWidget->currentData().toInt();
  570. if(townIdx > -1)
  571. cond.position = controller.map()->objects[townIdx]->pos;
  572. specialVictory.effect.toOtherMessage.appendTextID("core.genrltxt.283");
  573. specialVictory.onFulfill.appendTextID("core.genrltxt.282");
  574. specialVictory.trigger = EventExpression(cond);
  575. break;
  576. }
  577. case 4: {
  578. EventCondition cond(EventCondition::CONTROL);
  579. assert(victoryTypeWidget);
  580. cond.objectType = Obj::TOWN;
  581. int townIdx = victoryTypeWidget->currentData().toInt();
  582. cond.position = controller.map()->objects[townIdx]->pos;
  583. specialVictory.effect.toOtherMessage.appendTextID("core.genrltxt.250");
  584. specialVictory.onFulfill.appendTextID("core.genrltxt.249");
  585. specialVictory.trigger = EventExpression(cond);
  586. break;
  587. }
  588. case 5: {
  589. EventCondition cond(EventCondition::DESTROY);
  590. assert(victoryTypeWidget);
  591. cond.objectType = Obj::HERO;
  592. int heroIdx = victoryTypeWidget->currentData().toInt();
  593. cond.position = controller.map()->objects[heroIdx]->pos;
  594. specialVictory.effect.toOtherMessage.appendTextID("core.genrltxt.253");
  595. specialVictory.onFulfill.appendTextID("core.genrltxt.252");
  596. specialVictory.trigger = EventExpression(cond);
  597. break;
  598. }
  599. case 6: {
  600. EventCondition cond(EventCondition::TRANSPORT);
  601. assert(victoryTypeWidget);
  602. cond.objectType = victoryTypeWidget->currentData().toInt();
  603. int townIdx = victorySelectWidget->currentData().toInt();
  604. if(townIdx > -1)
  605. cond.position = controller.map()->objects[townIdx]->pos;
  606. specialVictory.effect.toOtherMessage.appendTextID("core.genrltxt.293");
  607. specialVictory.onFulfill.appendTextID("core.genrltxt.292");
  608. specialVictory.trigger = EventExpression(cond);
  609. break;
  610. }
  611. }
  612. // if condition is human-only turn it into following construction: AllOf(human, condition)
  613. if(ui->onlyForHumansCheck->isChecked())
  614. {
  615. EventExpression::OperatorAll oper;
  616. EventCondition notAI(EventCondition::IS_HUMAN);
  617. notAI.value = 1;
  618. oper.expressions.push_back(notAI);
  619. oper.expressions.push_back(specialVictory.trigger.get());
  620. specialVictory.trigger = EventExpression(oper);
  621. }
  622. // if normal victory allowed - add one more quest
  623. if(ui->standardVictoryCheck->isChecked())
  624. {
  625. controller.map()->victoryMessage.appendRawString(" / ");
  626. controller.map()->victoryMessage.appendTextID(VLC->generaltexth->victoryConditions[0]);
  627. controller.map()->triggeredEvents.push_back(standardVictory);
  628. }
  629. controller.map()->triggeredEvents.push_back(specialVictory);
  630. }
  631. //DEFEAT
  632. if(ui->loseComboBox->currentIndex() == 0)
  633. {
  634. controller.map()->triggeredEvents.push_back(standardDefeat);
  635. controller.map()->defeatIconIndex = 3;
  636. controller.map()->defeatMessage.appendTextID("core.lcdesc.0");
  637. }
  638. else
  639. {
  640. int lossCondition = ui->loseComboBox->currentIndex() - 1;
  641. TriggeredEvent specialDefeat;
  642. specialDefeat.effect.type = EventEffect::DEFEAT;
  643. specialDefeat.identifier = "specialDefeat";
  644. specialDefeat.description.clear(); // TODO: display in quest window
  645. controller.map()->defeatIconIndex = lossCondition;
  646. switch(lossCondition)
  647. {
  648. case 0: {
  649. EventExpression::OperatorNone noneOf;
  650. EventCondition cond(EventCondition::CONTROL);
  651. cond.objectType = Obj::TOWN;
  652. assert(loseTypeWidget);
  653. int townIdx = loseTypeWidget->currentData().toInt();
  654. cond.position = controller.map()->objects[townIdx]->pos;
  655. noneOf.expressions.push_back(cond);
  656. specialDefeat.onFulfill.appendTextID("core.genrltxt.251");
  657. specialDefeat.trigger = EventExpression(noneOf);
  658. controller.map()->defeatMessage.appendTextID("core.lcdesc.1");
  659. break;
  660. }
  661. case 1: {
  662. EventExpression::OperatorNone noneOf;
  663. EventCondition cond(EventCondition::CONTROL);
  664. cond.objectType = Obj::HERO;
  665. assert(loseTypeWidget);
  666. int townIdx = loseTypeWidget->currentData().toInt();
  667. cond.position = controller.map()->objects[townIdx]->pos;
  668. noneOf.expressions.push_back(cond);
  669. specialDefeat.onFulfill.appendTextID("core.genrltxt.253");
  670. specialDefeat.trigger = EventExpression(noneOf);
  671. controller.map()->defeatMessage.appendTextID("core.lcdesc.2");
  672. break;
  673. }
  674. case 2: {
  675. EventCondition cond(EventCondition::DAYS_PASSED);
  676. assert(loseValueWidget);
  677. cond.value = expiredDate(loseValueWidget->text());
  678. specialDefeat.onFulfill.appendTextID("core.genrltxt.254");
  679. specialDefeat.trigger = EventExpression(cond);
  680. controller.map()->defeatMessage.appendTextID("core.lcdesc.3");
  681. break;
  682. }
  683. case 3: {
  684. EventCondition cond(EventCondition::DAYS_WITHOUT_TOWN);
  685. assert(loseValueWidget);
  686. cond.value = loseValueWidget->text().toInt();
  687. specialDefeat.onFulfill.appendTextID("core.genrltxt.7");
  688. specialDefeat.trigger = EventExpression(cond);
  689. break;
  690. }
  691. }
  692. EventExpression::OperatorAll allOf;
  693. EventCondition isHuman(EventCondition::IS_HUMAN);
  694. isHuman.value = 1;
  695. allOf.expressions.push_back(specialDefeat.trigger.get());
  696. allOf.expressions.push_back(isHuman);
  697. specialDefeat.trigger = EventExpression(allOf);
  698. if(ui->standardLoseCheck->isChecked())
  699. {
  700. controller.map()->triggeredEvents.push_back(standardDefeat);
  701. }
  702. controller.map()->triggeredEvents.push_back(specialDefeat);
  703. }
  704. //Mod management
  705. auto widgetAction = [&](QTreeWidgetItem * item)
  706. {
  707. if(item->checkState(0) == Qt::Checked)
  708. {
  709. auto modName = item->data(0, Qt::UserRole).toString().toStdString();
  710. controller.map()->mods[modName] = VLC->modh->getModInfo(modName).version;
  711. }
  712. };
  713. controller.map()->mods.clear();
  714. for (int i = 0; i < ui->treeMods->topLevelItemCount(); ++i)
  715. {
  716. QTreeWidgetItem *item = ui->treeMods->topLevelItem(i);
  717. traverseNode(item, widgetAction);
  718. }
  719. close();
  720. }
  721. void MapSettings::on_victoryComboBox_currentIndexChanged(int index)
  722. {
  723. delete victoryTypeWidget;
  724. delete victoryValueWidget;
  725. delete victorySelectWidget;
  726. victoryTypeWidget = nullptr;
  727. victoryValueWidget = nullptr;
  728. victorySelectWidget = nullptr;
  729. if(index == 0)
  730. {
  731. ui->standardVictoryCheck->setChecked(true);
  732. ui->standardVictoryCheck->setEnabled(false);
  733. ui->onlyForHumansCheck->setChecked(false);
  734. ui->onlyForHumansCheck->setEnabled(false);
  735. return;
  736. }
  737. ui->onlyForHumansCheck->setEnabled(true);
  738. ui->standardVictoryCheck->setEnabled(true);
  739. int vicCondition = index - 1;
  740. switch(vicCondition)
  741. {
  742. case 0: { //EventCondition::HAVE_ARTIFACT
  743. victoryTypeWidget = new QComboBox;
  744. ui->victoryParamsLayout->addWidget(victoryTypeWidget);
  745. for(int i = 0; i < controller.map()->allowedArtifact.size(); ++i)
  746. victoryTypeWidget->addItem(QString::fromStdString(VLC->arth->objects[i]->getNameTranslated()), QVariant::fromValue(i));
  747. break;
  748. }
  749. case 1: { //EventCondition::HAVE_CREATURES
  750. victoryTypeWidget = new QComboBox;
  751. ui->victoryParamsLayout->addWidget(victoryTypeWidget);
  752. for(int i = 0; i < VLC->creh->objects.size(); ++i)
  753. victoryTypeWidget->addItem(QString::fromStdString(VLC->creh->objects[i]->getNamePluralTranslated()), QVariant::fromValue(i));
  754. victoryValueWidget = new QLineEdit;
  755. ui->victoryParamsLayout->addWidget(victoryValueWidget);
  756. victoryValueWidget->setText("1");
  757. break;
  758. }
  759. case 2: { //EventCondition::HAVE_RESOURCES
  760. victoryTypeWidget = new QComboBox;
  761. ui->victoryParamsLayout->addWidget(victoryTypeWidget);
  762. {
  763. for(int resType = 0; resType < GameConstants::RESOURCE_QUANTITY; ++resType)
  764. {
  765. auto resName = QString::fromStdString(GameConstants::RESOURCE_NAMES[resType]);
  766. victoryTypeWidget->addItem(resName, QVariant::fromValue(resType));
  767. }
  768. }
  769. victoryValueWidget = new QLineEdit;
  770. ui->victoryParamsLayout->addWidget(victoryValueWidget);
  771. victoryValueWidget->setText("1");
  772. break;
  773. }
  774. case 3: { //EventCondition::HAVE_BUILDING
  775. victoryTypeWidget = new QComboBox;
  776. ui->victoryParamsLayout->addWidget(victoryTypeWidget);
  777. auto * ctown = VLC->townh->randomTown;
  778. for(int bId : ctown->getAllBuildings())
  779. victoryTypeWidget->addItem(QString::fromStdString(defaultBuildingIdConversion(BuildingID(bId))), QVariant::fromValue(bId));
  780. victorySelectWidget = new QComboBox;
  781. ui->victoryParamsLayout->addWidget(victorySelectWidget);
  782. victorySelectWidget->addItem("Any town", QVariant::fromValue(-1));
  783. for(int i : getObjectIndexes<CGTownInstance>())
  784. victorySelectWidget->addItem(getTownName(i).c_str(), QVariant::fromValue(i));
  785. break;
  786. }
  787. case 4: { //EventCondition::CONTROL (Obj::TOWN)
  788. victoryTypeWidget = new QComboBox;
  789. ui->victoryParamsLayout->addWidget(victoryTypeWidget);
  790. for(int i : getObjectIndexes<CGTownInstance>())
  791. victoryTypeWidget->addItem(tr(getTownName(i).c_str()), QVariant::fromValue(i));
  792. break;
  793. }
  794. case 5: { //EventCondition::DESTROY (Obj::HERO)
  795. victoryTypeWidget = new QComboBox;
  796. ui->victoryParamsLayout->addWidget(victoryTypeWidget);
  797. for(int i : getObjectIndexes<CGHeroInstance>())
  798. victoryTypeWidget->addItem(tr(getHeroName(i).c_str()), QVariant::fromValue(i));
  799. break;
  800. }
  801. case 6: { //EventCondition::TRANSPORT (Obj::ARTEFACT)
  802. victoryTypeWidget = new QComboBox;
  803. ui->victoryParamsLayout->addWidget(victoryTypeWidget);
  804. for(int i = 0; i < controller.map()->allowedArtifact.size(); ++i)
  805. victoryTypeWidget->addItem(QString::fromStdString(VLC->arth->objects[i]->getNameTranslated()), QVariant::fromValue(i));
  806. victorySelectWidget = new QComboBox;
  807. ui->victoryParamsLayout->addWidget(victorySelectWidget);
  808. for(int i : getObjectIndexes<CGTownInstance>())
  809. victorySelectWidget->addItem(tr(getTownName(i).c_str()), QVariant::fromValue(i));
  810. break;
  811. }
  812. //TODO: support this vectory type
  813. // in order to do that, need to implement finding creature by position
  814. // selecting from map would be the best user experience
  815. /*case 7: { //EventCondition::DESTROY (Obj::MONSTER)
  816. victoryTypeWidget = new QComboBox;
  817. ui->loseParamsLayout->addWidget(victoryTypeWidget);
  818. for(int i : getObjectIndexes<CGCreature>())
  819. victoryTypeWidget->addItem(tr(getMonsterName(i).c_str()), QVariant::fromValue(i));
  820. break;
  821. }*/
  822. }
  823. }
  824. void MapSettings::on_loseComboBox_currentIndexChanged(int index)
  825. {
  826. delete loseTypeWidget;
  827. delete loseValueWidget;
  828. delete loseSelectWidget;
  829. loseTypeWidget = nullptr;
  830. loseValueWidget = nullptr;
  831. loseSelectWidget = nullptr;
  832. if(index == 0)
  833. {
  834. ui->standardLoseCheck->setChecked(true);
  835. ui->standardLoseCheck->setEnabled(false);
  836. return;
  837. }
  838. ui->standardLoseCheck->setEnabled(true);
  839. int loseCondition = index - 1;
  840. switch(loseCondition)
  841. {
  842. case 0: { //EventCondition::CONTROL (Obj::TOWN)
  843. loseTypeWidget = new QComboBox;
  844. ui->loseParamsLayout->addWidget(loseTypeWidget);
  845. for(int i : getObjectIndexes<CGTownInstance>())
  846. loseTypeWidget->addItem(tr(getTownName(i).c_str()), QVariant::fromValue(i));
  847. break;
  848. }
  849. case 1: { //EventCondition::CONTROL (Obj::HERO)
  850. loseTypeWidget = new QComboBox;
  851. ui->loseParamsLayout->addWidget(loseTypeWidget);
  852. for(int i : getObjectIndexes<CGHeroInstance>())
  853. loseTypeWidget->addItem(tr(getHeroName(i).c_str()), QVariant::fromValue(i));
  854. break;
  855. }
  856. case 2: { //EventCondition::DAYS_PASSED
  857. loseValueWidget = new QLineEdit;
  858. ui->loseParamsLayout->addWidget(loseValueWidget);
  859. loseValueWidget->setText("2m 1w 1d");
  860. break;
  861. }
  862. case 3: { //EventCondition::DAYS_WITHOUT_TOWN
  863. loseValueWidget = new QLineEdit;
  864. ui->loseParamsLayout->addWidget(loseValueWidget);
  865. loseValueWidget->setText("7");
  866. break;
  867. }
  868. }
  869. }
  870. void MapSettings::on_heroLevelLimitCheck_toggled(bool checked)
  871. {
  872. ui->heroLevelLimit->setEnabled(checked);
  873. }
  874. void MapSettings::on_modResolution_map_clicked()
  875. {
  876. updateModWidgetBasedOnMods(MapController::modAssessmentMap(*controller.map()));
  877. }
  878. void MapSettings::on_modResolution_full_clicked()
  879. {
  880. updateModWidgetBasedOnMods(MapController::modAssessmentAll());
  881. }
  882. void MapSettings::on_treeMods_itemChanged(QTreeWidgetItem *item, int column)
  883. {
  884. //set state for children
  885. for (int i = 0; i < item->childCount(); ++i)
  886. item->child(i)->setCheckState(0, item->checkState(0));
  887. //set state for parent
  888. ui->treeMods->blockSignals(true);
  889. if(item->checkState(0) == Qt::Checked)
  890. {
  891. while(item->parent())
  892. {
  893. item->parent()->setCheckState(0, Qt::Checked);
  894. item = item->parent();
  895. }
  896. }
  897. ui->treeMods->blockSignals(false);
  898. }