mirror of
https://github.com/MihailRis/voxelcore.git
synced 2026-10-11 05:51:51 +00:00
Merge branch 'MihailRis:main' into main
This commit is contained in:
commit
7536ac2736
130 changed files with 2932 additions and 1028 deletions
34
dev/tests/io_stream.lua
Normal file
34
dev/tests/io_stream.lua
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
local device = file.create_memory_device()
|
||||||
|
file.write(device..":test.txt", "Hello\nWorld")
|
||||||
|
file.write_bytes(device..":test.bin", Bytearray({20, 30, 100, 200, 255}))
|
||||||
|
|
||||||
|
local stream = file.open(device..":test.txt", 'r')
|
||||||
|
|
||||||
|
local line1 = stream:read_line()
|
||||||
|
local line2 = stream:read_line()
|
||||||
|
|
||||||
|
assert(line1 == "Hello")
|
||||||
|
assert(line2 == "World")
|
||||||
|
|
||||||
|
local stream2 = file.open(device..":test.bin", 'rb')
|
||||||
|
local data = stream2:read(5)
|
||||||
|
assert(data[1] == 20)
|
||||||
|
assert(data[2] == 30)
|
||||||
|
assert(data[3] == 100)
|
||||||
|
assert(data[4] == 200)
|
||||||
|
assert(data[5] == 255)
|
||||||
|
|
||||||
|
local stream3 = file.open(device..":test.txt", 'r')
|
||||||
|
assert(stream3:is_alive())
|
||||||
|
stream3:close()
|
||||||
|
assert(not stream3:is_alive())
|
||||||
|
|
||||||
|
local stream4 = file.open(device..":test.txt", 'r')
|
||||||
|
stream4:seek('b', 6)
|
||||||
|
local line = stream4:read_line()
|
||||||
|
assert(line == "World")
|
||||||
|
|
||||||
|
local stream5 = file.open(device..":test.txt", 'r')
|
||||||
|
stream5:seek('e', 0)
|
||||||
|
local pos = stream5:tell()
|
||||||
|
assert(pos == 11)
|
||||||
|
|
@ -4,14 +4,15 @@
|
||||||
|
|
||||||
The appearance of 3D text, is configured via a table, like [particles](particles.md). All fields are optional.
|
The appearance of 3D text, is configured via a table, like [particles](particles.md). All fields are optional.
|
||||||
|
|
||||||
| Field | Description | Default |
|
| Field | Description | Default |
|
||||||
| --------------- | ---------------------------- | ---------------- |
|
| --------------- | ---------------------------------------- | ---------------- |
|
||||||
| display | Display format | static_billboard |
|
| display | Display format | static_billboard |
|
||||||
| color | Text color | {1, 1, 1, 1} |
|
| color | Text color | {1, 1, 1, 1} |
|
||||||
| scale | Text scale | 1 |
|
| scale | Text scale | 1 |
|
||||||
| render_distance | Text rendering distance | 32 |
|
| render_distance | Text rendering distance | 32 |
|
||||||
| xray_opacity | Visibility through obstacles | 0 |
|
| xray_opacity | Visibility through obstacles | 0 |
|
||||||
| perspective | Perspective coefficient | 1 |
|
| perspective | Perspective coefficient | 1 |
|
||||||
|
| font | Font. If absent, the default one is used | "" |
|
||||||
|
|
||||||
Available display formats:
|
Available display formats:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
# Block properties
|
# Block properties
|
||||||
|
|
||||||
## Visual
|
## Visual & Audio
|
||||||
|
|
||||||
### *texture*
|
### *texture*
|
||||||
|
|
||||||
|
|
@ -70,6 +70,18 @@ Rotation profile (set of available block rotations and behaviour of placing bloc
|
||||||
- "pane" - panels, doors, signs
|
- "pane" - panels, doors, signs
|
||||||
- "stairs" - "pane" + flipped variants
|
- "stairs" - "pane" + flipped variants
|
||||||
|
|
||||||
|
### *particles*
|
||||||
|
|
||||||
|
Particles are specified as a JSON object. Property names can be found [in the particles section](particles.md).
|
||||||
|
|
||||||
|
When camera is near to the block, the engine will create an emitter that will run
|
||||||
|
until the block is destroyed or the camera moves away a certain distance.
|
||||||
|
|
||||||
|
### Material - *material*
|
||||||
|
|
||||||
|
Defines the name of the block's material in the format `pack:material_name`, which affects the selection of block interaction sounds.
|
||||||
|
Material definitions are located in /block_materials.
|
||||||
|
|
||||||
## Variants
|
## Variants
|
||||||
|
|
||||||
Some properties can vary dynamically, depending on the variant number stored in the user bits of the block.
|
Some properties can vary dynamically, depending on the variant number stored in the user bits of the block.
|
||||||
|
|
|
||||||
|
|
@ -61,12 +61,16 @@ Determines how the physics engine will work with it.
|
||||||
|
|
||||||
### *blocking*
|
### *blocking*
|
||||||
|
|
||||||
Determines whether the entity blocks installation of blocks.
|
If true - the entity blocks the placement of a block that intersects with the entity's hitbox.
|
||||||
|
|
||||||
*In the future will also block other entities movement.*
|
*In the future will also block other entities movement.*
|
||||||
|
|
||||||
Default value: *true*.
|
Default value: *true*.
|
||||||
|
|
||||||
|
### *selectable*
|
||||||
|
|
||||||
|
If set to `false` the cursor will ignore the entity, passing the ray.
|
||||||
|
|
||||||
### *sensors*
|
### *sensors*
|
||||||
|
|
||||||
A sensor is an area attached to a physical body that detects the entry of other bodies into it.
|
A sensor is an area attached to a physical body that detects the entry of other bodies into it.
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ Models are loaded automatically; adding them to preload.json is not required.
|
||||||
|
|
||||||
## Models
|
## Models
|
||||||
|
|
||||||
Models should be located in the models folder. Currently only OBJ format is supported.
|
Models must be located in the models folder. Currently, the following formats are supported: obj, vec3, and [vcm](vcm.md).
|
||||||
|
|
||||||
>[!IMPORTANT]
|
>[!IMPORTANT]
|
||||||
> When loading an obj model, the \*.mtl file is ignored.
|
> When loading an obj model, the \*.mtl file is ignored.
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,7 @@ Subsections:
|
||||||
- [utf8](scripting/builtins/libutf8.md)
|
- [utf8](scripting/builtins/libutf8.md)
|
||||||
- [vec2, vec3, vec4](scripting/builtins/libvecn.md)
|
- [vec2, vec3, vec4](scripting/builtins/libvecn.md)
|
||||||
- [world](scripting/builtins/libworld.md)
|
- [world](scripting/builtins/libworld.md)
|
||||||
|
- [Extensions for standard libraries](scripting/extensions.md)
|
||||||
- [Module core:bit_converter](scripting/modules/core_bit_converter.md)
|
- [Module core:bit_converter](scripting/modules/core_bit_converter.md)
|
||||||
- [Module core:data_buffer](scripting/modules/core_data_buffer.md)
|
- [Module core:data_buffer](scripting/modules/core_data_buffer.md)
|
||||||
- [Module core:vector2, core:vector3](scripting/modules/core_vector2_vector3.md)
|
- [Module core:vector2, core:vector3](scripting/modules/core_vector2_vector3.md)
|
||||||
|
|
|
||||||
|
|
@ -180,14 +180,34 @@ app.get_content_sources() -> table<string>
|
||||||
|
|
||||||
Returns a list of content sources (paths), in descending priority order.
|
Returns a list of content sources (paths), in descending priority order.
|
||||||
|
|
||||||
``lua
|
```lua
|
||||||
app.set_content_sources(sources: table<string>)
|
app.set_content_sources(sources: table<string>)
|
||||||
```
|
```
|
||||||
|
|
||||||
Sets a list of content sources (paths). Specified in descending priority order.
|
Sets a list of content sources (paths). Specified in descending priority order.
|
||||||
|
|
||||||
``lua
|
```lua
|
||||||
app.reset_content_sources()
|
app.reset_content_sources()
|
||||||
```
|
```
|
||||||
|
|
||||||
Resets content sources.
|
Resets content sources.
|
||||||
|
|
||||||
|
## Sub-instances
|
||||||
|
|
||||||
|
```lua
|
||||||
|
-- Creates a headless engine instance with the current project and the specified application script.
|
||||||
|
-- Returns the instance ID. The number of active sub-instances is currently limited to one.
|
||||||
|
app.start_background_instance(
|
||||||
|
-- script file
|
||||||
|
app_script: string,
|
||||||
|
-- log file
|
||||||
|
output_file: string | nil
|
||||||
|
) -> int
|
||||||
|
|
||||||
|
-- Checks if the engine sub-instance is alive.
|
||||||
|
app.is_instance_alive(handle: int) -> boolean
|
||||||
|
|
||||||
|
-- Stops the engine sub-instance.
|
||||||
|
-- Returns true if the instance was alive at the time of the call.
|
||||||
|
app.terminate(handle: int) -> boolean
|
||||||
|
```
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,8 @@ block.get(x: int, y: int, z: int) -> int
|
||||||
block.get_states(x: int, y: int, z: int) -> int
|
block.get_states(x: int, y: int, z: int) -> int
|
||||||
|
|
||||||
-- Set block with given integer ID and state (default - 0) at given position.
|
-- Set block with given integer ID and state (default - 0) at given position.
|
||||||
block.set(x: int, y: int, z: int, id: int, states: int)
|
-- If noupdate=true is passed, the `on_update` event will not be called for adjacent blocks.
|
||||||
|
block.set(x: int, y: int, z: int, id: int, states: int, noupdate: boolean=false)
|
||||||
|
|
||||||
-- Places a block with a given integer id and state (default - 0) at given position.
|
-- Places a block with a given integer id and state (default - 0) at given position.
|
||||||
-- on behalf of the player, calling the on_placed event.
|
-- on behalf of the player, calling the on_placed event.
|
||||||
|
|
|
||||||
|
|
@ -92,6 +92,8 @@ world.raycast(
|
||||||
-- exclusion filtering mode:
|
-- exclusion filtering mode:
|
||||||
-- if true, filter_entities defines the list of ignored entity types
|
-- if true, filter_entities defines the list of ignored entity types
|
||||||
entities_exclusion: bool = false,
|
entities_exclusion: bool = false,
|
||||||
|
-- include entities with selectable=false
|
||||||
|
nonselect_entities: bool = false;
|
||||||
|
|
||||||
[==[ additional parameters (blocks) ]==]
|
[==[ additional parameters (blocks) ]==]
|
||||||
-- id of the block types used for filtering
|
-- id of the block types used for filtering
|
||||||
|
|
|
||||||
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
|
||||||
|
```
|
||||||
|
|
@ -12,6 +12,7 @@
|
||||||
| render_distance | Дистанция отрисовки текста | 32 |
|
| render_distance | Дистанция отрисовки текста | 32 |
|
||||||
| xray_opacity | Коэффициент видимости через препятствия (просвечивание) | 0 |
|
| xray_opacity | Коэффициент видимости через препятствия (просвечивание) | 0 |
|
||||||
| perspective | Коэффициент перспективы | 1 |
|
| perspective | Коэффициент перспективы | 1 |
|
||||||
|
| font | Шрифт. При отсутствии используется основной | "" |
|
||||||
|
|
||||||
Доступные форматы отображения:
|
Доступные форматы отображения:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
# Свойства блоков
|
# Свойства блоков
|
||||||
|
|
||||||
## Вид
|
## Вид & Звук
|
||||||
|
|
||||||
### Текстура - *texture*
|
### Текстура - *texture*
|
||||||
|
|
||||||
|
|
@ -78,6 +78,11 @@
|
||||||
При приближении к блоку движок создаст эмиттер, который будет работать
|
При приближении к блоку движок создаст эмиттер, который будет работать
|
||||||
до разрушения блока или отдаления камеры на некоторое расстояние.
|
до разрушения блока или отдаления камеры на некоторое расстояние.
|
||||||
|
|
||||||
|
### Материал - *material*
|
||||||
|
|
||||||
|
Определяет имя материала блока в формате `пак:имя_материала`, что влияет на выбор звуков взаимодействия с блоком.
|
||||||
|
Определения материалов расположены в /block_materials.
|
||||||
|
|
||||||
## Варианты
|
## Варианты
|
||||||
|
|
||||||
Некоторые свойства могут варьироваться динамически, в зависимости от номера варианта, хранимого в пользовательских битах блока.
|
Некоторые свойства могут варьироваться динамически, в зависимости от номера варианта, хранимого в пользовательских битах блока.
|
||||||
|
|
|
||||||
|
|
@ -61,12 +61,16 @@
|
||||||
|
|
||||||
### Блокирование - *blocking*
|
### Блокирование - *blocking*
|
||||||
|
|
||||||
Определяет блокирует ли сущность установку блоков.
|
Определяет, блокирует ли сущность установку блока, пересекающегося с хитбоксом сущности.
|
||||||
|
|
||||||
*В будущем будет также блокировать движение других сущностей.*
|
*В будущем будет также блокировать движение других сущностей.*
|
||||||
|
|
||||||
Значение по-умолчанию: *true*.
|
Значение по-умолчанию: *true*.
|
||||||
|
|
||||||
|
### Выделяемость - *selectable*
|
||||||
|
|
||||||
|
При значении в `false` курсор будет игнорировать cущность, пропуская луч далее.
|
||||||
|
|
||||||
### Список сенсоров - *sensors*
|
### Список сенсоров - *sensors*
|
||||||
|
|
||||||
Сенсор - область пространства, привязанная к физическому телу, детектирующее попадание в него других тел.
|
Сенсор - область пространства, привязанная к физическому телу, детектирующее попадание в него других тел.
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@
|
||||||
|
|
||||||
## Модели
|
## Модели
|
||||||
|
|
||||||
Модели должны располагаться в папке models. На данный момент поддерживается только OBJ формат.
|
Модели должны располагаться в папке models. На данный момент поддерживаются форматы: obj, vec3 и [vcm](vcm.md).
|
||||||
|
|
||||||
>[!IMPORTANT]
|
>[!IMPORTANT]
|
||||||
> При загрузке obj модели игнорируется файл \*.mtl.
|
> При загрузке obj модели игнорируется файл \*.mtl.
|
||||||
|
|
|
||||||
|
|
@ -142,4 +142,24 @@ app.set_content_sources(sources: table<string>)
|
||||||
|
|
||||||
-- Сбрасывает список источников контента.
|
-- Сбрасывает список источников контента.
|
||||||
app.reset_content_sources()
|
app.reset_content_sources()
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Под-экземпляры
|
||||||
|
|
||||||
|
```lua
|
||||||
|
-- Создаёт headless-экземпляр движка с текущим проектом и указанным сценарием.
|
||||||
|
-- Возвращает id экземпляра. Число живых под-экземпляров, на данный момент, ограничено одним.
|
||||||
|
app.start_background_instance(
|
||||||
|
-- файл сценария
|
||||||
|
app_script: string,
|
||||||
|
-- файл лога
|
||||||
|
output_file: string | nil
|
||||||
|
) -> int
|
||||||
|
|
||||||
|
-- Проверяет, жив ли под-экземпляр движка.
|
||||||
|
app.is_instance_alive(handle: int) -> boolean
|
||||||
|
|
||||||
|
-- Останавливает под-экземпляр движка.
|
||||||
|
-- Возвращает true если экземпляр был жив в момент вызова.
|
||||||
|
app.terminate_instance(handle: int) -> boolean
|
||||||
|
```
|
||||||
|
|
|
||||||
|
|
@ -92,6 +92,11 @@ socket:recv_async(
|
||||||
[опционально] usetable: boolean=false
|
[опционально] usetable: boolean=false
|
||||||
) -> nil|table|Bytearray
|
) -> nil|table|Bytearray
|
||||||
|
|
||||||
|
-- Оборачивает сокет в io_stream (см. ../io_stream.md)
|
||||||
|
socket:as_stream(
|
||||||
|
[опционально] binary_mode: boolean=true
|
||||||
|
) -> io_stream
|
||||||
|
|
||||||
-- Закрывает соединение
|
-- Закрывает соединение
|
||||||
socket:close()
|
socket:close()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -91,6 +91,8 @@ world.raycast(
|
||||||
-- исключающий режим фильтрации:
|
-- исключающий режим фильтрации:
|
||||||
-- при значении (true) filter_entities определяет список игнорируемых
|
-- при значении (true) filter_entities определяет список игнорируемых
|
||||||
entities_exclusion: bool = false,
|
entities_exclusion: bool = false,
|
||||||
|
-- учитывать сущности с selectable=false
|
||||||
|
nonselect_entities: bool = false;
|
||||||
|
|
||||||
[==[ дополнительные параметры (блоки) ]==]
|
[==[ дополнительные параметры (блоки) ]==]
|
||||||
-- id типов блоков используемых для фильтрации
|
-- id типов блоков используемых для фильтрации
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
- [расширения для string](#расширения-для-string)
|
- [расширения для string](#расширения-для-string)
|
||||||
- [расширения для math](#расширения-для-math)
|
- [расширения для math](#расширения-для-math)
|
||||||
- [расширения для bit](#расширения-для-bit)
|
- [расширения для bit](#расширения-для-bit)
|
||||||
- [Дополнительные глобальные функции](#дополнительные-глобальные-функции)
|
- [дополнительные глобальные функции](#дополнительные-глобальные-функции)
|
||||||
|
|
||||||
## Расширения для table
|
## Расширения для table
|
||||||
```lua
|
```lua
|
||||||
|
|
@ -62,6 +62,12 @@ table.sub(arr: table, start: number | nil, stop: number | nil) -> table
|
||||||
table.insert_unique(t: table, val: any)
|
table.insert_unique(t: table, val: any)
|
||||||
table.insert_unique(t: table, pos: int, val: any)
|
table.insert_unique(t: table, pos: int, val: any)
|
||||||
|
|
||||||
|
-- Возвращает таблицу, содержащую все ключи переданной таблицы, включая числовые.
|
||||||
|
table.keys(t: table) -> table
|
||||||
|
|
||||||
|
-- Добавляет в таблицу t все пары ключ-значение из таблицы extension, при этом если в extension присутствует ключ из t, то его значение будет перезаписано.
|
||||||
|
table.extend(t: table, extension: table) -> table
|
||||||
|
|
||||||
-- Конвертирует переданную таблицу в строку.
|
-- Конвертирует переданную таблицу в строку.
|
||||||
table.tostring(t: table) -> string
|
table.tostring(t: table) -> string
|
||||||
```
|
```
|
||||||
|
|
@ -158,6 +164,12 @@ math.sum(x: number, ... | t: table) -> number
|
||||||
|
|
||||||
-- Возвращает целое число, указывающее знак числа (-1/0/1)
|
-- Возвращает целое число, указывающее знак числа (-1/0/1)
|
||||||
math.sign(x: number) -> int
|
math.sign(x: number) -> int
|
||||||
|
|
||||||
|
-- Одномерный шум с значениями в диапазоне [-1..1].
|
||||||
|
math.noise(x: number, [опционально] octaves: int = 1) -> number
|
||||||
|
|
||||||
|
-- Двумерный шум с значениями в диапазоне [-1..1].
|
||||||
|
math.noise2d(x: number, y: number [опционально] octaves: int = 1) -> number
|
||||||
```
|
```
|
||||||
|
|
||||||
## Расширения для bit
|
## Расширения для bit
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@
|
||||||
Поток имеет три различных вида режима:
|
Поток имеет три различных вида режима:
|
||||||
- Режим общего поведения (`general` / `mode`)
|
- Режим общего поведения (`general` / `mode`)
|
||||||
|
|
||||||
- Режим сброса (`flush` / `flushMode`)
|
- Режим сброса (`flush` / `flush_mode`)
|
||||||
|
|
||||||
- Двоичный режим (`binary`)
|
- Двоичный режим (`binary`)
|
||||||
|
|
||||||
|
|
@ -27,34 +27,34 @@
|
||||||
Определяет, как поток обрабатывает чтение и запись,
|
Определяет, как поток обрабатывает чтение и запись,
|
||||||
имеет три подрежима:
|
имеет три подрежима:
|
||||||
|
|
||||||
| Режим | Описание |
|
| Режим | Описание |
|
||||||
|---------------|--------|
|
|---------------|-------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
| `"default"` | Прямой режим. `read` может вернуть меньше байт, чем запрошено. `write` сразу отправляет данные в низкоуровневый дескриптор. Нет буферизации. |
|
| `"default"` | Прямой режим. `read` может вернуть меньше байт, чем запрошено. `write` сразу отправляет данные в низкоуровневый дескриптор. Нет буферизации. |
|
||||||
| `"yield"` | Как `default`, но при нехватке данных в `read(n)` поток будет вызывать `coroutine.yield()`, пока не соберёт ровно `n` байт. Удобно для корутин. |
|
| `"yield"` | Как `default`, но при нехватке данных в `read(n)` поток будет вызывать `coroutine.yield()`, пока не соберёт ровно `n` байт. Удобно для корутин. |
|
||||||
| `"buffered"` | Включает внутренние буферы чтения и записи. `read` берёт данные из буфера, `write` — складывает в буфер. При превышении `maxBufferSize` — ошибка `buffer overflow`. |
|
| `"buffered"` | Включает внутренние буферы чтения и записи. `read` берёт данные из буфера , `write` — складывает в буфер. |
|
||||||
|
|
||||||
### flush
|
### flush
|
||||||
|
|
||||||
Работает только в режиме `"buffered"`,
|
Работает только в режиме `"buffered"`,
|
||||||
имеет два подрежима:
|
имеет два подрежима:
|
||||||
|
|
||||||
| Режим | Что делает `flush()` |
|
| Режим | Что делает `flush()` |
|
||||||
|------------------|----------------------|
|
|------------------------|-----------------------------------------------------------------------------------------------------|
|
||||||
| `"all"` (по умолчанию) | Сначала сбрасывает буфер записи → низкоуровневый `write`, затем вызывает `ioLib.flush(descriptor)` |
|
| `"all"` (по умолчанию) | Сначала сбрасывает буфер записи → низкоуровневый `write`, затем вызывает `io_lib.flush(descriptor)` |
|
||||||
| `"buffer"` | Сбрасывает только буфер записи, без вызова системного `flush` |
|
| `"buffer"` | Сбрасывает только буфер записи, без вызова системного `flush` |
|
||||||
|
|
||||||
|
|
||||||
### binary
|
### binary
|
||||||
**Независимый флаг** (включается через `set_binary_mode(true)` или при создании потока).
|
**Независимый флаг** (включается через `set_binary_mode(true)` или при создании потока).
|
||||||
Определяет, в каком виде методы `read` и `write` принимают и возвращают данные:
|
Определяет, в каком виде методы `read` и `write` принимают и возвращают данные:
|
||||||
|
|
||||||
| binary = true | binary = false (по умолчанию) |
|
| binary = true | binary = false (по умолчанию) |
|
||||||
|-----------------------------------------|----------------------------------------|
|
|------------------------------------------------------------|----------------------------------------|
|
||||||
| Данные — это **байты** (`Bytearray`, таблица чисел 0..255) | Данные — это **текстовые строки** |
|
| Данные — это **байты** (`Bytearray`, таблица чисел 0..255) | Данные — это **текстовые строки** |
|
||||||
| `read(n)` → `Bytearray` или `table<number>`| `read()` → одна строка |
|
| `read(n)` → `Bytearray` или `table<int>` | `read()` → одна строка |
|
||||||
| `read("i4 f")` → распаковка через `byteutil.unpack` | `read(n)` → n строк в таблице |
|
| `read("i4 f")` → распаковка через `byteutil.unpack` | `read(n)` → n строк в таблице |
|
||||||
| `write(Bytearray)` → запись байтов | `write("hello")` → строка + `\n` |
|
| `write(Bytearray)` → запись байтов | `write("hello")` → строка + `\n` |
|
||||||
| `write("i4", 42)` → `byteutil.pack` | `write({"a","b"})` → две строки с `\n`|
|
| `write("i4", 42)` → `byteutil.pack` | `write({"a","b"})` → две строки с `\n` |
|
||||||
|
|
||||||
`read_line` / `write_line` — работают как в текстовом режиме
|
`read_line` / `write_line` — работают как в текстовом режиме
|
||||||
|
|
||||||
|
|
@ -90,25 +90,25 @@ io_stream:set_flush_mode(string)
|
||||||
--[[
|
--[[
|
||||||
Читает данные из потока
|
Читает данные из потока
|
||||||
В двоичном режиме:
|
В двоичном режиме:
|
||||||
Если arg - number, то читает из потока arg байт и возвращает ввиде Bytearray или таблицы, если useTable = true
|
Если arg - int, то читает из потока arg байт и возвращает ввиде Bytearray или таблицы, если use_table = true
|
||||||
|
|
||||||
Если arg - string, то функция интерпретирует arg как шаблон для byteutil. Прочитает кол-во байт, которое определено шаблоном, передаст их в byteutil.unpack и вернёт результат
|
Если arg - string, то функция интерпретирует arg как шаблон для byteutil. Прочитает кол-во байт, которое определено шаблоном, передаст их в byteutil.unpack и вернёт результат
|
||||||
В текстовом режиме:
|
В текстовом режиме:
|
||||||
Если arg - number, то читает нужное кол-во строк с окончанием CRLF/LF из arg и возвращает ввиде таблицы. Также, если trimEmptyLines = true, то удаляет пустые строки с начала и конца из итоговой таблицы
|
Если arg - int, то читает нужное кол-во строк с окончанием CRLF/LF из arg и возвращает ввиде таблицы. Также, если trim_empty_lines = true, то удаляет пустые строки с начала и конца из итоговой таблицы
|
||||||
|
|
||||||
Если arg не определён, то читает одну строку с окончанием CRLF/LF и возвращает её.
|
Если arg не определён, то читает одну строку с окончанием CRLF/LF и возвращает её.
|
||||||
--]]
|
--]]
|
||||||
io_stream:read(
|
io_stream:read(
|
||||||
[опционально] arg: number | string,
|
[опционально] arg: int | string,
|
||||||
[опционально] useTable | trimEmptyLines: boolean
|
[опционально] use_table = false | trim_empty_lines = true: boolean
|
||||||
) -> Bytearray | table<number> | string | table<string> | ...
|
) -> Bytearray | table<int> | string | table<string> | ...
|
||||||
|
|
||||||
--[[
|
--[[
|
||||||
Записывает данные в поток
|
Записывает данные в поток
|
||||||
В двоичном режиме:
|
В двоичном режиме:
|
||||||
Если arg - string, то функция интерпретирует arg как шаблон для byteutil, передаст его и ... в byteutil.pack и результат запишет в поток
|
Если arg - string, то функция интерпретирует arg как шаблон для byteutil, передаст его и ... в byteutil.pack и результат запишет в поток
|
||||||
|
|
||||||
Если arg - Bytearray | table<number>, то записывает байты в поток
|
Если arg - Bytearray | table<int>, то записывает байты в поток
|
||||||
|
|
||||||
В текстовом режиме:
|
В текстовом режиме:
|
||||||
Если arg - string, то записывает строку в поток (вместе с окончанием LF)
|
Если arg - string, то записывает строку в поток (вместе с окончанием LF)
|
||||||
|
|
@ -116,7 +116,7 @@ io_stream:read(
|
||||||
Если arg - table<string>, то записывает каждую строку из таблицы отдельно
|
Если arg - table<string>, то записывает каждую строку из таблицы отдельно
|
||||||
--]]
|
--]]
|
||||||
io_stream:write(
|
io_stream:write(
|
||||||
arg: Bytearray | table<number> | string | table<string>,
|
arg: Bytearray | table<int> | string | table<string>,
|
||||||
[опционально] ...
|
[опционально] ...
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -128,53 +128,59 @@ io_stream:write_line(string)
|
||||||
|
|
||||||
--[[
|
--[[
|
||||||
В двоичном режиме:
|
В двоичном режиме:
|
||||||
Читает все доступные байты из потока и возвращает ввиде Bytearray или table<number>, если useTable = true
|
Читает все доступные байты из потока и возвращает ввиде Bytearray или table<int>, если use_table = true
|
||||||
|
|
||||||
В текстовом режиме:
|
В текстовом режиме:
|
||||||
Читает все доступные строки из потока в table<string> если useTable = true, или в одну строку вместе с окончаниями, если нет
|
Читает все доступные строки из потока в table<string> если use_table = true, или в одну строку вместе с окончаниями, если нет
|
||||||
|
|
||||||
--]]
|
--]]
|
||||||
io_stream:read_fully(
|
io_stream:read_fully(
|
||||||
[опционально] useTable: boolean
|
[опционально] use_table: boolean
|
||||||
) -> Bytearray | table<number> | table<string> | string
|
) -> Bytearray | table<int> | table<string> | string
|
||||||
|
|
||||||
--[[
|
--[[
|
||||||
Устанавливает позицию в потоке
|
Если length определён, то возвращает true, если length байт доступно к чтению. Иначе возвращает false.
|
||||||
|
|
||||||
|
Если не определён, то возвращает количество байт, которое можно прочитать.
|
||||||
|
|
||||||
|
В не буферизированном режиме потока может всегда возвращать 0 или false, если поток не поддерживает available.
|
||||||
|
--]]
|
||||||
|
io_stream:available(
|
||||||
|
[опционально] length: int
|
||||||
|
) -> int | boolean
|
||||||
|
|
||||||
|
--[[
|
||||||
|
Устанавливает позицию в потоке (всегда в байтах)
|
||||||
Режимы:
|
Режимы:
|
||||||
b - Задаёт позицию относительно начало файла
|
b - Задаёт позицию относительно начало файла
|
||||||
c - Задаёт позицию относительно текущей позиции
|
c - Задаёт позицию относительно текущей позиции
|
||||||
e - Задаёт позицию относительно конца файла
|
e - Задаёт позицию относительно конца файла
|
||||||
|
|
||||||
|
Может бросать ошибку, если поток не поддерживает seek.
|
||||||
--]]
|
--]]
|
||||||
io_stream:seek(
|
io_stream:seek(
|
||||||
mode: string
|
mode: string
|
||||||
offset: number
|
offset: int
|
||||||
)
|
)
|
||||||
|
|
||||||
|
-- Возвращает текущую позицию в потоке от начала.
|
||||||
|
-- Может бросать ошибку, если поток не поддерживает tell.
|
||||||
|
io_stream:tell() -> int
|
||||||
```
|
```
|
||||||
|
|
||||||
## Методы Buffered-режима
|
## Методы Buffered-режима
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
--[[
|
|
||||||
Если length определён, то возвращает true, если length байт доступно к чтению. Иначе возвращает false
|
|
||||||
|
|
||||||
Если не определён, то возвращает количество байт, которое можно прочитать
|
|
||||||
|
|
||||||
--]]
|
|
||||||
io_stream:available(
|
|
||||||
[опционально] length: number
|
|
||||||
) -> number | boolean
|
|
||||||
|
|
||||||
-- Возвращает максимальный размер буферов
|
-- Возвращает максимальный размер буферов
|
||||||
io_stream:get_max_buffer_size() -> number
|
io_stream:get_max_buffer_size() -> int
|
||||||
|
|
||||||
-- Задаёт новый максимальный размер буферов
|
-- Задаёт новый максимальный размер буферов
|
||||||
io_stream:set_max_buffer_size(max_size: number)
|
io_stream:set_max_buffer_size(max_size: int)
|
||||||
```
|
```
|
||||||
|
|
||||||
## Методы контроля состояния потока
|
## Методы контроля состояния потока
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
|
|
||||||
-- Возвращает true, если поток открыт на данный момент
|
-- Возвращает true, если поток открыт на данный момент
|
||||||
io_stream:is_alive() -> bool
|
io_stream:is_alive() -> bool
|
||||||
|
|
||||||
|
|
@ -185,16 +191,27 @@ io_stream:is_closed() -> bool
|
||||||
io_stream:close()
|
io_stream:close()
|
||||||
|
|
||||||
-- Записывает все данные из write-буфера в поток в buffer/all flush-режимах
|
-- Записывает все данные из write-буфера в поток в buffer/all flush-режимах
|
||||||
-- Вызывает ioLib.flush() в all flush-режиме
|
-- Вызывает ioLib.flush() в all flush-режиме, или ничего не делает, если
|
||||||
|
-- ioLib не поддерживает flush.
|
||||||
io_stream:flush()
|
io_stream:flush()
|
||||||
|
|
||||||
|
-- Создаёт новый поток из Bytearray.
|
||||||
|
-- Может использоваться одновременно как для чтения, так и для записи.
|
||||||
|
-- Результат записи будет записан в тот же Bytearray, что был передан в функцию.
|
||||||
|
io_stream.wrap_bytearray(
|
||||||
|
buffer: Bytearray,
|
||||||
|
|
||||||
|
-- по-умолчанию равен true, поскольку функция из вводных аргументов
|
||||||
|
-- будет использоваться преимущественно для работы с двоичными данными.
|
||||||
|
[опционально] binary_mode: boolean = true
|
||||||
|
) -> io_stream
|
||||||
|
|
||||||
-- Создаёт новый поток с переданным дескриптором и использующим переданную I/O библиотеку. (Более подробно в core:io_stream.lua)
|
-- Создаёт новый поток с переданным дескриптором и использующим переданную I/O библиотеку. (Более подробно в core:io_stream.lua)
|
||||||
io_stream.new(
|
io_stream.new(
|
||||||
descriptor: int,
|
descriptor: int,
|
||||||
binaryMode: bool,
|
binary_mode: boolean,
|
||||||
ioLib: table,
|
io_lib: table,
|
||||||
[опционально] mode: string = "default",
|
[опционально] mode: string = "default",
|
||||||
[опционально] flushMode: string = "all"
|
[опционально] flush_mode: string = "all"
|
||||||
) -> io_stream
|
) -> io_stream
|
||||||
```
|
```
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
generator = "base:demo"
|
generator = "base:demo"
|
||||||
player-entity = "base:player"
|
player-entity = "base:player"
|
||||||
hand-skeleton = "base:hand"
|
hand-skeleton = "base:hand"
|
||||||
|
block-material = "base:stone"
|
||||||
|
|
|
||||||
|
|
@ -12,14 +12,14 @@ function util.drop(ppos, itemid, count, data, pickup_delay)
|
||||||
}})
|
}})
|
||||||
end
|
end
|
||||||
|
|
||||||
local function calc_loot(loot_table)
|
function util.calc_loot(loot_table)
|
||||||
local results = {}
|
local results = {}
|
||||||
for _, loot in ipairs(loot_table) do
|
for _, loot in ipairs(loot_table) do
|
||||||
local chance = loot.chance or 1
|
local chance = loot.chance or 1
|
||||||
local count = loot.count or 1
|
local count = loot.count or 1
|
||||||
|
|
||||||
local roll = math.random()
|
local roll = math.random()
|
||||||
|
|
||||||
if roll < chance then
|
if roll < chance then
|
||||||
if loot.min and loot.max then
|
if loot.min and loot.max then
|
||||||
count = math.random(loot.min, loot.max)
|
count = math.random(loot.min, loot.max)
|
||||||
|
|
@ -37,7 +37,7 @@ end
|
||||||
function util.block_loot(blockid)
|
function util.block_loot(blockid)
|
||||||
local lootscheme = block.properties[blockid]["base:loot"]
|
local lootscheme = block.properties[blockid]["base:loot"]
|
||||||
if lootscheme then
|
if lootscheme then
|
||||||
return calc_loot(lootscheme)
|
return util.calc_loot(lootscheme)
|
||||||
end
|
end
|
||||||
return {{item=block.get_picking_item(blockid), count=1}}
|
return {{item=block.get_picking_item(blockid), count=1}}
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ dropitem = ARGS
|
||||||
if dropitem.item then
|
if dropitem.item then
|
||||||
dropitem.id = item.index(dropitem.item)
|
dropitem.id = item.index(dropitem.item)
|
||||||
end
|
end
|
||||||
if dropitem then
|
if dropitem then
|
||||||
timer = dropitem.pickup_delay or timer
|
timer = dropitem.pickup_delay or timer
|
||||||
end
|
end
|
||||||
if SAVED_DATA.item then
|
if SAVED_DATA.item then
|
||||||
|
|
@ -26,6 +26,7 @@ function on_save()
|
||||||
end
|
end
|
||||||
|
|
||||||
if vc.is_client() then
|
if vc.is_client() then
|
||||||
|
local SCALE = 0.3
|
||||||
local scale = {1, 1, 1}
|
local scale = {1, 1, 1}
|
||||||
local rotation = mat4.rotate({
|
local rotation = mat4.rotate({
|
||||||
math.random(), math.random(), math.random()
|
math.random(), math.random(), math.random()
|
||||||
|
|
@ -35,7 +36,7 @@ if vc.is_client() then
|
||||||
local matrix = mat4.idt()
|
local matrix = mat4.idt()
|
||||||
rig:set_model(0, item.model_name(dropitem.id))
|
rig:set_model(0, item.model_name(dropitem.id))
|
||||||
local bodysize = math.min(scale[1], scale[2], scale[3])
|
local bodysize = math.min(scale[1], scale[2], scale[3])
|
||||||
body:set_size({scale[1], bodysize, scale[3]})
|
body:set_size(vec3.mul({scale[1], bodysize, scale[3]}, SCALE))
|
||||||
mat4.mul(matrix, rotation, matrix)
|
mat4.mul(matrix, rotation, matrix)
|
||||||
mat4.scale(matrix, scale, matrix)
|
mat4.scale(matrix, scale, matrix)
|
||||||
rig:set_matrix(0, matrix)
|
rig:set_matrix(0, matrix)
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,6 @@
|
||||||
|
local dirtid = block.index("base:dirt")
|
||||||
|
local grass_blockid = block.index("base:grass_block")
|
||||||
|
|
||||||
local offsets = {}
|
local offsets = {}
|
||||||
for lx=-1,1 do
|
for lx=-1,1 do
|
||||||
for ly=-1,1 do
|
for ly=-1,1 do
|
||||||
|
|
@ -8,18 +11,17 @@ for lx=-1,1 do
|
||||||
end
|
end
|
||||||
|
|
||||||
function on_random_update(x, y, z)
|
function on_random_update(x, y, z)
|
||||||
local dirtid = block.index('base:dirt');
|
|
||||||
if block.is_solid_at(x, y+1, z) then
|
if block.is_solid_at(x, y+1, z) then
|
||||||
block.set(x, y, z, dirtid, 0)
|
block.set(x, y, z, dirtid, 0)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
local grassblockid = block.index('base:grass_block')
|
|
||||||
for _, offset in ipairs(offsets) do
|
for _, offset in ipairs(offsets) do
|
||||||
local nx, ny, nz = x + offset[1], y + offset[2], z + offset[3]
|
local nx, ny, nz = x + offset[1], y + offset[2], z + offset[3]
|
||||||
|
|
||||||
if block.get(nx, ny, nz) == dirtid and
|
if block.get(nx, ny, nz) == dirtid and
|
||||||
not block.is_solid_at(nx, ny + 1, nz) then
|
not block.is_solid_at(nx, ny + 1, nz) then
|
||||||
block.set(nx, ny, nz, grassblockid, 0)
|
block.set(nx, ny, nz, grass_blockid, 0)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ local function update_line(line, uptime)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
events.on("core:chat", function(message)
|
local core_chat_handler = events.on("core:chat", function(message)
|
||||||
while #lines >= max_lines do
|
while #lines >= max_lines do
|
||||||
document[lines[1][1]]:destruct()
|
document[lines[1][1]]:destruct()
|
||||||
table.remove(lines, 1)
|
table.remove(lines, 1)
|
||||||
|
|
@ -36,7 +36,7 @@ end)
|
||||||
function on_open()
|
function on_open()
|
||||||
if not initialized then
|
if not initialized then
|
||||||
initialized = true
|
initialized = true
|
||||||
|
|
||||||
document.root:setInterval(1/animation_fps * 1000, function ()
|
document.root:setInterval(1/animation_fps * 1000, function ()
|
||||||
local uptime = time.uptime()
|
local uptime = time.uptime()
|
||||||
for _, line in ipairs(lines) do
|
for _, line in ipairs(lines) do
|
||||||
|
|
@ -52,3 +52,7 @@ function on_open()
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function on_destroy()
|
||||||
|
events.remove("core:chat", core_chat_handler)
|
||||||
|
end
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ function on_open(params)
|
||||||
mode = params.mode
|
mode = params.mode
|
||||||
end
|
end
|
||||||
refresh()
|
refresh()
|
||||||
|
document.search_textbox.focused = true
|
||||||
end
|
end
|
||||||
|
|
||||||
-- add - packs to be added to the world (after apply)
|
-- add - packs to be added to the world (after apply)
|
||||||
|
|
@ -178,40 +179,40 @@ function place_pack(panel, packinfo, callback, position_func)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local Version = {};
|
local Version = {}
|
||||||
|
|
||||||
function Version.matches_pattern(version)
|
function Version.matches_pattern(version)
|
||||||
for _, letter in string.gmatch(version, "%.+") do
|
local t = string.split(version, ".")
|
||||||
if type(letter) ~= "number" or letter ~= "." then
|
if #t ~= 2 and #t ~= 3 then return false end
|
||||||
return false;
|
|
||||||
end
|
|
||||||
|
|
||||||
local t = string.split(version, ".");
|
for i = 1, #t do
|
||||||
|
local matched = string.match(t[i], "^%d+$")
|
||||||
return #t == 2 or #t == 3;
|
if not matched then return false end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
return true
|
||||||
end
|
end
|
||||||
|
|
||||||
function Version.__equal(ver1, ver2)
|
function Version.__equal(ver1, ver2)
|
||||||
return ver1[1] == ver2[1] and ver1[2] == ver2[2] and ver1[3] == ver2[3];
|
return ver1[1] == ver2[1] and ver1[2] == ver2[2] and ver1[3] == ver2[3]
|
||||||
end
|
end
|
||||||
|
|
||||||
function Version.__greater(ver1, ver2)
|
function Version.__greater(ver1, ver2)
|
||||||
if ver1[1] ~= ver2[1] then return ver1[1] > ver2[1] end;
|
if ver1[1] ~= ver2[1] then return ver1[1] > ver2[1] end
|
||||||
if ver1[2] ~= ver2[2] then return ver1[2] > ver2[2] end;
|
if ver1[2] ~= ver2[2] then return ver1[2] > ver2[2] end
|
||||||
return ver1[3] > ver2[3];
|
return ver1[3] > ver2[3]
|
||||||
end
|
end
|
||||||
|
|
||||||
function Version.__less(ver1, ver2)
|
function Version.__less(ver1, ver2)
|
||||||
return Version.__greater(ver2, ver1);
|
return Version.__greater(ver2, ver1)
|
||||||
end
|
end
|
||||||
|
|
||||||
function Version.__greater_or_equal(ver1, ver2)
|
function Version.__greater_or_equal(ver1, ver2)
|
||||||
return not Version.__less(ver1, ver2);
|
return not Version.__less(ver2, ver1)
|
||||||
end
|
end
|
||||||
|
|
||||||
function Version.__less_or_equal(ver1, ver2)
|
function Version.__less_or_equal(ver1, ver2)
|
||||||
return not Version.__greater(ver1, ver2);
|
return not Version.__greater(ver1, ver2)
|
||||||
end
|
end
|
||||||
|
|
||||||
Version.operators = {
|
Version.operators = {
|
||||||
|
|
@ -223,40 +224,39 @@ Version.operators = {
|
||||||
}
|
}
|
||||||
|
|
||||||
function Version.compare(op, ver1, ver2)
|
function Version.compare(op, ver1, ver2)
|
||||||
ver1 = string.split(ver1, ".");
|
ver1 = string.split(ver1, ".")
|
||||||
ver2 = string.split(ver2, ".");
|
ver2 = string.split(ver2, ".")
|
||||||
|
|
||||||
local comparison_func = Version.operators[op];
|
local comparison_func = Version.operators[op];
|
||||||
|
|
||||||
if comparison_func then
|
if comparison_func then
|
||||||
return comparison_func(ver1, ver2);
|
return comparison_func(ver1, ver2)
|
||||||
else
|
else
|
||||||
return false;
|
return false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function Version.parse(version)
|
function Version.parse(version)
|
||||||
local op = string.sub(version, 1, 2);
|
local op = string.sub(version, 1, 2)
|
||||||
if op == ">=" or op == "<=" then
|
if op == ">=" or op == "<=" then
|
||||||
return op, string.sub(version, #op + 1);
|
return op, string.sub(version, #op + 1)
|
||||||
end
|
end
|
||||||
|
|
||||||
op = string.sub(version, 1, 1);
|
op = string.sub(version, 1, 1);
|
||||||
if op == ">" or op == "<" then
|
if op == ">" or op == "<" then
|
||||||
return op, string.sub(version, #op + 1);
|
return op, string.sub(version, #op + 1)
|
||||||
end
|
end
|
||||||
|
|
||||||
return "=", version;
|
return "=", version
|
||||||
end
|
end
|
||||||
|
|
||||||
local function compare_version(dependent_version, actual_version)
|
local function compare_version(op, dependent_version, actual_version)
|
||||||
if Version.matches_pattern(dependent_version) and Version.matches_pattern(actual_version) then
|
if Version.matches_pattern(dependent_version) and Version.matches_pattern(actual_version) then
|
||||||
local op, dep_ver = Version.parse_version(dependent_version);
|
return Version.compare(op, dep_ver, actual_version)
|
||||||
Version.compare(op, dep_ver, actual_version);
|
|
||||||
elseif dependent_version == "*" or dependent_version == actual_version then
|
elseif dependent_version == "*" or dependent_version == actual_version then
|
||||||
return true;
|
return true
|
||||||
else
|
else
|
||||||
return false;
|
return false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -276,15 +276,15 @@ function check_dependencies(packinfo)
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
local dep_pack = pack.get_info(depid);
|
local dep_pack = pack.get_info(depid)
|
||||||
|
local op, ver = Version.parse(depver)
|
||||||
if not compare_version(depver, dep_pack.version) then
|
|
||||||
local op, ver = Version.parse(depver)
|
if not compare_version(op, ver, dep_pack.version) then
|
||||||
return string.format(
|
return string.format(
|
||||||
"%s: %s != %s (%s)",
|
"%s: %s != %s (%s)",
|
||||||
gui.str("error.dependency-version-not-met"),
|
gui.str("error.dependency-version-not-met"),
|
||||||
dep_pack.version, ver, depid
|
dep_pack.version, ver, depid
|
||||||
);
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
if table.has(packs_installed, packinfo.id) then
|
if table.has(packs_installed, packinfo.id) then
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@
|
||||||
@World generator
|
@World generator
|
||||||
</button>
|
</button>
|
||||||
<button id='content_btn' onclick='save_state() menu.page="content"'/>
|
<button id='content_btn' onclick='save_state() menu.page="content"'/>
|
||||||
<button onclick='create_world()' margin='1,20,1,1'>
|
<button onclick='create_world()' margin='0,20,0,1'>
|
||||||
@Create World
|
@Create World
|
||||||
</button>
|
</button>
|
||||||
<button onclick='menu:back()'>@Back</button>
|
<button onclick='menu:back()'>@Back</button>
|
||||||
|
|
|
||||||
|
|
@ -148,3 +148,29 @@ function error(message, level)
|
||||||
end
|
end
|
||||||
__error(message, level)
|
__error(message, level)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local LoggerClass = {
|
||||||
|
__index = {
|
||||||
|
info = function (self, text)
|
||||||
|
debug.log(string.format("[%s] %s", self.name, text))
|
||||||
|
end,
|
||||||
|
warning = function (self, text)
|
||||||
|
debug.warning(string.format("[%s] %s", self.name, text))
|
||||||
|
end,
|
||||||
|
error = function (self, text)
|
||||||
|
debug.error(string.format("[%s] %s", self.name, text))
|
||||||
|
end
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
local _getinfo = debug.getinfo
|
||||||
|
|
||||||
|
function debug.Logger(name)
|
||||||
|
if not name then
|
||||||
|
local source = _getinfo(2, "S").source
|
||||||
|
name = file.remove_ext(source)
|
||||||
|
else
|
||||||
|
name = string.format("%s:%s", file.prefix(_getinfo(2, "S").source), name)
|
||||||
|
end
|
||||||
|
return setmetatable({name=name}, LoggerClass)
|
||||||
|
end
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ function events.on(event, func)
|
||||||
events.handlers[event] = {}
|
events.handlers[event] = {}
|
||||||
end
|
end
|
||||||
table.insert(events.handlers[event], func)
|
table.insert(events.handlers[event], func)
|
||||||
|
return func
|
||||||
end
|
end
|
||||||
|
|
||||||
function events.reset(event, func)
|
function events.reset(event, func)
|
||||||
|
|
@ -24,6 +25,14 @@ function events.reset(event, func)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function events.remove(event, handler)
|
||||||
|
local handlers = events.handlers[event]
|
||||||
|
if not handlers then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
table.remove_value(handlers, handler)
|
||||||
|
end
|
||||||
|
|
||||||
function events.remove_by_prefix(prefix)
|
function events.remove_by_prefix(prefix)
|
||||||
for name, handlers in pairs(events.handlers) do
|
for name, handlers in pairs(events.handlers) do
|
||||||
local actualname = name
|
local actualname = name
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,10 @@ function file.parent(path)
|
||||||
return dir
|
return dir
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function file.remove_ext(path)
|
||||||
|
return string.format("%s/%s", file.parent(path), file.stem(path))
|
||||||
|
end
|
||||||
|
|
||||||
function file.path(path)
|
function file.path(path)
|
||||||
local pos = path:find(':')
|
local pos = path:find(':')
|
||||||
return path:sub(pos + 1)
|
return path:sub(pos + 1)
|
||||||
|
|
|
||||||
|
|
@ -39,3 +39,85 @@ end
|
||||||
function math.sign(x)
|
function math.sign(x)
|
||||||
return (x > 0) and 1 or (x < 0 and -1 or 0)
|
return (x > 0) and 1 or (x < 0 and -1 or 0)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local noise_period = 3021
|
||||||
|
local noise_rands = {}
|
||||||
|
|
||||||
|
for i=1,noise_period do
|
||||||
|
table.insert(noise_rands, math.random() * 2.0 - 1.0)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function smoothstep(t)
|
||||||
|
return t * t * (3 - 2 * t)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function sample_noise(x)
|
||||||
|
x = x % noise_period
|
||||||
|
local left_cell = math.floor(x)
|
||||||
|
local right_cell = (left_cell + 1) % noise_period
|
||||||
|
local t = smoothstep(x - left_cell)
|
||||||
|
return noise_rands[left_cell + 1] * (1.0 - t)
|
||||||
|
+ noise_rands[right_cell + 1] * t
|
||||||
|
end
|
||||||
|
|
||||||
|
function math.noise(x, octaves)
|
||||||
|
x = math.abs(x)
|
||||||
|
octaves = octaves or 1
|
||||||
|
local value = 0.0
|
||||||
|
local mul = 1.0
|
||||||
|
for i=1,octaves do
|
||||||
|
value = value * (1.0 - mul) + sample_noise(x + 37 * i) * mul
|
||||||
|
x = x * 2.0
|
||||||
|
mul = mul * 0.5
|
||||||
|
end
|
||||||
|
return value
|
||||||
|
end
|
||||||
|
|
||||||
|
local function lerp(a, b, t)
|
||||||
|
return a + (b - a) * t
|
||||||
|
end
|
||||||
|
|
||||||
|
local function rand_2d(x, y)
|
||||||
|
return noise_rands[math.floor(x * 317 + y) % noise_period + 1]
|
||||||
|
end
|
||||||
|
|
||||||
|
local function sample_noise2d(x, y)
|
||||||
|
x = math.abs(x)
|
||||||
|
y = math.abs(y)
|
||||||
|
|
||||||
|
local x0 = math.floor(x)
|
||||||
|
local y0 = math.floor(y)
|
||||||
|
local x1 = x0 + 1
|
||||||
|
local y1 = y0 + 1
|
||||||
|
|
||||||
|
local tx = x - x0
|
||||||
|
local ty = y - y0
|
||||||
|
|
||||||
|
local sx = smoothstep(tx)
|
||||||
|
local sy = smoothstep(ty)
|
||||||
|
|
||||||
|
local c00 = rand_2d(x0, y0)
|
||||||
|
local c10 = rand_2d(x1, y0)
|
||||||
|
local c01 = rand_2d(x0, y1)
|
||||||
|
local c11 = rand_2d(x1, y1)
|
||||||
|
|
||||||
|
local bottom = lerp(c00, c10, sx)
|
||||||
|
local top = lerp(c01, c11, sx)
|
||||||
|
|
||||||
|
return lerp(bottom, top, sy)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
function math.noise2d(x, y, octaves)
|
||||||
|
octaves = octaves or 1
|
||||||
|
local value = 0.0
|
||||||
|
local mul = 1.0
|
||||||
|
for i=1,octaves do
|
||||||
|
value = value * (1.0 - mul)
|
||||||
|
+ sample_noise2d(x + 37 * i, y + 73 * i) * mul
|
||||||
|
x = x * 2.0
|
||||||
|
y = y * 2.0
|
||||||
|
mul = mul * 0.5
|
||||||
|
end
|
||||||
|
return value
|
||||||
|
end
|
||||||
|
|
|
||||||
|
|
@ -59,11 +59,7 @@ end
|
||||||
|
|
||||||
function table.extend(t1, t2)
|
function table.extend(t1, t2)
|
||||||
for i, v in pairs(t2) do
|
for i, v in pairs(t2) do
|
||||||
if type(i) == "number" then
|
t1[i] = v
|
||||||
t1[#t1 + 1] = v
|
|
||||||
else
|
|
||||||
t1[i] = v
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return t1
|
return t1
|
||||||
|
|
@ -201,3 +197,11 @@ function table.tostring(t)
|
||||||
end
|
end
|
||||||
return s..']'
|
return s..']'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function table.keys(t)
|
||||||
|
local keys = {}
|
||||||
|
for key, _ in pairs(t) do
|
||||||
|
table.insert(keys, key)
|
||||||
|
end
|
||||||
|
return keys
|
||||||
|
end
|
||||||
|
|
|
||||||
|
|
@ -77,15 +77,18 @@ function Element.new(docname, name)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- the engine automatically creates an instance for every ui document (layout)
|
-- the engine automatically creates an instance for every ui document (layout)
|
||||||
local Document = {}
|
local Document = {
|
||||||
function Document.new(docname)
|
__index=function(self, k)
|
||||||
return setmetatable({name=docname}, {
|
if type(k) ~= "string" then
|
||||||
__index=function(self, k)
|
error("element id is not a string")
|
||||||
local elem = Element.new(self.name, k)
|
|
||||||
rawset(self, k, elem)
|
|
||||||
return elem
|
|
||||||
end
|
end
|
||||||
})
|
local elem = Element.new(self.name, k)
|
||||||
|
rawset(self, k, elem)
|
||||||
|
return elem
|
||||||
|
end
|
||||||
|
}
|
||||||
|
function Document.new(docname)
|
||||||
|
return setmetatable({name=docname}, Document)
|
||||||
end
|
end
|
||||||
|
|
||||||
local RadioGroup = {}
|
local RadioGroup = {}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
local rules = {nexid = 1, rules = {}}
|
local rules = {nextid = 1, rules = {}}
|
||||||
|
|
||||||
function rules.get_rule(name)
|
function rules.get_rule(name)
|
||||||
local rule = rules.rules[name]
|
local rule = rules.rules[name]
|
||||||
|
|
@ -27,13 +27,13 @@ end
|
||||||
|
|
||||||
function rules.reset(name)
|
function rules.reset(name)
|
||||||
local rule = rules.get_rule(name)
|
local rule = rules.get_rule(name)
|
||||||
rules.set(rule.default)
|
rules.set(name, rule.default)
|
||||||
end
|
end
|
||||||
|
|
||||||
function rules.listen(name, handler)
|
function rules.listen(name, handler)
|
||||||
local rule = rules.get_rule(name)
|
local rule = rules.get_rule(name)
|
||||||
local id = rules.nexid
|
local id = rules.nextid
|
||||||
rules.nextid = rules.nexid + 1
|
rules.nextid = rules.nextid + 1
|
||||||
rule.listeners[utf8.encode(id)] = handler
|
rule.listeners[utf8.encode(id)] = handler
|
||||||
return id
|
return id
|
||||||
end
|
end
|
||||||
|
|
|
||||||
131
res/modules/internal/stream_providers/bytearray.lua
Normal file
131
res/modules/internal/stream_providers/bytearray.lua
Normal file
|
|
@ -0,0 +1,131 @@
|
||||||
|
local io_stream = require "core:io_stream"
|
||||||
|
|
||||||
|
local lib = { }
|
||||||
|
|
||||||
|
local buffers = { }
|
||||||
|
local positions = { }
|
||||||
|
|
||||||
|
local next_descriptor = 0
|
||||||
|
|
||||||
|
local function open_descriptor(buffer)
|
||||||
|
next_descriptor = next_descriptor + 1
|
||||||
|
|
||||||
|
buffers[next_descriptor] = buffer
|
||||||
|
positions[next_descriptor] = 1
|
||||||
|
|
||||||
|
return next_descriptor
|
||||||
|
end
|
||||||
|
|
||||||
|
local function require_descriptor(descriptor)
|
||||||
|
if not buffers[descriptor] then
|
||||||
|
error("unknown descriptor")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function lib.read(descriptor, length)
|
||||||
|
require_descriptor(descriptor)
|
||||||
|
|
||||||
|
local buf = buffers[descriptor]
|
||||||
|
local buf_length = #buf
|
||||||
|
local pos = positions[descriptor]
|
||||||
|
|
||||||
|
local to_read = math.min(buf_length - pos + 1, length)
|
||||||
|
|
||||||
|
if to_read <= 0 then
|
||||||
|
return Bytearray()
|
||||||
|
end
|
||||||
|
|
||||||
|
local segment = buf:slice(pos, to_read)
|
||||||
|
|
||||||
|
positions[descriptor] = pos + to_read
|
||||||
|
|
||||||
|
return segment
|
||||||
|
end
|
||||||
|
|
||||||
|
function lib.write(descriptor, data)
|
||||||
|
require_descriptor(descriptor)
|
||||||
|
|
||||||
|
local buf = buffers[descriptor]
|
||||||
|
local pos = positions[descriptor]
|
||||||
|
|
||||||
|
local buf_length = #buf
|
||||||
|
local data_length = #data
|
||||||
|
|
||||||
|
local end_pos = pos + data_length - 1
|
||||||
|
|
||||||
|
-- size ensuring
|
||||||
|
if end_pos > buf_length then
|
||||||
|
for i = buf_length + 1, end_pos do
|
||||||
|
buf[i] = 0
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
for i = 1, data_length do
|
||||||
|
buf[i + pos - 1] = data[i]
|
||||||
|
end
|
||||||
|
|
||||||
|
positions[descriptor] = pos + data_length
|
||||||
|
end
|
||||||
|
|
||||||
|
function lib.seek(descriptor, mode, offset)
|
||||||
|
require_descriptor(descriptor)
|
||||||
|
|
||||||
|
local buf = buffers[descriptor]
|
||||||
|
local buf_length = #buf
|
||||||
|
|
||||||
|
local base
|
||||||
|
|
||||||
|
if mode == 'b' then
|
||||||
|
base = 1
|
||||||
|
elseif mode == 'c' then
|
||||||
|
base = positions[descriptor]
|
||||||
|
elseif mode == 'e' then
|
||||||
|
base = buf_length + 1
|
||||||
|
else error('invalid seek mode') end
|
||||||
|
|
||||||
|
local new_pos = base + offset
|
||||||
|
|
||||||
|
if new_pos < 1 then
|
||||||
|
error('failed to seek stream')
|
||||||
|
end
|
||||||
|
|
||||||
|
positions[descriptor] = new_pos
|
||||||
|
end
|
||||||
|
|
||||||
|
function lib.tell(descriptor)
|
||||||
|
require_descriptor(descriptor)
|
||||||
|
|
||||||
|
return positions[descriptor]
|
||||||
|
end
|
||||||
|
|
||||||
|
function lib.available(descriptor)
|
||||||
|
require_descriptor(descriptor)
|
||||||
|
|
||||||
|
local buf = buffers[descriptor]
|
||||||
|
local pos = positions[descriptor]
|
||||||
|
|
||||||
|
return math.max(#buf - pos + 1, 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
function lib.is_alive(descriptor)
|
||||||
|
return buffers[descriptor] ~= nil
|
||||||
|
end
|
||||||
|
|
||||||
|
function lib.close(descriptor)
|
||||||
|
require_descriptor(descriptor)
|
||||||
|
|
||||||
|
buffers[descriptor] = nil
|
||||||
|
positions[descriptor] = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
return function(buffer, binary_mode)
|
||||||
|
if binary_mode == nil then
|
||||||
|
binary_mode = true
|
||||||
|
end
|
||||||
|
|
||||||
|
return io_stream.new(
|
||||||
|
open_descriptor(buffer),
|
||||||
|
binary_mode,
|
||||||
|
lib
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
@ -4,15 +4,28 @@ local lib = {
|
||||||
read = file.__read_descriptor,
|
read = file.__read_descriptor,
|
||||||
write = file.__write_descriptor,
|
write = file.__write_descriptor,
|
||||||
seek = file.__seek_descriptor,
|
seek = file.__seek_descriptor,
|
||||||
|
tell = file.__tell_descriptor,
|
||||||
flush = file.__flush_descriptor,
|
flush = file.__flush_descriptor,
|
||||||
|
available = file.__available_descriptor,
|
||||||
is_alive = file.__has_descriptor,
|
is_alive = file.__has_descriptor,
|
||||||
close = file.__close_descriptor
|
close = file.__close_descriptor
|
||||||
}
|
}
|
||||||
|
|
||||||
|
local __open_descriptor = file.__open_descriptor
|
||||||
|
file.__open_descriptor = nil
|
||||||
|
file.__read_descriptor = nil
|
||||||
|
file.__write_descriptor = nil
|
||||||
|
file.__seek_descriptor = nil
|
||||||
|
file.__tell_descriptor = nil
|
||||||
|
file.__flush_descriptor = nil
|
||||||
|
file.__available_descriptor = nil
|
||||||
|
file.__has_descriptor = nil
|
||||||
|
file.__close_descriptor = nil
|
||||||
|
|
||||||
return function(path, mode)
|
return function(path, mode)
|
||||||
return io_stream.new(
|
return io_stream.new(
|
||||||
file.__open_descriptor(path, mode),
|
__open_descriptor(path, mode),
|
||||||
mode:find('b') ~= nil,
|
mode:find('b') ~= nil,
|
||||||
lib
|
lib
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ int close(int fd);
|
||||||
ssize_t read(int fd, void *buf, size_t count);
|
ssize_t read(int fd, void *buf, size_t count);
|
||||||
ssize_t write(int fd, const void *buf, size_t count);
|
ssize_t write(int fd, const void *buf, size_t count);
|
||||||
int fcntl(int fd, int cmd, ...);
|
int fcntl(int fd, int cmd, ...);
|
||||||
|
int ioctl(int fd, unsigned long request, ...);
|
||||||
|
|
||||||
const char *strerror(int errnum);
|
const char *strerror(int errnum);
|
||||||
]]
|
]]
|
||||||
|
|
@ -20,6 +21,7 @@ local O_WRONLY = 0x1
|
||||||
local O_RDWR = 0x2
|
local O_RDWR = 0x2
|
||||||
local O_NONBLOCK = 0x800
|
local O_NONBLOCK = 0x800
|
||||||
local F_GETFL = 3
|
local F_GETFL = 3
|
||||||
|
local FIONREAD = 0x541B
|
||||||
|
|
||||||
local function getError()
|
local function getError()
|
||||||
local err = FFI.errno()
|
local err = FFI.errno()
|
||||||
|
|
@ -58,12 +60,16 @@ function lib.write(fd, bytearray)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function lib.seek(fd, mode, offset)
|
function lib.available(fd)
|
||||||
error("cannot seek the named pipe")
|
if fd == nil or fd < 0 then return 0 end
|
||||||
end
|
|
||||||
|
|
||||||
function lib.flush(fd)
|
local bytes_ready = FFI.new("int[1]", 0)
|
||||||
-- no flush on unix
|
|
||||||
|
if C.ioctl(fd, FIONREAD, bytes_ready) == -1 then
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
|
||||||
|
return tonumber(bytes_ready[0])
|
||||||
end
|
end
|
||||||
|
|
||||||
function lib.is_alive(fd)
|
function lib.is_alive(fd)
|
||||||
|
|
|
||||||
|
|
@ -97,14 +97,26 @@ function lib.write(handle, bytearray)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function lib.seek(handle, mode, offset)
|
|
||||||
error("cannot seek the named pipe")
|
|
||||||
end
|
|
||||||
|
|
||||||
function lib.flush(handle)
|
function lib.flush(handle)
|
||||||
C.FlushFileBuffers(handle)
|
C.FlushFileBuffers(handle)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function lib.available(handle)
|
||||||
|
if handle == nil or handle == INVALID_HANDLE_VALUE then
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
|
||||||
|
local bytes_available = FFI.new("DWORD[1]")
|
||||||
|
|
||||||
|
local success = C.PeekNamedPipe(handle, nil, 0, nil, bytes_available, nil)
|
||||||
|
|
||||||
|
if success == 0 then
|
||||||
|
return 0
|
||||||
|
end
|
||||||
|
|
||||||
|
return tonumber(bytes_available[0])
|
||||||
|
end
|
||||||
|
|
||||||
function lib.is_alive(handle)
|
function lib.is_alive(handle)
|
||||||
if handle == nil or handle == INVALID_HANDLE_VALUE then
|
if handle == nil or handle == INVALID_HANDLE_VALUE then
|
||||||
return false
|
return false
|
||||||
|
|
|
||||||
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 CR = string.byte('\r')
|
||||||
local LF = string.byte('\n')
|
local LF = string.byte('\n')
|
||||||
|
|
||||||
local function readFully(result, readFunc)
|
local function read_fully(result, read_func)
|
||||||
local isTable = type(result) == "table"
|
local isTable = type(result) == "table"
|
||||||
|
|
||||||
local buf
|
local buf
|
||||||
|
|
||||||
repeat
|
repeat
|
||||||
buf = readFunc(MAX_BUFFER_SIZE)
|
buf = read_func(MAX_BUFFER_SIZE)
|
||||||
|
|
||||||
if isTable then
|
if isTable then
|
||||||
for i = 1, #buf do
|
for i = 1, #buf do
|
||||||
|
|
@ -42,41 +42,46 @@ local function readFully(result, readFunc)
|
||||||
end
|
end
|
||||||
|
|
||||||
--[[
|
--[[
|
||||||
|
|
||||||
descriptor - descriptor of stream for provided I/O library
|
descriptor - descriptor of stream for provided I/O library
|
||||||
binaryMode - if enabled, most methods will expect bytes instead of strings
|
binaryMode - if enabled, most methods will expect bytes instead of strings
|
||||||
ioLib - I/O library. Should include the following functions:
|
ioLib - I/O library. Should include the following functions:
|
||||||
read(descriptor: int, length: int) -> Bytearray
|
read(descriptor: int, length: int) -> Bytearray
|
||||||
May return bytearray with a smaller size if bytes have not arrived yet or have run out
|
May return bytearray with a smaller size if bytes have not arrived yet or have run out
|
||||||
|
May throw error if descriptor isn't readable
|
||||||
write(descriptor: int, data: Bytearray)
|
write(descriptor: int, data: Bytearray)
|
||||||
flush(descriptor: int)
|
May throw error if descriptor isn't writeable
|
||||||
|
[optional] seek(descriptor: int, mode: string, offset: int)
|
||||||
|
Mode may be 'b' (relative begin), 'c' (relative current), 'e' (relative end + 1)
|
||||||
|
[optional] tell(descriptor: int) -> int
|
||||||
|
[optional] flush(descriptor: int)
|
||||||
|
[optional] available(descriptor: int) -> int
|
||||||
is_alive(descriptor: int) -> bool
|
is_alive(descriptor: int) -> bool
|
||||||
close(descriptor: int)
|
close(descriptor: int)
|
||||||
--]]
|
--]]
|
||||||
|
|
||||||
function io_stream.new(descriptor, binaryMode, ioLib, mode, flushMode)
|
function io_stream.new(descriptor, binary_mode, io_lib, mode, flush_mode)
|
||||||
mode = mode or DEFAULT_MODE
|
mode = mode or DEFAULT_MODE
|
||||||
flushMode = flushMode or FLUSH_MODE_ALL
|
flush_mode = flush_mode or FLUSH_MODE_ALL
|
||||||
|
|
||||||
local self = setmetatable({}, io_stream)
|
local self = setmetatable({}, io_stream)
|
||||||
|
|
||||||
self.descriptor = descriptor
|
self.descriptor = descriptor
|
||||||
self.binaryMode = binaryMode
|
self.binary_mode = binary_mode
|
||||||
self.maxBufferSize = MAX_BUFFER_SIZE
|
self.max_buffer_size = MAX_BUFFER_SIZE
|
||||||
self.ioLib = ioLib
|
self.io_lib = io_lib
|
||||||
|
|
||||||
self:set_mode(mode)
|
self:set_mode(mode)
|
||||||
self:set_flush_mode(flushMode)
|
self:set_flush_mode(flush_mode)
|
||||||
|
|
||||||
return self
|
return self
|
||||||
end
|
end
|
||||||
|
|
||||||
function io_stream:is_binary_mode()
|
function io_stream:is_binary_mode()
|
||||||
return self.binaryMode
|
return self.binary_mode
|
||||||
end
|
end
|
||||||
|
|
||||||
function io_stream:set_binary_mode(binaryMode)
|
function io_stream:set_binary_mode(binary_mode)
|
||||||
self.binaryMode = binaryMode ~= nil
|
self.binary_mode = binary_mode ~= nil
|
||||||
end
|
end
|
||||||
|
|
||||||
function io_stream:get_mode()
|
function io_stream:get_mode()
|
||||||
|
|
@ -88,124 +93,141 @@ function io_stream:set_mode(mode)
|
||||||
error("invalid stream mode: "..mode)
|
error("invalid stream mode: "..mode)
|
||||||
end
|
end
|
||||||
|
|
||||||
if self.mode == BUFFERED_MODE then
|
if self.write_buffer then
|
||||||
self.writeBuffer:clear()
|
self.write_buffer:clear()
|
||||||
self.readBuffer:clear()
|
self.write_buffer = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
if mode == BUFFERED_MODE and not self.writeBuffer then
|
if self.read_buffer then
|
||||||
self.writeBuffer = Bytearray()
|
self.read_buffer:clear()
|
||||||
self.readBuffer = Bytearray()
|
self.read_buffer = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
if mode == BUFFERED_MODE then
|
||||||
|
self.write_buffer = Bytearray(self.max_buffer_size)
|
||||||
|
self.read_buffer = Bytearray(self.max_buffer_size)
|
||||||
end
|
end
|
||||||
|
|
||||||
self.mode = mode
|
self.mode = mode
|
||||||
end
|
end
|
||||||
|
|
||||||
function io_stream:get_flush_mode()
|
function io_stream:get_flush_mode()
|
||||||
return self.flushMode
|
return self.flush_mode
|
||||||
end
|
end
|
||||||
|
|
||||||
function io_stream:set_flush_mode(flushMode)
|
function io_stream:set_flush_mode(flush_mode)
|
||||||
if not table.has(ALL_FLUSH_MODES, flushMode) then
|
if not table.has(ALL_FLUSH_MODES, flush_mode) then
|
||||||
error("invalid flush mode: "..flushMode)
|
error("invalid flush mode: " .. flush_mode)
|
||||||
end
|
end
|
||||||
|
|
||||||
self.flushMode = flushMode
|
self.flush_mode = flush_mode
|
||||||
end
|
end
|
||||||
|
|
||||||
function io_stream:get_max_buffer_size()
|
function io_stream:get_max_buffer_size()
|
||||||
return self.maxBufferSize
|
return self.max_buffer_size
|
||||||
end
|
end
|
||||||
|
|
||||||
function io_stream:set_max_buffer_size(maxBufferSize)
|
function io_stream:set_max_buffer_size(max_buffer_size)
|
||||||
self.maxBufferSize = maxBufferSize
|
self.max_buffer_size = max_buffer_size
|
||||||
|
|
||||||
|
self.write_buffer = Bytearray(self.max_buffer_size)
|
||||||
|
self.read_buffer = Bytearray(self.max_buffer_size)
|
||||||
end
|
end
|
||||||
|
|
||||||
function io_stream:available(length)
|
function io_stream:available(length)
|
||||||
|
local available = self.io_lib.available and self.io_lib.available(self.descriptor) or 0
|
||||||
|
|
||||||
if self.mode == BUFFERED_MODE then
|
if self.mode == BUFFERED_MODE then
|
||||||
self:__update_read_buffer()
|
available = available + #self.read_buffer
|
||||||
|
end
|
||||||
|
|
||||||
if not length then
|
if not length then
|
||||||
return #self.readBuffer
|
return available
|
||||||
else
|
else
|
||||||
return #self.readBuffer >= length
|
return available >= length
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function io_stream:__update_read_buffer()
|
function io_stream:__read(length, from_read_fully)
|
||||||
local readed = Bytearray()
|
|
||||||
|
|
||||||
readFully(readed, function(length) return self.ioLib.read(self.descriptor, length) end)
|
|
||||||
|
|
||||||
self.readBuffer:append(readed)
|
|
||||||
|
|
||||||
if #self.readBuffer > self.maxBufferSize then
|
|
||||||
error "buffer overflow"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function io_stream:__read(length)
|
|
||||||
if self.mode == YIELD_MODE then
|
if self.mode == YIELD_MODE then
|
||||||
|
if from_read_fully then
|
||||||
|
return self.io_lib.read(self.descriptor, length)
|
||||||
|
end
|
||||||
|
|
||||||
local buffer = Bytearray()
|
local buffer = Bytearray()
|
||||||
|
|
||||||
while #buffer < length do
|
while #buffer < length do
|
||||||
buffer:append(self.ioLib.read(self.descriptor, length - #buffer))
|
buffer:append(self.io_lib.read(self.descriptor, length - #buffer))
|
||||||
|
|
||||||
if #buffer < length then coroutine.yield() end
|
if #buffer < length then coroutine.yield() end
|
||||||
end
|
end
|
||||||
|
|
||||||
return buffer
|
return buffer
|
||||||
elseif self.mode == BUFFERED_MODE then
|
elseif self.mode == BUFFERED_MODE then
|
||||||
self:__update_read_buffer()
|
local buf_len = #self.read_buffer
|
||||||
|
|
||||||
if #self.readBuffer < length then
|
if buf_len < length then
|
||||||
error "buffer underflow"
|
self.read_buffer:append(
|
||||||
|
self.io_lib.read(self.descriptor, self.max_buffer_size - buf_len)
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
local copy
|
buf_len = #self.read_buffer
|
||||||
|
|
||||||
if #self.readBuffer == length then
|
length = math.min(buf_len, length)
|
||||||
copy = Bytearray()
|
|
||||||
|
|
||||||
copy:append(self.readBuffer)
|
|
||||||
|
|
||||||
self.readBuffer:clear()
|
local copy = self.read_buffer:slice(1, length)
|
||||||
|
|
||||||
|
if buf_len == length then
|
||||||
|
self.read_buffer:clear()
|
||||||
else
|
else
|
||||||
copy = Bytearray()
|
self.read_buffer:remove(1, length)
|
||||||
|
|
||||||
for i = 1, length do
|
|
||||||
copy[i] = self.readBuffer[i]
|
|
||||||
end
|
|
||||||
|
|
||||||
self.readBuffer:remove(1, length)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return copy
|
return copy
|
||||||
elseif self.mode == DEFAULT_MODE then
|
elseif self.mode == DEFAULT_MODE then
|
||||||
return self.ioLib.read(self.descriptor, length)
|
return self.io_lib.read(self.descriptor, length)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function io_stream:__write(data)
|
function io_stream:__write(data)
|
||||||
if self.mode == BUFFERED_MODE then
|
if self.mode == BUFFERED_MODE then
|
||||||
self.writeBuffer:append(data)
|
local data_length = #data
|
||||||
|
|
||||||
if #self.writeBuffer > self.maxBufferSize then
|
if #self.write_buffer + data_length > self.max_buffer_size then
|
||||||
error "buffer overflow"
|
self:flush()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if data_length > self.max_buffer_size then
|
||||||
|
local to_write = math.floor(data_length / self.max_buffer_size) * self.max_buffer_size
|
||||||
|
local to_save = data_length - to_write
|
||||||
|
|
||||||
|
self.io_lib.write(self.descriptor, data:slice(1, to_write))
|
||||||
|
|
||||||
|
self:flush()
|
||||||
|
|
||||||
|
self.write_buffer = data:slice(to_write + 1, to_save)
|
||||||
|
else self.write_buffer:append(data) end
|
||||||
elseif self.mode == DEFAULT_MODE or self.mode == YIELD_MODE then
|
elseif self.mode == DEFAULT_MODE or self.mode == YIELD_MODE then
|
||||||
return self.ioLib.write(self.descriptor, data)
|
return self.io_lib.write(self.descriptor, data)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function io_stream:read_fully(useTable)
|
function io_stream:read_fully(use_table)
|
||||||
if self.binaryMode then
|
if self.binary_mode then
|
||||||
local result = useTable and Bytearray() or { }
|
local result = use_table and { } or Bytearray()
|
||||||
|
|
||||||
readFully(result, function() return self:__read(self.maxBufferSize) end)
|
local avail = self:available()
|
||||||
|
|
||||||
|
if avail == 0 then
|
||||||
|
avail = self.max_buffer_size
|
||||||
|
end
|
||||||
|
|
||||||
|
read_fully(result, function() return self:__read(avail, true) end)
|
||||||
|
|
||||||
|
return result
|
||||||
else
|
else
|
||||||
if useTable then
|
if use_table then
|
||||||
local lines = { }
|
local lines = { }
|
||||||
|
|
||||||
local line
|
local line
|
||||||
|
|
@ -220,7 +242,7 @@ function io_stream:read_fully(useTable)
|
||||||
else
|
else
|
||||||
local result = Bytearray()
|
local result = Bytearray()
|
||||||
|
|
||||||
readFully(result, function() return self:__read(self.maxBufferSize) end)
|
read_fully(result, function() return self:__read(self.max_buffer_size) end)
|
||||||
|
|
||||||
return utf8.tostring(result)
|
return utf8.tostring(result)
|
||||||
end
|
end
|
||||||
|
|
@ -262,57 +284,61 @@ function io_stream:write_line(str)
|
||||||
self:__write(utf8.tobytes(str .. "\n"))
|
self:__write(utf8.tobytes(str .. "\n"))
|
||||||
end
|
end
|
||||||
|
|
||||||
function io_stream:read(arg, useTable)
|
function io_stream:read(arg, use_table)
|
||||||
local argType = type(arg)
|
local arg_type = type(arg)
|
||||||
|
|
||||||
if self.binaryMode then
|
if self.binary_mode then
|
||||||
local byteArr
|
local byte_arr
|
||||||
|
|
||||||
if argType == "number" then
|
if arg_type == "number" then
|
||||||
-- using 'arg' as length
|
-- using 'arg' as length
|
||||||
|
|
||||||
byteArr = self:__read(arg)
|
byte_arr = self:__read(arg)
|
||||||
|
|
||||||
if useTable == true then
|
if use_table == true then
|
||||||
local t = { }
|
local t = { }
|
||||||
|
|
||||||
for i = 1, #byteArr do
|
for i = 1, #byte_arr do
|
||||||
t[i] = byteArr[i]
|
t[i] = byte_arr[i]
|
||||||
end
|
end
|
||||||
|
|
||||||
return t
|
return t
|
||||||
else
|
else
|
||||||
return byteArr
|
return byte_arr
|
||||||
end
|
end
|
||||||
elseif argType == "string" then
|
elseif arg_type == "string" then
|
||||||
return byteutil.unpack(
|
return byteutil.unpack(
|
||||||
arg,
|
arg,
|
||||||
self:__read(byteutil.get_size(arg))
|
self:__read(byteutil.get_size(arg))
|
||||||
)
|
)
|
||||||
elseif argType == nil then
|
elseif arg_type == "nil" then
|
||||||
error(
|
error(
|
||||||
"in binary mode the first argument must be a string data format"..
|
"in binary mode the first argument must be a string data format"..
|
||||||
" for the library \"byteutil\" or the number of bytes to read"
|
" for the library \"byteutil\" or the number of bytes to read"
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
error("unknown argument type: "..argType)
|
error("unknown argument type: "..arg_type)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
if not arg then
|
if not arg then
|
||||||
return self:read_line()
|
return self:read_line()
|
||||||
else
|
else
|
||||||
local linesCount = arg
|
local lines_count = arg
|
||||||
local trimLastEmptyLines = useTable or true
|
local trim_last_empty_lines = use_table
|
||||||
|
|
||||||
if linesCount < 0 then error "count of lines to read must be positive" end
|
if use_table == nil then
|
||||||
|
trim_last_empty_lines = true
|
||||||
|
end
|
||||||
|
|
||||||
|
if lines_count < 0 then error "count of lines to read must be positive" end
|
||||||
|
|
||||||
local result = { }
|
local result = { }
|
||||||
|
|
||||||
for i = 1, linesCount do
|
for i = 1, lines_count do
|
||||||
result[i] = self:read_line()
|
result[i] = self:read_line()
|
||||||
end
|
end
|
||||||
|
|
||||||
if trimLastEmptyLines then
|
if trim_last_empty_lines then
|
||||||
local i = #result
|
local i = #result
|
||||||
|
|
||||||
while i >= 0 do
|
while i >= 0 do
|
||||||
|
|
@ -340,41 +366,53 @@ function io_stream:read(arg, useTable)
|
||||||
end
|
end
|
||||||
|
|
||||||
function io_stream:write(arg, ...)
|
function io_stream:write(arg, ...)
|
||||||
local argType = type(arg)
|
local arg_type = type(arg)
|
||||||
|
|
||||||
if self.binaryMode then
|
if self.binary_mode then
|
||||||
local byteArr
|
local byte_arr
|
||||||
|
|
||||||
if argType ~= "string" then
|
if arg_type ~= "string" then
|
||||||
-- using arg as bytes table/bytearray
|
-- using arg as bytes table/bytearray
|
||||||
|
|
||||||
if argType == "table" then
|
if arg_type == "table" then
|
||||||
byteArr = Bytearray(arg)
|
byte_arr = Bytearray(arg)
|
||||||
else
|
else
|
||||||
byteArr = arg
|
byte_arr = arg
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
byteArr = byteutil.pack(arg, ...)
|
byte_arr = byteutil.pack(arg, ...)
|
||||||
end
|
end
|
||||||
|
|
||||||
self:__write(byteArr)
|
self:__write(byte_arr)
|
||||||
else
|
else
|
||||||
if argType == "string" then
|
if arg_type == "string" then
|
||||||
self:write_line(arg)
|
self:write_line(arg)
|
||||||
elseif argType == "table" then
|
elseif arg_type == "table" then
|
||||||
for i = 1, #arg do
|
for i = 1, #arg do
|
||||||
self:write_line(arg[i])
|
self:write_line(arg[i])
|
||||||
end
|
end
|
||||||
else error("unknown argument type: "..argType) end
|
else error("unknown argument type: "..arg_type) end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function io_stream:seek(mode, offset)
|
function io_stream:seek(mode, offset)
|
||||||
self.ioLib.seek(self.descriptor, mode, offset)
|
if not self.io_lib.seek then
|
||||||
|
error("cannot seek this stream")
|
||||||
|
end
|
||||||
|
|
||||||
|
self.io_lib.seek(self.descriptor, mode, offset)
|
||||||
|
end
|
||||||
|
|
||||||
|
function io_stream:tell()
|
||||||
|
if not self.io_lib.tell then
|
||||||
|
error("cannot tell this stream")
|
||||||
|
end
|
||||||
|
|
||||||
|
return self.io_lib.tell(self.descriptor)
|
||||||
end
|
end
|
||||||
|
|
||||||
function io_stream:is_alive()
|
function io_stream:is_alive()
|
||||||
return self.ioLib.is_alive(self.descriptor)
|
return self.io_lib.is_alive(self.descriptor)
|
||||||
end
|
end
|
||||||
|
|
||||||
function io_stream:is_closed()
|
function io_stream:is_closed()
|
||||||
|
|
@ -383,20 +421,24 @@ end
|
||||||
|
|
||||||
function io_stream:close()
|
function io_stream:close()
|
||||||
if self.mode == BUFFERED_MODE then
|
if self.mode == BUFFERED_MODE then
|
||||||
self.readBuffer:clear()
|
self.read_buffer:clear()
|
||||||
self.writeBuffer:clear()
|
self.write_buffer:clear()
|
||||||
end
|
end
|
||||||
|
|
||||||
return self.ioLib.close(self.descriptor)
|
return self.io_lib.close(self.descriptor)
|
||||||
end
|
end
|
||||||
|
|
||||||
function io_stream:flush()
|
function io_stream:flush()
|
||||||
if self.mode == BUFFERED_MODE and #self.writeBuffer > 0 then
|
if self.mode == BUFFERED_MODE and #self.write_buffer > 0 then
|
||||||
self.ioLib.write(self.descriptor, self.writeBuffer)
|
self.io_lib.write(self.descriptor, self.write_buffer)
|
||||||
self.writeBuffer:clear()
|
self.write_buffer:clear()
|
||||||
end
|
end
|
||||||
|
|
||||||
if self.flushMode ~= FLUSH_MODE_ONLY_BUFFER then self.ioLib.flush(self.descriptor) end
|
if self.flush_mode ~= FLUSH_MODE_ONLY_BUFFER then
|
||||||
|
if self.io_lib.flush then
|
||||||
|
self.io_lib.flush(self.descriptor)
|
||||||
|
elseif self:is_closed() then error("stream is closed") end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
return io_stream
|
return io_stream
|
||||||
|
|
@ -3,5 +3,6 @@ name = "default"
|
||||||
base_packs = ["base"]
|
base_packs = ["base"]
|
||||||
permissions = [
|
permissions = [
|
||||||
"network",
|
"network",
|
||||||
"record-audio"
|
"record-audio",
|
||||||
|
"sub-instances",
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,7 @@ local Socket = {__index={
|
||||||
end
|
end
|
||||||
return self:recv(length, usetable)
|
return self:recv(length, usetable)
|
||||||
end,
|
end,
|
||||||
|
as_stream=network.__as_stream,
|
||||||
close=function(self) return network.__close(self.id) end,
|
close=function(self) return network.__close(self.id) end,
|
||||||
available=function(self) return network.__available(self.id) or 0 end,
|
available=function(self) return network.__available(self.id) or 0 end,
|
||||||
is_alive=function(self) return network.__is_alive(self.id) end,
|
is_alive=function(self) return network.__is_alive(self.id) end,
|
||||||
|
|
|
||||||
|
|
@ -140,7 +140,7 @@ console.add_command(
|
||||||
for y=y1,y2 do
|
for y=y1,y2 do
|
||||||
for z=z1,z2 do
|
for z=z1,z2 do
|
||||||
for x=x1,x2 do
|
for x=x1,x2 do
|
||||||
block.set(x, y, z, id)
|
block.set(x, y, z, id, 0, true)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -127,6 +127,10 @@ require "core:internal/extensions/inventory"
|
||||||
asserts = require "core:internal/asserts"
|
asserts = require "core:internal/asserts"
|
||||||
events = require "core:internal/events"
|
events = require "core:internal/events"
|
||||||
|
|
||||||
|
if test then
|
||||||
|
require "core:internal/test"
|
||||||
|
end
|
||||||
|
|
||||||
function pack.unload(prefix)
|
function pack.unload(prefix)
|
||||||
events.remove_by_prefix(prefix)
|
events.remove_by_prefix(prefix)
|
||||||
end
|
end
|
||||||
|
|
@ -136,7 +140,20 @@ local __vc_named_coroutines = {}
|
||||||
local __vc_next_coroutine = 1
|
local __vc_next_coroutine = 1
|
||||||
|
|
||||||
function __vc_start_coroutine(chunk)
|
function __vc_start_coroutine(chunk)
|
||||||
local co = coroutine.create(chunk)
|
local co = coroutine.create(function()
|
||||||
|
local _, err = xpcall(chunk, function(msg)
|
||||||
|
local traceback = debug.get_traceback(0)
|
||||||
|
local s = string.format("%s:", msg)
|
||||||
|
for i=1,#traceback - 2 do
|
||||||
|
local frame = traceback[i]
|
||||||
|
s = s .. "\n\t"..tb_frame_tostring(frame)
|
||||||
|
end
|
||||||
|
return s
|
||||||
|
end)
|
||||||
|
if err then
|
||||||
|
error(err)
|
||||||
|
end
|
||||||
|
end)
|
||||||
local id = __vc_next_coroutine
|
local id = __vc_next_coroutine
|
||||||
__vc_next_coroutine = __vc_next_coroutine + 1
|
__vc_next_coroutine = __vc_next_coroutine + 1
|
||||||
__vc_coroutines[id] = co
|
__vc_coroutines[id] = co
|
||||||
|
|
@ -334,6 +351,10 @@ else
|
||||||
os.pid = ffi.C.getpid()
|
os.pid = ffi.C.getpid()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
require("core:io_stream").wrap_bytearray = require "core:internal/stream_providers/bytearray"
|
||||||
|
|
||||||
|
network.__as_stream = require "core:internal/stream_providers/socket"
|
||||||
|
|
||||||
math.randomseed(time.uptime() * 1536227939)
|
math.randomseed(time.uptime() * 1536227939)
|
||||||
|
|
||||||
rules = require "core:internal/rules"
|
rules = require "core:internal/rules"
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,9 @@ vc = {
|
||||||
is_client = function()
|
is_client = function()
|
||||||
return not _vc_headless
|
return not _vc_headless
|
||||||
end,
|
end,
|
||||||
|
get_version = __vc_app.get_version,
|
||||||
get_setting = __vc_app.get_setting,
|
get_setting = __vc_app.get_setting,
|
||||||
get_setting_str = __vc_app.get_setting_str,
|
str_setting = __vc_app.str_setting,
|
||||||
get_setting_info = __vc_app.get_setting_info,
|
get_setting_info = __vc_app.get_setting_info,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -173,6 +173,12 @@ void audio::initialize(
|
||||||
logger.info() << "initializing NoAudio backend";
|
logger.info() << "initializing NoAudio backend";
|
||||||
backend = NoAudio::create().release();
|
backend = NoAudio::create().release();
|
||||||
}
|
}
|
||||||
|
if (settings.inputDevice.get() == "auto") {
|
||||||
|
auto inputDevices = audio::get_input_devices_names();
|
||||||
|
if (!inputDevices.empty()) {
|
||||||
|
settings.inputDevice.set(inputDevices.at(0));
|
||||||
|
}
|
||||||
|
}
|
||||||
struct {
|
struct {
|
||||||
std::string name;
|
std::string name;
|
||||||
NumberSetting* setting;
|
NumberSetting* setting;
|
||||||
|
|
@ -210,7 +216,9 @@ void audio::initialize(
|
||||||
::input_device = backend->openInputDevice(
|
::input_device = backend->openInputDevice(
|
||||||
settings.inputDevice.get(), 44100, 1, 16
|
settings.inputDevice.get(), 44100, 1, 16
|
||||||
);
|
);
|
||||||
::input_device->startCapture();
|
if (::input_device != nullptr) {
|
||||||
|
::input_device->startCapture();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (::input_device) {
|
if (::input_device) {
|
||||||
::input_device->stopCapture();
|
::input_device->stopCapture();
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ namespace {
|
||||||
public:
|
public:
|
||||||
FTFontFile(FT_Face face, util::Buffer<ubyte> buffer)
|
FTFontFile(FT_Face face, util::Buffer<ubyte> buffer)
|
||||||
: face(std::move(face)), buffer(std::move(buffer)) {
|
: face(std::move(face)), buffer(std::move(buffer)) {
|
||||||
|
monospace = this->face->face_flags & FT_FACE_FLAG_FIXED_WIDTH;
|
||||||
}
|
}
|
||||||
|
|
||||||
~FTFontFile() {
|
~FTFontFile() {
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,12 @@ namespace vector_fonts {
|
||||||
virtual std::unique_ptr<Texture> renderPage(
|
virtual std::unique_ptr<Texture> renderPage(
|
||||||
int pageid, std::vector<Glyph>& glyphs, int size
|
int pageid, std::vector<Glyph>& glyphs, int size
|
||||||
) = 0;
|
) = 0;
|
||||||
|
|
||||||
|
bool isMonospace() const {
|
||||||
|
return monospace;
|
||||||
|
}
|
||||||
|
protected:
|
||||||
|
bool monospace = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
void initialize();
|
void initialize();
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,9 @@ inline constexpr bool ENGINE_DEBUG_BUILD = true;
|
||||||
|
|
||||||
inline const std::string ENGINE_VERSION_STRING = "0.32";
|
inline const std::string ENGINE_VERSION_STRING = "0.32";
|
||||||
|
|
||||||
|
inline constexpr uint MAX_SUBPROCESSES = 1;
|
||||||
|
inline constexpr uint MAX_SUBPROCESS_DEPTH = 2;
|
||||||
|
|
||||||
/// @brief world regions format version
|
/// @brief world regions format version
|
||||||
inline constexpr uint REGION_FORMAT_VERSION = 3;
|
inline constexpr uint REGION_FORMAT_VERSION = 3;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -188,6 +188,7 @@ public:
|
||||||
ContentUnitDefs<GeneratorDef> generators;
|
ContentUnitDefs<GeneratorDef> generators;
|
||||||
std::unique_ptr<DrawGroups> const drawGroups;
|
std::unique_ptr<DrawGroups> const drawGroups;
|
||||||
ResourceIndicesSet resourceIndices {};
|
ResourceIndicesSet resourceIndices {};
|
||||||
|
std::set<std::string> components {};
|
||||||
|
|
||||||
Content(
|
Content(
|
||||||
std::unique_ptr<ContentIndices> indices,
|
std::unique_ptr<ContentIndices> indices,
|
||||||
|
|
|
||||||
|
|
@ -26,17 +26,21 @@ std::unique_ptr<Content> ContentBuilder::build() {
|
||||||
def.rt.tags.insert(tags.add(tag));
|
def.rt.tags.insert(tags.add(tag));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto isSolid = [](const Variant& variant) {
|
||||||
|
return variant.model.type == BlockModelType::BLOCK ||
|
||||||
|
variant.culling == CullingMode::OPTIONAL;
|
||||||
|
};
|
||||||
|
|
||||||
if (def.variants) {
|
if (def.variants) {
|
||||||
for (auto& variant : def.variants->variants) {
|
for (auto& variant : def.variants->variants) {
|
||||||
variant.rt.solid =
|
variant.rt.solid = isSolid(variant) || def.explictlySolid;
|
||||||
variant.model.type == BlockModelType::BLOCK ||
|
|
||||||
def.explictlySolid;
|
|
||||||
}
|
}
|
||||||
def.defaults = def.variants->variants.at(0);
|
def.defaults = def.variants->variants.at(0);
|
||||||
} else {
|
} else {
|
||||||
def.defaults.rt.solid =
|
def.defaults.rt.solid = isSolid(def.defaults) || def.explictlySolid;
|
||||||
def.defaults.model.type == BlockModelType::BLOCK ||
|
}
|
||||||
def.explictlySolid;
|
if (def.material.empty()) {
|
||||||
|
defaults.at("block-material").get(def.material);
|
||||||
}
|
}
|
||||||
|
|
||||||
const float EPSILON = 0.01f;
|
const float EPSILON = 0.01f;
|
||||||
|
|
|
||||||
|
|
@ -506,6 +506,7 @@ void ContentLoader::loadScripts(Content& content) {
|
||||||
scripting::on_scripts_loading();
|
scripting::on_scripts_loading();
|
||||||
load_scripts(content, content.blocks);
|
load_scripts(content, content.blocks);
|
||||||
load_scripts(content, content.items);
|
load_scripts(content, content.items);
|
||||||
|
auto& tmpContent = content;
|
||||||
|
|
||||||
for (const auto& [packid, runtime] : content.getPacks()) {
|
for (const auto& [packid, runtime] : content.getPacks()) {
|
||||||
auto env = runtime->getEnvironment();
|
auto env = runtime->getEnvironment();
|
||||||
|
|
@ -516,7 +517,7 @@ void ContentLoader::loadScripts(Content& content) {
|
||||||
|
|
||||||
// Load entity components
|
// Load entity components
|
||||||
io::path componentsDir = folder / "scripts/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();
|
auto name = pack.id + ":" + file.stem();
|
||||||
scripting::load_entity_component(
|
scripting::load_entity_component(
|
||||||
env,
|
env,
|
||||||
|
|
@ -524,9 +525,21 @@ void ContentLoader::loadScripts(Content& content) {
|
||||||
file,
|
file,
|
||||||
pack.id + ":scripts/components/" + file.name()
|
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();
|
scripting::on_content_loaded();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -89,6 +89,7 @@ template<> void ContentUnitLoader<EntityDef>::loadUnit(
|
||||||
root.at("skeleton-name").get(def.skeletonName);
|
root.at("skeleton-name").get(def.skeletonName);
|
||||||
root.at("material").get(def.material);
|
root.at("material").get(def.material);
|
||||||
root.at("blocking").get(def.blocking);
|
root.at("blocking").get(def.blocking);
|
||||||
|
root.at("selectable").get(def.selectable);
|
||||||
root.at("solid").get(def.solid);
|
root.at("solid").get(def.solid);
|
||||||
root.at("mass").get(def.mass);
|
root.at("mass").get(def.mass);
|
||||||
root.at("elasticity").get(def.elasticity);
|
root.at("elasticity").get(def.elasticity);
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,10 @@
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
|
||||||
|
#ifdef VC_ENABLE_REFLECTION
|
||||||
|
#include "util/EnumMetadata.hpp"
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace util {
|
namespace util {
|
||||||
template<class T> class Buffer;
|
template<class T> class Buffer;
|
||||||
}
|
}
|
||||||
|
|
@ -70,6 +74,14 @@ namespace dv {
|
||||||
return ptr != nullptr;
|
return ptr != nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline value* operator->() noexcept {
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline const value* operator->() const noexcept {
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
inline value& operator*() noexcept {
|
inline value& operator*() noexcept {
|
||||||
return *ptr;
|
return *ptr;
|
||||||
}
|
}
|
||||||
|
|
@ -78,6 +90,11 @@ namespace dv {
|
||||||
return *ptr;
|
return *ptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef VC_ENABLE_REFLECTION
|
||||||
|
template <typename T>
|
||||||
|
bool get(T& dst, const util::EnumMetadata<T>& mt) const;
|
||||||
|
#endif
|
||||||
|
|
||||||
bool get(std::string& dst) const;
|
bool get(std::string& dst) const;
|
||||||
bool get(bool& dst) const;
|
bool get(bool& dst) const;
|
||||||
bool get(char& dst) const;
|
bool get(char& dst) const;
|
||||||
|
|
@ -552,6 +569,17 @@ namespace dv {
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef VC_ENABLE_REFLECTION
|
||||||
|
template <typename T>
|
||||||
|
inline bool optionalvalue::get(T& dst, const util::EnumMetadata<T>& mt) const {
|
||||||
|
if (ptr) {
|
||||||
|
return mt.getItem(ptr->asString(), dst);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
inline bool optionalvalue::get(std::string& dst) const {
|
inline bool optionalvalue::get(std::string& dst) const {
|
||||||
if (ptr) {
|
if (ptr) {
|
||||||
dst = ptr->asString();
|
dst = ptr->asString();
|
||||||
|
|
|
||||||
59
src/devtools/AppScriptsControl.cpp
Normal file
59
src/devtools/AppScriptsControl.cpp
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
#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) {
|
||||||
|
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();
|
||||||
|
}
|
||||||
29
src/devtools/AppScriptsControl.hpp
Normal file
29
src/devtools/AppScriptsControl.hpp
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
#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);
|
||||||
|
|
||||||
|
void tick();
|
||||||
|
void loadProjectClientScript();
|
||||||
|
void terminate(std::string_view reason);
|
||||||
|
|
||||||
|
void onScreenChange(const std::string& name, bool show);
|
||||||
|
|
||||||
|
bool isFinished() const;
|
||||||
|
private:
|
||||||
|
std::unique_ptr<scripting::IClientProjectScript> clientScript;
|
||||||
|
std::unique_ptr<Process> scriptCoroutine;
|
||||||
|
};
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
#include "data/dv_util.hpp"
|
#include "data/dv_util.hpp"
|
||||||
#include "debug/Logger.hpp"
|
#include "debug/Logger.hpp"
|
||||||
|
#include "engine/CoreParameters.hpp"
|
||||||
#include "io/io.hpp"
|
#include "io/io.hpp"
|
||||||
#include "io/path.hpp"
|
#include "io/path.hpp"
|
||||||
#include "logic/scripting/scripting.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 {
|
bool Permissions::has(const std::string& name) const {
|
||||||
return permissions.find(name) != permissions.end();
|
return permissions.find(name) != permissions.end();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,19 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "interfaces/Serializable.hpp"
|
||||||
|
#include "io/path.hpp"
|
||||||
|
|
||||||
#include <set>
|
#include <set>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
#include "interfaces/Process.hpp"
|
|
||||||
#include "interfaces/Serializable.hpp"
|
|
||||||
|
|
||||||
namespace scripting {
|
|
||||||
class IClientProjectScript;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Permissions {
|
struct Permissions {
|
||||||
static inline std::string DEBUGGING = "debugging";
|
static inline std::string DEBUGGING = "debugging";
|
||||||
static inline std::string NETWORK = "network";
|
static inline std::string NETWORK = "network";
|
||||||
static inline std::string RECORD_AUDIO = "record-audio";
|
static inline std::string RECORD_AUDIO = "record-audio";
|
||||||
static inline std::string WRITE_TO_USER = "write-to-user";
|
static inline std::string WRITE_TO_USER = "write-to-user";
|
||||||
|
static inline std::string SUB_INSTANCES = "sub-instances";
|
||||||
|
|
||||||
std::set<std::string> permissions;
|
std::set<std::string> permissions;
|
||||||
|
|
||||||
|
|
@ -26,16 +23,12 @@ struct Permissions {
|
||||||
struct Project : Serializable {
|
struct Project : Serializable {
|
||||||
std::string name;
|
std::string name;
|
||||||
std::string title;
|
std::string title;
|
||||||
|
io::path path;
|
||||||
std::vector<std::string> basePacks;
|
std::vector<std::string> basePacks;
|
||||||
std::unique_ptr<scripting::IClientProjectScript> clientScript;
|
|
||||||
std::unique_ptr<Process> setupCoroutine;
|
|
||||||
Permissions permissions;
|
Permissions permissions;
|
||||||
|
|
||||||
~Project();
|
~Project();
|
||||||
|
|
||||||
dv::value serialize() const override;
|
dv::value serialize() const override;
|
||||||
void deserialize(const dv::value& src) override;
|
void deserialize(const dv::value& src) override;
|
||||||
|
|
||||||
void loadProjectClientScript();
|
|
||||||
void loadProjectStartScript();
|
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,9 @@ struct CoreParameters {
|
||||||
std::filesystem::path userFolder = ".";
|
std::filesystem::path userFolder = ".";
|
||||||
std::filesystem::path scriptFile;
|
std::filesystem::path scriptFile;
|
||||||
std::filesystem::path projectFolder;
|
std::filesystem::path projectFolder;
|
||||||
|
std::filesystem::path logFile;
|
||||||
std::string debugServerString;
|
std::string debugServerString;
|
||||||
int tps = 20;
|
int tps = 20;
|
||||||
|
int subProcessDepth = 0;
|
||||||
std::unordered_map<std::string, std::string> projectArgs;
|
std::unordered_map<std::string, std::string> projectArgs;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@
|
||||||
#include "content/ContentControl.hpp"
|
#include "content/ContentControl.hpp"
|
||||||
#include "core_defs.hpp"
|
#include "core_defs.hpp"
|
||||||
#include "debug/Logger.hpp"
|
#include "debug/Logger.hpp"
|
||||||
|
#include "devtools/AppScriptsControl.hpp"
|
||||||
#include "devtools/DebuggingServer.hpp"
|
#include "devtools/DebuggingServer.hpp"
|
||||||
#include "devtools/Editor.hpp"
|
#include "devtools/Editor.hpp"
|
||||||
#include "devtools/Project.hpp"
|
#include "devtools/Project.hpp"
|
||||||
|
|
@ -191,10 +192,8 @@ void Engine::initialize(CoreParameters coreParameters) {
|
||||||
audio::set_input_device(name == "auto" ? "" : name);
|
audio::set_input_device(name == "auto" ? "" : name);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
project->loadProjectStartScript();
|
appScripts = std::make_unique<AppScriptsControl>(params);
|
||||||
if (!params.headless) {
|
|
||||||
project->loadProjectClientScript();
|
|
||||||
}
|
|
||||||
if (params.stdinCommands) {
|
if (params.stdinCommands) {
|
||||||
cmd::start_stdin_cmd_reader(*this);
|
cmd::start_stdin_cmd_reader(*this);
|
||||||
}
|
}
|
||||||
|
|
@ -263,9 +262,7 @@ void Engine::detachDebugger() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Engine::applicationTick() {
|
void Engine::applicationTick() {
|
||||||
if (project->setupCoroutine && project->setupCoroutine->isActive()) {
|
appScripts->tick();
|
||||||
project->setupCoroutine->update();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Engine::updateFrontend() {
|
void Engine::updateFrontend() {
|
||||||
|
|
@ -353,6 +350,7 @@ void Engine::close() {
|
||||||
network.reset();
|
network.reset();
|
||||||
clearKeepedObjects();
|
clearKeepedObjects();
|
||||||
project.reset();
|
project.reset();
|
||||||
|
appScripts.reset();
|
||||||
scripting::close();
|
scripting::close();
|
||||||
logger.info() << "scripting finished";
|
logger.info() << "scripting finished";
|
||||||
if (!params.headless) {
|
if (!params.headless) {
|
||||||
|
|
@ -382,13 +380,14 @@ void Engine::loadAssets() {
|
||||||
void Engine::loadProject() {
|
void Engine::loadProject() {
|
||||||
io::path projectFile = "project:project.toml";
|
io::path projectFile = "project:project.toml";
|
||||||
project = std::make_unique<Project>();
|
project = std::make_unique<Project>();
|
||||||
|
project->path = "project:";
|
||||||
project->deserialize(io::read_object(projectFile));
|
project->deserialize(io::read_object(projectFile));
|
||||||
logger.info() << "loaded project " << util::quote(project->name);
|
logger.info() << "loaded project " << util::quote(project->name);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Engine::setScreen(std::shared_ptr<Screen> screen) {
|
void Engine::setScreen(std::shared_ptr<Screen> screen) {
|
||||||
if (project->clientScript && this->screen) {
|
if (this->screen) {
|
||||||
project->clientScript->onScreenChange(this->screen->getName(), false);
|
appScripts->onScreenChange(this->screen->getName(), false);
|
||||||
}
|
}
|
||||||
// reset audio channels (stop all sources)
|
// reset audio channels (stop all sources)
|
||||||
audio::reset_channel(audio::get_channel_index("regular"));
|
audio::reset_channel(audio::get_channel_index("regular"));
|
||||||
|
|
@ -397,8 +396,8 @@ void Engine::setScreen(std::shared_ptr<Screen> screen) {
|
||||||
if (this->screen) {
|
if (this->screen) {
|
||||||
this->screen->onOpen();
|
this->screen->onOpen();
|
||||||
}
|
}
|
||||||
if (project->clientScript && this->screen) {
|
if (this->screen) {
|
||||||
project->clientScript->onScreenChange(this->screen->getName(), true);
|
appScripts->onScreenChange(this->screen->getName(), true);
|
||||||
window->setShouldRefresh();
|
window->setShouldRefresh();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -414,6 +413,7 @@ void Engine::onWorldClosed() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Engine::quit() {
|
void Engine::quit() {
|
||||||
|
logger.info() << "quitSignal set to true";
|
||||||
quitSignal = true;
|
quitSignal = true;
|
||||||
if (!isHeadless()) {
|
if (!isHeadless()) {
|
||||||
window->setShouldClose(true);
|
window->setShouldClose(true);
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@
|
||||||
class Assets;
|
class Assets;
|
||||||
class AssetsLoader;
|
class AssetsLoader;
|
||||||
class AssetsManagement;
|
class AssetsManagement;
|
||||||
|
class AppScriptsControl;
|
||||||
class ContentControl;
|
class ContentControl;
|
||||||
class EngineController;
|
class EngineController;
|
||||||
class EnginePaths;
|
class EnginePaths;
|
||||||
|
|
@ -55,6 +56,7 @@ class Engine : public util::ObjectsKeeper {
|
||||||
std::unique_ptr<Project> project;
|
std::unique_ptr<Project> project;
|
||||||
std::unique_ptr<SettingsHandler> settingsHandler;
|
std::unique_ptr<SettingsHandler> settingsHandler;
|
||||||
std::unique_ptr<AssetsManagement> assets;
|
std::unique_ptr<AssetsManagement> assets;
|
||||||
|
std::unique_ptr<AppScriptsControl> appScripts;
|
||||||
std::shared_ptr<Screen> screen;
|
std::shared_ptr<Screen> screen;
|
||||||
std::unique_ptr<ContentControl> content;
|
std::unique_ptr<ContentControl> content;
|
||||||
std::unique_ptr<EngineController> controller;
|
std::unique_ptr<EngineController> controller;
|
||||||
|
|
@ -184,5 +186,9 @@ public:
|
||||||
return debuggingServer.get();
|
return debuggingServer.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AppScriptsControl& getAppScripts() {
|
||||||
|
return *appScripts;
|
||||||
|
}
|
||||||
|
|
||||||
void detachDebugger();
|
void detachDebugger();
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,17 @@
|
||||||
#include "Mainloop.hpp"
|
#include "Mainloop.hpp"
|
||||||
|
|
||||||
#include "Engine.hpp"
|
|
||||||
#include "debug/Logger.hpp"
|
#include "debug/Logger.hpp"
|
||||||
|
#include "devtools/AppScriptsControl.hpp"
|
||||||
#include "devtools/Project.hpp"
|
#include "devtools/Project.hpp"
|
||||||
#include "frontend/screens/MenuScreen.hpp"
|
#include "Engine.hpp"
|
||||||
#include "frontend/screens/LevelScreen.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 "window/Window.hpp"
|
||||||
#include "world/Level.hpp"
|
#include "world/Level.hpp"
|
||||||
#include "graphics/ui/GUI.hpp"
|
|
||||||
#include "graphics/ui/elements/Container.hpp"
|
|
||||||
|
|
||||||
static debug::Logger logger("mainloop");
|
static debug::Logger logger("mainloop");
|
||||||
|
|
||||||
|
|
@ -19,6 +22,7 @@ void Mainloop::run() {
|
||||||
auto& time = engine.getTime();
|
auto& time = engine.getTime();
|
||||||
auto& window = engine.getWindow();
|
auto& window = engine.getWindow();
|
||||||
auto& settings = engine.getSettings();
|
auto& settings = engine.getSettings();
|
||||||
|
const auto& coreParams = engine.getCoreParameters();
|
||||||
|
|
||||||
engine.setLevelConsumer([this](auto level, int64_t localPlayer) {
|
engine.setLevelConsumer([this](auto level, int64_t localPlayer) {
|
||||||
if (level == nullptr) {
|
if (level == nullptr) {
|
||||||
|
|
@ -35,9 +39,11 @@ void Mainloop::run() {
|
||||||
|
|
||||||
logger.info() << "starting menu screen";
|
logger.info() << "starting menu screen";
|
||||||
engine.setScreen(std::make_shared<MenuScreen>(engine));
|
engine.setScreen(std::make_shared<MenuScreen>(engine));
|
||||||
|
|
||||||
|
auto& appScripts = engine.getAppScripts();
|
||||||
|
|
||||||
logger.info() << "main loop started";
|
logger.info() << "main loop started";
|
||||||
while (!window.isShouldClose()){
|
while (!window.isShouldClose() && !engine.isQuitSignal()) {
|
||||||
time.update(window.time());
|
time.update(window.time());
|
||||||
engine.applicationTick();
|
engine.applicationTick();
|
||||||
engine.updateFrontend();
|
engine.updateFrontend();
|
||||||
|
|
@ -50,6 +56,10 @@ void Mainloop::run() {
|
||||||
settings.display.adaptiveFpsInMenu.get() &&
|
settings.display.adaptiveFpsInMenu.get() &&
|
||||||
dynamic_cast<const MenuScreen*>(engine.getScreen().get()) != nullptr
|
dynamic_cast<const MenuScreen*>(engine.getScreen().get()) != nullptr
|
||||||
);
|
);
|
||||||
|
if (coreParams.testMode && appScripts.isFinished()) {
|
||||||
|
logger.info() << "test finished";
|
||||||
|
engine.quit();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
logger.info() << "main loop stopped";
|
logger.info() << "main loop stopped";
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,14 @@
|
||||||
#include "ServerMainloop.hpp"
|
#include "ServerMainloop.hpp"
|
||||||
|
|
||||||
#include "Engine.hpp"
|
#include "Engine.hpp"
|
||||||
#include "logic/scripting/scripting.hpp"
|
#include "devtools/AppScriptsControl.hpp"
|
||||||
#include "logic/LevelController.hpp"
|
#include "logic/LevelController.hpp"
|
||||||
#include "interfaces/Process.hpp"
|
#include "interfaces/Process.hpp"
|
||||||
#include "debug/Logger.hpp"
|
#include "debug/Logger.hpp"
|
||||||
#include "world/Level.hpp"
|
#include "world/Level.hpp"
|
||||||
#include "world/World.hpp"
|
#include "world/World.hpp"
|
||||||
#include "util/platform.hpp"
|
#include "util/platform.hpp"
|
||||||
|
#include "devtools/Project.hpp"
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
|
|
||||||
|
|
@ -32,21 +33,12 @@ void ServerMainloop::run() {
|
||||||
setLevel(std::move(level));
|
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 targetDelta = 1.0 / static_cast<double>(coreParams.tps);
|
||||||
double delta = targetDelta;
|
double delta = targetDelta;
|
||||||
auto begin = system_clock::now();
|
auto begin = system_clock::now();
|
||||||
auto startupTime = begin;
|
auto startupTime = begin;
|
||||||
|
|
||||||
while (process->isActive()) {
|
while (!engine.isQuitSignal() && !engine.getAppScripts().isFinished()) {
|
||||||
if (engine.isQuitSignal()) {
|
|
||||||
process->terminate();
|
|
||||||
logger.info() << "script has been terminated due to quit signal";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (coreParams.testMode) {
|
if (coreParams.testMode) {
|
||||||
time.step(delta);
|
time.step(delta);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -55,7 +47,6 @@ void ServerMainloop::run() {
|
||||||
duration_cast<microseconds>(now - startupTime).count() / 1e6);
|
duration_cast<microseconds>(now - startupTime).count() / 1e6);
|
||||||
delta = time.getDelta();
|
delta = time.getDelta();
|
||||||
}
|
}
|
||||||
process->update();
|
|
||||||
if (controller) {
|
if (controller) {
|
||||||
controller->getLevel()->getWorld()->updateTimers(delta);
|
controller->getLevel()->getWorld()->updateTimers(delta);
|
||||||
controller->update(glm::min(delta, 0.2), false);
|
controller->update(glm::min(delta, 0.2), false);
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ void ContentGfxCache::refreshVariant(
|
||||||
uint8_t variantIndex,
|
uint8_t variantIndex,
|
||||||
const Atlas& atlas
|
const Atlas& atlas
|
||||||
) {
|
) {
|
||||||
|
std::unordered_map<std::string, std::string> optionalTextures;
|
||||||
bool denseRender = settings.denseRender.get();
|
bool denseRender = settings.denseRender.get();
|
||||||
for (uint side = 0; side < 6; side++) {
|
for (uint side = 0; side < 6; side++) {
|
||||||
std::string tex = variant.textureFaces[side];
|
std::string tex = variant.textureFaces[side];
|
||||||
|
|
@ -43,6 +44,7 @@ void ContentGfxCache::refreshVariant(
|
||||||
texOpaque = tex;
|
texOpaque = tex;
|
||||||
} else if (variant.culling == CullingMode::OPTIONAL && !denseRender) {
|
} else if (variant.culling == CullingMode::OPTIONAL && !denseRender) {
|
||||||
tex = texOpaque;
|
tex = texOpaque;
|
||||||
|
optionalTextures[variant.textureFaces[side]] = texOpaque;
|
||||||
}
|
}
|
||||||
size_t index = getRegionIndex(def.rt.id, variantIndex, side, false);
|
size_t index = getRegionIndex(def.rt.id, variantIndex, side, false);
|
||||||
sideregions[index] = atlas.get(tex);
|
sideregions[index] = atlas.get(tex);
|
||||||
|
|
@ -56,7 +58,12 @@ void ContentGfxCache::refreshVariant(
|
||||||
if (pos == std::string::npos) {
|
if (pos == std::string::npos) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (auto region = atlas.getIf(mesh.texture.substr(pos+1))) {
|
std::string texture = mesh.texture.substr(pos+1);
|
||||||
|
const auto& found = optionalTextures.find(texture);
|
||||||
|
if (found != optionalTextures.end()) {
|
||||||
|
texture = found->second;
|
||||||
|
}
|
||||||
|
if (auto region = atlas.getIf(texture)) {
|
||||||
for (auto& vertex : mesh.vertices) {
|
for (auto& vertex : mesh.vertices) {
|
||||||
vertex.uv = region->apply(vertex.uv);
|
vertex.uv = region->apply(vertex.uv);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,9 @@
|
||||||
#include "graphics/ui/elements/InventoryView.hpp"
|
#include "graphics/ui/elements/InventoryView.hpp"
|
||||||
#include "graphics/ui/gui_xml.hpp"
|
#include "graphics/ui/gui_xml.hpp"
|
||||||
#include "logic/scripting/scripting.hpp"
|
#include "logic/scripting/scripting.hpp"
|
||||||
|
#include "debug/Logger.hpp"
|
||||||
|
|
||||||
|
static debug::Logger logger("ui-document");
|
||||||
|
|
||||||
UiDocument::UiDocument(
|
UiDocument::UiDocument(
|
||||||
std::string id,
|
std::string id,
|
||||||
|
|
@ -17,6 +20,19 @@ UiDocument::UiDocument(
|
||||||
rebuildIndices();
|
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() {
|
void UiDocument::rebuildIndices() {
|
||||||
map.clear();
|
map.clear();
|
||||||
gui::UINode::getIndices(root, map);
|
gui::UINode::getIndices(root, map);
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ struct UiDocScript {
|
||||||
bool onopen : 1;
|
bool onopen : 1;
|
||||||
bool onprogress : 1;
|
bool onprogress : 1;
|
||||||
bool onclose : 1;
|
bool onclose : 1;
|
||||||
|
bool ondestroy : 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
using UINodesMap = std::unordered_map<std::string, std::weak_ptr<gui::UINode>>;
|
using UINodesMap = std::unordered_map<std::string, std::weak_ptr<gui::UINode>>;
|
||||||
|
|
@ -35,6 +36,8 @@ public:
|
||||||
scriptenv env
|
scriptenv env
|
||||||
);
|
);
|
||||||
|
|
||||||
|
~UiDocument();
|
||||||
|
|
||||||
void rebuildIndices();
|
void rebuildIndices();
|
||||||
void pushIndices(const std::shared_ptr<gui::UINode>& node);
|
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]));
|
args.emplace_back(static_cast<integer_t>(blockPos[i]));
|
||||||
}
|
}
|
||||||
scripting::on_ui_open(
|
scripting::on_ui_open(
|
||||||
element.getDocument(),
|
*element.getDocument(),
|
||||||
std::move(args)
|
std::move(args)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -578,7 +578,7 @@ void Hud::onRemove(const HudElement& element) {
|
||||||
if (invview) {
|
if (invview) {
|
||||||
inventory = invview->getInventory().get();
|
inventory = invview->getInventory().get();
|
||||||
}
|
}
|
||||||
scripting::on_ui_close(document, inventory);
|
scripting::on_ui_close(*document, inventory);
|
||||||
if (invview) {
|
if (invview) {
|
||||||
invview->unbind();
|
invview->unbind();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -86,7 +86,7 @@ UiDocument* menus::show(
|
||||||
);
|
);
|
||||||
auto document = documentPtr.get();
|
auto document = documentPtr.get();
|
||||||
engine.getAssets()->store(std::move(documentPtr), fullname);
|
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->addPage(name, document->getRoot());
|
||||||
menu->setPage(name);
|
menu->setPage(name);
|
||||||
return document;
|
return document;
|
||||||
|
|
@ -99,13 +99,13 @@ void menus::show_process_panel(
|
||||||
|
|
||||||
auto menu = engine.getGUI().getMenu();
|
auto menu = engine.getGUI().getMenu();
|
||||||
menu->reset();
|
menu->reset();
|
||||||
auto doc =
|
auto document =
|
||||||
menus::show(engine, "process", {util::wstr2str_utf8(langs::get(text))});
|
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, [=]() {
|
->listenInterval(0.01f, [=]() {
|
||||||
task->update();
|
task->update();
|
||||||
|
|
||||||
uint tasksDone = task->getWorkDone();
|
uint tasksDone = task->getWorkDone();
|
||||||
scripting::on_ui_progress(doc, tasksDone, initialWork);
|
scripting::on_ui_progress(*document, tasksDone, initialWork);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
#include <GL/glew.h>
|
#include <GL/glew.h>
|
||||||
|
|
||||||
Cubemap::Cubemap(uint width, uint height, ImageFormat imageFormat)
|
Cubemap::Cubemap(uint width, uint height, ImageFormat imageFormat)
|
||||||
: Texture(0, width, height)
|
: Texture(0U, width, height, imageFormat)
|
||||||
{
|
{
|
||||||
glGenTextures(1, &id);
|
glGenTextures(1, &id);
|
||||||
glBindTexture(GL_TEXTURE_CUBE_MAP, id);
|
glBindTexture(GL_TEXTURE_CUBE_MAP, id);
|
||||||
|
|
@ -14,7 +14,6 @@ Cubemap::Cubemap(uint width, uint height, ImageFormat imageFormat)
|
||||||
glTexParameterf(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
glTexParameterf(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||||
glTexParameterf(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
|
glTexParameterf(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
|
||||||
|
|
||||||
uint format = gl::to_glenum(imageFormat);
|
|
||||||
for (uint face = 0; face < 6; face++) {
|
for (uint face = 0; face < 6; face++) {
|
||||||
glTexImage2D(
|
glTexImage2D(
|
||||||
GL_TEXTURE_CUBE_MAP_POSITIVE_X + face,
|
GL_TEXTURE_CUBE_MAP_POSITIVE_X + face,
|
||||||
|
|
@ -37,3 +36,23 @@ void Cubemap::bind() const {
|
||||||
void Cubemap::unbind() const {
|
void Cubemap::unbind() const {
|
||||||
glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
|
glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Cubemap::reload(const ubyte* data, uint width, uint height) {
|
||||||
|
this->width = width;
|
||||||
|
this->height = height;
|
||||||
|
glBindTexture(GL_TEXTURE_CUBE_MAP, id);
|
||||||
|
for (uint face = 0; face < 6; face++) {
|
||||||
|
glTexImage2D(
|
||||||
|
GL_TEXTURE_CUBE_MAP_POSITIVE_X + face,
|
||||||
|
0,
|
||||||
|
format,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
0,
|
||||||
|
format,
|
||||||
|
GL_UNSIGNED_BYTE,
|
||||||
|
data
|
||||||
|
);
|
||||||
|
}
|
||||||
|
glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,4 +9,5 @@ public:
|
||||||
|
|
||||||
virtual void bind() const override;
|
virtual void bind() const override;
|
||||||
virtual void unbind() const override;
|
virtual void unbind() const override;
|
||||||
|
virtual void reload(const ubyte* data, uint w, uint h) override;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -74,6 +74,18 @@ DrawContext::~DrawContext() {
|
||||||
if (lineWidth != parent->lineWidth) {
|
if (lineWidth != parent->lineWidth) {
|
||||||
glLineWidth(parent->lineWidth);
|
glLineWidth(parent->lineWidth);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < textures.size(); i++) {
|
||||||
|
auto texture = parent->textures[i];
|
||||||
|
if (textures[i] != texture) {
|
||||||
|
glActiveTexture(GL_TEXTURE0 + i);
|
||||||
|
if (texture == nullptr) {
|
||||||
|
glBindTexture(GL_TEXTURE_2D, 0);
|
||||||
|
} else {
|
||||||
|
texture->bind();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const glm::uvec2& DrawContext::getViewport() const {
|
const glm::uvec2& DrawContext::getViewport() const {
|
||||||
|
|
@ -154,3 +166,13 @@ void DrawContext::setLineWidth(float width) {
|
||||||
lineWidth = width;
|
lineWidth = width;
|
||||||
glLineWidth(width);
|
glLineWidth(width);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DrawContext::useTexture(int target, const Bindable* texture) {
|
||||||
|
glActiveTexture(GL_TEXTURE0 + target);
|
||||||
|
if (texture == nullptr) {
|
||||||
|
glBindTexture(GL_TEXTURE_2D, 0);
|
||||||
|
} else {
|
||||||
|
texture->bind();
|
||||||
|
}
|
||||||
|
textures.at(target) = texture;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <array>
|
||||||
#include <glm/vec2.hpp>
|
#include <glm/vec2.hpp>
|
||||||
#include <glm/vec4.hpp>
|
#include <glm/vec4.hpp>
|
||||||
|
|
||||||
|
|
@ -23,6 +24,7 @@ class DrawContext {
|
||||||
BlendMode blendMode = BlendMode::normal;
|
BlendMode blendMode = BlendMode::normal;
|
||||||
int scissorsCount = 0;
|
int scissorsCount = 0;
|
||||||
float lineWidth = 1.0f;
|
float lineWidth = 1.0f;
|
||||||
|
std::array<const Bindable*, advanced_pipeline::TARGETS_COUNT> textures {};
|
||||||
public:
|
public:
|
||||||
DrawContext(
|
DrawContext(
|
||||||
const DrawContext* parent,
|
const DrawContext* parent,
|
||||||
|
|
@ -44,4 +46,5 @@ public:
|
||||||
void setBlendMode(BlendMode mode);
|
void setBlendMode(BlendMode mode);
|
||||||
void setScissors(const glm::vec4& area);
|
void setScissors(const glm::vec4& area);
|
||||||
void setLineWidth(float width);
|
void setLineWidth(float width);
|
||||||
|
void useTexture(int target, const Bindable* texture);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,11 @@ Font::Font(
|
||||||
pages(std::move(pages)),
|
pages(std::move(pages)),
|
||||||
glyphs(std::move(glyphs)),
|
glyphs(std::move(glyphs)),
|
||||||
fontFile(std::move(fontFile)) {
|
fontFile(std::move(fontFile)) {
|
||||||
|
if (this->fontFile.has_value()) {
|
||||||
|
if (auto fontPtr = this->fontFile->lock()) {
|
||||||
|
monospace = fontPtr->isMonospace();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Font::~Font() = default;
|
Font::~Font() = default;
|
||||||
|
|
@ -34,6 +39,10 @@ int Font::getYOffset() const {
|
||||||
return yoffset;
|
return yoffset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Font::isMonospace() const {
|
||||||
|
return monospace;
|
||||||
|
}
|
||||||
|
|
||||||
int Font::getLineHeight() const {
|
int Font::getLineHeight() const {
|
||||||
return lineHeight;
|
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 {
|
int FontMetrics::calcWidth(std::wstring_view text, size_t offset, size_t length) const {
|
||||||
auto font = this->font.has_value() ? this->font->lock() : nullptr;
|
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;
|
return std::min(text.length() - offset, length) * _glyphInterval;
|
||||||
}
|
}
|
||||||
int totalWidth = 0;
|
int totalWidth = 0;
|
||||||
|
|
@ -153,6 +162,8 @@ static inline void draw_text(
|
||||||
const FontStylesScheme* styles,
|
const FontStylesScheme* styles,
|
||||||
size_t styleMapOffset
|
size_t styleMapOffset
|
||||||
) {
|
) {
|
||||||
|
bool is3d = std::is_same<Batch, Batch3D>();
|
||||||
|
|
||||||
static FontStylesScheme defStyles {{{}}, {0}};
|
static FontStylesScheme defStyles {{{}}, {0}};
|
||||||
|
|
||||||
if (styles == nullptr) {
|
if (styles == nullptr) {
|
||||||
|
|
@ -185,13 +196,26 @@ static inline void draw_text(
|
||||||
float advance = baseAdvance;
|
float advance = baseAdvance;
|
||||||
if (auto glyph = font.getGlyph(c)) {
|
if (auto glyph = font.getGlyph(c)) {
|
||||||
yOffset = glyph->yOffset;
|
yOffset = glyph->yOffset;
|
||||||
advance = glyph->xAdvance;
|
advance = glyph->xAdvance /
|
||||||
|
static_cast<float>(font.getLineHeight()) * 2.0f *
|
||||||
|
baseAdvance;
|
||||||
}
|
}
|
||||||
uint charpage = c >> 8;
|
uint charpage = c >> 8;
|
||||||
if (charpage == page){
|
if (charpage == page){
|
||||||
batch.texture(font.getPage(charpage));
|
batch.texture(font.getPage(charpage));
|
||||||
draw_glyph(
|
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){
|
else if (charpage > page && charpage < next){
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,7 @@ public:
|
||||||
);
|
);
|
||||||
~Font();
|
~Font();
|
||||||
|
|
||||||
|
bool isMonospace() const;
|
||||||
int getLineHeight() const;
|
int getLineHeight() const;
|
||||||
int getYOffset() const;
|
int getYOffset() const;
|
||||||
|
|
||||||
|
|
@ -94,6 +95,7 @@ private:
|
||||||
int lineHeight;
|
int lineHeight;
|
||||||
int yoffset;
|
int yoffset;
|
||||||
int glyphInterval;
|
int glyphInterval;
|
||||||
|
bool monospace = true;
|
||||||
std::vector<std::unique_ptr<Texture>> pages;
|
std::vector<std::unique_ptr<Texture>> pages;
|
||||||
std::vector<Glyph> glyphs;
|
std::vector<Glyph> glyphs;
|
||||||
std::optional<std::weak_ptr<vector_fonts::FontFile>> fontFile;
|
std::optional<std::weak_ptr<vector_fonts::FontFile>> fontFile;
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
#include "Framebuffer.hpp"
|
#include "Framebuffer.hpp"
|
||||||
|
|
||||||
#include <GL/glew.h>
|
#include <GL/glew.h>
|
||||||
|
|
||||||
#include "Texture.hpp"
|
#include "Texture.hpp"
|
||||||
#include "debug/Logger.hpp"
|
#include "debug/Logger.hpp"
|
||||||
|
|
||||||
static debug::Logger logger("gl-framebuffer");
|
static debug::Logger logger("gl-framebuffer");
|
||||||
|
|
||||||
Framebuffer::Framebuffer(uint fbo, uint depth, std::unique_ptr<Texture> texture)
|
Framebuffer::Framebuffer(uint fbo, uint depth, std::unique_ptr<Texture> texture)
|
||||||
: fbo(fbo), depth(depth), texture(std::move(texture))
|
: fbo(fbo), depth(depth), texture(std::move(texture)) {
|
||||||
{
|
|
||||||
if (this->texture) {
|
if (this->texture) {
|
||||||
width = this->texture->getWidth();
|
width = this->texture->getWidth();
|
||||||
height = this->texture->getHeight();
|
height = this->texture->getHeight();
|
||||||
|
|
@ -18,35 +18,55 @@ Framebuffer::Framebuffer(uint fbo, uint depth, std::unique_ptr<Texture> texture)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::unique_ptr<Texture> create_texture(int width, int height, int format) {
|
static std::unique_ptr<Texture> create_texture(
|
||||||
GLuint tex;
|
int width, int height, bool alpha
|
||||||
glGenTextures(1, &tex);
|
) {
|
||||||
glBindTexture(GL_TEXTURE_2D, tex);
|
GLenum glformat = alpha ? GL_RGBA : GL_RGB;
|
||||||
glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, nullptr);
|
|
||||||
|
GLuint textureid;
|
||||||
|
glGenTextures(1, &textureid);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, textureid);
|
||||||
|
glTexImage2D(
|
||||||
|
GL_TEXTURE_2D,
|
||||||
|
0,
|
||||||
|
glformat,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
0,
|
||||||
|
glformat,
|
||||||
|
GL_UNSIGNED_BYTE,
|
||||||
|
nullptr
|
||||||
|
);
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 0);
|
glFramebufferTexture2D(
|
||||||
return std::make_unique<Texture>(tex, width, height);
|
GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, textureid, 0
|
||||||
|
);
|
||||||
|
return std::make_unique<Texture>(
|
||||||
|
textureid,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
alpha ? ImageFormat::RGBA8888 : ImageFormat::RGB888
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Framebuffer::Framebuffer(uint width, uint height, bool alpha)
|
Framebuffer::Framebuffer(uint width, uint height, bool alpha)
|
||||||
: width(width), height(height)
|
: width(width), height(height) {
|
||||||
{
|
|
||||||
glGenFramebuffers(1, &fbo);
|
glGenFramebuffers(1, &fbo);
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
|
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
|
||||||
|
|
||||||
format = alpha ? GL_RGBA : GL_RGB;
|
|
||||||
|
|
||||||
// Setup color attachment (texture)
|
// Setup color attachment (texture)
|
||||||
texture = create_texture(width, height, format);
|
texture = create_texture(width, height, alpha);
|
||||||
|
|
||||||
// Setup depth attachment
|
// Setup depth attachment
|
||||||
glGenRenderbuffers(1, &depth);
|
glGenRenderbuffers(1, &depth);
|
||||||
glBindRenderbuffer(GL_RENDERBUFFER, depth);
|
glBindRenderbuffer(GL_RENDERBUFFER, depth);
|
||||||
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, width, height);
|
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, width, height);
|
||||||
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depth);
|
glFramebufferRenderbuffer(
|
||||||
|
GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depth
|
||||||
|
);
|
||||||
|
|
||||||
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
|
if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
|
||||||
logger.error() << "framebuffer is not complete!";
|
logger.error() << "framebuffer is not complete!";
|
||||||
|
|
@ -60,11 +80,15 @@ Framebuffer::~Framebuffer() {
|
||||||
glDeleteRenderbuffers(1, &depth);
|
glDeleteRenderbuffers(1, &depth);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Framebuffer::bind() {
|
void Framebuffer::setTexture(std::unique_ptr<Texture> texture) {
|
||||||
|
this->texture = std::move(texture);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Framebuffer::bind() const {
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
|
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Framebuffer::unbind() {
|
void Framebuffer::unbind() const {
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -81,7 +105,7 @@ void Framebuffer::resize(uint width, uint height) {
|
||||||
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, width, height);
|
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT24, width, height);
|
||||||
glBindRenderbuffer(GL_RENDERBUFFER, 0);
|
glBindRenderbuffer(GL_RENDERBUFFER, 0);
|
||||||
|
|
||||||
texture = create_texture(width, height, format);
|
texture->resize(width, height);
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -102,5 +126,5 @@ uint Framebuffer::getHeight() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
uint Framebuffer::getFBO() const {
|
uint Framebuffer::getFBO() const {
|
||||||
return fbo;
|
return fbo;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,18 +12,19 @@ class Framebuffer : public Bindable {
|
||||||
uint depth;
|
uint depth;
|
||||||
uint width;
|
uint width;
|
||||||
uint height;
|
uint height;
|
||||||
uint format;
|
|
||||||
std::shared_ptr<Texture> texture;
|
std::shared_ptr<Texture> texture;
|
||||||
public:
|
public:
|
||||||
Framebuffer(uint fbo, uint depth, std::unique_ptr<Texture> texture);
|
Framebuffer(uint fbo, uint depth, std::unique_ptr<Texture> texture);
|
||||||
Framebuffer(uint width, uint height, bool alpha=false);
|
Framebuffer(uint width, uint height, bool alpha=false);
|
||||||
~Framebuffer();
|
~Framebuffer();
|
||||||
|
|
||||||
|
void setTexture(std::unique_ptr<Texture> texture);
|
||||||
|
|
||||||
/// @brief Use framebuffer
|
/// @brief Use framebuffer
|
||||||
void bind() override;
|
void bind() const override;
|
||||||
|
|
||||||
/// @brief Stop using framebuffer
|
/// @brief Stop using framebuffer
|
||||||
void unbind() override;
|
void unbind() const override;
|
||||||
|
|
||||||
/// @brief Update framebuffer texture size
|
/// @brief Update framebuffer texture size
|
||||||
/// @param width new width
|
/// @param width new width
|
||||||
|
|
|
||||||
|
|
@ -108,7 +108,7 @@ GBuffer::~GBuffer() {
|
||||||
glDeleteFramebuffers(1, &ssaoFbo);
|
glDeleteFramebuffers(1, &ssaoFbo);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GBuffer::bind() {
|
void GBuffer::bind() const {
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
|
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
|
||||||
glClear(GL_COLOR_BUFFER_BIT);
|
glClear(GL_COLOR_BUFFER_BIT);
|
||||||
}
|
}
|
||||||
|
|
@ -117,7 +117,7 @@ void GBuffer::bindSSAO() const {
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, ssaoFbo);
|
glBindFramebuffer(GL_FRAMEBUFFER, ssaoFbo);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GBuffer::unbind() {
|
void GBuffer::unbind() const {
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,9 +9,9 @@ public:
|
||||||
GBuffer(uint width, uint height);
|
GBuffer(uint width, uint height);
|
||||||
~GBuffer() override;
|
~GBuffer() override;
|
||||||
|
|
||||||
void bind() override;
|
void bind() const override;
|
||||||
void bindSSAO() const;
|
void bindSSAO() const;
|
||||||
void unbind() override;
|
void unbind() const override;
|
||||||
|
|
||||||
void bindBuffers() const;
|
void bindBuffers() const;
|
||||||
void bindSSAOBuffer() const;
|
void bindSSAOBuffer() const;
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,8 @@
|
||||||
|
|
||||||
uint Texture::MAX_RESOLUTION = 1024; // Window.initialize overrides it
|
uint Texture::MAX_RESOLUTION = 1024; // Window.initialize overrides it
|
||||||
|
|
||||||
Texture::Texture(uint id, uint width, uint height)
|
Texture::Texture(uint id, uint width, uint height, ImageFormat imageFormat)
|
||||||
: id(id), width(width), height(height) {
|
: id(id), width(width), height(height), format(gl::to_glenum(imageFormat)) {
|
||||||
}
|
}
|
||||||
|
|
||||||
Texture::Texture(const ubyte* data, uint width, uint height, ImageFormat imageFormat)
|
Texture::Texture(const ubyte* data, uint width, uint height, ImageFormat imageFormat)
|
||||||
|
|
@ -17,7 +17,7 @@ Texture::Texture(const ubyte* data, uint width, uint height, ImageFormat imageFo
|
||||||
glBindTexture(GL_TEXTURE_2D, id);
|
glBindTexture(GL_TEXTURE_2D, id);
|
||||||
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
|
||||||
|
|
||||||
GLenum format = gl::to_glenum(imageFormat);
|
format = gl::to_glenum(imageFormat);
|
||||||
glTexImage2D(
|
glTexImage2D(
|
||||||
GL_TEXTURE_2D, 0, format, width, height, 0,
|
GL_TEXTURE_2D, 0, format, width, height, 0,
|
||||||
format, GL_UNSIGNED_BYTE, static_cast<const GLvoid*>(data)
|
format, GL_UNSIGNED_BYTE, static_cast<const GLvoid*>(data)
|
||||||
|
|
@ -41,23 +41,27 @@ void Texture::unbind() const {
|
||||||
glBindTexture(GL_TEXTURE_2D, 0);
|
glBindTexture(GL_TEXTURE_2D, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Texture::resize(uint width, uint height) {
|
||||||
|
reload(nullptr, width, height);
|
||||||
|
}
|
||||||
|
|
||||||
void Texture::reload(const ImageData& image) {
|
void Texture::reload(const ImageData& image) {
|
||||||
width = image.getWidth();
|
|
||||||
height = image.getHeight();
|
|
||||||
reload(image.getData(), width, height);
|
reload(image.getData(), width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Texture::reload(const ubyte* data, uint width, uint height) {
|
void Texture::reload(const ubyte* data, uint width, uint height) {
|
||||||
|
this->width = width;
|
||||||
|
this->height = height;
|
||||||
glBindTexture(GL_TEXTURE_2D, id);
|
glBindTexture(GL_TEXTURE_2D, id);
|
||||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0,
|
glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0,
|
||||||
GL_RGBA, GL_UNSIGNED_BYTE, static_cast<const GLvoid*>(data));
|
format, GL_UNSIGNED_BYTE, static_cast<const GLvoid*>(data));
|
||||||
glGenerateMipmap(GL_TEXTURE_2D);
|
glGenerateMipmap(GL_TEXTURE_2D);
|
||||||
glBindTexture(GL_TEXTURE_2D, 0);
|
glBindTexture(GL_TEXTURE_2D, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Texture::reloadPartial(const ImageData& image, uint x, uint y, uint w, uint h) {
|
void Texture::reloadPartial(const ImageData& image, uint x, uint y, uint w, uint h) {
|
||||||
glBindTexture(GL_TEXTURE_2D, id);
|
glBindTexture(GL_TEXTURE_2D, id);
|
||||||
glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, w, h, GL_RGBA, GL_UNSIGNED_BYTE, image.getData());
|
glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, w, h, format, GL_UNSIGNED_BYTE, image.getData());
|
||||||
glGenerateMipmap(GL_TEXTURE_2D);
|
glGenerateMipmap(GL_TEXTURE_2D);
|
||||||
glBindTexture(GL_TEXTURE_2D, 0);
|
glBindTexture(GL_TEXTURE_2D, 0);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,29 +1,32 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "typedefs.hpp"
|
|
||||||
#include "maths/UVRegion.hpp"
|
|
||||||
#include "ImageData.hpp"
|
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
class Texture {
|
#include "ImageData.hpp"
|
||||||
|
#include "commons.hpp"
|
||||||
|
#include "maths/UVRegion.hpp"
|
||||||
|
#include "typedefs.hpp"
|
||||||
|
|
||||||
|
class Texture : public Bindable {
|
||||||
protected:
|
protected:
|
||||||
uint id;
|
uint id;
|
||||||
uint width;
|
uint width;
|
||||||
uint height;
|
uint height;
|
||||||
|
uint format;
|
||||||
public:
|
public:
|
||||||
Texture(uint id, uint width, uint height);
|
Texture(uint id, uint width, uint height, ImageFormat imageFormat);
|
||||||
Texture(const ubyte* data, uint width, uint height, ImageFormat format);
|
Texture(const ubyte* data, uint width, uint height, ImageFormat format);
|
||||||
virtual ~Texture();
|
virtual ~Texture();
|
||||||
|
|
||||||
virtual void bind() const;
|
virtual void bind() const override;
|
||||||
virtual void unbind() const;
|
virtual void unbind() const override;
|
||||||
void reload(const ubyte* data, uint w, uint h);
|
virtual void reload(const ubyte* data, uint w, uint h);
|
||||||
void reloadPartial(const ImageData& image, uint x, uint y, uint w, uint h);
|
void reloadPartial(const ImageData& image, uint x, uint y, uint w, uint h);
|
||||||
|
|
||||||
void setNearestFilter();
|
void setNearestFilter();
|
||||||
|
|
||||||
void reload(const ImageData& image);
|
void reload(const ImageData& image);
|
||||||
|
virtual void resize(uint w, uint h);
|
||||||
|
|
||||||
void setMipMapping(bool flag, bool pixelated);
|
void setMipMapping(bool flag, bool pixelated);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,8 @@ namespace advanced_pipeline {
|
||||||
inline constexpr int TARGET_SSAO = 5;
|
inline constexpr int TARGET_SSAO = 5;
|
||||||
inline constexpr int TARGET_SHADOWS0 = 6;
|
inline constexpr int TARGET_SHADOWS0 = 6;
|
||||||
inline constexpr int TARGET_SHADOWS1 = 7;
|
inline constexpr int TARGET_SHADOWS1 = 7;
|
||||||
|
|
||||||
|
inline constexpr int TARGETS_COUNT = 8;
|
||||||
}
|
}
|
||||||
|
|
||||||
VC_ENUM_METADATA(CursorShape)
|
VC_ENUM_METADATA(CursorShape)
|
||||||
|
|
@ -85,6 +87,6 @@ class Bindable {
|
||||||
public:
|
public:
|
||||||
virtual ~Bindable() = default;
|
virtual ~Bindable() = default;
|
||||||
|
|
||||||
virtual void bind() = 0;
|
virtual void bind() const = 0;
|
||||||
virtual void unbind() = 0;
|
virtual void unbind() const = 0;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -538,7 +538,6 @@ void BlocksRenderer::render(
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case BlockModelType::CUSTOM: {
|
case BlockModelType::CUSTOM: {
|
||||||
if (!denseRender)
|
|
||||||
blockCustomModel(
|
blockCustomModel(
|
||||||
{x, y, z},
|
{x, y, z},
|
||||||
def,
|
def,
|
||||||
|
|
|
||||||
|
|
@ -34,4 +34,7 @@ void HandsRenderer::render(const Camera& camera) {
|
||||||
glm::vec3(),
|
glm::vec3(),
|
||||||
glm::vec3(1.0f)
|
glm::vec3(1.0f)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
modelBatch.render();
|
||||||
|
modelBatch.setLightsOffset(glm::vec3());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,91 +1,64 @@
|
||||||
#include "Skybox.hpp"
|
#include "Skybox.hpp"
|
||||||
#include "assets/Assets.hpp"
|
|
||||||
#include "graphics/core/Shader.hpp"
|
#include <GL/glew.h>
|
||||||
#include "graphics/core/Mesh.hpp"
|
|
||||||
#include "graphics/core/Batch3D.hpp"
|
|
||||||
#include "graphics/core/Texture.hpp"
|
|
||||||
#include "graphics/core/Cubemap.hpp"
|
|
||||||
#include "graphics/core/Framebuffer.hpp"
|
|
||||||
#include "graphics/core/DrawContext.hpp"
|
|
||||||
#include "window/Window.hpp"
|
|
||||||
#include "window/Camera.hpp"
|
|
||||||
#include "maths/UVRegion.hpp"
|
|
||||||
|
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <iostream>
|
|
||||||
#include <GL/glew.h>
|
|
||||||
#include <glm/glm.hpp>
|
#include <glm/glm.hpp>
|
||||||
#include <glm/gtc/matrix_transform.hpp>
|
|
||||||
#include <glm/gtc/constants.hpp>
|
#include <glm/gtc/constants.hpp>
|
||||||
|
#include <glm/gtc/matrix_transform.hpp>
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
|
#include "assets/Assets.hpp"
|
||||||
|
#include "graphics/core/Batch3D.hpp"
|
||||||
|
#include "graphics/core/Cubemap.hpp"
|
||||||
|
#include "graphics/core/DrawContext.hpp"
|
||||||
|
#include "graphics/core/Framebuffer.hpp"
|
||||||
|
#include "graphics/core/Mesh.hpp"
|
||||||
|
#include "graphics/core/Shader.hpp"
|
||||||
|
#include "graphics/core/Texture.hpp"
|
||||||
|
#include "maths/UVRegion.hpp"
|
||||||
|
#include "window/Camera.hpp"
|
||||||
|
#include "window/Window.hpp"
|
||||||
|
|
||||||
using namespace advanced_pipeline;
|
using namespace advanced_pipeline;
|
||||||
|
|
||||||
const int STARS_COUNT = 3000;
|
const int STARS_COUNT = 3000;
|
||||||
const int STARS_SEED = 632;
|
const int STARS_SEED = 632;
|
||||||
|
|
||||||
Skybox::Skybox(uint size, Shader& shader)
|
Skybox::Skybox(uint size, const Assets& assets)
|
||||||
: size(size),
|
: size(size),
|
||||||
shader(shader),
|
assets(assets),
|
||||||
batch3d(std::make_unique<Batch3D>(4096))
|
shader(assets.require<Shader>("skybox_gen")),
|
||||||
{
|
batch3d(std::make_unique<Batch3D>(4096)) {
|
||||||
auto cubemap = std::make_unique<Cubemap>(size, size, ImageFormat::RGB888);
|
|
||||||
|
|
||||||
uint fboid;
|
SkyboxVertex vertices[] {
|
||||||
glGenFramebuffers(1, &fboid);
|
|
||||||
fbo = std::make_unique<Framebuffer>(fboid, 0, std::move(cubemap));
|
|
||||||
|
|
||||||
SkyboxVertex vertices[]{
|
|
||||||
{{-1.0f, -1.0f}},
|
{{-1.0f, -1.0f}},
|
||||||
{{-1.0f, 1.0f}},
|
{{-1.0f, 1.0f}},
|
||||||
{{1.0f, 1.0f}},
|
{{1.0f, 1.0f}},
|
||||||
{{-1.0f, -1.0f}},
|
{{-1.0f, -1.0f}},
|
||||||
{{1.0f, 1.0f}},
|
{{1.0f, 1.0f}},
|
||||||
{{1.0f, -1.0f}}
|
{{1.0f, -1.0f}}};
|
||||||
};
|
|
||||||
|
|
||||||
mesh = std::make_unique<Mesh<SkyboxVertex>>(vertices, 6);
|
mesh = std::make_unique<Mesh<SkyboxVertex>>(vertices, 6);
|
||||||
|
setMode(mode);
|
||||||
sprites.push_back(SkySprite {
|
|
||||||
"misc/moon",
|
|
||||||
glm::pi<float>() * 0.5f,
|
|
||||||
4.0f,
|
|
||||||
false,
|
|
||||||
glm::pi<float>() * 0.25f,
|
|
||||||
});
|
|
||||||
|
|
||||||
sprites.push_back(SkySprite {
|
|
||||||
"misc/moon_flare",
|
|
||||||
glm::pi<float>() * 0.5f,
|
|
||||||
0.5f,
|
|
||||||
false,
|
|
||||||
glm::pi<float>() * 0.25f,
|
|
||||||
});
|
|
||||||
|
|
||||||
sprites.push_back(SkySprite {
|
|
||||||
"misc/sun",
|
|
||||||
glm::pi<float>() * 1.5f,
|
|
||||||
4.0f,
|
|
||||||
true,
|
|
||||||
glm::pi<float>() * 0.25f,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Skybox::~Skybox() = default;
|
Skybox::~Skybox() = default;
|
||||||
|
|
||||||
void Skybox::drawBackground(
|
void Skybox::drawBackground(const Camera& camera, int width, int height) {
|
||||||
const Camera& camera, const Assets& assets, int width, int height
|
|
||||||
) {
|
|
||||||
auto backShader = assets.get<Shader>("background");
|
auto backShader = assets.get<Shader>("background");
|
||||||
backShader->use();
|
backShader->use();
|
||||||
backShader->uniformMatrix("u_view", camera.getView(false));
|
backShader->uniformMatrix("u_view", camera.getView(false));
|
||||||
backShader->uniform1f(
|
backShader->uniform1f(
|
||||||
"u_zoom", camera.zoom * camera.getFov() / glm::half_pi<float>()
|
"u_zoom", camera.zoom * camera.getFov() / glm::half_pi<float>()
|
||||||
);
|
);
|
||||||
backShader->uniform1f("u_ar", float(width)/float(height));
|
backShader->uniform1f("u_ar", float(width) / float(height));
|
||||||
backShader->uniform1i("u_skybox", 1);
|
backShader->uniform1i("u_skybox", 1);
|
||||||
bind();
|
|
||||||
|
auto texture = fbo->getTexture();
|
||||||
|
texture->bind();
|
||||||
mesh->draw();
|
mesh->draw();
|
||||||
unbind();
|
texture->unbind();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Skybox::drawStars(float angle, float opacity) {
|
void Skybox::drawStars(float angle, float opacity) {
|
||||||
|
|
@ -93,9 +66,7 @@ void Skybox::drawStars(float angle, float opacity) {
|
||||||
random.setSeed(STARS_SEED);
|
random.setSeed(STARS_SEED);
|
||||||
|
|
||||||
glm::mat4 rotation = glm::rotate(
|
glm::mat4 rotation = glm::rotate(
|
||||||
glm::mat4(1.0f),
|
glm::mat4(1.0f), -angle + glm::pi<float>() * 0.5f, glm::vec3(0, 0, -1)
|
||||||
-angle + glm::pi<float>() * 0.5f,
|
|
||||||
glm::vec3(0, 0, -1)
|
|
||||||
);
|
);
|
||||||
rotation = glm::rotate(rotation, sunAltitude, glm::vec3(1, 0, 0));
|
rotation = glm::rotate(rotation, sunAltitude, glm::vec3(1, 0, 0));
|
||||||
|
|
||||||
|
|
@ -108,41 +79,21 @@ void Skybox::drawStars(float angle, float opacity) {
|
||||||
glm::vec3 pos = glm::vec4(rx, ry, rz, 1) * rotation;
|
glm::vec3 pos = glm::vec4(rx, ry, rz, 1) * rotation;
|
||||||
|
|
||||||
float sopacity = random.randFloat();
|
float sopacity = random.randFloat();
|
||||||
if (pos.y < 0.0f)
|
if (pos.y < 0.0f) continue;
|
||||||
continue;
|
|
||||||
|
|
||||||
sopacity *= (0.2f + std::sqrt(std::cos(angle)) * 0.5f) - 0.05f;
|
sopacity *= (0.2f + std::sqrt(std::cos(angle)) * 0.5f) - 0.05f;
|
||||||
glm::vec4 tint (1,1,1, sopacity * opacity);
|
glm::vec4 tint(1, 1, 1, sopacity * opacity);
|
||||||
batch3d->point(pos * depth, tint);
|
batch3d->point(pos * depth, tint);
|
||||||
}
|
}
|
||||||
batch3d->flushPoints();
|
batch3d->flushPoints();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Skybox::draw(
|
void Skybox::drawSkySprites(
|
||||||
const DrawContext& pctx,
|
|
||||||
const Camera& camera,
|
|
||||||
const Assets& assets,
|
|
||||||
float daytime,
|
float daytime,
|
||||||
float fog)
|
float angle,
|
||||||
{
|
float opacity,
|
||||||
const glm::uvec2& viewport = pctx.getViewport();
|
const std::vector<SkySprite>& sprites
|
||||||
|
) {
|
||||||
glActiveTexture(GL_TEXTURE0);
|
|
||||||
|
|
||||||
drawBackground(camera, assets, viewport.x, viewport.y);
|
|
||||||
|
|
||||||
DrawContext ctx = pctx.sub();
|
|
||||||
ctx.setBlendMode(BlendMode::addition);
|
|
||||||
|
|
||||||
auto shader = assets.get<Shader>("ui3d");
|
|
||||||
shader->use();
|
|
||||||
shader->uniformMatrix("u_projview", camera.getProjView(false));
|
|
||||||
shader->uniformMatrix("u_apply", glm::mat4(1.0f));
|
|
||||||
batch3d->begin();
|
|
||||||
|
|
||||||
float angle = daytime * glm::pi<float>() * 2.0f;
|
|
||||||
float opacity = glm::pow(1.0f - fog, 7.0f);
|
|
||||||
|
|
||||||
float depthScale = 2e3;
|
float depthScale = 2e3;
|
||||||
for (auto& sprite : sprites) {
|
for (auto& sprite : sprites) {
|
||||||
batch3d->texture(assets.get<Texture>(sprite.texture));
|
batch3d->texture(assets.get<Texture>(sprite.texture));
|
||||||
|
|
@ -159,39 +110,124 @@ void Skybox::draw(
|
||||||
glm::vec3 pos = glm::vec4(0, distance, 0, 1) * rotation;
|
glm::vec3 pos = glm::vec4(0, distance, 0, 1) * rotation;
|
||||||
glm::vec3 up = glm::vec4(depthScale, 0, 0, 1) * rotation;
|
glm::vec3 up = glm::vec4(depthScale, 0, 0, 1) * rotation;
|
||||||
glm::vec3 right = glm::vec4(0, 0, depthScale, 1) * rotation;
|
glm::vec3 right = glm::vec4(0, 0, depthScale, 1) * rotation;
|
||||||
glm::vec4 tint (1,1,1, opacity);
|
glm::vec4 tint(1, 1, 1, opacity);
|
||||||
if (!sprite.emissive) {
|
if (!sprite.emissive) {
|
||||||
tint *= 0.6f + std::cos(angle)*0.4;
|
tint *= 0.6f + std::cos(angle) * 0.4;
|
||||||
}
|
}
|
||||||
batch3d->sprite(pos, right,
|
batch3d->sprite(pos, right, up, 1, 1, UVRegion(), tint);
|
||||||
up, 1, 1, UVRegion(), tint);
|
|
||||||
}
|
}
|
||||||
batch3d->flush();
|
batch3d->flush();
|
||||||
drawStars(angle, opacity);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Skybox::refresh(const DrawContext& pctx, float t, float mie, const glm::vec3& tint, const glm::vec3& hightlight, uint quality) {
|
void Skybox::draw(
|
||||||
|
const Environment& environment,
|
||||||
|
const DrawContext& pctx,
|
||||||
|
const Camera& camera,
|
||||||
|
float daytime,
|
||||||
|
float fog
|
||||||
|
) {
|
||||||
|
const glm::uvec2& viewport = pctx.getViewport();
|
||||||
|
drawBackground(camera, viewport.x, viewport.y);
|
||||||
|
|
||||||
|
float angle = daytime * glm::pi<float>() * 2.0f;
|
||||||
|
float opacity = glm::pow(1.0f - fog, 7.0f);
|
||||||
|
|
||||||
|
DrawContext ctx = pctx.sub();
|
||||||
|
ctx.setBlendMode(BlendMode::addition);
|
||||||
|
|
||||||
|
if (environment.sky.sprites.empty() && !environment.sky.stars) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto shader = assets.get<Shader>("ui3d");
|
||||||
|
shader->use();
|
||||||
|
shader->uniformMatrix("u_projview", camera.getProjView(false));
|
||||||
|
shader->uniformMatrix("u_apply", glm::mat4(1.0f));
|
||||||
|
batch3d->begin();
|
||||||
|
|
||||||
|
if (!environment.sky.sprites.empty()) {
|
||||||
|
drawSkySprites(daytime, angle, opacity, environment.sky.sprites);
|
||||||
|
}
|
||||||
|
if (environment.sky.stars) {
|
||||||
|
drawStars(angle, opacity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Skybox::setMode(SkyMode mode) {
|
||||||
|
if (this->mode == mode && fbo) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this->mode = mode;
|
||||||
|
|
||||||
|
uint size = mode == SkyMode::BOX ? this->size : 1U;
|
||||||
|
prevT = std::numeric_limits<float>().min();
|
||||||
|
|
||||||
|
uint fboid;
|
||||||
|
glGenFramebuffers(1, &fboid);
|
||||||
|
fbo = std::make_unique<Framebuffer>(
|
||||||
|
fboid,
|
||||||
|
false,
|
||||||
|
std::make_unique<Cubemap>(size, size, ImageFormat::RGB888)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Skybox::refresh(
|
||||||
|
const Environment& environment,
|
||||||
|
const DrawContext& pctx,
|
||||||
|
float dayTime,
|
||||||
|
float mie,
|
||||||
|
const glm::vec3& tint,
|
||||||
|
const glm::vec3& hightlight,
|
||||||
|
uint quality
|
||||||
|
) {
|
||||||
|
setMode(environment.sky.mode);
|
||||||
|
|
||||||
|
if (mode == SkyMode::NONE) {
|
||||||
|
return;
|
||||||
|
} else if (mode == SkyMode::SOLID) {
|
||||||
|
DrawContext ctx = pctx.sub();
|
||||||
|
ctx.setDepthMask(false);
|
||||||
|
ctx.setDepthTest(false);
|
||||||
|
ctx.setFramebuffer(fbo.get());
|
||||||
|
ctx.setViewport({fbo->getWidth(), fbo->getHeight()});
|
||||||
|
ctx.useTexture(advanced_pipeline::TARGET_SKYBOX, fbo->getTexture());
|
||||||
|
ctx.useTexture(advanced_pipeline::TARGET_COLOR, nullptr);
|
||||||
|
|
||||||
|
auto cubemap = dynamic_cast<Cubemap*>(fbo->getTexture());
|
||||||
|
assert(cubemap != nullptr);
|
||||||
|
|
||||||
|
for (int face = 0; face < 6; face++) {
|
||||||
|
glFramebufferTexture2D(
|
||||||
|
GL_FRAMEBUFFER,
|
||||||
|
GL_COLOR_ATTACHMENT0,
|
||||||
|
GL_TEXTURE_CUBE_MAP_POSITIVE_X + face,
|
||||||
|
cubemap->getId(),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
glClearColor(tint.r, tint.g, tint.b, 1);
|
||||||
|
glClear(GL_COLOR_BUFFER_BIT);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
frameid++;
|
frameid++;
|
||||||
float dayTime = t;
|
|
||||||
DrawContext ctx = pctx.sub();
|
DrawContext ctx = pctx.sub();
|
||||||
ctx.setDepthMask(false);
|
ctx.setDepthMask(false);
|
||||||
ctx.setDepthTest(false);
|
ctx.setDepthTest(false);
|
||||||
ctx.setFramebuffer(fbo.get());
|
ctx.setFramebuffer(fbo.get());
|
||||||
ctx.setViewport({size, size});
|
ctx.setViewport({fbo->getWidth(), fbo->getHeight()});
|
||||||
|
ctx.useTexture(advanced_pipeline::TARGET_SKYBOX, fbo->getTexture());
|
||||||
|
ctx.useTexture(advanced_pipeline::TARGET_COLOR, nullptr);
|
||||||
|
|
||||||
auto cubemap = dynamic_cast<Cubemap*>(fbo->getTexture());
|
auto cubemap = dynamic_cast<Cubemap*>(fbo->getTexture());
|
||||||
assert(cubemap != nullptr);
|
assert(cubemap != nullptr);
|
||||||
|
|
||||||
ready = true;
|
float t = dayTime * glm::two_pi<float>();
|
||||||
glActiveTexture(GL_TEXTURE0 + TARGET_SKYBOX);
|
|
||||||
cubemap->bind();
|
|
||||||
shader.use();
|
|
||||||
t *= glm::two_pi<float>();
|
|
||||||
|
|
||||||
lightDir = glm::normalize(glm::vec3(sin(t), -cos(t), 0.0f));
|
lightDir = glm::normalize(glm::vec3(sin(t), -cos(t), 0.0f));
|
||||||
|
|
||||||
float sunAngle = glm::radians((t / glm::two_pi<float>() - 0.25f) * 360.0f);
|
float sunAngle = glm::radians((dayTime - 0.25f) * 360.0f);
|
||||||
float x = -glm::cos(sunAngle + glm::pi<float>() * 0.5f) * glm::radians(sunAltitude);
|
float x = -glm::cos(sunAngle + glm::pi<float>() * 0.5f) *
|
||||||
|
glm::radians(sunAltitude);
|
||||||
float y = sunAngle - glm::pi<float>() * 0.5f;
|
float y = sunAngle - glm::pi<float>() * 0.5f;
|
||||||
float z = glm::radians(0.0f);
|
float z = glm::radians(0.0f);
|
||||||
rotation = glm::rotate(glm::mat4(1.0f), y, glm::vec3(0, 1, 0));
|
rotation = glm::rotate(glm::mat4(1.0f), y, glm::vec3(0, 1, 0));
|
||||||
|
|
@ -199,6 +235,7 @@ void Skybox::refresh(const DrawContext& pctx, float t, float mie, const glm::vec
|
||||||
rotation = glm::rotate(rotation, z, glm::vec3(0, 0, 1));
|
rotation = glm::rotate(rotation, z, glm::vec3(0, 0, 1));
|
||||||
lightDir = glm::vec3(rotation * glm::vec4(0, 0, -1, 1));
|
lightDir = glm::vec3(rotation * glm::vec4(0, 0, -1, 1));
|
||||||
|
|
||||||
|
shader.use();
|
||||||
shader.uniform1i("u_quality", quality);
|
shader.uniform1i("u_quality", quality);
|
||||||
shader.uniform1f("u_mie", mie);
|
shader.uniform1f("u_mie", mie);
|
||||||
shader.uniform3f("u_tint", tint);
|
shader.uniform3f("u_tint", tint);
|
||||||
|
|
@ -207,23 +244,22 @@ void Skybox::refresh(const DrawContext& pctx, float t, float mie, const glm::vec
|
||||||
shader.uniform3f("u_lightDir", lightDir);
|
shader.uniform3f("u_lightDir", lightDir);
|
||||||
shader.uniform1f("u_dayTime", dayTime);
|
shader.uniform1f("u_dayTime", dayTime);
|
||||||
|
|
||||||
if (glm::abs(mie-prevMie) + glm::abs(t-prevT) + glm::abs(prevHighlight.r - hightlight.r) >= 0.01) {
|
if (glm::abs(mie - prevMie) + glm::abs(t - prevT) +
|
||||||
|
glm::abs(prevHighlight.r - hightlight.r) >=
|
||||||
|
0.01) {
|
||||||
for (uint face = 0; face < 6; face++) {
|
for (uint face = 0; face < 6; face++) {
|
||||||
refreshFace(face, cubemap);
|
refreshFace(face, *cubemap);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
uint face = frameid % 6;
|
uint face = frameid % 6;
|
||||||
refreshFace(face, cubemap);
|
refreshFace(face, *cubemap);
|
||||||
}
|
}
|
||||||
prevMie = mie;
|
prevMie = mie;
|
||||||
prevT = t;
|
prevT = t;
|
||||||
prevHighlight = hightlight;
|
prevHighlight = hightlight;
|
||||||
|
|
||||||
cubemap->unbind();
|
|
||||||
glActiveTexture(GL_TEXTURE0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Skybox::refreshFace(uint face, Cubemap* cubemap) {
|
void Skybox::refreshFace(uint face, Cubemap& cubemap) {
|
||||||
const glm::vec3 xaxs[] = {
|
const glm::vec3 xaxs[] = {
|
||||||
{0.0f, 0.0f, -1.0f},
|
{0.0f, 0.0f, -1.0f},
|
||||||
{0.0f, 0.0f, 1.0f},
|
{0.0f, 0.0f, 1.0f},
|
||||||
|
|
@ -256,7 +292,7 @@ void Skybox::refreshFace(uint face, Cubemap* cubemap) {
|
||||||
GL_FRAMEBUFFER,
|
GL_FRAMEBUFFER,
|
||||||
GL_COLOR_ATTACHMENT0,
|
GL_COLOR_ATTACHMENT0,
|
||||||
GL_TEXTURE_CUBE_MAP_POSITIVE_X + face,
|
GL_TEXTURE_CUBE_MAP_POSITIVE_X + face,
|
||||||
cubemap->getId(),
|
cubemap.getId(),
|
||||||
0
|
0
|
||||||
);
|
);
|
||||||
shader.uniform3f("u_xaxis", xaxs[face]);
|
shader.uniform3f("u_xaxis", xaxs[face]);
|
||||||
|
|
@ -265,14 +301,6 @@ void Skybox::refreshFace(uint face, Cubemap* cubemap) {
|
||||||
mesh->draw();
|
mesh->draw();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Skybox::bind() const {
|
const Cubemap* Skybox::getCubemap() const {
|
||||||
glActiveTexture(GL_TEXTURE0 + TARGET_SKYBOX);
|
return dynamic_cast<const Cubemap*>(fbo->getTexture());
|
||||||
fbo->getTexture()->bind();
|
|
||||||
glActiveTexture(GL_TEXTURE0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Skybox::unbind() const {
|
|
||||||
glActiveTexture(GL_TEXTURE0 + TARGET_SKYBOX);
|
|
||||||
fbo->getTexture()->unbind();
|
|
||||||
glActiveTexture(GL_TEXTURE0);
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,17 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <glm/glm.hpp>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <glm/glm.hpp>
|
|
||||||
#include "typedefs.hpp"
|
|
||||||
#include "maths/fastmaths.hpp"
|
|
||||||
#include "graphics/core/MeshData.hpp"
|
|
||||||
|
|
||||||
template<typename VertexStructure> class Mesh;
|
#include "graphics/core/MeshData.hpp"
|
||||||
|
#include "maths/fastmaths.hpp"
|
||||||
|
#include "typedefs.hpp"
|
||||||
|
#include "world/Environment.hpp"
|
||||||
|
|
||||||
|
template <typename VertexStructure>
|
||||||
|
class Mesh;
|
||||||
class Shader;
|
class Shader;
|
||||||
class Assets;
|
class Assets;
|
||||||
class Camera;
|
class Camera;
|
||||||
|
|
@ -21,29 +24,20 @@ struct SkyboxVertex {
|
||||||
glm::vec2 position;
|
glm::vec2 position;
|
||||||
|
|
||||||
static constexpr VertexAttribute ATTRIBUTES[] {
|
static constexpr VertexAttribute ATTRIBUTES[] {
|
||||||
{VertexAttribute::Type::FLOAT, false, 2},
|
{VertexAttribute::Type::FLOAT, false, 2}, {{}, 0}};
|
||||||
{{}, 0}};
|
|
||||||
};
|
|
||||||
|
|
||||||
struct SkySprite {
|
|
||||||
std::string texture;
|
|
||||||
float phase;
|
|
||||||
float distance;
|
|
||||||
bool emissive;
|
|
||||||
float altitude;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class Skybox {
|
class Skybox {
|
||||||
std::unique_ptr<Framebuffer> fbo;
|
SkyMode mode = SkyMode::SOLID;
|
||||||
uint size;
|
uint size;
|
||||||
|
std::unique_ptr<Framebuffer> fbo;
|
||||||
|
const Assets& assets;
|
||||||
Shader& shader;
|
Shader& shader;
|
||||||
bool ready = false;
|
|
||||||
FastRandom random;
|
FastRandom random;
|
||||||
glm::vec3 lightDir;
|
glm::vec3 lightDir;
|
||||||
|
|
||||||
std::unique_ptr<Mesh<SkyboxVertex>> mesh;
|
std::unique_ptr<Mesh<SkyboxVertex>> mesh;
|
||||||
std::unique_ptr<Batch3D> batch3d;
|
std::unique_ptr<Batch3D> batch3d;
|
||||||
std::vector<SkySprite> sprites;
|
|
||||||
int frameid = 0;
|
int frameid = 0;
|
||||||
|
|
||||||
float prevMie = -1.0f;
|
float prevMie = -1.0f;
|
||||||
|
|
@ -53,30 +47,41 @@ class Skybox {
|
||||||
glm::mat4 rotation;
|
glm::mat4 rotation;
|
||||||
|
|
||||||
void drawStars(float angle, float opacity);
|
void drawStars(float angle, float opacity);
|
||||||
void drawBackground(
|
void drawBackground(const Camera& camera, int width, int height);
|
||||||
const Camera& camera, const Assets& assets, int width, int height
|
void drawSkySprites(
|
||||||
|
float daytime,
|
||||||
|
float angle,
|
||||||
|
float opacity,
|
||||||
|
const std::vector<SkySprite>& sprites
|
||||||
);
|
);
|
||||||
void refreshFace(uint face, Cubemap* cubemap);
|
void refreshFace(uint face, Cubemap& cubemap);
|
||||||
public:
|
public:
|
||||||
Skybox(uint size, Shader& shader);
|
Skybox(uint size, const Assets& assets);
|
||||||
~Skybox();
|
~Skybox();
|
||||||
|
|
||||||
|
void setMode(SkyMode mode);
|
||||||
|
|
||||||
void draw(
|
void draw(
|
||||||
const DrawContext& pctx,
|
const Environment& environment,
|
||||||
const Camera& camera,
|
const DrawContext& pctx,
|
||||||
const Assets& assets,
|
const Camera& camera,
|
||||||
float daytime,
|
float daytime,
|
||||||
float fog
|
float fog
|
||||||
);
|
);
|
||||||
|
|
||||||
void refresh(const DrawContext& pctx, float t, float mie, const glm::vec3& tint, const glm::vec3& hightlight, uint quality);
|
void refresh(
|
||||||
void bind() const;
|
const Environment& environment,
|
||||||
void unbind() const;
|
const DrawContext& pctx,
|
||||||
bool isReady() const {
|
float t,
|
||||||
return ready;
|
float mie,
|
||||||
}
|
const glm::vec3& tint,
|
||||||
|
const glm::vec3& hightlight,
|
||||||
|
uint quality
|
||||||
|
);
|
||||||
|
|
||||||
const glm::vec3 getLightDir() const {
|
const Cubemap* getCubemap() const;
|
||||||
|
|
||||||
|
const glm::vec3& getLightDir() const {
|
||||||
return lightDir;
|
return lightDir;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,10 @@ void TextsRenderer::renderNote(
|
||||||
}
|
}
|
||||||
opacity = preset.xrayOpacity;
|
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 xvec = note.getAxisX();
|
||||||
glm::vec3 yvec = note.getAxisY();
|
glm::vec3 yvec = note.getAxisY();
|
||||||
|
|
@ -76,7 +79,10 @@ void TextsRenderer::renderNote(
|
||||||
yvec = camera.up;
|
yvec = camera.up;
|
||||||
}
|
}
|
||||||
float scale =
|
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;
|
xvec *= 1.0f + scale;
|
||||||
yvec *= 1.0f + scale;
|
yvec *= 1.0f + scale;
|
||||||
}
|
}
|
||||||
|
|
@ -118,6 +124,7 @@ void TextsRenderer::renderNote(
|
||||||
}
|
}
|
||||||
auto color = preset.color;
|
auto color = preset.color;
|
||||||
batch.setColor(glm::vec4(color.r, color.g, color.b, color.a * opacity));
|
batch.setColor(glm::vec4(color.r, color.g, color.b, color.a * opacity));
|
||||||
|
|
||||||
font.draw(
|
font.draw(
|
||||||
batch,
|
batch,
|
||||||
text,
|
text,
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,39 @@
|
||||||
#include "WorldRenderer.hpp"
|
#include "WorldRenderer.hpp"
|
||||||
|
|
||||||
|
#include "BlockWrapsRenderer.hpp"
|
||||||
|
#include "ChunksRenderer.hpp"
|
||||||
|
#include "CloudsRenderer.hpp"
|
||||||
|
#include "DebugLinesRenderer.hpp"
|
||||||
|
#include "Emitter.hpp"
|
||||||
|
#include "HandsRenderer.hpp"
|
||||||
|
#include "ModelBatch.hpp"
|
||||||
|
#include "NamedSkeletons.hpp"
|
||||||
|
#include "ParticlesRenderer.hpp"
|
||||||
|
#include "PrecipitationRenderer.hpp"
|
||||||
|
#include "Skybox.hpp"
|
||||||
|
#include "TextNote.hpp"
|
||||||
|
#include "TextsRenderer.hpp"
|
||||||
#include "assets/Assets.hpp"
|
#include "assets/Assets.hpp"
|
||||||
#include "assets/assets_util.hpp"
|
#include "assets/assets_util.hpp"
|
||||||
|
#include "coders/GLSLExtension.hpp"
|
||||||
#include "content/Content.hpp"
|
#include "content/Content.hpp"
|
||||||
#include "engine/Engine.hpp"
|
#include "engine/Engine.hpp"
|
||||||
#include "coders/GLSLExtension.hpp"
|
|
||||||
#include "frontend/LevelFrontend.hpp"
|
|
||||||
#include "frontend/ContentGfxCache.hpp"
|
#include "frontend/ContentGfxCache.hpp"
|
||||||
|
#include "frontend/LevelFrontend.hpp"
|
||||||
|
#include "graphics/commons/Model.hpp"
|
||||||
|
#include "graphics/core/Atlas.hpp"
|
||||||
|
#include "graphics/core/Batch3D.hpp"
|
||||||
|
#include "graphics/core/DrawContext.hpp"
|
||||||
|
#include "graphics/core/Font.hpp"
|
||||||
|
#include "graphics/core/Framebuffer.hpp"
|
||||||
|
#include "graphics/core/GBuffer.hpp"
|
||||||
|
#include "graphics/core/LineBatch.hpp"
|
||||||
|
#include "graphics/core/Mesh.hpp"
|
||||||
|
#include "graphics/core/PostProcessing.hpp"
|
||||||
|
#include "graphics/core/Shader.hpp"
|
||||||
|
#include "graphics/core/Shadows.hpp"
|
||||||
|
#include "graphics/core/Cubemap.hpp"
|
||||||
|
#include "graphics/core/Texture.hpp"
|
||||||
#include "items/Inventory.hpp"
|
#include "items/Inventory.hpp"
|
||||||
#include "items/ItemDef.hpp"
|
#include "items/ItemDef.hpp"
|
||||||
#include "items/ItemStack.hpp"
|
#include "items/ItemStack.hpp"
|
||||||
|
|
@ -25,32 +52,6 @@
|
||||||
#include "world/Level.hpp"
|
#include "world/Level.hpp"
|
||||||
#include "world/LevelEvents.hpp"
|
#include "world/LevelEvents.hpp"
|
||||||
#include "world/World.hpp"
|
#include "world/World.hpp"
|
||||||
#include "graphics/commons/Model.hpp"
|
|
||||||
#include "graphics/core/Atlas.hpp"
|
|
||||||
#include "graphics/core/Batch3D.hpp"
|
|
||||||
#include "graphics/core/DrawContext.hpp"
|
|
||||||
#include "graphics/core/LineBatch.hpp"
|
|
||||||
#include "graphics/core/Mesh.hpp"
|
|
||||||
#include "graphics/core/PostProcessing.hpp"
|
|
||||||
#include "graphics/core/Framebuffer.hpp"
|
|
||||||
#include "graphics/core/Shader.hpp"
|
|
||||||
#include "graphics/core/Texture.hpp"
|
|
||||||
#include "graphics/core/Font.hpp"
|
|
||||||
#include "graphics/core/Shadows.hpp"
|
|
||||||
#include "graphics/core/GBuffer.hpp"
|
|
||||||
#include "BlockWrapsRenderer.hpp"
|
|
||||||
#include "ParticlesRenderer.hpp"
|
|
||||||
#include "PrecipitationRenderer.hpp"
|
|
||||||
#include "HandsRenderer.hpp"
|
|
||||||
#include "NamedSkeletons.hpp"
|
|
||||||
#include "TextsRenderer.hpp"
|
|
||||||
#include "ChunksRenderer.hpp"
|
|
||||||
#include "DebugLinesRenderer.hpp"
|
|
||||||
#include "ModelBatch.hpp"
|
|
||||||
#include "Skybox.hpp"
|
|
||||||
#include "Emitter.hpp"
|
|
||||||
#include "TextNote.hpp"
|
|
||||||
#include "CloudsRenderer.hpp"
|
|
||||||
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
@ -58,8 +59,6 @@
|
||||||
#include <glm/gtc/matrix_transform.hpp>
|
#include <glm/gtc/matrix_transform.hpp>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
using namespace advanced_pipeline;
|
|
||||||
|
|
||||||
inline constexpr size_t BATCH3D_CAPACITY = 4096;
|
inline constexpr size_t BATCH3D_CAPACITY = 4096;
|
||||||
inline constexpr size_t MODEL_BATCH_CAPACITY = 20'000;
|
inline constexpr size_t MODEL_BATCH_CAPACITY = 20'000;
|
||||||
|
|
||||||
|
|
@ -105,8 +104,7 @@ WorldRenderer::WorldRenderer(
|
||||||
[this](LevelEventType, Chunk* chunk) { chunksRenderer->unload(chunk); }
|
[this](LevelEventType, Chunk* chunk) { chunksRenderer->unload(chunk); }
|
||||||
);
|
);
|
||||||
skybox = std::make_unique<Skybox>(
|
skybox = std::make_unique<Skybox>(
|
||||||
settings.graphics.skyboxResolution.get(),
|
settings.graphics.skyboxResolution.get(), assets
|
||||||
assets.require<Shader>("skybox_gen")
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const auto& content = level.content;
|
const auto& content = level.content;
|
||||||
|
|
@ -160,7 +158,7 @@ void WorldRenderer::setupWorldShader(
|
||||||
shader.uniform1i("u_debugNormals", false);
|
shader.uniform1i("u_debugNormals", false);
|
||||||
shader.uniform1f("u_dayTime", level.getWorld()->getInfo().daytime);
|
shader.uniform1f("u_dayTime", level.getWorld()->getInfo().daytime);
|
||||||
shader.uniform2f("u_lightDir", skybox->getLightDir());
|
shader.uniform2f("u_lightDir", skybox->getLightDir());
|
||||||
shader.uniform1i("u_skybox", TARGET_SKYBOX);
|
shader.uniform1i("u_skybox", advanced_pipeline::TARGET_SKYBOX);
|
||||||
|
|
||||||
auto indices = level.content.getIndices();
|
auto indices = level.content.getIndices();
|
||||||
// Light emission when an emissive item is chosen
|
// Light emission when an emissive item is chosen
|
||||||
|
|
@ -179,6 +177,11 @@ void WorldRenderer::setupWorldShader(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
float WorldRenderer::calcFogFactor() const {
|
||||||
|
const auto& settings = engine.getSettings();
|
||||||
|
return 15.0f / static_cast<float>(settings.chunks.loadDistance.get() - 2);
|
||||||
|
}
|
||||||
|
|
||||||
void WorldRenderer::renderOpaque(
|
void WorldRenderer::renderOpaque(
|
||||||
const DrawContext& ctx,
|
const DrawContext& ctx,
|
||||||
const Camera& camera,
|
const Camera& camera,
|
||||||
|
|
@ -187,14 +190,12 @@ void WorldRenderer::renderOpaque(
|
||||||
) {
|
) {
|
||||||
texts->render(ctx, camera, settings, hudVisible, false);
|
texts->render(ctx, camera, settings, hudVisible, false);
|
||||||
|
|
||||||
bool culling = engine.getSettings().graphics.frustumCulling.get();
|
float fogFactor = calcFogFactor();
|
||||||
float fogFactor =
|
|
||||||
15.0f / static_cast<float>(settings.chunks.loadDistance.get() - 2);
|
|
||||||
|
|
||||||
auto& entityShader = assets.require<Shader>("entity");
|
auto& entityShader = assets.require<Shader>("entity");
|
||||||
setupWorldShader(entityShader, camera, settings, fogFactor);
|
setupWorldShader(entityShader, camera, settings, fogFactor);
|
||||||
skybox->bind();
|
|
||||||
|
|
||||||
|
bool culling = engine.getSettings().graphics.frustumCulling.get();
|
||||||
if (culling) {
|
if (culling) {
|
||||||
frustumCulling->update(camera.getProjView());
|
frustumCulling->update(camera.getProjView());
|
||||||
}
|
}
|
||||||
|
|
@ -213,25 +214,26 @@ void WorldRenderer::renderOpaque(
|
||||||
|
|
||||||
auto& shader = assets.require<Shader>("main");
|
auto& shader = assets.require<Shader>("main");
|
||||||
auto& cloudsShader = assets.require<Shader>("clouds");
|
auto& cloudsShader = assets.require<Shader>("clouds");
|
||||||
auto& linesShader = assets.require<Shader>("lines");
|
|
||||||
|
|
||||||
setupWorldShader(shader, camera, settings, fogFactor);
|
setupWorldShader(shader, camera, settings, fogFactor);
|
||||||
|
|
||||||
chunksRenderer->drawChunks(camera, shader);
|
chunksRenderer->drawChunks(camera, shader);
|
||||||
blockWraps->draw(ctx);
|
blockWraps->draw(ctx);
|
||||||
|
|
||||||
int cloudsQuality = settings.graphics.cloudsQuality.get();
|
if (level.environment.sky.clouds) {
|
||||||
if (cloudsQuality > 0) {
|
int cloudsQuality = settings.graphics.cloudsQuality.get();
|
||||||
setupWorldShader(cloudsShader, camera, settings, fogFactor);
|
if (cloudsQuality > 0) {
|
||||||
cloudsRenderer->draw(
|
setupWorldShader(cloudsShader, camera, settings, fogFactor);
|
||||||
cloudsShader, weather, timer, fogFactor, camera, cloudsQuality
|
cloudsRenderer->draw(
|
||||||
);
|
cloudsShader, weather, timer, fogFactor, camera, cloudsQuality
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hudVisible) {
|
if (hudVisible) {
|
||||||
|
auto& linesShader = assets.require<Shader>("lines");
|
||||||
renderLines(camera, linesShader, ctx);
|
renderLines(camera, linesShader, ctx);
|
||||||
}
|
}
|
||||||
skybox->unbind();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void WorldRenderer::renderBlockSelection() {
|
void WorldRenderer::renderBlockSelection() {
|
||||||
|
|
@ -287,21 +289,19 @@ void WorldRenderer::refreshSettings(Shader** shaders) {
|
||||||
|
|
||||||
int shadowsQuality = graphics.shadowsQuality.get() * gbufferPipeline;
|
int shadowsQuality = graphics.shadowsQuality.get() * gbufferPipeline;
|
||||||
shadowMapping->setQuality(shadowsQuality);
|
shadowMapping->setQuality(shadowsQuality);
|
||||||
|
|
||||||
CompileTimeShaderSettings currentSettings {
|
CompileTimeShaderSettings currentSettings {
|
||||||
gbufferPipeline,
|
gbufferPipeline,
|
||||||
shadowsQuality != 0,
|
shadowsQuality != 0,
|
||||||
graphics.ssao.get() && gbufferPipeline
|
graphics.ssao.get() && gbufferPipeline};
|
||||||
};
|
if (prevCTShaderSettings.advancedRender != currentSettings.advancedRender ||
|
||||||
if (
|
|
||||||
prevCTShaderSettings.advancedRender != currentSettings.advancedRender ||
|
|
||||||
prevCTShaderSettings.shadows != currentSettings.shadows ||
|
prevCTShaderSettings.shadows != currentSettings.shadows ||
|
||||||
prevCTShaderSettings.ssao != currentSettings.ssao
|
prevCTShaderSettings.ssao != currentSettings.ssao) {
|
||||||
) {
|
|
||||||
std::vector<std::string> defines;
|
std::vector<std::string> defines;
|
||||||
if (currentSettings.shadows) defines.emplace_back("ENABLE_SHADOWS");
|
if (currentSettings.shadows) defines.emplace_back("ENABLE_SHADOWS");
|
||||||
if (currentSettings.ssao) defines.emplace_back("ENABLE_SSAO");
|
if (currentSettings.ssao) defines.emplace_back("ENABLE_SSAO");
|
||||||
if (currentSettings.advancedRender) defines.emplace_back("ADVANCED_RENDER");
|
if (currentSettings.advancedRender)
|
||||||
|
defines.emplace_back("ADVANCED_RENDER");
|
||||||
|
|
||||||
for (size_t i = 0; shaders[i]; i++) {
|
for (size_t i = 0; shaders[i]; i++) {
|
||||||
shaders[i]->recompile(defines);
|
shaders[i]->recompile(defines);
|
||||||
|
|
@ -333,7 +333,8 @@ void WorldRenderer::renderFrame(
|
||||||
auto& entityShader = assets.require<Shader>("entity");
|
auto& entityShader = assets.require<Shader>("entity");
|
||||||
auto& cloudsShader = assets.require<Shader>("clouds");
|
auto& cloudsShader = assets.require<Shader>("clouds");
|
||||||
auto& translucentShader = assets.require<Shader>("translucent");
|
auto& translucentShader = assets.require<Shader>("translucent");
|
||||||
auto& deferredShader = assets.require<PostEffect>("deferred_lighting").getShader();
|
auto& deferredShader =
|
||||||
|
assets.require<PostEffect>("deferred_lighting").getShader();
|
||||||
|
|
||||||
const auto& settings = engine.getSettings();
|
const auto& settings = engine.getSettings();
|
||||||
|
|
||||||
|
|
@ -343,19 +344,19 @@ void WorldRenderer::renderFrame(
|
||||||
&cloudsShader,
|
&cloudsShader,
|
||||||
&translucentShader,
|
&translucentShader,
|
||||||
&deferredShader,
|
&deferredShader,
|
||||||
nullptr
|
nullptr};
|
||||||
};
|
|
||||||
|
|
||||||
refreshSettings(affectedShaders);
|
refreshSettings(affectedShaders);
|
||||||
|
|
||||||
const auto& worldInfo = world->getInfo();
|
const auto& worldInfo = world->getInfo();
|
||||||
|
|
||||||
float clouds = weather.clouds();
|
float clouds = weather.clouds();
|
||||||
clouds = glm::max(worldInfo.fog, clouds);
|
clouds = glm::max(worldInfo.fog, clouds);
|
||||||
float mie = 1.0f + glm::max(worldInfo.fog, clouds * 0.5f) * 2.0f;
|
float mie = 1.0f + glm::max(worldInfo.fog, clouds * 0.5f) * 2.0f;
|
||||||
|
|
||||||
float random = rand() / static_cast<float>(RAND_MAX);
|
float random = rand() / static_cast<float>(RAND_MAX);
|
||||||
skybox->refresh(
|
skybox->refresh(
|
||||||
|
level.environment,
|
||||||
pctx,
|
pctx,
|
||||||
worldInfo.daytime,
|
worldInfo.daytime,
|
||||||
mie,
|
mie,
|
||||||
|
|
@ -374,20 +375,19 @@ void WorldRenderer::renderFrame(
|
||||||
{
|
{
|
||||||
DrawContext wctx = pctx.sub();
|
DrawContext wctx = pctx.sub();
|
||||||
postProcessing.use(wctx, gbufferPipeline);
|
postProcessing.use(wctx, gbufferPipeline);
|
||||||
|
|
||||||
display::clearDepth();
|
display::clearDepth();
|
||||||
|
|
||||||
/* Main opaque pass (GBuffer pass) */ {
|
/* Main opaque pass (GBuffer pass) */ {
|
||||||
DrawContext ctx = wctx.sub();
|
DrawContext ctx = wctx.sub();
|
||||||
ctx.setDepthTest(true);
|
ctx.setDepthTest(true);
|
||||||
ctx.setCullFace(true);
|
ctx.setCullFace(true);
|
||||||
|
ctx.useTexture(advanced_pipeline::TARGET_SKYBOX, skybox->getCubemap());
|
||||||
|
ctx.useTexture(advanced_pipeline::TARGET_COLOR, nullptr);
|
||||||
renderOpaque(ctx, camera, settings, hudVisible);
|
renderOpaque(ctx, camera, settings, hudVisible);
|
||||||
}
|
}
|
||||||
texts->render(pctx, camera, settings, hudVisible, true);
|
texts->render(pctx, camera, settings, hudVisible, true);
|
||||||
}
|
}
|
||||||
skybox->bind();
|
float fogFactor = calcFogFactor();
|
||||||
float fogFactor =
|
|
||||||
15.0f / static_cast<float>(settings.chunks.loadDistance.get() - 2);
|
|
||||||
if (gbufferPipeline) {
|
if (gbufferPipeline) {
|
||||||
deferredShader.use();
|
deferredShader.use();
|
||||||
setupWorldShader(deferredShader, camera, settings, fogFactor);
|
setupWorldShader(deferredShader, camera, settings, fogFactor);
|
||||||
|
|
@ -396,6 +396,7 @@ void WorldRenderer::renderFrame(
|
||||||
{
|
{
|
||||||
DrawContext ctx = pctx.sub();
|
DrawContext ctx = pctx.sub();
|
||||||
ctx.setDepthTest(true);
|
ctx.setDepthTest(true);
|
||||||
|
ctx.useTexture(advanced_pipeline::TARGET_COLOR, nullptr);
|
||||||
|
|
||||||
if (gbufferPipeline) {
|
if (gbufferPipeline) {
|
||||||
postProcessing.bindDepthBuffer();
|
postProcessing.bindDepthBuffer();
|
||||||
|
|
@ -404,7 +405,9 @@ void WorldRenderer::renderFrame(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Background sky plane
|
// Background sky plane
|
||||||
skybox->draw(ctx, camera, assets, worldInfo.daytime, clouds);
|
skybox->draw(
|
||||||
|
level.environment, ctx, camera, worldInfo.daytime, clouds
|
||||||
|
);
|
||||||
|
|
||||||
auto& linesShader = assets.require<Shader>("lines");
|
auto& linesShader = assets.require<Shader>("lines");
|
||||||
linesShader.use();
|
linesShader.use();
|
||||||
|
|
@ -416,10 +419,12 @@ void WorldRenderer::renderFrame(
|
||||||
linesShader.uniformMatrix("u_projview", projView);
|
linesShader.uniformMatrix("u_projview", projView);
|
||||||
lineBatch->flush();
|
lineBatch->flush();
|
||||||
|
|
||||||
skybox->bind();
|
DrawContext wctx = ctx.sub();
|
||||||
|
wctx.useTexture(advanced_pipeline::TARGET_SKYBOX, skybox->getCubemap());
|
||||||
|
wctx.useTexture(advanced_pipeline::TARGET_COLOR, nullptr);
|
||||||
// Translucent blocks
|
// Translucent blocks
|
||||||
{
|
{
|
||||||
auto sctx = ctx.sub();
|
auto sctx = wctx.sub();
|
||||||
sctx.setCullFace(true);
|
sctx.setCullFace(true);
|
||||||
translucentShader.use();
|
translucentShader.use();
|
||||||
setupWorldShader(translucentShader, camera, settings, fogFactor);
|
setupWorldShader(translucentShader, camera, settings, fogFactor);
|
||||||
|
|
@ -430,44 +435,41 @@ void WorldRenderer::renderFrame(
|
||||||
entityShader.use();
|
entityShader.use();
|
||||||
setupWorldShader(entityShader, camera, settings, fogFactor);
|
setupWorldShader(entityShader, camera, settings, fogFactor);
|
||||||
|
|
||||||
std::array<const WeatherPreset*, 2> weatherInstances {&weather.a, &weather.b};
|
std::array<const WeatherPreset*, 2> weatherInstances {
|
||||||
|
&weather.a, &weather.b};
|
||||||
for (const auto& weather : weatherInstances) {
|
for (const auto& weather : weatherInstances) {
|
||||||
float maxIntensity = weather->fall.maxIntensity;
|
float maxIntensity = weather->fall.maxIntensity;
|
||||||
float zero = weather->fall.minOpacity;
|
float zero = weather->fall.minOpacity;
|
||||||
float one = weather->fall.maxOpacity;
|
float one = weather->fall.maxOpacity;
|
||||||
float t = (weather->intensity * (one - zero)) * maxIntensity + zero;
|
float t = (weather->intensity * (one - zero)) * maxIntensity + zero;
|
||||||
entityShader.uniform1i("u_alphaClip", weather->fall.opaque);
|
entityShader.uniform1i("u_alphaClip", weather->fall.opaque);
|
||||||
entityShader.uniform1f("u_opacity", weather->fall.opaque ? t * t : t);
|
entityShader.uniform1f(
|
||||||
|
"u_opacity", weather->fall.opaque ? t * t : t
|
||||||
|
);
|
||||||
if (weather->intensity > 1.e-3f && !weather->fall.texture.empty()) {
|
if (weather->intensity > 1.e-3f && !weather->fall.texture.empty()) {
|
||||||
precipitation->render(camera, *weather);
|
precipitation->render(camera, *weather);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
skybox->unbind();
|
|
||||||
|
|
||||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||||
}
|
}
|
||||||
postProcessing.render(pctx, assets, timer, camera);
|
postProcessing.render(pctx, assets, timer, camera);
|
||||||
|
|
||||||
if (player.currentCamera == player.fpCamera) {
|
if (player.currentCamera == player.fpCamera) {
|
||||||
DrawContext ctx = pctx.sub();
|
DrawContext ctx = pctx.sub();
|
||||||
ctx.setDepthTest(true);
|
ctx.setDepthTest(true);
|
||||||
ctx.setCullFace(true);
|
ctx.setCullFace(true);
|
||||||
|
ctx.useTexture(advanced_pipeline::TARGET_SKYBOX, skybox->getCubemap());
|
||||||
|
ctx.useTexture(advanced_pipeline::TARGET_COLOR, nullptr);
|
||||||
|
display::clearDepth();
|
||||||
|
|
||||||
// prepare modified HUD camera
|
// prepare modified HUD camera
|
||||||
Camera hudcam = camera;
|
Camera hudcam = camera;
|
||||||
hudcam.far = 10.0f;
|
hudcam.far = 10.0f;
|
||||||
hudcam.setFov(0.9f);
|
hudcam.setFov(0.9f);
|
||||||
hudcam.position = {};
|
hudcam.position = {};
|
||||||
|
|
||||||
hands->render(camera);
|
|
||||||
|
|
||||||
display::clearDepth();
|
|
||||||
setupWorldShader(entityShader, hudcam, engine.getSettings(), 0.0f);
|
setupWorldShader(entityShader, hudcam, engine.getSettings(), 0.0f);
|
||||||
|
hands->render(camera);
|
||||||
skybox->bind();
|
|
||||||
modelBatch->render();
|
|
||||||
modelBatch->setLightsOffset(glm::vec3());
|
|
||||||
skybox->unbind();
|
|
||||||
}
|
}
|
||||||
renderBlockOverlay(pctx);
|
renderBlockOverlay(pctx);
|
||||||
|
|
||||||
|
|
@ -487,13 +489,12 @@ void WorldRenderer::renderBlockOverlay(const DrawContext& wctx) {
|
||||||
if (def.overlayTexture.empty()) {
|
if (def.overlayTexture.empty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
auto textureRegion = util::get_texture_region(
|
auto textureRegion =
|
||||||
assets, def.overlayTexture, "blocks:notfound"
|
util::get_texture_region(assets, def.overlayTexture, "blocks:notfound");
|
||||||
);
|
|
||||||
DrawContext ctx = wctx.sub();
|
DrawContext ctx = wctx.sub();
|
||||||
ctx.setDepthTest(false);
|
ctx.setDepthTest(false);
|
||||||
ctx.setCullFace(false);
|
ctx.setCullFace(false);
|
||||||
|
|
||||||
auto& shader = assets.require<Shader>("ui3d");
|
auto& shader = assets.require<Shader>("ui3d");
|
||||||
shader.use();
|
shader.use();
|
||||||
batch3d->begin();
|
batch3d->begin();
|
||||||
|
|
|
||||||
|
|
@ -99,6 +99,8 @@ class WorldRenderer {
|
||||||
);
|
);
|
||||||
|
|
||||||
void refreshSettings(Shader** shaders);
|
void refreshSettings(Shader** shaders);
|
||||||
|
|
||||||
|
float calcFogFactor() const;
|
||||||
public:
|
public:
|
||||||
std::unique_ptr<ParticlesRenderer> particles;
|
std::unique_ptr<ParticlesRenderer> particles;
|
||||||
std::unique_ptr<TextsRenderer> texts;
|
std::unique_ptr<TextsRenderer> texts;
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ InlineFrame::~InlineFrame() = default;
|
||||||
void InlineFrame::setSrc(const std::string& src) {
|
void InlineFrame::setSrc(const std::string& src) {
|
||||||
this->src = src;
|
this->src = src;
|
||||||
if (document) {
|
if (document) {
|
||||||
scripting::on_ui_close(document.get(), nullptr);
|
scripting::on_ui_close(*document, nullptr);
|
||||||
document = nullptr;
|
document = nullptr;
|
||||||
root = nullptr;
|
root = nullptr;
|
||||||
}
|
}
|
||||||
|
|
@ -31,7 +31,7 @@ void InlineFrame::setDocument(const std::shared_ptr<UiDocument>& document) {
|
||||||
root->setSize(size);
|
root->setSize(size);
|
||||||
|
|
||||||
gui.postRunnable([this]() {
|
gui.postRunnable([this]() {
|
||||||
scripting::on_ui_open(this->document.get(), {});
|
scripting::on_ui_open(*this->document, {});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,8 +15,33 @@ public:
|
||||||
memory_streambuf(const memory_streambuf&) = delete;
|
memory_streambuf(const memory_streambuf&) = delete;
|
||||||
memory_streambuf& operator=(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:
|
protected:
|
||||||
int_type underflow() override {
|
int_type underflow() override {
|
||||||
|
if (gptr() < egptr()) {
|
||||||
|
return traits_type::to_int_type(*gptr());
|
||||||
|
}
|
||||||
return traits_type::eof();
|
return traits_type::eof();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -36,7 +61,7 @@ private:
|
||||||
class memory_view_streambuf : public std::streambuf {
|
class memory_view_streambuf : public std::streambuf {
|
||||||
public:
|
public:
|
||||||
explicit memory_view_streambuf(const util::Buffer<char>& buffer)
|
explicit memory_view_streambuf(const util::Buffer<char>& buffer)
|
||||||
: buffer(std::move(buffer)) {
|
: buffer(buffer) {
|
||||||
char* base = const_cast<char*>(this->buffer.data());
|
char* base = const_cast<char*>(this->buffer.data());
|
||||||
char* end = base + this->buffer.size();
|
char* end = base + this->buffer.size();
|
||||||
setg(base, base, end);
|
setg(base, base, end);
|
||||||
|
|
@ -45,8 +70,33 @@ public:
|
||||||
memory_view_streambuf(const memory_view_streambuf&) = delete;
|
memory_view_streambuf(const memory_view_streambuf&) = delete;
|
||||||
memory_view_streambuf& operator=(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:
|
protected:
|
||||||
int_type underflow() override {
|
int_type underflow() override {
|
||||||
|
if (gptr() < egptr()) {
|
||||||
|
return traits_type::to_int_type(*gptr());
|
||||||
|
}
|
||||||
return traits_type::eof();
|
return traits_type::eof();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,8 @@ namespace io {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
path(std::string_view str) : path(std::string(str)) {}
|
||||||
|
|
||||||
path(const char* str) : path(std::string(str)) {}
|
path(const char* str) : path(std::string(str)) {}
|
||||||
|
|
||||||
bool operator==(const std::string& other) const {
|
bool operator==(const std::string& other) const {
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ const uint MIN_SURROUNDING = 9;
|
||||||
ChunksController::ChunksController(Level& level)
|
ChunksController::ChunksController(Level& level)
|
||||||
: level(level),
|
: level(level),
|
||||||
generator(std::make_unique<WorldGenerator>(
|
generator(std::make_unique<WorldGenerator>(
|
||||||
level.content.generators.require(level.getWorld()->getGenerator()),
|
level.content.generators.require(level.environment.generator),
|
||||||
level.content,
|
level.content,
|
||||||
level.getWorld()->getSeed()
|
level.getWorld()->getSeed()
|
||||||
)) {}
|
)) {}
|
||||||
|
|
|
||||||
|
|
@ -179,7 +179,7 @@ void EngineController::onMissingContent(
|
||||||
if (engine.isHeadless()) {
|
if (engine.isHeadless()) {
|
||||||
throw std::runtime_error(
|
throw std::runtime_error(
|
||||||
"missing content: " +
|
"missing content: " +
|
||||||
json::stringify(create_missing_content_report(report), true, " ")
|
util::quote(json::stringify(create_missing_content_report(report), true, " "))
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
engine.setScreen(std::make_shared<MenuScreen>(engine));
|
engine.setScreen(std::make_shared<MenuScreen>(engine));
|
||||||
|
|
@ -268,7 +268,7 @@ inline uint64_t str2seed(const std::string& seedstr) {
|
||||||
void EngineController::createWorld(
|
void EngineController::createWorld(
|
||||||
const std::string& name,
|
const std::string& name,
|
||||||
const std::string& seedstr,
|
const std::string& seedstr,
|
||||||
const std::string& generatorID
|
const std::string& environment
|
||||||
) {
|
) {
|
||||||
uint64_t seed = str2seed(seedstr);
|
uint64_t seed = str2seed(seedstr);
|
||||||
|
|
||||||
|
|
@ -279,17 +279,31 @@ void EngineController::createWorld(
|
||||||
paths.setCurrentWorldFolder(folder);
|
paths.setCurrentWorldFolder(folder);
|
||||||
engine.getContentControl().loadContent();
|
engine.getContentControl().loadContent();
|
||||||
});
|
});
|
||||||
|
const auto& generators = engine.getContentControl().get()->generators;
|
||||||
|
|
||||||
|
// generator-based environments must die in 1.0
|
||||||
|
bool genBasedEnv = false;
|
||||||
|
if (auto genDef = generators.find(environment)) {
|
||||||
|
genBasedEnv = true;
|
||||||
|
}
|
||||||
|
|
||||||
auto& contentControl = engine.getContentControl();
|
auto& contentControl = engine.getContentControl();
|
||||||
auto level = World::create(
|
auto level = World::create(
|
||||||
name,
|
name,
|
||||||
generatorID,
|
genBasedEnv ? "" : environment,
|
||||||
|
genBasedEnv ? environment : "",
|
||||||
folder,
|
folder,
|
||||||
seed,
|
seed,
|
||||||
engine.getSettings(),
|
engine.getSettings(),
|
||||||
*contentControl.get(),
|
*contentControl.get(),
|
||||||
contentControl.getContentPacks()
|
contentControl.getContentPacks()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// generator-based environment initialization
|
||||||
|
if (genBasedEnv) {
|
||||||
|
level->environment.generator = environment;
|
||||||
|
}
|
||||||
|
|
||||||
if (!engine.isHeadless()) {
|
if (!engine.isHeadless()) {
|
||||||
level->players->create(localPlayer);
|
level->players->create(localPlayer);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -149,7 +149,7 @@ void LevelController::saveWorld() {
|
||||||
world->wfile->createDirectories();
|
world->wfile->createDirectories();
|
||||||
scripting::on_world_save();
|
scripting::on_world_save();
|
||||||
level->onSave();
|
level->onSave();
|
||||||
level->getWorld()->write(level.get());
|
level->getWorld()->write(*level);
|
||||||
}
|
}
|
||||||
|
|
||||||
void LevelController::onWorldQuit() {
|
void LevelController::onWorldQuit() {
|
||||||
|
|
|
||||||
|
|
@ -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 quatlib[];
|
||||||
extern const luaL_Reg randomlib[];
|
extern const luaL_Reg randomlib[];
|
||||||
extern const luaL_Reg compressionlib[];
|
extern const luaL_Reg compressionlib[];
|
||||||
|
extern const luaL_Reg testlib[];
|
||||||
extern const luaL_Reg text3dlib[]; // gfx.text3d
|
extern const luaL_Reg text3dlib[]; // gfx.text3d
|
||||||
extern const luaL_Reg timelib[];
|
extern const luaL_Reg timelib[];
|
||||||
extern const luaL_Reg tomllib[];
|
extern const luaL_Reg tomllib[];
|
||||||
|
|
|
||||||
|
|
@ -20,8 +20,14 @@
|
||||||
#include "window/Window.hpp"
|
#include "window/Window.hpp"
|
||||||
#include "world/Level.hpp"
|
#include "world/Level.hpp"
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
|
||||||
using namespace scripting;
|
using namespace scripting;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
static std::array<std::unique_ptr<Process>, MAX_SUBPROCESSES> processes;
|
||||||
|
}
|
||||||
|
|
||||||
/// @brief Check if content is loaded
|
/// @brief Check if content is loaded
|
||||||
static int l_is_content_loaded(lua::State* L) {
|
static int l_is_content_loaded(lua::State* L) {
|
||||||
return lua::pushboolean(L, content != nullptr);
|
return lua::pushboolean(L, content != nullptr);
|
||||||
|
|
@ -337,6 +343,10 @@ static int l_start_debug_instance(lua::State* L) {
|
||||||
if (!engine->getProject().permissions.has(Permissions::DEBUGGING)) {
|
if (!engine->getProject().permissions.has(Permissions::DEBUGGING)) {
|
||||||
throw std::runtime_error("project has no debugging permission");
|
throw std::runtime_error("project has no debugging permission");
|
||||||
}
|
}
|
||||||
|
const auto& params = engine->getCoreParameters();
|
||||||
|
if (params.subProcessDepth >= MAX_SUBPROCESS_DEPTH) {
|
||||||
|
throw std::runtime_error("max subprocess depth exceeded");
|
||||||
|
}
|
||||||
|
|
||||||
int port = lua::tointeger(L, 1);
|
int port = lua::tointeger(L, 1);
|
||||||
if (port == 0) {
|
if (port == 0) {
|
||||||
|
|
@ -357,6 +367,8 @@ static int l_start_debug_instance(lua::State* L) {
|
||||||
"--res", paths.getResourcesFolder().u8string(),
|
"--res", paths.getResourcesFolder().u8string(),
|
||||||
"--dir", paths.getUserFilesFolder().u8string(),
|
"--dir", paths.getUserFilesFolder().u8string(),
|
||||||
"--dbg-server", "tcp:" + std::to_string(port),
|
"--dbg-server", "tcp:" + std::to_string(port),
|
||||||
|
"--sub-depth", std::to_string(engine->getCoreParameters()
|
||||||
|
.subProcessDepth + 1),
|
||||||
};
|
};
|
||||||
if (!projectPath.empty()) {
|
if (!projectPath.empty()) {
|
||||||
args.emplace_back("--project");
|
args.emplace_back("--project");
|
||||||
|
|
@ -365,11 +377,76 @@ static int l_start_debug_instance(lua::State* L) {
|
||||||
|
|
||||||
platform::new_engine_instance(
|
platform::new_engine_instance(
|
||||||
std::move(args),
|
std::move(args),
|
||||||
outputPath.empty() ? "" : io::resolve(std::string(outputPath))
|
outputPath.empty() ? "" : io::resolve(std::string(outputPath)),
|
||||||
|
false
|
||||||
);
|
);
|
||||||
return lua::pushinteger(L, port);
|
return lua::pushinteger(L, port);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int l_start_background_instance(lua::State* L) {
|
||||||
|
if (!engine->getProject().permissions.has(Permissions::SUB_INSTANCES)) {
|
||||||
|
throw std::runtime_error("project has no sub-instances permission");
|
||||||
|
}
|
||||||
|
const auto& params = engine->getCoreParameters();
|
||||||
|
if (params.subProcessDepth >= MAX_SUBPROCESS_DEPTH) {
|
||||||
|
throw std::runtime_error("max subprocess depth exceeded");
|
||||||
|
}
|
||||||
|
|
||||||
|
auto scriptPath = lua::require_lstring(L, 1);
|
||||||
|
io::path outputPath = lua::isstring(L, 2) ? lua::require_lstring(L, 2) : "";
|
||||||
|
const auto& paths = engine->getPaths();
|
||||||
|
|
||||||
|
std::vector<std::string> args {
|
||||||
|
"--headless",
|
||||||
|
"--res", paths.getResourcesFolder().u8string(),
|
||||||
|
"--dir", paths.getUserFilesFolder().u8string(),
|
||||||
|
"--script", io::resolve(scriptPath).u8string(),
|
||||||
|
"--sub-depth", std::to_string(engine->getCoreParameters()
|
||||||
|
.subProcessDepth + 1),
|
||||||
|
"--log", io::resolve(outputPath).u8string(),
|
||||||
|
};
|
||||||
|
args.emplace_back("--project");
|
||||||
|
args.emplace_back(io::resolve(engine->getProject().path).u8string());
|
||||||
|
|
||||||
|
int handle = -1;
|
||||||
|
for (int i = 0; i < ::processes.size(); i++) {
|
||||||
|
if (!::processes[i] || !::processes[i]->isActive()) {
|
||||||
|
handle = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (handle == -1) {
|
||||||
|
throw std::runtime_error("sub-processes limit exceeded");
|
||||||
|
}
|
||||||
|
::processes[handle] =
|
||||||
|
platform::new_engine_instance(std::move(args), "", true);
|
||||||
|
|
||||||
|
return lua::pushinteger(L, handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int l_is_instance_alive(lua::State* L) {
|
||||||
|
int handle = lua::tointeger(L, 1);
|
||||||
|
if (handle < 0 || handle >= ::processes.size()) {
|
||||||
|
throw std::runtime_error("invalid process handle");
|
||||||
|
}
|
||||||
|
auto& process = ::processes[handle];
|
||||||
|
return lua::pushboolean(L, process && process->isActive());
|
||||||
|
}
|
||||||
|
|
||||||
|
static int l_terminate_instance(lua::State* L) {
|
||||||
|
int handle = lua::tointeger(L, 1);
|
||||||
|
if (handle < 0 || handle >= ::processes.size()) {
|
||||||
|
throw std::runtime_error("invalid process handle");
|
||||||
|
}
|
||||||
|
auto& process = ::processes[handle];
|
||||||
|
if (process == nullptr) {
|
||||||
|
return lua::pushboolean(L, false);
|
||||||
|
}
|
||||||
|
bool active = process->isActive();
|
||||||
|
process.reset();
|
||||||
|
return lua::pushboolean(L, active);
|
||||||
|
}
|
||||||
|
|
||||||
const luaL_Reg applib[] = {
|
const luaL_Reg applib[] = {
|
||||||
/// content
|
/// content
|
||||||
{"is_content_loaded", lua::wrap<l_is_content_loaded>},
|
{"is_content_loaded", lua::wrap<l_is_content_loaded>},
|
||||||
|
|
@ -402,5 +479,8 @@ const luaL_Reg applib[] = {
|
||||||
{"get_version", lua::wrap<l_get_version>},
|
{"get_version", lua::wrap<l_get_version>},
|
||||||
{"create_memory_device", lua::wrap<l_create_memory_device>},
|
{"create_memory_device", lua::wrap<l_create_memory_device>},
|
||||||
{"start_debug_instance", lua::wrap<l_start_debug_instance>},
|
{"start_debug_instance", lua::wrap<l_start_debug_instance>},
|
||||||
|
{"start_background_instance", lua::wrap<l_start_background_instance>},
|
||||||
|
{"is_instance_alive", lua::wrap<l_is_instance_alive>},
|
||||||
|
{"terminate_instance", lua::wrap<l_terminate_instance>},
|
||||||
{nullptr, nullptr}
|
{nullptr, nullptr}
|
||||||
};
|
};
|
||||||
|
|
|
||||||
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