|
| 1 | +// Script example for ScriptAPI |
| 2 | +// Author: Jayly <https://github.com/JaylyDev> |
| 3 | +// Project: https://github.com/JaylyDev/ScriptAPI |
| 4 | +import { MinecraftBlockTypes, Vector } from "@minecraft/server"; |
| 5 | +import { registerEditorExtension, CursorControlMode, CursorTargetMode, EditorInputContext, ActionTypes, KeyboardKey, InputModifier, createPaneBindingObject, BlockVolume, SelectionBlockVolumeAction, MouseActionType, MouseInputType, executeLargeOperation } from "@minecraft/server-editor"; |
| 6 | +import { getRotationCorrectedDirectionVector, Direction } from "editor-utilities/index"; |
| 7 | +registerEditorExtension('randomFill', (uiSession) => { |
| 8 | + const tool = uiSession.toolRail.addTool({ |
| 9 | + displayString: "Random Fill (CTRL + R)", |
| 10 | + tooltip: "Left mouse click or drag-to-paint", |
| 11 | + icon: "pack://textures/editor/Select-Fill.png?filtering=point", |
| 12 | + }); |
| 13 | + const currentCursorState = uiSession.extensionContext.cursor.getState(); |
| 14 | + currentCursorState.color = { |
| 15 | + red: 1, |
| 16 | + green: 1, |
| 17 | + blue: 0, |
| 18 | + alpha: 1 |
| 19 | + }; |
| 20 | + currentCursorState.controlMode = CursorControlMode.KeyboardAndMouse; |
| 21 | + currentCursorState.targetMode = CursorTargetMode.Block; |
| 22 | + currentCursorState.visible = true; |
| 23 | + const previewSelection = uiSession.extensionContext.selectionManager.createSelection(); |
| 24 | + previewSelection.visible = true; |
| 25 | + previewSelection.borderColor = { |
| 26 | + red: 0, |
| 27 | + green: 0.5, |
| 28 | + blue: 0.5, |
| 29 | + alpha: 0.2 |
| 30 | + }; |
| 31 | + previewSelection.fillColor = { |
| 32 | + red: 0, |
| 33 | + green: 0, |
| 34 | + blue: 0.5, |
| 35 | + alpha: 0.1 |
| 36 | + }; |
| 37 | + uiSession.scratchStorage = { |
| 38 | + currentCursorState, |
| 39 | + previewSelection, |
| 40 | + }; |
| 41 | + tool.onModalToolActivation.subscribe(eventData => { |
| 42 | + if (eventData.isActiveTool) |
| 43 | + uiSession.extensionContext.cursor.setState(uiSession.scratchStorage.currentCursorState); |
| 44 | + }); |
| 45 | + uiSession.inputManager.registerKeyBinding(EditorInputContext.GlobalToolMode, uiSession.actionManager.createAction({ |
| 46 | + actionType: ActionTypes.NoArgsAction, |
| 47 | + onExecute: () => { |
| 48 | + uiSession.toolRail.setSelectedOptionId(tool.id, true); |
| 49 | + }, |
| 50 | + }), KeyboardKey.KEY_R, InputModifier.Control); |
| 51 | + const pane = uiSession.createPropertyPane({ |
| 52 | + titleAltText: "Cube", |
| 53 | + }); |
| 54 | + const settings = createPaneBindingObject(pane, { |
| 55 | + size: 3, |
| 56 | + hollow: false, |
| 57 | + face: false, |
| 58 | + blockType: MinecraftBlockTypes.stone, |
| 59 | + }); |
| 60 | + pane.addNumber(settings, "size", { |
| 61 | + titleAltText: "Brush Size", |
| 62 | + min: 1, |
| 63 | + max: 16, |
| 64 | + showSlider: true, |
| 65 | + }); |
| 66 | + pane.addBool(settings, "hollow", { |
| 67 | + titleAltText: "Hollow", |
| 68 | + }); |
| 69 | + pane.addBool(settings, "face", { |
| 70 | + titleAltText: "Face Mode", |
| 71 | + onChange: (_obj, _property, _oldValue, _newValue) => { |
| 72 | + if (uiSession.scratchStorage === undefined) { |
| 73 | + console.error('Cylinder storage was not initialized.'); |
| 74 | + return; |
| 75 | + } |
| 76 | + uiSession.scratchStorage.currentCursorState.targetMode = settings.face |
| 77 | + ? CursorTargetMode.Face |
| 78 | + : CursorTargetMode.Block; |
| 79 | + uiSession.extensionContext.cursor.setState(uiSession.scratchStorage.currentCursorState); |
| 80 | + }, |
| 81 | + }); |
| 82 | + pane.addBlockPicker(settings, "blockType", { |
| 83 | + titleAltText: "Block Type", |
| 84 | + }); |
| 85 | + tool.bindPropertyPane(pane); |
| 86 | + const onExecute = () => { |
| 87 | + if (!uiSession.scratchStorage?.previewSelection) { |
| 88 | + console.error('Cube storage was not initialized.'); |
| 89 | + return; |
| 90 | + } |
| 91 | + ; |
| 92 | + const previewSelection = uiSession.scratchStorage.previewSelection; |
| 93 | + const player = uiSession.extensionContext.player; |
| 94 | + const targetBlock = player.dimension.getBlock(uiSession.extensionContext.cursor.position); |
| 95 | + if (!targetBlock) |
| 96 | + return; |
| 97 | + const rotationY = uiSession.extensionContext.player.getRotation().y; |
| 98 | + const directionRight = getRotationCorrectedDirectionVector(rotationY, Direction.Right); |
| 99 | + const directionForward = getRotationCorrectedDirectionVector(rotationY, Direction.Back); |
| 100 | + const relativeDirection = Vector.add(Vector.add(directionRight, directionForward), Vector.up); |
| 101 | + const sizeHalf = Math.floor(settings.size / 2); |
| 102 | + let fromOffset = Vector.multiply(relativeDirection, -sizeHalf); |
| 103 | + const toOffset = Vector.multiply(relativeDirection, settings.size - 1); |
| 104 | + const isEven = settings.size % 2 === 0; |
| 105 | + if (isEven) |
| 106 | + fromOffset = Vector.add(fromOffset, Vector.up); |
| 107 | + const location = targetBlock.location; |
| 108 | + const from = { |
| 109 | + x: location.x + fromOffset.x, |
| 110 | + y: location.y + fromOffset.y, |
| 111 | + z: location.z + fromOffset.z, |
| 112 | + }; |
| 113 | + const to = { x: from.x + toOffset.x, y: from.y + toOffset.y, z: from.z + toOffset.z }; |
| 114 | + const blockVolume = new BlockVolume(from, to); |
| 115 | + if (uiSession.scratchStorage.lastVolumePlaced?.equals(blockVolume.boundingBox)) |
| 116 | + return; |
| 117 | + previewSelection.pushVolume(SelectionBlockVolumeAction.add, blockVolume); |
| 118 | + uiSession.scratchStorage.lastVolumePlaced = blockVolume.boundingBox; |
| 119 | + if (settings.hollow && |
| 120 | + blockVolume.boundingBox.spanX > 2 && |
| 121 | + blockVolume.boundingBox.spanY > 2 && |
| 122 | + blockVolume.boundingBox.spanZ > 2) { |
| 123 | + const subtractBlockVolume = new BlockVolume({ x: from.x, y: from.y + 1, z: from.z }, { x: to.x, y: to.y - 1, z: to.z }); |
| 124 | + previewSelection.pushVolume(SelectionBlockVolumeAction.subtract, subtractBlockVolume); |
| 125 | + } |
| 126 | + ; |
| 127 | + }; |
| 128 | + tool.registerMouseButtonBinding(uiSession.actionManager.createAction({ |
| 129 | + actionType: ActionTypes.MouseRayCastAction, |
| 130 | + onExecute: async (mouseRay, mouseProps) => { |
| 131 | + if (mouseProps.mouseAction == MouseActionType.LeftButton) { |
| 132 | + if (mouseProps.inputType == MouseInputType.ButtonDown) { |
| 133 | + uiSession.extensionContext.transactionManager.openTransaction("cylinderTool"); |
| 134 | + uiSession.scratchStorage.previewSelection.clear(); |
| 135 | + onExecute(); |
| 136 | + } |
| 137 | + else if (mouseProps.inputType == MouseInputType.ButtonUp) { |
| 138 | + const player = uiSession.extensionContext.player; |
| 139 | + uiSession.extensionContext.transactionManager.trackBlockChangeSelection(uiSession.scratchStorage.previewSelection); |
| 140 | + await executeLargeOperation(uiSession.scratchStorage.previewSelection, blockLocation => { |
| 141 | + const block = player.dimension.getBlock(blockLocation); |
| 142 | + block.setType(settings.blockType); |
| 143 | + }).catch(() => { |
| 144 | + uiSession.extensionContext.transactionManager.commitOpenTransaction(); |
| 145 | + uiSession.scratchStorage?.previewSelection.clear(); |
| 146 | + }).then(() => { |
| 147 | + uiSession.extensionContext.transactionManager.commitOpenTransaction(); |
| 148 | + uiSession.scratchStorage?.previewSelection.clear(); |
| 149 | + }); |
| 150 | + } |
| 151 | + ; |
| 152 | + } |
| 153 | + ; |
| 154 | + }, |
| 155 | + })); |
| 156 | + tool.registerMouseDragBinding(uiSession.actionManager.createAction({ |
| 157 | + actionType: ActionTypes.MouseRayCastAction, |
| 158 | + onExecute: (mouseRay, mouseProps) => { |
| 159 | + if (mouseProps.inputType === MouseInputType.Drag) |
| 160 | + onExecute(); |
| 161 | + }, |
| 162 | + })); |
| 163 | +}); |
0 commit comments