@@ -1,3 +1,6 @@
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
/build
|
/build
|
||||||
/.regolith
|
/.regolith
|
||||||
|
docs/superpowers
|
||||||
|
.claude
|
||||||
|
node_modules/
|
||||||
@@ -6,8 +6,9 @@
|
|||||||
|
|
||||||
[](https://github.com/ForestOfLight/Construct/releases)
|
[](https://github.com/ForestOfLight/Construct/releases)
|
||||||
[](https://www.curseforge.com/minecraft-bedrock/addons/construct)
|
[](https://www.curseforge.com/minecraft-bedrock/addons/construct)
|
||||||
[-brightgreen)](https://feedback.minecraft.net/hc/en-us/sections/360001186971-Release-Changelogs)
|
[-brightgreen)](https://feedback.minecraft.net/hc/en-us/sections/360001186971-Release-Changelogs)
|
||||||
[](https://discord.gg/9KGche8fxm)
|
[](https://discord.gg/9KGche8fxm)
|
||||||
|
[](https://buymeacoffee.com/forestoflight)
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -78,3 +79,7 @@ If you have any issues or suggestions, please don't hesitate to open an issue on
|
|||||||
### Adding Translations
|
### Adding Translations
|
||||||
|
|
||||||
Construct currently supports American English and Chinese (thanks to [wed150](https://github.com/wed150) & [EndrTrekker](https://github.com/EndrTrekker)). If you would like to contribute a translation, please join our Discord and reach out!
|
Construct currently supports American English and Chinese (thanks to [wed150](https://github.com/wed150) & [EndrTrekker](https://github.com/EndrTrekker)). If you would like to contribute a translation, please join our Discord and reach out!
|
||||||
|
|
||||||
|
### Donate
|
||||||
|
|
||||||
|
If you appreciate my work here and would like to support the future development of my addons, please consider donating to me on [BuyMeACoffee](https://buymeacoffee.com/forestoflight). Your support is greatly appreciated!
|
||||||
|
|||||||
+16
@@ -0,0 +1,16 @@
|
|||||||
|
# API Reference
|
||||||
|
|
||||||
|
This section documents the public API of Construct, including all endpoints that can be accessed by other addons or scripts. This is intended for advanced users who want to extend or integrate with Construct beyond the provided CLI commands and GUI features.
|
||||||
|
|
||||||
|
# API Overview
|
||||||
|
|
||||||
|
Construct's API can be accessed via the [AddonAPIKit](https://github.com/ForestOflight/addonapikit) package. Instructions for importing the API into your project can be found in the AddonAPIKit documentation.
|
||||||
|
|
||||||
|
You'll need to include Construct's Data Model definitions in your project to work with the API effectively. These data models define the structure of the data passed to and from the API endpoints. They can be found at [`packs/BP/scripts/API/ConstructAPIModel.js`](https://github.com/ForestOfLight/Construct/blob/main/packs/BP/scripts/API/ConstructAPIModel.js). Copy the file into your project and import the models as needed.
|
||||||
|
|
||||||
|
Feedback on the Construct API is very welcome! If you have suggestions for new endpoints, improvements to existing ones, or any other feedback, please open a GitHub issue.
|
||||||
|
|
||||||
|
Detailed information about the available API endpoints can be found in the following pages:
|
||||||
|
|
||||||
|
- [Endpoints](./API/Endpoints.md)
|
||||||
|
- [Data Models](./API/DataModels.md)
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
# Data Models
|
||||||
|
|
||||||
|
The following data models are used to represent the format of the data passed by the API endpoints. More information about the API endpoints can be found in the [Endpoints](./Endpoints.md) page.
|
||||||
|
|
||||||
|
You'll need to include Construct's Data Model definitions in your project to work with the API effectively. These data models define the structure of the data passed to and from the API endpoints. They can be found at [`packs/BP/scripts/API/ConstructAPIModel.js`](https://github.com/ForestOfLight/Construct/blob/main/packs/BP/scripts/API/ConstructAPIModel.js). Copy the file into your project and import the models as needed.
|
||||||
|
|
||||||
|
## Instance
|
||||||
|
|
||||||
|
An `Instance` represents a single structure instance in the world, along with its properties and state.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface Instance {
|
||||||
|
name: PROTO.String,
|
||||||
|
structureId: PROTO.String,
|
||||||
|
isEnabled: PROTO.Boolean,
|
||||||
|
dimensionId: PROTO.Optional(PROTO.String),
|
||||||
|
location: PROTO.Optional({
|
||||||
|
x: PROTO.Float64,
|
||||||
|
y: PROTO.Float64,
|
||||||
|
z: PROTO.Float64
|
||||||
|
}),
|
||||||
|
bounds: PROTO.Optional(PROTO.Object({
|
||||||
|
min: {
|
||||||
|
x: PROTO.Float64,
|
||||||
|
y: PROTO.Float64,
|
||||||
|
z: PROTO.Float64
|
||||||
|
},
|
||||||
|
max: {
|
||||||
|
x: PROTO.Float64,
|
||||||
|
y: PROTO.Float64,
|
||||||
|
z: PROTO.Float64
|
||||||
|
}
|
||||||
|
})),
|
||||||
|
currentLayer: PROTO.Int16,
|
||||||
|
maxLayer: PROTO.Int16,
|
||||||
|
verifier: PROTO.Object({
|
||||||
|
isEnabled: PROTO.Boolean,
|
||||||
|
trackPlayerDistance: PROTO.Int8,
|
||||||
|
particleLifetime: PROTO.Int32
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Builder
|
||||||
|
|
||||||
|
A `Builder` represents a single builder (Construct's name for Players) in the world, along with its settings and properties.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface Builder {
|
||||||
|
playerId: PROTO.String,
|
||||||
|
easyPlace: PROTO.Boolean,
|
||||||
|
fastEasyPlace: PROTO.Boolean,
|
||||||
|
materialGrabber: PROTO.Boolean,
|
||||||
|
materialInstanceName: PROTO.String
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
# Endpoints
|
||||||
|
|
||||||
|
This page documents all the available API endpoints that can be accessed by other addons or scripts. The API is designed so that entire objects are passed at once, rather than making multiple calls to edit or query individual properties. This allows for fewer API calls and easier access to data.
|
||||||
|
|
||||||
|
## Instances
|
||||||
|
|
||||||
|
### `construct:instances`
|
||||||
|
|
||||||
|
Get a list of all registered instance names.
|
||||||
|
|
||||||
|
- **Parameters**: `void`
|
||||||
|
- **Returns**: `string[]`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `construct:instance:get`
|
||||||
|
|
||||||
|
Get the full data object for a specific instance.
|
||||||
|
|
||||||
|
- **Parameters**: `instanceName: string`
|
||||||
|
- **Returns**: `Instance` (see [Data Model](./DataModels.md#instance))
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `construct:instance:add`
|
||||||
|
|
||||||
|
Create a new instance with a given name and structure ID.
|
||||||
|
|
||||||
|
- **Parameters**: `instanceName: string`, `structureId: string`
|
||||||
|
- **Returns**: `Instance` (see [Data Model](./DataModels.md#instance))
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `construct:instance:edit`
|
||||||
|
|
||||||
|
Edit properties of an existing instance (e.g. enabled state, position).
|
||||||
|
|
||||||
|
- **Parameters**: `instanceName: string`, `properties: Instance`
|
||||||
|
- **Returns**: `Instance` (see [Data Model](./DataModels.md#instance))
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `construct:instance:delete`
|
||||||
|
|
||||||
|
Permanently delete an instance.
|
||||||
|
|
||||||
|
- **Parameters**: `instanceName: string`
|
||||||
|
- **Returns**: `void`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `construct:instance:materials`
|
||||||
|
|
||||||
|
Get a list of materials required to build the active section of an instance. Respects the active layer.
|
||||||
|
|
||||||
|
- **Parameters**: `instanceName: string`
|
||||||
|
- **Returns**: `Map<string, number>` (material name to quantity)
|
||||||
|
|
||||||
|
## Builders
|
||||||
|
|
||||||
|
### `construct:builder:get`
|
||||||
|
|
||||||
|
Get the full data object for a specific builder (player).
|
||||||
|
|
||||||
|
- **Parameters**: `playerId: string`
|
||||||
|
- **Returns**: `Builder` (see [Data Model](./DataModels.md#builder))
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `construct:builder:edit`
|
||||||
|
|
||||||
|
Edit properties of an existing builder.
|
||||||
|
|
||||||
|
- **Parameters**: `playerId: string`, `properties: Builder`
|
||||||
|
- **Returns**: `Builder` (see [Data Model](./DataModels.md#builder))
|
||||||
+149
@@ -0,0 +1,149 @@
|
|||||||
|
# CLI Reference
|
||||||
|
|
||||||
|
All commands are prefixed with `construct:`. Arguments in angle brackets (`<arg>`) are required, while those in square brackets (`[arg]`) are optional. Assume commands can be run from any source (player, entity, block, or server) unless otherwise noted.
|
||||||
|
|
||||||
|
## Instance Management
|
||||||
|
|
||||||
|
### `construct:create <instanceName> <structureId>`
|
||||||
|
Create a new instance bound to a structure.
|
||||||
|
|
||||||
|
| Argument | Description |
|
||||||
|
|---|---|
|
||||||
|
| `<instanceName>` | Unique name for this instance. |
|
||||||
|
| `<structureId>` | ID of a structure saved in the world (without the `mystructure:` prefix). |
|
||||||
|
|
||||||
|
> Corresponds to the "create new instance" flow in the main menu. Errors if `instanceName` is already taken or `structureId` does not exist.
|
||||||
|
|
||||||
|
### `construct:delete <instanceName>`
|
||||||
|
Permanently delete an instance.
|
||||||
|
|
||||||
|
| Argument | Description |
|
||||||
|
|---|---|
|
||||||
|
| `<instanceName>` | Name of the instance to delete. |
|
||||||
|
|
||||||
|
> Calls `structureCollection.delete()`. Also disables the instance and clears its saved dynamic properties before removal.
|
||||||
|
|
||||||
|
### `construct:rename <instanceName> <newInstanceName>`
|
||||||
|
Rename an existing instance.
|
||||||
|
|
||||||
|
| Argument | Description |
|
||||||
|
|---|---|
|
||||||
|
| `<instanceName>` | Current instance name. |
|
||||||
|
| `<newInstanceName>` | Desired new name. Errors if already in use. |
|
||||||
|
|
||||||
|
### `construct:list`
|
||||||
|
List all registered instances and their status.
|
||||||
|
|
||||||
|
> Prints each instance name, its bound structure ID, enabled/disabled state, and placed location (if any). Useful for scripting and quick inspection without opening the GUI.
|
||||||
|
|
||||||
|
## Placement & Movement
|
||||||
|
|
||||||
|
### `construct:place <instanceName> <x y z>`
|
||||||
|
Enable and place an instance at a location.
|
||||||
|
|
||||||
|
| Argument | Description |
|
||||||
|
|---|---|
|
||||||
|
| `<instanceName>` | Instance to place. |
|
||||||
|
| `<x y z>` | World coordinates. Errors if omitted. Supports tilde (`~`) notation. |
|
||||||
|
|
||||||
|
> Equivalent to the "Place" button in the instance menu — enables the instance and calls `move()` in one step. If the instance already has a location, this is a move, not a fresh place.
|
||||||
|
|
||||||
|
### `construct:move <instanceName> [x y z]`
|
||||||
|
Reposition a placed instance without toggling its enabled state.
|
||||||
|
|
||||||
|
| Argument | Description |
|
||||||
|
|---|---|
|
||||||
|
| `<instanceName>` | Name of a placed instance. |
|
||||||
|
| `<x y z>` | Target world coordinates. |
|
||||||
|
|
||||||
|
## Enable / Disable
|
||||||
|
|
||||||
|
### `construct:enable <instanceName>`
|
||||||
|
Enable a placed instance.
|
||||||
|
|
||||||
|
> Requires the instance to have a saved location. Refreshes the outliner, verifier, and materials cache.
|
||||||
|
|
||||||
|
### `construct:disable <instanceName>`
|
||||||
|
Disable an active instance.
|
||||||
|
|
||||||
|
> Tears down outliner rendering and pauses the verifier. The instance retains its saved location and can be re-enabled.
|
||||||
|
|
||||||
|
## Layer Control
|
||||||
|
|
||||||
|
### `construct:layer <instanceName> <layer>`
|
||||||
|
Set the active layer of an instance.
|
||||||
|
|
||||||
|
| Argument | Description |
|
||||||
|
|---|---|
|
||||||
|
| `<instanceName>` | Name of the instance. |
|
||||||
|
| `<layer>` | Integer layer index. `0` = whole structure (no layer selected). Valid range: `0` to `structure.height`. |
|
||||||
|
|
||||||
|
> Errors if `layer` is out of bounds. Only meaningful for structures with height > 1.
|
||||||
|
|
||||||
|
### `construct:nextlayer <instanceName>`
|
||||||
|
Step the layer up by one (wraps from max back to `0`).
|
||||||
|
|
||||||
|
> Mirrors the "Next layer" button. Wrapping from max → `0` restores the whole-structure view.
|
||||||
|
|
||||||
|
### `construct:prevlayer <instanceName>`
|
||||||
|
Step the layer down by one (wraps from `0` back to max).
|
||||||
|
|
||||||
|
> Mirrors the "Previous layer" button.
|
||||||
|
|
||||||
|
## Settings
|
||||||
|
|
||||||
|
### `construct:verifier <instanceName> true|false`
|
||||||
|
Toggle the structure verifier for an instance.
|
||||||
|
|
||||||
|
| Argument | Description |
|
||||||
|
|---|---|
|
||||||
|
| `<instanceName>` | Name of the instance. |
|
||||||
|
| `true\|false` | Whether to run the verifier. Corresponds to the "validation" toggle in Settings. |
|
||||||
|
|
||||||
|
### `construct:option <optionId> true|false`
|
||||||
|
Enable or disable a per-player builder option. Must be run as a player source.
|
||||||
|
|
||||||
|
| Argument | Description |
|
||||||
|
|---|---|
|
||||||
|
| `<optionId>` | One of: `easyPlace`, `fastEasyPlace`, `materialGrabber`. |
|
||||||
|
| `true\|false` | Desired state. Runs the option's enable/disable callback (gives or removes the action item). |
|
||||||
|
|
||||||
|
> Replaces the toggles in the Builder Options form. State is saved per-player via dynamic properties.
|
||||||
|
|
||||||
|
## Information
|
||||||
|
|
||||||
|
### `construct:info <instanceName>`
|
||||||
|
Print instance details to chat.
|
||||||
|
|
||||||
|
> Outputs: bound structure ID, enabled state, placed location and dimension, current layer, verifier enabled, structure bounds (min/max). Equivalent to the data shown in the instance menu body.
|
||||||
|
|
||||||
|
### `construct:stats <instanceName>`
|
||||||
|
Run the structure verifier and print statistics.
|
||||||
|
|
||||||
|
> Triggers a standalone `StructureVerifier` pass (same as the "Statistics" button) and sends the result to chat. Errors if a verifier is already running on this instance.
|
||||||
|
|
||||||
|
### `construct:materials <instanceName> [missing]`
|
||||||
|
Print the material list for an instance.
|
||||||
|
|
||||||
|
| Argument | Description |
|
||||||
|
|---|---|
|
||||||
|
| `<instanceName>` | Name of the instance. |
|
||||||
|
| `missing` | When present, show only materials the player does not have in their inventory (mirrors the "missing only" toggle). Can only be used by a player source. |
|
||||||
|
|
||||||
|
> Respects the active layer: if a layer is set, only that layer's material counts are shown.
|
||||||
|
|
||||||
|
## Utility
|
||||||
|
|
||||||
|
### `construct:item`
|
||||||
|
Give yourself the Construct menu item.
|
||||||
|
|
||||||
|
> Already implemented as a native custom command. Needs to be refactored to fit the new command pipeline.
|
||||||
|
|
||||||
|
### `construct:tag <instanceName>`
|
||||||
|
Rename the held Construct item to an instance name for quick-open. Errors if the item is not a construct item or if the instance name is not registered.
|
||||||
|
|
||||||
|
| Argument | Description |
|
||||||
|
|---|---|
|
||||||
|
| `<instanceName>` | Instance name to embed in the item's `nameTag`. Using the item in-world will jump straight to that instance's menu. |
|
||||||
|
|
||||||
|
> The item-use handler in `construct.js` already checks `itemStack.nameTag` against known instance names; this command just makes it easy to tag an item without renaming it in an anvil.
|
||||||
Generated
+47
@@ -0,0 +1,47 @@
|
|||||||
|
{
|
||||||
|
"name": "Construct",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"dependencies": {
|
||||||
|
"@minecraft/server": "^2.9.0-beta.1.26.30-stable",
|
||||||
|
"@minecraft/server-ui": "^2.2.0-beta.1.26.30-stable"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@minecraft/common": {
|
||||||
|
"version": "1.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@minecraft/common/-/common-1.3.0.tgz",
|
||||||
|
"integrity": "sha512-GLT8USFhvEyeTTFHZAgszbrnoT007hmXmK+aO4l+2A1up9/zwZ+4e8R8F0KcKrCWDjLEqkOJtPow0hQCcNJ++Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true
|
||||||
|
},
|
||||||
|
"node_modules/@minecraft/server": {
|
||||||
|
"version": "2.9.0-rc.1.26.40-preview.24",
|
||||||
|
"resolved": "https://registry.npmjs.org/@minecraft/server/-/server-2.9.0-rc.1.26.40-preview.24.tgz",
|
||||||
|
"integrity": "sha512-/DH82sRkYjADbVh5lEzpQQYKy+oRuQqLLOMGI1Yml1Q/fd3nIHedveoGpU1GkL0hzNFTJGG/pi7D2pPJEhfGzw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@minecraft/common": "^1.2.0",
|
||||||
|
"@minecraft/vanilla-data": ">=1.20.70 || 1.26.40-preview.24"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@minecraft/server-ui": {
|
||||||
|
"version": "2.2.0-beta.1.26.30-stable",
|
||||||
|
"resolved": "https://registry.npmjs.org/@minecraft/server-ui/-/server-ui-2.2.0-beta.1.26.30-stable.tgz",
|
||||||
|
"integrity": "sha512-OMkGdrU5w/g/oIHR6ltpxbTNzEDYcDoHI56jW5FOBK+U6UwBO5wQteAPVvKKcBqD8KsY0R72xw1cKNfVwwJFIg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"@minecraft/common": "^1.0.0",
|
||||||
|
"@minecraft/server": "^2.0.0 || ^2.9.0-beta.1.26.30-stable"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@minecraft/vanilla-data": {
|
||||||
|
"version": "1.26.21",
|
||||||
|
"resolved": "https://registry.npmjs.org/@minecraft/vanilla-data/-/vanilla-data-1.26.21.tgz",
|
||||||
|
"integrity": "sha512-bDmqSIjZBoaChpAdK2H3SVzhIod4/kXwY+viEA3AgftAbda1X1dkjMiBbxZcmwzRrVwT9embIB3zsEkYn1rSNA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peer": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"@minecraft/server": "^2.9.0-beta.1.26.30-stable",
|
||||||
|
"@minecraft/server-ui": "^2.2.0-beta.1.26.30-stable"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
{
|
{
|
||||||
"format_version": 2,
|
"format_version": 2,
|
||||||
"header": {
|
"header": {
|
||||||
"name": "Construct [BP] v1.0.9",
|
"name": "Construct [BP] v1.1.0",
|
||||||
"description": "Survival building addon by §aForestOfLight§r.",
|
"description": "Survival building addon by §aForestOfLight§r.",
|
||||||
"uuid": "8c0c0153-d8b9-482a-889f-aef922b8fe58",
|
"uuid": "8c0c0153-d8b9-482a-889f-aef922b8fe58",
|
||||||
"min_engine_version": [1, 26, 20],
|
"min_engine_version": [1, 26, 30],
|
||||||
"version": [1, 0, 9]
|
"version": [1, 1, 0]
|
||||||
},
|
},
|
||||||
"modules": [
|
"modules": [
|
||||||
{
|
{
|
||||||
@@ -26,15 +26,15 @@
|
|||||||
"dependencies": [
|
"dependencies": [
|
||||||
{
|
{
|
||||||
"module_name": "@minecraft/server",
|
"module_name": "@minecraft/server",
|
||||||
"version": "2.8.0-beta"
|
"version": "2.9.0-beta"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"module_name": "@minecraft/server-ui",
|
"module_name": "@minecraft/server-ui",
|
||||||
"version": "2.1.0-beta"
|
"version": "2.2.0-beta"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"uuid": "375ec465-3dc1-429f-8b4c-a337889e1ed4", // Construct RP
|
"uuid": "375ec465-3dc1-429f-8b4c-a337889e1ed4",
|
||||||
"version": [1, 0, 9]
|
"version": [1, 1, 0]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"metadata": {
|
"metadata": {
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import { AddonAPIServer } from "../lib/AddonAPIKit";
|
||||||
|
import { PACK_IDENTIFIER } from "../consts";
|
||||||
|
import { InstancesController } from "./controllers/InstancesController";
|
||||||
|
import { structureCollection } from "../classes/Structure/StructureCollection";
|
||||||
|
import { BuildersController } from "./controllers/BuildersController";
|
||||||
|
import { Builders } from "../classes/Builder/Builders";
|
||||||
|
|
||||||
|
class ConstructAPI extends AddonAPIServer {
|
||||||
|
constructor(version) {
|
||||||
|
super(PACK_IDENTIFIER, version);
|
||||||
|
const instancesController = new InstancesController(structureCollection);
|
||||||
|
this.setupController(instancesController);
|
||||||
|
const buildersController = new BuildersController(Builders);
|
||||||
|
this.setupController(buildersController);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const constructAPI = new ConstructAPI("1.0.0");
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
import { PROTO } from "../lib/AddonAPIKit";
|
||||||
|
|
||||||
|
// Instances
|
||||||
|
|
||||||
|
const LocationModel = PROTO.Object({
|
||||||
|
x: PROTO.Float64,
|
||||||
|
y: PROTO.Float64,
|
||||||
|
z: PROTO.Float64
|
||||||
|
});
|
||||||
|
|
||||||
|
export const InstanceModel = PROTO.Object({
|
||||||
|
name: PROTO.String,
|
||||||
|
structureId: PROTO.String,
|
||||||
|
isEnabled: PROTO.Boolean,
|
||||||
|
dimensionId: PROTO.Optional(PROTO.String),
|
||||||
|
location: PROTO.Optional(LocationModel),
|
||||||
|
bounds: PROTO.Optional(PROTO.Object({
|
||||||
|
min: LocationModel,
|
||||||
|
max: LocationModel
|
||||||
|
})),
|
||||||
|
currentLayer: PROTO.Int16,
|
||||||
|
maxLayer: PROTO.Int16,
|
||||||
|
verifier: PROTO.Object({
|
||||||
|
isEnabled: PROTO.Boolean,
|
||||||
|
trackPlayerDistance: PROTO.Int8,
|
||||||
|
particleLifetime: PROTO.Int32
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
export const InstancesModel = PROTO.Array(InstanceModel);
|
||||||
|
|
||||||
|
export const StructureMaterialsModel = PROTO.Map(PROTO.String, PROTO.Int32);
|
||||||
|
|
||||||
|
export const InstanceNameParameterModel = PROTO.Object({
|
||||||
|
instanceName: PROTO.String
|
||||||
|
});
|
||||||
|
|
||||||
|
export const AddInstanceParameterModel = PROTO.Object({
|
||||||
|
instanceName: PROTO.String,
|
||||||
|
structureId: PROTO.String
|
||||||
|
});
|
||||||
|
|
||||||
|
export const EditInstanceParameterModel = PROTO.Object({
|
||||||
|
instanceName: PROTO.String,
|
||||||
|
instance: InstanceModel
|
||||||
|
});
|
||||||
|
|
||||||
|
// Builders
|
||||||
|
|
||||||
|
export const BuilderModel = PROTO.Object({
|
||||||
|
playerId: PROTO.String,
|
||||||
|
easyPlace: PROTO.Boolean,
|
||||||
|
fastEasyPlace: PROTO.Boolean,
|
||||||
|
materialGrabber: PROTO.Boolean,
|
||||||
|
materialInstanceName: PROTO.String
|
||||||
|
});
|
||||||
|
|
||||||
|
export const BuilderIdParameterModel = PROTO.Object({
|
||||||
|
playerId: PROTO.String
|
||||||
|
});
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import { BuilderNotFoundError } from "../../classes/Errors/BuilderNotFoundError";
|
||||||
|
import { APICallerError, APIController } from "../../lib/AddonAPIKit";
|
||||||
|
import { BuilderIdParameterModel, BuilderModel } from "../ConstructAPIModel";
|
||||||
|
|
||||||
|
export class BuildersController extends APIController {
|
||||||
|
#context;
|
||||||
|
|
||||||
|
constructor(context) {
|
||||||
|
super();
|
||||||
|
this.addEndpoint("builder:get", this.getBuilder, BuilderIdParameterModel, BuilderModel);
|
||||||
|
this.addEndpoint("builder:edit", this.editBuilder, BuilderModel, BuilderModel);
|
||||||
|
this.#context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
getBuilder(playerId) {
|
||||||
|
try {
|
||||||
|
const builder = this.#context.get(playerId);
|
||||||
|
return builder.asPacket();
|
||||||
|
} catch(error) {
|
||||||
|
if (error instanceof BuilderNotFoundErrors)
|
||||||
|
throw new APICallerError(error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
editBuilder(builderOptions) {
|
||||||
|
try {
|
||||||
|
const builder = this.#context.get(builderOptions.playerId);
|
||||||
|
builder.setOptions(builderOptions);
|
||||||
|
} catch(error) {
|
||||||
|
if (error instanceof BuilderNotFoundError)
|
||||||
|
throw new APICallerError(error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
import { InstanceExistsError } from "../../classes/Errors/InstanceExistsError";
|
||||||
|
import { InstanceNotFoundError } from "../../classes/Errors/InstanceNotFoundError";
|
||||||
|
import { StructureNotFoundError } from "../../classes/Errors/StructureNotFoundError";
|
||||||
|
import { APICallerError, VoidModel, APIController } from "../../lib/AddonAPIKit";
|
||||||
|
import { AddInstanceParameterModel, EditInstanceParameterModel, InstanceModel, InstanceNameParameterModel, InstancesModel, StructureMaterialsModel } from "../ConstructAPIModel";
|
||||||
|
|
||||||
|
export class InstancesController extends APIController {
|
||||||
|
#context;
|
||||||
|
|
||||||
|
constructor(context) {
|
||||||
|
super();
|
||||||
|
this.addEndpoint("instances", this.getInstances, VoidModel, InstancesModel);
|
||||||
|
this.addEndpoint("instance:get", this.getInstance, InstanceNameParameterModel, InstanceModel);
|
||||||
|
this.addEndpoint("instance:add", this.addInstance, AddInstanceParameterModel, InstanceModel);
|
||||||
|
this.addEndpoint("instance:edit", this.editInstance, EditInstanceParameterModel, InstanceModel);
|
||||||
|
this.addEndpoint("instance:delete", this.deleteInstance, InstanceNameParameterModel, VoidModel);
|
||||||
|
this.addEndpoint("instance:materials", this.getMaterials, InstanceNameParameterModel, StructureMaterialsModel);
|
||||||
|
this.#context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
getInstances() {
|
||||||
|
return this.#context.getInstanceNames();
|
||||||
|
}
|
||||||
|
|
||||||
|
getInstance(instanceName) {
|
||||||
|
try {
|
||||||
|
const instance = this.#context.get(instanceName);
|
||||||
|
return instance.asPacket();
|
||||||
|
} catch(error) {
|
||||||
|
if (error instanceof InstanceNotFoundError)
|
||||||
|
throw new APICallerError(error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addInstance(instanceName, structureId) {
|
||||||
|
try {
|
||||||
|
const instance = this.#context.add(instanceName, structureId);
|
||||||
|
return instance.asPacket();
|
||||||
|
} catch(error) {
|
||||||
|
if (error instanceof InstanceExistsError || error instanceof StructureNotFoundError)
|
||||||
|
throw new APICallerError(error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
editInstance(instanceName, instanceOptions) {
|
||||||
|
try {
|
||||||
|
const instance = this.#context.get(instanceName);
|
||||||
|
instance.setOptions(instanceOptions);
|
||||||
|
return instance.asPacket();
|
||||||
|
} catch(error) {
|
||||||
|
if (error instanceof InstanceNotFoundError || error instanceof InstanceExistsError || error instanceof StructureNotFoundError)
|
||||||
|
throw new APICallerError(error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteInstance(instanceName) {
|
||||||
|
try {
|
||||||
|
this.#context.delete(instanceName);
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof InstanceNotFoundError)
|
||||||
|
throw new APICallerError(error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getMaterials(instanceName) {
|
||||||
|
try {
|
||||||
|
const instance = this.#context.get(instanceName);
|
||||||
|
const structureMaterials = instance.getActiveMaterials();
|
||||||
|
return structureMaterials.allMaterials;
|
||||||
|
} catch(error) {
|
||||||
|
if (error instanceof InstanceNotFoundError)
|
||||||
|
throw new APICallerError(error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -20,4 +20,21 @@ export class Builder {
|
|||||||
isFlexibleInstanceMoving() {
|
isFlexibleInstanceMoving() {
|
||||||
return this.flexibleInstanceMovement !== void 0;
|
return this.flexibleInstanceMovement !== void 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
asPacket() {
|
||||||
|
return {
|
||||||
|
playerId: this.playerId,
|
||||||
|
easyPlace: this.isOptionEnabled('easyPlace'),
|
||||||
|
fastEasyPlace: this.isOptionEnabled('fastEasyPlace'),
|
||||||
|
materialGrabber: this.isOptionEnabled('materialGrabber'),
|
||||||
|
materialInstanceName: this.materialInstanceName
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
setOptions(builderOptions) {
|
||||||
|
this.setOption('easyPlace', builderOptions.easyPlace);
|
||||||
|
this.setOption('fastEasyPlace', builderOptions.fastEasyPlace);
|
||||||
|
this.setOption('materialGrabber', builderOptions.materialGrabber);
|
||||||
|
this.materialInstanceName = builderOptions.materialInstanceName;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,29 +1,37 @@
|
|||||||
import { world } from "@minecraft/server";
|
import { world } from "@minecraft/server";
|
||||||
import { Builder } from "./Builder";
|
import { Builder } from "./Builder";
|
||||||
|
import { BuilderNotFoundError } from "../Errors/BuilderNotFoundError";
|
||||||
|
|
||||||
export class Builders {
|
export class Builders {
|
||||||
static builders = {};
|
static builders = {};
|
||||||
|
|
||||||
static add(playerId) {
|
static add(playerId) {
|
||||||
if (this.builders[playerId])
|
if (Builders.builders[playerId])
|
||||||
return;
|
return;
|
||||||
this.builders[playerId] = new Builder(playerId);
|
Builders.builders[playerId] = new Builder(playerId);
|
||||||
}
|
}
|
||||||
|
|
||||||
static remove(playerId) {
|
static remove(playerId) {
|
||||||
delete this.builders[playerId];
|
delete Builders.builders[playerId];
|
||||||
}
|
}
|
||||||
|
|
||||||
static get(id) {
|
static get(id) {
|
||||||
return this.builders[id];
|
const builder = Builders.builders[id];
|
||||||
|
if (builder === void 0)
|
||||||
|
throw new BuilderNotFoundError(id);
|
||||||
|
return builder;
|
||||||
}
|
}
|
||||||
|
|
||||||
static onJoin(playerId) {
|
static onJoin(playerId) {
|
||||||
this.add(playerId);
|
Builders.add(playerId);
|
||||||
}
|
}
|
||||||
|
|
||||||
static onLeave(playerId) {
|
static onLeave(playerId) {
|
||||||
this.remove(playerId);
|
Builders.remove(playerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
static getIds() {
|
||||||
|
return Object.keys(Builders.builders);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { CommandOrigin } from "./CommandOrigin";
|
||||||
|
import { FeedbackMessageType } from "./FeedbackMessageType";
|
||||||
|
|
||||||
|
export class BlockCommandOrigin extends CommandOrigin {
|
||||||
|
getSource() {
|
||||||
|
return this.source.sourceBlock;
|
||||||
|
}
|
||||||
|
|
||||||
|
sendMessage() {
|
||||||
|
return FeedbackMessageType.None;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
import { CustomCommandParamType, CustomCommandSource, CustomCommandStatus, Player, RawMessageError, system } from "@minecraft/server";
|
||||||
|
import { Commands } from "./Commands.js";
|
||||||
|
import { BlockCommandOrigin } from "./BlockCommandOrigin";
|
||||||
|
import { EntityCommandOrigin } from "./EntityCommandOrigin";
|
||||||
|
import { ServerCommandOrigin } from "./ServerCommandOrigin";
|
||||||
|
import { PlayerCommandOrigin } from "./PlayerCommandOrigin";
|
||||||
|
import { PACK_IDENTIFIER } from "../../consts.js";
|
||||||
|
|
||||||
|
export class Command {
|
||||||
|
customCommand;
|
||||||
|
|
||||||
|
static resolveCommandOrigin(origin) {
|
||||||
|
switch (origin.sourceType) {
|
||||||
|
case CustomCommandSource.Block:
|
||||||
|
return new BlockCommandOrigin(origin);
|
||||||
|
case CustomCommandSource.Entity:
|
||||||
|
if (origin.sourceEntity instanceof Player)
|
||||||
|
return new PlayerCommandOrigin(origin);
|
||||||
|
return new EntityCommandOrigin(origin);
|
||||||
|
case CustomCommandSource.Server:
|
||||||
|
return new ServerCommandOrigin(origin);
|
||||||
|
default:
|
||||||
|
throw new Error("Unknown command source: " + origin?.sourceType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(customCommand) {
|
||||||
|
this.customCommand = customCommand;
|
||||||
|
this.#setDefaultArgs();
|
||||||
|
Commands.register(this);
|
||||||
|
system.beforeEvents.startup.subscribe(this.setupForRegistry.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
getName() {
|
||||||
|
return this.customCommand.name.replace(/^[^:]+:/, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
isCheatsRequired() {
|
||||||
|
return this.customCommand.cheatsRequired;
|
||||||
|
}
|
||||||
|
|
||||||
|
setupForRegistry(startupEvent) {
|
||||||
|
this.#registerCommand(startupEvent.customCommandRegistry);
|
||||||
|
system.beforeEvents.startup.unsubscribe(this.setupForRegistry.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
#registerCommand(customCommandRegistry) {
|
||||||
|
this.#addPreCallback();
|
||||||
|
this.#registerEnums(customCommandRegistry);
|
||||||
|
this.#registerSingleCommand(customCommandRegistry);
|
||||||
|
this.#registerAliasCommands(customCommandRegistry);
|
||||||
|
}
|
||||||
|
|
||||||
|
#setDefaultArgs() {
|
||||||
|
if (this.customCommand.cheatsRequired === void 0)
|
||||||
|
this.customCommand.cheatsRequired = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
#addPreCallback() {
|
||||||
|
this.callback = (origin, ...args) => {
|
||||||
|
const source = Command.resolveCommandOrigin(origin);
|
||||||
|
if (this.#commandSourceIsNotAllowed(source))
|
||||||
|
return { status: CustomCommandStatus.Failure, message: 'construct.error.invalidCommandSource' };
|
||||||
|
try {
|
||||||
|
return this.customCommand.callback(source, ...args);
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof RawMessageError)
|
||||||
|
error.sendTo(source);
|
||||||
|
else
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#registerEnums(customCommandRegistry) {
|
||||||
|
if (this.customCommand.enums) {
|
||||||
|
for (const customEnum of this.customCommand.enums)
|
||||||
|
customCommandRegistry.registerEnum(`${PACK_IDENTIFIER}:${customEnum.name}`, customEnum.values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#registerSingleCommand(customCommandRegistry, name = this.customCommand.name) {
|
||||||
|
customCommandRegistry.registerCommand({
|
||||||
|
name: `${PACK_IDENTIFIER}:${name}`,
|
||||||
|
description: this.customCommand.description,
|
||||||
|
permissionLevel: this.customCommand.permissionLevel,
|
||||||
|
mandatoryParameters: this.#prepParameters(this.customCommand.mandatoryParameters),
|
||||||
|
optionalParameters: this.#prepParameters(this.customCommand.optionalParameters),
|
||||||
|
cheatsRequired: this.customCommand.cheatsRequired
|
||||||
|
}, this.callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
#prepParameters(parameters) {
|
||||||
|
if (!parameters)
|
||||||
|
return [];
|
||||||
|
for (const parameter of parameters) {
|
||||||
|
if (parameter.name)
|
||||||
|
parameter.name = `${parameter.name}`;
|
||||||
|
if (parameter.type === CustomCommandParamType.Enum)
|
||||||
|
parameter.name = `${PACK_IDENTIFIER}:${parameter.name}`;
|
||||||
|
}
|
||||||
|
return parameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
#registerAliasCommands(customCommandRegistry) {
|
||||||
|
if (this.customCommand.aliases) {
|
||||||
|
for (const alias of this.customCommand.aliases)
|
||||||
|
this.#registerSingleCommand(customCommandRegistry, alias);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#commandSourceIsNotAllowed(source) {
|
||||||
|
if (!this.customCommand.allowedSources)
|
||||||
|
return false;
|
||||||
|
return !this.customCommand.allowedSources.includes(source.constructor);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import { FeedbackMessageType } from "./FeedbackMessageType";
|
||||||
|
|
||||||
|
export class CommandOrigin {
|
||||||
|
constructor(source) {
|
||||||
|
this.source = source;
|
||||||
|
}
|
||||||
|
|
||||||
|
getType() {
|
||||||
|
return this.source.sourceType;
|
||||||
|
}
|
||||||
|
|
||||||
|
getSource() {
|
||||||
|
throw new Error("getSource() not implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
sendMessage(message) {
|
||||||
|
console.error(`Unknown source type: ${this.source.sourceType}`, message);
|
||||||
|
return FeedbackMessageType.ConsoleError;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
export class Commands {
|
||||||
|
static #commands = [];
|
||||||
|
|
||||||
|
static register(command) {
|
||||||
|
this.#commands.push(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
static getAll() {
|
||||||
|
return [...this.#commands];
|
||||||
|
}
|
||||||
|
|
||||||
|
static clear() {
|
||||||
|
this.#commands = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { CommandOrigin } from "./CommandOrigin";
|
||||||
|
import { FeedbackMessageType } from "./FeedbackMessageType";
|
||||||
|
|
||||||
|
export class EntityCommandOrigin extends CommandOrigin {
|
||||||
|
getSource() {
|
||||||
|
return this.source.sourceEntity;
|
||||||
|
}
|
||||||
|
|
||||||
|
sendMessage(message) {
|
||||||
|
this.getSource().sendMessage(message);
|
||||||
|
return FeedbackMessageType.ChatMessage;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
export const FeedbackMessageType = Object.freeze({
|
||||||
|
None: "none",
|
||||||
|
ConsoleInfo: "info",
|
||||||
|
ConsoleWarn: "warn",
|
||||||
|
ConsoleError: "error",
|
||||||
|
ChatMessage: "message"
|
||||||
|
});
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { CommandOrigin } from "./CommandOrigin";
|
||||||
|
import { FeedbackMessageType } from "./FeedbackMessageType";
|
||||||
|
|
||||||
|
export class PlayerCommandOrigin extends CommandOrigin {
|
||||||
|
getType() {
|
||||||
|
return "Player";
|
||||||
|
}
|
||||||
|
|
||||||
|
getSource() {
|
||||||
|
return this.source.sourceEntity;
|
||||||
|
}
|
||||||
|
|
||||||
|
sendMessage(message) {
|
||||||
|
this.getSource().sendMessage(message);
|
||||||
|
return FeedbackMessageType.ChatMessage;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { CommandOrigin } from "./CommandOrigin";
|
||||||
|
import { FeedbackMessageType } from "./FeedbackMessageType";
|
||||||
|
|
||||||
|
export class ServerCommandOrigin extends CommandOrigin {
|
||||||
|
getSource() {
|
||||||
|
return this.source.sourceType;
|
||||||
|
}
|
||||||
|
|
||||||
|
sendMessage(message) {
|
||||||
|
console.log(message);
|
||||||
|
return FeedbackMessageType.ConsoleInfo;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
export class BuilderNotFoundError extends Error {
|
||||||
|
constructor(builderId) {
|
||||||
|
super(`§cBuilder "${builderId}" not found.`);
|
||||||
|
this.name = 'BuilderNotFoundError';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
export class CommandResponseError extends Error {
|
||||||
|
constructor(message) {
|
||||||
|
super(message);
|
||||||
|
this.name = 'CommandResponseError';
|
||||||
|
}
|
||||||
|
|
||||||
|
getRawMessage() {
|
||||||
|
throw new Error('getRawMessage() must be implemented by subclasses of CommandResponseError');
|
||||||
|
}
|
||||||
|
|
||||||
|
sendTo(origin) {
|
||||||
|
origin.sendMessage(this.getRawMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { CommandResponseError } from "./CommandResponseError";
|
||||||
|
|
||||||
|
export class InstanceExistsError extends CommandResponseError {
|
||||||
|
instanceName;
|
||||||
|
|
||||||
|
constructor(instanceName) {
|
||||||
|
super(`An instance with the name "${instanceName}" already exists.`);
|
||||||
|
this.name = 'InstanceExistsError';
|
||||||
|
this.instanceName = instanceName;
|
||||||
|
}
|
||||||
|
|
||||||
|
getRawMessage() {
|
||||||
|
return { translate: 'construct.error.instanceExists', with: [this.instanceName] };
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { CommandResponseError } from "./CommandResponseError";
|
||||||
|
|
||||||
|
export class InstanceNotFoundError extends CommandResponseError {
|
||||||
|
instanceName;
|
||||||
|
|
||||||
|
constructor(instanceName) {
|
||||||
|
super(`§cInstance "${instanceName}" not found.`);
|
||||||
|
this.name = 'InstanceNotFoundError';
|
||||||
|
this.instanceName = instanceName;
|
||||||
|
}
|
||||||
|
|
||||||
|
getRawMessage() {
|
||||||
|
return { translate: 'construct.error.instanceNotFound', with: [this.instanceName] };
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
export class InvalidInstanceError extends Error {
|
|
||||||
constructor(message) {
|
|
||||||
super(message);
|
|
||||||
this.name = 'InvalidInstanceError';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
export class InvalidStructureError extends Error {
|
|
||||||
constructor(message) {
|
|
||||||
super(message);
|
|
||||||
this.name = 'InvalidStructureError';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { CommandResponseError } from "./CommandResponseError";
|
||||||
|
|
||||||
|
export class NotAPlayerError extends CommandResponseError {
|
||||||
|
constructor(message = 'Command requires a player source.') {
|
||||||
|
super(message);
|
||||||
|
this.name = 'NotAPlayerError';
|
||||||
|
}
|
||||||
|
|
||||||
|
getRawMessage() {
|
||||||
|
return { translate: 'construct.commands.error.notAPlayer' };
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { CommandResponseError } from "./CommandResponseError";
|
||||||
|
|
||||||
|
export class StructureNotFoundError extends CommandResponseError {
|
||||||
|
structureId;
|
||||||
|
|
||||||
|
constructor(structureId) {
|
||||||
|
super(`Structure with ID "${structureId}" not found.`);
|
||||||
|
this.name = 'StructureNotFoundError';
|
||||||
|
this.structureId = structureId;
|
||||||
|
}
|
||||||
|
|
||||||
|
getRawMessage() {
|
||||||
|
return { translate: 'construct.error.structureNotFound', with: [this.structureId] };
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { InputPermissionCategory, world, system } from "@minecraft/server";
|
import { InputPermissionCategory, world, system } from "@minecraft/server";
|
||||||
import { Outliner } from "../Outliner";
|
import { Outliner } from "../Outliner";
|
||||||
import { MENU_ITEM } from "../../commands/construct";
|
import { MENU_ITEM } from "../../consts";
|
||||||
import { Vector } from "../../lib/Vector";
|
import { Vector } from "../../lib/Vector";
|
||||||
import { PlayerMovement } from "../PlayerMovement";
|
import { PlayerMovement } from "../PlayerMovement";
|
||||||
import { Builders } from "../Builder/Builders";
|
import { Builders } from "../Builder/Builders";
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ export class InstanceOptions extends Option {
|
|||||||
this.instanceName = instanceName;
|
this.instanceName = instanceName;
|
||||||
this.structureId = structureId;
|
this.structureId = structureId;
|
||||||
this.load();
|
this.load();
|
||||||
|
this.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
save() {
|
save() {
|
||||||
@@ -45,13 +46,8 @@ export class InstanceOptions extends Option {
|
|||||||
return world.getDimension(this.dimensionId);
|
return world.getDimension(this.dimensionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
enable() {
|
setEnabled(enable) {
|
||||||
this.isEnabled = true;
|
this.isEnabled = enable;
|
||||||
this.save();
|
|
||||||
}
|
|
||||||
|
|
||||||
disable() {
|
|
||||||
this.isEnabled = false;
|
|
||||||
this.save();
|
this.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,7 +64,7 @@ export class InstanceOptions extends Option {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setLayer(layer) {
|
setLayer(layer) {
|
||||||
this.currentLayer = layer;
|
this.currentLayer = Math.floor(layer);
|
||||||
this.save();
|
this.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,4 +77,9 @@ export class InstanceOptions extends Option {
|
|||||||
this.verifier.trackPlayerDistance = distance;
|
this.verifier.trackPlayerDistance = distance;
|
||||||
this.save();
|
this.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setVerifierParticleLifetime(lifetime) {
|
||||||
|
this.verifier.particleLifetime = lifetime;
|
||||||
|
this.save();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -7,6 +7,8 @@ import { world, system, TicksPerSecond } from "@minecraft/server";
|
|||||||
import { InstanceNotPlacedError } from "../Errors/InstanceNotPlacedError";
|
import { InstanceNotPlacedError } from "../Errors/InstanceNotPlacedError";
|
||||||
import { StructureMaterials } from "../Materials/StructureMaterials";
|
import { StructureMaterials } from "../Materials/StructureMaterials";
|
||||||
import { VerificationRenderer } from "../Render/VerificationRenderer";
|
import { VerificationRenderer } from "../Render/VerificationRenderer";
|
||||||
|
import { structureCollection } from "../Structure/StructureCollection";
|
||||||
|
import { InstanceExistsError } from "../Errors/InstanceExistsError";
|
||||||
|
|
||||||
export class StructureInstance {
|
export class StructureInstance {
|
||||||
options;
|
options;
|
||||||
@@ -185,19 +187,26 @@ export class StructureInstance {
|
|||||||
}
|
}
|
||||||
|
|
||||||
enable() {
|
enable() {
|
||||||
this.options.enable();
|
this.options.setEnabled(true);
|
||||||
this.refreshBox();
|
this.refreshBox();
|
||||||
}
|
}
|
||||||
|
|
||||||
disable() {
|
disable() {
|
||||||
this.options.disable();
|
this.options.setEnabled(false);
|
||||||
this.refreshBox();
|
this.refreshBox();
|
||||||
}
|
}
|
||||||
|
|
||||||
rename(newName) {
|
rename(newName) {
|
||||||
|
if (structureCollection.has(newName))
|
||||||
|
throw new InstanceExistsError(newName);
|
||||||
this.options.rename(newName);
|
this.options.rename(newName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setStructure(structureId) {
|
||||||
|
this.options.structureId = structureId;
|
||||||
|
this.structure = new Structure(structureId);
|
||||||
|
}
|
||||||
|
|
||||||
place(dimensionId, worldLocation) {
|
place(dimensionId, worldLocation) {
|
||||||
this.enable();
|
this.enable();
|
||||||
this.move(dimensionId, worldLocation);
|
this.move(dimensionId, worldLocation);
|
||||||
@@ -277,4 +286,38 @@ export class StructureInstance {
|
|||||||
toStructureCoords(worldLocation) {
|
toStructureCoords(worldLocation) {
|
||||||
return Vector.from(worldLocation).subtract(this.options.worldLocation);
|
return Vector.from(worldLocation).subtract(this.options.worldLocation);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
asPacket() {
|
||||||
|
const dimensionLocation = this.getLocation();
|
||||||
|
return {
|
||||||
|
name: this.getName(),
|
||||||
|
structureId: this.getStructureId(),
|
||||||
|
isEnabled: this.isEnabled(),
|
||||||
|
dimensionId: dimensionLocation.dimensionId,
|
||||||
|
location: { x: dimensionLocation.location.x, y: dimensionLocation.location.y, z: dimensionLocation.location.z },
|
||||||
|
bounds: this.getBounds(),
|
||||||
|
currentLayer: this.getLayer(),
|
||||||
|
maxLayer: this.getMaxLayer(),
|
||||||
|
verifier: {
|
||||||
|
isEnabled: this.options.verifier.isEnabled,
|
||||||
|
trackPlayerDistance: this.options.verifier.trackPlayerDistance,
|
||||||
|
particleLifetime: this.options.verifier.particleLifetime
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
setOptions(newOptions) {
|
||||||
|
const newVerifierOptions = newOptions.verifier;
|
||||||
|
if (newOptions.name !== this.getName())
|
||||||
|
structureCollection.rename(this.getName(), newOptions.name);
|
||||||
|
this.setStructure(newOptions.structureId);
|
||||||
|
this.options.setEnabled(newOptions.isEnabled);
|
||||||
|
this.options.move(newOptions.dimensionId, newOptions.location);
|
||||||
|
this.options.setLayer(newOptions.currentLayer);
|
||||||
|
this.options.setVerifierEnabled(newVerifierOptions.isEnabled);
|
||||||
|
this.options.setVerifierDistance(newVerifierOptions.trackPlayerDistance);
|
||||||
|
this.options.setVerifierParticleLifetime(newVerifierOptions.particleLifetime);
|
||||||
|
this.options.save();
|
||||||
|
this.refreshBox();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { ItemStack, system } from "@minecraft/server";
|
import { ItemStack, system } from "@minecraft/server";
|
||||||
import { Vector } from "../../lib/Vector";
|
import { Vector } from "../../lib/Vector";
|
||||||
|
import { InstanceNotPlacedError } from "../Errors/InstanceNotPlacedError";
|
||||||
|
|
||||||
class StructureMaterials {
|
class StructureMaterials {
|
||||||
instance;
|
instance;
|
||||||
@@ -21,14 +22,18 @@ class StructureMaterials {
|
|||||||
system.runJob(this.populateActive());
|
system.runJob(this.populateActive());
|
||||||
else
|
else
|
||||||
system.runJob(this.populateAll());
|
system.runJob(this.populateAll());
|
||||||
} catch (e) {
|
} catch (error) {
|
||||||
if (e.name === 'InstanceNotPlacedError')
|
if (error instanceof InstanceNotPlacedError)
|
||||||
this.clear();
|
this.clear();
|
||||||
else
|
else
|
||||||
throw e;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get materials() {
|
||||||
|
return this.materials;
|
||||||
|
}
|
||||||
|
|
||||||
get(itemType) {
|
get(itemType) {
|
||||||
return this.materials[itemType];
|
return this.materials[itemType];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import { structureCollection } from './Structure/StructureCollection';
|
|||||||
import { MenuFormBuilder } from './MenuFormBuilder';
|
import { MenuFormBuilder } from './MenuFormBuilder';
|
||||||
import { InstanceForm } from './Instance/InstanceForm';
|
import { InstanceForm } from './Instance/InstanceForm';
|
||||||
import { BuilderForm } from './Builder/BuilderForm';
|
import { BuilderForm } from './Builder/BuilderForm';
|
||||||
|
import { InstanceExistsError } from './Errors/InstanceExistsError';
|
||||||
|
import { StructureNotFoundError } from './Errors/StructureNotFoundError';
|
||||||
|
|
||||||
export class MenuForm {
|
export class MenuForm {
|
||||||
constructor(player, { jumpToInstance = false, instanceName = void 0 } = {}) {
|
constructor(player, { jumpToInstance = false, instanceName = void 0 } = {}) {
|
||||||
@@ -43,12 +45,12 @@ export class MenuForm {
|
|||||||
return selectedInstanceName || this.createNewInstance();
|
return selectedInstanceName || this.createNewInstance();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (error) {
|
||||||
if (e.message === 'Menu timed out.') {
|
if (error.message === 'Menu timed out.') {
|
||||||
this.player.sendMessage({ translate: 'construct.menu.open.timeout' });
|
this.player.sendMessage({ translate: 'construct.menu.open.timeout' });
|
||||||
return void 0;
|
return void 0;
|
||||||
}
|
}
|
||||||
throw e;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,16 +66,12 @@ export class MenuForm {
|
|||||||
return void 0;
|
return void 0;
|
||||||
try {
|
try {
|
||||||
structureCollection.add(instanceName, structureId);
|
structureCollection.add(instanceName, structureId);
|
||||||
} catch (e) {
|
} catch (error) {
|
||||||
if (e.name === 'InvalidInstanceError') {
|
if (error instanceof InstanceExistsError || error instanceof StructureNotFoundError) {
|
||||||
this.player.sendMessage({ translate: 'construct.mainmenu.instance.exists', with: [instanceName] });
|
error.sendTo(this.player);
|
||||||
return void 0;
|
return void 0;
|
||||||
}
|
}
|
||||||
if (e.name === 'InvalidStructureError') {
|
throw error;
|
||||||
this.player.sendMessage({ translate: 'construct.mainmenu.instance.notfound', with: [structureId] });
|
|
||||||
return void 0;
|
|
||||||
}
|
|
||||||
throw e;
|
|
||||||
}
|
}
|
||||||
return instanceName;
|
return instanceName;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import { world, system } from '@minecraft/server';
|
||||||
|
import { MENU_ITEM } from '../consts';
|
||||||
|
import { MenuForm } from './MenuForm';
|
||||||
|
import { structureCollection } from './Structure/StructureCollection';
|
||||||
|
import { Builders } from './Builder/Builders';
|
||||||
|
|
||||||
|
world.beforeEvents.itemUse.subscribe((event) => {
|
||||||
|
if (!event.source || event.itemStack?.typeId !== MENU_ITEM) return;
|
||||||
|
event.cancel = true;
|
||||||
|
const builder = Builders.get(event.source.id);
|
||||||
|
system.run(() => {
|
||||||
|
if (builder.isFlexibleInstanceMoving())
|
||||||
|
return;
|
||||||
|
openMenu(event.source, event);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function openMenu(player, event = void 0) {
|
||||||
|
const options = { jumpToInstance: true };
|
||||||
|
if (event) {
|
||||||
|
const instanceNames = structureCollection.getInstanceNames();
|
||||||
|
const instanceName = event.itemStack?.nameTag;
|
||||||
|
if (instanceNames.includes(instanceName))
|
||||||
|
options.instanceName = instanceName;
|
||||||
|
}
|
||||||
|
new MenuForm(player, options);
|
||||||
|
}
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { StructureNotFoundError } from '../Errors/StructureNotFoundError';
|
||||||
import { Outliner } from '../Outliner';
|
import { Outliner } from '../Outliner';
|
||||||
|
|
||||||
export class StructureOutliner {
|
export class StructureOutliner {
|
||||||
@@ -13,11 +14,11 @@ export class StructureOutliner {
|
|||||||
this.bounds = this.instance.getBounds();
|
this.bounds = this.instance.getBounds();
|
||||||
this.bounds.min = this.instance.toGlobalCoords(this.bounds.min);
|
this.bounds.min = this.instance.toGlobalCoords(this.bounds.min);
|
||||||
this.bounds.max = this.instance.toGlobalCoords(this.bounds.max);
|
this.bounds.max = this.instance.toGlobalCoords(this.bounds.max);
|
||||||
} catch (e) {
|
} catch (error) {
|
||||||
if (e.name === 'InvalidStructureError')
|
if (error instanceof StructureNotFoundError)
|
||||||
this.outliner.stopDraw();
|
this.outliner.stopDraw();
|
||||||
else
|
else
|
||||||
throw e;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { world } from "@minecraft/server";
|
import { world } from "@minecraft/server";
|
||||||
import { Vector } from "../../lib/Vector";
|
import { Vector } from "../../lib/Vector";
|
||||||
import { InvalidStructureError } from "../Errors/InvalidStructureError";
|
import { StructureNotFoundError } from "../Errors/StructureNotFoundError";
|
||||||
|
|
||||||
export class Structure {
|
export class Structure {
|
||||||
structureId;
|
structureId;
|
||||||
@@ -10,7 +10,7 @@ export class Structure {
|
|||||||
this.structureId = structureId;
|
this.structureId = structureId;
|
||||||
this.#structure = world.structureManager.get(structureId);
|
this.#structure = world.structureManager.get(structureId);
|
||||||
if (!this.#structure)
|
if (!this.#structure)
|
||||||
throw new InvalidStructureError(`[Construct] Structure '${structureId}' not found on world.`);
|
throw new StructureNotFoundError(structureId);
|
||||||
this.#structure.saveToWorld();
|
this.#structure.saveToWorld();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import { InvalidInstanceError } from '../Errors/InvalidInstanceError';
|
import { InstanceExistsError } from '../Errors/InstanceExistsError';
|
||||||
|
import { InstanceNotFoundError } from '../Errors/InstanceNotFoundError';
|
||||||
|
import { StructureNotFoundError } from '../Errors/StructureNotFoundError';
|
||||||
import { InstanceOptions } from '../Instance/InstanceOptions';
|
import { InstanceOptions } from '../Instance/InstanceOptions';
|
||||||
import { StructureInstance } from '../Instance/StructureInstance';
|
import { StructureInstance } from '../Instance/StructureInstance';
|
||||||
import { world } from '@minecraft/server';
|
import { InvalidStructureError, world } from '@minecraft/server';
|
||||||
|
|
||||||
class StructureCollection {
|
class StructureCollection {
|
||||||
structures;
|
structures;
|
||||||
@@ -27,7 +29,7 @@ class StructureCollection {
|
|||||||
|
|
||||||
add(instanceName, structureId) {
|
add(instanceName, structureId) {
|
||||||
if (this.structures[instanceName])
|
if (this.structures[instanceName])
|
||||||
throw new InvalidInstanceError(`Instance ${instanceName} already exists.`);
|
throw new InstanceExistsError(instanceName);
|
||||||
const structure = new StructureInstance(instanceName, structureId);
|
const structure = new StructureInstance(instanceName, structureId);
|
||||||
this.structures[instanceName] = structure;
|
this.structures[instanceName] = structure;
|
||||||
return structure;
|
return structure;
|
||||||
@@ -36,7 +38,7 @@ class StructureCollection {
|
|||||||
get(instanceName) {
|
get(instanceName) {
|
||||||
const structure = this.structures[instanceName];
|
const structure = this.structures[instanceName];
|
||||||
if (!structure)
|
if (!structure)
|
||||||
throw new InvalidInstanceError(`Instance ${instanceName} not found.`);
|
throw new InstanceNotFoundError(instanceName);
|
||||||
return structure;
|
return structure;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,12 +60,12 @@ class StructureCollection {
|
|||||||
return Object.values(this.structures).filter(structure => {
|
return Object.values(this.structures).filter(structure => {
|
||||||
try {
|
try {
|
||||||
return structure.isLocationActive(dimensionId, structure.toStructureCoords(location), options)
|
return structure.isLocationActive(dimensionId, structure.toStructureCoords(location), options)
|
||||||
} catch (e) {
|
} catch (error) {
|
||||||
if (e.name === 'InvalidStructureError') {
|
if (error instanceof StructureNotFoundError || error instanceof InvalidStructureError) {
|
||||||
structureCollection.delete(structure.name);
|
this.delete(structure.name);
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
throw e;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -101,7 +103,7 @@ class StructureCollection {
|
|||||||
rename(instanceName, newName) {
|
rename(instanceName, newName) {
|
||||||
const structure = this.get(instanceName);
|
const structure = this.get(instanceName);
|
||||||
if (this.structures[newName])
|
if (this.structures[newName])
|
||||||
throw new Error(`Instance '${newName}' already exists.`);
|
throw new InstanceExistsError(newName);
|
||||||
structure.rename(newName);
|
structure.rename(newName);
|
||||||
this.structures[newName] = structure;
|
this.structures[newName] = structure;
|
||||||
delete this.structures[instanceName];
|
delete this.structures[instanceName];
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import { CommandPermissionLevel, CustomCommandParamType, CustomCommandStatus, system } from '@minecraft/server';
|
||||||
|
import { Command } from '../classes/Commands/Command';
|
||||||
|
import { PlayerCommandOrigin } from '../classes/Commands/PlayerCommandOrigin';
|
||||||
|
import { BuilderOptions } from '../classes/Builder/BuilderOptions';
|
||||||
|
|
||||||
|
export class BuilderCommand extends Command {
|
||||||
|
constructor() {
|
||||||
|
super({
|
||||||
|
name: 'builder',
|
||||||
|
description: 'construct.commands.builder',
|
||||||
|
allowedSources: [PlayerCommandOrigin],
|
||||||
|
mandatoryParameters: [
|
||||||
|
{ name: 'builderOption', type: CustomCommandParamType.Enum },
|
||||||
|
{ name: 'state', type: CustomCommandParamType.Boolean }
|
||||||
|
],
|
||||||
|
enums: [
|
||||||
|
{ name: 'builderOption', values: ['easyPlace', 'fastEasyPlace', 'materialGrabber'] }
|
||||||
|
],
|
||||||
|
permissionLevel: CommandPermissionLevel.Any,
|
||||||
|
callback: (origin, builderOption, state) => this.run(origin, builderOption, state)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
run(origin, builderOption, state) {
|
||||||
|
if (!BuilderOptions.get(builderOption)) {
|
||||||
|
origin.sendMessage({ translate: 'construct.commands.builder.unknownOption', with: [builderOption] });
|
||||||
|
return void 0;
|
||||||
|
}
|
||||||
|
system.run(() => {
|
||||||
|
const player = origin.getSource();
|
||||||
|
BuilderOptions.setValue(builderOption, player.id, state);
|
||||||
|
origin.sendMessage({ translate: 'construct.commands.builder.success', with: [builderOption, String(state)] });
|
||||||
|
});
|
||||||
|
return { status: CustomCommandStatus.Success };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const builderCommand = new BuilderCommand();
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import { CommandPermissionLevel, CustomCommandStatus, EntityComponentTypes, ItemStack, system } from '@minecraft/server';
|
||||||
|
import { Command } from '../classes/Commands/Command';
|
||||||
|
import { PlayerCommandOrigin } from '../classes/Commands/PlayerCommandOrigin';
|
||||||
|
import { MENU_ITEM } from '../consts';
|
||||||
|
|
||||||
|
export class ConstructCommand extends Command {
|
||||||
|
constructor() {
|
||||||
|
super({
|
||||||
|
name: 'construct',
|
||||||
|
description: 'construct.commands.construct',
|
||||||
|
cheatsRequired: false,
|
||||||
|
allowedSources: [PlayerCommandOrigin],
|
||||||
|
permissionLevel: CommandPermissionLevel.Any,
|
||||||
|
callback: (origin) => this.run(origin)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
run(origin) {
|
||||||
|
const player = origin.getSource();
|
||||||
|
system.run(() => {
|
||||||
|
this.giveMenuItem(player);
|
||||||
|
});
|
||||||
|
return { status: CustomCommandStatus.Success };
|
||||||
|
}
|
||||||
|
|
||||||
|
giveMenuItem(player) {
|
||||||
|
const inventoryComponent = player.getComponent(EntityComponentTypes.Inventory);
|
||||||
|
const inventoryContainer = inventoryComponent?.container;
|
||||||
|
const remaining = inventoryContainer?.addItem(new ItemStack(MENU_ITEM));
|
||||||
|
if (remaining)
|
||||||
|
player.sendMessage({ translate: 'construct.commands.construct.fail' });
|
||||||
|
else
|
||||||
|
player.sendMessage({ translate: 'construct.commands.construct.success' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const constructCommand = new ConstructCommand();
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import { Command } from '../classes/Commands/Command';
|
||||||
|
import { CommandPermissionLevel, CustomCommandParamType, CustomCommandStatus, system } from '@minecraft/server';
|
||||||
|
import { structureCollection } from '../classes/Structure/StructureCollection';
|
||||||
|
import { InstanceExistsError } from '../classes/Errors/InstanceExistsError';
|
||||||
|
import { StructureNotFoundError } from '../classes/Errors/StructureNotFoundError';
|
||||||
|
|
||||||
|
export class CreateCommand extends Command {
|
||||||
|
constructor() {
|
||||||
|
super({
|
||||||
|
name: 'create',
|
||||||
|
description: 'construct.commands.create',
|
||||||
|
mandatoryParameters: [
|
||||||
|
{ name: 'instanceName', type: CustomCommandParamType.String },
|
||||||
|
{ name: 'structureId', type: CustomCommandParamType.String }
|
||||||
|
],
|
||||||
|
permissionLevel: CommandPermissionLevel.Any,
|
||||||
|
callback: (origin, instanceName, structureId) => this.run(origin, instanceName, structureId)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
run(origin, instanceName, structureId) {
|
||||||
|
this.tryAddStructure(origin, instanceName, structureId);
|
||||||
|
return { status: CustomCommandStatus.Success };
|
||||||
|
}
|
||||||
|
|
||||||
|
tryAddStructure(origin, instanceName, structureId) {
|
||||||
|
system.run(() => {
|
||||||
|
try {
|
||||||
|
this.addStructure(origin, instanceName, structureId);
|
||||||
|
} catch (error) {
|
||||||
|
this.handleStructureAdditionErrors(origin, error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
addStructure(origin, instanceName, structureId) {
|
||||||
|
structureCollection.add(instanceName, structureId);
|
||||||
|
origin.sendMessage({ translate: 'construct.commands.create.success', with: [instanceName, structureId] });
|
||||||
|
}
|
||||||
|
|
||||||
|
handleStructureAdditionErrors(origin, error) {
|
||||||
|
if (error instanceof InstanceExistsError || error instanceof StructureNotFoundError)
|
||||||
|
error.sendTo(origin);
|
||||||
|
else
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createCommand = new CreateCommand();
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import { Command } from '../classes/Commands/Command';
|
||||||
|
import { CustomCommandParamType, CustomCommandStatus, CommandPermissionLevel, system } from '@minecraft/server';
|
||||||
|
import { structureCollection } from '../classes/Structure/StructureCollection';
|
||||||
|
|
||||||
|
export class DeleteCommand extends Command {
|
||||||
|
constructor() {
|
||||||
|
super({
|
||||||
|
name: 'delete',
|
||||||
|
description: 'construct.commands.delete',
|
||||||
|
mandatoryParameters: [
|
||||||
|
{ name: 'instanceName', type: CustomCommandParamType.String }
|
||||||
|
],
|
||||||
|
permissionLevel: CommandPermissionLevel.Any,
|
||||||
|
callback: (origin, instanceName) => this.run(origin, instanceName)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
run(origin, instanceName) {
|
||||||
|
const instance = structureCollection.get(instanceName);
|
||||||
|
system.run(() => {
|
||||||
|
structureCollection.delete(instanceName);
|
||||||
|
origin.sendMessage({ translate: 'construct.commands.delete.success', with: [instanceName] });
|
||||||
|
});
|
||||||
|
return { status: CustomCommandStatus.Success };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const deleteCommand = new DeleteCommand();
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import { Command } from '../classes/Commands/Command';
|
||||||
|
import { CustomCommandParamType, CustomCommandStatus, CommandPermissionLevel, system } from '@minecraft/server';
|
||||||
|
import { structureCollection } from '../classes/Structure/StructureCollection';
|
||||||
|
|
||||||
|
export class EnableCommand extends Command {
|
||||||
|
constructor() {
|
||||||
|
super({
|
||||||
|
name: 'enable',
|
||||||
|
description: 'construct.commands.enable',
|
||||||
|
mandatoryParameters: [
|
||||||
|
{ name: 'instanceName', type: CustomCommandParamType.String },
|
||||||
|
{ name: 'state', type: CustomCommandParamType.Boolean }
|
||||||
|
],
|
||||||
|
permissionLevel: CommandPermissionLevel.Any,
|
||||||
|
callback: (origin, instanceName, state) => this.run(origin, instanceName, state)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
run(origin, instanceName, state) {
|
||||||
|
const instance = structureCollection.get(instanceName);
|
||||||
|
if (state && !instance.hasLocation()) {
|
||||||
|
origin.sendMessage({ translate: 'construct.commands.error.noLocation', with: [instanceName] });
|
||||||
|
return void 0;
|
||||||
|
}
|
||||||
|
system.run(() => {
|
||||||
|
if (state)
|
||||||
|
instance.enable();
|
||||||
|
else
|
||||||
|
instance.disable();
|
||||||
|
this.sendFeedback(origin, instanceName, state);
|
||||||
|
});
|
||||||
|
return { status: CustomCommandStatus.Success };
|
||||||
|
}
|
||||||
|
|
||||||
|
sendFeedback(origin, instanceName, state) {
|
||||||
|
origin.sendMessage({ translate: state ? 'construct.commands.enable.true' : 'construct.commands.enable.false', with: [instanceName] });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const enableCommand = new EnableCommand();
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
import { CommandPermissionLevel, CustomCommandParamType, CustomCommandStatus, system } from '@minecraft/server';
|
||||||
|
import { Command } from '../classes/Commands/Command';
|
||||||
|
import { structureCollection } from '../classes/Structure/StructureCollection';
|
||||||
|
import { Vector } from '../lib/Vector';
|
||||||
|
|
||||||
|
export class InstanceInfoCommand extends Command {
|
||||||
|
constructor() {
|
||||||
|
super({
|
||||||
|
name: 'instanceinfo',
|
||||||
|
description: 'construct.commands.instanceinfo',
|
||||||
|
mandatoryParameters: [
|
||||||
|
{ name: 'instanceName', type: CustomCommandParamType.String }
|
||||||
|
],
|
||||||
|
permissionLevel: CommandPermissionLevel.Any,
|
||||||
|
callback: (origin, instanceName) => this.run(origin, instanceName)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
run(origin, instanceName) {
|
||||||
|
const instance = structureCollection.get(instanceName);
|
||||||
|
const message = { rawtext: [
|
||||||
|
this.getHeaderText(instance),
|
||||||
|
{ text: '\n' },
|
||||||
|
this.getStructureIdText(instance),
|
||||||
|
{ text: '\n' },
|
||||||
|
this.getLocationText(instance),
|
||||||
|
{ text: '\n' },
|
||||||
|
this.getEnabledText(instance),
|
||||||
|
{ text: '\n' },
|
||||||
|
this.getLayerText(instance),
|
||||||
|
{ text: '\n' },
|
||||||
|
this.getVerifierText(instance),
|
||||||
|
{ text: '\n' },
|
||||||
|
this.getSizeText(instance)
|
||||||
|
]};
|
||||||
|
origin.sendMessage(message);
|
||||||
|
return { status: CustomCommandStatus.Success };
|
||||||
|
}
|
||||||
|
|
||||||
|
getHeaderText(instance) {
|
||||||
|
return { translate: 'construct.commands.instanceinfo.header', with: [instance.getName()] };
|
||||||
|
}
|
||||||
|
|
||||||
|
getStructureIdText(instance) {
|
||||||
|
return { translate: 'construct.commands.instanceinfo.structure', with: [instance.getStructureId()] };
|
||||||
|
}
|
||||||
|
|
||||||
|
getLocationText(instance) {
|
||||||
|
if (!instance.hasLocation())
|
||||||
|
return { translate: 'construct.commands.instanceinfo.noLocation' };
|
||||||
|
const { dimensionId, location } = instance.getLocation();
|
||||||
|
return { translate: 'construct.commands.instanceinfo.location', with: [location.toString(), dimensionId.replace('minecraft:', '')] };
|
||||||
|
}
|
||||||
|
|
||||||
|
getEnabledText(instance) {
|
||||||
|
return { translate: 'construct.commands.instanceinfo.enabled', with: [String(instance.isEnabled())] };
|
||||||
|
}
|
||||||
|
|
||||||
|
getLayerText(instance) {
|
||||||
|
return { translate: 'construct.commands.instanceinfo.layer', with: [String(instance.getLayer()), String(instance.getMaxLayer())] };
|
||||||
|
}
|
||||||
|
|
||||||
|
getVerifierText(instance) {
|
||||||
|
const verifier = instance.options.verifier;
|
||||||
|
return { translate: 'construct.commands.instanceinfo.verifier', with: [String(verifier.isEnabled)] };
|
||||||
|
}
|
||||||
|
|
||||||
|
getSizeText(instance) {
|
||||||
|
const bounds = instance.getBounds();
|
||||||
|
return { translate: 'construct.commands.instanceinfo.size', with: [bounds.max.toString(), Vector.volume(bounds.min, bounds.max).toString()] };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const instanceInfoCommand = new InstanceInfoCommand();
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import { CommandPermissionLevel, CustomCommandStatus, system } from '@minecraft/server';
|
||||||
|
import { Command } from '../classes/Commands/Command';
|
||||||
|
import { structureCollection } from '../classes/Structure/StructureCollection';
|
||||||
|
|
||||||
|
export class InstancesCommand extends Command {
|
||||||
|
constructor() {
|
||||||
|
super({
|
||||||
|
name: 'instances',
|
||||||
|
description: 'construct.commands.instances',
|
||||||
|
permissionLevel: CommandPermissionLevel.Any,
|
||||||
|
callback: (origin) => this.run(origin)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
run(origin) {
|
||||||
|
const names = structureCollection.getInstanceNames();
|
||||||
|
if (names.length === 0)
|
||||||
|
return { status: CustomCommandStatus.Success, message: 'construct.commands.instances.empty' };
|
||||||
|
const rawtext = [
|
||||||
|
{ translate: 'construct.commands.instances.header', with: [String(names.length)] },
|
||||||
|
{ text: '\n' }
|
||||||
|
];
|
||||||
|
for (const name of names) {
|
||||||
|
const instance = structureCollection.get(name);
|
||||||
|
const status = this.formatStatus(instance);
|
||||||
|
rawtext.push({
|
||||||
|
translate: 'construct.commands.instances.row',
|
||||||
|
with: { rawtext: [{ text: name }, { text: instance.getStructureId() }, status] }
|
||||||
|
});
|
||||||
|
rawtext.push({ text: '\n' });
|
||||||
|
}
|
||||||
|
origin.sendMessage({ rawtext });
|
||||||
|
return { status: CustomCommandStatus.Success };
|
||||||
|
}
|
||||||
|
|
||||||
|
formatStatus(instance) {
|
||||||
|
const statusMessage = { rawtext: [] };
|
||||||
|
if (instance.isEnabled())
|
||||||
|
statusMessage.rawtext.push({ translate: 'construct.commands.instances.row.enabled' });
|
||||||
|
else
|
||||||
|
statusMessage.rawtext.push({ translate: 'construct.commands.instances.row.disabled' });
|
||||||
|
if (instance.hasLocation()) {
|
||||||
|
const { dimensionId, location } = instance.getLocation();
|
||||||
|
statusMessage.rawtext.push({ translate: 'construct.commands.instances.row.location', with: [location.toString(), dimensionId.replace('minecraft:', '')] });
|
||||||
|
} else {
|
||||||
|
statusMessage.rawtext.push({ translate: 'construct.commands.instances.row.nolocation' });
|
||||||
|
}
|
||||||
|
return statusMessage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const instancesCommand = new InstancesCommand();
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import { Command } from '../classes/Commands/Command';
|
||||||
|
import { CommandPermissionLevel, CustomCommandParamType, CustomCommandStatus, system } from '@minecraft/server';
|
||||||
|
import { structureCollection } from '../classes/Structure/StructureCollection';
|
||||||
|
|
||||||
|
export class LayerCommand extends Command {
|
||||||
|
constructor() {
|
||||||
|
super({
|
||||||
|
name: 'layer',
|
||||||
|
description: 'construct.commands.layer',
|
||||||
|
mandatoryParameters: [
|
||||||
|
{ name: 'instanceName', type: CustomCommandParamType.String },
|
||||||
|
{ name: 'layer', type: CustomCommandParamType.Integer }
|
||||||
|
],
|
||||||
|
permissionLevel: CommandPermissionLevel.Any,
|
||||||
|
callback: (origin, instanceName, layer) => this.run(origin, instanceName, layer)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
run(origin, instanceName, layer) {
|
||||||
|
const instance = structureCollection.get(instanceName);
|
||||||
|
const max = instance.getMaxLayer();
|
||||||
|
if (layer < 0 || layer > max) {
|
||||||
|
origin.sendMessage({ translate: 'construct.commands.layer.outOfBounds', with: [String(layer), instanceName, String(max)] });
|
||||||
|
return void 0;
|
||||||
|
}
|
||||||
|
instance.setLayer(layer);
|
||||||
|
origin.sendMessage({ translate: 'construct.commands.layer.success', with: [instanceName, String(layer)] });
|
||||||
|
return { status: CustomCommandStatus.Success };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const layerCommand = new LayerCommand();
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
import { CommandPermissionLevel, CustomCommandParamType, CustomCommandStatus, EntityComponentTypes, system } from '@minecraft/server';
|
||||||
|
import { Command } from '../classes/Commands/Command';
|
||||||
|
import { structureCollection } from '../classes/Structure/StructureCollection';
|
||||||
|
import { PlayerCommandOrigin } from '../classes/Commands/PlayerCommandOrigin';
|
||||||
|
import { NotAPlayerError } from '../classes/Errors/NotAPlayerError';
|
||||||
|
|
||||||
|
export class MaterialsCommand extends Command {
|
||||||
|
constructor() {
|
||||||
|
super({
|
||||||
|
name: 'materials',
|
||||||
|
description: 'construct.commands.materials',
|
||||||
|
mandatoryParameters: [
|
||||||
|
{ name: 'instanceName', type: CustomCommandParamType.String }
|
||||||
|
],
|
||||||
|
optionalParameters: [
|
||||||
|
{ name: 'missing', type: CustomCommandParamType.Boolean }
|
||||||
|
],
|
||||||
|
permissionLevel: CommandPermissionLevel.Any,
|
||||||
|
callback: (origin, instanceName, missing) => this.run(origin, instanceName, missing)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
run(origin, instanceName, missing) {
|
||||||
|
const instance = structureCollection.get(instanceName);
|
||||||
|
const onlyMissing = missing === true;
|
||||||
|
if (onlyMissing)
|
||||||
|
this.assertIsPlayer(origin);
|
||||||
|
const headerKey = onlyMissing ? 'construct.commands.materials.headerMissing' : 'construct.commands.materials.headerAll';
|
||||||
|
const rawtext = [
|
||||||
|
{ translate: headerKey, with: [instanceName] },
|
||||||
|
{ text: '\n' }
|
||||||
|
];
|
||||||
|
const list = this.getMaterialList(origin, instance, onlyMissing);
|
||||||
|
if (!list.rawtext || list.rawtext.length === 0)
|
||||||
|
rawtext.push({ translate: 'construct.commands.materials.empty' });
|
||||||
|
else
|
||||||
|
rawtext.push(list);
|
||||||
|
origin.sendMessage({ rawtext });
|
||||||
|
return { status: CustomCommandStatus.Success };
|
||||||
|
}
|
||||||
|
|
||||||
|
assertIsPlayer(origin) {
|
||||||
|
if (!(origin instanceof PlayerCommandOrigin))
|
||||||
|
throw new NotAPlayerError();
|
||||||
|
}
|
||||||
|
|
||||||
|
getMaterialList(origin, instance, onlyMissing) {
|
||||||
|
const materials = instance.getActiveMaterials();
|
||||||
|
let container;
|
||||||
|
if (onlyMissing) {
|
||||||
|
const player = origin.getSource();
|
||||||
|
const inventoryComponent = player?.getComponent(EntityComponentTypes.Inventory);
|
||||||
|
container = inventoryComponent?.container;
|
||||||
|
}
|
||||||
|
const materialsMap = onlyMissing ? materials.getMaterialsDifference(container) : void 0;
|
||||||
|
return materials.formatString(materialsMap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const materialsCommand = new MaterialsCommand();
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
import { Command } from '../classes/Commands/Command';
|
||||||
|
import { CommandPermissionLevel, CustomCommandParamType, CustomCommandStatus, system, world } from '@minecraft/server';
|
||||||
|
import { Vector } from '../lib/Vector';
|
||||||
|
import { structureCollection } from '../classes/Structure/StructureCollection';
|
||||||
|
import { PlayerCommandOrigin } from '../classes/Commands/PlayerCommandOrigin';
|
||||||
|
|
||||||
|
export class MoveCommand extends Command {
|
||||||
|
constructor() {
|
||||||
|
super({
|
||||||
|
name: 'move',
|
||||||
|
description: 'construct.commands.move',
|
||||||
|
mandatoryParameters: [
|
||||||
|
{ name: 'instanceName', type: CustomCommandParamType.String }
|
||||||
|
],
|
||||||
|
optionalParameters: [
|
||||||
|
{ name: 'dimensionId', type: CustomCommandParamType.Enum }, // Enum defined in PlaceCommand.js
|
||||||
|
{ name: 'location', type: CustomCommandParamType.Location }
|
||||||
|
],
|
||||||
|
permissionLevel: CommandPermissionLevel.Any,
|
||||||
|
callback: (origin, instanceName, dimensionId, location) => this.run(origin, instanceName, dimensionId, location)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
run(origin, instanceName, dimensionId, location) {
|
||||||
|
const instance = structureCollection.get(instanceName);
|
||||||
|
if (dimensionId === void 0 || location === void 0) {
|
||||||
|
if (!(origin instanceof PlayerCommandOrigin))
|
||||||
|
return { status: CustomCommandStatus.Failure, message: 'construct.commands.move.locationRequired' };
|
||||||
|
const player = origin.getSource();
|
||||||
|
location = player.location;
|
||||||
|
dimensionId = player.dimension.id;
|
||||||
|
}
|
||||||
|
this.assertDimensionExists(dimensionId);
|
||||||
|
const flooredLocation = Vector.from(location).floor();
|
||||||
|
system.run(() => {
|
||||||
|
instance.move(dimensionId, flooredLocation);
|
||||||
|
origin.sendMessage({ translate: 'construct.commands.move.success', with: [instanceName, flooredLocation.toString(), dimensionId.replace('minecraft:', '')] });
|
||||||
|
});
|
||||||
|
return { status: CustomCommandStatus.Success };
|
||||||
|
}
|
||||||
|
|
||||||
|
assertDimensionExists(dimensionId) {
|
||||||
|
return world.getDimension(dimensionId) !== void 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const moveCommand = new MoveCommand();
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { CommandPermissionLevel, CustomCommandParamType, CustomCommandStatus, system } from '@minecraft/server';
|
||||||
|
import { Command } from '../classes/Commands/Command';
|
||||||
|
import { structureCollection } from '../classes/Structure/StructureCollection';
|
||||||
|
|
||||||
|
export class NextLayerCommand extends Command {
|
||||||
|
constructor() {
|
||||||
|
super({
|
||||||
|
name: 'nextlayer',
|
||||||
|
description: 'construct.commands.nextlayer',
|
||||||
|
mandatoryParameters: [
|
||||||
|
{ name: 'instanceName', type: CustomCommandParamType.String }
|
||||||
|
],
|
||||||
|
permissionLevel: CommandPermissionLevel.Any,
|
||||||
|
callback: (origin, instanceName) => this.run(origin, instanceName)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
run(origin, instanceName) {
|
||||||
|
const instance = structureCollection.get(instanceName);
|
||||||
|
instance.increaseLayer();
|
||||||
|
origin.sendMessage({ translate: 'construct.commands.nextlayer.success', with: [instanceName, String(instance.getLayer())] });
|
||||||
|
return { status: CustomCommandStatus.Success };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const nextLayerCommand = new NextLayerCommand();
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import { Command } from '../classes/Commands/Command';
|
||||||
|
import { CommandPermissionLevel, CustomCommandParamType, CustomCommandStatus, DimensionTypes, system, world } from '@minecraft/server';
|
||||||
|
import { structureCollection } from '../classes/Structure/StructureCollection';
|
||||||
|
import { InstanceExistsError } from '../classes/Errors/InstanceExistsError';
|
||||||
|
import { Vector } from '../lib/Vector';
|
||||||
|
|
||||||
|
export class PlaceCommand extends Command {
|
||||||
|
constructor() {
|
||||||
|
super({
|
||||||
|
name: 'place',
|
||||||
|
description: 'construct.commands.place',
|
||||||
|
enums: [ { name: 'dimensionId', values: Object.values(DimensionTypes.getAll().map(d => d.typeId)) } ],
|
||||||
|
mandatoryParameters: [
|
||||||
|
{ name: 'instanceName', type: CustomCommandParamType.String },
|
||||||
|
{ name: 'dimensionId', type: CustomCommandParamType.Enum },
|
||||||
|
{ name: 'location', type: CustomCommandParamType.Location }
|
||||||
|
],
|
||||||
|
permissionLevel: CommandPermissionLevel.Any,
|
||||||
|
callback: (origin, instanceName, dimensionId, location) => this.run(origin, instanceName, dimensionId, location)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
run(origin, instanceName, dimensionId, location) {
|
||||||
|
const instance = structureCollection.get(instanceName);
|
||||||
|
const flooredLocation = Vector.from(location).floor();
|
||||||
|
this.assertDimensionExists(dimensionId);
|
||||||
|
system.run(() => {
|
||||||
|
instance.place(dimensionId, flooredLocation);
|
||||||
|
origin.sendMessage({ translate: 'construct.commands.place.success', with: [instanceName, flooredLocation.toString(), dimensionId.replace('minecraft:', '')] });
|
||||||
|
});
|
||||||
|
return { status: CustomCommandStatus.Success };
|
||||||
|
}
|
||||||
|
|
||||||
|
assertDimensionExists(dimensionId) {
|
||||||
|
return world.getDimension(dimensionId) !== void 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const placeCommand = new PlaceCommand();
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { CommandPermissionLevel, CustomCommandParamType, CustomCommandStatus, system } from '@minecraft/server';
|
||||||
|
import { Command } from '../classes/Commands/Command';
|
||||||
|
import { structureCollection } from '../classes/Structure/StructureCollection';
|
||||||
|
|
||||||
|
export class PrevLayerCommand extends Command {
|
||||||
|
constructor() {
|
||||||
|
super({
|
||||||
|
name: 'prevlayer',
|
||||||
|
description: 'construct.commands.prevlayer',
|
||||||
|
mandatoryParameters: [
|
||||||
|
{ name: 'instanceName', type: CustomCommandParamType.String }
|
||||||
|
],
|
||||||
|
permissionLevel: CommandPermissionLevel.Any,
|
||||||
|
callback: (origin, instanceName) => this.run(origin, instanceName)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
run(origin, instanceName) {
|
||||||
|
const instance = structureCollection.get(instanceName);
|
||||||
|
instance.decreaseLayer();
|
||||||
|
origin.sendMessage({ translate: 'construct.commands.prevlayer.success', with: [instanceName, String(instance.getLayer())] });
|
||||||
|
return { status: CustomCommandStatus.Success };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const prevLayerCommand = new PrevLayerCommand();
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import { Command } from '../classes/Commands/Command';
|
||||||
|
import { CommandPermissionLevel, CustomCommandParamType, CustomCommandStatus, system } from '@minecraft/server';
|
||||||
|
import { structureCollection } from '../classes/Structure/StructureCollection';
|
||||||
|
|
||||||
|
export class RenameCommand extends Command {
|
||||||
|
constructor() {
|
||||||
|
super({
|
||||||
|
name: 'rename',
|
||||||
|
description: 'construct.commands.rename',
|
||||||
|
mandatoryParameters: [
|
||||||
|
{ name: 'instanceName', type: CustomCommandParamType.String },
|
||||||
|
{ name: 'newName', type: CustomCommandParamType.String }
|
||||||
|
],
|
||||||
|
permissionLevel: CommandPermissionLevel.Any,
|
||||||
|
callback: (origin, instanceName, newName) => this.run(origin, instanceName, newName)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
run(origin, instanceName, newName) {
|
||||||
|
const instance = structureCollection.get(instanceName);
|
||||||
|
if (structureCollection.has(newName)) {
|
||||||
|
origin.sendMessage({ translate: 'construct.error.instanceExists', with: [newName] });
|
||||||
|
return void 0;
|
||||||
|
}
|
||||||
|
structureCollection.rename(instanceName, newName);
|
||||||
|
origin.sendMessage({ translate: 'construct.commands.rename.success', with: [instanceName, newName] });
|
||||||
|
return { status: CustomCommandStatus.Success };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const renameCommand = new RenameCommand();
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import { Command } from '../classes/Commands/Command';
|
||||||
|
import { CommandPermissionLevel, CustomCommandParamType, CustomCommandStatus, system, TicksPerSecond } from '@minecraft/server';
|
||||||
|
import { InstanceFormBuilder } from '../classes/Instance/InstanceFormBuilder';
|
||||||
|
import { structureCollection } from '../classes/Structure/StructureCollection';
|
||||||
|
import { StructureVerifier } from '../classes/Verifier/StructureVerifier';
|
||||||
|
import { StructureStatistics } from '../classes/Structure/StructureStatistics';
|
||||||
|
|
||||||
|
export class StatsCommand extends Command {
|
||||||
|
constructor() {
|
||||||
|
super({
|
||||||
|
name: 'stats',
|
||||||
|
description: 'construct.commands.stats',
|
||||||
|
mandatoryParameters: [
|
||||||
|
{ name: 'instanceName', type: CustomCommandParamType.String }
|
||||||
|
],
|
||||||
|
permissionLevel: CommandPermissionLevel.Any,
|
||||||
|
callback: (origin, instanceName) => this.run(origin, instanceName)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
run(origin, instanceName) {
|
||||||
|
const instance = structureCollection.get(instanceName);
|
||||||
|
if (this.structureVerifier)
|
||||||
|
return { status: CustomCommandStatus.Failure, error: 'construct.commands.stats.alreadyRunning' };
|
||||||
|
system.run(async () => {
|
||||||
|
origin.sendMessage(await this.getStatsMessage(instance));
|
||||||
|
});
|
||||||
|
return { status: CustomCommandStatus.Success };
|
||||||
|
}
|
||||||
|
|
||||||
|
async getStatsMessage(instance) {
|
||||||
|
const verifierOptions = { isEnabled: true, particleLifetime: 1*TicksPerSecond, isStandalone: true };
|
||||||
|
this.structureVerifier = new StructureVerifier(instance, verifierOptions);
|
||||||
|
const verification = await this.structureVerifier.verifyStructure(true);
|
||||||
|
const statistics = new StructureStatistics(instance, verification);
|
||||||
|
const statsMessage = statistics.getMessage();
|
||||||
|
this.structureVerifier = void 0;
|
||||||
|
return statsMessage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const statsCommand = new StatsCommand();
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import { Command } from '../classes/Commands/Command';
|
||||||
|
import { CommandPermissionLevel, CustomCommandParamType, CustomCommandStatus, EntityComponentTypes, EquipmentSlot, system } from '@minecraft/server';
|
||||||
|
import { PlayerCommandOrigin } from '../classes/Commands/PlayerCommandOrigin';
|
||||||
|
import { MENU_ITEM } from '../consts';
|
||||||
|
import { structureCollection } from '../classes/Structure/StructureCollection';
|
||||||
|
|
||||||
|
export class TagCommand extends Command {
|
||||||
|
constructor() {
|
||||||
|
super({
|
||||||
|
name: 'tag',
|
||||||
|
description: 'construct.commands.tag',
|
||||||
|
allowedSources: [PlayerCommandOrigin],
|
||||||
|
mandatoryParameters: [
|
||||||
|
{ name: 'instanceName', type: CustomCommandParamType.String }
|
||||||
|
],
|
||||||
|
permissionLevel: CommandPermissionLevel.Any,
|
||||||
|
callback: (origin, instanceName) => this.run(origin, instanceName)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
run(origin, instanceName) {
|
||||||
|
const instance = structureCollection.get(instanceName);
|
||||||
|
const player = origin.getSource();
|
||||||
|
const equipment = player.getComponent(EntityComponentTypes.Equippable);
|
||||||
|
const itemStack = equipment?.getEquipment(EquipmentSlot.Mainhand);
|
||||||
|
if (itemStack?.typeId !== MENU_ITEM)
|
||||||
|
return { status: CustomCommandStatus.Failure, message: 'construct.commands.tag.notHoldingItem' };
|
||||||
|
system.run(() => {
|
||||||
|
itemStack.nameTag = instanceName;
|
||||||
|
equipment.setEquipment(EquipmentSlot.Mainhand, itemStack);
|
||||||
|
origin.sendMessage({ translate: 'construct.commands.tag.success', with: [instanceName] });
|
||||||
|
});
|
||||||
|
return { status: CustomCommandStatus.Success };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const tagCommand = new TagCommand();
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import { CommandPermissionLevel, CustomCommandParamType, CustomCommandStatus, system } from '@minecraft/server';
|
||||||
|
import { Command } from '../classes/Commands/Command';
|
||||||
|
import { structureCollection } from '../classes/Structure/StructureCollection';
|
||||||
|
|
||||||
|
export class VerifierCommand extends Command {
|
||||||
|
constructor() {
|
||||||
|
super({
|
||||||
|
name: 'verifier',
|
||||||
|
description: 'construct.commands.verifier',
|
||||||
|
mandatoryParameters: [
|
||||||
|
{ name: 'instanceName', type: CustomCommandParamType.String },
|
||||||
|
{ name: 'state', type: CustomCommandParamType.Boolean }
|
||||||
|
],
|
||||||
|
permissionLevel: CommandPermissionLevel.Any,
|
||||||
|
callback: (origin, instanceName, state) => this.run(origin, instanceName, state)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
run(origin, instanceName, state) {
|
||||||
|
const instance = structureCollection.get(instanceName);
|
||||||
|
if (state)
|
||||||
|
instance.setVerifierEnabled(true);
|
||||||
|
else
|
||||||
|
instance.setVerifierEnabled(false);
|
||||||
|
this.sendFeedback(origin, instanceName, state);
|
||||||
|
return { status: CustomCommandStatus.Success };
|
||||||
|
}
|
||||||
|
|
||||||
|
sendFeedback(origin, instanceName, state) {
|
||||||
|
origin.sendMessage({ translate: state ? 'construct.commands.verifier.enabled' : 'construct.commands.verifier.disabled', with: [instanceName] });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const verifierCommand = new VerifierCommand();
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
import { world, system, EntityComponentTypes, ItemStack, CommandPermissionLevel, CustomCommandStatus, Player } from '@minecraft/server';
|
|
||||||
import { MenuForm } from '../classes/MenuForm';
|
|
||||||
import { structureCollection } from '../classes/Structure/StructureCollection'
|
|
||||||
import { Builders } from '../classes/Builder/Builders';
|
|
||||||
|
|
||||||
export const MENU_ITEM = 'construct:menu';
|
|
||||||
|
|
||||||
system.beforeEvents.startup.subscribe((event) => {
|
|
||||||
const command = {
|
|
||||||
name: 'construct:construct',
|
|
||||||
description: 'construct.commands.construct',
|
|
||||||
permissionLevel: CommandPermissionLevel.Any,
|
|
||||||
cheatsRequired: false
|
|
||||||
};
|
|
||||||
event.customCommandRegistry.registerCommand(command, givePlayerConstructItem);
|
|
||||||
});
|
|
||||||
|
|
||||||
function givePlayerConstructItem(origin) {
|
|
||||||
const player = origin.sourceEntity;
|
|
||||||
if (player instanceof Player === false)
|
|
||||||
return { status: CustomCommandStatus.Failure, message: 'construct.commands.construct.denyorigin' };
|
|
||||||
system.run(() => {
|
|
||||||
const givenItemStack = player.getComponent(EntityComponentTypes.Inventory)?.container?.addItem(new ItemStack(MENU_ITEM));
|
|
||||||
if (givenItemStack)
|
|
||||||
player.sendMessage({ translate: 'construct.commands.construct.fail' });
|
|
||||||
else
|
|
||||||
player.sendMessage({ translate: 'construct.commands.construct.success' });
|
|
||||||
});
|
|
||||||
return { status: CustomCommandStatus.Success };
|
|
||||||
}
|
|
||||||
|
|
||||||
world.beforeEvents.itemUse.subscribe((event) => {
|
|
||||||
if (!event.source || event.itemStack?.typeId !== MENU_ITEM) return;
|
|
||||||
event.cancel = true;
|
|
||||||
const builder = Builders.get(event.source.id);
|
|
||||||
system.run(() => {
|
|
||||||
if (builder.isFlexibleInstanceMoving())
|
|
||||||
return;
|
|
||||||
openMenu(event.source, event);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
function openMenu(player, event = void 0) {
|
|
||||||
const options = { jumpToInstance: true }
|
|
||||||
if (event) {
|
|
||||||
const instanceNames = structureCollection.getInstanceNames();
|
|
||||||
const instanceName = event.itemStack?.nameTag;
|
|
||||||
if (instanceNames.includes(instanceName))
|
|
||||||
options.instanceName = instanceName;
|
|
||||||
}
|
|
||||||
new MenuForm(player, options);
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
export const PACK_IDENTIFIER = 'construct';
|
||||||
|
export const MENU_ITEM = 'construct:menu';
|
||||||
@@ -0,0 +1,914 @@
|
|||||||
|
/** @license MIT
|
||||||
|
* AddonAPIKit - Copyright (c) 2026 ForestOfLight
|
||||||
|
* MCBE-IPC - Copyright (c) 2026 OmniacDev
|
||||||
|
* See LICENSE for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// src/MCBE-IPC/ipc.js
|
||||||
|
import { ScriptEventSource, system } from "@minecraft/server";
|
||||||
|
var UTIL;
|
||||||
|
(function(UTIL2) {
|
||||||
|
function generate_id() {
|
||||||
|
const r = Math.random() * 4294967296 >>> 0;
|
||||||
|
return r.toString(16).padStart(8, "0").toUpperCase();
|
||||||
|
}
|
||||||
|
UTIL2.generate_id = generate_id;
|
||||||
|
})(UTIL || (UTIL = {}));
|
||||||
|
var PROTO;
|
||||||
|
(function(PROTO2) {
|
||||||
|
class Buffer {
|
||||||
|
get end() {
|
||||||
|
return this._length + this._offset;
|
||||||
|
}
|
||||||
|
get front() {
|
||||||
|
return this._offset;
|
||||||
|
}
|
||||||
|
get data_view() {
|
||||||
|
return this._data_view;
|
||||||
|
}
|
||||||
|
constructor(size = 256) {
|
||||||
|
this._buffer = new Uint8Array(size);
|
||||||
|
this._data_view = new DataView(this._buffer.buffer);
|
||||||
|
this._length = 0;
|
||||||
|
this._offset = 0;
|
||||||
|
}
|
||||||
|
reserve(amount) {
|
||||||
|
this.ensure_capacity(amount);
|
||||||
|
const end = this.end;
|
||||||
|
this._length += amount;
|
||||||
|
return end;
|
||||||
|
}
|
||||||
|
consume(amount) {
|
||||||
|
if (amount > this._length)
|
||||||
|
throw new Error("not enough bytes");
|
||||||
|
const front = this.front;
|
||||||
|
this._length -= amount;
|
||||||
|
this._offset += amount;
|
||||||
|
return front;
|
||||||
|
}
|
||||||
|
write(input) {
|
||||||
|
if (typeof input === "number") {
|
||||||
|
const offset = this.reserve(1);
|
||||||
|
this._buffer[offset] = input;
|
||||||
|
} else {
|
||||||
|
const offset = this.reserve(input.length);
|
||||||
|
this._buffer.set(input, offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
read(amount) {
|
||||||
|
if (amount === void 0) {
|
||||||
|
const offset = this.consume(1);
|
||||||
|
return this._buffer[offset];
|
||||||
|
} else {
|
||||||
|
const offset = this.consume(amount);
|
||||||
|
return this._buffer.slice(offset, offset + amount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ensure_capacity(size) {
|
||||||
|
if (this.end + size > this._buffer.length) {
|
||||||
|
const larger_buffer = new Uint8Array((this.end + size) * 2);
|
||||||
|
larger_buffer.set(this._buffer.subarray(this._offset, this.end), 0);
|
||||||
|
this._buffer = larger_buffer;
|
||||||
|
this._offset = 0;
|
||||||
|
this._data_view = new DataView(this._buffer.buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static from_uint8array(array) {
|
||||||
|
const buffer = new Buffer();
|
||||||
|
buffer._buffer = array;
|
||||||
|
buffer._length = array.length;
|
||||||
|
buffer._offset = 0;
|
||||||
|
buffer._data_view = new DataView(array.buffer);
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
to_uint8array() {
|
||||||
|
return this._buffer.subarray(this._offset, this.end);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PROTO2.Buffer = Buffer;
|
||||||
|
let MIPS;
|
||||||
|
(function(MIPS2) {
|
||||||
|
function is_valid(str) {
|
||||||
|
return str.startsWith("(0x") && str.endsWith(")");
|
||||||
|
}
|
||||||
|
MIPS2.is_valid = is_valid;
|
||||||
|
function* serialize(stream) {
|
||||||
|
const uint8array = stream.to_uint8array();
|
||||||
|
let str = "(0x";
|
||||||
|
for (let i = 0; i < uint8array.length; i++) {
|
||||||
|
const hex = uint8array[i].toString(16).padStart(2, "0").toUpperCase();
|
||||||
|
str += hex;
|
||||||
|
yield;
|
||||||
|
}
|
||||||
|
str += ")";
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
MIPS2.serialize = serialize;
|
||||||
|
function* deserialize(str) {
|
||||||
|
if (is_valid(str)) {
|
||||||
|
const buffer = new Buffer();
|
||||||
|
const hex_str = str.slice(3, str.length - 1);
|
||||||
|
for (let i = 0; i < hex_str.length; i++) {
|
||||||
|
const hex = hex_str[i] + hex_str[++i];
|
||||||
|
buffer.write(parseInt(hex, 16));
|
||||||
|
yield;
|
||||||
|
}
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
return new Buffer();
|
||||||
|
}
|
||||||
|
MIPS2.deserialize = deserialize;
|
||||||
|
})(MIPS = PROTO2.MIPS || (PROTO2.MIPS = {}));
|
||||||
|
PROTO2.Void = {
|
||||||
|
*serialize() {
|
||||||
|
},
|
||||||
|
*deserialize() {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
PROTO2.Null = {
|
||||||
|
*serialize() {
|
||||||
|
},
|
||||||
|
*deserialize() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
PROTO2.Undefined = {
|
||||||
|
*serialize() {
|
||||||
|
},
|
||||||
|
*deserialize() {
|
||||||
|
return void 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
PROTO2.Int8 = {
|
||||||
|
*serialize(value, stream) {
|
||||||
|
stream.data_view.setInt8(stream.reserve(1), value);
|
||||||
|
},
|
||||||
|
*deserialize(stream) {
|
||||||
|
return stream.data_view.getInt8(stream.consume(1));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
PROTO2.Int16 = {
|
||||||
|
*serialize(value, stream) {
|
||||||
|
stream.data_view.setInt16(stream.reserve(2), value);
|
||||||
|
},
|
||||||
|
*deserialize(stream) {
|
||||||
|
return stream.data_view.getInt16(stream.consume(2));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
PROTO2.Int32 = {
|
||||||
|
*serialize(value, stream) {
|
||||||
|
stream.data_view.setInt32(stream.reserve(4), value);
|
||||||
|
},
|
||||||
|
*deserialize(stream) {
|
||||||
|
return stream.data_view.getInt32(stream.consume(4));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
PROTO2.UInt8 = {
|
||||||
|
*serialize(value, stream) {
|
||||||
|
stream.data_view.setUint8(stream.reserve(1), value);
|
||||||
|
},
|
||||||
|
*deserialize(stream) {
|
||||||
|
return stream.data_view.getUint8(stream.consume(1));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
PROTO2.UInt16 = {
|
||||||
|
*serialize(value, stream) {
|
||||||
|
stream.data_view.setUint16(stream.reserve(2), value);
|
||||||
|
},
|
||||||
|
*deserialize(stream) {
|
||||||
|
return stream.data_view.getUint16(stream.consume(2));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
PROTO2.UInt32 = {
|
||||||
|
*serialize(value, stream) {
|
||||||
|
stream.data_view.setUint32(stream.reserve(4), value);
|
||||||
|
},
|
||||||
|
*deserialize(stream) {
|
||||||
|
return stream.data_view.getUint32(stream.consume(4));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
PROTO2.UVarInt32 = {
|
||||||
|
*serialize(value, stream) {
|
||||||
|
value >>>= 0;
|
||||||
|
while (value >= 128) {
|
||||||
|
stream.write(value & 127 | 128);
|
||||||
|
value >>>= 7;
|
||||||
|
yield;
|
||||||
|
}
|
||||||
|
stream.write(value);
|
||||||
|
},
|
||||||
|
*deserialize(stream) {
|
||||||
|
let value = 0;
|
||||||
|
for (let size = 0; size < 5; size++) {
|
||||||
|
const byte = stream.read();
|
||||||
|
value |= (byte & 127) << size * 7;
|
||||||
|
yield;
|
||||||
|
if ((byte & 128) == 0)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return value >>> 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
PROTO2.VarInt32 = {
|
||||||
|
*serialize(value, stream) {
|
||||||
|
const zigzag = value << 1 ^ value >> 31;
|
||||||
|
yield* PROTO2.UVarInt32.serialize(zigzag, stream);
|
||||||
|
},
|
||||||
|
*deserialize(stream) {
|
||||||
|
const zigzag = yield* PROTO2.UVarInt32.deserialize(stream);
|
||||||
|
return zigzag >>> 1 ^ -(zigzag & 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
PROTO2.Float32 = {
|
||||||
|
*serialize(value, stream) {
|
||||||
|
stream.data_view.setFloat32(stream.reserve(4), value);
|
||||||
|
},
|
||||||
|
*deserialize(stream) {
|
||||||
|
return stream.data_view.getFloat32(stream.consume(4));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
PROTO2.Float64 = {
|
||||||
|
*serialize(value, stream) {
|
||||||
|
stream.data_view.setFloat64(stream.reserve(8), value);
|
||||||
|
},
|
||||||
|
*deserialize(stream) {
|
||||||
|
return stream.data_view.getFloat64(stream.consume(8));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
PROTO2.String = {
|
||||||
|
*serialize(value, stream) {
|
||||||
|
yield* PROTO2.UVarInt32.serialize(value.length, stream);
|
||||||
|
for (let i = 0; i < value.length; i++) {
|
||||||
|
const code = value.charCodeAt(i);
|
||||||
|
yield* PROTO2.UVarInt32.serialize(code, stream);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
*deserialize(stream) {
|
||||||
|
const length = yield* PROTO2.UVarInt32.deserialize(stream);
|
||||||
|
let value = "";
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
const code = yield* PROTO2.UVarInt32.deserialize(stream);
|
||||||
|
value += globalThis.String.fromCharCode(code);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
PROTO2.Boolean = {
|
||||||
|
*serialize(value, stream) {
|
||||||
|
stream.write(value ? 1 : 0);
|
||||||
|
},
|
||||||
|
*deserialize(stream) {
|
||||||
|
return stream.read() !== 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
PROTO2.UInt8Array = {
|
||||||
|
*serialize(value, stream) {
|
||||||
|
yield* PROTO2.UVarInt32.serialize(value.length, stream);
|
||||||
|
stream.write(value);
|
||||||
|
},
|
||||||
|
*deserialize(stream) {
|
||||||
|
const length = yield* PROTO2.UVarInt32.deserialize(stream);
|
||||||
|
return stream.read(length);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
PROTO2.Date = {
|
||||||
|
*serialize(value, stream) {
|
||||||
|
yield* PROTO2.Float64.serialize(value.getTime(), stream);
|
||||||
|
},
|
||||||
|
*deserialize(stream) {
|
||||||
|
return new globalThis.Date(yield* PROTO2.Float64.deserialize(stream));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
function Object2(s) {
|
||||||
|
return {
|
||||||
|
*serialize(value, stream) {
|
||||||
|
for (const key in s) {
|
||||||
|
yield* s[key].serialize(value[key], stream);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
*deserialize(stream) {
|
||||||
|
const result = {};
|
||||||
|
for (const key in s) {
|
||||||
|
result[key] = yield* s[key].deserialize(stream);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
PROTO2.Object = Object2;
|
||||||
|
function Array2(s) {
|
||||||
|
return {
|
||||||
|
*serialize(value, stream) {
|
||||||
|
yield* PROTO2.UVarInt32.serialize(value.length, stream);
|
||||||
|
for (const item of value) {
|
||||||
|
yield* s.serialize(item, stream);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
*deserialize(stream) {
|
||||||
|
const result = [];
|
||||||
|
const length = yield* PROTO2.UVarInt32.deserialize(stream);
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
result[i] = yield* s.deserialize(stream);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
PROTO2.Array = Array2;
|
||||||
|
function Tuple(...s) {
|
||||||
|
return {
|
||||||
|
*serialize(value, stream) {
|
||||||
|
for (let i = 0; i < s.length; i++) {
|
||||||
|
yield* s[i].serialize(value[i], stream);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
*deserialize(stream) {
|
||||||
|
const result = [];
|
||||||
|
for (let i = 0; i < s.length; i++) {
|
||||||
|
result[i] = yield* s[i].deserialize(stream);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
PROTO2.Tuple = Tuple;
|
||||||
|
function Optional(s) {
|
||||||
|
return {
|
||||||
|
*serialize(value, stream) {
|
||||||
|
const def = value !== void 0;
|
||||||
|
yield* PROTO2.Boolean.serialize(def, stream);
|
||||||
|
if (def)
|
||||||
|
yield* s.serialize(value, stream);
|
||||||
|
},
|
||||||
|
*deserialize(stream) {
|
||||||
|
const def = yield* PROTO2.Boolean.deserialize(stream);
|
||||||
|
if (def)
|
||||||
|
return yield* s.deserialize(stream);
|
||||||
|
return void 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
PROTO2.Optional = Optional;
|
||||||
|
function Map2(kS, vS) {
|
||||||
|
return {
|
||||||
|
*serialize(value, stream) {
|
||||||
|
yield* PROTO2.UVarInt32.serialize(value.size, stream);
|
||||||
|
for (const [k, v] of value) {
|
||||||
|
yield* kS.serialize(k, stream);
|
||||||
|
yield* vS.serialize(v, stream);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
*deserialize(stream) {
|
||||||
|
const size = yield* PROTO2.UVarInt32.deserialize(stream);
|
||||||
|
const result = new globalThis.Map();
|
||||||
|
for (let i = 0; i < size; i++) {
|
||||||
|
const k = yield* kS.deserialize(stream);
|
||||||
|
const v = yield* vS.deserialize(stream);
|
||||||
|
result.set(k, v);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
PROTO2.Map = Map2;
|
||||||
|
function Set(s) {
|
||||||
|
return {
|
||||||
|
*serialize(set, stream) {
|
||||||
|
yield* PROTO2.UVarInt32.serialize(set.size, stream);
|
||||||
|
for (const v of set) {
|
||||||
|
yield* s.serialize(v, stream);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
*deserialize(stream) {
|
||||||
|
const size = yield* PROTO2.UVarInt32.deserialize(stream);
|
||||||
|
const result = new globalThis.Set();
|
||||||
|
for (let i = 0; i < size; i++) {
|
||||||
|
const v = yield* s.deserialize(stream);
|
||||||
|
result.add(v);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
PROTO2.Set = Set;
|
||||||
|
function Cached(s, depth = 16) {
|
||||||
|
const cache = new globalThis.Map();
|
||||||
|
return {
|
||||||
|
*serialize(value, stream) {
|
||||||
|
const hit = cache.get(value);
|
||||||
|
if (hit !== void 0) {
|
||||||
|
stream.write(hit);
|
||||||
|
cache.delete(value);
|
||||||
|
cache.set(value, hit);
|
||||||
|
} else {
|
||||||
|
const buffer = new PROTO2.Buffer();
|
||||||
|
yield* s.serialize(value, buffer);
|
||||||
|
const bytes = buffer.to_uint8array();
|
||||||
|
stream.write(bytes);
|
||||||
|
cache.set(value, bytes);
|
||||||
|
if (cache.size > depth) {
|
||||||
|
const first = cache.keys().next().value;
|
||||||
|
cache.delete(first);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
*deserialize(stream) {
|
||||||
|
return yield* s.deserialize(stream);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
PROTO2.Cached = Cached;
|
||||||
|
})(PROTO || (PROTO = {}));
|
||||||
|
var NET;
|
||||||
|
(function(NET2) {
|
||||||
|
const Endpoint = PROTO.String;
|
||||||
|
const Meta = PROTO.Object({
|
||||||
|
guid: PROTO.String,
|
||||||
|
signature: PROTO.String
|
||||||
|
});
|
||||||
|
const Header = PROTO.Object({
|
||||||
|
meta: Meta,
|
||||||
|
index: PROTO.UVarInt32,
|
||||||
|
final: PROTO.Boolean
|
||||||
|
});
|
||||||
|
const LISTENERS = /* @__PURE__ */ new Map();
|
||||||
|
NET2.SIGNATURE = "mcbe-ipc:v3";
|
||||||
|
NET2.FRAG_MAX = 2048;
|
||||||
|
function* serialize(buffer, max_size = Infinity) {
|
||||||
|
const uint8array = buffer.to_uint8array();
|
||||||
|
const result = [];
|
||||||
|
let acc_str = "";
|
||||||
|
let acc_size = 0;
|
||||||
|
for (let i = 0; i < uint8array.length; i++) {
|
||||||
|
const char_code = uint8array[i] | uint8array[++i] << 8;
|
||||||
|
const utf16_size = char_code <= 127 ? 1 : char_code <= 2047 ? 2 : char_code <= 65535 ? 3 : 4;
|
||||||
|
const char_size = char_code > 255 ? utf16_size : 2;
|
||||||
|
if (acc_size + char_size > max_size) {
|
||||||
|
result.push(acc_str);
|
||||||
|
acc_str = "";
|
||||||
|
acc_size = 0;
|
||||||
|
}
|
||||||
|
if (char_code > 255) {
|
||||||
|
acc_str += String.fromCharCode(char_code);
|
||||||
|
acc_size += utf16_size;
|
||||||
|
} else {
|
||||||
|
acc_str += char_code.toString(16).padStart(2, "0").toUpperCase();
|
||||||
|
acc_size += 2;
|
||||||
|
}
|
||||||
|
yield;
|
||||||
|
}
|
||||||
|
result.push(acc_str);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
NET2.serialize = serialize;
|
||||||
|
function* deserialize(strings) {
|
||||||
|
const buffer = new PROTO.Buffer();
|
||||||
|
for (let i = 0; i < strings.length; i++) {
|
||||||
|
const str = strings[i];
|
||||||
|
for (let j = 0; j < str.length; j++) {
|
||||||
|
const char_code = str.charCodeAt(j);
|
||||||
|
if (char_code <= 255) {
|
||||||
|
const hex = str[j] + str[++j];
|
||||||
|
const hex_code = parseInt(hex, 16);
|
||||||
|
buffer.write(hex_code & 255);
|
||||||
|
buffer.write(hex_code >> 8);
|
||||||
|
} else {
|
||||||
|
buffer.write(char_code & 255);
|
||||||
|
buffer.write(char_code >> 8);
|
||||||
|
}
|
||||||
|
yield;
|
||||||
|
}
|
||||||
|
yield;
|
||||||
|
}
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
NET2.deserialize = deserialize;
|
||||||
|
system.afterEvents.scriptEventReceive.subscribe((event) => {
|
||||||
|
system.runJob((function* () {
|
||||||
|
if (event.sourceType !== ScriptEventSource.Server)
|
||||||
|
return;
|
||||||
|
const [serialized_endpoint, serialized_header] = event.id.split(":");
|
||||||
|
if (!PROTO.MIPS.is_valid(serialized_endpoint))
|
||||||
|
return;
|
||||||
|
const endpoint_stream = yield* PROTO.MIPS.deserialize(serialized_endpoint);
|
||||||
|
const endpoint = yield* Endpoint.deserialize(endpoint_stream);
|
||||||
|
const listeners = LISTENERS.get(endpoint);
|
||||||
|
if (listeners !== void 0 && PROTO.MIPS.is_valid(serialized_header)) {
|
||||||
|
const header_stream = yield* PROTO.MIPS.deserialize(serialized_header);
|
||||||
|
const header = yield* Header.deserialize(header_stream);
|
||||||
|
for (const listener of [...listeners]) {
|
||||||
|
try {
|
||||||
|
yield* listener(header, event.message);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`[MCBE-IPC] listener error while handling packet on "${endpoint}":`, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})());
|
||||||
|
});
|
||||||
|
function register(endpoint, listener) {
|
||||||
|
let listeners = LISTENERS.get(endpoint);
|
||||||
|
if (listeners === void 0) {
|
||||||
|
listeners = new Array();
|
||||||
|
LISTENERS.set(endpoint, listeners);
|
||||||
|
}
|
||||||
|
listeners.push(listener);
|
||||||
|
return () => {
|
||||||
|
const idx = listeners.indexOf(listener);
|
||||||
|
if (idx !== -1)
|
||||||
|
listeners.splice(idx, 1);
|
||||||
|
if (listeners.length === 0) {
|
||||||
|
LISTENERS.delete(endpoint);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function* emit(endpoint, serializer, value, options) {
|
||||||
|
const guid = options?.metaOverride?.guid ?? UTIL.generate_id();
|
||||||
|
const signature = options?.metaOverride?.signature ?? NET2.SIGNATURE;
|
||||||
|
const endpoint_stream = new PROTO.Buffer();
|
||||||
|
yield* Endpoint.serialize(endpoint, endpoint_stream);
|
||||||
|
const serialized_endpoint = yield* PROTO.MIPS.serialize(endpoint_stream);
|
||||||
|
const packet_stream = new PROTO.Buffer();
|
||||||
|
yield* serializer.serialize(value, packet_stream);
|
||||||
|
const serialized_packets = yield* serialize(packet_stream, NET2.FRAG_MAX);
|
||||||
|
for (let i = 0; i < serialized_packets.length; i++) {
|
||||||
|
const serialized_packet = serialized_packets[i];
|
||||||
|
const header = {
|
||||||
|
meta: { guid, signature },
|
||||||
|
index: i,
|
||||||
|
final: i === serialized_packets.length - 1
|
||||||
|
};
|
||||||
|
const header_stream = new PROTO.Buffer();
|
||||||
|
yield* Header.serialize(header, header_stream);
|
||||||
|
const serialized_header = yield* PROTO.MIPS.serialize(header_stream);
|
||||||
|
system.sendScriptEvent(`${serialized_endpoint}:${serialized_header}`, serialized_packet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NET2.emit = emit;
|
||||||
|
function listen(endpoint, deserializer, callback, options) {
|
||||||
|
const buffer = /* @__PURE__ */ new Map();
|
||||||
|
const listener = function* (header, fragment) {
|
||||||
|
let packet = buffer.get(header.meta.guid);
|
||||||
|
if (packet === void 0) {
|
||||||
|
if (options?.filter?.(header.meta) === false)
|
||||||
|
return;
|
||||||
|
packet = { size: -1, fragments: [], received: 0 };
|
||||||
|
buffer.set(header.meta.guid, packet);
|
||||||
|
}
|
||||||
|
if (header.final) {
|
||||||
|
packet.size = header.index + 1;
|
||||||
|
}
|
||||||
|
if (packet.fragments[header.index] === void 0) {
|
||||||
|
packet.fragments[header.index] = fragment;
|
||||||
|
packet.received++;
|
||||||
|
} else {
|
||||||
|
throw new Error(`received duplicate fragment ${header.index} for packet ${header.meta.guid}`);
|
||||||
|
}
|
||||||
|
if (packet.size !== -1 && packet.size === packet.received) {
|
||||||
|
const stream = yield* deserialize(packet.fragments);
|
||||||
|
const value = yield* deserializer.deserialize(stream);
|
||||||
|
yield* callback(value, header.meta);
|
||||||
|
buffer.delete(header.meta.guid);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return register(endpoint, listener);
|
||||||
|
}
|
||||||
|
NET2.listen = listen;
|
||||||
|
})(NET || (NET = {}));
|
||||||
|
var IPC;
|
||||||
|
(function(IPC2) {
|
||||||
|
function send(channel, serializer, value) {
|
||||||
|
system.runJob(NET.emit(`ipc:${channel}:send`, serializer, value));
|
||||||
|
}
|
||||||
|
IPC2.send = send;
|
||||||
|
function invoke(channel, serializer, value, deserializer) {
|
||||||
|
const id = UTIL.generate_id();
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const terminate = NET.listen(`ipc:${channel}:handle`, deserializer, function* (value2, meta) {
|
||||||
|
if (meta.signature.includes(`+correlation`) && meta.guid !== id)
|
||||||
|
return;
|
||||||
|
resolve(value2);
|
||||||
|
terminate();
|
||||||
|
}, {
|
||||||
|
filter: (meta) => !meta.signature.includes(`+correlation`) || meta.guid === id
|
||||||
|
});
|
||||||
|
system.runJob(NET.emit(`ipc:${channel}:invoke`, serializer, value, {
|
||||||
|
metaOverride: {
|
||||||
|
guid: id,
|
||||||
|
signature: `${NET.SIGNATURE}+correlation`
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
IPC2.invoke = invoke;
|
||||||
|
function on(channel, deserializer, listener) {
|
||||||
|
return NET.listen(`ipc:${channel}:send`, deserializer, function* (value) {
|
||||||
|
listener(value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
IPC2.on = on;
|
||||||
|
function once(channel, deserializer, listener) {
|
||||||
|
const terminate = NET.listen(`ipc:${channel}:send`, deserializer, function* (value) {
|
||||||
|
listener(value);
|
||||||
|
terminate();
|
||||||
|
});
|
||||||
|
return terminate;
|
||||||
|
}
|
||||||
|
IPC2.once = once;
|
||||||
|
function handle(channel, deserializer, serializer, listener) {
|
||||||
|
return NET.listen(`ipc:${channel}:invoke`, deserializer, function* (value, meta) {
|
||||||
|
const result = listener(value);
|
||||||
|
yield* NET.emit(`ipc:${channel}:handle`, serializer, result, {
|
||||||
|
metaOverride: meta.signature.includes(`+correlation`) ? {
|
||||||
|
guid: meta.guid,
|
||||||
|
signature: `${NET.SIGNATURE}+correlation`
|
||||||
|
} : void 0
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
IPC2.handle = handle;
|
||||||
|
})(IPC || (IPC = {}));
|
||||||
|
|
||||||
|
// src/Errors/APIErrorEnum.js
|
||||||
|
var APIErrorEnum = Object.freeze({
|
||||||
|
Unknown: 0,
|
||||||
|
Success: 1,
|
||||||
|
Caller: 2,
|
||||||
|
Server: 3
|
||||||
|
});
|
||||||
|
|
||||||
|
// src/Errors/APICallerError.js
|
||||||
|
var APICallerError = class extends Error {
|
||||||
|
constructor(error) {
|
||||||
|
const message = error.name + ": " + error.message;
|
||||||
|
super(message);
|
||||||
|
this.thrownError = error;
|
||||||
|
this.errorCode = APIErrorEnum.Caller;
|
||||||
|
this.name = "APICallerError";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// src/Errors/APIServerError.js
|
||||||
|
var APIServerError = class extends Error {
|
||||||
|
constructor(error) {
|
||||||
|
const message = error.name + ": " + error.message;
|
||||||
|
super(message);
|
||||||
|
this.thrownError = error;
|
||||||
|
this.errorCode = APIErrorEnum.Server;
|
||||||
|
this.name = "APIServerError";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// src/Errors/APIVersionMismatchError.js
|
||||||
|
var APIVersionMismatchError = class extends Error {
|
||||||
|
constructor(serverApiVersion, callerApiVersion) {
|
||||||
|
super(`API version numbers do not match (${callerApiVersion} != ${serverApiVersion}). Please use API version ${serverApiVersion}.`);
|
||||||
|
this.name = "APIVersionMismatchError";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// src/APIModels.js
|
||||||
|
var VoidModel = PROTO.Optional(PROTO.Void);
|
||||||
|
var ErrorModel = PROTO.Optional(PROTO.Object({
|
||||||
|
code: PROTO.Int8,
|
||||||
|
name: PROTO.Optional(PROTO.String),
|
||||||
|
message: PROTO.Optional(PROTO.String)
|
||||||
|
}));
|
||||||
|
var ReturnModelShell = {
|
||||||
|
apiVersion: PROTO.String,
|
||||||
|
data: void 0,
|
||||||
|
error: ErrorModel
|
||||||
|
};
|
||||||
|
var CallModelShell = {
|
||||||
|
apiVersion: PROTO.String,
|
||||||
|
parameterMap: void 0
|
||||||
|
};
|
||||||
|
var EndpointModel = PROTO.String;
|
||||||
|
var EndpointsModel = PROTO.Array(EndpointModel);
|
||||||
|
|
||||||
|
// src/APIController.js
|
||||||
|
var APIController = class {
|
||||||
|
#endpoints = {};
|
||||||
|
get endpoints() {
|
||||||
|
return this.#endpoints;
|
||||||
|
}
|
||||||
|
addEndpoint(endpoint, callback, parameterModel, returnModel) {
|
||||||
|
this.#endpoints[endpoint] = { callback, parameterModel, returnModel };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// src/EndpointsController.js
|
||||||
|
var EndpointsController = class extends APIController {
|
||||||
|
#api;
|
||||||
|
constructor(api) {
|
||||||
|
super();
|
||||||
|
this.#api = api;
|
||||||
|
this.addEndpoint("endpoints", this.getEndpoints, VoidModel, EndpointsModel);
|
||||||
|
this.addEndpoint("endpoints:has", this.hasEndpoint, EndpointModel, PROTO.Boolean);
|
||||||
|
}
|
||||||
|
getEndpoints() {
|
||||||
|
return this.#api.endpoints;
|
||||||
|
}
|
||||||
|
hasEndpoint(endpoint) {
|
||||||
|
return this.getEndpoints().includes(endpoint);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// src/AddonAPIServer.js
|
||||||
|
var AddonAPIServer = class {
|
||||||
|
#name;
|
||||||
|
#version;
|
||||||
|
#allEndpoints = [];
|
||||||
|
constructor(name, version) {
|
||||||
|
this.#name = name;
|
||||||
|
this.#version = version;
|
||||||
|
const endpointsController = new EndpointsController(this);
|
||||||
|
this.setupController(endpointsController);
|
||||||
|
}
|
||||||
|
get name() {
|
||||||
|
return this.#name;
|
||||||
|
}
|
||||||
|
get version() {
|
||||||
|
return this.#version;
|
||||||
|
}
|
||||||
|
get endpointBase() {
|
||||||
|
return this.#name + ":";
|
||||||
|
}
|
||||||
|
get endpoints() {
|
||||||
|
return this.#allEndpoints;
|
||||||
|
}
|
||||||
|
setupController(apiController) {
|
||||||
|
for (const [endpoint, features] of Object.entries(apiController.endpoints)) {
|
||||||
|
const { callback, parameterModel, returnModel } = features;
|
||||||
|
const boundCallback = callback.bind(apiController);
|
||||||
|
this.#setupEndpoint(endpoint, boundCallback, parameterModel, returnModel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#setupEndpoint(endpoint, callback, parameterModel, returnDataModel) {
|
||||||
|
const callPacketModel = this.#resolveCallModel(parameterModel);
|
||||||
|
const returnPacketModel = this.#resolveReturnModel(returnDataModel);
|
||||||
|
const endpointPath = this.endpointBase + endpoint;
|
||||||
|
IPC.handle(endpointPath, callPacketModel, returnPacketModel, (callPacket) => {
|
||||||
|
console.info(`Received at ${endpointPath}: ${JSON.stringify(callPacket)}`);
|
||||||
|
const apiVersion = callPacket.apiVersion;
|
||||||
|
const parameters = this.#resolveParameters(callPacket);
|
||||||
|
const returnPacket = this.#handleCallback(apiVersion, callback, parameters);
|
||||||
|
console.info(`Replying ${JSON.stringify(returnPacket)}`);
|
||||||
|
return returnPacket;
|
||||||
|
});
|
||||||
|
this.#allEndpoints.push(endpointPath);
|
||||||
|
}
|
||||||
|
#handleCallback(apiVersion, callback, parameters) {
|
||||||
|
try {
|
||||||
|
this.#assertVersionsMatch(apiVersion);
|
||||||
|
const returnValue = callback(...parameters);
|
||||||
|
return this.#bundleReturnPacket({ code: APIErrorEnum.Success }, returnValue);
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof APICallerError) {
|
||||||
|
const errorPacket2 = this.#resolveErrorPacket(error);
|
||||||
|
return this.#bundleReturnPacket(errorPacket2);
|
||||||
|
}
|
||||||
|
console.error(error, error.stack);
|
||||||
|
const apiError = new APIServerError(error);
|
||||||
|
const errorPacket = this.#resolveErrorPacket(apiError);
|
||||||
|
return this.#bundleReturnPacket(errorPacket);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#assertVersionsMatch(versionToCheck) {
|
||||||
|
if (versionToCheck !== this.version) {
|
||||||
|
const apiVersionMismatchError = new APIVersionMismatchError(this.version, versionToCheck);
|
||||||
|
throw new APICallerError(apiVersionMismatchError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#resolveCallModel(parameterModel) {
|
||||||
|
return PROTO.Object({ ...CallModelShell, parameterMap: parameterModel });
|
||||||
|
}
|
||||||
|
#resolveReturnModel(returnDataModel) {
|
||||||
|
return PROTO.Object({ ...ReturnModelShell, data: PROTO.Optional(returnDataModel) });
|
||||||
|
}
|
||||||
|
#resolveParameters(callPacket) {
|
||||||
|
if (callPacket.parameterMap === void 0)
|
||||||
|
return [];
|
||||||
|
return Object.values(callPacket.parameterMap);
|
||||||
|
}
|
||||||
|
#bundleReturnPacket(errorPacket, returnValue = void 0) {
|
||||||
|
return {
|
||||||
|
apiVersion: this.version,
|
||||||
|
data: returnValue,
|
||||||
|
error: errorPacket
|
||||||
|
};
|
||||||
|
}
|
||||||
|
#resolveErrorPacket(error) {
|
||||||
|
return {
|
||||||
|
code: error.errorCode,
|
||||||
|
name: error.thrownError.name,
|
||||||
|
message: error.thrownError.message
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// src/AddonAPICaller.js
|
||||||
|
import { system as system2 } from "@minecraft/server";
|
||||||
|
|
||||||
|
// src/Errors/APIEndpointNotFoundError.js
|
||||||
|
var APIEndpointNotFoundError = class extends Error {
|
||||||
|
constructor(endpoint) {
|
||||||
|
super(`Endpoint "${endpoint}" was not found.`);
|
||||||
|
this.name = "APIEndpointNotFoundError";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// src/AddonAPICaller.js
|
||||||
|
var AddonAPICaller = class {
|
||||||
|
#name;
|
||||||
|
#version;
|
||||||
|
#validEndpointCache = [];
|
||||||
|
constructor(name, version) {
|
||||||
|
this.#name = name;
|
||||||
|
this.#version = version;
|
||||||
|
}
|
||||||
|
async call(endpoint, parameterMapModel, parameterMap, returnDataModel) {
|
||||||
|
await this.#tryPopulateEndpointCache(endpoint);
|
||||||
|
if (this.#endpointExists(endpoint))
|
||||||
|
return this.#callDirect(endpoint, parameterMapModel, parameterMap, returnDataModel);
|
||||||
|
else
|
||||||
|
throw new APIEndpointNotFoundError(endpoint);
|
||||||
|
}
|
||||||
|
async #tryPopulateEndpointCache(endpoint) {
|
||||||
|
if (this.#validEndpointCache.length === 0) {
|
||||||
|
const endpointBase = endpoint.split(":")[0];
|
||||||
|
const validEndpoints = await this.#callDirect(endpointBase + ":endpoints", VoidModel, void 0, EndpointsModel);
|
||||||
|
this.#validEndpointCache.push(...validEndpoints);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async #callDirect(endpoint, parameterMapModel, parameterMap, returnDataModel) {
|
||||||
|
const parameterPacket = { apiVersion: this.#version, parameterMap };
|
||||||
|
const parameterModel = this.#resolveParameterModel(parameterMapModel);
|
||||||
|
const returnModel = this.#resolveReturnModel(returnDataModel);
|
||||||
|
console.info(`Sending to ${endpoint}: ${JSON.stringify(parameterPacket)}`);
|
||||||
|
const returnPacket = await IPC.invoke(endpoint, parameterModel, parameterPacket, returnModel);
|
||||||
|
console.info(`Received from ${endpoint}: ${JSON.stringify(returnPacket)}`);
|
||||||
|
return this.#unwrapReturnPacket(returnPacket);
|
||||||
|
}
|
||||||
|
#endpointExists(endpoint) {
|
||||||
|
return this.#validEndpointCache.includes(endpoint);
|
||||||
|
}
|
||||||
|
#resolveParameterModel(parameterMapModel) {
|
||||||
|
return PROTO.Object({ ...CallModelShell, parameterMap: parameterMapModel });
|
||||||
|
}
|
||||||
|
#resolveReturnModel(returnDataModel) {
|
||||||
|
return PROTO.Object({ ...ReturnModelShell, data: PROTO.Optional(returnDataModel) });
|
||||||
|
}
|
||||||
|
#unwrapReturnPacket(packet) {
|
||||||
|
const { data, error } = packet;
|
||||||
|
if (error.code === APIErrorEnum.Success)
|
||||||
|
return data;
|
||||||
|
else
|
||||||
|
this.#throwAPIError(packet.error);
|
||||||
|
}
|
||||||
|
#throwAPIError(errorData) {
|
||||||
|
switch (errorData.code) {
|
||||||
|
case APIErrorEnum.Caller:
|
||||||
|
throw new APICallerError(errorData);
|
||||||
|
case APIErrorEnum.Server:
|
||||||
|
throw new APIServerError(errorData);
|
||||||
|
case APIErrorEnum.Unknown:
|
||||||
|
default:
|
||||||
|
throw new Error(errorData.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
export {
|
||||||
|
APICallerError,
|
||||||
|
APIController,
|
||||||
|
APIEndpointNotFoundError,
|
||||||
|
APIErrorEnum,
|
||||||
|
AddonAPICaller,
|
||||||
|
AddonAPIServer,
|
||||||
|
PROTO,
|
||||||
|
VoidModel
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* @license
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2026 OmniacDev
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
@@ -6,8 +6,30 @@ import './options/easyPlace';
|
|||||||
import './options/fastEasyPlace';
|
import './options/fastEasyPlace';
|
||||||
import './options/materialGrabber';
|
import './options/materialGrabber';
|
||||||
|
|
||||||
|
// Menu item handler
|
||||||
|
import './classes/MenuItemHandler';
|
||||||
|
|
||||||
// Commands
|
// Commands
|
||||||
import './commands/construct';
|
import './commands/ConstructCommand';
|
||||||
|
import './commands/CreateCommand';
|
||||||
|
import './commands/DeleteCommand';
|
||||||
|
import './commands/RenameCommand';
|
||||||
|
import './commands/InstancesCommand';
|
||||||
|
import './commands/PlaceCommand';
|
||||||
|
import './commands/MoveCommand';
|
||||||
|
import './commands/EnableCommand';
|
||||||
|
import './commands/LayerCommand';
|
||||||
|
import './commands/NextLayerCommand';
|
||||||
|
import './commands/PrevLayerCommand';
|
||||||
|
import './commands/VerifierCommand';
|
||||||
|
import './commands/BuilderCommand';
|
||||||
|
import './commands/InstanceInfoCommand';
|
||||||
|
import './commands/StatsCommand';
|
||||||
|
import './commands/MaterialsCommand';
|
||||||
|
import './commands/TagCommand';
|
||||||
|
|
||||||
|
// API
|
||||||
|
import './API/ConstructAPI';
|
||||||
|
|
||||||
// Other
|
// Other
|
||||||
import './classes/BlockInfo';
|
import './classes/BlockInfo';
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
{
|
{
|
||||||
"format_version": 2,
|
"format_version": 2,
|
||||||
"header": {
|
"header": {
|
||||||
"name": "Construct [RP] v1.0.9",
|
"name": "Construct [RP] v1.1.0",
|
||||||
"description": "Survival building addon by §aForestOfLight§r.",
|
"description": "Survival building addon by §aForestOfLight§r.",
|
||||||
"uuid": "375ec465-3dc1-429f-8b4c-a337889e1ed4",
|
"uuid": "375ec465-3dc1-429f-8b4c-a337889e1ed4",
|
||||||
"version": [1, 0, 9],
|
"version": [1, 1, 0],
|
||||||
"min_engine_version": [1, 26, 20]
|
"min_engine_version": [1, 26, 30]
|
||||||
},
|
},
|
||||||
"modules": [
|
"modules": [
|
||||||
{
|
{
|
||||||
@@ -16,8 +16,8 @@
|
|||||||
],
|
],
|
||||||
"dependencies": [
|
"dependencies": [
|
||||||
{
|
{
|
||||||
"uuid": "8c0c0153-d8b9-482a-889f-aef922b8fe58", // Construct BP
|
"uuid": "8c0c0153-d8b9-482a-889f-aef922b8fe58",
|
||||||
"version": [1, 0, 9]
|
"version": [1, 1, 0]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"capabilities": [
|
"capabilities": [
|
||||||
|
|||||||
+107
-15
@@ -57,15 +57,6 @@ construct.structure.statistics.stateincorrect=§7Block State Incorrect: §e%s ##
|
|||||||
construct.structure.statistics.incorrect=§7Incorrect: §c%s ## Insert string: number of incorrectly placed blocks
|
construct.structure.statistics.incorrect=§7Incorrect: §c%s ## Insert string: number of incorrectly placed blocks
|
||||||
construct.structure.statistics.missing=§7Missing: §3%s ## Insert string: number of missing blocks
|
construct.structure.statistics.missing=§7Missing: §3%s ## Insert string: number of missing blocks
|
||||||
|
|
||||||
## Structure Block Info Display
|
|
||||||
construct.blockinfo.header=Structure:
|
|
||||||
construct.blockinfo.none=§7None
|
|
||||||
construct.blockinfo.nosupply= §c[No Supply] ## When the targeted block is not in the player's inventory
|
|
||||||
construct.blockinfo.unknown=§7Unknown
|
|
||||||
construct.blockinfo.waterlogged=§7isWaterlogged: §3true
|
|
||||||
|
|
||||||
construct.mainmenu.instance.exists=§cInstance '%s' already exists. Try again with a new name. ## Insert string: instance name
|
|
||||||
construct.mainmenu.instance.notfound=§cStructure ID '%s' not found. If you're looking for a structure that you put in the structures folder, please restart your world and try again. ## Insert string: structure name
|
|
||||||
construct.mainmenu.title=§l§2Construct ## This is the name of the pack.
|
construct.mainmenu.title=§l§2Construct ## This is the name of the pack.
|
||||||
construct.mainmenu.selectinstance=Select an instance:
|
construct.mainmenu.selectinstance=Select an instance:
|
||||||
construct.mainmenu.howto=How to Add/Remove Structures
|
construct.mainmenu.howto=How to Add/Remove Structures
|
||||||
@@ -84,13 +75,14 @@ construct.mainmenu.howto.add.mcstructure=§7To transfer structures §fbetween wo
|
|||||||
construct.mainmenu.howto.remove.header=§cHow to Remove Structures:
|
construct.mainmenu.howto.remove.header=§cHow to Remove Structures:
|
||||||
construct.mainmenu.howto.remove.body=§7- Use the §f/structure delete§7 command to remove a structure from the world.
|
construct.mainmenu.howto.remove.body=§7- Use the §f/structure delete§7 command to remove a structure from the world.
|
||||||
|
|
||||||
## Commands
|
## Structure Block Info Display
|
||||||
construct.commands.construct=Gives you the Construct item. Use it to open the Construct menu.
|
construct.blockinfo.header=Structure:
|
||||||
construct.commands.construct.denyorigin=This command can only be used by players.
|
construct.blockinfo.none=§7None
|
||||||
construct.commands.construct.fail=§cFailed to give you the Construct item.
|
construct.blockinfo.nosupply= §c[No Supply] ## When the targeted block is not in the player's inventory
|
||||||
construct.commands.construct.success=§aYou recieved the Construct item! Use it to open the Construct menu.
|
construct.blockinfo.unknown=§7Unknown
|
||||||
|
construct.blockinfo.waterlogged=§7isWaterlogged: §3true
|
||||||
|
|
||||||
## Options
|
## Builder Options
|
||||||
construct.option.enabled= is now enabled!
|
construct.option.enabled= is now enabled!
|
||||||
construct.option.disabled= is now disabled.
|
construct.option.disabled= is now disabled.
|
||||||
construct.option.easyplace.name=Easy Place
|
construct.option.easyplace.name=Easy Place
|
||||||
@@ -106,3 +98,103 @@ construct.option.materialgrabber.howto=Interact with inventories using the Mater
|
|||||||
construct.option.materialgrabber.grabbed.zero=§7Grabbed 0 items.
|
construct.option.materialgrabber.grabbed.zero=§7Grabbed 0 items.
|
||||||
construct.option.materialgrabber.grabbed.one=§aGrabbed 1 item.
|
construct.option.materialgrabber.grabbed.one=§aGrabbed 1 item.
|
||||||
construct.option.materialgrabber.grabbed.many=§aGrabbed %s items. ## Insert string: number of items transferred to the player
|
construct.option.materialgrabber.grabbed.many=§aGrabbed %s items. ## Insert string: number of items transferred to the player
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
construct.commands.construct=Gives you the Construct item. Use it to open the Construct menu.
|
||||||
|
construct.commands.construct.fail=§cFailed to give you the Construct item.
|
||||||
|
construct.commands.construct.success=§aYou recieved the Construct item! Use it to open the Construct menu.
|
||||||
|
|
||||||
|
## construct:create
|
||||||
|
construct.commands.create=Create a new Construct instance bound to a structure.
|
||||||
|
construct.commands.create.success=§aCreated instance "%1" bound to structure "%2". ## Insert strings: instance name, structure name
|
||||||
|
construct.commands.create.unknownStructure=§cNo structure with id "%1" found in the world. ## Insert string: structure name
|
||||||
|
|
||||||
|
## construct:delete
|
||||||
|
construct.commands.delete=Permanently delete a Construct instance.
|
||||||
|
construct.commands.delete.success=§aDeleted instance "%1". ## Insert string: instance name
|
||||||
|
|
||||||
|
## construct:rename
|
||||||
|
construct.commands.rename=Rename an existing Construct instance.
|
||||||
|
construct.commands.rename.success=§aRenamed instance "%1" to "%2". ## Insert strings: old instance name, new instance name
|
||||||
|
|
||||||
|
## construct:instances
|
||||||
|
construct.commands.instances=List all registered Construct instances.
|
||||||
|
construct.commands.instances.empty=§7No instances registered.
|
||||||
|
construct.commands.instances.header=§aRegistered instances (%1):§r ## Insert string: number of registered instances
|
||||||
|
construct.commands.instances.row=§a%1§r §8[§r%2§8]§r §7%3§r ## Insert strings: instance name, enabled/disabled, location (if available)
|
||||||
|
construct.commands.instances.row.enabled=enabled
|
||||||
|
construct.commands.instances.row.disabled=disabled
|
||||||
|
construct.commands.instances.row.location= at %1 in %2 ## Insert strings: coordinates, dimension
|
||||||
|
construct.commands.instances.row.nolocation= (no location)
|
||||||
|
|
||||||
|
## construct:place
|
||||||
|
construct.commands.place=Place a Construct instance in the world.
|
||||||
|
construct.commands.place.success=§aPlaced instance "%1" at %2 in %3. ## Insert strings: instance name, coordinates, dimension
|
||||||
|
|
||||||
|
## construct:move
|
||||||
|
construct.commands.move=Reposition a Construct instance without toggling its enabled state.
|
||||||
|
construct.commands.move.success=§aMoved instance "%1" to %2 in %3. ## Insert strings: instance name, coordinates, dimension
|
||||||
|
construct.commands.move.locationRequired=§cMust provide a dimension and coordinates when not running as a player.
|
||||||
|
|
||||||
|
## construct:enable
|
||||||
|
construct.commands.enable=Enable or disable a Construct instance.
|
||||||
|
construct.commands.enable.true=§aEnabled instance "%1". ## Insert string: instance name
|
||||||
|
construct.commands.enable.false=§aDisabled instance "%1". ## Insert string: instance name
|
||||||
|
construct.commands.error.noLocation=§cInstance "%1" has no saved location.
|
||||||
|
|
||||||
|
## construct:layer
|
||||||
|
construct.commands.layer=Set the active layer of a Construct instance (Use 0 for the whole structure).
|
||||||
|
construct.commands.layer.success=§aSet instance "%1" layer to %2. ## Insert strings: instance name, layer number
|
||||||
|
construct.commands.layer.outOfBounds=§cLayer %1 is out of bounds for instance "%2" (max %3). ## Insert strings: layer number, instance name, max layer number
|
||||||
|
|
||||||
|
## construct:nextlayer
|
||||||
|
construct.commands.nextlayer=Step a Construct instance one layer up.
|
||||||
|
construct.commands.nextlayer.success=§aInstance "%1" advanced to layer %2. ## Insert strings: instance name, layer number
|
||||||
|
|
||||||
|
## construct:prevlayer
|
||||||
|
construct.commands.prevlayer=Step a Construct instance one layer down.
|
||||||
|
construct.commands.prevlayer.success=§aInstance "%1" stepped back to layer %2. ## Insert strings: instance name, layer number
|
||||||
|
|
||||||
|
## construct:verifier
|
||||||
|
construct.commands.verifier=Toggle block validation overlay for a Construct instance.
|
||||||
|
construct.commands.verifier.enabled=§aEnabled verifier for instance "%1". ## Insert string: instance name
|
||||||
|
construct.commands.verifier.disabled=§aDisabled verifier for instance "%1". ## Insert string: instance name
|
||||||
|
|
||||||
|
## construct:builder
|
||||||
|
construct.commands.builder=Toggle your Construct builder options.
|
||||||
|
construct.commands.builder.success=§aSet option "%1" to %2. ## Insert strings: option name, option value
|
||||||
|
construct.commands.builder.unknownOption=§cUnknown option "%1". ## Insert string: option name
|
||||||
|
|
||||||
|
## construct:instanceinfo
|
||||||
|
construct.commands.instanceinfo=Print Construct instance details to chat.
|
||||||
|
construct.commands.instanceinfo.header=§a=== Instance Info for "%1" ===§r ## Insert string: instance name
|
||||||
|
construct.commands.instanceinfo.structure=§7Structure:§r %1 ## Insert string: structure name
|
||||||
|
construct.commands.instanceinfo.location=§7Location:§r %1 in %2 ## Insert strings: coordinates, dimension
|
||||||
|
construct.commands.instanceinfo.noLocation=§7Location:§r (none) ## Used when the instance has no saved location
|
||||||
|
construct.commands.instanceinfo.enabled=§7Enabled:§r %1 ## Insert string: true/false indicating whether the instance is enabled
|
||||||
|
construct.commands.instanceinfo.layer=§7Layer:§r %1 / %2 ## Insert strings: current layer, max layer
|
||||||
|
construct.commands.instanceinfo.verifier=§7Verifier running:§r %1 ## Insert string: true/false indicating whether the verifier is running
|
||||||
|
construct.commands.instanceinfo.size=§7Size:§r %1 (%2 blocks) ## Insert strings: size (x, y, z), total block volume count
|
||||||
|
|
||||||
|
## construct:stats
|
||||||
|
construct.commands.stats=Run the Construct structure verifier and print statistics.
|
||||||
|
construct.commands.stats.alreadyRunning=§cA verification is already in progress. Please wait until it finishes.
|
||||||
|
|
||||||
|
## construct:materials
|
||||||
|
construct.commands.materials=Print the material list for a Construct instance.
|
||||||
|
construct.commands.materials.headerAll=§aMaterials for "%1":§r ## Insert string: instance name
|
||||||
|
construct.commands.materials.headerMissing=§aMissing materials for "%1":§r ## Insert string: instance name
|
||||||
|
construct.commands.materials.empty=§7(no materials)
|
||||||
|
|
||||||
|
## construct:tag
|
||||||
|
construct.commands.tag=Rename the held Construct item to an instance name for quick-open.
|
||||||
|
construct.commands.tag.success=§aTagged held Construct item with instance "%1". ## Insert string: instance name
|
||||||
|
construct.commands.tag.notHoldingItem=§cYou must be holding a Construct item.
|
||||||
|
|
||||||
|
## Errors
|
||||||
|
construct.error.invalidCommandSource=§cThis command cannot be run from this source.
|
||||||
|
construct.error.instanceNotFound=§cInstance "%1" not found. ## Insert string: instance name
|
||||||
|
construct.error.instanceExists=§cInstance "%1" already exists. Try again with a new name. ## Insert string: instance name
|
||||||
|
construct.error.structureNotFound=§cStructure ID "%1" not found. If you're looking for a structure that you put in the structures folder, please restart your world and try again. ## Insert string: structure name
|
||||||
|
construct.error.dimensionNotFound=§cDimension "%1" not found. ## Insert string: dimension name
|
||||||
|
construct.error.notAPlayer=§cThis command can only be used by players.
|
||||||
Reference in New Issue
Block a user