From 9539a1d1a1d48e7ac4970a9d8545c25384a92797 Mon Sep 17 00:00:00 2001 From: ForestOfLight Date: Thu, 18 Sep 2025 18:42:41 -0700 Subject: [PATCH] flexible instance movement --- packs/BP/scripts/classes/Builder/Builder.js | 5 + .../classes/Instance/FlexibleInstanceMove.js | 122 +++++++++++++++--- .../scripts/classes/Instance/InstanceForm.js | 9 +- .../classes/Instance/StructureInstance.js | 13 -- packs/BP/scripts/classes/Outliner.js | 11 +- packs/BP/scripts/classes/PlayerMovement.js | 38 ++++++ packs/BP/scripts/commands/construct.js | 18 ++- packs/RP/particles/outline.particle.json | 2 +- 8 files changed, 178 insertions(+), 40 deletions(-) create mode 100644 packs/BP/scripts/classes/PlayerMovement.js diff --git a/packs/BP/scripts/classes/Builder/Builder.js b/packs/BP/scripts/classes/Builder/Builder.js index 300a144..a2b6607 100644 --- a/packs/BP/scripts/classes/Builder/Builder.js +++ b/packs/BP/scripts/classes/Builder/Builder.js @@ -3,6 +3,7 @@ import { BuilderOptions } from "./BuilderOptions"; export class Builder { playerId; materialInstanceName = void 0; + flexibleInstanceMovement = void 0; constructor(playerId) { this.playerId = playerId; @@ -15,4 +16,8 @@ export class Builder { setOption(optionId, value) { return BuilderOptions.setValue(optionId, this.playerId, value); } + + isFlexibleInstanceMoving() { + return this.flexibleInstanceMovement !== void 0; + } } \ No newline at end of file diff --git a/packs/BP/scripts/classes/Instance/FlexibleInstanceMove.js b/packs/BP/scripts/classes/Instance/FlexibleInstanceMove.js index 4ca638a..02fddef 100644 --- a/packs/BP/scripts/classes/Instance/FlexibleInstanceMove.js +++ b/packs/BP/scripts/classes/Instance/FlexibleInstanceMove.js @@ -1,35 +1,125 @@ +import { InputPermissionCategory, world, system } from "@minecraft/server"; +import { Outliner } from "../Outliner"; +import { MENU_ITEM } from "../../commands/construct"; +import { Vector } from "../../lib/Vector"; +import { PlayerMovement } from "../PlayerMovement"; +import { Builders } from "../Builder/Builders"; + export class FlexibleInstanceMove { player; instance; + outliner; + currentInstanceLocation; + runner = void 0; constructor(instance, player) { this.instance = instance; this.player = player; - this.tryBeginFlexibleMove(); + this.currentInstanceLocation = Vector.from(instance.getLocation().location); + this.onPlayerUseItemBound = this.onPlayerUseItem.bind(this); + this.onPlayerLeaveBound = this.onPlayerLeave.bind(this); + this.tryStart(); } - tryBeginFlexibleMove() { - if (this.instance.isFlexMoving) { - this.feedback('§cThis instance is already being moved.'); + tryStart() { + if (this.instance.isFlexibleMoving()) { + this.sendFeedback('§cThis instance is already being moved.'); return; } - this.beginFlexibleMove(); + this.start(); + } + + start() { + this.prepInstanceForMovement(); + this.prepPlayerForMovement(); + this.runner = system.runInterval(this.onFlexibleMovementTick.bind(this)); } - beginFlexibleMove() { - // place structure in movement mode - this.instance.startFlexibleMove(this.player); - // lock player controls - // start handling player movement controls + prepInstanceForMovement() { + this.instance.flexMovingPlayerId = this.player.id; + this.instance.disable(); + const bounds = this.instance.getBounds(); + const maxWorldLocation = this.currentInstanceLocation.add(Vector.from(bounds.max)); + this.outliner = new Outliner(this.instance.getDimension(), this.currentInstanceLocation, maxWorldLocation, 1, 1); + this.outliner.startDraw(); } - endFlexibleMove() { - this.instance.stopFlexibleMove(); + prepPlayerForMovement() { + this.allowPlayerMovement(false); + world.beforeEvents.itemUse.subscribe(this.onPlayerUseItemBound); + world.beforeEvents.playerLeave.unsubscribe(this.onPlayerLeaveBound); + this.sendFeedback(`§aNow moving "${this.instance.getName()}". Use the Construct item when finished.`); } - feedback(str) { - this.player.sendMessage(str); + onFlexibleMovementTick() { + const playerMovement = new PlayerMovement(this.player); + const instanceVelocity = this.calculateInstanceMovement(playerMovement); + this.move(instanceVelocity); + } + + calculateInstanceMovement(playerMovement) { + const speedFactor = 0.5; + + const viewDir = playerMovement.getMajorDirectionFacing(); + const forward = new Vector(viewDir.x, viewDir.y, viewDir.z); + const right = new Vector(forward.z, 0, -forward.x); + const moveInput = playerMovement.getMovementVector(); + let velocity = forward.multiply(moveInput.y).add(right.multiply(moveInput.x)); + + if (playerMovement.isJumping()) + velocity.y += 1; + if (playerMovement.isSneaking()) + velocity.y -= 1; + + if (velocity.length > 0) + velocity = velocity.normalized; + return velocity.multiply(speedFactor); + } + + move(instanceVelocity) { + this.currentInstanceLocation = this.currentInstanceLocation.add(instanceVelocity); + this.moveOutline(); + } + + moveOutline() { + const bounds = this.instance.getBounds(); + const minWorldLocation = this.currentInstanceLocation.floor() + const maxWorldLocation = minWorldLocation.add(Vector.from(bounds.max)); + this.outliner.setVertices(this.instance.getDimension(), minWorldLocation, maxWorldLocation); + } + + onPlayerUseItem(event) { + if (!event.source || event.itemStack?.typeId !== MENU_ITEM) return; + event.cancel = true; + system.run(() => this.finish()); + } + + onPlayerLeave(event) { + if (event.player?.id === this.player.id) + system.run(() => this.finish()); + } + + finish() { + system.clearRun(this.runner); + this.outliner.stopDraw(); + this.outliner = void 0; + this.instance.move(this.instance.getDimension().id, this.currentInstanceLocation); + this.instance.enable(); + this.instance.flexMovingPlayerId = void 0; + this.allowPlayerMovement(true); + const builder = Builders.get(this.player.id); + builder.flexibleInstanceMovement = void 0; + world.beforeEvents.itemUse.unsubscribe(this.onPlayerUseItemBound); + world.beforeEvents.playerLeave.unsubscribe(this.onPlayerLeaveBound); + this.sendFeedback(`§aMoved "${this.instance.getName()}" to ${this.currentInstanceLocation.floor()}.`); + } + + allowPlayerMovement(enable) { + const inputPermissions = this.player.inputPermissions; + inputPermissions.setPermissionCategory(InputPermissionCategory.Movement, enable); + } + + sendFeedback(message) { + system.run(() => this.player.onScreenDisplay.setActionBar(message)); } } - -// if the player logs out, stop the move \ No newline at end of file diff --git a/packs/BP/scripts/classes/Instance/InstanceForm.js b/packs/BP/scripts/classes/Instance/InstanceForm.js index 392b84d..e9c7e9b 100644 --- a/packs/BP/scripts/classes/Instance/InstanceForm.js +++ b/packs/BP/scripts/classes/Instance/InstanceForm.js @@ -5,6 +5,7 @@ import { InstanceButtons } from '../Enums/InstanceButtons'; import { InstanceFormBuilder } from './InstanceFormBuilder'; import { FormCancelationReason } from '@minecraft/server-ui'; import { FlexibleInstanceMove } from './FlexibleInstanceMove'; +import { Builders } from '../Builder/Builders'; export class InstanceForm { instanceName; @@ -13,6 +14,7 @@ export class InstanceForm { InstanceButtons.NextLayer, InstanceButtons.PreviousLayer, InstanceButtons.Move, + InstanceButtons.FlexibleMove, InstanceButtons.Settings, InstanceButtons.Statistics, InstanceButtons.Materials, @@ -96,7 +98,7 @@ export class InstanceForm { this.instance.move(this.player.dimension.id, this.player.location); break; case InstanceButtons.FlexibleMove: - new FlexibleInstanceMove(this.instance, this.player); + this.flexibleMovement(); break; case InstanceButtons.Settings: this.settingsForm(); @@ -135,6 +137,11 @@ export class InstanceForm { }); } + flexibleMovement() { + const builder = Builders.get(this.player.id); + builder.flexibleInstanceMovement = new FlexibleInstanceMove(this.instance, this.player); + } + setLayerForm() { InstanceFormBuilder.buildSetLayer(this.instance.getBounds().max.y, this.instance.getLayer()).show(this.player).then((response) => { if (response.canceled) diff --git a/packs/BP/scripts/classes/Instance/StructureInstance.js b/packs/BP/scripts/classes/Instance/StructureInstance.js index 4410dce..925f73e 100644 --- a/packs/BP/scripts/classes/Instance/StructureInstance.js +++ b/packs/BP/scripts/classes/Instance/StructureInstance.js @@ -246,19 +246,6 @@ export class StructureInstance { 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; } diff --git a/packs/BP/scripts/classes/Outliner.js b/packs/BP/scripts/classes/Outliner.js index 0a50d5d..34b40c1 100644 --- a/packs/BP/scripts/classes/Outliner.js +++ b/packs/BP/scripts/classes/Outliner.js @@ -1,4 +1,4 @@ -import { MolangVariableMap, system, world } from "@minecraft/server"; +import { MolangVariableMap, system, TicksPerSecond, world } from "@minecraft/server"; import { Vector } from "../lib/Vector"; export class Outliner { @@ -6,15 +6,18 @@ export class Outliner { min = new Vector(); max = new Vector(); drawParticle = "construct:outline"; - drawFrequency = 10; + drawFrequency; + particleLifetime; #drawParticles = []; #runner = void 0; - constructor(dimension, min, max) { + constructor(dimension, min, max, drawFrequency = 10, particleLifetime = 20) { this.dimension = dimension; this.min = Vector.from(min); this.max = Vector.from(max); + this.drawFrequency = drawFrequency; + this.particleLifetime = particleLifetime; this.vertices = this.getVertices(min, max); } @@ -42,6 +45,8 @@ export class Outliner { for (const [particleType, location] of this.#drawParticles) { const molang = new MolangVariableMap(); molang.setColorRGBA("dot_color", colorCallback()); + const lifetimeSeconds = this.particleLifetime / TicksPerSecond; + molang.setFloat("lifetime", lifetimeSeconds); try { this.dimension.spawnParticle(particleType, location, molang); } catch (e) { diff --git a/packs/BP/scripts/classes/PlayerMovement.js b/packs/BP/scripts/classes/PlayerMovement.js new file mode 100644 index 0000000..b3bdd78 --- /dev/null +++ b/packs/BP/scripts/classes/PlayerMovement.js @@ -0,0 +1,38 @@ +import { ButtonState, InputButton } from "@minecraft/server"; + +export class PlayerMovement { + inputInfo; + + constructor(player) { + this.player = player; + this.inputInfo = player.inputInfo; + } + + isJumping() { + return this.inputInfo.getButtonState(InputButton.Jump) === ButtonState.Pressed; + } + + isSneaking() { + return this.inputInfo.getButtonState(InputButton.Sneak) === ButtonState.Pressed; + } + + getMovementVector() { + return this.inputInfo.getMovementVector(); + } + + getMajorDirectionFacing() { + const { x, y, z } = this.player.getViewDirection(); + const xzAngle = Math.atan2(z, x) * (180 / Math.PI); + if (y > 0.7) + return { x: 0, y: 1, z: 0 }; + if (y < -0.7) + return { x: 0, y: -1, z: 0 }; + if (xzAngle >= -45 && xzAngle < 45) + return { x: 1, y: 0, z: 0 }; + else if (xzAngle >= 45 && xzAngle < 135) + return { x: 0, y: 0, z: 1 }; + else if (xzAngle >= 135 || xzAngle < -135) + return { x: -1, y: 0, z: 0 }; + return { x: 0, y: 0, z: -1 }; + } +} \ No newline at end of file diff --git a/packs/BP/scripts/commands/construct.js b/packs/BP/scripts/commands/construct.js index aa3cd9d..c87925a 100644 --- a/packs/BP/scripts/commands/construct.js +++ b/packs/BP/scripts/commands/construct.js @@ -3,8 +3,9 @@ import { extension } from '../config'; import { world, system, EntityComponentTypes, ItemStack, CommandPermissionLevel, CustomCommandStatus, Player } from '@minecraft/server'; import { MenuForm } from '../classes/MenuForm'; import { structureCollection } from '../classes/Structure/StructureCollection' +import { Builders } from '../classes/Builder/Builders'; -const ACTION_ITEM = 'construct:menu'; +export const MENU_ITEM = 'construct:menu'; const menuCmd = new Command({ name: 'construct', @@ -29,7 +30,7 @@ function givePlayerConstructItem(origin) { if (player instanceof Player === false) return { status: CustomCommandStatus.Failure, message: 'This command can only be used by players.' }; system.run(() => { - const givenItemStack = player.getComponent(EntityComponentTypes.Inventory)?.container?.addItem(new ItemStack(ACTION_ITEM)); + const givenItemStack = player.getComponent(EntityComponentTypes.Inventory)?.container?.addItem(new ItemStack(MENU_ITEM)); if (givenItemStack) player.sendMessage('§cFailed to give you the Construct item.'); else @@ -39,12 +40,17 @@ function givePlayerConstructItem(origin) { } world.beforeEvents.itemUse.subscribe((event) => { - if (!event.source || event.itemStack?.typeId !== ACTION_ITEM) return; + if (!event.source || event.itemStack?.typeId !== MENU_ITEM) return; event.cancel = true; - system.run(() => openMenu(event.source, event)); + const builder = Builders.get(event.source.id); + system.run(() => { + if (builder.isFlexibleInstanceMoving()) + return; + openMenu(event.source, event); + }); }); -function openMenu(sender, event = void 0) { +function openMenu(player, event = void 0) { const options = { jumpToInstance: true } if (event) { const instanceNames = structureCollection.getInstanceNames(); @@ -52,5 +58,5 @@ function openMenu(sender, event = void 0) { if (instanceNames.includes(instanceName)) options.instanceName = instanceName; } - new MenuForm(sender, options); + new MenuForm(player, options); } \ No newline at end of file diff --git a/packs/RP/particles/outline.particle.json b/packs/RP/particles/outline.particle.json index 304db04..7924645 100644 --- a/packs/RP/particles/outline.particle.json +++ b/packs/RP/particles/outline.particle.json @@ -17,7 +17,7 @@ }, "minecraft:emitter_shape_point": {}, "minecraft:particle_lifetime_expression": { - "max_lifetime": 1 + "max_lifetime": "variable.lifetime" }, "minecraft:particle_appearance_billboard": { "size": [0.2, 0.2],