Merge branch 'MihailRis:main' into main
This commit is contained in:
commit
dc83e9b466
@ -77,12 +77,14 @@ Checks if content is loaded.
|
||||
|
||||
```lua
|
||||
app.new_world(
|
||||
-- world name
|
||||
-- world name, empty string will create a nameless world
|
||||
name: str,
|
||||
-- generation seed
|
||||
seed: str,
|
||||
-- generator name
|
||||
generator: str
|
||||
-- local player id
|
||||
[optional] local_player: int=0
|
||||
)
|
||||
```
|
||||
|
||||
|
||||
@ -62,4 +62,7 @@ hud.is_paused() -> bool
|
||||
|
||||
-- Returns true if inventory is open or overlay is shown.
|
||||
hud.is_inventory_open() -> bool
|
||||
|
||||
-- Sets whether to allow pausing. If false, the pause menu will not pause the game.
|
||||
hud.set_allow_pause(flag: bool)
|
||||
```
|
||||
|
||||
@ -37,10 +37,10 @@ player.set_vel(playerid: int, x: number, y: number, z: number)
|
||||
Sets x, y, z player linear velocity
|
||||
|
||||
```lua
|
||||
player.get_rot(playerid: int) -> number, number, number
|
||||
player.get_rot(playerid: int, interpolated: bool) -> number, number, number
|
||||
```
|
||||
|
||||
Returns x, y, z of camera rotation (radians)
|
||||
Returns x, y, z of camera rotation (radians). Interpolation is relevant in cases where the rotation refresh rate is lower than the frame rate.
|
||||
|
||||
```lua
|
||||
player.set_rot(playerid: int, x: number, y: number, z: number)
|
||||
|
||||
@ -56,6 +56,7 @@ Common element methods:
|
||||
| ------------------- | ----------------------------------------------------------------------------------- |
|
||||
| moveInto(container) | moves the element to the specified container (the element is specified, not the id) |
|
||||
| destruct() | removes element |
|
||||
| reposition() | updates the element position based on the `positionfunc` |
|
||||
|
||||
## Containers
|
||||
|
||||
|
||||
@ -19,3 +19,34 @@ Styles can be combined. Example:
|
||||
Output:
|
||||
|
||||
***<ins>Message</ins>*** using *~~combed~~ combined* styles<ins>~~.~~</ins>
|
||||
|
||||
# Colors
|
||||
|
||||
Text color can be set using a color code: [#RRGGBB]
|
||||
|
||||
|
||||
| Component | Purpose |
|
||||
| --------- | --------------------------------- |
|
||||
| R | Represents the intensity of red |
|
||||
| G | Represents the intensity of green |
|
||||
| B | Represents the intensity of blue |
|
||||
|
||||
### Example:
|
||||
|
||||
<span style="color: #ff0000">
|
||||
<span style="color:rgb(105, 105, 105)">
|
||||
[#ff0000]
|
||||
</span>Red Text
|
||||
</span>
|
||||
|
||||
<span style="color: #00ff00">
|
||||
<span style="color:rgb(105, 105, 105)">
|
||||
[#00ff00]
|
||||
</span>Green Text
|
||||
</span>
|
||||
|
||||
<span style="color: #0000ff">
|
||||
<span style="color:rgb(105, 105, 105)">
|
||||
[#0000ff]
|
||||
</span>Blue Text
|
||||
</span>
|
||||
|
||||
@ -35,6 +35,7 @@ Examples:
|
||||
- `margin` - element margin. Type: 4D vector
|
||||
*left, top, right, bottom*
|
||||
- `visible` - element visibility. Type: boolean (true/false)
|
||||
- `min-size` - minimal element size. Type: 2D vector.
|
||||
- `position-func` - position supplier for an element (two numbers), called on every parent container size update or on element adding on a container. May be called before *on_hud_open*
|
||||
- `size-func` - element size provider (two numbers), called when the size of the container in which the element is located changes, or when an element is added to the container. Can be called before on_hud_open is called.
|
||||
- `onclick` - lua function called when an element is clicked.
|
||||
@ -65,6 +66,7 @@ Buttons and panels are also containers.
|
||||
Buttons are also panels.
|
||||
|
||||
- `max-length` - maximal length of panel stretching before scrolling (if scrollable = true). Type: number
|
||||
- `min-length` - minimal length of panel. Type: number
|
||||
- `orientation` - panel orientation: horizontal/vertical.
|
||||
|
||||
# Common elements
|
||||
|
||||
@ -77,12 +77,14 @@ app.is_content_loaded() -> bool
|
||||
|
||||
```lua
|
||||
app.new_world(
|
||||
-- название мира
|
||||
-- название мира, пустая строка приведёт к созданию безымянного мира
|
||||
name: str,
|
||||
-- зерно генерации
|
||||
seed: str,
|
||||
-- название генератора
|
||||
generator: str
|
||||
-- id локального игрока
|
||||
[опционально] local_player: int=0
|
||||
)
|
||||
```
|
||||
|
||||
|
||||
@ -65,4 +65,7 @@ hud.is_paused() -> bool
|
||||
|
||||
-- Возвращает true если открыт инвентарь или оверлей.
|
||||
hud.is_inventory_open() -> bool
|
||||
|
||||
-- Устанавливает разрешение на паузу. При значении false меню паузы не приостанавливает игру.
|
||||
hud.set_allow_pause(flag: bool)
|
||||
```
|
||||
|
||||
@ -37,10 +37,10 @@ player.set_vel(playerid: int, x: number, y: number, z: number)
|
||||
Устанавливает x, y, z линейной скорости игрока
|
||||
|
||||
```lua
|
||||
player.get_rot(playerid: int) -> number, number, number
|
||||
player.get_rot(playerid: int, interpolated: bool=false) -> number, number, number
|
||||
```
|
||||
|
||||
Возвращает x, y, z вращения камеры (в радианах)
|
||||
Возвращает x, y, z вращения камеры (в радианах). Интерполяция актуальна в случаях, когда частота обновления вращения ниже частоты кадров.
|
||||
|
||||
```lua
|
||||
player.set_rot(playerid: int, x: number, y: number, z: number)
|
||||
|
||||
@ -56,6 +56,7 @@ document["worlds-panel"]:clear()
|
||||
| ------------------- | ----------------------------------------------------------------------- |
|
||||
| moveInto(container) | перемещает элемент в указанный контейнер (указывается элемент, а не id) |
|
||||
| destruct() | удаляет элемент |
|
||||
| reposition() | обновляет позицию элемента на основе функции позиционирования |
|
||||
|
||||
## Контейнеры
|
||||
|
||||
|
||||
@ -19,3 +19,33 @@
|
||||
Вывод:
|
||||
|
||||
***<ins>Сообщение</ins>***, демонстрирующее *~~обедненные~~ объединенные* стили<ins>~~.~~</ins>
|
||||
|
||||
## Цвета
|
||||
|
||||
Цвет текста задается при помощи цветового кода: [#RRGGBB]
|
||||
|
||||
| Компонент | Назначение |
|
||||
| ------------ | ------------------------- |
|
||||
| R | Используется для интенсивности красного |
|
||||
| G | Используется для интенсивности зеленого |
|
||||
| B | Используется для интенсивности синего |
|
||||
|
||||
### Например:
|
||||
|
||||
<span style="color: #ff0000">
|
||||
<span style="color:rgb(105, 105, 105)">
|
||||
[#ff0000]
|
||||
</span>Красный Текст
|
||||
</span>
|
||||
|
||||
<span style="color: #00ff00">
|
||||
<span style="color:rgb(105, 105, 105)">
|
||||
[#00ff00]
|
||||
</span>Зеленый Текст
|
||||
</span>
|
||||
|
||||
<span style="color: #0000ff">
|
||||
<span style="color:rgb(105, 105, 105)">
|
||||
[#0000ff]
|
||||
</span>Синий Текст
|
||||
</span>
|
||||
@ -39,6 +39,7 @@
|
||||
- `margin` - внешний отступ элемента. Тип: 4D вектор.
|
||||
Порядок: `"left,top,right,bottom"`
|
||||
- `visible` - видимость элемента. Тип: логический ("true"/"false").
|
||||
- `min-size` - минимальный размер элемента. Тип: 2D вектор.
|
||||
- `position-func` - поставщик позиции элемента (два числа), вызываемый при изменении размера контейнера, в котором находится элемент, либо при добавлении элемента в контейнер. Может быть вызван до вызова on_hud_open.
|
||||
- `size-func` - поставщик размера элемента (два числа), вызываемый при изменении размера контейнера, в котором находится элемент, либо при добавлении элемента в контейнер. Может быть вызван до вызова on_hud_open.
|
||||
- `onclick` - lua функция вызываемая при нажатии на элемент.
|
||||
@ -67,6 +68,7 @@
|
||||
|
||||
В число панелей также входят кнопки.
|
||||
- `max-length` - максимальная длина, на которую растягивается панель до начала скроллинга (если scrollable = true). Тип: число
|
||||
- `min-length` - минимальная длина панели. Тип: число
|
||||
- `orientation` - ориентация панели: horizontal/vertical.
|
||||
|
||||
# Основные элементы
|
||||
|
||||
@ -3,7 +3,9 @@ local body = entity.rigidbody
|
||||
local rig = entity.skeleton
|
||||
|
||||
local itemid = 0
|
||||
local headIndex = rig:index("head")
|
||||
local itemIndex = rig:index("item")
|
||||
local bodyIndex = rig:index("body")
|
||||
|
||||
local function refresh_model(id)
|
||||
itemid = id
|
||||
@ -12,7 +14,16 @@ local function refresh_model(id)
|
||||
end
|
||||
|
||||
function on_render()
|
||||
local invid, slotid = player.get_inventory()
|
||||
local pid = entity:get_player()
|
||||
if pid == -1 then
|
||||
return
|
||||
end
|
||||
|
||||
local rx, ry, rz = player.get_rot(pid, true)
|
||||
rig:set_matrix(headIndex, mat4.rotate({1, 0, 0}, ry))
|
||||
rig:set_matrix(bodyIndex, mat4.rotate({0, 1, 0}, rx))
|
||||
|
||||
local invid, slotid = player.get_inventory(pid)
|
||||
local id, _ = inventory.get(invid, slotid)
|
||||
if id ~= itemid then
|
||||
refresh_model(id)
|
||||
|
||||
@ -134,6 +134,10 @@ function add_to_history(text)
|
||||
end
|
||||
|
||||
function submit(text)
|
||||
if #text == 0 then
|
||||
document.prompt.focused = true
|
||||
return
|
||||
end
|
||||
text = text:trim()
|
||||
add_to_history(text)
|
||||
|
||||
@ -204,4 +208,11 @@ function on_open(mode)
|
||||
elseif mode then
|
||||
modes:set(mode)
|
||||
end
|
||||
hud.close("core:ingame_chat")
|
||||
end
|
||||
|
||||
function on_close()
|
||||
time.post_runnable(function()
|
||||
hud.open_permanent("core:ingame_chat")
|
||||
end)
|
||||
end
|
||||
|
||||
8
res/layouts/ingame_chat.xml
Normal file
8
res/layouts/ingame_chat.xml
Normal 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>
|
||||
59
res/layouts/ingame_chat.xml.lua
Normal file
59
res/layouts/ingame_chat.xml.lua
Normal 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
|
||||
@ -37,4 +37,6 @@ end
|
||||
|
||||
function on_open()
|
||||
refresh()
|
||||
|
||||
input.add_callback("key:f5", refresh, document.root)
|
||||
end
|
||||
|
||||
3
res/layouts/templates/chat_line.xml
Normal file
3
res/layouts/templates/chat_line.xml
Normal file
@ -0,0 +1,3 @@
|
||||
<container id='%{id}' color="#00000050" size="-1,20">
|
||||
<label pos='5' id='%{id}L' markup='md'/>
|
||||
</container>
|
||||
@ -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
|
||||
107
res/modules/internal/gui_util.lua
Normal file
107
res/modules/internal/gui_util.lua
Normal 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
|
||||
@ -49,6 +49,7 @@ local Skeleton = {__index={
|
||||
set_visible=function(self, i, b) return __skeleton.set_visible(self.eid, i, b) end,
|
||||
get_color=function(self) return __skeleton.get_color(self.eid) end,
|
||||
set_color=function(self, color) return __skeleton.set_color(self.eid, color) end,
|
||||
set_interpolated=function(self, b) return __skeleton.set_interpolated(self.eid, b) end,
|
||||
}}
|
||||
|
||||
local function new_Skeleton(eid)
|
||||
@ -66,6 +67,7 @@ local Entity = {__index={
|
||||
get_uid=function(self) return self.eid end,
|
||||
def_index=function(self) return entities.get_def(self.eid) end,
|
||||
def_name=function(self) return entities.def_name(entities.get_def(self.eid)) end,
|
||||
get_player=function(self) return entities.get_player(self.eid) end,
|
||||
}}
|
||||
|
||||
local entities = {}
|
||||
|
||||
@ -260,7 +260,7 @@ console.add_command(
|
||||
"chat text:str",
|
||||
"Send chat message",
|
||||
function (args, kwargs)
|
||||
console.log("[you] "..args[1])
|
||||
console.chat("[you] "..args[1])
|
||||
end
|
||||
)
|
||||
|
||||
|
||||
@ -146,64 +146,16 @@ function events.emit(event, ...)
|
||||
return result
|
||||
end
|
||||
|
||||
-- class designed for simple UI-nodes access via properties syntax
|
||||
local Element = {}
|
||||
function Element.new(docname, name)
|
||||
return setmetatable({docname=docname, name=name}, {
|
||||
__index=function(self, k)
|
||||
return gui.getattr(self.docname, self.name, k)
|
||||
end,
|
||||
__newindex=function(self, k, v)
|
||||
gui.setattr(self.docname, self.name, k, v)
|
||||
end
|
||||
})
|
||||
end
|
||||
gui_util = require "core:internal/gui_util"
|
||||
|
||||
-- the engine automatically creates an instance for every ui document (layout)
|
||||
Document = {}
|
||||
function Document.new(docname)
|
||||
return setmetatable({name=docname}, {
|
||||
__index=function(self, k)
|
||||
local elem = Element.new(self.name, k)
|
||||
rawset(self, k, elem)
|
||||
return elem
|
||||
end
|
||||
})
|
||||
end
|
||||
|
||||
local _RadioGroup = {}
|
||||
function _RadioGroup:set(key)
|
||||
if type(self) ~= 'table' then
|
||||
error("called as non-OOP via '.', use radiogroup:set")
|
||||
end
|
||||
if self.current then
|
||||
self.elements[self.current].enabled = true
|
||||
end
|
||||
self.elements[key].enabled = false
|
||||
self.current = key
|
||||
if self.callback then
|
||||
self.callback(key)
|
||||
end
|
||||
end
|
||||
function _RadioGroup:__call(elements, onset, default)
|
||||
local group = setmetatable({
|
||||
elements=elements,
|
||||
callback=onset,
|
||||
current=nil
|
||||
}, {__index=_RadioGroup})
|
||||
group:set(default)
|
||||
return group
|
||||
end
|
||||
setmetatable(_RadioGroup, _RadioGroup)
|
||||
RadioGroup = _RadioGroup
|
||||
Document = gui_util.Document
|
||||
RadioGroup = gui_util.RadioGroup
|
||||
__vc_page_loader = gui_util.load_page
|
||||
|
||||
_GUI_ROOT = Document.new("core:root")
|
||||
_MENU = _GUI_ROOT.menu
|
||||
menu = _MENU
|
||||
|
||||
local gui_util = require "core:gui_util"
|
||||
__vc_page_loader = gui_util.load_page
|
||||
|
||||
--- Console library extension ---
|
||||
console.cheats = {}
|
||||
|
||||
@ -225,6 +177,11 @@ function console.log(...)
|
||||
log_element:paste(text)
|
||||
end
|
||||
|
||||
function console.chat(...)
|
||||
console.log(...)
|
||||
events.emit("core:chat", ...)
|
||||
end
|
||||
|
||||
function gui.template(name, params)
|
||||
local text = file.read(file.find("layouts/templates/"..name..".xml"))
|
||||
for k,v in pairs(params) do
|
||||
@ -385,6 +342,16 @@ function __vc_on_hud_open()
|
||||
hud.show_overlay("core:console", false, {"chat"})
|
||||
end)
|
||||
end)
|
||||
input.add_callback("key:escape", function()
|
||||
if hud.is_paused() then
|
||||
hud.resume()
|
||||
elseif hud.is_inventory_open() then
|
||||
hud.close_inventory()
|
||||
else
|
||||
hud.pause()
|
||||
end
|
||||
end)
|
||||
hud.open_permanent("core:ingame_chat")
|
||||
end
|
||||
|
||||
local RULES_FILE = "world:rules.toml"
|
||||
@ -408,6 +375,7 @@ end
|
||||
|
||||
function __vc_on_world_quit()
|
||||
_rules.clear()
|
||||
gui_util:reset_local()
|
||||
end
|
||||
|
||||
local __vc_coroutines = {}
|
||||
@ -470,7 +438,10 @@ function __process_post_runnables()
|
||||
|
||||
local dead = {}
|
||||
for name, co in pairs(__vc_named_coroutines) do
|
||||
coroutine.resume(co)
|
||||
local success, err = coroutine.resume(co)
|
||||
if not success then
|
||||
debug.error(err)
|
||||
end
|
||||
if coroutine.status(co) == "dead" then
|
||||
table.insert(dead, name)
|
||||
end
|
||||
|
||||
@ -76,8 +76,7 @@ std::shared_ptr<ContentReport> ContentReport::create(
|
||||
}
|
||||
|
||||
auto root = files::read_json(filename);
|
||||
// TODO: remove default value 2 in 0.24
|
||||
uint regionsVersion = 2U;
|
||||
uint regionsVersion = 2U; // old worlds compatibility (pre 0.23)
|
||||
root.at("region-version").get(regionsVersion);
|
||||
auto& blocklist = root["blocks"];
|
||||
auto& itemlist = root["items"];
|
||||
|
||||
@ -217,6 +217,7 @@ void Engine::renderFrame() {
|
||||
Viewport viewport(Window::width, Window::height);
|
||||
DrawContext ctx(nullptr, viewport, nullptr);
|
||||
gui->draw(ctx, *assets);
|
||||
gui->postAct();
|
||||
}
|
||||
|
||||
void Engine::saveSettings() {
|
||||
@ -272,7 +273,7 @@ PacksManager Engine::createPacksManager(const fs::path& worldFolder) {
|
||||
return manager;
|
||||
}
|
||||
|
||||
void Engine::setLevelConsumer(consumer<std::unique_ptr<Level>> levelConsumer) {
|
||||
void Engine::setLevelConsumer(OnWorldOpen levelConsumer) {
|
||||
this->levelConsumer = std::move(levelConsumer);
|
||||
}
|
||||
|
||||
@ -446,14 +447,14 @@ void Engine::setLanguage(std::string locale) {
|
||||
langs::setup(paths.getResourcesFolder(), std::move(locale), contentPacks);
|
||||
}
|
||||
|
||||
void Engine::onWorldOpen(std::unique_ptr<Level> level) {
|
||||
void Engine::onWorldOpen(std::unique_ptr<Level> level, int64_t localPlayer) {
|
||||
logger.info() << "world open";
|
||||
levelConsumer(std::move(level));
|
||||
levelConsumer(std::move(level), localPlayer);
|
||||
}
|
||||
|
||||
void Engine::onWorldClosed() {
|
||||
logger.info() << "world closed";
|
||||
levelConsumer(nullptr);
|
||||
levelConsumer(nullptr, -1);
|
||||
}
|
||||
|
||||
void Engine::quit() {
|
||||
|
||||
@ -53,6 +53,8 @@ struct CoreParameters {
|
||||
std::filesystem::path scriptFile;
|
||||
};
|
||||
|
||||
using OnWorldOpen = std::function<void(std::unique_ptr<Level>, int64_t)>;
|
||||
|
||||
class Engine : public util::ObjectsKeeper {
|
||||
CoreParameters params;
|
||||
EngineSettings settings;
|
||||
@ -71,7 +73,7 @@ class Engine : public util::ObjectsKeeper {
|
||||
std::unique_ptr<gui::GUI> gui;
|
||||
PostRunnables postRunnables;
|
||||
Time time;
|
||||
consumer<std::unique_ptr<Level>> levelConsumer;
|
||||
OnWorldOpen levelConsumer;
|
||||
bool quitSignal = false;
|
||||
|
||||
void loadControls();
|
||||
@ -134,7 +136,7 @@ public:
|
||||
/// @brief Get engine resource paths controller
|
||||
ResPaths* getResPaths();
|
||||
|
||||
void onWorldOpen(std::unique_ptr<Level> level);
|
||||
void onWorldOpen(std::unique_ptr<Level> level, int64_t localPlayer);
|
||||
void onWorldClosed();
|
||||
|
||||
void quit();
|
||||
@ -166,7 +168,7 @@ public:
|
||||
|
||||
PacksManager createPacksManager(const fs::path& worldFolder);
|
||||
|
||||
void setLevelConsumer(consumer<std::unique_ptr<Level>> levelConsumer);
|
||||
void setLevelConsumer(OnWorldOpen levelConsumer);
|
||||
|
||||
SettingsHandler& getSettingsHandler();
|
||||
|
||||
|
||||
@ -15,14 +15,16 @@ Mainloop::Mainloop(Engine& engine) : engine(engine) {
|
||||
void Mainloop::run() {
|
||||
auto& time = engine.getTime();
|
||||
|
||||
engine.setLevelConsumer([this](auto level) {
|
||||
engine.setLevelConsumer([this](auto level, int64_t localPlayer) {
|
||||
if (level == nullptr) {
|
||||
// destroy LevelScreen and run quit callbacks
|
||||
engine.setScreen(nullptr);
|
||||
// create and go to menu screen
|
||||
engine.setScreen(std::make_shared<MenuScreen>(engine));
|
||||
} else {
|
||||
engine.setScreen(std::make_shared<LevelScreen>(engine, std::move(level)));
|
||||
engine.setScreen(std::make_shared<LevelScreen>(
|
||||
engine, std::move(level), localPlayer
|
||||
));
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@ -30,7 +30,7 @@ void ServerMainloop::run() {
|
||||
logger.info() << "nothing to do";
|
||||
return;
|
||||
}
|
||||
engine.setLevelConsumer([this](auto level) {
|
||||
engine.setLevelConsumer([this](auto level, auto) {
|
||||
setLevel(std::move(level));
|
||||
});
|
||||
|
||||
|
||||
@ -13,7 +13,9 @@
|
||||
#include "graphics/render/ParticlesRenderer.hpp"
|
||||
#include "graphics/render/ChunksRenderer.hpp"
|
||||
#include "logic/scripting/scripting.hpp"
|
||||
#include "network/Network.hpp"
|
||||
#include "objects/Player.hpp"
|
||||
#include "objects/Players.hpp"
|
||||
#include "objects/Entities.hpp"
|
||||
#include "objects/EntityDef.hpp"
|
||||
#include "physics/Hitbox.hpp"
|
||||
@ -41,6 +43,7 @@ static std::shared_ptr<Label> create_label(wstringsupplier supplier) {
|
||||
|
||||
// TODO: move to xml
|
||||
// TODO: move to xml finally
|
||||
// TODO: move to xml finally
|
||||
std::shared_ptr<UINode> create_debug_panel(
|
||||
Engine& engine,
|
||||
Level& level,
|
||||
@ -56,6 +59,10 @@ std::shared_ptr<UINode> create_debug_panel(
|
||||
static int fpsMax = fps;
|
||||
static std::wstring fpsString = L"";
|
||||
|
||||
static size_t lastTotalDownload = 0;
|
||||
static size_t lastTotalUpload = 0;
|
||||
static std::wstring netSpeedString = L"";
|
||||
|
||||
panel->listenInterval(0.016f, [&engine]() {
|
||||
fps = 1.0f / engine.getTime().getDelta();
|
||||
fpsMin = std::min(fps, fpsMin);
|
||||
@ -67,6 +74,19 @@ std::shared_ptr<UINode> create_debug_panel(
|
||||
fpsMin = fps;
|
||||
fpsMax = fps;
|
||||
});
|
||||
|
||||
panel->listenInterval(1.0f, [&engine]() {
|
||||
const auto& network = engine.getNetwork();
|
||||
size_t totalDownload = network.getTotalDownload();
|
||||
size_t totalUpload = network.getTotalUpload();
|
||||
netSpeedString =
|
||||
L"download: " + std::to_wstring(totalDownload - lastTotalDownload) +
|
||||
L" B/s upload: " + std::to_wstring(totalUpload - lastTotalUpload) +
|
||||
L" B/s";
|
||||
lastTotalDownload = totalDownload;
|
||||
lastTotalUpload = totalUpload;
|
||||
});
|
||||
|
||||
panel->add(create_label([]() { return L"fps: "+fpsString;}));
|
||||
|
||||
panel->add(create_label([]() {
|
||||
@ -84,6 +104,7 @@ std::shared_ptr<UINode> create_debug_panel(
|
||||
panel->add(create_label([]() {
|
||||
return L"lua-stack: " + std::to_wstring(scripting::get_values_on_stack());
|
||||
}));
|
||||
panel->add(create_label([]() { return netSpeedString; }));
|
||||
panel->add(create_label([&engine]() {
|
||||
auto& settings = engine.getSettings();
|
||||
bool culling = settings.graphics.frustumCulling.get();
|
||||
@ -103,6 +124,10 @@ std::shared_ptr<UINode> create_debug_panel(
|
||||
return L"entities: "+std::to_wstring(level.entities->size())+L" next: "+
|
||||
std::to_wstring(level.entities->peekNextID());
|
||||
}));
|
||||
panel->add(create_label([&]() {
|
||||
return L"players: "+std::to_wstring(level.players->size())+L" local: "+
|
||||
std::to_wstring(player.getId());
|
||||
}));
|
||||
panel->add(create_label([&]() -> std::wstring {
|
||||
const auto& vox = player.selection.vox;
|
||||
std::wstringstream stream;
|
||||
|
||||
@ -201,17 +201,6 @@ Hud::Hud(Engine& engine, LevelFrontend& frontend, Player& player)
|
||||
"' pos='0' size='256' gravity='top-right' margin='0,20,0,0'/>"
|
||||
);
|
||||
add(HudElement(hud_element_mode::permanent, nullptr, debugMinimap, true));
|
||||
|
||||
keepAlive(Events::keyCallbacks[keycode::ESCAPE].add([this]() -> bool {
|
||||
if (pause) {
|
||||
setPause(false);
|
||||
} else if (inventoryOpen) {
|
||||
closeInventory();
|
||||
} else {
|
||||
setPause(true);
|
||||
}
|
||||
return false;
|
||||
}));
|
||||
}
|
||||
|
||||
Hud::~Hud() {
|
||||
@ -234,7 +223,8 @@ void Hud::cleanup() {
|
||||
}
|
||||
|
||||
void Hud::processInput(bool visible) {
|
||||
if (!Window::isFocused() && !pause && !isInventoryOpen()) {
|
||||
auto menu = gui.getMenu();
|
||||
if (!Window::isFocused() && !menu->hasOpenPage() && !isInventoryOpen()) {
|
||||
setPause(true);
|
||||
}
|
||||
if (!pause && visible && Events::jactive(BIND_HUD_INVENTORY)) {
|
||||
@ -329,14 +319,13 @@ void Hud::update(bool visible) {
|
||||
if (!visible && inventoryOpen) {
|
||||
closeInventory();
|
||||
}
|
||||
if (pause && menu->getCurrent().panel == nullptr) {
|
||||
if (pause && !menu->hasOpenPage()) {
|
||||
setPause(false);
|
||||
}
|
||||
|
||||
if (!gui.isFocusCaught()) {
|
||||
processInput(visible);
|
||||
}
|
||||
if ((pause || inventoryOpen) == Events::_cursor_locked) {
|
||||
if ((menu->hasOpenPage() || inventoryOpen) == Events::isCursorLocked()) {
|
||||
Events::toggleCursor();
|
||||
}
|
||||
|
||||
@ -601,7 +590,9 @@ void Hud::draw(const DrawContext& ctx){
|
||||
const Viewport& viewport = ctx.getViewport();
|
||||
const uint width = viewport.getWidth();
|
||||
const uint height = viewport.getHeight();
|
||||
auto menu = gui.getMenu();
|
||||
|
||||
darkOverlay->setVisible(menu->hasOpenPage());
|
||||
updateElementsPosition(viewport);
|
||||
|
||||
uicamera->setFov(height);
|
||||
@ -687,19 +678,20 @@ void Hud::setPause(bool pause) {
|
||||
if (this->pause == pause) {
|
||||
return;
|
||||
}
|
||||
this->pause = pause;
|
||||
if (allowPause) {
|
||||
this->pause = pause;
|
||||
}
|
||||
|
||||
if (inventoryOpen) {
|
||||
closeInventory();
|
||||
}
|
||||
|
||||
const auto& menu = gui.getMenu();
|
||||
if (pause) {
|
||||
menu->setPage("pause");
|
||||
} else {
|
||||
if (menu->hasOpenPage()) {
|
||||
menu->reset();
|
||||
} else {
|
||||
menu->setPage("pause");
|
||||
}
|
||||
darkOverlay->setVisible(pause);
|
||||
menu->setVisible(pause);
|
||||
}
|
||||
|
||||
@ -732,3 +724,12 @@ void Hud::setDebugCheats(bool flag) {
|
||||
debugPanel->setZIndex(2);
|
||||
gui.add(debugPanel);
|
||||
}
|
||||
|
||||
void Hud::setAllowPause(bool flag) {
|
||||
if (pause) {
|
||||
auto menu = gui.getMenu();
|
||||
setPause(false);
|
||||
menu->setPage("pause", true);
|
||||
}
|
||||
allowPause = flag;
|
||||
}
|
||||
|
||||
@ -113,6 +113,8 @@ class Hud : public util::ObjectsKeeper {
|
||||
bool showContentPanel = true;
|
||||
/// @brief Provide cheat controllers to the debug panel
|
||||
bool allowDebugCheats = true;
|
||||
/// @brief Allow actual pause
|
||||
bool allowPause = true;
|
||||
bool debug = false;
|
||||
/// @brief UI element will be dynamicly positioned near to inventory or in screen center
|
||||
std::shared_ptr<gui::UINode> secondUI;
|
||||
@ -206,6 +208,8 @@ public:
|
||||
|
||||
void setDebugCheats(bool flag);
|
||||
|
||||
void setAllowPause(bool flag);
|
||||
|
||||
static bool showGeneratorMinimap;
|
||||
|
||||
/// @brief Runtime updating debug visualization texture
|
||||
|
||||
@ -36,9 +36,10 @@
|
||||
|
||||
static debug::Logger logger("level-screen");
|
||||
|
||||
LevelScreen::LevelScreen(Engine& engine, std::unique_ptr<Level> levelPtr)
|
||||
: Screen(engine), postProcessing(std::make_unique<PostProcessing>())
|
||||
{
|
||||
LevelScreen::LevelScreen(
|
||||
Engine& engine, std::unique_ptr<Level> levelPtr, int64_t localPlayer
|
||||
)
|
||||
: Screen(engine), postProcessing(std::make_unique<PostProcessing>()) {
|
||||
Level* level = levelPtr.get();
|
||||
|
||||
auto& settings = engine.getSettings();
|
||||
@ -46,7 +47,9 @@ LevelScreen::LevelScreen(Engine& engine, std::unique_ptr<Level> levelPtr)
|
||||
auto menu = engine.getGUI()->getMenu();
|
||||
menu->reset();
|
||||
|
||||
auto player = level->players->get(0);
|
||||
auto player = level->players->get(localPlayer);
|
||||
assert(player != nullptr);
|
||||
|
||||
controller =
|
||||
std::make_unique<LevelController>(&engine, std::move(levelPtr), player);
|
||||
playerController = std::make_unique<PlayerController>(
|
||||
@ -165,14 +168,16 @@ void LevelScreen::updateHotkeys() {
|
||||
|
||||
void LevelScreen::update(float delta) {
|
||||
gui::GUI* gui = engine.getGUI();
|
||||
auto menu = gui->getMenu();
|
||||
|
||||
bool inputLocked = hud->isPause() ||
|
||||
bool inputLocked = menu->hasOpenPage() ||
|
||||
hud->isInventoryOpen() ||
|
||||
gui->isFocusCaught();
|
||||
if (!gui->isFocusCaught()) {
|
||||
updateHotkeys();
|
||||
}
|
||||
|
||||
auto level = controller->getLevel();
|
||||
auto player = playerController->getPlayer();
|
||||
auto camera = player->currentCamera;
|
||||
|
||||
@ -189,7 +194,6 @@ void LevelScreen::update(float delta) {
|
||||
camera->dir,
|
||||
glm::vec3(0, 1, 0)
|
||||
);
|
||||
auto level = controller->getLevel();
|
||||
const auto& settings = engine.getSettings();
|
||||
|
||||
if (!hud->isPause()) {
|
||||
|
||||
@ -34,7 +34,9 @@ class LevelScreen : public Screen {
|
||||
void initializeContent();
|
||||
void initializePack(ContentPackRuntime* pack);
|
||||
public:
|
||||
LevelScreen(Engine& engine, std::unique_ptr<Level> level);
|
||||
LevelScreen(
|
||||
Engine& engine, std::unique_ptr<Level> level, int64_t localPlayer
|
||||
);
|
||||
~LevelScreen();
|
||||
|
||||
void update(float delta) override;
|
||||
|
||||
@ -12,6 +12,7 @@
|
||||
#include "window/Camera.hpp"
|
||||
#include "objects/Player.hpp"
|
||||
#include "objects/Players.hpp"
|
||||
#include "objects/Entities.hpp"
|
||||
#include "logic/LevelController.hpp"
|
||||
#include "util/stringutil.hpp"
|
||||
#include "engine/Engine.hpp"
|
||||
@ -156,9 +157,14 @@ void Decorator::update(float delta, const Camera& camera) {
|
||||
auto note = renderer.texts->get(textsIter->second);
|
||||
auto player = level.players->get(textsIter->first);
|
||||
if (player == nullptr) {
|
||||
renderer.texts->remove(textsIter->second);
|
||||
textsIter = playerTexts.erase(textsIter);
|
||||
} else {
|
||||
note->setPosition(player->getPosition() + glm::vec3(0, 1, 0));
|
||||
glm::vec3 position = player->getPosition();
|
||||
if (auto entity = level.entities->get(player->getEntity())) {
|
||||
position = entity->getInterpolatedPosition();
|
||||
}
|
||||
note->setPosition(position + glm::vec3(0, 1, 0));
|
||||
++textsIter;
|
||||
}
|
||||
}
|
||||
|
||||
@ -274,8 +274,9 @@ void WorldRenderer::renderHands(
|
||||
glm::mat4(1.0f), -glm::pi<float>() * 0.5f, glm::vec3(0, 1, 0)
|
||||
);
|
||||
prevRotation = rotation;
|
||||
glm::vec3 cameraRotation = player.getRotation();
|
||||
auto offset = -(camera.position - player.getPosition());
|
||||
float angle = glm::radians(player.rotation.x - 90);
|
||||
float angle = glm::radians(cameraRotation.x - 90);
|
||||
float cos = glm::cos(angle);
|
||||
float sin = glm::sin(angle);
|
||||
|
||||
|
||||
@ -166,7 +166,7 @@ void GUI::actFocused() {
|
||||
focus->keyPressed(key);
|
||||
}
|
||||
|
||||
if (!Events::_cursor_locked) {
|
||||
if (!Events::isCursorLocked()) {
|
||||
if (Events::clicked(mousecode::BUTTON_1) &&
|
||||
(Events::jclicked(mousecode::BUTTON_1) || Events::delta.x || Events::delta.y))
|
||||
{
|
||||
@ -178,18 +178,12 @@ void GUI::actFocused() {
|
||||
}
|
||||
|
||||
void GUI::act(float delta, const Viewport& vp) {
|
||||
while (!postRunnables.empty()) {
|
||||
runnable callback = postRunnables.back();
|
||||
postRunnables.pop();
|
||||
callback();
|
||||
}
|
||||
|
||||
container->setSize(vp.size());
|
||||
container->act(delta);
|
||||
auto prevfocus = focus;
|
||||
|
||||
updateTooltip(delta);
|
||||
if (!Events::_cursor_locked) {
|
||||
if (!Events::isCursorLocked()) {
|
||||
actMouse(delta);
|
||||
} else {
|
||||
if (hover) {
|
||||
@ -206,6 +200,14 @@ void GUI::act(float delta, const Viewport& vp) {
|
||||
}
|
||||
}
|
||||
|
||||
void GUI::postAct() {
|
||||
while (!postRunnables.empty()) {
|
||||
runnable callback = postRunnables.back();
|
||||
postRunnables.pop();
|
||||
callback();
|
||||
}
|
||||
}
|
||||
|
||||
void GUI::draw(const DrawContext& pctx, const Assets& assets) {
|
||||
auto ctx = pctx.sub(batch2D.get());
|
||||
|
||||
|
||||
@ -106,6 +106,8 @@ namespace gui {
|
||||
/// @param assets active assets storage
|
||||
void draw(const DrawContext& pctx, const Assets& assets);
|
||||
|
||||
void postAct();
|
||||
|
||||
/// @brief Add element to the main container
|
||||
/// @param node UI element
|
||||
void add(std::shared_ptr<UINode> node);
|
||||
|
||||
@ -91,6 +91,10 @@ Page& Menu::getCurrent() {
|
||||
return current;
|
||||
}
|
||||
|
||||
bool Menu::hasOpenPage() const {
|
||||
return current.panel != nullptr;
|
||||
}
|
||||
|
||||
void Menu::clearHistory() {
|
||||
pageStack = std::stack<Page>();
|
||||
}
|
||||
|
||||
@ -64,5 +64,7 @@ namespace gui {
|
||||
|
||||
/// @brief Get current page
|
||||
Page& getCurrent();
|
||||
|
||||
bool hasOpenPage() const;
|
||||
};
|
||||
}
|
||||
|
||||
@ -23,6 +23,14 @@ int Panel::getMaxLength() const {
|
||||
return maxLength;
|
||||
}
|
||||
|
||||
void Panel::setMinLength(int value) {
|
||||
minLength = value;
|
||||
}
|
||||
|
||||
int Panel::getMinLength() const {
|
||||
return minLength;
|
||||
}
|
||||
|
||||
void Panel::setPadding(glm::vec4 padding) {
|
||||
this->padding = padding;
|
||||
refresh();
|
||||
@ -34,9 +42,11 @@ glm::vec4 Panel::getPadding() const {
|
||||
|
||||
void Panel::cropToContent() {
|
||||
if (maxLength > 0.0f) {
|
||||
setSize(glm::vec2(getSize().x, glm::min(maxLength, actualLength)));
|
||||
setSize(glm::vec2(
|
||||
getSize().x, glm::max(minLength, glm::min(maxLength, actualLength))
|
||||
));
|
||||
} else {
|
||||
setSize(glm::vec2(getSize().x, actualLength));
|
||||
setSize(glm::vec2(getSize().x, glm::max(minLength, actualLength)));
|
||||
}
|
||||
}
|
||||
|
||||
@ -52,6 +62,11 @@ void Panel::add(const std::shared_ptr<UINode> &node) {
|
||||
fullRefresh();
|
||||
}
|
||||
|
||||
void Panel::remove(const std::shared_ptr<UINode> &node) {
|
||||
Container::remove(node);
|
||||
fullRefresh();
|
||||
}
|
||||
|
||||
void Panel::refresh() {
|
||||
UINode::refresh();
|
||||
std::stable_sort(nodes.begin(), nodes.end(), [](auto a, auto b) {
|
||||
|
||||
@ -9,6 +9,7 @@ namespace gui {
|
||||
Orientation orientation = Orientation::vertical;
|
||||
glm::vec4 padding {2.0f};
|
||||
float interval = 2.0f;
|
||||
int minLength = 0;
|
||||
int maxLength = 0;
|
||||
public:
|
||||
Panel(
|
||||
@ -24,6 +25,7 @@ namespace gui {
|
||||
Orientation getOrientation() const;
|
||||
|
||||
virtual void add(const std::shared_ptr<UINode>& node) override;
|
||||
virtual void remove(const std::shared_ptr<UINode>& node) override;
|
||||
|
||||
virtual void refresh() override;
|
||||
virtual void fullRefresh() override;
|
||||
@ -31,6 +33,9 @@ namespace gui {
|
||||
virtual void setMaxLength(int value);
|
||||
int getMaxLength() const;
|
||||
|
||||
virtual void setMinLength(int value);
|
||||
int getMinLength() const;
|
||||
|
||||
virtual void setPadding(glm::vec4 padding);
|
||||
glm::vec4 getPadding() const;
|
||||
};
|
||||
|
||||
@ -289,12 +289,16 @@ const std::string& UINode::getId() const {
|
||||
}
|
||||
|
||||
void UINode::reposition() {
|
||||
if (sizefunc) {
|
||||
auto newSize = sizefunc();
|
||||
setSize(
|
||||
{newSize.x < 0 ? size.x : newSize.x,
|
||||
newSize.y < 0 ? size.y : newSize.y}
|
||||
);
|
||||
}
|
||||
if (positionfunc) {
|
||||
setPos(positionfunc());
|
||||
}
|
||||
if (sizefunc) {
|
||||
setSize(sizefunc());
|
||||
}
|
||||
}
|
||||
|
||||
void UINode::setGravity(Gravity gravity) {
|
||||
|
||||
@ -91,6 +91,9 @@ static void _readUINode(
|
||||
if (element.has("pos")) {
|
||||
node.setPos(element.attr("pos").asVec2());
|
||||
}
|
||||
if (element.has("min-size")) {
|
||||
node.setMinSize(element.attr("min-size").asVec2());
|
||||
}
|
||||
if (element.has("size")) {
|
||||
node.setSize(element.attr("size").asVec2());
|
||||
}
|
||||
@ -220,6 +223,9 @@ static void _readPanel(UiXmlReader& reader, const xml::xmlelement& element, Pane
|
||||
if (element.has("max-length")) {
|
||||
panel.setMaxLength(element.attr("max-length").asInt());
|
||||
}
|
||||
if (element.has("min-length")) {
|
||||
panel.setMinLength(element.attr("min-length").asInt());
|
||||
}
|
||||
if (element.has("orientation")) {
|
||||
auto &oname = element.attr("orientation").getText();
|
||||
if (oname == "horizontal") {
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
#include "markdown.hpp"
|
||||
|
||||
#include "coders/commons.hpp"
|
||||
#include "graphics/core/Font.hpp"
|
||||
|
||||
using namespace markdown;
|
||||
@ -9,7 +10,7 @@ static inline void emit(
|
||||
CharT c, FontStylesScheme& styles, std::basic_stringstream<CharT>& ss
|
||||
) {
|
||||
ss << c;
|
||||
styles.map.emplace_back(styles.palette.size()-1);
|
||||
styles.map.emplace_back(styles.palette.size() - 1);
|
||||
}
|
||||
|
||||
template <typename CharT>
|
||||
@ -20,6 +21,38 @@ static inline void emit_md(
|
||||
styles.map.emplace_back(0);
|
||||
}
|
||||
|
||||
template <typename CharT>
|
||||
static glm::vec4 parse_color(const std::basic_string_view<CharT>& color_code) {
|
||||
if (color_code.size() != 8 || color_code[0] != '#') {
|
||||
return glm::vec4(1, 1, 1, 1); // default to white
|
||||
}
|
||||
|
||||
auto hex_to_float = [](char high, char low) {
|
||||
int high_val = hexchar2int(high);
|
||||
int low_val = hexchar2int(low);
|
||||
if (high_val == -1 || low_val == -1) {
|
||||
return 1.0f; // default to max value on error
|
||||
}
|
||||
return (high_val * 16 + low_val) / 255.0f;
|
||||
};
|
||||
|
||||
return glm::vec4(
|
||||
hex_to_float(color_code[1], color_code[2]),
|
||||
hex_to_float(color_code[3], color_code[4]),
|
||||
hex_to_float(color_code[5], color_code[6]),
|
||||
1
|
||||
);
|
||||
}
|
||||
|
||||
template <typename CharT>
|
||||
static inline void apply_color(
|
||||
const std::basic_string_view<CharT>& color_code, FontStylesScheme& styles
|
||||
) {
|
||||
FontStyle style = styles.palette.back();
|
||||
style.color = parse_color(color_code);
|
||||
styles.palette.push_back(style);
|
||||
}
|
||||
|
||||
template <typename CharT>
|
||||
static inline void restyle(
|
||||
CharT c,
|
||||
@ -43,13 +76,14 @@ Result<CharT> process_markdown(
|
||||
std::basic_stringstream<CharT> ss;
|
||||
FontStylesScheme styles {
|
||||
// markdown default
|
||||
{{false, false, false, false, glm::vec4(1,1,1,0.5f)}, {}},
|
||||
{{false, false, false, false, glm::vec4(1, 1, 1, 0.5f)}, {}},
|
||||
{}
|
||||
};
|
||||
FontStyle style;
|
||||
int pos = 0;
|
||||
while (pos < source.size()) {
|
||||
CharT first = source[pos];
|
||||
|
||||
if (first == '\\') {
|
||||
if (pos + 1 < source.size()) {
|
||||
CharT second = source[++pos];
|
||||
@ -63,14 +97,37 @@ Result<CharT> process_markdown(
|
||||
emit(second, styles, ss);
|
||||
pos++;
|
||||
continue;
|
||||
case '[':
|
||||
if (pos + 9 < source.size() && source[pos + 1] == '#' &&
|
||||
source[pos + 8] == ']') {
|
||||
if (!eraseMarkdown) {
|
||||
emit_md(source[pos - 1], styles, ss);
|
||||
}
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
|
||||
emit(source[pos + i], styles, ss);
|
||||
}
|
||||
|
||||
pos += 10;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
pos--;
|
||||
}
|
||||
} else if (first == '[' && pos + 9 <= source.size() && source[pos + 1] == '#' && source[pos + 8] == ']') {
|
||||
std::basic_string_view<CharT> color_code = source.substr(pos + 1, 8);
|
||||
apply_color(color_code, styles);
|
||||
if (!eraseMarkdown) {
|
||||
for (int i = 0; i < 9; ++i) {
|
||||
emit_md(source[pos + i], styles, ss);
|
||||
}
|
||||
}
|
||||
pos += 9; // Skip past the color code
|
||||
continue;
|
||||
} else if (first == '*') {
|
||||
if (pos + 1 < source.size() && source[pos+1] == '*') {
|
||||
if (pos + 1 < source.size() && source[pos + 1] == '*') {
|
||||
pos++;
|
||||
if (!eraseMarkdown)
|
||||
emit_md(first, styles, ss);
|
||||
if (!eraseMarkdown) emit_md(first, styles, ss);
|
||||
style.bold = !style.bold;
|
||||
restyle(first, style, styles, ss, pos, eraseMarkdown);
|
||||
continue;
|
||||
@ -78,17 +135,17 @@ Result<CharT> process_markdown(
|
||||
style.italic = !style.italic;
|
||||
restyle(first, style, styles, ss, pos, eraseMarkdown);
|
||||
continue;
|
||||
} else if (first == '_' && pos + 1 < source.size() && source[pos+1] == '_') {
|
||||
} else if (first == '_' && pos + 1 < source.size() &&
|
||||
source[pos + 1] == '_') {
|
||||
pos++;
|
||||
if (!eraseMarkdown)
|
||||
emit_md(first, styles, ss);
|
||||
if (!eraseMarkdown) emit_md(first, styles, ss);
|
||||
style.underline = !style.underline;
|
||||
restyle(first, style, styles, ss, pos, eraseMarkdown);
|
||||
continue;
|
||||
} else if (first == '~' && pos + 1 < source.size() && source[pos+1] == '~') {
|
||||
} else if (first == '~' && pos + 1 < source.size() &&
|
||||
source[pos + 1] == '~') {
|
||||
pos++;
|
||||
if (!eraseMarkdown)
|
||||
emit_md(first, styles, ss);
|
||||
if (!eraseMarkdown) emit_md(first, styles, ss);
|
||||
style.strikethrough = !style.strikethrough;
|
||||
restyle(first, style, styles, ss, pos, eraseMarkdown);
|
||||
continue;
|
||||
@ -106,6 +163,8 @@ Result<char> markdown::process(std::string_view source, bool eraseMarkdown) {
|
||||
return process_markdown(source, eraseMarkdown);
|
||||
}
|
||||
|
||||
Result<wchar_t> markdown::process(std::wstring_view source, bool eraseMarkdown) {
|
||||
Result<wchar_t> markdown::process(
|
||||
std::wstring_view source, bool eraseMarkdown
|
||||
) {
|
||||
return process_markdown(source, eraseMarkdown);
|
||||
}
|
||||
|
||||
@ -8,9 +8,12 @@
|
||||
#include "voxels/Block.hpp"
|
||||
#include "constants.hpp"
|
||||
#include "util/timeutil.hpp"
|
||||
#include "debug/Logger.hpp"
|
||||
|
||||
#include <memory>
|
||||
|
||||
static debug::Logger logger("lighting");
|
||||
|
||||
Lighting::Lighting(const Content& content, Chunks& chunks)
|
||||
: content(content), chunks(chunks) {
|
||||
auto& indices = *content.getIndices();
|
||||
@ -63,6 +66,10 @@ void Lighting::buildSkyLight(int cx, int cz){
|
||||
const auto blockDefs = content.getIndices()->blocks.getDefs();
|
||||
|
||||
Chunk* chunk = chunks.getChunk(cx, cz);
|
||||
if (chunk == nullptr) {
|
||||
logger.error() << "attempted to build sky lights to chunk missing in local matrix";
|
||||
return;
|
||||
}
|
||||
for (int z = 0; z < CHUNK_D; z++){
|
||||
for (int x = 0; x < CHUNK_W; x++){
|
||||
int gx = x + cx * CHUNK_W;
|
||||
@ -95,7 +102,10 @@ void Lighting::onChunkLoaded(int cx, int cz, bool expand) {
|
||||
|
||||
auto blockDefs = content.getIndices()->blocks.getDefs();
|
||||
auto chunk = chunks.getChunk(cx, cz);
|
||||
|
||||
if (chunk == nullptr) {
|
||||
logger.error() << "attempted to build lights to chunk missing in local matrix";
|
||||
return;
|
||||
}
|
||||
for (uint y = 0; y < CHUNK_H; y++){
|
||||
for (uint z = 0; z < CHUNK_D; z++){
|
||||
for (uint x = 0; x < CHUNK_W; x++){
|
||||
|
||||
@ -92,7 +92,7 @@ bool ChunksController::loadVisible(const Player& player, uint padding) const {
|
||||
}
|
||||
|
||||
const auto& chunk = chunks.getChunks()[nearZ * sizeX + nearX];
|
||||
if (chunk != nullptr || !assigned) {
|
||||
if (chunk != nullptr || !assigned || !player.isLoadingChunks()) {
|
||||
return false;
|
||||
}
|
||||
int offsetX = chunks.getOffsetX();
|
||||
@ -101,7 +101,9 @@ bool ChunksController::loadVisible(const Player& player, uint padding) const {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ChunksController::buildLights(const Player& player, const std::shared_ptr<Chunk>& chunk) const {
|
||||
bool ChunksController::buildLights(
|
||||
const Player& player, const std::shared_ptr<Chunk>& chunk
|
||||
) const {
|
||||
int surrounding = 0;
|
||||
for (int oz = -1; oz <= 1; oz++) {
|
||||
for (int ox = -1; ox <= 1; ox++) {
|
||||
|
||||
@ -143,7 +143,9 @@ static bool load_world_content(Engine& engine, const fs::path& folder) {
|
||||
}
|
||||
|
||||
static void load_world(
|
||||
Engine& engine, const std::shared_ptr<WorldFiles>& worldFiles
|
||||
Engine& engine,
|
||||
const std::shared_ptr<WorldFiles>& worldFiles,
|
||||
int64_t localPlayer
|
||||
) {
|
||||
try {
|
||||
auto content = engine.getContent();
|
||||
@ -151,7 +153,7 @@ static void load_world(
|
||||
auto& settings = engine.getSettings();
|
||||
|
||||
auto level = World::load(worldFiles, settings, *content, packs);
|
||||
engine.onWorldOpen(std::move(level));
|
||||
engine.onWorldOpen(std::move(level), localPlayer);
|
||||
} catch (const world_load_error& error) {
|
||||
guiutil::alert(
|
||||
engine,
|
||||
@ -233,7 +235,7 @@ void EngineController::openWorld(const std::string& name, bool confirmConvert) {
|
||||
}
|
||||
return;
|
||||
}
|
||||
load_world(engine, std::move(worldFiles));
|
||||
load_world(engine, std::move(worldFiles), localPlayer);
|
||||
}
|
||||
|
||||
inline uint64_t str2seed(const std::string& seedstr) {
|
||||
@ -279,9 +281,13 @@ void EngineController::createWorld(
|
||||
engine.getContentPacks()
|
||||
);
|
||||
if (!engine.isHeadless()) {
|
||||
level->players->create();
|
||||
level->players->create(localPlayer);
|
||||
}
|
||||
engine.onWorldOpen(std::move(level));
|
||||
engine.onWorldOpen(std::move(level), localPlayer);
|
||||
}
|
||||
|
||||
void EngineController::setLocalPlayer(int64_t player) {
|
||||
localPlayer = player;
|
||||
}
|
||||
|
||||
void EngineController::reopenWorld(World* world) {
|
||||
|
||||
@ -12,6 +12,7 @@ class LevelController;
|
||||
class EngineController {
|
||||
Engine& engine;
|
||||
|
||||
int64_t localPlayer = -1;
|
||||
void onMissingContent(const std::shared_ptr<ContentReport>& report);
|
||||
public:
|
||||
EngineController(Engine& engine);
|
||||
@ -37,5 +38,7 @@ public:
|
||||
const std::string& generatorID
|
||||
);
|
||||
|
||||
void setLocalPlayer(int64_t player);
|
||||
|
||||
void reopenWorld(World* world);
|
||||
};
|
||||
|
||||
@ -50,6 +50,10 @@ LevelController::LevelController(
|
||||
do {
|
||||
confirmed = 0;
|
||||
for (const auto& [_, player] : *level->players) {
|
||||
if (!player->isLoadingChunks()) {
|
||||
confirmed++;
|
||||
continue;
|
||||
}
|
||||
glm::vec3 position = player->getPosition();
|
||||
player->chunks->configure(
|
||||
std::floor(position.x), std::floor(position.z), 1
|
||||
@ -66,6 +70,11 @@ LevelController::LevelController(
|
||||
|
||||
void LevelController::update(float delta, bool pause) {
|
||||
for (const auto& [_, player] : *level->players) {
|
||||
if (player->isSuspended()) {
|
||||
continue;
|
||||
}
|
||||
player->rotationInterpolation.updateTimer(delta);
|
||||
player->updateEntity();
|
||||
glm::vec3 position = player->getPosition();
|
||||
player->chunks->configure(
|
||||
position.x,
|
||||
@ -85,6 +94,9 @@ void LevelController::update(float delta, bool pause) {
|
||||
level->entities->updatePhysics(delta);
|
||||
level->entities->update(delta);
|
||||
for (const auto& [_, player] : *level->players) {
|
||||
if (player->isSuspended()) {
|
||||
continue;
|
||||
}
|
||||
if (playerTickClock.update(delta)) {
|
||||
if (player->getId() % playerTickClock.getParts() ==
|
||||
playerTickClock.getPart()) {
|
||||
|
||||
@ -54,7 +54,7 @@ void CameraControl::refresh() {
|
||||
}
|
||||
|
||||
void CameraControl::updateMouse(PlayerInput& input) {
|
||||
glm::vec3& rotation = player.rotation;
|
||||
glm::vec3 rotation = player.getRotation();
|
||||
|
||||
float sensitivity =
|
||||
(input.zoom ? settings.sensitivity.get() / 4.f
|
||||
@ -75,6 +75,8 @@ void CameraControl::updateMouse(PlayerInput& input) {
|
||||
rotation.x += 360.f;
|
||||
}
|
||||
|
||||
player.setRotation(rotation);
|
||||
|
||||
camera->rotation = glm::mat4(1.0f);
|
||||
camera->rotate(
|
||||
glm::radians(rotation.y),
|
||||
@ -170,8 +172,8 @@ void CameraControl::update(
|
||||
switchCamera();
|
||||
}
|
||||
|
||||
auto& spCamera = player.spCamera;
|
||||
auto& tpCamera = player.tpCamera;
|
||||
const auto& spCamera = player.spCamera;
|
||||
const auto& tpCamera = player.tpCamera;
|
||||
|
||||
refresh();
|
||||
|
||||
@ -305,7 +307,6 @@ void PlayerController::resetKeyboard() {
|
||||
}
|
||||
|
||||
void PlayerController::updatePlayer(float delta) {
|
||||
player.updateEntity();
|
||||
player.updateInput(input, delta);
|
||||
}
|
||||
|
||||
@ -317,15 +318,15 @@ static int determine_rotation(
|
||||
if (name == "pipe") {
|
||||
if (norm.x < 0.0f)
|
||||
return BLOCK_DIR_WEST;
|
||||
else if (norm.x > 0.0f)
|
||||
if (norm.x > 0.0f)
|
||||
return BLOCK_DIR_EAST;
|
||||
else if (norm.y > 0.0f)
|
||||
if (norm.y > 0.0f)
|
||||
return BLOCK_DIR_UP;
|
||||
else if (norm.y < 0.0f)
|
||||
if (norm.y < 0.0f)
|
||||
return BLOCK_DIR_DOWN;
|
||||
else if (norm.z > 0.0f)
|
||||
if (norm.z > 0.0f)
|
||||
return BLOCK_DIR_NORTH;
|
||||
else if (norm.z < 0.0f)
|
||||
if (norm.z < 0.0f)
|
||||
return BLOCK_DIR_SOUTH;
|
||||
} else if (name == "pane") {
|
||||
if (abs(camDir.x) > abs(camDir.z)) {
|
||||
|
||||
@ -17,7 +17,7 @@ static int l_set_vel(lua::State* L) {
|
||||
|
||||
static int l_is_enabled(lua::State* L) {
|
||||
if (auto entity = get_entity(L, 1)) {
|
||||
lua::pushboolean(L, entity->getRigidbody().enabled);
|
||||
return lua::pushboolean(L, entity->getRigidbody().enabled);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -130,6 +130,22 @@ static int l_set_color(lua::State* L) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_is_interpolated(lua::State* L) {
|
||||
if (auto entity = get_entity(L, 1)) {
|
||||
auto& skeleton = entity->getSkeleton();
|
||||
return lua::pushboolean(L, skeleton.interpolation.isEnabled());
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_set_interpolated(lua::State* L) {
|
||||
if (auto entity = get_entity(L, 1)) {
|
||||
auto& skeleton = entity->getSkeleton();
|
||||
skeleton.interpolation.setEnabled(lua::toboolean(L, 2));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
const luaL_Reg skeletonlib[] = {
|
||||
{"get_model", lua::wrap<l_get_model>},
|
||||
{"set_model", lua::wrap<l_set_model>},
|
||||
@ -142,4 +158,6 @@ const luaL_Reg skeletonlib[] = {
|
||||
{"set_visible", lua::wrap<l_set_visible>},
|
||||
{"get_color", lua::wrap<l_get_color>},
|
||||
{"set_color", lua::wrap<l_set_color>},
|
||||
{"is_interpolated", lua::wrap<l_is_interpolated>},
|
||||
{"set_interpolated", lua::wrap<l_set_interpolated>},
|
||||
{NULL, NULL}};
|
||||
|
||||
@ -198,8 +198,8 @@ static int l_tpack(lua::State* L) {
|
||||
}
|
||||
|
||||
const luaL_Reg byteutillib[] = {
|
||||
{"pack", l_pack},
|
||||
{"tpack", l_tpack},
|
||||
{"unpack", l_unpack},
|
||||
{"pack", lua::wrap<l_pack>},
|
||||
{"tpack", lua::wrap<l_tpack>},
|
||||
{"unpack", lua::wrap<l_unpack>},
|
||||
{NULL, NULL}
|
||||
};
|
||||
|
||||
@ -55,10 +55,15 @@ static int l_new_world(lua::State* L) {
|
||||
auto name = lua::require_string(L, 1);
|
||||
auto seed = lua::require_string(L, 2);
|
||||
auto generator = lua::require_string(L, 3);
|
||||
int64_t localPlayer = 0;
|
||||
if (lua::gettop(L) >= 4) {
|
||||
localPlayer = lua::tointeger(L, 4);
|
||||
}
|
||||
if (level != nullptr) {
|
||||
throw std::runtime_error("world must be closed before");
|
||||
}
|
||||
auto controller = engine->getController();
|
||||
controller->setLocalPlayer(localPlayer);
|
||||
controller->createWorld(name, seed, generator);
|
||||
return 0;
|
||||
}
|
||||
@ -71,6 +76,7 @@ static int l_open_world(lua::State* L) {
|
||||
throw std::runtime_error("world must be closed before");
|
||||
}
|
||||
auto controller = engine->getController();
|
||||
controller->setLocalPlayer(0);
|
||||
controller->openWorld(name, false);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -54,7 +54,7 @@ static int l_get_def(lua::State* L) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_show(lua::State* L) {
|
||||
static int l_spawn(lua::State* L) {
|
||||
auto level = controller->getLevel();
|
||||
auto defname = lua::tostring(L, 1);
|
||||
auto& def = content->entities.require(defname);
|
||||
@ -81,6 +81,15 @@ static int l_get_skeleton(lua::State* L) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_get_player(lua::State* L) {
|
||||
entityid_t eid = lua::touinteger(L, 1);
|
||||
auto level = controller->getLevel();
|
||||
if (auto entity = level->entities->get(eid)) {
|
||||
return lua::pushinteger(L, entity->getPlayer());
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_set_skeleton(lua::State* L) {
|
||||
if (auto entity = get_entity(L, 1)) {
|
||||
std::string skeletonName = lua::require_string(L, 2);
|
||||
@ -221,10 +230,11 @@ const luaL_Reg entitylib[] = {
|
||||
{"def_hitbox", lua::wrap<l_def_hitbox>},
|
||||
{"get_def", lua::wrap<l_get_def>},
|
||||
{"defs_count", lua::wrap<l_defs_count>},
|
||||
{"spawn", lua::wrap<l_show>},
|
||||
{"spawn", lua::wrap<l_spawn>},
|
||||
{"despawn", lua::wrap<l_despawn>},
|
||||
{"get_skeleton", lua::wrap<l_get_skeleton>},
|
||||
{"set_skeleton", lua::wrap<l_set_skeleton>},
|
||||
{"get_player", lua::wrap<l_get_player>},
|
||||
{"get_all_in_box", lua::wrap<l_get_all_in_box>},
|
||||
{"get_all_in_radius", lua::wrap<l_get_all_in_radius>},
|
||||
{"raycast", lua::wrap<l_raycast>},
|
||||
|
||||
@ -101,6 +101,12 @@ static int l_node_destruct(lua::State* L) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_node_reposition(lua::State* L) {
|
||||
auto docnode = get_document_node(L);
|
||||
docnode.node->reposition();
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_container_clear(lua::State* L) {
|
||||
auto node = get_document_node(L, 1);
|
||||
if (auto container = std::dynamic_pointer_cast<Container>(node.node)) {
|
||||
@ -328,6 +334,10 @@ static int p_get_destruct(UINode*, lua::State* L) {
|
||||
return lua::pushcfunction(L, lua::wrap<l_node_destruct>);
|
||||
}
|
||||
|
||||
static int p_get_reposition(UINode*, lua::State* L) {
|
||||
return lua::pushcfunction(L, lua::wrap<l_node_reposition>);
|
||||
}
|
||||
|
||||
static int p_get_clear(UINode* node, lua::State* L) {
|
||||
if (dynamic_cast<Container*>(node)) {
|
||||
return lua::pushcfunction(L, lua::wrap<l_container_clear>);
|
||||
@ -424,6 +434,7 @@ static int l_gui_getattr(lua::State* L) {
|
||||
{"moveInto", p_move_into},
|
||||
{"add", p_get_add},
|
||||
{"destruct", p_get_destruct},
|
||||
{"reposition", p_get_reposition},
|
||||
{"clear", p_get_clear},
|
||||
{"setInterval", p_set_interval},
|
||||
{"placeholder", p_get_placeholder},
|
||||
|
||||
@ -170,6 +170,11 @@ static int l_set_debug_cheats(lua::State* L) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_set_allow_pause(lua::State* L) {
|
||||
hud->setAllowPause(lua::toboolean(L, 1));
|
||||
return 0;
|
||||
}
|
||||
|
||||
const luaL_Reg hudlib[] = {
|
||||
{"open_inventory", lua::wrap<l_open_inventory>},
|
||||
{"close_inventory", lua::wrap<l_close_inventory>},
|
||||
@ -187,5 +192,6 @@ const luaL_Reg hudlib[] = {
|
||||
{"_is_content_access", lua::wrap<l_is_content_access>},
|
||||
{"_set_content_access", lua::wrap<l_set_content_access>},
|
||||
{"_set_debug_cheats", lua::wrap<l_set_debug_cheats>},
|
||||
{"set_allow_pause", lua::wrap<l_set_allow_pause>},
|
||||
{NULL, NULL}
|
||||
};
|
||||
|
||||
@ -45,11 +45,6 @@ static int l_add_callback(lua::State* L) {
|
||||
handler = Events::keyCallbacks[key].add(actual_callback);
|
||||
}
|
||||
}
|
||||
|
||||
const auto& bind = Events::bindings.find(bindname);
|
||||
if (bind == Events::bindings.end()) {
|
||||
throw std::runtime_error("unknown binding " + util::quote(bindname));
|
||||
}
|
||||
auto callback = [=]() -> bool {
|
||||
if (!scripting::engine->getGUI()->isFocusCaught()) {
|
||||
return actual_callback();
|
||||
@ -57,6 +52,10 @@ static int l_add_callback(lua::State* L) {
|
||||
return false;
|
||||
};
|
||||
if (handler == nullptr) {
|
||||
const auto& bind = Events::bindings.find(bindname);
|
||||
if (bind == Events::bindings.end()) {
|
||||
throw std::runtime_error("unknown binding " + util::quote(bindname));
|
||||
}
|
||||
handler = bind->second.onactived.add(callback);
|
||||
}
|
||||
|
||||
|
||||
@ -73,7 +73,7 @@ static int l_connect(lua::State* L) {
|
||||
static int l_close(lua::State* L) {
|
||||
u64id_t id = lua::tointeger(L, 1);
|
||||
if (auto connection = engine->getNetwork().getConnection(id)) {
|
||||
connection->close();
|
||||
connection->close(true);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -52,6 +52,7 @@ static int l_set_vel(lua::State* L) {
|
||||
auto x = lua::tonumber(L, 2);
|
||||
auto y = lua::tonumber(L, 3);
|
||||
auto z = lua::tonumber(L, 4);
|
||||
|
||||
if (auto hitbox = player->getHitbox()) {
|
||||
hitbox->velocity = glm::vec3(x, y, z);
|
||||
}
|
||||
@ -60,7 +61,7 @@ static int l_set_vel(lua::State* L) {
|
||||
|
||||
static int l_get_rot(lua::State* L) {
|
||||
if (auto player = get_player(L, 1)) {
|
||||
return lua::pushvec_stack(L, player->rotation);
|
||||
return lua::pushvec_stack(L, player->getRotation(lua::toboolean(L, 2)));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@ -70,7 +71,7 @@ static int l_set_rot(lua::State* L) {
|
||||
if (!player) {
|
||||
return 0;
|
||||
}
|
||||
glm::vec3& rotation = player->rotation;
|
||||
glm::vec3 rotation = player->getRotation();
|
||||
|
||||
auto x = lua::tonumber(L, 2);
|
||||
auto y = lua::tonumber(L, 3);
|
||||
@ -81,6 +82,7 @@ static int l_set_rot(lua::State* L) {
|
||||
rotation.x = x;
|
||||
rotation.y = y;
|
||||
rotation.z = z;
|
||||
player->setRotation(rotation);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -272,15 +274,33 @@ static int l_set_name(lua::State* L) {
|
||||
}
|
||||
|
||||
static int l_create(lua::State* L) {
|
||||
auto player = level->players->create();
|
||||
if (lua::isstring(L, 1)) {
|
||||
player->setName(lua::require_string(L, 1));
|
||||
int64_t playerId = Players::NONE;
|
||||
if (lua::gettop(L) >= 2) {
|
||||
playerId = lua::tointeger(L, 2);
|
||||
}
|
||||
auto player = level->players->create(playerId);
|
||||
player->setName(lua::require_string(L, 1));
|
||||
return lua::pushinteger(L, player->getId());
|
||||
}
|
||||
|
||||
static int l_delete(lua::State* L) {
|
||||
level->players->remove(lua::tointeger(L, 1));
|
||||
auto id = lua::tointeger(L, 1);
|
||||
level->players->suspend(id);
|
||||
level->players->remove(id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_is_suspended(lua::State* L) {
|
||||
if (auto player = get_player(L, 1)) {
|
||||
return lua::pushboolean(L, player->isSuspended());
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int l_set_suspended(lua::State* L) {
|
||||
if (auto player = get_player(L, 1)) {
|
||||
player->setSuspended(lua::toboolean(L, 2));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -293,6 +313,8 @@ const luaL_Reg playerlib[] = {
|
||||
{"set_rot", lua::wrap<l_set_rot>},
|
||||
{"get_dir", lua::wrap<l_get_dir>},
|
||||
{"get_inventory", lua::wrap<l_get_inv>},
|
||||
{"is_suspended", lua::wrap<l_is_suspended>},
|
||||
{"set_suspended", lua::wrap<l_set_suspended>},
|
||||
{"is_flight", lua::wrap<l_is_flight>},
|
||||
{"set_flight", lua::wrap<l_set_flight>},
|
||||
{"is_noclip", lua::wrap<l_is_noclip>},
|
||||
|
||||
@ -160,13 +160,13 @@ static void integrate_chunk_client(Chunk& chunk) {
|
||||
static int l_set_chunk_data(lua::State* L) {
|
||||
int x = static_cast<int>(lua::tointeger(L, 1));
|
||||
int z = static_cast<int>(lua::tointeger(L, 2));
|
||||
auto buffer = lua::touserdata<lua::LuaBytearray>(L, 3);
|
||||
auto buffer = lua::require_bytearray(L, 3);
|
||||
auto chunk = level->chunks->getChunk(x, z);
|
||||
if (chunk == nullptr) {
|
||||
return 0;
|
||||
return lua::pushboolean(L, false);
|
||||
}
|
||||
compressed_chunks::decode(
|
||||
*chunk, buffer->data().data(), buffer->data().size()
|
||||
*chunk, buffer.data(), buffer.size()
|
||||
);
|
||||
if (controller->getChunksController()->lighting == nullptr) {
|
||||
return lua::pushboolean(L, true);
|
||||
|
||||
@ -133,6 +133,13 @@ dv::value lua::tovalue(State* L, int idx) {
|
||||
return map;
|
||||
}
|
||||
}
|
||||
case LUA_TUSERDATA: {
|
||||
if (auto bytes = touserdata<LuaBytearray>(L, idx)) {
|
||||
const auto& data = bytes->data();
|
||||
return std::make_shared<dv::objects::Bytes>(data.data(), data.size());
|
||||
}
|
||||
[[fallthrough]];
|
||||
}
|
||||
default:
|
||||
throw std::runtime_error(
|
||||
"lua type " + std::string(lua_typename(L, type)) +
|
||||
|
||||
@ -19,6 +19,7 @@ namespace lua {
|
||||
int userdata_destructor(lua::State* L);
|
||||
|
||||
std::string env_name(int env);
|
||||
void dump_stack(lua::State*);
|
||||
|
||||
inline bool getglobal(lua::State* L, const std::string& name) {
|
||||
lua_getglobal(L, name.c_str());
|
||||
@ -208,7 +209,7 @@ namespace lua {
|
||||
return lua_isnumber(L, idx);
|
||||
}
|
||||
inline bool isstring(lua::State* L, int idx) {
|
||||
return lua_isstring(L, idx);
|
||||
return lua_type(L, idx) == LUA_TSTRING;
|
||||
}
|
||||
inline bool istable(lua::State* L, int idx) {
|
||||
return lua_istable(L, idx);
|
||||
@ -226,6 +227,11 @@ namespace lua {
|
||||
return lua_toboolean(L, idx);
|
||||
}
|
||||
inline lua::Integer tointeger(lua::State* L, int idx) {
|
||||
#ifndef NDEBUG
|
||||
if (lua_type(L, idx) == LUA_TSTRING) {
|
||||
throw std::runtime_error("integer expected, got string");
|
||||
}
|
||||
#endif
|
||||
return lua_tointeger(L, idx);
|
||||
}
|
||||
inline uint64_t touinteger(lua::State* L, int idx) {
|
||||
@ -236,6 +242,11 @@ namespace lua {
|
||||
return static_cast<uint64_t>(val);
|
||||
}
|
||||
inline lua::Number tonumber(lua::State* L, int idx) {
|
||||
#ifndef NDEBUG
|
||||
if (lua_type(L, idx) != LUA_TNUMBER && !lua_isnoneornil(L, idx)) {
|
||||
throw std::runtime_error("integer expected");
|
||||
}
|
||||
#endif
|
||||
return lua_tonumber(L, idx);
|
||||
}
|
||||
inline const char* tostring(lua::State* L, int idx) {
|
||||
@ -588,7 +599,6 @@ namespace lua {
|
||||
}
|
||||
int create_environment(lua::State*, int parent);
|
||||
void remove_environment(lua::State*, int id);
|
||||
void dump_stack(lua::State*);
|
||||
|
||||
inline void close(lua::State* L) {
|
||||
lua_close(L);
|
||||
|
||||
@ -400,10 +400,15 @@ public:
|
||||
return readBatch.size();
|
||||
}
|
||||
|
||||
void close() override {
|
||||
if (state != ConnectionState::CLOSED) {
|
||||
shutdown(descriptor, 2);
|
||||
closesocket(descriptor);
|
||||
void close(bool discardAll=false) override {
|
||||
{
|
||||
std::lock_guard lock(mutex);
|
||||
readBatch.clear();
|
||||
|
||||
if (state != ConnectionState::CLOSED) {
|
||||
shutdown(descriptor, 2);
|
||||
closesocket(descriptor);
|
||||
}
|
||||
}
|
||||
if (thread) {
|
||||
thread->join();
|
||||
|
||||
@ -49,7 +49,7 @@ namespace network {
|
||||
virtual void connect(runnable callback) = 0;
|
||||
virtual int recv(char* buffer, size_t length) = 0;
|
||||
virtual int send(const char* buffer, size_t length) = 0;
|
||||
virtual void close() = 0;
|
||||
virtual void close(bool discardAll=false) = 0;
|
||||
virtual int available() = 0;
|
||||
|
||||
virtual size_t pullUpload() = 0;
|
||||
|
||||
@ -38,6 +38,18 @@ void Transform::refresh() {
|
||||
dirty = false;
|
||||
}
|
||||
|
||||
void Entity::setInterpolatedPosition(const glm::vec3& position) {
|
||||
getSkeleton().interpolation.refresh(position);
|
||||
}
|
||||
|
||||
glm::vec3 Entity::getInterpolatedPosition() const {
|
||||
const auto& skeleton = getSkeleton();
|
||||
if (skeleton.interpolation.isEnabled()) {
|
||||
return skeleton.interpolation.getCurrent();
|
||||
}
|
||||
return getTransform().pos;
|
||||
}
|
||||
|
||||
void Entity::destroy() {
|
||||
if (isValid()) {
|
||||
entities.despawn(id);
|
||||
@ -561,11 +573,14 @@ void Entities::render(
|
||||
if (transform.dirty) {
|
||||
transform.refresh();
|
||||
}
|
||||
if (skeleton.interpolation.isEnabled()) {
|
||||
skeleton.interpolation.updateTimer(delta);
|
||||
}
|
||||
const auto& pos = transform.pos;
|
||||
const auto& size = transform.size;
|
||||
if (!frustum || frustum->isBoxVisible(pos - size, pos + size)) {
|
||||
const auto* rigConfig = skeleton.config;
|
||||
rigConfig->render(assets, batch, skeleton, transform.combined);
|
||||
rigConfig->render(assets, batch, skeleton, transform.combined, pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -34,6 +34,7 @@ struct EntityId {
|
||||
entityid_t uid;
|
||||
const EntityDef& def;
|
||||
bool destroyFlag = false;
|
||||
int64_t player = -1;
|
||||
};
|
||||
|
||||
struct Transform {
|
||||
@ -161,6 +162,18 @@ public:
|
||||
return entity;
|
||||
}
|
||||
|
||||
int64_t getPlayer() const {
|
||||
return registry.get<EntityId>(entity).player;
|
||||
}
|
||||
|
||||
void setPlayer(int64_t id) {
|
||||
registry.get<EntityId>(entity).player = id;
|
||||
}
|
||||
|
||||
void setInterpolatedPosition(const glm::vec3& position);
|
||||
|
||||
glm::vec3 getInterpolatedPosition() const;
|
||||
|
||||
void destroy();
|
||||
};
|
||||
|
||||
|
||||
@ -65,8 +65,14 @@ void Player::updateEntity() {
|
||||
if (eid == 0) {
|
||||
auto& def = level.content.entities.require("base:player");
|
||||
eid = level.entities->spawn(def, getPosition());
|
||||
if (auto entity = level.entities->get(eid)) {
|
||||
entity->setPlayer(id);
|
||||
}
|
||||
} else if (auto entity = level.entities->get(eid)) {
|
||||
position = entity->getTransform().pos;
|
||||
if (auto entity = level.entities->get(eid)) {
|
||||
entity->setPlayer(id);
|
||||
}
|
||||
} else if (chunks->getChunkByVoxel(position)) {
|
||||
logger.error() << "player entity despawned or deleted; "
|
||||
"will be respawned";
|
||||
@ -164,23 +170,9 @@ void Player::postUpdate() {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: ERASE & FORGET
|
||||
auto& skeleton = entity->getSkeleton();
|
||||
|
||||
skeleton.visible = currentCamera != fpCamera;
|
||||
|
||||
auto body = skeleton.config->find("body");
|
||||
auto head = skeleton.config->find("head");
|
||||
|
||||
if (body) {
|
||||
skeleton.pose.matrices[body->getIndex()] = glm::rotate(
|
||||
glm::mat4(1.0f), glm::radians(rotation.x), glm::vec3(0, 1, 0)
|
||||
);
|
||||
}
|
||||
if (head) {
|
||||
skeleton.pose.matrices[head->getIndex()] = glm::rotate(
|
||||
glm::mat4(1.0f), glm::radians(rotation.y), glm::vec3(1, 0, 0)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void Player::teleport(glm::vec3 position) {
|
||||
@ -189,6 +181,7 @@ void Player::teleport(glm::vec3 position) {
|
||||
if (auto entity = level.entities->get(eid)) {
|
||||
entity->getRigidbody().hitbox.position = position;
|
||||
entity->getTransform().setPos(position);
|
||||
entity->setInterpolatedPosition(position);
|
||||
}
|
||||
}
|
||||
|
||||
@ -224,6 +217,14 @@ float Player::getSpeed() const {
|
||||
return speed;
|
||||
}
|
||||
|
||||
bool Player::isSuspended() const {
|
||||
return suspended;
|
||||
}
|
||||
|
||||
void Player::setSuspended(bool flag) {
|
||||
suspended = flag;
|
||||
}
|
||||
|
||||
bool Player::isFlight() const {
|
||||
return flight;
|
||||
}
|
||||
@ -296,6 +297,18 @@ glm::vec3 Player::getSpawnPoint() const {
|
||||
return spawnpoint;
|
||||
}
|
||||
|
||||
glm::vec3 Player::getRotation(bool interpolated) const {
|
||||
if (interpolated) {
|
||||
return rotationInterpolation.getCurrent();
|
||||
}
|
||||
return rotation;
|
||||
}
|
||||
|
||||
void Player::setRotation(const glm::vec3& rotation) {
|
||||
this->rotation = rotation;
|
||||
rotationInterpolation.refresh(rotation);
|
||||
}
|
||||
|
||||
dv::value Player::serialize() const {
|
||||
auto root = dv::object();
|
||||
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
#include "interfaces/Serializable.hpp"
|
||||
#include "settings.hpp"
|
||||
#include "voxels/voxel.hpp"
|
||||
#include "util/Interpolation.hpp"
|
||||
|
||||
class Chunks;
|
||||
class Camera;
|
||||
@ -47,6 +48,7 @@ class Player : public Serializable {
|
||||
glm::vec3 position;
|
||||
glm::vec3 spawnpoint {};
|
||||
std::shared_ptr<Inventory> inventory;
|
||||
bool suspended = false;
|
||||
bool flight = false;
|
||||
bool noclip = false;
|
||||
bool infiniteItems = true;
|
||||
@ -54,11 +56,15 @@ class Player : public Serializable {
|
||||
bool loadingChunks = true;
|
||||
entityid_t eid;
|
||||
entityid_t selectedEid = 0;
|
||||
|
||||
glm::vec3 rotation {};
|
||||
public:
|
||||
util::VecInterpolation<3, float, true> rotationInterpolation {true};
|
||||
|
||||
std::unique_ptr<Chunks> chunks;
|
||||
std::shared_ptr<Camera> fpCamera, spCamera, tpCamera;
|
||||
std::shared_ptr<Camera> currentCamera;
|
||||
glm::vec3 rotation {};
|
||||
|
||||
CursorSelection selection {};
|
||||
|
||||
Player(
|
||||
@ -85,6 +91,9 @@ public:
|
||||
int getChosenSlot() const;
|
||||
float getSpeed() const;
|
||||
|
||||
bool isSuspended() const;
|
||||
void setSuspended(bool flag);
|
||||
|
||||
bool isFlight() const;
|
||||
void setFlight(bool flag);
|
||||
|
||||
@ -119,6 +128,9 @@ public:
|
||||
void setSpawnPoint(glm::vec3 point);
|
||||
glm::vec3 getSpawnPoint() const;
|
||||
|
||||
glm::vec3 getRotation(bool interpolated=false) const;
|
||||
void setRotation(const glm::vec3& rotation);
|
||||
|
||||
dv::value serialize() const override;
|
||||
void deserialize(const dv::value& src) override;
|
||||
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
#include "items/Inventories.hpp"
|
||||
#include "world/Level.hpp"
|
||||
#include "world/World.hpp"
|
||||
#include "objects/Entities.hpp"
|
||||
|
||||
Players::Players(Level& level) : level(level) {}
|
||||
|
||||
@ -19,10 +20,19 @@ Player* Players::get(int64_t id) const {
|
||||
return found->second.get();
|
||||
}
|
||||
|
||||
Player* Players::create() {
|
||||
Player* Players::create(int64_t id) {
|
||||
int64_t& nextPlayerID = level.getWorld()->getInfo().nextPlayerId;
|
||||
if (id == NONE) {
|
||||
id = nextPlayerID++;
|
||||
} else {
|
||||
if (auto player = get(id)) {
|
||||
return player;
|
||||
}
|
||||
nextPlayerID = std::max(id + 1, nextPlayerID);
|
||||
}
|
||||
auto playerPtr = std::make_unique<Player>(
|
||||
level,
|
||||
level.getWorld()->getInfo().nextPlayerId++,
|
||||
id,
|
||||
"",
|
||||
glm::vec3(0, DEF_PLAYER_Y, 0),
|
||||
DEF_PLAYER_SPEED,
|
||||
@ -36,6 +46,26 @@ Player* Players::create() {
|
||||
return player;
|
||||
}
|
||||
|
||||
void Players::suspend(int64_t id) {
|
||||
if (auto player = get(id)) {
|
||||
if (player->isSuspended()) {
|
||||
return;
|
||||
}
|
||||
player->setSuspended(true);
|
||||
level.entities->despawn(player->getEntity());
|
||||
player->setEntity(0);
|
||||
}
|
||||
}
|
||||
|
||||
void Players::resume(int64_t id) {
|
||||
if (auto player = get(id)) {
|
||||
if (!player->isSuspended()) {
|
||||
return;
|
||||
}
|
||||
player->setSuspended(false);
|
||||
}
|
||||
}
|
||||
|
||||
void Players::remove(int64_t id) {
|
||||
players.erase(id);
|
||||
}
|
||||
|
||||
@ -14,6 +14,9 @@ class Level;
|
||||
class Player;
|
||||
|
||||
class Players : public Serializable {
|
||||
public:
|
||||
static inline int64_t NONE = -1;
|
||||
private:
|
||||
Level& level;
|
||||
std::unordered_map<int64_t, std::unique_ptr<Player>> players;
|
||||
|
||||
@ -23,7 +26,11 @@ public:
|
||||
|
||||
Player* get(int64_t id) const;
|
||||
|
||||
Player* create();
|
||||
Player* create(int64_t id=NONE);
|
||||
|
||||
void suspend(int64_t id);
|
||||
|
||||
void resume(int64_t id);
|
||||
|
||||
void remove(int64_t id);
|
||||
|
||||
|
||||
@ -6,9 +6,8 @@
|
||||
#include "graphics/commons/Model.hpp"
|
||||
#include "graphics/render/ModelBatch.hpp"
|
||||
|
||||
#define GLM_ENABLE_EXPERIMENTAL
|
||||
#include <glm/ext/matrix_transform.hpp>
|
||||
#include <glm/gtx/norm.hpp>
|
||||
#include <glm/gtx/matrix_decompose.hpp>
|
||||
|
||||
using namespace rigging;
|
||||
|
||||
@ -69,7 +68,7 @@ SkeletonConfig::SkeletonConfig(
|
||||
}
|
||||
|
||||
size_t SkeletonConfig::update(
|
||||
size_t index, Skeleton& skeleton, Bone* node, glm::mat4 matrix
|
||||
size_t index, Skeleton& skeleton, Bone* node, const glm::mat4& matrix
|
||||
) const {
|
||||
auto boneMatrix = skeleton.pose.matrices[index];
|
||||
auto boneOffset = node->getOffset();
|
||||
@ -90,17 +89,32 @@ size_t SkeletonConfig::update(
|
||||
return count;
|
||||
}
|
||||
|
||||
void SkeletonConfig::update(Skeleton& skeleton, glm::mat4 matrix) const {
|
||||
update(0, skeleton, root.get(), matrix);
|
||||
void SkeletonConfig::update(
|
||||
Skeleton& skeleton, const glm::mat4& matrix, const glm::vec3& position
|
||||
) const {
|
||||
if (skeleton.interpolation.isEnabled()) {
|
||||
const auto& interpolation = skeleton.interpolation;
|
||||
glm::vec3 scale, translation, skew;
|
||||
glm::quat rotation;
|
||||
glm::vec4 perspective;
|
||||
glm::decompose(matrix, scale, rotation, translation, skew, perspective);
|
||||
|
||||
auto delta = interpolation.getCurrent() - position;
|
||||
auto interpolatedMatrix = glm::translate(matrix, delta);
|
||||
update(0, skeleton, root.get(), interpolatedMatrix);
|
||||
} else {
|
||||
update(0, skeleton, root.get(), matrix);
|
||||
}
|
||||
}
|
||||
|
||||
void SkeletonConfig::render(
|
||||
const Assets& assets,
|
||||
ModelBatch& batch,
|
||||
Skeleton& skeleton,
|
||||
const glm::mat4& matrix
|
||||
const glm::mat4& matrix,
|
||||
const glm::vec3& position
|
||||
) const {
|
||||
update(skeleton, matrix);
|
||||
update(skeleton, matrix, position);
|
||||
|
||||
if (!skeleton.visible) {
|
||||
return;
|
||||
|
||||
@ -1,12 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#define GLM_ENABLE_EXPERIMENTAL
|
||||
#include <glm/gtx/norm.hpp>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <cmath>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "typedefs.hpp"
|
||||
#include "util/Interpolation.hpp"
|
||||
|
||||
class Assets;
|
||||
class ModelBatch;
|
||||
@ -83,6 +86,8 @@ namespace rigging {
|
||||
bool visible;
|
||||
glm::vec3 tint {1.0f, 1.0f, 1.0f};
|
||||
|
||||
util::VecInterpolation<3, float> interpolation {false};
|
||||
|
||||
Skeleton(const SkeletonConfig* config);
|
||||
};
|
||||
|
||||
@ -100,7 +105,7 @@ namespace rigging {
|
||||
std::vector<Bone*> nodes;
|
||||
|
||||
size_t update(
|
||||
size_t index, Skeleton& skeleton, Bone* node, glm::mat4 matrix
|
||||
size_t index, Skeleton& skeleton, Bone* node, const glm::mat4& matrix
|
||||
) const;
|
||||
public:
|
||||
SkeletonConfig(
|
||||
@ -109,12 +114,18 @@ namespace rigging {
|
||||
size_t nodesCount
|
||||
);
|
||||
|
||||
void update(Skeleton& skeleton, glm::mat4 matrix) const;
|
||||
void update(
|
||||
Skeleton& skeleton,
|
||||
const glm::mat4& matrix,
|
||||
const glm::vec3& position
|
||||
) const;
|
||||
|
||||
void render(
|
||||
const Assets& assets,
|
||||
ModelBatch& batch,
|
||||
Skeleton& skeleton,
|
||||
const glm::mat4& matrix
|
||||
const glm::mat4& matrix,
|
||||
const glm::vec3& position
|
||||
) const;
|
||||
|
||||
Skeleton instance() const {
|
||||
|
||||
@ -14,7 +14,7 @@ namespace util {
|
||||
int nextid = 1;
|
||||
std::unordered_map<int, std::function<bool(Types...)>> handlers;
|
||||
std::vector<int> order;
|
||||
std::recursive_mutex mutex;
|
||||
std::mutex mutex;
|
||||
public:
|
||||
HandlersList() = default;
|
||||
|
||||
@ -46,11 +46,15 @@ namespace util {
|
||||
}
|
||||
|
||||
void notify(Types...args) {
|
||||
std::lock_guard lock(mutex);
|
||||
|
||||
auto orderCopy = order;
|
||||
std::vector<int> orderCopy;
|
||||
decltype(handlers) handlersCopy;
|
||||
{
|
||||
std::lock_guard lock(mutex);
|
||||
orderCopy = order;
|
||||
handlersCopy = handlers;
|
||||
}
|
||||
for (auto it = orderCopy.rbegin(); it != orderCopy.rend(); ++it) {
|
||||
if (handlers.at(*it)(args...)) {
|
||||
if (handlersCopy.at(*it)(args...)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
60
src/util/Interpolation.hpp
Normal file
60
src/util/Interpolation.hpp
Normal file
@ -0,0 +1,60 @@
|
||||
#pragma once
|
||||
|
||||
#include <limits>
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
namespace util {
|
||||
template<int N, typename T, bool angular=false>
|
||||
class VecInterpolation {
|
||||
bool enabled;
|
||||
glm::vec<N, T> prevPos {std::numeric_limits<T>::quiet_NaN()};
|
||||
glm::vec<N, T> nextPos {};
|
||||
T refreshInterval = 0.0;
|
||||
T timer = 0.0;
|
||||
T intervalUpdateFactor = 0.1;
|
||||
public:
|
||||
VecInterpolation(bool enabled) : enabled(enabled) {}
|
||||
|
||||
void refresh(const glm::vec<N, T>& position) {
|
||||
auto current = getCurrent();
|
||||
prevPos = current;
|
||||
nextPos = position;
|
||||
refreshInterval = timer * intervalUpdateFactor +
|
||||
refreshInterval * (1.0 - intervalUpdateFactor);
|
||||
timer = 0.0;
|
||||
|
||||
if constexpr (angular) {
|
||||
for (int i = 0; i < N; i++) {
|
||||
T diff = nextPos[i] - prevPos[i];
|
||||
if (glm::abs(diff) > 180.0f) {
|
||||
nextPos[i] += (diff > 0.0f ? -360.0f : 360.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void updateTimer(T delta) {
|
||||
timer += delta;
|
||||
}
|
||||
|
||||
glm::vec<N, T> getCurrent() const {
|
||||
if (refreshInterval < 0.001 || std::isnan(prevPos.x)) {
|
||||
return nextPos;
|
||||
}
|
||||
T t = timer / refreshInterval;
|
||||
return nextPos * t + prevPos * (1.0f - t);
|
||||
}
|
||||
|
||||
T getRefreshInterval() const {
|
||||
return refreshInterval;
|
||||
}
|
||||
|
||||
bool isEnabled() const {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
void setEnabled(bool enabled) {
|
||||
this->enabled = enabled;
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -18,8 +18,8 @@ uint Events::currentFrame = 0;
|
||||
int Events::scroll = 0;
|
||||
glm::vec2 Events::delta = {};
|
||||
glm::vec2 Events::cursor = {};
|
||||
bool Events::cursor_drag = false;
|
||||
bool Events::_cursor_locked = false;
|
||||
bool Events::cursorDrag = false;
|
||||
bool Events::cursorLocked = false;
|
||||
std::vector<uint> Events::codepoints;
|
||||
std::vector<keycode> Events::pressedKeys;
|
||||
std::unordered_map<std::string, Binding> Events::bindings;
|
||||
@ -61,10 +61,10 @@ bool Events::jclicked(int button) {
|
||||
}
|
||||
|
||||
void Events::toggleCursor() {
|
||||
cursor_drag = false;
|
||||
_cursor_locked = !_cursor_locked;
|
||||
cursorDrag = false;
|
||||
cursorLocked = !cursorLocked;
|
||||
Window::setCursorMode(
|
||||
_cursor_locked ? GLFW_CURSOR_DISABLED : GLFW_CURSOR_NORMAL
|
||||
cursorLocked ? GLFW_CURSOR_DISABLED : GLFW_CURSOR_NORMAL
|
||||
);
|
||||
}
|
||||
|
||||
@ -170,11 +170,11 @@ void Events::setButton(int button, bool b) {
|
||||
}
|
||||
|
||||
void Events::setPosition(float xpos, float ypos) {
|
||||
if (Events::cursor_drag) {
|
||||
if (Events::cursorDrag) {
|
||||
Events::delta.x += xpos - Events::cursor.x;
|
||||
Events::delta.y += ypos - Events::cursor.y;
|
||||
} else {
|
||||
Events::cursor_drag = true;
|
||||
Events::cursorDrag = true;
|
||||
}
|
||||
Events::cursor.x = xpos;
|
||||
Events::cursor.y = ypos;
|
||||
@ -249,3 +249,7 @@ void Events::enableBindings() {
|
||||
binding.enable = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool Events::isCursorLocked() {
|
||||
return cursorLocked;
|
||||
}
|
||||
|
||||
@ -19,12 +19,12 @@ class Events {
|
||||
static bool keys[KEYS_BUFFER_SIZE];
|
||||
static uint frames[KEYS_BUFFER_SIZE];
|
||||
static uint currentFrame;
|
||||
static bool cursor_drag;
|
||||
static bool cursorDrag;
|
||||
static bool cursorLocked;
|
||||
public:
|
||||
static int scroll;
|
||||
static glm::vec2 delta;
|
||||
static glm::vec2 cursor;
|
||||
static bool _cursor_locked;
|
||||
static std::vector<uint> codepoints;
|
||||
static std::vector<keycode> pressedKeys;
|
||||
static std::unordered_map<std::string, Binding> bindings;
|
||||
@ -65,4 +65,6 @@ public:
|
||||
BindType bindType
|
||||
);
|
||||
static void enableBindings();
|
||||
|
||||
static bool isCursorLocked();
|
||||
};
|
||||
|
||||
@ -389,7 +389,9 @@ void Window::toggleFullscreen() {
|
||||
GLFWmonitor* monitor = glfwGetPrimaryMonitor();
|
||||
const GLFWvidmode* mode = glfwGetVideoMode(monitor);
|
||||
|
||||
if (Events::_cursor_locked) Events::toggleCursor();
|
||||
if (Events::isCursorLocked()){
|
||||
Events::toggleCursor();
|
||||
}
|
||||
|
||||
if (fullscreen) {
|
||||
glfwGetWindowPos(window, &posX, &posY);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user