diff --git a/README.md b/README.md
index 36b2827..1573aa2 100644
--- a/README.md
+++ b/README.md
@@ -2,71 +2,42 @@
-
Canopy Extension Example
+ StrucTool
-[](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)
-[](LICENSE)
+[](LICENSE)
[](https://discord.gg/9KGche8fxm)
---
-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 [true/false]`**
-**Canopy** will automatically include the names of your rules in the `./canopy` command.
+All commands are prefixed with `./`. Do `./help` for more information.
-**`./help`**
-**Canopy** will automatically include a Rules and a Commands help page for your extension.
+**Usage: `./struct add `**
+Adds a new structure.
-
+**Usage: `./struct remove `**
+Removes a structure.
-What the extension handles:
+## Roadmap
-**`./example [message]`**
-This command sends a message to the chat. It also has a default message if no message is provided. (Alias: `./ex`)
-
-**`commandExample`**
-This rule will enable the `./example` command.
-
-**`exampleRule`**
-This rule will enable a message in chat when you push a button.
-
-## 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.
+- [ ] Add new structures by pasting a string
+- [ ] Make a viewable material list
+- [ ] structuraMode & holoprintMode rules for ease of use
+- [ ] Make automatic material grabbing from inventories
+- [ ] structure placement (ghost and solid)
+- [ ] easyPlace rule
+- [ ] more litematica features!
+- [ ] send structure as a scriptevent so that other addons can use it
## Issues & Suggestions
diff --git a/example_help.png b/example_help.png
deleted file mode 100644
index 4889a69..0000000
Binary files a/example_help.png and /dev/null differ
diff --git a/manifest.json b/manifest.json
index c4b9575..ffc6aa4 100644
--- a/manifest.json
+++ b/manifest.json
@@ -1,9 +1,9 @@
{
"format_version": 2,
"header": {
- "name": "Canopy Extension Example",
- "description": "Example extension for §l§aCanopy§r by §aForestOfLight§r.",
- "uuid": "e4e844d5-f45b-4623-82a7-534d44bb4c6f",
+ "name": "StrucTool",
+ "description": "Survival building extension for §l§aCanopy§r by §aForestOfLight§r.",
+ "uuid": "8c0c0153-d8b9-482a-889f-aef922b8fe58",
"min_engine_version": [1, 21, 60],
"version": [1, 0, 0]
},
@@ -11,7 +11,7 @@
{
"description": "Behavior Pack Module",
"type": "data",
- "uuid": "1ce9eab8-0d4a-4e40-a899-575791a1b40d",
+ "uuid": "f4d52ae1-2c26-4938-b8c2-7e455d495620",
"version": [1, 0, 0]
},
{
@@ -19,7 +19,7 @@
"type": "script",
"language": "javascript",
"entry": "scripts/main.js",
- "uuid": "839a937e-b4f9-4484-84a4-0db984822f25",
+ "uuid": "8bb9a1e4-0531-4b93-8ade-3880bfe1e2fc",
"version": [1, 0, 0]
}
],
@@ -28,6 +28,10 @@
"module_name": "@minecraft/server",
"version": "1.18.0-beta"
},
+ {
+ "module_name": "@minecraft/server-ui",
+ "version": "1.4.0-beta"
+ },
{
"uuid": "bcf34368-ed0c-4cf7-938e-582cccf9950d", // Canopy RP
"version": [1, 0, 2]
@@ -36,5 +40,9 @@
"uuid": "7f6b23df-a583-476b-b0e4-87457e65f7c0", // Canopy BP
"version": [1, 3, 8]
}
- ]
+ ],
+ "metadata": {
+ "authors": [ "ForestOfLight" ],
+ "license": "MIT"
+ }
}
diff --git a/scripts/classes/Outliner.js b/scripts/classes/Outliner.js
new file mode 100644
index 0000000..18d2a61
--- /dev/null
+++ b/scripts/classes/Outliner.js
@@ -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]);
+ }
+}
\ No newline at end of file
diff --git a/scripts/classes/Structure.js b/scripts/classes/Structure.js
new file mode 100644
index 0000000..ba35f29
--- /dev/null
+++ b/scripts/classes/Structure.js
@@ -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;
+ }
+}
\ No newline at end of file
diff --git a/scripts/classes/StructureCollection.js b/scripts/classes/StructureCollection.js
new file mode 100644
index 0000000..cbd578a
--- /dev/null
+++ b/scripts/classes/StructureCollection.js
@@ -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();
\ No newline at end of file
diff --git a/scripts/commands/struct.js b/scripts/commands/struct.js
new file mode 100644
index 0000000..3ce064b
--- /dev/null
+++ b/scripts/commands/struct.js
@@ -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}'` });
+}
\ No newline at end of file
diff --git a/scripts/config.js b/scripts/config.js
new file mode 100644
index 0000000..39efd76
--- /dev/null
+++ b/scripts/config.js
@@ -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'
+});
\ No newline at end of file
diff --git a/scripts/lib/Vector.js b/scripts/lib/Vector.js
new file mode 100644
index 0000000..85fe960
--- /dev/null
+++ b/scripts/lib/Vector.js
@@ -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}>`; }
+}
\ No newline at end of file
diff --git a/scripts/main.js b/scripts/main.js
index 9646470..f03bc75 100644
--- a/scripts/main.js
+++ b/scripts/main.js
@@ -1,97 +1,4 @@
-import { CanopyExtension, Command, Rule } from './lib/canopy/CanopyExtension';
-import { world } from '@minecraft/server';
+// Rules
-/**
- * Create a new CanopyExtension instance to define your extension.
- */
-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}`);
-}
+// Commands
+import './commands/struct.js';
diff --git a/scripts/utils.js b/scripts/utils.js
new file mode 100644
index 0000000..6cf2187
--- /dev/null
+++ b/scripts/utils.js
@@ -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)] });
+};
\ No newline at end of file
diff --git a/structures/Everything.mcstructure b/structures/Everything.mcstructure
new file mode 100644
index 0000000..5eb8d95
Binary files /dev/null and b/structures/Everything.mcstructure differ