import * as mc from "@minecraft/server";
import { Vector3 } from "./vector3";
import { mcerror, mclog, mcwarn } from "./log";
import { runCommand } from "./commands";
import { abortEvents, waitForEvent, waitForLoopEvent, waitForTicks, waitForWorldLoad } from "./events";
import { getScoreAsBool, setScore } from "./scores";
import { getAllPlayersSafe, getEntitiesSafe } from "./entity";
import { overworld } from "./dimension";
import { clearDynamicProperty, readDynamicProperty, writeDynamicProperty } from "./properties";
import { GlobalNamespace } from "./namespace";
import { addUniqueToArray, getRandomArrayEntry, removeFromArray } from "./array";
import { ButtonMenu, MessageMenu, ModalMenu } from "./menu";
import { Command, CoordinateArg, EnumArg, StringArg } from "./debug";
const DEFAULT_BIOME_FILTERS = {
    sand: {
        surface: [{ block: 'minecraft:sand', properties: { 'sand_type': 'normal' } }],
        replace: [
            { from: { block: 'minecraft:grass' }, to: { block: 'minecraft:sand' } },
            { from: { block: 'minecraft:dirt' }, to: { block: 'minecraft:sandstone' } },
            { from: { block: 'minecraft:tallgrass' }, to: { block: 'minecraft:air' } },
            { from: { block: 'minecraft:double_plant' }, to: { block: 'minecraft:air' } },
            { from: { block: 'minecraft:yellow_flower' }, to: { block: 'minecraft:air' } },
            { from: { block: 'minecraft:red_flower' }, to: { block: 'minecraft:air' } },
            { from: { block: 'minecraft:stone', properties: { 'stone_type': 'stone' } }, to: { block: 'minecraft:sandstone' } },
        ]
    },
    red_sand: {
        surface: [{ block: 'minecraft:sand', properties: { 'sand_type': 'red' } }],
        replace: [
            { from: { block: 'minecraft:grass' }, to: { block: 'minecraft:sand', properties: { 'sand_type': 'red' } } },
            { from: { block: 'minecraft:dirt' }, to: { block: 'minecraft:red_sandstone' } },
            { from: { block: 'minecraft:tallgrass' }, to: { block: 'minecraft:deadbush' } },
            { from: { block: 'minecraft:double_plant' }, to: { block: 'minecraft:deadbush' } },
            { from: { block: 'minecraft:yellow_flower' }, to: { block: 'minecraft:deadbush' } },
            { from: { block: 'minecraft:red_flower' }, to: { block: 'minecraft:deadbush' } },
            { from: { block: 'minecraft:stone', properties: { 'stone_type': 'stone' } }, to: { block: 'minecraft:red_sandstone' } },
        ]
    },
    mesa: {
        surface: [{ block: 'minecraft:hardened_clay' }],
        replace: [
            { from: { block: 'minecraft:grass' }, to: { block: 'minecraft:sand', properties: { 'sand_type': 'red' } } },
            { from: { block: 'minecraft:dirt' }, to: { block: 'minecraft:hardened_clay' } },
            { from: { block: 'minecraft:tallgrass' }, to: { block: 'minecraft:deadbush' } },
            { from: { block: 'minecraft:double_plant' }, to: { block: 'minecraft:deadbush' } },
            { from: { block: 'minecraft:yellow_flower' }, to: { block: 'minecraft:deadbush' } },
            { from: { block: 'minecraft:red_flower' }, to: { block: 'minecraft:deadbush' } },
            { from: { block: 'minecraft:stone', properties: { 'stone_type': 'stone' } }, to: { block: 'minecraft:hardened_clay' } },
        ]
    },
    snowy: {
        surface: [{ block: 'minecraft:snow_layer' }, { block: 'minecraft:snow' }],
        replace: [
            { from: { block: 'minecraft:grass' }, to: { block: 'minecraft:snow' } },
            { from: { block: 'minecraft:dirt' }, to: { block: 'minecraft:snow' } },
            { from: { block: 'minecraft:tallgrass' }, to: { block: 'minecraft:air' } },
            { from: { block: 'minecraft:double_plant' }, to: { block: 'minecraft:air' } },
            { from: { block: 'minecraft:yellow_flower' }, to: { block: 'minecraft:air' } },
            { from: { block: 'minecraft:red_flower' }, to: { block: 'minecraft:air' } },
        ]
    },
    ice: {
        surface: [{ block: 'minecraft:ice' }, { block: 'minecraft:packed_ice' }],
        replace: [
            { from: { block: 'minecraft:grass' }, to: { block: 'minecraft:ice' } },
            { from: { block: 'minecraft:dirt' }, to: { block: 'minecraft:packed_ice' } },
            { from: { block: 'minecraft:tallgrass' }, to: { block: 'minecraft:air' } },
            { from: { block: 'minecraft:double_plant' }, to: { block: 'minecraft:air' } },
            { from: { block: 'minecraft:yellow_flower' }, to: { block: 'minecraft:air' } },
            { from: { block: 'minecraft:red_flower' }, to: { block: 'minecraft:air' } },
            { from: { block: 'minecraft:stone', properties: { 'stone_type': 'stone' } }, to: { block: 'minecraft:packed_ice' } },
        ]
    }
};
export class Build {
    static get BuildData() {
        const val = readDynamicProperty(mc.world, GlobalNamespace.Namespace + "spawn_build");
        if (val) {
            val.origin = new Vector3(val.origin);
        }
        return val;
    }
    constructor(options) {
        var _a, _b, _c, _d;
        this.name = (_a = options.name) !== null && _a !== void 0 ? _a : "spawn_cell";
        this.floorOffset = (_b = options.floorOffset) !== null && _b !== void 0 ? _b : 1;
        this.cellCount = new Vector3((_c = options.cellCount) !== null && _c !== void 0 ? _c : 1);
        this.playerSpawnOffset = new Vector3((_d = options.playerSpawnOffset) !== null && _d !== void 0 ? _d : 0);
        this.blockFilters = options.blockFilters;
    }
    static alreadyBuilt() {
        const data = this.BuildData;
        if (data) {
            this.builtEvents.forEach(callback => callback(data));
        }
    }
    // #region Load
    async load(origin, options) {
        await waitForWorldLoad();
        mclog(`Loading Build ${this.name}`, "build");
        mclog(`Origin: ${origin}`, "build");
        if (!options.useOriginDirectly) {
            origin = await this.getLoadOrigin(origin);
        }
        mclog(`Selected y height: ${origin.y}`, "build");
        // Using 0 accounts for both failed raycasts (which return -64) and flatworlds.
        if (origin.y <= 0) {
            throw ("Failed to find a valid build point.");
        }
        const filter = this.getBlockFilters(origin);
        origin.y -= this.floorOffset;
        for (let x = 1; x <= this.cellCount.x; x++) {
            for (let y = 1; y <= this.cellCount.y; y++) {
                for (let z = 1; z <= this.cellCount.z; z++) {
                    const cellIndex = new Vector3({ x, y, z });
                    const useIndex = cellIndex.subtract(new Vector3(1));
                    const cellLocation = origin.add(Build.CellSize.multiply(useIndex)).add(useIndex);
                    await this.loadCell(cellLocation, cellIndex, false, filter);
                }
            }
        }
        if (options.spawnBuild) {
            await waitForEvent(5, () => this.setWorldData(origin));
        }
        for (let x = 1; x <= this.cellCount.x; x++) {
            for (let z = 1; z <= this.cellCount.z; z++) {
                const cellIndex = new Vector3({ x, y: 0, z });
                const useIndex = cellIndex.subtract(new Vector3({ x: 1, y: 0, z: 1 }));
                const cellLocation = origin.add(Build.FoundationSize.multiply(useIndex)).add(useIndex);
                for (let y = origin.y - 1; y > 60; y--) {
                    cellLocation.y = y;
                    await this.loadCell(cellLocation, cellIndex, true, filter);
                }
            }
        }
        mclog(`Finished Loading Build ${this.name}`, "build");
        return this;
    }
    async getLoadOrigin(origin) {
        const raycastOrigins = [
            new Vector3({ x: origin.x, y: Build.Ceiling, z: origin.z }),
            new Vector3({ x: origin.x + (Build.CellSize.x * this.cellCount.x), y: Build.Ceiling, z: origin.z }),
            new Vector3({ x: origin.x, y: Build.Ceiling, z: origin.z + (Build.CellSize.x * this.cellCount.x) }),
            new Vector3({ x: origin.x + (Build.CellSize.x * this.cellCount.x), y: Build.Ceiling, z: origin.z + (Build.CellSize.x * this.cellCount.x) }),
        ];
        const mappedValues = await Promise.all(raycastOrigins.map(async (origin) => this.performOriginRaycast(origin)));
        origin.y = mappedValues.sort((a, b) => b - a)[0];
        return origin;
    }
    async performOriginRaycast(raycastOrigin) {
        const excludeTypes = [
            "minecraft:oak_log",
            "minecraft:spruce_log",
            "minecraft:birch_log",
            "minecraft:jungle_log",
            "minecraft:acacia_log",
            "minecraft:mangrove_log",
            "minecraft:cherry_log",
            "minecraft:dark_oak_log",
            "minecraft:leaves",
            "minecraft:leaves2",
            "minecraft:mangrove_leaves",
            "minecraft:cherry_leaves",
            "minecraft:azalea_leaves",
            "minecraft:vine",
            "minecraft:red_mushroom_block",
            "minecraft:brown_mushroom_block",
        ];
        let hit = overworld.getBlockFromRay(raycastOrigin, Vector3.DOWN, { includeLiquidBlocks: true, maxDistance: 1000, excludeTypes });
        let attempts = 1;
        if (!hit) {
            runCommand(`tickingarea add ${raycastOrigin} ${raycastOrigin} raycast`);
            await waitForTicks(0);
            hit = overworld.getBlockFromRay(raycastOrigin, Vector3.DOWN, { includeLiquidBlocks: true, maxDistance: 1000, excludeTypes });
            await waitForTicks(0);
            while (!hit) {
                await waitForTicks(9);
                hit = overworld.getBlockFromRay(raycastOrigin, Vector3.DOWN, { includeLiquidBlocks: true, maxDistance: 1000, excludeTypes });
                attempts++;
                if (attempts > 20) {
                    mcwarn(`Raycast from ${raycastOrigin} failed ${attempts - 1} times, defaulting to ${overworld.heightRange.min}`, "build");
                    return overworld.heightRange.min;
                }
            }
            runCommand(`tickingarea remove raycast`);
        }
        mclog(`Raycast from ${raycastOrigin} hit block at ${hit.block.location.y} after ${attempts} attempts`, "build");
        return hit.block.location.y;
    }
    getBlockFilters(origin) {
        var _a;
        const block = overworld.getBlock(origin);
        if (!block)
            return this.blockFilters;
        for (const id of Object.keys(DEFAULT_BIOME_FILTERS)) {
            const filter = DEFAULT_BIOME_FILTERS[id];
            if (filter.surface.some(surface => block.permutation.matches(surface.block, surface.properties))) {
                mclog(`Biome Filter: ${id}`, "build");
                const defaultFilters = (_a = this.blockFilters) !== null && _a !== void 0 ? _a : [];
                return [...filter.replace, ...defaultFilters];
            }
        }
        return this.blockFilters;
    }
    async loadCell(origin, index, foundation, filter) {
        const name = this.name + (foundation ? "_foundation" : "");
        const suffix = foundation ? `${index.x}_${index.z}` : `${index.x}_${index.y}_${index.z}`;
        const structureName = `${name}_${suffix}`;
        const size = foundation ? Build.FoundationSize : Build.CellSize;
        const end = origin.add(size);
        mclog(`Loading ${structureName}`, "build");
        getEntitiesSafe(overworld, { excludeTypes: ["minecraft:player"] }).forEach(entity => {
            const shouldDelete = readDynamicProperty(entity, "shouldDeleteOnBuild");
            if (shouldDelete) {
                entity.remove();
            }
            else {
                clearDynamicProperty(entity, "shouldDeleteOnBuild");
            }
        });
        runCommand(`tickingarea add ${origin} ${end} build_loader`);
        await waitForTicks(0);
        mc.world.structureManager.place("mystructure:" + structureName, overworld, origin);
        await waitForTicks(0);
        runCommand(`tickingarea remove build_loader`);
        if (filter) {
            filter.forEach(option => {
                const fromOptions = option.from.properties ? JSON.stringify(option.from.properties).replace(':', '=').replace('{', '[').replace('}', ']') : '';
                const toOptions = option.to.properties ? JSON.stringify(option.to.properties).replace(':', '=').replace('{', '[').replace('}', ']') : '';
                overworld.runCommand(`fill ${origin} ${end} ${option.to.block} ${toOptions} replace ${option.from.block} ${fromOptions}`);
            });
        }
    }
    setWorldData(origin) {
        setScore(".var", Build.BuildObjective, 1);
        const spawnpoint = origin.add(this.playerSpawnOffset);
        getAllPlayersSafe().forEach(player => {
            player.teleport(spawnpoint);
            player.setSpawnPoint(Object.assign({ dimension: overworld }, spawnpoint));
        });
        mc.world.setDefaultSpawnLocation(spawnpoint);
        runCommand("gamerule spawnradius 0");
        const data = {
            origin,
            options: {
                name: this.name,
                cellCount: this.cellCount,
                floorOffset: this.floorOffset,
                playerSpawnOffset: this.playerSpawnOffset,
            }
        };
        writeDynamicProperty(mc.world, GlobalNamespace.Namespace + "spawn_build", data);
        Build.builtEvents.forEach(callback => callback(data));
    }
    // #endregion
    // #region Save
    async save(origin) {
        origin.y += 1;
        for (let x = 1; x <= this.cellCount.x; x++) {
            for (let y = 1; y <= this.cellCount.y; y++) {
                for (let z = 1; z <= this.cellCount.z; z++) {
                    const cellIndex = new Vector3({ x, y, z });
                    const useIndex = cellIndex.subtract(new Vector3(1));
                    const cellLocation = origin.add(Build.CellSize.multiply(useIndex)).add(useIndex);
                    await this.saveCell(cellLocation, cellIndex, false);
                }
            }
        }
        origin.y -= 1;
        for (let x = 1; x <= this.cellCount.x; x++) {
            for (let z = 1; z <= this.cellCount.z; z++) {
                const cellIndex = new Vector3({ x, y: 0, z });
                const useIndex = cellIndex.subtract(new Vector3({ x: 1, y: 0, z: 1 }));
                const cellLocation = origin.add(Build.FoundationSize.multiply(useIndex)).add(useIndex);
                await this.saveCell(cellLocation, cellIndex, true);
            }
        }
        return this;
    }
    async saveCell(origin, index, foundation) {
        const name = this.name + (foundation ? "_foundation" : "");
        const suffix = foundation ? `${index.x}_${index.z}` : `${index.x}_${index.y}_${index.z}`;
        const structureName = `${name}_${suffix}`;
        const size = foundation ? Build.FoundationSize : Build.CellSize;
        const end = origin.add(size);
        runCommand(`tickingarea add ${origin} ${end} build_saver`);
        await waitForTicks(0);
        const entities = this.getEntitiesInCell(origin, foundation);
        if (entities.length) {
            getEntitiesSafe(overworld, { excludeTypes: ["minecraft:player"] }).forEach(entity => writeDynamicProperty(entity, "shouldDeleteOnBuild", true));
            entities.forEach(entity => writeDynamicProperty(entity, "shouldDeleteOnBuild", false));
        }
        runCommand(`structure save ${structureName} ${origin} ${end} ${entities.length > 0} disk`);
        await waitForTicks(0);
        runCommand(`tickingarea remove build_saver`);
        runCommand(`structure-export ${structureName}`);
        mclog(`Saving ${structureName} with ${entities.length} entities`, "build");
    }
    // #endregion
    getEntitiesInCell(origin, foundation) {
        const offset = foundation ? Build.FoundationSize.add({ x: 1, y: 0, z: 1 }) : Build.CellSize.add(1);
        return getEntitiesSafe(overworld).filter(entity => entity.typeId !== "minecraft:player" && entity.location.inBounds(origin, origin.add(offset)));
    }
}
Build.CellSize = new Vector3(15);
Build.FoundationSize = new Vector3({ x: 15, y: 0, z: 15 });
Build.Ceiling = overworld.heightRange.max;
Build.BuildObjective = "HasBuiltSpawn";
Build.CachedBuilds = [];
Build.builtEvents = [];
Build.afterEvents = {
    built: {
        subscribe(callback) {
            addUniqueToArray(Build.builtEvents, callback);
        },
        unsubscribe(callback) {
            removeFromArray(Build.builtEvents, callback);
        }
    }
};
/**
 * @remarks Creates the spawn build on the first world load. If the spawn build has already been created, this will not run again.
 * @param builds An array of build options. One build will randomly be selected to use as the spawn build.
 */
