diff --git a/.gitignore b/.gitignore index c0b0e1c9d..4d0155832 100644 --- a/.gitignore +++ b/.gitignore @@ -49,3 +49,6 @@ appimage-build/ # libs /libs/ /vcpkg_installed/ + +# vcstudio +.vcstudio diff --git a/README.md b/README.md index e672ef541..1fd77cc8c 100644 --- a/README.md +++ b/README.md @@ -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 ``` diff --git a/dev/tests/bytearray.lua b/dev/tests/bytearray.lua index 415768c65..71e895d37 100644 --- a/dev/tests/bytearray.lua +++ b/dev/tests/bytearray.lua @@ -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") diff --git a/dev/tests/io_stream.lua b/dev/tests/io_stream.lua new file mode 100644 index 000000000..d08c68b0c --- /dev/null +++ b/dev/tests/io_stream.lua @@ -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) diff --git a/doc/en/3d-text.md b/doc/en/3d-text.md index b0c9fd1f6..be4a2757f 100644 --- a/doc/en/3d-text.md +++ b/doc/en/3d-text.md @@ -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: diff --git a/doc/en/block-properties.md b/doc/en/block-properties.md index f3137dd60..82ba10484 100644 --- a/doc/en/block-properties.md +++ b/doc/en/block-properties.md @@ -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: diff --git a/doc/en/content-packs.md b/doc/en/content-packs.md index c193fb188..48ec57e6d 100644 --- a/doc/en/content-packs.md +++ b/doc/en/content-packs.md @@ -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'. diff --git a/doc/en/entity-properties.md b/doc/en/entity-properties.md index 8bc4d5d37..b0aee9aea 100644 --- a/doc/en/entity-properties.md +++ b/doc/en/entity-properties.md @@ -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. diff --git a/doc/en/item-properties.md b/doc/en/item-properties.md index 7bef07664..d3ae82e7d 100644 --- a/doc/en/item-properties.md +++ b/doc/en/item-properties.md @@ -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`. diff --git a/doc/en/rigging.md b/doc/en/rigging.md index 4b69b5769..abb5ad81a 100644 --- a/doc/en/rigging.md +++ b/doc/en/rigging.md @@ -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. diff --git a/doc/en/scripting.md b/doc/en/scripting.md index a21b959ed..2072d4353 100644 --- a/doc/en/scripting.md +++ b/doc/en/scripting.md @@ -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. diff --git a/doc/en/scripting/builtins/libapp.md b/doc/en/scripting/builtins/libapp.md index 0ec755689..48d79bf8e 100644 --- a/doc/en/scripting/builtins/libapp.md +++ b/doc/en/scripting/builtins/libapp.md @@ -180,14 +180,34 @@ app.get_content_sources() -> table Returns a list of content sources (paths), in descending priority order. -``lua +```lua app.set_content_sources(sources: table) ``` 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 +``` diff --git a/doc/en/scripting/builtins/libblock.md b/doc/en/scripting/builtins/libblock.md index 4513d57cf..cc69e47d8 100644 --- a/doc/en/scripting/builtins/libblock.md +++ b/doc/en/scripting/builtins/libblock.md @@ -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. diff --git a/doc/en/scripting/builtins/libctypes.md b/doc/en/scripting/builtins/libctypes.md new file mode 100644 index 000000000..5517af0fa --- /dev/null +++ b/doc/en/scripting/builtins/libctypes.md @@ -0,0 +1,28 @@ +# *ctypes* Library + +A library for safe work with C types. + +| ctypes.\ | 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) +``` diff --git a/doc/en/scripting/builtins/libentities.md b/doc/en/scripting/builtins/libentities.md index e34cd7a53..9e653ba78 100644 --- a/doc/en/scripting/builtins/libentities.md +++ b/doc/en/scripting/builtins/libentities.md @@ -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) -> 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 --- 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 diff --git a/doc/en/scripting/builtins/libgui.md b/doc/en/scripting/builtins/libgui.md index 53d6e4ad2..a7ddbeaec 100644 --- a/doc/en/scripting/builtins/libgui.md +++ b/doc/en/scripting/builtins/libgui.md @@ -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( diff --git a/doc/en/scripting/builtins/libhud.md b/doc/en/scripting/builtins/libhud.md index 1d6264b2d..550659ccc 100644 --- a/doc/en/scripting/builtins/libhud.md +++ b/doc/en/scripting/builtins/libhud.md @@ -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 diff --git a/doc/en/scripting/builtins/libquat.md b/doc/en/scripting/builtins/libquat.md index f7cc7a0f6..5d72413e2 100644 --- a/doc/en/scripting/builtins/libquat.md +++ b/doc/en/scripting/builtins/libquat.md @@ -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 ``` diff --git a/doc/en/scripting/builtins/libworld.md b/doc/en/scripting/builtins/libworld.md index 54b44fe15..72bfaf8e4 100644 --- a/doc/en/scripting/builtins/libworld.md +++ b/doc/en/scripting/builtins/libworld.md @@ -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 diff --git a/doc/en/scripting/ecs.md b/doc/en/scripting/ecs.md index 52be70363..9636cf763 100644 --- a/doc/en/scripting/ecs.md +++ b/doc/en/scripting/ecs.md @@ -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 diff --git a/doc/en/scripting/events.md b/doc/en/scripting/events.md index 75180edc3..9baa9fd85 100644 --- a/doc/en/scripting/events.md +++ b/doc/en/scripting/events.md @@ -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 diff --git a/doc/en/scripting/extensions.md b/doc/en/scripting/extensions.md new file mode 100644 index 000000000..94a55ef00 --- /dev/null +++ b/doc/en/scripting/extensions.md @@ -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 + +-- 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 + +-- 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 +``` diff --git a/doc/en/scripting/filesystem.md b/doc/en/scripting/filesystem.md index 8341e9665..12a190daa 100644 --- a/doc/en/scripting/filesystem.md +++ b/doc/en/scripting/filesystem.md @@ -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 + + + +``` + +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). diff --git a/doc/en/scripting/ui.md b/doc/en/scripting/ui.md index 04784b449..7a11b2f5e 100644 --- a/doc/en/scripting/ui.md +++ b/doc/en/scripting/ui.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: diff --git a/doc/en/world-generator.md b/doc/en/world-generator.md index cd868b404..3c0a774f3 100644 --- a/doc/en/world-generator.md +++ b/doc/en/world-generator.md @@ -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 diff --git a/doc/en/xml-ui-layouts.md b/doc/en/xml-ui-layouts.md index 0bd1b5353..704a0b786 100644 --- a/doc/en/xml-ui-layouts.md +++ b/doc/en/xml-ui-layouts.md @@ -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* diff --git a/doc/ru/3d-text.md b/doc/ru/3d-text.md index c2d209434..548289701 100644 --- a/doc/ru/3d-text.md +++ b/doc/ru/3d-text.md @@ -12,6 +12,7 @@ | render_distance | Дистанция отрисовки текста | 32 | | xray_opacity | Коэффициент видимости через препятствия (просвечивание) | 0 | | perspective | Коэффициент перспективы | 1 | +| font | Шрифт. При отсутствии используется основной | "" | Доступные форматы отображения: diff --git a/doc/ru/block-properties.md b/doc/ru/block-properties.md index 50bd7f023..09db5c8ef 100644 --- a/doc/ru/block-properties.md +++ b/doc/ru/block-properties.md @@ -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* - массив хитбоксов, например: diff --git a/doc/ru/content-packs.md b/doc/ru/content-packs.md index 32f9be593..4ab002104 100644 --- a/doc/ru/content-packs.md +++ b/doc/ru/content-packs.md @@ -12,7 +12,7 @@ { "id": "выбранное_имя_пака", "title": "имя контент-пака для отображения в меню контента", - "version": "версия контент-пака в формате major.minor", + "version": "версия контент-пака в формате major.minor.[patch]", "creator": "создатель контент-пака", "description": "краткое описание", "dependencies": [ @@ -28,6 +28,7 @@ - '!' - обязательная зависимость - '?' - опциональная зависимость - '~' - слабая зависимость + Отсутствие префикса интерпретируется как '!'. Пример: '~randutil' - слабая зависимость 'randutil'. diff --git a/doc/ru/entity-properties.md b/doc/ru/entity-properties.md index de10cfd32..2f5ca3791 100644 --- a/doc/ru/entity-properties.md +++ b/doc/ru/entity-properties.md @@ -61,12 +61,16 @@ ### Блокирование - *blocking* -Определяет блокирует ли сущность установку блоков. +Определяет, блокирует ли сущность установку блока, пересекающегося с хитбоксом сущности. *В будущем будет также блокировать движение других сущностей.* Значение по-умолчанию: *true*. +### Выделяемость - *selectable* + +При значении в `false` курсор будет игнорировать cущность, пропуская луч далее. + ### Список сенсоров - *sensors* Сенсор - область пространства, привязанная к физическому телу, детектирующее попадание в него других тел. diff --git a/doc/ru/item-properties.md b/doc/ru/item-properties.md index cb45827a1..7453789fb 100644 --- a/doc/ru/item-properties.md +++ b/doc/ru/item-properties.md @@ -66,6 +66,10 @@ - `relation` - отношение текущего значения к изначальному (x/y) - `vbar` - вертикальная шкала (используется по-умолчанию) +### Имя скрипта - `script-name` + +Определяет имя скрипта, содержащего обработчики событий предмета. По-умолчанию соответствует строковому id (имени) предмета. +Свойство обеспечивает возможность использования одного скрипта для нескольких предметов. ## Теги - *tags* diff --git a/doc/ru/rigging.md b/doc/ru/rigging.md index 86ec0a09b..388b19ea2 100644 --- a/doc/ru/rigging.md +++ b/doc/ru/rigging.md @@ -38,7 +38,7 @@ ## Модели -Модели должны располагаться в папке models. На данный момент поддерживается только OBJ формат. +Модели должны располагаться в папке models. На данный момент поддерживаются форматы: obj, vec3 и [vcm](vcm.md). >[!IMPORTANT] > При загрузке obj модели игнорируется файл \*.mtl. diff --git a/doc/ru/scripting.md b/doc/ru/scripting.md index cd8a3b00a..1e6d73098 100644 --- a/doc/ru/scripting.md +++ b/doc/ru/scripting.md @@ -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 вернёт кешированный результат. diff --git a/doc/ru/scripting/builtins/libapp.md b/doc/ru/scripting/builtins/libapp.md index 07e358ef4..bbdf5cd2f 100644 --- a/doc/ru/scripting/builtins/libapp.md +++ b/doc/ru/scripting/builtins/libapp.md @@ -142,4 +142,24 @@ app.set_content_sources(sources: table) -- Сбрасывает список источников контента. app.reset_content_sources() -``` \ No newline at end of file +``` + +## Под-экземпляры + +```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 +``` diff --git a/doc/ru/scripting/builtins/libctypes.md b/doc/ru/scripting/builtins/libctypes.md new file mode 100644 index 000000000..d6c985b44 --- /dev/null +++ b/doc/ru/scripting/builtins/libctypes.md @@ -0,0 +1,28 @@ +# Библиотека *ctypes* + +Библиотека для безопасной работы с типами языка С. + +| ctypes.\ | С-тип | Минимум | Максимум | +| ------------- | -------- | -------------------- | -------------------- | +| 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) +``` diff --git a/doc/ru/scripting/builtins/libentities.md b/doc/ru/scripting/builtins/libentities.md index 27d8d03b1..9753c7e15 100644 --- a/doc/ru/scripting/builtins/libentities.md +++ b/doc/ru/scripting/builtins/libentities.md @@ -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) -> table --- Возвращает список UID сущностей, попадающих в прямоугольную область +-- Возвращает список UID сущностей, позиции которых попадают в прямоугольную область -- pos - минимальный угол области -- size - размер области entities.get_all_in_box(pos: vec3, size: vec3) -> table --- Возвращает список UID сущностей, попадающих в радиус +-- Возвращает список UID сущностей, позиции которых попадают в радиус -- center - центр области -- radius - радиус области entities.get_all_in_radius(center: vec3, radius: number) -> table diff --git a/doc/ru/scripting/builtins/libgui.md b/doc/ru/scripting/builtins/libgui.md index 9c2eb7418..ea1221415 100644 --- a/doc/ru/scripting/builtins/libgui.md +++ b/doc/ru/scripting/builtins/libgui.md @@ -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( diff --git a/doc/ru/scripting/builtins/libhud.md b/doc/ru/scripting/builtins/libhud.md index bad0e93c6..7a93c93e4 100644 --- a/doc/ru/scripting/builtins/libhud.md +++ b/doc/ru/scripting/builtins/libhud.md @@ -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() -``` \ No newline at end of file +``` diff --git a/doc/ru/scripting/builtins/libnetwork.md b/doc/ru/scripting/builtins/libnetwork.md index 31f59fb25..46a3eded8 100644 --- a/doc/ru/scripting/builtins/libnetwork.md +++ b/doc/ru/scripting/builtins/libnetwork.md @@ -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() diff --git a/doc/ru/scripting/builtins/libquat.md b/doc/ru/scripting/builtins/libquat.md index e991de252..a32a24e6d 100644 --- a/doc/ru/scripting/builtins/libquat.md +++ b/doc/ru/scripting/builtins/libquat.md @@ -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(...)* diff --git a/doc/ru/scripting/builtins/libworld.md b/doc/ru/scripting/builtins/libworld.md index 6b945aa76..52812f295 100644 --- a/doc/ru/scripting/builtins/libworld.md +++ b/doc/ru/scripting/builtins/libworld.md @@ -91,6 +91,8 @@ world.raycast( -- исключающий режим фильтрации: -- при значении (true) filter_entities определяет список игнорируемых entities_exclusion: bool = false, + -- учитывать сущности с selectable=false + nonselect_entities: bool = false; [==[ дополнительные параметры (блоки) ]==] -- id типов блоков используемых для фильтрации diff --git a/doc/ru/scripting/bytearray.md b/doc/ru/scripting/bytearray.md index c90b6d67e..d15cd6a58 100644 --- a/doc/ru/scripting/bytearray.md +++ b/doc/ru/scripting/bytearray.md @@ -2,6 +2,8 @@ *Bytearray* - динамический байтовый массив, реализованный через LuaJIT FFI. По нему можно итерироваться стандартными функциями **pairs** и **ipairs** +Примечание касаемо условных обозначений в документации: псведотип `posint` означает натуральное (положительное ненулевое целое) число. + ## Основное ### Создание массива ```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 байт* | ---- +* Размеры "оригинальных" C-шных типов являются платформозависимыми и их точный размер не описан в стандарте языка. В данной таблице предоставлены наиболее распространённые размеры типов. -### Пример использования +### Специфические методы "вьюшек" + +```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 -``` \ No newline at end of file +``` diff --git a/doc/ru/scripting/ecs.md b/doc/ru/scripting/ecs.md index d659f342d..cb4a79bec 100644 --- a/doc/ru/scripting/ecs.md +++ b/doc/ru/scripting/ecs.md @@ -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] > При выходе сущности за пределы зоны прогрузки она удаляется, вызывая события. > При повторном попадании в зону прогрузки сущность спавнится заново. diff --git a/doc/ru/scripting/events.md b/doc/ru/scripting/events.md index ff4bfb456..bfa0b9388 100644 --- a/doc/ru/scripting/events.md +++ b/doc/ru/scripting/events.md @@ -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 diff --git a/doc/ru/scripting/extensions.md b/doc/ru/scripting/extensions.md index 868210056..91859fbf1 100644 --- a/doc/ru/scripting/extensions.md +++ b/doc/ru/scripting/extensions.md @@ -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 -``` \ No newline at end of file +``` diff --git a/doc/ru/scripting/filesystem.md b/doc/ru/scripting/filesystem.md index 0e060d94c..a3bee78a6 100644 --- a/doc/ru/scripting/filesystem.md +++ b/doc/ru/scripting/filesystem.md @@ -52,6 +52,60 @@ yaml.parse(code: str) -> table Парсит YAML строку в таблицу. +## Библиотека xml + +Библиотека содержит функции для сериализации и десериализации XML: + +Далее XML как тип данных обозначает Lua-таблицу со следующей структурой: +- Тег элемента доступен по ключу `#` +- XML-аттрибуты доступны как пары ключ = значение +- Вложенные элементы доступны по индексу + +Таблица используется как словарь, так и массив. + +Пример: + +```xml + + + +``` + +Эквивалентная 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). diff --git a/doc/ru/scripting/io_stream.md b/doc/ru/scripting/io_stream.md index cc3403eda..e54ca1fdb 100644 --- a/doc/ru/scripting/io_stream.md +++ b/doc/ru/scripting/io_stream.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`| `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` | `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 | string | table | ... + [опционально] arg: int | string, + [опционально] use_table = false | trim_empty_lines = true: boolean +) -> Bytearray | table | string | table | ... --[[ Записывает данные в поток В двоичном режиме: Если arg - string, то функция интерпретирует arg как шаблон для byteutil, передаст его и ... в byteutil.pack и результат запишет в поток - Если arg - Bytearray | table, то записывает байты в поток + Если arg - Bytearray | table, то записывает байты в поток В текстовом режиме: Если arg - string, то записывает строку в поток (вместе с окончанием LF) @@ -116,7 +116,7 @@ io_stream:read( Если arg - table, то записывает каждую строку из таблицы отдельно --]] io_stream:write( - arg: Bytearray | table | string | table, + arg: Bytearray | table | string | table, [опционально] ... ) @@ -128,53 +128,59 @@ io_stream:write_line(string) --[[ В двоичном режиме: - Читает все доступные байты из потока и возвращает ввиде Bytearray или table, если useTable = true + Читает все доступные байты из потока и возвращает ввиде Bytearray или table, если use_table = true В текстовом режиме: - Читает все доступные строки из потока в table если useTable = true, или в одну строку вместе с окончаниями, если нет + Читает все доступные строки из потока в table если use_table = true, или в одну строку вместе с окончаниями, если нет --]] io_stream:read_fully( - [опционально] useTable: boolean -) -> Bytearray | table | table | string + [опционально] use_table: boolean +) -> Bytearray | table | table | 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 ``` \ No newline at end of file diff --git a/doc/ru/scripting/ui.md b/doc/ru/scripting/ui.md index 6cff70e2e..09e296f9b 100644 --- a/doc/ru/scripting/ui.md +++ b/doc/ru/scripting/ui.md @@ -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 определяет порядок | Общие методы элементов: diff --git a/doc/ru/world-generator.md b/doc/ru/world-generator.md index 30e4b1098..3415a81d6 100644 --- a/doc/ru/world-generator.md +++ b/doc/ru/world-generator.md @@ -215,12 +215,28 @@ local map = Heightmap(ширина, высота) Операции применяются ко всем значениям высоты. ```lua +-- Приведение значений высот к абсолютным map:abs() + +-- Округление к меньшему целому +map:floor() + +-- Округление к большему целому +map:ceil() + +-- Округление к ближайшему целому +map:round() + +-- Синус от значений высот (в радианах) +map:sin() + +-- Косинус от значений высот (в радианах) +map:cos() + +-- Тангенс от значений высот (в радианах) +map:tan() ``` -Приводит значения высот к абсолютным. - - ### Бинарные операции Операции с применением второй карты или скаляра. diff --git a/doc/ru/xml-ui-layouts.md b/doc/ru/xml-ui-layouts.md index 2ca6da1ce..9be788e7f 100644 --- a/doc/ru/xml-ui-layouts.md +++ b/doc/ru/xml-ui-layouts.md @@ -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* diff --git a/res/content/base/config/defaults.toml b/res/content/base/config/defaults.toml index 650c24d74..d2c5142cc 100644 --- a/res/content/base/config/defaults.toml +++ b/res/content/base/config/defaults.toml @@ -1,3 +1,4 @@ generator = "base:demo" player-entity = "base:player" hand-skeleton = "base:hand" +block-material = "base:stone" diff --git a/res/content/base/generators/demo.files/script.lua b/res/content/base/generators/demo.files/script.lua index 7a3282c3c..ff05e08cc 100644 --- a/res/content/base/generators/demo.files/script.lua +++ b/res/content/base/generators/demo.files/script.lua @@ -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 diff --git a/res/content/base/modules/util.lua b/res/content/base/modules/util.lua index b1a97ad06..843f818c7 100644 --- a/res/content/base/modules/util.lua +++ b/res/content/base/modules/util.lua @@ -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 diff --git a/res/content/base/scripts/components/drop.lua b/res/content/base/scripts/components/drop.lua index 39bd03ceb..335b81d89 100644 --- a/res/content/base/scripts/components/drop.lua +++ b/res/content/base/scripts/components/drop.lua @@ -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) diff --git a/res/content/base/scripts/grass_block.lua b/res/content/base/scripts/grass_block.lua index fb7482c20..b5965de43 100644 --- a/res/content/base/scripts/grass_block.lua +++ b/res/content/base/scripts/grass_block.lua @@ -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 diff --git a/res/content/base/scripts/hud.lua b/res/content/base/scripts/hud.lua index 9fc10f2a1..5d3bfeb46 100644 --- a/res/content/base/scripts/hud.lua +++ b/res/content/base/scripts/hud.lua @@ -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 diff --git a/res/content/base/scripts/world.lua b/res/content/base/scripts/world.lua index 419dcc769..3da6f6c2a 100644 --- a/res/content/base/scripts/world.lua +++ b/res/content/base/scripts/world.lua @@ -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 diff --git a/res/layouts/code_editor.xml b/res/layouts/code_editor.xml index b3cd2ed9c..fb4833da1 100644 --- a/res/layouts/code_editor.xml +++ b/res/layouts/code_editor.xml @@ -17,7 +17,7 @@ color="#FFFFFF80" size="16" pos="4,6" hover-color="#1080FF"> + size="76,24" padding="8,6,8,2" interval="8" color="0"> + + diff --git a/res/layouts/code_editor.xml.lua b/res/layouts/code_editor.xml.lua index 733fa9036..61e90842f 100644 --- a/res/layouts/code_editor.xml.lua +++ b/res/layouts/code_editor.xml.lua @@ -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 diff --git a/res/layouts/ingame_chat.xml.lua b/res/layouts/ingame_chat.xml.lua index dce629887..fdd535045 100644 --- a/res/layouts/ingame_chat.xml.lua +++ b/res/layouts/ingame_chat.xml.lua @@ -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 diff --git a/res/layouts/inventory.xml b/res/layouts/inventory.xml index 4291ab4d3..7a33b054f 100644 --- a/res/layouts/inventory.xml +++ b/res/layouts/inventory.xml @@ -1,4 +1,4 @@ - - + + diff --git a/res/layouts/inventory.xml.lua b/res/layouts/inventory.xml.lua index 9f836223d..5c79db6ff 100644 --- a/res/layouts/inventory.xml.lua +++ b/res/layouts/inventory.xml.lua @@ -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 diff --git a/res/layouts/pages/content.xml.lua b/res/layouts/pages/content.xml.lua index 2937542f4..6d684b130 100644 --- a/res/layouts/pages/content.xml.lua +++ b/res/layouts/pages/content.xml.lua @@ -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 diff --git a/res/layouts/pages/new_world.xml b/res/layouts/pages/new_world.xml index 84a9519fa..fc13d5084 100644 --- a/res/layouts/pages/new_world.xml +++ b/res/layouts/pages/new_world.xml @@ -11,7 +11,7 @@ @World generator diff --git a/res/layouts/templates/pack.xml b/res/layouts/templates/pack.xml index 56bd50e50..d4e721d0c 100644 --- a/res/layouts/templates/pack.xml +++ b/res/layouts/templates/pack.xml @@ -1,6 +1,6 @@ -