diff --git a/res/layouts/inventory.xml b/res/layouts/inventory.xml new file mode 100644 index 00000000..c33d57d6 --- /dev/null +++ b/res/layouts/inventory.xml @@ -0,0 +1,3 @@ + + + diff --git a/res/layouts/inventory.xml.lua b/res/layouts/inventory.xml.lua new file mode 100644 index 00000000..532b9ee5 --- /dev/null +++ b/res/layouts/inventory.xml.lua @@ -0,0 +1,11 @@ +function on_open(inv) + print("OPEN", inv) +end + +function on_close(inv) + print("CLOSE", inv) +end + +function inventory_share_func(invid, slotid) + inventory.set(invid, slotid, 0, 0) +end diff --git a/res/modules/toml.lua b/res/modules/toml.lua new file mode 100644 index 00000000..fb082e75 --- /dev/null +++ b/res/modules/toml.lua @@ -0,0 +1,65 @@ +-- TOML serialization module +local toml = {} + +-- Convert table to TOML +function toml.serialize(tb, isinner) + local text = "" + for k, v in pairs(tb) do + local tp = type(v) + if tp ~= "table" then + text = text..k.." = " + if tp == "string" then + text = text..string.format("%q", v) + else + text = text..tostring(v) + end + text = text.."\n" + end + end + for k, v in pairs(tb) do + local tp = type(v) + if tp == "table" then + if isinner then + error("only one level of subtables supported") + end + text = text.."["..k.."]\n"..toml.serialize(v).."\n" + end + end + return text +end + +-- Parse TOML to new table +function toml.deserialize(s) + local output = {} + local current = output + local lines = {} + for line in string.gmatch(s, "[^\r\n]+") do + line = string.gsub(line, "%s+", "") + table.insert(lines, line) + end + for i = 1,#lines do + local s = lines[i] + if string.sub(s, 1, 1) == "[" then + local section = s.sub(s, 2, #s-1) + current = {} + output[section] = current + else + for k, v in string.gmatch(s, "(%w+)=(.+)" ) do + v = string.gsub(v, "%s+", "") + if v.sub(v, 1, 1) == "\"" then + current[k] = v.sub(v, 2, #v-1) + elseif v == "true" or v == "false" then + current[k] = v == "true" + end + + local num = tonumber(v) + if num ~= nil then + current[k] = num + end + end + end + end + return output +end + +return toml diff --git a/res/scripts/stdlib.lua b/res/scripts/stdlib.lua index ace88536..09d28f55 100644 --- a/res/scripts/stdlib.lua +++ b/res/scripts/stdlib.lua @@ -37,10 +37,14 @@ function load_script(path, nocache) if not nocache and __cached_scripts[fullpath] ~= nil then return __cached_results[fullpath] end - local script = loadfile(fullpath) - if script == nil then + if not file.isfile(fullpath) then error("script '"..filename.."' not found in '"..packname.."'") end + + local script, err = loadfile(fullpath) + if script == nil then + error(err) + end local result = script() if not nocache then __cached_scripts[fullpath] = script @@ -49,6 +53,16 @@ function load_script(path, nocache) return result end +function require(path) + local prefix, file = parse_path(path) + return load_script(prefix..":modules/"..file..".lua") +end + +function __reset_scripts_cache() + __cached_scripts = {} + __cached_results = {} +end + function sleep(timesec) local start = time.uptime() while time.uptime() - start < timesec do @@ -73,3 +87,7 @@ function dofile(path) end return _dofile(path) end + +function pack.is_installed(packid) + return file.isfile(packid..":package.json") +end diff --git a/res/scripts/world.lua b/res/scripts/world.lua index e69de29b..964100e7 100644 --- a/res/scripts/world.lua +++ b/res/scripts/world.lua @@ -0,0 +1,3 @@ +-- use for engine development tests +-- must be empty in release +-- must not be modified by content-packs diff --git a/res/shaders/main.glslv b/res/shaders/main.glslv index f368328d..e59839a0 100644 --- a/res/shaders/main.glslv +++ b/res/shaders/main.glslv @@ -26,6 +26,7 @@ uniform float u_torchlightDistance; void main(){ vec3 pos3d = (u_model * vec4(v_position, 1.0)).xyz-u_cameraPos.xyz; vec4 modelpos = u_model * vec4(v_position, 1.0); + modelpos.y -= pow(length(pos3d.xz)*0.002, 3.0); vec4 viewmodelpos = u_view * modelpos; vec4 decomp_light = decompress_light(v_light); vec3 light = decomp_light.rgb; diff --git a/res/texts/fi_FI.txt b/res/texts/fi_FI.txt index 4da41a1c..124a2956 100644 --- a/res/texts/fi_FI.txt +++ b/res/texts/fi_FI.txt @@ -13,8 +13,8 @@ menu.New World = Uusi Maailma menu.Quit=Poistu menu.Continue=Jatka menu.Save and Quit to Menu=Tallenna ja poistu valikkoon -menu.missing-content=Puuttuu jotkut lisäosat! -menu.Content Error=Sisältövirhe! +menu.missing-content=Puuttuu lisäosia! +menu.Content Error=Lisäosa virhe! menu.Controls=Ohjaus menu.Back to Main Menu=Takaisin Valikoon menu.Settings=Asetukset diff --git a/res/textures/gui/error.png b/res/textures/gui/error.png new file mode 100644 index 00000000..5651ac6c Binary files /dev/null and b/res/textures/gui/error.png differ diff --git a/res/textures/gui/warning.png b/res/textures/gui/warning.png new file mode 100644 index 00000000..7dbbd087 Binary files /dev/null and b/res/textures/gui/warning.png differ diff --git a/src/assets/Assets.cpp b/src/assets/Assets.cpp index c13f3da0..5183cca2 100644 --- a/src/assets/Assets.cpp +++ b/src/assets/Assets.cpp @@ -4,6 +4,8 @@ #include "../graphics/Shader.h" #include "../graphics/Atlas.h" #include "../graphics/Font.h" +#include "../frontend/UiDocument.h" +#include "../logic/scripting/scripting.h" Assets::~Assets() { } @@ -62,6 +64,17 @@ void Assets::store(const TextureAnimation& animation) { animations.emplace_back(animation); } +UiDocument* Assets::getLayout(std::string name) const { + auto found = layouts.find(name); + if (found == layouts.end()) + return nullptr; + return found->second.get(); +} + +void Assets::store(UiDocument* layout, std::string name) { + layouts[name].reset(layout); +} + void Assets::extend(const Assets& assets) { for (auto entry : assets.textures) { textures[entry.first] = entry.second; @@ -75,6 +88,9 @@ void Assets::extend(const Assets& assets) { for (auto entry : assets.atlases) { atlases[entry.first] = entry.second; } + for (auto entry : assets.layouts) { + layouts[entry.first] = entry.second; + } animations.clear(); for (auto entry : assets.animations) { animations.emplace_back(entry); diff --git a/src/assets/Assets.h b/src/assets/Assets.h index 6df72ac4..87e94380 100644 --- a/src/assets/Assets.h +++ b/src/assets/Assets.h @@ -12,12 +12,20 @@ class Texture; class Shader; class Font; class Atlas; +class UiDocument; + +struct LayoutCfg { + int env; + + LayoutCfg(int env) : env(env) {} +}; class Assets { std::unordered_map> textures; std::unordered_map> shaders; std::unordered_map> fonts; std::unordered_map> atlases; + std::unordered_map> layouts; std::vector animations; public: ~Assets(); @@ -36,6 +44,9 @@ public: const std::vector& getAnimations(); void store(const TextureAnimation& animation); + UiDocument* getLayout(std::string name) const; + void store(UiDocument* layout, std::string name); + void extend(const Assets& assets); }; diff --git a/src/assets/AssetsLoader.cpp b/src/assets/AssetsLoader.cpp index 00ec8e0a..c03ebf4b 100644 --- a/src/assets/AssetsLoader.cpp +++ b/src/assets/AssetsLoader.cpp @@ -8,20 +8,24 @@ #include "../constants.h" #include "../files/engine_paths.h" - -using std::filesystem::path; -using std::unique_ptr; +#include "../content/Content.h" +#include "../logic/scripting/scripting.h" AssetsLoader::AssetsLoader(Assets* assets, const ResPaths* paths) : assets(assets), paths(paths) { + addLoader(ASSET_SHADER, assetload::shader); + addLoader(ASSET_TEXTURE, assetload::texture); + addLoader(ASSET_FONT, assetload::font); + addLoader(ASSET_ATLAS, assetload::atlas); + addLoader(ASSET_LAYOUT, assetload::layout); } void AssetsLoader::addLoader(int tag, aloader_func func) { loaders[tag] = func; } -void AssetsLoader::add(int tag, const std::string filename, const std::string alias) { - entries.push(aloader_entry{ tag, filename, alias }); +void AssetsLoader::add(int tag, const std::string filename, const std::string alias, std::shared_ptr settings) { + entries.push(aloader_entry{ tag, filename, alias, settings}); } bool AssetsLoader::hasNext() const { @@ -38,19 +42,25 @@ bool AssetsLoader::loadNext() { return false; } aloader_func loader = found->second; - bool status = loader(assets, paths, entry.filename, entry.alias); + bool status = loader(*this, assets, paths, entry.filename, entry.alias, entry.config); entries.pop(); return status; } -void AssetsLoader::createDefaults(AssetsLoader& loader) { - loader.addLoader(ASSET_SHADER, assetload::shader); - loader.addLoader(ASSET_TEXTURE, assetload::texture); - loader.addLoader(ASSET_FONT, assetload::font); - loader.addLoader(ASSET_ATLAS, assetload::atlas); +void addLayouts(int env, const std::string& prefix, const fs::path& folder, AssetsLoader& loader) { + if (!fs::is_directory(folder)) { + return; + } + for (auto& entry : fs::directory_iterator(folder)) { + const fs::path file = entry.path(); + if (file.extension().u8string() != ".xml") + continue; + std::string name = prefix+":"+file.stem().u8string(); + loader.add(ASSET_LAYOUT, file.u8string(), name, std::make_shared(env)); + } } -void AssetsLoader::addDefaults(AssetsLoader& loader, bool world) { +void AssetsLoader::addDefaults(AssetsLoader& loader, const Content* content) { loader.add(ASSET_FONT, FONTS_FOLDER"/font", "normal"); loader.add(ASSET_SHADER, SHADERS_FOLDER"/ui", "ui"); loader.add(ASSET_SHADER, SHADERS_FOLDER"/main", "main"); @@ -58,12 +68,21 @@ void AssetsLoader::addDefaults(AssetsLoader& loader, bool world) { loader.add(ASSET_TEXTURE, TEXTURES_FOLDER"/gui/menubg.png", "gui/menubg"); loader.add(ASSET_TEXTURE, TEXTURES_FOLDER"/gui/delete_icon.png", "gui/delete_icon"); loader.add(ASSET_TEXTURE, TEXTURES_FOLDER"/gui/no_icon.png", "gui/no_icon"); - if (world) { + loader.add(ASSET_TEXTURE, TEXTURES_FOLDER"/gui/warning.png", "gui/warning"); + loader.add(ASSET_TEXTURE, TEXTURES_FOLDER"/gui/error.png", "gui/error"); + if (content) { loader.add(ASSET_SHADER, SHADERS_FOLDER"/ui3d", "ui3d"); loader.add(ASSET_SHADER, SHADERS_FOLDER"/background", "background"); loader.add(ASSET_SHADER, SHADERS_FOLDER"/skybox_gen", "skybox_gen"); loader.add(ASSET_TEXTURE, TEXTURES_FOLDER"/misc/moon.png", "misc/moon"); loader.add(ASSET_TEXTURE, TEXTURES_FOLDER"/misc/sun.png", "misc/sun"); + + addLayouts(0, "core", loader.getPaths()->getMainRoot()/fs::path("layouts"), loader); + for (auto& pack : content->getPacks()) { + auto& info = pack->getInfo(); + fs::path folder = info.folder / fs::path("layouts"); + addLayouts(pack->getEnvironment()->getId(), info.id, folder, loader); + } } loader.add(ASSET_ATLAS, TEXTURES_FOLDER"/blocks", "blocks"); loader.add(ASSET_ATLAS, TEXTURES_FOLDER"/items", "items"); @@ -71,4 +90,4 @@ void AssetsLoader::addDefaults(AssetsLoader& loader, bool world) { const ResPaths* AssetsLoader::getPaths() const { return paths; -} \ No newline at end of file +} diff --git a/src/assets/AssetsLoader.h b/src/assets/AssetsLoader.h index 61a88b30..de401846 100644 --- a/src/assets/AssetsLoader.h +++ b/src/assets/AssetsLoader.h @@ -2,6 +2,7 @@ #define ASSETS_ASSETS_LOADER_H #include +#include #include #include #include @@ -10,16 +11,20 @@ const short ASSET_TEXTURE = 1; const short ASSET_SHADER = 2; const short ASSET_FONT = 3; const short ASSET_ATLAS = 4; +const short ASSET_LAYOUT = 5; class ResPaths; class Assets; +class AssetsLoader; +class Content; -typedef std::function aloader_func; +using aloader_func = std::function)>; struct aloader_entry { int tag; const std::string filename; const std::string alias; + std::shared_ptr config; }; class AssetsLoader { @@ -30,13 +35,18 @@ class AssetsLoader { public: AssetsLoader(Assets* assets, const ResPaths* paths); void addLoader(int tag, aloader_func func); - void add(int tag, const std::string filename, const std::string alias); + void add( + int tag, + const std::string filename, + const std::string alias, + std::shared_ptr settings=nullptr + ); + bool hasNext() const; bool loadNext(); - static void createDefaults(AssetsLoader& loader); - static void addDefaults(AssetsLoader& loader, bool world); + static void addDefaults(AssetsLoader& loader, const Content* content); const ResPaths* getPaths() const; }; diff --git a/src/assets/assetload_funcs.cpp b/src/assets/assetload_funcs.cpp index 8336f5a8..eeff84e0 100644 --- a/src/assets/assetload_funcs.cpp +++ b/src/assets/assetload_funcs.cpp @@ -3,6 +3,7 @@ #include #include #include "Assets.h" +#include "AssetsLoader.h" #include "../files/files.h" #include "../files/engine_paths.h" #include "../coders/png.h" @@ -13,194 +14,233 @@ #include "../graphics/Atlas.h" #include "../graphics/Font.h" #include "../graphics/TextureAnimation.h" +#include "../frontend/UiDocument.h" +#include "../logic/scripting/scripting.h" namespace fs = std::filesystem; -bool assetload::texture(Assets* assets, - const ResPaths* paths, - const std::string filename, - const std::string name) { - std::unique_ptr texture( - png::load_texture(paths->find(filename).string()) +bool assetload::texture( + AssetsLoader&, + Assets* assets, + const ResPaths* paths, + const std::string filename, + const std::string name, + std::shared_ptr +) { + std::unique_ptr texture( + png::load_texture(paths->find(filename).u8string()) ); - if (texture == nullptr) { - std::cerr << "failed to load texture '" << name << "'" << std::endl; - return false; - } - assets->store(texture.release(), name); - return true; + if (texture == nullptr) { + std::cerr << "failed to load texture '" << name << "'" << std::endl; + return false; + } + assets->store(texture.release(), name); + return true; } -bool assetload::shader(Assets* assets, - const ResPaths* paths, - const std::string filename, - const std::string name) { - fs::path vertexFile = paths->find(filename+".glslv"); - fs::path fragmentFile = paths->find(filename+".glslf"); +bool assetload::shader( + AssetsLoader&, + Assets* assets, + const ResPaths* paths, + const std::string filename, + const std::string name, + std::shared_ptr +) { + fs::path vertexFile = paths->find(filename+".glslv"); + fs::path fragmentFile = paths->find(filename+".glslf"); - std::string vertexSource = files::read_string(vertexFile); - std::string fragmentSource = files::read_string(fragmentFile); + std::string vertexSource = files::read_string(vertexFile); + std::string fragmentSource = files::read_string(fragmentFile); - Shader* shader = Shader::loadShader( - vertexFile.string(), - fragmentFile.string(), - vertexSource, fragmentSource); + Shader* shader = Shader::loadShader( + vertexFile.string(), + fragmentFile.string(), + vertexSource, fragmentSource); - if (shader == nullptr) { - std::cerr << "failed to load shader '" << name << "'" << std::endl; - return false; - } - assets->store(shader, name); - return true; + if (shader == nullptr) { + std::cerr << "failed to load shader '" << name << "'" << std::endl; + return false; + } + assets->store(shader, name); + return true; } static bool appendAtlas(AtlasBuilder& atlas, const fs::path& file) { - // png is only supported format - if (file.extension() != ".png") - return false; - std::string name = file.stem().string(); - // skip duplicates - if (atlas.has(name)) { - return false; - } - std::unique_ptr image(png::load_image(file.string())); - if (image == nullptr) { - std::cerr << "could not to load " << file.string() << std::endl; - return false; - } - image->fixAlphaColor(); - atlas.add(name, image.release()); + // png is only supported format + if (file.extension() != ".png") + return false; + std::string name = file.stem().string(); + // skip duplicates + if (atlas.has(name)) { + return false; + } + std::unique_ptr image(png::load_image(file.string())); + if (image == nullptr) { + std::cerr << "could not to load " << file.string() << std::endl; + return false; + } + image->fixAlphaColor(); + atlas.add(name, image.release()); - return true; + return true; } -bool assetload::atlas(Assets* assets, - const ResPaths* paths, - const std::string directory, - const std::string name) { - AtlasBuilder builder; - for (const auto& file : paths->listdir(directory)) { - if (!appendAtlas(builder, file)) continue; - } - Atlas* atlas = builder.build(2); - assets->store(atlas, name); - for (const auto& file : builder.getNames()) { - assetload::animation(assets, paths, "textures", file, atlas); - } - return true; +bool assetload::atlas( + AssetsLoader&, + Assets* assets, + const ResPaths* paths, + const std::string directory, + const std::string name, + std::shared_ptr +) { + AtlasBuilder builder; + for (const auto& file : paths->listdir(directory)) { + if (!appendAtlas(builder, file)) continue; + } + Atlas* atlas = builder.build(2); + assets->store(atlas, name); + for (const auto& file : builder.getNames()) { + assetload::animation(assets, paths, "textures", file, atlas); + } + return true; } -bool assetload::font(Assets* assets, - const ResPaths* paths, - const std::string filename, - const std::string name) { - std::vector> pages; - for (size_t i = 0; i <= 4; i++) { +bool assetload::font( + AssetsLoader&, + Assets* assets, + const ResPaths* paths, + const std::string filename, + const std::string name, + std::shared_ptr +) { + std::vector> pages; + for (size_t i = 0; i <= 4; i++) { std::string name = filename + "_" + std::to_string(i) + ".png"; name = paths->find(name).string(); - std::unique_ptr texture (png::load_texture(name)); - if (texture == nullptr) { - std::cerr << "failed to load bitmap font '" << name; + std::unique_ptr texture (png::load_texture(name)); + if (texture == nullptr) { + std::cerr << "failed to load bitmap font '" << name; std::cerr << "' (missing page " << std::to_string(i) << ")"; std::cerr << std::endl; - return false; - } - pages.push_back(std::move(texture)); - } + return false; + } + pages.push_back(std::move(texture)); + } int res = pages[0]->height / 16; - assets->store(new Font(std::move(pages), res, 4), name); - return true; + assets->store(new Font(std::move(pages), res, 4), name); + return true; +} + +bool assetload::layout( + AssetsLoader& loader, + Assets* assets, + const ResPaths* paths, + const std::string file, + const std::string name, + std::shared_ptr config +) { + try { + LayoutCfg* cfg = reinterpret_cast(config.get()); + auto document = UiDocument::read(loader, cfg->env, name, file); + assets->store(document.release(), name); + return true; + } catch (const parsing_error& err) { + std::cerr << "failed to parse layout XML '" << file << "'" << std::endl; + std::cerr << err.errorLog() << std::endl; + return false; + } } bool assetload::animation(Assets* assets, - const ResPaths* paths, - const std::string directory, - const std::string name, - Atlas* dstAtlas) { - std::string animsDir = directory + "/animations"; - std::string blocksDir = directory + "/blocks"; + const ResPaths* paths, + const std::string directory, + const std::string name, + Atlas* dstAtlas) { + std::string animsDir = directory + "/animations"; + std::string blocksDir = directory + "/blocks"; - for (const auto& folder : paths->listdir(animsDir)) { - if (!fs::is_directory(folder)) continue; - if (folder.filename().string() != name) continue; - if (fs::is_empty(folder)) continue; - - AtlasBuilder builder; + for (const auto& folder : paths->listdir(animsDir)) { + if (!fs::is_directory(folder)) continue; + if (folder.filename().string() != name) continue; + if (fs::is_empty(folder)) continue; + + AtlasBuilder builder; appendAtlas(builder, paths->find(blocksDir + "/" + name + ".png")); - std::string animFile = folder.string() + "/animation.json"; + std::string animFile = folder.string() + "/animation.json"; - std::vector> frameList; + std::vector> frameList; - if (fs::exists(animFile)) { - auto root = files::read_json(animFile); + if (fs::exists(animFile)) { + auto root = files::read_json(animFile); - auto frameArr = root->list("frames"); + auto frameArr = root->list("frames"); - float frameDuration = DEFAULT_FRAME_DURATION; - std::string frameName; + float frameDuration = DEFAULT_FRAME_DURATION; + std::string frameName; - if (frameArr) { - for (size_t i = 0; i < frameArr->size(); i++) { - auto currentFrame = frameArr->list(i); + if (frameArr) { + for (size_t i = 0; i < frameArr->size(); i++) { + auto currentFrame = frameArr->list(i); - frameName = currentFrame->str(0); - if (currentFrame->size() > 1) frameDuration = static_cast(currentFrame->integer(1)) / 1000; + frameName = currentFrame->str(0); + if (currentFrame->size() > 1) + frameDuration = static_cast(currentFrame->integer(1)) / 1000; - frameList.emplace_back(frameName, frameDuration); - } - } - } - for (const auto& file : paths->listdir(animsDir + "/" + name)) { - if (!frameList.empty()) { - bool contains = false; - for (const auto& elem : frameList) { - if (file.stem() == elem.first) { - contains = true; - break; - } - } - if (!contains) continue; - } - if (!appendAtlas(builder, file)) continue; - } + frameList.emplace_back(frameName, frameDuration); + } + } + } + for (const auto& file : paths->listdir(animsDir + "/" + name)) { + if (!frameList.empty()) { + bool contains = false; + for (const auto& elem : frameList) { + if (file.stem() == elem.first) { + contains = true; + break; + } + } + if (!contains) continue; + } + if (!appendAtlas(builder, file)) continue; + } - std::unique_ptr srcAtlas (builder.build(2)); + std::unique_ptr srcAtlas (builder.build(2)); - Texture* srcTex = srcAtlas->getTexture(); - Texture* dstTex = dstAtlas->getTexture(); + Texture* srcTex = srcAtlas->getTexture(); + Texture* dstTex = dstAtlas->getTexture(); - TextureAnimation animation(srcTex, dstTex); - Frame frame; - UVRegion region = dstAtlas->get(name); + TextureAnimation animation(srcTex, dstTex); + Frame frame; + UVRegion region = dstAtlas->get(name); - frame.dstPos = glm::ivec2(region.u1 * dstTex->width, region.v1 * dstTex->height); - frame.size = glm::ivec2(region.u2 * dstTex->width, region.v2 * dstTex->height) - frame.dstPos; + frame.dstPos = glm::ivec2(region.u1 * dstTex->width, region.v1 * dstTex->height); + frame.size = glm::ivec2(region.u2 * dstTex->width, region.v2 * dstTex->height) - frame.dstPos; - if (frameList.empty()) { - for (const auto& elem : builder.getNames()) { - region = srcAtlas->get(elem); - frame.srcPos = glm::ivec2(region.u1 * srcTex->width, srcTex->height - region.v2 * srcTex->height); - animation.addFrame(frame); - } - } - else { - for (const auto& elem : frameList) { - if (!srcAtlas->has(elem.first)) { - std::cerr << "Unknown frame name: " << elem.first << std::endl; - continue; - } - region = srcAtlas->get(elem.first); - frame.duration = elem.second; - frame.srcPos = glm::ivec2(region.u1 * srcTex->width, srcTex->height - region.v2 * srcTex->height); - animation.addFrame(frame); - } - } + if (frameList.empty()) { + for (const auto& elem : builder.getNames()) { + region = srcAtlas->get(elem); + frame.srcPos = glm::ivec2(region.u1 * srcTex->width, srcTex->height - region.v2 * srcTex->height); + animation.addFrame(frame); + } + } + else { + for (const auto& elem : frameList) { + if (!srcAtlas->has(elem.first)) { + std::cerr << "Unknown frame name: " << elem.first << std::endl; + continue; + } + region = srcAtlas->get(elem.first); + frame.duration = elem.second; + frame.srcPos = glm::ivec2(region.u1 * srcTex->width, srcTex->height - region.v2 * srcTex->height); + animation.addFrame(frame); + } + } - assets->store(srcAtlas.release(), name + "_animation"); - assets->store(animation); + assets->store(srcAtlas.release(), name + "_animation"); + assets->store(animation); - return true; - } - return true; + return true; + } + return true; } diff --git a/src/assets/assetload_funcs.h b/src/assets/assetload_funcs.h index 67da528c..a9ef5d26 100644 --- a/src/assets/assetload_funcs.h +++ b/src/assets/assetload_funcs.h @@ -2,33 +2,62 @@ #define ASSETS_ASSET_LOADERS_H_ #include +#include class ResPaths; class Assets; +class AssetsLoader; class Atlas; namespace assetload { - bool texture(Assets* assets, - const ResPaths* paths, - const std::string filename, - const std::string name); - bool shader(Assets* assets, - const ResPaths* paths, - const std::string filename, - const std::string name); - bool atlas(Assets* assets, - const ResPaths* paths, - const std::string directory, - const std::string name); - bool font(Assets* assets, - const ResPaths* paths, - const std::string filename, - const std::string name); - bool animation(Assets* assets, - const ResPaths* paths, - const std::string directory, - const std::string name, - Atlas* dstAtlas); + bool texture( + AssetsLoader&, + Assets*, + const ResPaths* paths, + const std::string filename, + const std::string name, + std::shared_ptr settings + ); + bool shader( + AssetsLoader&, + Assets*, + const ResPaths* paths, + const std::string filename, + const std::string name, + std::shared_ptr settings + ); + bool atlas( + AssetsLoader&, + Assets*, + const ResPaths* paths, + const std::string directory, + const std::string name, + std::shared_ptr settings + ); + bool font( + AssetsLoader&, + Assets*, + const ResPaths* paths, + const std::string filename, + const std::string name, + std::shared_ptr settings + ); + bool layout( + AssetsLoader&, + Assets*, + const ResPaths* paths, + const std::string file, + const std::string name, + std::shared_ptr settings + ); + + bool animation( + Assets*, + const ResPaths* paths, + const std::string directory, + const std::string name, + Atlas* dstAtlas + ); } #endif // ASSETS_ASSET_LOADERS_H_ \ No newline at end of file diff --git a/src/coders/byte_utils.cpp b/src/coders/byte_utils.cpp index 5b9876a7..ce52a156 100644 --- a/src/coders/byte_utils.cpp +++ b/src/coders/byte_utils.cpp @@ -200,3 +200,11 @@ std::string ByteReader::getString() { bool ByteReader::hasNext() const { return pos < size; } + +const ubyte* ByteReader::pointer() const { + return data + pos; +} + +void ByteReader::skip(size_t n) { + pos += n; +} diff --git a/src/coders/byte_utils.h b/src/coders/byte_utils.h index 7bd2846d..4d651892 100644 --- a/src/coders/byte_utils.h +++ b/src/coders/byte_utils.h @@ -71,6 +71,9 @@ public: const char* getCString(); std::string getString(); bool hasNext() const; + + const ubyte* pointer() const; + void skip(size_t n); }; #endif // CODERS_BYTE_UTILS_H_ diff --git a/src/coders/xml.cpp b/src/coders/xml.cpp index aeb08dd1..03b4b3cd 100644 --- a/src/coders/xml.cpp +++ b/src/coders/xml.cpp @@ -32,6 +32,77 @@ bool Attribute::asBool() const { return text == "true" || text == "1"; } +/* Read 2d vector formatted `x,y`*/ +glm::vec2 Attribute::asVec2() const { + size_t pos = text.find(','); + if (pos == std::string::npos) { + throw std::runtime_error("invalid vec2 value "+escape_string(text)); + } + return glm::vec2( + util::parse_double(text, 0, pos), + util::parse_double(text, pos+1, text.length()-pos-1) + ); +} + +/* Read 3d vector formatted `x,y,z`*/ +glm::vec3 Attribute::asVec3() const { + size_t pos1 = text.find(','); + if (pos1 == std::string::npos) { + throw std::runtime_error("invalid vec3 value "+escape_string(text)); + } + size_t pos2 = text.find(',', pos1+1); + if (pos2 == std::string::npos) { + throw std::runtime_error("invalid vec3 value "+escape_string(text)); + } + return glm::vec3( + util::parse_double(text, 0, pos1), + util::parse_double(text, pos1+1, pos2), + util::parse_double(text, pos2+1, text.length()-pos2-1) + ); +} + +/* Read 4d vector formatted `x,y,z,w`*/ +glm::vec4 Attribute::asVec4() const { + size_t pos1 = text.find(','); + if (pos1 == std::string::npos) { + throw std::runtime_error("invalid vec4 value "+escape_string(text)); + } + size_t pos2 = text.find(',', pos1+1); + if (pos2 == std::string::npos) { + throw std::runtime_error("invalid vec4 value "+escape_string(text)); + } + size_t pos3 = text.find(',', pos2+1); + if (pos3 == std::string::npos) { + throw std::runtime_error("invalid vec4 value "+escape_string(text)); + } + return glm::vec4( + util::parse_double(text, 0, pos1), + util::parse_double(text, pos1+1, pos2-pos1-1), + util::parse_double(text, pos2+1, pos3-pos2-1), + util::parse_double(text, pos3+1, text.length()-pos3-1) + ); +} + +/* Read RGBA color. Supported formats: + - "#RRGGBB" or "#RRGGBBAA" hex color */ +glm::vec4 Attribute::asColor() const { + if (text[0] == '#') { + if (text.length() != 7 && text.length() != 9) { + throw std::runtime_error("#RRGGBB or #RRGGBBAA required"); + } + int a = 255; + int r = (hexchar2int(text[1]) << 4) | hexchar2int(text[2]); + int g = (hexchar2int(text[3]) << 4) | hexchar2int(text[4]); + int b = (hexchar2int(text[5]) << 4) | hexchar2int(text[6]); + if (text.length() == 9) { + a = (hexchar2int(text[7]) << 4) | hexchar2int(text[8]); + } + return glm::vec4(r / 255.f, g / 255.f, b / 255.f, a / 255.f); + } else { + throw std::runtime_error("hex colors are only supported"); + } +} + Node::Node(std::string tag) : tag(tag) { } diff --git a/src/coders/xml.h b/src/coders/xml.h index 275bd279..824b8209 100644 --- a/src/coders/xml.h +++ b/src/coders/xml.h @@ -4,6 +4,7 @@ #include #include #include +#include #include #include "commons.h" @@ -30,6 +31,10 @@ namespace xml { int64_t asInt() const; double asFloat() const; bool asBool() const; + glm::vec2 asVec2() const; + glm::vec3 asVec3() const; + glm::vec4 asVec4() const; + glm::vec4 asColor() const; }; /* XML element class. Text element has tag 'text' and attribute 'text' */ diff --git a/src/constants.h b/src/constants.h index 05cc1a21..56f54c7f 100644 --- a/src/constants.h +++ b/src/constants.h @@ -36,5 +36,6 @@ constexpr uint vox_index(uint x, uint y, uint z, uint w=CHUNK_W, uint d=CHUNK_D) #define SHADERS_FOLDER "shaders" #define TEXTURES_FOLDER "textures" #define FONTS_FOLDER "fonts" +#define LAYOUTS_FOLDER "layouts" #endif // SRC_CONSTANTS_H_ diff --git a/src/content/Content.cpp b/src/content/Content.cpp index cdf03842..d208af2b 100644 --- a/src/content/Content.cpp +++ b/src/content/Content.cpp @@ -7,6 +7,9 @@ #include "../voxels/Block.h" #include "../items/ItemDef.h" +#include "ContentPack.h" +#include "../logic/scripting/scripting.h" + ContentBuilder::~ContentBuilder() { } @@ -22,7 +25,11 @@ void ContentBuilder::add(ItemDef* def) { itemIds.push_back(def->name); } -Block* ContentBuilder::createBlock(std::string id) { +void ContentBuilder::add(ContentPackRuntime* pack) { + packs.push_back(std::unique_ptr(pack)); +} + +Block& ContentBuilder::createBlock(std::string id) { auto found = blockDefs.find(id); if (found != blockDefs.end()) { //return found->second; @@ -30,20 +37,20 @@ Block* ContentBuilder::createBlock(std::string id) { } Block* block = new Block(id); add(block); - return block; + return *block; } -ItemDef* ContentBuilder::createItem(std::string id) { +ItemDef& ContentBuilder::createItem(std::string id) { auto found = itemDefs.find(id); if (found != itemDefs.end()) { if (found->second->generated) { - return found->second; + return *found->second; } throw namereuse_error("name "+id+" is already used", contenttype::item); } ItemDef* item = new ItemDef(id); add(item); - return item; + return *item; } void ContentBuilder::checkIdentifier(std::string id) { @@ -65,7 +72,7 @@ contenttype ContentBuilder::checkContentType(std::string id) { Content* ContentBuilder::build() { std::vector blockDefsIndices; - DrawGroups* groups = new DrawGroups; + auto groups = std::make_unique(); for (const std::string& name : blockIds) { Block* def = blockDefs[name]; @@ -100,15 +107,18 @@ Content* ContentBuilder::build() { } auto indices = new ContentIndices(blockDefsIndices, itemDefsIndices); - std::unique_ptr content (new Content(indices, groups, blockDefs, itemDefs)); - // Now, it's time to solve foreign keys + auto content = std::make_unique( + indices, std::move(groups), blockDefs, itemDefs, std::move(packs) + ); + + // Now, it's time to resolve foreign keys for (Block* def : blockDefsIndices) { - def->rt.pickingItem = content->requireItem(def->pickingItem)->rt.id; + def->rt.pickingItem = content->requireItem(def->pickingItem).rt.id; } for (ItemDef* def : itemDefsIndices) { - def->rt.placingBlock = content->requireBlock(def->placingBlock)->rt.id; + def->rt.placingBlock = content->requireBlock(def->placingBlock).rt.id; } return content.release(); @@ -121,17 +131,19 @@ ContentIndices::ContentIndices( itemDefs(itemDefs) { } -Content::Content(ContentIndices* indices, DrawGroups* drawGroups, +Content::Content(ContentIndices* indices, + std::unique_ptr drawGroups, std::unordered_map blockDefs, - std::unordered_map itemDefs) + std::unordered_map itemDefs, + std::vector> packs) : blockDefs(blockDefs), itemDefs(itemDefs), indices(indices), - drawGroups(drawGroups) { + packs(std::move(packs)), + drawGroups(std::move(drawGroups)) { } Content::~Content() { - delete drawGroups; } Block* Content::findBlock(std::string id) const { @@ -142,12 +154,12 @@ Block* Content::findBlock(std::string id) const { return found->second; } -Block* Content::requireBlock(std::string id) const { +Block& Content::requireBlock(std::string id) const { auto found = blockDefs.find(id); if (found == blockDefs.end()) { throw std::runtime_error("missing block "+id); } - return found->second; + return *found->second; } ItemDef* Content::findItem(std::string id) const { @@ -158,10 +170,14 @@ ItemDef* Content::findItem(std::string id) const { return found->second; } -ItemDef* Content::requireItem(std::string id) const { +ItemDef& Content::requireItem(std::string id) const { auto found = itemDefs.find(id); if (found == itemDefs.end()) { throw std::runtime_error("missing item "+id); } - return found->second; + return *found->second; +} + +const std::vector>& Content::getPacks() const { + return packs; } diff --git a/src/content/Content.h b/src/content/Content.h index 9df2a04d..fa0e9c53 100644 --- a/src/content/Content.h +++ b/src/content/Content.h @@ -14,6 +14,7 @@ using DrawGroups = std::set; class Block; class ItemDef; class Content; +class ContentPackRuntime; enum class contenttype { none, block, item @@ -46,14 +47,17 @@ class ContentBuilder { std::unordered_map itemDefs; std::vector itemIds; + + std::vector> packs; public: ~ContentBuilder(); void add(Block* def); void add(ItemDef* def); + void add(ContentPackRuntime* pack); - Block* createBlock(std::string id); - ItemDef* createItem(std::string id); + Block& createBlock(std::string id); + ItemDef& createItem(std::string id); void checkIdentifier(std::string id); contenttype checkContentType(std::string id); @@ -104,12 +108,15 @@ class Content { std::unordered_map blockDefs; std::unordered_map itemDefs; std::unique_ptr indices; + std::vector> packs; public: - DrawGroups* const drawGroups; + std::unique_ptr const drawGroups; - Content(ContentIndices* indices, DrawGroups* drawGroups, + Content(ContentIndices* indices, + std::unique_ptr drawGroups, std::unordered_map blockDefs, - std::unordered_map itemDefs); + std::unordered_map itemDefs, + std::vector> packs); ~Content(); inline ContentIndices* getIndices() const { @@ -117,10 +124,12 @@ public: } Block* findBlock(std::string id) const; - Block* requireBlock(std::string id) const; + Block& requireBlock(std::string id) const; ItemDef* findItem(std::string id) const; - ItemDef* requireItem(std::string id) const; + ItemDef& requireItem(std::string id) const; + + const std::vector>& getPacks() const; }; #endif // CONTENT_CONTENT_H_ \ No newline at end of file diff --git a/src/content/ContentLoader.cpp b/src/content/ContentLoader.cpp index 54f585f9..740ad1de 100644 --- a/src/content/ContentLoader.cpp +++ b/src/content/ContentLoader.cpp @@ -92,7 +92,7 @@ void ContentLoader::fixPackIndices() { } // TODO: add basic validation and logging -void ContentLoader::loadBlock(Block* def, std::string name, fs::path file) { +void ContentLoader::loadBlock(Block& def, std::string name, fs::path file) { auto root = files::read_json(file); // block texturing @@ -100,22 +100,22 @@ void ContentLoader::loadBlock(Block* def, std::string name, fs::path file) { std::string texture; root->str("texture", texture); for (uint i = 0; i < 6; i++) { - def->textureFaces[i] = texture; + def.textureFaces[i] = texture; } } else if (root->has("texture-faces")) { auto texarr = root->list("texture-faces"); for (uint i = 0; i < 6; i++) { - def->textureFaces[i] = texarr->str(i); + def.textureFaces[i] = texarr->str(i); } } // block model std::string model = "block"; root->str("model", model); - if (model == "block") def->model = BlockModel::block; - else if (model == "aabb") def->model = BlockModel::aabb; + if (model == "block") def.model = BlockModel::block; + else if (model == "aabb") def.model = BlockModel::aabb; else if (model == "custom") { - def->model = BlockModel::custom; + def.model = BlockModel::custom; if (root->has("model-primitives")) { loadCustomBlockModel(def, root->map("model-primitives")); } @@ -124,30 +124,30 @@ void ContentLoader::loadBlock(Block* def, std::string name, fs::path file) { << name << " parsed: no \"model-primitives\" found" << std::endl; } } - else if (model == "X") def->model = BlockModel::xsprite; - else if (model == "none") def->model = BlockModel::none; + else if (model == "X") def.model = BlockModel::xsprite; + else if (model == "none") def.model = BlockModel::none; else { std::cerr << "unknown model " << model << std::endl; - def->model = BlockModel::none; + def.model = BlockModel::none; } // rotation profile std::string profile = "none"; root->str("rotation", profile); - def->rotatable = profile != "none"; + def.rotatable = profile != "none"; if (profile == "pipe") { - def->rotations = BlockRotProfile::PIPE; + def.rotations = BlockRotProfile::PIPE; } else if (profile == "pane") { - def->rotations = BlockRotProfile::PANE; + def.rotations = BlockRotProfile::PANE; } else if (profile != "none") { std::cerr << "unknown rotation profile " << profile << std::endl; - def->rotatable = false; + def.rotatable = false; } // block hitbox AABB [x, y, z, width, height, depth] auto boxarr = root->list("hitbox"); if (boxarr) { - AABB& aabb = def->hitbox; + AABB& aabb = def.hitbox; aabb.a = glm::vec3(boxarr->num(0), boxarr->num(1), boxarr->num(2)); aabb.b = glm::vec3(boxarr->num(3), boxarr->num(4), boxarr->num(5)); aabb.b += aabb.a; @@ -156,27 +156,27 @@ void ContentLoader::loadBlock(Block* def, std::string name, fs::path file) { // block light emission [r, g, b] where r,g,b in range [0..15] auto emissionarr = root->list("emission"); if (emissionarr) { - def->emission[0] = emissionarr->num(0); - def->emission[1] = emissionarr->num(1); - def->emission[2] = emissionarr->num(2); + def.emission[0] = emissionarr->num(0); + def.emission[1] = emissionarr->num(1); + def.emission[2] = emissionarr->num(2); } // primitive properties - root->flag("obstacle", def->obstacle); - root->flag("replaceable", def->replaceable); - root->flag("light-passing", def->lightPassing); - root->flag("breakable", def->breakable); - root->flag("selectable", def->selectable); - root->flag("grounded", def->grounded); - root->flag("hidden", def->hidden); - root->flag("sky-light-passing", def->skyLightPassing); - root->num("draw-group", def->drawGroup); - root->str("picking-item", def->pickingItem); - root->str("script-name", def->scriptName); - root->num("inventory-size", def->inventorySize); + root->flag("obstacle", def.obstacle); + root->flag("replaceable", def.replaceable); + root->flag("light-passing", def.lightPassing); + root->flag("breakable", def.breakable); + root->flag("selectable", def.selectable); + root->flag("grounded", def.grounded); + root->flag("hidden", def.hidden); + root->flag("sky-light-passing", def.skyLightPassing); + root->num("draw-group", def.drawGroup); + root->str("picking-item", def.pickingItem); + root->str("script-name", def.scriptName); + root->num("inventory-size", def.inventorySize); } -void ContentLoader::loadCustomBlockModel(Block* def, dynamic::Map* primitives) { +void ContentLoader::loadCustomBlockModel(Block& def, dynamic::Map* primitives) { if (primitives->has("aabbs")) { auto modelboxes = primitives->list("aabbs"); for (uint i = 0; i < modelboxes->size(); i++ ) { @@ -186,19 +186,19 @@ void ContentLoader::loadCustomBlockModel(Block* def, dynamic::Map* primitives) { modelbox.a = glm::vec3(boxarr->num(0), boxarr->num(1), boxarr->num(2)); modelbox.b = glm::vec3(boxarr->num(3), boxarr->num(4), boxarr->num(5)); modelbox.b += modelbox.a; - def->modelBoxes.push_back(modelbox); + def.modelBoxes.push_back(modelbox); if (boxarr->size() == 7) for (uint i = 6; i < 12; i++) { - def->modelTextures.push_back(boxarr->str(6)); + def.modelTextures.push_back(boxarr->str(6)); } else if (boxarr->size() == 12) for (uint i = 6; i < 12; i++) { - def->modelTextures.push_back(boxarr->str(i)); + def.modelTextures.push_back(boxarr->str(i)); } else for (uint i = 6; i < 12; i++) { - def->modelTextures.push_back("notfound"); + def.modelTextures.push_back("notfound"); } } } @@ -210,77 +210,81 @@ void ContentLoader::loadCustomBlockModel(Block* def, dynamic::Map* primitives) { glm::vec3 p1(tgonobj->num(0), tgonobj->num(1), tgonobj->num(2)), xw(tgonobj->num(3), tgonobj->num(4), tgonobj->num(5)), yh(tgonobj->num(6), tgonobj->num(7), tgonobj->num(8)); - def->modelExtraPoints.push_back(p1); - def->modelExtraPoints.push_back(p1+xw); - def->modelExtraPoints.push_back(p1+xw+yh); - def->modelExtraPoints.push_back(p1+yh); + def.modelExtraPoints.push_back(p1); + def.modelExtraPoints.push_back(p1+xw); + def.modelExtraPoints.push_back(p1+xw+yh); + def.modelExtraPoints.push_back(p1+yh); - def->modelTextures.push_back(tgonobj->str(9)); + def.modelTextures.push_back(tgonobj->str(9)); } } } -void ContentLoader::loadItem(ItemDef* def, std::string name, fs::path file) { +void ContentLoader::loadItem(ItemDef& def, std::string name, fs::path file) { auto root = files::read_json(file); std::string iconTypeStr = ""; root->str("icon-type", iconTypeStr); if (iconTypeStr == "none") { - def->iconType = item_icon_type::none; + def.iconType = item_icon_type::none; } else if (iconTypeStr == "block") { - def->iconType = item_icon_type::block; + def.iconType = item_icon_type::block; } else if (iconTypeStr == "sprite") { - def->iconType = item_icon_type::sprite; + def.iconType = item_icon_type::sprite; } else if (iconTypeStr.length()){ std::cerr << "unknown icon type" << iconTypeStr << std::endl; } - root->str("icon", def->icon); - root->str("placing-block", def->placingBlock); - root->str("script-name", def->scriptName); - root->num("stack-size", def->stackSize); + root->str("icon", def.icon); + root->str("placing-block", def.placingBlock); + root->str("script-name", def.scriptName); + root->num("stack-size", def.stackSize); // item light emission [r, g, b] where r,g,b in range [0..15] auto emissionarr = root->list("emission"); if (emissionarr) { - def->emission[0] = emissionarr->num(0); - def->emission[1] = emissionarr->num(1); - def->emission[2] = emissionarr->num(2); + def.emission[0] = emissionarr->num(0); + def.emission[1] = emissionarr->num(1); + def.emission[2] = emissionarr->num(2); } } -void ContentLoader::loadBlock(Block* def, std::string full, std::string name) { +void ContentLoader::loadBlock(Block& def, std::string full, std::string name) { auto folder = pack->folder; fs::path configFile = folder/fs::path("blocks/"+name+".json"); loadBlock(def, full, configFile); - fs::path scriptfile = folder/fs::path("scripts/"+def->scriptName+".lua"); + fs::path scriptfile = folder/fs::path("scripts/"+def.scriptName+".lua"); if (fs::is_regular_file(scriptfile)) { - scripting::load_block_script(full, scriptfile, &def->rt.funcsset); + scripting::load_block_script(env, full, scriptfile, def.rt.funcsset); } } -void ContentLoader::loadItem(ItemDef* def, std::string full, std::string name) { +void ContentLoader::loadItem(ItemDef& def, std::string full, std::string name) { auto folder = pack->folder; fs::path configFile = folder/fs::path("items/"+name+".json"); loadItem(def, full, configFile); - fs::path scriptfile = folder/fs::path("scripts/"+def->scriptName+".lua"); + fs::path scriptfile = folder/fs::path("scripts/"+def.scriptName+".lua"); if (fs::is_regular_file(scriptfile)) { - scripting::load_item_script(full, scriptfile, &def->rt.funcsset); + scripting::load_item_script(env, full, scriptfile, def.rt.funcsset); } } -void ContentLoader::load(ContentBuilder* builder) { +void ContentLoader::load(ContentBuilder& builder) { std::cout << "-- loading pack [" << pack->id << "]" << std::endl; + auto runtime = new ContentPackRuntime(*pack, scripting::create_pack_environment(*pack)); + builder.add(runtime); + env = runtime->getEnvironment()->getId(); + fixPackIndices(); auto folder = pack->folder; fs::path scriptFile = folder/fs::path("scripts/world.lua"); if (fs::is_regular_file(scriptFile)) { - scripting::load_world_script(pack->id, scriptFile); + scripting::load_world_script(env, pack->id, scriptFile); } if (!fs::is_regular_file(pack->getContentFile())) @@ -291,17 +295,17 @@ void ContentLoader::load(ContentBuilder* builder) { for (uint i = 0; i < blocksarr->size(); i++) { std::string name = blocksarr->str(i); std::string full = pack->id+":"+name; - auto def = builder->createBlock(full); + auto& def = builder.createBlock(full); loadBlock(def, full, name); - if (!def->hidden) { - auto item = builder->createItem(full+BLOCK_ITEM_SUFFIX); - item->generated = true; - item->iconType = item_icon_type::block; - item->icon = full; - item->placingBlock = full; + if (!def.hidden) { + auto& item = builder.createItem(full+BLOCK_ITEM_SUFFIX); + item.generated = true; + item.iconType = item_icon_type::block; + item.icon = full; + item.placingBlock = full; for (uint j = 0; j < 4; j++) { - item->emission[j] = def->emission[j]; + item.emission[j] = def.emission[j]; } } } @@ -312,7 +316,7 @@ void ContentLoader::load(ContentBuilder* builder) { for (uint i = 0; i < itemsarr->size(); i++) { std::string name = itemsarr->str(i); std::string full = pack->id+":"+name; - loadItem(builder->createItem(full), full, name); + loadItem(builder.createItem(full), full, name); } } } diff --git a/src/content/ContentLoader.h b/src/content/ContentLoader.h index df14910a..2606e71d 100644 --- a/src/content/ContentLoader.h +++ b/src/content/ContentLoader.h @@ -17,10 +17,11 @@ namespace dynamic { class ContentLoader { const ContentPack* pack; + int env = 0; - void loadBlock(Block* def, std::string full, std::string name); - void loadCustomBlockModel(Block* def, dynamic::Map* primitives); - void loadItem(ItemDef* def, std::string full, std::string name); + void loadBlock(Block& def, std::string full, std::string name); + void loadCustomBlockModel(Block& def, dynamic::Map* primitives); + void loadItem(ItemDef& def, std::string full, std::string name); public: ContentLoader(ContentPack* pack); @@ -28,9 +29,9 @@ public: dynamic::Map* indicesRoot, std::string contentSection); void fixPackIndices(); - void loadBlock(Block* def, std::string name, fs::path file); - void loadItem(ItemDef* def, std::string name, fs::path file); - void load(ContentBuilder* builder); + void loadBlock(Block& def, std::string name, fs::path file); + void loadItem(ItemDef& def, std::string name, fs::path file); + void load(ContentBuilder& builder); }; #endif // CONTENT_CONTENT_LOADER_H_ diff --git a/src/content/ContentPack.cpp b/src/content/ContentPack.cpp index b72930db..ad6587c1 100644 --- a/src/content/ContentPack.cpp +++ b/src/content/ContentPack.cpp @@ -7,6 +7,7 @@ #include "../files/files.h" #include "../files/engine_paths.h" #include "../data/dynamic.h" +#include "../logic/scripting/scripting.h" namespace fs = std::filesystem; @@ -149,3 +150,10 @@ void ContentPack::readPacks(const EnginePaths* paths, packs.push_back(ContentPack::read(packfolder)); } } + +ContentPackRuntime::ContentPackRuntime( + ContentPack info, + std::unique_ptr env +) : info(info), env(std::move(env)) +{ +} diff --git a/src/content/ContentPack.h b/src/content/ContentPack.h index 6f82519a..ac4a5b79 100644 --- a/src/content/ContentPack.h +++ b/src/content/ContentPack.h @@ -8,16 +8,22 @@ class EnginePaths; +namespace fs = std::filesystem; + +namespace scripting { + class Environment; +} + class contentpack_error : public std::runtime_error { std::string packId; - std::filesystem::path folder; + fs::path folder; public: contentpack_error(std::string packId, - std::filesystem::path folder, + fs::path folder, std::string message); std::string getPackId() const; - std::filesystem::path getFolder() const; + fs::path getFolder() const; }; struct ContentPack { @@ -26,35 +32,61 @@ struct ContentPack { std::string version = "0.0"; std::string creator = ""; std::string description = "no description"; - std::filesystem::path folder; + fs::path folder; std::vector dependencies; - std::filesystem::path getContentFile() const; + fs::path getContentFile() const; static const std::string PACKAGE_FILENAME; static const std::string CONTENT_FILENAME; - static const std::filesystem::path BLOCKS_FOLDER; - static const std::filesystem::path ITEMS_FOLDER; + static const fs::path BLOCKS_FOLDER; + static const fs::path ITEMS_FOLDER; static const std::vector RESERVED_NAMES; - static bool is_pack(std::filesystem::path folder); - static ContentPack read(std::filesystem::path folder); + static bool is_pack(fs::path folder); + static ContentPack read(fs::path folder); - static void scan(std::filesystem::path folder, + static void scan(fs::path folder, std::vector& packs); static void scan(EnginePaths* paths, std::vector& packs); - static std::vector worldPacksList(std::filesystem::path folder); + static std::vector worldPacksList(fs::path folder); - static std::filesystem::path findPack( + static fs::path findPack( const EnginePaths* paths, - std::filesystem::path worldDir, - std::string name); - static void readPacks(const EnginePaths* paths, - std::vector& packs, - const std::vector& names, - std::filesystem::path worldDir); + fs::path worldDir, + std::string name + ); + + static void readPacks( + const EnginePaths* paths, + std::vector& packs, + const std::vector& names, + fs::path worldDir + ); +}; + +class ContentPackRuntime { + ContentPack info; + std::unique_ptr env; +public: + ContentPackRuntime( + ContentPack info, + std::unique_ptr env + ); + + inline const std::string& getId() { + return info.id; + } + + inline const ContentPack& getInfo() const { + return info; + } + + inline scripting::Environment* getEnvironment() const { + return env.get(); + } }; #endif // CONTENT_CONTENT_PACK_H_ diff --git a/src/core_defs.cpp b/src/core_defs.cpp index da3136ba..adf3c938 100644 --- a/src/core_defs.cpp +++ b/src/core_defs.cpp @@ -9,18 +9,18 @@ // All in-game definitions (blocks, items, etc..) void corecontent::setup(ContentBuilder* builder) { - Block* block = builder->createBlock("core:air"); - block->replaceable = true; - block->drawGroup = 1; - block->lightPassing = true; - block->skyLightPassing = true; - block->obstacle = false; - block->selectable = false; - block->model = BlockModel::none; - block->pickingItem = "core:empty"; + Block& block = builder->createBlock("core:air"); + block.replaceable = true; + block.drawGroup = 1; + block.lightPassing = true; + block.skyLightPassing = true; + block.obstacle = false; + block.selectable = false; + block.model = BlockModel::none; + block.pickingItem = "core:empty"; - ItemDef* item = builder->createItem("core:empty"); - item->iconType = item_icon_type::none; + ItemDef& item = builder->createItem("core:empty"); + item.iconType = item_icon_type::none; } void corecontent::setup_bindings() { diff --git a/src/delegates.h b/src/delegates.h index 06f0d3a5..61360464 100644 --- a/src/delegates.h +++ b/src/delegates.h @@ -4,7 +4,9 @@ #include #include -typedef std::function runnable; -typedef std::function stringconsumer; +using runnable = std::function; +using stringconsumer = std::function; +using wstringsupplier = std::function; +using wstringconsumer = std::function; #endif // DELEGATES_H_ diff --git a/src/engine.cpp b/src/engine.cpp index a2a8fd27..d1e85eeb 100644 --- a/src/engine.cpp +++ b/src/engine.cpp @@ -42,7 +42,8 @@ namespace fs = std::filesystem; Engine::Engine(EngineSettings& settings, EnginePaths* paths) - : settings(settings), paths(paths) { + : settings(settings), paths(paths) +{ if (Window::initialize(settings.display)){ throw initialize_error("could not initialize window"); } @@ -52,18 +53,21 @@ Engine::Engine(EngineSettings& settings, EnginePaths* paths) std::cout << "-- loading assets" << std::endl; std::vector roots {resdir}; - resPaths.reset(new ResPaths(resdir, roots)); - assets.reset(new Assets()); + + resPaths = std::make_unique(resdir, roots); + assets = std::make_unique(); + + AssetsLoader loader(assets.get(), resPaths.get()); - AssetsLoader::createDefaults(loader); - AssetsLoader::addDefaults(loader, false); + AssetsLoader::addDefaults(loader, nullptr); Shader::preprocessor->setPaths(resPaths.get()); while (loader.hasNext()) { if (!loader.loadNext()) { assets.reset(); + scripting::close(); Window::terminate(); - throw initialize_error("could not to initialize assets"); + throw initialize_error("could not to load assets"); } } @@ -73,7 +77,6 @@ Engine::Engine(EngineSettings& settings, EnginePaths* paths) settings.ui.language = langs::locale_by_envlocale(platform::detect_locale(), paths->getResources()); } setLanguage(settings.ui.language); - std::cout << "-- initializing finished" << std::endl; } void Engine::updateTimers() { @@ -98,12 +101,11 @@ void Engine::updateHotkeys() { void Engine::mainloop() { setScreen(std::make_shared(this)); - - std::cout << "-- preparing systems" << std::endl; Batch2D batch(1024); lastTime = Window::time(); + std::cout << "-- initialized" << std::endl; while (!Window::isShouldClose()){ assert(screen != nullptr); updateTimers(); @@ -129,13 +131,13 @@ void Engine::mainloop() { } Engine::~Engine() { - screen = nullptr; - scripting::close(); + std::cout << "-- shutting down" << std::endl; + screen.reset(); + content.reset(); Audio::finalize(); - - std::cout << "-- shutting down" << std::endl; assets.reset(); + scripting::close(); Window::terminate(); std::cout << "-- engine finished" << std::endl; } @@ -163,13 +165,14 @@ void Engine::loadContent() { for (auto& pack : srcPacks) { if(loadedPacks.find(pack.id) != loadedPacks.end()) continue; missingDependency = checkPacks(existingPacks, pack.dependencies); - if(!missingDependency.empty()) throw contentpack_error(pack.id, pack.folder, "missing dependency '"+missingDependency+"'"); + if(!missingDependency.empty()) + throw contentpack_error(pack.id, pack.folder, "missing dependency '"+missingDependency+"'"); if(pack.dependencies.empty() || checkPacks(loadedPacks, pack.dependencies).empty()) { loadedPacks.insert(pack.id); resRoots.push_back(pack.folder); contentPacks.push_back(pack); ContentLoader loader(&pack); - loader.load(&contentBuilder); + loader.load(contentBuilder); } } } @@ -182,8 +185,7 @@ void Engine::loadContent() { std::unique_ptr new_assets(new Assets()); std::cout << "-- loading assets" << std::endl; AssetsLoader loader(new_assets.get(), resPaths.get()); - AssetsLoader::createDefaults(loader); - AssetsLoader::addDefaults(loader, true); + AssetsLoader::addDefaults(loader, content.get()); while (loader.hasNext()) { if (!loader.loadNext()) { new_assets.reset(); @@ -196,7 +198,6 @@ void Engine::loadContent() { void Engine::loadWorldContent(const fs::path& folder) { contentPacks.clear(); auto packNames = ContentPack::worldPacksList(folder); - std::cout << folder << " " << packNames.size() << std::endl; ContentPack::readPacks(paths, contentPacks, packNames, folder); loadContent(); } @@ -243,4 +244,4 @@ EnginePaths* Engine::getPaths() { std::shared_ptr Engine::getScreen() { return screen; -} \ No newline at end of file +} diff --git a/src/engine.h b/src/engine.h index 595b80da..1532e84b 100644 --- a/src/engine.h +++ b/src/engine.h @@ -67,4 +67,4 @@ public: std::shared_ptr getScreen(); }; -#endif // SRC_ENGINE_H_ \ No newline at end of file +#endif // SRC_ENGINE_H_ diff --git a/src/files/WorldFiles.cpp b/src/files/WorldFiles.cpp index 6b011909..b0263c92 100644 --- a/src/files/WorldFiles.cpp +++ b/src/files/WorldFiles.cpp @@ -29,6 +29,8 @@ #include #include +const size_t BUFFER_SIZE_UNKNOWN = -1; + regfile::regfile(fs::path filename) : file(filename) { if (file.length() < REGION_HEADER_SIZE) throw std::runtime_error("incomplete region file header"); @@ -217,7 +219,7 @@ void WorldFiles::put(Chunk* chunk){ for (auto& entry : inventories) { builder.putInt32(entry.first); auto map = entry.second->serialize(); - auto bytes = json::to_binary(map.get(), true); + auto bytes = json::to_binary(map.get(), false); builder.putInt32(bytes.size()); builder.put(bytes.data(), bytes.size()); } @@ -226,8 +228,8 @@ void WorldFiles::put(Chunk* chunk){ auto datavec = builder.data(); uint datasize = builder.size(); - auto data = std::make_unique(builder.size()); - for (uint i = 0; i < builder.size(); i++) { + auto data = std::make_unique(datasize); + for (uint i = 0; i < datasize; i++) { data[i] = datavec[i]; } region->put(localX, localZ, data.release(), datasize); @@ -289,20 +291,39 @@ fs::path WorldFiles::getPacksFile() const { } ubyte* WorldFiles::getChunk(int x, int z){ - return getData(regions, getRegionsFolder(), x, z, REGION_LAYER_VOXELS); + return getData(regions, getRegionsFolder(), x, z, REGION_LAYER_VOXELS, true); } /* Get cached lights for chunk at x,z * @return lights data or nullptr */ light_t* WorldFiles::getLights(int x, int z) { - std::unique_ptr data (getData(lights, getLightsFolder(), x, z, REGION_LAYER_LIGHTS)); + std::unique_ptr data (getData(lights, getLightsFolder(), x, z, REGION_LAYER_LIGHTS, true)); if (data == nullptr) return nullptr; return Lightmap::decode(data.get()); } +chunk_inventories_map WorldFiles::fetchInventories(int x, int z) { + chunk_inventories_map inventories; + const ubyte* data = getData(storages, getInventoriesFolder(), x, z, REGION_LAYER_INVENTORIES, false); + if (data == nullptr) + return inventories; + ByteReader reader(data, BUFFER_SIZE_UNKNOWN); + int count = reader.getInt32(); + for (int i = 0; i < count; i++) { + uint index = reader.getInt32(); + uint size = reader.getInt32(); + auto map = json::from_binary(reader.pointer(), size); + reader.skip(size); + auto inv = std::make_shared(0, 0); + inv->deserialize(map.get()); + inventories[index] = inv; + } + return inventories; +} + ubyte* WorldFiles::getData(regionsmap& regions, const fs::path& folder, - int x, int z, int layer) { + int x, int z, int layer, bool compression) { int regionX = floordiv(x, REGION_SIZE); int regionZ = floordiv(z, REGION_SIZE); @@ -320,7 +341,10 @@ ubyte* WorldFiles::getData(regionsmap& regions, const fs::path& folder, } if (data != nullptr) { size_t size = region->getChunkDataSize(localX, localZ); - return decompress(data, size, CHUNK_DATA_LEN); + if (compression) { + return decompress(data, size, CHUNK_DATA_LEN); + } + return data; } return nullptr; } @@ -372,17 +396,16 @@ ubyte* WorldFiles::readChunkData(int x, file.seekg(table_offset + chunkIndex * 4); file.read((char*)(&offset), 4); offset = dataio::read_int32_big((const ubyte*)(&offset), 0); + if (offset == 0){ return nullptr; } + file.seekg(offset); file.read((char*)(&offset), 4); length = dataio::read_int32_big((const ubyte*)(&offset), 0); - ubyte* data = new ubyte[length]; + ubyte* data = new ubyte[length]{}; file.read((char*)data, length); - if (data == nullptr) { - std::cerr << "ERROR: failed to read data of chunk x("<< x <<"), z("<< z <<")" << std::endl; - } return data; } @@ -482,7 +505,7 @@ void WorldFiles::write(const World* world, const Content* content) { writeIndices(content->getIndices()); writeRegions(regions, regionsFolder, REGION_LAYER_VOXELS); writeRegions(lights, lightsFolder, REGION_LAYER_LIGHTS); - writeRegions(storages, inventoriesFolder, REGION_LAYER_STORAGES); + writeRegions(storages, inventoriesFolder, REGION_LAYER_INVENTORIES); } void WorldFiles::writePacks(const World* world) { diff --git a/src/files/WorldFiles.h b/src/files/WorldFiles.h index a6f9d534..34203b97 100644 --- a/src/files/WorldFiles.h +++ b/src/files/WorldFiles.h @@ -15,11 +15,13 @@ #include "../typedefs.h" #include "../settings.h" +#include "../voxels/Chunk.h" + const uint REGION_HEADER_SIZE = 10; const uint REGION_LAYER_VOXELS = 0; const uint REGION_LAYER_LIGHTS = 1; -const uint REGION_LAYER_STORAGES = 2; +const uint REGION_LAYER_INVENTORIES = 2; const uint REGION_SIZE_BIT = 5; const uint REGION_SIZE = (1 << (REGION_SIZE_BIT)); @@ -32,7 +34,6 @@ const uint MAX_OPEN_REGION_FILES = 16; #define WORLD_FORMAT_MAGIC ".VOXWLD" class Player; -class Chunk; class Content; class ContentIndices; class World; @@ -109,7 +110,7 @@ class WorldFiles { ubyte* getData(regionsmap& regions, const fs::path& folder, - int x, int z, int layer); + int x, int z, int layer, bool compression); regfile* getRegFile(glm::ivec3 coord, const fs::path& folder); @@ -139,6 +140,7 @@ public: ubyte* getChunk(int x, int z); light_t* getLights(int x, int z); + chunk_inventories_map fetchInventories(int x, int z); bool readWorldInfo(World* world); bool readPlayer(Player* player); diff --git a/src/files/engine_paths.cpp b/src/files/engine_paths.cpp index 10157ded..585268dc 100644 --- a/src/files/engine_paths.cpp +++ b/src/files/engine_paths.cpp @@ -159,3 +159,7 @@ std::vector ResPaths::listdir(const std::string& folderName) const { } return entries; } + +const fs::path& ResPaths::getMainRoot() const { + return mainRoot; +} diff --git a/src/files/engine_paths.h b/src/files/engine_paths.h index 760a1ba5..04fcca67 100644 --- a/src/files/engine_paths.h +++ b/src/files/engine_paths.h @@ -42,6 +42,8 @@ public: fs::path find(const std::string& filename) const; std::vector listdir(const std::string& folder) const; + + const fs::path& getMainRoot() const; }; -#endif // FILES_ENGINE_PATHS_H_ \ No newline at end of file +#endif // FILES_ENGINE_PATHS_H_ diff --git a/src/files/settings_io.cpp b/src/files/settings_io.cpp index 037282d0..2a67fb34 100644 --- a/src/files/settings_io.cpp +++ b/src/files/settings_io.cpp @@ -32,6 +32,7 @@ toml::Wrapper* create_wrapper(EngineSettings& settings) { camera.add("sensitivity", &settings.camera.sensitivity); toml::Section& graphics = wrapper->add("graphics"); + graphics.add("gamma", &settings.graphics.gamma); graphics.add("fog-curve", &settings.graphics.fogCurve); graphics.add("backlight", &settings.graphics.backlight); graphics.add("frustum-culling", &settings.graphics.frustumCulling); diff --git a/src/frontend/ContentGfxCache.cpp b/src/frontend/ContentGfxCache.cpp index cb9a9ae0..f8e7bd40 100644 --- a/src/frontend/ContentGfxCache.cpp +++ b/src/frontend/ContentGfxCache.cpp @@ -4,38 +4,49 @@ #include "../assets/Assets.h" #include "../content/Content.h" +#include "../content/ContentPack.h" #include "../graphics/Atlas.h" #include "../voxels/Block.h" +#include "../core_defs.h" +#include "UiDocument.h" -ContentGfxCache::ContentGfxCache(const Content* content, Assets* assets) { +ContentGfxCache::ContentGfxCache(const Content* content, Assets* assets) : content(content) { auto indices = content->getIndices(); - sideregions = new UVRegion[indices->countBlockDefs() * 6]; - Atlas* atlas = assets->getAtlas("blocks"); - - for (uint i = 0; i < indices->countBlockDefs(); i++) { - Block* def = indices->getBlockDef(i); - for (uint side = 0; side < 6; side++) { - std::string tex = def->textureFaces[side]; - if (atlas->has(tex)) { - sideregions[i * 6 + side] = atlas->get(tex); - } else { - if (atlas->has("notfound")) - sideregions[i * 6 + side] = atlas->get("notfound"); - } - } - for (uint side = 0; side < def->modelTextures.size(); side++) - { - std::string tex = def->modelTextures[side]; - if (atlas->has(tex)) { - def->modelUVs.push_back(atlas->get(tex)); - } else { - if (atlas->has("notfound")) - def->modelUVs.push_back(atlas->get("notfound")); - } - } + sideregions = std::make_unique(indices->countBlockDefs() * 6); + Atlas* atlas = assets->getAtlas("blocks"); + + for (uint i = 0; i < indices->countBlockDefs(); i++) { + Block* def = indices->getBlockDef(i); + for (uint side = 0; side < 6; side++) { + const std::string& tex = def->textureFaces[side]; + if (atlas->has(tex)) { + sideregions[i * 6 + side] = atlas->get(tex); + } else if (atlas->has(TEXTURE_NOTFOUND)) { + sideregions[i * 6 + side] = atlas->get(TEXTURE_NOTFOUND); + } + } + for (uint side = 0; side < def->modelTextures.size(); side++) { + const std::string& tex = def->modelTextures[side]; + if (atlas->has(tex)) { + def->modelUVs.push_back(atlas->get(tex)); + } else if (atlas->has(TEXTURE_NOTFOUND)) { + def->modelUVs.push_back(atlas->get(TEXTURE_NOTFOUND)); + } + } } } ContentGfxCache::~ContentGfxCache() { - delete[] sideregions; +} + +std::shared_ptr ContentGfxCache::getLayout(const std::string& id) { + auto found = layouts.find(id); + if (found == layouts.end()) { + return nullptr; + } + return found->second; +} + +const Content* ContentGfxCache::getContent() const { + return content; } diff --git a/src/frontend/ContentGfxCache.h b/src/frontend/ContentGfxCache.h index 1f2b3773..b561742b 100644 --- a/src/frontend/ContentGfxCache.h +++ b/src/frontend/ContentGfxCache.h @@ -1,15 +1,24 @@ #ifndef FRONTEND_BLOCKS_GFX_CACHE_H_ #define FRONTEND_BLOCKS_GFX_CACHE_H_ +#include +#include +#include #include "../graphics/UVRegion.h" #include "../typedefs.h" class Content; class Assets; +class UiDocument; + +using uidocuments_map = std::unordered_map>; class ContentGfxCache { + const Content* content; // array of block sides uv regions (6 per block) - UVRegion* sideregions; + std::unique_ptr sideregions; + // all loaded layouts + uidocuments_map layouts; public: ContentGfxCache(const Content* content, Assets* assets); ~ContentGfxCache(); @@ -17,6 +26,10 @@ public: inline const UVRegion& getRegion(blockid_t id, int side) const { return sideregions[id * 6 + side]; } + + std::shared_ptr getLayout(const std::string& id); + + const Content* getContent() const; }; #endif // FRONTEND_BLOCKS_GFX_CACHE_H_ diff --git a/src/frontend/InventoryView.cpp b/src/frontend/InventoryView.cpp index 00db92b0..cc8902a7 100644 --- a/src/frontend/InventoryView.cpp +++ b/src/frontend/InventoryView.cpp @@ -19,65 +19,30 @@ #include "../maths/voxmaths.h" #include "../objects/Player.h" #include "../voxels/Block.h" +#include "../frontend/gui/panels.h" #include "../frontend/gui/controls.h" #include "../util/stringutil.h" - -InventoryLayout::InventoryLayout(glm::vec2 size) : size(size) {} - -void InventoryLayout::add(SlotLayout slot) { - slots.push_back(slot); -} - -void InventoryLayout::add(InventoryPanel panel) { - panels.push_back(panel); -} - -void InventoryLayout::setSize(glm::vec2 size) { - this->size = size; -} - -void InventoryLayout::setOrigin(glm::vec2 origin) { - this->origin = origin; -} - -glm::vec2 InventoryLayout::getSize() const { - return size; -} - -glm::vec2 InventoryLayout::getOrigin() const { - return origin; -} - -std::vector& InventoryLayout::getSlots() { - return slots; -} - -std::vector& InventoryLayout::getPanels() { - return panels; -} +#include "../world/Level.h" +#include "../logic/scripting/scripting.h" SlotLayout::SlotLayout( + int index, glm::vec2 position, bool background, bool itemSource, itemsharefunc shareFunc, slotcallback rightClick ) - : position(position), + : index(index), + position(position), background(background), itemSource(itemSource), shareFunc(shareFunc), rightClick(rightClick) {} -InventoryPanel::InventoryPanel( - glm::vec2 position, - glm::vec2 size, - glm::vec4 color) - : position(position), size(size), color(color) {} - -InventoryBuilder::InventoryBuilder() - : layout(std::make_unique(glm::vec2())) -{} +InventoryBuilder::InventoryBuilder() { + view = std::make_shared(); +} void InventoryBuilder::addGrid( int cols, int count, @@ -94,14 +59,20 @@ void InventoryBuilder::addGrid( uint width = cols * (slotSize + interval) - interval + padding*2; uint height = rows * (slotSize + interval) - interval + padding*2; - auto lsize = layout->getSize(); - if (coord.x + width > lsize.x) { - lsize.x = coord.x + width; + glm::vec2 vsize = view->getSize(); + if (coord.x + width > vsize.x) { + vsize.x = coord.x + width; } - if (coord.y + height > lsize.y) { - lsize.y = coord.y + height; + if (coord.y + height > vsize.y) { + vsize.y = coord.y + height; + } + view->setSize(vsize); + + if (addpanel) { + auto panel = std::make_shared(coord, glm::vec2(width, height)); + view->setColor(glm::vec4(0, 0, 0, 0.5f)); + view->add(panel); } - layout->setSize(lsize); for (int row = 0; row < rows; row++) { for (int col = 0; col < cols; col++) { @@ -112,59 +83,34 @@ void InventoryBuilder::addGrid( row * (slotSize + interval) + padding ); auto builtSlot = slotLayout; + builtSlot.index = row * cols + col; builtSlot.position = position; - layout->add(builtSlot); + add(builtSlot); } } - - if (addpanel) { - add(InventoryPanel( - coord, - glm::vec2(width, height), - glm::vec4(0, 0, 0, 0.5f))); - } } -void InventoryBuilder::add(SlotLayout slotLayout) { - uint width = InventoryView::SLOT_SIZE; - uint height = InventoryView::SLOT_SIZE; - - auto coord = slotLayout.position; - auto lsize = layout->getSize(); - if (coord.x + width > lsize.x) { - lsize.x = coord.x + width; - } - if (coord.y + height > lsize.y) { - lsize.y = coord.y + height; - } - layout->add(slotLayout); +void InventoryBuilder::add(SlotLayout layout) { + view->add(view->addSlot(layout), layout.position); } -void InventoryBuilder::add(InventoryPanel panel) { - layout->add(panel); -} - -std::unique_ptr InventoryBuilder::build() { - return std::unique_ptr(layout.release()); +std::shared_ptr InventoryBuilder::build() { + return view; } SlotView::SlotView( - ItemStack& stack, - LevelFrontend* frontend, - InventoryInteraction* interaction, - const Content* content, - SlotLayout layout) - : UINode(glm::vec2(), glm::vec2(InventoryView::SLOT_SIZE)), - frontend(frontend), - interaction(interaction), - content(content), - stack(stack), - layout(layout) { + SlotLayout layout +) : UINode(glm::vec2(), glm::vec2(InventoryView::SLOT_SIZE)), + layout(layout) +{ setColor(glm::vec4(0, 0, 0, 0.2f)); } -// performance disaster void SlotView::draw(const GfxContext* pctx, Assets* assets) { + if (bound == nullptr) + throw std::runtime_error("unbound slot"); + ItemStack& stack = *bound; + glm::vec2 coord = calcCoord(); int slotSize = InventoryView::SLOT_SIZE; @@ -197,10 +143,10 @@ void SlotView::draw(const GfxContext* pctx, Assets* assets) { case item_icon_type::none: break; case item_icon_type::block: { - Block* cblock = content->requireBlock(item->icon); + const Block& cblock = content->requireBlock(item->icon); batch->texture(previews->getTexture()); - UVRegion region = previews->get(cblock->name); + UVRegion region = previews->get(cblock.name); batch->rect( coord.x, coord.y, slotSize, slotSize, 0, 0, 0, region, false, true, tint); @@ -250,11 +196,16 @@ bool SlotView::isHighlighted() const { } void SlotView::clicked(gui::GUI* gui, int button) { + if (bound == nullptr) + throw std::runtime_error("unbound slot"); + ItemStack& grabbed = interaction->getGrabbedItem(); + ItemStack& stack = *bound; + if (button == mousecode::BUTTON_1) { if (Events::pressed(keycode::LEFT_SHIFT)) { if (layout.shareFunc) { - layout.shareFunc(stack); + layout.shareFunc(layout.index, stack); } return; } @@ -302,42 +253,69 @@ void SlotView::focus(gui::GUI* gui) { clicked(gui, 0); } -InventoryView::InventoryView( - const Content* content, - LevelFrontend* frontend, - InventoryInteraction* interaction, - std::shared_ptr inventory, - std::unique_ptr layout) - : Container(glm::vec2(), glm::vec2()), - content(content), - indices(content->getIndices()), - inventory(inventory), - layout(std::move(layout)), - frontend(frontend), - interaction(interaction) { - setSize(this->layout->getSize()); +void SlotView::bind( + ItemStack& stack, + LevelFrontend* frontend, + InventoryInteraction* interaction +) { + bound = &stack; + content = frontend->getLevel()->content; + this->frontend = frontend; + this->interaction = interaction; +} + +const SlotLayout& SlotView::getLayout() const { + return layout; +} + +InventoryView::InventoryView() : Container(glm::vec2(), glm::vec2()) { setColor(glm::vec4(0, 0, 0, 0.0f)); } InventoryView::~InventoryView() {} -void InventoryView::build() { - size_t index = 0; - for (auto& slot : layout->getSlots()) { - if (index >= inventory->size()) - break; - ItemStack& item = inventory->getSlot(index); +std::shared_ptr InventoryView::addSlot(SlotLayout layout) { + uint width = InventoryView::SLOT_SIZE + layout.padding; + uint height = InventoryView::SLOT_SIZE + layout.padding; - auto view = std::make_shared( - item, frontend, interaction, content, slot + auto coord = layout.position; + auto vsize = getSize(); + if (coord.x + width > vsize.x) { + vsize.x = coord.x + width; + } + if (coord.y + height > vsize.y) { + vsize.y = coord.y + height; + } + setSize(vsize); + + auto slot = std::make_shared(layout); + if (!layout.background) { + slot->setColor(glm::vec4()); + } + slots.push_back(slot.get()); + return slot; +} + +std::shared_ptr InventoryView::getInventory() const { + return inventory; +} + +void InventoryView::bind( + std::shared_ptr inventory, + LevelFrontend* frontend, + InventoryInteraction* interaction +) { + this->frontend = frontend; + this->interaction = interaction; + this->inventory = inventory; + content = frontend->getLevel()->content; + indices = content->getIndices(); + for (auto slot : slots) { + slot->bind( + inventory->getSlot(slot->getLayout().index), + frontend, interaction ); - if (!slot.background) { - view->setColor(glm::vec4()); - } - slots.push_back(view.get()); - add(view, slot.position); - index++; } } @@ -349,27 +327,116 @@ void InventoryView::setSelected(int index) { } void InventoryView::setCoord(glm::vec2 coord) { - Container::setCoord(coord - layout->getOrigin()); + Container::setCoord(coord - origin); +} + +void InventoryView::setOrigin(glm::vec2 origin) { + this->origin = origin; +} + +glm::vec2 InventoryView::getOrigin() const { + return origin; } void InventoryView::setInventory(std::shared_ptr inventory) { this->inventory = inventory; } -InventoryLayout* InventoryView::getLayout() const { - return layout.get(); +#include "../coders/xml.h" +#include "gui/gui_xml.h" + +static itemsharefunc readShareFunc(InventoryView* view, gui::UiXmlReader& reader, xml::xmlelement& element) { + auto consumer = scripting::create_int_array_consumer( + reader.getEnvironment().getId(), + element->attr("sharefunc").getText() + ); + return [=](uint slot, ItemStack& stack) { + int args[] {int(view->getInventory()->getId()), int(slot)}; + consumer(args, 2); + }; } -void InventoryView::drawBackground(const GfxContext* pctx, Assets* assets) { - glm::vec2 coord = calcCoord(); - auto batch = pctx->getBatch2D(); +static void readSlot(InventoryView* view, gui::UiXmlReader& reader, xml::xmlelement element) { + int index = element->attr("index", "0").asInt(); + bool itemSource = element->attr("item-source", "false").asBool(); + SlotLayout layout(index, glm::vec2(), true, itemSource, nullptr, nullptr); + if (element->has("coord")) { + layout.position = element->attr("coord").asVec2(); + } + if (element->has("sharefunc")) { + layout.shareFunc = readShareFunc(view, reader, element); + } + auto slot = view->addSlot(layout); + reader.readUINode(reader, element, *slot); + view->add(slot); +} - batch->texture(nullptr); +static void readSlotsGrid(InventoryView* view, gui::UiXmlReader& reader, xml::xmlelement element) { + int startIndex = element->attr("start-index", "0").asInt(); + int rows = element->attr("rows", "0").asInt(); + int cols = element->attr("cols", "0").asInt(); + int count = element->attr("count", "0").asInt(); + const int slotSize = InventoryView::SLOT_SIZE; + int interval = element->attr("interval", "-1").asInt(); + if (interval < 0) { + interval = InventoryView::SLOT_INTERVAL; + } + int padding = element->attr("padding", "-1").asInt(); + if (padding < 0) { + padding = interval; + } + if (rows == 0) { + rows = ceildiv(count, cols); + } else if (cols == 0) { + cols = ceildiv(count, rows); + } else if (count == 0) { + count = rows * cols; + } + bool itemSource = element->attr("item-source", "false").asBool(); + SlotLayout layout(-1, glm::vec2(), true, itemSource, nullptr, nullptr); + if (element->has("coord")) { + layout.position = element->attr("coord").asVec2(); + } + if (element->has("sharefunc")) { + layout.shareFunc = readShareFunc(view, reader, element); + } + layout.padding = padding; - for (auto& panel : layout->getPanels()) { - glm::vec2 size = panel.size; - glm::vec2 pos = coord + panel.position; - batch->color = panel.color; - batch->rect(pos.x-1, pos.y-1, size.x+2, size.y+2); + glm::vec2 size ( + cols * slotSize + (cols - 1) * interval + padding * 2, + rows * slotSize + (rows - 1) * interval + padding * 2 + ); + int idx = 0; + for (int row = 0; row < rows; row++) { + for (int col = 0; col < cols; col++, idx++) { + if (idx >= count) { + return; + } + SlotLayout slotLayout = layout; + slotLayout.index = startIndex + idx; + slotLayout.position += glm::vec2( + padding + col * (slotSize + interval), + padding + row * (slotSize + interval) + ); + auto slot = view->addSlot(slotLayout); + view->add(slot, slotLayout.position); + } } } + +void InventoryView::createReaders(gui::UiXmlReader& reader) { + reader.add("inventory", [=](gui::UiXmlReader& reader, xml::xmlelement element) { + auto view = std::make_shared(); + reader.addIgnore("slots-grid"); + reader.readUINode(reader, element, *view); + + for (auto& sub : element->getElements()) { + if (sub->getTag() == "slot") { + readSlot(view.get(), reader, sub); + } else if (sub->getTag() == "slots-grid") { + readSlotsGrid(view.get(), reader, sub); + } + } + return view; + }); +} diff --git a/src/frontend/InventoryView.h b/src/frontend/InventoryView.h index 9b9e01b4..7a8238ae 100644 --- a/src/frontend/InventoryView.h +++ b/src/frontend/InventoryView.h @@ -18,133 +18,16 @@ class ContentIndices; class LevelFrontend; class Inventory; -typedef std::function itemsharefunc; -typedef std::function slotcallback; +namespace gui { + class UiXmlReader; +} -class InventoryInteraction; +namespace scripting { + class Environment; +} -struct SlotLayout { - glm::vec2 position; - bool background; - bool itemSource; - itemsharefunc shareFunc; - slotcallback rightClick; - - SlotLayout(glm::vec2 position, - bool background, - bool itemSource, - itemsharefunc shareFunc, - slotcallback rightClick); -}; - -// temporary unused -struct InventoryPanel { - glm::vec2 position; - glm::vec2 size; - glm::vec4 color; - - InventoryPanel(glm::vec2 position, - glm::vec2 size, - glm::vec4 color); -}; - -class InventoryLayout { - glm::vec2 size {}; - glm::vec2 origin {}; - std::vector slots; - std::vector panels; -public: - InventoryLayout(glm::vec2 size); - - void add(SlotLayout slot); - void add(InventoryPanel panel); - void setSize(glm::vec2 size); - void setOrigin(glm::vec2 origin); - - glm::vec2 getSize() const; - glm::vec2 getOrigin() const; - - std::vector& getSlots(); - std::vector& getPanels(); -}; - -class InventoryBuilder { - std::unique_ptr layout; -public: - InventoryBuilder(); - - void addGrid( - int cols, int count, - glm::vec2 coord, - int padding, - bool addpanel, - SlotLayout slotLayout); - - void add(SlotLayout slotLayout); - void add(InventoryPanel panel); - - std::unique_ptr build(); -}; - -class SlotView : public gui::UINode { - LevelFrontend* frontend; - InventoryInteraction* interaction; - const Content* const content; - ItemStack& stack; - bool highlighted = false; - - SlotLayout layout; -public: - SlotView(ItemStack& stack, - LevelFrontend* frontend, - InventoryInteraction* interaction, - const Content* content, - SlotLayout layout); - - virtual void draw(const GfxContext* pctx, Assets* assets) override; - - void setHighlighted(bool flag); - bool isHighlighted() const; - - virtual void clicked(gui::GUI*, int) override; - virtual void focus(gui::GUI*) override; -}; - -class InventoryView : public gui::Container { - const Content* content; - const ContentIndices* indices; - - std::shared_ptr inventory; - std::unique_ptr layout; - LevelFrontend* frontend; - InventoryInteraction* interaction; - - std::vector slots; -public: - InventoryView( - const Content* content, - LevelFrontend* frontend, - InventoryInteraction* interaction, - std::shared_ptr inventory, - std::unique_ptr layout); - - virtual ~InventoryView(); - - void build(); - - virtual void drawBackground(const GfxContext* pctx, Assets* assets) override; - - void setInventory(std::shared_ptr inventory); - - virtual void setCoord(glm::vec2 coord) override; - - InventoryLayout* getLayout() const; - - void setSelected(int index); - - static const int SLOT_INTERVAL = 4; - static const int SLOT_SIZE = ITEM_ICON_SIZE; -}; +using itemsharefunc = std::function; +using slotcallback = std::function; class InventoryInteraction { ItemStack grabbedItem; @@ -156,4 +39,105 @@ public: } }; +struct SlotLayout { + int index; + glm::vec2 position; + bool background; + bool itemSource; + itemsharefunc shareFunc; + slotcallback rightClick; + int padding = 0; + + SlotLayout(int index, + glm::vec2 position, + bool background, + bool itemSource, + itemsharefunc shareFunc, + slotcallback rightClick); +}; + +class SlotView : public gui::UINode { + LevelFrontend* frontend = nullptr; + InventoryInteraction* interaction = nullptr; + const Content* content; + SlotLayout layout; + bool highlighted = false; + + ItemStack* bound = nullptr; +public: + SlotView(SlotLayout layout); + + virtual void draw(const GfxContext* pctx, Assets* assets) override; + + void setHighlighted(bool flag); + bool isHighlighted() const; + + virtual void clicked(gui::GUI*, int) override; + virtual void focus(gui::GUI*) override; + + void bind( + ItemStack& stack, + LevelFrontend* frontend, + InventoryInteraction* interaction + ); + + const SlotLayout& getLayout() const; +}; + +class InventoryView : public gui::Container { + const Content* content; + const ContentIndices* indices; + + std::shared_ptr inventory; + LevelFrontend* frontend = nullptr; + InventoryInteraction* interaction = nullptr; + + std::vector slots; + glm::vec2 origin {}; +public: + InventoryView(); + virtual ~InventoryView(); + + void setInventory(std::shared_ptr inventory); + + virtual void setCoord(glm::vec2 coord) override; + + void setOrigin(glm::vec2 origin); + glm::vec2 getOrigin() const; + + void setSelected(int index); + + void bind( + std::shared_ptr inventory, + LevelFrontend* frontend, + InventoryInteraction* interaction + ); + + std::shared_ptr addSlot(SlotLayout layout); + + std::shared_ptr getInventory() const; + + static void createReaders(gui::UiXmlReader& reader); + + static const int SLOT_INTERVAL = 4; + static const int SLOT_SIZE = ITEM_ICON_SIZE; +}; + +class InventoryBuilder { + std::shared_ptr view; +public: + InventoryBuilder(); + + void addGrid( + int cols, int count, + glm::vec2 coord, + int padding, + bool addpanel, + SlotLayout slotLayout + ); + + void add(SlotLayout slotLayout); + std::shared_ptr build(); +}; + #endif // FRONTEND_INVENTORY_VIEW_H_ diff --git a/src/frontend/LevelFrontend.h b/src/frontend/LevelFrontend.h index 98a8a111..772c5309 100644 --- a/src/frontend/LevelFrontend.h +++ b/src/frontend/LevelFrontend.h @@ -24,5 +24,4 @@ public: Atlas* getBlocksAtlas() const; }; - #endif // FRONTEND_LEVEL_FRONTEND_H_ diff --git a/src/frontend/UiDocument.cpp b/src/frontend/UiDocument.cpp new file mode 100644 index 00000000..9df8f123 --- /dev/null +++ b/src/frontend/UiDocument.cpp @@ -0,0 +1,70 @@ +#include "UiDocument.h" + +#include +#include "gui/UINode.h" +#include "gui/panels.h" +#include "InventoryView.h" +#include "../logic/scripting/scripting.h" +#include "../files/files.h" +#include "../frontend/gui/gui_xml.h" + +UiDocument::UiDocument( + std::string id, + uidocscript script, + std::shared_ptr root, + std::unique_ptr env +) : id(id), script(script), root(root), env(std::move(env)) { + collect(map, root); +} + + +const uinodes_map& UiDocument::getMap() const { + return map; +} + +const std::string& UiDocument::getId() const { + return id; +} + +const std::shared_ptr UiDocument::getRoot() const { + return root; +} + +const uidocscript& UiDocument::getScript() const { + return script; +} + +int UiDocument::getEnvironment() const { + return env->getId(); +} + +void UiDocument::collect(uinodes_map& map, std::shared_ptr node) { + const std::string& id = node->getId(); + if (!id.empty()) { + map[id] = node; + } + auto container = dynamic_cast(node.get()); + if (container) { + for (auto subnode : container->getNodes()) { + collect(map, subnode); + } + } +} + +std::unique_ptr UiDocument::read(AssetsLoader& loader, int penv, std::string namesp, fs::path file) { + const std::string text = files::read_string(file); + auto xmldoc = xml::parse(file.u8string(), text); + + auto env = scripting::create_doc_environment(penv, namesp); + gui::UiXmlReader reader(*env, loader); + InventoryView::createReaders(reader); + auto view = reader.readXML( + file.u8string(), xmldoc->getRoot() + ); + uidocscript script {}; + auto scriptFile = fs::path(file.u8string()+".lua"); + if (fs::is_regular_file(scriptFile)) { + scripting::load_layout_script(env->getId(), namesp, scriptFile, script); + } + return std::make_unique(namesp, script, view, std::move(env)); +} diff --git a/src/frontend/UiDocument.h b/src/frontend/UiDocument.h new file mode 100644 index 00000000..7668a414 --- /dev/null +++ b/src/frontend/UiDocument.h @@ -0,0 +1,54 @@ +#ifndef FRONTEND_UI_DOCUMENT_H_ +#define FRONTEND_UI_DOCUMENT_H_ + +#include +#include +#include +#include + +namespace fs = std::filesystem; + +namespace gui { + class UINode; +} + +namespace scripting { + class Environment; +} + +struct uidocscript { + int environment; + bool onopen : 1; + bool onclose : 1; +}; + +using uinodes_map = std::unordered_map>; + +class AssetsLoader; + +class UiDocument { + std::string id; + uidocscript script; + uinodes_map map; + std::shared_ptr root; + std::unique_ptr env; +public: + UiDocument( + std::string id, + uidocscript script, + std::shared_ptr root, + std::unique_ptr env + ); + + const std::string& getId() const; + const uinodes_map& getMap() const; + const std::shared_ptr getRoot() const; + const uidocscript& getScript() const; + int getEnvironment() const; + /* Collect map of all uinodes having identifiers */ + static void collect(uinodes_map& map, std::shared_ptr node); + + static std::unique_ptr read(AssetsLoader& loader, int env, std::string namesp, fs::path file); +}; + +#endif // FRONTEND_UI_DOCUMENT_H_ diff --git a/src/frontend/WorldRenderer.cpp b/src/frontend/WorldRenderer.cpp index cf20fe9d..2e25d6f4 100644 --- a/src/frontend/WorldRenderer.cpp +++ b/src/frontend/WorldRenderer.cpp @@ -157,7 +157,7 @@ void WorldRenderer::draw(const GfxContext& pctx, Camera* camera, bool hudVisible shader->use(); shader->uniformMatrix("u_proj", camera->getProjection()); shader->uniformMatrix("u_view", camera->getView()); - shader->uniform1f("u_gamma", 1.0f); + shader->uniform1f("u_gamma", settings.graphics.gamma); shader->uniform1f("u_fogFactor", fogFactor); shader->uniform1f("u_fogCurve", settings.graphics.fogCurve); shader->uniform3f("u_cameraPos", camera->position); diff --git a/src/frontend/gui/UINode.cpp b/src/frontend/gui/UINode.cpp index 653e0f97..95e8e4b7 100644 --- a/src/frontend/gui/UINode.cpp +++ b/src/frontend/gui/UINode.cpp @@ -5,10 +5,7 @@ using gui::UINode; using gui::Align; -using glm::vec2; -using glm::vec4; - -UINode::UINode(vec2 coord, vec2 size) : coord(coord), size(size) { +UINode::UINode(glm::vec2 coord, glm::vec2 size) : coord(coord), size(size) { } UINode::~UINode() { @@ -67,13 +64,13 @@ bool UINode::isFocused() const { } bool UINode::isInside(glm::vec2 pos) { - vec2 coord = calcCoord(); - vec2 size = getSize(); + glm::vec2 coord = calcCoord(); + glm::vec2 size = getSize(); return (pos.x >= coord.x && pos.y >= coord.y && pos.x < coord.x + size.x && pos.y < coord.y + size.y); } -std::shared_ptr UINode::getAt(vec2 pos, std::shared_ptr self) { +std::shared_ptr UINode::getAt(glm::vec2 pos, std::shared_ptr self) { if (!interactive) { return nullptr; } @@ -96,7 +93,7 @@ bool UINode::isResizing() const { return resizing; } -vec2 UINode::calcCoord() const { +glm::vec2 UINode::calcCoord() const { if (parent) { return coord + parent->calcCoord() + parent->contentOffset(); } @@ -109,19 +106,19 @@ void UINode::scrolled(int value) { } } -void UINode::setCoord(vec2 coord) { +void UINode::setCoord(glm::vec2 coord) { this->coord = coord; } -vec2 UINode::getSize() const { +glm::vec2 UINode::getSize() const { return size; } -void UINode::setSize(vec2 size) { +void UINode::setSize(glm::vec2 size) { this->size = size; } -void UINode::setColor(vec4 color) { +void UINode::setColor(glm::vec4 color) { this->color = color; this->hoverColor = color; } @@ -134,17 +131,33 @@ glm::vec4 UINode::getHoverColor() const { return hoverColor; } -vec4 UINode::getColor() const { +glm::vec4 UINode::getColor() const { return color; } -void UINode::setMargin(vec4 margin) { +void UINode::setMargin(glm::vec4 margin) { this->margin = margin; } -vec4 UINode::getMargin() const { +glm::vec4 UINode::getMargin() const { return margin; } +void UINode::setZIndex(int zindex) { + this->zindex = zindex; +} + +int UINode::getZIndex() const { + return zindex; +} + void UINode::lock() { -} \ No newline at end of file +} + +void UINode::setId(const std::string& id) { + this->id = id; +} + +const std::string& UINode::getId() const { + return id; +} diff --git a/src/frontend/gui/UINode.h b/src/frontend/gui/UINode.h index 630bb54b..1e195932 100644 --- a/src/frontend/gui/UINode.h +++ b/src/frontend/gui/UINode.h @@ -4,6 +4,7 @@ #include #include #include +#include #include class GfxContext; @@ -21,6 +22,7 @@ namespace gui { }; class UINode { + std::string id = ""; protected: glm::vec2 coord; glm::vec2 size; @@ -33,6 +35,7 @@ namespace gui { bool focused = false; bool interactive = true; bool resizing = true; + int zindex = 0; Align align = Align::left; UINode* parent = nullptr; UINode(glm::vec2 coord, glm::vec2 size); @@ -66,6 +69,9 @@ namespace gui { virtual void setMargin(glm::vec4 margin); glm::vec4 getMargin() const; + virtual void setZIndex(int idx); + int getZIndex() const; + virtual void focus(GUI*) {focused = true;} virtual void click(GUI*, int x, int y); virtual void clicked(GUI*, int button) {} @@ -110,6 +116,9 @@ namespace gui { virtual void setSize(glm::vec2 size); virtual void refresh() {}; virtual void lock(); + + void setId(const std::string& id); + const std::string& getId() const; }; } diff --git a/src/frontend/gui/controls.cpp b/src/frontend/gui/controls.cpp index e05c66d2..d79a2cd8 100644 --- a/src/frontend/gui/controls.cpp +++ b/src/frontend/gui/controls.cpp @@ -177,13 +177,20 @@ Button* Button::listenAction(onaction action) { return this; } -void Button::textAlign(Align align) { +void Button::setTextAlign(Align align) { if (label) { label->setAlign(align); refresh(); } } +Align Button::getTextAlign() const { + if (label) { + return label->getAlign(); + } + return Align::left; +} + // ============================== RichButton ================================== RichButton::RichButton(vec2 size) : Container(vec2(), size) { setHoverColor(glm::vec4(0.05f, 0.1f, 0.15f, 0.75f)); @@ -262,7 +269,7 @@ void TextBox::typed(unsigned int codepoint) { bool TextBox::validate() { if (validator) { - valid = validator(input); + valid = validator(getText()); } else { valid = true; } diff --git a/src/frontend/gui/controls.h b/src/frontend/gui/controls.h index 281ec1ee..ae07b4d7 100644 --- a/src/frontend/gui/controls.h +++ b/src/frontend/gui/controls.h @@ -17,9 +17,6 @@ class Batch2D; class Assets; namespace gui { - using wstringsupplier = std::function; - using wstringconsumer = std::function; - using doublesupplier = std::function; using doubleconsumer = std::function; @@ -49,7 +46,7 @@ namespace gui { protected: std::string texture; public: - Image(std::string texture, glm::vec2 size); + Image(std::string texture, glm::vec2 size=glm::vec2(32,32)); virtual void draw(const GfxContext* pctx, Assets* assets) override; }; @@ -73,7 +70,8 @@ namespace gui { virtual void mouseRelease(GUI*, int x, int y) override; virtual Button* listenAction(onaction action); - virtual void textAlign(Align align); + virtual Align getTextAlign() const; + virtual void setTextAlign(Align align); virtual void setText(std::wstring text); virtual std::wstring getText() const; diff --git a/src/frontend/gui/gui_xml.cpp b/src/frontend/gui/gui_xml.cpp index 6601ad22..2721b5c1 100644 --- a/src/frontend/gui/gui_xml.cpp +++ b/src/frontend/gui/gui_xml.cpp @@ -6,140 +6,94 @@ #include "panels.h" #include "controls.h" +#include "../../assets/AssetsLoader.h" #include "../locale/langs.h" #include "../../logic/scripting/scripting.h" #include "../../util/stringutil.h" using namespace gui; -static double readDouble(const std::string& str, size_t offset, size_t len) { - double value; - auto res = std::from_chars(str.data()+offset, str.data()+offset+len, value); - if (res.ptr != str.data()+offset+len) { - throw std::runtime_error("invalid number format "+escape_string(str)); - } - return value; -} - -/* Read 2d vector formatted `x,y`*/ -static glm::vec2 readVec2(const std::string& str) { - size_t pos = str.find(','); - if (pos == std::string::npos) { - throw std::runtime_error("invalid vec2 value "+escape_string(str)); - } - return glm::vec2( - readDouble(str, 0, pos), - readDouble(str, pos+1, str.length()-pos-1) - ); -} - -/* Read 3d vector formatted `x,y,z`*/ -[[maybe_unused]] -static glm::vec3 readVec3(const std::string& str) { - size_t pos1 = str.find(','); - if (pos1 == std::string::npos) { - throw std::runtime_error("invalid vec3 value "+escape_string(str)); - } - size_t pos2 = str.find(',', pos1+1); - if (pos2 == std::string::npos) { - throw std::runtime_error("invalid vec3 value "+escape_string(str)); - } - return glm::vec3( - readDouble(str, 0, pos1), - readDouble(str, pos1+1, pos2), - readDouble(str, pos2+1, str.length()-pos2-1) - ); -} - -/* Read 4d vector formatted `x,y,z,w`*/ -static glm::vec4 readVec4(const std::string& str) { - size_t pos1 = str.find(','); - if (pos1 == std::string::npos) { - throw std::runtime_error("invalid vec4 value "+escape_string(str)); - } - size_t pos2 = str.find(',', pos1+1); - if (pos2 == std::string::npos) { - throw std::runtime_error("invalid vec4 value "+escape_string(str)); - } - size_t pos3 = str.find(',', pos2+1); - if (pos3 == std::string::npos) { - throw std::runtime_error("invalid vec4 value "+escape_string(str)); - } - return glm::vec4( - readDouble(str, 0, pos1), - readDouble(str, pos1+1, pos2-pos1-1), - readDouble(str, pos2+1, pos3-pos2-1), - readDouble(str, pos3+1, str.length()-pos3-1) - ); -} - -/* Read RGBA color. Supported formats: - - "#RRGGBB" or "#RRGGBBAA" hex color */ -static glm::vec4 readColor(const std::string& str) { - if (str[0] == '#') { - if (str.length() != 7 && str.length() != 9) { - throw std::runtime_error("#RRGGBB or #RRGGBBAA required"); - } - int a = 255; - int r = (hexchar2int(str[1]) << 4) | hexchar2int(str[2]); - int g = (hexchar2int(str[3]) << 4) | hexchar2int(str[4]); - int b = (hexchar2int(str[5]) << 4) | hexchar2int(str[6]); - if (str.length() == 9) { - a = (hexchar2int(str[7]) << 4) | hexchar2int(str[8]); - } - return glm::vec4( - r / 255.f, - g / 255.f, - b / 255.f, - a / 255.f - ); - } else { - throw std::runtime_error("hex colors are only supported"); - } +static Align align_from_string(const std::string& str, Align def) { + if (str == "left") return Align::left; + if (str == "center") return Align::center; + if (str == "right") return Align::right; + return def; } /* Read basic UINode properties */ -static void readUINode(xml::xmlelement element, UINode& node) { +static void _readUINode(xml::xmlelement element, UINode& node) { + if (element->has("id")) { + node.setId(element->attr("id").getText()); + } if (element->has("coord")) { - node.setCoord(readVec2(element->attr("coord").getText())); + node.setCoord(element->attr("coord").asVec2()); } if (element->has("size")) { - node.setSize(readVec2(element->attr("size").getText())); + node.setSize(element->attr("size").asVec2()); } if (element->has("color")) { - node.setColor(readColor(element->attr("color").getText())); + node.setColor(element->attr("color").asColor()); } + if (element->has("margin")) { + node.setMargin(element->attr("margin").asVec4()); + } + if (element->has("z-index")) { + node.setZIndex(element->attr("z-index").asInt()); + } + std::string alignName = element->attr("align", "").getText(); + node.setAlign(align_from_string(alignName, node.getAlign())); } -static void _readContainer(UiXmlReader& reader, xml::xmlelement element, Container& container) { - readUINode(element, container); +static void _readContainer(UiXmlReader& reader, xml::xmlelement element, Container& container) { + _readUINode(element, container); + + if (element->has("scrollable")) { + container.setScrollable(element->attr("scrollable").asBool()); + } for (auto& sub : element->getElements()) { if (sub->isText()) continue; - container.add(reader.readUINode(sub)); + auto subnode = reader.readUINode(sub); + if (subnode) { + container.add(subnode); + } } } +void UiXmlReader::readUINode(UiXmlReader& reader, xml::xmlelement element, Container& container) { + _readContainer(reader, element, container); +} + +void UiXmlReader::readUINode(UiXmlReader& reader, xml::xmlelement element, UINode& node) { + _readUINode(element, node); +} + static void _readPanel(UiXmlReader& reader, xml::xmlelement element, Panel& panel) { - readUINode(element, panel); + _readUINode(element, panel); if (element->has("padding")) { - panel.setPadding(readVec4(element->attr("padding").getText())); + glm::vec4 padding = element->attr("padding").asVec4(); + panel.setPadding(padding); + glm::vec2 size = panel.getSize(); + panel.setSize(glm::vec2( + size.x + padding.x + padding.z, + size.y + padding.y + padding.w + )); } - - if (element->has("margin")) { - panel.setMargin(readVec4(element->attr("margin").getText())); - } - if (element->has("size")) { panel.setResizing(false); } - + if (element->has("max-length")) { + panel.setMaxLength(element->attr("max-length").asInt()); + } for (auto& sub : element->getElements()) { if (sub->isText()) continue; - panel.add(reader.readUINode(sub)); + auto subnode = reader.readUINode(sub); + if (subnode) { + panel.add(subnode); + } } } @@ -147,7 +101,9 @@ static void _readPanel(UiXmlReader& reader, xml::xmlelement element, Panel& pane static std::wstring readAndProcessInnerText(xml::xmlelement element) { std::wstring text = L""; if (element->size() == 1) { - text = util::str2wstr_utf8(element->sub(0)->attr("#").getText()); + std::string source = element->sub(0)->attr("#").getText(); + util::trim(source); + text = util::str2wstr_utf8(source); if (text[0] == '@') { text = langs::get(text.substr(1)); } @@ -158,7 +114,7 @@ static std::wstring readAndProcessInnerText(xml::xmlelement element) { static std::shared_ptr readLabel(UiXmlReader& reader, xml::xmlelement element) { std::wstring text = readAndProcessInnerText(element); auto label = std::make_shared