Merge remote-tracking branch 'upstream/main'
This commit is contained in:
commit
9813170a6f
4
.github/workflows/appimage.yml
vendored
4
.github/workflows/appimage.yml
vendored
@ -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
|
||||
|
||||
13
Dockerfile
13
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
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -218,6 +218,8 @@
|
||||
|
||||
## Пользовательские свойства
|
||||
|
||||
Доступ к пользовательским свойствам производится через таблицу `block.properties[id]["свойство"]` где id - числовой id (индекс) блока.
|
||||
|
||||
Пользовательские свойства должны быть объявляены в файле `пак:config/user-props.toml`:
|
||||
```toml
|
||||
"пак:имя_свойства" = {}
|
||||
|
||||
@ -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).
|
||||
|
||||
## Разделы
|
||||
|
||||
|
||||
@ -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
|
||||
```
|
||||
|
||||
Возвращает сумму всех принимаемых аргументов. Если в качестве аргумента была передана таблица, метод вернёт сумму всех её элементов.
|
||||
|
||||
## Дополнительные глобальные функции
|
||||
|
||||
В этом же скрипте также определены и другие глобальные функции которые доступны для использования. Ниже их список
|
||||
|
||||
@ -1 +1,2 @@
|
||||
generator = "core:default"
|
||||
player-entity = ""
|
||||
|
||||
@ -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"
|
||||
}
|
||||
|
||||
@ -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"
|
||||
}
|
||||
|
||||
@ -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"
|
||||
}
|
||||
|
||||
@ -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"
|
||||
}
|
||||
|
||||
@ -2,5 +2,5 @@
|
||||
"texture": "dirt",
|
||||
"material": "base:ground",
|
||||
"surface-replacement": "base:grass_block",
|
||||
"base:durability": 1.0
|
||||
"base:durability": 1.5
|
||||
}
|
||||
|
||||
@ -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": []
|
||||
}
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
"grass_side",
|
||||
"grass_side"
|
||||
],
|
||||
"base:durability": 1.3,
|
||||
"base:durability": 1.7,
|
||||
"base:loot": [
|
||||
{"item": "base:dirt.item"}
|
||||
]
|
||||
|
||||
@ -1 +1,2 @@
|
||||
generator = "base:demo"
|
||||
player-entity = "base:player"
|
||||
|
||||
@ -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
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
{
|
||||
"icon-type": "sprite",
|
||||
"icon": "items:bazalt_breaker"
|
||||
"icon": "items:bazalt_breaker",
|
||||
"uses": 100
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"id": "base",
|
||||
"title": "Base",
|
||||
"version": "0.26",
|
||||
"version": "0.27",
|
||||
"description": "basic content package"
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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))
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
BIN
res/content/base/sounds/blocks/ground_hit.ogg
Normal file
BIN
res/content/base/sounds/blocks/ground_hit.ogg
Normal file
Binary file not shown.
Binary file not shown.
BIN
res/content/base/sounds/blocks/stone_hit.ogg
Normal file
BIN
res/content/base/sounds/blocks/stone_hit.ogg
Normal file
Binary file not shown.
BIN
res/content/base/sounds/blocks/wood_hit.ogg
Normal file
BIN
res/content/base/sounds/blocks/wood_hit.ogg
Normal file
Binary file not shown.
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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=Змяніць рэжым камеры
|
||||
72
run.sh
72
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
|
||||
@ -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<LayoutCfg>(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<std::filesystem::path>& alternatives
|
||||
const std::vector<io::path>& alternatives
|
||||
) {
|
||||
if (assets->get<Texture>(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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <set>
|
||||
@ -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<std::filesystem::path>& alternatives
|
||||
const std::vector<io::path>& alternatives
|
||||
);
|
||||
};
|
||||
|
||||
@ -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<AssetCfg>&
|
||||
) {
|
||||
auto actualFile = paths->find(filename + ".png").u8string();
|
||||
auto actualFile = paths->find(filename + ".png");
|
||||
try {
|
||||
std::shared_ptr<ImageData> image(
|
||||
imageio::read(fs::u8path(actualFile)).release()
|
||||
);
|
||||
std::shared_ptr<ImageData> 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<AssetCfg>&
|
||||
) {
|
||||
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<AtlasCfg>(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<std::string> 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<AssetCfg>&
|
||||
) {
|
||||
auto path = paths->find(file + ".vec3");
|
||||
if (fs::exists(path)) {
|
||||
auto bytes = files::read_bytes_buffer(path);
|
||||
auto modelVEC3 = std::make_shared<vec3::File>(vec3::load(path.u8string(), bytes));
|
||||
if (io::exists(path)) {
|
||||
auto bytes = io::read_bytes_buffer(path);
|
||||
auto modelVEC3 = std::make_shared<vec3::File>(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::Model>(model), name);
|
||||
@ -309,7 +307,7 @@ static void read_anim_file(
|
||||
const std::string& animFile,
|
||||
std::vector<std::pair<std::string, int>>& 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<std::pair<std::string, int>> 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;
|
||||
|
||||
@ -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));
|
||||
}
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
|
||||
#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<PCM> 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<PCM> 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<PCM> audio::load_PCM(const fs::path& file, bool headerOnly) {
|
||||
throw std::runtime_error("unsupported audio format");
|
||||
}
|
||||
|
||||
std::unique_ptr<Sound> audio::load_sound(const fs::path& file, bool keepPCM) {
|
||||
std::unique_ptr<Sound> audio::load_sound(const io::path& file, bool keepPCM) {
|
||||
std::shared_ptr<PCM> pcm(
|
||||
load_PCM(file, !keepPCM && backend->isDummy()).release()
|
||||
);
|
||||
@ -207,8 +208,8 @@ std::unique_ptr<Sound> audio::create_sound(
|
||||
return backend->createSound(std::move(pcm), keepPCM);
|
||||
}
|
||||
|
||||
std::unique_ptr<PCMStream> audio::open_PCM_stream(const fs::path& file) {
|
||||
std::string ext = file.extension().u8string();
|
||||
std::unique_ptr<PCMStream> 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<PCMStream> audio::open_PCM_stream(const fs::path& file) {
|
||||
}
|
||||
|
||||
std::unique_ptr<Stream> 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,
|
||||
|
||||
@ -1,14 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <glm/glm.hpp>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#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<PCM> load_PCM(const fs::path& file, bool headerOnly);
|
||||
std::unique_ptr<PCM> 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<Sound> load_sound(const fs::path& file, bool keepPCM);
|
||||
std::unique_ptr<Sound> 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<PCMStream> open_PCM_stream(const fs::path& file);
|
||||
std::unique_ptr<PCMStream> 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<Stream> open_stream(const fs::path& file, bool keepSource);
|
||||
std::unique_ptr<Stream> 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,
|
||||
|
||||
@ -5,13 +5,10 @@
|
||||
#include <stdexcept>
|
||||
#include <utility>
|
||||
|
||||
#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;
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#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
|
||||
);
|
||||
|
||||
@ -1,15 +1,12 @@
|
||||
#include "imageio.hpp"
|
||||
|
||||
#include <filesystem>
|
||||
#include <functional>
|
||||
#include <unordered_map>
|
||||
|
||||
#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<std::unique_ptr<ImageData>(const ubyte*, size_t)>;
|
||||
using image_writer = std::function<void(const std::string&, const ImageData*)>;
|
||||
@ -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<ImageData> imageio::read(const fs::path& filename) {
|
||||
auto found = readers.find(extensionOf(filename.u8string()));
|
||||
std::unique_ptr<ImageData> 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<ImageData>(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);
|
||||
}
|
||||
|
||||
@ -2,7 +2,8 @@
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <filesystem>
|
||||
|
||||
#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<ImageData> read(const std::filesystem::path& file);
|
||||
void write(const std::string& filename, const ImageData* image);
|
||||
std::unique_ptr<ImageData> read(const io::path& file);
|
||||
void write(const io::path& file, const ImageData* image);
|
||||
}
|
||||
|
||||
@ -80,7 +80,7 @@ public:
|
||||
return std::string({first});
|
||||
case '-':
|
||||
skip(1);
|
||||
if (peekNoJump() == '-') {
|
||||
if (hasNext() && peekNoJump() == '-') {
|
||||
skip(1);
|
||||
return "--";
|
||||
}
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
|
||||
#include <string>
|
||||
|
||||
#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<audio::PCM> 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<char> data;
|
||||
@ -166,10 +167,10 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_ptr<PCMStream> ogg::create_stream(const fs::path& file) {
|
||||
std::unique_ptr<PCMStream> 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<OggStream>(vf);
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
|
||||
#include "io/fwd.hpp"
|
||||
|
||||
namespace audio {
|
||||
struct PCM;
|
||||
@ -9,9 +11,9 @@ namespace audio {
|
||||
|
||||
namespace ogg {
|
||||
std::unique_ptr<audio::PCM> load_pcm(
|
||||
const std::filesystem::path& file, bool headerOnly
|
||||
const io::path& file, bool headerOnly
|
||||
);
|
||||
std::unique_ptr<audio::PCMStream> create_stream(
|
||||
const std::filesystem::path& file
|
||||
const io::path& file
|
||||
);
|
||||
}
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
#include <iostream>
|
||||
|
||||
#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<Texture> png::load_texture(const ubyte* bytes, size_t size) {
|
||||
}
|
||||
|
||||
std::unique_ptr<Texture> 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) {
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
#include <sstream>
|
||||
|
||||
#include "data/setting.hpp"
|
||||
#include "files/settings_io.hpp"
|
||||
#include "io/settings_io.hpp"
|
||||
#include "util/stringutil.hpp"
|
||||
#include "commons.hpp"
|
||||
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "io/io.hpp"
|
||||
#include "audio/audio.hpp"
|
||||
#include "debug/Logger.hpp"
|
||||
|
||||
@ -118,11 +119,11 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_ptr<audio::PCMStream> wav::create_stream(const fs::path& file) {
|
||||
std::ifstream in(file, std::ios::binary);
|
||||
std::unique_ptr<audio::PCMStream> 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<audio::PCMStream> wav::create_stream(const fs::path& file) {
|
||||
}
|
||||
|
||||
std::unique_ptr<audio::PCM> wav::load_pcm(
|
||||
const fs::path& file, bool headerOnly
|
||||
const io::path& file, bool headerOnly
|
||||
) {
|
||||
auto stream = wav::create_stream(file);
|
||||
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
|
||||
#include "io/fwd.hpp"
|
||||
|
||||
namespace audio {
|
||||
struct PCM;
|
||||
@ -9,9 +11,9 @@ namespace audio {
|
||||
|
||||
namespace wav {
|
||||
std::unique_ptr<audio::PCM> load_pcm(
|
||||
const std::filesystem::path& file, bool headerOnly
|
||||
const io::path& file, bool headerOnly
|
||||
);
|
||||
std::unique_ptr<audio::PCMStream> create_stream(
|
||||
const std::filesystem::path& file
|
||||
const io::path& file
|
||||
);
|
||||
}
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
#include <string>
|
||||
|
||||
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;
|
||||
|
||||
@ -34,12 +34,14 @@ Content::Content(
|
||||
UptrsMap<std::string, ContentPackRuntime> packs,
|
||||
UptrsMap<std::string, BlockMaterial> blockMaterials,
|
||||
UptrsMap<std::string, rigging::SkeletonConfig> 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)),
|
||||
|
||||
@ -201,6 +201,7 @@ class Content {
|
||||
UptrsMap<std::string, ContentPackRuntime> packs;
|
||||
UptrsMap<std::string, BlockMaterial> blockMaterials;
|
||||
UptrsMap<std::string, rigging::SkeletonConfig> skeletons;
|
||||
dv::value defaults = nullptr;
|
||||
public:
|
||||
ContentUnitDefs<Block> blocks;
|
||||
ContentUnitDefs<ItemDef> items;
|
||||
@ -219,7 +220,8 @@ public:
|
||||
UptrsMap<std::string, ContentPackRuntime> packs,
|
||||
UptrsMap<std::string, BlockMaterial> blockMaterials,
|
||||
UptrsMap<std::string, rigging::SkeletonConfig> skeletons,
|
||||
ResourceIndicesSet resourceIndices
|
||||
ResourceIndicesSet resourceIndices,
|
||||
dv::value defaults
|
||||
);
|
||||
~Content();
|
||||
|
||||
@ -231,6 +233,10 @@ public:
|
||||
return resourceIndices[static_cast<size_t>(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;
|
||||
|
||||
@ -84,7 +84,8 @@ std::unique_ptr<Content> 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
|
||||
|
||||
@ -73,6 +73,7 @@ public:
|
||||
ContentUnitBuilder<EntityDef> entities {allNames, ContentType::ENTITY};
|
||||
ContentUnitBuilder<GeneratorDef> generators {allNames, ContentType::GENERATOR};
|
||||
ResourceIndicesSet resourceIndices {};
|
||||
dv::value defaults = nullptr;
|
||||
|
||||
~ContentBuilder();
|
||||
|
||||
|
||||
@ -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<std::string>& 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<std::tuple<std::string, std::string>>& 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<std::tuple<std::string, std::string>> 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<std::string, std::string, std::string> create_unit_id(
|
||||
@ -566,21 +581,25 @@ static std::tuple<std::string, std::string, std::string> 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<std::pair<std::string, std::string>> 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<void(const fs::path&)> handler
|
||||
const io::path& dir, std::function<void(const io::path&)> 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<T>& 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()
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@ -1,14 +1,12 @@
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#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
|
||||
);
|
||||
|
||||
@ -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<std::string> 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<ContentPack>& packs
|
||||
const std::string& path, const io::path& folder, std::vector<ContentPack>& 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<std::string> ContentPack::worldPacksList(const fs::path& folder) {
|
||||
fs::path listfile = folder / fs::path("packs.list");
|
||||
if (!fs::is_regular_file(listfile)) {
|
||||
std::vector<std::string> 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)
|
||||
|
||||
@ -1,27 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#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<DependencyPack> 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<std::string> 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<ContentPack>& packs
|
||||
);
|
||||
|
||||
static std::vector<std::string> 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 "";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -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> ContentReport::create(
|
||||
const std::shared_ptr<WorldFiles>& worldFiles,
|
||||
const fs::path& filename,
|
||||
const io::path& filename,
|
||||
const Content* content
|
||||
) {
|
||||
auto worldInfo = worldFiles->readWorldInfo();
|
||||
@ -75,7 +75,7 @@ std::shared_ptr<ContentReport> 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"];
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
@ -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<ContentReport> create(
|
||||
const std::shared_ptr<WorldFiles>& worldFiles,
|
||||
const fs::path& filename,
|
||||
const io::path& filename,
|
||||
const Content* content
|
||||
);
|
||||
|
||||
|
||||
@ -2,12 +2,13 @@
|
||||
|
||||
#include <queue>
|
||||
#include <sstream>
|
||||
#include <algorithm>
|
||||
|
||||
#include "util/listutil.hpp"
|
||||
|
||||
PacksManager::PacksManager() = default;
|
||||
|
||||
void PacksManager::setSources(std::vector<std::pair<std::string, fs::path>> sources) {
|
||||
void PacksManager::setSources(std::vector<std::pair<std::string, io::path>> sources) {
|
||||
this->sources = std::move(sources);
|
||||
}
|
||||
|
||||
@ -42,7 +43,7 @@ std::vector<ContentPack> 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<std::string> PacksManager::assemble(
|
||||
std::queue<const ContentPack*> queue;
|
||||
std::queue<const ContentPack*> 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);
|
||||
}
|
||||
|
||||
@ -1,21 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "io/io.hpp"
|
||||
#include "ContentPack.hpp"
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
class PacksManager {
|
||||
std::unordered_map<std::string, ContentPack> packs;
|
||||
std::vector<std::pair<std::string, fs::path>> sources;
|
||||
std::vector<std::pair<std::string, io::path>> sources;
|
||||
public:
|
||||
PacksManager();
|
||||
|
||||
/// @brief Set content packs sources (search folders)
|
||||
void setSources(std::vector<std::pair<std::string, fs::path>> sources);
|
||||
void setSources(std::vector<std::pair<std::string, io::path>> sources);
|
||||
|
||||
/// @brief Scan sources and collect all found packs excluding duplication.
|
||||
/// Scanning order depends on sources order
|
||||
|
||||
@ -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<std::unique_ptr<VoxelStructure>> 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<std::unique_ptr<VoxelStructure>> 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<VoxelFragment>();
|
||||
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<std::unique_ptr<VoxelStructure>> 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) +
|
||||
|
||||
@ -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
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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<ImageData> load_icon(const fs::path& resdir) {
|
||||
static std::unique_ptr<ImageData> 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<std::string> 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<ResPaths>(resdir, resRoots);
|
||||
resPaths = std::make_unique<ResPaths>("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<PathsRoot> 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<ResPaths>(resdir, resRoots);
|
||||
resPaths = std::make_unique<ResPaths>("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> 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> level, int64_t localPlayer) {
|
||||
|
||||
@ -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 <filesystem>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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<double>(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<microseconds>(end - begin).count() / 1000);
|
||||
int64_t millis = targetDelta * 1000 -
|
||||
duration_cast<microseconds>(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> level) {
|
||||
if (level == nullptr) {
|
||||
controller->onWorldQuit();
|
||||
engine.getPaths().setCurrentWorldFolder(fs::path());
|
||||
engine.getPaths().setCurrentWorldFolder("");
|
||||
controller = nullptr;
|
||||
} else {
|
||||
controller = std::make_unique<LevelController>(
|
||||
|
||||
@ -1,340 +0,0 @@
|
||||
#include "engine_paths.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <filesystem>
|
||||
#include <sstream>
|
||||
#include <stack>
|
||||
#include "typedefs.hpp"
|
||||
#include "util/stringutil.hpp"
|
||||
#include <utility>
|
||||
|
||||
#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<std::string> 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<std::filesystem::path> EnginePaths::scanForWorlds() const {
|
||||
std::vector<std::filesystem::path> 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<ContentPack>* contentPacks) {
|
||||
this->contentPacks = contentPacks;
|
||||
}
|
||||
|
||||
std::tuple<std::string, std::string> 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<PathsRoot> 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<std::string> ResPaths::listdirRaw(const std::string& folderName) const {
|
||||
std::vector<std::string> 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<std::filesystem::path> ResPaths::listdir(
|
||||
const std::string& folderName
|
||||
) const {
|
||||
std::vector<std::filesystem::path> 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;
|
||||
}
|
||||
@ -1,87 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <stdexcept>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <tuple>
|
||||
|
||||
#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<ContentPack>* contentPacks);
|
||||
|
||||
std::vector<std::filesystem::path> scanForWorlds() const;
|
||||
|
||||
std::filesystem::path resolve(const std::string& path, bool throwErr = true) const;
|
||||
|
||||
static std::tuple<std::string, std::string> 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<std::filesystem::path> scriptFolder;
|
||||
std::vector<ContentPack>* contentPacks = nullptr;
|
||||
};
|
||||
|
||||
struct PathsRoot {
|
||||
std::string name;
|
||||
std::filesystem::path path;
|
||||
};
|
||||
|
||||
class ResPaths {
|
||||
public:
|
||||
ResPaths(std::filesystem::path mainRoot, std::vector<PathsRoot> roots);
|
||||
|
||||
std::filesystem::path find(const std::string& filename) const;
|
||||
std::string findRaw(const std::string& filename) const;
|
||||
std::vector<std::filesystem::path> listdir(const std::string& folder) const;
|
||||
std::vector<std::string> 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<PathsRoot> roots;
|
||||
};
|
||||
@ -1,199 +0,0 @@
|
||||
#include "files.hpp"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
|
||||
#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<ubyte> files::read_bytes_buffer(const fs::path& path) {
|
||||
size_t size;
|
||||
auto bytes = files::read_bytes(path, size);
|
||||
return util::Buffer<ubyte>(std::move(bytes), size);
|
||||
}
|
||||
|
||||
std::unique_ptr<ubyte[]> 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<ubyte[]>(length);
|
||||
input.read((char*)data.get(), length);
|
||||
input.close();
|
||||
return data;
|
||||
}
|
||||
|
||||
std::vector<ubyte> 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<ubyte> 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<std::string> 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<std::string> 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 <map>
|
||||
|
||||
#include "coders/json.hpp"
|
||||
#include "coders/toml.hpp"
|
||||
|
||||
using DecodeFunc = dv::value(*)(std::string_view, std::string_view);
|
||||
|
||||
static std::map<fs::path, DecodeFunc> 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());
|
||||
}
|
||||
}
|
||||
@ -1,80 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#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<ubyte> read_bytes_buffer(const fs::path&);
|
||||
std::unique_ptr<ubyte[]> read_bytes(const fs::path&, size_t& length);
|
||||
std::vector<ubyte> 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<std::string> 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);
|
||||
}
|
||||
@ -2,7 +2,7 @@
|
||||
|
||||
#include <utility>
|
||||
|
||||
#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> 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> UiDocument::read(
|
||||
}
|
||||
|
||||
std::shared_ptr<gui::UINode> 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();
|
||||
}
|
||||
|
||||
@ -4,10 +4,9 @@
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <filesystem>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
#include "io/fwd.hpp"
|
||||
|
||||
namespace gui {
|
||||
class UINode;
|
||||
@ -48,10 +47,10 @@ public:
|
||||
static std::unique_ptr<UiDocument> 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<gui::UINode> readElement(
|
||||
const fs::path& file, const std::string& fileName
|
||||
const io::path& file, const std::string& fileName
|
||||
);
|
||||
};
|
||||
|
||||
@ -70,7 +70,7 @@ std::shared_ptr<UINode> create_debug_panel(
|
||||
);
|
||||
|
||||
HudElement::HudElement(
|
||||
hud_element_mode mode,
|
||||
HudElementMode mode,
|
||||
UiDocument* document,
|
||||
std::shared_ptr<UINode> 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<UINode> HudElement::getNode() const {
|
||||
|
||||
std::shared_ptr<InventoryView> 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<Inventory>(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<Plotter>(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)
|
||||
"<image src='"+DEBUG_WORLDGEN_IMAGE+
|
||||
"' pos='0' size='256' gravity='top-right' margin='0,20,0,0'/>"
|
||||
);
|
||||
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<UiDocument>("core:inventory");
|
||||
inventoryView = std::dynamic_pointer_cast<InventoryView>(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<Inventory> Hud::openInventory(
|
||||
@ -401,7 +401,7 @@ std::shared_ptr<Inventory> 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<InventoryView>(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 {
|
||||
|
||||
@ -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<gui::UINode> node;
|
||||
|
||||
bool debug;
|
||||
bool removed = false;
|
||||
public:
|
||||
HudElement(hud_element_mode mode, UiDocument* document, std::shared_ptr<gui::UINode> node, bool debug);
|
||||
HudElement(HudElementMode mode, UiDocument* document, std::shared_ptr<gui::UINode> node, bool debug);
|
||||
|
||||
void update(bool pause, bool inventoryOpen, bool debug);
|
||||
|
||||
@ -57,7 +57,7 @@ public:
|
||||
std::shared_ptr<gui::UINode> getNode() const;
|
||||
|
||||
bool isInventoryBound() const {
|
||||
return mode == hud_element_mode::inventory_bound;
|
||||
return mode == HudElementMode::INVENTORY;
|
||||
}
|
||||
|
||||
void setRemoved() {
|
||||
|
||||
@ -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<ContentPack>& 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<ContentPack>& 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<ContentPack>& packs) {
|
||||
std::string fallback = langs::FALLBACK_DEFAULT;
|
||||
|
||||
@ -3,9 +3,10 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <filesystem>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "io/fwd.hpp"
|
||||
|
||||
struct ContentPack;
|
||||
|
||||
namespace langs {
|
||||
@ -44,17 +45,17 @@ namespace langs {
|
||||
extern std::unordered_map<std::string, LocaleInfo> 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<ContentPack>& 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<ContentPack>& 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<ContentPack>& packs);
|
||||
}
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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<Batch2D>(1024)) {
|
||||
container = std::make_shared<Container>(glm::vec2(1000));
|
||||
GUI::GUI()
|
||||
: batch2D(std::make_unique<Batch2D>(1024)),
|
||||
container(std::make_shared<Container>(glm::vec2(1000))) {
|
||||
container->setId("root");
|
||||
uicamera = std::make_unique<Camera>(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<gui::Panel>(page.panel)) {
|
||||
panel->cropToContent();
|
||||
}
|
||||
}
|
||||
menu->setPos((wsize - menu->getSize()) / 2.0f);
|
||||
uicamera->setFov(wsize.y);
|
||||
|
||||
|
||||
@ -13,6 +13,7 @@ InputBindBox::InputBindBox(Binding& binding, glm::vec4 padding)
|
||||
label(std::make_shared<Label>(L"")) {
|
||||
add(label);
|
||||
setScrollable(false);
|
||||
hoverColor = glm::vec4(0.05f, 0.1f, 0.2f, 0.75f);
|
||||
}
|
||||
|
||||
void InputBindBox::drawBackground(const DrawContext& pctx, const Assets&) {
|
||||
@ -25,8 +26,10 @@ void InputBindBox::drawBackground(const DrawContext& pctx, const Assets&) {
|
||||
}
|
||||
|
||||
void InputBindBox::clicked(GUI*, mousecode button) {
|
||||
binding.reset(button);
|
||||
defocus();
|
||||
if (isFocused()) {
|
||||
binding.reset(button);
|
||||
defocus();
|
||||
}
|
||||
}
|
||||
|
||||
void InputBindBox::keyPressed(keycode key) {
|
||||
|
||||
@ -7,8 +7,7 @@ namespace gui {
|
||||
|
||||
class InputBindBox : public Panel {
|
||||
protected:
|
||||
glm::vec4 hoverColor {0.05f, 0.1f, 0.2f, 0.75f};
|
||||
glm::vec4 focusedColor {0.0f, 0.0f, 0.0f, 1.0f};
|
||||
glm::vec4 focusedColor {0.1f, 0.15f, 0.35f, 0.75f};
|
||||
std::shared_ptr<Label> label;
|
||||
Binding& binding;
|
||||
public:
|
||||
|
||||
@ -8,7 +8,6 @@
|
||||
#include "items/Inventories.hpp"
|
||||
#include "items/Inventory.hpp"
|
||||
#include "items/ItemDef.hpp"
|
||||
#include "items/ItemStack.hpp"
|
||||
#include "logic/scripting/scripting.hpp"
|
||||
#include "maths/voxmaths.hpp"
|
||||
#include "objects/Player.hpp"
|
||||
@ -115,26 +114,73 @@ SlotView::SlotView(
|
||||
setTooltipDelay(0.0f);
|
||||
}
|
||||
|
||||
void SlotView::refreshTooltip(const ItemStack& stack, const ItemDef& item) {
|
||||
itemid_t itemid = stack.getItemId();
|
||||
if (itemid == cache.stack.getItemId()) {
|
||||
return;
|
||||
}
|
||||
if (itemid) {
|
||||
tooltip = util::pascal_case(
|
||||
langs::get(util::str2wstr_utf8(item.caption))
|
||||
);
|
||||
} else {
|
||||
tooltip.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void SlotView::drawItemIcon(
|
||||
Batch2D& batch,
|
||||
const ItemStack& stack,
|
||||
const ItemDef& item,
|
||||
const Assets& assets,
|
||||
const glm::vec4& tint,
|
||||
const glm::vec2& pos
|
||||
) {
|
||||
const int SLOT_SIZE = InventoryView::SLOT_SIZE;
|
||||
const auto& previews = assets.require<Atlas>("block-previews");
|
||||
batch.setColor(glm::vec4(1.0f));
|
||||
switch (item.iconType) {
|
||||
case ItemIconType::NONE:
|
||||
break;
|
||||
case ItemIconType::BLOCK: {
|
||||
const Block& block = content->blocks.require(item.icon);
|
||||
batch.texture(previews.getTexture());
|
||||
|
||||
UVRegion region = previews.get(block.name);
|
||||
batch.rect(
|
||||
pos.x, pos.y, SLOT_SIZE, SLOT_SIZE,
|
||||
0, 0, 0, region, false, true, tint
|
||||
);
|
||||
break;
|
||||
}
|
||||
case ItemIconType::SPRITE: {
|
||||
auto textureRegion =
|
||||
util::get_texture_region(assets, item.icon, "blocks:notfound");
|
||||
|
||||
batch.texture(textureRegion.texture);
|
||||
batch.rect(
|
||||
pos.x, pos.y, SLOT_SIZE, SLOT_SIZE,
|
||||
0, 0, 0, textureRegion.region, false, true, tint
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SlotView::draw(const DrawContext& pctx, const Assets& assets) {
|
||||
if (bound == nullptr) {
|
||||
return;
|
||||
}
|
||||
itemid_t itemid = bound->getItemId();
|
||||
if (itemid != prevItem) {
|
||||
if (itemid) {
|
||||
auto& def = content->getIndices()->items.require(itemid);
|
||||
tooltip = util::pascal_case(
|
||||
langs::get(util::str2wstr_utf8(def.caption))
|
||||
);
|
||||
} else {
|
||||
tooltip.clear();
|
||||
}
|
||||
}
|
||||
prevItem = itemid;
|
||||
|
||||
const int slotSize = InventoryView::SLOT_SIZE;
|
||||
|
||||
const auto& indices = *content->getIndices();
|
||||
const ItemStack& stack = *bound;
|
||||
const ItemDef& item = indices.items.require(stack.getItemId());
|
||||
|
||||
if (cache.stack.getCount() != stack.getCount()) {
|
||||
cache.countStr = std::to_wstring(stack.getCount());
|
||||
}
|
||||
refreshTooltip(stack, item);
|
||||
cache.stack.set(ItemStack(stack.getItemId(), stack.getCount()));
|
||||
|
||||
glm::vec4 tint(1, 1, 1, isEnabled() ? 1 : 0.5f);
|
||||
glm::vec2 pos = calcPos();
|
||||
glm::vec4 color = getColor();
|
||||
@ -144,59 +190,84 @@ void SlotView::draw(const DrawContext& pctx, const Assets& assets) {
|
||||
color = glm::vec4(1, 1, 1, 0.2f);
|
||||
}
|
||||
|
||||
auto batch = pctx.getBatch2D();
|
||||
batch->setColor(color);
|
||||
auto& batch = *pctx.getBatch2D();
|
||||
|
||||
if (color.a > 0.0) {
|
||||
batch->texture(nullptr);
|
||||
batch.setColor(color);
|
||||
batch.texture(nullptr);
|
||||
|
||||
const int size = InventoryView::SLOT_SIZE;
|
||||
if (highlighted) {
|
||||
batch->rect(pos.x-4, pos.y-4, slotSize+8, slotSize+8);
|
||||
batch.rect(pos.x - 4, pos.y - 4, size + 8, size + 8);
|
||||
} else {
|
||||
batch->rect(pos.x, pos.y, slotSize, slotSize);
|
||||
}
|
||||
}
|
||||
|
||||
batch->setColor(glm::vec4(1.0f));
|
||||
|
||||
auto previews = assets.get<Atlas>("block-previews");
|
||||
auto indices = content->getIndices();
|
||||
|
||||
auto& item = indices->items.require(stack.getItemId());
|
||||
switch (item.iconType) {
|
||||
case ItemIconType::NONE:
|
||||
break;
|
||||
case ItemIconType::BLOCK: {
|
||||
const Block& cblock = content->blocks.require(item.icon);
|
||||
batch->texture(previews->getTexture());
|
||||
|
||||
UVRegion region = previews->get(cblock.name);
|
||||
batch->rect(
|
||||
pos.x, pos.y, slotSize, slotSize,
|
||||
0, 0, 0, region, false, true, tint);
|
||||
break;
|
||||
}
|
||||
case ItemIconType::SPRITE: {
|
||||
auto textureRegion =
|
||||
util::get_texture_region(assets, item.icon, "blocks:notfound");
|
||||
|
||||
batch->texture(textureRegion.texture);
|
||||
batch->rect(
|
||||
pos.x, pos.y, slotSize, slotSize,
|
||||
0, 0, 0, textureRegion.region, false, true, tint);
|
||||
break;
|
||||
batch.rect(pos.x, pos.y, size, size);
|
||||
}
|
||||
}
|
||||
|
||||
drawItemIcon(batch, stack, item, assets, tint, pos);
|
||||
|
||||
if (stack.getCount() > 1 || stack.getFields() != nullptr) {
|
||||
const auto& font = assets.require<Font>("normal");
|
||||
drawItemInfo(batch, stack, item, font, pos);
|
||||
}
|
||||
}
|
||||
|
||||
static void draw_shaded_text(
|
||||
Batch2D& batch, const Font& font, const std::wstring& text, int x, int y
|
||||
) {
|
||||
batch.setColor({0, 0, 0, 1.0f});
|
||||
font.draw(batch, text, x + 1, y + 1, nullptr, 0);
|
||||
batch.resetColor();
|
||||
font.draw(batch, text, x, y, nullptr, 0);
|
||||
}
|
||||
|
||||
void SlotView::drawItemInfo(
|
||||
Batch2D& batch,
|
||||
const ItemStack& stack,
|
||||
const ItemDef& item,
|
||||
const Font& font,
|
||||
const glm::vec2& pos
|
||||
) {
|
||||
const int SLOT_SIZE = InventoryView::SLOT_SIZE;
|
||||
if (stack.getCount() > 1) {
|
||||
auto font = assets.get<Font>("normal");
|
||||
std::wstring text = std::to_wstring(stack.getCount());
|
||||
const auto& countStr = cache.countStr;
|
||||
int x = pos.x + SLOT_SIZE - countStr.length() * 8;
|
||||
int y = pos.y + SLOT_SIZE - 16;
|
||||
draw_shaded_text(batch, font, countStr, x, y);
|
||||
}
|
||||
|
||||
int x = pos.x+slotSize-text.length()*8;
|
||||
int y = pos.y+slotSize-16;
|
||||
|
||||
batch->setColor({0, 0, 0, 1.0f});
|
||||
font->draw(*batch, text, x+1, y+1, nullptr, 0);
|
||||
batch->setColor(glm::vec4(1.0f));
|
||||
font->draw(*batch, text, x, y, nullptr, 0);
|
||||
auto usesPtr = stack.getField("uses");
|
||||
if (usesPtr == nullptr || !usesPtr->isInteger()) {
|
||||
return;
|
||||
}
|
||||
int16_t uses = usesPtr->asInteger();
|
||||
if (uses < 0) {
|
||||
return;
|
||||
}
|
||||
switch (item.usesDisplay) {
|
||||
case ItemUsesDisplay::NONE:
|
||||
break;
|
||||
case ItemUsesDisplay::RELATION:
|
||||
draw_shaded_text(
|
||||
batch, font, std::to_wstring(item.uses), pos.x - 3, pos.y + 9
|
||||
);
|
||||
[[fallthrough]];
|
||||
case ItemUsesDisplay::NUMBER:
|
||||
draw_shaded_text(
|
||||
batch, font, std::to_wstring(uses), pos.x - 3, pos.y - 3
|
||||
);
|
||||
break;
|
||||
case ItemUsesDisplay::VBAR: {
|
||||
batch.untexture();
|
||||
batch.setColor({0, 0, 0, 0.75f});
|
||||
batch.rect(pos.x - 2, pos.y - 2, 6, SLOT_SIZE + 4);
|
||||
float t = static_cast<float>(uses) / item.uses;
|
||||
|
||||
int height = SLOT_SIZE * t;
|
||||
batch.setColor({(1.0f - t * 0.8f), 0.4f, t * 0.8f + 0.2f, 1.0f});
|
||||
batch.rect(pos.x, pos.y + SLOT_SIZE - height, 2, height);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -219,7 +290,7 @@ void SlotView::performLeftClick(ItemStack& stack, ItemStack& grabbed) {
|
||||
return;
|
||||
}
|
||||
if (!layout.itemSource && stack.accepts(grabbed) && layout.placing) {
|
||||
stack.move(grabbed, content->getIndices());
|
||||
stack.move(grabbed, *content->getIndices());
|
||||
} else {
|
||||
if (layout.itemSource) {
|
||||
if (grabbed.isEmpty()) {
|
||||
@ -249,10 +320,11 @@ void SlotView::performRightClick(ItemStack& stack, ItemStack& grabbed) {
|
||||
return;
|
||||
if (grabbed.isEmpty()) {
|
||||
if (!stack.isEmpty() && layout.taking) {
|
||||
grabbed.set(stack);
|
||||
grabbed.set(std::move(stack));
|
||||
int halfremain = stack.getCount() / 2;
|
||||
grabbed.setCount(stack.getCount() - halfremain);
|
||||
stack.setCount(halfremain);
|
||||
// reset all data in the origin slot
|
||||
stack = ItemStack(stack.getItemId(), halfremain);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@ -261,9 +333,14 @@ void SlotView::performRightClick(ItemStack& stack, ItemStack& grabbed) {
|
||||
return;
|
||||
}
|
||||
if (stack.isEmpty()) {
|
||||
stack.set(grabbed);
|
||||
itemcount_t count = grabbed.getCount();
|
||||
stack.set(std::move(grabbed));
|
||||
stack.setCount(1);
|
||||
grabbed.setCount(grabbed.getCount() - 1);
|
||||
if (count == 1) {
|
||||
grabbed = {};
|
||||
} else {
|
||||
grabbed = ItemStack(stack.getItemId(), count - 1);
|
||||
}
|
||||
} else if (stack.accepts(grabbed) && stack.getCount() < stackDef.stackSize) {
|
||||
stack.setCount(stack.getCount() + 1);
|
||||
grabbed.setCount(grabbed.getCount() - 1);
|
||||
|
||||
@ -4,15 +4,18 @@
|
||||
#include "Container.hpp"
|
||||
#include "typedefs.hpp"
|
||||
#include "constants.hpp"
|
||||
#include "items/ItemStack.hpp"
|
||||
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
class Font;
|
||||
class Assets;
|
||||
class ItemDef;
|
||||
class Batch2D;
|
||||
class DrawContext;
|
||||
class Content;
|
||||
class ItemStack;
|
||||
class ContentIndices;
|
||||
class LevelFrontend;
|
||||
class Inventory;
|
||||
@ -49,6 +52,11 @@ namespace gui {
|
||||
};
|
||||
|
||||
class SlotView : public gui::UINode {
|
||||
struct {
|
||||
ItemStack stack {};
|
||||
std::wstring countStr;
|
||||
} cache;
|
||||
|
||||
const Content* content = nullptr;
|
||||
SlotLayout layout;
|
||||
bool highlighted = false;
|
||||
@ -56,11 +64,27 @@ namespace gui {
|
||||
int64_t inventoryid = 0;
|
||||
ItemStack* bound = nullptr;
|
||||
|
||||
std::wstring tooltip;
|
||||
itemid_t prevItem = 0;
|
||||
|
||||
void performLeftClick(ItemStack& stack, ItemStack& grabbed);
|
||||
void performRightClick(ItemStack& stack, ItemStack& grabbed);
|
||||
|
||||
void drawItemIcon(
|
||||
Batch2D& batch,
|
||||
const ItemStack& stack,
|
||||
const ItemDef& item,
|
||||
const Assets& assets,
|
||||
const glm::vec4& tint,
|
||||
const glm::vec2& pos
|
||||
);
|
||||
|
||||
void drawItemInfo(
|
||||
Batch2D& batch,
|
||||
const ItemStack& stack,
|
||||
const ItemDef& item,
|
||||
const Font& font,
|
||||
const glm::vec2& pos
|
||||
);
|
||||
|
||||
void refreshTooltip(const ItemStack& stack, const ItemDef& item);
|
||||
public:
|
||||
SlotView(SlotLayout layout);
|
||||
|
||||
|
||||
@ -21,6 +21,20 @@ void LabelCache::prepare(Font* font, size_t wrapWidth) {
|
||||
}
|
||||
}
|
||||
|
||||
size_t LabelCache::getTextLineOffset(size_t line) const {
|
||||
line = std::min(lines.size()-1, line);
|
||||
return lines.at(line).offset;
|
||||
}
|
||||
|
||||
uint LabelCache::getLineByTextIndex(size_t index) const {
|
||||
for (size_t i = 0; i < lines.size(); i++) {
|
||||
if (lines[i].offset > index) {
|
||||
return i-1;
|
||||
}
|
||||
}
|
||||
return lines.size()-1;
|
||||
}
|
||||
|
||||
void LabelCache::update(const std::wstring& text, bool multiline, bool wrap) {
|
||||
resetFlag = false;
|
||||
lines.clear();
|
||||
@ -49,7 +63,7 @@ void LabelCache::update(const std::wstring& text, bool multiline, bool wrap) {
|
||||
}
|
||||
|
||||
Label::Label(const std::string& text, std::string fontName)
|
||||
: UINode(glm::vec2(text.length() * 8, 15)),
|
||||
: UINode(glm::vec2(text.length() * 8, 16)),
|
||||
text(util::str2wstr_utf8(text)),
|
||||
fontName(std::move(fontName))
|
||||
{
|
||||
@ -59,7 +73,7 @@ Label::Label(const std::string& text, std::string fontName)
|
||||
|
||||
|
||||
Label::Label(const std::wstring& text, std::string fontName)
|
||||
: UINode(glm::vec2(text.length() * 8, 15)),
|
||||
: UINode(glm::vec2(text.length() * 8, 16)),
|
||||
text(text),
|
||||
fontName(std::move(fontName))
|
||||
{
|
||||
@ -131,8 +145,7 @@ int Label::getTextYOffset() const {
|
||||
}
|
||||
|
||||
size_t Label::getTextLineOffset(size_t line) const {
|
||||
line = std::min(cache.lines.size()-1, line);
|
||||
return cache.lines.at(line).offset;
|
||||
return cache.getTextLineOffset(line);
|
||||
}
|
||||
|
||||
bool Label::isFakeLine(size_t line) const {
|
||||
@ -152,12 +165,7 @@ uint Label::getLineByYOffset(int offset) const {
|
||||
}
|
||||
|
||||
uint Label::getLineByTextIndex(size_t index) const {
|
||||
for (size_t i = 0; i < cache.lines.size(); i++) {
|
||||
if (cache.lines[i].offset > index) {
|
||||
return i-1;
|
||||
}
|
||||
}
|
||||
return cache.lines.size()-1;
|
||||
return cache.getLineByTextIndex(index);
|
||||
}
|
||||
|
||||
uint Label::getLinesNumber() const {
|
||||
|
||||
@ -20,6 +20,9 @@ namespace gui {
|
||||
|
||||
void prepare(Font* font, size_t wrapWidth);
|
||||
void update(const std::wstring& text, bool multiline, bool wrap);
|
||||
|
||||
size_t getTextLineOffset(size_t line) const;
|
||||
uint getLineByTextIndex(size_t index) const;
|
||||
};
|
||||
|
||||
class Label : public UINode {
|
||||
|
||||
@ -65,9 +65,10 @@ void Menu::setPage(Page page, bool history) {
|
||||
setSize(current.panel->getSize());
|
||||
}
|
||||
|
||||
void Menu::back() {
|
||||
if (pageStack.empty())
|
||||
return;
|
||||
bool Menu::back() {
|
||||
if (pageStack.empty()) {
|
||||
return false;
|
||||
}
|
||||
Page page = pageStack.top();
|
||||
pageStack.pop();
|
||||
|
||||
@ -77,6 +78,7 @@ void Menu::back() {
|
||||
}
|
||||
|
||||
setPage(page, false);
|
||||
return true;
|
||||
}
|
||||
|
||||
void Menu::setPageLoader(PageLoaderFunc loader) {
|
||||
|
||||
@ -54,7 +54,7 @@ namespace gui {
|
||||
PageLoaderFunc getPageLoader();
|
||||
|
||||
/// @brief Set page to previous saved in history
|
||||
void back();
|
||||
bool back();
|
||||
|
||||
/// @brief Clear pages history
|
||||
void clearHistory();
|
||||
|
||||
@ -70,9 +70,6 @@ void Panel::remove(const std::shared_ptr<UINode> &node) {
|
||||
|
||||
void Panel::refresh() {
|
||||
UINode::refresh();
|
||||
std::stable_sort(nodes.begin(), nodes.end(), [](auto a, auto b) {
|
||||
return a->getZIndex() < b->getZIndex();
|
||||
});
|
||||
|
||||
float x = padding.x;
|
||||
float y = padding.y;
|
||||
@ -80,20 +77,21 @@ void Panel::refresh() {
|
||||
if (orientation == Orientation::vertical) {
|
||||
float maxw = size.x;
|
||||
for (auto& node : nodes) {
|
||||
glm::vec2 nodesize = node->getSize();
|
||||
const glm::vec4 margin = node->getMargin();
|
||||
y += margin.y;
|
||||
|
||||
float ex = x + margin.x;
|
||||
node->setPos(glm::vec2(ex, y));
|
||||
y += nodesize.y + margin.w + interval;
|
||||
|
||||
|
||||
float width = size.x - padding.x - padding.z - margin.x - margin.z;
|
||||
if (node->isResizing()) {
|
||||
node->setSize(glm::vec2(width, nodesize.y));
|
||||
node->setMaxSize({width, node->getMaxSize().y});
|
||||
node->setSize(glm::vec2(width, node->getSize().y));
|
||||
}
|
||||
node->refresh();
|
||||
maxw = fmax(maxw, ex+node->getSize().x+margin.z+padding.z);
|
||||
glm::vec2 nodeSize = node->getSize();
|
||||
y += nodeSize.y + margin.w + interval;
|
||||
maxw = fmax(maxw, ex+nodeSize.x+margin.z+padding.z);
|
||||
}
|
||||
actualLength = y + padding.w;
|
||||
} else {
|
||||
|
||||
@ -57,6 +57,8 @@ void TextBox::draw(const DrawContext& pctx, const Assets& assets) {
|
||||
if (!isFocused()) {
|
||||
return;
|
||||
}
|
||||
const auto& labelText = getText();
|
||||
|
||||
glm::vec2 pos = calcPos();
|
||||
glm::vec2 size = getSize();
|
||||
|
||||
@ -70,8 +72,8 @@ void TextBox::draw(const DrawContext& pctx, const Assets& assets) {
|
||||
batch->texture(nullptr);
|
||||
batch->setColor(glm::vec4(1.0f));
|
||||
if (editable && int((Window::time() - caretLastMove) * 2) % 2 == 0) {
|
||||
uint line = label->getLineByTextIndex(caret);
|
||||
uint lcaret = caret - label->getTextLineOffset(line);
|
||||
uint line = rawTextCache.getLineByTextIndex(caret);
|
||||
uint lcaret = caret - rawTextCache.getTextLineOffset(line);
|
||||
int width = font->calcWidth(input, lcaret);
|
||||
batch->rect(
|
||||
lcoord.x + width,
|
||||
@ -89,10 +91,10 @@ void TextBox::draw(const DrawContext& pctx, const Assets& assets) {
|
||||
|
||||
batch->setColor(glm::vec4(0.8f, 0.9f, 1.0f, 0.25f));
|
||||
int start = font->calcWidth(
|
||||
input, selectionStart - label->getTextLineOffset(startLine)
|
||||
labelText, selectionStart - label->getTextLineOffset(startLine)
|
||||
);
|
||||
int end = font->calcWidth(
|
||||
input, selectionEnd - label->getTextLineOffset(endLine)
|
||||
labelText, selectionEnd - label->getTextLineOffset(endLine)
|
||||
);
|
||||
int lineY = label->getLineYOffset(startLine);
|
||||
|
||||
@ -192,11 +194,14 @@ void TextBox::drawBackground(const DrawContext& pctx, const Assets&) {
|
||||
}
|
||||
|
||||
void TextBox::refreshLabel() {
|
||||
rawTextCache.prepare(font, static_cast<size_t>(getSize().x));
|
||||
rawTextCache.update(input, multiline, false);
|
||||
|
||||
label->setColor(textColor * glm::vec4(input.empty() ? 0.5f : 1.0f));
|
||||
|
||||
const auto& displayText = input.empty() && !hint.empty() ? hint : getText();
|
||||
if (markup == "md") {
|
||||
auto [processedText, styles] = markdown::process(displayText, !focused);
|
||||
auto [processedText, styles] = markdown::process(displayText, !focused || !editable);
|
||||
label->setText(std::move(processedText));
|
||||
label->setStyles(std::move(styles));
|
||||
} else {
|
||||
@ -293,6 +298,7 @@ bool TextBox::eraseSelected() {
|
||||
}
|
||||
erase(selectionStart, selectionEnd-selectionStart);
|
||||
resetSelection();
|
||||
onInput();
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -312,7 +318,7 @@ size_t TextBox::getLineLength(uint line) const {
|
||||
size_t position = label->getTextLineOffset(line);
|
||||
size_t lineLength = label->getTextLineOffset(line+1)-position;
|
||||
if (lineLength == 0) {
|
||||
lineLength = input.length() - position + 1;
|
||||
lineLength = label->getText().length() - position + 1;
|
||||
}
|
||||
return lineLength;
|
||||
}
|
||||
@ -324,8 +330,8 @@ size_t TextBox::getSelectionLength() const {
|
||||
/// @brief Set scroll offset
|
||||
/// @param x scroll offset
|
||||
void TextBox::setTextOffset(uint x) {
|
||||
label->setPos(glm::vec2(textInitX - int(x), label->getPos().y));
|
||||
textOffset = x;
|
||||
refresh();
|
||||
}
|
||||
|
||||
void TextBox::typed(unsigned int codepoint) {
|
||||
@ -402,7 +408,9 @@ void TextBox::refresh() {
|
||||
Container::refresh();
|
||||
label->setSize(size-glm::vec2(padding.z+padding.x, padding.w+padding.y));
|
||||
label->setPos(glm::vec2(
|
||||
padding.x + LINE_NUMBERS_PANE_WIDTH * showLineNumbers, padding.y
|
||||
padding.x + LINE_NUMBERS_PANE_WIDTH * showLineNumbers + textInitX -
|
||||
static_cast<int>(textOffset),
|
||||
padding.y
|
||||
));
|
||||
}
|
||||
|
||||
@ -420,15 +428,16 @@ size_t TextBox::normalizeIndex(int index) {
|
||||
int TextBox::calcIndexAt(int x, int y) const {
|
||||
if (font == nullptr)
|
||||
return 0;
|
||||
const auto& labelText = label->getText();
|
||||
glm::vec2 lcoord = label->calcPos();
|
||||
uint line = label->getLineByYOffset(y-lcoord.y);
|
||||
line = std::min(line, label->getLinesNumber()-1);
|
||||
size_t lineLength = getLineLength(line);
|
||||
uint offset = 0;
|
||||
while (lcoord.x + font->calcWidth(input, offset) < x && offset < lineLength-1) {
|
||||
while (lcoord.x + font->calcWidth(labelText, offset) < x && offset < lineLength-1) {
|
||||
offset++;
|
||||
}
|
||||
return std::min(offset+label->getTextLineOffset(line), input.length());
|
||||
return std::min(offset+label->getTextLineOffset(line), labelText.length());
|
||||
}
|
||||
|
||||
static inline std::wstring get_alphabet(wchar_t c) {
|
||||
@ -442,21 +451,22 @@ static inline std::wstring get_alphabet(wchar_t c) {
|
||||
}
|
||||
|
||||
void TextBox::tokenSelectAt(int index) {
|
||||
if (input.empty()) {
|
||||
const auto& actualText = label->getText();
|
||||
if (actualText.empty()) {
|
||||
return;
|
||||
}
|
||||
int left = index;
|
||||
int right = index;
|
||||
|
||||
std::wstring alphabet = get_alphabet(input.at(index));
|
||||
std::wstring alphabet = get_alphabet(actualText.at(index));
|
||||
while (left >= 0) {
|
||||
if (alphabet.find(input.at(left)) == std::wstring::npos) {
|
||||
if (alphabet.find(actualText.at(left)) == std::wstring::npos) {
|
||||
break;
|
||||
}
|
||||
left--;
|
||||
}
|
||||
while (static_cast<size_t>(right) < input.length()) {
|
||||
if (alphabet.find(input.at(right)) == std::wstring::npos) {
|
||||
while (static_cast<size_t>(right) < actualText.length()) {
|
||||
if (alphabet.find(actualText.at(right)) == std::wstring::npos) {
|
||||
break;
|
||||
}
|
||||
right++;
|
||||
@ -799,7 +809,8 @@ void TextBox::setHint(const std::wstring& text) {
|
||||
}
|
||||
|
||||
std::wstring TextBox::getSelection() const {
|
||||
return input.substr(selectionStart, selectionEnd-selectionStart);
|
||||
const auto& text = label->getText();
|
||||
return text.substr(selectionStart, selectionEnd-selectionStart);
|
||||
}
|
||||
|
||||
size_t TextBox::getCaret() const {
|
||||
@ -807,26 +818,34 @@ size_t TextBox::getCaret() const {
|
||||
}
|
||||
|
||||
void TextBox::setCaret(size_t position) {
|
||||
this->caret = std::min(static_cast<size_t>(position), input.length());
|
||||
const auto& labelText = label->getText();
|
||||
caret = std::min(static_cast<size_t>(position), input.length());
|
||||
if (font == nullptr) {
|
||||
return;
|
||||
}
|
||||
caretLastMove = Window::time();
|
||||
int width = label->getSize().x;
|
||||
uint line = label->getLineByTextIndex(caret);
|
||||
|
||||
rawTextCache.prepare(font, width);
|
||||
rawTextCache.update(input, multiline, label->isTextWrapping());
|
||||
|
||||
caretLastMove = Window::time();
|
||||
|
||||
uint line = rawTextCache.getLineByTextIndex(caret);
|
||||
int offset = label->getLineYOffset(line) + getContentOffset().y;
|
||||
uint lineHeight = font->getLineHeight()*label->getLineInterval();
|
||||
if (scrollStep == 0) {
|
||||
scrollStep = lineHeight;
|
||||
}
|
||||
if (offset < 0) {
|
||||
scrolled(-glm::floor(offset/static_cast<double>(scrollStep)+0.5f));
|
||||
scrolled(-glm::floor(offset / static_cast<double>(scrollStep)+0.5f));
|
||||
} else if (offset >= getSize().y) {
|
||||
offset -= getSize().y;
|
||||
scrolled(-glm::ceil(offset/static_cast<double>(scrollStep)+0.5f));
|
||||
scrolled(-glm::ceil(offset / static_cast<double>(scrollStep)+0.5f));
|
||||
}
|
||||
uint lcaret = caret - label->getTextLineOffset(line);
|
||||
int realoffset = font->calcWidth(input, lcaret)-int(textOffset) - padding.x;
|
||||
int lcaret = caret - rawTextCache.getTextLineOffset(line);
|
||||
int realoffset =
|
||||
font->calcWidth(labelText, lcaret) - static_cast<int>(textOffset) + 2;
|
||||
|
||||
if (realoffset-width > 0) {
|
||||
setTextOffset(textOffset + realoffset-width);
|
||||
} else if (realoffset < 0) {
|
||||
|
||||
@ -6,9 +6,8 @@
|
||||
class Font;
|
||||
|
||||
namespace gui {
|
||||
class Label;
|
||||
|
||||
class TextBox : public Container {
|
||||
LabelCache rawTextCache;
|
||||
protected:
|
||||
glm::vec4 focusedColor {0.0f, 0.0f, 0.0f, 1.0f};
|
||||
glm::vec4 invalidColor {0.1f, 0.05f, 0.03f, 1.0f};
|
||||
@ -43,11 +42,12 @@ namespace gui {
|
||||
/// @brief Actual local (line) position of the caret on vertical move
|
||||
size_t maxLocalCaret = 0;
|
||||
size_t textOffset = 0;
|
||||
int textInitX;
|
||||
int textInitX = 0;
|
||||
/// @brief Last time of the caret was moved (used for blink animation)
|
||||
double caretLastMove = 0.0;
|
||||
Font* font = nullptr;
|
||||
|
||||
// Note: selection does not include markup
|
||||
size_t selectionStart = 0;
|
||||
size_t selectionEnd = 0;
|
||||
size_t selectionOrigin = 0;
|
||||
|
||||
@ -195,7 +195,8 @@ glm::vec2 UINode::getSize() const {
|
||||
|
||||
void UINode::setSize(glm::vec2 size) {
|
||||
this->size = glm::vec2(
|
||||
glm::max(minSize.x, size.x), glm::max(minSize.y, size.y)
|
||||
glm::max(minSize.x, glm::min(maxSize.x, size.x)),
|
||||
glm::max(minSize.y, glm::min(maxSize.y, size.y))
|
||||
);
|
||||
}
|
||||
|
||||
@ -208,6 +209,15 @@ void UINode::setMinSize(glm::vec2 minSize) {
|
||||
setSize(getSize());
|
||||
}
|
||||
|
||||
glm::vec2 UINode::getMaxSize() const {
|
||||
return maxSize;
|
||||
}
|
||||
|
||||
void UINode::setMaxSize(glm::vec2 maxSize) {
|
||||
this->maxSize = maxSize;
|
||||
setSize(getSize());
|
||||
}
|
||||
|
||||
void UINode::setColor(glm::vec4 color) {
|
||||
this->color = color;
|
||||
this->hoverColor = color;
|
||||
|
||||
@ -75,6 +75,8 @@ namespace gui {
|
||||
glm::vec2 size;
|
||||
/// @brief minimal element size
|
||||
glm::vec2 minSize {1.0f};
|
||||
/// @brief maximal element size
|
||||
glm::vec2 maxSize {1e6f};
|
||||
/// @brief element primary color (background-color or text-color if label)
|
||||
glm::vec4 color {1.0f};
|
||||
/// @brief element color when mouse is over it
|
||||
@ -224,6 +226,8 @@ namespace gui {
|
||||
virtual void setSize(glm::vec2 size);
|
||||
virtual glm::vec2 getMinSize() const;
|
||||
virtual void setMinSize(glm::vec2 size);
|
||||
virtual glm::vec2 getMaxSize() const;
|
||||
virtual void setMaxSize(glm::vec2 size);
|
||||
/// @brief Called in containers when new element added
|
||||
virtual void refresh() {};
|
||||
virtual void fullRefresh() {
|
||||
|
||||
@ -31,7 +31,17 @@ void guiutil::alert(
|
||||
const std::wstring& text,
|
||||
const runnable& on_hidden
|
||||
) {
|
||||
auto panel = std::make_shared<Panel>(glm::vec2(500, 300), glm::vec4(4.0f), 4.0f);
|
||||
auto panel = std::make_shared<Panel>(
|
||||
glm::vec2(
|
||||
glm::min(
|
||||
static_cast<size_t>(650),
|
||||
glm::max(text.length() * 10, static_cast<size_t>(200))
|
||||
),
|
||||
300
|
||||
),
|
||||
glm::vec4(4.0f),
|
||||
4.0f
|
||||
);
|
||||
panel->setColor(glm::vec4(0.0f, 0.0f, 0.0f, 0.5f));
|
||||
|
||||
auto menuPtr = engine.getGUI()->getMenu();
|
||||
@ -40,14 +50,15 @@ void guiutil::alert(
|
||||
menu.removePage("<alert>");
|
||||
if (on_hidden) {
|
||||
on_hidden();
|
||||
} else {
|
||||
menu.back();
|
||||
} else if (!menu.back()) {
|
||||
menu.reset();
|
||||
}
|
||||
};
|
||||
|
||||
auto label = std::make_shared<Label>(text);
|
||||
label->setMultiline(true);
|
||||
label->setSize(glm::vec2(1, 24));
|
||||
label->setAutoResize(true);
|
||||
panel->add(label);
|
||||
panel->add(std::make_shared<Button>(
|
||||
langs::get(L"Ok"), glm::vec4(10.f),
|
||||
@ -56,6 +67,7 @@ void guiutil::alert(
|
||||
}
|
||||
));
|
||||
panel->refresh();
|
||||
|
||||
panel->keepAlive(Events::keyCallbacks[keycode::ENTER].add([on_hidden_final](){
|
||||
on_hidden_final();
|
||||
return true;
|
||||
|
||||
16
src/io/devices/Device.cpp
Normal file
16
src/io/devices/Device.cpp
Normal file
@ -0,0 +1,16 @@
|
||||
#include "Device.hpp"
|
||||
|
||||
#include "../io.hpp"
|
||||
|
||||
using namespace io;
|
||||
|
||||
SubDevice::SubDevice(
|
||||
std::shared_ptr<Device> parent,
|
||||
const std::string& path,
|
||||
bool createDirectory
|
||||
)
|
||||
: parent(std::move(parent)), root(path) {
|
||||
if (createDirectory && !this->parent->exists(path)) {
|
||||
this->parent->mkdirs(path);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user