diff --git a/doc/en/scripting/builtins/libplayer.md b/doc/en/scripting/builtins/libplayer.md index 99482c68..f71b8c95 100644 --- a/doc/en/scripting/builtins/libplayer.md +++ b/doc/en/scripting/builtins/libplayer.md @@ -77,6 +77,13 @@ player.get_spawnpoint(playerid: int) -> number, number, number Spawn point setter and getter +```lua +player.set_name(playerid: int, name: str) +player.get_name(playerid: int) -> str +``` + +Player name setter and getter + ```lua player.get_selected_block(playerid: int) -> x,y,z ``` diff --git a/doc/en/scripting/builtins/libworld.md b/doc/en/scripting/builtins/libworld.md index 349e9106..5bf01abb 100644 --- a/doc/en/scripting/builtins/libworld.md +++ b/doc/en/scripting/builtins/libworld.md @@ -10,6 +10,8 @@ world.get_list() -> tables array { name: str, -- world icon/preview (loading automatically) icon: str + -- engine version the world was saved on + version: {int, int} } -- Returns current day time in range \[0.0-1.0\] diff --git a/doc/en/scripting/events.md b/doc/en/scripting/events.md index 8dcd1a06..4b13ec7b 100644 --- a/doc/en/scripting/events.md +++ b/doc/en/scripting/events.md @@ -16,6 +16,12 @@ function on_broken(x, y, z, playerid) Called on block broken by player +```lua +function on_replaced(x, y, z, playerid) +``` + +Called on block replaced with other by player + ```lua function on_interact(x, y, z, playerid) -> bool ``` diff --git a/doc/ru/scripting/builtins/libplayer.md b/doc/ru/scripting/builtins/libplayer.md index 179e6b18..916fd4ab 100644 --- a/doc/ru/scripting/builtins/libplayer.md +++ b/doc/ru/scripting/builtins/libplayer.md @@ -77,6 +77,13 @@ player.get_spawnpoint(playerid: int) -> number, number, number Сеттер и геттер точки спавна игрока +```lua +player.set_name(playerid: int, name: str) +player.get_name(playerid: int) -> str +``` + +Сеттер и геттер имени игрока + ```lua player.get_selected_block(playerid: int) -> x,y,z ``` diff --git a/doc/ru/scripting/builtins/libworld.md b/doc/ru/scripting/builtins/libworld.md index b82f3b53..9d7d635e 100644 --- a/doc/ru/scripting/builtins/libworld.md +++ b/doc/ru/scripting/builtins/libworld.md @@ -9,7 +9,9 @@ world.get_list() -> массив таблиц { -- название мира name: str, -- предпросмотр (автоматически загружаемая текстура) - icon: str + icon: str, + -- версия движка, на которой был сохранен мир + version: {int, int} } -- Возвращает текущее игровое время от 0.0 до 1.0, где 0.0 и 1.0 - полночь, 0.5 - полдень. diff --git a/doc/ru/scripting/events.md b/doc/ru/scripting/events.md index 6d908459..08192cfa 100644 --- a/doc/ru/scripting/events.md +++ b/doc/ru/scripting/events.md @@ -16,6 +16,12 @@ function on_broken(x, y, z, playerid) Вызывается после разрушения блока игроком +```lua +function on_replaced(x, y, z, playerid) +``` + +Вызывается после замены блока игроком + ```lua function on_interact(x, y, z, playerid) -> bool ``` diff --git a/res/layouts/pages/worlds.xml.lua b/res/layouts/pages/worlds.xml.lua index 37c348a9..1cbcd53d 100644 --- a/res/layouts/pages/worlds.xml.lua +++ b/res/layouts/pages/worlds.xml.lua @@ -1,6 +1,13 @@ function on_open() local worlds = world.get_list() for _, info in ipairs(worlds) do + local major, minor = core.get_version() + if info.version[1] > major or info.version[2] > minor then + info.versionColor = "#A02010" + else + info.versionColor = "#808080" + end + info.versionString = string.format("%s.%s", unpack(info.version)) document.worlds:add(gui.template("world", info)) end end diff --git a/res/layouts/templates/world.xml b/res/layouts/templates/world.xml index c20223fc..a258604b 100644 --- a/res/layouts/templates/world.xml +++ b/res/layouts/templates/world.xml @@ -14,4 +14,5 @@ onclick='core.delete_world("%{name}")'> + diff --git a/res/presets/text3d/player_name.toml b/res/presets/text3d/player_name.toml new file mode 100644 index 00000000..7942ff48 --- /dev/null +++ b/res/presets/text3d/player_name.toml @@ -0,0 +1,3 @@ +display = "projected" +xray_opacity = 0.3 +render_distance = 128 diff --git a/src/content/ContentPack.hpp b/src/content/ContentPack.hpp index b790d0e7..857f4fe0 100644 --- a/src/content/ContentPack.hpp +++ b/src/content/ContentPack.hpp @@ -97,6 +97,7 @@ struct ContentPackStats { struct world_funcs_set { bool onblockplaced : 1; + bool onblockreplaced : 1; bool onblockbroken : 1; bool onblockinteract : 1; bool onplayertick : 1; diff --git a/src/engine.hpp b/src/engine.hpp index 891606a5..04a221df 100644 --- a/src/engine.hpp +++ b/src/engine.hpp @@ -18,7 +18,6 @@ #include #include -class Level; class Screen; class EnginePaths; class ResPaths; diff --git a/src/frontend/screens/LevelScreen.cpp b/src/frontend/screens/LevelScreen.cpp index 1acd9200..34d1db67 100644 --- a/src/frontend/screens/LevelScreen.cpp +++ b/src/frontend/screens/LevelScreen.cpp @@ -50,7 +50,7 @@ LevelScreen::LevelScreen(Engine* engine, std::unique_ptr levelPtr) hud = std::make_unique(engine, *frontend, controller->getPlayer()); decorator = std::make_unique( - *controller, *worldRenderer->particles, assets + *engine, *controller, *worldRenderer, assets ); keepAlive(settings.graphics.backlight.observe([=](bool) { diff --git a/src/graphics/render/ChunksRenderer.cpp b/src/graphics/render/ChunksRenderer.cpp index 78ed45bf..9a72916c 100644 --- a/src/graphics/render/ChunksRenderer.cpp +++ b/src/graphics/render/ChunksRenderer.cpp @@ -251,14 +251,16 @@ void ChunksRenderer::drawSortedMeshes(const Camera& camera, Shader& shader) { continue; } - glm::vec3 min(chunk->x * CHUNK_W, chunk->bottom, chunk->z * CHUNK_D); - glm::vec3 max( - chunk->x * CHUNK_W + CHUNK_W, - chunk->top, - chunk->z * CHUNK_D + CHUNK_D - ); + if (culling) { + glm::vec3 min(chunk->x * CHUNK_W, chunk->bottom, chunk->z * CHUNK_D); + glm::vec3 max( + chunk->x * CHUNK_W + CHUNK_W, + chunk->top, + chunk->z * CHUNK_D + CHUNK_D + ); - if (!frustum.isBoxVisible(min, max)) continue; + if (!frustum.isBoxVisible(min, max)) continue; + } auto& chunkEntries = found->second.sortingMeshData.entries; diff --git a/src/graphics/render/Decorator.cpp b/src/graphics/render/Decorator.cpp index 4bac77af..b7bd4218 100644 --- a/src/graphics/render/Decorator.cpp +++ b/src/graphics/render/Decorator.cpp @@ -1,13 +1,22 @@ #include "Decorator.hpp" #include "ParticlesRenderer.hpp" +#include "WorldRenderer.hpp" +#include "TextsRenderer.hpp" +#include "TextNote.hpp" #include "assets/assets_util.hpp" #include "content/Content.hpp" #include "voxels/Chunks.hpp" #include "voxels/Block.hpp" #include "world/Level.hpp" #include "window/Camera.hpp" +#include "objects/Players.hpp" #include "logic/LevelController.hpp" +#include "util/stringutil.hpp" +#include "engine.hpp" +#include "files/files.hpp" + +namespace fs = std::filesystem; /// @brief Not greather than 64 for this BIG_PRIME value inline constexpr int UPDATE_AREA_DIAMETER = 32; @@ -20,15 +29,32 @@ inline constexpr int ITERATIONS = 512; inline constexpr int BIG_PRIME = 666667; Decorator::Decorator( - LevelController& controller, ParticlesRenderer& particles, const Assets& assets + Engine& engine, LevelController& controller, WorldRenderer& renderer, const Assets& assets ) - : level(*controller.getLevel()), particles(particles), assets(assets) { + : engine(engine), + level(*controller.getLevel()), + renderer(renderer), + assets(assets), + player(*controller.getPlayer()) { controller.getBlocksController()->listenBlockInteraction( [this](auto player, const auto& pos, const auto& def, BlockInteraction type) { if (type == BlockInteraction::placing && def.particles) { addParticles(def, pos); } }); + for (const auto& [id, player] : *level.players) { + if (id == controller.getPlayer()->getId()) { + continue; + } + playerTexts[id] = renderer.texts->add(std::make_unique( + util::str2wstr_utf8(player->getName()), + playerNamePreset, + player->getPosition() + )); + } + playerNamePreset.deserialize(engine.getResPaths()->readCombinedObject( + "presets/text3d/player_name.toml" + )); } void Decorator::addParticles(const Block& def, const glm::ivec3& pos) { @@ -37,7 +63,7 @@ void Decorator::addParticles(const Block& def, const glm::ivec3& pos) { auto treg = util::get_texture_region( assets, def.particles->texture, "" ); - blockEmitters[pos] = particles.add(std::make_unique( + blockEmitters[pos] = renderer.particles->add(std::make_unique( level, glm::vec3{pos.x + 0.5, pos.y + 0.5, pos.z + 0.5}, *def.particles, @@ -81,7 +107,7 @@ void Decorator::update(float delta, const Camera& camera) { const auto& indices = *level.content->getIndices(); auto iter = blockEmitters.begin(); while (iter != blockEmitters.end()) { - auto emitter = particles.getEmitter(iter->second); + auto emitter = renderer.particles->getEmitter(iter->second); if (emitter == nullptr) { iter = blockEmitters.erase(iter); continue; @@ -108,4 +134,28 @@ void Decorator::update(float delta, const Camera& camera) { } iter++; } + + for (const auto& [id, player] : *level.players) { + if (id == this->player.getId() || + playerTexts.find(id) != playerTexts.end()) { + continue; + } + playerTexts[id] = renderer.texts->add(std::make_unique( + util::str2wstr_utf8(player->getName()), + playerNamePreset, + player->getPosition() + )); + } + + auto textsIter = playerTexts.begin(); + while (textsIter != playerTexts.end()) { + auto note = renderer.texts->get(textsIter->second); + auto player = level.players->get(textsIter->first); + if (player == nullptr) { + textsIter = playerTexts.erase(textsIter); + } else { + note->setPosition(player->getPosition() + glm::vec3(0, 1, 0)); + ++textsIter; + } + } } diff --git a/src/graphics/render/Decorator.hpp b/src/graphics/render/Decorator.hpp index 32f9540a..fbc196aa 100644 --- a/src/graphics/render/Decorator.hpp +++ b/src/graphics/render/Decorator.hpp @@ -6,20 +6,29 @@ #include +#include "typedefs.hpp" +#include "presets/NotePreset.hpp" + class Level; class Chunks; class Camera; class Assets; +class Player; struct Block; +class Engine; class LevelController; -class ParticlesRenderer; +class WorldRenderer; class Decorator { + Engine& engine; const Level& level; const Assets& assets; - ParticlesRenderer& particles; + Player& player; + WorldRenderer& renderer; std::unordered_map blockEmitters; + std::unordered_map playerTexts; int currentIndex = 0; + NotePreset playerNamePreset {}; void update( float delta, const glm::ivec3& areaStart, const glm::ivec3& areaCenter @@ -27,7 +36,10 @@ class Decorator { void addParticles(const Block& def, const glm::ivec3& pos); public: Decorator( - LevelController& level, ParticlesRenderer& particles, const Assets& assets + Engine& engine, + LevelController& level, + WorldRenderer& renderer, + const Assets& assets ); void update(float delta, const Camera& camera); diff --git a/src/graphics/render/TextsRenderer.cpp b/src/graphics/render/TextsRenderer.cpp index 92b628f8..676ca349 100644 --- a/src/graphics/render/TextsRenderer.cpp +++ b/src/graphics/render/TextsRenderer.cpp @@ -61,6 +61,10 @@ void TextsRenderer::renderNote( if (preset.displayMode == NoteDisplayMode::XY_FREE_BILLBOARD) { yvec = camera.up; } + float scale = + (1.0f - preset.perspective) * glm::pow(glm::distance(camera.position, pos), 1.0f-preset.perspective); + xvec *= 1.0f + scale; + yvec *= 1.0f + scale; } if (preset.displayMode != NoteDisplayMode::PROJECTED) { if (!frustum.isBoxVisible(pos - xvec * (width * 0.5f), diff --git a/src/interfaces/Object.hpp b/src/interfaces/Object.hpp deleted file mode 100644 index bb21fedb..00000000 --- a/src/interfaces/Object.hpp +++ /dev/null @@ -1,23 +0,0 @@ -#pragma once - -#include -#include -#include - -class Level; - -class Object { -private: - -public: - uint64_t objectUID; - bool shouldUpdate = true; - -public: - ~Object() { destroyed(); } - -public: - virtual void spawned() { } - virtual void update(float delta) { } - virtual void destroyed() { } -}; diff --git a/src/logic/BlocksController.cpp b/src/logic/BlocksController.cpp index 01679546..980c8bfe 100644 --- a/src/logic/BlocksController.cpp +++ b/src/logic/BlocksController.cpp @@ -75,6 +75,13 @@ void BlocksController::breakBlock( void BlocksController::placeBlock( Player* player, const Block& def, blockstate state, int x, int y, int z ) { + auto voxel = chunks.get(x, y, z); + if (voxel == nullptr) { + return; + } + const auto& prevDef = level.content->getIndices()->blocks.require(voxel->id); + scripting::on_block_replaced(player, prevDef, {x, y, z}); + onBlockInteraction( player, glm::ivec3(x, y, z), def, BlockInteraction::placing ); diff --git a/src/logic/LevelController.cpp b/src/logic/LevelController.cpp index bd8af15c..5d2ddd9b 100644 --- a/src/logic/LevelController.cpp +++ b/src/logic/LevelController.cpp @@ -5,7 +5,6 @@ #include "debug/Logger.hpp" #include "engine.hpp" #include "files/WorldFiles.hpp" -#include "interfaces/Object.hpp" #include "objects/Entities.hpp" #include "physics/Hitbox.hpp" #include "settings.hpp" @@ -26,7 +25,7 @@ LevelController::LevelController(Engine* engine, std::unique_ptr levelPtr *level, settings.chunks.padding.get() )), player(std::make_unique( - settings, this->level.get(), blocks.get() + settings, level.get(), blocks.get() )) { scripting::on_world_load(this); } @@ -45,11 +44,6 @@ void LevelController::update(float delta, bool input, bool pause) { if (!pause) { // update all objects that needed - for (const auto& obj : level->objects) { - if (obj && obj->shouldUpdate) { - obj->update(delta); - } - } blocks->update(delta); player->update(delta, input, pause); level->entities->updatePhysics(delta); @@ -57,17 +51,6 @@ void LevelController::update(float delta, bool input, bool pause) { } level->entities->clean(); player->postUpdate(delta, input, pause); - - // erease null pointers - auto& objects = level->objects; - objects.erase( - std::remove_if( - objects.begin(), - objects.end(), - [](auto obj) { return obj == nullptr; } - ), - objects.end() - ); } void LevelController::saveWorld() { diff --git a/src/logic/PlayerController.cpp b/src/logic/PlayerController.cpp index 0ad5ef6b..0b23942e 100644 --- a/src/logic/PlayerController.cpp +++ b/src/logic/PlayerController.cpp @@ -13,6 +13,7 @@ #include "lighting/Lighting.hpp" #include "objects/Entities.hpp" #include "objects/Player.hpp" +#include "objects/Players.hpp" #include "physics/Hitbox.hpp" #include "physics/PhysicsSolver.hpp" #include "settings.hpp" @@ -40,7 +41,7 @@ const float C_ZOOM = 0.1f; const float CROUCH_SHIFT_Y = -0.2f; CameraControl::CameraControl( - const std::shared_ptr& player, const CameraSettings& settings + Player* player, const CameraSettings& settings ) : player(player), camera(player->fpCamera), @@ -193,7 +194,7 @@ PlayerController::PlayerController( BlocksController* blocksController ) : settings(settings), level(level), - player(level->getObject(0)), + player(level->players->get(0)), camControl(player, settings.camera), blocksController(blocksController), playerTickClock(20, 3) { @@ -215,7 +216,7 @@ void PlayerController::onFootstep(const Hitbox& hitbox) { continue; } blocksController->onBlockInteraction( - player.get(), + player, glm::ivec3(x, y, z), def, BlockInteraction::step @@ -254,7 +255,7 @@ void PlayerController::update(float delta, bool input, bool pause) { if (playerTickClock.update(delta)) { if (player->getId() % playerTickClock.getParts() == playerTickClock.getPart()) { - scripting::on_player_tick(player.get(), playerTickClock.getTickRate()); + scripting::on_player_tick(player, playerTickClock.getTickRate()); } } } @@ -390,12 +391,12 @@ voxel* PlayerController::updateSelection(float maxDistance) { if (selection.entity != prevEntity) { if (prevEntity != ENTITY_NONE) { if (auto pentity = level->entities->get(prevEntity)) { - scripting::on_aim_off(*pentity, player.get()); + scripting::on_aim_off(*pentity, player); } } if (selection.entity != ENTITY_NONE) { if (auto pentity = level->entities->get(selection.entity)) { - scripting::on_aim_on(*pentity, player.get()); + scripting::on_aim_on(*pentity, player); } } } @@ -432,8 +433,8 @@ void PlayerController::processRightClick(const Block& def, const Block& target) if (!input.shift && target.rt.funcsset.oninteract) { if (scripting::on_block_interact( - player.get(), target, selection.actualPosition - )) { + player, target, selection.actualPosition + )) { return; } } @@ -474,7 +475,7 @@ void PlayerController::processRightClick(const Block& def, const Block& target) slot.setCount(slot.getCount()-1); } blocksController->placeBlock( - player.get(), def, state, coord.x, coord.y, coord.z + player, def, state, coord.x, coord.y, coord.z ); } } @@ -488,10 +489,10 @@ void PlayerController::updateEntityInteraction( } auto entity = *entityOpt; if (lclick) { - scripting::on_attacked(entity, player.get(), player->getEntity()); + scripting::on_attacked(entity, player, player->getEntity()); } if (rclick) { - scripting::on_entity_used(entity, player.get()); + scripting::on_entity_used(entity, player); } } @@ -523,7 +524,7 @@ void PlayerController::updateInteraction(float delta) { auto vox = updateSelection(maxDistance); if (vox == nullptr) { if (rclick && item.rt.funcsset.on_use) { - scripting::on_item_use(player.get(), item); + scripting::on_item_use(player, item); } if (selection.entity) { updateEntityInteraction(selection.entity, lattack, rclick); @@ -534,7 +535,7 @@ void PlayerController::updateInteraction(float delta) { auto iend = selection.position; if (lclick && !input.shift && item.rt.funcsset.on_block_break_by) { if (scripting::on_item_break_block( - player.get(), item, iend.x, iend.y, iend.z + player, item, iend.x, iend.y, iend.z )) { return; } @@ -543,7 +544,7 @@ void PlayerController::updateInteraction(float delta) { if (lclick) { if (player->isInstantDestruction() && target.breakable) { blocksController->breakBlock( - player.get(), target, iend.x, iend.y, iend.z + player, target, iend.x, iend.y, iend.z ); } } @@ -551,10 +552,10 @@ void PlayerController::updateInteraction(float delta) { bool preventDefault = false; if (item.rt.funcsset.on_use_on_block) { preventDefault = scripting::on_item_use_on_block( - player.get(), item, iend, selection.normal + player, item, iend, selection.normal ); } else if (item.rt.funcsset.on_use) { - preventDefault = scripting::on_item_use(player.get(), item); + preventDefault = scripting::on_item_use(player, item); } if (preventDefault) { return; @@ -566,10 +567,10 @@ void PlayerController::updateInteraction(float delta) { } if (Events::jactive(BIND_PLAYER_PICK)) { auto coord = selection.actualPosition; - pick_block(indices, chunks, player.get(), coord.x, coord.y, coord.z); + pick_block(indices, chunks, player, coord.x, coord.y, coord.z); } } Player* PlayerController::getPlayer() { - return player.get(); + return player; } diff --git a/src/logic/PlayerController.hpp b/src/logic/PlayerController.hpp index 3fe3decf..8d82a3f9 100644 --- a/src/logic/PlayerController.hpp +++ b/src/logic/PlayerController.hpp @@ -18,7 +18,7 @@ struct CameraSettings; struct EngineSettings; class CameraControl { - std::shared_ptr player; + Player* player; std::shared_ptr camera; const CameraSettings& settings; glm::vec3 offset; @@ -40,7 +40,7 @@ class CameraControl { void switchCamera(); public: CameraControl( - const std::shared_ptr& player, const CameraSettings& settings + Player* player, const CameraSettings& settings ); void updateMouse(PlayerInput& input); void update(PlayerInput input, float delta, Chunks* chunks); @@ -50,7 +50,7 @@ public: class PlayerController { const EngineSettings& settings; Level* level; - std::shared_ptr player; + Player* player; PlayerInput input {}; CameraControl camControl; BlocksController* blocksController; diff --git a/src/logic/scripting/lua/libs/libblock.cpp b/src/logic/scripting/lua/libs/libblock.cpp index 20d3253d..2f24e0fe 100644 --- a/src/logic/scripting/lua/libs/libblock.cpp +++ b/src/logic/scripting/lua/libs/libblock.cpp @@ -2,6 +2,7 @@ #include "lighting/Lighting.hpp" #include "logic/BlocksController.hpp" #include "logic/LevelController.hpp" +#include "objects/Players.hpp" #include "voxels/Block.hpp" #include "voxels/Chunk.hpp" #include "voxels/Chunks.hpp" @@ -350,9 +351,9 @@ static int l_place(lua::State* L) { "there is no block with index " + std::to_string(id) ); } - auto player = level->getObject(playerid); + auto player = level->players->get(playerid); controller->getBlocksController()->placeBlock( - player ? player.get() : nullptr, *def, int2blockstate(state), x, y, z + player, *def, int2blockstate(state), x, y, z ); return 0; } @@ -367,10 +368,8 @@ static int l_destruct(lua::State* L) { return 0; } auto& def = level->content->getIndices()->blocks.require(voxel->id); - auto player = level->getObject(playerid); - controller->getBlocksController()->breakBlock( - player ? player.get() : nullptr, def, x, y, z - ); + auto player = level->players->get(playerid); + controller->getBlocksController()->breakBlock(player, def, x, y, z); return 0; } diff --git a/src/logic/scripting/lua/libs/libcore.cpp b/src/logic/scripting/lua/libs/libcore.cpp index 5a100b9c..c656d0e6 100644 --- a/src/logic/scripting/lua/libs/libcore.cpp +++ b/src/logic/scripting/lua/libs/libcore.cpp @@ -20,6 +20,12 @@ using namespace scripting; +static int l_get_version(lua::State* L) { + return lua::pushvec_stack( + L, glm::vec2(ENGINE_VERSION_MAJOR, ENGINE_VERSION_MINOR) + ); +} + /// @brief Creating new world /// @param name Name world /// @param seed Seed world @@ -239,6 +245,7 @@ static int l_quit(lua::State*) { } const luaL_Reg corelib[] = { + {"get_version", lua::wrap}, {"new_world", lua::wrap}, {"open_world", lua::wrap}, {"reopen_world", lua::wrap}, diff --git a/src/logic/scripting/lua/libs/libplayer.cpp b/src/logic/scripting/lua/libs/libplayer.cpp index 18b29405..0628c5af 100644 --- a/src/logic/scripting/lua/libs/libplayer.cpp +++ b/src/logic/scripting/lua/libs/libplayer.cpp @@ -4,6 +4,7 @@ #include "items/Inventory.hpp" #include "objects/Entities.hpp" #include "objects/Player.hpp" +#include "objects/Players.hpp" #include "physics/Hitbox.hpp" #include "window/Camera.hpp" #include "world/Level.hpp" @@ -11,8 +12,8 @@ using namespace scripting; -inline std::shared_ptr get_player(lua::State* L, int idx) { - return level->getObject(lua::tointeger(L, idx)); +inline Player* get_player(lua::State* L, int idx) { + return level->players->get(lua::tointeger(L, idx)); } static int l_get_pos(lua::State* L) { @@ -235,6 +236,20 @@ static int l_set_camera(lua::State* L) { return 0; } +static int l_get_name(lua::State* L) { + if (auto player = get_player(L, 1)) { + return lua::pushstring(L, player->getName()); + } + return 0; +} + +static int l_set_name(lua::State* L) { + if (auto player = get_player(L, 1)) { + player->setName(lua::require_string(L, 2)); + } + return 0; +} + const luaL_Reg playerlib[] = { {"get_pos", lua::wrap}, {"set_pos", lua::wrap}, @@ -260,5 +275,7 @@ const luaL_Reg playerlib[] = { {"set_entity", lua::wrap}, {"get_camera", lua::wrap}, {"set_camera", lua::wrap}, + {"get_name", lua::wrap}, + {"set_name", lua::wrap}, {NULL, NULL} }; diff --git a/src/logic/scripting/lua/libs/libworld.cpp b/src/logic/scripting/lua/libs/libworld.cpp index 06da877c..5422c873 100644 --- a/src/logic/scripting/lua/libs/libworld.cpp +++ b/src/logic/scripting/lua/libs/libworld.cpp @@ -4,7 +4,9 @@ #include "assets/Assets.hpp" #include "assets/AssetsLoader.hpp" +#include "coders/json.hpp" #include "engine.hpp" +#include "files/files.hpp" #include "files/engine_paths.hpp" #include "world/Level.hpp" #include "world/World.hpp" @@ -32,22 +34,34 @@ static int l_get_list(lua::State* L) { for (size_t i = 0; i < worlds.size(); i++) { lua::createtable(L, 0, 1); - auto name = worlds[i].filename().u8string(); + const auto& folder = worlds[i]; + + auto root = json::parse(files::read_string(folder/fs::u8path("world.json"))); + const auto& versionMap = root["version"]; + int versionMajor = versionMap["major"].asInteger(); + int versionMinor = versionMap["minor"].asInteger(); + + + auto name = folder.filename().u8string(); lua::pushstring(L, name); lua::setfield(L, "name"); auto assets = engine->getAssets(); std::string icon = "world#" + name + ".icon"; if (!AssetsLoader::loadExternalTexture( - assets, - icon, - {worlds[i] / fs::path("icon.png"), - worlds[i] / fs::path("preview.png")} - )) { + assets, + icon, + {worlds[i] / fs::path("icon.png"), + worlds[i] / fs::path("preview.png")} + )) { icon = "gui/no_world_icon"; } lua::pushstring(L, icon); lua::setfield(L, "icon"); + + lua::pushvec2(L, {versionMajor, versionMinor}); + lua::setfield(L, "version"); + lua::rawseti(L, i + 1); } return 1; diff --git a/src/logic/scripting/scripting.cpp b/src/logic/scripting/scripting.cpp index 0778fd45..055ecf34 100644 --- a/src/logic/scripting/scripting.cpp +++ b/src/logic/scripting/scripting.cpp @@ -284,6 +284,32 @@ void scripting::on_block_placed( } } +void scripting::on_block_replaced( + Player* player, const Block& block, const glm::ivec3& pos +) { + if (block.rt.funcsset.onreplaced) { + std::string name = block.name + ".replaced"; + lua::emit_event(lua::get_main_state(), name, [pos, player](auto L) { + lua::pushivec_stack(L, pos); + lua::pushinteger(L, player ? player->getId() : -1); + return 4; + }); + } + auto args = [&](lua::State* L) { + lua::pushinteger(L, block.rt.id); + lua::pushivec_stack(L, pos); + lua::pushinteger(L, player ? player->getId() : -1); + return 5; + }; + for (auto& [packid, pack] : content->getPacks()) { + if (pack->worldfuncsset.onblockreplaced) { + lua::emit_event( + lua::get_main_state(), packid + ":.blockreplaced", args + ); + } + } +} + void scripting::on_block_broken( Player* player, const Block& block, const glm::ivec3& pos ) { @@ -701,6 +727,8 @@ void scripting::load_content_script( register_event(env, "on_random_update", prefix + ".randupdate"); funcsset.onbroken = register_event(env, "on_broken", prefix + ".broken"); funcsset.onplaced = register_event(env, "on_placed", prefix + ".placed"); + funcsset.onreplaced = + register_event(env, "on_replaced", prefix + ".replaced"); funcsset.oninteract = register_event(env, "on_interact", prefix + ".interact"); funcsset.onblockstick = @@ -752,6 +780,8 @@ void scripting::load_world_script( register_event(env, "on_block_placed", prefix + ":.blockplaced"); funcsset.onblockbroken = register_event(env, "on_block_broken", prefix + ":.blockbroken"); + funcsset.onblockreplaced = + register_event(env, "on_block_replaced", prefix + ":.blockreplaced"); funcsset.onblockinteract = register_event(env, "on_block_interact", prefix + ":.blockinteract"); funcsset.onplayertick = diff --git a/src/logic/scripting/scripting.hpp b/src/logic/scripting/scripting.hpp index 3cd8a022..0ab3c035 100644 --- a/src/logic/scripting/scripting.hpp +++ b/src/logic/scripting/scripting.hpp @@ -71,6 +71,9 @@ namespace scripting { void on_block_placed( Player* player, const Block& block, const glm::ivec3& pos ); + void on_block_replaced( + Player* player, const Block& block, const glm::ivec3& pos + ); void on_block_broken( Player* player, const Block& block, const glm::ivec3& pos ); diff --git a/src/objects/Player.cpp b/src/objects/Player.cpp index 5c2a2b85..ae8c0139 100644 --- a/src/objects/Player.cpp +++ b/src/objects/Player.cpp @@ -29,12 +29,16 @@ constexpr int SPAWN_ATTEMPTS_PER_UPDATE = 64; Player::Player( Level* level, + int64_t id, + const std::string& name, glm::vec3 position, float speed, std::shared_ptr inv, entityid_t eid ) : level(level), + id(id), + name(name), speed(speed), chosenSlot(0), position(position), @@ -49,8 +53,7 @@ Player::Player( tpCamera->setFov(glm::radians(90.0f)); } -Player::~Player() { -} +Player::~Player() = default; void Player::updateEntity() { if (eid == 0) { @@ -268,6 +271,14 @@ entityid_t Player::getSelectedEntity() const { return selectedEid; } +void Player::setName(const std::string& name) { + this->name = name; +} + +const std::string& Player::getName() const { + return name; +} + const std::shared_ptr& Player::getInventory() const { return inventory; } @@ -283,6 +294,9 @@ glm::vec3 Player::getSpawnPoint() const { dv::value Player::serialize() const { auto root = dv::object(); + root["id"] = id; + root["name"] = name; + root["position"] = dv::to_value(position); root["rotation"] = dv::to_value(cam); root["spawnpoint"] = dv::to_value(spawnpoint); @@ -304,6 +318,9 @@ dv::value Player::serialize() const { } void Player::deserialize(const dv::value& src) { + src.at("id").get(id); + src.at("name").get(name); + const auto& posarr = src["position"]; dv::get_vec(posarr, position); diff --git a/src/objects/Player.hpp b/src/objects/Player.hpp index 2badd9dc..920e06e4 100644 --- a/src/objects/Player.hpp +++ b/src/objects/Player.hpp @@ -4,7 +4,6 @@ #include #include -#include "interfaces/Object.hpp" #include "interfaces/Serializable.hpp" #include "settings.hpp" #include "voxels/voxel.hpp" @@ -40,8 +39,10 @@ struct CursorSelection { entityid_t entity = ENTITY_NONE; }; -class Player : public Object, public Serializable { +class Player : public Serializable { Level* level; + int64_t id; + std::string name; float speed; int chosenSlot; glm::vec3 position; @@ -52,7 +53,7 @@ class Player : public Object, public Serializable { bool infiniteItems = true; bool instantDestruction = true; entityid_t eid; - entityid_t selectedEid; + entityid_t selectedEid = 0; public: std::shared_ptr fpCamera, spCamera, tpCamera; std::shared_ptr currentCamera; @@ -62,6 +63,8 @@ public: Player( Level* level, + int64_t id, + const std::string& name, glm::vec3 position, float speed, std::shared_ptr inv, @@ -99,9 +102,12 @@ public: entityid_t getSelectedEntity() const; + void setName(const std::string& name); + const std::string& getName() const; + const std::shared_ptr& getInventory() const; - glm::vec3 getPosition() const { + const glm::vec3& getPosition() const { return position; } @@ -115,7 +121,7 @@ public: static void convert(dv::value& data, const ContentReport* report); - inline int getId() const { - return objectUID; + inline u64id_t getId() const { + return id; } }; diff --git a/src/objects/Players.cpp b/src/objects/Players.cpp new file mode 100644 index 00000000..90031991 --- /dev/null +++ b/src/objects/Players.cpp @@ -0,0 +1,73 @@ +#include "Players.hpp" + +#include "Player.hpp" +#include "items/Inventories.hpp" +#include "world/Level.hpp" +#include "world/World.hpp" + +Players::Players(Level* level) : level(level) {} + +void Players::addPlayer(std::unique_ptr player) { + players[player->getId()] = std::move(player); +} + +Player* Players::get(int64_t id) const { + const auto& found = players.find(id); + if (found == players.end()) { + return nullptr; + } + return found->second.get(); +} + +Player* Players::create() { + auto playerPtr = std::make_unique( + level, + level->getWorld()->getInfo().nextPlayerId++, + "", + glm::vec3(0, DEF_PLAYER_Y, 0), + DEF_PLAYER_SPEED, + level->inventories->create(DEF_PLAYER_INVENTORY_SIZE), + 0 + ); + auto player = playerPtr.get(); + addPlayer(std::move(playerPtr)); + + level->inventories->store(player->getInventory()); + return player; +} + +dv::value Players::serialize() const { + auto root = dv::object(); + auto& list = root.list("players"); + + for (const auto& [id, player] : players) { + list.add(player->serialize()); + } + return root; +} + +void Players::deserialize(const dv::value& src) { + players.clear(); + + const auto& players = src["players"]; + for (auto& playerMap : players) { + auto playerPtr = std::make_unique( + level, + 0, + "", + glm::vec3(0, DEF_PLAYER_Y, 0), + DEF_PLAYER_SPEED, + level->inventories->create(DEF_PLAYER_INVENTORY_SIZE), + 0 + ); + auto player = playerPtr.get(); + player->deserialize(playerMap); + addPlayer(std::move(playerPtr)); + auto& inventory = player->getInventory(); + // invalid inventory id pre 0.25 + if (inventory->getId() == 0) { + inventory->setId(level->getWorld()->getNextInventoryId()); + } + level->inventories->store(player->getInventory()); + } +} diff --git a/src/objects/Players.hpp b/src/objects/Players.hpp new file mode 100644 index 00000000..f66d181f --- /dev/null +++ b/src/objects/Players.hpp @@ -0,0 +1,39 @@ +#pragma once + +#include +#include + +#include "typedefs.hpp" +#include "interfaces/Serializable.hpp" + +inline constexpr float DEF_PLAYER_Y = 100.0f; +inline constexpr float DEF_PLAYER_SPEED = 4.0f; +inline constexpr int DEF_PLAYER_INVENTORY_SIZE = 40; + +class Level; +class Player; + +class Players : public Serializable { + Level* level; + std::unordered_map> players; + + void addPlayer(std::unique_ptr player); +public: + Players(Level* level); + + Player* get(int64_t id) const; + + Player* create(); + + dv::value serialize() const override; + + void deserialize(const dv::value& src) override; + + auto begin() const { + return players.begin(); + } + + auto end() const { + return players.end(); + } +}; diff --git a/src/voxels/Block.hpp b/src/voxels/Block.hpp index 4fe51b0d..95768c88 100644 --- a/src/voxels/Block.hpp +++ b/src/voxels/Block.hpp @@ -39,6 +39,7 @@ struct block_funcs_set { bool update : 1; bool onplaced : 1; bool onbroken : 1; + bool onreplaced : 1; bool oninteract : 1; bool randupdate : 1; bool onblockstick : 1; diff --git a/src/world/Level.cpp b/src/world/Level.cpp index 9c58d0dc..ed370d08 100644 --- a/src/world/Level.cpp +++ b/src/world/Level.cpp @@ -7,6 +7,7 @@ #include "lighting/Lighting.hpp" #include "objects/Entities.hpp" #include "objects/Player.hpp" +#include "objects/Players.hpp" #include "physics/Hitbox.hpp" #include "physics/PhysicsSolver.hpp" #include "settings.hpp" @@ -28,8 +29,9 @@ 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)), settings(settings) { - auto& worldInfo = world->getInfo(); + const auto& worldInfo = world->getInfo(); auto& cameraIndices = content->getIndices(ResourceType::CAMERA); for (size_t i = 0; i < cameraIndices.size(); i++) { auto camera = std::make_shared(); @@ -51,12 +53,6 @@ Level::Level( if (worldInfo.nextEntityId) { entities->setNextID(worldInfo.nextEntityId); } - auto inv = std::make_shared( - world->getNextInventoryId(), DEF_PLAYER_INVENTORY_SIZE - ); - auto player = spawnObject( - this, glm::vec3(0, DEF_PLAYER_Y, 0), DEF_PLAYER_SPEED, inv, 0 - ); uint matrixSize = (settings.chunks.loadDistance.get() + settings.chunks.padding.get()) * @@ -71,14 +67,9 @@ Level::Level( }); inventories = std::make_unique(*this); - inventories->store(player->getInventory()); } -Level::~Level() { - for (auto obj : objects) { - obj.reset(); - } -} +Level::~Level() = default; void Level::loadMatrix(int32_t x, int32_t z, uint32_t radius) { chunks->setCenter(x, z); diff --git a/src/world/Level.hpp b/src/world/Level.hpp index 767844cf..1f3ef183 100644 --- a/src/world/Level.hpp +++ b/src/world/Level.hpp @@ -2,12 +2,10 @@ #include #include +#include +#include -#include "interfaces/Object.hpp" - -inline constexpr float DEF_PLAYER_Y = 100.0f; -inline constexpr float DEF_PLAYER_SPEED = 4.0f; -inline constexpr int DEF_PLAYER_INVENTORY_SIZE = 40; +#include "typedefs.hpp" class Content; class World; @@ -19,6 +17,7 @@ class Lighting; class PhysicsSolver; class ChunksStorage; class Camera; +class Players; struct EngineSettings; /// @brief A level, contains chunks and objects @@ -26,7 +25,7 @@ class Level { std::unique_ptr world; public: const Content* const content; - std::vector> objects; + std::unique_ptr chunks; std::unique_ptr chunksStorage; std::unique_ptr inventories; @@ -35,6 +34,7 @@ public: std::unique_ptr lighting; std::unique_ptr events; std::unique_ptr entities; + std::unique_ptr players; std::vector> cameras; // move somewhere? const EngineSettings& settings; @@ -52,36 +52,6 @@ public: const World* getWorld() const; - /// Spawns object of class T and returns pointer to it. - /// @param T class that derives the Object class - /// @param args pass arguments needed for T class constructor - template - std::shared_ptr spawnObject(Args&&... args) { - static_assert( - std::is_base_of::value, - "T must be a derived of Object class" - ); - std::shared_ptr tObj = std::make_shared(args...); - - std::shared_ptr obj = - std::dynamic_pointer_cast(tObj); - obj->objectUID = objects.size(); - objects.push_back(obj); - obj->spawned(); - return tObj; - } - - template - std::shared_ptr getObject(uint64_t id) const { - static_assert( - std::is_base_of::value, - "T must be a derived of Object class" - ); - if (id >= objects.size()) return nullptr; - std::shared_ptr object = std::dynamic_pointer_cast(objects[id]); - return object; - } - void onSave(); std::shared_ptr getCamera(const std::string& name); diff --git a/src/world/World.cpp b/src/world/World.cpp index 0f6ea373..4eb2631a 100644 --- a/src/world/World.cpp +++ b/src/world/World.cpp @@ -11,6 +11,7 @@ #include "items/Inventories.hpp" #include "objects/Entities.hpp" #include "objects/Player.hpp" +#include "objects/Players.hpp" #include "settings.hpp" #include "voxels/Chunk.hpp" #include "voxels/Chunks.hpp" @@ -69,13 +70,7 @@ void World::write(Level* level) { info.nextEntityId = level->entities->peekNextID(); wfile->write(this, content); - auto playerFile = dv::object(); - auto& players = playerFile.list("players"); - for (const auto& object : level->objects) { - if (auto player = std::dynamic_pointer_cast(object)) { - players.add(player->serialize()); - } - } + auto playerFile = level->players->serialize(); files::write_json(wfile->getPlayerFile(), playerFile); writeResources(content); @@ -100,7 +95,9 @@ std::unique_ptr World::create( content, packs ); - return std::make_unique(std::move(world), content, settings); + auto level = std::make_unique(std::move(world), content, settings); + level->players->create(); + return level; } std::unique_ptr World::load( @@ -121,45 +118,23 @@ std::unique_ptr World::load( << " generator: " << info->generator; auto world = std::make_unique( - info.value(), - std::move(worldFilesPtr), - content, - packs + info.value(), std::move(worldFilesPtr), content, packs ); auto& wfile = world->wfile; wfile->readResourcesData(content); auto level = std::make_unique(std::move(world), content, settings); - { - fs::path file = wfile->getPlayerFile(); - if (!fs::is_regular_file(file)) { - logger.warning() << "player.json does not exists"; - } else { - auto playerRoot = files::read_json(file); - if (playerRoot.has("players")) { - level->objects.clear(); - const auto& players = playerRoot["players"]; - for (auto& playerMap : players) { - auto player = level->spawnObject( - level.get(), - glm::vec3(0, DEF_PLAYER_Y, 0), - DEF_PLAYER_SPEED, - level->inventories->create(DEF_PLAYER_INVENTORY_SIZE), - 0 - ); - player->deserialize(playerMap); - auto& inventory = player->getInventory(); - // invalid inventory id pre 0.25 - if (inventory->getId() == 0) { - inventory->setId(level->getWorld()->getNextInventoryId()); - } - level->inventories->store(player->getInventory()); - } - } else { - auto player = level->getObject(0); - player->deserialize(playerRoot); - level->inventories->store(player->getInventory()); - } + + fs::path file = wfile->getPlayerFile(); + if (!fs::is_regular_file(file)) { + logger.warning() << "player.json does not exists"; + level->players->create(); + } else { + auto playerRoot = files::read_json(file); + level->players->deserialize(playerRoot); + + if (!playerRoot["players"][0].has("id")) { + level->getWorld()->getInfo().nextPlayerId++; } } return level; @@ -231,6 +206,7 @@ void WorldInfo::deserialize(const dv::value& root) { } nextInventoryId = root["next-inventory-id"].asInteger(2); nextEntityId = root["next-entity-id"].asInteger(1); + root.at("next-player-id").get(nextPlayerId); } dv::value WorldInfo::serialize() const { @@ -254,5 +230,6 @@ dv::value WorldInfo::serialize() const { root["next-inventory-id"] = nextInventoryId; root["next-entity-id"] = nextEntityId; + root["next-player-id"] = nextPlayerId; return root; } diff --git a/src/world/World.hpp b/src/world/World.hpp index 2f8e7fb3..24042c40 100644 --- a/src/world/World.hpp +++ b/src/world/World.hpp @@ -28,6 +28,7 @@ struct WorldInfo : public Serializable { std::string generator; uint64_t seed; int64_t nextInventoryId = 1; + int64_t nextPlayerId = 0; /// @brief Day/night loop timer in range 0..1 where /// 0.0 - is midnight and