Merge branch 'MihailRis:main' into main

This commit is contained in:
Xertis 2025-01-18 15:54:34 +03:00 committed by GitHub
commit dc83e9b466
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
78 changed files with 930 additions and 254 deletions

View File

@ -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
)
```

View File

@ -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)
```

View File

@ -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)

View File

@ -56,6 +56,7 @@ Common element methods:
| ------------------- | ----------------------------------------------------------------------------------- |
| moveInto(container) | moves the element to the specified container (the element is specified, not the id) |
| destruct() | removes element |
| reposition() | updates the element position based on the `positionfunc` |
## Containers

View File

@ -19,3 +19,34 @@ Styles can be combined. Example:
Output:
***<ins>Message</ins>*** using *~~combed~~ combined* styles<ins>~~.~~</ins>
# Colors
Text color can be set using a color code: [#RRGGBB]
| Component | Purpose |
| --------- | --------------------------------- |
| R | Represents the intensity of red |
| G | Represents the intensity of green |
| B | Represents the intensity of blue |
### Example:
<span style="color: #ff0000">
<span style="color:rgb(105, 105, 105)">
[#ff0000]
</span>Red Text
</span>
<span style="color: #00ff00">
<span style="color:rgb(105, 105, 105)">
[#00ff00]
</span>Green Text
</span>
<span style="color: #0000ff">
<span style="color:rgb(105, 105, 105)">
[#0000ff]
</span>Blue Text
</span>

View File

@ -35,6 +35,7 @@ Examples:
- `margin` - element margin. Type: 4D vector
*left, top, right, bottom*
- `visible` - element visibility. Type: boolean (true/false)
- `min-size` - minimal element size. Type: 2D vector.
- `position-func` - position supplier for an element (two numbers), called on every parent container size update or on element adding on a container. May be called before *on_hud_open*
- `size-func` - element size provider (two numbers), called when the size of the container in which the element is located changes, or when an element is added to the container. Can be called before on_hud_open is called.
- `onclick` - lua function called when an element is clicked.
@ -65,6 +66,7 @@ Buttons and panels are also containers.
Buttons are also panels.
- `max-length` - maximal length of panel stretching before scrolling (if scrollable = true). Type: number
- `min-length` - minimal length of panel. Type: number
- `orientation` - panel orientation: horizontal/vertical.
# Common elements

View File

@ -77,12 +77,14 @@ app.is_content_loaded() -> bool
```lua
app.new_world(
-- название мира
-- название мира, пустая строка приведёт к созданию безымянного мира
name: str,
-- зерно генерации
seed: str,
-- название генератора
generator: str
-- id локального игрока
[опционально] local_player: int=0
)
```

View File

@ -65,4 +65,7 @@ hud.is_paused() -> bool
-- Возвращает true если открыт инвентарь или оверлей.
hud.is_inventory_open() -> bool
-- Устанавливает разрешение на паузу. При значении false меню паузы не приостанавливает игру.
hud.set_allow_pause(flag: bool)
```

View File

@ -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)

View File

@ -56,6 +56,7 @@ document["worlds-panel"]:clear()
| ------------------- | ----------------------------------------------------------------------- |
| moveInto(container) | перемещает элемент в указанный контейнер (указывается элемент, а не id) |
| destruct() | удаляет элемент |
| reposition() | обновляет позицию элемента на основе функции позиционирования |
## Контейнеры

View File

@ -19,3 +19,33 @@
Вывод:
***<ins>Сообщение</ins>***, демонстрирующее *~~обедненные~~ объединенные* стили<ins>~~.~~</ins>
## Цвета
Цвет текста задается при помощи цветового кода: [#RRGGBB]
| Компонент | Назначение |
| ------------ | ------------------------- |
| R | Используется для интенсивности красного |
| G | Используется для интенсивности зеленого |
| B | Используется для интенсивности синего |
### Например:
<span style="color: #ff0000">
<span style="color:rgb(105, 105, 105)">
[#ff0000]
</span>Красный Текст
</span>
<span style="color: #00ff00">
<span style="color:rgb(105, 105, 105)">
[#00ff00]
</span>Зеленый Текст
</span>
<span style="color: #0000ff">
<span style="color:rgb(105, 105, 105)">
[#0000ff]
</span>Синий Текст
</span>

View File

@ -39,6 +39,7 @@
- `margin` - внешний отступ элемента. Тип: 4D вектор.
Порядок: `"left,top,right,bottom"`
- `visible` - видимость элемента. Тип: логический ("true"/"false").
- `min-size` - минимальный размер элемента. Тип: 2D вектор.
- `position-func` - поставщик позиции элемента (два числа), вызываемый при изменении размера контейнера, в котором находится элемент, либо при добавлении элемента в контейнер. Может быть вызван до вызова on_hud_open.
- `size-func` - поставщик размера элемента (два числа), вызываемый при изменении размера контейнера, в котором находится элемент, либо при добавлении элемента в контейнер. Может быть вызван до вызова on_hud_open.
- `onclick` - lua функция вызываемая при нажатии на элемент.
@ -67,6 +68,7 @@
В число панелей также входят кнопки.
- `max-length` - максимальная длина, на которую растягивается панель до начала скроллинга (если scrollable = true). Тип: число
- `min-length` - минимальная длина панели. Тип: число
- `orientation` - ориентация панели: horizontal/vertical.
# Основные элементы

View File

@ -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)

View File

@ -134,6 +134,10 @@ function add_to_history(text)
end
function submit(text)
if #text == 0 then
document.prompt.focused = true
return
end
text = text:trim()
add_to_history(text)
@ -204,4 +208,11 @@ function on_open(mode)
elseif mode then
modes:set(mode)
end
hud.close("core:ingame_chat")
end
function on_close()
time.post_runnable(function()
hud.open_permanent("core:ingame_chat")
end)
end

View File

@ -0,0 +1,8 @@
<panel size="300,0" margin="0,0,0,70" max-length='300' min-length='0'
size-func="gui.get_viewport()[1]/2,-1"
padding="0"
min-size="0"
color="0"
interval="0"
gravity="bottom-left" interactive="false">
</panel>

View File

@ -0,0 +1,59 @@
local lines = {}
local dead_lines = {}
local nextid = 0
local timeout = 7
local fadeout = 1
local initialized = false
local max_lines = 15
local animation_fps = 30
local function remove_line(line)
document[line[1]]:destruct()
time.post_runnable(function() document.root:reposition() end)
end
local function update_line(line, uptime)
local diff = uptime - line[2]
if diff > timeout then
remove_line(line)
table.insert(dead_lines, i)
elseif diff > timeout-fadeout then
local opacity = (timeout - diff) / fadeout
document[line[1]].color = {0, 0, 0, opacity * 80}
document[line[1].."L"].color = {255, 255, 255, opacity * 255}
end
end
events.on("core:chat", function(message)
local current_time = time.uptime()
local id = 'l'..tostring(nextid)
document.root:add(gui.template("chat_line", {id=id}))
document.root:reposition()
document[id.."L"].text = message
nextid = nextid + 1
if #lines == max_lines then
remove_line(lines[1])
table.remove(lines, 1)
end
table.insert(lines, {id, current_time})
end)
function on_open()
if not initialized then
initialized = true
document.root:setInterval(1/animation_fps * 1000, function ()
local uptime = time.uptime()
for _, line in ipairs(lines) do
update_line(line, uptime)
end
if #dead_lines > 0 then
for i = #dead_lines, 1, -1 do
local index = dead_lines[i]
table.remove(lines, i)
end
dead_lines = {}
end
end)
end
end

View File

@ -37,4 +37,6 @@ end
function on_open()
refresh()
input.add_callback("key:f5", refresh, document.root)
end

View File

@ -0,0 +1,3 @@
<container id='%{id}' color="#00000050" size="-1,20">
<label pos='5' id='%{id}L' markup='md'/>
</container>

View File

@ -1,36 +0,0 @@
local gui_util = {}
--- Parse `pagename?arg1=value1&arg2=value2` queries
--- @param query page query string
--- @return page_name, args_table
function gui_util.parse_query(query)
local args = {}
local name
local index = string.find(query, '?')
if index then
local argstr = string.sub(query, index + 1)
name = string.sub(query, 1, index - 1)
for key, value in string.gmatch(argstr, "([^=&]*)=([^&]*)") do
args[key] = value
end
else
name = query
end
return name, args
end
--- @param query page query string
--- @return document_id
function gui_util.load_page(query)
local name, args = gui_util.parse_query(query)
local filename = file.find(string.format("layouts/pages/%s.xml", name))
if filename then
name = file.prefix(filename)..":pages/"..name
gui.load_document(filename, name, args)
return name
end
end
return gui_util

View File

@ -0,0 +1,107 @@
local gui_util = {
local_dispatchers = {}
}
--- Parse `pagename?arg1=value1&arg2=value2` queries
--- @param query page query string
--- @return page_name, args_table
function gui_util.parse_query(query)
local args = {}
local name
local index = string.find(query, '?')
if index then
local argstr = string.sub(query, index + 1)
name = string.sub(query, 1, index - 1)
for key, value in string.gmatch(argstr, "([^=&]*)=([^&]*)") do
args[key] = value
end
else
name = query
end
return name, args
end
--- @param query page query string
--- @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
gui.load_document(filename, name, args)
return name
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
-- class designed for simple UI-nodes access via properties syntax
local Element = {}
function Element.new(docname, name)
return setmetatable({docname=docname, name=name}, {
__index=function(self, k)
return gui.getattr(self.docname, self.name, k)
end,
__newindex=function(self, k, v)
gui.setattr(self.docname, self.name, k, v)
end
})
end
-- the engine automatically creates an instance for every ui document (layout)
local Document = {}
function Document.new(docname)
return setmetatable({name=docname}, {
__index=function(self, k)
local elem = Element.new(self.name, k)
rawset(self, k, elem)
return elem
end
})
end
local RadioGroup = {}
function RadioGroup:set(key)
if type(self) ~= 'table' then
error("called as non-OOP via '.', use radiogroup:set")
end
if self.current then
self.elements[self.current].enabled = true
end
self.elements[key].enabled = false
self.current = key
if self.callback then
self.callback(key)
end
end
function RadioGroup:__call(elements, onset, default)
local group = setmetatable({
elements=elements,
callback=onset,
current=nil
}, {__index=self})
group:set(default)
return group
end
setmetatable(RadioGroup, RadioGroup)
gui_util.Document = Document
gui_util.RadioGroup = RadioGroup
return gui_util

View File

@ -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 = {}

View File

@ -260,7 +260,7 @@ console.add_command(
"chat text:str",
"Send chat message",
function (args, kwargs)
console.log("[you] "..args[1])
console.chat("[you] "..args[1])
end
)

View File

@ -146,64 +146,16 @@ function events.emit(event, ...)
return result
end
-- class designed for simple UI-nodes access via properties syntax
local Element = {}
function Element.new(docname, name)
return setmetatable({docname=docname, name=name}, {
__index=function(self, k)
return gui.getattr(self.docname, self.name, k)
end,
__newindex=function(self, k, v)
gui.setattr(self.docname, self.name, k, v)
end
})
end
gui_util = require "core:internal/gui_util"
-- the engine automatically creates an instance for every ui document (layout)
Document = {}
function Document.new(docname)
return setmetatable({name=docname}, {
__index=function(self, k)
local elem = Element.new(self.name, k)
rawset(self, k, elem)
return elem
end
})
end
local _RadioGroup = {}
function _RadioGroup:set(key)
if type(self) ~= 'table' then
error("called as non-OOP via '.', use radiogroup:set")
end
if self.current then
self.elements[self.current].enabled = true
end
self.elements[key].enabled = false
self.current = key
if self.callback then
self.callback(key)
end
end
function _RadioGroup:__call(elements, onset, default)
local group = setmetatable({
elements=elements,
callback=onset,
current=nil
}, {__index=_RadioGroup})
group:set(default)
return group
end
setmetatable(_RadioGroup, _RadioGroup)
RadioGroup = _RadioGroup
Document = gui_util.Document
RadioGroup = gui_util.RadioGroup
__vc_page_loader = gui_util.load_page
_GUI_ROOT = Document.new("core:root")
_MENU = _GUI_ROOT.menu
menu = _MENU
local gui_util = require "core:gui_util"
__vc_page_loader = gui_util.load_page
--- Console library extension ---
console.cheats = {}
@ -225,6 +177,11 @@ function console.log(...)
log_element:paste(text)
end
function console.chat(...)
console.log(...)
events.emit("core:chat", ...)
end
function gui.template(name, params)
local text = file.read(file.find("layouts/templates/"..name..".xml"))
for k,v in pairs(params) do
@ -385,6 +342,16 @@ 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)
hud.open_permanent("core:ingame_chat")
end
local RULES_FILE = "world:rules.toml"
@ -408,6 +375,7 @@ end
function __vc_on_world_quit()
_rules.clear()
gui_util:reset_local()
end
local __vc_coroutines = {}
@ -470,7 +438,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

View File

@ -76,8 +76,7 @@ std::shared_ptr<ContentReport> ContentReport::create(
}
auto root = files::read_json(filename);
// TODO: remove default value 2 in 0.24
uint regionsVersion = 2U;
uint regionsVersion = 2U; // old worlds compatibility (pre 0.23)
root.at("region-version").get(regionsVersion);
auto& blocklist = root["blocks"];
auto& itemlist = root["items"];

View File

@ -217,6 +217,7 @@ void Engine::renderFrame() {
Viewport viewport(Window::width, Window::height);
DrawContext ctx(nullptr, viewport, nullptr);
gui->draw(ctx, *assets);
gui->postAct();
}
void Engine::saveSettings() {
@ -272,7 +273,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 +447,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() {

View File

@ -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();

View File

@ -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
));
}
});

View File

@ -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));
});

View File

@ -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;

View File

@ -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;
}

View File

@ -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

View File

@ -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()) {

View File

@ -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;

View File

@ -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;
}
}

View File

@ -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);

View File

@ -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))
{
@ -178,18 +178,12 @@ void GUI::actFocused() {
}
void GUI::act(float delta, const Viewport& vp) {
while (!postRunnables.empty()) {
runnable callback = postRunnables.back();
postRunnables.pop();
callback();
}
container->setSize(vp.size());
container->act(delta);
auto prevfocus = focus;
updateTooltip(delta);
if (!Events::_cursor_locked) {
if (!Events::isCursorLocked()) {
actMouse(delta);
} else {
if (hover) {
@ -206,6 +200,14 @@ void GUI::act(float delta, const Viewport& vp) {
}
}
void GUI::postAct() {
while (!postRunnables.empty()) {
runnable callback = postRunnables.back();
postRunnables.pop();
callback();
}
}
void GUI::draw(const DrawContext& pctx, const Assets& assets) {
auto ctx = pctx.sub(batch2D.get());

View File

@ -106,6 +106,8 @@ namespace gui {
/// @param assets active assets storage
void draw(const DrawContext& pctx, const Assets& assets);
void postAct();
/// @brief Add element to the main container
/// @param node UI element
void add(std::shared_ptr<UINode> node);

View File

@ -91,6 +91,10 @@ Page& Menu::getCurrent() {
return current;
}
bool Menu::hasOpenPage() const {
return current.panel != nullptr;
}
void Menu::clearHistory() {
pageStack = std::stack<Page>();
}

View File

@ -64,5 +64,7 @@ namespace gui {
/// @brief Get current page
Page& getCurrent();
bool hasOpenPage() const;
};
}

View File

@ -23,6 +23,14 @@ int Panel::getMaxLength() const {
return maxLength;
}
void Panel::setMinLength(int value) {
minLength = value;
}
int Panel::getMinLength() const {
return minLength;
}
void Panel::setPadding(glm::vec4 padding) {
this->padding = padding;
refresh();
@ -34,9 +42,11 @@ glm::vec4 Panel::getPadding() const {
void Panel::cropToContent() {
if (maxLength > 0.0f) {
setSize(glm::vec2(getSize().x, glm::min(maxLength, actualLength)));
setSize(glm::vec2(
getSize().x, glm::max(minLength, glm::min(maxLength, actualLength))
));
} else {
setSize(glm::vec2(getSize().x, actualLength));
setSize(glm::vec2(getSize().x, glm::max(minLength, actualLength)));
}
}
@ -52,6 +62,11 @@ void Panel::add(const std::shared_ptr<UINode> &node) {
fullRefresh();
}
void Panel::remove(const std::shared_ptr<UINode> &node) {
Container::remove(node);
fullRefresh();
}
void Panel::refresh() {
UINode::refresh();
std::stable_sort(nodes.begin(), nodes.end(), [](auto a, auto b) {

View File

@ -9,6 +9,7 @@ namespace gui {
Orientation orientation = Orientation::vertical;
glm::vec4 padding {2.0f};
float interval = 2.0f;
int minLength = 0;
int maxLength = 0;
public:
Panel(
@ -24,6 +25,7 @@ namespace gui {
Orientation getOrientation() const;
virtual void add(const std::shared_ptr<UINode>& node) override;
virtual void remove(const std::shared_ptr<UINode>& node) override;
virtual void refresh() override;
virtual void fullRefresh() override;
@ -31,6 +33,9 @@ namespace gui {
virtual void setMaxLength(int value);
int getMaxLength() const;
virtual void setMinLength(int value);
int getMinLength() const;
virtual void setPadding(glm::vec4 padding);
glm::vec4 getPadding() const;
};

View File

@ -289,12 +289,16 @@ const std::string& UINode::getId() const {
}
void UINode::reposition() {
if (sizefunc) {
auto newSize = sizefunc();
setSize(
{newSize.x < 0 ? size.x : newSize.x,
newSize.y < 0 ? size.y : newSize.y}
);
}
if (positionfunc) {
setPos(positionfunc());
}
if (sizefunc) {
setSize(sizefunc());
}
}
void UINode::setGravity(Gravity gravity) {

View File

@ -91,6 +91,9 @@ static void _readUINode(
if (element.has("pos")) {
node.setPos(element.attr("pos").asVec2());
}
if (element.has("min-size")) {
node.setMinSize(element.attr("min-size").asVec2());
}
if (element.has("size")) {
node.setSize(element.attr("size").asVec2());
}
@ -220,6 +223,9 @@ static void _readPanel(UiXmlReader& reader, const xml::xmlelement& element, Pane
if (element.has("max-length")) {
panel.setMaxLength(element.attr("max-length").asInt());
}
if (element.has("min-length")) {
panel.setMinLength(element.attr("min-length").asInt());
}
if (element.has("orientation")) {
auto &oname = element.attr("orientation").getText();
if (oname == "horizontal") {

View File

@ -1,5 +1,6 @@
#include "markdown.hpp"
#include "coders/commons.hpp"
#include "graphics/core/Font.hpp"
using namespace markdown;
@ -9,7 +10,7 @@ static inline void emit(
CharT c, FontStylesScheme& styles, std::basic_stringstream<CharT>& ss
) {
ss << c;
styles.map.emplace_back(styles.palette.size()-1);
styles.map.emplace_back(styles.palette.size() - 1);
}
template <typename CharT>
@ -20,6 +21,38 @@ static inline void emit_md(
styles.map.emplace_back(0);
}
template <typename CharT>
static glm::vec4 parse_color(const std::basic_string_view<CharT>& color_code) {
if (color_code.size() != 8 || color_code[0] != '#') {
return glm::vec4(1, 1, 1, 1); // default to white
}
auto hex_to_float = [](char high, char low) {
int high_val = hexchar2int(high);
int low_val = hexchar2int(low);
if (high_val == -1 || low_val == -1) {
return 1.0f; // default to max value on error
}
return (high_val * 16 + low_val) / 255.0f;
};
return glm::vec4(
hex_to_float(color_code[1], color_code[2]),
hex_to_float(color_code[3], color_code[4]),
hex_to_float(color_code[5], color_code[6]),
1
);
}
template <typename CharT>
static inline void apply_color(
const std::basic_string_view<CharT>& color_code, FontStylesScheme& styles
) {
FontStyle style = styles.palette.back();
style.color = parse_color(color_code);
styles.palette.push_back(style);
}
template <typename CharT>
static inline void restyle(
CharT c,
@ -43,13 +76,14 @@ Result<CharT> process_markdown(
std::basic_stringstream<CharT> ss;
FontStylesScheme styles {
// markdown default
{{false, false, false, false, glm::vec4(1,1,1,0.5f)}, {}},
{{false, false, false, false, glm::vec4(1, 1, 1, 0.5f)}, {}},
{}
};
FontStyle style;
int pos = 0;
while (pos < source.size()) {
CharT first = source[pos];
if (first == '\\') {
if (pos + 1 < source.size()) {
CharT second = source[++pos];
@ -63,14 +97,37 @@ Result<CharT> process_markdown(
emit(second, styles, ss);
pos++;
continue;
case '[':
if (pos + 9 < source.size() && source[pos + 1] == '#' &&
source[pos + 8] == ']') {
if (!eraseMarkdown) {
emit_md(source[pos - 1], styles, ss);
}
for (int i = 0; i < 10; ++i) {
emit(source[pos + i], styles, ss);
}
pos += 10;
continue;
}
}
pos--;
}
} else if (first == '[' && pos + 9 <= source.size() && source[pos + 1] == '#' && source[pos + 8] == ']') {
std::basic_string_view<CharT> color_code = source.substr(pos + 1, 8);
apply_color(color_code, styles);
if (!eraseMarkdown) {
for (int i = 0; i < 9; ++i) {
emit_md(source[pos + i], styles, ss);
}
}
pos += 9; // Skip past the color code
continue;
} else if (first == '*') {
if (pos + 1 < source.size() && source[pos+1] == '*') {
if (pos + 1 < source.size() && source[pos + 1] == '*') {
pos++;
if (!eraseMarkdown)
emit_md(first, styles, ss);
if (!eraseMarkdown) emit_md(first, styles, ss);
style.bold = !style.bold;
restyle(first, style, styles, ss, pos, eraseMarkdown);
continue;
@ -78,17 +135,17 @@ Result<CharT> process_markdown(
style.italic = !style.italic;
restyle(first, style, styles, ss, pos, eraseMarkdown);
continue;
} else if (first == '_' && pos + 1 < source.size() && source[pos+1] == '_') {
} else if (first == '_' && pos + 1 < source.size() &&
source[pos + 1] == '_') {
pos++;
if (!eraseMarkdown)
emit_md(first, styles, ss);
if (!eraseMarkdown) emit_md(first, styles, ss);
style.underline = !style.underline;
restyle(first, style, styles, ss, pos, eraseMarkdown);
continue;
} else if (first == '~' && pos + 1 < source.size() && source[pos+1] == '~') {
} else if (first == '~' && pos + 1 < source.size() &&
source[pos + 1] == '~') {
pos++;
if (!eraseMarkdown)
emit_md(first, styles, ss);
if (!eraseMarkdown) emit_md(first, styles, ss);
style.strikethrough = !style.strikethrough;
restyle(first, style, styles, ss, pos, eraseMarkdown);
continue;
@ -106,6 +163,8 @@ Result<char> markdown::process(std::string_view source, bool eraseMarkdown) {
return process_markdown(source, eraseMarkdown);
}
Result<wchar_t> markdown::process(std::wstring_view source, bool eraseMarkdown) {
Result<wchar_t> markdown::process(
std::wstring_view source, bool eraseMarkdown
) {
return process_markdown(source, eraseMarkdown);
}

View File

@ -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++){

View File

@ -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++) {

View File

@ -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) {

View File

@ -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);
};

View File

@ -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()) {

View File

@ -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)) {

View File

@ -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;
}

View File

@ -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}};

View File

@ -198,8 +198,8 @@ static int l_tpack(lua::State* L) {
}
const luaL_Reg byteutillib[] = {
{"pack", l_pack},
{"tpack", l_tpack},
{"unpack", l_unpack},
{"pack", lua::wrap<l_pack>},
{"tpack", lua::wrap<l_tpack>},
{"unpack", lua::wrap<l_unpack>},
{NULL, NULL}
};

View File

@ -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;
}

View File

@ -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>},

View File

@ -101,6 +101,12 @@ static int l_node_destruct(lua::State* L) {
return 0;
}
static int l_node_reposition(lua::State* L) {
auto docnode = get_document_node(L);
docnode.node->reposition();
return 0;
}
static int l_container_clear(lua::State* L) {
auto node = get_document_node(L, 1);
if (auto container = std::dynamic_pointer_cast<Container>(node.node)) {
@ -328,6 +334,10 @@ static int p_get_destruct(UINode*, lua::State* L) {
return lua::pushcfunction(L, lua::wrap<l_node_destruct>);
}
static int p_get_reposition(UINode*, lua::State* L) {
return lua::pushcfunction(L, lua::wrap<l_node_reposition>);
}
static int p_get_clear(UINode* node, lua::State* L) {
if (dynamic_cast<Container*>(node)) {
return lua::pushcfunction(L, lua::wrap<l_container_clear>);
@ -424,6 +434,7 @@ static int l_gui_getattr(lua::State* L) {
{"moveInto", p_move_into},
{"add", p_get_add},
{"destruct", p_get_destruct},
{"reposition", p_get_reposition},
{"clear", p_get_clear},
{"setInterval", p_set_interval},
{"placeholder", p_get_placeholder},

View File

@ -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}
};

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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>},

View File

@ -160,13 +160,13 @@ static void integrate_chunk_client(Chunk& chunk) {
static int l_set_chunk_data(lua::State* L) {
int x = static_cast<int>(lua::tointeger(L, 1));
int z = static_cast<int>(lua::tointeger(L, 2));
auto buffer = lua::touserdata<lua::LuaBytearray>(L, 3);
auto buffer = lua::require_bytearray(L, 3);
auto chunk = level->chunks->getChunk(x, z);
if (chunk == nullptr) {
return 0;
return lua::pushboolean(L, false);
}
compressed_chunks::decode(
*chunk, buffer->data().data(), buffer->data().size()
*chunk, buffer.data(), buffer.size()
);
if (controller->getChunksController()->lighting == nullptr) {
return lua::pushboolean(L, true);

View File

@ -133,6 +133,13 @@ dv::value lua::tovalue(State* L, int idx) {
return map;
}
}
case LUA_TUSERDATA: {
if (auto bytes = touserdata<LuaBytearray>(L, idx)) {
const auto& data = bytes->data();
return std::make_shared<dv::objects::Bytes>(data.data(), data.size());
}
[[fallthrough]];
}
default:
throw std::runtime_error(
"lua type " + std::string(lua_typename(L, type)) +

View File

@ -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);

View File

@ -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();

View File

@ -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;

View File

@ -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);
}
}
}

View File

@ -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();
};

View File

@ -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();

View File

@ -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;

View File

@ -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);
}

View File

@ -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);

View File

@ -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;

View File

@ -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 {

View File

@ -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;
}
}

View 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;
}
};
}

View File

@ -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;
}

View File

@ -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();
};

View File

@ -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);