From 37fa1b7748fcea6b05ef6e716ba2985bad3e479e Mon Sep 17 00:00:00 2001 From: "Vincent Yanzee J. Tan" Date: Thu, 19 Sep 2024 11:13:58 +0800 Subject: [PATCH] refactored src/index.ts --- pack/items/debug_stick.json | 42 +++---- src/index.ts | 244 +++++++++++++++++++++++++----------- 2 files changed, 194 insertions(+), 92 deletions(-) diff --git a/pack/items/debug_stick.json b/pack/items/debug_stick.json index edce846..2d64ba5 100644 --- a/pack/items/debug_stick.json +++ b/pack/items/debug_stick.json @@ -1,27 +1,27 @@ { - "format_version": "1.20.20", + "format_version": "1.20.20", - "minecraft:item": { + "minecraft:item": { - "description": { - "identifier": "vyt:debug_stick", - "menu_category": { - "category": "items" - } - }, + "description": { + "identifier": "vyt:debug_stick", + "menu_category": { + "category": "items" + } + }, - "components": { - "minecraft:icon": { - "texture": "stick" - }, - "minecraft:hand_equipped": true, - "minecraft:display_name": { - "value": "§dDebug Stick§r" - }, - "minecraft:max_stack_size": 1, - "minecraft:can_destroy_in_creative": false, - "minecraft:glint": true - } - } + "components": { + "minecraft:icon": { + "texture": "stick" + }, + "minecraft:hand_equipped": true, + "minecraft:display_name": { + "value": "§dDebug Stick§r" + }, + "minecraft:max_stack_size": 1, + "minecraft:can_destroy_in_creative": false, + "minecraft:glint": true + } + } } diff --git a/src/index.ts b/src/index.ts index 3b9a56a..aeb46ea 100644 --- a/src/index.ts +++ b/src/index.ts @@ -14,51 +14,52 @@ +* \*============================================================================*/ -import { Player, Block, BlockStates, world, system } from "@minecraft/server"; - +import { + Player, + Block, + BlockStates, + ItemStack, + world, + system +} from "@minecraft/server"; + +/** + * Block state type + */ type BlockStateValue = boolean | number | string; -// record of: player id -> selected property name -const record: Record = {}; +/** + * The item identifier of the debug stick + */ +const DEBUG_STICK_ID = "vyt:debug_stick"; -// show message to player's actionbar -function message(msg: string, player: Player) { - return player.runCommandAsync(`titleraw @s actionbar {"rawtext":[{"text":${JSON.stringify(msg)}}]}`); -} -// listen for interaction with blocks world.afterEvents.entityHitBlock.subscribe((ev) => { - // check for player - if (ev.damagingEntity.typeId != "minecraft:player") return; + if (ev.damagingEntity.typeId != "minecraft:player") + return; - // the player - const player = world.getAllPlayers().find(v => v.id == ev.damagingEntity.id); + const player = getPlayerByID(ev.damagingEntity.id); - // check if player holds the debug stick - if (player.getComponent("minecraft:inventory").container - .getItem(player.selectedSlotIndex)?.typeId != "vyt:debug_stick" - ) return; + if (!isHoldingDebugStick(player)) + return; - // change selected property for block - changeSelectedProperty(player, ev.hitBlock); + safeCall(changeSelectedProperty, player, ev.hitBlock); }); -// listen for clicks in blocks world.beforeEvents.itemUseOn.subscribe((ev) => { - // check for player and his held item - if (ev.source.typeId != "minecraft:player" || ev.itemStack?.typeId != "vyt:debug_stick") + if (ev.source.typeId != "minecraft:player") + return; + if (ev.itemStack?.typeId != DEBUG_STICK_ID) return; - // cancel event behaviour ev.cancel = true; - // the player - const player = world.getAllPlayers().find(v => v.id == ev.source.id); + const player = getPlayerByID(ev.source.id); - // display info about the block - if (player.isSneaking) displayBlockInfo(player, ev.block); - // update block property - else updateBlockProperty(player, ev.block); + if (player.isSneaking) + safeCall(displayBlockInfo, player, ev.block); + else + safeCall(updateBlockProperty, player, ev.block); }); @@ -66,118 +67,111 @@ world.beforeEvents.itemUseOn.subscribe((ev) => { +* Action functions \*============================================================================*/ -// change selected property +/** + * Change a selected property + * @param player + * @param block + */ function changeSelectedProperty(player: Player, block: Block) { - // the permutation of block const permutation = block.permutation; - - // get a list of allowed states for block const states = permutation.getAllStates(); const names = Object.keys(states); - // check if block has any property to change if (!names.length && !block.type.canBeWaterlogged) return message(`${block.typeId} has no properties`, player); - // get the next property name - let prop = names[names.indexOf(record[player.id]) + 1]; - let val: BlockStateValue = states[prop]; + let prop = getCurrentProperty(player, block.type.id); + let val: BlockStateValue; + + prop = names[names.indexOf(prop) + 1]; + val = states[prop]; - // prop is undefined, it means we reached the end of the property list if (!prop) { - // check if waterlog property is available if (block.type.canBeWaterlogged) { prop = "waterlogged"; val = block.isWaterlogged; } - // else, use the first property instead else { prop = names[0]; val = states[prop]; } } - // update the player's record - record[player.id] = prop; + setCurrentProperty(player, block.type.id, prop); - // send response message message(`selected "${prop}" (${val})`, player); } -// sets the state value for the property on block +/** + * Change a block property + * @param player + * @param block + */ function updateBlockProperty(player: Player, block: Block) { - // the permutation of block const permutation = block.permutation; - - // get all block states/properties const states = permutation.getAllStates(); const names = Object.keys(states); - // check block if (!names.length && !block.type.canBeWaterlogged) return message(`${block.typeId} has no properties`, player); - // retrieve property name - let prop = record[player.id]; + let prop = getCurrentProperty(player, block.type.id); let val: BlockStateValue; - // check if recorded state is available on the block if (prop == "waterlogged" ? !block.type.canBeWaterlogged : !names.includes(prop)) prop = names[0]; - // change the waterlog property instead - if (!prop && block.type.canBeWaterlogged) prop = "waterlogged"; - // toggle waterlog + if (!prop && block.type.canBeWaterlogged) + prop = "waterlogged"; + if (prop == "waterlogged") { val = !block.isWaterlogged; - // delayed because we're in read-only mode system.run(() => { block.setWaterlogged(val as boolean); }); } - // it is a property else { - // get the valid property values const valids = BlockStates.get(prop).validValues; val = valids[valids.indexOf(states[prop]) + 1]; - // val is undefined, set to the first value again - if (typeof val === "undefined") val = valids[0]; + if (typeof val === "undefined") + val = valids[0]; - // update property system.run(() => { block.setPermutation(permutation.withState(prop, val)); }); } - // update record - record[player.id] = prop; - // send response message + setCurrentProperty(player, block.type.id, prop); + message(`"${prop}" to ${val}`, player); } -// shows some useful details about the block +/** + * The block viewer feature + * @param player + * @param block + */ function displayBlockInfo(player: Player, block: Block) { - // block id let info = "§l§b" + block.typeId + "§r"; - // block coordinates + // coordinates info += "\n§4" + block.x + " §a" + block.y + " §9" + block.z; - // the matter state + // matter state info += "\n§7matter state§8: §e"; if (block.isLiquid) info += "liquid"; else if (block.isAir) info += "gas"; else info += "solid"; - // whether the block is solid and impassible + // impassable info += "\n§7hard block§8: " + (block.isSolid ? "§ayes" : "§cno"); // redstone power info += "\n§7redstone power§8: §c" + (block.getRedstonePower() ?? 0); - // block states/properties + // block states Object.entries(block.permutation.getAllStates()).forEach(([k, v]) => { info += "\n§o§7" + k + "§r§8: "; if (typeof v == "string") info += "§e"; @@ -187,12 +181,120 @@ function displayBlockInfo(player: Player, block: Block) { }); // waterlog property - if (block.type.canBeWaterlogged) info += "\n§o§7waterlogged§r§8: §6" + block.isWaterlogged; + if (block.type.canBeWaterlogged) + info += "\n§o§7waterlogged§r§8: §6" + block.isWaterlogged; // block tags block.getTags().forEach(v => info += "\n§d#" + v); - // show to player message(info, player); } + +/*============================================================================*\ ++* Utility functions +\*============================================================================*/ + +const record: Record> = {}; + +/** + * Message a player into their actionbar + * @param msg The message + * @param player The player to message + */ +function message(msg: string, player: Player) { + return player + .runCommandAsync( + `titleraw @s actionbar {"rawtext":[{"text":${JSON.stringify(msg)}}]}` + ); +} + +/** + * Returns a player's currently held item + * @param player The player + * @returns ItemStack or undefined + */ +function getHeldItem(player: Player): ItemStack | undefined { + return player + .getComponent("minecraft:inventory") + .container + .getItem(player.selectedSlotIndex); +} + +/** + * Queries whether a player is currently using the debug + * stick item + * @param player The player + * @returns true if the player currently holds the debug + * stick + */ +function isHoldingDebugStick(player: Player): boolean { + return getHeldItem(player) + ?.typeId == DEBUG_STICK_ID; +} + +/** + * Utility function to return a player class from an entity ID + * @param id The entity ID + * @returns Player instance or undefined + */ +function getPlayerByID(id: string): Player | undefined { + return world + .getAllPlayers() + .find(v => v.id == id); +} + +/** + * Get the currently selected property for a block given the + * interacting player + * @param stick The debug stick + * @param block The block ID + * @returns The current selected property or undefined + */ +function getCurrentProperty(player: Player, block: string): string | undefined { + if (!(player.id in record)) + record[player.id] = {}; + return record[player.id][block]; +} + +/** + * Change the currently selected property for a block given + * the interacting player + * @param stick The debug stick + * @param block The block ID + * @param val The new property name + */ +function setCurrentProperty(player: Player, block: string, val: string): void { + if (!(player.id in record)) + record[player.id] = {}; + record[player.id][block] = val; +} + +/** + * Safely call a function. Catch errors into content log + * @param func The function + * @param args Arguments of the function + * @returns Whatever that function will return + */ +function safeCall( + func: (...args: A) => R, + ...args: A + ): R { + + try { + return func.apply({}, args); + } + catch (e) { + let msg = "DEBUG STICK ERROR\n"; + msg += "Please report this issue on GitHub:\n"; + msg += " https://github.com/vytdev/debug-stick/issues/new\n"; + msg += "\n"; + + msg += e; + + if (e?.stack) + msg += `\n${e.stack}`; + + console.error(msg); + } +}