diff --git a/.gitignore b/.gitignore
index d7b5168d..8039c093 100644
--- a/.gitignore
+++ b/.gitignore
@@ -45,3 +45,4 @@ appimage-build/
/res/content/*
!/res/content/base
+*.mtl
diff --git a/doc/en/block-properties.md b/doc/en/block-properties.md
index 602df3eb..19cd01d1 100644
--- a/doc/en/block-properties.md
+++ b/doc/en/block-properties.md
@@ -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*
diff --git a/doc/en/entity-properties.md b/doc/en/entity-properties.md
new file mode 100644
index 00000000..7aaad78a
--- /dev/null
+++ b/doc/en/entity-properties.md
@@ -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
(type, damping, crouching) | false |
diff --git a/doc/en/item-properties.md b/doc/en/item-properties.md
index bab9769c..0ec4bf73 100644
--- a/doc/en/item-properties.md
+++ b/doc/en/item-properties.md
@@ -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.
diff --git a/doc/en/main-page.md b/doc/en/main-page.md
index 919f6a9b..193b4279 100644
--- a/doc/en/main-page.md
+++ b/doc/en/main-page.md
@@ -12,3 +12,5 @@
- [Scripting](scripting.md)
- [Console](console.md)
- [Block models](block-models.md)
+- [Rigging](rigging.md)
+- [Resources (resources.json)](resources.md)
diff --git a/doc/en/resources.md b/doc/en/resources.md
new file mode 100644
index 00000000..2643f6d7
--- /dev/null
+++ b/doc/en/resources.md
@@ -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*.
diff --git a/doc/en/rigging.md b/doc/en/rigging.md
new file mode 100644
index 00000000..9b7a033c
--- /dev/null
+++ b/doc/en/rigging.md
@@ -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.
diff --git a/doc/en/scripting.md b/doc/en/scripting.md
index ce062577..00d09b89 100644
--- a/doc/en/scripting.md
+++ b/doc/en/scripting.md
@@ -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
diff --git a/doc/en/scripting/builtins/libblock.md b/doc/en/scripting/builtins/libblock.md
new file mode 100644
index 00000000..1b095afc
--- /dev/null
+++ b/doc/en/scripting/builtins/libblock.md
@@ -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.
diff --git a/doc/en/scripting/builtins/libentities.md b/doc/en/scripting/builtins/libentities.md
new file mode 100644
index 00000000..2dfe2736
--- /dev/null
+++ b/doc/en/scripting/builtins/libentities.md
@@ -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) -> 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
+
+-- 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
+```
+
+```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.
diff --git a/doc/en/scripting/builtins/libvecn.md b/doc/en/scripting/builtins/libvecn.md
new file mode 100644
index 00000000..0a42eec8
--- /dev/null
+++ b/doc/en/scripting/builtins/libvecn.md
@@ -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
diff --git a/doc/en/scripting/ecs.md b/doc/en/scripting/ecs.md
new file mode 100644
index 00000000..b509ba1b
--- /dev/null
+++ b/doc/en/scripting/ecs.md
@@ -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.
diff --git a/doc/en/scripting/user-input.md b/doc/en/scripting/user-input.md
index b2c6dd6a..422b4279 100644
--- a/doc/en/scripting/user-input.md
+++ b/doc/en/scripting/user-input.md
@@ -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") {
+ ...
+}
+```
diff --git a/doc/ru/block-properties.md b/doc/ru/block-properties.md
index 1c9c92ed..495fd8ca 100644
--- a/doc/ru/block-properties.md
+++ b/doc/ru/block-properties.md
@@ -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*
diff --git a/doc/ru/entity-properties.md b/doc/ru/entity-properties.md
new file mode 100644
index 00000000..9c68c3ab
--- /dev/null
+++ b/doc/ru/entity-properties.md
@@ -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 | измененные настройки тела
(type, damping, crouching) | false |
diff --git a/doc/ru/item-properties.md b/doc/ru/item-properties.md
index 180aef4a..50f82f2c 100644
--- a/doc/ru/item-properties.md
+++ b/doc/ru/item-properties.md
@@ -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.
diff --git a/doc/ru/main-page.md b/doc/ru/main-page.md
index e33b25ae..c0f52e61 100644
--- a/doc/ru/main-page.md
+++ b/doc/ru/main-page.md
@@ -12,3 +12,5 @@
- [Скриптинг](scripting.md)
- [Консоль](console.md)
- [Модели блоков](block-models.md)
+- [Риггинг](rigging.md)
+- [Ресурсы (resources.json)](resources.md)
diff --git a/doc/ru/resources.md b/doc/ru/resources.md
new file mode 100644
index 00000000..e8ea1b93
--- /dev/null
+++ b/doc/ru/resources.md
@@ -0,0 +1,22 @@
+# Ресурсы
+
+К ресурсам относятся:
+- камеры
+- слоты для эффектов
+- фреймбуферы
+- и подобные ограниченные по количеству ресурсы
+
+На данный момент реализованы только **камеры**.
+
+Запрашиваемые паком ресурсы указываются через файл resources.json в формате:
+```json
+{
+ "тип-ресура": [
+ "имена",
+ "ресурсов"
+ ]
+}
+```
+
+После загрузки пака имена ресурсов получат префикс пака. Например камера
+*cinematic* в паке base получает имя *base:cinematic*.
diff --git a/doc/ru/rigging.md b/doc/ru/rigging.md
new file mode 100644
index 00000000..b9f834b0
--- /dev/null
+++ b/doc/ru/rigging.md
@@ -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 не обязательно.
diff --git a/doc/ru/scripting.md b/doc/ru/scripting.md
index 6d2a0d53..fdc55f22 100644
--- a/doc/ru/scripting.md
+++ b/doc/ru/scripting.md
@@ -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
diff --git a/doc/ru/scripting/builtins/libblock.md b/doc/ru/scripting/builtins/libblock.md
new file mode 100644
index 00000000..8e3926d8
--- /dev/null
+++ b/doc/ru/scripting/builtins/libblock.md
@@ -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) -> таблица строк
+```
diff --git a/doc/ru/scripting/builtins/libentities.md b/doc/ru/scripting/builtins/libentities.md
new file mode 100644
index 00000000..fa805797
--- /dev/null
+++ b/doc/ru/scripting/builtins/libentities.md
@@ -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) -> table
+
+-- Возвращает список UID сущностей, попадающих в прямоугольную область
+-- pos - минимальный угол области
+-- size - размер области
+entities.get_all_in_box(pos: vec3, size: vec3) -> array
+
+-- Возвращает список UID сущностей, попадающих в радиус
+-- center - центр области
+-- radius - радиус области
+entities.get_all_in_radius(center: vec3, radius: number) -> array
+```
+
+```lua
+entities.raycast(start: vec3, dir: vec3, max_distance: number,
+ ignore: int, [optional] destination: table) -> table или nil
+```
+
+Функция является расширенным вариантом [block.raycast](libblock.md#raycast). Возвращает таблицу с результатами если луч касается блока, либо сущности.
+
+Соответственно это повлияет на наличие полей *entity* и *block*.
diff --git a/doc/ru/scripting/builtins/libvecn.md b/doc/ru/scripting/builtins/libvecn.md
index bc847bf9..104c389a 100644
--- a/doc/ru/scripting/builtins/libvecn.md
+++ b/doc/ru/scripting/builtins/libvecn.md
@@ -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
diff --git a/doc/ru/scripting/ecs.md b/doc/ru/scripting/ecs.md
new file mode 100644
index 00000000..52e94d62
--- /dev/null
+++ b/doc/ru/scripting/ecs.md
@@ -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 игрока передается в качестве аргумента.
+
diff --git a/doc/ru/scripting/user-input.md b/doc/ru/scripting/user-input.md
index e113b3b1..422e17bd 100644
--- a/doc/ru/scripting/user-input.md
+++ b/doc/ru/scripting/user-input.md
@@ -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") {
+ ...
+}
+```
diff --git a/res/config/bindings.toml b/res/config/bindings.toml
index e13be068..2d373cb9 100644
--- a/res/config/bindings.toml
+++ b/res/config/bindings.toml
@@ -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"
diff --git a/res/content/base/blocks/torch.json b/res/content/base/blocks/torch.json
index 7b0605b7..133f9739 100644
--- a/res/content/base/blocks/torch.json
+++ b/res/content/base/blocks/torch.json
@@ -14,5 +14,6 @@
"shadeless": true,
"obstacle": false,
"grounded": true,
- "rotation": "pipe"
+ "rotation": "pipe",
+ "material": "base:wood"
}
diff --git a/res/content/base/content.json b/res/content/base/content.json
index 0e6dc07f..9b696dfd 100644
--- a/res/content/base/content.json
+++ b/res/content/base/content.json
@@ -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"
]
}
\ No newline at end of file
diff --git a/res/content/base/entities/drop.json b/res/content/base/entities/drop.json
new file mode 100644
index 00000000..9577eb7e
--- /dev/null
+++ b/res/content/base/entities/drop.json
@@ -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
+}
diff --git a/res/content/base/entities/falling_block.json b/res/content/base/entities/falling_block.json
new file mode 100644
index 00000000..cd0459b5
--- /dev/null
+++ b/res/content/base/entities/falling_block.json
@@ -0,0 +1,7 @@
+{
+ "components": [
+ "base:falling_block"
+ ],
+ "skeleton-name": "base:block",
+ "hitbox": [0.8, 0.8, 0.8]
+}
diff --git a/res/content/base/entities/player.json b/res/content/base/entities/player.json
new file mode 100644
index 00000000..36be7bab
--- /dev/null
+++ b/res/content/base/entities/player.json
@@ -0,0 +1,3 @@
+{
+ "hitbox": [0.6, 1.8, 0.6]
+}
diff --git a/res/content/base/models/block.obj b/res/content/base/models/block.obj
new file mode 100644
index 00000000..2e307e52
--- /dev/null
+++ b/res/content/base/models/block.obj
@@ -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
diff --git a/res/content/base/models/drop-block.obj b/res/content/base/models/drop-block.obj
new file mode 100644
index 00000000..be4cf551
--- /dev/null
+++ b/res/content/base/models/drop-block.obj
@@ -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
diff --git a/res/content/base/models/drop-item.obj b/res/content/base/models/drop-item.obj
new file mode 100644
index 00000000..a12cca01
--- /dev/null
+++ b/res/content/base/models/drop-item.obj
@@ -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
diff --git a/res/content/base/models/player-head.obj b/res/content/base/models/player-head.obj
new file mode 100644
index 00000000..6a6a9024
--- /dev/null
+++ b/res/content/base/models/player-head.obj
@@ -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
diff --git a/res/content/base/models/player.obj b/res/content/base/models/player.obj
new file mode 100644
index 00000000..f9f65297
--- /dev/null
+++ b/res/content/base/models/player.obj
@@ -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
diff --git a/res/content/base/preload.json b/res/content/base/preload.json
index 801938a6..005abdfd 100644
--- a/res/content/base/preload.json
+++ b/res/content/base/preload.json
@@ -1,6 +1,10 @@
{
"sounds": [
"blocks/door_open",
- "blocks/door_close"
+ "blocks/door_close",
+ "events/pickup"
+ ],
+ "models": [
+ "drop-item"
]
}
diff --git a/res/content/base/resources.json b/res/content/base/resources.json
new file mode 100644
index 00000000..93922266
--- /dev/null
+++ b/res/content/base/resources.json
@@ -0,0 +1,8 @@
+{
+ "camera": [
+ "first-person",
+ "third-person-front",
+ "third-person-back",
+ "cinematic"
+ ]
+}
diff --git a/res/content/base/scripts/components/drop.lua b/res/content/base/scripts/components/drop.lua
new file mode 100644
index 00000000..9fd7dc5e
--- /dev/null
+++ b/res/content/base/scripts/components/drop.lua
@@ -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
diff --git a/res/content/base/scripts/components/falling_block.lua b/res/content/base/scripts/components/falling_block.lua
new file mode 100644
index 00000000..c5a91a79
--- /dev/null
+++ b/res/content/base/scripts/components/falling_block.lua
@@ -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
diff --git a/res/content/base/scripts/hud.lua b/res/content/base/scripts/hud.lua
new file mode 100644
index 00000000..694ff847
--- /dev/null
+++ b/res/content/base/scripts/hud.lua
@@ -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
diff --git a/res/content/base/scripts/sand.lua b/res/content/base/scripts/sand.lua
new file mode 100644
index 00000000..a3f148dc
--- /dev/null
+++ b/res/content/base/scripts/sand.lua
@@ -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
diff --git a/res/content/base/skeletons/block.json b/res/content/base/skeletons/block.json
new file mode 100644
index 00000000..77444f6e
--- /dev/null
+++ b/res/content/base/skeletons/block.json
@@ -0,0 +1,5 @@
+{
+ "root": {
+ "model": "block"
+ }
+}
diff --git a/res/content/base/skeletons/drop.json b/res/content/base/skeletons/drop.json
new file mode 100644
index 00000000..530b400d
--- /dev/null
+++ b/res/content/base/skeletons/drop.json
@@ -0,0 +1,5 @@
+{
+ "root": {
+ "model": "drop-block"
+ }
+}
diff --git a/res/content/base/skeletons/player.json b/res/content/base/skeletons/player.json
new file mode 100644
index 00000000..ce2be7b6
--- /dev/null
+++ b/res/content/base/skeletons/player.json
@@ -0,0 +1,16 @@
+{
+ "root": {
+ "nodes": [
+ {
+ "name": "body",
+ "model": "player",
+ "nodes": [
+ {
+ "name": "head",
+ "model": "player-head"
+ }
+ ]
+ }
+ ]
+ }
+}
diff --git a/res/content/base/sounds/events/pickup.ogg b/res/content/base/sounds/events/pickup.ogg
new file mode 100644
index 00000000..3ec97741
Binary files /dev/null and b/res/content/base/sounds/events/pickup.ogg differ
diff --git a/res/content/base/textures/entities/player.png b/res/content/base/textures/entities/player.png
new file mode 100644
index 00000000..3e92a430
Binary files /dev/null and b/res/content/base/textures/entities/player.png differ
diff --git a/res/layouts/console.xml.lua b/res/layouts/console.xml.lua
index f71920ce..e23ce357 100644
--- a/res/layouts/console.xml.lua
+++ b/res/layouts/console.xml.lua
@@ -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()
diff --git a/res/layouts/pages/settings.xml b/res/layouts/pages/settings.xml
index 5e0ea285..223a7f96 100644
--- a/res/layouts/pages/settings.xml
+++ b/res/layouts/pages/settings.xml
@@ -1,6 +1,7 @@
+
diff --git a/res/layouts/pages/settings.xml.lua b/res/layouts/pages/settings.xml.lua
index f21f25c3..345d5620 100644
--- a/res/layouts/pages/settings.xml.lua
+++ b/res/layouts/pages/settings.xml.lua
@@ -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
diff --git a/res/layouts/pages/settings_display.xml b/res/layouts/pages/settings_display.xml
new file mode 100644
index 00000000..2aa3aeca
--- /dev/null
+++ b/res/layouts/pages/settings_display.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/res/layouts/pages/settings_display.xml.lua b/res/layouts/pages/settings_display.xml.lua
new file mode 100644
index 00000000..e861deb3
--- /dev/null
+++ b/res/layouts/pages/settings_display.xml.lua
@@ -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(
+ "%s",
+ 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
diff --git a/res/layouts/pages/settings_graphics.xml.lua b/res/layouts/pages/settings_graphics.xml.lua
index 179d8df8..de00c87b 100644
--- a/res/layouts/pages/settings_graphics.xml.lua
+++ b/res/layouts/pages/settings_graphics.xml.lua
@@ -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
diff --git a/res/modules/internal/stdcomp.lua b/res/modules/internal/stdcomp.lua
new file mode 100644
index 00000000..e3f5589f
--- /dev/null
+++ b/res/modules/internal/stdcomp.lua
@@ -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
+}
diff --git a/res/scripts/stdcmd.lua b/res/scripts/stdcmd.lua
index 4368174b..1933ae03 100644
--- a/res/scripts/stdcmd.lua
+++ b/res/scripts/stdcmd.lua
@@ -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
+)
diff --git a/res/scripts/stdlib.lua b/res/scripts/stdlib.lua
index 1873dd3e..22b315d1 100644
--- a/res/scripts/stdlib.lua
+++ b/res/scripts/stdlib.lua
@@ -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
diff --git a/res/texts/en_US.txt b/res/texts/en_US.txt
index 4b8d5e5e..f36f12a7 100644
--- a/res/texts/en_US.txt
+++ b/res/texts/en_US.txt
@@ -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
diff --git a/res/texts/ru_RU.txt b/res/texts/ru_RU.txt
index d04997f9..905a1fd3 100644
--- a/res/texts/ru_RU.txt
+++ b/res/texts/ru_RU.txt
@@ -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=Сменить Режим Камеры
diff --git a/src/assets/Assets.hpp b/src/assets/Assets.hpp
index fd39e8fe..1602463b 100644
--- a/src/assets/Assets.hpp
+++ b/src/assets/Assets.hpp
@@ -5,6 +5,7 @@
#include
#include
+#include
#include
#include
#include
@@ -16,6 +17,11 @@ class Assets;
namespace assetload {
/// @brief final work to do in the main thread
using postfunc = std::function;
+
+ using setupfunc = std::function;
+
+ template
+ void assets_setup(const Assets*);
}
class Assets {
@@ -23,6 +29,7 @@ class Assets {
using assets_map = std::unordered_map>;
std::unordered_map assets;
+ std::vector setupFuncs;
public:
Assets() {}
Assets(const Assets&) = delete;
@@ -49,6 +56,34 @@ public:
}
return static_cast(found->second.get());
}
+
+ template
+ std::optional 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
+void assetload::assets_setup(const Assets* assets) {
+ if (auto mapPtr = assets->getMap()) {
+ for (const auto& entry : **mapPtr) {
+ static_cast(entry.second.get())->setup();
+ }
+ }
+}
+
#endif // ASSETS_ASSETS_HPP_
diff --git a/src/assets/AssetsLoader.cpp b/src/assets/AssetsLoader.cpp
index 65f52da7..ae39e4d8 100644
--- a/src/assets/AssetsLoader.cpp
+++ b/src/assets/AssetsLoader.cpp
@@ -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(env));
+ loader.add(AssetType::LAYOUT, file.u8string(), name, std::make_shared(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 "";
}
@@ -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(
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(
diff --git a/src/assets/AssetsLoader.hpp b/src/assets/AssetsLoader.hpp
index 91eb1036..b6a9b75d 100644
--- a/src/assets/AssetsLoader.hpp
+++ b/src/assets/AssetsLoader.hpp
@@ -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;
diff --git a/src/assets/assetload_funcs.cpp b/src/assets/assetload_funcs.cpp
index b7fe8ccf..ccbe3e80 100644
--- a/src/assets/assetload_funcs.cpp
+++ b/src/assets/assetload_funcs.cpp
@@ -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), name);
};
diff --git a/src/assets/assetload_funcs.hpp b/src/assets/assetload_funcs.hpp
index 71b3cb93..6251ff88 100644
--- a/src/assets/assetload_funcs.hpp
+++ b/src/assets/assetload_funcs.hpp
@@ -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& settings
);
postfunc shader(
AssetsLoader*,
const ResPaths* paths,
const std::string& filename,
- const std::string &name,
+ const std::string& name,
const std::shared_ptr& 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& settings
);
postfunc font(
AssetsLoader*,
const ResPaths* paths,
const std::string& filename,
- const std::string &name,
+ const std::string& name,
const std::shared_ptr& settings
);
postfunc layout(
AssetsLoader*,
const ResPaths* paths,
const std::string& file,
- const std::string &name,
+ const std::string& name,
const std::shared_ptr& settings
);
postfunc sound(
AssetsLoader*,
const ResPaths* paths,
const std::string& file,
- const std::string &name,
+ const std::string& name,
const std::shared_ptr& settings
);
postfunc model(
AssetsLoader*,
const ResPaths* paths,
const std::string& file,
- const std::string &name,
+ const std::string& name,
const std::shared_ptr& settings
);
}
diff --git a/src/coders/binary_json.cpp b/src/coders/binary_json.cpp
index 8c3236e8..f1f87af6 100644
--- a/src/coders/binary_json.cpp
+++ b/src/coders/binary_json.cpp
@@ -83,6 +83,13 @@ std::vector json::to_binary(const Map* obj, bool compress) {
return builder.build();
}
+std::vector json::to_binary(const Value& value, bool compress) {
+ if (auto map = std::get_if(&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) {
diff --git a/src/coders/binary_json.hpp b/src/coders/binary_json.hpp
index c79a7d4d..0cb9945b 100644
--- a/src/coders/binary_json.hpp
+++ b/src/coders/binary_json.hpp
@@ -1,7 +1,7 @@
#ifndef CODERS_BINARY_JSON_HPP_
#define CODERS_BINARY_JSON_HPP_
-#include "../typedefs.hpp"
+#include "../data/dynamic_fwd.hpp"
#include
#include
@@ -26,8 +26,9 @@ namespace json {
inline constexpr int BJSON_TYPE_NULL = 0xC;
inline constexpr int BJSON_TYPE_CDOCUMENT = 0x1F;
- extern std::vector to_binary(const dynamic::Map* obj, bool compress=false);
- extern std::shared_ptr from_binary(const ubyte* src, size_t size);
+ std::vector to_binary(const dynamic::Map* obj, bool compress=false);
+ std::vector to_binary(const dynamic::Value& obj, bool compress=false);
+ std::shared_ptr from_binary(const ubyte* src, size_t size);
}
#endif // CODERS_BINARY_JSON_HPP_
diff --git a/src/coders/json.cpp b/src/coders/json.cpp
index dbe42b5a..c88e0c1d 100644
--- a/src/coders/json.cpp
+++ b/src/coders/json.cpp
@@ -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("", source);
}
diff --git a/src/coders/json.hpp b/src/coders/json.hpp
index f4379fdc..7dc7b92e 100644
--- a/src/coders/json.hpp
+++ b/src/coders/json.hpp
@@ -9,8 +9,8 @@
#include
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,
diff --git a/src/constants.hpp b/src/constants.hpp
index 76c13a74..56ff3160 100644
--- a/src/constants.hpp
+++ b/src/constants.hpp
@@ -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_
diff --git a/src/content/Content.cpp b/src/content/Content.cpp
index 8e0b0749..11e204d6 100644
--- a/src/content/Content.cpp
+++ b/src/content/Content.cpp
@@ -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 blockDefs,
- std::vector itemDefs
-) : blockDefs(std::move(blockDefs)),
- itemDefs(std::move(itemDefs))
+ ContentUnitIndices blocks,
+ ContentUnitIndices items,
+ ContentUnitIndices entities
+) : blocks(std::move(blocks)),
+ items(std::move(items)),
+ entities(std::move(entities))
{}
Content::Content(
std::unique_ptr indices,
std::unique_ptr drawGroups,
- std::unordered_map> blockDefs,
- std::unordered_map> itemDefs,
- std::unordered_map> packs,
- std::unordered_map> blockMaterials
-) : blockDefs(std::move(blockDefs)),
- itemDefs(std::move(itemDefs)),
- indices(std::move(indices)),
+ ContentUnitDefs blocks,
+ ContentUnitDefs items,
+ ContentUnitDefs entities,
+ UptrsMap packs,
+ UptrsMap blockMaterials,
+ UptrsMap 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>& Content::getBlockMaterials() const {
+const UptrsMap& Content::getBlockMaterials() const {
return blockMaterials;
}
-const std::unordered_map>& Content::getPacks() const {
+const UptrsMap& Content::getPacks() const {
return packs;
}
+
+const UptrsMap& Content::getSkeletons() const {
+ return skeletons;
+}
diff --git a/src/content/Content.hpp b/src/content/Content.hpp
index 75237757..16e072b7 100644
--- a/src/content/Content.hpp
+++ b/src/content/Content.hpp
@@ -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
#include
#include
+#include
#include
#include
#include
using DrawGroups = std::set;
+template
+using UptrsMap = std::unordered_map>;
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 blockDefs;
- std::vector itemDefs;
+template
+class ContentUnitIndices {
+ std::vector defs;
public:
- ContentIndices(
- std::vector blockDefs,
- std::vector itemDefs
- );
+ ContentUnitIndices(std::vector 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> blockDefs;
- std::unordered_map> itemDefs;
- std::unique_ptr indices;
- std::unordered_map> packs;
- std::unordered_map> blockMaterials;
+/// @brief Runtime defs cache: indices
+class ContentIndices {
public:
+ ContentUnitIndices blocks;
+ ContentUnitIndices items;
+ ContentUnitIndices entities;
+
+ ContentIndices(
+ ContentUnitIndices blocks,
+ ContentUnitIndices items,
+ ContentUnitIndices entities
+ );
+};
+
+template
+class ContentUnitDefs {
+ UptrsMap defs;
+public:
+ ContentUnitDefs(UptrsMap 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 names;
+ std::unordered_map indices;
+ std::unique_ptr> savedData;
+public:
+ ResourceIndices()
+ : savedData(std::make_unique>()){
+ }
+
+ 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_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 indices;
+ UptrsMap packs;
+ UptrsMap blockMaterials;
+ UptrsMap skeletons;
+public:
+ ContentUnitDefs blocks;
+ ContentUnitDefs items;
+ ContentUnitDefs entities;
std::unique_ptr const drawGroups;
+ ResourceIndicesSet resourceIndices {};
Content(
std::unique_ptr indices,
std::unique_ptr drawGroups,
- std::unordered_map> blockDefs,
- std::unordered_map> itemDefs,
- std::unordered_map> packs,
- std::unordered_map> blockMaterials
+ ContentUnitDefs blocks,
+ ContentUnitDefs items,
+ ContentUnitDefs entities,
+ UptrsMap packs,
+ UptrsMap blockMaterials,
+ UptrsMap 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(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>& getBlockMaterials() const;
- const std::unordered_map>& getPacks() const;
+ const UptrsMap& getBlockMaterials() const;
+ const UptrsMap& getPacks() const;
+ const UptrsMap& getSkeletons() const;
};
#endif // CONTENT_CONTENT_HPP_
diff --git a/src/content/ContentBuilder.cpp b/src/content/ContentBuilder.cpp
index a660ca9a..6f7b730b 100644
--- a/src/content/ContentBuilder.cpp
+++ b/src/content/ContentBuilder.cpp
@@ -1,31 +1,15 @@
#include "ContentBuilder.hpp"
+#include "../objects/rigging.hpp"
+
ContentBuilder::~ContentBuilder() {}
void ContentBuilder::add(std::unique_ptr 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(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(id);
- return *itemDefs[id];
+void ContentBuilder::add(std::unique_ptr 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 ContentBuilder::build() {
std::vector blockDefsIndices;
auto groups = std::make_unique();
- 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 ContentBuilder::build() {
}
std::vector 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 ContentBuilder::build() {
itemDefsIndices.push_back(&def);
}
+ std::vector 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(
- std::make_unique(blockDefsIndices, itemDefsIndices),
- std::move(groups),
- std::move(blockDefs),
- std::move(itemDefs),
- std::move(packs),
- std::move(blockMaterials)
+ std::make_unique(
+ 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;
diff --git a/src/content/ContentBuilder.hpp b/src/content/ContentBuilder.hpp
index 0c7a6d31..5c188ca3 100644
--- a/src/content/ContentBuilder.hpp
+++ b/src/content/ContentBuilder.hpp
@@ -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
#include
-class ContentBuilder {
- std::unordered_map> blockDefs;
- std::vector blockIds;
+template
+class ContentUnitBuilder {
+ std::unordered_map& allNames;
+ contenttype type;
- std::unordered_map> itemDefs;
- std::vector itemIds;
-
- std::unordered_map> blockMaterials;
- std::unordered_map> 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 defs;
+ std::vector names;
+
+ ContentUnitBuilder(
+ std::unordered_map& 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(id);
+ return *defs[id];
+ }
+
+ auto build() {
+ return std::move(defs);
+ }
+};
+
+class ContentBuilder {
+ UptrsMap blockMaterials;
+ UptrsMap skeletons;
+ UptrsMap packs;
+ std::unordered_map allNames;
+public:
+ ContentUnitBuilder blocks {allNames, contenttype::block};
+ ContentUnitBuilder items {allNames, contenttype::item};
+ ContentUnitBuilder entities {allNames, contenttype::entity};
+ ResourceIndicesSet resourceIndices {};
+
~ContentBuilder();
void add(std::unique_ptr pack);
+ void add(std::unique_ptr 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 build();
};
diff --git a/src/content/ContentLUT.cpp b/src/content/ContentLUT.cpp
index bb7ed390..5aa95bc3 100644
--- a/src/content/ContentLUT.cpp
+++ b/src/content/ContentLUT.cpp
@@ -6,31 +6,16 @@
#include "../coders/json.hpp"
#include "../voxels/Block.hpp"
#include "../items/ItemDef.hpp"
-#include "../data/dynamic.hpp"
-
#include
-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 static constexpr size_t get_entries_count(
+ const ContentUnitIndices& indices, const dynamic::List_sptr& list) {
+ return list ? std::max(list->size(), indices.count()) : indices.count();
}
std::shared_ptr ContentLUT::create(
@@ -42,38 +27,13 @@ std::shared_ptr 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(content, blocks_c, items_c);
+ auto lut = std::make_shared(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::create(
std::vector ContentLUT::getMissingContent() const {
std::vector 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;
}
diff --git a/src/content/ContentLUT.hpp b/src/content/ContentLUT.hpp
index f7c8a799..baf347d7 100644
--- a/src/content/ContentLUT.hpp
+++ b/src/content/ContentLUT.hpp
@@ -5,6 +5,7 @@
#include "../typedefs.hpp"
#include "../constants.hpp"
+#include "../data/dynamic.hpp"
#include
#include
@@ -18,58 +19,82 @@ struct contententry {
std::string name;
};
-// TODO: make it unified for all types of content
+template
+class ContentUnitLUT {
+ std::vector indices;
+ std::vector names;
+ bool missingContent = false;
+ bool reorderContent = false;
+ T missingValue;
+ contenttype type;
+public:
+ ContentUnitLUT(size_t count, const ContentUnitIndices& 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& 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& 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 blocks;
- std::vector blockNames;
-
- std::vector items;
- std::vector itemNames;
-
- bool reorderContent = false;
- bool missingContent = false;
public:
- ContentLUT(const Content* content, size_t blocks, size_t items);
+ ContentUnitLUT blocks;
+ ContentUnitLUT 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 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 getMissingContent() const;
diff --git a/src/content/ContentLoader.cpp b/src/content/ContentLoader.cpp
index 73d8b3a3..56480dfb 100644
--- a/src/content/ContentLoader.cpp
+++ b/src/content/ContentLoader.cpp
@@ -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
@@ -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(
+ *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& 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 detected;
- std::vector 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 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(
- *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(type)].add(
+ pack->id+":"+list->str(i), nullptr);
+ }
}
diff --git a/src/content/ContentLoader.hpp b/src/content/ContentLoader.hpp
index fa1c4b61..2aa243df 100644
--- a/src/content/ContentLoader.hpp
+++ b/src/content/ContentLoader.hpp
@@ -1,33 +1,48 @@
#ifndef CONTENT_CONTENT_LOADER_HPP_
#define CONTENT_CONTENT_LOADER_HPP_
-#include "../typedefs.hpp"
+#include "content_fwd.hpp"
#include
+#include
#include
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_
diff --git a/src/content/ContentPack.cpp b/src/content/ContentPack.cpp
index e3a5f0f1..8388e30c 100644
--- a/src/content/ContentPack.cpp
+++ b/src/content/ContentPack.cpp
@@ -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 ContentPack::RESERVED_NAMES = {
"res", "abs", "local", "core", "user", "world", "none", "null"
};
diff --git a/src/content/ContentPack.hpp b/src/content/ContentPack.hpp
index ca0aff2e..88c81a4e 100644
--- a/src/content/ContentPack.hpp
+++ b/src/content/ContentPack.hpp
@@ -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 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
diff --git a/src/content/content_fwd.hpp b/src/content/content_fwd.hpp
new file mode 100644
index 00000000..52bedab8
--- /dev/null
+++ b/src/content/content_fwd.hpp
@@ -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(ResourceType::LAST)+1;
+
+#endif // CONTENT_CONTENT_FWD_HPP_
diff --git a/src/core_defs.cpp b/src/core_defs.cpp
index 57c85bbc..549988d0 100644
--- a/src/core_defs.cpp
+++ b/src/core_defs.cpp
@@ -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");
diff --git a/src/data/dynamic.cpp b/src/data/dynamic.cpp
index 0cc88103..d8819bcd 100644
--- a/src/data/dynamic.cpp
+++ b/src/data/dynamic.cpp
@@ -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(&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(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(&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(found->second).get();
+ return std::get(found->second);
return nullptr;
}
diff --git a/src/data/dynamic.hpp b/src/data/dynamic.hpp
index 5dc5dbed..90f4fe69 100644
--- a/src/data/dynamic.hpp
+++ b/src/data/dynamic.hpp
@@ -1,42 +1,22 @@
#ifndef DATA_DYNAMIC_HPP_
#define DATA_DYNAMIC_HPP_
-#include "../typedefs.hpp"
+#include "dynamic_fwd.hpp"
#include
#include
#include
#include
#include
-#include
+
#include
#include
namespace dynamic {
- class Map;
- class List;
-
enum class Type {
none=0, map, list, string, number, boolean, integer
};
- using Map_sptr = std::shared_ptr