Merge branch 'main' into release-0.26

This commit is contained in:
MihailRis 2025-01-19 20:17:58 +03:00
commit 8c884846f1
81 changed files with 950 additions and 269 deletions

View File

@ -77,12 +77,14 @@ Checks if content is loaded.
```lua ```lua
app.new_world( app.new_world(
-- world name -- world name, empty string will create a nameless world
name: str, name: str,
-- generation seed -- generation seed
seed: str, seed: str,
-- generator name -- generator name
generator: str 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. -- Returns true if inventory is open or overlay is shown.
hud.is_inventory_open() -> bool 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 Sets x, y, z player linear velocity
```lua ```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 ```lua
player.set_rot(playerid: int, x: number, y: number, z: number) 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) | | moveInto(container) | moves the element to the specified container (the element is specified, not the id) |
| destruct() | removes element | | destruct() | removes element |
| reposition() | updates the element position based on the `positionfunc` |
## Containers ## Containers

View File

@ -19,3 +19,34 @@ Styles can be combined. Example:
Output: Output:
***<ins>Message</ins>*** using *~~combed~~ combined* styles<ins>~~.~~</ins> ***<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 - `margin` - element margin. Type: 4D vector
*left, top, right, bottom* *left, top, right, bottom*
- `visible` - element visibility. Type: boolean (true/false) - `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* - `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. - `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. - `onclick` - lua function called when an element is clicked.
@ -65,6 +66,7 @@ Buttons and panels are also containers.
Buttons are also panels. Buttons are also panels.
- `max-length` - maximal length of panel stretching before scrolling (if scrollable = true). Type: number - `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. - `orientation` - panel orientation: horizontal/vertical.
# Common elements # Common elements

View File

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

View File

@ -65,4 +65,7 @@ hud.is_paused() -> bool
-- Возвращает true если открыт инвентарь или оверлей. -- Возвращает true если открыт инвентарь или оверлей.
hud.is_inventory_open() -> bool 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 линейной скорости игрока Устанавливает x, y, z линейной скорости игрока
```lua ```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 ```lua
player.set_rot(playerid: int, x: number, y: number, z: number) player.set_rot(playerid: int, x: number, y: number, z: number)

View File

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

View File

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

View File

@ -3,7 +3,9 @@ local body = entity.rigidbody
local rig = entity.skeleton local rig = entity.skeleton
local itemid = 0 local itemid = 0
local headIndex = rig:index("head")
local itemIndex = rig:index("item") local itemIndex = rig:index("item")
local bodyIndex = rig:index("body")
local function refresh_model(id) local function refresh_model(id)
itemid = id itemid = id
@ -12,7 +14,16 @@ local function refresh_model(id)
end end
function on_render() 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) local id, _ = inventory.get(invid, slotid)
if id ~= itemid then if id ~= itemid then
refresh_model(id) refresh_model(id)

View File

@ -134,6 +134,10 @@ function add_to_history(text)
end end
function submit(text) function submit(text)
if #text == 0 then
document.prompt.focused = true
return
end
text = text:trim() text = text:trim()
add_to_history(text) add_to_history(text)
@ -204,4 +208,11 @@ function on_open(mode)
elseif mode then elseif mode then
modes:set(mode) modes:set(mode)
end end
hud.close("core:ingame_chat")
end
function on_close()
time.post_runnable(function()
hud.open_permanent("core:ingame_chat")
end)
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() function on_open()
refresh() refresh()
input.add_callback("key:f5", refresh, document.root)
end 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, 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, 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_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) local function new_Skeleton(eid)
@ -66,6 +67,7 @@ local Entity = {__index={
get_uid=function(self) return self.eid end, get_uid=function(self) return self.eid end,
def_index=function(self) return entities.get_def(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, 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 = {} local entities = {}

View File

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

View File

@ -146,64 +146,16 @@ function events.emit(event, ...)
return result return result
end end
-- class designed for simple UI-nodes access via properties syntax gui_util = require "core:internal/gui_util"
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) Document = gui_util.Document
Document = {} RadioGroup = gui_util.RadioGroup
function Document.new(docname) __vc_page_loader = gui_util.load_page
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
_GUI_ROOT = Document.new("core:root") _GUI_ROOT = Document.new("core:root")
_MENU = _GUI_ROOT.menu _MENU = _GUI_ROOT.menu
menu = _MENU menu = _MENU
local gui_util = require "core:gui_util"
__vc_page_loader = gui_util.load_page
--- Console library extension --- --- Console library extension ---
console.cheats = {} console.cheats = {}
@ -225,6 +177,11 @@ function console.log(...)
log_element:paste(text) log_element:paste(text)
end end
function console.chat(...)
console.log(...)
events.emit("core:chat", ...)
end
function gui.template(name, params) function gui.template(name, params)
local text = file.read(file.find("layouts/templates/"..name..".xml")) local text = file.read(file.find("layouts/templates/"..name..".xml"))
for k,v in pairs(params) do for k,v in pairs(params) do
@ -385,6 +342,16 @@ function __vc_on_hud_open()
hud.show_overlay("core:console", false, {"chat"}) hud.show_overlay("core:console", false, {"chat"})
end) end)
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 end
local RULES_FILE = "world:rules.toml" local RULES_FILE = "world:rules.toml"
@ -408,20 +375,15 @@ end
function __vc_on_world_quit() function __vc_on_world_quit()
_rules.clear() _rules.clear()
gui_util:reset_local()
end end
local __vc_coroutines = {} local __vc_coroutines = {}
local __vc_named_coroutines = {} local __vc_named_coroutines = {}
local __vc_next_coroutine = 1 local __vc_next_coroutine = 1
local __vc_coroutine_error = nil
function __vc_start_coroutine(chunk) function __vc_start_coroutine(chunk)
local co = coroutine.create(function() local co = coroutine.create(chunk)
local status, err = pcall(chunk)
if not status then
__vc_coroutine_error = err
end
end)
local id = __vc_next_coroutine local id = __vc_next_coroutine
__vc_next_coroutine = __vc_next_coroutine + 1 __vc_next_coroutine = __vc_next_coroutine + 1
__vc_coroutines[id] = co __vc_coroutines[id] = co
@ -431,10 +393,10 @@ end
function __vc_resume_coroutine(id) function __vc_resume_coroutine(id)
local co = __vc_coroutines[id] local co = __vc_coroutines[id]
if co then if co then
coroutine.resume(co) local success, err = coroutine.resume(co)
if __vc_coroutine_error then if not success then
debug.error(__vc_coroutine_error) debug.error(err)
error(__vc_coroutine_error) error(err)
end end
return coroutine.status(co) ~= "dead" return coroutine.status(co) ~= "dead"
end end
@ -476,7 +438,10 @@ function __process_post_runnables()
local dead = {} local dead = {}
for name, co in pairs(__vc_named_coroutines) do 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 if coroutine.status(co) == "dead" then
table.insert(dead, name) table.insert(dead, name)
end end

View File

@ -20,6 +20,9 @@ void main() {
if (alpha < 0.2f) if (alpha < 0.2f)
discard; discard;
alpha = 1.0; alpha = 1.0;
} else {
if (alpha < 0.002f)
discard;
} }
f_color = mix(a_color * tex_color, vec4(fogColor,1.0), f_color = mix(a_color * tex_color, vec4(fogColor,1.0),
min(1.0, pow(depth*u_fogFactor, u_fogCurve))); min(1.0, pow(depth*u_fogFactor, u_fogCurve)));

View File

