From e8856d7485fdbc38dc1e8edeca920ebdc72b2fda Mon Sep 17 00:00:00 2001 From: Tom MOUSSET <62646888+pytomprog@users.noreply.github.com> Date: Sun, 18 Jan 2026 15:14:20 +0100 Subject: [PATCH 1/6] Refactor chunk generation to use Lua scripts Replaces hardcoded chunk generation logic with Lua script-based generation in Planet::GenerateChunk. Updates the function signature to accept a script name and modifies PlayerSessionHandler to use planet data from the database for chunk regeneration. --- include/CommonLib/Planet.hpp | 2 +- src/CommonLib/Planet.cpp | 287 +----------------- .../Session/PlayerSessionHandler.cpp | 13 +- 3 files changed, 26 insertions(+), 276 deletions(-) diff --git a/include/CommonLib/Planet.hpp b/include/CommonLib/Planet.hpp index 0a1e2561..dc235fa8 100644 --- a/include/CommonLib/Planet.hpp +++ b/include/CommonLib/Planet.hpp @@ -44,7 +44,7 @@ namespace tsom void ForEachChunk(Nz::FunctionRef callback) override; void ForEachChunk(Nz::FunctionRef callback) const override; - void GenerateChunk(const BlockLibrary& blockLibrary, Chunk& chunk, Nz::UInt32 seed, const Nz::Vector3ui& chunkCount); + void GenerateChunk(const BlockLibrary& blockLibrary, Chunk& chunk, Nz::UInt32 seed, const Nz::Vector3ui& chunkCount, std::string scriptName); void GenerateChunks(const BlockLibrary& blockLibrary, Nz::TaskScheduler& taskScheduler, Nz::UInt32 seed, const Nz::Vector3ui& chunkCount, std::string scriptName); void GeneratePlatform(const BlockLibrary& blockLibrary, Direction upDirection, const BlockIndices& platformCenter); diff --git a/src/CommonLib/Planet.cpp b/src/CommonLib/Planet.cpp index 8822712d..01931bcf 100644 --- a/src/CommonLib/Planet.cpp +++ b/src/CommonLib/Planet.cpp @@ -227,289 +227,30 @@ namespace tsom callback(chunkIndices, *chunkData.chunk); } - void Planet::GenerateChunk(const BlockLibrary& blockLibrary, Chunk& chunk, Nz::UInt32 seed, const Nz::Vector3ui& chunkCount) + void Planet::GenerateChunk(const BlockLibrary& blockLibrary, Chunk& chunk, Nz::UInt32 seed, const Nz::Vector3ui& chunkCount, std::string scriptName) { - constexpr std::size_t freeSpace = 30; - ChunkIndices chunkIndices = chunk.GetIndices(); - Nz::UInt32 chunkSeed = seed + static_cast(chunkIndices.x) + static_cast(chunkIndices.y) + static_cast(chunkIndices.z); - std::minstd_rand rand(chunkSeed); - std::bernoulli_distribution dis(0.9); + ScriptingContext scriptingContext = ScriptingContext(m_app); + scriptingContext.RegisterLibrary(); + scriptingContext.RegisterLibrary(); - BlockIndex dirtBlockIndex = blockLibrary.GetBlockIndex("dirt"); - BlockIndex grassBlockIndex = blockLibrary.GetBlockIndex("grass"); - BlockIndex stoneBlockIndex = blockLibrary.GetBlockIndex("stone"); - BlockIndex stoneMossyBlockIndex = blockLibrary.GetBlockIndex("stone_mossy"); - BlockIndex snowBlockIndex = blockLibrary.GetBlockIndex("snow"); + Nz::Result execResult = scriptingContext.LoadFile(fmt::format("scripts/planets/{}.lua", scriptName)); + if (!execResult) + return; - Nz::Vector3i maxHeight((Nz::Vector3i(chunkCount) + Nz::Vector3i(1)) / 2); - maxHeight *= int(Planet::ChunkSize); + sol::protected_function generationFunction = execResult.GetValue(); chunk.LockWrite(); NAZARA_DEFER({ chunk.UnlockWrite(); }); - chunk.Reset([&](BlockIndex* blockIndices) + auto result = generationFunction(chunk, seed, chunkCount); + if (!result.valid()) { - // Fill all blocks based on their depth - ChunkIndices chunkIndices = chunk.GetIndices(); - - double heightScale = 1.5f * m_tileSize; - double scale = 0.02f * m_tileSize; - - siv::PerlinNoise perlin; - - BlockIndex* blockIndexPtr = blockIndices; -#if 0 - for (unsigned int z = 0; z < Planet::ChunkSize; ++z) - { - for (unsigned int y = 0; y < Planet::ChunkSize; ++y) - { - for (unsigned int x = 0; x < Planet::ChunkSize; ++x) - { - Nz::Vector3i blockPos = GetBlockIndices(chunkIndices, { x, y, z }); - float distToSurface = sdRoundBox(Nz::Vector3f(blockPos), Nz::Vector3f(80.f, 80.f, 80.f), m_cornerRadius); - - if (distToSurface <= 0.f) - *blockIndexPtr++ = dirtBlockIndex; - else - *blockIndexPtr++ = EmptyBlockIndex; - } - } - } - #endif - - #if 1 - for (unsigned int z = 0; z < Planet::ChunkSize; ++z) - { - for (unsigned int y = 0; y < Planet::ChunkSize; ++y) - { - for (unsigned int x = 0; x < Planet::ChunkSize; ++x) - { - Nz::Vector3i blockPos = GetBlockIndices(chunkIndices, { x, y, z }); - unsigned int depth = Nz::SafeCaster(std::min({ - maxHeight.x - std::abs(blockPos.x), - maxHeight.y - std::abs(blockPos.z), - maxHeight.z - std::abs(blockPos.y) - })); - - if (depth < freeSpace) - { - *blockIndexPtr++ = EmptyBlockIndex; - continue; - } - - depth -= freeSpace; - - BlockIndices mapPos = GetBlockIndices(chunkIndices, { x, y, z }); - double presence = perlin.normalizedOctave3D_01(mapPos.x * scale, mapPos.y * scale, mapPos.z * scale, 4); - - if (depth < 20) - presence *= std::max(double(depth) / 20.0, 1.0); - - presence += double(depth) / std::max({ maxHeight.x, maxHeight.y, maxHeight.z }); - - BlockIndex blockIndex; - if (presence > 0.6) - { - if (depth <= 6 * 2) - blockIndex = snowBlockIndex; - else if (depth <= 18 * 2) - blockIndex = dirtBlockIndex; - else - blockIndex = (dis(rand)) ? stoneBlockIndex : stoneMossyBlockIndex; - } - else - blockIndex = EmptyBlockIndex; - - if (std::abs(blockPos.x) <= 2 && std::abs(blockPos.z) <= 2) - blockIndex = EmptyBlockIndex; - - if (blockIndex != InvalidBlockIndex) - *blockIndexPtr++ = blockIndex; - - #if 0 - - depth -= freeSpace; - - BlockIndex blockIndex; - if (depth <= 6 * 2) - blockIndex = snowBlockIndex; - else if (depth <= 18 * 2) - blockIndex = dirtBlockIndex; - else - blockIndex = (dis(rand)) ? stoneBlockIndex : stoneMossyBlockIndex; - - if (std::abs(blockPos.x) <= 2 && std::abs(blockPos.z) <= 2) - blockIndex = EmptyBlockIndex; - - if (blockIndex != InvalidBlockIndex) - *blockIndexPtr++ = blockIndex; - #endif - } - } - } - #endif - - #if 0 - Nz::EnumArray perlin; - for (auto&& [dir, noise] : perlin.iter_kv()) - noise.reseed(seed + static_cast(dir)); - - double heightScale = 1.5f * m_tileSize; - double scale = 0.02f * m_tileSize; - - // +X - for (unsigned int y = 0; y < Planet::ChunkSize; ++y) - { - for (unsigned int x = 0; x < Planet::ChunkSize; ++x) - { - BlockIndices mapPos = GetBlockIndices(chunkIndices, { 0, x, y }); - double height = perlin[Direction::Right].normalizedOctave2D_01(mapPos.y * scale, mapPos.z * scale, 4) * heightScale; - - int terrainDepth = std::round(std::min(height * (maxHeight.x / 2 - freeSpace) + freeSpace, maxHeight.x / 2)); - int blockDepth = maxHeight.x - mapPos.x + 1; - if (blockDepth < terrainDepth) - continue; - - unsigned int startHeight = Nz::SafeCaster(blockDepth - terrainDepth); - if (startHeight >= Planet::ChunkSize) - continue; - - if (BlockIndex& blockType = blockIndices[chunk.GetBlockLocalIndex({ startHeight, x, y })]; blockType == dirtBlockIndex) - blockType = grassBlockIndex; - - for (unsigned int height = startHeight + 1; height < Planet::ChunkSize; ++height) - blockIndices[chunk.GetBlockLocalIndex({ height, x, y })] = EmptyBlockIndex; - } - } - - // -X - for (unsigned int y = 0; y < Planet::ChunkSize; ++y) - { - for (unsigned int x = 0; x < Planet::ChunkSize; ++x) - { - BlockIndices mapPos = GetBlockIndices(chunkIndices, { Planet::ChunkSize - 1, x, y }); - double height = perlin[Direction::Left].normalizedOctave2D_01(mapPos.y * scale, mapPos.z * scale, 4) * heightScale; - - int terrainDepth = std::round(std::min(height * (maxHeight.x / 2 - freeSpace) + freeSpace, maxHeight.x / 2)); - int blockDepth = maxHeight.x + mapPos.x + 1; - if (blockDepth < terrainDepth) - continue; - - unsigned int startHeight = Nz::SafeCast(blockDepth - terrainDepth); - if (startHeight >= Planet::ChunkSize) - continue; - - if (BlockIndex& blockType = blockIndices[chunk.GetBlockLocalIndex({ Planet::ChunkSize - startHeight - 1, x, y })]; blockType == dirtBlockIndex) - blockType = grassBlockIndex; - - for (unsigned int height = startHeight + 1; height < Planet::ChunkSize; ++height) - blockIndices[chunk.GetBlockLocalIndex({ Planet::ChunkSize - height - 1, x, y })] = EmptyBlockIndex; - } - } - - // +Y - for (unsigned int z = 0; z < Planet::ChunkSize; ++z) - { - for (unsigned int x = 0; x < Planet::ChunkSize; ++x) - { - BlockIndices mapPos = GetBlockIndices(chunkIndices, { x, z, 0 }); - double height = perlin[Direction::Up].normalizedOctave2D_01(mapPos.x * scale, mapPos.z * scale, 4) * heightScale; - - int terrainDepth = std::round(std::min(height * (maxHeight.y / 2 - freeSpace) + freeSpace, maxHeight.y / 2)); - int blockDepth = maxHeight.y - mapPos.y + 1; - if (blockDepth < terrainDepth) - continue; - - unsigned int startHeight = Nz::SafeCaster(blockDepth - terrainDepth); - if (startHeight >= Planet::ChunkSize) - continue; - - if (BlockIndex& blockType = blockIndices[chunk.GetBlockLocalIndex({ x, z, startHeight })]; blockType == dirtBlockIndex) - blockType = grassBlockIndex; - - for (unsigned int height = startHeight + 1; height < Planet::ChunkSize; ++height) - blockIndices[chunk.GetBlockLocalIndex({ x, z, height })] = EmptyBlockIndex; - } - } - - // -Y - for (unsigned int z = 0; z < Planet::ChunkSize; ++z) - { - for (unsigned int x = 0; x < Planet::ChunkSize; ++x) - { - BlockIndices mapPos = GetBlockIndices(chunkIndices, { x, z, Planet::ChunkSize - 1 }); - double height = perlin[Direction::Down].normalizedOctave2D_01(mapPos.x * scale, mapPos.z * scale, 4) * heightScale; - - int terrainDepth = std::round(std::min(height * (maxHeight.y / 2 - freeSpace) + freeSpace, maxHeight.y / 2)); - int blockDepth = maxHeight.y + mapPos.y + 1; - if (blockDepth < terrainDepth) - continue; - - unsigned int startHeight = Nz::SafeCast(blockDepth - terrainDepth); - if (startHeight >= Planet::ChunkSize) - continue; - - if (BlockIndex& blockType = blockIndices[chunk.GetBlockLocalIndex({ x, z, Planet::ChunkSize - startHeight - 1 })]; blockType == dirtBlockIndex) - blockType = grassBlockIndex; - - for (unsigned int height = startHeight + 1; height < Planet::ChunkSize; ++height) - blockIndices[chunk.GetBlockLocalIndex({ x, z, Planet::ChunkSize - height - 1 })] = EmptyBlockIndex; - } - } - - // +Z - for (unsigned int y = 0; y < Planet::ChunkSize; ++y) - { - for (unsigned int x = 0; x < Planet::ChunkSize; ++x) - { - BlockIndices mapPos = GetBlockIndices(chunkIndices, { x, 0, y }); - double height = perlin[Direction::Back].normalizedOctave2D_01(mapPos.x * scale, mapPos.y * scale, 4) * heightScale; - - int terrainDepth = std::round(std::min(height * (maxHeight.z / 2 - freeSpace) + freeSpace, maxHeight.z / 2)); - int blockDepth = maxHeight.z - mapPos.z + 1; - if (blockDepth < terrainDepth) - continue; - - unsigned int startHeight = Nz::SafeCaster(blockDepth - terrainDepth); - if (startHeight >= Planet::ChunkSize) - continue; - - if (BlockIndex& blockType = blockIndices[chunk.GetBlockLocalIndex({ x, startHeight, y })]; blockType == dirtBlockIndex) - blockType = grassBlockIndex; - - for (unsigned int height = startHeight + 1; height < Planet::ChunkSize; ++height) - blockIndices[chunk.GetBlockLocalIndex({ x, height, y })] = EmptyBlockIndex; - } - } - - // -Z - for (unsigned int y = 0; y < Planet::ChunkSize; ++y) - { - for (unsigned int x = 0; x < Planet::ChunkSize; ++x) - { - BlockIndices mapPos = GetBlockIndices(chunkIndices, { x, Planet::ChunkSize - 1, y }); - double height = perlin[Direction::Front].normalizedOctave2D_01(mapPos.x * scale, mapPos.y * scale, 4) * heightScale; - - int terrainDepth = std::round(std::min(height * (maxHeight.z / 2 - freeSpace) + freeSpace, maxHeight.z / 2)); - int blockDepth = maxHeight.z + mapPos.z + 1; - if (blockDepth < terrainDepth) - continue; - - unsigned int startHeight = Nz::SafeCaster(blockDepth - terrainDepth); - if (startHeight >= Planet::ChunkSize) - continue; - - if (BlockIndex& blockType = blockIndices[chunk.GetBlockLocalIndex({ x, Planet::ChunkSize - startHeight - 1, y })]; blockType == dirtBlockIndex) - blockType = grassBlockIndex; - - for (unsigned int height = startHeight + 1; height < Planet::ChunkSize; ++height) - blockIndices[chunk.GetBlockLocalIndex({ x, Planet::ChunkSize - height - 1, y })] = EmptyBlockIndex; - } - } - #endif - }); + sol::error err = result; + spdlog::error("chunk {};{};{} failed to generate: {}", chunkIndices.x, chunkIndices.y, chunkIndices.z, err.what()); + return; + } } void Planet::GenerateChunks(const BlockLibrary& blockLibrary, Nz::TaskScheduler& taskScheduler, Nz::UInt32 seed, const Nz::Vector3ui& chunkCount, std::string scriptName) diff --git a/src/ServerLib/Session/PlayerSessionHandler.cpp b/src/ServerLib/Session/PlayerSessionHandler.cpp index ab4527ec..f9727390 100644 --- a/src/ServerLib/Session/PlayerSessionHandler.cpp +++ b/src/ServerLib/Session/PlayerSessionHandler.cpp @@ -364,8 +364,17 @@ namespace tsom if (Chunk* chunk = planet.GetChunk(chunkIndices)) { - planet.GenerateChunk(blockLibrary, *chunk, 42, Nz::Vector3ui(5)); - spdlog::info("regenerated chunk {};{};{}", chunkIndices.x, chunkIndices.y, chunkIndices.z); + std::optional planetId = planetEnvironment->GetDatabaseId(); + serverInstance.GetServerDatabase().GetAllPlanets([&](Database::Planet&& planetData) + { + if (planetData.id == planetId) + { + planet.GenerateChunk(blockLibrary, *chunk, planetData.seed, planetData.chunkCount, planetData.generatorName.data()); + spdlog::info("regenerated chunk {};{};{}", chunkIndices.x, chunkIndices.y, chunkIndices.z); + return false; + } + return true; + }); } return; } From 1ab98349564a3e5bbed878c08aa5ebc157898fe9 Mon Sep 17 00:00:00 2001 From: Tom MOUSSET <62646888+pytomprog@users.noreply.github.com> Date: Sun, 5 Jul 2026 22:10:44 +0200 Subject: [PATCH 2/6] Add admin /tpplayer command Adds a `/tpplayer ` admin chat command that teleports the caller to the named player's current position. The command reports when the target player cannot be found or has no controlled entity. --- .../Session/PlayerSessionHandler.cpp | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/src/ServerLib/Session/PlayerSessionHandler.cpp b/src/ServerLib/Session/PlayerSessionHandler.cpp index 0bb82f21..724bdad7 100644 --- a/src/ServerLib/Session/PlayerSessionHandler.cpp +++ b/src/ServerLib/Session/PlayerSessionHandler.cpp @@ -205,6 +205,31 @@ namespace tsom m_player->UpdateRootEnvironment(env); m_player->Respawn(env, spawnPos, Nz::Quaternionf::Identity()); } + else if (message.starts_with("/tpplayer ") && m_player->HasPermission(PlayerPermission::Admin)) + { + constexpr std::size_t commandLength = sizeof("/tpplayer ") - 1; + + std::string targetName(&message[commandLength], message.size() - commandLength); + ServerPlayer* targetPlayer = m_player->GetServerInstance().FindPlayerByNickname(targetName); + if (!targetPlayer) + { + m_player->SendChatMessage("player not found"); + return; + } + + entt::handle targetEntity = targetPlayer->GetControlledEntityReference(); + if (!targetEntity) + { + m_player->SendChatMessage("target player has no controlled entity"); + return; + } + + Nz::Vector3f targetPos = targetEntity.get().GetPosition(); + ServerEnvironment* targetEnvironment = ServerEnvironment::GetEnvironment(targetEntity); + m_player->Respawn(targetEnvironment, targetPos + Nz::Vector3f::Up() * 2.f, Nz::Quaternionf::Identity()); + + return; + } else if (message == "/fly") { entt::handle playerEntity = m_player->GetControlledEntityReference(); From b2110977b26ed1ad6bd550c2ecbd31ee8e944df7 Mon Sep 17 00:00:00 2001 From: Tom MOUSSET <62646888+pytomprog@users.noreply.github.com> Date: Sun, 5 Jul 2026 22:16:30 +0200 Subject: [PATCH 3/6] Add /help and /links chat commands Adds two player chat commands: `/links` for community/resource links and `/help` for a command list, with admin-only entries shown only to admins. --- .../Session/PlayerSessionHandler.cpp | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/ServerLib/Session/PlayerSessionHandler.cpp b/src/ServerLib/Session/PlayerSessionHandler.cpp index 724bdad7..489be82b 100644 --- a/src/ServerLib/Session/PlayerSessionHandler.cpp +++ b/src/ServerLib/Session/PlayerSessionHandler.cpp @@ -749,6 +749,37 @@ namespace tsom { throw std::runtime_error("test exception"); } + else if (message == "/links") + { + m_player->SendChatMessage("--- Game ressources ---"); + m_player->SendChatMessage("Join our community on Discord : http://discord.gg/2WUU6Sp"); + m_player->SendChatMessage("Check out the wiki : https://tsom.fandom.com/fr"); + m_player->SendChatMessage("To suggest a new feature or an idea : https://tsom-idea.digitalpulse.software"); + m_player->SendChatMessage("To report an issue : https://github.com/DigitalPulseSoftware/ThisSpaceOfMine/issues"); + return; + } + else if (message == "/help") { + m_player->SendChatMessage("--- Available commands ---"); + m_player->SendChatMessage("/help - Show this help message"); + m_player->SendChatMessage("/links - Show game resources"); + m_player->SendChatMessage("/respawn - Respawn at the default spawnpoint"); + m_player->SendChatMessage("/tpplanet - Teleport to the specified planet"); + m_player->SendChatMessage("/fly - Toggle flying mode"); + m_player->SendChatMessage("/spawnship [slot] - Spawn your ship from the specified slot (default: 0). Slot must be in [0;3["); + m_player->SendChatMessage("/spawncomputer - Spawn a computer where you're looking at"); + m_player->SendChatMessage("/spawnsensor - Spawn an atmosphere sensor where you're looking at"); + if (m_player->HasPermission(PlayerPermission::Admin)) + { + m_player->SendChatMessage("/tpplayer - Teleport to the specified player"); + m_player->SendChatMessage("/regenchunk - Regenerate the chunk you're currently in"); + m_player->SendChatMessage("/spawntree - Spawn a tree where you're looking at"); + m_player->SendChatMessage("/removetree - Remove a tree where you're looking at"); + m_player->SendChatMessage("/spawnplatform - Spawn a platform at your position (deprecated)"); + m_player->SendChatMessage("/crashserver - Crash the server (for testing purposes)"); + m_player->SendChatMessage("/crashserver 1 - Throw an exception on the server (for testing purposes)"); + } + return; + } m_player->GetServerInstance().BroadcastChatMessage(std::move(playerChat.message), m_player->GetPlayerIndex()); } From f759308b940e383be772f99147e8346578a2fd42 Mon Sep 17 00:00:00 2001 From: Tom MOUSSET <62646888+pytomprog@users.noreply.github.com> Date: Sun, 5 Jul 2026 22:22:54 +0200 Subject: [PATCH 4/6] Add block picking to selection bar Add a picked-block selection path to the block selection bar and wire middle-click picking in game state so the hotbar updates to the targeted block when available. Right-click mine and left-click place behavior remain unchanged. --- include/ClientLib/BlockSelectionBar.hpp | 1 + src/ClientLib/BlockSelectionBar.cpp | 15 +++++++++++++++ src/Game/States/GameState.cpp | 11 +++++++---- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/include/ClientLib/BlockSelectionBar.hpp b/include/ClientLib/BlockSelectionBar.hpp index 508985a4..4d7a1ff3 100644 --- a/include/ClientLib/BlockSelectionBar.hpp +++ b/include/ClientLib/BlockSelectionBar.hpp @@ -46,6 +46,7 @@ namespace tsom void SelectNext(); void SelectPrevious(); + void SelectPickedBlock(tsom::BlockIndex pickedBlockIndex); BlockSelectionBar& operator=(const BlockSelectionBar&) = delete; BlockSelectionBar& operator=(BlockSelectionBar&&) = delete; diff --git a/src/ClientLib/BlockSelectionBar.cpp b/src/ClientLib/BlockSelectionBar.cpp index c93e538c..e2f6b43a 100644 --- a/src/ClientLib/BlockSelectionBar.cpp +++ b/src/ClientLib/BlockSelectionBar.cpp @@ -63,6 +63,21 @@ namespace tsom m_selectedBlockIndex = m_blockLibrary.GetBlockIndex(s_selectableBlocks[m_selectedIndex]); } + void BlockSelectionBar::SelectPickedBlock(tsom::BlockIndex pickedBlockIndex) + { + if (!m_blockLibrary.IsValidBlock(pickedBlockIndex)) + return; + + auto it = std::find(s_selectableBlocks.begin(), s_selectableBlocks.end(), m_blockLibrary.GetBlockData(pickedBlockIndex).name); + if (it == s_selectableBlocks.end()) + return; + + m_inventorySprites[m_selectedIndex]->SetColor(Nz::Color::sRGBToLinear(Nz::Color::Gray())); + m_selectedIndex = std::distance(s_selectableBlocks.begin(), it); + m_inventorySprites[m_selectedIndex]->SetColor(Nz::Color::White()); + m_selectedBlockIndex = pickedBlockIndex; + } + void BlockSelectionBar::Layout() { BaseWidget::Layout(); diff --git a/src/Game/States/GameState.cpp b/src/Game/States/GameState.cpp index 4ec39bb8..ab477f2f 100644 --- a/src/Game/States/GameState.cpp +++ b/src/Game/States/GameState.cpp @@ -713,9 +713,6 @@ namespace tsom if (!m_isMouseLocked) return; - if (event.button != Nz::Mouse::Left && event.button != Nz::Mouse::Right) - return; - auto& stateData = GetStateData(); if (!m_pilotedShip) @@ -749,7 +746,7 @@ namespace tsom stateData.networkSession->SendPacket(mineBlock); } - else + else if (event.button == Nz::Mouse::Right) { BlockIndices blockIndices = chunkContainer.GetBlockIndices(hitChunk.GetIndices(), hitCoordinates->blockIndices); @@ -772,6 +769,12 @@ namespace tsom stateData.networkSession->SendPacket(placeBlock); } + else + { + // Pick block + tsom::BlockIndex pickedBlockIndex = chunkComponent->chunk.Get()->GetBlockContent(hitCoordinates->blockIndices); + m_blockSelectionBar->SelectPickedBlock(pickedBlockIndex); + } } } } From 7a419c644455c880a0feb2d97ccd1b9af053f5c3 Mon Sep 17 00:00:00 2001 From: Tom MOUSSET <62646888+pytomprog@users.noreply.github.com> Date: Sun, 5 Jul 2026 22:27:52 +0200 Subject: [PATCH 5/6] Add configurable FPS limiting Adds a new `Graphics.FPSLimit` config option with a default of `-1`, registers it in the game config, and applies frame pacing in `GameAppComponent` by sleeping to cap the frame rate when the limit is set above zero. --- gameconfig.lua.default | 3 +++ src/Game/GameAppComponent.cpp | 16 ++++++++++++++++ src/Game/GameAppComponent.hpp | 4 ++++ src/Game/GameConfigAppComponent.cpp | 2 ++ src/Game/GameConfigs.hpp | 1 + 5 files changed, 26 insertions(+) diff --git a/gameconfig.lua.default b/gameconfig.lua.default index b147b469..618e0846 100644 --- a/gameconfig.lua.default +++ b/gameconfig.lua.default @@ -17,3 +17,6 @@ Server = { Visual = { ChunkNormalSmoothAngle = 0.0 } +Graphics = { + FPSLimit = -1 +} diff --git a/src/Game/GameAppComponent.cpp b/src/Game/GameAppComponent.cpp index 04bb7a71..857032c9 100644 --- a/src/Game/GameAppComponent.cpp +++ b/src/Game/GameAppComponent.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -160,6 +161,8 @@ namespace tsom stateData->swapchain = &swapchain; stateData->world = &world; + m_fpsLimit = stateData->config->GetIntegerValue(Config::Graphics_FPSLimit); + #ifdef TSOM_DEV_TOOLS stateData->imgui = &m_imguiRuntime.value(); #endif @@ -209,6 +212,19 @@ namespace tsom if (m_imguiRuntime) m_imguiRuntime->EndFrame(); #endif + + // FPS limiting + if (m_fpsLimit > 0) + { + Nz::Time targetDuration = Nz::Time::Seconds(1.f / m_fpsLimit); + Nz::Time elapsed = m_frameClock.GetElapsedTime(); + if (elapsed < targetDuration) + { + Nz::Time sleepTime = targetDuration - elapsed; + std::this_thread::sleep_for(std::chrono::microseconds(sleepTime.AsMicroseconds())); + } + } + m_frameClock.Restart(); } bool GameAppComponent::CheckAssets() diff --git a/src/Game/GameAppComponent.hpp b/src/Game/GameAppComponent.hpp index 886d08f4..d6ef0a82 100644 --- a/src/Game/GameAppComponent.hpp +++ b/src/Game/GameAppComponent.hpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -62,6 +63,9 @@ namespace tsom std::optional m_imguiRuntime; #endif Nz::StateMachine m_stateMachine; + + Nz::HighPrecisionClock m_frameClock; + Nz::Int16 m_fpsLimit; }; } diff --git a/src/Game/GameConfigAppComponent.cpp b/src/Game/GameConfigAppComponent.cpp index 87c16853..81c554f2 100644 --- a/src/Game/GameConfigAppComponent.cpp +++ b/src/Game/GameConfigAppComponent.cpp @@ -51,6 +51,8 @@ namespace tsom return Nz::Ok(shadowMapSize); }); + + RegisterIntegerOption(Config::Graphics_FPSLimit, -1, 1000, -1); } std::filesystem::path GameConfigFile::GetPath() diff --git a/src/Game/GameConfigs.hpp b/src/Game/GameConfigs.hpp index c681625b..1e684803 100644 --- a/src/Game/GameConfigs.hpp +++ b/src/Game/GameConfigs.hpp @@ -17,6 +17,7 @@ namespace tsom::Config static constexpr auto Input_MouseSensitivity = ConfigFile::FloatOptionName{ "Input.MouseSensitivity" }; static constexpr auto Player_Token = ConfigFile::StringOptionName{ "Player.Token" }; static constexpr auto Server_Port = ConfigFile::IntegerOptionName{ "Server.Port" }; + static constexpr auto Graphics_FPSLimit = ConfigFile::IntegerOptionName{ "Graphics.FPSLimit" }; } #endif // TSOM_GAME_GAMECONFIGS_HPP From 580c3416ffc7d871b63e526376140ec3866fc2db Mon Sep 17 00:00:00 2001 From: Tom MOUSSET <62646888+pytomprog@users.noreply.github.com> Date: Sun, 5 Jul 2026 22:55:24 +0200 Subject: [PATCH 6/6] Fix scripting and crash handler includes Fixes a few bugs in `ScriptingContext.cpp`, adds the missing `fmt/format.h` include for Win32 crash handling, and exposes `GetChunkContainer` on `ServerPlanetEnvironment`. --- src/CommonLib/Scripting/ScriptingContext.cpp | 13 ++++++++----- src/CommonLib/Utility/CrashHandlerWin32.cpp | 1 + src/ServerLib/Scripting/ServerScriptingLibrary.cpp | 4 +++- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/CommonLib/Scripting/ScriptingContext.cpp b/src/CommonLib/Scripting/ScriptingContext.cpp index 301666f9..adc829b0 100644 --- a/src/CommonLib/Scripting/ScriptingContext.cpp +++ b/src/CommonLib/Scripting/ScriptingContext.cpp @@ -42,7 +42,7 @@ namespace tsom case LUA_TNUMBER: if (lua_isinteger(L, idx)) - lua_pushfstring(L, LUA_INTEGER_FMT, (LUAI_UACINT)lua_tointeger(L, idx)); + lua_pushstring(L, std::to_string(lua_tointeger(L, idx)).c_str()); else lua_pushfstring(L, LUA_NUMBER_FMT, (LUAI_UACNUMBER)lua_tonumber(L, idx)); break; @@ -71,7 +71,7 @@ namespace tsom default: { int tt = luaL_getmetafield(L, idx, "__name"); - const char* name = (tt == LUA_TSTRING) ? lua_tostring(L, idx) : lua_typename(L, t); + const char* name = (tt == LUA_TSTRING) ? lua_tostring(L, -1) : lua_typename(L, t); lua_pushfstring(L, "%s: %p", name, lua_topointer(L, idx)); if (tt != LUA_TNIL) lua_replace(L, -2); @@ -121,7 +121,9 @@ namespace tsom { lua_pushvalue(L, -1); lua_gettable(L, visitedIndex); - if (lua_isnil(L, -1)) + bool is_not_visited = lua_isnil(L, -1); + lua_pop(L, 1); + if (is_not_visited) { lua_pop(L, 1); @@ -166,7 +168,7 @@ namespace tsom lua_pop(L, 2); // pop new indentation and visited table - luaL_addvalue(&b); //< add (and pop) new indentation to the buffer + luaL_addvalue(&b); //< add (and pop) old indentation to the buffer lua_pushstring(L, "}"); luaL_addvalue(&b); @@ -202,9 +204,10 @@ namespace tsom std::size_t length; const char* str = lua_tolstring(L, -1, &length); - oss << std::string(str, length); + lua_pop(L, 1); if (!first) oss << "\t"; + oss << std::string(str, length); first = false; } diff --git a/src/CommonLib/Utility/CrashHandlerWin32.cpp b/src/CommonLib/Utility/CrashHandlerWin32.cpp index 5f21d89f..cf3988dc 100644 --- a/src/CommonLib/Utility/CrashHandlerWin32.cpp +++ b/src/CommonLib/Utility/CrashHandlerWin32.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include diff --git a/src/ServerLib/Scripting/ServerScriptingLibrary.cpp b/src/ServerLib/Scripting/ServerScriptingLibrary.cpp index ae4e741e..0d7082b5 100644 --- a/src/ServerLib/Scripting/ServerScriptingLibrary.cpp +++ b/src/ServerLib/Scripting/ServerScriptingLibrary.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -137,7 +138,8 @@ namespace tsom // TODO: Build properties from Lua table return std::make_unique(m_serverInstance, databaseId, std::move(generatorName), chunkCount, type, std::vector{}); }), - "GetDatabaseId", LuaFunction(&ServerPlanetEnvironment::GetDatabaseId) + "GetDatabaseId", LuaFunction(&ServerPlanetEnvironment::GetDatabaseId), + "GetChunkContainer", LuaFunction([](ServerPlanetEnvironment& env) -> ChunkContainer* { return &env.GetPlanet(); }) ); state.new_usertype("ShipEnvironment",