#include "Engine.hpp" #ifndef GLEW_STATIC #define GLEW_STATIC #endif #include "debug/Logger.hpp" #include "assets/AssetsLoader.hpp" #include "audio/audio.hpp" #include "coders/GLSLExtension.hpp" #include "coders/imageio.hpp" #include "coders/json.hpp" #include "coders/toml.hpp" #include "coders/commons.hpp" #include "content/Content.hpp" #include "content/ContentBuilder.hpp" #include "content/ContentLoader.hpp" #include "core_defs.hpp" #include "files/files.hpp" #include "frontend/locale.hpp" #include "frontend/menu.hpp" #include "frontend/screens/Screen.hpp" #include "graphics/render/ModelsGenerator.hpp" #include "graphics/core/DrawContext.hpp" #include "graphics/core/ImageData.hpp" #include "graphics/core/Shader.hpp" #include "graphics/ui/GUI.hpp" #include "objects/rigging.hpp" #include "logic/EngineController.hpp" #include "logic/CommandsInterpreter.hpp" #include "logic/scripting/scripting.hpp" #include "logic/scripting/scripting_hud.hpp" #include "network/Network.hpp" #include "util/listutil.hpp" #include "util/platform.hpp" #include "window/Camera.hpp" #include "window/Events.hpp" #include "window/input.hpp" #include "window/Window.hpp" #include "world/Level.hpp" #include "Mainloop.hpp" #include "ServerMainloop.hpp" #include #include #include #include #include #include static debug::Logger logger("engine"); namespace fs = std::filesystem; static void create_channel(Engine* engine, std::string name, NumberSetting& setting) { if (name != "master") { audio::create_channel(name); } engine->keepAlive(setting.observe([=](auto value) { audio::get_channel(name)->setVolume(value*value); }, true)); } static std::unique_ptr load_icon(const fs::path& resdir) { try { auto file = resdir / fs::u8path("textures/misc/icon.png"); if (fs::exists(file)) { return imageio::read(file); } } catch (const std::exception& err) { logger.error() << "could not load window icon: " << err.what(); } return nullptr; } Engine::Engine(CoreParameters coreParameters) : params(std::move(coreParameters)), settings(), settingsHandler({settings}), interpreter(std::make_unique()), network(network::Network::create(settings.network)) { logger.info() << "engine version: " << ENGINE_VERSION_STRING; if (params.headless) { logger.info() << "headless mode is enabled"; } paths.setResourcesFolder(params.resFolder); paths.setUserFilesFolder(params.userFolder); paths.prepare(); if (!params.scriptFile.empty()) { paths.setScriptFolder(params.scriptFile.parent_path()); } loadSettings(); auto resdir = paths.getResourcesFolder(); controller = std::make_unique(*this); if (!params.headless) { if (Window::initialize(&settings.display)){ throw initialize_error("could not initialize window"); } time.set(Window::time()); if (auto icon = load_icon(resdir)) { icon->flipY(); Window::setIcon(icon.get()); } loadControls(); gui = std::make_unique(); if (ENGINE_DEBUG_BUILD) { menus::create_version_label(*this); } } audio::initialize(settings.audio.enabled.get() && !params.headless); create_channel(this, "master", settings.audio.volumeMaster); create_channel(this, "regular", settings.audio.volumeRegular); create_channel(this, "music", settings.audio.volumeMusic); create_channel(this, "ambient", settings.audio.volumeAmbient); create_channel(this, "ui", settings.audio.volumeUI); bool langNotSet = settings.ui.language.get() == "auto"; if (langNotSet) { settings.ui.language.set(langs::locale_by_envlocale( platform::detect_locale(), paths.getResourcesFolder() )); } scripting::initialize(this); if (!isHeadless()) { gui->setPageLoader(scripting::create_page_loader()); } keepAlive(settings.ui.language.observe([this](auto lang) { setLanguage(lang); }, true)); basePacks = files::read_list(resdir/fs::path("config/builtins.list")); } void Engine::loadSettings() { fs::path settings_file = paths.getSettingsFile(); if (fs::is_regular_file(settings_file)) { logger.info() << "loading settings"; std::string text = files::read_string(settings_file); try { toml::parse(settingsHandler, settings_file.string(), text); } catch (const parsing_error& err) { logger.error() << err.errorLog(); throw; } } } void Engine::loadControls() { fs::path controls_file = paths.getControlsFile(); if (fs::is_regular_file(controls_file)) { logger.info() << "loading controls"; std::string text = files::read_string(controls_file); Events::loadBindings(controls_file.u8string(), text, BindType::BIND); } } void Engine::onAssetsLoaded() { assets->setup(); gui->onAssetsLoad(assets.get()); } void Engine::updateHotkeys() { if (Events::jpressed(keycode::F2)) { saveScreenshot(); } if (Events::jpressed(keycode::F11)) { settings.display.fullscreen.toggle(); } } void Engine::saveScreenshot() { auto image = Window::takeScreenshot(); image->flipY(); fs::path filename = paths.getNewScreenshotFile("png"); imageio::write(filename.string(), image.get()); logger.info() << "saved screenshot as " << filename.u8string(); } void Engine::run() { if (params.headless) { ServerMainloop(*this).run(); } else { Mainloop(*this).run(); } } void Engine::postUpdate() { network->update(); postRunnables.run(); scripting::process_post_runnables(); } void Engine::updateFrontend() { double delta = time.getDelta(); updateHotkeys(); audio::update(delta); gui->act(delta, Viewport(Window::width, Window::height)); screen->update(delta); gui->postAct(); } void Engine::nextFrame() { Window::setFramerate( Window::isIconified() && settings.display.limitFpsIconified.get() ? 20 : settings.display.framerate.get() ); Window::swapBuffers(); Events::pollEvents(); } void Engine::renderFrame() { screen->draw(time.getDelta()); Viewport viewport(Window::width, Window::height); DrawContext ctx(nullptr, viewport, nullptr); gui->draw(ctx, *assets); } void Engine::saveSettings() { logger.info() << "saving settings"; files::write_string(paths.getSettingsFile(), toml::stringify(settingsHandler)); if (!params.headless) { logger.info() << "saving bindings"; files::write_string(paths.getControlsFile(), Events::writeBindings()); } } Engine::~Engine() { saveSettings(); logger.info() << "shutting down"; if (screen) { screen->onEngineShutdown(); screen.reset(); } content.reset(); assets.reset(); interpreter.reset(); if (gui) { gui.reset(); logger.info() << "gui finished"; } audio::close(); network.reset(); clearKeepedObjects(); scripting::close(); logger.info() << "scripting finished"; if (!params.headless) { Window::terminate(); logger.info() << "window closed"; } logger.info() << "engine finished"; } EngineController* Engine::getController() { return controller.get(); } cmd::CommandsInterpreter* Engine::getCommandsInterpreter() { return interpreter.get(); } PacksManager Engine::createPacksManager(const fs::path& worldFolder) { PacksManager manager; manager.setSources({ {"world:content", worldFolder.empty() ? worldFolder : worldFolder/fs::path("content")}, {"user:content", paths.getUserFilesFolder()/fs::path("content")}, {"res:content", paths.getResourcesFolder()/fs::path("content")} }); return manager; } void Engine::setLevelConsumer(OnWorldOpen levelConsumer) { this->levelConsumer = std::move(levelConsumer); } void Engine::loadAssets() { logger.info() << "loading assets"; Shader::preprocessor->setPaths(resPaths.get()); auto new_assets = std::make_unique(); AssetsLoader loader(new_assets.get(), resPaths.get()); AssetsLoader::addDefaults(loader, content.get()); // no need // correct log messages order is more useful bool threading = false; // look at two upper lines if (threading) { auto task = loader.startTask([=](){}); task->waitForEnd(); } else { while (loader.hasNext()) { loader.loadNext(); } } assets = std::move(new_assets); if (content == nullptr) { return; } for (auto& [name, def] : content->blocks.getDefs()) { if (def->model == BlockModel::custom && def->modelName.empty()) { assets->store( std::make_unique( ModelsGenerator::loadCustomBlockModel( def->customModelRaw, *assets, !def->shadeless ) ), name + ".model" ); def->modelName = def->name + ".model"; } } for (auto& [name, def] : content->items.getDefs()) { assets->store( std::make_unique( ModelsGenerator::generate(*def, *content, *assets) ), name + ".model" ); } } static void load_configs(const fs::path& root) { auto configFolder = root/fs::path("config"); auto bindsFile = configFolder/fs::path("bindings.toml"); if (fs::is_regular_file(bindsFile)) { Events::loadBindings( bindsFile.u8string(), files::read_string(bindsFile), BindType::BIND ); } } void Engine::loadContent() { scripting::cleanup(); auto resdir = paths.getResourcesFolder(); std::vector names; for (auto& pack : contentPacks) { names.push_back(pack.id); } ContentBuilder contentBuilder; corecontent::setup(paths, contentBuilder); paths.setContentPacks(&contentPacks); PacksManager manager = createPacksManager(paths.getCurrentWorldFolder()); manager.scan(); names = manager.assemble(names); contentPacks = manager.getAll(names); auto corePack = ContentPack::createCore(paths); // Setup filesystem entry points std::vector resRoots { {"core", corePack.folder} }; for (auto& pack : contentPacks) { resRoots.push_back({pack.id, pack.folder}); } resPaths = std::make_unique(resdir, resRoots); // Load content { ContentLoader(&corePack, contentBuilder, *resPaths).load(); load_configs(corePack.folder); } for (auto& pack : contentPacks) { ContentLoader(&pack, contentBuilder, *resPaths).load(); load_configs(pack.folder); } content = contentBuilder.build(); interpreter->reset(); scripting::on_content_load(content.get()); ContentLoader::loadScripts(*content); langs::setup(resdir, langs::current->getId(), contentPacks); if (!isHeadless()) { loadAssets(); onAssetsLoaded(); } } void Engine::resetContent() { scripting::cleanup(); auto resdir = paths.getResourcesFolder(); std::vector resRoots; { auto pack = ContentPack::createCore(paths); resRoots.push_back({"core", pack.folder}); load_configs(pack.folder); } auto manager = createPacksManager(fs::path()); manager.scan(); for (const auto& pack : manager.getAll(basePacks)) { resRoots.push_back({pack.id, pack.folder}); } resPaths = std::make_unique(resdir, resRoots); contentPacks.clear(); content.reset(); langs::setup(resdir, langs::current->getId(), contentPacks); if (!isHeadless()) { loadAssets(); onAssetsLoaded(); } contentPacks = manager.getAll(basePacks); } void Engine::loadWorldContent(const fs::path& folder) { contentPacks.clear(); auto packNames = ContentPack::worldPacksList(folder); PacksManager manager; manager.setSources( {{"world:content", folder.empty() ? folder : folder / fs::path("content")}, {"user:content", paths.getUserFilesFolder() / fs::path("content")}, {"res:content", paths.getResourcesFolder() / fs::path("content")}} ); manager.scan(); contentPacks = manager.getAll(manager.assemble(packNames)); paths.setCurrentWorldFolder(folder); loadContent(); } void Engine::loadAllPacks() { PacksManager manager = createPacksManager(paths.getCurrentWorldFolder()); manager.scan(); auto allnames = manager.getAllNames(); contentPacks = manager.getAll(manager.assemble(allnames)); } void Engine::setScreen(std::shared_ptr screen) { // reset audio channels (stop all sources) audio::reset_channel(audio::get_channel_index("regular")); audio::reset_channel(audio::get_channel_index("ambient")); this->screen = std::move(screen); } void Engine::setLanguage(std::string locale) { langs::setup(paths.getResourcesFolder(), std::move(locale), contentPacks); } void Engine::onWorldOpen(std::unique_ptr level, int64_t localPlayer) { logger.info() << "world open"; levelConsumer(std::move(level), localPlayer); } void Engine::onWorldClosed() { logger.info() << "world closed"; levelConsumer(nullptr, -1); } void Engine::quit() { quitSignal = true; if (!isHeadless()) { Window::setShouldClose(true); } } bool Engine::isQuitSignal() const { return quitSignal; } gui::GUI* Engine::getGUI() { return gui.get(); } EngineSettings& Engine::getSettings() { return settings; } Assets* Engine::getAssets() { return assets.get(); } const Content* Engine::getContent() const { return content.get(); } std::vector Engine::getAllContentPacks() { auto packs = getContentPacks(); packs.insert(packs.begin(), ContentPack::createCore(paths)); return packs; } std::vector& Engine::getContentPacks() { return contentPacks; } std::vector& Engine::getBasePacks() { return basePacks; } EnginePaths& Engine::getPaths() { return paths; } ResPaths* Engine::getResPaths() { return resPaths.get(); } std::shared_ptr Engine::getScreen() { return screen; } SettingsHandler& Engine::getSettingsHandler() { return settingsHandler; } network::Network& Engine::getNetwork() { return *network; } Time& Engine::getTime() { return time; } const CoreParameters& Engine::getCoreParameters() const { return params; } bool Engine::isHeadless() const { return params.headless; }