@ -113,8 +113,9 @@ public:
auto start = currentLocation(); auto start = currentLocation();
if (is_lua_identifier_start(c)) { if (is_lua_identifier_start(c)) {
auto name = parseLuaName(); auto name = parseLuaName();
TokenTag tag = (is_lua_keyword(name) ? TokenTag::KEYWORD : TokenTag::NAME);
emitToken( emitToken(
is_lua_keyword(name) ? TokenTag::KEYWORD : TokenTag::NAME, tag,
std::move(name), std::move(name),
start start
); );

View File

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

View File

@ -44,6 +44,9 @@ StructLayout StructLayout::create(const std::vector<Field>& fields) {
} }
std::sort(builtFields.begin(), builtFields.end(), std::sort(builtFields.begin(), builtFields.end(),
[](const Field& a, const Field& b) { [](const Field& a, const Field& b) {
if (a.size == b.size) {
return a.name < b.name;
}
return a.size > b.size; return a.size > b.size;
} }
); );

View File

@ -217,6 +217,7 @@ void Engine::renderFrame() {
Viewport viewport(Window::width, Window::height); Viewport viewport(Window::width, Window::height);
DrawContext ctx(nullptr, viewport, nullptr); DrawContext ctx(nullptr, viewport, nullptr);
gui->draw(ctx, *assets); gui->draw(ctx, *assets);
gui->postAct();
} }
void Engine::saveSettings() { void Engine::saveSettings() {
@ -272,7 +273,7 @@ PacksManager Engine::createPacksManager(const fs::path& worldFolder) {
return manager; return manager;
} }
void Engine::setLevelConsumer(consumer<std::unique_ptr<Level>> levelConsumer) { void Engine::setLevelConsumer(OnWorldOpen levelConsumer) {
this->levelConsumer = std::move(levelConsumer); this->levelConsumer = std::move(levelConsumer);
} }
@ -446,14 +447,14 @@ void Engine::setLanguage(std::string locale) {
langs::setup(paths.getResourcesFolder(), std::move(locale), contentPacks); 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"; logger.info() << "world open";
levelConsumer(std::move(level)); levelConsumer(std::move(level), localPlayer);
} }
void Engine::onWorldClosed() { void Engine::onWorldClosed() {
logger.info() << "world closed"; logger.info() << "world closed";
levelConsumer(nullptr); levelConsumer(nullptr, -1);
} }
void Engine::quit() { void Engine::quit() {

View File

@ -53,6 +53,8 @@ struct CoreParameters {
std::filesystem::path scriptFile; std::filesystem::path scriptFile;
}; };
using OnWorldOpen = std::function<void(std::unique_ptr<Level>, int64_t)>;
class Engine : public util::ObjectsKeeper { class Engine : public util::ObjectsKeeper {
CoreParameters params; CoreParameters params;
EngineSettings settings; EngineSettings settings;
@ -71,7 +73,7 @@ class Engine : public util::ObjectsKeeper {
std::unique_ptr<gui::GUI> gui; std::unique_ptr<gui::GUI> gui;
PostRunnables postRunnables; PostRunnables postRunnables;
Time time; Time time;
consumer<std::unique_ptr<Level>> levelConsumer; OnWorldOpen levelConsumer;
bool quitSignal = false; bool quitSignal = false;
void loadControls(); void loadControls();
@ -134,7 +136,7 @@ public:
/// @brief Get engine resource paths controller /// @brief Get engine resource paths controller
ResPaths* getResPaths(); ResPaths* getResPaths();
void onWorldOpen(std::unique_ptr<Level> level); void onWorldOpen(std::unique_ptr<Level> level, int64_t localPlayer);
void onWorldClosed(); void onWorldClosed();
void quit(); void quit();
@ -166,7 +168,7 @@ public:
PacksManager createPacksManager(const fs::path& worldFolder); PacksManager createPacksManager(const fs::path& worldFolder);
void setLevelConsumer(consumer<std::unique_ptr<Level>> levelConsumer); void setLevelConsumer(OnWorldOpen levelConsumer);
SettingsHandler& getSettingsHandler(); SettingsHandler& getSettingsHandler();

View File

@ -15,14 +15,16 @@ Mainloop::Mainloop(Engine& engine) : engine(engine) {
void Mainloop::run() { void Mainloop::run() {
auto& time = engine.getTime(); auto& time = engine.getTime();
engine.setLevelConsumer([this](auto level) { engine.setLevelConsumer([this](auto level, int64_t localPlayer) {
if (level == nullptr) { if (level == nullptr) {
// destroy LevelScreen and run quit callbacks // destroy LevelScreen and run quit callbacks
engine.setScreen(nullptr); engine.setScreen(nullptr);
// create and go to menu screen // create and go to menu screen
engine.setScreen(std::make_shared<MenuScreen>(engine)); engine.setScreen(std::make_shared<MenuScreen>(engine));
} else { } 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"; logger.info() << "nothing to do";
return; return;
} }
engine.setLevelConsumer([this](auto level) { engine.setLevelConsumer([this](auto level, auto) {
setLevel(std::move(level)); setLevel(std::move(level));
}); });

View File

@ -13,7 +13,9 @@
#include "graphics/render/ParticlesRenderer.hpp" #include "graphics/render/ParticlesRenderer.hpp"
#include "graphics/render/ChunksRenderer.hpp" #include "graphics/render/ChunksRenderer.hpp"
#include "logic/scripting/scripting.hpp" #include "logic/scripting/scripting.hpp"
#include "network/Network.hpp"
#include "objects/Player.hpp" #include "objects/Player.hpp"
#include "objects/Players.hpp"
#include "objects/Entities.hpp" #include "objects/Entities.hpp"
#include "objects/EntityDef.hpp" #include "objects/EntityDef.hpp"
#include "physics/Hitbox.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
// TODO: move to xml finally // TODO: move to xml finally
// TODO: move to xml finally
std::shared_ptr<UINode> create_debug_panel( std::shared_ptr<UINode> create_debug_panel(
Engine& engine, Engine& engine,
Level& level, Level& level,
@ -56,6 +59,10 @@ std::shared_ptr<UINode> create_debug_panel(
static int fpsMax = fps; static int fpsMax = fps;
static std::wstring fpsString = L""; 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]() { panel->listenInterval(0.016f, [&engine]() {
fps = 1.0f / engine.getTime().getDelta(); fps = 1.0f / engine.getTime().getDelta();
fpsMin = std::min(fps, fpsMin); fpsMin = std::min(fps, fpsMin);
@ -67,6 +74,19 @@ std::shared_ptr<UINode> create_debug_panel(
fpsMin = fps; fpsMin = fps;
fpsMax = 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([]() { return L"fps: "+fpsString;}));
panel->add(create_label([]() { panel->add(create_label([]() {
@ -84,6 +104,7 @@ std::shared_ptr<UINode> create_debug_panel(
panel->add(create_label([]() { panel->add(create_label([]() {
return L"lua-stack: " + std::to_wstring(scripting::get_values_on_stack()); return L"lua-stack: " + std::to_wstring(scripting::get_values_on_stack());
})); }));
panel->add(create_label([]() { return netSpeedString; }));
panel->add(create_label([&engine]() { panel->add(create_label([&engine]() {
auto& settings = engine.getSettings(); auto& settings = engine.getSettings();
bool culling = settings.graphics.frustumCulling.get(); 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: "+ return L"entities: "+std::to_wstring(level.entities->size())+L" next: "+
std::to_wstring(level.entities->peekNextID()); 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 { panel->add(create_label([&]() -> std::wstring {
const auto& vox = player.selection.vox; const auto& vox = player.selection.vox;
std::wstringstream stream; 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'/>" "' pos='0' size='256' gravity='top-right' margin='0,20,0,0'/>"
); );
add(HudElement(hud_element_mode::permanent, nullptr, debugMinimap, true)); 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() { Hud::~Hud() {
@ -234,7 +223,8 @@ void Hud::cleanup() {
} }
void Hud::processInput(bool visible) { void Hud::processInput(bool visible) {
if (!Window::isFocused() && !pause && !isInventoryOpen()) { auto menu = gui.getMenu();
if (!Window::isFocused() && !menu->hasOpenPage() && !isInventoryOpen()) {
setPause(true); setPause(true);
} }
if (!pause && visible && Events::jactive(BIND_HUD_INVENTORY)) { if (!pause && visible && Events::jactive(BIND_HUD_INVENTORY)) {
@ -329,14 +319,13 @@ void Hud::update(bool visible) {
if (!visible && inventoryOpen) { if (!visible && inventoryOpen) {
closeInventory(); closeInventory();
} }
if (pause && menu->getCurrent().panel == nullptr) { if (pause && !menu->hasOpenPage()) {
setPause(false); setPause(false);
} }
if (!gui.isFocusCaught()) { if (!gui.isFocusCaught()) {
processInput(visible); processInput(visible);
} }
if ((pause || inventoryOpen) == Events::_cursor_locked) { if ((menu->hasOpenPage() || inventoryOpen) == Events::isCursorLocked()) {
Events::toggleCursor(); Events::toggleCursor();
} }
@ -601,7 +590,9 @@ void Hud::draw(const DrawContext& ctx){
const Viewport& viewport = ctx.getViewport(); const Viewport& viewport = ctx.getViewport();
const uint width = viewport.getWidth(); const uint width = viewport.getWidth();
const uint height = viewport.getHeight(); const uint height = viewport.getHeight();
auto menu = gui.getMenu();
darkOverlay->setVisible(menu->hasOpenPage());
updateElementsPosition(viewport); updateElementsPosition(viewport);
uicamera->setFov(height); uicamera->setFov(height);
@ -687,19 +678,20 @@ void Hud::setPause(bool pause) {
if (this->pause == pause) { if (this->pause == pause) {
return; return;
} }
this->pause = pause; if (allowPause) {
this->pause = pause;
}
if (inventoryOpen) { if (inventoryOpen) {
closeInventory(); closeInventory();
} }
const auto& menu = gui.getMenu(); const auto& menu = gui.getMenu();
if (pause) { if (menu->hasOpenPage()) {
menu->setPage("pause");
} else {
menu->reset(); menu->reset();
} else {
menu->setPage("pause");
} }
darkOverlay->setVisible(pause);
menu->setVisible(pause); menu->setVisible(pause);
} }
@ -732,3 +724,12 @@ void Hud::setDebugCheats(bool flag) {
debugPanel->setZIndex(2); debugPanel->setZIndex(2);
gui.add(debugPanel); 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; bool showContentPanel = true;
/// @brief Provide cheat controllers to the debug panel /// @brief Provide cheat controllers to the debug panel
bool allowDebugCheats = true; bool allowDebugCheats = true;
/// @brief Allow actual pause
bool allowPause = true;
bool debug = false; bool debug = false;
/// @brief UI element will be dynamicly positioned near to inventory or in screen center /// @brief UI element will be dynamicly positioned near to inventory or in screen center
std::shared_ptr<gui::UINode> secondUI; std::shared_ptr<gui::UINode> secondUI;
@ -206,6 +208,8 @@ public:
void setDebugCheats(bool flag); void setDebugCheats(bool flag);
void setAllowPause(bool flag);
static bool showGeneratorMinimap; static bool showGeneratorMinimap;
/// @brief Runtime updating debug visualization texture /// @brief Runtime updating debug visualization texture

View File

@ -36,9 +36,10 @@
static debug::Logger logger("level-screen"); static debug::Logger logger("level-screen");
LevelScreen::LevelScreen(Engine& engine, std::unique_ptr<Level> levelPtr) LevelScreen::LevelScreen(
: Screen(engine), postProcessing(std::make_unique<PostProcessing>()) Engine& engine, std::unique_ptr<Level> levelPtr, int64_t localPlayer
{ )
: Screen(engine), postProcessing(std::make_unique<PostProcessing>()) {
Level* level = levelPtr.get(); Level* level = levelPtr.get();
auto& settings = engine.getSettings(); auto& settings = engine.getSettings();
@ -46,7 +47,9 @@ LevelScreen::LevelScreen(Engine& engine, std::unique_ptr<Level> levelPtr)
auto menu = engine.getGUI()->getMenu(); auto menu = engine.getGUI()->getMenu();
menu->reset(); menu->reset();
auto player = level->players->get(0); auto player = level->players->get(localPlayer);
assert(player != nullptr);
controller = controller =
std::make_unique<LevelController>(&engine, std::move(levelPtr), player); std::make_unique<LevelController>(&engine, std::move(levelPtr), player);
playerController = std::make_unique<PlayerController>( playerController = std::make_unique<PlayerController>(
@ -165,14 +168,16 @@ void LevelScreen::updateHotkeys() {
void LevelScreen::update(float delta) { void LevelScreen::update(float delta) {
gui::GUI* gui = engine.getGUI(); gui::GUI* gui = engine.getGUI();
auto menu = gui->getMenu();
bool inputLocked = hud->isPause() || bool inputLocked = menu->hasOpenPage() ||
hud->isInventoryOpen() || hud->isInventoryOpen() ||
gui->isFocusCaught(); gui->isFocusCaught();
if (!gui->isFocusCaught()) { if (!gui->isFocusCaught()) {
updateHotkeys(); updateHotkeys();
} }
auto level = controller->getLevel();
auto player = playerController->getPlayer(); auto player = playerController->getPlayer();
auto camera = player->currentCamera; auto camera = player->currentCamera;
@ -189,7 +194,6 @@ void LevelScreen::update(float delta) {
camera->dir, camera->dir,
glm::vec3(0, 1, 0) glm::vec3(0, 1, 0)
); );
auto level = controller->getLevel();
const auto& settings = engine.getSettings(); const auto& settings = engine.getSettings();
if (!hud->isPause()) { if (!hud->isPause()) {

View File

@ -34,7 +34,9 @@ class LevelScreen : public Screen {
void initializeContent(); void initializeContent();
void initializePack(ContentPackRuntime* pack); void initializePack(ContentPackRuntime* pack);
public: public:
LevelScreen(Engine& engine, std::unique_ptr<Level> level); LevelScreen(
Engine& engine, std::unique_ptr<Level> level, int64_t localPlayer
);
~LevelScreen(); ~LevelScreen();
void update(float delta) override; void update(float delta) override;

View File

@ -12,6 +12,7 @@
#include "window/Camera.hpp" #include "window/Camera.hpp"
#include "objects/Player.hpp" #include "objects/Player.hpp"
#include "objects/Players.hpp" #include "objects/Players.hpp"
#include "objects/Entities.hpp"
#include "logic/LevelController.hpp" #include "logic/LevelController.hpp"
#include "util/stringutil.hpp" #include "util/stringutil.hpp"
#include "engine/Engine.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 note = renderer.texts->get(textsIter->second);
auto player = level.players->get(textsIter->first); auto player = level.players->get(textsIter->first);
if (player == nullptr) { if (player == nullptr) {
renderer.texts->remove(textsIter->second);
textsIter = playerTexts.erase(textsIter); textsIter = playerTexts.erase(textsIter);
} else { } 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; ++textsIter;
} }
} }

View File

@ -274,8 +274,9 @@ void WorldRenderer::renderHands(
glm::mat4(1.0f), -glm::pi<float>() * 0.5f, glm::vec3(0, 1, 0) glm::mat4(1.0f), -glm::pi<float>() * 0.5f, glm::vec3(0, 1, 0)
); );
prevRotation = rotation; prevRotation = rotation;
glm::vec3 cameraRotation = player.getRotation();
auto offset = -(camera.position - player.getPosition()); 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 cos = glm::cos(angle);
float sin = glm::sin(angle); float sin = glm::sin(angle);

View File

@ -166,7 +166,7 @@ void GUI::actFocused() {
focus->keyPressed(key); focus->keyPressed(key);
} }
if (!Events::_cursor_locked) { if (!Events::isCursorLocked()) {
if (Events::clicked(mousecode::BUTTON_1) && if (Events::clicked(mousecode::BUTTON_1) &&
(Events::jclicked(mousecode::BUTTON_1) || Events::delta.x || Events::delta.y)) (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) { void GUI::act(float delta, const Viewport& vp) {
while (!postRunnables.empty()) {
runnable callback = postRunnables.back();
postRunnables.pop();
callback();
}
container->setSize(vp.size()); container->setSize(vp.size());
container->act(delta); container->act(delta);
auto prevfocus = focus; auto prevfocus = focus;
updateTooltip(delta); updateTooltip(delta);
if (!Events::_cursor_locked) { if (!Events::isCursorLocked()) {
actMouse(delta); actMouse(delta);
} else { } else {
if (hover) { 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) { void GUI::draw(const DrawContext& pctx, const Assets& assets) {
auto ctx = pctx.sub(batch2D.get()); auto ctx = pctx.sub(batch2D.get());

View File

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

View File

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

View File

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

View File

@ -23,6 +23,14 @@ int Panel::getMaxLength() const {
return maxLength; return maxLength;
} }
void Panel::setMinLength(int value) {
minLength = value;
}
int Panel::getMinLength() const {
return minLength;
}
void Panel::setPadding(glm::vec4 padding) { void Panel::setPadding(glm::vec4 padding) {
this->padding = padding; this->padding = padding;
refresh(); refresh();
@ -34,9 +42,11 @@ glm::vec4 Panel::getPadding() const {
void Panel::cropToContent() { void Panel::cropToContent() {
if (maxLength > 0.0f) { 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 { } 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(); fullRefresh();
} }
void Panel::remove(const std::shared_ptr<UINode> &node) {
Container::remove(node);
fullRefresh();
}
void Panel::refresh() { void Panel::refresh() {
UINode::refresh(); UINode::refresh();
std::stable_sort(nodes.begin(), nodes.end(), [](auto a, auto b) { std::stable_sort(nodes.begin(), nodes.end(), [](auto a, auto b) {

View File

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

View File

@ -289,12 +289,16 @@ const std::string& UINode::getId() const {
} }
void UINode::reposition() { 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) { if (positionfunc) {
setPos(positionfunc()); setPos(positionfunc());
} }
if (sizefunc) {
setSize(sizefunc());
}
} }
void UINode::setGravity(Gravity gravity) { void UINode::setGravity(Gravity gravity) {

View File

@ -91,6 +91,9 @@ static void _readUINode(
if (element.has("pos")) { if (element.has("pos")) {
node.setPos(element.attr("pos").asVec2()); node.setPos(element.attr("pos").asVec2());
} }
if (element.has("min-size")) {
node.setMinSize(element.attr("min-size").asVec2());
}
if (element.has("size")) { if (element.has("size")) {
node.setSize(element.attr("size").asVec2()); 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")) { if (element.has("max-length")) {
panel.setMaxLength(element.attr("max-length").asInt()); panel.setMaxLength(element.attr("max-length").asInt());
} }
if (element.has("min-length")) {
panel.setMinLength(element.attr("min-length").asInt());
}
if (element.has("orientation")) { if (element.has("orientation")) {
auto &oname = element.attr("orientation").getText(); auto &oname = element.attr("orientation").getText();
if (oname == "horizontal") { if (oname == "horizontal") {

View File

@ -1,5 +1,6 @@
#include "markdown.hpp" #include "markdown.hpp"
#include "coders/commons.hpp"
#include "graphics/core/Font.hpp" #include "graphics/core/Font.hpp"
using namespace markdown; using namespace markdown;
@ -9,7 +10,7 @@ static inline void emit(
CharT c, FontStylesScheme& styles, std::basic_stringstream<CharT>& ss CharT c, FontStylesScheme& styles, std::basic_stringstream<CharT>& ss
) { ) {
ss << c; ss << c;
styles.map.emplace_back(styles.palette.size()-1); styles.map.emplace_back(styles.palette.size() - 1);
} }
template <typename CharT> template <typename CharT>
@ -20,6 +21,38 @@ static inline void emit_md(
styles.map.emplace_back(0); 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> template <typename CharT>
static inline void restyle( static inline void restyle(
CharT c, CharT c,
@ -43,13 +76,14 @@ Result<CharT> process_markdown(
std::basic_stringstream<CharT> ss; std::basic_stringstream<CharT> ss;
FontStylesScheme styles { FontStylesScheme styles {
// markdown default // 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; FontStyle style;
int pos = 0; int pos = 0;
while (pos < source.size()) { while (pos < source.size()) {
CharT first = source[pos]; CharT first = source[pos];
if (first == '\\') { if (first == '\\') {
if (pos + 1 < source.size()) { if (pos + 1 < source.size()) {
CharT second = source[++pos]; CharT second = source[++pos];
@ -63,14 +97,37 @@ Result<CharT> process_markdown(
emit(second, styles, ss); emit(second, styles, ss);
pos++; pos++;
continue; 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--; 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 == '*') { } else if (first == '*') {
if (pos + 1 < source.size() && source[pos+1] == '*') { if (pos + 1 < source.size() && source[pos + 1] == '*') {
pos++; pos++;
if (!eraseMarkdown) if (!eraseMarkdown) emit_md(first, styles, ss);
emit_md(first, styles, ss);
style.bold = !style.bold; style.bold = !style.bold;
restyle(first, style, styles, ss, pos, eraseMarkdown); restyle(first, style, styles, ss, pos, eraseMarkdown);
continue; continue;
@ -78,17 +135,17 @@ Result<CharT> process_markdown(
style.italic = !style.italic; style.italic = !style.italic;
restyle(first, style, styles, ss, pos, eraseMarkdown); restyle(first, style, styles, ss, pos, eraseMarkdown);
continue; continue;
} else if (first == '_' && pos + 1 < source.size() && source[pos+1] == '_') { } else if (first == '_' && pos + 1 < source.size() &&
source[pos + 1] == '_') {
pos++; pos++;
if (!eraseMarkdown) if (!eraseMarkdown) emit_md(first, styles, ss);
emit_md(first, styles, ss);
style.underline = !style.underline; style.underline = !style.underline;
restyle(first, style, styles, ss, pos, eraseMarkdown); restyle(first, style, styles, ss, pos, eraseMarkdown);
continue; continue;
} else if (first == '~' && pos + 1 < source.size() && source[pos+1] == '~') { } else if (first == '~' && pos + 1 < source.size() &&
source[pos + 1] == '~') {
pos++; pos++;
if (!eraseMarkdown) if (!eraseMarkdown) emit_md(first, styles, ss);
emit_md(first, styles, ss);
style.strikethrough = !style.strikethrough; style.strikethrough = !style.strikethrough;
restyle(first, style, styles, ss, pos, eraseMarkdown); restyle(first, style, styles, ss, pos, eraseMarkdown);
continue; continue;
@ -106,6 +163,8 @@ Result<char> markdown::process(std::string_view source, bool eraseMarkdown) {
return process_markdown(source, 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); return process_markdown(source, eraseMarkdown);
} }

View File

@ -8,9 +8,12 @@
#include "voxels/Block.hpp" #include "voxels/Block.hpp"
#include "constants.hpp" #include "constants.hpp"
#include "util/timeutil.hpp" #include "util/timeutil.hpp"
#include "debug/Logger.hpp"
#include <memory> #include <memory>
static debug::Logger logger("lighting");
Lighting::Lighting(const Content& content, Chunks& chunks) Lighting::Lighting(const Content& content, Chunks& chunks)
: content(content), chunks(chunks) { : content(content), chunks(chunks) {
auto& indices = *content.getIndices(); auto& indices = *content.getIndices();
@ -63,6 +66,10 @@ void Lighting::buildSkyLight(int cx, int cz){
const auto blockDefs = content.getIndices()->blocks.getDefs(); const auto blockDefs = content.getIndices()->blocks.getDefs();
Chunk* chunk = chunks.getChunk(cx, cz); 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 z = 0; z < CHUNK_D; z++){
for (int x = 0; x < CHUNK_W; x++){ for (int x = 0; x < CHUNK_W; x++){
int gx = x + cx * CHUNK_W; 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 blockDefs = content.getIndices()->blocks.getDefs();
auto chunk = chunks.getChunk(cx, cz); 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 y = 0; y < CHUNK_H; y++){
for (uint z = 0; z < CHUNK_D; z++){ for (uint z = 0; z < CHUNK_D; z++){
for (uint x = 0; x < CHUNK_W; x++){ 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]; const auto& chunk = chunks.getChunks()[nearZ * sizeX + nearX];
if (chunk != nullptr || !assigned) { if (chunk != nullptr || !assigned || !player.isLoadingChunks()) {
return false; return false;
} }
int offsetX = chunks.getOffsetX(); int offsetX = chunks.getOffsetX();
@ -101,7 +101,9 @@ bool ChunksController::loadVisible(const Player& player, uint padding) const {
return true; 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; int surrounding = 0;
for (int oz = -1; oz <= 1; oz++) { for (int oz = -1; oz <= 1; oz++) {
for (int ox = -1; ox <= 1; ox++) { 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( static void load_world(
Engine& engine, const std::shared_ptr<WorldFiles>& worldFiles Engine& engine,
const std::shared_ptr<WorldFiles>& worldFiles,
int64_t localPlayer
) { ) {
try { try {
auto content = engine.getContent(); auto content = engine.getContent();
@ -151,7 +153,7 @@ static void load_world(
auto& settings = engine.getSettings(); auto& settings = engine.getSettings();
auto level = World::load(worldFiles, settings, *content, packs); 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) { } catch (const world_load_error& error) {
guiutil::alert( guiutil::alert(
engine, engine,
@ -233,7 +235,7 @@ void EngineController::openWorld(const std::string& name, bool confirmConvert) {
} }
return; return;
} }
load_world(engine, std::move(worldFiles)); load_world(engine, std::move(worldFiles), localPlayer);
} }
inline uint64_t str2seed(const std::string& seedstr) { inline uint64_t str2seed(const std::string& seedstr) {
@ -279,9 +281,13 @@ void EngineController::createWorld(
engine.getContentPacks() engine.getContentPacks()
); );
if (!engine.isHeadless()) { 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) { void EngineController::reopenWorld(World* world) {

View File

@ -12,6 +12,7 @@ class LevelController;
class EngineController { class EngineController {
Engine& engine; Engine& engine;
int64_t localPlayer = -1;
void onMissingContent(const std::shared_ptr<ContentReport>& report); void onMissingContent(const std::shared_ptr<ContentReport>& report);
public: public:
EngineController(Engine& engine); EngineController(Engine& engine);
@ -37,5 +38,7 @@ public:
const std::string& generatorID const std::string& generatorID
); );
void setLocalPlayer(int64_t player);
void reopenWorld(World* world); void reopenWorld(World* world);
}; };

View File

@ -50,6 +50,10 @@ LevelController::LevelController(
do { do {
confirmed = 0; confirmed = 0;
for (const auto& [_, player] : *level->players) { for (const auto& [_, player] : *level->players) {
if (!player->isLoadingChunks()) {
confirmed++;
continue;
}
glm::vec3 position = player->getPosition(); glm::vec3 position = player->getPosition();
player->chunks->configure( player->chunks->configure(
std::floor(position.x), std::floor(position.z), 1 std::floor(position.x), std::floor(position.z), 1
@ -66,6 +70,11 @@ LevelController::LevelController(
void LevelController::update(float delta, bool pause) { void LevelController::update(float delta, bool pause) {
for (const auto& [_, player] : *level->players) { for (const auto& [_, player] : *level->players) {
if (player->isSuspended()) {
continue;
}
player->rotationInterpolation.updateTimer(delta);
player->updateEntity();
glm::vec3 position = player->getPosition(); glm::vec3 position = player->getPosition();
player->chunks->configure( player->chunks->configure(
position.x, position.x,
@ -85,6 +94,9 @@ void LevelController::update(float delta, bool pause) {
level->entities->updatePhysics(delta); level->entities->updatePhysics(delta);
level->entities->update(delta); level->entities->update(delta);
for (const auto& [_, player] : *level->players) { for (const auto& [_, player] : *level->players) {
if (player->isSuspended()) {
continue;
}
if (playerTickClock.update(delta)) { if (playerTickClock.update(delta)) {
if (player->getId() % playerTickClock.getParts() == if (player->getId() % playerTickClock.getParts() ==
playerTickClock.getPart()) { playerTickClock.getPart()) {

View File

@ -54,7 +54,7 @@ void CameraControl::refresh() {
} }
void CameraControl::updateMouse(PlayerInput& input) { void CameraControl::updateMouse(PlayerInput& input) {
glm::vec3& rotation = player.rotation; glm::vec3 rotation = player.getRotation();
float sensitivity = float sensitivity =
(input.zoom ? settings.sensitivity.get() / 4.f (input.zoom ? settings.sensitivity.get() / 4.f
@ -75,6 +75,8 @@ void CameraControl::updateMouse(PlayerInput& input) {
rotation.x += 360.f; rotation.x += 360.f;
} }
player.setRotation(rotation);
camera->rotation = glm::mat4(1.0f); camera->rotation = glm::mat4(1.0f);
camera->rotate( camera->rotate(
glm::radians(rotation.y), glm::radians(rotation.y),
@ -170,8 +172,8 @@ void CameraControl::update(
switchCamera(); switchCamera();
} }
auto& spCamera = player.spCamera; const auto& spCamera = player.spCamera;
auto& tpCamera = player.tpCamera; const auto& tpCamera = player.tpCamera;
refresh(); refresh();
@ -305,7 +307,6 @@ void PlayerController::resetKeyboard() {
} }
void PlayerController::updatePlayer(float delta) { void PlayerController::updatePlayer(float delta) {
player.updateEntity();
player.updateInput(input, delta); player.updateInput(input, delta);
} }
@ -317,15 +318,15 @@ static int determine_rotation(
if (name == "pipe") { if (name == "pipe") {
if (norm.x < 0.0f) if (norm.x < 0.0f)
return BLOCK_DIR_WEST; return BLOCK_DIR_WEST;
else if (norm.x > 0.0f) if (norm.x > 0.0f)
return BLOCK_DIR_EAST; return BLOCK_DIR_EAST;
else if (norm.y > 0.0f) if (norm.y > 0.0f)
return BLOCK_DIR_UP; return BLOCK_DIR_UP;
else if (norm.y < 0.0f) if (norm.y < 0.0f)
return BLOCK_DIR_DOWN; return BLOCK_DIR_DOWN;
else if (norm.z > 0.0f) if (norm.z > 0.0f)
return BLOCK_DIR_NORTH; return BLOCK_DIR_NORTH;
else if (norm.z < 0.0f) if (norm.z < 0.0f)
return BLOCK_DIR_SOUTH; return BLOCK_DIR_SOUTH;
} else if (name == "pane") { } else if (name == "pane") {
if (abs(camDir.x) > abs(camDir.z)) { 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) { static int l_is_enabled(lua::State* L) {
if (auto entity = get_entity(L, 1)) { if (auto entity = get_entity(L, 1)) {
lua::pushboolean(L, entity->getRigidbody().enabled); return lua::pushboolean(L, entity->getRigidbody().enabled);
} }
return 0; return 0;
} }

View File

@ -130,6 +130,22 @@ static int l_set_color(lua::State* L) {
return 0; 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[] = { const luaL_Reg skeletonlib[] = {
{"get_model", lua::wrap<l_get_model>}, {"get_model", lua::wrap<l_get_model>},
{"set_model", lua::wrap<l_set_model>}, {"set_model", lua::wrap<l_set_model>},
@ -142,4 +158,6 @@ const luaL_Reg skeletonlib[] = {
{"set_visible", lua::wrap<l_set_visible>}, {"set_visible", lua::wrap<l_set_visible>},
{"get_color", lua::wrap<l_get_color>}, {"get_color", lua::wrap<l_get_color>},
{"set_color", lua::wrap<l_set_color>}, {"set_color", lua::wrap<l_set_color>},
{"is_interpolated", lua::wrap<l_is_interpolated>},
{"set_interpolated", lua::wrap<l_set_interpolated>},
{NULL, NULL}}; {NULL, NULL}};

View File

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

View File

@ -55,10 +55,15 @@ static int l_new_world(lua::State* L) {
auto name = lua::require_string(L, 1); auto name = lua::require_string(L, 1);
auto seed = lua::require_string(L, 2); auto seed = lua::require_string(L, 2);
auto generator = lua::require_string(L, 3); auto generator = lua::require_string(L, 3);
int64_t localPlayer = 0;
if (lua::gettop(L) >= 4) {
localPlayer = lua::tointeger(L, 4);
}
if (level != nullptr) { if (level != nullptr) {
throw std::runtime_error("world must be closed before"); throw std::runtime_error("world must be closed before");
} }
auto controller = engine->getController(); auto controller = engine->getController();
controller->setLocalPlayer(localPlayer);
controller->createWorld(name, seed, generator); controller->createWorld(name, seed, generator);
return 0; return 0;
} }
@ -71,6 +76,7 @@ static int l_open_world(lua::State* L) {
throw std::runtime_error("world must be closed before"); throw std::runtime_error("world must be closed before");
} }
auto controller = engine->getController(); auto controller = engine->getController();
controller->setLocalPlayer(0);
controller->openWorld(name, false); controller->openWorld(name, false);
return 0; return 0;
} }

View File

@ -54,7 +54,7 @@ static int l_get_def(lua::State* L) {
return 0; return 0;
} }
static int l_show(lua::State* L) { static int l_spawn(lua::State* L) {
auto level = controller->getLevel(); auto level = controller->getLevel();
auto defname = lua::tostring(L, 1); auto defname = lua::tostring(L, 1);
auto& def = content->entities.require(defname); auto& def = content->entities.require(defname);
@ -81,6 +81,15 @@ static int l_get_skeleton(lua::State* L) {
return 0; 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) { static int l_set_skeleton(lua::State* L) {
if (auto entity = get_entity(L, 1)) { if (auto entity = get_entity(L, 1)) {
std::string skeletonName = lua::require_string(L, 2); std::string skeletonName = lua::require_string(L, 2);
@ -221,10 +230,11 @@ const luaL_Reg entitylib[] = {
{"def_hitbox", lua::wrap<l_def_hitbox>}, {"def_hitbox", lua::wrap<l_def_hitbox>},
{"get_def", lua::wrap<l_get_def>}, {"get_def", lua::wrap<l_get_def>},
{"defs_count", lua::wrap<l_defs_count>}, {"defs_count", lua::wrap<l_defs_count>},
{"spawn", lua::wrap<l_show>}, {"spawn", lua::wrap<l_spawn>},
{"despawn", lua::wrap<l_despawn>}, {"despawn", lua::wrap<l_despawn>},
{"get_skeleton", lua::wrap<l_get_skeleton>}, {"get_skeleton", lua::wrap<l_get_skeleton>},
{"set_skeleton", lua::wrap<l_set_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_box", lua::wrap<l_get_all_in_box>},
{"get_all_in_radius", lua::wrap<l_get_all_in_radius>}, {"get_all_in_radius", lua::wrap<l_get_all_in_radius>},
{"raycast", lua::wrap<l_raycast>}, {"raycast", lua::wrap<l_raycast>},

View File

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

View File

@ -170,6 +170,11 @@ static int l_set_debug_cheats(lua::State* L) {
return 0; return 0;
} }
static int l_set_allow_pause(lua::State* L) {
hud->setAllowPause(lua::toboolean(L, 1));
return 0;
}
const luaL_Reg hudlib[] = { const luaL_Reg hudlib[] = {
{"open_inventory", lua::wrap<l_open_inventory>}, {"open_inventory", lua::wrap<l_open_inventory>},
{"close_inventory", lua::wrap<l_close_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>}, {"_is_content_access", lua::wrap<l_is_content_access>},
{"_set_content_access", lua::wrap<l_set_content_access>}, {"_set_content_access", lua::wrap<l_set_content_access>},
{"_set_debug_cheats", lua::wrap<l_set_debug_cheats>}, {"_set_debug_cheats", lua::wrap<l_set_debug_cheats>},
{"set_allow_pause", lua::wrap<l_set_allow_pause>},
{NULL, NULL} {NULL, NULL}
}; };

View File

@ -45,11 +45,6 @@ static int l_add_callback(lua::State* L) {
handler = Events::keyCallbacks[key].add(actual_callback); 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 { auto callback = [=]() -> bool {
if (!scripting::engine->getGUI()->isFocusCaught()) { if (!scripting::engine->getGUI()->isFocusCaught()) {
return actual_callback(); return actual_callback();
@ -57,6 +52,10 @@ static int l_add_callback(lua::State* L) {
return false; return false;
}; };
if (handler == nullptr) { 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); 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) { static int l_close(lua::State* L) {
u64id_t id = lua::tointeger(L, 1); u64id_t id = lua::tointeger(L, 1);
if (auto connection = engine->getNetwork().getConnection(id)) { if (auto connection = engine->getNetwork().getConnection(id)) {
connection->close(); connection->close(true);
} }
return 0; return 0;
} }
@ -89,7 +89,8 @@ static int l_closeserver(lua::State* L) {
static int l_send(lua::State* L) { static int l_send(lua::State* L) {
u64id_t id = lua::tointeger(L, 1); u64id_t id = lua::tointeger(L, 1);
auto connection = engine->getNetwork().getConnection(id); auto connection = engine->getNetwork().getConnection(id);
if (connection == nullptr) { if (connection == nullptr ||
connection->getState() == network::ConnectionState::CLOSED) {
return 0; return 0;
} }
if (lua::istable(L, 2)) { if (lua::istable(L, 2)) {
@ -167,7 +168,9 @@ static int l_is_alive(lua::State* L) {
u64id_t id = lua::tointeger(L, 1); u64id_t id = lua::tointeger(L, 1);
if (auto connection = engine->getNetwork().getConnection(id)) { if (auto connection = engine->getNetwork().getConnection(id)) {
return lua::pushboolean( return lua::pushboolean(
L, connection->getState() != network::ConnectionState::CLOSED L,
connection->getState() != network::ConnectionState::CLOSED ||
connection->available() > 0
); );
} }
return lua::pushboolean(L, false); return lua::pushboolean(L, false);

View File

@ -52,6 +52,7 @@ static int l_set_vel(lua::State* L) {
auto x = lua::tonumber(L, 2); auto x = lua::tonumber(L, 2);
auto y = lua::tonumber(L, 3); auto y = lua::tonumber(L, 3);
auto z = lua::tonumber(L, 4); auto z = lua::tonumber(L, 4);
if (auto hitbox = player->getHitbox()) { if (auto hitbox = player->getHitbox()) {
hitbox->velocity = glm::vec3(x, y, z); 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) { static int l_get_rot(lua::State* L) {
if (auto player = get_player(L, 1)) { 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; return 0;
} }
@ -70,7 +71,7 @@ static int l_set_rot(lua::State* L) {
if (!player) { if (!player) {
return 0; return 0;
} }
glm::vec3& rotation = player->rotation; glm::vec3 rotation = player->getRotation();
auto x = lua::tonumber(L, 2); auto x = lua::tonumber(L, 2);
auto y = lua::tonumber(L, 3); auto y = lua::tonumber(L, 3);
@ -81,6 +82,7 @@ static int l_set_rot(lua::State* L) {
rotation.x = x; rotation.x = x;
rotation.y = y; rotation.y = y;
rotation.z = z; rotation.z = z;
player->setRotation(rotation);
return 0; return 0;
} }
@ -272,15 +274,33 @@ static int l_set_name(lua::State* L) {
} }
static int l_create(lua::State* L) { static int l_create(lua::State* L) {
auto player = level->players->create(); int64_t playerId = Players::NONE;
if (lua::isstring(L, 1)) { if (lua::gettop(L) >= 2) {
player->setName(lua::require_string(L, 1)); playerId = lua::tointeger(L, 2);
} }
auto player = level->players->create(playerId);
player->setName(lua::require_string(L, 1));
return lua::pushinteger(L, player->getId()); return lua::pushinteger(L, player->getId());
} }
static int l_delete(lua::State* L) { 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; return 0;
} }
@ -293,6 +313,8 @@ const luaL_Reg playerlib[] = {
{"set_rot", lua::wrap<l_set_rot>}, {"set_rot", lua::wrap<l_set_rot>},
{"get_dir", lua::wrap<l_get_dir>}, {"get_dir", lua::wrap<l_get_dir>},
{"get_inventory", lua::wrap<l_get_inv>}, {"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>}, {"is_flight", lua::wrap<l_is_flight>},
{"set_flight", lua::wrap<l_set_flight>}, {"set_flight", lua::wrap<l_set_flight>},
{"is_noclip", lua::wrap<l_is_noclip>}, {"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) { static int l_set_chunk_data(lua::State* L) {
int x = static_cast<int>(lua::tointeger(L, 1)); int x = static_cast<int>(lua::tointeger(L, 1));
int z = static_cast<int>(lua::tointeger(L, 2)); 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); auto chunk = level->chunks->getChunk(x, z);
if (chunk == nullptr) { if (chunk == nullptr) {
return 0; return lua::pushboolean(L, false);
} }
compressed_chunks::decode( compressed_chunks::decode(
*chunk, buffer->data().data(), buffer->data().size() *chunk, buffer.data(), buffer.size()
); );
if (controller->getChunksController()->lighting == nullptr) { if (controller->getChunksController()->lighting == nullptr) {
return lua::pushboolean(L, true); return lua::pushboolean(L, true);

View File

@ -133,6 +133,13 @@ dv::value lua::tovalue(State* L, int idx) {
return map; 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: default:
throw std::runtime_error( throw std::runtime_error(
"lua type " + std::string(lua_typename(L, type)) + "lua type " + std::string(lua_typename(L, type)) +
@ -270,12 +277,13 @@ scripting::common_func lua::create_lambda(State* L) {
return [=](const std::vector<dv::value>& args) -> dv::value { return [=](const std::vector<dv::value>& args) -> dv::value {
if (!get_from(L, LAMBDAS_TABLE, *funcptr, false)) if (!get_from(L, LAMBDAS_TABLE, *funcptr, false))
return nullptr; return nullptr;
int top = gettop(L) + 1; int top = gettop(L) - 1;
for (const auto& arg : args) { for (const auto& arg : args) {
pushvalue(L, arg); pushvalue(L, arg);
} }
if (call(L, args.size(), 1)) { if (call(L, args.size(), 1)) {
int nres = gettop(L) - top; int nres = gettop(L) - top;
assert(nres >= 0);
if (nres) { if (nres) {
auto result = tovalue(L, -1); auto result = tovalue(L, -1);
pop(L, 1 + nres); pop(L, 1 + nres);

View File

@ -19,6 +19,7 @@ namespace lua {
int userdata_destructor(lua::State* L); int userdata_destructor(lua::State* L);
std::string env_name(int env); std::string env_name(int env);
void dump_stack(lua::State*);
inline bool getglobal(lua::State* L, const std::string& name) { inline bool getglobal(lua::State* L, const std::string& name) {
lua_getglobal(L, name.c_str()); lua_getglobal(L, name.c_str());
@ -208,7 +209,7 @@ namespace lua {
return lua_isnumber(L, idx); return lua_isnumber(L, idx);
} }
inline bool isstring(lua::State* L, int 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) { inline bool istable(lua::State* L, int idx) {
return lua_istable(L, idx); return lua_istable(L, idx);
@ -226,6 +227,11 @@ namespace lua {
return lua_toboolean(L, idx); return lua_toboolean(L, idx);
} }
inline lua::Integer tointeger(lua::State* L, int 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); return lua_tointeger(L, idx);
} }
inline uint64_t touinteger(lua::State* L, int idx) { inline uint64_t touinteger(lua::State* L, int idx) {
@ -236,6 +242,11 @@ namespace lua {
return static_cast<uint64_t>(val); return static_cast<uint64_t>(val);
} }
inline lua::Number tonumber(lua::State* L, int idx) { 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); return lua_tonumber(L, idx);
} }
inline const char* tostring(lua::State* L, int idx) { inline const char* tostring(lua::State* L, int idx) {
@ -588,7 +599,6 @@ namespace lua {
} }
int create_environment(lua::State*, int parent); int create_environment(lua::State*, int parent);
void remove_environment(lua::State*, int id); void remove_environment(lua::State*, int id);
void dump_stack(lua::State*);
inline void close(lua::State* L) { inline void close(lua::State* L) {
lua_close(L); lua_close(L);

View File

@ -400,10 +400,15 @@ public:
return readBatch.size(); return readBatch.size();
} }
void close() override { void close(bool discardAll=false) override {
if (state != ConnectionState::CLOSED) { {
shutdown(descriptor, 2); std::lock_guard lock(mutex);
closesocket(descriptor); readBatch.clear();
if (state != ConnectionState::CLOSED) {
shutdown(descriptor, 2);
closesocket(descriptor);
}
} }
if (thread) { if (thread) {
thread->join(); thread->join();

View File

@ -49,7 +49,7 @@ namespace network {
virtual void connect(runnable callback) = 0; virtual void connect(runnable callback) = 0;
virtual int recv(char* buffer, size_t length) = 0; virtual int recv(char* buffer, size_t length) = 0;
virtual int send(const 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 int available() = 0;
virtual size_t pullUpload() = 0; virtual size_t pullUpload() = 0;

View File

@ -38,6 +38,18 @@ void Transform::refresh() {
dirty = false; 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() { void Entity::destroy() {
if (isValid()) { if (isValid()) {
entities.despawn(id); entities.despawn(id);
@ -561,11 +573,14 @@ void Entities::render(
if (transform.dirty) { if (transform.dirty) {
transform.refresh(); transform.refresh();
} }
if (skeleton.interpolation.isEnabled()) {
skeleton.interpolation.updateTimer(delta);
}
const auto& pos = transform.pos; const auto& pos = transform.pos;
const auto& size = transform.size; const auto& size = transform.size;
if (!frustum || frustum->isBoxVisible(pos - size, pos + size)) { if (!frustum || frustum->isBoxVisible(pos - size, pos + size)) {
const auto* rigConfig = skeleton.config; 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; entityid_t uid;
const EntityDef& def; const EntityDef& def;
bool destroyFlag = false; bool destroyFlag = false;
int64_t player = -1;
}; };
struct Transform { struct Transform {
@ -161,6 +162,18 @@ public:
return entity; 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(); void destroy();
}; };

View File

@ -65,8 +65,14 @@ void Player::updateEntity() {
if (eid == 0) { if (eid == 0) {
auto& def = level.content.entities.require("base:player"); auto& def = level.content.entities.require("base:player");
eid = level.entities->spawn(def, getPosition()); eid = level.entities->spawn(def, getPosition());
if (auto entity = level.entities->get(eid)) {
entity->setPlayer(id);
}
} else if (auto entity = level.entities->get(eid)) { } else if (auto entity = level.entities->get(eid)) {
position = entity->getTransform().pos; position = entity->getTransform().pos;
if (auto entity = level.entities->get(eid)) {
entity->setPlayer(id);
}
} else if (chunks->getChunkByVoxel(position)) { } else if (chunks->getChunkByVoxel(position)) {
logger.error() << "player entity despawned or deleted; " logger.error() << "player entity despawned or deleted; "
"will be respawned"; "will be respawned";
@ -164,23 +170,9 @@ void Player::postUpdate() {
} }
} }
// TODO: ERASE & FORGET
auto& skeleton = entity->getSkeleton(); auto& skeleton = entity->getSkeleton();
skeleton.visible = currentCamera != fpCamera; 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) { void Player::teleport(glm::vec3 position) {
@ -189,6 +181,7 @@ void Player::teleport(glm::vec3 position) {
if (auto entity = level.entities->get(eid)) { if (auto entity = level.entities->get(eid)) {
entity->getRigidbody().hitbox.position = position; entity->getRigidbody().hitbox.position = position;
entity->getTransform().setPos(position); entity->getTransform().setPos(position);
entity->setInterpolatedPosition(position);
} }
} }
@ -224,6 +217,14 @@ float Player::getSpeed() const {
return speed; return speed;
} }
bool Player::isSuspended() const {
return suspended;
}
void Player::setSuspended(bool flag) {
suspended = flag;
}
bool Player::isFlight() const { bool Player::isFlight() const {
return flight; return flight;
} }
@ -296,6 +297,18 @@ glm::vec3 Player::getSpawnPoint() const {
return spawnpoint; 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 { dv::value Player::serialize() const {
auto root = dv::object(); auto root = dv::object();

View File

@ -7,6 +7,7 @@
#include "interfaces/Serializable.hpp" #include "interfaces/Serializable.hpp"
#include "settings.hpp" #include "settings.hpp"
#include "voxels/voxel.hpp" #include "voxels/voxel.hpp"
#include "util/Interpolation.hpp"
class Chunks; class Chunks;
class Camera; class Camera;
@ -47,6 +48,7 @@ class Player : public Serializable {
glm::vec3 position; glm::vec3 position;
glm::vec3 spawnpoint {}; glm::vec3 spawnpoint {};
std::shared_ptr<Inventory> inventory; std::shared_ptr<Inventory> inventory;
bool suspended = false;
bool flight = false; bool flight = false;
bool noclip = false; bool noclip = false;
bool infiniteItems = true; bool infiniteItems = true;
@ -54,11 +56,15 @@ class Player : public Serializable {
bool loadingChunks = true; bool loadingChunks = true;
entityid_t eid; entityid_t eid;
entityid_t selectedEid = 0; entityid_t selectedEid = 0;
glm::vec3 rotation {};
public: public:
util::VecInterpolation<3, float, true> rotationInterpolation {true};
std::unique_ptr<Chunks> chunks; std::unique_ptr<Chunks> chunks;
std::shared_ptr<Camera> fpCamera, spCamera, tpCamera; std::shared_ptr<Camera> fpCamera, spCamera, tpCamera;
std::shared_ptr<Camera> currentCamera; std::shared_ptr<Camera> currentCamera;
glm::vec3 rotation {};
CursorSelection selection {}; CursorSelection selection {};
Player( Player(
@ -85,6 +91,9 @@ public:
int getChosenSlot() const; int getChosenSlot() const;
float getSpeed() const; float getSpeed() const;
bool isSuspended() const;
void setSuspended(bool flag);
bool isFlight() const; bool isFlight() const;
void setFlight(bool flag); void setFlight(bool flag);
@ -119,6 +128,9 @@ public:
void setSpawnPoint(glm::vec3 point); void setSpawnPoint(glm::vec3 point);
glm::vec3 getSpawnPoint() const; glm::vec3 getSpawnPoint() const;
glm::vec3 getRotation(bool interpolated=false) const;
void setRotation(const glm::vec3& rotation);
dv::value serialize() const override; dv::value serialize() const override;
void deserialize(const dv::value& src) override; void deserialize(const dv::value& src) override;

View File

@ -4,6 +4,7 @@
#include "items/Inventories.hpp" #include "items/Inventories.hpp"
#include "world/Level.hpp" #include "world/Level.hpp"
#include "world/World.hpp" #include "world/World.hpp"
#include "objects/Entities.hpp"
Players::Players(Level& level) : level(level) {} Players::Players(Level& level) : level(level) {}
@ -19,10 +20,19 @@ Player* Players::get(int64_t id) const {
return found->second.get(); 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>( auto playerPtr = std::make_unique<Player>(
level, level,
level.getWorld()->getInfo().nextPlayerId++, id,
"", "",
glm::vec3(0, DEF_PLAYER_Y, 0), glm::vec3(0, DEF_PLAYER_Y, 0),
DEF_PLAYER_SPEED, DEF_PLAYER_SPEED,
@ -36,6 +46,26 @@ Player* Players::create() {
return player; 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) { void Players::remove(int64_t id) {
players.erase(id); players.erase(id);
} }

View File

@ -14,6 +14,9 @@ class Level;
class Player; class Player;
class Players : public Serializable { class Players : public Serializable {
public:
static inline int64_t NONE = -1;
private:
Level& level; Level& level;
std::unordered_map<int64_t, std::unique_ptr<Player>> players; std::unordered_map<int64_t, std::unique_ptr<Player>> players;
@ -23,7 +26,11 @@ public:
Player* get(int64_t id) const; 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); void remove(int64_t id);

View File

@ -6,9 +6,8 @@
#include "graphics/commons/Model.hpp" #include "graphics/commons/Model.hpp"
#include "graphics/render/ModelBatch.hpp" #include "graphics/render/ModelBatch.hpp"
#define GLM_ENABLE_EXPERIMENTAL
#include <glm/ext/matrix_transform.hpp> #include <glm/ext/matrix_transform.hpp>
#include <glm/gtx/norm.hpp> #include <glm/gtx/matrix_decompose.hpp>
using namespace rigging; using namespace rigging;
@ -69,7 +68,7 @@ SkeletonConfig::SkeletonConfig(
} }
size_t SkeletonConfig::update( 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 { ) const {
auto boneMatrix = skeleton.pose.matrices[index]; auto boneMatrix = skeleton.pose.matrices[index];
auto boneOffset = node->getOffset(); auto boneOffset = node->getOffset();
@ -90,17 +89,32 @@ size_t SkeletonConfig::update(
return count; return count;
} }
void SkeletonConfig::update(Skeleton& skeleton, glm::mat4 matrix) const { void SkeletonConfig::update(
update(0, skeleton, root.get(), matrix); 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( void SkeletonConfig::render(
const Assets& assets, const Assets& assets,
ModelBatch& batch, ModelBatch& batch,
Skeleton& skeleton, Skeleton& skeleton,
const glm::mat4& matrix const glm::mat4& matrix,
const glm::vec3& position
) const { ) const {
update(skeleton, matrix); update(skeleton, matrix, position);
if (!skeleton.visible) { if (!skeleton.visible) {
return; return;

View File

@ -1,12 +1,15 @@
#pragma once #pragma once
#include <glm/glm.hpp> #define GLM_ENABLE_EXPERIMENTAL
#include <glm/gtx/norm.hpp>
#include <memory> #include <memory>
#include <string> #include <string>
#include <cmath>
#include <unordered_map> #include <unordered_map>
#include <vector> #include <vector>
#include "typedefs.hpp" #include "typedefs.hpp"
#include "util/Interpolation.hpp"
class Assets; class Assets;
class ModelBatch; class ModelBatch;
@ -83,6 +86,8 @@ namespace rigging {
bool visible; bool visible;
glm::vec3 tint {1.0f, 1.0f, 1.0f}; glm::vec3 tint {1.0f, 1.0f, 1.0f};
util::VecInterpolation<3, float> interpolation {false};
Skeleton(const SkeletonConfig* config); Skeleton(const SkeletonConfig* config);
}; };
@ -100,7 +105,7 @@ namespace rigging {
std::vector<Bone*> nodes; std::vector<Bone*> nodes;
size_t update( 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; ) const;
public: public:
SkeletonConfig( SkeletonConfig(
@ -109,12 +114,18 @@ namespace rigging {
size_t nodesCount 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( void render(
const Assets& assets, const Assets& assets,
ModelBatch& batch, ModelBatch& batch,
Skeleton& skeleton, Skeleton& skeleton,
const glm::mat4& matrix const glm::mat4& matrix,
const glm::vec3& position
) const; ) const;
Skeleton instance() const { Skeleton instance() const {

View File

@ -14,7 +14,7 @@ namespace util {
int nextid = 1; int nextid = 1;
std::unordered_map<int, std::function<bool(Types...)>> handlers; std::unordered_map<int, std::function<bool(Types...)>> handlers;
std::vector<int> order; std::vector<int> order;
std::recursive_mutex mutex; std::mutex mutex;
public: public:
HandlersList() = default; HandlersList() = default;
@ -46,11 +46,15 @@ namespace util {
} }
void notify(Types...args) { void notify(Types...args) {
std::lock_guard lock(mutex); std::vector<int> orderCopy;
decltype(handlers) handlersCopy;
auto orderCopy = order; {
std::lock_guard lock(mutex);
orderCopy = order;
handlersCopy = handlers;
}
for (auto it = orderCopy.rbegin(); it != orderCopy.rend(); ++it) { for (auto it = orderCopy.rbegin(); it != orderCopy.rend(); ++it) {
if (handlers.at(*it)(args...)) { if (handlersCopy.at(*it)(args...)) {
break; 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; int Events::scroll = 0;
glm::vec2 Events::delta = {}; glm::vec2 Events::delta = {};
glm::vec2 Events::cursor = {}; glm::vec2 Events::cursor = {};
bool Events::cursor_drag = false; bool Events::cursorDrag = false;
bool Events::_cursor_locked = false; bool Events::cursorLocked = false;
std::vector<uint> Events::codepoints; std::vector<uint> Events::codepoints;
std::vector<keycode> Events::pressedKeys; std::vector<keycode> Events::pressedKeys;
std::unordered_map<std::string, Binding> Events::bindings; std::unordered_map<std::string, Binding> Events::bindings;
@ -61,10 +61,10 @@ bool Events::jclicked(int button) {
} }
void Events::toggleCursor() { void Events::toggleCursor() {
cursor_drag = false; cursorDrag = false;
_cursor_locked = !_cursor_locked; cursorLocked = !cursorLocked;
Window::setCursorMode( 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) { void Events::setPosition(float xpos, float ypos) {
if (Events::cursor_drag) { if (Events::cursorDrag) {
Events::delta.x += xpos - Events::cursor.x; Events::delta.x += xpos - Events::cursor.x;
Events::delta.y += ypos - Events::cursor.y; Events::delta.y += ypos - Events::cursor.y;
} else { } else {
Events::cursor_drag = true; Events::cursorDrag = true;
} }
Events::cursor.x = xpos; Events::cursor.x = xpos;
Events::cursor.y = ypos; Events::cursor.y = ypos;
@ -249,3 +249,7 @@ void Events::enableBindings() {
binding.enable = true; binding.enable = true;
} }
} }
bool Events::isCursorLocked() {
return cursorLocked;
}

View File

@ -19,12 +19,12 @@ class Events {
static bool keys[KEYS_BUFFER_SIZE]; static bool keys[KEYS_BUFFER_SIZE];
static uint frames[KEYS_BUFFER_SIZE]; static uint frames[KEYS_BUFFER_SIZE];
static uint currentFrame; static uint currentFrame;
static bool cursor_drag; static bool cursorDrag;
static bool cursorLocked;
public: public:
static int scroll; static int scroll;
static glm::vec2 delta; static glm::vec2 delta;
static glm::vec2 cursor; static glm::vec2 cursor;
static bool _cursor_locked;
static std::vector<uint> codepoints; static std::vector<uint> codepoints;
static std::vector<keycode> pressedKeys; static std::vector<keycode> pressedKeys;
static std::unordered_map<std::string, Binding> bindings; static std::unordered_map<std::string, Binding> bindings;
@ -65,4 +65,6 @@ public:
BindType bindType BindType bindType
); );
static void enableBindings(); static void enableBindings();
static bool isCursorLocked();
}; };

View File

@ -389,7 +389,9 @@ void Window::toggleFullscreen() {
GLFWmonitor* monitor = glfwGetPrimaryMonitor(); GLFWmonitor* monitor = glfwGetPrimaryMonitor();
const GLFWvidmode* mode = glfwGetVideoMode(monitor); const GLFWvidmode* mode = glfwGetVideoMode(monitor);
if (Events::_cursor_locked) Events::toggleCursor(); if (Events::isCursorLocked()){
Events::toggleCursor();
}
if (fullscreen) { if (fullscreen) {
glfwGetWindowPos(window, &posX, &posY); glfwGetWindowPos(window, &posX, &posY);