porymap/src/config/legacy.cpp
2026-02-14 13:59:16 -05:00

424 lines
20 KiB
C++

#include "config.h"
#include "log.h"
#include "tile.h"
#include "block.h"
#include <QString>
#include <QDir>
#include <QColor>
QString getLegacyFilename(const QString &newFilename) {
static const QMap<QString, QString> map = {
{QStringLiteral("settings.json"), QStringLiteral("porymap.cfg")},
{QStringLiteral("shortcuts.json"), QStringLiteral("porymap.shortcuts.cfg")},
{QStringLiteral("porymap.project.json"), QStringLiteral("porymap.project.cfg")},
{QStringLiteral("porymap.user.json"), QStringLiteral("porymap.user.cfg")},
};
return map.value(newFilename);
}
bool KeyValueConfigBase::loadLegacy() {
const QString oldFilename = getLegacyFilename(filename());
if (oldFilename.isEmpty()) return false;
QDir dir(root());
QFile file(dir.absoluteFilePath(oldFilename));
if (!file.exists() || !file.open(QIODevice::ReadOnly)) return false;
QTextStream in(&file);
static const QRegularExpression re("^(?<key>[^=]+)=(?<value>.*)$");
while (!in.atEnd()) {
QString line = in.readLine().trimmed();
int commentIndex = line.indexOf("#");
if (commentIndex >= 0) {
line = line.left(commentIndex).trimmed();
}
if (line.length() == 0) {
continue;
}
QRegularExpressionMatch match = re.match(line);
if (match.hasMatch()) {
parseLegacyKeyValue(match.captured("key").trimmed(), match.captured("value").trimmed());
}
}
logInfo(QString("Loaded legacy config file '%1'").arg(oldFilename));
// Save before deleting the old config file to ensure no data is lost.
if (save()/* && !file.remove()*/) { // TODO: Once non-legacy loading is complete, we can uncomment this.
//logWarn(QString("Failed to delete old config file '%1'.").arg(oldFilename));
}
return true;
}
bool toBool(const QString &value) {
return (value.toInt(nullptr, 0) != 0);
}
int toInt(const QString &value, int min = INT_MIN, int max = INT_MAX, int defaultValue = 0) {
bool ok;
int result = value.toInt(&ok, 0);
if (!ok) result = defaultValue;
return qBound(min, result, max);
}
uint32_t toUInt(const QString &value, uint32_t min = 0, uint32_t max = UINT_MAX, uint32_t defaultValue = 0) {
bool ok;
uint32_t result = value.toUInt(&ok, 0);
if (!ok) result = defaultValue;
return qBound(min, result, max);
}
QColor toColor(const QString &value, const QColor &defaultValue = QColor(Qt::black)) {
if (value.isEmpty()) return QColor();
QColor color = QColor("#" + value);
if (!color.isValid()) color = defaultValue;
return color;
}
bool PorymapConfig::parseLegacyKeyValue(const QString &key, const QString &value) {
if (key == "recent_project") {
this->recentProjects = value.split(",", Qt::SkipEmptyParts);
this->recentProjects.removeDuplicates();
} else if (key == "project_manually_closed") {
this->projectManuallyClosed = toBool(value);
} else if (key == "reopen_on_launch") {
this->reopenOnLaunch = toBool(value);
} else if (key == "pretty_cursors") {
this->prettyCursors = toBool(value);
} else if (key == "map_list_tab") {
this->mapListTab = toInt(value, 0, 2, 0);
} else if (key == "map_list_edit_groups_enabled") {
this->mapListEditGroupsEnabled = toBool(value);
} else if (key.startsWith("map_list_hide_empty_enabled/")) {
bool ok;
int tab = key.mid(QStringLiteral("map_list_hide_empty_enabled/").length()).toInt(&ok, 0);
if (ok && toBool(value)) this->mapListTabsHidingEmptyFolders.insert(tab);
} else if (key == "map_list_layouts_sorted") {
this->mapListLayoutsSorted = toBool(value);
} else if (key == "map_list_locations_sorted") {
this->mapListLocationsSorted = toBool(value);
} else if (key == "mirror_connecting_maps") {
this->mirrorConnectingMaps = toBool(value);
} else if (key == "show_dive_emerge_maps") {
this->showDiveEmergeMaps = toBool(value);
} else if (key == "dive_emerge_map_opacity") {
this->diveEmergeMapOpacity = toInt(value, 10, 90, 30);
} else if (key == "dive_map_opacity") {
this->diveMapOpacity = toInt(value, 10, 90, 15);
} else if (key == "emerge_map_opacity") {
this->emergeMapOpacity = toInt(value, 10, 90, 15);
} else if (key == "collision_opacity") {
this->collisionOpacity = toInt(value, 0, 100, 50);
} else if (key == "tileset_editor_geometry") {
} else if (key == "metatiles_zoom") {
this->metatilesZoom = toInt(value, 10, 100, 30);
} else if (key == "collision_zoom") {
this->collisionZoom = toInt(value, 10, 100, 30);
} else if (key == "tileset_editor_metatiles_zoom") {
this->tilesetEditorMetatilesZoom = toInt(value, 10, 100, 30);
} else if (key == "tileset_editor_tiles_zoom") {
this->tilesetEditorTilesZoom = toInt(value, 10, 100, 30);
} else if (key == "tileset_editor_layer_orientation") {
// Being explicit here to avoid casting out-of-range values.
this->tilesetEditorLayerOrientation = (toInt(value) == static_cast<int>(Qt::Horizontal)) ? Qt::Horizontal : Qt::Vertical;
} else if (key == "show_player_view") {
this->showPlayerView = toBool(value);
} else if (key == "show_cursor_tile") {
this->showCursorTile = toBool(value);
} else if (key == "show_border") {
this->showBorder = toBool(value);
} else if (key == "show_grid") {
this->showGrid = toBool(value);
} else if (key == "show_tileset_editor_metatile_grid") {
this->showTilesetEditorMetatileGrid = toBool(value);
} else if (key == "show_tileset_editor_layer_grid") {
this->showTilesetEditorLayerGrid = toBool(value);
} else if (key == "show_tileset_editor_divider") {
this->showTilesetEditorDivider = toBool(value);
} else if (key == "show_tileset_editor_raw_attributes") {
this->showTilesetEditorRawAttributes = toBool(value);
} else if (key == "show_palette_editor_unused_colors") {
this->showPaletteEditorUnusedColors = toBool(value);
} else if (key == "monitor_files") {
this->monitorFiles = toBool(value);
} else if (key == "tileset_checkerboard_fill") {
this->tilesetCheckerboardFill = toBool(value);
} else if (key == "new_map_header_section_expanded") {
this->newMapHeaderSectionExpanded = toBool(value);
} else if (key == "theme") {
this->theme = value;
} else if (key == "wild_mon_chart_theme") {
this->wildMonChartTheme = value;
} else if (key == "text_editor_open_directory") {
this->textEditorOpenFolder = value;
} else if (key == "text_editor_goto_line") {
this->textEditorGotoLine = value;
} else if (key == "palette_editor_bit_depth") {
this->paletteEditorBitDepth = toInt(value, 15, 24, 24);
if (this->paletteEditorBitDepth != 15 && this->paletteEditorBitDepth != 24){
this->paletteEditorBitDepth = 24;
}
} else if (key == "project_settings_tab") {
this->projectSettingsTab = toInt(value, 0);
} else if (key == "load_all_event_scripts") { // Old setting replaced by script_autocomplete_mode
this->scriptAutocompleteMode = toBool(value) ? ScriptAutocompleteMode::All : ScriptAutocompleteMode::MapOnly;
} else if (key == "script_autocomplete_mode") {
this->scriptAutocompleteMode = static_cast<ScriptAutocompleteMode>(toInt(value, ScriptAutocompleteMode::MapOnly, ScriptAutocompleteMode::All));
} else if (key == "warp_behavior_warning_disabled") {
this->warpBehaviorWarningDisabled = toBool(value);
} else if (key == "event_delete_warning_disabled") {
this->eventDeleteWarningDisabled = toBool(value);
} else if (key == "event_overlay_enabled") {
this->eventOverlayEnabled = toBool(value);
} else if (key == "check_for_updates") {
this->checkForUpdates = toBool(value);
} else if (key == "show_project_loading_screen") {
this->showProjectLoadingScreen = toBool(value);
} else if (key == "last_update_check_time") {
this->lastUpdateCheckTime = QDateTime::fromString(value).toLocalTime();
} else if (key == "last_update_check_version") {
auto version = QVersionNumber::fromString(value);
if (version.segmentCount() != 3) {
this->lastUpdateCheckVersion = porymapVersion;
} else {
this->lastUpdateCheckVersion = version;
}
} else if (key.startsWith("rate_limit_time/")) {
static const QRegularExpression regex("\\brate_limit_time/(?<url>.+)");
QRegularExpressionMatch match = regex.match(key);
if (match.hasMatch()) {
this->rateLimitTimes.insert(match.captured("url"), QDateTime::fromString(value).toLocalTime());
}
} else if (key == "event_selection_shape_mode") {
if (value == "mask") {
this->eventSelectionShapeMode = QGraphicsPixmapItem::MaskShape;
} else if (value == "bounding_rect") {
this->eventSelectionShapeMode = QGraphicsPixmapItem::BoundingRectShape;
}
} else if (key == "shown_in_game_reload_message") {
this->shownInGameReloadMessage = toBool(value);
} else if (key == "grid_width") {
this->gridSettings.width = toUInt(value);
} else if (key == "grid_height") {
this->gridSettings.height = toUInt(value);
} else if (key == "grid_x") {
this->gridSettings.offsetX = toInt(value, 0, 999);
} else if (key == "grid_y") {
this->gridSettings.offsetY = toInt(value, 0, 999);
} else if (key == "grid_style") {
this->gridSettings.style = GridSettings::getStyleFromName(value);
} else if (key == "grid_color") {
this->gridSettings.color = toColor(value);
} else if (key == "status_bar_log_types") {
this->statusBarLogTypes.clear();
auto typeStrings = value.split(",", Qt::SkipEmptyParts);
for (const auto &typeString : typeStrings) {
LogType type = static_cast<LogType>(toInt(typeString, 0, 2));
this->statusBarLogTypes.insert(type);
}
} else if (key == "application_font") {
this->applicationFont = QFont();
this->applicationFont.fromString(value);
} else if (key == "map_list_font") {
this->mapListFont = QFont();
this->mapListFont.fromString(value);
} else if (key == "image_export_color_space_id") {
this->imageExportColorSpaceId = toInt(value, 0, 8);
} else {
return false;
}
return true;
}
bool ProjectConfig::parseLegacyKeyValue(const QString &key, const QString &value_) {
QString value(value_);
if (key == "base_game_version") {
this->baseGameVersion = BaseGame::stringToVersion(value);
} else if (key == "use_poryscript") {
this->usePoryScript = toBool(value);
} else if (key == "use_custom_border_size") {
this->useCustomBorderSize = toBool(value);
} else if (key == "enable_event_weather_trigger") {
this->eventWeatherTriggerEnabled = toBool(value);
} else if (key == "enable_event_secret_base") {
this->eventSecretBaseEnabled = toBool(value);
} else if (key == "enable_hidden_item_quantity") {
this->hiddenItemQuantityEnabled = toBool(value);
} else if (key == "enable_hidden_item_requires_itemfinder") {
this->hiddenItemRequiresItemfinderEnabled = toBool(value);
} else if (key == "enable_heal_location_respawn_data") {
this->healLocationRespawnDataEnabled = toBool(value);
} else if (key == "enable_event_clone_object" || key == "enable_object_event_in_connection") {
this->eventCloneObjectEnabled = toBool(value);
} else if (key == "enable_floor_number") {
this->floorNumberEnabled = toBool(value);
} else if (key == "create_map_text_file") {
this->createMapTextFileEnabled = toBool(value);
} else if (key == "enable_triple_layer_metatiles") {
this->tripleLayerMetatilesEnabled = toBool(value);
} else if (key == "default_metatile") {
this->defaultMetatileId = toUInt(value, 0, Block::MaxValue);
} else if (key == "default_elevation") {
this->defaultElevation = toUInt(value, 0, Block::MaxValue);
} else if (key == "default_collision") {
this->defaultCollision = toUInt(value, 0, Block::MaxValue);
} else if (key == "default_map_width") {
this->defaultMapSize.setWidth(toInt(value, 1));
} else if (key == "default_map_height") {
this->defaultMapSize.setHeight(toInt(value, 1));
} else if (key == "new_map_border_metatiles") {
this->newMapBorderMetatileIds.clear();
QList<QString> metatileIds = value.split(",");
for (int i = 0; i < metatileIds.size(); i++) {
int metatileId = toUInt(metatileIds.at(i), 0, Block::MaxValue);
this->newMapBorderMetatileIds.append(metatileId);
}
} else if (key == "default_primary_tileset") {
this->defaultPrimaryTileset = value;
} else if (key == "default_secondary_tileset") {
this->defaultSecondaryTileset = value;
} else if (key == "metatile_attributes_size") {
int size = toInt(value, 1, 4, 2);
if (!(size & (size - 1))) this->metatileAttributesSize = size;
} else if (key == "metatile_behavior_mask") {
this->metatileBehaviorMask = toUInt(value);
} else if (key == "metatile_terrain_type_mask") {
this->metatileTerrainTypeMask = toUInt(value);
} else if (key == "metatile_encounter_type_mask") {
this->metatileEncounterTypeMask = toUInt(value);
} else if (key == "metatile_layer_type_mask") {
this->metatileLayerTypeMask = toUInt(value);
} else if (key == "block_metatile_id_mask") {
this->blockMetatileIdMask = toUInt(value, 0, Block::MaxValue);
} else if (key == "block_collision_mask") {
this->blockCollisionMask = toUInt(value, 0, Block::MaxValue);
} else if (key == "block_elevation_mask") {
this->blockElevationMask = toUInt(value, 0, Block::MaxValue);
} else if (key == "unused_tile_normal") {
this->unusedTileNormal = toUInt(value, 0, Tile::MaxValue);
} else if (key == "unused_tile_covered") {
this->unusedTileCovered = toUInt(value, 0, Tile::MaxValue);
} else if (key == "unused_tile_split") {
this->unusedTileSplit = toUInt(value, 0, Tile::MaxValue);
} else if (key == "enable_map_allow_flags") {
this->mapAllowFlagsEnabled = toBool(value);
} else if (key.startsWith("path/")) {
auto k = reverseDefaultPaths(key.mid(QStringLiteral("path/").length()));
if (k != static_cast<ProjectFilePath>(-1)) {
this->setFilePath(k, value);
}
} else if (key.startsWith("ident/")) {
auto identifierId = reverseDefaultIdentifier(key.mid(QStringLiteral("ident/").length()));
if (identifierId != static_cast<ProjectIdentifier>(-1)) {
this->setIdentifier(identifierId, value);
}
} else if (key.startsWith("global_constant/")) {
this->globalConstants.insert(key.mid(QStringLiteral("global_constant/").length()), value);
} else if (key == "global_constants_filepaths") {
this->globalConstantsFilepaths = value.split(",", Qt::SkipEmptyParts);
} else if (key == "tilesets_have_callback") {
this->tilesetsHaveCallback = toBool(value);
} else if (key == "tilesets_have_is_compressed") {
this->tilesetsHaveIsCompressed = toBool(value);
} else if (key == "set_transparent_pixels_black") { // Old setting replaced by transparency_color
this->transparencyColor = toBool(value) ? QColor(Qt::black) : QColor();
} else if (key == "transparency_color") {
this->transparencyColor = toColor(value);
} else if (key == "preserve_matching_only_data") {
this->preserveMatchingOnlyData = toBool(value);
} else if (key == "event_icon_path_object") {
this->eventIconPaths[Event::Group::Object] = value;
} else if (key == "event_icon_path_warp") {
this->eventIconPaths[Event::Group::Warp] = value;
} else if (key == "event_icon_path_coord") {
this->eventIconPaths[Event::Group::Coord] = value;
} else if (key == "event_icon_path_bg") {
this->eventIconPaths[Event::Group::Bg] = value;
} else if (key == "event_icon_path_heal") {
this->eventIconPaths[Event::Group::Heal] = value;
} else if (key.startsWith("pokemon_icon_path/")) {
this->pokemonIconPaths.insert(key.mid(QStringLiteral("pokemon_icon_path/").length()), value);
} else if (key == "events_tab_icon_path") {
this->eventsTabIconPath = value;
} else if (key == "collision_sheet_path") {
this->collisionSheetPath = value;
} else if (key == "collision_sheet_width") {
this->collisionSheetSize.setWidth(toInt(value, 1, Block::MaxValue));
} else if (key == "collision_sheet_height") {
this->collisionSheetSize.setHeight(toInt(value, 1, Block::MaxValue));
} else if (key == "player_view_north") {
this->playerViewDistance.setTop(toInt(value, 0, INT_MAX, GBA_V_DIST_TO_CENTER));
} else if (key == "player_view_south") {
this->playerViewDistance.setBottom(toInt(value, 0, INT_MAX, GBA_V_DIST_TO_CENTER));
} else if (key == "player_view_west") {
this->playerViewDistance.setLeft(toInt(value, 0, INT_MAX, GBA_H_DIST_TO_CENTER));
} else if (key == "player_view_east") {
this->playerViewDistance.setRight(toInt(value, 0, INT_MAX, GBA_H_DIST_TO_CENTER));
} else if (key == "warp_behaviors") {
this->warpBehaviors.clear();
value.remove(" ");
const QStringList behaviorList = value.split(",", Qt::SkipEmptyParts);
for (auto s : behaviorList)
this->warpBehaviors.insert(toUInt(s));
} else if (key == "max_events_per_group") {
this->maxEventsPerGroup = toInt(value, 1, INT_MAX, 255);
} else if (key == "metatile_selector_width") {
this->metatileSelectorWidth = toInt(value, 1, INT_MAX, 8);
} else {
return false;
}
return true;
}
// Read input from the config to get the script paths and whether each is enabled or disbled.
// The format is a comma-separated list of paths. Each path can be followed (before the comma)
// by a :0 or :1 to indicate whether it should be disabled or enabled, respectively. If neither
// follow, it's assumed the script should be enabled.
QList<ScriptSettings> parseCustomScripts(const QString &input) {
QMap<QString,bool> customScripts;
const QList<QString> paths = input.split(",", Qt::SkipEmptyParts);
for (QString path : paths) {
// Read and remove suffix
bool enabled = !path.endsWith(":0");
if (!enabled || path.endsWith(":1"))
path.chop(2);
if (!path.isEmpty()) {
// If a path is repeated only its last instance will be considered.
customScripts.insert(path, enabled);
}
}
QList<ScriptSettings> settingsList;
for (auto it = customScripts.begin(); it != customScripts.end(); it++) {
settingsList.append({
.path = it.key(),
.enabled = it.value(),
.userOnly = true,
});
}
return settingsList;
}
bool UserConfig::parseLegacyKeyValue(const QString &key, const QString &value) {
if (key == "recent_map_or_layout") {
this->recentMapOrLayout = value;
} else if (key == "use_encounter_json") {
this->useEncounterJson = toBool(value);
} else if (key == "custom_scripts") {
this->customScripts = parseCustomScripts(value);
} else {
return false;
}
return true;
}
bool ShortcutsConfig::parseLegacyKeyValue(const QString &key, const QString &value) {
QStringList keySequences = value.split(' ');
for (auto keySequence : keySequences)
user_shortcuts.insert(key, keySequence);
return true;
}