Files
Construct/packs/BP/scripts/classes/Instance/StructureInstance.js
T
2025-09-18 11:08:34 -07:00

293 lines
8.8 KiB
JavaScript

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;
flexMovingPlayerId = 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);
}
startFlexibleMove(player) {
this.flexMovingPlayerId = player.id;
this.disable();
// create flex movement outliner
}
stopFlexibleMove(finalLocation) {
// disable flex movement outliner
this.move(this.getDimension(), finalLocation);
this.enable();
this.flexMovingPlayerId = void 0;
}
isFlexibleMoving() {
return this.flexMovingPlayerId !== void 0;
}
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);
}
}