mirror of
https://github.com/MihailRis/voxelcore.git
synced 2026-10-04 18:41:51 +00:00
Merge branch 'main' into environments
This commit is contained in:
commit
f9bc401236
134 changed files with 3186 additions and 883 deletions
|
|
@ -15,6 +15,22 @@
|
|||
"VOXELENGINE_BUILD_TESTS": "ON"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "default-ninja-mingw-windows",
|
||||
"condition": {
|
||||
"type": "equals",
|
||||
"lhs": "${hostSystemName}",
|
||||
"rhs": "Windows"
|
||||
},
|
||||
"generator": "Ninja Multi-Config",
|
||||
"binaryDir": "${sourceDir}/build-mingw",
|
||||
"cacheVariables": {
|
||||
"CMAKE_TOOLCHAIN_FILE": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake",
|
||||
"VCPKG_HOST_TRIPLET": "x64-mingw-static",
|
||||
"VCPKG_TARGET_TRIPLET": "x64-mingw-static",
|
||||
"VOXELENGINE_BUILD_TESTS": "ON"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "default-ninja-gnu-linux",
|
||||
"condition": {
|
||||
|
|
@ -60,6 +76,11 @@
|
|||
"configurePreset": "default-vs-msvc-windows",
|
||||
"configuration": "Debug"
|
||||
},
|
||||
{
|
||||
"name": "default-ninja-mingw-windows",
|
||||
"configurePreset": "default-ninja-mingw-windows",
|
||||
"configuration": "Debug"
|
||||
},
|
||||
{
|
||||
"name": "default-ninja-gnu-linux",
|
||||
"configurePreset": "default-ninja-gnu-linux",
|
||||
|
|
|
|||
20
README.md
20
README.md
|
|
@ -152,6 +152,26 @@ cmake --build --preset default-vs-msvc-windows
|
|||
> [!NOTE]
|
||||
> Make sure your `CMakeUserPresets.json` (if used) contains the correct `VCPKG_ROOT` path.
|
||||
|
||||
---
|
||||
### Building with CLion (MinGW / Ninja)
|
||||
|
||||
> [!NOTE]
|
||||
> Requirements: **vcpkg**, **Git**, and **CLion** (bundled with CMake, Ninja and MinGW toolchain).
|
||||
|
||||
> [!WARNING]
|
||||
> Avoid installing tools in paths with spaces (e.g. `Program Files`).
|
||||
> This may break MinGW builds (especially `windres`).
|
||||
|
||||
Install **vcpkg** manually as described in the Visual Studio section.
|
||||
|
||||
|
||||
#### Configure CLion
|
||||
Open the project in CLion and configure CMake profile:
|
||||
```text
|
||||
Settings → Build, Execution, Deployment → CMake
|
||||
```
|
||||
Reload CMake project and build the engine.
|
||||
|
||||
---
|
||||
|
||||
## Build using Docker
|
||||
|
|
|
|||
BIN
dev/tests/attachments/line_clipping_test.png
Normal file
BIN
dev/tests/attachments/line_clipping_test.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.5 KiB |
51
dev/tests/canvas_lines.lua
Normal file
51
dev/tests/canvas_lines.lua
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
local W, H = 128, 128
|
||||
|
||||
local canvas = Canvas({W, H})
|
||||
canvas:clear(0xff202020)
|
||||
|
||||
local WHITE = 0xffffffff
|
||||
local RED = 0xffff4040
|
||||
local GREEN = 0xff40ff40
|
||||
local BLUE = 0xff4080ff
|
||||
local YELLOW = 0xffffff40
|
||||
|
||||
-- border
|
||||
canvas:line(0, 0, W - 1, 0, WHITE)
|
||||
canvas:line(W - 1, 0, W - 1, H - 1, WHITE)
|
||||
canvas:line(W - 1, H - 1, 0, H - 1, WHITE)
|
||||
canvas:line(0, H - 1, 0, 0, WHITE)
|
||||
|
||||
-- horizontal
|
||||
canvas:line(-40, 16, W + 40, 16, RED)
|
||||
canvas:line(-40, H / 2, W + 40, H / 2, RED)
|
||||
canvas:line(-40, H - 17, W + 40, H - 17, RED)
|
||||
|
||||
-- vertical
|
||||
canvas:line(16, -40, 16, H + 40, GREEN)
|
||||
canvas:line(W / 2, -40, W / 2, H + 40, GREEN)
|
||||
canvas:line(W - 17, -40, W - 17, H + 40, GREEN)
|
||||
|
||||
-- diagonals
|
||||
canvas:line(-40, -40, W + 40, H + 40, BLUE)
|
||||
canvas:line(-40, H + 40, W + 40, -40, BLUE)
|
||||
|
||||
canvas:line(W / 2, -40, W + 40, H / 2, BLUE)
|
||||
canvas:line(-40, H / 2, W / 2, H + 40, BLUE)
|
||||
|
||||
-- outer
|
||||
canvas:line(-30, -30, 20, 20, YELLOW)
|
||||
canvas:line(W - 20, H - 20, W + 30, H + 30, YELLOW)
|
||||
|
||||
canvas:line(W + 30, -30, W - 20, 20, YELLOW)
|
||||
canvas:line(-30, H + 30, 20, H - 20, YELLOW)
|
||||
|
||||
-- completely outside
|
||||
canvas:line(-50, -20, -10, H + 20, 0xffff00ff)
|
||||
canvas:line(W + 10, -20, W + 50, H + 20, 0xffff00ff)
|
||||
canvas:line(-20, -20, W + 20, -10, 0xffff00ff)
|
||||
canvas:line(-20, H + 10, W + 20, H + 30, 0xffff00ff)
|
||||
|
||||
local expected = Canvas.decode(
|
||||
file.read_bytes("script:attachments/line_clipping_test.png"), "png"
|
||||
)
|
||||
asserts.equals(expected, canvas)
|
||||
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)
|
||||
47
dev/tests/raycast.lua
Normal file
47
dev/tests/raycast.lua
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
local util = require "core:tests_util"
|
||||
|
||||
-- Create world and prepare settings
|
||||
util.create_demo_world("core:default")
|
||||
app.set_setting("chunks.load-distance", 3)
|
||||
app.set_setting("chunks.load-speed", 1)
|
||||
|
||||
-- Create player
|
||||
local pid = player.create("Xerxes")
|
||||
player.set_spawnpoint(pid, 0, 100, 0)
|
||||
player.set_pos(pid, 0, 100, 0)
|
||||
app.sleep_until(function () return block.get(0, 0, 0) ~= -1 end)
|
||||
app.sleep_until(function () return block.get(-5, 8, 2) ~= -1 end)
|
||||
|
||||
local sand_idx = block.index("base:sand")
|
||||
block.set(0, 1, 0, sand_idx)
|
||||
|
||||
local start = {-5, 8, 2}
|
||||
local target = {0.5, 1.5, 0.5}
|
||||
local ray = world.raycast({
|
||||
start = start,
|
||||
dir = vec3.normalize(vec3.sub(target, start)),
|
||||
distance = 10,
|
||||
})
|
||||
debug.print(ray)
|
||||
|
||||
assert(ray, "ray didn't hit")
|
||||
util.check_int(sand_idx, ray.block, "ray.block")
|
||||
util.check_vec3({0, 1, 0}, ray.normal, "ray.normal")
|
||||
util.check_vec3({0.0769, 2, 0.6153}, ray.endpoint, "ray.endpoint")
|
||||
util.check_vec3({0, 1, 0}, ray.iendpoint, "ray.iendpoint")
|
||||
util.check_float(7.9807, ray.length, "ray.length")
|
||||
|
||||
ray = world.raycast({
|
||||
start = start,
|
||||
dir = vec3.normalize(vec3.sub(target, start)),
|
||||
distance = 10,
|
||||
filter_blocks = {sand_idx},
|
||||
})
|
||||
debug.print(ray)
|
||||
|
||||
assert(ray, "ray didn't hit")
|
||||
util.check_int(block.index("core:obstacle"), ray.block, "ray.block")
|
||||
util.check_vec3({0, 1, 0}, ray.normal, "ray.normal")
|
||||
util.check_vec3({0.923, 1, 0.3846}, ray.endpoint, "ray.endpoint")
|
||||
util.check_vec3({0, 0, 0}, ray.iendpoint, "ray.iendpoint")
|
||||
util.check_float(9.3108, ray.length, "ray.length")
|
||||
|
|
@ -5,13 +5,14 @@
|
|||
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 |
|
||||
| font | Font. If absent, the default one is used | "" |
|
||||
|
||||
Available display formats:
|
||||
|
||||
|
|
|
|||
|
|
@ -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`.
|
||||
|
|
|
|||
|
|
@ -9,14 +9,14 @@ Particles are a table, all fields of which are optional.
|
|||
| lighting | Lighting. | true |
|
||||
| collision | Collision detection. | true |
|
||||
| max_distance | Maximum distance from the camera at which particles may spawn. | 16.0 |
|
||||
| spawn_interval | Particle spawn interval in seconds. | 1.0 |
|
||||
| spawn_interval | Particle spawn interval in seconds. Also initial spawn delay | 1.0 |
|
||||
| lifetime | Average lifetime of particles in seconds. | 5.0 |
|
||||
| lifetime_spread | Maximum deviation of particle lifetime (from 0.0 to 1.0). | 0.2 |
|
||||
| velocity | Initial linear velocity of particles. | {0, 0, 0} |
|
||||
| acceleration | Particles acceleration. | {0, -16, 0} |
|
||||
| explosion | Force of particles explosion on spawn. | {2, 2, 2} |
|
||||
| size | Size of particles. | {0.1, 0.1, 0.1} |
|
||||
| size_spread | Maximum particle size spread over time. | 0.2 |
|
||||
| size_spread | Maximum particle size spread. | 0.2 |
|
||||
| angle_spread | Maximum initial rotation angle spread (0 to 1) | 0.0 |
|
||||
| min_angular_vel | Minimum angular velocity (radians per sec). Non-negative. | 0.0 |
|
||||
| max_angular_vel | Maximum angular velocity (radians per sec). Non-negative. | 0.0 |
|
||||
|
|
@ -35,7 +35,8 @@ gfx.particles.emit(
|
|||
count: int,
|
||||
-- particle settings table
|
||||
preset: table,
|
||||
-- additional particle settings table
|
||||
-- particle settings table overriding 'preset' parameters
|
||||
-- use it instead of 'presets' table copying for modification
|
||||
[optional] extension: table
|
||||
) -> int
|
||||
```
|
||||
|
|
@ -63,7 +64,7 @@ Returns the static position or uid of the entity the emitter is bound to.
|
|||
|
||||
Returns nil if the emitter does not exist.
|
||||
|
||||
``lua
|
||||
```lua
|
||||
gfx.particles.set_origin(id: int, origin: vec3 | int)
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -42,6 +42,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 +59,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.
|
||||
|
|
|
|||
|
|
@ -159,6 +159,8 @@ The function returns a table with the results or nil if the ray does not hit any
|
|||
|
||||
The result will use the destination table instead of creating a new one if the optional argument specified.
|
||||
|
||||
Use `world.raycast(...)` for more detailed configuration.
|
||||
|
||||
## Model and physics
|
||||
|
||||
```lua
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -71,3 +74,5 @@ entities.raycast(start: vec3, dir: vec3, max_distance: number, ignore: int, [opt
|
|||
The function is an extended version of [block.raycast](libblock.md#raycast). Returns a table with the results if the ray touches a block or entity.
|
||||
|
||||
Accordingly, this will affect the presence of the *entity* and *block* fields.
|
||||
|
||||
Use `world.raycast(...)` for more detailed configuration.
|
||||
|
|
|
|||
|
|
@ -162,7 +162,19 @@ inventory.get_all_data(
|
|||
invid: int,
|
||||
-- slot index
|
||||
slot: int,
|
||||
) -> table
|
||||
) -> table | nil
|
||||
|
||||
-- Sets the values of all local item properties.
|
||||
inventory.set_all_data(
|
||||
-- inventory id
|
||||
invid: int,
|
||||
-- slot index
|
||||
slot: int,
|
||||
-- property:value table
|
||||
fields: table,
|
||||
-- clear other property values
|
||||
[optional] clear: boolean = false
|
||||
)
|
||||
```
|
||||
|
||||
```lua
|
||||
|
|
|
|||
|
|
@ -70,4 +70,45 @@ world.save_chunk_data(
|
|||
-- compressed chunk data
|
||||
data: Bytearray
|
||||
)
|
||||
|
||||
-- Casts a ray from the start point in the dir direction.
|
||||
world.raycast(
|
||||
params: table {
|
||||
[==[ required parameters ]==]
|
||||
-- starting point
|
||||
start: vec3,
|
||||
-- ray direction vector
|
||||
dir: vec3,
|
||||
-- maximum distance
|
||||
distance: number,
|
||||
|
||||
[==[ additional parameters (entities) ]==]
|
||||
-- consider entities
|
||||
entities: bool = true,
|
||||
-- uid of the ignored entity
|
||||
ignore_uid: number = 0 (ENTITY_NONE),
|
||||
-- id of the entity types used for filtering
|
||||
filter_entities: table<string|number> = nil,
|
||||
-- exclusion filtering mode:
|
||||
-- if true, filter_entities defines the list of ignored entity types
|
||||
entities_exclusion: bool = false,
|
||||
|
||||
[==[ additional parameters (blocks) ]==]
|
||||
-- id of the block types used for filtering
|
||||
filter_blocks: table<string|number> = nil,
|
||||
-- exclusion filtering mode:
|
||||
-- if true, filter_blocks defines the list Ignored
|
||||
blocks_exclusion: bool = true,
|
||||
-- include blocks with selectable=false
|
||||
nonselect_blocks: bool = false;
|
||||
}
|
||||
) -> {
|
||||
block: int or nil, -- block id
|
||||
entity: int or nil, -- entity uid
|
||||
endpoint: vec3, -- ray touchpoint
|
||||
iendpoint: vec3, -- position of the block touched by the ray
|
||||
length: number, -- ray length
|
||||
normal: vec3, -- normal vector of the surface touched by the ray
|
||||
} or nil if the ray did not touch a block or entity
|
||||
```
|
||||
```
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -264,3 +275,9 @@ function on_used(playerid: int)
|
|||
```
|
||||
|
||||
Called when an entity is used (RMB by entity). The player ID is passed as an argument.
|
||||
|
||||
```lua
|
||||
function on_player_set(playerid: int)
|
||||
```
|
||||
|
||||
Called when an entity is attached to a player.
|
||||
|
|
|
|||
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
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
| render_distance | Дистанция отрисовки текста | 32 |
|
||||
| xray_opacity | Коэффициент видимости через препятствия (просвечивание) | 0 |
|
||||
| perspective | Коэффициент перспективы | 1 |
|
||||
| font | Шрифт. При отсутствии используется основной | "" |
|
||||
|
||||
Доступные форматы отображения:
|
||||
|
||||
|
|
|
|||
|
|
@ -66,6 +66,10 @@
|
|||
- `relation` - отношение текущего значения к изначальному (x/y)
|
||||
- `vbar` - вертикальная шкала (используется по-умолчанию)
|
||||
|
||||
### Имя скрипта - `script-name`
|
||||
|
||||
Определяет имя скрипта, содержащего обработчики событий предмета. По-умолчанию соответствует строковому id (имени) предмета.
|
||||
Свойство обеспечивает возможность использования одного скрипта для нескольких предметов.
|
||||
|
||||
## Теги - *tags*
|
||||
|
||||
|
|
|
|||
|
|
@ -10,14 +10,14 @@
|
|||
| lighting | Освещение. | true |
|
||||
| collision | Обнаружение столкновений. | true |
|
||||
| max_distance | Максимальная дистанция от камеры, при которой происходит спавн частиц. | 16.0 |
|
||||
| spawn_interval | Интервал спавна частиц в секундах. | 1.0 |
|
||||
| spawn_interval | Интервал спавна частиц в секундах, также задержка перед спавном первой. | 1.0 |
|
||||
| lifetime | Среднее время жизни частиц в секундах. | 5.0 |
|
||||
| lifetime_spread | Максимальное отклонение времени жизни частицы (от 0.0 до 1.0). | 0.2 |
|
||||
| velocity | Начальная линейная скорость частиц. | {0, 0, 0} |
|
||||
| acceleration | Ускорение частиц. | {0, -16, 0} |
|
||||
| explosion | Сила разлёта частиц при спавне. | {2, 2, 2} |
|
||||
| size | Размер частиц. | {0.1, 0.1, 0.1} |
|
||||
| size_spread | Максимальное отклонение времени размера частиц. | 0.2 |
|
||||
| size_spread | Максимальное отклонение размера частиц. | 0.2 |
|
||||
| angle_spread | Максимальное отклонение начального угла поворота (от 0 до 1) | 0.0 |
|
||||
| min_angular_vel | Минимальная угловая скорость (радианы в сек.). Неотрицательное. | 0.0 |
|
||||
| max_angular_vel | Максимальная угловая скорость (радианы в сек.). Неотрицательное. | 0.0 |
|
||||
|
|
@ -36,7 +36,8 @@ gfx.particles.emit(
|
|||
count: int,
|
||||
-- таблица настроек частиц
|
||||
preset: table,
|
||||
-- дополнительная таблица настроек частиц
|
||||
-- таблица настроек частиц перезаписывающая значения параметров
|
||||
-- используется вместо копирования и модификации 'preset'
|
||||
[опционально] extension: table
|
||||
) -> int
|
||||
```
|
||||
|
|
|
|||
|
|
@ -60,6 +60,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 вернёт кешированный результат.
|
||||
|
|
|
|||
|
|
@ -115,6 +115,8 @@ block.raycast(start: vec3, dir: vec3, max_distance: number, [опциональ
|
|||
|
||||
Для результата будет использоваться целевая (dest) таблица вместо создания новой, если указан опциональный аргумент.
|
||||
|
||||
Для более тонкой настройки используйте `world.raycast(...)`.
|
||||
|
||||
## Вращение
|
||||
|
||||
```lua
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -70,3 +73,5 @@ entities.raycast(start: vec3, dir: vec3, max_distance: number, ignore: int, [opt
|
|||
Функция является расширенным вариантом [block.raycast](libblock.md#raycast). Возвращает таблицу с результатами если луч касается блока, либо сущности.
|
||||
|
||||
Соответственно это повлияет на наличие полей *entity* и *block*.
|
||||
|
||||
Для более тонкой настройки используйте `world.raycast(...)`.
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ input.get_bindings() -> table<string>
|
|||
input.get_binding_text(bindname: string) -> string
|
||||
|
||||
-- Проверяет активность привязки.
|
||||
input.is_active(bindname: string) -> string
|
||||
input.is_active(bindname: string) -> boolean
|
||||
|
||||
-- Включает/выключает привязку до выхода из мира.
|
||||
input.set_enabled(bindname: string, flag: boolean)
|
||||
|
|
|
|||
|
|
@ -170,7 +170,19 @@ inventory.get_all_data(
|
|||
invid: int,
|
||||
-- индекс слота
|
||||
slot: int,
|
||||
) -> table
|
||||
) -> table | nil
|
||||
|
||||
-- Устанавливает значения локальных cвойств предмета.
|
||||
inventory.set_all_data(
|
||||
-- id инвентаря
|
||||
invid: int,
|
||||
-- индекс слота
|
||||
slot: int,
|
||||
-- таблица cвойство:значение
|
||||
fields: table,
|
||||
-- удалить остальные значения свойств
|
||||
[опционально] clear: boolean = false
|
||||
)
|
||||
```
|
||||
|
||||
```lua
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
|
|
@ -69,4 +69,44 @@ world.save_chunk_data(
|
|||
-- сжатые данные чанка
|
||||
data: Bytearray
|
||||
)
|
||||
|
||||
-- Бросает луч из точки start в направлении dir.
|
||||
world.raycast(
|
||||
params: table {
|
||||
[==[ обязательные параметры ]==]
|
||||
-- стартовая точка
|
||||
start: vec3,
|
||||
-- вектор направления луча
|
||||
dir: vec3,
|
||||
-- максимальное расстояние
|
||||
distance: number,
|
||||
|
||||
[==[ дополнительные параметры (сущности) ]==]
|
||||
-- учитывать сущности
|
||||
entities: bool = true,
|
||||
-- uid игнорируемой сущности
|
||||
ignore_uid: number = 0 (ENTITY_NONE),
|
||||
-- id типов сущностей используемых для фильтрации
|
||||
filter_entities: table<string|number> = nil,
|
||||
-- исключающий режим фильтрации:
|
||||
-- при значении (true) filter_entities определяет список игнорируемых
|
||||
entities_exclusion: bool = false,
|
||||
|
||||
[==[ дополнительные параметры (блоки) ]==]
|
||||
-- id типов блоков используемых для фильтрации
|
||||
filter_blocks: table<string|number> = nil,
|
||||
-- исключающий режим фильтрации:
|
||||
-- при значении (true) filter_blocks определяет список игнорируемых
|
||||
blocks_exclusion: bool = true,
|
||||
-- учитывать блоки с selectable=false
|
||||
nonselect_blocks: bool = false;
|
||||
}
|
||||
) -> {
|
||||
block: int или nil, -- id блока
|
||||
entity: int или nil, -- uid сущности
|
||||
endpoint: vec3, -- точка касания луча
|
||||
iendpoint: vec3, -- позиция блока, которого касается луч
|
||||
length: number, -- длина луча
|
||||
normal: vec3, -- вектор нормали поверхности, которой касается луч
|
||||
} или nil если луч не коснулся блока или сущности
|
||||
```
|
||||
|
|
@ -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]
|
||||
> При выходе сущности за пределы зоны прогрузки она удаляется, вызывая события.
|
||||
> При повторном попадании в зону прогрузки сущность спавнится заново.
|
||||
|
|
@ -265,3 +276,8 @@ function on_used(playerid: int)
|
|||
|
||||
Вызывается при использовании сущности (ПКМ по сущности). ID игрока передается в качестве аргумента.
|
||||
|
||||
```lua
|
||||
function on_player_set(playerid: int)
|
||||
```
|
||||
|
||||
Вызывается при прикреплении сущности к игроку.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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`)
|
||||
|
||||
|
|
@ -28,10 +28,10 @@
|
|||
имеет три подрежима:
|
||||
|
||||
| Режим | Описание |
|
||||
|---------------|--------|
|
||||
|---------------|-------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `"default"` | Прямой режим. `read` может вернуть меньше байт, чем запрошено. `write` сразу отправляет данные в низкоуровневый дескриптор. Нет буферизации. |
|
||||
| `"yield"` | Как `default`, но при нехватке данных в `read(n)` поток будет вызывать `coroutine.yield()`, пока не соберёт ровно `n` байт. Удобно для корутин. |
|
||||
| `"buffered"` | Включает внутренние буферы чтения и записи. `read` берёт данные из буфера, `write` — складывает в буфер. При превышении `maxBufferSize` — ошибка `buffer overflow`. |
|
||||
| `"buffered"` | Включает внутренние буферы чтения и записи. `read` берёт данные из буфера , `write` — складывает в буфер. |
|
||||
|
||||
### flush
|
||||
|
||||
|
|
@ -39,8 +39,8 @@
|
|||
имеет два подрежима:
|
||||
|
||||
| Режим | Что делает `flush()` |
|
||||
|------------------|----------------------|
|
||||
| `"all"` (по умолчанию) | Сначала сбрасывает буфер записи → низкоуровневый `write`, затем вызывает `ioLib.flush(descriptor)` |
|
||||
|------------------------|-----------------------------------------------------------------------------------------------------|
|
||||
| `"all"` (по умолчанию) | Сначала сбрасывает буфер записи → низкоуровневый `write`, затем вызывает `io_lib.flush(descriptor)` |
|
||||
| `"buffer"` | Сбрасывает только буфер записи, без вызова системного `flush` |
|
||||
|
||||
|
||||
|
|
@ -49,12 +49,12 @@
|
|||
Определяет, в каком виде методы `read` и `write` принимают и возвращают данные:
|
||||
|
||||
| binary = true | binary = false (по умолчанию) |
|
||||
|-----------------------------------------|----------------------------------------|
|
||||
|------------------------------------------------------------|----------------------------------------|
|
||||
| Данные — это **байты** (`Bytearray`, таблица чисел 0..255) | Данные — это **текстовые строки** |
|
||||
| `read(n)` → `Bytearray` или `table<number>`| `read()` → одна строка |
|
||||
| `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`|
|
||||
| `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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
@ -52,3 +52,7 @@ function on_open()
|
|||
end)
|
||||
end
|
||||
end
|
||||
|
||||
function on_destroy()
|
||||
events.remove("core:chat", core_chat_handler)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -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,8 +1,77 @@
|
|||
local this = {}
|
||||
local app = __vc_app
|
||||
|
||||
function this.equals(expected, fact)
|
||||
local function diff_canvas(expected, fact)
|
||||
this.equals(expected.width, fact.width, "image width")
|
||||
this.equals(expected.width, fact.width, "image height")
|
||||
|
||||
local diff = Canvas({expected.width, expected.height})
|
||||
|
||||
local mismatches = 0
|
||||
|
||||
for y = 0, expected.height - 1 do
|
||||
for x = 0, expected.width - 1 do
|
||||
local ca = expected:at(x, y)
|
||||
local cb = fact:at(x, y)
|
||||
|
||||
if ca == cb then
|
||||
diff:set(x, y, ca)
|
||||
else
|
||||
diff:set(x, y, 0xff0000ff)
|
||||
mismatches = mismatches + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
return diff, mismatches
|
||||
end
|
||||
|
||||
local TEST_ARTIFACTS_PATH = "export:test-results"
|
||||
|
||||
local function get_test_artifact_path(name)
|
||||
local path = TEST_ARTIFACTS_PATH
|
||||
if not file.isdir(path) then
|
||||
file.mkdirs(path)
|
||||
end
|
||||
return file.join(path, name)
|
||||
end
|
||||
|
||||
function this.equals(expected, fact, prefix)
|
||||
local mt = getmetatable(expected)
|
||||
if mt == Canvas then
|
||||
local diff, mismatches = diff_canvas(expected, fact)
|
||||
if mismatches > 0 then
|
||||
prefix = prefix or app.script or base64.encode_urlsafe(random.bytes(6))
|
||||
local path_diff = get_test_artifact_path(
|
||||
string.format("%s.diff.png", prefix)
|
||||
)
|
||||
local path_fact = get_test_artifact_path(
|
||||
string.format("%s.fact.png", prefix)
|
||||
)
|
||||
local path_expected = get_test_artifact_path(
|
||||
string.format("%s.expected.png", prefix)
|
||||
)
|
||||
file.write_bytes(path_diff, diff:encode('png'))
|
||||
file.write_bytes(path_fact, fact:encode('png'))
|
||||
file.write_bytes(path_expected, expected:encode('png'))
|
||||
local total_pixels = expected.width * expected.height
|
||||
assert(mismatches == 0, string.format(
|
||||
"%s pixels of %s (%s%%) are different, diff saved as %s",
|
||||
mismatches,
|
||||
total_pixels,
|
||||
math.round(mismatches / total_pixels * 100, 2),
|
||||
string.escape(path_diff))
|
||||
)
|
||||
end
|
||||
return
|
||||
end
|
||||
if prefix then
|
||||
prefix = prefix .. " "
|
||||
else
|
||||
prefix = ""
|
||||
end
|
||||
assert(fact == expected, string.format(
|
||||
"(fact == expected) assertion failed\n Expected: %s\n Fact: %s",
|
||||
prefix .. "(fact == expected) assertion failed\n Expected: %s\n Fact: %s",
|
||||
expected, fact
|
||||
))
|
||||
end
|
||||
|
|
|
|||
|
|
@ -12,11 +12,30 @@ FFI.cdef[[
|
|||
} bytearray_t;
|
||||
]]
|
||||
|
||||
local malloc = FFI.C.malloc
|
||||
local free = FFI.C.free
|
||||
|
||||
local FFIBytearray
|
||||
local bytearray_type
|
||||
|
||||
local allocated_bytes = 0
|
||||
local GC_THRESHOLD = 50 * 1024 * 1024
|
||||
|
||||
local function malloc(size)
|
||||
local raw = FFI.C.malloc(size)
|
||||
if raw == nil then
|
||||
return nil
|
||||
end
|
||||
|
||||
allocated_bytes = allocated_bytes + size
|
||||
|
||||
if allocated_bytes >= GC_THRESHOLD then
|
||||
collectgarbage("step", 200)
|
||||
allocated_bytes = 0
|
||||
end
|
||||
|
||||
return raw
|
||||
end
|
||||
|
||||
local function grow_buffer(self, elems)
|
||||
local new_capacity = math.ceil(self.capacity / 0.75 + elems)
|
||||
local prev = self.bytes
|
||||
|
|
|
|||
|
|
@ -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,12 +59,8 @@ 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
|
||||
end
|
||||
|
||||
return t1
|
||||
end
|
||||
|
|
@ -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}, {
|
||||
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 = {}
|
||||
|
|
|
|||
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,14 +4,27 @@ 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
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
return available
|
||||
else
|
||||
return #self.readBuffer >= length
|
||||
end
|
||||
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()
|
||||
length = math.min(buf_len, length)
|
||||
|
||||
copy:append(self.readBuffer)
|
||||
local copy = self.read_buffer:slice(1, length)
|
||||
|
||||
self.readBuffer:clear()
|
||||
if buf_len == length then
|
||||
self.read_buffer:clear()
|
||||
else
|
||||
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
|
||||
|
|
@ -5,4 +5,25 @@ function util.create_demo_world(generator)
|
|||
app.new_world("demo", "2019", generator or "core:default")
|
||||
end
|
||||
|
||||
function util.check_vec3(expected, fact, name, delta)
|
||||
delta = delta or 1e-4
|
||||
for i=1,3 do
|
||||
assert(math.abs(fact[i] - expected[i]) < delta,
|
||||
name.."["..i.."] expected: "..expected[i]..", fact: "..fact[i])
|
||||
end
|
||||
end
|
||||
|
||||
function util.check_float(expected, fact, name, delta)
|
||||
delta = delta or 1e-4
|
||||
local actual_delta = math.abs(fact - expected)
|
||||
assert(actual_delta < delta,
|
||||
name.." expected: "..expected..", fact: "..fact..
|
||||
" (delta: "..actual_delta.." > "..delta..")")
|
||||
end
|
||||
|
||||
function util.check_int(expected, fact, name)
|
||||
assert(fact == expected,
|
||||
name.." expected: "..expected..", fact: "..fact)
|
||||
end
|
||||
|
||||
return util
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -142,14 +142,16 @@ end
|
|||
|
||||
function is_flight() return flight end
|
||||
|
||||
function set_flight(flag) flight = flag end
|
||||
function set_flight(flag)
|
||||
flight = flag
|
||||
body:set_gravity_scale(flight and 0.0 or props.gravity_scale)
|
||||
end
|
||||
|
||||
local prev_angle = (vec2.angle({dir[3], dir[1]})) % 360
|
||||
|
||||
function on_physics_update(delta)
|
||||
local grounded = body:is_grounded()
|
||||
body:set_vdamping(flight)
|
||||
body:set_gravity_scale(flight and 0.0 or props.gravity_scale)
|
||||
body:set_linear_damping(
|
||||
(flight or not grounded) and props.air_damping or props.ground_damping
|
||||
)
|
||||
|
|
|
|||
|
|
@ -58,16 +58,12 @@ local function process_player_inputs(pid, rot, delta)
|
|||
end
|
||||
end
|
||||
|
||||
local prepared = false
|
||||
body:set_enabled(false)
|
||||
mob.set_flight(true)
|
||||
function on_player_set(pid)
|
||||
mob.set_flight(player.is_flight(pid))
|
||||
body:set_body_type(player.is_noclip(pid) and "kinematic" or "dynamic")
|
||||
end
|
||||
|
||||
function on_physics_update(delta)
|
||||
if not prepared then
|
||||
prepared = true
|
||||
body:set_enabled(true)
|
||||
end
|
||||
|
||||
local pid = entity:get_player()
|
||||
if pid == -1 then
|
||||
return
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -312,6 +316,8 @@ entities.get_all = function(uids)
|
|||
return stdcomp.get_all(uids)
|
||||
end
|
||||
end
|
||||
world.raycast = entities.__world_raycast
|
||||
entities.__world_raycast = nil
|
||||
|
||||
__vc_scripts_registry = require "core:internal/scripts_registry"
|
||||
|
||||
|
|
@ -332,6 +338,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"
|
||||
|
|
|
|||
|
|
@ -37,6 +37,14 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
|
|||
"$ENV{VCPKG_ROOT}/packages/luajit_${VCPKG_TARGET_TRIPLET}/include/"
|
||||
)
|
||||
else()
|
||||
set(LUAJIT_PACKAGE_DIR "$ENV{VCPKG_ROOT}/packages/luajit_${VCPKG_TARGET_TRIPLET}")
|
||||
|
||||
if(EXISTS "$ENV{VCPKG_ROOT}/packages/luajit_${VCPKG_TARGET_TRIPLET}/include/luajit-2.1")
|
||||
set(LUAJIT_INCLUDE_DIR "$ENV{VCPKG_ROOT}/packages/luajit_${VCPKG_TARGET_TRIPLET}/include/luajit-2.1")
|
||||
else()
|
||||
set(LUAJIT_INCLUDE_DIR "$ENV{VCPKG_ROOT}/packages/luajit_${VCPKG_TARGET_TRIPLET}/include/luajit")
|
||||
endif()
|
||||
|
||||
add_library(luajit SHARED IMPORTED)
|
||||
set_target_properties(
|
||||
luajit
|
||||
|
|
@ -45,8 +53,7 @@ if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
|
|||
"$ENV{VCPKG_ROOT}/packages/luajit_${VCPKG_TARGET_TRIPLET}/bin/lua51.dll"
|
||||
IMPORTED_IMPLIB
|
||||
"$ENV{VCPKG_ROOT}/packages/luajit_${VCPKG_TARGET_TRIPLET}/lib/lua51.lib"
|
||||
INTERFACE_INCLUDE_DIRECTORIES
|
||||
"$ENV{VCPKG_ROOT}/packages/luajit_${VCPKG_TARGET_TRIPLET}/include/luajit"
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${LUAJIT_INCLUDE_DIR}"
|
||||
)
|
||||
endif()
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -210,7 +216,9 @@ void audio::initialize(
|
|||
::input_device = backend->openInputDevice(
|
||||
settings.inputDevice.get(), 44100, 1, 16
|
||||
);
|
||||
if (::input_device != nullptr) {
|
||||
::input_device->startCapture();
|
||||
}
|
||||
} else {
|
||||
if (::input_device) {
|
||||
::input_device->stopCapture();
|
||||
|
|
|
|||
|
|
@ -22,9 +22,26 @@ namespace {
|
|||
}
|
||||
|
||||
inline double power(double base, int64_t power) {
|
||||
if (power == 0) {
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
uint64_t exp;
|
||||
if (power < 0) {
|
||||
base = 1.0 / base;
|
||||
exp = static_cast<uint64_t>(-(power + 1)) + 1;
|
||||
} else {
|
||||
exp = static_cast<uint64_t>(power);
|
||||
}
|
||||
|
||||
double result = 1.0;
|
||||
for (int64_t i = 0; i < power; i++) {
|
||||
|
||||
while (exp > 0) {
|
||||
if (exp & 1)
|
||||
result *= base;
|
||||
|
||||
base *= base;
|
||||
exp >>= 1;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -49,9 +49,9 @@ static util::Buffer<ubyte> write_to_memory(uint width, uint height, const ubyte*
|
|||
png_write_info(png_ptr, info_ptr);
|
||||
|
||||
auto row = std::make_unique<png_byte[]>(pixsize * width);
|
||||
for (uint y = 0; y < height; y++) {
|
||||
for (uint x = 0; x < width; x++) {
|
||||
for (uint i = 0; i < pixsize; i++) {
|
||||
for (int y = height-1; y >= 0; y--) {
|
||||
for (int x = 0; x < width; x++) {
|
||||
for (int i = 0; i < pixsize; i++) {
|
||||
row[x * pixsize + i] =
|
||||
(png_byte)data[(y * width + x) * pixsize + i];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -188,6 +188,7 @@ public:
|
|||
ContentUnitDefs<GeneratorDef> generators;
|
||||
std::unique_ptr<DrawGroups> const drawGroups;
|
||||
ResourceIndicesSet resourceIndices {};
|
||||
std::set<std::string> components {};
|
||||
|
||||
Content(
|
||||
std::unique_ptr<ContentIndices> indices,
|
||||
|
|
|
|||
|
|
@ -178,7 +178,16 @@ void ContentUnitLoader<DefT>::loadUnit(
|
|||
) {
|
||||
auto folder = pack.folder;
|
||||
auto configFile = folder / (defsDir + "/" + name + ".json");
|
||||
if (io::exists(configFile)) loadUnit(def, full, configFile);
|
||||
if (io::exists(configFile)) {
|
||||
try {
|
||||
loadUnit(def, full, configFile);
|
||||
} catch (const std::runtime_error& err) {
|
||||
throw std::runtime_error(
|
||||
"file " + util::quote(configFile.string()) + ": " +
|
||||
std::string(err.what())
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ContentLoader::loadBlockMaterial(
|
||||
|
|
@ -497,6 +506,7 @@ void ContentLoader::loadScripts(Content& content) {
|
|||
scripting::on_scripts_loading();
|
||||
load_scripts(content, content.blocks);
|
||||
load_scripts(content, content.items);
|
||||
auto& tmpContent = content;
|
||||
|
||||
for (const auto& [packid, runtime] : content.getPacks()) {
|
||||
auto env = runtime->getEnvironment();
|
||||
|
|
@ -507,7 +517,7 @@ void ContentLoader::loadScripts(Content& content) {
|
|||
|
||||
// Load entity components
|
||||
io::path componentsDir = folder / "scripts/components";
|
||||
foreach_file(componentsDir, [&pack, env](const io::path& file) {
|
||||
foreach_file(componentsDir, [&pack, env, &tmpContent](const io::path& file) {
|
||||
auto name = pack.id + ":" + file.stem();
|
||||
scripting::load_entity_component(
|
||||
env,
|
||||
|
|
@ -515,9 +525,21 @@ void ContentLoader::loadScripts(Content& content) {
|
|||
file,
|
||||
pack.id + ":scripts/components/" + file.name()
|
||||
);
|
||||
tmpContent.components.insert(name);
|
||||
});
|
||||
}
|
||||
|
||||
for (const auto& [eid, def] : content.entities.getDefs()) {
|
||||
for (const auto& instance : def->components) {
|
||||
if (content.components.find(instance.component) == content.components.end()) {
|
||||
throw std::runtime_error(
|
||||
"component " + instance.component +
|
||||
" is not available (required by entity " + eid + ")"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
scripting::on_content_loaded();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -53,6 +53,9 @@ static void load_variant(
|
|||
}
|
||||
} else if (root.has("texture-faces")) {
|
||||
const auto& texarr = root["texture-faces"];
|
||||
if (texarr.length() < 6) {
|
||||
throw std::runtime_error("texture-faces requires 6 elements");
|
||||
}
|
||||
for (uint i = 0; i < 6; i++) {
|
||||
variant.textureFaces[i] = texarr[i].asString();
|
||||
}
|
||||
|
|
|
|||
63
src/devtools/AppScriptsControl.cpp
Normal file
63
src/devtools/AppScriptsControl.cpp
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
#include "AppScriptsControl.hpp"
|
||||
|
||||
#include "debug/Logger.hpp"
|
||||
#include "engine/CoreParameters.hpp"
|
||||
#include "io/io.hpp"
|
||||
#include "Project.hpp"
|
||||
#include "logic/scripting/scripting.hpp"
|
||||
|
||||
static debug::Logger logger("app-scripts");
|
||||
|
||||
AppScriptsControl::AppScriptsControl(
|
||||
const CoreParameters& params, const Project& project
|
||||
)
|
||||
: project(project) {
|
||||
|
||||
io::path scriptFile =
|
||||
params.scriptFile.empty()
|
||||
? "project:start.lua"
|
||||
: std::string("script:") + params.scriptFile.filename().u8string();
|
||||
if (io::exists(scriptFile)) {
|
||||
logger.info() << "starting script: " << params.scriptFile.u8string();
|
||||
scriptCoroutine = scripting::start_app_script(scriptFile);
|
||||
} else {
|
||||
logger.warning() << "script does not exists: " << params.scriptFile.u8string();
|
||||
}
|
||||
|
||||
if (!params.headless) {
|
||||
loadProjectClientScript();
|
||||
}
|
||||
}
|
||||
|
||||
void AppScriptsControl::loadProjectClientScript() {
|
||||
io::path scriptFile = "project:project_client.lua";
|
||||
if (io::exists(scriptFile)) {
|
||||
logger.info() << "starting project client script: " << scriptFile.string();
|
||||
clientScript = scripting::load_client_project_script(scriptFile);
|
||||
} else {
|
||||
logger.warning() << "project client script does not exists: " << scriptFile.string();
|
||||
}
|
||||
}
|
||||
|
||||
void AppScriptsControl::onScreenChange(const std::string& name, bool show) {
|
||||
if (clientScript) {
|
||||
clientScript->onScreenChange(name, show);
|
||||
}
|
||||
}
|
||||
|
||||
void AppScriptsControl::tick() {
|
||||
if (scriptCoroutine && scriptCoroutine->isActive()) {
|
||||
scriptCoroutine->update();
|
||||
}
|
||||
}
|
||||
|
||||
void AppScriptsControl::terminate(std::string_view reason) {
|
||||
if (scriptCoroutine->isActive()) {
|
||||
scriptCoroutine->terminate();
|
||||
logger.info() << "script has been terminated due to " << reason;
|
||||
}
|
||||
}
|
||||
|
||||
bool AppScriptsControl::isFinished() const {
|
||||
return scriptCoroutine == nullptr || !scriptCoroutine->isActive();
|
||||
}
|
||||
30
src/devtools/AppScriptsControl.hpp
Normal file
30
src/devtools/AppScriptsControl.hpp
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
#pragma once
|
||||
|
||||
#include "interfaces/Process.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
namespace scripting {
|
||||
class IClientProjectScript;
|
||||
}
|
||||
|
||||
struct CoreParameters;
|
||||
struct Project;
|
||||
|
||||
class AppScriptsControl {
|
||||
public:
|
||||
AppScriptsControl(const CoreParameters& params, const Project& project);
|
||||
|
||||
void tick();
|
||||
void loadProjectClientScript();
|
||||
void terminate(std::string_view reason);
|
||||
|
||||
void onScreenChange(const std::string& name, bool show);
|
||||
|
||||
bool isFinished() const;
|
||||
private:
|
||||
const Project& project;
|
||||
std::unique_ptr<scripting::IClientProjectScript> clientScript;
|
||||
std::unique_ptr<Process> scriptCoroutine;
|
||||
};
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include "data/dv_util.hpp"
|
||||
#include "debug/Logger.hpp"
|
||||
#include "engine/CoreParameters.hpp"
|
||||
#include "io/io.hpp"
|
||||
#include "io/path.hpp"
|
||||
#include "logic/scripting/scripting.hpp"
|
||||
|
|
@ -40,26 +41,6 @@ void Project::deserialize(const dv::value& src) {
|
|||
}
|
||||
}
|
||||
|
||||
void Project::loadProjectClientScript() {
|
||||
io::path scriptFile = "project:project_client.lua";
|
||||
if (io::exists(scriptFile)) {
|
||||
logger.info() << "starting project client script";
|
||||
clientScript = scripting::load_client_project_script(scriptFile);
|
||||
} else {
|
||||
logger.warning() << "project client script does not exists";
|
||||
}
|
||||
}
|
||||
|
||||
void Project::loadProjectStartScript() {
|
||||
io::path scriptFile = "project:start.lua";
|
||||
if (io::exists(scriptFile)) {
|
||||
logger.info() << "starting project start script";
|
||||
setupCoroutine = scripting::start_app_script(scriptFile);
|
||||
} else {
|
||||
logger.warning() << "project start script does not exists";
|
||||
}
|
||||
}
|
||||
|
||||
bool Permissions::has(const std::string& name) const {
|
||||
return permissions.find(name) != permissions.end();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,12 @@
|
|||
#pragma once
|
||||
|
||||
#include "interfaces/Serializable.hpp"
|
||||
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
#include "interfaces/Process.hpp"
|
||||
#include "interfaces/Serializable.hpp"
|
||||
|
||||
namespace scripting {
|
||||
class IClientProjectScript;
|
||||
}
|
||||
|
||||
struct Permissions {
|
||||
static inline std::string DEBUGGING = "debugging";
|
||||
static inline std::string NETWORK = "network";
|
||||
|
|
@ -27,15 +22,10 @@ struct Project : Serializable {
|
|||
std::string name;
|
||||
std::string title;
|
||||
std::vector<std::string> basePacks;
|
||||
std::unique_ptr<scripting::IClientProjectScript> clientScript;
|
||||
std::unique_ptr<Process> setupCoroutine;
|
||||
Permissions permissions;
|
||||
|
||||
~Project();
|
||||
|
||||
dv::value serialize() const override;
|
||||
void deserialize(const dv::value& src) override;
|
||||
|
||||
void loadProjectClientScript();
|
||||
void loadProjectStartScript();
|
||||
};
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
#include "content/ContentControl.hpp"
|
||||
#include "core_defs.hpp"
|
||||
#include "debug/Logger.hpp"
|
||||
#include "devtools/AppScriptsControl.hpp"
|
||||
#include "devtools/DebuggingServer.hpp"
|
||||
#include "devtools/Editor.hpp"
|
||||
#include "devtools/Project.hpp"
|
||||
|
|
@ -191,10 +192,8 @@ void Engine::initialize(CoreParameters coreParameters) {
|
|||
audio::set_input_device(name == "auto" ? "" : name);
|
||||
}));
|
||||
|
||||
project->loadProjectStartScript();
|
||||
if (!params.headless) {
|
||||
project->loadProjectClientScript();
|
||||
}
|
||||
appScripts = std::make_unique<AppScriptsControl>(params, *project);
|
||||
|
||||
if (params.stdinCommands) {
|
||||
cmd::start_stdin_cmd_reader(*this);
|
||||
}
|
||||
|
|
@ -263,9 +262,7 @@ void Engine::detachDebugger() {
|
|||
}
|
||||
|
||||
void Engine::applicationTick() {
|
||||
if (project->setupCoroutine && project->setupCoroutine->isActive()) {
|
||||
project->setupCoroutine->update();
|
||||
}
|
||||
appScripts->tick();
|
||||
}
|
||||
|
||||
void Engine::updateFrontend() {
|
||||
|
|
@ -353,6 +350,7 @@ void Engine::close() {
|
|||
network.reset();
|
||||
clearKeepedObjects();
|
||||
project.reset();
|
||||
appScripts.reset();
|
||||
scripting::close();
|
||||
logger.info() << "scripting finished";
|
||||
if (!params.headless) {
|
||||
|
|
@ -387,8 +385,8 @@ void Engine::loadProject() {
|
|||
}
|
||||
|
||||
void Engine::setScreen(std::shared_ptr<Screen> screen) {
|
||||
if (project->clientScript && this->screen) {
|
||||
project->clientScript->onScreenChange(this->screen->getName(), false);
|
||||
if (this->screen) {
|
||||
appScripts->onScreenChange(this->screen->getName(), false);
|
||||
}
|
||||
// reset audio channels (stop all sources)
|
||||
audio::reset_channel(audio::get_channel_index("regular"));
|
||||
|
|
@ -397,8 +395,8 @@ void Engine::setScreen(std::shared_ptr<Screen> screen) {
|
|||
if (this->screen) {
|
||||
this->screen->onOpen();
|
||||
}
|
||||
if (project->clientScript && this->screen) {
|
||||
project->clientScript->onScreenChange(this->screen->getName(), true);
|
||||
if (this->screen) {
|
||||
appScripts->onScreenChange(this->screen->getName(), true);
|
||||
window->setShouldRefresh();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
class Assets;
|
||||
class AssetsLoader;
|
||||
class AssetsManagement;
|
||||
class AppScriptsControl;
|
||||
class ContentControl;
|
||||
class EngineController;
|
||||
class EnginePaths;
|
||||
|
|
@ -55,6 +56,7 @@ class Engine : public util::ObjectsKeeper {
|
|||
std::unique_ptr<Project> project;
|
||||
std::unique_ptr<SettingsHandler> settingsHandler;
|
||||
std::unique_ptr<AssetsManagement> assets;
|
||||
std::unique_ptr<AppScriptsControl> appScripts;
|
||||
std::shared_ptr<Screen> screen;
|
||||
std::unique_ptr<ContentControl> content;
|
||||
std::unique_ptr<EngineController> controller;
|
||||
|
|
@ -184,5 +186,9 @@ public:
|
|||
return debuggingServer.get();
|
||||
}
|
||||
|
||||
AppScriptsControl& getAppScripts() {
|
||||
return *appScripts;
|
||||
}
|
||||
|
||||
void detachDebugger();
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,14 +1,17 @@
|
|||
#include "Mainloop.hpp"
|
||||
|
||||
#include "Engine.hpp"
|
||||
#include "debug/Logger.hpp"
|
||||
#include "devtools/AppScriptsControl.hpp"
|
||||
#include "devtools/Project.hpp"
|
||||
#include "frontend/screens/MenuScreen.hpp"
|
||||
#include "Engine.hpp"
|
||||
#include "frontend/screens/LevelScreen.hpp"
|
||||
#include "frontend/screens/MenuScreen.hpp"
|
||||
#include "graphics/ui/elements/Container.hpp"
|
||||
#include "graphics/ui/GUI.hpp"
|
||||
#include "io/path.hpp"
|
||||
#include "logic/scripting/scripting.hpp"
|
||||
#include "window/Window.hpp"
|
||||
#include "world/Level.hpp"
|
||||
#include "graphics/ui/GUI.hpp"
|
||||
#include "graphics/ui/elements/Container.hpp"
|
||||
|
||||
static debug::Logger logger("mainloop");
|
||||
|
||||
|
|
@ -19,6 +22,7 @@ void Mainloop::run() {
|
|||
auto& time = engine.getTime();
|
||||
auto& window = engine.getWindow();
|
||||
auto& settings = engine.getSettings();
|
||||
const auto& coreParams = engine.getCoreParameters();
|
||||
|
||||
engine.setLevelConsumer([this](auto level, int64_t localPlayer) {
|
||||
if (level == nullptr) {
|
||||
|
|
@ -36,8 +40,10 @@ void Mainloop::run() {
|
|||
logger.info() << "starting menu screen";
|
||||
engine.setScreen(std::make_shared<MenuScreen>(engine));
|
||||
|
||||
auto& appScripts = engine.getAppScripts();
|
||||
|
||||
logger.info() << "main loop started";
|
||||
while (!window.isShouldClose()){
|
||||
while (!window.isShouldClose()) {
|
||||
time.update(window.time());
|
||||
engine.applicationTick();
|
||||
engine.updateFrontend();
|
||||
|
|
@ -50,6 +56,10 @@ void Mainloop::run() {
|
|||
settings.display.adaptiveFpsInMenu.get() &&
|
||||
dynamic_cast<const MenuScreen*>(engine.getScreen().get()) != nullptr
|
||||
);
|
||||
if (coreParams.testMode && appScripts.isFinished()) {
|
||||
logger.info() << "test finished";
|
||||
engine.quit();
|
||||
}
|
||||
}
|
||||
logger.info() << "main loop stopped";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,14 @@
|
|||
#include "ServerMainloop.hpp"
|
||||
|
||||
#include "Engine.hpp"
|
||||
#include "logic/scripting/scripting.hpp"
|
||||
#include "devtools/AppScriptsControl.hpp"
|
||||
#include "logic/LevelController.hpp"
|
||||
#include "interfaces/Process.hpp"
|
||||
#include "debug/Logger.hpp"
|
||||
#include "world/Level.hpp"
|
||||
#include "world/World.hpp"
|
||||
#include "util/platform.hpp"
|
||||
#include "devtools/Project.hpp"
|
||||
|
||||
#include <chrono>
|
||||
|
||||
|
|
@ -32,21 +33,12 @@ void ServerMainloop::run() {
|
|||
setLevel(std::move(level));
|
||||
});
|
||||
|
||||
auto process = scripting::start_app_script(
|
||||
"script:" + coreParams.scriptFile.filename().u8string()
|
||||
);
|
||||
|
||||
double targetDelta = 1.0 / static_cast<double>(coreParams.tps);
|
||||
double delta = targetDelta;
|
||||
auto begin = system_clock::now();
|
||||
auto startupTime = begin;
|
||||
|
||||
while (process->isActive()) {
|
||||
if (engine.isQuitSignal()) {
|
||||
process->terminate();
|
||||
logger.info() << "script has been terminated due to quit signal";
|
||||
break;
|
||||
}
|
||||
while (!engine.isQuitSignal() && !engine.getAppScripts().isFinished()) {
|
||||
if (coreParams.testMode) {
|
||||
time.step(delta);
|
||||
} else {
|
||||
|
|
@ -55,7 +47,6 @@ void ServerMainloop::run() {
|
|||
duration_cast<microseconds>(now - startupTime).count() / 1e6);
|
||||
delta = time.getDelta();
|
||||
}
|
||||
process->update();
|
||||
if (controller) {
|
||||
controller->getLevel()->getWorld()->updateTimers(delta);
|
||||
controller->update(glm::min(delta, 0.2), false);
|
||||
|
|
|
|||
|
|
@ -7,6 +7,9 @@
|
|||
#include "graphics/ui/elements/InventoryView.hpp"
|
||||
#include "graphics/ui/gui_xml.hpp"
|
||||
#include "logic/scripting/scripting.hpp"
|
||||
#include "debug/Logger.hpp"
|
||||
|
||||
static debug::Logger logger("ui-document");
|
||||
|
||||
UiDocument::UiDocument(
|
||||
std::string id,
|
||||
|
|
@ -17,6 +20,19 @@ UiDocument::UiDocument(
|
|||
rebuildIndices();
|
||||
}
|
||||
|
||||
UiDocument::~UiDocument() {
|
||||
try {
|
||||
scripting::on_ui_destroy(*this);
|
||||
} catch (const std::exception& err) {
|
||||
logger.error() << "an error occurred on calling on_destroy event for document '"
|
||||
<< id << "': " << err.what();
|
||||
} catch (...) {
|
||||
logger.error() << "unknown exception caught on calling on_destroy "
|
||||
"event for document '"
|
||||
<< id << "'";
|
||||
}
|
||||
}
|
||||
|
||||
void UiDocument::rebuildIndices() {
|
||||
map.clear();
|
||||
gui::UINode::getIndices(root, map);
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ struct UiDocScript {
|
|||
bool onopen : 1;
|
||||
bool onprogress : 1;
|
||||
bool onclose : 1;
|
||||
bool ondestroy : 1;
|
||||
};
|
||||
|
||||
using UINodesMap = std::unordered_map<std::string, std::weak_ptr<gui::UINode>>;
|
||||
|
|
@ -35,6 +36,8 @@ public:
|
|||
scriptenv env
|
||||
);
|
||||
|
||||
~UiDocument();
|
||||
|
||||
void rebuildIndices();
|
||||
void pushIndices(const std::shared_ptr<gui::UINode>& node);
|
||||
|
||||
|
|
|
|||
|
|
@ -563,7 +563,7 @@ void Hud::add(const HudElement& element, const dv::value& argsArray) {
|
|||
args.emplace_back(static_cast<integer_t>(blockPos[i]));
|
||||
}
|
||||
scripting::on_ui_open(
|
||||
element.getDocument(),
|
||||
*element.getDocument(),
|
||||
std::move(args)
|
||||
);
|
||||
}
|
||||
|
|
@ -578,7 +578,7 @@ void Hud::onRemove(const HudElement& element) {
|
|||
if (invview) {
|
||||
inventory = invview->getInventory().get();
|
||||
}
|
||||
scripting::on_ui_close(document, inventory);
|
||||
scripting::on_ui_close(*document, inventory);
|
||||
if (invview) {
|
||||
invview->unbind();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ UiDocument* menus::show(
|
|||
);
|
||||
auto document = documentPtr.get();
|
||||
engine.getAssets()->store(std::move(documentPtr), fullname);
|
||||
scripting::on_ui_open(document, std::move(args));
|
||||
scripting::on_ui_open(*document, std::move(args));
|
||||
menu->addPage(name, document->getRoot());
|
||||
menu->setPage(name);
|
||||
return document;
|
||||
|
|
@ -99,13 +99,13 @@ void menus::show_process_panel(
|
|||
|
||||
auto menu = engine.getGUI().getMenu();
|
||||
menu->reset();
|
||||
auto doc =
|
||||
auto document =
|
||||
menus::show(engine, "process", {util::wstr2str_utf8(langs::get(text))});
|
||||
std::dynamic_pointer_cast<Container>(doc->getRoot())
|
||||
std::dynamic_pointer_cast<Container>(document->getRoot())
|
||||
->listenInterval(0.01f, [=]() {
|
||||
task->update();
|
||||
|
||||
uint tasksDone = task->getWorkDone();
|
||||
scripting::on_ui_progress(doc, tasksDone, initialWork);
|
||||
scripting::on_ui_progress(*document, tasksDone, initialWork);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,11 @@ Font::Font(
|
|||
pages(std::move(pages)),
|
||||
glyphs(std::move(glyphs)),
|
||||
fontFile(std::move(fontFile)) {
|
||||
if (this->fontFile.has_value()) {
|
||||
if (auto fontPtr = this->fontFile->lock()) {
|
||||
monospace = fontPtr->isMonospace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Font::~Font() = default;
|
||||
|
|
@ -34,6 +39,10 @@ int Font::getYOffset() const {
|
|||
return yoffset;
|
||||
}
|
||||
|
||||
bool Font::isMonospace() const {
|
||||
return monospace;
|
||||
}
|
||||
|
||||
int Font::getLineHeight() const {
|
||||
return lineHeight;
|
||||
}
|
||||
|
|
@ -53,7 +62,7 @@ bool Font::isPrintableChar(uint codepoint) const {
|
|||
|
||||
int FontMetrics::calcWidth(std::wstring_view text, size_t offset, size_t length) const {
|
||||
auto font = this->font.has_value() ? this->font->lock() : nullptr;
|
||||
if (font == nullptr) {
|
||||
if (font == nullptr || font->isMonospace()) {
|
||||
return std::min(text.length() - offset, length) * _glyphInterval;
|
||||
}
|
||||
int totalWidth = 0;
|
||||
|
|
@ -153,6 +162,8 @@ static inline void draw_text(
|
|||
const FontStylesScheme* styles,
|
||||
size_t styleMapOffset
|
||||
) {
|
||||
bool is3d = std::is_same<Batch, Batch3D>();
|
||||
|
||||
static FontStylesScheme defStyles {{{}}, {0}};
|
||||
|
||||
if (styles == nullptr) {
|
||||
|
|
@ -185,13 +196,26 @@ static inline void draw_text(
|
|||
float advance = baseAdvance;
|
||||
if (auto glyph = font.getGlyph(c)) {
|
||||
yOffset = glyph->yOffset;
|
||||
advance = glyph->xAdvance;
|
||||
advance = glyph->xAdvance /
|
||||
static_cast<float>(font.getLineHeight()) * 2.0f *
|
||||
baseAdvance;
|
||||
}
|
||||
uint charpage = c >> 8;
|
||||
if (charpage == page){
|
||||
batch.texture(font.getPage(charpage));
|
||||
draw_glyph(
|
||||
batch, pos, glm::vec2(x, y - yOffset / static_cast<float>(font.getLineHeight())), c, right, up, interval, style
|
||||
batch,
|
||||
pos,
|
||||
glm::vec2(
|
||||
x,
|
||||
y - yOffset * (is3d ? -1 : 1) /
|
||||
static_cast<float>(font.getLineHeight())
|
||||
),
|
||||
c,
|
||||
right,
|
||||
up,
|
||||
interval,
|
||||
style
|
||||
);
|
||||
}
|
||||
else if (charpage > page && charpage < next){
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ public:
|
|||
);
|
||||
~Font();
|
||||
|
||||
bool isMonospace() const;
|
||||
int getLineHeight() const;
|
||||
int getYOffset() const;
|
||||
|
||||
|
|
@ -94,6 +95,7 @@ private:
|
|||
int lineHeight;
|
||||
int yoffset;
|
||||
int glyphInterval;
|
||||
bool monospace = true;
|
||||
std::vector<std::unique_ptr<Texture>> pages;
|
||||
std::vector<Glyph> glyphs;
|
||||
std::optional<std::weak_ptr<vector_fonts::FontFile>> fontFile;
|
||||
|
|
|
|||
|
|
@ -9,8 +9,8 @@ class Font;
|
|||
struct FontMetrics {
|
||||
std::optional<std::weak_ptr<Font>> font;
|
||||
|
||||
int lineHeight;
|
||||
int yoffset;
|
||||
int lineHeight = 0;
|
||||
int yoffset = 0;
|
||||
int _glyphInterval = 8;
|
||||
|
||||
~FontMetrics();
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
#include "ImageData.hpp"
|
||||
|
||||
#include "debug/Logger.hpp"
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <assert.h>
|
||||
#include <stdexcept>
|
||||
|
|
@ -7,6 +9,8 @@
|
|||
#include <cmath>
|
||||
#include <algorithm>
|
||||
|
||||
static debug::Logger logger("image-data");
|
||||
|
||||
ImageData::ImageData(ImageFormat format, uint width, uint height)
|
||||
: format(format), width(width), height(height) {
|
||||
size_t pixsize;
|
||||
|
|
@ -106,47 +110,83 @@ std::unique_ptr<ImageData> ImageData::cropped(int x, int y, int width, int heigh
|
|||
return subImage;
|
||||
}
|
||||
|
||||
static bool clip_line(int& x1, int& y1, int& x2, int& y2, int width, int height) {
|
||||
const int left = 0;
|
||||
const int right = width;
|
||||
const int bottom = 0;
|
||||
const int top = height;
|
||||
|
||||
int dx = x2 - x1;
|
||||
int dy = y2 - y1;
|
||||
|
||||
float t0 = 0.0f;
|
||||
float t1 = 1.0f;
|
||||
|
||||
auto clip = [](int p, int q, float& t0, float& t1) {
|
||||
if (p == 0) {
|
||||
return q >= 0;
|
||||
static bool clip_line(
|
||||
int& x0, int& y0, int& x1, int& y1, int width, int height
|
||||
) {
|
||||
if (width <= 0 || height <= 0) {
|
||||
return false;
|
||||
}
|
||||
float t = static_cast<float>(q) / p;
|
||||
if (p < 0) {
|
||||
if (t > t1) return false;
|
||||
if (t > t0) t0 = t;
|
||||
} else {
|
||||
if (t < t0) return false;
|
||||
if (t < t1) t1 = t;
|
||||
}
|
||||
return true;
|
||||
constexpr int INSIDE = 0;
|
||||
constexpr int LEFT = 1;
|
||||
constexpr int RIGHT = 2;
|
||||
constexpr int BOTTOM = 4;
|
||||
constexpr int TOP = 8;
|
||||
|
||||
const int xmin = 0;
|
||||
const int ymin = 0;
|
||||
const int xmax = width - 1;
|
||||
const int ymax = height - 1;
|
||||
|
||||
auto outcode = [&](int x, int y) {
|
||||
int code = INSIDE;
|
||||
|
||||
if (x < xmin)
|
||||
code |= LEFT;
|
||||
else if (x > xmax)
|
||||
code |= RIGHT;
|
||||
|
||||
if (y < ymin)
|
||||
code |= BOTTOM;
|
||||
else if (y > ymax)
|
||||
code |= TOP;
|
||||
|
||||
return code;
|
||||
};
|
||||
|
||||
if (!clip(-dx, x1 - left, t0, t1)) return false;
|
||||
if (!clip( dx, right - x1, t0, t1)) return false;
|
||||
if (!clip(-dy, y1 - bottom, t0, t1)) return false;
|
||||
if (!clip( dy, top - y1, t0, t1)) return false;
|
||||
int c0 = outcode(x0, y0);
|
||||
int c1 = outcode(x1, y1);
|
||||
|
||||
if (t1 < 1.0f) {
|
||||
x2 = x1 + static_cast<int>(std::round(t1 * dx));
|
||||
y2 = y1 + static_cast<int>(std::round(t1 * dy));
|
||||
}
|
||||
if (t0 > 0.0f) {
|
||||
x1 = x1 + static_cast<int>(std::round(t0 * dx));
|
||||
y1 = y1 + static_cast<int>(std::round(t0 * dy));
|
||||
}
|
||||
while (true) {
|
||||
if (!(c0 | c1)) {
|
||||
return true;
|
||||
}
|
||||
if (c0 & c1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const int out = c0 ? c0 : c1;
|
||||
|
||||
double x = 0.0;
|
||||
double y = 0.0;
|
||||
|
||||
if (out & TOP) {
|
||||
y = ymax;
|
||||
x = x0 + (x1 - x0) * static_cast<double>(ymax - y0) / (y1 - y0);
|
||||
} else if (out & BOTTOM) {
|
||||
y = ymin;
|
||||
x = x0 + (x1 - x0) * static_cast<double>(ymin - y0) / (y1 - y0);
|
||||
} else if (out & RIGHT) {
|
||||
x = xmax;
|
||||
y = y0 + (y1 - y0) * static_cast<double>(xmax - x0) / (x1 - x0);
|
||||
} else { // LEFT
|
||||
x = xmin;
|
||||
y = y0 + (y1 - y0) * static_cast<double>(xmin - x0) / (x1 - x0);
|
||||
}
|
||||
|
||||
if (out == c0) {
|
||||
x0 = std::clamp(static_cast<int>(std::lround(x)), xmin, xmax);
|
||||
y0 = std::clamp(static_cast<int>(std::lround(y)), ymin, ymax);
|
||||
c0 = outcode(x0, y0);
|
||||
} else {
|
||||
x1 = std::clamp(static_cast<int>(std::lround(x)), xmin, xmax);
|
||||
y1 = std::clamp(static_cast<int>(std::lround(y)), ymin, ymax);
|
||||
c1 = outcode(x1, y1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool is_point_outside(int x, int y, int width, int height) {
|
||||
return x < 0 || y < 0 || x >= width || y >= height;
|
||||
}
|
||||
|
||||
template<uint channels>
|
||||
|
|
@ -155,11 +195,24 @@ static void draw_line(ImageData& image, int x1, int y1, int x2, int y2, const gl
|
|||
uint width = image.getWidth();
|
||||
uint height = image.getHeight();
|
||||
|
||||
glm::ivec4 init {x1, y1, x2, y2};
|
||||
|
||||
if ((x1 < 0 || x1 >= width || x2 < 0 || x2 >= width ||
|
||||
y1 < 0 || y1 >= height || y2 < 0 || y2 >= height) &&
|
||||
!clip_line(x1, y1, x2, y2, width, height)) {
|
||||
return;
|
||||
}
|
||||
// spam info for bug report
|
||||
if (is_point_outside(x1, y1, width, height) || is_point_outside(x2, y2, width, height)) {
|
||||
logger.warning() << "clip_line fault: [" << init.x << ", " << init.y
|
||||
<< "] - [" << init.z << ", " << init.w << "] --> ["
|
||||
<< x1 << ", " << y1 << "] - [" << x2 << ", " << y2
|
||||
<< "]";
|
||||
x1 = glm::clamp(x1, 0, static_cast<int>(width) - 1);
|
||||
y1 = glm::clamp(y1, 0, static_cast<int>(height) - 1);
|
||||
x2 = glm::clamp(x2, 0, static_cast<int>(width) - 1);
|
||||
y2 = glm::clamp(y2, 0, static_cast<int>(height) - 1);
|
||||
}
|
||||
|
||||
int dx = std::abs(x2 - x1);
|
||||
int dy = -std::abs(y2 - y1);
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@
|
|||
#include "voxels/Block.hpp"
|
||||
#include "voxels/Chunk.hpp"
|
||||
#include "voxels/Chunks.hpp"
|
||||
#include "voxels/blocks_agent.hpp"
|
||||
#include "window/Camera.hpp"
|
||||
#include "world/Level.hpp"
|
||||
#include "WorldRenderer.hpp"
|
||||
|
|
@ -98,7 +99,7 @@ void Decorator::updateAcoustics(const Camera& camera) {
|
|||
|
||||
auto& chunks = *player.chunks;
|
||||
const auto& start = camera.position;
|
||||
float rayLength = 40.0f;
|
||||
float length = 40.0f;
|
||||
|
||||
auto& blocks = level.content.getIndices()->blocks;
|
||||
|
||||
|
|
@ -107,7 +108,7 @@ void Decorator::updateAcoustics(const Camera& camera) {
|
|||
float averageDistance = 0.0f;
|
||||
float averageAbsorption = 0.0f;
|
||||
float speedOfSound = 300.0f;
|
||||
float minDistance = rayLength;
|
||||
float minDistance = length;
|
||||
for (int i = 0; i < rays; i++) {
|
||||
float u1 = random.randFloat();
|
||||
float u2 = random.randFloat();
|
||||
|
|
@ -121,12 +122,13 @@ void Decorator::updateAcoustics(const Camera& camera) {
|
|||
glm::vec3 end;
|
||||
glm::ivec3 norm;
|
||||
glm::ivec3 iend;
|
||||
auto vox = chunks.rayCast(start, dir, rayLength, end, norm, iend);
|
||||
blocks_agent::RaycastSettings raycast {};
|
||||
auto vox = chunks.rayCast(start, dir, length, end, norm, iend, raycast);
|
||||
if (vox == nullptr) {
|
||||
continue;
|
||||
}
|
||||
auto distance = glm::distance(start, end);
|
||||
if (distance >= rayLength * 0.98f) {
|
||||
if (distance >= length * 0.98f) {
|
||||
continue;
|
||||
}
|
||||
if (distance < minDistance) {
|
||||
|
|
@ -149,7 +151,7 @@ void Decorator::updateAcoustics(const Camera& camera) {
|
|||
averageAbsorption /= hit;
|
||||
}
|
||||
|
||||
float decayTime = 20.0f * (averageDistance / rayLength);
|
||||
float decayTime = 20.0f * (averageDistance / length);
|
||||
float escapeRatio = static_cast<float>(rays - hit) / static_cast<float>(rays);
|
||||
|
||||
acoustics.reverbGain = glm::pow(hit / static_cast<float>(rays), 3.0f);
|
||||
|
|
|
|||
|
|
@ -58,7 +58,10 @@ void TextsRenderer::renderNote(
|
|||
}
|
||||
opacity = preset.xrayOpacity;
|
||||
}
|
||||
auto& font = assets.require<Font>(FONT_DEFAULT);
|
||||
|
||||
auto specifiedFont = assets.get<Font>(preset.font);
|
||||
auto& font =
|
||||
specifiedFont ? *specifiedFont : assets.require<Font>(FONT_DEFAULT);
|
||||
|
||||
glm::vec3 xvec = note.getAxisX();
|
||||
glm::vec3 yvec = note.getAxisY();
|
||||
|
|
@ -76,7 +79,10 @@ void TextsRenderer::renderNote(
|
|||
yvec = camera.up;
|
||||
}
|
||||
float scale =
|
||||
(1.0f - preset.perspective) * glm::pow(glm::distance(camera.position, pos), 1.0f-preset.perspective);
|
||||
(1.0f - preset.perspective) *
|
||||
glm::pow(
|
||||
glm::distance(camera.position, pos), 1.0f - preset.perspective
|
||||
);
|
||||
xvec *= 1.0f + scale;
|
||||
yvec *= 1.0f + scale;
|
||||
}
|
||||
|
|
@ -118,6 +124,7 @@ void TextsRenderer::renderNote(
|
|||
}
|
||||
auto color = preset.color;
|
||||
batch.setColor(glm::vec4(color.r, color.g, color.b, color.a * opacity));
|
||||
|
||||
font.draw(
|
||||
batch,
|
||||
text,
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ InlineFrame::~InlineFrame() = default;
|
|||
void InlineFrame::setSrc(const std::string& src) {
|
||||
this->src = src;
|
||||
if (document) {
|
||||
scripting::on_ui_close(document.get(), nullptr);
|
||||
scripting::on_ui_close(*document, nullptr);
|
||||
document = nullptr;
|
||||
root = nullptr;
|
||||
}
|
||||
|
|
@ -31,7 +31,7 @@ void InlineFrame::setDocument(const std::shared_ptr<UiDocument>& document) {
|
|||
root->setSize(size);
|
||||
|
||||
gui.postRunnable([this]() {
|
||||
scripting::on_ui_open(this->document.get(), {});
|
||||
scripting::on_ui_open(*this->document, {});
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -207,6 +207,9 @@ uint Label::getLinesNumber() const {
|
|||
void Label::draw(const DrawContext& pctx, const Assets& assets) {
|
||||
auto batch = pctx.getBatch2D();
|
||||
auto font = assets.getShared<Font>(fontName);
|
||||
if (font == nullptr) {
|
||||
return;
|
||||
}
|
||||
cache.prepare(
|
||||
font,
|
||||
font->getMetrics(),
|
||||
|
|
|
|||
|
|
@ -353,7 +353,9 @@ void TextBox::draw(const DrawContext& pctx, const Assets& assets) {
|
|||
|
||||
void TextBox::drawBackground(const DrawContext& pctx, const Assets& assets) {
|
||||
auto font = assets.getShared<Font>(label->getFontName());
|
||||
if (font != nullptr) {
|
||||
rawTextCache.prepare(font, font->getMetrics(), label->getSize().x);
|
||||
}
|
||||
|
||||
glm::vec2 pos = calcPos();
|
||||
|
||||
|
|
|
|||
|
|
@ -173,8 +173,12 @@ bool io::write_binary_json(
|
|||
}
|
||||
|
||||
dv::value io::read_json(const path& filename) {
|
||||
std::string text = io::read_string(filename);
|
||||
auto text = io::read_string(filename);
|
||||
try {
|
||||
return json::parse(filename.string(), text);
|
||||
} catch (const parsing_error& err) {
|
||||
throw err.toRuntimeError();
|
||||
}
|
||||
}
|
||||
|
||||
dv::value io::read_binary_json(const path& file) {
|
||||
|
|
@ -184,7 +188,12 @@ dv::value io::read_binary_json(const path& file) {
|
|||
}
|
||||
|
||||
dv::value io::read_toml(const path& file) {
|
||||
return toml::parse(file.string(), io::read_string(file));
|
||||
auto text = io::read_string(file);
|
||||
try {
|
||||
return toml::parse(file.string(), text);
|
||||
} catch (const parsing_error& err) {
|
||||
throw err.toRuntimeError();
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> io::read_list(const io::path& filename) {
|
||||
|
|
|
|||
|
|
@ -15,8 +15,33 @@ public:
|
|||
memory_streambuf(const memory_streambuf&) = delete;
|
||||
memory_streambuf& operator=(const memory_streambuf&) = delete;
|
||||
|
||||
pos_type seekoff(off_type off, std::ios_base::seekdir way,
|
||||
std::ios_base::openmode mode = std::ios_base::in) override {
|
||||
if (!(mode & std::ios_base::in)) return pos_type(off_type(-1));
|
||||
char* base = eback();
|
||||
char* end = egptr();
|
||||
if (!base || !end) return pos_type(off_type(-1));
|
||||
|
||||
std::ptrdiff_t current = gptr() - base;
|
||||
std::ptrdiff_t newpos;
|
||||
if (way == std::ios_base::beg) newpos = off;
|
||||
else if (way == std::ios_base::cur) newpos = current + off;
|
||||
else if (way == std::ios_base::end) newpos = (end - base) + off;
|
||||
else return pos_type(off_type(-1));
|
||||
|
||||
if (newpos < 0 || base + newpos > end) return pos_type(off_type(-1));
|
||||
setg(base, base + newpos, end);
|
||||
return pos_type(off_type(newpos));
|
||||
}
|
||||
|
||||
pos_type seekpos(pos_type sp, std::ios_base::openmode mode = std::ios_base::in) override {
|
||||
return seekoff(off_type(sp), std::ios_base::beg, mode);
|
||||
}
|
||||
protected:
|
||||
int_type underflow() override {
|
||||
if (gptr() < egptr()) {
|
||||
return traits_type::to_int_type(*gptr());
|
||||
}
|
||||
return traits_type::eof();
|
||||
}
|
||||
|
||||
|
|
@ -36,7 +61,7 @@ private:
|
|||
class memory_view_streambuf : public std::streambuf {
|
||||
public:
|
||||
explicit memory_view_streambuf(const util::Buffer<char>& buffer)
|
||||
: buffer(std::move(buffer)) {
|
||||
: buffer(buffer) {
|
||||
char* base = const_cast<char*>(this->buffer.data());
|
||||
char* end = base + this->buffer.size();
|
||||
setg(base, base, end);
|
||||
|
|
@ -45,8 +70,33 @@ public:
|
|||
memory_view_streambuf(const memory_view_streambuf&) = delete;
|
||||
memory_view_streambuf& operator=(const memory_view_streambuf&) = delete;
|
||||
|
||||
pos_type seekoff(off_type off, std::ios_base::seekdir way,
|
||||
std::ios_base::openmode mode = std::ios_base::in) override {
|
||||
if (!(mode & std::ios_base::in)) return pos_type(off_type(-1));
|
||||
char* base = eback();
|
||||
char* end = egptr();
|
||||
if (!base || !end) return pos_type(off_type(-1));
|
||||
|
||||
std::ptrdiff_t current = gptr() - base;
|
||||
std::ptrdiff_t newpos;
|
||||
if (way == std::ios_base::beg) newpos = off;
|
||||
else if (way == std::ios_base::cur) newpos = current + off;
|
||||
else if (way == std::ios_base::end) newpos = (end - base) + off;
|
||||
else return pos_type(off_type(-1));
|
||||
|
||||
if (newpos < 0 || base + newpos > end) return pos_type(off_type(-1));
|
||||
setg(base, base + newpos, end);
|
||||
return pos_type(off_type(newpos));
|
||||
}
|
||||
|
||||
pos_type seekpos(pos_type sp, std::ios_base::openmode mode = std::ios_base::in) override {
|
||||
return seekoff(off_type(sp), std::ios_base::beg, mode);
|
||||
}
|
||||
protected:
|
||||
int_type underflow() override {
|
||||
if (gptr() < egptr()) {
|
||||
return traits_type::to_int_type(*gptr());
|
||||
}
|
||||
return traits_type::eof();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -55,6 +55,16 @@ public:
|
|||
return fields;
|
||||
}
|
||||
|
||||
void setFields(dv::value&& table, bool clear) {
|
||||
if (clear) {
|
||||
fields = std::move(table);
|
||||
return;
|
||||
}
|
||||
for (auto&& [key, value] : table.asObject()) {
|
||||
fields[std::move(key)] = std::move(value);
|
||||
}
|
||||
}
|
||||
|
||||
bool hasFields() const {
|
||||
return fields != nullptr;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -82,7 +82,6 @@ void LevelController::update(float delta, bool pause) {
|
|||
continue;
|
||||
}
|
||||
player->rotationInterpolation.updateTimer(delta);
|
||||
player->updateEntity();
|
||||
glm::vec3 position = player->getPosition();
|
||||
player->chunks->configure(
|
||||
glm::floor(position.x),
|
||||
|
|
@ -96,9 +95,9 @@ void LevelController::update(float delta, bool pause) {
|
|||
*player,
|
||||
player.get() == clientPlayer
|
||||
);
|
||||
player->updateEntity();
|
||||
}
|
||||
if (!pause) {
|
||||
// update all objects that needed
|
||||
blocks->update(delta, settings.chunks.padding.get());
|
||||
level->entities->update(delta);
|
||||
for (const auto& [_, player] : *level->players) {
|
||||
|
|
|
|||
|
|
@ -200,8 +200,13 @@ void CameraControl::update(
|
|||
auto castRay = [](Player& player, Camera& camera, const glm::vec3& front)
|
||||
-> glm::vec3 {
|
||||
auto blockEnd = player.chunks->rayCastToObstacle(camera.position, front, 3.0f);
|
||||
|
||||
Entities::RaycastSettings raycast {};
|
||||
raycast.ignoredUid = player.getEntity();
|
||||
raycast.solidEntitiesOnly = true;
|
||||
|
||||
auto entityRay = player.getLevel().entities->rayCast(
|
||||
camera.position, front, 3.0f, player.getEntity(), true
|
||||
camera.position, front, 3.0f, raycast
|
||||
);
|
||||
if (entityRay.has_value() &&
|
||||
entityRay->distance < glm::distance(camera.position, blockEnd)) {
|
||||
|
|
@ -350,7 +355,13 @@ voxel* PlayerController::updateSelection(float maxDistance) {
|
|||
glm::ivec3 iend;
|
||||
glm::ivec3 norm;
|
||||
voxel* vox = chunks.rayCast(
|
||||
camera->position, camera->front, maxDistance, end, norm, iend
|
||||
camera->position,
|
||||
camera->front,
|
||||
maxDistance,
|
||||
end,
|
||||
norm,
|
||||
iend,
|
||||
blocks_agent::RaycastSettings {}
|
||||
);
|
||||
if (vox) {
|
||||
maxDistance = glm::distance(camera->position, end);
|
||||
|
|
@ -358,8 +369,12 @@ voxel* PlayerController::updateSelection(float maxDistance) {
|
|||
auto prevEntity = selection.entity;
|
||||
selection.entity = ENTITY_NONE;
|
||||
selection.actualPosition = iend;
|
||||
|
||||
Entities::RaycastSettings raycast {};
|
||||
raycast.ignoredUid = player.getEntity();
|
||||
|
||||
if (auto result = level.entities->rayCast(
|
||||
camera->position, camera->front, maxDistance, player.getEntity()
|
||||
camera->position, camera->front, maxDistance, raycast
|
||||
)) {
|
||||
selection.entity = result->entity;
|
||||
selection.hitPosition =
|
||||
|
|
|
|||
|
|
@ -1,104 +0,0 @@
|
|||
#include "logic/scripting/descriptors_manager.hpp"
|
||||
|
||||
#include "debug/Logger.hpp"
|
||||
|
||||
static debug::Logger logger("descriptors-manager");
|
||||
|
||||
namespace scripting {
|
||||
|
||||
std::vector<std::optional<StreamDescriptor>> descriptors_manager::descriptors;
|
||||
|
||||
std::istream* descriptors_manager::get_input(int descriptor) {
|
||||
if (!is_readable(descriptor))
|
||||
return nullptr;
|
||||
|
||||
return descriptors[descriptor]->in.get();
|
||||
}
|
||||
|
||||
std::ostream* descriptors_manager::get_output(int descriptor) {
|
||||
if (!is_writeable(descriptor))
|
||||
return nullptr;
|
||||
|
||||
return descriptors[descriptor]->out.get();
|
||||
}
|
||||
|
||||
void descriptors_manager::flush(int descriptor) {
|
||||
if (is_writeable(descriptor)) {
|
||||
descriptors[descriptor]->out->flush();
|
||||
}
|
||||
}
|
||||
|
||||
bool descriptors_manager::has_descriptor(int descriptor) {
|
||||
return is_readable(descriptor) || is_writeable(descriptor);
|
||||
}
|
||||
|
||||
bool descriptors_manager::is_readable(int descriptor) {
|
||||
return descriptor >= 0 && descriptor < static_cast<int>(descriptors.size())
|
||||
&& descriptors[descriptor].has_value()
|
||||
&& descriptors[descriptor]->in != nullptr;
|
||||
}
|
||||
|
||||
bool descriptors_manager::is_writeable(int descriptor) {
|
||||
return descriptor >= 0 && descriptor < static_cast<int>(descriptors.size())
|
||||
&& descriptors[descriptor].has_value()
|
||||
&& descriptors[descriptor]->out != nullptr;
|
||||
}
|
||||
|
||||
void descriptors_manager::close(int descriptor) {
|
||||
if (descriptor >= 0 && descriptor < static_cast<int>(descriptors.size())) {
|
||||
if (descriptors[descriptor].has_value()) {
|
||||
auto& desc = descriptors[descriptor].value();
|
||||
|
||||
if (desc.out)
|
||||
desc.out->flush();
|
||||
|
||||
desc.in.reset();
|
||||
desc.out.reset();
|
||||
}
|
||||
|
||||
descriptors[descriptor].reset();
|
||||
|
||||
descriptors[descriptor] = std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
int descriptors_manager::open_descriptor(const io::path& path, bool write, bool read) {
|
||||
std::unique_ptr<std::istream> in;
|
||||
std::unique_ptr<std::ostream> out;
|
||||
|
||||
try {
|
||||
if (read)
|
||||
in = io::read(path);
|
||||
|
||||
if (write)
|
||||
out = io::write(path);
|
||||
} catch (const std::exception& e) {
|
||||
logger.error() << "failed to open descriptor for " << path.string()
|
||||
<< ": " << e.what();
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (int i = 0; i < static_cast<int>(descriptors.size()); ++i) {
|
||||
if (!descriptors[i].has_value()) {
|
||||
descriptors[i] = StreamDescriptor{ std::move(in), std::move(out) };
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
descriptors.emplace_back(StreamDescriptor{ std::move(in), std::move(out) });
|
||||
|
||||
return static_cast<int>(descriptors.size() - 1);
|
||||
}
|
||||
|
||||
|
||||
void descriptors_manager::close_all_descriptors() {
|
||||
for (int i = 0; i < static_cast<int>(descriptors.size()); ++i) {
|
||||
if (descriptors[i].has_value()) {
|
||||
close(i);
|
||||
}
|
||||
}
|
||||
|
||||
descriptors.clear();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <istream>
|
||||
#include <ostream>
|
||||
|
||||
#include "io/io.hpp"
|
||||
|
||||
namespace scripting {
|
||||
|
||||
struct StreamDescriptor {
|
||||
std::unique_ptr<std::istream> in;
|
||||
std::unique_ptr<std::ostream> out;
|
||||
};
|
||||
|
||||
class descriptors_manager {
|
||||
private:
|
||||
static std::vector<std::optional<StreamDescriptor>> descriptors;
|
||||
|
||||
public:
|
||||
static std::istream* get_input(int descriptor);
|
||||
static std::ostream* get_output(int descriptor);
|
||||
|
||||
static void flush(int descriptor);
|
||||
|
||||
static bool has_descriptor(int descriptor);
|
||||
|
||||
static bool is_readable(int descriptor);
|
||||
static bool is_writeable(int descriptor);
|
||||
|
||||
static void close(int descriptor);
|
||||
static int open_descriptor(const io::path& path, bool write, bool read);
|
||||
|
||||
static void close_all_descriptors();
|
||||
};
|
||||
}
|
||||
160
src/logic/scripting/io_descriptors.cpp
Normal file
160
src/logic/scripting/io_descriptors.cpp
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
#include "logic/scripting/io_descriptors.hpp"
|
||||
|
||||
#include "debug/Logger.hpp"
|
||||
#include "io/io.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <optional>
|
||||
|
||||
static debug::Logger logger("descriptors-manager");
|
||||
|
||||
using namespace scripting;
|
||||
|
||||
namespace {
|
||||
struct StreamDescriptor {
|
||||
// TODO: std::iostream?
|
||||
std::unique_ptr<std::istream> in;
|
||||
std::unique_ptr<std::ostream> out;
|
||||
};
|
||||
std::vector<std::optional<StreamDescriptor>> descriptors;
|
||||
}
|
||||
|
||||
std::istream* io_descriptors::get_input(int id) {
|
||||
if (!is_readable(id)) {
|
||||
return nullptr;
|
||||
}
|
||||
return ::descriptors[id]->in.get();
|
||||
}
|
||||
|
||||
std::ostream* io_descriptors::get_output(int id) {
|
||||
if (!is_writeable(id)) {
|
||||
return nullptr;
|
||||
}
|
||||
return ::descriptors[id]->out.get();
|
||||
}
|
||||
|
||||
static StreamDescriptor& require_descriptor(int id) {
|
||||
if (!io_descriptors::has_descriptor(id)) {
|
||||
throw std::runtime_error(
|
||||
"io-descriptor with id " + std::to_string(id) + " does not exists"
|
||||
);
|
||||
}
|
||||
return *::descriptors[id];
|
||||
}
|
||||
|
||||
std::istream& io_descriptors::require_input(int id) {
|
||||
const auto& descriptor = require_descriptor(id);
|
||||
if (descriptor.in) {
|
||||
return *descriptor.in;
|
||||
}
|
||||
throw std::runtime_error("io-descriptor is not readable");
|
||||
}
|
||||
|
||||
std::ostream& io_descriptors::require_output(int id) {
|
||||
const auto& descriptor = require_descriptor(id);
|
||||
if (descriptor.out) {
|
||||
return *descriptor.out;
|
||||
}
|
||||
throw std::runtime_error("io-descriptor is not writeable");
|
||||
}
|
||||
|
||||
void io_descriptors::flush(int id) {
|
||||
if (is_writeable(id)) {
|
||||
::descriptors[id]->out->flush();
|
||||
}
|
||||
}
|
||||
|
||||
int io_descriptors::available(int id) {
|
||||
if (!is_readable(id))
|
||||
return 0;
|
||||
|
||||
auto* stream = descriptors[id]->in.get();
|
||||
|
||||
auto current_pos = stream->tellg();
|
||||
|
||||
if (current_pos == -1) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
stream->seekg(0, std::ios::end);
|
||||
|
||||
auto end_pos = stream->tellg();
|
||||
|
||||
stream->seekg(current_pos, std::ios::beg);
|
||||
|
||||
auto remaining = end_pos - current_pos;
|
||||
|
||||
if (remaining > std::numeric_limits<int>::max()) {
|
||||
return std::numeric_limits<int>::max();
|
||||
}
|
||||
|
||||
return static_cast<int>(remaining);
|
||||
}
|
||||
|
||||
bool io_descriptors::has_descriptor(int id) {
|
||||
return id >= 0 && id < static_cast<int>(::descriptors.size()) &&
|
||||
::descriptors[id].has_value();
|
||||
}
|
||||
|
||||
bool io_descriptors::is_readable(int id) {
|
||||
return has_descriptor(id) && ::descriptors[id]->in != nullptr;
|
||||
}
|
||||
|
||||
bool io_descriptors::is_writeable(int id) {
|
||||
return id >= 0 && id < static_cast<int>(::descriptors.size())
|
||||
&& ::descriptors[id].has_value()
|
||||
&& ::descriptors[id]->out != nullptr;
|
||||
}
|
||||
|
||||
void io_descriptors::close(int id) {
|
||||
if (!has_descriptor(id)) {
|
||||
return;
|
||||
}
|
||||
auto& desc = ::descriptors[id].value();
|
||||
if (desc.out) {
|
||||
desc.out->flush();
|
||||
}
|
||||
desc.in.reset();
|
||||
desc.out.reset();
|
||||
::descriptors[id] = std::nullopt;
|
||||
}
|
||||
|
||||
int io_descriptors::open_descriptor(const io::path& path, bool write, bool read) {
|
||||
std::unique_ptr<std::istream> in;
|
||||
std::unique_ptr<std::ostream> out;
|
||||
|
||||
try {
|
||||
if (read) {
|
||||
in = io::read(path);
|
||||
}
|
||||
if (write) {
|
||||
out = io::write(path);
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
logger.error() << "failed to open descriptor for " << path.string()
|
||||
<< ": " << e.what();
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (int i = 0; i < static_cast<int>(descriptors.size()); ++i) {
|
||||
if (!descriptors[i].has_value()) {
|
||||
descriptors[i] = StreamDescriptor{ std::move(in), std::move(out) };
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
::descriptors.emplace_back(StreamDescriptor{ std::move(in), std::move(out) });
|
||||
return static_cast<int>(::descriptors.size() - 1);
|
||||
}
|
||||
|
||||
|
||||
void io_descriptors::close_all_descriptors() {
|
||||
for (int i = 0; i < static_cast<int>(::descriptors.size()); ++i) {
|
||||
if (::descriptors[i].has_value()) {
|
||||
close(i);
|
||||
}
|
||||
}
|
||||
::descriptors.clear();
|
||||
}
|
||||
27
src/logic/scripting/io_descriptors.hpp
Normal file
27
src/logic/scripting/io_descriptors.hpp
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
#pragma once
|
||||
|
||||
#include <istream>
|
||||
#include <ostream>
|
||||
|
||||
#include "io/fwd.hpp"
|
||||
|
||||
namespace scripting::io_descriptors {
|
||||
std::istream* get_input(int id);
|
||||
std::ostream* get_output(int id);
|
||||
|
||||
std::istream& require_input(int id);
|
||||
std::ostream& require_output(int id);
|
||||
|
||||
void flush(int id);
|
||||
int available(int id);
|
||||
|
||||
bool has_descriptor(int id);
|
||||
|
||||
bool is_readable(int id);
|
||||
bool is_writeable(int id);
|
||||
|
||||
void close(int id);
|
||||
int open_descriptor(const io::path& path, bool write, bool read);
|
||||
|
||||
void close_all_descriptors();
|
||||
}
|
||||
|
|
@ -44,6 +44,7 @@ extern const luaL_Reg posteffectslib[]; // gfx.posteffects
|
|||
extern const luaL_Reg quatlib[];
|
||||
extern const luaL_Reg randomlib[];
|
||||
extern const luaL_Reg compressionlib[];
|
||||
extern const luaL_Reg testlib[];
|
||||
extern const luaL_Reg text3dlib[]; // gfx.text3d
|
||||
extern const luaL_Reg timelib[];
|
||||
extern const luaL_Reg tomllib[];
|
||||
|
|
@ -53,6 +54,7 @@ extern const luaL_Reg vec3lib[]; // vecn.cpp
|
|||
extern const luaL_Reg vec4lib[]; // vecn.cpp
|
||||
extern const luaL_Reg weatherlib[]; // gfx.weather
|
||||
extern const luaL_Reg worldlib[];
|
||||
extern const luaL_Reg xmllib[];
|
||||
extern const luaL_Reg yamllib[];
|
||||
|
||||
// Components
|
||||
|
|
|
|||
|
|
@ -543,13 +543,16 @@ static int l_destruct(lua::State* L) {
|
|||
}
|
||||
|
||||
static int l_raycast(lua::State* L) {
|
||||
auto& level = require_level();
|
||||
const auto& level = require_level();
|
||||
|
||||
auto start = lua::tovec<3>(L, 1);
|
||||
auto dir = lua::tovec<3>(L, 2);
|
||||
auto maxDistance = lua::tonumber(L, 3);
|
||||
bool includeNonSelectable = false;
|
||||
|
||||
blocks_agent::RaycastSettings raycast {};
|
||||
std::set<blockid_t> filteredBlocks {};
|
||||
raycast.filter = &filteredBlocks;
|
||||
|
||||
const int luaStackSize = lua::gettop(L);
|
||||
if (luaStackSize >= 5) {
|
||||
if (lua::istable(L, 5)) {
|
||||
|
|
@ -568,21 +571,14 @@ static int l_raycast(lua::State* L) {
|
|||
}
|
||||
}
|
||||
if (luaStackSize >= 6) {
|
||||
includeNonSelectable = lua::toboolean(L, 6);
|
||||
raycast.includeNonSelectable = lua::toboolean(L, 6);
|
||||
}
|
||||
glm::vec3 end;
|
||||
glm::ivec3 normal;
|
||||
glm::ivec3 iend;
|
||||
|
||||
if (auto voxel = blocks_agent::raycast(
|
||||
*level.chunks,
|
||||
start,
|
||||
dir,
|
||||
maxDistance,
|
||||
end,
|
||||
normal,
|
||||
iend,
|
||||
filteredBlocks,
|
||||
includeNonSelectable
|
||||
*level.chunks, start, dir, maxDistance, end, normal, iend, raycast
|
||||
)) {
|
||||
if (luaStackSize >= 4 && !lua::isnil(L, 4)) {
|
||||
lua::pushvalue(L, 4);
|
||||
|
|
|
|||
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