diff --git a/doc/en/entity-properties.md b/doc/en/entity-properties.md index f9ff2aa0..a6530191 100644 --- a/doc/en/entity-properties.md +++ b/doc/en/entity-properties.md @@ -20,6 +20,22 @@ Example: ] ``` +You can pass values ​​in ARGS from the entity configuration. +They will be passed both when creating a new entity and when loading a saved one. +The `args` list is used for this: + +```json +"components": [ + { + "name": "base:drop", + "args": { + "item": "base:stone.item", + "count": 1 + } + } +] +``` + The components code should be in `scripts/components`. ## Physics diff --git a/doc/en/scripting.md b/doc/en/scripting.md index 131b9cfa..c68d6b0c 100644 --- a/doc/en/scripting.md +++ b/doc/en/scripting.md @@ -32,6 +32,7 @@ Subsections: - [mat4](scripting/builtins/libmat4.md) - [network](scripting/builtins/libnetwork.md) - [pack](scripting/builtins/libpack.md) + - [pathfinding](scripting/builtins/libpathfinding.md) - [player](scripting/builtins/libplayer.md) - [quat](scripting/builtins/libquat.md) - [rules](scripting/builtins/librules.md) diff --git a/doc/en/scripting/builtins/libpathfinding.md b/doc/en/scripting/builtins/libpathfinding.md new file mode 100644 index 00000000..19562177 --- /dev/null +++ b/doc/en/scripting/builtins/libpathfinding.md @@ -0,0 +1,66 @@ +# *pathfinding* library + +The *pathfinding* library provides functions for working with the pathfinding system in the game world. It allows you to create and manage agents finding routes between points in the world. + +When used in entity logic, the `core:pathfinding` component should be used. + +## `core:pathfinding` component + +```lua +local pf = entity:get_component("core:pathfinding") + +--- ... +local x = ... +local y = ... +local z = ... + +--- Set the target for the agent +pf.set_target({x, y, z}) + +--- Get the current target of the agent +local target = pf.get_target() --> vec3 or nil +--- ... + +--- Get the current route of the agent +local route = pf.get_route() --> table or nil +--- ... +``` + +## Library functions + +```lua +--- Create a new agent. Returns the ID of the created agent +local agent = pathfinding.create_agent() --> int + +--- Delete an agent by ID. Returns true if the agent existed, otherwise false +pathfinding.remove_agent(agent: int) --> bool + +--- Set the agent state (enabled/disabled) +pathfinding.set_enabled(agent: int, enabled: bool) + +--- Check the agent state. Returns true if the agent is enabled, otherwise false +pathfinding.is_enabled(agent: int) --> bool + +--- Create a route based on the given points. Returns an array of route points +pathfinding.make_route(start: vec3, target: vec3) --> table + +--- Asynchronously create a route based on the given points. +--- This function allows to perform pathfinding in the background without blocking the main thread of execution +pathfinding.make_route_async(agent: int, start: vec3, target: vec3) + +--- Get the route that the agent has already found. Used to get the route after an asynchronous search. +--- If the search has not yet completed, returns nil. If the route is not found, returns an empty table. +pathfinding.pull_route(agent: int) --> table or nil + +--- Set the maximum number of visited blocks for the agent. Used to limit the amount of work of the pathfinding algorithm. +pathfinding.set_max_visited(agent: int, max_visited: int) + +--- Adding an avoided blocks tag +pathfinding.avoid_tag( + agent: int, + -- tag for avoided blocks + tag: string, [optional], + -- cost of crossing a block + cost: int = 10 +) +``` diff --git a/doc/en/scripting/builtins/libvecn.md b/doc/en/scripting/builtins/libvecn.md index 22552eb7..f330b2c4 100644 --- a/doc/en/scripting/builtins/libvecn.md +++ b/doc/en/scripting/builtins/libvecn.md @@ -100,6 +100,13 @@ vecn.length(a: vector) ``` +#### Distance - *vecn.distance(...)* + +```lua +-- returns the distance between two vectors +vecn.distance(a: vector, b: vector) +``` + #### Absolute value - *vecn.abs(...)* ```lua @@ -136,6 +143,16 @@ vecn.pow(v: vector, exponent: number, dst: vector) vecn.dot(a: vector, b: vector) ``` +#### Mixing - *vecn.mix(...)* + +```lua +-- returns vector a * (1.0 - t) + b * t +vecn.mix(a: vector, b: vector, t: number) + +-- writes to dst vector a * (1.0 - t) + b * t +vecn.mix(a: vector, b: vector, t: number, dst: vector) +``` + #### Convert to string - *vecn.tostring(...)* > [!WARNING] > Returns only if the content is a vector @@ -160,6 +177,12 @@ vec2.angle(v: vec2) -- returns the direction angle of the vector {x, y} in degrees [0, 360] vec2.angle(x: number, y: number) + +-- returns the vector rotated by an angle in degrees counterclockwise +vec2.rotate(v: vec2, angle: number) -> vec2 + +-- writes the vector rotated by an angle in degrees counterclockwise to dst +vec2.rotate(v: vec2, angle: number, dst: vec2) -> vec2 ``` @@ -188,6 +211,10 @@ print("mul: " .. vec3.tostring(result_mul)) -- {10, 40, 80} local result_mul_scal = vec3.mul(v1_3d, scal) print("mul_scal: " .. vec3.tostring(result_mul_scal)) -- {6, 12, 12} +-- calculating distance between vectors +local result_distance = vec3.distance(v1_3d, v2_3d) +print("distance: " .. result_distance) -- 43 + -- vector normalization local result_norm = vec3.normalize(v1_3d) print("norm: " .. vec3.tostring(result_norm)) -- {0.333, 0.667, 0.667} @@ -211,3 +238,7 @@ print("pow: " .. vec3.tostring(result_pow)) -- {1, 4, 4} -- scalar product of vectors local result_dot = vec3.dot(v1_3d, v2_3d) print("dot: " ..result_dot) -- 250 + +-- mixing vectors +local result_mix = vec3.mix(v1_3d, v2_3d, 0.25) +print("mix: " .. vec3.tostring(result_mix)) -- {3.25, 6.5, 11.5} diff --git a/doc/en/scripting/ecs.md b/doc/en/scripting/ecs.md index f0a33a97..e72723af 100644 --- a/doc/en/scripting/ecs.md +++ b/doc/en/scripting/ecs.md @@ -26,6 +26,8 @@ entity:get_uid() -> int entity:get_component(name: str) -> component or nil -- Checks for the presence of a component by name entity:has_component(name: str) -> bool +-- Retrieves a component by name. Throws an exception if it does not exist +entity:require_component(name: str) -> component -- Enables/disables the component entity:set_enabled(name: str, enable: bool) @@ -93,10 +95,12 @@ body:get_linear_damping() -> number -- Sets the linear velocity attenuation multiplier body:set_linear_damping(value: number) --- Checks if vertical velocity attenuation is enabled +-- Checks if vertical damping is enabled body:is_vdamping() -> bool --- Enables/disables vertical velocity attenuation -body:set_vdamping(enabled: bool) +-- Returns the vertical damping multiplier +body:get_vdamping() -> number +-- Enables/disables vertical damping / sets vertical damping multiplier +body:set_vdamping(enabled: bool | number) -- Checks if the entity is on the ground body:is_grounded() -> bool @@ -188,6 +192,12 @@ function on_update(tps: int) Called every entities tick (currently 20 times per second). +```lua +function on_physics_update(delta: number) +``` + +Called after each physics step + ```lua function on_render(delta: number) ``` diff --git a/doc/ru/entity-properties.md b/doc/ru/entity-properties.md index ec11306c..3489e3aa 100644 --- a/doc/ru/entity-properties.md +++ b/doc/ru/entity-properties.md @@ -20,6 +20,22 @@ ] ``` +Из конфигурации сущности можно передавать значения в ARGS. +Они будут передаваться как при создании новой сущности, так и при загрузке сохранённой. +Для этого используется список `args`: + +```json +"components": [ + { + "name": "base:drop", + "args": { + "item": "base:stone.item", + "count": 1 + } + } +] +``` + Код компонентов должен находиться в `scripts/components`. ## Физика diff --git a/doc/ru/scripting.md b/doc/ru/scripting.md index aa25e072..40cac325 100644 --- a/doc/ru/scripting.md +++ b/doc/ru/scripting.md @@ -32,6 +32,7 @@ - [mat4](scripting/builtins/libmat4.md) - [network](scripting/builtins/libnetwork.md) - [pack](scripting/builtins/libpack.md) + - [pathfinding](scripting/builtins/libpathfinding.md) - [player](scripting/builtins/libplayer.md) - [quat](scripting/builtins/libquat.md) - [rules](scripting/builtins/librules.md) diff --git a/doc/ru/scripting/builtins/libpathfinding.md b/doc/ru/scripting/builtins/libpathfinding.md new file mode 100644 index 00000000..fe31c18e --- /dev/null +++ b/doc/ru/scripting/builtins/libpathfinding.md @@ -0,0 +1,66 @@ +# Библиотека *pathfinding* + +Библиотека *pathfinding* предоставляет функции для работы с системой поиска пути в игровом мире. Она позволяет создавать и управлять агентами, которые могут находить маршруты между точками в мире. + +При использовании в логике сущностей следует использовать компонент `core:pathfinding`. + +## Компонент `core:pathfinding` + +```lua +local pf = entity:get_component("core:pathfinding") + +--- ... +local x = ... +local y = ... +local z = ... + +--- Установка цели для агента +pf.set_target({x, y, z}) + +--- Получение текущей цели агента +local target = pf.get_target() --> vec3 или nil +--- ... + +--- Получение текущего маршрута агента +local route = pf.get_route() --> table или nil +--- ... +``` + +## Функции библиотеки + +```lua +--- Создание нового агента. Возвращает идентификатор созданного агента +local agent = pathfinding.create_agent() --> int + +--- Удаление агента по идентификатору. Возвращает true, если агент существовал, иначе false +pathfinding.remove_agent(agent: int) --> bool + +--- Установка состояния агента (включен/выключен) +pathfinding.set_enabled(agent: int, enabled: bool) + +--- Проверка состояния агента. Возвращает true, если агент включен, иначе false +pathfinding.is_enabled(agent: int) --> bool + +--- Создание маршрута на основе заданных точек. Возвращает массив точек маршрута +pathfinding.make_route(start: vec3, target: vec3) --> table + +--- Асинхронное создание маршрута на основе заданных точек. +--- Функция позволяет выполнять поиск пути в фоновом режиме, не блокируя основной поток выполнения +pathfinding.make_route_async(agent: int, start: vec3, target: vec3) + +--- Получение маршрута, который агент уже нашел. Используется для получения маршрута после асинхронного поиска. +--- Если поиск ещё не завершён, возвращает nil. Если маршрут не найден, возвращает пустую таблицу. +pathfinding.pull_route(agent: int) --> table или nil + +--- Установка максимального количества посещенных блоков для агента. Используется для ограничения объема работы алгоритма поиска пути. +pathfinding.set_max_visited(agent: int, max_visited: int) + +--- Добавление тега избегаемых блоков +pathfinding.avoid_tag( + agent: int, + -- тег избегаемых блоков + tag: string, [опционально], + -- стоимость пересечения блока + cost: int = 10 +) +``` diff --git a/doc/ru/scripting/builtins/libvecn.md b/doc/ru/scripting/builtins/libvecn.md index 44cfa994..b3791bad 100644 --- a/doc/ru/scripting/builtins/libvecn.md +++ b/doc/ru/scripting/builtins/libvecn.md @@ -100,6 +100,13 @@ vecn.length(a: vector) ``` +#### Дистанция - *vecn.distance(...)* + +```lua +-- возвращает расстояние между двумя векторами +vecn.distance(a: vector, b: vector) +``` + #### Абсолютное значение - *vecn.abs(...)* ```lua @@ -136,6 +143,16 @@ vecn.pow(v: vector, exponent: number, dst: vector) vecn.dot(a: vector, b: vector) ``` +#### Смешивание - *vecn.mix(...)* + +```lua +-- возвращает вектор a * (1.0 - t) + b * t +vecn.mix(a: vector, b: vector, t: number) + +-- записывает в dst вектор a * (1.0 - t) + b * t +vecn.mix(a: vector, b: vector, t: number, dst: vector) +``` + #### Перевод в строку - *vecn.tostring(...)* > [!WARNING] > Возвращает только тогда, когда содержимым является вектор @@ -160,6 +177,12 @@ vec2.angle(v: vec2) -- возвращает угол направления вектора {x, y} в градусах [0, 360] vec2.angle(x: number, y: number) + +-- возвращает повернутый вектор на угол в градусах против часовой стрелки +vec2.rotate(v: vec2, angle: number) -> vec2 + +-- записывает повернутый вектор на угол в градусах против часовой стрелки в dst +vec2.rotate(v: vec2, angle: number, dst: vec2) -> vec2 ``` @@ -192,6 +215,10 @@ print("mul_scal: " .. vec3.tostring(result_mul_scal)) -- {6, 12, 12} local result_norm = vec3.normalize(v1_3d) print("norm: " .. vec3.tostring(result_norm)) -- {0.333, 0.667, 0.667} +-- дистанция между векторами +local result_distance = vec3.distance(v1_3d, v2_3d) +print("distance: " .. result_distance) -- 43 + -- длина вектора local result_len = vec3.length(v1_3d) print("len: " .. result_len) -- 3 @@ -211,4 +238,9 @@ print("pow: " .. vec3.tostring(result_pow)) -- {1, 4, 4} -- скалярное произведение векторов local result_dot = vec3.dot(v1_3d, v2_3d) print("dot: " .. result_dot) -- 250 + +-- смешивание векторов +local result_mix = vec3.mix(v1_3d, v2_3d, 0.25) +print("mix: " .. vec3.tostring(result_mix)) -- {3.25, 6.5, 11.5} + ``` diff --git a/doc/ru/scripting/ecs.md b/doc/ru/scripting/ecs.md index 89af90fa..ea217b00 100644 --- a/doc/ru/scripting/ecs.md +++ b/doc/ru/scripting/ecs.md @@ -26,6 +26,8 @@ entity:get_uid() -> int entity:get_component(name: str) -> компонент или nil -- Проверяет наличие компонента по имени entity:has_component(name: str) -> bool +-- Запрашивает компонент по имени. Бросает исключение при отсутствии +entity:require_component(name: str) -> компонент -- Включает/выключает компонент по имени entity:set_enabled(name: str, enable: bool) @@ -95,8 +97,10 @@ body:set_linear_damping(value: number) -- Проверяет, включено ли вертикальное затухание скорости body:is_vdamping() -> bool --- Включает/выключает вертикальное затухание скорости -body:set_vdamping(enabled: bool) +-- Возвращает множитель вертикального затухания скорости +body:get_vdamping() -> number +-- Включает/выключает вертикальное затухание скорости / устанавливает значение множителя +body:set_vdamping(enabled: bool | number) -- Проверяет, находится ли сущность на земле (приземлена) body:is_grounded() -> bool @@ -188,6 +192,12 @@ function on_update(tps: int) Вызывается каждый такт сущностей (на данный момент - 20 раз в секунду). +```lua +function on_physics_update(delta: number) +``` + +Вызывается после каждого шага физики + ```lua function on_render(delta: number) ``` diff --git a/res/content/base/blocks/water.json b/res/content/base/blocks/water.json index 0c2fa020..3bbc014b 100644 --- a/res/content/base/blocks/water.json +++ b/res/content/base/blocks/water.json @@ -8,5 +8,5 @@ "selectable": false, "replaceable": true, "translucent": true, - "tags": ["base:liquid"] + "tags": ["core:liquid"] } diff --git a/res/content/base/entities/drop.json b/res/content/base/entities/drop.json index 4c5e064f..951ea5e6 100644 --- a/res/content/base/entities/drop.json +++ b/res/content/base/entities/drop.json @@ -1,6 +1,13 @@ { "components": [ - "base:drop" + { + "name": "base:drop", + "args": { + "item": "base:stone.item", + "count": 1 + } + } + ], "hitbox": [0.4, 0.25, 0.4], "sensors": [ diff --git a/res/content/base/entities/player.json b/res/content/base/entities/player.json index b20a7aed..b6656914 100644 --- a/res/content/base/entities/player.json +++ b/res/content/base/entities/player.json @@ -1,5 +1,12 @@ { "components": [ + { + "name": "core:mob", + "args": { + "jump_force": 8.0 + } + }, + "core:player", "base:player_animator" ], "hitbox": [0.6, 1.8, 0.6] diff --git a/res/content/base/scripts/components/drop.lua b/res/content/base/scripts/components/drop.lua index a979c72d..2b798a38 100644 --- a/res/content/base/scripts/components/drop.lua +++ b/res/content/base/scripts/components/drop.lua @@ -8,6 +8,9 @@ timer = 0.3 local def_index = entity:def_index() dropitem = ARGS +if dropitem.item then + dropitem.id = item.index(dropitem.item) +end if dropitem then timer = dropitem.pickup_delay or timer end diff --git a/res/content/base/scripts/components/player_animator.lua b/res/content/base/scripts/components/player_animator.lua index 78049a2c..f82927cc 100644 --- a/res/content/base/scripts/components/player_animator.lua +++ b/res/content/base/scripts/components/player_animator.lua @@ -1,11 +1,10 @@ local tsf = entity.transform local body = entity.rigidbody local rig = entity.skeleton +local mob = entity:require_component("core:mob") 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 @@ -18,10 +17,11 @@ function on_render() if pid == -1 then return end - - local rx, ry, rz = player.get_rot(pid, pid ~= hud.get_player()) - rig:set_matrix(headIndex, mat4.rotate({1, 0, 0}, ry)) - rig:set_matrix(bodyIndex, mat4.rotate({0, 1, 0}, rx)) + + local rx, _, _ = player.get_rot(pid, pid ~= hud.get_player()) + + local dir = vec2.rotate({0, -1}, -rx) + mob.set_dir({dir[1], 0, dir[2]}) local invid, slotid = player.get_inventory(pid) local id, _ = inventory.get(invid, slotid) diff --git a/res/modules/internal/maths_inline.lua b/res/modules/internal/maths_inline.lua index e3e37fcd..36c0aecf 100644 --- a/res/modules/internal/maths_inline.lua +++ b/res/modules/internal/maths_inline.lua @@ -110,6 +110,21 @@ function vec3.dot(a, b) return a[1] * b[1] + a[2] * b[2] + a[3] * b[3] end +function vec3.mix(a, b, t, dest) + if dest then + dest[1] = a[1] * (1.0 - t) + b[1] * t + dest[2] = a[2] * (1.0 - t) + b[2] * t + dest[3] = a[3] * (1.0 - t) + b[3] * t + return dest + else + return { + a[1] * (1.0 - t) + b[1] * t, + a[2] * (1.0 - t) + b[2] * t, + a[3] * (1.0 - t) + b[3] * t, + } + end +end + -- =================================================== -- -- ====================== vec2 ======================= -- -- =================================================== -- @@ -210,3 +225,16 @@ end function vec2.dot(a, b) return a[1] * b[1] + a[2] * b[2] end + +function vec2.mix(a, b, t, dest) + if dest then + dest[1] = a[1] * (1.0 - t) + b[1] * t + dest[2] = a[2] * (1.0 - t) + b[2] * t + return dest + else + return { + a[1] * (1.0 - t) + b[1] * t, + a[2] * (1.0 - t) + b[2] * t, + } + end +end diff --git a/res/modules/internal/stdcomp.lua b/res/modules/internal/stdcomp.lua index a62989a9..7cedd188 100644 --- a/res/modules/internal/stdcomp.lua +++ b/res/modules/internal/stdcomp.lua @@ -25,6 +25,7 @@ local Rigidbody = {__index={ get_linear_damping=function(self) return __rigidbody.get_linear_damping(self.eid) end, set_linear_damping=function(self, f) return __rigidbody.set_linear_damping(self.eid, f) end, is_vdamping=function(self) return __rigidbody.is_vdamping(self.eid) end, + get_vdamping=function(self) return __rigidbody.get_vdamping(self.eid) end, set_vdamping=function(self, b) return __rigidbody.set_vdamping(self.eid, b) end, is_grounded=function(self) return __rigidbody.is_grounded(self.eid) end, is_crouching=function(self) return __rigidbody.is_crouching(self.eid) end, @@ -63,6 +64,13 @@ local Entity = {__index={ get_skeleton=function(self) return entities.get_skeleton(self.eid) end, set_skeleton=function(self, s) return entities.set_skeleton(self.eid, s) end, get_component=function(self, name) return self.components[name] end, + require_component=function(self, name) + local component = self.components[name] + if not component then + error(("entity has no required component '%s'"):format(name)) + end + return component + end, has_component=function(self, name) return self.components[name] ~= nil end, get_uid=function(self) return self.eid end, def_index=function(self) return entities.get_def(self.eid) end, @@ -125,6 +133,19 @@ return { ::continue:: end end, + physics_update = function(delta) + for uid, entity in pairs(entities) do + for _, component in pairs(entity.components) do + local callback = component.on_physics_update + if not component.__disabled and callback then + local result, err = pcall(callback, delta) + if err then + debug.error(err) + end + end + end + end + end, render = function(delta) for _,entity in pairs(entities) do for _, component in pairs(entity.components) do diff --git a/res/modules/schedule.lua b/res/modules/schedule.lua index 5d24a086..457cbf90 100644 --- a/res/modules/schedule.lua +++ b/res/modules/schedule.lua @@ -11,6 +11,9 @@ local Schedule = { self._next_interval = id + 1 return id end, + set_timeout = function(self, ms, callback) + self:set_interval(ms, callback, 1) + end, tick = function(self, dt) local timer = self._timer + dt for id, interval in pairs(self._intervals) do diff --git a/res/scripts/components/mob.lua b/res/scripts/components/mob.lua new file mode 100644 index 00000000..4594f06b --- /dev/null +++ b/res/scripts/components/mob.lua @@ -0,0 +1,177 @@ +local body = entity.rigidbody +local tsf = entity.transform +local rig = entity.skeleton + +local props = {} + +local function def_prop(name, def_value) + props[name] = SAVED_DATA[name] or ARGS[name] or def_value + this["get_"..name] = function() return props[name] end + this["set_"..name] = function(value) + props[name] = value + if math.abs(value - def_value) < 1e-7 then + SAVED_DATA[name] = nil + else + SAVED_DATA[name] = value + end + end +end + +def_prop("jump_force", 0.0) +def_prop("air_damping", 1.0) +def_prop("ground_damping", 1.0) +def_prop("movement_speed", 3.0) +def_prop("run_speed_mul", 1.5) +def_prop("crouch_speed_mul", 0.35) +def_prop("flight_speed_mul", 4.0) +def_prop("gravity_scale", 1.0) + +local function normalize_angle(angle) + while angle > 180 do + angle = angle - 360 + end + while angle <= -180 do + angle = angle + 360 + end + return angle +end + +local function angle_delta(a, b) + return normalize_angle(a - b) +end + +local dir = mat4.mul(tsf:get_rot(), {0, 0, -1}) +local flight = false + +function jump(multiplier) + local vel = body:get_vel() + body:set_vel( + vec3.add(vel, {0, props.jump_force * (multiplier or 1.0), 0}, vel)) +end + +function move_vertical(speed, vel) + vel = vel or body:get_vel() + vel[2] = vel[2] * 0.2 + props.movement_speed * speed * 0.8 + body:set_vel(vel) +end + +local function move_horizontal(speed, dir, vel) + vel = vel or body:get_vel() + if vec2.length(dir) > 0.0 then + vec2.normalize(dir, dir) + + local magnitude = vec2.length({vel[1], vel[3]}) + + if magnitude <= 1e-4 or (magnitude < speed or vec2.dot( + {vel[1] / magnitude, vel[3] / magnitude}, dir) < 0.9) + then + vel[1] = vel[1] * 0.2 + dir[1] * speed * 0.8 + vel[3] = vel[3] * 0.2 + dir[2] * speed * 0.8 + end + magnitude = vec3.length({vel[1], 0, vel[3]}) + if vec2.dot({vel[1] / magnitude, vel[3] / magnitude}, dir) > 0.5 then + vel[1] = vel[1] / magnitude * speed + vel[3] = vel[3] / magnitude * speed + end + end + body:set_vel(vel) +end + +function go(dir, speed_multiplier, sprint, crouch, vel) + local speed = props.movement_speed * speed_multiplier + if flight then + speed = speed * props.flight_speed_mul + end + if sprint then + speed = speed * props.run_speed_mul + elseif crouch then + speed = speed * props.crouch_speed_mul + end + move_horizontal(speed, dir, vel) +end + +local headIndex = rig:index("head") + +function look_at(point, change_dir) + local pos = tsf:get_pos() + local viewdir = vec3.normalize(vec3.sub(point, pos)) + + local dot = vec3.dot(viewdir, dir) + if dot < 0.0 and not change_dir then + viewdir = mat4.mul(tsf:get_rot(), {0, 0, -1}) + else + dir[1] = dir[1] * 0.8 + viewdir[1] * 0.2 + dir[3] = dir[3] * 0.8 + viewdir[3] * 0.2 + end + + if not headIndex then + return + end + + local headrot = mat4.idt() + local curdir = mat4.mul(mat4.mul(tsf:get_rot(), + rig:get_matrix(headIndex)), {0, 0, -1}) + + vec3.mix(curdir, viewdir, 0.2, viewdir) + + headrot = mat4.inverse(mat4.look_at({0,0,0}, viewdir, {0, 1, 0})) + headrot = mat4.mul(mat4.inverse(tsf:get_rot()), headrot) + rig:set_matrix(headIndex, headrot) +end + +function follow_waypoints(pathfinding) + pathfinding = pathfinding or entity:require_component("core:pathfinding") + local pos = tsf:get_pos() + local waypoint = pathfinding.next_waypoint() + if not waypoint then + return + end + local speed = props.movement_speed + local vel = body:get_vel() + dir = vec3.sub( + vec3.add(waypoint, {0.5, 0, 0.5}), + {pos[1], math.floor(pos[2]), pos[3]} + ) + local upper = dir[2] > 0 + dir[2] = 0.0 + vec3.normalize(dir, dir) + move_horizontal(speed, {dir[1], dir[3]}, vel) + if upper and body:is_grounded() then + jump(1.0) + end +end + +function set_dir(new_dir) + dir = new_dir +end + +function is_flight() return flight end + +function set_flight(flag) flight = flag end + +local prev_angle = (vec2.angle({dir[3], dir[1]})) % 360 + +function on_physics_update(delta) + local grounded = body:is_grounded() + body:set_vdamping(flight) + body:set_gravity_scale({0, flight and 0.0 or props.gravity_scale, 0}) + body:set_linear_damping( + (flight or not grounded) and props.air_damping or props.ground_damping + ) + + local new_angle = (vec2.angle({dir[3], dir[1]})) % 360 + local angle = prev_angle + + local adelta = angle_delta( + normalize_angle(new_angle), + normalize_angle(prev_angle) + ) + local rotate_speed = entity:get_player() == -1 and 200 or 400 + + if math.abs(adelta) > 5 then + angle = angle + delta * rotate_speed * (adelta > 0 and 1 or -1) + end + + tsf:set_rot(mat4.rotate({0, 1, 0}, angle + 180)) + prev_angle = angle +end diff --git a/res/scripts/components/pathfinding.lua b/res/scripts/components/pathfinding.lua new file mode 100644 index 00000000..2a6e51aa --- /dev/null +++ b/res/scripts/components/pathfinding.lua @@ -0,0 +1,71 @@ +local target +local route +local started + +local tsf = entity.transform +local body = entity.rigidbody + +agent = pathfinding.create_agent() +pathfinding.set_max_visited(agent, 1e3) +pathfinding.avoid_tag(agent, "core:liquid", 8) + +function set_target(new_target) + target = new_target +end + +function set_jump_height(height) + pathfinding.set_jump_height(agent, height) +end + +function get_target() + return target +end + +function get_route() + return route +end + +function next_waypoint() + if not route or #route == 0 then + return + end + local waypoint = route[#route] + local pos = tsf:get_pos() + local dst = vec2.length({ + math.floor(waypoint[1] - math.floor(pos[1])), + math.floor(waypoint[3] - math.floor(pos[3])) + }) + if dst < 1.0 then + table.remove(route, #route) + end + return route[#route] +end + +local refresh_internal = 100 +local frameid = math.random(0, refresh_internal) + +function set_refresh_interval(interval) + refresh_internal = interval +end + +function on_update() + if not started then + frameid = frameid + 1 + if body:is_grounded() then + if target and (frameid % refresh_internal == 1 or not route) then + pathfinding.make_route_async(agent, tsf:get_pos(), target) + started = true + end + end + else + local new_route = pathfinding.pull_route(agent) + if new_route then + route = new_route + started = false + end + end +end + +function on_despawn() + pathfinding.remove_agent(agent) +end diff --git a/res/scripts/components/player.lua b/res/scripts/components/player.lua new file mode 100644 index 00000000..15a2d473 --- /dev/null +++ b/res/scripts/components/player.lua @@ -0,0 +1,62 @@ +local tsf = entity.transform +local body = entity.rigidbody +local mob = entity:require_component("core:mob") + +local cheat_speed_mul = 5.0 + +local function process_player_inputs(pid, delta) + if not hud or hud.is_inventory_open() or menu.page ~= "" then + return + end + local cam = cameras.get("core:first-person") + local front = cam:get_front() + local right = cam:get_right() + front[2] = 0.0 + vec3.normalize(front, front) + + local isjump = input.is_active('movement.jump') + local issprint = input.is_active('movement.sprint') + local iscrouch = input.is_active('movement.crouch') + local isforward = input.is_active('movement.forward') + local ischeat = input.is_active('movement.cheat') + local isback = input.is_active('movement.back') + local isleft = input.is_active('movement.left') + local isright = input.is_active('movement.right') + mob.set_flight(player.is_flight(pid)) + body:set_body_type(player.is_noclip(pid) and "kinematic" or "dynamic") + body:set_crouching(iscrouch) + + local vel = body:get_vel() + local speed = ischeat and cheat_speed_mul or 1.0 + + local dir = {0, 0, 0} + + if isforward then vec3.add(dir, front, dir) end + if isback then vec3.sub(dir, front, dir) end + if isright then vec3.add(dir, right, dir) end + if isleft then vec3.sub(dir, right, dir) end + + if vec3.length(dir) > 0.0 then + mob.go({dir[1], dir[3]}, speed, issprint, iscrouch, vel) + end + + if mob.is_flight() then + if isjump then + mob.move_vertical(speed * 4) + elseif iscrouch then + mob.move_vertical(-speed * 4) + end + elseif body:is_grounded() and isjump then + mob.jump() + end +end + +function on_physics_update(delta) + local pid = entity:get_player() + if pid ~= -1 then + local pos = tsf:get_pos() + local cam = cameras.get("core:first-person") + process_player_inputs(pid, delta) + mob.look_at(vec3.add(pos, cam:get_front())) + end +end diff --git a/res/scripts/hud.lua b/res/scripts/hud.lua index abd1eaf4..90883d94 100644 --- a/res/scripts/hud.lua +++ b/res/scripts/hud.lua @@ -22,6 +22,39 @@ local function configure_SSAO() -- for test purposes end +local function update_hand() + local skeleton = gfx.skeletons + local pid = hud.get_player() + local invid, slot = player.get_inventory(pid) + local itemid = inventory.get(invid, slot) + + local cam = cameras.get("core:first-person") + local bone = skeleton.index("hand", "item") + + local offset = vec3.mul(vec3.sub(cam:get_pos(), {player.get_pos(pid)}), -1) + + local rotation = cam:get_rot() + + local angle = player.get_rot(pid) - 90 + local cos = math.cos(angle / (180 / math.pi)) + local sin = math.sin(angle / (180 / math.pi)) + + local newX = offset[1] * cos - offset[3] * sin + local newZ = offset[1] * sin + offset[3] * cos + + offset[1] = newX + offset[3] = newZ + + local mat = mat4.translate(mat4.idt(), {0.06, 0.035, -0.1}) + mat4.scale(mat, {0.1, 0.1, 0.1}, mat) + mat4.mul(rotation, mat, mat) + mat4.rotate(mat, {0, 1, 0}, -90, mat) + mat4.translate(mat, offset, mat) + + skeleton.set_matrix("hand", bone, mat) + skeleton.set_model("hand", bone, item.model_name(itemid)) +end + function on_hud_open() input.add_callback("player.pick", function () if hud.is_paused() or hud.is_inventory_open() then @@ -63,7 +96,7 @@ function on_hud_open() player.set_noclip(pid, true) end end) - + input.add_callback("player.flight", function () if hud.is_paused() or hud.is_inventory_open() then return @@ -81,39 +114,8 @@ function on_hud_open() end) configure_SSAO() -end -local function update_hand() - local skeleton = gfx.skeletons - local pid = hud.get_player() - local invid, slot = player.get_inventory(pid) - local itemid = inventory.get(invid, slot) - - local cam = cameras.get("core:first-person") - local bone = skeleton.index("hand", "item") - - local offset = vec3.mul(vec3.sub(cam:get_pos(), {player.get_pos(pid)}), -1) - - local rotation = cam:get_rot() - - local angle = player.get_rot() - 90 - local cos = math.cos(angle / (180 / math.pi)) - local sin = math.sin(angle / (180 / math.pi)) - - local newX = offset[1] * cos - offset[3] * sin - local newZ = offset[1] * sin + offset[3] * cos - - offset[1] = newX - offset[3] = newZ - - local mat = mat4.translate(mat4.idt(), {0.06, 0.035, -0.1}) - mat4.scale(mat, {0.1, 0.1, 0.1}, mat) - mat4.mul(rotation, mat, mat) - mat4.rotate(mat, {0, 1, 0}, -90, mat) - mat4.translate(mat, offset, mat) - - skeleton.set_matrix("hand", bone, mat) - skeleton.set_model("hand", bone, item.model_name(itemid)) + hud.default_hand_controller = update_hand end function on_hud_render() diff --git a/res/scripts/stdcmd.lua b/res/scripts/stdcmd.lua index 0308910f..73f4b059 100644 --- a/res/scripts/stdcmd.lua +++ b/res/scripts/stdcmd.lua @@ -157,6 +157,16 @@ console.add_command( end ) + +console.add_command( + "entity.spawn name:str x:num~pos.x y:num~pos.y z:num~pos.z", + "Spawn entity with default parameters", + function(args, kwargs) + local eid = entities.spawn(args[1], {args[2], args[3], args[4]}) + return string.format("spawned %s at %s, %s, %s", unpack(args)) + end +) + console.add_command( "entity.despawn entity:sel=$entity.selected", "Despawn entity", diff --git a/res/scripts/stdlib.lua b/res/scripts/stdlib.lua index fa93d2c9..2a0c0173 100644 --- a/res/scripts/stdlib.lua +++ b/res/scripts/stdlib.lua @@ -429,6 +429,8 @@ function __vc_on_hud_open() hud.open_permanent("core:ingame_chat") end +local Schedule = require "core:schedule" + local ScheduleGroup_mt = { __index = { publish = function(self, schedule) @@ -440,10 +442,11 @@ local ScheduleGroup_mt = { for id, schedule in pairs(self._schedules) do schedule:tick(dt) end + self.common:tick(dt) end, remove = function(self, id) self._schedules[id] = nil - end + end, } } @@ -451,6 +454,7 @@ local function ScheduleGroup() return setmetatable({ _next_schedule = 1, _schedules = {}, + common = Schedule() }, ScheduleGroup_mt) end diff --git a/src/assets/assetload_funcs.cpp b/src/assets/assetload_funcs.cpp index 78d89a0b..88dd66bd 100644 --- a/src/assets/assetload_funcs.cpp +++ b/src/assets/assetload_funcs.cpp @@ -71,8 +71,8 @@ static auto process_program(const ResPaths& paths, const std::string& filename) auto& preprocessor = *Shader::preprocessor; - auto vertex = preprocessor.process(vertexFile, vertexSource); - auto fragment = preprocessor.process(fragmentFile, fragmentSource); + auto vertex = preprocessor.process(vertexFile, vertexSource, false, {}); + auto fragment = preprocessor.process(fragmentFile, fragmentSource, false, {}); return std::make_pair(vertex, fragment); } @@ -121,7 +121,7 @@ assetload::postfunc assetload::posteffect( auto& preprocessor = *Shader::preprocessor; preprocessor.addHeader( - "__effect__", preprocessor.process(effectFile, effectSource, true) + "__effect__", preprocessor.process(effectFile, effectSource, true, {}) ); auto [vertex, fragment] = process_program(paths, SHADERS_FOLDER + "/effect"); diff --git a/src/coders/GLSLExtension.cpp b/src/coders/GLSLExtension.cpp index 8105b09e..5d53cb35 100644 --- a/src/coders/GLSLExtension.cpp +++ b/src/coders/GLSLExtension.cpp @@ -22,6 +22,10 @@ void GLSLExtension::setPaths(const ResPaths* paths) { this->paths = paths; } +void GLSLExtension::setTraceOutput(bool enabled) { + this->traceOutput = enabled; +} + void GLSLExtension::loadHeader(const std::string& name) { if (paths == nullptr) { return; @@ -29,7 +33,7 @@ void GLSLExtension::loadHeader(const std::string& name) { io::path file = paths->find("shaders/lib/" + name + ".glsl"); std::string source = io::read_string(file); addHeader(name, {}); - addHeader(name, process(file, source, true)); + addHeader(name, process(file, source, true, {})); } void GLSLExtension::addHeader(const std::string& name, ProcessingResult header) { @@ -123,13 +127,22 @@ static Value default_value_for(Type type) { class GLSLParser : public BasicParser { public: - GLSLParser(GLSLExtension& glsl, std::string_view file, std::string_view source, bool header) + GLSLParser( + GLSLExtension& glsl, + std::string_view file, + std::string_view source, + bool header, + const std::vector& defines + ) : BasicParser(file, source), glsl(glsl) { if (!header) { ss << "#version " << GLSLExtension::VERSION << '\n'; - } - for (auto& entry : glsl.getDefines()) { - ss << "#define " << entry.first << " " << entry.second << '\n'; + for (auto& entry : defines) { + ss << "#define " << entry << '\n'; + } + for (auto& entry : defines) { + ss << "#define " << entry << '\n'; + } } uint linenum = 1; source_line(ss, linenum); @@ -289,10 +302,34 @@ private: std::stringstream ss; }; +static void trace_output( + const io::path& file, + const std::string& source, + const GLSLExtension::ProcessingResult& result +) { + std::stringstream ss; + ss << "export:trace/" << file.name(); + io::path outfile = ss.str(); + try { + io::create_directories(outfile.parent()); + io::write_string(outfile, result.code); + } catch (const std::runtime_error& err) { + logger.error() << "error on saving GLSLExtension::preprocess output (" + << outfile.string() << "): " << err.what(); + } +} + GLSLExtension::ProcessingResult GLSLExtension::process( - const io::path& file, const std::string& source, bool header + const io::path& file, + const std::string& source, + bool header, + const std::vector& defines ) { std::string filename = file.string(); - GLSLParser parser(*this, filename, source, header); - return parser.process(); + GLSLParser parser(*this, filename, source, header, defines); + auto result = parser.process(); + if (traceOutput) { + trace_output(file, source, result); + } + return result; } diff --git a/src/coders/GLSLExtension.hpp b/src/coders/GLSLExtension.hpp index b61ca512..53f52930 100644 --- a/src/coders/GLSLExtension.hpp +++ b/src/coders/GLSLExtension.hpp @@ -5,6 +5,7 @@ #include #include "io/io.hpp" +#include "data/setting.hpp" #include "graphics/core/PostEffect.hpp" class ResPaths; @@ -19,6 +20,7 @@ public: }; void setPaths(const ResPaths* paths); + void setTraceOutput(bool enabled); void define(const std::string& name, std::string value); void undefine(const std::string& name); @@ -37,7 +39,8 @@ public: ProcessingResult process( const io::path& file, const std::string& source, - bool header = false + bool header, + const std::vector& defines ); static inline std::string VERSION = "330 core"; @@ -46,4 +49,5 @@ private: std::unordered_map defines; const ResPaths* paths = nullptr; + bool traceOutput = false; }; diff --git a/src/content/loading/EntityLoader.cpp b/src/content/loading/EntityLoader.cpp index 7778282c..29a72cb7 100644 --- a/src/content/loading/EntityLoader.cpp +++ b/src/content/loading/EntityLoader.cpp @@ -30,7 +30,18 @@ template<> void ContentUnitLoader::loadUnit( if (auto found = root.at("components")) { for (const auto& elem : *found) { - def.components.emplace_back(elem.asString()); + std::string name; + dv::value params; + if (elem.isObject()) { + name = elem["name"].asString(); + if (elem.has("args")) { + params = elem["args"]; + } + } else { + name = elem.asString(); + } + def.components.push_back(ComponentInstance { + std::move(name), std::move(params)}); } } if (auto found = root.at("hitbox")) { diff --git a/src/data/dv_util.hpp b/src/data/dv_util.hpp index 3f09ffde..ffec61dd 100644 --- a/src/data/dv_util.hpp +++ b/src/data/dv_util.hpp @@ -46,12 +46,12 @@ namespace dv { if (!map.has(key)) { return; } - auto& list = map[key]; + const auto& srcList = map[key]; for (size_t i = 0; i < n; i++) { if constexpr (std::is_floating_point()) { - vec[i] = list[i].asNumber(); + vec[i] = srcList[i].asNumber(); } else { - vec[i] = list[i].asInteger(); + vec[i] = srcList[i].asInteger(); } } } diff --git a/src/engine/Engine.cpp b/src/engine/Engine.cpp index 0288d609..212765f3 100644 --- a/src/engine/Engine.cpp +++ b/src/engine/Engine.cpp @@ -143,6 +143,12 @@ void Engine::initializeClient() { }, true )); + keepAlive(settings.debug.doTraceShaders.observe( + [](bool value) { + Shader::preprocessor->setTraceOutput(value); + }, + true + )); } void Engine::initialize(CoreParameters coreParameters) { diff --git a/src/frontend/debug_panel.cpp b/src/frontend/debug_panel.cpp index 06ab5fc8..10c6b85c 100644 --- a/src/frontend/debug_panel.cpp +++ b/src/frontend/debug_panel.cpp @@ -12,12 +12,14 @@ #include "graphics/render/WorldRenderer.hpp" #include "graphics/render/ParticlesRenderer.hpp" #include "graphics/render/ChunksRenderer.hpp" +#include "graphics/render/DebugLinesRenderer.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 "objects/Entity.hpp" #include "physics/Hitbox.hpp" #include "util/stringutil.hpp" #include "voxels/Block.hpp" @@ -44,6 +46,7 @@ static std::shared_ptr