/* * victoryconditions.cpp, part of VCMI engine * * Authors: listed in file AUTHORS in main folder * * License: GNU General Public License v2.0 or later * Full text of license available in license.txt file, in main folder * */ #include "StdInc.h" #include "victoryconditions.h" #include "ui_victoryconditions.h" #include "../mapcontroller.h" #include "../../lib/constants/StringConstants.h" #include "../../lib/entities/faction/CTownHandler.h" #include "../../lib/mapObjects/CGCreature.h" #include "../../lib/texts/CGeneralTextHandler.h" #include "../inspector/townbuildingswidget.h" //to convert BuildingID to string VictoryConditions::VictoryConditions(QWidget *parent) : AbstractSettings(parent), ui(new Ui::VictoryConditions) { ui->setupUi(this); } void VictoryConditions::initialize(MapController & c) { AbstractSettings::initialize(c); //victory message ui->victoryMessageEdit->setText(QString::fromStdString(controller->map()->victoryMessage.toString())); //victory conditions const std::array conditionStringsWin = { QT_TR_NOOP("No special victory"), QT_TR_NOOP("Capture artifact"), QT_TR_NOOP("Hire creatures"), QT_TR_NOOP("Accumulate resources"), QT_TR_NOOP("Construct building"), QT_TR_NOOP("Capture town"), QT_TR_NOOP("Defeat hero"), QT_TR_NOOP("Transport artifact"), QT_TR_NOOP("Kill monster") }; for(auto & s : conditionStringsWin) { ui->victoryComboBox->addItem(QString::fromStdString(s)); } ui->standardVictoryCheck->setChecked(false); ui->onlyForHumansCheck->setChecked(false); for(auto & ev : controller->map()->triggeredEvents) { if(ev.effect.type == EventEffect::VICTORY) { if(ev.identifier == "standardVictory") ui->standardVictoryCheck->setChecked(true); if(ev.identifier == "specialVictory") { auto readjson = ev.trigger.toJson(AbstractSettings::conditionToJson); auto linearNodes = linearJsonArray(readjson); for(auto & json : linearNodes) { switch(json["condition"].Integer()) { case EventCondition::HAVE_ARTIFACT: { ui->victoryComboBox->setCurrentIndex(1); assert(victoryTypeWidget); auto artifactId = ArtifactID::decode(json["objectType"].String()); auto idx = victoryTypeWidget->findData(artifactId); victoryTypeWidget->setCurrentIndex(idx); break; } case EventCondition::HAVE_CREATURES: { ui->victoryComboBox->setCurrentIndex(2); assert(victoryTypeWidget); assert(victoryValueWidget); auto creatureId = CreatureID::decode(json["objectType"].String()); auto idx = victoryTypeWidget->findData(creatureId); victoryTypeWidget->setCurrentIndex(idx); victoryValueWidget->setText(QString::number(json["value"].Integer())); break; } case EventCondition::HAVE_RESOURCES: { ui->victoryComboBox->setCurrentIndex(3); assert(victoryTypeWidget); assert(victoryValueWidget); auto resourceId = EGameResID::decode(json["objectType"].String()); auto idx = victoryTypeWidget->findData(resourceId); victoryTypeWidget->setCurrentIndex(idx); victoryValueWidget->setText(QString::number(json["value"].Integer())); break; } case EventCondition::HAVE_BUILDING: { ui->victoryComboBox->setCurrentIndex(4); assert(victoryTypeWidget); assert(victorySelectWidget); auto buildingId = BuildingID::decode(json["objectType"].String()); auto idx = victoryTypeWidget->findData(buildingId); victoryTypeWidget->setCurrentIndex(idx); int townIdx = getObjectByPos(*controller->map(), posFromJson(json["position"])); if(townIdx >= 0) { auto idx = victorySelectWidget->findData(townIdx); victorySelectWidget->setCurrentIndex(idx); } break; } case EventCondition::CONTROL: { ui->victoryComboBox->setCurrentIndex(5); assert(victoryTypeWidget); auto mapObject = MapObjectID::decode(json["objectType"].String()); if(mapObject == Obj::TOWN) { int townIdx = getObjectByPos(*controller->map(), posFromJson(json["position"])); if(townIdx >= 0) { auto idx = victoryTypeWidget->findData(townIdx); victoryTypeWidget->setCurrentIndex(idx); } } //TODO: support control other objects (dwellings, mines) break; } case EventCondition::DESTROY: { auto objectType = MapObjectID::decode(json["objectType"].String()); if(objectType == Obj::HERO) { ui->victoryComboBox->setCurrentIndex(6); assert(victoryTypeWidget); int heroIdx = getObjectByPos(*controller->map(), posFromJson(json["position"])); if(heroIdx >= 0) { auto idx = victoryTypeWidget->findData(heroIdx); victoryTypeWidget->setCurrentIndex(idx); } } if(objectType == Obj::MONSTER) { ui->victoryComboBox->setCurrentIndex(8); assert(victoryTypeWidget); int monsterIdx = getObjectByPos(*controller->map(), posFromJson(json["position"])); if(monsterIdx >= 0) { auto idx = victoryTypeWidget->findData(monsterIdx); victoryTypeWidget->setCurrentIndex(idx); } } break; } case EventCondition::TRANSPORT: { ui->victoryComboBox->setCurrentIndex(7); assert(victoryTypeWidget); assert(victorySelectWidget); auto artifactId = ArtifactID::decode(json["objectType"].String()); auto idx = victoryTypeWidget->findData(int(artifactId)); victoryTypeWidget->setCurrentIndex(idx); int townIdx = getObjectByPos(*controller->map(), posFromJson(json["position"])); if(townIdx >= 0) { auto idx = victorySelectWidget->findData(townIdx); victorySelectWidget->setCurrentIndex(idx); } break; } case EventCondition::IS_HUMAN: { ui->onlyForHumansCheck->setChecked(true); break; } }; } } } } } void VictoryConditions::update() { //victory messages bool customMessage = true; //victory conditions EventCondition victoryCondition(EventCondition::STANDARD_WIN); //Victory condition - defeat all TriggeredEvent standardVictory; standardVictory.effect.type = EventEffect::VICTORY; standardVictory.effect.toOtherMessage.appendTextID("core.genrltxt.5"); standardVictory.identifier = "standardVictory"; standardVictory.description.clear(); // TODO: display in quest window standardVictory.onFulfill.appendTextID("core.genrltxt.659"); standardVictory.trigger = EventExpression(victoryCondition); //VICTORY if(ui->victoryComboBox->currentIndex() == 0) { controller->map()->triggeredEvents.push_back(standardVictory); controller->map()->victoryIconIndex = 11; controller->map()->victoryMessage = MetaString::createFromTextID("core.vcdesc.0"); customMessage = false; } else { int vicCondition = ui->victoryComboBox->currentIndex() - 1; TriggeredEvent specialVictory; specialVictory.effect.type = EventEffect::VICTORY; specialVictory.identifier = "specialVictory"; specialVictory.description.clear(); // TODO: display in quest window controller->map()->victoryIconIndex = vicCondition; controller->map()->victoryMessage = MetaString::createFromTextID("core.vcdesc." + std::to_string(vicCondition + 1)); customMessage = false; switch(vicCondition) { case 0: { EventCondition cond(EventCondition::HAVE_ARTIFACT); assert(victoryTypeWidget); cond.objectType = ArtifactID(victoryTypeWidget->currentData().toInt()); specialVictory.effect.toOtherMessage.appendTextID("core.genrltxt.281"); specialVictory.onFulfill.appendTextID("core.genrltxt.280"); specialVictory.trigger = EventExpression(cond); break; } case 1: { EventCondition cond(EventCondition::HAVE_CREATURES); assert(victoryTypeWidget); cond.objectType = CreatureID(victoryTypeWidget->currentData().toInt()); cond.value = victoryValueWidget->text().toInt(); specialVictory.effect.toOtherMessage.appendTextID("core.genrltxt.277"); specialVictory.onFulfill.appendTextID("core.genrltxt.276"); specialVictory.trigger = EventExpression(cond); break; } case 2: { EventCondition cond(EventCondition::HAVE_RESOURCES); assert(victoryTypeWidget); cond.objectType = GameResID(victoryTypeWidget->currentData().toInt()); cond.value = victoryValueWidget->text().toInt(); specialVictory.effect.toOtherMessage.appendTextID("core.genrltxt.279"); specialVictory.onFulfill.appendTextID("core.genrltxt.278"); specialVictory.trigger = EventExpression(cond); break; } case 3: { EventCondition cond(EventCondition::HAVE_BUILDING); assert(victoryTypeWidget); cond.objectType = BuildingID(victoryTypeWidget->currentData().toInt()); int townIdx = victorySelectWidget->currentData().toInt(); if(townIdx > -1) cond.position = controller->map()->objects[townIdx]->pos; specialVictory.effect.toOtherMessage.appendTextID("core.genrltxt.283"); specialVictory.onFulfill.appendTextID("core.genrltxt.282"); specialVictory.trigger = EventExpression(cond); break; } case 4: { EventCondition cond(EventCondition::CONTROL); assert(victoryTypeWidget); cond.objectType = Obj(Obj::TOWN); int townIdx = victoryTypeWidget->currentData().toInt(); cond.position = controller->map()->objects[townIdx]->pos; specialVictory.effect.toOtherMessage.appendTextID("core.genrltxt.250"); specialVictory.onFulfill.appendTextID("core.genrltxt.249"); specialVictory.trigger = EventExpression(cond); break; } case 5: { EventCondition cond(EventCondition::DESTROY); assert(victoryTypeWidget); cond.objectType = Obj(Obj::HERO); int heroIdx = victoryTypeWidget->currentData().toInt(); cond.position = controller->map()->objects[heroIdx]->pos; specialVictory.effect.toOtherMessage.appendTextID("core.genrltxt.253"); specialVictory.onFulfill.appendTextID("core.genrltxt.252"); specialVictory.trigger = EventExpression(cond); break; } case 6: { EventCondition cond(EventCondition::TRANSPORT); assert(victoryTypeWidget); cond.objectType = ArtifactID(victoryTypeWidget->currentData().toInt()); int townIdx = victorySelectWidget->currentData().toInt(); if(townIdx > -1) cond.position = controller->map()->objects[townIdx]->pos; specialVictory.effect.toOtherMessage.appendTextID("core.genrltxt.293"); specialVictory.onFulfill.appendTextID("core.genrltxt.292"); specialVictory.trigger = EventExpression(cond); break; } case 7: { EventCondition cond(EventCondition::DESTROY); assert(victoryTypeWidget); cond.objectType = Obj(Obj::MONSTER); int monsterIdx = victoryTypeWidget->currentData().toInt(); cond.position = controller->map()->objects[monsterIdx]->pos; specialVictory.effect.toOtherMessage.appendTextID("core.genrltxt.287"); specialVictory.onFulfill.appendTextID("core.genrltxt.286"); specialVictory.trigger = EventExpression(cond); break; } } // if condition is human-only turn it into following construction: AllOf(human, condition) if(ui->onlyForHumansCheck->isChecked()) { EventExpression::OperatorAll oper; EventCondition notAI(EventCondition::IS_HUMAN); notAI.value = 1; oper.expressions.push_back(notAI); oper.expressions.push_back(specialVictory.trigger.get()); specialVictory.trigger = EventExpression(oper); } // if normal victory allowed - add one more quest if(ui->standardVictoryCheck->isChecked()) { controller->map()->victoryMessage.appendRawString(" / "); controller->map()->victoryMessage.appendTextID("core.vcdesc.0"); controller->map()->triggeredEvents.push_back(standardVictory); customMessage = false; } controller->map()->triggeredEvents.push_back(specialVictory); } if(customMessage) { controller->map()->victoryMessage = MetaString::createFromTextID(mapRegisterLocalizedString("map", *controller->map(), TextIdentifier("header", "victoryMessage"), ui->victoryMessageEdit->text().toStdString())); } } VictoryConditions::~VictoryConditions() { delete ui; } void VictoryConditions::on_victoryComboBox_currentIndexChanged(int index) { delete victoryTypeWidget; delete victoryValueWidget; delete victorySelectWidget; delete pickObjectButton; victoryTypeWidget = nullptr; victoryValueWidget = nullptr; victorySelectWidget = nullptr; pickObjectButton = nullptr; if(index == 0) { ui->standardVictoryCheck->setChecked(true); ui->standardVictoryCheck->setEnabled(false); ui->onlyForHumansCheck->setChecked(false); ui->onlyForHumansCheck->setEnabled(false); return; } ui->onlyForHumansCheck->setEnabled(true); ui->standardVictoryCheck->setEnabled(true); int vicCondition = index - 1; switch(vicCondition) { case 0: { //EventCondition::HAVE_ARTIFACT victoryTypeWidget = new QComboBox; ui->victoryParamsLayout->addWidget(victoryTypeWidget); for(int i = 0; i < controller->map()->allowedArtifact.size(); ++i) victoryTypeWidget->addItem(QString::fromStdString(VLC->arth->objects[i]->getNameTranslated()), QVariant::fromValue(i)); break; } case 1: { //EventCondition::HAVE_CREATURES victoryTypeWidget = new QComboBox; ui->victoryParamsLayout->addWidget(victoryTypeWidget); for(int i = 0; i < VLC->creh->objects.size(); ++i) victoryTypeWidget->addItem(QString::fromStdString(VLC->creh->objects[i]->getNamePluralTranslated()), QVariant::fromValue(i)); victoryValueWidget = new QLineEdit; ui->victoryParamsLayout->addWidget(victoryValueWidget); victoryValueWidget->setText("1"); break; } case 2: { //EventCondition::HAVE_RESOURCES victoryTypeWidget = new QComboBox; ui->victoryParamsLayout->addWidget(victoryTypeWidget); { for(int resType = 0; resType < GameConstants::RESOURCE_QUANTITY; ++resType) { auto resName = QString::fromStdString(GameConstants::RESOURCE_NAMES[resType]); victoryTypeWidget->addItem(resName, QVariant::fromValue(resType)); } } victoryValueWidget = new QLineEdit; ui->victoryParamsLayout->addWidget(victoryValueWidget); victoryValueWidget->setText("1"); break; } case 3: { //EventCondition::HAVE_BUILDING victoryTypeWidget = new QComboBox; ui->victoryParamsLayout->addWidget(victoryTypeWidget); auto * ctown = VLC->townh->randomTown; for(int bId : ctown->getAllBuildings()) victoryTypeWidget->addItem(QString::fromStdString(defaultBuildingIdConversion(BuildingID(bId))), QVariant::fromValue(bId)); victorySelectWidget = new QComboBox; ui->victoryParamsLayout->addWidget(victorySelectWidget); victorySelectWidget->addItem("Any town", QVariant::fromValue(-1)); for(int i : getObjectIndexes(*controller->map())) victorySelectWidget->addItem(getTownName(*controller->map(), i).c_str(), QVariant::fromValue(i)); pickObjectButton = new QToolButton; connect(pickObjectButton, &QToolButton::clicked, this, &VictoryConditions::onObjectSelect); ui->victoryParamsLayout->addWidget(pickObjectButton); break; } case 4: { //EventCondition::CONTROL (Obj::TOWN) victoryTypeWidget = new QComboBox; ui->victoryParamsLayout->addWidget(victoryTypeWidget); for(int i : getObjectIndexes(*controller->map())) victoryTypeWidget->addItem(tr(getTownName(*controller->map(), i).c_str()), QVariant::fromValue(i)); pickObjectButton = new QToolButton; connect(pickObjectButton, &QToolButton::clicked, this, &VictoryConditions::onObjectSelect); ui->victoryParamsLayout->addWidget(pickObjectButton); break; } case 5: { //EventCondition::DESTROY (Obj::HERO) victoryTypeWidget = new QComboBox; ui->victoryParamsLayout->addWidget(victoryTypeWidget); for(int i : getObjectIndexes(*controller->map())) victoryTypeWidget->addItem(tr(getHeroName(*controller->map(), i).c_str()), QVariant::fromValue(i)); pickObjectButton = new QToolButton; connect(pickObjectButton, &QToolButton::clicked, this, &VictoryConditions::onObjectSelect); ui->victoryParamsLayout->addWidget(pickObjectButton); break; } case 6: { //EventCondition::TRANSPORT (Obj::ARTEFACT) victoryTypeWidget = new QComboBox; ui->victoryParamsLayout->addWidget(victoryTypeWidget); for(int i = 0; i < controller->map()->allowedArtifact.size(); ++i) victoryTypeWidget->addItem(QString::fromStdString(VLC->arth->objects[i]->getNameTranslated()), QVariant::fromValue(i)); victorySelectWidget = new QComboBox; ui->victoryParamsLayout->addWidget(victorySelectWidget); for(int i : getObjectIndexes(*controller->map())) victorySelectWidget->addItem(tr(getTownName(*controller->map(), i).c_str()), QVariant::fromValue(i)); pickObjectButton = new QToolButton; connect(pickObjectButton, &QToolButton::clicked, this, &VictoryConditions::onObjectSelect); ui->victoryParamsLayout->addWidget(pickObjectButton); break; } case 7: { //EventCondition::DESTROY (Obj::MONSTER) victoryTypeWidget = new QComboBox; ui->victoryParamsLayout->addWidget(victoryTypeWidget); for(int i : getObjectIndexes(*controller->map())) victoryTypeWidget->addItem(getMonsterName(*controller->map(), i).c_str(), QVariant::fromValue(i)); pickObjectButton = new QToolButton; connect(pickObjectButton, &QToolButton::clicked, this, &VictoryConditions::onObjectSelect); ui->victoryParamsLayout->addWidget(pickObjectButton); break; } } } void VictoryConditions::onObjectSelect() { int vicConditions = ui->victoryComboBox->currentIndex() - 1; for(int lvl : {0, 1}) { auto & l = controller->scene(lvl)->objectPickerView; switch(vicConditions) { case 3: { //EventCondition::HAVE_BUILDING l.highlight(); break; } case 4: { //EventCondition::CONTROL (Obj::TOWN) l.highlight(); break; } case 5: { //EventCondition::DESTROY (Obj::HERO) l.highlight(); break; } case 6: { //EventCondition::TRANSPORT (Obj::ARTEFACT) l.highlight(); break; } case 7: { //EventCondition::DESTROY (Obj::MONSTER) l.highlight(); break; } default: return; } l.update(); QObject::connect(&l, &ObjectPickerLayer::selectionMade, this, &VictoryConditions::onObjectPicked); } dynamic_cast(parent()->parent()->parent()->parent()->parent()->parent()->parent())->hide(); } void VictoryConditions::onObjectPicked(const CGObjectInstance * obj) { dynamic_cast(parent()->parent()->parent()->parent()->parent()->parent()->parent())->show(); for(int lvl : {0, 1}) { auto & l = controller->scene(lvl)->objectPickerView; l.clear(); l.update(); QObject::disconnect(&l, &ObjectPickerLayer::selectionMade, this, &VictoryConditions::onObjectPicked); } if(!obj) //discarded return; int vicConditions = ui->victoryComboBox->currentIndex() - 1; QComboBox * w = victoryTypeWidget; if(vicConditions == 3 || vicConditions == 6) w = victorySelectWidget; for(int i = 0; i < w->count(); ++i) { if(w->itemData(i).toInt() < 0) continue; auto data = controller->map()->objects.at(w->itemData(i).toInt()); if(data == obj) { w->setCurrentIndex(i); break; } } }