From 6c3ee3c46d2f22f3320034786058fa4e0cf5edda Mon Sep 17 00:00:00 2001
From: garak <garakmon@gmail.com>
Date: Thu, 12 Aug 2021 19:19:21 -0400
Subject: [PATCH] order wild encounter json reading

---
 include/core/parseutil.h   |  1 +
 include/core/wildmoninfo.h |  5 +--
 src/core/parseutil.cpp     | 11 ++++++
 src/core/wildmoninfo.cpp   |  2 +-
 src/editor.cpp             | 50 +++++++++++++++++----------
 src/project.cpp            | 71 ++++++++++++++++++++++----------------
 src/ui/montabwidget.cpp    | 12 ++++---
 7 files changed, 95 insertions(+), 57 deletions(-)

diff --git a/include/core/parseutil.h b/include/core/parseutil.h
index 009e07c3..d1ec1623 100644
--- a/include/core/parseutil.h
+++ b/include/core/parseutil.h
@@ -53,6 +53,7 @@ public:
     QList<QStringList> getLabelMacros(const QList<QStringList>&, const QString&);
     QStringList getLabelValues(const QList<QStringList>&, const QString&);
     bool tryParseJsonFile(QJsonDocument *out, const QString &filepath);
+    bool tryParseOrderedJsonFile(poryjson::Json::object *out, const QString &filepath);
     bool ensureFieldsExist(const QJsonObject &obj, const QList<QString> &fields);
 
     // Returns the 1-indexed line number for the definition of scriptLabel in the scripts file at filePath.
diff --git a/include/core/wildmoninfo.h b/include/core/wildmoninfo.h
index 06877622..a42522e7 100644
--- a/include/core/wildmoninfo.h
+++ b/include/core/wildmoninfo.h
@@ -3,6 +3,7 @@
 #define GUARD_WILDMONINFO_H
 
 #include <QtWidgets>
