Merge branch 'headless-mode' into Fix-usage-vcpkg-for-windows-and-add-cmake-preset-for-vscode

This commit is contained in:
Stepanov Igor 2024-12-24 13:23:41 +03:00
commit f73bc1b06b
75 changed files with 966 additions and 385 deletions

50
dev/tests/filesystem.lua Normal file
View File

@ -0,0 +1,50 @@
debug.log("check initial state")
assert(file.exists("config:"))
debug.log("write text file")
assert(file.write("config:text.txt", "example, пример"))
assert(file.exists("config:text.txt"))
debug.log("read text file")
assert(file.read("config:text.txt") == "example, пример")
debug.log("delete file")
file.remove("config:text.txt")
assert(not file.exists("config:text.txt"))
debug.log("create directory")
file.mkdir("config:dir")
assert(file.isdir("config:dir"))
debug.log("remove directory")
file.remove("config:dir")
debug.log("create directories")
file.mkdirs("config:dir/subdir/other")
assert(file.isdir("config:dir/subdir/other"))
debug.log("remove tree")
file.remove_tree("config:dir")
assert(not file.isdir("config:dir"))
debug.log("write binary file")
local bytes = {0xDE, 0xAD, 0xC0, 0xDE}
file.write_bytes("config:binary", bytes)
assert(file.exists("config:binary"))
debug.log("read binary file")
local rbytes = file.read_bytes("config:binary")
assert(#rbytes == #bytes)
for i, b in ipairs(bytes) do
assert(rbytes[i] == b)
end
debug.log("delete file")
file.remove("config:binary")
assert(not file.exists("config:binary"))
debug.log("checking entry points for writeability")
assert(file.is_writeable("config:"))
assert(file.is_writeable("export:"))
assert(not file.is_writeable("user:"))
assert(not file.is_writeable("res:"))

View File

@ -81,6 +81,13 @@ Turns off block model shading
Determines the presence of the vertex AO effect. Turned-on by default.
### *culling*
Face culling mode:
- **default** - normal face culling
- **optional** - face culling among blocks of the same rendering group can be disabled via the `graphics.dense-render` setting.
- **disabled** - face culling among blocks of the same rendering group disabled.
## Physics
### *obstacle*

View File

@ -16,6 +16,10 @@ Particles are a table, all fields of which are optional.
| acceleration | Particles acceleration. | {0, -16, 0} |
| explosion | Force of particles explosion on spawn. | {2, 2, 2} |
| size | Size of particles. | {0.1, 0.1, 0.1} |
| size_spread | Maximum particle size spread over time. | 0.2 |
| angle_spread | Maximum initial rotation angle spread (0 to 1) | 0.0 |
| min_angular_vel | Minimum angular velocity (radians per sec). Non-negative. | 0.0 |
| max_angular_vel | Maximum angular velocity (radians per sec). Non-negative. | 0.0 |
| spawn_shape | Shape of particle spawn area. (ball/sphere/box) | ball |
| spawn_spread | Size of particle spawn area. | {0, 0, 0} |
| random_sub_uv | Size of random texture subregion (1 - entire texture will be used). | 1.0 |

View File

@ -25,6 +25,12 @@ file.read_bytes(path: str) -> array of integers
Read file into bytes array.
```lua
file.is_writeable(path: str) -> bool
```
Checks if the specified path is writable.
```python
file.write(path: str, text: str) -> nil
```

View File

@ -32,6 +32,21 @@ inventory.size(invid: int) -> int
-- Returns remaining count if could not to add fully.
inventory.add(invid: int, itemid: int, count: int) -> int
-- Returns the index of the first matching slot in the given range.
-- If no matching slot was found, returns nil
inventory.find_by_item(
-- inventory id
invid: int,
-- item id
itemid: int,
-- [optional] index of the slot range start (from 0)
range_begin: int,
-- [optional] index of the slot range end (from 0)
range_end: int,
-- [optional] minimum item count in the slot
min_count: int = 1
) -> int
-- Returns block inventory ID or 0.
inventory.get_block(x: int, y: int, z: int) -> int

View File

@ -70,6 +70,13 @@ player.set_instant_destruction(playerid: int, bool)
Getter and setter for instant destruction of blocks when the `player.destroy` binding is activated.
```lua
player.is_loading_chunks(playerid: int) -> bool
player.set_loading_chunks(playerid: int, bool)
```
Getter and setter of the property that determines whether the player is loading chunks.
``` lua
player.set_spawnpoint(playerid: int, x: number, y: number, z: number)
player.get_spawnpoint(playerid: int) -> number, number, number
@ -84,6 +91,12 @@ player.get_name(playerid: int) -> str
Player name setter and getter
```lua
player.set_selected_slot(playerid: int, slotid: int)
```
Sets the selected slot index
```lua
player.get_selected_block(playerid: int) -> x,y,z
```

View File

@ -27,6 +27,7 @@
* [Small structures placement](#small-structures-placement)
* [Wide structures placement](#wide-structures-placement)
- [Structural air](#structural-air)
- [Generator 'Demo' (base:demo)](#generator-demo-basedemo)
## Basic concepts
@ -473,3 +474,24 @@ function place_structures_wide(
`core:struct_air` - a block that should be used in chunks to mark empty space that should not be filled with blocks when generated in the world.
<image src="../../res/textures/blocks/struct_air.png" width="128px" height="128px" style="image-rendering: pixelated">
# Generator 'Demo' (base:demo)
## Adding new ore
To add a new ore in your pack:
1. In the `generators` folder, create a `demo.files` folder (you don't need to create demo.toml).
2. In the created folder, create a fragments folder and place the ore fragment file in it.
3. In `demo.files`, create a structures.toml file:
```toml
fragment_name = {}
```
4. Also in `demo.files`, create an ores.json file:
```json
[
{"struct": "fragment_name", "rarity": rarity}
]
```
The higher the rarity value, the less ore generation chance.
You can rely on the rarity of coal ore: 4400.

View File

@ -82,7 +82,6 @@
При значении `true` блок не препятствует прохождению вертикального луча солнечного света.
### Без освещения - *shadeless*
Выключает освещение на модели блока.
@ -91,6 +90,13 @@
Определяет наличие эффекта вершинного AO. Включен по-умолчанию.
### Отсечение - *culling*
Режим отсечения граней:
- **default** - обычное отсечение граней
- **optional** - отсечение граней среди блоков одной группы отрисовки можно отключить через настройку `graphics.dense-render` (Плотный рендер блоков).
- **disabled** - отсечение граней среди блоков одной группы отрисовки отключено.
## Физика
### Препятствие - *obstacle*

View File

@ -18,6 +18,9 @@
| explosion | Сила разлёта частиц при спавне. | {2, 2, 2} |
| size | Размер частиц. | {0.1, 0.1, 0.1} |
| size_spread | Максимальное отклонение времени размера частиц. | 0.2 |
| angle_spread | Максимальное отклонение начального угла поворота (от 0 до 1) | 0.0 |
| min_angular_vel | Минимальная угловая скорость (радианы в сек.). Неотрицательное. | 0.0 |
| max_angular_vel | Максимальная угловая скорость (радианы в сек.). Неотрицательное. | 0.0 |
| spawn_shape | Форма области спавна частиц. (ball/sphere/box) | ball |
| spawn_spread | Размер области спавна частиц. | {0, 0, 0} |
| random_sub_uv | Размер случайного подрегиона текстуры (1 - будет использована вся текстура). | 1.0 |

View File

@ -25,6 +25,12 @@ file.read_bytes(путь: str) -> array of integers
Читает файл в массив байт.
```lua
file.is_writeable(путь: str) -> bool
```
Проверяет, доступно ли право записи по указанному пути.
```python
file.write(путь: str, текст: str) -> nil
```

View File

@ -38,6 +38,21 @@ inventory.add(
count: int
) -> int
-- Возвращает индекс первого подходящего под критерии слота в заданном диапазоне.
-- Если подходящий слот не был найден, возвращает nil
inventory.find_by_item(
-- id инвентаря
invid: int,
-- id предмета
itemid: int,
-- [опционально] индекс начала диапазона слотов (c 0)
range_begin: int,
-- [опционально] индекс конца диапазона слотов (c 0)
range_end: int,
-- [опционально] минимальное количество предмета в слоте
min_count: int = 1
) -> int
-- Функция возвращает id инвентаря блока.
-- Если блок не может иметь инвентарь - возвращает 0.
inventory.get_block(x: int, y: int, z: int) -> int

View File

@ -70,6 +70,13 @@ player.set_instant_destruction(playerid: int, bool)
Геттер и сеттер мнгновенного разрушения блоков при активации привязки `player.destroy`.
```lua
player.is_loading_chunks(playerid: int) -> bool
player.set_loading_chunks(playerid: int, bool)
```
Геттер и сеттер свойства, определяющего, прогружает ли игрок чанки вокруг.
```lua
player.set_spawnpoint(playerid: int, x: number, y: number, z: number)
player.get_spawnpoint(playerid: int) -> number, number, number
@ -84,6 +91,12 @@ player.get_name(playerid: int) -> str
Сеттер и геттер имени игрока
```lua
player.set_selected_slot(playerid: int, slotid: int)
```
Устанавливает индекс выбранного слота
```lua
player.get_selected_block(playerid: int) -> x,y,z
```

View File

@ -46,6 +46,13 @@ table.remove_value(t: table, x: object)
Удаляет элемент **x** из **t**.
```lua
table.shuffle(t: table) -> table
```
Перемешивает значения в таблице.
```lua
table.tostring(t: table) -> string
```
@ -146,6 +153,18 @@ math.rand(low, high)
Возвращает случайное дробное число в диапазоне от **low** до **high**.
```lua
math.normalize(num: number, [опционально] conf: num) -> number
```
Возвращает нормализованное значение num относительно conf.
```lua
math.round(num: number, [опционально] places: num) -> number
```
Возвращает округлённое значение num до указанного количества знаков после запятой places.
## Дополнительные глобальные функции
В этом же скрипте также определены и другие глобальные функции которые доступны для использования. Ниже их список

View File

@ -27,6 +27,7 @@
* [Расстановка малых структур](#расстановка-малых-структур)
* [Расстановка 'широких' структур](#расстановка-широких-структур)
- [Структурный воздух](#структурный-воздух)
- [Генератор 'Demo' (base:demo)](#генератор-demo-basedemo)
## Основные понятия
@ -478,3 +479,23 @@ function place_structures_wide(
`core:struct_air` - блок, которые следует использовать в фрагментах для обозначения пустого пространства, которое не должно заполняться блоками при генерации в мире.
<image src="../../res/textures/blocks/struct_air.png" width="128px" height="128px" style="image-rendering: pixelated">
# Генератор 'Demo' (base:demo)
## Добавление новой руды
Чтобы добавить новую руду из своего пака:
1. В папке `generators` создайте папку `demo.files` (demo.toml создавать не нужно).
2. В созданной папке создайте папку fragments и поместите в неё файл фрагмента руды.
3. В `demo.files` создайте файл structures.toml:
```toml
имя_фрагмента = {}
```
4. Также в `demo.files` создайте файл ores.json:
```json
[
{"struct": "имя_фрагмента", "rarity": редкость}
]
```
Чем выше значение редкости, тем меньше вероятность генерации руды.
Опираться можно на редкость угольной руды: 4400.

View File

@ -1,5 +1,26 @@
{
"texture": "leaves",
"material": "base:grass",
"draw-group": 5,
"culling": "optional",
"particles": {
"lifetime": 4.0,
"spawn_interval": 1000.0,
"acceleration": [0, -0.1, 0],
"velocity": [0.2, -2.5, 0.3],
"explosion": [0, 0, 0],
"collision": false,
"size": [0.3, 0.3, 0.3],
"size_spread": 0.2,
"spawn_shape": "box",
"spawn_spread": [0.2, 0.2, 0.2],
"angle_spread": 1.0,
"min_angular_vel": 0.5,
"max_angular_vel": 2.0,
"lighting": true,
"frames": [
"particles:leaf_0"
]
},
"base:durability": 0.7
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

After

Width:  |  Height:  |  Size: 899 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

View File

@ -41,4 +41,5 @@ function on_open()
create_setting("graphics.fog-curve", "Fog Curve", 0.1)
create_setting("graphics.gamma", "Gamma", 0.05, "", "graphics.gamma.tooltip")
create_checkbox("graphics.backlight", "Backlight", "graphics.backlight.tooltip")
create_checkbox("graphics.dense-render", "Dense blocks render", "graphics.dense-render.tooltip")
end

View File

@ -72,9 +72,14 @@ function data_buffer:put_byte(byte)
end
function data_buffer:put_bytes(bytes)
for i = 1, #bytes do
self:put_byte(bytes[i])
end
if type(self.bytes) == 'table' then
for i = 1, #bytes do
self:put_byte(bytes[i])
end
else
self.bytes:insert(self.pos, bytes)
self.pos = self.pos + #bytes
end
end
function data_buffer:put_single(single)
@ -308,4 +313,4 @@ end
setmetatable(data_buffer, data_buffer)
return data_buffer
return data_buffer

58
res/scripts/hud.lua Normal file
View File

@ -0,0 +1,58 @@
function on_hud_open()
input.add_callback("player.pick", function ()
if hud.is_paused() or hud.is_inventory_open() then
return
end
local pid = hud.get_player()
local x, y, z = player.get_selected_block(pid)
if x == nil then
return
end
local id = block.get_picking_item(block.get(x, y, z))
local inv, cur_slot = player.get_inventory(pid)
local slot = inventory.find_by_item(inv, id, 0, 9)
if slot then
player.set_selected_slot(pid, slot)
return
end
if not rules.get("allow-content-access") then
return
end
slot = inventory.find_by_item(inv, 0, 0, 9)
if slot then
cur_slot = slot
end
player.set_selected_slot(pid, cur_slot)
inventory.set(inv, cur_slot, id, 1)
end)
input.add_callback("player.noclip", function ()
if hud.is_paused() or hud.is_inventory_open() then
return
end
local pid = hud.get_player()
if player.is_noclip(pid) then
player.set_flight(pid, false)
player.set_noclip(pid, false)
else
player.set_flight(pid, true)
player.set_noclip(pid, true)
end
end)
input.add_callback("player.flight", function ()
if hud.is_paused() or hud.is_inventory_open() then
return
end
local pid = hud.get_player()
if player.is_noclip(pid) then
return
end
if player.is_flight(pid) then
player.set_flight(pid, false)
else
player.set_flight(pid, true)
player.set_vel(pid, 0, 1, 0)
end
end)
end

View File

@ -320,7 +320,6 @@ function __vc_on_hud_open()
_rules.create("allow-content-access", hud._is_content_access(), function(value)
hud._set_content_access(value)
input.set_enabled("player.pick", value)
end)
_rules.create("allow-flight", true, function(value)
input.set_enabled("player.flight", value)

View File

@ -1,6 +1,6 @@
-- Check if given table is an array
function is_array(x)
if #t > 0 then
if #x > 0 then
return true
end
for k, v in pairs(x) do
@ -51,6 +51,19 @@ function math.rand(low, high)
return low + (high - low) * math.random()
end
function math.normalize(num, conf)
conf = conf or 1
return (num / conf) % 1
end
function math.round(num, places)
places = places or 0
local mult = 10 ^ places
return math.floor(num * mult + 0.5) / mult
end
----------------------------------------------
function table.copy(t)
@ -91,6 +104,15 @@ function table.random(t)
return t[math.random(1, #t)]
end
function table.shuffle(t)
for i = #t, 2, -1 do
local j = math.random(i)
t[i], t[j] = t[j], t[i]
end
return t
end
----------------------------------------------
local pattern_escape_replacements = {

View File

@ -16,6 +16,7 @@ devtools.traceback=Traceback (most recent call first)
# Tooltips
graphics.gamma.tooltip=Lighting brightness curve
graphics.backlight.tooltip=Backlight to prevent total darkness
graphics.dense-render.tooltip=Enables transparency in blocks like leaves
# settings
settings.Controls Search Mode=Search by attached button name

View File

@ -28,6 +28,7 @@ pack.remove-confirm=Удалить весь поставляемый паком/
# Подсказки
graphics.gamma.tooltip=Кривая яркости освещения
graphics.backlight.tooltip=Подсветка, предотвращающая полную темноту
graphics.dense-render.tooltip=Включает прозрачность блоков, таких как листья.
# Меню
menu.Apply=Применить
@ -67,6 +68,7 @@ world.delete-confirm=Удалить мир безвозвратно?
# Настройки
settings.Ambient=Фон
settings.Backlight=Подсветка
settings.Dense blocks render=Плотный рендер блоков
settings.Camera Shaking=Тряска Камеры
settings.Camera Inertia=Инерция Камеры
settings.Camera FOV Effects=Эффекты поля зрения

View File

@ -239,10 +239,18 @@ void ContentLoader::loadBlock(
}
def.model = *model;
} else if (!modelTypeName.empty()) {
logger.error() << "unknown model " << modelTypeName;
logger.error() << "unknown model: " << modelTypeName;
def.model = BlockModel::none;
}
std::string cullingModeName = to_string(def.culling);
root.at("culling").get(cullingModeName);
if (auto mode = CullingMode_from(cullingModeName)) {
def.culling = *mode;
} else {
logger.error() << "unknown culling mode: " << cullingModeName;
}
root.at("material").get(def.material);
// rotation profile

View File

@ -95,12 +95,13 @@ struct ContentPackStats {
}
};
struct world_funcs_set {
bool onblockplaced : 1;
bool onblockreplaced : 1;
bool onblockbroken : 1;
bool onblockinteract : 1;
bool onplayertick : 1;
struct WorldFuncsSet {
bool onblockplaced;
bool onblockreplaced;
bool onblockbreaking;
bool onblockbroken;
bool onblockinteract;
bool onplayertick;
};
class ContentPackRuntime {
@ -108,7 +109,7 @@ class ContentPackRuntime {
ContentPackStats stats {};
scriptenv env;
public:
world_funcs_set worldfuncsset {};
WorldFuncsSet worldfuncsset {};
ContentPackRuntime(ContentPack info, scriptenv env);
~ContentPackRuntime();

View File

@ -21,12 +21,9 @@ inline const std::string BIND_MOVE_CROUCH = "movement.crouch";
inline const std::string BIND_MOVE_CHEAT = "movement.cheat";
inline const std::string BIND_CAM_ZOOM = "camera.zoom";
inline const std::string BIND_CAM_MODE = "camera.mode";
inline const std::string BIND_PLAYER_NOCLIP = "player.noclip";
inline const std::string BIND_PLAYER_FLIGHT = "player.flight";
inline const std::string BIND_PLAYER_ATTACK = "player.attack";
inline const std::string BIND_PLAYER_DESTROY = "player.destroy";
inline const std::string BIND_PLAYER_BUILD = "player.build";
inline const std::string BIND_PLAYER_PICK = "player.pick";
inline const std::string BIND_PLAYER_FAST_INTERACTOIN =
"player.fast_interaction";
inline const std::string BIND_HUD_INVENTORY = "hud.inventory";

View File

@ -83,6 +83,9 @@ Engine::Engine(CoreParameters coreParameters)
paths.setResourcesFolder(params.resFolder);
paths.setUserFilesFolder(params.userFolder);
paths.prepare();
if (!params.scriptFile.empty()) {
paths.setScriptFolder(params.scriptFile.parent_path());
}
loadSettings();
auto resdir = paths.getResourcesFolder();

View File

@ -169,6 +169,10 @@ 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);
}
@ -211,7 +215,9 @@ std::filesystem::path EnginePaths::resolve(
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) {

View File

@ -2,6 +2,7 @@
#include <filesystem>
#include <stdexcept>
#include <optional>
#include <string>
#include <vector>
#include <tuple>
@ -26,6 +27,8 @@ public:
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;
@ -51,6 +54,7 @@ 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;
};

View File

@ -68,10 +68,12 @@ SettingsHandler::SettingsHandler(EngineSettings& settings) {
builder.section("graphics");
builder.add("fog-curve", &settings.graphics.fogCurve);
builder.add("backlight", &settings.graphics.backlight);
builder.add("dense-render", &settings.graphics.denseRender);
builder.add("gamma", &settings.graphics.gamma);
builder.add("frustum-culling", &settings.graphics.frustumCulling);
builder.add("skybox-resolution", &settings.graphics.skyboxResolution);
builder.add("chunk-max-vertices", &settings.graphics.chunkMaxVertices);
builder.add("chunk-max-vertices-dense", &settings.graphics.chunkMaxVerticesDense);
builder.add("chunk-max-renderers", &settings.graphics.chunkMaxRenderers);
builder.section("ui");

View File

@ -6,53 +6,71 @@
#include "assets/Assets.hpp"
#include "content/Content.hpp"
#include "content/ContentPack.hpp"
#include "core_defs.hpp"
#include "graphics/core/Atlas.hpp"
#include "maths/UVRegion.hpp"
#include "voxels/Block.hpp"
#include "core_defs.hpp"
#include "settings.hpp"
ContentGfxCache::ContentGfxCache(const Content* content, const Assets& assets)
: content(content) {
auto indices = content->getIndices();
ContentGfxCache::ContentGfxCache(
const Content& content,
const Assets& assets,
const GraphicsSettings& settings
)
: content(content), assets(assets), settings(settings) {
refresh();
}
void ContentGfxCache::refresh(const Block& def, const Atlas& atlas) {
for (uint side = 0; side < 6; side++) {
std::string tex = def.textureFaces[side];
if (def.culling == CullingMode::OPTIONAL &&
!settings.denseRender.get() && atlas.has(tex + "_opaque")) {
tex = tex + "_opaque";
}
if (atlas.has(tex)) {
sideregions[def.rt.id * 6 + side] = atlas.get(tex);
} else if (atlas.has(TEXTURE_NOTFOUND)) {
sideregions[def.rt.id * 6 + side] = atlas.get(TEXTURE_NOTFOUND);
}
}
if (def.model == BlockModel::custom) {
auto model = assets.require<model::Model>(def.modelName);
// temporary dirty fix tbh
if (def.modelName.find(':') == std::string::npos) {
for (auto& mesh : model.meshes) {
size_t pos = mesh.texture.find(':');
if (pos == std::string::npos) {
continue;
}
if (auto region = atlas.getIf(mesh.texture.substr(pos+1))) {
for (auto& vertex : mesh.vertices) {
vertex.uv = region->apply(vertex.uv);
}
}
}
}
models[def.rt.id] = std::move(model);
}
}
void ContentGfxCache::refresh() {
auto indices = content.getIndices();
sideregions = std::make_unique<UVRegion[]>(indices->blocks.count() * 6);
const auto& atlas = assets.require<Atlas>("blocks");
const auto& blocks = indices->blocks.getIterable();
for (blockid_t i = 0; i < blocks.size(); i++) {
auto def = blocks[i];
for (uint side = 0; side < 6; side++) {
const std::string& tex = def->textureFaces[side];
if (atlas.has(tex)) {
sideregions[i * 6 + side] = atlas.get(tex);
} else if (atlas.has(TEXTURE_NOTFOUND)) {
sideregions[i * 6 + side] = atlas.get(TEXTURE_NOTFOUND);
}
}
if (def->model == BlockModel::custom) {
auto model = assets.require<model::Model>(def->modelName);
// temporary dirty fix tbh
if (def->modelName.find(':') == std::string::npos) {
for (auto& mesh : model.meshes) {
size_t pos = mesh.texture.find(':');
if (pos == std::string::npos) {
continue;
}
if (auto region = atlas.getIf(mesh.texture.substr(pos+1))) {
for (auto& vertex : mesh.vertices) {
vertex.uv = region->apply(vertex.uv);
}
}
}
}
models[def->rt.id] = std::move(model);
}
refresh(*def, atlas);
}
}
ContentGfxCache::~ContentGfxCache() = default;
const Content* ContentGfxCache::getContent() const {
return content;
return &content;
}
const model::Model& ContentGfxCache::getModel(blockid_t id) const {

View File

@ -10,19 +10,29 @@
class Content;
class Assets;
class Atlas;
class Block;
struct UVRegion;
struct GraphicsSettings;
namespace model {
struct Model;
}
class ContentGfxCache {
const Content* content;
const Content& content;
const Assets& assets;
const GraphicsSettings& settings;
// array of block sides uv regions (6 per block)
std::unique_ptr<UVRegion[]> sideregions;
std::unordered_map<blockid_t, model::Model> models;
public:
ContentGfxCache(const Content* content, const Assets& assets);
ContentGfxCache(
const Content& content,
const Assets& assets,
const GraphicsSettings& settings
);
~ContentGfxCache();
inline const UVRegion& getRegion(blockid_t id, int side) const {
@ -32,4 +42,8 @@ public:
const model::Model& getModel(blockid_t id) const;
const Content* getContent() const;
void refresh(const Block& block, const Atlas& atlas);
void refresh();
};

View File

@ -14,12 +14,17 @@
#include "world/Level.hpp"
LevelFrontend::LevelFrontend(
Player* currentPlayer, LevelController* controller, Assets& assets
) : level(*controller->getLevel()),
controller(controller),
assets(assets),
contentCache(std::make_unique<ContentGfxCache>(level.content, assets))
{
Player* currentPlayer,
LevelController* controller,
Assets& assets,
const EngineSettings& settings
)
: level(*controller->getLevel()),
controller(controller),
assets(assets),
contentCache(std::make_unique<ContentGfxCache>(
*level.content, assets, settings.graphics
)) {
assets.store(
BlocksPreview::build(
*contentCache, assets, *level.content->getIndices()
@ -98,6 +103,10 @@ const Assets& LevelFrontend::getAssets() const {
return assets;
}
ContentGfxCache& LevelFrontend::getContentGfxCache() {
return *contentCache;
}
const ContentGfxCache& LevelFrontend::getContentGfxCache() const {
return *contentCache;
}

View File

@ -7,6 +7,7 @@ class Assets;
class Player;
class ContentGfxCache;
class LevelController;
struct EngineSettings;
class LevelFrontend {
Level& level;
@ -14,12 +15,18 @@ class LevelFrontend {
const Assets& assets;
std::unique_ptr<ContentGfxCache> contentCache;
public:
LevelFrontend(Player* currentPlayer, LevelController* controller, Assets& assets);
LevelFrontend(
Player* currentPlayer,
LevelController* controller,
Assets& assets,
const EngineSettings& settings
);
~LevelFrontend();
Level& getLevel();
const Level& getLevel() const;
const Assets& getAssets() const;
const ContentGfxCache& getContentGfxCache() const;
ContentGfxCache& getContentGfxCache();
LevelController* getController() const;
};

View File

@ -154,7 +154,7 @@ static constexpr uint WORLDGEN_IMG_SIZE = 128U;
Hud::Hud(Engine& engine, LevelFrontend& frontend, Player& player)
: engine(engine),
assets(*engine.getAssets()),
gui(engine.getGUI()),
gui(*engine.getGUI()),
frontend(frontend),
player(player),
debugImgWorldGen(std::make_unique<ImageData>(
@ -183,11 +183,11 @@ Hud::Hud(Engine& engine, LevelFrontend& frontend, Player& player)
engine, frontend.getLevel(), player, allowDebugCheats
);
debugPanel->setZIndex(2);
gui->add(debugPanel);
gui.add(debugPanel);
gui->add(darkOverlay);
gui->add(hotbarView);
gui->add(contentAccessPanel);
gui.add(darkOverlay);
gui.add(hotbarView);
gui.add(contentAccessPanel);
auto dplotter = std::make_shared<Plotter>(350, 250, 2000, 16);
dplotter->setGravity(Gravity::bottom_right);
@ -208,10 +208,10 @@ Hud::~Hud() {
for (auto& element : elements) {
onRemove(element);
}
gui->remove(hotbarView);
gui->remove(darkOverlay);
gui->remove(contentAccessPanel);
gui->remove(debugPanel);
gui.remove(hotbarView);
gui.remove(darkOverlay);
gui.remove(contentAccessPanel);
gui.remove(debugPanel);
}
/// @brief Remove all elements marked as removed
@ -322,9 +322,9 @@ void Hud::updateWorldGenDebugVisualization() {
void Hud::update(bool visible) {
const auto& level = frontend.getLevel();
const auto& chunks = *player.chunks;
auto menu = gui->getMenu();
const auto& menu = gui.getMenu();
debugPanel->setVisible(player.debug && visible);
debugPanel->setVisible(debug && visible);
if (!visible && inventoryOpen) {
closeInventory();
@ -333,7 +333,7 @@ void Hud::update(bool visible) {
setPause(false);
}
if (!gui->isFocusCaught()) {
if (!gui.isFocusCaught()) {
processInput(visible);
}
if ((pause || inventoryOpen) == Events::_cursor_locked) {
@ -359,7 +359,7 @@ void Hud::update(bool visible) {
if (visible) {
for (auto& element : elements) {
element.update(pause, inventoryOpen, player.debug);
element.update(pause, inventoryOpen, debug);
if (element.isRemoved()) {
onRemove(element);
}
@ -367,8 +367,8 @@ void Hud::update(bool visible) {
}
cleanup();
debugMinimap->setVisible(player.debug && showGeneratorMinimap);
if (player.debug && showGeneratorMinimap) {
debugMinimap->setVisible(debug && showGeneratorMinimap);
if (debug && showGeneratorMinimap) {
updateWorldGenDebugVisualization();
}
}
@ -460,7 +460,7 @@ void Hud::showExchangeSlot() {
exchangeSlot->setColor(glm::vec4());
exchangeSlot->setInteractive(false);
exchangeSlot->setZIndex(1);
gui->store(SlotView::EXCHANGE_SLOT_NAME, exchangeSlot);
gui.store(SlotView::EXCHANGE_SLOT_NAME, exchangeSlot);
}
@ -494,7 +494,7 @@ void Hud::openPermanent(UiDocument* doc) {
void Hud::dropExchangeSlot() {
auto slotView = std::dynamic_pointer_cast<SlotView>(
gui->get(SlotView::EXCHANGE_SLOT_NAME)
gui.get(SlotView::EXCHANGE_SLOT_NAME)
);
if (slotView == nullptr) {
return;
@ -518,7 +518,7 @@ void Hud::dropExchangeSlot() {
void Hud::closeInventory() {
dropExchangeSlot();
gui->remove(SlotView::EXCHANGE_SLOT_NAME);
gui.remove(SlotView::EXCHANGE_SLOT_NAME);
exchangeSlot = nullptr;
exchangeSlotInv = nullptr;
inventoryOpen = false;
@ -536,7 +536,7 @@ void Hud::closeInventory() {
}
void Hud::add(const HudElement& element, const dv::value& argsArray) {
gui->add(element.getNode());
gui.add(element.getNode());
auto document = element.getDocument();
if (document) {
auto invview = std::dynamic_pointer_cast<InventoryView>(element.getNode());
@ -572,7 +572,7 @@ void Hud::onRemove(const HudElement& element) {
invview->unbind();
}
}
gui->remove(element.getNode());
gui.remove(element.getNode());
}
void Hud::remove(const std::shared_ptr<UINode>& node) {
@ -585,6 +585,10 @@ void Hud::remove(const std::shared_ptr<UINode>& node) {
cleanup();
}
void Hud::setDebug(bool flag) {
debug = flag;
}
void Hud::draw(const DrawContext& ctx){
const Viewport& viewport = ctx.getViewport();
const uint width = viewport.getWidth();
@ -602,7 +606,7 @@ void Hud::draw(const DrawContext& ctx){
uishader.uniformMatrix("u_projview", uicamera->getProjView());
// Crosshair
if (!pause && !inventoryOpen && !player.debug) {
if (!pause && !inventoryOpen && !debug) {
DrawContext chctx = ctx.sub(batch);
chctx.setBlendMode(BlendMode::inversion);
auto texture = assets.get<Texture>("gui/crosshair");
@ -681,7 +685,7 @@ void Hud::setPause(bool pause) {
closeInventory();
}
auto menu = gui->getMenu();
const auto& menu = gui.getMenu();
if (pause) {
menu->setPage("pause");
} else {
@ -713,10 +717,10 @@ void Hud::setContentAccess(bool flag) {
void Hud::setDebugCheats(bool flag) {
allowDebugCheats = flag;
gui->remove(debugPanel);
gui.remove(debugPanel);
debugPanel = create_debug_panel(
engine, frontend.getLevel(), player, allowDebugCheats
);
debugPanel->setZIndex(2);
gui->add(debugPanel);
gui.add(debugPanel);
}

View File

@ -73,7 +73,7 @@ class Hud : public util::ObjectsKeeper {
Engine& engine;
Assets& assets;
std::unique_ptr<Camera> uicamera;
gui::GUI* gui;
gui::GUI& gui;
LevelFrontend& frontend;
Player& player;
@ -113,6 +113,7 @@ class Hud : public util::ObjectsKeeper {
bool showContentPanel = true;
/// @brief Provide cheat controllers to the debug panel
bool allowDebugCheats = true;
bool debug = false;
/// @brief UI element will be dynamicly positioned near to inventory or in screen center
std::shared_ptr<gui::UINode> secondUI;
@ -193,6 +194,8 @@ public:
void onRemove(const HudElement& element);
void remove(const std::shared_ptr<gui::UINode>& node);
void setDebug(bool flag);
Player* getPlayer() const;
std::shared_ptr<Inventory> getBlockInventory();

View File

@ -17,6 +17,8 @@
#include "graphics/render/WorldRenderer.hpp"
#include "graphics/ui/GUI.hpp"
#include "graphics/ui/elements/Menu.hpp"
#include "graphics/ui/GUI.hpp"
#include "frontend/ContentGfxCache.hpp"
#include "logic/LevelController.hpp"
#include "logic/PlayerController.hpp"
#include "logic/scripting/scripting.hpp"
@ -55,7 +57,7 @@ LevelScreen::LevelScreen(Engine& engine, std::unique_ptr<Level> levelPtr)
);
frontend = std::make_unique<LevelFrontend>(
player, controller.get(), assets
player, controller.get(), assets, settings
);
worldRenderer = std::make_unique<WorldRenderer>(
engine, *frontend, *player
@ -70,6 +72,11 @@ LevelScreen::LevelScreen(Engine& engine, std::unique_ptr<Level> levelPtr)
player->chunks->saveAndClear();
worldRenderer->clear();
}));
keepAlive(settings.graphics.denseRender.observe([=](bool) {
player->chunks->saveAndClear();
worldRenderer->clear();
frontend->getContentGfxCache().refresh();
}));
keepAlive(settings.camera.fov.observe([=](double value) {
player->fpCamera->setFov(glm::radians(value));
}));
@ -150,7 +157,9 @@ void LevelScreen::updateHotkeys() {
hudVisible = !hudVisible;
}
if (Events::jpressed(keycode::F3)) {
player->debug = !player->debug;
debug = !debug;
hud->setDebug(debug);
worldRenderer->setDebug(debug);
}
}

View File

@ -29,6 +29,7 @@ class LevelScreen : public Screen {
void saveWorldPreview();
bool hudVisible = true;
bool debug = false;
void updateHotkeys();
void initializeContent();
void initializePack(ContentPackRuntime* pack);

View File

@ -25,7 +25,7 @@ GLTexture::GLTexture(const ubyte* data, uint width, uint height, ImageFormat ima
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glGenerateMipmap(GL_TEXTURE_2D);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 2);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1);
glBindTexture(GL_TEXTURE_2D, 0);
}

View File

@ -8,9 +8,6 @@
#include "voxels/Chunks.hpp"
#include "lighting/Lightmap.hpp"
#include "frontend/ContentGfxCache.hpp"
#include "settings.hpp"
#include <glm/glm.hpp>
const glm::vec3 BlocksRenderer::SUN_VECTOR (0.411934f, 0.863868f, -0.279161f);
@ -342,41 +339,41 @@ void BlocksRenderer::blockCube(
}
if (ao) {
if (isOpen(coord + Z, group)) {
if (isOpen(coord + Z, block)) {
faceAO(coord, X, Y, Z, texfaces[5], lights);
}
if (isOpen(coord - Z, group)) {
if (isOpen(coord - Z, block)) {
faceAO(coord, -X, Y, -Z, texfaces[4], lights);
}
if (isOpen(coord + Y, group)) {
if (isOpen(coord + Y, block)) {
faceAO(coord, X, -Z, Y, texfaces[3], lights);
}
if (isOpen(coord - Y, group)) {
if (isOpen(coord - Y, block)) {
faceAO(coord, X, Z, -Y, texfaces[2], lights);
}
if (isOpen(coord + X, group)) {
if (isOpen(coord + X, block)) {
faceAO(coord, -Z, Y, X, texfaces[1], lights);
}
if (isOpen(coord - X, group)) {
if (isOpen(coord - X, block)) {
faceAO(coord, Z, Y, -X, texfaces[0], lights);
}
} else {
if (isOpen(coord + Z, group)) {
if (isOpen(coord + Z, block)) {
face(coord, X, Y, Z, texfaces[5], pickLight(coord + Z), lights);
}
if (isOpen(coord - Z, group)) {
if (isOpen(coord - Z, block)) {
face(coord, -X, Y, -Z, texfaces[4], pickLight(coord - Z), lights);
}
if (isOpen(coord + Y, group)) {
if (isOpen(coord + Y, block)) {
face(coord, X, -Z, Y, texfaces[3], pickLight(coord + Y), lights);
}
if (isOpen(coord - Y, group)) {
if (isOpen(coord - Y, block)) {
face(coord, X, Z, -Y, texfaces[2], pickLight(coord - Y), lights);
}
if (isOpen(coord + X, group)) {
if (isOpen(coord + X, block)) {
face(coord, -Z, Y, X, texfaces[1], pickLight(coord + X), lights);
}
if (isOpen(coord - X, group)) {
if (isOpen(coord - X, block)) {
face(coord, Z, Y, -X, texfaces[0], pickLight(coord - X), lights);
}
}

View File

@ -13,6 +13,7 @@
#include "graphics/core/MeshData.hpp"
#include "maths/util.hpp"
#include "commons.hpp"
#include "settings.hpp"
class Content;
class Mesh;
@ -22,7 +23,6 @@ class Chunks;
class VoxelsVolume;
class Chunks;
class ContentGfxCache;
struct EngineSettings;
struct UVRegion;
class BlocksRenderer {
@ -118,7 +118,7 @@ class BlocksRenderer {
bool isOpenForLight(int x, int y, int z) const;
// Does block allow to see other blocks sides (is it transparent)
inline bool isOpen(const glm::ivec3& pos, ubyte group) const {
inline bool isOpen(const glm::ivec3& pos, const Block& def) const {
auto id = voxelsBuffer->pickBlockId(
chunk->x * CHUNK_W + pos.x, pos.y, chunk->z * CHUNK_D + pos.z
);
@ -126,7 +126,13 @@ class BlocksRenderer {
return false;
}
const auto& block = *blockDefsCache[id];
if ((block.drawGroup != group && block.lightPassing) || !block.rt.solid) {
if (((block.drawGroup != def.drawGroup) && block.drawGroup) || !block.rt.solid) {
return true;
}
if ((def.culling == CullingMode::DISABLED ||
(def.culling == CullingMode::OPTIONAL &&
settings.graphics.denseRender.get())) &&
id == def.rt.id) {
return true;
}
return !id;

View File

@ -24,15 +24,22 @@ class RendererWorker : public util::Worker<std::shared_ptr<Chunk>, RendererResul
BlocksRenderer renderer;
public:
RendererWorker(
const Level& level,
const Level& level,
const Chunks& chunks,
const ContentGfxCache& cache,
const EngineSettings& settings
) : level(level),
chunks(chunks),
renderer(settings.graphics.chunkMaxVertices.get(),
*level.content, cache, settings)
{}
)
: level(level),
chunks(chunks),
renderer(
settings.graphics.denseRender.get()
? settings.graphics.chunkMaxVerticesDense.get()
: settings.graphics.chunkMaxVertices.get(),
*level.content,
cache,
settings
) {
}
RendererResult operator()(const std::shared_ptr<Chunk>& chunk) override {
renderer.build(chunk.get(), &chunks);

View File

@ -104,9 +104,8 @@ void Decorator::update(
void Decorator::update(float delta, const Camera& camera) {
glm::ivec3 pos = camera.position;
pos -= glm::ivec3(UPDATE_AREA_DIAMETER / 2);
for (int i = 0; i < ITERATIONS; i++) {
update(delta, pos, camera.position);
update(delta, pos - glm::ivec3(UPDATE_AREA_DIAMETER / 2), pos);
}
const auto& chunks = *player.chunks;
const auto& indices = *level.content->getIndices();

View File

@ -14,7 +14,7 @@ class Chunks;
class Camera;
class Assets;
class Player;
struct Block;
class Block;
class Engine;
class LevelController;
class WorldRenderer;

View File

@ -19,12 +19,13 @@ Emitter::Emitter(
)
: level(level),
origin(std::move(origin)),
prototype({this, 0, glm::vec3(), preset.velocity, preset.lifetime, region}),
prototype({this, 0, {}, preset.velocity, preset.lifetime, region}),
texture(texture),
count(count),
preset(std::move(preset)) {
random.setSeed(reinterpret_cast<ptrdiff_t>(this));
this->prototype.emitter = this;
timer = preset.spawnInterval;
timer = preset.spawnInterval * random.randFloat();
}
const Texture* Emitter::getTexture() const {
@ -76,6 +77,10 @@ void Emitter::update(
count = std::max(0, count - skipped);
timer -= skipped * spawnInterval;
}
if (count < 0) {
int skipped = timer / spawnInterval;
timer -= skipped * spawnInterval;
}
return;
}
while (count && timer > spawnInterval) {
@ -83,6 +88,15 @@ void Emitter::update(
Particle particle = prototype;
particle.emitter = this;
particle.random = random.rand32();
if (glm::abs(preset.angleSpread) >= 0.005f) {
particle.angle =
random.randFloat() * preset.angleSpread * glm::pi<float>() * 2;
}
particle.angularVelocity =
(preset.minAngularVelocity +
random.randFloat() *
(preset.maxAngularVelocity - preset.minAngularVelocity)) *
((random.rand() % 2) * 2 - 1);
glm::vec3 spawnOffset = generate_coord(preset.spawnShape);
spawnOffset *= preset.spawnSpread;
@ -103,6 +117,7 @@ void Emitter::update(
if (count > 0) {
count--;
}
refCount++;
}
}
@ -114,6 +129,10 @@ bool Emitter::isDead() const {
return count == 0;
}
bool Emitter::isReferred() const {
return refCount > 0;
}
const EmitterOrigin& Emitter::getOrigin() const {
return origin;
}

View File

@ -27,6 +27,10 @@ struct Particle {
float lifetime;
/// @brief UV region
UVRegion region;
/// @brief Current rotation angle
float angle;
/// @brief Angular velocity
float angularVelocity;
};
class Texture;
@ -39,7 +43,7 @@ class Emitter {
EmitterOrigin origin;
/// @brief Particle prototype
Particle prototype;
/// @brief Particle
/// @brief Particle texture
const Texture* texture;
/// @brief Number of particles should be spawned before emitter deactivation.
/// -1 is infinite.
@ -50,6 +54,9 @@ class Emitter {
util::PseudoRandom random;
public:
/// @brief Number of references (alive particles)
int refCount = 0;
/// @brief Particle settings
ParticlesPreset preset;
Emitter(
@ -82,6 +89,9 @@ public:
/// @return true if the emitter has spawned all particles
bool isDead() const;
/// @return true if there is at least one alive referring particle left
bool isReferred() const;
const EmitterOrigin& getOrigin() const;
void setOrigin(const EmitterOrigin& origin);

View File

@ -7,7 +7,7 @@
struct ItemDef;
class Assets;
class Content;
struct Block;
class Block;
class ModelsGenerator {
public:

View File

@ -36,12 +36,14 @@ static inline void update_particle(
const auto& preset = particle.emitter->preset;
auto& pos = particle.position;
auto& vel = particle.velocity;
auto& angle = particle.angle;
vel += delta * preset.acceleration;
if (preset.collision && chunks.isObstacleAt(pos + vel * delta)) {
vel *= 0.0f;
}
pos += vel * delta;
angle += particle.angularVelocity * delta;
particle.lifetime -= delta;
}
@ -65,7 +67,8 @@ void ParticlesRenderer::renderParticles(const Camera& camera, float delta) {
auto iter = vec.begin();
while (iter != vec.end()) {
auto& particle = *iter;
auto& preset = particle.emitter->preset;
auto& emitter = *particle.emitter;
auto& preset = emitter.preset;
if (!preset.frames.empty()) {
float time = preset.lifetime - particle.lifetime;
@ -86,19 +89,52 @@ void ParticlesRenderer::renderParticles(const Camera& camera, float delta) {
}
update_particle(particle, delta, chunks);
float scale = 1.0f + ((particle.random ^ 2628172) % 1000) *
0.001f * preset.sizeSpread;
glm::vec4 light(1, 1, 1, 0);
if (preset.lighting) {
light = MainBatch::sampleLight(
particle.position, chunks, backlight
particle.position,
chunks,
backlight
);
auto size = glm::max(glm::vec3(0.5f), preset.size * scale);
for (int x = -1; x <= 1; x++) {
for (int y = -1; y <= 1; y++) {
for (int z = -1; z <= 1; z++) {
light = glm::max(
light,
MainBatch::sampleLight(
particle.position -
size * glm::vec3(x, y, z),
chunks,
backlight
)
);
}
}
}
light *= 0.9f + (particle.random % 100) * 0.001f;
}
float scale = 1.0f + ((particle.random ^ 2628172) % 1000) *
0.001f * preset.sizeSpread;
glm::vec3 localRight = right;
glm::vec3 localUp = preset.globalUpVector ? glm::vec3(0, 1, 0) : up;
float angle = particle.angle;
if (glm::abs(angle) >= 0.005f) {
glm::vec3 rotatedRight(glm::cos(angle), -glm::sin(angle), 0.0f);
glm::vec3 rotatedUp(glm::sin(angle), glm::cos(angle), 0.0f);
localRight = right * rotatedRight.x + localUp * rotatedRight.y +
camera.front * rotatedRight.z;
localUp = right * rotatedUp.x + localUp * rotatedUp.y +
camera.front * rotatedUp.z;
}
batch->quad(
particle.position,
right,
preset.globalUpVector ? glm::vec3(0, 1, 0) : up,
localRight,
localUp,
preset.size * scale,
light,
glm::vec3(1.0f),
@ -106,6 +142,7 @@ void ParticlesRenderer::renderParticles(const Camera& camera, float delta) {
);
if (particle.lifetime <= 0.0f) {
iter = vec.erase(iter);
emitter.refCount--;
} else {
iter++;
}
@ -128,19 +165,15 @@ void ParticlesRenderer::render(const Camera& camera, float delta) {
auto iter = emitters.begin();
while (iter != emitters.end()) {
auto& emitter = *iter->second;
if (emitter.isDead() && !emitter.isReferred()) {
// destruct Emitter only when there is no particles spawned by it
iter = emitters.erase(iter);
continue;
}
auto texture = emitter.getTexture();
const auto& found = particles.find(texture);
std::vector<Particle>* vec;
if (found == particles.end()) {
if (emitter.isDead()) {
// destruct Emitter only when there is no particles spawned by it
iter = emitters.erase(iter);
continue;
}
vec = &particles[texture];
} else {
vec = &found->second;
}
vec = &particles[texture];
emitter.update(delta, camera.position, *vec);
iter++;
}

View File

@ -211,7 +211,7 @@ void WorldRenderer::renderBlockSelection() {
lineBatch->box(
center, size + glm::vec3(0.01), glm::vec4(0.f, 0.f, 0.f, 0.5f)
);
if (player.debug) {
if (debug) {
lineBatch->line(
point, point + norm * 0.5f, glm::vec4(1.0f, 0.0f, 1.0f, 1.0f)
);
@ -228,7 +228,7 @@ void WorldRenderer::renderLines(
if (player.selection.vox.id != BLOCK_VOID) {
renderBlockSelection();
}
if (player.debug && showEntitiesDebug) {
if (debug && showEntitiesDebug) {
auto ctx = pctx.sub(lineBatch.get());
bool culling = engine.getSettings().graphics.frustumCulling.get();
level.entities->renderDebug(
@ -337,7 +337,7 @@ void WorldRenderer::draw(
renderLevel(ctx, camera, settings, delta, pause, hudVisible);
// Debug lines
if (hudVisible) {
if (player.debug) {
if (debug) {
guides->renderDebugLines(
ctx, camera, *lineBatch, linesShader, showChunkBorders
);
@ -410,3 +410,7 @@ void WorldRenderer::renderBlockOverlay(const DrawContext& wctx) {
void WorldRenderer::clear() {
chunks->clear();
}
void WorldRenderer::setDebug(bool flag) {
debug = flag;
}

View File

@ -45,6 +45,7 @@ class WorldRenderer {
std::unique_ptr<ModelBatch> modelBatch;
float timer = 0.0f;
bool debug = false;
/// @brief Render block selection lines
void renderBlockSelection();
@ -100,4 +101,6 @@ public:
);
void clear();
void setDebug(bool flag);
};

View File

@ -23,10 +23,13 @@ size_t Inventory::findEmptySlot(size_t begin, size_t end) const {
return npos;
}
size_t Inventory::findSlotByItem(itemid_t id, size_t begin, size_t end) {
size_t Inventory::findSlotByItem(
itemid_t id, size_t begin, size_t end, size_t minCount
) {
end = std::min(slots.size(), end);
for (size_t i = begin; i < end; i++) {
if (slots[i].getItemId() == id) {
const auto& stack = slots[i];
if (stack.getItemId() == id && stack.getCount() >= minCount) {
return i;
}
}

View File

@ -22,7 +22,9 @@ public:
ItemStack& getSlot(size_t index);
size_t findEmptySlot(size_t begin = 0, size_t end = -1) const;
size_t findSlotByItem(itemid_t id, size_t begin = 0, size_t end = -1);
size_t findSlotByItem(
itemid_t id, size_t begin = 0, size_t end = -1, size_t minCount = 1
);
inline size_t size() const {
return slots.size();

View File

@ -6,7 +6,7 @@
#include "data/dv.hpp"
#include "typedefs.hpp"
struct item_funcs_set {
struct ItemFuncsSet {
bool init : 1;
bool on_use : 1;
bool on_use_on_block : 1;
@ -43,7 +43,7 @@ struct ItemDef {
struct {
itemid_t id;
blockid_t placingBlock;
item_funcs_set funcsset {};
ItemFuncsSet funcsset {};
bool emissive = false;
} rt {};

View File

@ -38,7 +38,10 @@ void ChunksController::update(
int centerX = floordiv<CHUNK_W>(position.x);
int centerY = floordiv<CHUNK_D>(position.z);
generator->update(centerX, centerY, loadDistance);
if (player.isLoadingChunks()) {
/// FIXME: one generator for multiple players
generator->update(centerX, centerY, loadDistance);
}
int64_t mcstotal = 0;
@ -121,6 +124,12 @@ bool ChunksController::buildLights(const Player& player, const std::shared_ptr<C
}
void ChunksController::createChunk(const Player& player, int x, int z) const {
if (!player.isLoadingChunks()) {
if (auto chunk = level.chunks->fetch(x, z)) {
player.chunks->putChunk(chunk);
}
return;
}
auto chunk = level.chunks->create(x, z);
player.chunks->putChunk(chunk);
auto& chunkFlags = chunk->flags;

View File

@ -47,9 +47,7 @@ LevelController::LevelController(
);
chunks->update(16, 1, 0, *player);
if (player->chunks->get(
std::floor(position.x),
std::floor(position.y),
std::floor(position.z)
std::floor(position.x), 0, std::floor(position.z)
)) {
confirmed++;
}
@ -83,7 +81,7 @@ void LevelController::update(float delta, bool pause) {
playerTickClock.getPart()) {
const auto& position = player->getPosition();
if (!player->chunks->get(
if (player->chunks->get(
std::floor(position.x),
std::floor(position.y),
std::floor(position.z)

View File

@ -288,8 +288,6 @@ void PlayerController::updateKeyboard() {
input.jump = Events::active(BIND_MOVE_JUMP);
input.zoom = Events::active(BIND_CAM_ZOOM);
input.cameraMode = Events::jactive(BIND_CAM_MODE);
input.noclip = Events::jactive(BIND_PLAYER_NOCLIP);
input.flight = Events::jactive(BIND_PLAYER_FLIGHT);
}
void PlayerController::resetKeyboard() {
@ -341,28 +339,6 @@ static int determine_rotation(
return 0;
}
static void pick_block(
ContentIndices* indices,
const Block& block,
Player& player,
int x,
int y,
int z
) {
itemid_t id = block.rt.pickingItem;
auto inventory = player.getInventory();
size_t slotid = inventory->findSlotByItem(id, 0, 10);
if (slotid == Inventory::npos) {
slotid = player.getChosenSlot();
} else {
player.setChosenSlot(slotid);
}
ItemStack& stack = inventory->getSlot(slotid);
if (stack.getItemId() != id) {
stack.set(ItemStack(id, 1));
}
}
voxel* PlayerController::updateSelection(float maxDistance) {
auto indices = level.content->getIndices();
auto& chunks = *player.chunks;
@ -544,6 +520,7 @@ void PlayerController::updateInteraction(float delta) {
}
auto& target = indices->blocks.require(vox->id);
if (lclick) {
scripting::on_block_breaking(&player, target, iend);
if (player.isInstantDestruction() && target.breakable) {
blocksController.breakBlock(
&player, target, iend.x, iend.y, iend.z
@ -567,10 +544,6 @@ void PlayerController::updateInteraction(float delta) {
if (def && rclick) {
processRightClick(*def, target);
}
if (Events::jactive(BIND_PLAYER_PICK)) {
auto coord = selection.actualPosition;
pick_block(indices, target, player, coord.x, coord.y, coord.z);
}
}
Player* PlayerController::getPlayer() {

View File

@ -57,12 +57,7 @@ static fs::path get_writeable_path(lua::State* L) {
fs::path path = resolve_path(rawpath);
auto entryPoint = rawpath.substr(0, rawpath.find(':'));
if (writeable_entry_points.find(entryPoint) == writeable_entry_points.end()) {
if (lua::getglobal(L, "__vc_warning")) {
lua::pushstring(L, "writing to read-only entry point");
lua::pushstring(L, entryPoint);
lua::pushinteger(L, 1);
lua::call_nothrow(L, 3);
}
throw std::runtime_error("access denied");
}
return path;
}
@ -149,27 +144,6 @@ static int l_read_bytes(lua::State* L) {
);
}
static void read_bytes_from_table(
lua::State* L, int tableIndex, std::vector<ubyte>& bytes
) {
if (!lua::istable(L, tableIndex)) {
throw std::runtime_error("table expected");
} else {
size_t size = lua::objlen(L, tableIndex);
for (size_t i = 0; i < size; i++) {
lua::rawgeti(L, i + 1, tableIndex);
const int byte = lua::tointeger(L, -1);
lua::pop(L);
if (byte < 0 || byte > 255) {
throw std::runtime_error(
"invalid byte '" + std::to_string(byte) + "'"
);
}
bytes.push_back(byte);
}
}
}
static int l_write_bytes(lua::State* L) {
fs::path path = get_writeable_path(L);
@ -181,7 +155,7 @@ static int l_write_bytes(lua::State* L) {
}
std::vector<ubyte> bytes;
read_bytes_from_table(L, 2, bytes);
lua::read_bytes_from_table(L, 2, bytes);
return lua::pushboolean(
L, files::write_bytes(path, bytes.data(), bytes.size())
);
@ -223,7 +197,7 @@ static int l_list(lua::State* L) {
static int l_gzip_compress(lua::State* L) {
std::vector<ubyte> bytes;
read_bytes_from_table(L, 1, bytes);
lua::read_bytes_from_table(L, 1, bytes);
auto compressed_bytes = gzip::compress(bytes.data(), bytes.size());
int newTable = lua::gettop(L);
@ -237,7 +211,7 @@ static int l_gzip_compress(lua::State* L) {
static int l_gzip_decompress(lua::State* L) {
std::vector<ubyte> bytes;
read_bytes_from_table(L, 1, bytes);
lua::read_bytes_from_table(L, 1, bytes);
auto decompressed_bytes = gzip::decompress(bytes.data(), bytes.size());
int newTable = lua::gettop(L);
@ -264,6 +238,16 @@ static int l_read_combined_object(lua::State* L) {
return lua::pushvalue(L, engine->getResPaths()->readCombinedObject(path));
}
static int l_is_writeable(lua::State* L) {
std::string rawpath = lua::require_string(L, 1);
fs::path path = resolve_path(rawpath);
auto entryPoint = rawpath.substr(0, rawpath.find(':'));
if (writeable_entry_points.find(entryPoint) == writeable_entry_points.end()) {
return lua::pushboolean(L, false);
}
return lua::pushboolean(L, true);
}
const luaL_Reg filelib[] = {
{"exists", lua::wrap<l_exists>},
{"find", lua::wrap<l_find>},
@ -284,4 +268,5 @@ const luaL_Reg filelib[] = {
{"gzip_decompress", lua::wrap<l_gzip_decompress>},
{"read_combined_list", lua::wrap<l_read_combined_list>},
{"read_combined_object", lua::wrap<l_read_combined_object>},
{"is_writeable", lua::wrap<l_is_writeable>},
{NULL, NULL}};

View File

@ -13,15 +13,15 @@ static void validate_itemid(itemid_t id) {
}
}
static std::shared_ptr<Inventory> get_inventory(int64_t id) {
static Inventory& get_inventory(int64_t id) {
auto inv = level->inventories->get(id);
if (inv == nullptr) {
throw std::runtime_error("inventory not found: " + std::to_string(id));
}
return inv;
return *inv;
}
static std::shared_ptr<Inventory> get_inventory(int64_t id, int arg) {
static Inventory& get_inventory(int64_t id, int arg) {
auto inv = level->inventories->get(id);
if (inv == nullptr) {
throw std::runtime_error(
@ -29,62 +29,62 @@ static std::shared_ptr<Inventory> get_inventory(int64_t id, int arg) {
std::to_string(arg)
);
}
return inv;
return *inv;
}
static void validate_slotid(int slotid, Inventory* inv) {
if (static_cast<size_t>(slotid) >= inv->size()) {
static void validate_slotid(int slotid, const Inventory& inv) {
if (static_cast<size_t>(slotid) >= inv.size()) {
throw std::runtime_error(
"slot index is out of range [0..inventory.size(invid)]"
);
}
}
static int l_inventory_get(lua::State* L) {
static int l_get(lua::State* L) {
auto invid = lua::tointeger(L, 1);
auto slotid = lua::tointeger(L, 2);
auto inv = get_inventory(invid);
validate_slotid(slotid, inv.get());
const ItemStack& item = inv->getSlot(slotid);
validate_slotid(slotid, inv);
const ItemStack& item = inv.getSlot(slotid);
lua::pushinteger(L, item.getItemId());
lua::pushinteger(L, item.getCount());
return 2;
}
static int l_inventory_set(lua::State* L) {
static int l_set(lua::State* L) {
auto invid = lua::tointeger(L, 1);
auto slotid = lua::tointeger(L, 2);
auto itemid = lua::tointeger(L, 3);
auto count = lua::tointeger(L, 4);
validate_itemid(itemid);
auto inv = get_inventory(invid);
auto& inv = get_inventory(invid);
validate_slotid(slotid, inv.get());
ItemStack& item = inv->getSlot(slotid);
validate_slotid(slotid, inv);
ItemStack& item = inv.getSlot(slotid);
item.set(ItemStack(itemid, count));
return 0;
}
static int l_inventory_size(lua::State* L) {
static int l_size(lua::State* L) {
auto invid = lua::tointeger(L, 1);
auto inv = get_inventory(invid);
return lua::pushinteger(L, inv->size());
auto& inv = get_inventory(invid);
return lua::pushinteger(L, inv.size());
}
static int l_inventory_add(lua::State* L) {
static int l_add(lua::State* L) {
auto invid = lua::tointeger(L, 1);
auto itemid = lua::tointeger(L, 2);
auto count = lua::tointeger(L, 3);
validate_itemid(itemid);
auto inv = get_inventory(invid);
auto& inv = get_inventory(invid);
ItemStack item(itemid, count);
inv->move(item, indices);
inv.move(item, indices);
return lua::pushinteger(L, item.getCount());
}
static int l_inventory_get_block(lua::State* L) {
static int l_get_block(lua::State* L) {
auto x = lua::tointeger(L, 1);
auto y = lua::tointeger(L, 2);
auto z = lua::tointeger(L, 3);
@ -92,7 +92,7 @@ static int l_inventory_get_block(lua::State* L) {
return lua::pushinteger(L, id);
}
static int l_inventory_bind_block(lua::State* L) {
static int l_bind_block(lua::State* L) {
auto id = lua::tointeger(L, 1);
auto x = lua::tointeger(L, 2);
auto y = lua::tointeger(L, 3);
@ -101,7 +101,7 @@ static int l_inventory_bind_block(lua::State* L) {
return 0;
}
static int l_inventory_unbind_block(lua::State* L) {
static int l_unbind_block(lua::State* L) {
auto x = lua::tointeger(L, 1);
auto y = lua::tointeger(L, 2);
auto z = lua::tointeger(L, 3);
@ -109,7 +109,7 @@ static int l_inventory_unbind_block(lua::State* L) {
return 0;
}
static int l_inventory_create(lua::State* L) {
static int l_create(lua::State* L) {
auto invsize = lua::tointeger(L, 1);
auto inv = level->inventories->create(invsize);
if (inv == nullptr) {
@ -118,18 +118,13 @@ static int l_inventory_create(lua::State* L) {
return lua::pushinteger(L, inv->getId());
}
static int l_inventory_remove(lua::State* L) {
static int l_remove(lua::State* L) {
auto invid = lua::tointeger(L, 1);
auto inv = get_inventory(invid);
if (inv == nullptr) {
return 0;
}
level->inventories->remove(invid);
return 0;
}
static int l_inventory_clone(lua::State* L) {
static int l_clone(lua::State* L) {
auto id = lua::tointeger(L, 1);
auto clone = level->inventories->clone(id);
if (clone == nullptr) {
@ -138,54 +133,69 @@ static int l_inventory_clone(lua::State* L) {
return lua::pushinteger(L, clone->getId());
}
static int l_inventory_move(lua::State* L) {
static int l_move(lua::State* L) {
auto invAid = lua::tointeger(L, 1);
auto slotAid = lua::tointeger(L, 2);
auto invA = get_inventory(invAid, 1);
validate_slotid(slotAid, invA.get());
auto& invA = get_inventory(invAid, 1);
validate_slotid(slotAid, invA);
auto invBid = lua::tointeger(L, 3);
auto slotBid = lua::isnil(L, 4) ? -1 : lua::tointeger(L, 4);
auto invB = get_inventory(invBid, 3);
auto& slot = invA->getSlot(slotAid);
auto& invB = get_inventory(invBid, 3);
auto& slot = invA.getSlot(slotAid);
if (slotBid == -1) {
invB->move(slot, content->getIndices());
invB.move(slot, content->getIndices());
} else {
invB->move(slot, content->getIndices(), slotBid, slotBid + 1);
invB.move(slot, content->getIndices(), slotBid, slotBid + 1);
}
return 0;
}
static int l_inventory_move_range(lua::State* L) {
static int l_move_range(lua::State* L) {
auto invAid = lua::tointeger(L, 1);
auto slotAid = lua::tointeger(L, 2);
auto invA = get_inventory(invAid, 1);
validate_slotid(slotAid, invA.get());
auto& invA = get_inventory(invAid, 1);
validate_slotid(slotAid, invA);
auto invBid = lua::tointeger(L, 3);
auto slotBegin = lua::isnoneornil(L, 4) ? -1 : lua::tointeger(L, 4);
auto slotEnd = lua::isnoneornil(L, 5) ? -1 : lua::tointeger(L, 5) + 1;
auto invB = get_inventory(invBid, 3);
auto& slot = invA->getSlot(slotAid);
auto& slot = invA.getSlot(slotAid);
if (slotBegin == -1) {
invB->move(slot, content->getIndices());
invB.move(slot, content->getIndices());
} else {
invB->move(slot, content->getIndices(), slotBegin, slotEnd);
invB.move(slot, content->getIndices(), slotBegin, slotEnd);
}
return 0;
}
static int l_find_by_item(lua::State* L) {
auto invId = lua::tointeger(L, 1);
auto& inv = get_inventory(invId, 1);
integer_t blockid = lua::tointeger(L, 2);
integer_t begin = lua::isnumber(L, 3) ? lua::tointeger(L, 3) : 0;
integer_t end = lua::isnumber(L, 4) ? lua::tointeger(L, 4) : -1;
integer_t minCount = lua::isnumber(L, 5) ? lua::tointeger(L, 5) : blockid != 0;
size_t index = inv.findSlotByItem(blockid, begin, end, minCount);
if (index == Inventory::npos) {
return 0;
}
return lua::pushinteger(L, index);
}
const luaL_Reg inventorylib[] = {
{"get", lua::wrap<l_inventory_get>},
{"set", lua::wrap<l_inventory_set>},
{"size", lua::wrap<l_inventory_size>},
{"add", lua::wrap<l_inventory_add>},
{"move", lua::wrap<l_inventory_move>},
{"move_range", lua::wrap<l_inventory_move_range>},
{"get_block", lua::wrap<l_inventory_get_block>},
{"bind_block", lua::wrap<l_inventory_bind_block>},
{"unbind_block", lua::wrap<l_inventory_unbind_block>},
{"create", lua::wrap<l_inventory_create>},
{"remove", lua::wrap<l_inventory_remove>},
{"clone", lua::wrap<l_inventory_clone>},
{"get", lua::wrap<l_get>},
{"set", lua::wrap<l_set>},
{"size", lua::wrap<l_size>},
{"add", lua::wrap<l_add>},
{"move", lua::wrap<l_move>},
{"move_range", lua::wrap<l_move_range>},
{"find_by_item", lua::wrap<l_find_by_item>},
{"get_block", lua::wrap<l_get_block>},
{"bind_block", lua::wrap<l_bind_block>},
{"unbind_block", lua::wrap<l_unbind_block>},
{"create", lua::wrap<l_create>},
{"remove", lua::wrap<l_remove>},
{"clone", lua::wrap<l_clone>},
{NULL, NULL}};

View File

@ -101,6 +101,13 @@ static int l_get_inv(lua::State* L) {
return 2;
}
static int l_set_selected_slot(lua::State* L) {
if (auto player = get_player(L, 1)) {
player->setChosenSlot(lua::tointeger(L, 2) % 10);
}
return 0;
}
static int l_is_flight(lua::State* L) {
if (auto player = get_player(L, 1)) {
return lua::pushboolean(L, player->isFlight());
@ -157,6 +164,20 @@ static int l_set_instant_destruction(lua::State* L) {
return 0;
}
static int l_is_loading_chunks(lua::State* L) {
if (auto player = get_player(L, 1)) {
return lua::pushboolean(L, player->isLoadingChunks());
}
return 0;
}
static int l_set_loading_chunks(lua::State* L) {
if (auto player = get_player(L, 1)) {
player->setLoadingChunks(lua::toboolean(L, 2));
}
return 0;
}
static int l_get_selected_block(lua::State* L) {
if (auto player = get_player(L, 1)) {
if (player->selection.vox.id == BLOCK_VOID) {
@ -275,6 +296,9 @@ const luaL_Reg playerlib[] = {
{"set_infinite_items", lua::wrap<l_set_infinite_items>},
{"is_instant_destruction", lua::wrap<l_is_instant_destruction>},
{"set_instant_destruction", lua::wrap<l_set_instant_destruction>},
{"is_loading_chunks", lua::wrap<l_is_loading_chunks>},
{"set_loading_chunks", lua::wrap<l_set_loading_chunks>},
{"set_selected_slot", lua::wrap<l_set_selected_slot>},
{"get_selected_block", lua::wrap<l_get_selected_block>},
{"get_selected_entity", lua::wrap<l_get_selected_entity>},
{"set_spawnpoint", lua::wrap<l_set_spawnpoint>},

View File

@ -2,6 +2,7 @@
#include <typeindex>
#include <typeinfo>
#include <stdexcept>
#include <unordered_map>
#include "data/dv.hpp"
@ -698,4 +699,25 @@ namespace lua {
}
return def;
}
inline void read_bytes_from_table(
lua::State* L, int tableIndex, std::vector<ubyte>& bytes
) {
if (!lua::istable(L, tableIndex)) {
throw std::runtime_error("table expected");
} else {
size_t size = lua::objlen(L, tableIndex);
for (size_t i = 0; i < size; i++) {
lua::rawgeti(L, i + 1, tableIndex);
const int byte = lua::tointeger(L, -1);
lua::pop(L);
if (byte < 0 || byte > 255) {
throw std::runtime_error(
"invalid byte '" + std::to_string(byte) + "'"
);
}
bytes.push_back(byte);
}
}
}
}

View File

@ -2,6 +2,7 @@
#include <sstream>
#include "util/listutil.hpp"
#include "../lua_util.hpp"
using namespace lua;
@ -18,8 +19,16 @@ LuaBytearray::~LuaBytearray() {
static int l_append(lua::State* L) {
if (auto buffer = touserdata<LuaBytearray>(L, 1)) {
auto value = tointeger(L, 2);
buffer->data().push_back(static_cast<ubyte>(value));
if (lua::isnumber(L, 2)) {
auto value = tointeger(L, 2);
buffer->data().push_back(static_cast<ubyte>(value));
} else if (lua::istable(L, 2)) {
lua::read_bytes_from_table(L, 2, buffer->data());
} else if (auto extension = lua::touserdata<LuaBytearray>(L, 2)) {
util::concat(buffer->data(), extension->data());
} else {
throw std::runtime_error("integer/table/Bytearray expected");
}
}
return 0;
}
@ -34,8 +43,19 @@ static int l_insert(lua::State* L) {
if (static_cast<size_t>(index) > data.size()) {
return 0;
}
auto value = tointeger(L, 3);
data.insert(data.begin() + index, static_cast<ubyte>(value));
if (lua::isnumber(L, 3)) {
auto value = tointeger(L, 3);
data.insert(data.begin() + index, static_cast<ubyte>(value));
} else if (lua::istable(L, 3)) {
std::vector<ubyte> temp;
lua::read_bytes_from_table(L, 3, temp);
data.insert(data.begin() + index, temp.begin(), temp.end());
} else if (auto extension = lua::touserdata<LuaBytearray>(L, 3)) {
const std::vector<ubyte>& src = extension->data();
data.insert(data.begin() + index, src.begin(), src.end());
} else {
throw std::runtime_error("integer/table/Bytearray expected");
}
return 0;
}

View File

@ -336,16 +336,24 @@ void scripting::random_update_block(const Block& block, const glm::ivec3& pos) {
});
}
void scripting::on_block_placed(
Player* player, const Block& block, const glm::ivec3& pos
/// TODO: replace template with index
template<bool WorldFuncsSet::*worldfunc>
static bool on_block_common(
const std::string& suffix,
bool blockfunc,
Player* player,
const Block& block,
const glm::ivec3& pos
) {
if (block.rt.funcsset.onplaced) {
std::string name = block.name + ".placed";
lua::emit_event(lua::get_main_state(), name, [pos, player](auto L) {
lua::pushivec_stack(L, pos);
lua::pushinteger(L, player ? player->getId() : -1);
return 4;
});
bool result = false;
if (blockfunc) {
std::string name = block.name + "." + suffix;
result =
lua::emit_event(lua::get_main_state(), name, [pos, player](auto L) {
lua::pushivec_stack(L, pos);
lua::pushinteger(L, player->getId());
return 4;
});
}
auto args = [&](lua::State* L) {
lua::pushinteger(L, block.rt.id);
@ -354,93 +362,53 @@ void scripting::on_block_placed(
return 5;
};
for (auto& [packid, pack] : content->getPacks()) {
if (pack->worldfuncsset.onblockplaced) {
if (pack->worldfuncsset.*worldfunc) {
lua::emit_event(
lua::get_main_state(), packid + ":.blockplaced", args
lua::get_main_state(), packid + ":.block" + suffix, args
);
}
}
return result;
}
void scripting::on_block_placed(
Player* player, const Block& block, const glm::ivec3& pos
) {
on_block_common<&WorldFuncsSet::onblockplaced>(
"placed", block.rt.funcsset.onplaced, player, block, pos
);
}
void scripting::on_block_replaced(
Player* player, const Block& block, const glm::ivec3& pos
) {
if (block.rt.funcsset.onreplaced) {
std::string name = block.name + ".replaced";
lua::emit_event(lua::get_main_state(), name, [pos, player](auto L) {
lua::pushivec_stack(L, pos);
lua::pushinteger(L, player ? player->getId() : -1);
return 4;
});
}
auto args = [&](lua::State* L) {
lua::pushinteger(L, block.rt.id);
lua::pushivec_stack(L, pos);
lua::pushinteger(L, player ? player->getId() : -1);
return 5;
};
for (auto& [packid, pack] : content->getPacks()) {
if (pack->worldfuncsset.onblockreplaced) {
lua::emit_event(
lua::get_main_state(), packid + ":.blockreplaced", args
);
}
}
on_block_common<&WorldFuncsSet::onblockreplaced>(
"replaced", block.rt.funcsset.onreplaced, player, block, pos
);
}
void scripting::on_block_breaking(
Player* player, const Block& block, const glm::ivec3& pos
) {
on_block_common<&WorldFuncsSet::onblockbreaking>(
"breaking", block.rt.funcsset.onbreaking, player, block, pos
);
}
void scripting::on_block_broken(
Player* player, const Block& block, const glm::ivec3& pos
) {
if (block.rt.funcsset.onbroken) {
std::string name = block.name + ".broken";
lua::emit_event(
lua::get_main_state(),
name,
[pos, player](auto L) {
lua::pushivec_stack(L, pos);
lua::pushinteger(L, player ? player->getId() : -1);
return 4;
}
);
}
auto args = [&](lua::State* L) {
lua::pushinteger(L, block.rt.id);
lua::pushivec_stack(L, pos);
lua::pushinteger(L, player ? player->getId() : -1);
return 5;
};
for (auto& [packid, pack] : content->getPacks()) {
if (pack->worldfuncsset.onblockbroken) {
lua::emit_event(
lua::get_main_state(), packid + ":.blockbroken", args
);
}
}
on_block_common<&WorldFuncsSet::onblockbroken>(
"broken", block.rt.funcsset.onbroken, player, block, pos
);
}
bool scripting::on_block_interact(
Player* player, const Block& block, const glm::ivec3& pos
) {
std::string name = block.name + ".interact";
auto result = lua::emit_event(lua::get_main_state(), name, [pos, player](auto L) {
lua::pushivec_stack(L, pos);
lua::pushinteger(L, player->getId());
return 4;
});
auto args = [&](lua::State* L) {
lua::pushinteger(L, block.rt.id);
lua::pushivec_stack(L, pos);
lua::pushinteger(L, player ? player->getId() : -1);
return 5;
};
for (auto& [packid, pack] : content->getPacks()) {
if (pack->worldfuncsset.onblockinteract) {
lua::emit_event(
lua::get_main_state(), packid + ":.blockinteract", args
);
}
}
return result;
return on_block_common<&WorldFuncsSet::onblockinteract>(
"interact", block.rt.funcsset.oninteract, player, block, pos
);
}
void scripting::on_player_tick(Player* player, int tps) {
@ -599,7 +567,7 @@ static void process_entity_callback(
static void process_entity_callback(
const Entity& entity,
const std::string& name,
bool entity_funcs_set::*flag,
bool EntityFuncsSet::*flag,
std::function<int(lua::State*)> args
) {
const auto& script = entity.getScripting();
@ -612,7 +580,7 @@ static void process_entity_callback(
void scripting::on_entity_despawn(const Entity& entity) {
process_entity_callback(
entity, "on_despawn", &entity_funcs_set::on_despawn, nullptr
entity, "on_despawn", &EntityFuncsSet::on_despawn, nullptr
);
auto L = lua::get_main_state();
lua::get_from(L, "stdcomp", "remove_Entity", true);
@ -624,20 +592,20 @@ void scripting::on_entity_grounded(const Entity& entity, float force) {
process_entity_callback(
entity,
"on_grounded",
&entity_funcs_set::on_grounded,
&EntityFuncsSet::on_grounded,
[force](auto L) { return lua::pushnumber(L, force); }
);
}
void scripting::on_entity_fall(const Entity& entity) {
process_entity_callback(
entity, "on_fall", &entity_funcs_set::on_fall, nullptr
entity, "on_fall", &EntityFuncsSet::on_fall, nullptr
);
}
void scripting::on_entity_save(const Entity& entity) {
process_entity_callback(
entity, "on_save", &entity_funcs_set::on_save, nullptr
entity, "on_save", &EntityFuncsSet::on_save, nullptr
);
}
@ -647,7 +615,7 @@ void scripting::on_sensor_enter(
process_entity_callback(
entity,
"on_sensor_enter",
&entity_funcs_set::on_sensor_enter,
&EntityFuncsSet::on_sensor_enter,
[index, oid](auto L) {
lua::pushinteger(L, index);
lua::pushinteger(L, oid);
@ -662,7 +630,7 @@ void scripting::on_sensor_exit(
process_entity_callback(
entity,
"on_sensor_exit",
&entity_funcs_set::on_sensor_exit,
&EntityFuncsSet::on_sensor_exit,
[index, oid](auto L) {
lua::pushinteger(L, index);
lua::pushinteger(L, oid);
@ -675,7 +643,7 @@ void scripting::on_aim_on(const Entity& entity, Player* player) {
process_entity_callback(
entity,
"on_aim_on",
&entity_funcs_set::on_aim_on,
&EntityFuncsSet::on_aim_on,
[player](auto L) { return lua::pushinteger(L, player->getId()); }
);
}
@ -684,7 +652,7 @@ void scripting::on_aim_off(const Entity& entity, Player* player) {
process_entity_callback(
entity,
"on_aim_off",
&entity_funcs_set::on_aim_off,
&EntityFuncsSet::on_aim_off,
[player](auto L) { return lua::pushinteger(L, player->getId()); }
);
}
@ -695,7 +663,7 @@ void scripting::on_attacked(
process_entity_callback(
entity,
"on_attacked",
&entity_funcs_set::on_attacked,
&EntityFuncsSet::on_attacked,
[player, attacker](auto L) {
lua::pushinteger(L, attacker);
lua::pushinteger(L, player->getId());
@ -708,7 +676,7 @@ void scripting::on_entity_used(const Entity& entity, Player* player) {
process_entity_callback(
entity,
"on_used",
&entity_funcs_set::on_used,
&EntityFuncsSet::on_used,
[player](auto L) { return lua::pushinteger(L, player->getId()); }
);
}
@ -796,7 +764,7 @@ void scripting::load_content_script(
const std::string& prefix,
const fs::path& file,
const std::string& fileName,
block_funcs_set& funcsset
BlockFuncsSet& funcsset
) {
int env = *senv;
lua::pop(lua::get_main_state(), load_script(env, "block", file, fileName));
@ -804,6 +772,8 @@ void scripting::load_content_script(
funcsset.update = register_event(env, "on_update", prefix + ".update");
funcsset.randupdate =
register_event(env, "on_random_update", prefix + ".randupdate");
funcsset.onbreaking =
register_event(env, "on_breaking", prefix + ".breaking");
funcsset.onbroken = register_event(env, "on_broken", prefix + ".broken");
funcsset.onplaced = register_event(env, "on_placed", prefix + ".placed");
funcsset.onreplaced =
@ -819,7 +789,7 @@ void scripting::load_content_script(
const std::string& prefix,
const fs::path& file,
const std::string& fileName,
item_funcs_set& funcsset
ItemFuncsSet& funcsset
) {
int env = *senv;
lua::pop(lua::get_main_state(), load_script(env, "item", file, fileName));
@ -846,7 +816,7 @@ void scripting::load_world_script(
const std::string& prefix,
const fs::path& file,
const std::string& fileName,
world_funcs_set& funcsset
WorldFuncsSet& funcsset
) {
int env = *senv;
lua::pop(lua::get_main_state(), load_script(env, "world", file, fileName));
@ -857,6 +827,8 @@ void scripting::load_world_script(
register_event(env, "on_world_quit", prefix + ":.worldquit");
funcsset.onblockplaced =
register_event(env, "on_block_placed", prefix + ":.blockplaced");
funcsset.onblockbreaking =
register_event(env, "on_block_breaking", prefix + ":.blockbreaking");
funcsset.onblockbroken =
register_event(env, "on_block_broken", prefix + ":.blockbroken");
funcsset.onblockreplaced =

View File

@ -21,9 +21,9 @@ class Player;
struct ItemDef;
class Inventory;
class UiDocument;
struct block_funcs_set;
struct item_funcs_set;
struct world_funcs_set;
struct BlockFuncsSet;
struct ItemFuncsSet;
struct WorldFuncsSet;
struct UserComponent;
struct uidocscript;
class BlocksController;
@ -77,6 +77,9 @@ namespace scripting {
void on_block_replaced(
Player* player, const Block& block, const glm::ivec3& pos
);
void on_block_breaking(
Player* player, const Block& block, const glm::ivec3& pos
);
void on_block_broken(
Player* player, const Block& block, const glm::ivec3& pos
);
@ -141,7 +144,7 @@ namespace scripting {
const std::string& prefix,
const std::filesystem::path& file,
const std::string& fileName,
block_funcs_set& funcsset
BlockFuncsSet& funcsset
);
/// @brief Load script associated with an Item
@ -155,7 +158,7 @@ namespace scripting {
const std::string& prefix,
const std::filesystem::path& file,
const std::string& fileName,
item_funcs_set& funcsset
ItemFuncsSet& funcsset
);
/// @brief Load component script
@ -184,7 +187,7 @@ namespace scripting {
const std::string& packid,
const std::filesystem::path& file,
const std::string& fileName,
world_funcs_set& funcsset
WorldFuncsSet& funcsset
);
/// @brief Load script associated with an UiDocument

30
src/maths/UVFace.hpp Normal file
View File

@ -0,0 +1,30 @@
#pragma once
#include <array>
#include <glm/glm.hpp>
#include "UVRegion.hpp"
struct UVFace {
std::array<glm::vec2, 4> points;
UVFace(const UVRegion& region) {
points[0] = {region.u1, region.v1};
points[1] = {region.u2, region.v1};
points[2] = {region.u2, region.v2};
points[3] = {region.u1, region.v2};
}
template<int n>
inline void rotate(int times) {
int times = n % 4;
if (times < 0) {
times += 4;
}
std::array<glm::vec2, 4> temp = points;
points[0] = temp[times];
points[1] = temp[(times + 1) % 4];
points[2] = temp[(times + 2) % 4];
points[3] = temp[(times + 3) % 4];
}
};

View File

@ -162,7 +162,7 @@ entityid_t Entities::spawn(
for (auto& componentName : def.components) {
auto component = std::make_unique<UserComponent>(
componentName, entity_funcs_set {}, nullptr
componentName, EntityFuncsSet {}, nullptr
);
scripting.components.emplace_back(std::move(component));
}

View File

@ -14,7 +14,7 @@
#include <glm/gtx/norm.hpp>
#include <unordered_map>
struct entity_funcs_set {
struct EntityFuncsSet {
bool init;
bool on_despawn;
bool on_grounded;
@ -77,11 +77,11 @@ struct Rigidbody {
struct UserComponent {
std::string name;
entity_funcs_set funcsset;
EntityFuncsSet funcsset;
scriptenv env;
UserComponent(
const std::string& name, entity_funcs_set funcsset, scriptenv env
const std::string& name, EntityFuncsSet funcsset, scriptenv env
)
: name(name), funcsset(funcsset), env(env) {
}

View File

@ -135,18 +135,7 @@ void Player::updateInput(PlayerInput& input, float delta) {
hitbox->velocity.y = JUMP_FORCE;
}
if ((input.flight && !noclip) || (input.noclip && flight == noclip)) {
flight = !flight;
if (flight) {
hitbox->velocity.y += 1.0f;
}
}
hitbox->type = noclip ? BodyType::KINEMATIC : BodyType::DYNAMIC;
if (input.noclip) {
noclip = !noclip;
}
input.noclip = false;
input.flight = false;
}
void Player::updateSelectedEntity() {
@ -262,6 +251,14 @@ void Player::setInstantDestruction(bool flag) {
instantDestruction = flag;
}
bool Player::isLoadingChunks() const {
return loadingChunks;
}
void Player::setLoadingChunks(bool flag) {
loadingChunks = flag;
}
entityid_t Player::getEntity() const {
return eid;
}
@ -308,6 +305,7 @@ dv::value Player::serialize() const {
root["noclip"] = noclip;
root["infinite-items"] = infiniteItems;
root["instant-destruction"] = instantDestruction;
root["loading-chunks"] = loadingChunks;
root["chosen-slot"] = chosenSlot;
root["entity"] = eid;
root["inventory"] = inventory->serialize();
@ -340,7 +338,8 @@ void Player::deserialize(const dv::value& src) {
noclip = src["noclip"].asBoolean();
src.at("infinite-items").get(infiniteItems);
src.at("instant-destruction").get(instantDestruction);
src.at("loading-chunks").get(loadingChunks);
setChosenSlot(src["chosen-slot"].asInteger());
eid = src["entity"].asNumber();

View File

@ -27,8 +27,6 @@ struct PlayerInput {
bool shift : 1;
bool cheat : 1;
bool jump : 1;
bool noclip : 1;
bool flight : 1;
};
struct CursorSelection {
@ -53,13 +51,13 @@ class Player : public Serializable {
bool noclip = false;
bool infiniteItems = true;
bool instantDestruction = true;
bool loadingChunks = true;
entityid_t eid;
entityid_t selectedEid = 0;
public:
std::unique_ptr<Chunks> chunks; // not in use yet
std::unique_ptr<Chunks> chunks;
std::shared_ptr<Camera> fpCamera, spCamera, tpCamera;
std::shared_ptr<Camera> currentCamera;
bool debug = false;
std::shared_ptr<Camera> currentCamera;;
glm::vec3 cam {};
CursorSelection selection {};
@ -99,6 +97,9 @@ public:
bool isInstantDestruction() const;
void setInstantDestruction(bool flag);
bool isLoadingChunks() const;
void setLoadingChunks(bool flag);
entityid_t getEntity() const;
void setEntity(entityid_t eid);

View File

@ -43,6 +43,9 @@ dv::value ParticlesPreset::serialize() const {
root["explosion"] = dv::to_value(explosion);
root["size"] = dv::to_value(size);
root["size_spread"] = sizeSpread;
root["angle_spread"] = angleSpread;
root["min_angular_vel"] = minAngularVelocity;
root["max_angular_vel"] = maxAngularVelocity;
root["spawn_spread"] = dv::to_value(size);
root["spawn_shape"] = to_string(spawnShape);
root["random_sub_uv"] = randomSubUV;
@ -58,6 +61,9 @@ void ParticlesPreset::deserialize(const dv::value& src) {
src.at("spawn_interval").get(spawnInterval);
src.at("lifetime").get(lifetime);
src.at("lifetime_spread").get(lifetimeSpread);
src.at("angle_spread").get(angleSpread);
src.at("min_angular_vel").get(minAngularVelocity);
src.at("max_angular_vel").get(maxAngularVelocity);
src.at("random_sub_uv").get(randomSubUV);
if (src.has("velocity")) {
dv::get_vec(src["velocity"], velocity);

View File

@ -27,7 +27,7 @@ struct ParticlesPreset : public Serializable {
/// @brief Use global up vector instead of camera-dependent one
bool globalUpVector = false;
/// @brief Max distance of actually spawning particles.
float maxDistance = 16.0f;
float maxDistance = 32.0f;
/// @brief Particles spawn interval
float spawnInterval = 0.1f;
/// @brief Particle life time
@ -44,6 +44,12 @@ struct ParticlesPreset : public Serializable {
glm::vec3 size {0.1f};
/// @brief Particles size spread
float sizeSpread = 0.2f;
/// @brief Random initial angle spread
float angleSpread = 0.0f;
/// @brief Minimum angular velocity
float minAngularVelocity = 0.0f;
/// @brief Maximum angular velocity
float maxAngularVelocity = 0.0f;
/// @brief Spawn spread shape
ParticleSpawnShape spawnShape = BALL;
/// @brief Spawn spread

View File

@ -63,16 +63,24 @@ struct GraphicsSettings {
NumberSetting gamma {1.0f, 0.4f, 1.0f};
/// @brief Enable blocks backlight to prevent complete darkness
FlagSetting backlight {true};
/// @brief Disable culling with 'optional' mode
FlagSetting denseRender {true};
/// @brief Enable chunks frustum culling
FlagSetting frustumCulling {true};
/// @brief Skybox texture face resolution
IntegerSetting skyboxResolution {64 + 32, 64, 128};
/// @brief Chunk renderer vertices buffer capacity
IntegerSetting chunkMaxVertices {200'000, 0, 4'000'000};
/// @brief Chunk renderer vertices buffer capacity in dense render mode
IntegerSetting chunkMaxVerticesDense {800'000, 0, 8'000'000};
/// @brief Limit of chunk renderers count
IntegerSetting chunkMaxRenderers {6, -4, 32};
};
struct DebugSettings {
/// @brief Turns off chunks saving/loading
FlagSetting generatorTestMode {false};
/// @brief Write lights cache
FlagSetting doWriteLights {true};
};

View File

@ -49,6 +49,30 @@ std::optional<BlockModel> BlockModel_from(std::string_view str) {
return std::nullopt;
}
std::string to_string(CullingMode mode) {
switch (mode) {
case CullingMode::DEFAULT:
return "default";
case CullingMode::OPTIONAL:
return "optional";
case CullingMode::DISABLED:
return "disabled";
default:
return "unknown";
}
}
std::optional<CullingMode> CullingMode_from(std::string_view str) {
if (str == "default") {
return CullingMode::DEFAULT;
} else if (str == "optional") {
return CullingMode::OPTIONAL;
} else if (str == "disabled") {
return CullingMode::DISABLED;
}
return std::nullopt;
}
CoordSystem::CoordSystem(glm::ivec3 axisX, glm::ivec3 axisY, glm::ivec3 axisZ)
: axisX(axisX), axisY(axisY), axisZ(axisZ) {
fix = glm::ivec3(0);

View File

@ -34,10 +34,11 @@ inline constexpr size_t MAX_USER_BLOCK_FIELDS_SIZE = 240;
inline std::string DEFAULT_MATERIAL = "base:stone";
struct block_funcs_set {
struct BlockFuncsSet {
bool init : 1;
bool update : 1;
bool onplaced : 1;
bool onbreaking : 1;
bool onbroken : 1;
bool onreplaced : 1;
bool oninteract : 1;
@ -97,6 +98,15 @@ enum class BlockModel {
std::string to_string(BlockModel model);
std::optional<BlockModel> BlockModel_from(std::string_view str);
enum class CullingMode {
DEFAULT,
OPTIONAL,
DISABLED,
};
std::string to_string(CullingMode mode);
std::optional<CullingMode> CullingMode_from(std::string_view str);
using BoxModel = AABB;
/// @brief Common kit of block properties applied to groups of blocks
@ -142,6 +152,9 @@ public:
std::string modelName = "";
/// @brief Culling mode
CullingMode culling = CullingMode::DEFAULT;
/// @brief Does the block passing lights into itself
bool lightPassing = false;
@ -229,7 +242,7 @@ public:
std::vector<AABB> hitboxes[BlockRotProfile::MAX_COUNT];
/// @brief set of block callbacks flags
block_funcs_set funcsset {};
BlockFuncsSet funcsset {};
/// @brief picking item integer id
itemid_t pickingItem = 0;