mirror of
https://github.com/opentibiabr/remeres-map-editor
synced 2026-08-15 18:26:04 -04:00
Bug Fixes: - Improved Lua scripting: Lua-visible nil is now consistent for missing/empty values across storage, JSON parsing, item/map/tile APIs, dialogs, app properties, and overlay hover. - Improved macOS graphics reliability (OpenGL context/linking) and safer background/thread handling for streaming and SQLite bootstrap. - Fixed client asset loading paths and improved SQLite migration input paths. UI Improvements: - Replaced “Missing …” modal dialogs with inline text boxes. - Made “Client directory” editable; added macOS app-picker browse flow. CI: - Added compile-scoped macOS builds via reusable workflow and a new build-macos job.
213 lines
7.6 KiB
C++
213 lines
7.6 KiB
C++
//////////////////////////////////////////////////////////////////////
|
|
// This file is part of Canary Map Editor
|
|
//////////////////////////////////////////////////////////////////////
|
|
// Canary Map Editor is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// Canary Map Editor is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
//////////////////////////////////////////////////////////////////////
|
|
|
|
#include "main.h"
|
|
|
|
#include "client_assets.h"
|
|
|
|
#include "settings.h"
|
|
#include "filehandle.h"
|
|
#include "preferences.h"
|
|
#include "sprite_appearances.h"
|
|
#include "gui.h"
|
|
#include "otml.h"
|
|
|
|
#include <appearances.pb.h>
|
|
|
|
namespace {
|
|
bool logErrorAndSetMessage(const std::string &message, wxString &error) {
|
|
spdlog::error(message);
|
|
error = message;
|
|
return false;
|
|
}
|
|
} // namespace (internal use only)
|
|
|
|
using json = nlohmann::json;
|
|
|
|
std::string ClientAssets::version_name;
|
|
wxString ClientAssets::data_path;
|
|
wxString ClientAssets::assets_path;
|
|
bool ClientAssets::loaded = false;
|
|
|
|
void ClientAssets::load() {
|
|
// Load the data directory info
|
|
try {
|
|
auto dataDirs = g_settings.getString(Config::ASSETS_DATA_DIRS);
|
|
if (!dataDirs.empty()) {
|
|
json read_obj = json::parse(dataDirs);
|
|
auto ver_obj = read_obj.at(0).get<json::object_t>();
|
|
auto path = ver_obj.at("path").get<std::string>();
|
|
setPath(wxstr(path));
|
|
}
|
|
} catch ([[maybe_unused]] const json::exception &e) {
|
|
spdlog::info("Json exception with error code {}", e.what());
|
|
}
|
|
}
|
|
|
|
bool ClientAssets::loadAppearanceProtobuf(wxString &error, wxArrayString &warnings) {
|
|
using namespace canary::protobuf::appearances;
|
|
using json = nlohmann::json;
|
|
|
|
const std::filesystem::path selectedDirectory(ClientAssets::getPath().ToStdString());
|
|
std::error_code filesystemError;
|
|
if (!std::filesystem::is_directory(selectedDirectory, filesystemError)) {
|
|
if (filesystemError && filesystemError != std::errc::no_such_file_or_directory) {
|
|
return logErrorAndSetMessage(fmt::format("Could not inspect client path {}: {}", selectedDirectory.string(), filesystemError.message()), error);
|
|
}
|
|
return logErrorAndSetMessage(fmt::format("Client directory is not a valid path: {}", selectedDirectory.string()), error);
|
|
}
|
|
|
|
std::filesystem::path assetsDirectory = selectedDirectory / "assets";
|
|
if (!std::filesystem::is_directory(assetsDirectory, filesystemError)) {
|
|
if (filesystemError && filesystemError != std::errc::no_such_file_or_directory) {
|
|
return logErrorAndSetMessage(fmt::format("Could not inspect assets path {}: {}", assetsDirectory.string(), filesystemError.message()), error);
|
|
}
|
|
assetsDirectory = selectedDirectory / "Contents" / "Resources" / "assets";
|
|
}
|
|
if (!std::filesystem::is_directory(assetsDirectory, filesystemError)) {
|
|
if (filesystemError && filesystemError != std::errc::no_such_file_or_directory) {
|
|
return logErrorAndSetMessage(fmt::format("Could not inspect assets path {}: {}", assetsDirectory.string(), filesystemError.message()), error);
|
|
}
|
|
return logErrorAndSetMessage(fmt::format("Assets directory not found for client path: {}", selectedDirectory.string()), error);
|
|
}
|
|
|
|
const std::string assetsPath = assetsDirectory.string() + "/";
|
|
if (!g_spriteAppearances.loadCatalogContent(assetsPath, false)) {
|
|
return logErrorAndSetMessage(fmt::format("Failed to load catalog content from directory: {}", assetsPath), error);
|
|
}
|
|
|
|
std::filesystem::path packagePath;
|
|
for (const auto &candidate : {
|
|
selectedDirectory / "package.json",
|
|
assetsDirectory.parent_path() / "package.json",
|
|
assetsDirectory.parent_path() / "app" / "package.json",
|
|
assetsDirectory.parent_path().parent_path().parent_path() / "package.json",
|
|
}) {
|
|
if (std::filesystem::exists(candidate, filesystemError)) {
|
|
packagePath = candidate;
|
|
break;
|
|
}
|
|
if (filesystemError && filesystemError != std::errc::no_such_file_or_directory) {
|
|
return logErrorAndSetMessage(fmt::format("Could not inspect package path {}: {}", candidate.string(), filesystemError.message()), error);
|
|
}
|
|
}
|
|
if (packagePath.empty()) {
|
|
return logErrorAndSetMessage(fmt::format("The file package.json is not present for client path: {}", selectedDirectory.string()), error);
|
|
}
|
|
|
|
std::ifstream file(packagePath, std::ios::in);
|
|
if (!file.is_open()) {
|
|
error = "Failed to open packages.json";
|
|
spdlog::error("Failed to open packages.json");
|
|
return false;
|
|
}
|
|
|
|
json document = json::parse(file, nullptr, false);
|
|
file.close();
|
|
// Save version from package.json
|
|
std::string version = document.at("version").get<std::string>();
|
|
version_name = version;
|
|
|
|
const std::string appearanceFileName = g_spriteAppearances.getAppearanceFileName();
|
|
|
|
std::fstream fileStream(assetsDirectory / appearanceFileName, std::ios::in | std::ios::binary);
|
|
if (!fileStream.is_open()) {
|
|
error = "Failed to load " + appearanceFileName + " from the client folder, file cannot be oppened";
|
|
spdlog::error("[{}] - Failed to load {}, file cannot be oppened", __func__, appearanceFileName);
|
|
fileStream.close();
|
|
return false;
|
|
}
|
|
|
|
// Verify that the version of the library that we linked against is
|
|
// compatible with the version of the headers we compiled against.
|
|
GOOGLE_PROTOBUF_VERIFY_VERSION;
|
|
g_gui.m_appearancesPtr = std::make_unique<Appearances>();
|
|
if (!g_gui.m_appearancesPtr->ParseFromIstream(&fileStream)) {
|
|
error = "Failed to parse binary file " + appearanceFileName + ", file is invalid";
|
|
spdlog::error("[{}] - Failed to parse binary file {}, file is invalid", __func__, appearanceFileName);
|
|
fileStream.close();
|
|
return false;
|
|
}
|
|
|
|
// Parsing all items into ItemType
|
|
bool rt = g_items.loadFromProtobuf(error, warnings, *g_gui.m_appearancesPtr);
|
|
if (!rt) {
|
|
error = "Failed to parse item types from protobuf";
|
|
spdlog::error("[{}] - Failed to parse item types from protobuf", __func__);
|
|
fileStream.close();
|
|
return false;
|
|
}
|
|
|
|
// Load looktypes
|
|
for (int i = 0; i < g_gui.m_appearancesPtr->outfit().size(); i++) {
|
|
const auto &outfit = g_gui.m_appearancesPtr->outfit().Get(i);
|
|
if (!g_gui.gfx.loadOutfitSpriteMetadata(outfit, error, warnings)) {
|
|
error = "Failed to parse outfit types from protobuf";
|
|
spdlog::error("[{}] - Failed to parse outfit types from protobuf", __func__);
|
|
fileStream.close();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
fileStream.close();
|
|
|
|
// Client loaded
|
|
setLoaded(true);
|
|
return true;
|
|
}
|
|
|
|
void ClientAssets::save() {
|
|
try {
|
|
json vers_obj;
|
|
|
|
json ver_obj;
|
|
ver_obj["id"] = getVersionName();
|
|
wxFileName fileName;
|
|
fileName.Assign(getPath());
|
|
ver_obj["path"] = fileName.GetFullPath().ToStdString();
|
|
auto path = fileName.GetFullPath().ToStdString();
|
|
vers_obj.push_back(ver_obj);
|
|
|
|
std::ostringstream out;
|
|
out << vers_obj;
|
|
g_settings.setString(Config::ASSETS_DATA_DIRS, out.str());
|
|
} catch ([[maybe_unused]] const json::exception &e) {
|
|
// pass
|
|
}
|
|
}
|
|
|
|
FileName ClientAssets::getDataPath() {
|
|
wxString basePath = g_gui.GetDataDirectory();
|
|
if (!wxFileName(basePath).DirExists()) {
|
|
basePath = g_gui.getFoundDataDirectory();
|
|
}
|
|
return basePath + data_path + FileName::GetPathSeparator();
|
|
}
|
|
|
|
FileName ClientAssets::getLocalPath() {
|
|
FileName f = g_gui.GetLocalDataDirectory() + data_path + FileName::GetPathSeparator();
|
|
f.Mkdir(0755, wxPATH_MKDIR_FULL);
|
|
return f;
|
|
}
|
|
|
|
wxString ClientAssets::getPath() {
|
|
return assets_path;
|
|
}
|
|
|
|
std::string ClientAssets::getVersionName() {
|
|
return version_name;
|
|
}
|