From 0d2d5770d3dda959af4069243cf9552c781cec08 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Mon, 28 Jul 2025 00:50:14 +0300 Subject: [PATCH 001/125] add ImageData.drawRect --- src/graphics/core/ImageData.cpp | 36 +++++++++++++++++++++++++++++++++ src/graphics/core/ImageData.hpp | 1 + 2 files changed, 37 insertions(+) diff --git a/src/graphics/core/ImageData.cpp b/src/graphics/core/ImageData.cpp index a2b1d524..44a1936e 100644 --- a/src/graphics/core/ImageData.cpp +++ b/src/graphics/core/ImageData.cpp @@ -1,5 +1,6 @@ #include "ImageData.hpp" +#include #include #include #include @@ -187,6 +188,41 @@ void ImageData::drawLine(int x1, int y1, int x2, int y2, const glm::ivec4& color } } +template +static void draw_rect(ImageData& image, int dstX, int dstY, int width, int height, const glm::ivec4& color) { + ubyte* data = image.getData(); + int imageWidth = image.getWidth(); + int imageHeight = image.getHeight(); + + int x1 = glm::min(glm::max(dstX, 0), imageWidth - 1); + int y1 = glm::min(glm::max(dstY, 0), imageHeight - 1); + + int x2 = glm::min(glm::max(dstX + width, 0), imageWidth - 1); + int y2 = glm::min(glm::max(dstY + height, 0), imageHeight - 1); + + for (int y = y1; y <= y2; y++) { + for (int x = x1; x <= x2; x++) { + int index = (y * imageWidth + x) * channels; + for (int i = 0; i < channels; i++) { + data[index + i] = color[i]; + } + } + } +} + +void ImageData::drawRect(int x, int y, int width, int height, const glm::ivec4& color) { + switch (format) { + case ImageFormat::rgb888: + draw_rect<3>(*this, x, y, width, height, color); + break; + case ImageFormat::rgba8888: + draw_rect<4>(*this, x, y, width, height, color); + break; + default: + break; + } +} + void ImageData::blitRGB_on_RGBA(const ImageData& image, int x, int y) { ubyte* source = image.getData(); uint srcwidth = image.getWidth(); diff --git a/src/graphics/core/ImageData.hpp b/src/graphics/core/ImageData.hpp index 213a352c..d09a0411 100644 --- a/src/graphics/core/ImageData.hpp +++ b/src/graphics/core/ImageData.hpp @@ -28,6 +28,7 @@ public: void flipY(); void drawLine(int x1, int y1, int x2, int y2, const glm::ivec4& color); + void drawRect(int x, int y, int width, int height, const glm::ivec4& color); void blit(const ImageData& image, int x, int y); void extrude(int x, int y, int w, int h); void fixAlphaColor(); From 70c5c67bd1759ff16831d8f2a39407cc4a6aec08 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Mon, 28 Jul 2025 23:00:27 +0300 Subject: [PATCH 002/125] add in-world lines renderer --- src/graphics/render/LinesRenderer.cpp | 14 ++++++++++++++ src/graphics/render/LinesRenderer.hpp | 22 ++++++++++++++++++++++ src/graphics/render/WorldRenderer.cpp | 6 ++++++ src/graphics/render/WorldRenderer.hpp | 2 ++ 4 files changed, 44 insertions(+) create mode 100644 src/graphics/render/LinesRenderer.cpp create mode 100644 src/graphics/render/LinesRenderer.hpp diff --git a/src/graphics/render/LinesRenderer.cpp b/src/graphics/render/LinesRenderer.cpp new file mode 100644 index 00000000..a5d92550 --- /dev/null +++ b/src/graphics/render/LinesRenderer.cpp @@ -0,0 +1,14 @@ +#include "LinesRenderer.hpp" + +#include "graphics/core/LineBatch.hpp" + +void LinesRenderer::draw(LineBatch& batch) { + for (const auto& line : queue) { + batch.line(line.a, line.b, line.color); + } + queue.clear(); +} + +void LinesRenderer::pushLine(const glm::vec3& a, const glm::vec3& b, const glm::vec4& color) { + queue.push_back({a, b, color}); +} diff --git a/src/graphics/render/LinesRenderer.hpp b/src/graphics/render/LinesRenderer.hpp new file mode 100644 index 00000000..4c42e4f5 --- /dev/null +++ b/src/graphics/render/LinesRenderer.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include +#include +#include + +class LineBatch; + +class LinesRenderer { +public: + struct Line { + glm::vec3 a; + glm::vec3 b; + glm::vec4 color; + }; + + void draw(LineBatch& batch); + + void pushLine(const glm::vec3& a, const glm::vec3& b, const glm::vec4& color); +private: + std::vector queue; +}; diff --git a/src/graphics/render/WorldRenderer.cpp b/src/graphics/render/WorldRenderer.cpp index ba1e580b..f2425e0b 100644 --- a/src/graphics/render/WorldRenderer.cpp +++ b/src/graphics/render/WorldRenderer.cpp @@ -52,6 +52,7 @@ #include "TextsRenderer.hpp" #include "ChunksRenderer.hpp" #include "GuidesRenderer.hpp" +#include "LinesRenderer.hpp" #include "ModelBatch.hpp" #include "Skybox.hpp" #include "Emitter.hpp" @@ -118,6 +119,7 @@ WorldRenderer::WorldRenderer( hands = std::make_unique( *assets, *modelBatch, skeletons->createSkeleton("hand", &skeletonConfig) ); + lines = std::make_unique(); } WorldRenderer::~WorldRenderer() = default; @@ -481,6 +483,10 @@ void WorldRenderer::draw( // Drawing background sky plane skybox->draw(ctx, camera, assets, worldInfo.daytime, clouds); + linesShader.use(); + lines->draw(*lineBatch); + lineBatch->flush(); + { auto sctx = ctx.sub(); sctx.setCullFace(true); diff --git a/src/graphics/render/WorldRenderer.hpp b/src/graphics/render/WorldRenderer.hpp index e53a5d80..600e2e4f 100644 --- a/src/graphics/render/WorldRenderer.hpp +++ b/src/graphics/render/WorldRenderer.hpp @@ -23,6 +23,7 @@ class PrecipitationRenderer; class HandsRenderer; class NamedSkeletons; class GuidesRenderer; +class LinesRenderer; class TextsRenderer; class Shader; class Frustum; @@ -102,6 +103,7 @@ public: std::unique_ptr blockWraps; std::unique_ptr precipitation; std::unique_ptr skeletons; + std::unique_ptr lines; static bool showChunkBorders; static bool showEntitiesDebug; From 8290c147c54dc47b2c5e3eb8c8345e3026317417 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Tue, 29 Jul 2025 00:29:55 +0300 Subject: [PATCH 003/125] refactor: move shadows-related code to separate module --- src/frontend/screens/LevelScreen.cpp | 4 +- src/graphics/core/ShadowMap.cpp | 47 ------ src/graphics/core/ShadowMap.hpp | 18 --- src/graphics/core/Shadows.cpp | 199 +++++++++++++++++++++++++ src/graphics/core/Shadows.hpp | 46 ++++++ src/graphics/render/ChunksRenderer.cpp | 2 +- src/graphics/render/ChunksRenderer.hpp | 2 +- src/graphics/render/WorldRenderer.cpp | 199 +++++++------------------ src/graphics/render/WorldRenderer.hpp | 43 ++---- 9 files changed, 317 insertions(+), 243 deletions(-) delete mode 100644 src/graphics/core/ShadowMap.cpp delete mode 100644 src/graphics/core/ShadowMap.hpp create mode 100644 src/graphics/core/Shadows.cpp create mode 100644 src/graphics/core/Shadows.hpp diff --git a/src/frontend/screens/LevelScreen.cpp b/src/frontend/screens/LevelScreen.cpp index ca80ed11..7b2a2a5d 100644 --- a/src/frontend/screens/LevelScreen.cpp +++ b/src/frontend/screens/LevelScreen.cpp @@ -173,7 +173,7 @@ void LevelScreen::saveWorldPreview() { static_cast(previewSize)} ); - renderer->draw(ctx, camera, false, true, 0.0f, *postProcessing); + renderer->renderFrame(ctx, camera, false, true, 0.0f, *postProcessing); auto image = postProcessing->toImage(); image->flipY(); imageio::write("world:preview.png", image.get()); @@ -260,7 +260,7 @@ void LevelScreen::draw(float delta) { if (!hud->isPause()) { scripting::on_entities_render(engine.getTime().getDelta()); } - renderer->draw( + renderer->renderFrame( ctx, *camera, hudVisible, hud->isPause(), delta, *postProcessing ); diff --git a/src/graphics/core/ShadowMap.cpp b/src/graphics/core/ShadowMap.cpp deleted file mode 100644 index e5032d8b..00000000 --- a/src/graphics/core/ShadowMap.cpp +++ /dev/null @@ -1,47 +0,0 @@ -#include "ShadowMap.hpp" - -#include - -ShadowMap::ShadowMap(int resolution) : resolution(resolution) { - glGenTextures(1, &depthMap); - glBindTexture(GL_TEXTURE_2D, depthMap); - glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, - resolution, resolution, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_BORDER); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE); - float border[4] {1.0f, 1.0f, 1.0f, 1.0f}; - glTexParameterfv(GL_TEXTURE_2D,GL_TEXTURE_BORDER_COLOR, border); - - glGenFramebuffers(1, &fbo); - glBindFramebuffer(GL_FRAMEBUFFER, fbo); - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depthMap, 0); - glDrawBuffer(GL_NONE); - glReadBuffer(GL_NONE); - glBindFramebuffer(GL_FRAMEBUFFER, 0); -} - -ShadowMap::~ShadowMap() { - glDeleteFramebuffers(1, &fbo); - glDeleteTextures(1, &depthMap); -} - -void ShadowMap::bind() { - glBindFramebuffer(GL_FRAMEBUFFER, fbo); - glClear(GL_DEPTH_BUFFER_BIT); -} - -void ShadowMap::unbind() { - glBindFramebuffer(GL_FRAMEBUFFER, 0); -} - -uint ShadowMap::getDepthMap() const { - return depthMap; -} - -int ShadowMap::getResolution() const { - return resolution; -} diff --git a/src/graphics/core/ShadowMap.hpp b/src/graphics/core/ShadowMap.hpp deleted file mode 100644 index 828c51aa..00000000 --- a/src/graphics/core/ShadowMap.hpp +++ /dev/null @@ -1,18 +0,0 @@ -#pragma once - -#include "typedefs.hpp" - -class ShadowMap { -public: - ShadowMap(int resolution); - ~ShadowMap(); - - void bind(); - void unbind(); - uint getDepthMap() const; - int getResolution() const; -private: - uint fbo; - uint depthMap; - int resolution; -}; diff --git a/src/graphics/core/Shadows.cpp b/src/graphics/core/Shadows.cpp new file mode 100644 index 00000000..c6ac8e37 --- /dev/null +++ b/src/graphics/core/Shadows.cpp @@ -0,0 +1,199 @@ +#include "Shadows.hpp" + +#include + +#include + +#include "assets/Assets.hpp" +#include "graphics/core/DrawContext.hpp" +#include "graphics/core/Shader.hpp" +#include "graphics/core/commons.hpp" +#include "world/Level.hpp" +#include "world/Weather.hpp" +#include "world/World.hpp" + +using namespace advanced_pipeline; + +inline constexpr int MIN_SHADOW_MAP_RES = 512; +inline constexpr GLenum TEXTURE_MAIN = GL_TEXTURE0; + +class ShadowMap { +public: + ShadowMap(int resolution) : resolution(resolution) { + glGenTextures(1, &depthMap); + glBindTexture(GL_TEXTURE_2D, depthMap); + glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, + resolution, resolution, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_BORDER); + glTexParameteri( + GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE + ); + float border[4] {1.0f, 1.0f, 1.0f, 1.0f}; + glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, border); + + glGenFramebuffers(1, &fbo); + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + glFramebufferTexture2D( + GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depthMap, 0 + ); + glDrawBuffer(GL_NONE); + glReadBuffer(GL_NONE); + glBindFramebuffer(GL_FRAMEBUFFER, 0); + } + + ~ShadowMap() { + glDeleteFramebuffers(1, &fbo); + glDeleteTextures(1, &depthMap); + } + + void bind(){ + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + glClear(GL_DEPTH_BUFFER_BIT); + } + + void unbind() { + glBindFramebuffer(GL_FRAMEBUFFER, 0); + } + uint getDepthMap() const { + return depthMap; + } + int getResolution() const { + return resolution; + } +private: + uint fbo; + uint depthMap; + int resolution; +}; + +Shadows::Shadows(const Level& level) : level(level) {} + +Shadows::~Shadows() = default; + +void Shadows::setQuality(int quality) { + int resolution = MIN_SHADOW_MAP_RES << quality; + if (quality > 0 && !shadows) { + shadowMap = std::make_unique(resolution); + wideShadowMap = std::make_unique(resolution); + shadows = true; + } else if (quality == 0 && shadows) { + shadowMap.reset(); + wideShadowMap.reset(); + shadows = false; + } + if (shadows && shadowMap->getResolution() != resolution) { + shadowMap = std::make_unique(resolution); + wideShadowMap = std::make_unique(resolution); + } + this->quality = quality; +} + +void Shadows::setup(Shader& shader, const Weather& weather) { + if (shadows) { + const auto& worldInfo = level.getWorld()->getInfo(); + float cloudsIntensity = glm::max(worldInfo.fog, weather.clouds()); + shader.uniform1i("u_screen", 0); + shader.uniformMatrix("u_shadowsMatrix[0]", shadowCamera.getProjView()); + shader.uniformMatrix("u_shadowsMatrix[1]", wideShadowCamera.getProjView()); + shader.uniform3f("u_sunDir", shadowCamera.front); + shader.uniform1i("u_shadowsRes", shadowMap->getResolution()); + shader.uniform1f("u_shadowsOpacity", 1.0f - cloudsIntensity); // TODO: make it configurable + shader.uniform1f("u_shadowsSoftness", 1.0f + cloudsIntensity * 4); // TODO: make it configurable + + glActiveTexture(GL_TEXTURE0 + TARGET_SHADOWS0); + shader.uniform1i("u_shadows[0]", TARGET_SHADOWS0); + glBindTexture(GL_TEXTURE_2D, shadowMap->getDepthMap()); + + glActiveTexture(GL_TEXTURE0 + TARGET_SHADOWS1); + shader.uniform1i("u_shadows[1]", TARGET_SHADOWS1); + glBindTexture(GL_TEXTURE_2D, wideShadowMap->getDepthMap()); + + glActiveTexture(TEXTURE_MAIN); + } +} + +void Shadows::refresh(const Camera& camera, const DrawContext& pctx, std::function renderShadowPass) { + static int frameid = 0; + if (shadows) { + if (frameid % 2 == 0) { + generateShadowsMap(camera, pctx, *shadowMap, shadowCamera, 1.0f, renderShadowPass); + } else { + generateShadowsMap(camera, pctx, *wideShadowMap, wideShadowCamera, 3.0f, renderShadowPass); + } + } + frameid++; +} + +void Shadows::generateShadowsMap( + const Camera& camera, + const DrawContext& pctx, + ShadowMap& shadowMap, + Camera& shadowCamera, + float scale, + std::function renderShadowPass +) { + auto world = level.getWorld(); + const auto& worldInfo = world->getInfo(); + + int resolution = shadowMap.getResolution(); + float shadowMapScale = 0.32f / (1 << glm::max(0, quality)) * scale; + float shadowMapSize = resolution * shadowMapScale; + + glm::vec3 basePos = glm::floor(camera.position / 4.0f) * 4.0f; + glm::vec3 prevPos = shadowCamera.position; + shadowCamera = Camera( + glm::distance2(prevPos, basePos) > 25.0f ? basePos : prevPos, + shadowMapSize + ); + shadowCamera.near = 0.1f; + shadowCamera.far = 1000.0f; + shadowCamera.perspective = false; + shadowCamera.setAspectRatio(1.0f); + + float t = worldInfo.daytime - 0.25f; + if (t < 0.0f) { + t += 1.0f; + } + t = fmod(t, 0.5f); + + float sunCycleStep = 1.0f / 500.0f; + float sunAngle = glm::radians( + 90.0f - + ((static_cast(t / sunCycleStep)) * sunCycleStep + 0.25f) * 360.0f + ); + float sunAltitude = glm::pi() * 0.25f; + shadowCamera.rotate( + -glm::cos(sunAngle + glm::pi() * 0.5f) * sunAltitude, + sunAngle - glm::pi() * 0.5f, + glm::radians(0.0f) + ); + + shadowCamera.position -= shadowCamera.front * 500.0f; + shadowCamera.position += shadowCamera.up * 0.0f; + shadowCamera.position += camera.front * 0.0f; + + auto view = shadowCamera.getView(); + + auto currentPos = shadowCamera.position; + auto topRight = shadowCamera.right + shadowCamera.up; + auto min = view * glm::vec4(currentPos - topRight * shadowMapSize * 0.5f, 1.0f); + auto max = view * glm::vec4(currentPos + topRight * shadowMapSize * 0.5f, 1.0f); + + shadowCamera.setProjection(glm::ortho(min.x, max.x, min.y, max.y, 0.1f, 1000.0f)); + + { + auto sctx = pctx.sub(); + sctx.setDepthTest(true); + sctx.setCullFace(true); + sctx.setViewport({resolution, resolution}); + shadowMap.bind(); + if (renderShadowPass) { + renderShadowPass(shadowCamera); + } + shadowMap.unbind(); + } +} diff --git a/src/graphics/core/Shadows.hpp b/src/graphics/core/Shadows.hpp new file mode 100644 index 00000000..6f21a2d7 --- /dev/null +++ b/src/graphics/core/Shadows.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include +#include + +#include "typedefs.hpp" +#include "window/Camera.hpp" + +class Shader; +class Level; +class Assets; +struct Weather; +class DrawContext; +struct EngineSettings; +class ShadowMap; + +class Shadows { +public: + Shadows(const Level& level); + ~Shadows(); + + void setup(Shader& shader, const Weather& weather); + void setQuality(int quality); + void refresh( + const Camera& camera, + const DrawContext& pctx, + std::function renderShadowPass + ); +private: + const Level& level; + bool shadows = false; + Camera shadowCamera; + Camera wideShadowCamera; + std::unique_ptr shadowMap; + std::unique_ptr wideShadowMap; + int quality = 0; + + void generateShadowsMap( + const Camera& camera, + const DrawContext& pctx, + ShadowMap& shadowMap, + Camera& shadowCamera, + float scale, + std::function renderShadowPass + ); +}; diff --git a/src/graphics/render/ChunksRenderer.cpp b/src/graphics/render/ChunksRenderer.cpp index bb816c37..571a10e5 100644 --- a/src/graphics/render/ChunksRenderer.cpp +++ b/src/graphics/render/ChunksRenderer.cpp @@ -184,7 +184,7 @@ const Mesh* ChunksRenderer::retrieveChunk( return mesh; } -void ChunksRenderer::drawChunksShadowsPass( +void ChunksRenderer::drawShadowsPass( const Camera& camera, Shader& shader, const Camera& playerCamera ) { Frustum frustum; diff --git a/src/graphics/render/ChunksRenderer.hpp b/src/graphics/render/ChunksRenderer.hpp index 70925343..9215a863 100644 --- a/src/graphics/render/ChunksRenderer.hpp +++ b/src/graphics/render/ChunksRenderer.hpp @@ -73,7 +73,7 @@ public: const std::shared_ptr& chunk, bool important ); - void drawChunksShadowsPass( + void drawShadowsPass( const Camera& camera, Shader& shader, const Camera& playerCamera ); diff --git a/src/graphics/render/WorldRenderer.cpp b/src/graphics/render/WorldRenderer.cpp index f2425e0b..97bf870d 100644 --- a/src/graphics/render/WorldRenderer.cpp +++ b/src/graphics/render/WorldRenderer.cpp @@ -42,7 +42,7 @@ #include "graphics/core/Shader.hpp" #include "graphics/core/Texture.hpp" #include "graphics/core/Font.hpp" -#include "graphics/core/ShadowMap.hpp" +#include "graphics/core/Shadows.hpp" #include "graphics/core/GBuffer.hpp" #include "BlockWrapsRenderer.hpp" #include "ParticlesRenderer.hpp" @@ -62,8 +62,6 @@ using namespace advanced_pipeline; inline constexpr size_t BATCH3D_CAPACITY = 4096; inline constexpr size_t MODEL_BATCH_CAPACITY = 20'000; -inline constexpr GLenum TEXTURE_MAIN = GL_TEXTURE0; -inline constexpr int MIN_SHADOW_MAP_RES = 512; bool WorldRenderer::showChunkBorders = false; bool WorldRenderer::showEntitiesDebug = false; @@ -82,7 +80,7 @@ WorldRenderer::WorldRenderer( MODEL_BATCH_CAPACITY, assets, *player.chunks, engine.getSettings() )), guides(std::make_unique()), - chunks(std::make_unique( + chunksRenderer(std::make_unique( &level, *player.chunks, assets, @@ -103,7 +101,7 @@ WorldRenderer::WorldRenderer( auto& settings = engine.getSettings(); level.events->listen( LevelEventType::CHUNK_HIDDEN, - [this](LevelEventType, Chunk* chunk) { chunks->unload(chunk); } + [this](LevelEventType, Chunk* chunk) { chunksRenderer->unload(chunk); } ); auto assets = engine.getAssets(); skybox = std::make_unique( @@ -120,10 +118,24 @@ WorldRenderer::WorldRenderer( *assets, *modelBatch, skeletons->createSkeleton("hand", &skeletonConfig) ); lines = std::make_unique(); + shadowMapping = std::make_unique(level); } WorldRenderer::~WorldRenderer() = default; +static void setup_weather(Shader& shader, const Weather& weather) { + shader.uniform1f("u_weatherFogOpacity", weather.fogOpacity()); + shader.uniform1f("u_weatherFogDencity", weather.fogDencity()); + shader.uniform1f("u_weatherFogCurve", weather.fogCurve()); +} + +static void setup_camera(Shader& shader, const Camera& camera) { + shader.uniformMatrix("u_model", glm::mat4(1.0f)); + shader.uniformMatrix("u_proj", camera.getProjection()); + shader.uniformMatrix("u_view", camera.getView()); + shader.uniform3f("u_cameraPos", camera.position); +} + void WorldRenderer::setupWorldShader( Shader& shader, const Camera& camera, @@ -131,45 +143,20 @@ void WorldRenderer::setupWorldShader( float fogFactor ) { shader.use(); - shader.uniformMatrix("u_model", glm::mat4(1.0f)); - shader.uniformMatrix("u_proj", camera.getProjection()); - shader.uniformMatrix("u_view", camera.getView()); + + setup_camera(shader, camera); + setup_weather(shader, weather); + shadowMapping->setup(shader, weather); + shader.uniform1f("u_timer", timer); shader.uniform1f("u_gamma", settings.graphics.gamma.get()); shader.uniform1f("u_fogFactor", fogFactor); shader.uniform1f("u_fogCurve", settings.graphics.fogCurve.get()); shader.uniform1i("u_debugLights", lightsDebug); shader.uniform1i("u_debugNormals", false); - shader.uniform1f("u_weatherFogOpacity", weather.fogOpacity()); - shader.uniform1f("u_weatherFogDencity", weather.fogDencity()); - shader.uniform1f("u_weatherFogCurve", weather.fogCurve()); shader.uniform1f("u_dayTime", level.getWorld()->getInfo().daytime); shader.uniform2f("u_lightDir", skybox->getLightDir()); - shader.uniform3f("u_cameraPos", camera.position); - shader.uniform1i("u_skybox", 1); - shader.uniform1i("u_enableShadows", shadows); - - if (shadows) { - const auto& worldInfo = level.getWorld()->getInfo(); - float cloudsIntensity = glm::max(worldInfo.fog, weather.clouds()); - shader.uniform1i("u_screen", 0); - shader.uniformMatrix("u_shadowsMatrix[0]", shadowCamera.getProjView()); - shader.uniformMatrix("u_shadowsMatrix[1]", wideShadowCamera.getProjView()); - shader.uniform3f("u_sunDir", shadowCamera.front); - shader.uniform1i("u_shadowsRes", shadowMap->getResolution()); - shader.uniform1f("u_shadowsOpacity", 1.0f - cloudsIntensity); // TODO: make it configurable - shader.uniform1f("u_shadowsSoftness", 1.0f + cloudsIntensity * 4); // TODO: make it configurable - - glActiveTexture(GL_TEXTURE0 + TARGET_SHADOWS0); - shader.uniform1i("u_shadows[0]", TARGET_SHADOWS0); - glBindTexture(GL_TEXTURE_2D, shadowMap->getDepthMap()); - - glActiveTexture(GL_TEXTURE0 + TARGET_SHADOWS1); - shader.uniform1i("u_shadows[1]", TARGET_SHADOWS1); - glBindTexture(GL_TEXTURE_2D, wideShadowMap->getDepthMap()); - - glActiveTexture(TEXTURE_MAIN); - } + shader.uniform1i("u_skybox", TARGET_SKYBOX); auto indices = level.content.getIndices(); // Light emission when an emissive item is chosen @@ -188,7 +175,7 @@ void WorldRenderer::setupWorldShader( } } -void WorldRenderer::renderLevel( +void WorldRenderer::renderOpaque( const DrawContext& ctx, const Camera& camera, const EngineSettings& settings, @@ -227,7 +214,7 @@ void WorldRenderer::renderLevel( setupWorldShader(shader, camera, settings, fogFactor); - chunks->drawChunks(camera, shader); + chunksRenderer->drawChunks(camera, shader); blockWraps->draw(ctx, player); if (hudVisible) { @@ -286,79 +273,7 @@ void WorldRenderer::renderLines( } } -void WorldRenderer::generateShadowsMap( - const Camera& camera, - const DrawContext& pctx, - ShadowMap& shadowMap, - Camera& shadowCamera, - float scale -) { - auto& shadowsShader = assets.require("shadows"); - - auto world = level.getWorld(); - const auto& worldInfo = world->getInfo(); - - const auto& settings = engine.getSettings(); - int resolution = shadowMap.getResolution(); - int quality = settings.graphics.shadowsQuality.get(); - float shadowMapScale = 0.32f / (1 << glm::max(0, quality)) * scale; - float shadowMapSize = resolution * shadowMapScale; - - glm::vec3 basePos = glm::floor(camera.position / 4.0f) * 4.0f; - glm::vec3 prevPos = shadowCamera.position; - shadowCamera = Camera( - glm::distance2(prevPos, basePos) > 25.0f ? basePos : prevPos, - shadowMapSize - ); - shadowCamera.near = 0.1f; - shadowCamera.far = 1000.0f; - shadowCamera.perspective = false; - shadowCamera.setAspectRatio(1.0f); - - float t = worldInfo.daytime - 0.25f; - if (t < 0.0f) { - t += 1.0f; - } - t = fmod(t, 0.5f); - - float sunCycleStep = 1.0f / 500.0f; - float sunAngle = glm::radians( - 90.0f - - ((static_cast(t / sunCycleStep)) * sunCycleStep + 0.25f) * 360.0f - ); - float sunAltitude = glm::pi() * 0.25f; - shadowCamera.rotate( - -glm::cos(sunAngle + glm::pi() * 0.5f) * sunAltitude, - sunAngle - glm::pi() * 0.5f, - glm::radians(0.0f) - ); - - shadowCamera.position -= shadowCamera.front * 500.0f; - shadowCamera.position += shadowCamera.up * 0.0f; - shadowCamera.position += camera.front * 0.0f; - - auto view = shadowCamera.getView(); - - auto currentPos = shadowCamera.position; - auto topRight = shadowCamera.right + shadowCamera.up; - auto min = view * glm::vec4(currentPos - topRight * shadowMapSize * 0.5f, 1.0f); - auto max = view * glm::vec4(currentPos + topRight * shadowMapSize * 0.5f, 1.0f); - - shadowCamera.setProjection(glm::ortho(min.x, max.x, min.y, max.y, 0.1f, 1000.0f)); - - { - auto sctx = pctx.sub(); - sctx.setDepthTest(true); - sctx.setCullFace(true); - sctx.setViewport({resolution, resolution}); - shadowMap.bind(); - setupWorldShader(shadowsShader, shadowCamera, settings, 0.0f); - chunks->drawChunksShadowsPass(shadowCamera, shadowsShader, camera); - shadowMap.unbind(); - } -} - -void WorldRenderer::draw( +void WorldRenderer::renderFrame( const DrawContext& pctx, Camera& camera, bool hudVisible, @@ -382,22 +297,17 @@ void WorldRenderer::draw( auto& deferredShader = assets.require("deferred_lighting").getShader(); const auto& settings = engine.getSettings(); + Shader* affectedShaders[] { + &mainShader, &entityShader, &translucentShader, &deferredShader + }; + gbufferPipeline = settings.graphics.advancedRender.get(); int shadowsQuality = settings.graphics.shadowsQuality.get() * gbufferPipeline; - int resolution = MIN_SHADOW_MAP_RES << shadowsQuality; - if (shadowsQuality > 0 && !shadows) { - shadowMap = std::make_unique(resolution); - wideShadowMap = std::make_unique(resolution); - shadows = true; - } else if (shadowsQuality == 0 && shadows) { - shadowMap.reset(); - wideShadowMap.reset(); - shadows = false; - } + shadowMapping->setQuality(shadowsQuality); CompileTimeShaderSettings currentSettings { gbufferPipeline, - shadows, + shadowsQuality != 0, settings.graphics.ssao.get() && gbufferPipeline }; if ( @@ -408,18 +318,12 @@ void WorldRenderer::draw( Shader::preprocessor->setDefined("ENABLE_SHADOWS", currentSettings.shadows); Shader::preprocessor->setDefined("ENABLE_SSAO", currentSettings.ssao); Shader::preprocessor->setDefined("ADVANCED_RENDER", currentSettings.advancedRender); - mainShader.recompile(); - entityShader.recompile(); - deferredShader.recompile(); - translucentShader.recompile(); + for (auto shader : affectedShaders) { + shader->recompile(); + } prevCTShaderSettings = currentSettings; } - if (shadows && shadowMap->getResolution() != resolution) { - shadowMap = std::make_unique(resolution); - wideShadowMap = std::make_unique(resolution); - } - const auto& worldInfo = world->getInfo(); float clouds = weather.clouds(); @@ -428,30 +332,27 @@ void WorldRenderer::draw( skybox->refresh(pctx, worldInfo.daytime, mie, 4); - chunks->update(); + chunksRenderer->update(); - static int frameid = 0; - if (shadows) { - if (frameid % 2 == 0) { - generateShadowsMap(camera, pctx, *shadowMap, shadowCamera, 1.0f); - } else { - generateShadowsMap(camera, pctx, *wideShadowMap, wideShadowCamera, 3.0f); - } - } - frameid++; + shadowMapping->refresh(camera, pctx, [this, &camera](Camera& shadowCamera) { + auto& shader = assets.require("shadows"); + setupWorldShader(shader, shadowCamera, engine.getSettings(), 0.0f); + chunksRenderer->drawShadowsPass(shadowCamera, shader, camera); + }); auto& linesShader = assets.require("lines"); - /* World render scope with diegetic HUD included */ { + + { DrawContext wctx = pctx.sub(); postProcessing.use(wctx, gbufferPipeline); display::clearDepth(); - /* Actually world render with depth buffer on */ { + /* Main opaque pass (GBuffer pass) */ { DrawContext ctx = wctx.sub(); ctx.setDepthTest(true); ctx.setCullFace(true); - renderLevel(ctx, camera, settings, uiDelta, pause, hudVisible); + renderOpaque(ctx, camera, settings, uiDelta, pause, hudVisible); // Debug lines if (hudVisible) { if (debug) { @@ -480,23 +381,27 @@ void WorldRenderer::draw( } else { postProcessing.getFramebuffer()->bind(); } - // Drawing background sky plane + + // Background sky plane skybox->draw(ctx, camera, assets, worldInfo.daytime, clouds); + // In-world lines linesShader.use(); lines->draw(*lineBatch); lineBatch->flush(); + // Translucent blocks { auto sctx = ctx.sub(); sctx.setCullFace(true); skybox->bind(); translucentShader.use(); setupWorldShader(translucentShader, camera, settings, fogFactor); - chunks->drawSortedMeshes(camera, translucentShader); + chunksRenderer->drawSortedMeshes(camera, translucentShader); skybox->unbind(); } + // Weather effects entityShader.use(); setupWorldShader(entityShader, camera, settings, fogFactor); @@ -588,7 +493,7 @@ void WorldRenderer::renderBlockOverlay(const DrawContext& wctx) { } void WorldRenderer::clear() { - chunks->clear(); + chunksRenderer->clear(); } void WorldRenderer::setDebug(bool flag) { diff --git a/src/graphics/render/WorldRenderer.hpp b/src/graphics/render/WorldRenderer.hpp index 600e2e4f..955568fa 100644 --- a/src/graphics/render/WorldRenderer.hpp +++ b/src/graphics/render/WorldRenderer.hpp @@ -34,7 +34,7 @@ class PostProcessing; class DrawContext; class ModelBatch; class Assets; -class ShadowMap; +class Shadows; class GBuffer; struct EngineSettings; @@ -54,20 +54,17 @@ class WorldRenderer { std::unique_ptr batch3d; std::unique_ptr modelBatch; std::unique_ptr guides; - std::unique_ptr chunks; + std::unique_ptr chunksRenderer; std::unique_ptr hands; std::unique_ptr skybox; - std::unique_ptr shadowMap; - std::unique_ptr wideShadowMap; + std::unique_ptr shadowMapping; Weather weather {}; - Camera shadowCamera; - Camera wideShadowCamera; float timer = 0.0f; bool debug = false; bool lightsDebug = false; bool gbufferPipeline = false; - bool shadows = false; + CompileTimeShaderSettings prevCTShaderSettings {}; @@ -90,12 +87,17 @@ class WorldRenderer { float fogFactor ); - void generateShadowsMap( - const Camera& camera, - const DrawContext& pctx, - ShadowMap& shadowMap, - Camera& shadowCamera, - float scale + /// @brief Render opaque pass + /// @param context graphics context + /// @param camera active camera + /// @param settings engine settings + void renderOpaque( + const DrawContext& context, + const Camera& camera, + const EngineSettings& settings, + float delta, + bool pause, + bool hudVisible ); public: std::unique_ptr particles; @@ -111,7 +113,7 @@ public: WorldRenderer(Engine& engine, LevelFrontend& frontend, Player& player); ~WorldRenderer(); - void draw( + void renderFrame( const DrawContext& context, Camera& camera, bool hudVisible, @@ -120,19 +122,6 @@ public: PostProcessing& postProcessing ); - /// @brief Render level without diegetic interface - /// @param context graphics context - /// @param camera active camera - /// @param settings engine settings - void renderLevel( - const DrawContext& context, - const Camera& camera, - const EngineSettings& settings, - float delta, - bool pause, - bool hudVisible - ); - void clear(); void setDebug(bool flag); From 530670cfdb69be33ca8005c21b1f9a5ce4eec9a3 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Tue, 29 Jul 2025 00:35:26 +0300 Subject: [PATCH 004/125] add `#define GLM_ENABLE_EXPERIMENTAL` --- src/graphics/core/Shadows.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/graphics/core/Shadows.cpp b/src/graphics/core/Shadows.cpp index c6ac8e37..d37dc557 100644 --- a/src/graphics/core/Shadows.cpp +++ b/src/graphics/core/Shadows.cpp @@ -1,7 +1,7 @@ #include "Shadows.hpp" #include - +#define GLM_ENABLE_EXPERIMENTAL #include #include "assets/Assets.hpp" From 88721344c1198ec3754645eca752f0ccc87ae176 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Wed, 30 Jul 2025 21:55:21 +0300 Subject: [PATCH 005/125] fix in-world lines projection --- src/graphics/render/WorldRenderer.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/graphics/render/WorldRenderer.cpp b/src/graphics/render/WorldRenderer.cpp index 97bf870d..fba83c64 100644 --- a/src/graphics/render/WorldRenderer.cpp +++ b/src/graphics/render/WorldRenderer.cpp @@ -387,6 +387,7 @@ void WorldRenderer::renderFrame( // In-world lines linesShader.use(); + linesShader.uniformMatrix("u_projview", camera.getProjView()); lines->draw(*lineBatch); lineBatch->flush(); From 9bb50db29771e9452c2f66b3dc15b09696cd8beb Mon Sep 17 00:00:00 2001 From: MihailRis Date: Thu, 31 Jul 2025 23:29:22 +0300 Subject: [PATCH 006/125] add pathfinding --- src/voxels/Pathfinding.cpp | 115 +++++++++++++++++++++++++++++++++++++ src/voxels/Pathfinding.hpp | 48 ++++++++++++++++ src/world/Level.cpp | 4 +- src/world/Level.hpp | 5 ++ 4 files changed, 171 insertions(+), 1 deletion(-) create mode 100644 src/voxels/Pathfinding.cpp create mode 100644 src/voxels/Pathfinding.hpp diff --git a/src/voxels/Pathfinding.cpp b/src/voxels/Pathfinding.cpp new file mode 100644 index 00000000..f235b554 --- /dev/null +++ b/src/voxels/Pathfinding.cpp @@ -0,0 +1,115 @@ +#include "Pathfinding.hpp" + +#define GLM_ENABLE_EXPERIMENTAL +#include + +#include +#include +#include + +#include "world/Level.hpp" +#include "voxels/GlobalChunks.hpp" +#include "voxels/Chunk.hpp" +#include "voxels/blocks_agent.hpp" +#include "content/Content.hpp" + +using namespace voxels; + +struct Node { + glm::ivec3 pos; + glm::ivec3 parent; + float gScore; + float fScore; +}; + + +struct NodeLess { + bool operator()(const Node& l, const Node& r) const { + return l.fScore > r.fScore; + } +}; + +static float distance(const glm::ivec3& a, const glm::ivec3& b) { + return glm::distance(glm::vec3(a), glm::vec3(b)); +} + +Pathfinding::Pathfinding(const Level& level) : level(level) {} + +Route Pathfinding::perform(const glm::ivec3& start, const glm::ivec3& end) { + Route route {}; + + std::priority_queue, NodeLess> queue; + queue.push({start, {}, 0, distance(start, end)}); + + std::unordered_set blocked; + std::unordered_map parents; + + const auto& chunks = *level.chunks; + + while (!queue.empty()) { + auto node = queue.top(); + queue.pop(); + + if (blocked.find(node.pos) != blocked.end()) { + continue; + } + + if (node.pos.x == end.x && node.pos.z == end.z) { + auto prev = glm::ivec3(); + auto pos = node.pos; + while (pos != start) { + const auto& found = parents.find(pos); + if (found == parents.end()) { + route.nodes.push_back({pos}); + break; + } + route.nodes.push_back({pos}); + + prev = pos; + pos = found->second.pos; + } + route.nodes.push_back({start}); + route.found = true; + break; + } + + blocked.emplace(node.pos); + glm::ivec2 neighbors[8] { + {0, 1}, {1, 0}, {0, -1}, {-1, 0}, + {-1, -1}, {1, -1}, {1, 1}, {-1, 1}, + }; + + for (int i = 0; i < sizeof(neighbors) / sizeof(glm::ivec2); i++) { + auto offset = neighbors[i]; + auto point = node.pos + glm::ivec3(offset.x, 0, offset.y); + if (blocks_agent::is_obstacle_at( + chunks, point.x + 0.5f, point.y + 0.5f, point.z + 0.5f + )) { + continue; + } + if (i >= 4) { + auto a = node.pos + glm::ivec3(offset.x, 0, offset.y); + auto b = node.pos + glm::ivec3(offset.x, 0, offset.y); + if (blocks_agent::is_obstacle_at(chunks, a.x, a.y, a.z)) + continue; + if (blocks_agent::is_obstacle_at(chunks, b.x, b.y, b.z)) + continue; + } + if (blocked.find(point) != blocked.end()) { + continue; + } + float gScore = + node.gScore + glm::abs(offset.x) + glm::abs(offset.y); + const auto& foundParent = parents.find(point); + bool queued = foundParent != parents.end(); + if (!queued || gScore < foundParent->second.gScore) { + float hScore = distance(point, end); + float fScore = gScore + hScore; + Node nNode {point, node.pos, gScore, fScore}; + parents[point] = Node {node.pos, node.parent, gScore, fScore}; + queue.push(nNode); + } + } + } + return route; +} diff --git a/src/voxels/Pathfinding.hpp b/src/voxels/Pathfinding.hpp new file mode 100644 index 00000000..cefecdb2 --- /dev/null +++ b/src/voxels/Pathfinding.hpp @@ -0,0 +1,48 @@ +#pragma once + +#include +#include +#include +#include + +class Level; + +namespace voxels { + struct RouteNode { + glm::ivec3 pos; + }; + + struct Route { + bool found; + std::vector nodes; + }; + + struct Map { + int width; + int height; + std::unique_ptr map; + + Map(int width, int height) + : width(width), + height(height), + map(std::make_unique(width * height)) { + } + + uint8_t& operator[](int i) { + return map[i]; + } + + const uint8_t& operator[](int i) const { + return map[i]; + } + }; + + class Pathfinding { + public: + Pathfinding(const Level& level); + + Route perform(const glm::ivec3& start, const glm::ivec3& end); + private: + const Level& level; + }; +} diff --git a/src/world/Level.cpp b/src/world/Level.cpp index 159b79de..c33c7e0f 100644 --- a/src/world/Level.cpp +++ b/src/world/Level.cpp @@ -12,6 +12,7 @@ #include "settings.hpp" #include "voxels/Chunk.hpp" #include "voxels/GlobalChunks.hpp" +#include "voxels/Pathfinding.hpp" #include "window/Camera.hpp" #include "LevelEvents.hpp" #include "World.hpp" @@ -27,7 +28,8 @@ Level::Level( physics(std::make_unique(glm::vec3(0, -22.6f, 0))), events(std::make_unique()), entities(std::make_unique(*this)), - players(std::make_unique(*this)) { + players(std::make_unique(*this)), + pathfinding(std::make_unique(*this)) { const auto& worldInfo = world->getInfo(); auto& cameraIndices = content.getIndices(ResourceType::CAMERA); for (size_t i = 0; i < cameraIndices.size(); i++) { diff --git a/src/world/Level.hpp b/src/world/Level.hpp index 8809b3f1..0dd3251f 100644 --- a/src/world/Level.hpp +++ b/src/world/Level.hpp @@ -18,6 +18,10 @@ class Camera; class Players; struct EngineSettings; +namespace voxels { + class Pathfinding; +} + /// @brief A level, contains chunks and objects class Level { std::unique_ptr world; @@ -30,6 +34,7 @@ public: std::unique_ptr events; std::unique_ptr entities; std::unique_ptr players; + std::unique_ptr pathfinding; std::vector> cameras; // move somewhere? Level( From ec4b836b3c2a62a31042c5dda025b6a1af0d3409 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Fri, 1 Aug 2025 23:32:35 +0300 Subject: [PATCH 007/125] feat: vertical movement and agent height --- src/voxels/Pathfinding.cpp | 143 ++++++++++++++++++++++++++++--------- src/voxels/Pathfinding.hpp | 14 +++- 2 files changed, 120 insertions(+), 37 deletions(-) diff --git a/src/voxels/Pathfinding.cpp b/src/voxels/Pathfinding.cpp index f235b554..59da59f2 100644 --- a/src/voxels/Pathfinding.cpp +++ b/src/voxels/Pathfinding.cpp @@ -22,7 +22,6 @@ struct Node { float fScore; }; - struct NodeLess { bool operator()(const Node& l, const Node& r) const { return l.fScore > r.fScore; @@ -33,9 +32,54 @@ static float distance(const glm::ivec3& a, const glm::ivec3& b) { return glm::distance(glm::vec3(a), glm::vec3(b)); } -Pathfinding::Pathfinding(const Level& level) : level(level) {} +Pathfinding::Pathfinding(const Level& level) + : level(level), chunks(*level.chunks) { +} + +static bool check_passability( + const Agent& agent, + const GlobalChunks& chunks, + const Node& node, + const glm::ivec2& offset, + bool diagonal +) { + if (!diagonal) { + return true; + } + auto a = node.pos + glm::ivec3(offset.x, 0, 0); + auto b = node.pos + glm::ivec3(0, 0, offset.y); + + for (int i = 0; i < agent.height; i++) { + if (blocks_agent::is_obstacle_at(chunks, a.x, a.y + i, a.z)) + return false; + if (blocks_agent::is_obstacle_at(chunks, b.x, b.y + i, b.z)) + return false; + } + return true; +} + +static void restore_route( + Route& route, + const Node& node, + const std::unordered_map& parents +) { + auto pos = node.pos; + while (true) { + const auto& found = parents.find(pos); + if (found == parents.end()) { + route.nodes.push_back({pos}); + break; + } + route.nodes.push_back({pos}); + pos = found->second.pos; + } +} + +Route Pathfinding::perform( + const Agent& agent, const glm::ivec3& start, const glm::ivec3& end +) { + using namespace blocks_agent; -Route Pathfinding::perform(const glm::ivec3& start, const glm::ivec3& end) { Route route {}; std::priority_queue, NodeLess> queue; @@ -50,24 +94,8 @@ Route Pathfinding::perform(const glm::ivec3& start, const glm::ivec3& end) { auto node = queue.top(); queue.pop(); - if (blocked.find(node.pos) != blocked.end()) { - continue; - } - if (node.pos.x == end.x && node.pos.z == end.z) { - auto prev = glm::ivec3(); - auto pos = node.pos; - while (pos != start) { - const auto& found = parents.find(pos); - if (found == parents.end()) { - route.nodes.push_back({pos}); - break; - } - route.nodes.push_back({pos}); - - prev = pos; - pos = found->second.pos; - } + restore_route(route, node, parents); route.nodes.push_back({start}); route.found = true; break; @@ -81,28 +109,30 @@ Route Pathfinding::perform(const glm::ivec3& start, const glm::ivec3& end) { for (int i = 0; i < sizeof(neighbors) / sizeof(glm::ivec2); i++) { auto offset = neighbors[i]; - auto point = node.pos + glm::ivec3(offset.x, 0, offset.y); - if (blocks_agent::is_obstacle_at( - chunks, point.x + 0.5f, point.y + 0.5f, point.z + 0.5f - )) { + auto pos = node.pos; + + int surface = getSurfaceAt(pos + glm::ivec3(offset.x, 0, offset.y), 1); + + if (surface == -1) { continue; } - if (i >= 4) { - auto a = node.pos + glm::ivec3(offset.x, 0, offset.y); - auto b = node.pos + glm::ivec3(offset.x, 0, offset.y); - if (blocks_agent::is_obstacle_at(chunks, a.x, a.y, a.z)) - continue; - if (blocks_agent::is_obstacle_at(chunks, b.x, b.y, b.z)) - continue; + pos.y = surface; + auto point = pos + glm::ivec3(offset.x, 0, offset.y); + + if (is_obstacle_at(chunks, pos.x, pos.y + agent.height / 2, pos.z)) { + continue; + } + if (!check_passability(agent, chunks, node, offset, i >= 4)) { + continue; } if (blocked.find(point) != blocked.end()) { continue; } + int score = glm::abs(node.pos.y - pos.y); float gScore = - node.gScore + glm::abs(offset.x) + glm::abs(offset.y); - const auto& foundParent = parents.find(point); - bool queued = foundParent != parents.end(); - if (!queued || gScore < foundParent->second.gScore) { + node.gScore + glm::abs(offset.x) + glm::abs(offset.y) + score; + const auto& found = parents.find(point); + if (found == parents.end() || gScore < found->second.gScore) { float hScore = distance(point, end); float fScore = gScore + hScore; Node nNode {point, node.pos, gScore, fScore}; @@ -113,3 +143,46 @@ Route Pathfinding::perform(const glm::ivec3& start, const glm::ivec3& end) { } return route; } + +static int check_point( + const ContentUnitIndices& defs, + const GlobalChunks& chunks, + int x, + int y, + int z +) { + auto vox = blocks_agent::get(chunks, x, y, z); + if (vox == nullptr) { + return 0; + } + const auto& def = defs.require(vox->id); + if (def.obstacle) { + return 0; + } + if (def.translucent) { + return -1; + } + return 1; +} + +int Pathfinding::getSurfaceAt(const glm::ivec3& pos, int maxDelta) { + using namespace blocks_agent; + + const auto& defs = level.content.getIndices()->blocks; + + int status; + int surface = pos.y; + if (check_point(defs, chunks, pos.x, surface, pos.z) <= 0) { + if (check_point(defs, chunks, pos.x, surface + 1, pos.z) <= 0) + return -1; + else + return surface + 1; + } else if ((status = check_point(defs, chunks, pos.x, surface - 1, pos.z)) <= 0) { + if (status == -1) + return -1; + return surface; + } else if (check_point(defs, chunks, pos.x, surface - 2, pos.z) == 0) { + return surface - 1; + } + return -1; +} diff --git a/src/voxels/Pathfinding.hpp b/src/voxels/Pathfinding.hpp index cefecdb2..f743fd1c 100644 --- a/src/voxels/Pathfinding.hpp +++ b/src/voxels/Pathfinding.hpp @@ -6,6 +6,7 @@ #include class Level; +class GlobalChunks; namespace voxels { struct RouteNode { @@ -17,6 +18,10 @@ namespace voxels { std::vector nodes; }; + struct Agent { + int height; + }; + struct Map { int width; int height; @@ -40,9 +45,14 @@ namespace voxels { class Pathfinding { public: Pathfinding(const Level& level); - - Route perform(const glm::ivec3& start, const glm::ivec3& end); + + Route perform( + const Agent& agent, const glm::ivec3& start, const glm::ivec3& end + ); private: const Level& level; + const GlobalChunks& chunks; + + int getSurfaceAt(const glm::ivec3& pos, int maxDelta); }; } From 958fc1d689fcbccd75c6817e7a0e6fa921b3088b Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sat, 2 Aug 2025 12:19:52 +0300 Subject: [PATCH 008/125] fix target reach check --- src/voxels/Pathfinding.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/voxels/Pathfinding.cpp b/src/voxels/Pathfinding.cpp index 59da59f2..4282d5bb 100644 --- a/src/voxels/Pathfinding.cpp +++ b/src/voxels/Pathfinding.cpp @@ -94,7 +94,7 @@ Route Pathfinding::perform( auto node = queue.top(); queue.pop(); - if (node.pos.x == end.x && node.pos.z == end.z) { + if (node.pos.x == end.x && glm::abs((node.pos.y - end.y) / agent.height) == 0 && node.pos.z == end.z) { restore_route(route, node, parents); route.nodes.push_back({start}); route.found = true; From caab689731719a2a70f7ad944020bd7404562845 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sat, 2 Aug 2025 17:18:40 +0300 Subject: [PATCH 009/125] add pathfinding library --- src/graphics/render/WorldRenderer.cpp | 31 +++++++++++ src/logic/scripting/lua/libs/api_lua.hpp | 1 + .../scripting/lua/libs/libpathfinding.cpp | 55 +++++++++++++++++++ src/logic/scripting/lua/lua_engine.cpp | 1 + src/voxels/Pathfinding.cpp | 24 +++++++- src/voxels/Pathfinding.hpp | 16 +++++- 6 files changed, 126 insertions(+), 2 deletions(-) create mode 100644 src/logic/scripting/lua/libs/libpathfinding.cpp diff --git a/src/graphics/render/WorldRenderer.cpp b/src/graphics/render/WorldRenderer.cpp index fba83c64..d7290998 100644 --- a/src/graphics/render/WorldRenderer.cpp +++ b/src/graphics/render/WorldRenderer.cpp @@ -27,6 +27,7 @@ #include "voxels/Block.hpp" #include "voxels/Chunk.hpp" #include "voxels/Chunks.hpp" +#include "voxels/Pathfinding.hpp" #include "window/Window.hpp" #include "world/Level.hpp" #include "world/LevelEvents.hpp" @@ -386,6 +387,36 @@ void WorldRenderer::renderFrame( skybox->draw(ctx, camera, assets, worldInfo.daytime, clouds); // In-world lines + for (const auto& [_, agent] : level.pathfinding->getAgents()) { + const auto& route = agent.route; + if (!route.found) + continue; + for (int i = 1; i < route.nodes.size(); i++) { + const auto& a = route.nodes.at(i - 1); + const auto& b = route.nodes.at(i); + + if (i == 1) { + lines->pushLine( + glm::vec3(a.pos) + glm::vec3(0.5f), + glm::vec3(a.pos) + glm::vec3(0.5f, 1.0f, 0.5f), + glm::vec4(1, 1, 1, 1) + ); + } + + lines->pushLine( + glm::vec3(a.pos) + glm::vec3(0.5f), + glm::vec3(b.pos) + glm::vec3(0.5f), + glm::vec4(1, 0, 1, 1) + ); + + lines->pushLine( + glm::vec3(b.pos) + glm::vec3(0.5f), + glm::vec3(b.pos) + glm::vec3(0.5f, 1.0f, 0.5f), + glm::vec4(1, 1, 1, 1) + ); + } + } + linesShader.use(); linesShader.uniformMatrix("u_projview", camera.getProjView()); lines->draw(*lineBatch); diff --git a/src/logic/scripting/lua/libs/api_lua.hpp b/src/logic/scripting/lua/libs/api_lua.hpp index 6e65b790..c1d9f933 100644 --- a/src/logic/scripting/lua/libs/api_lua.hpp +++ b/src/logic/scripting/lua/libs/api_lua.hpp @@ -38,6 +38,7 @@ extern const luaL_Reg mat4lib[]; extern const luaL_Reg networklib[]; extern const luaL_Reg packlib[]; extern const luaL_Reg particleslib[]; // gfx.particles +extern const luaL_Reg pathfindinglib[]; extern const luaL_Reg playerlib[]; extern const luaL_Reg posteffectslib[]; // gfx.posteffects extern const luaL_Reg quatlib[]; diff --git a/src/logic/scripting/lua/libs/libpathfinding.cpp b/src/logic/scripting/lua/libs/libpathfinding.cpp new file mode 100644 index 00000000..96ac0d13 --- /dev/null +++ b/src/logic/scripting/lua/libs/libpathfinding.cpp @@ -0,0 +1,55 @@ +#include "api_lua.hpp" + +#include "voxels/Pathfinding.hpp" +#include "world/Level.hpp" + +using namespace scripting; + +static voxels::Agent* get_agent(lua::State* L) { + return level->pathfinding->getAgent(lua::tointeger(L, 1)); +} + +static int l_create_agent(lua::State* L) { + return lua::pushinteger(L, level->pathfinding->createAgent()); +} + +static int l_set_enabled(lua::State* L) { + if (auto agent = get_agent(L)) { + agent->enabled = lua::toboolean(L, 2); + } + return 0; +} + +static int l_is_enabled(lua::State* L) { + if (auto agent = get_agent(L)) { + return lua::pushboolean(L, agent->enabled); + } + return lua::pushboolean(L, false); +} + +static int l_make_route(lua::State* L) { + if (auto agent = get_agent(L)) { + auto start = lua::tovec3(L, 2); + auto target = lua::tovec3(L, 3); + auto route = level->pathfinding->perform(*agent, start, target); + agent->route = route; + if (!route.found) { + return 0; + } + lua::createtable(L, route.nodes.size(), 0); + for (int i = 0; i < route.nodes.size(); i++) { + lua::pushvec3(L, route.nodes[i].pos); + lua::rawseti(L, i + 1); + } + return 1; + } + return 0; +} + +const luaL_Reg pathfindinglib[] = { + {"create_agent", lua::wrap}, + {"set_enabled", lua::wrap}, + {"is_enabled", lua::wrap}, + {"make_route", lua::wrap}, + {NULL, NULL} +}; diff --git a/src/logic/scripting/lua/lua_engine.cpp b/src/logic/scripting/lua/lua_engine.cpp index 891b89c7..f6ce4fe3 100644 --- a/src/logic/scripting/lua/lua_engine.cpp +++ b/src/logic/scripting/lua/lua_engine.cpp @@ -72,6 +72,7 @@ static void create_libs(State* L, StateType stateType) { openlib(L, "input", inputlib); openlib(L, "inventory", inventorylib); openlib(L, "network", networklib); + openlib(L, "pathfinding", pathfindinglib); openlib(L, "player", playerlib); openlib(L, "time", timelib); openlib(L, "world", worldlib); diff --git a/src/voxels/Pathfinding.cpp b/src/voxels/Pathfinding.cpp index 4282d5bb..54020cdd 100644 --- a/src/voxels/Pathfinding.cpp +++ b/src/voxels/Pathfinding.cpp @@ -75,6 +75,12 @@ static void restore_route( } } +int Pathfinding::createAgent() { + int id = nextAgent++; + agents[id] = Agent(); + return id; +} + Route Pathfinding::perform( const Agent& agent, const glm::ivec3& start, const glm::ivec3& end ) { @@ -89,12 +95,16 @@ Route Pathfinding::perform( std::unordered_map parents; const auto& chunks = *level.chunks; + int height = std::max(agent.height, 1); while (!queue.empty()) { + if (blocked.size() == agent.maxVisitedBlocks) { + break; + } auto node = queue.top(); queue.pop(); - if (node.pos.x == end.x && glm::abs((node.pos.y - end.y) / agent.height) == 0 && node.pos.z == end.z) { + if (node.pos.x == end.x && glm::abs((node.pos.y - end.y) / height) == 0 && node.pos.z == end.z) { restore_route(route, node, parents); route.nodes.push_back({start}); route.found = true; @@ -144,6 +154,18 @@ Route Pathfinding::perform( return route; } +Agent* Pathfinding::getAgent(int id) { + const auto& found = agents.find(id); + if (found != agents.end()) { + return &found->second; + } + return nullptr; +} + +const std::unordered_map& Pathfinding::getAgents() const { + return agents; +} + static int check_point( const ContentUnitIndices& defs, const GlobalChunks& chunks, diff --git a/src/voxels/Pathfinding.hpp b/src/voxels/Pathfinding.hpp index f743fd1c..04e039cb 100644 --- a/src/voxels/Pathfinding.hpp +++ b/src/voxels/Pathfinding.hpp @@ -4,6 +4,7 @@ #include #include #include +#include class Level; class GlobalChunks; @@ -19,7 +20,12 @@ namespace voxels { }; struct Agent { - int height; + bool enabled = false; + int height = 1; + int maxVisitedBlocks = 1e5; + glm::ivec3 start; + glm::ivec3 target; + Route route; }; struct Map { @@ -46,12 +52,20 @@ namespace voxels { public: Pathfinding(const Level& level); + int createAgent(); + Route perform( const Agent& agent, const glm::ivec3& start, const glm::ivec3& end ); + + Agent* getAgent(int id); + + const std::unordered_map& getAgents() const; private: const Level& level; const GlobalChunks& chunks; + std::unordered_map agents; + int nextAgent = 1; int getSurfaceAt(const glm::ivec3& pos, int maxDelta); }; From be3fb8346f35671df0fb0a012730291e03e53987 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sat, 2 Aug 2025 23:03:40 +0300 Subject: [PATCH 010/125] add 'mayBeIncomplete' parameter --- src/graphics/render/WorldRenderer.cpp | 51 ++++++++++------- .../scripting/lua/libs/libpathfinding.cpp | 8 +++ src/voxels/Pathfinding.cpp | 55 ++++++++++++------- src/voxels/Pathfinding.hpp | 10 +++- 4 files changed, 82 insertions(+), 42 deletions(-) diff --git a/src/graphics/render/WorldRenderer.cpp b/src/graphics/render/WorldRenderer.cpp index d7290998..149f5766 100644 --- a/src/graphics/render/WorldRenderer.cpp +++ b/src/graphics/render/WorldRenderer.cpp @@ -387,33 +387,42 @@ void WorldRenderer::renderFrame( skybox->draw(ctx, camera, assets, worldInfo.daytime, clouds); // In-world lines - for (const auto& [_, agent] : level.pathfinding->getAgents()) { - const auto& route = agent.route; - if (!route.found) - continue; - for (int i = 1; i < route.nodes.size(); i++) { - const auto& a = route.nodes.at(i - 1); - const auto& b = route.nodes.at(i); + if (debug) { + for (const auto& [_, agent] : level.pathfinding->getAgents()) { + const auto& route = agent.route; + if (!route.found) + continue; + for (const auto& blocked : route.visited) { + lines->pushLine( + glm::vec3(blocked) + glm::vec3(0.5f), + glm::vec3(blocked) + glm::vec3(0.5f, 1.0f, 0.5f), + glm::vec4(1, 0, 0, 1) + ); + } + for (int i = 1; i < route.nodes.size(); i++) { + const auto& a = route.nodes.at(i - 1); + const auto& b = route.nodes.at(i); + + if (i == 1) { + lines->pushLine( + glm::vec3(a.pos) + glm::vec3(0.5f), + glm::vec3(a.pos) + glm::vec3(0.5f, 1.0f, 0.5f), + glm::vec4(1, 1, 1, 1) + ); + } - if (i == 1) { lines->pushLine( glm::vec3(a.pos) + glm::vec3(0.5f), - glm::vec3(a.pos) + glm::vec3(0.5f, 1.0f, 0.5f), + glm::vec3(b.pos) + glm::vec3(0.5f), + glm::vec4(1, 0, 1, 1) + ); + + lines->pushLine( + glm::vec3(b.pos) + glm::vec3(0.5f), + glm::vec3(b.pos) + glm::vec3(0.5f, 1.0f, 0.5f), glm::vec4(1, 1, 1, 1) ); } - - lines->pushLine( - glm::vec3(a.pos) + glm::vec3(0.5f), - glm::vec3(b.pos) + glm::vec3(0.5f), - glm::vec4(1, 0, 1, 1) - ); - - lines->pushLine( - glm::vec3(b.pos) + glm::vec3(0.5f), - glm::vec3(b.pos) + glm::vec3(0.5f, 1.0f, 0.5f), - glm::vec4(1, 1, 1, 1) - ); } } diff --git a/src/logic/scripting/lua/libs/libpathfinding.cpp b/src/logic/scripting/lua/libs/libpathfinding.cpp index 96ac0d13..e66b1f2a 100644 --- a/src/logic/scripting/lua/libs/libpathfinding.cpp +++ b/src/logic/scripting/lua/libs/libpathfinding.cpp @@ -46,10 +46,18 @@ static int l_make_route(lua::State* L) { return 0; } +static int l_set_max_visited_blocks(lua::State* L) { + if (auto agent = get_agent(L)) { + agent->maxVisitedBlocks = lua::tointeger(L, 2); + } + return 0; +} + const luaL_Reg pathfindinglib[] = { {"create_agent", lua::wrap}, {"set_enabled", lua::wrap}, {"is_enabled", lua::wrap}, {"make_route", lua::wrap}, + {"set_max_visited", lua::wrap}, {NULL, NULL} }; diff --git a/src/voxels/Pathfinding.cpp b/src/voxels/Pathfinding.cpp index 54020cdd..829bbde8 100644 --- a/src/voxels/Pathfinding.cpp +++ b/src/voxels/Pathfinding.cpp @@ -1,10 +1,6 @@ #include "Pathfinding.hpp" -#define GLM_ENABLE_EXPERIMENTAL -#include - #include -#include #include #include "world/Level.hpp" @@ -13,6 +9,8 @@ #include "voxels/blocks_agent.hpp" #include "content/Content.hpp" +inline constexpr float SQRT2 = 1.4142135623730951f; // sqrt(2) + using namespace voxels; struct Node { @@ -28,7 +26,7 @@ struct NodeLess { } }; -static float distance(const glm::ivec3& a, const glm::ivec3& b) { +static float heuristic(const glm::ivec3& a, const glm::ivec3& b) { return glm::distance(glm::vec3(a), glm::vec3(b)); } @@ -60,10 +58,10 @@ static bool check_passability( static void restore_route( Route& route, - const Node& node, + const glm::ivec3& lastPos, const std::unordered_map& parents ) { - auto pos = node.pos; + auto pos = lastPos; while (true) { const auto& found = parents.find(pos); if (found == parents.end()) { @@ -89,7 +87,7 @@ Route Pathfinding::perform( Route route {}; std::priority_queue, NodeLess> queue; - queue.push({start, {}, 0, distance(start, end)}); + queue.push({start, {}, 0, heuristic(start, end)}); std::unordered_set blocked; std::unordered_map parents; @@ -97,18 +95,31 @@ Route Pathfinding::perform( const auto& chunks = *level.chunks; int height = std::max(agent.height, 1); + glm::ivec3 nearest = start; + float minHScore = heuristic(start, end); + while (!queue.empty()) { if (blocked.size() == agent.maxVisitedBlocks) { + if (agent.mayBeIncomplete) { + restore_route(route, nearest, parents); + route.nodes.push_back({start}); + route.found = true; + route.visited = std::move(blocked); + return route; + } break; } auto node = queue.top(); queue.pop(); - if (node.pos.x == end.x && glm::abs((node.pos.y - end.y) / height) == 0 && node.pos.z == end.z) { - restore_route(route, node, parents); + if (node.pos.x == end.x && + glm::abs((node.pos.y - end.y) / height) == 0 && + node.pos.z == end.z) { + restore_route(route, node.pos, parents); route.nodes.push_back({start}); route.found = true; - break; + route.visited = std::move(blocked); + return route; } blocked.emplace(node.pos); @@ -128,6 +139,9 @@ Route Pathfinding::perform( } pos.y = surface; auto point = pos + glm::ivec3(offset.x, 0, offset.y); + if (blocked.find(point) != blocked.end()) { + continue; + } if (is_obstacle_at(chunks, pos.x, pos.y + agent.height / 2, pos.z)) { continue; @@ -135,18 +149,21 @@ Route Pathfinding::perform( if (!check_passability(agent, chunks, node, offset, i >= 4)) { continue; } - if (blocked.find(point) != blocked.end()) { - continue; - } - int score = glm::abs(node.pos.y - pos.y); + + int score = glm::abs(node.pos.y - pos.y) * 10; + float sum = glm::abs(offset.x) + glm::abs(offset.y); float gScore = - node.gScore + glm::abs(offset.x) + glm::abs(offset.y) + score; + node.gScore + glm::max(sum, SQRT2) * 0.5f + sum * 0.5f + score; const auto& found = parents.find(point); - if (found == parents.end() || gScore < found->second.gScore) { - float hScore = distance(point, end); + if (found == parents.end()) { + float hScore = heuristic(point, end); + if (hScore < minHScore) { + minHScore = hScore; + nearest = point; + } float fScore = gScore + hScore; Node nNode {point, node.pos, gScore, fScore}; - parents[point] = Node {node.pos, node.parent, gScore, fScore}; + parents[point] = node; queue.push(nNode); } } diff --git a/src/voxels/Pathfinding.hpp b/src/voxels/Pathfinding.hpp index 04e039cb..4b0b0008 100644 --- a/src/voxels/Pathfinding.hpp +++ b/src/voxels/Pathfinding.hpp @@ -1,10 +1,14 @@ #pragma once +#define GLM_ENABLE_EXPERIMENTAL +#include + #include #include #include #include #include +#include class Level; class GlobalChunks; @@ -17,12 +21,14 @@ namespace voxels { struct Route { bool found; std::vector nodes; + std::unordered_set visited; }; struct Agent { bool enabled = false; - int height = 1; - int maxVisitedBlocks = 1e5; + bool mayBeIncomplete = true; + int height = 2; + int maxVisitedBlocks = 1e3; glm::ivec3 start; glm::ivec3 target; Route route; From a78931205f7e6835c2e4d4760ac66979a9e3b3d2 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sun, 3 Aug 2025 01:41:18 +0300 Subject: [PATCH 011/125] feat: async pathfinding --- src/graphics/render/WorldRenderer.cpp | 68 +++++------ src/io/settings_io.cpp | 3 + src/logic/LevelController.cpp | 4 + .../scripting/lua/libs/libpathfinding.cpp | 39 ++++++- src/settings.hpp | 6 + src/voxels/Pathfinding.cpp | 109 +++++++++--------- src/voxels/Pathfinding.hpp | 51 ++++---- 7 files changed, 168 insertions(+), 112 deletions(-) diff --git a/src/graphics/render/WorldRenderer.cpp b/src/graphics/render/WorldRenderer.cpp index 149f5766..8db4f4c7 100644 --- a/src/graphics/render/WorldRenderer.cpp +++ b/src/graphics/render/WorldRenderer.cpp @@ -274,6 +274,39 @@ void WorldRenderer::renderLines( } } +static void draw_route( + LinesRenderer& lines, const voxels::Agent& agent +) { + const auto& route = agent.route; + if (!route.found) + return; + + for (int i = 1; i < route.nodes.size(); i++) { + const auto& a = route.nodes.at(i - 1); + const auto& b = route.nodes.at(i); + + if (i == 1) { + lines.pushLine( + glm::vec3(a.pos) + glm::vec3(0.5f), + glm::vec3(a.pos) + glm::vec3(0.5f, 1.0f, 0.5f), + glm::vec4(1, 1, 1, 1) + ); + } + + lines.pushLine( + glm::vec3(a.pos) + glm::vec3(0.5f), + glm::vec3(b.pos) + glm::vec3(0.5f), + glm::vec4(1, 0, 1, 1) + ); + + lines.pushLine( + glm::vec3(b.pos) + glm::vec3(0.5f), + glm::vec3(b.pos) + glm::vec3(0.5f, 1.0f, 0.5f), + glm::vec4(1, 1, 1, 1) + ); + } +} + void WorldRenderer::renderFrame( const DrawContext& pctx, Camera& camera, @@ -389,40 +422,7 @@ void WorldRenderer::renderFrame( // In-world lines if (debug) { for (const auto& [_, agent] : level.pathfinding->getAgents()) { - const auto& route = agent.route; - if (!route.found) - continue; - for (const auto& blocked : route.visited) { - lines->pushLine( - glm::vec3(blocked) + glm::vec3(0.5f), - glm::vec3(blocked) + glm::vec3(0.5f, 1.0f, 0.5f), - glm::vec4(1, 0, 0, 1) - ); - } - for (int i = 1; i < route.nodes.size(); i++) { - const auto& a = route.nodes.at(i - 1); - const auto& b = route.nodes.at(i); - - if (i == 1) { - lines->pushLine( - glm::vec3(a.pos) + glm::vec3(0.5f), - glm::vec3(a.pos) + glm::vec3(0.5f, 1.0f, 0.5f), - glm::vec4(1, 1, 1, 1) - ); - } - - lines->pushLine( - glm::vec3(a.pos) + glm::vec3(0.5f), - glm::vec3(b.pos) + glm::vec3(0.5f), - glm::vec4(1, 0, 1, 1) - ); - - lines->pushLine( - glm::vec3(b.pos) + glm::vec3(0.5f), - glm::vec3(b.pos) + glm::vec3(0.5f, 1.0f, 0.5f), - glm::vec4(1, 1, 1, 1) - ); - } + draw_route(*lines, agent); } } diff --git a/src/io/settings_io.cpp b/src/io/settings_io.cpp index cdbf567e..b69be64c 100644 --- a/src/io/settings_io.cpp +++ b/src/io/settings_io.cpp @@ -83,6 +83,9 @@ SettingsHandler::SettingsHandler(EngineSettings& settings) { builder.add("language", &settings.ui.language); builder.add("world-preview-size", &settings.ui.worldPreviewSize); + builder.section("pathfinding"); + builder.add("steps-per-async-agent", &settings.pathfinding.stepsPerAsyncAgent); + builder.section("debug"); builder.add("generator-test-mode", &settings.debug.generatorTestMode); builder.add("do-write-lights", &settings.debug.doWriteLights); diff --git a/src/logic/LevelController.cpp b/src/logic/LevelController.cpp index f1aca628..18225ea0 100644 --- a/src/logic/LevelController.cpp +++ b/src/logic/LevelController.cpp @@ -11,6 +11,7 @@ #include "objects/Player.hpp" #include "physics/Hitbox.hpp" #include "voxels/Chunks.hpp" +#include "voxels/Pathfinding.hpp" #include "scripting/scripting.hpp" #include "lighting/Lighting.hpp" #include "settings.hpp" @@ -69,6 +70,9 @@ LevelController::LevelController( } void LevelController::update(float delta, bool pause) { + level->pathfinding->performAllAsync( + settings.pathfinding.stepsPerAsyncAgent.get() + ); for (const auto& [_, player] : *level->players) { if (player->isSuspended()) { continue; diff --git a/src/logic/scripting/lua/libs/libpathfinding.cpp b/src/logic/scripting/lua/libs/libpathfinding.cpp index e66b1f2a..4d2374a1 100644 --- a/src/logic/scripting/lua/libs/libpathfinding.cpp +++ b/src/logic/scripting/lua/libs/libpathfinding.cpp @@ -31,8 +31,10 @@ static int l_make_route(lua::State* L) { if (auto agent = get_agent(L)) { auto start = lua::tovec3(L, 2); auto target = lua::tovec3(L, 3); - auto route = level->pathfinding->perform(*agent, start, target); - agent->route = route; + agent->state = {}; + agent->start = start; + agent->target = target; + auto route = level->pathfinding->perform(*agent); if (!route.found) { return 0; } @@ -46,6 +48,37 @@ static int l_make_route(lua::State* L) { return 0; } +static int l_make_route_async(lua::State* L) { + if (auto agent = get_agent(L)) { + auto start = lua::tovec3(L, 2); + auto target = lua::tovec3(L, 3); + agent->state = {}; + agent->start = start; + agent->target = target; + level->pathfinding->perform(*agent, 0); + } + return 0; +} + +static int l_pull_route(lua::State* L) { + if (auto agent = get_agent(L)) { + auto& route = agent->route; + if (!agent->state.finished) { + return 0; + } + if (!route.found) { + return lua::createtable(L, 0, 0); + } + lua::createtable(L, route.nodes.size(), 0); + for (int i = 0; i < route.nodes.size(); i++) { + lua::pushvec3(L, route.nodes[i].pos); + lua::rawseti(L, i + 1); + } + return 1; + } + return 0; +} + static int l_set_max_visited_blocks(lua::State* L) { if (auto agent = get_agent(L)) { agent->maxVisitedBlocks = lua::tointeger(L, 2); @@ -58,6 +91,8 @@ const luaL_Reg pathfindinglib[] = { {"set_enabled", lua::wrap}, {"is_enabled", lua::wrap}, {"make_route", lua::wrap}, + {"make_route_async", lua::wrap}, + {"pull_route", lua::wrap}, {"set_max_visited", lua::wrap}, {NULL, NULL} }; diff --git a/src/settings.hpp b/src/settings.hpp index d4c21688..d51079a2 100644 --- a/src/settings.hpp +++ b/src/settings.hpp @@ -85,6 +85,11 @@ struct GraphicsSettings { IntegerSetting denseRenderDistance {56, 0, 10'000}; }; +struct PathfindingSettings { + /// @brief Max visited blocks by an agent per async tick + IntegerSetting stepsPerAsyncAgent {256, 1, 2048}; +}; + struct DebugSettings { /// @brief Turns off chunks saving/loading FlagSetting generatorTestMode {false}; @@ -109,4 +114,5 @@ struct EngineSettings { DebugSettings debug; UiSettings ui; NetworkSettings network; + PathfindingSettings pathfinding; }; diff --git a/src/voxels/Pathfinding.cpp b/src/voxels/Pathfinding.cpp index 829bbde8..b39fe99e 100644 --- a/src/voxels/Pathfinding.cpp +++ b/src/voxels/Pathfinding.cpp @@ -1,8 +1,5 @@ #include "Pathfinding.hpp" -#include -#include - #include "world/Level.hpp" #include "voxels/GlobalChunks.hpp" #include "voxels/Chunk.hpp" @@ -13,19 +10,6 @@ inline constexpr float SQRT2 = 1.4142135623730951f; // sqrt(2) using namespace voxels; -struct Node { - glm::ivec3 pos; - glm::ivec3 parent; - float gScore; - float fScore; -}; - -struct NodeLess { - bool operator()(const Node& l, const Node& r) const { - return l.fScore > r.fScore; - } -}; - static float heuristic(const glm::ivec3& a, const glm::ivec3& b) { return glm::distance(glm::vec3(a), glm::vec3(b)); } @@ -79,50 +63,70 @@ int Pathfinding::createAgent() { return id; } -Route Pathfinding::perform( - const Agent& agent, const glm::ivec3& start, const glm::ivec3& end -) { +void Pathfinding::performAllAsync(int stepsPerAgent) { + for (auto& [id, agent] : agents) { + if (agent.state.finished) { + continue; + } + perform(agent, stepsPerAgent); + } +} + +Route Pathfinding::perform(Agent& agent, int maxVisited) { using namespace blocks_agent; Route route {}; - std::priority_queue, NodeLess> queue; - queue.push({start, {}, 0, heuristic(start, end)}); - - std::unordered_set blocked; - std::unordered_map parents; + State state = std::move(agent.state); + if (state.queue.empty()) { + state.queue.push({agent.start, {}, 0, heuristic(agent.start, agent.target)}); + } const auto& chunks = *level.chunks; int height = std::max(agent.height, 1); - glm::ivec3 nearest = start; - float minHScore = heuristic(start, end); + if (state.nearest == glm::ivec3(0)) { + state.nearest = agent.start; + state.minHScore = heuristic(agent.start, agent.target); + } + int visited = -1; - while (!queue.empty()) { - if (blocked.size() == agent.maxVisitedBlocks) { + while (!state.queue.empty()) { + if (state.blocked.size() == agent.maxVisitedBlocks) { if (agent.mayBeIncomplete) { - restore_route(route, nearest, parents); - route.nodes.push_back({start}); + restore_route(route, state.nearest, state.parents); + route.nodes.push_back({agent.start}); route.found = true; - route.visited = std::move(blocked); + state.finished = true; + agent.state = std::move(state); + agent.route = route; return route; } break; } - auto node = queue.top(); - queue.pop(); + visited++; + if (visited == maxVisited) { + state.finished = false; + agent.state = std::move(state); + return {}; + } - if (node.pos.x == end.x && - glm::abs((node.pos.y - end.y) / height) == 0 && - node.pos.z == end.z) { - restore_route(route, node.pos, parents); - route.nodes.push_back({start}); + auto node = state.queue.top(); + state.queue.pop(); + + if (node.pos.x == agent.target.x && + glm::abs((node.pos.y - agent.target.y) / height) == 0 && + node.pos.z == agent.target.z) { + restore_route(route, node.pos, state.parents); + route.nodes.push_back({agent.start}); route.found = true; - route.visited = std::move(blocked); + state.finished = true; + agent.state = std::move(state); + agent.route = route; return route; } - blocked.emplace(node.pos); + state.blocked.emplace(node.pos); glm::ivec2 neighbors[8] { {0, 1}, {1, 0}, {0, -1}, {-1, 0}, {-1, -1}, {1, -1}, {1, 1}, {-1, 1}, @@ -139,7 +143,7 @@ Route Pathfinding::perform( } pos.y = surface; auto point = pos + glm::ivec3(offset.x, 0, offset.y); - if (blocked.find(point) != blocked.end()) { + if (state.blocked.find(point) != state.blocked.end()) { continue; } @@ -153,22 +157,23 @@ Route Pathfinding::perform( int score = glm::abs(node.pos.y - pos.y) * 10; float sum = glm::abs(offset.x) + glm::abs(offset.y); float gScore = - node.gScore + glm::max(sum, SQRT2) * 0.5f + sum * 0.5f + score; - const auto& found = parents.find(point); - if (found == parents.end()) { - float hScore = heuristic(point, end); - if (hScore < minHScore) { - minHScore = hScore; - nearest = point; + node.gScore + sum + score; + const auto& found = state.parents.find(point); + if (found == state.parents.end()) { + float hScore = heuristic(point, agent.target); + if (hScore < state.minHScore) { + state.minHScore = hScore; + state.nearest = point; } - float fScore = gScore + hScore; + float fScore = gScore * 0.75f + hScore; Node nNode {point, node.pos, gScore, fScore}; - parents[point] = node; - queue.push(nNode); + state.parents[point] = node; + state.queue.push(nNode); } } } - return route; + agent.state = std::move(state); + return {}; } Agent* Pathfinding::getAgent(int id) { diff --git a/src/voxels/Pathfinding.hpp b/src/voxels/Pathfinding.hpp index 4b0b0008..c89841e5 100644 --- a/src/voxels/Pathfinding.hpp +++ b/src/voxels/Pathfinding.hpp @@ -5,6 +5,7 @@ #include #include +#include #include #include #include @@ -21,7 +22,28 @@ namespace voxels { struct Route { bool found; std::vector nodes; - std::unordered_set visited; + }; + + struct Node { + glm::ivec3 pos; + glm::ivec3 parent; + float gScore; + float fScore; + }; + + struct NodeLess { + bool operator()(const Node& l, const Node& r) const { + return l.fScore > r.fScore; + } + }; + + struct State { + std::priority_queue, NodeLess> queue; + std::unordered_set blocked; + std::unordered_map parents; + glm::ivec3 nearest; + float minHScore; + bool finished = true; }; struct Agent { @@ -32,26 +54,7 @@ namespace voxels { glm::ivec3 start; glm::ivec3 target; Route route; - }; - - struct Map { - int width; - int height; - std::unique_ptr map; - - Map(int width, int height) - : width(width), - height(height), - map(std::make_unique(width * height)) { - } - - uint8_t& operator[](int i) { - return map[i]; - } - - const uint8_t& operator[](int i) const { - return map[i]; - } + State state {}; }; class Pathfinding { @@ -60,9 +63,9 @@ namespace voxels { int createAgent(); - Route perform( - const Agent& agent, const glm::ivec3& start, const glm::ivec3& end - ); + void performAllAsync(int stepsPerAgent); + + Route perform(Agent& agent, int maxVisited = -1); Agent* getAgent(int id); From 3aac6ecbcbc0b8865282cdce13c3f211e7a05774 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sun, 3 Aug 2025 02:03:20 +0300 Subject: [PATCH 012/125] refactor: extract DebugLinesRenderer --- ...desRenderer.cpp => DebugLinesRenderer.cpp} | 66 +++++++++++++++---- src/graphics/render/DebugLinesRenderer.hpp | 39 +++++++++++ src/graphics/render/GuidesRenderer.hpp | 28 -------- src/graphics/render/WorldRenderer.cpp | 64 +++--------------- src/graphics/render/WorldRenderer.hpp | 4 +- 5 files changed, 104 insertions(+), 97 deletions(-) rename src/graphics/render/{GuidesRenderer.cpp => DebugLinesRenderer.cpp} (66%) create mode 100644 src/graphics/render/DebugLinesRenderer.hpp delete mode 100644 src/graphics/render/GuidesRenderer.hpp diff --git a/src/graphics/render/GuidesRenderer.cpp b/src/graphics/render/DebugLinesRenderer.cpp similarity index 66% rename from src/graphics/render/GuidesRenderer.cpp rename to src/graphics/render/DebugLinesRenderer.cpp index 285793f4..ce6512e5 100644 --- a/src/graphics/render/GuidesRenderer.cpp +++ b/src/graphics/render/DebugLinesRenderer.cpp @@ -1,15 +1,49 @@ -#include "GuidesRenderer.hpp" - -#include +#include "DebugLinesRenderer.hpp" #include "graphics/core/Shader.hpp" +#include "window/Camera.hpp" #include "graphics/core/LineBatch.hpp" #include "graphics/core/DrawContext.hpp" +#include "graphics/render/LinesRenderer.hpp" +#include "world/Level.hpp" +#include "voxels/Chunk.hpp" +#include "voxels/Pathfinding.hpp" #include "maths/voxmaths.hpp" -#include "window/Camera.hpp" -#include "constants.hpp" -void GuidesRenderer::drawBorders( +static void draw_route( + LinesRenderer& lines, const voxels::Agent& agent +) { + const auto& route = agent.route; + if (!route.found) + return; + + for (int i = 1; i < route.nodes.size(); i++) { + const auto& a = route.nodes.at(i - 1); + const auto& b = route.nodes.at(i); + + if (i == 1) { + lines.pushLine( + glm::vec3(a.pos) + glm::vec3(0.5f), + glm::vec3(a.pos) + glm::vec3(0.5f, 1.0f, 0.5f), + glm::vec4(1, 1, 1, 1) + ); + } + + lines.pushLine( + glm::vec3(a.pos) + glm::vec3(0.5f), + glm::vec3(b.pos) + glm::vec3(0.5f), + glm::vec4(1, 0, 1, 1) + ); + + lines.pushLine( + glm::vec3(b.pos) + glm::vec3(0.5f), + glm::vec3(b.pos) + glm::vec3(0.5f, 1.0f, 0.5f), + glm::vec4(1, 1, 1, 1) + ); + } +} + +void DebugLinesRenderer::drawBorders( LineBatch& batch, int sx, int sy, int sz, int ex, int ey, int ez ) { int ww = ex - sx; @@ -37,7 +71,7 @@ void GuidesRenderer::drawBorders( batch.flush(); } -void GuidesRenderer::drawCoordSystem( +void DebugLinesRenderer::drawCoordSystem( LineBatch& batch, const DrawContext& pctx, float length ) { auto ctx = pctx.sub(); @@ -55,14 +89,20 @@ void GuidesRenderer::drawCoordSystem( batch.line(0.f, 0.f, 0.f, 0.f, 0.f, length, 0.f, 0.f, 1.f, 1.f); } -void GuidesRenderer::renderDebugLines( - const DrawContext& pctx, + +void DebugLinesRenderer::render( + DrawContext& pctx, const Camera& camera, - LineBatch& batch, + LinesRenderer& renderer, + LineBatch& linesBatch, Shader& linesShader, bool showChunkBorders ) { - DrawContext ctx = pctx.sub(&batch); + // In-world lines + for (const auto& [_, agent] : level.pathfinding->getAgents()) { + draw_route(renderer, agent); + } + DrawContext ctx = pctx.sub(&linesBatch); const auto& viewport = ctx.getViewport(); ctx.setDepthTest(true); @@ -78,7 +118,7 @@ void GuidesRenderer::renderDebugLines( int cz = floordiv(static_cast(coord.z), CHUNK_D); drawBorders( - batch, + linesBatch, cx * CHUNK_W, 0, cz * CHUNK_D, @@ -103,5 +143,5 @@ void GuidesRenderer::renderDebugLines( ) * model * glm::inverse(camera.rotation) ); - drawCoordSystem(batch, ctx, length); + drawCoordSystem(linesBatch, ctx, length); } diff --git a/src/graphics/render/DebugLinesRenderer.hpp b/src/graphics/render/DebugLinesRenderer.hpp new file mode 100644 index 00000000..ba4e83f9 --- /dev/null +++ b/src/graphics/render/DebugLinesRenderer.hpp @@ -0,0 +1,39 @@ +#pragma once + +class DrawContext; +class Camera; +class LineBatch; +class LinesRenderer; +class Shader; +class Level; + +class DebugLinesRenderer { +public: + DebugLinesRenderer(const Level& level) + : level(level) {}; + + /// @brief Render debug lines in the world + /// @param ctx Draw context + /// @param camera Camera used for rendering + /// @param renderer Lines renderer used for rendering lines + /// @param linesShader Shader used for rendering lines + /// @param showChunkBorders Whether to show chunk borders + void render( + DrawContext& ctx, + const Camera& camera, + LinesRenderer& renderer, + LineBatch& linesBatch, + Shader& linesShader, + bool showChunkBorders + ); +private: + const Level& level; + + void drawBorders( + LineBatch& batch, int sx, int sy, int sz, int ex, int ey, int ez + ); + void drawCoordSystem( + LineBatch& batch, const DrawContext& pctx, float length + ); + +}; diff --git a/src/graphics/render/GuidesRenderer.hpp b/src/graphics/render/GuidesRenderer.hpp deleted file mode 100644 index 36daad82..00000000 --- a/src/graphics/render/GuidesRenderer.hpp +++ /dev/null @@ -1,28 +0,0 @@ -#pragma once - -class LineBatch; -class DrawContext; -class Camera; -class Shader; - -class GuidesRenderer { -public: - void drawBorders( - LineBatch& batch, int sx, int sy, int sz, int ex, int ey, int ez - ); - void drawCoordSystem( - LineBatch& batch, const DrawContext& pctx, float length - ); - - /// @brief Render all debug lines (chunks borders, coord system guides) - /// @param context graphics context - /// @param camera active camera - /// @param linesShader shader used - void renderDebugLines( - const DrawContext& context, - const Camera& camera, - LineBatch& batch, - Shader& linesShader, - bool showChunkBorders - ); -}; diff --git a/src/graphics/render/WorldRenderer.cpp b/src/graphics/render/WorldRenderer.cpp index 8db4f4c7..935b887c 100644 --- a/src/graphics/render/WorldRenderer.cpp +++ b/src/graphics/render/WorldRenderer.cpp @@ -52,8 +52,8 @@ #include "NamedSkeletons.hpp" #include "TextsRenderer.hpp" #include "ChunksRenderer.hpp" -#include "GuidesRenderer.hpp" #include "LinesRenderer.hpp" +#include "DebugLinesRenderer.hpp" #include "ModelBatch.hpp" #include "Skybox.hpp" #include "Emitter.hpp" @@ -80,7 +80,6 @@ WorldRenderer::WorldRenderer( modelBatch(std::make_unique( MODEL_BATCH_CAPACITY, assets, *player.chunks, engine.getSettings() )), - guides(std::make_unique()), chunksRenderer(std::make_unique( &level, *player.chunks, @@ -120,6 +119,7 @@ WorldRenderer::WorldRenderer( ); lines = std::make_unique(); shadowMapping = std::make_unique(level); + debugLines = std::make_unique(level); } WorldRenderer::~WorldRenderer() = default; @@ -274,39 +274,6 @@ void WorldRenderer::renderLines( } } -static void draw_route( - LinesRenderer& lines, const voxels::Agent& agent -) { - const auto& route = agent.route; - if (!route.found) - return; - - for (int i = 1; i < route.nodes.size(); i++) { - const auto& a = route.nodes.at(i - 1); - const auto& b = route.nodes.at(i); - - if (i == 1) { - lines.pushLine( - glm::vec3(a.pos) + glm::vec3(0.5f), - glm::vec3(a.pos) + glm::vec3(0.5f, 1.0f, 0.5f), - glm::vec4(1, 1, 1, 1) - ); - } - - lines.pushLine( - glm::vec3(a.pos) + glm::vec3(0.5f), - glm::vec3(b.pos) + glm::vec3(0.5f), - glm::vec4(1, 0, 1, 1) - ); - - lines.pushLine( - glm::vec3(b.pos) + glm::vec3(0.5f), - glm::vec3(b.pos) + glm::vec3(0.5f, 1.0f, 0.5f), - glm::vec4(1, 1, 1, 1) - ); - } -} - void WorldRenderer::renderFrame( const DrawContext& pctx, Camera& camera, @@ -316,6 +283,9 @@ void WorldRenderer::renderFrame( PostProcessing& postProcessing ) { // TODO: REFACTOR WHOLE RENDER ENGINE + + auto projView = camera.getProjView(); + float delta = uiDelta * !pause; timer += delta; weather.update(delta); @@ -373,9 +343,6 @@ void WorldRenderer::renderFrame( setupWorldShader(shader, shadowCamera, engine.getSettings(), 0.0f); chunksRenderer->drawShadowsPass(shadowCamera, shader, camera); }); - - auto& linesShader = assets.require("lines"); - { DrawContext wctx = pctx.sub(); postProcessing.use(wctx, gbufferPipeline); @@ -387,14 +354,6 @@ void WorldRenderer::renderFrame( ctx.setDepthTest(true); ctx.setCullFace(true); renderOpaque(ctx, camera, settings, uiDelta, pause, hudVisible); - // Debug lines - if (hudVisible) { - if (debug) { - guides->renderDebugLines( - ctx, camera, *lineBatch, linesShader, showChunkBorders - ); - } - } } texts->render(pctx, camera, settings, hudVisible, true); } @@ -419,15 +378,12 @@ void WorldRenderer::renderFrame( // Background sky plane skybox->draw(ctx, camera, assets, worldInfo.daytime, clouds); - // In-world lines - if (debug) { - for (const auto& [_, agent] : level.pathfinding->getAgents()) { - draw_route(*lines, agent); - } - } - + auto& linesShader = assets.require("lines"); linesShader.use(); - linesShader.uniformMatrix("u_projview", camera.getProjView()); + debugLines->render( + ctx, camera, *lines, *lineBatch, linesShader, showChunkBorders + ); + linesShader.uniformMatrix("u_projview", projView); lines->draw(*lineBatch); lineBatch->flush(); diff --git a/src/graphics/render/WorldRenderer.hpp b/src/graphics/render/WorldRenderer.hpp index 955568fa..4c8911db 100644 --- a/src/graphics/render/WorldRenderer.hpp +++ b/src/graphics/render/WorldRenderer.hpp @@ -22,7 +22,6 @@ class BlockWrapsRenderer; class PrecipitationRenderer; class HandsRenderer; class NamedSkeletons; -class GuidesRenderer; class LinesRenderer; class TextsRenderer; class Shader; @@ -36,6 +35,7 @@ class ModelBatch; class Assets; class Shadows; class GBuffer; +class DebugLinesRenderer; struct EngineSettings; struct CompileTimeShaderSettings { @@ -53,11 +53,11 @@ class WorldRenderer { std::unique_ptr lineBatch; std::unique_ptr batch3d; std::unique_ptr modelBatch; - std::unique_ptr guides; std::unique_ptr chunksRenderer; std::unique_ptr hands; std::unique_ptr skybox; std::unique_ptr shadowMapping; + std::unique_ptr debugLines; Weather weather {}; float timer = 0.0f; From 5a6537c89d65f459f73feb5cd7efe1319587c7b9 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sun, 3 Aug 2025 02:48:52 +0300 Subject: [PATCH 013/125] add pathfinding.remove_agent --- src/logic/scripting/lua/libs/libpathfinding.cpp | 6 ++++++ src/voxels/Pathfinding.cpp | 9 +++++++++ src/voxels/Pathfinding.hpp | 2 ++ 3 files changed, 17 insertions(+) diff --git a/src/logic/scripting/lua/libs/libpathfinding.cpp b/src/logic/scripting/lua/libs/libpathfinding.cpp index 4d2374a1..0370e3a2 100644 --- a/src/logic/scripting/lua/libs/libpathfinding.cpp +++ b/src/logic/scripting/lua/libs/libpathfinding.cpp @@ -13,6 +13,11 @@ static int l_create_agent(lua::State* L) { return lua::pushinteger(L, level->pathfinding->createAgent()); } +static int l_remove_agent(lua::State* L) { + int id = lua::tointeger(L, 1); + return lua::pushboolean(L, level->pathfinding->removeAgent(id)); +} + static int l_set_enabled(lua::State* L) { if (auto agent = get_agent(L)) { agent->enabled = lua::toboolean(L, 2); @@ -88,6 +93,7 @@ static int l_set_max_visited_blocks(lua::State* L) { const luaL_Reg pathfindinglib[] = { {"create_agent", lua::wrap}, + {"remove_agent", lua::wrap}, {"set_enabled", lua::wrap}, {"is_enabled", lua::wrap}, {"make_route", lua::wrap}, diff --git a/src/voxels/Pathfinding.cpp b/src/voxels/Pathfinding.cpp index b39fe99e..51079f61 100644 --- a/src/voxels/Pathfinding.cpp +++ b/src/voxels/Pathfinding.cpp @@ -63,6 +63,15 @@ int Pathfinding::createAgent() { return id; } +bool Pathfinding::removeAgent(int id) { + auto found = agents.find(id); + if (found != agents.end()) { + agents.erase(found); + return true; + } + return false; +} + void Pathfinding::performAllAsync(int stepsPerAgent) { for (auto& [id, agent] : agents) { if (agent.state.finished) { diff --git a/src/voxels/Pathfinding.hpp b/src/voxels/Pathfinding.hpp index c89841e5..f7b3b0c4 100644 --- a/src/voxels/Pathfinding.hpp +++ b/src/voxels/Pathfinding.hpp @@ -63,6 +63,8 @@ namespace voxels { int createAgent(); + bool removeAgent(int id); + void performAllAsync(int stepsPerAgent); Route perform(Agent& agent, int maxVisited = -1); From 5310b6325ebdb4932d254c500963c3aea2bb5c00 Mon Sep 17 00:00:00 2001 From: Xertis <118364459+Xertis@users.noreply.github.com> Date: Sun, 3 Aug 2025 14:07:32 +0300 Subject: [PATCH 014/125] fix hud.lua --- res/scripts/hud.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/scripts/hud.lua b/res/scripts/hud.lua index abd1eaf4..cf2153a2 100644 --- a/res/scripts/hud.lua +++ b/res/scripts/hud.lua @@ -96,7 +96,7 @@ local function update_hand() local rotation = cam:get_rot() - local angle = player.get_rot() - 90 + local angle = player.get_rot(pid) - 90 local cos = math.cos(angle / (180 / math.pi)) local sin = math.sin(angle / (180 / math.pi)) From a110022ec2d678d77922748b7cc7c7df09b57243 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sun, 3 Aug 2025 19:06:22 +0300 Subject: [PATCH 015/125] add 'core:pathfinding' component --- res/scripts/components/pathfinding.lua | 39 ++++++++++++++++++++++++++ src/graphics/render/WorldRenderer.cpp | 8 ++++-- src/voxels/Pathfinding.cpp | 1 + 3 files changed, 45 insertions(+), 3 deletions(-) create mode 100644 res/scripts/components/pathfinding.lua diff --git a/res/scripts/components/pathfinding.lua b/res/scripts/components/pathfinding.lua new file mode 100644 index 00000000..31d6d780 --- /dev/null +++ b/res/scripts/components/pathfinding.lua @@ -0,0 +1,39 @@ +local target +local route +local started + +local tsf = entity.transform + +agent = pathfinding.create_agent() +pathfinding.set_max_visited(agent, 100000) + +function set_target(new_target) + target = new_target +end + +function get_target() + return target +end + +function get_route() + return route +end + +function on_update() + if not started then + if target then + pathfinding.make_route_async(agent, tsf:get_pos(), target) + started = true + end + else + local new_route = pathfinding.pull_route(agent) + if new_route then + route = new_route + started = false + end + end +end + +function on_despawn() + pathfinding.remove_agent(agent) +end diff --git a/src/graphics/render/WorldRenderer.cpp b/src/graphics/render/WorldRenderer.cpp index 935b887c..eef4c41e 100644 --- a/src/graphics/render/WorldRenderer.cpp +++ b/src/graphics/render/WorldRenderer.cpp @@ -380,9 +380,11 @@ void WorldRenderer::renderFrame( auto& linesShader = assets.require("lines"); linesShader.use(); - debugLines->render( - ctx, camera, *lines, *lineBatch, linesShader, showChunkBorders - ); + if (debug && hudVisible) { + debugLines->render( + ctx, camera, *lines, *lineBatch, linesShader, showChunkBorders + ); + } linesShader.uniformMatrix("u_projview", projView); lines->draw(*lineBatch); lineBatch->flush(); diff --git a/src/voxels/Pathfinding.cpp b/src/voxels/Pathfinding.cpp index 51079f61..1a34a8b2 100644 --- a/src/voxels/Pathfinding.cpp +++ b/src/voxels/Pathfinding.cpp @@ -181,6 +181,7 @@ Route Pathfinding::perform(Agent& agent, int maxVisited) { } } } + state.finished = true; agent.state = std::move(state); return {}; } From 89c07cbf7518bf439caccc77060424e20c24616f Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sun, 3 Aug 2025 19:09:43 +0300 Subject: [PATCH 016/125] update docs --- doc/en/scripting.md | 1 + doc/en/scripting/builtins/libpathfinding.md | 57 +++++++++++++++++++++ doc/ru/scripting.md | 1 + doc/ru/scripting/builtins/libpathfinding.md | 57 +++++++++++++++++++++ 4 files changed, 116 insertions(+) create mode 100644 doc/en/scripting/builtins/libpathfinding.md create mode 100644 doc/ru/scripting/builtins/libpathfinding.md diff --git a/doc/en/scripting.md b/doc/en/scripting.md index 05a89a26..9752e46e 100644 --- a/doc/en/scripting.md +++ b/doc/en/scripting.md @@ -31,6 +31,7 @@ Subsections: - [mat4](scripting/builtins/libmat4.md) - [network](scripting/builtins/libnetwork.md) - [pack](scripting/builtins/libpack.md) + - [pathfinding](scripting/builtins/libpathfinding.md) - [player](scripting/builtins/libplayer.md) - [quat](scripting/builtins/libquat.md) - [rules](scripting/builtins/librules.md) diff --git a/doc/en/scripting/builtins/libpathfinding.md b/doc/en/scripting/builtins/libpathfinding.md new file mode 100644 index 00000000..4d07fa75 --- /dev/null +++ b/doc/en/scripting/builtins/libpathfinding.md @@ -0,0 +1,57 @@ +# *pathfinding* library + +The *pathfinding* library provides functions for working with the pathfinding system in the game world. It allows you to create and manage agents finding routes between points in the world. + +When used in entity logic, the `core:pathfinding` component should be used. + +## `core:pathfinding` component + +```lua +local pf = entity:get_component("core:pathfinding") + +--- ... +local x = ... +local y = ... +local z = ... + +--- Set the target for the agent +pf.set_target({x, y, z}) + +--- Get the current target of the agent +local target = pf.get_target() --> vec3 or nil +--- ... + +--- Get the current route of the agent +local route = pf.get_route() --> table or nil +--- ... +``` + +## Library functions + +```lua +--- Create a new agent. Returns the ID of the created agent +local agent = pathfinding.create_agent() --> int + +--- Delete an agent by ID. Returns true if the agent existed, otherwise false +pathfinding.remove_agent(agent: int) --> bool + +--- Set the agent state (enabled/disabled) +pathfinding.set_enabled(agent: int, enabled: bool) + +--- Check the agent state. Returns true if the agent is enabled, otherwise false +pathfinding.is_enabled(agent: int) --> bool + +--- Create a route based on the given points. Returns an array of route points +pathfinding.make_route(start: vec3, target: vec3) --> table + +--- Asynchronously create a route based on the given points. +--- This function allows to perform pathfinding in the background without blocking the main thread of execution +pathfinding.make_route_async(agent: int, start: vec3, target: vec3) + +--- Get the route that the agent has already found. Used to get the route after an asynchronous search. +--- If the search has not yet completed, returns nil. If the route is not found, returns an empty table. +pathfinding.pull_route(agent: int) --> table or nil + +--- Set the maximum number of visited blocks for the agent. Used to limit the amount of work of the pathfinding algorithm. +pathfinding.set_max_visited(agent: int, max_visited: int) +``` diff --git a/doc/ru/scripting.md b/doc/ru/scripting.md index eaae3302..913b5784 100644 --- a/doc/ru/scripting.md +++ b/doc/ru/scripting.md @@ -31,6 +31,7 @@ - [mat4](scripting/builtins/libmat4.md) - [network](scripting/builtins/libnetwork.md) - [pack](scripting/builtins/libpack.md) + - [pathfinding](scripting/builtins/libpathfinding.md) - [player](scripting/builtins/libplayer.md) - [quat](scripting/builtins/libquat.md) - [rules](scripting/builtins/librules.md) diff --git a/doc/ru/scripting/builtins/libpathfinding.md b/doc/ru/scripting/builtins/libpathfinding.md new file mode 100644 index 00000000..d4f5ed7c --- /dev/null +++ b/doc/ru/scripting/builtins/libpathfinding.md @@ -0,0 +1,57 @@ +# Библиотека *pathfinding* + +Библиотека *pathfinding* предоставляет функции для работы с системой поиска пути в игровом мире. Она позволяет создавать и управлять агентами, которые могут находить маршруты между точками в мире. + +При использовании в логике сущностей следует использовать компонент `core:pathfinding`. + +## Компонент `core:pathfinding` + +```lua +local pf = entity:get_component("core:pathfinding") + +--- ... +local x = ... +local y = ... +local z = ... + +--- Установка цели для агента +pf.set_target({x, y, z}) + +--- Получение текущей цели агента +local target = pf.get_target() --> vec3 или nil +--- ... + +--- Получение текущего маршрута агента +local route = pf.get_route() --> table или nil +--- ... +``` + +## Функции библиотеки + +```lua +--- Создание нового агента. Возвращает идентификатор созданного агента +local agent = pathfinding.create_agent() --> int + +--- Удаление агента по идентификатору. Возвращает true, если агент существовал, иначе false +pathfinding.remove_agent(agent: int) --> bool + +--- Установка состояния агента (включен/выключен) +pathfinding.set_enabled(agent: int, enabled: bool) + +--- Проверка состояния агента. Возвращает true, если агент включен, иначе false +pathfinding.is_enabled(agent: int) --> bool + +--- Создание маршрута на основе заданных точек. Возвращает массив точек маршрута +pathfinding.make_route(start: vec3, target: vec3) --> table + +--- Асинхронное создание маршрута на основе заданных точек. +--- Функция позволяет выполнять поиск пути в фоновом режиме, не блокируя основной поток выполнения +pathfinding.make_route_async(agent: int, start: vec3, target: vec3) + +--- Получение маршрута, который агент уже нашел. Используется для получения маршрута после асинхронного поиска. +--- Если поиск ещё не завершён, возвращает nil. Если маршрут не найден, возвращает пустую таблицу. +pathfinding.pull_route(agent: int) --> table или nil + +--- Установка максимального количества посещенных блоков для агента. Используется для ограничения объема работы алгоритма поиска пути. +pathfinding.set_max_visited(agent: int, max_visited: int) +``` From f2aa77db8baa018d5e02d195ce8b3722bfaf0915 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sun, 3 Aug 2025 19:39:21 +0300 Subject: [PATCH 017/125] add vecn.distance function --- doc/en/scripting/builtins/libvecn.md | 11 +++++++++++ doc/ru/scripting/builtins/libvecn.md | 11 +++++++++++ src/logic/scripting/lua/libs/libvecn.cpp | 11 +++++++++++ 3 files changed, 33 insertions(+) diff --git a/doc/en/scripting/builtins/libvecn.md b/doc/en/scripting/builtins/libvecn.md index 22552eb7..36987dfb 100644 --- a/doc/en/scripting/builtins/libvecn.md +++ b/doc/en/scripting/builtins/libvecn.md @@ -100,6 +100,13 @@ vecn.length(a: vector) ``` +#### Distance - *vecn.distance(...)* + +```lua +-- returns the distance between two vectors +vecn.distance(a: vector, b: vector) +``` + #### Absolute value - *vecn.abs(...)* ```lua @@ -188,6 +195,10 @@ print("mul: " .. vec3.tostring(result_mul)) -- {10, 40, 80} local result_mul_scal = vec3.mul(v1_3d, scal) print("mul_scal: " .. vec3.tostring(result_mul_scal)) -- {6, 12, 12} +-- calculating distance between vectors +local result_distance = vec3.distance(v1_3d, v2_3d) +print("distance: " .. result_distance) -- 43 + -- vector normalization local result_norm = vec3.normalize(v1_3d) print("norm: " .. vec3.tostring(result_norm)) -- {0.333, 0.667, 0.667} diff --git a/doc/ru/scripting/builtins/libvecn.md b/doc/ru/scripting/builtins/libvecn.md index 44cfa994..939e2da1 100644 --- a/doc/ru/scripting/builtins/libvecn.md +++ b/doc/ru/scripting/builtins/libvecn.md @@ -100,6 +100,13 @@ vecn.length(a: vector) ``` +#### Дистанция - *vecn.distance(...)* + +```lua +-- возвращает расстояние между двумя векторами +vecn.distance(a: vector, b: vector) +``` + #### Абсолютное значение - *vecn.abs(...)* ```lua @@ -192,6 +199,10 @@ print("mul_scal: " .. vec3.tostring(result_mul_scal)) -- {6, 12, 12} local result_norm = vec3.normalize(v1_3d) print("norm: " .. vec3.tostring(result_norm)) -- {0.333, 0.667, 0.667} +-- дистанция между векторами +local result_distance = vec3.distance(v1_3d, v2_3d) +print("distance: " .. result_distance) -- 43 + -- длина вектора local result_len = vec3.length(v1_3d) print("len: " .. result_len) -- 3 diff --git a/src/logic/scripting/lua/libs/libvecn.cpp b/src/logic/scripting/lua/libs/libvecn.cpp index 724885fe..000a4367 100644 --- a/src/logic/scripting/lua/libs/libvecn.cpp +++ b/src/logic/scripting/lua/libs/libvecn.cpp @@ -74,6 +74,14 @@ static int l_scalar_op(lua::State* L) { return lua::pushnumber(L, func(vec)); } +template +static int l_distance(lua::State* L) { + lua::check_argc(L, 2); + auto a = lua::tovec(L, 1); + auto b = lua::tovec(L, 2); + return lua::pushnumber(L,glm::distance(a, b)); +} + template static int l_pow(lua::State* L) { uint argc = lua::check_argc(L, 2, 3); @@ -182,6 +190,7 @@ const luaL_Reg vec2lib[] = { {"sub", lua::wrap>}, {"mul", lua::wrap>}, {"div", lua::wrap>}, + {"distance", lua::wrap>}, {"normalize", lua::wrap>}, {"length", lua::wrap>}, {"tostring", lua::wrap>}, @@ -198,6 +207,7 @@ const luaL_Reg vec3lib[] = { {"sub", lua::wrap>}, {"mul", lua::wrap>}, {"div", lua::wrap>}, + {"distance", lua::wrap>}, {"normalize", lua::wrap>}, {"length", lua::wrap>}, {"tostring", lua::wrap>}, @@ -214,6 +224,7 @@ const luaL_Reg vec4lib[] = { {"sub", lua::wrap>}, {"mul", lua::wrap>}, {"div", lua::wrap>}, + {"distance", lua::wrap>}, {"normalize", lua::wrap>}, {"length", lua::wrap>}, {"tostring", lua::wrap>}, From f6be6689aa2a24fe26091405c319aa5bebcdb279 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Wed, 6 Aug 2025 23:34:00 +0300 Subject: [PATCH 018/125] feat: passing args to component in entity definition --- src/content/loading/EntityLoader.cpp | 13 ++++++++++++- src/logic/scripting/scripting.cpp | 2 ++ src/objects/Entities.cpp | 4 ++-- src/objects/Entities.hpp | 11 +++++++++-- src/objects/EntityDef.hpp | 10 ++++++++-- 5 files changed, 33 insertions(+), 7 deletions(-) diff --git a/src/content/loading/EntityLoader.cpp b/src/content/loading/EntityLoader.cpp index 7778282c..29a72cb7 100644 --- a/src/content/loading/EntityLoader.cpp +++ b/src/content/loading/EntityLoader.cpp @@ -30,7 +30,18 @@ template<> void ContentUnitLoader::loadUnit( if (auto found = root.at("components")) { for (const auto& elem : *found) { - def.components.emplace_back(elem.asString()); + std::string name; + dv::value params; + if (elem.isObject()) { + name = elem["name"].asString(); + if (elem.has("args")) { + params = elem["args"]; + } + } else { + name = elem.asString(); + } + def.components.push_back(ComponentInstance { + std::move(name), std::move(params)}); } } if (auto found = root.at("hitbox")) { diff --git a/src/logic/scripting/scripting.cpp b/src/logic/scripting/scripting.cpp index 39168843..56c5a17e 100644 --- a/src/logic/scripting/scripting.cpp +++ b/src/logic/scripting/scripting.cpp @@ -603,6 +603,8 @@ void scripting::on_entity_spawn( } else { lua::createtable(L, 0, 0); } + } else if (component->params != nullptr) { + lua::pushvalue(L, component->params); } else { lua::createtable(L, 0, 0); } diff --git a/src/objects/Entities.cpp b/src/objects/Entities.cpp index 4cfe1b29..cdf979e0 100644 --- a/src/objects/Entities.cpp +++ b/src/objects/Entities.cpp @@ -173,9 +173,9 @@ entityid_t Entities::spawn( auto& scripting = registry.emplace(entity); registry.emplace(entity, skeleton->instance()); - for (auto& componentName : def.components) { + for (auto& instance : def.components) { auto component = std::make_unique( - componentName, EntityFuncsSet {}, nullptr + instance.component, EntityFuncsSet {}, nullptr, instance.params ); scripting.components.emplace_back(std::move(component)); } diff --git a/src/objects/Entities.hpp b/src/objects/Entities.hpp index 6518b599..5bd8f955 100644 --- a/src/objects/Entities.hpp +++ b/src/objects/Entities.hpp @@ -80,11 +80,18 @@ struct UserComponent { std::string name; EntityFuncsSet funcsset; scriptenv env; + dv::value params; UserComponent( - const std::string& name, EntityFuncsSet funcsset, scriptenv env + const std::string& name, + EntityFuncsSet funcsset, + scriptenv env, + dv::value params ) - : name(name), funcsset(funcsset), env(env) { + : name(name), + funcsset(funcsset), + env(std::move(env)), + params(std::move(params)) { } }; diff --git a/src/objects/EntityDef.hpp b/src/objects/EntityDef.hpp index 53c69c95..4d10a2d0 100644 --- a/src/objects/EntityDef.hpp +++ b/src/objects/EntityDef.hpp @@ -5,6 +5,7 @@ #include #include "typedefs.hpp" +#include "data/dv.hpp" #include "maths/aabb.hpp" #include "physics/Hitbox.hpp" @@ -12,12 +13,17 @@ namespace rigging { class SkeletonConfig; } +struct ComponentInstance { + std::string component; + dv::value params; +}; + struct EntityDef { /// @brief Entity string id (with prefix included) std::string const name; - /// @brief Component IDs - std::vector components; + /// @brief Component instances + std::vector components; /// @brief Physic body type BodyType bodyType = BodyType::DYNAMIC; From 5f7a75bd7a327bab18498d1fadb05cdb4e1fd558 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Wed, 6 Aug 2025 23:34:31 +0300 Subject: [PATCH 019/125] update base:drop --- res/content/base/entities/drop.json | 9 ++++++++- res/content/base/scripts/components/drop.lua | 3 +++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/res/content/base/entities/drop.json b/res/content/base/entities/drop.json index 4c5e064f..951ea5e6 100644 --- a/res/content/base/entities/drop.json +++ b/res/content/base/entities/drop.json @@ -1,6 +1,13 @@ { "components": [ - "base:drop" + { + "name": "base:drop", + "args": { + "item": "base:stone.item", + "count": 1 + } + } + ], "hitbox": [0.4, 0.25, 0.4], "sensors": [ diff --git a/res/content/base/scripts/components/drop.lua b/res/content/base/scripts/components/drop.lua index a979c72d..2b798a38 100644 --- a/res/content/base/scripts/components/drop.lua +++ b/res/content/base/scripts/components/drop.lua @@ -8,6 +8,9 @@ timer = 0.3 local def_index = entity:def_index() dropitem = ARGS +if dropitem.item then + dropitem.id = item.index(dropitem.item) +end if dropitem then timer = dropitem.pickup_delay or timer end From 36a522bee09a6784c394664f45240ebdd257cdf9 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Wed, 6 Aug 2025 23:37:55 +0300 Subject: [PATCH 020/125] add 'entity.spawn' command --- res/scripts/stdcmd.lua | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/res/scripts/stdcmd.lua b/res/scripts/stdcmd.lua index 0308910f..1b204aa4 100644 --- a/res/scripts/stdcmd.lua +++ b/res/scripts/stdcmd.lua @@ -157,6 +157,16 @@ console.add_command( end ) + +console.add_command( + "entity.spawn nmae:str x:int~pos.x y:int~pos.y z:int~pos.z", + "Spawn entity with default parameters", + function(args, kwargs) + local eid = entities.spawn(args[1], {args[2], args[3], args[4]}) + return string.format("spawned %s at %s, %s, %s", unpack(args)) + end +) + console.add_command( "entity.despawn entity:sel=$entity.selected", "Despawn entity", From 3eae37702422b0d6fabdd4c413937efc68650eb7 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sat, 9 Aug 2025 21:19:01 +0300 Subject: [PATCH 021/125] refactor Entities --- src/frontend/debug_panel.cpp | 1 + src/graphics/render/Decorator.cpp | 1 + src/graphics/render/Emitter.cpp | 1 + src/logic/PlayerController.cpp | 15 +- src/logic/scripting/lua/libs/libentity.cpp | 2 + src/logic/scripting/lua/libs/libentity.hpp | 1 + src/logic/scripting/lua/libs/libplayer.cpp | 1 + src/logic/scripting/scripting.cpp | 1 + src/objects/Entities.cpp | 176 ++------------------- src/objects/Entities.hpp | 174 +------------------- src/objects/Entity.cpp | 119 ++++++++++++++ src/objects/Entity.hpp | 79 +++++++++ src/objects/Player.cpp | 20 ++- src/objects/Player.hpp | 3 + src/objects/Rigidbody.cpp | 74 +++++++++ src/objects/Rigidbody.hpp | 22 +++ src/objects/ScriptComponents.hpp | 49 ++++++ src/objects/Transform.cpp | 25 +++ src/objects/Transform.hpp | 43 +++++ src/objects/rigging.cpp | 19 ++- src/objects/rigging.hpp | 5 +- src/voxels/GlobalChunks.cpp | 1 + src/world/Level.cpp | 1 + 23 files changed, 485 insertions(+), 348 deletions(-) create mode 100644 src/objects/Entity.cpp create mode 100644 src/objects/Entity.hpp create mode 100644 src/objects/Rigidbody.cpp create mode 100644 src/objects/Rigidbody.hpp create mode 100644 src/objects/ScriptComponents.hpp create mode 100644 src/objects/Transform.cpp create mode 100644 src/objects/Transform.hpp diff --git a/src/frontend/debug_panel.cpp b/src/frontend/debug_panel.cpp index 06ab5fc8..5e238f42 100644 --- a/src/frontend/debug_panel.cpp +++ b/src/frontend/debug_panel.cpp @@ -18,6 +18,7 @@ #include "objects/Players.hpp" #include "objects/Entities.hpp" #include "objects/EntityDef.hpp" +#include "objects/Entity.hpp" #include "physics/Hitbox.hpp" #include "util/stringutil.hpp" #include "voxels/Block.hpp" diff --git a/src/graphics/render/Decorator.cpp b/src/graphics/render/Decorator.cpp index dc928b4d..4742b68f 100644 --- a/src/graphics/render/Decorator.cpp +++ b/src/graphics/render/Decorator.cpp @@ -15,6 +15,7 @@ #include "objects/Player.hpp" #include "objects/Players.hpp" #include "objects/Entities.hpp" +#include "objects/Entity.hpp" #include "logic/LevelController.hpp" #include "util/stringutil.hpp" #include "engine/Engine.hpp" diff --git a/src/graphics/render/Emitter.cpp b/src/graphics/render/Emitter.cpp index cdc883ae..483a6339 100644 --- a/src/graphics/render/Emitter.cpp +++ b/src/graphics/render/Emitter.cpp @@ -7,6 +7,7 @@ #include "window/Camera.hpp" #include "graphics/core/Texture.hpp" #include "objects/Entities.hpp" +#include "objects/Entity.hpp" #include "world/Level.hpp" Emitter::Emitter( diff --git a/src/logic/PlayerController.cpp b/src/logic/PlayerController.cpp index df471c36..f5fdea1b 100644 --- a/src/logic/PlayerController.cpp +++ b/src/logic/PlayerController.cpp @@ -13,6 +13,7 @@ #include "items/ItemStack.hpp" #include "lighting/Lighting.hpp" #include "objects/Entities.hpp" +#include "objects/Entity.hpp" #include "objects/Player.hpp" #include "objects/Players.hpp" #include "physics/Hitbox.hpp" @@ -309,16 +310,7 @@ void PlayerController::updateKeyboard(const Input& inputEvents) { } void PlayerController::resetKeyboard() { - input.zoom = false; - input.moveForward = false; - input.moveBack = false; - input.moveLeft = false; - input.moveRight = false; - input.sprint = false; - input.shift = false; - input.cheat = false; - input.jump = false; - input.delta = {}; + input = {}; } void PlayerController::updatePlayer(float delta) { @@ -338,7 +330,8 @@ static int determine_rotation( if (norm.z > 0.0f) return BLOCK_DIR_NORTH; if (norm.z < 0.0f) return BLOCK_DIR_SOUTH; } else if (name == "pane" || name == "stairs") { - int verticalBit = (name == "stairs" && (norm.y - camDir.y * 0.5f) < 0.0) ? 4 : 0; + int verticalBit = + (name == "stairs" && (norm.y - camDir.y * 0.5f) < 0.0) ? 4 : 0; if (abs(camDir.x) > abs(camDir.z)) { if (camDir.x > 0.0f) return BLOCK_DIR_EAST | verticalBit; if (camDir.x < 0.0f) return BLOCK_DIR_WEST | verticalBit; diff --git a/src/logic/scripting/lua/libs/libentity.cpp b/src/logic/scripting/lua/libs/libentity.cpp index 8e00aa1c..45e4e14e 100644 --- a/src/logic/scripting/lua/libs/libentity.cpp +++ b/src/logic/scripting/lua/libs/libentity.cpp @@ -4,6 +4,8 @@ #include "engine/Engine.hpp" #include "objects/Entities.hpp" #include "objects/EntityDef.hpp" +#include "objects/Entity.hpp" +#include "objects/Rigidbody.hpp" #include "objects/Player.hpp" #include "objects/rigging.hpp" #include "physics/Hitbox.hpp" diff --git a/src/logic/scripting/lua/libs/libentity.hpp b/src/logic/scripting/lua/libs/libentity.hpp index b7708f02..5400b449 100644 --- a/src/logic/scripting/lua/libs/libentity.hpp +++ b/src/logic/scripting/lua/libs/libentity.hpp @@ -4,6 +4,7 @@ #include "frontend/hud.hpp" #include "objects/Entities.hpp" +#include "objects/Entity.hpp" #include "world/Level.hpp" #include "logic/LevelController.hpp" #include "api_lua.hpp" diff --git a/src/logic/scripting/lua/libs/libplayer.cpp b/src/logic/scripting/lua/libs/libplayer.cpp index d4e55b77..d850231d 100644 --- a/src/logic/scripting/lua/libs/libplayer.cpp +++ b/src/logic/scripting/lua/libs/libplayer.cpp @@ -5,6 +5,7 @@ #include "objects/Entities.hpp" #include "objects/Player.hpp" #include "objects/Players.hpp" +#include "objects/Entity.hpp" #include "physics/Hitbox.hpp" #include "window/Camera.hpp" #include "world/Level.hpp" diff --git a/src/logic/scripting/scripting.cpp b/src/logic/scripting/scripting.cpp index 56c5a17e..87044201 100644 --- a/src/logic/scripting/scripting.cpp +++ b/src/logic/scripting/scripting.cpp @@ -21,6 +21,7 @@ #include "maths/Heightmap.hpp" #include "objects/Entities.hpp" #include "objects/EntityDef.hpp" +#include "objects/Entity.hpp" #include "objects/Player.hpp" #include "util/stringutil.hpp" #include "util/timeutil.hpp" diff --git a/src/objects/Entities.cpp b/src/objects/Entities.cpp index cdf979e0..5088a70d 100644 --- a/src/objects/Entities.cpp +++ b/src/objects/Entities.cpp @@ -17,6 +17,7 @@ #include "maths/FrustumCulling.hpp" #include "maths/rays.hpp" #include "EntityDef.hpp" +#include "Entity.hpp" #include "rigging.hpp" #include "physics/Hitbox.hpp" #include "physics/PhysicsSolver.hpp" @@ -24,103 +25,16 @@ static debug::Logger logger("entities"); -static inline std::string COMP_TRANSFORM = "transform"; -static inline std::string COMP_RIGIDBODY = "rigidbody"; -static inline std::string COMP_SKELETON = "skeleton"; -static inline std::string SAVED_DATA_VARNAME = "SAVED_DATA"; - -void Transform::refresh() { - combined = glm::mat4(1.0f); - combined = glm::translate(combined, pos); - combined = combined * glm::mat4(rot); - combined = glm::scale(combined, size); - displayPos = pos; - displaySize = size; - dirty = false; -} - -void Entity::setInterpolatedPosition(const glm::vec3& position) { - getSkeleton().interpolation.refresh(position); -} - -glm::vec3 Entity::getInterpolatedPosition() const { - const auto& skeleton = getSkeleton(); - if (skeleton.interpolation.isEnabled()) { - return skeleton.interpolation.getCurrent(); - } - return getTransform().pos; -} - -void Entity::destroy() { - if (isValid()) { - entities.despawn(id); - } -} - -rigging::Skeleton& Entity::getSkeleton() const { - return registry.get(entity); -} - -void Entity::setRig(const rigging::SkeletonConfig* rigConfig) { - auto& skeleton = registry.get(entity); - skeleton.config = rigConfig; - skeleton.pose.matrices.resize( - rigConfig->getBones().size(), glm::mat4(1.0f) - ); - skeleton.calculated.matrices.resize( - rigConfig->getBones().size(), glm::mat4(1.0f) - ); -} - Entities::Entities(Level& level) : level(level), sensorsTickClock(20, 3), updateTickClock(20, 3) { } -template -static sensorcallback create_sensor_callback(Entities* entities) { - return [=](auto entityid, auto index, auto otherid) { - if (auto entity = entities->get(entityid)) { - if (entity->isValid()) { - callback(*entity, index, otherid); - } - } - }; -} - -static void initialize_body( - const EntityDef& def, Rigidbody& body, entityid_t id, Entities* entities -) { - body.sensors.resize(def.radialSensors.size() + def.boxSensors.size()); - for (auto& [i, box] : def.boxSensors) { - SensorParams params {}; - params.aabb = box; - body.sensors[i] = Sensor { - true, - SensorType::AABB, - i, - id, - params, - params, - {}, - {}, - create_sensor_callback(entities), - create_sensor_callback(entities)}; - } - for (auto& [i, radius] : def.radialSensors) { - SensorParams params {}; - params.radial = glm::vec4(radius); - body.sensors[i] = Sensor { - true, - SensorType::RADIUS, - i, - id, - params, - params, - {}, - {}, - create_sensor_callback(entities), - create_sensor_callback(entities)}; +std::optional Entities::get(entityid_t id) { + const auto& found = entities.find(id); + if (found != entities.end() && registry.valid(found->second)) { + return Entity(*this, id, registry, found->second); } + return std::nullopt; } entityid_t Entities::spawn( @@ -168,7 +82,7 @@ entityid_t Entities::spawn( Hitbox {def.bodyType, position, def.hitbox * 0.5f}, std::vector {} ); - initialize_body(def, body, id, this); + body.initialize(def, id, *this); auto& scripting = registry.emplace(entity); registry.emplace(entity, skeleton->instance()); @@ -232,8 +146,8 @@ void Entities::loadEntity(const dv::value& map, Entity entity) { if (skeletonName != skeleton.config->getName()) { skeleton.config = level.content.getSkeleton(skeletonName); } - if (auto found = map.at(COMP_SKELETON)) { - auto& skeletonmap = *found; + if (auto foundSkeleton = map.at(COMP_SKELETON)) { + auto& skeletonmap = *foundSkeleton; if (auto found = skeletonmap.at("textures")) { auto& texturesmap = *found; for (auto& [slot, _] : texturesmap.asObject()) { @@ -284,7 +198,7 @@ std::optional Entities::rayCast( void Entities::loadEntities(dv::value root) { clean(); - auto& list = root["data"]; + const auto& list = root["data"]; for (auto& map : list) { try { loadEntity(map); @@ -298,74 +212,6 @@ void Entities::onSave(const Entity& entity) { scripting::on_entity_save(entity); } -dv::value Entities::serialize(const Entity& entity) { - auto root = dv::object(); - auto& eid = entity.getID(); - auto& def = eid.def; - root["def"] = def.name; - root["uid"] = eid.uid; - { - auto& transform = entity.getTransform(); - auto& tsfmap = root.object(COMP_TRANSFORM); - tsfmap["pos"] = dv::to_value(transform.pos); - if (transform.size != glm::vec3(1.0f)) { - tsfmap["size"] = dv::to_value(transform.size); - } - if (transform.rot != glm::mat3(1.0f)) { - tsfmap["rot"] = dv::to_value(transform.rot); - } - } - { - auto& rigidbody = entity.getRigidbody(); - auto& hitbox = rigidbody.hitbox; - auto& bodymap = root.object(COMP_RIGIDBODY); - if (!rigidbody.enabled) { - bodymap["enabled"] = false; - } - if (def.save.body.velocity) { - bodymap["vel"] = dv::to_value(rigidbody.hitbox.velocity); - } - if (def.save.body.settings) { - bodymap["damping"] = rigidbody.hitbox.linearDamping; - if (hitbox.type != def.bodyType) { - bodymap["type"] = BodyTypeMeta.getNameString(hitbox.type); - } - if (hitbox.crouching) { - bodymap["crouch"] = hitbox.crouching; - } - } - } - auto& skeleton = entity.getSkeleton(); - if (skeleton.config->getName() != def.skeletonName) { - root["skeleton"] = skeleton.config->getName(); - } - if (def.save.skeleton.pose || def.save.skeleton.textures) { - auto& skeletonmap = root.object(COMP_SKELETON); - if (def.save.skeleton.textures) { - auto& map = skeletonmap.object("textures"); - for (auto& [slot, texture] : skeleton.textures) { - map[slot] = texture; - } - } - if (def.save.skeleton.pose) { - auto& list = skeletonmap.list("pose"); - for (auto& mat : skeleton.pose.matrices) { - list.add(dv::to_value(mat)); - } - } - } - auto& scripts = entity.getScripting(); - if (!scripts.components.empty()) { - auto& compsMap = root.object("comps"); - for (auto& comp : scripts.components) { - auto data = - scripting::get_component_value(comp->env, SAVED_DATA_VARNAME); - compsMap[comp->name] = data; - } - } - return root; -} - dv::value Entities::serialize(const std::vector& entities) { auto list = dv::list(); for (auto& entity : entities) { @@ -375,7 +221,7 @@ dv::value Entities::serialize(const std::vector& entities) { } level.entities->onSave(entity); if (!eid.destroyFlag) { - list.add(level.entities->serialize(entity)); + list.add(entity.serialize()); } } return list; diff --git a/src/objects/Entities.hpp b/src/objects/Entities.hpp index 5bd8f955..99da0db3 100644 --- a/src/objects/Entities.hpp +++ b/src/objects/Entities.hpp @@ -5,108 +5,21 @@ #include #include -#include "data/dv.hpp" #include "physics/Hitbox.hpp" +#include "Transform.hpp" +#include "Rigidbody.hpp" +#include "ScriptComponents.hpp" #include "typedefs.hpp" #include "util/Clock.hpp" -#define GLM_ENABLE_EXPERIMENTAL -#include -#include -#include -struct EntityFuncsSet { - bool init; - bool on_despawn; - bool on_grounded; - bool on_fall; - bool on_sensor_enter; - bool on_sensor_exit; - bool on_save; - bool on_aim_on; - bool on_aim_off; - bool on_attacked; - bool on_used; -}; +#include +#include struct EntityDef; -struct EntityId { - entityid_t uid; - const EntityDef& def; - bool destroyFlag = false; - int64_t player = -1; -}; - -struct Transform { - static inline constexpr float EPSILON = 0.0000001f; - glm::vec3 pos; - glm::vec3 size; - glm::mat3 rot; - glm::mat4 combined; - bool dirty = true; - - glm::vec3 displayPos; - glm::vec3 displaySize; - - void refresh(); - - inline void setRot(glm::mat3 m) { - rot = m; - dirty = true; - } - - inline void setSize(glm::vec3 v) { - if (glm::distance2(displaySize, v) >= EPSILON) { - dirty = true; - } - size = v; - } - - inline void setPos(glm::vec3 v) { - if (glm::distance2(displayPos, v) >= EPSILON) { - dirty = true; - } - pos = v; - } -}; - -struct Rigidbody { - bool enabled = true; - Hitbox hitbox; - std::vector sensors; -}; - -struct UserComponent { - std::string name; - EntityFuncsSet funcsset; - scriptenv env; - dv::value params; - - UserComponent( - const std::string& name, - EntityFuncsSet funcsset, - scriptenv env, - dv::value params - ) - : name(name), - funcsset(funcsset), - env(std::move(env)), - params(std::move(params)) { - } -}; - -struct ScriptComponents { - std::vector> components; - - ScriptComponents() = default; - - ScriptComponents(ScriptComponents&& other) - : components(std::move(other.components)) { - } -}; - class Level; class Assets; +class Entity; class LineBatch; class ModelBatch; class Frustum; @@ -118,72 +31,6 @@ namespace rigging { class SkeletonConfig; } -class Entity { - Entities& entities; - entityid_t id; - entt::registry& registry; - const entt::entity entity; -public: - Entity( - Entities& entities, - entityid_t id, - entt::registry& registry, - const entt::entity entity - ) - : entities(entities), id(id), registry(registry), entity(entity) { - } - - EntityId& getID() const { - return registry.get(entity); - } - - bool isValid() const { - return registry.valid(entity); - } - - const EntityDef& getDef() const { - return registry.get(entity).def; - } - - Transform& getTransform() const { - return registry.get(entity); - } - - Rigidbody& getRigidbody() const { - return registry.get(entity); - } - - ScriptComponents& getScripting() const { - return registry.get(entity); - } - - rigging::Skeleton& getSkeleton() const; - - void setRig(const rigging::SkeletonConfig* rigConfig); - - entityid_t getUID() const { - return registry.get(entity).uid; - } - - entt::entity getHandler() const { - return entity; - } - - int64_t getPlayer() const { - return registry.get(entity).player; - } - - void setPlayer(int64_t id) { - registry.get(entity).player = id; - } - - void setInterpolatedPosition(const glm::vec3& position); - - glm::vec3 getInterpolatedPosition() const; - - void destroy(); -}; - class Entities { entt::registry registry; Level& level; @@ -229,13 +76,7 @@ public: entityid_t uid = 0 ); - std::optional get(entityid_t id) { - const auto& found = entities.find(id); - if (found != entities.end() && registry.valid(found->second)) { - return Entity(*this, id, registry, found->second); - } - return std::nullopt; - } + std::optional get(entityid_t id); /// @brief Entities raycast. No blocks check included, use combined with /// Chunks.rayCast @@ -260,7 +101,6 @@ public: std::vector getAllInRadius(glm::vec3 center, float radius); void despawn(entityid_t id); void despawn(std::vector entities); - dv::value serialize(const Entity& entity); dv::value serialize(const std::vector& entities); void setNextID(entityid_t id) { diff --git a/src/objects/Entity.cpp b/src/objects/Entity.cpp new file mode 100644 index 00000000..24aa1736 --- /dev/null +++ b/src/objects/Entity.cpp @@ -0,0 +1,119 @@ +#include "Entity.hpp" + +#include "Transform.hpp" +#include "Rigidbody.hpp" +#include "ScriptComponents.hpp" +#include "Entities.hpp" +#include "EntityDef.hpp" +#include "rigging.hpp" +#include "logic/scripting/scripting.hpp" + +#include + +static inline std::string SAVED_DATA_VARNAME = "SAVED_DATA"; + +void Entity::setInterpolatedPosition(const glm::vec3& position) { + getSkeleton().interpolation.refresh(position); +} + +glm::vec3 Entity::getInterpolatedPosition() const { + const auto& skeleton = getSkeleton(); + if (skeleton.interpolation.isEnabled()) { + return skeleton.interpolation.getCurrent(); + } + return getTransform().pos; +} + +void Entity::destroy() { + if (isValid()) { + entities.despawn(id); + } +} + +rigging::Skeleton& Entity::getSkeleton() const { + return registry.get(entity); +} + +void Entity::setRig(const rigging::SkeletonConfig* rigConfig) { + auto& skeleton = registry.get(entity); + skeleton.config = rigConfig; + skeleton.pose.matrices.resize( + rigConfig->getBones().size(), glm::mat4(1.0f) + ); + skeleton.calculated.matrices.resize( + rigConfig->getBones().size(), glm::mat4(1.0f) + ); +} + +dv::value Entity::serialize() const { + const auto& eid = getID(); + const auto& def = eid.def; + const auto& transform = getTransform(); + const auto& rigidbody = getRigidbody(); + const auto& skeleton = getSkeleton(); + const auto& scripts = getScripting(); + + auto root = dv::object(); + root["def"] = def.name; + root["uid"] = eid.uid; + + root[COMP_TRANSFORM] = transform.serialize(); + root[COMP_RIGIDBODY] = + rigidbody.serialize(def.save.body.velocity, def.save.body.settings); + + if (skeleton.config->getName() != def.skeletonName) { + root["skeleton"] = skeleton.config->getName(); + } + if (def.save.skeleton.pose || def.save.skeleton.textures) { + root[COMP_SKELETON] = skeleton.serialize( + def.save.skeleton.pose, def.save.skeleton.textures + ); + } + if (!scripts.components.empty()) { + auto& compsMap = root.object("comps"); + for (auto& comp : scripts.components) { + auto data = + scripting::get_component_value(comp->env, SAVED_DATA_VARNAME); + compsMap[comp->name] = data; + } + } + return root; +} + +EntityId& Entity::getID() const { + return registry.get(entity); +} + +bool Entity::isValid() const { + return registry.valid(entity); +} + +Transform& Entity::getTransform() const { + return registry.get(entity); +} + + +ScriptComponents& Entity::getScripting() const { + return registry.get(entity); +} + +const EntityDef& Entity::getDef() const { + return registry.get(entity).def; +} + +Rigidbody& Entity::getRigidbody() const { + return registry.get(entity); +} + +entityid_t Entity::getUID() const { + return registry.get(entity).uid; +} + +int64_t Entity::getPlayer() const { + return registry.get(entity).player; +} + +void Entity::setPlayer(int64_t id) { + registry.get(entity).player = id; +} + diff --git a/src/objects/Entity.hpp b/src/objects/Entity.hpp new file mode 100644 index 00000000..b7e8c273 --- /dev/null +++ b/src/objects/Entity.hpp @@ -0,0 +1,79 @@ +#pragma once + +#include "typedefs.hpp" +#include "data/dv_fwd.hpp" + +#include +#include + +class Entities; +struct EntityDef; +struct Transform; +struct Rigidbody; +struct ScriptComponents; + +inline std::string COMP_TRANSFORM = "transform"; +inline std::string COMP_RIGIDBODY = "rigidbody"; +inline std::string COMP_SKELETON = "skeleton"; + +namespace rigging { + struct Skeleton; + class SkeletonConfig; +} + +struct EntityId { + entityid_t uid; + const EntityDef& def; + bool destroyFlag = false; + int64_t player = -1; +}; + +class Entity { + Entities& entities; + entityid_t id; + entt::registry& registry; + const entt::entity entity; +public: + Entity( + Entities& entities, + entityid_t id, + entt::registry& registry, + const entt::entity entity + ) + : entities(entities), id(id), registry(registry), entity(entity) { + } + + dv::value serialize() const; + + EntityId& getID() const; + + bool isValid() const; + + const EntityDef& getDef() const; + + Transform& getTransform() const; + + Rigidbody& getRigidbody() const; + + ScriptComponents& getScripting() const; + + rigging::Skeleton& getSkeleton() const; + + void setRig(const rigging::SkeletonConfig* rigConfig); + + entityid_t getUID() const; + + int64_t getPlayer() const; + + void setPlayer(int64_t id); + + void setInterpolatedPosition(const glm::vec3& position); + + glm::vec3 getInterpolatedPosition() const; + + entt::entity getHandler() const { + return entity; + } + + void destroy(); +}; diff --git a/src/objects/Player.cpp b/src/objects/Player.cpp index 21497244..8f2b0d76 100644 --- a/src/objects/Player.cpp +++ b/src/objects/Player.cpp @@ -9,6 +9,7 @@ #include "content/ContentReport.hpp" #include "items/Inventory.hpp" #include "Entities.hpp" +#include "Entity.hpp" #include "rigging.hpp" #include "physics/Hitbox.hpp" #include "physics/PhysicsSolver.hpp" @@ -137,7 +138,7 @@ void Player::updateInput(PlayerInput& input, float delta) { } if (glm::length(dir) > 0.0f) { dir = glm::normalize(dir); - hitbox->velocity += dir * speed * delta * 9.0f; + doMove(dir, speed, delta); } if (flight) { if (input.jump) { @@ -146,9 +147,22 @@ void Player::updateInput(PlayerInput& input, float delta) { if (input.shift) { hitbox->velocity.y -= speed * delta * 9; } + } else if (input.jump) { + doJump(); } - if (input.jump && hitbox->grounded) { - hitbox->velocity.y = JUMP_FORCE; +} + +void Player::doMove(const glm::vec3& dir, float speed, float delta) { + if (auto hitbox = getHitbox()) { + hitbox->velocity += dir * speed * delta * 9.0f; + } +} + +void Player::doJump() { + if (auto hitbox = getHitbox()) { + if (hitbox->grounded) { + hitbox->velocity.y = JUMP_FORCE; + } } } diff --git a/src/objects/Player.hpp b/src/objects/Player.hpp index c4d847b3..2da8ee1d 100644 --- a/src/objects/Player.hpp +++ b/src/objects/Player.hpp @@ -59,6 +59,9 @@ class Player : public Serializable { entityid_t eid = ENTITY_AUTO; entityid_t selectedEid = 0; + void doMove(const glm::vec3& dir, float speed, float delta); + void doJump(); + glm::vec3 rotation {}; public: util::VecInterpolation<3, float, true> rotationInterpolation {true}; diff --git a/src/objects/Rigidbody.cpp b/src/objects/Rigidbody.cpp new file mode 100644 index 00000000..62f88128 --- /dev/null +++ b/src/objects/Rigidbody.cpp @@ -0,0 +1,74 @@ +#define VC_ENABLE_REFLECTION + +#include "Rigidbody.hpp" + +#include "EntityDef.hpp" +#include "Entities.hpp" +#include "Entity.hpp" +#include "data/dv_util.hpp" +#include "logic/scripting/scripting.hpp" + +dv::value Rigidbody::serialize(bool saveVelocity, bool saveBodySettings) const { + auto bodymap = dv::object(); + if (!enabled) { + bodymap["enabled"] = false; + } + if (saveVelocity) { + bodymap["vel"] = dv::to_value(hitbox.velocity); + } + if (saveBodySettings) { + bodymap["damping"] = hitbox.linearDamping; + bodymap["type"] = BodyTypeMeta.getNameString(hitbox.type); + if (hitbox.crouching) { + bodymap["crouch"] = hitbox.crouching; + } + } + return bodymap; +} + +template +static sensorcallback create_sensor_callback(Entities& entities) { + return [&entities](auto entityid, auto index, auto otherid) { + if (auto entity = entities.get(entityid)) { + if (entity->isValid()) { + callback(*entity, index, otherid); + } + } + }; +} + +void Rigidbody::initialize( + const EntityDef& def, entityid_t id, Entities& entities +) { + sensors.resize(def.radialSensors.size() + def.boxSensors.size()); + for (auto& [i, box] : def.boxSensors) { + SensorParams params {}; + params.aabb = box; + sensors[i] = Sensor { + true, + SensorType::AABB, + i, + id, + params, + params, + {}, + {}, + create_sensor_callback(entities), + create_sensor_callback(entities)}; + } + for (auto& [i, radius] : def.radialSensors) { + SensorParams params {}; + params.radial = glm::vec4(radius); + sensors[i] = Sensor { + true, + SensorType::RADIUS, + i, + id, + params, + params, + {}, + {}, + create_sensor_callback(entities), + create_sensor_callback(entities)}; + } +} diff --git a/src/objects/Rigidbody.hpp b/src/objects/Rigidbody.hpp new file mode 100644 index 00000000..818329d2 --- /dev/null +++ b/src/objects/Rigidbody.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include "data/dv_fwd.hpp" +#include "physics/Hitbox.hpp" + +#include +#include + +class Entities; +struct EntityDef; + +struct Rigidbody { + bool enabled = true; + Hitbox hitbox; + std::vector sensors; + + dv::value serialize(bool saveVelocity, bool saveBodySettings) const; + + void initialize( + const EntityDef& def, entityid_t id, Entities& entities + ); +}; diff --git a/src/objects/ScriptComponents.hpp b/src/objects/ScriptComponents.hpp new file mode 100644 index 00000000..2711d482 --- /dev/null +++ b/src/objects/ScriptComponents.hpp @@ -0,0 +1,49 @@ +#pragma once + +#include "typedefs.hpp" +#include "data/dv.hpp" + +#include + +struct EntityFuncsSet { + bool init; + bool on_despawn; + bool on_grounded; + bool on_fall; + bool on_sensor_enter; + bool on_sensor_exit; + bool on_save; + bool on_aim_on; + bool on_aim_off; + bool on_attacked; + bool on_used; +}; + +struct UserComponent { + std::string name; + EntityFuncsSet funcsset; + scriptenv env; + dv::value params; + + UserComponent( + const std::string& name, + EntityFuncsSet funcsset, + scriptenv env, + dv::value params + ) + : name(name), + funcsset(funcsset), + env(std::move(env)), + params(std::move(params)) { + } +}; + +struct ScriptComponents { + std::vector> components; + + ScriptComponents() = default; + + ScriptComponents(ScriptComponents&& other) + : components(std::move(other.components)) { + } +}; diff --git a/src/objects/Transform.cpp b/src/objects/Transform.cpp new file mode 100644 index 00000000..9c14353e --- /dev/null +++ b/src/objects/Transform.cpp @@ -0,0 +1,25 @@ +#include "Transform.hpp" + +#include "data/dv_util.hpp" + +void Transform::refresh() { + combined = glm::mat4(1.0f); + combined = glm::translate(combined, pos); + combined = combined * glm::mat4(rot); + combined = glm::scale(combined, size); + displayPos = pos; + displaySize = size; + dirty = false; +} + +dv::value Transform::serialize() const { + auto tsfmap = dv::object(); + tsfmap["pos"] = dv::to_value(pos); + if (size != glm::vec3(1.0f)) { + tsfmap["size"] = dv::to_value(size); + } + if (rot != glm::mat3(1.0f)) { + tsfmap["rot"] = dv::to_value(rot); + } + return tsfmap; +} diff --git a/src/objects/Transform.hpp b/src/objects/Transform.hpp new file mode 100644 index 00000000..0eb73236 --- /dev/null +++ b/src/objects/Transform.hpp @@ -0,0 +1,43 @@ +#pragma once + +#define GLM_ENABLE_EXPERIMENTAL + +#include +#include +#include +#include + +struct Transform { + static inline constexpr float EPSILON = 0.0000001f; + glm::vec3 pos; + glm::vec3 size; + glm::mat3 rot; + glm::mat4 combined; + bool dirty = true; + + glm::vec3 displayPos; + glm::vec3 displaySize; + + dv::value serialize() const; + + void refresh(); + + inline void setRot(glm::mat3 m) { + rot = m; + dirty = true; + } + + inline void setSize(glm::vec3 v) { + if (glm::distance2(displaySize, v) >= EPSILON) { + dirty = true; + } + size = v; + } + + inline void setPos(glm::vec3 v) { + if (glm::distance2(displayPos, v) >= EPSILON) { + dirty = true; + } + pos = v; + } +}; diff --git a/src/objects/rigging.cpp b/src/objects/rigging.cpp index 69ec5ce1..58138a48 100644 --- a/src/objects/rigging.cpp +++ b/src/objects/rigging.cpp @@ -23,7 +23,7 @@ Bone::Bone( std::string name, std::string model, std::vector> bones, - glm::vec3 offset + const glm::vec3& offset ) : index(index), name(std::move(name)), @@ -53,6 +53,23 @@ Skeleton::Skeleton(const SkeletonConfig* config) } } +dv::value Skeleton::serialize(bool saveTextures, bool savePose) const { + auto root = dv::object(); + if (saveTextures) { + auto& map = root.object("textures"); + for (auto& [slot, texture] : textures) { + map[slot] = texture; + } + } + if (savePose) { + auto& list = root.list("pose"); + for (auto& mat : pose.matrices) { + list.add(dv::to_value(mat)); + } + } + return root; +} + static void get_all_nodes(std::vector& nodes, Bone* node) { nodes[node->getIndex()] = node; for (auto& subnode : node->getSubnodes()) { diff --git a/src/objects/rigging.hpp b/src/objects/rigging.hpp index 2c326a4a..0c377c43 100644 --- a/src/objects/rigging.hpp +++ b/src/objects/rigging.hpp @@ -9,6 +9,7 @@ #include #include "typedefs.hpp" +#include "data/dv_fwd.hpp" #include "util/Interpolation.hpp" class Assets; @@ -50,7 +51,7 @@ namespace rigging { std::string name, std::string model, std::vector> bones, - glm::vec3 offset + const glm::vec3& offset ); void setModel(const std::string& name); @@ -89,6 +90,8 @@ namespace rigging { util::VecInterpolation<3, float> interpolation {false}; Skeleton(const SkeletonConfig* config); + + dv::value serialize(bool saveTextures, bool savePose) const; }; class SkeletonConfig { diff --git a/src/voxels/GlobalChunks.cpp b/src/voxels/GlobalChunks.cpp index fbca2eec..7451a563 100644 --- a/src/voxels/GlobalChunks.cpp +++ b/src/voxels/GlobalChunks.cpp @@ -10,6 +10,7 @@ #include "lighting/Lightmap.hpp" #include "maths/voxmaths.hpp" #include "objects/Entities.hpp" +#include "objects/Entity.hpp" #include "voxels/blocks_agent.hpp" #include "typedefs.hpp" #include "world/LevelEvents.hpp" diff --git a/src/world/Level.cpp b/src/world/Level.cpp index c33c7e0f..2a1583bd 100644 --- a/src/world/Level.cpp +++ b/src/world/Level.cpp @@ -5,6 +5,7 @@ #include "items/Inventories.hpp" #include "items/Inventory.hpp" #include "objects/Entities.hpp" +#include "objects/Entity.hpp" #include "objects/Player.hpp" #include "objects/Players.hpp" #include "physics/Hitbox.hpp" From 61da6b44a135bbc86d13e1e6a49ccbbb3e9191bc Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sat, 9 Aug 2025 21:30:55 +0300 Subject: [PATCH 022/125] fix windows build --- src/objects/Entity.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/objects/Entity.hpp b/src/objects/Entity.hpp index b7e8c273..4a306c86 100644 --- a/src/objects/Entity.hpp +++ b/src/objects/Entity.hpp @@ -3,6 +3,7 @@ #include "typedefs.hpp" #include "data/dv_fwd.hpp" +#include #include #include From 5583734bc2773a1a08ad0a6a01f1be29f420e164 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sat, 9 Aug 2025 22:43:06 +0300 Subject: [PATCH 023/125] refactor --- src/data/dv_util.hpp | 6 +- src/logic/scripting/scripting.cpp | 275 -------------------- src/logic/scripting/scripting_entities.cpp | 288 +++++++++++++++++++++ src/objects/Entities.cpp | 33 +-- src/objects/Rigidbody.cpp | 9 + src/objects/Rigidbody.hpp | 1 + src/objects/Transform.cpp | 6 + src/objects/Transform.hpp | 3 +- src/objects/rigging.cpp | 16 ++ src/objects/rigging.hpp | 1 + 10 files changed, 331 insertions(+), 307 deletions(-) create mode 100644 src/logic/scripting/scripting_entities.cpp diff --git a/src/data/dv_util.hpp b/src/data/dv_util.hpp index 3f09ffde..ffec61dd 100644 --- a/src/data/dv_util.hpp +++ b/src/data/dv_util.hpp @@ -46,12 +46,12 @@ namespace dv { if (!map.has(key)) { return; } - auto& list = map[key]; + const auto& srcList = map[key]; for (size_t i = 0; i < n; i++) { if constexpr (std::is_floating_point()) { - vec[i] = list[i].asNumber(); + vec[i] = srcList[i].asNumber(); } else { - vec[i] = list[i].asInteger(); + vec[i] = srcList[i].asInteger(); } } } diff --git a/src/logic/scripting/scripting.cpp b/src/logic/scripting/scripting.cpp index 87044201..6a2ca0a5 100644 --- a/src/logic/scripting/scripting.cpp +++ b/src/logic/scripting/scripting.cpp @@ -19,9 +19,6 @@ #include "lua/lua_engine.hpp" #include "lua/lua_custom_types.hpp" #include "maths/Heightmap.hpp" -#include "objects/Entities.hpp" -#include "objects/EntityDef.hpp" -#include "objects/Entity.hpp" #include "objects/Player.hpp" #include "util/stringutil.hpp" #include "util/timeutil.hpp" @@ -34,8 +31,6 @@ using namespace scripting; static debug::Logger logger("scripting"); -static inline const std::string STDCOMP = "stdcomp"; - std::ostream* scripting::output_stream = &std::cout; std::ostream* scripting::error_stream = &std::cerr; Engine* scripting::engine = nullptr; @@ -198,36 +193,6 @@ std::unique_ptr scripting::start_coroutine( }); } -[[nodiscard]] static scriptenv create_component_environment( - const scriptenv& parent, int entityIdx, const std::string& name -) { - auto L = lua::get_main_state(); - int id = lua::create_environment(L, *parent); - - lua::pushvalue(L, entityIdx); - - lua::pushenv(L, id); - - lua::pushvalue(L, -1); - lua::setfield(L, "this"); - - lua::pushvalue(L, -2); - lua::setfield(L, "entity"); - - lua::pop(L); - if (lua::getfield(L, "components")) { - lua::pushenv(L, id); - lua::setfield(L, name); - lua::pop(L); - } - lua::pop(L); - - return std::shared_ptr(new int(id), [=](int* id) { //-V508 - lua::remove_environment(L, *id); - delete id; - }); -} - void scripting::process_post_runnables() { auto L = lua::get_main_state(); if (lua::getglobal(L, "__process_post_runnables")) { @@ -559,246 +524,6 @@ bool scripting::on_item_break_block( ); } -dv::value scripting::get_component_value( - const scriptenv& env, const std::string& name -) { - auto L = lua::get_main_state(); - lua::pushenv(L, *env); - if (lua::getfield(L, name)) { - return lua::tovalue(L, -1); - } - return nullptr; -} - -void scripting::on_entity_spawn( - const EntityDef&, - entityid_t eid, - const std::vector>& components, - const dv::value& args, - const dv::value& saved -) { - auto L = lua::get_main_state(); - lua::stackguard guard(L); - lua::requireglobal(L, STDCOMP); - if (lua::getfield(L, "new_Entity")) { - lua::pushinteger(L, eid); - lua::call(L, 1); - } - if (components.size() > 1) { - for (size_t i = 0; i < components.size() - 1; i++) { - lua::pushvalue(L, -1); - } - } - for (auto& component : components) { - auto compenv = create_component_environment( - get_root_environment(), -1, component->name - ); - lua::get_from(L, lua::CHUNKS_TABLE, component->name, true); - lua::pushenv(L, *compenv); - - if (args != nullptr) { - std::string compfieldname = component->name; - util::replaceAll(compfieldname, ":", "__"); - if (args.has(compfieldname)) { - lua::pushvalue(L, args[compfieldname]); - } else { - lua::createtable(L, 0, 0); - } - } else if (component->params != nullptr) { - lua::pushvalue(L, component->params); - } else { - lua::createtable(L, 0, 0); - } - lua::setfield(L, "ARGS"); - - if (saved == nullptr) { - lua::createtable(L, 0, 0); - } else { - if (saved.has(component->name)) { - lua::pushvalue(L, saved[component->name]); - } else { - lua::createtable(L, 0, 0); - } - } - lua::setfield(L, "SAVED_DATA"); - - lua::setfenv(L); - lua::call_nothrow(L, 0, 0); - - lua::pushenv(L, *compenv); - auto& funcsset = component->funcsset; - funcsset.on_grounded = lua::hasfield(L, "on_grounded"); - funcsset.on_fall = lua::hasfield(L, "on_fall"); - funcsset.on_despawn = lua::hasfield(L, "on_despawn"); - funcsset.on_sensor_enter = lua::hasfield(L, "on_sensor_enter"); - funcsset.on_sensor_exit = lua::hasfield(L, "on_sensor_exit"); - funcsset.on_save = lua::hasfield(L, "on_save"); - funcsset.on_aim_on = lua::hasfield(L, "on_aim_on"); - funcsset.on_aim_off = lua::hasfield(L, "on_aim_off"); - funcsset.on_attacked = lua::hasfield(L, "on_attacked"); - funcsset.on_used = lua::hasfield(L, "on_used"); - lua::pop(L, 2); - - component->env = compenv; - } -} - -static void process_entity_callback( - const scriptenv& env, - const std::string& name, - std::function args -) { - auto L = lua::get_main_state(); - lua::pushenv(L, *env); - if (lua::hasfield(L, "__disabled")) { - lua::pop(L); - return; - } - if (lua::getfield(L, name)) { - if (args) { - lua::call_nothrow(L, args(L), 0); - } else { - lua::call_nothrow(L, 0, 0); - } - } - lua::pop(L); -} - -static void process_entity_callback( - const Entity& entity, - const std::string& name, - bool EntityFuncsSet::*flag, - std::function args -) { - const auto& script = entity.getScripting(); - for (auto& component : script.components) { - if (component->funcsset.*flag) { - process_entity_callback(component->env, name, args); - } - } -} - -void scripting::on_entity_despawn(const Entity& entity) { - process_entity_callback( - entity, "on_despawn", &EntityFuncsSet::on_despawn, nullptr - ); - auto L = lua::get_main_state(); - lua::get_from(L, "stdcomp", "remove_Entity", true); - lua::pushinteger(L, entity.getUID()); - lua::call(L, 1, 0); -} - -void scripting::on_entity_grounded(const Entity& entity, float force) { - process_entity_callback( - entity, - "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", &EntityFuncsSet::on_fall, nullptr - ); -} - -void scripting::on_entity_save(const Entity& entity) { - process_entity_callback( - entity, "on_save", &EntityFuncsSet::on_save, nullptr - ); -} - -void scripting::on_sensor_enter( - const Entity& entity, size_t index, entityid_t oid -) { - process_entity_callback( - entity, - "on_sensor_enter", - &EntityFuncsSet::on_sensor_enter, - [index, oid](auto L) { - lua::pushinteger(L, index); - lua::pushinteger(L, oid); - return 2; - } - ); -} - -void scripting::on_sensor_exit( - const Entity& entity, size_t index, entityid_t oid -) { - process_entity_callback( - entity, - "on_sensor_exit", - &EntityFuncsSet::on_sensor_exit, - [index, oid](auto L) { - lua::pushinteger(L, index); - lua::pushinteger(L, oid); - return 2; - } - ); -} - -void scripting::on_aim_on(const Entity& entity, Player* player) { - process_entity_callback( - entity, - "on_aim_on", - &EntityFuncsSet::on_aim_on, - [player](auto L) { return lua::pushinteger(L, player->getId()); } - ); -} - -void scripting::on_aim_off(const Entity& entity, Player* player) { - process_entity_callback( - entity, - "on_aim_off", - &EntityFuncsSet::on_aim_off, - [player](auto L) { return lua::pushinteger(L, player->getId()); } - ); -} - -void scripting::on_attacked( - const Entity& entity, Player* player, entityid_t attacker -) { - process_entity_callback( - entity, - "on_attacked", - &EntityFuncsSet::on_attacked, - [player, attacker](auto L) { - lua::pushinteger(L, attacker); - lua::pushinteger(L, player->getId()); - return 2; - } - ); -} - -void scripting::on_entity_used(const Entity& entity, Player* player) { - process_entity_callback( - entity, - "on_used", - &EntityFuncsSet::on_used, - [player](auto L) { return lua::pushinteger(L, player->getId()); } - ); -} - -void scripting::on_entities_update(int tps, int parts, int part) { - auto L = lua::get_main_state(); - lua::get_from(L, STDCOMP, "update", true); - lua::pushinteger(L, tps); - lua::pushinteger(L, parts); - lua::pushinteger(L, part); - lua::call_nothrow(L, 3, 0); - lua::pop(L); -} - -void scripting::on_entities_render(float delta) { - auto L = lua::get_main_state(); - lua::get_from(L, STDCOMP, "render", true); - lua::pushnumber(L, delta); - lua::call_nothrow(L, 1, 0); - lua::pop(L); -} - void scripting::on_ui_open( UiDocument* layout, std::vector args ) { diff --git a/src/logic/scripting/scripting_entities.cpp b/src/logic/scripting/scripting_entities.cpp new file mode 100644 index 00000000..4deabd4b --- /dev/null +++ b/src/logic/scripting/scripting_entities.cpp @@ -0,0 +1,288 @@ +#include "scripting.hpp" + +#include "lua/lua_engine.hpp" +#include "objects/Entities.hpp" +#include "objects/EntityDef.hpp" +#include "objects/Entity.hpp" +#include "objects/Player.hpp" +#include "util/stringutil.hpp" + +using namespace scripting; + +static inline const std::string STDCOMP = "stdcomp"; + +[[nodiscard]] static scriptenv create_component_environment( + const scriptenv& parent, int entityIdx, const std::string& name +) { + auto L = lua::get_main_state(); + int id = lua::create_environment(L, *parent); + + lua::pushvalue(L, entityIdx); + + lua::pushenv(L, id); + + lua::pushvalue(L, -1); + lua::setfield(L, "this"); + + lua::pushvalue(L, -2); + lua::setfield(L, "entity"); + + lua::pop(L); + if (lua::getfield(L, "components")) { + lua::pushenv(L, id); + lua::setfield(L, name); + lua::pop(L); + } + lua::pop(L); + + return std::shared_ptr(new int(id), [=](int* id) { //-V508 + lua::remove_environment(L, *id); + delete id; + }); +} + +dv::value scripting::get_component_value( + const scriptenv& env, const std::string& name +) { + auto L = lua::get_main_state(); + lua::pushenv(L, *env); + if (lua::getfield(L, name)) { + return lua::tovalue(L, -1); + } + return nullptr; +} + +static void create_component( + lua::State* L, + int entityIdx, + UserComponent& component, + const dv::value& args, + const dv::value& saved +) { + lua::pushvalue(L, entityIdx); + auto compenv = create_component_environment( + get_root_environment(), -1, component.name + ); + lua::get_from(L, lua::CHUNKS_TABLE, component.name, true); + lua::pushenv(L, *compenv); + + if (args != nullptr) { + std::string compfieldname = component.name; + util::replaceAll(compfieldname, ":", "__"); + if (args.has(compfieldname)) { + lua::pushvalue(L, args[compfieldname]); + } else { + lua::createtable(L, 0, 0); + } + } else if (component.params != nullptr) { + lua::pushvalue(L, component.params); + } else { + lua::createtable(L, 0, 0); + } + lua::setfield(L, "ARGS"); + + if (saved == nullptr) { + lua::createtable(L, 0, 0); + } else { + if (saved.has(component.name)) { + lua::pushvalue(L, saved[component.name]); + } else { + lua::createtable(L, 0, 0); + } + } + lua::setfield(L, "SAVED_DATA"); + + lua::setfenv(L); + lua::call_nothrow(L, 0, 0); + + lua::pushenv(L, *compenv); + auto& funcsset = component.funcsset; + funcsset.on_grounded = lua::hasfield(L, "on_grounded"); + funcsset.on_fall = lua::hasfield(L, "on_fall"); + funcsset.on_despawn = lua::hasfield(L, "on_despawn"); + funcsset.on_sensor_enter = lua::hasfield(L, "on_sensor_enter"); + funcsset.on_sensor_exit = lua::hasfield(L, "on_sensor_exit"); + funcsset.on_save = lua::hasfield(L, "on_save"); + funcsset.on_aim_on = lua::hasfield(L, "on_aim_on"); + funcsset.on_aim_off = lua::hasfield(L, "on_aim_off"); + funcsset.on_attacked = lua::hasfield(L, "on_attacked"); + funcsset.on_used = lua::hasfield(L, "on_used"); + lua::pop(L, 2); + + component.env = compenv; +} + +void scripting::on_entity_spawn( + const EntityDef&, + entityid_t eid, + const std::vector>& components, + const dv::value& args, + const dv::value& saved +) { + auto L = lua::get_main_state(); + lua::stackguard guard(L); + lua::requireglobal(L, STDCOMP); + if (lua::getfield(L, "new_Entity")) { + lua::pushinteger(L, eid); + lua::call(L, 1); + } + for (auto& component : components) { + create_component(L, -1, *component, args, saved); + } +} + +static void process_entity_callback( + const scriptenv& env, + const std::string& name, + std::function args +) { + auto L = lua::get_main_state(); + lua::pushenv(L, *env); + if (lua::hasfield(L, "__disabled")) { + lua::pop(L); + return; + } + if (lua::getfield(L, name)) { + if (args) { + lua::call_nothrow(L, args(L), 0); + } else { + lua::call_nothrow(L, 0, 0); + } + } + lua::pop(L); +} + +static void process_entity_callback( + const Entity& entity, + const std::string& name, + bool EntityFuncsSet::*flag, + std::function args +) { + const auto& script = entity.getScripting(); + for (auto& component : script.components) { + if (component->funcsset.*flag) { + process_entity_callback(component->env, name, args); + } + } +} + +void scripting::on_entity_despawn(const Entity& entity) { + process_entity_callback( + entity, "on_despawn", &EntityFuncsSet::on_despawn, nullptr + ); + auto L = lua::get_main_state(); + lua::get_from(L, "stdcomp", "remove_Entity", true); + lua::pushinteger(L, entity.getUID()); + lua::call(L, 1, 0); +} + +void scripting::on_entity_grounded(const Entity& entity, float force) { + process_entity_callback( + entity, + "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", &EntityFuncsSet::on_fall, nullptr + ); +} + +void scripting::on_entity_save(const Entity& entity) { + process_entity_callback( + entity, "on_save", &EntityFuncsSet::on_save, nullptr + ); +} + +void scripting::on_sensor_enter( + const Entity& entity, size_t index, entityid_t oid +) { + process_entity_callback( + entity, + "on_sensor_enter", + &EntityFuncsSet::on_sensor_enter, + [index, oid](auto L) { + lua::pushinteger(L, index); + lua::pushinteger(L, oid); + return 2; + } + ); +} + +void scripting::on_sensor_exit( + const Entity& entity, size_t index, entityid_t oid +) { + process_entity_callback( + entity, + "on_sensor_exit", + &EntityFuncsSet::on_sensor_exit, + [index, oid](auto L) { + lua::pushinteger(L, index); + lua::pushinteger(L, oid); + return 2; + } + ); +} + +void scripting::on_aim_on(const Entity& entity, Player* player) { + process_entity_callback( + entity, + "on_aim_on", + &EntityFuncsSet::on_aim_on, + [player](auto L) { return lua::pushinteger(L, player->getId()); } + ); +} + +void scripting::on_aim_off(const Entity& entity, Player* player) { + process_entity_callback( + entity, + "on_aim_off", + &EntityFuncsSet::on_aim_off, + [player](auto L) { return lua::pushinteger(L, player->getId()); } + ); +} + +void scripting::on_attacked( + const Entity& entity, Player* player, entityid_t attacker +) { + process_entity_callback( + entity, + "on_attacked", + &EntityFuncsSet::on_attacked, + [player, attacker](auto L) { + lua::pushinteger(L, attacker); + lua::pushinteger(L, player->getId()); + return 2; + } + ); +} + +void scripting::on_entity_used(const Entity& entity, Player* player) { + process_entity_callback( + entity, + "on_used", + &EntityFuncsSet::on_used, + [player](auto L) { return lua::pushinteger(L, player->getId()); } + ); +} + +void scripting::on_entities_update(int tps, int parts, int part) { + auto L = lua::get_main_state(); + lua::get_from(L, STDCOMP, "update", true); + lua::pushinteger(L, tps); + lua::pushinteger(L, parts); + lua::pushinteger(L, part); + lua::call_nothrow(L, 3, 0); + lua::pop(L); +} + +void scripting::on_entities_render(float delta) { + auto L = lua::get_main_state(); + lua::get_from(L, STDCOMP, "render", true); + lua::pushnumber(L, delta); + lua::call_nothrow(L, 1, 0); + lua::pop(L); +} diff --git a/src/objects/Entities.cpp b/src/objects/Entities.cpp index 5088a70d..8f3d3cb1 100644 --- a/src/objects/Entities.cpp +++ b/src/objects/Entities.cpp @@ -19,7 +19,6 @@ #include "EntityDef.hpp" #include "Entity.hpp" #include "rigging.hpp" -#include "physics/Hitbox.hpp" #include "physics/PhysicsSolver.hpp" #include "world/Level.hpp" @@ -100,7 +99,8 @@ entityid_t Entities::spawn( } body.hitbox.position = tsf.pos; scripting::on_entity_spawn( - def, id, scripting.components, args, componentsMap); + def, id, scripting.components, args, componentsMap + ); return id; } @@ -127,19 +127,10 @@ void Entities::loadEntity(const dv::value& map, Entity entity) { auto& skeleton = entity.getSkeleton(); if (map.has(COMP_RIGIDBODY)) { - auto& bodymap = map[COMP_RIGIDBODY]; - dv::get_vec(bodymap, "vel", body.hitbox.velocity); - std::string bodyTypeName; - map.at("type").get(bodyTypeName); - BodyTypeMeta.getItem(bodyTypeName, body.hitbox.type); - bodymap["crouch"].asBoolean(body.hitbox.crouching); - bodymap["damping"].asNumber(body.hitbox.linearDamping); + body.deserialize(map[COMP_RIGIDBODY]); } if (map.has(COMP_TRANSFORM)) { - auto& tsfmap = map[COMP_TRANSFORM]; - dv::get_vec(tsfmap, "pos", transform.pos); - dv::get_vec(tsfmap, "size", transform.size); - dv::get_mat(tsfmap, "rot", transform.rot); + transform.deserialize(map[COMP_TRANSFORM]); } std::string skeletonName = skeleton.config->getName(); map.at("skeleton").get(skeletonName); @@ -147,21 +138,7 @@ void Entities::loadEntity(const dv::value& map, Entity entity) { skeleton.config = level.content.getSkeleton(skeletonName); } if (auto foundSkeleton = map.at(COMP_SKELETON)) { - auto& skeletonmap = *foundSkeleton; - if (auto found = skeletonmap.at("textures")) { - auto& texturesmap = *found; - for (auto& [slot, _] : texturesmap.asObject()) { - texturesmap.at(slot).get(skeleton.textures[slot]); - } - } - if (auto found = skeletonmap.at("pose")) { - auto& posearr = *found; - for (size_t i = 0; - i < std::min(skeleton.pose.matrices.size(), posearr.size()); - i++) { - dv::get_mat(posearr[i], skeleton.pose.matrices[i]); - } - } + skeleton.deserialize(*foundSkeleton); } } diff --git a/src/objects/Rigidbody.cpp b/src/objects/Rigidbody.cpp index 62f88128..9b1f78d2 100644 --- a/src/objects/Rigidbody.cpp +++ b/src/objects/Rigidbody.cpp @@ -26,6 +26,15 @@ dv::value Rigidbody::serialize(bool saveVelocity, bool saveBodySettings) const { return bodymap; } +void Rigidbody::deserialize(const dv::value& root) { + dv::get_vec(root, "vel", hitbox.velocity); + std::string bodyTypeName; + root.at("type").get(bodyTypeName); + BodyTypeMeta.getItem(bodyTypeName, hitbox.type); + root["crouch"].asBoolean(hitbox.crouching); + root["damping"].asNumber(hitbox.linearDamping); +} + template static sensorcallback create_sensor_callback(Entities& entities) { return [&entities](auto entityid, auto index, auto otherid) { diff --git a/src/objects/Rigidbody.hpp b/src/objects/Rigidbody.hpp index 818329d2..91790d8a 100644 --- a/src/objects/Rigidbody.hpp +++ b/src/objects/Rigidbody.hpp @@ -15,6 +15,7 @@ struct Rigidbody { std::vector sensors; dv::value serialize(bool saveVelocity, bool saveBodySettings) const; + void deserialize(const dv::value& root); void initialize( const EntityDef& def, entityid_t id, Entities& entities diff --git a/src/objects/Transform.cpp b/src/objects/Transform.cpp index 9c14353e..87ed4a73 100644 --- a/src/objects/Transform.cpp +++ b/src/objects/Transform.cpp @@ -23,3 +23,9 @@ dv::value Transform::serialize() const { } return tsfmap; } + +void Transform::deserialize(const dv::value& root) { + dv::get_vec(root, "pos", pos); + dv::get_vec(root, "size", size); + dv::get_mat(root, "rot", rot); +} diff --git a/src/objects/Transform.hpp b/src/objects/Transform.hpp index 0eb73236..183890dd 100644 --- a/src/objects/Transform.hpp +++ b/src/objects/Transform.hpp @@ -8,7 +8,7 @@ #include struct Transform { - static inline constexpr float EPSILON = 0.0000001f; + static inline constexpr float EPSILON = 1e-7f; glm::vec3 pos; glm::vec3 size; glm::mat3 rot; @@ -19,6 +19,7 @@ struct Transform { glm::vec3 displaySize; dv::value serialize() const; + void deserialize(const dv::value& root); void refresh(); diff --git a/src/objects/rigging.cpp b/src/objects/rigging.cpp index 58138a48..1da11f8c 100644 --- a/src/objects/rigging.cpp +++ b/src/objects/rigging.cpp @@ -70,6 +70,22 @@ dv::value Skeleton::serialize(bool saveTextures, bool savePose) const { return root; } +void Skeleton::deserialize(const dv::value& root) { + if (auto found = root.at("textures")) { + auto& texturesmap = *found; + for (auto& [slot, _] : texturesmap.asObject()) { + texturesmap.at(slot).get(textures[slot]); + } + } + if (auto found = root.at("pose")) { + auto& posearr = *found; + auto& matrices = pose.matrices; + for (size_t i = 0; i < std::min(matrices.size(), posearr.size()); i++) { + dv::get_mat(posearr[i], pose.matrices[i]); + } + } +} + static void get_all_nodes(std::vector& nodes, Bone* node) { nodes[node->getIndex()] = node; for (auto& subnode : node->getSubnodes()) { diff --git a/src/objects/rigging.hpp b/src/objects/rigging.hpp index 0c377c43..76ee764b 100644 --- a/src/objects/rigging.hpp +++ b/src/objects/rigging.hpp @@ -92,6 +92,7 @@ namespace rigging { Skeleton(const SkeletonConfig* config); dv::value serialize(bool saveTextures, bool savePose) const; + void deserialize(const dv::value& root); }; class SkeletonConfig { From fc573b4c6ee838cb4ca13278f5874597642c91c5 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sat, 9 Aug 2025 23:40:09 +0300 Subject: [PATCH 024/125] erase and forget --- src/graphics/render/WorldRenderer.cpp | 4 +++- src/objects/Entities.cpp | 19 ++++++++++--------- src/objects/Entities.hpp | 3 ++- src/objects/Player.cpp | 4 ---- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/graphics/render/WorldRenderer.cpp b/src/graphics/render/WorldRenderer.cpp index eef4c41e..099dd989 100644 --- a/src/graphics/render/WorldRenderer.cpp +++ b/src/graphics/render/WorldRenderer.cpp @@ -205,7 +205,9 @@ void WorldRenderer::renderOpaque( *modelBatch, culling ? frustumCulling.get() : nullptr, delta, - pause + pause, + player.currentCamera.get() == player.fpCamera.get() ? player.getEntity() + : 0 ); modelBatch->render(); particles->render(camera, delta * !pause); diff --git a/src/objects/Entities.cpp b/src/objects/Entities.cpp index 8f3d3cb1..8dcdecd4 100644 --- a/src/objects/Entities.cpp +++ b/src/objects/Entities.cpp @@ -328,13 +328,10 @@ static void debug_render_skeleton( size_t pindex = bone->getIndex(); for (auto& sub : bone->getSubnodes()) { size_t sindex = sub->getIndex(); + const auto& matrices = skeleton.calculated.matrices; batch.line( - glm::vec3( - skeleton.calculated.matrices[pindex] * glm::vec4(0, 0, 0, 1) - ), - glm::vec3( - skeleton.calculated.matrices[sindex] * glm::vec4(0, 0, 0, 1) - ), + glm::vec3(matrices[pindex] * glm::vec4(0, 0, 0, 1)), + glm::vec3(matrices[sindex] * glm::vec4(0, 0, 0, 1)), glm::vec4(0, 0.5f, 0, 1) ); debug_render_skeleton(batch, sub.get(), skeleton); @@ -391,10 +388,14 @@ void Entities::render( ModelBatch& batch, const Frustum* frustum, float delta, - bool pause + bool pause, + entityid_t fpsEntity ) { - auto view = registry.view(); - for (auto [entity, transform, skeleton] : view.each()) { + auto view = registry.view(); + for (auto [entity, eid, transform, skeleton] : view.each()) { + if (eid.uid == fpsEntity) { + continue; + } if (transform.dirty) { transform.refresh(); } diff --git a/src/objects/Entities.hpp b/src/objects/Entities.hpp index 99da0db3..03fcec8c 100644 --- a/src/objects/Entities.hpp +++ b/src/objects/Entities.hpp @@ -65,7 +65,8 @@ public: ModelBatch& batch, const Frustum* frustum, float delta, - bool pause + bool pause, + entityid_t fpsEntity ); entityid_t spawn( diff --git a/src/objects/Player.cpp b/src/objects/Player.cpp index 8f2b0d76..befdd0b3 100644 --- a/src/objects/Player.cpp +++ b/src/objects/Player.cpp @@ -186,10 +186,6 @@ void Player::postUpdate() { attemptToFindSpawnpoint(); } } - - // TODO: ERASE & FORGET - auto& skeleton = entity->getSkeleton(); - skeleton.visible = currentCamera != fpCamera; } void Player::teleport(glm::vec3 position) { From dfb83f6835de486e4c2b621cd9bcccefdf0e7436 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sun, 10 Aug 2025 22:55:34 +0300 Subject: [PATCH 025/125] add core:mob component & move player movement to scripting --- res/content/base/entities/player.json | 6 ++ res/scripts/components/mob.lua | 116 ++++++++++++++++++++++++++ src/logic/PlayerController.cpp | 5 -- src/logic/PlayerController.hpp | 1 - src/objects/Entities.cpp | 4 +- src/objects/Player.cpp | 82 ------------------ src/objects/Player.hpp | 4 - src/physics/Hitbox.cpp | 3 +- src/physics/Hitbox.hpp | 3 +- src/physics/PhysicsSolver.cpp | 8 +- 10 files changed, 132 insertions(+), 100 deletions(-) create mode 100644 res/scripts/components/mob.lua diff --git a/res/content/base/entities/player.json b/res/content/base/entities/player.json index b20a7aed..d3d5712c 100644 --- a/res/content/base/entities/player.json +++ b/res/content/base/entities/player.json @@ -1,5 +1,11 @@ { "components": [ + { + "name": "core:mob", + "args": { + "jump_force": 8.0 + } + }, "base:player_animator" ], "hitbox": [0.6, 1.8, 0.6] diff --git a/res/scripts/components/mob.lua b/res/scripts/components/mob.lua new file mode 100644 index 00000000..e3909da1 --- /dev/null +++ b/res/scripts/components/mob.lua @@ -0,0 +1,116 @@ +local body = entity.rigidbody + +local jump_force = SAVED_DATA.jump_force or ARGS.jump_force or 0.0 +local air_damping = SAVED_DATA.air_damping or ARGS.air_damping or 1.0 +local ground_damping = SAVED_DATA.ground_damping or ARGS.ground_damping or 1.0 +local movement_speed = SAVED_DATA.movement_speed or ARGS.movement_speed or 4.0 +local run_speed_mul = SAVED_DATA.run_speed_mul or ARGS.run_speed_mul or 1.5 +local crouch_speed_mul = SAVED_DATA.crouch_speed_mul or ARGS.crouch_speed_mul or 0.35 +local flight_speed_mul = SAVED_DATA.flight_speed_mul or ARGS.flight_speed_mul or 4.0 +local cheat_speed_mul = SAVED_DATA.cheat_speed_mul or ARGS.cheat_speed_mul or 5.0 + +function jump(multiplier) + if body:is_grounded() then + local vel = body:get_vel() + body:set_vel( + vec3.add(vel, {0, jump_force * (multiplier or 1.0), 0}, vel)) + end +end + +function elevate(speed, delta, vel) + vel = vel or body:get_vel() + body:set_vel( + vec3.add(vel, {0, speed * delta, 0}, vel)) +end + +function lower(speed, delta, vel) + vel = vel or body:get_vel() + body:set_vel( + vec3.add(vel, {0, -speed * delta, 0}, vel)) +end + +function move_horizontal(speed, dir, vel) + vel = vel or body:get_vel() + if vec3.length(dir) > 0.0 then + vec3.normalize(dir, dir) + + vel[1] = dir[1] * speed + vel[3] = dir[3] * speed + end + body:set_vel(vel) +end + +function on_update(tps) + local delta = (1.0 / tps) + local pid = entity:get_player() + if pid then + -- todo: replace with entity direction + local cam = cameras.get("core:first-person") + local front = cam:get_front() + local right = cam:get_right() + front[2] = 0.0 + vec3.normalize(front, front) + + local grounded = body:is_grounded() + + local isjump = input.is_active('movement.jump') + local issprint = input.is_active('movement.sprint') + local iscrouch = input.is_active('movement.crouch') + local isforward = input.is_active('movement.forward') + local ischeat = input.is_active('movement.cheat') + local isback = input.is_active('movement.back') + local isleft = input.is_active('movement.left') + local isright = input.is_active('movement.right') + local flight = player.is_flight(pid) + local noclip = player.is_noclip(pid) + + local vel = body:get_vel() + + local speed = movement_speed + + if flight then + speed = speed * flight_speed_mul + elseif issprint then + speed = speed * run_speed_mul + elseif iscrouch and grounded then + speed = speed * crouch_speed_mul + end + body:set_crouching(iscrouch) + + if ischeat then + speed = speed * cheat_speed_mul + end + + local dir = {0, 0, 0} + if isforward then + vec3.add(dir, front, dir) + end + if isback then + vec3.sub(dir, front, dir) + end + if isright then + vec3.add(dir, right, dir) + end + if isleft then + vec3.sub(dir, right, dir) + end + + if vec3.length(dir) > 0.0 then + move_horizontal(speed, dir, vel) + end + + if flight then + if isjump then + elevate(speed * 8.0, delta) + elseif iscrouch then + lower(speed * 8.0, delta) + end + elseif isjump then + jump() + end + body:set_vdamping(flight) + body:set_gravity_scale(flight and 0.0 or 1.0) + body:set_linear_damping((flight or not grounded) and air_damping or ground_damping) + body:set_body_type(noclip and "kinematic" or "dynamic") + end +end diff --git a/src/logic/PlayerController.cpp b/src/logic/PlayerController.cpp index f5fdea1b..8021db51 100644 --- a/src/logic/PlayerController.cpp +++ b/src/logic/PlayerController.cpp @@ -271,7 +271,6 @@ void PlayerController::update(float delta, const Input* inputEvents) { } else { resetKeyboard(); } - updatePlayer(delta); } void PlayerController::postUpdate( @@ -313,10 +312,6 @@ void PlayerController::resetKeyboard() { input = {}; } -void PlayerController::updatePlayer(float delta) { - player.updateInput(input, delta); -} - static int determine_rotation( const Block* def, const glm::ivec3& norm, const glm::vec3& camDir ) { diff --git a/src/logic/PlayerController.hpp b/src/logic/PlayerController.hpp index cd49642c..7f895ee3 100644 --- a/src/logic/PlayerController.hpp +++ b/src/logic/PlayerController.hpp @@ -60,7 +60,6 @@ class PlayerController { void updateKeyboard(const Input& inputEvents); void resetKeyboard(); - void updatePlayer(float delta); void updateEntityInteraction(entityid_t eid, bool lclick, bool rclick); void updateInteraction(const Input& inputEvents, float delta); diff --git a/src/objects/Entities.cpp b/src/objects/Entities.cpp index 8dcdecd4..2353dc1b 100644 --- a/src/objects/Entities.cpp +++ b/src/objects/Entities.cpp @@ -297,7 +297,9 @@ void Entities::updatePhysics(float delta) { int substeps = static_cast(delta * vel * 20); substeps = std::min(100, std::max(2, substeps)); physics->step(*level.chunks, hitbox, delta, substeps, eid.uid); - hitbox.linearDamping = hitbox.grounded * 24; + hitbox.friction = glm::abs(hitbox.gravityScale <= 1e-7f) + ? 8.0f + : (!grounded ? 2.0f : 10.0f); transform.setPos(hitbox.position); if (hitbox.grounded && !grounded) { scripting::on_entity_grounded( diff --git a/src/objects/Player.cpp b/src/objects/Player.cpp index befdd0b3..2c2fbe24 100644 --- a/src/objects/Player.cpp +++ b/src/objects/Player.cpp @@ -21,13 +21,6 @@ static debug::Logger logger("player"); -constexpr float CROUCH_SPEED_MUL = 0.35f; -constexpr float RUN_SPEED_MUL = 1.5f; -constexpr float PLAYER_GROUND_DAMPING = 10.0f; -constexpr float PLAYER_AIR_DAMPING = 8.0f; -constexpr float FLIGHT_SPEED_MUL = 4.0f; -constexpr float CHEAT_SPEED_MUL = 5.0f; -constexpr float JUMP_FORCE = 8.0f; constexpr int SPAWN_ATTEMPTS_PER_UPDATE = 64; Player::Player( @@ -82,17 +75,6 @@ void Player::updateEntity() { "will be respawned"; eid = ENTITY_AUTO; } - auto hitbox = getHitbox(); - if (hitbox == nullptr) { - return; - } - hitbox->linearDamping = PLAYER_GROUND_DAMPING; - hitbox->verticalDamping = flight; - hitbox->gravityScale = flight ? 0.0f : 1.0f; - if (flight || !hitbox->grounded) { - hitbox->linearDamping = PLAYER_AIR_DAMPING; - } - hitbox->type = noclip ? BodyType::KINEMATIC : BodyType::DYNAMIC; } Hitbox* Player::getHitbox() { @@ -102,70 +84,6 @@ Hitbox* Player::getHitbox() { return nullptr; } -void Player::updateInput(PlayerInput& input, float delta) { - auto hitbox = getHitbox(); - if (hitbox == nullptr) { - return; - } - bool crouch = input.shift && hitbox->grounded && !input.sprint; - float speed = this->speed; - if (flight) { - speed *= FLIGHT_SPEED_MUL; - } - if (input.cheat) { - speed *= CHEAT_SPEED_MUL; - } - - hitbox->crouching = crouch; - if (crouch) { - speed *= CROUCH_SPEED_MUL; - } else if (input.sprint) { - speed *= RUN_SPEED_MUL; - } - - glm::vec3 dir(0, 0, 0); - if (input.moveForward) { - dir += fpCamera->dir; - } - if (input.moveBack) { - dir -= fpCamera->dir; - } - if (input.moveRight) { - dir += fpCamera->right; - } - if (input.moveLeft) { - dir -= fpCamera->right; - } - if (glm::length(dir) > 0.0f) { - dir = glm::normalize(dir); - doMove(dir, speed, delta); - } - if (flight) { - if (input.jump) { - hitbox->velocity.y += speed * delta * 9; - } - if (input.shift) { - hitbox->velocity.y -= speed * delta * 9; - } - } else if (input.jump) { - doJump(); - } -} - -void Player::doMove(const glm::vec3& dir, float speed, float delta) { - if (auto hitbox = getHitbox()) { - hitbox->velocity += dir * speed * delta * 9.0f; - } -} - -void Player::doJump() { - if (auto hitbox = getHitbox()) { - if (hitbox->grounded) { - hitbox->velocity.y = JUMP_FORCE; - } - } -} - void Player::updateSelectedEntity() { selectedEid = selection.entity; } diff --git a/src/objects/Player.hpp b/src/objects/Player.hpp index 2da8ee1d..b9999c78 100644 --- a/src/objects/Player.hpp +++ b/src/objects/Player.hpp @@ -59,9 +59,6 @@ class Player : public Serializable { entityid_t eid = ENTITY_AUTO; entityid_t selectedEid = 0; - void doMove(const glm::vec3& dir, float speed, float delta); - void doJump(); - glm::vec3 rotation {}; public: util::VecInterpolation<3, float, true> rotationInterpolation {true}; @@ -85,7 +82,6 @@ public: void teleport(glm::vec3 position); void updateEntity(); - void updateInput(PlayerInput& input, float delta); void updateSelectedEntity(); void postUpdate(); diff --git a/src/physics/Hitbox.cpp b/src/physics/Hitbox.cpp index 83ad823c..adf07841 100644 --- a/src/physics/Hitbox.cpp +++ b/src/physics/Hitbox.cpp @@ -6,6 +6,5 @@ Hitbox::Hitbox(BodyType type, glm::vec3 position, glm::vec3 halfsize) : type(type), position(position), halfsize(halfsize), - velocity(0.0f,0.0f,0.0f), - linearDamping(0.1f) + velocity(0.0f,0.0f,0.0f) {} diff --git a/src/physics/Hitbox.hpp b/src/physics/Hitbox.hpp index e825cefe..2285a632 100644 --- a/src/physics/Hitbox.hpp +++ b/src/physics/Hitbox.hpp @@ -52,7 +52,8 @@ struct Hitbox { glm::vec3 position; glm::vec3 halfsize; glm::vec3 velocity; - float linearDamping; + float linearDamping = 0.5; + float friction = 1.0f; bool verticalDamping = false; bool grounded = false; float gravityScale = 1.0f; diff --git a/src/physics/PhysicsSolver.cpp b/src/physics/PhysicsSolver.cpp index 62a6abd6..0d1cb4a0 100644 --- a/src/physics/PhysicsSolver.cpp +++ b/src/physics/PhysicsSolver.cpp @@ -25,7 +25,7 @@ void PhysicsSolver::step( entityid_t entity ) { float dt = delta / static_cast(substeps); - float linearDamping = hitbox.linearDamping; + float linearDamping = hitbox.linearDamping * hitbox.friction; float s = 2.0f/BLOCK_AABB_GRID; const glm::vec3& half = hitbox.halfsize; @@ -45,11 +45,11 @@ void PhysicsSolver::step( colisionCalc(chunks, hitbox, vel, pos, half, (prevGrounded && gravityScale > 0.0f) ? 0.5f : 0.0f); } - vel.x *= glm::max(0.0f, 1.0f - dt * linearDamping); + vel.x /= 1.0f + dt * linearDamping; + vel.z /= 1.0f + dt * linearDamping; if (hitbox.verticalDamping) { - vel.y *= glm::max(0.0f, 1.0f - dt * linearDamping); + vel.y /= 1.0f + dt * linearDamping; } - vel.z *= glm::max(0.0f, 1.0f - dt * linearDamping); pos += vel * dt + gravity * gravityScale * dt * dt * 0.5f; if (hitbox.grounded && pos.y < py) { From 5a8f4de503a95ad6e5d5e9509429f9f331113e0a Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sun, 10 Aug 2025 23:19:10 +0300 Subject: [PATCH 026/125] fix input library in headless mode --- res/scripts/components/mob.lua | 4 +++- src/logic/scripting/lua/libs/libinput.cpp | 19 ++++++++++++++++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/res/scripts/components/mob.lua b/res/scripts/components/mob.lua index e3909da1..01da449a 100644 --- a/res/scripts/components/mob.lua +++ b/res/scripts/components/mob.lua @@ -110,7 +110,9 @@ function on_update(tps) end body:set_vdamping(flight) body:set_gravity_scale(flight and 0.0 or 1.0) - body:set_linear_damping((flight or not grounded) and air_damping or ground_damping) + body:set_linear_damping( + (flight or not grounded) and air_damping or ground_damping + ) body:set_body_type(noclip and "kinematic" or "dynamic") end end diff --git a/src/logic/scripting/lua/libs/libinput.cpp b/src/logic/scripting/lua/libs/libinput.cpp index c5f20cc4..3537cd36 100644 --- a/src/logic/scripting/lua/libs/libinput.cpp +++ b/src/logic/scripting/lua/libs/libinput.cpp @@ -31,6 +31,8 @@ static int l_mousecode(lua::State* L) { } static int l_add_callback(lua::State* L) { + if (engine->isHeadless()) + return 0; std::string bindname = lua::require_string(L, 1); size_t pos = bindname.find(':'); @@ -75,10 +77,14 @@ static int l_add_callback(lua::State* L) { } static int l_get_mouse_pos(lua::State* L) { + if (engine->isHeadless()) + return 0; return lua::pushvec2(L, engine->getInput().getCursor().pos); } static int l_get_bindings(lua::State* L) { + if (engine->isHeadless()) + return 0; const auto& bindings = engine->getInput().getBindings().getAll(); lua::createtable(L, bindings.size(), 0); @@ -92,18 +98,24 @@ static int l_get_bindings(lua::State* L) { } static int l_get_binding_text(lua::State* L) { + if (engine->isHeadless()) + return 0; auto bindname = lua::require_string(L, 1); const auto& bind = engine->getInput().getBindings().require(bindname); return lua::pushstring(L, bind.text()); } static int l_is_active(lua::State* L) { + if (engine->isHeadless()) + return 0; auto bindname = lua::require_string(L, 1); auto& bind = engine->getInput().getBindings().require(bindname); return lua::pushboolean(L, bind.active()); } static int l_is_pressed(lua::State* L) { + if (engine->isHeadless()) + return 0; std::string code = lua::require_string(L, 1); size_t sep = code.find(':'); if (sep == std::string::npos) { @@ -136,6 +148,8 @@ static void reset_pack_bindings(const io::path& packFolder) { } static int l_reset_bindings(lua::State*) { + if (engine->isHeadless()) + return 0; reset_pack_bindings("res:"); for (const auto& pack : content_control->getContentPacks()) { reset_pack_bindings(pack.folder); @@ -144,6 +158,8 @@ static int l_reset_bindings(lua::State*) { } static int l_set_enabled(lua::State* L) { + if (engine->isHeadless()) + return 0; std::string bindname = lua::require_string(L, 1); bool enabled = lua::toboolean(L, 2); engine->getInput().getBindings().require(bindname).enabled = enabled; @@ -161,4 +177,5 @@ const luaL_Reg inputlib[] = { {"is_pressed", lua::wrap}, {"reset_bindings", lua::wrap}, {"set_enabled", lua::wrap}, - {NULL, NULL}}; + {NULL, NULL} +}; From b60f0f0ea2df151a8b4a86d79a5c7fbd3845901b Mon Sep 17 00:00:00 2001 From: MihailRis Date: Mon, 11 Aug 2025 01:03:28 +0300 Subject: [PATCH 027/125] update core:mob component --- res/scripts/components/mob.lua | 50 ++++++++++++++++++++++------------ 1 file changed, 33 insertions(+), 17 deletions(-) diff --git a/res/scripts/components/mob.lua b/res/scripts/components/mob.lua index 01da449a..8cc63dc5 100644 --- a/res/scripts/components/mob.lua +++ b/res/scripts/components/mob.lua @@ -1,19 +1,35 @@ local body = entity.rigidbody -local jump_force = SAVED_DATA.jump_force or ARGS.jump_force or 0.0 -local air_damping = SAVED_DATA.air_damping or ARGS.air_damping or 1.0 -local ground_damping = SAVED_DATA.ground_damping or ARGS.ground_damping or 1.0 -local movement_speed = SAVED_DATA.movement_speed or ARGS.movement_speed or 4.0 -local run_speed_mul = SAVED_DATA.run_speed_mul or ARGS.run_speed_mul or 1.5 -local crouch_speed_mul = SAVED_DATA.crouch_speed_mul or ARGS.crouch_speed_mul or 0.35 -local flight_speed_mul = SAVED_DATA.flight_speed_mul or ARGS.flight_speed_mul or 4.0 -local cheat_speed_mul = SAVED_DATA.cheat_speed_mul or ARGS.cheat_speed_mul or 5.0 +local props = {} + +local function def_prop(name, def_value) + props[name] = SAVED_DATA[name] or ARGS[name] or def_value + this["get_"..name] = function() return props[name] end + this["set_"..name] = function(value) + props[name] = value + if math.abs(value - def_value) < 1e-7 then + SAVED_DATA[name] = nil + else + SAVED_DATA[name] = value + end + end +end + +def_prop("jump_force", 0.0) +def_prop("air_damping", 1.0) +def_prop("ground_damping", 1.0) +def_prop("movement_speed", 4.0) +def_prop("run_speed_mul", 1.5) +def_prop("crouch_speed_mul", 0.35) +def_prop("flight_speed_mul", 4.0) +def_prop("cheat_speed_mul", 5.0) +def_prop("gravity_scale", 1.0) function jump(multiplier) if body:is_grounded() then local vel = body:get_vel() body:set_vel( - vec3.add(vel, {0, jump_force * (multiplier or 1.0), 0}, vel)) + vec3.add(vel, {0, props.jump_force * (multiplier or 1.0), 0}, vel)) end end @@ -43,7 +59,7 @@ end function on_update(tps) local delta = (1.0 / tps) local pid = entity:get_player() - if pid then + if pid and hud and not hud.is_inventory_open() and not menu.page ~= "" then -- todo: replace with entity direction local cam = cameras.get("core:first-person") local front = cam:get_front() @@ -66,19 +82,19 @@ function on_update(tps) local vel = body:get_vel() - local speed = movement_speed + local speed = props.movement_speed if flight then - speed = speed * flight_speed_mul + speed = speed * props.flight_speed_mul elseif issprint then - speed = speed * run_speed_mul + speed = speed * props.run_speed_mul elseif iscrouch and grounded then - speed = speed * crouch_speed_mul + speed = speed * props.crouch_speed_mul end body:set_crouching(iscrouch) if ischeat then - speed = speed * cheat_speed_mul + speed = speed * props.cheat_speed_mul end local dir = {0, 0, 0} @@ -109,9 +125,9 @@ function on_update(tps) jump() end body:set_vdamping(flight) - body:set_gravity_scale(flight and 0.0 or 1.0) + body:set_gravity_scale(flight and 0.0 or props.gravity_scale) body:set_linear_damping( - (flight or not grounded) and air_damping or ground_damping + (flight or not grounded) and props.air_damping or props.ground_damping ) body:set_body_type(noclip and "kinematic" or "dynamic") end From 6d3dac910671ce381e5bd985ebd6dd9776a61ec5 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Wed, 13 Aug 2025 21:29:20 +0300 Subject: [PATCH 028/125] add 'on_physics_update' entity event --- res/modules/internal/stdcomp.lua | 17 +++++++++++++++++ res/scripts/components/mob.lua | 4 ++-- src/logic/scripting/scripting.hpp | 1 + src/logic/scripting/scripting_entities.cpp | 10 ++++++++++ src/objects/Entities.cpp | 12 +++++++++++- src/objects/Entities.hpp | 1 + 6 files changed, 42 insertions(+), 3 deletions(-) diff --git a/res/modules/internal/stdcomp.lua b/res/modules/internal/stdcomp.lua index a62989a9..767bfa35 100644 --- a/res/modules/internal/stdcomp.lua +++ b/res/modules/internal/stdcomp.lua @@ -125,6 +125,23 @@ return { ::continue:: end end, + physics_update = function(tps, parts, part) + for uid, entity in pairs(entities) do + if uid % parts ~= part then + goto continue + end + for _, component in pairs(entity.components) do + local callback = component.on_physics_update + if not component.__disabled and callback then + local result, err = pcall(callback, tps) + if err then + debug.error(err) + end + end + end + ::continue:: + end + end, render = function(delta) for _,entity in pairs(entities) do for _, component in pairs(entity.components) do diff --git a/res/scripts/components/mob.lua b/res/scripts/components/mob.lua index 8cc63dc5..783506a1 100644 --- a/res/scripts/components/mob.lua +++ b/res/scripts/components/mob.lua @@ -18,7 +18,7 @@ end def_prop("jump_force", 0.0) def_prop("air_damping", 1.0) def_prop("ground_damping", 1.0) -def_prop("movement_speed", 4.0) +def_prop("movement_speed", 3.0) def_prop("run_speed_mul", 1.5) def_prop("crouch_speed_mul", 0.35) def_prop("flight_speed_mul", 4.0) @@ -56,7 +56,7 @@ function move_horizontal(speed, dir, vel) body:set_vel(vel) end -function on_update(tps) +function on_physics_update(tps) local delta = (1.0 / tps) local pid = entity:get_player() if pid and hud and not hud.is_inventory_open() and not menu.page ~= "" then diff --git a/src/logic/scripting/scripting.hpp b/src/logic/scripting/scripting.hpp index bae5aac6..ddc87e8b 100644 --- a/src/logic/scripting/scripting.hpp +++ b/src/logic/scripting/scripting.hpp @@ -130,6 +130,7 @@ namespace scripting { void on_entity_fall(const Entity& entity); void on_entity_save(const Entity& entity); void on_entities_update(int tps, int parts, int part); + void on_entities_physics_update(int tps, int parts, int part); void on_entities_render(float delta); void on_sensor_enter(const Entity& entity, size_t index, entityid_t oid); void on_sensor_exit(const Entity& entity, size_t index, entityid_t oid); diff --git a/src/logic/scripting/scripting_entities.cpp b/src/logic/scripting/scripting_entities.cpp index 4deabd4b..2ab63ae3 100644 --- a/src/logic/scripting/scripting_entities.cpp +++ b/src/logic/scripting/scripting_entities.cpp @@ -279,6 +279,16 @@ void scripting::on_entities_update(int tps, int parts, int part) { lua::pop(L); } +void scripting::on_entities_physics_update(int tps, int parts, int part) { + auto L = lua::get_main_state(); + lua::get_from(L, STDCOMP, "physics_update", true); + lua::pushinteger(L, tps); + lua::pushinteger(L, parts); + lua::pushinteger(L, part); + lua::call_nothrow(L, 3, 0); + lua::pop(L); +} + void scripting::on_entities_render(float delta) { auto L = lua::get_main_state(); lua::get_from(L, STDCOMP, "render", true); diff --git a/src/objects/Entities.cpp b/src/objects/Entities.cpp index 2353dc1b..4982c636 100644 --- a/src/objects/Entities.cpp +++ b/src/objects/Entities.cpp @@ -25,7 +25,10 @@ static debug::Logger logger("entities"); Entities::Entities(Level& level) - : level(level), sensorsTickClock(20, 3), updateTickClock(20, 3) { + : level(level), + sensorsTickClock(20, 3), + updateTickClock(20, 3), + physicsTickClock(60, 1) { } std::optional Entities::get(entityid_t id) { @@ -320,6 +323,13 @@ void Entities::update(float delta) { updateTickClock.getPart() ); } + if (physicsTickClock.update(delta)) { + scripting::on_entities_physics_update( + physicsTickClock.getTickRate(), + physicsTickClock.getParts(), + physicsTickClock.getPart() + ); + } } static void debug_render_skeleton( diff --git a/src/objects/Entities.hpp b/src/objects/Entities.hpp index 03fcec8c..95e3be68 100644 --- a/src/objects/Entities.hpp +++ b/src/objects/Entities.hpp @@ -39,6 +39,7 @@ class Entities { entityid_t nextID = 1; util::Clock sensorsTickClock; util::Clock updateTickClock; + util::Clock physicsTickClock; void updateSensors( Rigidbody& body, const Transform& tsf, std::vector& sensors From 669cb48f544a41f67e73658f805530b5ca42aadb Mon Sep 17 00:00:00 2001 From: MihailRis Date: Tue, 19 Aug 2025 21:20:44 +0300 Subject: [PATCH 029/125] fix set_gravity_scale use --- res/scripts/components/mob.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/scripts/components/mob.lua b/res/scripts/components/mob.lua index 783506a1..840aba42 100644 --- a/res/scripts/components/mob.lua +++ b/res/scripts/components/mob.lua @@ -125,7 +125,7 @@ function on_physics_update(tps) jump() end body:set_vdamping(flight) - body:set_gravity_scale(flight and 0.0 or props.gravity_scale) + body:set_gravity_scale({0, flight and 0.0 or props.gravity_scale, 0}) body:set_linear_damping( (flight or not grounded) and props.air_damping or props.ground_damping ) From 5994371145dd6b914700ea3db3721ff25ac856ee Mon Sep 17 00:00:00 2001 From: KotIsOff Date: Sat, 23 Aug 2025 01:33:06 +0300 Subject: [PATCH 030/125] =?UTF-8?q?=D0=9F=D1=80=D0=BE=D0=B2=D0=B5=D1=80?= =?UTF-8?q?=D0=BA=D0=B0=20=D0=B2=D0=B5=D1=80=D1=81=D0=B8=D0=B9=20=D0=B7?= =?UTF-8?q?=D0=B0=D0=B2=D0=B8=D1=81=D0=B8=D0=BC=D0=BE=D1=81=D1=82=D0=B5?= =?UTF-8?q?=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Добавил проверку версий установленных зависимостей паков. --- res/layouts/pages/content.xml.lua | 13 +++++++++++-- res/texts/en_US.txt | 1 + res/texts/ru_RU.txt | 1 + src/content/ContentPack.cpp | 10 +++++++++- src/content/ContentPack.hpp | 1 + src/content/PacksManager.cpp | 8 ++++++++ src/logic/scripting/lua/libs/libpack.cpp | 3 ++- 7 files changed, 33 insertions(+), 4 deletions(-) diff --git a/res/layouts/pages/content.xml.lua b/res/layouts/pages/content.xml.lua index 3f1903f3..50288d17 100644 --- a/res/layouts/pages/content.xml.lua +++ b/res/layouts/pages/content.xml.lua @@ -181,8 +181,9 @@ function check_dependencies(packinfo) return end for i,dep in ipairs(packinfo.dependencies) do - local depid = dep:sub(2,-1) - if dep:sub(1,1) == '!' then + local depid, depver = unpack(string.split(dep:sub(2,-1), "@")) + + if dep:sub(1,1) == '!' then if not table.has(packs_all, depid) then return string.format( "%s (%s)", gui.str("error.dependency-not-found"), depid @@ -192,6 +193,14 @@ function check_dependencies(packinfo) table.insert(required, depid) end end + + local dep_pack = pack.get_info(depid); + + if depver ~= "*" or depver ~= dep_pack.version then + return string.format("%s (%s@%s != %s)", gui.str("error.dependency-version-not-met"), depid, dep_pack.version, depver); + end + + debug.print(packinfo); end return end diff --git a/res/texts/en_US.txt b/res/texts/en_US.txt index 57a6cb6b..267566cd 100644 --- a/res/texts/en_US.txt +++ b/res/texts/en_US.txt @@ -7,6 +7,7 @@ world.convert-block-layouts=Blocks fields have changes! Convert world files? pack.remove-confirm=Do you want to erase all pack(s) content from the world forever? error.pack-not-found=Could not to find pack error.dependency-not-found=Dependency pack is not found +error.dependency-version-not-met=Dependency pack version is not met. world.delete-confirm=Do you want to delete world forever? world.generators.default=Default world.generators.flat=Flat diff --git a/res/texts/ru_RU.txt b/res/texts/ru_RU.txt index d2d7c63e..f7f11a36 100644 --- a/res/texts/ru_RU.txt +++ b/res/texts/ru_RU.txt @@ -32,6 +32,7 @@ devtools.output=Вывод error.pack-not-found=Не удалось найти пакет error.dependency-not-found=Используемая зависимость не найдена +error.dependency-version-not-met=Версия зависимости не соответствует необходимой pack.remove-confirm=Удалить весь поставляемый паком/паками контент из мира (безвозвратно)? # Подсказки diff --git a/src/content/ContentPack.cpp b/src/content/ContentPack.cpp index ac8c8d59..8c3ea541 100644 --- a/src/content/ContentPack.cpp +++ b/src/content/ContentPack.cpp @@ -132,7 +132,15 @@ ContentPack ContentPack::read(const io::path& folder) { level = DependencyLevel::weak; break; } - pack.dependencies.push_back({level, depName}); + + std::string depVersion = "*"; + size_t version_pos = depName.rfind("@"); + if (version_pos != std::string::npos){ + depVersion = depName.substr(version_pos + 1); + depName = depName.substr(0, version_pos); + } + + pack.dependencies.push_back({level, depName, depVersion}); } } diff --git a/src/content/ContentPack.hpp b/src/content/ContentPack.hpp index f4e44801..f8bc760c 100644 --- a/src/content/ContentPack.hpp +++ b/src/content/ContentPack.hpp @@ -35,6 +35,7 @@ enum class DependencyLevel { struct DependencyPack { DependencyLevel level; std::string id; + std::string verison; }; struct ContentPackStats { diff --git a/src/content/PacksManager.cpp b/src/content/PacksManager.cpp index 0e9925c8..76cc54d7 100644 --- a/src/content/PacksManager.cpp +++ b/src/content/PacksManager.cpp @@ -105,6 +105,14 @@ static bool resolve_dependencies( // added continue; } + if (dep.verison == "*" || dep.verison == found->second.version){ + // dependency pack version mets the required one + continue; + } else { + throw contentpack_error( + dep.id, io::path(), "does not meet required version '" + dep.verison +"' of '" + pack->id + "'" + ); + } if (!util::contains(allNames, dep.id) && dep.level != DependencyLevel::weak) { diff --git a/src/logic/scripting/lua/libs/libpack.cpp b/src/logic/scripting/lua/libs/libpack.cpp index adc45096..be38fcb2 100644 --- a/src/logic/scripting/lua/libs/libpack.cpp +++ b/src/logic/scripting/lua/libs/libpack.cpp @@ -114,7 +114,8 @@ static int l_pack_get_info( default: throw std::runtime_error(""); } - lua::pushfstring(L, "%s%s", prefix.c_str(), dpack.id.c_str()); + + lua::pushfstring(L, "%s%s@%s", prefix.c_str(), dpack.id.c_str(), dpack.verison.c_str()); lua::rawseti(L, i + 1); } lua::setfield(L, "dependencies"); From bbbd6c44014f71f6dd0306e9395bcdf3e2ac77b9 Mon Sep 17 00:00:00 2001 From: KotIsOff <71443736+kotisoff@users.noreply.github.com> Date: Sat, 23 Aug 2025 01:34:50 +0300 Subject: [PATCH 031/125] fixed comment typo --- src/content/PacksManager.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/content/PacksManager.cpp b/src/content/PacksManager.cpp index 76cc54d7..c898d590 100644 --- a/src/content/PacksManager.cpp +++ b/src/content/PacksManager.cpp @@ -106,7 +106,7 @@ static bool resolve_dependencies( continue; } if (dep.verison == "*" || dep.verison == found->second.version){ - // dependency pack version mets the required one + // dependency pack version meets the required one continue; } else { throw contentpack_error( From 3131068ad8aa64202e64d0766ed9efde753ac19a Mon Sep 17 00:00:00 2001 From: KotIsOff Date: Sat, 23 Aug 2025 01:42:13 +0300 Subject: [PATCH 032/125] removed print --- res/layouts/pages/content.xml.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/res/layouts/pages/content.xml.lua b/res/layouts/pages/content.xml.lua index 50288d17..1c49f72d 100644 --- a/res/layouts/pages/content.xml.lua +++ b/res/layouts/pages/content.xml.lua @@ -200,7 +200,6 @@ function check_dependencies(packinfo) return string.format("%s (%s@%s != %s)", gui.str("error.dependency-version-not-met"), depid, dep_pack.version, depver); end - debug.print(packinfo); end return end From 5d91d94433e7b6b0158ca6f10c9425a7ba564ae0 Mon Sep 17 00:00:00 2001 From: KotIsOff Date: Sat, 23 Aug 2025 01:46:13 +0300 Subject: [PATCH 033/125] moved depver lua code in dep level condition --- res/layouts/pages/content.xml.lua | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/res/layouts/pages/content.xml.lua b/res/layouts/pages/content.xml.lua index 1c49f72d..e57cea5f 100644 --- a/res/layouts/pages/content.xml.lua +++ b/res/layouts/pages/content.xml.lua @@ -189,17 +189,16 @@ function check_dependencies(packinfo) "%s (%s)", gui.str("error.dependency-not-found"), depid ) end + + local dep_pack = pack.get_info(depid); + if depver ~= "*" or depver ~= dep_pack.version then + return string.format("%s (%s@%s != %s)", gui.str("error.dependency-version-not-met"), depid, dep_pack.version, depver); + end + if table.has(packs_installed, packinfo.id) then table.insert(required, depid) end end - - local dep_pack = pack.get_info(depid); - - if depver ~= "*" or depver ~= dep_pack.version then - return string.format("%s (%s@%s != %s)", gui.str("error.dependency-version-not-met"), depid, dep_pack.version, depver); - end - end return end From 74238d08445e532891f62f503ed1ff580bd22084 Mon Sep 17 00:00:00 2001 From: KotIsOff Date: Sat, 23 Aug 2025 02:00:07 +0300 Subject: [PATCH 034/125] fixed condition double yat --- res/layouts/pages/content.xml.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/res/layouts/pages/content.xml.lua b/res/layouts/pages/content.xml.lua index e57cea5f..8ce1cebc 100644 --- a/res/layouts/pages/content.xml.lua +++ b/res/layouts/pages/content.xml.lua @@ -191,10 +191,10 @@ function check_dependencies(packinfo) end local dep_pack = pack.get_info(depid); - if depver ~= "*" or depver ~= dep_pack.version then + if depver ~= "*" and depver ~= dep_pack.version then return string.format("%s (%s@%s != %s)", gui.str("error.dependency-version-not-met"), depid, dep_pack.version, depver); end - + if table.has(packs_installed, packinfo.id) then table.insert(required, depid) end From f91b734aeaee5ed490133db26c5f1fd211fec5ef Mon Sep 17 00:00:00 2001 From: KotIsOff Date: Sat, 23 Aug 2025 16:43:42 +0300 Subject: [PATCH 035/125] docs for dependency version --- doc/en/content-packs.md | 5 +++++ doc/ru/content-packs.md | 7 ++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/doc/en/content-packs.md b/doc/en/content-packs.md index c32d1b03..b7a78938 100644 --- a/doc/en/content-packs.md +++ b/doc/en/content-packs.md @@ -30,6 +30,11 @@ If prefix is not specified, '!' level will be used. Example: '~randutil' - weak dependency 'randutil'. +Dependency version is indicated by postfix after '@' symbol. +If postfix is not specified, '*' (any) version will be used. + +Example: 'randutil@1.0' - dependency 'randutil' which requires version 1.0. + Example: ```json { diff --git a/doc/ru/content-packs.md b/doc/ru/content-packs.md index 77a78636..716e25ef 100644 --- a/doc/ru/content-packs.md +++ b/doc/ru/content-packs.md @@ -30,7 +30,12 @@ - '~' - слабая зависимость Отсутствие префикса интерпретируется как '!'. -Пример: '~randutil' - слабая зависимость 'randutil'. +Пример: '~randutil@1.0' - слабая зависимость 'randutil'. + +Версии зависимостей указываются с помощью постфикса через '@'. +Отсутствие версии зависимости интерпретируется как '*', т.е. любая версия. + +Пример: 'randutil@1.0' - зависимость 'randutil' версии 1.0. Пример: ```json From 45bc4037ecc7fa7d39d0c36ac7d642a83670a47d Mon Sep 17 00:00:00 2001 From: KotIsOff Date: Sat, 23 Aug 2025 16:46:31 +0300 Subject: [PATCH 036/125] fix: escaping for '*' symbol --- doc/en/content-packs.md | 2 +- doc/ru/content-packs.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/en/content-packs.md b/doc/en/content-packs.md index b7a78938..53e095b9 100644 --- a/doc/en/content-packs.md +++ b/doc/en/content-packs.md @@ -31,7 +31,7 @@ If prefix is not specified, '!' level will be used. Example: '~randutil' - weak dependency 'randutil'. Dependency version is indicated by postfix after '@' symbol. -If postfix is not specified, '*' (any) version will be used. +If postfix is not specified, '\*' (any) version will be used. Example: 'randutil@1.0' - dependency 'randutil' which requires version 1.0. diff --git a/doc/ru/content-packs.md b/doc/ru/content-packs.md index 716e25ef..34ccaab7 100644 --- a/doc/ru/content-packs.md +++ b/doc/ru/content-packs.md @@ -33,7 +33,7 @@ Пример: '~randutil@1.0' - слабая зависимость 'randutil'. Версии зависимостей указываются с помощью постфикса через '@'. -Отсутствие версии зависимости интерпретируется как '*', т.е. любая версия. +Отсутствие версии зависимости интерпретируется как '\*', т.е. любая версия. Пример: 'randutil@1.0' - зависимость 'randutil' версии 1.0. From 5b18639d94d6ef304bddfd73cc8b6211bb4d09c8 Mon Sep 17 00:00:00 2001 From: KotIsOff Date: Mon, 25 Aug 2025 21:21:05 +0300 Subject: [PATCH 037/125] slightly corrected docs --- doc/en/content-packs.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/en/content-packs.md b/doc/en/content-packs.md index 53e095b9..10c70afb 100644 --- a/doc/en/content-packs.md +++ b/doc/en/content-packs.md @@ -30,8 +30,8 @@ If prefix is not specified, '!' level will be used. Example: '~randutil' - weak dependency 'randutil'. -Dependency version is indicated by postfix after '@' symbol. -If postfix is not specified, '\*' (any) version will be used. +Dependency version is indicated after '@' symbol. +If version is not specified, '\*' (any) version will be used. Example: 'randutil@1.0' - dependency 'randutil' which requires version 1.0. From 7aa4c33721ab9c0238b8caf49f7305e9d1052ac2 Mon Sep 17 00:00:00 2001 From: KotIsOff Date: Tue, 26 Aug 2025 01:52:26 +0300 Subject: [PATCH 038/125] dependency version operators MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit а также сделал и вынес Version в ContentPackVersion ну и подправил луа скрипты. требуются тесты --- res/layouts/pages/content.xml.lua | 84 +++++++++++++++++++++++- src/content/ContentPack.cpp | 41 ++++++++++-- src/content/ContentPack.hpp | 10 ++- src/content/ContentPackVersion.cpp | 66 +++++++++++++++++++ src/content/ContentPackVersion.hpp | 51 ++++++++++++++ src/content/PacksManager.cpp | 14 +++- src/logic/scripting/lua/libs/libpack.cpp | 2 +- 7 files changed, 255 insertions(+), 13 deletions(-) create mode 100644 src/content/ContentPackVersion.cpp create mode 100644 src/content/ContentPackVersion.hpp diff --git a/res/layouts/pages/content.xml.lua b/res/layouts/pages/content.xml.lua index 8ce1cebc..1fe5c160 100644 --- a/res/layouts/pages/content.xml.lua +++ b/res/layouts/pages/content.xml.lua @@ -176,6 +176,81 @@ function place_pack(panel, packinfo, callback, position_func) end end +local Version = {}; + +function Version.matches_pattern(version) + for _, letter in string.gmatch(version, "%s+") do + if type(letter) ~= "number" or letter ~= "." then + return false; + end + + local t = string.split(version, "."); + + return #t == 2 or #t == 3; + end +end + +function Version.__equal(ver1, ver2) + return ver1[1] == ver2[1] and ver1[2] == ver2[2] and ver1[3] == ver2[3]; +end + +function Version.__more(ver1, ver2) + if ver1[1] ~= ver2[1] then return ver1[1] > ver2[1] end; + if ver1[2] ~= ver2[2] then return ver1[2] > ver2[2] end; + return ver1[3] > ver2[3]; +end + +function Version.__less(ver1, ver2) + return Version.__more(ver2, ver1); +end + +function Version.__more_or_equal(ver1, ver2) + return not Version.__less(ver1, ver2); +end + +function Version.__less_or_equal(ver1, ver2) + return not Version.__more(ver1, ver2); +end + +function Version.compare(op, ver1, ver2) + ver1 = string.split(ver1, "."); + ver2 = string.split(ver2, "."); + + if op == "=" then return Version.__equal(ver1, ver2); + elseif op == ">" then return Version.__more(ver1, ver2); + elseif op == "<" then return Version.__less(ver1, ver2); + elseif op == ">=" then return Version.__more_or_equal(ver1, ver2); + elseif op == "<=" then return Version.__less_or_equal(ver1, ver2); + else return false; end +end + +function Version.parse(version) + local op = string.sub(version, 1, 2); + if op == ">=" or op == "=>" then + return ">=", string.sub(version, #op + 1); + elseif op == "<=" or op == "=<" then + return "<=", string.sub(version, #op + 1); + end + + op = string.sub(version, 1, 1); + if op == ">" or op == "<" then + return op, string.sub(version, #op + 1); + end + + return "=", version; +end + +local function compare_version(dependent_version, actual_version) + if Version.matches_pattern(dependent_version) and Version.matches_pattern(actual_version) then + local op, dep_ver = Version.parse_version(dependent_version); + Version.compare(op, dep_ver, actual_version); + elseif dependent_version == "*" or dependent_version == actual_version then + return true; + else + return false; + end +end + function check_dependencies(packinfo) if packinfo.dependencies == nil then return @@ -190,9 +265,14 @@ function check_dependencies(packinfo) ) end + local dep_pack = pack.get_info(depid); - if depver ~= "*" and depver ~= dep_pack.version then - return string.format("%s (%s@%s != %s)", gui.str("error.dependency-version-not-met"), depid, dep_pack.version, depver); + + if not compare_version(depver, dep_pack.version) then + local op, ver = Version.parse(depver); + + print(string.format("%s: %s !%s %s (%s)", gui.str("error.dependency-version-not-met"), dep_pack.version, op, ver, depid)); + return string.format("%s: %s != %s (%s)", gui.str("error.dependency-version-not-met"), dep_pack.version, ver, depid); end if table.has(packs_installed, packinfo.id) then diff --git a/src/content/ContentPack.cpp b/src/content/ContentPack.cpp index 8c3ea541..49d7ee1f 100644 --- a/src/content/ContentPack.cpp +++ b/src/content/ContentPack.cpp @@ -133,14 +133,43 @@ ContentPack ContentPack::read(const io::path& folder) { break; } - std::string depVersion = "*"; - size_t version_pos = depName.rfind("@"); - if (version_pos != std::string::npos){ - depVersion = depName.substr(version_pos + 1); - depName = depName.substr(0, version_pos); + std::string depVer = "*"; + std::string depVerOperator = "="; + + size_t versionPos = depName.rfind("@"); + if (versionPos != std::string::npos) { + depVer = depName.substr(versionPos + 1); + depName = depName.substr(0, versionPos); + + if (depVer.size() >= 2) { + std::string op = depVer.substr(0, 2); + std::uint8_t op_size = 0; + + // Two symbol operators + if (op == ">=" || op == "=>" || op == "<=" || op == "=<") { + op_size = 2; + depVerOperator = op; + } + + // One symbol operators + else { + op = depVer.substr(0, 1); + + if (op == ">" || op == "<") { + op_size = 1; + depVerOperator = op; + } + } + + depVer = depVer.substr(op_size); + } else { + if (depVer == ">" || depVer == "<"){ + depVer = "*"; + } + } } - pack.dependencies.push_back({level, depName, depVersion}); + pack.dependencies.push_back({level, depName, depVer, depVerOperator}); } } diff --git a/src/content/ContentPack.hpp b/src/content/ContentPack.hpp index f8bc760c..0069553a 100644 --- a/src/content/ContentPack.hpp +++ b/src/content/ContentPack.hpp @@ -20,11 +20,16 @@ public: io::path folder, const std::string& message ); - + std::string getPackId() const; io::path getFolder() const; }; +enum class DependencyVersionOperator { + equal, more, less, + more_or_equal, less_or_equal +}; + enum class DependencyLevel { required, // dependency must be installed optional, // dependency will be installed if found @@ -35,7 +40,8 @@ enum class DependencyLevel { struct DependencyPack { DependencyLevel level; std::string id; - std::string verison; + std::string version; + std::string op; }; struct ContentPackStats { diff --git a/src/content/ContentPackVersion.cpp b/src/content/ContentPackVersion.cpp new file mode 100644 index 00000000..b8a0564b --- /dev/null +++ b/src/content/ContentPackVersion.cpp @@ -0,0 +1,66 @@ +#include "ContentPackVersion.hpp" + +#include +#include +#include + +#include "coders/commons.hpp" + +Version::Version(const std::string& version) { + major = 0; + minor = 0; + patch = 0; + + std::vector parts; + + std::stringstream ss(version); + std::string part; + while (std::getline(ss, part, '.')) { + if (!part.empty()) { + parts.push_back(std::stoi(part)); + } + } + + if (parts.size() > 0) major = parts[0]; + if (parts.size() > 1) minor = parts[1]; + if (parts.size() > 2) patch = parts[2]; +} + +DependencyVersionOperator Version::string_to_operator(const std::string& op) { + if (op == "=") + return DependencyVersionOperator::equal; + else if (op == ">") + return DependencyVersionOperator::more; + else if (op == "<") + return DependencyVersionOperator::less; + else if (op == ">=" || op == "=>") + return DependencyVersionOperator::more_or_equal; + else if (op == "<=" || op == "=<") + return DependencyVersionOperator::less_or_equal; + else return DependencyVersionOperator::equal; +} + +bool isNumber(const std::string& s) { + return !s.empty() && std::all_of(s.begin(), s.end(), ::is_digit); +} + +bool Version::matches_pattern(const std::string& version) { + for (char c : version) { + if (!isdigit(c) && c != '.') { + return false; + } + } + + std::stringstream ss(version); + + std::vector parts; + std::string part; + while (std::getline(ss, part, '.')) { + if (part.empty()) return false; + if (!isNumber(part)) return false; + + parts.push_back(part); + } + + return parts.size() == 2 || parts.size() == 3; +} \ No newline at end of file diff --git a/src/content/ContentPackVersion.hpp b/src/content/ContentPackVersion.hpp new file mode 100644 index 00000000..ef298b9e --- /dev/null +++ b/src/content/ContentPackVersion.hpp @@ -0,0 +1,51 @@ +#include + +#include "content/ContentPack.hpp" + +class Version { + public: + int major; + int minor; + int patch; + + Version(const std::string& version); + + bool operator==(const Version& other) const { + return major == other.major && minor == other.minor && patch == other.patch; + } + + bool operator<(const Version& other) const { + if (major != other.major) return major < other.major; + if (minor != other.minor) return minor < other.minor; + return patch < other.patch; + } + + + bool operator>(const Version& other) const { + return other < *this; + } + + bool operator>=(const Version& other) const { + return !(*this < other); + } + + bool operator<=(const Version& other) const { + return !(*this > other); + } + + bool process_operator(const std::string& op, const Version& other) const { + auto dep_op = Version::string_to_operator(op); + + switch(dep_op) { + case DependencyVersionOperator::equal: return *this == other; + case DependencyVersionOperator::more: return *this > other; + case DependencyVersionOperator::less: return *this < other; + case DependencyVersionOperator::less_or_equal: return *this <= other; + case DependencyVersionOperator::more_or_equal: return *this >= other; + default: return false; + } + } + + static DependencyVersionOperator string_to_operator(const std::string& op); + static bool matches_pattern(const std::string& version); +}; \ No newline at end of file diff --git a/src/content/PacksManager.cpp b/src/content/PacksManager.cpp index c898d590..1e2ed49f 100644 --- a/src/content/PacksManager.cpp +++ b/src/content/PacksManager.cpp @@ -3,6 +3,7 @@ #include #include +#include "ContentPackVersion.hpp" #include "util/listutil.hpp" PacksManager::PacksManager() = default; @@ -105,12 +106,21 @@ static bool resolve_dependencies( // added continue; } - if (dep.verison == "*" || dep.verison == found->second.version){ + + auto dep_pack = found -> second; + + if (Version::matches_pattern(dep.version) && Version::matches_pattern(dep_pack.version) + && Version(dep_pack.version) + .process_operator(dep.op, Version(dep.version)) + ) { // dependency pack version meets the required one continue; + } else if (dep.version == "*" || dep.version == dep_pack.version){ + // fallback: dependency pack version also meets required one + continue; } else { throw contentpack_error( - dep.id, io::path(), "does not meet required version '" + dep.verison +"' of '" + pack->id + "'" + dep.id, io::path(), "does not meet required version '" + dep.op + dep.version +"' of '" + pack->id + "'" ); } diff --git a/src/logic/scripting/lua/libs/libpack.cpp b/src/logic/scripting/lua/libs/libpack.cpp index be38fcb2..fc5e2252 100644 --- a/src/logic/scripting/lua/libs/libpack.cpp +++ b/src/logic/scripting/lua/libs/libpack.cpp @@ -115,7 +115,7 @@ static int l_pack_get_info( throw std::runtime_error(""); } - lua::pushfstring(L, "%s%s@%s", prefix.c_str(), dpack.id.c_str(), dpack.verison.c_str()); + lua::pushfstring(L, "%s%s@%s%s", prefix.c_str(), dpack.id.c_str(), dpack.op.c_str(), dpack.version.c_str()); lua::rawseti(L, i + 1); } lua::setfield(L, "dependencies"); From 01b3229aac8a07be7eadf85f8997d00c9a422930 Mon Sep 17 00:00:00 2001 From: KotIsOff Date: Tue, 26 Aug 2025 01:59:00 +0300 Subject: [PATCH 039/125] fix: match wrong magic symbol --- res/layouts/pages/content.xml.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/layouts/pages/content.xml.lua b/res/layouts/pages/content.xml.lua index 1fe5c160..8ed37630 100644 --- a/res/layouts/pages/content.xml.lua +++ b/res/layouts/pages/content.xml.lua @@ -179,7 +179,7 @@ end local Version = {}; function Version.matches_pattern(version) - for _, letter in string.gmatch(version, "%s+") do + for _, letter in string.gmatch(version, "%.+") do if type(letter) ~= "number" or letter ~= "." then return false; end From a0c66d56d89aeabb2462b7e732b59f92e79f2464 Mon Sep 17 00:00:00 2001 From: KotIsOff Date: Tue, 26 Aug 2025 02:10:16 +0300 Subject: [PATCH 040/125] docs for operators --- doc/en/content-packs.md | 4 ++-- doc/ru/content-packs.md | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/en/content-packs.md b/doc/en/content-packs.md index 10c70afb..a41f95f9 100644 --- a/doc/en/content-packs.md +++ b/doc/en/content-packs.md @@ -30,10 +30,10 @@ If prefix is not specified, '!' level will be used. Example: '~randutil' - weak dependency 'randutil'. -Dependency version is indicated after '@' symbol. +Dependency version is indicated after '@' symbol and have operators to restrict acceptable versions. If version is not specified, '\*' (any) version will be used. -Example: 'randutil@1.0' - dependency 'randutil' which requires version 1.0. +Example: 'randutil@>=1.0' - dependency 'randutil' which requires version 1.0 or newer. Example: ```json diff --git a/doc/ru/content-packs.md b/doc/ru/content-packs.md index 34ccaab7..4245555f 100644 --- a/doc/ru/content-packs.md +++ b/doc/ru/content-packs.md @@ -30,12 +30,12 @@ - '~' - слабая зависимость Отсутствие префикса интерпретируется как '!'. -Пример: '~randutil@1.0' - слабая зависимость 'randutil'. +Пример: '~randutil' - слабая зависимость 'randutil'. -Версии зависимостей указываются с помощью постфикса через '@'. +Версии зависимостей указываются после '@' и имеют операторы для ограничения допустимых версий. Отсутствие версии зависимости интерпретируется как '\*', т.е. любая версия. -Пример: 'randutil@1.0' - зависимость 'randutil' версии 1.0. +Пример: 'randutil@>=1.0' - зависимость 'randutil' версии 1.0 и старше. Пример: ```json From ac1623f86c61c6260ff806926c20ce6208ef0008 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Wed, 27 Aug 2025 19:31:34 +0300 Subject: [PATCH 041/125] add 'Show Paths' checkbox --- src/frontend/debug_panel.cpp | 14 ++++++++++++++ src/graphics/render/DebugLinesRenderer.cpp | 8 ++++++-- src/graphics/render/DebugLinesRenderer.hpp | 2 ++ src/graphics/render/WorldRenderer.hpp | 1 - 4 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/frontend/debug_panel.cpp b/src/frontend/debug_panel.cpp index 5e238f42..10c6b85c 100644 --- a/src/frontend/debug_panel.cpp +++ b/src/frontend/debug_panel.cpp @@ -12,6 +12,7 @@ #include "graphics/render/WorldRenderer.hpp" #include "graphics/render/ParticlesRenderer.hpp" #include "graphics/render/ChunksRenderer.hpp" +#include "graphics/render/DebugLinesRenderer.hpp" #include "logic/scripting/scripting.hpp" #include "network/Network.hpp" #include "objects/Player.hpp" @@ -45,6 +46,7 @@ static std::shared_ptr