diff --git a/.github/workflows/appimage.yml b/.github/workflows/appimage.yml index 7b6e738da..f2f39f2b0 100644 --- a/.github/workflows/appimage.yml +++ b/.github/workflows/appimage.yml @@ -2,7 +2,7 @@ name: x86-64 AppImage on: push: - branches: [ "main", "dev", "release-**"] + branches: [ "main", "release-**"] pull_request: branches: [ "main", "dev" ] diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index 8cfc20084..6a2fea64c 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -2,7 +2,7 @@ name: Macos Build on: push: - branches: [ "main", "dev", "release-**"] + branches: [ "main", "release-**"] pull_request: branches: [ "main", "dev" ] diff --git a/.github/workflows/windows-clang.yml b/.github/workflows/windows-clang.yml index 3b860ed8c..adb8afc3d 100644 --- a/.github/workflows/windows-clang.yml +++ b/.github/workflows/windows-clang.yml @@ -2,7 +2,7 @@ name: Windows Build (CLang) on: push: - branches: [ "main", "dev", "release-**"] + branches: [ "main", "release-**"] pull_request: branches: [ "main", "dev" ] diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 03cdffcf4..fa0c9b762 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -2,7 +2,7 @@ name: MSVC Build on: push: - branches: [ "main", "dev", "release-**"] + branches: [ "main", "release-**"] pull_request: branches: [ "main", "dev" ] diff --git a/README.md b/README.md index c6e2a993e..d4eedc546 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ ## Latest release - [Download](https://github.com/MihailRis/VoxelCore/releases/latest) | [Скачать](https://github.com/MihailRis/VoxelCore/releases/latest) -- [Documentation](https://github.com/MihailRis/VoxelCore/blob/release-0.28/doc/en/main-page.md) | [Документация](https://github.com/MihailRis/VoxelCore/blob/release-0.28/doc/ru/main-page.md) +- [Documentation](https://github.com/MihailRis/VoxelCore/blob/release-0.29/doc/en/main-page.md) | [Документация](https://github.com/MihailRis/VoxelCore/blob/release-0.29/doc/ru/main-page.md) --- diff --git a/dev/tests/filesystem.lua b/dev/tests/filesystem.lua index a9ff58ce0..38e7c4a04 100644 --- a/dev/tests/filesystem.lua +++ b/dev/tests/filesystem.lua @@ -4,6 +4,7 @@ assert(file.exists("config:")) debug.log("write text file") assert(file.write("config:text.txt", "example, пример")) assert(file.exists("config:text.txt")) +assert(file.isfile("config:text.txt")) debug.log("read text file") assert(file.read("config:text.txt") == "example, пример") @@ -18,11 +19,23 @@ assert(file.isdir("config:dir")) debug.log("remove directory") file.remove("config:dir") +assert(not file.isdir("config:dir")) debug.log("create directories") file.mkdirs("config:dir/subdir/other") assert(file.isdir("config:dir/subdir/other")) +debug.log("list directory") +file.write("config:dir/subdir/a.txt", "helloworld") +file.write("config:dir/subdir/b.txt", "gfgsfhs") + +local entries = file.list("config:dir/subdir") +assert(#entries == 3) +table.sort(entries) +assert(entries[1] == "config:dir/subdir/a.txt") +assert(entries[2] == "config:dir/subdir/b.txt") +assert(entries[3] == "config:dir/subdir/other") + debug.log("remove tree") file.remove_tree("config:dir") assert(not file.isdir("config:dir")) diff --git a/dev/tests/memory_filesystem.lua b/dev/tests/memory_filesystem.lua new file mode 100644 index 000000000..e246279a7 --- /dev/null +++ b/dev/tests/memory_filesystem.lua @@ -0,0 +1,67 @@ +app.create_memory_device("memtest") +local tmp = file.create_memory_device() + +debug.log("check initial state") +assert(file.exists("memtest:")) +assert(file.is_writeable("memtest:")) +assert(file.is_writeable(tmp..":")) + +debug.log("write text file") +assert(file.write("memtest:text.txt", "example, пример")) +assert(file.exists("memtest:text.txt")) +assert(file.isfile("memtest:text.txt")) + +debug.log("read text file") +assert(file.read("memtest:text.txt") == "example, пример") + +debug.log("delete file") +file.remove("memtest:text.txt") +assert(not file.exists("memtest:text.txt")) + +debug.log("create directory") +file.mkdir("memtest:dir") +assert(file.isdir("memtest:dir")) + +debug.log("remove directory") +file.remove("memtest:dir") +assert(not file.isdir("memtest:dir")) + +debug.log("create directories") +file.mkdirs("memtest:dir/subdir/other") +assert(file.isdir("memtest:dir/subdir/other")) + +debug.log("list directory") +file.write("memtest:dir/subdir/a.txt", "helloworld") +file.write("memtest:dir/subdir/b.txt", "gfgsfhs") + +local entries = file.list("memtest:dir/subdir") +assert(#entries == 3) +table.sort(entries) +assert(entries[1] == "memtest:dir/subdir/a.txt") +assert(entries[2] == "memtest:dir/subdir/b.txt") +assert(entries[3] == "memtest:dir/subdir/other") + +debug.log("remove tree") +file.remove_tree("memtest:dir") +assert(not file.isdir("memtest:dir")) + +debug.log("write binary file") +local bytes = {0xDE, 0xAD, 0xC0, 0xDE} +file.write_bytes("memtest:binary", bytes) +assert(file.exists("memtest:binary")) + +debug.log("write binary file") +local bytes = {0xDE, 0xAD, 0xC0, 0xDE} +file.write_bytes("memtest:binary", bytes) +assert(file.exists("memtest:binary")) + +debug.log("read binary file") +local rbytes = file.read_bytes("memtest:binary") +assert(#rbytes == #bytes) +for i, b in ipairs(bytes) do + assert(rbytes[i] == b) +end + +debug.log("delete file") +file.remove("memtest:binary") +assert(not file.exists("memtest:binary")) diff --git a/doc/en/audio.md b/doc/en/audio.md index cacd71074..095104020 100644 --- a/doc/en/audio.md +++ b/doc/en/audio.md @@ -203,3 +203,79 @@ audio.count_speakers() -> integer -- get current number of playing streams audio.count_streams() -> integer ``` + +### audio.PCMStream + +```lua +-- Creating a PCM data source +local stream = audio.PCMStream( + -- Sample rate + sample_rate: integer, + -- Number of channels (1 - mono, 2 - stereo) + channels: integer, + -- Number of bits per sample (8 or 16) + bits_per_sample: integer, +) + +-- Feeding PCM data into the stream +stream:feed( + -- PCM data to be fed into the stream + data: Bytearray +) + +-- Publishing the PCM data source for use by the engine systems +stream:share( + -- Alias of the audio stream, which can be referenced in audio.play_stream + alias: string +) + +-- Creating a sound from the PCM data in the stream +stream:create_sound( + -- Name of the created sound + name: string +) +``` + +### Audio Recording + +```lua +-- Requests access to audio recording +-- On confirmation, the callback receives a token for use in audio.input.fetch +audio.input.request_open(callback: function(string)) + +-- Reads new PCM audio input data +audio.input.fetch( + -- Token obtained through audio.input.request_open + access_token: string, + -- Maximum buffer size in bytes (optional) + [optional] max_read_size: integer +) +``` + +### Example of Audio Generation: + +```lua +-- For working with 16-bit samples, use a U16view over Bytearray +-- Example: +local max_amplitude = 32767 +local sample_rate = 44100 +local total_samples = sample_rate * 5 -- 5 seconds of mono +local bytes = Bytearray(total_samples * 2) -- 5 seconds of 16-bit mono +local samples = I16view(bytes) + +local frequency_hz = 400 +for i=1, total_samples do + local value = math.sin(i * math.pi * 2 / sample_rate * frequency_hz) + samples[i] = value * max_amplitude +end + +local stream_name = "test-stream" +local stream = audio.PCMStream(sample_rate, 1, 16) +stream:feed(bytes) +stream:share(stream_name) + +local volume = 1.0 +local pitch = 1.0 +local channel = "ui" +audio.play_stream_2d(stream_name, volume, pitch, channel) +``` diff --git a/doc/en/block-properties.md b/doc/en/block-properties.md index 1244da99d..82811b630 100644 --- a/doc/en/block-properties.md +++ b/doc/en/block-properties.md @@ -98,6 +98,32 @@ Properties available for variance: Variants are managed via `block.set_variant(x, y, z, index)`. +### Custom model variants (geometry switching) + +You can use different custom models for different variants. Provide a separate `model-name` for each variant that needs different geometry. The renderer caches geometry per (block id, variant). + +The base model (specified in root) becomes variant 0. The variants array maps to indices 1+. + +Example (default + two custom variants): +```json +{ + "model": "custom", + "model-name": "stairs_middle", + "state-based": { + "bits": 4, + "variants": [ + { "model": "custom", "model-name": "stairs_left" }, + { "model": "custom", "model-name": "stairs_right" } + ] + } +} +``` + +In this example: +- Variant 0 = `stairs_middle` (from root) +- Variant 1 = `stairs_left` (from variants[0]) +- Variant 2 = `stairs_right` (from variants[1]) + ## Lighting ### *emission* diff --git a/doc/en/content-packs.md b/doc/en/content-packs.md index a41f95f99..c193fb188 100644 --- a/doc/en/content-packs.md +++ b/doc/en/content-packs.md @@ -48,6 +48,79 @@ Example: Content pack picture should be added as *icon.png* file. Recommended size: 128x128 -See *res/content/base* as an example of content pack structure. +# File System +Every loaded pack is mounted to the internal file system as a new mount point, whose name matches the pack's id. +This means that accessing pack files does not require the use of additional functions: + +```lua +print(file.read("your_pack_id:package.json")) -- will output the contents of the pack's package.json +``` + +This is also one of the reasons why some ids are reserved and cannot be used. + +Mount points are mounted as read-only. To gain write access, use the `pack.request_writeable` function. + +Read more about the [file](scripting/builtins/libfile.md) library. + +# Content Pack Structure + +Don't be intimidated by the following text, as a minimal pack only requires `package.json`. + +- Content: + - `block_materials/` - Block material definitions + - `blocks/` - Block definitions + - `items/` - Item definitions + - `generators/` - World generators + - `entities/` - Entity definitions + - `skeletons/` - Entity skeleton definitions + - `presets/` - Presets (can also be used by packs for their own purposes) + - `text3d/` - 3D Text + - `weather/` - Weather +- Code: + - `modules/` - Script modules + - `scripts/` - Content scripts, world scripts + - `components/` - Entity components +- Assets (Client-side resources): + - `fonts/` - Fonts + - `models/` - 3D Models + - `textures/` - Textures + - `shaders/` - Shaders + - `effects/` - Post-processing effects + - `sounds/` - Sounds and Music + - `texts/` - Localization files +- GUI: + - `layouts/` - UI Layouts + - `pages/` - Menu page layouts (for the pagebox element) +- Configuration: + - `config/` - Configuration files + - `defaults.toml` - Overrides for standard content bindings, such as the player entity, default generator, etc. + - `bindings.toml` - Keyboard/Mouse bindings + - `user-props.toml` - User properties for content definitions + - `devtools/` - Auxiliary files for internal debugging tools + - `content.json` - Automatically generated content lists, used for validating world indices and for conversion when mismatched + - `icon.png` - Pack icon + - `package.json` - Pack definition file + - `preload.json` - Asset preload lists for assets that are not loaded automatically; avoid listing assets unnecessarily + - `resources.json` - Definition lists for [resources](resources.md) (not to be confused with assets) + - `resource-aliases.json` - Files declaring aliases for [resources](resources.md) + +> [!WARNING] +> Manually editing `content.json` is strongly discouraged and will most likely lead to irreversible world corruption. + +# Content Sources + +Content packs are searched for within **content sources**, which are paths in the engine's file system. + +Source priority determines the scan order: if the same pack is found in multiple sources, the one belonging to the source with the highest priority will be selected. + +Content sources in descending order of priority: +- `world:content` - Content in the world folder (`world/content/`) +- `user:content` - Content in the user folder (`$HOME/.voxeng/content/`) +- `project:content` - Content in the project folder (`project/content/`) +- `res:content` - Built-in content shipped with the engine core (`res/content/`) + +I.e., if the same pack exists in both `world:content` and `user:content`, the version from `world:content` will be selected. + +The pack version, however, is currently not taken into account. diff --git a/doc/en/main-page.md b/doc/en/main-page.md index 5eb767f94..30cb106c5 100644 --- a/doc/en/main-page.md +++ b/doc/en/main-page.md @@ -1,8 +1,9 @@ # Documentation -Documentation for 0.30 (in development). +Documentation for 0.30. -[Documentation for 0.29.](https://github.com/MihailRis/voxelcore/blob/release-0.29/doc/ru/main-page.md) +> [!WARNING] +> Version is in development. Proceed to [Documentation for 0.29.](https://github.com/MihailRis/voxelcore/blob/release-0.29/doc/ru/main-page.md) ## Sections diff --git a/doc/en/scripting/builtins/libapp.md b/doc/en/scripting/builtins/libapp.md index 69a472eef..0ec755689 100644 --- a/doc/en/scripting/builtins/libapp.md +++ b/doc/en/scripting/builtins/libapp.md @@ -7,6 +7,8 @@ The script/test name without the path and extension is available as `app.script` local filename = "script:"..app.script..".lua" ``` +Since the control script may not belong to any of the packs, it does not belongs to its own package and has its own global namespace in which all global functions and tables are available, as well as the `app` library. + ## Functions ```lua @@ -156,3 +158,36 @@ app.get_setting_info(name: str) -> { ``` Returns a table with information about a setting. Throws an exception if the setting does not exist. + +```lua +app.focus() +``` + +Brings the window to front and sets input focus. + +```lua +app.create_memory_device( + -- entry-point name + name: str +) +``` + +Creates an in-memory filesystem. + +```lua +app.get_content_sources() -> table +``` + +Returns a list of content sources (paths), in descending priority order. + +``lua +app.set_content_sources(sources: table) +``` + +Sets a list of content sources (paths). Specified in descending priority order. + +``lua +app.reset_content_sources() +``` + +Resets content sources. diff --git a/doc/en/scripting/builtins/libassets.md b/doc/en/scripting/builtins/libassets.md index bd095435f..b39239a5b 100644 --- a/doc/en/scripting/builtins/libassets.md +++ b/doc/en/scripting/builtins/libassets.md @@ -25,4 +25,12 @@ assets.parse_model( -- Model name after loading name: str ) + +-- Creates a Canvas from a loaded texture. +assets.to_canvas( + -- The name of the loaded texture. + -- Both standalone textures ("texture_name") and + -- those in an atlas ("atlas:texture_name") are supported + name: str +) --> Canvas ``` diff --git a/doc/en/scripting/builtins/libfile.md b/doc/en/scripting/builtins/libfile.md index 73d286af1..0d4374cac 100644 --- a/doc/en/scripting/builtins/libfile.md +++ b/doc/en/scripting/builtins/libfile.md @@ -139,6 +139,12 @@ file.create_zip(directory: str, output_file: str) --> str Creates a ZIP archive from the contents of the specified directory. +```lua +file.create_memory_device() --> str +``` + +Creates a memory file system and returns entry point name. Lives until content unload. + ```lua file.name(path: str) --> str ``` diff --git a/doc/en/scripting/builtins/libhud.md b/doc/en/scripting/builtins/libhud.md index 58d783020..a7e3fa853 100644 --- a/doc/en/scripting/builtins/libhud.md +++ b/doc/en/scripting/builtins/libhud.md @@ -20,6 +20,12 @@ hud.open( [optional] invid: int ) -> int +-- Returns true if specified layout is open. +hud.is_open( + layoutid: str +) -> bool + + -- Open block UI and inventory. -- Throws an exception if block has no UI layout. -- Returns block inventory ID (if *"inventory-size"=0* a virtual diff --git a/doc/en/scripting/builtins/libinput.md b/doc/en/scripting/builtins/libinput.md index 02f2a8d08..9856abc0b 100644 --- a/doc/en/scripting/builtins/libinput.md +++ b/doc/en/scripting/builtins/libinput.md @@ -13,7 +13,16 @@ input.mousecode(mousename: str) --> int Returns mouse button code or -1 if unknown ```lua -input.add_callback(bindname: str, callback: function) +input.add_callback( + -- Binding name + bindname: str, + -- Handler + callback: function + -- UI element that owns the handler (responsible for the handler's lifetime) + [optional] owner: Element, + -- Ignore input capture by UI elements + [optional] istoplevel: bool +) ``` Add binding activation callback. Example: @@ -48,6 +57,12 @@ input.get_mouse_pos() --> {int, int} Returns cursor screen position. +```lua +input.get_mouse_delta() --> {int, int} +``` + +Returns cursor movement delta. + ```lua input.get_bindings() --> strings array ``` diff --git a/doc/en/scripting/builtins/libnetwork.md b/doc/en/scripting/builtins/libnetwork.md index 0d32d8497..3ceac9c84 100644 --- a/doc/en/scripting/builtins/libnetwork.md +++ b/doc/en/scripting/builtins/libnetwork.md @@ -57,7 +57,10 @@ network.tcp_connect( -- Function called upon successful connection -- Sending will not work before connection -- Socket is passed as the only argument - callback: function(Socket) + callback: function(Socket), + -- Function called when a connection error occurs + -- Arguments passed: socket and error text + [optional] error_callback: function(Socket, str) ) --> Socket ``` @@ -79,6 +82,16 @@ socket:recv( -- Returns nil on error (socket is closed or does not exist). -- If there is no data yet, returns an empty byte array. +-- Asynchronous version for use in coroutines. +-- Waits for the entire specified number of bytes to be received. +-- If socket closes, function works like socket:recv +socket:recv_async( + -- Size of the byte array to read + length: int, + -- Use table instead of Bytearray + [optional] usetable: bool=false +) -> nil|table|Bytearray + -- Closes the connection socket:close() @@ -129,3 +142,10 @@ network.get_total_upload() --> int -- in bytes. network.get_total_download() --> int ``` + +## Other + +```lua +-- Looks for a free port to use. +network.find_free_port() --> int or nil +``` diff --git a/doc/en/scripting/builtins/libplayer.md b/doc/en/scripting/builtins/libplayer.md index 4bc814242..7e27d817c 100644 --- a/doc/en/scripting/builtins/libplayer.md +++ b/doc/en/scripting/builtins/libplayer.md @@ -40,13 +40,13 @@ Sets x, y, z player linear velocity player.get_rot(playerid: int, interpolated: bool) -> number, number, number ``` -Returns x, y, z of camera rotation (radians). Interpolation is relevant in cases where the rotation refresh rate is lower than the frame rate. +Returns x, y, z of camera rotation (degrees). Interpolation is relevant in cases where the rotation refresh rate is lower than the frame rate. ```lua player.set_rot(playerid: int, x: number, y: number, z: number) ``` -Set camera rotation (radians) +Set camera rotation (degrees) ```lua player.get_inventory(playerid: int) -> int, int @@ -119,6 +119,18 @@ player.get_name(playerid: int) -> str Player name setter and getter +```lua +player.get_camera(playerid: int) -> int +``` + +Returns the index of the player's current camera. + +```lua +player.set_camera(playerid: int, camera_index: int) +``` + +Switches the player's camera. See [cameras](libcameras.md). + ```lua player.set_selected_slot(playerid: int, slotid: int) ``` diff --git a/doc/en/scripting/events.md b/doc/en/scripting/events.md index 712dcb8c9..fc8bb27f8 100644 --- a/doc/en/scripting/events.md +++ b/doc/en/scripting/events.md @@ -4,6 +4,10 @@ Callbacks specified in block script. +> [!WARNING] +> events such as on_block_tick, on_block_present, and on_block_removed +> can cause performance issues if used carelessly or excessively. + ```lua function on_placed(x, y, z, playerid) ``` @@ -53,6 +57,21 @@ function on_block_tick(x, y, z, tps: number) Called tps (20 / tick-interval) times per second for a block. Use 1/tps instead of `time.delta()`. +```lua +function on_block_present(x, y, z) +``` + +Called for a specific block when it appears in the world (generated/loaded/placed). +The call occurs within a time period that may depend on the event queue load. +Under light load, it occurs during the first tick interval of the block. +on_block_tick is not called until the event is called. + +```lua +function on_block_removed(x, y, z) +``` + +Called when chunk containing the block unloads. + ```lua function on_player_tick(playerid: int, tps: int) ``` @@ -190,6 +209,12 @@ function on_hud_open(playerid: int) Called after world open. +```lua +function on_hud_render() +``` + +Called every frame. Used for client-side tasks such as animation and camera control. + ```lua function on_hud_close(playerid: int) ``` diff --git a/doc/en/scripting/ui.md b/doc/en/scripting/ui.md index 2da6843c1..b52460a7a 100644 --- a/doc/en/scripting/ui.md +++ b/doc/en/scripting/ui.md @@ -37,6 +37,7 @@ Properties that apply to all elements: | Name | Type | Read | Write | Description | | ------------- | ------- | ---- | ----- | ------------------------------------------- | | id | string | yes | *no* | element id | +| exists | bool | yes | *no* | checks if element exists | | pos | vec2 | yes | yes | element position inside a container | | wpos | vec2 | yes | yes | element position inside the window | | size | vec2 | yes | yes | element size | @@ -194,8 +195,21 @@ Here, *color* can be specified in the following ways: | data:clear() | clears the canvas | | data:clear(*color*) | fills the canvas with the specified RGBA color | | data:update() | applies changes to the canvas and uploads it to the GPU | -| data:set_data(data: table) | replaces pixel data (width * height * 4 numbers) | +| data:set_data(data: Bytearray | table) | replaces pixel data (width * height * 4 numbers) | +| data:get_data() | creates a Bytearray object with the image's pixel data | | data:create_texture(name: str) | creates and shares texture to renderer | +| data:unbind_texture() | unbinds the texture from the canvas | +| data:mul(*color* or Canvas) | multiplies a color by the specified color or canvas | +| data:add(*color* or Canvas) | adds a color or another canvas to a color | +| data:sub(*color* or Canvas) | subtracts a color or another canvas to a color | +| data:encode(format: str) | encodes image to specified format and returns bytearray | + +To decode a byte array into a Canvas, use the static method: +```lua +Canvas.decode(data: Bytearray, format: str) -> Canvas +``` + +Currently, only png is supported. ## Inline frame (iframe) diff --git a/doc/en/xml-ui-layouts.md b/doc/en/xml-ui-layouts.md index 1127d79fe..5a4d7be4e 100644 --- a/doc/en/xml-ui-layouts.md +++ b/doc/en/xml-ui-layouts.md @@ -136,6 +136,7 @@ The key code for comparison can be obtained via `input.keycode("key_name")` - `text-wrap` - allows automatic text wrapping (works only with multiline: "true") - `editable` - determines whether the text can be edited. - `line-numbers` - enables line numbers display. +- `keep-line-selection` - keep showing selected line after defocus. - `error-color` - color when entering incorrect data (the text does not pass the validator check). Type: RGBA color. - `text-color` - text color. Type: RGBA color. - `validator` - lua function that checks text for correctness. Takes a string as input, returns true if the text is correct. diff --git a/doc/ru/audio.md b/doc/ru/audio.md index 1fce6c358..8291e7bab 100644 --- a/doc/ru/audio.md +++ b/doc/ru/audio.md @@ -29,6 +29,7 @@ Доступ к спикерам производится по целочисленным id, которые не повторяются за время работы движка, следует избегать хранения прямых указателей на объекты класса. Нумерация ID спикеров начинается с 1. ID 0 означает невозможность воспроизведения, по какой-либо причине. + ### Звук (Sound) Звуковые данные загруженные в память для возможности одновременного воспроизведения из нескольких источников. Может предоставлять доступ к PCM данным. @@ -203,3 +204,79 @@ audio.count_speakers() -> integer -- получить текущее число проигрываемых аудио-потоков audio.count_streams() -> integer ``` + +### audio.PCMStream + +```lua +-- создание источника PCM данных +local stream = audio.PCMStream( + -- частота дискретизации + sample_rate: integer, + -- число каналов (1 - моно, 2 - стерео) + channels: integer, + -- число бит на сэмпл (8 или 16) + bits_per_sample: integer, +) + +-- подача PCM данных в поток +stream:feed( + -- PCM данные для подачи в поток + data: Bytearray +) + +-- публикация источника PCM данных для использования системами движка +stream:share( + -- имя потокового аудио, которое можно будет указать в audio.play_stream + alias: string +) + +-- создание звука из имеющихся в потоке PCM данных +stream:create_sound( + -- имя создаваемого звука + name: string +) +``` + +### Запись звука + +```lua +-- запрашивает доступ к записи звука +-- при подтверждении, в callback передаётся токен для использовании в audio.input.fetch +audio.input.request_open(callback: function(string)) + +-- читает новые PCM данные аудио ввода +audio.input.fetch( + -- токен, полученный через audio.input.request_open + access_token: string, + -- максимальное размер буфера в байтах + [опционально] max_read_size: integer +) +``` + +### Пример генерации аудио: + +```lua +-- для работы с 16-битными семплами используйте U16view поверх Bytearray +-- пример: +local max_amplitude = 32767 +local sample_rate = 44100 +local total_samples = sample_rate * 5 -- 5 секунд моно +local bytes = Bytearray(total_samples * 2) -- 5 секунд 16 бит моно +local samples = I16view(bytes) + +local frequency_hz = 400 +for i=1, total_samples do + local value = math.sin(i * math.pi * 2 / sample_rate * frequency_hz) + samples[i] = value * max_amplitude +end + +local stream_name = "test-stream" +local stream = audio.PCMStream(sample_rate, 1, 16) +stream:feed(bytes) +stream:share(stream_name) + +local volume = 1.0 +local pitch = 1.0 +local channel = "ui" +audio.play_stream_2d(stream_name, volume, pitch, channel) +``` diff --git a/doc/ru/block-properties.md b/doc/ru/block-properties.md index 0003cb927..4569cc493 100644 --- a/doc/ru/block-properties.md +++ b/doc/ru/block-properties.md @@ -106,6 +106,32 @@ Управление состоянием производится через `block.set_variant(x, y, z, index)`. +### Кастомные модели по вариантам (переключение геометрии) + +Для custom-моделей можно переключать геометрию по варианту. Укажите отдельный `model-name` в нужных вариантах — рендерер кэширует геометрию по паре (id блока, вариант). + +Базовая модель (из корня) становится вариантом 0. Массив variants соответствует индексам 1+. + +Пример (база + два кастомных варианта): +```json +{ + "model": "custom", + "model-name": "stairs_middle", + "state-based": { + "bits": 4, + "variants": [ + { "model": "custom", "model-name": "stairs_left" }, + { "model": "custom", "model-name": "stairs_right" } + ] + } +} +``` + +В этом примере: +- Вариант 0 = `stairs_middle` (из корня) +- Вариант 1 = `stairs_left` (из variants[0]) +- Вариант 2 = `stairs_right` (из variants[1]) + ## Освещение ### Излучение - *emission*: diff --git a/doc/ru/content-packs.md b/doc/ru/content-packs.md index 4245555f8..32f9be593 100644 --- a/doc/ru/content-packs.md +++ b/doc/ru/content-packs.md @@ -50,5 +50,80 @@ Изображение контент-пака добавляется в виде файла *icon.png* в папку пака (не в textures). Рекомендованный размер изображения: 128x128 -Новые блоки добавляются в под-папку **blocks**, предметы в **items**, текстуры в **textures** -С примером файловой структуры лучше ознакомиться через базовый пакет (*res/content/base*) +# Файловая система + +Каждый загруженный пак монтируется к внутренней файловой системе как новая точка входа, имя которой совпадает с id пака. + +Это значит, что для доступа к файлам пака не требуется использование дополнительных функций: + +```lua +print(file.read("пак:package.json")) -- выведет содержимое package.json пака +``` + +Также это является одной из причин того, что некоторые id зарезервированы от использования. + +Точки входа монтируются как read-only. Для получения доступа к записи существует функция pack.request_writeable. + +Подробнее про библиотеку [file](scripting/builtins/libfile.md). + +# Структура контент-пака + +Пусть вас не пугает следующий текст, ведь минимальный пак содержит только package.json. + +- Контент: + - `block_materials/` - определения материалов блоков + - `blocks/` - определения блоков + - `items/` - определения предметов + - `generators/` - генераторы мира + - `entities/` - определения сущностей + - `skeletons/` - определения скелетов сущностей + - `presets/` - пресеты (может использоваться и паками в своих целях) + - `text3d/` - 3D текст + - `weather/` - погода +- Код: + - `modules/` - скриптовые модули + - `scripts/` - скрипты контента, мира + - `components/` - компоненты сущностей +- Ассеты (ресурсы клиентской стороны): + - `fonts/` - шрифты + - `models/` - 3D модели + - `textures/` - текстуры + - `shaders/` - шейдеры + - `effects/` - эффекты пост-обработки + - `sounds/` - звуки и музыка + - `texts/` - файлы локализации +- GUI: + - `layouts/` - макеты UI + - `pages/` - макеты страниц меню (элемент pagebox) +- Конфигурация: + - `config/` - файлы конфигурации + - `defaults.toml` - переопределения стандартных привязок к контенту, такие как сущность игрока, генератор по-умолчанию и т.д. + - `bindings.toml` - привязки к клавишам / мыши + - `user-props.toml` - пользовательские свойства определений контента + - `devtools/` - вспомогательные файлы внутренних инструментов отладки + - `content.json` - генерируемые автоматически списки контента, используемое для проверки корректности индексов в мире и конвертации при несовпадении + - `icon.png` - иконка пака + - `package.json` - файл опеределения пака + - `preload.json` - списки предзагрузки ассетов, которые не загружаются автоматически, не следует указывать ассеты без надобности + - `resources.json` - списки определений [ресурсов](resources.md) (не путать с ассетами) + - `resource-aliases.json` - файлы объявления псевдонимов для [ресурсов](resources.md) + +> [!WARNING] +> Ручное редактирование `content.json` противопоказано и скорее всего приведёт к необратимой поломке миров. + +# Источники контента + +Поиск контент-паков производится среди **источников контента**, являющихся путями в файловой системе движка. + +Приоритет источника определяет порядок сканирования: если один и тот же пак найден в нескольких источниках, +будет выбран тот, что принадлежит источнику с наивысшим приоритетом. + +Источники контента в порядке убывания приоритета: +- `world:content` - контент папке с миром (`мир/content/`) +- `user:content` - контент в папке пользователя (`$HOME/.voxeng/content/`) +- `project:content` - контент в папке с проектом (`проект/content/`) +- `res:content` - встроенный контент, поставляемый вместе с ядром движка (`res/content/`) + +Т.е. если в `world:content` и в `user:content` есть один и тот же пак, будет выбрана версия из `world:content`. + +Версия пака, при этом, на данный момент, не учитывается. diff --git a/doc/ru/main-page.md b/doc/ru/main-page.md index 60d0794d7..e74c29155 100644 --- a/doc/ru/main-page.md +++ b/doc/ru/main-page.md @@ -1,8 +1,9 @@ # Документация -Документация версии 0.30 (в разработке). +Документация версии 0.30. -[Документация 0.29.](https://github.com/MihailRis/voxelcore/blob/release-0.29/doc/ru/main-page.md) +> [!WARNING] +> Версия находится в разработке. Перейдите к [документации для 0.28](https://github.com/MihailRis/voxelcore/blob/release-0.29/doc/ru/main-page.md) ## Разделы diff --git a/doc/ru/scripting/builtins/libapp.md b/doc/ru/scripting/builtins/libapp.md index 4e9493c4f..d8a1e81ca 100644 --- a/doc/ru/scripting/builtins/libapp.md +++ b/doc/ru/scripting/builtins/libapp.md @@ -7,6 +7,8 @@ local filename = "script:"..app.script..".lua" ``` +Так как управляющий сценарий может не принадлежать ни одному из паков, он не относиться к своему паку и имеет собственное пространство имён, в котором доступны все глобальные функции и таблицы, а также библиотека `app`. + ## Функции ```lua @@ -157,3 +159,35 @@ app.get_setting_info(name: str) -> { ``` Возвращает таблицу с информацией о настройке. Бросает исключение, если настройки не существует. + +```lua +app.focus() +``` + +Переводит окно на передний план и устанавливает фокус ввода. + +app.create_memory_device( + -- имя точки входа + name: str +) +``` + +Создаёт файловую систему в памяти. + +```lua +app.get_content_sources() -> table +``` + +Возвращает список источников контента (путей), в порядке убывания приоритета. + +```lua +app.set_content_sources(sources: table) +``` + +Устанавливает список источников контента (путей). Указывается в порядке убывания приоритета. + +```lua +app.reset_content_sources() +``` + +Сбрасывает список источников контента. diff --git a/doc/ru/scripting/builtins/libassets.md b/doc/ru/scripting/builtins/libassets.md index 2c5109485..ec069e1a0 100644 --- a/doc/ru/scripting/builtins/libassets.md +++ b/doc/ru/scripting/builtins/libassets.md @@ -25,4 +25,12 @@ assets.parse_model( -- Имя модели после загрузки name: str ) + +-- Создаёт холст (Canvas) из загруженной текстуры +assets.to_canvas( + -- Имя загруженной текстуры. + -- Поддерживается как отдельные ("имя_текстуры"), + -- так и находящиеся в атласе ("атлас:имя_текстуры"). + name: str +) --> Canvas ``` diff --git a/doc/ru/scripting/builtins/libfile.md b/doc/ru/scripting/builtins/libfile.md index 4afbe23c9..67af51785 100644 --- a/doc/ru/scripting/builtins/libfile.md +++ b/doc/ru/scripting/builtins/libfile.md @@ -139,6 +139,12 @@ file.create_zip(директория: str, выходной_файл: str) --> s Создаёт ZIP-архив из содержимого указанной директории. +```lua +file.create_memory_device() --> str +``` + +Создаёт файловую систему в памяти, возвращает имя точки входа. Удаляется при выгрузке контента. + ```lua file.name(путь: str) --> str ``` diff --git a/doc/ru/scripting/builtins/libhud.md b/doc/ru/scripting/builtins/libhud.md index 5f0856b30..6ed81e050 100644 --- a/doc/ru/scripting/builtins/libhud.md +++ b/doc/ru/scripting/builtins/libhud.md @@ -20,6 +20,10 @@ hud.open( [опционально] invid: int ) -> int +-- Возвращает true если указаный макет UI открыт. +hud.is_open( + layoutid: str +) -> bool -- Открывает инвентарь и UI блока. -- Если блок не имеет макета UI - бросается исключение. diff --git a/doc/ru/scripting/builtins/libinput.md b/doc/ru/scripting/builtins/libinput.md index afe22ee62..6aadbdc13 100644 --- a/doc/ru/scripting/builtins/libinput.md +++ b/doc/ru/scripting/builtins/libinput.md @@ -13,7 +13,16 @@ input.mousecode(mousename: str) --> int Возвращает код кнопки мыши по имени, либо -1 ```lua -input.add_callback(bindname: str, callback: function) +input.add_callback( + -- Имя привязки + bindname: str, + -- Обработчик + callback: function + -- UI элемент-владелец обработчика (отвечает за срок жизни) + [опционально] owner: Element, + -- Игнорировать захват ввода UI элементами + [опционально] istoplevel: bool +) ``` Назначает функцию, которая будет вызываться при активации привязки. Пример: @@ -48,6 +57,12 @@ input.get_mouse_pos() --> {int, int} Возвращает позицию курсора на экране. +```lua +input.get_mouse_delta() --> {int, int} +``` + +Возращает дельту позиции курсора. + ```lua input.get_bindings() --> массив строк ``` diff --git a/doc/ru/scripting/builtins/libnetwork.md b/doc/ru/scripting/builtins/libnetwork.md index bef8967c5..8c574f361 100644 --- a/doc/ru/scripting/builtins/libnetwork.md +++ b/doc/ru/scripting/builtins/libnetwork.md @@ -58,6 +58,9 @@ network.tcp_connect( -- До подключения отправка работать не будет -- Как единственный аргумент передаётся сокет callback: function(Socket) + -- Функция, вызываемая при ошибке подключения + -- Как аргументы передаются сокет и текст ошибки + [опционально] error_callback: function(Socket, str) ) --> Socket ``` @@ -79,6 +82,16 @@ socket:recv( -- В случае ошибки возвращает nil (сокет закрыт или несуществует). -- Если данных пока нет, возвращает пустой массив байт. +-- Асинхронный вариант для использования в корутинах. +-- Ожидает получение всего указанного числа байт. +-- При закрытии сокета работает как socket:recv +socket:recv_async( + -- Размер читаемого массива байт + length: int, + -- Использовать таблицу вместо Bytearray + [опционально] usetable: bool=false +) -> nil|table|Bytearray + -- Закрывает соединение socket:close() @@ -194,3 +207,10 @@ network.get_total_upload() --> int -- в байтах. network.get_total_download() --> int ``` + +## Другое + +```lua +-- Ищет свободный для использования порт. +network.find_free_port() --> int или nil +``` diff --git a/doc/ru/scripting/builtins/libplayer.md b/doc/ru/scripting/builtins/libplayer.md index c038ecae4..c789df8d2 100644 --- a/doc/ru/scripting/builtins/libplayer.md +++ b/doc/ru/scripting/builtins/libplayer.md @@ -40,13 +40,13 @@ player.set_vel(playerid: int, x: number, y: number, z: number) player.get_rot(playerid: int, interpolated: bool=false) -> number, number, number ``` -Возвращает x, y, z вращения камеры (в радианах). Интерполяция актуальна в случаях, когда частота обновления вращения ниже частоты кадров. +Возвращает x, y, z вращения камеры (в градусах). Интерполяция актуальна в случаях, когда частота обновления вращения ниже частоты кадров. ```lua player.set_rot(playerid: int, x: number, y: number, z: number) ``` -Устанавливает x, y вращения камеры (в радианах) +Устанавливает x, y вращения камеры (в градусах) ```lua player.get_inventory(playerid: int) -> int, int @@ -119,6 +119,18 @@ player.get_name(playerid: int) -> str Сеттер и геттер имени игрока +```lua +player.get_camera(playerid: int) -> int +``` + +Возвращает индекс текущей камеры игрока + +```lua +player.set_camera(playerid: int, camera_index: int) +``` + +Переключает камеру игрока. См. [камеры](libcameras.md). + ```lua player.set_selected_slot(playerid: int, slotid: int) ``` diff --git a/doc/ru/scripting/events.md b/doc/ru/scripting/events.md index 4ca3fda52..83baef253 100644 --- a/doc/ru/scripting/events.md +++ b/doc/ru/scripting/events.md @@ -4,6 +4,10 @@ Функции для обработки событий, прописываемые в скрипте блока. +> [!WARNING] +> Mass events such as on_block_tick, on_block_present, and on_block_removed, +> if used carelessly or excessively, can lead to performance issues. + ```lua function on_placed(x, y, z, playerid) ``` @@ -53,6 +57,21 @@ function on_block_tick(x, y, z, tps: number) Вызывается tps (20 / tick-interval) раз в секунду для конкретного блока. Используйте 1/tps вместо `time.delta()`. +```lua +function on_block_present(x, y, z) +``` + +Вызывается для конкретного блока при появлении (генерации/загрузке/размещении). +Вызов происходит в течение времени, которое может зависеть от нагрузки очереди событий. +При малой нагрузке происходит в течение первого такта блока (tick-interval). +До вызова события on_block_tick не вызывается. + +```lua +function on_block_removed(x, y, z) +``` + +Вызывается при выгрузке чанка, в котором находится блок. + ```lua function on_player_tick(playerid: int, tps: int) ``` @@ -190,6 +209,12 @@ function on_hud_open(playerid: int) Вызывается после входа в мир, когда становится доступна библиотека hud. Здесь на экран добавляются постоянные элементы. +```lua +function on_hud_render() +``` + +Вызывается каждый кадр. Используется для клиентских задач, таких как анимация, управление камерой. + ```lua function on_hud_close(playerid: int) ``` diff --git a/doc/ru/scripting/ui.md b/doc/ru/scripting/ui.md index 19f1f8abe..d58495413 100644 --- a/doc/ru/scripting/ui.md +++ b/doc/ru/scripting/ui.md @@ -37,6 +37,7 @@ document["worlds-panel"]:clear() | Название | Тип | Чтение | Запись | Описание | | ------------- | ------- | ------ | ------ | ----------------------------------------- | | id | string | да | *нет* | идентификатор элемента | +| exists | bool | да | *нет* | проверяет, существует ли элемент | | pos | vec2 | да | да | позиция элемента внутри контейнера | | wpos | vec2 | да | да | позиция элемента в окне | | size | vec2 | да | да | размер элемента | @@ -185,17 +186,30 @@ document["worlds-panel"]:clear() - r: int, g: int, b: int - r: int, g: int, b: int, a: int -| Метод | Описание | -|----------------------------------------------------------|------------------------------------------------------| -| data:at(x: int, y: int) | возвращает RGBA пиксель по указанным координатам | -| data:set(x: int, y: int, *цвет*) | изменяет RGBA пиксель по указанным координатам | -| data:line(x1: int, y1: int, x2: int, y2: int, *цвет*) | рисует линию с указанным RGBA цветом | -| data:blit(src: Canvas, dst_x: int, dst_y: int) | рисует src-холст на указанных координатах | -| data:clear() | очищает холст | -| data:clear(*цвет*) | заполняет холст указанным RGBA цветом | -| data:update() | применяет изменения и загружает холст в видеопамять | -| data:set_data(data: table) | заменяет данные пикселей (ширина * высота * 4 чисел) | -| data:create_texture(name: str) | создаёт и делится текстурой с рендерером | +| Метод | Описание | +|----------------------------------------------------------|-----------------------------------------------------------------| +| data:at(x: int, y: int) | возвращает RGBA пиксель по указанным координатам | +| data:set(x: int, y: int, *цвет*) | изменяет RGBA пиксель по указанным координатам | +| data:line(x1: int, y1: int, x2: int, y2: int, *цвет*) | рисует линию с указанным RGBA цветом | +| data:blit(src: Canvas, dst_x: int, dst_y: int) | рисует src-холст на указанных координатах | +| data:clear() | очищает холст | +| data:clear(*цвет*) | заполняет холст указанным RGBA цветом | +| data:update() | применяет изменения и загружает холст в видеопамять | +| data:set_data(data: bytearray | table) | заменяет данные пикселей (ширина * высота * 4 чисел) | +| data:get_data() | создаёт объект Bytearray с пиксельными данными изображения | +| data:create_texture(name: str) | создаёт и делится текстурой с рендерером | +| data:unbind_texture() | отвязывает текстуру от холста | +| data:mul(*цвет* или Canvas) | умножает увет на указанный цвет или холст | +| data:add(*цвет* или Canvas) | прибавляет цвет или другой холст к цвету | +| data:sub(*цвет* или Canvas) | вычитает цвет или другой холст к цвету | +| data:encode(format: str) | кодирует изображение в указанный формат и возращает массив байт | + +Для декодирования массива байт в Canvas используйте статический метод: +```lua +Canvas.decode(data: Bytearray, format: str) -> Canvas +``` + +На данный момент, из форматов поддерживается только png. ## Рамка встраивания (iframe) diff --git a/doc/ru/xml-ui-layouts.md b/doc/ru/xml-ui-layouts.md index a2f068cab..b6efa7b29 100644 --- a/doc/ru/xml-ui-layouts.md +++ b/doc/ru/xml-ui-layouts.md @@ -137,6 +137,7 @@ - `text-wrap` - разрешает автоматический перенос текста (работает только при multiline: "true") - `editable`- определяет возможность редактирования текста. - `line-numbers` - включает отображение номеров строк. +- `keep-line-selection` - продолжать отображать выбранную строку при потере фокуса. - `error-color` - цвет при вводе некорректных данных (текст не проходит проверку валидатора). Тип: RGBA цвет. - `text-color` - цвет текста. Тип: RGBA цвет. - `validator` - lua функция, проверяющая текст на корректность. Принимает на вход строку, возвращает true если текст корректен. diff --git a/doc/specs/debugging_protocol.md b/doc/specs/debugging_protocol.md new file mode 100644 index 000000000..ba4cf96a8 --- /dev/null +++ b/doc/specs/debugging_protocol.md @@ -0,0 +1,153 @@ +# VC-DBG protocol v1 + +## Notes + +- '?' in name means that the attribute is optional. + +## Connecting + +### Step 1 + +Connection initiating with binary header exchange: + +``` +'v' 'c' '-' 'd' 'b' 'g' NUL XX +76 63 2D 64 62 67 00 XX +``` + +XX - protocol version number + +Client sends header to the server. Then server responds. +Server closes connection after sending header if it mismatches. + +### Step 2 + +Client sends 'connect' command. + +## Messages + +Message is: +- 32 bit little-endian unsigned integer - number of encoded message bytes. +- message itself (UTF-8 encoded json). + +## Client-to-server + +### Establishing connection + +```json +{ + "type": "connect", + "?disconnect-action": "resume|detach|terminate" +} +``` + +Configuring connection. Disconnect-action is action that debugged instance must perform on debugging client connection closed/refused. + +- `resume` - Resume from pause mode and start listening for client. +- `detach` - Resume from pause mode and stop server. +- `terminate` - Stop the debugged application. + + +### Specific action signals. + +```json +{ + "type": "pause|resume|terminate|detach" +} +``` + +### Breakpoints management + +```json +{ + "type": "set-breakpoint|remove-breakpoint", + "source": "entry_point:path", + "line": 1 +} +``` + +### Local value details request + +```json +{ + "type": "get-value", + "frame": 0, + "local": 1, + "path": ["path", "to", 1, "value"] +} +``` + +- `frame` - Call stack frame index (indexing from most recent call) +- `local` - Local variable index (based on `paused` event stack trace) +- `path` - Requsted value path segments. Example: `['a', 'b', 5]` is `local_variable.a.b[5]` + +Responds with: + +```json +{ + "type": "value", + "frame": 0, + "local": 1, + "path": ["path", "to", 1, "value"], + "value": "value itself" +} +``` + +Example: actual value is table: +```lua +{a=5, b="test", 2={}} +``` + +Then `value` is: +```json +{ + "a": { + "type": "number", + "short": "5" + }, + "b": { + "type": "string", + "short": "test" + }, + "1": { + "type": "table", + "short": "{...}" + } +} +``` + +## Server-to-client + +### Response signals + +```json +{ + "type": "success|resumed" +} +``` + +### Pause event + +```json +{ + "type": "paused", + "?reason": "breakpoint|exception|step", + "?message": "...", + "?stack": [ + { + "?function": "function name", + "source": "source name", + "what": "what", + "line": 1, + "locals": [ + "name": { + "type": "local type", + "index": 1, + "short": "short value", + "size": 0 + } + ] + } + ] +} +``` diff --git a/res/devtools/syntax/lua.toml b/res/devtools/syntax/lua.toml index cbccce751..d2469d2fb 100644 --- a/res/devtools/syntax/lua.toml +++ b/res/devtools/syntax/lua.toml @@ -8,5 +8,5 @@ multiline-string-end = "]]" keywords = [ "and", "break", "do", "else", "elseif", "end", "false", "for", "function", "if", "in", "local", "nil", "not", "or", "repeat", "return", "then", "true", - "until", "while" + "until", "while", "self", "error" ] diff --git a/res/layouts/pages/scripts.xml.lua b/res/layouts/pages/scripts.xml.lua index dad0d1ff7..7e8a266f7 100644 --- a/res/layouts/pages/scripts.xml.lua +++ b/res/layouts/pages/scripts.xml.lua @@ -1,18 +1,10 @@ function run_script(path) - debug.log("starting application script "..path) - - local code = file.read(path) - local chunk, err = loadstring(code, path) - if chunk == nil then - error(err) - end - setfenv(chunk, setmetatable({app=__vc_app}, {__index=_G})) - start_coroutine(chunk, path) + __vc_start_app_script(path, path) end function refresh() document.list:clear() - + local allpacks = table.merge(pack.get_available(), pack.get_installed()) local infos = pack.get_info(allpacks) for _, name in ipairs(allpacks) do diff --git a/res/layouts/pages/settings_audio.xml.lua b/res/layouts/pages/settings_audio.xml.lua index 9b29bd52e..298598190 100644 --- a/res/layouts/pages/settings_audio.xml.lua +++ b/res/layouts/pages/settings_audio.xml.lua @@ -24,10 +24,46 @@ function update_setting(x, id, name, postfix) ) end +local initialized = false + function on_open() + if not initialized then + initialized = true + local token = audio.input.__get_core_token() + document.root:add("") + local prev_amplitude = 0.0 + document.tm:setInterval(16, function() + audio.input.fetch(token) + local amplitude = audio.input.get_max_amplitude() + if amplitude > 0.0 then + amplitude = math.sqrt(amplitude) + end + amplitude = math.max(amplitude, prev_amplitude - time.delta()) + document.input_volume_inner.size = { + prev_amplitude * + document.input_volume_outer.size[1], + document.input_volume_outer.size[2] + } + prev_amplitude = amplitude + end) + end create_setting("audio.volume-master", "Master Volume", 0.01) create_setting("audio.volume-regular", "Regular Sounds", 0.01) create_setting("audio.volume-ui", "UI Sounds", 0.01) create_setting("audio.volume-ambient", "Ambient", 0.01) create_setting("audio.volume-music", "Music", 0.01) + document.root:add("") + document.root:add(" input; + }; + WindowControl(Engine& engine); + + Result initialize(); + + void nextFrame(bool waitForRefresh); + + void saveScreenshot(); + + void toggleFullscreen(); +private: + Engine& engine; +}; diff --git a/src/frontend/ContentGfxCache.cpp b/src/frontend/ContentGfxCache.cpp index 47443aff5..20b7cdaa7 100644 --- a/src/frontend/ContentGfxCache.cpp +++ b/src/frontend/ContentGfxCache.cpp @@ -62,7 +62,7 @@ void ContentGfxCache::refreshVariant( } } } - models[def.rt.id] = std::move(model); + models[modelKey(def.rt.id, variantIndex)] = std::move(model); } } @@ -94,8 +94,8 @@ void ContentGfxCache::refresh() { ContentGfxCache::~ContentGfxCache() = default; -const model::Model& ContentGfxCache::getModel(blockid_t id) const { - const auto& found = models.find(id); +const model::Model& ContentGfxCache::getModel(blockid_t id, uint8_t variant) const { + const auto& found = models.find(modelKey(id, variant)); if (found == models.end()) { throw std::runtime_error("model not found"); } diff --git a/src/frontend/ContentGfxCache.hpp b/src/frontend/ContentGfxCache.hpp index 8eda04e57..2d2bacaa1 100644 --- a/src/frontend/ContentGfxCache.hpp +++ b/src/frontend/ContentGfxCache.hpp @@ -27,7 +27,11 @@ class ContentGfxCache { // array of block sides uv regions (6 per block) std::unique_ptr sideregions; - std::unordered_map models; + std::unordered_map models; + + static inline uint64_t modelKey(blockid_t id, uint8_t variant) { + return (uint64_t(id) << 8) | uint64_t(variant & 0xFF); + } void refreshVariant( const Block& def, @@ -53,7 +57,7 @@ public: return sideregions[getRegionIndex(id, variant, side, !dense)]; } - const model::Model& getModel(blockid_t id) const; + const model::Model& getModel(blockid_t id, uint8_t variant) const; void refresh(const Block& block, const Atlas& atlas); diff --git a/src/frontend/LevelFrontend.cpp b/src/frontend/LevelFrontend.cpp index 5dfdf8227..9a8553da6 100644 --- a/src/frontend/LevelFrontend.cpp +++ b/src/frontend/LevelFrontend.cpp @@ -49,8 +49,7 @@ LevelFrontend::LevelFrontend( auto sound = rassets.get(material->stepsSound); glm::vec3 pos {}; auto soundsCamera = currentPlayer->currentCamera.get(); - if (soundsCamera == currentPlayer->spCamera.get() || - soundsCamera == currentPlayer->tpCamera.get()) { + if (currentPlayer->isCurrentCameraBuiltin()) { soundsCamera = currentPlayer->fpCamera.get(); } bool relative = player == currentPlayer && diff --git a/src/frontend/debug_panel.cpp b/src/frontend/debug_panel.cpp index 10c6b85c0..32439b659 100644 --- a/src/frontend/debug_panel.cpp +++ b/src/frontend/debug_panel.cpp @@ -9,6 +9,7 @@ #include "graphics/ui/elements/TextBox.hpp" #include "graphics/ui/elements/TrackBar.hpp" #include "graphics/ui/elements/InputBindBox.hpp" +#include "graphics/ui/GUI.hpp" #include "graphics/render/WorldRenderer.hpp" #include "graphics/render/ParticlesRenderer.hpp" #include "graphics/render/ChunksRenderer.hpp" @@ -43,10 +44,15 @@ static std::shared_ptr