diff --git a/doc/en/block-properties.md b/doc/en/block-properties.md
index 72a4b4c48..f3137dd60 100644
--- a/doc/en/block-properties.md
+++ b/doc/en/block-properties.md
@@ -241,6 +241,15 @@ Number of block inventory slots. Default - 0 (no inventory).
Array of three integers. Default value is `[1, 1, 1]`.
+### *grounding-behaviour*
+
+Defines the behavior of the extended grounded block. Available behavior options:
+
+- "partial" - at least one segment must be grounded (default)
+- "complete" - all segments must be grounded
+- "origin" - origin segment must be grounded
+
+
## Block fields
Block fields allow you to write more data unique to a specified voxel than the user bits allow.
diff --git a/doc/en/main-page.md b/doc/en/main-page.md
index 57a5b25d8..5bb2d8f2d 100644
--- a/doc/en/main-page.md
+++ b/doc/en/main-page.md
@@ -21,5 +21,6 @@ Documentation for 0.31.
- [Rigging](rigging.md)
- [Scripting](scripting.md)
- [Text styles](text-styles.md)
+- [VCM models format](vcm.md)
- [World generator engine](world-generator.md)
- [XML UI building](xml-ui-layouts.md)
diff --git a/doc/en/scripting/builtins/libblock.md b/doc/en/scripting/builtins/libblock.md
index b2528c364..db407e92a 100644
--- a/doc/en/scripting/builtins/libblock.md
+++ b/doc/en/scripting/builtins/libblock.md
@@ -67,7 +67,7 @@ block.get_picking_item(id: int) -> int
block.get_variant(x: int, y: int, z: int) -> int
-- Sets the block variant by index
-block.set_variant(x: int, y: int, z: int, index: int) -> int
+block.set_variant(x: int, y: int, z: int, index: int)
-- Checks if an block has specified tag
block.has_tag(id: int, tag: str) -> bool
diff --git a/doc/en/vcm.md b/doc/en/vcm.md
new file mode 100644
index 000000000..d384bb7d4
--- /dev/null
+++ b/doc/en/vcm.md
@@ -0,0 +1,101 @@
+# VCM
+
+A text format for describing 3D models, replacing the deprecated `model-primitives` property.
+
+Syntax:
+
+```
+@primitive attribute (value1, value2,...) attribute2 "value3" {
+ @inner_primitive ...
+}
+```
+
+Same structure in XML:
+
+```xml
+
+
+
+```
+
+Unlike XML, the root can contain multiple elements.
+
+Currently, there are three types of primitives: `tri`, `box`, `rect` and `part` (describes a part of a primitive, such as the side of a cube).
+
+> [!NOTE]
+> By default, texture coordinates are determined by the dimensions of the primitives.
+> For sizes greater than 1.0, the result is undefined and may change in future updates.
+> Use `region` or `region-scale` for manual adjustments.
+
+## Primitives
+
+### `tri` Properties
+
+This primitive describes a triangle.
+
+- `a` - Point A. Example: `a (0,0,0)`
+- `b` - Point B. Example: `b (1,0,0)`
+- `c` - Point C. Example: `c (1,1,0)`
+- `uv` - Normalized texture coordinates of the points (6 numbers, 2 per point). Example: `uv (0,0,1,0,1)`
+- `texture` - The texture to display. Default: `$0`. Example: `texture "blocks:sand"`
+- `region` - UV region within the selected texture, defined by the positions of opposite corners of a square of normalized texture coordinates. Example: `region (0,0,1,1)`
+- `region-scale` - multiplier vector for the UV region. Example: `region-scale (0.5,1)`
+- `shading` - determines whether shading is enabled on the primitive. Example: `shading off`
+
+By default, texture coordinates are not affected by shape or size, unlike other primitives.
+
+### `rect` Properties
+
+This primitive describes a parallelogram using the right and up vectors.
+
+- `from` - the origin of the primitive. Example: `from (0,0.5,0.125)`
+- `right` - the X vector, which also determines the width of the primitive. Example: `right (1,0,0)`
+- `up` - the Y vector, which also determines the primitive's height. Example: `up (0,1,0)`
+- `texture` - the texture to display. Default: `$0`. Example: `texture "blocks:sand"`
+- `region` - the UV region within the selected texture, defined by the positions of opposite corners. Example: `region (0,0,1,1)`
+- `region-scale` - the multiplier vector for the automatically selected UV region. Example: `region-scale (0.5,1)`
+- `shading` - determines whether shading is enabled on the primitive. Example: `shading off`
+- `flip` - flips the UV region horizontally or vertically. Example: `flip v`
+
+### `box` Properties
+
+This primitive describes a rectangular parallelepiped.
+
+- `from` - the primitive's origin. Example: `from (0,0,0)`
+- `to` - the point opposite the origin. Example: `to (1,1,1)`
+- `origin` - the point relative to which the rotation will be applied. Default: the center of the primitive. Example: `origin (0.5,0.5,0.5)`
+- `rotate` - rotation around the (x,y,z) axes in degrees, or a quaternion (x,y,z,w). Example: `rotate (45,0,0)` or `rotate (0.3826834, 0, 0, 0.9238795)`
+- `texture` - the default texture to display for all sides.
+- `shading` determines whether shading is enabled on the primitive. Example: `shading off`
+- `delete` deletes sides by name (top, bottom, east, west, north, south)
+
+### `part` properties (nested in `box`)
+
+This primitive describes the properties of the sides of a rectangular parallelepiped with the specified tags (top/bottom/north/south/east/west).
+
+- `tags` - determines which side the properties will be applied to. Example: `tags (top,bottom)`
+- `texture` - the texture to display. Default: `$side_index`.
+- `region` - the UV region within the selected texture, defined by the positions of opposite corners. Example: `region (0,0,1,1)`
+- `region-scale` is a multiplier vector for the automatically selected UV region. Example: `region-scale (0.5,1)`
+
+## Skeleton
+
+VCM can describe the [skeleton](rigging.md) of a model. The `@bone` element is used for this.
+
+There are two types of bones:
+- unnamed - used only for transformations on inner primitives and not included in the final skeleton.
+- named - will be included in the final skeleton. The name is specified via the `name` attribute.
+
+The main bone (`root`) is created automatically for the entire model.
+If bones are explicitly specified, the model will be divided into several parts named as `model_name.bone_name`.
+Since the model is an asset and has no prefix, the loaded skeleton receives the model name, also without a prefix.
+The loaded skeleton can be used in the [entity](entity-properties.md#skeleton-name) via the `skeleton-name` property.
+
+In the `pack/preload.json` list, if `"squash": true` is specified, the vcm will be loaded as a single model, without a skeleton.
+
+### `bone` Properties
+
+- `name` - The bone name. If omitted, the bone will be "dissolved" and will not be included in the final skeleton, but transformations will be applied.
+- `move` - Moves nested elements.
+- `scale` - Scales nested elements. Applied before moving.
+- `rotate` - Rotates nested elements around the (x, y, z) axes in degrees, or as a quaternion (x, y, z, w). Example: `rotate (45,0,0)` or `rotate (0.3826834, 0, 0, 0.9238795)`
diff --git a/doc/ru/block-properties.md b/doc/ru/block-properties.md
index 97e8fedc6..50bd7f023 100644
--- a/doc/ru/block-properties.md
+++ b/doc/ru/block-properties.md
@@ -247,6 +247,14 @@
Массив из трех целых чисел. Значение по-умолчанию - `[1, 1, 1]`.
+### Поведение приземленности - *grounding-behaviour*
+
+Определяет поведение расширенного приземленного блока. Доступные варианты поведения:
+
+- "partial" - как минимум один сегмент блока должен быть приземленным (по-умолчанию)
+- "complete" - все сегменты блока должны быть приземленными
+- "origin" - главный сегмент должен быть приземленным
+
## Поля блока
Поля блоков позволяет записывать больше уникальных для конкретного блока данных, чем это позволяют пользовательские биты.
diff --git a/doc/ru/main-page.md b/doc/ru/main-page.md
index c1773d5d1..517dcdfad 100644
--- a/doc/ru/main-page.md
+++ b/doc/ru/main-page.md
@@ -22,5 +22,5 @@
- [Свойства сущностей](entity-properties.md)
- [Скриптинг](scripting.md)
- [Стили текста](text-styles.md)
-- [Частицы](particles.md)
- [Формат моделей VCM](vcm.md)
+- [Частицы](particles.md)
diff --git a/doc/ru/scripting/builtins/libblock.md b/doc/ru/scripting/builtins/libblock.md
index a82d2ff66..b7af3e462 100644
--- a/doc/ru/scripting/builtins/libblock.md
+++ b/doc/ru/scripting/builtins/libblock.md
@@ -46,7 +46,7 @@ block.destruct(x: int, y: int, z: int, playerid: int)
block.get_variant(x: int, y: int, z: int) -> int
-- Устанавливает вариант блока по индексу
-block.set_variant(x: int, y: int, z: int, index: int) -> int
+block.set_variant(x: int, y: int, z: int, index: int)
```
> [!WARNING]
diff --git a/src/assets/Assets.cpp b/src/assets/Assets.cpp
index 993e7360c..3e0776747 100644
--- a/src/assets/Assets.cpp
+++ b/src/assets/Assets.cpp
@@ -1,6 +1,19 @@
#include "Assets.hpp"
-Assets::~Assets() = default;
+Assets::Assets(util::ObjectsKeeper* vault) : vault(vault) {
+}
+
+Assets::~Assets() {
+ if (vault == nullptr) {
+ return;
+ }
+ for (auto& [_, map] : assets) {
+ for (auto& [__, asset] : map) {
+ vault->keepAlive(std::move(asset));
+ }
+ map.clear();
+ }
+}
const std::vector& Assets::getAnimations() {
return animations;
diff --git a/src/assets/Assets.hpp b/src/assets/Assets.hpp
index ea0753aec..b9faa877f 100644
--- a/src/assets/Assets.hpp
+++ b/src/assets/Assets.hpp
@@ -12,6 +12,7 @@
#include
#include "util/stringutil.hpp"
+#include "util/ObjectsKeeper.hpp"
#include "graphics/core/TextureAnimation.hpp"
class Assets;
@@ -64,13 +65,14 @@ namespace assetload {
}
class Assets {
+ util::ObjectsKeeper* vault;
std::vector animations;
using assets_map = std::unordered_map>;
std::unordered_map assets;
std::vector setupFuncs;
public:
- Assets() = default;
+ Assets(util::ObjectsKeeper* vault);
Assets(const Assets&) = delete;
~Assets();
@@ -79,12 +81,20 @@ public:
template
void store(std::unique_ptr asset, const std::string& name) {
- assets[typeid(T)][name].reset(asset.release());
+ auto& dst = assets[typeid(T)][name];
+ if (vault != nullptr && dst != nullptr) {
+ vault->keepAlive(std::move(dst));
+ }
+ dst.reset(asset.release());
}
template
void store(std::shared_ptr asset, const std::string& name) {
- assets[typeid(T)][name] = std::move(asset);
+ auto& dst = assets[typeid(T)][name];
+ if (vault != nullptr && dst != nullptr) {
+ vault->keepAlive(std::move(dst));
+ }
+ dst = std::move(asset);
}
template
diff --git a/src/constants.hpp b/src/constants.hpp
index 501c5f62e..3ec66c259 100644
--- a/src/constants.hpp
+++ b/src/constants.hpp
@@ -33,6 +33,8 @@ inline constexpr int CHUNK_W = 16;
inline constexpr int CHUNK_H = 256;
inline constexpr int CHUNK_D = 16;
+inline constexpr int EXTENDED_BLOCK_LIMIT = CHUNK_W; // must not be greater than chunk width and depth
+
inline constexpr uint VOXEL_USER_BITS = 8;
inline constexpr uint VOXEL_USER_BITS_OFFSET = sizeof(blockstate_t)*8-VOXEL_USER_BITS;
diff --git a/src/content/loading/BlockLoader.cpp b/src/content/loading/BlockLoader.cpp
index 87c10fa67..f44d3a9ca 100644
--- a/src/content/loading/BlockLoader.cpp
+++ b/src/content/loading/BlockLoader.cpp
@@ -12,6 +12,7 @@
#include "presets/ParticlesPreset.hpp"
#include "util/stringutil.hpp"
#include "voxels/Block.hpp"
+#include "constants.hpp"
using namespace data;
@@ -120,8 +121,8 @@ template<> void ContentUnitLoader::loadUnit(
}
def.variants = std::make_unique();
- def.variants->offset = 0;
- def.variants->mask = 0xF;
+ def.variants->offset = offset;
+ def.variants->mask = (1ULL << bitsCount) - 1;
def.variants->variants.push_back(def.defaults);
for (int i = 0; i < variants.size(); i++) {
Variant variant = def.defaults;
@@ -184,7 +185,11 @@ template<> void ContentUnitLoader::loadUnit(
if (auto found = root.at("emission")) {
const auto& emissionarr = *found;
for (size_t i = 0; i < 3; i++) {
- def.emission[i] = glm::clamp(emissionarr[i].asInteger(), static_cast(0), static_cast(15));
+ def.emission[i] = glm::clamp(
+ emissionarr[i].asInteger(),
+ static_cast(0),
+ static_cast(15)
+ );
}
}
@@ -199,13 +204,31 @@ template<> void ContentUnitLoader::loadUnit(
"block " + util::quote(def.name) + ": invalid block size"
);
}
-
+
+ if (def.size.x > EXTENDED_BLOCK_LIMIT ||
+ def.size.y > EXTENDED_BLOCK_LIMIT ||
+ def.size.z > EXTENDED_BLOCK_LIMIT) {
+ throw std::runtime_error(
+ "extended block " + util::quote(def.name) + " size limit " +
+ std::to_string(EXTENDED_BLOCK_LIMIT) + " exceeded"
+ );
+ }
+
// should variant modify hitbox?
if (def.defaults.model.type == BlockModelType::BLOCK &&
(def.size.x != 1 || def.size.y != 1 || def.size.z != 1)) {
def.defaults.model.type = BlockModelType::AABB;
def.hitboxes = {AABB(def.size)};
}
+
+ // grounding behaviour
+ if (root.has("grounding-behaviour")) {
+ std::string groundingBehaviourName = GroundingBehaviourMeta.getNameString(def.groundingBehaviour);
+ root.at("grounding-behaviour").get(groundingBehaviourName);
+ if (!GroundingBehaviourMeta.getItem(groundingBehaviourName, def.groundingBehaviour)) {
+ logger.error() << "unknown grounding behaviour: " << groundingBehaviourName;
+ }
+ }
}
// primitive properties
diff --git a/src/engine/AssetsManagement.cpp b/src/engine/AssetsManagement.cpp
index 578a0e45b..1ca1935c9 100644
--- a/src/engine/AssetsManagement.cpp
+++ b/src/engine/AssetsManagement.cpp
@@ -51,7 +51,9 @@ void AssetsManagement::loadAssets(Content* content) {
logger.info() << "loading assets";
Shader::preprocessor->setPaths(&paths.resPaths);
- auto new_assets = std::make_unique();
+ auto new_assets = std::make_unique(
+ settings.system.preserveAssetsDuringFrame.get() ? &assetsVault : nullptr
+ );
AssetsLoader loader(engine, *new_assets, paths.resPaths);
AssetsLoader::addDefaults(loader, content);
@@ -78,6 +80,7 @@ void AssetsManagement::loadAssets(Content* content) {
}
void AssetsManagement::update() {
+ assetsVault.clearKeepedObjects();
if (backgroundLoaderTask) {
backgroundLoaderTask->update();
}
diff --git a/src/engine/AssetsManagement.hpp b/src/engine/AssetsManagement.hpp
index 4e14c9305..81c33bef8 100644
--- a/src/engine/AssetsManagement.hpp
+++ b/src/engine/AssetsManagement.hpp
@@ -1,6 +1,7 @@
#pragma once
#include "assets/Assets.hpp"
+#include "util/ObjectsKeeper.hpp"
#include
@@ -30,4 +31,5 @@ private:
std::unique_ptr assets;
std::unique_ptr backgroundLoader;
std::shared_ptr backgroundLoaderTask;
+ util::ObjectsKeeper assetsVault;
};
diff --git a/src/frontend/ContentGfxCache.cpp b/src/frontend/ContentGfxCache.cpp
index 20b7cdaa7..b8ce69a5f 100644
--- a/src/frontend/ContentGfxCache.cpp
+++ b/src/frontend/ContentGfxCache.cpp
@@ -70,7 +70,7 @@ void ContentGfxCache::refresh(const Block& def, const Atlas& atlas) {
refreshVariant(def, def.defaults, 0, atlas);
if (def.variants) {
const auto& variants = def.variants->variants;
- for (int i = 1; i < variants.size() - 1; i++) {
+ for (int i = 1; i < variants.size(); i++) {
refreshVariant(def, variants[i], i, atlas);
}
def.variants->variants.at(0) = def.defaults;
diff --git a/src/frontend/screens/LevelScreen.cpp b/src/frontend/screens/LevelScreen.cpp
index acb3edf70..13618d33a 100644
--- a/src/frontend/screens/LevelScreen.cpp
+++ b/src/frontend/screens/LevelScreen.cpp
@@ -114,7 +114,7 @@ LevelScreen::~LevelScreen() {
}
scripting::on_frontend_close();
input.getBindings().enableAll();
- playerController->getPlayer()->chunks->saveAndClear();
+ playerController->getPlayer().chunks->saveAndClear();
controller->onWorldQuit();
}
@@ -168,12 +168,12 @@ void LevelScreen::saveDecorations() {
void LevelScreen::saveWorldPreview() {
try {
logger.info() << "saving world preview";
- auto player = playerController->getPlayer();
- auto& settings = engine.getSettings();
+ const Player& player = playerController->getPlayer();
+ const auto& settings = engine.getSettings();
int previewSize = settings.ui.worldPreviewSize.get();
// camera special copy for world preview
- Camera camera = *player->fpCamera;
+ Camera& camera = *player.fpCamera;
camera.setFov(glm::radians(70.0f));
DrawContext pctx(nullptr, engine.getWindow(), batch.get());
@@ -215,18 +215,18 @@ void LevelScreen::updateHotkeys() {
}
void LevelScreen::updateAudio() {
- auto player = playerController->getPlayer();
- auto camera = player->currentCamera;
+ Player& player = playerController->getPlayer();
+ Camera& camera = *player.currentCamera;
bool paused = hud->isPause();
audio::get_channel("regular")->setPaused(paused);
audio::get_channel("ambient")->setPaused(paused);
glm::vec3 velocity {};
- if (auto hitbox = player->getHitbox()) {
+ if (auto hitbox = player.getHitbox()) {
velocity = hitbox->velocity;
}
audio::set_listener(
- camera->position, velocity, camera->dir, glm::vec3(0, 1, 0)
+ camera.position, velocity, camera.dir, glm::vec3(0, 1, 0)
);
}
@@ -257,22 +257,22 @@ void LevelScreen::update(float delta) {
hud->update(hudVisible);
- const auto& weather = renderer->getWeather();
- const auto& player = *playerController->getPlayer();
- const auto& camera = *player.currentCamera;
+ const Weather& weather = renderer->getWeather();
+ const Player& player = playerController->getPlayer();
+ const Camera& camera = *player.currentCamera;
decorator->update(paused ? 0.0f : delta, camera, weather);
}
void LevelScreen::draw(float delta) {
- auto camera = playerController->getPlayer()->currentCamera;
+ Camera& camera = *playerController->getPlayer().currentCamera;
- DrawContext ctx(nullptr, engine.getWindow(), batch.get());
+ const DrawContext ctx(nullptr, engine.getWindow(), batch.get());
if (!hud->isPause()) {
scripting::on_entities_render(engine.getTime().getDelta());
}
- renderer->update(*camera, delta * !hud->isPause());
- renderer->renderFrame(ctx, *camera, hudVisible, *postProcessing);
+ renderer->update(camera, delta * !hud->isPause());
+ renderer->renderFrame(ctx, camera, hudVisible, *postProcessing);
if (!hud->isPause()) {
scripting::on_frontend_render();
}
diff --git a/src/graphics/render/ChunksRenderer.cpp b/src/graphics/render/ChunksRenderer.cpp
index 8498091c9..57603d63e 100644
--- a/src/graphics/render/ChunksRenderer.cpp
+++ b/src/graphics/render/ChunksRenderer.cpp
@@ -203,7 +203,7 @@ const Mesh* ChunksRenderer::retrieveChunk(
);
auto mesh = getOrRender(
chunk,
- distance < CHUNK_W * 1.5f * 10.0f,
+ distance < CHUNK_W * 1.5f,
distance > CHUNK_W * settings.chunks.loadDistance.get() * 0.5
);
if (mesh == nullptr) {
diff --git a/src/graphics/render/ModelsGenerator.cpp b/src/graphics/render/ModelsGenerator.cpp
index b4f423480..da82680bb 100644
--- a/src/graphics/render/ModelsGenerator.cpp
+++ b/src/graphics/render/ModelsGenerator.cpp
@@ -92,6 +92,12 @@ void ModelsGenerator::prepareModel(
void ModelsGenerator::prepare(Content& content, Assets& assets) {
for (auto& [name, def] : content.blocks.getDefs()) {
prepareModel(assets, *def, def->defaults, 0);
+ if (def->variants) {
+ auto& variants = def->variants->variants;
+ for (int i = 1; i < variants.size(); i++) {
+ prepareModel(assets, *def, variants[i], i);
+ }
+ }
}
for (auto& [name, def] : content.items.getDefs()) {
assets.store(
diff --git a/src/io/settings_io.cpp b/src/io/settings_io.cpp
index af4547a14..c66b5c451 100644
--- a/src/io/settings_io.cpp
+++ b/src/io/settings_io.cpp
@@ -100,6 +100,7 @@ SettingsHandler::SettingsHandler(EngineSettings& settings) {
builder.addSection("system");
builder.add("max-bg-asset-loaders", &settings.system.maxBgAssetLoaders);
+ builder.add("preserve-assets-during-frame", &settings.system.preserveAssetsDuringFrame);
}
dv::value SettingsHandler::getValue(const std::string& name) const {
diff --git a/src/logic/BlocksController.cpp b/src/logic/BlocksController.cpp
index 2de34442f..3c0d0412e 100644
--- a/src/logic/BlocksController.cpp
+++ b/src/logic/BlocksController.cpp
@@ -111,10 +111,17 @@ void BlocksController::updateBlock(int x, int y, int z) {
if (vox == nullptr) return;
const auto& def = level.content.getIndices()->blocks.require(vox->id);
if (def.grounded) {
- const auto& vec = get_ground_direction(def, vox->state.rotation);
- if (!blocks_agent::is_solid_at(chunks, x + vec.x, y + vec.y, z + vec.z)) {
- breakBlock(nullptr, def, x, y, z);
- return;
+ if (def.rt.extended) {
+ auto origin = blocks_agent::seek_origin(chunks, {x, y, z}, def, vox->state);
+ if (!blocks_agent::check_grounding(chunks, def, vox->state.rotation, origin)) {
+ breakBlock(nullptr, def, origin.x, origin.y, origin.z);
+ return;
+ }
+ } else {
+ if (!blocks_agent::check_grounding(chunks, def, vox->state.rotation, {x, y, z})) {
+ breakBlock(nullptr, def, x, y, z);
+ return;
+ }
}
}
if (def.rt.funcsset.update) {
diff --git a/src/logic/PlayerController.cpp b/src/logic/PlayerController.cpp
index 17c4fe23f..e707d1637 100644
--- a/src/logic/PlayerController.cpp
+++ b/src/logic/PlayerController.cpp
@@ -22,6 +22,7 @@
#include "scripting/scripting.hpp"
#include "settings.hpp"
#include "voxels/Block.hpp"
+#include "voxels/blocks_agent.hpp"
#include "voxels/Chunks.hpp"
#include "voxels/voxel.hpp"
#include "window/Camera.hpp"
@@ -252,18 +253,18 @@ void PlayerController::onFootstep(const Hitbox& hitbox) {
}
void PlayerController::updateFootsteps(float delta) {
- const float pi = glm::pi();
+ constexpr float GLM_PI = glm::pi();
auto hitbox = player.getHitbox();
if (hitbox && hitbox->grounded) {
const glm::vec3& vel = hitbox->velocity;
float f = glm::length(glm::vec2(vel.x, vel.z));
stepsTimer += delta * f * STEPS_SPEED;
- if (stepsTimer >= pi) {
- stepsTimer = fmod(stepsTimer, pi);
+ if (stepsTimer >= GLM_PI) {
+ stepsTimer = fmod(stepsTimer, GLM_PI);
onFootstep(*hitbox);
}
} else {
- stepsTimer = pi;
+ stepsTimer = GLM_PI;
}
}
@@ -448,10 +449,7 @@ void PlayerController::processRightClick(
return;
}
if (def.grounded) {
- const auto& vec = get_ground_direction(def, state.rotation);
- if (!chunks.isSolidBlock(
- coord.x + vec.x, coord.y + vec.y, coord.z + vec.z
- )) {
+ if (!blocks_agent::check_grounding(chunks, def, state.rotation, coord)) {
return;
}
}
@@ -556,6 +554,6 @@ void PlayerController::updateInteraction(const Input& inputEvents, float delta)
}
}
-Player* PlayerController::getPlayer() {
- return &player;
+Player& PlayerController::getPlayer() {
+ return player;
}
diff --git a/src/logic/PlayerController.hpp b/src/logic/PlayerController.hpp
index 7f895ee3c..e730ff37c 100644
--- a/src/logic/PlayerController.hpp
+++ b/src/logic/PlayerController.hpp
@@ -89,5 +89,5 @@ public:
void postUpdate(
float delta, int windowHeight, const Input* inputEvents, bool pause
);
- Player* getPlayer();
+ Player& getPlayer();
};
diff --git a/src/logic/scripting/lua/libs/libassets.cpp b/src/logic/scripting/lua/libs/libassets.cpp
index fa0b51cdb..a9691038e 100644
--- a/src/logic/scripting/lua/libs/libassets.cpp
+++ b/src/logic/scripting/lua/libs/libassets.cpp
@@ -1,16 +1,17 @@
#include "api_lua.hpp"
+#include "../usertypes/lua_type_canvas.hpp"
#include "assets/Assets.hpp"
#include "assets/AssetsLoader.hpp"
+#include "coders/obj.hpp"
#include "coders/png.hpp"
#include "coders/vcm.hpp"
#include "debug/Logger.hpp"
#include "engine/Engine.hpp"
#include "graphics/commons/Model.hpp"
-#include "graphics/core/Texture.hpp"
#include "graphics/core/Atlas.hpp"
+#include "graphics/core/Texture.hpp"
#include "util/Buffer.hpp"
-#include "../usertypes/lua_type_canvas.hpp"
using namespace scripting;
@@ -76,6 +77,11 @@ static int l_parse_model(lua::State* L) {
skeletonName = lua::require_string(L, 4);
}
+ if (format == "obj") {
+ assets.store(obj::parse(name, string), name);
+ return 0;
+ }
+
if (format != "xml" && format != "vcm") {
throw std::runtime_error(
"unknown format " + util::quote(std::string(format))
diff --git a/src/logic/scripting/lua/libs/libblock.cpp b/src/logic/scripting/lua/libs/libblock.cpp
index 6a84b4113..2fa2b1281 100644
--- a/src/logic/scripting/lua/libs/libblock.cpp
+++ b/src/logic/scripting/lua/libs/libblock.cpp
@@ -246,11 +246,12 @@ static int l_get_user_bits(lua::State* L) {
}
static int l_get_variant(lua::State* L) {
+ auto& chunks = *level->chunks;
auto x = lua::tointeger(L, 1);
auto y = lua::tointeger(L, 2);
auto z = lua::tointeger(L, 3);
- auto vox = blocks_agent::get(*level->chunks, x, y, z);
+ auto vox = blocks_agent::get(chunks, x, y, z);
if (vox == nullptr) {
return lua::pushinteger(L, 0);
}
@@ -258,6 +259,13 @@ static int l_get_variant(lua::State* L) {
if (def.variants == nullptr) {
return lua::pushinteger(L, 0);
}
+ if (def.rt.extended) {
+ auto origin = blocks_agent::seek_origin(chunks, {x, y, z}, def, vox->state);
+ vox = blocks_agent::get(chunks, origin.x, origin.y, origin.z);
+ if (vox == nullptr) {
+ return lua::pushinteger(L, 0);
+ }
+ }
return lua::pushinteger(
L, (vox->state.userbits >> def.variants->offset) & def.variants->mask
);
@@ -290,6 +298,14 @@ static int l_set_user_bits(lua::State* L) {
if (vox == nullptr) {
return 0;
}
+ int ocx = floordiv(origin.x);
+ int ocz = floordiv(origin.z);
+ if (cx != ocx || cz != ocz) {
+ chunk = blocks_agent::get_chunk(chunks, ocx, ocz);
+ if (chunk == nullptr) {
+ return 0;
+ }
+ }
}
vox->state.userbits = (vox->state.userbits & (~mask)) | value;
chunk->setModifiedAndUnsaved();
@@ -318,8 +334,8 @@ static int l_set_variant(lua::State* L) {
}
auto offset = def.variants->offset;
- auto mask = def.variants->mask;
- auto value = (lua::tointeger(L, 4) << offset) & mask;
+ auto mask = def.variants->mask << offset;
+ auto value = (lua::tointeger(L, 4) << offset);
if (def.rt.extended) {
auto origin = blocks_agent::seek_origin(chunks, {x, y, z}, def, vox->state);
@@ -327,6 +343,14 @@ static int l_set_variant(lua::State* L) {
if (vox == nullptr) {
return 0;
}
+ int ocx = floordiv(origin.x);
+ int ocz = floordiv(origin.z);
+ if (cx != ocx || cz != ocz) {
+ chunk = blocks_agent::get_chunk(chunks, ocx, ocz);
+ if (chunk == nullptr) {
+ return 0;
+ }
+ }
}
vox->state.userbits = (vox->state.userbits & (~mask)) | value;
chunk->setModifiedAndUnsaved();
diff --git a/src/settings.hpp b/src/settings.hpp
index 331ff3ee8..782330f14 100644
--- a/src/settings.hpp
+++ b/src/settings.hpp
@@ -122,7 +122,10 @@ struct NetworkSettings {
};
struct SystemSettings {
+ /// @brief Max background assets loaders
IntegerSetting maxBgAssetLoaders {3, -4, 16};
+ /// @brief Preserve assets during current frame from being destroyed
+ FlagSetting preserveAssetsDuringFrame {true};
};
struct EngineSettings {
diff --git a/src/voxels/Block.hpp b/src/voxels/Block.hpp
index e1a68c28f..5ba3bb267 100644
--- a/src/voxels/Block.hpp
+++ b/src/voxels/Block.hpp
@@ -140,6 +140,22 @@ VC_ENUM_METADATA(CullingMode)
{"disabled", CullingMode::DISABLED},
VC_ENUM_END
+/// @brief Grounding behaviour for extended blocks
+enum class GroundingBehaviour : uint8_t {
+ /// @brief at least one segment must be grounded
+ PARTIAL,
+ /// @brief all segments must be grounded
+ COMPLETE,
+ /// @brief origin segment must be grounded
+ ORIGIN
+};
+
+VC_ENUM_METADATA(GroundingBehaviour)
+ {"partial", GroundingBehaviour::PARTIAL},
+ {"complete", GroundingBehaviour::COMPLETE},
+ {"origin", GroundingBehaviour::ORIGIN},
+VC_ENUM_END
+
/// @brief Common kit of block properties applied to groups of blocks
struct BlockMaterial : Serializable {
std::string name;
@@ -239,6 +255,9 @@ public:
/// @brief Explicitly overriding 'solid' property if true assigned
bool explictlySolid = false;
+ /// @brief Grounding behaviour
+ GroundingBehaviour groundingBehaviour = GroundingBehaviour::PARTIAL;
+
/// @brief Set of block physical hitboxes
std::vector hitboxes {AABB()};
diff --git a/src/voxels/blocks_agent.hpp b/src/voxels/blocks_agent.hpp
index 9ffac7f93..0742a7293 100644
--- a/src/voxels/blocks_agent.hpp
+++ b/src/voxels/blocks_agent.hpp
@@ -2,23 +2,23 @@
/// blocks_agent is set of templates but not a class to minimize OOP overhead.
-#include "voxel.hpp"
#include "Block.hpp"
#include "Chunk.hpp"
#include "Chunks.hpp"
-#include "VoxelsVolume.hpp"
-#include "GlobalChunks.hpp"
#include "constants.hpp"
-#include "typedefs.hpp"
#include "content/Content.hpp"
+#include "GlobalChunks.hpp"
#include "maths/voxmaths.hpp"
+#include "typedefs.hpp"
+#include "voxel.hpp"
+#include "VoxelsVolume.hpp"
#include
-#include
#include
-#include
-#include
#include
+#include
+#include
+#include
struct AABB;
@@ -96,7 +96,6 @@ inline const Block& get_block_def(const Storage& chunks, blockid_t id) {
return chunks.getContentIndices().blocks.require(id);
}
-
/// @brief Check if block at specified position is solid.
/// @tparam Storage chunks storage class
/// @param chunks chunks storage
@@ -104,8 +103,10 @@ inline const Block& get_block_def(const Storage& chunks, blockid_t id) {
/// @param y position Y
/// @param z position Z
/// @return true if block exists and solid
-template
-inline bool is_solid_at(const Storage& chunks, int32_t x, int32_t y, int32_t z) {
+template
+inline bool is_solid_at(
+ const Storage& chunks, int32_t x, int32_t y, int32_t z
+) {
if (auto vox = get(chunks, x, y, z)) {
return get_block_def(chunks, vox->id).rt.solid;
}
@@ -119,8 +120,10 @@ inline bool is_solid_at(const Storage& chunks, int32_t x, int32_t y, int32_t z)
/// @param y position Y
/// @param z position Z
/// @return true if block exists and replaceable
-template
-inline bool is_replaceable_at(const Storage& chunks, int32_t x, int32_t y, int32_t z) {
+template
+inline bool is_replaceable_at(
+ const Storage& chunks, int32_t x, int32_t y, int32_t z
+) {
if (auto vox = get(chunks, x, y, z)) {
return get_block_def(chunks, vox->id).replaceable;
}
@@ -456,12 +459,18 @@ voxel* raycast(
bool includeNonSelectable
);
-void get_voxels(const Chunks& chunks, VoxelsVolume* volume, bool backlight=false);
+void get_voxels(
+ const Chunks& chunks, VoxelsVolume* volume, bool backlight = false
+);
-void get_voxels(const GlobalChunks& chunks, VoxelsVolume* volume, bool backlight=false);
+void get_voxels(
+ const GlobalChunks& chunks, VoxelsVolume* volume, bool backlight = false
+);
template
-inline const AABB* is_obstacle_at(const Storage& chunks, float x, float y, float z) {
+inline const AABB* is_obstacle_at(
+ const Storage& chunks, float x, float y, float z
+) {
int ix = std::floor(x);
int iy = std::floor(y);
int iz = std::floor(z);
@@ -494,4 +503,63 @@ inline const AABB* is_obstacle_at(const Storage& chunks, float x, float y, float
return nullptr;
}
+/// @brief Check block grounding
+/// @tparam Storage chunks storage class
+/// @param chunks chunks storage
+/// @param def block definition
+/// @param rotationIndex target block rotation index
+/// @param origin target block origin
+/// @return true if grounded
+template
+inline bool check_grounding(
+ const Storage& chunks,
+ const Block& def,
+ uint8_t rotationIndex,
+ const glm::ivec3& origin
+) {
+ const auto& vec = get_ground_direction(def, rotationIndex);
+
+ if (!def.rt.extended) {
+ return blocks_agent::is_solid_at(
+ chunks, origin.x + vec.x, origin.y + vec.y, origin.z + vec.z
+ );
+ }
+
+ const auto& rotation = def.rotations.variants[rotationIndex];
+
+ if (def.groundingBehaviour == GroundingBehaviour::PARTIAL) {
+ for (int sz = 0; sz < def.size.z; sz++) {
+ for (int sx = 0; sx < def.size.x; sx++) {
+ auto pos = origin;
+ pos += rotation.axes[0] * sx;
+ pos += rotation.axes[2] * sz;
+ if (blocks_agent::is_solid_at(
+ chunks, pos.x + vec.x, pos.y + vec.y, pos.z + vec.z
+ )) {
+ return true;
+ }
+ }
+ }
+ return false;
+ } else if (def.groundingBehaviour == GroundingBehaviour::COMPLETE) {
+ for (int sz = 0; sz < def.size.z; sz++) {
+ for (int sx = 0; sx < def.size.x; sx++) {
+ auto pos = origin;
+ pos += rotation.axes[0] * sx;
+ pos += rotation.axes[2] * sz;
+ if (!blocks_agent::is_solid_at(
+ chunks, pos.x + vec.x, pos.y + vec.y, pos.z + vec.z
+ )) {
+ return false;
+ }
+ }
+ }
+ return true;
+ } else {
+ return blocks_agent::is_solid_at(
+ chunks, origin.x + vec.x, origin.y + vec.y, origin.z + vec.z
+ );
+ }
+}
+
} // blocks_agent