diff --git a/include/core/wildmoninfo.h b/include/core/wildmoninfo.h index f9011c9c..51eed213 100644 --- a/include/core/wildmoninfo.h +++ b/include/core/wildmoninfo.h @@ -19,10 +19,15 @@ struct WildPokemonHeader { QMap wildMons; }; -typedef QVector>> Fields; -typedef QPair> Field; +struct EncounterField { + QString name; + QVector encounterRates; + QMap> groups; +}; -WildMonInfo getDefaultMonInfo(Field field); -WildMonInfo copyMonInfoFromTab(QTableWidget *table); +typedef QVector EncounterFields; + +WildMonInfo getDefaultMonInfo(EncounterField field); +WildMonInfo copyMonInfoFromTab(QTableWidget *table, EncounterField monField); #endif // GUARD_WILDMONINFO_H diff --git a/include/editor.h b/include/editor.h index 7e1ec0ee..bfb0ee95 100644 --- a/include/editor.h +++ b/include/editor.h @@ -146,7 +146,7 @@ private: void updateMirroredConnectionDirection(MapConnection*, QString); void updateMirroredConnectionMap(MapConnection*, QString); void updateMirroredConnection(MapConnection*, QString, QString, bool isDelete = false); - void updateEncounterFields(Fields newFields); + void updateEncounterFields(EncounterFields newFields); Event* createNewObjectEvent(); Event* createNewWarpEvent(); Event* createNewHealLocationEvent(); diff --git a/include/project.h b/include/project.h index 8609b899..a0195b2f 100644 --- a/include/project.h +++ b/include/project.h @@ -91,7 +91,7 @@ public: void readWildMonData(); QMap> wildMonData; - QVector>> wildMonFields; + QVector wildMonFields; QVector encounterGroupLabels; QMap extraEncounterGroups; diff --git a/src/core/wildmoninfo.cpp b/src/core/wildmoninfo.cpp index 1769dc93..d411071a 100644 --- a/src/core/wildmoninfo.cpp +++ b/src/core/wildmoninfo.cpp @@ -4,26 +4,28 @@ -WildMonInfo getDefaultMonInfo(Field field) { +WildMonInfo getDefaultMonInfo(EncounterField field) { WildMonInfo newInfo; newInfo.active = true; newInfo.encounterRate = 0; - for (int row : field.second) + int size = field.encounterRates.size(); + while (size--) newInfo.wildPokemon.append(WildPokemon()); return newInfo; } -WildMonInfo copyMonInfoFromTab(QTableWidget *monTable) { +WildMonInfo copyMonInfoFromTab(QTableWidget *monTable, EncounterField monField) { WildMonInfo newInfo; QVector newWildMons; + bool extraColumn = !monField.groups.isEmpty(); for (int row = 0; row < monTable->rowCount(); row++) { WildPokemon newWildMon; - newWildMon.species = monTable->cellWidget(row, 1)->findChild()->currentText(); - newWildMon.minLevel = monTable->cellWidget(row, 2)->findChild()->value(); - newWildMon.maxLevel = monTable->cellWidget(row, 3)->findChild()->value(); + newWildMon.species = monTable->cellWidget(row, extraColumn ? 2 : 1)->findChild()->currentText(); + newWildMon.minLevel = monTable->cellWidget(row, extraColumn ? 3 : 2)->findChild()->value(); + newWildMon.maxLevel = monTable->cellWidget(row, extraColumn ? 4 : 3)->findChild()->value(); newWildMons.append(newWildMon); } newInfo.active = true; diff --git a/src/editor.cpp b/src/editor.cpp index 86edb18d..3907e6bf 100644 --- a/src/editor.cpp +++ b/src/editor.cpp @@ -188,8 +188,8 @@ void Editor::displayWildMonTables() { stack->insertWidget(labelIndex, tabWidget); int tabIndex = 0; - for (Field monField : project->wildMonFields) { - QString fieldName = monField.first; + for (EncounterField monField : project->wildMonFields) { + QString fieldName = monField.name; tabWidget->clearTableAt(tabIndex); @@ -245,24 +245,24 @@ void Editor::addNewWildMonGroup(QWidget *window) { copyCheckbox->setEnabled(stack->count()); form.addRow(new QLabel("Copy from current group"), copyCheckbox); QVector fieldCheckboxes; - for (Field monField : project->wildMonFields) { + for (EncounterField monField : project->wildMonFields) { QCheckBox *fieldCheckbox = new QCheckBox; fieldCheckboxes.append(fieldCheckbox); - form.addRow(new QLabel(monField.first), fieldCheckbox); + form.addRow(new QLabel(monField.name), fieldCheckbox); } // Reading from ui here so not saving to disk before user. connect(copyCheckbox, &QCheckBox::stateChanged, [=](int state){ if (state == Qt::Checked) { int fieldIndex = 0; MonTabWidget *monWidget = static_cast(stack->widget(stack->currentIndex())); - for (Field monField : project->wildMonFields) { + for (EncounterField monField : project->wildMonFields) { fieldCheckboxes[fieldIndex]->setChecked(monWidget->isTabEnabled(fieldIndex)); fieldCheckboxes[fieldIndex]->setEnabled(false); fieldIndex++; } } else if (state == Qt::Unchecked) { int fieldIndex = 0; - for (Field monField : project->wildMonFields) { + for (EncounterField monField : project->wildMonFields) { fieldCheckboxes[fieldIndex]->setEnabled(true); fieldIndex++; } @@ -281,8 +281,8 @@ void Editor::addNewWildMonGroup(QWidget *window) { if (dialog.exec() == QDialog::Accepted) { WildPokemonHeader header; - for (Field monField : project->wildMonFields) { - QString fieldName = monField.first; + for (EncounterField monField : project->wildMonFields) { + QString fieldName = monField.name; header.wildMons[fieldName].active = false; header.wildMons[fieldName].encounterRate = 0; } @@ -294,14 +294,14 @@ void Editor::addNewWildMonGroup(QWidget *window) { labelCombo->setCurrentIndex(labelCombo->count() - 1); int tabIndex = 0; - for (Field monField : project->wildMonFields) { - QString fieldName = monField.first; + for (EncounterField monField : project->wildMonFields) { + QString fieldName = monField.name; tabWidget->clearTableAt(tabIndex); if (fieldCheckboxes[tabIndex]->isChecked()) { if (copyCheckbox->isChecked()) { MonTabWidget *copyFrom = static_cast(stack->widget(stackIndex)); if (copyFrom->isTabEnabled(tabIndex)) - header.wildMons.insert(fieldName, copyMonInfoFromTab(copyFrom->tableAt(tabIndex))); + header.wildMons.insert(fieldName, copyMonInfoFromTab(copyFrom->tableAt(tabIndex), monField)); else header.wildMons.insert(fieldName, getDefaultMonInfo(monField)); } else { @@ -319,25 +319,52 @@ void Editor::addNewWildMonGroup(QWidget *window) { void Editor::configureEncounterJSON(QWidget *window) { QVector fieldSlots; - Fields tempFields = project->wildMonFields; + EncounterFields tempFields = project->wildMonFields; QLabel *totalLabel = new QLabel; - auto updateTotal = [&fieldSlots, totalLabel](Field ¤tField) { + // lambda: Update the total displayed at the bottom of the Configure JSON + // window. Take groups into account when applicable. + auto updateTotal = [&fieldSlots, totalLabel](EncounterField ¤tField) { int total = 0, spinnerIndex = 0; + QString groupTotalMessage; + QMap groupTotals; + for (QString key : currentField.groups.keys()) + groupTotals.insert(key, 0);// add to group map and initialize total to zero for (auto slot : fieldSlots) { QSpinBox *spinner = slot->findChild(); int val = spinner->value(); - currentField.second[spinnerIndex++] = spinner->value(); - total += val; + currentField.encounterRates[spinnerIndex] = val; + if (!currentField.groups.isEmpty()) { + for (QString key : currentField.groups.keys()) { + if (currentField.groups[key].contains(spinnerIndex)) { + groupTotals[key] += val; + break; + } + } + } else { + total += val; + } + spinnerIndex++; + } + if (!currentField.groups.isEmpty()) { + groupTotalMessage += "Totals: "; + for (QString key : currentField.groups.keys()) { + groupTotalMessage += QString("%1 (%2),\t").arg(groupTotals[key]).arg(key); + } + groupTotalMessage.chop(2); + totalLabel->setText(groupTotalMessage); + } else { + totalLabel->setText(QString("Total: %1").arg(QString::number(total))); } - totalLabel->setText(QString("Total: %1").arg(QString::number(total))); }; - auto createNewSlot = [&fieldSlots, &updateTotal](int index, Field ¤tField) { + // lambda: Create a new "slot", which is the widget containing a spinner and an index label. + // Add the slot to a list of fieldSlots, which exists to keep track of them for memory management. + auto createNewSlot = [&fieldSlots, &tempFields, &updateTotal](int index, EncounterField ¤tField) { QLabel *indexLabel = new QLabel(QString("Index: %1").arg(QString::number(index))); QSpinBox *chanceSpinner = new QSpinBox; - int chance = currentField.second.at(index); + int chance = currentField.encounterRates.at(index); chanceSpinner->setMinimum(1); chanceSpinner->setMaximum(9999); chanceSpinner->setValue(chance); @@ -345,11 +372,46 @@ void Editor::configureEncounterJSON(QWidget *window) { updateTotal(currentField); }); + bool useGroups = !currentField.groups.isEmpty(); + + QFrame *slotChoiceFrame = new QFrame; + QVBoxLayout *slotChoiceLayout = new QVBoxLayout; + if (useGroups) { + QComboBox *groupCombo = new QComboBox; + connect(groupCombo, QOverload::of(&QComboBox::activated), [&tempFields, ¤tField, index](QString newGroupName) { + for (EncounterField &field : tempFields) { + if (field.name == currentField.name) { + for (QString groupName : field.groups.keys()) { + if (field.groups[groupName].contains(index)) { + field.groups[groupName].removeAll(index); + break; + } + } + for (QString groupName : field.groups.keys()) { + if (groupName == newGroupName) field.groups[newGroupName].append(index); + } + break; + } + } + }); + groupCombo->addItems(currentField.groups.keys()); + QString currentGroupName; + for (QString groupName : currentField.groups.keys()) { + if (currentField.groups[groupName].contains(index)) { + currentGroupName = groupName; + break; + } + } + groupCombo->setCurrentText(currentGroupName); + slotChoiceLayout->addWidget(groupCombo); + } + slotChoiceLayout->addWidget(chanceSpinner); + slotChoiceFrame->setLayout(slotChoiceLayout); + QFrame *slot = new QFrame; QHBoxLayout *slotLayout = new QHBoxLayout; - slotLayout->addStretch(); slotLayout->addWidget(indexLabel); - slotLayout->addWidget(chanceSpinner); + slotLayout->addWidget(slotChoiceFrame); slot->setLayout(slotLayout); fieldSlots.append(slot); @@ -368,13 +430,16 @@ void Editor::configureEncounterJSON(QWidget *window) { connect(&buttonBox, SIGNAL(accepted()), &dialog, SLOT(accept())); connect(&buttonBox, SIGNAL(rejected()), &dialog, SLOT(reject())); + // lambda: Get a QStringList of the existing field names. auto getFieldNames = [&tempFields]() { QStringList fieldNames; - for (Field field : tempFields) - fieldNames.append(field.first); + for (EncounterField field : tempFields) + fieldNames.append(field.name); return fieldNames; }; - auto drawSlotWidgets = [this, &grid, &createNewSlot, &fieldSlots, &updateTotal, &tempFields](int index) { + + // lambda: Draws the slot widgets onto a grid (4 wide) on the dialog window. + auto drawSlotWidgets = [this, &dialog, &grid, &createNewSlot, &fieldSlots, &updateTotal, &tempFields](int index) { // Clear them first. while (!fieldSlots.isEmpty()) { auto slot = fieldSlots.takeFirst(); @@ -382,12 +447,14 @@ void Editor::configureEncounterJSON(QWidget *window) { delete slot; } - Field ¤tField = tempFields[index]; - for (int i = 0; i < currentField.second.size(); i++) { + EncounterField ¤tField = tempFields[index]; + for (int i = 0; i < currentField.encounterRates.size(); i++) { grid.addWidget(createNewSlot(i, currentField), i / 4 + 1, i % 4); } updateTotal(currentField); + + dialog.adjustSize();// TODO: why is this updating only on second call? reproduce: land->fishing->rock_smash->water }; QComboBox *fieldChoices = new QComboBox; connect(fieldChoices, QOverload::of(&QComboBox::currentIndexChanged), drawSlotWidgets); @@ -414,7 +481,7 @@ void Editor::configureEncounterJSON(QWidget *window) { if (newNameDialog.exec() == QDialog::Accepted) { QString newFieldName = newNameEdit->text(); QVector newFieldRates(1, 100); - tempFields.append({newFieldName, newFieldRates}); + tempFields.append({newFieldName, newFieldRates, {}}); fieldChoices->addItem(newFieldName); fieldChoices->setCurrentIndex(fieldChoices->count() - 1); } @@ -431,16 +498,16 @@ void Editor::configureEncounterJSON(QWidget *window) { QPushButton *addSlotButton = new QPushButton(QIcon(":/icons/add.ico"), ""); addSlotButton->setFlat(true); connect(addSlotButton, &QPushButton::clicked, [this, &fieldChoices, &drawSlotWidgets, &tempFields]() { - Field &field = tempFields[fieldChoices->currentIndex()]; - field.second.append(1); + EncounterField &field = tempFields[fieldChoices->currentIndex()]; + field.encounterRates.append(1); drawSlotWidgets(fieldChoices->currentIndex()); }); QPushButton *removeSlotButton = new QPushButton(QIcon(":/icons/delete.ico"), ""); removeSlotButton->setFlat(true); connect(removeSlotButton, &QPushButton::clicked, [this, &fieldChoices, &drawSlotWidgets, &tempFields]() { - Field &field = tempFields[fieldChoices->currentIndex()]; - if (field.second.size() > 1) - field.second.removeLast(); + EncounterField &field = tempFields[fieldChoices->currentIndex()]; + if (field.encounterRates.size() > 1) + field.encounterRates.removeLast(); drawSlotWidgets(fieldChoices->currentIndex()); }); @@ -490,39 +557,39 @@ void Editor::saveEncounterTabData() { WildPokemonHeader &encounterHeader = encounterMap[labelCombo->itemText(groupIndex)]; int fieldIndex = 0; - for (Field monField : project->wildMonFields) { - QString fieldName = monField.first; + for (EncounterField monField : project->wildMonFields) { + QString fieldName = monField.name; if (!tabWidget->isTabEnabled(fieldIndex++)) continue; QTableWidget *monTable = static_cast(tabWidget->widget(fieldIndex - 1)); QVector newWildMons; - encounterHeader.wildMons[fieldName] = copyMonInfoFromTab(monTable); + encounterHeader.wildMons[fieldName] = copyMonInfoFromTab(monTable, monField); } } } // Update encounters for every map based on the new encounter JSON field data. -void Editor::updateEncounterFields(Fields newFields) { - Fields oldFields = project->wildMonFields; +void Editor::updateEncounterFields(EncounterFields newFields) { + EncounterFields oldFields = project->wildMonFields; // Go through fields and determine whether we need to update a field. // If the field is new, do nothing. // If the field is deleted, remove from all maps. // If the field is changed, change all maps accordingly. - for (Field oldField : oldFields) { - QString oldFieldName = oldField.first; + for (EncounterField oldField : oldFields) { + QString oldFieldName = oldField.name; bool fieldDeleted = true; - for (Field newField : newFields) { - QString newFieldName = newField.first; + for (EncounterField newField : newFields) { + QString newFieldName = newField.name; if (oldFieldName == newFieldName) { fieldDeleted = false; - if (oldField.second.size() != newField.second.size()) { + if (oldField.encounterRates.size() != newField.encounterRates.size()) { for (QString map : project->wildMonData.keys()) { for (QString groupName : project->wildMonData.value(map).keys()) { WildPokemonHeader &monHeader = project->wildMonData[map][groupName]; for (QString fieldName : monHeader.wildMons.keys()) { if (fieldName == oldFieldName) { - monHeader.wildMons[fieldName].wildPokemon.resize(newField.second.size()); + monHeader.wildMons[fieldName].wildPokemon.resize(newField.encounterRates.size()); } } } diff --git a/src/project.cpp b/src/project.cpp index cc12f15f..45e1c8a0 100644 --- a/src/project.cpp +++ b/src/project.cpp @@ -539,16 +539,27 @@ void Project::saveWildMonData() { monHeadersObject["for_maps"] = true; QJsonArray fieldsInfoArray; - for (QPair> fieldInfo : wildMonFields) { + for (EncounterField fieldInfo : wildMonFields) { QJsonObject fieldObject; QJsonArray rateArray; - for (int rate : fieldInfo.second) + for (int rate : fieldInfo.encounterRates) { rateArray.append(rate); + } - fieldObject["type"] = fieldInfo.first; + fieldObject["type"] = fieldInfo.name; fieldObject["encounter_rates"] = rateArray; + QJsonObject groupsObject; + for (QString groupName : fieldInfo.groups.keys()) { + QJsonArray subGroupIndices; + for (int slotIndex : fieldInfo.groups[groupName]) { + subGroupIndices.append(slotIndex); + } + groupsObject[groupName] = subGroupIndices; + } + fieldObject["groups"] = groupsObject; + fieldsInfoArray.append(fieldObject); } monHeadersObject["fields"] = fieldsInfoArray; @@ -1399,10 +1410,16 @@ void Project::readWildMonData() { } for (auto field : subObject["fields"].toArray()) { - QPair> encounterField; - encounterField.first = field.toObject()["type"].toString(); - for (auto val : field.toObject()["encounter_rates"].toArray()) - encounterField.second.append(val.toInt()); + EncounterField encounterField; + encounterField.name = field.toObject()["type"].toString(); + for (auto val : field.toObject()["encounter_rates"].toArray()) { + encounterField.encounterRates.append(val.toInt()); + } + for (QString group : field.toObject()["groups"].toObject().keys()) { + for (auto slotNum : field.toObject()["groups"].toObject()[group].toArray()) { + encounterField.groups[group].append(slotNum.toInt()); + } + } wildMonFields.append(encounterField); } @@ -1412,8 +1429,8 @@ void Project::readWildMonData() { WildPokemonHeader header; - for (QPair> monField : wildMonFields) { - QString field = monField.first; + for (EncounterField monField : wildMonFields) { + QString field = monField.name; if (encounter.toObject().value(field) != QJsonValue::Undefined) { header.wildMons[field].active = true; header.wildMons[field].encounterRate = encounter.toObject().value(field).toObject().value("encounter_rate").toInt(); diff --git a/src/ui/montabwidget.cpp b/src/ui/montabwidget.cpp index 6838f32e..0ef30a08 100644 --- a/src/ui/montabwidget.cpp +++ b/src/ui/montabwidget.cpp @@ -22,16 +22,16 @@ bool MonTabWidget::eventFilter(QObject *, QEvent *event) { } void MonTabWidget::populate() { - Fields fields = project->wildMonFields; + EncounterFields fields = project->wildMonFields; activeTabs = QVector(fields.size(), false); - for (QPair> field : fields) { + for (EncounterField field : fields) { QTableWidget *table = new QTableWidget; table->setEditTriggers(QAbstractItemView::NoEditTriggers); table->setFocusPolicy(Qt::NoFocus); table->setSelectionMode(QAbstractItemView::NoSelection); table->clearFocus(); - addTab(table, field.first); + addTab(table, field.name); } } @@ -62,11 +62,21 @@ void MonTabWidget::clearTableAt(int tabIndex) { void MonTabWidget::populateTab(int tabIndex, WildMonInfo monInfo, QString fieldName) { QTableWidget *speciesTable = tableAt(tabIndex); + int fieldIndex = 0; + for (EncounterField field : project->wildMonFields) { + if (field.name == fieldName) break; + fieldIndex++; + } + bool insertGroupLabel = false; + if (!project->wildMonFields[fieldIndex].groups.isEmpty()) insertGroupLabel = true; + speciesTable->setRowCount(monInfo.wildPokemon.size()); - speciesTable->setColumnCount(7); + speciesTable->setColumnCount(insertGroupLabel ? 8 : 7); QStringList landMonTableHeaders; - landMonTableHeaders << "Slot" << "Species" << "Min Level" << "Max Level" + landMonTableHeaders << "Slot"; + if (insertGroupLabel) landMonTableHeaders << "Group"; + landMonTableHeaders << "Species" << "Min Level" << "Max Level" << "Encounter Chance" << "Slot Ratio" << "Encounter Rate"; speciesTable->setHorizontalHeaderLabels(landMonTableHeaders); speciesTable->horizontalHeader()->show(); @@ -85,7 +95,7 @@ void MonTabWidget::populateTab(int tabIndex, WildMonInfo monInfo, QString fieldN encounterRate->setValue(monInfo.encounterRate); encounterLayout->addWidget(encounterRate); encounterFrame->setLayout(encounterLayout); - speciesTable->setCellWidget(0, 6, encounterFrame); + speciesTable->setCellWidget(0, insertGroupLabel? 7 : 6, encounterFrame); int i = 0; for (WildPokemon mon : monInfo.wildPokemon) { @@ -127,19 +137,32 @@ void MonTabWidget::createSpeciesTableRow(QTableWidget *table, WildPokemon mon, i }); int fieldIndex = 0; - for (auto field : project->wildMonFields) { - if (field.first == fieldName) break; + for (EncounterField field : project->wildMonFields) { + if (field.name == fieldName) break; fieldIndex++; } - double slotChanceTotal = 0; - for (auto chance : project->wildMonFields[fieldIndex].second) { - slotChanceTotal += static_cast(chance); + + double slotChanceTotal = 0.0; + if (!project->wildMonFields[fieldIndex].groups.isEmpty()) { + for (QString groupKey : project->wildMonFields[fieldIndex].groups.keys()) { + if (project->wildMonFields[fieldIndex].groups[groupKey].contains(index)) { + for (int chanceIndex : project->wildMonFields[fieldIndex].groups[groupKey]) { + slotChanceTotal += static_cast(project->wildMonFields[fieldIndex].encounterRates[chanceIndex]); + } + break; + } + } + } else { + for (auto chance : project->wildMonFields[fieldIndex].encounterRates) { + slotChanceTotal += static_cast(chance); + } } + QLabel *percentLabel = new QLabel(QString("%1%").arg( - QString::number(project->wildMonFields[fieldIndex].second[index] / slotChanceTotal * 100.0, 'f', 2) + QString::number(project->wildMonFields[fieldIndex].encounterRates[index] / slotChanceTotal * 100.0, 'f', 2) )); QLabel *ratioLabel = new QLabel(QString("%1").arg( - QString::number(project->wildMonFields[fieldIndex].second[index] + QString::number(project->wildMonFields[fieldIndex].encounterRates[index] ))); QFrame *speciesSelector = new QFrame; @@ -158,12 +181,25 @@ void MonTabWidget::createSpeciesTableRow(QTableWidget *table, WildPokemon mon, i maxLevelSpinboxLayout->addWidget(maxLevel); maxLevelFrame->setLayout(maxLevelSpinboxLayout); + bool insertGroupLabel = false; + if (!project->wildMonFields[fieldIndex].groups.isEmpty()) insertGroupLabel = true; table->setCellWidget(index, 0, monNum); - table->setCellWidget(index, 1, speciesSelector); - table->setCellWidget(index, 2, minLevelFrame); - table->setCellWidget(index, 3, maxLevelFrame); - table->setCellWidget(index, 4, percentLabel); - table->setCellWidget(index, 5, ratioLabel); + if (insertGroupLabel) { + QString groupName = QString(); + for (QString groupKey : project->wildMonFields[fieldIndex].groups.keys()) { + if (project->wildMonFields[fieldIndex].groups[groupKey].contains(index)) { + groupName = groupKey; + break; + } + } + QLabel *groupNameLabel = new QLabel(groupName); + table->setCellWidget(index, 1, groupNameLabel); + } + table->setCellWidget(index, insertGroupLabel? 2 : 1, speciesSelector); + table->setCellWidget(index, insertGroupLabel? 3 : 2, minLevelFrame); + table->setCellWidget(index, insertGroupLabel? 4 : 3, maxLevelFrame); + table->setCellWidget(index, insertGroupLabel? 5 : 4, percentLabel); + table->setCellWidget(index, insertGroupLabel? 6 : 5, ratioLabel); } QTableWidget *MonTabWidget::tableAt(int tabIndex) {