Merge pull request #255 from MihailRis/entities

Entities
This commit is contained in:
MihailRis 2024-07-17 15:19:47 +03:00 committed by GitHub
commit a02d626c0a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
166 changed files with 6627 additions and 1357 deletions

1
.gitignore vendored
View File

@ -45,3 +45,4 @@ appimage-build/
/res/content/*
!/res/content/base
*.mtl

View File

@ -82,12 +82,12 @@ Block is not a physical obstacle if **false**
An array of 6 numbers describing an offset an size of a block hitbox.
Array *\[0.25, 0.0, 0.5, 0.75, 0.4, 0.3\]* describes hitbox width:
- 0.75m width (from east to west)
- 0.4m height
- 0.3m length (from south to north)
- offset 0.25m east
- offset 0.0m up
- offset 0.5m north
- 0.75m width (from east to west)
- 0.4m height
- 0.3m length (from south to north)
### *grounded*

101
doc/en/entity-properties.md Normal file
View File

@ -0,0 +1,101 @@
# Entity properties
## Logic
### *components*
Defines components and the order in which they are initialized.
```json
"components": [
list of components
]
```
Example:
```json
"components": [
"base:drop"
]
```
The components code should be in `scripts/components`.
## Physics
### *hitbox*
An array of three numbers indicating the size of the entity's hitbox.
Example:
```json
"hitbox": [0.6, 1.8, 0.6]
```
### *body-type*
Determines how the physics engine will work with it.
- *dynamic* - default type. The physics engine calculates movement and collisions.
- *kinematic* - only movement is calculated, without collisions.
### *blocking*
Determines whether the entity blocks installation of blocks.
*In the future will also block other entities movement.*
Default value: *true*.
### *sensors*
A sensor is an area attached to a physical body that detects the entry of other bodies into it.
- When a body enter, the *on_sensor_enter* event is triggered.
- When a body exit, the *on_sensor_exit* event is called.
Sensors are indexed in the same order as they are presented in the list, starting from 0.
The following types (shapes) of sensors exist:
- *radius* is the simplest sensor. Defines the area around the center of the hitbox. The following values are specified:
- radius - number.
- *aabb* - a rectangular area that changes position depending on the rotation of the entity. **The area itself does not rotate.** The following values are specified:
- three numbers x, y, z of the minimal corner of the area.
- three numbers x, y, z of the opposite corner of the area.
Example:
```json
"sensors": [
["aabb", -0.2, -0.2, -0.2, 0.2, 0.2, 0.2],
["radius", 1.6]
]
```
0. A rectangular area with a width, height and length of 0.4 m, centered at 0.0.
1. Radial area with a radius of 1.6 m.
## View
### *skeleton-name*
The default value is the same as the entity name. Determines which skeleton will be used by the entity. See [rigging](rigging.md).
## Saving/Loading
In addition to custom components, the engine automatically saves data from the built-in ones: transform, rigidbody, skeleton.
There is a set of flags that allow you to specify which data will be saved and which will not.
(Boolean values are specified)
| Title | Target | Default |
| ---------------------- | ---------------------------------------------------- | ------- |
| save | the entity itself | true |
| save-skeleton-pose | skeleton pose | false |
| save-skeleton-textures | dynamically assigned textures | false |
| save-body-velocity | body velocity | true |
| save-body-settings | changed body settings <br>(type, damping, crouching) | false |

View File

@ -1,6 +1,8 @@
# Visual
# Item properties
## *icon-type* and *icon* itself
## Visual
### *icon-type* and *icon* itself
Icon type defines a source of an item image displayed in inventory.
- **none** - invisible type, used for *core:empty* only (empty item, like the air block). May be removed in future updates.
@ -10,9 +12,9 @@ Icon type defines a source of an item image displayed in inventory.
- **items** (generated from *png* files in *res/textures/items/*)
- **block** - block preview. Block ID must be specified in **icon** property. Example: *base:wood*.
# Behaviour
## Behaviour
## *placing-block*
### *placing-block*
Specifies what block will be placed on RMB click. Automatically specified in generated items.
@ -22,7 +24,7 @@ Example: an items that places bazalt blocks:
"placing-block": "base:bazalt"
```
## *emission*
### *emission*
Light emitted when player holds the item in hand.
@ -34,6 +36,6 @@ Examples:
- *\[7, 0, 0\]* - dim red light
- *\[0, 0, 0\]* - no emission (default value)
## *stack-size*
### *stack-size*
Maximal number of an item units in one slot. Default - 64.

View File

@ -12,3 +12,5 @@
- [Scripting](scripting.md)
- [Console](console.md)
- [Block models](block-models.md)
- [Rigging](rigging.md)
- [Resources (resources.json)](resources.md)

22
doc/en/resources.md Normal file
View File

@ -0,0 +1,22 @@
# Resources
Resources include:
- cameras
- effects slots
- framebuffers
- and other limited resources
At the moment only **cameras** are implemented.
The resources requested by the pack are specified through the *resources.json* file in the format:
```json
{
"resource-type": [
"resources",
"names"
]
}
```
After loading the pack, resource names will have the pack prefix. For example camera
*cinematic* in the base package will be *base:cinematic*.

46
doc/en/rigging.md Normal file
View File

@ -0,0 +1,46 @@
# Rigging
## Skeletons
Entity skeletons are created via json files in the *skeletons* folder.
> [!IMPORTANT]
>
> The skeleton is a non-indexable content unit. When loading it, the pack prefix is added to the name (example: *drop* in the base pack -> *base:drop*).
A skeletal element, or bone, consists of a transformation matrix defining its position, rotation and scale relative to the parent element (bone) or entity if the element is the root, a model and a list of sub-elements.
A skeleton file has the following structure:
```json
{
"root": {
"name": "name",
"model": "model_name",
"nodes": [
...
]
}
}
```
- root - root element
- name - name of the element to get the index (field optional)
- model - name of the model to display the element (field optional)
- nodes - list of elements - descendants, which are affected by the matrix of this element (the field is optional)
At the moment, positioning, rotation, scaling is done through scripting, as well as animation.
The process of working with skeletons will be simplified in the future.
Models are loaded automatically; adding them to preload.json is not required.
## Models
Models should be located in the models folder. Currently only OBJ format is supported.
>[!IMPORTANT]
> When loading an obj model, the \*.mtl file is ignored.
A texture is defined by a material name that matches the texture naming format used in preload.json.
Textures are loaded automatically; it is not necessary to specify the textures used by the model in preload.json.

View File

@ -7,8 +7,12 @@ Subsections:
- [User input](scripting/user-input.md)
- [Filesystem and serialization](scripting/filesystem.md)
- [UI properties and methods](scripting/ui.md)
- [Entities and components](scripting/ecs.md)
- [Libraries](#)
- [block](scripting/builtins/libblock.md)
- [entities](scripting/builtins/libentities.md)
- [mat4](scripting/builtins/libmat4.md)
- [vec2, vec3, vec4](scripting/builtins/libvecn.md)
- [Module core:bit_converter](scripting/modules/core_bit_converter.md)
- [Module core:data_buffer](scripting/modules/core_data_buffer.md)
- [Module core:vector2, core:vector3](scripting/modules/core_vector2_vector3.md)
@ -104,6 +108,18 @@ player.get_selected_block(playerid: int) -> x,y,z
Returns position of the selected block or nil
```python
player.get_selected_entity(playerid: int) -> int
```
Returns unique indentifier of the entity selected by player
```python
player.get_entity(playerid: int) -> int
```
Returns unique identifier of the player entity
## *world* library
## Библиотека *world*
@ -324,151 +340,6 @@ inventory.move(invA: int, slotA: int, invB: int, slotB: int)
Move item from slotA of invA to slotB of invB. invA may be the same as invB.
If slotB will be chosen automaticly if argument is not specified.
## *block* library
```python
block.name(blockid: int) -> str
```
Returns block string ID (name) by index.
```python
block.index(name: str) -> int
```
Returns block integer ID (index) by name.
```python
block.material(blockid: int) -> str
```
Returns the id of the block material.
```python
block.caption(blockid: int) -> str
```
Returns the block name displayed in the interface.
```python
block.get(x: int, y: int, z: int) -> int
```
Returns integer ID by block position
```python
block.get_states(x: int, y: int, z: int) -> int
```
Returns block state (rotation + additional information) as an integer.
```python
block.set(x: int, y: int, z: int, id: int, states: int)
```
Set block with specified integer ID and state (default - 0) at specified position.
> [!WARNING]
> `block.set` does not trigger on_placed.
```python
block.is_solid_at(x: int, y: int, z: int) -> bool
```
Check if block at the specified position is solid.
```python
block.is_replaceable_at(x: int, y: int, z: int) -> bool
```
Check if block may be placed at specified position. (Examples: air, water, grass, flower)
```python
block.defs_count() -> int
```
Returns count of available block IDs.
Following three functions return direction vectors based on block rotation.
```python
block.get_X(x: int, y: int, z: int) -> int, int, int
```
Returns X: integer direction vector of the block at specified coordinates.
Example: no rotation: 1, 0, 0
```python
block.get_Y(x: int, y: int, z: int) -> int, int, int
```
Returns Y: integer direction vector of the block at specified coordinates.
Example: no rotation: 0, 1, 0
```python
block.get_Z(x: int, y: int, z: int) -> int, int, int
```
Returns Z: integer direction vector of the block at specified coordinates.
Example: no rotation: 0, 0, 1
```python
block.get_rotation(x: int, y: int, z: int) -> int
```
Returns block rotation index based on used profile.
```python
block.set_rotation(x: int, y: int, z: int, rotation: int)
```
Set block rotation by index.
### Extended blocks
Extended blocks are blocks with size greather than 1x1x1
```python
block.is_extended(id: int) -> bool
```
Checks whether the block is extended.
```python
block.get_size(id: int) -> int, int, int
```
Returns the block size.
```python
block.is_segment(x: int, y: int, z: int) -> bool
```
Checks whether the block is a non-origin segment of an extended block.
```python
block.seek_origin(x: int, y: int, z: int) -> int, int, int
```
Returns the position of the main segment of an extended block or the original position,
if the block is not extended.
### User bits
Part of a voxel data used for scripting. Size: 8 bit.
```python
block.get_user_bits(x: int, y: int, z: int, offset: int, bits: int) -> int
```
Get specified bits as an unsigned integer.
```python
block.set_user_bits(x: int, y: int, z: int, offset: int, bits: int, value: int) -> int
```
Set specified bits.
## *item* library
```python
@ -571,6 +442,18 @@ hud.resume()
Closes the pause menu.
```python
hud.is_paused() -> bool
```
Returns true if pause menu is open.
```python
hud.is_inventory_open() -> bool
```
Returns true if inventory is open or overlay is shown.
### *time* library
```python

View File

@ -0,0 +1,115 @@
# *block* library
```lua
-- Returns block string ID (name) by index.
block.name(blockid: int) -> str
-- Returns block integer ID (index) by name.
block.index(name: str) -> int
-- Returns the id of the block material.
block.material(blockid: int) -> str
-- Returns the block name displayed in the UI.
block.caption(blockid: int) -> str
-- Returns integer ID by block position
block.get(x: int, y: int, z: int) -> int
-- Returns block state (rotation + additional information) as an integer.
-- Used to save complete block information.
block.get_states(x: int, y: int, z: int) -> int
-- Set block with specified integer ID and state (default - 0) at specified position.
block.set(x: int, y: int, z: int, id: int, states: int)
```
> [!WARNING]
> `block.set` does not trigger on_placed.
```lua
-- Check if block at the specified position is solid.
block.is_solid_at(x: int, y: int, z: int) -> bool
-- Check if block may be placed at specified position.
-- (Examples: air, water, grass, flower)
block.is_replaceable_at(x: int, y: int, z: int) -> bool
-- Returns count of available block IDs.
block.defs_count() -> int
```
## Rotation
Following three functions return direction vectors based on block rotation.
```lua
-- Returns X: integer direction vector of the block at specified coordinates.
-- Example: no rotation: 1, 0, 0.
block.get_X(x: int, y: int, z: int) -> int, int, int
-- Same for axis Y. Default: 0, 1, 0.
block.get_Y(x: int, y: int, z: int) -> int, int, int
-- Same for axis Z. Default: 0, 0, 1.
block.get_Z(x: int, y: int, z: int) -> int, int, int
-- Returns block rotation index based on used profile.
block.get_rotation(x: int, y: int, z: int) -> int
-- Set block rotation by index.
block.set_rotation(x: int, y: int, z: int, rotation: int)
```
## Extended blocks
Extended blocks are blocks with size greather than 1x1x1
```lua
-- Checks whether the block is extended.
block.is_extended(id: int) -> bool
-- Returns the block size.
block.get_size(id: int) -> int, int, int
-- Checks whether the block is a non-origin segment of an extended block.
block.is_segment(x: int, y: int, z: int) -> bool
-- Returns the position of the main segment of an extended block
-- or the original position, if the block is not extended.
block.seek_origin(x: int, y: int, z: int) -> int, int, int
```
## User bits
Part of a voxel data used for scripting. Size: 8 bit.
```python
block.get_user_bits(x: int, y: int, z: int, offset: int, bits: int) -> int
```
Get specified bits as an unsigned integer.
```python
block.set_user_bits(x: int, y: int, z: int, offset: int, bits: int, value: int) -> int
```
Set specified bits.
## Raycast
```lua
block.raycast(start: vec3, dir: vec3, max_distance: number, [optional] dest: table) -> {
block: int, -- block id
endpoint: vec3, -- point of the ray hit point
iendpoint: vec3, -- position of the block hit by the ray
length: number, -- ray length
normal: vec3 -- normal vector of the surface hit by the ray
} or nil
```
Casts a ray from the start point in the direction of *dir*. Max_distance specifies the maximum ray length.
The function returns a table with the results or nil if the ray does not hit any block.
The result will use the destination table instead of creating a new one if the optional argument specified.

View File

@ -0,0 +1,42 @@
# Library *entities*
The library is designed to work with a registry of entities.
```lua
-- Returns an entity by unique identifier
-- The table returned is the same one available in the entity components.
entities.get(uid: int) -> table
-- Creates the specified entity.
-- args - table of component parameter tables (ARGS variable)
-- args is optional
entities.spawn(name: str, pos: vec3, [optional] args: table)
-- Checks the existence of an entity by a unique identifier.
entities.exists(uid: int) -> bool
-- Returns a table of all loaded entities
entities.get_all() -> table
-- Returns a table of loaded entities based on the passed list of UIDs
entities.get_all(uids: array<int>) -> table
-- Returns a list of UIDs of entities inside the rectangular area
-- pos - minimal area corner
-- size - area size
entities.get_all_in_box(pos: vec3, size: vec3) -> array<int>
-- Returns a list of UIDs of entities inside the radius
-- center - center of the area
-- radius - radius of the area
entities.get_all_in_radius(center: vec3, radius: number) -> array<int>
```
```lua
entities.raycast(start: vec3, dir: vec3, max_distance: number,
ignore: int, [optional] destination: table) -> table or nil
```
The function is an extended version of [block.raycast](libblock.md#raycast). Returns a table with the results if the ray touches a block or entity.
Accordingly, this will affect the presence of the *entity* and *block* fields.

View File

@ -0,0 +1,207 @@
# Library vec*n*
*vecn* contains a set of functions for working with vectors of dimensions 2, 3 or 4.
Most functions have several options for argument lists (overloads).
> [!WARNING]
>
> vecn, where n == vector dimension (2, 3, 4), i.e. vec2, vec3, vec4
>
## Data types
Type conventions will be used on this page.
- vector - an array of two, three or four numbers
- vec2 - array of two numbers
- vec3 - array of three numbers
- vec4 - array of four numbers
> [!WARNING]
>
> Type annotations are part of the documentation and are not specified when calling functions.
## Operations with vectors
#### Addition - *vecn.add(...)*
```lua
-- returns the result of vector addition
vecn.add(a: vector, b: vector)
-- returns the result of adding a vector and a scalar
vecn.add(a: vector, b: number)
-- writes the result of adding two vectors to dst
vecn.add(a: vector, b: vector, dst: vector)
```
#### Subtraction - *vecn.sub(...)*
```lua
-- returns the result of vector subtraction
vecn.sub(a: vector, b: vector)
-- returns the result of subtracting a scalar from a vector
vecn.sub(a: vector, b: number)
-- writes the result of subtracting two vectors to dst
vecn.sub(a: vector, b: vector, dst: vector)
```
#### Multiplication - *vecn.mul(...)*
```lua
-- returns the result of vector multiplication
vecn.mul(a: vector, b: vector)
-- returns the result of multiplying a vector by a scalar
vecn.mul(a: vector, b: number)
```
#### Inversion - *vecn.inv(...)*
```lua
-- returns the result of the inversion (opposite) of the vector
vecn.inverse(a: vector)
-- writes the inverted vector to dst
vecn.inverse(v: vector, dst: vector)
```
#### Division - *vecn.div(...)*
```lua
-- returns the result of vector division
vecn.div(a: vector, b: vector)
-- returns the result of dividing a vector by a scalar
vecn.div(a: vector, b: number)
-- writes the result of dividing two vectors to dst
vecn.div(a: vector, b: vector, dst: vector)
```
#### Normalization - *vecn.norm(...)*
```lua
-- returns normalized vector
vecn.normalize(a: vector)
-- writes the normalized vector to dst
vecn.normalize(v: vector, dst: vector)
```
#### Vector length - *vecn.len(...)*
```lua
-- returns the length of the vector
vecn.length(a: vector)
```
#### Absolute value - *vecn.abs(...)*
```lua
-- returns a vector with absolute values
vecn.abs(a: vector)
-- writes the absolute value of the vector to dst
vecn.abs(v: vector, dst: vector)
```
#### Rounding - *vecn.round(...)*
```lua
-- returns a vector with rounded values
vecn.round(a: vector)
-- writes rounded vector to dst
vecn.round(v: vector, dst: vector)
```
#### Exponentiation - *vecn.pow(...)*
```lua
-- returns a vector with elements raised to powers
vecn.pow(a: vector, b: number)
-- writes the vector raised to a power to dst
vecn.pow(v: vector, exponent: number, dst: vector)
```
#### Dot product - *vecn.dot(...)*
```lua
-- returns the scalar product of vectors
vecn.dot(a: vector, b: vector)
```
#### Convert to string - *vecn.tostring(...)*
> [!WARNING]
> Returns only if the content is a vector
```lua
-- returns a string representing the contents of the vector
vecn.tostring(a: vector)
```
## Specific functions
Functions related to specific vector dimensions.
```lua
-- returns a random vector whose coordinates are uniformly distributed on a sphere of a given radius
vec3.spherical_rand(radius: number)
-- writes a random vector whose coordinates are uniformly distributed on a sphere of a given radius in dst
vec3.spherical_rand(radius: number, dst: vec3)
```
## Example
```lua
-- creating vectors of different dimensions
local v1_3d = {1, 2, 2}
local v2_3d = {10, 20, 40}
local v3_4d = {1, 2, 4, 1}
local v4_2d = {1, 0}
local scal = 6 -- regular scalar
-- vector addition
local result_add = vec3.add(v1_3d, v2_3d)
print("add: " .. vec3.tostring(result_add)) -- {11, 22, 42}
-- vector subtraction
local result_sub = vec3.sub(v2_3d, v1_3d)
print("sub: " .. vec3.tostring(result_sub)) -- {9, 18, 38}
-- vector multiplication
local result_mul = vec3.mul(v1_3d, v2_3d)
print("mul: " .. vec3.tostring(result_mul)) -- {10, 40, 80}
-- multiplying a vector by a scalar
local result_mul_scal = vec3.mul(v1_3d, scal)
print("mul_scal: " .. vec3.tostring(result_mul_scal)) -- {6, 12, 12}
-- vector normalization
local result_norm = vec3.normalize(v1_3d)
print("norm: " .. vec3.tostring(result_norm)) -- {0.333, 0.667, 0.667}
-- vector length
local result_len = vec3.length(v1_3d)
print("len: " .. result_len) -- 3
-- absolute value of the vector
local result_abs = vec3.abs(v1_3d)
print("abs: " .. vec3.tostring(result_abs)) -- {1, 2, 2}
-- vector rounding
local result_round = vec3.round(v1_3d)
print("round: " .. vec3.tostring(result_round)) -- {1, 2, 2}
-- vector exponentiation
local result_pow = vec3.pow(v1_3d, 2)
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

201
doc/en/scripting/ecs.md Normal file
View File

@ -0,0 +1,201 @@
# Entities and components
## Types notation used below
- vec3 - 3D vector (array of three numbers)
- mat4 - 4x4 matrix (array of 16 numbers)
Type annotations are added for documentation purposes and are not part of the Lua syntax.
## Entity
The entity object is available in components as a global variable **entity**.
```lua
-- Deletes an entity (the entity may continue to exist until the frame ends, but will not be displayed in that frame)
entity:despawn()
-- Returns the name of the entity skeleton
entity:get_skeleton() -> str
-- Replaces the entity skeleton
entity:set_skeleton(name: str)
-- Returns the unique entity identifier
entity:get_uid() -> int
-- Returns the component by name
entity:get_component(name: str) -> component or nil
-- Checks for the presence of a component by name
entity:has_component(name: str) -> bool
```
## Built-in components
### Transform
The component is responsible for the position, scale and rotation of the entity.
```lua
-- Alias
local tsf = entity.transform
-- Returns the position of the entity
tsf:get_pos() -> vec3
-- Sets the entity position
tsf:set_pos(pos:vec3)
-- Returns the entity scale
tsf:get_size() -> vec3
-- Sets the entity scale
tsf:set_size(size: vec3)
-- Returns the entity rotation
tsf:get_rot() -> mat4
-- Sets entity rotation
tsf:set_rot(size: mat4)
```
### Rigidbody
The component is responsible for the physical body of the entity.
```lua
-- Alias
local body = entity.rigidbody
-- Checks if body physics calculation is enabled
body:is_enabled() -> bool
-- Enables/disables body physics calculation
body:set_enabled(enabled: bool)
-- Returns linear velocity
body:get_vel() -> vec3
-- Sets linear velocity
body:set_vel(vel: vec3)
-- Returns the size of the hitbox
body:get_size() -> vec3
-- Sets the hitbox size
body:set_size(size: vec3)
-- Returns the gravity multiplier
body:get_gravity_scale() -> vec3
-- Sets the gravity multiplier
body:set_gravity_scale(scale: vec3)
-- Returns the linear velocity attenuation multiplier (used to simulate air resistance and friction)
body:get_linear_damping() -> number
-- Sets the linear velocity attenuation multiplier
body:set_linear_damping(value: number)
-- Checks if vertical velocity attenuation is enabled
body:is_vdamping() -> bool
-- Enables/disables vertical velocity attenuation
body:set_vdamping(enabled: bool)
-- Checks if the entity is on the ground
body:is_grounded() -> bool
-- Checks if the entity is in a "crouching" state (cannot fall from blocks)
body:is_crouching() -> bool
-- Enables/disables the "crouching" state
body:set_crouching(enabled: bool)
-- Returns the type of physical body (dynamic/kinematic)
body:get_body_type() -> str
-- Sets the physical body type
body:set_body_type(type: str)
```
### Skeleton
The component is responsible for the entity skeleton. See [rigging](../rigging.md).
```lua
-- Alias
local rig = entity.skeleton
-- Returns the model name assigned to the bone at the specified index
rig:get_model(index: int) -> str
-- Reassigns the bone model at the specified index
-- Resets to original if name is not specified
rig:set_model(index: int, name: str)
-- Returns the bone transformation matrix at the specified index
rig:get_matrix(index: int) -> mat4
-- Sets the bone transformation matrix at the specified index
rig:set_matrix(index: int, matrix: mat4)
-- Returns the texture by key (dynamically assigned textures - '$name')
rig:get_texture(key: str) -> str
-- Assigns texture by key
rig:set_texture(key: str, value: str)
-- Returns the bone index by name or nil
rig:index(name: str) -> int
```
## Component events
```lua
function on_despawn()
```
Called when the entity is despawned.
```lua
function on_grounded(force: number)
```
Called on landing. The first argument is the impact force (Speed module).
```lua
function on_fall()
```
Called when the entity starts to fall.
```lua
function on_save()
```
Called before component data is saved. Here you can write the data you want to save into the *SAVED_DATA* table, which is available for the entire life of the component.
```lua
function on_sensor_enter(index: int, entity: int)
```
Called when another entity hits the sensor with the index passed as the first argument. The UID of the entity that entered the sensor is passed as the second argument.
```lua
function on_sensor_exit(index: int, entity: int)
```
Called when another entity exits the sensor with the index passed as the first argument. The UID of the entity that left the sensor is passed as the second argument.
```lua
function on_aim_on(playerid: int)
```
Called when the player aims at the entity. The player ID is passed as an argument.
```lua
function on_aim_off(playerid: int)
```
Called when the player takes aim away from the entity. The player ID is passed as an argument.
```lua
function on_attacked(attackerid: int, playerid: int)
```
Called when an entity is attacked (LMB on the entity). The first argument is the UID of the attacking entity. The attacking player ID is passed as the second argument. If the entity was not attacked by a player, the value of the second argument will be -1.
```lua
function on_used(playerid: int)
```
Called when an entity is used (RMB by entity). The player ID is passed as an argument.

View File

@ -59,3 +59,18 @@ input.get_bindings() -> strings array
```
Returns all binding names.
```python
input.is_pressed(code: str) -> bool
```
Checks input activity using a code consisting of:
- input type: *key* or *mouse*
- input code: [key name](#key names) or mouse button name (left, middle, right)
Example:
```lua
if (input.is_pressed("key:enter") {
...
}
```

View File

@ -86,13 +86,13 @@
Числа указываются в диапазоне [0.0, 1.0] - т.е в пределах блока.
Массив `[0.25, 0.0, 0.5, 0.75, 0.4, 0.3]` описывает хитбокс:
- шириной (с востока на запад) 0.75 м
- высотой 0.4 м
- длиной (с юга на север) 0.3 м
Массив `[0.25, 0.0, 0.5, 0.75, 0.4, 0.3]` описывает хитбокс:
- смещен на 0.25 м на запад
- смещен на 0.0 м вверх
- смещен на 0.5 м на север
- шириной (с востока на запад) 0.75 м
- высотой 0.4 м
- длиной (с юга на север) 0.3 м
### Приземленность - *grounded*

101
doc/ru/entity-properties.md Normal file
View File

@ -0,0 +1,101 @@
# Свойства сущностей
## Логика
### Cписок компонентов - *components*
Определяет компоненты и порядок их инициализации.
```json
"components": [
список компонентов
]
```
Пример:
```json
"components": [
"base:drop"
]
```
Код компонентов должен находиться в `scripts/components`.
## Физика
### Хитбокс - *hitbox*
Массив из трех чисел, указывающих размер хитбокса сущности.
Пример:
```json
"hitbox": [0.6, 1.8, 0.6]
```
### Тип тела - *body-type*
Определяет то, как с ним будет работать физический движок.
- *dynamic* (динамический) - тип по-умолчанию. Физический движок просчитывает движение и столкновения.
- *kinematic* (кинематический) - просчитывается только движение, без столкновений.
### Блокирование - *blocking*
Определяет блокирует ли сущность установку блоков.
*В будущем будет также блокировать движение других сущностей.*
Значение по-умолчанию: *true*.
### Список сенсоров - *sensors*
Сенсор - область пространства, привязанная к физическому телу, детектирующее попадание в него других тел.
- При попадании тела вызывается событие *on_sensor_enter*.
- При покидании тела вызывается событие *on_sensor_exit*.
Сенсоры индексируются в том же порядке, в каком представлены в списке, начиная с 0.
Существуют следующие типы (формы) сенсоров:
- radius (радиус) - самый простой сенсор. Определяет область вокруг центра хитбокса. Указываются следующие значения:
- радиус - число.
- aabb (коробка) - прямоугольная область, меняющая положение в зависимости от поворота сущности. **Сама область не поворачивается.** Указываются следующие значения:
- три числа x, y, z минимального угла области.
- три числа x, y, z противоположного угла области.
Пример:
```json
"sensors": [
["aabb", -0.2, -0.2, -0.2, 0.2, 0.2, 0.2],
["radius", 1.6]
]
```
0. Прямоугольная область шириной, высотой и длиной в 0.4 м. с центром в 0.0.
1. Радиальная область с радиусом 1.6 м.
## Вид
### Имя скелета - *skeleton-name*
Значение по-умолчанию совпадает с именем сущности. Определяет то, какой скелет будет использоваться сущностью. См. [риггинг](rigging.md).
## Сохранение/загрузка
Кроме данных пользовательских компонентов, движок автоматически сохраняет данные встроенных: transform, rigidbody, skeleton.
Есть набор флагов, позволяющих указать какие данные будут сохранены, а какие нет.
(Указываются булевы значения)
| Название | Цель | По-умолчанию |
| ---------------------- | -------------------------------------------------------- | ------------ |
| save | сама сущность | true |
| save-skeleton-pose | поза скелета сущности | false |
| save-skeleton-textures | динамически назначенные текстуры | false |
| save-body-velocity | скорость движения тела | true |
| save-body-settings | измененные настройки тела <br>(type, damping, crouching) | false |

View File

@ -1,6 +1,8 @@
# Вид
# Свойства предметов
## Тип иконки - `icon-type` и сама иконка - `icon`
## Вид
### Тип иконки - `icon-type` и сама иконка - `icon`
В последней версии движка существуют следующие типы иконок предметов, определяющих то, как предмет будет отображаться в инвентаре:
- `none` - невидимый тип, используется только для `core:empty` (пустой предмет). Не влияет на появление предмета на панель доступа к контенту. Тип может быть удалён в будущем
@ -9,9 +11,9 @@
- items (генерируется из png файлов в `res/textures/items/`)
- `block` - отображает предпросмотр блока. В icon указывается строковый id блока который нужно отображать. Пример `base:wood`
# Поведение
## Поведение
## Устанавливаемый блок - `placing-block`
### Устанавливаемый блок - `placing-block`
При указании строкового id блока предмет устанавливает его при нажатии ПКМ. Именно это свойство используется у всех сгенерированных для блоков предметов.
@ -21,7 +23,7 @@
"placing-block": "base:bazalt"
```
## Излучение - `emission`
### Излучение - `emission`
Влияет на свет излучаемый предметом, когда он находится в руке игрока.
@ -33,6 +35,6 @@
- `[7, 0, 0]` - слабый красный свет
- `[0, 0, 0]` - предмет не излучает свет (по-умолчанию)
## Размер стопки (стека) - `stack-size`
### Размер стопки (стека) - `stack-size`
Определяет максимальное количество предмета в одном слоте. Значение по-умолчанию - 64.

View File

@ -12,3 +12,5 @@
- [Скриптинг](scripting.md)
- [Консоль](console.md)
- [Модели блоков](block-models.md)
- [Риггинг](rigging.md)
- [Ресурсы (resources.json)](resources.md)

22
doc/ru/resources.md Normal file
View File

@ -0,0 +1,22 @@
# Ресурсы
К ресурсам относятся:
- камеры
- слоты для эффектов
- фреймбуферы
- и подобные ограниченные по количеству ресурсы
На данный момент реализованы только **камеры**.
Запрашиваемые паком ресурсы указываются через файл resources.json в формате:
```json
{
"тип-ресура": [
"имена",
"ресурсов"
]
}
```
После загрузки пака имена ресурсов получат префикс пака. Например камера
*cinematic* в паке base получает имя *base:cinematic*.

46
doc/ru/rigging.md Normal file
View File

@ -0,0 +1,46 @@
# Риггинг
## Скелеты
Скелеты сущностей создаются через json файлы в папке skeletons.
> [!IMPORTANT]
>
> Скелет является неиндексируемой единицей контента. При его загрузке к имени добавляется префикс пака (пример: *drop* в паке base -> *base:drop*).
Элемент скелета, или кость, состоит из матрицы транформации, определяющей её положение, вращение и масштаб относительно родительского элемента (кости) или сущности, если элемент является корневым, модели и списка под-элементов.
Файл скелета имеет следующую структуру:
```json
{
"root": {
"name": "имя",
"model": "имя_модели",
"nodes": [
...
]
}
}
```
- root - корневой элемент
- name - имя элемента для получения индекса (поле опционально)
- model - имя модели для отображения элемента (поле опционально)
- nodes - список элементов - потомков, на которые влияет матрица данного элемента (поле опционально)
На данный момент расположение, вращение, масштабирование выполняется через скриптинг, так же как и анимация.
Процесс работы со скелетами будет упрощен в будущем.
Модели загружаются автоматически, добавление их в preload.json не требуется.
## Модели
Модели должны располагаться в папке models. На данный момент поддерживается только OBJ формат.
>[!IMPORTANT]
> При загрузке obj модели игнорируется файл \*.mtl.
Текстура определяется именем материала, соответствующем формату имен текстур, используемому в preload.json.
Текстуры загружаются автоматически, указывать используемые моделью текстуры в preload.json не обязательно.

View File

@ -7,8 +7,12 @@
- [Пользовательский ввод](scripting/user-input.md)
- [Файловая система и сериализация](scripting/filesystem.md)
- [Свойства и методы UI элементов](scripting/ui.md)
- [Сущности и компоненты](scripting/ecs.md)
- [Библиотеки](#)
- [block](scripting/builtins/libblock.md)
- [entities](scripting/builtins/libentities.md)
- [mat4](scripting/builtins/libmat4.md)
- [vec2, vec3, vec4](scripting/builtins/libvecn.md)
- [Модуль core:bit_converter](scripting/modules/core_bit_converter.md)
- [Модуль core:data_buffer](scripting/modules/core_data_buffer.md)
- [Модули core:vector2, core:vector3](scripting/modules/core_vector2_vector3.md)
@ -100,6 +104,18 @@ player.get_selected_block(playerid: int) -> x,y,z
Возвращает координаты выделенного блока, либо nil
```python
player.get_selected_entity(playerid: int) -> int
```
Возвращает уникальный идентификатор сущности, на которую нацелен игрок
```python
player.get_entity(playerid: int) -> int
```
Возвращает уникальный идентификатор сущности игрока
## Библиотека *world*
```python
@ -319,150 +335,6 @@ inventory.move(invA: int, slotA: int, invB: int, slotB: int)
invA и invB могут указывать на один инвентарь.
slotB будет выбран автоматически, если не указывать явно.
## Библиотека *block*
```python
block.name(blockid: int) -> str
```
Возвращает строковый id блока по его числовому id.
```python
block.index(name: str) -> int
```
Возвращает числовой id блока, принимая в качестве агрумента строковый.
```python
block.material(blockid: int) -> str
```
Возвращает id материала блока.
```python
block.caption(blockid: int) -> str
```
Возвращает название блока, отображаемое в интерфейсе.
```python
block.get(x: int, y: int, z: int) -> int
```
Возвращает числовой id блока на указанных координатах. Если чанк на указанных координатах не загружен, возвращает -1.
```python
block.get_states(x: int, y: int, z: int) -> int
```
Возвращает состояние (поворот + доп. информация) в виде целого числа
```python
block.set(x: int, y: int, z: int, id: int, states: int)
```
Устанавливает блок с заданным числовым id и состоянием (0 - по-умолчанию) на заданных координатах.
> [!WARNING]
> `block.set` не вызывает событие on_placed.
```python
block.is_solid_at(x: int, y: int, z: int) -> bool
```
Проверяет, является ли блок на указанных координатах полным
```python
block.is_replaceable_at(x: int, y: int, z: int) -> bool
```
Проверяет, можно ли на заданных координатах поставить блок (примеры: воздух, трава, цветы, вода)
```python
block.defs_count() -> int
```
Возвращает количество id доступных в движке блоков
Следующие три функции используется для учёта вращения блока при обращении к соседним блокам или других целей, где направление блока имеет решающее значение.
```python
block.get_X(x: int, y: int, z: int) -> int, int, int
```
Возвращает целочисленный единичный вектор X блока на указанных координатах с учётом его вращения (три целых числа).
Если поворот отсутствует, возвращает 1, 0, 0
```python
block.get_Y(x: int, y: int, z: int) -> int, int, int
```
Возвращает целочисленный единичный вектор Y блока на указанных координатах с учётом его вращения (три целых числа).
Если поворот отсутствует, возвращает 0, 1, 0
```python
block.get_Z(x: int, y: int, z: int) -> int, int, int
```
Возвращает целочисленный единичный вектор Z блока на указанных координатах с учётом его вращения (три целых числа).
Если поворот отсутствует, возвращает 0, 0, 1
```python
block.get_rotation(x: int, y: int, z: int) -> int
```
Возвращает индекс поворота блока в его профиле вращения.
```python
block.set_rotation(x: int, y: int, z: int, rotation: int)
```
Устанавливает вращение блока по индексу в его профиле вращения.
### Расширенные блоки
Расширенные блоки - те, размер которых превышает 1x1x1
```python
block.is_extended(id: int) -> bool
```
Проверяет, является ли блок расширенным.
```python
block.get_size(id: int) -> int, int, int
```
Возвращает размер блока.
```python
block.is_segment(x: int, y: int, z: int) -> bool
```
Проверяет является ли блок сегментом расширенного блока, не являющимся главным.
```python
block.seek_origin(x: int, y: int, z: int) -> int, int, int
```
Возвращает позицию главного сегмента расширенного блока или исходную позицию,
если блок не являющийся расширенным.
### Пользовательские биты
Выделенная под использования в скриптах часть поля `voxel.states` хранящего доп-информацию о вокселе, такую как вращение блока. На данный момент выделенная часть составляет 8 бит.
```python
block.get_user_bits(x: int, y: int, z: int, offset: int, bits: int) -> int
```
Возвращает выбранное число бит с указанного смещения в виде целого беззнакового числа
```python
block.set_user_bits(x: int, y: int, z: int, offset: int, bits: int, value: int) -> int
```
Записывает указанное число бит значения value в user bits по выбранному смещению
## Библиотека item
```python
@ -562,6 +434,18 @@ hud.resume()
Закрывает меню паузы.
```python
hud.is_paused() -> bool
```
Возвращает true если открыто меню паузы.
```python
hud.is_inventory_open() -> bool
```
Возвращает true если открыт инвентарь или оверлей.
## Библиотека time
```python

View File

@ -0,0 +1,143 @@
# Библиотека *block*
```lua
-- Возвращает строковый id блока по его числовому id.
block.name(blockid: int) -> str
-- Возвращает числовой id блока, принимая в качестве агрумента строковый
block.index(name: str) -> int
-- Возвращает id материала блока.
block.material(blockid: int) -> str
-- Возвращает название блока, отображаемое в интерфейсе.
block.caption(blockid: int) -> str
-- Возвращает числовой id блока на указанных координатах.
-- Если чанк на указанных координатах не загружен, возвращает -1.
block.get(x: int, y: int, z: int) -> int
-- Возвращает состояние (поворот + доп. информация) в виде целого числа
block.get_states(x: int, y: int, z: int) -> int
-- Устанавливает блок с заданным числовым id и состоянием (0 - по-умолчанию) на заданных координатах.
block.set(x: int, y: int, z: int, id: int, states: int)
```
> [!WARNING]
> `block.set` не вызывает событие on_placed.
```lua
-- Проверяет, является ли блок на указанных координатах полным
block.is_solid_at(x: int, y: int, z: int) -> bool
-- Проверяет, можно ли на заданных координатах поставить блок
-- (примеры: воздух, трава, цветы, вода)
block.is_replaceable_at(x: int, y: int, z: int) -> bool
-- Возвращает количество id доступных в загруженном контенте блоков
block.defs_count() -> int
-- Возвращает числовой id предмета, указанного в свойстве *picking-item*.
block.get_picking_item(id: int) -> int
```
### Raycast
```lua
block.raycast(start: vec3, dir: vec3, max_distance: number, [опционально] dest: table) -> {
block: int, -- id блока
endpoint: vec3, -- точка касания луча
iendpoint: vec3, -- позиция блока, которого касается луч
length: number, -- длина луча
normal: vec3 -- вектор нормали поверхности, которой касается луч
} или nil
```
Бросает луч из точки start в направлении dir. Max_distance указывает максимальную длину луча.
Функция возвращает таблицу с результатами или nil, если луч не касается блока.
Для результата будет использоваться целевая (dest) таблица вместо создания новой, если указан опциональный аргумент.
## Вращение
Следующие функции используется для учёта вращения блока при обращении к соседним блокам или других целей, где направление блока имеет решающее значение.
```lua
-- Возвращает целочисленный единичный вектор X блока на указанных координатах с учётом его вращения (три целых числа).
-- Если поворот отсутствует, возвращает 1, 0, 0
block.get_X(x: int, y: int, z: int) -> int, int, int
-- То же, но для оси Y (по-умолчанию 0, 1, 0)
block.get_Y(x: int, y: int, z: int) -> int, int, int
-- То же, но для оси Z (по-умолчанию 0, 0, 1)
block.get_Z(x: int, y: int, z: int) -> int, int, int
-- Возвращает индекс поворота блока в его профиле вращения (не превышает 7).
block.get_rotation(x: int, y: int, z: int) -> int
-- Устанавливает вращение блока по индексу в его профиле вращения.
block.set_rotation(x: int, y: int, z: int, rotation: int)
-- Возвращает имя профиля вращения (none/pane/pipe)
block.get_rotation_profile(id: int) -> str
```
## Расширенные блоки
Расширенные блоки - те, размер которых превышает 1x1x1
```lua
-- Проверяет, является ли блок расширенным.
block.is_extended(id: int) -> bool
-- Возвращает размер блока.
block.get_size(id: int) -> int, int, int
-- Проверяет является ли блок сегментом расширенного блока, не являющимся главным.
block.is_segment(x: int, y: int, z: int) -> bool
-- Возвращает позицию главного сегмента расширенного блока или исходную позицию,
-- если блок не является расширенным.
block.seek_origin(x: int, y: int, z: int) -> int, int, int
```
## Пользовательские биты
Выделенная под использования в скриптах часть поля `voxel.states` хранящего доп-информацию о вокселе, такую как вращение блока. На данный момент выделенная часть составляет 8 бит.
```lua
-- Возвращает выбранное число бит с указанного смещения в виде целого беззнакового числа
block.get_user_bits(x: int, y: int, z: int, offset: int, bits: int) -> int
-- Записывает указанное число бит значения value в user bits по выбранному смещению
block.set_user_bits(x: int, y: int, z: int, offset: int, bits: int, value: int) -> int
```
## Физика
Информация свойствах блока, используемых физическим движком.
```lua
-- Возвращает массив из двух векторов (массивов из 3 чисел):
-- 1. Минимальная точка хитбокса
-- 2. Размер хитбокса
-- rotation_index - индекс поворота блока
block.get_hitbox(id: int, rotation_index: int) -> {vec3, vec3}
```
## Модель
Информация о модели блока.
```lua
-- возвращает тип модели блока (block/aabb/custom/...)
block.get_model(id: int) -> str
-- возвращает массив из 6 текстур, назначенных на стороны блока
block.get_textures(id: int) -> таблица строк
```

View File

@ -0,0 +1,42 @@
# Библиотека *entities*
Библиотека предназначена для работы с реестром сущностей.
```lua
-- Возвращает сущность по уникальному идентификатору
-- Возвращаемая таблица - та же, что доступна в компонентах сущности.
entities.get(uid: int) -> table
-- Создает указанную сущность.
-- args - таблица таблиц параметров компонентов (переменная ARGS)
-- args не является обязательным
entities.spawn(name: str, pos: vec3, [optional] args: table)
-- Проверяет наличие сущности по уникальному идентификатору.
entities.exists(uid: int) -> bool
-- Возвращает таблицу всех загруженных сущностей
entities.get_all() -> table
-- Возвращает таблицу загруженных сущностей по переданному списку UID
entities.get_all(uids: array<int>) -> table
-- Возвращает список UID сущностей, попадающих в прямоугольную область
-- pos - минимальный угол области
-- size - размер области
entities.get_all_in_box(pos: vec3, size: vec3) -> array<int>
-- Возвращает список UID сущностей, попадающих в радиус
-- center - центр области
-- radius - радиус области
entities.get_all_in_radius(center: vec3, radius: number) -> array<int>
```
```lua
entities.raycast(start: vec3, dir: vec3, max_distance: number,
ignore: int, [optional] destination: table) -> table или nil
```
Функция является расширенным вариантом [block.raycast](libblock.md#raycast). Возвращает таблицу с результатами если луч касается блока, либо сущности.
Соответственно это повлияет на наличие полей *entity* и *block*.

View File

@ -33,7 +33,7 @@ vecn.add(a: vector, b: vector)
vecn.add(a: vector, b: number)
-- записывает результат сложения двух векторов в dst
vec.add(a: vector, b: vector, dst: vector)
vecn.add(a: vector, b: vector, dst: vector)
```
#### Вычитание - *vecn.sub(...)*
@ -46,7 +46,7 @@ vecn.sub(a: vector, b: vector)
vecn.sub(a: vector, b: number)
-- записывает результат вычитания двух векторов в dst
vec.sub(a: vector, b: vector, dst: vector)
vecn.sub(a: vector, b: vector, dst: vector)
```
#### Умножение - *vecn.mul(...)*
@ -66,7 +66,7 @@ vecn.mul(a: vector, b: number)
vecn.inverse(a: vector)
-- записывает инвертированный вектор в dst
vec.inverse(v: vector, dst: vector)
vecn.inverse(v: vector, dst: vector)
```
#### Деление - *vecn.div(...)*
@ -79,7 +79,7 @@ vecn.div(a: vector, b: vector)
vecn.div(a: vector, b: number)
-- записывает результат деления двух векторов в dst
vec.div(a: vector, b: vector, dst: vector)
vecn.div(a: vector, b: vector, dst: vector)
```
#### Нормализация - *vecn.norm(...)*
@ -89,7 +89,7 @@ vec.div(a: vector, b: vector, dst: vector)
vecn.normalize(a: vector)
-- записывает нормализованный вектор в dst
vec.normalize(v: vector, dst: vector)
vecn.normalize(v: vector, dst: vector)
```
#### Длина вектора - *vecn.len(...)*
@ -107,7 +107,7 @@ vecn.length(a: vector)
vecn.abs(a: vector)
-- записывает абсолютное значение вектора в dst
vec.abs(v: vector, dst: vector)
vecn.abs(v: vector, dst: vector)
```
#### Округление - *vecn.round(...)*
@ -117,7 +117,7 @@ vec.abs(v: vector, dst: vector)
vecn.round(a: vector)
-- записывает округленный вектор в dst
vec.round(v: vector, dst: vector)
vecn.round(v: vector, dst: vector)
```
#### Степень - *vecn.pow(...)*
@ -127,7 +127,7 @@ vec.round(v: vector, dst: vector)
vecn.pow(a: vector, b: number)
-- записывает вектор, возведенный в степень, в dst
vec.pow(v: vector, exponent: number, dst: vector)
vecn.pow(v: vector, exponent: number, dst: vector)
```
#### Скалярное произведение - *vecn.dot(...)*
@ -144,6 +144,18 @@ vecn.dot(a: vector, b: vector)
vecn.tostring(a: vector)
```
## Специфические функции
Функции относящиеся к конкретным размерностям векторов.
```lua
-- возвращает случайный вектор, координаты которого равномерно распределены на сфере заданного радиуса
vec3.spherical_rand(radius: number)
-- записывает случайный вектор, координаты которого равномерно распределены на сфере заданного радиуса в dst
vec3.spherical_rand(radius: number, dst: vec3)
```
## Пример
```lua

211
doc/ru/scripting/ecs.md Normal file
View File

@ -0,0 +1,211 @@
# Сущности и компоненты
## Обозначения типов, используемые далее
- vec3 - 3D вектор (массив из трех чисел)
- mat4 - матрица 4x4 (массив из 16 чисел)
Аннотации типов добавлены в целях документации и не являются частью синтаксиса
Lua.
## Сущность
Объект сущности доступен в компонентах как глобальная переменная **entity**.
```lua
-- Удаляет сущность (сущность может продолжать существовать до завершения кадра, но не будет отображена в этом кадре)
entity:despawn()
-- Возращает имя скелета сущности
entity:get_skeleton() -> str
-- Заменяет скелет сущности
entity:set_skeleton(name: str)
-- Возращает уникальный идентификатор сущности
entity:get_uid() -> int
-- Возвращает компонент по имени
entity:get_component(name: str) -> компонент или nil
-- Проверяет наличие компонента по имени
entity:has_component(name: str) -> bool
```
## Встроенные компоненты
### Transform
Компонент отвечает за позицию, масштаб и вращение сущности.
```lua
-- Сокращение
local tsf = entity.transform
-- Возвращает позицию сущности
tsf:get_pos() -> vec3
-- Устанавливает позицию сущности
tsf:set_pos(pos: vec3)
-- Возвращает масштаб сущности
tsf:get_size() -> vec3
-- Устанавливает масштаб сущности
tsf:set_size(size: vec3)
-- Возвращает вращение сущности
tsf:get_rot() -> mat4
-- Устанавливает вращение сущности
tsf:set_rot(size: mat4)
```
### Rigidbody
Компонент отвечает за физическое тело сущности.
```lua
-- Сокращение
local body = entity.rigidbody
-- Проверяет, включен ли рассчет физики тела
body:is_enabled() -> bool
-- Включает/выключает рассчет физики тела
body:set_enabled(enabled: bool)
-- Возвращает линейную скорость
body:get_vel() -> vec3
-- Устанавливает линейную скорость
body:set_vel(vel: vec3)
-- Возвращает размер хитбокса
body:get_size() -> vec3
-- Устанавливает размер хитбокса
body:set_size(size: vec3)
-- Возвращает множитель гравитации
body:get_gravity_scale() -> vec3
-- Устанавливает множитель гравитации
body:set_gravity_scale(scale: vec3)
-- Возвращает множитель затухания линейной скорости (используется для имитации сопротивления воздуха и трения)
body:get_linear_damping() -> number
-- Устанавливает множитель затухания линейной скорости
body:set_linear_damping(value: number)
-- Проверяет, включено ли вертикальное затухание скорости
body:is_vdamping() -> bool
-- Включает/выключает вертикальное затухание скорости
body:set_vdamping(enabled: bool)
-- Проверяет, находится ли сущность на земле (приземлена)
body:is_grounded() -> bool
-- Проверяет, находится ли сущность в "крадущемся" состоянии (не может упасть с блоков)
body:is_crouching() -> bool
-- Включает/выключает "крадущееся" состояние
body:set_crouching(enabled: bool)
-- Возвращает тип физического тела (dynamic/kinematic)
body:get_body_type() -> str
-- Устанавливает тип физического тела
body:set_body_type(type: str)
```
### Skeleton
Компонент отвечает за скелет сущности. См. [риггинг](../rigging.md).
```lua
-- Сокращение
local rig = entity.skeleton
-- Возвращает имя модели, назначенной на кость с указанным индексом
rig:get_model(index: int) -> str
-- Переназначает модель кости с указанным индексом
-- Сбрасывает до изначальной, если не указывать имя
rig:set_model(index: int, name: str)
-- Возвращает матрицу трансформации кости с указанным индексом
rig:get_matrix(index: int) -> mat4
-- Устанавливает матрицу трансформации кости с указанным индексом
rig:set_matrix(index: int, matrix: mat4)
-- Возвращает текстуру по ключу (динамически назначаемые текстуры - '$имя')
rig:get_texture(key: str) -> str
-- Назначает текстуру по ключу
rig:set_texture(key: str, value: str)
-- Возвращает индекс кости по имени или nil
rig:index(name: str) -> int
-- Проверяет статус видимости кости по индесу
-- или всего скелета, если индекс не указан
rig:is_visible([optional] index: int) -> bool
-- Устанавливает статус видимости кости по индексу
-- или всего скелета, если индекс не указан
rig:set_visible([optional] index: int, status: bool)
```
## События компонента
```lua
function on_despawn()
```
Вызывается при удалении сущности.
```lua
function on_grounded(force: number)
```
Вызывается при приземлении. В качестве первого аргумента передается сила удара. (На данный момент - модуль скорости).
```lua
function on_fall()
```
Вызывается при начале падения.
```lua
function on_save()
```
Вызывается перед сохранением данных компонентов. Здесь можно записать данные, которые нужно сохранить, в таблицу *SAVED_DATA*, которая доступна весь срок жизни компонента.
```lua
function on_sensor_enter(index: int, entity: int)
```
Вызывается при попадании другой сущности в сенсор с индексом, передаваемым первым аргументом. UID сущности, попавшей в сенсор, передается вторым аргументом.
```lua
function on_sensor_exit(index: int, entity: int)
```
Вызывается при выходе другой сущности из сенсора с индексом, передаваемым первым аргументом. UID сущности, покинувшей сенсор, передается вторым аргументом.
```lua
function on_aim_on(playerid: int)
```
Вызывается при наведении прицела игрока на сущность. ID игрока передается в качестве аргумента.
```lua
function on_aim_off(playerid: int)
```
Вызывается когда игрок уводит прицел с сущности. ID игрока передается в качестве аргумента.
```lua
function on_attacked(attackerid: int, playerid: int)
```
Вызывается при атаке на сущность (ЛКМ по сущности). Первым аргументом передается UID атакующей сущности. ID атакующего игрока передается вторым аргументом. Если сущность была атакована не игроком, значение второго аргумента будет равно -1.
```lua
function on_used(playerid: int)
```
Вызывается при использовании сущности (ПКМ по сущности). ID игрока передается в качестве аргумента.

View File

@ -57,3 +57,24 @@ input.get_bindings() -> массив строк
```
Возвращает названия всех доступных привязок.
```python
input.is_active(bindname: str) -> bool
```
Проверяет активность привязки.
```python
input.is_pressed(code: str) -> bool
```
Проверяет активность ввода по коду, состоящему из:
- типа ввода: key (клавиша) или mouse (кнопка мыши)
- код ввода: [имя клавиши](#имена-клавиш) или имя кнопки мыши (left, middle, right)
Пример:
```lua
if (input.is_pressed("key:enter") {
...
}
```

View File

@ -15,4 +15,5 @@ player.flight="key:f"
player.attack="mouse:left"
player.build="mouse:right"
player.pick="mouse:middle"
player.drop="key:q"
hud.inventory="key:tab"

View File

@ -14,5 +14,6 @@
"shadeless": true,
"obstacle": false,
"grounded": true,
"rotation": "pipe"
"rotation": "pipe",
"material": "base:wood"
}

View File

@ -1,6 +1,8 @@
{
"items": [
"bazalt_breaker"
"entities": [
"drop",
"player",
"falling_block"
],
"blocks": [
"dirt",
@ -27,5 +29,8 @@
"lightbulb",
"torch",
"wooden_door"
],
"items": [
"bazalt_breaker"
]
}

View File

@ -0,0 +1,11 @@
{
"components": [
"base:drop"
],
"hitbox": [0.4, 0.25, 0.4],
"sensors": [
["aabb", -0.2, -0.2, -0.2, 0.2, 0.2, 0.2],
["radius", 1.6]
],
"blocking": false
}

View File

@ -0,0 +1,7 @@
{
"components": [
"base:falling_block"
],
"skeleton-name": "base:block",
"hitbox": [0.8, 0.8, 0.8]
}

View File

@ -0,0 +1,3 @@
{
"hitbox": [0.6, 1.8, 0.6]
}

View File

@ -0,0 +1,48 @@
o Cube
v 0.5 -0.5 -0.5
v 0.5 -0.5 0.5
v -0.5 -0.5 0.5
v -0.5 -0.5 -0.5
v 0.5 0.5 -0.5
v 0.5 0.5 0.5
v -0.5 0.5 0.5
v -0.5 0.5 -0.5
vt 0.0 0.0
vt 1.0 0.0
vt 1.0 1.0
vt 0.0 1.0
vt 0.0 0.0
vt 1.0 0.0
vt 1.0 1.0
vt 0.0 1.0
vt 1.0 0.0
vt 1.0 1.0
vt 0.0 0.0
vt 1.0 0.0
vt 0.0 1.0
vt 0.0 0.0
vt 0.0 1.0
vt 1.0 0.0
vt 1.0 1.0
vt 1.0 1.0
vt 0.0 1.0
vt 0.0 0.0
vn 0.0 -1.0 0.0
vn 0.0 1.0 0.0
vn 1.0 -0.0 0.0
vn -1.0 -0.0 -0.0
vn 0.0 0.0 -1.0
vn -0.0 -0.0 1.0
usemtl $2
s off
f 1/1/1 2/2/1 3/3/1 4/4/1
usemtl $3
f 5/5/2 8/6/2 7/7/2 6/8/2
usemtl $0
f 1/9/3 5/10/3 6/8/3 2/11/3
usemtl $1
f 3/12/4 7/7/4 8/13/4 4/14/4
usemtl $4
f 5/15/5 1/1/5 4/16/5 8/17/5
usemtl $5
f 2/2/6 6/18/6 7/19/6 3/20/6

View File

@ -0,0 +1,48 @@
o Cube
v 0.125 -0.125 -0.125
v 0.125 -0.125 0.125
v -0.125 -0.125 0.125
v -0.125 -0.125 -0.125
v 0.125 0.125 -0.125
v 0.125 0.125 0.125
v -0.125 0.125 0.125
v -0.125 0.125 -0.125
vt 0.0 0.0
vt 1.0 0.0
vt 1.0 1.0
vt 0.0 1.0
vt 0.0 0.0
vt 1.0 0.0
vt 1.0 1.0
vt 0.0 1.0
vt 1.0 0.0
vt 1.0 1.0
vt 0.0 0.0
vt 1.0 0.0
vt 0.0 1.0
vt 0.0 0.0
vt 0.0 1.0
vt 1.0 0.0
vt 1.0 1.0
vt 1.0 1.0
vt 0.0 1.0
vt 0.0 0.0
vn 0.0 -1.0 0.0
vn 0.0 1.0 0.0
vn 1.0 -0.0 0.0
vn -1.0 -0.0 -0.0
vn 0.0 0.0 -1.0
vn -0.0 -0.0 1.0
usemtl $2
s off
f 1/1/1 2/2/1 3/3/1 4/4/1
usemtl $3
f 5/5/2 8/6/2 7/7/2 6/8/2
usemtl $0
f 1/9/3 5/10/3 6/8/3 2/11/3
usemtl $1
f 3/12/4 7/7/4 8/13/4 4/14/4
usemtl $4
f 5/15/5 1/1/5 4/16/5 8/17/5
usemtl $5
f 2/2/6 6/18/6 7/19/6 3/20/6

View File

@ -0,0 +1,113 @@
o Cube
v 0.282501 -0.000054 -0.282500
v -0.282501 -0.000054 -0.282501
v -0.282501 -0.000054 0.282500
v 0.282500 -0.000054 0.282501
v 0.282501 0.012502 -0.282500
v -0.282501 0.012502 -0.282501
v -0.282501 0.012502 0.282500
v 0.282500 0.012502 0.282501
v 0.282501 0.012502 -0.282500
v 0.282500 0.012502 0.282501
v -0.282501 0.012502 0.282500
v -0.282501 0.012502 -0.282501
v 0.282501 -0.000054 -0.282500
v 0.282500 -0.000054 0.282501
v -0.282501 -0.000054 0.282500
v -0.282501 -0.000054 -0.282501
v 0.282501 0.012502 -0.282500
v -0.282501 0.012502 -0.282501
v -0.282501 0.012502 0.282500
v 0.282500 0.012502 0.282501
v 0.282501 0.012502 -0.282500
v 0.282500 0.012502 0.282501
v -0.282501 0.012502 0.282500
v -0.282501 0.012502 -0.282501
v 0.282501 -0.015821 -0.282500
v -0.282501 -0.015821 -0.282501
v -0.282501 -0.015821 0.282500
v 0.282500 -0.015821 0.282501
v 0.282501 0.027439 -0.282500
v -0.282501 0.027439 -0.282501
v -0.282501 0.027439 0.282500
v 0.282500 0.027439 0.282501
v 0.282501 0.027439 -0.282500
v 0.282500 0.027439 0.282501
v -0.282501 0.027439 0.282500
v -0.282501 0.027439 -0.282501
v 0.282501 -0.015821 -0.282500
v 0.282500 -0.015821 0.282501
v -0.282501 -0.015821 0.282500
v -0.282501 -0.015821 -0.282501
v 0.282501 0.027439 -0.282500
v -0.282501 0.027439 -0.282501
v -0.282501 0.027439 0.282500
v 0.282500 0.027439 0.282501
v 0.282501 0.027439 -0.282500
v 0.282500 0.027439 0.282501
v -0.282501 0.027439 0.282500
v -0.282501 0.027439 -0.282501
vt 0.000000 0.000000
vt 1.000000 0.000000
vt 1.000000 1.000000
vt 0.000000 1.000000
vt 0.000000 0.000000
vt 1.000000 0.000000
vt 1.000000 1.000000
vt 0.000000 1.000000
vt 0.000000 0.000000
vt 0.000000 1.000000
vt 1.000000 1.000000
vt 1.000000 0.000000
vt 0.000000 0.000000
vt 0.000000 1.000000
vt 1.000000 1.000000
vt 1.000000 0.000000
vt 0.000000 0.000000
vt 1.000000 0.000000
vt 1.000000 1.000000
vt 0.000000 1.000000
vt 0.000000 0.000000
vt 0.000000 1.000000
vt 1.000000 1.000000
vt 1.000000 0.000000
vt 0.000000 0.000000
vt 1.000000 0.000000
vt 1.000000 1.000000
vt 0.000000 1.000000
vt 0.000000 0.000000
vt 1.000000 0.000000
vt 1.000000 1.000000
vt 0.000000 1.000000
vt 0.000000 0.000000
vt 0.000000 1.000000
vt 1.000000 1.000000
vt 1.000000 0.000000
vt 0.000000 0.000000
vt 0.000000 1.000000
vt 1.000000 1.000000
vt 1.000000 0.000000
vt 0.000000 0.000000
vt 1.000000 0.000000
vt 1.000000 1.000000
vt 0.000000 1.000000
vt 0.000000 0.000000
vt 0.000000 1.000000
vt 1.000000 1.000000
vt 1.000000 0.000000
vn -0.0000 1.0000 0.0000
vn 0.0000 -1.0000 -0.0000
usemtl $0
s 1
f 1/1/1 2/2/1 3/3/1 4/4/1
f 5/5/1 6/6/1 7/7/1 8/8/1
f 9/9/2 10/10/2 11/11/2 12/12/2
f 13/13/2 14/14/2 15/15/2 16/16/2
f 17/17/1 18/18/1 19/19/1 20/20/1
f 21/21/2 22/22/2 23/23/2 24/24/2
f 25/25/1 26/26/1 27/27/1 28/28/1
f 29/29/1 30/30/1 31/31/1 32/32/1
f 33/33/2 34/34/2 35/35/2 36/36/2
f 37/37/2 38/38/2 39/39/2 40/40/2
f 41/41/1 42/42/1 43/43/1 44/44/1
f 45/45/2 46/46/2 47/47/2 48/48/2

View File

@ -0,0 +1,48 @@
# Blender v2.79 (sub 0) OBJ File: 'player.blend'
# www.blender.org
mtllib player-head.mtl
o Cube.002_Cube.003
v -0.238204 0.476553 -0.238204
v -0.238204 0.000145 -0.238204
v -0.238204 0.476553 0.238204
v -0.238204 0.000145 0.238204
v 0.238204 0.000145 0.238204
v 0.238204 0.476553 0.238204
v 0.238204 0.000145 -0.238204
v 0.238204 0.476553 -0.238204
vt 0.783122 0.009685
vt 0.982503 0.009685
vt 0.982503 0.209065
vt 0.783122 0.209065
vt 0.735873 0.213345
vt 0.735873 0.739780
vt 0.209439 0.739780
vt 0.209439 0.213345
vt 0.783122 0.009685
vt 0.982503 0.009685
vt 0.982503 0.209065
vt 0.783122 0.209065
vt 0.783122 0.009685
vt 0.982503 0.009685
vt 0.982503 0.209065
vt 0.783122 0.209065
vt 0.783122 0.009685
vt 0.982503 0.009685
vt 0.982503 0.209065
vt 0.783122 0.009685
vt 0.982503 0.009685
vt 0.783122 0.209065
vn -1.0000 0.0000 0.0000
vn 0.0000 0.0000 -1.0000
vn 1.0000 0.0000 0.0000
vn 0.0000 0.0000 1.0000
vn 0.0000 -1.0000 0.0000
vn 0.0000 1.0000 0.0000
usemtl entities/player
s off
f 4/1/1 3/2/1 1/3/1 2/4/1
f 2/5/2 1/6/2 8/7/2 7/8/2
f 7/9/3 8/10/3 6/11/3 5/12/3
f 5/13/4 6/14/4 3/15/4 4/16/4
f 2/17/5 7/18/5 5/19/5 4/16/5
f 8/20/6 1/21/6 3/15/6 6/22/6

View File

@ -0,0 +1,46 @@
# Blender v2.79 (sub 0) OBJ File: 'player.blend'
# www.blender.org
mtllib player.mtl
o Cube
v 0.125000 -0.900000 -0.125000
v 0.125000 -0.900000 0.125000
v -0.125000 -0.900000 0.125000
v -0.125000 -0.900000 -0.125000
v 0.125000 0.491919 -0.125000
v 0.125000 0.491919 0.125000
v -0.125000 0.491919 0.125000
v -0.125000 0.491919 -0.125000
vt 0.783122 0.009685
vt 0.982503 0.009685
vt 0.982503 0.209065
vt 0.783122 0.209065
vt 0.783122 0.009685
vt 0.982503 0.009685
vt 0.982503 0.209065
vt 0.783122 0.209065
vt 0.982503 0.009685
vt 0.982503 0.209065
vt 0.783122 0.209065
vt 0.783122 0.009685
vt 0.982503 0.009685
vt 0.783122 0.209065
vt 0.783122 0.009685
vt 0.982503 0.009685
vt 0.982503 0.209065
vt 0.982503 0.009685
vt 0.982503 0.209065
vt 0.783122 0.209065
vn 0.0000 -1.0000 -0.0000
vn 0.0000 1.0000 0.0000
vn 1.0000 0.0000 0.0000
vn -0.0000 -0.0000 1.0000
vn -1.0000 -0.0000 -0.0000
vn 0.0000 0.0000 -1.0000
usemtl entities/player
s off
f 1/1/1 2/2/1 3/3/1 4/4/1
f 5/5/2 8/6/2 7/7/2 6/8/2
f 1/1/3 5/9/3 6/10/3 2/11/3
f 2/12/4 6/13/4 7/7/4 3/14/4
f 3/15/5 7/16/5 8/17/5 4/4/5
f 5/5/6 1/18/6 4/19/6 8/20/6

View File

@ -1,6 +1,10 @@
{
"sounds": [
"blocks/door_open",
"blocks/door_close"
"blocks/door_close",
"events/pickup"
],
"models": [
"drop-item"
]
}

View File

@ -0,0 +1,8 @@
{
"camera": [
"first-person",
"third-person-front",
"third-person-back",
"cinematic"
]
}

View File

@ -0,0 +1,117 @@
local tsf = entity.transform
local body = entity.rigidbody
local rig = entity.skeleton
inair = true
ready = false
target = -1
ARGS = ARGS or {}
local dropitem = ARGS
if SAVED_DATA.item then
dropitem.id = item.index(SAVED_DATA.item)
dropitem.count = SAVED_DATA.count
end
local scale = {1, 1, 1}
local rotation = mat4.rotate({
math.random(), math.random(), math.random()
}, 360)
function on_save()
SAVED_DATA.item = item.name(dropitem.id)
SAVED_DATA.count = dropitem.count
end
do -- setup visuals
local matrix = mat4.idt()
local icon = item.icon(dropitem.id)
if icon:find("^block%-previews%:") then
local bid = block.index(icon:sub(16))
model = block.get_model(bid)
if model == "X" then
body:set_size(vec3.mul(body:get_size(), {1.0, 0.3, 1.0}))
rig:set_model(0, "drop-item")
rig:set_texture("$0", icon)
else
if model == "aabb" then
local rot = block.get_rotation_profile(bid) == "pipe" and 4 or 0
scale = block.get_hitbox(bid, rot)[2]
body:set_size(vec3.mul(body:get_size(), {1.0, 0.7, 1.0}))
vec3.mul(scale, 1.5, scale)
end
local textures = block.get_textures(bid)
for i,t in ipairs(textures) do
rig:set_texture("$"..tostring(i-1), "blocks:"..textures[i])
end
end
else
body:set_size(vec3.mul(body:get_size(), {1.0, 0.3, 1.0}))
rig:set_model(0, "drop-item")
rig:set_texture("$0", icon)
end
mat4.mul(matrix, rotation, matrix)
mat4.scale(matrix, scale, matrix)
rig:set_matrix(0, matrix)
end
function on_grounded(force)
local matrix = mat4.idt()
mat4.rotate(matrix, {0, 1, 0}, math.random()*360, matrix)
if model == "aabb" then
mat4.rotate(matrix, {1, 0, 0}, 90, matrix)
end
mat4.scale(matrix, scale, matrix)
rig:set_matrix(0, matrix)
inair = false
ready = true
end
function on_fall()
inair = true
end
function on_sensor_enter(index, oid)
local playerentity = player.get_entity(hud.get_player())
if ready and oid == playerentity and index == 0 then
entity:despawn()
inventory.add(player.get_inventory(oid), dropitem.id, dropitem.count)
audio.play_sound_2d("events/pickup", 0.5, 0.8+math.random()*0.4, "regular")
end
if index == 1 and ready and oid == playerentity then
target = oid
end
end
function on_sensor_exit(index, oid)
if oid == target and index == 1 then
target = -1
end
end
function on_render()
if inair then
local dt = time.delta();
mat4.rotate(rotation, {0, 1, 0}, 240*dt, rotation)
mat4.rotate(rotation, {0, 0, 1}, 240*dt, rotation)
local matrix = mat4.idt()
mat4.mul(matrix, rotation, matrix)
mat4.scale(matrix, scale, matrix)
rig:set_matrix(0, matrix)
end
end
function on_update()
if target ~= -1 then
local dir = vec3.sub(entities.get(target).transform:get_pos(), tsf:get_pos())
vec3.normalize(dir, dir)
vec3.mul(dir, 10.0, dir)
body:set_vel(dir)
end
end
function on_attacked(attacker, pid)
body:set_vel({0, 10, 0})
end

View File

@ -0,0 +1,33 @@
local tsf = entity.transform
local body = entity.rigidbody
local rig = entity.skeleton
ARGS = ARGS or {}
local blockid = ARGS.block
if SAVED_DATA.block then
blockid = SAVED_DATA.block
else
SAVED_DATA.block = blockid
end
do -- setup visuals
local textures = block.get_textures(block.index(blockid))
for i,t in ipairs(textures) do
rig:set_texture("$"..tostring(i-1), "blocks:"..textures[i])
end
end
function on_grounded()
local pos = tsf:get_pos()
local ix = math.floor(pos[1])
local iy = math.floor(pos[2])
local iz = math.floor(pos[3])
if block.is_replaceable_at(ix, iy, iz) then
block.set(ix, iy, iz, block.index(blockid))
else
local picking_item = block.get_picking_item(block.index(blockid))
local drop = entities.spawn("base:drop", pos, {base__drop={id=picking_item, count=1}})
drop.rigidbody:set_vel(vec3.spherical_rand(5.0))
end
entity:despawn()
end

View File

@ -0,0 +1,27 @@
local DROP_FORCE = 8
local DROP_INIT_VEL = {0, 3, 0}
function on_hud_open()
input.add_callback("player.drop", function ()
if hud.is_paused() or hud.is_inventory_open() then
return
end
local pid = hud.get_player()
local invid, slot = player.get_inventory(pid)
local itemid, itemcount = inventory.get(invid, slot)
if itemid == 0 then
return
end
inventory.set(invid, slot, itemid, itemcount-1)
local pvel = {player.get_vel(pid)}
local ppos = vec3.add({player.get_pos(pid)}, {0, 0.7, 0})
local throw_force = vec3.mul(player.get_dir(pid), DROP_FORCE)
local drop = entities.spawn("base:drop", ppos, {base__drop={
id=itemid,
count=1
}})
local velocity = vec3.add(throw_force, vec3.add(pvel, DROP_INIT_VEL))
drop.rigidbody:set_vel(velocity)
end)
end

View File

@ -0,0 +1,15 @@
local function update(x, y, z)
if block.is_replaceable_at(x, y-1, z) then
entities.spawn("base:falling_block", {x+0.5, y+0.5, z+0.5},
{base__falling_block={block='base:sand'}})
block.set(x, y, z, 0)
end
end
function on_update(x, y, z)
update(x, y, z)
end
function on_placed(x, y, z)
update(x, y, z)
end

View File

@ -0,0 +1,5 @@
{
"root": {
"model": "block"
}
}

View File

@ -0,0 +1,5 @@
{
"root": {
"model": "drop-block"
}
}

View File

@ -0,0 +1,16 @@
{
"root": {
"nodes": [
{
"name": "body",
"model": "player",
"nodes": [
{
"name": "head",
"model": "player-head"
}
]
}
]
}
}

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 579 B

View File

@ -2,10 +2,19 @@ history = session.get_entry("commands_history")
history_pointer = #history
function setup_variables()
local x,y,z = player.get_pos(hud.get_player())
local pid = hud.get_player()
local x,y,z = player.get_pos(pid)
console.set('pos.x', x)
console.set('pos.y', y)
console.set('pos.z', z)
local pentity = player.get_entity(pid)
if pentity ~= 0 then
console.set('entity.id', pentity)
end
local sentity = player.get_selected_entity(pid)
if sentity ~= nil then
console.set('entity.selected', sentity)
end
end
function on_history_up()

View File

@ -1,6 +1,7 @@
<container size='668,418' color='#0F1E2DB2' context='menu'>
<panel pos='6' size='250' color='0' interval='1'>
<button id='s_aud' onclick='set_page("s_aud", "settings_audio")'>@Audio</button>
<button id='s_dsp' onclick='set_page("s_dsp", "settings_display")'>@Display</button>
<button id='s_gfx' onclick='set_page("s_gfx", "settings_graphics")'>@Graphics</button>
<button id='s_ctl' onclick='set_page("s_ctl", "settings_controls")'>@Controls</button>
</panel>

View File

@ -8,6 +8,7 @@ end
function set_page(btn, page)
document.s_aud.enabled = true
document.s_dsp.enabled = true
document.s_gfx.enabled = true
document.s_ctl.enabled = true
document[btn].enabled = false

View File

@ -0,0 +1,2 @@
<panel size='400' color='0' interval='1' context='menu'>
</panel>

View File

@ -0,0 +1,43 @@
function create_setting(id, name, step, postfix, tooltip)
local info = core.get_setting_info(id)
postfix = postfix or ""
tooltip = tooltip or ""
document.root:add(gui.template("track_setting", {
id=id,
name=gui.str(name, "settings"),
value=core.get_setting(id),
min=info.min,
max=info.max,
step=step,
postfix=postfix,
tooltip=tooltip
}))
update_setting(core.get_setting(id), id, name, postfix)
end
function update_setting(x, id, name, postfix)
core.set_setting(id, x)
-- updating label
document[id..".L"].text = string.format(
"%s: %s%s",
gui.str(name, "settings"),
core.str_setting(id),
postfix
)
end
function create_checkbox(id, name, tooltip)
tooltip = tooltip or ''
document.root:add(string.format(
"<checkbox consumer='function(x) core.set_setting(\"%s\", x) end' checked='%s' tooltip='%s'>%s</checkbox>",
id, core.str_setting(id), gui.str(tooltip, "settings"), gui.str(name, "settings")
))
end
function on_open()
create_setting("camera.fov", "FOV", 1, "°")
create_checkbox("display.fullscreen", "Fullscreen")
create_checkbox("display.vsync", "V-Sync")
create_checkbox("camera.shaking", "Camera Shaking")
create_checkbox("camera.inertia", "Camera Inertia")
end

View File

@ -39,9 +39,5 @@ function on_open()
create_setting("chunks.load-speed", "Load Speed", 1)
create_setting("graphics.fog-curve", "Fog Curve", 0.1)
create_setting("graphics.gamma", "Gamma", 0.05, "", "graphics.gamma.tooltip")
create_setting("camera.fov", "FOV", 1, "°")
create_checkbox("display.fullscreen", "Fullscreen")
create_checkbox("display.vsync", "V-Sync")
create_checkbox("graphics.backlight", "Backlight", "graphics.backlight.tooltip")
create_checkbox("camera.shaking", "Camera Shaking")
end

View File

@ -0,0 +1,126 @@
-- Standard components OOP wrappers (__index tables of metatables)
local Transform = {__index={
get_pos=function(self) return __transform.get_pos(self.eid) end,
set_pos=function(self, v) return __transform.set_pos(self.eid, v) end,
get_size=function(self) return __transform.get_size(self.eid) end,
set_size=function(self, v) return __transform.set_size(self.eid, v) end,
get_rot=function(self) return __transform.get_rot(self.eid) end,
set_rot=function(self, m) return __transform.set_rot(self.eid, m) end,
}}
local function new_Transform(eid)
return setmetatable({eid=eid}, Transform)
end
local Rigidbody = {__index={
is_enabled=function(self) return __rigidbody.is_enabled(self.eid) end,
set_enabled=function(self, b) return __rigidbody.set_enabled(self.eid, b) end,
get_vel=function(self) return __rigidbody.get_vel(self.eid) end,
set_vel=function(self, v) return __rigidbody.set_vel(self.eid, v) end,
get_size=function(self) return __rigidbody.get_size(self.eid) end,
set_size=function(self, v) return __rigidbody.set_size(self.eid, v) end,
get_gravity_scale=function(self) return __rigidbody.get_gravity_scale(self.eid) end,
set_gravity_scale=function(self, s) return __rigidbody.set_gravity_scale(self.eid, s) end,
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,
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,
set_crouching=function(self, b) return __rigidbody.set_crouching(self.eid, b) end,
get_body_type=function(self) return __rigidbody.get_body_type(self.eid) end,
set_body_type=function(self, s) return __rigidbody.set_body_type(self.eid, s) end,
}}
local function new_Rigidbody(eid)
return setmetatable({eid=eid}, Rigidbody)
end
local Skeleton = {__index={
get_model=function(self, i) return __skeleton.get_model(self.eid, i) end,
set_model=function(self, i, s) return __skeleton.set_model(self.eid, i, s) end,
get_matrix=function(self, i) return __skeleton.get_matrix(self.eid, i) end,
set_matrix=function(self, i, m) return __skeleton.set_matrix(self.eid, i, m) end,
get_texture=function(self, s) return __skeleton.get_texture(self.eid, s) end,
set_texture=function(self, s, s2) return __skeleton.set_texture(self.eid, s, s2) end,
index=function(self, s) return __skeleton.index(self.eid, s) end,
is_visible=function(self, i) return __skeleton.is_visible(self.eid, i) end,
set_visible=function(self, i, b) return __skeleton.set_visible(self.eid, i, b) end,
}}
local function new_Skeleton(eid)
return setmetatable({eid=eid}, Skeleton)
end
-- Entity class
local Entity = {__index={
despawn=function(self) return entities.despawn(self.eid) end,
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,
has_component=function(self, name) return self.components[name] ~= nil end,
get_uid=function(self) return self.eid end,
}}
local entities = {}
return {
new_Entity = function(eid)
local entity = setmetatable({eid=eid}, Entity)
entity.transform = new_Transform(eid)
entity.rigidbody = new_Rigidbody(eid)
entity.skeleton = new_Skeleton(eid)
entity.components = {}
entities[eid] = entity;
return entity
end,
get_Entity = function(eid)
return entities[eid]
end,
remove_Entity = function(eid)
local entity = entities[eid]
if entity then
entity.components = nil
entities[eid] = nil;
end
end,
update = function()
for _,entity in pairs(entities) do
for _, component in pairs(entity.components) do
local callback = component.on_update
if callback then
local result, err = pcall(callback)
if err then
debug.error(err)
end
end
end
end
end,
render = function()
for _,entity in pairs(entities) do
for _, component in pairs(entity.components) do
local callback = component.on_render
if callback then
local result, err = pcall(callback)
if err then
debug.error(err)
end
end
end
end
end,
get_all = function(uids)
if uids == nil then
return entities
else
local values = {}
for _, uid in ipairs(uids) do
values[uid] = entities[uid]
end
return values
end
end
}

View File

@ -106,10 +106,14 @@ console.add_command(
)
console.add_command(
"tp obj:sel=$obj.id x:num~pos.x y:num~pos.y z:num~pos.z",
"Teleport object",
"tp entity:sel=$entity.id x:num~pos.x y:num~pos.y z:num~pos.z",
"Teleport entity",
function (args, kwargs)
player.set_pos(unpack(args))
local eid, x, y, z = unpack(args)
local entity = entities.get(eid)
if entity ~= nil then
entity.transform:set_pos({x, y, z})
end
end
)
console.add_command(
@ -142,3 +146,26 @@ console.add_command(
return tostring(w*h*d).." blocks set"
end
)
console.add_command(
"player.respawn player:sel=$obj.id",
"Respawn player entity",
function (args, kwargs)
local eid = entities.spawn("base:player", {player.get_pos(args[1])}):get_uid()
player.set_entity(args[1], eid)
return "spawned new player entity #"..tostring(eid)
end
)
console.add_command(
"entity.despawn entity:sel=$entity.selected",
"Despawn entity",
function (args, kwargs)
local eid = args[1]
local entity = entities.get(eid)
if entity ~= nil then
entity:despawn()
return "despawned entity #"..tostring(eid)
end
end
)

View File

@ -252,9 +252,11 @@ function session.reset_entry(name)
session.entries[name] = nil
end
function timeit(func, ...)
function timeit(iters, func, ...)
local tm = time.uptime()
func(...)
for i=1,iters do
func(...)
end
print("[time mcs]", (time.uptime()-tm) * 1000000)
end
@ -303,6 +305,22 @@ function file.readlines(path)
return lines
end
stdcomp = require "core:internal/stdcomp"
entities.get = stdcomp.get_Entity
entities.get_all = function(uids)
if uids == nil then
local values = {}
for k,v in pairs(stdcomp.get_all()) do
values[k] = v
end
return values
else
return stdcomp.get_all(uids)
end
end
math.randomseed(time.uptime()*1536227939)
-- Deprecated functions
block_index = block.index
block_name = block.name

View File

@ -29,5 +29,6 @@ player.attack=Attack / Break
player.build=Place Block
player.flight=Flight
player.noclip=No-clip
player.drop=Drop Item
camera.zoom=Zoom
camera.mode=Switch Camera Mode

View File

@ -28,6 +28,7 @@ menu.Content Error=Ошибка Контента
menu.Content=Контент
menu.Continue=Продолжить
menu.Controls=Управление
menu.Display=Дисплей
menu.Graphics=Графика
menu.missing-content=Отсутствует Контент!
menu.New World=Новый Мир
@ -50,6 +51,7 @@ world.delete-confirm=Удалить мир безвозвратно?
settings.Ambient=Фон
settings.Backlight=Подсветка
settings.Camera Shaking=Тряска Камеры
settings.Camera Inertia=Инерция Камеры
settings.Fog Curve=Кривая Тумана
settings.FOV=Поле Зрения
settings.Fullscreen=Полный экран
@ -80,5 +82,6 @@ player.pick=Подобрать Блок
player.attack=Атаковать / Сломать
player.build=Поставить Блок
player.flight=Полёт
player.drop=Выбросить Предмет
camera.zoom=Приближение
camera.mode=Сменить Режим Камеры

View File

@ -5,6 +5,7 @@
#include <string>
#include <memory>
#include <optional>
#include <functional>
#include <unordered_map>
#include <typeindex>
@ -16,6 +17,11 @@ class Assets;
namespace assetload {
/// @brief final work to do in the main thread
using postfunc = std::function<void(Assets*)>;
using setupfunc = std::function<void(const Assets*)>;
template<class T>
void assets_setup(const Assets*);
}
class Assets {
@ -23,6 +29,7 @@ class Assets {
using assets_map = std::unordered_map<std::string, std::shared_ptr<void>>;
std::unordered_map<std::type_index, assets_map> assets;
std::vector<assetload::setupfunc> setupFuncs;
public:
Assets() {}
Assets(const Assets&) = delete;
@ -49,6 +56,34 @@ public:
}
return static_cast<T*>(found->second.get());
}
template<class T>
std::optional<const assets_map*> getMap() const {
const auto& mapIter = assets.find(typeid(T));
if (mapIter == assets.end()) {
return std::nullopt;
}
return &mapIter->second;
}
void setup() {
for (auto& setupFunc : setupFuncs) {
setupFunc(this);
}
}
void addSetupFunc(assetload::setupfunc setupfunc) {
setupFuncs.push_back(setupfunc);
}
};
template<class T>
void assetload::assets_setup(const Assets* assets) {
if (auto mapPtr = assets->getMap<T>()) {
for (const auto& entry : **mapPtr) {
static_cast<T*>(entry.second.get())->setup();
}
}
}
#endif // ASSETS_ASSETS_HPP_

View File

@ -12,6 +12,7 @@
#include "../content/Content.hpp"
#include "../content/ContentPack.hpp"
#include "../voxels/Block.hpp"
#include "../objects/rigging.hpp"
#include "../graphics/core/Texture.hpp"
#include "../logic/scripting/scripting.hpp"
@ -24,13 +25,13 @@ static debug::Logger logger("assets-loader");
AssetsLoader::AssetsLoader(Assets* assets, const ResPaths* paths)
: assets(assets), paths(paths)
{
addLoader(AssetType::shader, assetload::shader);
addLoader(AssetType::texture, assetload::texture);
addLoader(AssetType::font, assetload::font);
addLoader(AssetType::atlas, assetload::atlas);
addLoader(AssetType::layout, assetload::layout);
addLoader(AssetType::sound, assetload::sound);
addLoader(AssetType::model, assetload::model);
addLoader(AssetType::SHADER, assetload::shader);
addLoader(AssetType::TEXTURE, assetload::texture);
addLoader(AssetType::FONT, assetload::font);
addLoader(AssetType::ATLAS, assetload::atlas);
addLoader(AssetType::LAYOUT, assetload::layout);
addLoader(AssetType::SOUND, assetload::sound);
addLoader(AssetType::MODEL, assetload::model);
}
void AssetsLoader::addLoader(AssetType tag, aloader_func func) {
@ -80,7 +81,7 @@ void addLayouts(const scriptenv& env, const std::string& prefix, const fs::path&
if (file.extension().u8string() != ".xml")
continue;
std::string name = prefix+":"+file.stem().u8string();
loader.add(AssetType::layout, file.u8string(), name, std::make_shared<LayoutCfg>(env));
loader.add(AssetType::LAYOUT, file.u8string(), name, std::make_shared<LayoutCfg>(env));
}
}
@ -89,18 +90,18 @@ void AssetsLoader::tryAddSound(const std::string& name) {
return;
}
std::string file = SOUNDS_FOLDER+"/"+name;
add(AssetType::sound, file, name);
add(AssetType::SOUND, file, name);
}
static std::string assets_def_folder(AssetType tag) {
switch (tag) {
case AssetType::font: return FONTS_FOLDER;
case AssetType::shader: return SHADERS_FOLDER;
case AssetType::texture: return TEXTURES_FOLDER;
case AssetType::atlas: return TEXTURES_FOLDER;
case AssetType::layout: return LAYOUTS_FOLDER;
case AssetType::sound: return SOUNDS_FOLDER;
case AssetType::model: return MODELS_FOLDER;
case AssetType::FONT: return FONTS_FOLDER;
case AssetType::SHADER: return SHADERS_FOLDER;
case AssetType::TEXTURE: return TEXTURES_FOLDER;
case AssetType::ATLAS: return TEXTURES_FOLDER;
case AssetType::LAYOUT: return LAYOUTS_FOLDER;
case AssetType::SOUND: return SOUNDS_FOLDER;
case AssetType::MODEL: return MODELS_FOLDER;
}
return "<error>";
}
@ -118,7 +119,7 @@ void AssetsLoader::processPreload(
}
map->str("path", path);
switch (tag) {
case AssetType::sound:
case AssetType::SOUND:
add(tag, path, name, std::make_shared<SoundCfg>(
map->get("keep-pcm", false)
));
@ -153,11 +154,11 @@ void AssetsLoader::processPreloadList(AssetType tag, dynamic::List* list) {
void AssetsLoader::processPreloadConfig(const fs::path& file) {
auto root = files::read_json(file);
processPreloadList(AssetType::font, root->list("fonts"));
processPreloadList(AssetType::shader, root->list("shaders"));
processPreloadList(AssetType::texture, root->list("textures"));
processPreloadList(AssetType::sound, root->list("sounds"));
processPreloadList(AssetType::model, root->list("models"));
processPreloadList(AssetType::FONT, root->list("fonts").get());
processPreloadList(AssetType::SHADER, root->list("shaders").get());
processPreloadList(AssetType::TEXTURE, root->list("textures").get());
processPreloadList(AssetType::SOUND, root->list("sounds").get());
processPreloadList(AssetType::MODEL, root->list("models").get());
// layouts are loaded automatically
}
@ -176,18 +177,18 @@ void AssetsLoader::processPreloadConfigs(const Content* content) {
}
void AssetsLoader::addDefaults(AssetsLoader& loader, const Content* content) {
loader.add(AssetType::font, FONTS_FOLDER+"/font", "normal");
loader.add(AssetType::shader, SHADERS_FOLDER+"/ui", "ui");
loader.add(AssetType::shader, SHADERS_FOLDER+"/main", "main");
loader.add(AssetType::shader, SHADERS_FOLDER+"/lines", "lines");
loader.add(AssetType::texture, TEXTURES_FOLDER+"/gui/menubg", "gui/menubg");
loader.add(AssetType::texture, TEXTURES_FOLDER+"/gui/delete_icon", "gui/delete_icon");
loader.add(AssetType::texture, TEXTURES_FOLDER+"/gui/no_icon", "gui/no_icon");
loader.add(AssetType::texture, TEXTURES_FOLDER+"/gui/no_world_icon", "gui/no_world_icon");
loader.add(AssetType::texture, TEXTURES_FOLDER+"/gui/warning", "gui/warning");
loader.add(AssetType::texture, TEXTURES_FOLDER+"/gui/error", "gui/error");
loader.add(AssetType::texture, TEXTURES_FOLDER+"/gui/cross", "gui/cross");
loader.add(AssetType::texture, TEXTURES_FOLDER+"/gui/refresh", "gui/refresh");
loader.add(AssetType::FONT, FONTS_FOLDER+"/font", "normal");
loader.add(AssetType::SHADER, SHADERS_FOLDER+"/ui", "ui");
loader.add(AssetType::SHADER, SHADERS_FOLDER+"/main", "main");
loader.add(AssetType::SHADER, SHADERS_FOLDER+"/lines", "lines");
loader.add(AssetType::TEXTURE, TEXTURES_FOLDER+"/gui/menubg", "gui/menubg");
loader.add(AssetType::TEXTURE, TEXTURES_FOLDER+"/gui/delete_icon", "gui/delete_icon");
loader.add(AssetType::TEXTURE, TEXTURES_FOLDER+"/gui/no_icon", "gui/no_icon");
loader.add(AssetType::TEXTURE, TEXTURES_FOLDER+"/gui/no_world_icon", "gui/no_world_icon");
loader.add(AssetType::TEXTURE, TEXTURES_FOLDER+"/gui/warning", "gui/warning");
loader.add(AssetType::TEXTURE, TEXTURES_FOLDER+"/gui/error", "gui/error");
loader.add(AssetType::TEXTURE, TEXTURES_FOLDER+"/gui/cross", "gui/cross");
loader.add(AssetType::TEXTURE, TEXTURES_FOLDER+"/gui/refresh", "gui/refresh");
if (content) {
loader.processPreloadConfigs(content);
@ -205,9 +206,19 @@ void AssetsLoader::addDefaults(AssetsLoader& loader, const Content* content) {
fs::path folder = info.folder / fs::path("layouts");
addLayouts(pack->getEnvironment(), info.id, folder, loader);
}
for (auto& entry : content->getSkeletons()) {
auto& skeleton = *entry.second;
for (auto& bone : skeleton.getBones()) {
auto& model = bone->model.name;
if (!model.empty()) {
loader.add(AssetType::MODEL, MODELS_FOLDER+"/"+model, model);
}
}
}
}
loader.add(AssetType::atlas, TEXTURES_FOLDER+"/blocks", "blocks");
loader.add(AssetType::atlas, TEXTURES_FOLDER+"/items", "items");
loader.add(AssetType::ATLAS, TEXTURES_FOLDER+"/blocks", "blocks");
loader.add(AssetType::ATLAS, TEXTURES_FOLDER+"/items", "items");
}
bool AssetsLoader::loadExternalTexture(

View File

@ -20,13 +20,13 @@ namespace dynamic {
}
enum class AssetType {
texture,
shader,
font,
atlas,
layout,
sound,
model,
TEXTURE,
SHADER,
FONT,
ATLAS,
LAYOUT,
SOUND,
MODEL
};
class ResPaths;

View File

@ -18,6 +18,7 @@
#include "../graphics/core/Font.hpp"
#include "../graphics/core/Model.hpp"
#include "../graphics/core/TextureAnimation.hpp"
#include "../objects/rigging.hpp"
#include "../frontend/UiDocument.hpp"
#include "../constants.hpp"
@ -214,8 +215,10 @@ assetload::postfunc assetload::model(
auto model = obj::parse(path.u8string(), text).release();
return [=](Assets* assets) {
for (auto& mesh : model->meshes) {
auto filename = TEXTURES_FOLDER+"/"+mesh.texture;
loader->add(AssetType::texture, filename, mesh.texture, nullptr);
if (mesh.texture.find('$') == std::string::npos) {
auto filename = TEXTURES_FOLDER+"/"+mesh.texture;
loader->add(AssetType::TEXTURE, filename, mesh.texture, nullptr);
}
}
assets->store(std::unique_ptr<model::Model>(model), name);
};

View File

@ -18,49 +18,49 @@ namespace assetload {
AssetsLoader*,
const ResPaths* paths,
const std::string& filename,
const std::string &name,
const std::string& name,
const std::shared_ptr<AssetCfg>& settings
);
postfunc shader(
AssetsLoader*,
const ResPaths* paths,
const std::string& filename,
const std::string &name,
const std::string& name,
const std::shared_ptr<AssetCfg>& settings
);
postfunc atlas(
AssetsLoader*,
const ResPaths* paths,
const std::string &directory,
const std::string &name,
const std::string& directory,
const std::string& name,
const std::shared_ptr<AssetCfg>& settings
);
postfunc font(
AssetsLoader*,
const ResPaths* paths,
const std::string& filename,
const std::string &name,
const std::string& name,
const std::shared_ptr<AssetCfg>& settings
);
postfunc layout(
AssetsLoader*,
const ResPaths* paths,
const std::string& file,
const std::string &name,
const std::string& name,
const std::shared_ptr<AssetCfg>& settings
);
postfunc sound(
AssetsLoader*,
const ResPaths* paths,
const std::string& file,
const std::string &name,
const std::string& name,
const std::shared_ptr<AssetCfg>& settings
);
postfunc model(
AssetsLoader*,
const ResPaths* paths,
const std::string& file,
const std::string &name,
const std::string& name,
const std::shared_ptr<AssetCfg>& settings
);
}

View File

@ -83,6 +83,13 @@ std::vector<ubyte> json::to_binary(const Map* obj, bool compress) {
return builder.build();
}
std::vector<ubyte> json::to_binary(const Value& value, bool compress) {
if (auto map = std::get_if<Map_sptr>(&value)) {
return to_binary(map->get(), compress);
}
throw std::runtime_error("map is only supported as the root element");
}
static Value value_from_binary(ByteReader& reader) {
ubyte typecode = reader.get();
switch (typecode) {

View File

@ -1,7 +1,7 @@
#ifndef CODERS_BINARY_JSON_HPP_
#define CODERS_BINARY_JSON_HPP_
#include "../typedefs.hpp"
#include "../data/dynamic_fwd.hpp"
#include <vector>
#include <memory>
@ -26,8 +26,9 @@ namespace json {
inline constexpr int BJSON_TYPE_NULL = 0xC;
inline constexpr int BJSON_TYPE_CDOCUMENT = 0x1F;
extern std::vector<ubyte> to_binary(const dynamic::Map* obj, bool compress=false);
extern std::shared_ptr<dynamic::Map> from_binary(const ubyte* src, size_t size);
std::vector<ubyte> to_binary(const dynamic::Map* obj, bool compress=false);
std::vector<ubyte> to_binary(const dynamic::Value& obj, bool compress=false);
std::shared_ptr<dynamic::Map> from_binary(const ubyte* src, size_t size);
}
#endif // CODERS_BINARY_JSON_HPP_

View File

@ -97,6 +97,10 @@ void stringifyObj(
const std::string& indentstr,
bool nice
) {
if (obj == nullptr) {
ss << "nullptr";
return;
}
if (obj->values.empty()) {
ss << "{}";
return;
@ -238,11 +242,11 @@ Value Parser::parseValue() {
throw error("unexpected character '"+std::string({next})+"'");
}
dynamic::Map_sptr json::parse(const std::string& filename, const std::string& source) {
dynamic::Map_sptr json::parse(std::string_view filename, std::string_view source) {
Parser parser(filename, source);
return parser.parse();
}
dynamic::Map_sptr json::parse(const std::string& source) {
dynamic::Map_sptr json::parse(std::string_view source) {
return parse("<string>", source);
}

View File

@ -9,8 +9,8 @@
#include <string>
namespace json {
dynamic::Map_sptr parse(const std::string& filename, const std::string& source);
dynamic::Map_sptr parse(const std::string& source);
dynamic::Map_sptr parse(std::string_view filename, std::string_view source);
dynamic::Map_sptr parse(std::string_view source);
std::string stringify(
const dynamic::Map* obj,

View File

@ -19,6 +19,7 @@ inline const std::string ENGINE_VERSION_STRING = "0.22";
inline constexpr blockid_t BLOCK_AIR = 0;
inline constexpr itemid_t ITEM_EMPTY = 0;
inline constexpr entityid_t ENTITY_NONE = 0;
inline constexpr int CHUNK_W = 16;
inline constexpr int CHUNK_H = 256;
@ -50,5 +51,6 @@ inline const std::string FONTS_FOLDER = "fonts";
inline const std::string LAYOUTS_FOLDER = "layouts";
inline const std::string SOUNDS_FOLDER = "sounds";
inline const std::string MODELS_FOLDER = "models";
inline const std::string SKELETONS_FOLDER = "skeletons";
#endif // CONSTANTS_HPP_

View File

@ -7,67 +7,56 @@
#include "../voxels/Block.hpp"
#include "../items/ItemDef.hpp"
#include "../objects/EntityDef.hpp"
#include "../objects/rigging.hpp"
#include "ContentPack.hpp"
#include "../logic/scripting/scripting.hpp"
ContentIndices::ContentIndices(
std::vector<Block*> blockDefs,
std::vector<ItemDef*> itemDefs
) : blockDefs(std::move(blockDefs)),
itemDefs(std::move(itemDefs))
ContentUnitIndices<Block> blocks,
ContentUnitIndices<ItemDef> items,
ContentUnitIndices<EntityDef> entities
) : blocks(std::move(blocks)),
items(std::move(items)),
entities(std::move(entities))
{}
Content::Content(
std::unique_ptr<ContentIndices> indices,
std::unique_ptr<DrawGroups> drawGroups,
std::unordered_map<std::string, std::unique_ptr<Block>> blockDefs,
std::unordered_map<std::string, std::unique_ptr<ItemDef>> itemDefs,
std::unordered_map<std::string, std::unique_ptr<ContentPackRuntime>> packs,
std::unordered_map<std::string, std::unique_ptr<BlockMaterial>> blockMaterials
) : blockDefs(std::move(blockDefs)),
itemDefs(std::move(itemDefs)),
indices(std::move(indices)),
ContentUnitDefs<Block> blocks,
ContentUnitDefs<ItemDef> items,
ContentUnitDefs<EntityDef> entities,
UptrsMap<std::string, ContentPackRuntime> packs,
UptrsMap<std::string, BlockMaterial> blockMaterials,
UptrsMap<std::string, rigging::SkeletonConfig> skeletons,
ResourceIndicesSet resourceIndices
) : indices(std::move(indices)),
packs(std::move(packs)),
blockMaterials(std::move(blockMaterials)),
drawGroups(std::move(drawGroups))
{}
skeletons(std::move(skeletons)),
blocks(std::move(blocks)),
items(std::move(items)),
entities(std::move(entities)),
drawGroups(std::move(drawGroups))
{
for (size_t i = 0; i < RESOURCE_TYPES_COUNT; i++) {
this->resourceIndices[i] = std::move(resourceIndices[i]);
}
}
Content::~Content() {
}
Block* Content::findBlock(const std::string& id) const {
auto found = blockDefs.find(id);
if (found == blockDefs.end()) {
const rigging::SkeletonConfig* Content::getSkeleton(const std::string& id) const {
auto found = skeletons.find(id);
if (found == skeletons.end()) {
return nullptr;
}
return found->second.get();
}
Block& Content::requireBlock(const std::string& id) const {
auto found = blockDefs.find(id);
if (found == blockDefs.end()) {
throw std::runtime_error("missing block "+id);
}
return *found->second;
}
ItemDef* Content::findItem(const std::string& id) const {
auto found = itemDefs.find(id);
if (found == itemDefs.end()) {
return nullptr;
}
return found->second.get();
}
ItemDef& Content::requireItem(const std::string& id) const {
auto found = itemDefs.find(id);
if (found == itemDefs.end()) {
throw std::runtime_error("missing item "+id);
}
return *found->second;
}
const BlockMaterial* Content::findBlockMaterial(const std::string& id) const {
auto found = blockMaterials.find(id);
if (found == blockMaterials.end()) {
@ -84,10 +73,14 @@ const ContentPackRuntime* Content::getPackRuntime(const std::string& id) const {
return found->second.get();
}
const std::unordered_map<std::string, std::unique_ptr<BlockMaterial>>& Content::getBlockMaterials() const {
const UptrsMap<std::string, BlockMaterial>& Content::getBlockMaterials() const {
return blockMaterials;
}
const std::unordered_map<std::string, std::unique_ptr<ContentPackRuntime>>& Content::getPacks() const {
const UptrsMap<std::string, ContentPackRuntime>& Content::getPacks() const {
return packs;
}
const UptrsMap<std::string, rigging::SkeletonConfig>& Content::getSkeletons() const {
return skeletons;
}

View File

@ -1,32 +1,37 @@
#ifndef CONTENT_CONTENT_HPP_
#define CONTENT_CONTENT_HPP_
#include "../typedefs.hpp"
#include "content_fwd.hpp"
#include "../data/dynamic_fwd.hpp"
#include <string>
#include <vector>
#include <memory>
#include <optional>
#include <stdexcept>
#include <unordered_map>
#include <set>
using DrawGroups = std::set<ubyte>;
template<class K, class V>
using UptrsMap = std::unordered_map<K, std::unique_ptr<V>>;
class Block;
struct BlockMaterial;
class ItemDef;
class Content;
class ContentPackRuntime;
struct ItemDef;
struct EntityDef;
enum class contenttype {
none, block, item
};
namespace rigging {
class SkeletonConfig;
}
inline const char* contenttype_name(contenttype type) {
constexpr const char* contenttype_name(contenttype type) {
switch (type) {
case contenttype::none: return "none";
case contenttype::block: return "block";
case contenttype::item: return "item";
case contenttype::entity: return "entity";
default:
return "unknown";
}
@ -43,82 +48,165 @@ public:
}
};
/// @brief Runtime defs cache: indices
class ContentIndices {
std::vector<Block*> blockDefs;
std::vector<ItemDef*> itemDefs;
template<class T>
class ContentUnitIndices {
std::vector<T*> defs;
public:
ContentIndices(
std::vector<Block*> blockDefs,
std::vector<ItemDef*> itemDefs
);
ContentUnitIndices(std::vector<T*> defs) : defs(std::move(defs)) {}
inline Block* getBlockDef(blockid_t id) const {
if (id >= blockDefs.size())
inline T* get(blockid_t id) const {
if (id >= defs.size()) {
return nullptr;
return blockDefs[id];
}
return defs[id];
}
inline ItemDef* getItemDef(itemid_t id) const {
if (id >= itemDefs.size())
return nullptr;
return itemDefs[id];
inline size_t count() const {
return defs.size();
}
inline size_t countBlockDefs() const {
return blockDefs.size();
}
inline size_t countItemDefs() const {
return itemDefs.size();
}
// use this for critical spots to prevent range check overhead
const Block* const* getBlockDefs() const {
return blockDefs.data();
}
const ItemDef* const* getItemDefs() const {
return itemDefs.data();
inline const T* const* getDefs() const {
return defs.data();
}
};
/* Content is a definitions repository */
class Content {
std::unordered_map<std::string, std::unique_ptr<Block>> blockDefs;
std::unordered_map<std::string, std::unique_ptr<ItemDef>> itemDefs;
std::unique_ptr<ContentIndices> indices;
std::unordered_map<std::string, std::unique_ptr<ContentPackRuntime>> packs;
std::unordered_map<std::string, std::unique_ptr<BlockMaterial>> blockMaterials;
/// @brief Runtime defs cache: indices
class ContentIndices {
public:
ContentUnitIndices<Block> blocks;
ContentUnitIndices<ItemDef> items;
ContentUnitIndices<EntityDef> entities;
ContentIndices(
ContentUnitIndices<Block> blocks,
ContentUnitIndices<ItemDef> items,
ContentUnitIndices<EntityDef> entities
);
};
template<class T>
class ContentUnitDefs {
UptrsMap<std::string, T> defs;
public:
ContentUnitDefs(UptrsMap<std::string, T> defs)
: defs(std::move(defs)) {
}
T* find(const std::string& id) const {
const auto& found = defs.find(id);
if (found == defs.end()) {
return nullptr;
}
return found->second.get();
}
T& require(const std::string& id) const {
const auto& found = defs.find(id);
if (found == defs.end()) {
throw std::runtime_error("missing content unit "+id);
}
return *found->second;
}
};
class ResourceIndices {
std::vector<std::string> names;
std::unordered_map<std::string, size_t> indices;
std::unique_ptr<std::vector<dynamic::Map_sptr>> savedData;
public:
ResourceIndices()
: savedData(std::make_unique<std::vector<dynamic::Map_sptr>>()){
}
static constexpr size_t MISSING = SIZE_MAX;
void add(std::string name, dynamic::Map_sptr map) {
indices[name] = names.size();
names.push_back(name);
savedData->push_back(map);
}
const std::string& getName(size_t index) const {
return names.at(index);
}
size_t indexOf(const std::string& name) const {
const auto& found = indices.find(name);
if (found != indices.end()) {
return found->second;
}
return MISSING;
}
dynamic::Map_sptr getSavedData(size_t index) const {
return savedData->at(index);
}
void saveData(size_t index, dynamic::Map_sptr map) const {
savedData->at(index) = map;
}
size_t size() const {
return names.size();
}
};
constexpr const char* to_string(ResourceType type) {
switch (type) {
case ResourceType::CAMERA: return "camera";
default: return "unknown";
}
}
inline std::optional<ResourceType> ResourceType_from(std::string_view str) {
if (str == "camera") {
return ResourceType::CAMERA;
}
return std::nullopt;
}
using ResourceIndicesSet = ResourceIndices[RESOURCE_TYPES_COUNT];
/// @brief Content is a definitions repository
class Content {
std::unique_ptr<ContentIndices> indices;
UptrsMap<std::string, ContentPackRuntime> packs;
UptrsMap<std::string, BlockMaterial> blockMaterials;
UptrsMap<std::string, rigging::SkeletonConfig> skeletons;
public:
ContentUnitDefs<Block> blocks;
ContentUnitDefs<ItemDef> items;
ContentUnitDefs<EntityDef> entities;
std::unique_ptr<DrawGroups> const drawGroups;
ResourceIndicesSet resourceIndices {};
Content(
std::unique_ptr<ContentIndices> indices,
std::unique_ptr<DrawGroups> drawGroups,
std::unordered_map<std::string, std::unique_ptr<Block>> blockDefs,
std::unordered_map<std::string, std::unique_ptr<ItemDef>> itemDefs,
std::unordered_map<std::string, std::unique_ptr<ContentPackRuntime>> packs,
std::unordered_map<std::string, std::unique_ptr<BlockMaterial>> blockMaterials
ContentUnitDefs<Block> blocks,
ContentUnitDefs<ItemDef> items,
ContentUnitDefs<EntityDef> entities,
UptrsMap<std::string, ContentPackRuntime> packs,
UptrsMap<std::string, BlockMaterial> blockMaterials,
UptrsMap<std::string, rigging::SkeletonConfig> skeletons,
ResourceIndicesSet resourceIndices
);
~Content();
inline ContentIndices* getIndices() const {
return indices.get();
}
Block* findBlock(const std::string& id) const;
Block& requireBlock(const std::string& id) const;
ItemDef* findItem(const std::string& id) const;
ItemDef& requireItem(const std::string& id) const;
inline const ResourceIndices& getIndices(ResourceType type) const {
return resourceIndices[static_cast<size_t>(type)];
}
const rigging::SkeletonConfig* getSkeleton(const std::string& id) const;
const BlockMaterial* findBlockMaterial(const std::string& id) const;
const ContentPackRuntime* getPackRuntime(const std::string& id) const;
const std::unordered_map<std::string, std::unique_ptr<BlockMaterial>>& getBlockMaterials() const;
const std::unordered_map<std::string, std::unique_ptr<ContentPackRuntime>>& getPacks() const;
const UptrsMap<std::string, BlockMaterial>& getBlockMaterials() const;
const UptrsMap<std::string, ContentPackRuntime>& getPacks() const;
const UptrsMap<std::string, rigging::SkeletonConfig>& getSkeletons() const;
};
#endif // CONTENT_CONTENT_HPP_

View File

@ -1,31 +1,15 @@
#include "ContentBuilder.hpp"
#include "../objects/rigging.hpp"
ContentBuilder::~ContentBuilder() {}
void ContentBuilder::add(std::unique_ptr<ContentPackRuntime> pack) {
packs[pack->getId()] = std::move(pack);
}
Block& ContentBuilder::createBlock(const std::string& id) {
auto found = blockDefs.find(id);
if (found != blockDefs.end()) {
return *found->second;
}
checkIdentifier(id);
blockIds.push_back(id);
blockDefs[id] = std::make_unique<Block>(id);
return *blockDefs[id];
}
ItemDef& ContentBuilder::createItem(const std::string& id) {
auto found = itemDefs.find(id);
if (found != itemDefs.end()) {
return *found->second;
}
checkIdentifier(id);
itemIds.push_back(id);
itemDefs[id] = std::make_unique<ItemDef>(id);
return *itemDefs[id];
void ContentBuilder::add(std::unique_ptr<rigging::SkeletonConfig> skeleton) {
skeletons[skeleton->getName()] = std::move(skeleton);
}
BlockMaterial& ContentBuilder::createBlockMaterial(const std::string& id) {
@ -35,28 +19,11 @@ BlockMaterial& ContentBuilder::createBlockMaterial(const std::string& id) {
return material;
}
void ContentBuilder::checkIdentifier(const std::string& id) {
contenttype result;
if (((result = checkContentType(id)) != contenttype::none)) {
throw namereuse_error("name "+id+" is already used", result);
}
}
contenttype ContentBuilder::checkContentType(const std::string& id) {
if (blockDefs.find(id) != blockDefs.end()) {
return contenttype::block;
}
if (itemDefs.find(id) != itemDefs.end()) {
return contenttype::item;
}
return contenttype::none;
}
std::unique_ptr<Content> ContentBuilder::build() {
std::vector<Block*> blockDefsIndices;
auto groups = std::make_unique<DrawGroups>();
for (const std::string& name : blockIds) {
Block& def = *blockDefs[name];
for (const std::string& name : blocks.names) {
Block& def = *blocks.defs[name];
// Generating runtime info
def.rt.id = blockDefsIndices.size();
@ -79,8 +46,8 @@ std::unique_ptr<Content> ContentBuilder::build() {
}
std::vector<ItemDef*> itemDefsIndices;
for (const std::string& name : itemIds) {
ItemDef& def = *itemDefs[name];
for (const std::string& name : items.names) {
ItemDef& def = *items.defs[name];
// Generating runtime info
def.rt.id = itemDefsIndices.size();
@ -88,22 +55,37 @@ std::unique_ptr<Content> ContentBuilder::build() {
itemDefsIndices.push_back(&def);
}
std::vector<EntityDef*> entityDefsIndices;
for (const std::string& name : entities.names) {
EntityDef& def = *entities.defs[name];
// Generating runtime info
def.rt.id = entityDefsIndices.size();
entityDefsIndices.push_back(&def);
}
auto content = std::make_unique<Content>(
std::make_unique<ContentIndices>(blockDefsIndices, itemDefsIndices),
std::move(groups),
std::move(blockDefs),
std::move(itemDefs),
std::move(packs),
std::move(blockMaterials)
std::make_unique<ContentIndices>(
blockDefsIndices,
itemDefsIndices,
entityDefsIndices),
std::move(groups),
blocks.build(),
items.build(),
entities.build(),
std::move(packs),
std::move(blockMaterials),
std::move(skeletons),
std::move(resourceIndices)
);
// Now, it's time to resolve foreign keys
for (Block* def : blockDefsIndices) {
def->rt.pickingItem = content->requireItem(def->pickingItem).rt.id;
def->rt.pickingItem = content->items.require(def->pickingItem).rt.id;
}
for (ItemDef* def : itemDefsIndices) {
def->rt.placingBlock = content->requireBlock(def->placingBlock).rt.id;
def->rt.placingBlock = content->blocks.require(def->placingBlock).rt.id;
}
return content;

View File

@ -3,6 +3,7 @@
#include "../items/ItemDef.hpp"
#include "../voxels/Block.hpp"
#include "../objects/EntityDef.hpp"
#include "../content/Content.hpp"
#include "../content/ContentPack.hpp"
@ -10,27 +11,61 @@
#include <vector>
#include <unordered_map>
class ContentBuilder {
std::unordered_map<std::string, std::unique_ptr<Block>> blockDefs;
std::vector<std::string> blockIds;
template<class T>
class ContentUnitBuilder {
std::unordered_map<std::string, contenttype>& allNames;
contenttype type;
std::unordered_map<std::string, std::unique_ptr<ItemDef>> itemDefs;
std::vector<std::string> itemIds;
std::unordered_map<std::string, std::unique_ptr<BlockMaterial>> blockMaterials;
std::unordered_map<std::string, std::unique_ptr<ContentPackRuntime>> packs;
void checkIdentifier(const std::string& id) {
const auto& found = allNames.find(id);
if (found != allNames.end()) {
throw namereuse_error("name "+id+" is already used", found->second);
}
}
public:
UptrsMap<std::string, T> defs;
std::vector<std::string> names;
ContentUnitBuilder(
std::unordered_map<std::string, contenttype>& allNames,
contenttype type
) : allNames(allNames), type(type) {}
T& create(const std::string& id) {
auto found = defs.find(id);
if (found != defs.end()) {
return *found->second;
}
checkIdentifier(id);
allNames[id] = type;
names.push_back(id);
defs[id] = std::make_unique<T>(id);
return *defs[id];
}
auto build() {
return std::move(defs);
}
};
class ContentBuilder {
UptrsMap<std::string, BlockMaterial> blockMaterials;
UptrsMap<std::string, rigging::SkeletonConfig> skeletons;
UptrsMap<std::string, ContentPackRuntime> packs;
std::unordered_map<std::string, contenttype> allNames;
public:
ContentUnitBuilder<Block> blocks {allNames, contenttype::block};
ContentUnitBuilder<ItemDef> items {allNames, contenttype::item};
ContentUnitBuilder<EntityDef> entities {allNames, contenttype::entity};
ResourceIndicesSet resourceIndices {};
~ContentBuilder();
void add(std::unique_ptr<ContentPackRuntime> pack);
void add(std::unique_ptr<rigging::SkeletonConfig> skeleton);
Block& createBlock(const std::string& id);
ItemDef& createItem(const std::string& id);
BlockMaterial& createBlockMaterial(const std::string& id);
void checkIdentifier(const std::string& id);
contenttype checkContentType(const std::string& id);
std::unique_ptr<Content> build();
};

View File

@ -6,31 +6,16 @@
#include "../coders/json.hpp"
#include "../voxels/Block.hpp"
#include "../items/ItemDef.hpp"
#include "../data/dynamic.hpp"
#include <memory>
ContentLUT::ContentLUT(const Content* content, size_t blocksCount, size_t itemsCount) {
auto* indices = content->getIndices();
for (size_t i = 0; i < blocksCount; i++) {
blocks.push_back(i);
}
for (size_t i = 0; i < indices->countBlockDefs(); i++) {
blockNames.push_back(indices->getBlockDef(i)->name);
}
for (size_t i = indices->countBlockDefs(); i < blocksCount; i++) {
blockNames.emplace_back("");
}
ContentLUT::ContentLUT(const ContentIndices* indices, size_t blocksCount, size_t itemsCount)
: blocks(blocksCount, indices->blocks, BLOCK_VOID, contenttype::block),
items(itemsCount, indices->items, ITEM_VOID, contenttype::item)
{}
for (size_t i = 0; i < itemsCount; i++) {
items.push_back(i);
}
for (size_t i = 0; i < indices->countItemDefs(); i++) {
itemNames.push_back(indices->getItemDef(i)->name);
}
for (size_t i = indices->countItemDefs(); i < itemsCount; i++) {
itemNames.emplace_back();
}
template<class T> static constexpr size_t get_entries_count(
const ContentUnitIndices<T>& indices, const dynamic::List_sptr& list) {
return list ? std::max(list->size(), indices.count()) : indices.count();
}
std::shared_ptr<ContentLUT> ContentLUT::create(
@ -42,38 +27,13 @@ std::shared_ptr<ContentLUT> ContentLUT::create(
auto itemlist = root->list("items");
auto* indices = content->getIndices();
size_t blocks_c = blocklist
? std::max(blocklist->size(), indices->countBlockDefs())
: indices->countBlockDefs();
size_t items_c = itemlist
? std::max(itemlist->size(), indices->countItemDefs())
: indices->countItemDefs();
size_t blocks_c = get_entries_count(indices->blocks, blocklist);
size_t items_c = get_entries_count(indices->items, itemlist);
auto lut = std::make_shared<ContentLUT>(content, blocks_c, items_c);
auto lut = std::make_shared<ContentLUT>(indices, blocks_c, items_c);
if (blocklist) {
for (size_t i = 0; i < blocklist->size(); i++) {
std::string name = blocklist->str(i);
Block* def = content->findBlock(name);
if (def) {
lut->setBlock(i, name, def->rt.id);
} else {
lut->setBlock(i, name, BLOCK_VOID);
}
}
}
if (itemlist) {
for (size_t i = 0; i < itemlist->size(); i++) {
std::string name = itemlist->str(i);
ItemDef* def = content->findItem(name);
if (def) {
lut->setItem(i, name, def->rt.id);
} else {
lut->setItem(i, name, ITEM_VOID);
}
}
}
lut->blocks.setup(blocklist.get(), content->blocks);
lut->items.setup(itemlist.get(), content->items);
if (lut->hasContentReorder() || lut->hasMissingContent()) {
return lut;
@ -84,17 +44,7 @@ std::shared_ptr<ContentLUT> ContentLUT::create(
std::vector<contententry> ContentLUT::getMissingContent() const {
std::vector<contententry> entries;
for (size_t i = 0; i < blocks.size(); i++) {
if (blocks[i] == BLOCK_VOID) {
auto& name = blockNames[i];
entries.push_back(contententry {contenttype::block, name});
}
}
for (size_t i = 0; i < items.size(); i++) {
if (items[i] == ITEM_VOID) {
auto& name = itemNames[i];
entries.push_back(contententry {contenttype::item, name});
}
}
blocks.getMissingContent(entries);
items.getMissingContent(entries);
return entries;
}

View File

@ -5,6 +5,7 @@
#include "../typedefs.hpp"
#include "../constants.hpp"
#include "../data/dynamic.hpp"
#include <string>
#include <utility>
@ -18,58 +19,82 @@ struct contententry {
std::string name;
};
// TODO: make it unified for all types of content
template<typename T, class U>
class ContentUnitLUT {
std::vector<T> indices;
std::vector<std::string> names;
bool missingContent = false;
bool reorderContent = false;
T missingValue;
contenttype type;
public:
ContentUnitLUT(size_t count, const ContentUnitIndices<U>& unitIndices, T missingValue, contenttype type)
: missingValue(missingValue), type(type) {
for (size_t i = 0; i < count; i++) {
indices.push_back(i);
}
for (size_t i = 0; i < unitIndices.count(); i++) {
names.push_back(unitIndices.get(i)->name);
}
for (size_t i = unitIndices.count(); i < count; i++) {
names.emplace_back("");
}
}
void setup(dynamic::List* list, const ContentUnitDefs<U>& defs) {
if (list) {
for (size_t i = 0; i < list->size(); i++) {
std::string name = list->str(i);
if (auto def = defs.find(name)) {
set(i, name, def->rt.id);
} else {
set(i, name, missingValue);
}
}
}
}
void getMissingContent(std::vector<contententry>& entries) const {
for (size_t i = 0; i < count(); i++) {
if (indices[i] == missingValue) {
auto& name = names[i];
entries.push_back(contententry {type, name});
}
}
}
inline const std::string& getName(T index) const {
return names[index];
}
inline T getId(T index) const {
return indices[index];
}
inline void set(T index, std::string name, T id) {
indices[index] = id;
names[index] = std::move(name);
if (id == missingValue) {
missingContent = true;
} else if (index != id) {
reorderContent = true;
}
}
inline size_t count() const {
return indices.size();
}
inline bool hasContentReorder() const {
return reorderContent;
}
inline bool hasMissingContent() const {
return missingContent;
}
};
/// @brief Content indices lookup table or report
/// used to convert world with different indices
/// Building with indices.json
class ContentLUT {
std::vector<blockid_t> blocks;
std::vector<std::string> blockNames;
std::vector<itemid_t> items;
std::vector<std::string> itemNames;
bool reorderContent = false;
bool missingContent = false;
public:
ContentLUT(const Content* content, size_t blocks, size_t items);
ContentUnitLUT<blockid_t, Block> blocks;
ContentUnitLUT<itemid_t, ItemDef> items;
inline const std::string& getBlockName(blockid_t index) const {
return blockNames[index];
}
inline blockid_t getBlockId(blockid_t index) const {
return blocks[index];
}
inline void setBlock(blockid_t index, std::string name, blockid_t id) {
blocks[index] = id;
blockNames[index] = std::move(name);
if (id == BLOCK_VOID) {
missingContent = true;
} else if (index != id) {
reorderContent = true;
}
}
inline const std::string& getItemName(blockid_t index) const {
return itemNames[index];
}
inline itemid_t getItemId(itemid_t index) const {
return items[index];
}
inline void setItem(itemid_t index, std::string name, itemid_t id) {
items[index] = id;
itemNames[index] = std::move(name);
if (id == ITEM_VOID) {
missingContent = true;
} else if (index != id) {
reorderContent = true;
}
}
ContentLUT(const ContentIndices* indices, size_t blocks, size_t items);
static std::shared_ptr<ContentLUT> create(
const fs::path& filename,
@ -77,18 +102,10 @@ public:
);
inline bool hasContentReorder() const {
return reorderContent;
return blocks.hasContentReorder() || items.hasContentReorder();
}
inline bool hasMissingContent() const {
return missingContent;
}
inline size_t countBlocks() const {
return blocks.size();
}
inline size_t countItems() const {
return items.size();
return blocks.hasMissingContent() || items.hasMissingContent();
}
std::vector<contententry> getMissingContent() const;

View File

@ -9,9 +9,11 @@
#include "../debug/Logger.hpp"
#include "../files/files.hpp"
#include "../items/ItemDef.hpp"
#include "../objects/rigging.hpp"
#include "../logic/scripting/scripting.hpp"
#include "../typedefs.hpp"
#include "../util/listutil.hpp"
#include "../util/stringutil.hpp"
#include "../voxels/Block.hpp"
#include <iostream>
@ -24,7 +26,37 @@ namespace fs = std::filesystem;
static debug::Logger logger("content-loader");
ContentLoader::ContentLoader(ContentPack* pack) : pack(pack) {
ContentLoader::ContentLoader(ContentPack* pack, ContentBuilder& builder)
: pack(pack), builder(builder)
{
auto runtime = std::make_unique<ContentPackRuntime>(
*pack, scripting::create_pack_environment(*pack)
);
stats = &runtime->getStatsWriteable();
env = runtime->getEnvironment();
this->runtime = runtime.get();
builder.add(std::move(runtime));
}
static void detect_defs(
const fs::path& folder,
const std::string& prefix,
std::vector<std::string>& detected
) {
if (fs::is_directory(folder)) {
for (const auto& entry : fs::directory_iterator(folder)) {
const fs::path& file = entry.path();
std::string name = file.stem().string();
if (name[0] == '_') {
continue;
}
if (fs::is_regular_file(file) && file.extension() == ".json") {
detected.push_back(prefix.empty() ? name : prefix + ":" + name);
} else if (fs::is_directory(file)) {
detect_defs(file, name, detected);
}
}
}
}
bool ContentLoader::fixPackIndices(
@ -33,32 +65,9 @@ bool ContentLoader::fixPackIndices(
const std::string& contentSection
) {
std::vector<std::string> detected;
std::vector<std::string> indexed;
if (fs::is_directory(folder)) {
for (const auto& entry : fs::directory_iterator(folder)) {
const fs::path& file = entry.path();
if (fs::is_regular_file(file) && file.extension() == ".json") {
std::string name = file.stem().string();
if (name[0] == '_')
continue;
detected.push_back(name);
} else if (fs::is_directory(file)) {
std::string space = file.stem().string();
if (space[0] == '_')
continue;
for (const auto& entry : fs::directory_iterator(file)) {
const fs::path& file = entry.path();
if (fs::is_regular_file(file) && file.extension() == ".json") {
std::string name = file.stem().string();
if (name[0] == '_')
continue;
detected.push_back(space + ':' + name);
}
}
}
}
}
detect_defs(folder, "", detected);
std::vector<std::string> indexed;
bool modified = false;
if (!indicesRoot->has(contentSection)) {
indicesRoot->putList(contentSection);
@ -90,6 +99,7 @@ void ContentLoader::fixPackIndices() {
auto indexFile = pack->getContentFile();
auto blocksFolder = folder/ContentPack::BLOCKS_FOLDER;
auto itemsFolder = folder/ContentPack::ITEMS_FOLDER;
auto entitiesFolder = folder/ContentPack::ENTITIES_FOLDER;
dynamic::Map_sptr root;
if (fs::is_regular_file(indexFile)) {
@ -99,9 +109,9 @@ void ContentLoader::fixPackIndices() {
}
bool modified = false;
modified |= fixPackIndices(blocksFolder, root.get(), "blocks");
modified |= fixPackIndices(itemsFolder, root.get(), "items");
modified |= fixPackIndices(entitiesFolder, root.get(), "entities");
if (modified){
// rewrite modified json
@ -129,22 +139,19 @@ void ContentLoader::loadBlock(Block& def, const std::string& name, const fs::pat
}
// block model
std::string model = "block";
root->str("model", model);
if (model == "block") def.model = BlockModel::block;
else if (model == "aabb") def.model = BlockModel::aabb;
else if (model == "custom") {
def.model = BlockModel::custom;
if (root->has("model-primitives")) {
loadCustomBlockModel(def, root->map("model-primitives"));
} else {
logger.error() << name << ": no 'model-primitives' found";
std::string modelName;
root->str("model", modelName);
if (auto model = BlockModel_from(modelName)) {
if (*model == BlockModel::custom) {
if (root->has("model-primitives")) {
loadCustomBlockModel(def, root->map("model-primitives").get());
} else {
logger.error() << name << ": no 'model-primitives' found";
}
}
}
else if (model == "X") def.model = BlockModel::xsprite;
else if (model == "none") def.model = BlockModel::none;
else {
logger.error() << "unknown model " << model;
def.model = *model;
} else if (!modelName.empty()) {
logger.error() << "unknown model " << modelName;
def.model = BlockModel::none;
}
@ -173,7 +180,7 @@ void ContentLoader::loadBlock(Block& def, const std::string& name, const fs::pat
def.hitboxes[i].b = glm::vec3(box->num(3), box->num(4), box->num(5));
def.hitboxes[i].b += def.hitboxes[i].a;
}
} else if (auto boxarr = root->list("hitbox")){
} else if ((boxarr = root->list("hitbox"))){
AABB aabb;
aabb.a = glm::vec3(boxarr->num(0), boxarr->num(1), boxarr->num(2));
aabb.b = glm::vec3(boxarr->num(3), boxarr->num(4), boxarr->num(5));
@ -294,33 +301,93 @@ void ContentLoader::loadItem(ItemDef& def, const std::string& name, const fs::pa
root->num("stack-size", def.stackSize);
// item light emission [r, g, b] where r,g,b in range [0..15]
auto emissionarr = root->list("emission");
if (emissionarr) {
if (auto emissionarr = root->list("emission")) {
def.emission[0] = emissionarr->num(0);
def.emission[1] = emissionarr->num(1);
def.emission[2] = emissionarr->num(2);
}
}
void ContentLoader::loadEntity(EntityDef& def, const std::string& name, const fs::path& file) {
auto root = files::read_json(file);
if (auto componentsarr = root->list("components")) {
for (size_t i = 0; i < componentsarr->size(); i++) {
def.components.push_back(componentsarr->str(i));
}
}
if (auto boxarr = root->list("hitbox")) {
def.hitbox = glm::vec3(boxarr->num(0), boxarr->num(1), boxarr->num(2));
}
if (auto sensorsarr = root->list("sensors")) {
for (size_t i = 0; i < sensorsarr->size(); i++) {
if (auto sensorarr = sensorsarr->list(i)) {
auto sensorType = sensorarr->str(0);
if (sensorType == "aabb") {
def.boxSensors.push_back({i, {
{sensorarr->num(1), sensorarr->num(2), sensorarr->num(3)},
{sensorarr->num(4), sensorarr->num(5), sensorarr->num(6)}
}});
} else if (sensorType == "radius") {
def.radialSensors.push_back({i, sensorarr->num(1)});
} else {
logger.error() << name << ": sensor #" << i << " - unknown type "
<< util::quote(sensorType);
}
}
}
}
root->flag("save", def.save.enabled);
root->flag("save-skeleton-pose", def.save.skeleton.pose);
root->flag("save-skeleton-textures", def.save.skeleton.textures);
root->flag("save-body-velocity", def.save.body.velocity);
root->flag("save-body-settings", def.save.body.settings);
std::string bodyTypeName;
root->str("body-type", bodyTypeName);
if (auto bodyType = BodyType_from(bodyTypeName)) {
def.bodyType = *bodyType;
}
root->str("skeleton-name", def.skeletonName);
root->flag("blocking", def.blocking);
}
void ContentLoader::loadEntity(EntityDef& def, const std::string& full, const std::string& name) {
auto folder = pack->folder;
auto configFile = folder/fs::path("entities/"+name+".json");
if (fs::exists(configFile)) loadEntity(def, full, configFile);
}
void ContentLoader::loadBlock(Block& def, const std::string& full, const std::string& name) {
auto folder = pack->folder;
fs::path configFile = folder/fs::path("blocks/"+name+".json");
auto configFile = folder/fs::path("blocks/"+name+".json");
if (fs::exists(configFile)) loadBlock(def, full, configFile);
fs::path scriptfile = folder/fs::path("scripts/"+def.scriptName+".lua");
auto scriptfile = folder/fs::path("scripts/"+def.scriptName+".lua");
if (fs::is_regular_file(scriptfile)) {
scripting::load_block_script(env, full, scriptfile, def.rt.funcsset);
}
if (!def.hidden) {
auto& item = builder.items.create(full+BLOCK_ITEM_SUFFIX);
item.generated = true;
item.caption = def.caption;
item.iconType = item_icon_type::block;
item.icon = full;
item.placingBlock = full;
for (uint j = 0; j < 4; j++) {
item.emission[j] = def.emission[j];
}
stats->totalItems++;
}
}
void ContentLoader::loadItem(ItemDef& def, const std::string& full, const std::string& name) {
auto folder = pack->folder;
fs::path configFile = folder/fs::path("items/"+name+".json");
auto configFile = folder/fs::path("items/"+name+".json");
if (fs::exists(configFile)) loadItem(def, full, configFile);
fs::path scriptfile = folder/fs::path("scripts/"+def.scriptName+".lua");
auto scriptfile = folder/fs::path("scripts/"+def.scriptName+".lua");
if (fs::is_regular_file(scriptfile)) {
scripting::load_item_script(env, full, scriptfile, def.rt.funcsset);
}
@ -333,69 +400,57 @@ void ContentLoader::loadBlockMaterial(BlockMaterial& def, const fs::path& file)
root->str("break-sound", def.breakSound);
}
void ContentLoader::load(ContentBuilder& builder) {
void ContentLoader::load() {
logger.info() << "loading pack [" << pack->id << "]";
auto runtime = std::make_unique<ContentPackRuntime>(
*pack, scripting::create_pack_environment(*pack)
);
env = runtime->getEnvironment();
ContentPackStats& stats = runtime->getStatsWriteable();
builder.add(std::move(runtime));
fixPackIndices();
auto folder = pack->folder;
fs::path scriptFile = folder/fs::path("scripts/world.lua");
if (fs::is_regular_file(scriptFile)) {
scripting::load_world_script(env, pack->id, scriptFile);
scripting::load_world_script(env, pack->id, scriptFile, runtime->worldfuncsset);
}
if (!fs::is_regular_file(pack->getContentFile()))
return;
auto root = files::read_json(pack->getContentFile());
auto blocksarr = root->list("blocks");
if (blocksarr) {
for (uint i = 0; i < blocksarr->size(); i++) {
if (auto blocksarr = root->list("blocks")) {
for (size_t i = 0; i < blocksarr->size(); i++) {
std::string name = blocksarr->str(i);
auto colon = name.find(':');
std::string full = colon == std::string::npos ? pack->id + ":" + name : name;
if (colon != std::string::npos) name[colon] = '/';
auto& def = builder.createBlock(full);
if (colon != std::string::npos) {
def.scriptName = name.substr(0, colon) + '/' + def.scriptName;
}
auto& def = builder.blocks.create(full);
if (colon != std::string::npos) def.scriptName = name.substr(0, colon) + '/' + def.scriptName;
loadBlock(def, full, name);
stats.totalBlocks++;
if (!def.hidden) {
auto& item = builder.createItem(full+BLOCK_ITEM_SUFFIX);
item.generated = true;
item.caption = def.caption;
item.iconType = item_icon_type::block;
item.icon = full;
item.placingBlock = full;
for (uint j = 0; j < 4; j++) {
item.emission[j] = def.emission[j];
}
stats.totalItems++;
}
stats->totalBlocks++;
}
}
auto itemsarr = root->list("items");
if (itemsarr) {
for (uint i = 0; i < itemsarr->size(); i++) {
if (auto itemsarr = root->list("items")) {
for (size_t i = 0; i < itemsarr->size(); i++) {
std::string name = itemsarr->str(i);
auto colon = name.find(':');
std::string full = colon == std::string::npos ? pack->id + ":" + name : name;
if (colon != std::string::npos) name[colon] = '/';
auto& def = builder.createItem(full);
auto& def = builder.items.create(full);
if (colon != std::string::npos) def.scriptName = name.substr(0, colon) + '/' + def.scriptName;
loadItem(def, full, name);
stats.totalItems++;
stats->totalItems++;
}
}
if (auto entitiesarr = root->list("entities")) {
for (size_t i = 0; i < entitiesarr->size(); i++) {
std::string name = entitiesarr->str(i);
auto colon = name.find(':');
std::string full = colon == std::string::npos ? pack->id + ":" + name : name;
if (colon != std::string::npos) name[colon] = '/';
auto& def = builder.entities.create(full);
loadEntity(def, full, name);
stats->totalEntities++;
}
}
@ -407,4 +462,46 @@ void ContentLoader::load(ContentBuilder& builder) {
loadBlockMaterial(builder.createBlockMaterial(name), file);
}
}
fs::path skeletonsDir = folder / fs::u8path("skeletons");
if (fs::is_directory(skeletonsDir)) {
for (const auto& entry : fs::directory_iterator(skeletonsDir)) {
const fs::path& file = entry.path();
std::string name = pack->id+":"+file.stem().u8string();
std::string text = files::read_string(file);
builder.add(rigging::SkeletonConfig::parse(text, file.u8string(), name));
}
}
fs::path componentsDir = folder / fs::u8path("scripts/components");
if (fs::is_directory(componentsDir)) {
for (const auto& entry : fs::directory_iterator(componentsDir)) {
fs::path scriptfile = entry.path();
if (fs::is_regular_file(scriptfile)) {
auto name = pack->id+":"+scriptfile.stem().u8string();
scripting::load_entity_component(name, scriptfile);
}
}
}
fs::path resourcesFile = folder / fs::u8path("resources.json");
if (fs::exists(resourcesFile)) {
auto resRoot = files::read_json(resourcesFile);
for (const auto& [key, _] : resRoot->values) {
if (auto resType = ResourceType_from(key)) {
if (auto arr = resRoot->list(key)) {
loadResources(*resType, arr.get());
}
} else {
logger.warning() << "unknown resource type: " << key;
}
}
}
}
void ContentLoader::loadResources(ResourceType type, dynamic::List* list) {
for (size_t i = 0; i < list->size(); i++) {
builder.resourceIndices[static_cast<size_t>(type)].add(
pack->id+":"+list->str(i), nullptr);
}
}

View File

@ -1,33 +1,48 @@
#ifndef CONTENT_CONTENT_LOADER_HPP_
#define CONTENT_CONTENT_LOADER_HPP_
#include "../typedefs.hpp"
#include "content_fwd.hpp"
#include <string>
#include <memory>
#include <filesystem>
namespace fs = std::filesystem;
class Block;
struct BlockMaterial;
class ItemDef;
struct ItemDef;
struct EntityDef;
struct ContentPack;
class ContentBuilder;
class ContentPackRuntime;
struct ContentPackStats;
namespace dynamic {
class Map;
class List;
}
class ContentLoader {
const ContentPack* pack;
ContentPackRuntime* runtime;
scriptenv env;
ContentBuilder& builder;
ContentPackStats* stats;
void loadBlock(Block& def, const std::string& full, const std::string& name);
void loadCustomBlockModel(Block& def, dynamic::Map* primitives);
void loadItem(ItemDef& def, const std::string& full, const std::string& name);
void loadBlockMaterial(BlockMaterial& def, const fs::path& file);
void loadEntity(EntityDef& def, const std::string& full, const std::string& name);
static void loadCustomBlockModel(Block& def, dynamic::Map* primitives);
static void loadBlockMaterial(BlockMaterial& def, const fs::path& file);
static void loadBlock(Block& def, const std::string& name, const fs::path& file);
static void loadItem(ItemDef& def, const std::string& name, const fs::path& file);
static void loadEntity(EntityDef& def, const std::string& name, const fs::path& file);
void loadResources(ResourceType type, dynamic::List* list);
public:
ContentLoader(ContentPack* pack);
ContentLoader(ContentPack* pack, ContentBuilder& builder);
bool fixPackIndices(
const fs::path& folder,
@ -35,9 +50,7 @@ public:
const std::string& contentSection
);
void fixPackIndices();
void loadBlock(Block& def, const std::string& name, const fs::path& file);
void loadItem(ItemDef& def, const std::string& name, const fs::path& file);
void load(ContentBuilder& builder);
void load();
};
#endif // CONTENT_CONTENT_LOADER_HPP_

View File

@ -11,10 +11,6 @@
namespace fs = std::filesystem;
const std::string ContentPack::PACKAGE_FILENAME = "package.json";
const std::string ContentPack::CONTENT_FILENAME = "content.json";
const fs::path ContentPack::BLOCKS_FOLDER = "blocks";
const fs::path ContentPack::ITEMS_FOLDER = "items";
const std::vector<std::string> ContentPack::RESERVED_NAMES = {
"res", "abs", "local", "core", "user", "world", "none", "null"
};

View File

@ -46,10 +46,11 @@ struct ContentPack {
fs::path getContentFile() const;
static const std::string PACKAGE_FILENAME;
static const std::string CONTENT_FILENAME;
static const fs::path BLOCKS_FOLDER;
static const fs::path ITEMS_FOLDER;
static inline const std::string PACKAGE_FILENAME = "package.json";
static inline const std::string CONTENT_FILENAME = "content.json";
static inline const fs::path BLOCKS_FOLDER = "blocks";
static inline const fs::path ITEMS_FOLDER = "items";
static inline const fs::path ENTITIES_FOLDER = "entities";
static const std::vector<std::string> RESERVED_NAMES;
static bool is_pack(const fs::path& folder);
@ -72,17 +73,25 @@ struct ContentPack {
struct ContentPackStats {
size_t totalBlocks;
size_t totalItems;
size_t totalEntities;
inline bool hasSavingContent() const {
return totalBlocks + totalItems > 0;
return totalBlocks + totalItems + totalEntities > 0;
}
};
struct world_funcs_set {
bool onblockplaced : 1;
bool onblockbroken : 1;
};
class ContentPackRuntime {
ContentPack info;
ContentPackStats stats {};
scriptenv env;
public:
world_funcs_set worldfuncsset {};
ContentPackRuntime(
ContentPack info,
scriptenv env

View File

@ -0,0 +1,20 @@
#ifndef CONTENT_CONTENT_FWD_HPP_
#define CONTENT_CONTENT_FWD_HPP_
#include "../typedefs.hpp"
class Content;
class ContentPackRuntime;
enum class contenttype {
none, block, item, entity
};
enum class ResourceType : size_t {
CAMERA,
LAST=CAMERA
};
inline constexpr auto RESOURCE_TYPES_COUNT = static_cast<size_t>(ResourceType::LAST)+1;
#endif // CONTENT_CONTENT_FWD_HPP_

View File

@ -12,7 +12,7 @@
// All in-game definitions (blocks, items, etc..)
void corecontent::setup(EnginePaths* paths, ContentBuilder* builder) {
Block& block = builder->createBlock("core:air");
Block& block = builder->blocks.create("core:air");
block.replaceable = true;
block.drawGroup = 1;
block.lightPassing = true;
@ -22,7 +22,7 @@ void corecontent::setup(EnginePaths* paths, ContentBuilder* builder) {
block.model = BlockModel::none;
block.pickingItem = "core:empty";
ItemDef& item = builder->createItem("core:empty");
ItemDef& item = builder->items.create("core:empty");
item.iconType = item_icon_type::none;
auto bindsFile = paths->getResources()/fs::path("bindings.toml");

View File

@ -55,9 +55,9 @@ integer_t List::integer(size_t index) const {
}
}
Map* List::map(size_t index) const {
const Map_sptr& List::map(size_t index) const {
if (auto* val = std::get_if<Map_sptr>(&values[index])) {
return val->get();
return *val;
} else {
throw std::runtime_error("type error");
}
@ -192,20 +192,20 @@ void Map::num(const std::string& key, uint& dst) const {
dst = get(key, static_cast<integer_t>(dst));
}
Map* Map::map(const std::string& key) const {
Map_sptr Map::map(const std::string& key) const {
auto found = values.find(key);
if (found != values.end()) {
if (auto* val = std::get_if<Map_sptr>(&found->second)) {
return val->get();
return *val;
}
}
return nullptr;
}
List* Map::list(const std::string& key) const {
List_sptr Map::list(const std::string& key) const {
auto found = values.find(key);
if (found != values.end())
return std::get<List_sptr>(found->second).get();
return std::get<List_sptr>(found->second);
return nullptr;
}

View File

@ -1,42 +1,22 @@
#ifndef DATA_DYNAMIC_HPP_
#define DATA_DYNAMIC_HPP_
#include "../typedefs.hpp"
#include "dynamic_fwd.hpp"
#include <cmath>
#include <string>
#include <vector>
#include <memory>
#include <ostream>
#include <variant>
#include <stdexcept>
#include <unordered_map>
namespace dynamic {
class Map;
class List;
enum class Type {
none=0, map, list, string, number, boolean, integer
};
using Map_sptr = std::shared_ptr<Map>;
using List_sptr = std::shared_ptr<List>;
struct none {};
inline constexpr none NONE = {};
using Value = std::variant<
none,
Map_sptr,
List_sptr,
std::string,
number_t,
bool,
integer_t
>;
const std::string& type_name(const Value& value);
List_sptr create_list(std::initializer_list<Value> values={});
Map_sptr create_map(std::initializer_list<std::pair<const std::string, Value>> entries={});
@ -67,7 +47,7 @@ namespace dynamic {
std::string str(size_t index) const;
number_t num(size_t index) const;
integer_t integer(size_t index) const;
Map* map(size_t index) const;
const Map_sptr& map(size_t index) const;
List* list(size_t index) const;
bool flag(size_t index) const;
@ -134,8 +114,8 @@ namespace dynamic {
void num(const std::string& key, uint64_t& dst) const;
void num(const std::string& key, ubyte& dst) const;
void num(const std::string& key, double& dst) const;
Map* map(const std::string& key) const;
List* list(const std::string& key) const;
Map_sptr map(const std::string& key) const;
List_sptr list(const std::string& key) const;
void flag(const std::string& key, bool& dst) const;
Map& put(std::string key, std::unique_ptr<Map> value) {
@ -153,6 +133,9 @@ namespace dynamic {
Map& put(std::string key, int64_t value) {
return put(key, Value(static_cast<integer_t>(value)));
}
Map& put(std::string key, uint64_t value) {
return put(key, Value(static_cast<integer_t>(value)));
}
Map& put(std::string key, float value) {
return put(key, Value(static_cast<number_t>(value)));
}

32
src/data/dynamic_fwd.hpp Normal file
View File

@ -0,0 +1,32 @@
#ifndef DATA_DYNAMIC_FWD_HPP_
#define DATA_DYNAMIC_FWD_HPP_
#include "../typedefs.hpp"
#include <memory>
#include <string>
#include <variant>
namespace dynamic {
class Map;
class List;
using Map_sptr = std::shared_ptr<Map>;
using List_sptr = std::shared_ptr<List>;
struct none {};
inline constexpr none NONE = {};
using Value = std::variant<
none,
Map_sptr,
List_sptr,
std::string,
number_t,
bool,
integer_t
>;
}
#endif // DATA_DYNAMIC_FWD_HPP_

70
src/data/dynamic_util.hpp Normal file
View File

@ -0,0 +1,70 @@
#ifndef DATA_DYNAMIC_UTIL_HPP_
#define DATA_DYNAMIC_UTIL_HPP_
#include "dynamic.hpp"
#include <glm/glm.hpp>
namespace dynamic {
template<int n>
inline dynamic::List_sptr to_value(glm::vec<n, float> vec) {
auto list = dynamic::create_list();
for (size_t i = 0; i < n; i++) {
list->put(vec[i]);
}
return list;
}
template<int n, int m>
inline dynamic::List_sptr to_value(glm::mat<n, m, float> mat) {
auto list = dynamic::create_list();
for (size_t i = 0; i < n; i++) {
for (size_t j = 0; j < m; j++) {
list->put(mat[i][j]);
}
}
return list;
}
template<int n>
void get_vec(const dynamic::Map_sptr& root, const std::string& name, glm::vec<n, float>& vec) {
if (const auto& list = root->list(name)) {
for (size_t i = 0; i < n; i++) {
vec[i] = list->num(i);
}
}
}
template<int n>
void get_vec(const dynamic::List_sptr& root, size_t index, glm::vec<n, float>& vec) {
if (const auto& list = root->list(index)) {
for (size_t i = 0; i < n; i++) {
vec[i] = list->num(i);
}
}
}
template<int n, int m>
void get_mat(const dynamic::Map_sptr& root, const std::string& name, glm::mat<n, m, float>& mat) {
if (const auto& list = root->list(name)) {
for (size_t y = 0; y < n; y++) {
for (size_t x = 0; x < m; x++) {
mat[y][x] = list->num(y*m+x);
}
}
}
}
template<int n, int m>
void get_mat(const dynamic::List_sptr& root, size_t index, glm::mat<n, m, float>& mat) {
if (const auto& list = root->list(index)) {
for (size_t y = 0; y < n; y++) {
for (size_t x = 0; x < m; x++) {
mat[y][x] = list->num(y*m+x);
}
}
}
}
}
#endif // DATA_DYNAMIC_UTIL_HPP_

View File

@ -9,6 +9,7 @@
#include "coders/imageio.hpp"
#include "coders/json.hpp"
#include "coders/toml.hpp"
#include "content/Content.hpp"
#include "content/ContentBuilder.hpp"
#include "content/ContentLoader.hpp"
#include "core_defs.hpp"
@ -23,6 +24,7 @@
#include "graphics/core/ImageData.hpp"
#include "graphics/core/Shader.hpp"
#include "graphics/ui/GUI.hpp"
#include "objects/rigging.hpp"
#include "logic/EngineController.hpp"
#include "logic/CommandsInterpreter.hpp"
#include "logic/scripting/scripting.hpp"
@ -121,6 +123,7 @@ void Engine::loadControls() {
}
void Engine::onAssetsLoaded() {
assets->setup();
gui->onAssetsLoad(assets.get());
}
@ -246,6 +249,8 @@ void Engine::loadAssets() {
AssetsLoader loader(new_assets.get(), resPaths.get());
AssetsLoader::addDefaults(loader, content.get());
// no need
// correct log messages order is more useful
bool threading = false;
if (threading) {
auto task = loader.startTask([=](){});
@ -289,10 +294,7 @@ void Engine::loadContent() {
std::vector<PathsRoot> resRoots;
for (auto& pack : contentPacks) {
resRoots.push_back({pack.id, pack.folder});
ContentLoader loader(&pack);
loader.load(contentBuilder);
ContentLoader(&pack, contentBuilder).load();
load_configs(pack.folder);
}
load_configs(paths->getResources());

View File

@ -5,7 +5,7 @@
#include "typedefs.hpp"
#include "assets/Assets.hpp"
#include "content/Content.hpp"
#include "content/content_fwd.hpp"
#include "content/ContentPack.hpp"
#include "content/PacksManager.hpp"
#include "files/engine_paths.hpp"
@ -48,11 +48,11 @@ class Engine : public util::ObjectsKeeper {
SettingsHandler& settingsHandler;
EnginePaths* paths;
std::unique_ptr<Assets> assets = nullptr;
std::shared_ptr<Screen> screen = nullptr;
std::unique_ptr<Assets> assets;
std::shared_ptr<Screen> screen;
std::vector<ContentPack> contentPacks;
std::unique_ptr<Content> content = nullptr;
std::unique_ptr<ResPaths> resPaths = nullptr;
std::unique_ptr<Content> content;
std::unique_ptr<ResPaths> resPaths;
std::queue<runnable> postRunnables;
std::recursive_mutex postRunnablesMutex;
std::unique_ptr<EngineController> controller;

View File

@ -6,11 +6,13 @@
#include "../content/Content.hpp"
#include "../core_defs.hpp"
#include "../data/dynamic.hpp"
#include "../debug/Logger.hpp"
#include "../items/Inventory.hpp"
#include "../items/ItemDef.hpp"
#include "../lighting/Lightmap.hpp"
#include "../maths/voxmaths.hpp"
#include "../objects/Player.hpp"
#include "../objects/EntityDef.hpp"
#include "../physics/Hitbox.hpp"
#include "../typedefs.hpp"
#include "../settings.hpp"
@ -31,6 +33,8 @@
#define WORLD_FORMAT_MAGIC ".VOXWLD"
static debug::Logger logger("world-files");
WorldFiles::WorldFiles(const fs::path& directory) : directory(directory), regions(directory) {
}
@ -55,6 +59,10 @@ fs::path WorldFiles::getPlayerFile() const {
return directory/fs::path("player.json");
}
fs::path WorldFiles::getResourcesFile() const {
return directory/fs::path("resources.json");
}
fs::path WorldFiles::getWorldFile() const {
return directory/fs::path(WORLD_FILE);
}
@ -92,23 +100,19 @@ void WorldFiles::writePacks(const std::vector<ContentPack>& packs) {
files::write_string(packsFile, ss.str());
}
template<class T>
static void write_indices(const ContentUnitIndices<T>& indices, dynamic::List& list) {
size_t count = indices.count();
for (size_t i = 0; i < count; i++) {
list.put(indices.get(i)->name);
}
}
void WorldFiles::writeIndices(const ContentIndices* indices) {
dynamic::Map root;
uint count;
auto& blocks = root.putList("blocks");
count = indices->countBlockDefs();
for (uint i = 0; i < count; i++) {
const Block* def = indices->getBlockDef(i);
blocks.put(def->name);
}
auto& items = root.putList("items");
count = indices->countItemDefs();
for (uint i = 0; i < count; i++) {
const ItemDef* def = indices->getItemDef(i);
items.put(def->name);
}
write_indices(indices->blocks, root.putList("blocks"));
write_indices(indices->items, root.putList("items"));
write_indices(indices->entities, root.putList("entities"));
files::write_json(getIndicesFile(), &root);
}
@ -119,15 +123,52 @@ void WorldFiles::writeWorldInfo(const World* world) {
bool WorldFiles::readWorldInfo(World* world) {
fs::path file = getWorldFile();
if (!fs::is_regular_file(file)) {
std::cerr << "warning: world.json does not exists" << std::endl;
logger.warning() << "world.json does not exists";
return false;
}
auto root = files::read_json(file);
world->deserialize(root.get());
return true;
}
static void read_resources_data(
const Content* content,
const dynamic::List_sptr& list,
ResourceType type
) {
const auto& indices = content->getIndices(type);
for (size_t i = 0; i < list->size(); i++) {
auto map = list->map(i);
std::string name;
map->str("name", name);
size_t index = indices.indexOf(name);
if (index == ResourceIndices::MISSING) {
logger.warning() << "discard " << name;
} else {
indices.saveData(index, map->map("saved"));
}
}
}
bool WorldFiles::readResourcesData(const Content* content) {
fs::path file = getResourcesFile();
if (!fs::is_regular_file(file)) {
logger.warning() << "resources.json does not exists";
return false;
}
auto root = files::read_json(file);
for (const auto& [key, _] : root->values) {
if (auto resType = ResourceType_from(key)) {
if (auto arr = root->list(key)) {
read_resources_data(content, arr, *resType);
}
} else {
logger.warning() << "unknown resource type: " << key;
}
}
return true;
}
static void erase_pack_indices(dynamic::Map* root, const std::string& id) {
auto prefix = id+":";
auto blocks = root->list("blocks");

View File

@ -46,9 +46,11 @@ public:
~WorldFiles();
fs::path getPlayerFile() const;
fs::path getResourcesFile() const;
void createDirectories();
bool readWorldInfo(World* world);
bool readResourcesData(const Content* content);
/// @brief Write all unsaved data to world files
/// @param world target world

View File

@ -9,6 +9,7 @@
#include <cstring>
#include <utility>
#include <vector>
#define REGION_FORMAT_MAGIC ".VOXREG"
@ -36,17 +37,17 @@ std::unique_ptr<ubyte[]> regfile::read(int index, uint32_t& length) {
uint32_t offset;
file.seekg(table_offset + index * 4);
file.read((char*)(&offset), 4);
offset = dataio::read_int32_big((const ubyte*)(&offset), 0);
file.read(reinterpret_cast<char*>(&offset), 4);
offset = dataio::read_int32_big(reinterpret_cast<const ubyte*>(&offset), 0);
if (offset == 0){
return nullptr;
}
file.seekg(offset);
file.read((char*)(&offset), 4);
length = dataio::read_int32_big((const ubyte*)(&offset), 0);
file.read(reinterpret_cast<char*>(&offset), 4);
length = dataio::read_int32_big(reinterpret_cast<const ubyte*>(&offset), 0);
auto data = std::make_unique<ubyte[]>(length);
file.read((char*)data.get(), length);
file.read(reinterpret_cast<char*>(data.get()), length);
return data;
}
@ -88,12 +89,13 @@ uint WorldRegion::getChunkDataSize(uint x, uint z) {
}
WorldRegions::WorldRegions(const fs::path& directory) : directory(directory) {
for (uint i = 0; i < sizeof(layers)/sizeof(RegionsLayer); i++) {
for (size_t i = 0; i < sizeof(layers)/sizeof(RegionsLayer); i++) {
layers[i].layer = i;
}
layers[REGION_LAYER_VOXELS].folder = directory/fs::path("regions");
layers[REGION_LAYER_LIGHTS].folder = directory/fs::path("lights");
layers[REGION_LAYER_INVENTORIES].folder = directory/fs::path("inventories");
layers[REGION_LAYER_ENTITIES].folder = directory/fs::path("entities");
}
WorldRegions::~WorldRegions() {
@ -123,7 +125,7 @@ WorldRegion* WorldRegions::getOrCreateRegion(int x, int z, int layer) {
std::unique_ptr<ubyte[]> WorldRegions::compress(const ubyte* src, size_t srclen, size_t& len) {
auto buffer = bufferPool.get();
ubyte* bytes = buffer.get();
auto bytes = buffer.get();
len = extrle::encode(src, srclen, bytes);
auto data = std::make_unique<ubyte[]>(len);
@ -150,7 +152,7 @@ inline void calc_reg_coords(
std::unique_ptr<ubyte[]> WorldRegions::readChunkData(
int x, int z, uint32_t& length, regfile* rfile
){
) {
int regionX, regionZ, localX, localZ;
calc_reg_coords(x, z, regionX, regionZ, localX, localZ);
int chunkIndex = localZ * REGION_SIZE + localX;
@ -171,10 +173,7 @@ void WorldRegions::fetchChunks(WorldRegion* region, int x, int z, regfile* file)
}
}
ubyte* WorldRegions::getData(
int x, int z, int layer,
uint32_t& size
) {
ubyte* WorldRegions::getData(int x, int z, int layer, uint32_t& size) {
if (generatorTestMode) {
return nullptr;
}
@ -301,7 +300,7 @@ void WorldRegions::writeRegion(int x, int z, int layer, WorldRegion* entry){
offset += 4 + compressedSize;
file.write(intbuf, 4);
file.write((const char*)chunk, compressedSize);
file.write(reinterpret_cast<const char*>(chunk), compressedSize);
}
}
for (size_t i = 0; i < REGION_CHUNKS_COUNT; i++) {
@ -313,8 +312,9 @@ void WorldRegions::writeRegion(int x, int z, int layer, WorldRegion* entry){
void WorldRegions::writeRegions(int layer) {
for (auto& it : layers[layer].regions){
WorldRegion* region = it.second.get();
if (region->getChunks() == nullptr || !region->isUnsaved())
if (region->getChunks() == nullptr || !region->isUnsaved()) {
continue;
}
glm::ivec2 key = it.first;
writeRegion(key[0], key[1], layer, region);
}
@ -354,13 +354,13 @@ static std::unique_ptr<ubyte[]> write_inventories(Chunk* chunk, uint& datasize)
}
/// @brief Store chunk data (voxels and lights) in region (existing or new)
void WorldRegions::put(Chunk* chunk){
void WorldRegions::put(Chunk* chunk, std::vector<ubyte> entitiesData){
assert(chunk != nullptr);
if (!chunk->flags.lighted) {
return;
}
bool lightsUnsaved = !chunk->flags.loadedLights && doWriteLights;
if (!chunk->flags.unsaved && !lightsUnsaved) {
if (!chunk->flags.unsaved && !lightsUnsaved && !chunk->flags.entities) {
return;
}
@ -376,12 +376,21 @@ void WorldRegions::put(Chunk* chunk){
chunk->lightmap.encode(), LIGHTMAP_DATA_LEN, true);
}
// Writing block inventories
if (!chunk->inventories.empty()){
if (!chunk->inventories.empty()) {
uint datasize;
auto data = write_inventories(chunk, datasize);
put(chunk->x, chunk->z, REGION_LAYER_INVENTORIES,
std::move(data), datasize, false);
}
// Writing entities
if (!entitiesData.empty()) {
auto data = std::make_unique<ubyte[]>(entitiesData.size());
for (size_t i = 0; i < entitiesData.size(); i++) {
data[i] = entitiesData[i];
}
put(chunk->x, chunk->z, REGION_LAYER_ENTITIES,
std::move(data), entitiesData.size(), false);
}
}
std::unique_ptr<ubyte[]> WorldRegions::getChunk(int x, int z){
@ -398,8 +407,9 @@ std::unique_ptr<ubyte[]> WorldRegions::getChunk(int x, int z){
std::unique_ptr<light_t[]> WorldRegions::getLights(int x, int z) {
uint32_t size;
auto* bytes = getData(x, z, REGION_LAYER_LIGHTS, size);
if (bytes == nullptr)
if (bytes == nullptr) {
return nullptr;
}
auto data = decompress(bytes, size, LIGHTMAP_DATA_LEN);
return Lightmap::decode(data.get());
}
@ -412,7 +422,7 @@ chunk_inventories_map WorldRegions::fetchInventories(int x, int z) {
return meta;
}
ByteReader reader(data, bytesSize);
int count = reader.getInt32();
auto count = reader.getInt32();
for (int i = 0; i < count; i++) {
uint index = reader.getInt32();
uint size = reader.getInt32();
@ -425,6 +435,19 @@ chunk_inventories_map WorldRegions::fetchInventories(int x, int z) {
return meta;
}
dynamic::Map_sptr WorldRegions::fetchEntities(int x, int z) {
uint32_t bytesSize;
const ubyte* data = getData(x, z, REGION_LAYER_ENTITIES, bytesSize);
if (data == nullptr) {
return nullptr;
}
auto map = json::from_binary(data, bytesSize);
if (map->size() == 0) {
return nullptr;
}
return map;
}
void WorldRegions::processRegionVoxels(int x, int z, const regionproc& func) {
if (getRegion(x, z, REGION_LAYER_VOXELS)) {
throw std::runtime_error("not implemented for in-memory regions");
@ -439,8 +462,9 @@ void WorldRegions::processRegionVoxels(int x, int z, const regionproc& func) {
int gz = cz + z * REGION_SIZE;
uint32_t length;
auto data = readChunkData(gx, gz, length, regfile.get());
if (data == nullptr)
if (data == nullptr) {
continue;
}
data = decompress(data.get(), length, CHUNK_DATA_LEN);
if (func(data.get())) {
put(gx, gz, REGION_LAYER_VOXELS, std::move(data), CHUNK_DATA_LEN, true);

View File

@ -5,6 +5,7 @@
#include "../typedefs.hpp"
#include "../util/BufferPool.hpp"
#include "../voxels/Chunk.hpp"
#include "../data/dynamic_fwd.hpp"
#include <mutex>
#include <memory>
@ -24,6 +25,7 @@ inline constexpr uint REGION_HEADER_SIZE = 10;
inline constexpr uint REGION_LAYER_VOXELS = 0;
inline constexpr uint REGION_LAYER_LIGHTS = 1;
inline constexpr uint REGION_LAYER_INVENTORIES = 2;
inline constexpr uint REGION_LAYER_ENTITIES = 3;
inline constexpr uint REGION_SIZE_BIT = 5;
inline constexpr uint REGION_SIZE = (1 << (REGION_SIZE_BIT));
@ -119,7 +121,7 @@ class WorldRegions {
std::unordered_map<glm::ivec3, std::unique_ptr<regfile>> openRegFiles;
std::mutex regFilesMutex;
std::condition_variable regFilesCv;
RegionsLayer layers[3] {};
RegionsLayer layers[4] {};
util::BufferPool<ubyte> bufferPool {
std::max(CHUNK_DATA_LEN, LIGHTMAP_DATA_LEN) * 2
};
@ -170,7 +172,7 @@ public:
~WorldRegions();
/// @brief Put all chunk data to regions
void put(Chunk* chunk);
void put(Chunk* chunk, std::vector<ubyte> entitiesData);
/// @brief Store data in specified region
/// @param x chunk.x
@ -184,6 +186,7 @@ public:
std::unique_ptr<ubyte[]> getChunk(int x, int z);
std::unique_ptr<light_t[]> getLights(int x, int z);
chunk_inventories_map fetchInventories(int x, int z);
dynamic::Map_sptr fetchEntities(int x, int z);
void processRegionVoxels(int x, int z, const regionproc& func);

View File

@ -56,6 +56,7 @@ SettingsHandler::SettingsHandler(EngineSettings& settings) {
builder.add("fov", &settings.camera.fov);
builder.add("fov-effects", &settings.camera.fovEffects);
builder.add("shaking", &settings.camera.shaking);
builder.add("inertia", &settings.camera.inertia);
builder.section("chunks");
builder.add("load-distance", &settings.chunks.loadDistance);

View File

@ -14,11 +14,11 @@
ContentGfxCache::ContentGfxCache(const Content* content, Assets* assets) : content(content) {
auto indices = content->getIndices();
sideregions = std::make_unique<UVRegion[]>(indices->countBlockDefs() * 6);
sideregions = std::make_unique<UVRegion[]>(indices->blocks.count() * 6);
auto atlas = assets->get<Atlas>("blocks");
for (uint i = 0; i < indices->countBlockDefs(); i++) {
Block* def = indices->getBlockDef(i);
for (uint i = 0; i < indices->blocks.count(); i++) {
Block* def = indices->blocks.get(i);
for (uint side = 0; side < 6; side++) {
const std::string& tex = def->textureFaces[side];
if (atlas->has(tex)) {

View File

@ -22,7 +22,7 @@ LevelFrontend::LevelFrontend(LevelController* controller, Assets* assets)
BlocksPreview::build(contentCache.get(), assets, level->content),
"block-previews"
);
controller->getPlayerController()->listenBlockInteraction(
controller->getBlocksController()->listenBlockInteraction(
[=](Player*, glm::ivec3 pos, const Block* def, BlockInteraction type) {
auto material = level->content->findBlockMaterial(def->material);
if (material == nullptr) {

View File

@ -2,6 +2,7 @@
#include "../delegates.hpp"
#include "../engine.hpp"
#include "../settings.hpp"
#include "../content/Content.hpp"
#include "../graphics/core/Mesh.hpp"
#include "../graphics/ui/elements/CheckBox.hpp"
#include "../graphics/ui/elements/TextBox.hpp"
@ -10,6 +11,8 @@
#include "../graphics/render/WorldRenderer.hpp"
#include "../logic/scripting/scripting.hpp"
#include "../objects/Player.hpp"
#include "../objects/Entities.hpp"
#include "../objects/EntityDef.hpp"
#include "../physics/Hitbox.hpp"
#include "../util/stringutil.hpp"
#include "../voxels/Block.hpp"
@ -32,6 +35,7 @@ static std::shared_ptr<Label> create_label(wstringsupplier supplier) {
return label;
}
// TODO: move to xml
std::shared_ptr<UINode> create_debug_panel(
Engine* engine,
Level* level,
@ -57,24 +61,24 @@ std::shared_ptr<UINode> create_debug_panel(
fpsMin = fps;
fpsMax = fps;
});
panel->add(create_label([](){ return L"fps: "+fpsString;}));
panel->add(create_label([]() { return L"fps: "+fpsString;}));
panel->add(create_label([](){
panel->add(create_label([]() {
return L"meshes: " + std::to_wstring(Mesh::meshesCount);
}));
panel->add(create_label([](){
panel->add(create_label([]() {
int drawCalls = Mesh::drawCalls;
Mesh::drawCalls = 0;
return L"draw-calls: " + std::to_wstring(drawCalls);
}));
panel->add(create_label([](){
panel->add(create_label([]() {
return L"speakers: " + std::to_wstring(audio::count_speakers())+
L" streams: " + std::to_wstring(audio::count_streams());
}));
panel->add(create_label([](){
panel->add(create_label([]() {
return L"lua-stack: " + std::to_wstring(scripting::get_values_on_stack());
}));
panel->add(create_label([=](){
panel->add(create_label([=]() {
auto& settings = engine->getSettings();
bool culling = settings.graphics.frustumCulling.get();
return L"frustum-culling: "+std::wstring(culling ? L"on" : L"off");
@ -83,7 +87,11 @@ std::shared_ptr<UINode> create_debug_panel(
return L"chunks: "+std::to_wstring(level->chunks->chunksCount)+
L" visible: "+std::to_wstring(level->chunks->visible);
}));
panel->add(create_label([=](){
panel->add(create_label([=]() {
return L"entities: "+std::to_wstring(level->entities->size())+L" next: "+
std::to_wstring(level->entities->peekNextID());
}));
panel->add(create_label([=]() {
const auto& vox = player->selection.vox;
std::wstringstream stream;
stream << "r:" << vox.state.rotation << " s:"
@ -96,9 +104,20 @@ std::shared_ptr<UINode> create_debug_panel(
L" "+stream.str();
}
}));
panel->add(create_label([=]() {
auto eid = player->getSelectedEntity();
if (eid == ENTITY_NONE) {
return std::wstring {L"entity: -"};
} else if (auto entity = level->entities->get(eid)) {
return L"entity: "+util::str2wstr_utf8(entity->getDef().name)+
L" uid: "+std::to_wstring(entity->getUID());
} else {
return std::wstring {L"entity: error (invalid UID)"};
}
}));
panel->add(create_label([=](){
auto* indices = level->content->getIndices();
if (auto def = indices->getBlockDef(player->selection.vox.id)) {
if (auto def = indices->blocks.get(player->selection.vox.id)) {
return L"name: " + util::str2wstr_utf8(def->name);
} else {
return std::wstring {L"name: void"};
@ -123,20 +142,18 @@ std::shared_ptr<UINode> create_debug_panel(
auto box = std::make_shared<TextBox>(L"");
auto boxRef = box.get();
box->setTextSupplier([=]() {
Hitbox* hitbox = player->hitbox.get();
return util::to_wstring(hitbox->position[ax], 2);
return util::to_wstring(player->getPosition()[ax], 2);
});
box->setTextConsumer([=](const std::wstring& text) {
try {
glm::vec3 position = player->hitbox->position;
glm::vec3 position = player->getPosition();
position[ax] = std::stoi(text);
player->teleport(position);
} catch (std::exception& _){
}
});
box->setOnEditStart([=](){
Hitbox* hitbox = player->hitbox.get();
boxRef->setText(std::to_wstring(int(hitbox->position[ax])));
boxRef->setText(std::to_wstring(static_cast<int>(player->getPosition()[ax])));
});
box->setSize(glm::vec2(230, 27));
@ -176,6 +193,18 @@ std::shared_ptr<UINode> create_debug_panel(
});
panel->add(checkbox);
}
{
auto checkbox = std::make_shared<FullCheckBox>(
L"Show Hitboxes", glm::vec2(400, 24)
);
checkbox->setSupplier([=]() {
return WorldRenderer::showEntitiesDebug;
});
checkbox->setConsumer([=](bool checked) {
WorldRenderer::showEntitiesDebug = checked;
});
panel->add(checkbox);
}
panel->refresh();
return panel;
}

View File

@ -102,9 +102,9 @@ std::shared_ptr<InventoryView> Hud::createContentAccess() {
auto indices = content->getIndices();
auto inventory = player->getInventory();
int itemsCount = indices->countItemDefs();
size_t itemsCount = indices->items.count();
auto accessInventory = std::make_shared<Inventory>(0, itemsCount);
for (int id = 1; id < itemsCount; id++) {
for (size_t id = 1; id < itemsCount; id++) {
accessInventory->getSlot(id-1).set(ItemStack(id, 1));
}

View File

@ -8,6 +8,7 @@
#include "../../debug/Logger.hpp"
#include "../../engine.hpp"
#include "../../files/files.hpp"
#include "../../content/Content.hpp"
#include "../../graphics/core/DrawContext.hpp"
#include "../../graphics/core/ImageData.hpp"
#include "../../graphics/core/PostProcessing.hpp"
@ -98,7 +99,7 @@ void LevelScreen::saveWorldPreview() {
Viewport viewport(previewSize * 1.5, previewSize);
DrawContext ctx(&pctx, viewport, batch.get());
worldRenderer->draw(ctx, &camera, false, postProcessing.get());
worldRenderer->draw(ctx, &camera, false, true, postProcessing.get());
auto image = postProcessing->toImage();
image->flipY();
imageio::write(paths->resolve("world:preview.png").u8string(), image.get());
@ -136,9 +137,13 @@ void LevelScreen::update(float delta) {
bool paused = hud->isPause();
audio::get_channel("regular")->setPaused(paused);
audio::get_channel("ambient")->setPaused(paused);
glm::vec3 velocity {};
if (auto hitbox = player->getHitbox()) {
velocity = hitbox->velocity;
}
audio::set_listener(
camera->position-camera->dir,
player->hitbox->velocity,
velocity,
camera->dir,
glm::vec3(0, 1, 0)
);
@ -157,7 +162,7 @@ void LevelScreen::draw(float) {
Viewport viewport(Window::width, Window::height);
DrawContext ctx(nullptr, viewport, batch.get());
worldRenderer->draw(ctx, camera.get(), hudVisible, postProcessing.get());
worldRenderer->draw(ctx, camera.get(), hudVisible, hud->isPause(), postProcessing.get());
if (hudVisible) {
hud->draw(ctx);

View File

@ -123,7 +123,7 @@ std::unique_ptr<Atlas> BlocksPreview::build(
const Content* content
) {
auto indices = content->getIndices();
size_t count = indices->countBlockDefs();
size_t count = indices->blocks.count();
size_t iconSize = ITEM_ICON_SIZE;
auto shader = assets->get<Shader>("ui3d");
@ -153,7 +153,7 @@ std::unique_ptr<Atlas> BlocksPreview::build(
fbo.bind();
for (size_t i = 0; i < count; i++) {
auto def = indices->getBlockDef(i);
auto def = indices->blocks.get(i);
atlas->getTexture()->bind();
builder.add(def->name, draw(cache, shader, &fbo, &batch, def, iconSize));
}

View File

@ -40,7 +40,7 @@ BlocksRenderer::BlocksRenderer(
CHUNK_W + voxelBufferPadding*2,
CHUNK_H,
CHUNK_D + voxelBufferPadding*2);
blockDefsCache = content->getIndices()->getBlockDefs();
blockDefsCache = content->getIndices()->blocks.getDefs();
}
BlocksRenderer::~BlocksRenderer() {

View File

@ -2,6 +2,7 @@
#include "../core/Mesh.hpp"
#include "../core/Model.hpp"
#include "../core/Atlas.hpp"
#include "../core/Texture.hpp"
#include "../../assets/Assets.hpp"
#include "../../window/Window.hpp"
@ -53,16 +54,18 @@ ModelBatch::ModelBatch(size_t capacity, Assets* assets, Chunks* chunks)
ModelBatch::~ModelBatch() {
}
void ModelBatch::draw(const model::Mesh& mesh, const glm::mat4& matrix, const glm::mat3& rotation) {
void ModelBatch::draw(const model::Mesh& mesh, const glm::mat4& matrix,
const glm::mat3& rotation,
const texture_names_map* varTextures) {
glm::vec3 gpos = matrix * glm::vec4(glm::vec3(), 1.0f);
light_t light = chunks->getLight(gpos.x, gpos.y, gpos.z);
light_t light = chunks->getLight(floor(gpos.x), floor(gpos.y), floor(gpos.z));
glm::vec4 lights (
Lightmap::extract(light, 0) / 15.0f,
Lightmap::extract(light, 1) / 15.0f,
Lightmap::extract(light, 2) / 15.0f,
Lightmap::extract(light, 3) / 15.0f
);
setTexture(assets->get<Texture>(mesh.texture));
setTexture(mesh.texture, varTextures);
size_t vcount = mesh.vertices.size();
const auto& vertexData = mesh.vertices.data();
for (size_t i = 0; i < vcount / 3; i++) {
@ -70,7 +73,7 @@ void ModelBatch::draw(const model::Mesh& mesh, const glm::mat4& matrix, const gl
flush();
}
for (size_t j = 0; j < 3; j++) {
const auto& vert = vertexData[i * 3 + j];
const auto vert = vertexData[i * 3 + j];
auto norm = rotation * vert.normal;
float d = glm::dot(norm, SUN_VECTOR);
d = 0.8f + d * 0.2f;
@ -81,9 +84,10 @@ void ModelBatch::draw(const model::Mesh& mesh, const glm::mat4& matrix, const gl
}
}
void ModelBatch::draw(const model::Model* model) {
void ModelBatch::draw(const model::Model* model,
const texture_names_map* varTextures) {
for (const auto& mesh : model->meshes) {
entries.push_back({combined, rotation, &mesh});
entries.push_back({combined, rotation, &mesh, varTextures});
}
}
@ -94,7 +98,7 @@ void ModelBatch::render() {
}
);
for (auto& entry : entries) {
draw(*entry.mesh, entry.matrix, entry.rotation);
draw(*entry.mesh, entry.matrix, entry.rotation, entry.varTextures);
}
flush();
entries.clear();
@ -114,6 +118,34 @@ void ModelBatch::box(glm::vec3 pos, glm::vec3 size, glm::vec4 lights) {
plane(pos-X*size, Z*size, Y*size, -X, lights);
}
void ModelBatch::setTexture(const std::string& name,
const texture_names_map* varTextures) {
if (name.at(0) == '$') {
const auto& found = varTextures->find(name);
if (found == varTextures->end()) {
return setTexture(nullptr);
} else {
return setTexture(found->second, varTextures);
}
}
size_t sep = name.find(':');
if (sep == std::string::npos) {
setTexture(assets->get<Texture>(name));
} else {
auto atlas = assets->get<Atlas>(name.substr(0, sep));
if (atlas == nullptr) {
setTexture(nullptr);
} else {
setTexture(atlas->getTexture());
if (auto reg = atlas->getIf(name.substr(sep+1))) {
region = *reg;
} else {
setTexture("blocks:notfound", varTextures);
}
}
}
}
void ModelBatch::setTexture(Texture* texture) {
if (texture == nullptr) {
texture = blank.get();
@ -122,6 +154,7 @@ void ModelBatch::setTexture(Texture* texture) {
flush();
}
this->texture = texture;
region = UVRegion {0.0f, 0.0f, 1.0f, 1.0f};
}
void ModelBatch::flush() {

View File

@ -1,9 +1,13 @@
#ifndef GRAPHICS_RENDER_MODEL_BATCH_HPP_
#define GRAPHICS_RENDER_MODEL_BATCH_HPP_
#include "../../maths/UVRegion.hpp"
#include <memory>
#include <vector>
#include <string>
#include <glm/glm.hpp>
#include <unordered_map>
class Mesh;
class Texture;
@ -15,6 +19,8 @@ namespace model {
struct Model;
}
using texture_names_map = std::unordered_map<std::string, std::string>;
class ModelBatch {
std::unique_ptr<float[]> const buffer;
size_t const capacity;
@ -30,6 +36,7 @@ class ModelBatch {
Assets* assets;
Chunks* chunks;
Texture* texture = nullptr;
UVRegion region {0.0f, 0.0f, 1.0f, 1.0f};
static inline glm::vec3 SUN_VECTOR {0.411934f, 0.863868f, -0.279161f};
@ -40,8 +47,8 @@ class ModelBatch {
buffer[index++] = pos.x;
buffer[index++] = pos.y;
buffer[index++] = pos.z;
buffer[index++] = uv.x;
buffer[index++] = uv.y;
buffer[index++] = uv.x * region.getWidth() + region.u1;
buffer[index++] = uv.y * region.getHeight() + region.v1;
union {
float floating;
@ -72,8 +79,11 @@ class ModelBatch {
vertex(pos-right+up, {0,1}, color);
}
void draw(const model::Mesh& mesh, const glm::mat4& matrix, const glm::mat3& rotation);
void draw(const model::Mesh& mesh, const glm::mat4& matrix,
const glm::mat3& rotation, const texture_names_map* varTextures);
void box(glm::vec3 pos, glm::vec3 size, glm::vec4 lights);
void setTexture(const std::string& name,
const texture_names_map* varTextures);
void setTexture(Texture* texture);
void flush();
@ -81,6 +91,7 @@ class ModelBatch {
glm::mat4 matrix;
glm::mat3 rotation;
const model::Mesh* mesh;
const texture_names_map* varTextures;
};
std::vector<DrawEntry> entries;
public:
@ -93,7 +104,8 @@ public:
void pushMatrix(glm::mat4 matrix);
void popMatrix();
void draw(const model::Model* model);
void draw(const model::Model* model,
const texture_names_map* varTextures);
void render();
};

Some files were not shown because too many files have changed in this diff Show More