diff --git a/mapeditor/CMakeLists.txt b/mapeditor/CMakeLists.txt index a7b0c0407..ddda34ba0 100644 --- a/mapeditor/CMakeLists.txt +++ b/mapeditor/CMakeLists.txt @@ -15,6 +15,8 @@ set(editor_SRCS mapview.cpp radiopushbutton.cpp objectbrowser.cpp + inspector.cpp + mapsettings.cpp ) set(editor_HEADERS @@ -33,12 +35,15 @@ set(editor_HEADERS mapview.h radiopushbutton.h objectbrowser.h + inspector.h + mapsettings.h ) set(editor_FORMS mainwindow.ui windownewmap.ui generatorprogress.ui + mapsettings.ui ) assign_source_group(${editor_SRCS} ${editor_HEADERS} VCMI_launcher.rc) diff --git a/mapeditor/inspector.cpp b/mapeditor/inspector.cpp new file mode 100644 index 000000000..6344425c0 --- /dev/null +++ b/mapeditor/inspector.cpp @@ -0,0 +1,185 @@ +#include "StdInc.h" +#include "inspector.h" +#include "../lib/mapObjects/CObjectHandler.h" +#include "../lib/mapObjects/CObjectClassesHandler.h" +#include "../lib/mapObjects/CGTownInstance.h" +#include "../lib/spells/CSpellHandler.h" + +void Inspector::setProperty(const QString & key, const QVariant & value) +{ + if(!obj) + return; + + setProperty(dynamic_cast(obj), key, value); + + //updateProperties(); +} + +void Inspector::setProperty(CGTownInstance * object, const QString & key, const QVariant & value) +{ + if(!object) + return; + + if(key == "Owner") + { + PlayerColor owner(value.toString().toInt()); + if(value == "NEUTRAL") + owner = PlayerColor::NEUTRAL; + if(value == "UNFLAGGABLE") + owner = PlayerColor::UNFLAGGABLE; + object->tempOwner = owner; + return; + } +} + +CGTownInstance * initialize(CGTownInstance * o) +{ + if(!o) + return nullptr; + + o->tempOwner = PlayerColor::NEUTRAL; + o->builtBuildings.insert(BuildingID::FORT); + o->builtBuildings.insert(BuildingID::DEFAULT); + + for(auto spell : VLC->spellh->objects) //add all regular spells to town + { + if(!spell->isSpecial() && !spell->isCreatureAbility()) + o->possibleSpells.push_back(spell->id); + } +} + +Initializer::Initializer(CGObjectInstance * o) +{ + initialize(dynamic_cast(o)); +} + +Inspector::Inspector(CGObjectInstance * o, QTableWidget * t): obj(o), table(t) +{ + /* + /// Position of bottom-right corner of object on map + int3 pos; + /// Type of object, e.g. town, hero, creature. + Obj ID; + /// Subtype of object, depends on type + si32 subID; + /// Current owner of an object (when below PLAYER_LIMIT) + PlayerColor tempOwner; + /// Index of object in map's list of objects + ObjectInstanceID id; + /// Defines appearance of object on map (animation, blocked tiles, blit order, etc) + ObjectTemplate appearance; + /// If true hero can visit this object only from neighbouring tiles and can't stand on this object + bool blockVisit; + + std::string instanceName; + std::string typeName; + std::string subTypeName;*/ +} + +void Inspector::updateProperties() +{ + if(!obj) + return; + + addProperty("Indentifier", obj); + addProperty("ID", obj->ID.getNum()); + addProperty("SubID", obj->subID); + addProperty("InstanceName", obj->instanceName); + addProperty("TypeName", obj->typeName); + addProperty("SubTypeName", obj->subTypeName); + addProperty("Owner", obj->tempOwner, false); + + auto factory = VLC->objtypeh->getHandlerFor(obj->ID, obj->subID); + addProperty("IsStatic", factory->isStaticObject()); + + table->show(); +} + +QTableWidgetItem * Inspector::addProperty(CGObjectInstance * value) +{ + using NumericPointer = unsigned long long; + static_assert(sizeof(CGObjectInstance *) == sizeof(NumericPointer), + "Compilied for 64 bit arcitecture. Use NumericPointer = unsigned int"); + return new QTableWidgetItem(QString::number(reinterpret_cast(value))); +} + +QTableWidgetItem * Inspector::addProperty(int value) +{ + return new QTableWidgetItem(QString::number(value)); +} + +QTableWidgetItem * Inspector::addProperty(bool value) +{ + return new QTableWidgetItem(value ? "TRUE" : "FALSE"); +} + +QTableWidgetItem * Inspector::addProperty(const std::string & value) +{ + return addProperty(QString::fromStdString(value)); +} + +QTableWidgetItem * Inspector::addProperty(const QString & value) +{ + return new QTableWidgetItem(value); +} + +QTableWidgetItem * Inspector::addProperty(const int3 & value) +{ + return new QTableWidgetItem(QString("(%1, %2, %3)").arg(value.x, value.y, value.z)); +} + +QTableWidgetItem * Inspector::addProperty(const PlayerColor & value) +{ + //auto str = QString("PLAYER %1").arg(value.getNum()); + auto str = QString::number(value.getNum()); + if(value == PlayerColor::NEUTRAL) + str = "NEUTRAL"; + if(value == PlayerColor::UNFLAGGABLE) + str = "UNFLAGGABLE"; + return new QTableWidgetItem(str); +} + +QWidget * PlayerColorDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + if (index.data().canConvert()) + { + auto *editor = new QComboBox(parent); + connect(editor, SIGNAL(activated(int)), this, SLOT(commitAndCloseEditor(int))); + return editor; + } + return QStyledItemDelegate::createEditor(parent, option, index); +} + +void PlayerColorDelegate::commitAndCloseEditor(int id) +{ + QComboBox *editor = qobject_cast(sender()); + emit commitData(editor); + emit closeEditor(editor); +} + +void PlayerColorDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const +{ + if (index.data().canConvert()) + { + PlayerColor player(qvariant_cast(index.data())); + QComboBox *editor = qobject_cast(editor); + editor->addItem(QString::number(player.getNum())); + } + else + { + QStyledItemDelegate::setEditorData(editor, index); + } +} + +void PlayerColorDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const +{ + if (index.data().canConvert()) + { + QComboBox *editor = qobject_cast(editor); + model->setData(index, QVariant::fromValue(editor->currentText())); + } + else + { + QStyledItemDelegate::setModelData(editor, model, index); + } +} diff --git a/mapeditor/inspector.h b/mapeditor/inspector.h new file mode 100644 index 000000000..a18611d07 --- /dev/null +++ b/mapeditor/inspector.h @@ -0,0 +1,77 @@ +#ifndef INSPECTOR_H +#define INSPECTOR_H + +#include +#include +#include +#include "../lib/int3.h" +#include "../lib/GameConstants.h" + +class CGObjectInstance; +class CGTownInstance; + +class Initializer +{ +public: + Initializer(CGObjectInstance *); +}; + +class Inspector +{ +public: + Inspector(CGObjectInstance *, QTableWidget *); + + void setProperty(const QString & key, const QVariant & value); + + void updateProperties(); + +protected: + QTableWidgetItem * addProperty(int value); + QTableWidgetItem * addProperty(const std::string & value); + QTableWidgetItem * addProperty(const QString & value); + QTableWidgetItem * addProperty(const int3 & value); + QTableWidgetItem * addProperty(const PlayerColor & value); + QTableWidgetItem * addProperty(bool value); + QTableWidgetItem * addProperty(CGObjectInstance * value); + + void setProperty(CGTownInstance * obj, const QString & key, const QVariant & value); + + template + void addProperty(const QString & key, const T & value, bool restricted = true) + { + auto * itemKey = new QTableWidgetItem(key); + auto * itemValue = addProperty(value); + itemKey->setFlags(Qt::NoItemFlags); + if(restricted) + itemValue->setFlags(Qt::NoItemFlags); + + if(table->rowCount() < row + 1) + table->setRowCount(row + 1); + table->setItem(row, 0, itemKey); + table->setItem(row, 1, itemValue); + ++row; + } + +protected: + int row = 0; + QTableWidget * table; + CGObjectInstance * obj; +}; + +class PlayerColorDelegate : public QStyledItemDelegate +{ + Q_OBJECT +public: + using QStyledItemDelegate::QStyledItemDelegate; + + //void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override; + //QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override; + QWidget * createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override; + void setEditorData(QWidget *editor, const QModelIndex &index) const override; + void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override; + +private slots: + void commitAndCloseEditor(int); +}; + +#endif // INSPECTOR_H diff --git a/mapeditor/mainwindow.cpp b/mapeditor/mainwindow.cpp index 68fc87934..efb45b907 100644 --- a/mapeditor/mainwindow.cpp +++ b/mapeditor/mainwindow.cpp @@ -27,6 +27,7 @@ #include "graphics.h" #include "windownewmap.h" #include "objectbrowser.h" +#include "inspector.h" static CBasicLogConfigurator * logConfig; @@ -61,6 +62,10 @@ MainWindow::MainWindow(QWidget *parent) : ui->setupUi(this); ui->mapView->setMain(this); + + // Set current working dir to executable folder. + // This is important on Mac for relative paths to work inside DMG. + QDir::setCurrent(QApplication::applicationDirPath()); //configure logging const boost::filesystem::path logPath = VCMIDirs::get().userCachePath() / "VCMI_Editor_log.txt"; @@ -172,6 +177,9 @@ MapHandler * MainWindow::getMapHandler() void MainWindow::resetMapHandler() { mapHandler.reset(new MapHandler(map.get())); + + unsaved = true; + setWindowTitle(filename + "* - VCMI Map Editor"); } void MainWindow::setMapRaw(std::unique_ptr cmap) @@ -701,6 +709,7 @@ void MainWindow::preparePreview(const QModelIndex &index, bool createNew) auto factory = VLC->objtypeh->getHandlerFor(objId, objSubId); auto templ = factory->getTemplates()[templateId]; scenes[mapLevel]->selectionObjectsView.newObject = factory->create(templ); + scenes[mapLevel]->selectionObjectsView.selectionMode = 2; scenes[mapLevel]->selectionObjectsView.draw(); } } @@ -780,3 +789,36 @@ void MainWindow::on_actionFill_triggered() scenes[mapLevel]->updateViews(); } +void MainWindow::loadInspector(CGObjectInstance * obj) +{ + Inspector inspector(obj, ui->inspectorWidget); + inspector.updateProperties(); +} + +void MainWindow::on_inspectorWidget_itemChanged(QTableWidgetItem *item) +{ + if(!item->isSelected()) + return; + + int r = item->row(); + int c = item->column(); + if(c < 1) + return; + + auto * tableWidget = item->tableWidget(); + + //get identifier + auto identifier = tableWidget->item(0, 1)->text(); + static_assert(sizeof(CGObjectInstance *) == sizeof(decltype(identifier.toLongLong())), + "Compilied for 64 bit arcitecture. Use .toInt() method"); + + CGObjectInstance * obj = reinterpret_cast(identifier.toLongLong()); + + //get parameter name + auto param = tableWidget->item(r, c - 1)->text(); + + //set parameter + Inspector inspector(obj, tableWidget); + inspector.setProperty(param, item->text()); +} + diff --git a/mapeditor/mainwindow.h b/mapeditor/mainwindow.h index dc31dadef..4dcba78bb 100644 --- a/mapeditor/mainwindow.h +++ b/mapeditor/mainwindow.h @@ -11,6 +11,7 @@ class CMap; class ObjectBrowser; +class CGObjectInstance; namespace Ui { class MainWindow; @@ -37,6 +38,8 @@ public: void setStatusMessage(const QString & status); + void loadInspector(CGObjectInstance * obj); + MapView * getMapView(); int getMapLevel() const {return mapLevel;} @@ -76,6 +79,8 @@ private slots: void on_toolBrush4_clicked(bool checked); + void on_inspectorWidget_itemChanged(QTableWidgetItem *item); + public slots: void treeViewSelected(const QModelIndex &selected, const QModelIndex &deselected); diff --git a/mapeditor/mainwindow.ui b/mapeditor/mainwindow.ui index 0894cdbfe..fda479d21 100644 --- a/mapeditor/mainwindow.ui +++ b/mapeditor/mainwindow.ui @@ -213,7 +213,7 @@ - 0 + 1 @@ -223,7 +223,7 @@ - Objects + Browser @@ -293,6 +293,59 @@ + + + Inspector + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 10 + + + + QAbstractItemView::DoubleClicked|QAbstractItemView::EditKeyPressed|QAbstractItemView::SelectedClicked + + + QAbstractItemView::SingleSelection + + + 2 + + + false + + + 20 + + + + Property + + + + + Value + + + + + + diff --git a/mapeditor/mapsettings.cpp b/mapeditor/mapsettings.cpp new file mode 100644 index 000000000..e458e8930 --- /dev/null +++ b/mapeditor/mapsettings.cpp @@ -0,0 +1,14 @@ +#include "mapsettings.h" +#include "ui_mapsettings.h" + +MapSettings::MapSettings(QWidget *parent) : + QDialog(parent), + ui(new Ui::MapSettings) +{ + ui->setupUi(this); +} + +MapSettings::~MapSettings() +{ + delete ui; +} diff --git a/mapeditor/mapsettings.h b/mapeditor/mapsettings.h new file mode 100644 index 000000000..6ef31ae2b --- /dev/null +++ b/mapeditor/mapsettings.h @@ -0,0 +1,22 @@ +#ifndef MAPSETTINGS_H +#define MAPSETTINGS_H + +#include + +namespace Ui { +class MapSettings; +} + +class MapSettings : public QDialog +{ + Q_OBJECT + +public: + explicit MapSettings(QWidget *parent = nullptr); + ~MapSettings(); + +private: + Ui::MapSettings *ui; +}; + +#endif // MAPSETTINGS_H diff --git a/mapeditor/mapsettings.ui b/mapeditor/mapsettings.ui new file mode 100644 index 000000000..97fa1b14f --- /dev/null +++ b/mapeditor/mapsettings.ui @@ -0,0 +1,102 @@ + + + MapSettings + + + + 0 + 0 + 308 + 251 + + + + Map settings + + + + + + Map description + + + + + + + + + + Map name + + + + + + + Players amount + + + + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + + + 1 + + + + + 2 + + + + + 3 + + + + + 4 + + + + + 5 + + + + + 6 + + + + + 7 + + + + + 8 + + + + + + + + + diff --git a/mapeditor/mapview.cpp b/mapeditor/mapview.cpp index 5bd4588a3..839433276 100644 --- a/mapeditor/mapview.cpp +++ b/mapeditor/mapview.cpp @@ -1,6 +1,7 @@ #include "StdInc.h" #include "mapview.h" #include "mainwindow.h" +#include "inspector.h" #include #include "../lib/mapping/CMapEditManager.h" @@ -99,10 +100,22 @@ void MapView::mouseMoveEvent(QMouseEvent *mouseEvent) auto sh = tile - tileStart; sc->selectionObjectsView.shift = QPoint(sh.x, sh.y); - if((sh.x || sh.y) && sc->selectionObjectsView.newObject) + if(sh.x || sh.y) { - sc->selectionObjectsView.shift = QPoint(tile.x, tile.y); - sc->selectionObjectsView.selectObject(sc->selectionObjectsView.newObject); + if(sc->selectionObjectsView.newObject) + { + sc->selectionObjectsView.shift = QPoint(tile.x, tile.y); + sc->selectionObjectsView.selectObject(sc->selectionObjectsView.newObject); + sc->selectionObjectsView.selectionMode = 2; + } + else if(mouseEvent->buttons() & Qt::LeftButton) + { + if(sc->selectionObjectsView.selectionMode == 1) + { + sc->selectionObjectsView.clear(); + sc->selectionObjectsView.selectObjects(tileStart.x, tileStart.y, tile.x, tile.y); + } + } } sc->selectionObjectsView.draw(); @@ -196,9 +209,29 @@ void MapView::mousePressEvent(QMouseEvent *event) } else { - sc->selectionObjectsView.clear(); if(event->button() == Qt::LeftButton) - sc->selectionObjectsView.selectObjectAt(tileStart.x, tileStart.y); + { + auto * obj = sc->selectionObjectsView.selectObjectAt(tileStart.x, tileStart.y); + if(obj) + { + if(sc->selectionObjectsView.isSelected(obj)) + { + sc->selectionObjectsView.selectionMode = 2; + } + else + { + sc->selectionObjectsView.clear(); + sc->selectionObjectsView.selectionMode = 2; + sc->selectionObjectsView.selectObject(obj); + } + } + else + { + sc->selectionObjectsView.clear(); + sc->selectionObjectsView.selectionMode = 1; + } + } + sc->selectionObjectsView.shift = QPoint(0, 0); sc->selectionObjectsView.draw(); } break; @@ -230,16 +263,23 @@ void MapView::mouseReleaseEvent(QMouseEvent *event) if(event->button() == Qt::RightButton) break; //switch position - if(sc->selectionObjectsView.applyShift()) + if(sc->selectionObjectsView.selectionMode == 2 && sc->selectionObjectsView.applyShift()) { sc->selectionObjectsView.newObject = nullptr; + sc->selectionObjectsView.selectionMode = 0; + sc->selectionObjectsView.shift = QPoint(0, 0); main->resetMapHandler(); sc->updateViews(); } else { + sc->selectionObjectsView.selectionMode = 0; sc->selectionObjectsView.shift = QPoint(0, 0); sc->selectionObjectsView.draw(); + //check if we have only one object + auto selection = sc->selectionObjectsView.getSelection(); + if(selection.size() == 1) + main->loadInspector(*selection.begin()); } break; } @@ -664,7 +704,7 @@ void SelectionObjectsView::draw() } //show translation - if(shift.x() || shift.y()) + if(selectionMode == 2 && (shift.x() || shift.y())) { painter.setOpacity(0.5); auto newPos = QPoint(obj->getPosition().x, obj->getPosition().y) + shift; @@ -675,7 +715,7 @@ void SelectionObjectsView::draw() redraw(); } -CGObjectInstance * SelectionObjectsView::selectObjectAt(int x, int y) +CGObjectInstance * SelectionObjectsView::selectObjectAt(int x, int y) const { if(!main->getMap() || !main->getMapHandler() || !main->getMap()->isInTheMap(int3(x, y, scene->level))) return nullptr; @@ -685,12 +725,11 @@ CGObjectInstance * SelectionObjectsView::selectObjectAt(int x, int y) //visitable is most important for(auto & object : objects) { - if(!object.obj || selectedObjects.count(object.obj)) + if(!object.obj) continue; if(object.obj->visitableAt(x, y)) { - selectedObjects.insert(object.obj); return object.obj; } } @@ -698,12 +737,11 @@ CGObjectInstance * SelectionObjectsView::selectObjectAt(int x, int y) //if not visitable tile - try to get blocked for(auto & object : objects) { - if(!object.obj || selectedObjects.count(object.obj)) + if(!object.obj) continue; if(object.obj->blockingAt(x, y)) { - selectedObjects.insert(object.obj); return object.obj; } } @@ -711,12 +749,11 @@ CGObjectInstance * SelectionObjectsView::selectObjectAt(int x, int y) //finally, we can take any object for(auto & object : objects) { - if(!object.obj || selectedObjects.count(object.obj)) + if(!object.obj) continue; if(object.obj->coveringAt(x, y)) { - selectedObjects.insert(object.obj); return object.obj; } } @@ -738,6 +775,7 @@ bool SelectionObjectsView::applyShift() { newObject->pos = pos; main->getMap()->getEditManager()->insertObject(newObject); + Initializer init(newObject); } else { @@ -759,11 +797,25 @@ void SelectionObjectsView::deleteSelection() clear(); } -std::set SelectionObjectsView::selectObjects(int x1, int y1, int x2, int y2) +void SelectionObjectsView::selectObjects(int x1, int y1, int x2, int y2) { - std::set result; - //TBD - return result; + if(!main->getMap() || !main->getMapHandler()) + return; + + if(x1 > x2) + std::swap(x1, x2); + + if(y1 > y2) + std::swap(y1, y2); + + for(int j = y1; j < y2; ++j) + { + for(int i = x1; i < x2; ++i) + { + for(auto & o : main->getMapHandler()->getObjects(i, j, scene->level)) + selectObject(o.obj); + } + } } void SelectionObjectsView::selectObject(CGObjectInstance * obj) @@ -776,6 +828,11 @@ bool SelectionObjectsView::isSelected(const CGObjectInstance * obj) const return selectedObjects.count(const_cast(obj)); } +std::set SelectionObjectsView::getSelection() const +{ + return selectedObjects; +} + void SelectionObjectsView::clear() { selectedObjects.clear(); diff --git a/mapeditor/mapview.h b/mapeditor/mapview.h index 8fbeb11a6..09b6902df 100644 --- a/mapeditor/mapview.h +++ b/mapeditor/mapview.h @@ -134,10 +134,11 @@ public: void draw(); - CGObjectInstance * selectObjectAt(int x, int y); - std::set selectObjects(int x1, int y1, int x2, int y2); + CGObjectInstance * selectObjectAt(int x, int y) const; + void selectObjects(int x1, int y1, int x2, int y2); void selectObject(CGObjectInstance *); bool isSelected(const CGObjectInstance *) const; + std::set getSelection() const; void moveSelection(int x, int y); void clear(); @@ -146,6 +147,7 @@ public: QPoint shift; CGObjectInstance * newObject; + int selectionMode = 0; //0 - nothing, 1 - selection, 2 - movement private: std::set selectedObjects; diff --git a/mapeditor/windownewmap.cpp b/mapeditor/windownewmap.cpp index 0125861d0..253bd111b 100644 --- a/mapeditor/windownewmap.cpp +++ b/mapeditor/windownewmap.cpp @@ -68,6 +68,7 @@ void WindowNewMap::on_okButtong_clicked() mapGenOptions.setWaterContent(water); mapGenOptions.setMonsterStrength(monster); + CMapGenerator generator(mapGenOptions); //TODO: fix water and roads