configure for regolith
This commit is contained in:
@@ -0,0 +1,179 @@
|
||||
import { structureCollection } from '../Structure/StructureCollection';
|
||||
import { MenuForm } from '../MenuForm';
|
||||
import { forceShow } from '../../utils';
|
||||
import { InstanceButtons } from '../Enums/InstanceButtons';
|
||||
import { InstanceFormBuilder } from './InstanceFormBuilder';
|
||||
import { FormCancelationReason } from '@minecraft/server-ui';
|
||||
|
||||
export class InstanceForm {
|
||||
instanceName;
|
||||
#buttons = {
|
||||
isEnabled: [
|
||||
InstanceButtons.NextLayer,
|
||||
InstanceButtons.PreviousLayer,
|
||||
InstanceButtons.Move,
|
||||
InstanceButtons.Settings,
|
||||
InstanceButtons.Statistics,
|
||||
InstanceButtons.Materials,
|
||||
InstanceButtons.Rename,
|
||||
InstanceButtons.Disable
|
||||
],
|
||||
isNotEnabledAndIsNotPlaced: [
|
||||
InstanceButtons.Place,
|
||||
InstanceButtons.Rename
|
||||
],
|
||||
isNotEnabledButIsPlaced: [
|
||||
InstanceButtons.Enable,
|
||||
InstanceButtons.Rename
|
||||
],
|
||||
common: [
|
||||
InstanceButtons.Delete,
|
||||
InstanceButtons.MainMenu
|
||||
]
|
||||
}
|
||||
|
||||
constructor(player, instanceName) {
|
||||
this.player = player;
|
||||
this.instanceName = instanceName;
|
||||
this.instance = structureCollection.get(this.instanceName);
|
||||
this.show();
|
||||
}
|
||||
|
||||
show() {
|
||||
const currentOptions = this.getActiveOptions();
|
||||
forceShow(this.player, InstanceFormBuilder.buildInstance(this.instance, currentOptions)).then((response) => {
|
||||
if (response.canceled) return;
|
||||
this.handleOption(currentOptions[response.selection]);
|
||||
});
|
||||
}
|
||||
|
||||
getActiveOptions() {
|
||||
let currentOptions = [];
|
||||
if (this.instance.isEnabled())
|
||||
currentOptions = this.#buttons.isEnabled;
|
||||
else if (this.instance.hasLocation())
|
||||
currentOptions = this.#buttons.isNotEnabledButIsPlaced;
|
||||
else
|
||||
currentOptions = this.#buttons.isNotEnabledAndIsNotPlaced;
|
||||
currentOptions = currentOptions.concat(this.#buttons.common);
|
||||
|
||||
if (!this.instance.hasLayers())
|
||||
currentOptions = currentOptions.filter(option =>
|
||||
option !== InstanceButtons.SetLayer
|
||||
&& option !== InstanceButtons.NextLayer
|
||||
&& option !== InstanceButtons.PreviousLayer
|
||||
);
|
||||
return currentOptions;
|
||||
}
|
||||
|
||||
handleOption(option) {
|
||||
switch (option) {
|
||||
case InstanceButtons.Enable:
|
||||
this.instance.enable();
|
||||
break;
|
||||
case InstanceButtons.Disable:
|
||||
this.instance.disable();
|
||||
break;
|
||||
case InstanceButtons.Place:
|
||||
this.instance.place(this.player.dimension.id, this.player.location);
|
||||
break;
|
||||
case InstanceButtons.Rename:
|
||||
this.renameInstanceForm();
|
||||
break;
|
||||
case InstanceButtons.Delete:
|
||||
structureCollection.delete(this.instanceName);
|
||||
break;
|
||||
case InstanceButtons.NextLayer:
|
||||
this.instance.increaseLayer();
|
||||
new InstanceForm(this.player, this.instanceName);
|
||||
break;
|
||||
case InstanceButtons.PreviousLayer:
|
||||
this.instance.decreaseLayer();
|
||||
new InstanceForm(this.player, this.instanceName);
|
||||
break;
|
||||
case InstanceButtons.Move:
|
||||
this.instance.move(this.player.dimension.id, this.player.location);
|
||||
break;
|
||||
case InstanceButtons.Settings:
|
||||
this.settingsForm();
|
||||
break;
|
||||
case InstanceButtons.Statistics:
|
||||
this.statisticsForm();
|
||||
break;
|
||||
case InstanceButtons.Materials:
|
||||
this.materialsForm();
|
||||
break;
|
||||
case InstanceButtons.MainMenu:
|
||||
new MenuForm(this.player, { jumpToInstance: false });
|
||||
break;
|
||||
default:
|
||||
this.player.sendMessage(`§cUnknown option: ${option}`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
renameInstanceForm() {
|
||||
InstanceFormBuilder.buildRenameInstance(this.instanceName).show(this.player).then((response) => {
|
||||
if (response.canceled)
|
||||
return;
|
||||
const newName = response.formValues[0];
|
||||
if (newName === '') {
|
||||
this.player.sendMessage('§cInstance name cannot be empty.');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
structureCollection.rename(this.instanceName, newName);
|
||||
this.instanceName = newName;
|
||||
} catch (e) {
|
||||
this.player.sendMessage(`§cError renaming instance: ${e.message}`);
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
setLayerForm() {
|
||||
InstanceFormBuilder.buildSetLayer(this.instance.getBounds().max.y, this.instance.getLayer()).show(this.player).then((response) => {
|
||||
if (response.canceled)
|
||||
return;
|
||||
this.instance.setLayer(parseInt(response.formValues[0]));
|
||||
});
|
||||
}
|
||||
|
||||
async statisticsForm() {
|
||||
let statsForm;
|
||||
try {
|
||||
statsForm = await InstanceFormBuilder.buildStatistics(this.instance);
|
||||
} catch (e) {
|
||||
if (e.message === 'StructureVerifier is already running.') {
|
||||
this.player.sendMessage('§cA verification is already in progress. Please wait until it finishes.');
|
||||
return;
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
if (!statsForm) {
|
||||
this.player.sendMessage('§cFailed to build statistics form.');
|
||||
return;
|
||||
}
|
||||
statsForm.form.show(this.player).then((response) => {
|
||||
if (response.canceled && response.cancelationReason === FormCancelationReason.UserBusy)
|
||||
this.player.sendMessage(statsForm.stats);
|
||||
});
|
||||
}
|
||||
|
||||
settingsForm() {
|
||||
InstanceFormBuilder.buildSettings(this.instance).show(this.player).then((response) => {
|
||||
if (response.canceled)
|
||||
return;
|
||||
this.instance.setVerifierEnabled(response.formValues[0]);
|
||||
this.instance.setLayer(parseInt(response.formValues[1]));
|
||||
});
|
||||
}
|
||||
|
||||
materialsForm(onlyShowMissingMaterials = false) {
|
||||
forceShow(this.player, InstanceFormBuilder.buildMaterialList(this.instance, onlyShowMissingMaterials, this.player)).then((response) => {
|
||||
if (response.canceled) return;
|
||||
if (response.selection === 0)
|
||||
this.materialsForm(!onlyShowMissingMaterials);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
import { ActionFormData, ModalFormData } from '@minecraft/server-ui';
|
||||
import { MenuFormBuilder } from '../MenuFormBuilder';
|
||||
import { StructureVerifier } from '../Verifier/StructureVerifier';
|
||||
import { StructureStatistics } from '../Structure/StructureStatistics';
|
||||
import { EntityComponentTypes, TicksPerSecond } from '@minecraft/server';
|
||||
|
||||
export class InstanceFormBuilder {
|
||||
static structureVerifier;
|
||||
|
||||
static buildInstance(instance, options) {
|
||||
const location = instance.getLocation();
|
||||
const form = new ActionFormData()
|
||||
.title(MenuFormBuilder.menuTitle)
|
||||
let body = `Instance: §a${instance.getName()}\n§fStructure: §2${instance.getStructureId()}\n`;
|
||||
if (instance.hasLocation())
|
||||
body += `§7(${location.location.x} ${location.location.y} ${location.location.z} in ${location.dimensionId})\n`;
|
||||
form.body(body);
|
||||
options.forEach(option => {
|
||||
form.button(`${option}`);
|
||||
});
|
||||
return form;
|
||||
}
|
||||
|
||||
static buildRenameInstance(currentName) {
|
||||
return new ModalFormData()
|
||||
.title(MenuFormBuilder.menuTitle)
|
||||
.textField('Enter a new name for the instance:', currentName)
|
||||
.submitButton('Rename');
|
||||
}
|
||||
|
||||
static async buildStatistics(instance) {
|
||||
const buildStatisticsForm = new ActionFormData()
|
||||
.title(MenuFormBuilder.menuTitle)
|
||||
if (this.structureVerifier)
|
||||
throw new Error('StructureVerifier is already running.');
|
||||
this.structureVerifier = new StructureVerifier(instance, { isEnabled: true, particleLifetime: 1*TicksPerSecond, isStandalone: true });
|
||||
const verification = await this.structureVerifier.verifyStructure(true);
|
||||
const statistics = new StructureStatistics(instance, verification);
|
||||
const statsMessage = statistics.getMessage();
|
||||
this.structureVerifier = void 0;
|
||||
buildStatisticsForm.body(statsMessage);
|
||||
return { form: buildStatisticsForm, stats: statsMessage };
|
||||
}
|
||||
|
||||
static buildSettings(instance) {
|
||||
return new ModalFormData()
|
||||
.title(MenuFormBuilder.menuTitle)
|
||||
.toggle('Block Validation', { defaultValue: instance.options.verifier.isEnabled, tooltip: 'Shows missing and incorrect block overlay.' })
|
||||
.slider("Layer", 0, instance.getMaxLayer(), { defaultValue: instance.getLayer(), valueStep: 1, tooltip: 'Changes the active layer. Use 0 for all layers.' })
|
||||
.submitButton('§2Apply');
|
||||
}
|
||||
|
||||
static buildMaterialList(instance, onlyMissing = false, player = false) {
|
||||
const materials = instance.getActiveMaterials();
|
||||
const form = new ActionFormData()
|
||||
.title(MenuFormBuilder.menuTitle)
|
||||
const bodyText = { rawtext: [] };
|
||||
let buttonText = void 0;
|
||||
if (onlyMissing) {
|
||||
const inventoryContainer = player?.getComponent(EntityComponentTypes.Inventory)?.container;
|
||||
if (!inventoryContainer) {
|
||||
form.body('§cNo player inventory found.');
|
||||
return form;
|
||||
}
|
||||
bodyText.rawtext.push({ text: `§cMaterials Missing From Inventory:` });
|
||||
if (instance.hasLayerSelected())
|
||||
bodyText.rawtext.push({ text: ` §7(layer ${instance.getLayer()})` });
|
||||
bodyText.rawtext.push({ text: `§f\n\n` });
|
||||
bodyText.rawtext.push(materials.formatString(materials.getMaterialsDifference(inventoryContainer)));
|
||||
buttonText = "Show All Materials";
|
||||
} else {
|
||||
bodyText.rawtext.push({ text: `§aAll Materials:` });
|
||||
if (instance.hasLayerSelected())
|
||||
bodyText.rawtext.push({ text: ` §7(layer ${instance.getLayer()})` });
|
||||
bodyText.rawtext.push({ text: `§f\n\n` });
|
||||
bodyText.rawtext.push(materials.formatString());
|
||||
buttonText = "Show Missing Materials";
|
||||
}
|
||||
form.body(bodyText);
|
||||
form.button(buttonText);
|
||||
return form;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
import { Vector } from "../../lib/Vector";
|
||||
import { world } from "@minecraft/server";
|
||||
import { Option } from "../Option";
|
||||
|
||||
export class InstanceOptions extends Option {
|
||||
#DP_NAMESPACE = "instanceOptions";
|
||||
instanceName = void 0;
|
||||
structureId = void 0;
|
||||
isEnabled = false;
|
||||
dimensionId = void 0;
|
||||
worldLocation = new Vector();
|
||||
currentLayer = 0;
|
||||
verifier = {
|
||||
isEnabled: true,
|
||||
trackPlayerDistance: 5,
|
||||
particleLifetime: 10
|
||||
};
|
||||
|
||||
static getInstanceStructureId(instanceName) {
|
||||
const options = new InstanceOptions(instanceName, void 0);
|
||||
return options.structureId;
|
||||
}
|
||||
|
||||
constructor(instanceName, structureId) {
|
||||
super();
|
||||
this.instanceName = instanceName;
|
||||
this.structureId = structureId;
|
||||
this.load();
|
||||
}
|
||||
|
||||
save() {
|
||||
this.saveToDP(this.#DP_NAMESPACE, this.instanceName, this);
|
||||
}
|
||||
|
||||
load() {
|
||||
this.loadFromDP(this.#DP_NAMESPACE, this.instanceName);
|
||||
this.worldLocation = Vector.from(this.worldLocation);
|
||||
}
|
||||
|
||||
clear() {
|
||||
this.clearDP(this.#DP_NAMESPACE, this.instanceName);
|
||||
}
|
||||
|
||||
getDimension() {
|
||||
return world.getDimension(this.dimensionId);
|
||||
}
|
||||
|
||||
enable() {
|
||||
this.isEnabled = true;
|
||||
this.save();
|
||||
}
|
||||
|
||||
disable() {
|
||||
this.isEnabled = false;
|
||||
this.save();
|
||||
}
|
||||
|
||||
rename(newName) {
|
||||
this.clear();
|
||||
this.instanceName = newName;
|
||||
this.save();
|
||||
}
|
||||
|
||||
move(dimensionId, worldLocation) {
|
||||
this.dimensionId = dimensionId;
|
||||
this.worldLocation = Vector.from(worldLocation).floor();
|
||||
this.save();
|
||||
}
|
||||
|
||||
setLayer(layer) {
|
||||
this.currentLayer = layer;
|
||||
this.save();
|
||||
}
|
||||
|
||||
setVerifierEnabled(enable) {
|
||||
this.verifier.isEnabled = enable;
|
||||
this.save();
|
||||
}
|
||||
|
||||
setVerifierDistance(distance) {
|
||||
this.verifier.trackPlayerDistance = distance;
|
||||
this.save();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,275 @@
|
||||
import { Vector } from "../../lib/Vector";
|
||||
import { StructureOutliner } from "../Render/StructureOutliner";
|
||||
import { StructureVerifier } from "../Verifier/StructureVerifier";
|
||||
import { Structure } from "../Structure/Structure";
|
||||
import { InstanceOptions } from "./InstanceOptions";
|
||||
import { world, system, TicksPerSecond } from "@minecraft/server";
|
||||
import { InstanceNotPlacedError } from "../Errors/InstanceNotPlacedError";
|
||||
import { StructureMaterials } from "../Materials/StructureMaterials";
|
||||
import { VerificationRenderer } from "../Render/VerificationRenderer";
|
||||
|
||||
export class StructureInstance {
|
||||
options;
|
||||
structure = void 0;
|
||||
outliner = void 0;
|
||||
verifier = void 0;
|
||||
verificationRenderer = void 0;
|
||||
materials = void 0;
|
||||
|
||||
constructor(instanceName, structureId) {
|
||||
this.structure = new Structure(structureId);
|
||||
this.options = new InstanceOptions(instanceName, structureId);
|
||||
this.refreshBox();
|
||||
this.subscribeToEvents();
|
||||
}
|
||||
|
||||
delete() {
|
||||
this.disable();
|
||||
this.options.clear();
|
||||
delete this.options;
|
||||
delete this.structure;
|
||||
delete this.outliner;
|
||||
delete this.verifier;
|
||||
delete this.verificationRenderer;
|
||||
delete this.materials;
|
||||
}
|
||||
|
||||
refreshBox() {
|
||||
if (!this.hasLocation())
|
||||
return;
|
||||
if (!this.outliner)
|
||||
this.outliner = new StructureOutliner(this);
|
||||
if (!this.verifier)
|
||||
this.verifier = new StructureVerifier(this, { isEnabled: this.options.verifier.isEnabled });
|
||||
if (!this.verificationRenderer)
|
||||
this.verificationRenderer = new VerificationRenderer(this);
|
||||
if (!this.materials)
|
||||
this.materials = new StructureMaterials(this);
|
||||
this.outliner.refresh();
|
||||
this.verifier.refresh();
|
||||
this.verificationRenderer.refresh();
|
||||
this.materials.refresh();
|
||||
}
|
||||
|
||||
subscribeToEvents() {
|
||||
this.disableInstanceWhenNoPlayersOnline();
|
||||
}
|
||||
|
||||
getName() {
|
||||
return this.options.instanceName;
|
||||
}
|
||||
|
||||
getStructureId() {
|
||||
return this.options.structureId;
|
||||
}
|
||||
|
||||
getLocation() {
|
||||
return { dimensionId: this.options.dimensionId, location: this.options.worldLocation };
|
||||
}
|
||||
|
||||
getDimension() {
|
||||
return this.options?.getDimension();
|
||||
}
|
||||
|
||||
getLayer() {
|
||||
return this.options.currentLayer;
|
||||
}
|
||||
|
||||
getMaxLayer() {
|
||||
return this.structure.getHeight();
|
||||
}
|
||||
|
||||
getBounds() {
|
||||
return {
|
||||
min: this.structure.getMin(),
|
||||
max: this.structure.getMax()
|
||||
}
|
||||
}
|
||||
|
||||
getActiveBounds() {
|
||||
if (!this.options.isEnabled)
|
||||
throw new InstanceNotPlacedError(`[Construct] Instance '${this.options.instanceName}' is not placed.`);
|
||||
if (this.hasLayerSelected())
|
||||
return this.getLayerBounds(this.getLayer());
|
||||
return this.getBounds();
|
||||
}
|
||||
|
||||
getLayerBounds(layer) {
|
||||
if (!this.options.isEnabled)
|
||||
throw new InstanceNotPlacedError(`[Construct] Instance '${this.options.instanceName}' is not placed.`);
|
||||
const min = this.structure.getMin();
|
||||
const max = this.structure.getMax();
|
||||
return {
|
||||
min: new Vector(min.x, layer - 1, min.z),
|
||||
max: new Vector(max.x, layer, max.z)
|
||||
};
|
||||
}
|
||||
|
||||
getBlock(structureLocation) {
|
||||
return this.structure.getBlock(structureLocation);
|
||||
}
|
||||
|
||||
getBlocks(structureLocations) {
|
||||
return this.structure.getBlocks(structureLocations);
|
||||
}
|
||||
|
||||
getLayerBlocks(layer) {
|
||||
return this.structure.getLayerBlocks(layer);
|
||||
}
|
||||
|
||||
getAllBlocks() {
|
||||
return this.structure.getAllBlocks();
|
||||
}
|
||||
|
||||
getActiveBlocks() {
|
||||
if (!this.options.isEnabled)
|
||||
throw new InstanceNotPlacedError(`[Construct] Instance '${this.options.instanceName}' is not placed.`);
|
||||
if (this.hasLayerSelected())
|
||||
return this.getLayerBlocks(this.getLayer() - 1);
|
||||
return this.getAllBlocks();
|
||||
}
|
||||
|
||||
isLocationActive(dimensionId, structureLocation, { useActiveLayer = true } = {}) {
|
||||
if (!this.options.isEnabled || this.options.dimensionId !== dimensionId)
|
||||
return false;
|
||||
let bounds;
|
||||
if (useActiveLayer)
|
||||
bounds = this.getActiveBounds();
|
||||
else
|
||||
bounds = this.getBounds();
|
||||
return structureLocation.x >= bounds.min.x && structureLocation.x < bounds.max.x
|
||||
&& structureLocation.y >= bounds.min.y && structureLocation.y < bounds.max.y
|
||||
&& structureLocation.z >= bounds.min.z && structureLocation.z < bounds.max.z;
|
||||
}
|
||||
|
||||
getAllActiveLocations() {
|
||||
if (!this.options.isEnabled)
|
||||
throw new InstanceNotPlacedError(`[Construct] Instance '${this.options.instanceName}' is not placed.`);
|
||||
if (this.hasLayerSelected())
|
||||
return this.structure.getLayerLocations(this.getLayer()-1);
|
||||
else
|
||||
return this.structure.getAllLocations();
|
||||
}
|
||||
|
||||
getActiveMaterials() {
|
||||
return this.materials;
|
||||
}
|
||||
|
||||
isEnabled() {
|
||||
return this.options.isEnabled;
|
||||
}
|
||||
|
||||
hasLocation() {
|
||||
return this.options.dimensionId && (this.options.worldLocation.x !== 0 || this.options.worldLocation.y !== 0 || this.options.worldLocation.z !== 0);
|
||||
}
|
||||
|
||||
hasLayers() {
|
||||
return this.getMaxLayer() > 1;
|
||||
}
|
||||
|
||||
hasLayerSelected() {
|
||||
return this.hasLayers() && this.options.currentLayer !== 0;
|
||||
}
|
||||
|
||||
hasWholeStructureSelected() {
|
||||
return this.hasLocation() && this.options.currentLayer === 0;
|
||||
}
|
||||
|
||||
isAtMaxLayer() {
|
||||
return !this.hasLayers || this.options.currentLayer >= this.getMaxLayer();
|
||||
}
|
||||
|
||||
isAtMinLayer() {
|
||||
return !this.hasLayers || this.options.currentLayer <= 0;
|
||||
}
|
||||
|
||||
enable() {
|
||||
this.options.enable();
|
||||
this.refreshBox();
|
||||
}
|
||||
|
||||
disable() {
|
||||
this.options.disable();
|
||||
this.refreshBox();
|
||||
}
|
||||
|
||||
rename(newName) {
|
||||
this.options.rename(newName);
|
||||
}
|
||||
|
||||
place(dimensionId, worldLocation) {
|
||||
this.enable();
|
||||
this.move(dimensionId, worldLocation);
|
||||
}
|
||||
|
||||
move(dimensionId, worldLocation) {
|
||||
this.options.move(dimensionId, worldLocation);
|
||||
this.refreshBox();
|
||||
}
|
||||
|
||||
setLayer(layer) {
|
||||
if (layer < 0 || layer > this.getMaxLayer())
|
||||
throw new Error(`[Construct] Layer ${layer} is out of bounds.`);
|
||||
this.options.setLayer(layer);
|
||||
this.refreshBox();
|
||||
}
|
||||
|
||||
setVerifierEnabled(enable) {
|
||||
this.options.setVerifierEnabled(enable);
|
||||
this.verifier.refresh();
|
||||
this.verificationRenderer.refresh();
|
||||
}
|
||||
|
||||
setVerifierDistance(distance) {
|
||||
this.options.setVerifierDistance(distance);
|
||||
if (this.options.verifier.trackPlayerDistance === 0) {
|
||||
const bounds = this.getBounds();
|
||||
this.options.verifier.particleLifetime = Math.max(bounds.min.volume(bounds.max) / TicksPerSecond, 2*TicksPerSecond);
|
||||
} else {
|
||||
this.options.verifier.particleLifetime = 10;
|
||||
}
|
||||
this.verificationRenderer.refresh();
|
||||
}
|
||||
|
||||
increaseLayer() {
|
||||
if (this.isAtMaxLayer())
|
||||
this.setLayer(0);
|
||||
else
|
||||
this.setLayer(this.options.currentLayer + 1);
|
||||
}
|
||||
|
||||
decreaseLayer() {
|
||||
if (this.isAtMinLayer())
|
||||
this.setLayer(this.getMaxLayer());
|
||||
else
|
||||
this.setLayer(this.options.currentLayer - 1);
|
||||
}
|
||||
|
||||
disableInstanceWhenNoPlayersOnline() {
|
||||
// This prevents a memory leak when no players are online.
|
||||
world.beforeEvents.playerLeave.subscribe(event => {
|
||||
system.run(() => {
|
||||
if (world.getAllPlayers().length === 0 && this.options.isEnabled) {
|
||||
console.info(`[Construct] No players are online. Disabling instance: '${this.options.instanceName}'`);
|
||||
this.disable();
|
||||
this.shouldEnableOnJoin = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
world.afterEvents.playerJoin.subscribe(event => {
|
||||
if (this.shouldEnableOnJoin) {
|
||||
console.info(`[Construct] A player rejoined. Re-enabling instance: '${this.options.instanceName}'`);
|
||||
this.enable();
|
||||
this.shouldEnableOnJoin = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
toGlobalCoords(structureLocation) {
|
||||
return Vector.from(structureLocation).add(this.options.worldLocation);
|
||||
}
|
||||
|
||||
toStructureCoords(worldLocation) {
|
||||
return Vector.from(worldLocation).subtract(this.options.worldLocation);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user