Merge branch 'main' into fix-assets-pipeline

This commit is contained in:
MihailRis 2026-08-19 23:54:50 +03:00
commit b9b8c6b7da
259 changed files with 5368 additions and 2080 deletions

3
.gitignore vendored
View file

@ -49,3 +49,6 @@ appimage-build/
# libs # libs
/libs/ /libs/
/vcpkg_installed/ /vcpkg_installed/
# vcstudio
.vcstudio

View file

@ -18,7 +18,7 @@ Installing last version that supports C++17.
```sh ```sh
git clone --branch v3.16.0 https://github.com/skypjack/entt.git git clone --branch v3.16.0 https://github.com/skypjack/entt.git
cd entt cd entt
mkdir build && cd build mkdir -p build && cd build
cmake -DCMAKE_BUILD_TYPE=Release -DENTT_INSTALL=ON .. cmake -DCMAKE_BUILD_TYPE=Release -DENTT_INSTALL=ON ..
sudo make install sudo make install
``` ```

View file

@ -18,19 +18,86 @@ Bytearray.insert(arr, {5, 3, 6})
assert(#arr == 12) assert(#arr == 12)
Bytearray.insert(arr, 2, 8) Bytearray.insert(arr, 2, 8)
assert(#arr == 13) asserts.equals(13, #arr)
for i=1,10 do for i=1,10 do
assert(arr[i] == 10 - i) assert(arr[i] == 10 - i)
end end
print(#arr, arr:get_capacity()) print(#arr, arr:get_capacity())
arr:trim() arr:trim()
assert(#arr == arr:get_capacity()) asserts.equals(#arr, arr:get_capacity())
arr = Bytearray({0, 2, 7, 1, 16, 75, 25}) arr = Bytearray({0, 2, 7, 1, 16, 75, 25})
assert(arr[6] == 75) assert(arr[6] == 75)
arr:insert(2, {5, 6}) arr:insert(2, {5, 6})
assert(arr[#arr] == 25) asserts.equals(25, arr[#arr])
assert(arr[2] == 5) asserts.equals(5, arr[2])
assert(arr[3] == 6) asserts.equals(6, arr[3])
-- =============================
arr = Bytearray({254, 255, 255, 255, 255, 255, 255, 255})
asserts.equals(U64view(arr)[1], ctypes.uint64(2) ^ 64 - 2)
asserts.equals(I64view(arr)[1], -2)
asserts.equals(U32view(arr)[1], 2 ^ 32 - 2)
asserts.equals(I32view(arr)[1], -2)
asserts.equals(U16view(arr)[1], 2 ^ 16 - 2)
asserts.equals(I16view(arr)[1], -2)
asserts.equals(I8view(arr)[1], -2)
asserts.equals(I8view(arr)[2], -1)
asserts.equals(I32view(arr)[2], -1)
arr = Bytearray({0xFF, 0x7F})
local arri16 = I16view(arr)
local arru16 = U16view(arr)
asserts.equals(arri16[1], 32767)
arru16[1] = arru16[1] + 1
asserts.equals(arri16[1], -32768)
asserts.equals(arru16[1], 32768)
arr = Bytearray({0x9A, 0x99, 0x59, 0x40}) -- approx. equal 3.4 in hexadecimal representation of single-precision float number.
local arrflt = FLTview(arr)
local arru32 = U32view(arr)
asserts.equals(arrflt[1], 3.4000000953674316)
asserts.equals(arru32[1], 0x4059999A)
arrflt[1] = arrflt[1] + 0.1
asserts.equals(arrflt[1], 3.5)
asserts.equals(arru32[1], 0x40600000) -- hexadecimal representation of 3.5 single-precision float number.
arr = Bytearray({0x18, 0x2D, 0x44, 0x54, 0xFB, 0x21, 9, 64}) -- hexadecimal representation of 3.141592653589793 double-precision float number.
assert(DBLview(arr)[1] == 3.141592653589793)
-- =============================================
function barrtostr(barr)
local outstr = ""
for i = 1, #barr do
local lastfmt = ""
if i ~= #barr then lastfmt = "|" end
outstr = outstr .. barr[i] .. lastfmt
end
return outstr
end
arr = Bytearray({0, 5, 34, 87, 21, 0, 210})
asserts.equals(barrtostr(arr), "0|5|34|87|21|0|210")
arr:move(2, 3, 4)
asserts.equals(barrtostr(arr), "0|5|5|34|87|21|210")
local arr2 = Bytearray({1, 2, 3, 4, 6, 7, 8})
asserts.equals(barrtostr(arr2), "1|2|3|4|6|7|8")
arr:copy(4, arr2, 2, 3)
asserts.equals(barrtostr(arr2), "1|34|87|21|6|7|8")
arr:fill(nil, nil, 192)
asserts.equals(barrtostr(arr), "192|192|192|192|192|192|192")
arr:fill(nil, 3, 233)
asserts.equals(barrtostr(arr), "233|233|233|233|233|233|233")
arr:fill(2, nil, 254)
asserts.equals(barrtostr(arr), "254|254|254|254|254|254|254")
arr:fill(2, 2, 66)
asserts.equals(barrtostr(arr), "254|66|66|254|254|254|254")

34
dev/tests/io_stream.lua Normal file
View file

@ -0,0 +1,34 @@
local device = file.create_memory_device()
file.write(device..":test.txt", "Hello\nWorld")
file.write_bytes(device..":test.bin", Bytearray({20, 30, 100, 200, 255}))
local stream = file.open(device..":test.txt", 'r')
local line1 = stream:read_line()
local line2 = stream:read_line()
assert(line1 == "Hello")
assert(line2 == "World")
local stream2 = file.open(device..":test.bin", 'rb')
local data = stream2:read(5)
assert(data[1] == 20)
assert(data[2] == 30)
assert(data[3] == 100)
assert(data[4] == 200)
assert(data[5] == 255)
local stream3 = file.open(device..":test.txt", 'r')
assert(stream3:is_alive())
stream3:close()
assert(not stream3:is_alive())
local stream4 = file.open(device..":test.txt", 'r')
stream4:seek('b', 6)
local line = stream4:read_line()
assert(line == "World")
local stream5 = file.open(device..":test.txt", 'r')
stream5:seek('e', 0)
local pos = stream5:tell()
assert(pos == 11)

View file

@ -4,14 +4,15 @@
The appearance of 3D text, is configured via a table, like [particles](particles.md). All fields are optional. The appearance of 3D text, is configured via a table, like [particles](particles.md). All fields are optional.
| Field | Description | Default | | Field | Description | Default |
| --------------- | ---------------------------- | ---------------- | | --------------- | ---------------------------------------- | ---------------- |
| display | Display format | static_billboard | | display | Display format | static_billboard |
| color | Text color | {1, 1, 1, 1} | | color | Text color | {1, 1, 1, 1} |
| scale | Text scale | 1 | | scale | Text scale | 1 |
| render_distance | Text rendering distance | 32 | | render_distance | Text rendering distance | 32 |
| xray_opacity | Visibility through obstacles | 0 | | xray_opacity | Visibility through obstacles | 0 |
| perspective | Perspective coefficient | 1 | | perspective | Perspective coefficient | 1 |
| font | Font. If absent, the default one is used | "" |
Available display formats: Available display formats:

View file

@ -1,6 +1,6 @@
# Block properties # Block properties
## Visual ## Visual & Audio
### *texture* ### *texture*
@ -69,6 +69,19 @@ Rotation profile (set of available block rotations and behaviour of placing bloc
- "pipe" - wood logs, pipes, pillars - "pipe" - wood logs, pipes, pillars
- "pane" - panels, doors, signs - "pane" - panels, doors, signs
- "stairs" - "pane" + flipped variants - "stairs" - "pane" + flipped variants
- "ladder" - like a "pipe", but on horizontal surfaces it is installed like "pane"
### *particles*
Particles are specified as a JSON object. Property names can be found [in the particles section](particles.md).
When camera is near to the block, the engine will create an emitter that will run
until the block is destroyed or the camera moves away a certain distance.
### Material - *material*
Defines the name of the block's material in the format `pack:material_name`, which affects the selection of block interaction sounds.
Material definitions are located in /block_materials.
## Variants ## Variants
@ -179,12 +192,12 @@ An array of 6 numbers describing an offset an size of a block hitbox.
The numbers are specified in the range [0.0, 1.0] - i.e. within the block (in the case of an extended block, the hitbox can be larger than one, but must not go beyond the "size" property). The numbers are specified in the range [0.0, 1.0] - i.e. within the block (in the case of an extended block, the hitbox can be larger than one, but must not go beyond the "size" property).
Array *\[0.25, 0.0, 0.5, 0.75, 0.4, 0.3\]* describes hitbox width: Array *\[0.25, 0.0, 0.5, 0.75, 0.4, 0.3\]* describes hitbox width:
- offset 0.25m east - offset 0.25m west
- offset 0.0m up - offset 0.0m up
- offset 0.5m north - offset 0.5m south
- 0.75m width (from east to west) - 0.75m width (from west to east)
- 0.4m height - 0.4m height
- 0.3m length (from south to north) - 0.3m length (from north to south)
For composite hitboxes, the *hitboxes* property is used - an array of hitboxes, for example: For composite hitboxes, the *hitboxes* property is used - an array of hitboxes, for example:

View file

@ -12,7 +12,7 @@ Content-pack folder must contain file **package.json** with following contents:
{ {
"id": "pack_id", "id": "pack_id",
"title": "pack name will be displayed in the content menu", "title": "pack name will be displayed in the content menu",
"version": "content-pack version - major.minor", "version": "content-pack version - major.minor.[patch])",
"creator": "content-pack creator", "creator": "content-pack creator",
"description": "short description", "description": "short description",
"dependencies": [ "dependencies": [
@ -26,6 +26,7 @@ Dependency levels are indicated by prefixes in the name:
- '!' - required dependency - '!' - required dependency
- '?' - optional dependency - '?' - optional dependency
- '~' - weak dependency - '~' - weak dependency
If prefix is not specified, '!' level will be used. If prefix is not specified, '!' level will be used.
Example: '~randutil' - weak dependency 'randutil'. Example: '~randutil' - weak dependency 'randutil'.

View file

@ -61,12 +61,16 @@ Determines how the physics engine will work with it.
### *blocking* ### *blocking*
Determines whether the entity blocks installation of blocks. If true - the entity blocks the placement of a block that intersects with the entity's hitbox.
*In the future will also block other entities movement.* *In the future will also block other entities movement.*
Default value: *true*. Default value: *true*.
### *selectable*
If set to `false` the cursor will ignore the entity, passing the ray.
### *sensors* ### *sensors*
A sensor is an area attached to a physical body that detects the entry of other bodies into it. A sensor is an area attached to a physical body that detects the entry of other bodies into it.

View file

@ -60,13 +60,18 @@ Property used via [inventory.use](scripting/builtins/libinventory.md).
Property status is displayed in the inventory interface. Display method is defined via `uses-display`. Property status is displayed in the inventory interface. Display method is defined via `uses-display`.
### Display of uses - `uses-display` ### Display of remaining uses count - `uses-display`
- `none` - display disabled - `none` - display disabled
- `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)
### *script-name*
Defines the name of the script containing the item's event handlers. By default, equals to the item's string id (name).
Allows you to reuse a single script for multiple items.
## Tags ## Tags
Tags allow you to designate general properties of items. Names should be formatted as `prefix:tag_name`. Tags allow you to designate general properties of items. Names should be formatted as `prefix:tag_name`.

View file

@ -38,7 +38,7 @@ Models are loaded automatically; adding them to preload.json is not required.
## Models ## Models
Models should be located in the models folder. Currently only OBJ format is supported. Models must be located in the models folder. Currently, the following formats are supported: obj, vec3, and [vcm](vcm.md).
>[!IMPORTANT] >[!IMPORTANT]
> When loading an obj model, the \*.mtl file is ignored. > When loading an obj model, the \*.mtl file is ignored.

View file

@ -16,6 +16,7 @@ Subsections:
- [block](scripting/builtins/libblock.md) - [block](scripting/builtins/libblock.md)
- [byteutil](scripting/builtins/libbyteutil.md) - [byteutil](scripting/builtins/libbyteutil.md)
- [cameras](scripting/builtins/libcameras.md) - [cameras](scripting/builtins/libcameras.md)
- [ctypes](scripting/builtins/libctypes.md)
- [entities](scripting/builtins/libentities.md) - [entities](scripting/builtins/libentities.md)
- [file](scripting/builtins/libfile.md) - [file](scripting/builtins/libfile.md)
- [gfx.blockwraps](scripting/builtins/libgfx-blockwraps.md) - [gfx.blockwraps](scripting/builtins/libgfx-blockwraps.md)
@ -42,6 +43,7 @@ Subsections:
- [utf8](scripting/builtins/libutf8.md) - [utf8](scripting/builtins/libutf8.md)
- [vec2, vec3, vec4](scripting/builtins/libvecn.md) - [vec2, vec3, vec4](scripting/builtins/libvecn.md)
- [world](scripting/builtins/libworld.md) - [world](scripting/builtins/libworld.md)
- [Extensions for standard libraries](scripting/extensions.md)
- [Module core:bit_converter](scripting/modules/core_bit_converter.md) - [Module core:bit_converter](scripting/modules/core_bit_converter.md)
- [Module core:data_buffer](scripting/modules/core_data_buffer.md) - [Module core:data_buffer](scripting/modules/core_data_buffer.md)
- [Module core:vector2, core:vector3](scripting/modules/core_vector2_vector3.md) - [Module core:vector2, core:vector3](scripting/modules/core_vector2_vector3.md)
@ -58,9 +60,58 @@ not part of Lua syntax.
- quat - array of four numbers - quaternion - quat - array of four numbers - quaternion
- matrix - array of 16 numbers - matrix - matrix - array of 16 numbers - matrix
## Core functions ## Namespaces
Currently, the engine uses a namespace hierarchy that minimizes conflicts between individual modules, scripts, and even packs.
The following structure is relevant:
- Global namespace (_G), which is the root namespace. Writing to it outside of engine core modules is highly undesirable, as it can waste hours of debugging time.
- Pack namespace - created each time content is loaded for each pack included in the configuration. This namespace is used by modules, as well as item and block scripts.
- [Component namespace](scripting/ecs.md#built-in-components).
- [UI document namespace](scripting/ui.md).
- Isolated world generator namespace.
## Modules
A module is a global object with a lifetime limited to the lifetime of the loaded content, used both for interaction between different packages and as a substitute for global variables within the package itself.
The module must be located in `contentpack/modules/**` (subfolders are allowed, but you will need to specify them)
```lua ```lua
require "packid:module_name" -- load Lua module from pack-folder/modules/ local module_name = require "contentpack:module_name" -- imports the module
-- no extension included, just name
-- If the module is in the same pack in which it is imported, you can use the shortcut:
local module_name = require "module_name"
-- When using nested folders:
local module_name = require "contentpack:path/to/module_name" -- the path does not include `modules`
local module_name = require "path/to/module_name" -- if in the same pack
``` ```
The module will have the namespace of the folder in which it is located, regardless of the namespace from which the first import was performed.
When creating a module, you should follow the following approach, which is what *require* expects:
```lua
local this = {
variable_name = ... -- public variables
}
-- private module variables
local variable_name = ...
-- private module functions
local function function_name(...)
...
end
-- public module functions
function this.function_name(...)
...
end
return this -- the result that require will return and cache
```
When reimported, the module will not be reloaded; instead, require will return the cached result.

View file

@ -180,14 +180,34 @@ app.get_content_sources() -> table<string>
Returns a list of content sources (paths), in descending priority order. Returns a list of content sources (paths), in descending priority order.
``lua ```lua
app.set_content_sources(sources: table<string>) app.set_content_sources(sources: table<string>)
``` ```
Sets a list of content sources (paths). Specified in descending priority order. Sets a list of content sources (paths). Specified in descending priority order.
``lua ```lua
app.reset_content_sources() app.reset_content_sources()
``` ```
Resets content sources. Resets content sources.
## Sub-instances
```lua
-- Creates a headless engine instance with the current project and the specified application script.
-- Returns the instance ID. The number of active sub-instances is currently limited to one.
app.start_background_instance(
-- script file
app_script: string,
-- log file
output_file: string | nil
) -> int
-- Checks if the engine sub-instance is alive.
app.is_instance_alive(handle: int) -> boolean
-- Stops the engine sub-instance.
-- Returns true if the instance was alive at the time of the call.
app.terminate(handle: int) -> boolean
```

View file

@ -28,7 +28,8 @@ block.get(x: int, y: int, z: int) -> int
block.get_states(x: int, y: int, z: int) -> int block.get_states(x: int, y: int, z: int) -> int
-- Set block with given integer ID and state (default - 0) at given position. -- Set block with given integer ID and state (default - 0) at given position.
block.set(x: int, y: int, z: int, id: int, states: int) -- If noupdate=true is passed, the `on_update` event will not be called for adjacent blocks.
block.set(x: int, y: int, z: int, id: int, states: int, noupdate: boolean=false)
-- Places a block with a given integer id and state (default - 0) at given position. -- Places a block with a given integer id and state (default - 0) at given position.
-- on behalf of the player, calling the on_placed event. -- on behalf of the player, calling the on_placed event.

View file

@ -0,0 +1,28 @@
# *ctypes* Library
A library for safe work with C types.
| ctypes.\<name\> | C-Type | Minimum | Maximum |
| --------------- | -------- | -------------------- | -------------------- |
| uint8 | uint8_t | 0 | 255 |
| uint16 | uint16_t | 0 | 65535 |
| uint32 | uint32_t | 0 | 4294967295 |
| uint64 | uint64_t | 0 | 18446744073709551615 |
| int8 | int8_t | -128 | 127 |
| int16 | int16_t | -32768 | 32767 |
| int32 | int32_t | -2147483648 | 2147483647 |
| int64 | int64_t | -9223372036854775808 | 9223372036854775807 |
Usage examples:
```lua
local x = ctypes.uint64(1234)
print(x) --> 1234ULL (cdata)
local y = ctypes.uint64("18446744073709551615")
print(y) --> 18446744073709551615ULL (cdata)
print(x + y) --> 1233ULL
local z = tonumber(x)
print(z) --> 1234 (number)
```

View file

@ -30,6 +30,9 @@ entities.def_hitbox(id: int) -> vec3
-- Returns entity definition name by index (string ID). -- Returns entity definition name by index (string ID).
entities.def_name(id: int) -> str entities.def_name(id: int) -> str
-- Returns true if the entity is a solid obstacle.
entities.def_solid(id: int) -> boolean
-- Returns entity definition index by name (integer ID). -- Returns entity definition index by name (integer ID).
entities.def_index(name: str) -> int entities.def_index(name: str) -> int
@ -51,12 +54,12 @@ entities.get_all() -> table
-- Returns a table of loaded entities based on the passed list of UIDs -- Returns a table of loaded entities based on the passed list of UIDs
entities.get_all(uids: array<int>) -> table entities.get_all(uids: array<int>) -> table
-- Returns a list of UIDs of entities inside the rectangular area -- Returns a list of UIDs of entities with origin inside the rectangular area
-- pos - minimal area corner -- pos - minimal area corner
-- size - area size -- size - area size
entities.get_all_in_box(pos: vec3, size: vec3) -> array<int> entities.get_all_in_box(pos: vec3, size: vec3) -> array<int>
-- Returns a list of UIDs of entities inside the radius -- Returns a list of UIDs of entities with origin inside the radius
-- center - center of the area -- center - center of the area
-- radius - radius of the area -- radius - radius of the area
entities.get_all_in_radius(center: vec3, radius: number) -> array<int> entities.get_all_in_radius(center: vec3, radius: number) -> array<int>

View file

@ -139,7 +139,7 @@ gui.confirm(
## Documents and templates ## Documents and templates
```lua ```lua
-- Loads a UI document with its script, returns the name of the document if successfully loaded. -- Loads a UI document with its script. Returns document environment table
gui.load_document( gui.load_document(
-- Path to the xml file of the page. Example: `core:layouts/pages/main.xml` -- Path to the xml file of the page. Example: `core:layouts/pages/main.xml`
path: str, path: str,
@ -147,7 +147,7 @@ gui.load_document(
name: str name: str
-- Table of parameters passed to the on_open event -- Table of parameters passed to the on_open event
args: table args: table
) -> str ) -> table
-- Loads and processes layout template from file -- Loads and processes layout template from file
gui.template( gui.template(

View file

@ -57,6 +57,10 @@ hud.get_block_inventory() -> int
-- Gives the ID of the second open inventory (block, virtual inventory...) or 0. -- Gives the ID of the second open inventory (block, virtual inventory...) or 0.
hud.get_second_inventory() -> int hud.get_second_inventory() -> int
-- Get the inventory ID of exchange item (grabbed item in inventory) or 0.
-- It has only one slot - 0
hud.get_exchange_inventory() -> int
-- Gives the ID of the player that the UI is bound to. -- Gives the ID of the player that the UI is bound to.
hud.get_player() -> int hud.get_player() -> int

View file

@ -2,14 +2,36 @@
Quaternions manipulation library. Quaternions manipulation library.
## Quaternion from matrix - *mat4.from_quat(...)* Quaternions can be created as a table of 4 numbers:
`{w, x, y, z}`
## Quaternion from matrix - *quat.from_mat4(...)*
```lua ```lua
-- creates a quaternion based on the rotation matrix -- creates a quaternion based on the rotation matrix
quat.from_mat4(m: matrix) quat.from_mat4(m: matrix, [optional] dst: quat) -> quat
```
-- writes a quaternion from the rotation matrix to dst ## Quaternion of Euler angles - *quat.from_euler(...)*
quat.from_mat4(m: matrix, dst: quat)
```lua
-- creates quaternion from euler angles passed as XYZ (pitch, yaw, roll)
-- angles in degrees
quat.from_euler(euler: vec3, [optional] dst: quat) -> quat
```
## Quaternion composition - quat.mul(...)
```lua
-- multiplies quaternions
quat.mul(a: quat, b: quat, [optional] dst: quat) -> quat
```
## 3D vector rotation - quat.mul_vec3(...)
```lua
-- rotates vector by quaternion
quat.mul_vec3(a: quat, b: vec3, [optional] dst: vec3) -> vec3
``` ```
## Spherical linear interpolation - *quat.slerp(...)* ## Spherical linear interpolation - *quat.slerp(...)*
@ -19,16 +41,12 @@ The interpolation always take the short path and the rotation is performed at co
```lua ```lua
-- creates a quaternion as an interpolation between a and b, -- creates a quaternion as an interpolation between a and b,
-- where t is interpolation factor -- where t is interpolation factor
quat.slerp(a: quat, b: quat, t: number) quat.slerp(a: quat, b: quat, t: number, [optional] dst: quat) -> quat
-- writes a quaternion as an interpolation between a and b to dst,
-- where t is interpolation factor
quat.slerp(a: quat, b: quat, t: number, dst: quat)
``` ```
## Casting to string - *quat.tostring(...)* ## Casting to string - *quat.tostring(...)*
```lua ```lua
-- returns a string representing the contents of the quaternion -- returns a string representing the contents of the quaternion
quat.tostring(q: quat) quat.tostring(q: quat) -> string
``` ```

View file

@ -92,6 +92,8 @@ world.raycast(
-- exclusion filtering mode: -- exclusion filtering mode:
-- if true, filter_entities defines the list of ignored entity types -- if true, filter_entities defines the list of ignored entity types
entities_exclusion: bool = false, entities_exclusion: bool = false,
-- include entities with selectable=false
nonselect_entities: bool = false;
[==[ additional parameters (blocks) ]==] [==[ additional parameters (blocks) ]==]
-- id of the block types used for filtering -- id of the block types used for filtering

View file

@ -38,6 +38,17 @@ entity:set_enabled(name: str, enable: bool)
entity:get_player() -> int or nil entity:get_player() -> int or nil
``` ```
## Custom Components
A component is defined as a script in `{pack}/scripts/components/{name}.lua`.
The component will be available for use in the entity definition as `{pack}:{name}`.
Example: `core:scripts/components/pathfinding.lua` -> `core:pathfinding`.
For **each** entity containing a component, a **separate instance** is created with **its own namespace** within the pack namespace.
Global variables declared in it act as public fields of the component (same for global functions).
Entity components are specified via the ["components" list](../entity-properties.md#components)
## Built-in components ## Built-in components
### Transform ### Transform

View file

@ -24,7 +24,7 @@ Called on block broken by player
function on_replaced(x, y, z, playerid) function on_replaced(x, y, z, playerid)
``` ```
Called on block replaced with other by player Called before a player replaces a block
```lua ```lua
function on_interact(x, y, z, playerid) -> bool function on_interact(x, y, z, playerid) -> bool

View file

@ -0,0 +1,204 @@
# Standard Library Extensions
The **stdmin.lua** kernel script defines functions that extend and complement some of the standard **Lua** libraries.
## Contents:
- [table extensions](#table-extensions)
- [string extensions](#string-extensions)
- [math extensions](#math-extensions)
- [bit extensions](#bit-extensions)
- [additional global functions](#additional-global-functions)
## Table Extensions
```lua
-- Creates and returns a copy of the given table by creating a new one and copying all elements from the given table into it.
table.copy(t: table) -> table
-- The deep copy function creates a full copy of the source table, including all its subtables.
table.deep_copy(t: table) -> table
-- Returns the number of pairs in the given table.
table.count_pairs(t: table) -> int
-- Returns one element from the given table at a random position.
table.random(t: table) -> any
-- Returns true if x is contained in t.
table.has(t: table, x: any) -> boolean
-- Returns the index of x in t. If the given object is not contained in the table, the function returns -1.
table.index(t: table, x: any) -> int
-- Removes element x from t.
table.remove_value(t: table, x: any)
-- Shuffles values ​​in the table.
table.shuffle(t: table) -> table
-- Adds values ​​from table t2 to table t1. If table t2 contains a key from t1, the key's value will not be changed.
table.merge(t1: table, t2: table) -> table
-- Iterates through the table and applies a func to all its elements, returning the new value of the element.
table.map(t: table, func: function(indx, value)) -> table
-- Iterates through the table using a func that returns true if the element should be kept and false if it should be deleted.
table.filter(t: table, func: function(indx, value)) -> table
-- Allows you to safely retrieve the value for the specified key. If the key exists in the table, the method will return its value.
-- If the key is missing, the method will set it to the default value and return it.
table.set_default(t: table, key: int | string, default: any) -> any
-- Returns a "flattened" version of the original table.
table.flat(t: table) -> table
-- Returns a deep "flat" version of the original table.
table.deep_flat(t: table) -> table
-- Returns a truncated version of the table from index start to index stop, inclusive. Key-value pairs
-- are not preserved in the new table. For nil values, the value starts at 1 and ends at #arr, respectively.
table.sub(arr: table, start: number | nil, stop: number | nil) -> table
-- Adds a value to the table only if it was not originally there.
table.insert_unique(t: table, val: any)
table.insert_unique(t: table, pos: int, val: any)
-- Returns a table containing all keys of the given table, including numeric ones. table.keys(t: table) -> table
-- Adds all key-value pairs from table extension to table t. If a key from t is present in extension, its value will be overwritten.
table.extend(t: table, extension: table) -> table
-- Converts the passed table to a string.
table.tostring(t: table) -> string
```
## String extensions
It's important to note that all of the functions listed below that extend **string** can be used as meta-methods on string instances, i.e.:
```lua
local str = "ABA str BAB"
if str:starts_with("ABA") and str:ends_with("BAB") then
print(str:replace("BA", "DC"))
end
```
```lua
-- Splits the string into parts based on the specified separator/expression and returns the result as a table of strings. If withpattern is true, the separator parameter will be evaluated as a regular expression. string.explode(separator: string, str: string, withpattern: boolean) -> table<string>
-- Splits the string into parts based on the specified delimiter and returns the result as a table of strings.
string.split(str: string, delimiter: string) -> table<string>
-- Escapes special characters in the string, such as `()[]+-.$%^?*`, into `%character` format. The `NUL` character (`\0`) will be converted to `%z`.
string.pattern_safe(str: string) -> string
-- Splits seconds into hours, minutes, and milliseconds and formats them using the following parameter order: `minutes, seconds, milliseconds`, and then returns the result. If format is not specified, it returns a table where:
-- h - hours,
-- m - minutes,
-- s - seconds,
-- ms - milliseconds.
string.formatted_time(seconds: number, format: string) -> string | table
-- Replaces all substrings in str equal to tofind with toreplace and returns a string with all the modified substrings.
string.replace(str: string, tofind: string, toreplace: string) -> string
-- Removes all characters equal to char from the string from the left and right ends and returns the result.
-- If the char parameter is undefined, all empty characters will be selected.
string.trim(str: string, char: string) -> string
-- Removes all characters equal to char from the string from the left end and returns the result.
-- If the char parameter is undefined, all empty characters will be selected.
string.trim_left(str: string, char: string) -> string
-- Removes all characters equal to char from the right end of the string and returns the result.
-- If the char parameter is undefined, all empty characters will be selected.
string.trim_right(str: string, char: string) -> string
-- Returns true if the string begins with the substring start.
string.starts_with(str: string, start: string) -> boolean
-- Returns true if the string ends with the substring endStr.
string.ends_with(str: string, endStr: string) -> boolean
-- The string.lower and string.upper functions are also overridden by utf8.lower and utf8.upper.
-- Escapes a string. It is an alias for utf8.escape.
string.escape(str: string) -> string
-- Escapes special XML characters. An alias for utf8.escape_xml.
string.escape_xml(text: string) -> string
-- Adds a char to the left and right of the string until its size equals size.
-- By default, char is equal to the space character.
string.pad(str: string, size: int, char: string) -> string
-- Adds a char to the left of the string until its size equals size.
-- By default, char is equal to the space character.
string.left_pad(str: string, size: int, char: string) -> string
-- Adds a char to the right of the string until its size equals size.
-- By default, char is equal to the space character.
string.right_pad(str: string, size: int, char: string) -> string
```
## Math extensions
```lua
-- Returns _in if it is in the range low <= _in <= high
-- Otherwise, returns the boundary to which _in is closest.
math.clamp(_in: number, low: number, high: number) -> number
-- Returns a random fractional number in the range low to high.
math.rand(low: number, high: number) -> number
-- Returns the normalized value of num relative to conf.
math.normalize(num: number, [optional] conf: number) -> number
-- Returns the rounded value of num to the specified number of decimal places.
math.round(num: number, [optional] places: number) -> number
-- Returns the sum of all received arguments. If a table was passed as an argument, the method will return the sum of all its elements.
math.sum(x: number, ... | t: table) -> number
```
## Bit extensions
```lua
-- Common arguments:
-- * expr: A string containing a bitwise expression, conforming to the Lua 5.3 bitwise operations format
-- * args: A list of names of the expression arguments. If nil, the list is automatically generated based on the detected identifiers.
- Compiles the function to perform bitwise operations
-- * asFunction: If true, returns the function; otherwise, returns a string of function code
bit.compile(expr: string, args: table | nil, asFunction: boolean=true) -> function | string
-- Compiles the function to perform bitwise operations and executes it in place
-- * ...: Values ​​to be passed to the compiled function. bit.execute(expr: string, args: table | nil, ...) -> number
```
## Additional Global Functions
This script also defines other global functions that are available for use. Their list is below.
```lua
-- Returns true if the passed table is an array, that is, if each key is an integer greater than or equal to one
-- and if each key follows the previous one.
is_array(x: table) -> boolean
-- Splits the path into two parts and returns them: the entry point and the file path.
parse_path(path: string) -> string, string
-- Calls the function func iters times, passing it the arguments ..., and then prints to the console the time in microseconds that has elapsed
-- since the call to timeit. timeit(iters: int, func: function, ...)
-- Causes the coroutine to pause until the number of seconds specified in timesec has elapsed.
-- The function can only be used inside a coroutine.
sleep(timesec: number)
-- Waits for the passed coroutine to complete, returning the control flow. The function can only be used inside a coroutine.
-- Returns values ​​similar to those returned by pcall.
await(co: coroutine) -> result, error
-- A constant storing the PID of the current engine instance.
os.pid -> number
```

View file

@ -52,6 +52,60 @@ yaml.parse(code: str) -> table
Parses a YAML string into a table. Parses a YAML string into a table.
## XML Library
The library contains functions for serializing and deserializing XML:
Further, XML as a data type denotes a Lua table with the following structure:
- Element tag is accessible by the key `#`
- XML ​​attributes are accessible as key = value pairs
- Inner elements are accessible by index
The table is used both as a dictionary and an array.
Example:
```xml
<panel size='400' color='0' interval='1' context='menu'>
<button onclick='menu.page="worlds"'>@Worlds</button>
</panel>
```
Equivalent Lua table:
```lua
{
['#'] = "panel",
size = "400",
color = "0",
interval = "1",
context = "menu",
{
['#'] = "button",
onclick = 'menu.page="worlds"',
{
"@Worlds"
}
}
}
```
Consequently, the button element is accessible as `panel_element[1]`.
```lua
-- Serializes a tree in XML format
-- multiline - use multiline formatting
xml.tostring(data: XML, multiline: bool = true) -> str
-- Parses XML
xml.parse(code: str) -> XML
-- Parses the VCD (VoxelCore Declaration) format used by the VCM format.
-- root_tag - a root element with the specified tag will be implicitly created,
-- containing all root elements
xml.parse_vcd(code: str, root_tag: str = "root") -> XML
```
## *bjson* library ## *bjson* library
The library contains functions for working with the binary data exchange format [vcbjson](../../specs/binary_json_spec.md). The library contains functions for working with the binary data exchange format [vcbjson](../../specs/binary_json_spec.md).

View file

@ -1,7 +1,7 @@
# UI properties and methods # UI properties and methods
UI elements in scripts are accessed through a Document instance UI elements in scripts are accessed through a Document instance
(*document* variable) by id specified in xml. (global *document* variable) by id specified in xml.
Example: print the pos property of an element with id: "worlds-panel" to the console: Example: print the pos property of an element with id: "worlds-panel" to the console:
```lua ```lua
@ -53,7 +53,7 @@ Properties that apply to all elements:
| contentOffset | vec2 | yes | *no* | element content offset | | contentOffset | vec2 | yes | *no* | element content offset |
| cursor | string | yes | yes | cursor displayed on hover | | cursor | string | yes | yes | cursor displayed on hover |
| parent | Element | yes | *no* | parent element or nil | | parent | Element | yes | *no* | parent element or nil |
| zIndex | bool | yes | *no* | z-index value. In panel, it determines the order | | zIndex | int | yes | *no* | z-index value. In panel, it determines the order |
Common element methods: Common element methods:

View file

@ -212,10 +212,27 @@ local map = Heightmap(width, height)
Operations apply to all height values. Operations apply to all height values.
```lua ```lua
-- Convert to absolute values
map:abs() map:abs()
```
Casts height values ​​to absolute. -- Round down
map:floor()
-- Round up
map:ceil()
-- Round to the nearest integer
map:round()
-- Sine of height values ​​(in radians)
map:sin()
-- Cosine of height values ​​(in radians)
map:cos()
-- Tangent of height values ​​(in radians)
map:tan()
```
### Binary Operations ### Binary Operations

View file

@ -39,6 +39,8 @@ Examples:
- `position-func` - position supplier for an element (two numbers), called on every parent container size update or on element adding on a container. May be called before *on_hud_open* - `position-func` - position supplier for an element (two numbers), called on every parent container size update or on element adding on a container. May be called before *on_hud_open*
- `size-func` - element size provider (two numbers), called when the size of the container in which the element is located changes, or when an element is added to the container. Can be called before on_hud_open is called. - `size-func` - element size provider (two numbers), called when the size of the container in which the element is located changes, or when an element is added to the container. Can be called before on_hud_open is called.
- `onclick` - lua function called when an element is clicked. - `onclick` - lua function called when an element is clicked.
- `onrightclick` - lua function called when the right mouse button is pressed on an element.
- `onmiddleclick` - lua function called when the middle mouse button is pressed on an element.
- `ondoubleclick` - lua function called when you double click on an element. - `ondoubleclick` - lua function called when you double click on an element.
- `onfocus` - lua function called when focusing on an element. - `onfocus` - lua function called when focusing on an element.
- `ondefocus` - lua function called when the element loses focus. - `ondefocus` - lua function called when the element loses focus.
@ -192,6 +194,9 @@ Example of list description:
- `width` - minimum content width. Default: 100. - `width` - minimum content width. Default: 100.
- `selected` - initially selected value. Default: "". - `selected` - initially selected value. Default: "".
- `onselect` - function to which the user-selected value is passed - `onselect` - function to which the user-selected value is passed
- `mode` - behaviour / display mode:
- `select` - default mode.
- `button` - button mode: the text is not replaced with the selected option; the element rendered as a regular button.
## *bindbox* ## *bindbox*

View file

@ -12,6 +12,7 @@
| render_distance | Дистанция отрисовки текста | 32 | | render_distance | Дистанция отрисовки текста | 32 |
| xray_opacity | Коэффициент видимости через препятствия (просвечивание) | 0 | | xray_opacity | Коэффициент видимости через препятствия (просвечивание) | 0 |
| perspective | Коэффициент перспективы | 1 | | perspective | Коэффициент перспективы | 1 |
| font | Шрифт. При отсутствии используется основной | "" |
Доступные форматы отображения: Доступные форматы отображения:

View file

@ -1,6 +1,6 @@
# Свойства блоков # Свойства блоков
## Вид ## Вид & Звук
### Текстура - *texture* ### Текстура - *texture*
@ -70,6 +70,7 @@
- "pipe" - профиль "труба". Примеры блоков: бревно, труба, лампочка - "pipe" - профиль "труба". Примеры блоков: бревно, труба, лампочка
- "pane" - профиль "панель". Примеры блоков: панель, дверь, табличка - "pane" - профиль "панель". Примеры блоков: панель, дверь, табличка
- "stairs" - профиль "ступеньки" ("pane" + перевернутые варианты) - "stairs" - профиль "ступеньки" ("pane" + перевернутые варианты)
- "ladder" - профиль "лестница" (как "pipe", но на горизонтальных поверхностях как "pane")
### Испускаемые частицы - *particles* ### Испускаемые частицы - *particles*
@ -78,6 +79,11 @@
При приближении к блоку движок создаст эмиттер, который будет работать При приближении к блоку движок создаст эмиттер, который будет работать
до разрушения блока или отдаления камеры на некоторое расстояние. до разрушения блока или отдаления камеры на некоторое расстояние.
### Материал - *material*
Определяет имя материала блока в формате `пак:имя_материала`, что влияет на выбор звуков взаимодействия с блоком.
Определения материалов расположены в /block_materials.
## Варианты ## Варианты
Некоторые свойства могут варьироваться динамически, в зависимости от номера варианта, хранимого в пользовательских битах блока. Некоторые свойства могут варьироваться динамически, в зависимости от номера варианта, хранимого в пользовательских битах блока.
@ -188,12 +194,12 @@
Числа указываются в диапазоне [0.0, 1.0] - т.е в пределах блока (в случае расширенного блока хитбокс может быть больше единицы, но не должен выходить за пределы size). Числа указываются в диапазоне [0.0, 1.0] - т.е в пределах блока (в случае расширенного блока хитбокс может быть больше единицы, но не должен выходить за пределы size).
Массив `[0.25, 0.0, 0.5, 0.75, 0.4, 0.3]` описывает хитбокс: Массив `[0.25, 0.0, 0.5, 0.75, 0.4, 0.3]` описывает хитбокс:
- смещен на 0.25 м на запад - смещен на 0.25 м на восток
- смещен на 0.0 м вверх - смещен на 0.0 м вверх
- смещен на 0.5 м на север - смещен на 0.5 м на юг
- шириной (с востока на запад) 0.75 м - шириной (с запада на восток) 0.75 м
- высотой 0.4 м - высотой 0.4 м
- длиной (с юга на север) 0.3 м - длиной (с севера на юг) 0.3 м
Для составных хитбоксов используется свойство *hitboxes* - массив хитбоксов, например: Для составных хитбоксов используется свойство *hitboxes* - массив хитбоксов, например:

View file

@ -12,7 +12,7 @@
{ {
"id": "выбранное_имя_пака", "id": "выбранное_имя_пака",
"title": "имя контент-пака для отображения в меню контента", "title": "имя контент-пака для отображения в меню контента",
"version": "версия контент-пака в формате major.minor", "version": "версия контент-пака в формате major.minor.[patch]",
"creator": "создатель контент-пака", "creator": "создатель контент-пака",
"description": "краткое описание", "description": "краткое описание",
"dependencies": [ "dependencies": [
@ -28,6 +28,7 @@
- '!' - обязательная зависимость - '!' - обязательная зависимость
- '?' - опциональная зависимость - '?' - опциональная зависимость
- '~' - слабая зависимость - '~' - слабая зависимость
Отсутствие префикса интерпретируется как '!'. Отсутствие префикса интерпретируется как '!'.
Пример: '~randutil' - слабая зависимость 'randutil'. Пример: '~randutil' - слабая зависимость 'randutil'.

View file

@ -61,12 +61,16 @@
### Блокирование - *blocking* ### Блокирование - *blocking*
Определяет блокирует ли сущность установку блоков. Определяет, блокирует ли сущность установку блока, пересекающегося с хитбоксом сущности.
*В будущем будет также блокировать движение других сущностей.* *В будущем будет также блокировать движение других сущностей.*
Значение по-умолчанию: *true*. Значение по-умолчанию: *true*.
### Выделяемость - *selectable*
При значении в `false` курсор будет игнорировать cущность, пропуская луч далее.
### Список сенсоров - *sensors* ### Список сенсоров - *sensors*
Сенсор - область пространства, привязанная к физическому телу, детектирующее попадание в него других тел. Сенсор - область пространства, привязанная к физическому телу, детектирующее попадание в него других тел.

View file

@ -66,6 +66,10 @@
- `relation` - отношение текущего значения к изначальному (x/y) - `relation` - отношение текущего значения к изначальному (x/y)
- `vbar` - вертикальная шкала (используется по-умолчанию) - `vbar` - вертикальная шкала (используется по-умолчанию)
### Имя скрипта - `script-name`
Определяет имя скрипта, содержащего обработчики событий предмета. По-умолчанию соответствует строковому id (имени) предмета.
Свойство обеспечивает возможность использования одного скрипта для нескольких предметов.
## Теги - *tags* ## Теги - *tags*

View file

@ -38,7 +38,7 @@
## Модели ## Модели
Модели должны располагаться в папке models. На данный момент поддерживается только OBJ формат. Модели должны располагаться в папке models. На данный момент поддерживаются форматы: obj, vec3 и [vcm](vcm.md).
>[!IMPORTANT] >[!IMPORTANT]
> При загрузке obj модели игнорируется файл \*.mtl. > При загрузке obj модели игнорируется файл \*.mtl.

View file

@ -16,6 +16,7 @@
- [block](scripting/builtins/libblock.md) - [block](scripting/builtins/libblock.md)
- [byteutil](scripting/builtins/libbyteutil.md) - [byteutil](scripting/builtins/libbyteutil.md)
- [cameras](scripting/builtins/libcameras.md) - [cameras](scripting/builtins/libcameras.md)
- [ctypes](scripting/builtins/libctypes.md)
- [entities](scripting/builtins/libentities.md) - [entities](scripting/builtins/libentities.md)
- [file](scripting/builtins/libfile.md) - [file](scripting/builtins/libfile.md)
- [gfx.blockwraps](scripting/builtins/libgfx-blockwraps.md) - [gfx.blockwraps](scripting/builtins/libgfx-blockwraps.md)
@ -60,6 +61,58 @@
- quat - массив из четырех чисел - кватернион - quat - массив из четырех чисел - кватернион
- matrix - массив из 16 чисел - матрица - matrix - массив из 16 чисел - матрица
## Пространства имён
В настоящее время, движок использует иерархию пространств имён, минимизирующую конфликты между отдельными модулями, скриптами, да и паками.
Актуальна следующая структура:
- Глобальное пространство (оно же - _G), являющееся корневым, запись в которое, вне модулей ядра движка, является крайне нежелательным для вашего же времени, что может уйти на лишние часы отладки.
- Пространство пака - создаётся каждый раз при загрузке контента для каждого, включённого в конфигурацию, пака. Это пространство используют модули, а также скрипты предметов и блоков.
- Пространство [компонента](scripting/ecs.md#пользовательские-компоненты).
- Пространство [UI-документа](scripting/ui.md)
- Изолированное пространство имён генератора мира.
## Модули
Модуль - глобальный объект со сроком жизни, ограниченным сроком жизни контента, используемый как для взаимодействия разных паков, так и для вместо глобальных переменных в пределах самого пака.
Модуль должен находиться в `контентпак/modules/**` (допускаются вложенные папки, что нужно будет указывать)
```lua ```lua
require "контентпак:имя_модуля" -- загружает lua модуль из папки modules (расширение не указывается) local имя_модуля = require "контентпак:имя_модуля" -- импортирует модуль
-- если модуль находится в том же паке, в котором импортируется, можно использовать сокращённый вариант:
local имя_модуля = require "имя_модуля"
-- при использовании вложенных папок:
local имя_модуля = require "контентпак:путь/к/имя_модуля" -- путь не включает `modules`
local имя_модуля = require "путь/к/имя_модуля" -- если в том же паке
``` ```
Модуль будет иметь пространство имён того пака, в котором находится, независимо от пространства имён, из которого был выполнен первый импорт.
При создании модуля следует придерживаться следующего подхода, которого и ожидает *require*:
```lua
local this = {
имя_переменной = ... -- публичные переменные
}
-- приватные переменные модуля
local имя_переменной = ...
-- приватные функции модуля
local function имя_функции(...)
...
end
-- публичные функции модуля
function this.имя_функции(...)
...
end
return this -- результат, который и будет возвращать и кешировать require
```
При повторном импорте модуль не будет перезагружен - вместо этого require вернёт кешированный результат.

View file

@ -143,3 +143,23 @@ app.set_content_sources(sources: table<string>)
-- Сбрасывает список источников контента. -- Сбрасывает список источников контента.
app.reset_content_sources() app.reset_content_sources()
``` ```
## Под-экземпляры
```lua
-- Создаёт headless-экземпляр движка с текущим проектом и указанным сценарием.
-- Возвращает id экземпляра. Число живых под-экземпляров, на данный момент, ограничено одним.
app.start_background_instance(
-- файл сценария
app_script: string,
-- файл лога
output_file: string | nil
) -> int
-- Проверяет, жив ли под-экземпляр движка.
app.is_instance_alive(handle: int) -> boolean
-- Останавливает под-экземпляр движка.
-- Возвращает true если экземпляр был жив в момент вызова.
app.terminate_instance(handle: int) -> boolean
```

View file

@ -0,0 +1,28 @@
# Библиотека *ctypes*
Библиотека для безопасной работы с типами языка С.
| ctypes.\<name\> | С-тип | Минимум | Максимум |
| ------------- | -------- | -------------------- | -------------------- |
| uint8 | uint8_t | 0 | 255 |
| uint16 | uint16_t | 0 | 65535 |
| uint32 | uint32_t | 0 | 4294967295 |
| uint64 | uint64_t | 0 | 18446744073709551615 |
| int8 | int8_t | -128 | 127 |
| int16 | int16_t | -32768 | 32767 |
| int32 | int32_t | -2147483648 | 2147483647 |
| int64 | int64_t | -9223372036854775808 | 9223372036854775807 |
Примеры использования:
```lua
local x = ctypes.uint64(1234)
print(x) --> 1234ULL (cdata)
local y = ctypes.uint64("18446744073709551615")
print(y) --> 18446744073709551615ULL (cdata)
print(x + y) --> 1233ULL
local z = tonumber(x)
print(z) --> 1234 (number)
```

View file

@ -30,6 +30,9 @@ entities.def_name(id: int) -> string
-- Возвращает значение свойства 'hitbox' сущности -- Возвращает значение свойства 'hitbox' сущности
entities.def_hitbox(id: int) -> vec3 entities.def_hitbox(id: int) -> vec3
-- Возвращает true если сущность является осязаемым препятствием.
entities.def_solid(id: int) -> boolean
-- Возвращает индекс определения сущности по имени (числовой ID). -- Возвращает индекс определения сущности по имени (числовой ID).
entities.def_index(name: string) -> int entities.def_index(name: string) -> int
@ -50,12 +53,12 @@ entities.get_all() -> table
-- Возвращает таблицу загруженных сущностей по переданному списку UID -- Возвращает таблицу загруженных сущностей по переданному списку UID
entities.get_all(uids: table<int>) -> table entities.get_all(uids: table<int>) -> table
-- Возвращает список UID сущностей, попадающих в прямоугольную область -- Возвращает список UID сущностей, позиции которых попадают в прямоугольную область
-- pos - минимальный угол области -- pos - минимальный угол области
-- size - размер области -- size - размер области
entities.get_all_in_box(pos: vec3, size: vec3) -> table<int> entities.get_all_in_box(pos: vec3, size: vec3) -> table<int>
-- Возвращает список UID сущностей, попадающих в радиус -- Возвращает список UID сущностей, позиции которых попадают в радиус
-- center - центр области -- center - центр области
-- radius - радиус области -- radius - радиус области
entities.get_all_in_radius(center: vec3, radius: number) -> table<int> entities.get_all_in_radius(center: vec3, radius: number) -> table<int>

View file

@ -140,12 +140,12 @@ gui.confirm(
## Документы и шаблоны ## Документы и шаблоны
```lua ```lua
-- Загружает UI документ и его скрипт. Возвращает имя документа. -- Загружает UI документ и его скрипт. Возвращает пространство имён документа
gui.load_document( gui.load_document(
path: string, -- путь к xml файлу, например: core:layouts/pages/main.xml path: string, -- путь к xml файлу, например: core:layouts/pages/main.xml
name: string, -- id документа, например: core:pages/main name: string, -- id документа, например: core:pages/main
args: table -- параметры для события on_open args: table -- параметры для события on_open
) -> string ) -> table
-- Обрабатывает xml шаблон макета из файла -- Обрабатывает xml шаблон макета из файла
gui.template( gui.template(

View file

@ -58,6 +58,11 @@ hud.get_block_inventory() -> int
-- Дает ID второго открытого инвентаря (блок, виртуальный инвентарь...) или 0. -- Дает ID второго открытого инвентаря (блок, виртуальный инвентарь...) или 0.
hud.get_second_inventory() -> int hud.get_second_inventory() -> int
-- Получить ID инвентаря, в котором находится предмет во время
-- его перемещения в инвентаре, или 0 если его не существует.
-- Имеет единственный слот - 0
hud.get_exchange_inventory() -> int
-- Дает ID игрока, к которому привязан пользовательский интерфейс. -- Дает ID игрока, к которому привязан пользовательский интерфейс.
hud.get_player() -> int hud.get_player() -> int

View file

@ -92,6 +92,11 @@ socket:recv_async(
[опционально] usetable: boolean=false [опционально] usetable: boolean=false
) -> nil|table|Bytearray ) -> nil|table|Bytearray
-- Оборачивает сокет в io_stream (см. ../io_stream.md)
socket:as_stream(
[опционально] binary_mode: boolean=true
) -> io_stream
-- Закрывает соединение -- Закрывает соединение
socket:close() socket:close()

View file

@ -2,14 +2,14 @@
Библиотека для работы с кватернионами. Библиотека для работы с кватернионами.
Кватернионы задаюся таблицей, состоящей из 4 компонентов:
`{w, x, y, z}`
## Кватернион из матрицы - *quat.from_mat4(...)* ## Кватернион из матрицы - *quat.from_mat4(...)*
```lua ```lua
-- создаёт кватернион на основе матрицы вращения -- создаёт кватернион на основе матрицы вращения
quat.from_mat4(m: matrix) -> quat quat.from_mat4(m: matrix, [опционально] dst: quat) -> quat
-- записывает кватернион по матрице вращения в dst
quat.from_mat4(m: matrix, dst: quat)
``` ```
## Кватернион из углов Эйлера - *quat.from_euler(...)* ## Кватернион из углов Эйлера - *quat.from_euler(...)*
@ -17,11 +17,21 @@ quat.from_mat4(m: matrix, dst: quat)
```lua ```lua
-- создаёт кватернион на основе вектора, содержащего углы Эйлера в порядке XYZ (pitch, yaw, roll) -- создаёт кватернион на основе вектора, содержащего углы Эйлера в порядке XYZ (pitch, yaw, roll)
-- (значения углов строго в градусах) -- (значения углов строго в градусах)
quat.from_euler(euler: vec3) -> quat quat.from_euler(euler: vec3, [опционально] dst: quat) -> quat
```
-- записывает кватернион в dst на основе вектора, содержащего углы Эйлера в порядке XYZ (pitch, yaw, roll) ## Композиция кватернионов - quat.mul(...)
-- (значения углов строго в градусах)
quat.from_euler(euler: vec3, dst: quat) ```lua
-- Умножает два кватерниона (композиция поворотов)
quat.mul(a: quat, b: quat, [опционально] dst: quat) -> quat
```
## Поворот 3д вектора на кватернион - quat.mul_vec3(...)
```lua
-- Поворачивает вектор на заданный кватернион
quat.mul_vec3(a: quat, b: vec3, [опционально] dst: vec3) -> vec3
``` ```
## Сферическая линейная интерполяция - *quat.slerp(...)* ## Сферическая линейная интерполяция - *quat.slerp(...)*
@ -31,11 +41,7 @@ quat.from_euler(euler: vec3, dst: quat)
```lua ```lua
-- создаёт кватернион как интерполяцию между a и b, -- создаёт кватернион как интерполяцию между a и b,
-- где t - фактор интерполяции -- где t - фактор интерполяции
quat.slerp(a: quat, b: quat, t: number) -> quat quat.slerp(a: quat, b: quat, t: number, [опционально] dst: quat) -> quat
-- записывает кватернион как интерполяцию между a и b в dst,
-- где t - фактор интерполяции
quat.slerp(a: quat, b: quat, t: number, dst: quat)
``` ```
## Перевод в строку - *quat.tostring(...)* ## Перевод в строку - *quat.tostring(...)*

View file

@ -91,6 +91,8 @@ world.raycast(
-- исключающий режим фильтрации: -- исключающий режим фильтрации:
-- при значении (true) filter_entities определяет список игнорируемых -- при значении (true) filter_entities определяет список игнорируемых
entities_exclusion: bool = false, entities_exclusion: bool = false,
-- учитывать сущности с selectable=false
nonselect_entities: bool = false;
[==[ дополнительные параметры (блоки) ]==] [==[ дополнительные параметры (блоки) ]==]
-- id типов блоков используемых для фильтрации -- id типов блоков используемых для фильтрации

View file

@ -2,6 +2,8 @@
*Bytearray* - динамический байтовый массив, реализованный через LuaJIT FFI. По нему можно итерироваться стандартными функциями **pairs** и **ipairs** *Bytearray* - динамический байтовый массив, реализованный через LuaJIT FFI. По нему можно итерироваться стандартными функциями **pairs** и **ipairs**
<small>Примечание касаемо условных обозначений в документации: псведотип `posint` означает натуральное (положительное ненулевое целое) число.</small>
## Основное ## Основное
### Создание массива ### Создание массива
```lua ```lua
@ -22,29 +24,64 @@ bytes:insert(index: int, value: int)
-- Удаляет элемент(ы), если передан `count`, удалит `count` кол-во элементов с выбранного индекса. -- Удаляет элемент(ы), если передан `count`, удалит `count` кол-во элементов с выбранного индекса.
bytes:remove(index: int, [опционально] count: int) bytes:remove(index: int, [опционально] count: int)
-- Очищает массив -- Очищает массив.
bytes:clear() bytes:clear()
--[[
Заполняет массив определённым байтом полностью либо в определённом диапазоне.
Логика работы следующая: если параметр size или index равны nil, то указанным байтом будет заполнен весь массив,
иначе будет заполнен диапазон внутри массива начиная с индекса index и размером size.
В случае выхода заполняемого блока за границы массива генерирует соответствующую ошибку.
]]
bytes:fill(index: posint/nil, size: posint/nil, byte: uint8)
-- Создаёт новый Bytearray, содержащий копию части данных с offset до offset+length -- Создаёт новый Bytearray, содержащий копию части данных с offset до offset+length
bytes:slice(offset: int, length: int) -> Bytearray bytes:slice(offset: int, length: int) -> Bytearray
--[[
Копирует блок байт из исходного массива, начиная с индекса srcindex, в массив dst, начиная в нём с индекса dstindex.
В случае выхода за границы исходного, конечного массива, либо при некорректном значении параметра size генерирует соответствующую ошибку.
]]
bytes:copy(srcindex: posint, dst: Bytearray, dstindex: posint, size: posint)
--[[
Безопасно копирует перекрывающиеся области памяти (байт) в пределах одного массива.
В случае выхода за пределы массива начального или конечного диапазона байт генерирует соответствующую ошибку.
]]
bytes:move(fromindex: posint, toindex: posint, size: posint)
``` ```
## View ## View
Это "вьюшки" поверх Bytearray, которые интерпретируют его байты как массив чисел другого размера - **без копирования данных**. Это "вьюшки" поверх Bytearray, которые интерпретируют его байты как массив чисел другого размера - **без копирования данных**.
Не имеет своих методов, является просто объектной ссылкой на оригинальный Bytearray. Практически не имеют своих методов, являются простыми объектными ссылками на оригинальный Bytearray.
| Класс | Тип элементов | Размер | | Класс | Тип элементов | Размер |
| ------------ | ------------------ | ------- | | ------------ | ------------------ | --------- |
| `I16view` | `int16_t` | 2 байта | | `I8view` | `int8_t` | 1 байт |
| `U16view` | `uint16_t` | 2 байта | | `I16view` | `int16_t` | 2 байта |
| `I32view` | `int32_t` | 4 байта | | `U16view` | `uint16_t` | 2 байта |
| `U32view` | `uint32_t` | 4 байта | | `I32view` | `int32_t` | 4 байта |
| `U32view` | `uint32_t` | 4 байта |
| `I64view` | `int64_t` | 8 байт |
| `U64view` | `uint64_t` | 8 байт |
| `FLTview` | `float` | 4 байта* |
| `DBLview` | `double` | 8 байт* |
--- <small>* Размеры "оригинальных" C-шных типов являются платформозависимыми и их точный размер не описан в стандарте языка. В данной таблице предоставлены наиболее распространённые размеры типов.</small>
### Пример использования ### Специфические методы "вьюшек"
```lua
--[[
Возвращает размер типа вьюшки. Результат тот же, что и от применения оператора sizeof() из языка C для данного типа.
Полезно для вьюшек, использующих C-шные типы нефиксированного размера, например те же float или double.
]]
view:typesize() -> posint
```
## Пример использования
```lua ```lua
local bytes = Bytearray({1, 0, 2, 0, 250, 255}) local bytes = Bytearray({1, 0, 2, 0, 250, 255})

View file

@ -180,6 +180,17 @@ rig:get_color() -> vec3
rig:set_color(color: vec3) rig:set_color(color: vec3)
``` ```
## Пользовательские компоненты
Компонент описывается в виде скрипта `{пак}/scripts/components/{имя}.lua`.
Компонент будет доступен для использования в описании сущности как `{пак}:{имя}`.
Пример: `core:scripts/components/pathfinding.lua` -> `core:pathfinding`.
Для **каждой** сущности, содержащей компонент, создаётся **отдельный экземпляр** со **своим пространством имен** внутри пространства имён пака.
Глобальные переменные, объявляемые в нём, играют роль публичных полей компонента (как и глобальные функции).
Компоненты сущности указываются через [список "components"](../entity-properties.md#cписок-компонентов---components)
> [!WARNING] > [!WARNING]
> При выходе сущности за пределы зоны прогрузки она удаляется, вызывая события. > При выходе сущности за пределы зоны прогрузки она удаляется, вызывая события.
> При повторном попадании в зону прогрузки сущность спавнится заново. > При повторном попадании в зону прогрузки сущность спавнится заново.

View file

@ -34,7 +34,7 @@ function on_broken(x, y, z, playerid)
function on_replaced(x, y, z, playerid) function on_replaced(x, y, z, playerid)
``` ```
Вызывается после замены блока игроком Вызывается перед заменой блока игроком
```lua ```lua
function on_interact(x, y, z, playerid) -> bool function on_interact(x, y, z, playerid) -> bool

View file

@ -7,7 +7,7 @@
- [расширения для string](#расширения-для-string) - [расширения для string](#расширения-для-string)
- [расширения для math](#расширения-для-math) - [расширения для math](#расширения-для-math)
- [расширения для bit](#расширения-для-bit) - [расширения для bit](#расширения-для-bit)
- [Дополнительные глобальные функции](#дополнительные-глобальные-функции) - [дополнительные глобальные функции](#дополнительные-глобальные-функции)
## Расширения для table ## Расширения для table
```lua ```lua
@ -62,6 +62,12 @@ table.sub(arr: table, start: number | nil, stop: number | nil) -> table
table.insert_unique(t: table, val: any) table.insert_unique(t: table, val: any)
table.insert_unique(t: table, pos: int, val: any) table.insert_unique(t: table, pos: int, val: any)
-- Возвращает таблицу, содержащую все ключи переданной таблицы, включая числовые.
table.keys(t: table) -> table
-- Добавляет в таблицу t все пары ключ-значение из таблицы extension, при этом если в extension присутствует ключ из t, то его значение будет перезаписано.
table.extend(t: table, extension: table) -> table
-- Конвертирует переданную таблицу в строку. -- Конвертирует переданную таблицу в строку.
table.tostring(t: table) -> string table.tostring(t: table) -> string
``` ```
@ -156,6 +162,14 @@ math.round(num: number, [опционально] places: number) -> number
-- Возвращает сумму всех принимаемых аргументов. Если в качестве аргумента была передана таблица, метод вернёт сумму всех её элементов. -- Возвращает сумму всех принимаемых аргументов. Если в качестве аргумента была передана таблица, метод вернёт сумму всех её элементов.
math.sum(x: number, ... | t: table) -> number math.sum(x: number, ... | t: table) -> number
-- Возвращает целое число, указывающее знак числа (-1/0/1)
math.sign(x: number) -> int
-- Одномерный шум с значениями в диапазоне [-1..1].
math.noise(x: number, [опционально] octaves: int = 1) -> number
-- Двумерный шум с значениями в диапазоне [-1..1].
math.noise2d(x: number, y: number [опционально] octaves: int = 1) -> number
``` ```
## Расширения для bit ## Расширения для bit

View file

@ -52,6 +52,60 @@ yaml.parse(code: str) -> table
Парсит YAML строку в таблицу. Парсит YAML строку в таблицу.
## Библиотека xml
Библиотека содержит функции для сериализации и десериализации XML:
Далее XML как тип данных обозначает Lua-таблицу со следующей структурой:
- Тег элемента доступен по ключу `#`
- XML-аттрибуты доступны как пары ключ = значение
- Вложенные элементы доступны по индексу
Таблица используется как словарь, так и массив.
Пример:
```xml
<panel size='400' color='0' interval='1' context='menu'>
<button onclick='menu.page="worlds"'>@Worlds</button>
</panel>
```
Эквивалентная Lua-таблица:
```lua
{
['#'] = "panel",
size = "400",
color = "0",
interval = "1",
context = "menu",
{
['#'] = "button",
onclick = 'menu.page="worlds"',
{
"@Worlds"
}
}
}
```
Соответственно, элемент button доступен как `элемент_panel[1]`.
```lua
-- Сериализует дерево в формате XML
-- multiline - использовать ли многострочное форматирование
xml.tostring(data: XML, multiline: bool = true) -> str
-- Парсит XML
xml.parse(code: str) -> XML
-- Парсит формат VCD (VoxelCore Declaration), используемый форматом VCM.
-- root_tag - будет неявно создан корневой элемент с указанным тегом,
-- содержащий все корневые элементы
xml.parse_vcd(code: str, root_tag: str | nil = nil) -> XML
```
## Библиотека bjson ## Библиотека bjson
Библиотека содержит функции для работы с двоичным форматом обмена данными [vcbjson](../../specs/binary_json_spec.md). Библиотека содержит функции для работы с двоичным форматом обмена данными [vcbjson](../../specs/binary_json_spec.md).

View file

@ -18,7 +18,7 @@
Поток имеет три различных вида режима: Поток имеет три различных вида режима:
- Режим общего поведения (`general` / `mode`) - Режим общего поведения (`general` / `mode`)
- Режим сброса (`flush` / `flushMode`) - Режим сброса (`flush` / `flush_mode`)
- Двоичный режим (`binary`) - Двоичный режим (`binary`)
@ -27,34 +27,34 @@
Определяет, как поток обрабатывает чтение и запись, Определяет, как поток обрабатывает чтение и запись,
имеет три подрежима: имеет три подрежима:
| Режим | Описание | | Режим | Описание |
|---------------|--------| |---------------|-------------------------------------------------------------------------------------------------------------------------------------------------|
| `"default"` | Прямой режим. `read` может вернуть меньше байт, чем запрошено. `write` сразу отправляет данные в низкоуровневый дескриптор. Нет буферизации. | | `"default"` | Прямой режим. `read` может вернуть меньше байт, чем запрошено. `write` сразу отправляет данные в низкоуровневый дескриптор. Нет буферизации. |
| `"yield"` | Как `default`, но при нехватке данных в `read(n)` поток будет вызывать `coroutine.yield()`, пока не соберёт ровно `n` байт. Удобно для корутин. | | `"yield"` | Как `default`, но при нехватке данных в `read(n)` поток будет вызывать `coroutine.yield()`, пока не соберёт ровно `n` байт. Удобно для корутин. |
| `"buffered"` | Включает внутренние буферы чтения и записи. `read` берёт данные из буфера, `write` — складывает в буфер. При превышении `maxBufferSize` — ошибка `buffer overflow`. | | `"buffered"` | Включает внутренние буферы чтения и записи. `read` берёт данные из буфера , `write` — складывает в буфер. |
### flush ### flush
Работает только в режиме `"buffered"`, Работает только в режиме `"buffered"`,
имеет два подрежима: имеет два подрежима:
| Режим | Что делает `flush()` | | Режим | Что делает `flush()` |
|------------------|----------------------| |------------------------|-----------------------------------------------------------------------------------------------------|
| `"all"` (по умолчанию) | Сначала сбрасывает буфер записи → низкоуровневый `write`, затем вызывает `ioLib.flush(descriptor)` | | `"all"` (по умолчанию) | Сначала сбрасывает буфер записи → низкоуровневый `write`, затем вызывает `io_lib.flush(descriptor)` |
| `"buffer"` | Сбрасывает только буфер записи, без вызова системного `flush` | | `"buffer"` | Сбрасывает только буфер записи, без вызова системного `flush` |
### binary ### binary
**Независимый флаг** (включается через `set_binary_mode(true)` или при создании потока). **Независимый флаг** (включается через `set_binary_mode(true)` или при создании потока).
Определяет, в каком виде методы `read` и `write` принимают и возвращают данные: Определяет, в каком виде методы `read` и `write` принимают и возвращают данные:
| binary = true | binary = false (по умолчанию) | | binary = true | binary = false (по умолчанию) |
|-----------------------------------------|----------------------------------------| |------------------------------------------------------------|----------------------------------------|
| Данные — это **байты** (`Bytearray`, таблица чисел 0..255) | Данные — это **текстовые строки** | | Данные — это **байты** (`Bytearray`, таблица чисел 0..255) | Данные — это **текстовые строки** |
| `read(n)` → `Bytearray` или `table<number>`| `read()` → одна строка | | `read(n)` → `Bytearray` или `table<int>` | `read()` → одна строка |
| `read("i4 f")` → распаковка через `byteutil.unpack` | `read(n)` → n строк в таблице | | `read("i4 f")` → распаковка через `byteutil.unpack` | `read(n)` → n строк в таблице |
| `write(Bytearray)` → запись байтов | `write("hello")` → строка + `\n` | | `write(Bytearray)` → запись байтов | `write("hello")` → строка + `\n` |
| `write("i4", 42)` → `byteutil.pack` | `write({"a","b"})` → две строки с `\n`| | `write("i4", 42)` → `byteutil.pack` | `write({"a","b"})` → две строки с `\n` |
`read_line` / `write_line` — работают как в текстовом режиме `read_line` / `write_line` — работают как в текстовом режиме
@ -90,25 +90,25 @@ io_stream:set_flush_mode(string)
--[[ --[[
Читает данные из потока Читает данные из потока
В двоичном режиме: В двоичном режиме:
Если arg - number, то читает из потока arg байт и возвращает ввиде Bytearray или таблицы, если useTable = true Если arg - int, то читает из потока arg байт и возвращает ввиде Bytearray или таблицы, если use_table = true
Если arg - string, то функция интерпретирует arg как шаблон для byteutil. Прочитает кол-во байт, которое определено шаблоном, передаст их в byteutil.unpack и вернёт результат Если arg - string, то функция интерпретирует arg как шаблон для byteutil. Прочитает кол-во байт, которое определено шаблоном, передаст их в byteutil.unpack и вернёт результат
В текстовом режиме: В текстовом режиме:
Если arg - number, то читает нужное кол-во строк с окончанием CRLF/LF из arg и возвращает ввиде таблицы. Также, если trimEmptyLines = true, то удаляет пустые строки с начала и конца из итоговой таблицы Если arg - int, то читает нужное кол-во строк с окончанием CRLF/LF из arg и возвращает ввиде таблицы. Также, если trim_empty_lines = true, то удаляет пустые строки с начала и конца из итоговой таблицы
Если arg не определён, то читает одну строку с окончанием CRLF/LF и возвращает её. Если arg не определён, то читает одну строку с окончанием CRLF/LF и возвращает её.
--]] --]]
io_stream:read( io_stream:read(
[опционально] arg: number | string, [опционально] arg: int | string,
[опционально] useTable | trimEmptyLines: boolean [опционально] use_table = false | trim_empty_lines = true: boolean
) -> Bytearray | table<number> | string | table<string> | ... ) -> Bytearray | table<int> | string | table<string> | ...
--[[ --[[
Записывает данные в поток Записывает данные в поток
В двоичном режиме: В двоичном режиме:
Если arg - string, то функция интерпретирует arg как шаблон для byteutil, передаст его и ... в byteutil.pack и результат запишет в поток Если arg - string, то функция интерпретирует arg как шаблон для byteutil, передаст его и ... в byteutil.pack и результат запишет в поток
Если arg - Bytearray | table<number>, то записывает байты в поток Если arg - Bytearray | table<int>, то записывает байты в поток
В текстовом режиме: В текстовом режиме:
Если arg - string, то записывает строку в поток (вместе с окончанием LF) Если arg - string, то записывает строку в поток (вместе с окончанием LF)
@ -116,7 +116,7 @@ io_stream:read(
Если arg - table<string>, то записывает каждую строку из таблицы отдельно Если arg - table<string>, то записывает каждую строку из таблицы отдельно
--]] --]]
io_stream:write( io_stream:write(
arg: Bytearray | table<number> | string | table<string>, arg: Bytearray | table<int> | string | table<string>,
[опционально] ... [опционально] ...
) )
@ -128,53 +128,59 @@ io_stream:write_line(string)
--[[ --[[
В двоичном режиме: В двоичном режиме:
Читает все доступные байты из потока и возвращает ввиде Bytearray или table<number>, если useTable = true Читает все доступные байты из потока и возвращает ввиде Bytearray или table<int>, если use_table = true
В текстовом режиме: В текстовом режиме:
Читает все доступные строки из потока в table<string> если useTable = true, или в одну строку вместе с окончаниями, если нет Читает все доступные строки из потока в table<string> если use_table = true, или в одну строку вместе с окончаниями, если нет
--]] --]]
io_stream:read_fully( io_stream:read_fully(
[опционально] useTable: boolean [опционально] use_table: boolean
) -> Bytearray | table<number> | table<string> | string ) -> Bytearray | table<int> | table<string> | string
--[[ --[[
Устанавливает позицию в потоке Если length определён, то возвращает true, если length байт доступно к чтению. Иначе возвращает false.
Если не определён, то возвращает количество байт, которое можно прочитать.
В не буферизированном режиме потока может всегда возвращать 0 или false, если поток не поддерживает available.
--]]
io_stream:available(
[опционально] length: int
) -> int | boolean
--[[
Устанавливает позицию в потоке (всегда в байтах)
Режимы: Режимы:
b - Задаёт позицию относительно начало файла b - Задаёт позицию относительно начало файла
c - Задаёт позицию относительно текущей позиции c - Задаёт позицию относительно текущей позиции
e - Задаёт позицию относительно конца файла e - Задаёт позицию относительно конца файла
Может бросать ошибку, если поток не поддерживает seek.
--]] --]]
io_stream:seek( io_stream:seek(
mode: string mode: string
offset: number offset: int
) )
-- Возвращает текущую позицию в потоке от начала.
-- Может бросать ошибку, если поток не поддерживает tell.
io_stream:tell() -> int
``` ```
## Методы Buffered-режима ## Методы Buffered-режима
```lua ```lua
--[[
Если length определён, то возвращает true, если length байт доступно к чтению. Иначе возвращает false
Если не определён, то возвращает количество байт, которое можно прочитать
--]]
io_stream:available(
[опционально] length: number
) -> number | boolean
-- Возвращает максимальный размер буферов -- Возвращает максимальный размер буферов
io_stream:get_max_buffer_size() -> number io_stream:get_max_buffer_size() -> int
-- Задаёт новый максимальный размер буферов -- Задаёт новый максимальный размер буферов
io_stream:set_max_buffer_size(max_size: number) io_stream:set_max_buffer_size(max_size: int)
``` ```
## Методы контроля состояния потока ## Методы контроля состояния потока
```lua ```lua
-- Возвращает true, если поток открыт на данный момент -- Возвращает true, если поток открыт на данный момент
io_stream:is_alive() -> bool io_stream:is_alive() -> bool
@ -185,16 +191,27 @@ io_stream:is_closed() -> bool
io_stream:close() io_stream:close()
-- Записывает все данные из write-буфера в поток в buffer/all flush-режимах -- Записывает все данные из write-буфера в поток в buffer/all flush-режимах
-- Вызывает ioLib.flush() в all flush-режиме -- Вызывает ioLib.flush() в all flush-режиме, или ничего не делает, если
-- ioLib не поддерживает flush.
io_stream:flush() io_stream:flush()
-- Создаёт новый поток из Bytearray.
-- Может использоваться одновременно как для чтения, так и для записи.
-- Результат записи будет записан в тот же Bytearray, что был передан в функцию.
io_stream.wrap_bytearray(
buffer: Bytearray,
-- по-умолчанию равен true, поскольку функция из вводных аргументов
-- будет использоваться преимущественно для работы с двоичными данными.
[опционально] binary_mode: boolean = true
) -> io_stream
-- Создаёт новый поток с переданным дескриптором и использующим переданную I/O библиотеку. (Более подробно в core:io_stream.lua) -- Создаёт новый поток с переданным дескриптором и использующим переданную I/O библиотеку. (Более подробно в core:io_stream.lua)
io_stream.new( io_stream.new(
descriptor: int, descriptor: int,
binaryMode: bool, binary_mode: boolean,
ioLib: table, io_lib: table,
[опционально] mode: string = "default", [опционально] mode: string = "default",
[опционально] flushMode: string = "all" [опционально] flush_mode: string = "all"
) -> io_stream ) -> io_stream
``` ```

View file

@ -1,7 +1,7 @@
# Свойства и методы UI элементов # Свойства и методы UI элементов
Обращение к UI элементов в скриптах произодится через экземпляр Document Обращение к UI элементов в скриптах произодится через экземпляр Document
(переменная document) по id, указанному в xml. (глобальная переменная document) по id, указанному в xml.
Пример: вывод в консоль свойства pos элемента с id: "worlds-panel": Пример: вывод в консоль свойства pos элемента с id: "worlds-panel":
```lua ```lua
@ -53,7 +53,7 @@ document["worlds-panel"]:clear()
| contentOffset | vec2 | да | *нет* | смещение содержимого | | contentOffset | vec2 | да | *нет* | смещение содержимого |
| cursor | string | да | да | курсор, отображаемый при наведении | | cursor | string | да | да | курсор, отображаемый при наведении |
| parent | Element | да | *нет* | родительский элемент или nil | | parent | Element | да | *нет* | родительский элемент или nil |
| zIndex | bool | да | *нет* | значение z-индекса. В panel определяет порядок | | zIndex | int | да | *нет* | значение z-индекса. В panel определяет порядок |
Общие методы элементов: Общие методы элементов:

View file

@ -215,12 +215,28 @@ local map = Heightmap(ширина, высота)
Операции применяются ко всем значениям высоты. Операции применяются ко всем значениям высоты.
```lua ```lua
-- Приведение значений высот к абсолютным
map:abs() map:abs()
-- Округление к меньшему целому
map:floor()
-- Округление к большему целому
map:ceil()
-- Округление к ближайшему целому
map:round()
-- Синус от значений высот (в радианах)
map:sin()
-- Косинус от значений высот (в радианах)
map:cos()
-- Тангенс от значений высот (в радианах)
map:tan()
``` ```
Приводит значения высот к абсолютным.
### Бинарные операции ### Бинарные операции
Операции с применением второй карты или скаляра. Операции с применением второй карты или скаляра.

View file

@ -43,6 +43,8 @@
- `position-func` - поставщик позиции элемента (два числа), вызываемый при изменении размера контейнера, в котором находится элемент, либо при добавлении элемента в контейнер. Может быть вызван до вызова on_hud_open. - `position-func` - поставщик позиции элемента (два числа), вызываемый при изменении размера контейнера, в котором находится элемент, либо при добавлении элемента в контейнер. Может быть вызван до вызова on_hud_open.
- `size-func` - поставщик размера элемента (два числа), вызываемый при изменении размера контейнера, в котором находится элемент, либо при добавлении элемента в контейнер. Может быть вызван до вызова on_hud_open. - `size-func` - поставщик размера элемента (два числа), вызываемый при изменении размера контейнера, в котором находится элемент, либо при добавлении элемента в контейнер. Может быть вызван до вызова on_hud_open.
- `onclick` - lua функция вызываемая при нажатии на элемент. - `onclick` - lua функция вызываемая при нажатии на элемент.
- `onrightclick` - lua функция вызываемая при нажатии правой кнопки мыши на элемент.
- `onmiddleclick` - lua функция вызываемая при нажатии средней кнопки мыши на элемент.
- `ondoubleclick` - lua функция вызываемая при двойном нажатии на элемент. - `ondoubleclick` - lua функция вызываемая при двойном нажатии на элемент.
- `onfocus` - lua функция вызываемая при фокусировке на элемент. - `onfocus` - lua функция вызываемая при фокусировке на элемент.
- `ondefocus` - lua функция вызываемая при потере фокуса элеметом. - `ondefocus` - lua функция вызываемая при потере фокуса элеметом.
@ -193,6 +195,9 @@
- `width` - минимальная ширина содержимого. По-умолчанию: 100. - `width` - минимальная ширина содержимого. По-умолчанию: 100.
- `selected` - изначально выбранное значение. По-умолчанию: "". - `selected` - изначально выбранное значение. По-умолчанию: "".
- `onselect` - функция, в которую передаётся выбранное пользователем значение - `onselect` - функция, в которую передаётся выбранное пользователем значение
- `mode` - режим работы:
- `select` - режим по-умолчанию.
- `button` - режим кнопки: текст не заменяется на выбранную опцию, а элемент выглядит как обычная кнопка.
## Привязка ввода - *bindbox* ## Привязка ввода - *bindbox*

View file

@ -1,3 +1,4 @@
generator = "base:demo" generator = "base:demo"
player-entity = "base:player" player-entity = "base:player"
hand-skeleton = "base:hand" hand-skeleton = "base:hand"
block-material = "base:stone"

View file

@ -86,12 +86,27 @@ function generate_heightmap(x, y, w, h, s, inputs)
local rivermap = Heightmap(w, h) local rivermap = Heightmap(w, h)
rivermap.noiseSeed = SEED rivermap.noiseSeed = SEED
rivermap:noise({x+21, y+12}, 0.1*s, 4) rivermap:noise({ x + 21, y + 12 }, 0.1 * s, 4)
rivermap:abs() rivermap:pow(4)
rivermap:mul(2.0) rivermap:mul(3500)
rivermap:pow(0.15) rivermap:add(1)
rivermap:max(0.5) rivermap:pow(-1)
map:mul(rivermap) rivermap:mul(-1)
rivermap:add(1)
local rivermap_bottom = Heightmap(w, h)
rivermap_bottom:add(0.23)
rivermap_bottom:noise({ x, y }, 2 * s, 4, 0.005)
map:sub(rivermap_bottom)
local map_multiplied = Heightmap(w, h)
map_multiplied:add(map)
map_multiplied:mul(rivermap)
map:min(map_multiplied)
map:add(rivermap_bottom)
local desertmap = Heightmap(w, h) local desertmap = Heightmap(w, h)
desertmap.noiseSeed = SEED desertmap.noiseSeed = SEED

View file

@ -1,18 +1,84 @@
local util = {} local util = {}
function util.drop(ppos, itemid, count, data, pickup_delay) util.DROP_FORCE = 8
if itemid == 0 or not itemid then util.DROP_INIT_VEL = { 0, 3, 0 }
return nil util.DROP_MAX_ITEM_DIR_SHIFT_DEGREES = 65
---@param dir_shift? vec2 direction shift for drop; x and y must be clamped to [-0.5, 0.5]
local function calculate_item_drop_dir(pid, dir_shift)
if not dir_shift then
dir_shift = { 0, 0 }
end end
return entities.spawn("base:drop", ppos, {base__drop={ local yaw, pitch = player.get_rot(pid)
id=itemid, local q_yaw = quat.from_euler({ 0, yaw, 0 })
count=count, local q_pitch = quat.from_euler({ pitch, 0, 0 })
data=data, local q_rotation = quat.mul(q_yaw, q_pitch)
pickup_delay=pickup_delay
}}) local q_shift = quat.from_euler({
-dir_shift[2] * util.DROP_MAX_ITEM_DIR_SHIFT_DEGREES * 2,
-dir_shift[1] * util.DROP_MAX_ITEM_DIR_SHIFT_DEGREES * 2,
0,
})
quat.mul(q_rotation, q_shift, q_rotation)
return quat.mul_vec3(q_rotation, { 0, 0, -1 })
end end
local function calc_loot(loot_table) --- Create a drop from slot, set it velocity, and return it
---@param mode integer 0 - whole stack, 1 - single item, 2 - dupe whole stack
---@param dir_shift? vec2 direction shift for drop; x and y must be clamped to [-0.5, 0.5]
---@return table? entity, string? error
function util.drop_from_slot(pid, invid, slot, mode, dir_shift)
if invid == 0 then
return nil, "invid cannot be 0"
end
if mode == 2 and not player.is_infinite_items(pid) then
return nil, "no permission to dupe items"
end
local itemid, itemcount = inventory.get(invid, slot)
if itemid == 0 then
return nil, "no item in slot"
end
local drop_itemcount = 1
if mode == 0 then
drop_itemcount = itemcount
end
if mode == 2 then
drop_itemcount = item.stack_size(itemid)
else
inventory.set(invid, slot, itemid, itemcount - drop_itemcount)
end
local data = inventory.get_all_data(invid, slot)
local pvel = { player.get_vel(pid) }
local ppos = vec3.add({ player.get_pos(pid) }, { 0, 0.7, 0 })
local dir = calculate_item_drop_dir(pid, dir_shift)
local throw_force = vec3.mul(dir, util.DROP_FORCE)
local drop, err = util.drop(ppos, itemid, drop_itemcount, data, 1.5)
if not drop then
return nil, err
end
local velocity = vec3.add(throw_force, vec3.add(pvel, util.DROP_INIT_VEL))
drop.rigidbody:set_vel(velocity)
return drop
end
---@return table? entity, string? error
function util.drop(ppos, itemid, count, data, pickup_delay)
if itemid == 0 or not itemid then
return nil, "item is empty"
end
return entities.spawn("base:drop", ppos, {
base__drop = {
id = itemid,
count = count,
data = data,
pickup_delay = pickup_delay
}
})
end
function util.calc_loot(loot_table)
local results = {} local results = {}
for _, loot in ipairs(loot_table) do for _, loot in ipairs(loot_table) do
local chance = loot.chance or 1 local chance = loot.chance or 1
@ -27,7 +93,9 @@ local function calc_loot(loot_table)
if count == 0 then if count == 0 then
goto continue goto continue
end end
table.insert(results, {item=item.index(loot.item), count=count}) table.insert(results, {
item = item.index(loot.item), count = count
})
end end
::continue:: ::continue::
end end
@ -37,9 +105,9 @@ end
function util.block_loot(blockid) function util.block_loot(blockid)
local lootscheme = block.properties[blockid]["base:loot"] local lootscheme = block.properties[blockid]["base:loot"]
if lootscheme then if lootscheme then
return calc_loot(lootscheme) return util.calc_loot(lootscheme)
end end
return {{item=block.get_picking_item(blockid), count=1}} return { { item = block.get_picking_item(blockid), count = 1 } }
end end
return util return util

View file

@ -26,6 +26,7 @@ function on_save()
end end
if vc.is_client() then if vc.is_client() then
local SCALE = 0.3
local scale = {1, 1, 1} local scale = {1, 1, 1}
local rotation = mat4.rotate({ local rotation = mat4.rotate({
math.random(), math.random(), math.random() math.random(), math.random(), math.random()
@ -35,7 +36,7 @@ if vc.is_client() then
local matrix = mat4.idt() local matrix = mat4.idt()
rig:set_model(0, item.model_name(dropitem.id)) rig:set_model(0, item.model_name(dropitem.id))
local bodysize = math.min(scale[1], scale[2], scale[3]) local bodysize = math.min(scale[1], scale[2], scale[3])
body:set_size({scale[1], bodysize, scale[3]}) body:set_size(vec3.mul({scale[1], bodysize, scale[3]}, SCALE))
mat4.mul(matrix, rotation, matrix) mat4.mul(matrix, rotation, matrix)
mat4.scale(matrix, scale, matrix) mat4.scale(matrix, scale, matrix)
rig:set_matrix(0, matrix) rig:set_matrix(0, matrix)

View file

@ -1,3 +1,6 @@
local dirtid = block.index("base:dirt")
local grass_blockid = block.index("base:grass_block")
local offsets = {} local offsets = {}
for lx=-1,1 do for lx=-1,1 do
for ly=-1,1 do for ly=-1,1 do
@ -8,18 +11,17 @@ for lx=-1,1 do
end end
function on_random_update(x, y, z) function on_random_update(x, y, z)
local dirtid = block.index('base:dirt');
if block.is_solid_at(x, y+1, z) then if block.is_solid_at(x, y+1, z) then
block.set(x, y, z, dirtid, 0) block.set(x, y, z, dirtid, 0)
return return
end end
local grassblockid = block.index('base:grass_block')
for _, offset in ipairs(offsets) do for _, offset in ipairs(offsets) do
local nx, ny, nz = x + offset[1], y + offset[2], z + offset[3] local nx, ny, nz = x + offset[1], y + offset[2], z + offset[3]
if block.get(nx, ny, nz) == dirtid and if block.get(nx, ny, nz) == dirtid and
not block.is_solid_at(nx, ny + 1, nz) then not block.is_solid_at(nx, ny + 1, nz) then
block.set(nx, ny, nz, grassblockid, 0) block.set(nx, ny, nz, grass_blockid, 0)
return return
end end
end end

View file

@ -1,30 +1,26 @@
local base_util = require "util" local base_util = require "util"
local DROP_FORCE = 8
local DROP_INIT_VEL = {0, 3, 0}
function on_hud_open() function on_hud_open()
input.add_callback("player.drop", function () events.on("core:drop_outside_inventory", function(mode)
local pid = hud.get_player()
local invid = hud.get_exchange_inventory()
local vp = gui.get_viewport()
local dir_shift = table.map(input.get_mouse_pos(), function(i, v)
return v / vp[i] - 0.5
end)
base_util.drop_from_slot(pid, invid, 0, mode, dir_shift)
end)
input.add_callback("player.drop", function()
if hud.is_paused() or hud.is_inventory_open() then if hud.is_paused() or hud.is_inventory_open() then
return return
end end
local pid = hud.get_player() local pid = hud.get_player()
local invid, slot = player.get_inventory(pid) local invid, slot = player.get_inventory(pid)
local itemid, itemcount = inventory.get(invid, slot) local mode = 1
if itemid == 0 then if input.is_pressed("key:left-ctrl") or input.is_pressed("key:right-ctrl") then
return mode = 0
end end
local data = inventory.get_all_data(invid, slot) base_util.drop_from_slot(pid, invid, slot, mode)
inventory.set(invid, slot, itemid, itemcount-1)
local pvel = {player.get_vel(pid)}
local ppos = vec3.add({player.get_pos(pid)}, {0, 0.7, 0})
local throw_force = vec3.mul(player.get_dir(pid), DROP_FORCE)
local drop = base_util.drop(ppos, itemid, 1, data, 1.5)
if not drop then
return
end
local velocity = vec3.add(throw_force, vec3.add(pvel, DROP_INIT_VEL))
drop.rigidbody:set_vel(velocity)
end) end)
rules.create("do-loot-non-player", true)
end end

View file

@ -16,6 +16,4 @@ function on_block_broken(id, x, y, z, playerid)
spawn_spread=vec3.mul(size, 0.4) spawn_spread=vec3.mul(size, 0.4)
}) })
end end
rules.create("do-loot-non-player", true)
end end

View file

@ -17,7 +17,7 @@
color="#FFFFFF80" size="16" pos="4,6" color="#FFFFFF80" size="16" pos="4,6"
hover-color="#1080FF"></image> hover-color="#1080FF"></image>
<panel orientation="horizontal" gravity="top-right" <panel orientation="horizontal" gravity="top-right"
size="76,24" padding="8" interval="8" color="0"> size="76,24" padding="8,6,8,2" interval="8" color="0">
<image id="saveIcon" src="gui/save" tooltip="@Save" <image id="saveIcon" src="gui/save" tooltip="@Save"
enabled="false" interactive="true" enabled="false" interactive="true"
hover-color="#1080FF" hover-color="#1080FF"
@ -31,6 +31,12 @@
hover-color="#1080FF" hover-color="#1080FF"
onclick="run_current_file()" onclick="run_current_file()"
color="#FFFFFF80" size="16"></image> color="#FFFFFF80" size="16"></image>
<!-- TODO: remove after 0.32-->
<image src="gui/item" size="16"
enabled="true" interactive="true"
hover-color="#1080FF"
tooltip="Fix old VCM regions (0.31 bug)"
onclick="fix_vcm_regions()"></image>
</panel> </panel>
<label id="title" pos="26,8"></label> <label id="title" pos="26,8"></label>
</container> </container>

View file

@ -113,6 +113,78 @@ local function reload_model(filename, name)
assets.parse_model(file.ext(filename), document.editor.text, name) assets.parse_model(file.ext(filename), document.editor.text, name)
end end
-- === TODO: remove after 0.32 ===
local pattern =
"region %(%s*"
.. "([+-]?%d+%.?%d*[eE]?[+-]?%d*)%s*,%s*"
.. "([+-]?%d+%.?%d*[eE]?[+-]?%d*)%s*,%s*"
.. "([+-]?%d+%.?%d*[eE]?[+-]?%d*)%s*,%s*"
.. "([+-]?%d+%.?%d*[eE]?[+-]?%d*)"
.. "%s*%)"
local function replace1(x1, y1, x2, y2)
return string.format("region (%s, %s, %s, %s)", x2, y2, x1, y1)
end
local function replace2(x1, y1, x2, y2)
return string.format("region (%s, %s, %s, %s)", x2, y1, x1, y2)
end
function convert_vcm(text)
local lines = {}
local zpairs = {
{"%(north%)", "%(south%)", "(TMPS)", "(TMPN)"},
{"%,north", "%,south", ",TMPS", ",TMPN"},
{"north%,", "south%,", "TMPS,", "TMPN,"},
}
local xpairs = {
{"%(west%)", "%(east%)", "(TMPE)", "(TMPW)"},
{"%,west", "%,east", ",TMPE", ",TMPW"},
{"west%,", "east%,", "TMPE,", "TMPW,"},
}
for i, pair in ipairs(zpairs) do
text = text:gsub(pair[1], pair[3])
text = text:gsub(pair[2], pair[4])
end
text = text:gsub("TMPS", "south")
text = text:gsub("TMPN", "north")
for i, pair in ipairs(xpairs) do
text = text:gsub(pair[1], pair[3])
text = text:gsub(pair[2], pair[4])
end
text = text:gsub("TMPW", "west")
text = text:gsub("TMPE", "east")
for line in text:gmatch("[^\n]*\n?") do
if line == "" then
break
end
if line:find("(top)", 1, true) then
line = line:gsub(pattern, replace1)
end
if line:find("(bottom)", 1, true)
or line:find("(east)", 1, true) then
line = line:gsub(pattern, replace2)
end
table.insert(lines, line)
end
return table.concat(lines)
end
function fix_vcm_regions()
if not current_file.filename then
return
end
document.editor.text = convert_vcm(document.editor.text)
end
-- ===============================
function run_current_file() function run_current_file()
if not current_file.filename then if not current_file.filename then
return return

View file

@ -19,7 +19,7 @@ local function update_line(line, uptime)
end end
end end
events.on("core:chat", function(message) local core_chat_handler = events.on("core:chat", function(message)
while #lines >= max_lines do while #lines >= max_lines do
document[lines[1][1]]:destruct() document[lines[1][1]]:destruct()
table.remove(lines, 1) table.remove(lines, 1)
@ -52,3 +52,7 @@ function on_open()
end) end)
end end
end end
function on_destroy()
events.remove("core:chat", core_chat_handler)
end

View file

@ -1,4 +1,4 @@
<inventory> <inventory>
<slots-grid pos="0, 164" rows="1" count="10" sharefunc="inventory_share_func"/> <slots-grid pos="0, 164" rows="1" count="10" sharefunc="share_func"/>
<slots-grid rows="3" start-index="10" count="30" sharefunc="inventory_share_func"/> <slots-grid rows="3" start-index="10" count="30" sharefunc="share_func"/>
</inventory> </inventory>

View file

@ -1,12 +1 @@
function inventory_share_func(invid, slotid) share_func = require("core:inventory_utils").share_default_func
local blockinv = hud.get_block_inventory()
if blockinv ~= 0 then
inventory.move(invid, slotid, blockinv)
elseif rules.get("allow-content-access") then
inventory.set(invid, slotid, 0, 0)
elseif slotid < 10 then
inventory.move_range(invid, slotid, invid, 10)
else
inventory.move_range(invid, slotid, invid, 0, 9)
end
end

View file

@ -3,6 +3,7 @@ function on_open(params)
mode = params.mode mode = params.mode
end end
refresh() refresh()
document.search_textbox.focused = true
end end
-- add - packs to be added to the world (after apply) -- add - packs to be added to the world (after apply)
@ -178,40 +179,40 @@ function place_pack(panel, packinfo, callback, position_func)
end end
end end
local Version = {}; local Version = {}
function Version.matches_pattern(version) function Version.matches_pattern(version)
for _, letter in string.gmatch(version, "%.+") do local t = string.split(version, ".")
if type(letter) ~= "number" or letter ~= "." then if #t ~= 2 and #t ~= 3 then return false end
return false;
end
local t = string.split(version, "."); for i = 1, #t do
local matched = string.match(t[i], "^%d+$")
return #t == 2 or #t == 3; if not matched then return false end
end end
return true
end end
function Version.__equal(ver1, ver2) function Version.__equal(ver1, ver2)
return ver1[1] == ver2[1] and ver1[2] == ver2[2] and ver1[3] == ver2[3]; return ver1[1] == ver2[1] and ver1[2] == ver2[2] and ver1[3] == ver2[3]
end end
function Version.__greater(ver1, ver2) function Version.__greater(ver1, ver2)
if ver1[1] ~= ver2[1] then return ver1[1] > ver2[1] end; if ver1[1] ~= ver2[1] then return ver1[1] > ver2[1] end
if ver1[2] ~= ver2[2] then return ver1[2] > ver2[2] end; if ver1[2] ~= ver2[2] then return ver1[2] > ver2[2] end
return ver1[3] > ver2[3]; return ver1[3] > ver2[3]
end end
function Version.__less(ver1, ver2) function Version.__less(ver1, ver2)
return Version.__greater(ver2, ver1); return Version.__greater(ver2, ver1)
end end
function Version.__greater_or_equal(ver1, ver2) function Version.__greater_or_equal(ver1, ver2)
return not Version.__less(ver1, ver2); return not Version.__less(ver2, ver1)
end end
function Version.__less_or_equal(ver1, ver2) function Version.__less_or_equal(ver1, ver2)
return not Version.__greater(ver1, ver2); return not Version.__greater(ver1, ver2)
end end
Version.operators = { Version.operators = {
@ -223,40 +224,39 @@ Version.operators = {
} }
function Version.compare(op, ver1, ver2) function Version.compare(op, ver1, ver2)
ver1 = string.split(ver1, "."); ver1 = string.split(ver1, ".")
ver2 = string.split(ver2, "."); ver2 = string.split(ver2, ".")
local comparison_func = Version.operators[op]; local comparison_func = Version.operators[op];
if comparison_func then if comparison_func then
return comparison_func(ver1, ver2); return comparison_func(ver1, ver2)
else else
return false; return false
end end
end end
function Version.parse(version) function Version.parse(version)
local op = string.sub(version, 1, 2); local op = string.sub(version, 1, 2)
if op == ">=" or op == "<=" then if op == ">=" or op == "<=" then
return op, string.sub(version, #op + 1); return op, string.sub(version, #op + 1)
end end
op = string.sub(version, 1, 1); op = string.sub(version, 1, 1);
if op == ">" or op == "<" then if op == ">" or op == "<" then
return op, string.sub(version, #op + 1); return op, string.sub(version, #op + 1)
end end
return "=", version; return "=", version
end end
local function compare_version(dependent_version, actual_version) local function compare_version(op, dependent_version, actual_version)
if Version.matches_pattern(dependent_version) and Version.matches_pattern(actual_version) then if Version.matches_pattern(dependent_version) and Version.matches_pattern(actual_version) then
local op, dep_ver = Version.parse_version(dependent_version); return Version.compare(op, dep_ver, actual_version)
Version.compare(op, dep_ver, actual_version);
elseif dependent_version == "*" or dependent_version == actual_version then elseif dependent_version == "*" or dependent_version == actual_version then
return true; return true
else else
return false; return false
end end
end end
@ -276,15 +276,15 @@ function check_dependencies(packinfo)
) )
end end
local dep_pack = pack.get_info(depid); local dep_pack = pack.get_info(depid)
local op, ver = Version.parse(depver)
if not compare_version(depver, dep_pack.version) then if not compare_version(op, ver, dep_pack.version) then
local op, ver = Version.parse(depver)
return string.format( return string.format(
"%s: %s != %s (%s)", "%s: %s != %s (%s)",
gui.str("error.dependency-version-not-met"), gui.str("error.dependency-version-not-met"),
dep_pack.version, ver, depid dep_pack.version, ver, depid
); )
end end
if table.has(packs_installed, packinfo.id) then if table.has(packs_installed, packinfo.id) then

View file

@ -11,7 +11,7 @@
@World generator @World generator
</button> </button>
<button id='content_btn' onclick='save_state() menu.page="content"'/> <button id='content_btn' onclick='save_state() menu.page="content"'/>
<button onclick='create_world()' margin='1,20,1,1'> <button onclick='create_world()' margin='0,20,0,1'>
@Create World @Create World
</button> </button>
<button onclick='menu:back()'>@Back</button> <button onclick='menu:back()'>@Back</button>

View file

@ -1,6 +1,6 @@
<container id='pack_%{id}' onclick='%{callback}' size='0,80' color='#00000040' hover-color='#00000080' position-func="%{position_func}" z-index="%{index}"> <container id='pack_%{id}' onclick='%{callback}' size='0,80' color='#00000040' hover-color='#00000080' position-func="%{position_func}" z-index="%{index}">
<label color='#FFFFFF80' size='300,25' align='right' gravity='top-right'> <label color='#FFFFFF80' size='340,25' align='right' gravity='top-right'>
[%{id_verbose}] [%{id_verbose} %{version}]
</label> </label>
<label pos='78,6'>%{title}</label> <label pos='78,6'>%{title}</label>
<label if='%{creator}' color='#CCFFE5B2' size='300,20' align='right' <label if='%{creator}' color='#CCFFE5B2' size='300,20' align='right'

View file

@ -1,4 +1,4 @@
@box from (0,0,0) to (1,0.5,1) delete (top,south) @box from (0,0,0) to (1,0.5,1) delete (top,north)
@box from (0,0.5,0) to (1,1,0.5) delete (bottom,south) @box from (0,0.5,0) to (1,1,0.5) delete (bottom,north)
@rect from (0,0.5,0.5) right (1,0,0) up (0,0,0.5) texture "$2" @rect from (0,0.5,0.5) right (1,0,0) up (0,0,0.5) texture "$2"
@rect from (0,0,0) right (1,0,0) up (0,1,0) texture "$1" @rect from (0,0,0) right (1,0,0) up (0,1,0) texture "$1"

View file

@ -4,12 +4,14 @@ local FFI = ffi
FFI.cdef[[ FFI.cdef[[
void* malloc(size_t); void* malloc(size_t);
void* realloc(void*, size_t);
void free(void*); void free(void*);
typedef struct { typedef struct {
unsigned char* bytes; unsigned char* bytes;
int size; int size;
int capacity; int capacity;
} bytearray_t; } bytearray_t;
void* memmove(void*, const void*, size_t);
]] ]]
local free = FFI.C.free local free = FFI.C.free
@ -22,12 +24,33 @@ local GC_THRESHOLD = 50 * 1024 * 1024
local function malloc(size) local function malloc(size)
local raw = FFI.C.malloc(size) local raw = FFI.C.malloc(size)
if raw == nil then if not raw then
return nil error("failed memory allocation of " .. size .. " bytes")
end end
allocated_bytes = allocated_bytes + size allocated_bytes = allocated_bytes + size
if allocated_bytes >= GC_THRESHOLD then
collectgarbage("step", 200)
allocated_bytes = 0
end
return raw
end
local function realloc(buff, size, throwerror)
if throwerror == nil then throwerror = true end
if size <= 0 then
free(buff)
return
end
local raw = FFI.C.realloc(buff, size)
if not raw and throwerror then
error("failed memory reallocation of block " .. buff .. " to new size " .. size)
end
allocated_bytes = allocated_bytes + size
if allocated_bytes >= GC_THRESHOLD then if allocated_bytes >= GC_THRESHOLD then
collectgarbage("step", 200) collectgarbage("step", 200)
allocated_bytes = 0 allocated_bytes = 0
@ -38,23 +61,17 @@ end
local function grow_buffer(self, elems) local function grow_buffer(self, elems)
local new_capacity = math.ceil(self.capacity / 0.75 + elems) local new_capacity = math.ceil(self.capacity / 0.75 + elems)
local prev = self.bytes self.bytes = realloc(self.bytes, new_capacity)
self.bytes = malloc(new_capacity)
FFI.copy(self.bytes, prev, self.size)
self.capacity = new_capacity self.capacity = new_capacity
free(prev)
end end
local function trim_buffer(self) local function trim_buffer(self)
if self.size == self.capacity then if self.size == self.capacity then return end
return local new_bytes = realloc(self.bytes, self.size, false)
if new_bytes then
self.bytes = new_bytes
self.capacity = self.size
end end
local size = self.size
local prev = self.bytes
self.bytes = malloc(size)
FFI.copy(self.bytes, prev, self.size)
self.capacity = size
free(prev)
end end
local function count_elements(b) local function count_elements(b)
@ -125,15 +142,21 @@ local function clear(self)
self.size = 0 self.size = 0
end end
local function reserve(self, new_capacity) local function fill(self, index, size, byte)
if new_capacity <= self.capacity then if index ~= nil and size ~= nil and size > 0 then
return if index < 1 or index + size - 1 > self.size then
error("selected fill range is out of array bounds")
end
FFI.fill(self.bytes + index - 1, size, byte)
else
FFI.fill(self.bytes, self.size, byte)
end end
local prev = self.bytes end
self.bytes = malloc(new_capacity)
FFI.copy(self.bytes, prev, self.size) local function reserve(self, new_capacity)
if new_capacity <= self.capacity then return end
self.bytes = realloc(self.bytes, new_capacity)
self.capacity = new_capacity self.capacity = new_capacity
free(prev)
end end
local function get_capacity(self) local function get_capacity(self)
@ -150,22 +173,46 @@ local function slice(self, offset, length)
length = self.size - offset + 1 length = self.size - offset + 1
end end
local buffer = malloc(length) local buffer = malloc(length)
if not buffer then
error("malloc(" .. length .. ") returned NULL")
end
FFI.copy(buffer, self.bytes + (offset - 1), length) FFI.copy(buffer, self.bytes + (offset - 1), length)
return bytearray_type(buffer, length, length) return bytearray_type(buffer, length, length)
end end
local function copy(self, srcindex, dst, dstindex, size)
if size <= 0 then error("size of byte range must be positive non-zero integer") end
if srcindex < 1 or srcindex + size - 1 > self.size then
error("specified source byte range is out of source array bounds")
end
if dstindex < 1 or dstindex + size - 1 > dst.size then
error("specified destination byte range is out of destination array bounds")
end
FFI.copy(dst.bytes + dstindex - 1, self.bytes + srcindex - 1, size)
end
local function move(self, fromindex, toindex, size)
if size <= 0 then error("size of byte range must be positive non-zero integer") end
if fromindex < 1 or fromindex + size - 1 > self.size then
error("specified source byte range is out of array bounds")
end
if toindex < 1 or toindex + size - 1 > self.size then
error("specified destination byte range is out of array bounds")
end
FFI.C.memmove(self.bytes + toindex - 1, self.bytes + fromindex - 1, size)
end
local bytearray_methods = { local bytearray_methods = {
append=append, append = append,
insert=insert, insert = insert,
remove=remove, remove = remove,
trim=trim_buffer, trim = trim_buffer,
clear=clear, clear = clear,
reserve=reserve, reserve = reserve,
get_capacity=get_capacity, get_capacity = get_capacity,
slice=slice, slice = slice,
fill = fill,
copy = copy,
move = move
} }
local bytearray_mt = { local bytearray_mt = {
@ -256,7 +303,8 @@ local function FFIBytearray_as_string(bytes)
end end
end end
local function create_FFIview_class(name, typename, typesize) local function create_FFIview_class(name, typename)
local typesize = FFI.sizeof(typename)
local ptrtype = typename .. "*" local ptrtype = typename .. "*"
local FFIview_mt = { local FFIview_mt = {
__index = function(self, key) __index = function(self, key)
@ -291,27 +339,44 @@ local function create_FFIview_class(name, typename, typesize)
return i, ptr[i - 1] return i, ptr[i - 1]
end end
end end
end end,
} }
return function (bytes) return setmetatable({}, {
local x = setmetatable({ __call = function (self, bytes)
bytes=bytes, local x = setmetatable({
}, FFIview_mt) bytes=bytes,
return x }, FFIview_mt)
end return x
end,
__index = {
typesize = function()
return typesize
end
}
})
end end
local FFII16view = create_FFIview_class("FFII16view", "int16_t", 2) local FFII8view = create_FFIview_class("FFII8view", "int8_t")
local FFIU16view = create_FFIview_class("FFIU16view", "uint16_t", 2) local FFII16view = create_FFIview_class("FFII16view", "int16_t")
local FFII32view = create_FFIview_class("FFII32view", "int32_t", 4) local FFIU16view = create_FFIview_class("FFIU16view", "uint16_t")
local FFIU32view = create_FFIview_class("FFIU32view", "uint32_t", 4) local FFII32view = create_FFIview_class("FFII32view", "int32_t")
local FFIU32view = create_FFIview_class("FFIU32view", "uint32_t")
local FFII64view = create_FFIview_class("FFII64view", "int64_t")
local FFIU64view = create_FFIview_class("FFIU64view", "uint64_t")
local FFIFLTview = create_FFIview_class("FFIFLTview", "float")
local FFIDBLview = create_FFIview_class("FFIDBLview", "double")
return { return {
FFIBytearray = setmetatable(FFIBytearray, FFIBytearray), FFIBytearray = setmetatable(FFIBytearray, FFIBytearray),
FFIBytearray_as_string = FFIBytearray_as_string, FFIBytearray_as_string = FFIBytearray_as_string,
FFIBytearray_as_ptr = FFIBytearray_as_ptr, FFIBytearray_as_ptr = FFIBytearray_as_ptr,
FFII8view = FFII8view,
FFIU16view = FFIU16view, FFIU16view = FFIU16view,
FFII16view = FFII16view, FFII16view = FFII16view,
FFIU32view = FFIU32view, FFIU32view = FFIU32view,
FFII32view = FFII32view, FFII32view = FFII32view,
FFIU64view = FFIU64view,
FFII64view = FFII64view,
FFIFLTview = FFIFLTview,
FFIDBLview = FFIDBLview,
} }

View file

@ -0,0 +1,38 @@
local FFI = ffi
FFI.cdef[[
unsigned long long strtoull(const char* nptr, char** endptr, int base);
long long strtoll(const char* nptr, char** endptr, int base);
]]
local this = {}
local function create_integral_type(ctype_name, signed)
local parse_func = signed and FFI.C.strtoll or FFI.C.strtoull
return function(value, base)
value = value or 0
local typename = type(value)
if typename == "number" then
return FFI.cast(ctype_name, value)
elseif typename == "string" then
if type(base) ~= "number" then
base = 10
end
return parse_func(value, nil, base)
end
error(string.format("invalid value type: %s", typename))
end
end
this.uint8 = create_integral_type("uint8_t", false)
this.uint16 = create_integral_type("uint16_t", false)
this.uint32 = create_integral_type("uint32_t", false)
this.uint64 = create_integral_type("uint64_t", false)
this.int8 = create_integral_type("int8_t", true)
this.int16 = create_integral_type("int16_t", true)
this.int32 = create_integral_type("int32_t", true)
this.int64 = create_integral_type("int64_t", true)
return this

View file

@ -148,3 +148,29 @@ function error(message, level)
end end
__error(message, level) __error(message, level)
end end
local LoggerClass = {
__index = {
info = function (self, text)
debug.log(string.format("[%s] %s", self.name, text))
end,
warning = function (self, text)
debug.warning(string.format("[%s] %s", self.name, text))
end,
error = function (self, text)
debug.error(string.format("[%s] %s", self.name, text))
end
}
}
local _getinfo = debug.getinfo
function debug.Logger(name)
if not name then
local source = _getinfo(2, "S").source
name = file.remove_ext(source)
else
name = string.format("%s:%s", file.prefix(_getinfo(2, "S").source), name)
end
return setmetatable({name=name}, LoggerClass)
end

View file

@ -14,6 +14,7 @@ function events.on(event, func)
events.handlers[event] = {} events.handlers[event] = {}
end end
table.insert(events.handlers[event], func) table.insert(events.handlers[event], func)
return func
end end
function events.reset(event, func) function events.reset(event, func)
@ -24,6 +25,14 @@ function events.reset(event, func)
end end
end end
function events.remove(event, handler)
local handlers = events.handlers[event]
if not handlers then
return
end
table.remove_value(handlers, handler)
end
function events.remove_by_prefix(prefix) function events.remove_by_prefix(prefix)
for name, handlers in pairs(events.handlers) do for name, handlers in pairs(events.handlers) do
local actualname = name local actualname = name

View file

@ -23,6 +23,10 @@ function file.parent(path)
return dir return dir
end end
function file.remove_ext(path)
return string.format("%s/%s", file.parent(path), file.stem(path))
end
function file.path(path) function file.path(path)
local pos = path:find(':') local pos = path:find(':')
return path:sub(pos + 1) return path:sub(pos + 1)

View file

@ -35,3 +35,89 @@ function math.sum(...)
return sum return sum
end end
function math.sign(x)
return (x > 0) and 1 or (x < 0 and -1 or 0)
end
local noise_period = 3021
local noise_rands = {}
for i=1,noise_period do
table.insert(noise_rands, math.random() * 2.0 - 1.0)
end
local function smoothstep(t)
return t * t * (3 - 2 * t)
end
local function sample_noise(x)
x = x % noise_period
local left_cell = math.floor(x)
local right_cell = (left_cell + 1) % noise_period
local t = smoothstep(x - left_cell)
return noise_rands[left_cell + 1] * (1.0 - t)
+ noise_rands[right_cell + 1] * t
end
function math.noise(x, octaves)
x = math.abs(x)
octaves = octaves or 1
local value = 0.0
local mul = 1.0
for i=1,octaves do
value = value * (1.0 - mul) + sample_noise(x + 37 * i) * mul
x = x * 2.0
mul = mul * 0.5
end
return value
end
local function lerp(a, b, t)
return a + (b - a) * t
end
local function rand_2d(x, y)
return noise_rands[math.floor(x * 317 + y) % noise_period + 1]
end
local function sample_noise2d(x, y)
x = math.abs(x)
y = math.abs(y)
local x0 = math.floor(x)
local y0 = math.floor(y)
local x1 = x0 + 1
local y1 = y0 + 1
local tx = x - x0
local ty = y - y0
local sx = smoothstep(tx)
local sy = smoothstep(ty)
local c00 = rand_2d(x0, y0)
local c10 = rand_2d(x1, y0)
local c01 = rand_2d(x0, y1)
local c11 = rand_2d(x1, y1)
local bottom = lerp(c00, c10, sx)
local top = lerp(c01, c11, sx)
return lerp(bottom, top, sy)
end
function math.noise2d(x, y, octaves)
octaves = octaves or 1
local value = 0.0
local mul = 1.0
for i=1,octaves do
value = value * (1.0 - mul)
+ sample_noise2d(x + 37 * i, y + 73 * i) * mul
x = x * 2.0
y = y * 2.0
mul = mul * 0.5
end
return value
end

View file

@ -59,11 +59,7 @@ end
function table.extend(t1, t2) function table.extend(t1, t2)
for i, v in pairs(t2) do for i, v in pairs(t2) do
if type(i) == "number" then t1[i] = v
t1[#t1 + 1] = v
else
t1[i] = v
end
end end
return t1 return t1
@ -201,3 +197,11 @@ function table.tostring(t)
end end
return s..']' return s..']'
end end
function table.keys(t)
local keys = {}
for key, _ in pairs(t) do
table.insert(keys, key)
end
return keys
end

View file

@ -77,15 +77,18 @@ function Element.new(docname, name)
end end
-- the engine automatically creates an instance for every ui document (layout) -- the engine automatically creates an instance for every ui document (layout)
local Document = {} local Document = {
function Document.new(docname) __index=function(self, k)
return setmetatable({name=docname}, { if type(k) ~= "string" then
__index=function(self, k) error("element id is not a string")
local elem = Element.new(self.name, k)
rawset(self, k, elem)
return elem
end end
}) local elem = Element.new(self.name, k)
rawset(self, k, elem)
return elem
end
}
function Document.new(docname)
return setmetatable({name=docname}, Document)
end end
local RadioGroup = {} local RadioGroup = {}
@ -189,13 +192,15 @@ function gui.ask(text, on_yes, on_no, yes_text, no_text)
<container id='%s' color='#00000080' size-func='-1,-1' z-index='10'> <container id='%s' color='#00000080' size-func='-1,-1' z-index='10'>
<panel color='#507090E0' size='600' padding='16' <panel color='#507090E0' size='600' padding='16'
gravity='center-center' interval='4'> gravity='center-center' interval='4'>
<label margin='4'>%s</label> <label margin='4' markup='md'>%s</label>
<button onclick='DATA.on_yes()'>@%s</button> <button onclick='DATA.on_yes()'>@%s</button>
<button onclick='DATA.on_no()'>@%s</button> <button onclick='DATA.on_no()'>@%s</button>
</panel> </panel>
</container> </container>
]], id, string.escape_xml(text)), {on_yes=yes_callback, on_no=no_callback}, ]], id,
string.escape_xml(yes_text), string.escape_xml(no_text)) string.escape_xml(text),
string.escape_xml(yes_text),
string.escape_xml(no_text)), {on_yes=yes_callback, on_no=no_callback})
input.add_callback("key:escape", no_callback, gui.root[id]) input.add_callback("key:escape", no_callback, gui.root[id])
end end

View file

@ -1,4 +1,4 @@
local rules = {nexid = 1, rules = {}} local rules = {nextid = 1, rules = {}}
function rules.get_rule(name) function rules.get_rule(name)
local rule = rules.rules[name] local rule = rules.rules[name]
@ -27,13 +27,13 @@ end
function rules.reset(name) function rules.reset(name)
local rule = rules.get_rule(name) local rule = rules.get_rule(name)
rules.set(rule.default) rules.set(name, rule.default)
end end
function rules.listen(name, handler) function rules.listen(name, handler)
local rule = rules.get_rule(name) local rule = rules.get_rule(name)
local id = rules.nexid local id = rules.nextid
rules.nextid = rules.nexid + 1 rules.nextid = rules.nextid + 1
rule.listeners[utf8.encode(id)] = handler rule.listeners[utf8.encode(id)] = handler
return id return id
end end

View file

@ -129,7 +129,7 @@ return {
goto continue goto continue
end end
for _, component in pairs(entity.components) do for _, component in pairs(entity.components) do
local callback = component.on_update local callback = rawget(component, "on_update")
if not component.__disabled and callback then if not component.__disabled and callback then
local result, err = pcall(callback, tps) local result, err = pcall(callback, tps)
if err then if err then
@ -143,7 +143,7 @@ return {
physics_update = function(delta) physics_update = function(delta)
for uid, entity in pairs(entities) do for uid, entity in pairs(entities) do
for _, component in pairs(entity.components) do for _, component in pairs(entity.components) do
local callback = component.on_physics_update local callback = rawget(component, "on_physics_update")
if not component.__disabled and callback then if not component.__disabled and callback then
local result, err = pcall(callback, delta) local result, err = pcall(callback, delta)
if err then if err then
@ -156,7 +156,7 @@ return {
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
local callback = component.on_render local callback = rawget(component, "on_render")
if not component.__disabled and callback then if not component.__disabled and callback then
local result, err = pcall(callback, delta) local result, err = pcall(callback, delta)
if err then if err then

View file

@ -0,0 +1,131 @@
local io_stream = require "core:io_stream"
local lib = { }
local buffers = { }
local positions = { }
local next_descriptor = 0
local function open_descriptor(buffer)
next_descriptor = next_descriptor + 1
buffers[next_descriptor] = buffer
positions[next_descriptor] = 1
return next_descriptor
end
local function require_descriptor(descriptor)
if not buffers[descriptor] then
error("unknown descriptor")
end
end
function lib.read(descriptor, length)
require_descriptor(descriptor)
local buf = buffers[descriptor]
local buf_length = #buf
local pos = positions[descriptor]
local to_read = math.min(buf_length - pos + 1, length)
if to_read <= 0 then
return Bytearray()
end
local segment = buf:slice(pos, to_read)
positions[descriptor] = pos + to_read
return segment
end
function lib.write(descriptor, data)
require_descriptor(descriptor)
local buf = buffers[descriptor]
local pos = positions[descriptor]
local buf_length = #buf
local data_length = #data
local end_pos = pos + data_length - 1
-- size ensuring
if end_pos > buf_length then
for i = buf_length + 1, end_pos do
buf[i] = 0
end
end
for i = 1, data_length do
buf[i + pos - 1] = data[i]
end
positions[descriptor] = pos + data_length
end
function lib.seek(descriptor, mode, offset)
require_descriptor(descriptor)
local buf = buffers[descriptor]
local buf_length = #buf
local base
if mode == 'b' then
base = 1
elseif mode == 'c' then
base = positions[descriptor]
elseif mode == 'e' then
base = buf_length + 1
else error('invalid seek mode') end
local new_pos = base + offset
if new_pos < 1 then
error('failed to seek stream')
end
positions[descriptor] = new_pos
end
function lib.tell(descriptor)
require_descriptor(descriptor)
return positions[descriptor]
end
function lib.available(descriptor)
require_descriptor(descriptor)
local buf = buffers[descriptor]
local pos = positions[descriptor]
return math.max(#buf - pos + 1, 0)
end
function lib.is_alive(descriptor)
return buffers[descriptor] ~= nil
end
function lib.close(descriptor)
require_descriptor(descriptor)
buffers[descriptor] = nil
positions[descriptor] = nil
end
return function(buffer, binary_mode)
if binary_mode == nil then
binary_mode = true
end
return io_stream.new(
open_descriptor(buffer),
binary_mode,
lib
)
end

View file

@ -4,14 +4,27 @@ local lib = {
read = file.__read_descriptor, read = file.__read_descriptor,
write = file.__write_descriptor, write = file.__write_descriptor,
seek = file.__seek_descriptor, seek = file.__seek_descriptor,
tell = file.__tell_descriptor,
flush = file.__flush_descriptor, flush = file.__flush_descriptor,
available = file.__available_descriptor,
is_alive = file.__has_descriptor, is_alive = file.__has_descriptor,
close = file.__close_descriptor close = file.__close_descriptor
} }
local __open_descriptor = file.__open_descriptor
file.__open_descriptor = nil
file.__read_descriptor = nil
file.__write_descriptor = nil
file.__seek_descriptor = nil
file.__tell_descriptor = nil
file.__flush_descriptor = nil
file.__available_descriptor = nil
file.__has_descriptor = nil
file.__close_descriptor = nil
return function(path, mode) return function(path, mode)
return io_stream.new( return io_stream.new(
file.__open_descriptor(path, mode), __open_descriptor(path, mode),
mode:find('b') ~= nil, mode:find('b') ~= nil,
lib lib
) )

View file

@ -9,6 +9,7 @@ int close(int fd);
ssize_t read(int fd, void *buf, size_t count); ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count); ssize_t write(int fd, const void *buf, size_t count);
int fcntl(int fd, int cmd, ...); int fcntl(int fd, int cmd, ...);
int ioctl(int fd, unsigned long request, ...);
const char *strerror(int errnum); const char *strerror(int errnum);
]] ]]
@ -20,6 +21,7 @@ local O_WRONLY = 0x1
local O_RDWR = 0x2 local O_RDWR = 0x2
local O_NONBLOCK = 0x800 local O_NONBLOCK = 0x800
local F_GETFL = 3 local F_GETFL = 3
local FIONREAD = 0x541B
local function getError() local function getError()
local err = FFI.errno() local err = FFI.errno()
@ -58,12 +60,16 @@ function lib.write(fd, bytearray)
end end
end end
function lib.seek(fd, mode, offset) function lib.available(fd)
error("cannot seek the named pipe") if fd == nil or fd < 0 then return 0 end
end
function lib.flush(fd) local bytes_ready = FFI.new("int[1]", 0)
-- no flush on unix
if C.ioctl(fd, FIONREAD, bytes_ready) == -1 then
return 0
end
return tonumber(bytes_ready[0])
end end
function lib.is_alive(fd) function lib.is_alive(fd)

View file

@ -97,14 +97,26 @@ function lib.write(handle, bytearray)
end end
end end
function lib.seek(handle, mode, offset)
error("cannot seek the named pipe")
end
function lib.flush(handle) function lib.flush(handle)
C.FlushFileBuffers(handle) C.FlushFileBuffers(handle)
end end
function lib.available(handle)
if handle == nil or handle == INVALID_HANDLE_VALUE then
return 0
end
local bytes_available = FFI.new("DWORD[1]")
local success = C.PeekNamedPipe(handle, nil, 0, nil, bytes_available, nil)
if success == 0 then
return 0
end
return tonumber(bytes_available[0])
end
function lib.is_alive(handle) function lib.is_alive(handle)
if handle == nil or handle == INVALID_HANDLE_VALUE then if handle == nil or handle == INVALID_HANDLE_VALUE then
return false return false

View file

@ -0,0 +1,64 @@
local io_stream = require "core:io_stream"
local lib = { }
local sockets = { }
local next_descriptor = 0
local function open_descriptor(socket)
next_descriptor = next_descriptor + 1
sockets[next_descriptor] = socket
return next_descriptor
end
local function require_descriptor(descriptor)
if not sockets[descriptor] then
error("unknown descriptor")
end
end
function lib.read(descriptor, length)
require_descriptor(descriptor)
return sockets[descriptor]:recv(length)
end
function lib.write(descriptor, data)
require_descriptor(descriptor)
sockets[descriptor]:send(data)
end
function lib.available(descriptor)
require_descriptor(descriptor)
return sockets[descriptor]:available()
end
function lib.is_alive(descriptor)
local socket = sockets[descriptor]
return socket ~= nil and socket:is_alive()
end
function lib.close(descriptor)
require_descriptor(descriptor)
sockets[descriptor]:close()
sockets[descriptor] = nil
end
return function(socket, binary_mode)
if binary_mode == nil then
binary_mode = true
end
return io_stream.new(
open_descriptor(socket),
binary_mode,
lib
)
end

View file

@ -0,0 +1,139 @@
local __app = __vc_app
local function get_node_center(node)
local pos = node.wpos
local size = node.size
return pos[1] + size[1] / 2, pos[2] + size[2] / 2
end
local function types_check_enum(value, enum)
if value == nil then
return true, nil
end
if type(value) ~= "string" then
return false, string.format("string expected [%s]", table.concat(table.keys(enum), "|"))
end
return true, nil
end
local function types_check_number(value)
if value == nil then
return true, nil
end
local value_type = type(value)
if value_type ~= "number" then
return false, string.format("number expected, got %s", value_type)
end
end
local function check_option(t, key, func, ...)
local value = t[key]
if value == nil then
return nil
end
local success, message = func(value, ...)
if not success then
error(string.format("invalid option '%s': %s", key, message))
end
end
local buttons_enum = {
left = 0,
right = 1,
middle = 2,
}
local no_options = {}
local _tick = __app.tick
local _set_button_pressed = test.set_button_pressed
local _set_key_pressed = test.set_key_pressed
local _enter_text = test.enter_text
test.set_button_pressed = nil
test.set_key_pressed = nil
test.enter_text = nil
function test.click(node, options)
options = options or no_options
check_option(options, 'button', types_check_enum, buttons_enum)
check_option(options, 'timeout', types_check_number)
local button = buttons_enum[options.button or 'left']
local timeout = options.timeout or 1000
local tm = time.precise_time()
while (time.precise_time() - tm) * 1000 < timeout do
if node.exists then
break
end
_tick()
end
if not node.exists then
error(string.format("timeout %s ms exceeded: element %s does not exists",
math.floor(timeout), string.escape(rawget(node, 'name'))))
end
local center_x, center_y = get_node_center(node)
_set_button_pressed(button, center_x, center_y, true)
_tick()
_set_button_pressed(button, center_x, center_y, false)
_tick()
end
-- TODO: replace with resolving in C++ side
function test.find_by_attr(node, attr, value)
if getmetatable(node) == Document then
return test.find_by_attr(node.root, attr, value)
end
if node[attr] == value then
return node
end
local i = 1
while true do
local subnode = node[i]
if subnode == nil then
break
end
local found = test.find_by_attr(subnode, attr, value)
if found then
return found
end
i = i + 1
end
end
function test.find_by_text(node, text)
return test.find_by_attr(node, "text", text)
end
function test.find_by_attr_presence(node, attr)
if node[attr] then
return node
end
local i = 1
while true do
local subnode = node[i]
if subnode == nil then
break
end
local found = test.find_by_attr_presence(subnode, attr)
if found then
return found
end
i = i + 1
end
end
function test.fill(node, text, options)
test.click(node, options)
__app.tick()
_enter_text(text)
test.press("enter")
end
function test.press(key)
_set_key_pressed(key, true)
__app.tick()
_set_key_pressed(key, false)
__app.tick()
end

View file

@ -0,0 +1,21 @@
local M = {}
function M.share_default_func(invid, slotid)
local blockinv = hud.get_block_inventory()
local playerinv = player.get_inventory(hud.get_player())
if blockinv ~= 0 then
if invid == blockinv then
inventory.move(blockinv, slotid, playerinv)
else
inventory.move(invid, slotid, blockinv)
end
elseif rules.get("allow-content-access") then
inventory.set(invid, slotid, 0, 0)
elseif slotid < 10 then
inventory.move_range(invid, slotid, invid, 10)
else
inventory.move_range(invid, slotid, invid, 0, 9)
end
end
return M

View file

@ -25,13 +25,13 @@ local ALL_FLUSH_MODES = {
local CR = string.byte('\r') local CR = string.byte('\r')
local LF = string.byte('\n') local LF = string.byte('\n')
local function readFully(result, readFunc) local function read_fully(result, read_func)
local isTable = type(result) == "table" local isTable = type(result) == "table"
local buf local buf
repeat repeat
buf = readFunc(MAX_BUFFER_SIZE) buf = read_func(MAX_BUFFER_SIZE)
if isTable then if isTable then
for i = 1, #buf do for i = 1, #buf do
@ -42,41 +42,46 @@ local function readFully(result, readFunc)
end end
--[[ --[[
descriptor - descriptor of stream for provided I/O library descriptor - descriptor of stream for provided I/O library
binaryMode - if enabled, most methods will expect bytes instead of strings binaryMode - if enabled, most methods will expect bytes instead of strings
ioLib - I/O library. Should include the following functions: ioLib - I/O library. Should include the following functions:
read(descriptor: int, length: int) -> Bytearray read(descriptor: int, length: int) -> Bytearray
May return bytearray with a smaller size if bytes have not arrived yet or have run out May return bytearray with a smaller size if bytes have not arrived yet or have run out
May throw error if descriptor isn't readable
write(descriptor: int, data: Bytearray) write(descriptor: int, data: Bytearray)
flush(descriptor: int) May throw error if descriptor isn't writeable
[optional] seek(descriptor: int, mode: string, offset: int)
Mode may be 'b' (relative begin), 'c' (relative current), 'e' (relative end + 1)
[optional] tell(descriptor: int) -> int
[optional] flush(descriptor: int)
[optional] available(descriptor: int) -> int
is_alive(descriptor: int) -> bool is_alive(descriptor: int) -> bool
close(descriptor: int) close(descriptor: int)
--]] --]]
function io_stream.new(descriptor, binaryMode, ioLib, mode, flushMode) function io_stream.new(descriptor, binary_mode, io_lib, mode, flush_mode)
mode = mode or DEFAULT_MODE mode = mode or DEFAULT_MODE
flushMode = flushMode or FLUSH_MODE_ALL flush_mode = flush_mode or FLUSH_MODE_ALL
local self = setmetatable({}, io_stream) local self = setmetatable({}, io_stream)
self.descriptor = descriptor self.descriptor = descriptor
self.binaryMode = binaryMode self.binary_mode = binary_mode
self.maxBufferSize = MAX_BUFFER_SIZE self.max_buffer_size = MAX_BUFFER_SIZE
self.ioLib = ioLib self.io_lib = io_lib
self:set_mode(mode) self:set_mode(mode)
self:set_flush_mode(flushMode) self:set_flush_mode(flush_mode)
return self return self
end end
function io_stream:is_binary_mode() function io_stream:is_binary_mode()
return self.binaryMode return self.binary_mode
end end
function io_stream:set_binary_mode(binaryMode) function io_stream:set_binary_mode(binary_mode)
self.binaryMode = binaryMode ~= nil self.binary_mode = binary_mode ~= nil
end end
function io_stream:get_mode() function io_stream:get_mode()
@ -88,124 +93,141 @@ function io_stream:set_mode(mode)
error("invalid stream mode: "..mode) error("invalid stream mode: "..mode)
end end
if self.mode == BUFFERED_MODE then if self.write_buffer then
self.writeBuffer:clear() self.write_buffer:clear()
self.readBuffer:clear() self.write_buffer = nil
end end
if mode == BUFFERED_MODE and not self.writeBuffer then if self.read_buffer then
self.writeBuffer = Bytearray() self.read_buffer:clear()
self.readBuffer = Bytearray() self.read_buffer = nil
end
if mode == BUFFERED_MODE then
self.write_buffer = Bytearray(self.max_buffer_size)
self.read_buffer = Bytearray(self.max_buffer_size)
end end
self.mode = mode self.mode = mode
end end
function io_stream:get_flush_mode() function io_stream:get_flush_mode()
return self.flushMode return self.flush_mode
end end
function io_stream:set_flush_mode(flushMode) function io_stream:set_flush_mode(flush_mode)
if not table.has(ALL_FLUSH_MODES, flushMode) then if not table.has(ALL_FLUSH_MODES, flush_mode) then
error("invalid flush mode: "..flushMode) error("invalid flush mode: " .. flush_mode)
end end
self.flushMode = flushMode self.flush_mode = flush_mode
end end
function io_stream:get_max_buffer_size() function io_stream:get_max_buffer_size()
return self.maxBufferSize return self.max_buffer_size
end end
function io_stream:set_max_buffer_size(maxBufferSize) function io_stream:set_max_buffer_size(max_buffer_size)
self.maxBufferSize = maxBufferSize self.max_buffer_size = max_buffer_size
self.write_buffer = Bytearray(self.max_buffer_size)
self.read_buffer = Bytearray(self.max_buffer_size)
end end
function io_stream:available(length) function io_stream:available(length)
local available = self.io_lib.available and self.io_lib.available(self.descriptor) or 0
if self.mode == BUFFERED_MODE then if self.mode == BUFFERED_MODE then
self:__update_read_buffer() available = available + #self.read_buffer
end
if not length then if not length then
return #self.readBuffer return available
else else
return #self.readBuffer >= length return available >= length
end
end end
end end
function io_stream:__update_read_buffer() function io_stream:__read(length, from_read_fully)
local readed = Bytearray()
readFully(readed, function(length) return self.ioLib.read(self.descriptor, length) end)
self.readBuffer:append(readed)
if #self.readBuffer > self.maxBufferSize then
error "buffer overflow"
end
end
function io_stream:__read(length)
if self.mode == YIELD_MODE then if self.mode == YIELD_MODE then
if from_read_fully then
return self.io_lib.read(self.descriptor, length)
end
local buffer = Bytearray() local buffer = Bytearray()
while #buffer < length do while #buffer < length do
buffer:append(self.ioLib.read(self.descriptor, length - #buffer)) buffer:append(self.io_lib.read(self.descriptor, length - #buffer))
if #buffer < length then coroutine.yield() end if #buffer < length then coroutine.yield() end
end end
return buffer return buffer
elseif self.mode == BUFFERED_MODE then elseif self.mode == BUFFERED_MODE then
self:__update_read_buffer() local buf_len = #self.read_buffer
if #self.readBuffer < length then if buf_len < length then
error "buffer underflow" self.read_buffer:append(
self.io_lib.read(self.descriptor, self.max_buffer_size - buf_len)
)
end end
local copy buf_len = #self.read_buffer
if #self.readBuffer == length then length = math.min(buf_len, length)
copy = Bytearray()
copy:append(self.readBuffer) local copy = self.read_buffer:slice(1, length)
self.readBuffer:clear() if buf_len == length then
self.read_buffer:clear()
else else
copy = Bytearray() self.read_buffer:remove(1, length)
for i = 1, length do
copy[i] = self.readBuffer[i]
end
self.readBuffer:remove(1, length)
end end
return copy return copy
elseif self.mode == DEFAULT_MODE then elseif self.mode == DEFAULT_MODE then
return self.ioLib.read(self.descriptor, length) return self.io_lib.read(self.descriptor, length)
end end
end end
function io_stream:__write(data) function io_stream:__write(data)
if self.mode == BUFFERED_MODE then if self.mode == BUFFERED_MODE then
self.writeBuffer:append(data) local data_length = #data
if #self.writeBuffer > self.maxBufferSize then if #self.write_buffer + data_length > self.max_buffer_size then
error "buffer overflow" self:flush()
end end
if data_length > self.max_buffer_size then
local to_write = math.floor(data_length / self.max_buffer_size) * self.max_buffer_size
local to_save = data_length - to_write
self.io_lib.write(self.descriptor, data:slice(1, to_write))
self:flush()
self.write_buffer = data:slice(to_write + 1, to_save)
else self.write_buffer:append(data) end
elseif self.mode == DEFAULT_MODE or self.mode == YIELD_MODE then elseif self.mode == DEFAULT_MODE or self.mode == YIELD_MODE then
return self.ioLib.write(self.descriptor, data) return self.io_lib.write(self.descriptor, data)
end end
end end
function io_stream:read_fully(useTable) function io_stream:read_fully(use_table)
if self.binaryMode then if self.binary_mode then
local result = useTable and Bytearray() or { } local result = use_table and { } or Bytearray()
readFully(result, function() return self:__read(self.maxBufferSize) end) local avail = self:available()
if avail == 0 then
avail = self.max_buffer_size
end
read_fully(result, function() return self:__read(avail, true) end)
return result
else else
if useTable then if use_table then
local lines = { } local lines = { }
local line local line
@ -220,7 +242,7 @@ function io_stream:read_fully(useTable)
else else
local result = Bytearray() local result = Bytearray()
readFully(result, function() return self:__read(self.maxBufferSize) end) read_fully(result, function() return self:__read(self.max_buffer_size) end)
return utf8.tostring(result) return utf8.tostring(result)
end end
@ -262,57 +284,61 @@ function io_stream:write_line(str)
self:__write(utf8.tobytes(str .. "\n")) self:__write(utf8.tobytes(str .. "\n"))
end end
function io_stream:read(arg, useTable) function io_stream:read(arg, use_table)
local argType = type(arg) local arg_type = type(arg)
if self.binaryMode then if self.binary_mode then
local byteArr local byte_arr
if argType == "number" then if arg_type == "number" then
-- using 'arg' as length -- using 'arg' as length
byteArr = self:__read(arg) byte_arr = self:__read(arg)
if useTable == true then if use_table == true then
local t = { } local t = { }
for i = 1, #byteArr do for i = 1, #byte_arr do
t[i] = byteArr[i] t[i] = byte_arr[i]
end end
return t return t
else else
return byteArr return byte_arr
end end
elseif argType == "string" then elseif arg_type == "string" then
return byteutil.unpack( return byteutil.unpack(
arg, arg,
self:__read(byteutil.get_size(arg)) self:__read(byteutil.get_size(arg))
) )
elseif argType == nil then elseif arg_type == "nil" then
error( error(
"in binary mode the first argument must be a string data format".. "in binary mode the first argument must be a string data format"..
" for the library \"byteutil\" or the number of bytes to read" " for the library \"byteutil\" or the number of bytes to read"
) )
else else
error("unknown argument type: "..argType) error("unknown argument type: "..arg_type)
end end
else else
if not arg then if not arg then
return self:read_line() return self:read_line()
else else
local linesCount = arg local lines_count = arg
local trimLastEmptyLines = useTable or true local trim_last_empty_lines = use_table
if linesCount < 0 then error "count of lines to read must be positive" end if use_table == nil then
trim_last_empty_lines = true
end
if lines_count < 0 then error "count of lines to read must be positive" end
local result = { } local result = { }
for i = 1, linesCount do for i = 1, lines_count do
result[i] = self:read_line() result[i] = self:read_line()
end end
if trimLastEmptyLines then if trim_last_empty_lines then
local i = #result local i = #result
while i >= 0 do while i >= 0 do
@ -340,41 +366,53 @@ function io_stream:read(arg, useTable)
end end
function io_stream:write(arg, ...) function io_stream:write(arg, ...)
local argType = type(arg) local arg_type = type(arg)
if self.binaryMode then if self.binary_mode then
local byteArr local byte_arr
if argType ~= "string" then if arg_type ~= "string" then
-- using arg as bytes table/bytearray -- using arg as bytes table/bytearray
if argType == "table" then if arg_type == "table" then
byteArr = Bytearray(arg) byte_arr = Bytearray(arg)
else else
byteArr = arg byte_arr = arg
end end
else else
byteArr = byteutil.pack(arg, ...) byte_arr = byteutil.pack(arg, ...)
end end
self:__write(byteArr) self:__write(byte_arr)
else else
if argType == "string" then if arg_type == "string" then
self:write_line(arg) self:write_line(arg)
elseif argType == "table" then elseif arg_type == "table" then
for i = 1, #arg do for i = 1, #arg do
self:write_line(arg[i]) self:write_line(arg[i])
end end
else error("unknown argument type: "..argType) end else error("unknown argument type: "..arg_type) end
end end
end end
function io_stream:seek(mode, offset) function io_stream:seek(mode, offset)
self.ioLib.seek(self.descriptor, mode, offset) if not self.io_lib.seek then
error("cannot seek this stream")
end
self.io_lib.seek(self.descriptor, mode, offset)
end
function io_stream:tell()
if not self.io_lib.tell then
error("cannot tell this stream")
end
return self.io_lib.tell(self.descriptor)
end end
function io_stream:is_alive() function io_stream:is_alive()
return self.ioLib.is_alive(self.descriptor) return self.io_lib.is_alive(self.descriptor)
end end
function io_stream:is_closed() function io_stream:is_closed()
@ -383,20 +421,24 @@ end
function io_stream:close() function io_stream:close()
if self.mode == BUFFERED_MODE then if self.mode == BUFFERED_MODE then
self.readBuffer:clear() self.read_buffer:clear()
self.writeBuffer:clear() self.write_buffer:clear()
end end
return self.ioLib.close(self.descriptor) return self.io_lib.close(self.descriptor)
end end
function io_stream:flush() function io_stream:flush()
if self.mode == BUFFERED_MODE and #self.writeBuffer > 0 then if self.mode == BUFFERED_MODE and #self.write_buffer > 0 then
self.ioLib.write(self.descriptor, self.writeBuffer) self.io_lib.write(self.descriptor, self.write_buffer)
self.writeBuffer:clear() self.write_buffer:clear()
end end
if self.flushMode ~= FLUSH_MODE_ONLY_BUFFER then self.ioLib.flush(self.descriptor) end if self.flush_mode ~= FLUSH_MODE_ONLY_BUFFER then
if self.io_lib.flush then
self.io_lib.flush(self.descriptor)
elseif self:is_closed() then error("stream is closed") end
end
end end
return io_stream return io_stream

View file

@ -3,5 +3,6 @@ name = "default"
base_packs = ["base"] base_packs = ["base"]
permissions = [ permissions = [
"network", "network",
"record-audio" "record-audio",
"sub-instances",
] ]

View file

@ -49,6 +49,7 @@ local Socket = {__index={
end end
return self:recv(length, usetable) return self:recv(length, usetable)
end, end,
as_stream=network.__as_stream,
close=function(self) return network.__close(self.id) end, close=function(self) return network.__close(self.id) end,
available=function(self) return network.__available(self.id) or 0 end, available=function(self) return network.__available(self.id) or 0 end,
is_alive=function(self) return network.__is_alive(self.id) end, is_alive=function(self) return network.__is_alive(self.id) end,

View file

@ -140,7 +140,7 @@ console.add_command(
for y=y1,y2 do for y=y1,y2 do
for z=z1,z2 do for z=z1,z2 do
for x=x1,x2 do for x=x1,x2 do
block.set(x, y, z, id) block.set(x, y, z, id, 0, true)
end end
end end
end end

View file

@ -127,6 +127,10 @@ require "core:internal/extensions/inventory"
asserts = require "core:internal/asserts" asserts = require "core:internal/asserts"
events = require "core:internal/events" events = require "core:internal/events"
if test then
require "core:internal/test"
end
function pack.unload(prefix) function pack.unload(prefix)
events.remove_by_prefix(prefix) events.remove_by_prefix(prefix)
end end
@ -136,7 +140,20 @@ local __vc_named_coroutines = {}
local __vc_next_coroutine = 1 local __vc_next_coroutine = 1
function __vc_start_coroutine(chunk) function __vc_start_coroutine(chunk)
local co = coroutine.create(chunk) local co = coroutine.create(function()
local _, err = xpcall(chunk, function(msg)
local traceback = debug.get_traceback(0)
local s = string.format("%s:", msg)
for i=1,#traceback - 2 do
local frame = traceback[i]
s = s .. "\n\t"..tb_frame_tostring(frame)
end
return s
end)
if err then
error(err)
end
end)
local id = __vc_next_coroutine local id = __vc_next_coroutine
__vc_next_coroutine = __vc_next_coroutine + 1 __vc_next_coroutine = __vc_next_coroutine + 1
__vc_coroutines[id] = co __vc_coroutines[id] = co
@ -334,6 +351,10 @@ else
os.pid = ffi.C.getpid() os.pid = ffi.C.getpid()
end end
require("core:io_stream").wrap_bytearray = require "core:internal/stream_providers/bytearray"
network.__as_stream = require "core:internal/stream_providers/socket"
math.randomseed(time.uptime() * 1536227939) math.randomseed(time.uptime() * 1536227939)
rules = require "core:internal/rules" rules = require "core:internal/rules"

View file

@ -10,8 +10,9 @@ vc = {
is_client = function() is_client = function()
return not _vc_headless return not _vc_headless
end, end,
get_version = __vc_app.get_version,
get_setting = __vc_app.get_setting, get_setting = __vc_app.get_setting,
get_setting_str = __vc_app.get_setting_str, str_setting = __vc_app.str_setting,
get_setting_info = __vc_app.get_setting_info, get_setting_info = __vc_app.get_setting_info,
} }
@ -310,12 +311,19 @@ local bytearray = require "core:internal/bytearray"
Bytearray = bytearray.FFIBytearray Bytearray = bytearray.FFIBytearray
Bytearray_as_string = bytearray.FFIBytearray_as_string Bytearray_as_string = bytearray.FFIBytearray_as_string
Bytearray_as_ptr = bytearray.FFIBytearray_as_ptr Bytearray_as_ptr = bytearray.FFIBytearray_as_ptr
I8view = bytearray.FFII8view
U16view = bytearray.FFIU16view U16view = bytearray.FFIU16view
I16view = bytearray.FFII16view I16view = bytearray.FFII16view
U32view = bytearray.FFIU32view U32view = bytearray.FFIU32view
I32view = bytearray.FFII32view I32view = bytearray.FFII32view
U64view = bytearray.FFIU64view
I64view = bytearray.FFII64view
FLTview = bytearray.FFIFLTview
DBLview = bytearray.FFIDBLview
Bytearray_construct = function(...) return Bytearray(...) end Bytearray_construct = function(...) return Bytearray(...) end
ctypes = require "core:internal/ctypes"
bit.compile = require "core:bitwise/compiler" bit.compile = require "core:bitwise/compiler"
bit.execute = require "core:bitwise/executor" bit.execute = require "core:bitwise/executor"

View file

@ -1,6 +1,9 @@
#include <shadows> #include <shadows>
#include <fog> #include <fog>
#define CLOUDS_FOG_FACTOR_MUL 0.3f
#define CLOUDS_FOG_CURVE_MUL 0.4f
vec4 effect() { vec4 effect() {
vec4 pos = texture(u_position, v_uv); vec4 pos = texture(u_position, v_uv);
float light = 1.0; float light = 1.0;
@ -32,6 +35,10 @@ vec4 effect() {
light = pow(light, u_gamma); light = pow(light, u_gamma);
vec3 fogColor = texture(u_skybox, dir).rgb; vec3 fogColor = texture(u_skybox, dir).rgb;
float fog = calc_fog(length(u_view * vec4((modelpos.xyz - u_cameraPos) * FOG_POS_SCALE, 0.0)) / 256.0); float fog = calc_fog(
length(u_view * vec4((modelpos.xyz - u_cameraPos) * FOG_POS_SCALE, 0.0)) / 256.0,
mix(1.0, CLOUDS_FOG_FACTOR_MUL, emission),
mix(1.0, CLOUDS_FOG_CURVE_MUL, emission)
);
return vec4(mix(texture(u_screen, v_uv).rgb * mix(1.0, light, 1.0), fogColor, fog), 1.0); return vec4(mix(texture(u_screen, v_uv).rgb * mix(1.0, light, 1.0), fogColor, fog), 1.0);
} }

View file

@ -15,11 +15,33 @@ uniform float u_fogCurve;
uniform bool u_alphaClip; uniform bool u_alphaClip;
uniform vec3 u_sunDir; uniform vec3 u_sunDir;
const float BAYER_MATRIX[64] = float[](
0.0, 32.0, 8.0, 40.0, 2.0, 34.0, 10.0, 42.0,
48.0, 16.0, 56.0, 24.0, 50.0, 18.0, 58.0, 26.0,
12.0, 44.0, 4.0, 36.0, 14.0, 46.0, 6.0, 38.0,
60.0, 28.0, 52.0, 20.0, 62.0, 30.0, 54.0, 22.0,
3.0, 35.0, 11.0, 43.0, 1.0, 33.0, 9.0, 41.0,
51.0, 19.0, 59.0, 27.0, 49.0, 17.0, 57.0, 25.0,
15.0, 47.0, 7.0, 39.0, 13.0, 45.0, 5.0, 37.0,
63.0, 31.0, 55.0, 23.0, 61.0, 29.0, 53.0, 21.0
);
float calc_bayer(vec2 pos){
ivec2 p = ivec2(mod(pos, 8.0));
return BAYER_MATRIX[p.y * 8 + p.x] / 64.0;
}
void main() { void main() {
vec4 texColor = texture(u_texture0, a_texCoord); vec4 texColor = texture(u_texture0, a_texCoord);
float alpha = a_color.a * texColor.a; float alpha = a_color.a * texColor.a;
// anyway it's any alpha-test alternative required // anyway it's any alpha-test alternative required
if (alpha < (u_alphaClip ? 0.5f : 0.15f)) { if (u_alphaClip) {
float bayer = calc_bayer(gl_FragCoord.xy);
if (alpha - bayer < 0.01f) {
discard;
}
alpha = 1.0;
} else if (alpha < 0.05f) {
discard; discard;
} }
f_color = a_color * texColor; f_color = a_color * texColor;

View file

@ -7,13 +7,17 @@ uniform float u_weatherFogOpacity;
uniform float u_weatherFogDencity; uniform float u_weatherFogDencity;
uniform float u_weatherFogCurve; uniform float u_weatherFogCurve;
float calc_fog(float depth) { float calc_fog(float depth, float fogFactor, float fogCurve) {
return min( return min(
1.0, 1.0,
max(pow(depth * u_fogFactor, u_fogCurve), max(pow(depth * u_fogFactor * fogFactor, u_fogCurve * fogCurve),
min(pow(depth * u_weatherFogDencity, u_weatherFogCurve), min(pow(depth * u_weatherFogDencity, u_weatherFogCurve),
u_weatherFogOpacity)) u_weatherFogOpacity))
); );
} }
float calc_fog(float depth) {
return calc_fog(depth, 1.0, 1.0);
}
#endif // FOG_GLSL_ #endif // FOG_GLSL_

View file

@ -173,6 +173,12 @@ void audio::initialize(
logger.info() << "initializing NoAudio backend"; logger.info() << "initializing NoAudio backend";
backend = NoAudio::create().release(); backend = NoAudio::create().release();
} }
if (settings.inputDevice.get() == "auto") {
auto inputDevices = audio::get_input_devices_names();
if (!inputDevices.empty()) {
settings.inputDevice.set(inputDevices.at(0));
}
}
struct { struct {
std::string name; std::string name;
NumberSetting* setting; NumberSetting* setting;

View file

@ -57,3 +57,7 @@ std::string parsing_error::errorLog() const {
ss << "^"; ss << "^";
return ss.str(); return ss.str();
} }
std::runtime_error parsing_error::toRuntimeError() const {
return std::runtime_error("parsing error: " + errorLog());
}

View file

@ -73,4 +73,6 @@ public:
uint linestart uint linestart
); );
std::string errorLog() const; std::string errorLog() const;
std::runtime_error toRuntimeError() const;
}; };

View file

@ -55,12 +55,12 @@ model::Model VcmModel::squashed() const {
} }
static const std::unordered_map<std::string, int> side_indices { static const std::unordered_map<std::string, int> side_indices {
{"east", 0}, {"west", 0}, // -X
{"west", 1}, {"east", 1}, // +X
{"bottom", 2}, {"bottom", 2}, // -Y
{"top", 3}, {"top", 3}, // +Y
{"south", 4}, {"north", 4}, // -Z
{"north", 5}, {"south", 5}, // +Z
}; };
static bool to_boolean(const xml::Attribute& attr) { static bool to_boolean(const xml::Attribute& attr) {
@ -267,12 +267,12 @@ static void perform_box(const xmlelement& root, ModelBuilder& builder) {
} }
UVRegion regions[6] {}; UVRegion regions[6] {};
regions[0].scale(to.x - from.x, to.y - from.y); regions[0].scale(to.z - from.z, to.y - from.y);
regions[1].scale(from.x - to.x, to.y - from.y); regions[1].scale(from.z - to.z, to.y - from.y);
regions[2].scale(to.x - from.x, to.z - from.z); regions[2].scale(to.x - from.x, to.z - from.z);
regions[3].scale(from.x - to.x, to.z - from.z); regions[3].scale(from.x - to.x, to.z - from.z);
regions[4].scale(to.z - from.z, to.y - from.y); regions[4].scale(to.x - from.x, to.y - from.y);
regions[5].scale(from.z - to.z, to.y - from.y); regions[5].scale(from.x - to.x, to.y - from.y);
auto center = (from + to) * 0.5f; auto center = (from + to) * 0.5f;
auto halfsize = (to - from) * 0.5f; auto halfsize = (to - from) * 0.5f;
@ -306,7 +306,11 @@ static void perform_box(const xmlelement& root, ModelBuilder& builder) {
texfaces[idx] = elem->attr("texture").getText(); texfaces[idx] = elem->attr("texture").getText();
} }
if (elem->has("region")) { if (elem->has("region")) {
regions[idx].set(elem->attr("region").asVec4()); auto region = elem->attr("region").asVec4();
if (idx % 2 == 1) {
std::swap(region[0], region[2]);
}
regions[idx].set(region);
} }
if (elem->has("region-scale")) { if (elem->has("region-scale")) {
regions[idx].scale(elem->attr("region-scale").asVec2()); regions[idx].scale(elem->attr("region-scale").asVec2());

View file

@ -30,6 +30,7 @@ namespace {
public: public:
FTFontFile(FT_Face face, util::Buffer<ubyte> buffer) FTFontFile(FT_Face face, util::Buffer<ubyte> buffer)
: face(std::move(face)), buffer(std::move(buffer)) { : face(std::move(face)), buffer(std::move(buffer)) {
monospace = this->face->face_flags & FT_FACE_FLAG_FIXED_WIDTH;
} }
~FTFontFile() { ~FTFontFile() {

View file

@ -19,6 +19,12 @@ namespace vector_fonts {
virtual std::unique_ptr<Texture> renderPage( virtual std::unique_ptr<Texture> renderPage(
int pageid, std::vector<Glyph>& glyphs, int size int pageid, std::vector<Glyph>& glyphs, int size
) = 0; ) = 0;
bool isMonospace() const {
return monospace;
}
protected:
bool monospace = true;
}; };
void initialize(); void initialize();

View file

@ -385,10 +385,22 @@ public:
} }
if (c == '(') { if (c == '(') {
nextChar(); nextChar();
// TODO: replace with array parsing after moving to dv::value's int depth = 1;
std::string value = std::string(readUntil(')')); size_t start = pos;
while (hasNext()) {
char c = nextChar();
if (c == '(') {
depth++;
} else if (c == ')') {
depth--;
if (depth == 0) {
goBack(1);
break;
}
}
}
expect(')'); expect(')');
return value; return std::string(source.substr(start, pos - start - 1));
} }
return std::string(readUntilWhitespace()); return std::string(readUntilWhitespace());
} }
@ -528,3 +540,11 @@ std::string xml::stringify(
return ss.str(); return ss.str();
} }
std::string xml::stringify(
const Node& element, bool nice, const std::string& indentStr
) {
std::stringstream ss;
stringifyElement(ss, element, nice, indentStr, 0);
return ss.str();
}

View file

@ -119,6 +119,12 @@ namespace xml {
const std::string& indentStr = " " const std::string& indentStr = " "
); );
std::string stringify(
const Node& element,
bool nice = true,
const std::string& indentStr = " "
);
/// @brief Read XML Document from string /// @brief Read XML Document from string
/// @param filename file name will be shown in error messages /// @param filename file name will be shown in error messages
/// @param source xml source code string /// @param source xml source code string

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