+#include "orderedmap.h"
 
 struct WildPokemon {
     int minLevel = 5;
@@ -17,13 +18,13 @@ struct WildMonInfo {
 };
 
 struct WildPokemonHeader {
-    QHash<QString, WildMonInfo> wildMons;
+    tsl::ordered_map<QString, WildMonInfo> wildMons;
 };
 
 struct EncounterField {
     QString name;
     QVector<int> encounterRates;
-    QMap<QString, QVector<int>> groups;
+    tsl::ordered_map<QString, QVector<int>> groups;
 };
 
 typedef QVector<EncounterField> EncounterFields;
diff --git a/src/core/parseutil.cpp b/src/core/parseutil.cpp
index 79464b8b..fad3c804 100644
--- a/src/core/parseutil.cpp
+++ b/src/core/parseutil.cpp
@@ -404,6 +404,17 @@ bool ParseUtil::tryParseJsonFile(QJsonDocument *out, const QString &filepath) {
     return true;
 }
 
+bool ParseUtil::tryParseOrderedJsonFile(poryjson::Json::object *out, const QString &filepath) {
+    QString err;
+    QString jsonTxt = readTextFile(filepath);
+    *out = OrderedJson::parse(jsonTxt, err).object_items();
+    if (!err.isEmpty()) {
+        logError(QString("Error: Failed to parse json file %1: %2").arg(filepath).arg(err));
+        return false;
+    }
+    return true;
+}
+
 bool ParseUtil::ensureFieldsExist(const QJsonObject &obj, const QList<QString> &fields) {
     for (QString field : fields) {
         if (!obj.contains(field)) {
diff --git a/src/core/wildmoninfo.cpp b/src/core/wildmoninfo.cpp
index a6e730e3..2fe9be7a 100644
--- a/src/core/wildmoninfo.cpp
+++ b/src/core/wildmoninfo.cpp
@@ -19,7 +19,7 @@ WildMonInfo copyMonInfoFromTab(QTableWidget *monTable, EncounterField monField)
     WildMonInfo newInfo;
     QVector<WildPokemon> newWildMons;
 
-    bool extraColumn = !monField.groups.isEmpty();
+    bool extraColumn = !monField.groups.empty();
     for (int row = 0; row < monTable->rowCount(); row++) {
         WildPokemon newWildMon;
         newWildMon.species = monTable->cellWidget(row, extraColumn ? 2 : 1)->findChild<QComboBox *>()->currentText();
diff --git a/src/editor.cpp b/src/editor.cpp
index bd7b0caa..87574adc 100644
--- a/src/editor.cpp
+++ b/src/editor.cpp
@@ -340,12 +340,14 @@ void Editor::addNewWildMonGroup(QWidget *window) {
             if (fieldCheckboxes[tabIndex]->isChecked()) {
                 if (copyCheckbox->isChecked()) {
                     MonTabWidget *copyFrom = static_cast<MonTabWidget *>(stack->widget(stackIndex));
-                    if (copyFrom->isTabEnabled(tabIndex))
-                        header.wildMons.insert(fieldName, copyMonInfoFromTab(copyFrom->tableAt(tabIndex), monField));
-                    else
-                        header.wildMons.insert(fieldName, getDefaultMonInfo(monField));
+                    if (copyFrom->isTabEnabled(tabIndex)) {
+                        header.wildMons[fieldName] = copyMonInfoFromTab(copyFrom->tableAt(tabIndex), monField);
+                    }
+                    else {
+                        header.wildMons[fieldName] = getDefaultMonInfo(monField);
+                    }
                 } else {
-                    header.wildMons.insert(fieldName, getDefaultMonInfo(monField));
+                    header.wildMons[fieldName] = getDefaultMonInfo(monField);
                 }
                 tabWidget->populateTab(tabIndex, header.wildMons[fieldName], fieldName);
             } else {
@@ -409,14 +411,16 @@ void Editor::configureEncounterJSON(QWidget *window) {
         int total = 0, spinnerIndex = 0;
         QString groupTotalMessage;
         QMap<QString, int> groupTotals;
-        for (QString key : currentField.groups.keys())
-            groupTotals.insert(key, 0);// add to group map and initialize total to zero
+        for (auto keyPair : currentField.groups) {
+            groupTotals.insert(keyPair.first, 0);// add to group map and initialize total to zero
+        }
         for (auto slot : fieldSlots) {
             QSpinBox *spinner = slot->findChild<QSpinBox *>();
             int val = spinner->value();
             currentField.encounterRates[spinnerIndex] = val;
-            if (!currentField.groups.isEmpty()) {
-                for (QString key : currentField.groups.keys()) {
+            if (!currentField.groups.empty()) {
+                for (auto keyPair : currentField.groups) {
+                    QString key = keyPair.first;
                     if (currentField.groups[key].contains(spinnerIndex)) {
                         groupTotals[key] += val;
                         break;
@@ -427,9 +431,10 @@ void Editor::configureEncounterJSON(QWidget *window) {
             }
             spinnerIndex++;
         }
-        if (!currentField.groups.isEmpty()) {
+        if (!currentField.groups.empty()) {
             groupTotalMessage += "Totals: ";
-            for (QString key : currentField.groups.keys()) {
+            for (auto keyPair : currentField.groups) {
+                QString key = keyPair.first;
                 groupTotalMessage += QString("%1 (%2),\t").arg(groupTotals[key]).arg(key);
             }
             groupTotalMessage.chop(2);
@@ -456,7 +461,7 @@ void Editor::configureEncounterJSON(QWidget *window) {
             updateTotal(currentField);
         });
 
-        bool useGroups = !currentField.groups.isEmpty();
+        bool useGroups = !currentField.groups.empty();
 
         QFrame *slotChoiceFrame = new QFrame;
         QVBoxLayout *slotChoiceLayout = new QVBoxLayout;
@@ -465,22 +470,27 @@ void Editor::configureEncounterJSON(QWidget *window) {
             connect(groupCombo, QOverload<const QString &>::of(&QComboBox::textActivated), [&tempFields, &currentField, index](QString newGroupName) {
                 for (EncounterField &field : tempFields) {
                     if (field.name == currentField.name) {
-                        for (QString groupName : field.groups.keys()) {
+                        for (auto groupNameIterator : field.groups) {
+                            QString groupName = groupNameIterator.first;
                             if (field.groups[groupName].contains(index)) {
                                 field.groups[groupName].removeAll(index);
                                 break;
                             }
                         }
-                        for (QString groupName : field.groups.keys()) {
+                        for (auto groupNameIterator : field.groups) {
+                            QString groupName = groupNameIterator.first;
                             if (groupName == newGroupName) field.groups[newGroupName].append(index);
                         }
                         break;
                     }
                 }
             });
-            groupCombo->addItems(currentField.groups.keys());
+            for (auto groupNameIterator : currentField.groups) {
+                groupCombo->addItem(groupNameIterator.first);
+            }
             QString currentGroupName;
-            for (QString groupName : currentField.groups.keys()) {
+            for (auto groupNameIterator : currentField.groups) {
+                QString groupName = groupNameIterator.first;
                 if (currentField.groups[groupName].contains(index)) {
                     currentGroupName = groupName;
                     break;
@@ -676,7 +686,8 @@ void Editor::updateEncounterFields(EncounterFields newFields) {
                         for (auto groupNamePair : project->wildMonData[map]) {
                             QString groupName = groupNamePair.first;
                             WildPokemonHeader &monHeader = project->wildMonData[map][groupName];
-                            for (QString fieldName : monHeader.wildMons.keys()) {
+                            for (auto fieldNamePair : monHeader.wildMons) {
+                                QString fieldName = fieldNamePair.first;
                                 if (fieldName == oldFieldName) {
                                     monHeader.wildMons[fieldName].wildPokemon.resize(newField.encounterRates.size());
                                 }
@@ -692,9 +703,10 @@ void Editor::updateEncounterFields(EncounterFields newFields) {
                 for (auto groupNamePair : project->wildMonData[map]) {
                     QString groupName = groupNamePair.first;
                     WildPokemonHeader &monHeader = project->wildMonData[map][groupName];
-                    for (QString fieldName : monHeader.wildMons.keys()) {
+                    for (auto fieldNamePair : monHeader.wildMons) {
+                        QString fieldName = fieldNamePair.first;
                         if (fieldName == oldFieldName) {
-                            monHeader.wildMons.remove(fieldName);
+                            monHeader.wildMons.erase(fieldName);
                         }
                     }
                 }
diff --git a/src/project.cpp b/src/project.cpp
index 3217da1c..7051dcd0 100644
--- a/src/project.cpp
+++ b/src/project.cpp
@@ -730,7 +730,8 @@ void Project::saveWildMonData() {
         fieldObject["encounter_rates"] = rateArray;
 
         OrderedJson::object groupsObject;
-        for (QString groupName : fieldInfo.groups.keys()) {
+        for (auto groupNamePair : fieldInfo.groups) {
+            QString groupName = groupNamePair.first;
             OrderedJson::array subGroupIndices;
             std::sort(fieldInfo.groups[groupName].begin(), fieldInfo.groups[groupName].end());
             for (int slotIndex : fieldInfo.groups[groupName]) {
@@ -754,9 +755,10 @@ void Project::saveWildMonData() {
             encounterObject["base_label"] = groupLabel;
 
             WildPokemonHeader encounterHeader = wildMonData[key][groupLabel];
-            for (QString fieldName : encounterHeader.wildMons.keys()) {
+            for (auto fieldNamePair : encounterHeader.wildMons) {
+                QString fieldName = fieldNamePair.first;
                 OrderedJson::object fieldObject;
-                WildMonInfo monInfo = encounterHeader.wildMons.value(fieldName);
+                WildMonInfo monInfo = encounterHeader.wildMons[fieldName];
                 fieldObject["encounter_rate"] = monInfo.encounterRate;
                 OrderedJson::array monArray;
                 for (WildPokemon wildMon : monInfo.wildPokemon) {
@@ -1726,20 +1728,18 @@ bool Project::readWildMonData() {
 
     QString wildMonJsonFilepath = QString("%1/src/data/wild_encounters.json").arg(root);
     fileWatcher.addPath(wildMonJsonFilepath);
-    QJsonDocument wildMonsJsonDoc;
-    if (!parser.tryParseJsonFile(&wildMonsJsonDoc, wildMonJsonFilepath)) {
+
+    OrderedJson::object wildMonObj;
+    if (!parser.tryParseOrderedJsonFile(&wildMonObj, wildMonJsonFilepath)) {
         logError(QString("Failed to read wild encounters from %1").arg(wildMonJsonFilepath));
         return false;
     }
 
-    QJsonObject wildMonObj = wildMonsJsonDoc.object();
-
-    for (auto subObjectRef : wildMonObj["wild_encounter_groups"].toArray()) {
-        QJsonObject subObject = subObjectRef.toObject();
-        if (!subObject["for_maps"].toBool()) {
+    for (OrderedJson subObjectRef : wildMonObj["wild_encounter_groups"].array_items()) {
+        OrderedJson::object subObject = subObjectRef.object_items();
+        if (!subObject["for_maps"].bool_value()) {
             QString err;
-            QString subObjson = QJsonDocument(subObject).toJson();
-            OrderedJson::object orderedSubObject = OrderedJson::parse(subObjson, err).object_items();
+            OrderedJson::object orderedSubObject = OrderedJson::parse(OrderedJson(subObject).dump(), err).object_items();
             extraEncounterGroups.push_back(orderedSubObject);
             if (!err.isEmpty()) {
                 logWarn(QString("Encountered a problem while parsing extra encounter groups: %1").arg(err));
@@ -1747,44 +1747,55 @@ bool Project::readWildMonData() {
             continue;
         }
 
-        for (auto field : subObject["fields"].toArray()) {
+        for (OrderedJson field : subObject["fields"].array_items()) {
             EncounterField encounterField;
-            encounterField.name = field.toObject()["type"].toString();
-            for (auto val : field.toObject()["encounter_rates"].toArray()) {
-                encounterField.encounterRates.append(val.toInt());
+            OrderedJson::object fieldObj = field.object_items();
+            encounterField.name = fieldObj["type"].string_value();
+            for (auto val : fieldObj["encounter_rates"].array_items()) {
+                encounterField.encounterRates.append(val.int_value());
             }
-            for (QString group : field.toObject()["groups"].toObject().keys()) {
-                for (auto slotNum : field.toObject()["groups"].toObject()[group].toArray()) {
-                    encounterField.groups[group].append(slotNum.toInt());
+
+            QList<QString> subGroups;
+            for (auto groupPair : fieldObj["groups"].object_items()) {
+                subGroups.append(groupPair.first);
+            }
+            for (QString group : subGroups) {
+                OrderedJson::object groupsObj = fieldObj["groups"].object_items();
+                for (auto slotNum : groupsObj[group].array_items()) {
+                    encounterField.groups[group].append(slotNum.int_value());
                 }
             }
             wildMonFields.append(encounterField);
         }
 
-        QJsonArray encounters = subObject["encounters"].toArray();
-        for (QJsonValue encounter : encounters) {
-            QString mapConstant = encounter.toObject().value("map").toString();
+        auto encounters = subObject["encounters"].array_items();
+        for (auto encounter : encounters) {
+            OrderedJson::object encounterObj = encounter.object_items();
+            QString mapConstant = encounterObj["map"].string_value();
 
             WildPokemonHeader header;
 
             for (EncounterField monField : wildMonFields) {
                 QString field = monField.name;
-                if (encounter.toObject().value(field) != QJsonValue::Undefined) {
+                if (!encounterObj[field].is_null()) {
+                    OrderedJson::object encounterFieldObj = encounterObj[field].object_items();
                     header.wildMons[field].active = true;
-                    header.wildMons[field].encounterRate = encounter.toObject().value(field).toObject().value("encounter_rate").toInt();
-                    for (QJsonValue mon : encounter.toObject().value(field).toObject().value("mons").toArray()) {
+                    header.wildMons[field].encounterRate = encounterFieldObj["encounter_rate"].int_value();
+                    for (auto mon : encounterFieldObj["mons"].array_items()) {
                         WildPokemon newMon;
-                        newMon.minLevel = mon.toObject().value("min_level").toInt();
-                        newMon.maxLevel = mon.toObject().value("max_level").toInt();
-                        newMon.species = mon.toObject().value("species").toString();
+                        OrderedJson::object monObj = mon.object_items();
+                        newMon.minLevel = monObj["min_level"].int_value();
+                        newMon.maxLevel = monObj["max_level"].int_value();
+                        newMon.species = monObj["species"].string_value();
                         header.wildMons[field].wildPokemon.append(newMon);
                     }
                 }
             }
-            wildMonData[mapConstant].insert({encounter.toObject().value("base_label").toString(), header});
-            encounterGroupLabels.append(encounter.toObject().value("base_label").toString());
+            wildMonData[mapConstant].insert({encounterObj["base_label"].string_value(), header});
+            encounterGroupLabels.append(encounterObj["base_label"].string_value());
         }
     }
+
     return true;
 }
 
diff --git a/src/ui/montabwidget.cpp b/src/ui/montabwidget.cpp
index bc3c24e8..765749ca 100644
--- a/src/ui/montabwidget.cpp
+++ b/src/ui/montabwidget.cpp
@@ -71,7 +71,7 @@ void MonTabWidget::populateTab(int tabIndex, WildMonInfo monInfo, QString fieldN
         fieldIndex++;
     }
     bool insertGroupLabel = false;
-    if (!editor->project->wildMonFields[fieldIndex].groups.isEmpty()) insertGroupLabel = true;
+    if (!editor->project->wildMonFields[fieldIndex].groups.empty()) insertGroupLabel = true;
 
     speciesTable->setRowCount(monInfo.wildPokemon.size());
     speciesTable->setColumnCount(insertGroupLabel ? 8 : 7);
@@ -159,8 +159,9 @@ void MonTabWidget::createSpeciesTableRow(QTableWidget *table, WildPokemon mon, i
     }
 
     double slotChanceTotal = 0.0;
-    if (!editor->project->wildMonFields[fieldIndex].groups.isEmpty()) {
-        for (QString groupKey : editor->project->wildMonFields[fieldIndex].groups.keys()) {
+    if (!editor->project->wildMonFields[fieldIndex].groups.empty()) {
+        for (auto groupKeyPair : editor->project->wildMonFields[fieldIndex].groups) {
+            QString groupKey = groupKeyPair.first;
             if (editor->project->wildMonFields[fieldIndex].groups[groupKey].contains(index)) {
                 for (int chanceIndex : editor->project->wildMonFields[fieldIndex].groups[groupKey]) {
                     slotChanceTotal += static_cast<double>(editor->project->wildMonFields[fieldIndex].encounterRates[chanceIndex]);
@@ -198,11 +199,12 @@ void MonTabWidget::createSpeciesTableRow(QTableWidget *table, WildPokemon mon, i
     maxLevelFrame->setLayout(maxLevelSpinboxLayout);
 
     bool insertGroupLabel = false;
-    if (!editor->project->wildMonFields[fieldIndex].groups.isEmpty()) insertGroupLabel = true;
+    if (!editor->project->wildMonFields[fieldIndex].groups.empty()) insertGroupLabel = true;
     table->setCellWidget(index, 0, monNum);
     if (insertGroupLabel) {
         QString groupName = QString();
-        for (QString groupKey : editor->project->wildMonFields[fieldIndex].groups.keys()) {
+        for (auto groupKeyPair : editor->project->wildMonFields[fieldIndex].groups) {
+            QString groupKey = groupKeyPair.first;
             if (editor->project->wildMonFields[fieldIndex].groups[groupKey].contains(index)) {
                 groupName = groupKey;
                 break;