Merge branch 'dev' of https://github.com/Xertis/VoxelEngine-Cpp into dev
This commit is contained in:
commit
86053fa86d
167
CHANGELOG.md
167
CHANGELOG.md
@ -1,74 +1,131 @@
|
||||
# 0.28 - 2025.07.18
|
||||
# 0.29 - 2025.09.20
|
||||
|
||||
[Documentation](https://github.com/MihailRis/VoxelEngine-Cpp/tree/release-0.28/doc/en/main-page.md) for 0.28
|
||||
[Documentation](https://github.com/MihailRis/VoxelEngine-Cpp/tree/release-0.29/doc/en/main-page.md) for 0.29
|
||||
|
||||
Table of contents:
|
||||
|
||||
- [Added](#added)
|
||||
- [Changes](#changes)
|
||||
- [Functions](#functions)
|
||||
- [Changes](#changes)
|
||||
- [Fixes](#fixes)
|
||||
|
||||
## Added
|
||||
|
||||
- advanced graphics mode
|
||||
- state bits based models
|
||||
- post-effects
|
||||
- ui elements:
|
||||
- iframe
|
||||
- select
|
||||
- modelviewer
|
||||
- vcm models format
|
||||
- bit.compile
|
||||
- yaml encoder/decoder
|
||||
- error handler argument in http.get, http.post
|
||||
- ui properties:
|
||||
- image.region
|
||||
- rotation profiles:
|
||||
- stairs
|
||||
- libraries
|
||||
- gfx.posteffects
|
||||
- yaml
|
||||
- stairs rotation profile
|
||||
- models editing in console
|
||||
- syntax highlighting: xml, glsl, vcm
|
||||
- beginning of projects system
|
||||
- pathfinding
|
||||
- components:
|
||||
- core:pathfinding
|
||||
- core:player
|
||||
- core:mob
|
||||
- libraries:
|
||||
- random
|
||||
- gfx.skeletons
|
||||
- (documented) assets
|
||||
- udp support
|
||||
- schedules
|
||||
- events:
|
||||
- on_physics_update (components)
|
||||
- on_block_tick(x, y, z, tps) (blocks)
|
||||
- custom hand controller
|
||||
- http headers
|
||||
- named pipes
|
||||
- optimizations:
|
||||
- speed up block.set
|
||||
- speed up vectors
|
||||
- items description
|
||||
- item properties methods
|
||||
- tab + shift+tab
|
||||
- blocks, items tags
|
||||
- pack dependencies versions
|
||||
- ~~allow to disable autospawn position~~ use player.set_spawnpoint
|
||||
- entity.spawn command
|
||||
- project script
|
||||
- gui.root document
|
||||
- time.schedules.world.common: Schedule
|
||||
|
||||
### Changes
|
||||
|
||||
- reserved 'project', 'pack', 'packid', 'root' entry points
|
||||
- Bytearray optimized with FFI
|
||||
- chunks non-unloading zone limited with circle
|
||||
- app.sleep_until - added 'timeout argument'
|
||||
- network.get / post - added 'data' argument to error callback
|
||||
- autorefresh model preview
|
||||
- move player controls to lua
|
||||
- move hand control to lua
|
||||
|
||||
### Functions
|
||||
|
||||
- yaml.tostring
|
||||
- yaml.parse
|
||||
- gfx.posteffects.index
|
||||
- gfx.posteffects.set_effect
|
||||
- gfx.posteffects.get_intensity
|
||||
- gfx.posteffects.set_intensity
|
||||
- gfx.posteffects.is_active
|
||||
- gfx.posteffects.set_params
|
||||
- gfx.posteffects.set_array
|
||||
- block.get_variant
|
||||
- block.set_variant
|
||||
- bit.compile
|
||||
- Bytearray_as_string
|
||||
- block.model_name
|
||||
- block.has_tag
|
||||
- item.has_tag
|
||||
- item.description
|
||||
- base64.encode_urlsafe
|
||||
- base64.decode_urlsafe
|
||||
- vec2.rotate
|
||||
- vecn.distance
|
||||
- vecn.mix
|
||||
- rigidbody:get_vdamping
|
||||
- rigidbody:set_vdamping
|
||||
- entity:require_component
|
||||
- network.udp_connect
|
||||
- random.random
|
||||
- random.bytes
|
||||
- random.uuid
|
||||
- Random:random
|
||||
- Random:seed
|
||||
- hud.hand_controller
|
||||
- inventory.get_caption
|
||||
- inventory.set_caption
|
||||
- inventory.get_description
|
||||
- inventory.set_description
|
||||
- pathfinding.create_agent
|
||||
- pathfinding.remove_agent
|
||||
- pathfinding.set_enabled
|
||||
- pathfinding.is_enabled
|
||||
- pathfinding.make_route
|
||||
- pathfinding.make_route_async
|
||||
- pathfinding.pull_route
|
||||
- pathfinding.set_max_visited
|
||||
- pathfinding.avoid_tag
|
||||
- gfx.skeletons.get
|
||||
- Skeleton:index
|
||||
- Skeleton:get_model
|
||||
- Skeleton:set_model
|
||||
- Skeleton:get_matrix
|
||||
- Skeleton:set_matrix
|
||||
- Skeleton:get_texture
|
||||
- Skeleton:set_texture
|
||||
- Skeleton:is_visible
|
||||
- Skeleton:set_visible
|
||||
- Skeleton:get_color
|
||||
- Skeleton:set_color
|
||||
- Schedule:set_timeout(time_ms, callback)
|
||||
- Schedule:set_interval(interval_ms, callback, [optional] repetions): int
|
||||
- Schedule:remove_interval(id)
|
||||
- ScheduleGroup:publish(schedule: Schedule)
|
||||
|
||||
## Fixes
|
||||
|
||||
- [fix: "unknown argument --memcheck" in vctest](https://github.com/MihailRis/voxelcore/commit/281d5e09e6f1c016646af6000f6b111695c994b3)
|
||||
- [fix "upgrade square is not fully inside of area" error](https://github.com/MihailRis/voxelcore/commit/bf79f6bc75a7686d59fdd0dba8b9018d6191e980 )
|
||||
- [fix generator area centering](https://github.com/MihailRis/voxelcore/commit/98813472a8c25b1de93dd5d843af38c5aec9b1d8 "fix generator area centering")
|
||||
- [fix incomplete content reset](https://github.com/MihailRis/voxelcore/commit/61af8ba943a24f6544c6482def2e244cf0af4d18)
|
||||
- [fix stack traces](https://github.com/MihailRis/voxelcore/commit/05ddffb5c9902e237c73cdea55d4ac1e303c6a8e)
|
||||
- [fix containers refreshing](https://github.com/MihailRis/voxelcore/commit/34295faca276b55c6e3c0ddd98b867a0aab3eb2a)
|
||||
- [fix toml encoder](https://github.com/MihailRis/voxelcore/commit/9cd95bb0eb73521bef07f6f0d5e8b78f3e309ebf)
|
||||
- [fix InputBindBox](https://github.com/MihailRis/voxelcore/commit/7c976a573b01e3fb6f43bacaab22e34037b55b73 "fix InputBindBox")
|
||||
- [fix inventory.* functions error messages](https://github.com/MihailRis/voxelcore/commit/af3c315c04959eea6c11f5ae2854a6f253e3450f)
|
||||
- [fix: validator not called after backspace](https://github.com/MihailRis/voxelcore/commit/df3640978d279b85653d647facb26ef15c509848)
|
||||
- [fix: missing pack.has_indices if content is not loaded](https://github.com/MihailRis/voxelcore/commit/b02b45457322e1ce8f6b9735caeb5b58b1e2ffb4)
|
||||
- [fix: entities despawn on F5](https://github.com/MihailRis/voxelcore/commit/6ab48fda935f3f1d97d76a833c8511522857ba6a)
|
||||
- [bug fix [#549]](https://github.com/MihailRis/voxelcore/commit/49727ec02647e48323266fbf814c15f6d5632ee9)
|
||||
- [fix player camera zoom with fov-effects disabled](https://github.com/MihailRis/voxelcore/commit/014ffab183687ed9acbb93ab90e43d8f82ed826a)
|
||||
- fix 3d text position / culling
|
||||
- fix fragment:place rotation (#593)
|
||||
- fix server socket creation in macos
|
||||
- fix: base packs not scanned for app scripts
|
||||
- fix lua::getfield and events registering
|
||||
- fix UIDocument::rebuildIndices
|
||||
- fix input library in headless mode
|
||||
- fix rigidbody:set_gravity_scale
|
||||
- fix extended blocks destruction particles spawn spread, offset
|
||||
- fix shaders recompiling
|
||||
- fix: C++ vecn functions precision loss
|
||||
- fix coroutines errors handling
|
||||
- fix: viewport size on toggle fullscreen
|
||||
- fix: fullscreen monitor refresh rate
|
||||
- fix: content menu panel height
|
||||
- fix generation.create_fragment (#596)
|
||||
- fix bytearray:insert (#594)
|
||||
- fix: script overriding
|
||||
- fix: hud.close after hud.show_overlay bug
|
||||
- fix: 'cannot resume dead coroutine' (#569)
|
||||
- fix: skybox is not visible behind translucent blocks
|
||||
- fix: sampler arrays inbdexed with non-constant / uniform-based expressions are forbidden
|
||||
- fix initial weather intensity
|
||||
- fix drop count (560)
|
||||
- fix BasicParser::parseNumber() out of range (560)
|
||||
- fix rotation interpolation (#557)
|
||||
|
||||
11
dev/tests/network_http.lua
Normal file
11
dev/tests/network_http.lua
Normal file
@ -0,0 +1,11 @@
|
||||
local response_received = false
|
||||
|
||||
network.get("https://api.github.com/repos/MihailRis/VoxelEngine-Cpp/releases/latest", function (s)
|
||||
print(json.parse(s).name)
|
||||
response_received = true
|
||||
end, function (code)
|
||||
print("repond with code", code)
|
||||
response_received = true
|
||||
end)
|
||||
|
||||
app.sleep_until(function () return response_received end, nil, 10)
|
||||
45
dev/tests/network_tcp.lua
Normal file
45
dev/tests/network_tcp.lua
Normal file
@ -0,0 +1,45 @@
|
||||
for i=1,3 do
|
||||
print(string.format("iteration %s", i + 1))
|
||||
local text = ""
|
||||
local complete = false
|
||||
|
||||
for j=1,100 do
|
||||
text = text .. math.random(0, 9)
|
||||
end
|
||||
|
||||
local server = network.tcp_open(7645, function (client)
|
||||
print("client connected")
|
||||
start_coroutine(function()
|
||||
print("client-listener started")
|
||||
local received_text = ""
|
||||
while client:is_alive() and #received_text < #text do
|
||||
local received = client:recv(512)
|
||||
if received then
|
||||
received_text = received_text .. utf8.tostring(received)
|
||||
print(string.format("received %s byte(s) from client", #received))
|
||||
end
|
||||
coroutine.yield()
|
||||
end
|
||||
asserts.equals (text, received_text)
|
||||
complete = true
|
||||
end, "client-listener")
|
||||
end)
|
||||
|
||||
network.tcp_connect("localhost", 7645, function (socket)
|
||||
print("connected to server")
|
||||
start_coroutine(function()
|
||||
print("data-sender started")
|
||||
local ptr = 1
|
||||
while ptr <= #text do
|
||||
local n = math.random(1, 20)
|
||||
socket:send(string.sub(text, ptr, ptr + n - 1))
|
||||
print(string.format("sent %s byte(s) to server", n))
|
||||
ptr = ptr + n
|
||||
end
|
||||
socket:close()
|
||||
end, "data-sender")
|
||||
end)
|
||||
|
||||
app.sleep_until(function () return complete end, nil, 5)
|
||||
server:close()
|
||||
end
|
||||
@ -297,3 +297,29 @@ Methods are used to manage the overwriting of properties when extending a block
|
||||
### `property_name@append`
|
||||
|
||||
Adds elements to the end of the list instead of completely overwriting it.
|
||||
|
||||
## Tags
|
||||
|
||||
Tags allow you to designate general properties of blocks. Names should be formatted as `prefix:tag_name`.
|
||||
The prefix is optional, but helps avoid unwanted logical collisions. Example:
|
||||
|
||||
```json
|
||||
{
|
||||
"tags": [
|
||||
"core:ore",
|
||||
"base_survival:food",
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Block tags can also be added from other packs using the `your_pack:tags.toml` file. Example:
|
||||
|
||||
```toml
|
||||
"prefix:tag_name" = [
|
||||
"random_pack:some_block",
|
||||
"another_pack:item",
|
||||
]
|
||||
"other_prefix:other_tag_name" = [
|
||||
# ...
|
||||
]
|
||||
``
|
||||
|
||||
@ -30,6 +30,11 @@ If prefix is not specified, '!' level will be used.
|
||||
|
||||
Example: '~randutil' - weak dependency 'randutil'.
|
||||
|
||||
Dependency version is indicated after '@' symbol and have operators to restrict acceptable versions.
|
||||
If version is not specified, '\*' (any) version will be used.
|
||||
|
||||
Example: 'randutil@>=1.0' - dependency 'randutil' which requires version 1.0 or newer.
|
||||
|
||||
Example:
|
||||
```json
|
||||
{
|
||||
|
||||
@ -20,6 +20,22 @@ Example:
|
||||
]
|
||||
```
|
||||
|
||||
You can pass values in ARGS from the entity configuration.
|
||||
They will be passed both when creating a new entity and when loading a saved one.
|
||||
The `args` list is used for this:
|
||||
|
||||
```json
|
||||
"components": [
|
||||
{
|
||||
"name": "base:drop",
|
||||
"args": {
|
||||
"item": "base:stone.item",
|
||||
"count": 1
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
The components code should be in `scripts/components`.
|
||||
|
||||
## Physics
|
||||
|
||||
@ -66,3 +66,29 @@ Property status is displayed in the inventory interface. Display method is defin
|
||||
- `number` - number
|
||||
- `relation` - current value to initial value (x/y)
|
||||
- `vbar` - vertical scale (used by default)
|
||||
|
||||
## Tags
|
||||
|
||||
Tags allow you to designate general properties of items. Names should be formatted as `prefix:tag_name`.
|
||||
The prefix is optional, but helps avoid unwanted logical collisions. Example:
|
||||
|
||||
```json
|
||||
{
|
||||
"tags": [
|
||||
"core:fuel",
|
||||
"base_survival:poison",
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Tags can also be added to items from other packs using the `your_pack:tags.toml` file. Example
|
||||
|
||||
```toml
|
||||
"prefix:tag_name" = [
|
||||
"random_pack:item",
|
||||
"another_pack:some_block",
|
||||
]
|
||||
"other_prefix:other_tag_name" = [
|
||||
# ...
|
||||
]
|
||||
```
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
# Documentation
|
||||
|
||||
Documentation for 0.29 (in-development).
|
||||
Documentation for 0.30 (in development).
|
||||
|
||||
[Documentation for release 0.28](https://github.com/MihailRis/voxelcore/blob/release-0.28/doc/en/main-page.md)
|
||||
[Documentation for 0.29.](https://github.com/MihailRis/voxelcore/blob/release-0.29/doc/ru/main-page.md)
|
||||
|
||||
## Sections
|
||||
|
||||
|
||||
@ -32,8 +32,10 @@ Subsections:
|
||||
- [mat4](scripting/builtins/libmat4.md)
|
||||
- [network](scripting/builtins/libnetwork.md)
|
||||
- [pack](scripting/builtins/libpack.md)
|
||||
- [pathfinding](scripting/builtins/libpathfinding.md)
|
||||
- [player](scripting/builtins/libplayer.md)
|
||||
- [quat](scripting/builtins/libquat.md)
|
||||
- [random](scripting/builtins/librandom.md)
|
||||
- [rules](scripting/builtins/librules.md)
|
||||
- [time](scripting/builtins/libtime.md)
|
||||
- [utf8](scripting/builtins/libutf8.md)
|
||||
|
||||
@ -25,9 +25,12 @@ Waits for the specified time in seconds, performing the main engine loop.
|
||||
app.sleep_until(
|
||||
-- function that checks the condition for ending the wait
|
||||
predicate: function() -> bool,
|
||||
-- the maximum number of engine loop ticks after which
|
||||
-- maximum number of engine loop ticks after which
|
||||
-- a "max ticks exceed" exception will be thrown
|
||||
[optional] max_ticks = 1e9
|
||||
[optional] max_ticks = 1e9,
|
||||
-- maximum wait time in seconds.
|
||||
-- (works with system time, including test mode)
|
||||
[optional] timeout = 1e9
|
||||
)
|
||||
```
|
||||
|
||||
|
||||
@ -8,4 +8,10 @@ base64.encode(bytes: table|ByteArray) -> str
|
||||
|
||||
-- Decode base64 string to ByteArray or lua table if second argument is set to true
|
||||
base64.decode(base64string: str, [optional]usetable: bool=false) -> table|ByteArray
|
||||
|
||||
-- Encode bytes to urlsafe-base64 string ('-', '_' instead of '+', '/')
|
||||
base64.encode_urlsafe(bytes: table|ByteArray) -> str
|
||||
|
||||
-- Decodes urlsafe-base64 string to a ByteArray or a table of numbers if the second argument is set to true
|
||||
base64.decode_urlsafe(base64string: str, [optional]usetable: bool=false) -> table|ByteArray
|
||||
```
|
||||
|
||||
@ -68,6 +68,9 @@ block.get_variant(x: int, y: int, z: int) -> int
|
||||
|
||||
-- Sets the block variant by index
|
||||
block.set_variant(x: int, y: int, z: int, index: int) -> int
|
||||
|
||||
-- Checks if an block has specified tag
|
||||
block.has_tag(id: int, tag: str) -> bool
|
||||
```
|
||||
|
||||
## Rotation
|
||||
|
||||
@ -33,4 +33,7 @@ item.emission(itemid: int) -> str
|
||||
|
||||
-- Returns the value of the `uses` property
|
||||
item.uses(itemid: int) -> int
|
||||
|
||||
-- Checks if an item has specified tag
|
||||
item.has_tag(itemid: int, tag: str) -> bool
|
||||
```
|
||||
|
||||
@ -6,9 +6,15 @@ A library for working with the network.
|
||||
|
||||
```lua
|
||||
-- Performs a GET request to the specified URL.
|
||||
-- After receiving the response, passes the text to the callback function.
|
||||
-- In case of an error, the HTTP response code will be passed to onfailure.
|
||||
network.get(url: str, callback: function(str), [optional] onfailure: function(int))
|
||||
network.get(
|
||||
url: str,
|
||||
-- Function to call when response is received
|
||||
callback: function(str),
|
||||
-- Error handler
|
||||
[optional] onfailure: function(int, str),
|
||||
-- List of additional request headers
|
||||
[optional] headers: table<str>
|
||||
)
|
||||
|
||||
-- Example:
|
||||
network.get("https://api.github.com/repos/MihailRis/VoxelEngine-Cpp/releases/latest", function (s)
|
||||
@ -16,13 +22,28 @@ network.get("https://api.github.com/repos/MihailRis/VoxelEngine-Cpp/releases/lat
|
||||
end)
|
||||
|
||||
-- A variant for binary files, with a byte array instead of a string in the response.
|
||||
network.get_binary(url: str, callback: function(table|ByteArray), [optional] onfailure: function(int))
|
||||
network.get_binary(
|
||||
url: str,
|
||||
callback: function(ByteArray),
|
||||
[optional] onfailure: function(int, str),
|
||||
[optional] headers: table<str>
|
||||
)
|
||||
|
||||
-- Performs a POST request to the specified URL.
|
||||
-- Currently, only `Content-Type: application/json` is supported
|
||||
-- After receiving the response, passes the text to the callback function.
|
||||
-- In case of an error, the HTTP response code will be passed to onfailure.
|
||||
network.post(url: str, data: table, callback: function(str), [optional] onfailure: function(int))
|
||||
network.post(
|
||||
url: str,
|
||||
-- Request body as a table (will be converted to JSON) or string
|
||||
body: table|str,
|
||||
-- Function called when response is received
|
||||
callback: function(str),
|
||||
-- Error handler
|
||||
[optional] onfailure: function(int, str),
|
||||
-- List of additional request headers
|
||||
[optional] headers: table<str>
|
||||
)
|
||||
```
|
||||
|
||||
## TCP Connections
|
||||
|
||||
66
doc/en/scripting/builtins/libpathfinding.md
Normal file
66
doc/en/scripting/builtins/libpathfinding.md
Normal file
@ -0,0 +1,66 @@
|
||||
# *pathfinding* library
|
||||
|
||||
The *pathfinding* library provides functions for working with the pathfinding system in the game world. It allows you to create and manage agents finding routes between points in the world.
|
||||
|
||||
When used in entity logic, the `core:pathfinding` component should be used.
|
||||
|
||||
## `core:pathfinding` component
|
||||
|
||||
```lua
|
||||
local pf = entity:get_component("core:pathfinding")
|
||||
|
||||
--- ...
|
||||
local x = ...
|
||||
local y = ...
|
||||
local z = ...
|
||||
|
||||
--- Set the target for the agent
|
||||
pf.set_target({x, y, z})
|
||||
|
||||
--- Get the current target of the agent
|
||||
local target = pf.get_target() --> vec3 or nil
|
||||
--- ...
|
||||
|
||||
--- Get the current route of the agent
|
||||
local route = pf.get_route() --> table<vec3> or nil
|
||||
--- ...
|
||||
```
|
||||
|
||||
## Library functions
|
||||
|
||||
```lua
|
||||
--- Create a new agent. Returns the ID of the created agent
|
||||
local agent = pathfinding.create_agent() --> int
|
||||
|
||||
--- Delete an agent by ID. Returns true if the agent existed, otherwise false
|
||||
pathfinding.remove_agent(agent: int) --> bool
|
||||
|
||||
--- Set the agent state (enabled/disabled)
|
||||
pathfinding.set_enabled(agent: int, enabled: bool)
|
||||
|
||||
--- Check the agent state. Returns true if the agent is enabled, otherwise false
|
||||
pathfinding.is_enabled(agent: int) --> bool
|
||||
|
||||
--- Create a route based on the given points. Returns an array of route points
|
||||
pathfinding.make_route(start: vec3, target: vec3) --> table<vec3>
|
||||
|
||||
--- Asynchronously create a route based on the given points.
|
||||
--- This function allows to perform pathfinding in the background without blocking the main thread of execution
|
||||
pathfinding.make_route_async(agent: int, start: vec3, target: vec3)
|
||||
|
||||
--- Get the route that the agent has already found. Used to get the route after an asynchronous search.
|
||||
--- If the search has not yet completed, returns nil. If the route is not found, returns an empty table.
|
||||
pathfinding.pull_route(agent: int) --> table<vec3> or nil
|
||||
|
||||
--- Set the maximum number of visited blocks for the agent. Used to limit the amount of work of the pathfinding algorithm.
|
||||
pathfinding.set_max_visited(agent: int, max_visited: int)
|
||||
|
||||
--- Adding an avoided blocks tag
|
||||
pathfinding.avoid_tag(
|
||||
agent: int,
|
||||
-- tag for avoided blocks
|
||||
tag: string, [optional],
|
||||
-- cost of crossing a block
|
||||
cost: int = 10
|
||||
)
|
||||
```
|
||||
@ -89,6 +89,13 @@ player.set_loading_chunks(playerid: int, bool)
|
||||
|
||||
Getter and setter of the property that determines whether the player is loading chunks.
|
||||
|
||||
```lua
|
||||
player.get_interaction_distance(playerid: int) -> float
|
||||
player.set_interaction_distance(playerid: int, distance: float)
|
||||
```
|
||||
|
||||
Getter and setter of the property for max interaction distance.
|
||||
|
||||
``` lua
|
||||
player.set_spawnpoint(playerid: int, x: number, y: number, z: number)
|
||||
player.get_spawnpoint(playerid: int) -> number, number, number
|
||||
|
||||
38
doc/en/scripting/builtins/librandom.md
Normal file
38
doc/en/scripting/builtins/librandom.md
Normal file
@ -0,0 +1,38 @@
|
||||
# *random* library
|
||||
|
||||
A library of functions for generating random numbers.
|
||||
|
||||
## Non-deterministic numbers
|
||||
|
||||
```lua
|
||||
-- Generates a random number in the range [0..1)
|
||||
random.random() --> number
|
||||
|
||||
-- Generates a random integer in the range [0..n]
|
||||
random.random(n) --> number
|
||||
|
||||
-- Generates a random integer in the range [a..b]
|
||||
random.random(a, b) --> number
|
||||
|
||||
-- Generates a random byte array of length n
|
||||
random.bytes(n: number) -> Bytearray
|
||||
|
||||
-- Generates a UUID version 4
|
||||
random.uuid() -> str
|
||||
```
|
||||
|
||||
## Pseudorandom numbers
|
||||
|
||||
The library provides the Random class - a generator with its own isolated state.
|
||||
|
||||
```lua
|
||||
local rng = random.Random()
|
||||
|
||||
-- Used similarly to math.random
|
||||
local a = rng:random() --> [0..1)
|
||||
local b = rng:random(10) --> [0..10]
|
||||
local c = rng:random(5, 20) --> [5..20]
|
||||
|
||||
-- Sets the generator state to generate a reproducible sequence of random numbers
|
||||
rng:seed(42)
|
||||
```
|
||||
@ -100,6 +100,13 @@ vecn.length(a: vector)
|
||||
|
||||
```
|
||||
|
||||
#### Distance - *vecn.distance(...)*
|
||||
|
||||
```lua
|
||||
-- returns the distance between two vectors
|
||||
vecn.distance(a: vector, b: vector)
|
||||
```
|
||||
|
||||
#### Absolute value - *vecn.abs(...)*
|
||||
|
||||
```lua
|
||||
@ -136,6 +143,16 @@ vecn.pow(v: vector, exponent: number, dst: vector)
|
||||
vecn.dot(a: vector, b: vector)
|
||||
```
|
||||
|
||||
#### Mixing - *vecn.mix(...)*
|
||||
|
||||
```lua
|
||||
-- returns vector a * (1.0 - t) + b * t
|
||||
vecn.mix(a: vector, b: vector, t: number)
|
||||
|
||||
-- writes to dst vector a * (1.0 - t) + b * t
|
||||
vecn.mix(a: vector, b: vector, t: number, dst: vector)
|
||||
```
|
||||
|
||||
#### Convert to string - *vecn.tostring(...)*
|
||||
> [!WARNING]
|
||||
> Returns only if the content is a vector
|
||||
@ -160,6 +177,12 @@ vec2.angle(v: vec2)
|
||||
|
||||
-- returns the direction angle of the vector {x, y} in degrees [0, 360]
|
||||
vec2.angle(x: number, y: number)
|
||||
|
||||
-- returns the vector rotated by an angle in degrees counterclockwise
|
||||
vec2.rotate(v: vec2, angle: number) -> vec2
|
||||
|
||||
-- writes the vector rotated by an angle in degrees counterclockwise to dst
|
||||
vec2.rotate(v: vec2, angle: number, dst: vec2) -> vec2
|
||||
```
|
||||
|
||||
|
||||
@ -188,6 +211,10 @@ print("mul: " .. vec3.tostring(result_mul)) -- {10, 40, 80}
|
||||
local result_mul_scal = vec3.mul(v1_3d, scal)
|
||||
print("mul_scal: " .. vec3.tostring(result_mul_scal)) -- {6, 12, 12}
|
||||
|
||||
-- calculating distance between vectors
|
||||
local result_distance = vec3.distance(v1_3d, v2_3d)
|
||||
print("distance: " .. result_distance) -- 43
|
||||
|
||||
-- vector normalization
|
||||
local result_norm = vec3.normalize(v1_3d)
|
||||
print("norm: " .. vec3.tostring(result_norm)) -- {0.333, 0.667, 0.667}
|
||||
@ -211,3 +238,7 @@ print("pow: " .. vec3.tostring(result_pow)) -- {1, 4, 4}
|
||||
-- scalar product of vectors
|
||||
local result_dot = vec3.dot(v1_3d, v2_3d)
|
||||
print("dot: " ..result_dot) -- 250
|
||||
|
||||
-- mixing vectors
|
||||
local result_mix = vec3.mix(v1_3d, v2_3d, 0.25)
|
||||
print("mix: " .. vec3.tostring(result_mix)) -- {3.25, 6.5, 11.5}
|
||||
|
||||
@ -26,6 +26,8 @@ entity:get_uid() -> int
|
||||
entity:get_component(name: str) -> component or nil
|
||||
-- Checks for the presence of a component by name
|
||||
entity:has_component(name: str) -> bool
|
||||
-- Retrieves a component by name. Throws an exception if it does not exist
|
||||
entity:require_component(name: str) -> component
|
||||
|
||||
-- Enables/disables the component
|
||||
entity:set_enabled(name: str, enable: bool)
|
||||
@ -93,10 +95,12 @@ body:get_linear_damping() -> number
|
||||
-- Sets the linear velocity attenuation multiplier
|
||||
body:set_linear_damping(value: number)
|
||||
|
||||
-- Checks if vertical velocity attenuation is enabled
|
||||
-- Checks if vertical damping is enabled
|
||||
body:is_vdamping() -> bool
|
||||
-- Enables/disables vertical velocity attenuation
|
||||
body:set_vdamping(enabled: bool)
|
||||
-- Returns the vertical damping multiplier
|
||||
body:get_vdamping() -> number
|
||||
-- Enables/disables vertical damping / sets vertical damping multiplier
|
||||
body:set_vdamping(enabled: bool | number)
|
||||
|
||||
-- Checks if the entity is on the ground
|
||||
body:is_grounded() -> bool
|
||||
@ -188,6 +192,12 @@ function on_update(tps: int)
|
||||
|
||||
Called every entities tick (currently 20 times per second).
|
||||
|
||||
```lua
|
||||
function on_physics_update(delta: number)
|
||||
```
|
||||
|
||||
Called after each physics step
|
||||
|
||||
```lua
|
||||
function on_render(delta: number)
|
||||
```
|
||||
|
||||
@ -46,6 +46,13 @@ function on_blocks_tick(tps: int)
|
||||
|
||||
Called tps (20) times per second. Use 1/tps instead of `time.delta()`.
|
||||
|
||||
```lua
|
||||
function on_block_tick(x, y, z, tps: number)
|
||||
```
|
||||
|
||||
Called tps (20 / tick-interval) times per second for a block.
|
||||
Use 1/tps instead of `time.delta()`.
|
||||
|
||||
```lua
|
||||
function on_player_tick(playerid: int, tps: int)
|
||||
```
|
||||
|
||||
@ -306,3 +306,29 @@
|
||||
### `имя_свойства@append`
|
||||
|
||||
Добавляет элементы в конец списка, вместо его полной перезаписи.
|
||||
|
||||
## Теги - *tags*
|
||||
|
||||
Теги позволяют обозначать обобщённые свойства блоков. Названия следует формировать как `префикс:имя_тега`.
|
||||
Префикс не является обязательным, но позволяет избегать нежелательных логических коллизий. Пример:
|
||||
|
||||
```json
|
||||
{
|
||||
"tags": [
|
||||
"core:ore",
|
||||
"base_survival:food",
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Теги блокам можно добавлять и из других паков, с помощью файла `ваш_пак:tags.toml`. Пример
|
||||
|
||||
```toml
|
||||
"префикс:имя_тега" = [
|
||||
"рандомный_пак:какой_то_блок",
|
||||
"ещё_один_пак:предмет",
|
||||
]
|
||||
"другой_префикс:другое_имя_тега" = [
|
||||
# ...
|
||||
]
|
||||
```
|
||||
|
||||
@ -32,6 +32,11 @@
|
||||
|
||||
Пример: '~randutil' - слабая зависимость 'randutil'.
|
||||
|
||||
Версии зависимостей указываются после '@' и имеют операторы для ограничения допустимых версий.
|
||||
Отсутствие версии зависимости интерпретируется как '\*', т.е. любая версия.
|
||||
|
||||
Пример: 'randutil@>=1.0' - зависимость 'randutil' версии 1.0 и старше.
|
||||
|
||||
Пример:
|
||||
```json
|
||||
{
|
||||
|
||||
@ -20,6 +20,22 @@
|
||||
]
|
||||
```
|
||||
|
||||
Из конфигурации сущности можно передавать значения в ARGS.
|
||||
Они будут передаваться как при создании новой сущности, так и при загрузке сохранённой.
|
||||
Для этого используется список `args`:
|
||||
|
||||
```json
|
||||
"components": [
|
||||
{
|
||||
"name": "base:drop",
|
||||
"args": {
|
||||
"item": "base:stone.item",
|
||||
"count": 1
|
||||
}
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
Код компонентов должен находиться в `scripts/components`.
|
||||
|
||||
## Физика
|
||||
|
||||
@ -65,3 +65,30 @@
|
||||
- `number` - число
|
||||
- `relation` - отношение текущего значения к изначальному (x/y)
|
||||
- `vbar` - вертикальная шкала (используется по-умолчанию)
|
||||
|
||||
|
||||
## Теги - *tags*
|
||||
|
||||
Теги позволяют обозначать обобщённые свойства предметов. Названия следует формировать как `префикс:имя_тега`.
|
||||
Префикс не является обязательным, но позволяет избегать нежелательных логических коллизий. Пример:
|
||||
|
||||
```json
|
||||
{
|
||||
"tags": [
|
||||
"core:fuel",
|
||||
"base_survival:poison",
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Теги предметам можно добавлять и из других паков, с помощью файла `ваш_пак:tags.toml`. Пример
|
||||
|
||||
```toml
|
||||
"префикс:имя_тега" = [
|
||||
"рандомный_пак:предмет",
|
||||
"ещё_один_пак:какой_то_блок",
|
||||
]
|
||||
"другой_префикс:другое_имя_тега" = [
|
||||
# ...
|
||||
]
|
||||
```
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
# Документация
|
||||
|
||||
Документация версии 0.29 (в разработке).
|
||||
Документация версии 0.30 (в разработке).
|
||||
|
||||
[Документация версии 0.28](https://github.com/MihailRis/voxelcore/blob/release-0.28/doc/ru/main-page.md)
|
||||
[Документация 0.29.](https://github.com/MihailRis/voxelcore/blob/release-0.29/doc/ru/main-page.md)
|
||||
|
||||
## Разделы
|
||||
|
||||
|
||||
@ -32,8 +32,10 @@
|
||||
- [mat4](scripting/builtins/libmat4.md)
|
||||
- [network](scripting/builtins/libnetwork.md)
|
||||
- [pack](scripting/builtins/libpack.md)
|
||||
- [pathfinding](scripting/builtins/libpathfinding.md)
|
||||
- [player](scripting/builtins/libplayer.md)
|
||||
- [quat](scripting/builtins/libquat.md)
|
||||
- [random](scripting/builtins/librandom.md)
|
||||
- [rules](scripting/builtins/librules.md)
|
||||
- [time](scripting/builtins/libtime.md)
|
||||
- [utf8](scripting/builtins/libutf8.md)
|
||||
|
||||
@ -27,7 +27,10 @@ app.sleep_until(
|
||||
predicate: function() -> bool,
|
||||
-- максимальное количество тактов цикла движка, после истечения которых
|
||||
-- будет брошено исключение "max ticks exceed"
|
||||
[опционально] max_ticks = 1e9
|
||||
[опционально] max_ticks = 1e9,
|
||||
-- максимальное длительность ожидания в секундах.
|
||||
-- (работает с системным временем, включая test-режим)
|
||||
[опционально] timeout = 1e9
|
||||
)
|
||||
```
|
||||
|
||||
|
||||
@ -8,4 +8,10 @@ base64.encode(bytes: table|ByteArray) -> str
|
||||
|
||||
-- Декодирует base64 строку в ByteArray или таблицу чисел, если второй аргумент установлен на true
|
||||
base64.decode(base64string: str, [опционально]usetable: bool=false) -> table|ByteArray
|
||||
|
||||
-- Кодирует массив байт в urlsafe-base64 строку ('-', '_' вместо '+', '/')
|
||||
base64.encode_urlsafe(bytes: table|ByteArray) -> str
|
||||
|
||||
-- Декодирует urlsafe-base64 строку в ByteArray или таблицу чисел, если второй аргумент установлен на true
|
||||
base64.decode_urlsafe(base64string: str, [опционально]usetable: bool=false) -> table|ByteArray
|
||||
```
|
||||
|
||||
@ -67,6 +67,9 @@ block.get_variant(x: int, y: int, z: int) -> int
|
||||
|
||||
-- Устанавливает вариант блока по индексу
|
||||
block.set_variant(x: int, y: int, z: int, index: int) -> int
|
||||
|
||||
-- Проверяет наличие тега у блока
|
||||
block.has_tag(id: int, tag: str) -> bool
|
||||
```
|
||||
|
||||
### Raycast
|
||||
|
||||
@ -33,6 +33,9 @@ item.emission(itemid: int) -> str
|
||||
|
||||
-- Возвращает значение свойства `uses`
|
||||
item.uses(itemid: int) -> int
|
||||
|
||||
-- Проверяет наличие тега у предмета
|
||||
item.has_tag(itemid: int, tag: str) -> bool
|
||||
```
|
||||
|
||||
|
||||
|
||||
@ -6,9 +6,15 @@
|
||||
|
||||
```lua
|
||||
-- Выполняет GET запрос к указанному URL.
|
||||
-- После получения ответа, передаёт текст в функцию callback.
|
||||
-- В случае ошибки в onfailure будет передан HTTP-код ответа.
|
||||
network.get(url: str, callback: function(str), [опционально] onfailure: function(int))
|
||||
network.get(
|
||||
url: str,
|
||||
-- Функция, вызываемая при получении ответа
|
||||
callback: function(str),
|
||||
-- Обработчик ошибок
|
||||
[опционально] onfailure: function(int, str),
|
||||
-- Список дополнительных заголовков запроса
|
||||
[опционально] headers: table<str>
|
||||
)
|
||||
|
||||
-- Пример:
|
||||
network.get("https://api.github.com/repos/MihailRis/VoxelEngine-Cpp/releases/latest", function (s)
|
||||
@ -16,13 +22,28 @@ network.get("https://api.github.com/repos/MihailRis/VoxelEngine-Cpp/releases/lat
|
||||
end)
|
||||
|
||||
-- Вариант для двоичных файлов, с массивом байт вместо строки в ответе.
|
||||
network.get_binary(url: str, callback: function(table|ByteArray), [опционально] onfailure: function(int))
|
||||
network.get_binary(
|
||||
url: str,
|
||||
callback: function(ByteArray),
|
||||
[опционально] onfailure: function(int, Bytearray),
|
||||
[опционально] headers: table<str>
|
||||
)
|
||||
|
||||
-- Выполняет POST запрос к указанному URL.
|
||||
-- На данный момент реализована поддержка только `Content-Type: application/json`
|
||||
-- После получения ответа, передаёт текст в функцию callback.
|
||||
-- В случае ошибки в onfailure будет передан HTTP-код ответа.
|
||||
network.post(url: str, data: table, callback: function(str), [опционально] onfailure: function(int))
|
||||
network.post(
|
||||
url: str,
|
||||
-- Тело запроса в виде таблицы, конвертируемой в JSON или строки
|
||||
body: table|str,
|
||||
-- Функция, вызываемая при получении ответа
|
||||
callback: function(str),
|
||||
-- Обработчик ошибок
|
||||
[опционально] onfailure: function(int, str),
|
||||
-- Список дополнительных заголовков запроса
|
||||
[опционально] headers: table<str>
|
||||
)
|
||||
```
|
||||
|
||||
## TCP-Соединения
|
||||
|
||||
66
doc/ru/scripting/builtins/libpathfinding.md
Normal file
66
doc/ru/scripting/builtins/libpathfinding.md
Normal file
@ -0,0 +1,66 @@
|
||||
# Библиотека *pathfinding*
|
||||
|
||||
Библиотека *pathfinding* предоставляет функции для работы с системой поиска пути в игровом мире. Она позволяет создавать и управлять агентами, которые могут находить маршруты между точками в мире.
|
||||
|
||||
При использовании в логике сущностей следует использовать компонент `core:pathfinding`.
|
||||
|
||||
## Компонент `core:pathfinding`
|
||||
|
||||
```lua
|
||||
local pf = entity:get_component("core:pathfinding")
|
||||
|
||||
--- ...
|
||||
local x = ...
|
||||
local y = ...
|
||||
local z = ...
|
||||
|
||||
--- Установка цели для агента
|
||||
pf.set_target({x, y, z})
|
||||
|
||||
--- Получение текущей цели агента
|
||||
local target = pf.get_target() --> vec3 или nil
|
||||
--- ...
|
||||
|
||||
--- Получение текущего маршрута агента
|
||||
local route = pf.get_route() --> table<vec3> или nil
|
||||
--- ...
|
||||
```
|
||||
|
||||
## Функции библиотеки
|
||||
|
||||
```lua
|
||||
--- Создание нового агента. Возвращает идентификатор созданного агента
|
||||
local agent = pathfinding.create_agent() --> int
|
||||
|
||||
--- Удаление агента по идентификатору. Возвращает true, если агент существовал, иначе false
|
||||
pathfinding.remove_agent(agent: int) --> bool
|
||||
|
||||
--- Установка состояния агента (включен/выключен)
|
||||
pathfinding.set_enabled(agent: int, enabled: bool)
|
||||
|
||||
--- Проверка состояния агента. Возвращает true, если агент включен, иначе false
|
||||
pathfinding.is_enabled(agent: int) --> bool
|
||||
|
||||
--- Создание маршрута на основе заданных точек. Возвращает массив точек маршрута
|
||||
pathfinding.make_route(start: vec3, target: vec3) --> table<vec3>
|
||||
|
||||
--- Асинхронное создание маршрута на основе заданных точек.
|
||||
--- Функция позволяет выполнять поиск пути в фоновом режиме, не блокируя основной поток выполнения
|
||||
pathfinding.make_route_async(agent: int, start: vec3, target: vec3)
|
||||
|
||||
--- Получение маршрута, который агент уже нашел. Используется для получения маршрута после асинхронного поиска.
|
||||
--- Если поиск ещё не завершён, возвращает nil. Если маршрут не найден, возвращает пустую таблицу.
|
||||
pathfinding.pull_route(agent: int) --> table<vec3> или nil
|
||||
|
||||
--- Установка максимального количества посещенных блоков для агента. Используется для ограничения объема работы алгоритма поиска пути.
|
||||
pathfinding.set_max_visited(agent: int, max_visited: int)
|
||||
|
||||
--- Добавление тега избегаемых блоков
|
||||
pathfinding.avoid_tag(
|
||||
agent: int,
|
||||
-- тег избегаемых блоков
|
||||
tag: string, [опционально],
|
||||
-- стоимость пересечения блока
|
||||
cost: int = 10
|
||||
)
|
||||
```
|
||||
@ -89,6 +89,13 @@ player.set_loading_chunks(playerid: int, bool)
|
||||
|
||||
Геттер и сеттер свойства, определяющего, прогружает ли игрок чанки вокруг.
|
||||
|
||||
```lua
|
||||
player.get_interaction_distance(playerid: int) -> float
|
||||
player.set_interaction_distance(playerid: int, distance: float)
|
||||
```
|
||||
|
||||
Геттер и сеттер свойства, определяющего максимальную дистанцию взаимодействия.
|
||||
|
||||
```lua
|
||||
player.set_spawnpoint(playerid: int, x: number, y: number, z: number)
|
||||
player.get_spawnpoint(playerid: int) -> number, number, number
|
||||
|
||||
38
doc/ru/scripting/builtins/librandom.md
Normal file
38
doc/ru/scripting/builtins/librandom.md
Normal file
@ -0,0 +1,38 @@
|
||||
# Библиотека *random*
|
||||
|
||||
Библиотека функций для генерации случайный чисел.
|
||||
|
||||
## Недетерминированные числа
|
||||
|
||||
```lua
|
||||
-- Генерирует случайное число в диапазоне [0..1)
|
||||
random.random() --> number
|
||||
|
||||
-- Генерирует случайное целое число в диапазоне [0..n]
|
||||
random.random(n) --> number
|
||||
|
||||
-- Генерирует случайное целое число в диапазоне [a..b]
|
||||
random.random(a, b) --> number
|
||||
|
||||
-- Генерирует случайный массив байт длиной n
|
||||
random.bytes(n: number) -> Bytearray
|
||||
|
||||
-- Генерирует UUID версии 4
|
||||
random.uuid() -> str
|
||||
```
|
||||
|
||||
## Псевдослучайные числа
|
||||
|
||||
Библиотека предоставляет класс Random - генератор с собственным изолированным состоянием.
|
||||
|
||||
```lua
|
||||
local rng = random.Random()
|
||||
|
||||
-- Используется аналогично math.random
|
||||
local a = rng:random() --> [0..1)
|
||||
local b = rng:random(10) --> [0..10]
|
||||
local c = rng:random(5, 20) --> [5..20]
|
||||
|
||||
-- Устанавливает состояние генератора для генерации воспроизводимой последовательности случайных чисел
|
||||
rng:seed(42)
|
||||
```
|
||||
@ -100,6 +100,13 @@ vecn.length(a: vector)
|
||||
|
||||
```
|
||||
|
||||
#### Дистанция - *vecn.distance(...)*
|
||||
|
||||
```lua
|
||||
-- возвращает расстояние между двумя векторами
|
||||
vecn.distance(a: vector, b: vector)
|
||||
```
|
||||
|
||||
#### Абсолютное значение - *vecn.abs(...)*
|
||||
|
||||
```lua
|
||||
@ -136,6 +143,16 @@ vecn.pow(v: vector, exponent: number, dst: vector)
|
||||
vecn.dot(a: vector, b: vector)
|
||||
```
|
||||
|
||||
#### Смешивание - *vecn.mix(...)*
|
||||
|
||||
```lua
|
||||
-- возвращает вектор a * (1.0 - t) + b * t
|
||||
vecn.mix(a: vector, b: vector, t: number)
|
||||
|
||||
-- записывает в dst вектор a * (1.0 - t) + b * t
|
||||
vecn.mix(a: vector, b: vector, t: number, dst: vector)
|
||||
```
|
||||
|
||||
#### Перевод в строку - *vecn.tostring(...)*
|
||||
> [!WARNING]
|
||||
> Возвращает только тогда, когда содержимым является вектор
|
||||
@ -160,6 +177,12 @@ vec2.angle(v: vec2)
|
||||
|
||||
-- возвращает угол направления вектора {x, y} в градусах [0, 360]
|
||||
vec2.angle(x: number, y: number)
|
||||
|
||||
-- возвращает повернутый вектор на угол в градусах против часовой стрелки
|
||||
vec2.rotate(v: vec2, angle: number) -> vec2
|
||||
|
||||
-- записывает повернутый вектор на угол в градусах против часовой стрелки в dst
|
||||
vec2.rotate(v: vec2, angle: number, dst: vec2) -> vec2
|
||||
```
|
||||
|
||||
|
||||
@ -192,6 +215,10 @@ print("mul_scal: " .. vec3.tostring(result_mul_scal)) -- {6, 12, 12}
|
||||
local result_norm = vec3.normalize(v1_3d)
|
||||
print("norm: " .. vec3.tostring(result_norm)) -- {0.333, 0.667, 0.667}
|
||||
|
||||
-- дистанция между векторами
|
||||
local result_distance = vec3.distance(v1_3d, v2_3d)
|
||||
print("distance: " .. result_distance) -- 43
|
||||
|
||||
-- длина вектора
|
||||
local result_len = vec3.length(v1_3d)
|
||||
print("len: " .. result_len) -- 3
|
||||
@ -211,4 +238,9 @@ print("pow: " .. vec3.tostring(result_pow)) -- {1, 4, 4}
|
||||
-- скалярное произведение векторов
|
||||
local result_dot = vec3.dot(v1_3d, v2_3d)
|
||||
print("dot: " .. result_dot) -- 250
|
||||
|
||||
-- смешивание векторов
|
||||
local result_mix = vec3.mix(v1_3d, v2_3d, 0.25)
|
||||
print("mix: " .. vec3.tostring(result_mix)) -- {3.25, 6.5, 11.5}
|
||||
|
||||
```
|
||||
|
||||
@ -26,6 +26,8 @@ entity:get_uid() -> int
|
||||
entity:get_component(name: str) -> компонент или nil
|
||||
-- Проверяет наличие компонента по имени
|
||||
entity:has_component(name: str) -> bool
|
||||
-- Запрашивает компонент по имени. Бросает исключение при отсутствии
|
||||
entity:require_component(name: str) -> компонент
|
||||
|
||||
-- Включает/выключает компонент по имени
|
||||
entity:set_enabled(name: str, enable: bool)
|
||||
@ -95,8 +97,10 @@ body:set_linear_damping(value: number)
|
||||
|
||||
-- Проверяет, включено ли вертикальное затухание скорости
|
||||
body:is_vdamping() -> bool
|
||||
-- Включает/выключает вертикальное затухание скорости
|
||||
body:set_vdamping(enabled: bool)
|
||||
-- Возвращает множитель вертикального затухания скорости
|
||||
body:get_vdamping() -> number
|
||||
-- Включает/выключает вертикальное затухание скорости / устанавливает значение множителя
|
||||
body:set_vdamping(enabled: bool | number)
|
||||
|
||||
-- Проверяет, находится ли сущность на земле (приземлена)
|
||||
body:is_grounded() -> bool
|
||||
@ -188,6 +192,12 @@ function on_update(tps: int)
|
||||
|
||||
Вызывается каждый такт сущностей (на данный момент - 20 раз в секунду).
|
||||
|
||||
```lua
|
||||
function on_physics_update(delta: number)
|
||||
```
|
||||
|
||||
Вызывается после каждого шага физики
|
||||
|
||||
```lua
|
||||
function on_render(delta: number)
|
||||
```
|
||||
|
||||
@ -46,6 +46,13 @@ function on_blocks_tick(tps: int)
|
||||
|
||||
Вызывается tps (20) раз в секунду. Используйте 1/tps вместо `time.delta()`.
|
||||
|
||||
```lua
|
||||
function on_block_tick(x, y, z, tps: number)
|
||||
```
|
||||
|
||||
Вызывается tps (20 / tick-interval) раз в секунду для конкретного блока.
|
||||
Используйте 1/tps вместо `time.delta()`.
|
||||
|
||||
```lua
|
||||
function on_player_tick(playerid: int, tps: int)
|
||||
```
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
{
|
||||
"texture": "coal_ore",
|
||||
"tags": ["base:ore"],
|
||||
"base:durability": 16.0
|
||||
}
|
||||
|
||||
@ -7,5 +7,6 @@
|
||||
"obstacle": false,
|
||||
"selectable": false,
|
||||
"replaceable": true,
|
||||
"translucent": true
|
||||
"translucent": true,
|
||||
"tags": ["core:liquid"]
|
||||
}
|
||||
|
||||
@ -1,6 +1,13 @@
|
||||
{
|
||||
"components": [
|
||||
"base:drop"
|
||||
{
|
||||
"name": "base:drop",
|
||||
"args": {
|
||||
"item": "base:stone.item",
|
||||
"count": 1
|
||||
}
|
||||
}
|
||||
|
||||
],
|
||||
"hitbox": [0.4, 0.25, 0.4],
|
||||
"sensors": [
|
||||
|
||||
@ -1,5 +1,12 @@
|
||||
{
|
||||
"components": [
|
||||
{
|
||||
"name": "core:mob",
|
||||
"args": {
|
||||
"jump_force": 8.0
|
||||
}
|
||||
},
|
||||
"core:player",
|
||||
"base:player_animator"
|
||||
],
|
||||
"hitbox": [0.6, 1.8, 0.6]
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"id": "base",
|
||||
"title": "Base",
|
||||
"version": "0.29",
|
||||
"version": "0.30",
|
||||
"description": "basic content package"
|
||||
}
|
||||
|
||||
@ -8,6 +8,9 @@ timer = 0.3
|
||||
|
||||
local def_index = entity:def_index()
|
||||
dropitem = ARGS
|
||||
if dropitem.item then
|
||||
dropitem.id = item.index(dropitem.item)
|
||||
end
|
||||
if dropitem then
|
||||
timer = dropitem.pickup_delay or timer
|
||||
end
|
||||
|
||||
@ -1,11 +1,10 @@
|
||||
local tsf = entity.transform
|
||||
local body = entity.rigidbody
|
||||
local rig = entity.skeleton
|
||||
local mob = entity:require_component("core:mob")
|
||||
|
||||
local itemid = 0
|
||||
local headIndex = rig:index("head")
|
||||
local itemIndex = rig:index("item")
|
||||
local bodyIndex = rig:index("body")
|
||||
|
||||
local function refresh_model(id)
|
||||
itemid = id
|
||||
@ -19,9 +18,10 @@ function on_render()
|
||||
return
|
||||
end
|
||||
|
||||
local rx, ry, rz = player.get_rot(pid, pid ~= hud.get_player())
|
||||
rig:set_matrix(headIndex, mat4.rotate({1, 0, 0}, ry))
|
||||
rig:set_matrix(bodyIndex, mat4.rotate({0, 1, 0}, rx))
|
||||
local rx, _, _ = player.get_rot(pid, pid ~= hud.get_player())
|
||||
|
||||
local dir = vec2.rotate({0, -1}, -rx)
|
||||
mob.set_dir({dir[1], 0, dir[2]})
|
||||
|
||||
local invid, slotid = player.get_inventory(pid)
|
||||
local id, _ = inventory.get(invid, slotid)
|
||||
|
||||
@ -176,18 +176,105 @@ function place_pack(panel, packinfo, callback, position_func)
|
||||
end
|
||||
end
|
||||
|
||||
local Version = {};
|
||||
|
||||
function Version.matches_pattern(version)
|
||||
for _, letter in string.gmatch(version, "%.+") do
|
||||
if type(letter) ~= "number" or letter ~= "." then
|
||||
return false;
|
||||
end
|
||||
|
||||
local t = string.split(version, ".");
|
||||
|
||||
return #t == 2 or #t == 3;
|
||||
end
|
||||
end
|
||||
|
||||
function Version.__equal(ver1, ver2)
|
||||
return ver1[1] == ver2[1] and ver1[2] == ver2[2] and ver1[3] == ver2[3];
|
||||
end
|
||||
|
||||
function Version.__more(ver1, ver2)
|
||||
if ver1[1] ~= ver2[1] then return ver1[1] > ver2[1] end;
|
||||
if ver1[2] ~= ver2[2] then return ver1[2] > ver2[2] end;
|
||||
return ver1[3] > ver2[3];
|
||||
end
|
||||
|
||||
function Version.__less(ver1, ver2)
|
||||
return Version.__more(ver2, ver1);
|
||||
end
|
||||
|
||||
function Version.__more_or_equal(ver1, ver2)
|
||||
return not Version.__less(ver1, ver2);
|
||||
end
|
||||
|
||||
function Version.__less_or_equal(ver1, ver2)
|
||||
return not Version.__more(ver1, ver2);
|
||||
end
|
||||
|
||||
function Version.compare(op, ver1, ver2)
|
||||
ver1 = string.split(ver1, ".");
|
||||
ver2 = string.split(ver2, ".");
|
||||
|
||||
if op == "=" then return Version.__equal(ver1, ver2);
|
||||
elseif op == ">" then return Version.__more(ver1, ver2);
|
||||
elseif op == "<" then return Version.__less(ver1, ver2);
|
||||
elseif op == ">=" then return Version.__more_or_equal(ver1, ver2);
|
||||
elseif op == "<=" then return Version.__less_or_equal(ver1, ver2);
|
||||
else return false; end
|
||||
end
|
||||
|
||||
function Version.parse(version)
|
||||
local op = string.sub(version, 1, 2);
|
||||
if op == ">=" or op == "=>" then
|
||||
return ">=", string.sub(version, #op + 1);
|
||||
elseif op == "<=" or op == "=<" then
|
||||
return "<=", string.sub(version, #op + 1);
|
||||
end
|
||||
|
||||
op = string.sub(version, 1, 1);
|
||||
if op == ">" or op == "<" then
|
||||
return op, string.sub(version, #op + 1);
|
||||
end
|
||||
|
||||
return "=", version;
|
||||
end
|
||||
|
||||
local function compare_version(dependent_version, actual_version)
|
||||
if Version.matches_pattern(dependent_version) and Version.matches_pattern(actual_version) then
|
||||
local op, dep_ver = Version.parse_version(dependent_version);
|
||||
Version.compare(op, dep_ver, actual_version);
|
||||
elseif dependent_version == "*" or dependent_version == actual_version then
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
end
|
||||
end
|
||||
|
||||
function check_dependencies(packinfo)
|
||||
if packinfo.dependencies == nil then
|
||||
return
|
||||
end
|
||||
for i,dep in ipairs(packinfo.dependencies) do
|
||||
local depid = dep:sub(2,-1)
|
||||
local depid, depver = unpack(string.split(dep:sub(2,-1), "@"))
|
||||
|
||||
if dep:sub(1,1) == '!' then
|
||||
if not table.has(packs_all, depid) then
|
||||
return string.format(
|
||||
"%s (%s)", gui.str("error.dependency-not-found"), depid
|
||||
)
|
||||
end
|
||||
|
||||
|
||||
local dep_pack = pack.get_info(depid);
|
||||
|
||||
if not compare_version(depver, dep_pack.version) then
|
||||
local op, ver = Version.parse(depver);
|
||||
|
||||
print(string.format("%s: %s !%s %s (%s)", gui.str("error.dependency-version-not-met"), dep_pack.version, op, ver, depid));
|
||||
return string.format("%s: %s != %s (%s)", gui.str("error.dependency-version-not-met"), dep_pack.version, ver, depid);
|
||||
end
|
||||
|
||||
if table.has(packs_installed, packinfo.id) then
|
||||
table.insert(required, depid)
|
||||
end
|
||||
|
||||
@ -13,9 +13,9 @@ end
|
||||
function refresh()
|
||||
document.list:clear()
|
||||
|
||||
local available = pack.get_available()
|
||||
local infos = pack.get_info(available)
|
||||
for _, name in ipairs(available) do
|
||||
local allpacks = table.merge(pack.get_available(), pack.get_installed())
|
||||
local infos = pack.get_info(allpacks)
|
||||
for _, name in ipairs(allpacks) do
|
||||
local info = infos[name]
|
||||
local scripts_dir = info.path.."/scripts/app"
|
||||
if not file.exists(scripts_dir) then
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
<panel size='400' color='0' interval='1' context='menu'>
|
||||
|
||||
<button onclick='menu.page="new_world"'>@New World</button>
|
||||
|
||||
<panel id='worlds' size='390,1' padding='5' color='#FFFFFF11' max-length='400'>
|
||||
|
||||
10
res/modules/internal/asserts.lua
Normal file
10
res/modules/internal/asserts.lua
Normal file
@ -0,0 +1,10 @@
|
||||
local this = {}
|
||||
|
||||
function this.equals(expected, fact)
|
||||
assert(fact == expected, string.format(
|
||||
"(fact == expected) assertion failed\n Expected: %s\n Fact: %s",
|
||||
expected, fact
|
||||
))
|
||||
end
|
||||
|
||||
return this
|
||||
@ -110,6 +110,21 @@ function vec3.dot(a, b)
|
||||
return a[1] * b[1] + a[2] * b[2] + a[3] * b[3]
|
||||
end
|
||||
|
||||
function vec3.mix(a, b, t, dest)
|
||||
if dest then
|
||||
dest[1] = a[1] * (1.0 - t) + b[1] * t
|
||||
dest[2] = a[2] * (1.0 - t) + b[2] * t
|
||||
dest[3] = a[3] * (1.0 - t) + b[3] * t
|
||||
return dest
|
||||
else
|
||||
return {
|
||||
a[1] * (1.0 - t) + b[1] * t,
|
||||
a[2] * (1.0 - t) + b[2] * t,
|
||||
a[3] * (1.0 - t) + b[3] * t,
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
-- =================================================== --
|
||||
-- ====================== vec2 ======================= --
|
||||
-- =================================================== --
|
||||
@ -210,3 +225,16 @@ end
|
||||
function vec2.dot(a, b)
|
||||
return a[1] * b[1] + a[2] * b[2]
|
||||
end
|
||||
|
||||
function vec2.mix(a, b, t, dest)
|
||||
if dest then
|
||||
dest[1] = a[1] * (1.0 - t) + b[1] * t
|
||||
dest[2] = a[2] * (1.0 - t) + b[2] * t
|
||||
return dest
|
||||
else
|
||||
return {
|
||||
a[1] * (1.0 - t) + b[1] * t,
|
||||
a[2] * (1.0 - t) + b[2] * t,
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
35
res/modules/internal/random_generator.lua
Normal file
35
res/modules/internal/random_generator.lua
Normal file
@ -0,0 +1,35 @@
|
||||
local Random = {}
|
||||
|
||||
local M = 2 ^ 31
|
||||
local A = 1103515245
|
||||
local C = 12345
|
||||
|
||||
function Random.randint(self)
|
||||
self._seed = (A * self._seed + C) % M
|
||||
return self._seed
|
||||
end
|
||||
|
||||
function Random.random(self, a, b)
|
||||
local num = self:randint() % M / M
|
||||
if b then
|
||||
return math.floor(num * (b - a + 1) + a)
|
||||
elseif a then
|
||||
return math.floor(num * a + 1)
|
||||
else
|
||||
return num
|
||||
end
|
||||
end
|
||||
|
||||
function Random.seed(self, number)
|
||||
if type(number) ~= "number" then
|
||||
error("number expected")
|
||||
end
|
||||
self._seed = number
|
||||
end
|
||||
|
||||
return function(seed)
|
||||
if seed and type(seed) ~= "number" then
|
||||
error("number expected")
|
||||
end
|
||||
return setmetatable({_seed = seed or random.random(M)}, {__index = Random})
|
||||
end
|
||||
@ -25,6 +25,7 @@ local Rigidbody = {__index={
|
||||
get_linear_damping=function(self) return __rigidbody.get_linear_damping(self.eid) end,
|
||||
set_linear_damping=function(self, f) return __rigidbody.set_linear_damping(self.eid, f) end,
|
||||
is_vdamping=function(self) return __rigidbody.is_vdamping(self.eid) end,
|
||||
get_vdamping=function(self) return __rigidbody.get_vdamping(self.eid) end,
|
||||
set_vdamping=function(self, b) return __rigidbody.set_vdamping(self.eid, b) end,
|
||||
is_grounded=function(self) return __rigidbody.is_grounded(self.eid) end,
|
||||
is_crouching=function(self) return __rigidbody.is_crouching(self.eid) end,
|
||||
@ -63,6 +64,13 @@ local Entity = {__index={
|
||||
get_skeleton=function(self) return entities.get_skeleton(self.eid) end,
|
||||
set_skeleton=function(self, s) return entities.set_skeleton(self.eid, s) end,
|
||||
get_component=function(self, name) return self.components[name] end,
|
||||
require_component=function(self, name)
|
||||
local component = self.components[name]
|
||||
if not component then
|
||||
error(("entity has no required component '%s'"):format(name))
|
||||
end
|
||||
return component
|
||||
end,
|
||||
has_component=function(self, name) return self.components[name] ~= nil end,
|
||||
get_uid=function(self) return self.eid end,
|
||||
def_index=function(self) return entities.get_def(self.eid) end,
|
||||
@ -125,6 +133,19 @@ return {
|
||||
::continue::
|
||||
end
|
||||
end,
|
||||
physics_update = function(delta)
|
||||
for uid, entity in pairs(entities) do
|
||||
for _, component in pairs(entity.components) do
|
||||
local callback = component.on_physics_update
|
||||
if not component.__disabled and callback then
|
||||
local result, err = pcall(callback, delta)
|
||||
if err then
|
||||
debug.error(err)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end,
|
||||
render = function(delta)
|
||||
for _,entity in pairs(entities) do
|
||||
for _, component in pairs(entity.components) do
|
||||
|
||||
@ -11,6 +11,9 @@ local Schedule = {
|
||||
self._next_interval = id + 1
|
||||
return id
|
||||
end,
|
||||
set_timeout = function(self, ms, callback)
|
||||
self:set_interval(ms, callback, 1)
|
||||
end,
|
||||
tick = function(self, dt)
|
||||
local timer = self._timer + dt
|
||||
for id, interval in pairs(self._intervals) do
|
||||
|
||||
@ -23,4 +23,5 @@ function on_menu_setup()
|
||||
menubg = gui.root.menubg
|
||||
controller.resize_menu_bg()
|
||||
menu.page = "main"
|
||||
menu.visible = true
|
||||
end
|
||||
|
||||
@ -74,6 +74,38 @@ local _tcp_client_callbacks = {}
|
||||
local _udp_server_callbacks = {}
|
||||
local _udp_client_datagram_callbacks = {}
|
||||
local _udp_client_open_callbacks = {}
|
||||
local _http_response_callbacks = {}
|
||||
local _http_error_callbacks = {}
|
||||
|
||||
network.get = function(url, callback, errorCallback, headers)
|
||||
local id = network.__get(url, headers)
|
||||
if callback then
|
||||
_http_response_callbacks[id] = callback
|
||||
end
|
||||
if errorCallback then
|
||||
_http_error_callbacks[id] = errorCallback
|
||||
end
|
||||
end
|
||||
|
||||
network.get_binary = function(url, callback, errorCallback, headers)
|
||||
local id = network.__get_binary(url, headers)
|
||||
if callback then
|
||||
_http_response_callbacks[id] = callback
|
||||
end
|
||||
if errorCallback then
|
||||
_http_error_callbacks[id] = errorCallback
|
||||
end
|
||||
end
|
||||
|
||||
network.post = function(url, data, callback, errorCallback, headers)
|
||||
local id = network.__post(url, data, headers)
|
||||
if callback then
|
||||
_http_response_callbacks[id] = callback
|
||||
end
|
||||
if errorCallback then
|
||||
_http_error_callbacks[id] = errorCallback
|
||||
end
|
||||
end
|
||||
|
||||
network.tcp_open = function (port, handler)
|
||||
local socket = setmetatable({id=network.__open_tcp(port)}, ServerSocket)
|
||||
@ -131,10 +163,80 @@ local function clean(iterable, checkFun, ...)
|
||||
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
|
||||
|
||||
print(type, id, x, y, z)
|
||||
end
|
||||
end
|
||||
|
||||
network.__process_events = function()
|
||||
local CLIENT_CONNECTED = 1
|
||||
local CONNECTED_TO_SERVER = 2
|
||||
local DATAGRAM = 3
|
||||
local RESPONSE = 4
|
||||
|
||||
local ON_SERVER = 1
|
||||
local ON_CLIENT = 2
|
||||
@ -160,6 +262,22 @@ network.__process_events = function()
|
||||
elseif side == ON_SERVER then
|
||||
_udp_server_callbacks[sid](addr, port, data)
|
||||
end
|
||||
elseif etype == RESPONSE then
|
||||
if event[2] / 100 == 2 then
|
||||
local callback = _http_response_callbacks[event[3]]
|
||||
_http_response_callbacks[event[3]] = nil
|
||||
_http_error_callbacks[event[3]] = nil
|
||||
if callback then
|
||||
callback(event[4])
|
||||
end
|
||||
else
|
||||
local callback = _http_error_callbacks[event[3]]
|
||||
_http_response_callbacks[event[3]] = nil
|
||||
_http_error_callbacks[event[3]] = nil
|
||||
if callback then
|
||||
callback(event[2], event[4])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- remove dead servers
|
||||
|
||||
177
res/scripts/components/mob.lua
Normal file
177
res/scripts/components/mob.lua
Normal file
@ -0,0 +1,177 @@
|
||||
local body = entity.rigidbody
|
||||
local tsf = entity.transform
|
||||
local rig = entity.skeleton
|
||||
|
||||
local props = {}
|
||||
|
||||
local function def_prop(name, def_value)
|
||||
props[name] = SAVED_DATA[name] or ARGS[name] or def_value
|
||||
this["get_"..name] = function() return props[name] end
|
||||
this["set_"..name] = function(value)
|
||||
props[name] = value
|
||||
if math.abs(value - def_value) < 1e-7 then
|
||||
SAVED_DATA[name] = nil
|
||||
else
|
||||
SAVED_DATA[name] = value
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def_prop("jump_force", 0.0)
|
||||
def_prop("air_damping", 1.0)
|
||||
def_prop("ground_damping", 1.0)
|
||||
def_prop("movement_speed", 3.0)
|
||||
def_prop("run_speed_mul", 1.5)
|
||||
def_prop("crouch_speed_mul", 0.35)
|
||||
def_prop("flight_speed_mul", 2.0)
|
||||
def_prop("gravity_scale", 1.0)
|
||||
|
||||
local function normalize_angle(angle)
|
||||
while angle > 180 do
|
||||
angle = angle - 360
|
||||
end
|
||||
while angle <= -180 do
|
||||
angle = angle + 360
|
||||
end
|
||||
return angle
|
||||
end
|
||||
|
||||
local function angle_delta(a, b)
|
||||
return normalize_angle(a - b)
|
||||
end
|
||||
|
||||
local dir = mat4.mul(tsf:get_rot(), {0, 0, -1})
|
||||
local flight = false
|
||||
|
||||
function jump(multiplier)
|
||||
local vel = body:get_vel()
|
||||
body:set_vel(
|
||||
vec3.add(vel, {0, props.jump_force * (multiplier or 1.0), 0}, vel))
|
||||
end
|
||||
|
||||
function move_vertical(speed, vel)
|
||||
vel = vel or body:get_vel()
|
||||
vel[2] = vel[2] * 0.2 + props.movement_speed * speed * 0.8
|
||||
body:set_vel(vel)
|
||||
end
|
||||
|
||||
local function move_horizontal(speed, dir, vel)
|
||||
vel = vel or body:get_vel()
|
||||
if vec2.length(dir) > 0.0 then
|
||||
vec2.normalize(dir, dir)
|
||||
|
||||
local magnitude = vec2.length({vel[1], vel[3]})
|
||||
|
||||
if magnitude <= 1e-4 or (magnitude < speed or vec2.dot(
|
||||
{vel[1] / magnitude, vel[3] / magnitude}, dir) < 0.9)
|
||||
then
|
||||
vel[1] = vel[1] * 0.2 + dir[1] * speed * 0.8
|
||||
vel[3] = vel[3] * 0.2 + dir[2] * speed * 0.8
|
||||
end
|
||||
magnitude = vec3.length({vel[1], 0, vel[3]})
|
||||
if vec2.dot({vel[1] / magnitude, vel[3] / magnitude}, dir) > 0.5 then
|
||||
vel[1] = vel[1] / magnitude * speed
|
||||
vel[3] = vel[3] / magnitude * speed
|
||||
end
|
||||
end
|
||||
body:set_vel(vel)
|
||||
end
|
||||
|
||||
function go(dir, speed_multiplier, sprint, crouch, vel)
|
||||
local speed = props.movement_speed * speed_multiplier
|
||||
if flight then
|
||||
speed = speed * props.flight_speed_mul
|
||||
end
|
||||
if sprint then
|
||||
speed = speed * props.run_speed_mul
|
||||
elseif crouch then
|
||||
speed = speed * props.crouch_speed_mul
|
||||
end
|
||||
move_horizontal(speed, dir, vel)
|
||||
end
|
||||
|
||||
local headIndex = rig:index("head")
|
||||
|
||||
function look_at(point, change_dir)
|
||||
local pos = tsf:get_pos()
|
||||
local viewdir = vec3.normalize(vec3.sub(point, pos))
|
||||
|
||||
local dot = vec3.dot(viewdir, dir)
|
||||
if dot < 0.0 and not change_dir then
|
||||
viewdir = mat4.mul(tsf:get_rot(), {0, 0, -1})
|
||||
else
|
||||
dir[1] = dir[1] * 0.8 + viewdir[1] * 0.13
|
||||
dir[3] = dir[3] * 0.8 + viewdir[3] * 0.13
|
||||
end
|
||||
|
||||
if not headIndex then
|
||||
return
|
||||
end
|
||||
|
||||
local headrot = mat4.idt()
|
||||
local curdir = mat4.mul(mat4.mul(tsf:get_rot(),
|
||||
rig:get_matrix(headIndex)), {0, 0, -1})
|
||||
|
||||
vec3.mix(curdir, viewdir, 0.2, viewdir)
|
||||
|
||||
headrot = mat4.inverse(mat4.look_at({0,0,0}, viewdir, {0, 1, 0}))
|
||||
headrot = mat4.mul(mat4.inverse(tsf:get_rot()), headrot)
|
||||
rig:set_matrix(headIndex, headrot)
|
||||
end
|
||||
|
||||
function follow_waypoints(pathfinding)
|
||||
pathfinding = pathfinding or entity:require_component("core:pathfinding")
|
||||
local pos = tsf:get_pos()
|
||||
local waypoint = pathfinding.next_waypoint()
|
||||
if not waypoint then
|
||||
return
|
||||
end
|
||||
local speed = props.movement_speed
|
||||
local vel = body:get_vel()
|
||||
dir = vec3.sub(
|
||||
vec3.add(waypoint, {0.5, 0, 0.5}),
|
||||
{pos[1], math.floor(pos[2]), pos[3]}
|
||||
)
|
||||
local upper = dir[2] > 0
|
||||
dir[2] = 0.0
|
||||
vec3.normalize(dir, dir)
|
||||
move_horizontal(speed, {dir[1], dir[3]}, vel)
|
||||
if upper and body:is_grounded() then
|
||||
jump(1.0)
|
||||
end
|
||||
end
|
||||
|
||||
function set_dir(new_dir)
|
||||
dir = new_dir
|
||||
end
|
||||
|
||||
function is_flight() return flight end
|
||||
|
||||
function set_flight(flag) flight = flag end
|
||||
|
||||
local prev_angle = (vec2.angle({dir[3], dir[1]})) % 360
|
||||
|
||||
function on_physics_update(delta)
|
||||
local grounded = body:is_grounded()
|
||||
body:set_vdamping(flight)
|
||||
body:set_gravity_scale({0, flight and 0.0 or props.gravity_scale, 0})
|
||||
body:set_linear_damping(
|
||||
(flight or not grounded) and props.air_damping or props.ground_damping
|
||||
)
|
||||
|
||||
local new_angle = (vec2.angle({dir[3], dir[1]})) % 360
|
||||
local angle = prev_angle
|
||||
|
||||
local adelta = angle_delta(
|
||||
normalize_angle(new_angle),
|
||||
normalize_angle(prev_angle)
|
||||
)
|
||||
local rotate_speed = entity:get_player() == -1 and 200 or 400
|
||||
|
||||
if math.abs(adelta) > 5 then
|
||||
angle = angle + delta * rotate_speed * (adelta > 0 and 1 or -1)
|
||||
end
|
||||
|
||||
tsf:set_rot(mat4.rotate({0, 1, 0}, angle + 180))
|
||||
prev_angle = angle
|
||||
end
|
||||
71
res/scripts/components/pathfinding.lua
Normal file
71
res/scripts/components/pathfinding.lua
Normal file
@ -0,0 +1,71 @@
|
||||
local target
|
||||
local route
|
||||
local started
|
||||
|
||||
local tsf = entity.transform
|
||||
local body = entity.rigidbody
|
||||
|
||||
agent = pathfinding.create_agent()
|
||||
pathfinding.set_max_visited(agent, 1e3)
|
||||
pathfinding.avoid_tag(agent, "core:liquid", 8)
|
||||
|
||||
function set_target(new_target)
|
||||
target = new_target
|
||||
end
|
||||
|
||||
function set_jump_height(height)
|
||||
pathfinding.set_jump_height(agent, height)
|
||||
end
|
||||
|
||||
function get_target()
|
||||
return target
|
||||
end
|
||||
|
||||
function get_route()
|
||||
return route
|
||||
end
|
||||
|
||||
function next_waypoint()
|
||||
if not route or #route == 0 then
|
||||
return
|
||||
end
|
||||
local waypoint = route[#route]
|
||||
local pos = tsf:get_pos()
|
||||
local dst = vec2.length({
|
||||
math.floor(waypoint[1] - math.floor(pos[1])),
|
||||
math.floor(waypoint[3] - math.floor(pos[3]))
|
||||
})
|
||||
if dst < 1.0 then
|
||||
table.remove(route, #route)
|
||||
end
|
||||
return route[#route]
|
||||
end
|
||||
|
||||
local refresh_internal = 100
|
||||
local frameid = math.random(0, refresh_internal)
|
||||
|
||||
function set_refresh_interval(interval)
|
||||
refresh_internal = interval
|
||||
end
|
||||
|
||||
function on_update()
|
||||
if not started then
|
||||
frameid = frameid + 1
|
||||
if body:is_grounded() then
|
||||
if target and (frameid % refresh_internal == 1 or not route) then
|
||||
pathfinding.make_route_async(agent, tsf:get_pos(), target)
|
||||
started = true
|
||||
end
|
||||
end
|
||||
else
|
||||
local new_route = pathfinding.pull_route(agent)
|
||||
if new_route then
|
||||
route = new_route
|
||||
started = false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function on_despawn()
|
||||
pathfinding.remove_agent(agent)
|
||||
end
|
||||
62
res/scripts/components/player.lua
Normal file
62
res/scripts/components/player.lua
Normal file
@ -0,0 +1,62 @@
|
||||
local tsf = entity.transform
|
||||
local body = entity.rigidbody
|
||||
local mob = entity:require_component("core:mob")
|
||||
|
||||
local cheat_speed_mul = 10.0
|
||||
|
||||
local function process_player_inputs(pid, delta)
|
||||
if not hud or hud.is_inventory_open() or menu.page ~= "" then
|
||||
return
|
||||
end
|
||||
local cam = cameras.get("core:first-person")
|
||||
local front = cam:get_front()
|
||||
local right = cam:get_right()
|
||||
front[2] = 0.0
|
||||
vec3.normalize(front, front)
|
||||
|
||||
local isjump = input.is_active('movement.jump')
|
||||
local issprint = input.is_active('movement.sprint')
|
||||
local iscrouch = input.is_active('movement.crouch')
|
||||
local isforward = input.is_active('movement.forward')
|
||||
local ischeat = input.is_active('movement.cheat')
|
||||
local isback = input.is_active('movement.back')
|
||||
local isleft = input.is_active('movement.left')
|
||||
local isright = input.is_active('movement.right')
|
||||
mob.set_flight(player.is_flight(pid))
|
||||
body:set_body_type(player.is_noclip(pid) and "kinematic" or "dynamic")
|
||||
body:set_crouching(iscrouch)
|
||||
|
||||
local vel = body:get_vel()
|
||||
local speed = ischeat and cheat_speed_mul or 1.0
|
||||
|
||||
local dir = {0, 0, 0}
|
||||
|
||||
if isforward then vec3.add(dir, front, dir) end
|
||||
if isback then vec3.sub(dir, front, dir) end
|
||||
if isright then vec3.add(dir, right, dir) end
|
||||
if isleft then vec3.sub(dir, right, dir) end
|
||||
|
||||
if vec3.length(dir) > 0.0 then
|
||||
mob.go({dir[1], dir[3]}, speed, issprint, iscrouch, vel)
|
||||
end
|
||||
|
||||
if mob.is_flight() then
|
||||
if isjump then
|
||||
mob.move_vertical(speed * 3)
|
||||
elseif iscrouch then
|
||||
mob.move_vertical(-speed * 3)
|
||||
end
|
||||
elseif body:is_grounded() and isjump then
|
||||
mob.jump()
|
||||
end
|
||||
end
|
||||
|
||||
function on_physics_update(delta)
|
||||
local pid = entity:get_player()
|
||||
if pid ~= -1 then
|
||||
local pos = tsf:get_pos()
|
||||
local cam = cameras.get("core:first-person")
|
||||
process_player_inputs(pid, delta)
|
||||
mob.look_at(vec3.add(pos, cam:get_front()))
|
||||
end
|
||||
end
|
||||
@ -22,6 +22,39 @@ local function configure_SSAO()
|
||||
-- for test purposes
|
||||
end
|
||||
|
||||
local function update_hand()
|
||||
local skeleton = gfx.skeletons
|
||||
local pid = hud.get_player()
|
||||
local invid, slot = player.get_inventory(pid)
|
||||
local itemid = inventory.get(invid, slot)
|
||||
|
||||
local cam = cameras.get("core:first-person")
|
||||
local bone = skeleton.index("hand", "item")
|
||||
|
||||
local offset = vec3.mul(vec3.sub(cam:get_pos(), {player.get_pos(pid)}), -1)
|
||||
|
||||
local rotation = cam:get_rot()
|
||||
|
||||
local angle = player.get_rot(pid) - 90
|
||||
local cos = math.cos(angle / (180 / math.pi))
|
||||
local sin = math.sin(angle / (180 / math.pi))
|
||||
|
||||
local newX = offset[1] * cos - offset[3] * sin
|
||||
local newZ = offset[1] * sin + offset[3] * cos
|
||||
|
||||
offset[1] = newX
|
||||
offset[3] = newZ
|
||||
|
||||
local mat = mat4.translate(mat4.idt(), {0.06, 0.035, -0.1})
|
||||
mat4.scale(mat, {0.1, 0.1, 0.1}, mat)
|
||||
mat4.mul(rotation, mat, mat)
|
||||
mat4.rotate(mat, {0, 1, 0}, -90, mat)
|
||||
mat4.translate(mat, offset, mat)
|
||||
|
||||
skeleton.set_matrix("hand", bone, mat)
|
||||
skeleton.set_model("hand", bone, item.model_name(itemid))
|
||||
end
|
||||
|
||||
function on_hud_open()
|
||||
input.add_callback("player.pick", function ()
|
||||
if hud.is_paused() or hud.is_inventory_open() then
|
||||
@ -81,39 +114,8 @@ function on_hud_open()
|
||||
end)
|
||||
|
||||
configure_SSAO()
|
||||
end
|
||||
|
||||
local function update_hand()
|
||||
local skeleton = gfx.skeletons
|
||||
local pid = hud.get_player()
|
||||
local invid, slot = player.get_inventory(pid)
|
||||
local itemid = inventory.get(invid, slot)
|
||||
|
||||
local cam = cameras.get("core:first-person")
|
||||
local bone = skeleton.index("hand", "item")
|
||||
|
||||
local offset = vec3.mul(vec3.sub(cam:get_pos(), {player.get_pos(pid)}), -1)
|
||||
|
||||
local rotation = cam:get_rot()
|
||||
|
||||
local angle = player.get_rot() - 90
|
||||
local cos = math.cos(angle / (180 / math.pi))
|
||||
local sin = math.sin(angle / (180 / math.pi))
|
||||
|
||||
local newX = offset[1] * cos - offset[3] * sin
|
||||
local newZ = offset[1] * sin + offset[3] * cos
|
||||
|
||||
offset[1] = newX
|
||||
offset[3] = newZ
|
||||
|
||||
local mat = mat4.translate(mat4.idt(), {0.06, 0.035, -0.1})
|
||||
mat4.scale(mat, {0.1, 0.1, 0.1}, mat)
|
||||
mat4.mul(rotation, mat, mat)
|
||||
mat4.rotate(mat, {0, 1, 0}, -90, mat)
|
||||
mat4.translate(mat, offset, mat)
|
||||
|
||||
skeleton.set_matrix("hand", bone, mat)
|
||||
skeleton.set_model("hand", bone, item.model_name(itemid))
|
||||
hud.default_hand_controller = update_hand
|
||||
end
|
||||
|
||||
function on_hud_render()
|
||||
|
||||
@ -12,22 +12,23 @@ local names = {
|
||||
for name, _ in pairs(user_props) do
|
||||
table.insert(names, name)
|
||||
end
|
||||
-- remove undefined properties
|
||||
for id, blockprops in pairs(block.properties) do
|
||||
for propname, value in pairs(blockprops) do
|
||||
|
||||
-- remove undefined properties and build tags set
|
||||
local function process_properties(lib)
|
||||
for id, props in pairs(lib.properties) do
|
||||
for propname, _ in pairs(props) do
|
||||
if not table.has(names, propname) then
|
||||
blockprops[propname] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
for id, itemprops in pairs(item.properties) do
|
||||
for propname, value in pairs(itemprops) do
|
||||
if not table.has(names, propname) then
|
||||
itemprops[propname] = nil
|
||||
props[propname] = nil
|
||||
end
|
||||
end
|
||||
|
||||
props.tags_set = lib.__get_tags(id)
|
||||
end
|
||||
end
|
||||
|
||||
process_properties(block)
|
||||
process_properties(item)
|
||||
|
||||
local function make_read_only(t)
|
||||
setmetatable(t, {
|
||||
__newindex = function()
|
||||
@ -57,6 +58,19 @@ local function cache_names(library)
|
||||
function library.index(name)
|
||||
return indices[name]
|
||||
end
|
||||
|
||||
function library.has_tag(id, tag)
|
||||
if id == nil then
|
||||
error("id is nil")
|
||||
end
|
||||
local props = library.properties[id]
|
||||
local tags_set = props.tags_set
|
||||
if tags_set then
|
||||
return tags_set[tag]
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
cache_names(block)
|
||||
|
||||
@ -157,6 +157,16 @@ console.add_command(
|
||||
end
|
||||
)
|
||||
|
||||
|
||||
console.add_command(
|
||||
"entity.spawn name:str x:num~pos.x y:num~pos.y z:num~pos.z",
|
||||
"Spawn entity with default parameters",
|
||||
function(args, kwargs)
|
||||
local eid = entities.spawn(args[1], {args[2], args[3], args[4]})
|
||||
return string.format("spawned %s at %s, %s, %s", unpack(args))
|
||||
end
|
||||
)
|
||||
|
||||
console.add_command(
|
||||
"entity.despawn entity:sel=$entity.selected",
|
||||
"Despawn entity",
|
||||
|
||||
@ -79,13 +79,20 @@ local function complete_app_lib(app)
|
||||
coroutine.yield()
|
||||
end
|
||||
|
||||
function app.sleep_until(predicate, max_ticks)
|
||||
function app.sleep_until(predicate, max_ticks, max_time)
|
||||
max_ticks = max_ticks or 1e9
|
||||
max_time = max_time or 1e9
|
||||
local ticks = 0
|
||||
while ticks < max_ticks and not predicate() do
|
||||
local start_time = os.clock()
|
||||
while ticks < max_ticks and
|
||||
os.clock() - start_time < max_time
|
||||
and not predicate() do
|
||||
app.tick()
|
||||
ticks = ticks + 1
|
||||
end
|
||||
if os.clock() - start_time >= max_time then
|
||||
error("timeout")
|
||||
end
|
||||
if ticks == max_ticks then
|
||||
error("max ticks exceed")
|
||||
end
|
||||
@ -174,6 +181,7 @@ if enable_experimental then
|
||||
require "core:internal/maths_inline"
|
||||
end
|
||||
|
||||
asserts = require "core:internal/asserts"
|
||||
events = require "core:internal/events"
|
||||
|
||||
function pack.unload(prefix)
|
||||
@ -429,6 +437,8 @@ function __vc_on_hud_open()
|
||||
hud.open_permanent("core:ingame_chat")
|
||||
end
|
||||
|
||||
local Schedule = require "core:schedule"
|
||||
|
||||
local ScheduleGroup_mt = {
|
||||
__index = {
|
||||
publish = function(self, schedule)
|
||||
@ -440,10 +450,11 @@ local ScheduleGroup_mt = {
|
||||
for id, schedule in pairs(self._schedules) do
|
||||
schedule:tick(dt)
|
||||
end
|
||||
self.common:tick(dt)
|
||||
end,
|
||||
remove = function(self, id)
|
||||
self._schedules[id] = nil
|
||||
end
|
||||
end,
|
||||
}
|
||||
}
|
||||
|
||||
@ -451,6 +462,7 @@ local function ScheduleGroup()
|
||||
return setmetatable({
|
||||
_next_schedule = 1,
|
||||
_schedules = {},
|
||||
common = Schedule()
|
||||
}, ScheduleGroup_mt)
|
||||
end
|
||||
|
||||
@ -527,6 +539,8 @@ function start_coroutine(chunk, name)
|
||||
local co = coroutine.create(function()
|
||||
local status, error = xpcall(chunk, function(err)
|
||||
local fullmsg = "error: "..string.match(err, ": (.+)").."\n"..debug.traceback()
|
||||
|
||||
if hud then
|
||||
gui.alert(fullmsg, function()
|
||||
if world.is_open() then
|
||||
__vc_app.close_world()
|
||||
@ -536,6 +550,7 @@ function start_coroutine(chunk, name)
|
||||
menu.page = "main"
|
||||
end
|
||||
end)
|
||||
end
|
||||
return fullmsg
|
||||
end)
|
||||
if not status then
|
||||
@ -573,6 +588,8 @@ function __process_post_runnables()
|
||||
end
|
||||
|
||||
network.__process_events()
|
||||
block.__process_register_events()
|
||||
block.__perform_ticks(time.delta())
|
||||
end
|
||||
|
||||
function time.post_runnable(runnable)
|
||||
|
||||
@ -668,3 +668,5 @@ end
|
||||
|
||||
bit.compile = require "core:bitwise/compiler"
|
||||
bit.execute = require "core:bitwise/executor"
|
||||
|
||||
random.Random = require "core:internal/random_generator"
|
||||
|
||||
@ -9,7 +9,7 @@
|
||||
|
||||
// lighting
|
||||
#define SKY_LIGHT_MUL 2.9
|
||||
#define SKY_LIGHT_TINT vec3(0.9, 0.8, 1.0)
|
||||
#define SKY_LIGHT_TINT (vec3(1.0, 0.95, 0.9) * 2.0)
|
||||
#define MIN_SKY_LIGHT vec3(0.2, 0.25, 0.33)
|
||||
|
||||
// fog
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
#include <constants>
|
||||
|
||||
vec3 pick_sky_color(samplerCube cubemap) {
|
||||
vec3 skyLightColor = texture(cubemap, vec3(0.4f, 0.0f, 0.4f)).rgb;
|
||||
vec3 skyLightColor = texture(cubemap, vec3(0.8f, 0.01f, 0.4f)).rgb;
|
||||
skyLightColor *= SKY_LIGHT_TINT;
|
||||
skyLightColor = min(vec3(1.0f), skyLightColor * SKY_LIGHT_MUL);
|
||||
skyLightColor = max(MIN_SKY_LIGHT, skyLightColor);
|
||||
|
||||
@ -25,6 +25,7 @@ Grant %{0} pack modification permission?=Выдаць дазвол на мады
|
||||
Error at line %{0}=Памылка ў радку %{0}
|
||||
Run=Запусціць
|
||||
Filter=Фільтр
|
||||
Are you sure you want to open the link: =Ці вы ўпэўненыя, што хочаце адкрыць спасылку:
|
||||
|
||||
editor.info.tooltip=CTRL+S - Захаваць\nCTRL+R - Запусціць\nCTRL+Z - Скасаваць\nCTRL+Y - Паўтарыць
|
||||
devtools.traceback=Стэк выклікаў (ад апошняга)
|
||||
|
||||
@ -7,6 +7,7 @@ Back=Zurück
|
||||
Continue=Weitermachen
|
||||
Add=Hinzufügen
|
||||
Converting world...=Weltkonvertierung im Gange...
|
||||
Are you sure you want to open the link: =Sind Sie sicher, dass Sie den Link öffnen möchten:
|
||||
|
||||
error.pack-not-found=Paket konnte nicht gefunden werden
|
||||
error.dependency-not-found=Die verwendete Abhängigkeit wurde nicht gefunden
|
||||
|
||||
@ -7,6 +7,7 @@ world.convert-block-layouts=Blocks fields have changes! Convert world files?
|
||||
pack.remove-confirm=Do you want to erase all pack(s) content from the world forever?
|
||||
error.pack-not-found=Could not to find pack
|
||||
error.dependency-not-found=Dependency pack is not found
|
||||
error.dependency-version-not-met=Dependency pack version is not met.
|
||||
world.delete-confirm=Do you want to delete world forever?
|
||||
world.generators.default=Default
|
||||
world.generators.flat=Flat
|
||||
|
||||
@ -19,6 +19,7 @@ Problems=Ongelmia
|
||||
Monitor=Valvonta
|
||||
Debug=Virheenkorjaus
|
||||
File=Tiedosto
|
||||
Are you sure you want to open the link: =Haluatko varmasti avata linkin:
|
||||
|
||||
devtools.traceback=Puhelupino (viimeisestä)
|
||||
error.pack-not-found=Pakettia ei löytynyt!
|
||||
|
||||
@ -7,6 +7,7 @@ Back=Powrót
|
||||
Continue=Kontynuacja
|
||||
Add=Dodać
|
||||
Converting world...=Konwersja świata w toku...
|
||||
Are you sure you want to open the link: =Czy na pewno chcesz otworzyć link:
|
||||
|
||||
error.pack-not-found=Nie udało się znaleźć pakietu
|
||||
|
||||
|
||||
@ -25,6 +25,7 @@ Grant %{0} pack modification permission?=Выдать разрешение на
|
||||
Error at line %{0}=Ошибка на строке %{0}
|
||||
Run=Запустить
|
||||
Filter=Фильтр
|
||||
Are you sure you want to open the link: =Вы уверены, что хотите открыть ссылку:
|
||||
|
||||
editor.info.tooltip=CTRL+S - Сохранить\nCTRL+R - Запустить\nCTRL+Z - Отменить\nCTRL+Y - Повторить
|
||||
devtools.traceback=Стек вызовов (от последнего)
|
||||
@ -32,6 +33,7 @@ devtools.output=Вывод
|
||||
|
||||
error.pack-not-found=Не удалось найти пакет
|
||||
error.dependency-not-found=Используемая зависимость не найдена
|
||||
error.dependency-version-not-met=Версия зависимости не соответствует необходимой
|
||||
pack.remove-confirm=Удалить весь поставляемый паком/паками контент из мира (безвозвратно)?
|
||||
|
||||
# Подсказки
|
||||
|
||||
@ -23,6 +23,7 @@ devtools.traceback=Стек викликів (від останнього)
|
||||
error.pack-not-found=Не вдалося знайти пакет
|
||||
error.dependency-not-found=Використовувана залежність не знайдена
|
||||
pack.remove-confirm=Видалити весь контент, що постачається паком/паками зі світу (безповоротно)?
|
||||
Are you sure you want to open the link: =Ви впевнені, що хочете відкрити посилання:
|
||||
|
||||
# Меню
|
||||
menu.Apply=Застосувати
|
||||
|
||||
@ -24,6 +24,7 @@ Save=Saqlash
|
||||
Grant %{0} pack modification permission?=%{0} to‘plamini o‘zgartirish ruxsatini berilsinmi?
|
||||
Error at line %{0}=%{0}-qatorida xatolik
|
||||
Run=Ishga tushirish
|
||||
Are you sure you want to open the link: =Haqiqatan ham havolani ochmoqchimisiz:
|
||||
|
||||
editor.info.tooltip=CTRL+S - Saqlash\nCTRL+R - Ishga tushirish\nCTRL+Z - Bekor qilish\nCTRL+Y - Qayta bajarish
|
||||
devtools.traceback=Chaqiruvlar steki (so`nggisidan boshlab)
|
||||
|
||||
@ -71,8 +71,8 @@ static auto process_program(const ResPaths& paths, const std::string& filename)
|
||||
|
||||
auto& preprocessor = *Shader::preprocessor;
|
||||
|
||||
auto vertex = preprocessor.process(vertexFile, vertexSource);
|
||||
auto fragment = preprocessor.process(fragmentFile, fragmentSource);
|
||||
auto vertex = preprocessor.process(vertexFile, vertexSource, false, {});
|
||||
auto fragment = preprocessor.process(fragmentFile, fragmentSource, false, {});
|
||||
return std::make_pair(vertex, fragment);
|
||||
}
|
||||
|
||||
@ -121,7 +121,7 @@ assetload::postfunc assetload::posteffect(
|
||||
|
||||
auto& preprocessor = *Shader::preprocessor;
|
||||
preprocessor.addHeader(
|
||||
"__effect__", preprocessor.process(effectFile, effectSource, true)
|
||||
"__effect__", preprocessor.process(effectFile, effectSource, true, {})
|
||||
);
|
||||
|
||||
auto [vertex, fragment] = process_program(paths, SHADERS_FOLDER + "/effect");
|
||||
|
||||
@ -22,6 +22,10 @@ void GLSLExtension::setPaths(const ResPaths* paths) {
|
||||
this->paths = paths;
|
||||
}
|
||||
|
||||
void GLSLExtension::setTraceOutput(bool enabled) {
|
||||
this->traceOutput = enabled;
|
||||
}
|
||||
|
||||
void GLSLExtension::loadHeader(const std::string& name) {
|
||||
if (paths == nullptr) {
|
||||
return;
|
||||
@ -29,7 +33,7 @@ void GLSLExtension::loadHeader(const std::string& name) {
|
||||
io::path file = paths->find("shaders/lib/" + name + ".glsl");
|
||||
std::string source = io::read_string(file);
|
||||
addHeader(name, {});
|
||||
addHeader(name, process(file, source, true));
|
||||
addHeader(name, process(file, source, true, {}));
|
||||
}
|
||||
|
||||
void GLSLExtension::addHeader(const std::string& name, ProcessingResult header) {
|
||||
@ -123,13 +127,22 @@ static Value default_value_for(Type type) {
|
||||
|
||||
class GLSLParser : public BasicParser<char> {
|
||||
public:
|
||||
GLSLParser(GLSLExtension& glsl, std::string_view file, std::string_view source, bool header)
|
||||
GLSLParser(
|
||||
GLSLExtension& glsl,
|
||||
std::string_view file,
|
||||
std::string_view source,
|
||||
bool header,
|
||||
const std::vector<std::string>& defines
|
||||
)
|
||||
: BasicParser(file, source), glsl(glsl) {
|
||||
if (!header) {
|
||||
ss << "#version " << GLSLExtension::VERSION << '\n';
|
||||
for (auto& entry : defines) {
|
||||
ss << "#define " << entry << '\n';
|
||||
}
|
||||
for (auto& entry : defines) {
|
||||
ss << "#define " << entry << '\n';
|
||||
}
|
||||
for (auto& entry : glsl.getDefines()) {
|
||||
ss << "#define " << entry.first << " " << entry.second << '\n';
|
||||
}
|
||||
uint linenum = 1;
|
||||
source_line(ss, linenum);
|
||||
@ -289,10 +302,34 @@ private:
|
||||
std::stringstream ss;
|
||||
};
|
||||
|
||||
static void trace_output(
|
||||
const io::path& file,
|
||||
const std::string& source,
|
||||
const GLSLExtension::ProcessingResult& result
|
||||
) {
|
||||
std::stringstream ss;
|
||||
ss << "export:trace/" << file.name();
|
||||
io::path outfile = ss.str();
|
||||
try {
|
||||
io::create_directories(outfile.parent());
|
||||
io::write_string(outfile, result.code);
|
||||
} catch (const std::runtime_error& err) {
|
||||
logger.error() << "error on saving GLSLExtension::preprocess output ("
|
||||
<< outfile.string() << "): " << err.what();
|
||||
}
|
||||
}
|
||||
|
||||
GLSLExtension::ProcessingResult GLSLExtension::process(
|
||||
const io::path& file, const std::string& source, bool header
|
||||
const io::path& file,
|
||||
const std::string& source,
|
||||
bool header,
|
||||
const std::vector<std::string>& defines
|
||||
) {
|
||||
std::string filename = file.string();
|
||||
GLSLParser parser(*this, filename, source, header);
|
||||
return parser.process();
|
||||
GLSLParser parser(*this, filename, source, header, defines);
|
||||
auto result = parser.process();
|
||||
if (traceOutput) {
|
||||
trace_output(file, source, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "io/io.hpp"
|
||||
#include "data/setting.hpp"
|
||||
#include "graphics/core/PostEffect.hpp"
|
||||
|
||||
class ResPaths;
|
||||
@ -19,6 +20,7 @@ public:
|
||||
};
|
||||
|
||||
void setPaths(const ResPaths* paths);
|
||||
void setTraceOutput(bool enabled);
|
||||
|
||||
void define(const std::string& name, std::string value);
|
||||
void undefine(const std::string& name);
|
||||
@ -37,7 +39,8 @@ public:
|
||||
ProcessingResult process(
|
||||
const io::path& file,
|
||||
const std::string& source,
|
||||
bool header = false
|
||||
bool header,
|
||||
const std::vector<std::string>& defines
|
||||
);
|
||||
|
||||
static inline std::string VERSION = "330 core";
|
||||
@ -46,4 +49,5 @@ private:
|
||||
std::unordered_map<std::string, std::string> defines;
|
||||
|
||||
const ResPaths* paths = nullptr;
|
||||
bool traceOutput = false;
|
||||
};
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
#include <string>
|
||||
|
||||
inline constexpr int ENGINE_VERSION_MAJOR = 0;
|
||||
inline constexpr int ENGINE_VERSION_MINOR = 29;
|
||||
inline constexpr int ENGINE_VERSION_MINOR = 30;
|
||||
|
||||
#ifdef NDEBUG
|
||||
inline constexpr bool ENGINE_DEBUG_BUILD = false;
|
||||
@ -14,7 +14,7 @@ inline constexpr bool ENGINE_DEBUG_BUILD = false;
|
||||
inline constexpr bool ENGINE_DEBUG_BUILD = true;
|
||||
#endif // NDEBUG
|
||||
|
||||
inline const std::string ENGINE_VERSION_STRING = "0.29";
|
||||
inline const std::string ENGINE_VERSION_STRING = "0.30";
|
||||
|
||||
/// @brief world regions format version
|
||||
inline constexpr uint REGION_FORMAT_VERSION = 3;
|
||||
|
||||
@ -35,13 +35,15 @@ Content::Content(
|
||||
UptrsMap<std::string, BlockMaterial> blockMaterials,
|
||||
UptrsMap<std::string, rigging::SkeletonConfig> skeletons,
|
||||
ResourceIndicesSet resourceIndices,
|
||||
dv::value defaults
|
||||
dv::value defaults,
|
||||
std::unordered_map<std::string, int> tags
|
||||
)
|
||||
: indices(std::move(indices)),
|
||||
packs(std::move(packs)),
|
||||
blockMaterials(std::move(blockMaterials)),
|
||||
skeletons(std::move(skeletons)),
|
||||
defaults(std::move(defaults)),
|
||||
tags(std::move(tags)),
|
||||
blocks(std::move(blocks)),
|
||||
items(std::move(items)),
|
||||
entities(std::move(entities)),
|
||||
|
||||
@ -176,6 +176,7 @@ class Content {
|
||||
UptrsMap<std::string, BlockMaterial> blockMaterials;
|
||||
UptrsMap<std::string, rigging::SkeletonConfig> skeletons;
|
||||
dv::value defaults = nullptr;
|
||||
std::unordered_map<std::string, int> tags;
|
||||
public:
|
||||
ContentUnitDefs<Block> blocks;
|
||||
ContentUnitDefs<ItemDef> items;
|
||||
@ -195,7 +196,8 @@ public:
|
||||
UptrsMap<std::string, BlockMaterial> blockMaterials,
|
||||
UptrsMap<std::string, rigging::SkeletonConfig> skeletons,
|
||||
ResourceIndicesSet resourceIndices,
|
||||
dv::value defaults
|
||||
dv::value defaults,
|
||||
std::unordered_map<std::string, int> tags
|
||||
);
|
||||
~Content();
|
||||
|
||||
@ -211,6 +213,14 @@ public:
|
||||
return defaults;
|
||||
}
|
||||
|
||||
int getTagIndex(const std::string& tag) const {
|
||||
const auto& found = tags.find(tag);
|
||||
if (found == tags.end()) {
|
||||
return -1;
|
||||
}
|
||||
return found->second;
|
||||
}
|
||||
|
||||
const rigging::SkeletonConfig* getSkeleton(const std::string& id) const;
|
||||
const rigging::SkeletonConfig& requireSkeleton(const std::string& id) const;
|
||||
const BlockMaterial* findBlockMaterial(const std::string& id) const;
|
||||
|
||||
@ -28,6 +28,9 @@ std::unique_ptr<Content> ContentBuilder::build() {
|
||||
// Generating runtime info
|
||||
def.rt.id = blockDefsIndices.size();
|
||||
def.rt.emissive = *reinterpret_cast<uint32_t*>(def.emission);
|
||||
for (const auto& tag : def.tags) {
|
||||
def.rt.tags.insert(tags.add(tag));
|
||||
}
|
||||
|
||||
if (def.variants) {
|
||||
for (auto& variant : def.variants->variants) {
|
||||
@ -58,7 +61,7 @@ std::unique_ptr<Content> ContentBuilder::build() {
|
||||
}
|
||||
|
||||
blockDefsIndices.push_back(&def);
|
||||
groups->insert(def.defaults.drawGroup); // FIXME
|
||||
groups->insert(def.defaults.drawGroup); // FIXME: variants
|
||||
}
|
||||
|
||||
std::vector<ItemDef*> itemDefsIndices;
|
||||
@ -93,7 +96,8 @@ std::unique_ptr<Content> ContentBuilder::build() {
|
||||
std::move(blockMaterials),
|
||||
std::move(skeletons),
|
||||
std::move(resourceIndices),
|
||||
std::move(defaults)
|
||||
std::move(defaults),
|
||||
std::move(tags.map)
|
||||
);
|
||||
|
||||
// Now, it's time to resolve foreign keys
|
||||
|
||||
@ -62,6 +62,27 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
struct TagsIndices {
|
||||
int nextIndex = 1;
|
||||
std::unordered_map<std::string, int> map;
|
||||
|
||||
int add(const std::string& tag) {
|
||||
const auto& found = map.find(tag);
|
||||
if (found != map.end()) {
|
||||
return found->second;
|
||||
}
|
||||
return map[tag] = nextIndex++;
|
||||
}
|
||||
|
||||
int indexOf(const std::string& tag) {
|
||||
const auto& found = map.find(tag);
|
||||
if (found == map.end()) {
|
||||
return -1;
|
||||
}
|
||||
return found->second;
|
||||
}
|
||||
};
|
||||
|
||||
class ContentBuilder {
|
||||
UptrsMap<std::string, BlockMaterial> blockMaterials;
|
||||
UptrsMap<std::string, rigging::SkeletonConfig> skeletons;
|
||||
@ -74,6 +95,7 @@ public:
|
||||
ContentUnitBuilder<GeneratorDef> generators {allNames, ContentType::GENERATOR};
|
||||
ResourceIndicesSet resourceIndices {};
|
||||
dv::value defaults = nullptr;
|
||||
TagsIndices tags {};
|
||||
|
||||
~ContentBuilder();
|
||||
|
||||
|
||||
@ -288,6 +288,7 @@ void ContentLoader::loadContent(const dv::value& root) {
|
||||
item.iconType = ItemIconType::BLOCK;
|
||||
item.icon = def.name;
|
||||
item.placingBlock = def.name;
|
||||
item.tags = def.tags;
|
||||
|
||||
for (uint j = 0; j < 4; j++) {
|
||||
item.emission[j] = def.emission[j];
|
||||
@ -412,6 +413,25 @@ void ContentLoader::load() {
|
||||
if (io::exists(contentFile)) {
|
||||
loadContent(io::read_json(contentFile));
|
||||
}
|
||||
|
||||
// Load attached tags
|
||||
io::path tagsFile = folder / "tags.toml";
|
||||
if (io::exists(tagsFile)) {
|
||||
auto tagsMap = io::read_object(tagsFile);
|
||||
for (const auto& [key, list] : tagsMap.asObject()) {
|
||||
for (const auto& id : list) {
|
||||
const auto& stringId = id.asString();
|
||||
if (auto block = builder.blocks.get(stringId)) {
|
||||
block->tags.push_back(key);
|
||||
if (auto item = builder.items.get(stringId + BLOCK_ITEM_SUFFIX)) {
|
||||
item->tags.push_back(key);
|
||||
}
|
||||
} else if (auto item = builder.items.get(stringId)) {
|
||||
item->tags.push_back(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <class T>
|
||||
|
||||
@ -118,21 +118,58 @@ ContentPack ContentPack::read(const io::path& folder) {
|
||||
const auto& dependencies = *found;
|
||||
for (const auto& elem : dependencies) {
|
||||
std::string depName = elem.asString();
|
||||
auto level = DependencyLevel::required;
|
||||
auto level = DependencyLevel::REQUIRED;
|
||||
switch (depName.at(0)) {
|
||||
case '!':
|
||||
depName = depName.substr(1);
|
||||
break;
|
||||
case '?':
|
||||
depName = depName.substr(1);
|
||||
level = DependencyLevel::optional;
|
||||
level = DependencyLevel::OPTIONAL;
|
||||
break;
|
||||
case '~':
|
||||
depName = depName.substr(1);
|
||||
level = DependencyLevel::weak;
|
||||
level = DependencyLevel::WEAK;
|
||||
break;
|
||||
}
|
||||
pack.dependencies.push_back({level, depName});
|
||||
|
||||
std::string depVer = "*";
|
||||
std::string depVerOperator = "=";
|
||||
|
||||
size_t versionPos = depName.rfind("@");
|
||||
if (versionPos != std::string::npos) {
|
||||
depVer = depName.substr(versionPos + 1);
|
||||
depName = depName.substr(0, versionPos);
|
||||
|
||||
if (depVer.size() >= 2) {
|
||||
std::string op = depVer.substr(0, 2);
|
||||
std::uint8_t op_size = 0;
|
||||
|
||||
// Two symbol operators
|
||||
if (op == ">=" || op == "=>" || op == "<=" || op == "=<") {
|
||||
op_size = 2;
|
||||
depVerOperator = op;
|
||||
}
|
||||
|
||||
// One symbol operators
|
||||
else {
|
||||
op = depVer.substr(0, 1);
|
||||
|
||||
if (op == ">" || op == "<") {
|
||||
op_size = 1;
|
||||
depVerOperator = op;
|
||||
}
|
||||
}
|
||||
|
||||
depVer = depVer.substr(op_size);
|
||||
} else {
|
||||
if (depVer == ">" || depVer == "<"){
|
||||
depVer = "*";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pack.dependencies.push_back({level, depName, depVer, depVerOperator});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -25,16 +25,23 @@ public:
|
||||
io::path getFolder() const;
|
||||
};
|
||||
|
||||
enum class DependencyVersionOperator {
|
||||
EQUAL, GREATHER, LESS,
|
||||
GREATHER_OR_EQUAL, LESS_OR_EQUAL
|
||||
};
|
||||
|
||||
enum class DependencyLevel {
|
||||
required, // dependency must be installed
|
||||
optional, // dependency will be installed if found
|
||||
weak, // only affects packs order
|
||||
REQUIRED, // dependency must be installed
|
||||
OPTIONAL, // dependency will be installed if found
|
||||
WEAK, // only affects packs order
|
||||
};
|
||||
|
||||
/// @brief Content-pack that should be installed earlier the dependent
|
||||
struct DependencyPack {
|
||||
DependencyLevel level;
|
||||
std::string id;
|
||||
std::string version;
|
||||
std::string op;
|
||||
};
|
||||
|
||||
struct ContentPackStats {
|
||||
|
||||
67
src/content/ContentPackVersion.cpp
Normal file
67
src/content/ContentPackVersion.cpp
Normal file
@ -0,0 +1,67 @@
|
||||
#include "ContentPackVersion.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
|
||||
#include "coders/commons.hpp"
|
||||
|
||||
Version::Version(const std::string& version) {
|
||||
major = 0;
|
||||
minor = 0;
|
||||
patch = 0;
|
||||
|
||||
std::vector<int> parts;
|
||||
|
||||
std::stringstream ss(version);
|
||||
std::string part;
|
||||
while (std::getline(ss, part, '.')) {
|
||||
if (!part.empty()) {
|
||||
parts.push_back(std::stoi(part));
|
||||
}
|
||||
}
|
||||
|
||||
if (parts.size() > 0) major = parts[0];
|
||||
if (parts.size() > 1) minor = parts[1];
|
||||
if (parts.size() > 2) patch = parts[2];
|
||||
}
|
||||
|
||||
DependencyVersionOperator Version::string_to_operator(const std::string& op) {
|
||||
if (op == "=")
|
||||
return DependencyVersionOperator::EQUAL;
|
||||
else if (op == ">")
|
||||
return DependencyVersionOperator::GREATHER;
|
||||
else if (op == "<")
|
||||
return DependencyVersionOperator::LESS;
|
||||
else if (op == ">=" || op == "=>")
|
||||
return DependencyVersionOperator::GREATHER_OR_EQUAL;
|
||||
else if (op == "<=" || op == "=<")
|
||||
return DependencyVersionOperator::LESS_OR_EQUAL;
|
||||
else
|
||||
return DependencyVersionOperator::EQUAL;
|
||||
}
|
||||
|
||||
bool isNumber(const std::string& s) {
|
||||
return !s.empty() && std::all_of(s.begin(), s.end(), ::is_digit);
|
||||
}
|
||||
|
||||
bool Version::matches_pattern(const std::string& version) {
|
||||
for (char c : version) {
|
||||
if (!isdigit(c) && c != '.') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
std::stringstream ss(version);
|
||||
|
||||
std::vector<std::string> parts;
|
||||
std::string part;
|
||||
while (std::getline(ss, part, '.')) {
|
||||
if (part.empty()) return false;
|
||||
if (!isNumber(part)) return false;
|
||||
|
||||
parts.push_back(part);
|
||||
}
|
||||
|
||||
return parts.size() == 2 || parts.size() == 3;
|
||||
}
|
||||
57
src/content/ContentPackVersion.hpp
Normal file
57
src/content/ContentPackVersion.hpp
Normal file
@ -0,0 +1,57 @@
|
||||
#include <string>
|
||||
|
||||
#include "content/ContentPack.hpp"
|
||||
|
||||
class Version {
|
||||
public:
|
||||
int major;
|
||||
int minor;
|
||||
int patch;
|
||||
|
||||
Version(const std::string& version);
|
||||
|
||||
bool operator==(const Version& other) const {
|
||||
return major == other.major && minor == other.minor &&
|
||||
patch == other.patch;
|
||||
}
|
||||
|
||||
bool operator<(const Version& other) const {
|
||||
if (major != other.major) return major < other.major;
|
||||
if (minor != other.minor) return minor < other.minor;
|
||||
return patch < other.patch;
|
||||
}
|
||||
|
||||
bool operator>(const Version& other) const {
|
||||
return other < *this;
|
||||
}
|
||||
|
||||
bool operator>=(const Version& other) const {
|
||||
return !(*this < other);
|
||||
}
|
||||
|
||||
bool operator<=(const Version& other) const {
|
||||
return !(*this > other);
|
||||
}
|
||||
|
||||
bool process_operator(const std::string& op, const Version& other) const {
|
||||
auto dep_op = Version::string_to_operator(op);
|
||||
|
||||
switch (dep_op) {
|
||||
case DependencyVersionOperator::EQUAL:
|
||||
return *this == other;
|
||||
case DependencyVersionOperator::GREATHER:
|
||||
return *this > other;
|
||||
case DependencyVersionOperator::LESS:
|
||||
return *this < other;
|
||||
case DependencyVersionOperator::LESS_OR_EQUAL:
|
||||
return *this <= other;
|
||||
case DependencyVersionOperator::GREATHER_OR_EQUAL:
|
||||
return *this >= other;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static DependencyVersionOperator string_to_operator(const std::string& op);
|
||||
static bool matches_pattern(const std::string& version);
|
||||
};
|
||||
@ -3,6 +3,7 @@
|
||||
#include <queue>
|
||||
#include <sstream>
|
||||
|
||||
#include "ContentPackVersion.hpp"
|
||||
#include "util/listutil.hpp"
|
||||
|
||||
PacksManager::PacksManager() = default;
|
||||
@ -90,7 +91,7 @@ static bool resolve_dependencies(
|
||||
}
|
||||
auto found = packs.find(dep.id);
|
||||
bool exists = found != packs.end();
|
||||
if (!exists && dep.level == DependencyLevel::required) {
|
||||
if (!exists && dep.level == DependencyLevel::REQUIRED) {
|
||||
throw contentpack_error(
|
||||
dep.id, io::path(), "dependency of '" + pack->id + "'"
|
||||
);
|
||||
@ -99,15 +100,32 @@ static bool resolve_dependencies(
|
||||
// ignored for optional or weak dependencies
|
||||
continue;
|
||||
}
|
||||
if (resolveWeaks && dep.level == DependencyLevel::weak) {
|
||||
if (resolveWeaks && dep.level == DependencyLevel::WEAK) {
|
||||
// dependency pack is found but not added yet
|
||||
// resolveWeaks is used on second iteration, so it's will not be
|
||||
// added
|
||||
continue;
|
||||
}
|
||||
|
||||
auto dep_pack = found -> second;
|
||||
|
||||
if (Version::matches_pattern(dep.version) && Version::matches_pattern(dep_pack.version)
|
||||
&& Version(dep_pack.version)
|
||||
.process_operator(dep.op, Version(dep.version))
|
||||
) {
|
||||
// dependency pack version meets the required one
|
||||
continue;
|
||||
} else if (dep.version == "*" || dep.version == dep_pack.version){
|
||||
// fallback: dependency pack version also meets required one
|
||||
continue;
|
||||
} else {
|
||||
throw contentpack_error(
|
||||
dep.id, io::path(), "does not meet required version '" + dep.op + dep.version +"' of '" + pack->id + "'"
|
||||
);
|
||||
}
|
||||
|
||||
if (!util::contains(allNames, dep.id) &&
|
||||
dep.level != DependencyLevel::weak) {
|
||||
dep.level != DependencyLevel::WEAK) {
|
||||
allNames.push_back(dep.id);
|
||||
queue.push(&found->second);
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
#define VC_ENABLE_REFLECTION
|
||||
#include "ContentUnitLoader.hpp"
|
||||
#include "ContentLoadingCommons.hpp"
|
||||
|
||||
#include "../ContentBuilder.hpp"
|
||||
#include "coders/json.hpp"
|
||||
@ -87,20 +88,8 @@ template<> void ContentUnitLoader<Block>::loadUnit(
|
||||
Block& def, const std::string& name, const io::path& file
|
||||
) {
|
||||
auto root = io::read_json(file);
|
||||
if (def.properties == nullptr) {
|
||||
def.properties = dv::object();
|
||||
def.properties["name"] = name;
|
||||
}
|
||||
for (auto& [key, value] : root.asObject()) {
|
||||
auto pos = key.rfind('@');
|
||||
if (pos == std::string::npos) {
|
||||
def.properties[key] = value;
|
||||
continue;
|
||||
}
|
||||
auto field = key.substr(0, pos);
|
||||
auto suffix = key.substr(pos + 1);
|
||||
process_method(def.properties, suffix, field, value);
|
||||
}
|
||||
process_properties(def, name, root);
|
||||
process_tags(def, root);
|
||||
|
||||
if (root.has("parent")) {
|
||||
const auto& parentName = root["parent"].asString();
|
||||
|
||||
37
src/content/loading/ContentLoadingCommons.hpp
Normal file
37
src/content/loading/ContentLoadingCommons.hpp
Normal file
@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include "data/dv.hpp"
|
||||
|
||||
#include <string>
|
||||
|
||||
template <typename T>
|
||||
inline void process_properties(T& def, const std::string& name, const dv::value& root) {
|
||||
if (def.properties == nullptr) {
|
||||
def.properties = dv::object();
|
||||
def.properties["name"] = name;
|
||||
}
|
||||
for (auto& [key, value] : root.asObject()) {
|
||||
auto pos = key.rfind('@');
|
||||
if (pos == std::string::npos) {
|
||||
def.properties[key] = value;
|
||||
continue;
|
||||
}
|
||||
auto field = key.substr(0, pos);
|
||||
auto suffix = key.substr(pos + 1);
|
||||
process_method(def.properties, suffix, field, value);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline void process_tags(T& def, const dv::value& root) {
|
||||
if (!root.has("tags")) {
|
||||
return;
|
||||
}
|
||||
const auto& tags = root["tags"];
|
||||
for (const auto& tagValue : tags) {
|
||||
if (!tagValue.isString()) {
|
||||
continue;
|
||||
}
|
||||
def.tags.push_back(tagValue.asString());
|
||||
}
|
||||
}
|
||||
@ -30,7 +30,18 @@ template<> void ContentUnitLoader<EntityDef>::loadUnit(
|
||||
|
||||
if (auto found = root.at("components")) {
|
||||
for (const auto& elem : *found) {
|
||||
def.components.emplace_back(elem.asString());
|
||||
std::string name;
|
||||
dv::value params;
|
||||
if (elem.isObject()) {
|
||||
name = elem["name"].asString();
|
||||
if (elem.has("args")) {
|
||||
params = elem["args"];
|
||||
}
|
||||
} else {
|
||||
name = elem.asString();
|
||||
}
|
||||
def.components.push_back(ComponentInstance {
|
||||
std::move(name), std::move(params)});
|
||||
}
|
||||
}
|
||||
if (auto found = root.at("hitbox")) {
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
#define VC_ENABLE_REFLECTION
|
||||
#include "ContentUnitLoader.hpp"
|
||||
#include "ContentLoadingCommons.hpp"
|
||||
|
||||
#include "../ContentBuilder.hpp"
|
||||
#include "coders/json.hpp"
|
||||
@ -12,11 +13,13 @@
|
||||
|
||||
static debug::Logger logger("item-content-loader");
|
||||
|
||||
|
||||
template<> void ContentUnitLoader<ItemDef>::loadUnit(
|
||||
ItemDef& def, const std::string& name, const io::path& file
|
||||
) {
|
||||
auto root = io::read_json(file);
|
||||
def.properties = root;
|
||||
process_properties(def, name, root);
|
||||
process_tags(def, root);
|
||||
|
||||
if (root.has("parent")) {
|
||||
const auto& parentName = root["parent"].asString();
|
||||
|
||||
@ -46,12 +46,12 @@ namespace dv {
|
||||
if (!map.has(key)) {
|
||||
return;
|
||||
}
|
||||
auto& list = map[key];
|
||||
const auto& srcList = map[key];
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
if constexpr (std::is_floating_point<T>()) {
|
||||
vec[i] = list[i].asNumber();
|
||||
vec[i] = srcList[i].asNumber();
|
||||
} else {
|
||||
vec[i] = list[i].asInteger();
|
||||
vec[i] = srcList[i].asInteger();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -143,6 +143,12 @@ void Engine::initializeClient() {
|
||||
},
|
||||
true
|
||||
));
|
||||
keepAlive(settings.debug.doTraceShaders.observe(
|
||||
[](bool value) {
|
||||
Shader::preprocessor->setTraceOutput(value);
|
||||
},
|
||||
true
|
||||
));
|
||||
}
|
||||
|
||||
void Engine::initialize(CoreParameters coreParameters) {
|
||||
|
||||
@ -12,12 +12,14 @@
|
||||
#include "graphics/render/WorldRenderer.hpp"
|
||||
#include "graphics/render/ParticlesRenderer.hpp"
|
||||
#include "graphics/render/ChunksRenderer.hpp"
|
||||
#include "graphics/render/DebugLinesRenderer.hpp"
|
||||
#include "logic/scripting/scripting.hpp"
|
||||
#include "network/Network.hpp"
|
||||
#include "objects/Player.hpp"
|
||||
#include "objects/Players.hpp"
|
||||
#include "objects/Entities.hpp"
|
||||
#include "objects/EntityDef.hpp"
|
||||
#include "objects/Entity.hpp"
|
||||
#include "physics/Hitbox.hpp"
|
||||
#include "util/stringutil.hpp"
|
||||
#include "voxels/Block.hpp"
|
||||
@ -44,6 +46,7 @@ static std::shared_ptr<Label> create_label(GUI& gui, wstringsupplier supplier) {
|
||||
// TODO: move to xml
|
||||
// TODO: move to xml finally
|
||||
// TODO: move to xml finally
|
||||
// TODO: move to xml finally
|
||||
std::shared_ptr<UINode> create_debug_panel(
|
||||
Engine& engine,
|
||||
Level& level,
|
||||
@ -260,6 +263,18 @@ std::shared_ptr<UINode> create_debug_panel(
|
||||
});
|
||||
panel->add(checkbox);
|
||||
}
|
||||
{
|
||||
auto checkbox = std::make_shared<FullCheckBox>(
|
||||
gui, L"Show Paths", glm::vec2(400, 24)
|
||||
);
|
||||
checkbox->setSupplier([=]() {
|
||||
return DebugLinesRenderer::showPaths;
|
||||
});
|
||||
checkbox->setConsumer([=](bool checked) {
|
||||
DebugLinesRenderer::showPaths = checked;
|
||||
});
|
||||
panel->add(checkbox);
|
||||
}
|
||||
{
|
||||
auto checkbox = std::make_shared<FullCheckBox>(
|
||||
gui, L"Show Generator Minimap", glm::vec2(400, 24)
|
||||
|
||||
@ -207,6 +207,9 @@ Hud::Hud(Engine& engine, LevelFrontend& frontend, Player& player)
|
||||
}
|
||||
|
||||
Hud::~Hud() {
|
||||
if (input.isCursorLocked()) {
|
||||
input.toggleCursor();
|
||||
}
|
||||
// removing all controlled ui
|
||||
for (auto& element : elements) {
|
||||
onRemove(element);
|
||||
@ -339,7 +342,7 @@ void Hud::update(bool visible) {
|
||||
if (!gui.isFocusCaught()) {
|
||||
processInput(visible);
|
||||
}
|
||||
if ((isMenuOpen || inventoryOpen) == input.getCursor().locked) {
|
||||
if ((isMenuOpen || inventoryOpen) == input.isCursorLocked()) {
|
||||
input.toggleCursor();
|
||||
}
|
||||
|
||||
|
||||
@ -176,7 +176,7 @@ void LevelScreen::saveWorldPreview() {
|
||||
static_cast<uint>(previewSize)}
|
||||
);
|
||||
|
||||
renderer->draw(ctx, camera, false, true, 0.0f, *postProcessing);
|
||||
renderer->renderFrame(ctx, camera, false, true, 0.0f, *postProcessing);
|
||||
auto image = postProcessing->toImage();
|
||||
image->flipY();
|
||||
imageio::write("world:preview.png", image.get());
|
||||
@ -263,7 +263,7 @@ void LevelScreen::draw(float delta) {
|
||||
if (!hud->isPause()) {
|
||||
scripting::on_entities_render(engine.getTime().getDelta());
|
||||
}
|
||||
renderer->draw(
|
||||
renderer->renderFrame(
|
||||
ctx, *camera, hudVisible, hud->isPause(), delta, *postProcessing
|
||||
);
|
||||
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
#include "ImageData.hpp"
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <assert.h>
|
||||
#include <stdexcept>
|
||||
#include <cstring>
|
||||
@ -187,6 +188,41 @@ void ImageData::drawLine(int x1, int y1, int x2, int y2, const glm::ivec4& color
|
||||
}
|
||||
}
|
||||
|
||||
template<uint channels>
|
||||
static void draw_rect(ImageData& image, int dstX, int dstY, int width, int height, const glm::ivec4& color) {
|
||||
ubyte* data = image.getData();
|
||||
int imageWidth = image.getWidth();
|
||||
int imageHeight = image.getHeight();
|
||||
|
||||
int x1 = glm::min(glm::max(dstX, 0), imageWidth - 1);
|
||||
int y1 = glm::min(glm::max(dstY, 0), imageHeight - 1);
|
||||
|
||||
int x2 = glm::min(glm::max(dstX + width, 0), imageWidth - 1);
|
||||
int y2 = glm::min(glm::max(dstY + height, 0), imageHeight - 1);
|
||||
|
||||
for (int y = y1; y <= y2; y++) {
|
||||
for (int x = x1; x <= x2; x++) {
|
||||
int index = (y * imageWidth + x) * channels;
|
||||
for (int i = 0; i < channels; i++) {
|
||||
data[index + i] = color[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ImageData::drawRect(int x, int y, int width, int height, const glm::ivec4& color) {
|
||||
switch (format) {
|
||||
case ImageFormat::rgb888:
|
||||
draw_rect<3>(*this, x, y, width, height, color);
|
||||
break;
|
||||
case ImageFormat::rgba8888:
|
||||
draw_rect<4>(*this, x, y, width, height, color);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void ImageData::blitRGB_on_RGBA(const ImageData& image, int x, int y) {
|
||||
ubyte* source = image.getData();
|
||||
uint srcwidth = image.getWidth();
|
||||
|
||||
@ -28,6 +28,7 @@ public:
|
||||
void flipY();
|
||||
|
||||
void drawLine(int x1, int y1, int x2, int y2, const glm::ivec4& color);
|
||||
void drawRect(int x, int y, int width, int height, const glm::ivec4& color);
|
||||
void blit(const ImageData& image, int x, int y);
|
||||
void extrude(int x, int y, int w, int h);
|
||||
void fixAlphaColor();
|
||||
|
||||
@ -2,7 +2,6 @@
|
||||
|
||||
#include <exception>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <sstream>
|
||||
#include <filesystem>
|
||||
|
||||
@ -138,15 +137,21 @@ glshader compile_shader(GLenum type, const GLchar* source, const std::string& fi
|
||||
}
|
||||
|
||||
static GLuint compile_program(
|
||||
const Shader::Source& vertexSource, const Shader::Source& fragmentSource
|
||||
const Shader::Source& vertexSource,
|
||||
const Shader::Source& fragmentSource,
|
||||
const std::vector<std::string>& defines
|
||||
) {
|
||||
auto& preprocessor = *Shader::preprocessor;
|
||||
|
||||
auto vertexCode = std::move(
|
||||
preprocessor.process(vertexSource.file, vertexSource.code).code
|
||||
preprocessor
|
||||
.process(vertexSource.file, vertexSource.code, false, defines)
|
||||
.code
|
||||
);
|
||||
auto fragmentCode = std::move(
|
||||
preprocessor.process(fragmentSource.file, fragmentSource.code).code
|
||||
preprocessor
|
||||
.process(fragmentSource.file, fragmentSource.code, false, defines)
|
||||
.code
|
||||
);
|
||||
|
||||
const GLchar* vCode = vertexCode.c_str();
|
||||
@ -176,8 +181,8 @@ static GLuint compile_program(
|
||||
return program;
|
||||
}
|
||||
|
||||
void Shader::recompile() {
|
||||
GLuint newProgram = compile_program(vertexSource, fragmentSource);
|
||||
void Shader::recompile(const std::vector<std::string>& defines) {
|
||||
GLuint newProgram = compile_program(vertexSource, fragmentSource, defines);
|
||||
glDeleteProgram(id);
|
||||
id = newProgram;
|
||||
uniformLocations.clear();
|
||||
@ -188,7 +193,7 @@ std::unique_ptr<Shader> Shader::create(
|
||||
Source&& vertexSource, Source&& fragmentSource
|
||||
) {
|
||||
return std::make_unique<Shader>(
|
||||
compile_program(vertexSource, fragmentSource),
|
||||
compile_program(vertexSource, fragmentSource, {}),
|
||||
std::move(vertexSource),
|
||||
std::move(fragmentSource)
|
||||
);
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
@ -50,7 +51,7 @@ public:
|
||||
void uniform4v(const std::string& name, int length, const float* v);
|
||||
|
||||
/// @brief Re-preprocess source code and re-compile shader program
|
||||
void recompile();
|
||||
void recompile(const std::vector<std::string>& defines);
|
||||
|
||||
/// @brief Create shader program using vertex and fragment shaders source.
|
||||
/// @return linked shader program containing vertex and fragment shaders
|
||||
|
||||
@ -1,47 +0,0 @@
|
||||
#include "ShadowMap.hpp"
|
||||
|
||||
#include <GL/glew.h>
|
||||
|
||||
ShadowMap::ShadowMap(int resolution) : resolution(resolution) {
|
||||
glGenTextures(1, &depthMap);
|
||||
glBindTexture(GL_TEXTURE_2D, depthMap);
|
||||
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT,
|
||||
resolution, resolution, 0, GL_DEPTH_COMPONENT, GL_FLOAT, NULL);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_BORDER);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_REF_TO_TEXTURE);
|
||||
float border[4] {1.0f, 1.0f, 1.0f, 1.0f};
|
||||
glTexParameterfv(GL_TEXTURE_2D,GL_TEXTURE_BORDER_COLOR, border);
|
||||
|
||||
glGenFramebuffers(1, &fbo);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
|
||||
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, depthMap, 0);
|
||||
glDrawBuffer(GL_NONE);
|
||||
glReadBuffer(GL_NONE);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
}
|
||||
|
||||
ShadowMap::~ShadowMap() {
|
||||
glDeleteFramebuffers(1, &fbo);
|
||||
glDeleteTextures(1, &depthMap);
|
||||
}
|
||||
|
||||
void ShadowMap::bind() {
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
|
||||
glClear(GL_DEPTH_BUFFER_BIT);
|
||||
}
|
||||
|
||||
void ShadowMap::unbind() {
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
}
|
||||
|
||||
uint ShadowMap::getDepthMap() const {
|
||||
return depthMap;
|
||||
}
|
||||
|
||||
int ShadowMap::getResolution() const {
|
||||
return resolution;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user