#define VC_ENABLE_REFLECTION #include "EngineController.hpp" #include #include #include "engine/Engine.hpp" #include "coders/commons.hpp" #include "debug/Logger.hpp" #include "coders/json.hpp" #include "content/ContentReport.hpp" #include "content/ContentControl.hpp" #include "content/PacksManager.hpp" #include "world/files/WorldConverter.hpp" #include "world/files/WorldFiles.hpp" #include "frontend/locale.hpp" #include "frontend/menu.hpp" #include "frontend/screens/LevelScreen.hpp" #include "frontend/screens/MenuScreen.hpp" #include "graphics/ui/GUI.hpp" #include "graphics/ui/elements/Menu.hpp" #include "graphics/ui/gui_util.hpp" #include "objects/Players.hpp" #include "interfaces/Task.hpp" #include "util/stringutil.hpp" #include "world/Level.hpp" #include "world/World.hpp" #include "LevelController.hpp" static debug::Logger logger("engine-control"); EngineController::EngineController(Engine& engine) : engine(engine) {} void EngineController::deleteWorld(const std::string& name) { io::path folder = engine.getPaths().getWorldFolderByName(name); auto deletion = [this, folder]() { logger.info() << "deleting " << folder.string(); io::remove_all(folder); if (!engine.isHeadless()) { engine.getGUI().getMenu()->back(); } }; if (engine.isHeadless()) { deletion(); return; } guiutil::confirm( engine, langs::get(L"delete-confirm", L"world") + L" (" + util::str2wstr_utf8(folder.string()) + L")", deletion ); } std::shared_ptr create_converter( Engine& engine, const std::shared_ptr& worldFiles, const Content* content, const std::shared_ptr& report, const runnable& postRunnable ) { ConvertMode mode; if (report->isUpgradeRequired()) { mode = ConvertMode::UPGRADE; } else if (report->hasContentReorder()) { mode = ConvertMode::REINDEX; } else { mode = ConvertMode::BLOCK_FIELDS; } return WorldConverter::startTask( worldFiles, content, report, [&engine, postRunnable]() { engine.postRunnable([=]() { postRunnable(); }); }, mode, true ); } struct ConfirmRequest { bool memo; std::wstring message; std::wstring text; }; static ConfirmRequest create_convert_request( const std::shared_ptr& report ) { ConfirmRequest request {false, L"", L""}; request.message = L"world.convert-block-layouts"; if (report->hasContentReorder()) { request.message = L"world.convert-request"; } if (report->isUpgradeRequired()) { request.message = L"world.upgrade-request"; } else if (report->hasDataLoss()) { request.message = L"world.convert-with-loss"; request.memo = true; for (const auto& line : report->getDataLoss()) { request.text += util::str2wstr_utf8(line) + L"\n"; } } return request; } static void call(Engine& engine, runnable func) { if (engine.isHeadless()) { func(); } else { menus::call(engine, std::move(func)); } } static void start(Engine& engine, std::shared_ptr task, const std::wstring& message) { if (engine.isHeadless()) { task->waitForEnd(); } else { menus::show_process_panel(engine, task, message); } } static void check_world(const EnginePaths& paths, const io::path& folder) { auto worldFile = folder / "world.json"; if (!io::exists(worldFile)) { throw std::runtime_error(worldFile.string() + " does not exists"); } } static const Content* load_world_content(Engine& engine, const io::path& folder) { auto& paths = engine.getPaths(); auto& contentControl = engine.getContentControl(); paths.setCurrentWorldFolder(folder); check_world(paths, folder); call(engine, [&contentControl]() { contentControl.loadContent(ContentPack::worldPacksList("world:")); }); return contentControl.get(); } static void load_world( Engine& engine, const std::shared_ptr& worldFiles, int64_t localPlayer ) { call(engine, [&engine, worldFiles, localPlayer] () { auto& contentControl = engine.getContentControl(); auto content = contentControl.get(); const auto& packs = contentControl.getContentPacks(); auto& settings = engine.getSettings(); auto level = World::load(worldFiles, settings, *content, packs); engine.onWorldOpen(std::move(level), localPlayer); }); } static dv::value create_missing_content_report( const std::shared_ptr& report ) { auto root = dv::object(); auto& contentEntries = root.list("content"); for (auto& entry : report->getMissingContent()) { std::string contentName = ContentTypeMeta.getNameString(entry.type); auto& contentEntry = contentEntries.object(); contentEntry["type"] = contentName; contentEntry["name"] = entry.name; } return root; } void EngineController::onMissingContent( const std::shared_ptr& report ) { if (engine.isHeadless()) { throw std::runtime_error( "missing content: " + json::stringify(create_missing_content_report(report), true, " ") ); } else { engine.setScreen(std::make_shared(engine)); menus::show( engine, "reports/missing_content", {create_missing_content_report(report)} ); } } static void confirm( Engine& engine, ConfirmRequest request, bool confirmed, runnable callback ) { if (confirmed || engine.isHeadless()) { callback(); return; } if (request.memo) { guiutil::confirm_with_memo( engine, langs::get(request.message), request.text, callback, L"", langs::get(L"Cancel") ); } else { guiutil::confirm( engine, langs::get(request.message), callback, nullptr, L"", langs::get(L"Cancel") ); } } void EngineController::openWorld(const std::string& name, bool confirmConvert) { const auto& paths = engine.getPaths(); auto& debugSettings = engine.getSettings().debug; auto folder = paths.getWorldsFolder() / name; auto content = load_world_content(engine, folder); auto worldFiles = std::make_shared(folder, debugSettings); auto report = World::checkIndices(worldFiles, content); if (report == nullptr) { load_world(engine, std::move(worldFiles), localPlayer); return; } if (report->hasMissingContent()) { onMissingContent(report); return; } auto request = create_convert_request(report); confirm(engine, std::move(request), confirmConvert, [=]() { auto task = create_converter( engine, worldFiles, content, report, [=]() { openWorld(name, false); } ); start(engine, std::move(task), L"Converting world..."); }); } inline uint64_t str2seed(const std::string& seedstr) { if (util::is_integer(seedstr)) { try { return std::stoull(seedstr); } catch (const std::out_of_range& err) { std::hash hash; return hash(seedstr); } } else { std::hash hash; return hash(seedstr); } } void EngineController::createWorld( const std::string& name, const std::string& seedstr, const std::string& generatorID ) { uint64_t seed = str2seed(seedstr); EnginePaths& paths = engine.getPaths(); auto folder = paths.getWorldsFolder() / name; call(engine, [this, &paths, folder]() { engine.getContentControl().loadContent(); paths.setCurrentWorldFolder(folder); }); auto& contentControl = engine.getContentControl(); auto level = World::create( name, generatorID, folder, seed, engine.getSettings(), *contentControl.get(), contentControl.getContentPacks() ); if (!engine.isHeadless()) { level->players->create(localPlayer); } engine.onWorldOpen(std::move(level), localPlayer); } void EngineController::setLocalPlayer(int64_t player) { localPlayer = player; } void EngineController::reopenWorld(World* world) { std::string name = world->wfile->getFolder().name(); engine.onWorldClosed(); openWorld(name, true); } static void reconfig_packs_outside( ContentControl& contentControl, const std::vector& packsToAdd, const std::vector& packsToRemove ) { auto& manager = contentControl.scan(); auto names = PacksManager::getNames( contentControl.getContentPacks() ); for (const auto& id : packsToAdd) { names.push_back(id); } for (const auto& id : packsToRemove) { manager.exclude(id); names.erase(std::find(names.begin(), names.end(), id)); } names = manager.assemble(names); contentControl.setContentPacksRaw(manager.getAll(names)); } static void reconfig_packs_inside( PacksManager& manager, std::vector& names, const std::vector& packsToAdd, const std::vector& packsToRemove ) { for (const auto& id : packsToAdd) { names.push_back(id); } for (const auto& id : packsToRemove) { manager.exclude(id); const auto& found = std::find(names.begin(), names.end(), id); if (found != names.end()) { names.erase(found); } else { logger.warning() << "attempt to remove non-installed pack: " << id; } } } void EngineController::reconfigPacks( LevelController* controller, const std::vector& packsToAdd, const std::vector& packsToRemove ) { auto& contentControl = engine.getContentControl(); auto content = contentControl.get(); runnable removeFunc = [this, controller, packsToAdd, packsToRemove, &contentControl]() { if (controller == nullptr) { try { reconfig_packs_outside(contentControl, packsToAdd, packsToRemove); } catch (const contentpack_error& err) { throw std::runtime_error( std::string(err.what()) + " [" + err.getPackId() + "]" ); } } else { auto world = controller->getLevel()->getWorld(); controller->saveWorld(); auto names = PacksManager::getNames(world->getPacks()); auto& manager = contentControl.scan(); reconfig_packs_inside(manager, names, packsToAdd, packsToRemove); auto& wfile = *world->wfile; wfile.removeIndices(packsToRemove); wfile.writePacks(manager.getAll(names)); reopenWorld(world); } }; bool hasIndices = false; std::stringstream ss; if (content) { for (const auto& id : packsToRemove) { auto runtime = content->getPackRuntime(id); if (runtime && runtime->getStats().hasSavingContent()) { if (hasIndices) { ss << ", "; } hasIndices = true; ss << id; } } } if (hasIndices && !engine.isHeadless()) { guiutil::confirm( engine, langs::get(L"remove-confirm", L"pack") + L" (" + util::str2wstr_utf8(ss.str()) + L")", [=]() { removeFunc(); } ); } else { removeFunc(); } }