export async function buildOnFirstLoad(...builds) {
    await waitForWorldLoad();
    Build.CachedBuilds.push(...builds.map(option => new Build(option)));
    if (!getScoreAsBool(".var", Build.BuildObjective)) {
        const build = new Build(getRandomArrayEntry(Build.CachedBuilds));
        const origin = getAllPlayersSafe()[0].location;
        try {
            await build.load(origin, { spawnBuild: true });
        }
        catch (error) {
            mcerror(error, "build");
        }
    }
    else {
        mclog("Already Built Spawn", "build");
        Build.alreadyBuilt();
    }
}
// #region Build Helper
class BuildHelper {
    constructor(source) {
        this.buildOptions = { cellCount: new Vector3(1) };
        this.itemUseEvent = this.itemUse.bind(this);
        this.stopParticles = false;
        this.MainMenu = new ButtonMenu("Build Helper", [
            {
                text: "Save Menu",
                texture: "textures/ui/structure_block_save",
                onClick: (player) => {
                    this.SettingMenu.showMenuToPlayer(player);
                    const inventory = player.Inventory;
                    let item = new mc.ItemStack("minecraft:stick", 1);
                    item.nameTag = "Save Build";
                    item.setLore(["Use item on negative-most corner", "to select coordinate.", "Make sure to get the foundation,", "not the build."]);
                    item.lockMode = mc.ItemLockMode.slot;
                    inventory === null || inventory === void 0 ? void 0 : inventory.setItem(0, item);
                    item = new mc.ItemStack("minecraft:barrier", 1);
                    item.nameTag = "Cancel";
                    item.setLore(["Exit Build Helper"]);
                    item.lockMode = mc.ItemLockMode.slot;
                    inventory === null || inventory === void 0 ? void 0 : inventory.setItem(1, item);
                },
            },
            {
                text: "Load Menu",
                texture: "textures/ui/structure_block_load",
                onClick: (player) => {
                    this.SettingMenu.showMenuToPlayer(player);
                    const inventory = player.Inventory;
                    let item = new mc.ItemStack("minecraft:stick", 1);
                    item.nameTag = "Load Build";
                    item.setLore(["Use item on negative-most corner", "to select coordinate"]);
                    item.lockMode = mc.ItemLockMode.slot;
                    inventory === null || inventory === void 0 ? void 0 : inventory.setItem(0, item);
                    item = new mc.ItemStack("minecraft:barrier", 1);
                    item.nameTag = "Cancel";
                    item.setLore(["Exit Build Helper"]);
                    item.lockMode = mc.ItemLockMode.slot;
                    inventory === null || inventory === void 0 ? void 0 : inventory.setItem(1, item);
                },
            },
            {
                text: "Reset Build Score",
                texture: "textures/ui/structure_block_corner",
                onClick: (player) => {
                    setScore(".var", Build.BuildObjective, 0);
                },
            },
        ]);
        this.SettingMenu = new ModalMenu("Build Settings", [
            {
                type: "textField",
                text: "Build Name",
                placeholderText: "spawn_cell",
                defaultValue: "spawn_cell",
                onSubmit: (player, textFieldValue) => {
                    this.buildOptions.name = textFieldValue;
                },
            },
            {
                type: "slider",
                text: "X Cell Count",
                minimumValue: 1,
                maximumValue: 10,
                valueStep: 1,
                onSubmit: (player, sliderValue) => {
                    this.buildOptions.cellCount.x = sliderValue;
                },
            },
            {
                type: "slider",
                text: "Y Cell Count",
                minimumValue: 1,
                maximumValue: 10,
                valueStep: 1,
                onSubmit: (player, sliderValue) => {
                    this.buildOptions.cellCount.y = sliderValue;
                },
            },
            {
                type: "slider",
                text: "Z Cell Count",
                minimumValue: 1,
                maximumValue: 10,
                valueStep: 1,
                onSubmit: (player, sliderValue) => {
                    this.buildOptions.cellCount.z = sliderValue;
                },
            }
        ]);
        const player = source.getPlayer();
        if (player) {
            this.player = player;
            this.MainMenu.showMenuToPlayer(player);
            mc.world.afterEvents.itemUse.subscribe(this.itemUseEvent);
        }
        else {
            throw ("Only players should construct a build helper");
        }
    }
    itemUse(event) {
        if (event.source.id === this.player.id && (event.itemStack.typeId === "minecraft:stick" || event.itemStack.typeId === "minecraft:barrier")) {
            const hit = event.source.getBlockFromViewDirection({ maxDistance: 8 });
            switch (event.itemStack.nameTag) {
                case "Save Build":
                    if (this.origin && !hit) {
                        this.confirmSaveOrLoad("Save Build Here?", true);
                    }
                    else if (hit) {
                        this.selectOrigin(new Vector3(hit.block.location));
                    }
                    break;
                case "Load Build":
                    if (this.origin && !hit) {
                        this.confirmSaveOrLoad("Load Build Here?", false);
                    }
                    else if (hit) {
                        this.selectOrigin(new Vector3(hit.block.location));
                    }
                    break;
                case "Cancel":
                    this.cleanup();
                    break;
            }
        }
    }
    selectOrigin(origin) {
        this.origin = origin;
        this.stopParticles = false;
        this.showParticles();
    }
    confirmSaveOrLoad(message, save) {
        if (!this.origin)
            return;
        this.stopParticles = true;
        new MessageMenu("Confirm", {
            confirm: {
                text: "Yes",
                onClick: (player) => {
                    if (save) {
                        new Build(this.buildOptions).save(this.origin);
                    }
                    else {
                        new Build(this.buildOptions).load(this.origin, { spawnBuild: false, useOriginDirectly: true });
                    }
                    this.cleanup();
                },
            },
            deny: {
                text: "No",
                onClick: (player) => {
                    this.origin = undefined;
                }
            }
        }, message).showMenuToPlayer(this.player);
    }
    showParticles() {
        const size = Build.CellSize.add(1).multiply(this.buildOptions.cellCount);
        waitForLoopEvent(20, () => {
            if (!this.origin)
                return true;
            for (let x = this.origin.x; x <= this.origin.x + size.x; x++) {
                overworld.spawnParticle('minecraft:endrod', { x, y: this.origin.y, z: this.origin.z });
            }
            for (let y = this.origin.y; y <= this.origin.y + size.y + 1; y++) {
                overworld.spawnParticle('minecraft:endrod', { x: this.origin.x, y, z: this.origin.z });
            }
            for (let z = this.origin.z; z <= this.origin.z + size.z; z++) {
                overworld.spawnParticle('minecraft:endrod', { x: this.origin.x, y: this.origin.y, z: z });
            }
            return this.stopParticles;
        }, "BuildHelper" + this.player.id);
    }
    cleanup() {
        mc.world.afterEvents.itemUse.unsubscribe(this.itemUseEvent);
        this.stopParticles = true;
        abortEvents("BuildHelper" + this.player.id);
        const inventory = this.player.Inventory;
        inventory === null || inventory === void 0 ? void 0 : inventory.setItem(0);
        inventory === null || inventory === void 0 ? void 0 : inventory.setItem(1);
        this.origin = undefined;
    }
}
new Command("build", source => new BuildHelper(source))
    .description("Opens the build helper menu");
new Command("load_build", (source, name) => {
    var _a, _b, _c;
    if (name !== "random") {
        const loadBuild = Build.CachedBuilds.find(build => build.name === name);
        if (loadBuild) {
            loadBuild.load(source.location, { spawnBuild: true });
        }
        else {
            (_a = source.getPlayer()) === null || _a === void 0 ? void 0 : _a.sendMessage(`§cCouldn't find build with name ${name}`);
            (_b = source.getPlayer()) === null || _b === void 0 ? void 0 : _b.sendMessage(`Valid options are: ${Build.CachedBuilds.map(build => build.name)}`);
        }
    }
    else {
        const build = (_c = getRandomArrayEntry(Build.CachedBuilds)) !== null && _c !== void 0 ? _c : new Build({});
        build.load(source.location, { spawnBuild: true });
    }
})
    .required(new EnumArg([...Build.CachedBuilds.map(build => build.name), "random"]))
    .description("Loads a build immediately");
new Command("save_build", (source, name, location) => {
    new Build({ name }).save(location !== null && location !== void 0 ? location : source.location);
})
    .required(new StringArg())
    .optional(new CoordinateArg())
    .description("Saves a build");
// #endregion
