@minecraft/creator-tools
Version:
Minecraft Creator Tools command line and libraries.
1,039 lines • 121 kB
JSON
{
"camera-grapple.ts": {
"description": "camera-grapple.ts ",
"prefix": ["mc"],
"body": [
"// Copyright (c) Mojang AB. All rights reserved.",
"import { Vector3Utils } from '@minecraft/math';",
"import {",
" ActionTypes,",
" CursorControlMode,",
" EditorInputContext,",
" IPlayerUISession,",
" KeyboardKey,",
" registerEditorExtension,",
"} from '@minecraft/server-editor';",
"import { EasingType, TicksPerSecond, Vector3, system } from '@minecraft/server';",
"interface GrappleStorage {",
" latestRunId?: number;",
"}",
"type GrappleSession = IPlayerUISession<GrappleStorage>;",
"function flyCameraToTarget(uiSession: GrappleSession, viewTarget: Vector3, radius: number) {",
" if (uiSession.scratchStorage) {",
" const player = uiSession.extensionContext.player;",
" // This is imperfect and causes a visible pop. Would be better if we could get the player's exact eye height",
" const eyeHeight = Vector3Utils.subtract(player.getHeadLocation(), player.location);",
" const viewVector = player.getViewDirection();",
" radius = Math.max(radius, 1);",
" // FOV in first_person.json is 66 degrees",
" const halfFOV = 66 / 2;",
" // Compute adjacent side of triangle (distance) when opposite side is radius",
" const distanceAway = radius / Math.tan((halfFOV * Math.PI) / 180);",
" const destCameraLocation = Vector3Utils.subtract(viewTarget, Vector3Utils.scale(viewVector, distanceAway));",
" const destPlayerLocation = Vector3Utils.subtract(destCameraLocation, eyeHeight);",
" const easeTimeInSeconds = 1.5;",
" // Unhook camera and have it start moving to the new location",
" player.camera.setCamera('minecraft:free', {",
" rotation: { x: player.getRotation().x, y: player.getRotation().y },",
" location: { x: destCameraLocation.x, y: destCameraLocation.y, z: destCameraLocation.z },",
" easeOptions: {",
" easeTime: easeTimeInSeconds,",
" easeType: EasingType.InOutQuad,",
" },",
" });",
" uiSession.scratchStorage.latestRunId = system.runTimeout(() => {",
" // Move the player to the final location and re-hook the camera to it",
" player.teleport(destPlayerLocation);",
" player.camera.clear();",
" }, easeTimeInSeconds * TicksPerSecond);",
" }",
"}",
"/**",
" * Provides a 'Grapple' extension for quickly moving the player around the world",
" * @beta",
" */",
"export function registerCameraGrapple() {",
" registerEditorExtension(",
" 'camera-grapple-sample',",
" (uiSession: GrappleSession) => {",
" uiSession.log.debug(`Initializing extension [${uiSession.extensionContext.extensionInfo.name}]`);",
" const grappleAction = uiSession.actionManager.createAction({",
" actionType: ActionTypes.NoArgsAction,",
" onExecute: () => {",
" // don't execute if there is already a command running as this can be visually disorienting",
" if (uiSession.scratchStorage?.latestRunId) {",
" return;",
" }",
" let destBlockLoc: Vector3 | undefined = undefined;",
" const cursor = uiSession.extensionContext.cursor;",
" // Fixed cursor mode will default to the player view direction",
" if (cursor.isVisible && cursor.getProperties().controlMode !== CursorControlMode.Fixed) {",
" destBlockLoc = cursor.getPosition();",
" } else {",
" const result = uiSession.extensionContext.player.getBlockFromViewDirection();",
" if (!result) {",
" uiSession.log.warning('No Block Found. Aborting Grapple');",
" return;",
" }",
" destBlockLoc = result?.block.location;",
" }",
" // Location of the center of the block",
" const viewTarget = Vector3Utils.add(destBlockLoc, { x: 0.5, y: 0.5, z: 0.5 });",
" flyCameraToTarget(uiSession, viewTarget, 2);",
" },",
" });",
" const frameAction = uiSession.actionManager.createAction({",
" actionType: ActionTypes.NoArgsAction,",
" onExecute: () => {",
" // don't execute if there is already a command running as this can be visually disorienting",
" if (uiSession.scratchStorage?.latestRunId) {",
" return;",
" }",
" const selection = uiSession.extensionContext.selectionManager.selection;",
" if (selection.isEmpty) {",
" return;",
" }",
" const bounds = selection.getBoundingBox();",
" bounds.max = Vector3Utils.add(bounds.max, { x: 1, y: 1, z: 1 });",
" const halfSize = Vector3Utils.scale(Vector3Utils.subtract(bounds.max, bounds.min), 0.5);",
" const viewTarget = Vector3Utils.add(bounds.min, halfSize);",
" const radius = Math.sqrt(",
" halfSize.x * halfSize.x + halfSize.y * halfSize.y + halfSize.z * halfSize.z",
" );",
" flyCameraToTarget(uiSession, viewTarget, radius);",
" },",
" });",
" uiSession.inputManager.registerKeyBinding(",
" EditorInputContext.GlobalToolMode,",
" grappleAction,",
" { key: KeyboardKey.KEY_G },",
" {",
" uniqueId: 'editorSamples:grapple:flyToCursor',",
" label: 'sample.cameragrapple.keyBinding.flyToCursor',",
" }",
" );",
" uiSession.inputManager.registerKeyBinding(",
" EditorInputContext.GlobalToolMode,",
" frameAction,",
" { key: KeyboardKey.KEY_F },",
" {",
" uniqueId: 'editorSamples:grapple:flyToSelection',",
" label: 'sample.cameragrapple.keyBinding.flyToSelection',",
" }",
" );",
" return [];",
" },",
" (uiSession: GrappleSession) => {",
" uiSession.log.debug(",
" `Shutting down extension [${uiSession.extensionContext.extensionInfo.name}] for player [${uiSession.extensionContext.player.name}]`",
" );",
" if (uiSession.scratchStorage?.latestRunId) {",
" system.clearRun(uiSession.scratchStorage.latestRunId);",
" uiSession.scratchStorage.latestRunId = undefined;",
" }",
" },",
" {",
" description: 'Camera Grapple Sample Extension',",
" notes: 'by Jonas',",
" }",
" );",
"}",
""
]
},
"dye-brush.ts": {
"description": "dye-brush.ts ",
"prefix": ["mc"],
"body": [
"// Copyright (c) Mojang AB. All rights reserved.",
"import {",
" ActionTypes,",
" ColorPickerPropertyItemVariant,",
" CursorTargetMode,",
" IDropdownItem,",
" IModalTool,",
" IObservable,",
" IPlayerUISession,",
" makeObservable,",
" ModalToolLifecycleEventPayload,",
" MouseActionType,",
" MouseInputType,",
" MouseProps,",
" registerEditorExtension,",
" Selection,",
"} from '@minecraft/server-editor';",
"import {",
" BlockVolume,",
" BoundingBox,",
" BoundingBoxUtils,",
" RGBA,",
" CompoundBlockVolumeAction,",
" Dimension,",
" Direction,",
" EntityColorComponent,",
" Player,",
" Vector3,",
" RGB,",
"} from '@minecraft/server';",
"import { Vector3Utils, VECTOR3_UP } from '@minecraft/math';",
"// Color identifiers expected by EntityColorComponent",
"enum EntityColor {",
" White = 0,",
" Orange = 1,",
" Magenta = 2,",
" LightBlue = 3,",
" Yellow = 4,",
" LightGreen = 5,",
" Pink = 6,",
" Gray = 7,",
" Silver = 8,",
" Cyan = 9,",
" Purple = 10,",
" Blue = 11,",
" Brown = 12,",
" Green = 13,",
" Red = 14,",
" Black = 15,",
"}",
"const directionLookup: Record<Direction, Vector3> = {",
" [Direction.North]: { x: 0, y: 0, z: 1 },",
" [Direction.East]: { x: -1, y: 0, z: 0 },",
" [Direction.South]: { x: 0, y: 0, z: -1 },",
" [Direction.West]: { x: 1, y: 0, z: 0 },",
" [Direction.Up]: { x: 0, y: 1, z: 0 },",
" [Direction.Down]: { x: 0, y: -1, z: 0 },",
"};",
"const directionToQuadrant: Record<Direction, number> = {",
" [Direction.North]: 0,",
" [Direction.East]: 1,",
" [Direction.South]: 2,",
" [Direction.West]: 3,",
" [Direction.Up]: 4,",
" [Direction.Down]: 5,",
"};",
"const quadrantToDirection: Record<number, Direction> = {",
" [0]: Direction.North,",
" [1]: Direction.East,",
" [2]: Direction.South,",
" [3]: Direction.West,",
" [4]: Direction.Up,",
" [5]: Direction.Down,",
"};",
"export function getRotationCorrectedDirection(rotationY: number, realDirection: Direction): Direction {",
" if (realDirection === Direction.Up || realDirection === Direction.Down) {",
" return realDirection;",
" }",
" const quadrant = directionToQuadrant[realDirection];",
" const rotatedQuadrant = Math.floor(((rotationY + 405 + quadrant * 90) % 360) / 90);",
" const rotatedDirection = quadrantToDirection[rotatedQuadrant];",
" return rotatedDirection;",
"}",
"export function getRotationCorrectedDirectionVector(rotationY: number, realDirection: Direction): Vector3 {",
" const relativeDirection = getRotationCorrectedDirection(rotationY, realDirection);",
" return directionLookup[relativeDirection];",
"}",
"// Calculate nearest entity color to an RGBA color",
"function findClosestColor(targetColor: RGBA, colorPalette: Map<EntityColor, RGB>): EntityColor {",
" let minDistance = Number.MAX_VALUE;",
" let closestColor: EntityColor = EntityColor.White;",
" colorPalette.forEach((paletteColor, color) => {",
" const distance = Math.sqrt(",
" Math.pow(targetColor.red - paletteColor.red, 2) +",
" Math.pow(targetColor.green - paletteColor.green, 2) +",
" Math.pow(targetColor.blue - paletteColor.blue, 2)",
" );",
" if (distance < minDistance) {",
" minDistance = distance;",
" closestColor = color;",
" }",
" });",
" return closestColor;",
"}",
"const colorPalette = new Map<EntityColor, RGB>([",
" [EntityColor.White, { red: 1, green: 1, blue: 1 }],",
" [EntityColor.Orange, { red: 0.95, green: 0.459, blue: 0 }],",
" [EntityColor.Magenta, { red: 0.94, green: 0, blue: 0.9 }],",
" [EntityColor.LightBlue, { red: 0, green: 0.85, blue: 0.95 }],",
" [EntityColor.Yellow, { red: 0.85, green: 0.95, blue: 0 }],",
" [EntityColor.LightGreen, { red: 0, green: 0.95, blue: 0.6 }],",
" [EntityColor.Pink, { red: 0.9, green: 0.65, blue: 0.85 }],",
" [EntityColor.Gray, { red: 0.6, green: 0.6, blue: 0.6 }],",
" [EntityColor.Silver, { red: 0.75, green: 0.75, blue: 0.75 }],",
" [EntityColor.Cyan, { red: 0, green: 0.9, blue: 0.9 }],",
" [EntityColor.Purple, { red: 0.45, green: 0, blue: 0.9 }],",
" [EntityColor.Blue, { red: 0, green: 0, blue: 1 }],",
" [EntityColor.Brown, { red: 0.8, green: 0.5, blue: 0.1 }],",
" [EntityColor.Green, { red: 0, green: 1, blue: 0 }],",
" [EntityColor.Red, { red: 1, green: 0, blue: 0 }],",
" [EntityColor.Black, { red: 0, green: 0, blue: 0 }],",
"]);",
"interface DyeBrushStorage {",
" previewSelection: Selection;",
" lastVolumePlaced?: BoundingBox;",
" currentColor: EntityColor;",
" brushColor: IObservable<RGBA>;",
" brushSize: number;",
"}",
"type DyeBrushSession = IPlayerUISession<DyeBrushStorage>;",
"function onColorUpdated(newColor: RGBA, uiSession: DyeBrushSession) {",
" if (uiSession.scratchStorage) {",
" uiSession.scratchStorage.previewSelection.setFillColor(newColor);",
" uiSession.scratchStorage.previewSelection.setOutlineColor({ ...newColor, alpha: 1 });",
" const cursorProps = uiSession.extensionContext.cursor.getProperties();",
" cursorProps.outlineColor = { ...newColor, alpha: 1 };",
" cursorProps.targetMode = CursorTargetMode.Face;",
" uiSession.extensionContext.cursor.setProperties(cursorProps);",
" }",
"}",
"function addDyeBrushPane(uiSession: DyeBrushSession, tool: IModalTool) {",
" if (!uiSession.scratchStorage) {",
" throw Error('UI Session storage should exist');",
" }",
" const brushColor = uiSession.scratchStorage.brushColor;",
" const brushSize = uiSession.scratchStorage.brushSize;",
" const pane = uiSession.createPropertyPane({",
" title: 'sample.dyeBrush.pane.title',",
" });",
" const entityBrush = makeObservable(EntityColor.White);",
" onColorUpdated(brushColor.value, uiSession);",
" pane.addDropdown(entityBrush, {",
" title: 'Brush',",
" entries: Object.values(EntityColor).reduce<IDropdownItem[]>((list, dye, index) => {",
" if (typeof dye === 'string') {",
" list.push({",
" label: dye,",
" value: index,",
" });",
" }",
" return list;",
" }, []),",
" onChange: (newVal: number) => {",
" if (newVal in EntityColor) {",
" const foundColor = colorPalette.get(newVal);",
" if (foundColor) {",
" brushColor.set({ ...foundColor, alpha: brushColor.value.alpha });",
" }",
" onColorUpdated(brushColor.value, uiSession);",
" }",
" },",
" });",
" pane.addColorPicker(brushColor, {",
" variant: ColorPickerPropertyItemVariant.Expanded,",
" onChange: (color: RGBA) => {",
" entityBrush.set(findClosestColor(color, colorPalette));",
" onColorUpdated(brushColor.value, uiSession);",
" },",
" });",
" tool.bindPropertyPane(pane);",
" const onExecuteBrush = () => {",
" if (uiSession.scratchStorage === undefined) {",
" uiSession.log.error('Storage was not initialized.');",
" return;",
" }",
" const previewSelection = uiSession.scratchStorage.previewSelection;",
" const player = uiSession.extensionContext.player;",
" const targetBlock = player.dimension.getBlock(uiSession.extensionContext.cursor.getPosition());",
" if (targetBlock === undefined) {",
" return;",
" }",
" const rotationY = uiSession.extensionContext.player.getRotation().y;",
" const directionRight = getRotationCorrectedDirectionVector(rotationY, Direction.East);",
" const directionForward = getRotationCorrectedDirectionVector(rotationY, Direction.South);",
" const relativeDirection = Vector3Utils.add(Vector3Utils.add(directionRight, directionForward), VECTOR3_UP);",
" const sizeHalf = Math.floor(brushSize / 2);",
" let fromOffset = Vector3Utils.scale(relativeDirection, -sizeHalf);",
" const toOffset = Vector3Utils.scale(relativeDirection, brushSize - 1);",
" const isEven = brushSize % 2 === 0;",
" if (isEven) {",
" fromOffset = Vector3Utils.add(fromOffset, VECTOR3_UP);",
" }",
" const location = targetBlock.location;",
" const from: Vector3 = {",
" x: location.x + fromOffset.x,",
" y: location.y + fromOffset.y,",
" z: location.z + fromOffset.z,",
" };",
" const to: Vector3 = { x: from.x + toOffset.x, y: from.y + toOffset.y, z: from.z + toOffset.z };",
" const blockVolume = new BlockVolume(from, to);",
" const bounds = blockVolume.getBoundingBox();",
" if (uiSession.scratchStorage.lastVolumePlaced) {",
" if (BoundingBoxUtils.equals(uiSession.scratchStorage.lastVolumePlaced, bounds)) {",
" return;",
" }",
" }",
" previewSelection.pushVolume({ action: CompoundBlockVolumeAction.Add, volume: blockVolume });",
" uiSession.scratchStorage.lastVolumePlaced = bounds;",
" };",
" const mouseButtonAction = uiSession.actionManager.createAction({",
" actionType: ActionTypes.MouseRayCastAction,",
" onExecute: (_, mouseProps: MouseProps) => {",
" if (uiSession.scratchStorage === undefined) {",
" uiSession.log.error('Storage was not initialized.');",
" return;",
" }",
" if (mouseProps.mouseAction === MouseActionType.LeftButton) {",
" if (mouseProps.inputType === MouseInputType.ButtonDown) {",
" uiSession.scratchStorage.previewSelection.clear();",
" onExecuteBrush();",
" } else if (mouseProps.inputType === MouseInputType.ButtonUp) {",
" const player: Player = uiSession.extensionContext.player;",
" const dimension: Dimension = player.dimension;",
" const iterator = uiSession.scratchStorage.previewSelection.getBlockLocationIterator();",
" for (const pos of iterator) {",
" const entities = dimension.getEntities({ location: pos, closest: 1 });",
" for (const entity of entities) {",
" const colorComp = entity.getComponent('minecraft:color') as EntityColorComponent;",
" if (colorComp) {",
" colorComp.value = entityBrush.value;",
" }",
" }",
" }",
" uiSession.scratchStorage.previewSelection.clear();",
" }",
" }",
" },",
" });",
" tool.registerMouseButtonBinding(mouseButtonAction);",
" const executeBrushRayAction = uiSession.actionManager.createAction({",
" actionType: ActionTypes.MouseRayCastAction,",
" onExecute: (_, mouseProps: MouseProps) => {",
" if (mouseProps.inputType === MouseInputType.Drag) {",
" onExecuteBrush();",
" }",
" },",
" });",
" tool.registerMouseDragBinding(executeBrushRayAction);",
" // Example for adding mouse wheel",
" const executeBrushSizeAction = uiSession.actionManager.createAction({",
" actionType: ActionTypes.MouseRayCastAction,",
" onExecute: (_, mouseProps: MouseProps) => {",
" if (mouseProps.mouseAction === MouseActionType.Wheel) {",
" if (mouseProps.inputType === MouseInputType.WheelOut) {",
" if (entityBrush.value > 0) {",
" entityBrush.set(entityBrush.value - 1);",
" }",
" } else if (mouseProps.inputType === MouseInputType.WheelIn) {",
" if (entityBrush.value < 15) {",
" entityBrush.set(entityBrush.value + 1);",
" }",
" }",
" onColorUpdated(brushColor.value, uiSession);",
" }",
" },",
" });",
" tool.registerMouseWheelBinding(executeBrushSizeAction);",
" tool.onModalToolActivation.subscribe((evt: ModalToolLifecycleEventPayload) => {",
" if (evt.isActiveTool) {",
" onColorUpdated(brushColor.value, uiSession);",
" }",
" uiSession.scratchStorage?.previewSelection?.clear();",
" });",
" pane.hide();",
"}",
"export function addDyeBrushTool(uiSession: DyeBrushSession) {",
" const tool = uiSession.toolRail.addTool({",
" title: 'sample.dyebrush.tool.title',",
" tooltip: 'sample.dyebrush.tool.tooltip',",
" icon: 'pack://textures/dye-brush.png',",
" });",
" return tool;",
"}",
"export function registerDyeBrushExtension() {",
" registerEditorExtension<DyeBrushStorage>(",
" 'dye-brush-sample',",
" (uiSession: IPlayerUISession<DyeBrushStorage>) => {",
" uiSession.log.debug(`Initializing extension [${uiSession.extensionContext.extensionInfo.name}]`);",
" const previewSelection = uiSession.extensionContext.selectionManager.create();",
" previewSelection.visible = true;",
" const storage: DyeBrushStorage = {",
" previewSelection: previewSelection,",
" currentColor: EntityColor.White,",
" brushColor: makeObservable<RGBA>({ red: 1, green: 1, blue: 1, alpha: 0.5 }),",
" brushSize: 4,",
" };",
" uiSession.scratchStorage = storage;",
" const cubeBrushTool = addDyeBrushTool(uiSession);",
" addDyeBrushPane(uiSession, cubeBrushTool);",
" return [];",
" },",
" (uiSession: IPlayerUISession<DyeBrushStorage>) => {",
" uiSession.log.debug(`Shutting down extension [${uiSession.extensionContext.extensionInfo.name}] `);",
" },",
" {",
" description: 'Dye Brush Sample Extension',",
" notes: 'By Eser',",
" }",
" );",
"}",
""
]
},
"farm-generator.ts": {
"description": "farm-generator.ts ",
"prefix": ["mc"],
"body": [
"// Copyright (c) Mojang AB. All rights reserved.",
"import { VECTOR3_ZERO } from '@minecraft/math';",
"import {",
" ActionTypes,",
" EditorInputContext,",
" IModalTool,",
" IObservable,",
" IPlayerUISession,",
" InputModifier,",
" KeyboardKey,",
" MouseActionType,",
" MouseInputType,",
" MouseProps,",
" Ray,",
" makeObservable,",
" registerEditorExtension,",
"} from '@minecraft/server-editor';",
"import { Player, Vector3 } from '@minecraft/server';",
"import { MinecraftBlockTypes, MinecraftEntityTypes } from '@minecraft/vanilla-data';",
"type CommonSettingsType = {",
" farmWidth: IObservable<number>;",
" farmLength: IObservable<number>;",
" irrigation: IObservable<boolean>;",
" fenceType: IObservable<number>;",
"};",
"type CropSettingsType = {",
" wheat: IObservable<boolean>;",
" pumpkin: IObservable<boolean>;",
" potato: IObservable<boolean>;",
" carrot: IObservable<boolean>;",
" beetroot: IObservable<boolean>;",
"};",
"type AnimalSettingsType = {",
" pig: IObservable<boolean>;",
" sheep: IObservable<boolean>;",
" cow: IObservable<boolean>;",
"};",
"function getRandomInt(upper: number) {",
" return Math.floor(Math.random() * (upper + 1));",
"}",
"function fenceTypeToBlockType(fenceType: number): string {",
" switch (fenceType) {",
" case 0:",
" return MinecraftBlockTypes.OakFence;",
" case 1:",
" return MinecraftBlockTypes.BirchFence;",
" case 2:",
" return MinecraftBlockTypes.AcaciaFence;",
" case 3:",
" return MinecraftBlockTypes.BambooFence;",
" case 4:",
" return MinecraftBlockTypes.CherryFence;",
" case 5:",
" return MinecraftBlockTypes.JungleFence;",
" case 6:",
" return MinecraftBlockTypes.SpruceFence;",
" case 7:",
" return MinecraftBlockTypes.WarpedFence;",
" case 8:",
" return MinecraftBlockTypes.CrimsonFence;",
" default:",
" return MinecraftBlockTypes.OakFence;",
" }",
"}",
"const buildFarm = (",
" targetCorner: Vector3,",
" x: number,",
" z: number,",
" length: number,",
" width: number,",
" possibleAnimals: MinecraftEntityTypes[],",
" possibleCrops: string[],",
" player: Player,",
" commonSettings: CommonSettingsType",
") => {",
" let didPlaceAnimal = false;",
" for (let i = 0; i < width; i++) {",
" for (let j = length - 1; j > -1; j--) {",
" const xOffset = i * x;",
" const zOffset = z * j;",
" const location: Vector3 = { x: targetCorner.x + xOffset, y: targetCorner.y, z: targetCorner.z + zOffset };",
" const locationAbove: Vector3 = {",
" x: targetCorner.x + xOffset,",
" y: targetCorner.y + 1,",
" z: targetCorner.z + zOffset,",
" };",
" const block = player.dimension.getBlock(location);",
" const blockAbove = player.dimension.getBlock(locationAbove);",
" const isBorder = i === 0 || i === width - 1 || j === 0 || j === length - 1;",
" if (xOffset % 3 === 0 && !isBorder && commonSettings.irrigation.value) {",
" block?.setType(MinecraftBlockTypes.Water);",
" } else {",
" block?.setType(MinecraftBlockTypes.Farmland);",
" }",
" if (isBorder) {",
" blockAbove?.setType(fenceTypeToBlockType(commonSettings.fenceType.value));",
" } else if (possibleAnimals.length > 0 && getRandomInt(5) === 5) {",
" const animal = getRandomInt(possibleAnimals.length - 1);",
" const entityType = possibleAnimals[animal];",
" player.dimension.spawnEntity(entityType, blockAbove?.location ?? VECTOR3_ZERO, {",
" initialPersistence: true,",
" });",
" didPlaceAnimal = true;",
" } else if (!block?.isLiquid && possibleCrops.length > 0) {",
" const crop = getRandomInt(possibleCrops.length - 1);",
" const blockType = possibleCrops[crop];",
" blockAbove?.setType(blockType);",
" }",
" }",
" }",
" // Guarantee there is at least one animal spawned if we haven't placed one yet and there is room to place one",
" if (!didPlaceAnimal && possibleAnimals.length > 0 && width > 2 && length > 2) {",
" const locationAbove: Vector3 = {",
" x: targetCorner.x + x,",
" y: targetCorner.y + 1,",
" z: targetCorner.z + z,",
" };",
" const blockAbove = player.dimension.getBlock(locationAbove);",
" const animal = getRandomInt(possibleAnimals.length - 1);",
" const entityType = possibleAnimals[animal];",
" player.dimension.spawnEntity(entityType, blockAbove?.location ?? VECTOR3_ZERO, { initialPersistence: true });",
" }",
"};",
"function addFarmGeneratorSettingsPane(uiSession: IPlayerUISession, tool: IModalTool) {",
" const windowPane = uiSession.createPropertyPane({",
" title: 'sample.farmgenerator.pane.title',",
" });",
" const cropPane = windowPane.createSubPane({",
" title: 'sample.farmgenerator.pane.crops.title',",
" });",
" const animalPane = windowPane.createSubPane({",
" title: 'sample.farmgenerator.pane.animals.title',",
" });",
" const commonSettings: CommonSettingsType = {",
" farmWidth: makeObservable(10),",
" farmLength: makeObservable(10),",
" irrigation: makeObservable(false),",
" fenceType: makeObservable(0),",
" };",
" const cropSettings: CropSettingsType = {",
" wheat: makeObservable(false),",
" pumpkin: makeObservable(false),",
" potato: makeObservable(false),",
" carrot: makeObservable(false),",
" beetroot: makeObservable(false),",
" };",
" const animalSettings: AnimalSettingsType = {",
" pig: makeObservable(false),",
" sheep: makeObservable(false),",
" cow: makeObservable(false),",
" };",
" const onExecuteGenerator = (ray?: Ray) => {",
" const player: Player = uiSession.extensionContext.player;",
" // Use the mouse ray if it is available",
" const raycastResult =",
" ray !== undefined",
" ? player.dimension.getBlockFromRay(ray.location, ray.direction)",
" : player.getBlockFromViewDirection();",
" if (!raycastResult) {",
" uiSession.log.error('No block from view vector');",
" return;",
" }",
" const targetBlock = raycastResult.block;",
" let targetCorner: Vector3 = { x: targetBlock.location.x, y: targetBlock.location.y, z: targetBlock.location.z };",
" const possibleCrops: string[] = [];",
" if (cropSettings.beetroot.value) {",
" possibleCrops.push(MinecraftBlockTypes.Beetroot);",
" }",
" if (cropSettings.carrot.value) {",
" possibleCrops.push(MinecraftBlockTypes.Carrots);",
" }",
" if (cropSettings.pumpkin.value) {",
" possibleCrops.push(MinecraftBlockTypes.Pumpkin);",
" }",
" if (cropSettings.wheat.value) {",
" possibleCrops.push(MinecraftBlockTypes.Wheat);",
" }",
" if (cropSettings.potato.value) {",
" possibleCrops.push(MinecraftBlockTypes.Potatoes);",
" }",
" const possibleAnimals: MinecraftEntityTypes[] = [];",
" if (animalSettings.sheep.value) {",
" possibleAnimals.push(MinecraftEntityTypes.Sheep);",
" }",
" if (animalSettings.cow.value) {",
" possibleAnimals.push(MinecraftEntityTypes.Cow);",
" }",
" if (animalSettings.pig.value) {",
" possibleAnimals.push(MinecraftEntityTypes.Pig);",
" }",
" let x = 1;",
" let z = 1;",
" let length = commonSettings.farmLength.value;",
" let width = commonSettings.farmWidth.value;",
" if (Math.round(player.getViewDirection().z) === -1) {",
" targetCorner = {",
" x: targetCorner.x + (commonSettings.farmWidth.value / 2 - 1),",
" y: targetCorner.y,",
" z: targetCorner.z - (commonSettings.farmLength.value / 2 - 1),",
" };",
" uiSession.log.info('Facing north');",
" x = -1;",
" } else if (Math.round(player.getViewDirection().x) === 1) {",
" targetCorner = {",
" x: targetCorner.x + (commonSettings.farmWidth.value / 2 - 1),",
" y: targetCorner.y,",
" z: targetCorner.z + (commonSettings.farmLength.value / 2 - 1),",
" };",
" uiSession.log.info('Facing east');",
" length = commonSettings.farmWidth.value;",
" width = commonSettings.farmLength.value;",
" x = -1;",
" z = -1;",
" }",
" if (Math.round(player.getViewDirection().z) === 1) {",
" targetCorner = {",
" x: targetCorner.x - (commonSettings.farmWidth.value / 2 - 1),",
" y: targetCorner.y,",
" z: targetCorner.z + (commonSettings.farmLength.value / 2 - 1),",
" };",
" uiSession.log.info('Facing south');",
" z = -1;",
" } else if (Math.round(player.getViewDirection().x) === -1) {",
" targetCorner = {",
" x: targetCorner.x - (commonSettings.farmWidth.value / 2 - 1),",
" y: targetCorner.y,",
" z: targetCorner.z - (commonSettings.farmLength.value / 2 - 1),",
" };",
" uiSession.log.info('Facing west');",
" length = commonSettings.farmWidth.value;",
" width = commonSettings.farmLength.value;",
" }",
" buildFarm(targetCorner, x, z, length, width, possibleAnimals, possibleCrops, player, commonSettings);",
" };",
" // Create an action that will be executed on left mouse click",
" const executeMouseAction = uiSession.actionManager.createAction({",
" actionType: ActionTypes.MouseRayCastAction,",
" onExecute: (mouseRay: Ray, mouseProps: MouseProps) => {",
" if (mouseProps.mouseAction === MouseActionType.LeftButton && mouseProps.inputType === MouseInputType.ButtonDown) {",
" onExecuteGenerator(mouseRay);",
" }",
" },",
" });",
" // Create and an action that will be executed on CTRL + P",
" const executeKeyAction = uiSession.actionManager.createAction({",
" actionType: ActionTypes.NoArgsAction,",
" onExecute: () => {",
" onExecuteGenerator();",
" },",
" });",
" // Register actions as input bindings to tool context",
" tool.registerKeyBinding(",
" executeKeyAction,",
" { key: KeyboardKey.KEY_P, modifier: InputModifier.Control },",
" {",
" uniqueId: 'editorSamples:farmGenerator:place',",
" label: 'sample.farmgenerator.keyBinding.place',",
" }",
" );",
" tool.registerMouseButtonBinding(executeMouseAction);",
" windowPane.addNumber(commonSettings.farmLength, {",
" title: 'sample.farmgenerator.pane.length',",
" min: 2,",
" max: 20,",
" isInteger: true,",
" });",
" windowPane.addNumber(commonSettings.farmWidth, {",
" title: 'sample.farmgenerator.pane.width',",
" min: 2,",
" max: 20,",
" isInteger: true,",
" });",
" windowPane.addDropdown(commonSettings.fenceType, {",
" title: 'sample.farmgenerator.pane.fence',",
" enable: true,",
" entries: [",
" {",
" label: 'Oak',",
" value: 0,",
" },",
" {",
" label: 'Birch',",
" value: 1,",
" },",
" {",
" label: 'Acacia',",
" value: 2,",
" },",
" {",
" label: 'Bamboo',",
" value: 3,",
" },",
" {",
" label: 'Cherry',",
" value: 4,",
" },",
" {",
" label: 'Jungle',",
" value: 5,",
" },",
" {",
" label: 'Spruce',",
" value: 6,",
" },",
" {",
" label: 'Warped',",
" value: 7,",
" },",
" {",
" label: 'Crimson',",
" value: 8,",
" },",
" ],",
" });",
" windowPane.addBool(commonSettings.irrigation, {",
" title: 'sample.farmgenerator.pane.irrigation',",
" tooltip: 'sample.farmgenerator.pane.irrigation.tooltip',",
" });",
" cropPane.addBool(cropSettings.wheat, {",
" title: 'sample.farmgenerator.pane.crops.wheat',",
" });",
" cropPane.addBool(cropSettings.potato, {",
" title: 'sample.farmgenerator.pane.crops.potato',",
" });",
" cropPane.addBool(cropSettings.beetroot, {",
" title: 'sample.farmgenerator.pane.crops.beets',",
" });",
" cropPane.addBool(cropSettings.pumpkin, {",
" title: 'sample.farmgenerator.pane.crops.pumpkin',",
" });",
" cropPane.addBool(cropSettings.carrot, {",
" title: 'sample.farmgenerator.pane.crops.carrot',",
" });",
" animalPane.addBool(animalSettings.cow, {",
" title: 'sample.farmgenerator.pane.animals.cow',",
" });",
" animalPane.addBool(animalSettings.sheep, {",
" title: 'sample.farmgenerator.pane.animals.sheep',",
" });",
" animalPane.addBool(animalSettings.pig, {",
" title: 'sample.farmgenerator.pane.animals.pig',",
" });",
" tool.bindPropertyPane(windowPane);",
"}",
"/**",
" * Create a new tool rail item for farm generator",
" */",
"function addFarmGeneratorTool(uiSession: IPlayerUISession) {",
" // Create action",
" const toolToggleAction = uiSession.actionManager.createAction({",
" actionType: ActionTypes.NoArgsAction,",
" onExecute: () => {",
" uiSession.toolRail.setSelectedToolId(tool.id);",
" },",
" });",
" const tool = uiSession.toolRail.addTool(",
" {",
" title: 'sample.farmgenerator.tool.title',",
" icon: 'pack://textures/farm-generator.png',",
" tooltip: 'sample.farmgenerator.tool.tooltip',",
" inputContextId: 'editorSamples:farmGenerator',",
" inputContextLabel: 'sample.farmgenerator.tool.title',",
" },",
" toolToggleAction",
" );",
" // Register a global shortcut (CTRL + SHIFT + P) to select the tool",
" uiSession.inputManager.registerKeyBinding(",
" EditorInputContext.GlobalToolMode,",
" toolToggleAction,",
" { key: KeyboardKey.KEY_F, modifier: InputModifier.Control | InputModifier.Shift },",
" {",
" uniqueId: 'editorSamples:farmGenerator:toggleTool',",
" label: 'sample.farmgenerator.keyBinding.toggleTool',",
" }",
" );",
" return tool;",
"}",
"/**",
" * Register Farm Generator extension",
" */",
"export function registerFarmGeneratorExtension() {",
" registerEditorExtension(",
" 'FarmGenerator-sample',",
" (uiSession: IPlayerUISession) => {",
" uiSession.log.debug(`Initializing [${uiSession.extensionContext.extensionInfo.name}] extension`);",
" // Add tool to tool rail",
" const farmGeneratorTool = addFarmGeneratorTool(uiSession);",
" // Create settings pane/window",
" addFarmGeneratorSettingsPane(uiSession, farmGeneratorTool);",
" return [];",
" },",
" (uiSession: IPlayerUISession) => {",
" uiSession.log.debug(`Initializing [${uiSession.extensionContext.extensionInfo.name}] extension`);",
" },",
" {",
" description: 'Farm Generator Sample Extension',",
" notes: 'by Molly',",
" }",
" );",
"}",
""
]
},
"goto-mark.ts": {
"description": "goto-mark.ts ",
"prefix": ["mc"],
"body": [
"// Copyright (c) Mojang AB. All rights reserved.",
"import {",
" ActionTypes,",
" IButtonPropertyItem,",
" IDropdownItem,",
" IDropdownPropertyItem,",
" IModalTool,",
" IObservable,",
" IPlayerUISession,",
" IPropertyPane,",
" UserDefinedTransactionHandle,",
" bindDataSource,",
" makeObservable,",
" registerEditorExtension,",
" registerUserDefinedTransactionHandler,",
"} from '@minecraft/server-editor';",
"import { Vector3, system } from '@minecraft/server';",
"const storedLocationDynamicPropertyName = 'goto-mark:storedLocations'; // The key of the stored location dynamic property",
"const storedLocationNameMaxLength = 16; // This is the maximum length of the name of a stored location",
"const storedLocationsMax = 9; // The maximum number of stored locations",
"type GotoTeleportTransactionPayload = {",
" current: Vector3;",
" destination: Vector3;",
"};",
"// The stored location data structure that represents each of the stored locations",
"// this is also the JSON format that is stored in the dynamic property",
"type LocationData = {",
" location: Vector3;",
" name: string;",
"};",
"// UI Pane data for the whole extension pane",
"type ParentPaneDataSourceType = {",
" playerLocation: Vector3;",
"};",
"// UI Pane data for the sub pane with the stored locations",
"type LocationPaneDataSourceType = {",
" newName: IObservable<string>;",
"};",
"// Extension storage data which is pertinent to the the player's context of this extension",
"type ExtensionStorage = {",
" tool?: IModalTool; // The tool handle for the extension",
" previousLocation: Vector3; // The players last recorded position",
" updateHandle?: number; // The handle for the repeating interval that updates the player position",
" parentPaneDataSource?: ParentPaneDataSourceType; // The data source for the parent pane",
" parentPane?: IPropertyPane; // The parent pane",
" dropdownMenu?: IDropdownPropertyItem; // The dropdown",
" locationPaneDataSource?: LocationPaneDataSourceType; // The data source for the location pane",
" storedLocations: LocationData[]; // The list of stored locations",
" transactionHandler: UserDefinedTransactionHandle<GotoTeleportTransactionPayload>; // The transaction handler for the extension",
" teleportButton?: IButtonPropertyItem;",
"};",
"// Handy helper to turn a Vector3 into a pretty string",
"function vector3ToString(vec: Vector3): string {",
" return `(${vec.x}, ${vec.y}, ${vec.z})`;",
"}",
"// Equality check for a Vector3",
"function vector3Equals(vec1: Vector3, vec2: Vector3): boolean {",
" return vec1.x === vec2.x && vec1.y === vec2.y && vec1.z === vec2.z;",
"}",
"// Truncate a Vector3 to the nearest block",
"function vector3Truncate(vec: Vector3): Vector3 {",
" const blockLocation: Vector3 = { x: Math.floor(vec.x), y: Math.floor(vec.y), z: Math.floor(vec.z) };",
" return blockLocation;",
"}",
"function mapDropdownItems(storage: ExtensionStorage): IDropdownItem[] {",
" return storage.storedLocations.map((v, index): IDropdownItem => {",
" const item: IDropdownItem = {",
" label: `${index + 1}: ${v.name} (${vector3ToString(v.location)})`,",
" value: index,",
" };",
" return item;",
" });",
"}",
"function createTransaction(uiSession: IPlayerUISession<ExtensionStorage>, current: Vector3, destination: Vector3) {",
" const transactionPayload: GotoTeleportTransactionPayload = {",
" current,",
" destination,",
" };",
" if (!uiSession.scratchStorage) {",
" return;",
" }",
" uiSession.extensionContext.transactionManager.openTransaction('goto position');",
" uiSession.scratchStorage.transactionHandler.addUserDefinedOperation(transactionPayload, 'Goto(Teleport)');",
" uiSession.extensionContext.transactionManager.commitOpenTransaction();",
"}",
"function teleportTo(uiSession: IPlayerUISession<ExtensionStorage>, destination: Vector3) {",
" createTransaction(uiSession, uiSession.extensionContext.player.location, destination);",
" uiSession.log.info(`Teleporting to location ${vector3ToString(destination)}`);",
" try {",
" uiSession.extensionContext.player.teleport(destination);",
" } catch (e) {",
" uiSession.log.error(`Teleport failed: ${e}`);",
" }",
"}",
"// Add the extension to the tool rail and give it an icon",
"function addExtensionTool(uiSession: IPlayerUISession<ExtensionStorage>): IModalTool {",
" const tool = uiSession.toolRail.addTool({",
" title: 'sample.gotomark.tool.title',",
" icon: 'pack://textures/goto-mark.png',",
" tooltip: 'Set or Jump to a stored location',",
" });",
" return tool;",
"}",
"function buildParentPane(uiSession: IPlayerUISession<ExtensionStorage>, storage: ExtensionStorage): IPropertyPane {",
" const parentPane = uiSession.createPropertyPane({",
" title: 'sample.gotomark.pane.title',",
" });",
" const currentLocation = vector3Truncate(uiSession.extensionContext.player.location);",
" const initialPaneData: ParentPaneDataSourceType = {",
" playerLocation: currentLocation,",
" };",
" storage.parentPaneDataSource = bindDataSource(parentPane, initialPaneData);",
" storage.previousLocation = currentLocation;",
" parentPane.addVector3_deprecated(storage.parentPaneDataSource, 'playerLocation', {",
" title: 'sample.gotomark.pane.location',",
" });",
" // Run interval to refresh coordinate population",
" // Issue with refresh on tick rate with show/hide",
" storage.updateHandle = system.runInterval(() => {",
" if (!storage.parentPaneDataSource) {",
" return;",
" }",
" const currentLocation = vector3Truncate(uiSession.extensionContext.player.location);",
" const previousLocation = vector3Truncate(storage.previousLocation);",
" // Player hasn't moved - don't refresh",
" if (vector3Equals(currentLocation, previousLocation) || !parentPane.visible) {",
" return;",
" }",
" storage.previousLocation = currentLocation;",
" storage.parentPaneDataSource.playerLocation = { ...currentLocation };",
" }, 10);",
" // Jump directly to the location specified in the Vector3 UI control",
" parentPane.addButton(",
" uiSession.actionManager.createAction({",
" actionType: ActionTypes.NoArgsAction,",
" onExecute: () => {",
" if (!storage.parentPaneDataSource) {",
" uiSession.log.error('An error occurred: No UI pane datasource could be found');",
" return;",
" }",
" const panelLocation = storage.parentPaneDataSource.playerLocation;",
" teleportTo(uiSession, panelLocation);",
" },",
" }),",
" {",
" title: 'sample.gotomark.pane.button.teleport',",
" visible: true,",
" }",
" );",
" parentPane.addDivider();",
" // Set the players spawn location based on the current location (or the location typed into the",
" // Vector3 UI control)",
" parentPane.addButton(",
" uiSession.actionManager.createAction({",
" actionType: ActionTypes.NoArgsAction,",
" onExecute: () => {",
" if (!storage.parentPaneDataSource) {",
" uiSession.log.error('An error occurred: No UI pane dataso