This commit is contained in:
Xertis 2025-09-30 23:45:25 +03:00
commit 86053fa86d
181 changed files with 4659 additions and 1546 deletions

View File

@ -1,74 +1,131 @@
# 0.28 - 2025.07.18 # 0.29 - 2025.09.20
[Documentation](https://github.com/MihailRis/VoxelEngine-Cpp/tree/release-0.28/doc/en/main-page.md) for 0.28 [Documentation](https://github.com/MihailRis/VoxelEngine-Cpp/tree/release-0.29/doc/en/main-page.md) for 0.29
Table of contents: Table of contents:
- [Added](#added) - [Added](#added)
- [Changes](#changes)
- [Functions](#functions) - [Functions](#functions)
- [Changes](#changes)
- [Fixes](#fixes) - [Fixes](#fixes)
## Added ## Added
- advanced graphics mode - pathfinding
- state bits based models - components:
- post-effects - core:pathfinding
- ui elements: - core:player
- iframe - core:mob
- select - libraries:
- modelviewer - random
- vcm models format - gfx.skeletons
- bit.compile - (documented) assets
- yaml encoder/decoder - udp support
- error handler argument in http.get, http.post - schedules
- ui properties: - events:
- image.region - on_physics_update (components)
- rotation profiles: - on_block_tick(x, y, z, tps) (blocks)
- stairs - custom hand controller
- libraries - http headers
- gfx.posteffects - named pipes
- yaml - optimizations:
- stairs rotation profile - speed up block.set
- models editing in console - speed up vectors
- syntax highlighting: xml, glsl, vcm - items description
- beginning of projects system - item properties methods
- tab + shift+tab
- blocks, items tags
- pack dependencies versions
- ~~allow to disable autospawn position~~ use player.set_spawnpoint
- entity.spawn command
- project script
- gui.root document
- time.schedules.world.common: Schedule
### Changes ### Changes
- reserved 'project', 'pack', 'packid', 'root' entry points - app.sleep_until - added 'timeout argument'
- Bytearray optimized with FFI - network.get / post - added 'data' argument to error callback
- chunks non-unloading zone limited with circle - autorefresh model preview
- move player controls to lua
- move hand control to lua
### Functions ### Functions
- yaml.tostring - block.model_name
- yaml.parse - block.has_tag
- gfx.posteffects.index - item.has_tag
- gfx.posteffects.set_effect - item.description
- gfx.posteffects.get_intensity - base64.encode_urlsafe
- gfx.posteffects.set_intensity - base64.decode_urlsafe
- gfx.posteffects.is_active - vec2.rotate
- gfx.posteffects.set_params - vecn.distance
- gfx.posteffects.set_array - vecn.mix
- block.get_variant - rigidbody:get_vdamping
- block.set_variant - rigidbody:set_vdamping
- bit.compile - entity:require_component
- Bytearray_as_string - network.udp_connect
- random.random
- random.bytes
- random.uuid
- Random:random
- Random:seed
- hud.hand_controller
- inventory.get_caption
- inventory.set_caption
- inventory.get_description
- inventory.set_description
- pathfinding.create_agent
- pathfinding.remove_agent
- pathfinding.set_enabled
- pathfinding.is_enabled
- pathfinding.make_route
- pathfinding.make_route_async
- pathfinding.pull_route
- pathfinding.set_max_visited
- pathfinding.avoid_tag
- gfx.skeletons.get
- Skeleton:index
- Skeleton:get_model
- Skeleton:set_model
- Skeleton:get_matrix
- Skeleton:set_matrix
- Skeleton:get_texture
- Skeleton:set_texture
- Skeleton:is_visible
- Skeleton:set_visible
- Skeleton:get_color
- Skeleton:set_color
- Schedule:set_timeout(time_ms, callback)
- Schedule:set_interval(interval_ms, callback, [optional] repetions): int
- Schedule:remove_interval(id)
- ScheduleGroup:publish(schedule: Schedule)
## Fixes ## Fixes
- [fix: "unknown argument --memcheck" in vctest](https://github.com/MihailRis/voxelcore/commit/281d5e09e6f1c016646af6000f6b111695c994b3) - fix 3d text position / culling
- [fix "upgrade square is not fully inside of area" error](https://github.com/MihailRis/voxelcore/commit/bf79f6bc75a7686d59fdd0dba8b9018d6191e980 ) - fix fragment:place rotation (#593)
- [fix generator area centering](https://github.com/MihailRis/voxelcore/commit/98813472a8c25b1de93dd5d843af38c5aec9b1d8 "fix generator area centering") - fix server socket creation in macos
- [fix incomplete content reset](https://github.com/MihailRis/voxelcore/commit/61af8ba943a24f6544c6482def2e244cf0af4d18) - fix: base packs not scanned for app scripts
- [fix stack traces](https://github.com/MihailRis/voxelcore/commit/05ddffb5c9902e237c73cdea55d4ac1e303c6a8e) - fix lua::getfield and events registering
- [fix containers refreshing](https://github.com/MihailRis/voxelcore/commit/34295faca276b55c6e3c0ddd98b867a0aab3eb2a) - fix UIDocument::rebuildIndices
- [fix toml encoder](https://github.com/MihailRis/voxelcore/commit/9cd95bb0eb73521bef07f6f0d5e8b78f3e309ebf) - fix input library in headless mode
- [fix InputBindBox](https://github.com/MihailRis/voxelcore/commit/7c976a573b01e3fb6f43bacaab22e34037b55b73 "fix InputBindBox") - fix rigidbody:set_gravity_scale
- [fix inventory.* functions error messages](https://github.com/MihailRis/voxelcore/commit/af3c315c04959eea6c11f5ae2854a6f253e3450f) - fix extended blocks destruction particles spawn spread, offset
- [fix: validator not called after backspace](https://github.com/MihailRis/voxelcore/commit/df3640978d279b85653d647facb26ef15c509848) - fix shaders recompiling
- [fix: missing pack.has_indices if content is not loaded](https://github.com/MihailRis/voxelcore/commit/b02b45457322e1ce8f6b9735caeb5b58b1e2ffb4) - fix: C++ vecn functions precision loss
- [fix: entities despawn on F5](https://github.com/MihailRis/voxelcore/commit/6ab48fda935f3f1d97d76a833c8511522857ba6a) - fix coroutines errors handling
- [bug fix [#549]](https://github.com/MihailRis/voxelcore/commit/49727ec02647e48323266fbf814c15f6d5632ee9) - fix: viewport size on toggle fullscreen
- [fix player camera zoom with fov-effects disabled](https://github.com/MihailRis/voxelcore/commit/014ffab183687ed9acbb93ab90e43d8f82ed826a) - fix: fullscreen monitor refresh rate
- fix: content menu panel height
- fix generation.create_fragment (#596)
- fix bytearray:insert (#594)
- fix: script overriding
- fix: hud.close after hud.show_overlay bug
- fix: 'cannot resume dead coroutine' (#569)
- fix: skybox is not visible behind translucent blocks
- fix: sampler arrays inbdexed with non-constant / uniform-based expressions are forbidden
- fix initial weather intensity
- fix drop count (560)
- fix BasicParser::parseNumber() out of range (560)
- fix rotation interpolation (#557)

View File

@ -0,0 +1,11 @@
local response_received = false
network.get("https://api.github.com/repos/MihailRis/VoxelEngine-Cpp/releases/latest", function (s)
print(json.parse(s).name)
response_received = true
end, function (code)
print("repond with code", code)
response_received = true
end)
app.sleep_until(function () return response_received end, nil, 10)

45
dev/tests/network_tcp.lua Normal file
View File

@ -0,0 +1,45 @@
for i=1,3 do
print(string.format("iteration %s", i + 1))
local text = ""
local complete = false
for j=1,100 do
text = text .. math.random(0, 9)
end
local server = network.tcp_open(7645, function (client)
print("client connected")
start_coroutine(function()
print("client-listener started")
local received_text = ""
while client:is_alive() and #received_text < #text do
local received = client:recv(512)
if received then
received_text = received_text .. utf8.tostring(received)
print(string.format("received %s byte(s) from client", #received))
end
coroutine.yield()
end
asserts.equals (text, received_text)
complete = true
end, "client-listener")
end)
network.tcp_connect("localhost", 7645, function (socket)
print("connected to server")
start_coroutine(function()
print("data-sender started")
local ptr = 1
while ptr <= #text do
local n = math.random(1, 20)
socket:send(string.sub(text, ptr, ptr + n - 1))
print(string.format("sent %s byte(s) to server", n))
ptr = ptr + n
end
socket:close()
end, "data-sender")
end)
app.sleep_until(function () return complete end, nil, 5)
server:close()
end

View File

@ -297,3 +297,29 @@ Methods are used to manage the overwriting of properties when extending a block
### `property_name@append` ### `property_name@append`
Adds elements to the end of the list instead of completely overwriting it. Adds elements to the end of the list instead of completely overwriting it.
## Tags
Tags allow you to designate general properties of blocks. Names should be formatted as `prefix:tag_name`.
The prefix is optional, but helps avoid unwanted logical collisions. Example:
```json
{
"tags": [
"core:ore",
"base_survival:food",
]
}
```
Block tags can also be added from other packs using the `your_pack:tags.toml` file. Example:
```toml
"prefix:tag_name" = [
"random_pack:some_block",
"another_pack:item",
]
"other_prefix:other_tag_name" = [
# ...
]
``

View File

@ -30,6 +30,11 @@ If prefix is not specified, '!' level will be used.
Example: '~randutil' - weak dependency 'randutil'. Example: '~randutil' - weak dependency 'randutil'.
Dependency version is indicated after '@' symbol and have operators to restrict acceptable versions.
If version is not specified, '\*' (any) version will be used.
Example: 'randutil@>=1.0' - dependency 'randutil' which requires version 1.0 or newer.
Example: Example:
```json ```json
{ {

View File

@ -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`. The components code should be in `scripts/components`.
## Physics ## Physics

View File

@ -66,3 +66,29 @@ Property status is displayed in the inventory interface. Display method is defin
- `number` - number - `number` - number
- `relation` - current value to initial value (x/y) - `relation` - current value to initial value (x/y)
- `vbar` - vertical scale (used by default) - `vbar` - vertical scale (used by default)
## Tags
Tags allow you to designate general properties of items. Names should be formatted as `prefix:tag_name`.
The prefix is optional, but helps avoid unwanted logical collisions. Example:
```json
{
"tags": [
"core:fuel",
"base_survival:poison",
]
}
```
Tags can also be added to items from other packs using the `your_pack:tags.toml` file. Example
```toml
"prefix:tag_name" = [
"random_pack:item",
"another_pack:some_block",
]
"other_prefix:other_tag_name" = [
# ...
]
```

View File

@ -1,8 +1,8 @@
# Documentation # Documentation
Documentation for 0.29 (in-development). Documentation for 0.30 (in development).
[Documentation for release 0.28](https://github.com/MihailRis/voxelcore/blob/release-0.28/doc/en/main-page.md) [Documentation for 0.29.](https://github.com/MihailRis/voxelcore/blob/release-0.29/doc/ru/main-page.md)
## Sections ## Sections

View File

@ -32,8 +32,10 @@ Subsections:
- [mat4](scripting/builtins/libmat4.md) - [mat4](scripting/builtins/libmat4.md)
- [network](scripting/builtins/libnetwork.md) - [network](scripting/builtins/libnetwork.md)
- [pack](scripting/builtins/libpack.md) - [pack](scripting/builtins/libpack.md)
- [pathfinding](scripting/builtins/libpathfinding.md)
- [player](scripting/builtins/libplayer.md) - [player](scripting/builtins/libplayer.md)
- [quat](scripting/builtins/libquat.md) - [quat](scripting/builtins/libquat.md)
- [random](scripting/builtins/librandom.md)
- [rules](scripting/builtins/librules.md) - [rules](scripting/builtins/librules.md)
- [time](scripting/builtins/libtime.md) - [time](scripting/builtins/libtime.md)
- [utf8](scripting/builtins/libutf8.md) - [utf8](scripting/builtins/libutf8.md)

View File

@ -25,9 +25,12 @@ Waits for the specified time in seconds, performing the main engine loop.
app.sleep_until( app.sleep_until(
-- function that checks the condition for ending the wait -- function that checks the condition for ending the wait
predicate: function() -> bool, predicate: function() -> bool,
-- the maximum number of engine loop ticks after which -- maximum number of engine loop ticks after which
-- a "max ticks exceed" exception will be thrown -- a "max ticks exceed" exception will be thrown
[optional] max_ticks = 1e9 [optional] max_ticks = 1e9,
-- maximum wait time in seconds.
-- (works with system time, including test mode)
[optional] timeout = 1e9
) )
``` ```

View File

@ -8,4 +8,10 @@ base64.encode(bytes: table|ByteArray) -> str
-- Decode base64 string to ByteArray or lua table if second argument is set to true -- Decode base64 string to ByteArray or lua table if second argument is set to true
base64.decode(base64string: str, [optional]usetable: bool=false) -> table|ByteArray base64.decode(base64string: str, [optional]usetable: bool=false) -> table|ByteArray
-- Encode bytes to urlsafe-base64 string ('-', '_' instead of '+', '/')
base64.encode_urlsafe(bytes: table|ByteArray) -> str
-- Decodes urlsafe-base64 string to a ByteArray or a table of numbers if the second argument is set to true
base64.decode_urlsafe(base64string: str, [optional]usetable: bool=false) -> table|ByteArray
``` ```

View File

@ -68,6 +68,9 @@ block.get_variant(x: int, y: int, z: int) -> int
-- Sets the block variant by index -- Sets the block variant by index
block.set_variant(x: int, y: int, z: int, index: int) -> int block.set_variant(x: int, y: int, z: int, index: int) -> int
-- Checks if an block has specified tag
block.has_tag(id: int, tag: str) -> bool
``` ```
## Rotation ## Rotation

View File

@ -33,4 +33,7 @@ item.emission(itemid: int) -> str
-- Returns the value of the `uses` property -- Returns the value of the `uses` property
item.uses(itemid: int) -> int item.uses(itemid: int) -> int
-- Checks if an item has specified tag
item.has_tag(itemid: int, tag: str) -> bool
``` ```

View File

@ -6,9 +6,15 @@ A library for working with the network.
```lua ```lua
-- Performs a GET request to the specified URL. -- Performs a GET request to the specified URL.
-- After receiving the response, passes the text to the callback function. network.get(
-- In case of an error, the HTTP response code will be passed to onfailure. url: str,
network.get(url: str, callback: function(str), [optional] onfailure: function(int)) -- Function to call when response is received
callback: function(str),
-- Error handler
[optional] onfailure: function(int, str),
-- List of additional request headers
[optional] headers: table<str>
)
-- Example: -- Example:
network.get("https://api.github.com/repos/MihailRis/VoxelEngine-Cpp/releases/latest", function (s) network.get("https://api.github.com/repos/MihailRis/VoxelEngine-Cpp/releases/latest", function (s)
@ -16,13 +22,28 @@ network.get("https://api.github.com/repos/MihailRis/VoxelEngine-Cpp/releases/lat
end) end)
-- A variant for binary files, with a byte array instead of a string in the response. -- A variant for binary files, with a byte array instead of a string in the response.
network.get_binary(url: str, callback: function(table|ByteArray), [optional] onfailure: function(int)) network.get_binary(
url: str,
callback: function(ByteArray),
[optional] onfailure: function(int, str),
[optional] headers: table<str>
)
-- Performs a POST request to the specified URL. -- Performs a POST request to the specified URL.
-- Currently, only `Content-Type: application/json` is supported -- Currently, only `Content-Type: application/json` is supported
-- After receiving the response, passes the text to the callback function. -- After receiving the response, passes the text to the callback function.
-- In case of an error, the HTTP response code will be passed to onfailure. -- In case of an error, the HTTP response code will be passed to onfailure.
network.post(url: str, data: table, callback: function(str), [optional] onfailure: function(int)) network.post(
url: str,
-- Request body as a table (will be converted to JSON) or string
body: table|str,
-- Function called when response is received
callback: function(str),
-- Error handler
[optional] onfailure: function(int, str),
-- List of additional request headers
[optional] headers: table<str>
)
``` ```
## TCP Connections ## TCP Connections

View File

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

View File

@ -89,6 +89,13 @@ player.set_loading_chunks(playerid: int, bool)
Getter and setter of the property that determines whether the player is loading chunks. Getter and setter of the property that determines whether the player is loading chunks.
```lua
player.get_interaction_distance(playerid: int) -> float
player.set_interaction_distance(playerid: int, distance: float)
```
Getter and setter of the property for max interaction distance.
``` lua ``` lua
player.set_spawnpoint(playerid: int, x: number, y: number, z: number) player.set_spawnpoint(playerid: int, x: number, y: number, z: number)
player.get_spawnpoint(playerid: int) -> number, number, number player.get_spawnpoint(playerid: int) -> number, number, number

View File

@ -0,0 +1,38 @@
# *random* library
A library of functions for generating random numbers.
## Non-deterministic numbers
```lua
-- Generates a random number in the range [0..1)
random.random() --> number
-- Generates a random integer in the range [0..n]
random.random(n) --> number
-- Generates a random integer in the range [a..b]
random.random(a, b) --> number
-- Generates a random byte array of length n
random.bytes(n: number) -> Bytearray
-- Generates a UUID version 4
random.uuid() -> str
```
## Pseudorandom numbers
The library provides the Random class - a generator with its own isolated state.
```lua
local rng = random.Random()
-- Used similarly to math.random
local a = rng:random() --> [0..1)
local b = rng:random(10) --> [0..10]
local c = rng:random(5, 20) --> [5..20]
-- Sets the generator state to generate a reproducible sequence of random numbers
rng:seed(42)
```

View File

@ -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(...)* #### Absolute value - *vecn.abs(...)*
```lua ```lua
@ -136,6 +143,16 @@ vecn.pow(v: vector, exponent: number, dst: vector)
vecn.dot(a: vector, b: 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(...)* #### Convert to string - *vecn.tostring(...)*
> [!WARNING] > [!WARNING]
> Returns only if the content is a vector > 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] -- returns the direction angle of the vector {x, y} in degrees [0, 360]
vec2.angle(x: number, y: number) 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) local result_mul_scal = vec3.mul(v1_3d, scal)
print("mul_scal: " .. vec3.tostring(result_mul_scal)) -- {6, 12, 12} 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 -- vector normalization
local result_norm = vec3.normalize(v1_3d) local result_norm = vec3.normalize(v1_3d)
print("norm: " .. vec3.tostring(result_norm)) -- {0.333, 0.667, 0.667} 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 -- scalar product of vectors
local result_dot = vec3.dot(v1_3d, v2_3d) local result_dot = vec3.dot(v1_3d, v2_3d)
print("dot: " ..result_dot) -- 250 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}

View File

@ -26,6 +26,8 @@ entity:get_uid() -> int
entity:get_component(name: str) -> component or nil entity:get_component(name: str) -> component or nil
-- Checks for the presence of a component by name -- Checks for the presence of a component by name
entity:has_component(name: str) -> bool 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 -- Enables/disables the component
entity:set_enabled(name: str, enable: bool) entity:set_enabled(name: str, enable: bool)
@ -93,10 +95,12 @@ body:get_linear_damping() -> number
-- Sets the linear velocity attenuation multiplier -- Sets the linear velocity attenuation multiplier
body:set_linear_damping(value: number) body:set_linear_damping(value: number)
-- Checks if vertical velocity attenuation is enabled -- Checks if vertical damping is enabled
body:is_vdamping() -> bool body:is_vdamping() -> bool
-- Enables/disables vertical velocity attenuation -- Returns the vertical damping multiplier
body:set_vdamping(enabled: bool) 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 -- Checks if the entity is on the ground
body:is_grounded() -> bool body:is_grounded() -> bool
@ -188,6 +192,12 @@ function on_update(tps: int)
Called every entities tick (currently 20 times per second). Called every entities tick (currently 20 times per second).
```lua
function on_physics_update(delta: number)
```
Called after each physics step
```lua ```lua
function on_render(delta: number) function on_render(delta: number)
``` ```

View File

@ -46,6 +46,13 @@ function on_blocks_tick(tps: int)
Called tps (20) times per second. Use 1/tps instead of `time.delta()`. Called tps (20) times per second. Use 1/tps instead of `time.delta()`.
```lua
function on_block_tick(x, y, z, tps: number)
```
Called tps (20 / tick-interval) times per second for a block.
Use 1/tps instead of `time.delta()`.
```lua ```lua
function on_player_tick(playerid: int, tps: int) function on_player_tick(playerid: int, tps: int)
``` ```

View File

@ -306,3 +306,29 @@
### `имя_свойства@append` ### `имя_свойства@append`
Добавляет элементы в конец списка, вместо его полной перезаписи. Добавляет элементы в конец списка, вместо его полной перезаписи.
## Теги - *tags*
Теги позволяют обозначать обобщённые свойства блоков. Названия следует формировать как `префикс:имя_тега`.
Префикс не является обязательным, но позволяет избегать нежелательных логических коллизий. Пример:
```json
{
"tags": [
"core:ore",
"base_survival:food",
]
}
```
Теги блокам можно добавлять и из других паков, с помощью файла аш_пак:tags.toml`. Пример
```toml
"префикс:имя_тега" = [
"рандомный_пак:какой_то_блок",
"ещё_один_пак:предмет",
]
ругой_префиксругое_имя_тега" = [
# ...
]
```

View File

@ -32,6 +32,11 @@
Пример: '~randutil' - слабая зависимость 'randutil'. Пример: '~randutil' - слабая зависимость 'randutil'.
Версии зависимостей указываются после '@' и имеют операторы для ограничения допустимых версий.
Отсутствие версии зависимости интерпретируется как '\*', т.е. любая версия.
Пример: 'randutil@>=1.0' - зависимость 'randutil' версии 1.0 и старше.
Пример: Пример:
```json ```json
{ {

View File

@ -20,6 +20,22 @@
] ]
``` ```
Из конфигурации сущности можно передавать значения в ARGS.
Они будут передаваться как при создании новой сущности, так и при загрузке сохранённой.
Для этого используется список `args`:
```json
"components": [
{
"name": "base:drop",
"args": {
"item": "base:stone.item",
"count": 1
}
}
]
```
Код компонентов должен находиться в `scripts/components`. Код компонентов должен находиться в `scripts/components`.
## Физика ## Физика

View File

@ -65,3 +65,30 @@
- `number` - число - `number` - число
- `relation` - отношение текущего значения к изначальному (x/y) - `relation` - отношение текущего значения к изначальному (x/y)
- `vbar` - вертикальная шкала (используется по-умолчанию) - `vbar` - вертикальная шкала (используется по-умолчанию)
## Теги - *tags*
Теги позволяют обозначать обобщённые свойства предметов. Названия следует формировать как `префикс:имя_тега`.
Префикс не является обязательным, но позволяет избегать нежелательных логических коллизий. Пример:
```json
{
"tags": [
"core:fuel",
"base_survival:poison",
]
}
```
Теги предметам можно добавлять и из других паков, с помощью файла аш_пак:tags.toml`. Пример
```toml
"префикс:имя_тега" = [
"рандомный_пак:предмет",
"ещё_один_пак:какой_то_блок",
]
ругой_префиксругое_имя_тега" = [
# ...
]
```

View File

@ -1,8 +1,8 @@
# Документация # Документация
Документация версии 0.29 (в разработке). Документация версии 0.30 (в разработке).
[Документация версии 0.28](https://github.com/MihailRis/voxelcore/blob/release-0.28/doc/ru/main-page.md) [Документация 0.29.](https://github.com/MihailRis/voxelcore/blob/release-0.29/doc/ru/main-page.md)
## Разделы ## Разделы

View File

@ -32,8 +32,10 @@
- [mat4](scripting/builtins/libmat4.md) - [mat4](scripting/builtins/libmat4.md)
- [network](scripting/builtins/libnetwork.md) - [network](scripting/builtins/libnetwork.md)
- [pack](scripting/builtins/libpack.md) - [pack](scripting/builtins/libpack.md)
- [pathfinding](scripting/builtins/libpathfinding.md)
- [player](scripting/builtins/libplayer.md) - [player](scripting/builtins/libplayer.md)
- [quat](scripting/builtins/libquat.md) - [quat](scripting/builtins/libquat.md)
- [random](scripting/builtins/librandom.md)
- [rules](scripting/builtins/librules.md) - [rules](scripting/builtins/librules.md)
- [time](scripting/builtins/libtime.md) - [time](scripting/builtins/libtime.md)
- [utf8](scripting/builtins/libutf8.md) - [utf8](scripting/builtins/libutf8.md)

View File

@ -27,7 +27,10 @@ app.sleep_until(
predicate: function() -> bool, predicate: function() -> bool,
-- максимальное количество тактов цикла движка, после истечения которых -- максимальное количество тактов цикла движка, после истечения которых
-- будет брошено исключение "max ticks exceed" -- будет брошено исключение "max ticks exceed"
[опционально] max_ticks = 1e9 [опционально] max_ticks = 1e9,
-- максимальное длительность ожидания в секундах.
-- (работает с системным временем, включая test-режим)
[опционально] timeout = 1e9
) )
``` ```

View File

@ -8,4 +8,10 @@ base64.encode(bytes: table|ByteArray) -> str
-- Декодирует base64 строку в ByteArray или таблицу чисел, если второй аргумент установлен на true -- Декодирует base64 строку в ByteArray или таблицу чисел, если второй аргумент установлен на true
base64.decode(base64string: str, [опционально]usetable: bool=false) -> table|ByteArray base64.decode(base64string: str, [опционально]usetable: bool=false) -> table|ByteArray
-- Кодирует массив байт в urlsafe-base64 строку ('-', '_' вместо '+', '/')
base64.encode_urlsafe(bytes: table|ByteArray) -> str
-- Декодирует urlsafe-base64 строку в ByteArray или таблицу чисел, если второй аргумент установлен на true
base64.decode_urlsafe(base64string: str, [опционально]usetable: bool=false) -> table|ByteArray
``` ```

View File

@ -67,6 +67,9 @@ block.get_variant(x: int, y: int, z: int) -> int
-- Устанавливает вариант блока по индексу -- Устанавливает вариант блока по индексу
block.set_variant(x: int, y: int, z: int, index: int) -> int block.set_variant(x: int, y: int, z: int, index: int) -> int
-- Проверяет наличие тега у блока
block.has_tag(id: int, tag: str) -> bool
``` ```
### Raycast ### Raycast

View File

@ -33,6 +33,9 @@ item.emission(itemid: int) -> str
-- Возвращает значение свойства `uses` -- Возвращает значение свойства `uses`
item.uses(itemid: int) -> int item.uses(itemid: int) -> int
-- Проверяет наличие тега у предмета
item.has_tag(itemid: int, tag: str) -> bool
``` ```

View File

@ -6,9 +6,15 @@
```lua ```lua
-- Выполняет GET запрос к указанному URL. -- Выполняет GET запрос к указанному URL.
-- После получения ответа, передаёт текст в функцию callback. network.get(
-- В случае ошибки в onfailure будет передан HTTP-код ответа. url: str,
network.get(url: str, callback: function(str), [опционально] onfailure: function(int)) -- Функция, вызываемая при получении ответа
callback: function(str),
-- Обработчик ошибок
[опционально] onfailure: function(int, str),
-- Список дополнительных заголовков запроса
[опционально] headers: table<str>
)
-- Пример: -- Пример:
network.get("https://api.github.com/repos/MihailRis/VoxelEngine-Cpp/releases/latest", function (s) network.get("https://api.github.com/repos/MihailRis/VoxelEngine-Cpp/releases/latest", function (s)
@ -16,13 +22,28 @@ network.get("https://api.github.com/repos/MihailRis/VoxelEngine-Cpp/releases/lat
end) end)
-- Вариант для двоичных файлов, с массивом байт вместо строки в ответе. -- Вариант для двоичных файлов, с массивом байт вместо строки в ответе.
network.get_binary(url: str, callback: function(table|ByteArray), [опционально] onfailure: function(int)) network.get_binary(
url: str,
callback: function(ByteArray),
[опционально] onfailure: function(int, Bytearray),
[опционально] headers: table<str>
)
-- Выполняет POST запрос к указанному URL. -- Выполняет POST запрос к указанному URL.
-- На данный момент реализована поддержка только `Content-Type: application/json` -- На данный момент реализована поддержка только `Content-Type: application/json`
-- После получения ответа, передаёт текст в функцию callback. -- После получения ответа, передаёт текст в функцию callback.
-- В случае ошибки в onfailure будет передан HTTP-код ответа. -- В случае ошибки в onfailure будет передан HTTP-код ответа.
network.post(url: str, data: table, callback: function(str), [опционально] onfailure: function(int)) network.post(
url: str,
-- Тело запроса в виде таблицы, конвертируемой в JSON или строки
body: table|str,
-- Функция, вызываемая при получении ответа
callback: function(str),
-- Обработчик ошибок
[опционально] onfailure: function(int, str),
-- Список дополнительных заголовков запроса
[опционально] headers: table<str>
)
``` ```
## TCP-Соединения ## TCP-Соединения

View File

@ -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<vec3> или 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<vec3>
--- Асинхронное создание маршрута на основе заданных точек.
--- Функция позволяет выполнять поиск пути в фоновом режиме, не блокируя основной поток выполнения
pathfinding.make_route_async(agent: int, start: vec3, target: vec3)
--- Получение маршрута, который агент уже нашел. Используется для получения маршрута после асинхронного поиска.
--- Если поиск ещё не завершён, возвращает nil. Если маршрут не найден, возвращает пустую таблицу.
pathfinding.pull_route(agent: int) --> table<vec3> или nil
--- Установка максимального количества посещенных блоков для агента. Используется для ограничения объема работы алгоритма поиска пути.
pathfinding.set_max_visited(agent: int, max_visited: int)
--- Добавление тега избегаемых блоков
pathfinding.avoid_tag(
agent: int,
-- тег избегаемых блоков
tag: string, [опционально],
-- стоимость пересечения блока
cost: int = 10
)
```

View File

@ -89,6 +89,13 @@ player.set_loading_chunks(playerid: int, bool)
Геттер и сеттер свойства, определяющего, прогружает ли игрок чанки вокруг. Геттер и сеттер свойства, определяющего, прогружает ли игрок чанки вокруг.
```lua
player.get_interaction_distance(playerid: int) -> float
player.set_interaction_distance(playerid: int, distance: float)
```
Геттер и сеттер свойства, определяющего максимальную дистанцию взаимодействия.
```lua ```lua
player.set_spawnpoint(playerid: int, x: number, y: number, z: number) player.set_spawnpoint(playerid: int, x: number, y: number, z: number)
player.get_spawnpoint(playerid: int) -> number, number, number player.get_spawnpoint(playerid: int) -> number, number, number

View File

@ -0,0 +1,38 @@
# Библиотека *random*
Библиотека функций для генерации случайный чисел.
## Недетерминированные числа
```lua
-- Генерирует случайное число в диапазоне [0..1)
random.random() --> number
-- Генерирует случайное целое число в диапазоне [0..n]
random.random(n) --> number
-- Генерирует случайное целое число в диапазоне [a..b]
random.random(a, b) --> number
-- Генерирует случайный массив байт длиной n
random.bytes(n: number) -> Bytearray
-- Генерирует UUID версии 4
random.uuid() -> str
```
## Псевдослучайные числа
Библиотека предоставляет класс Random - генератор с собственным изолированным состоянием.
```lua
local rng = random.Random()
-- Используется аналогично math.random
local a = rng:random() --> [0..1)
local b = rng:random(10) --> [0..10]
local c = rng:random(5, 20) --> [5..20]
-- Устанавливает состояние генератора для генерации воспроизводимой последовательности случайных чисел
rng:seed(42)
```

View File

@ -100,6 +100,13 @@ vecn.length(a: vector)
``` ```
#### Дистанция - *vecn.distance(...)*
```lua
-- возвращает расстояние между двумя векторами
vecn.distance(a: vector, b: vector)
```
#### Абсолютное значение - *vecn.abs(...)* #### Абсолютное значение - *vecn.abs(...)*
```lua ```lua
@ -136,6 +143,16 @@ vecn.pow(v: vector, exponent: number, dst: vector)
vecn.dot(a: vector, b: 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(...)* #### Перевод в строку - *vecn.tostring(...)*
> [!WARNING] > [!WARNING]
> Возвращает только тогда, когда содержимым является вектор > Возвращает только тогда, когда содержимым является вектор
@ -160,6 +177,12 @@ vec2.angle(v: vec2)
-- возвращает угол направления вектора {x, y} в градусах [0, 360] -- возвращает угол направления вектора {x, y} в градусах [0, 360]
vec2.angle(x: number, y: number) 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) local result_norm = vec3.normalize(v1_3d)
print("norm: " .. vec3.tostring(result_norm)) -- {0.333, 0.667, 0.667} 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) local result_len = vec3.length(v1_3d)
print("len: " .. result_len) -- 3 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) local result_dot = vec3.dot(v1_3d, v2_3d)
print("dot: " .. result_dot) -- 250 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}
``` ```

View File

@ -26,6 +26,8 @@ entity:get_uid() -> int
entity:get_component(name: str) -> компонент или nil entity:get_component(name: str) -> компонент или nil
-- Проверяет наличие компонента по имени -- Проверяет наличие компонента по имени
entity:has_component(name: str) -> bool entity:has_component(name: str) -> bool
-- Запрашивает компонент по имени. Бросает исключение при отсутствии
entity:require_component(name: str) -> компонент
-- Включает/выключает компонент по имени -- Включает/выключает компонент по имени
entity:set_enabled(name: str, enable: bool) entity:set_enabled(name: str, enable: bool)
@ -95,8 +97,10 @@ body:set_linear_damping(value: number)
-- Проверяет, включено ли вертикальное затухание скорости -- Проверяет, включено ли вертикальное затухание скорости
body:is_vdamping() -> bool body:is_vdamping() -> bool
-- Включает/выключает вертикальное затухание скорости -- Возвращает множитель вертикального затухания скорости
body:set_vdamping(enabled: bool) body:get_vdamping() -> number
-- Включает/выключает вертикальное затухание скорости / устанавливает значение множителя
body:set_vdamping(enabled: bool | number)
-- Проверяет, находится ли сущность на земле (приземлена) -- Проверяет, находится ли сущность на земле (приземлена)
body:is_grounded() -> bool body:is_grounded() -> bool
@ -188,6 +192,12 @@ function on_update(tps: int)
Вызывается каждый такт сущностей (на данный момент - 20 раз в секунду). Вызывается каждый такт сущностей (на данный момент - 20 раз в секунду).
```lua
function on_physics_update(delta: number)
```
Вызывается после каждого шага физики
```lua ```lua
function on_render(delta: number) function on_render(delta: number)
``` ```

View File

@ -46,6 +46,13 @@ function on_blocks_tick(tps: int)
Вызывается tps (20) раз в секунду. Используйте 1/tps вместо `time.delta()`. Вызывается tps (20) раз в секунду. Используйте 1/tps вместо `time.delta()`.
```lua
function on_block_tick(x, y, z, tps: number)
```
Вызывается tps (20 / tick-interval) раз в секунду для конкретного блока.
Используйте 1/tps вместо `time.delta()`.
```lua ```lua
function on_player_tick(playerid: int, tps: int) function on_player_tick(playerid: int, tps: int)
``` ```

View File

@ -1,4 +1,5 @@
{ {
"texture": "coal_ore", "texture": "coal_ore",
"tags": ["base:ore"],
"base:durability": 16.0 "base:durability": 16.0
} }

View File

@ -7,5 +7,6 @@
"obstacle": false, "obstacle": false,
"selectable": false, "selectable": false,
"replaceable": true, "replaceable": true,
"translucent": true "translucent": true,
"tags": ["core:liquid"]
} }

View File

@ -1,6 +1,13 @@
{ {
"components": [ "components": [
"base:drop" {
"name": "base:drop",
"args": {
"item": "base:stone.item",
"count": 1
}
}
], ],
"hitbox": [0.4, 0.25, 0.4], "hitbox": [0.4, 0.25, 0.4],
"sensors": [ "sensors": [

View File

@ -1,5 +1,12 @@
{ {
"components": [ "components": [
{
"name": "core:mob",
"args": {
"jump_force": 8.0
}
},
"core:player",
"base:player_animator" "base:player_animator"
], ],
"hitbox": [0.6, 1.8, 0.6] "hitbox": [0.6, 1.8, 0.6]

View File

@ -1,6 +1,6 @@
{ {
"id": "base", "id": "base",
"title": "Base", "title": "Base",
"version": "0.29", "version": "0.30",
"description": "basic content package" "description": "basic content package"
} }

View File

@ -8,6 +8,9 @@ timer = 0.3
local def_index = entity:def_index() local def_index = entity:def_index()
dropitem = ARGS dropitem = ARGS
if dropitem.item then
dropitem.id = item.index(dropitem.item)
end
if dropitem then if dropitem then
timer = dropitem.pickup_delay or timer timer = dropitem.pickup_delay or timer
end end

View File

@ -1,11 +1,10 @@
local tsf = entity.transform local tsf = entity.transform
local body = entity.rigidbody local body = entity.rigidbody
local rig = entity.skeleton local rig = entity.skeleton
local mob = entity:require_component("core:mob")
local itemid = 0 local itemid = 0
local headIndex = rig:index("head")
local itemIndex = rig:index("item") local itemIndex = rig:index("item")
local bodyIndex = rig:index("body")
local function refresh_model(id) local function refresh_model(id)
itemid = id itemid = id
@ -18,10 +17,11 @@ function on_render()
if pid == -1 then if pid == -1 then
return return
end end
local rx, ry, rz = player.get_rot(pid, pid ~= hud.get_player()) local rx, _, _ = 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 dir = vec2.rotate({0, -1}, -rx)
mob.set_dir({dir[1], 0, dir[2]})
local invid, slotid = player.get_inventory(pid) local invid, slotid = player.get_inventory(pid)
local id, _ = inventory.get(invid, slotid) local id, _ = inventory.get(invid, slotid)

View File

@ -176,18 +176,105 @@ function place_pack(panel, packinfo, callback, position_func)
end end
end end
local Version = {};
function Version.matches_pattern(version)
for _, letter in string.gmatch(version, "%.+") do
if type(letter) ~= "number" or letter ~= "." then
return false;
end
local t = string.split(version, ".");
return #t == 2 or #t == 3;
end
end
function Version.__equal(ver1, ver2)
return ver1[1] == ver2[1] and ver1[2] == ver2[2] and ver1[3] == ver2[3];
end
function Version.__more(ver1, ver2)
if ver1[1] ~= ver2[1] then return ver1[1] > ver2[1] end;
if ver1[2] ~= ver2[2] then return ver1[2] > ver2[2] end;
return ver1[3] > ver2[3];
end
function Version.__less(ver1, ver2)
return Version.__more(ver2, ver1);
end
function Version.__more_or_equal(ver1, ver2)
return not Version.__less(ver1, ver2);
end
function Version.__less_or_equal(ver1, ver2)
return not Version.__more(ver1, ver2);
end
function Version.compare(op, ver1, ver2)
ver1 = string.split(ver1, ".");
ver2 = string.split(ver2, ".");
if op == "=" then return Version.__equal(ver1, ver2);
elseif op == ">" then return Version.__more(ver1, ver2);
elseif op == "<" then return Version.__less(ver1, ver2);
elseif op == ">=" then return Version.__more_or_equal(ver1, ver2);
elseif op == "<=" then return Version.__less_or_equal(ver1, ver2);
else return false; end
end
function Version.parse(version)
local op = string.sub(version, 1, 2);
if op == ">=" or op == "=>" then
return ">=", string.sub(version, #op + 1);
elseif op == "<=" or op == "=<" then
return "<=", string.sub(version, #op + 1);
end
op = string.sub(version, 1, 1);
if op == ">" or op == "<" then
return op, string.sub(version, #op + 1);
end
return "=", version;
end
local function compare_version(dependent_version, actual_version)
if Version.matches_pattern(dependent_version) and Version.matches_pattern(actual_version) then
local op, dep_ver = Version.parse_version(dependent_version);
Version.compare(op, dep_ver, actual_version);
elseif dependent_version == "*" or dependent_version == actual_version then
return true;
else
return false;
end
end
function check_dependencies(packinfo) function check_dependencies(packinfo)
if packinfo.dependencies == nil then if packinfo.dependencies == nil then
return return
end end
for i,dep in ipairs(packinfo.dependencies) do for i,dep in ipairs(packinfo.dependencies) do
local depid = dep:sub(2,-1) local depid, depver = unpack(string.split(dep:sub(2,-1), "@"))
if dep:sub(1,1) == '!' then
if dep:sub(1,1) == '!' then
if not table.has(packs_all, depid) then if not table.has(packs_all, depid) then
return string.format( return string.format(
"%s (%s)", gui.str("error.dependency-not-found"), depid "%s (%s)", gui.str("error.dependency-not-found"), depid
) )
end end
local dep_pack = pack.get_info(depid);
if not compare_version(depver, dep_pack.version) then
local op, ver = Version.parse(depver);
print(string.format("%s: %s !%s %s (%s)", gui.str("error.dependency-version-not-met"), dep_pack.version, op, ver, depid));
return string.format("%s: %s != %s (%s)", gui.str("error.dependency-version-not-met"), dep_pack.version, ver, depid);
end
if table.has(packs_installed, packinfo.id) then if table.has(packs_installed, packinfo.id) then
table.insert(required, depid) table.insert(required, depid)
end end

View File

@ -13,9 +13,9 @@ end
function refresh() function refresh()
document.list:clear() document.list:clear()
local available = pack.get_available() local allpacks = table.merge(pack.get_available(), pack.get_installed())
local infos = pack.get_info(available) local infos = pack.get_info(allpacks)
for _, name in ipairs(available) do for _, name in ipairs(allpacks) do
local info = infos[name] local info = infos[name]
local scripts_dir = info.path.."/scripts/app" local scripts_dir = info.path.."/scripts/app"
if not file.exists(scripts_dir) then if not file.exists(scripts_dir) then

View File

@ -1,5 +1,4 @@
<panel size='400' color='0' interval='1' context='menu'> <panel size='400' color='0' interval='1' context='menu'>
<button onclick='menu.page="new_world"'>@New World</button> <button onclick='menu.page="new_world"'>@New World</button>
<panel id='worlds' size='390,1' padding='5' color='#FFFFFF11' max-length='400'> <panel id='worlds' size='390,1' padding='5' color='#FFFFFF11' max-length='400'>

View File

@ -0,0 +1,10 @@
local this = {}
function this.equals(expected, fact)
assert(fact == expected, string.format(
"(fact == expected) assertion failed\n Expected: %s\n Fact: %s",
expected, fact
))
end
return this

View File

@ -110,6 +110,21 @@ function vec3.dot(a, b)
return a[1] * b[1] + a[2] * b[2] + a[3] * b[3] return a[1] * b[1] + a[2] * b[2] + a[3] * b[3]
end 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 ======================= -- -- ====================== vec2 ======================= --
-- =================================================== -- -- =================================================== --
@ -210,3 +225,16 @@ end
function vec2.dot(a, b) function vec2.dot(a, b)
return a[1] * b[1] + a[2] * b[2] return a[1] * b[1] + a[2] * b[2]
end 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

View File

@ -0,0 +1,35 @@
local Random = {}
local M = 2 ^ 31
local A = 1103515245
local C = 12345
function Random.randint(self)
self._seed = (A * self._seed + C) % M
return self._seed
end
function Random.random(self, a, b)
local num = self:randint() % M / M
if b then
return math.floor(num * (b - a + 1) + a)
elseif a then
return math.floor(num * a + 1)
else
return num
end
end
function Random.seed(self, number)
if type(number) ~= "number" then
error("number expected")
end
self._seed = number
end
return function(seed)
if seed and type(seed) ~= "number" then
error("number expected")
end
return setmetatable({_seed = seed or random.random(M)}, {__index = Random})
end

View File

@ -25,6 +25,7 @@ local Rigidbody = {__index={
get_linear_damping=function(self) return __rigidbody.get_linear_damping(self.eid) end, 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, 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, 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, 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_grounded=function(self) return __rigidbody.is_grounded(self.eid) end,
is_crouching=function(self) return __rigidbody.is_crouching(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, get_skeleton=function(self) return entities.get_skeleton(self.eid) end,
set_skeleton=function(self, s) return entities.set_skeleton(self.eid, s) end, set_skeleton=function(self, s) return entities.set_skeleton(self.eid, s) end,
get_component=function(self, name) return self.components[name] 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, has_component=function(self, name) return self.components[name] ~= nil end,
get_uid=function(self) return self.eid end, get_uid=function(self) return self.eid end,
def_index=function(self) return entities.get_def(self.eid) end, def_index=function(self) return entities.get_def(self.eid) end,
@ -125,6 +133,19 @@ return {
::continue:: ::continue::
end end
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) render = function(delta)
for _,entity in pairs(entities) do for _,entity in pairs(entities) do
for _, component in pairs(entity.components) do for _, component in pairs(entity.components) do

View File

@ -11,6 +11,9 @@ local Schedule = {
self._next_interval = id + 1 self._next_interval = id + 1
return id return id
end, end,
set_timeout = function(self, ms, callback)
self:set_interval(ms, callback, 1)
end,
tick = function(self, dt) tick = function(self, dt)
local timer = self._timer + dt local timer = self._timer + dt
for id, interval in pairs(self._intervals) do for id, interval in pairs(self._intervals) do

View File

@ -23,4 +23,5 @@ function on_menu_setup()
menubg = gui.root.menubg menubg = gui.root.menubg
controller.resize_menu_bg() controller.resize_menu_bg()
menu.page = "main" menu.page = "main"
menu.visible = true
end end

View File

@ -74,6 +74,38 @@ local _tcp_client_callbacks = {}
local _udp_server_callbacks = {} local _udp_server_callbacks = {}
local _udp_client_datagram_callbacks = {} local _udp_client_datagram_callbacks = {}
local _udp_client_open_callbacks = {} local _udp_client_open_callbacks = {}
local _http_response_callbacks = {}
local _http_error_callbacks = {}
network.get = function(url, callback, errorCallback, headers)
local id = network.__get(url, headers)
if callback then
_http_response_callbacks[id] = callback
end
if errorCallback then
_http_error_callbacks[id] = errorCallback
end
end
network.get_binary = function(url, callback, errorCallback, headers)
local id = network.__get_binary(url, headers)
if callback then
_http_response_callbacks[id] = callback
end
if errorCallback then
_http_error_callbacks[id] = errorCallback
end
end
network.post = function(url, data, callback, errorCallback, headers)
local id = network.__post(url, data, headers)
if callback then
_http_response_callbacks[id] = callback
end
if errorCallback then
_http_error_callbacks[id] = errorCallback
end
end
network.tcp_open = function (port, handler) network.tcp_open = function (port, handler)
local socket = setmetatable({id=network.__open_tcp(port)}, ServerSocket) local socket = setmetatable({id=network.__open_tcp(port)}, ServerSocket)
@ -131,10 +163,80 @@ local function clean(iterable, checkFun, ...)
end end
end end
local updating_blocks = {}
local TYPE_REGISTER = 0
local TYPE_UNREGISTER = 1
block.__perform_ticks = function(delta)
for id, entry in pairs(updating_blocks) do
entry.timer = entry.timer + delta
local steps = math.floor(entry.timer / entry.delta * #entry / 3)
if steps == 0 then
goto continue
end
entry.timer = 0.0
local event = entry.event
local tps = entry.tps
for i=1, steps do
local x = entry[entry.pointer + 1]
local y = entry[entry.pointer + 2]
local z = entry[entry.pointer + 3]
entry.pointer = (entry.pointer + 3) % #entry
events.emit(event, x, y, z, tps)
end
::continue::
end
end
block.__process_register_events = function()
local register_events = block.__pull_register_events()
if not register_events then
return
end
for i=1, #register_events, 4 do
local header = register_events[i]
local type = bit.band(header, 0xFFFF)
local id = bit.rshift(header, 16)
local x = register_events[i + 1]
local y = register_events[i + 2]
local z = register_events[i + 3]
local list = updating_blocks[id]
if type == TYPE_REGISTER then
if not list then
list = {}
list.event = block.name(id) .. ".blocktick"
list.tps = 20 / (block.properties[id]["tick-interval"] or 1)
list.delta = 1.0 / list.tps
list.timer = 0.0
list.pointer = 0
updating_blocks[id] = list
end
table.insert(list, x)
table.insert(list, y)
table.insert(list, z)
elseif type == TYPE_UNREGISTER then
if list then
for j=1, #list, 3 do
if list[j] == x and list[j + 1] == y and list[j + 2] == z then
for k=1,3 do
table.remove(list, j)
end
j = j - 3
end
end
end
end
print(type, id, x, y, z)
end
end
network.__process_events = function() network.__process_events = function()
local CLIENT_CONNECTED = 1 local CLIENT_CONNECTED = 1
local CONNECTED_TO_SERVER = 2 local CONNECTED_TO_SERVER = 2
local DATAGRAM = 3 local DATAGRAM = 3
local RESPONSE = 4
local ON_SERVER = 1 local ON_SERVER = 1
local ON_CLIENT = 2 local ON_CLIENT = 2
@ -160,6 +262,22 @@ network.__process_events = function()
elseif side == ON_SERVER then elseif side == ON_SERVER then
_udp_server_callbacks[sid](addr, port, data) _udp_server_callbacks[sid](addr, port, data)
end end
elseif etype == RESPONSE then
if event[2] / 100 == 2 then
local callback = _http_response_callbacks[event[3]]
_http_response_callbacks[event[3]] = nil
_http_error_callbacks[event[3]] = nil
if callback then
callback(event[4])
end
else
local callback = _http_error_callbacks[event[3]]
_http_response_callbacks[event[3]] = nil
_http_error_callbacks[event[3]] = nil
if callback then
callback(event[2], event[4])
end
end
end end
-- remove dead servers -- remove dead servers

View File

@ -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", 2.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.13
dir[3] = dir[3] * 0.8 + viewdir[3] * 0.13
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

View File

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

View File

@ -0,0 +1,62 @@
local tsf = entity.transform
local body = entity.rigidbody
local mob = entity:require_component("core:mob")
local cheat_speed_mul = 10.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 * 3)
elseif iscrouch then
mob.move_vertical(-speed * 3)
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

View File

@ -22,6 +22,39 @@ local function configure_SSAO()
-- for test purposes -- for test purposes
end 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() function on_hud_open()
input.add_callback("player.pick", function () input.add_callback("player.pick", function ()
if hud.is_paused() or hud.is_inventory_open() then if hud.is_paused() or hud.is_inventory_open() then
@ -63,7 +96,7 @@ function on_hud_open()
player.set_noclip(pid, true) player.set_noclip(pid, true)
end end
end) end)
input.add_callback("player.flight", function () input.add_callback("player.flight", function ()
if hud.is_paused() or hud.is_inventory_open() then if hud.is_paused() or hud.is_inventory_open() then
return return
@ -81,39 +114,8 @@ function on_hud_open()
end) end)
configure_SSAO() configure_SSAO()
end
local function update_hand() hud.default_hand_controller = 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))
end end
function on_hud_render() function on_hud_render()

View File

@ -6,28 +6,29 @@ local names = {
"shadeless", "ambient-occlusion", "breakable", "selectable", "grounded", "shadeless", "ambient-occlusion", "breakable", "selectable", "grounded",
"hidden", "draw-group", "picking-item", "surface-replacement", "script-name", "hidden", "draw-group", "picking-item", "surface-replacement", "script-name",
"ui-layout", "inventory-size", "tick-interval", "overlay-texture", "ui-layout", "inventory-size", "tick-interval", "overlay-texture",
"translucent", "fields", "particles", "icon-type", "icon", "placing-block", "translucent", "fields", "particles", "icon-type", "icon", "placing-block",
"stack-size", "name", "script-file", "culling" "stack-size", "name", "script-file", "culling"
} }
for name, _ in pairs(user_props) do for name, _ in pairs(user_props) do
table.insert(names, name) table.insert(names, name)
end end
-- remove undefined properties
for id, blockprops in pairs(block.properties) do -- remove undefined properties and build tags set
for propname, value in pairs(blockprops) do local function process_properties(lib)
if not table.has(names, propname) then for id, props in pairs(lib.properties) do
blockprops[propname] = nil for propname, _ in pairs(props) do
end if not table.has(names, propname) then
end props[propname] = nil
end end
for id, itemprops in pairs(item.properties) do
for propname, value in pairs(itemprops) do
if not table.has(names, propname) then
itemprops[propname] = nil
end end
props.tags_set = lib.__get_tags(id)
end end
end end
process_properties(block)
process_properties(item)
local function make_read_only(t) local function make_read_only(t)
setmetatable(t, { setmetatable(t, {
__newindex = function() __newindex = function()
@ -57,6 +58,19 @@ local function cache_names(library)
function library.index(name) function library.index(name)
return indices[name] return indices[name]
end end
function library.has_tag(id, tag)
if id == nil then
error("id is nil")
end
local props = library.properties[id]
local tags_set = props.tags_set
if tags_set then
return tags_set[tag]
else
return false
end
end
end end
cache_names(block) cache_names(block)

View File

@ -157,6 +157,16 @@ console.add_command(
end 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( console.add_command(
"entity.despawn entity:sel=$entity.selected", "entity.despawn entity:sel=$entity.selected",
"Despawn entity", "Despawn entity",

View File

@ -79,13 +79,20 @@ local function complete_app_lib(app)
coroutine.yield() coroutine.yield()
end end
function app.sleep_until(predicate, max_ticks) function app.sleep_until(predicate, max_ticks, max_time)
max_ticks = max_ticks or 1e9 max_ticks = max_ticks or 1e9
max_time = max_time or 1e9
local ticks = 0 local ticks = 0
while ticks < max_ticks and not predicate() do local start_time = os.clock()
while ticks < max_ticks and
os.clock() - start_time < max_time
and not predicate() do
app.tick() app.tick()
ticks = ticks + 1 ticks = ticks + 1
end end
if os.clock() - start_time >= max_time then
error("timeout")
end
if ticks == max_ticks then if ticks == max_ticks then
error("max ticks exceed") error("max ticks exceed")
end end
@ -174,6 +181,7 @@ if enable_experimental then
require "core:internal/maths_inline" require "core:internal/maths_inline"
end end
asserts = require "core:internal/asserts"
events = require "core:internal/events" events = require "core:internal/events"
function pack.unload(prefix) function pack.unload(prefix)
@ -429,6 +437,8 @@ function __vc_on_hud_open()
hud.open_permanent("core:ingame_chat") hud.open_permanent("core:ingame_chat")
end end
local Schedule = require "core:schedule"
local ScheduleGroup_mt = { local ScheduleGroup_mt = {
__index = { __index = {
publish = function(self, schedule) publish = function(self, schedule)
@ -440,10 +450,11 @@ local ScheduleGroup_mt = {
for id, schedule in pairs(self._schedules) do for id, schedule in pairs(self._schedules) do
schedule:tick(dt) schedule:tick(dt)
end end
self.common:tick(dt)
end, end,
remove = function(self, id) remove = function(self, id)
self._schedules[id] = nil self._schedules[id] = nil
end end,
} }
} }
@ -451,6 +462,7 @@ local function ScheduleGroup()
return setmetatable({ return setmetatable({
_next_schedule = 1, _next_schedule = 1,
_schedules = {}, _schedules = {},
common = Schedule()
}, ScheduleGroup_mt) }, ScheduleGroup_mt)
end end
@ -527,15 +539,18 @@ function start_coroutine(chunk, name)
local co = coroutine.create(function() local co = coroutine.create(function()
local status, error = xpcall(chunk, function(err) local status, error = xpcall(chunk, function(err)
local fullmsg = "error: "..string.match(err, ": (.+)").."\n"..debug.traceback() local fullmsg = "error: "..string.match(err, ": (.+)").."\n"..debug.traceback()
gui.alert(fullmsg, function()
if world.is_open() then if hud then
__vc_app.close_world() gui.alert(fullmsg, function()
else if world.is_open() then
__vc_app.reset_content() __vc_app.close_world()
menu:reset() else
menu.page = "main" __vc_app.reset_content()
end menu:reset()
end) menu.page = "main"
end
end)
end
return fullmsg return fullmsg
end) end)
if not status then if not status then
@ -573,6 +588,8 @@ function __process_post_runnables()
end end
network.__process_events() network.__process_events()
block.__process_register_events()
block.__perform_ticks(time.delta())
end end
function time.post_runnable(runnable) function time.post_runnable(runnable)

View File

@ -668,3 +668,5 @@ end
bit.compile = require "core:bitwise/compiler" bit.compile = require "core:bitwise/compiler"
bit.execute = require "core:bitwise/executor" bit.execute = require "core:bitwise/executor"
random.Random = require "core:internal/random_generator"

View File

@ -9,7 +9,7 @@
// lighting // lighting
#define SKY_LIGHT_MUL 2.9 #define SKY_LIGHT_MUL 2.9
#define SKY_LIGHT_TINT vec3(0.9, 0.8, 1.0) #define SKY_LIGHT_TINT (vec3(1.0, 0.95, 0.9) * 2.0)
#define MIN_SKY_LIGHT vec3(0.2, 0.25, 0.33) #define MIN_SKY_LIGHT vec3(0.2, 0.25, 0.33)
// fog // fog

View File

@ -4,7 +4,7 @@
#include <constants> #include <constants>
vec3 pick_sky_color(samplerCube cubemap) { vec3 pick_sky_color(samplerCube cubemap) {
vec3 skyLightColor = texture(cubemap, vec3(0.4f, 0.0f, 0.4f)).rgb; vec3 skyLightColor = texture(cubemap, vec3(0.8f, 0.01f, 0.4f)).rgb;
skyLightColor *= SKY_LIGHT_TINT; skyLightColor *= SKY_LIGHT_TINT;
skyLightColor = min(vec3(1.0f), skyLightColor * SKY_LIGHT_MUL); skyLightColor = min(vec3(1.0f), skyLightColor * SKY_LIGHT_MUL);
skyLightColor = max(MIN_SKY_LIGHT, skyLightColor); skyLightColor = max(MIN_SKY_LIGHT, skyLightColor);

View File

@ -25,6 +25,7 @@ Grant %{0} pack modification permission?=Выдаць дазвол на мады
Error at line %{0}=Памылка ў радку %{0} Error at line %{0}=Памылка ў радку %{0}
Run=Запусціць Run=Запусціць
Filter=Фільтр Filter=Фільтр
Are you sure you want to open the link: =Ці вы ўпэўненыя, што хочаце адкрыць спасылку:
editor.info.tooltip=CTRL+S - Захаваць\nCTRL+R - Запусціць\nCTRL+Z - Скасаваць\nCTRL+Y - Паўтарыць editor.info.tooltip=CTRL+S - Захаваць\nCTRL+R - Запусціць\nCTRL+Z - Скасаваць\nCTRL+Y - Паўтарыць
devtools.traceback=Стэк выклікаў (ад апошняга) devtools.traceback=Стэк выклікаў (ад апошняга)

View File

@ -7,6 +7,7 @@ Back=Zurück
Continue=Weitermachen Continue=Weitermachen
Add=Hinzufügen Add=Hinzufügen
Converting world...=Weltkonvertierung im Gange... Converting world...=Weltkonvertierung im Gange...
Are you sure you want to open the link: =Sind Sie sicher, dass Sie den Link öffnen möchten:
error.pack-not-found=Paket konnte nicht gefunden werden error.pack-not-found=Paket konnte nicht gefunden werden
error.dependency-not-found=Die verwendete Abhängigkeit wurde nicht gefunden error.dependency-not-found=Die verwendete Abhängigkeit wurde nicht gefunden

View File

@ -7,6 +7,7 @@ world.convert-block-layouts=Blocks fields have changes! Convert world files?
pack.remove-confirm=Do you want to erase all pack(s) content from the world forever? pack.remove-confirm=Do you want to erase all pack(s) content from the world forever?
error.pack-not-found=Could not to find pack error.pack-not-found=Could not to find pack
error.dependency-not-found=Dependency pack is not found error.dependency-not-found=Dependency pack is not found
error.dependency-version-not-met=Dependency pack version is not met.
world.delete-confirm=Do you want to delete world forever? world.delete-confirm=Do you want to delete world forever?
world.generators.default=Default world.generators.default=Default
world.generators.flat=Flat world.generators.flat=Flat

View File

@ -19,6 +19,7 @@ Problems=Ongelmia
Monitor=Valvonta Monitor=Valvonta
Debug=Virheenkorjaus Debug=Virheenkorjaus
File=Tiedosto File=Tiedosto
Are you sure you want to open the link: =Haluatko varmasti avata linkin:
devtools.traceback=Puhelupino (viimeisestä) devtools.traceback=Puhelupino (viimeisestä)
error.pack-not-found=Pakettia ei löytynyt! error.pack-not-found=Pakettia ei löytynyt!

View File

@ -7,6 +7,7 @@ Back=Powrót
Continue=Kontynuacja Continue=Kontynuacja
Add=Dodać Add=Dodać
Converting world...=Konwersja świata w toku... Converting world...=Konwersja świata w toku...
Are you sure you want to open the link: =Czy na pewno chcesz otworzyć link:
error.pack-not-found=Nie udało się znaleźć pakietu error.pack-not-found=Nie udało się znaleźć pakietu

View File

@ -25,6 +25,7 @@ Grant %{0} pack modification permission?=Выдать разрешение на
Error at line %{0}=Ошибка на строке %{0} Error at line %{0}=Ошибка на строке %{0}
Run=Запустить Run=Запустить
Filter=Фильтр Filter=Фильтр
Are you sure you want to open the link: =Вы уверены, что хотите открыть ссылку:
editor.info.tooltip=CTRL+S - Сохранить\nCTRL+R - Запустить\nCTRL+Z - Отменить\nCTRL+Y - Повторить editor.info.tooltip=CTRL+S - Сохранить\nCTRL+R - Запустить\nCTRL+Z - Отменить\nCTRL+Y - Повторить
devtools.traceback=Стек вызовов (от последнего) devtools.traceback=Стек вызовов (от последнего)
@ -32,6 +33,7 @@ devtools.output=Вывод
error.pack-not-found=Не удалось найти пакет error.pack-not-found=Не удалось найти пакет
error.dependency-not-found=Используемая зависимость не найдена error.dependency-not-found=Используемая зависимость не найдена
error.dependency-version-not-met=Версия зависимости не соответствует необходимой
pack.remove-confirm=Удалить весь поставляемый паком/паками контент из мира (безвозвратно)? pack.remove-confirm=Удалить весь поставляемый паком/паками контент из мира (безвозвратно)?
# Подсказки # Подсказки

View File

@ -23,6 +23,7 @@ devtools.traceback=Стек викликів (від останнього)
error.pack-not-found=Не вдалося знайти пакет error.pack-not-found=Не вдалося знайти пакет
error.dependency-not-found=Використовувана залежність не знайдена error.dependency-not-found=Використовувана залежність не знайдена
pack.remove-confirm=Видалити весь контент, що постачається паком/паками зі світу (безповоротно)? pack.remove-confirm=Видалити весь контент, що постачається паком/паками зі світу (безповоротно)?
Are you sure you want to open the link: =Ви впевнені, що хочете відкрити посилання:
# Меню # Меню
menu.Apply=Застосувати menu.Apply=Застосувати

View File

@ -24,6 +24,7 @@ Save=Saqlash
Grant %{0} pack modification permission?=%{0} toplamini ozgartirish ruxsatini berilsinmi? Grant %{0} pack modification permission?=%{0} toplamini ozgartirish ruxsatini berilsinmi?
Error at line %{0}=%{0}-qatorida xatolik Error at line %{0}=%{0}-qatorida xatolik
Run=Ishga tushirish Run=Ishga tushirish
Are you sure you want to open the link: =Haqiqatan ham havolani ochmoqchimisiz:
editor.info.tooltip=CTRL+S - Saqlash\nCTRL+R - Ishga tushirish\nCTRL+Z - Bekor qilish\nCTRL+Y - Qayta bajarish editor.info.tooltip=CTRL+S - Saqlash\nCTRL+R - Ishga tushirish\nCTRL+Z - Bekor qilish\nCTRL+Y - Qayta bajarish
devtools.traceback=Chaqiruvlar steki (so`nggisidan boshlab) devtools.traceback=Chaqiruvlar steki (so`nggisidan boshlab)

View File

@ -71,8 +71,8 @@ static auto process_program(const ResPaths& paths, const std::string& filename)
auto& preprocessor = *Shader::preprocessor; auto& preprocessor = *Shader::preprocessor;
auto vertex = preprocessor.process(vertexFile, vertexSource); auto vertex = preprocessor.process(vertexFile, vertexSource, false, {});
auto fragment = preprocessor.process(fragmentFile, fragmentSource); auto fragment = preprocessor.process(fragmentFile, fragmentSource, false, {});
return std::make_pair(vertex, fragment); return std::make_pair(vertex, fragment);
} }
@ -121,7 +121,7 @@ assetload::postfunc assetload::posteffect(
auto& preprocessor = *Shader::preprocessor; auto& preprocessor = *Shader::preprocessor;
preprocessor.addHeader( preprocessor.addHeader(
"__effect__", preprocessor.process(effectFile, effectSource, true) "__effect__", preprocessor.process(effectFile, effectSource, true, {})
); );
auto [vertex, fragment] = process_program(paths, SHADERS_FOLDER + "/effect"); auto [vertex, fragment] = process_program(paths, SHADERS_FOLDER + "/effect");

View File

@ -22,6 +22,10 @@ void GLSLExtension::setPaths(const ResPaths* paths) {
this->paths = paths; this->paths = paths;
} }
void GLSLExtension::setTraceOutput(bool enabled) {
this->traceOutput = enabled;
}
void GLSLExtension::loadHeader(const std::string& name) { void GLSLExtension::loadHeader(const std::string& name) {
if (paths == nullptr) { if (paths == nullptr) {
return; return;
@ -29,7 +33,7 @@ void GLSLExtension::loadHeader(const std::string& name) {
io::path file = paths->find("shaders/lib/" + name + ".glsl"); io::path file = paths->find("shaders/lib/" + name + ".glsl");
std::string source = io::read_string(file); std::string source = io::read_string(file);
addHeader(name, {}); addHeader(name, {});
addHeader(name, process(file, source, true)); addHeader(name, process(file, source, true, {}));
} }
void GLSLExtension::addHeader(const std::string& name, ProcessingResult header) { void GLSLExtension::addHeader(const std::string& name, ProcessingResult header) {
@ -123,13 +127,22 @@ static Value default_value_for(Type type) {
class GLSLParser : public BasicParser<char> { class GLSLParser : public BasicParser<char> {
public: 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<std::string>& defines
)
: BasicParser(file, source), glsl(glsl) { : BasicParser(file, source), glsl(glsl) {
if (!header) { if (!header) {
ss << "#version " << GLSLExtension::VERSION << '\n'; ss << "#version " << GLSLExtension::VERSION << '\n';
} for (auto& entry : defines) {
for (auto& entry : glsl.getDefines()) { ss << "#define " << entry << '\n';
ss << "#define " << entry.first << " " << entry.second << '\n'; }
for (auto& entry : defines) {
ss << "#define " << entry << '\n';
}
} }
uint linenum = 1; uint linenum = 1;
source_line(ss, linenum); source_line(ss, linenum);
@ -289,10 +302,34 @@ private:
std::stringstream ss; 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( 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<std::string>& defines
) { ) {
std::string filename = file.string(); std::string filename = file.string();
GLSLParser parser(*this, filename, source, header); GLSLParser parser(*this, filename, source, header, defines);
return parser.process(); auto result = parser.process();
if (traceOutput) {
trace_output(file, source, result);
}
return result;
} }

View File

@ -5,6 +5,7 @@
#include <vector> #include <vector>
#include "io/io.hpp" #include "io/io.hpp"
#include "data/setting.hpp"
#include "graphics/core/PostEffect.hpp" #include "graphics/core/PostEffect.hpp"
class ResPaths; class ResPaths;
@ -19,6 +20,7 @@ public:
}; };
void setPaths(const ResPaths* paths); void setPaths(const ResPaths* paths);
void setTraceOutput(bool enabled);
void define(const std::string& name, std::string value); void define(const std::string& name, std::string value);
void undefine(const std::string& name); void undefine(const std::string& name);
@ -37,7 +39,8 @@ public:
ProcessingResult process( ProcessingResult process(
const io::path& file, const io::path& file,
const std::string& source, const std::string& source,
bool header = false bool header,
const std::vector<std::string>& defines
); );
static inline std::string VERSION = "330 core"; static inline std::string VERSION = "330 core";
@ -46,4 +49,5 @@ private:
std::unordered_map<std::string, std::string> defines; std::unordered_map<std::string, std::string> defines;
const ResPaths* paths = nullptr; const ResPaths* paths = nullptr;
bool traceOutput = false;
}; };

View File

@ -6,7 +6,7 @@
#include <string> #include <string>
inline constexpr int ENGINE_VERSION_MAJOR = 0; inline constexpr int ENGINE_VERSION_MAJOR = 0;
inline constexpr int ENGINE_VERSION_MINOR = 29; inline constexpr int ENGINE_VERSION_MINOR = 30;
#ifdef NDEBUG #ifdef NDEBUG
inline constexpr bool ENGINE_DEBUG_BUILD = false; inline constexpr bool ENGINE_DEBUG_BUILD = false;
@ -14,7 +14,7 @@ inline constexpr bool ENGINE_DEBUG_BUILD = false;
inline constexpr bool ENGINE_DEBUG_BUILD = true; inline constexpr bool ENGINE_DEBUG_BUILD = true;
#endif // NDEBUG #endif // NDEBUG
inline const std::string ENGINE_VERSION_STRING = "0.29"; inline const std::string ENGINE_VERSION_STRING = "0.30";
/// @brief world regions format version /// @brief world regions format version
inline constexpr uint REGION_FORMAT_VERSION = 3; inline constexpr uint REGION_FORMAT_VERSION = 3;

View File

@ -35,13 +35,15 @@ Content::Content(
UptrsMap<std::string, BlockMaterial> blockMaterials, UptrsMap<std::string, BlockMaterial> blockMaterials,
UptrsMap<std::string, rigging::SkeletonConfig> skeletons, UptrsMap<std::string, rigging::SkeletonConfig> skeletons,
ResourceIndicesSet resourceIndices, ResourceIndicesSet resourceIndices,
dv::value defaults dv::value defaults,
std::unordered_map<std::string, int> tags
) )
: indices(std::move(indices)), : indices(std::move(indices)),
packs(std::move(packs)), packs(std::move(packs)),
blockMaterials(std::move(blockMaterials)), blockMaterials(std::move(blockMaterials)),
skeletons(std::move(skeletons)), skeletons(std::move(skeletons)),
defaults(std::move(defaults)), defaults(std::move(defaults)),
tags(std::move(tags)),
blocks(std::move(blocks)), blocks(std::move(blocks)),
items(std::move(items)), items(std::move(items)),
entities(std::move(entities)), entities(std::move(entities)),

View File

@ -176,6 +176,7 @@ class Content {
UptrsMap<std::string, BlockMaterial> blockMaterials; UptrsMap<std::string, BlockMaterial> blockMaterials;
UptrsMap<std::string, rigging::SkeletonConfig> skeletons; UptrsMap<std::string, rigging::SkeletonConfig> skeletons;
dv::value defaults = nullptr; dv::value defaults = nullptr;
std::unordered_map<std::string, int> tags;
public: public:
ContentUnitDefs<Block> blocks; ContentUnitDefs<Block> blocks;
ContentUnitDefs<ItemDef> items; ContentUnitDefs<ItemDef> items;
@ -195,7 +196,8 @@ public:
UptrsMap<std::string, BlockMaterial> blockMaterials, UptrsMap<std::string, BlockMaterial> blockMaterials,
UptrsMap<std::string, rigging::SkeletonConfig> skeletons, UptrsMap<std::string, rigging::SkeletonConfig> skeletons,
ResourceIndicesSet resourceIndices, ResourceIndicesSet resourceIndices,
dv::value defaults dv::value defaults,
std::unordered_map<std::string, int> tags
); );
~Content(); ~Content();
@ -211,6 +213,14 @@ public:
return defaults; return defaults;
} }
int getTagIndex(const std::string& tag) const {
const auto& found = tags.find(tag);
if (found == tags.end()) {
return -1;
}
return found->second;
}
const rigging::SkeletonConfig* getSkeleton(const std::string& id) const; const rigging::SkeletonConfig* getSkeleton(const std::string& id) const;
const rigging::SkeletonConfig& requireSkeleton(const std::string& id) const; const rigging::SkeletonConfig& requireSkeleton(const std::string& id) const;
const BlockMaterial* findBlockMaterial(const std::string& id) const; const BlockMaterial* findBlockMaterial(const std::string& id) const;

View File

@ -28,6 +28,9 @@ std::unique_ptr<Content> ContentBuilder::build() {
// Generating runtime info // Generating runtime info
def.rt.id = blockDefsIndices.size(); def.rt.id = blockDefsIndices.size();
def.rt.emissive = *reinterpret_cast<uint32_t*>(def.emission); def.rt.emissive = *reinterpret_cast<uint32_t*>(def.emission);
for (const auto& tag : def.tags) {
def.rt.tags.insert(tags.add(tag));
}
if (def.variants) { if (def.variants) {
for (auto& variant : def.variants->variants) { for (auto& variant : def.variants->variants) {
@ -58,7 +61,7 @@ std::unique_ptr<Content> ContentBuilder::build() {
} }
blockDefsIndices.push_back(&def); blockDefsIndices.push_back(&def);
groups->insert(def.defaults.drawGroup); // FIXME groups->insert(def.defaults.drawGroup); // FIXME: variants
} }
std::vector<ItemDef*> itemDefsIndices; std::vector<ItemDef*> itemDefsIndices;
@ -93,7 +96,8 @@ std::unique_ptr<Content> ContentBuilder::build() {
std::move(blockMaterials), std::move(blockMaterials),
std::move(skeletons), std::move(skeletons),
std::move(resourceIndices), std::move(resourceIndices),
std::move(defaults) std::move(defaults),
std::move(tags.map)
); );
// Now, it's time to resolve foreign keys // Now, it's time to resolve foreign keys

View File

@ -62,6 +62,27 @@ public:
} }
}; };
struct TagsIndices {
int nextIndex = 1;
std::unordered_map<std::string, int> map;
int add(const std::string& tag) {
const auto& found = map.find(tag);
if (found != map.end()) {
return found->second;
}
return map[tag] = nextIndex++;
}
int indexOf(const std::string& tag) {
const auto& found = map.find(tag);
if (found == map.end()) {
return -1;
}
return found->second;
}
};
class ContentBuilder { class ContentBuilder {
UptrsMap<std::string, BlockMaterial> blockMaterials; UptrsMap<std::string, BlockMaterial> blockMaterials;
UptrsMap<std::string, rigging::SkeletonConfig> skeletons; UptrsMap<std::string, rigging::SkeletonConfig> skeletons;
@ -74,6 +95,7 @@ public:
ContentUnitBuilder<GeneratorDef> generators {allNames, ContentType::GENERATOR}; ContentUnitBuilder<GeneratorDef> generators {allNames, ContentType::GENERATOR};
ResourceIndicesSet resourceIndices {}; ResourceIndicesSet resourceIndices {};
dv::value defaults = nullptr; dv::value defaults = nullptr;
TagsIndices tags {};
~ContentBuilder(); ~ContentBuilder();

View File

@ -288,6 +288,7 @@ void ContentLoader::loadContent(const dv::value& root) {
item.iconType = ItemIconType::BLOCK; item.iconType = ItemIconType::BLOCK;
item.icon = def.name; item.icon = def.name;
item.placingBlock = def.name; item.placingBlock = def.name;
item.tags = def.tags;
for (uint j = 0; j < 4; j++) { for (uint j = 0; j < 4; j++) {
item.emission[j] = def.emission[j]; item.emission[j] = def.emission[j];
@ -412,6 +413,25 @@ void ContentLoader::load() {
if (io::exists(contentFile)) { if (io::exists(contentFile)) {
loadContent(io::read_json(contentFile)); loadContent(io::read_json(contentFile));
} }
// Load attached tags
io::path tagsFile = folder / "tags.toml";
if (io::exists(tagsFile)) {
auto tagsMap = io::read_object(tagsFile);
for (const auto& [key, list] : tagsMap.asObject()) {
for (const auto& id : list) {
const auto& stringId = id.asString();
if (auto block = builder.blocks.get(stringId)) {
block->tags.push_back(key);
if (auto item = builder.items.get(stringId + BLOCK_ITEM_SUFFIX)) {
item->tags.push_back(key);
}
} else if (auto item = builder.items.get(stringId)) {
item->tags.push_back(key);
}
}
}
}
} }
template <class T> template <class T>

View File

@ -118,21 +118,58 @@ ContentPack ContentPack::read(const io::path& folder) {
const auto& dependencies = *found; const auto& dependencies = *found;
for (const auto& elem : dependencies) { for (const auto& elem : dependencies) {
std::string depName = elem.asString(); std::string depName = elem.asString();
auto level = DependencyLevel::required; auto level = DependencyLevel::REQUIRED;
switch (depName.at(0)) { switch (depName.at(0)) {
case '!': case '!':
depName = depName.substr(1); depName = depName.substr(1);
break; break;
case '?': case '?':
depName = depName.substr(1); depName = depName.substr(1);
level = DependencyLevel::optional; level = DependencyLevel::OPTIONAL;
break; break;
case '~': case '~':
depName = depName.substr(1); depName = depName.substr(1);
level = DependencyLevel::weak; level = DependencyLevel::WEAK;
break; break;
} }
pack.dependencies.push_back({level, depName});
std::string depVer = "*";
std::string depVerOperator = "=";
size_t versionPos = depName.rfind("@");
if (versionPos != std::string::npos) {
depVer = depName.substr(versionPos + 1);
depName = depName.substr(0, versionPos);
if (depVer.size() >= 2) {
std::string op = depVer.substr(0, 2);
std::uint8_t op_size = 0;
// Two symbol operators
if (op == ">=" || op == "=>" || op == "<=" || op == "=<") {
op_size = 2;
depVerOperator = op;
}
// One symbol operators
else {
op = depVer.substr(0, 1);
if (op == ">" || op == "<") {
op_size = 1;
depVerOperator = op;
}
}
depVer = depVer.substr(op_size);
} else {
if (depVer == ">" || depVer == "<"){
depVer = "*";
}
}
}
pack.dependencies.push_back({level, depName, depVer, depVerOperator});
} }
} }

View File

@ -20,21 +20,28 @@ public:
io::path folder, io::path folder,
const std::string& message const std::string& message
); );
std::string getPackId() const; std::string getPackId() const;
io::path getFolder() const; io::path getFolder() const;
}; };
enum class DependencyVersionOperator {
EQUAL, GREATHER, LESS,
GREATHER_OR_EQUAL, LESS_OR_EQUAL
};
enum class DependencyLevel { enum class DependencyLevel {
required, // dependency must be installed REQUIRED, // dependency must be installed
optional, // dependency will be installed if found OPTIONAL, // dependency will be installed if found
weak, // only affects packs order WEAK, // only affects packs order
}; };
/// @brief Content-pack that should be installed earlier the dependent /// @brief Content-pack that should be installed earlier the dependent
struct DependencyPack { struct DependencyPack {
DependencyLevel level; DependencyLevel level;
std::string id; std::string id;
std::string version;
std::string op;
}; };
struct ContentPackStats { struct ContentPackStats {

View File

@ -0,0 +1,67 @@
#include "ContentPackVersion.hpp"
#include <algorithm>
#include <iostream>
#include <sstream>
#include "coders/commons.hpp"
Version::Version(const std::string& version) {
major = 0;
minor = 0;
patch = 0;
std::vector<int> parts;
std::stringstream ss(version);
std::string part;
while (std::getline(ss, part, '.')) {
if (!part.empty()) {
parts.push_back(std::stoi(part));
}
}
if (parts.size() > 0) major = parts[0];
if (parts.size() > 1) minor = parts[1];
if (parts.size() > 2) patch = parts[2];
}
DependencyVersionOperator Version::string_to_operator(const std::string& op) {
if (op == "=")
return DependencyVersionOperator::EQUAL;
else if (op == ">")
return DependencyVersionOperator::GREATHER;
else if (op == "<")
return DependencyVersionOperator::LESS;
else if (op == ">=" || op == "=>")
return DependencyVersionOperator::GREATHER_OR_EQUAL;
else if (op == "<=" || op == "=<")
return DependencyVersionOperator::LESS_OR_EQUAL;
else
return DependencyVersionOperator::EQUAL;
}
bool isNumber(const std::string& s) {
return !s.empty() && std::all_of(s.begin(), s.end(), ::is_digit);
}
bool Version::matches_pattern(const std::string& version) {
for (char c : version) {
if (!isdigit(c) && c != '.') {
return false;
}
}
std::stringstream ss(version);
std::vector<std::string> parts;
std::string part;
while (std::getline(ss, part, '.')) {
if (part.empty()) return false;
if (!isNumber(part)) return false;
parts.push_back(part);
}
return parts.size() == 2 || parts.size() == 3;
}

View File

@ -0,0 +1,57 @@
#include <string>
#include "content/ContentPack.hpp"
class Version {
public:
int major;
int minor;
int patch;
Version(const std::string& version);
bool operator==(const Version& other) const {
return major == other.major && minor == other.minor &&
patch == other.patch;
}
bool operator<(const Version& other) const {
if (major != other.major) return major < other.major;
if (minor != other.minor) return minor < other.minor;
return patch < other.patch;
}
bool operator>(const Version& other) const {
return other < *this;
}
bool operator>=(const Version& other) const {
return !(*this < other);
}
bool operator<=(const Version& other) const {
return !(*this > other);
}
bool process_operator(const std::string& op, const Version& other) const {
auto dep_op = Version::string_to_operator(op);
switch (dep_op) {
case DependencyVersionOperator::EQUAL:
return *this == other;
case DependencyVersionOperator::GREATHER:
return *this > other;
case DependencyVersionOperator::LESS:
return *this < other;
case DependencyVersionOperator::LESS_OR_EQUAL:
return *this <= other;
case DependencyVersionOperator::GREATHER_OR_EQUAL:
return *this >= other;
default:
return false;
}
}
static DependencyVersionOperator string_to_operator(const std::string& op);
static bool matches_pattern(const std::string& version);
};

View File

@ -3,6 +3,7 @@
#include <queue> #include <queue>
#include <sstream> #include <sstream>
#include "ContentPackVersion.hpp"
#include "util/listutil.hpp" #include "util/listutil.hpp"
PacksManager::PacksManager() = default; PacksManager::PacksManager() = default;
@ -90,7 +91,7 @@ static bool resolve_dependencies(
} }
auto found = packs.find(dep.id); auto found = packs.find(dep.id);
bool exists = found != packs.end(); bool exists = found != packs.end();
if (!exists && dep.level == DependencyLevel::required) { if (!exists && dep.level == DependencyLevel::REQUIRED) {
throw contentpack_error( throw contentpack_error(
dep.id, io::path(), "dependency of '" + pack->id + "'" dep.id, io::path(), "dependency of '" + pack->id + "'"
); );
@ -99,15 +100,32 @@ static bool resolve_dependencies(
// ignored for optional or weak dependencies // ignored for optional or weak dependencies
continue; continue;
} }
if (resolveWeaks && dep.level == DependencyLevel::weak) { if (resolveWeaks && dep.level == DependencyLevel::WEAK) {
// dependency pack is found but not added yet // dependency pack is found but not added yet
// resolveWeaks is used on second iteration, so it's will not be // resolveWeaks is used on second iteration, so it's will not be
// added // added
continue; continue;
} }
auto dep_pack = found -> second;
if (Version::matches_pattern(dep.version) && Version::matches_pattern(dep_pack.version)
&& Version(dep_pack.version)
.process_operator(dep.op, Version(dep.version))
) {
// dependency pack version meets the required one
continue;
} else if (dep.version == "*" || dep.version == dep_pack.version){
// fallback: dependency pack version also meets required one
continue;
} else {
throw contentpack_error(
dep.id, io::path(), "does not meet required version '" + dep.op + dep.version +"' of '" + pack->id + "'"
);
}
if (!util::contains(allNames, dep.id) && if (!util::contains(allNames, dep.id) &&
dep.level != DependencyLevel::weak) { dep.level != DependencyLevel::WEAK) {
allNames.push_back(dep.id); allNames.push_back(dep.id);
queue.push(&found->second); queue.push(&found->second);
} }

View File

@ -1,5 +1,6 @@
#define VC_ENABLE_REFLECTION #define VC_ENABLE_REFLECTION
#include "ContentUnitLoader.hpp" #include "ContentUnitLoader.hpp"
#include "ContentLoadingCommons.hpp"
#include "../ContentBuilder.hpp" #include "../ContentBuilder.hpp"
#include "coders/json.hpp" #include "coders/json.hpp"
@ -87,20 +88,8 @@ template<> void ContentUnitLoader<Block>::loadUnit(
Block& def, const std::string& name, const io::path& file Block& def, const std::string& name, const io::path& file
) { ) {
auto root = io::read_json(file); auto root = io::read_json(file);
if (def.properties == nullptr) { process_properties(def, name, root);
def.properties = dv::object(); process_tags(def, root);
def.properties["name"] = name;
}
for (auto& [key, value] : root.asObject()) {
auto pos = key.rfind('@');
if (pos == std::string::npos) {
def.properties[key] = value;
continue;
}
auto field = key.substr(0, pos);
auto suffix = key.substr(pos + 1);
process_method(def.properties, suffix, field, value);
}
if (root.has("parent")) { if (root.has("parent")) {
const auto& parentName = root["parent"].asString(); const auto& parentName = root["parent"].asString();

View File

@ -0,0 +1,37 @@
#pragma once
#include "data/dv.hpp"
#include <string>
template <typename T>
inline void process_properties(T& def, const std::string& name, const dv::value& root) {
if (def.properties == nullptr) {
def.properties = dv::object();
def.properties["name"] = name;
}
for (auto& [key, value] : root.asObject()) {
auto pos = key.rfind('@');
if (pos == std::string::npos) {
def.properties[key] = value;
continue;
}
auto field = key.substr(0, pos);
auto suffix = key.substr(pos + 1);
process_method(def.properties, suffix, field, value);
}
}
template <typename T>
inline void process_tags(T& def, const dv::value& root) {
if (!root.has("tags")) {
return;
}
const auto& tags = root["tags"];
for (const auto& tagValue : tags) {
if (!tagValue.isString()) {
continue;
}
def.tags.push_back(tagValue.asString());
}
}

View File

@ -30,7 +30,18 @@ template<> void ContentUnitLoader<EntityDef>::loadUnit(
if (auto found = root.at("components")) { if (auto found = root.at("components")) {
for (const auto& elem : *found) { 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")) { if (auto found = root.at("hitbox")) {

View File

@ -1,5 +1,6 @@
#define VC_ENABLE_REFLECTION #define VC_ENABLE_REFLECTION
#include "ContentUnitLoader.hpp" #include "ContentUnitLoader.hpp"
#include "ContentLoadingCommons.hpp"
#include "../ContentBuilder.hpp" #include "../ContentBuilder.hpp"
#include "coders/json.hpp" #include "coders/json.hpp"
@ -12,11 +13,13 @@
static debug::Logger logger("item-content-loader"); static debug::Logger logger("item-content-loader");
template<> void ContentUnitLoader<ItemDef>::loadUnit( template<> void ContentUnitLoader<ItemDef>::loadUnit(
ItemDef& def, const std::string& name, const io::path& file ItemDef& def, const std::string& name, const io::path& file
) { ) {
auto root = io::read_json(file); auto root = io::read_json(file);
def.properties = root; process_properties(def, name, root);
process_tags(def, root);
if (root.has("parent")) { if (root.has("parent")) {
const auto& parentName = root["parent"].asString(); const auto& parentName = root["parent"].asString();

View File

@ -46,12 +46,12 @@ namespace dv {
if (!map.has(key)) { if (!map.has(key)) {
return; return;
} }
auto& list = map[key]; const auto& srcList = map[key];
for (size_t i = 0; i < n; i++) { for (size_t i = 0; i < n; i++) {
if constexpr (std::is_floating_point<T>()) { if constexpr (std::is_floating_point<T>()) {
vec[i] = list[i].asNumber(); vec[i] = srcList[i].asNumber();
} else { } else {
vec[i] = list[i].asInteger(); vec[i] = srcList[i].asInteger();
} }
} }
} }

View File

@ -143,6 +143,12 @@ void Engine::initializeClient() {
}, },
true true
)); ));
keepAlive(settings.debug.doTraceShaders.observe(
[](bool value) {
Shader::preprocessor->setTraceOutput(value);
},
true
));
} }
void Engine::initialize(CoreParameters coreParameters) { void Engine::initialize(CoreParameters coreParameters) {

View File

@ -12,12 +12,14 @@
#include "graphics/render/WorldRenderer.hpp" #include "graphics/render/WorldRenderer.hpp"
#include "graphics/render/ParticlesRenderer.hpp" #include "graphics/render/ParticlesRenderer.hpp"
#include "graphics/render/ChunksRenderer.hpp" #include "graphics/render/ChunksRenderer.hpp"
#include "graphics/render/DebugLinesRenderer.hpp"
#include "logic/scripting/scripting.hpp" #include "logic/scripting/scripting.hpp"
#include "network/Network.hpp" #include "network/Network.hpp"
#include "objects/Player.hpp" #include "objects/Player.hpp"
#include "objects/Players.hpp" #include "objects/Players.hpp"
#include "objects/Entities.hpp" #include "objects/Entities.hpp"
#include "objects/EntityDef.hpp" #include "objects/EntityDef.hpp"
#include "objects/Entity.hpp"
#include "physics/Hitbox.hpp" #include "physics/Hitbox.hpp"
#include "util/stringutil.hpp" #include "util/stringutil.hpp"
#include "voxels/Block.hpp" #include "voxels/Block.hpp"
@ -44,6 +46,7 @@ static std::shared_ptr<Label> create_label(GUI& gui, wstringsupplier supplier) {
// TODO: move to xml // TODO: move to xml
// TODO: move to xml finally // TODO: move to xml finally
// TODO: move to xml finally // TODO: move to xml finally
// TODO: move to xml finally
std::shared_ptr<UINode> create_debug_panel( std::shared_ptr<UINode> create_debug_panel(
Engine& engine, Engine& engine,
Level& level, Level& level,
@ -260,6 +263,18 @@ std::shared_ptr<UINode> create_debug_panel(
}); });
panel->add(checkbox); panel->add(checkbox);
} }
{
auto checkbox = std::make_shared<FullCheckBox>(
gui, L"Show Paths", glm::vec2(400, 24)
);
checkbox->setSupplier([=]() {
return DebugLinesRenderer::showPaths;
});
checkbox->setConsumer([=](bool checked) {
DebugLinesRenderer::showPaths = checked;
});
panel->add(checkbox);
}
{ {
auto checkbox = std::make_shared<FullCheckBox>( auto checkbox = std::make_shared<FullCheckBox>(
gui, L"Show Generator Minimap", glm::vec2(400, 24) gui, L"Show Generator Minimap", glm::vec2(400, 24)

View File

@ -207,6 +207,9 @@ Hud::Hud(Engine& engine, LevelFrontend& frontend, Player& player)
} }
Hud::~Hud() { Hud::~Hud() {
if (input.isCursorLocked()) {
input.toggleCursor();
}
// removing all controlled ui // removing all controlled ui
for (auto& element : elements) { for (auto& element : elements) {
onRemove(element); onRemove(element);
@ -339,7 +342,7 @@ void Hud::update(bool visible) {
if (!gui.isFocusCaught()) { if (!gui.isFocusCaught()) {
processInput(visible); processInput(visible);
} }
if ((isMenuOpen || inventoryOpen) == input.getCursor().locked) { if ((isMenuOpen || inventoryOpen) == input.isCursorLocked()) {
input.toggleCursor(); input.toggleCursor();
} }

View File

@ -176,7 +176,7 @@ void LevelScreen::saveWorldPreview() {
static_cast<uint>(previewSize)} static_cast<uint>(previewSize)}
); );
renderer->draw(ctx, camera, false, true, 0.0f, *postProcessing); renderer->renderFrame(ctx, camera, false, true, 0.0f, *postProcessing);
auto image = postProcessing->toImage(); auto image = postProcessing->toImage();
image->flipY(); image->flipY();
imageio::write("world:preview.png", image.get()); imageio::write("world:preview.png", image.get());
@ -263,7 +263,7 @@ void LevelScreen::draw(float delta) {
if (!hud->isPause()) { if (!hud->isPause()) {
scripting::on_entities_render(engine.getTime().getDelta()); scripting::on_entities_render(engine.getTime().getDelta());
} }
renderer->draw( renderer->renderFrame(
ctx, *camera, hudVisible, hud->isPause(), delta, *postProcessing ctx, *camera, hudVisible, hud->isPause(), delta, *postProcessing
); );

View File

@ -1,5 +1,6 @@
#include "ImageData.hpp" #include "ImageData.hpp"
#include <glm/glm.hpp>
#include <assert.h> #include <assert.h>
#include <stdexcept> #include <stdexcept>
#include <cstring> #include <cstring>
@ -187,6 +188,41 @@ void ImageData::drawLine(int x1, int y1, int x2, int y2, const glm::ivec4& color
} }
} }
template<uint channels>
static void draw_rect(ImageData& image, int dstX, int dstY, int width, int height, const glm::ivec4& color) {
ubyte* data = image.getData();
int imageWidth = image.getWidth();
int imageHeight = image.getHeight();
int x1 = glm::min(glm::max(dstX, 0), imageWidth - 1);
int y1 = glm::min(glm::max(dstY, 0), imageHeight - 1);
int x2 = glm::min(glm::max(dstX + width, 0), imageWidth - 1);
int y2 = glm::min(glm::max(dstY + height, 0), imageHeight - 1);
for (int y = y1; y <= y2; y++) {
for (int x = x1; x <= x2; x++) {
int index = (y * imageWidth + x) * channels;
for (int i = 0; i < channels; i++) {
data[index + i] = color[i];
}
}
}
}
void ImageData::drawRect(int x, int y, int width, int height, const glm::ivec4& color) {
switch (format) {
case ImageFormat::rgb888:
draw_rect<3>(*this, x, y, width, height, color);
break;
case ImageFormat::rgba8888:
draw_rect<4>(*this, x, y, width, height, color);
break;
default:
break;
}
}
void ImageData::blitRGB_on_RGBA(const ImageData& image, int x, int y) { void ImageData::blitRGB_on_RGBA(const ImageData& image, int x, int y) {
ubyte* source = image.getData(); ubyte* source = image.getData();
uint srcwidth = image.getWidth(); uint srcwidth = image.getWidth();

View File

@ -28,6 +28,7 @@ public:
void flipY(); void flipY();
void drawLine(int x1, int y1, int x2, int y2, const glm::ivec4& color); void drawLine(int x1, int y1, int x2, int y2, const glm::ivec4& color);
void drawRect(int x, int y, int width, int height, const glm::ivec4& color);
void blit(const ImageData& image, int x, int y); void blit(const ImageData& image, int x, int y);
void extrude(int x, int y, int w, int h); void extrude(int x, int y, int w, int h);
void fixAlphaColor(); void fixAlphaColor();

View File

@ -2,7 +2,6 @@
#include <exception> #include <exception>
#include <fstream> #include <fstream>
#include <iostream>
#include <sstream> #include <sstream>
#include <filesystem> #include <filesystem>
@ -138,15 +137,21 @@ glshader compile_shader(GLenum type, const GLchar* source, const std::string& fi
} }
static GLuint compile_program( static GLuint compile_program(
const Shader::Source& vertexSource, const Shader::Source& fragmentSource const Shader::Source& vertexSource,
const Shader::Source& fragmentSource,
const std::vector<std::string>& defines
) { ) {
auto& preprocessor = *Shader::preprocessor; auto& preprocessor = *Shader::preprocessor;
auto vertexCode = std::move( auto vertexCode = std::move(
preprocessor.process(vertexSource.file, vertexSource.code).code preprocessor
.process(vertexSource.file, vertexSource.code, false, defines)
.code
); );
auto fragmentCode = std::move( auto fragmentCode = std::move(
preprocessor.process(fragmentSource.file, fragmentSource.code).code preprocessor
.process(fragmentSource.file, fragmentSource.code, false, defines)
.code
); );
const GLchar* vCode = vertexCode.c_str(); const GLchar* vCode = vertexCode.c_str();
@ -176,8 +181,8 @@ static GLuint compile_program(
return program; return program;
} }
void Shader::recompile() { void Shader::recompile(const std::vector<std::string>& defines) {
GLuint newProgram = compile_program(vertexSource, fragmentSource); GLuint newProgram = compile_program(vertexSource, fragmentSource, defines);
glDeleteProgram(id); glDeleteProgram(id);
id = newProgram; id = newProgram;
uniformLocations.clear(); uniformLocations.clear();
@ -188,7 +193,7 @@ std::unique_ptr<Shader> Shader::create(
Source&& vertexSource, Source&& fragmentSource Source&& vertexSource, Source&& fragmentSource
) { ) {
return std::make_unique<Shader>( return std::make_unique<Shader>(
compile_program(vertexSource, fragmentSource), compile_program(vertexSource, fragmentSource, {}),
std::move(vertexSource), std::move(vertexSource),
std::move(fragmentSource) std::move(fragmentSource)
); );

View File

@ -4,6 +4,7 @@
#include <string> #include <string>
#include <memory> #include <memory>
#include <vector>
#include <unordered_map> #include <unordered_map>
#include <glm/glm.hpp> #include <glm/glm.hpp>
@ -50,7 +51,7 @@ public:
void uniform4v(const std::string& name, int length, const float* v); void uniform4v(const std::string& name, int length, const float* v);
/// @brief Re-preprocess source code and re-compile shader program /// @brief Re-preprocess source code and re-compile shader program
void recompile(); void recompile(const std::vector<std::string>& defines);
/// @brief Create shader program using vertex and fragment shaders source. /// @brief Create shader program using vertex and fragment shaders source.
/// @return linked shader program containing vertex and fragment shaders /// @return linked shader program containing vertex and fragment shaders

View File

@ -1,47 +0,0 @@
#include "ShadowMap.hpp"
#include <GL/glew.h>
ShadowMap::ShadowMap(int resolution) : resolution(resolution) {
glGenTextures(1, &depthMap);
glBindTexture(GL_TEXTURE_2D, depthMap);
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT,
resolution, resolution, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_BORDER);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE);
float border[4] {1.0f, 1.0f, 1.0f, 1.0f};
glTexParameterfv(GL_TEXTURE_2D,GL_TEXTURE_BORDER_COLOR, border);
glGenFramebuffers(1, &fbo);
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depthMap, 0);
glDrawBuffer(GL_NONE);
glReadBuffer(GL_NONE);
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
ShadowMap::~ShadowMap() {
glDeleteFramebuffers(1, &fbo);
glDeleteTextures(1, &depthMap);
}
void ShadowMap::bind() {
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glClear(GL_DEPTH_BUFFER_BIT);
}
void ShadowMap::unbind() {
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
uint ShadowMap::getDepthMap() const {
return depthMap;
}
int ShadowMap::getResolution() const {
return resolution;
}

Some files were not shown because too many files have changed in this diff Show More