Add and remove structure with outline
This commit is contained in:
@@ -2,71 +2,42 @@
|
|||||||
<a href="./pack_icon.png">
|
<a href="./pack_icon.png">
|
||||||
<img src="./pack_icon.png" alt="Canopy Extension Example Icon" width="100" height="100">
|
<img src="./pack_icon.png" alt="Canopy Extension Example Icon" width="100" height="100">
|
||||||
</a>
|
</a>
|
||||||
<p><b>Canopy Extension Example</b></p>
|
<p><b>StrucTool</b></p>
|
||||||
|
|
||||||
[](https://app.codacy.com/gh/ForestOfLight/Canopy-Extension-Example/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade)
|
[](https://app.codacy.com/gh/ForestOfLight/StrucTool/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade)
|
||||||
[-brightgreen)](https://feedback.minecraft.net/hc/en-us/sections/360001186971-Release-Changelogs)
|
[-brightgreen)](https://feedback.minecraft.net/hc/en-us/sections/360001186971-Release-Changelogs)
|
||||||
[](LICENSE)
|
[](LICENSE)
|
||||||
[](https://discord.gg/9KGche8fxm)
|
[](https://discord.gg/9KGche8fxm)
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
Extensions?! In a behavior pack?! Yep! This repo contains an example pack designed to teach you how to create your own extensions for **Canopy**. All extensions must be added to the same world as **Canopy** (the dependencies in the `manifest.json` should ensure that).
|
Intro paragraph.
|
||||||
|
|
||||||
## How this example pack functions ingame
|
> [!IMPORTANT]
|
||||||
|
> This addon is a **Canopy Extension**, which means **Canopy** must be installed in your world for it to work.
|
||||||
|
> [Download **Canopy** here!](https://github.com/ForestOfLight/Canopy)
|
||||||
|
|
||||||
What **Canopy** handles:
|
## Usage
|
||||||
|
|
||||||
**`./canopy <rule> [true/false]`**
|
All commands are prefixed with `./`. Do `./help` for more information.
|
||||||
**Canopy** will automatically include the names of your rules in the `./canopy` command.
|
|
||||||
|
|
||||||
**`./help`**
|
**Usage: `./struct add <name>`**
|
||||||
**Canopy** will automatically include a Rules and a Commands help page for your extension.
|
Adds a new structure.
|
||||||
|
|
||||||

|
**Usage: `./struct remove <name>`**
|
||||||
|
Removes a structure.
|
||||||
|
|
||||||
What the extension handles:
|
## Roadmap
|
||||||
|
|
||||||
**`./example [message]`**
|
- [ ] Add new structures by pasting a string
|
||||||
This command sends a message to the chat. It also has a default message if no message is provided. (Alias: `./ex`)
|
- [ ] Make a viewable material list
|
||||||
|
- [ ] structuraMode & holoprintMode rules for ease of use
|
||||||
**`commandExample`**
|
- [ ] Make automatic material grabbing from inventories
|
||||||
This rule will enable the `./example` command.
|
- [ ] structure placement (ghost and solid)
|
||||||
|
- [ ] easyPlace rule
|
||||||
**`exampleRule`**
|
- [ ] more litematica features!
|
||||||
This rule will enable a message in chat when you push a button.
|
- [ ] send structure as a scriptevent so that other addons can use it
|
||||||
|
|
||||||
## How to create an extension
|
|
||||||
|
|
||||||
1. Clone this repo.
|
|
||||||
```sh
|
|
||||||
git clone https://github.com/ForestOfLight/canopy-extension-example.git
|
|
||||||
```
|
|
||||||
2. Remove all files that are not the `pack_icon.png`, the `manifest.json` file, or in the `scripts/` folder.
|
|
||||||
3. Rename the folder to your extension's name.
|
|
||||||
```sh
|
|
||||||
mv canopy-extension-example your-extension-name
|
|
||||||
```
|
|
||||||
4. Open the folder in your favorite code editor.
|
|
||||||
5. Edit the `manifest.json` file to match your extension's information.
|
|
||||||
6. Replace the `pack_icon.png` with your extension's icon.
|
|
||||||
7. Edit the `main.js` file to add your extension's functionality.
|
|
||||||
|
|
||||||
> [!TIP]
|
|
||||||
> Most information about how to code a **Canopy Extension** can be found in the `main.js` file.
|
|
||||||
|
|
||||||
## How to use an extension
|
|
||||||
|
|
||||||
1. Add the extension pack to the same world as the **Canopy** RP & BP.
|
|
||||||
2. Profit.
|
|
||||||
|
|
||||||
**Canopy** will automatically detect the extension and load it!
|
|
||||||
|
|
||||||
## How does this work?
|
|
||||||
|
|
||||||
**Canopy** and its extensions all communicate using scriptevents! The vanilla `/scriptevent` command allows for sending strings to Behavior Packs and the `ScriptEventCommandMessage` event allows for recieving them. **Canopy** uses [MCBE-IPC](https://https://github.com/OmniacDev/MCBE-IPC), a library that streamlines the process of sending and recieving scriptevents.
|
|
||||||
**Canopy Extensions** use these to communicate with **Canopy** when new rules and commands are added. **Canopy** uses these as well to get rule data, set rule data, and call command callbacks from extensions.
|
|
||||||
|
|
||||||
## Issues & Suggestions
|
## Issues & Suggestions
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 82 KiB |
+14
-6
@@ -1,9 +1,9 @@
|
|||||||
{
|
{
|
||||||
"format_version": 2,
|
"format_version": 2,
|
||||||
"header": {
|
"header": {
|
||||||
"name": "Canopy Extension Example",
|
"name": "StrucTool",
|
||||||
"description": "Example extension for §l§aCanopy§r by §aForestOfLight§r.",
|
"description": "Survival building extension for §l§aCanopy§r by §aForestOfLight§r.",
|
||||||
"uuid": "e4e844d5-f45b-4623-82a7-534d44bb4c6f",
|
"uuid": "8c0c0153-d8b9-482a-889f-aef922b8fe58",
|
||||||
"min_engine_version": [1, 21, 60],
|
"min_engine_version": [1, 21, 60],
|
||||||
"version": [1, 0, 0]
|
"version": [1, 0, 0]
|
||||||
},
|
},
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
{
|
{
|
||||||
"description": "Behavior Pack Module",
|
"description": "Behavior Pack Module",
|
||||||
"type": "data",
|
"type": "data",
|
||||||
"uuid": "1ce9eab8-0d4a-4e40-a899-575791a1b40d",
|
"uuid": "f4d52ae1-2c26-4938-b8c2-7e455d495620",
|
||||||
"version": [1, 0, 0]
|
"version": [1, 0, 0]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
"type": "script",
|
"type": "script",
|
||||||
"language": "javascript",
|
"language": "javascript",
|
||||||
"entry": "scripts/main.js",
|
"entry": "scripts/main.js",
|
||||||
"uuid": "839a937e-b4f9-4484-84a4-0db984822f25",
|
"uuid": "8bb9a1e4-0531-4b93-8ade-3880bfe1e2fc",
|
||||||
"version": [1, 0, 0]
|
"version": [1, 0, 0]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@@ -28,6 +28,10 @@
|
|||||||
"module_name": "@minecraft/server",
|
"module_name": "@minecraft/server",
|
||||||
"version": "1.18.0-beta"
|
"version": "1.18.0-beta"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"module_name": "@minecraft/server-ui",
|
||||||
|
"version": "1.4.0-beta"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"uuid": "bcf34368-ed0c-4cf7-938e-582cccf9950d", // Canopy RP
|
"uuid": "bcf34368-ed0c-4cf7-938e-582cccf9950d", // Canopy RP
|
||||||
"version": [1, 0, 2]
|
"version": [1, 0, 2]
|
||||||
@@ -36,5 +40,9 @@
|
|||||||
"uuid": "7f6b23df-a583-476b-b0e4-87457e65f7c0", // Canopy BP
|
"uuid": "7f6b23df-a583-476b-b0e4-87457e65f7c0", // Canopy BP
|
||||||
"version": [1, 3, 8]
|
"version": [1, 3, 8]
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"metadata": {
|
||||||
|
"authors": [ "ForestOfLight" ],
|
||||||
|
"license": "MIT"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,82 @@
|
|||||||
|
import { MolangVariableMap, system, world } from "@minecraft/server";
|
||||||
|
import { Vector } from "../lib/Vector";
|
||||||
|
|
||||||
|
const drawFrequency = 8;
|
||||||
|
const drawParticle = "minecraft:villager_happy";
|
||||||
|
|
||||||
|
export class Outliner {
|
||||||
|
dimension;
|
||||||
|
min = new Vector();
|
||||||
|
max = new Vector();
|
||||||
|
#shouldDraw = true;
|
||||||
|
|
||||||
|
#drawParticles = [];
|
||||||
|
#runner = null;
|
||||||
|
|
||||||
|
constructor(dimension, min, max) {
|
||||||
|
this.dimension = world.getDimension(dimension);
|
||||||
|
this.min = new Vector(min.x, min.y, min.z);
|
||||||
|
this.max = new Vector(max.x, max.y, max.z);
|
||||||
|
}
|
||||||
|
|
||||||
|
startDraw() {
|
||||||
|
this.#shouldDraw = true;
|
||||||
|
this.#runner = system.runInterval(() => this.draw(), drawFrequency);
|
||||||
|
}
|
||||||
|
|
||||||
|
stopDraw() {
|
||||||
|
system.clearRun(this.#runner);
|
||||||
|
this.shouldDraw = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
draw() {
|
||||||
|
if (!this.#shouldDraw) return;
|
||||||
|
this.#drawParticles.length = 0;
|
||||||
|
this.#drawParticles.push(...this.getCubiodParticleLocations());
|
||||||
|
|
||||||
|
for (const [particleType, location] of this.#drawParticles) {
|
||||||
|
try {
|
||||||
|
this.dimension.spawnParticle(particleType, location);
|
||||||
|
} catch {
|
||||||
|
/* pass */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getCubiodParticleLocations() {
|
||||||
|
const vertices = [
|
||||||
|
new Vector(this.min.x, this.min.y, this.min.z),
|
||||||
|
new Vector(this.max.x, this.min.y, this.min.z),
|
||||||
|
new Vector(this.min.x, this.max.y, this.min.z),
|
||||||
|
new Vector(this.max.x, this.max.y, this.min.z),
|
||||||
|
new Vector(this.min.x, this.min.y, this.max.z),
|
||||||
|
new Vector(this.max.x, this.min.y, this.max.z),
|
||||||
|
new Vector(this.min.x, this.max.y, this.max.z),
|
||||||
|
new Vector(this.max.x, this.max.y, this.max.z)
|
||||||
|
];
|
||||||
|
const edges = [
|
||||||
|
[0, 1],
|
||||||
|
[0, 2],
|
||||||
|
[0, 4],
|
||||||
|
[1, 3],
|
||||||
|
[1, 5],
|
||||||
|
[2, 3],
|
||||||
|
[2, 6],
|
||||||
|
[3, 7],
|
||||||
|
[4, 5],
|
||||||
|
[4, 6],
|
||||||
|
[5, 7],
|
||||||
|
[6, 7]
|
||||||
|
];
|
||||||
|
const edgePoints = [];
|
||||||
|
for (const edge of edges) {
|
||||||
|
const [startVertex, endVertex] = [vertices[edge[0]], vertices[edge[1]]];
|
||||||
|
const resolution = Math.min(Math.floor(endVertex.subtract(startVertex).length), 16);
|
||||||
|
for (let i = 1; i < resolution; i++) {
|
||||||
|
const t = i / resolution;
|
||||||
|
edgePoints.push(startVertex.lerp(endVertex, t));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return vertices.concat(edgePoints).map((v) => [drawParticle, v]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
import { MinecraftDimensionTypes, world } from "@minecraft/server";
|
||||||
|
import { Outliner } from "./Outliner";
|
||||||
|
|
||||||
|
export class Structure {
|
||||||
|
#structure;
|
||||||
|
#options = {
|
||||||
|
isPlaced: false,
|
||||||
|
dimensionId: MinecraftDimensionTypes.overworld,
|
||||||
|
location: { x: 0, y: 0, z: 0 },
|
||||||
|
rotation: 0,
|
||||||
|
mirror: false
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(structureName) {
|
||||||
|
this.name = structureName;
|
||||||
|
this.#structure = world.structureManager.get(structureName);
|
||||||
|
this.#options = this.loadOptions();
|
||||||
|
}
|
||||||
|
|
||||||
|
loadOptions() {
|
||||||
|
try {
|
||||||
|
return JSON.parse(world.getDynamicProperty(`structOptions:${this.name}`));
|
||||||
|
} catch (e) {
|
||||||
|
world.setDynamicProperty(`structOptions:${this.name}`, JSON.stringify(this.#options));
|
||||||
|
}
|
||||||
|
return this.#options;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateOptions() {
|
||||||
|
world.setDynamicProperty(`structOptions:${this.name}`, JSON.stringify(this.#options));
|
||||||
|
}
|
||||||
|
|
||||||
|
getName() {
|
||||||
|
return this.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
getStructure() {
|
||||||
|
return this.#structure;
|
||||||
|
}
|
||||||
|
|
||||||
|
getOutlineLimits() {
|
||||||
|
if (!this.#options.isPlaced)
|
||||||
|
throw new Error(`[StrucTool] Structure '${this.name}' is not placed.`);
|
||||||
|
return {
|
||||||
|
min: {
|
||||||
|
x: this.#options.location.x,
|
||||||
|
y: this.#options.location.y,
|
||||||
|
z: this.#options.location.z
|
||||||
|
},
|
||||||
|
max: {
|
||||||
|
x: this.#options.location.x + this.#structure.size.x,
|
||||||
|
y: this.#options.location.y + this.#structure.size.y,
|
||||||
|
z: this.#options.location.z + this.#structure.size.z
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
place(dimensionId, location) {
|
||||||
|
this.#options = {
|
||||||
|
isPlaced: true,
|
||||||
|
dimensionId,
|
||||||
|
location: { x: Math.floor(location.x), y: Math.floor(location.y), z: Math.floor(location.z) },
|
||||||
|
};
|
||||||
|
this.updateOptions();
|
||||||
|
this.outliner = new Outliner(dimensionId, this.getOutlineLimits().min, this.getOutlineLimits().max);
|
||||||
|
this.outliner.startDraw();
|
||||||
|
}
|
||||||
|
|
||||||
|
remove() {
|
||||||
|
if (!this.#options.isPlaced)
|
||||||
|
throw new Error(`[StrucTool] Structure '${this.name}' is not placed.`);
|
||||||
|
this.#options.isPlaced = false;
|
||||||
|
this.updateOptions();
|
||||||
|
this.outliner.stopDraw();
|
||||||
|
delete this.outliner;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import { world } from '@minecraft/server';
|
||||||
|
import { Structure } from './Structure';
|
||||||
|
|
||||||
|
class StructureCollection {
|
||||||
|
#structures;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.#structures = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
add(name) {
|
||||||
|
const struct = world.structureManager.get(name);
|
||||||
|
this.#structures[name] = new Structure(name, struct);
|
||||||
|
}
|
||||||
|
|
||||||
|
get(name) {
|
||||||
|
const structure = this.#structures[name];
|
||||||
|
if (!structure) {
|
||||||
|
throw new Error(`Structure ${name} not found.`);
|
||||||
|
}
|
||||||
|
return structure;
|
||||||
|
}
|
||||||
|
|
||||||
|
remove(name) {
|
||||||
|
const struct = this.get(name);
|
||||||
|
struct.remove();
|
||||||
|
delete this.#structures[name];
|
||||||
|
}
|
||||||
|
|
||||||
|
place(name, dimensionId, location) {
|
||||||
|
const struct = this.get(name);
|
||||||
|
if (!struct) {
|
||||||
|
throw new Error(`Structure ${name} not found.`);
|
||||||
|
}
|
||||||
|
struct.place(dimensionId, location);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const structureCollection = new StructureCollection();
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import { Command } from '../lib/canopy/CanopyExtension';
|
||||||
|
import { extension } from '../config';
|
||||||
|
import { structureCollection } from '../classes/StructureCollection';
|
||||||
|
|
||||||
|
const structCmd = new Command({
|
||||||
|
name: 'struct',
|
||||||
|
description: { text: 'Manages current StrucTool structures.' },
|
||||||
|
usage: 'struct',
|
||||||
|
callback: structCommand,
|
||||||
|
args: [
|
||||||
|
{ type: 'string', name: 'option' },
|
||||||
|
{ type: 'string', name: 'name' }
|
||||||
|
]
|
||||||
|
});
|
||||||
|
extension.addCommand(structCmd);
|
||||||
|
|
||||||
|
function structCommand(sender, args) {
|
||||||
|
const { option, name, structure } = args;
|
||||||
|
switch (option) {
|
||||||
|
case 'add':
|
||||||
|
addStructure(sender, name, structure);
|
||||||
|
break;
|
||||||
|
case 'remove':
|
||||||
|
removeStructure(sender, name);
|
||||||
|
break;
|
||||||
|
case 'place':
|
||||||
|
structureCollection.place(name, sender.dimension.id, sender.location);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
structCmd.sendUsage(sender);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function addStructure(sender, name, filename) {
|
||||||
|
structureCollection.add(name, filename);
|
||||||
|
sender.sendMessage({ text: `Added structure '${name}'` });
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeStructure(sender, name) {
|
||||||
|
structureCollection.remove(name);
|
||||||
|
sender.sendMessage({ text: `Removed structure '${name}'` });
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import { CanopyExtension } from './lib/canopy/CanopyExtension';
|
||||||
|
|
||||||
|
export const extension = new CanopyExtension({
|
||||||
|
author: 'ForestOfLight',
|
||||||
|
name: 'StrucTool',
|
||||||
|
description: 'Survival building extension for §l§aCanopy§r!',
|
||||||
|
version: '1.0.0'
|
||||||
|
});
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
/**
|
||||||
|
* Part of ItemStack Database by @gameza_src
|
||||||
|
* Unknown author
|
||||||
|
*/
|
||||||
|
const isVec3Symbol = Symbol("isVec3");
|
||||||
|
export function Vector(x = 0, y = 0, z = 0) {
|
||||||
|
if (new.target) {
|
||||||
|
this.x = Number(x);
|
||||||
|
this.y = Number(y);
|
||||||
|
this.z = Number(z);
|
||||||
|
} else {return { x: Number(x), y: Number(y), z: Number(z), __proto__: Vector.prototype };}
|
||||||
|
}
|
||||||
|
Vector.magnitude = function magnitude(vec) { return Math.sqrt(vec.x * vec.x + vec.y * vec.y + vec.z * vec.z); }
|
||||||
|
Vector.normalize = function normalize(vec) { const l = Vector.magnitude(vec); return { x: vec.x / l, y: vec.y / l, z: vec.z / l, __proto__: Vector.prototype }; }
|
||||||
|
Vector.cross = function crossProduct(a, b) { return { x: a.y * b.z - a.z * b.y, y: a.x * b.z - a.z * b.x, z: a.x * b.y - a.y * b.x, __proto__: Vector.prototype }; }
|
||||||
|
Vector.dot = function dot(a, b) { return a.x * b.x + a.y * b.y + a.z * b.z; }
|
||||||
|
Vector.angleBetween = function angleBetween(a, b) { return Math.acos(Vector.dot(a, b) / (Vector.magnitude(a) * Vector.magnitude(b))); }
|
||||||
|
Vector.subtract = function subtract(a, b) { return { x: a.x - b.x, y: a.y - b.y, z: a.z - b.z, __proto__: Vector.prototype } };
|
||||||
|
Vector.add = function add(a, b) { return { x: a.x + b.x, y: a.y + b.y, z: a.z + b.z, __proto__: Vector.prototype } };
|
||||||
|
Vector.multiply = function multiply(vec, num) {
|
||||||
|
if (typeof num == "number") return { x: vec.x * num, y: vec.y * num, z: vec.z * num, __proto__: Vector.prototype };
|
||||||
|
return { x: vec.x * num.x, y: vec.y * num.y, z: vec.z * num.z, __proto__: Vector.prototype };
|
||||||
|
}
|
||||||
|
Vector.isVec3 = function isVec3(vec) { return vec[isVec3Symbol] === true; }
|
||||||
|
Vector.floor = function floor(vec) { return { x: Math.floor(vec.x), y: Math.floor(vec.y), z: Math.floor(vec.z), __proto__: Vector.prototype }; }
|
||||||
|
Vector.projection = function projection(a, b) { return Vector.multiply(b, Vector.dot(a, b) / ((b.x * b.x + b.y * b.y + b.z * b.z) ** 2)); }
|
||||||
|
Vector.rejection = function rejection(a, b) { return Vector.subtract(a, Vector.projection(a, b)); }
|
||||||
|
Vector.reflect = function reflect(v, n) { return Vector.subtract(v, Vector.multiply(n, 2 * Vector.dot(v, n))); }
|
||||||
|
Vector.lerp = function lerp(a, b, t) { return Vector.multiply(a, 1 - t).add(Vector.multiply(b, t)); }
|
||||||
|
Vector.distance = function distance(a, b) { return Vector.magnitude(Vector.subtract(a, b)); }
|
||||||
|
Vector.from = function from(object) {
|
||||||
|
if (Vector.isVec3(object)) return object;
|
||||||
|
if (Array.isArray(object)) return new Vector(object[0], object[1], object[2]);
|
||||||
|
const { x = 0, y = 0, z = 0 } = object ?? {};
|
||||||
|
return { x: Number(x), y: Number(y), z: Number(z), __proto__: Vector.prototype };
|
||||||
|
}
|
||||||
|
Vector.sort = function sort(vec1, vec2) {
|
||||||
|
const [x1, x2] = vec1.x < vec2.x ? [vec1.x, vec2.x] : [vec2.x, vec1.x];
|
||||||
|
const [y1, y2] = vec1.y < vec2.y ? [vec1.y, vec2.y] : [vec2.y, vec1.y];
|
||||||
|
const [z1, z2] = vec1.z < vec2.z ? [vec1.z, vec2.z] : [vec2.z, vec1.z];
|
||||||
|
return [{ x: x1, y: y1, z: z1, __proto__: Vector.prototype }, { x: x2, y: y2, z: z2, __proto__: Vector.prototype }];
|
||||||
|
}
|
||||||
|
Vector.up = { x: 0, y: 1, z: 0, __proto__: Vector.prototype };
|
||||||
|
Vector.down = { x: 0, y: -1, z: 0, __proto__: Vector.prototype };
|
||||||
|
Vector.right = { x: 1, y: 0, z: 0, __proto__: Vector.prototype };
|
||||||
|
Vector.left = { x: -1, y: 0, z: 0, __proto__: Vector.prototype };
|
||||||
|
Vector.forward = { x: 0, y: 0, z: 1, __proto__: Vector.prototype };
|
||||||
|
Vector.backward = { x: 0, y: 0, z: -1, __proto__: Vector.prototype };
|
||||||
|
Vector.zero = { x: 0, y: 0, z: 0, __proto__: Vector.prototype };
|
||||||
|
Vector.prototype = {
|
||||||
|
distance(vec) { return Vector.distance(this, vec); },
|
||||||
|
lerp(vec, t) { return Vector.lerp(this, vec, t); },
|
||||||
|
projection(vec) { return Vector.projection(this, vec); },
|
||||||
|
reflect(vec) { return Vector.reflect(this, vec); },
|
||||||
|
rejection(vec) { return Vector.rejection(this, vec); },
|
||||||
|
cross(vec) { return Vector.cross(this, vec); },
|
||||||
|
dot(vec) { return Vector.dot(this, vec); },
|
||||||
|
floor() { return Vector.floor(this); },
|
||||||
|
add(vec) { return Vector.add(this, vec); },
|
||||||
|
subtract(vec) { return Vector.subtract(this, vec); },
|
||||||
|
multiply(num) { return Vector.multiply(this, num); },
|
||||||
|
get length() { return Vector.magnitude(this); },
|
||||||
|
get normalized() { return Vector.normalize(this); },
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
z: 0,
|
||||||
|
[isVec3Symbol]: true,
|
||||||
|
toString() { return `<${this.x}, ${this.y}, ${this.z}>`; }
|
||||||
|
}
|
||||||
+3
-96
@@ -1,97 +1,4 @@
|
|||||||
import { CanopyExtension, Command, Rule } from './lib/canopy/CanopyExtension';
|
// Rules
|
||||||
import { world } from '@minecraft/server';
|
|
||||||
|
|
||||||
/**
|
// Commands
|
||||||
* Create a new CanopyExtension instance to define your extension.
|
import './commands/struct.js';
|
||||||
*/
|
|
||||||
const extension = new CanopyExtension({
|
|
||||||
author: 'YourName',
|
|
||||||
name: 'ExampleExtension',
|
|
||||||
description: 'Example extension for §l§aCanopy§r!',
|
|
||||||
version: '1.0.0'
|
|
||||||
});
|
|
||||||
|
|
||||||
// --------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adding a new Rule:
|
|
||||||
*/
|
|
||||||
const exampleRule = new Rule({
|
|
||||||
identifier: 'exampleRule', // The name of the rule
|
|
||||||
description: { text: 'An example rule that prints a message in chat when you hit a button.' }, // Shows up in the help command. Must be a RawMessage type (translatable!).
|
|
||||||
// Optional:
|
|
||||||
contingentRules: [], // Rules that will be enabled when this rule is enabled
|
|
||||||
independentRules: [], // Rules that will be disabled when this rule is enabled
|
|
||||||
onEnableCallback: () => world.afterEvents.buttonPush.subscribe(onButtonPush), // Function to run when the rule is enabled (also runs when the extension is loaded, if the rule is already enabled)
|
|
||||||
onDisableCallback: () => world.afterEvents.buttonPush.unsubscribe(onButtonPush) // Function to run when the rule is disabled
|
|
||||||
});
|
|
||||||
extension.addRule(exampleRule);
|
|
||||||
|
|
||||||
// use the rule to control your code flow
|
|
||||||
function onButtonPush(event) {
|
|
||||||
if (!exampleRule.getValue())
|
|
||||||
return;
|
|
||||||
if (event.source === undefined) // Always check for undefined entities and players. Simulated players always show up as undefined in events.
|
|
||||||
return;
|
|
||||||
event.source.sendMessage('§aYou pushed a button!');
|
|
||||||
}
|
|
||||||
|
|
||||||
// --------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Making a second new rule which we can use to enable the example command:
|
|
||||||
* (This is not required to make a new command, but it is helpful to allow admins to disable your commands.)
|
|
||||||
*/
|
|
||||||
const commandExampleRule = new Rule({
|
|
||||||
identifier: 'commandExample',
|
|
||||||
description: { text: 'Enables the example command.' } // Shows up in the help command. RawMessage type.
|
|
||||||
});
|
|
||||||
extension.addRule(commandExampleRule);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adding a new Command:
|
|
||||||
*/
|
|
||||||
const exampleCommand = new Command({
|
|
||||||
name: 'example', // The name of the command
|
|
||||||
description: { text: 'An example command that prints your message in chat.' }, // Shows up in the help command. RawMessage type.
|
|
||||||
usage: 'example [message]', // The usage of the command that shows up in the help command & when used incorrectly
|
|
||||||
callback: exampleCommandCallback, // The function to run when the command is executed
|
|
||||||
// Optional:
|
|
||||||
args: [
|
|
||||||
{ type: 'string|number', name: 'message' } // The arguments that the command takes. 'string|number' means it can be either a string or a number
|
|
||||||
],
|
|
||||||
contingentRules: ['commandExample'], // Rules that must be true for the command to be enabled
|
|
||||||
adminOnly: false, // Whether the command can only be run by admins (users with the 'CanopyAdmin' tag)
|
|
||||||
helpEntries: [ // Additional help entries that show up in the help command
|
|
||||||
{ usage: `example`, description: { text: `Run the example command with the default message.` } } // Description is a RawMessage type.
|
|
||||||
],
|
|
||||||
helpHidden: false // Whether the command should be hidden from the help command.
|
|
||||||
});
|
|
||||||
extension.addCommand(exampleCommand);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adding a command alias:
|
|
||||||
* This is essentially just adding a new command that runs the same function as the original command and hiding its help.
|
|
||||||
*/
|
|
||||||
const exampleCommandAlias = new Command({
|
|
||||||
name: 'ex',
|
|
||||||
description: { text: 'An alias for the example command.' },
|
|
||||||
usage: 'ex [message]',
|
|
||||||
callback: exampleCommandCallback,
|
|
||||||
args: [
|
|
||||||
{ type: 'string|number', name: 'message' }
|
|
||||||
],
|
|
||||||
contingentRules: ['commandExample'],
|
|
||||||
helpHidden: true
|
|
||||||
});
|
|
||||||
extension.addCommand(exampleCommandAlias);
|
|
||||||
|
|
||||||
// now you can define the function that will be called when the command is executed
|
|
||||||
function exampleCommandCallback(sender, args) {
|
|
||||||
let { message } = args;
|
|
||||||
if (message === null)
|
|
||||||
message = 'Hello, world!';
|
|
||||||
if (!isNaN(parseFloat(message)) && isFinite(message))
|
|
||||||
message = message.toString();
|
|
||||||
sender.sendMessage(`§aYou ran the example command with the message: §7${message}`);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { FormCancelationReason } from '@minecraft/server-ui';
|
||||||
|
|
||||||
|
export async function forceShow(player, form, timeout = Infinity) {
|
||||||
|
const startTick = system.currentTick;
|
||||||
|
while ((system.currentTick - startTick) < timeout) {
|
||||||
|
const response = await form.show(player);
|
||||||
|
if (startTick + 1 === system.currentTick && response.cancelationReason === FormCancelationReason.UserBusy)
|
||||||
|
player.sendMessage({ translate: 'commands.canopy.menu.busy' });
|
||||||
|
if (response.cancelationReason !== FormCancelationReason.UserBusy)
|
||||||
|
return response;
|
||||||
|
}
|
||||||
|
throw new Error({ translate: 'commands.canopy.menu.timeout', with: [String(timeout)] });
|
||||||
|
};
|
||||||
Binary file not shown.
Reference in New Issue
Block a user