diff --git a/.github/workflows/appimage.yml b/.github/workflows/appimage.yml index 14805655..ed214cd0 100644 --- a/.github/workflows/appimage.yml +++ b/.github/workflows/appimage.yml @@ -12,7 +12,7 @@ jobs: strategy: matrix: include: - - os: ubuntu-latest + - os: ubuntu-22.04 runs-on: ${{ matrix.os }} @@ -23,7 +23,7 @@ jobs: - name: Install dependencies run: | sudo apt-get update - sudo apt-get install -y build-essential libglfw3-dev libglfw3 libglew-dev \ + sudo apt-get install -y build-essential libglfw3-dev libglfw3 libglew-dev libglew2.2 \ libglm-dev libpng-dev libopenal-dev libluajit-5.1-dev libvorbis-dev \ libcurl4-openssl-dev libgtest-dev cmake squashfs-tools valgrind # fix luajit paths diff --git a/Dockerfile b/Dockerfile index d44f4b2e..f52496de 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,9 +1,9 @@ -# Build docker container: docker build -t voxel-engine . -# Build project: docker run --rm -it -v$(pwd):/project voxel-engine bash -c "cmake -DCMAKE_BUILD_TYPE=Release -Bbuild && cmake --build build" -# Run project in docker: docker run --rm -it -v$(pwd):/project -v/tmp/.X11-unix:/tmp/.X11-unix -v${XAUTHORITY}:/home/user/.Xauthority:ro -eDISPLAY --network=host voxel-engine ./build/VoxelEngine +# Build docker container: docker build -t voxelcore . +# Build project: docker run --rm -it -v$(pwd):/project voxelcore bash -c "cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_RPATH_USE_LINK_PATH=TRUE -Bbuild && cmake --build build" +# Run project in docker: docker run --rm -it -v$(pwd):/project -v/tmp/.X11-unix:/tmp/.X11-unix -v${XAUTHORITY}:/home/user/.Xauthority:ro -eDISPLAY --network=host voxelcore ./build/VoxelEngine -FROM debian:bullseye-slim -LABEL Description="Docker container for building VoxelEngine for Linux" +FROM debian:bookworm-slim +LABEL Description="Docker container for building VoxelCore for Linux" # Install dependencies RUN apt-get update && apt-get install --no-install-recommends -y \ @@ -17,6 +17,7 @@ RUN apt-get update && apt-get install --no-install-recommends -y \ libglfw3-dev \ libglfw3 \ libglew-dev \ + libglew2.2 \ libglm-dev \ libpng-dev \ libopenal-dev \ @@ -29,7 +30,7 @@ RUN apt-get update && apt-get install --no-install-recommends -y \ # Install EnTT RUN git clone https://github.com/skypjack/entt.git && \ cd entt/build && \ - cmake -DCMAKE_BUILD_TYPE=Release .. && \ + cmake -DCMAKE_BUILD_TYPE=Release -DENTT_INSTALL=on .. && \ make install && \ cd ../.. && rm -rf entt diff --git a/dev/AppImageBuilder.yml b/dev/AppImageBuilder.yml index 69e687e4..f4ffe233 100644 --- a/dev/AppImageBuilder.yml +++ b/dev/AppImageBuilder.yml @@ -11,19 +11,19 @@ AppDir: apt: arch: amd64 sources: - - sourceline: deb http://archive.ubuntu.com/ubuntu/ focal main restricted universe multiverse + - sourceline: deb http://archive.ubuntu.com/ubuntu/ jammy main restricted universe multiverse key_url: 'http://keyserver.ubuntu.com/pks/lookup?op=get&search=0x3b4fe6acc0b21f32' key_url: 'http://keyserver.ubuntu.com/pks/lookup?op=get&search=0x871920D1991BC93C' - - sourceline: deb http://archive.ubuntu.com/ubuntu/ focal-updates main restricted universe multiverse - - sourceline: deb http://archive.ubuntu.com/ubuntu/ focal-backports main restricted universe multiverse - - sourceline: deb http://archive.ubuntu.com/ubuntu/ focal-security main restricted universe multiverse + - sourceline: deb http://archive.ubuntu.com/ubuntu/ jammy-updates main restricted universe multiverse + - sourceline: deb http://archive.ubuntu.com/ubuntu/ jammy-backports main restricted universe multiverse + - sourceline: deb http://archive.ubuntu.com/ubuntu/ jammy-security main restricted universe multiverse include: - libbz2-1.0 - libexpat1 - libfam0 - libgcrypt20 - libglfw3 - - libglew2.1 + - libglew2.2 - libpng16-16 - libopenal1 - libasound2 diff --git a/doc/en/block-properties.md b/doc/en/block-properties.md index f5478e0b..8dc73d69 100644 --- a/doc/en/block-properties.md +++ b/doc/en/block-properties.md @@ -216,6 +216,8 @@ Example: [user properties of pack **base**](../../res/content/base/config/user-p ## Properties introduced by the `base` pack +Access to custom properties is provided through the table `block.properties[id]["property"]` where id is the numeric id (index) of the block. + ### *base:durability* The time it takes to break a block without tools or effects, measured in seconds. diff --git a/doc/en/main-page.md b/doc/en/main-page.md index 0a4748b4..3c3aaa57 100644 --- a/doc/en/main-page.md +++ b/doc/en/main-page.md @@ -1,6 +1,8 @@ # Documentation -Documentation for release 0.26. +Documentation for 0.27, which is in development. + +Documentation for [0.26.x](https://github.com/MihailRis/VoxelEngine-Cpp/blob/release-0.26/doc/en/main-page.md). ## Sections diff --git a/doc/ru/block-properties.md b/doc/ru/block-properties.md index 3b75759e..84ffac64 100644 --- a/doc/ru/block-properties.md +++ b/doc/ru/block-properties.md @@ -218,6 +218,8 @@ ## Пользовательские свойства +Доступ к пользовательским свойствам производится через таблицу `block.properties[id]["свойство"]` где id - числовой id (индекс) блока. + Пользовательские свойства должны быть объявляены в файле `пак:config/user-props.toml`: ```toml "пак:имя_свойства" = {} diff --git a/doc/ru/main-page.md b/doc/ru/main-page.md index 52e0faa1..8134eb2b 100644 --- a/doc/ru/main-page.md +++ b/doc/ru/main-page.md @@ -1,6 +1,8 @@ # Документация -Документация версии 0.26. +Документация 0.27, находящейся в разработке. + +Документация [0.26.x](https://github.com/MihailRis/VoxelEngine-Cpp/blob/release-0.26/doc/ru/main-page.md). ## Разделы diff --git a/doc/ru/scripting/extensions.md b/doc/ru/scripting/extensions.md index b72b8b09..32798e43 100644 --- a/doc/ru/scripting/extensions.md +++ b/doc/ru/scripting/extensions.md @@ -56,7 +56,43 @@ table.shuffle(t: table) -> table table.merge(t1: table, t2: table) -> table ``` -Возвращает объединённую таблицу t1 с t2. +Добавляет в таблицу **t1** значения из таблицы **t2**. Если в таблице **t2** присутствует ключ из **t1**, то значение ключа не будет изменено. + +```lua +table.map(t: table, func: function(indx, value) ) -> table +``` + +Проходится по таблице и применяет ко всем её элементам **func**, которая возвращает новое значение элемента. + +```lua +table.filter(t: table, func: function(indx, value) ) -> table +``` + +Проходится по таблице с помощью **func**, которая возвращает **true** если элемент надо сохранить и **false**, если его надо удалить. + +```lua +table.set_default(t: table, key: number | string, default: any) -> any | default +``` + +Позволяет безопасно получать значение по указанному ключу. Если ключ существует в таблице, метод вернет его значение. Если ключ отсутствует, метод установит его со значением **default** и вернет его. + +```lua +table.flat(t: table) -> table +``` + +Возвращает "плоскую" версию исходной таблицы. + +```lua +table.deep_flat(t: table) -> table +``` + +Возвращает глубокую "плоскую" версию исходной таблицы. + +```lua +table.sub(arr: table, start: number | nil, stop: number | nil) -> table +``` + +Возвращает обрезанную версию таблицы с индекса **start** до индекса **stop** включительно, при этом пары ключ-значение не сохраняются в новой таблице. При значениях **nil** начинает с **1** и заканчивает **#arr** соответственно. ```lua table.tostring(t: table) -> string @@ -144,6 +180,24 @@ string.escape(str: string) -> string Экранирует строку. Является псевдонимом `utf8.escape`. +```lua +string.pad(str: string, size: number, [опционально] char: string) -> string +``` + +Добавляет **char** слева и справа от строки, пока её размер не будет равен **size**. По стандарту **char** равен символу пробела + +```lua +string.left_pad(str: string, size: number, [опционально] char: string) -> string +``` + +Добавляет **char** слева от строки, пока её размер не будет равен **size**. По стандарту **char** равен символу пробела + +```lua +string.right_pad(str: string, size: number, [опционально] char: string) -> string +``` + +Добавляет **char** справа от строки, пока её размер не будет равен **size**. По стандарту **char** равен символу пробела + ## Расширения для math ```lua @@ -170,6 +224,12 @@ math.round(num: number, [опционально] places: num) -> number Возвращает округлённое значение num до указанного количества знаков после запятой places. +```lua +math.sum(x: number, ... | t: table) -> number +``` + +Возвращает сумму всех принимаемых аргументов. Если в качестве аргумента была передана таблица, метод вернёт сумму всех её элементов. + ## Дополнительные глобальные функции В этом же скрипте также определены и другие глобальные функции которые доступны для использования. Ниже их список diff --git a/res/config/defaults.toml b/res/config/defaults.toml index 53de523f..dd2bd314 100644 --- a/res/config/defaults.toml +++ b/res/config/defaults.toml @@ -1 +1,2 @@ generator = "core:default" +player-entity = "" diff --git a/res/content/base/block_materials/grass_block.json b/res/content/base/block_materials/grass_block.json index d866ff40..d45672a2 100644 --- a/res/content/base/block_materials/grass_block.json +++ b/res/content/base/block_materials/grass_block.json @@ -1,5 +1,6 @@ { "steps-sound": "steps/grass", "place-sound": "blocks/ground_place", - "break-sound": "blocks/ground_break" + "break-sound": "blocks/ground_break", + "hit-sound": "blocks/ground_hit" } diff --git a/res/content/base/block_materials/ground.json b/res/content/base/block_materials/ground.json index 36b15aed..951cb5df 100644 --- a/res/content/base/block_materials/ground.json +++ b/res/content/base/block_materials/ground.json @@ -1,5 +1,6 @@ { "steps-sound": "steps/ground", "place-sound": "blocks/ground_place", - "break-sound": "blocks/ground_break" + "break-sound": "blocks/ground_break", + "hit-sound": "blocks/ground_hit" } diff --git a/res/content/base/block_materials/stone.json b/res/content/base/block_materials/stone.json index a5b6df37..43f579e7 100644 --- a/res/content/base/block_materials/stone.json +++ b/res/content/base/block_materials/stone.json @@ -1,5 +1,6 @@ { "steps-sound": "steps/stone", "place-sound": "blocks/stone_place", - "break-sound": "blocks/stone_break" + "break-sound": "blocks/stone_break", + "hit-sound": "blocks/stone_hit" } diff --git a/res/content/base/block_materials/wood.json b/res/content/base/block_materials/wood.json index d29b8a63..0c8e9a46 100644 --- a/res/content/base/block_materials/wood.json +++ b/res/content/base/block_materials/wood.json @@ -1,5 +1,6 @@ { "steps-sound": "steps/wood", "place-sound": "blocks/wood_place", - "break-sound": "blocks/wood_break" + "break-sound": "blocks/wood_break", + "hit-sound": "blocks/wood_hit" } diff --git a/res/content/base/blocks/dirt.json b/res/content/base/blocks/dirt.json index ff512254..060325a8 100644 --- a/res/content/base/blocks/dirt.json +++ b/res/content/base/blocks/dirt.json @@ -2,5 +2,5 @@ "texture": "dirt", "material": "base:ground", "surface-replacement": "base:grass_block", - "base:durability": 1.0 + "base:durability": 1.5 } diff --git a/res/content/base/blocks/grass.json b/res/content/base/blocks/grass.json index e55bc2ac..d5b8472e 100644 --- a/res/content/base/blocks/grass.json +++ b/res/content/base/blocks/grass.json @@ -8,7 +8,7 @@ "replaceable": true, "grounded": true, "model": "X", - "hitbox": [0.15, 0.0, 0.15, 0.7, 0.7, 0.7], + "hitbox": [0.15, 0.0, 0.15, 0.7, 0.5, 0.7], "base:durability": 0.0, "base:loot": [] } diff --git a/res/content/base/blocks/grass_block.json b/res/content/base/blocks/grass_block.json index e89a774c..3a55bc5e 100644 --- a/res/content/base/blocks/grass_block.json +++ b/res/content/base/blocks/grass_block.json @@ -8,7 +8,7 @@ "grass_side", "grass_side" ], - "base:durability": 1.3, + "base:durability": 1.7, "base:loot": [ {"item": "base:dirt.item"} ] diff --git a/res/content/base/config/defaults.toml b/res/content/base/config/defaults.toml index d292d2ce..a099c198 100644 --- a/res/content/base/config/defaults.toml +++ b/res/content/base/config/defaults.toml @@ -1 +1,2 @@ generator = "base:demo" +player-entity = "base:player" diff --git a/res/content/base/generators/demo.files/script.lua b/res/content/base/generators/demo.files/script.lua index 9a995224..7a3282c3 100644 --- a/res/content/base/generators/demo.files/script.lua +++ b/res/content/base/generators/demo.files/script.lua @@ -1,7 +1,30 @@ local _, dir = parse_path(__DIR__) local ores = require "base:generation/ores" +math.randomseed(SEED) ores.load(dir) +local function get_rand(seed, x, y, z) + local h = bit.bxor(bit.bor(x * 23729, y % 16786), y * x + seed) + h = bit.bxor(h, z * 47917) + h = bit.bxor(h, bit.bor(z % 12345, x + y)) + + local n = (h % 10000) / 10000.0 + + return n +end + +local function gen_parameters(size, seed, x, y) + local res = {} + local rand = 0 + + for i=1, size do + rand = get_rand(seed, x, y, rand) + table.insert(res, rand) + end + + return res +end + function place_structures(x, z, w, d, hmap, chunk_height) local placements = {} ores.place(placements, x, z, w, d, SEED, hmap, chunk_height) @@ -10,15 +33,17 @@ end function place_structures_wide(x, z, w, d, chunk_height) local placements = {} - if math.random() < 0.05 then -- generate caves - local sx = x + math.random() * 10 - 5 - local sy = math.random() * (chunk_height / 4) + 10 - local sz = z + math.random() * 10 - 5 + local rands = gen_parameters(11, SEED, x, z) + if rands[1] < 0.05 then -- generate caves - local dir = math.random() * math.pi * 2 - local dir_inertia = (math.random() - 0.5) * 2 + local sx = x + rands[2] * 10 - 5 + local sy = rands[3] * (chunk_height / 4) + 10 + local sz = z + rands[4] * 10 - 5 + + local dir = rands[5] * math.pi * 2 + local dir_inertia = (rands[6] - 0.5) * 2 local elevation = -3 - local width = math.random() * 3 + 2 + local width = rands[7] * 3 + 2 for i=1,18 do local dx = math.sin(dir) * 10 @@ -34,11 +59,11 @@ function place_structures_wide(x, z, w, d, chunk_height) sx = ex sy = ey sz = ez - + dir_inertia = dir_inertia * 0.8 + - (math.random() - 0.5) * math.pow(math.random(), 2) * 8 + (rands[8] - 0.5) * math.pow(rands[9], 2) * 8 elevation = elevation * 0.9 + - (math.random() - 0.4) * (1.0-math.pow(math.random(), 4)) * 8 + (rands[10] - 0.4) * (1.0-math.pow(rands[11], 4)) * 8 dir = dir + dir_inertia end end diff --git a/res/content/base/items/bazalt_breaker.json b/res/content/base/items/bazalt_breaker.json index 59aeabdb..9e70fa55 100644 --- a/res/content/base/items/bazalt_breaker.json +++ b/res/content/base/items/bazalt_breaker.json @@ -1,4 +1,5 @@ { "icon-type": "sprite", - "icon": "items:bazalt_breaker" + "icon": "items:bazalt_breaker", + "uses": 100 } diff --git a/res/content/base/modules/util.lua b/res/content/base/modules/util.lua index d12337fb..b1a97ad0 100644 --- a/res/content/base/modules/util.lua +++ b/res/content/base/modules/util.lua @@ -1,12 +1,13 @@ local util = {} -function util.drop(ppos, itemid, count, pickup_delay) +function util.drop(ppos, itemid, count, data, pickup_delay) if itemid == 0 or not itemid then return nil end return entities.spawn("base:drop", ppos, {base__drop={ id=itemid, count=count, + data=data, pickup_delay=pickup_delay }}) end diff --git a/res/content/base/package.json b/res/content/base/package.json index 2878d3a6..4f992e4c 100644 --- a/res/content/base/package.json +++ b/res/content/base/package.json @@ -1,6 +1,6 @@ { "id": "base", "title": "Base", - "version": "0.26", + "version": "0.27", "description": "basic content package" } diff --git a/res/content/base/scripts/bazalt_breaker.lua b/res/content/base/scripts/bazalt_breaker.lua index 78b872b2..8869eca5 100644 --- a/res/content/base/scripts/bazalt_breaker.lua +++ b/res/content/base/scripts/bazalt_breaker.lua @@ -1,3 +1,6 @@ -function on_block_break_by(x, y, z, p) +function on_block_break_by(x, y, z, pid) block.set(x, y, z, 0, 0) + if not player.is_infinite_items(pid) then + inventory.use(player.get_inventory(pid)) + end end diff --git a/res/content/base/scripts/components/drop.lua b/res/content/base/scripts/components/drop.lua index 12e16265..cf57f72f 100644 --- a/res/content/base/scripts/components/drop.lua +++ b/res/content/base/scripts/components/drop.lua @@ -6,13 +6,15 @@ inair = true target = -1 timer = 0.3 -local dropitem = ARGS +local def_index = entity:def_index() +dropitem = ARGS if dropitem then timer = dropitem.pickup_delay or timer end if SAVED_DATA.item then dropitem.id = item.index(SAVED_DATA.item) dropitem.count = SAVED_DATA.count + dropitem.data = SAVED_DATA.data end local DROP_SCALE = 0.3 @@ -24,6 +26,7 @@ local rotation = mat4.rotate({ function on_save() SAVED_DATA.item = item.name(dropitem.id) SAVED_DATA.count = dropitem.count + SAVED_DATA.data = dropitem.data end do -- setup visuals @@ -38,7 +41,7 @@ end function on_grounded(force) local matrix = mat4.idt() - mat4.rotate(matrix, {0, 1, 0}, math.random()*360, matrix) + mat4.rotate(matrix, {0, 1, 0}, math.random() * 360, matrix) mat4.rotate(matrix, {1, 0, 0}, 90, matrix) mat4.scale(matrix, scale, matrix) rig:set_matrix(0, matrix) @@ -50,14 +53,33 @@ function on_fall() end function on_sensor_enter(index, oid) - local playerid = hud.get_player() - local playerentity = player.get_entity(playerid) - if timer < 0.0 and oid == playerentity and index == 0 then - entity:despawn() - inventory.add(player.get_inventory(playerid), dropitem.id, dropitem.count) - audio.play_sound_2d("events/pickup", 0.5, 0.8+math.random()*0.4, "regular") + local other = entities.get(oid) + if not other then + return end - if index == 1 and oid == playerentity then + local pid = other:get_player() + if pid == -1 then + -- other is base:drop too + if index == 0 and other:def_index() == def_index then + local odrop = other:get_component("base:drop").dropitem + if odrop.id == dropitem.id and not odrop.data then + local stack = item.stack_size(dropitem.id) + local sum = dropitem.count + odrop.count + if sum <= stack then + dropitem.count = sum + other:despawn() + end + end + end + return + end + + if timer < 0.0 and index == 0 then + entity:despawn() + inventory.add(player.get_inventory(pid), dropitem.id, dropitem.count, dropitem.data) + audio.play_sound_2d("events/pickup", 0.5, 0.8 + math.random() * 0.4, "regular") + end + if index == 1 then target = oid end end @@ -84,15 +106,18 @@ end function on_update(tps) timer = timer - 1.0/tps - if target ~= -1 then - if timer > 0.0 then - return - end - local dir = vec3.sub(entities.get(target).transform:get_pos(), tsf:get_pos()) - vec3.normalize(dir, dir) - vec3.mul(dir, 10.0, dir) - body:set_vel(dir) + + if timer > 0.0 or target == -1 then + return end + local target_entity = entities.get(target) + if not target_entity then + return + end + local dir = vec3.sub(target_entity.transform:get_pos(), tsf:get_pos()) + vec3.normalize(dir, dir) + vec3.mul(dir, 10.0, dir) + body:set_vel(dir) end function on_attacked(attacker, pid) diff --git a/res/content/base/scripts/components/player_animator.lua b/res/content/base/scripts/components/player_animator.lua index 8862c6c2..78049a2c 100644 --- a/res/content/base/scripts/components/player_animator.lua +++ b/res/content/base/scripts/components/player_animator.lua @@ -19,7 +19,7 @@ function on_render() return end - local rx, ry, rz = player.get_rot(pid, true) + local rx, ry, rz = player.get_rot(pid, pid ~= hud.get_player()) rig:set_matrix(headIndex, mat4.rotate({1, 0, 0}, ry)) rig:set_matrix(bodyIndex, mat4.rotate({0, 1, 0}, rx)) diff --git a/res/content/base/scripts/hud.lua b/res/content/base/scripts/hud.lua index 31125631..00f6bdc6 100644 --- a/res/content/base/scripts/hud.lua +++ b/res/content/base/scripts/hud.lua @@ -14,12 +14,13 @@ function on_hud_open() if itemid == 0 then return end + local data = inventory.get_all_data(invid, slot) inventory.set(invid, slot, itemid, itemcount-1) local pvel = {player.get_vel(pid)} local ppos = vec3.add({player.get_pos(pid)}, {0, 0.7, 0}) local throw_force = vec3.mul(player.get_dir(pid), DROP_FORCE) - local drop = base_util.drop(ppos, itemid, 1, 1.5) + local drop = base_util.drop(ppos, itemid, 1, data, 1.5) local velocity = vec3.add(throw_force, vec3.add(pvel, DROP_INIT_VEL)) drop.rigidbody:set_vel(velocity) end) diff --git a/res/content/base/sounds/blocks/ground_hit.ogg b/res/content/base/sounds/blocks/ground_hit.ogg new file mode 100644 index 00000000..f59207cd Binary files /dev/null and b/res/content/base/sounds/blocks/ground_hit.ogg differ diff --git a/res/content/base/sounds/blocks/stone_break.ogg b/res/content/base/sounds/blocks/stone_break.ogg index b54a78a8..e35369cd 100644 Binary files a/res/content/base/sounds/blocks/stone_break.ogg and b/res/content/base/sounds/blocks/stone_break.ogg differ diff --git a/res/content/base/sounds/blocks/stone_hit.ogg b/res/content/base/sounds/blocks/stone_hit.ogg new file mode 100644 index 00000000..cb956164 Binary files /dev/null and b/res/content/base/sounds/blocks/stone_hit.ogg differ diff --git a/res/content/base/sounds/blocks/wood_hit.ogg b/res/content/base/sounds/blocks/wood_hit.ogg new file mode 100644 index 00000000..8f8bea0f Binary files /dev/null and b/res/content/base/sounds/blocks/wood_hit.ogg differ diff --git a/res/scripts/stdlib.lua b/res/scripts/stdlib.lua index 9d29b873..8a5f66eb 100644 --- a/res/scripts/stdlib.lua +++ b/res/scripts/stdlib.lua @@ -94,6 +94,31 @@ elseif __vc_app then complete_app_lib(__vc_app) end +function inventory.get_uses(invid, slot) + local uses = inventory.get_data(invid, slot, "uses") + if uses == nil then + return item.uses(inventory.get(invid, slot)) + end + return uses +end + + +function inventory.use(invid, slot) + local itemid, count = inventory.get(invid, slot) + if itemid == nil then + return + end + local item_uses = inventory.get_uses(invid, slot) + if item_uses == nil then + return + end + if item_uses == 1 then + inventory.set(invid, slot, itemid, count - 1) + elseif item_uses > 1 then + inventory.set_data(invid, slot, "uses", item_uses - 1) + end +end + ------------------------------------------------ ------------------- Events --------------------- ------------------------------------------------ @@ -330,7 +355,7 @@ function __vc_on_hud_open() hud._set_debug_cheats(value) end) input.add_callback("devtools.console", function() - if hud.is_paused() then + if menu.page ~= "" then return end time.post_runnable(function() @@ -338,7 +363,7 @@ function __vc_on_hud_open() end) end) input.add_callback("hud.chat", function() - if hud.is_paused() then + if menu.page ~= "" then return end time.post_runnable(function() @@ -419,8 +444,9 @@ end function start_coroutine(chunk, name) local co = coroutine.create(function() - local status, error = xpcall(chunk, function(...) - gui.alert(debug.traceback(), function() + local status, error = xpcall(chunk, function(err) + local fullmsg = "error: "..string.match(err, ": (.+)").."\n"..debug.traceback() + gui.alert(fullmsg, function() if world.is_open() then __vc_app.close_world() else @@ -429,7 +455,7 @@ function start_coroutine(chunk, name) menu.page = "main" end end) - return ... + return fullmsg end) if not status then debug.error(error) diff --git a/res/scripts/stdmin.lua b/res/scripts/stdmin.lua index c993af6a..5ebf0176 100644 --- a/res/scripts/stdmin.lua +++ b/res/scripts/stdmin.lua @@ -64,6 +64,23 @@ function math.round(num, places) return math.floor(num * mult + 0.5) / mult end +function math.sum(...) + local numbers = nil + local sum = 0 + + if type(...) == "table" then + numbers = ... + else + numbers = {...} + end + + for _, v in ipairs(numbers) do + sum = sum + v + end + + return sum +end + ---------------------------------------------- function table.copy(t) @@ -125,6 +142,73 @@ function table.merge(t1, t2) return t1 end +function table.map(t, func) + for i, v in pairs(t) do + t[i] = func(i, v) + end + + return t +end + +function table.filter(t, func) + for i, v in pairs(t) do + if not func(i, v) then + t[i] = nil + end + end + + return t +end + +function table.set_default(t, key, default) + if t[key] == nil then + t[key] = default + return default + end + + return t[key] +end + +function table.flat(t) + local flat = {} + + for _, v in pairs(t) do + if type(v) == "table" then + table.merge(flat, v) + else + table.insert(flat, v) + end + end + + return flat +end + +function table.deep_flat(t) + local flat = {} + + for _, v in pairs(t) do + if type(v) == "table" then + table.merge(flat, table.deep_flat(v)) + else + table.insert(flat, v) + end + end + + return flat +end + +function table.sub(arr, start, stop) + local res = {} + start = start or 1 + stop = stop or #arr + + for i = start, stop do + table.insert(res, arr[i]) + end + + return res +end + ---------------------------------------------- local pattern_escape_replacements = { @@ -208,6 +292,29 @@ function string.trim_left(s, char) return string.match(s, "^" .. char .. "*(.+)$") or s end +function string.pad(str, size, char) + char = char == nil and " " or char + + local padding = math.floor((size - #str) / 2) + local extra_padding = (size - #str) % 2 + + return string.rep(char, padding) .. str .. string.rep(char, padding + extra_padding) +end + +function string.left_pad(str, size, char) + char = char == nil and " " or char + + local left_padding = size - #str + return string.rep(char, left_padding) .. str +end + +function string.right_pad(str, size, char) + char = char == nil and " " or char + + local right_padding = size - #str + return str .. string.rep(char, right_padding) +end + string.lower = utf8.lower string.upper = utf8.upper string.escape = utf8.escape diff --git a/res/texts/be_BY.txt b/res/texts/be_BY.txt index 4d325060..624e2028 100644 --- a/res/texts/be_BY.txt +++ b/res/texts/be_BY.txt @@ -1,33 +1,43 @@ -# Общее +# Агульнае Yes=Так No=Не Ok=Ок Cancel=Скасаваць Back=Назад -Continue=Працягнуть +Continue=Працягнуць Add=Дадаць Version=Версія Creator=Аўтар Dependencies=Залежнасці Description=Апісанне Converting world...=Выконваецца канвертацыя свету... +Unlimited=Бязмежна +Chat=Чат +Console=Кансоль +Log=Лог +Problems=Праблемы +Monitor=Маніторынг +Debug=Адладка +File=Файл +devtools.traceback=Стэк выклікаў (ад апошняга) error.pack-not-found=Не ўдалося знайсці пакет error.dependency-not-found=Выкарыстоўваная залежнасць не знойдзена -pack.remove-confirm=Выдаліць увесь кантэнт які пастаўляецца пакам са свету (беззваротна)? +pack.remove-confirm=Выдаліць увесь кантэнт, які пастаўляецца пакетам/пакетамі, са свету (беззваротна)? -# Подсказки +# Падказкі graphics.gamma.tooltip=Крывая яркасці асвятлення graphics.backlight.tooltip=Падсветка, якая прадухіляе поўную цемру +graphics.dense-render.tooltip=Уключае празрыстасць блокаў, такіх як лісце. # Меню menu.Apply=Ужыць menu.Audio=Гук menu.Back to Main Menu=Вярнуцца ў Меню +menu.Scripts=Сцэнарыі menu.Content Error=Памылка Кантэнту menu.Content=Кантэнт -menu.Contents Menu=Меню Кантэнтпакаў -menu.Continue=Працягнуть +menu.Continue=Працягнуць menu.Controls=Кіраванне menu.Display=Дысплей menu.Graphics=Графіка @@ -39,6 +49,10 @@ menu.Page not found=Старонка не знойдзена menu.Quit=Выхад menu.Save and Quit to Menu=Захаваць і Выйсці ў Меню menu.Settings=Налады +menu.Reset settings=Скінуць налады +menu.Contents Menu=Меню кантэнт-пакаў +menu.Open data folder=Адкрыць папку з дадзенымі +menu.Open content folder=Адкрыць папку [content] world.Seed=Зерне world.Name=Назва @@ -47,43 +61,55 @@ world.generators.default=Звычайны world.generators.flat=Плоскі world.Create World=Стварыць Свет world.convert-request=Ёсць змены ў індэксах! Канвертаваць свет? -world.delete-confirm=Выдаліць свет незваротна? +world.upgrade-request=Фармат свету састарэў! Канвертаваць свет? +world.convert-with-loss=Канвертаваць свет з стратамі? +world.convert-block-layouts=Ёсць змены ў палях блокаў! Канвертаваць свет? +world.delete-confirm=Выдаліць свет беззваротна? -# Настройки +# Налады settings.Ambient=Фон settings.Backlight=Падсветка -settings.Camera Shaking=Труска Камеры -settings.Camera Inertia=Інэрцыя Камеры -settings.Fog Curve=Крывая Туману -settings.FOV=Поле Зроку +settings.Dense blocks render=Шчыльны рэндэр блокаў +settings.Camera Shaking=Труска камеры +settings.Camera Inertia=Інэрцыя камеры +settings.Camera FOV Effects=Эфекты поля зроку +settings.Fog Curve=Крывая туману +settings.FOV=Поле зроку settings.Fullscreen=Поўны экран +settings.Framerate=Частата кадраў settings.Gamma=Гама settings.Language=Мова -settings.Load Distance=Дыстанцыя Загрузкі -settings.Load Speed=Хуткасць Загрузкі -settings.Master Volume=Агульная Гучнасць -settings.Mouse Sensitivity=Адчувальнасць Мышы +settings.Load Distance=Дыстанцыя загрузкі +settings.Load Speed=Хуткасць загрузкі +settings.Master Volume=Агульная гучнасць +settings.Mouse Sensitivity=Адчувальнасць мышы settings.Music=Музыка -settings.Regular Sounds=Звычайныя Гукі -settings.UI Sounds=Гукі Інтэрфейсу -settings.V-Sync=Вертыкальная Сінхранізацыя +settings.Regular Sounds=Звычайныя гукі +settings.UI Sounds=Гукі інтэрфейсу +settings.V-Sync=Вертыкальная сінхранізацыя +settings.Key=Кнопка +settings.Controls Search Mode=Пошук па прывязанай кнопцы кіравання +settings.Limit Background FPS=Абмежаваць фонавую частату кадраў -# Управление -chunks.reload=Перезагрузіць Чанкі +# Кіраванне +chunks.reload=Перазагрузіць чанкі devtools.console=Кансоль movement.forward=Уперад movement.back=Назад movement.left=Улева movement.right=Управа movement.jump=Скачок -movement.sprint=Ускорение +movement.sprint=Паскарэнне movement.crouch=Красціся movement.cheat=Чыт -hud.inventory=Рыштунак -player.pick=Падабраць Блок -player.attack=Атакаваць / Ламаць -player.build=Паставіць Блок +hud.inventory=Інвентар +hud.chat=Чат +player.pick=Падабраць блок +player.attack=Атакаваць +player.destroy=Зламаць +player.build=Паставіць блок +player.fast_interaction=Паскоранае ўзаемадзеянне player.flight=Палёт -player.drop=Выкінуць Прадмет +player.drop=Выкінуць прадмет camera.zoom=Прыбліжэнне -camera.mode=Змяніць Рэжым Камеры +camera.mode=Змяніць рэжым камеры \ No newline at end of file diff --git a/run.sh b/run.sh index 0bf5a60a..612f0693 100755 --- a/run.sh +++ b/run.sh @@ -1,6 +1,66 @@ -mkdir -p build -cd build -cmake -DCMAKE_BUILD_TYPE=Release .. -cmake --build . -j$(nproc) -cd .. -build/VoxelEngine +#!/bin/bash + + + +function delete { + echo "[RUN SCRIPT] Delete build directory" + rm -rf build +} + + +function build { + echo "[RUN SCRIPT] Build project" + mkdir -p build + cd build + cmake -DCMAKE_BUILD_TYPE=Release .. + cmake --build . -j$(nproc) + cd .. +} + + +function rebuild { + delete + build +} + + +run=true +function norun { + echo "[RUN SCRIPT] Build without run" + run= +} + + +function help { + echo "[RUN SCRIPT] Usage: ./run [ARGUMENT]..." + echo "[RUN SCRIPT] Arguments:" + echo "[RUN SCRIPT] -d, --delete Delete build directory" + echo "[RUN SCRIPT] -b, --build Build project" + echo "[RUN SCRIPT] -r, --rebuild Rebuild project" + echo "[RUN SCRIPT] -R, --norun Build without run" + echo "[RUN SCRIPT] -h, --help Print this page" +} + + +while [ -n "$1" ]; do + case "$1" in + -d | --delete) delete ;; + -b | --build) build ;; + -r | --rebuild) rebuild ;; + -R | --norun) norun ;; + -h | --help) help + norun + break ;; + *) echo "[RUN SCRIPT] Unknown argument: $1" + help + norun + break ;; + esac + shift +done + + +if [[ $run ]]; then + echo "[RUN SCRIPT] Run project" + ./build/VoxelEngine +fi \ No newline at end of file diff --git a/src/assets/AssetsLoader.cpp b/src/assets/AssetsLoader.cpp index 3f050055..e068295b 100644 --- a/src/assets/AssetsLoader.cpp +++ b/src/assets/AssetsLoader.cpp @@ -9,8 +9,8 @@ #include "content/Content.hpp" #include "content/ContentPack.hpp" #include "debug/Logger.hpp" -#include "files/engine_paths.hpp" -#include "files/files.hpp" +#include "io/engine_paths.hpp" +#include "io/io.hpp" #include "graphics/core/Texture.hpp" #include "logic/scripting/scripting.hpp" #include "objects/rigging.hpp" @@ -20,6 +20,8 @@ #include "Assets.hpp" #include "assetload_funcs.hpp" +namespace fs = std::filesystem; + static debug::Logger logger("assets-loader"); AssetsLoader::AssetsLoader(Assets* assets, const ResPaths* paths) @@ -83,22 +85,21 @@ void AssetsLoader::loadNext() { } } -void addLayouts( +static void add_layouts( const scriptenv& env, const std::string& prefix, - const fs::path& folder, + const io::path& folder, AssetsLoader& loader ) { - if (!fs::is_directory(folder)) { + if (!io::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(); + for (const auto& file : io::directory_iterator(folder)) { + if (file.extension() != ".xml") continue; + std::string name = prefix + ":" + file.stem(); loader.add( AssetType::LAYOUT, - file.u8string(), + file.string(), name, std::make_shared(env) ); @@ -186,8 +187,8 @@ void AssetsLoader::processPreloadList(AssetType tag, const dv::value& list) { } } -void AssetsLoader::processPreloadConfig(const fs::path& file) { - auto root = files::read_json(file); +void AssetsLoader::processPreloadConfig(const io::path& file) { + auto root = io::read_json(file); processPreloadList(AssetType::ATLAS, root["atlases"]); processPreloadList(AssetType::FONT, root["fonts"]); processPreloadList(AssetType::SHADER, root["shaders"]); @@ -198,8 +199,8 @@ void AssetsLoader::processPreloadConfig(const fs::path& file) { } void AssetsLoader::processPreloadConfigs(const Content* content) { - auto preloadFile = paths->getMainRoot() / fs::path("preload.json"); - if (fs::exists(preloadFile)) { + auto preloadFile = paths->getMainRoot() / "preload.json"; + if (io::exists(preloadFile)) { processPreloadConfig(preloadFile); } if (content == nullptr) { @@ -210,8 +211,8 @@ void AssetsLoader::processPreloadConfigs(const Content* content) { continue; } const auto& pack = entry.second; - auto preloadFile = pack->getInfo().folder / fs::path("preload.json"); - if (fs::exists(preloadFile)) { + auto preloadFile = pack->getInfo().folder / "preload.json"; + if (io::exists(preloadFile)) { processPreloadConfig(preloadFile); } } @@ -225,13 +226,14 @@ void AssetsLoader::addDefaults(AssetsLoader& loader, const Content* content) { loader.tryAddSound(material.stepsSound); loader.tryAddSound(material.placeSound); loader.tryAddSound(material.breakSound); + loader.tryAddSound(material.hitSound); } for (auto& entry : content->getPacks()) { auto pack = entry.second.get(); auto& info = pack->getInfo(); - fs::path folder = info.folder / fs::path("layouts"); - addLayouts(pack->getEnvironment(), info.id, folder, loader); + io::path folder = info.folder / "layouts"; + add_layouts(pack->getEnvironment(), info.id, folder, loader); } for (auto& entry : content->getSkeletons()) { @@ -274,20 +276,20 @@ void AssetsLoader::addDefaults(AssetsLoader& loader, const Content* content) { bool AssetsLoader::loadExternalTexture( Assets* assets, const std::string& name, - const std::vector& alternatives + const std::vector& alternatives ) { if (assets->get(name) != nullptr) { return true; } for (auto& path : alternatives) { - if (fs::exists(path)) { + if (io::exists(path)) { try { auto image = imageio::read(path); assets->store(Texture::from(image.get()), name); return true; } catch (const std::exception& err) { logger.error() << "error while loading external " - << path.u8string() << ": " << err.what(); + << path.string() << ": " << err.what(); } } } diff --git a/src/assets/AssetsLoader.hpp b/src/assets/AssetsLoader.hpp index cd6db950..bf117a3d 100644 --- a/src/assets/AssetsLoader.hpp +++ b/src/assets/AssetsLoader.hpp @@ -1,6 +1,5 @@ #pragma once -#include #include #include #include @@ -14,6 +13,7 @@ #include "typedefs.hpp" #include "Assets.hpp" #include "data/dv.hpp" +#include "io/fwd.hpp" class ResPaths; class AssetsLoader; @@ -73,7 +73,7 @@ class AssetsLoader { AssetType tag, const std::string& name, const dv::value& map ); void processPreloadList(AssetType tag, const dv::value& list); - void processPreloadConfig(const std::filesystem::path& file); + void processPreloadConfig(const io::path& file); void processPreloadConfigs(const Content* content); public: AssetsLoader(Assets* assets, const ResPaths* paths); @@ -109,6 +109,6 @@ public: static bool loadExternalTexture( Assets* assets, const std::string& name, - const std::vector& alternatives + const std::vector& alternatives ); }; diff --git a/src/assets/assetload_funcs.cpp b/src/assets/assetload_funcs.cpp index 399e7959..f8bcd84d 100644 --- a/src/assets/assetload_funcs.cpp +++ b/src/assets/assetload_funcs.cpp @@ -13,8 +13,8 @@ #include "coders/vec3.hpp" #include "constants.hpp" #include "debug/Logger.hpp" -#include "files/engine_paths.hpp" -#include "files/files.hpp" +#include "io/engine_paths.hpp" +#include "io/io.hpp" #include "frontend/UiDocument.hpp" #include "graphics/core/Atlas.hpp" #include "graphics/core/Font.hpp" @@ -48,16 +48,14 @@ assetload::postfunc assetload::texture( const std::string& name, const std::shared_ptr& ) { - auto actualFile = paths->find(filename + ".png").u8string(); + auto actualFile = paths->find(filename + ".png"); try { - std::shared_ptr image( - imageio::read(fs::u8path(actualFile)).release() - ); + std::shared_ptr image(imageio::read(actualFile).release()); return [name, image, actualFile](auto assets) { assets->store(Texture::from(image.get()), name); }; } catch (const std::runtime_error& err) { - logger.error() << actualFile << ": " << err.what(); + logger.error() << actualFile.string() << ": " << err.what(); return [](auto) {}; } } @@ -69,11 +67,11 @@ assetload::postfunc assetload::shader( const std::string& name, const std::shared_ptr& ) { - fs::path vertexFile = paths->find(filename + ".glslv"); - fs::path fragmentFile = paths->find(filename + ".glslf"); + io::path vertexFile = paths->find(filename + ".glslv"); + io::path fragmentFile = paths->find(filename + ".glslf"); - std::string vertexSource = files::read_string(vertexFile); - std::string fragmentSource = files::read_string(fragmentFile); + std::string vertexSource = io::read_string(vertexFile); + std::string fragmentSource = io::read_string(fragmentFile); vertexSource = Shader::preprocessor->process(vertexFile, vertexSource); fragmentSource = @@ -82,8 +80,8 @@ assetload::postfunc assetload::shader( return [=](auto assets) { assets->store( Shader::create( - vertexFile.u8string(), - fragmentFile.u8string(), + vertexFile.string(), + fragmentFile.string(), vertexSource, fragmentSource ), @@ -92,8 +90,8 @@ assetload::postfunc assetload::shader( }; } -static bool append_atlas(AtlasBuilder& atlas, const fs::path& file) { - std::string name = file.stem().string(); +static bool append_atlas(AtlasBuilder& atlas, const io::path& file) { + std::string name = file.stem(); // skip duplicates if (atlas.has(name)) { return false; @@ -114,19 +112,19 @@ assetload::postfunc assetload::atlas( auto atlasConfig = std::dynamic_pointer_cast(config); if (atlasConfig && atlasConfig->type == AtlasType::SEPARATE) { for (const auto& file : paths->listdir(directory)) { - if (!imageio::is_read_supported(file.extension().u8string())) + if (!imageio::is_read_supported(file.extension())) continue; loader->add( AssetType::TEXTURE, - directory + "/" + file.stem().u8string(), - name + "/" + file.stem().u8string() + directory + "/" + file.stem(), + name + "/" + file.stem() ); } return [](auto){}; } AtlasBuilder builder; for (const auto& file : paths->listdir(directory)) { - if (!imageio::is_read_supported(file.extension().u8string())) continue; + if (!imageio::is_read_supported(file.extension())) continue; if (!append_atlas(builder, file)) continue; } std::set names = builder.getNames(); @@ -151,7 +149,7 @@ assetload::postfunc assetload::font( for (size_t i = 0; i <= 1024; i++) { std::string pagefile = filename + "_" + std::to_string(i) + ".png"; auto file = paths->find(pagefile); - if (fs::exists(file)) { + if (io::exists(file)) { pages->push_back(imageio::read(file)); } else if (i == 0) { throw std::runtime_error("font must have page 0"); @@ -222,13 +220,13 @@ assetload::postfunc assetload::sound( extension = extensions[i]; // looking for 'sound_name' as base sound auto soundFile = paths->find(file + extension); - if (fs::exists(soundFile)) { + if (io::exists(soundFile)) { baseSound = audio::load_sound(soundFile, keepPCM); break; } // looking for 'sound_name_0' as base sound auto variantFile = paths->find(file + "_0" + extension); - if (fs::exists(variantFile)) { + if (io::exists(variantFile)) { baseSound = audio::load_sound(variantFile, keepPCM); break; } @@ -241,7 +239,7 @@ assetload::postfunc assetload::sound( for (uint i = 1;; i++) { auto variantFile = paths->find(file + "_" + std::to_string(i) + extension); - if (!fs::exists(variantFile)) { + if (!io::exists(variantFile)) { break; } baseSound->variants.emplace_back(audio::load_sound(variantFile, keepPCM)); @@ -272,9 +270,9 @@ assetload::postfunc assetload::model( const std::shared_ptr& ) { auto path = paths->find(file + ".vec3"); - if (fs::exists(path)) { - auto bytes = files::read_bytes_buffer(path); - auto modelVEC3 = std::make_shared(vec3::load(path.u8string(), bytes)); + if (io::exists(path)) { + auto bytes = io::read_bytes_buffer(path); + auto modelVEC3 = std::make_shared(vec3::load(path.string(), bytes)); return [loader, name, modelVEC3=std::move(modelVEC3)](Assets* assets) { for (auto& [modelName, model] : modelVEC3->models) { request_textures(loader, model.model); @@ -292,9 +290,9 @@ assetload::postfunc assetload::model( }; } path = paths->find(file + ".obj"); - auto text = files::read_string(path); + auto text = io::read_string(path); try { - auto model = obj::parse(path.u8string(), text).release(); + auto model = obj::parse(path.string(), text).release(); return [=](Assets* assets) { request_textures(loader, *model); assets->store(std::unique_ptr(model), name); @@ -309,7 +307,7 @@ static void read_anim_file( const std::string& animFile, std::vector>& frameList ) { - auto root = files::read_json(animFile); + auto root = io::read_json(animFile); float frameDuration = DEFAULT_FRAME_DURATION; std::string frameName; @@ -394,21 +392,21 @@ static bool load_animation( std::string animsDir = directory + "/animation"; for (const auto& folder : paths->listdir(animsDir)) { - if (!fs::is_directory(folder)) continue; - if (folder.filename().u8string() != name) continue; - if (fs::is_empty(folder)) continue; + if (!io::is_directory(folder)) continue; + if (folder.name() != name) continue; + //FIXME: if (fs::is_empty(folder)) continue; AtlasBuilder builder; append_atlas(builder, paths->find(directory + "/" + name + ".png")); std::vector> frameList; - std::string animFile = folder.u8string() + "/animation.json"; - if (fs::exists(animFile)) { + std::string animFile = folder.string() + "/animation.json"; + if (io::exists(animFile)) { read_anim_file(animFile, frameList); } for (const auto& file : paths->listdir(animsDir + "/" + name)) { if (!frameList.empty() && - !contains(frameList, file.stem().u8string())) { + !contains(frameList, file.stem())) { continue; } if (!append_atlas(builder, file)) continue; diff --git a/src/audio/AL/ALAudio.cpp b/src/audio/AL/ALAudio.cpp index 6843cba0..7f28882b 100644 --- a/src/audio/AL/ALAudio.cpp +++ b/src/audio/AL/ALAudio.cpp @@ -278,7 +278,7 @@ void ALSpeaker::play() { AL_CHECK(alSourcef( source, AL_GAIN, - volume * p_channel->getVolume() * get_channel(0)->getVolume() + volume * p_channel->getVolume() )); AL_CHECK(alSourcePlay(source)); } diff --git a/src/audio/audio.cpp b/src/audio/audio.cpp index 8ac8b713..761557af 100644 --- a/src/audio/audio.cpp +++ b/src/audio/audio.cpp @@ -4,6 +4,7 @@ #include #include +#include "io/io.hpp" #include "coders/ogg.hpp" #include "coders/wav.hpp" #include "AL/ALAudio.hpp" @@ -181,11 +182,11 @@ void audio::initialize(bool enabled, AudioSettings& settings) { } } -std::unique_ptr audio::load_PCM(const fs::path& file, bool headerOnly) { - if (!fs::exists(file)) { - throw std::runtime_error("file not found '" + file.u8string() + "'"); +std::unique_ptr audio::load_PCM(const io::path& file, bool headerOnly) { + if (!io::exists(file)) { + throw std::runtime_error("file not found '" + file.string() + "'"); } - std::string ext = file.extension().u8string(); + std::string ext = file.extension(); if (ext == ".wav" || ext == ".WAV") { return wav::load_pcm(file, headerOnly); } else if (ext == ".ogg" || ext == ".OGG") { @@ -194,7 +195,7 @@ std::unique_ptr audio::load_PCM(const fs::path& file, bool headerOnly) { throw std::runtime_error("unsupported audio format"); } -std::unique_ptr audio::load_sound(const fs::path& file, bool keepPCM) { +std::unique_ptr audio::load_sound(const io::path& file, bool keepPCM) { std::shared_ptr pcm( load_PCM(file, !keepPCM && backend->isDummy()).release() ); @@ -207,8 +208,8 @@ std::unique_ptr audio::create_sound( return backend->createSound(std::move(pcm), keepPCM); } -std::unique_ptr audio::open_PCM_stream(const fs::path& file) { - std::string ext = file.extension().u8string(); +std::unique_ptr audio::open_PCM_stream(const io::path& file) { + std::string ext = file.extension(); if (ext == ".wav" || ext == ".WAV") { return wav::create_stream(file); } else if (ext == ".ogg" || ext == ".OGG") { @@ -218,7 +219,7 @@ std::unique_ptr audio::open_PCM_stream(const fs::path& file) { } std::unique_ptr audio::open_stream( - const fs::path& file, bool keepSource + const io::path& file, bool keepSource ) { if (!keepSource && backend->isDummy()) { auto header = load_PCM(file, true); @@ -338,7 +339,7 @@ speakerid_t audio::play( } speakerid_t audio::play_stream( - const fs::path& file, + const io::path& file, glm::vec3 position, bool relative, float volume, diff --git a/src/audio/audio.hpp b/src/audio/audio.hpp index 37ae8e00..5893c334 100644 --- a/src/audio/audio.hpp +++ b/src/audio/audio.hpp @@ -1,14 +1,12 @@ #pragma once -#include #include #include #include #include "typedefs.hpp" #include "settings.hpp" - -namespace fs = std::filesystem; +#include "io/fwd.hpp" namespace audio { /// @brief playing speaker uid @@ -365,7 +363,7 @@ namespace audio { /// @param headerOnly read header only /// @throws std::runtime_error if I/O error ocurred or format is unknown /// @return PCM audio data - std::unique_ptr load_PCM(const fs::path& file, bool headerOnly); + std::unique_ptr load_PCM(const io::path& file, bool headerOnly); /// @brief Load sound from file /// @param file audio file path @@ -373,7 +371,7 @@ namespace audio { /// Sound::getPCM /// @throws std::runtime_error if I/O error ocurred or format is unknown /// @return new Sound instance - std::unique_ptr load_sound(const fs::path& file, bool keepPCM); + std::unique_ptr load_sound(const io::path& file, bool keepPCM); /// @brief Create new sound from PCM data /// @param pcm PCM data @@ -386,14 +384,14 @@ namespace audio { /// @param file audio file path /// @throws std::runtime_error if I/O error ocurred or format is unknown /// @return new PCMStream instance - std::unique_ptr open_PCM_stream(const fs::path& file); + std::unique_ptr open_PCM_stream(const io::path& file); /// @brief Open new audio stream from file /// @param file audio file path /// @param keepSource store PCMStream in stream to make it accessible with /// Stream::getSource /// @return new Stream instance - std::unique_ptr open_stream(const fs::path& file, bool keepSource); + std::unique_ptr open_stream(const io::path& file, bool keepSource); /// @brief Open new audio stream from source /// @param stream PCM data source @@ -464,7 +462,7 @@ namespace audio { /// @param channel channel index /// @return speaker id or 0 speakerid_t play_stream( - const fs::path& file, + const io::path& file, glm::vec3 position, bool relative, float volume, diff --git a/src/coders/GLSLExtension.cpp b/src/coders/GLSLExtension.cpp index 186522a5..b36e7a07 100644 --- a/src/coders/GLSLExtension.cpp +++ b/src/coders/GLSLExtension.cpp @@ -5,13 +5,10 @@ #include #include -#include "files/engine_paths.hpp" -#include "files/files.hpp" +#include "io/engine_paths.hpp" #include "typedefs.hpp" #include "util/stringutil.hpp" -namespace fs = std::filesystem; - void GLSLExtension::setVersion(std::string version) { this->version = std::move(version); } @@ -21,8 +18,8 @@ void GLSLExtension::setPaths(const ResPaths* paths) { } void GLSLExtension::loadHeader(const std::string& name) { - fs::path file = paths->find("shaders/lib/" + name + ".glsl"); - std::string source = files::read_string(file); + io::path file = paths->find("shaders/lib/" + name + ".glsl"); + std::string source = io::read_string(file); addHeader(name, ""); addHeader(name, process(file, source, true)); } @@ -66,7 +63,7 @@ void GLSLExtension::undefine(const std::string& name) { } inline std::runtime_error parsing_error( - const fs::path& file, uint linenum, const std::string& message + const io::path& file, uint linenum, const std::string& message ) { return std::runtime_error( "file " + file.string() + ": " + message + " at line " + @@ -75,7 +72,7 @@ inline std::runtime_error parsing_error( } inline void parsing_warning( - const fs::path& file, uint linenum, const std::string& message + const io::path& file, uint linenum, const std::string& message ) { std::cerr << "file " + file.string() + ": warning: " + message + " at line " + std::to_string(linenum) @@ -87,7 +84,7 @@ inline void source_line(std::stringstream& ss, uint linenum) { } std::string GLSLExtension::process( - const fs::path& file, const std::string& source, bool header + const io::path& file, const std::string& source, bool header ) { std::stringstream ss; size_t pos = 0; diff --git a/src/coders/GLSLExtension.hpp b/src/coders/GLSLExtension.hpp index 6187d7d4..c619c9d0 100644 --- a/src/coders/GLSLExtension.hpp +++ b/src/coders/GLSLExtension.hpp @@ -1,10 +1,11 @@ #pragma once -#include #include #include #include +#include "io/io.hpp" + class ResPaths; class GLSLExtension { @@ -29,7 +30,7 @@ public: bool hasDefine(const std::string& name) const; std::string process( - const std::filesystem::path& file, + const io::path& file, const std::string& source, bool header = false ); diff --git a/src/coders/imageio.cpp b/src/coders/imageio.cpp index a80f422c..36e1f76d 100644 --- a/src/coders/imageio.cpp +++ b/src/coders/imageio.cpp @@ -1,15 +1,12 @@ #include "imageio.hpp" -#include #include #include #include "graphics/core/ImageData.hpp" -#include "files/files.hpp" +#include "io/io.hpp" #include "png.hpp" -namespace fs = std::filesystem; - using image_reader = std::function(const ubyte*, size_t)>; using image_writer = std::function; @@ -30,33 +27,29 @@ bool imageio::is_write_supported(const std::string& extension) { return writers.find(extension) != writers.end(); } -inline std::string extensionOf(const std::string& filename) { - return fs::u8path(filename).extension().u8string(); -} - -std::unique_ptr imageio::read(const fs::path& filename) { - auto found = readers.find(extensionOf(filename.u8string())); +std::unique_ptr imageio::read(const io::path& file) { + auto found = readers.find(file.extension()); if (found == readers.end()) { throw std::runtime_error( - "file format is not supported (read): " + filename.u8string() + "file format is not supported (read): " + file.string() ); } - auto bytes = files::read_bytes_buffer(filename); + auto bytes = io::read_bytes_buffer(file); try { return std::unique_ptr(found->second(bytes.data(), bytes.size())); } catch (const std::runtime_error& err) { throw std::runtime_error( - "could not to load image " + filename.u8string() + ": " + err.what() + "could not to load image " + file.string() + ": " + err.what() ); } } -void imageio::write(const std::string& filename, const ImageData* image) { - auto found = writers.find(extensionOf(filename)); +void imageio::write(const io::path& file, const ImageData* image) { + auto found = writers.find(file.extension()); if (found == writers.end()) { throw std::runtime_error( - "file format is not supported (write): " + filename + "file format is not supported (write): " + file.string() ); } - return found->second(filename, image); + return found->second(io::resolve(file).u8string(), image); } diff --git a/src/coders/imageio.hpp b/src/coders/imageio.hpp index beab88ad..0e59ae37 100644 --- a/src/coders/imageio.hpp +++ b/src/coders/imageio.hpp @@ -2,7 +2,8 @@ #include #include -#include + +#include "io/fwd.hpp" class ImageData; @@ -12,6 +13,6 @@ namespace imageio { bool is_read_supported(const std::string& extension); bool is_write_supported(const std::string& extension); - std::unique_ptr read(const std::filesystem::path& file); - void write(const std::string& filename, const ImageData* image); + std::unique_ptr read(const io::path& file); + void write(const io::path& file, const ImageData* image); } diff --git a/src/coders/lua_parsing.cpp b/src/coders/lua_parsing.cpp index 3c0d3a53..b3492bae 100644 --- a/src/coders/lua_parsing.cpp +++ b/src/coders/lua_parsing.cpp @@ -80,7 +80,7 @@ public: return std::string({first}); case '-': skip(1); - if (peekNoJump() == '-') { + if (hasNext() && peekNoJump() == '-') { skip(1); return "--"; } diff --git a/src/coders/ogg.cpp b/src/coders/ogg.cpp index df5f9826..c5c27f49 100644 --- a/src/coders/ogg.cpp +++ b/src/coders/ogg.cpp @@ -5,6 +5,7 @@ #include +#include "io/io.hpp" #include "audio/audio.hpp" #include "debug/Logger.hpp" #include "typedefs.hpp" @@ -43,11 +44,11 @@ static inline std::string vorbis_error_message(int code) { } std::unique_ptr ogg::load_pcm( - const fs::path& file, bool headerOnly + const io::path& file, bool headerOnly ) { OggVorbis_File vf; int code; - if ((code = ov_fopen(file.u8string().c_str(), &vf))) { + if ((code = ov_fopen(io::resolve(file).u8string().c_str(), &vf))) { throw std::runtime_error("vorbis: " + vorbis_error_message(code)); } std::vector data; @@ -166,10 +167,10 @@ public: } }; -std::unique_ptr ogg::create_stream(const fs::path& file) { +std::unique_ptr ogg::create_stream(const io::path& file) { OggVorbis_File vf; int code; - if ((code = ov_fopen(file.u8string().c_str(), &vf))) { + if ((code = ov_fopen(io::resolve(file).u8string().c_str(), &vf))) { throw std::runtime_error("vorbis: " + vorbis_error_message(code)); } return std::make_unique(vf); diff --git a/src/coders/ogg.hpp b/src/coders/ogg.hpp index 67ea2458..86a807e7 100644 --- a/src/coders/ogg.hpp +++ b/src/coders/ogg.hpp @@ -1,6 +1,8 @@ #pragma once -#include +#include + +#include "io/fwd.hpp" namespace audio { struct PCM; @@ -9,9 +11,9 @@ namespace audio { namespace ogg { std::unique_ptr load_pcm( - const std::filesystem::path& file, bool headerOnly + const io::path& file, bool headerOnly ); std::unique_ptr create_stream( - const std::filesystem::path& file + const io::path& file ); } diff --git a/src/coders/png.cpp b/src/coders/png.cpp index b29afd34..ddf5fea8 100644 --- a/src/coders/png.cpp +++ b/src/coders/png.cpp @@ -6,7 +6,7 @@ #include #include "debug/Logger.hpp" -#include "files/files.hpp" +#include "io/io.hpp" #include "graphics/core/GLTexture.hpp" #include "graphics/core/ImageData.hpp" @@ -212,7 +212,7 @@ std::unique_ptr png::load_texture(const ubyte* bytes, size_t size) { } std::unique_ptr png::load_texture(const std::string& filename) { - auto bytes = files::read_bytes_buffer(fs::u8path(filename)); + auto bytes = io::read_bytes_buffer(filename); try { return load_texture(bytes.data(), bytes.size()); } catch (const std::runtime_error& err) { diff --git a/src/coders/toml.cpp b/src/coders/toml.cpp index 080df90e..3896bcec 100644 --- a/src/coders/toml.cpp +++ b/src/coders/toml.cpp @@ -7,7 +7,7 @@ #include #include "data/setting.hpp" -#include "files/settings_io.hpp" +#include "io/settings_io.hpp" #include "util/stringutil.hpp" #include "commons.hpp" diff --git a/src/coders/wav.cpp b/src/coders/wav.cpp index 6ba337fd..1e260a0c 100644 --- a/src/coders/wav.cpp +++ b/src/coders/wav.cpp @@ -6,6 +6,7 @@ #include #include +#include "io/io.hpp" #include "audio/audio.hpp" #include "debug/Logger.hpp" @@ -118,11 +119,11 @@ public: } }; -std::unique_ptr wav::create_stream(const fs::path& file) { - std::ifstream in(file, std::ios::binary); +std::unique_ptr wav::create_stream(const io::path& file) { + std::ifstream in(io::resolve(file), std::ios::binary); if (!in.is_open()) { throw std::runtime_error( - "could not to open file '" + file.u8string() + "'" + "could not to open file '" + file.string() + "'" ); } @@ -234,7 +235,7 @@ std::unique_ptr wav::create_stream(const fs::path& file) { } std::unique_ptr wav::load_pcm( - const fs::path& file, bool headerOnly + const io::path& file, bool headerOnly ) { auto stream = wav::create_stream(file); diff --git a/src/coders/wav.hpp b/src/coders/wav.hpp index 5f5270c2..218b2447 100644 --- a/src/coders/wav.hpp +++ b/src/coders/wav.hpp @@ -1,6 +1,8 @@ #pragma once -#include +#include + +#include "io/fwd.hpp" namespace audio { struct PCM; @@ -9,9 +11,9 @@ namespace audio { namespace wav { std::unique_ptr load_pcm( - const std::filesystem::path& file, bool headerOnly + const io::path& file, bool headerOnly ); std::unique_ptr create_stream( - const std::filesystem::path& file + const io::path& file ); } diff --git a/src/constants.hpp b/src/constants.hpp index 8755331d..9337513f 100644 --- a/src/constants.hpp +++ b/src/constants.hpp @@ -6,7 +6,7 @@ #include inline constexpr int ENGINE_VERSION_MAJOR = 0; -inline constexpr int ENGINE_VERSION_MINOR = 26; +inline constexpr int ENGINE_VERSION_MINOR = 27; #ifdef NDEBUG inline constexpr bool ENGINE_DEBUG_BUILD = false; @@ -14,7 +14,7 @@ inline constexpr bool ENGINE_DEBUG_BUILD = false; inline constexpr bool ENGINE_DEBUG_BUILD = true; #endif // NDEBUG -inline const std::string ENGINE_VERSION_STRING = "0.26"; +inline const std::string ENGINE_VERSION_STRING = "0.27"; /// @brief world regions format version inline constexpr uint REGION_FORMAT_VERSION = 3; diff --git a/src/content/Content.cpp b/src/content/Content.cpp index 68e9cec3..1cc2fdd7 100644 --- a/src/content/Content.cpp +++ b/src/content/Content.cpp @@ -34,12 +34,14 @@ Content::Content( UptrsMap packs, UptrsMap blockMaterials, UptrsMap skeletons, - ResourceIndicesSet resourceIndices + ResourceIndicesSet resourceIndices, + dv::value defaults ) : indices(std::move(indices)), packs(std::move(packs)), blockMaterials(std::move(blockMaterials)), skeletons(std::move(skeletons)), + defaults(std::move(defaults)), blocks(std::move(blocks)), items(std::move(items)), entities(std::move(entities)), diff --git a/src/content/Content.hpp b/src/content/Content.hpp index 3a836c4e..b95ef247 100644 --- a/src/content/Content.hpp +++ b/src/content/Content.hpp @@ -201,6 +201,7 @@ class Content { UptrsMap packs; UptrsMap blockMaterials; UptrsMap skeletons; + dv::value defaults = nullptr; public: ContentUnitDefs blocks; ContentUnitDefs items; @@ -219,7 +220,8 @@ public: UptrsMap packs, UptrsMap blockMaterials, UptrsMap skeletons, - ResourceIndicesSet resourceIndices + ResourceIndicesSet resourceIndices, + dv::value defaults ); ~Content(); @@ -231,6 +233,10 @@ public: return resourceIndices[static_cast(type)]; } + inline const dv::value& getDefaults() const { + return defaults; + } + const rigging::SkeletonConfig* getSkeleton(const std::string& id) const; const BlockMaterial* findBlockMaterial(const std::string& id) const; const ContentPackRuntime* getPackRuntime(const std::string& id) const; diff --git a/src/content/ContentBuilder.cpp b/src/content/ContentBuilder.cpp index 49433dfd..c57a7ad8 100644 --- a/src/content/ContentBuilder.cpp +++ b/src/content/ContentBuilder.cpp @@ -84,7 +84,8 @@ std::unique_ptr ContentBuilder::build() { std::move(packs), std::move(blockMaterials), std::move(skeletons), - std::move(resourceIndices) + std::move(resourceIndices), + std::move(defaults) ); // Now, it's time to resolve foreign keys diff --git a/src/content/ContentBuilder.hpp b/src/content/ContentBuilder.hpp index cc35838c..7d3df1f6 100644 --- a/src/content/ContentBuilder.hpp +++ b/src/content/ContentBuilder.hpp @@ -73,6 +73,7 @@ public: ContentUnitBuilder entities {allNames, ContentType::ENTITY}; ContentUnitBuilder generators {allNames, ContentType::GENERATOR}; ResourceIndicesSet resourceIndices {}; + dv::value defaults = nullptr; ~ContentBuilder(); diff --git a/src/content/ContentLoader.cpp b/src/content/ContentLoader.cpp index ce3f5060..b2457cf4 100644 --- a/src/content/ContentLoader.cpp +++ b/src/content/ContentLoader.cpp @@ -12,7 +12,7 @@ #include "coders/json.hpp" #include "core_defs.hpp" #include "debug/Logger.hpp" -#include "files/files.hpp" +#include "io/io.hpp" #include "items/ItemDef.hpp" #include "logic/scripting/scripting.hpp" #include "objects/rigging.hpp" @@ -23,6 +23,7 @@ #include "data/dv_util.hpp" #include "data/StructLayout.hpp" #include "presets/ParticlesPreset.hpp" +#include "io/engine_paths.hpp" namespace fs = std::filesystem; using namespace data; @@ -43,55 +44,55 @@ ContentLoader::ContentLoader( } static void detect_defs( - const fs::path& folder, + const io::path& folder, const std::string& prefix, std::vector& detected ) { - if (fs::is_directory(folder)) { - for (const auto& entry : fs::directory_iterator(folder)) { - const fs::path& file = entry.path(); - std::string name = file.stem().string(); - if (name[0] == '_') { - continue; - } - if (fs::is_regular_file(file) && files::is_data_file(file)) { - auto map = files::read_object(file); - std::string id = prefix.empty() ? name : prefix + ":" + name; - detected.emplace_back(id); - } else if (fs::is_directory(file) && - file.extension() != fs::u8path(".files")) { - detect_defs(file, name, detected); - } + if (!io::is_directory(folder)) { + return; + } + for (const auto& file : io::directory_iterator(folder)) { + std::string name = file.stem(); + if (name[0] == '_') { + continue; + } + if (io::is_regular_file(file) && io::is_data_file(file)) { + auto map = io::read_object(file); + std::string id = prefix.empty() ? name : prefix + ":" + name; + detected.emplace_back(id); + } else if (io::is_directory(file) && + file.extension() != fs::u8path(".files")) { + detect_defs(file, name, detected); } } } static void detect_defs_pairs( - const fs::path& folder, + const io::path& folder, const std::string& prefix, std::vector>& detected ) { - if (fs::is_directory(folder)) { - for (const auto& entry : fs::directory_iterator(folder)) { - const fs::path& file = entry.path(); - std::string name = file.stem().string(); - if (name[0] == '_') { - continue; - } - if (fs::is_regular_file(file) && files::is_data_file(file)) { - try { - auto map = files::read_object(file); - auto id = prefix.empty() ? name : prefix + ":" + name; - auto caption = util::id_to_caption(id); - map.at("caption").get(caption); - detected.emplace_back(id, name); - } catch (const std::runtime_error& err) { - logger.error() << err.what(); - } - } else if (fs::is_directory(file) && - file.extension() != fs::u8path(".files")) { - detect_defs_pairs(file, name, detected); + if (!io::is_directory(folder)) { + return; + } + for (const auto& file : io::directory_iterator(folder)) { + std::string name = file.stem(); + if (name[0] == '_') { + continue; + } + if (io::is_regular_file(file) && io::is_data_file(file)) { + try { + auto map = io::read_object(file); + auto id = prefix.empty() ? name : prefix + ":" + name; + auto caption = util::id_to_caption(id); + map.at("caption").get(caption); + detected.emplace_back(id, name); + } catch (const std::runtime_error& err) { + logger.error() << err.what(); } + } else if (io::is_directory(file) && + file.extension() != fs::u8path(".files")) { + detect_defs_pairs(file, name, detected); } } } @@ -106,7 +107,7 @@ std::vector> ContentLoader::scanContent( } bool ContentLoader::fixPackIndices( - const fs::path& folder, + const io::path& folder, dv::value& indicesRoot, const std::string& contentSection ) { @@ -146,8 +147,8 @@ void ContentLoader::fixPackIndices() { auto entitiesFolder = folder / ContentPack::ENTITIES_FOLDER; dv::value root; - if (fs::is_regular_file(contentFile)) { - root = files::read_json(contentFile); + if (io::is_regular_file(contentFile)) { + root = io::read_json(contentFile); } else { root = dv::object(); } @@ -159,7 +160,7 @@ void ContentLoader::fixPackIndices() { if (modified) { // rewrite modified json - files::write_json(contentFile, root); + io::write_json(contentFile, root); } } @@ -213,9 +214,9 @@ static void process_method( } void ContentLoader::loadBlock( - Block& def, const std::string& name, const fs::path& file + Block& def, const std::string& name, const io::path& file ) { - auto root = files::read_json(file); + auto root = io::read_json(file); if (def.properties == nullptr) { def.properties = dv::object(); def.properties["name"] = name; @@ -402,9 +403,9 @@ void ContentLoader::loadBlock( } void ContentLoader::loadItem( - ItemDef& def, const std::string& name, const fs::path& file + ItemDef& def, const std::string& name, const io::path& file ) { - auto root = files::read_json(file); + auto root = io::read_json(file); def.properties = root; if (root.has("parent")) { @@ -428,15 +429,29 @@ void ContentLoader::loadItem( } else if (iconTypeStr == "sprite") { def.iconType = ItemIconType::SPRITE; } else if (iconTypeStr.length()) { - logger.error() << name << ": unknown icon type" << iconTypeStr; + logger.error() << name << ": unknown icon type - " << iconTypeStr; } root.at("icon").get(def.icon); root.at("placing-block").get(def.placingBlock); root.at("script-name").get(def.scriptName); root.at("model-name").get(def.modelName); root.at("stack-size").get(def.stackSize); + root.at("uses").get(def.uses); + + std::string usesDisplayStr = ""; + root.at("uses-display").get(usesDisplayStr); + if (usesDisplayStr == "none") { + def.usesDisplay = ItemUsesDisplay::NONE; + } else if (usesDisplayStr == "number") { + def.usesDisplay = ItemUsesDisplay::NUMBER; + } else if (usesDisplayStr == "relation") { + def.usesDisplay = ItemUsesDisplay::RELATION; + } else if (usesDisplayStr == "vbar") { + def.usesDisplay = ItemUsesDisplay::VBAR; + } else if (usesDisplayStr.length()) { + logger.error() << name << ": unknown uses display mode - " << usesDisplayStr; + } - // item light emission [r, g, b] where r,g,b in range [0..15] if (auto found = root.at("emission")) { const auto& emissionarr = *found; def.emission[0] = emissionarr[0].asNumber(); @@ -446,9 +461,9 @@ void ContentLoader::loadItem( } void ContentLoader::loadEntity( - EntityDef& def, const std::string& name, const fs::path& file + EntityDef& def, const std::string& name, const io::path& file ) { - auto root = files::read_json(file); + auto root = io::read_json(file); if (root.has("parent")) { const auto& parentName = root["parent"].asString(); @@ -518,16 +533,16 @@ void ContentLoader::loadEntity( EntityDef& def, const std::string& full, const std::string& name ) { auto folder = pack->folder; - auto configFile = folder / fs::path("entities/" + name + ".json"); - if (fs::exists(configFile)) loadEntity(def, full, configFile); + auto configFile = folder / ("entities/" + name + ".json"); + if (io::exists(configFile)) loadEntity(def, full, configFile); } void ContentLoader::loadBlock( Block& def, const std::string& full, const std::string& name ) { auto folder = pack->folder; - auto configFile = folder / fs::path("blocks/" + name + ".json"); - if (fs::exists(configFile)) loadBlock(def, full, configFile); + auto configFile = folder / ("blocks/" + name + ".json"); + if (io::exists(configFile)) loadBlock(def, full, configFile); if (!def.hidden) { bool created; @@ -549,8 +564,8 @@ void ContentLoader::loadItem( ItemDef& def, const std::string& full, const std::string& name ) { auto folder = pack->folder; - auto configFile = folder / fs::path("items/" + name + ".json"); - if (fs::exists(configFile)) loadItem(def, full, configFile); + auto configFile = folder / ("items/" + name + ".json"); + if (io::exists(configFile)) loadItem(def, full, configFile); } static std::tuple create_unit_id( @@ -566,21 +581,25 @@ static std::tuple create_unit_id( } void ContentLoader::loadBlockMaterial( - BlockMaterial& def, const fs::path& file + BlockMaterial& def, const io::path& file ) { - auto root = files::read_json(file); + auto root = io::read_json(file); root.at("steps-sound").get(def.stepsSound); root.at("place-sound").get(def.placeSound); root.at("break-sound").get(def.breakSound); + root.at("hit-sound").get(def.hitSound); + if (def.hitSound.empty()) { + def.hitSound = def.stepsSound; + } } void ContentLoader::loadContent(const dv::value& root) { std::vector> pendingDefs; auto getJsonParent = [this](const std::string& prefix, const std::string& name) { - auto configFile = pack->folder / fs::path(prefix + "/" + name + ".json"); + auto configFile = pack->folder / (prefix + "/" + name + ".json"); std::string parent; - if (fs::exists(configFile)) { - auto root = files::read_json(configFile); + if (io::exists(configFile)) { + auto root = io::read_json(configFile); root.at("parent").get(parent); } return parent; @@ -740,16 +759,16 @@ void ContentLoader::loadContent(const dv::value& root) { } static inline void foreach_file( - const fs::path& dir, std::function handler + const io::path& dir, std::function handler ) { - if (fs::is_directory(dir)) { - for (const auto& entry : fs::directory_iterator(dir)) { - const auto& path = entry.path(); - if (fs::is_directory(path)) { - continue; - } - handler(path); + if (!io::is_directory(dir)) { + return; + } + for (const auto& path : io::directory_iterator(dir)) { + if (io::is_directory(path)) { + continue; } + handler(path); } } @@ -760,12 +779,15 @@ void ContentLoader::load() { auto folder = pack->folder; + builder.defaults = paths.readCombinedObject( + EnginePaths::CONFIG_DEFAULTS.string() + ); + // Load world generators - fs::path generatorsDir = folder / fs::u8path("generators"); - foreach_file(generatorsDir, [this](const fs::path& file) { - std::string name = file.stem().u8string(); - auto [packid, full, filename] = - create_unit_id(pack->id, file.stem().u8string()); + io::path generatorsDir = folder / "generators"; + foreach_file(generatorsDir, [this](const io::path& file) { + std::string name = file.stem(); + auto [packid, full, filename] = create_unit_id(pack->id, name); auto& def = builder.generators.create(full); try { @@ -776,9 +798,9 @@ void ContentLoader::load() { }); // Load pack resources.json - fs::path resourcesFile = folder / fs::u8path("resources.json"); - if (fs::exists(resourcesFile)) { - auto resRoot = files::read_json(resourcesFile); + io::path resourcesFile = folder / "resources.json"; + if (io::exists(resourcesFile)) { + auto resRoot = io::read_json(resourcesFile); for (const auto& [key, arr] : resRoot.asObject()) { if (auto resType = ResourceType_from(key)) { loadResources(*resType, arr); @@ -790,9 +812,9 @@ void ContentLoader::load() { } // Load pack resources aliases - fs::path aliasesFile = folder / fs::u8path("resource-aliases.json"); - if (fs::exists(aliasesFile)) { - auto resRoot = files::read_json(aliasesFile); + io::path aliasesFile = folder / "resource-aliases.json"; + if (io::exists(aliasesFile)) { + auto resRoot = io::read_json(aliasesFile); for (const auto& [key, arr] : resRoot.asObject()) { if (auto resType = ResourceType_from(key)) { loadResourceAliases(*resType, arr); @@ -804,33 +826,32 @@ void ContentLoader::load() { } // Load block materials - fs::path materialsDir = folder / fs::u8path("block_materials"); - if (fs::is_directory(materialsDir)) { - for (const auto& entry : fs::directory_iterator(materialsDir)) { - const auto& file = entry.path(); + io::path materialsDir = folder / "block_materials"; + if (io::is_directory(materialsDir)) { + for (const auto& file : io::directory_iterator(materialsDir)) { auto [packid, full, filename] = - create_unit_id(pack->id, file.stem().u8string()); + create_unit_id(pack->id, file.stem()); loadBlockMaterial( builder.createBlockMaterial(full), - materialsDir / fs::u8path(filename + ".json") + materialsDir / (filename + ".json") ); } } // Load skeletons - fs::path skeletonsDir = folder / fs::u8path("skeletons"); - foreach_file(skeletonsDir, [this](const fs::path& file) { - std::string name = pack->id + ":" + file.stem().u8string(); - std::string text = files::read_string(file); + io::path skeletonsDir = folder / "skeletons"; + foreach_file(skeletonsDir, [this](const io::path& file) { + std::string name = pack->id + ":" + file.stem(); + std::string text = io::read_string(file); builder.add( - rigging::SkeletonConfig::parse(text, file.u8string(), name) + rigging::SkeletonConfig::parse(text, file.string(), name) ); }); // Process content.json and load defined content units auto contentFile = pack->getContentFile(); - if (fs::exists(contentFile)) { - loadContent(files::read_json(contentFile)); + if (io::exists(contentFile)) { + loadContent(io::read_json(contentFile)); } } @@ -844,8 +865,8 @@ static void load_scripts(Content& content, ContentUnitDefs& units) { const auto runtime = content.getPackRuntime(name.substr(0, pos)); const auto& pack = runtime->getInfo(); const auto& folder = pack.folder; - auto scriptfile = folder / fs::path("scripts/" + def->scriptName + ".lua"); - if (fs::is_regular_file(scriptfile)) { + auto scriptfile = folder / ("scripts/" + def->scriptName + ".lua"); + if (io::is_regular_file(scriptfile)) { scripting::load_content_script( runtime->getEnvironment(), name, @@ -866,8 +887,8 @@ void ContentLoader::loadScripts(Content& content) { const auto& folder = pack.folder; // Load main world script - fs::path scriptFile = folder / fs::path("scripts/world.lua"); - if (fs::is_regular_file(scriptFile)) { + io::path scriptFile = folder / "scripts/world.lua"; + if (io::is_regular_file(scriptFile)) { scripting::load_world_script( runtime->getEnvironment(), pack.id, @@ -877,13 +898,13 @@ void ContentLoader::loadScripts(Content& content) { ); } // Load entity components - fs::path componentsDir = folder / fs::u8path("scripts/components"); - foreach_file(componentsDir, [&pack](const fs::path& file) { - auto name = pack.id + ":" + file.stem().u8string(); + io::path componentsDir = folder / "scripts/components"; + foreach_file(componentsDir, [&pack](const io::path& file) { + auto name = pack.id + ":" + file.stem(); scripting::load_entity_component( name, file, - pack.id + ":scripts/components/" + file.filename().u8string() + pack.id + ":scripts/components/" + file.name() ); }); } diff --git a/src/content/ContentLoader.hpp b/src/content/ContentLoader.hpp index d99aa622..f1135ee3 100644 --- a/src/content/ContentLoader.hpp +++ b/src/content/ContentLoader.hpp @@ -1,14 +1,12 @@ #pragma once -#include #include #include +#include "io/io.hpp" #include "content_fwd.hpp" #include "data/dv.hpp" -namespace fs = std::filesystem; - class Block; struct BlockMaterial; struct ItemDef; @@ -43,15 +41,15 @@ class ContentLoader { GeneratorDef& def, const std::string& full, const std::string& name ); - static void loadBlockMaterial(BlockMaterial& def, const fs::path& file); + static void loadBlockMaterial(BlockMaterial& def, const io::path& file); void loadBlock( - Block& def, const std::string& name, const fs::path& file + Block& def, const std::string& name, const io::path& file ); void loadItem( - ItemDef& def, const std::string& name, const fs::path& file + ItemDef& def, const std::string& name, const io::path& file ); void loadEntity( - EntityDef& def, const std::string& name, const fs::path& file + EntityDef& def, const std::string& name, const io::path& file ); void loadResources(ResourceType type, const dv::value& list); void loadResourceAliases(ResourceType type, const dv::value& aliases); @@ -66,7 +64,7 @@ public: // Refresh pack content.json static bool fixPackIndices( - const fs::path& folder, + const io::path& folder, dv::value& indicesRoot, const std::string& contentSection ); diff --git a/src/content/ContentPack.cpp b/src/content/ContentPack.cpp index 7135f231..73a9503e 100644 --- a/src/content/ContentPack.cpp +++ b/src/content/ContentPack.cpp @@ -7,15 +7,15 @@ #include "coders/json.hpp" #include "constants.hpp" #include "data/dv.hpp" -#include "files/engine_paths.hpp" -#include "files/files.hpp" +#include "io/engine_paths.hpp" +#include "io/io.hpp" namespace fs = std::filesystem; ContentPack ContentPack::createCore(const EnginePaths& paths) { return ContentPack { - "core", "Core", ENGINE_VERSION_STRING, "", "", paths.getResourcesFolder(), "res:", {} + "core", "Core", ENGINE_VERSION_STRING, "", "", "res:", "res:", {} }; } @@ -23,7 +23,7 @@ const std::vector ContentPack::RESERVED_NAMES = { "res", "abs", "local", "core", "user", "world", "none", "null"}; contentpack_error::contentpack_error( - std::string packId, fs::path folder, const std::string& message + std::string packId, io::path folder, const std::string& message ) : std::runtime_error(message), packId(std::move(packId)), @@ -33,19 +33,19 @@ contentpack_error::contentpack_error( std::string contentpack_error::getPackId() const { return packId; } -fs::path contentpack_error::getFolder() const { +io::path contentpack_error::getFolder() const { return folder; } -fs::path ContentPack::getContentFile() const { - return folder / fs::path(CONTENT_FILENAME); +io::path ContentPack::getContentFile() const { + return folder / CONTENT_FILENAME; } -bool ContentPack::is_pack(const fs::path& folder) { - return fs::is_regular_file(folder / fs::path(PACKAGE_FILENAME)); +bool ContentPack::is_pack(const io::path& folder) { + return io::is_regular_file(folder / PACKAGE_FILENAME); } -static void checkContentPackId(const std::string& id, const fs::path& folder) { +static void checkContentPackId(const std::string& id, const io::path& folder) { if (id.length() < 2 || id.length() > 24) throw contentpack_error( id, folder, "content-pack id length is out of range [2, 24]" @@ -70,8 +70,8 @@ static void checkContentPackId(const std::string& id, const fs::path& folder) { } } -ContentPack ContentPack::read(const std::string& path, const fs::path& folder) { - auto root = files::read_json(folder / fs::path(PACKAGE_FILENAME)); +ContentPack ContentPack::read(const std::string& path, const io::path& folder) { + auto root = io::read_json(folder / PACKAGE_FILENAME); ContentPack pack; root.at("id").get(pack.id); root.at("title").get(pack.title); @@ -124,21 +124,20 @@ ContentPack ContentPack::read(const std::string& path, const fs::path& folder) { } void ContentPack::scanFolder( - const std::string& path, const fs::path& folder, std::vector& packs + const std::string& path, const io::path& folder, std::vector& packs ) { - if (!fs::is_directory(folder)) { + if (!io::is_directory(folder)) { return; } - for (const auto& entry : fs::directory_iterator(folder)) { - const fs::path& packFolder = entry.path(); - if (!fs::is_directory(packFolder)) continue; + for (const auto& packFolder : io::directory_iterator(folder)) { + if (!io::is_directory(packFolder)) continue; if (!is_pack(packFolder)) continue; try { packs.push_back( - read(path + "/" + packFolder.filename().string(), packFolder) + read(path + "/" + packFolder.name(), packFolder) ); } catch (const contentpack_error& err) { - std::cerr << "package.json error at " << err.getFolder().u8string(); + std::cerr << "package.json error at " << err.getFolder().string(); std::cerr << ": " << err.what() << std::endl; } catch (const std::runtime_error& err) { std::cerr << err.what() << std::endl; @@ -146,26 +145,26 @@ void ContentPack::scanFolder( } } -std::vector ContentPack::worldPacksList(const fs::path& folder) { - fs::path listfile = folder / fs::path("packs.list"); - if (!fs::is_regular_file(listfile)) { +std::vector ContentPack::worldPacksList(const io::path& folder) { + io::path listfile = folder / "packs.list"; + if (!io::is_regular_file(listfile)) { throw std::runtime_error("missing file 'packs.list'"); } - return files::read_list(listfile); + return io::read_list(listfile); } -fs::path ContentPack::findPack( - const EnginePaths* paths, const fs::path& worldDir, const std::string& name +io::path ContentPack::findPack( + const EnginePaths* paths, const io::path& worldDir, const std::string& name ) { - fs::path folder = worldDir / fs::path("content") / fs::path(name); - if (fs::is_directory(folder)) { + io::path folder = worldDir / "content" / name; + if (io::is_directory(folder)) { return folder; } - folder = paths->getUserFilesFolder() / fs::path("content") / fs::path(name); - if (fs::is_directory(folder)) { + folder = io::path("user:content") / name; + if (io::is_directory(folder)) { return folder; } - return paths->getResourcesFolder() / fs::path("content") / fs::path(name); + return io::path("res:content") / name; } ContentPackRuntime::ContentPackRuntime(ContentPack info, scriptenv env) diff --git a/src/content/ContentPack.hpp b/src/content/ContentPack.hpp index e4360bdc..ed7c3eb8 100644 --- a/src/content/ContentPack.hpp +++ b/src/content/ContentPack.hpp @@ -1,27 +1,27 @@ #pragma once -#include #include #include #include #include "typedefs.hpp" #include "content_fwd.hpp" +#include "io/io.hpp" class EnginePaths; class contentpack_error : public std::runtime_error { std::string packId; - std::filesystem::path folder; + io::path folder; public: contentpack_error( std::string packId, - std::filesystem::path folder, + io::path folder, const std::string& message ); std::string getPackId() const; - std::filesystem::path getFolder() const; + io::path getFolder() const; }; enum class DependencyLevel { @@ -42,52 +42,52 @@ struct ContentPack { std::string version = "0.0"; std::string creator = ""; std::string description = "no description"; - std::filesystem::path folder; + io::path folder; std::string path; std::vector dependencies; std::string source = ""; - std::filesystem::path getContentFile() const; + io::path getContentFile() const; static inline const std::string PACKAGE_FILENAME = "package.json"; static inline const std::string CONTENT_FILENAME = "content.json"; - static inline const std::filesystem::path BLOCKS_FOLDER = "blocks"; - static inline const std::filesystem::path ITEMS_FOLDER = "items"; - static inline const std::filesystem::path ENTITIES_FOLDER = "entities"; - static inline const std::filesystem::path GENERATORS_FOLDER = "generators"; + static inline const io::path BLOCKS_FOLDER = "blocks"; + static inline const io::path ITEMS_FOLDER = "items"; + static inline const io::path ENTITIES_FOLDER = "entities"; + static inline const io::path GENERATORS_FOLDER = "generators"; static const std::vector RESERVED_NAMES; - static bool is_pack(const std::filesystem::path& folder); + static bool is_pack(const io::path& folder); static ContentPack read( - const std::string& path, const std::filesystem::path& folder + const std::string& path, const io::path& folder ); static void scanFolder( const std::string& path, - const std::filesystem::path& folder, + const io::path& folder, std::vector& packs ); static std::vector worldPacksList( - const std::filesystem::path& folder + const io::path& folder ); - static std::filesystem::path findPack( + static io::path findPack( const EnginePaths* paths, - const std::filesystem::path& worldDir, + const io::path& worldDir, const std::string& name ); static ContentPack createCore(const EnginePaths&); - static inline std::filesystem::path getFolderFor(ContentType type) { + static inline io::path getFolderFor(ContentType type) { switch (type) { case ContentType::BLOCK: return ContentPack::BLOCKS_FOLDER; case ContentType::ITEM: return ContentPack::ITEMS_FOLDER; case ContentType::ENTITY: return ContentPack::ENTITIES_FOLDER; case ContentType::GENERATOR: return ContentPack::GENERATORS_FOLDER; - case ContentType::NONE: return std::filesystem::u8path(""); - default: return std::filesystem::u8path(""); + case ContentType::NONE: return ""; + default: return ""; } } }; diff --git a/src/content/ContentReport.cpp b/src/content/ContentReport.cpp index 7de04be5..0d8a20e2 100644 --- a/src/content/ContentReport.cpp +++ b/src/content/ContentReport.cpp @@ -4,11 +4,11 @@ #include "coders/json.hpp" #include "constants.hpp" -#include "files/files.hpp" +#include "io/io.hpp" #include "items/ItemDef.hpp" #include "voxels/Block.hpp" #include "world/World.hpp" -#include "files/WorldFiles.hpp" +#include "world/files/WorldFiles.hpp" #include "Content.hpp" ContentReport::ContentReport( @@ -67,7 +67,7 @@ static void process_blocks_data( std::shared_ptr ContentReport::create( const std::shared_ptr& worldFiles, - const fs::path& filename, + const io::path& filename, const Content* content ) { auto worldInfo = worldFiles->readWorldInfo(); @@ -75,7 +75,7 @@ std::shared_ptr ContentReport::create( return nullptr; } - auto root = files::read_json(filename); + auto root = io::read_json(filename); uint regionsVersion = 2U; // old worlds compatibility (pre 0.23) root.at("region-version").get(regionsVersion); auto& blocklist = root["blocks"]; diff --git a/src/content/ContentReport.hpp b/src/content/ContentReport.hpp index 3476b872..325e5f29 100644 --- a/src/content/ContentReport.hpp +++ b/src/content/ContentReport.hpp @@ -1,6 +1,5 @@ #pragma once -#include #include #include #include @@ -10,10 +9,10 @@ #include "data/dv.hpp" #include "typedefs.hpp" #include "Content.hpp" +#include "io/io.hpp" #include "data/StructLayout.hpp" -#include "files/world_regions_fwd.hpp" +#include "world/files/world_regions_fwd.hpp" -namespace fs = std::filesystem; enum class ContentIssueType { REORDER, @@ -139,7 +138,7 @@ public: static std::shared_ptr create( const std::shared_ptr& worldFiles, - const fs::path& filename, + const io::path& filename, const Content* content ); diff --git a/src/content/PacksManager.cpp b/src/content/PacksManager.cpp index fdf39bb3..4eeaa5eb 100644 --- a/src/content/PacksManager.cpp +++ b/src/content/PacksManager.cpp @@ -2,12 +2,13 @@ #include #include +#include #include "util/listutil.hpp" PacksManager::PacksManager() = default; -void PacksManager::setSources(std::vector> sources) { +void PacksManager::setSources(std::vector> sources) { this->sources = std::move(sources); } @@ -42,7 +43,7 @@ std::vector PacksManager::getAll( for (auto& name : names) { auto found = packs.find(name); if (found == packs.end()) { - throw contentpack_error(name, fs::path(""), "pack not found"); + throw contentpack_error(name, io::path(), "pack not found"); } packsList.push_back(found->second); } @@ -92,7 +93,7 @@ static bool resolve_dependencies( bool exists = found != packs.end(); if (!exists && dep.level == DependencyLevel::required) { throw contentpack_error( - dep.id, fs::path(), "dependency of '" + pack->id + "'" + dep.id, io::path(), "dependency of '" + pack->id + "'" ); } if (!exists) { @@ -124,10 +125,12 @@ std::vector PacksManager::assemble( std::queue queue; std::queue queue2; - for (auto& name : names) { + std::sort(allNames.begin(), allNames.end()); + + for (auto& name : allNames) { auto found = packs.find(name); if (found == packs.end()) { - throw contentpack_error(name, fs::path(""), "pack not found"); + throw contentpack_error(name, io::path(), "pack not found"); } queue.push(&found->second); } diff --git a/src/content/PacksManager.hpp b/src/content/PacksManager.hpp index b60b06ce..32ac43d6 100644 --- a/src/content/PacksManager.hpp +++ b/src/content/PacksManager.hpp @@ -1,21 +1,19 @@ #pragma once -#include #include #include +#include "io/io.hpp" #include "ContentPack.hpp" -namespace fs = std::filesystem; - class PacksManager { std::unordered_map packs; - std::vector> sources; + std::vector> sources; public: PacksManager(); /// @brief Set content packs sources (search folders) - void setSources(std::vector> sources); + void setSources(std::vector> sources); /// @brief Scan sources and collect all found packs excluding duplication. /// Scanning order depends on sources order diff --git a/src/content/loading/GeneratorLoader.cpp b/src/content/loading/GeneratorLoader.cpp index f9f06d80..b14b0b2f 100644 --- a/src/content/loading/GeneratorLoader.cpp +++ b/src/content/loading/GeneratorLoader.cpp @@ -4,8 +4,8 @@ #include "../ContentPack.hpp" -#include "files/files.hpp" -#include "files/engine_paths.hpp" +#include "io/io.hpp" +#include "io/engine_paths.hpp" #include "logic/scripting/scripting.hpp" #include "world/generator/GeneratorDef.hpp" #include "world/generator/VoxelFragment.hpp" @@ -132,21 +132,21 @@ static VoxelStructureMeta load_structure_meta( } static std::vector> load_structures( - const dv::value& map, const fs::path& filesFolder, const ResPaths& paths + const dv::value& map, const io::path& filesFolder, const ResPaths& paths ) { - auto structuresDir = filesFolder / fs::path("fragments"); + auto structuresDir = filesFolder / "fragments"; std::vector> structures; for (auto& [name, config] : map.asObject()) { - auto structFile = structuresDir / fs::u8path(name + ".vox"); - structFile = paths.find(structFile.u8string()); - logger.debug() << "loading voxel fragment " << structFile.u8string(); - if (!fs::exists(structFile)) { + auto structFile = structuresDir / (name + ".vox"); + structFile = paths.find(structFile.string()); + logger.debug() << "loading voxel fragment " << structFile.string(); + if (!io::exists(structFile)) { throw std::runtime_error("structure file does not exist (" + - structFile.u8string()); + structFile.string()); } auto fragment = std::make_unique(); - fragment->deserialize(files::read_binary_json(structFile)); + fragment->deserialize(io::read_binary_json(structFile)); logger.info() << "fragment " << name << " has size [" << fragment->getSize().x << ", " << fragment->getSize().y << ", " << fragment->getSize().z << "]"; @@ -162,7 +162,7 @@ static std::vector> load_structures( static void load_structures( GeneratorDef& def, const dv::value& map, - const fs::path& filesFolder, + const io::path& filesFolder, const ResPaths& paths ) { auto rawStructures = load_structures(map, filesFolder, paths); @@ -178,9 +178,9 @@ static void load_structures( } } -static inline const auto STRUCTURES_FILE = fs::u8path("structures.toml"); -static inline const auto BIOMES_FILE = fs::u8path("biomes.toml"); -static inline const auto GENERATORS_DIR = fs::u8path("generators"); +static inline const io::path STRUCTURES_FILE = "structures.toml"; +static inline const io::path BIOMES_FILE = "biomes.toml"; +static inline const io::path GENERATORS_DIR = "generators"; static void load_biomes(GeneratorDef& def, const dv::value& root) { for (const auto& [biomeName, biomeMap] : root.asObject()) { @@ -198,11 +198,11 @@ void ContentLoader::loadGenerator( ) { auto packDir = pack->folder; auto generatorsDir = packDir / GENERATORS_DIR; - auto generatorFile = generatorsDir / fs::u8path(name + ".toml"); - if (!fs::exists(generatorFile)) { + auto generatorFile = generatorsDir / (name + ".toml"); + if (!io::exists(generatorFile)) { return; } - auto map = files::read_toml(generatorsDir / fs::u8path(name + ".toml")); + auto map = io::read_toml(generatorsDir / (name + ".toml")); map.at("caption").get(def.caption); map.at("biome-parameters").get(def.biomeParameters); map.at("biome-bpd").get(def.biomesBPD); @@ -233,15 +233,15 @@ void ContentLoader::loadGenerator( logger.warning() << "generator has heightmap-inputs but biomes-bpd " "is not equal to heights-bpd, generator will work slower!"; } - auto folder = generatorsDir / fs::u8path(name + ".files"); - auto scriptFile = folder / fs::u8path("script.lua"); + auto folder = generatorsDir / (name + ".files"); + auto scriptFile = folder / "script.lua"; - auto structuresFile = GENERATORS_DIR / fs::u8path(name + ".files") / STRUCTURES_FILE; - auto structuresMap = paths.readCombinedObject(structuresFile.u8string()); - load_structures(def, structuresMap, structuresFile.parent_path(), paths); + auto structuresFile = GENERATORS_DIR / (name + ".files") / STRUCTURES_FILE; + auto structuresMap = paths.readCombinedObject(structuresFile.string()); + load_structures(def, structuresMap, structuresFile.parent(), paths); - auto biomesFile = GENERATORS_DIR / fs::u8path(name + ".files") / BIOMES_FILE; - auto biomesMap = paths.readCombinedObject(biomesFile.u8string()); + auto biomesFile = GENERATORS_DIR / (name + ".files") / BIOMES_FILE; + auto biomesMap = paths.readCombinedObject(biomesFile.string()); if (biomesMap.empty()) { throw std::runtime_error( "generator " + util::quote(def.name) + diff --git a/src/core_defs.cpp b/src/core_defs.cpp index fb8540ba..def96d69 100644 --- a/src/core_defs.cpp +++ b/src/core_defs.cpp @@ -3,15 +3,15 @@ #include "items/ItemDef.hpp" #include "content/Content.hpp" #include "content/ContentBuilder.hpp" -#include "files/files.hpp" -#include "files/engine_paths.hpp" +#include "io/io.hpp" +#include "io/engine_paths.hpp" #include "window/Window.hpp" #include "window/Events.hpp" #include "window/input.hpp" #include "voxels/Block.hpp" // All in-game definitions (blocks, items, etc..) -void corecontent::setup(const EnginePaths& paths, ContentBuilder& builder) { +void corecontent::setup(ContentBuilder& builder) { { Block& block = builder.blocks.create(CORE_AIR); block.replaceable = true; @@ -28,10 +28,10 @@ void corecontent::setup(const EnginePaths& paths, ContentBuilder& builder) { item.iconType = ItemIconType::NONE; } - auto bindsFile = paths.getResourcesFolder()/fs::path("bindings.toml"); - if (fs::is_regular_file(bindsFile)) { + auto bindsFile = "res:bindings.toml"; + if (io::is_regular_file(bindsFile)) { Events::loadBindings( - bindsFile.u8string(), files::read_string(bindsFile), BindType::BIND + bindsFile, io::read_string(bindsFile), BindType::BIND ); } diff --git a/src/core_defs.hpp b/src/core_defs.hpp index 8042ca36..f269af10 100644 --- a/src/core_defs.hpp +++ b/src/core_defs.hpp @@ -28,9 +28,8 @@ inline const std::string BIND_PLAYER_FAST_INTERACTOIN = "player.fast_interaction"; inline const std::string BIND_HUD_INVENTORY = "hud.inventory"; -class EnginePaths; class ContentBuilder; namespace corecontent { - void setup(const EnginePaths& paths, ContentBuilder& builder); + void setup(ContentBuilder& builder); } diff --git a/src/engine/Engine.cpp b/src/engine/Engine.cpp index 73704468..80d774be 100644 --- a/src/engine/Engine.cpp +++ b/src/engine/Engine.cpp @@ -16,7 +16,7 @@ #include "content/ContentBuilder.hpp" #include "content/ContentLoader.hpp" #include "core_defs.hpp" -#include "files/files.hpp" +#include "io/io.hpp" #include "frontend/locale.hpp" #include "frontend/menu.hpp" #include "frontend/screens/Screen.hpp" @@ -52,10 +52,10 @@ static debug::Logger logger("engine"); namespace fs = std::filesystem; -static std::unique_ptr load_icon(const fs::path& resdir) { +static std::unique_ptr load_icon() { try { - auto file = resdir / fs::u8path("textures/misc/icon.png"); - if (fs::exists(file)) { + auto file = "res:textures/misc/icon.png"; + if (io::exists(file)) { return imageio::read(file); } } catch (const std::exception& err) { @@ -101,7 +101,7 @@ void Engine::initialize(CoreParameters coreParameters) { throw initialize_error("could not initialize window"); } time.set(Window::time()); - if (auto icon = load_icon(resdir)) { + if (auto icon = load_icon()) { icon->flipY(); Window::setIcon(icon.get()); } @@ -118,7 +118,7 @@ void Engine::initialize(CoreParameters coreParameters) { if (langNotSet) { settings.ui.language.set(langs::locale_by_envlocale( platform::detect_locale(), - paths.getResourcesFolder() + "res:" )); } scripting::initialize(this); @@ -128,14 +128,14 @@ void Engine::initialize(CoreParameters coreParameters) { keepAlive(settings.ui.language.observe([this](auto lang) { setLanguage(lang); }, true)); - basePacks = files::read_list(resdir/fs::path("config/builtins.list")); + basePacks = io::read_list("res:config/builtins.list"); } void Engine::loadSettings() { - fs::path settings_file = paths.getSettingsFile(); - if (fs::is_regular_file(settings_file)) { + io::path settings_file = paths.getSettingsFile(); + if (io::is_regular_file(settings_file)) { logger.info() << "loading settings"; - std::string text = files::read_string(settings_file); + std::string text = io::read_string(settings_file); try { toml::parse(*settingsHandler, settings_file.string(), text); } catch (const parsing_error& err) { @@ -146,11 +146,11 @@ void Engine::loadSettings() { } void Engine::loadControls() { - fs::path controls_file = paths.getControlsFile(); - if (fs::is_regular_file(controls_file)) { + io::path controls_file = paths.getControlsFile(); + if (io::is_regular_file(controls_file)) { logger.info() << "loading controls"; - std::string text = files::read_string(controls_file); - Events::loadBindings(controls_file.u8string(), text, BindType::BIND); + std::string text = io::read_string(controls_file); + Events::loadBindings(controls_file.string(), text, BindType::BIND); } } @@ -171,9 +171,9 @@ void Engine::updateHotkeys() { void Engine::saveScreenshot() { auto image = Window::takeScreenshot(); image->flipY(); - fs::path filename = paths.getNewScreenshotFile("png"); + io::path filename = paths.getNewScreenshotFile("png"); imageio::write(filename.string(), image.get()); - logger.info() << "saved screenshot as " << filename.u8string(); + logger.info() << "saved screenshot as " << filename.string(); } void Engine::run() { @@ -219,10 +219,10 @@ void Engine::renderFrame() { void Engine::saveSettings() { logger.info() << "saving settings"; - files::write_string(paths.getSettingsFile(), toml::stringify(*settingsHandler)); + io::write_string(paths.getSettingsFile(), toml::stringify(*settingsHandler)); if (!params.headless) { logger.info() << "saving bindings"; - files::write_string(paths.getControlsFile(), Events::writeBindings()); + io::write_string(paths.getControlsFile(), Events::writeBindings()); } } @@ -264,12 +264,12 @@ cmd::CommandsInterpreter* Engine::getCommandsInterpreter() { return interpreter.get(); } -PacksManager Engine::createPacksManager(const fs::path& worldFolder) { +PacksManager Engine::createPacksManager(const io::path& worldFolder) { PacksManager manager; manager.setSources({ - {"world:content", worldFolder.empty() ? worldFolder : worldFolder/fs::path("content")}, - {"user:content", paths.getUserFilesFolder()/fs::path("content")}, - {"res:content", paths.getResourcesFolder()/fs::path("content")} + {"world:content", worldFolder.empty() ? worldFolder : worldFolder / "content"}, + {"user:content", "user:content"}, + {"res:content", "res:content"} }); return manager; } @@ -325,12 +325,12 @@ void Engine::loadAssets() { } } -static void load_configs(const fs::path& root) { - auto configFolder = root/fs::path("config"); - auto bindsFile = configFolder/fs::path("bindings.toml"); - if (fs::is_regular_file(bindsFile)) { +static void load_configs(const io::path& root) { + auto configFolder = root / "config"; + auto bindsFile = configFolder / "bindings.toml"; + if (io::is_regular_file(bindsFile)) { Events::loadBindings( - bindsFile.u8string(), files::read_string(bindsFile), BindType::BIND + bindsFile.string(), io::read_string(bindsFile), BindType::BIND ); } } @@ -338,15 +338,13 @@ static void load_configs(const fs::path& root) { void Engine::loadContent() { scripting::cleanup(); - auto resdir = paths.getResourcesFolder(); - std::vector names; for (auto& pack : contentPacks) { names.push_back(pack.id); } ContentBuilder contentBuilder; - corecontent::setup(paths, contentBuilder); + corecontent::setup(contentBuilder); paths.setContentPacks(&contentPacks); PacksManager manager = createPacksManager(paths.getCurrentWorldFolder()); @@ -363,7 +361,7 @@ void Engine::loadContent() { for (auto& pack : contentPacks) { resRoots.push_back({pack.id, pack.folder}); } - resPaths = std::make_unique(resdir, resRoots); + resPaths = std::make_unique("res:", resRoots); // Load content { @@ -380,7 +378,7 @@ void Engine::loadContent() { ContentLoader::loadScripts(*content); - langs::setup(resdir, langs::current->getId(), contentPacks); + langs::setup("res:", langs::current->getId(), contentPacks); if (!isHeadless()) { loadAssets(); onAssetsLoaded(); @@ -389,23 +387,22 @@ void Engine::loadContent() { void Engine::resetContent() { scripting::cleanup(); - auto resdir = paths.getResourcesFolder(); std::vector resRoots; { auto pack = ContentPack::createCore(paths); resRoots.push_back({"core", pack.folder}); load_configs(pack.folder); } - auto manager = createPacksManager(fs::path()); + auto manager = createPacksManager(io::path()); manager.scan(); for (const auto& pack : manager.getAll(basePacks)) { resRoots.push_back({pack.id, pack.folder}); } - resPaths = std::make_unique(resdir, resRoots); + resPaths = std::make_unique("res:", resRoots); contentPacks.clear(); content.reset(); - langs::setup(resdir, langs::current->getId(), contentPacks); + langs::setup("res:", langs::current->getId(), contentPacks); if (!isHeadless()) { loadAssets(); onAssetsLoaded(); @@ -414,15 +411,14 @@ void Engine::resetContent() { contentPacks = manager.getAll(basePacks); } -void Engine::loadWorldContent(const fs::path& folder) { +void Engine::loadWorldContent(const io::path& folder) { contentPacks.clear(); auto packNames = ContentPack::worldPacksList(folder); PacksManager manager; manager.setSources( - {{"world:content", - folder.empty() ? folder : folder / fs::path("content")}, - {"user:content", paths.getUserFilesFolder() / fs::path("content")}, - {"res:content", paths.getResourcesFolder() / fs::path("content")}} + {{"world:content", folder.empty() ? folder : folder / "content"}, + {"user:content", "user:content"}, + {"res:content", "res:content"}} ); manager.scan(); contentPacks = manager.getAll(manager.assemble(packNames)); @@ -445,7 +441,7 @@ void Engine::setScreen(std::shared_ptr screen) { } void Engine::setLanguage(std::string locale) { - langs::setup(paths.getResourcesFolder(), std::move(locale), contentPacks); + langs::setup("res:", std::move(locale), contentPacks); } void Engine::onWorldOpen(std::unique_ptr level, int64_t localPlayer) { diff --git a/src/engine/Engine.hpp b/src/engine/Engine.hpp index d4cbf40a..b05881f6 100644 --- a/src/engine/Engine.hpp +++ b/src/engine/Engine.hpp @@ -8,13 +8,12 @@ #include "content/content_fwd.hpp" #include "content/ContentPack.hpp" #include "content/PacksManager.hpp" -#include "files/engine_paths.hpp" -#include "files/settings_io.hpp" +#include "io/engine_paths.hpp" +#include "io/settings_io.hpp" #include "util/ObjectsKeeper.hpp" #include "PostRunnables.hpp" #include "Time.hpp" -#include #include #include #include @@ -48,8 +47,8 @@ public: struct CoreParameters { bool headless = false; bool testMode = false; - std::filesystem::path resFolder {"res"}; - std::filesystem::path userFolder {"."}; + std::filesystem::path resFolder = "res"; + std::filesystem::path userFolder = "."; std::filesystem::path scriptFile; }; @@ -122,7 +121,7 @@ public: /// @brief Collect world content-packs and load content /// @see loadContent /// @param folder world folder - void loadWorldContent(const fs::path& folder); + void loadWorldContent(const io::path& folder); /// @brief Collect all available content-packs from res/content void loadAllPacks(); @@ -172,7 +171,7 @@ public: EngineController* getController(); cmd::CommandsInterpreter* getCommandsInterpreter(); - PacksManager createPacksManager(const fs::path& worldFolder); + PacksManager createPacksManager(const io::path& worldFolder); void setLevelConsumer(OnWorldOpen levelConsumer); diff --git a/src/engine/ServerMainloop.cpp b/src/engine/ServerMainloop.cpp index 954c2a11..27c35bdd 100644 --- a/src/engine/ServerMainloop.cpp +++ b/src/engine/ServerMainloop.cpp @@ -34,8 +34,10 @@ void ServerMainloop::run() { setLevel(std::move(level)); }); - logger.info() << "starting test " << coreParams.scriptFile; - auto process = scripting::start_coroutine(coreParams.scriptFile); + logger.info() << "starting test " << coreParams.scriptFile.string(); + auto process = scripting::start_coroutine( + "script:" + coreParams.scriptFile.filename().u8string() + ); double targetDelta = 1.0 / static_cast(TPS); double delta = targetDelta; @@ -65,8 +67,11 @@ void ServerMainloop::run() { if (!coreParams.testMode) { auto end = system_clock::now(); - platform::sleep(targetDelta * 1000 - - duration_cast(end - begin).count() / 1000); + int64_t millis = targetDelta * 1000 - + duration_cast(end - begin).count() / 1000; + if (millis > 0) { + platform::sleep(millis); + } begin = system_clock::now(); } } @@ -76,7 +81,7 @@ void ServerMainloop::run() { void ServerMainloop::setLevel(std::unique_ptr level) { if (level == nullptr) { controller->onWorldQuit(); - engine.getPaths().setCurrentWorldFolder(fs::path()); + engine.getPaths().setCurrentWorldFolder(""); controller = nullptr; } else { controller = std::make_unique( diff --git a/src/files/engine_paths.cpp b/src/files/engine_paths.cpp deleted file mode 100644 index 2c8cd02c..00000000 --- a/src/files/engine_paths.cpp +++ /dev/null @@ -1,340 +0,0 @@ -#include "engine_paths.hpp" - -#include -#include -#include -#include -#include -#include "typedefs.hpp" -#include "util/stringutil.hpp" -#include - -#include "WorldFiles.hpp" -#include "debug/Logger.hpp" - -static debug::Logger logger("engine-paths"); - -static inline auto SCREENSHOTS_FOLDER = std::filesystem::u8path("screenshots"); -static inline auto CONTENT_FOLDER = std::filesystem::u8path("content"); -static inline auto WORLDS_FOLDER = std::filesystem::u8path("worlds"); -static inline auto CONFIG_FOLDER = std::filesystem::u8path("config"); -static inline auto EXPORT_FOLDER = std::filesystem::u8path("export"); -static inline auto CONTROLS_FILE = std::filesystem::u8path("controls.toml"); -static inline auto SETTINGS_FILE = std::filesystem::u8path("settings.toml"); - -static std::filesystem::path toCanonic(std::filesystem::path path) { - std::stack parts; - path = path.lexically_normal(); - do { - parts.push(path.filename().u8string()); - path = path.parent_path(); - } while (!path.empty()); - - path = fs::u8path(""); - - while (!parts.empty()) { - const std::string part = parts.top(); - parts.pop(); - if (part == ".") { - continue; - } - if (part == "..") { - throw files_access_error("entry point reached"); - } - - path = path / std::filesystem::path(part); - } - return path; -} - -void EnginePaths::prepare() { - if (!fs::is_directory(resourcesFolder)) { - throw std::runtime_error( - resourcesFolder.u8string() + " is not a directory" - ); - } - if (!fs::is_directory(userFilesFolder)) { - fs::create_directories(userFilesFolder); - } - - logger.info() << "resources folder: " << fs::canonical(resourcesFolder).u8string(); - logger.info() << "user files folder: " << fs::canonical(userFilesFolder).u8string(); - - auto contentFolder = userFilesFolder / CONTENT_FOLDER; - if (!fs::is_directory(contentFolder)) { - fs::create_directories(contentFolder); - } - auto exportFolder = userFilesFolder / EXPORT_FOLDER; - if (!fs::is_directory(exportFolder)) { - fs::create_directories(exportFolder); - } - auto configFolder = userFilesFolder / CONFIG_FOLDER; - if (!fs::is_directory(configFolder)) { - fs::create_directories(configFolder); - } -} - -std::filesystem::path EnginePaths::getUserFilesFolder() const { - return userFilesFolder; -} - -std::filesystem::path EnginePaths::getResourcesFolder() const { - return resourcesFolder; -} - -std::filesystem::path EnginePaths::getNewScreenshotFile(const std::string& ext) { - auto folder = userFilesFolder / SCREENSHOTS_FOLDER; - if (!fs::is_directory(folder)) { - fs::create_directory(folder); - } - - auto t = std::time(nullptr); - auto tm = *std::localtime(&t); - - const char* format = "%Y-%m-%d_%H-%M-%S"; - std::stringstream ss; - ss << std::put_time(&tm, format); - std::string datetimestr = ss.str(); - - auto filename = folder / fs::u8path("screenshot-" + datetimestr + "." + ext); - uint index = 0; - while (fs::exists(filename)) { - filename = folder / fs::u8path( - "screenshot-" + datetimestr + "-" + - std::to_string(index) + "." + ext - ); - index++; - } - return filename; -} - -std::filesystem::path EnginePaths::getWorldsFolder() const { - return userFilesFolder / WORLDS_FOLDER; -} - -std::filesystem::path EnginePaths::getConfigFolder() const { - return userFilesFolder / CONFIG_FOLDER; -} - -std::filesystem::path EnginePaths::getCurrentWorldFolder() { - return currentWorldFolder; -} - -std::filesystem::path EnginePaths::getWorldFolderByName(const std::string& name) { - return getWorldsFolder() / std::filesystem::path(name); -} - -std::filesystem::path EnginePaths::getControlsFile() const { - return userFilesFolder / CONTROLS_FILE; -} - -std::filesystem::path EnginePaths::getSettingsFile() const { - return userFilesFolder / SETTINGS_FILE; -} - -std::vector EnginePaths::scanForWorlds() const { - std::vector folders; - - auto folder = getWorldsFolder(); - if (!fs::is_directory(folder)) return folders; - - for (const auto& entry : fs::directory_iterator(folder)) { - if (!entry.is_directory()) { - continue; - } - const auto& worldFolder = entry.path(); - auto worldFile = worldFolder / fs::u8path(WorldFiles::WORLD_FILE); - if (!fs::is_regular_file(worldFile)) { - continue; - } - folders.push_back(worldFolder); - } - std::sort( - folders.begin(), - folders.end(), - [](std::filesystem::path a, std::filesystem::path b) { - a = a / fs::u8path(WorldFiles::WORLD_FILE); - b = b / fs::u8path(WorldFiles::WORLD_FILE); - return fs::last_write_time(a) > fs::last_write_time(b); - } - ); - return folders; -} - -void EnginePaths::setUserFilesFolder(std::filesystem::path folder) { - this->userFilesFolder = std::move(folder); -} - -void EnginePaths::setResourcesFolder(std::filesystem::path folder) { - this->resourcesFolder = std::move(folder); -} - -void EnginePaths::setScriptFolder(std::filesystem::path folder) { - this->scriptFolder = std::move(folder); -} - -void EnginePaths::setCurrentWorldFolder(std::filesystem::path folder) { - this->currentWorldFolder = std::move(folder); -} - -void EnginePaths::setContentPacks(std::vector* contentPacks) { - this->contentPacks = contentPacks; -} - -std::tuple EnginePaths::parsePath(std::string_view path) { - size_t separator = path.find(':'); - if (separator == std::string::npos) { - return {"", std::string(path)}; - } - auto prefix = std::string(path.substr(0, separator)); - auto filename = std::string(path.substr(separator + 1)); - return {prefix, filename}; -} - -std::filesystem::path EnginePaths::resolve( - const std::string& path, bool throwErr -) const { - auto [prefix, filename] = EnginePaths::parsePath(path); - if (prefix.empty()) { - throw files_access_error("no entry point specified"); - } - filename = toCanonic(fs::u8path(filename)).u8string(); - - if (prefix == "res" || prefix == "core") { - return resourcesFolder / fs::u8path(filename); - } - if (prefix == "user") { - return userFilesFolder / fs::u8path(filename); - } - if (prefix == "config") { - return getConfigFolder() / fs::u8path(filename); - } - if (prefix == "world") { - return currentWorldFolder / fs::u8path(filename); - } - if (prefix == "export") { - return userFilesFolder / EXPORT_FOLDER / fs::u8path(filename); - } - if (prefix == "script" && scriptFolder) { - return scriptFolder.value() / fs::u8path(filename); - } - if (contentPacks) { - for (auto& pack : *contentPacks) { - if (pack.id == prefix) { - return pack.folder / fs::u8path(filename); - } - } - } - if (throwErr) { - throw files_access_error("unknown entry point '" + prefix + "'"); - } - return std::filesystem::path(filename); -} - -ResPaths::ResPaths(std::filesystem::path mainRoot, std::vector roots) - : mainRoot(std::move(mainRoot)), roots(std::move(roots)) { -} - -std::filesystem::path ResPaths::find(const std::string& filename) const { - for (int i = roots.size() - 1; i >= 0; i--) { - auto& root = roots[i]; - auto file = root.path / fs::u8path(filename); - if (fs::exists(file)) { - return file; - } - } - return mainRoot / fs::u8path(filename); -} - -std::string ResPaths::findRaw(const std::string& filename) const { - for (int i = roots.size() - 1; i >= 0; i--) { - auto& root = roots[i]; - if (fs::exists(root.path / std::filesystem::path(filename))) { - return root.name + ":" + filename; - } - } - throw std::runtime_error("could not to find file " + util::quote(filename)); -} - -std::vector ResPaths::listdirRaw(const std::string& folderName) const { - std::vector entries; - for (int i = roots.size() - 1; i >= 0; i--) { - auto& root = roots[i]; - auto folder = root.path / fs::u8path(folderName); - if (!fs::is_directory(folder)) continue; - for (const auto& entry : fs::directory_iterator(folder)) { - auto name = entry.path().filename().u8string(); - entries.emplace_back(root.name + ":" + folderName + "/" + name); - } - } - return entries; -} - -std::vector ResPaths::listdir( - const std::string& folderName -) const { - std::vector entries; - for (int i = roots.size() - 1; i >= 0; i--) { - auto& root = roots[i]; - std::filesystem::path folder = root.path / fs::u8path(folderName); - if (!fs::is_directory(folder)) continue; - for (const auto& entry : fs::directory_iterator(folder)) { - entries.push_back(entry.path()); - } - } - return entries; -} - -dv::value ResPaths::readCombinedList(const std::string& filename) const { - dv::value list = dv::list(); - for (const auto& root : roots) { - auto path = root.path / fs::u8path(filename); - if (!fs::exists(path)) { - continue; - } - try { - auto value = files::read_object(path); - if (!value.isList()) { - logger.warning() << "reading combined list " << root.name << ":" - << filename << " is not a list (skipped)"; - continue; - } - for (const auto& elem : value) { - list.add(elem); - } - } catch (const std::runtime_error& err) { - logger.warning() << "reading combined list " << root.name << ":" - << filename << ": " << err.what(); - } - } - return list; -} - -dv::value ResPaths::readCombinedObject(const std::string& filename) const { - dv::value object = dv::object(); - for (const auto& root : roots) { - auto path = root.path / fs::u8path(filename); - if (!fs::exists(path)) { - continue; - } - try { - auto value = files::read_object(path); - if (!value.isObject()) { - logger.warning() - << "reading combined object " << root.name << ": " - << filename << " is not an object (skipped)"; - } - for (const auto& [key, element] : value.asObject()) { - object[key] = element; - } - } catch (const std::runtime_error& err) { - logger.warning() << "reading combined object " << root.name << ":" - << filename << ": " << err.what(); - } - } - return object; -} - -const std::filesystem::path& ResPaths::getMainRoot() const { - return mainRoot; -} diff --git a/src/files/engine_paths.hpp b/src/files/engine_paths.hpp deleted file mode 100644 index 5ec34704..00000000 --- a/src/files/engine_paths.hpp +++ /dev/null @@ -1,87 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include - -#include "data/dv.hpp" -#include "content/ContentPack.hpp" - - -class files_access_error : public std::runtime_error { -public: - files_access_error(const std::string& msg) : std::runtime_error(msg) { - } -}; - -class EnginePaths { -public: - void prepare(); - - void setUserFilesFolder(std::filesystem::path folder); - std::filesystem::path getUserFilesFolder() const; - - void setResourcesFolder(std::filesystem::path folder); - std::filesystem::path getResourcesFolder() const; - - void setScriptFolder(std::filesystem::path folder); - - std::filesystem::path getWorldFolderByName(const std::string& name); - std::filesystem::path getWorldsFolder() const; - std::filesystem::path getConfigFolder() const; - - void setCurrentWorldFolder(std::filesystem::path folder); - std::filesystem::path getCurrentWorldFolder(); - - std::filesystem::path getNewScreenshotFile(const std::string& ext); - std::filesystem::path getControlsFile() const; - std::filesystem::path getSettingsFile() const; - - void setContentPacks(std::vector* contentPacks); - - std::vector scanForWorlds() const; - - std::filesystem::path resolve(const std::string& path, bool throwErr = true) const; - - static std::tuple parsePath(std::string_view view); - - static inline auto CONFIG_DEFAULTS = - std::filesystem::u8path("config/defaults.toml"); -private: - std::filesystem::path userFilesFolder {"."}; - std::filesystem::path resourcesFolder {"res"}; - std::filesystem::path currentWorldFolder; - std::optional scriptFolder; - std::vector* contentPacks = nullptr; -}; - -struct PathsRoot { - std::string name; - std::filesystem::path path; -}; - -class ResPaths { -public: - ResPaths(std::filesystem::path mainRoot, std::vector roots); - - std::filesystem::path find(const std::string& filename) const; - std::string findRaw(const std::string& filename) const; - std::vector listdir(const std::string& folder) const; - std::vector listdirRaw(const std::string& folder) const; - - /// @brief Read all found list versions from all packs and combine into a - /// single list. Invalid versions will be skipped with logging a warning - /// @param file *.json file path relative to entry point - dv::value readCombinedList(const std::string& file) const; - - dv::value readCombinedObject(const std::string& file) const; - - const std::filesystem::path& getMainRoot() const; - -private: - std::filesystem::path mainRoot; - std::vector roots; -}; diff --git a/src/files/files.cpp b/src/files/files.cpp deleted file mode 100644 index f7d3f460..00000000 --- a/src/files/files.cpp +++ /dev/null @@ -1,199 +0,0 @@ -#include "files.hpp" - -#include - -#include -#include -#include -#include - -#include "coders/commons.hpp" -#include "coders/gzip.hpp" -#include "coders/json.hpp" -#include "coders/toml.hpp" -#include "util/stringutil.hpp" - -namespace fs = std::filesystem; - -files::rafile::rafile(const fs::path& filename) - : file(filename, std::ios::binary | std::ios::ate) { - if (!file) { - throw std::runtime_error("could not to open file " + filename.string()); - } - filelength = file.tellg(); - file.seekg(0); -} - -size_t files::rafile::length() const { - return filelength; -} - -void files::rafile::seekg(std::streampos pos) { - file.seekg(pos); -} - -void files::rafile::read(char* buffer, std::streamsize size) { - file.read(buffer, size); -} - -bool files::write_bytes( - const fs::path& filename, const ubyte* data, size_t size -) { - std::ofstream output(filename, std::ios::binary); - if (!output.is_open()) return false; - output.write((const char*)data, size); - output.close(); - return true; -} - -uint files::append_bytes( - const fs::path& filename, const ubyte* data, size_t size -) { - std::ofstream output(filename, std::ios::binary | std::ios::app); - if (!output.is_open()) return 0; - uint position = output.tellp(); - output.write((const char*)data, size); - output.close(); - return position; -} - -bool files::read(const fs::path& filename, char* data, size_t size) { - std::ifstream output(filename, std::ios::binary); - if (!output.is_open()) return false; - output.read(data, size); - output.close(); - return true; -} - -util::Buffer files::read_bytes_buffer(const fs::path& path) { - size_t size; - auto bytes = files::read_bytes(path, size); - return util::Buffer(std::move(bytes), size); -} - -std::unique_ptr files::read_bytes( - const fs::path& filename, size_t& length -) { - std::ifstream input(filename, std::ios::binary); - if (!input.is_open()) { - throw std::runtime_error( - "could not to load file '" + filename.string() + "'" - ); - } - input.seekg(0, std::ios_base::end); - length = input.tellg(); - input.seekg(0, std::ios_base::beg); - - auto data = std::make_unique(length); - input.read((char*)data.get(), length); - input.close(); - return data; -} - -std::vector files::read_bytes(const fs::path& filename) { - std::ifstream input(filename, std::ios::binary); - if (!input.is_open()) return {}; - input.seekg(0, std::ios_base::end); - size_t length = input.tellg(); - input.seekg(0, std::ios_base::beg); - - std::vector data(length); - data.resize(length); - input.read((char*)data.data(), length); - input.close(); - return data; -} - -std::string files::read_string(const fs::path& filename) { - size_t size; - auto bytes = read_bytes(filename, size); - return std::string((const char*)bytes.get(), size); -} - -bool files::write_string(const fs::path& filename, std::string_view content) { - std::ofstream file(filename); - if (!file) { - return false; - } - file << content; - return true; -} - -bool files::write_json( - const fs::path& filename, const dv::value& obj, bool nice -) { - return files::write_string(filename, json::stringify(obj, nice, " ")); -} - -bool files::write_binary_json( - const fs::path& filename, const dv::value& obj, bool compression -) { - auto bytes = json::to_binary(obj, compression); - return files::write_bytes(filename, bytes.data(), bytes.size()); -} - -dv::value files::read_json(const fs::path& filename) { - std::string text = files::read_string(filename); - return json::parse(filename.string(), text); -} - -dv::value files::read_binary_json(const fs::path& file) { - size_t size; - auto bytes = files::read_bytes(file, size); - return json::from_binary(bytes.get(), size); -} - -dv::value files::read_toml(const fs::path& file) { - return toml::parse(file.u8string(), files::read_string(file)); -} - -std::vector files::read_list(const fs::path& filename) { - std::ifstream file(filename); - if (!file) { - throw std::runtime_error( - "could not to open file " + filename.u8string() - ); - } - std::vector lines; - std::string line; - while (std::getline(file, line)) { - util::trim(line); - if (line.length() == 0) continue; - if (line[0] == '#') continue; - lines.push_back(line); - } - return lines; -} - -#include - -#include "coders/json.hpp" -#include "coders/toml.hpp" - -using DecodeFunc = dv::value(*)(std::string_view, std::string_view); - -static std::map data_decoders { - {fs::u8path(".json"), json::parse}, - {fs::u8path(".toml"), toml::parse}, -}; - -bool files::is_data_file(const fs::path& file) { - return is_data_interchange_format(file.extension()); -} - -bool files::is_data_interchange_format(const fs::path& ext) { - return data_decoders.find(ext) != data_decoders.end(); -} - -dv::value files::read_object(const fs::path& file) { - const auto& found = data_decoders.find(file.extension()); - if (found == data_decoders.end()) { - throw std::runtime_error("unknown file format"); - } - auto text = read_string(file); - try { - return found->second(file.u8string(), text); - } catch (const parsing_error& err) { - throw std::runtime_error(err.errorLog()); - } -} diff --git a/src/files/files.hpp b/src/files/files.hpp deleted file mode 100644 index 4cf509d4..00000000 --- a/src/files/files.hpp +++ /dev/null @@ -1,80 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -#include "typedefs.hpp" -#include "data/dv.hpp" -#include "util/Buffer.hpp" - -namespace fs = std::filesystem; - -namespace files { - /// @brief Read-only random access file - class rafile { - std::ifstream file; - size_t filelength; - public: - rafile(const fs::path& filename); - - void seekg(std::streampos pos); - void read(char* buffer, std::streamsize size); - size_t length() const; - }; - - /// @brief Write bytes array to the file without any extra data - /// @param file target file - /// @param data data bytes array - /// @param size size of data bytes array - bool write_bytes(const fs::path& file, const ubyte* data, size_t size); - - /// @brief Append bytes array to the file without any extra data - /// @param file target file - /// @param data data bytes array - /// @param size size of data bytes array - uint append_bytes(const fs::path& file, const ubyte* data, size_t size); - - /// @brief Write string to the file - bool write_string(const fs::path& filename, std::string_view content); - - /// @brief Write dynamic data to the JSON file - /// @param nice if true, human readable format will be used, otherwise - /// minimal - bool write_json( - const fs::path& filename, const dv::value& obj, bool nice = true - ); - - /// @brief Write dynamic data to the binary JSON file - /// (see src/coders/binary_json_spec.md) - /// @param compressed use gzip compression - bool write_binary_json( - const fs::path& filename, - const dv::value& obj, - bool compressed = false - ); - - bool read(const fs::path&, char* data, size_t size); - util::Buffer read_bytes_buffer(const fs::path&); - std::unique_ptr read_bytes(const fs::path&, size_t& length); - std::vector read_bytes(const fs::path&); - std::string read_string(const fs::path& filename); - - /// @brief Read JSON or BJSON file - /// @param file *.json or *.bjson file - dv::value read_json(const fs::path& file); - - dv::value read_binary_json(const fs::path& file); - - /// @brief Read TOML file - /// @param file *.toml file - dv::value read_toml(const fs::path& file); - - std::vector read_list(const fs::path& file); - - bool is_data_file(const fs::path& file); - bool is_data_interchange_format(const fs::path& ext); - dv::value read_object(const fs::path& file); -} diff --git a/src/frontend/UiDocument.cpp b/src/frontend/UiDocument.cpp index 2108b46e..1dfa18aa 100644 --- a/src/frontend/UiDocument.cpp +++ b/src/frontend/UiDocument.cpp @@ -2,7 +2,7 @@ #include -#include "files/files.hpp" +#include "io/io.hpp" #include "graphics/ui/elements/UINode.hpp" #include "graphics/ui/elements/InventoryView.hpp" #include "graphics/ui/gui_xml.hpp" @@ -56,22 +56,22 @@ scriptenv UiDocument::getEnvironment() const { std::unique_ptr UiDocument::read( const scriptenv& penv, const std::string& name, - const fs::path& file, + const io::path& file, const std::string& fileName ) { - const std::string text = files::read_string(file); - auto xmldoc = xml::parse(file.u8string(), text); + const std::string text = io::read_string(file); + auto xmldoc = xml::parse(file.string(), text); auto env = penv == nullptr ? scripting::create_doc_environment(scripting::get_root_environment(), name) : scripting::create_doc_environment(penv, name); gui::UiXmlReader reader(env); - auto view = reader.readXML(file.u8string(), *xmldoc->getRoot()); + auto view = reader.readXML(file.string(), *xmldoc->getRoot()); view->setId("root"); uidocscript script {}; - auto scriptFile = fs::path(file.u8string()+".lua"); - if (fs::is_regular_file(scriptFile)) { + auto scriptFile = io::path(file.string()+".lua"); + if (io::is_regular_file(scriptFile)) { scripting::load_layout_script( env, name, scriptFile, fileName + ".lua", script ); @@ -80,8 +80,8 @@ std::unique_ptr UiDocument::read( } std::shared_ptr UiDocument::readElement( - const fs::path& file, const std::string& fileName + const io::path& file, const std::string& fileName ) { - auto document = read(nullptr, file.filename().u8string(), file, fileName); + auto document = read(nullptr, file.name(), file, fileName); return document->getRoot(); } diff --git a/src/frontend/UiDocument.hpp b/src/frontend/UiDocument.hpp index dbf91ef1..298e63bc 100644 --- a/src/frontend/UiDocument.hpp +++ b/src/frontend/UiDocument.hpp @@ -4,10 +4,9 @@ #include #include -#include #include -namespace fs = std::filesystem; +#include "io/fwd.hpp" namespace gui { class UINode; @@ -48,10 +47,10 @@ public: static std::unique_ptr read( const scriptenv& parent_env, const std::string& name, - const fs::path& file, + const io::path& file, const std::string& fileName ); static std::shared_ptr readElement( - const fs::path& file, const std::string& fileName + const io::path& file, const std::string& fileName ); }; diff --git a/src/frontend/hud.cpp b/src/frontend/hud.cpp index bcf623ce..1684799f 100644 --- a/src/frontend/hud.cpp +++ b/src/frontend/hud.cpp @@ -70,7 +70,7 @@ std::shared_ptr create_debug_panel( ); HudElement::HudElement( - hud_element_mode mode, + HudElementMode mode, UiDocument* document, std::shared_ptr node, bool debug @@ -83,16 +83,16 @@ void HudElement::update(bool pause, bool inventoryOpen, bool debugMode) { return; } switch (mode) { - case hud_element_mode::permanent: + case HudElementMode::PERMANENT: node->setVisible(true); break; - case hud_element_mode::ingame: + case HudElementMode::INGAME: node->setVisible(!pause && !inventoryOpen); break; - case hud_element_mode::inventory_any: + case HudElementMode::INVENTORY_ANY: node->setVisible(inventoryOpen); break; - case hud_element_mode::inventory_bound: + case HudElementMode::INVENTORY: removed = !inventoryOpen; break; } @@ -108,21 +108,21 @@ std::shared_ptr HudElement::getNode() const { std::shared_ptr Hud::createContentAccess() { auto& content = frontend.getLevel().content; - auto indices = content.getIndices(); + auto& indices = *content.getIndices(); auto inventory = player.getInventory(); - size_t itemsCount = indices->items.count(); + size_t itemsCount = indices.items.count(); auto accessInventory = std::make_shared(0, itemsCount); for (size_t id = 1; id < itemsCount; id++) { accessInventory->getSlot(id-1).set(ItemStack(id, 1)); } SlotLayout slotLayout(-1, glm::vec2(), false, true, nullptr, - [=](uint, ItemStack& item) { + [inventory, &indices](uint, ItemStack& item) { auto copy = ItemStack(item); inventory->move(copy, indices); }, - [=](uint, ItemStack& item) { + [this, inventory](uint, ItemStack& item) { inventory->getSlot(player.getChosenSlot()).set(item); }); @@ -192,7 +192,7 @@ Hud::Hud(Engine& engine, LevelFrontend& frontend, Player& player) auto dplotter = std::make_shared(350, 250, 2000, 16); dplotter->setGravity(Gravity::bottom_right); dplotter->setInteractive(false); - add(HudElement(hud_element_mode::permanent, nullptr, dplotter, true)); + add(HudElement(HudElementMode::PERMANENT, nullptr, dplotter, true)); assets.store(Texture::from(debugImgWorldGen.get()), DEBUG_WORLDGEN_IMAGE); @@ -200,7 +200,7 @@ Hud::Hud(Engine& engine, LevelFrontend& frontend, Player& player) "" ); - add(HudElement(hud_element_mode::permanent, nullptr, debugMinimap, true)); + add(HudElement(HudElementMode::PERMANENT, nullptr, debugMinimap, true)); } Hud::~Hud() { @@ -372,8 +372,8 @@ void Hud::openInventory() { auto inventoryDocument = assets.get("core:inventory"); inventoryView = std::dynamic_pointer_cast(inventoryDocument->getRoot()); inventoryView->bind(inventory, &content); - add(HudElement(hud_element_mode::inventory_bound, inventoryDocument, inventoryView, false)); - add(HudElement(hud_element_mode::inventory_bound, nullptr, exchangeSlot, false)); + add(HudElement(HudElementMode::INVENTORY, inventoryDocument, inventoryView, false)); + add(HudElement(HudElementMode::INVENTORY, nullptr, exchangeSlot, false)); } std::shared_ptr Hud::openInventory( @@ -401,7 +401,7 @@ std::shared_ptr Hud::openInventory( inv = level.inventories->createVirtual(secondInvView->getSlotsCount()); } secondInvView->bind(inv, &content); - add(HudElement(hud_element_mode::inventory_bound, doc, secondUI, false)); + add(HudElement(HudElementMode::INVENTORY, doc, secondUI, false)); scripting::on_inventory_open(&player, *inv); return inv; } @@ -436,7 +436,7 @@ void Hud::openInventory( blockUI->bind(blockinv, &content); blockPos = block; currentblockid = chunks.require(block.x, block.y, block.z).id; - add(HudElement(hud_element_mode::inventory_bound, doc, blockUI, false)); + add(HudElement(HudElementMode::INVENTORY, doc, blockUI, false)); scripting::on_inventory_open(&player, *blockinv); } @@ -468,8 +468,7 @@ void Hud::showOverlay( showExchangeSlot(); inventoryOpen = true; } - add(HudElement(hud_element_mode::inventory_bound, doc, secondUI, false), - args); + add(HudElement(HudElementMode::INVENTORY, doc, secondUI, false), args); } void Hud::openPermanent(UiDocument* doc) { @@ -480,7 +479,7 @@ void Hud::openPermanent(UiDocument* doc) { if (invview) { invview->bind(player.getInventory(), &frontend.getLevel().content); } - add(HudElement(hud_element_mode::permanent, doc, doc->getRoot(), false)); + add(HudElement(HudElementMode::PERMANENT, doc, doc->getRoot(), false)); } void Hud::dropExchangeSlot() { @@ -494,12 +493,12 @@ void Hud::dropExchangeSlot() { auto indices = frontend.getLevel().content.getIndices(); if (auto invView = std::dynamic_pointer_cast(blockUI)) { - invView->getInventory()->move(stack, indices); + invView->getInventory()->move(stack, *indices); } if (stack.isEmpty()) { return; } - player.getInventory()->move(stack, indices); + player.getInventory()->move(stack, *indices); if (!stack.isEmpty()) { logger.warning() << "discard item [" << stack.getItemId() << ":" << stack.getCount(); @@ -592,7 +591,10 @@ void Hud::draw(const DrawContext& ctx){ const uint height = viewport.getHeight(); auto menu = gui.getMenu(); - darkOverlay->setVisible(menu->hasOpenPage()); + bool is_menu_open = menu->hasOpenPage(); + darkOverlay->setVisible(is_menu_open); + menu->setVisible(is_menu_open); + updateElementsPosition(viewport); uicamera->setFov(height); @@ -693,7 +695,6 @@ void Hud::setPause(bool pause) { if (pause && !menu->hasOpenPage()) { menu->setPage("pause"); } - menu->setVisible(pause); } Player* Hud::getPlayer() const { diff --git a/src/frontend/hud.hpp b/src/frontend/hud.hpp index 07affc66..a978e282 100644 --- a/src/frontend/hud.hpp +++ b/src/frontend/hud.hpp @@ -30,26 +30,26 @@ namespace gui { class SlotView; } -enum class hud_element_mode { +enum class HudElementMode { // element is hidden if menu or inventory open - ingame, + INGAME, // element is visible if hud is visible - permanent, + PERMANENT, // element is visible in inventory mode - inventory_any, + INVENTORY_ANY, // element will be removed on inventory close - inventory_bound + INVENTORY }; class HudElement { - hud_element_mode mode; + HudElementMode mode; UiDocument* document; std::shared_ptr node; bool debug; bool removed = false; public: - HudElement(hud_element_mode mode, UiDocument* document, std::shared_ptr node, bool debug); + HudElement(HudElementMode mode, UiDocument* document, std::shared_ptr node, bool debug); void update(bool pause, bool inventoryOpen, bool debug); @@ -57,7 +57,7 @@ public: std::shared_ptr getNode() const; bool isInventoryBound() const { - return mode == hud_element_mode::inventory_bound; + return mode == HudElementMode::INVENTORY; } void setRemoved() { diff --git a/src/frontend/locale.cpp b/src/frontend/locale.cpp index 2af78076..d199cb49 100644 --- a/src/frontend/locale.cpp +++ b/src/frontend/locale.cpp @@ -5,7 +5,7 @@ #include "coders/json.hpp" #include "coders/commons.hpp" #include "content/ContentPack.hpp" -#include "files/files.hpp" +#include "io/io.hpp" #include "util/stringutil.hpp" #include "data/dv.hpp" #include "debug/Logger.hpp" @@ -69,9 +69,9 @@ namespace { }; } -void langs::loadLocalesInfo(const fs::path& resdir, std::string& fallback) { - auto file = resdir/fs::u8path(langs::TEXTS_FOLDER)/fs::u8path("langs.json"); - auto root = files::read_json(file); +void langs::loadLocalesInfo(const io::path& resdir, std::string& fallback) { + auto file = resdir / langs::TEXTS_FOLDER / "langs.json"; + auto root = io::read_json(file); langs::locales_info.clear(); root.at("fallback").get(fallback); @@ -94,7 +94,7 @@ void langs::loadLocalesInfo(const fs::path& resdir, std::string& fallback) { } } -std::string langs::locale_by_envlocale(const std::string& envlocale, const fs::path& resdir){ +std::string langs::locale_by_envlocale(const std::string& envlocale, const io::path& resdir){ std::string fallback = FALLBACK_DEFAULT; if (locales_info.size() == 0) { loadLocalesInfo(resdir, fallback); @@ -115,29 +115,29 @@ std::string langs::locale_by_envlocale(const std::string& envlocale, const fs::p } } -void langs::load(const fs::path& resdir, +void langs::load(const io::path& resdir, const std::string& locale, const std::vector& packs, Lang& lang) { - fs::path filename = fs::path(TEXTS_FOLDER)/fs::path(locale + LANG_FILE_EXT); - fs::path core_file = resdir/filename; + io::path filename = io::path(TEXTS_FOLDER) / (locale + LANG_FILE_EXT); + io::path core_file = resdir / filename; - if (fs::is_regular_file(core_file)) { - std::string text = files::read_string(core_file); + if (io::is_regular_file(core_file)) { + std::string text = io::read_string(core_file); Reader reader(core_file.string(), text); reader.read(lang, ""); } for (auto pack : packs) { - fs::path file = pack.folder/filename; - if (fs::is_regular_file(file)) { - std::string text = files::read_string(file); + io::path file = pack.folder / filename; + if (io::is_regular_file(file)) { + std::string text = io::read_string(file); Reader reader(file.string(), text); reader.read(lang, ""); } } } -void langs::load(const fs::path& resdir, +void langs::load(const io::path& resdir, const std::string& locale, const std::string& fallback, const std::vector& packs) { @@ -149,7 +149,7 @@ void langs::load(const fs::path& resdir, current = std::move(lang); } -void langs::setup(const fs::path& resdir, +void langs::setup(const io::path& resdir, std::string locale, const std::vector& packs) { std::string fallback = langs::FALLBACK_DEFAULT; diff --git a/src/frontend/locale.hpp b/src/frontend/locale.hpp index b3e200c9..1daaca21 100644 --- a/src/frontend/locale.hpp +++ b/src/frontend/locale.hpp @@ -3,9 +3,10 @@ #include #include #include -#include #include +#include "io/fwd.hpp" + struct ContentPack; namespace langs { @@ -44,17 +45,17 @@ namespace langs { extern std::unordered_map locales_info; extern void loadLocalesInfo( - const std::filesystem::path& resdir, + const io::path& resdir, std::string& fallback); extern std::string locale_by_envlocale(const std::string& envlocale, - const std::filesystem::path& resdir); + const io::path& resdir); - extern void load(const std::filesystem::path& resdir, + extern void load(const io::path& resdir, const std::string& locale, const std::vector& packs, Lang& lang); - extern void load(const std::filesystem::path& resdir, + extern void load(const io::path& resdir, const std::string& locale, const std::string& fallback, const std::vector& packs); @@ -63,7 +64,7 @@ namespace langs { extern const std::wstring& get(const std::wstring& key, const std::wstring& context); - extern void setup(const std::filesystem::path& resdir, + extern void setup(const io::path& resdir, std::string locale, const std::vector& packs); } diff --git a/src/frontend/menu.cpp b/src/frontend/menu.cpp index 4a36410c..da2e6ba2 100644 --- a/src/frontend/menu.cpp +++ b/src/frontend/menu.cpp @@ -8,7 +8,7 @@ #include "engine/Engine.hpp" #include "data/dv.hpp" #include "interfaces/Task.hpp" -#include "files/engine_paths.hpp" +#include "io/engine_paths.hpp" #include "graphics/ui/elements/Menu.hpp" #include "graphics/ui/gui_util.hpp" #include "graphics/ui/GUI.hpp" diff --git a/src/frontend/screens/LevelScreen.cpp b/src/frontend/screens/LevelScreen.cpp index 0e9eb609..08e1b41e 100644 --- a/src/frontend/screens/LevelScreen.cpp +++ b/src/frontend/screens/LevelScreen.cpp @@ -6,7 +6,7 @@ #include "core_defs.hpp" #include "debug/Logger.hpp" #include "engine/Engine.hpp" -#include "files/files.hpp" +#include "io/io.hpp" #include "frontend/LevelFrontend.hpp" #include "frontend/hud.hpp" #include "graphics/core/DrawContext.hpp" @@ -105,8 +105,8 @@ void LevelScreen::initializeContent() { void LevelScreen::initializePack(ContentPackRuntime* pack) { const ContentPack& info = pack->getInfo(); - fs::path scriptFile = info.folder/fs::path("scripts/hud.lua"); - if (fs::is_regular_file(scriptFile)) { + io::path scriptFile = info.folder / "scripts/hud.lua"; + if (io::is_regular_file(scriptFile)) { scripting::load_hud_script( pack->getEnvironment(), info.id, @@ -124,7 +124,7 @@ LevelScreen::~LevelScreen() { // unblock all bindings Events::enableBindings(); controller->onWorldQuit(); - engine.getPaths().setCurrentWorldFolder(fs::path()); + engine.getPaths().setCurrentWorldFolder(""); } void LevelScreen::saveWorldPreview() { @@ -147,7 +147,7 @@ void LevelScreen::saveWorldPreview() { worldRenderer->draw(ctx, camera, false, true, 0.0f, postProcessing.get()); auto image = postProcessing->toImage(); image->flipY(); - imageio::write(paths.resolve("world:preview.png").u8string(), image.get()); + imageio::write("world:preview.png", image.get()); } catch (const std::exception& err) { logger.error() << err.what(); } diff --git a/src/graphics/core/Batch2D.hpp b/src/graphics/core/Batch2D.hpp index 6bfdb14d..5610f603 100644 --- a/src/graphics/core/Batch2D.hpp +++ b/src/graphics/core/Batch2D.hpp @@ -48,10 +48,15 @@ public: void sprite(float x, float y, float w, float h, float skew, int atlasRes, int index, glm::vec4 tint); void point(float x, float y, float r, float g, float b, float a); - inline void setColor(glm::vec4 color) { + void setColor(glm::vec4 color) { this->color = color; } - inline glm::vec4 getColor() const { + + void resetColor() { + this->color = glm::vec4(1.0f); + } + + glm::vec4 getColor() const { return color; } diff --git a/src/graphics/render/BlockWrapsRenderer.cpp b/src/graphics/render/BlockWrapsRenderer.cpp index 0b833a60..da363352 100644 --- a/src/graphics/render/BlockWrapsRenderer.cpp +++ b/src/graphics/render/BlockWrapsRenderer.cpp @@ -56,7 +56,10 @@ void BlockWrapsRenderer::draw(const BlockWrapper& wrapper) { ); break; case BlockModel::aabb: { - const auto& aabb = def.rt.hitboxes[vox->state.rotation].at(0); + const auto& aabb = + (def.rotatable ? def.rt.hitboxes[vox->state.rotation] + : def.hitboxes) + .at(0); const auto& size = aabb.size(); regions[0].scale(size.z, size.y); regions[1].scale(size.z, size.y); diff --git a/src/graphics/render/Decorator.cpp b/src/graphics/render/Decorator.cpp index e6331820..605e9ec6 100644 --- a/src/graphics/render/Decorator.cpp +++ b/src/graphics/render/Decorator.cpp @@ -16,7 +16,7 @@ #include "logic/LevelController.hpp" #include "util/stringutil.hpp" #include "engine/Engine.hpp" -#include "files/files.hpp" +#include "io/io.hpp" namespace fs = std::filesystem; diff --git a/src/graphics/ui/GUI.cpp b/src/graphics/ui/GUI.cpp index 08485201..55dd8ceb 100644 --- a/src/graphics/ui/GUI.cpp +++ b/src/graphics/ui/GUI.cpp @@ -5,6 +5,7 @@ #include "elements/UINode.hpp" #include "elements/Label.hpp" #include "elements/Menu.hpp" +#include "elements/Panel.hpp" #include "assets/Assets.hpp" #include "frontend/UiDocument.hpp" @@ -23,8 +24,10 @@ using namespace gui; -GUI::GUI() : batch2D(std::make_unique(1024)) { - container = std::make_shared(glm::vec2(1000)); +GUI::GUI() + : batch2D(std::make_unique(1024)), + container(std::make_shared(glm::vec2(1000))) { + container->setId("root"); uicamera = std::make_unique(glm::vec3(), Window::height); uicamera->perspective = false; uicamera->flipped = true; @@ -214,6 +217,14 @@ void GUI::draw(const DrawContext& pctx, const Assets& assets) { auto& viewport = ctx.getViewport(); glm::vec2 wsize = viewport.size(); + auto& page = menu->getCurrent(); + if (page.panel) { + menu->setSize(page.panel->getSize()); + page.panel->refresh(); + if (auto panel = std::dynamic_pointer_cast(page.panel)) { + panel->cropToContent(); + } + } menu->setPos((wsize - menu->getSize()) / 2.0f); uicamera->setFov(wsize.y); diff --git a/src/graphics/ui/elements/InputBindBox.cpp b/src/graphics/ui/elements/InputBindBox.cpp index 2729cbb6..780b875b 100644 --- a/src/graphics/ui/elements/InputBindBox.cpp +++ b/src/graphics/ui/elements/InputBindBox.cpp @@ -13,6 +13,7 @@ InputBindBox::InputBindBox(Binding& binding, glm::vec4 padding) label(std::make_shared