mirror of
https://github.com/MihailRis/voxelcore.git
synced 2026-10-04 10:31:50 +00:00
Merge branch 'main' into continue-sdl-support-implementation
This commit is contained in:
commit
7f7533f33e
214 changed files with 5299 additions and 1731 deletions
2
.github/workflows/appimage.yml
vendored
2
.github/workflows/appimage.yml
vendored
|
|
@ -2,7 +2,7 @@ name: x86-64 AppImage
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [ "main", "dev", "release-**"]
|
branches: [ "main", "release-**"]
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [ "main", "dev" ]
|
branches: [ "main", "dev" ]
|
||||||
|
|
||||||
|
|
|
||||||
2
.github/workflows/macos.yml
vendored
2
.github/workflows/macos.yml
vendored
|
|
@ -2,7 +2,7 @@ name: Macos Build
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [ "main", "dev", "release-**"]
|
branches: [ "main", "release-**"]
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [ "main", "dev" ]
|
branches: [ "main", "dev" ]
|
||||||
|
|
||||||
|
|
|
||||||
2
.github/workflows/windows-clang.yml
vendored
2
.github/workflows/windows-clang.yml
vendored
|
|
@ -2,7 +2,7 @@ name: Windows Build (CLang)
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [ "main", "dev", "release-**"]
|
branches: [ "main", "release-**"]
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [ "main", "dev" ]
|
branches: [ "main", "dev" ]
|
||||||
|
|
||||||
|
|
|
||||||
2
.github/workflows/windows.yml
vendored
2
.github/workflows/windows.yml
vendored
|
|
@ -2,7 +2,7 @@ name: MSVC Build
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [ "main", "dev", "release-**"]
|
branches: [ "main", "release-**"]
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [ "main", "dev" ]
|
branches: [ "main", "dev" ]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
## Latest release
|
## Latest release
|
||||||
|
|
||||||
- [Download](https://github.com/MihailRis/VoxelCore/releases/latest) | [Скачать](https://github.com/MihailRis/VoxelCore/releases/latest)
|
- [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)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ assert(file.exists("config:"))
|
||||||
debug.log("write text file")
|
debug.log("write text file")
|
||||||
assert(file.write("config:text.txt", "example, пример"))
|
assert(file.write("config:text.txt", "example, пример"))
|
||||||
assert(file.exists("config:text.txt"))
|
assert(file.exists("config:text.txt"))
|
||||||
|
assert(file.isfile("config:text.txt"))
|
||||||
|
|
||||||
debug.log("read text file")
|
debug.log("read text file")
|
||||||
assert(file.read("config:text.txt") == "example, пример")
|
assert(file.read("config:text.txt") == "example, пример")
|
||||||
|
|
@ -18,11 +19,23 @@ assert(file.isdir("config:dir"))
|
||||||
|
|
||||||
debug.log("remove directory")
|
debug.log("remove directory")
|
||||||
file.remove("config:dir")
|
file.remove("config:dir")
|
||||||
|
assert(not file.isdir("config:dir"))
|
||||||
|
|
||||||
debug.log("create directories")
|
debug.log("create directories")
|
||||||
file.mkdirs("config:dir/subdir/other")
|
file.mkdirs("config:dir/subdir/other")
|
||||||
assert(file.isdir("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")
|
debug.log("remove tree")
|
||||||
file.remove_tree("config:dir")
|
file.remove_tree("config:dir")
|
||||||
assert(not file.isdir("config:dir"))
|
assert(not file.isdir("config:dir"))
|
||||||
|
|
|
||||||
67
dev/tests/memory_filesystem.lua
Normal file
67
dev/tests/memory_filesystem.lua
Normal file
|
|
@ -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"))
|
||||||
|
|
@ -203,3 +203,79 @@ audio.count_speakers() -> integer
|
||||||
-- get current number of playing streams
|
-- get current number of playing streams
|
||||||
audio.count_streams() -> integer
|
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)
|
||||||
|
```
|
||||||
|
|
|
||||||
|
|
@ -98,6 +98,32 @@ Properties available for variance:
|
||||||
|
|
||||||
Variants are managed via `block.set_variant(x, y, z, index)`.
|
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
|
## Lighting
|
||||||
|
|
||||||
### *emission*
|
### *emission*
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,79 @@ Example:
|
||||||
|
|
||||||
Content pack picture should be added as *icon.png* file. Recommended size: 128x128
|
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.
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
# Documentation
|
# 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
|
## Sections
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,8 @@ The script/test name without the path and extension is available as `app.script`
|
||||||
local filename = "script:"..app.script..".lua"
|
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
|
## Functions
|
||||||
|
|
||||||
```lua
|
```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.
|
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<string>
|
||||||
|
```
|
||||||
|
|
||||||
|
Returns a list of content sources (paths), in descending priority order.
|
||||||
|
|
||||||
|
``lua
|
||||||
|
app.set_content_sources(sources: table<string>)
|
||||||
|
```
|
||||||
|
|
||||||
|
Sets a list of content sources (paths). Specified in descending priority order.
|
||||||
|
|
||||||
|
``lua
|
||||||
|
app.reset_content_sources()
|
||||||
|
```
|
||||||
|
|
||||||
|
Resets content sources.
|
||||||
|
|
|
||||||
|
|
@ -25,4 +25,12 @@ assets.parse_model(
|
||||||
-- Model name after loading
|
-- Model name after loading
|
||||||
name: str
|
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
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -139,6 +139,12 @@ file.create_zip(directory: str, output_file: str) --> str
|
||||||
|
|
||||||
Creates a ZIP archive from the contents of the specified directory.
|
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
|
```lua
|
||||||
file.name(path: str) --> str
|
file.name(path: str) --> str
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,12 @@ hud.open(
|
||||||
[optional] invid: int
|
[optional] invid: int
|
||||||
) -> int
|
) -> int
|
||||||
|
|
||||||
|
-- Returns true if specified layout is open.
|
||||||
|
hud.is_open(
|
||||||
|
layoutid: str
|
||||||
|
) -> bool
|
||||||
|
|
||||||
|
|
||||||
-- Open block UI and inventory.
|
-- Open block UI and inventory.
|
||||||
-- Throws an exception if block has no UI layout.
|
-- Throws an exception if block has no UI layout.
|
||||||
-- Returns block inventory ID (if *"inventory-size"=0* a virtual
|
-- Returns block inventory ID (if *"inventory-size"=0* a virtual
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,16 @@ input.mousecode(mousename: str) --> int
|
||||||
Returns mouse button code or -1 if unknown
|
Returns mouse button code or -1 if unknown
|
||||||
|
|
||||||
```lua
|
```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:
|
Add binding activation callback. Example:
|
||||||
|
|
@ -48,6 +57,12 @@ input.get_mouse_pos() --> {int, int}
|
||||||
|
|
||||||
Returns cursor screen position.
|
Returns cursor screen position.
|
||||||
|
|
||||||
|
```lua
|
||||||
|
input.get_mouse_delta() --> {int, int}
|
||||||
|
```
|
||||||
|
|
||||||
|
Returns cursor movement delta.
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
input.get_bindings() --> strings array
|
input.get_bindings() --> strings array
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,10 @@ network.tcp_connect(
|
||||||
-- Function called upon successful connection
|
-- Function called upon successful connection
|
||||||
-- Sending will not work before connection
|
-- Sending will not work before connection
|
||||||
-- Socket is passed as the only argument
|
-- 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
|
) --> Socket
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -79,6 +82,16 @@ socket:recv(
|
||||||
-- Returns nil on error (socket is closed or does not exist).
|
-- Returns nil on error (socket is closed or does not exist).
|
||||||
-- If there is no data yet, returns an empty byte array.
|
-- 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
|
-- Closes the connection
|
||||||
socket:close()
|
socket:close()
|
||||||
|
|
||||||
|
|
@ -129,3 +142,10 @@ network.get_total_upload() --> int
|
||||||
-- in bytes.
|
-- in bytes.
|
||||||
network.get_total_download() --> int
|
network.get_total_download() --> int
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Other
|
||||||
|
|
||||||
|
```lua
|
||||||
|
-- Looks for a free port to use.
|
||||||
|
network.find_free_port() --> int or nil
|
||||||
|
```
|
||||||
|
|
|
||||||
|
|
@ -40,13 +40,13 @@ Sets x, y, z player linear velocity
|
||||||
player.get_rot(playerid: int, interpolated: bool) -> number, number, number
|
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
|
```lua
|
||||||
player.set_rot(playerid: int, x: number, y: number, z: number)
|
player.set_rot(playerid: int, x: number, y: number, z: number)
|
||||||
```
|
```
|
||||||
|
|
||||||
Set camera rotation (radians)
|
Set camera rotation (degrees)
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
player.get_inventory(playerid: int) -> int, int
|
player.get_inventory(playerid: int) -> int, int
|
||||||
|
|
@ -119,6 +119,18 @@ player.get_name(playerid: int) -> str
|
||||||
|
|
||||||
Player name setter and getter
|
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
|
```lua
|
||||||
player.set_selected_slot(playerid: int, slotid: int)
|
player.set_selected_slot(playerid: int, slotid: int)
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,10 @@
|
||||||
|
|
||||||
Callbacks specified in block script.
|
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
|
```lua
|
||||||
function on_placed(x, y, z, playerid)
|
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.
|
Called tps (20 / tick-interval) times per second for a block.
|
||||||
Use 1/tps instead of `time.delta()`.
|
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
|
```lua
|
||||||
function on_player_tick(playerid: int, tps: int)
|
function on_player_tick(playerid: int, tps: int)
|
||||||
```
|
```
|
||||||
|
|
@ -190,6 +209,12 @@ function on_hud_open(playerid: int)
|
||||||
|
|
||||||
Called after world open.
|
Called after world open.
|
||||||
|
|
||||||
|
```lua
|
||||||
|
function on_hud_render()
|
||||||
|
```
|
||||||
|
|
||||||
|
Called every frame. Used for client-side tasks such as animation and camera control.
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
function on_hud_close(playerid: int)
|
function on_hud_close(playerid: int)
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@ Properties that apply to all elements:
|
||||||
| Name | Type | Read | Write | Description |
|
| Name | Type | Read | Write | Description |
|
||||||
| ------------- | ------- | ---- | ----- | ------------------------------------------- |
|
| ------------- | ------- | ---- | ----- | ------------------------------------------- |
|
||||||
| id | string | yes | *no* | element id |
|
| id | string | yes | *no* | element id |
|
||||||
|
| exists | bool | yes | *no* | checks if element exists |
|
||||||
| pos | vec2 | yes | yes | element position inside a container |
|
| pos | vec2 | yes | yes | element position inside a container |
|
||||||
| wpos | vec2 | yes | yes | element position inside the window |
|
| wpos | vec2 | yes | yes | element position inside the window |
|
||||||
| size | vec2 | yes | yes | element size |
|
| 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() | clears the canvas |
|
||||||
| data:clear(*color*) | fills the canvas with the specified RGBA color |
|
| 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:update() | applies changes to the canvas and uploads it to the GPU |
|
||||||
| data:set_data(data: table<int>) | replaces pixel data (width * height * 4 numbers) |
|
| data:set_data(data: Bytearray | table<int>) | 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: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)
|
## Inline frame (iframe)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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")
|
- `text-wrap` - allows automatic text wrapping (works only with multiline: "true")
|
||||||
- `editable` - determines whether the text can be edited.
|
- `editable` - determines whether the text can be edited.
|
||||||
- `line-numbers` - enables line numbers display.
|
- `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.
|
- `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.
|
- `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.
|
- `validator` - lua function that checks text for correctness. Takes a string as input, returns true if the text is correct.
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@
|
||||||
Доступ к спикерам производится по целочисленным id, которые не повторяются за время работы движка, следует избегать хранения прямых указателей на объекты класса.
|
Доступ к спикерам производится по целочисленным id, которые не повторяются за время работы движка, следует избегать хранения прямых указателей на объекты класса.
|
||||||
|
|
||||||
Нумерация ID спикеров начинается с 1. ID 0 означает невозможность воспроизведения, по какой-либо причине.
|
Нумерация ID спикеров начинается с 1. ID 0 означает невозможность воспроизведения, по какой-либо причине.
|
||||||
|
|
||||||
### Звук (Sound)
|
### Звук (Sound)
|
||||||
|
|
||||||
Звуковые данные загруженные в память для возможности одновременного воспроизведения из нескольких источников. Может предоставлять доступ к PCM данным.
|
Звуковые данные загруженные в память для возможности одновременного воспроизведения из нескольких источников. Может предоставлять доступ к PCM данным.
|
||||||
|
|
@ -203,3 +204,79 @@ audio.count_speakers() -> integer
|
||||||
-- получить текущее число проигрываемых аудио-потоков
|
-- получить текущее число проигрываемых аудио-потоков
|
||||||
audio.count_streams() -> 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)
|
||||||
|
```
|
||||||
|
|
|
||||||
|
|
@ -106,6 +106,32 @@
|
||||||
|
|
||||||
Управление состоянием производится через `block.set_variant(x, y, z, index)`.
|
Управление состоянием производится через `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*:
|
### Излучение - *emission*:
|
||||||
|
|
|
||||||
|
|
@ -50,5 +50,80 @@
|
||||||
|
|
||||||
Изображение контент-пака добавляется в виде файла *icon.png* в папку пака (не в textures). Рекомендованный размер изображения: 128x128
|
Изображение контент-пака добавляется в виде файла *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`.
|
||||||
|
|
||||||
|
Версия пака, при этом, на данный момент, не учитывается.
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
||||||
## Разделы
|
## Разделы
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,8 @@
|
||||||
local filename = "script:"..app.script..".lua"
|
local filename = "script:"..app.script..".lua"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Так как управляющий сценарий может не принадлежать ни одному из паков, он не относиться к своему паку и имеет собственное пространство имён, в котором доступны все глобальные функции и таблицы, а также библиотека `app`.
|
||||||
|
|
||||||
## Функции
|
## Функции
|
||||||
|
|
||||||
```lua
|
```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<string>
|
||||||
|
```
|
||||||
|
|
||||||
|
Возвращает список источников контента (путей), в порядке убывания приоритета.
|
||||||
|
|
||||||
|
```lua
|
||||||
|
app.set_content_sources(sources: table<string>)
|
||||||
|
```
|
||||||
|
|
||||||
|
Устанавливает список источников контента (путей). Указывается в порядке убывания приоритета.
|
||||||
|
|
||||||
|
```lua
|
||||||
|
app.reset_content_sources()
|
||||||
|
```
|
||||||
|
|
||||||
|
Сбрасывает список источников контента.
|
||||||
|
|
|
||||||
|
|
@ -25,4 +25,12 @@ assets.parse_model(
|
||||||
-- Имя модели после загрузки
|
-- Имя модели после загрузки
|
||||||
name: str
|
name: str
|
||||||
)
|
)
|
||||||
|
|
||||||
|
-- Создаёт холст (Canvas) из загруженной текстуры
|
||||||
|
assets.to_canvas(
|
||||||
|
-- Имя загруженной текстуры.
|
||||||
|
-- Поддерживается как отдельные ("имя_текстуры"),
|
||||||
|
-- так и находящиеся в атласе ("атлас:имя_текстуры").
|
||||||
|
name: str
|
||||||
|
) --> Canvas
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -139,6 +139,12 @@ file.create_zip(директория: str, выходной_файл: str) --> s
|
||||||
|
|
||||||
Создаёт ZIP-архив из содержимого указанной директории.
|
Создаёт ZIP-архив из содержимого указанной директории.
|
||||||
|
|
||||||
|
```lua
|
||||||
|
file.create_memory_device() --> str
|
||||||
|
```
|
||||||
|
|
||||||
|
Создаёт файловую систему в памяти, возвращает имя точки входа. Удаляется при выгрузке контента.
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
file.name(путь: str) --> str
|
file.name(путь: str) --> str
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,10 @@ hud.open(
|
||||||
[опционально] invid: int
|
[опционально] invid: int
|
||||||
) -> int
|
) -> int
|
||||||
|
|
||||||
|
-- Возвращает true если указаный макет UI открыт.
|
||||||
|
hud.is_open(
|
||||||
|
layoutid: str
|
||||||
|
) -> bool
|
||||||
|
|
||||||
-- Открывает инвентарь и UI блока.
|
-- Открывает инвентарь и UI блока.
|
||||||
-- Если блок не имеет макета UI - бросается исключение.
|
-- Если блок не имеет макета UI - бросается исключение.
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,16 @@ input.mousecode(mousename: str) --> int
|
||||||
Возвращает код кнопки мыши по имени, либо -1
|
Возвращает код кнопки мыши по имени, либо -1
|
||||||
|
|
||||||
```lua
|
```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
|
```lua
|
||||||
input.get_bindings() --> массив строк
|
input.get_bindings() --> массив строк
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,9 @@ network.tcp_connect(
|
||||||
-- До подключения отправка работать не будет
|
-- До подключения отправка работать не будет
|
||||||
-- Как единственный аргумент передаётся сокет
|
-- Как единственный аргумент передаётся сокет
|
||||||
callback: function(Socket)
|
callback: function(Socket)
|
||||||
|
-- Функция, вызываемая при ошибке подключения
|
||||||
|
-- Как аргументы передаются сокет и текст ошибки
|
||||||
|
[опционально] error_callback: function(Socket, str)
|
||||||
) --> Socket
|
) --> Socket
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -79,6 +82,16 @@ socket:recv(
|
||||||
-- В случае ошибки возвращает nil (сокет закрыт или несуществует).
|
-- В случае ошибки возвращает nil (сокет закрыт или несуществует).
|
||||||
-- Если данных пока нет, возвращает пустой массив байт.
|
-- Если данных пока нет, возвращает пустой массив байт.
|
||||||
|
|
||||||
|
-- Асинхронный вариант для использования в корутинах.
|
||||||
|
-- Ожидает получение всего указанного числа байт.
|
||||||
|
-- При закрытии сокета работает как socket:recv
|
||||||
|
socket:recv_async(
|
||||||
|
-- Размер читаемого массива байт
|
||||||
|
length: int,
|
||||||
|
-- Использовать таблицу вместо Bytearray
|
||||||
|
[опционально] usetable: bool=false
|
||||||
|
) -> nil|table|Bytearray
|
||||||
|
|
||||||
-- Закрывает соединение
|
-- Закрывает соединение
|
||||||
socket:close()
|
socket:close()
|
||||||
|
|
||||||
|
|
@ -194,3 +207,10 @@ network.get_total_upload() --> int
|
||||||
-- в байтах.
|
-- в байтах.
|
||||||
network.get_total_download() --> int
|
network.get_total_download() --> int
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Другое
|
||||||
|
|
||||||
|
```lua
|
||||||
|
-- Ищет свободный для использования порт.
|
||||||
|
network.find_free_port() --> int или nil
|
||||||
|
```
|
||||||
|
|
|
||||||
|
|
@ -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
|
player.get_rot(playerid: int, interpolated: bool=false) -> number, number, number
|
||||||
```
|
```
|
||||||
|
|
||||||
Возвращает x, y, z вращения камеры (в радианах). Интерполяция актуальна в случаях, когда частота обновления вращения ниже частоты кадров.
|
Возвращает x, y, z вращения камеры (в градусах). Интерполяция актуальна в случаях, когда частота обновления вращения ниже частоты кадров.
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
player.set_rot(playerid: int, x: number, y: number, z: number)
|
player.set_rot(playerid: int, x: number, y: number, z: number)
|
||||||
```
|
```
|
||||||
|
|
||||||
Устанавливает x, y вращения камеры (в радианах)
|
Устанавливает x, y вращения камеры (в градусах)
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
player.get_inventory(playerid: int) -> int, int
|
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
|
```lua
|
||||||
player.set_selected_slot(playerid: int, slotid: int)
|
player.set_selected_slot(playerid: int, slotid: int)
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -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
|
```lua
|
||||||
function on_placed(x, y, z, playerid)
|
function on_placed(x, y, z, playerid)
|
||||||
```
|
```
|
||||||
|
|
@ -53,6 +57,21 @@ function on_block_tick(x, y, z, tps: number)
|
||||||
Вызывается tps (20 / tick-interval) раз в секунду для конкретного блока.
|
Вызывается tps (20 / tick-interval) раз в секунду для конкретного блока.
|
||||||
Используйте 1/tps вместо `time.delta()`.
|
Используйте 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
|
```lua
|
||||||
function on_player_tick(playerid: int, tps: int)
|
function on_player_tick(playerid: int, tps: int)
|
||||||
```
|
```
|
||||||
|
|
@ -190,6 +209,12 @@ function on_hud_open(playerid: int)
|
||||||
|
|
||||||
Вызывается после входа в мир, когда становится доступна библиотека hud. Здесь на экран добавляются постоянные элементы.
|
Вызывается после входа в мир, когда становится доступна библиотека hud. Здесь на экран добавляются постоянные элементы.
|
||||||
|
|
||||||
|
```lua
|
||||||
|
function on_hud_render()
|
||||||
|
```
|
||||||
|
|
||||||
|
Вызывается каждый кадр. Используется для клиентских задач, таких как анимация, управление камерой.
|
||||||
|
|
||||||
```lua
|
```lua
|
||||||
function on_hud_close(playerid: int)
|
function on_hud_close(playerid: int)
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,7 @@ document["worlds-panel"]:clear()
|
||||||
| Название | Тип | Чтение | Запись | Описание |
|
| Название | Тип | Чтение | Запись | Описание |
|
||||||
| ------------- | ------- | ------ | ------ | ----------------------------------------- |
|
| ------------- | ------- | ------ | ------ | ----------------------------------------- |
|
||||||
| id | string | да | *нет* | идентификатор элемента |
|
| id | string | да | *нет* | идентификатор элемента |
|
||||||
|
| exists | bool | да | *нет* | проверяет, существует ли элемент |
|
||||||
| pos | vec2 | да | да | позиция элемента внутри контейнера |
|
| pos | vec2 | да | да | позиция элемента внутри контейнера |
|
||||||
| wpos | vec2 | да | да | позиция элемента в окне |
|
| wpos | vec2 | да | да | позиция элемента в окне |
|
||||||
| size | vec2 | да | да | размер элемента |
|
| size | vec2 | да | да | размер элемента |
|
||||||
|
|
@ -185,17 +186,30 @@ document["worlds-panel"]:clear()
|
||||||
- r: int, g: int, b: int
|
- r: int, g: int, b: int
|
||||||
- r: int, g: int, b: int, a: int
|
- r: int, g: int, b: int, a: int
|
||||||
|
|
||||||
| Метод | Описание |
|
| Метод | Описание |
|
||||||
|----------------------------------------------------------|------------------------------------------------------|
|
|----------------------------------------------------------|-----------------------------------------------------------------|
|
||||||
| data:at(x: int, y: int) | возвращает RGBA пиксель по указанным координатам |
|
| data:at(x: int, y: int) | возвращает RGBA пиксель по указанным координатам |
|
||||||
| data:set(x: int, y: int, *цвет*) | изменяет RGBA пиксель по указанным координатам |
|
| data:set(x: int, y: int, *цвет*) | изменяет RGBA пиксель по указанным координатам |
|
||||||
| data:line(x1: int, y1: int, x2: int, y2: 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:blit(src: Canvas, dst_x: int, dst_y: int) | рисует src-холст на указанных координатах |
|
||||||
| data:clear() | очищает холст |
|
| data:clear() | очищает холст |
|
||||||
| data:clear(*цвет*) | заполняет холст указанным RGBA цветом |
|
| data:clear(*цвет*) | заполняет холст указанным RGBA цветом |
|
||||||
| data:update() | применяет изменения и загружает холст в видеопамять |
|
| data:update() | применяет изменения и загружает холст в видеопамять |
|
||||||
| data:set_data(data: table<int>) | заменяет данные пикселей (ширина * высота * 4 чисел) |
|
| data:set_data(data: bytearray | table<int>) | заменяет данные пикселей (ширина * высота * 4 чисел) |
|
||||||
| data:create_texture(name: str) | создаёт и делится текстурой с рендерером |
|
| 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)
|
## Рамка встраивания (iframe)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -137,6 +137,7 @@
|
||||||
- `text-wrap` - разрешает автоматический перенос текста (работает только при multiline: "true")
|
- `text-wrap` - разрешает автоматический перенос текста (работает только при multiline: "true")
|
||||||
- `editable`- определяет возможность редактирования текста.
|
- `editable`- определяет возможность редактирования текста.
|
||||||
- `line-numbers` - включает отображение номеров строк.
|
- `line-numbers` - включает отображение номеров строк.
|
||||||
|
- `keep-line-selection` - продолжать отображать выбранную строку при потере фокуса.
|
||||||
- `error-color` - цвет при вводе некорректных данных (текст не проходит проверку валидатора). Тип: RGBA цвет.
|
- `error-color` - цвет при вводе некорректных данных (текст не проходит проверку валидатора). Тип: RGBA цвет.
|
||||||
- `text-color` - цвет текста. Тип: RGBA цвет.
|
- `text-color` - цвет текста. Тип: RGBA цвет.
|
||||||
- `validator` - lua функция, проверяющая текст на корректность. Принимает на вход строку, возвращает true если текст корректен.
|
- `validator` - lua функция, проверяющая текст на корректность. Принимает на вход строку, возвращает true если текст корректен.
|
||||||
|
|
|
||||||
153
doc/specs/debugging_protocol.md
Normal file
153
doc/specs/debugging_protocol.md
Normal file
|
|
@ -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
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
@ -8,5 +8,5 @@ multiline-string-end = "]]"
|
||||||
keywords = [
|
keywords = [
|
||||||
"and", "break", "do", "else", "elseif", "end", "false", "for", "function",
|
"and", "break", "do", "else", "elseif", "end", "false", "for", "function",
|
||||||
"if", "in", "local", "nil", "not", "or", "repeat", "return", "then", "true",
|
"if", "in", "local", "nil", "not", "or", "repeat", "return", "then", "true",
|
||||||
"until", "while"
|
"until", "while", "self", "error"
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,10 @@
|
||||||
function run_script(path)
|
function run_script(path)
|
||||||
debug.log("starting application script "..path)
|
__vc_start_app_script(path, 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)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function refresh()
|
function refresh()
|
||||||
document.list:clear()
|
document.list:clear()
|
||||||
|
|
||||||
local allpacks = table.merge(pack.get_available(), pack.get_installed())
|
local allpacks = table.merge(pack.get_available(), pack.get_installed())
|
||||||
local infos = pack.get_info(allpacks)
|
local infos = pack.get_info(allpacks)
|
||||||
for _, name in ipairs(allpacks) do
|
for _, name in ipairs(allpacks) do
|
||||||
|
|
|
||||||
|
|
@ -24,10 +24,46 @@ function update_setting(x, id, name, postfix)
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local initialized = false
|
||||||
|
|
||||||
function on_open()
|
function on_open()
|
||||||
|
if not initialized then
|
||||||
|
initialized = true
|
||||||
|
local token = audio.input.__get_core_token()
|
||||||
|
document.root:add("<container id='tm' />")
|
||||||
|
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-master", "Master Volume", 0.01)
|
||||||
create_setting("audio.volume-regular", "Regular Sounds", 0.01)
|
create_setting("audio.volume-regular", "Regular Sounds", 0.01)
|
||||||
create_setting("audio.volume-ui", "UI Sounds", 0.01)
|
create_setting("audio.volume-ui", "UI Sounds", 0.01)
|
||||||
create_setting("audio.volume-ambient", "Ambient", 0.01)
|
create_setting("audio.volume-ambient", "Ambient", 0.01)
|
||||||
create_setting("audio.volume-music", "Music", 0.01)
|
create_setting("audio.volume-music", "Music", 0.01)
|
||||||
|
document.root:add("<label context='settings'>@Microphone</label>")
|
||||||
|
document.root:add("<select id='input_device_select' "..
|
||||||
|
"onselect='function(opt) audio.set_input_device(opt) end'/>")
|
||||||
|
document.root:add("<container id='input_volume_outer' color='#000000' size='4'>"
|
||||||
|
.."<container id='input_volume_inner' color='#00FF00FF' pos='1' size='2'/>"
|
||||||
|
.."</container>")
|
||||||
|
local selectbox = document.input_device_select
|
||||||
|
local devices = {}
|
||||||
|
local names = audio.get_input_devices_names()
|
||||||
|
for i, name in ipairs(names) do
|
||||||
|
table.insert(devices, {value=name, text=name})
|
||||||
|
end
|
||||||
|
selectbox.options = devices
|
||||||
|
selectbox.value = audio.get_input_info().device_specifier
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -70,4 +70,5 @@ function on_open()
|
||||||
create_checkbox("camera.inertia", "Camera Inertia")
|
create_checkbox("camera.inertia", "Camera Inertia")
|
||||||
create_checkbox("camera.fov-effects", "Camera FOV Effects")
|
create_checkbox("camera.fov-effects", "Camera FOV Effects")
|
||||||
create_checkbox("display.limit-fps-iconified", "Limit Background FPS")
|
create_checkbox("display.limit-fps-iconified", "Limit Background FPS")
|
||||||
|
create_setting("graphics.gamma", "Gamma", 0.05, "", "graphics.gamma.tooltip")
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -39,8 +39,9 @@ function on_open()
|
||||||
create_setting("chunks.load-distance", "Load Distance", 1)
|
create_setting("chunks.load-distance", "Load Distance", 1)
|
||||||
create_setting("chunks.load-speed", "Load Speed", 1)
|
create_setting("chunks.load-speed", "Load Speed", 1)
|
||||||
create_setting("graphics.fog-curve", "Fog Curve", 0.1)
|
create_setting("graphics.fog-curve", "Fog Curve", 0.1)
|
||||||
create_setting("graphics.gamma", "Gamma", 0.05, "", "graphics.gamma.tooltip")
|
|
||||||
create_checkbox("graphics.backlight", "Backlight", "graphics.backlight.tooltip")
|
create_checkbox("graphics.backlight", "Backlight", "graphics.backlight.tooltip")
|
||||||
|
create_checkbox("graphics.soft-lighting", "Soft lighting", "graphics.soft-lighting.tooltip")
|
||||||
create_checkbox("graphics.dense-render", "Dense blocks render", "graphics.dense-render.tooltip")
|
create_checkbox("graphics.dense-render", "Dense blocks render", "graphics.dense-render.tooltip")
|
||||||
create_checkbox("graphics.advanced-render", "Advanced render", "graphics.advanced-render.tooltip")
|
create_checkbox("graphics.advanced-render", "Advanced render", "graphics.advanced-render.tooltip")
|
||||||
create_checkbox("graphics.ssao", "SSAO", "graphics.ssao.tooltip")
|
create_checkbox("graphics.ssao", "SSAO", "graphics.ssao.tooltip")
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<container id="%{id}" size="32" tooltip="%{text}"
|
<container id="%{id}" size="32" tooltip="%{text}"
|
||||||
onclick="events.emit('core:open_traceback', '%{traceback}')">
|
onclick="events.emit('core:open_traceback', '%{traceback}')">
|
||||||
<image src="gui/%{type}" size="32"/>
|
<image src="gui/%{type}" size="32"/>
|
||||||
<label pos="36,2" sizefunc="-1,-1">%{text}</label>
|
<label pos="36,2" size-func="-1,-1">%{text}</label>
|
||||||
<image src="gui/cross" interactive="true" size="16" gravity="top-right"
|
<image src="gui/cross" interactive="true" size="16" gravity="top-right"
|
||||||
onclick="document['%{id}']:destruct()"></image>
|
onclick="document['%{id}']:destruct()"></image>
|
||||||
</container>
|
</container>
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
onclick='%{open_func}("%{filename}")'
|
onclick='%{open_func}("%{filename}")'
|
||||||
markup='md'
|
markup='md'
|
||||||
tooltip='%{unit}'
|
tooltip='%{unit}'
|
||||||
sizefunc="-1,-1">
|
size-func="-1,-1">
|
||||||
[#FFFFFF80]%{path}[#FFFFFFFF]%{name}
|
[#FFFFFF80]%{path}[#FFFFFFFF]%{name}
|
||||||
</label>
|
</label>
|
||||||
</container>
|
</container>
|
||||||
|
|
|
||||||
63
res/modules/internal/audio_input.lua
Normal file
63
res/modules/internal/audio_input.lua
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
local _base64_encode_urlsafe = base64.encode_urlsafe
|
||||||
|
local _random_bytes = random.bytes
|
||||||
|
local core_token = _base64_encode_urlsafe(_random_bytes(18))
|
||||||
|
|
||||||
|
local audio_input_tokens_store = {[core_token] = "core"}
|
||||||
|
audio.input = {}
|
||||||
|
|
||||||
|
local _gui_confirm = gui.confirm
|
||||||
|
local _debug_pack_by_frame = debug.get_pack_by_frame
|
||||||
|
local _audio_fetch_input = audio.__fetch_input
|
||||||
|
audio.__fetch_input = nil
|
||||||
|
local MAX_FETCH = 44100 * 4
|
||||||
|
local MAX_AMPLITUDE = 32768
|
||||||
|
|
||||||
|
local total_fetch = Bytearray()
|
||||||
|
local max_amplitude = 0.0
|
||||||
|
|
||||||
|
function audio.__reset_fetch_buffer()
|
||||||
|
total_fetch:clear()
|
||||||
|
max_amplitude = 0.0
|
||||||
|
end
|
||||||
|
|
||||||
|
function audio.input.get_max_amplitude()
|
||||||
|
return max_amplitude / MAX_AMPLITUDE
|
||||||
|
end
|
||||||
|
|
||||||
|
function audio.input.fetch(token, size)
|
||||||
|
size = size or MAX_FETCH
|
||||||
|
if audio_input_tokens_store[token] then
|
||||||
|
if #total_fetch >= size then
|
||||||
|
return total_fetch:slice(1, size)
|
||||||
|
end
|
||||||
|
local fetched = _audio_fetch_input(size - #total_fetch)
|
||||||
|
if not fetched then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
for i, sample in ipairs(I16view(fetched)) do
|
||||||
|
max_amplitude = math.max(math.abs(sample))
|
||||||
|
end
|
||||||
|
total_fetch:append(fetched)
|
||||||
|
return total_fetch:slice()
|
||||||
|
end
|
||||||
|
error("access denied")
|
||||||
|
end
|
||||||
|
|
||||||
|
local GRANT_PERMISSION_MSG = "Grant '%{0}' pack audio recording permission?"
|
||||||
|
|
||||||
|
function audio.input.request_open(callback)
|
||||||
|
local token = _base64_encode_urlsafe(_random_bytes(18))
|
||||||
|
local caller = _debug_pack_by_frame(1)
|
||||||
|
_gui_confirm(gui.str(GRANT_PERMISSION_MSG):gsub("%%{0}", caller), function()
|
||||||
|
audio_input_tokens_store[token] = caller
|
||||||
|
callback(token)
|
||||||
|
menu:reset()
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
function audio.input.__get_core_token()
|
||||||
|
local caller = _debug_pack_by_frame(1)
|
||||||
|
if caller == "core" then
|
||||||
|
return core_token
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -14,6 +14,8 @@ FFI.cdef[[
|
||||||
|
|
||||||
local malloc = FFI.C.malloc
|
local malloc = FFI.C.malloc
|
||||||
local free = FFI.C.free
|
local free = FFI.C.free
|
||||||
|
local FFIBytearray
|
||||||
|
local bytearray_type
|
||||||
|
|
||||||
local function grow_buffer(self, elems)
|
local function grow_buffer(self, elems)
|
||||||
local new_capacity = math.ceil(self.capacity / 0.75 + elems)
|
local new_capacity = math.ceil(self.capacity / 0.75 + elems)
|
||||||
|
|
@ -119,6 +121,23 @@ local function get_capacity(self)
|
||||||
return self.capacity
|
return self.capacity
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function slice(self, offset, length)
|
||||||
|
offset = offset or 1
|
||||||
|
length = length or (self.size - offset + 1)
|
||||||
|
if offset < 1 or offset > self.size then
|
||||||
|
return FFIBytearray(0)
|
||||||
|
end
|
||||||
|
if offset + length - 1 > self.size then
|
||||||
|
length = self.size - offset + 1
|
||||||
|
end
|
||||||
|
local buffer = malloc(length)
|
||||||
|
if not buffer then
|
||||||
|
error("malloc(" .. length .. ") returned NULL")
|
||||||
|
end
|
||||||
|
FFI.copy(buffer, self.bytes + (offset - 1), length)
|
||||||
|
return bytearray_type(buffer, length, length)
|
||||||
|
end
|
||||||
|
|
||||||
local bytearray_methods = {
|
local bytearray_methods = {
|
||||||
append=append,
|
append=append,
|
||||||
insert=insert,
|
insert=insert,
|
||||||
|
|
@ -127,6 +146,7 @@ local bytearray_methods = {
|
||||||
clear=clear,
|
clear=clear,
|
||||||
reserve=reserve,
|
reserve=reserve,
|
||||||
get_capacity=get_capacity,
|
get_capacity=get_capacity,
|
||||||
|
slice=slice,
|
||||||
}
|
}
|
||||||
|
|
||||||
local bytearray_mt = {
|
local bytearray_mt = {
|
||||||
|
|
@ -168,9 +188,9 @@ local bytearray_mt = {
|
||||||
}
|
}
|
||||||
bytearray_mt.__pairs = bytearray_mt.__ipairs
|
bytearray_mt.__pairs = bytearray_mt.__ipairs
|
||||||
|
|
||||||
local bytearray_type = FFI.metatype("bytearray_t", bytearray_mt)
|
bytearray_type = FFI.metatype("bytearray_t", bytearray_mt)
|
||||||
|
|
||||||
local FFIBytearray = {
|
FFIBytearray = {
|
||||||
__call = function (self, n)
|
__call = function (self, n)
|
||||||
local t = type(n)
|
local t = type(n)
|
||||||
if t == "string" then
|
if t == "string" then
|
||||||
|
|
@ -210,7 +230,59 @@ local function FFIBytearray_as_string(bytes)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local function create_FFIview_class(name, typename, typesize)
|
||||||
|
local FFIU16view_mt = {
|
||||||
|
__index = function(self, key)
|
||||||
|
if key <= 0 or key > self.size then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
return self.ptr[key - 1]
|
||||||
|
end,
|
||||||
|
__newindex = function(self, key, value)
|
||||||
|
if key == self.size + 1 then
|
||||||
|
return append(self, value)
|
||||||
|
elseif key <= 0 or key > self.size then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
self.ptr[key - 1] = value
|
||||||
|
end,
|
||||||
|
__len = function(self)
|
||||||
|
return self.size
|
||||||
|
end,
|
||||||
|
__tostring = function(self)
|
||||||
|
return string.format(name .. "[%s]{...}", tonumber(self.size))
|
||||||
|
end,
|
||||||
|
__ipairs = function(self)
|
||||||
|
local i = 0
|
||||||
|
return function()
|
||||||
|
i = i + 1
|
||||||
|
if i <= self.size then
|
||||||
|
return i, self.ptr[i - 1]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
}
|
||||||
|
return function (bytes)
|
||||||
|
local ptr = FFI.cast(typename .. "*", bytes.bytes)
|
||||||
|
local x = setmetatable({
|
||||||
|
bytes=bytes,
|
||||||
|
ptr=ptr,
|
||||||
|
size=math.floor(bytes.size / typesize),
|
||||||
|
}, FFIU16view_mt)
|
||||||
|
return x
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local FFII16view = create_FFIview_class("FFII16view", "int16_t", 2)
|
||||||
|
local FFIU16view = create_FFIview_class("FFIU16view", "uint16_t", 2)
|
||||||
|
local FFII32view = create_FFIview_class("FFII32view", "int32_t", 4)
|
||||||
|
local FFIU32view = create_FFIview_class("FFIU32view", "uint32_t", 4)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
FFIBytearray = setmetatable(FFIBytearray, FFIBytearray),
|
FFIBytearray = setmetatable(FFIBytearray, FFIBytearray),
|
||||||
FFIBytearray_as_string = FFIBytearray_as_string
|
FFIBytearray_as_string = FFIBytearray_as_string,
|
||||||
|
FFIU16view = FFIU16view,
|
||||||
|
FFII16view = FFII16view,
|
||||||
|
FFIU32view = FFIU32view,
|
||||||
|
FFII32view = FFII32view,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
150
res/modules/internal/debugging.lua
Normal file
150
res/modules/internal/debugging.lua
Normal file
|
|
@ -0,0 +1,150 @@
|
||||||
|
local breakpoints = {}
|
||||||
|
local dbg_steps_mode = false
|
||||||
|
local dbg_step_into_func = false
|
||||||
|
local hook_lock = false
|
||||||
|
local current_func
|
||||||
|
local current_func_stack_size
|
||||||
|
|
||||||
|
local __parse_path = parse_path
|
||||||
|
local _debug_getinfo = debug.getinfo
|
||||||
|
local _debug_getlocal = debug.getlocal
|
||||||
|
local __pause = debug.pause
|
||||||
|
local __error = error
|
||||||
|
local __sethook = debug.sethook
|
||||||
|
|
||||||
|
-- 'return' hook not called for some functions
|
||||||
|
-- todo: speedup
|
||||||
|
local function calc_stack_size()
|
||||||
|
local s = debug.traceback("", 2)
|
||||||
|
local count = 0
|
||||||
|
for i in s:gmatch("\n") do
|
||||||
|
count = count + 1
|
||||||
|
end
|
||||||
|
return count
|
||||||
|
end
|
||||||
|
|
||||||
|
local is_debugging = debug.is_debugging()
|
||||||
|
if is_debugging then
|
||||||
|
__sethook(function (e, line)
|
||||||
|
if e == "return" then
|
||||||
|
local info = _debug_getinfo(2)
|
||||||
|
if info.func == current_func then
|
||||||
|
current_func = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if dbg_steps_mode and not hook_lock then
|
||||||
|
hook_lock = true
|
||||||
|
|
||||||
|
if not dbg_step_into_func then
|
||||||
|
local func = _debug_getinfo(2).func
|
||||||
|
if func ~= current_func then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if current_func_stack_size ~= calc_stack_size() then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
current_func = func
|
||||||
|
__pause("step")
|
||||||
|
debug.pull_events()
|
||||||
|
end
|
||||||
|
hook_lock = false
|
||||||
|
local bps = breakpoints[line]
|
||||||
|
if not bps then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local source = _debug_getinfo(2).source
|
||||||
|
if not bps[source] then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
current_func = _debug_getinfo(2).func
|
||||||
|
current_func_stack_size = calc_stack_size()
|
||||||
|
__pause("breakpoint")
|
||||||
|
while debug.pull_events() do
|
||||||
|
__pause()
|
||||||
|
end
|
||||||
|
end, "lr")
|
||||||
|
end
|
||||||
|
|
||||||
|
local DBG_EVENT_SET_BREAKPOINT = 1
|
||||||
|
local DBG_EVENT_RM_BREAKPOINT = 2
|
||||||
|
local DBG_EVENT_STEP = 3
|
||||||
|
local DBG_EVENT_STEP_INTO_FUNCTION = 4
|
||||||
|
local DBG_EVENT_RESUME = 5
|
||||||
|
local DBG_EVENT_GET_VALUE = 6
|
||||||
|
local __pull_events = debug.__pull_events
|
||||||
|
local __sendvalue = debug.__sendvalue
|
||||||
|
debug.__pull_events = nil
|
||||||
|
debug.__sendvalue = nil
|
||||||
|
|
||||||
|
function debug.get_pack_by_frame(func)
|
||||||
|
local prefix, _ = __parse_path(_debug_getinfo(func, "S").source)
|
||||||
|
return prefix
|
||||||
|
end
|
||||||
|
|
||||||
|
function debug.pull_events()
|
||||||
|
if not is_debugging then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if not debug.is_debugging() then
|
||||||
|
is_debugging = false
|
||||||
|
__sethook()
|
||||||
|
end
|
||||||
|
local events = __pull_events()
|
||||||
|
if not events then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local keepPaused = false
|
||||||
|
for i, event in ipairs(events) do
|
||||||
|
if event[1] == DBG_EVENT_SET_BREAKPOINT then
|
||||||
|
debug.set_breakpoint(event[2], event[3])
|
||||||
|
elseif event[1] == DBG_EVENT_RM_BREAKPOINT then
|
||||||
|
debug.remove_breakpoint(event[2], event[3])
|
||||||
|
elseif event[1] == DBG_EVENT_STEP then
|
||||||
|
dbg_steps_mode = true
|
||||||
|
dbg_step_into_func = false
|
||||||
|
elseif event[1] == DBG_EVENT_STEP_INTO_FUNCTION then
|
||||||
|
dbg_steps_mode = true
|
||||||
|
dbg_step_into_func = true
|
||||||
|
elseif event[1] == DBG_EVENT_RESUME then
|
||||||
|
dbg_steps_mode = false
|
||||||
|
dbg_step_into_func = false
|
||||||
|
elseif event[1] == DBG_EVENT_GET_VALUE then
|
||||||
|
local _, value = _debug_getlocal(event[2] + 3, event[3])
|
||||||
|
for _, key in ipairs(event[4]) do
|
||||||
|
if value == nil then
|
||||||
|
value = "error: index nil value"
|
||||||
|
break
|
||||||
|
end
|
||||||
|
value = value[key]
|
||||||
|
end
|
||||||
|
__sendvalue(value, event[2], event[3], event[4])
|
||||||
|
keepPaused = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return keepPaused
|
||||||
|
end
|
||||||
|
|
||||||
|
function debug.set_breakpoint(source, line)
|
||||||
|
local bps = breakpoints[line]
|
||||||
|
if not bps then
|
||||||
|
bps = {}
|
||||||
|
breakpoints[line] = bps
|
||||||
|
end
|
||||||
|
bps[source] = true
|
||||||
|
end
|
||||||
|
|
||||||
|
function debug.remove_breakpoint(source, line)
|
||||||
|
local bps = breakpoints[line]
|
||||||
|
if not bps then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
bps[source] = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
function error(message, level)
|
||||||
|
if is_debugging then
|
||||||
|
__pause("exception", message)
|
||||||
|
end
|
||||||
|
__error(message, level)
|
||||||
|
end
|
||||||
48
res/modules/internal/deprecated.lua
Normal file
48
res/modules/internal/deprecated.lua
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
-- --------- Deprecated functions ------ --
|
||||||
|
local function wrap_deprecated(func, name, alternatives)
|
||||||
|
return function (...)
|
||||||
|
on_deprecated_call(name, alternatives)
|
||||||
|
return func(...)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
block_index = wrap_deprecated(block.index, "block_index", "block.index")
|
||||||
|
block_name = wrap_deprecated(block.name, "block_name", "block.name")
|
||||||
|
blocks_count = wrap_deprecated(block.defs_count, "blocks_count", "block.defs_count")
|
||||||
|
is_solid_at = wrap_deprecated(block.is_solid_at, "is_solid_at", "block.is_solid_at")
|
||||||
|
is_replaceable_at = wrap_deprecated(block.is_replaceable_at, "is_replaceable_at", "block.is_replaceable_at")
|
||||||
|
set_block = wrap_deprecated(block.set, "set_block", "block.set")
|
||||||
|
get_block = wrap_deprecated(block.get, "get_block", "block.get")
|
||||||
|
get_block_X = wrap_deprecated(block.get_X, "get_block_X", "block.get_X")
|
||||||
|
get_block_Y = wrap_deprecated(block.get_Y, "get_block_Y", "block.get_Y")
|
||||||
|
get_block_Z = wrap_deprecated(block.get_Z, "get_block_Z", "block.get_Z")
|
||||||
|
get_block_states = wrap_deprecated(block.get_states, "get_block_states", "block.get_states")
|
||||||
|
set_block_states = wrap_deprecated(block.set_states, "set_block_states", "block.set_states")
|
||||||
|
get_block_rotation = wrap_deprecated(block.get_rotation, "get_block_rotation", "block.get_rotation")
|
||||||
|
set_block_rotation = wrap_deprecated(block.set_rotation, "set_block_rotation", "block.set_rotation")
|
||||||
|
get_block_user_bits = wrap_deprecated(block.get_user_bits, "get_block_user_bits", "block.get_user_bits")
|
||||||
|
set_block_user_bits = wrap_deprecated(block.set_user_bits, "set_block_user_bits", "block.set_user_bits")
|
||||||
|
|
||||||
|
function load_script(path, nocache)
|
||||||
|
on_deprecated_call("load_script", "require or loadstring")
|
||||||
|
return __load_script(path, nocache)
|
||||||
|
end
|
||||||
|
|
||||||
|
_dofile = dofile
|
||||||
|
-- Replaces dofile('*/content/packid/*') with load_script('packid:*')
|
||||||
|
function dofile(path)
|
||||||
|
on_deprecated_call("dofile", "require or loadstring")
|
||||||
|
local index = string.find(path, "/content/")
|
||||||
|
if index then
|
||||||
|
local newpath = string.sub(path, index+9)
|
||||||
|
index = string.find(newpath, "/")
|
||||||
|
if index then
|
||||||
|
local label = string.sub(newpath, 1, index-1)
|
||||||
|
newpath = label..':'..string.sub(newpath, index+1)
|
||||||
|
if file.isfile(newpath) then
|
||||||
|
return __load_script(newpath, true)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return _dofile(path)
|
||||||
|
end
|
||||||
|
|
@ -2,7 +2,14 @@ local events = {
|
||||||
handlers = {}
|
handlers = {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
local __parse_path = parse_path
|
||||||
|
local __pack_is_installed = pack.is_installed
|
||||||
|
|
||||||
function events.on(event, func)
|
function events.on(event, func)
|
||||||
|
local prefix = __parse_path(event)
|
||||||
|
if prefix ~= "core" and not __pack_is_installed(prefix) then
|
||||||
|
error("pack prefix required")
|
||||||
|
end
|
||||||
if events.handlers[event] == nil then
|
if events.handlers[event] == nil then
|
||||||
events.handlers[event] = {}
|
events.handlers[event] = {}
|
||||||
end
|
end
|
||||||
|
|
|
||||||
45
res/modules/internal/extensions/file.lua
Normal file
45
res/modules/internal/extensions/file.lua
Normal file
|
|
@ -0,0 +1,45 @@
|
||||||
|
function file.name(path)
|
||||||
|
return path:match("([^:/\\]+)$")
|
||||||
|
end
|
||||||
|
|
||||||
|
function file.stem(path)
|
||||||
|
local name = file.name(path)
|
||||||
|
return name:match("(.+)%.[^%.]+$") or name
|
||||||
|
end
|
||||||
|
|
||||||
|
function file.ext(path)
|
||||||
|
return path:match("%.([^:/\\]+)$")
|
||||||
|
end
|
||||||
|
|
||||||
|
function file.prefix(path)
|
||||||
|
return path:match("^([^:]+)")
|
||||||
|
end
|
||||||
|
|
||||||
|
function file.parent(path)
|
||||||
|
local dir = path:match("(.*)/")
|
||||||
|
if not dir then
|
||||||
|
return file.prefix(path)..":"
|
||||||
|
end
|
||||||
|
return dir
|
||||||
|
end
|
||||||
|
|
||||||
|
function file.path(path)
|
||||||
|
local pos = path:find(':')
|
||||||
|
return path:sub(pos + 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
function file.join(a, b)
|
||||||
|
if a[#a] == ':' then
|
||||||
|
return a .. b
|
||||||
|
end
|
||||||
|
return a .. "/" .. b
|
||||||
|
end
|
||||||
|
|
||||||
|
function file.readlines(path)
|
||||||
|
local str = file.read(path)
|
||||||
|
local lines = {}
|
||||||
|
for s in str:gmatch("[^\r\n]+") do
|
||||||
|
table.insert(lines, s)
|
||||||
|
end
|
||||||
|
return lines
|
||||||
|
end
|
||||||
71
res/modules/internal/extensions/inventory.lua
Normal file
71
res/modules/internal/extensions/inventory.lua
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
function inventory.get_uses(invid, slot)
|
||||||
|
local uses = inventory.get_data(invid, slot, "uses")
|
||||||
|
if uses == nil then
|
||||||
|
return item.uses(inventory.get(invid, slot))
|
||||||
|
end
|
||||||
|
return uses
|
||||||
|
end
|
||||||
|
|
||||||
|
function inventory.use(invid, slot)
|
||||||
|
local itemid, count = inventory.get(invid, slot)
|
||||||
|
if itemid == nil then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
local item_uses = inventory.get_uses(invid, slot)
|
||||||
|
if item_uses == nil then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if item_uses == 1 then
|
||||||
|
inventory.set(invid, slot, itemid, count - 1)
|
||||||
|
elseif item_uses > 1 then
|
||||||
|
inventory.set_data(invid, slot, "uses", item_uses - 1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function inventory.decrement(invid, slot, count)
|
||||||
|
count = count or 1
|
||||||
|
local itemid, itemcount = inventory.get(invid, slot)
|
||||||
|
if itemcount <= count then
|
||||||
|
inventory.set(invid, slot, 0)
|
||||||
|
else
|
||||||
|
inventory.set_count(invid, slot, itemcount - count)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function inventory.get_caption(invid, slot)
|
||||||
|
local item_id, count = inventory.get(invid, slot)
|
||||||
|
local caption = inventory.get_data(invid, slot, "caption")
|
||||||
|
if not caption then return item.caption(item_id) end
|
||||||
|
|
||||||
|
return caption
|
||||||
|
end
|
||||||
|
|
||||||
|
function inventory.set_caption(invid, slot, caption)
|
||||||
|
local itemid, itemcount = inventory.get(invid, slot)
|
||||||
|
if itemid == 0 then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if caption == nil or type(caption) ~= "string" then
|
||||||
|
caption = ""
|
||||||
|
end
|
||||||
|
inventory.set_data(invid, slot, "caption", caption)
|
||||||
|
end
|
||||||
|
|
||||||
|
function inventory.get_description(invid, slot)
|
||||||
|
local item_id, count = inventory.get(invid, slot)
|
||||||
|
local description = inventory.get_data(invid, slot, "description")
|
||||||
|
if not description then return item.description(item_id) end
|
||||||
|
|
||||||
|
return description
|
||||||
|
end
|
||||||
|
|
||||||
|
function inventory.set_description(invid, slot, description)
|
||||||
|
local itemid, itemcount = inventory.get(invid, slot)
|
||||||
|
if itemid == 0 then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
if description == nil or type(description) ~= "string" then
|
||||||
|
description = ""
|
||||||
|
end
|
||||||
|
inventory.set_data(invid, slot, "description", description)
|
||||||
|
end
|
||||||
37
res/modules/internal/extensions/math.lua
Normal file
37
res/modules/internal/extensions/math.lua
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
function math.clamp(_in, low, high)
|
||||||
|
return math.min(math.max(_in, low), high)
|
||||||
|
end
|
||||||
|
|
||||||
|
function math.rand(low, high)
|
||||||
|
return low + (high - low) * math.random()
|
||||||
|
end
|
||||||
|
|
||||||
|
function math.normalize(num, conf)
|
||||||
|
conf = conf or 1
|
||||||
|
|
||||||
|
return (num / conf) % 1
|
||||||
|
end
|
||||||
|
|
||||||
|
function math.round(num, places)
|
||||||
|
places = places or 0
|
||||||
|
|
||||||
|
local mult = 10 ^ places
|
||||||
|
return math.floor(num * mult + 0.5) / mult
|
||||||
|
end
|
||||||
|
|
||||||
|
function math.sum(...)
|
||||||
|
local numbers = nil
|
||||||
|
local sum = 0
|
||||||
|
|
||||||
|
if type(...) == "table" then
|
||||||
|
numbers = ...
|
||||||
|
else
|
||||||
|
numbers = {...}
|
||||||
|
end
|
||||||
|
|
||||||
|
for _, v in ipairs(numbers) do
|
||||||
|
sum = sum + v
|
||||||
|
end
|
||||||
|
|
||||||
|
return sum
|
||||||
|
end
|
||||||
13
res/modules/internal/extensions/pack.lua
Normal file
13
res/modules/internal/extensions/pack.lua
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
function pack.is_installed(packid)
|
||||||
|
return file.isfile(packid..":package.json")
|
||||||
|
end
|
||||||
|
|
||||||
|
function pack.data_file(packid, name)
|
||||||
|
file.mkdirs("world:data/"..packid)
|
||||||
|
return "world:data/"..packid.."/"..name
|
||||||
|
end
|
||||||
|
|
||||||
|
function pack.shared_file(packid, name)
|
||||||
|
file.mkdirs("config:"..packid)
|
||||||
|
return "config:"..packid.."/"..name
|
||||||
|
end
|
||||||
126
res/modules/internal/extensions/string.lua
Normal file
126
res/modules/internal/extensions/string.lua
Normal file
|
|
@ -0,0 +1,126 @@
|
||||||
|
local pattern_escape_replacements = {
|
||||||
|
["("] = "%(",
|
||||||
|
[")"] = "%)",
|
||||||
|
["."] = "%.",
|
||||||
|
["%"] = "%%",
|
||||||
|
["+"] = "%+",
|
||||||
|
["-"] = "%-",
|
||||||
|
["*"] = "%*",
|
||||||
|
["?"] = "%?",
|
||||||
|
["["] = "%[",
|
||||||
|
["]"] = "%]",
|
||||||
|
["^"] = "%^",
|
||||||
|
["$"] = "%$",
|
||||||
|
["\0"] = "%z"
|
||||||
|
}
|
||||||
|
|
||||||
|
function string.pattern_safe(str)
|
||||||
|
return string.gsub(str, ".", pattern_escape_replacements)
|
||||||
|
end
|
||||||
|
|
||||||
|
local string_sub = string.sub
|
||||||
|
local string_find = string.find
|
||||||
|
local string_len = string.len
|
||||||
|
function string.explode(separator, str, withpattern)
|
||||||
|
if (withpattern == nil) then withpattern = false end
|
||||||
|
|
||||||
|
local ret = {}
|
||||||
|
local current_pos = 1
|
||||||
|
|
||||||
|
for i = 1, string_len(str) do
|
||||||
|
local start_pos, end_pos = string_find(
|
||||||
|
str, separator, current_pos, not withpattern)
|
||||||
|
if (not start_pos) then break end
|
||||||
|
ret[i] = string_sub(str, current_pos, start_pos - 1)
|
||||||
|
current_pos = end_pos + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
ret[#ret + 1] = string_sub(str, current_pos)
|
||||||
|
|
||||||
|
return ret
|
||||||
|
end
|
||||||
|
|
||||||
|
function string.split(str, delimiter)
|
||||||
|
return string.explode(delimiter, str)
|
||||||
|
end
|
||||||
|
|
||||||
|
function string.formatted_time(seconds, format)
|
||||||
|
if (not seconds) then seconds = 0 end
|
||||||
|
local hours = math.floor(seconds / 3600)
|
||||||
|
local minutes = math.floor((seconds / 60) % 60)
|
||||||
|
local millisecs = (seconds - math.floor(seconds)) * 1000
|
||||||
|
seconds = math.floor(seconds % 60)
|
||||||
|
|
||||||
|
if (format) then
|
||||||
|
return string.format(format, minutes, seconds, millisecs)
|
||||||
|
else
|
||||||
|
return { h = hours, m = minutes, s = seconds, ms = millisecs }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function string.replace(str, tofind, toreplace)
|
||||||
|
local tbl = string.explode(tofind, str)
|
||||||
|
if (tbl[1]) then return table.concat(tbl, toreplace) end
|
||||||
|
return str
|
||||||
|
end
|
||||||
|
|
||||||
|
function string.trim(s, char)
|
||||||
|
if char then char = string.pattern_safe(char) else char = "%s" end
|
||||||
|
return string.match(s, "^" .. char .. "*(.-)" .. char .. "*$") or s
|
||||||
|
end
|
||||||
|
|
||||||
|
function string.trim_right(s, char)
|
||||||
|
if char then char = string.pattern_safe(char) else char = "%s" end
|
||||||
|
return string.match(s, "^(.-)" .. char .. "*$") or s
|
||||||
|
end
|
||||||
|
|
||||||
|
function string.trim_left(s, char)
|
||||||
|
if char then char = string.pattern_safe(char) else char = "%s" end
|
||||||
|
return string.match(s, "^" .. char .. "*(.+)$") or s
|
||||||
|
end
|
||||||
|
|
||||||
|
function string.pad(str, size, char)
|
||||||
|
char = char == nil and " " or char
|
||||||
|
|
||||||
|
local padding = math.floor((size - #str) / 2)
|
||||||
|
local extra_padding = (size - #str) % 2
|
||||||
|
|
||||||
|
return string.rep(char, padding) .. str .. string.rep(char, padding + extra_padding)
|
||||||
|
end
|
||||||
|
|
||||||
|
function string.left_pad(str, size, char)
|
||||||
|
char = char == nil and " " or char
|
||||||
|
|
||||||
|
local left_padding = size - #str
|
||||||
|
return string.rep(char, left_padding) .. str
|
||||||
|
end
|
||||||
|
|
||||||
|
function string.right_pad(str, size, char)
|
||||||
|
char = char == nil and " " or char
|
||||||
|
|
||||||
|
local right_padding = size - #str
|
||||||
|
return str .. string.rep(char, right_padding)
|
||||||
|
end
|
||||||
|
|
||||||
|
string.lower = utf8.lower
|
||||||
|
string.upper = utf8.upper
|
||||||
|
string.escape = utf8.escape
|
||||||
|
|
||||||
|
local meta = getmetatable("")
|
||||||
|
|
||||||
|
function meta:__index(key)
|
||||||
|
local val = string[key]
|
||||||
|
if (val ~= nil) then
|
||||||
|
return val
|
||||||
|
elseif (tonumber(key)) then
|
||||||
|
return string.sub(self, key, key)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function string.starts_with(str, start)
|
||||||
|
return string.sub(str, 1, string.len(start)) == start
|
||||||
|
end
|
||||||
|
|
||||||
|
function string.ends_with(str, endStr)
|
||||||
|
return endStr == "" or string.sub(str, -string.len(endStr)) == endStr
|
||||||
|
end
|
||||||
179
res/modules/internal/extensions/table.lua
Normal file
179
res/modules/internal/extensions/table.lua
Normal file
|
|
@ -0,0 +1,179 @@
|
||||||
|
function table.copy(t)
|
||||||
|
local copied = {}
|
||||||
|
|
||||||
|
for k, v in pairs(t) do
|
||||||
|
copied[k] = v
|
||||||
|
end
|
||||||
|
|
||||||
|
return copied
|
||||||
|
end
|
||||||
|
|
||||||
|
function table.deep_copy(t)
|
||||||
|
local copied = {}
|
||||||
|
|
||||||
|
for k, v in pairs(t) do
|
||||||
|
if type(v) == "table" then
|
||||||
|
copied[k] = table.deep_copy(v)
|
||||||
|
else
|
||||||
|
copied[k] = v
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return setmetatable(copied, getmetatable(t))
|
||||||
|
end
|
||||||
|
|
||||||
|
function table.count_pairs(t)
|
||||||
|
local count = 0
|
||||||
|
|
||||||
|
for k, v in pairs(t) do
|
||||||
|
count = count + 1
|
||||||
|
end
|
||||||
|
|
||||||
|
return count
|
||||||
|
end
|
||||||
|
|
||||||
|
function table.random(t)
|
||||||
|
return t[math.random(1, #t)]
|
||||||
|
end
|
||||||
|
|
||||||
|
function table.shuffle(t)
|
||||||
|
for i = #t, 2, -1 do
|
||||||
|
local j = math.random(i)
|
||||||
|
t[i], t[j] = t[j], t[i]
|
||||||
|
end
|
||||||
|
|
||||||
|
return t
|
||||||
|
end
|
||||||
|
|
||||||
|
function table.merge(t1, t2)
|
||||||
|
for i, v in pairs(t2) do
|
||||||
|
if type(i) == "number" then
|
||||||
|
t1[#t1 + 1] = v
|
||||||
|
elseif t1[i] == nil then
|
||||||
|
t1[i] = v
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return t1
|
||||||
|
end
|
||||||
|
|
||||||
|
function table.map(t, func)
|
||||||
|
for i, v in pairs(t) do
|
||||||
|
t[i] = func(i, v)
|
||||||
|
end
|
||||||
|
|
||||||
|
return t
|
||||||
|
end
|
||||||
|
|
||||||
|
function table.filter(t, func)
|
||||||
|
|
||||||
|
for i = #t, 1, -1 do
|
||||||
|
if not func(i, t[i]) then
|
||||||
|
table.remove(t, i)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local size = #t
|
||||||
|
|
||||||
|
for i, v in pairs(t) do
|
||||||
|
local i_type = type(i)
|
||||||
|
if i_type == "number" then
|
||||||
|
if i < 1 or i > size then
|
||||||
|
if not func(i, v) then
|
||||||
|
t[i] = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
else
|
||||||
|
if not func(i, v) then
|
||||||
|
t[i] = nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return t
|
||||||
|
end
|
||||||
|
|
||||||
|
function table.set_default(t, key, default)
|
||||||
|
if t[key] == nil then
|
||||||
|
t[key] = default
|
||||||
|
return default
|
||||||
|
end
|
||||||
|
|
||||||
|
return t[key]
|
||||||
|
end
|
||||||
|
|
||||||
|
function table.flat(t)
|
||||||
|
local flat = {}
|
||||||
|
|
||||||
|
for _, v in pairs(t) do
|
||||||
|
if type(v) == "table" then
|
||||||
|
table.merge(flat, v)
|
||||||
|
else
|
||||||
|
table.insert(flat, v)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return flat
|
||||||
|
end
|
||||||
|
|
||||||
|
function table.deep_flat(t)
|
||||||
|
local flat = {}
|
||||||
|
|
||||||
|
for _, v in pairs(t) do
|
||||||
|
if type(v) == "table" then
|
||||||
|
table.merge(flat, table.deep_flat(v))
|
||||||
|
else
|
||||||
|
table.insert(flat, v)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return flat
|
||||||
|
end
|
||||||
|
|
||||||
|
function table.sub(arr, start, stop)
|
||||||
|
local res = {}
|
||||||
|
start = start or 1
|
||||||
|
stop = stop or #arr
|
||||||
|
|
||||||
|
for i = start, stop do
|
||||||
|
table.insert(res, arr[i])
|
||||||
|
end
|
||||||
|
|
||||||
|
return res
|
||||||
|
end
|
||||||
|
|
||||||
|
function table.has(t, x)
|
||||||
|
for i,v in ipairs(t) do
|
||||||
|
if v == x then
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
|
function table.index(t, x)
|
||||||
|
for i,v in ipairs(t) do
|
||||||
|
if v == x then
|
||||||
|
return i
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return -1
|
||||||
|
end
|
||||||
|
|
||||||
|
function table.remove_value(t, x)
|
||||||
|
local index = table.index(t, x)
|
||||||
|
if index ~= -1 then
|
||||||
|
table.remove(t, index)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function table.tostring(t)
|
||||||
|
local s = '['
|
||||||
|
for i,v in ipairs(t) do
|
||||||
|
s = s..tostring(v)
|
||||||
|
if i < #t then
|
||||||
|
s = s..', '
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return s..']'
|
||||||
|
end
|
||||||
70
res/modules/internal/rules.lua
Normal file
70
res/modules/internal/rules.lua
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
local rules = {nexid = 1, rules = {}}
|
||||||
|
|
||||||
|
function rules.get_rule(name)
|
||||||
|
local rule = rules.rules[name]
|
||||||
|
if rule == nil then
|
||||||
|
rule = {listeners={}}
|
||||||
|
rules.rules[name] = rule
|
||||||
|
end
|
||||||
|
return rule
|
||||||
|
end
|
||||||
|
|
||||||
|
function rules.get(name)
|
||||||
|
local rule = rules.rules[name]
|
||||||
|
if rule == nil then
|
||||||
|
return nil
|
||||||
|
end
|
||||||
|
return rule.value
|
||||||
|
end
|
||||||
|
|
||||||
|
function rules.set(name, value)
|
||||||
|
local rule = rules.get_rule(name)
|
||||||
|
rule.value = value
|
||||||
|
for _, handler in pairs(rule.listeners) do
|
||||||
|
handler(value)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function rules.reset(name)
|
||||||
|
local rule = rules.get_rule(name)
|
||||||
|
rules.set(rule.default)
|
||||||
|
end
|
||||||
|
|
||||||
|
function rules.listen(name, handler)
|
||||||
|
local rule = rules.get_rule(name)
|
||||||
|
local id = rules.nexid
|
||||||
|
rules.nextid = rules.nexid + 1
|
||||||
|
rule.listeners[utf8.encode(id)] = handler
|
||||||
|
return id
|
||||||
|
end
|
||||||
|
|
||||||
|
function rules.create(name, value, handler)
|
||||||
|
local rule = rules.get_rule(name)
|
||||||
|
rule.default = value
|
||||||
|
|
||||||
|
local handlerid
|
||||||
|
if handler ~= nil then
|
||||||
|
handlerid = rules.listen(name, handler)
|
||||||
|
end
|
||||||
|
if rules.get(name) == nil then
|
||||||
|
rules.set(name, value)
|
||||||
|
elseif handler then
|
||||||
|
handler(rules.get(name))
|
||||||
|
end
|
||||||
|
return handlerid
|
||||||
|
end
|
||||||
|
|
||||||
|
function rules.unlisten(name, id)
|
||||||
|
local rule = rules.rules[name]
|
||||||
|
if rule == nil then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
rule.listeners[utf8.encode(id)] = nil
|
||||||
|
end
|
||||||
|
|
||||||
|
function rules.clear()
|
||||||
|
rules.rules = {}
|
||||||
|
rules.nextid = 1
|
||||||
|
end
|
||||||
|
|
||||||
|
return rules
|
||||||
|
|
@ -3,7 +3,7 @@ local Schedule = {
|
||||||
set_interval = function(self, ms, callback, repetions)
|
set_interval = function(self, ms, callback, repetions)
|
||||||
local id = self._next_interval
|
local id = self._next_interval
|
||||||
self._intervals[id] = {
|
self._intervals[id] = {
|
||||||
last_called = 0.0,
|
last_called = self._timer,
|
||||||
delay = ms / 1000.0,
|
delay = ms / 1000.0,
|
||||||
callback = callback,
|
callback = callback,
|
||||||
repetions = repetions,
|
repetions = repetions,
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,16 @@ end
|
||||||
local Socket = {__index={
|
local Socket = {__index={
|
||||||
send=function(self, ...) return network.__send(self.id, ...) end,
|
send=function(self, ...) return network.__send(self.id, ...) end,
|
||||||
recv=function(self, ...) return network.__recv(self.id, ...) end,
|
recv=function(self, ...) return network.__recv(self.id, ...) end,
|
||||||
|
recv_async=function(self, length, usetable)
|
||||||
|
while self:is_alive() do
|
||||||
|
local available = self:available()
|
||||||
|
if available >= length then
|
||||||
|
return self:recv(length, usetable)
|
||||||
|
end
|
||||||
|
coroutine.yield()
|
||||||
|
end
|
||||||
|
return self:recv(length, usetable)
|
||||||
|
end,
|
||||||
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,
|
||||||
|
|
@ -70,6 +80,7 @@ local DatagramServerSocket = {__index={
|
||||||
|
|
||||||
local _tcp_server_callbacks = {}
|
local _tcp_server_callbacks = {}
|
||||||
local _tcp_client_callbacks = {}
|
local _tcp_client_callbacks = {}
|
||||||
|
local _tcp_client_error_callbacks = {}
|
||||||
|
|
||||||
local _udp_server_callbacks = {}
|
local _udp_server_callbacks = {}
|
||||||
local _udp_client_datagram_callbacks = {}
|
local _udp_client_datagram_callbacks = {}
|
||||||
|
|
@ -116,10 +127,13 @@ network.tcp_open = function (port, handler)
|
||||||
return socket
|
return socket
|
||||||
end
|
end
|
||||||
|
|
||||||
network.tcp_connect = function(address, port, callback)
|
network.tcp_connect = function(address, port, callback, errorCallback)
|
||||||
local socket = setmetatable({id=0}, Socket)
|
local socket = setmetatable({id=0}, Socket)
|
||||||
socket.id = network.__connect_tcp(address, port)
|
socket.id = network.__connect_tcp(address, port)
|
||||||
_tcp_client_callbacks[socket.id] = function() callback(socket) end
|
_tcp_client_callbacks[socket.id] = function() callback(socket) end
|
||||||
|
if errorCallback then
|
||||||
|
_tcp_client_error_callbacks[socket.id] = function(message) errorCallback(socket, message) end
|
||||||
|
end
|
||||||
return socket
|
return socket
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -167,78 +181,12 @@ local function clean(iterable, checkFun, ...)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local updating_blocks = {}
|
|
||||||
local TYPE_REGISTER = 0
|
|
||||||
local TYPE_UNREGISTER = 1
|
|
||||||
|
|
||||||
block.__perform_ticks = function(delta)
|
|
||||||
for id, entry in pairs(updating_blocks) do
|
|
||||||
entry.timer = entry.timer + delta
|
|
||||||
local steps = math.floor(entry.timer / entry.delta * #entry / 3)
|
|
||||||
if steps == 0 then
|
|
||||||
goto continue
|
|
||||||
end
|
|
||||||
entry.timer = 0.0
|
|
||||||
local event = entry.event
|
|
||||||
local tps = entry.tps
|
|
||||||
for i=1, steps do
|
|
||||||
local x = entry[entry.pointer + 1]
|
|
||||||
local y = entry[entry.pointer + 2]
|
|
||||||
local z = entry[entry.pointer + 3]
|
|
||||||
entry.pointer = (entry.pointer + 3) % #entry
|
|
||||||
events.emit(event, x, y, z, tps)
|
|
||||||
end
|
|
||||||
::continue::
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
block.__process_register_events = function()
|
|
||||||
local register_events = block.__pull_register_events()
|
|
||||||
if not register_events then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
for i=1, #register_events, 4 do
|
|
||||||
local header = register_events[i]
|
|
||||||
local type = bit.band(header, 0xFFFF)
|
|
||||||
local id = bit.rshift(header, 16)
|
|
||||||
local x = register_events[i + 1]
|
|
||||||
local y = register_events[i + 2]
|
|
||||||
local z = register_events[i + 3]
|
|
||||||
|
|
||||||
local list = updating_blocks[id]
|
|
||||||
if type == TYPE_REGISTER then
|
|
||||||
if not list then
|
|
||||||
list = {}
|
|
||||||
list.event = block.name(id) .. ".blocktick"
|
|
||||||
list.tps = 20 / (block.properties[id]["tick-interval"] or 1)
|
|
||||||
list.delta = 1.0 / list.tps
|
|
||||||
list.timer = 0.0
|
|
||||||
list.pointer = 0
|
|
||||||
updating_blocks[id] = list
|
|
||||||
end
|
|
||||||
table.insert(list, x)
|
|
||||||
table.insert(list, y)
|
|
||||||
table.insert(list, z)
|
|
||||||
elseif type == TYPE_UNREGISTER then
|
|
||||||
if list then
|
|
||||||
for j=1, #list, 3 do
|
|
||||||
if list[j] == x and list[j + 1] == y and list[j + 2] == z then
|
|
||||||
for k=1,3 do
|
|
||||||
table.remove(list, j)
|
|
||||||
end
|
|
||||||
j = j - 3
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
network.__process_events = function()
|
network.__process_events = function()
|
||||||
local CLIENT_CONNECTED = 1
|
local CLIENT_CONNECTED = 1
|
||||||
local CONNECTED_TO_SERVER = 2
|
local CONNECTED_TO_SERVER = 2
|
||||||
local DATAGRAM = 3
|
local DATAGRAM = 3
|
||||||
local RESPONSE = 4
|
local RESPONSE = 4
|
||||||
|
local CONNECTION_ERROR = 5
|
||||||
|
|
||||||
local ON_SERVER = 1
|
local ON_SERVER = 1
|
||||||
local ON_CLIENT = 2
|
local ON_CLIENT = 2
|
||||||
|
|
@ -258,6 +206,11 @@ network.__process_events = function()
|
||||||
if callback then
|
if callback then
|
||||||
callback()
|
callback()
|
||||||
end
|
end
|
||||||
|
elseif etype == CONNECTION_ERROR then
|
||||||
|
local callback = _tcp_client_error_callbacks[cid]
|
||||||
|
if callback then
|
||||||
|
callback(addr)
|
||||||
|
end
|
||||||
elseif etype == DATAGRAM then
|
elseif etype == DATAGRAM then
|
||||||
if side == ON_CLIENT then
|
if side == ON_CLIENT then
|
||||||
local callback = _udp_client_datagram_callbacks[cid]
|
local callback = _udp_client_datagram_callbacks[cid]
|
||||||
|
|
|
||||||
144
res/scripts/internal_events.lua
Normal file
144
res/scripts/internal_events.lua
Normal file
|
|
@ -0,0 +1,144 @@
|
||||||
|
local updating_blocks = {}
|
||||||
|
local present_queues = {}
|
||||||
|
local REGISTER_BIT = 0x1
|
||||||
|
local UPDATING_BIT = 0x2
|
||||||
|
local PRESENT_BIT = 0x4
|
||||||
|
local REMOVED_BIT = 0x8
|
||||||
|
|
||||||
|
block.__perform_ticks = function(delta)
|
||||||
|
for id, entry in pairs(updating_blocks) do
|
||||||
|
entry.timer = entry.timer + delta
|
||||||
|
local steps = math.floor(entry.timer / entry.delta * #entry / 3)
|
||||||
|
if steps == 0 then
|
||||||
|
goto continue
|
||||||
|
end
|
||||||
|
entry.timer = 0.0
|
||||||
|
local event = entry.event
|
||||||
|
local tps = entry.tps
|
||||||
|
for i=1, steps do
|
||||||
|
local x = entry[entry.pointer + 1]
|
||||||
|
local y = entry[entry.pointer + 2]
|
||||||
|
local z = entry[entry.pointer + 3]
|
||||||
|
entry.pointer = (entry.pointer + 3) % #entry
|
||||||
|
events.emit(event, x, y, z, tps)
|
||||||
|
end
|
||||||
|
::continue::
|
||||||
|
end
|
||||||
|
for id, queue in pairs(present_queues) do
|
||||||
|
queue.timer = queue.timer + delta
|
||||||
|
local steps = math.floor(queue.timer / queue.delta * #queue / 3)
|
||||||
|
if steps == 0 then
|
||||||
|
goto continue
|
||||||
|
end
|
||||||
|
queue.timer = 0.0
|
||||||
|
local event = queue.event
|
||||||
|
local update_list = updating_blocks[id]
|
||||||
|
for i=1, steps do
|
||||||
|
local index = #queue - 2
|
||||||
|
if index <= 0 then
|
||||||
|
break
|
||||||
|
end
|
||||||
|
local x = queue[index]
|
||||||
|
local y = queue[index + 1]
|
||||||
|
local z = queue[index + 2]
|
||||||
|
|
||||||
|
for j=1,3 do
|
||||||
|
table.remove(queue, index)
|
||||||
|
end
|
||||||
|
events.emit(event, x, y, z)
|
||||||
|
|
||||||
|
if queue.updating then
|
||||||
|
table.insert(update_list, x)
|
||||||
|
table.insert(update_list, y)
|
||||||
|
table.insert(update_list, z)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
::continue::
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local block_pull_register_events = block.__pull_register_events
|
||||||
|
block.__pull_register_events = nil
|
||||||
|
|
||||||
|
block.__process_register_events = function()
|
||||||
|
local register_events = block_pull_register_events()
|
||||||
|
if not register_events then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
|
||||||
|
local emit_event = events.emit
|
||||||
|
local removed_events = {}
|
||||||
|
|
||||||
|
for i=1, #register_events, 4 do
|
||||||
|
local header = register_events[i]
|
||||||
|
local event_bits = bit.band(header, 0xFFFF)
|
||||||
|
local id = bit.rshift(header, 16)
|
||||||
|
local x = register_events[i + 1]
|
||||||
|
local y = register_events[i + 2]
|
||||||
|
local z = register_events[i + 3]
|
||||||
|
|
||||||
|
local is_register = bit.band(event_bits, REGISTER_BIT) ~= 0
|
||||||
|
local is_updating = bit.band(event_bits, UPDATING_BIT) ~= 0
|
||||||
|
local is_present = bit.band(event_bits, PRESENT_BIT) ~= 0
|
||||||
|
local is_removed = bit.band(event_bits, REMOVED_BIT) ~= 0
|
||||||
|
local list = updating_blocks[id]
|
||||||
|
|
||||||
|
if not is_register and is_removed then
|
||||||
|
local rm_event = removed_events[id]
|
||||||
|
if not rm_event then
|
||||||
|
rm_event = block.name(id) .. ".blockremoved"
|
||||||
|
removed_events[id] = rm_event
|
||||||
|
end
|
||||||
|
emit_event(rm_event, x, y, z)
|
||||||
|
end
|
||||||
|
|
||||||
|
if not list and is_register and is_updating then
|
||||||
|
list = {}
|
||||||
|
list.event = block.name(id) .. ".blocktick"
|
||||||
|
list.tps = 20 / (block.properties[id]["tick-interval"] or 1)
|
||||||
|
list.delta = 1.0 / list.tps
|
||||||
|
list.timer = 0.0
|
||||||
|
list.pointer = 0
|
||||||
|
updating_blocks[id] = list
|
||||||
|
end
|
||||||
|
|
||||||
|
if is_register and is_present then
|
||||||
|
local present_queue = present_queues[id]
|
||||||
|
if not present_queue then
|
||||||
|
present_queue = {}
|
||||||
|
present_queue.event = block.name(id) .. ".blockpresent"
|
||||||
|
present_queue.tps = 20 / (block.properties[id]["tick-interval"] or 1)
|
||||||
|
present_queue.delta = 1.0 / present_queue.tps
|
||||||
|
present_queue.timer = 0.0
|
||||||
|
present_queue.pointer = 0
|
||||||
|
present_queue.updating = is_updating
|
||||||
|
present_queues[id] = present_queue
|
||||||
|
end
|
||||||
|
table.insert(present_queue, x)
|
||||||
|
table.insert(present_queue, y)
|
||||||
|
table.insert(present_queue, z)
|
||||||
|
goto continue
|
||||||
|
end
|
||||||
|
if not is_updating then
|
||||||
|
goto continue
|
||||||
|
end
|
||||||
|
if is_register then
|
||||||
|
table.insert(list, x)
|
||||||
|
table.insert(list, y)
|
||||||
|
table.insert(list, z)
|
||||||
|
else
|
||||||
|
if not list then
|
||||||
|
goto continue
|
||||||
|
end
|
||||||
|
for j=1, #list, 3 do
|
||||||
|
if list[j] == x and list[j + 1] == y and list[j + 2] == z then
|
||||||
|
for k=1,3 do
|
||||||
|
table.remove(list, j)
|
||||||
|
end
|
||||||
|
j = j - 3
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
::continue::
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -47,6 +47,7 @@ local function complete_app_lib(app)
|
||||||
end
|
end
|
||||||
app.reset_content = core.reset_content
|
app.reset_content = core.reset_content
|
||||||
app.is_content_loaded = core.is_content_loaded
|
app.is_content_loaded = core.is_content_loaded
|
||||||
|
app.set_title = core.set_title
|
||||||
|
|
||||||
function app.config_packs(packs_list)
|
function app.config_packs(packs_list)
|
||||||
-- Check if packs are valid and add dependencies to the configuration
|
-- Check if packs are valid and add dependencies to the configuration
|
||||||
|
|
@ -105,82 +106,10 @@ elseif __vc_app then
|
||||||
complete_app_lib(__vc_app)
|
complete_app_lib(__vc_app)
|
||||||
end
|
end
|
||||||
|
|
||||||
function inventory.get_uses(invid, slot)
|
require "core:internal/maths_inline"
|
||||||
local uses = inventory.get_data(invid, slot, "uses")
|
require "core:internal/debugging"
|
||||||
if uses == nil then
|
require "core:internal/audio_input"
|
||||||
return item.uses(inventory.get(invid, slot))
|
require "core:internal/extensions/inventory"
|
||||||
end
|
|
||||||
return uses
|
|
||||||
end
|
|
||||||
|
|
||||||
function inventory.use(invid, slot)
|
|
||||||
local itemid, count = inventory.get(invid, slot)
|
|
||||||
if itemid == nil then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
local item_uses = inventory.get_uses(invid, slot)
|
|
||||||
if item_uses == nil then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
if item_uses == 1 then
|
|
||||||
inventory.set(invid, slot, itemid, count - 1)
|
|
||||||
elseif item_uses > 1 then
|
|
||||||
inventory.set_data(invid, slot, "uses", item_uses - 1)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function inventory.decrement(invid, slot, count)
|
|
||||||
count = count or 1
|
|
||||||
local itemid, itemcount = inventory.get(invid, slot)
|
|
||||||
if itemcount <= count then
|
|
||||||
inventory.set(invid, slot, 0)
|
|
||||||
else
|
|
||||||
inventory.set_count(invid, slot, itemcount - count)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function inventory.get_caption(invid, slot)
|
|
||||||
local item_id, count = inventory.get(invid, slot)
|
|
||||||
local caption = inventory.get_data(invid, slot, "caption")
|
|
||||||
if not caption then return item.caption(item_id) end
|
|
||||||
|
|
||||||
return caption
|
|
||||||
end
|
|
||||||
|
|
||||||
function inventory.set_caption(invid, slot, caption)
|
|
||||||
local itemid, itemcount = inventory.get(invid, slot)
|
|
||||||
if itemid == 0 then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
if caption == nil or type(caption) ~= "string" then
|
|
||||||
caption = ""
|
|
||||||
end
|
|
||||||
inventory.set_data(invid, slot, "caption", caption)
|
|
||||||
end
|
|
||||||
|
|
||||||
function inventory.get_description(invid, slot)
|
|
||||||
local item_id, count = inventory.get(invid, slot)
|
|
||||||
local description = inventory.get_data(invid, slot, "description")
|
|
||||||
if not description then return item.description(item_id) end
|
|
||||||
|
|
||||||
return description
|
|
||||||
end
|
|
||||||
|
|
||||||
function inventory.set_description(invid, slot, description)
|
|
||||||
local itemid, itemcount = inventory.get(invid, slot)
|
|
||||||
if itemid == 0 then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
if description == nil or type(description) ~= "string" then
|
|
||||||
description = ""
|
|
||||||
end
|
|
||||||
inventory.set_data(invid, slot, "description", description)
|
|
||||||
end
|
|
||||||
|
|
||||||
if enable_experimental then
|
|
||||||
require "core:internal/maths_inline"
|
|
||||||
end
|
|
||||||
|
|
||||||
asserts = require "core:internal/asserts"
|
asserts = require "core:internal/asserts"
|
||||||
events = require "core:internal/events"
|
events = require "core:internal/events"
|
||||||
|
|
||||||
|
|
@ -188,6 +117,23 @@ function pack.unload(prefix)
|
||||||
events.remove_by_prefix(prefix)
|
events.remove_by_prefix(prefix)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function __vc_start_app_script(path, name)
|
||||||
|
debug.log("starting application script "..path)
|
||||||
|
|
||||||
|
local code = file.read(path)
|
||||||
|
local chunk, err = loadstring(code, path)
|
||||||
|
if chunk == nil then
|
||||||
|
error(err)
|
||||||
|
end
|
||||||
|
local script_env = setmetatable({app = app or __vc_app}, {__index=_G})
|
||||||
|
chunk = setfenv(chunk, script_env)
|
||||||
|
if name then
|
||||||
|
return start_coroutine(chunk, name)
|
||||||
|
else
|
||||||
|
return __vc_start_coroutine(chunk)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
gui_util = require "core:internal/gui_util"
|
gui_util = require "core:internal/gui_util"
|
||||||
|
|
||||||
Document = gui_util.Document
|
Document = gui_util.Document
|
||||||
|
|
@ -283,11 +229,6 @@ entities.get_all = function(uids)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local bytearray = require "core:internal/bytearray"
|
|
||||||
Bytearray = bytearray.FFIBytearray
|
|
||||||
Bytearray_as_string = bytearray.FFIBytearray_as_string
|
|
||||||
Bytearray_construct = function(...) return Bytearray(...) end
|
|
||||||
|
|
||||||
__vc_scripts_registry = require "core:internal/scripts_registry"
|
__vc_scripts_registry = require "core:internal/scripts_registry"
|
||||||
|
|
||||||
file.open = require "core:internal/stream_providers/file"
|
file.open = require "core:internal/stream_providers/file"
|
||||||
|
|
@ -307,86 +248,20 @@ else
|
||||||
os.pid = ffi.C.getpid()
|
os.pid = ffi.C.getpid()
|
||||||
end
|
end
|
||||||
|
|
||||||
ffi = nil
|
|
||||||
__vc_lock_internal_modules()
|
|
||||||
|
|
||||||
math.randomseed(time.uptime() * 1536227939)
|
math.randomseed(time.uptime() * 1536227939)
|
||||||
|
|
||||||
rules = {nexid = 1, rules = {}}
|
rules = require "core:internal/rules"
|
||||||
local _rules = rules
|
local _rules = rules
|
||||||
|
|
||||||
function _rules.get_rule(name)
|
|
||||||
local rule = _rules.rules[name]
|
|
||||||
if rule == nil then
|
|
||||||
rule = {listeners={}}
|
|
||||||
_rules.rules[name] = rule
|
|
||||||
end
|
|
||||||
return rule
|
|
||||||
end
|
|
||||||
|
|
||||||
function _rules.get(name)
|
|
||||||
local rule = _rules.rules[name]
|
|
||||||
if rule == nil then
|
|
||||||
return nil
|
|
||||||
end
|
|
||||||
return rule.value
|
|
||||||
end
|
|
||||||
|
|
||||||
function _rules.set(name, value)
|
|
||||||
local rule = _rules.get_rule(name)
|
|
||||||
rule.value = value
|
|
||||||
for _, handler in pairs(rule.listeners) do
|
|
||||||
handler(value)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function _rules.reset(name)
|
|
||||||
local rule = _rules.get_rule(name)
|
|
||||||
_rules.set(rule.default)
|
|
||||||
end
|
|
||||||
|
|
||||||
function _rules.listen(name, handler)
|
|
||||||
local rule = _rules.get_rule(name)
|
|
||||||
local id = _rules.nexid
|
|
||||||
_rules.nextid = _rules.nexid + 1
|
|
||||||
rule.listeners[utf8.encode(id)] = handler
|
|
||||||
return id
|
|
||||||
end
|
|
||||||
|
|
||||||
function _rules.create(name, value, handler)
|
|
||||||
local rule = _rules.get_rule(name)
|
|
||||||
rule.default = value
|
|
||||||
|
|
||||||
local handlerid
|
|
||||||
if handler ~= nil then
|
|
||||||
handlerid = _rules.listen(name, handler)
|
|
||||||
end
|
|
||||||
if _rules.get(name) == nil then
|
|
||||||
_rules.set(name, value)
|
|
||||||
elseif handler then
|
|
||||||
handler(_rules.get(name))
|
|
||||||
end
|
|
||||||
return handlerid
|
|
||||||
end
|
|
||||||
|
|
||||||
function _rules.unlisten(name, id)
|
|
||||||
local rule = _rules.rules[name]
|
|
||||||
if rule == nil then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
rule.listeners[utf8.encode(id)] = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
function _rules.clear()
|
|
||||||
_rules.rules = {}
|
|
||||||
_rules.nextid = 1
|
|
||||||
end
|
|
||||||
|
|
||||||
function __vc_on_hud_open()
|
function __vc_on_hud_open()
|
||||||
|
local _hud_is_content_access = hud._is_content_access
|
||||||
|
local _hud_set_content_access = hud._set_content_access
|
||||||
|
local _hud_set_debug_cheats = hud._set_debug_cheats
|
||||||
|
|
||||||
_rules.create("allow-cheats", true)
|
_rules.create("allow-cheats", true)
|
||||||
|
|
||||||
_rules.create("allow-content-access", hud._is_content_access(), function(value)
|
_rules.create("allow-content-access", _hud_is_content_access(), function(value)
|
||||||
hud._set_content_access(value)
|
_hud_set_content_access(value)
|
||||||
end)
|
end)
|
||||||
_rules.create("allow-flight", true, function(value)
|
_rules.create("allow-flight", true, function(value)
|
||||||
input.set_enabled("player.flight", value)
|
input.set_enabled("player.flight", value)
|
||||||
|
|
@ -407,7 +282,7 @@ function __vc_on_hud_open()
|
||||||
input.set_enabled("player.fast_interaction", value)
|
input.set_enabled("player.fast_interaction", value)
|
||||||
end)
|
end)
|
||||||
_rules.create("allow-debug-cheats", true, function(value)
|
_rules.create("allow-debug-cheats", true, function(value)
|
||||||
hud._set_debug_cheats(value)
|
_hud_set_debug_cheats(value)
|
||||||
end)
|
end)
|
||||||
input.add_callback("devtools.console", function()
|
input.add_callback("devtools.console", function()
|
||||||
if menu.page ~= "" then
|
if menu.page ~= "" then
|
||||||
|
|
@ -427,7 +302,9 @@ function __vc_on_hud_open()
|
||||||
end)
|
end)
|
||||||
input.add_callback("key:escape", function()
|
input.add_callback("key:escape", function()
|
||||||
if menu.page ~= "" then
|
if menu.page ~= "" then
|
||||||
menu:reset()
|
if not menu:back() then
|
||||||
|
menu:reset()
|
||||||
|
end
|
||||||
elseif hud.is_inventory_open() then
|
elseif hud.is_inventory_open() then
|
||||||
hud.close_inventory()
|
hud.close_inventory()
|
||||||
else
|
else
|
||||||
|
|
@ -485,6 +362,10 @@ function __vc_on_world_tick(tps)
|
||||||
time.schedules.world:tick(1.0 / tps)
|
time.schedules.world:tick(1.0 / tps)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function __vc_process_before_quit()
|
||||||
|
block.__process_register_events()
|
||||||
|
end
|
||||||
|
|
||||||
function __vc_on_world_save()
|
function __vc_on_world_save()
|
||||||
local rule_values = {}
|
local rule_values = {}
|
||||||
for name, rule in pairs(rules.rules) do
|
for name, rule in pairs(rules.rules) do
|
||||||
|
|
@ -562,6 +443,9 @@ end
|
||||||
|
|
||||||
local __post_runnables = {}
|
local __post_runnables = {}
|
||||||
|
|
||||||
|
local fn_audio_reset_fetch_buffer = audio.__reset_fetch_buffer
|
||||||
|
audio.__reset_fetch_buffer = nil
|
||||||
|
|
||||||
function __process_post_runnables()
|
function __process_post_runnables()
|
||||||
if #__post_runnables then
|
if #__post_runnables then
|
||||||
for _, func in ipairs(__post_runnables) do
|
for _, func in ipairs(__post_runnables) do
|
||||||
|
|
@ -587,6 +471,7 @@ function __process_post_runnables()
|
||||||
__vc_named_coroutines[name] = nil
|
__vc_named_coroutines[name] = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
fn_audio_reset_fetch_buffer()
|
||||||
debug.pull_events()
|
debug.pull_events()
|
||||||
network.__process_events()
|
network.__process_events()
|
||||||
block.__process_register_events()
|
block.__process_register_events()
|
||||||
|
|
@ -606,6 +491,7 @@ local _getinfo = debug.getinfo
|
||||||
for i, name in ipairs(removed_names) do
|
for i, name in ipairs(removed_names) do
|
||||||
debug[name] = nil
|
debug[name] = nil
|
||||||
end
|
end
|
||||||
|
|
||||||
debug.getinfo = function(lvl, fields)
|
debug.getinfo = function(lvl, fields)
|
||||||
if type(lvl) == "number" then
|
if type(lvl) == "number" then
|
||||||
lvl = lvl + 1
|
lvl = lvl + 1
|
||||||
|
|
@ -615,51 +501,7 @@ debug.getinfo = function(lvl, fields)
|
||||||
return debuginfo
|
return debuginfo
|
||||||
end
|
end
|
||||||
|
|
||||||
-- --------- Deprecated functions ------ --
|
require "core:internal/deprecated"
|
||||||
local function wrap_deprecated(func, name, alternatives)
|
|
||||||
return function (...)
|
|
||||||
on_deprecated_call(name, alternatives)
|
|
||||||
return func(...)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
block_index = wrap_deprecated(block.index, "block_index", "block.index")
|
ffi = nil
|
||||||
block_name = wrap_deprecated(block.name, "block_name", "block.name")
|
__vc_lock_internal_modules()
|
||||||
blocks_count = wrap_deprecated(block.defs_count, "blocks_count", "block.defs_count")
|
|
||||||
is_solid_at = wrap_deprecated(block.is_solid_at, "is_solid_at", "block.is_solid_at")
|
|
||||||
is_replaceable_at = wrap_deprecated(block.is_replaceable_at, "is_replaceable_at", "block.is_replaceable_at")
|
|
||||||
set_block = wrap_deprecated(block.set, "set_block", "block.set")
|
|
||||||
get_block = wrap_deprecated(block.get, "get_block", "block.get")
|
|
||||||
get_block_X = wrap_deprecated(block.get_X, "get_block_X", "block.get_X")
|
|
||||||
get_block_Y = wrap_deprecated(block.get_Y, "get_block_Y", "block.get_Y")
|
|
||||||
get_block_Z = wrap_deprecated(block.get_Z, "get_block_Z", "block.get_Z")
|
|
||||||
get_block_states = wrap_deprecated(block.get_states, "get_block_states", "block.get_states")
|
|
||||||
set_block_states = wrap_deprecated(block.set_states, "set_block_states", "block.set_states")
|
|
||||||
get_block_rotation = wrap_deprecated(block.get_rotation, "get_block_rotation", "block.get_rotation")
|
|
||||||
set_block_rotation = wrap_deprecated(block.set_rotation, "set_block_rotation", "block.set_rotation")
|
|
||||||
get_block_user_bits = wrap_deprecated(block.get_user_bits, "get_block_user_bits", "block.get_user_bits")
|
|
||||||
set_block_user_bits = wrap_deprecated(block.set_user_bits, "set_block_user_bits", "block.set_user_bits")
|
|
||||||
|
|
||||||
function load_script(path, nocache)
|
|
||||||
on_deprecated_call("load_script", "require or loadstring")
|
|
||||||
return __load_script(path, nocache)
|
|
||||||
end
|
|
||||||
|
|
||||||
_dofile = dofile
|
|
||||||
-- Replaces dofile('*/content/packid/*') with load_script('packid:*')
|
|
||||||
function dofile(path)
|
|
||||||
on_deprecated_call("dofile", "require or loadstring")
|
|
||||||
local index = string.find(path, "/content/")
|
|
||||||
if index then
|
|
||||||
local newpath = string.sub(path, index+9)
|
|
||||||
index = string.find(newpath, "/")
|
|
||||||
if index then
|
|
||||||
local label = string.sub(newpath, 1, index-1)
|
|
||||||
newpath = label..':'..string.sub(newpath, index+1)
|
|
||||||
if file.isfile(newpath) then
|
|
||||||
return __load_script(newpath, true)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return _dofile(path)
|
|
||||||
end
|
|
||||||
|
|
|
||||||
|
|
@ -1,201 +1,8 @@
|
||||||
local breakpoints = {}
|
|
||||||
local dbg_steps_mode = false
|
|
||||||
local dbg_step_into_func = false
|
|
||||||
local hook_lock = false
|
|
||||||
local current_func
|
|
||||||
local current_func_stack_size
|
|
||||||
|
|
||||||
local _debug_getinfo = debug.getinfo
|
|
||||||
local _debug_getlocal = debug.getlocal
|
|
||||||
local __pause = debug.pause
|
|
||||||
local __error = error
|
|
||||||
local __sethook = debug.sethook
|
|
||||||
|
|
||||||
-- 'return' hook not called for some functions
|
|
||||||
-- todo: speedup
|
|
||||||
local function calc_stack_size()
|
|
||||||
local s = debug.traceback("", 2)
|
|
||||||
local count = 0
|
|
||||||
for i in s:gmatch("\n") do
|
|
||||||
count = count + 1
|
|
||||||
end
|
|
||||||
return count
|
|
||||||
end
|
|
||||||
|
|
||||||
local is_debugging = debug.is_debugging()
|
|
||||||
if is_debugging then
|
|
||||||
__sethook(function (e, line)
|
|
||||||
if e == "return" then
|
|
||||||
local info = _debug_getinfo(2)
|
|
||||||
if info.func == current_func then
|
|
||||||
current_func = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if dbg_steps_mode and not hook_lock then
|
|
||||||
hook_lock = true
|
|
||||||
|
|
||||||
if not dbg_step_into_func then
|
|
||||||
local func = _debug_getinfo(2).func
|
|
||||||
if func ~= current_func then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
if current_func_stack_size ~= calc_stack_size() then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
end
|
|
||||||
current_func = func
|
|
||||||
__pause("step")
|
|
||||||
debug.pull_events()
|
|
||||||
end
|
|
||||||
hook_lock = false
|
|
||||||
local bps = breakpoints[line]
|
|
||||||
if not bps then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
local source = _debug_getinfo(2).source
|
|
||||||
if not bps[source] then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
current_func = _debug_getinfo(2).func
|
|
||||||
current_func_stack_size = calc_stack_size()
|
|
||||||
__pause("breakpoint")
|
|
||||||
debug.pull_events()
|
|
||||||
end, "lr")
|
|
||||||
end
|
|
||||||
|
|
||||||
local DBG_EVENT_SET_BREAKPOINT = 1
|
|
||||||
local DBG_EVENT_RM_BREAKPOINT = 2
|
|
||||||
local DBG_EVENT_STEP = 3
|
|
||||||
local DBG_EVENT_STEP_INTO_FUNCTION = 4
|
|
||||||
local DBG_EVENT_RESUME = 5
|
|
||||||
local DBG_EVENT_GET_VALUE = 6
|
|
||||||
local __pull_events = debug.__pull_events
|
|
||||||
local __sendvalue = debug.__sendvalue
|
|
||||||
debug.__pull_events = nil
|
|
||||||
debug.__sendvalue = nil
|
|
||||||
|
|
||||||
function debug.pull_events()
|
|
||||||
if not is_debugging then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
if not debug.is_debugging() then
|
|
||||||
is_debugging = false
|
|
||||||
__sethook()
|
|
||||||
end
|
|
||||||
local events = __pull_events()
|
|
||||||
if not events then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
for i, event in ipairs(events) do
|
|
||||||
if event[1] == DBG_EVENT_SET_BREAKPOINT then
|
|
||||||
debug.set_breakpoint(event[2], event[3])
|
|
||||||
elseif event[1] == DBG_EVENT_RM_BREAKPOINT then
|
|
||||||
debug.remove_breakpoint(event[2], event[3])
|
|
||||||
elseif event[1] == DBG_EVENT_STEP then
|
|
||||||
dbg_steps_mode = true
|
|
||||||
dbg_step_into_func = false
|
|
||||||
elseif event[1] == DBG_EVENT_STEP_INTO_FUNCTION then
|
|
||||||
dbg_steps_mode = true
|
|
||||||
dbg_step_into_func = true
|
|
||||||
elseif event[1] == DBG_EVENT_RESUME then
|
|
||||||
dbg_steps_mode = false
|
|
||||||
dbg_step_into_func = false
|
|
||||||
elseif event[1] == DBG_EVENT_GET_VALUE then
|
|
||||||
local _, value = _debug_getlocal(event[2] + 3, event[3])
|
|
||||||
for _, key in ipairs(event[4]) do
|
|
||||||
if value == nil then
|
|
||||||
value = "error: index nil value"
|
|
||||||
break
|
|
||||||
end
|
|
||||||
value = value[key]
|
|
||||||
end
|
|
||||||
__sendvalue(value, event[2], event[3], event[4])
|
|
||||||
__pause()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function debug.set_breakpoint(source, line)
|
|
||||||
local bps = breakpoints[line]
|
|
||||||
if not bps then
|
|
||||||
bps = {}
|
|
||||||
breakpoints[line] = bps
|
|
||||||
end
|
|
||||||
bps[source] = true
|
|
||||||
end
|
|
||||||
|
|
||||||
function debug.remove_breakpoint(source, line)
|
|
||||||
local bps = breakpoints[line]
|
|
||||||
if not bps then
|
|
||||||
return
|
|
||||||
end
|
|
||||||
bps[source] = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
function error(message, level)
|
|
||||||
if is_debugging then
|
|
||||||
__pause("exception", message)
|
|
||||||
end
|
|
||||||
__error(message, level)
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Lua has no parallelizm, also _set_data does not call any lua functions so
|
|
||||||
-- may be reused one global ffi buffer per lua_State
|
|
||||||
local canvas_ffi_buffer
|
|
||||||
local canvas_ffi_buffer_size = 0
|
|
||||||
|
|
||||||
local ipairs_mt_supported = false
|
|
||||||
for i, _ in ipairs(setmetatable({l={1}}, {
|
|
||||||
__ipairs=function(self) return ipairs(self.l) end})) do
|
|
||||||
ipairs_mt_supported = true
|
|
||||||
end
|
|
||||||
|
|
||||||
if not ipairs_mt_supported then
|
|
||||||
local raw_ipairs = ipairs
|
|
||||||
ipairs = function(t)
|
|
||||||
local metatable = getmetatable(t)
|
|
||||||
if metatable and metatable.__ipairs then
|
|
||||||
return metatable.__ipairs(t)
|
|
||||||
end
|
|
||||||
return raw_ipairs(t)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function await(co)
|
|
||||||
local res, err
|
|
||||||
while coroutine.status(co) ~= 'dead' do
|
|
||||||
coroutine.yield()
|
|
||||||
res, err = coroutine.resume(co)
|
|
||||||
if err then
|
|
||||||
return res, err
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return res, err
|
|
||||||
end
|
|
||||||
|
|
||||||
local _ffi = ffi
|
local _ffi = ffi
|
||||||
function __vc_Canvas_set_data(self, data)
|
local _debug_getinfo = debug.getinfo
|
||||||
if type(data) == "cdata" then
|
|
||||||
self:_set_data(tostring(_ffi.cast("uintptr_t", data)))
|
|
||||||
end
|
|
||||||
local width = self.width
|
|
||||||
local height = self.height
|
|
||||||
|
|
||||||
local size = width * height * 4
|
|
||||||
if size > canvas_ffi_buffer_size then
|
|
||||||
canvas_ffi_buffer = _ffi.new(
|
|
||||||
string.format("unsigned char[%s]", size)
|
|
||||||
)
|
|
||||||
canvas_ffi_buffer_size = size
|
|
||||||
end
|
|
||||||
for i=0, size - 1 do
|
|
||||||
canvas_ffi_buffer[i] = data[i + 1]
|
|
||||||
end
|
|
||||||
self:_set_data(tostring(_ffi.cast("uintptr_t", canvas_ffi_buffer)))
|
|
||||||
end
|
|
||||||
|
|
||||||
function crc32(bytes, chksum)
|
function crc32(bytes, chksum)
|
||||||
local chksum = chksum or 0
|
chksum = chksum or 0
|
||||||
|
|
||||||
local length = #bytes
|
local length = #bytes
|
||||||
if type(bytes) == "table" then
|
if type(bytes) == "table" then
|
||||||
|
|
@ -231,20 +38,59 @@ function parse_path(path)
|
||||||
return string.sub(path, 1, index-1), string.sub(path, index+1, -1)
|
return string.sub(path, 1, index-1), string.sub(path, index+1, -1)
|
||||||
end
|
end
|
||||||
|
|
||||||
function pack.is_installed(packid)
|
-- Lua has no parallelizm, also _set_data does not call any lua functions so
|
||||||
return file.isfile(packid..":package.json")
|
-- may be reused one global ffi buffer per lua_State
|
||||||
|
local canvas_ffi_buffer
|
||||||
|
local canvas_ffi_buffer_size = 0
|
||||||
|
local _ffi = ffi
|
||||||
|
function __vc_Canvas_set_data(self, data)
|
||||||
|
if type(data) == "cdata" then
|
||||||
|
self:_set_data(tostring(_ffi.cast("uintptr_t", data)))
|
||||||
|
end
|
||||||
|
local width = self.width
|
||||||
|
local height = self.height
|
||||||
|
|
||||||
|
local size = width * height * 4
|
||||||
|
if size > canvas_ffi_buffer_size then
|
||||||
|
canvas_ffi_buffer = _ffi.new(
|
||||||
|
string.format("unsigned char[%s]", size)
|
||||||
|
)
|
||||||
|
canvas_ffi_buffer_size = size
|
||||||
|
end
|
||||||
|
for i=0, size - 1 do
|
||||||
|
canvas_ffi_buffer[i] = data[i + 1]
|
||||||
|
end
|
||||||
|
self:_set_data(tostring(_ffi.cast("uintptr_t", canvas_ffi_buffer)))
|
||||||
end
|
end
|
||||||
|
|
||||||
function pack.data_file(packid, name)
|
local ipairs_mt_supported = false
|
||||||
file.mkdirs("world:data/"..packid)
|
for i, _ in ipairs(setmetatable({l={1}}, {
|
||||||
return "world:data/"..packid.."/"..name
|
__ipairs=function(self) return ipairs(self.l) end})) do
|
||||||
|
ipairs_mt_supported = true
|
||||||
end
|
end
|
||||||
|
|
||||||
function pack.shared_file(packid, name)
|
if not ipairs_mt_supported then
|
||||||
file.mkdirs("config:"..packid)
|
local raw_ipairs = ipairs
|
||||||
return "config:"..packid.."/"..name
|
ipairs = function(t)
|
||||||
|
local metatable = getmetatable(t)
|
||||||
|
if metatable and metatable.__ipairs then
|
||||||
|
return metatable.__ipairs(t)
|
||||||
|
end
|
||||||
|
return raw_ipairs(t)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function await(co)
|
||||||
|
local res, err
|
||||||
|
while coroutine.status(co) ~= 'dead' do
|
||||||
|
coroutine.yield()
|
||||||
|
res, err = coroutine.resume(co)
|
||||||
|
if err then
|
||||||
|
return res, err
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return res, err
|
||||||
|
end
|
||||||
|
|
||||||
function timeit(iters, func, ...)
|
function timeit(iters, func, ...)
|
||||||
local tm = os.clock()
|
local tm = os.clock()
|
||||||
|
|
@ -256,364 +102,6 @@ end
|
||||||
|
|
||||||
----------------------------------------------
|
----------------------------------------------
|
||||||
|
|
||||||
function math.clamp(_in, low, high)
|
|
||||||
return math.min(math.max(_in, low), high)
|
|
||||||
end
|
|
||||||
|
|
||||||
function math.rand(low, high)
|
|
||||||
return low + (high - low) * math.random()
|
|
||||||
end
|
|
||||||
|
|
||||||
function math.normalize(num, conf)
|
|
||||||
conf = conf or 1
|
|
||||||
|
|
||||||
return (num / conf) % 1
|
|
||||||
end
|
|
||||||
|
|
||||||
function math.round(num, places)
|
|
||||||
places = places or 0
|
|
||||||
|
|
||||||
local mult = 10 ^ places
|
|
||||||
return math.floor(num * mult + 0.5) / mult
|
|
||||||
end
|
|
||||||
|
|
||||||
function math.sum(...)
|
|
||||||
local numbers = nil
|
|
||||||
local sum = 0
|
|
||||||
|
|
||||||
if type(...) == "table" then
|
|
||||||
numbers = ...
|
|
||||||
else
|
|
||||||
numbers = {...}
|
|
||||||
end
|
|
||||||
|
|
||||||
for _, v in ipairs(numbers) do
|
|
||||||
sum = sum + v
|
|
||||||
end
|
|
||||||
|
|
||||||
return sum
|
|
||||||
end
|
|
||||||
|
|
||||||
----------------------------------------------
|
|
||||||
|
|
||||||
function table.copy(t)
|
|
||||||
local copied = {}
|
|
||||||
|
|
||||||
for k, v in pairs(t) do
|
|
||||||
copied[k] = v
|
|
||||||
end
|
|
||||||
|
|
||||||
return copied
|
|
||||||
end
|
|
||||||
|
|
||||||
function table.deep_copy(t)
|
|
||||||
local copied = {}
|
|
||||||
|
|
||||||
for k, v in pairs(t) do
|
|
||||||
if type(v) == "table" then
|
|
||||||
copied[k] = table.deep_copy(v)
|
|
||||||
else
|
|
||||||
copied[k] = v
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return setmetatable(copied, getmetatable(t))
|
|
||||||
end
|
|
||||||
|
|
||||||
function table.count_pairs(t)
|
|
||||||
local count = 0
|
|
||||||
|
|
||||||
for k, v in pairs(t) do
|
|
||||||
count = count + 1
|
|
||||||
end
|
|
||||||
|
|
||||||
return count
|
|
||||||
end
|
|
||||||
|
|
||||||
function table.random(t)
|
|
||||||
return t[math.random(1, #t)]
|
|
||||||
end
|
|
||||||
|
|
||||||
function table.shuffle(t)
|
|
||||||
for i = #t, 2, -1 do
|
|
||||||
local j = math.random(i)
|
|
||||||
t[i], t[j] = t[j], t[i]
|
|
||||||
end
|
|
||||||
|
|
||||||
return t
|
|
||||||
end
|
|
||||||
|
|
||||||
function table.merge(t1, t2)
|
|
||||||
for i, v in pairs(t2) do
|
|
||||||
if type(i) == "number" then
|
|
||||||
t1[#t1 + 1] = v
|
|
||||||
elseif t1[i] == nil then
|
|
||||||
t1[i] = v
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return t1
|
|
||||||
end
|
|
||||||
|
|
||||||
function table.map(t, func)
|
|
||||||
for i, v in pairs(t) do
|
|
||||||
t[i] = func(i, v)
|
|
||||||
end
|
|
||||||
|
|
||||||
return t
|
|
||||||
end
|
|
||||||
|
|
||||||
function table.filter(t, func)
|
|
||||||
|
|
||||||
for i = #t, 1, -1 do
|
|
||||||
if not func(i, t[i]) then
|
|
||||||
table.remove(t, i)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local size = #t
|
|
||||||
|
|
||||||
for i, v in pairs(t) do
|
|
||||||
local i_type = type(i)
|
|
||||||
if i_type == "number" then
|
|
||||||
if i < 1 or i > size then
|
|
||||||
if not func(i, v) then
|
|
||||||
t[i] = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
else
|
|
||||||
if not func(i, v) then
|
|
||||||
t[i] = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return t
|
|
||||||
end
|
|
||||||
|
|
||||||
function table.set_default(t, key, default)
|
|
||||||
if t[key] == nil then
|
|
||||||
t[key] = default
|
|
||||||
return default
|
|
||||||
end
|
|
||||||
|
|
||||||
return t[key]
|
|
||||||
end
|
|
||||||
|
|
||||||
function table.flat(t)
|
|
||||||
local flat = {}
|
|
||||||
|
|
||||||
for _, v in pairs(t) do
|
|
||||||
if type(v) == "table" then
|
|
||||||
table.merge(flat, v)
|
|
||||||
else
|
|
||||||
table.insert(flat, v)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return flat
|
|
||||||
end
|
|
||||||
|
|
||||||
function table.deep_flat(t)
|
|
||||||
local flat = {}
|
|
||||||
|
|
||||||
for _, v in pairs(t) do
|
|
||||||
if type(v) == "table" then
|
|
||||||
table.merge(flat, table.deep_flat(v))
|
|
||||||
else
|
|
||||||
table.insert(flat, v)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return flat
|
|
||||||
end
|
|
||||||
|
|
||||||
function table.sub(arr, start, stop)
|
|
||||||
local res = {}
|
|
||||||
start = start or 1
|
|
||||||
stop = stop or #arr
|
|
||||||
|
|
||||||
for i = start, stop do
|
|
||||||
table.insert(res, arr[i])
|
|
||||||
end
|
|
||||||
|
|
||||||
return res
|
|
||||||
end
|
|
||||||
|
|
||||||
----------------------------------------------
|
|
||||||
|
|
||||||
local pattern_escape_replacements = {
|
|
||||||
["("] = "%(",
|
|
||||||
[")"] = "%)",
|
|
||||||
["."] = "%.",
|
|
||||||
["%"] = "%%",
|
|
||||||
["+"] = "%+",
|
|
||||||
["-"] = "%-",
|
|
||||||
["*"] = "%*",
|
|
||||||
["?"] = "%?",
|
|
||||||
["["] = "%[",
|
|
||||||
["]"] = "%]",
|
|
||||||
["^"] = "%^",
|
|
||||||
["$"] = "%$",
|
|
||||||
["\0"] = "%z"
|
|
||||||
}
|
|
||||||
|
|
||||||
function string.pattern_safe(str)
|
|
||||||
return string.gsub(str, ".", pattern_escape_replacements)
|
|
||||||
end
|
|
||||||
|
|
||||||
local string_sub = string.sub
|
|
||||||
local string_find = string.find
|
|
||||||
local string_len = string.len
|
|
||||||
function string.explode(separator, str, withpattern)
|
|
||||||
if (withpattern == nil) then withpattern = false end
|
|
||||||
|
|
||||||
local ret = {}
|
|
||||||
local current_pos = 1
|
|
||||||
|
|
||||||
for i = 1, string_len(str) do
|
|
||||||
local start_pos, end_pos = string_find(
|
|
||||||
str, separator, current_pos, not withpattern)
|
|
||||||
if (not start_pos) then break end
|
|
||||||
ret[i] = string_sub(str, current_pos, start_pos - 1)
|
|
||||||
current_pos = end_pos + 1
|
|
||||||
end
|
|
||||||
|
|
||||||
ret[#ret + 1] = string_sub(str, current_pos)
|
|
||||||
|
|
||||||
return ret
|
|
||||||
end
|
|
||||||
|
|
||||||
function string.split(str, delimiter)
|
|
||||||
return string.explode(delimiter, str)
|
|
||||||
end
|
|
||||||
|
|
||||||
function string.formatted_time(seconds, format)
|
|
||||||
if (not seconds) then seconds = 0 end
|
|
||||||
local hours = math.floor(seconds / 3600)
|
|
||||||
local minutes = math.floor((seconds / 60) % 60)
|
|
||||||
local millisecs = (seconds - math.floor(seconds)) * 1000
|
|
||||||
seconds = math.floor(seconds % 60)
|
|
||||||
|
|
||||||
if (format) then
|
|
||||||
return string.format(format, minutes, seconds, millisecs)
|
|
||||||
else
|
|
||||||
return { h = hours, m = minutes, s = seconds, ms = millisecs }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function string.replace(str, tofind, toreplace)
|
|
||||||
local tbl = string.explode(tofind, str)
|
|
||||||
if (tbl[1]) then return table.concat(tbl, toreplace) end
|
|
||||||
return str
|
|
||||||
end
|
|
||||||
|
|
||||||
function string.trim(s, char)
|
|
||||||
if char then char = string.pattern_safe(char) else char = "%s" end
|
|
||||||
return string.match(s, "^" .. char .. "*(.-)" .. char .. "*$") or s
|
|
||||||
end
|
|
||||||
|
|
||||||
function string.trim_right(s, char)
|
|
||||||
if char then char = string.pattern_safe(char) else char = "%s" end
|
|
||||||
return string.match(s, "^(.-)" .. char .. "*$") or s
|
|
||||||
end
|
|
||||||
|
|
||||||
function string.trim_left(s, char)
|
|
||||||
if char then char = string.pattern_safe(char) else char = "%s" end
|
|
||||||
return string.match(s, "^" .. char .. "*(.+)$") or s
|
|
||||||
end
|
|
||||||
|
|
||||||
function string.pad(str, size, char)
|
|
||||||
char = char == nil and " " or char
|
|
||||||
|
|
||||||
local padding = math.floor((size - #str) / 2)
|
|
||||||
local extra_padding = (size - #str) % 2
|
|
||||||
|
|
||||||
return string.rep(char, padding) .. str .. string.rep(char, padding + extra_padding)
|
|
||||||
end
|
|
||||||
|
|
||||||
function string.left_pad(str, size, char)
|
|
||||||
char = char == nil and " " or char
|
|
||||||
|
|
||||||
local left_padding = size - #str
|
|
||||||
return string.rep(char, left_padding) .. str
|
|
||||||
end
|
|
||||||
|
|
||||||
function string.right_pad(str, size, char)
|
|
||||||
char = char == nil and " " or char
|
|
||||||
|
|
||||||
local right_padding = size - #str
|
|
||||||
return str .. string.rep(char, right_padding)
|
|
||||||
end
|
|
||||||
|
|
||||||
string.lower = utf8.lower
|
|
||||||
string.upper = utf8.upper
|
|
||||||
string.escape = utf8.escape
|
|
||||||
|
|
||||||
local meta = getmetatable("")
|
|
||||||
|
|
||||||
function meta:__index(key)
|
|
||||||
local val = string[key]
|
|
||||||
if (val ~= nil) then
|
|
||||||
return val
|
|
||||||
elseif (tonumber(key)) then
|
|
||||||
return string.sub(self, key, key)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function string.starts_with(str, start)
|
|
||||||
return string.sub(str, 1, string.len(start)) == start
|
|
||||||
end
|
|
||||||
|
|
||||||
function string.ends_with(str, endStr)
|
|
||||||
return endStr == "" or string.sub(str, -string.len(endStr)) == endStr
|
|
||||||
end
|
|
||||||
|
|
||||||
function table.has(t, x)
|
|
||||||
for i,v in ipairs(t) do
|
|
||||||
if v == x then
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
function table.index(t, x)
|
|
||||||
for i,v in ipairs(t) do
|
|
||||||
if v == x then
|
|
||||||
return i
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return -1
|
|
||||||
end
|
|
||||||
|
|
||||||
function table.remove_value(t, x)
|
|
||||||
local index = table.index(t, x)
|
|
||||||
if index ~= -1 then
|
|
||||||
table.remove(t, index)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function table.tostring(t)
|
|
||||||
local s = '['
|
|
||||||
for i,v in ipairs(t) do
|
|
||||||
s = s..tostring(v)
|
|
||||||
if i < #t then
|
|
||||||
s = s..', '
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return s..']'
|
|
||||||
end
|
|
||||||
|
|
||||||
function file.readlines(path)
|
|
||||||
local str = file.read(path)
|
|
||||||
local lines = {}
|
|
||||||
for s in str:gmatch("[^\r\n]+") do
|
|
||||||
table.insert(lines, s)
|
|
||||||
end
|
|
||||||
return lines
|
|
||||||
end
|
|
||||||
|
|
||||||
function debug.count_frames()
|
function debug.count_frames()
|
||||||
local frames = 1
|
local frames = 1
|
||||||
while true do
|
while true do
|
||||||
|
|
@ -697,7 +185,7 @@ local internal_locked = false
|
||||||
-- Example `base:scripts/tests.lua`
|
-- Example `base:scripts/tests.lua`
|
||||||
--
|
--
|
||||||
-- nocache - ignore cached script, load anyway
|
-- nocache - ignore cached script, load anyway
|
||||||
local function __load_script(path, nocache, env)
|
function __load_script(path, nocache, env)
|
||||||
local packname, filename = parse_path(path)
|
local packname, filename = parse_path(path)
|
||||||
|
|
||||||
if internal_locked and (packname == "res" or packname == "core")
|
if internal_locked and (packname == "res" or packname == "core")
|
||||||
|
|
@ -742,15 +230,25 @@ function require(path)
|
||||||
return __load_script(prefix .. ":modules/" .. file .. ".lua", nil, env)
|
return __load_script(prefix .. ":modules/" .. file .. ".lua", nil, env)
|
||||||
end
|
end
|
||||||
|
|
||||||
function __scripts_cleanup()
|
function __scripts_cleanup(non_reset_packs)
|
||||||
debug.log("cleaning scripts cache")
|
debug.log("cleaning scripts cache")
|
||||||
|
if #non_reset_packs == 0 then
|
||||||
|
debug.log("no non-reset packs")
|
||||||
|
else
|
||||||
|
debug.log("non-reset packs: "..table.concat(non_reset_packs, ", "))
|
||||||
|
end
|
||||||
for k, v in pairs(__cached_scripts) do
|
for k, v in pairs(__cached_scripts) do
|
||||||
local packname, _ = parse_path(k)
|
local packname, _ = parse_path(k)
|
||||||
|
if table.has(non_reset_packs, packname) then
|
||||||
|
goto continue
|
||||||
|
end
|
||||||
if packname ~= "core" then
|
if packname ~= "core" then
|
||||||
debug.log("unloaded "..k)
|
debug.log("unloaded "..k)
|
||||||
__cached_scripts[k] = nil
|
__cached_scripts[k] = nil
|
||||||
package.loaded[k] = nil
|
package.loaded[k] = nil
|
||||||
end
|
end
|
||||||
|
__vc__pack_envs[packname] = nil
|
||||||
|
::continue::
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -758,7 +256,7 @@ function __vc__error(msg, frame, n, lastn)
|
||||||
if events then
|
if events then
|
||||||
local frames = debug.get_traceback(1)
|
local frames = debug.get_traceback(1)
|
||||||
events.emit(
|
events.emit(
|
||||||
"core:error", msg,
|
"core:error", msg,
|
||||||
table.sub(frames, 1 + (n or 0), lastn and #frames-lastn)
|
table.sub(frames, 1 + (n or 0), lastn and #frames-lastn)
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
@ -772,42 +270,20 @@ function __vc_warning(msg, detail, n)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function file.name(path)
|
require "core:internal/extensions/pack"
|
||||||
return path:match("([^:/\\]+)$")
|
require "core:internal/extensions/math"
|
||||||
end
|
require "core:internal/extensions/file"
|
||||||
|
require "core:internal/extensions/table"
|
||||||
|
require "core:internal/extensions/string"
|
||||||
|
|
||||||
function file.stem(path)
|
local bytearray = require "core:internal/bytearray"
|
||||||
local name = file.name(path)
|
Bytearray = bytearray.FFIBytearray
|
||||||
return name:match("(.+)%.[^%.]+$") or name
|
Bytearray_as_string = bytearray.FFIBytearray_as_string
|
||||||
end
|
U16view = bytearray.FFIU16view
|
||||||
|
I16view = bytearray.FFII16view
|
||||||
function file.ext(path)
|
U32view = bytearray.FFIU32view
|
||||||
return path:match("%.([^:/\\]+)$")
|
I32view = bytearray.FFII32view
|
||||||
end
|
Bytearray_construct = function(...) return Bytearray(...) end
|
||||||
|
|
||||||
function file.prefix(path)
|
|
||||||
return path:match("^([^:]+)")
|
|
||||||
end
|
|
||||||
|
|
||||||
function file.parent(path)
|
|
||||||
local dir = path:match("(.*)/")
|
|
||||||
if not dir then
|
|
||||||
return file.prefix(path)..":"
|
|
||||||
end
|
|
||||||
return dir
|
|
||||||
end
|
|
||||||
|
|
||||||
function file.path(path)
|
|
||||||
local pos = path:find(':')
|
|
||||||
return path:sub(pos + 1)
|
|
||||||
end
|
|
||||||
|
|
||||||
function file.join(a, b)
|
|
||||||
if a[#a] == ':' then
|
|
||||||
return a .. b
|
|
||||||
end
|
|
||||||
return a .. "/" .. b
|
|
||||||
end
|
|
||||||
|
|
||||||
bit.compile = require "core:bitwise/compiler"
|
bit.compile = require "core:bitwise/compiler"
|
||||||
bit.execute = require "core:bitwise/executor"
|
bit.execute = require "core:bitwise/executor"
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ devtools.output=Output
|
||||||
graphics.gamma.tooltip=Lighting brightness curve
|
graphics.gamma.tooltip=Lighting brightness curve
|
||||||
graphics.backlight.tooltip=Backlight to prevent total darkness
|
graphics.backlight.tooltip=Backlight to prevent total darkness
|
||||||
graphics.dense-render.tooltip=Enables transparency in blocks like leaves
|
graphics.dense-render.tooltip=Enables transparency in blocks like leaves
|
||||||
|
graphics.soft-lighting.tooltip=Enables blocks soft lighting
|
||||||
|
|
||||||
# settings
|
# settings
|
||||||
settings.Controls Search Mode=Search by attached button name
|
settings.Controls Search Mode=Search by attached button name
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,8 @@ Grant %{0} pack modification permission?=Выдать разрешение на
|
||||||
Error at line %{0}=Ошибка на строке %{0}
|
Error at line %{0}=Ошибка на строке %{0}
|
||||||
Run=Запустить
|
Run=Запустить
|
||||||
Filter=Фильтр
|
Filter=Фильтр
|
||||||
Are you sure you want to open the link: =Вы уверены, что хотите открыть ссылку:
|
Are you sure you want to open the link: =Вы уверены, что хотите открыть ссылку:
|
||||||
|
Grant '%{0}' pack audio recording permission?=Выдать паку '%{0}' разрешение на запись звука?
|
||||||
|
|
||||||
editor.info.tooltip=CTRL+S - Сохранить\nCTRL+R - Запустить\nCTRL+Z - Отменить\nCTRL+Y - Повторить
|
editor.info.tooltip=CTRL+S - Сохранить\nCTRL+R - Запустить\nCTRL+Z - Отменить\nCTRL+Y - Повторить
|
||||||
devtools.traceback=Стек вызовов (от последнего)
|
devtools.traceback=Стек вызовов (от последнего)
|
||||||
|
|
@ -39,7 +40,8 @@ pack.remove-confirm=Удалить весь поставляемый паком/
|
||||||
# Подсказки
|
# Подсказки
|
||||||
graphics.gamma.tooltip=Кривая яркости освещения
|
graphics.gamma.tooltip=Кривая яркости освещения
|
||||||
graphics.backlight.tooltip=Подсветка, предотвращающая полную темноту
|
graphics.backlight.tooltip=Подсветка, предотвращающая полную темноту
|
||||||
graphics.dense-render.tooltip=Включает прозрачность блоков, таких как листья.
|
graphics.dense-render.tooltip=Включает прозрачность блоков, таких как листья
|
||||||
|
graphics.soft-lighting.tooltip=Включает мягкое освещение у блоков
|
||||||
|
|
||||||
# Меню
|
# Меню
|
||||||
menu.Apply=Применить
|
menu.Apply=Применить
|
||||||
|
|
@ -80,6 +82,7 @@ world.delete-confirm=Удалить мир безвозвратно?
|
||||||
settings.Ambient=Фон
|
settings.Ambient=Фон
|
||||||
settings.Backlight=Подсветка
|
settings.Backlight=Подсветка
|
||||||
settings.Dense blocks render=Плотный рендер блоков
|
settings.Dense blocks render=Плотный рендер блоков
|
||||||
|
settings.Soft lighting=Мягкое освещение
|
||||||
settings.Camera Shaking=Тряска Камеры
|
settings.Camera Shaking=Тряска Камеры
|
||||||
settings.Camera Inertia=Инерция Камеры
|
settings.Camera Inertia=Инерция Камеры
|
||||||
settings.Camera FOV Effects=Эффекты поля зрения
|
settings.Camera FOV Effects=Эффекты поля зрения
|
||||||
|
|
@ -105,6 +108,7 @@ settings.Shadows quality=Качество теней
|
||||||
settings.Conflict=Найдены возможные конфликты
|
settings.Conflict=Найдены возможные конфликты
|
||||||
settings.Windowed=Оконный
|
settings.Windowed=Оконный
|
||||||
settings.Borderless=Безрамочный
|
settings.Borderless=Безрамочный
|
||||||
|
settings.Microphone=Микрофон
|
||||||
|
|
||||||
# Управление
|
# Управление
|
||||||
chunks.reload=Перезагрузить Чанки
|
chunks.reload=Перезагрузить Чанки
|
||||||
|
|
|
||||||
|
|
@ -112,7 +112,7 @@ target_compile_options(
|
||||||
-Wno-sign-compare
|
-Wno-sign-compare
|
||||||
-Wno-unknown-pragmas
|
-Wno-unknown-pragmas
|
||||||
>
|
>
|
||||||
$<$<CXX_COMPILER_ID:Clang>:
|
$<$<CXX_COMPILER_ID:GNU>:
|
||||||
-Wduplicated-branches
|
-Wduplicated-branches
|
||||||
-Wduplicated-cond
|
-Wduplicated-cond
|
||||||
>)
|
>)
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
#include "content/Content.hpp"
|
#include "content/Content.hpp"
|
||||||
#include "content/ContentPack.hpp"
|
#include "content/ContentPack.hpp"
|
||||||
#include "debug/Logger.hpp"
|
#include "debug/Logger.hpp"
|
||||||
#include "io/engine_paths.hpp"
|
#include "engine/EnginePaths.hpp"
|
||||||
#include "io/io.hpp"
|
#include "io/io.hpp"
|
||||||
#include "graphics/core/Texture.hpp"
|
#include "graphics/core/Texture.hpp"
|
||||||
#include "logic/scripting/scripting.hpp"
|
#include "logic/scripting/scripting.hpp"
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@
|
||||||
#include "coders/vec3.hpp"
|
#include "coders/vec3.hpp"
|
||||||
#include "constants.hpp"
|
#include "constants.hpp"
|
||||||
#include "debug/Logger.hpp"
|
#include "debug/Logger.hpp"
|
||||||
#include "io/engine_paths.hpp"
|
#include "engine/EnginePaths.hpp"
|
||||||
#include "io/io.hpp"
|
#include "io/io.hpp"
|
||||||
#include "frontend/UiDocument.hpp"
|
#include "frontend/UiDocument.hpp"
|
||||||
#include "graphics/core/Atlas.hpp"
|
#include "graphics/core/Atlas.hpp"
|
||||||
|
|
@ -184,7 +184,7 @@ assetload::postfunc assetload::atlas(
|
||||||
if (!append_atlas(builder, file)) continue;
|
if (!append_atlas(builder, file)) continue;
|
||||||
}
|
}
|
||||||
std::set<std::string> names = builder.getNames();
|
std::set<std::string> names = builder.getNames();
|
||||||
Atlas* atlas = builder.build(2, false).release();
|
Atlas* atlas = builder.build(ATLAS_EXTRUSION, false).release();
|
||||||
return [=](auto assets) {
|
return [=](auto assets) {
|
||||||
atlas->prepare();
|
atlas->prepare();
|
||||||
assets->store(std::unique_ptr<Atlas>(atlas), name);
|
assets->store(std::unique_ptr<Atlas>(atlas), name);
|
||||||
|
|
@ -501,7 +501,7 @@ static bool load_animation(
|
||||||
}
|
}
|
||||||
if (!append_atlas(builder, file)) continue;
|
if (!append_atlas(builder, file)) continue;
|
||||||
}
|
}
|
||||||
auto srcAtlas = builder.build(2, true);
|
auto srcAtlas = builder.build(ATLAS_EXTRUSION, true);
|
||||||
if (frameList.empty()) {
|
if (frameList.empty()) {
|
||||||
for (const auto& frameName : builder.getNames()) {
|
for (const auto& frameName : builder.getNames()) {
|
||||||
frameList.emplace_back(frameName, 0);
|
frameList.emplace_back(frameName, 0);
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,41 @@
|
||||||
|
|
||||||
#include "debug/Logger.hpp"
|
#include "debug/Logger.hpp"
|
||||||
#include "alutil.hpp"
|
#include "alutil.hpp"
|
||||||
|
#include "../MemoryPCMStream.hpp"
|
||||||
|
|
||||||
static debug::Logger logger("al-audio");
|
static debug::Logger logger("al-audio");
|
||||||
|
|
||||||
using namespace audio;
|
using namespace audio;
|
||||||
|
|
||||||
|
const char* alc_error_to_string(ALCenum error) {
|
||||||
|
switch (error) {
|
||||||
|
case ALC_NO_ERROR:
|
||||||
|
return "no error";
|
||||||
|
case ALC_INVALID_DEVICE:
|
||||||
|
return "invalid device handle";
|
||||||
|
case ALC_INVALID_CONTEXT:
|
||||||
|
return "invalid context handle";
|
||||||
|
case ALC_INVALID_ENUM:
|
||||||
|
return "invalid enum parameter passed to an ALC call";
|
||||||
|
case ALC_INVALID_VALUE:
|
||||||
|
return "invalid value parameter passed to an ALC call";
|
||||||
|
case ALC_OUT_OF_MEMORY:
|
||||||
|
return "out of memory";
|
||||||
|
default:
|
||||||
|
return "unknown ALC error";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool check_alc_errors(ALCdevice* device, const char* context) {
|
||||||
|
ALCenum error = alcGetError(device);
|
||||||
|
if (error == ALC_NO_ERROR) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
logger.error() << context << ": " << alc_error_to_string(error) << "("
|
||||||
|
<< error << ")";
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
ALSound::ALSound(
|
ALSound::ALSound(
|
||||||
ALAudio* al, uint buffer, const std::shared_ptr<PCM>& pcm, bool keepPCM
|
ALAudio* al, uint buffer, const std::shared_ptr<PCM>& pcm, bool keepPCM
|
||||||
)
|
)
|
||||||
|
|
@ -37,6 +67,70 @@ std::unique_ptr<Speaker> ALSound::newInstance(int priority, int channel) const {
|
||||||
return speaker;
|
return speaker;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ALInputDevice::ALInputDevice(
|
||||||
|
ALAudio* al,
|
||||||
|
ALCdevice* device,
|
||||||
|
uint channels,
|
||||||
|
uint bitsPerSample,
|
||||||
|
uint sampleRate
|
||||||
|
)
|
||||||
|
: al(al),
|
||||||
|
device(device),
|
||||||
|
channels(channels),
|
||||||
|
bitsPerSample(bitsPerSample),
|
||||||
|
sampleRate(sampleRate) {
|
||||||
|
const ALCchar* deviceName = alcGetString(device, ALC_CAPTURE_DEVICE_SPECIFIER);
|
||||||
|
|
||||||
|
if (deviceName) {
|
||||||
|
deviceSpecifier = std::string(deviceName);
|
||||||
|
} else {
|
||||||
|
logger.warning() << "could not retrieve input device specifier";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ALInputDevice::~ALInputDevice() {
|
||||||
|
alcCaptureCloseDevice(device);
|
||||||
|
check_alc_errors(device, "alcCaptureCloseDevice");
|
||||||
|
}
|
||||||
|
|
||||||
|
void ALInputDevice::startCapture() {
|
||||||
|
alcCaptureStart(device);
|
||||||
|
check_alc_errors(device, "alcCaptureStart");
|
||||||
|
}
|
||||||
|
|
||||||
|
void ALInputDevice::stopCapture() {
|
||||||
|
alcCaptureStop(device);
|
||||||
|
check_alc_errors(device, "alcCaptureStop");
|
||||||
|
}
|
||||||
|
|
||||||
|
uint ALInputDevice::getChannels() const {
|
||||||
|
return channels;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint ALInputDevice::getSampleRate() const {
|
||||||
|
return sampleRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint ALInputDevice::getBitsPerSample() const {
|
||||||
|
return bitsPerSample;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string& ALInputDevice::getDeviceSpecifier() const {
|
||||||
|
return deviceSpecifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t ALInputDevice::read(char* buffer, size_t bufferSize) {
|
||||||
|
ALCint samplesCount = 0;
|
||||||
|
alcGetIntegerv(device, ALC_CAPTURE_SAMPLES, sizeof(samplesCount), &samplesCount);
|
||||||
|
check_alc_errors(device, "alcGetIntegerv(ALC_CAPTURE_SAMPLES)");
|
||||||
|
size_t samplesRead = std::min<ALCsizei>(
|
||||||
|
samplesCount, bufferSize / channels / (bitsPerSample >> 3)
|
||||||
|
);
|
||||||
|
alcCaptureSamples(device, buffer, samplesRead);
|
||||||
|
check_alc_errors(device, "alcCaptureSamples");
|
||||||
|
return samplesRead * channels * (bitsPerSample >> 3);
|
||||||
|
}
|
||||||
|
|
||||||
ALStream::ALStream(
|
ALStream::ALStream(
|
||||||
ALAudio* al, std::shared_ptr<PCMStream> source, bool keepSource
|
ALAudio* al, std::shared_ptr<PCMStream> source, bool keepSource
|
||||||
)
|
)
|
||||||
|
|
@ -81,9 +175,10 @@ std::unique_ptr<Speaker> ALStream::createSpeaker(bool loop, int channel) {
|
||||||
for (uint i = 0; i < ALStream::STREAM_BUFFERS; i++) {
|
for (uint i = 0; i < ALStream::STREAM_BUFFERS; i++) {
|
||||||
uint free_buffer = al->getFreeBuffer();
|
uint free_buffer = al->getFreeBuffer();
|
||||||
if (!preloadBuffer(free_buffer, loop)) {
|
if (!preloadBuffer(free_buffer, loop)) {
|
||||||
break;
|
unusedBuffers.push(free_buffer);
|
||||||
|
} else {
|
||||||
|
AL_CHECK(alSourceQueueBuffers(free_source, 1, &free_buffer));
|
||||||
}
|
}
|
||||||
AL_CHECK(alSourceQueueBuffers(free_source, 1, &free_buffer));
|
|
||||||
}
|
}
|
||||||
return std::make_unique<ALSpeaker>(al, free_source, PRIORITY_HIGH, channel);
|
return std::make_unique<ALSpeaker>(al, free_source, PRIORITY_HIGH, channel);
|
||||||
}
|
}
|
||||||
|
|
@ -130,11 +225,11 @@ void ALStream::unqueueBuffers(uint alsource) {
|
||||||
uint ALStream::enqueueBuffers(uint alsource) {
|
uint ALStream::enqueueBuffers(uint alsource) {
|
||||||
uint preloaded = 0;
|
uint preloaded = 0;
|
||||||
if (!unusedBuffers.empty()) {
|
if (!unusedBuffers.empty()) {
|
||||||
uint first_buffer = unusedBuffers.front();
|
uint firstBuffer = unusedBuffers.front();
|
||||||
if (preloadBuffer(first_buffer, loop)) {
|
if (preloadBuffer(firstBuffer, loop)) {
|
||||||
preloaded++;
|
preloaded++;
|
||||||
unusedBuffers.pop();
|
unusedBuffers.pop();
|
||||||
AL_CHECK(alSourceQueueBuffers(alsource, 1, &first_buffer));
|
AL_CHECK(alSourceQueueBuffers(alsource, 1, &firstBuffer));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return preloaded;
|
return preloaded;
|
||||||
|
|
@ -144,14 +239,14 @@ void ALStream::update(double delta) {
|
||||||
if (this->speaker == 0) {
|
if (this->speaker == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
auto p_speaker = audio::get_speaker(this->speaker);
|
auto speaker = audio::get_speaker(this->speaker);
|
||||||
if (p_speaker == nullptr) {
|
if (speaker == nullptr) {
|
||||||
this->speaker = 0;
|
this->speaker = 0;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ALSpeaker* alspeaker = dynamic_cast<ALSpeaker*>(p_speaker);
|
ALSpeaker* alspeaker = dynamic_cast<ALSpeaker*>(speaker);
|
||||||
assert(alspeaker != nullptr);
|
assert(alspeaker != nullptr);
|
||||||
if (alspeaker->stopped) {
|
if (alspeaker->manuallyStopped) {
|
||||||
this->speaker = 0;
|
this->speaker = 0;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -162,11 +257,11 @@ void ALStream::update(double delta) {
|
||||||
uint preloaded = enqueueBuffers(alsource);
|
uint preloaded = enqueueBuffers(alsource);
|
||||||
|
|
||||||
// alspeaker->stopped is assigned to false at ALSpeaker::play(...)
|
// alspeaker->stopped is assigned to false at ALSpeaker::play(...)
|
||||||
if (p_speaker->isStopped() && !alspeaker->stopped) { //TODO: -V560 false-positive?
|
if (speaker->isStopped() && !alspeaker->manuallyStopped) { //TODO: -V560 false-positive?
|
||||||
if (preloaded) {
|
if (preloaded) {
|
||||||
p_speaker->play();
|
speaker->play();
|
||||||
} else {
|
} else if (isStopOnEnd()){
|
||||||
p_speaker->stop();
|
speaker->stop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -207,6 +302,14 @@ void ALStream::setTime(duration_t time) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ALStream::isStopOnEnd() const {
|
||||||
|
return stopOnEnd;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ALStream::setStopOnEnd(bool flag) {
|
||||||
|
stopOnEnd = flag;
|
||||||
|
}
|
||||||
|
|
||||||
ALSpeaker::ALSpeaker(ALAudio* al, uint source, int priority, int channel)
|
ALSpeaker::ALSpeaker(ALAudio* al, uint source, int priority, int channel)
|
||||||
: al(al), priority(priority), channel(channel), source(source) {
|
: al(al), priority(priority), channel(channel), source(source) {
|
||||||
}
|
}
|
||||||
|
|
@ -273,7 +376,7 @@ void ALSpeaker::setLoop(bool loop) {
|
||||||
|
|
||||||
void ALSpeaker::play() {
|
void ALSpeaker::play() {
|
||||||
paused = false;
|
paused = false;
|
||||||
stopped = false;
|
manuallyStopped = false;
|
||||||
auto p_channel = get_channel(this->channel);
|
auto p_channel = get_channel(this->channel);
|
||||||
AL_CHECK(alSourcef(
|
AL_CHECK(alSourcef(
|
||||||
source,
|
source,
|
||||||
|
|
@ -289,7 +392,7 @@ void ALSpeaker::pause() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void ALSpeaker::stop() {
|
void ALSpeaker::stop() {
|
||||||
stopped = true;
|
manuallyStopped = true;
|
||||||
if (source) {
|
if (source) {
|
||||||
AL_CHECK(alSourceStop(source));
|
AL_CHECK(alSourceStop(source));
|
||||||
|
|
||||||
|
|
@ -353,6 +456,13 @@ int ALSpeaker::getPriority() const {
|
||||||
return priority;
|
return priority;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool ALSpeaker::isManuallyStopped() const {
|
||||||
|
return manuallyStopped;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool alc_enumeration_ext = false;
|
||||||
|
|
||||||
ALAudio::ALAudio(ALCdevice* device, ALCcontext* context)
|
ALAudio::ALAudio(ALCdevice* device, ALCcontext* context)
|
||||||
: device(device), context(context) {
|
: device(device), context(context) {
|
||||||
ALCint size;
|
ALCint size;
|
||||||
|
|
@ -365,9 +475,15 @@ ALAudio::ALAudio(ALCdevice* device, ALCcontext* context)
|
||||||
maxSources = attrs[i + 1];
|
maxSources = attrs[i + 1];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
auto devices = getAvailableDevices();
|
auto outputDevices = getOutputDeviceNames();
|
||||||
logger.info() << "devices:";
|
logger.info() << "output devices:";
|
||||||
for (auto& name : devices) {
|
for (auto& name : outputDevices) {
|
||||||
|
logger.info() << " " << name;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto inputDevices = getInputDeviceNames();
|
||||||
|
logger.info() << "input devices:";
|
||||||
|
for (auto& name : inputDevices) {
|
||||||
logger.info() << " " << name;
|
logger.info() << " " << name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -385,8 +501,10 @@ ALAudio::~ALAudio() {
|
||||||
AL_CHECK(alDeleteBuffers(1, &buffer));
|
AL_CHECK(alDeleteBuffers(1, &buffer));
|
||||||
}
|
}
|
||||||
|
|
||||||
AL_CHECK(alcMakeContextCurrent(context));
|
alcMakeContextCurrent(nullptr);
|
||||||
|
check_alc_errors(device, "alcMakeContextCurrent");
|
||||||
alcDestroyContext(context);
|
alcDestroyContext(context);
|
||||||
|
check_alc_errors(device, "alcDestroyContext");
|
||||||
if (!alcCloseDevice(device)) {
|
if (!alcCloseDevice(device)) {
|
||||||
logger.error() << "device not closed!";
|
logger.error() << "device not closed!";
|
||||||
}
|
}
|
||||||
|
|
@ -411,7 +529,71 @@ std::unique_ptr<Stream> ALAudio::openStream(
|
||||||
return std::make_unique<ALStream>(this, stream, keepSource);
|
return std::make_unique<ALStream>(this, stream, keepSource);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> ALAudio::getInputDeviceNames() {
|
||||||
|
std::vector<std::string> devices;
|
||||||
|
|
||||||
|
if (!alc_enumeration_ext) {
|
||||||
|
logger.warning() << "enumeration extension is not available";
|
||||||
|
return devices;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto deviceList = alcGetString(nullptr, ALC_CAPTURE_DEVICE_SPECIFIER);
|
||||||
|
if (deviceList == nullptr) {
|
||||||
|
logger.warning() << "no input devices found";
|
||||||
|
return devices;
|
||||||
|
}
|
||||||
|
while (*deviceList) {
|
||||||
|
std::string deviceName(deviceList);
|
||||||
|
devices.push_back(deviceName);
|
||||||
|
deviceList += deviceName.length() + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return devices;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> ALAudio::getOutputDeviceNames() {
|
||||||
|
std::vector<std::string> devices;
|
||||||
|
|
||||||
|
if (!alc_enumeration_ext) {
|
||||||
|
logger.warning() << "enumeration extension is not available";
|
||||||
|
return devices;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto deviceList = alcGetString(nullptr, ALC_ALL_DEVICES_SPECIFIER);
|
||||||
|
if (deviceList == nullptr) {
|
||||||
|
logger.warning() << "no input devices found";
|
||||||
|
return devices;
|
||||||
|
}
|
||||||
|
while (*deviceList) {
|
||||||
|
std::string deviceName(deviceList);
|
||||||
|
devices.push_back(deviceName);
|
||||||
|
deviceList += deviceName.length() + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return devices;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<InputDevice> ALAudio::openInputDevice(
|
||||||
|
const std::string& deviceName, uint sampleRate, uint channels, uint bitsPerSample
|
||||||
|
) {
|
||||||
|
uint bps = bitsPerSample >> 3;
|
||||||
|
ALCdevice* device = alcCaptureOpenDevice(
|
||||||
|
deviceName.empty() ? nullptr : deviceName.c_str(),
|
||||||
|
sampleRate,
|
||||||
|
AL::to_al_format(channels, bitsPerSample),
|
||||||
|
sampleRate * channels * bps / 8
|
||||||
|
);
|
||||||
|
if (check_alc_errors(device, "alcCaptureOpenDevice"))
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
return std::make_unique<ALInputDevice>(
|
||||||
|
this, device, channels, bitsPerSample, sampleRate
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
std::unique_ptr<ALAudio> ALAudio::create() {
|
std::unique_ptr<ALAudio> ALAudio::create() {
|
||||||
|
alc_enumeration_ext = alcIsExtensionPresent(nullptr, "ALC_ENUMERATION_EXT");
|
||||||
|
|
||||||
ALCdevice* device = alcOpenDevice(nullptr);
|
ALCdevice* device = alcOpenDevice(nullptr);
|
||||||
if (device == nullptr) return nullptr;
|
if (device == nullptr) return nullptr;
|
||||||
ALCcontext* context = alcCreateContext(device, nullptr);
|
ALCcontext* context = alcCreateContext(device, nullptr);
|
||||||
|
|
@ -468,24 +650,6 @@ void ALAudio::freeBuffer(uint buffer) {
|
||||||
freebuffers.push_back(buffer);
|
freebuffers.push_back(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<std::string> ALAudio::getAvailableDevices() const {
|
|
||||||
std::vector<std::string> devicesVec;
|
|
||||||
|
|
||||||
const ALCchar* devices;
|
|
||||||
devices = alcGetString(device, ALC_DEVICE_SPECIFIER);
|
|
||||||
if (!AL_GET_ERROR()) {
|
|
||||||
return devicesVec;
|
|
||||||
}
|
|
||||||
|
|
||||||
const char* ptr = devices;
|
|
||||||
do {
|
|
||||||
devicesVec.emplace_back(ptr);
|
|
||||||
ptr += devicesVec.back().size() + 1;
|
|
||||||
} while (ptr[0]);
|
|
||||||
|
|
||||||
return devicesVec;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ALAudio::setListener(
|
void ALAudio::setListener(
|
||||||
glm::vec3 position, glm::vec3 velocity, glm::vec3 at, glm::vec3 up
|
glm::vec3 position, glm::vec3 velocity, glm::vec3 at, glm::vec3 up
|
||||||
) {
|
) {
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,7 @@ namespace audio {
|
||||||
bool keepSource;
|
bool keepSource;
|
||||||
char buffer[BUFFER_SIZE];
|
char buffer[BUFFER_SIZE];
|
||||||
bool loop = false;
|
bool loop = false;
|
||||||
|
bool stopOnEnd = false;
|
||||||
|
|
||||||
bool preloadBuffer(uint buffer, bool loop);
|
bool preloadBuffer(uint buffer, bool loop);
|
||||||
void unqueueBuffers(uint alsource);
|
void unqueueBuffers(uint alsource);
|
||||||
|
|
@ -80,6 +81,39 @@ namespace audio {
|
||||||
void setTime(duration_t time) override;
|
void setTime(duration_t time) override;
|
||||||
|
|
||||||
static inline constexpr uint STREAM_BUFFERS = 3;
|
static inline constexpr uint STREAM_BUFFERS = 3;
|
||||||
|
|
||||||
|
bool isStopOnEnd() const override;
|
||||||
|
|
||||||
|
void setStopOnEnd(bool stopOnEnd) override;
|
||||||
|
};
|
||||||
|
|
||||||
|
class ALInputDevice : public InputDevice {
|
||||||
|
public:
|
||||||
|
ALInputDevice(
|
||||||
|
ALAudio* al,
|
||||||
|
ALCdevice* device,
|
||||||
|
uint channels,
|
||||||
|
uint bitsPerSample,
|
||||||
|
uint sampleRate
|
||||||
|
);
|
||||||
|
~ALInputDevice() override;
|
||||||
|
|
||||||
|
void startCapture() override;
|
||||||
|
void stopCapture() override;
|
||||||
|
|
||||||
|
uint getChannels() const override;
|
||||||
|
uint getSampleRate() const override;
|
||||||
|
uint getBitsPerSample() const override;
|
||||||
|
const std::string& getDeviceSpecifier() const override;
|
||||||
|
|
||||||
|
size_t read(char* buffer, size_t bufferSize) override;
|
||||||
|
private:
|
||||||
|
ALAudio* al;
|
||||||
|
ALCdevice* device;
|
||||||
|
uint channels;
|
||||||
|
uint bitsPerSample;
|
||||||
|
uint sampleRate;
|
||||||
|
std::string deviceSpecifier;
|
||||||
};
|
};
|
||||||
|
|
||||||
/// @brief AL source adapter
|
/// @brief AL source adapter
|
||||||
|
|
@ -90,7 +124,7 @@ namespace audio {
|
||||||
float volume = 0.0f;
|
float volume = 0.0f;
|
||||||
public:
|
public:
|
||||||
ALStream* stream = nullptr;
|
ALStream* stream = nullptr;
|
||||||
bool stopped = true;
|
bool manuallyStopped = true;
|
||||||
bool paused = false;
|
bool paused = false;
|
||||||
uint source;
|
uint source;
|
||||||
duration_t duration = 0.0f;
|
duration_t duration = 0.0f;
|
||||||
|
|
@ -130,6 +164,8 @@ namespace audio {
|
||||||
bool isRelative() const override;
|
bool isRelative() const override;
|
||||||
|
|
||||||
int getPriority() const override;
|
int getPriority() const override;
|
||||||
|
|
||||||
|
bool isManuallyStopped() const override;
|
||||||
};
|
};
|
||||||
|
|
||||||
class ALAudio : public Backend {
|
class ALAudio : public Backend {
|
||||||
|
|
@ -152,15 +188,24 @@ namespace audio {
|
||||||
void freeSource(uint source);
|
void freeSource(uint source);
|
||||||
void freeBuffer(uint buffer);
|
void freeBuffer(uint buffer);
|
||||||
|
|
||||||
std::vector<std::string> getAvailableDevices() const;
|
|
||||||
|
|
||||||
std::unique_ptr<Sound> createSound(
|
std::unique_ptr<Sound> createSound(
|
||||||
std::shared_ptr<PCM> pcm, bool keepPCM
|
std::shared_ptr<PCM> pcm, bool keepPCM
|
||||||
) override;
|
) override;
|
||||||
|
|
||||||
std::unique_ptr<Stream> openStream(
|
std::unique_ptr<Stream> openStream(
|
||||||
std::shared_ptr<PCMStream> stream, bool keepSource
|
std::shared_ptr<PCMStream> stream, bool keepSource
|
||||||
) override;
|
) override;
|
||||||
|
|
||||||
|
std::unique_ptr<InputDevice> openInputDevice(
|
||||||
|
const std::string& deviceName,
|
||||||
|
uint sampleRate,
|
||||||
|
uint channels,
|
||||||
|
uint bitsPerSample
|
||||||
|
) override;
|
||||||
|
|
||||||
|
std::vector<std::string> getOutputDeviceNames() override;
|
||||||
|
std::vector<std::string> getInputDeviceNames() override;
|
||||||
|
|
||||||
void setListener(
|
void setListener(
|
||||||
glm::vec3 position,
|
glm::vec3 position,
|
||||||
glm::vec3 velocity,
|
glm::vec3 velocity,
|
||||||
|
|
|
||||||
67
src/audio/MemoryPCMStream.cpp
Normal file
67
src/audio/MemoryPCMStream.cpp
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
#include "MemoryPCMStream.hpp"
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
using namespace audio;
|
||||||
|
|
||||||
|
MemoryPCMStream::MemoryPCMStream(
|
||||||
|
uint sampleRate, uint channels, uint bitsPerSample
|
||||||
|
)
|
||||||
|
: sampleRate(sampleRate), channels(channels), bitsPerSample(bitsPerSample) {
|
||||||
|
}
|
||||||
|
|
||||||
|
void MemoryPCMStream::feed(util::span<ubyte> bytes) {
|
||||||
|
buffer.insert(buffer.end(), bytes.begin(), bytes.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MemoryPCMStream::isOpen() const {
|
||||||
|
return open;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MemoryPCMStream::close() {
|
||||||
|
open = false;
|
||||||
|
buffer = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t MemoryPCMStream::read(char* dst, size_t bufferSize) {
|
||||||
|
if (!open) {
|
||||||
|
return PCMStream::ERROR;
|
||||||
|
}
|
||||||
|
if (buffer.empty()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
size_t count = std::min<size_t>(bufferSize, buffer.size());
|
||||||
|
std::memcpy(dst, buffer.data(), count);
|
||||||
|
buffer.erase(buffer.begin(), buffer.begin() + count);
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t MemoryPCMStream::getTotalSamples() const {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
duration_t MemoryPCMStream::getTotalDuration() const {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint MemoryPCMStream::getChannels() const {
|
||||||
|
return channels;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint MemoryPCMStream::getSampleRate() const {
|
||||||
|
return sampleRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint MemoryPCMStream::getBitsPerSample() const {
|
||||||
|
return bitsPerSample;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MemoryPCMStream::isSeekable() const {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MemoryPCMStream::seek(size_t position) {}
|
||||||
|
|
||||||
|
size_t MemoryPCMStream::available() const {
|
||||||
|
return buffer.size();
|
||||||
|
}
|
||||||
44
src/audio/MemoryPCMStream.hpp
Normal file
44
src/audio/MemoryPCMStream.hpp
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "audio.hpp"
|
||||||
|
#include "util/span.hpp"
|
||||||
|
|
||||||
|
namespace audio {
|
||||||
|
class MemoryPCMStream : public PCMStream {
|
||||||
|
public:
|
||||||
|
MemoryPCMStream(uint sampleRate, uint channels, uint bitsPerSample);
|
||||||
|
|
||||||
|
void feed(util::span<ubyte> bytes);
|
||||||
|
|
||||||
|
bool isOpen() const override;
|
||||||
|
|
||||||
|
void close() override;
|
||||||
|
|
||||||
|
size_t read(char* buffer, size_t bufferSize) override;
|
||||||
|
|
||||||
|
size_t getTotalSamples() const override;
|
||||||
|
|
||||||
|
duration_t getTotalDuration() const override;
|
||||||
|
|
||||||
|
uint getChannels() const override;
|
||||||
|
|
||||||
|
uint getSampleRate() const override;
|
||||||
|
|
||||||
|
uint getBitsPerSample() const override;
|
||||||
|
|
||||||
|
bool isSeekable() const override;
|
||||||
|
|
||||||
|
void seek(size_t position) override;
|
||||||
|
|
||||||
|
size_t available() const;
|
||||||
|
private:
|
||||||
|
uint sampleRate;
|
||||||
|
uint channels;
|
||||||
|
uint bitsPerSample;
|
||||||
|
bool open = true;
|
||||||
|
|
||||||
|
std::vector<ubyte> buffer;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -61,6 +61,13 @@ namespace audio {
|
||||||
|
|
||||||
void setTime(duration_t time) override {
|
void setTime(duration_t time) override {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool isStopOnEnd() const override {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setStopOnEnd(bool stopOnEnd) override {
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class NoAudio : public Backend {
|
class NoAudio : public Backend {
|
||||||
|
|
@ -71,10 +78,24 @@ namespace audio {
|
||||||
std::unique_ptr<Sound> createSound(
|
std::unique_ptr<Sound> createSound(
|
||||||
std::shared_ptr<PCM> pcm, bool keepPCM
|
std::shared_ptr<PCM> pcm, bool keepPCM
|
||||||
) override;
|
) override;
|
||||||
|
|
||||||
std::unique_ptr<Stream> openStream(
|
std::unique_ptr<Stream> openStream(
|
||||||
std::shared_ptr<PCMStream> stream, bool keepSource
|
std::shared_ptr<PCMStream> stream, bool keepSource
|
||||||
) override;
|
) override;
|
||||||
|
|
||||||
|
std::unique_ptr<InputDevice> openInputDevice(
|
||||||
|
const std::string& deviceName, uint sampleRate, uint channels, uint bitsPerSample
|
||||||
|
) override {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> getInputDeviceNames() override {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
std::vector<std::string> getOutputDeviceNames() override {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
void setListener(
|
void setListener(
|
||||||
glm::vec3 position, glm::vec3 velocity, glm::vec3 at, glm::vec3 up
|
glm::vec3 position, glm::vec3 velocity, glm::vec3 at, glm::vec3 up
|
||||||
) override {
|
) override {
|
||||||
|
|
|
||||||
|
|
@ -151,6 +151,8 @@ public:
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static std::unique_ptr<InputDevice> input_device = nullptr;
|
||||||
|
|
||||||
void audio::initialize(bool enabled, AudioSettings& settings) {
|
void audio::initialize(bool enabled, AudioSettings& settings) {
|
||||||
enabled = enabled && settings.enabled.get();
|
enabled = enabled && settings.enabled.get();
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
|
|
@ -180,6 +182,15 @@ void audio::initialize(bool enabled, AudioSettings& settings) {
|
||||||
audio::get_channel(channel.name)->setVolume(value * value);
|
audio::get_channel(channel.name)->setVolume(value * value);
|
||||||
}, true));
|
}, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input_device = backend->openInputDevice("", 44100, 1, 16);
|
||||||
|
if (input_device) {
|
||||||
|
input_device->startCapture();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
InputDevice* audio::get_input_device() {
|
||||||
|
return input_device.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<PCM> audio::load_PCM(const io::path& file, bool headerOnly) {
|
std::unique_ptr<PCM> audio::load_PCM(const io::path& file, bool headerOnly) {
|
||||||
|
|
@ -242,6 +253,38 @@ std::unique_ptr<Stream> audio::open_stream(
|
||||||
return backend->openStream(std::move(stream), keepSource);
|
return backend->openStream(std::move(stream), keepSource);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<InputDevice> audio::open_input_device(
|
||||||
|
const std::string& deviceName, uint sampleRate, uint channels, uint bitsPerSample
|
||||||
|
) {
|
||||||
|
return backend->openInputDevice(
|
||||||
|
deviceName, sampleRate, channels, bitsPerSample
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> audio::get_input_devices_names() {
|
||||||
|
return backend->getInputDeviceNames();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> audio::get_output_devices_names() {
|
||||||
|
return backend->getOutputDeviceNames();
|
||||||
|
}
|
||||||
|
|
||||||
|
void audio::set_input_device(const std::string& deviceName) {
|
||||||
|
auto newDevice = backend->openInputDevice(deviceName, 44100, 1, 16);
|
||||||
|
if (newDevice == nullptr) {
|
||||||
|
logger.error() << "could not open input device: " << deviceName;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input_device) {
|
||||||
|
input_device->stopCapture();
|
||||||
|
}
|
||||||
|
input_device = std::move(newDevice);
|
||||||
|
if (input_device) {
|
||||||
|
input_device->startCapture();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void audio::set_listener(
|
void audio::set_listener(
|
||||||
glm::vec3 position, glm::vec3 velocity, glm::vec3 lookAt, glm::vec3 up
|
glm::vec3 position, glm::vec3 velocity, glm::vec3 lookAt, glm::vec3 up
|
||||||
) {
|
) {
|
||||||
|
|
@ -421,8 +464,15 @@ void audio::update(double delta) {
|
||||||
speaker->update(channel);
|
speaker->update(channel);
|
||||||
}
|
}
|
||||||
if (speaker->isStopped()) {
|
if (speaker->isStopped()) {
|
||||||
streams.erase(it->first);
|
auto foundStream = streams.find(it->first);
|
||||||
it = speakers.erase(it);
|
if (foundStream == streams.end() ||
|
||||||
|
(!speaker->isManuallyStopped() &&
|
||||||
|
foundStream->second->isStopOnEnd())) {
|
||||||
|
streams.erase(it->first);
|
||||||
|
it = speakers.erase(it);
|
||||||
|
} else {
|
||||||
|
it++;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
it++;
|
it++;
|
||||||
}
|
}
|
||||||
|
|
@ -458,6 +508,9 @@ void audio::reset_channel(int index) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void audio::close() {
|
void audio::close() {
|
||||||
|
if (input_device) {
|
||||||
|
input_device->stopCapture();
|
||||||
|
}
|
||||||
speakers.clear();
|
speakers.clear();
|
||||||
delete backend;
|
delete backend;
|
||||||
backend = nullptr;
|
backend = nullptr;
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,8 @@ namespace audio {
|
||||||
/// @brief streams and important sounds
|
/// @brief streams and important sounds
|
||||||
constexpr inline int PRIORITY_HIGH = 10;
|
constexpr inline int PRIORITY_HIGH = 10;
|
||||||
|
|
||||||
|
constexpr inline size_t MAX_INPUT_SAMPLES = 22050;
|
||||||
|
|
||||||
class Speaker;
|
class Speaker;
|
||||||
|
|
||||||
/// @brief Audio speaker states
|
/// @brief Audio speaker states
|
||||||
|
|
@ -108,6 +110,31 @@ namespace audio {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class InputDevice {
|
||||||
|
public:
|
||||||
|
virtual ~InputDevice() {};
|
||||||
|
|
||||||
|
virtual void startCapture() = 0;
|
||||||
|
virtual void stopCapture() = 0;
|
||||||
|
|
||||||
|
/// @brief Get number of audio channels
|
||||||
|
/// @return 1 if mono, 2 if stereo
|
||||||
|
virtual uint getChannels() const = 0;
|
||||||
|
/// @brief Get audio sampling frequency
|
||||||
|
/// @return number of mono samples per second
|
||||||
|
virtual uint getSampleRate() const = 0;
|
||||||
|
/// @brief Get number of bits per mono sample
|
||||||
|
/// @return 8 or 16
|
||||||
|
virtual uint getBitsPerSample() const = 0;
|
||||||
|
|
||||||
|
/// @brief Read available data to buffer.
|
||||||
|
/// @return size of data received or PCMStream::ERROR in case of error
|
||||||
|
virtual size_t read(char* buffer, size_t bufferSize) = 0;
|
||||||
|
|
||||||
|
/// @brief Get device specifier string
|
||||||
|
virtual const std::string& getDeviceSpecifier() const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
/// @brief audio::PCMStream is a data source for audio::Stream
|
/// @brief audio::PCMStream is a data source for audio::Stream
|
||||||
class PCMStream {
|
class PCMStream {
|
||||||
public:
|
public:
|
||||||
|
|
@ -121,6 +148,10 @@ namespace audio {
|
||||||
/// (always equals bufferSize if seekable and looped)
|
/// (always equals bufferSize if seekable and looped)
|
||||||
virtual size_t readFully(char* buffer, size_t bufferSize, bool loop);
|
virtual size_t readFully(char* buffer, size_t bufferSize, bool loop);
|
||||||
|
|
||||||
|
/// @brief Read available data to buffer
|
||||||
|
/// @param buffer destination buffer
|
||||||
|
/// @param bufferSize destination buffer size
|
||||||
|
/// @return count of received bytes or PCMStream::ERROR
|
||||||
virtual size_t read(char* buffer, size_t bufferSize) = 0;
|
virtual size_t read(char* buffer, size_t bufferSize) = 0;
|
||||||
|
|
||||||
/// @brief Close stream
|
/// @brief Close stream
|
||||||
|
|
@ -195,6 +226,9 @@ namespace audio {
|
||||||
/// @brief Set playhead to the selected time
|
/// @brief Set playhead to the selected time
|
||||||
/// @param time selected time
|
/// @param time selected time
|
||||||
virtual void setTime(duration_t time) = 0;
|
virtual void setTime(duration_t time) = 0;
|
||||||
|
|
||||||
|
virtual bool isStopOnEnd() const = 0;
|
||||||
|
virtual void setStopOnEnd(bool stopOnEnd) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
/// @brief Sound is an audio asset that supposed to support many
|
/// @brief Sound is an audio asset that supposed to support many
|
||||||
|
|
@ -329,6 +363,8 @@ namespace audio {
|
||||||
inline bool isStopped() const {
|
inline bool isStopped() const {
|
||||||
return getState() == State::stopped;
|
return getState() == State::stopped;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
virtual bool isManuallyStopped() const = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
class Backend {
|
class Backend {
|
||||||
|
|
@ -341,12 +377,20 @@ namespace audio {
|
||||||
virtual std::unique_ptr<Stream> openStream(
|
virtual std::unique_ptr<Stream> openStream(
|
||||||
std::shared_ptr<PCMStream> stream, bool keepSource
|
std::shared_ptr<PCMStream> stream, bool keepSource
|
||||||
) = 0;
|
) = 0;
|
||||||
|
virtual std::unique_ptr<InputDevice> openInputDevice(
|
||||||
|
const std::string& deviceName,
|
||||||
|
uint sampleRate,
|
||||||
|
uint channels,
|
||||||
|
uint bitsPerSample
|
||||||
|
) = 0;
|
||||||
virtual void setListener(
|
virtual void setListener(
|
||||||
glm::vec3 position,
|
glm::vec3 position,
|
||||||
glm::vec3 velocity,
|
glm::vec3 velocity,
|
||||||
glm::vec3 lookAt,
|
glm::vec3 lookAt,
|
||||||
glm::vec3 up
|
glm::vec3 up
|
||||||
) = 0;
|
) = 0;
|
||||||
|
virtual std::vector<std::string> getInputDeviceNames() = 0;
|
||||||
|
virtual std::vector<std::string> getOutputDeviceNames() = 0;
|
||||||
virtual void update(double delta) = 0;
|
virtual void update(double delta) = 0;
|
||||||
|
|
||||||
/// @brief Check if backend is an abstraction that does not internally
|
/// @brief Check if backend is an abstraction that does not internally
|
||||||
|
|
@ -402,6 +446,28 @@ namespace audio {
|
||||||
std::shared_ptr<PCMStream> stream, bool keepSource
|
std::shared_ptr<PCMStream> stream, bool keepSource
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/// @brief Open audio input device
|
||||||
|
/// @param sampleRate sample rate
|
||||||
|
/// @param channels channels count (1 - mono, 2 - stereo)
|
||||||
|
/// @param bitsPerSample number of bits per sample (8 or 16)
|
||||||
|
/// @return new InputDevice instance or nullptr
|
||||||
|
std::unique_ptr<InputDevice> open_input_device(
|
||||||
|
const std::string& deviceName,
|
||||||
|
uint sampleRate,
|
||||||
|
uint channels,
|
||||||
|
uint bitsPerSample
|
||||||
|
);
|
||||||
|
|
||||||
|
/// @brief Retrieve names of available audio input devices
|
||||||
|
/// @return list of device names
|
||||||
|
std::vector<std::string> get_input_devices_names();
|
||||||
|
|
||||||
|
/// @brief Retrieve names of available audio output devices
|
||||||
|
/// @return list of device names
|
||||||
|
std::vector<std::string> get_output_devices_names();
|
||||||
|
|
||||||
|
void set_input_device(const std::string& deviceName);
|
||||||
|
|
||||||
/// @brief Configure 3D listener
|
/// @brief Configure 3D listener
|
||||||
/// @param position listener position
|
/// @param position listener position
|
||||||
/// @param velocity listener velocity (used for Doppler effect)
|
/// @param velocity listener velocity (used for Doppler effect)
|
||||||
|
|
@ -515,6 +581,8 @@ namespace audio {
|
||||||
/// @brief Stop all playing audio in channel, reset channel state
|
/// @brief Stop all playing audio in channel, reset channel state
|
||||||
void reset_channel(int channel);
|
void reset_channel(int channel);
|
||||||
|
|
||||||
|
InputDevice* get_input_device();
|
||||||
|
|
||||||
/// @brief Finalize audio system
|
/// @brief Finalize audio system
|
||||||
void close();
|
void close();
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
#include "debug/Logger.hpp"
|
#include "debug/Logger.hpp"
|
||||||
#include "io/engine_paths.hpp"
|
#include "engine/EnginePaths.hpp"
|
||||||
#include "typedefs.hpp"
|
#include "typedefs.hpp"
|
||||||
#include "util/stringutil.hpp"
|
#include "util/stringutil.hpp"
|
||||||
#include "coders/json.hpp"
|
#include "coders/json.hpp"
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
#define VC_ENABLE_REFLECTION
|
||||||
#include "imageio.hpp"
|
#include "imageio.hpp"
|
||||||
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
|
@ -7,28 +8,34 @@
|
||||||
#include "io/io.hpp"
|
#include "io/io.hpp"
|
||||||
#include "png.hpp"
|
#include "png.hpp"
|
||||||
|
|
||||||
|
using namespace imageio;
|
||||||
|
|
||||||
using image_reader =
|
using image_reader =
|
||||||
std::function<std::unique_ptr<ImageData>(const ubyte*, size_t)>;
|
std::function<std::unique_ptr<ImageData>(const ubyte*, size_t)>;
|
||||||
using image_writer = std::function<void(const std::string&, const ImageData*)>;
|
using image_writer = std::function<void(const std::string&, const ImageData*)>;
|
||||||
|
|
||||||
static std::unordered_map<std::string, image_reader> readers {
|
static std::unordered_map<ImageFileFormat, image_reader> readers {
|
||||||
{".png", png::load_image},
|
{ImageFileFormat::PNG, png::load_image},
|
||||||
};
|
};
|
||||||
|
|
||||||
static std::unordered_map<std::string, image_writer> writers {
|
static std::unordered_map<ImageFileFormat, image_writer> writers {
|
||||||
{".png", png::write_image},
|
{ImageFileFormat::PNG, png::write_image},
|
||||||
};
|
};
|
||||||
|
|
||||||
bool imageio::is_read_supported(const std::string& extension) {
|
bool imageio::is_read_supported(const std::string& extension) {
|
||||||
return readers.find(extension) != readers.end();
|
return extension == ".png";
|
||||||
}
|
}
|
||||||
|
|
||||||
bool imageio::is_write_supported(const std::string& extension) {
|
bool imageio::is_write_supported(const std::string& extension) {
|
||||||
return writers.find(extension) != writers.end();
|
return extension == ".png";
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<ImageData> imageio::read(const io::path& file) {
|
std::unique_ptr<ImageData> imageio::read(const io::path& file) {
|
||||||
auto found = readers.find(file.extension());
|
ImageFileFormat format;
|
||||||
|
if (!ImageFileFormatMeta.getItem(file.extension().substr(1), format)) {
|
||||||
|
throw std::runtime_error("unsupported image format");
|
||||||
|
}
|
||||||
|
auto found = readers.find(format);
|
||||||
if (found == readers.end()) {
|
if (found == readers.end()) {
|
||||||
throw std::runtime_error(
|
throw std::runtime_error(
|
||||||
"file format is not supported (read): " + file.string()
|
"file format is not supported (read): " + file.string()
|
||||||
|
|
@ -44,8 +51,25 @@ std::unique_ptr<ImageData> imageio::read(const io::path& file) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<ImageData> imageio::decode(
|
||||||
|
ImageFileFormat format, util::span<ubyte> src
|
||||||
|
) {
|
||||||
|
auto found = readers.find(format);
|
||||||
|
try {
|
||||||
|
return std::unique_ptr<ImageData>(found->second(src.data(), src.size()));
|
||||||
|
} catch (const std::runtime_error& err) {
|
||||||
|
throw std::runtime_error(
|
||||||
|
"could not to decode image: " + std::string(err.what())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void imageio::write(const io::path& file, const ImageData* image) {
|
void imageio::write(const io::path& file, const ImageData* image) {
|
||||||
auto found = writers.find(file.extension());
|
ImageFileFormat format;
|
||||||
|
if (!ImageFileFormatMeta.getItem(file.extension().substr(1), format)) {
|
||||||
|
throw std::runtime_error("unsupported image format");
|
||||||
|
}
|
||||||
|
auto found = writers.find(format);
|
||||||
if (found == writers.end()) {
|
if (found == writers.end()) {
|
||||||
throw std::runtime_error(
|
throw std::runtime_error(
|
||||||
"file format is not supported (write): " + file.string()
|
"file format is not supported (write): " + file.string()
|
||||||
|
|
@ -53,3 +77,14 @@ void imageio::write(const io::path& file, const ImageData* image) {
|
||||||
}
|
}
|
||||||
return found->second(io::resolve(file).u8string(), image);
|
return found->second(io::resolve(file).u8string(), image);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
util::Buffer<unsigned char> imageio::encode(
|
||||||
|
ImageFileFormat format, const ImageData& image
|
||||||
|
) {
|
||||||
|
switch (format) {
|
||||||
|
case ImageFileFormat::PNG:
|
||||||
|
return png::encode_image(image);
|
||||||
|
default:
|
||||||
|
throw std::runtime_error("file format is not supported for encoding");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,10 +4,22 @@
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include "io/fwd.hpp"
|
#include "io/fwd.hpp"
|
||||||
|
#include "util/Buffer.hpp"
|
||||||
|
#include "util/EnumMetadata.hpp"
|
||||||
|
#include "util/span.hpp"
|
||||||
|
#include "typedefs.hpp"
|
||||||
|
|
||||||
class ImageData;
|
class ImageData;
|
||||||
|
|
||||||
namespace imageio {
|
namespace imageio {
|
||||||
|
enum class ImageFileFormat {
|
||||||
|
PNG
|
||||||
|
};
|
||||||
|
|
||||||
|
VC_ENUM_METADATA(ImageFileFormat)
|
||||||
|
{"png", ImageFileFormat::PNG},
|
||||||
|
VC_ENUM_END
|
||||||
|
|
||||||
inline const std::string PNG = ".png";
|
inline const std::string PNG = ".png";
|
||||||
|
|
||||||
bool is_read_supported(const std::string& extension);
|
bool is_read_supported(const std::string& extension);
|
||||||
|
|
@ -15,4 +27,6 @@ namespace imageio {
|
||||||
|
|
||||||
std::unique_ptr<ImageData> read(const io::path& file);
|
std::unique_ptr<ImageData> read(const io::path& file);
|
||||||
void write(const io::path& file, const ImageData* image);
|
void write(const io::path& file, const ImageData* image);
|
||||||
|
std::unique_ptr<ImageData> decode(ImageFileFormat format, util::span<ubyte> src);
|
||||||
|
util::Buffer<unsigned char> encode(ImageFileFormat format, const ImageData& image);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,60 @@
|
||||||
|
|
||||||
static debug::Logger logger("png-coder");
|
static debug::Logger logger("png-coder");
|
||||||
|
|
||||||
|
static util::Buffer<ubyte> write_to_memory(uint width, uint height, const ubyte* data, bool alpha) {
|
||||||
|
uint pixsize = alpha ? 4 : 3;
|
||||||
|
|
||||||
|
std::vector<ubyte> buffer;
|
||||||
|
png_structp png_ptr = png_create_write_struct(
|
||||||
|
PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr
|
||||||
|
);
|
||||||
|
png_infop info_ptr = png_create_info_struct(png_ptr);
|
||||||
|
|
||||||
|
png_set_write_fn(
|
||||||
|
png_ptr,
|
||||||
|
&buffer,
|
||||||
|
[](png_structp pngPtr, png_bytep data, png_size_t length) {
|
||||||
|
auto& buf = *reinterpret_cast<std::vector<ubyte>*>(png_get_io_ptr(pngPtr));
|
||||||
|
buf.insert(
|
||||||
|
buf.end(),
|
||||||
|
reinterpret_cast<ubyte*>(data),
|
||||||
|
reinterpret_cast<ubyte*>(data) + length
|
||||||
|
);
|
||||||
|
},
|
||||||
|
nullptr
|
||||||
|
);
|
||||||
|
|
||||||
|
png_set_IHDR(
|
||||||
|
png_ptr,
|
||||||
|
info_ptr,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
8,
|
||||||
|
alpha ? PNG_COLOR_TYPE_RGBA : PNG_COLOR_TYPE_RGB,
|
||||||
|
PNG_INTERLACE_NONE,
|
||||||
|
PNG_COMPRESSION_TYPE_BASE,
|
||||||
|
PNG_FILTER_TYPE_BASE
|
||||||
|
);
|
||||||
|
|
||||||
|
png_write_info(png_ptr, info_ptr);
|
||||||
|
|
||||||
|
auto row = std::make_unique<png_byte[]>(pixsize * width);
|
||||||
|
for (uint y = 0; y < height; y++) {
|
||||||
|
for (uint x = 0; x < width; x++) {
|
||||||
|
for (uint i = 0; i < pixsize; i++) {
|
||||||
|
row[x * pixsize + i] =
|
||||||
|
(png_byte)data[(y * width + x) * pixsize + i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
png_write_row(png_ptr, row.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
png_write_end(png_ptr, nullptr);
|
||||||
|
png_free_data(png_ptr, info_ptr, PNG_FREE_ALL, -1);
|
||||||
|
png_destroy_write_struct(&png_ptr, &info_ptr);
|
||||||
|
return util::Buffer<ubyte>(buffer.data(), buffer.size());
|
||||||
|
}
|
||||||
|
|
||||||
// returns 0 if all-right, 1 otherwise
|
// returns 0 if all-right, 1 otherwise
|
||||||
static int png_write(
|
static int png_write(
|
||||||
const char* filename, uint width, uint height, const ubyte* data, bool alpha
|
const char* filename, uint width, uint height, const ubyte* data, bool alpha
|
||||||
|
|
@ -230,3 +284,13 @@ void png::write_image(const std::string& filename, const ImageData* image) {
|
||||||
image->getFormat() == ImageFormat::rgba8888
|
image->getFormat() == ImageFormat::rgba8888
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
util::Buffer<ubyte> png::encode_image(const ImageData& image) {
|
||||||
|
auto format = image.getFormat();
|
||||||
|
return write_to_memory(
|
||||||
|
image.getWidth(),
|
||||||
|
image.getHeight(),
|
||||||
|
image.getData(),
|
||||||
|
format == ImageFormat::rgba8888
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include "typedefs.hpp"
|
#include "typedefs.hpp"
|
||||||
|
#include "util/Buffer.hpp"
|
||||||
|
|
||||||
class Texture;
|
class Texture;
|
||||||
class ImageData;
|
class ImageData;
|
||||||
|
|
@ -11,6 +12,7 @@ class ImageData;
|
||||||
namespace png {
|
namespace png {
|
||||||
std::unique_ptr<ImageData> load_image(const ubyte* bytes, size_t size);
|
std::unique_ptr<ImageData> load_image(const ubyte* bytes, size_t size);
|
||||||
void write_image(const std::string& filename, const ImageData* image);
|
void write_image(const std::string& filename, const ImageData* image);
|
||||||
|
util::Buffer<ubyte> encode_image(const ImageData& image);
|
||||||
std::unique_ptr<Texture> load_texture(const ubyte* bytes, size_t size);
|
std::unique_ptr<Texture> load_texture(const ubyte* bytes, size_t size);
|
||||||
std::unique_ptr<Texture> load_texture(const std::string& filename);
|
std::unique_ptr<Texture> load_texture(const std::string& filename);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,8 @@ inline constexpr int ITEM_ICON_SIZE = 48;
|
||||||
|
|
||||||
inline constexpr int TRANSLUCENT_BLOCKS_SORT_INTERVAL = 8;
|
inline constexpr int TRANSLUCENT_BLOCKS_SORT_INTERVAL = 8;
|
||||||
|
|
||||||
|
inline constexpr int ATLAS_EXTRUSION = 2;
|
||||||
|
|
||||||
inline const std::string SHADERS_FOLDER = "shaders";
|
inline const std::string SHADERS_FOLDER = "shaders";
|
||||||
inline const std::string TEXTURES_FOLDER = "textures";
|
inline const std::string TEXTURES_FOLDER = "textures";
|
||||||
inline const std::string FONTS_FOLDER = "fonts";
|
inline const std::string FONTS_FOLDER = "fonts";
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
#include "ContentControl.hpp"
|
#include "ContentControl.hpp"
|
||||||
|
|
||||||
#include "io/io.hpp"
|
#include "io/io.hpp"
|
||||||
#include "io/engine_paths.hpp"
|
#include "engine/EnginePaths.hpp"
|
||||||
#include "Content.hpp"
|
#include "Content.hpp"
|
||||||
#include "ContentPack.hpp"
|
#include "ContentPack.hpp"
|
||||||
#include "ContentBuilder.hpp"
|
#include "ContentBuilder.hpp"
|
||||||
|
|
@ -16,6 +16,13 @@ static void load_configs(Input& input, const io::path& root) {
|
||||||
auto configFolder = root / "config";
|
auto configFolder = root / "config";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static std::vector<io::path> default_content_sources {
|
||||||
|
"world:content",
|
||||||
|
"user:content",
|
||||||
|
"project:content",
|
||||||
|
"res:content",
|
||||||
|
};
|
||||||
|
|
||||||
ContentControl::ContentControl(
|
ContentControl::ContentControl(
|
||||||
const Project& project,
|
const Project& project,
|
||||||
EnginePaths& paths,
|
EnginePaths& paths,
|
||||||
|
|
@ -27,11 +34,7 @@ ContentControl::ContentControl(
|
||||||
postContent(std::move(postContent)),
|
postContent(std::move(postContent)),
|
||||||
basePacks(project.basePacks),
|
basePacks(project.basePacks),
|
||||||
manager(std::make_unique<PacksManager>()) {
|
manager(std::make_unique<PacksManager>()) {
|
||||||
manager->setSources({
|
manager->setSources(default_content_sources);
|
||||||
"world:content",
|
|
||||||
"user:content",
|
|
||||||
"res:content",
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ContentControl::~ContentControl() = default;
|
ContentControl::~ContentControl() = default;
|
||||||
|
|
@ -48,10 +51,10 @@ std::vector<std::string>& ContentControl::getBasePacks() {
|
||||||
return basePacks;
|
return basePacks;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ContentControl::resetContent() {
|
void ContentControl::resetContent(const std::vector<std::string>& nonReset) {
|
||||||
paths.setCurrentWorldFolder("");
|
paths.setCurrentWorldFolder("");
|
||||||
|
|
||||||
scripting::cleanup();
|
scripting::cleanup(nonReset);
|
||||||
std::vector<PathsRoot> resRoots;
|
std::vector<PathsRoot> resRoots;
|
||||||
{
|
{
|
||||||
auto pack = ContentPack::createCore();
|
auto pack = ContentPack::createCore();
|
||||||
|
|
@ -67,6 +70,7 @@ void ContentControl::resetContent() {
|
||||||
scripting::on_content_reset();
|
scripting::on_content_reset();
|
||||||
|
|
||||||
setContentPacksRaw(manager->getAll(basePacks));
|
setContentPacksRaw(manager->getAll(basePacks));
|
||||||
|
resetContentSources();
|
||||||
|
|
||||||
postContent();
|
postContent();
|
||||||
}
|
}
|
||||||
|
|
@ -78,8 +82,6 @@ void ContentControl::loadContent(const std::vector<std::string>& names) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void ContentControl::loadContent() {
|
void ContentControl::loadContent() {
|
||||||
scripting::cleanup();
|
|
||||||
|
|
||||||
std::vector<std::string> names;
|
std::vector<std::string> names;
|
||||||
for (auto& pack : contentPacks) {
|
for (auto& pack : contentPacks) {
|
||||||
names.push_back(pack.id);
|
names.push_back(pack.id);
|
||||||
|
|
@ -140,3 +142,15 @@ PacksManager& ContentControl::scan() {
|
||||||
manager->scan();
|
manager->scan();
|
||||||
return *manager;
|
return *manager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ContentControl::setContentSources(std::vector<io::path> sources) {
|
||||||
|
manager->setSources(std::move(sources));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ContentControl::resetContentSources() {
|
||||||
|
manager->setSources(default_content_sources);
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<io::path>& ContentControl::getContentSources() const {
|
||||||
|
return manager->getSources();
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
|
||||||
|
#include "io/path.hpp"
|
||||||
#include "ContentPack.hpp"
|
#include "ContentPack.hpp"
|
||||||
|
|
||||||
class Content;
|
class Content;
|
||||||
|
|
@ -13,10 +14,6 @@ class EnginePaths;
|
||||||
class Input;
|
class Input;
|
||||||
struct Project;
|
struct Project;
|
||||||
|
|
||||||
namespace io {
|
|
||||||
class path;
|
|
||||||
}
|
|
||||||
|
|
||||||
class ContentControl {
|
class ContentControl {
|
||||||
public:
|
public:
|
||||||
ContentControl(
|
ContentControl(
|
||||||
|
|
@ -34,7 +31,7 @@ public:
|
||||||
std::vector<std::string>& getBasePacks();
|
std::vector<std::string>& getBasePacks();
|
||||||
|
|
||||||
/// @brief Reset content to base packs list
|
/// @brief Reset content to base packs list
|
||||||
void resetContent();
|
void resetContent(const std::vector<std::string>& nonReset);
|
||||||
|
|
||||||
void loadContent(const std::vector<std::string>& names);
|
void loadContent(const std::vector<std::string>& names);
|
||||||
|
|
||||||
|
|
@ -46,6 +43,10 @@ public:
|
||||||
const std::vector<ContentPack>& getAllContentPacks() const;
|
const std::vector<ContentPack>& getAllContentPacks() const;
|
||||||
|
|
||||||
PacksManager& scan();
|
PacksManager& scan();
|
||||||
|
|
||||||
|
void setContentSources(std::vector<io::path> sources);
|
||||||
|
void resetContentSources();
|
||||||
|
const std::vector<io::path>& getContentSources() const;
|
||||||
private:
|
private:
|
||||||
EnginePaths& paths;
|
EnginePaths& paths;
|
||||||
Input& input;
|
Input& input;
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@
|
||||||
#include "objects/rigging.hpp"
|
#include "objects/rigging.hpp"
|
||||||
#include "util/listutil.hpp"
|
#include "util/listutil.hpp"
|
||||||
#include "util/stringutil.hpp"
|
#include "util/stringutil.hpp"
|
||||||
#include "io/engine_paths.hpp"
|
#include "engine/EnginePaths.hpp"
|
||||||
|
|
||||||
static debug::Logger logger("content-loader");
|
static debug::Logger logger("content-loader");
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
#include "coders/json.hpp"
|
#include "coders/json.hpp"
|
||||||
#include "constants.hpp"
|
#include "constants.hpp"
|
||||||
#include "data/dv.hpp"
|
#include "data/dv.hpp"
|
||||||
#include "io/engine_paths.hpp"
|
#include "engine/EnginePaths.hpp"
|
||||||
#include "io/io.hpp"
|
#include "io/io.hpp"
|
||||||
#include "coders/commons.hpp"
|
#include "coders/commons.hpp"
|
||||||
#include "debug/Logger.hpp"
|
#include "debug/Logger.hpp"
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,10 @@
|
||||||
|
|
||||||
PacksManager::PacksManager() = default;
|
PacksManager::PacksManager() = default;
|
||||||
|
|
||||||
|
const std::vector<io::path>& PacksManager::getSources() const {
|
||||||
|
return sources;
|
||||||
|
}
|
||||||
|
|
||||||
void PacksManager::setSources(std::vector<io::path> sources) {
|
void PacksManager::setSources(std::vector<io::path> sources) {
|
||||||
this->sources = std::move(sources);
|
this->sources = std::move(sources);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,9 @@ class PacksManager {
|
||||||
public:
|
public:
|
||||||
PacksManager();
|
PacksManager();
|
||||||
|
|
||||||
|
/// @brief Get current content packs sources
|
||||||
|
const std::vector<io::path>& getSources() const;
|
||||||
|
|
||||||
/// @brief Set content packs sources (search folders)
|
/// @brief Set content packs sources (search folders)
|
||||||
void setSources(std::vector<io::path> sources);
|
void setSources(std::vector<io::path> sources);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
#include "../ContentPack.hpp"
|
#include "../ContentPack.hpp"
|
||||||
|
|
||||||
#include "io/io.hpp"
|
#include "io/io.hpp"
|
||||||
#include "io/engine_paths.hpp"
|
#include "engine/EnginePaths.hpp"
|
||||||
#include "logic/scripting/scripting.hpp"
|
#include "logic/scripting/scripting.hpp"
|
||||||
#include "util/stringutil.hpp"
|
#include "util/stringutil.hpp"
|
||||||
#include "world/generator/GeneratorDef.hpp"
|
#include "world/generator/GeneratorDef.hpp"
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
#include "content/Content.hpp"
|
#include "content/Content.hpp"
|
||||||
#include "content/ContentBuilder.hpp"
|
#include "content/ContentBuilder.hpp"
|
||||||
#include "io/io.hpp"
|
#include "io/io.hpp"
|
||||||
#include "io/engine_paths.hpp"
|
#include "engine/EnginePaths.hpp"
|
||||||
#include "window/input.hpp"
|
#include "window/input.hpp"
|
||||||
#include "voxels/Block.hpp"
|
#include "voxels/Block.hpp"
|
||||||
#include "coders/toml.hpp"
|
#include "coders/toml.hpp"
|
||||||
|
|
|
||||||
|
|
@ -197,6 +197,7 @@ bool DebuggingServer::performCommand(
|
||||||
connectionEstablished = true;
|
connectionEstablished = true;
|
||||||
logger.info() << "client connection established";
|
logger.info() << "client connection established";
|
||||||
connection->sendResponse("success");
|
connection->sendResponse("success");
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
if (!connectionEstablished) {
|
if (!connectionEstablished) {
|
||||||
return false;
|
return false;
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
#include "Editor.hpp"
|
#include "Editor.hpp"
|
||||||
|
|
||||||
#include "engine/Engine.hpp"
|
#include "engine/Engine.hpp"
|
||||||
#include "io/engine_paths.hpp"
|
#include "engine/EnginePaths.hpp"
|
||||||
#include "coders/syntax_parser.hpp"
|
#include "coders/syntax_parser.hpp"
|
||||||
#include "SyntaxProcessor.hpp"
|
#include "SyntaxProcessor.hpp"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,13 @@
|
||||||
#include "Project.hpp"
|
#include "Project.hpp"
|
||||||
|
|
||||||
#include "data/dv_util.hpp"
|
#include "data/dv_util.hpp"
|
||||||
|
#include "debug/Logger.hpp"
|
||||||
|
#include "io/io.hpp"
|
||||||
|
#include "io/path.hpp"
|
||||||
#include "logic/scripting/scripting.hpp"
|
#include "logic/scripting/scripting.hpp"
|
||||||
|
|
||||||
|
static debug::Logger logger("project");
|
||||||
|
|
||||||
Project::~Project() = default;
|
Project::~Project() = default;
|
||||||
|
|
||||||
dv::value Project::serialize() const {
|
dv::value Project::serialize() const {
|
||||||
|
|
@ -18,3 +23,23 @@ void Project::deserialize(const dv::value& src) {
|
||||||
src.at("title").get(title);
|
src.at("title").get(title);
|
||||||
dv::get(src, "base_packs", basePacks);
|
dv::get(src, "base_packs", basePacks);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
|
#include "interfaces/Process.hpp"
|
||||||
#include "interfaces/Serializable.hpp"
|
#include "interfaces/Serializable.hpp"
|
||||||
|
|
||||||
namespace scripting {
|
namespace scripting {
|
||||||
|
|
@ -15,9 +16,13 @@ struct Project : Serializable {
|
||||||
std::string title;
|
std::string title;
|
||||||
std::vector<std::string> basePacks;
|
std::vector<std::string> basePacks;
|
||||||
std::unique_ptr<scripting::IClientProjectScript> clientScript;
|
std::unique_ptr<scripting::IClientProjectScript> clientScript;
|
||||||
|
std::unique_ptr<Process> setupCoroutine;
|
||||||
|
|
||||||
~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();
|
||||||
};
|
};
|
||||||
|
|
|
||||||
15
src/engine/CoreParameters.hpp
Normal file
15
src/engine/CoreParameters.hpp
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <filesystem>
|
||||||
|
|
||||||
|
struct CoreParameters {
|
||||||
|
bool headless = false;
|
||||||
|
bool testMode = false;
|
||||||
|
std::filesystem::path resFolder = "res";
|
||||||
|
std::filesystem::path userFolder = ".";
|
||||||
|
std::filesystem::path scriptFile;
|
||||||
|
std::filesystem::path projectFolder;
|
||||||
|
std::string debugServerString;
|
||||||
|
int tps = 20;
|
||||||
|
};
|
||||||
|
|
@ -8,8 +8,6 @@
|
||||||
#include "assets/AssetsLoader.hpp"
|
#include "assets/AssetsLoader.hpp"
|
||||||
#include "audio/audio.hpp"
|
#include "audio/audio.hpp"
|
||||||
#include "coders/GLSLExtension.hpp"
|
#include "coders/GLSLExtension.hpp"
|
||||||
#include "coders/imageio.hpp"
|
|
||||||
#include "coders/json.hpp"
|
|
||||||
#include "coders/toml.hpp"
|
#include "coders/toml.hpp"
|
||||||
#include "coders/commons.hpp"
|
#include "coders/commons.hpp"
|
||||||
#include "devtools/Editor.hpp"
|
#include "devtools/Editor.hpp"
|
||||||
|
|
@ -18,27 +16,28 @@
|
||||||
#include "content/ContentControl.hpp"
|
#include "content/ContentControl.hpp"
|
||||||
#include "core_defs.hpp"
|
#include "core_defs.hpp"
|
||||||
#include "io/io.hpp"
|
#include "io/io.hpp"
|
||||||
|
#include "io/settings_io.hpp"
|
||||||
#include "frontend/locale.hpp"
|
#include "frontend/locale.hpp"
|
||||||
#include "frontend/menu.hpp"
|
#include "frontend/menu.hpp"
|
||||||
#include "frontend/screens/Screen.hpp"
|
#include "frontend/screens/Screen.hpp"
|
||||||
#include "graphics/render/ModelsGenerator.hpp"
|
#include "graphics/render/ModelsGenerator.hpp"
|
||||||
#include "graphics/core/DrawContext.hpp"
|
#include "graphics/core/DrawContext.hpp"
|
||||||
#include "graphics/core/ImageData.hpp"
|
|
||||||
#include "graphics/core/Shader.hpp"
|
#include "graphics/core/Shader.hpp"
|
||||||
#include "graphics/ui/GUI.hpp"
|
#include "graphics/ui/GUI.hpp"
|
||||||
#include "objects/rigging.hpp"
|
#include "graphics/ui/elements/Menu.hpp"
|
||||||
#include "logic/EngineController.hpp"
|
#include "logic/EngineController.hpp"
|
||||||
#include "logic/CommandsInterpreter.hpp"
|
#include "logic/CommandsInterpreter.hpp"
|
||||||
#include "logic/scripting/scripting.hpp"
|
#include "logic/scripting/scripting.hpp"
|
||||||
#include "logic/scripting/scripting_hud.hpp"
|
#include "logic/scripting/scripting_hud.hpp"
|
||||||
#include "network/Network.hpp"
|
#include "network/Network.hpp"
|
||||||
#include "util/platform.hpp"
|
#include "util/platform.hpp"
|
||||||
#include "window/Camera.hpp"
|
|
||||||
#include "window/input.hpp"
|
#include "window/input.hpp"
|
||||||
#include "window/Window.hpp"
|
#include "window/Window.hpp"
|
||||||
#include "world/Level.hpp"
|
#include "world/Level.hpp"
|
||||||
#include "Mainloop.hpp"
|
#include "Mainloop.hpp"
|
||||||
#include "ServerMainloop.hpp"
|
#include "ServerMainloop.hpp"
|
||||||
|
#include "WindowControl.hpp"
|
||||||
|
#include "EnginePaths.hpp"
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
|
@ -49,29 +48,6 @@
|
||||||
|
|
||||||
static debug::Logger logger("engine");
|
static debug::Logger logger("engine");
|
||||||
|
|
||||||
static std::unique_ptr<ImageData> load_icon() {
|
|
||||||
try {
|
|
||||||
auto file = "res:textures/misc/icon.png";
|
|
||||||
if (io::exists(file)) {
|
|
||||||
return imageio::read(file);
|
|
||||||
}
|
|
||||||
} catch (const std::exception& err) {
|
|
||||||
logger.error() << "could not load window icon: " << err.what();
|
|
||||||
}
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
static std::unique_ptr<scripting::IClientProjectScript> load_client_project_script() {
|
|
||||||
io::path scriptFile = "project:project_client.lua";
|
|
||||||
if (io::exists(scriptFile)) {
|
|
||||||
logger.info() << "starting project script";
|
|
||||||
return scripting::load_client_project_script(scriptFile);
|
|
||||||
} else {
|
|
||||||
logger.warning() << "project script does not exists";
|
|
||||||
}
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
Engine::Engine() = default;
|
Engine::Engine() = default;
|
||||||
Engine::~Engine() = default;
|
Engine::~Engine() = default;
|
||||||
|
|
||||||
|
|
@ -86,7 +62,7 @@ Engine& Engine::getInstance() {
|
||||||
|
|
||||||
void Engine::onContentLoad() {
|
void Engine::onContentLoad() {
|
||||||
editor->loadTools();
|
editor->loadTools();
|
||||||
langs::setup(langs::get_current(), paths.resPaths.collectRoots());
|
langs::setup(langs::get_current(), paths->resPaths.collectRoots());
|
||||||
|
|
||||||
if (isHeadless()) {
|
if (isHeadless()) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -94,6 +70,7 @@ void Engine::onContentLoad() {
|
||||||
for (auto& pack : content->getAllContentPacks()) {
|
for (auto& pack : content->getAllContentPacks()) {
|
||||||
auto configFolder = pack.folder / "config";
|
auto configFolder = pack.folder / "config";
|
||||||
auto bindsFile = configFolder / "bindings.toml";
|
auto bindsFile = configFolder / "bindings.toml";
|
||||||
|
logger.info() << "loading bindings: " << bindsFile.string();
|
||||||
if (io::is_regular_file(bindsFile)) {
|
if (io::is_regular_file(bindsFile)) {
|
||||||
input->getBindings().read(
|
input->getBindings().read(
|
||||||
toml::parse(
|
toml::parse(
|
||||||
|
|
@ -107,29 +84,9 @@ void Engine::onContentLoad() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Engine::initializeClient() {
|
void Engine::initializeClient() {
|
||||||
std::string title = project->title;
|
windowControl = std::make_unique<WindowControl>(*this);
|
||||||
if (title.empty()) {
|
auto [window, input] = windowControl->initialize();
|
||||||
title = "VoxelCore v" +
|
|
||||||
std::to_string(ENGINE_VERSION_MAJOR) + "." +
|
|
||||||
std::to_string(ENGINE_VERSION_MINOR);
|
|
||||||
}
|
|
||||||
if (ENGINE_DEBUG_BUILD) {
|
|
||||||
title += " [debug]";
|
|
||||||
}
|
|
||||||
if (debuggingServer) {
|
|
||||||
title = "[debugging] " + title;
|
|
||||||
}
|
|
||||||
auto [window, input] = Window::initialize(&settings.display, title);
|
|
||||||
if (!window || !input){
|
|
||||||
throw initialize_error("could not initialize window");
|
|
||||||
}
|
|
||||||
window->setFramerate(settings.display.framerate.get());
|
|
||||||
|
|
||||||
time.set(window->time());
|
|
||||||
if (auto icon = load_icon()) {
|
|
||||||
icon->flipY();
|
|
||||||
window->setIcon(icon.get());
|
|
||||||
}
|
|
||||||
this->window = std::move(window);
|
this->window = std::move(window);
|
||||||
this->input = std::move(input);
|
this->input = std::move(input);
|
||||||
|
|
||||||
|
|
@ -154,6 +111,14 @@ void Engine::initializeClient() {
|
||||||
},
|
},
|
||||||
true
|
true
|
||||||
));
|
));
|
||||||
|
|
||||||
|
keepAlive(this->input->addKeyCallback(Keycode::ESCAPE, [this]() {
|
||||||
|
auto& menu = *gui->getMenu();
|
||||||
|
if (menu.hasOpenPage() && menu.back()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Engine::initialize(CoreParameters coreParameters) {
|
void Engine::initialize(CoreParameters coreParameters) {
|
||||||
|
|
@ -162,15 +127,12 @@ void Engine::initialize(CoreParameters coreParameters) {
|
||||||
|
|
||||||
logger.info() << "engine version: " << ENGINE_VERSION_STRING;
|
logger.info() << "engine version: " << ENGINE_VERSION_STRING;
|
||||||
if (params.headless) {
|
if (params.headless) {
|
||||||
logger.info() << "headless mode is enabled";
|
logger.info() << "engine runs in headless mode";
|
||||||
}
|
}
|
||||||
if (params.projectFolder.empty()) {
|
if (params.projectFolder.empty()) {
|
||||||
params.projectFolder = params.resFolder;
|
params.projectFolder = params.resFolder;
|
||||||
}
|
}
|
||||||
paths.setResourcesFolder(params.resFolder);
|
paths = std::make_unique<EnginePaths>(params);
|
||||||
paths.setUserFilesFolder(params.userFolder);
|
|
||||||
paths.setProjectFolder(params.projectFolder);
|
|
||||||
paths.prepare();
|
|
||||||
loadProject();
|
loadProject();
|
||||||
|
|
||||||
editor = std::make_unique<devtools::Editor>(*this);
|
editor = std::make_unique<devtools::Editor>(*this);
|
||||||
|
|
@ -188,10 +150,6 @@ void Engine::initialize(CoreParameters coreParameters) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!params.scriptFile.empty()) {
|
|
||||||
paths.setScriptFolder(params.scriptFile.parent_path());
|
|
||||||
}
|
|
||||||
loadSettings();
|
loadSettings();
|
||||||
|
|
||||||
controller = std::make_unique<EngineController>(*this);
|
controller = std::make_unique<EngineController>(*this);
|
||||||
|
|
@ -205,7 +163,7 @@ void Engine::initialize(CoreParameters coreParameters) {
|
||||||
langs::locale_by_envlocale(platform::detect_locale())
|
langs::locale_by_envlocale(platform::detect_locale())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
content = std::make_unique<ContentControl>(*project, paths, *input, [this]() {
|
content = std::make_unique<ContentControl>(*project, *paths, *input, [this]() {
|
||||||
onContentLoad();
|
onContentLoad();
|
||||||
});
|
});
|
||||||
scripting::initialize(this);
|
scripting::initialize(this);
|
||||||
|
|
@ -214,10 +172,13 @@ void Engine::initialize(CoreParameters coreParameters) {
|
||||||
gui->setPageLoader(scripting::create_page_loader());
|
gui->setPageLoader(scripting::create_page_loader());
|
||||||
}
|
}
|
||||||
keepAlive(settings.ui.language.observe([this](auto lang) {
|
keepAlive(settings.ui.language.observe([this](auto lang) {
|
||||||
langs::setup(lang, paths.resPaths.collectRoots());
|
langs::setup(lang, paths->resPaths.collectRoots());
|
||||||
}, true));
|
}, true));
|
||||||
|
|
||||||
project->clientScript = load_client_project_script();
|
project->loadProjectStartScript();
|
||||||
|
if (!params.headless) {
|
||||||
|
project->loadProjectClientScript();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Engine::loadSettings() {
|
void Engine::loadSettings() {
|
||||||
|
|
@ -247,29 +208,17 @@ void Engine::loadControls() {
|
||||||
|
|
||||||
void Engine::updateHotkeys() {
|
void Engine::updateHotkeys() {
|
||||||
if (input->jpressed(Keycode::F2)) {
|
if (input->jpressed(Keycode::F2)) {
|
||||||
saveScreenshot();
|
windowControl->saveScreenshot();
|
||||||
}
|
}
|
||||||
if (input->pressed(Keycode::LEFT_CONTROL) && input->pressed(Keycode::F3) &&
|
if (input->pressed(Keycode::LEFT_CONTROL) && input->pressed(Keycode::F3) &&
|
||||||
input->jpressed(Keycode::U)) {
|
input->jpressed(Keycode::U)) {
|
||||||
gui->toggleDebug();
|
gui->toggleDebug();
|
||||||
}
|
}
|
||||||
if (input->jpressed(Keycode::F11)) {
|
if (input->jpressed(Keycode::F11)) {
|
||||||
if (settings.display.windowMode.get() != static_cast<int>(WindowMode::FULLSCREEN)) {
|
windowControl->toggleFullscreen();
|
||||||
settings.display.windowMode.set(static_cast<int>(WindowMode::FULLSCREEN));
|
|
||||||
} else {
|
|
||||||
settings.display.windowMode.set(static_cast<int>(WindowMode::WINDOWED));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Engine::saveScreenshot() {
|
|
||||||
auto image = window->takeScreenshot();
|
|
||||||
image->flipY();
|
|
||||||
io::path filename = paths.getNewScreenshotFile("png");
|
|
||||||
imageio::write(filename.string(), image.get());
|
|
||||||
logger.info() << "saved screenshot as " << filename.string();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Engine::run() {
|
void Engine::run() {
|
||||||
if (params.headless) {
|
if (params.headless) {
|
||||||
ServerMainloop(*this).run();
|
ServerMainloop(*this).run();
|
||||||
|
|
@ -292,6 +241,12 @@ void Engine::detachDebugger() {
|
||||||
debuggingServer.reset();
|
debuggingServer.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Engine::applicationTick() {
|
||||||
|
if (project->setupCoroutine && project->setupCoroutine->isActive()) {
|
||||||
|
project->setupCoroutine->update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Engine::updateFrontend() {
|
void Engine::updateFrontend() {
|
||||||
double delta = time.getDelta();
|
double delta = time.getDelta();
|
||||||
updateHotkeys();
|
updateHotkeys();
|
||||||
|
|
@ -301,15 +256,8 @@ void Engine::updateFrontend() {
|
||||||
gui->postAct();
|
gui->postAct();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Engine::nextFrame() {
|
void Engine::nextFrame(bool waitForRefresh) {
|
||||||
window->setFramerate(
|
windowControl->nextFrame(waitForRefresh);
|
||||||
window->isIconified() && settings.display.limitFpsIconified.get()
|
|
||||||
? 20
|
|
||||||
: settings.display.framerate.get()
|
|
||||||
);
|
|
||||||
window->swapBuffers();
|
|
||||||
// todo: process it on another thread
|
|
||||||
input->pollEvents();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Engine::startPauseLoop() {
|
void Engine::startPauseLoop() {
|
||||||
|
|
@ -328,7 +276,7 @@ void Engine::startPauseLoop() {
|
||||||
if (isHeadless()) {
|
if (isHeadless()) {
|
||||||
platform::sleep(1.0 / params.tps * 1000);
|
platform::sleep(1.0 / params.tps * 1000);
|
||||||
} else {
|
} else {
|
||||||
nextFrame();
|
nextFrame(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (initialCursorLocked) {
|
if (initialCursorLocked) {
|
||||||
|
|
@ -399,17 +347,18 @@ void Engine::setLevelConsumer(OnWorldOpen levelConsumer) {
|
||||||
|
|
||||||
void Engine::loadAssets() {
|
void Engine::loadAssets() {
|
||||||
logger.info() << "loading assets";
|
logger.info() << "loading assets";
|
||||||
Shader::preprocessor->setPaths(&paths.resPaths);
|
Shader::preprocessor->setPaths(&paths->resPaths);
|
||||||
|
|
||||||
auto content = this->content->get();
|
auto content = this->content->get();
|
||||||
|
|
||||||
auto new_assets = std::make_unique<Assets>();
|
auto new_assets = std::make_unique<Assets>();
|
||||||
AssetsLoader loader(*this, *new_assets, paths.resPaths);
|
AssetsLoader loader(*this, *new_assets, paths->resPaths);
|
||||||
AssetsLoader::addDefaults(loader, content);
|
AssetsLoader::addDefaults(loader, content);
|
||||||
|
|
||||||
// no need
|
// no need
|
||||||
// correct log messages order is more useful
|
// correct log messages order is more useful
|
||||||
bool threading = false; // look at two upper lines
|
// todo: before setting to true, check if GLSLExtension thread safe
|
||||||
|
bool threading = false; // look at three upper lines
|
||||||
if (threading) {
|
if (threading) {
|
||||||
auto task = loader.startTask([=](){});
|
auto task = loader.startTask([=](){});
|
||||||
task->waitForEnd();
|
task->waitForEnd();
|
||||||
|
|
@ -446,6 +395,7 @@ void Engine::setScreen(std::shared_ptr<Screen> screen) {
|
||||||
}
|
}
|
||||||
if (project->clientScript && this->screen) {
|
if (project->clientScript && this->screen) {
|
||||||
project->clientScript->onScreenChange(this->screen->getName(), true);
|
project->clientScript->onScreenChange(this->screen->getName(), true);
|
||||||
|
window->setShouldRefresh();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -479,11 +429,11 @@ Assets* Engine::getAssets() {
|
||||||
}
|
}
|
||||||
|
|
||||||
EnginePaths& Engine::getPaths() {
|
EnginePaths& Engine::getPaths() {
|
||||||
return paths;
|
return *paths;
|
||||||
}
|
}
|
||||||
|
|
||||||
ResPaths& Engine::getResPaths() {
|
ResPaths& Engine::getResPaths() {
|
||||||
return paths.resPaths;
|
return paths->resPaths;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<Screen> Engine::getScreen() {
|
std::shared_ptr<Screen> Engine::getScreen() {
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,27 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "delegates.hpp"
|
#include "CoreParameters.hpp"
|
||||||
#include "typedefs.hpp"
|
|
||||||
#include "settings.hpp"
|
|
||||||
|
|
||||||
#include "io/engine_paths.hpp"
|
|
||||||
#include "io/settings_io.hpp"
|
|
||||||
#include "util/ObjectsKeeper.hpp"
|
|
||||||
#include "PostRunnables.hpp"
|
#include "PostRunnables.hpp"
|
||||||
#include "Time.hpp"
|
#include "Time.hpp"
|
||||||
|
#include "delegates.hpp"
|
||||||
|
#include "settings.hpp"
|
||||||
|
#include "typedefs.hpp"
|
||||||
|
#include "util/ObjectsKeeper.hpp"
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
class Window;
|
|
||||||
class Assets;
|
class Assets;
|
||||||
class Level;
|
|
||||||
class Screen;
|
|
||||||
class ContentControl;
|
class ContentControl;
|
||||||
class EngineController;
|
class EngineController;
|
||||||
|
class EnginePaths;
|
||||||
class Input;
|
class Input;
|
||||||
|
class Level;
|
||||||
|
class ResPaths;
|
||||||
|
class Screen;
|
||||||
|
class SettingsHandler;
|
||||||
|
class Window;
|
||||||
|
class WindowControl;
|
||||||
struct Project;
|
struct Project;
|
||||||
|
|
||||||
namespace gui {
|
namespace gui {
|
||||||
|
|
@ -44,24 +46,12 @@ public:
|
||||||
initialize_error(const std::string& message) : std::runtime_error(message) {}
|
initialize_error(const std::string& message) : std::runtime_error(message) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct CoreParameters {
|
|
||||||
bool headless = false;
|
|
||||||
bool testMode = false;
|
|
||||||
std::filesystem::path resFolder = "res";
|
|
||||||
std::filesystem::path userFolder = ".";
|
|
||||||
std::filesystem::path scriptFile;
|
|
||||||
std::filesystem::path projectFolder;
|
|
||||||
std::string debugServerString;
|
|
||||||
int tps = 20;
|
|
||||||
};
|
|
||||||
|
|
||||||
using OnWorldOpen = std::function<void(std::unique_ptr<Level>, int64_t)>;
|
using OnWorldOpen = std::function<void(std::unique_ptr<Level>, int64_t)>;
|
||||||
|
|
||||||
class Engine : public util::ObjectsKeeper {
|
class Engine : public util::ObjectsKeeper {
|
||||||
CoreParameters params;
|
CoreParameters params;
|
||||||
EngineSettings settings;
|
EngineSettings settings;
|
||||||
EnginePaths paths;
|
std::unique_ptr<EnginePaths> paths;
|
||||||
|
|
||||||
std::unique_ptr<Project> project;
|
std::unique_ptr<Project> project;
|
||||||
std::unique_ptr<SettingsHandler> settingsHandler;
|
std::unique_ptr<SettingsHandler> settingsHandler;
|
||||||
std::unique_ptr<Assets> assets;
|
std::unique_ptr<Assets> assets;
|
||||||
|
|
@ -75,6 +65,7 @@ class Engine : public util::ObjectsKeeper {
|
||||||
std::unique_ptr<gui::GUI> gui;
|
std::unique_ptr<gui::GUI> gui;
|
||||||
std::unique_ptr<devtools::Editor> editor;
|
std::unique_ptr<devtools::Editor> editor;
|
||||||
std::unique_ptr<devtools::DebuggingServer> debuggingServer;
|
std::unique_ptr<devtools::DebuggingServer> debuggingServer;
|
||||||
|
std::unique_ptr<WindowControl> windowControl;
|
||||||
PostRunnables postRunnables;
|
PostRunnables postRunnables;
|
||||||
Time time;
|
Time time;
|
||||||
OnWorldOpen levelConsumer;
|
OnWorldOpen levelConsumer;
|
||||||
|
|
@ -105,9 +96,10 @@ public:
|
||||||
|
|
||||||
void postUpdate();
|
void postUpdate();
|
||||||
|
|
||||||
|
void applicationTick();
|
||||||
void updateFrontend();
|
void updateFrontend();
|
||||||
void renderFrame();
|
void renderFrame();
|
||||||
void nextFrame();
|
void nextFrame(bool waitForRefresh);
|
||||||
void startPauseLoop();
|
void startPauseLoop();
|
||||||
|
|
||||||
/// @brief Set screen (scene).
|
/// @brief Set screen (scene).
|
||||||
|
|
@ -143,8 +135,6 @@ public:
|
||||||
postRunnables.postRunnable(callback);
|
postRunnables.postRunnable(callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
void saveScreenshot();
|
|
||||||
|
|
||||||
EngineController* getController();
|
EngineController* getController();
|
||||||
|
|
||||||
void setLevelConsumer(OnWorldOpen levelConsumer);
|
void setLevelConsumer(OnWorldOpen levelConsumer);
|
||||||
|
|
|
||||||
|
|
@ -1,44 +1,57 @@
|
||||||
#include "engine_paths.hpp"
|
#include "EnginePaths.hpp"
|
||||||
|
|
||||||
|
#include "debug/Logger.hpp"
|
||||||
|
#include "io/devices/StdfsDevice.hpp"
|
||||||
|
#include "io/devices/MemoryDevice.hpp"
|
||||||
|
#include "io/devices/ZipFileDevice.hpp"
|
||||||
|
#include "maths/util.hpp"
|
||||||
|
#include "typedefs.hpp"
|
||||||
|
#include "util/platform.hpp"
|
||||||
|
#include "util/random.hpp"
|
||||||
|
#include "util/stringutil.hpp"
|
||||||
|
#include "world/files/WorldFiles.hpp"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <array>
|
#include <array>
|
||||||
|
#include <chrono>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <stack>
|
#include <stack>
|
||||||
#include "typedefs.hpp"
|
#include <stdexcept>
|
||||||
#include "util/stringutil.hpp"
|
|
||||||
#include "util/platform.hpp"
|
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
|
||||||
#include "io/devices/StdfsDevice.hpp"
|
|
||||||
#include "io/devices/ZipFileDevice.hpp"
|
|
||||||
#include "world/files/WorldFiles.hpp"
|
|
||||||
#include "debug/Logger.hpp"
|
|
||||||
|
|
||||||
#include <chrono>
|
|
||||||
#include "maths/util.hpp"
|
|
||||||
|
|
||||||
template<int n>
|
|
||||||
static std::string generate_random_base64() {
|
|
||||||
auto now = std::chrono::high_resolution_clock::now();
|
|
||||||
auto seed = now.time_since_epoch().count();
|
|
||||||
|
|
||||||
util::PseudoRandom random(seed); // fixme: replace with safe random
|
|
||||||
ubyte bytes[n];
|
|
||||||
random.rand(bytes, n);
|
|
||||||
return util::base64_urlsafe_encode(bytes, n);
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace fs = std::filesystem;
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
static debug::Logger logger("engine-paths");
|
static std::random_device random_device;
|
||||||
|
|
||||||
static inline io::path SCREENSHOTS_FOLDER = "user:screenshots";
|
static inline io::path SCREENSHOTS_FOLDER = "user:screenshots";
|
||||||
static inline io::path CONTENT_FOLDER = "user:content";
|
static inline io::path CONTENT_FOLDER = "user:content";
|
||||||
static inline io::path WORLDS_FOLDER = "user:worlds";
|
static inline io::path WORLDS_FOLDER = "user:worlds";
|
||||||
|
|
||||||
void EnginePaths::prepare() {
|
static debug::Logger logger("engine-paths");
|
||||||
|
|
||||||
|
template<int n>
|
||||||
|
static std::string generate_random_base64() {
|
||||||
|
auto randomEngine = util::seeded_random_engine(random_device);
|
||||||
|
static std::uniform_int_distribution<integer_t> dist(0, 0xFF);
|
||||||
|
ubyte bytes[n];
|
||||||
|
for (size_t i = 0; i < n; i++) {
|
||||||
|
bytes[i] = dist(randomEngine);
|
||||||
|
}
|
||||||
|
return util::base64_urlsafe_encode(bytes, n);
|
||||||
|
}
|
||||||
|
|
||||||
|
EnginePaths::EnginePaths(CoreParameters& params)
|
||||||
|
: resourcesFolder(params.resFolder),
|
||||||
|
userFilesFolder(params.userFolder),
|
||||||
|
projectFolder(params.projectFolder) {
|
||||||
|
if (!params.scriptFile.empty()) {
|
||||||
|
scriptFolder = params.scriptFile.parent_path();
|
||||||
|
io::set_device("script", std::make_shared<io::StdfsDevice>(*scriptFolder));
|
||||||
|
}
|
||||||
|
|
||||||
io::set_device("res", std::make_shared<io::StdfsDevice>(resourcesFolder, false));
|
io::set_device("res", std::make_shared<io::StdfsDevice>(resourcesFolder, false));
|
||||||
io::set_device("user", std::make_shared<io::StdfsDevice>(userFilesFolder));
|
io::set_device("user", std::make_shared<io::StdfsDevice>(userFilesFolder));
|
||||||
|
io::set_device("project", std::make_shared<io::StdfsDevice>(projectFolder));
|
||||||
|
|
||||||
if (!io::is_directory("res:")) {
|
if (!io::is_directory("res:")) {
|
||||||
throw std::runtime_error(
|
throw std::runtime_error(
|
||||||
|
|
@ -59,15 +72,15 @@ void EnginePaths::prepare() {
|
||||||
io::create_subdevice("config", "user", "config");
|
io::create_subdevice("config", "user", "config");
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::filesystem::path& EnginePaths::getUserFilesFolder() const {
|
std::filesystem::path EnginePaths::getResourcesFolder() const {
|
||||||
return userFilesFolder;
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::filesystem::path& EnginePaths::getResourcesFolder() const {
|
|
||||||
return resourcesFolder;
|
return resourcesFolder;
|
||||||
}
|
}
|
||||||
|
|
||||||
io::path EnginePaths::getNewScreenshotFile(const std::string& ext) {
|
std::filesystem::path EnginePaths::getUserFilesFolder() const {
|
||||||
|
return userFilesFolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
io::path EnginePaths::getNewScreenshotFile(const std::string& ext) const {
|
||||||
auto folder = SCREENSHOTS_FOLDER;
|
auto folder = SCREENSHOTS_FOLDER;
|
||||||
if (!io::is_directory(folder)) {
|
if (!io::is_directory(folder)) {
|
||||||
io::create_directories(folder);
|
io::create_directories(folder);
|
||||||
|
|
@ -95,10 +108,6 @@ io::path EnginePaths::getWorldsFolder() const {
|
||||||
return WORLDS_FOLDER;
|
return WORLDS_FOLDER;
|
||||||
}
|
}
|
||||||
|
|
||||||
io::path EnginePaths::getCurrentWorldFolder() {
|
|
||||||
return currentWorldFolder;
|
|
||||||
}
|
|
||||||
|
|
||||||
io::path EnginePaths::getWorldFolderByName(const std::string& name) {
|
io::path EnginePaths::getWorldFolderByName(const std::string& name) {
|
||||||
return getWorldsFolder() / name;
|
return getWorldsFolder() / name;
|
||||||
}
|
}
|
||||||
|
|
@ -132,24 +141,6 @@ std::vector<io::path> EnginePaths::scanForWorlds() const {
|
||||||
return folders;
|
return folders;
|
||||||
}
|
}
|
||||||
|
|
||||||
void EnginePaths::setUserFilesFolder(std::filesystem::path folder) {
|
|
||||||
this->userFilesFolder = std::move(folder);
|
|
||||||
}
|
|
||||||
|
|
||||||
void EnginePaths::setResourcesFolder(std::filesystem::path folder) {
|
|
||||||
this->resourcesFolder = std::move(folder);
|
|
||||||
}
|
|
||||||
|
|
||||||
void EnginePaths::setScriptFolder(std::filesystem::path folder) {
|
|
||||||
io::set_device("script", std::make_shared<io::StdfsDevice>(folder));
|
|
||||||
this->scriptFolder = std::move(folder);
|
|
||||||
}
|
|
||||||
|
|
||||||
void EnginePaths::setProjectFolder(std::filesystem::path folder) {
|
|
||||||
io::set_device("project", std::make_shared<io::StdfsDevice>(folder));
|
|
||||||
this->projectFolder = std::move(folder);
|
|
||||||
}
|
|
||||||
|
|
||||||
void EnginePaths::setCurrentWorldFolder(io::path folder) {
|
void EnginePaths::setCurrentWorldFolder(io::path folder) {
|
||||||
if (folder.empty()) {
|
if (folder.empty()) {
|
||||||
io::remove_device("world");
|
io::remove_device("world");
|
||||||
|
|
@ -186,6 +177,18 @@ void EnginePaths::unmount(const std::string& name) {
|
||||||
mounted.erase(found);
|
mounted.erase(found);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string EnginePaths::createMemoryDevice() {
|
||||||
|
auto device = std::make_unique<io::MemoryDevice>();
|
||||||
|
std::string name;
|
||||||
|
do {
|
||||||
|
name = std::string("W.") + generate_random_base64<6>();
|
||||||
|
} while (std::find(mounted.begin(), mounted.end(), name) != mounted.end());
|
||||||
|
|
||||||
|
io::set_device(name, std::move(device));
|
||||||
|
mounted.push_back(name);
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
std::string EnginePaths::createWriteableDevice(const std::string& name) {
|
std::string EnginePaths::createWriteableDevice(const std::string& name) {
|
||||||
const auto& found = writeables.find(name);
|
const auto& found = writeables.find(name);
|
||||||
if (found != writeables.end()) {
|
if (found != writeables.end()) {
|
||||||
|
|
@ -1,15 +1,15 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "io/io.hpp"
|
||||||
|
#include "data/dv.hpp"
|
||||||
|
#include "CoreParameters.hpp"
|
||||||
|
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <stdexcept>
|
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <tuple>
|
#include <tuple>
|
||||||
|
|
||||||
#include "io.hpp"
|
|
||||||
#include "data/dv.hpp"
|
|
||||||
|
|
||||||
struct PathsRoot {
|
struct PathsRoot {
|
||||||
std::string name;
|
std::string name;
|
||||||
io::path path;
|
io::path path;
|
||||||
|
|
@ -46,29 +46,22 @@ class EnginePaths {
|
||||||
public:
|
public:
|
||||||
ResPaths resPaths;
|
ResPaths resPaths;
|
||||||
|
|
||||||
void prepare();
|
EnginePaths(CoreParameters& params);
|
||||||
|
|
||||||
void setUserFilesFolder(std::filesystem::path folder);
|
std::filesystem::path getResourcesFolder() const;
|
||||||
const std::filesystem::path& getUserFilesFolder() const;
|
std::filesystem::path getUserFilesFolder() const;
|
||||||
|
|
||||||
void setResourcesFolder(std::filesystem::path folder);
|
|
||||||
const std::filesystem::path& getResourcesFolder() const;
|
|
||||||
|
|
||||||
void setScriptFolder(std::filesystem::path folder);
|
|
||||||
|
|
||||||
void setProjectFolder(std::filesystem::path folder);
|
|
||||||
|
|
||||||
io::path getWorldFolderByName(const std::string& name);
|
io::path getWorldFolderByName(const std::string& name);
|
||||||
io::path getWorldsFolder() const;
|
io::path getWorldsFolder() const;
|
||||||
|
|
||||||
void setCurrentWorldFolder(io::path folder);
|
void setCurrentWorldFolder(io::path folder);
|
||||||
io::path getCurrentWorldFolder();
|
io::path getNewScreenshotFile(const std::string& ext) const;
|
||||||
io::path getNewScreenshotFile(const std::string& ext);
|
|
||||||
|
|
||||||
std::string mount(const io::path& file);
|
std::string mount(const io::path& file);
|
||||||
void unmount(const std::string& name);
|
void unmount(const std::string& name);
|
||||||
|
|
||||||
std::string createWriteableDevice(const std::string& name);
|
std::string createWriteableDevice(const std::string& name);
|
||||||
|
std::string createMemoryDevice();
|
||||||
|
|
||||||
void setEntryPoints(std::vector<PathsRoot> entryPoints);
|
void setEntryPoints(std::vector<PathsRoot> entryPoints);
|
||||||
|
|
||||||
|
|
@ -80,9 +73,9 @@ public:
|
||||||
static inline io::path CONTROLS_FILE = "user:controls.toml";
|
static inline io::path CONTROLS_FILE = "user:controls.toml";
|
||||||
static inline io::path SETTINGS_FILE = "user:settings.toml";
|
static inline io::path SETTINGS_FILE = "user:settings.toml";
|
||||||
private:
|
private:
|
||||||
std::filesystem::path userFilesFolder {"."};
|
std::filesystem::path resourcesFolder;
|
||||||
std::filesystem::path resourcesFolder {"res"};
|
std::filesystem::path userFilesFolder;
|
||||||
std::filesystem::path projectFolder = resourcesFolder;
|
std::filesystem::path projectFolder;
|
||||||
io::path currentWorldFolder;
|
io::path currentWorldFolder;
|
||||||
std::optional<std::filesystem::path> scriptFolder;
|
std::optional<std::filesystem::path> scriptFolder;
|
||||||
std::vector<PathsRoot> entryPoints;
|
std::vector<PathsRoot> entryPoints;
|
||||||
|
|
@ -18,6 +18,7 @@ Mainloop::Mainloop(Engine& engine) : engine(engine) {
|
||||||
void Mainloop::run() {
|
void Mainloop::run() {
|
||||||
auto& time = engine.getTime();
|
auto& time = engine.getTime();
|
||||||
auto& window = engine.getWindow();
|
auto& window = engine.getWindow();
|
||||||
|
auto& settings = engine.getSettings();
|
||||||
|
|
||||||
engine.setLevelConsumer([this](auto level, int64_t localPlayer) {
|
engine.setLevelConsumer([this](auto level, int64_t localPlayer) {
|
||||||
if (level == nullptr) {
|
if (level == nullptr) {
|
||||||
|
|
@ -38,13 +39,17 @@ void Mainloop::run() {
|
||||||
logger.info() << "main loop started";
|
logger.info() << "main loop started";
|
||||||
while (!window.isShouldClose()){
|
while (!window.isShouldClose()){
|
||||||
time.update(window.time());
|
time.update(window.time());
|
||||||
|
engine.applicationTick();
|
||||||
engine.updateFrontend();
|
engine.updateFrontend();
|
||||||
|
|
||||||
if (!window.isIconified()) {
|
if (!window.isIconified()) {
|
||||||
engine.renderFrame();
|
engine.renderFrame();
|
||||||
}
|
}
|
||||||
engine.postUpdate();
|
engine.postUpdate();
|
||||||
engine.nextFrame();
|
engine.nextFrame(
|
||||||
|
settings.display.adaptiveFpsInMenu.get() &&
|
||||||
|
dynamic_cast<const MenuScreen*>(engine.getScreen().get()) != nullptr
|
||||||
|
);
|
||||||
}
|
}
|
||||||
logger.info() << "main loop stopped";
|
logger.info() << "main loop stopped";
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
#include "ServerMainloop.hpp"
|
#include "ServerMainloop.hpp"
|
||||||
|
|
||||||
#include "Engine.hpp"
|
#include "Engine.hpp"
|
||||||
|
#include "EnginePaths.hpp"
|
||||||
#include "logic/scripting/scripting.hpp"
|
#include "logic/scripting/scripting.hpp"
|
||||||
#include "logic/LevelController.hpp"
|
#include "logic/LevelController.hpp"
|
||||||
#include "interfaces/Process.hpp"
|
#include "interfaces/Process.hpp"
|
||||||
|
|
@ -32,8 +33,7 @@ void ServerMainloop::run() {
|
||||||
setLevel(std::move(level));
|
setLevel(std::move(level));
|
||||||
});
|
});
|
||||||
|
|
||||||
logger.info() << "starting test " << coreParams.scriptFile.string();
|
auto process = scripting::start_app_script(
|
||||||
auto process = scripting::start_coroutine(
|
|
||||||
"script:" + coreParams.scriptFile.filename().u8string()
|
"script:" + coreParams.scriptFile.filename().u8string()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -61,6 +61,7 @@ void ServerMainloop::run() {
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
engine.applicationTick();
|
||||||
engine.postUpdate();
|
engine.postUpdate();
|
||||||
|
|
||||||
if (!coreParams.testMode) {
|
if (!coreParams.testMode) {
|
||||||
|
|
|
||||||
94
src/engine/WindowControl.cpp
Normal file
94
src/engine/WindowControl.cpp
Normal file
|
|
@ -0,0 +1,94 @@
|
||||||
|
#include "WindowControl.hpp"
|
||||||
|
|
||||||
|
#include "Engine.hpp"
|
||||||
|
#include "engine/EnginePaths.hpp"
|
||||||
|
#include "devtools/Project.hpp"
|
||||||
|
#include "coders/imageio.hpp"
|
||||||
|
#include "window/Window.hpp"
|
||||||
|
#include "window/input.hpp"
|
||||||
|
#include "debug/Logger.hpp"
|
||||||
|
#include "graphics/core/ImageData.hpp"
|
||||||
|
#include "util/platform.hpp"
|
||||||
|
|
||||||
|
static debug::Logger logger("window-control");
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
static std::unique_ptr<ImageData> load_icon() {
|
||||||
|
try {
|
||||||
|
auto file = "res:textures/misc/icon.png";
|
||||||
|
if (io::exists(file)) {
|
||||||
|
return imageio::read(file);
|
||||||
|
}
|
||||||
|
} catch (const std::exception& err) {
|
||||||
|
logger.error() << "could not load window icon: " << err.what();
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
WindowControl::WindowControl(Engine& engine) : engine(engine) {}
|
||||||
|
|
||||||
|
WindowControl::Result WindowControl::initialize() {
|
||||||
|
const auto& project = engine.getProject();
|
||||||
|
auto& settings = engine.getSettings();
|
||||||
|
|
||||||
|
std::string title = project.title;
|
||||||
|
if (!title.empty()) {
|
||||||
|
title += " - ";
|
||||||
|
}
|
||||||
|
title += "VoxelCore v" +
|
||||||
|
std::to_string(ENGINE_VERSION_MAJOR) + "." +
|
||||||
|
std::to_string(ENGINE_VERSION_MINOR);
|
||||||
|
if (ENGINE_DEBUG_BUILD) {
|
||||||
|
title += " [debug]";
|
||||||
|
}
|
||||||
|
if (engine.getDebuggingServer()) {
|
||||||
|
title = "[debugging] " + title;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto [window, input] = Window::initialize(&settings.display, title);
|
||||||
|
if (!window || !input){
|
||||||
|
throw initialize_error("could not initialize window");
|
||||||
|
}
|
||||||
|
window->setFramerate(settings.display.framerate.get());
|
||||||
|
if (auto icon = load_icon()) {
|
||||||
|
icon->flipY();
|
||||||
|
window->setIcon(icon.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result {std::move(window), std::move(input)};
|
||||||
|
}
|
||||||
|
|
||||||
|
void WindowControl::saveScreenshot() {
|
||||||
|
auto& window = engine.getWindow();
|
||||||
|
const auto& paths = engine.getPaths();
|
||||||
|
|
||||||
|
auto image = window.takeScreenshot();
|
||||||
|
image->flipY();
|
||||||
|
io::path filename = paths.getNewScreenshotFile("png");
|
||||||
|
imageio::write(filename.string(), image.get());
|
||||||
|
logger.info() << "saved screenshot as " << filename.string();
|
||||||
|
}
|
||||||
|
|
||||||
|
void WindowControl::toggleFullscreen() {
|
||||||
|
auto& settings = engine.getSettings();
|
||||||
|
auto& windowMode = settings.display.windowMode;
|
||||||
|
if (windowMode.get() != static_cast<int>(WindowMode::FULLSCREEN)) {
|
||||||
|
windowMode.set(static_cast<int>(WindowMode::FULLSCREEN));
|
||||||
|
} else {
|
||||||
|
windowMode.set(static_cast<int>(WindowMode::WINDOWED));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WindowControl::nextFrame(bool waitForRefresh) {
|
||||||
|
const auto& settings = engine.getSettings();
|
||||||
|
auto& window = engine.getWindow();
|
||||||
|
auto& input = engine.getInput();
|
||||||
|
window.setFramerate(
|
||||||
|
window.isIconified() && settings.display.limitFpsIconified.get()
|
||||||
|
? 20
|
||||||
|
: settings.display.framerate.get()
|
||||||
|
);
|
||||||
|
window.swapBuffers();
|
||||||
|
input.pollEvents(waitForRefresh && !window.checkShouldRefresh());
|
||||||
|
}
|
||||||
26
src/engine/WindowControl.hpp
Normal file
26
src/engine/WindowControl.hpp
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
class Window;
|
||||||
|
class Input;
|
||||||
|
class Engine;
|
||||||
|
|
||||||
|
class WindowControl {
|
||||||
|
public:
|
||||||
|
struct Result {
|
||||||
|
std::unique_ptr<Window> window;
|
||||||
|
std::unique_ptr<Input> input;
|
||||||
|
};
|
||||||
|
WindowControl(Engine& engine);
|
||||||
|
|
||||||
|
Result initialize();
|
||||||
|
|
||||||
|
void nextFrame(bool waitForRefresh);
|
||||||
|
|
||||||
|
void saveScreenshot();
|
||||||
|
|
||||||
|
void toggleFullscreen();
|
||||||
|
private:
|
||||||
|
Engine& engine;
|
||||||
|
};
|
||||||
|
|
@ -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;
|
ContentGfxCache::~ContentGfxCache() = default;
|
||||||
|
|
||||||
const model::Model& ContentGfxCache::getModel(blockid_t id) const {
|
const model::Model& ContentGfxCache::getModel(blockid_t id, uint8_t variant) const {
|
||||||
const auto& found = models.find(id);
|
const auto& found = models.find(modelKey(id, variant));
|
||||||
if (found == models.end()) {
|
if (found == models.end()) {
|
||||||
throw std::runtime_error("model not found");
|
throw std::runtime_error("model not found");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
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