mirror of
https://github.com/MihailRis/voxelcore.git
synced 2026-10-04 10:31:50 +00:00
Merge branch 'main' into fix-assets-pipeline
This commit is contained in:
commit
b9b8c6b7da
259 changed files with 5368 additions and 2080 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -49,3 +49,6 @@ appimage-build/
|
|||
# libs
|
||||
/libs/
|
||||
/vcpkg_installed/
|
||||
|
||||
# vcstudio
|
||||
.vcstudio
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ Installing last version that supports C++17.
|
|||
```sh
|
||||
git clone --branch v3.16.0 https://github.com/skypjack/entt.git
|
||||
cd entt
|
||||
mkdir build && cd build
|
||||
mkdir -p build && cd build
|
||||
cmake -DCMAKE_BUILD_TYPE=Release -DENTT_INSTALL=ON ..
|
||||
sudo make install
|
||||
```
|
||||
|
|
|
|||
|
|
@ -18,19 +18,86 @@ Bytearray.insert(arr, {5, 3, 6})
|
|||
|
||||
assert(#arr == 12)
|
||||
Bytearray.insert(arr, 2, 8)
|
||||
assert(#arr == 13)
|
||||
asserts.equals(13, #arr)
|
||||
for i=1,10 do
|
||||
assert(arr[i] == 10 - i)
|
||||
end
|
||||
print(#arr, arr:get_capacity())
|
||||
arr:trim()
|
||||
assert(#arr == arr:get_capacity())
|
||||
asserts.equals(#arr, arr:get_capacity())
|
||||
|
||||
arr = Bytearray({0, 2, 7, 1, 16, 75, 25})
|
||||
assert(arr[6] == 75)
|
||||
|
||||
arr:insert(2, {5, 6})
|
||||
|
||||
assert(arr[#arr] == 25)
|
||||
assert(arr[2] == 5)
|
||||
assert(arr[3] == 6)
|
||||
asserts.equals(25, arr[#arr])
|
||||
asserts.equals(5, arr[2])
|
||||
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
34
dev/tests/io_stream.lua
Normal 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)
|
||||
|
|
@ -4,14 +4,15 @@
|
|||
|
||||
The appearance of 3D text, is configured via a table, like [particles](particles.md). All fields are optional.
|
||||
|
||||
| Field | Description | Default |
|
||||
| --------------- | ---------------------------- | ---------------- |
|
||||
| display | Display format | static_billboard |
|
||||
| color | Text color | {1, 1, 1, 1} |
|
||||
| scale | Text scale | 1 |
|
||||
| render_distance | Text rendering distance | 32 |
|
||||
| xray_opacity | Visibility through obstacles | 0 |
|
||||
| perspective | Perspective coefficient | 1 |
|
||||
| Field | Description | Default |
|
||||
| --------------- | ---------------------------------------- | ---------------- |
|
||||
| display | Display format | static_billboard |
|
||||
| color | Text color | {1, 1, 1, 1} |
|
||||
| scale | Text scale | 1 |
|
||||
| render_distance | Text rendering distance | 32 |
|
||||
| xray_opacity | Visibility through obstacles | 0 |
|
||||
| perspective | Perspective coefficient | 1 |
|
||||
| font | Font. If absent, the default one is used | "" |
|
||||
|
||||
Available display formats:
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# Block properties
|
||||
|
||||
## Visual
|
||||
## Visual & Audio
|
||||
|
||||
### *texture*
|
||||
|
||||
|
|
@ -69,6 +69,19 @@ Rotation profile (set of available block rotations and behaviour of placing bloc
|
|||
- "pipe" - wood logs, pipes, pillars
|
||||
- "pane" - panels, doors, signs
|
||||
- "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
|
||||
|
||||
|
|
@ -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).
|
||||
|
||||
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.5m north
|
||||
- 0.75m width (from east to west)
|
||||
- offset 0.5m south
|
||||
- 0.75m width (from west to east)
|
||||
- 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:
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ Content-pack folder must contain file **package.json** with following contents:
|
|||
{
|
||||
"id": "pack_id",
|
||||
"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",
|
||||
"description": "short description",
|
||||
"dependencies": [
|
||||
|
|
@ -26,6 +26,7 @@ Dependency levels are indicated by prefixes in the name:
|
|||
- '!' - required dependency
|
||||
- '?' - optional dependency
|
||||
- '~' - weak dependency
|
||||
|
||||
If prefix is not specified, '!' level will be used.
|
||||
|
||||
Example: '~randutil' - weak dependency 'randutil'.
|
||||
|
|
|
|||
|
|
@ -61,12 +61,16 @@ Determines how the physics engine will work with it.
|
|||
|
||||
### *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.*
|
||||
|
||||
Default value: *true*.
|
||||
|
||||
### *selectable*
|
||||
|
||||
If set to `false` the cursor will ignore the entity, passing the ray.
|
||||
|
||||
### *sensors*
|
||||
|
||||
A sensor is an area attached to a physical body that detects the entry of other bodies into it.
|
||||
|
|
|
|||
|
|
@ -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`.
|
||||
|
||||
### Display of uses - `uses-display`
|
||||
### Display of remaining uses count - `uses-display`
|
||||
|
||||
- `none` - display disabled
|
||||
- `number` - number
|
||||
- `relation` - current value to initial value (x/y)
|
||||
- `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 allow you to designate general properties of items. Names should be formatted as `prefix:tag_name`.
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ Models are loaded automatically; adding them to preload.json is not required.
|
|||
|
||||
## 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]
|
||||
> When loading an obj model, the \*.mtl file is ignored.
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ Subsections:
|
|||
- [block](scripting/builtins/libblock.md)
|
||||
- [byteutil](scripting/builtins/libbyteutil.md)
|
||||
- [cameras](scripting/builtins/libcameras.md)
|
||||
- [ctypes](scripting/builtins/libctypes.md)
|
||||
- [entities](scripting/builtins/libentities.md)
|
||||
- [file](scripting/builtins/libfile.md)
|
||||
- [gfx.blockwraps](scripting/builtins/libgfx-blockwraps.md)
|
||||
|
|
@ -42,6 +43,7 @@ Subsections:
|
|||
- [utf8](scripting/builtins/libutf8.md)
|
||||
- [vec2, vec3, vec4](scripting/builtins/libvecn.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:data_buffer](scripting/modules/core_data_buffer.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
|
||||
- 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
|
||||
require "packid:module_name" -- load Lua module from pack-folder/modules/
|
||||
-- no extension included, just name
|
||||
local module_name = require "contentpack:module_name" -- imports the module
|
||||
|
||||
-- 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.
|
||||
|
|
|
|||
|
|
@ -180,14 +180,34 @@ app.get_content_sources() -> table<string>
|
|||
|
||||
Returns a list of content sources (paths), in descending priority order.
|
||||
|
||||
``lua
|
||||
```lua
|
||||
app.set_content_sources(sources: table<string>)
|
||||
```
|
||||
|
||||
Sets a list of content sources (paths). Specified in descending priority order.
|
||||
|
||||
``lua
|
||||
```lua
|
||||
app.reset_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
|
||||
```
|
||||
|
|
|
|||
|
|
@ -28,7 +28,8 @@ block.get(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.
|
||||
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.
|
||||
-- on behalf of the player, calling the on_placed event.
|
||||
|
|
|
|||
28
doc/en/scripting/builtins/libctypes.md
Normal file
28
doc/en/scripting/builtins/libctypes.md
Normal 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)
|
||||
```
|
||||
|
|
@ -30,6 +30,9 @@ entities.def_hitbox(id: int) -> vec3
|
|||
-- Returns entity definition name by index (string ID).
|
||||
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).
|
||||
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
|
||||
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
|
||||
-- size - area size
|
||||
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
|
||||
-- radius - radius of the area
|
||||
entities.get_all_in_radius(center: vec3, radius: number) -> array<int>
|
||||
|
|
|
|||
|
|
@ -139,7 +139,7 @@ gui.confirm(
|
|||
## Documents and templates
|
||||
|
||||
```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(
|
||||
-- Path to the xml file of the page. Example: `core:layouts/pages/main.xml`
|
||||
path: str,
|
||||
|
|
@ -147,7 +147,7 @@ gui.load_document(
|
|||
name: str
|
||||
-- Table of parameters passed to the on_open event
|
||||
args: table
|
||||
) -> str
|
||||
) -> table
|
||||
|
||||
-- Loads and processes layout template from file
|
||||
gui.template(
|
||||
|
|
|
|||
|
|
@ -57,6 +57,10 @@ hud.get_block_inventory() -> int
|
|||
-- Gives the ID of the second open inventory (block, virtual inventory...) or 0.
|
||||
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.
|
||||
hud.get_player() -> int
|
||||
|
||||
|
|
|
|||
|
|
@ -2,14 +2,36 @@
|
|||
|
||||
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
|
||||
-- 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
|
||||
quat.from_mat4(m: matrix, dst: quat)
|
||||
## Quaternion of Euler angles - *quat.from_euler(...)*
|
||||
|
||||
```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(...)*
|
||||
|
|
@ -19,16 +41,12 @@ The interpolation always take the short path and the rotation is performed at co
|
|||
```lua
|
||||
-- creates a quaternion as an interpolation between a and b,
|
||||
-- where t is interpolation factor
|
||||
quat.slerp(a: quat, b: quat, t: number)
|
||||
|
||||
-- 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)
|
||||
quat.slerp(a: quat, b: quat, t: number, [optional] dst: quat) -> quat
|
||||
```
|
||||
|
||||
## Casting to string - *quat.tostring(...)*
|
||||
|
||||
```lua
|
||||
-- returns a string representing the contents of the quaternion
|
||||
quat.tostring(q: quat)
|
||||
quat.tostring(q: quat) -> string
|
||||
```
|
||||
|
|
|
|||
|
|
@ -92,6 +92,8 @@ world.raycast(
|
|||
-- exclusion filtering mode:
|
||||
-- if true, filter_entities defines the list of ignored entity types
|
||||
entities_exclusion: bool = false,
|
||||
-- include entities with selectable=false
|
||||
nonselect_entities: bool = false;
|
||||
|
||||
[==[ additional parameters (blocks) ]==]
|
||||
-- id of the block types used for filtering
|
||||
|
|
|
|||
|
|
@ -38,6 +38,17 @@ entity:set_enabled(name: str, enable: bool)
|
|||
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
|
||||
|
||||
### Transform
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ Called on block broken by player
|
|||
function on_replaced(x, y, z, playerid)
|
||||
```
|
||||
|
||||
Called on block replaced with other by player
|
||||
Called before a player replaces a block
|
||||
|
||||
```lua
|
||||
function on_interact(x, y, z, playerid) -> bool
|
||||
|
|
|
|||
204
doc/en/scripting/extensions.md
Normal file
204
doc/en/scripting/extensions.md
Normal 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
|
||||
```
|
||||
|
|
@ -52,6 +52,60 @@ yaml.parse(code: str) -> 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
|
||||
|
||||
The library contains functions for working with the binary data exchange format [vcbjson](../../specs/binary_json_spec.md).
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
# UI properties and methods
|
||||
|
||||
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:
|
||||
```lua
|
||||
|
|
@ -53,7 +53,7 @@ Properties that apply to all elements:
|
|||
| contentOffset | vec2 | yes | *no* | element content offset |
|
||||
| cursor | string | yes | yes | cursor displayed on hover |
|
||||
| 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:
|
||||
|
||||
|
|
|
|||
|
|
@ -212,10 +212,27 @@ local map = Heightmap(width, height)
|
|||
Operations apply to all height values.
|
||||
|
||||
```lua
|
||||
-- Convert to absolute values
|
||||
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
|
||||
|
||||
|
|
|
|||
|
|
@ -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*
|
||||
- `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.
|
||||
- `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.
|
||||
- `onfocus` - lua function called when focusing on an element.
|
||||
- `ondefocus` - lua function called when the element loses focus.
|
||||
|
|
@ -192,6 +194,9 @@ Example of list description:
|
|||
- `width` - minimum content width. Default: 100.
|
||||
- `selected` - initially selected value. Default: "".
|
||||
- `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*
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
| render_distance | Дистанция отрисовки текста | 32 |
|
||||
| xray_opacity | Коэффициент видимости через препятствия (просвечивание) | 0 |
|
||||
| perspective | Коэффициент перспективы | 1 |
|
||||
| font | Шрифт. При отсутствии используется основной | "" |
|
||||
|
||||
Доступные форматы отображения:
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# Свойства блоков
|
||||
|
||||
## Вид
|
||||
## Вид & Звук
|
||||
|
||||
### Текстура - *texture*
|
||||
|
||||
|
|
@ -70,6 +70,7 @@
|
|||
- "pipe" - профиль "труба". Примеры блоков: бревно, труба, лампочка
|
||||
- "pane" - профиль "панель". Примеры блоков: панель, дверь, табличка
|
||||
- "stairs" - профиль "ступеньки" ("pane" + перевернутые варианты)
|
||||
- "ladder" - профиль "лестница" (как "pipe", но на горизонтальных поверхностях как "pane")
|
||||
|
||||
### Испускаемые частицы - *particles*
|
||||
|
||||
|
|
@ -78,6 +79,11 @@
|
|||
При приближении к блоку движок создаст эмиттер, который будет работать
|
||||
до разрушения блока или отдаления камеры на некоторое расстояние.
|
||||
|
||||
### Материал - *material*
|
||||
|
||||
Определяет имя материала блока в формате `пак:имя_материала`, что влияет на выбор звуков взаимодействия с блоком.
|
||||
Определения материалов расположены в /block_materials.
|
||||
|
||||
## Варианты
|
||||
|
||||
Некоторые свойства могут варьироваться динамически, в зависимости от номера варианта, хранимого в пользовательских битах блока.
|
||||
|
|
@ -188,12 +194,12 @@
|
|||
Числа указываются в диапазоне [0.0, 1.0] - т.е в пределах блока (в случае расширенного блока хитбокс может быть больше единицы, но не должен выходить за пределы size).
|
||||
|
||||
Массив `[0.25, 0.0, 0.5, 0.75, 0.4, 0.3]` описывает хитбокс:
|
||||
- смещен на 0.25 м на запад
|
||||
- смещен на 0.25 м на восток
|
||||
- смещен на 0.0 м вверх
|
||||
- смещен на 0.5 м на север
|
||||
- шириной (с востока на запад) 0.75 м
|
||||
- смещен на 0.5 м на юг
|
||||
- шириной (с запада на восток) 0.75 м
|
||||
- высотой 0.4 м
|
||||
- длиной (с юга на север) 0.3 м
|
||||
- длиной (с севера на юг) 0.3 м
|
||||
|
||||
Для составных хитбоксов используется свойство *hitboxes* - массив хитбоксов, например:
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
{
|
||||
"id": "выбранное_имя_пака",
|
||||
"title": "имя контент-пака для отображения в меню контента",
|
||||
"version": "версия контент-пака в формате major.minor",
|
||||
"version": "версия контент-пака в формате major.minor.[patch]",
|
||||
"creator": "создатель контент-пака",
|
||||
"description": "краткое описание",
|
||||
"dependencies": [
|
||||
|
|
@ -28,6 +28,7 @@
|
|||
- '!' - обязательная зависимость
|
||||
- '?' - опциональная зависимость
|
||||
- '~' - слабая зависимость
|
||||
|
||||
Отсутствие префикса интерпретируется как '!'.
|
||||
|
||||
Пример: '~randutil' - слабая зависимость 'randutil'.
|
||||
|
|
|
|||
|
|
@ -61,12 +61,16 @@
|
|||
|
||||
### Блокирование - *blocking*
|
||||
|
||||
Определяет блокирует ли сущность установку блоков.
|
||||
Определяет, блокирует ли сущность установку блока, пересекающегося с хитбоксом сущности.
|
||||
|
||||
*В будущем будет также блокировать движение других сущностей.*
|
||||
|
||||
Значение по-умолчанию: *true*.
|
||||
|
||||
### Выделяемость - *selectable*
|
||||
|
||||
При значении в `false` курсор будет игнорировать cущность, пропуская луч далее.
|
||||
|
||||
### Список сенсоров - *sensors*
|
||||
|
||||
Сенсор - область пространства, привязанная к физическому телу, детектирующее попадание в него других тел.
|
||||
|
|
|
|||
|
|
@ -66,6 +66,10 @@
|
|||
- `relation` - отношение текущего значения к изначальному (x/y)
|
||||
- `vbar` - вертикальная шкала (используется по-умолчанию)
|
||||
|
||||
### Имя скрипта - `script-name`
|
||||
|
||||
Определяет имя скрипта, содержащего обработчики событий предмета. По-умолчанию соответствует строковому id (имени) предмета.
|
||||
Свойство обеспечивает возможность использования одного скрипта для нескольких предметов.
|
||||
|
||||
## Теги - *tags*
|
||||
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@
|
|||
|
||||
## Модели
|
||||
|
||||
Модели должны располагаться в папке models. На данный момент поддерживается только OBJ формат.
|
||||
Модели должны располагаться в папке models. На данный момент поддерживаются форматы: obj, vec3 и [vcm](vcm.md).
|
||||
|
||||
>[!IMPORTANT]
|
||||
> При загрузке obj модели игнорируется файл \*.mtl.
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
- [block](scripting/builtins/libblock.md)
|
||||
- [byteutil](scripting/builtins/libbyteutil.md)
|
||||
- [cameras](scripting/builtins/libcameras.md)
|
||||
- [ctypes](scripting/builtins/libctypes.md)
|
||||
- [entities](scripting/builtins/libentities.md)
|
||||
- [file](scripting/builtins/libfile.md)
|
||||
- [gfx.blockwraps](scripting/builtins/libgfx-blockwraps.md)
|
||||
|
|
@ -60,6 +61,58 @@
|
|||
- quat - массив из четырех чисел - кватернион
|
||||
- matrix - массив из 16 чисел - матрица
|
||||
|
||||
## Пространства имён
|
||||
|
||||
В настоящее время, движок использует иерархию пространств имён, минимизирующую конфликты между отдельными модулями, скриптами, да и паками.
|
||||
|
||||
Актуальна следующая структура:
|
||||
|
||||
- Глобальное пространство (оно же - _G), являющееся корневым, запись в которое, вне модулей ядра движка, является крайне нежелательным для вашего же времени, что может уйти на лишние часы отладки.
|
||||
- Пространство пака - создаётся каждый раз при загрузке контента для каждого, включённого в конфигурацию, пака. Это пространство используют модули, а также скрипты предметов и блоков.
|
||||
- Пространство [компонента](scripting/ecs.md#пользовательские-компоненты).
|
||||
- Пространство [UI-документа](scripting/ui.md)
|
||||
- Изолированное пространство имён генератора мира.
|
||||
|
||||
## Модули
|
||||
|
||||
Модуль - глобальный объект со сроком жизни, ограниченным сроком жизни контента, используемый как для взаимодействия разных паков, так и для вместо глобальных переменных в пределах самого пака.
|
||||
|
||||
Модуль должен находиться в `контентпак/modules/**` (допускаются вложенные папки, что нужно будет указывать)
|
||||
|
||||
```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 вернёт кешированный результат.
|
||||
|
|
|
|||
|
|
@ -142,4 +142,24 @@ app.set_content_sources(sources: table<string>)
|
|||
|
||||
-- Сбрасывает список источников контента.
|
||||
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
|
||||
```
|
||||
|
|
|
|||
28
doc/ru/scripting/builtins/libctypes.md
Normal file
28
doc/ru/scripting/builtins/libctypes.md
Normal 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)
|
||||
```
|
||||
|
|
@ -30,6 +30,9 @@ entities.def_name(id: int) -> string
|
|||
-- Возвращает значение свойства 'hitbox' сущности
|
||||
entities.def_hitbox(id: int) -> vec3
|
||||
|
||||
-- Возвращает true если сущность является осязаемым препятствием.
|
||||
entities.def_solid(id: int) -> boolean
|
||||
|
||||
-- Возвращает индекс определения сущности по имени (числовой ID).
|
||||
entities.def_index(name: string) -> int
|
||||
|
||||
|
|
@ -50,12 +53,12 @@ entities.get_all() -> table
|
|||
-- Возвращает таблицу загруженных сущностей по переданному списку UID
|
||||
entities.get_all(uids: table<int>) -> table
|
||||
|
||||
-- Возвращает список UID сущностей, попадающих в прямоугольную область
|
||||
-- Возвращает список UID сущностей, позиции которых попадают в прямоугольную область
|
||||
-- pos - минимальный угол области
|
||||
-- size - размер области
|
||||
entities.get_all_in_box(pos: vec3, size: vec3) -> table<int>
|
||||
|
||||
-- Возвращает список UID сущностей, попадающих в радиус
|
||||
-- Возвращает список UID сущностей, позиции которых попадают в радиус
|
||||
-- center - центр области
|
||||
-- radius - радиус области
|
||||
entities.get_all_in_radius(center: vec3, radius: number) -> table<int>
|
||||
|
|
|
|||
|
|
@ -140,12 +140,12 @@ gui.confirm(
|
|||
## Документы и шаблоны
|
||||
|
||||
```lua
|
||||
-- Загружает UI документ и его скрипт. Возвращает имя документа.
|
||||
-- Загружает UI документ и его скрипт. Возвращает пространство имён документа
|
||||
gui.load_document(
|
||||
path: string, -- путь к xml файлу, например: core:layouts/pages/main.xml
|
||||
name: string, -- id документа, например: core:pages/main
|
||||
args: table -- параметры для события on_open
|
||||
) -> string
|
||||
) -> table
|
||||
|
||||
-- Обрабатывает xml шаблон макета из файла
|
||||
gui.template(
|
||||
|
|
|
|||
|
|
@ -58,6 +58,11 @@ hud.get_block_inventory() -> int
|
|||
-- Дает ID второго открытого инвентаря (блок, виртуальный инвентарь...) или 0.
|
||||
hud.get_second_inventory() -> int
|
||||
|
||||
-- Получить ID инвентаря, в котором находится предмет во время
|
||||
-- его перемещения в инвентаре, или 0 если его не существует.
|
||||
-- Имеет единственный слот - 0
|
||||
hud.get_exchange_inventory() -> int
|
||||
|
||||
-- Дает ID игрока, к которому привязан пользовательский интерфейс.
|
||||
hud.get_player() -> int
|
||||
|
||||
|
|
@ -81,4 +86,4 @@ hud.set_allow_pause(flag: boolean)
|
|||
|
||||
-- Функция, управляющая именованным скелетом 'hand' (см. gfx.skeletons)
|
||||
hud.hand_controller: function()
|
||||
```
|
||||
```
|
||||
|
|
|
|||
|
|
@ -92,6 +92,11 @@ socket:recv_async(
|
|||
[опционально] usetable: boolean=false
|
||||
) -> nil|table|Bytearray
|
||||
|
||||
-- Оборачивает сокет в io_stream (см. ../io_stream.md)
|
||||
socket:as_stream(
|
||||
[опционально] binary_mode: boolean=true
|
||||
) -> io_stream
|
||||
|
||||
-- Закрывает соединение
|
||||
socket:close()
|
||||
|
||||
|
|
|
|||
|
|
@ -2,14 +2,14 @@
|
|||
|
||||
Библиотека для работы с кватернионами.
|
||||
|
||||
Кватернионы задаюся таблицей, состоящей из 4 компонентов:
|
||||
`{w, x, y, z}`
|
||||
|
||||
## Кватернион из матрицы - *quat.from_mat4(...)*
|
||||
|
||||
```lua
|
||||
-- создаёт кватернион на основе матрицы вращения
|
||||
quat.from_mat4(m: matrix) -> quat
|
||||
|
||||
-- записывает кватернион по матрице вращения в dst
|
||||
quat.from_mat4(m: matrix, dst: quat)
|
||||
quat.from_mat4(m: matrix, [опционально] dst: quat) -> quat
|
||||
```
|
||||
|
||||
## Кватернион из углов Эйлера - *quat.from_euler(...)*
|
||||
|
|
@ -17,11 +17,21 @@ quat.from_mat4(m: matrix, dst: quat)
|
|||
```lua
|
||||
-- создаёт кватернион на основе вектора, содержащего углы Эйлера в порядке XYZ (pitch, yaw, roll)
|
||||
-- (значения углов строго в градусах)
|
||||
quat.from_euler(euler: vec3) -> quat
|
||||
quat.from_euler(euler: vec3, [опционально] dst: quat) -> quat
|
||||
```
|
||||
|
||||
-- записывает кватернион в dst на основе вектора, содержащего углы Эйлера в порядке XYZ (pitch, yaw, roll)
|
||||
-- (значения углов строго в градусах)
|
||||
quat.from_euler(euler: vec3, dst: quat)
|
||||
## Композиция кватернионов - quat.mul(...)
|
||||
|
||||
```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(...)*
|
||||
|
|
@ -31,11 +41,7 @@ quat.from_euler(euler: vec3, dst: quat)
|
|||
```lua
|
||||
-- создаёт кватернион как интерполяцию между a и b,
|
||||
-- где t - фактор интерполяции
|
||||
quat.slerp(a: quat, b: quat, t: number) -> quat
|
||||
|
||||
-- записывает кватернион как интерполяцию между a и b в dst,
|
||||
-- где t - фактор интерполяции
|
||||
quat.slerp(a: quat, b: quat, t: number, dst: quat)
|
||||
quat.slerp(a: quat, b: quat, t: number, [опционально] dst: quat) -> quat
|
||||
```
|
||||
|
||||
## Перевод в строку - *quat.tostring(...)*
|
||||
|
|
|
|||
|
|
@ -91,6 +91,8 @@ world.raycast(
|
|||
-- исключающий режим фильтрации:
|
||||
-- при значении (true) filter_entities определяет список игнорируемых
|
||||
entities_exclusion: bool = false,
|
||||
-- учитывать сущности с selectable=false
|
||||
nonselect_entities: bool = false;
|
||||
|
||||
[==[ дополнительные параметры (блоки) ]==]
|
||||
-- id типов блоков используемых для фильтрации
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
*Bytearray* - динамический байтовый массив, реализованный через LuaJIT FFI. По нему можно итерироваться стандартными функциями **pairs** и **ipairs**
|
||||
|
||||
<small>Примечание касаемо условных обозначений в документации: псведотип `posint` означает натуральное (положительное ненулевое целое) число.</small>
|
||||
|
||||
## Основное
|
||||
### Создание массива
|
||||
```lua
|
||||
|
|
@ -22,29 +24,64 @@ bytes:insert(index: int, value: int)
|
|||
-- Удаляет элемент(ы), если передан `count`, удалит `count` кол-во элементов с выбранного индекса.
|
||||
bytes:remove(index: int, [опционально] count: int)
|
||||
|
||||
-- Очищает массив
|
||||
-- Очищает массив.
|
||||
bytes:clear()
|
||||
|
||||
--[[
|
||||
Заполняет массив определённым байтом полностью либо в определённом диапазоне.
|
||||
Логика работы следующая: если параметр size или index равны nil, то указанным байтом будет заполнен весь массив,
|
||||
иначе будет заполнен диапазон внутри массива начиная с индекса index и размером size.
|
||||
В случае выхода заполняемого блока за границы массива генерирует соответствующую ошибку.
|
||||
]]
|
||||
bytes:fill(index: posint/nil, size: posint/nil, byte: uint8)
|
||||
|
||||
-- Создаёт новый Bytearray, содержащий копию части данных с offset до offset+length
|
||||
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
|
||||
|
||||
Это "вьюшки" поверх Bytearray, которые интерпретируют его байты как массив чисел другого размера - **без копирования данных**.
|
||||
|
||||
Не имеет своих методов, является просто объектной ссылкой на оригинальный Bytearray.
|
||||
Практически не имеют своих методов, являются простыми объектными ссылками на оригинальный Bytearray.
|
||||
|
||||
| Класс | Тип элементов | Размер |
|
||||
| ------------ | ------------------ | ------- |
|
||||
| `I16view` | `int16_t` | 2 байта |
|
||||
| `U16view` | `uint16_t` | 2 байта |
|
||||
| `I32view` | `int32_t` | 4 байта |
|
||||
| `U32view` | `uint32_t` | 4 байта |
|
||||
| Класс | Тип элементов | Размер |
|
||||
| ------------ | ------------------ | --------- |
|
||||
| `I8view` | `int8_t` | 1 байт |
|
||||
| `I16view` | `int16_t` | 2 байта |
|
||||
| `U16view` | `uint16_t` | 2 байта |
|
||||
| `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
|
||||
local bytes = Bytearray({1, 0, 2, 0, 250, 255})
|
||||
|
|
@ -59,4 +96,4 @@ print(i16[3]) -- -6 (signed вьюшка)
|
|||
for _, num in ipairs(i16) do
|
||||
print(num)
|
||||
end -- 1; 2; -6
|
||||
```
|
||||
```
|
||||
|
|
|
|||
|
|
@ -180,6 +180,17 @@ rig:get_color() -> vec3
|
|||
rig:set_color(color: vec3)
|
||||
```
|
||||
|
||||
## Пользовательские компоненты
|
||||
|
||||
Компонент описывается в виде скрипта `{пак}/scripts/components/{имя}.lua`.
|
||||
Компонент будет доступен для использования в описании сущности как `{пак}:{имя}`.
|
||||
Пример: `core:scripts/components/pathfinding.lua` -> `core:pathfinding`.
|
||||
|
||||
Для **каждой** сущности, содержащей компонент, создаётся **отдельный экземпляр** со **своим пространством имен** внутри пространства имён пака.
|
||||
Глобальные переменные, объявляемые в нём, играют роль публичных полей компонента (как и глобальные функции).
|
||||
|
||||
Компоненты сущности указываются через [список "components"](../entity-properties.md#cписок-компонентов---components)
|
||||
|
||||
> [!WARNING]
|
||||
> При выходе сущности за пределы зоны прогрузки она удаляется, вызывая события.
|
||||
> При повторном попадании в зону прогрузки сущность спавнится заново.
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ function on_broken(x, y, z, playerid)
|
|||
function on_replaced(x, y, z, playerid)
|
||||
```
|
||||
|
||||
Вызывается после замены блока игроком
|
||||
Вызывается перед заменой блока игроком
|
||||
|
||||
```lua
|
||||
function on_interact(x, y, z, playerid) -> bool
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
- [расширения для string](#расширения-для-string)
|
||||
- [расширения для math](#расширения-для-math)
|
||||
- [расширения для bit](#расширения-для-bit)
|
||||
- [Дополнительные глобальные функции](#дополнительные-глобальные-функции)
|
||||
- [дополнительные глобальные функции](#дополнительные-глобальные-функции)
|
||||
|
||||
## Расширения для table
|
||||
```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, 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
|
||||
```
|
||||
|
|
@ -156,6 +162,14 @@ math.round(num: number, [опционально] places: number) -> 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
|
||||
|
|
@ -200,4 +214,4 @@ await(co: coroutine) -> result, error
|
|||
|
||||
-- Константа, в которой хранится PID текущего инстанса движка.
|
||||
os.pid -> number
|
||||
```
|
||||
```
|
||||
|
|
|
|||
|
|
@ -52,6 +52,60 @@ yaml.parse(code: str) -> table
|
|||
|
||||
Парсит 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
|
||||
|
||||
Библиотека содержит функции для работы с двоичным форматом обмена данными [vcbjson](../../specs/binary_json_spec.md).
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
Поток имеет три различных вида режима:
|
||||
- Режим общего поведения (`general` / `mode`)
|
||||
|
||||
- Режим сброса (`flush` / `flushMode`)
|
||||
- Режим сброса (`flush` / `flush_mode`)
|
||||
|
||||
- Двоичный режим (`binary`)
|
||||
|
||||
|
|
@ -27,34 +27,34 @@
|
|||
Определяет, как поток обрабатывает чтение и запись,
|
||||
имеет три подрежима:
|
||||
|
||||
| Режим | Описание |
|
||||
|---------------|--------|
|
||||
| `"default"` | Прямой режим. `read` может вернуть меньше байт, чем запрошено. `write` сразу отправляет данные в низкоуровневый дескриптор. Нет буферизации. |
|
||||
| Режим | Описание |
|
||||
|---------------|-------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `"default"` | Прямой режим. `read` может вернуть меньше байт, чем запрошено. `write` сразу отправляет данные в низкоуровневый дескриптор. Нет буферизации. |
|
||||
| `"yield"` | Как `default`, но при нехватке данных в `read(n)` поток будет вызывать `coroutine.yield()`, пока не соберёт ровно `n` байт. Удобно для корутин. |
|
||||
| `"buffered"` | Включает внутренние буферы чтения и записи. `read` берёт данные из буфера, `write` — складывает в буфер. При превышении `maxBufferSize` — ошибка `buffer overflow`. |
|
||||
| `"buffered"` | Включает внутренние буферы чтения и записи. `read` берёт данные из буфера , `write` — складывает в буфер. |
|
||||
|
||||
### flush
|
||||
|
||||
Работает только в режиме `"buffered"`,
|
||||
имеет два подрежима:
|
||||
|
||||
| Режим | Что делает `flush()` |
|
||||
|------------------|----------------------|
|
||||
| `"all"` (по умолчанию) | Сначала сбрасывает буфер записи → низкоуровневый `write`, затем вызывает `ioLib.flush(descriptor)` |
|
||||
| `"buffer"` | Сбрасывает только буфер записи, без вызова системного `flush` |
|
||||
| Режим | Что делает `flush()` |
|
||||
|------------------------|-----------------------------------------------------------------------------------------------------|
|
||||
| `"all"` (по умолчанию) | Сначала сбрасывает буфер записи → низкоуровневый `write`, затем вызывает `io_lib.flush(descriptor)` |
|
||||
| `"buffer"` | Сбрасывает только буфер записи, без вызова системного `flush` |
|
||||
|
||||
|
||||
### binary
|
||||
**Независимый флаг** (включается через `set_binary_mode(true)` или при создании потока).
|
||||
Определяет, в каком виде методы `read` и `write` принимают и возвращают данные:
|
||||
|
||||
| binary = true | binary = false (по умолчанию) |
|
||||
|-----------------------------------------|----------------------------------------|
|
||||
| binary = true | binary = false (по умолчанию) |
|
||||
|------------------------------------------------------------|----------------------------------------|
|
||||
| Данные — это **байты** (`Bytearray`, таблица чисел 0..255) | Данные — это **текстовые строки** |
|
||||
| `read(n)` → `Bytearray` или `table<number>`| `read()` → одна строка |
|
||||
| `read("i4 f")` → распаковка через `byteutil.unpack` | `read(n)` → n строк в таблице |
|
||||
| `write(Bytearray)` → запись байтов | `write("hello")` → строка + `\n` |
|
||||
| `write("i4", 42)` → `byteutil.pack` | `write({"a","b"})` → две строки с `\n`|
|
||||
| `read(n)` → `Bytearray` или `table<int>` | `read()` → одна строка |
|
||||
| `read("i4 f")` → распаковка через `byteutil.unpack` | `read(n)` → n строк в таблице |
|
||||
| `write(Bytearray)` → запись байтов | `write("hello")` → строка + `\n` |
|
||||
| `write("i4", 42)` → `byteutil.pack` | `write({"a","b"})` → две строки с `\n` |
|
||||
|
||||
`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 - number, то читает нужное кол-во строк с окончанием CRLF/LF из arg и возвращает ввиде таблицы. Также, если trimEmptyLines = true, то удаляет пустые строки с начала и конца из итоговой таблицы
|
||||
Если arg - int, то читает нужное кол-во строк с окончанием CRLF/LF из arg и возвращает ввиде таблицы. Также, если trim_empty_lines = true, то удаляет пустые строки с начала и конца из итоговой таблицы
|
||||
|
||||
Если arg не определён, то читает одну строку с окончанием CRLF/LF и возвращает её.
|
||||
--]]
|
||||
io_stream:read(
|
||||
[опционально] arg: number | string,
|
||||
[опционально] useTable | trimEmptyLines: boolean
|
||||
) -> Bytearray | table<number> | string | table<string> | ...
|
||||
[опционально] arg: int | string,
|
||||
[опционально] use_table = false | trim_empty_lines = true: boolean
|
||||
) -> Bytearray | table<int> | string | table<string> | ...
|
||||
|
||||
--[[
|
||||
Записывает данные в поток
|
||||
В двоичном режиме:
|
||||
Если arg - string, то функция интерпретирует arg как шаблон для byteutil, передаст его и ... в byteutil.pack и результат запишет в поток
|
||||
|
||||
Если arg - Bytearray | table<number>, то записывает байты в поток
|
||||
Если arg - Bytearray | table<int>, то записывает байты в поток
|
||||
|
||||
В текстовом режиме:
|
||||
Если arg - string, то записывает строку в поток (вместе с окончанием LF)
|
||||
|
|
@ -116,7 +116,7 @@ io_stream:read(
|
|||
Если arg - table<string>, то записывает каждую строку из таблицы отдельно
|
||||
--]]
|
||||
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(
|
||||
[опционально] useTable: boolean
|
||||
) -> Bytearray | table<number> | table<string> | string
|
||||
[опционально] use_table: boolean
|
||||
) -> Bytearray | table<int> | table<string> | string
|
||||
|
||||
--[[
|
||||
Устанавливает позицию в потоке
|
||||
Если length определён, то возвращает true, если length байт доступно к чтению. Иначе возвращает false.
|
||||
|
||||
Если не определён, то возвращает количество байт, которое можно прочитать.
|
||||
|
||||
В не буферизированном режиме потока может всегда возвращать 0 или false, если поток не поддерживает available.
|
||||
--]]
|
||||
io_stream:available(
|
||||
[опционально] length: int
|
||||
) -> int | boolean
|
||||
|
||||
--[[
|
||||
Устанавливает позицию в потоке (всегда в байтах)
|
||||
Режимы:
|
||||
b - Задаёт позицию относительно начало файла
|
||||
c - Задаёт позицию относительно текущей позиции
|
||||
e - Задаёт позицию относительно конца файла
|
||||
|
||||
Может бросать ошибку, если поток не поддерживает seek.
|
||||
--]]
|
||||
io_stream:seek(
|
||||
mode: string
|
||||
offset: number
|
||||
offset: int
|
||||
)
|
||||
|
||||
-- Возвращает текущую позицию в потоке от начала.
|
||||
-- Может бросать ошибку, если поток не поддерживает tell.
|
||||
io_stream:tell() -> int
|
||||
```
|
||||
|
||||
## Методы Buffered-режима
|
||||
|
||||
```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
|
||||
|
||||
-- Возвращает true, если поток открыт на данный момент
|
||||
io_stream:is_alive() -> bool
|
||||
|
||||
|
|
@ -185,16 +191,27 @@ io_stream:is_closed() -> bool
|
|||
io_stream:close()
|
||||
|
||||
-- Записывает все данные из write-буфера в поток в buffer/all flush-режимах
|
||||
-- Вызывает ioLib.flush() в all flush-режиме
|
||||
-- Вызывает ioLib.flush() в all flush-режиме, или ничего не делает, если
|
||||
-- ioLib не поддерживает 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)
|
||||
io_stream.new(
|
||||
descriptor: int,
|
||||
binaryMode: bool,
|
||||
ioLib: table,
|
||||
binary_mode: boolean,
|
||||
io_lib: table,
|
||||
[опционально] mode: string = "default",
|
||||
[опционально] flushMode: string = "all"
|
||||
[опционально] flush_mode: string = "all"
|
||||
) -> io_stream
|
||||
```
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
# Свойства и методы UI элементов
|
||||
|
||||
Обращение к UI элементов в скриптах произодится через экземпляр Document
|
||||
(переменная document) по id, указанному в xml.
|
||||
(глобальная переменная document) по id, указанному в xml.
|
||||
|
||||
Пример: вывод в консоль свойства pos элемента с id: "worlds-panel":
|
||||
```lua
|
||||
|
|
@ -53,7 +53,7 @@ document["worlds-panel"]:clear()
|
|||
| contentOffset | vec2 | да | *нет* | смещение содержимого |
|
||||
| cursor | string | да | да | курсор, отображаемый при наведении |
|
||||
| parent | Element | да | *нет* | родительский элемент или nil |
|
||||
| zIndex | bool | да | *нет* | значение z-индекса. В panel определяет порядок |
|
||||
| zIndex | int | да | *нет* | значение z-индекса. В panel определяет порядок |
|
||||
|
||||
Общие методы элементов:
|
||||
|
||||
|
|
|
|||
|
|
@ -215,12 +215,28 @@ local map = Heightmap(ширина, высота)
|
|||
Операции применяются ко всем значениям высоты.
|
||||
|
||||
```lua
|
||||
-- Приведение значений высот к абсолютным
|
||||
map:abs()
|
||||
|
||||
-- Округление к меньшему целому
|
||||
map:floor()
|
||||
|
||||
-- Округление к большему целому
|
||||
map:ceil()
|
||||
|
||||
-- Округление к ближайшему целому
|
||||
map:round()
|
||||
|
||||
-- Синус от значений высот (в радианах)
|
||||
map:sin()
|
||||
|
||||
-- Косинус от значений высот (в радианах)
|
||||
map:cos()
|
||||
|
||||
-- Тангенс от значений высот (в радианах)
|
||||
map:tan()
|
||||
```
|
||||
|
||||
Приводит значения высот к абсолютным.
|
||||
|
||||
|
||||
### Бинарные операции
|
||||
|
||||
Операции с применением второй карты или скаляра.
|
||||
|
|
|
|||
|
|
@ -43,6 +43,8 @@
|
|||
- `position-func` - поставщик позиции элемента (два числа), вызываемый при изменении размера контейнера, в котором находится элемент, либо при добавлении элемента в контейнер. Может быть вызван до вызова on_hud_open.
|
||||
- `size-func` - поставщик размера элемента (два числа), вызываемый при изменении размера контейнера, в котором находится элемент, либо при добавлении элемента в контейнер. Может быть вызван до вызова on_hud_open.
|
||||
- `onclick` - lua функция вызываемая при нажатии на элемент.
|
||||
- `onrightclick` - lua функция вызываемая при нажатии правой кнопки мыши на элемент.
|
||||
- `onmiddleclick` - lua функция вызываемая при нажатии средней кнопки мыши на элемент.
|
||||
- `ondoubleclick` - lua функция вызываемая при двойном нажатии на элемент.
|
||||
- `onfocus` - lua функция вызываемая при фокусировке на элемент.
|
||||
- `ondefocus` - lua функция вызываемая при потере фокуса элеметом.
|
||||
|
|
@ -193,6 +195,9 @@
|
|||
- `width` - минимальная ширина содержимого. По-умолчанию: 100.
|
||||
- `selected` - изначально выбранное значение. По-умолчанию: "".
|
||||
- `onselect` - функция, в которую передаётся выбранное пользователем значение
|
||||
- `mode` - режим работы:
|
||||
- `select` - режим по-умолчанию.
|
||||
- `button` - режим кнопки: текст не заменяется на выбранную опцию, а элемент выглядит как обычная кнопка.
|
||||
|
||||
## Привязка ввода - *bindbox*
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
generator = "base:demo"
|
||||
player-entity = "base:player"
|
||||
hand-skeleton = "base:hand"
|
||||
block-material = "base:stone"
|
||||
|
|
|
|||
|
|
@ -86,12 +86,27 @@ function generate_heightmap(x, y, w, h, s, inputs)
|
|||
|
||||
local rivermap = Heightmap(w, h)
|
||||
rivermap.noiseSeed = SEED
|
||||
rivermap:noise({x+21, y+12}, 0.1*s, 4)
|
||||
rivermap:abs()
|
||||
rivermap:mul(2.0)
|
||||
rivermap:pow(0.15)
|
||||
rivermap:max(0.5)
|
||||
map:mul(rivermap)
|
||||
rivermap:noise({ x + 21, y + 12 }, 0.1 * s, 4)
|
||||
rivermap:pow(4)
|
||||
rivermap:mul(3500)
|
||||
rivermap:add(1)
|
||||
rivermap:pow(-1)
|
||||
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)
|
||||
desertmap.noiseSeed = SEED
|
||||
|
|
|
|||
|
|
@ -1,25 +1,91 @@
|
|||
local util = {}
|
||||
|
||||
function util.drop(ppos, itemid, count, data, pickup_delay)
|
||||
if itemid == 0 or not itemid then
|
||||
return nil
|
||||
util.DROP_FORCE = 8
|
||||
util.DROP_INIT_VEL = { 0, 3, 0 }
|
||||
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
|
||||
return entities.spawn("base:drop", ppos, {base__drop={
|
||||
id=itemid,
|
||||
count=count,
|
||||
data=data,
|
||||
pickup_delay=pickup_delay
|
||||
}})
|
||||
local yaw, pitch = player.get_rot(pid)
|
||||
local q_yaw = quat.from_euler({ 0, yaw, 0 })
|
||||
local q_pitch = quat.from_euler({ pitch, 0, 0 })
|
||||
local q_rotation = quat.mul(q_yaw, q_pitch)
|
||||
|
||||
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
|
||||
|
||||
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 = {}
|
||||
for _, loot in ipairs(loot_table) do
|
||||
local chance = loot.chance or 1
|
||||
local count = loot.count or 1
|
||||
|
||||
|
||||
local roll = math.random()
|
||||
|
||||
|
||||
if roll < chance then
|
||||
if loot.min and loot.max then
|
||||
count = math.random(loot.min, loot.max)
|
||||
|
|
@ -27,7 +93,9 @@ local function calc_loot(loot_table)
|
|||
if count == 0 then
|
||||
goto continue
|
||||
end
|
||||
table.insert(results, {item=item.index(loot.item), count=count})
|
||||
table.insert(results, {
|
||||
item = item.index(loot.item), count = count
|
||||
})
|
||||
end
|
||||
::continue::
|
||||
end
|
||||
|
|
@ -37,9 +105,9 @@ end
|
|||
function util.block_loot(blockid)
|
||||
local lootscheme = block.properties[blockid]["base:loot"]
|
||||
if lootscheme then
|
||||
return calc_loot(lootscheme)
|
||||
return util.calc_loot(lootscheme)
|
||||
end
|
||||
return {{item=block.get_picking_item(blockid), count=1}}
|
||||
return { { item = block.get_picking_item(blockid), count = 1 } }
|
||||
end
|
||||
|
||||
return util
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ dropitem = ARGS
|
|||
if dropitem.item then
|
||||
dropitem.id = item.index(dropitem.item)
|
||||
end
|
||||
if dropitem then
|
||||
if dropitem then
|
||||
timer = dropitem.pickup_delay or timer
|
||||
end
|
||||
if SAVED_DATA.item then
|
||||
|
|
@ -26,6 +26,7 @@ function on_save()
|
|||
end
|
||||
|
||||
if vc.is_client() then
|
||||
local SCALE = 0.3
|
||||
local scale = {1, 1, 1}
|
||||
local rotation = mat4.rotate({
|
||||
math.random(), math.random(), math.random()
|
||||
|
|
@ -35,7 +36,7 @@ if vc.is_client() then
|
|||
local matrix = mat4.idt()
|
||||
rig:set_model(0, item.model_name(dropitem.id))
|
||||
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.scale(matrix, scale, matrix)
|
||||
rig:set_matrix(0, matrix)
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
local dirtid = block.index("base:dirt")
|
||||
local grass_blockid = block.index("base:grass_block")
|
||||
|
||||
local offsets = {}
|
||||
for lx=-1,1 do
|
||||
for ly=-1,1 do
|
||||
|
|
@ -8,18 +11,17 @@ for lx=-1,1 do
|
|||
end
|
||||
|
||||
function on_random_update(x, y, z)
|
||||
local dirtid = block.index('base:dirt');
|
||||
if block.is_solid_at(x, y+1, z) then
|
||||
block.set(x, y, z, dirtid, 0)
|
||||
return
|
||||
end
|
||||
local grassblockid = block.index('base:grass_block')
|
||||
|
||||
for _, offset in ipairs(offsets) do
|
||||
local nx, ny, nz = x + offset[1], y + offset[2], z + offset[3]
|
||||
|
||||
if block.get(nx, ny, nz) == dirtid and
|
||||
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
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,30 +1,26 @@
|
|||
local base_util = require "util"
|
||||
|
||||
local DROP_FORCE = 8
|
||||
local DROP_INIT_VEL = {0, 3, 0}
|
||||
|
||||
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
|
||||
return
|
||||
end
|
||||
local pid = hud.get_player()
|
||||
local invid, slot = player.get_inventory(pid)
|
||||
local itemid, itemcount = inventory.get(invid, slot)
|
||||
if itemid == 0 then
|
||||
return
|
||||
local mode = 1
|
||||
if input.is_pressed("key:left-ctrl") or input.is_pressed("key:right-ctrl") then
|
||||
mode = 0
|
||||
end
|
||||
local data = inventory.get_all_data(invid, slot)
|
||||
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)
|
||||
base_util.drop_from_slot(pid, invid, slot, mode)
|
||||
end)
|
||||
rules.create("do-loot-non-player", true)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -16,6 +16,4 @@ function on_block_broken(id, x, y, z, playerid)
|
|||
spawn_spread=vec3.mul(size, 0.4)
|
||||
})
|
||||
end
|
||||
|
||||
rules.create("do-loot-non-player", true)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@
|
|||
color="#FFFFFF80" size="16" pos="4,6"
|
||||
hover-color="#1080FF"></image>
|
||||
<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"
|
||||
enabled="false" interactive="true"
|
||||
hover-color="#1080FF"
|
||||
|
|
@ -31,6 +31,12 @@
|
|||
hover-color="#1080FF"
|
||||
onclick="run_current_file()"
|
||||
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>
|
||||
<label id="title" pos="26,8"></label>
|
||||
</container>
|
||||
|
|
|
|||
|
|
@ -113,6 +113,78 @@ local function reload_model(filename, name)
|
|||
assets.parse_model(file.ext(filename), document.editor.text, name)
|
||||
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()
|
||||
if not current_file.filename then
|
||||
return
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ local function update_line(line, uptime)
|
|||
end
|
||||
end
|
||||
|
||||
events.on("core:chat", function(message)
|
||||
local core_chat_handler = events.on("core:chat", function(message)
|
||||
while #lines >= max_lines do
|
||||
document[lines[1][1]]:destruct()
|
||||
table.remove(lines, 1)
|
||||
|
|
@ -36,7 +36,7 @@ end)
|
|||
function on_open()
|
||||
if not initialized then
|
||||
initialized = true
|
||||
|
||||
|
||||
document.root:setInterval(1/animation_fps * 1000, function ()
|
||||
local uptime = time.uptime()
|
||||
for _, line in ipairs(lines) do
|
||||
|
|
@ -52,3 +52,7 @@ function on_open()
|
|||
end)
|
||||
end
|
||||
end
|
||||
|
||||
function on_destroy()
|
||||
events.remove("core:chat", core_chat_handler)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<inventory>
|
||||
<slots-grid pos="0, 164" rows="1" count="10" sharefunc="inventory_share_func"/>
|
||||
<slots-grid rows="3" start-index="10" count="30" 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="share_func"/>
|
||||
</inventory>
|
||||
|
|
|
|||
|
|
@ -1,12 +1 @@
|
|||
function inventory_share_func(invid, slotid)
|
||||
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
|
||||
share_func = require("core:inventory_utils").share_default_func
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ function on_open(params)
|
|||
mode = params.mode
|
||||
end
|
||||
refresh()
|
||||
document.search_textbox.focused = true
|
||||
end
|
||||
|
||||
-- add - packs to be added to the world (after apply)
|
||||
|
|
@ -178,40 +179,40 @@ function place_pack(panel, packinfo, callback, position_func)
|
|||
end
|
||||
end
|
||||
|
||||
local Version = {};
|
||||
local Version = {}
|
||||
|
||||
function Version.matches_pattern(version)
|
||||
for _, letter in string.gmatch(version, "%.+") do
|
||||
if type(letter) ~= "number" or letter ~= "." then
|
||||
return false;
|
||||
end
|
||||
local t = string.split(version, ".")
|
||||
if #t ~= 2 and #t ~= 3 then return false end
|
||||
|
||||
local t = string.split(version, ".");
|
||||
|
||||
return #t == 2 or #t == 3;
|
||||
for i = 1, #t do
|
||||
local matched = string.match(t[i], "^%d+$")
|
||||
if not matched then return false end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
function Version.__greater(ver1, ver2)
|
||||
if ver1[1] ~= ver2[1] then return ver1[1] > ver2[1] end;
|
||||
if ver1[2] ~= ver2[2] then return ver1[2] > ver2[2] end;
|
||||
return ver1[3] > ver2[3];
|
||||
if ver1[1] ~= ver2[1] then return ver1[1] > ver2[1] end
|
||||
if ver1[2] ~= ver2[2] then return ver1[2] > ver2[2] end
|
||||
return ver1[3] > ver2[3]
|
||||
end
|
||||
|
||||
function Version.__less(ver1, ver2)
|
||||
return Version.__greater(ver2, ver1);
|
||||
return Version.__greater(ver2, ver1)
|
||||
end
|
||||
|
||||
function Version.__greater_or_equal(ver1, ver2)
|
||||
return not Version.__less(ver1, ver2);
|
||||
return not Version.__less(ver2, ver1)
|
||||
end
|
||||
|
||||
function Version.__less_or_equal(ver1, ver2)
|
||||
return not Version.__greater(ver1, ver2);
|
||||
return not Version.__greater(ver1, ver2)
|
||||
end
|
||||
|
||||
Version.operators = {
|
||||
|
|
@ -223,40 +224,39 @@ Version.operators = {
|
|||
}
|
||||
|
||||
function Version.compare(op, ver1, ver2)
|
||||
ver1 = string.split(ver1, ".");
|
||||
ver2 = string.split(ver2, ".");
|
||||
ver1 = string.split(ver1, ".")
|
||||
ver2 = string.split(ver2, ".")
|
||||
|
||||
local comparison_func = Version.operators[op];
|
||||
|
||||
if comparison_func then
|
||||
return comparison_func(ver1, ver2);
|
||||
return comparison_func(ver1, ver2)
|
||||
else
|
||||
return false;
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
function Version.parse(version)
|
||||
local op = string.sub(version, 1, 2);
|
||||
local op = string.sub(version, 1, 2)
|
||||
if op == ">=" or op == "<=" then
|
||||
return op, string.sub(version, #op + 1);
|
||||
return op, string.sub(version, #op + 1)
|
||||
end
|
||||
|
||||
op = string.sub(version, 1, 1);
|
||||
if op == ">" or op == "<" then
|
||||
return op, string.sub(version, #op + 1);
|
||||
return op, string.sub(version, #op + 1)
|
||||
end
|
||||
|
||||
return "=", version;
|
||||
return "=", version
|
||||
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
|
||||
local op, dep_ver = Version.parse_version(dependent_version);
|
||||
Version.compare(op, dep_ver, actual_version);
|
||||
return Version.compare(op, dep_ver, actual_version)
|
||||
elseif dependent_version == "*" or dependent_version == actual_version then
|
||||
return true;
|
||||
return true
|
||||
else
|
||||
return false;
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -276,15 +276,15 @@ function check_dependencies(packinfo)
|
|||
)
|
||||
end
|
||||
|
||||
local dep_pack = pack.get_info(depid);
|
||||
|
||||
if not compare_version(depver, dep_pack.version) then
|
||||
local op, ver = Version.parse(depver)
|
||||
local dep_pack = pack.get_info(depid)
|
||||
local op, ver = Version.parse(depver)
|
||||
|
||||
if not compare_version(op, ver, dep_pack.version) then
|
||||
return string.format(
|
||||
"%s: %s != %s (%s)",
|
||||
gui.str("error.dependency-version-not-met"),
|
||||
dep_pack.version, ver, depid
|
||||
);
|
||||
)
|
||||
end
|
||||
|
||||
if table.has(packs_installed, packinfo.id) then
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
@World generator
|
||||
</button>
|
||||
<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
|
||||
</button>
|
||||
<button onclick='menu:back()'>@Back</button>
|
||||
|
|
|
|||
|
|
@ -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}">
|
||||
<label color='#FFFFFF80' size='300,25' align='right' gravity='top-right'>
|
||||
[%{id_verbose}]
|
||||
<label color='#FFFFFF80' size='340,25' align='right' gravity='top-right'>
|
||||
[%{id_verbose} %{version}]
|
||||
</label>
|
||||
<label pos='78,6'>%{title}</label>
|
||||
<label if='%{creator}' color='#CCFFE5B2' size='300,20' align='right'
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
@box from (0,0,0) to (1,0.5,1) delete (top,south)
|
||||
@box from (0,0.5,0) to (1,1,0.5) delete (bottom,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,north)
|
||||
@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"
|
||||
|
|
|
|||
|
|
@ -4,12 +4,14 @@ local FFI = ffi
|
|||
|
||||
FFI.cdef[[
|
||||
void* malloc(size_t);
|
||||
void* realloc(void*, size_t);
|
||||
void free(void*);
|
||||
typedef struct {
|
||||
unsigned char* bytes;
|
||||
int size;
|
||||
int capacity;
|
||||
} bytearray_t;
|
||||
void* memmove(void*, const void*, size_t);
|
||||
]]
|
||||
|
||||
local free = FFI.C.free
|
||||
|
|
@ -22,12 +24,33 @@ local GC_THRESHOLD = 50 * 1024 * 1024
|
|||
|
||||
local function malloc(size)
|
||||
local raw = FFI.C.malloc(size)
|
||||
if raw == nil then
|
||||
return nil
|
||||
if not raw then
|
||||
error("failed memory allocation of " .. size .. " bytes")
|
||||
end
|
||||
|
||||
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
|
||||
collectgarbage("step", 200)
|
||||
allocated_bytes = 0
|
||||
|
|
@ -38,23 +61,17 @@ end
|
|||
|
||||
local function grow_buffer(self, elems)
|
||||
local new_capacity = math.ceil(self.capacity / 0.75 + elems)
|
||||
local prev = self.bytes
|
||||
self.bytes = malloc(new_capacity)
|
||||
FFI.copy(self.bytes, prev, self.size)
|
||||
self.bytes = realloc(self.bytes, new_capacity)
|
||||
self.capacity = new_capacity
|
||||
free(prev)
|
||||
end
|
||||
|
||||
local function trim_buffer(self)
|
||||
if self.size == self.capacity then
|
||||
return
|
||||
if self.size == self.capacity then return end
|
||||
local new_bytes = realloc(self.bytes, self.size, false)
|
||||
if new_bytes then
|
||||
self.bytes = new_bytes
|
||||
self.capacity = self.size
|
||||
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
|
||||
|
||||
local function count_elements(b)
|
||||
|
|
@ -125,15 +142,21 @@ local function clear(self)
|
|||
self.size = 0
|
||||
end
|
||||
|
||||
local function reserve(self, new_capacity)
|
||||
if new_capacity <= self.capacity then
|
||||
return
|
||||
local function fill(self, index, size, byte)
|
||||
if index ~= nil and size ~= nil and size > 0 then
|
||||
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
|
||||
local prev = self.bytes
|
||||
self.bytes = malloc(new_capacity)
|
||||
FFI.copy(self.bytes, prev, self.size)
|
||||
end
|
||||
|
||||
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
|
||||
free(prev)
|
||||
end
|
||||
|
||||
local function get_capacity(self)
|
||||
|
|
@ -150,22 +173,46 @@ local function slice(self, offset, length)
|
|||
length = self.size - offset + 1
|
||||
end
|
||||
local buffer = malloc(length)
|
||||
if not buffer then
|
||||
error("malloc(" .. length .. ") returned NULL")
|
||||
end
|
||||
FFI.copy(buffer, self.bytes + (offset - 1), length)
|
||||
return bytearray_type(buffer, length, length)
|
||||
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 = {
|
||||
append=append,
|
||||
insert=insert,
|
||||
remove=remove,
|
||||
trim=trim_buffer,
|
||||
clear=clear,
|
||||
reserve=reserve,
|
||||
get_capacity=get_capacity,
|
||||
slice=slice,
|
||||
append = append,
|
||||
insert = insert,
|
||||
remove = remove,
|
||||
trim = trim_buffer,
|
||||
clear = clear,
|
||||
reserve = reserve,
|
||||
get_capacity = get_capacity,
|
||||
slice = slice,
|
||||
fill = fill,
|
||||
copy = copy,
|
||||
move = move
|
||||
}
|
||||
|
||||
local bytearray_mt = {
|
||||
|
|
@ -256,7 +303,8 @@ local function FFIBytearray_as_string(bytes)
|
|||
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 FFIview_mt = {
|
||||
__index = function(self, key)
|
||||
|
|
@ -291,27 +339,44 @@ local function create_FFIview_class(name, typename, typesize)
|
|||
return i, ptr[i - 1]
|
||||
end
|
||||
end
|
||||
end
|
||||
end,
|
||||
}
|
||||
return function (bytes)
|
||||
local x = setmetatable({
|
||||
bytes=bytes,
|
||||
}, FFIview_mt)
|
||||
return x
|
||||
end
|
||||
return setmetatable({}, {
|
||||
__call = function (self, bytes)
|
||||
local x = setmetatable({
|
||||
bytes=bytes,
|
||||
}, FFIview_mt)
|
||||
return x
|
||||
end,
|
||||
__index = {
|
||||
typesize = function()
|
||||
return typesize
|
||||
end
|
||||
}
|
||||
})
|
||||
end
|
||||
|
||||
local FFII16view = create_FFIview_class("FFII16view", "int16_t", 2)
|
||||
local FFIU16view = create_FFIview_class("FFIU16view", "uint16_t", 2)
|
||||
local FFII32view = create_FFIview_class("FFII32view", "int32_t", 4)
|
||||
local FFIU32view = create_FFIview_class("FFIU32view", "uint32_t", 4)
|
||||
local FFII8view = create_FFIview_class("FFII8view", "int8_t")
|
||||
local FFII16view = create_FFIview_class("FFII16view", "int16_t")
|
||||
local FFIU16view = create_FFIview_class("FFIU16view", "uint16_t")
|
||||
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 {
|
||||
FFIBytearray = setmetatable(FFIBytearray, FFIBytearray),
|
||||
FFIBytearray_as_string = FFIBytearray_as_string,
|
||||
FFIBytearray_as_ptr = FFIBytearray_as_ptr,
|
||||
FFII8view = FFII8view,
|
||||
FFIU16view = FFIU16view,
|
||||
FFII16view = FFII16view,
|
||||
FFIU32view = FFIU32view,
|
||||
FFII32view = FFII32view,
|
||||
FFIU64view = FFIU64view,
|
||||
FFII64view = FFII64view,
|
||||
FFIFLTview = FFIFLTview,
|
||||
FFIDBLview = FFIDBLview,
|
||||
}
|
||||
|
|
|
|||
38
res/modules/internal/ctypes.lua
Normal file
38
res/modules/internal/ctypes.lua
Normal 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
|
||||
|
|
@ -148,3 +148,29 @@ function error(message, level)
|
|||
end
|
||||
__error(message, level)
|
||||
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
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ function events.on(event, func)
|
|||
events.handlers[event] = {}
|
||||
end
|
||||
table.insert(events.handlers[event], func)
|
||||
return func
|
||||
end
|
||||
|
||||
function events.reset(event, func)
|
||||
|
|
@ -24,6 +25,14 @@ function events.reset(event, func)
|
|||
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)
|
||||
for name, handlers in pairs(events.handlers) do
|
||||
local actualname = name
|
||||
|
|
|
|||
|
|
@ -23,6 +23,10 @@ function file.parent(path)
|
|||
return dir
|
||||
end
|
||||
|
||||
function file.remove_ext(path)
|
||||
return string.format("%s/%s", file.parent(path), file.stem(path))
|
||||
end
|
||||
|
||||
function file.path(path)
|
||||
local pos = path:find(':')
|
||||
return path:sub(pos + 1)
|
||||
|
|
|
|||
|
|
@ -35,3 +35,89 @@ function math.sum(...)
|
|||
|
||||
return sum
|
||||
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
|
||||
|
|
|
|||
|
|
@ -59,11 +59,7 @@ end
|
|||
|
||||
function table.extend(t1, t2)
|
||||
for i, v in pairs(t2) do
|
||||
if type(i) == "number" then
|
||||
t1[#t1 + 1] = v
|
||||
else
|
||||
t1[i] = v
|
||||
end
|
||||
t1[i] = v
|
||||
end
|
||||
|
||||
return t1
|
||||
|
|
@ -201,3 +197,11 @@ function table.tostring(t)
|
|||
end
|
||||
return s..']'
|
||||
end
|
||||
|
||||
function table.keys(t)
|
||||
local keys = {}
|
||||
for key, _ in pairs(t) do
|
||||
table.insert(keys, key)
|
||||
end
|
||||
return keys
|
||||
end
|
||||
|
|
|
|||
|
|
@ -77,15 +77,18 @@ function Element.new(docname, name)
|
|||
end
|
||||
|
||||
-- the engine automatically creates an instance for every ui document (layout)
|
||||
local Document = {}
|
||||
function Document.new(docname)
|
||||
return setmetatable({name=docname}, {
|
||||
__index=function(self, k)
|
||||
local elem = Element.new(self.name, k)
|
||||
rawset(self, k, elem)
|
||||
return elem
|
||||
local Document = {
|
||||
__index=function(self, k)
|
||||
if type(k) ~= "string" then
|
||||
error("element id is not a string")
|
||||
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
|
||||
|
||||
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'>
|
||||
<panel color='#507090E0' size='600' padding='16'
|
||||
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_no()'>@%s</button>
|
||||
</panel>
|
||||
</container>
|
||||
]], id, string.escape_xml(text)), {on_yes=yes_callback, on_no=no_callback},
|
||||
string.escape_xml(yes_text), string.escape_xml(no_text))
|
||||
]], id,
|
||||
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])
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
local rules = {nexid = 1, rules = {}}
|
||||
local rules = {nextid = 1, rules = {}}
|
||||
|
||||
function rules.get_rule(name)
|
||||
local rule = rules.rules[name]
|
||||
|
|
@ -27,13 +27,13 @@ end
|
|||
|
||||
function rules.reset(name)
|
||||
local rule = rules.get_rule(name)
|
||||
rules.set(rule.default)
|
||||
rules.set(name, rule.default)
|
||||
end
|
||||
|
||||
function rules.listen(name, handler)
|
||||
local rule = rules.get_rule(name)
|
||||
local id = rules.nexid
|
||||
rules.nextid = rules.nexid + 1
|
||||
local id = rules.nextid
|
||||
rules.nextid = rules.nextid + 1
|
||||
rule.listeners[utf8.encode(id)] = handler
|
||||
return id
|
||||
end
|
||||
|
|
|
|||
|
|
@ -129,7 +129,7 @@ return {
|
|||
goto continue
|
||||
end
|
||||
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
|
||||
local result, err = pcall(callback, tps)
|
||||
if err then
|
||||
|
|
@ -143,7 +143,7 @@ return {
|
|||
physics_update = function(delta)
|
||||
for uid, entity in pairs(entities) 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
|
||||
local result, err = pcall(callback, delta)
|
||||
if err then
|
||||
|
|
@ -156,7 +156,7 @@ return {
|
|||
render = function(delta)
|
||||
for _,entity in pairs(entities) 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
|
||||
local result, err = pcall(callback, delta)
|
||||
if err then
|
||||
|
|
|
|||
131
res/modules/internal/stream_providers/bytearray.lua
Normal file
131
res/modules/internal/stream_providers/bytearray.lua
Normal 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
|
||||
|
|
@ -4,15 +4,28 @@ local lib = {
|
|||
read = file.__read_descriptor,
|
||||
write = file.__write_descriptor,
|
||||
seek = file.__seek_descriptor,
|
||||
tell = file.__tell_descriptor,
|
||||
flush = file.__flush_descriptor,
|
||||
available = file.__available_descriptor,
|
||||
is_alive = file.__has_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 io_stream.new(
|
||||
file.__open_descriptor(path, mode),
|
||||
__open_descriptor(path, mode),
|
||||
mode:find('b') ~= nil,
|
||||
lib
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ int close(int fd);
|
|||
ssize_t read(int fd, void *buf, size_t count);
|
||||
ssize_t write(int fd, const void *buf, size_t count);
|
||||
int fcntl(int fd, int cmd, ...);
|
||||
int ioctl(int fd, unsigned long request, ...);
|
||||
|
||||
const char *strerror(int errnum);
|
||||
]]
|
||||
|
|
@ -20,6 +21,7 @@ local O_WRONLY = 0x1
|
|||
local O_RDWR = 0x2
|
||||
local O_NONBLOCK = 0x800
|
||||
local F_GETFL = 3
|
||||
local FIONREAD = 0x541B
|
||||
|
||||
local function getError()
|
||||
local err = FFI.errno()
|
||||
|
|
@ -58,12 +60,16 @@ function lib.write(fd, bytearray)
|
|||
end
|
||||
end
|
||||
|
||||
function lib.seek(fd, mode, offset)
|
||||
error("cannot seek the named pipe")
|
||||
end
|
||||
function lib.available(fd)
|
||||
if fd == nil or fd < 0 then return 0 end
|
||||
|
||||
function lib.flush(fd)
|
||||
-- no flush on unix
|
||||
local bytes_ready = FFI.new("int[1]", 0)
|
||||
|
||||
if C.ioctl(fd, FIONREAD, bytes_ready) == -1 then
|
||||
return 0
|
||||
end
|
||||
|
||||
return tonumber(bytes_ready[0])
|
||||
end
|
||||
|
||||
function lib.is_alive(fd)
|
||||
|
|
|
|||
|
|
@ -97,14 +97,26 @@ function lib.write(handle, bytearray)
|
|||
end
|
||||
end
|
||||
|
||||
function lib.seek(handle, mode, offset)
|
||||
error("cannot seek the named pipe")
|
||||
end
|
||||
|
||||
function lib.flush(handle)
|
||||
C.FlushFileBuffers(handle)
|
||||
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)
|
||||
if handle == nil or handle == INVALID_HANDLE_VALUE then
|
||||
return false
|
||||
|
|
|
|||
64
res/modules/internal/stream_providers/socket.lua
Normal file
64
res/modules/internal/stream_providers/socket.lua
Normal 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
|
||||
139
res/modules/internal/test.lua
Normal file
139
res/modules/internal/test.lua
Normal 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
|
||||
21
res/modules/inventory_utils.lua
Normal file
21
res/modules/inventory_utils.lua
Normal 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
|
||||
|
|
@ -25,13 +25,13 @@ local ALL_FLUSH_MODES = {
|
|||
local CR = string.byte('\r')
|
||||
local LF = string.byte('\n')
|
||||
|
||||
local function readFully(result, readFunc)
|
||||
local function read_fully(result, read_func)
|
||||
local isTable = type(result) == "table"
|
||||
|
||||
local buf
|
||||
|
||||
repeat
|
||||
buf = readFunc(MAX_BUFFER_SIZE)
|
||||
buf = read_func(MAX_BUFFER_SIZE)
|
||||
|
||||
if isTable then
|
||||
for i = 1, #buf do
|
||||
|
|
@ -42,41 +42,46 @@ local function readFully(result, readFunc)
|
|||
end
|
||||
|
||||
--[[
|
||||
|
||||
descriptor - descriptor of stream for provided I/O library
|
||||
binaryMode - if enabled, most methods will expect bytes instead of strings
|
||||
ioLib - I/O library. Should include the following functions:
|
||||
read(descriptor: int, length: int) -> Bytearray
|
||||
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)
|
||||
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
|
||||
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
|
||||
flushMode = flushMode or FLUSH_MODE_ALL
|
||||
flush_mode = flush_mode or FLUSH_MODE_ALL
|
||||
|
||||
local self = setmetatable({}, io_stream)
|
||||
|
||||
self.descriptor = descriptor
|
||||
self.binaryMode = binaryMode
|
||||
self.maxBufferSize = MAX_BUFFER_SIZE
|
||||
self.ioLib = ioLib
|
||||
self.binary_mode = binary_mode
|
||||
self.max_buffer_size = MAX_BUFFER_SIZE
|
||||
self.io_lib = io_lib
|
||||
|
||||
self:set_mode(mode)
|
||||
self:set_flush_mode(flushMode)
|
||||
self:set_flush_mode(flush_mode)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function io_stream:is_binary_mode()
|
||||
return self.binaryMode
|
||||
return self.binary_mode
|
||||
end
|
||||
|
||||
function io_stream:set_binary_mode(binaryMode)
|
||||
self.binaryMode = binaryMode ~= nil
|
||||
function io_stream:set_binary_mode(binary_mode)
|
||||
self.binary_mode = binary_mode ~= nil
|
||||
end
|
||||
|
||||
function io_stream:get_mode()
|
||||
|
|
@ -88,124 +93,141 @@ function io_stream:set_mode(mode)
|
|||
error("invalid stream mode: "..mode)
|
||||
end
|
||||
|
||||
if self.mode == BUFFERED_MODE then
|
||||
self.writeBuffer:clear()
|
||||
self.readBuffer:clear()
|
||||
if self.write_buffer then
|
||||
self.write_buffer:clear()
|
||||
self.write_buffer = nil
|
||||
end
|
||||
|
||||
if mode == BUFFERED_MODE and not self.writeBuffer then
|
||||
self.writeBuffer = Bytearray()
|
||||
self.readBuffer = Bytearray()
|
||||
if self.read_buffer then
|
||||
self.read_buffer:clear()
|
||||
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
|
||||
|
||||
self.mode = mode
|
||||
end
|
||||
|
||||
function io_stream:get_flush_mode()
|
||||
return self.flushMode
|
||||
return self.flush_mode
|
||||
end
|
||||
|
||||
function io_stream:set_flush_mode(flushMode)
|
||||
if not table.has(ALL_FLUSH_MODES, flushMode) then
|
||||
error("invalid flush mode: "..flushMode)
|
||||
function io_stream:set_flush_mode(flush_mode)
|
||||
if not table.has(ALL_FLUSH_MODES, flush_mode) then
|
||||
error("invalid flush mode: " .. flush_mode)
|
||||
end
|
||||
|
||||
self.flushMode = flushMode
|
||||
self.flush_mode = flush_mode
|
||||
end
|
||||
|
||||
function io_stream:get_max_buffer_size()
|
||||
return self.maxBufferSize
|
||||
return self.max_buffer_size
|
||||
end
|
||||
|
||||
function io_stream:set_max_buffer_size(maxBufferSize)
|
||||
self.maxBufferSize = maxBufferSize
|
||||
function io_stream:set_max_buffer_size(max_buffer_size)
|
||||
self.max_buffer_size = max_buffer_size
|
||||
|
||||
self.write_buffer = Bytearray(self.max_buffer_size)
|
||||
self.read_buffer = Bytearray(self.max_buffer_size)
|
||||
end
|
||||
|
||||
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
|
||||
self:__update_read_buffer()
|
||||
available = available + #self.read_buffer
|
||||
end
|
||||
|
||||
if not length then
|
||||
return #self.readBuffer
|
||||
else
|
||||
return #self.readBuffer >= length
|
||||
end
|
||||
if not length then
|
||||
return available
|
||||
else
|
||||
return available >= length
|
||||
end
|
||||
end
|
||||
|
||||
function io_stream:__update_read_buffer()
|
||||
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)
|
||||
function io_stream:__read(length, from_read_fully)
|
||||
if self.mode == YIELD_MODE then
|
||||
if from_read_fully then
|
||||
return self.io_lib.read(self.descriptor, length)
|
||||
end
|
||||
|
||||
local buffer = Bytearray()
|
||||
|
||||
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
|
||||
end
|
||||
|
||||
return buffer
|
||||
elseif self.mode == BUFFERED_MODE then
|
||||
self:__update_read_buffer()
|
||||
local buf_len = #self.read_buffer
|
||||
|
||||
if #self.readBuffer < length then
|
||||
error "buffer underflow"
|
||||
if buf_len < length then
|
||||
self.read_buffer:append(
|
||||
self.io_lib.read(self.descriptor, self.max_buffer_size - buf_len)
|
||||
)
|
||||
end
|
||||
|
||||
local copy
|
||||
buf_len = #self.read_buffer
|
||||
|
||||
if #self.readBuffer == length then
|
||||
copy = Bytearray()
|
||||
|
||||
copy:append(self.readBuffer)
|
||||
length = math.min(buf_len, length)
|
||||
|
||||
self.readBuffer:clear()
|
||||
local copy = self.read_buffer:slice(1, length)
|
||||
|
||||
if buf_len == length then
|
||||
self.read_buffer:clear()
|
||||
else
|
||||
copy = Bytearray()
|
||||
|
||||
for i = 1, length do
|
||||
copy[i] = self.readBuffer[i]
|
||||
end
|
||||
|
||||
self.readBuffer:remove(1, length)
|
||||
self.read_buffer:remove(1, length)
|
||||
end
|
||||
|
||||
return copy
|
||||
elseif self.mode == DEFAULT_MODE then
|
||||
return self.ioLib.read(self.descriptor, length)
|
||||
return self.io_lib.read(self.descriptor, length)
|
||||
end
|
||||
end
|
||||
|
||||
function io_stream:__write(data)
|
||||
if self.mode == BUFFERED_MODE then
|
||||
self.writeBuffer:append(data)
|
||||
local data_length = #data
|
||||
|
||||
if #self.writeBuffer > self.maxBufferSize then
|
||||
error "buffer overflow"
|
||||
if #self.write_buffer + data_length > self.max_buffer_size then
|
||||
self:flush()
|
||||
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
|
||||
return self.ioLib.write(self.descriptor, data)
|
||||
return self.io_lib.write(self.descriptor, data)
|
||||
end
|
||||
end
|
||||
|
||||
function io_stream:read_fully(useTable)
|
||||
if self.binaryMode then
|
||||
local result = useTable and Bytearray() or { }
|
||||
function io_stream:read_fully(use_table)
|
||||
if self.binary_mode then
|
||||
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
|
||||
if useTable then
|
||||
if use_table then
|
||||
local lines = { }
|
||||
|
||||
local line
|
||||
|
|
@ -220,7 +242,7 @@ function io_stream:read_fully(useTable)
|
|||
else
|
||||
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)
|
||||
end
|
||||
|
|
@ -262,57 +284,61 @@ function io_stream:write_line(str)
|
|||
self:__write(utf8.tobytes(str .. "\n"))
|
||||
end
|
||||
|
||||
function io_stream:read(arg, useTable)
|
||||
local argType = type(arg)
|
||||
function io_stream:read(arg, use_table)
|
||||
local arg_type = type(arg)
|
||||
|
||||
if self.binaryMode then
|
||||
local byteArr
|
||||
if self.binary_mode then
|
||||
local byte_arr
|
||||
|
||||
if argType == "number" then
|
||||
if arg_type == "number" then
|
||||
-- using 'arg' as length
|
||||
|
||||
byteArr = self:__read(arg)
|
||||
byte_arr = self:__read(arg)
|
||||
|
||||
if useTable == true then
|
||||
if use_table == true then
|
||||
local t = { }
|
||||
|
||||
for i = 1, #byteArr do
|
||||
t[i] = byteArr[i]
|
||||
for i = 1, #byte_arr do
|
||||
t[i] = byte_arr[i]
|
||||
end
|
||||
|
||||
return t
|
||||
else
|
||||
return byteArr
|
||||
return byte_arr
|
||||
end
|
||||
elseif argType == "string" then
|
||||
elseif arg_type == "string" then
|
||||
return byteutil.unpack(
|
||||
arg,
|
||||
self:__read(byteutil.get_size(arg))
|
||||
)
|
||||
elseif argType == nil then
|
||||
elseif arg_type == "nil" then
|
||||
error(
|
||||
"in binary mode the first argument must be a string data format"..
|
||||
" for the library \"byteutil\" or the number of bytes to read"
|
||||
)
|
||||
else
|
||||
error("unknown argument type: "..argType)
|
||||
error("unknown argument type: "..arg_type)
|
||||
end
|
||||
else
|
||||
if not arg then
|
||||
return self:read_line()
|
||||
else
|
||||
local linesCount = arg
|
||||
local trimLastEmptyLines = useTable or true
|
||||
local lines_count = arg
|
||||
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 = { }
|
||||
|
||||
for i = 1, linesCount do
|
||||
for i = 1, lines_count do
|
||||
result[i] = self:read_line()
|
||||
end
|
||||
|
||||
if trimLastEmptyLines then
|
||||
if trim_last_empty_lines then
|
||||
local i = #result
|
||||
|
||||
while i >= 0 do
|
||||
|
|
@ -340,41 +366,53 @@ function io_stream:read(arg, useTable)
|
|||
end
|
||||
|
||||
function io_stream:write(arg, ...)
|
||||
local argType = type(arg)
|
||||
local arg_type = type(arg)
|
||||
|
||||
if self.binaryMode then
|
||||
local byteArr
|
||||
if self.binary_mode then
|
||||
local byte_arr
|
||||
|
||||
if argType ~= "string" then
|
||||
if arg_type ~= "string" then
|
||||
-- using arg as bytes table/bytearray
|
||||
|
||||
if argType == "table" then
|
||||
byteArr = Bytearray(arg)
|
||||
if arg_type == "table" then
|
||||
byte_arr = Bytearray(arg)
|
||||
else
|
||||
byteArr = arg
|
||||
byte_arr = arg
|
||||
end
|
||||
else
|
||||
byteArr = byteutil.pack(arg, ...)
|
||||
byte_arr = byteutil.pack(arg, ...)
|
||||
end
|
||||
|
||||
self:__write(byteArr)
|
||||
self:__write(byte_arr)
|
||||
else
|
||||
if argType == "string" then
|
||||
if arg_type == "string" then
|
||||
self:write_line(arg)
|
||||
elseif argType == "table" then
|
||||
elseif arg_type == "table" then
|
||||
for i = 1, #arg do
|
||||
self:write_line(arg[i])
|
||||
end
|
||||
else error("unknown argument type: "..argType) end
|
||||
else error("unknown argument type: "..arg_type) end
|
||||
end
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
function io_stream:is_alive()
|
||||
return self.ioLib.is_alive(self.descriptor)
|
||||
return self.io_lib.is_alive(self.descriptor)
|
||||
end
|
||||
|
||||
function io_stream:is_closed()
|
||||
|
|
@ -383,20 +421,24 @@ end
|
|||
|
||||
function io_stream:close()
|
||||
if self.mode == BUFFERED_MODE then
|
||||
self.readBuffer:clear()
|
||||
self.writeBuffer:clear()
|
||||
self.read_buffer:clear()
|
||||
self.write_buffer:clear()
|
||||
end
|
||||
|
||||
return self.ioLib.close(self.descriptor)
|
||||
return self.io_lib.close(self.descriptor)
|
||||
end
|
||||
|
||||
function io_stream:flush()
|
||||
if self.mode == BUFFERED_MODE and #self.writeBuffer > 0 then
|
||||
self.ioLib.write(self.descriptor, self.writeBuffer)
|
||||
self.writeBuffer:clear()
|
||||
if self.mode == BUFFERED_MODE and #self.write_buffer > 0 then
|
||||
self.io_lib.write(self.descriptor, self.write_buffer)
|
||||
self.write_buffer:clear()
|
||||
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
|
||||
|
||||
return io_stream
|
||||
|
|
@ -3,5 +3,6 @@ name = "default"
|
|||
base_packs = ["base"]
|
||||
permissions = [
|
||||
"network",
|
||||
"record-audio"
|
||||
"record-audio",
|
||||
"sub-instances",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ local Socket = {__index={
|
|||
end
|
||||
return self:recv(length, usetable)
|
||||
end,
|
||||
as_stream=network.__as_stream,
|
||||
close=function(self) return network.__close(self.id) end,
|
||||
available=function(self) return network.__available(self.id) or 0 end,
|
||||
is_alive=function(self) return network.__is_alive(self.id) end,
|
||||
|
|
|
|||
|
|
@ -140,7 +140,7 @@ console.add_command(
|
|||
for y=y1,y2 do
|
||||
for z=z1,z2 do
|
||||
for x=x1,x2 do
|
||||
block.set(x, y, z, id)
|
||||
block.set(x, y, z, id, 0, true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -127,6 +127,10 @@ require "core:internal/extensions/inventory"
|
|||
asserts = require "core:internal/asserts"
|
||||
events = require "core:internal/events"
|
||||
|
||||
if test then
|
||||
require "core:internal/test"
|
||||
end
|
||||
|
||||
function pack.unload(prefix)
|
||||
events.remove_by_prefix(prefix)
|
||||
end
|
||||
|
|
@ -136,7 +140,20 @@ local __vc_named_coroutines = {}
|
|||
local __vc_next_coroutine = 1
|
||||
|
||||
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
|
||||
__vc_next_coroutine = __vc_next_coroutine + 1
|
||||
__vc_coroutines[id] = co
|
||||
|
|
@ -334,6 +351,10 @@ else
|
|||
os.pid = ffi.C.getpid()
|
||||
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)
|
||||
|
||||
rules = require "core:internal/rules"
|
||||
|
|
|
|||
|
|
@ -10,8 +10,9 @@ vc = {
|
|||
is_client = function()
|
||||
return not _vc_headless
|
||||
end,
|
||||
get_version = __vc_app.get_version,
|
||||
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,
|
||||
}
|
||||
|
||||
|
|
@ -310,12 +311,19 @@ local bytearray = require "core:internal/bytearray"
|
|||
Bytearray = bytearray.FFIBytearray
|
||||
Bytearray_as_string = bytearray.FFIBytearray_as_string
|
||||
Bytearray_as_ptr = bytearray.FFIBytearray_as_ptr
|
||||
I8view = bytearray.FFII8view
|
||||
U16view = bytearray.FFIU16view
|
||||
I16view = bytearray.FFII16view
|
||||
U32view = bytearray.FFIU32view
|
||||
I32view = bytearray.FFII32view
|
||||
U64view = bytearray.FFIU64view
|
||||
I64view = bytearray.FFII64view
|
||||
FLTview = bytearray.FFIFLTview
|
||||
DBLview = bytearray.FFIDBLview
|
||||
Bytearray_construct = function(...) return Bytearray(...) end
|
||||
|
||||
ctypes = require "core:internal/ctypes"
|
||||
|
||||
bit.compile = require "core:bitwise/compiler"
|
||||
bit.execute = require "core:bitwise/executor"
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
#include <shadows>
|
||||
#include <fog>
|
||||
|
||||
#define CLOUDS_FOG_FACTOR_MUL 0.3f
|
||||
#define CLOUDS_FOG_CURVE_MUL 0.4f
|
||||
|
||||
vec4 effect() {
|
||||
vec4 pos = texture(u_position, v_uv);
|
||||
float light = 1.0;
|
||||
|
|
@ -32,6 +35,10 @@ vec4 effect() {
|
|||
light = pow(light, u_gamma);
|
||||
|
||||
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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,11 +15,33 @@ uniform float u_fogCurve;
|
|||
uniform bool u_alphaClip;
|
||||
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() {
|
||||
vec4 texColor = texture(u_texture0, a_texCoord);
|
||||
float alpha = a_color.a * texColor.a;
|
||||
// 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;
|
||||
}
|
||||
f_color = a_color * texColor;
|
||||
|
|
|
|||
|
|
@ -7,13 +7,17 @@ uniform float u_weatherFogOpacity;
|
|||
uniform float u_weatherFogDencity;
|
||||
uniform float u_weatherFogCurve;
|
||||
|
||||
float calc_fog(float depth) {
|
||||
float calc_fog(float depth, float fogFactor, float fogCurve) {
|
||||
return min(
|
||||
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),
|
||||
u_weatherFogOpacity))
|
||||
);
|
||||
}
|
||||
|
||||
float calc_fog(float depth) {
|
||||
return calc_fog(depth, 1.0, 1.0);
|
||||
}
|
||||
|
||||
#endif // FOG_GLSL_
|
||||
|
|
|
|||
|
|
@ -173,6 +173,12 @@ void audio::initialize(
|
|||
logger.info() << "initializing NoAudio backend";
|
||||
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 {
|
||||
std::string name;
|
||||
NumberSetting* setting;
|
||||
|
|
|
|||
|
|
@ -57,3 +57,7 @@ std::string parsing_error::errorLog() const {
|
|||
ss << "^";
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::runtime_error parsing_error::toRuntimeError() const {
|
||||
return std::runtime_error("parsing error: " + errorLog());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -73,4 +73,6 @@ public:
|
|||
uint linestart
|
||||
);
|
||||
std::string errorLog() const;
|
||||
|
||||
std::runtime_error toRuntimeError() const;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -55,12 +55,12 @@ model::Model VcmModel::squashed() const {
|
|||
}
|
||||
|
||||
static const std::unordered_map<std::string, int> side_indices {
|
||||
{"east", 0},
|
||||
{"west", 1},
|
||||
{"bottom", 2},
|
||||
{"top", 3},
|
||||
{"south", 4},
|
||||
{"north", 5},
|
||||
{"west", 0}, // -X
|
||||
{"east", 1}, // +X
|
||||
{"bottom", 2}, // -Y
|
||||
{"top", 3}, // +Y
|
||||
{"north", 4}, // -Z
|
||||
{"south", 5}, // +Z
|
||||
};
|
||||
|
||||
static bool to_boolean(const xml::Attribute& attr) {
|
||||
|
|
@ -267,12 +267,12 @@ static void perform_box(const xmlelement& root, ModelBuilder& builder) {
|
|||
}
|
||||
|
||||
UVRegion regions[6] {};
|
||||
regions[0].scale(to.x - from.x, to.y - from.y);
|
||||
regions[1].scale(from.x - to.x, to.y - from.y);
|
||||
regions[0].scale(to.z - from.z, 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[3].scale(from.x - to.x, to.z - from.z);
|
||||
regions[4].scale(to.z - from.z, to.y - from.y);
|
||||
regions[5].scale(from.z - to.z, to.y - from.y);
|
||||
regions[4].scale(to.x - from.x, to.y - from.y);
|
||||
regions[5].scale(from.x - to.x, to.y - from.y);
|
||||
|
||||
auto center = (from + to) * 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();
|
||||
}
|
||||
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")) {
|
||||
regions[idx].scale(elem->attr("region-scale").asVec2());
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ namespace {
|
|||
public:
|
||||
FTFontFile(FT_Face face, util::Buffer<ubyte> buffer)
|
||||
: face(std::move(face)), buffer(std::move(buffer)) {
|
||||
monospace = this->face->face_flags & FT_FACE_FLAG_FIXED_WIDTH;
|
||||
}
|
||||
|
||||
~FTFontFile() {
|
||||
|
|
|
|||
|
|
@ -19,6 +19,12 @@ namespace vector_fonts {
|
|||
virtual std::unique_ptr<Texture> renderPage(
|
||||
int pageid, std::vector<Glyph>& glyphs, int size
|
||||
) = 0;
|
||||
|
||||
bool isMonospace() const {
|
||||
return monospace;
|
||||
}
|
||||
protected:
|
||||
bool monospace = true;
|
||||
};
|
||||
|
||||
void initialize();
|
||||
|
|
|
|||
|
|
@ -385,10 +385,22 @@ public:
|
|||
}
|
||||
if (c == '(') {
|
||||
nextChar();
|
||||
// TODO: replace with array parsing after moving to dv::value's
|
||||
std::string value = std::string(readUntil(')'));
|
||||
int depth = 1;
|
||||
size_t start = pos;
|
||||
while (hasNext()) {
|
||||
char c = nextChar();
|
||||
if (c == '(') {
|
||||
depth++;
|
||||
} else if (c == ')') {
|
||||
depth--;
|
||||
if (depth == 0) {
|
||||
goBack(1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
expect(')');
|
||||
return value;
|
||||
return std::string(source.substr(start, pos - start - 1));
|
||||
}
|
||||
return std::string(readUntilWhitespace());
|
||||
}
|
||||
|
|
@ -528,3 +540,11 @@ std::string xml::stringify(
|
|||
|
||||
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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -119,6 +119,12 @@ namespace xml {
|
|||
const std::string& indentStr = " "
|
||||
);
|
||||
|
||||
std::string stringify(
|
||||
const Node& element,
|
||||
bool nice = true,
|
||||
const std::string& indentStr = " "
|
||||
);
|
||||
|
||||
/// @brief Read XML Document from string
|
||||
/// @param filename file name will be shown in error messages
|
||||
/// @param source xml source code string
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue