commit
d48c00d001
@ -77,12 +77,14 @@ Checks if content is loaded.
|
||||
|
||||
```lua
|
||||
app.new_world(
|
||||
-- world name
|
||||
-- world name, empty string will create a nameless world
|
||||
name: str,
|
||||
-- generation seed
|
||||
seed: str,
|
||||
-- generator name
|
||||
generator: str
|
||||
-- local player id
|
||||
[optional] local_player: int=0
|
||||
)
|
||||
```
|
||||
|
||||
|
||||
@ -62,4 +62,7 @@ hud.is_paused() -> bool
|
||||
|
||||
-- Returns true if inventory is open or overlay is shown.
|
||||
hud.is_inventory_open() -> bool
|
||||
|
||||
-- Sets whether to allow pausing. If false, the pause menu will not pause the game.
|
||||
hud.set_allow_pause(flag: bool)
|
||||
```
|
||||
|
||||
@ -37,10 +37,10 @@ player.set_vel(playerid: int, x: number, y: number, z: number)
|
||||
Sets x, y, z player linear velocity
|
||||
|
||||
```lua
|
||||
player.get_rot(playerid: int) -> number, number, number
|
||||
player.get_rot(playerid: int, interpolated: bool) -> number, number, number
|
||||
```
|
||||
|
||||
Returns x, y, z of camera rotation (radians)
|
||||
Returns x, y, z of camera rotation (radians). Interpolation is relevant in cases where the rotation refresh rate is lower than the frame rate.
|
||||
|
||||
```lua
|
||||
player.set_rot(playerid: int, x: number, y: number, z: number)
|
||||
|
||||
@ -77,12 +77,14 @@ app.is_content_loaded() -> bool
|
||||
|
||||
```lua
|
||||
app.new_world(
|
||||
-- название мира
|
||||
-- название мира, пустая строка приведёт к созданию безымянного мира
|
||||
name: str,
|
||||
-- зерно генерации
|
||||
seed: str,
|
||||
-- название генератора
|
||||
generator: str
|
||||
-- id локального игрока
|
||||
[опционально] local_player: int=0
|
||||
)
|
||||
```
|
||||
|
||||
|
||||
@ -65,4 +65,7 @@ hud.is_paused() -> bool
|
||||
|
||||
-- Возвращает true если открыт инвентарь или оверлей.
|
||||
hud.is_inventory_open() -> bool
|
||||
|
||||
-- Устанавливает разрешение на паузу. При значении false меню паузы не приостанавливает игру.
|
||||
hud.set_allow_pause(flag: bool)
|
||||
```
|
||||
|
||||
@ -37,10 +37,10 @@ player.set_vel(playerid: int, x: number, y: number, z: number)
|
||||
Устанавливает x, y, z линейной скорости игрока
|
||||
|
||||
```lua
|
||||
player.get_rot(playerid: int) -> number, number, number
|
||||
player.get_rot(playerid: int, interpolated: bool=false) -> number, number, number
|
||||
```
|
||||
|
||||
Возвращает x, y, z вращения камеры (в радианах)
|
||||
Возвращает x, y, z вращения камеры (в радианах). Интерполяция актуальна в случаях, когда частота обновления вращения ниже частоты кадров.
|
||||
|
||||
```lua
|
||||
player.set_rot(playerid: int, x: number, y: number, z: number)
|
||||
|
||||
@ -3,7 +3,9 @@ local body = entity.rigidbody
|
||||
local rig = entity.skeleton
|
||||
|
||||
local itemid = 0
|
||||
local headIndex = rig:index("head")
|
||||
local itemIndex = rig:index("item")
|
||||
local bodyIndex = rig:index("body")
|
||||
|
||||
local function refresh_model(id)
|
||||
itemid = id
|
||||
@ -12,7 +14,16 @@ local function refresh_model(id)
|
||||
end
|
||||
|
||||
function on_render()
|
||||
local invid, slotid = player.get_inventory()
|
||||
local pid = entity:get_player()
|
||||
if pid == -1 then
|
||||
return
|
||||
end
|
||||
|
||||
local rx, ry, rz = player.get_rot(pid, true)
|
||||
rig:set_matrix(headIndex, mat4.rotate({1, 0, 0}, ry))
|
||||
rig:set_matrix(bodyIndex, mat4.rotate({0, 1, 0}, rx))
|
||||
|
||||
local invid, slotid = player.get_inventory(pid)
|
||||
local id, _ = inventory.get(invid, slotid)
|
||||
if id ~= itemid then
|
||||
refresh_model(id)
|
||||
|
||||
@ -37,4 +37,6 @@ end
|
||||
|
||||
function on_open()
|
||||
refresh()
|
||||
|
||||
input.add_callback("key:f5", refresh, document.root)
|
||||
end
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
local gui_util = {}
|
||||
local gui_util = {
|
||||
local_dispatchers = {}
|
||||
}
|
||||
|
||||
--- Parse `pagename?arg1=value1&arg2=value2` queries
|
||||
--- @param query page query string
|
||||
@ -25,6 +27,11 @@ end
|
||||
--- @return document_id
|
||||
function gui_util.load_page(query)
|
||||
local name, args = gui_util.parse_query(query)
|
||||
for i = #gui_util.local_dispatchers, 1, -1 do
|
||||
local newname, newargs = gui_util.local_dispatchers[i](name, args)
|
||||
name = newname or name
|
||||
args = newargs or args
|
||||
end
|
||||
local filename = file.find(string.format("layouts/pages/%s.xml", name))
|
||||
if filename then
|
||||
name = file.prefix(filename)..":pages/"..name
|
||||
@ -33,4 +40,15 @@ function gui_util.load_page(query)
|
||||
end
|
||||
end
|
||||
|
||||
function gui_util.add_page_dispatcher(dispatcher)
|
||||
if type(dispatcher) ~= "function" then
|
||||
error("function expected")
|
||||
end
|
||||
table.insert(gui_util.local_dispatchers, dispatcher)
|
||||
end
|
||||
|
||||
function gui_util.reset_local()
|
||||
gui_util.local_dispatchers = {}
|
||||
end
|
||||
|
||||
return gui_util
|
||||
|
||||
@ -49,6 +49,7 @@ local Skeleton = {__index={
|
||||
set_visible=function(self, i, b) return __skeleton.set_visible(self.eid, i, b) end,
|
||||
get_color=function(self) return __skeleton.get_color(self.eid) end,
|
||||
set_color=function(self, color) return __skeleton.set_color(self.eid, color) end,
|
||||
set_interpolated=function(self, b) return __skeleton.set_interpolated(self.eid, b) end,
|
||||
}}
|
||||
|
||||
local function new_Skeleton(eid)
|
||||
@ -66,6 +67,7 @@ local Entity = {__index={
|
||||
get_uid=function(self) return self.eid end,
|
||||
def_index=function(self) return entities.get_def(self.eid) end,
|
||||
def_name=function(self) return entities.def_name(entities.get_def(self.eid)) end,
|
||||
get_player=function(self) return entities.get_player(self.eid) end,
|
||||
}}
|
||||
|
||||
local entities = {}
|
||||
|
||||
@ -201,7 +201,7 @@ _GUI_ROOT = Document.new("core:root")
|
||||
_MENU = _GUI_ROOT.menu
|
||||
menu = _MENU
|
||||
|
||||
local gui_util = require "core:gui_util"
|
||||
gui_util = require "core:gui_util"
|
||||
__vc_page_loader = gui_util.load_page
|
||||
|
||||
--- Console library extension ---
|
||||
@ -385,6 +385,15 @@ function __vc_on_hud_open()
|
||||
hud.show_overlay("core:console", false, {"chat"})
|
||||
end)
|
||||
end)
|
||||
input.add_callback("key:escape", function()
|
||||
if hud.is_paused() then
|
||||
hud.resume()
|
||||
elseif hud.is_inventory_open() then
|
||||
hud.close_inventory()
|
||||
else
|
||||
hud.pause()
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
local RULES_FILE = "world:rules.toml"
|
||||
@ -408,6 +417,7 @@ end
|
||||
|
||||
function __vc_on_world_quit()
|
||||
_rules.clear()
|
||||
gui_util:reset_local()
|
||||
end
|
||||
|
||||
local __vc_coroutines = {}
|
||||
@ -470,7 +480,10 @@ function __process_post_runnables()
|
||||
|
||||
local dead = {}
|
||||
for name, co in pairs(__vc_named_coroutines) do
|
||||
coroutine.resume(co)
|
||||
local success, err = coroutine.resume(co)
|
||||
if not success then
|
||||
debug.error(err)
|
||||
end
|
||||
if coroutine.status(co) == "dead" then
|
||||
table.insert(dead, name)
|
||||
end
|
||||
|
||||
@ -272,7 +272,7 @@ PacksManager Engine::createPacksManager(const fs::path& worldFolder) {
|
||||
return manager;
|
||||
}
|
||||
|
||||
void Engine::setLevelConsumer(consumer<std::unique_ptr<Level>> levelConsumer) {
|
||||
void Engine::setLevelConsumer(OnWorldOpen levelConsumer) {
|
||||
this->levelConsumer = std::move(levelConsumer);
|
||||
}
|
||||
|
||||
@ -446,14 +446,14 @@ void Engine::setLanguage(std::string locale) {
|
||||
langs::setup(paths.getResourcesFolder(), std::move(locale), contentPacks);
|
||||
}
|
||||
|
||||
void Engine::onWorldOpen(std::unique_ptr<Level> level) {
|
||||
void Engine::onWorldOpen(std::unique_ptr<Level> level, int64_t localPlayer) {
|
||||
logger.info() << "world open";
|
||||
levelConsumer(std::move(level));
|
||||
levelConsumer(std::move(level), localPlayer);
|
||||
}
|
||||
|
||||
void Engine::onWorldClosed() {
|
||||
logger.info() << "world closed";
|
||||
levelConsumer(nullptr);
|
||||
levelConsumer(nullptr, -1);
|
||||
}
|
||||
|
||||
void Engine::quit() {
|
||||
|
||||
@ -53,6 +53,8 @@ struct CoreParameters {
|
||||
std::filesystem::path scriptFile;
|
||||
};
|
||||
|
||||
using OnWorldOpen = std::function<void(std::unique_ptr<Level>, int64_t)>;
|
||||
|
||||
class Engine : public util::ObjectsKeeper {
|
||||
CoreParameters params;
|
||||
EngineSettings settings;
|
||||
@ -71,7 +73,7 @@ class Engine : public util::ObjectsKeeper {
|
||||
std::unique_ptr<gui::GUI> gui;
|
||||
PostRunnables postRunnables;
|
||||
Time time;
|
||||
consumer<std::unique_ptr<Level>> levelConsumer;
|
||||
OnWorldOpen levelConsumer;
|
||||
bool quitSignal = false;
|
||||
|
||||
void loadControls();
|
||||
@ -134,7 +136,7 @@ public:
|
||||
/// @brief Get engine resource paths controller
|
||||
ResPaths* getResPaths();
|
||||
|
||||
void onWorldOpen(std::unique_ptr<Level> level);
|
||||
void onWorldOpen(std::unique_ptr<Level> level, int64_t localPlayer);
|
||||
void onWorldClosed();
|
||||
|
||||
void quit();
|
||||
@ -166,7 +168,7 @@ public:
|
||||
|
||||
PacksManager createPacksManager(const fs::path& worldFolder);
|
||||
|
||||
void setLevelConsumer(consumer<std::unique_ptr<Level>> levelConsumer);
|
||||
void setLevelConsumer(OnWorldOpen levelConsumer);
|
||||
|
||||
SettingsHandler& getSettingsHandler();
|
||||
|
||||
|
||||
@ -15,14 +15,16 @@ Mainloop::Mainloop(Engine& engine) : engine(engine) {
|
||||
void Mainloop::run() {
|
||||
auto& time = engine.getTime();
|
||||
|
||||
engine.setLevelConsumer([this](auto level) {
|
||||
engine.setLevelConsumer([this](auto level, int64_t localPlayer) {
|
||||
if (level == nullptr) {
|
||||
// destroy LevelScreen and run quit callbacks
|
||||
engine.setScreen(nullptr);
|
||||
// create and go to menu screen
|
||||
engine.setScreen(std::make_shared<MenuScreen>(engine));
|
||||
} else {
|
||||
engine.setScreen(std::make_shared<LevelScreen>(engine, std::move(level)));
|
||||
engine.setScreen(std::make_shared<LevelScreen>(
|
||||
engine, std::move(level), localPlayer
|
||||
));
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@ -30,7 +30,7 @@ void ServerMainloop::run() {
|
||||
logger.info() << "nothing to do";
|
||||
return;
|
||||
}
|
||||
engine.setLevelConsumer([this](auto level) {
|
||||
engine.setLevelConsumer([this](auto level, auto) {
|
||||
setLevel(std::move(level));
|
||||
});
|
||||
|
||||
|
||||
@ -13,7 +13,9 @@
|
||||
#include "graphics/render/ParticlesRenderer.hpp"
|
||||
#include "graphics/render/ChunksRenderer.hpp"
|
||||
#include "logic/scripting/scripting.hpp"
|
||||
#include "network/Network.hpp"
|
||||
#include "objects/Player.hpp"
|
||||
#include "objects/Players.hpp"
|
||||
#include "objects/Entities.hpp"
|
||||
#include "objects/EntityDef.hpp"
|
||||
#include "physics/Hitbox.hpp"
|
||||
@ -41,6 +43,7 @@ static std::shared_ptr<Label> create_label(wstringsupplier supplier) {
|
||||
|
||||
// TODO: move to xml
|
||||
// TODO: move to xml finally
|
||||
// TODO: move to xml finally
|
||||
std::shared_ptr<UINode> create_debug_panel(
|
||||
Engine& engine,
|
||||
Level& level,
|
||||
@ -56,6 +59,10 @@ std::shared_ptr<UINode> create_debug_panel(
|
||||
static int fpsMax = fps;
|
||||
static std::wstring fpsString = L"";
|
||||
|
||||
static size_t lastTotalDownload = 0;
|
||||
static size_t lastTotalUpload = 0;
|
||||
static std::wstring netSpeedString = L"";
|
||||
|
||||
panel->listenInterval(0.016f, [&engine]() {
|
||||
fps = 1.0f / engine.getTime().getDelta();
|
||||
fpsMin = std::min(fps, fpsMin);
|
||||
@ -67,6 +74,19 @@ std::shared_ptr<UINode> create_debug_panel(
|
||||
fpsMin = fps;
|
||||
fpsMax = fps;
|
||||
});
|
||||
|
||||
panel->listenInterval(1.0f, [&engine]() {
|
||||
const auto& network = engine.getNetwork();
|
||||
size_t totalDownload = network.getTotalDownload();
|
||||
size_t totalUpload = network.getTotalUpload();
|
||||
netSpeedString =
|
||||
L"download: " + std::to_wstring(totalDownload - lastTotalDownload) +
|
||||
L" B/s upload: " + std::to_wstring(totalUpload - lastTotalUpload) +
|
||||
L" B/s";
|
||||
lastTotalDownload = totalDownload;
|
||||
lastTotalUpload = totalUpload;
|
||||
});
|
||||
|
||||
panel->add(create_label([]() { return L"fps: "+fpsString;}));
|
||||
|
||||
panel->add(create_label([]() {
|
||||
@ -84,6 +104,7 @@ std::shared_ptr<UINode> create_debug_panel(
|
||||
panel->add(create_label([]() {
|
||||
return L"lua-stack: " + std::to_wstring(scripting::get_values_on_stack());
|
||||
}));
|
||||
panel->add(create_label([]() { return netSpeedString; }));
|
||||
panel->add(create_label([&engine]() {
|
||||
auto& settings = engine.getSettings();
|
||||
bool culling = settings.graphics.frustumCulling.get();
|
||||
@ -103,6 +124,10 @@ std::shared_ptr<UINode> create_debug_panel(
|
||||
return L"entities: "+std::to_wstring(level.entities->size())+L" next: "+
|
||||
std::to_wstring(level.entities->peekNextID());
|
||||
}));
|
||||
panel->add(create_label([&]() {
|
||||
return L"players: "+std::to_wstring(level.players->size())+L" local: "+
|
||||
std::to_wstring(player.getId());
|
||||
}));
|
||||
panel->add(create_label([&]() -> std::wstring {
|
||||
const auto& vox = player.selection.vox;
|
||||
std::wstringstream stream;
|
||||
|
||||
@ -201,17 +201,6 @@ Hud::Hud(Engine& engine, LevelFrontend& frontend, Player& player)
|
||||
"' pos='0' size='256' gravity='top-right' margin='0,20,0,0'/>"
|
||||
);
|
||||
add(HudElement(hud_element_mode::permanent, nullptr, debugMinimap, true));
|
||||
|
||||
keepAlive(Events::keyCallbacks[keycode::ESCAPE].add([this]() -> bool {
|
||||
if (pause) {
|
||||
setPause(false);
|
||||
} else if (inventoryOpen) {
|
||||
closeInventory();
|
||||
} else {
|
||||
setPause(true);
|
||||
}
|
||||
return false;
|
||||
}));
|
||||
}
|
||||
|
||||
Hud::~Hud() {
|
||||
@ -234,7 +223,8 @@ void Hud::cleanup() {
|
||||
}
|
||||
|
||||
void Hud::processInput(bool visible) {
|
||||
if (!Window::isFocused() && !pause && !isInventoryOpen()) {
|
||||
auto menu = gui.getMenu();
|
||||
if (!Window::isFocused() && !menu->hasOpenPage() && !isInventoryOpen()) {
|
||||
setPause(true);
|
||||
}
|
||||
if (!pause && visible && Events::jactive(BIND_HUD_INVENTORY)) {
|
||||
@ -329,14 +319,13 @@ void Hud::update(bool visible) {
|
||||
if (!visible && inventoryOpen) {
|
||||
closeInventory();
|
||||
}
|
||||
if (pause && menu->getCurrent().panel == nullptr) {
|
||||
if (pause && !menu->hasOpenPage()) {
|
||||
setPause(false);
|
||||
}
|
||||
|
||||
if (!gui.isFocusCaught()) {
|
||||
processInput(visible);
|
||||
}
|
||||
if ((pause || inventoryOpen) == Events::_cursor_locked) {
|
||||
if ((menu->hasOpenPage() || inventoryOpen) == Events::isCursorLocked()) {
|
||||
Events::toggleCursor();
|
||||
}
|
||||
|
||||
@ -601,7 +590,9 @@ void Hud::draw(const DrawContext& ctx){
|
||||
const Viewport& viewport = ctx.getViewport();
|
||||
const uint width = viewport.getWidth();
|
||||
const uint height = viewport.getHeight();
|
||||
auto menu = gui.getMenu();
|
||||
|
||||
darkOverlay->setVisible(menu->hasOpenPage());
|
||||
updateElementsPosition(viewport);
|
||||
|
||||
uicamera->setFov(height);
|
||||
@ -687,19 +678,20 @@ void Hud::setPause(bool pause) {
|
||||
if (this->pause == pause) {
|
||||
return;
|
||||
}
|
||||
this->pause = pause;
|
||||
if (allowPause) {
|
||||
this->pause = pause;
|
||||
}
|
||||
|
||||
if (inventoryOpen) {
|
||||
closeInventory();
|
||||
}
|
||||
|
||||
const auto& menu = gui.getMenu();
|
||||
if (pause) {
|
||||
menu->setPage("pause");
|
||||
} else {
|
||||
if (menu->hasOpenPage()) {
|
||||
menu->reset();
|
||||
} else {
|
||||
menu->setPage("pause");
|
||||
}
|
||||
darkOverlay->setVisible(pause);
|
||||
menu->setVisible(pause);
|
||||
}
|
||||
|
||||
@ -732,3 +724,12 @@ void Hud::setDebugCheats(bool flag) {
|
||||
debugPanel->setZIndex(2);
|
||||
gui.add(debugPanel);
|
||||
}
|
||||
|
||||
void Hud::setAllowPause(bool flag) {
|
||||
if (pause) {
|
||||
auto menu = gui.getMenu();
|
||||
setPause(false);
|
||||
menu->setPage("pause", true);
|
||||
}
|
||||
allowPause = flag;
|
||||
}
|
||||
|
||||
@ -113,6 +113,8 @@ class Hud : public util::ObjectsKeeper {
|
||||
bool showContentPanel = true;
|
||||
/// @brief Provide cheat controllers to the debug panel
|
||||
bool allowDebugCheats = true;
|
||||
/// @brief Allow actual pause
|
||||
bool allowPause = true;
|
||||
bool debug = false;
|
||||
/// @brief UI element will be dynamicly positioned near to inventory or in screen center
|
||||
std::shared_ptr<gui::UINode> secondUI;
|
||||
@ -206,6 +208,8 @@ public:
|
||||
|
||||
void setDebugCheats(bool flag);
|
||||
|
||||
void setAllowPause(bool flag);
|
||||
|
||||
static bool showGeneratorMinimap;
|
||||
|
||||
/// @brief Runtime updating debug visualization texture
|
||||
|
||||
@ -36,9 +36,10 @@
|
||||
|
||||
static debug::Logger logger("level-screen");
|
||||
|
||||
LevelScreen::LevelScreen(Engine& engine, std::unique_ptr<Level> levelPtr)
|
||||
: Screen(engine), postProcessing(std::make_unique<PostProcessing>())
|
||||
{
|
||||
LevelScreen::LevelScreen(
|
||||
Engine& engine, std::unique_ptr<Level> levelPtr, int64_t localPlayer
|
||||
)
|
||||
: Screen(engine), postProcessing(std::make_unique<PostProcessing>()) {
|
||||
Level* level = levelPtr.get();
|
||||
|
||||
auto& settings = engine.getSettings();
|
||||
@ -46,7 +47,9 @@ LevelScreen::LevelScreen(Engine& engine, std::unique_ptr<Level> levelPtr)
|
||||
auto menu = engine.getGUI()->getMenu();
|
||||
menu->reset();
|
||||
|
||||
auto player = level->players->get(0);
|
||||
auto player = level->players->get(localPlayer);
|
||||
assert(player != nullptr);
|
||||
|
||||
controller =
|
||||
std::make_unique<LevelController>(&engine, std::move(levelPtr), player);
|
||||
playerController = std::make_unique<PlayerController>(
|
||||
@ -165,14 +168,16 @@ void LevelScreen::updateHotkeys() {
|
||||
|
||||
void LevelScreen::update(float delta) {
|
||||
gui::GUI* gui = engine.getGUI();
|
||||
auto menu = gui->getMenu();
|
||||
|
||||
bool inputLocked = hud->isPause() ||
|
||||
bool inputLocked = menu->hasOpenPage() ||
|
||||
hud->isInventoryOpen() ||
|
||||
gui->isFocusCaught();
|
||||
if (!gui->isFocusCaught()) {
|
||||
updateHotkeys();
|
||||
}
|
||||
|
||||
auto level = controller->getLevel();
|
||||
auto player = playerController->getPlayer();
|
||||
auto camera = player->currentCamera;
|
||||
|
||||
@ -189,7 +194,6 @@ void LevelScreen::update(float delta) {
|
||||
camera->dir,
|
||||
glm::vec3(0, 1, 0)
|
||||
);
|
||||
auto level = controller->getLevel();
|
||||
const auto& settings = engine.getSettings();
|
||||
|
||||
if (!hud->isPause()) {
|
||||
|
||||
@ -34,7 +34,9 @@ class LevelScreen : public Screen {
|
||||
void initializeContent();
|
||||
void initializePack(ContentPackRuntime* pack);
|
||||
public:
|
||||
LevelScreen(Engine& engine, std::unique_ptr<Level> level);
|
||||
LevelScreen(
|
||||
Engine& engine, std::unique_ptr<Level> level, int64_t localPlayer
|
||||
);
|
||||
~LevelScreen();
|
||||
|
||||
void update(float delta) override;
|
||||
|
||||
@ -12,6 +12,7 @@
|
||||
#include "window/Camera.hpp"
|
||||
#include "objects/Player.hpp"
|
||||
#include "objects/Players.hpp"
|
||||
#include "objects/Entities.hpp"
|
||||
#include "logic/LevelController.hpp"
|
||||
#include "util/stringutil.hpp"
|
||||
#include "engine/Engine.hpp"
|
||||
@ -156,9 +157,14 @@ void Decorator::update(float delta, const Camera& camera) {
|
||||
auto note = renderer.texts->get(textsIter->second);
|
||||
auto player = level.players->get(textsIter->first);
|
||||
if (player == nullptr) {
|
||||
renderer.texts->remove(textsIter->second);
|
||||
textsIter = playerTexts.erase(textsIter);
|
||||
} else {
|
||||
note->setPosition(player->getPosition() + glm::vec3(0, 1, 0));
|
||||
glm::vec3 position = player->getPosition();
|
||||
if (auto entity = level.entities->get(player->getEntity())) {
|
||||
position = entity->getInterpolatedPosition();
|
||||
}
|
||||
note->setPosition(position + glm::vec3(0, 1, 0));
|
||||
++textsIter;
|
||||
}
|
||||
}
|
||||
|
||||
@ -274,8 +274,9 @@ void WorldRenderer::renderHands(
|
||||
glm::mat4(1.0f), -glm::pi<float>() * 0.5f, glm::vec3(0, 1, 0)
|
||||
);
|
||||
prevRotation = rotation;
|
||||
glm::vec3 cameraRotation = player.getRotation();
|
||||
auto offset = -(camera.position - player.getPosition());
|
||||
float angle = glm::radians(player.rotation.x - 90);
|
||||
float angle = glm::radians(cameraRotation.x - 90);
|
||||
float cos = glm::cos(angle);
|
||||
float sin = glm::sin(angle);
|
||||
|
||||
|
||||
@ -166,7 +166,7 @@ void GUI::actFocused() {
|
||||
focus->keyPressed(key);
|
||||
}
|
||||
|
||||
if (!Events::_cursor_locked) {
|
||||
if (!Events::isCursorLocked()) {
|
||||
if (Events::clicked(mousecode::BUTTON_1) &&
|
||||
(Events::jclicked(mousecode::BUTTON_1) || Events::delta.x || Events::delta.y))
|
||||
{
|
||||
@ -189,7 +189,7 @@ void GUI::act(float delta, const Viewport& vp) {
|
||||
auto prevfocus = focus;
|
||||
|
||||
updateTooltip(delta);
|
||||
if (!Events::_cursor_locked) {
|
||||
if (!Events::isCursorLocked()) {
|
||||
actMouse(delta);
|
||||
} else {
|
||||
if (hover) {
|
||||
|
||||
@ -91,6 +91,10 @@ Page& Menu::getCurrent() {
|
||||
return current;
|
||||
}
|
||||
|
||||
bool Menu::hasOpenPage() const {
|
||||
return current.panel != nullptr;
|
||||
}
|
||||
|
||||
void Menu::clearHistory() {
|
||||
pageStack = std::stack<Page>();
|
||||
}
|
||||
|
||||
@ -64,5 +64,7 @@ namespace gui {
|
||||
|
||||
/// @brief Get current page
|
||||
Page& getCurrent();
|
||||
|
||||
bool hasOpenPage() const;
|
||||
};
|
||||
}
|
||||
|
||||
@ -8,9 +8,12 @@
|
||||
#include "voxels/Block.hpp"
|
||||
#include "constants.hpp"
|
||||
#include "util/timeutil.hpp"
|
||||
#include "debug/Logger.hpp"
|
||||
|
||||
#include <memory>
|
||||
|
||||
static debug::Logger logger("lighting");
|
||||
|
||||
Lighting::Lighting(const Content& content, Chunks& chunks)
|
||||
: content(content), chunks(chunks) {
|
||||
auto& indices = *content.getIndices();
|
||||
@ -63,6 +66,10 @@ void Lighting::buildSkyLight(int cx, int cz){
|
||||
const auto blockDefs = content.getIndices()->blocks.getDefs();
|
||||
|
||||
Chunk* chunk = chunks.getChunk(cx, cz);
|
||||
if (chunk == nullptr) {
|
||||
logger.error() << "attempted to build sky lights to chunk missing in local matrix";
|
||||
return;
|
||||
}
|
||||
for (int z = 0; z < CHUNK_D; z++){
|
||||
for (int x = 0; x < CHUNK_W; x++){
|
||||
int gx = x + cx * CHUNK_W;
|
||||
@ -95,7 +102,10 @@ void Lighting::onChunkLoaded(int cx, int cz, bool expand) {
|
||||
|
||||
auto blockDefs = content.getIndices()->blocks.getDefs();
|
||||
auto chunk = chunks.getChunk(cx, cz);
|
||||
|
||||
if (chunk == nullptr) {
|
||||
logger.error() << "attempted to build lights to chunk missing in local matrix";
|
||||
return;
|
||||
}
|
||||
for (uint y = 0; y < CHUNK_H; y++){
|
||||
for (uint z = 0; z < CHUNK_D; z++){
|
||||
for (uint x = 0; x < CHUNK_W; x++){
|
||||
|
||||
@ -92,7 +92,7 @@ bool ChunksController::loadVisible(const Player& player, uint padding) const {
|
||||
}
|
||||
|
||||
const auto& chunk = chunks.getChunks()[nearZ * sizeX + nearX];
|
||||
if (chunk != nullptr || !assigned) {
|
||||
if (chunk != nullptr || !assigned || !player.isLoadingChunks()) {
|
||||
return false;
|
||||
}
|
||||
int offsetX = chunks.getOffsetX();
|
||||
@ -101,7 +101,9 @@ bool ChunksController::loadVisible(const Player& player, uint padding) const {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ChunksController::buildLights(const Player& player, const std::shared_ptr<Chunk>& chunk) const {
|
||||
bool ChunksController::buildLights(
|
||||
const Player& player, const std::shared_ptr<Chunk>& chunk
|
||||
) const {
|
||||
int surrounding = 0;
|
||||
for (int oz = -1; oz <= 1; oz++) {
|
||||
for (int ox = -1; ox <= 1; ox++) {
|
||||
|
||||
@ -143,7 +143,9 @@ static bool load_world_content(Engine& engine, const fs::path& folder) {
|
||||
}
|
||||
|
||||
static void load_world(
|
||||
Engine& engine, const std::shared_ptr<WorldFiles>& worldFiles
|
||||
Engine& engine,
|
||||
const std::shared_ptr<WorldFiles>& worldFiles,
|
||||
int64_t localPlayer
|
||||
) {
|
||||
try {
|
||||
auto content = engine.getContent();
|
||||
@ -151,7 +153,7 @@ static void load_world(
|
||||
auto& settings = engine.getSettings();
|
||||
|
||||
auto level = World::load(worldFiles, settings, *content, packs);
|
||||
engine.onWorldOpen(std::move(level));
|
||||
engine.onWorldOpen(std::move(level), localPlayer);
|
||||
} catch (const world_load_error& error) {
|
||||
guiutil::alert(
|
||||
engine,
|
||||
@ -233,7 +235,7 @@ void EngineController::openWorld(const std::string& name, bool confirmConvert) {
|
||||
}
|
||||
return;
|
||||
}
|
||||
load_world(engine, std::move(worldFiles));
|
||||
load_world(engine, std::move(worldFiles), localPlayer);
|
||||
}
|
||||
|
||||
inline uint64_t str2seed(const std::string& seedstr) {
|
||||
@ -279,9 +281,13 @@ void EngineController::createWorld(
|
||||
engine.getContentPacks()
|
||||
);
|
||||
if (!engine.isHeadless()) {
|
||||
level->players->create();
|
||||
level->players->create(localPlayer);
|
||||
}
|
||||
engine.onWorldOpen(std::move(level));
|
||||
engine.onWorldOpen(std::move(level), localPlayer);
|
||||
}
|
||||
|
||||
void EngineController::setLocalPlayer(int64_t player) {
|
||||
localPlayer = player;
|
||||
}
|
||||
|
||||
void EngineController::reopenWorld(World* world) {
|
||||
|
||||
@ -12,6 +12,7 @@ class LevelController;
|
||||
class EngineController {
|
||||
Engine& engine;
|
||||
|
||||
int64_t localPlayer = -1;
|
||||
void onMissingContent(const std::shared_ptr<ContentReport>& report);
|
||||
public:
|
||||
EngineController(Engine& engine);
|
||||
@ -37,5 +38,7 @@ public:
|
||||
const std::string& generatorID
|
||||
);
|
||||
|
||||
void setLocalPlayer(int64_t player);
|
||||
|
||||
void reopenWorld(World* world);
|
||||
};
|
||||
|
||||
@ -50,6 +50,10 @@ LevelController::LevelController(
|
||||
do {
|
||||
confirmed = 0;
|
||||
for (const auto& [_, player] : *level->players) {
|
||||
if (!player->isLoadingChunks()) {
|
||||
confirmed++;
|
||||
continue;
|
||||
}
|
||||
glm::vec3 position = player->getPosition();
|
||||
player->chunks->configure(
|
||||
std::floor(position.x), std::floor(position.z), 1
|
||||
@ -66,6 +70,11 @@ LevelController::LevelController(
|
||||
|
||||
void LevelController::update(float delta, bool pause) {
|
||||
for (const auto& [_, player] : *level->players) {
|
||||
if (player->isSuspended()) {
|
||||
continue;
|
||||
}
|
||||
player->rotationInterpolation.updateTimer(delta);
|
||||
player->updateEntity();
|
||||
glm::vec3 position = player->getPosition();
|
||||
player->chunks->configure(
|
||||
position.x,
|
||||
@ -85,6 +94,9 @@ void LevelController::update(float delta, bool pause) {
|
||||
level->entities->updatePhysics(delta);
|
||||
level->entities->update(delta);
|
||||
for (const auto& [_, player] : *level->players) {
|
||||
if (player->isSuspended()) {
|
||||
continue;
|
||||
}
|
||||
if (playerTickClock.update(delta)) {
|
||||
if (player->getId() % playerTickClock.getParts() ==
|
||||
playerTickClock.getPart()) {
|
||||
|
||||
@ -54,7 +54,7 @@ void CameraControl::refresh() {
|
||||
}
|
||||
|
||||
void CameraControl::updateMouse(PlayerInput& input) {
|
||||
glm::vec3& rotation = player.rotation;
|
||||
glm::vec3 rotation = player.getRotation();
|
||||
|
||||
float sensitivity =
|
||||
(input.zoom ? settings.sensitivity.get() / 4.f
|
||||
@ -75,6 +75,8 @@ void CameraControl::updateMouse(PlayerInput& input) {
|
||||
rotation.x += 360.f;
|
||||
}
|
||||
|
||||
player.setRotation(rotation);
|
||||
|
||||
camera->rotation = glm::mat4(1.0f);
|
||||
camera->rotate(
|
||||
glm::radians(rotation.y),
|
||||
@ -170,8 +172,8 @@ void CameraControl::update(
|
||||
switchCamera();
|
||||
}
|
||||
|
||||
auto& spCamera = player.spCamera;
|
||||
auto& tpCamera = player.tpCamera;
|
||||
const auto& spCamera = player.spCamera;
|
||||
const auto& tpCamera = player.tpCamera;
|
||||
|
||||
refresh();
|
||||
|
||||
@ -305,7 +307,6 @@ void PlayerController::resetKeyboard() {
|
||||
}
|
||||
|
||||
void PlayerController::updatePlayer(float delta) {
|
||||
player.updateEntity();
|
||||
player.updateInput(input, delta);
|
||||
}
|
||||
|
||||
@ -317,15 +318,15 @@ static int determine_rotation(
|
||||
if (name == "pipe") {
|
||||
if (norm.x < 0.0f)
|
||||
return BLOCK_DIR_WEST;
|
||||
else if (norm.x > 0.0f)
|
||||
if (norm.x > 0.0f)
|
||||
return BLOCK_DIR_EAST;
|
||||
else if (norm.y > 0.0f)
|
||||
if (norm.y > 0.0f)
|
||||
return BLOCK_DIR_UP;
|
||||
else if (norm.y < 0.0f)
|
||||
if (norm.y < 0.0f)
|
||||
return BLOCK_DIR_DOWN;
|
||||
else if (norm.z > 0.0f)
|
||||
if (norm.z > 0.0f)
|
||||
return BLOCK_DIR_NORTH;
|
||||
else if (norm.z < 0.0f)
|
||||
if (norm.z < 0.0f)
|
||||
return BLOCK_DIR_SOUTH;
|
||||
} else if (name == "pane") {
|
||||
if (abs(camDir.x) > abs(camDir.z)) {
|
||||
|
||||
@ -17,7 +17,7 @@ static int l_set_vel(lua::State* L) {
|
||||
|
||||
static int l_is_enabled(lua::State* L) {
|
||||
if (auto entity = get_entity(L, 1)) {
|
||||
lua::pushboolean(L, entity->getRigidbody().enabled);
|
||||
return lua::pushboolean(L, entity->getRigidbody().enabled);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -130,6 +130,22 @@ static int l_set_color(lua::State* L) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_is_interpolated(lua::State* L) {
|
||||
if (auto entity = get_entity(L, 1)) {
|
||||
auto& skeleton = entity->getSkeleton();
|
||||
return lua::pushboolean(L, skeleton.interpolation.isEnabled());
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_set_interpolated(lua::State* L) {
|
||||
if (auto entity = get_entity(L, 1)) {
|
||||
auto& skeleton = entity->getSkeleton();
|
||||
skeleton.interpolation.setEnabled(lua::toboolean(L, 2));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
const luaL_Reg skeletonlib[] = {
|
||||
{"get_model", lua::wrap<l_get_model>},
|
||||
{"set_model", lua::wrap<l_set_model>},
|
||||
@ -142,4 +158,6 @@ const luaL_Reg skeletonlib[] = {
|
||||
{"set_visible", lua::wrap<l_set_visible>},
|
||||
{"get_color", lua::wrap<l_get_color>},
|
||||
{"set_color", lua::wrap<l_set_color>},
|
||||
{"is_interpolated", lua::wrap<l_is_interpolated>},
|
||||
{"set_interpolated", lua::wrap<l_set_interpolated>},
|
||||
{NULL, NULL}};
|
||||
|
||||
@ -55,10 +55,15 @@ static int l_new_world(lua::State* L) {
|
||||
auto name = lua::require_string(L, 1);
|
||||
auto seed = lua::require_string(L, 2);
|
||||
auto generator = lua::require_string(L, 3);
|
||||
int64_t localPlayer = 0;
|
||||
if (lua::gettop(L) >= 4) {
|
||||
localPlayer = lua::tointeger(L, 4);
|
||||
}
|
||||
if (level != nullptr) {
|
||||
throw std::runtime_error("world must be closed before");
|
||||
}
|
||||
auto controller = engine->getController();
|
||||
controller->setLocalPlayer(localPlayer);
|
||||
controller->createWorld(name, seed, generator);
|
||||
return 0;
|
||||
}
|
||||
@ -71,6 +76,7 @@ static int l_open_world(lua::State* L) {
|
||||
throw std::runtime_error("world must be closed before");
|
||||
}
|
||||
auto controller = engine->getController();
|
||||
controller->setLocalPlayer(0);
|
||||
controller->openWorld(name, false);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -54,7 +54,7 @@ static int l_get_def(lua::State* L) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_show(lua::State* L) {
|
||||
static int l_spawn(lua::State* L) {
|
||||
auto level = controller->getLevel();
|
||||
auto defname = lua::tostring(L, 1);
|
||||
auto& def = content->entities.require(defname);
|
||||
@ -81,6 +81,15 @@ static int l_get_skeleton(lua::State* L) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_get_player(lua::State* L) {
|
||||
entityid_t eid = lua::touinteger(L, 1);
|
||||
auto level = controller->getLevel();
|
||||
if (auto entity = level->entities->get(eid)) {
|
||||
return lua::pushinteger(L, entity->getPlayer());
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_set_skeleton(lua::State* L) {
|
||||
if (auto entity = get_entity(L, 1)) {
|
||||
std::string skeletonName = lua::require_string(L, 2);
|
||||
@ -221,10 +230,11 @@ const luaL_Reg entitylib[] = {
|
||||
{"def_hitbox", lua::wrap<l_def_hitbox>},
|
||||
{"get_def", lua::wrap<l_get_def>},
|
||||
{"defs_count", lua::wrap<l_defs_count>},
|
||||
{"spawn", lua::wrap<l_show>},
|
||||
{"spawn", lua::wrap<l_spawn>},
|
||||
{"despawn", lua::wrap<l_despawn>},
|
||||
{"get_skeleton", lua::wrap<l_get_skeleton>},
|
||||
{"set_skeleton", lua::wrap<l_set_skeleton>},
|
||||
{"get_player", lua::wrap<l_get_player>},
|
||||
{"get_all_in_box", lua::wrap<l_get_all_in_box>},
|
||||
{"get_all_in_radius", lua::wrap<l_get_all_in_radius>},
|
||||
{"raycast", lua::wrap<l_raycast>},
|
||||
|
||||
@ -170,6 +170,11 @@ static int l_set_debug_cheats(lua::State* L) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_set_allow_pause(lua::State* L) {
|
||||
hud->setAllowPause(lua::toboolean(L, 1));
|
||||
return 0;
|
||||
}
|
||||
|
||||
const luaL_Reg hudlib[] = {
|
||||
{"open_inventory", lua::wrap<l_open_inventory>},
|
||||
{"close_inventory", lua::wrap<l_close_inventory>},
|
||||
@ -187,5 +192,6 @@ const luaL_Reg hudlib[] = {
|
||||
{"_is_content_access", lua::wrap<l_is_content_access>},
|
||||
{"_set_content_access", lua::wrap<l_set_content_access>},
|
||||
{"_set_debug_cheats", lua::wrap<l_set_debug_cheats>},
|
||||
{"set_allow_pause", lua::wrap<l_set_allow_pause>},
|
||||
{NULL, NULL}
|
||||
};
|
||||
|
||||
@ -45,11 +45,6 @@ static int l_add_callback(lua::State* L) {
|
||||
handler = Events::keyCallbacks[key].add(actual_callback);
|
||||
}
|
||||
}
|
||||
|
||||
const auto& bind = Events::bindings.find(bindname);
|
||||
if (bind == Events::bindings.end()) {
|
||||
throw std::runtime_error("unknown binding " + util::quote(bindname));
|
||||
}
|
||||
auto callback = [=]() -> bool {
|
||||
if (!scripting::engine->getGUI()->isFocusCaught()) {
|
||||
return actual_callback();
|
||||
@ -57,6 +52,10 @@ static int l_add_callback(lua::State* L) {
|
||||
return false;
|
||||
};
|
||||
if (handler == nullptr) {
|
||||
const auto& bind = Events::bindings.find(bindname);
|
||||
if (bind == Events::bindings.end()) {
|
||||
throw std::runtime_error("unknown binding " + util::quote(bindname));
|
||||
}
|
||||
handler = bind->second.onactived.add(callback);
|
||||
}
|
||||
|
||||
|
||||
@ -73,7 +73,7 @@ static int l_connect(lua::State* L) {
|
||||
static int l_close(lua::State* L) {
|
||||
u64id_t id = lua::tointeger(L, 1);
|
||||
if (auto connection = engine->getNetwork().getConnection(id)) {
|
||||
connection->close();
|
||||
connection->close(true);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -52,6 +52,7 @@ static int l_set_vel(lua::State* L) {
|
||||
auto x = lua::tonumber(L, 2);
|
||||
auto y = lua::tonumber(L, 3);
|
||||
auto z = lua::tonumber(L, 4);
|
||||
|
||||
if (auto hitbox = player->getHitbox()) {
|
||||
hitbox->velocity = glm::vec3(x, y, z);
|
||||
}
|
||||
@ -60,7 +61,7 @@ static int l_set_vel(lua::State* L) {
|
||||
|
||||
static int l_get_rot(lua::State* L) {
|
||||
if (auto player = get_player(L, 1)) {
|
||||
return lua::pushvec_stack(L, player->rotation);
|
||||
return lua::pushvec_stack(L, player->getRotation(lua::toboolean(L, 2)));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@ -70,7 +71,7 @@ static int l_set_rot(lua::State* L) {
|
||||
if (!player) {
|
||||
return 0;
|
||||
}
|
||||
glm::vec3& rotation = player->rotation;
|
||||
glm::vec3 rotation = player->getRotation();
|
||||
|
||||
auto x = lua::tonumber(L, 2);
|
||||
auto y = lua::tonumber(L, 3);
|
||||
@ -81,6 +82,7 @@ static int l_set_rot(lua::State* L) {
|
||||
rotation.x = x;
|
||||
rotation.y = y;
|
||||
rotation.z = z;
|
||||
player->setRotation(rotation);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -272,15 +274,33 @@ static int l_set_name(lua::State* L) {
|
||||
}
|
||||
|
||||
static int l_create(lua::State* L) {
|
||||
auto player = level->players->create();
|
||||
if (lua::isstring(L, 1)) {
|
||||
player->setName(lua::require_string(L, 1));
|
||||
int64_t playerId = Players::NONE;
|
||||
if (lua::gettop(L) >= 2) {
|
||||
playerId = lua::tointeger(L, 2);
|
||||
}
|
||||
auto player = level->players->create(playerId);
|
||||
player->setName(lua::require_string(L, 1));
|
||||
return lua::pushinteger(L, player->getId());
|
||||
}
|
||||
|
||||
static int l_delete(lua::State* L) {
|
||||
level->players->remove(lua::tointeger(L, 1));
|
||||
auto id = lua::tointeger(L, 1);
|
||||
level->players->suspend(id);
|
||||
level->players->remove(id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_is_suspended(lua::State* L) {
|
||||
if (auto player = get_player(L, 1)) {
|
||||
return lua::pushboolean(L, player->isSuspended());
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_set_suspended(lua::State* L) {
|
||||
if (auto player = get_player(L, 1)) {
|
||||
player->setSuspended(lua::toboolean(L, 2));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -293,6 +313,8 @@ const luaL_Reg playerlib[] = {
|
||||
{"set_rot", lua::wrap<l_set_rot>},
|
||||
{"get_dir", lua::wrap<l_get_dir>},
|
||||
{"get_inventory", lua::wrap<l_get_inv>},
|
||||
{"is_suspended", lua::wrap<l_is_suspended>},
|
||||
{"set_suspended", lua::wrap<l_set_suspended>},
|
||||
{"is_flight", lua::wrap<l_is_flight>},
|
||||
{"set_flight", lua::wrap<l_set_flight>},
|
||||
{"is_noclip", lua::wrap<l_is_noclip>},
|
||||
|
||||
@ -19,6 +19,7 @@ namespace lua {
|
||||
int userdata_destructor(lua::State* L);
|
||||
|
||||
std::string env_name(int env);
|
||||
void dump_stack(lua::State*);
|
||||
|
||||
inline bool getglobal(lua::State* L, const std::string& name) {
|
||||
lua_getglobal(L, name.c_str());
|
||||
@ -208,7 +209,7 @@ namespace lua {
|
||||
return lua_isnumber(L, idx);
|
||||
}
|
||||
inline bool isstring(lua::State* L, int idx) {
|
||||
return lua_isstring(L, idx);
|
||||
return lua_type(L, idx) == LUA_TSTRING;
|
||||
}
|
||||
inline bool istable(lua::State* L, int idx) {
|
||||
return lua_istable(L, idx);
|
||||
@ -226,6 +227,11 @@ namespace lua {
|
||||
return lua_toboolean(L, idx);
|
||||
}
|
||||
inline lua::Integer tointeger(lua::State* L, int idx) {
|
||||
#ifndef NDEBUG
|
||||
if (lua_type(L, idx) == LUA_TSTRING) {
|
||||
throw std::runtime_error("integer expected, got string");
|
||||
}
|
||||
#endif
|
||||
return lua_tointeger(L, idx);
|
||||
}
|
||||
inline uint64_t touinteger(lua::State* L, int idx) {
|
||||
@ -236,6 +242,11 @@ namespace lua {
|
||||
return static_cast<uint64_t>(val);
|
||||
}
|
||||
inline lua::Number tonumber(lua::State* L, int idx) {
|
||||
#ifndef NDEBUG
|
||||
if (lua_type(L, idx) != LUA_TNUMBER && !lua_isnoneornil(L, idx)) {
|
||||
throw std::runtime_error("integer expected");
|
||||
}
|
||||
#endif
|
||||
return lua_tonumber(L, idx);
|
||||
}
|
||||
inline const char* tostring(lua::State* L, int idx) {
|
||||
@ -588,7 +599,6 @@ namespace lua {
|
||||
}
|
||||
int create_environment(lua::State*, int parent);
|
||||
void remove_environment(lua::State*, int id);
|
||||
void dump_stack(lua::State*);
|
||||
|
||||
inline void close(lua::State* L) {
|
||||
lua_close(L);
|
||||
|
||||
@ -400,10 +400,15 @@ public:
|
||||
return readBatch.size();
|
||||
}
|
||||
|
||||
void close() override {
|
||||
if (state != ConnectionState::CLOSED) {
|
||||
shutdown(descriptor, 2);
|
||||
closesocket(descriptor);
|
||||
void close(bool discardAll=false) override {
|
||||
{
|
||||
std::lock_guard lock(mutex);
|
||||
readBatch.clear();
|
||||
|
||||
if (state != ConnectionState::CLOSED) {
|
||||
shutdown(descriptor, 2);
|
||||
closesocket(descriptor);
|
||||
}
|
||||
}
|
||||
if (thread) {
|
||||
thread->join();
|
||||
|
||||
@ -49,7 +49,7 @@ namespace network {
|
||||
virtual void connect(runnable callback) = 0;
|
||||
virtual int recv(char* buffer, size_t length) = 0;
|
||||
virtual int send(const char* buffer, size_t length) = 0;
|
||||
virtual void close() = 0;
|
||||
virtual void close(bool discardAll=false) = 0;
|
||||
virtual int available() = 0;
|
||||
|
||||
virtual size_t pullUpload() = 0;
|
||||
|
||||
@ -38,6 +38,18 @@ void Transform::refresh() {
|
||||
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);
|
||||
@ -561,11 +573,14 @@ void Entities::render(
|
||||
if (transform.dirty) {
|
||||
transform.refresh();
|
||||
}
|
||||
if (skeleton.interpolation.isEnabled()) {
|
||||
skeleton.interpolation.updateTimer(delta);
|
||||
}
|
||||
const auto& pos = transform.pos;
|
||||
const auto& size = transform.size;
|
||||
if (!frustum || frustum->isBoxVisible(pos - size, pos + size)) {
|
||||
const auto* rigConfig = skeleton.config;
|
||||
rigConfig->render(assets, batch, skeleton, transform.combined);
|
||||
rigConfig->render(assets, batch, skeleton, transform.combined, pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -34,6 +34,7 @@ struct EntityId {
|
||||
entityid_t uid;
|
||||
const EntityDef& def;
|
||||
bool destroyFlag = false;
|
||||
int64_t player = -1;
|
||||
};
|
||||
|
||||
struct Transform {
|
||||
@ -161,6 +162,18 @@ public:
|
||||
return entity;
|
||||
}
|
||||
|
||||
int64_t getPlayer() const {
|
||||
return registry.get<EntityId>(entity).player;
|
||||
}
|
||||
|
||||
void setPlayer(int64_t id) {
|
||||
registry.get<EntityId>(entity).player = id;
|
||||
}
|
||||
|
||||
void setInterpolatedPosition(const glm::vec3& position);
|
||||
|
||||
glm::vec3 getInterpolatedPosition() const;
|
||||
|
||||
void destroy();
|
||||
};
|
||||
|
||||
|
||||
@ -65,8 +65,14 @@ void Player::updateEntity() {
|
||||
if (eid == 0) {
|
||||
auto& def = level.content.entities.require("base:player");
|
||||
eid = level.entities->spawn(def, getPosition());
|
||||
if (auto entity = level.entities->get(eid)) {
|
||||
entity->setPlayer(id);
|
||||
}
|
||||
} else if (auto entity = level.entities->get(eid)) {
|
||||
position = entity->getTransform().pos;
|
||||
if (auto entity = level.entities->get(eid)) {
|
||||
entity->setPlayer(id);
|
||||
}
|
||||
} else if (chunks->getChunkByVoxel(position)) {
|
||||
logger.error() << "player entity despawned or deleted; "
|
||||
"will be respawned";
|
||||
@ -164,23 +170,9 @@ void Player::postUpdate() {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: ERASE & FORGET
|
||||
auto& skeleton = entity->getSkeleton();
|
||||
|
||||
skeleton.visible = currentCamera != fpCamera;
|
||||
|
||||
auto body = skeleton.config->find("body");
|
||||
auto head = skeleton.config->find("head");
|
||||
|
||||
if (body) {
|
||||
skeleton.pose.matrices[body->getIndex()] = glm::rotate(
|
||||
glm::mat4(1.0f), glm::radians(rotation.x), glm::vec3(0, 1, 0)
|
||||
);
|
||||
}
|
||||
if (head) {
|
||||
skeleton.pose.matrices[head->getIndex()] = glm::rotate(
|
||||
glm::mat4(1.0f), glm::radians(rotation.y), glm::vec3(1, 0, 0)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void Player::teleport(glm::vec3 position) {
|
||||
@ -189,6 +181,7 @@ void Player::teleport(glm::vec3 position) {
|
||||
if (auto entity = level.entities->get(eid)) {
|
||||
entity->getRigidbody().hitbox.position = position;
|
||||
entity->getTransform().setPos(position);
|
||||
entity->setInterpolatedPosition(position);
|
||||
}
|
||||
}
|
||||
|
||||
@ -224,6 +217,14 @@ float Player::getSpeed() const {
|
||||
return speed;
|
||||
}
|
||||
|
||||
bool Player::isSuspended() const {
|
||||
return suspended;
|
||||
}
|
||||
|
||||
void Player::setSuspended(bool flag) {
|
||||
suspended = flag;
|
||||
}
|
||||
|
||||
bool Player::isFlight() const {
|
||||
return flight;
|
||||
}
|
||||
@ -296,6 +297,18 @@ glm::vec3 Player::getSpawnPoint() const {
|
||||
return spawnpoint;
|
||||
}
|
||||
|
||||
glm::vec3 Player::getRotation(bool interpolated) const {
|
||||
if (interpolated) {
|
||||
return rotationInterpolation.getCurrent();
|
||||
}
|
||||
return rotation;
|
||||
}
|
||||
|
||||
void Player::setRotation(const glm::vec3& rotation) {
|
||||
this->rotation = rotation;
|
||||
rotationInterpolation.refresh(rotation);
|
||||
}
|
||||
|
||||
dv::value Player::serialize() const {
|
||||
auto root = dv::object();
|
||||
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
#include "interfaces/Serializable.hpp"
|
||||
#include "settings.hpp"
|
||||
#include "voxels/voxel.hpp"
|
||||
#include "util/Interpolation.hpp"
|
||||
|
||||
class Chunks;
|
||||
class Camera;
|
||||
@ -47,6 +48,7 @@ class Player : public Serializable {
|
||||
glm::vec3 position;
|
||||
glm::vec3 spawnpoint {};
|
||||
std::shared_ptr<Inventory> inventory;
|
||||
bool suspended = false;
|
||||
bool flight = false;
|
||||
bool noclip = false;
|
||||
bool infiniteItems = true;
|
||||
@ -54,11 +56,15 @@ class Player : public Serializable {
|
||||
bool loadingChunks = true;
|
||||
entityid_t eid;
|
||||
entityid_t selectedEid = 0;
|
||||
|
||||
glm::vec3 rotation {};
|
||||
public:
|
||||
util::VecInterpolation<3, float, true> rotationInterpolation {true};
|
||||
|
||||
std::unique_ptr<Chunks> chunks;
|
||||
std::shared_ptr<Camera> fpCamera, spCamera, tpCamera;
|
||||
std::shared_ptr<Camera> currentCamera;
|
||||
glm::vec3 rotation {};
|
||||
|
||||
CursorSelection selection {};
|
||||
|
||||
Player(
|
||||
@ -85,6 +91,9 @@ public:
|
||||
int getChosenSlot() const;
|
||||
float getSpeed() const;
|
||||
|
||||
bool isSuspended() const;
|
||||
void setSuspended(bool flag);
|
||||
|
||||
bool isFlight() const;
|
||||
void setFlight(bool flag);
|
||||
|
||||
@ -119,6 +128,9 @@ public:
|
||||
void setSpawnPoint(glm::vec3 point);
|
||||
glm::vec3 getSpawnPoint() const;
|
||||
|
||||
glm::vec3 getRotation(bool interpolated=false) const;
|
||||
void setRotation(const glm::vec3& rotation);
|
||||
|
||||
dv::value serialize() const override;
|
||||
void deserialize(const dv::value& src) override;
|
||||
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
#include "items/Inventories.hpp"
|
||||
#include "world/Level.hpp"
|
||||
#include "world/World.hpp"
|
||||
#include "objects/Entities.hpp"
|
||||
|
||||
Players::Players(Level& level) : level(level) {}
|
||||
|
||||
@ -19,10 +20,19 @@ Player* Players::get(int64_t id) const {
|
||||
return found->second.get();
|
||||
}
|
||||
|
||||
Player* Players::create() {
|
||||
Player* Players::create(int64_t id) {
|
||||
int64_t& nextPlayerID = level.getWorld()->getInfo().nextPlayerId;
|
||||
if (id == NONE) {
|
||||
id = nextPlayerID++;
|
||||
} else {
|
||||
if (auto player = get(id)) {
|
||||
return player;
|
||||
}
|
||||
nextPlayerID = std::max(id + 1, nextPlayerID);
|
||||
}
|
||||
auto playerPtr = std::make_unique<Player>(
|
||||
level,
|
||||
level.getWorld()->getInfo().nextPlayerId++,
|
||||
id,
|
||||
"",
|
||||
glm::vec3(0, DEF_PLAYER_Y, 0),
|
||||
DEF_PLAYER_SPEED,
|
||||
@ -36,6 +46,26 @@ Player* Players::create() {
|
||||
return player;
|
||||
}
|
||||
|
||||
void Players::suspend(int64_t id) {
|
||||
if (auto player = get(id)) {
|
||||
if (player->isSuspended()) {
|
||||
return;
|
||||
}
|
||||
player->setSuspended(true);
|
||||
level.entities->despawn(player->getEntity());
|
||||
player->setEntity(0);
|
||||
}
|
||||
}
|
||||
|
||||
void Players::resume(int64_t id) {
|
||||
if (auto player = get(id)) {
|
||||
if (!player->isSuspended()) {
|
||||
return;
|
||||
}
|
||||
player->setSuspended(false);
|
||||
}
|
||||
}
|
||||
|
||||
void Players::remove(int64_t id) {
|
||||
players.erase(id);
|
||||
}
|
||||
|
||||
@ -14,6 +14,9 @@ class Level;
|
||||
class Player;
|
||||
|
||||
class Players : public Serializable {
|
||||
public:
|
||||
static inline int64_t NONE = -1;
|
||||
private:
|
||||
Level& level;
|
||||
std::unordered_map<int64_t, std::unique_ptr<Player>> players;
|
||||
|
||||
@ -23,7 +26,11 @@ public:
|
||||
|
||||
Player* get(int64_t id) const;
|
||||
|
||||
Player* create();
|
||||
Player* create(int64_t id=NONE);
|
||||
|
||||
void suspend(int64_t id);
|
||||
|
||||
void resume(int64_t id);
|
||||
|
||||
void remove(int64_t id);
|
||||
|
||||
|
||||
@ -6,9 +6,8 @@
|
||||
#include "graphics/commons/Model.hpp"
|
||||
#include "graphics/render/ModelBatch.hpp"
|
||||
|
||||
#define GLM_ENABLE_EXPERIMENTAL
|
||||
#include <glm/ext/matrix_transform.hpp>
|
||||
#include <glm/gtx/norm.hpp>
|
||||
#include <glm/gtx/matrix_decompose.hpp>
|
||||
|
||||
using namespace rigging;
|
||||
|
||||
@ -69,7 +68,7 @@ SkeletonConfig::SkeletonConfig(
|
||||
}
|
||||
|
||||
size_t SkeletonConfig::update(
|
||||
size_t index, Skeleton& skeleton, Bone* node, glm::mat4 matrix
|
||||
size_t index, Skeleton& skeleton, Bone* node, const glm::mat4& matrix
|
||||
) const {
|
||||
auto boneMatrix = skeleton.pose.matrices[index];
|
||||
auto boneOffset = node->getOffset();
|
||||
@ -90,17 +89,32 @@ size_t SkeletonConfig::update(
|
||||
return count;
|
||||
}
|
||||
|
||||
void SkeletonConfig::update(Skeleton& skeleton, glm::mat4 matrix) const {
|
||||
update(0, skeleton, root.get(), matrix);
|
||||
void SkeletonConfig::update(
|
||||
Skeleton& skeleton, const glm::mat4& matrix, const glm::vec3& position
|
||||
) const {
|
||||
if (skeleton.interpolation.isEnabled()) {
|
||||
const auto& interpolation = skeleton.interpolation;
|
||||
glm::vec3 scale, translation, skew;
|
||||
glm::quat rotation;
|
||||
glm::vec4 perspective;
|
||||
glm::decompose(matrix, scale, rotation, translation, skew, perspective);
|
||||
|
||||
auto delta = interpolation.getCurrent() - position;
|
||||
auto interpolatedMatrix = glm::translate(matrix, delta);
|
||||
update(0, skeleton, root.get(), interpolatedMatrix);
|
||||
} else {
|
||||
update(0, skeleton, root.get(), matrix);
|
||||
}
|
||||
}
|
||||
|
||||
void SkeletonConfig::render(
|
||||
const Assets& assets,
|
||||
ModelBatch& batch,
|
||||
Skeleton& skeleton,
|
||||
const glm::mat4& matrix
|
||||
const glm::mat4& matrix,
|
||||
const glm::vec3& position
|
||||
) const {
|
||||
update(skeleton, matrix);
|
||||
update(skeleton, matrix, position);
|
||||
|
||||
if (!skeleton.visible) {
|
||||
return;
|
||||
|
||||
@ -1,12 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#define GLM_ENABLE_EXPERIMENTAL
|
||||
#include <glm/gtx/norm.hpp>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <cmath>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "typedefs.hpp"
|
||||
#include "util/Interpolation.hpp"
|
||||
|
||||
class Assets;
|
||||
class ModelBatch;
|
||||
@ -83,6 +86,8 @@ namespace rigging {
|
||||
bool visible;
|
||||
glm::vec3 tint {1.0f, 1.0f, 1.0f};
|
||||
|
||||
util::VecInterpolation<3, float> interpolation {false};
|
||||
|
||||
Skeleton(const SkeletonConfig* config);
|
||||
};
|
||||
|
||||
@ -100,7 +105,7 @@ namespace rigging {
|
||||
std::vector<Bone*> nodes;
|
||||
|
||||
size_t update(
|
||||
size_t index, Skeleton& skeleton, Bone* node, glm::mat4 matrix
|
||||
size_t index, Skeleton& skeleton, Bone* node, const glm::mat4& matrix
|
||||
) const;
|
||||
public:
|
||||
SkeletonConfig(
|
||||
@ -109,12 +114,18 @@ namespace rigging {
|
||||
size_t nodesCount
|
||||
);
|
||||
|
||||
void update(Skeleton& skeleton, glm::mat4 matrix) const;
|
||||
void update(
|
||||
Skeleton& skeleton,
|
||||
const glm::mat4& matrix,
|
||||
const glm::vec3& position
|
||||
) const;
|
||||
|
||||
void render(
|
||||
const Assets& assets,
|
||||
ModelBatch& batch,
|
||||
Skeleton& skeleton,
|
||||
const glm::mat4& matrix
|
||||
const glm::mat4& matrix,
|
||||
const glm::vec3& position
|
||||
) const;
|
||||
|
||||
Skeleton instance() const {
|
||||
|
||||
@ -14,7 +14,7 @@ namespace util {
|
||||
int nextid = 1;
|
||||
std::unordered_map<int, std::function<bool(Types...)>> handlers;
|
||||
std::vector<int> order;
|
||||
std::recursive_mutex mutex;
|
||||
std::mutex mutex;
|
||||
public:
|
||||
HandlersList() = default;
|
||||
|
||||
@ -46,11 +46,15 @@ namespace util {
|
||||
}
|
||||
|
||||
void notify(Types...args) {
|
||||
std::lock_guard lock(mutex);
|
||||
|
||||
auto orderCopy = order;
|
||||
std::vector<int> orderCopy;
|
||||
decltype(handlers) handlersCopy;
|
||||
{
|
||||
std::lock_guard lock(mutex);
|
||||
orderCopy = order;
|
||||
handlersCopy = handlers;
|
||||
}
|
||||
for (auto it = orderCopy.rbegin(); it != orderCopy.rend(); ++it) {
|
||||
if (handlers.at(*it)(args...)) {
|
||||
if (handlersCopy.at(*it)(args...)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
60
src/util/Interpolation.hpp
Normal file
60
src/util/Interpolation.hpp
Normal file
@ -0,0 +1,60 @@
|
||||
#pragma once
|
||||
|
||||
#include <limits>
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
namespace util {
|
||||
template<int N, typename T, bool angular=false>
|
||||
class VecInterpolation {
|
||||
bool enabled;
|
||||
glm::vec<N, T> prevPos {std::numeric_limits<T>::quiet_NaN()};
|
||||
glm::vec<N, T> nextPos {};
|
||||
T refreshInterval = 0.0;
|
||||
T timer = 0.0;
|
||||
T intervalUpdateFactor = 0.1;
|
||||
public:
|
||||
VecInterpolation(bool enabled) : enabled(enabled) {}
|
||||
|
||||
void refresh(const glm::vec<N, T>& position) {
|
||||
auto current = getCurrent();
|
||||
prevPos = current;
|
||||
nextPos = position;
|
||||
refreshInterval = timer * intervalUpdateFactor +
|
||||
refreshInterval * (1.0 - intervalUpdateFactor);
|
||||
timer = 0.0;
|
||||
|
||||
if constexpr (angular) {
|
||||
for (int i = 0; i < N; i++) {
|
||||
T diff = nextPos[i] - prevPos[i];
|
||||
if (glm::abs(diff) > 180.0f) {
|
||||
nextPos[i] += (diff > 0.0f ? -360.0f : 360.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void updateTimer(T delta) {
|
||||
timer += delta;
|
||||
}
|
||||
|
||||
glm::vec<N, T> getCurrent() const {
|
||||
if (refreshInterval < 0.001 || std::isnan(prevPos.x)) {
|
||||
return nextPos;
|
||||
}
|
||||
T t = timer / refreshInterval;
|
||||
return nextPos * t + prevPos * (1.0f - t);
|
||||
}
|
||||
|
||||
T getRefreshInterval() const {
|
||||
return refreshInterval;
|
||||
}
|
||||
|
||||
bool isEnabled() const {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
void setEnabled(bool enabled) {
|
||||
this->enabled = enabled;
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -18,8 +18,8 @@ uint Events::currentFrame = 0;
|
||||
int Events::scroll = 0;
|
||||
glm::vec2 Events::delta = {};
|
||||
glm::vec2 Events::cursor = {};
|
||||
bool Events::cursor_drag = false;
|
||||
bool Events::_cursor_locked = false;
|
||||
bool Events::cursorDrag = false;
|
||||
bool Events::cursorLocked = false;
|
||||
std::vector<uint> Events::codepoints;
|
||||
std::vector<keycode> Events::pressedKeys;
|
||||
std::unordered_map<std::string, Binding> Events::bindings;
|
||||
@ -61,10 +61,10 @@ bool Events::jclicked(int button) {
|
||||
}
|
||||
|
||||
void Events::toggleCursor() {
|
||||
cursor_drag = false;
|
||||
_cursor_locked = !_cursor_locked;
|
||||
cursorDrag = false;
|
||||
cursorLocked = !cursorLocked;
|
||||
Window::setCursorMode(
|
||||
_cursor_locked ? GLFW_CURSOR_DISABLED : GLFW_CURSOR_NORMAL
|
||||
cursorLocked ? GLFW_CURSOR_DISABLED : GLFW_CURSOR_NORMAL
|
||||
);
|
||||
}
|
||||
|
||||
@ -170,11 +170,11 @@ void Events::setButton(int button, bool b) {
|
||||
}
|
||||
|
||||
void Events::setPosition(float xpos, float ypos) {
|
||||
if (Events::cursor_drag) {
|
||||
if (Events::cursorDrag) {
|
||||
Events::delta.x += xpos - Events::cursor.x;
|
||||
Events::delta.y += ypos - Events::cursor.y;
|
||||
} else {
|
||||
Events::cursor_drag = true;
|
||||
Events::cursorDrag = true;
|
||||
}
|
||||
Events::cursor.x = xpos;
|
||||
Events::cursor.y = ypos;
|
||||
@ -249,3 +249,7 @@ void Events::enableBindings() {
|
||||
binding.enable = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool Events::isCursorLocked() {
|
||||
return cursorLocked;
|
||||
}
|
||||
|
||||
@ -19,12 +19,12 @@ class Events {
|
||||
static bool keys[KEYS_BUFFER_SIZE];
|
||||
static uint frames[KEYS_BUFFER_SIZE];
|
||||
static uint currentFrame;
|
||||
static bool cursor_drag;
|
||||
static bool cursorDrag;
|
||||
static bool cursorLocked;
|
||||
public:
|
||||
static int scroll;
|
||||
static glm::vec2 delta;
|
||||
static glm::vec2 cursor;
|
||||
static bool _cursor_locked;
|
||||
static std::vector<uint> codepoints;
|
||||
static std::vector<keycode> pressedKeys;
|
||||
static std::unordered_map<std::string, Binding> bindings;
|
||||
@ -65,4 +65,6 @@ public:
|
||||
BindType bindType
|
||||
);
|
||||
static void enableBindings();
|
||||
|
||||
static bool isCursorLocked();
|
||||
};
|
||||
|
||||
@ -389,7 +389,9 @@ void Window::toggleFullscreen() {
|
||||
GLFWmonitor* monitor = glfwGetPrimaryMonitor();
|
||||
const GLFWvidmode* mode = glfwGetVideoMode(monitor);
|
||||
|
||||
if (Events::_cursor_locked) Events::toggleCursor();
|
||||
if (Events::isCursorLocked()){
|
||||
Events::toggleCursor();
|
||||
}
|
||||
|
||||
if (fullscreen) {
|
||||
glfwGetWindowPos(window, &posX, &posY);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user