From 18c693dabc75c136f5922ee4876370f43723d9aa Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Tue, 18 Jun 2024 03:47:20 +0300 Subject: [PATCH 01/19] refactor app status provider, adding loading texts --- src/errorLoadingScreenHelpers.ts | 12 ++++++++++++ src/guessProblem.ts | 5 ----- src/react/AppStatusProvider.tsx | 2 +- 3 files changed, 13 insertions(+), 6 deletions(-) create mode 100644 src/errorLoadingScreenHelpers.ts delete mode 100644 src/guessProblem.ts diff --git a/src/errorLoadingScreenHelpers.ts b/src/errorLoadingScreenHelpers.ts new file mode 100644 index 000000000..2f882f897 --- /dev/null +++ b/src/errorLoadingScreenHelpers.ts @@ -0,0 +1,12 @@ +export const guessProblem = (errorMessage: string) => { + if (errorMessage.endsWith('Socket error: ECONNREFUSED')) { + return 'Most probably the server is not running.' + } +} + +export const loadingTexts = [ + 'Like the project? Give us a star on GitHub or rate us on AlternativeTo!', + 'To stay updated with the latest changes, go to the GitHub page, click on "Watch", choose "Custom", and then opt for "Releases"!', + 'Upvote features on GitHub issues to help us prioritize them!', + 'Want to contribute to the project? Check out Contributing.md on GitHub!', +] diff --git a/src/guessProblem.ts b/src/guessProblem.ts deleted file mode 100644 index ecc7dbf15..000000000 --- a/src/guessProblem.ts +++ /dev/null @@ -1,5 +0,0 @@ -export const guessProblem = (errorMessage: string) => { - if (errorMessage.endsWith('Socket error: ECONNREFUSED')) { - return 'Most probably the server is not running.' - } -} diff --git a/src/react/AppStatusProvider.tsx b/src/react/AppStatusProvider.tsx index 6ae39308d..a2f6234a8 100644 --- a/src/react/AppStatusProvider.tsx +++ b/src/react/AppStatusProvider.tsx @@ -3,7 +3,7 @@ import { useEffect } from 'react' import { activeModalStack, activeModalStacks, hideModal, insertActiveModalStack, miscUiState } from '../globalState' import { resetLocalStorageWorld } from '../browserfs' import { fsState } from '../loadSave' -import { guessProblem } from '../guessProblem' +import { guessProblem } from '../errorLoadingScreenHelpers' import AppStatus from './AppStatus' import DiveTransition from './DiveTransition' import { useDidUpdateEffect } from './utils' From f8fcee780b9cefe901c98a4841b1c32c069bc3ed Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Wed, 19 Jun 2024 00:51:00 +0300 Subject: [PATCH 02/19] feat: Add recipes, usages & guides for every item in the game to builtin JEI! --- README.MD | 1 + pnpm-lock.yaml | 8 +- scripts/getMissingRecipes.mjs | 41 ++ src/inventoryWindows.ts | 154 +++- src/itemsDescriptions.ts | 1290 +++++++++++++++++++++++++++++++++ 5 files changed, 1473 insertions(+), 21 deletions(-) create mode 100644 scripts/getMissingRecipes.mjs create mode 100644 src/itemsDescriptions.ts diff --git a/README.MD b/README.MD index 689250ce5..2cde2edd2 100644 --- a/README.MD +++ b/README.MD @@ -17,6 +17,7 @@ You can try this out at [mcraft.fun](https://mcraft.fun/), [pcm.gg](https://pcm. - Play with friends over internet! (P2P is powered by Peer.js discovery servers) - First-class touch (mobile) & controller support - Resource pack support +- Builtin JEI with recipes & guides for every item (also replaces creative inventory) - even even more! All components that are in [Storybook](https://mcraft.fun/storybook) are published as npm module and can be used in other projects: [`minecraft-react`](https://npmjs.com/minecraft-react) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3e1ab099a..952406e68 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -305,7 +305,7 @@ importers: version: 1.0.0 minecraft-inventory-gui: specifier: github:zardoy/minecraft-inventory-gui#next - version: https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/b424a566723067d0fb1a4bd70c1fb58a922f2ba4(@types/react@18.2.20)(react@18.2.0) + version: https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/5311971e783cc75e7759ea641bd1a15155d9c5cd(@types/react@18.2.20)(react@18.2.0) mineflayer: specifier: github:zardoy/mineflayer version: https://codeload.github.com/zardoy/mineflayer/tar.gz/a4b1b4ba7f8c972cee9c0a16eb1191ff4d21fe23(encoding@0.1.13) @@ -6062,8 +6062,8 @@ packages: minecraft-folder-path@1.2.0: resolution: {integrity: sha512-qaUSbKWoOsH9brn0JQuBhxNAzTDMwrOXorwuRxdJKKKDYvZhtml+6GVCUrY5HRiEsieBEjCUnhVpDuQiKsiFaw==} - minecraft-inventory-gui@https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/b424a566723067d0fb1a4bd70c1fb58a922f2ba4: - resolution: {tarball: https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/b424a566723067d0fb1a4bd70c1fb58a922f2ba4} + minecraft-inventory-gui@https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/5311971e783cc75e7759ea641bd1a15155d9c5cd: + resolution: {tarball: https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/5311971e783cc75e7759ea641bd1a15155d9c5cd} version: 1.0.1 minecraft-protocol@https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/495eed56ab230b2615596590064671356d86a2dc: @@ -15731,7 +15731,7 @@ snapshots: minecraft-folder-path@1.2.0: {} - minecraft-inventory-gui@https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/b424a566723067d0fb1a4bd70c1fb58a922f2ba4(@types/react@18.2.20)(react@18.2.0): + minecraft-inventory-gui@https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/5311971e783cc75e7759ea641bd1a15155d9c5cd(@types/react@18.2.20)(react@18.2.0): dependencies: valtio: 1.11.2(@types/react@18.2.20)(react@18.2.0) transitivePeerDependencies: diff --git a/scripts/getMissingRecipes.mjs b/scripts/getMissingRecipes.mjs new file mode 100644 index 000000000..59e786729 --- /dev/null +++ b/scripts/getMissingRecipes.mjs @@ -0,0 +1,41 @@ +//@ts-check +// tsx ./scripts/getMissingRecipes.mjs +import MinecraftData from 'minecraft-data' +import supportedVersions from '../src/supportedVersions.mjs' +import fs from 'fs' + +console.time('import-data') +const { descriptionGenerators } = await import('../src/itemsDescriptions') +console.timeEnd('import-data') + +const data = MinecraftData(supportedVersions.at(-1)) + +const hasDescription = name => { + for (const [key, value] of descriptionGenerators) { + if (Array.isArray(key) && key.includes(name)) { + return true + } + if (key instanceof RegExp && key.test(name)) { + return true + } + } + return false +} + +const result = [] +for (const item of data.itemsArray) { + const recipes = data.recipes[item.id] + if (!recipes) { + if (item.name.endsWith('_slab') || item.name.endsWith('_stairs') || item.name.endsWith('_wall')) { + console.warn('Must have recipe!', item.name) + continue + } + if (hasDescription(item.name)) { + continue + } + + result.push(item.name) + } +} + +fs.writeFileSync('./generated/noRecipies.json', JSON.stringify(result, null, 2)) diff --git a/src/inventoryWindows.ts b/src/inventoryWindows.ts index 772760d62..a8bd5cff0 100644 --- a/src/inventoryWindows.ts +++ b/src/inventoryWindows.ts @@ -28,12 +28,13 @@ import nbt from 'prismarine-nbt' import { splitEvery, equals } from 'rambda' import PItem, { Item } from 'prismarine-item' import Generic95 from '../assets/generic_95.png' -import { activeModalStack, hideCurrentModal, miscUiState, showModal } from './globalState' +import { activeModalStack, hideCurrentModal, hideModal, miscUiState, showModal } from './globalState' import invspriteJson from './invsprite.json' import { options } from './optionsStorage' import { assertDefined, inGameError } from './utils' import { MessageFormatPart } from './botUtils' import { currentScaling } from './scaleInterface' +import { descriptionGenerators, getItemDescription } from './itemsDescriptions' export const itemsAtlases: ItemsAtlasesOutputJson = _itemsAtlases const loadedImagesCache = new Map() @@ -119,7 +120,9 @@ export const onGameLoad = (onLoad) => { bot.on('windowClose', () => { // todo hide up to the window itself! - hideCurrentModal() + if (lastWindow) { + hideCurrentModal() + } }) bot.on('respawn', () => { // todo validate logic against native client (maybe login) if (lastWindow) { @@ -417,6 +420,30 @@ const upJei = (search: string) => { export const openItemsCanvas = (type, _bot = bot as typeof bot | null) => { const inv = showInventory(type, getImage, {}, _bot) + inv.canvasManager.children[0].callbacks.getItemRecipes = (item) => { + const allRecipes = getAllItemRecipes(item.name) + inv.canvasManager.children[0].messageDisplay = '' + const itemDescription = getItemDescription(item) + if (!allRecipes?.length && !itemDescription) { + inv.canvasManager.children[0].messageDisplay = `No recipes found for ${item.displayName}` + } + return [...allRecipes ?? [], ...itemDescription ? [ + [ + 'GenericDescription', + mapSlots([item])[0], + [], + itemDescription + ] + ] : []] + } + inv.canvasManager.children[0].callbacks.getItemUsages = (item) => { + const allItemUsages = getAllItemUsages(item.name) + inv.canvasManager.children[0].messageDisplay = '' + if (!allItemUsages?.length) { + inv.canvasManager.children[0].messageDisplay = `No usages found for ${item.displayName}` + } + return allItemUsages + } return inv } @@ -440,6 +467,7 @@ const openWindow = (type: string | undefined) => { if (type !== undefined && bot.currentWindow && !skipClosePacketSending) bot.currentWindow['close']() lastWindow.destroy() lastWindow = null as any + window.lastWindow = lastWindow miscUiState.displaySearchInput = false destroyFn() skipClosePacketSending = false @@ -452,8 +480,13 @@ const openWindow = (type: string | undefined) => { inv.canvas.style.position = 'fixed' inv.canvas.style.inset = '0' - inv.canvasManager.onClose = () => { - hideCurrentModal() + inv.canvasManager.onClose = async () => { + await new Promise(resolve => { + setTimeout(resolve, 0) + }) + if (activeModalStack.at(-1)?.reactType?.includes('player_win:')) { + hideModal(undefined, undefined, { force: true }) + } inv.canvasManager.destroy() } @@ -464,6 +497,18 @@ const openWindow = (type: string | undefined) => { upWindowItems() lastWindow.pwindow.touch = miscUiState.currentTouch + const oldOnInventoryEvent = lastWindow.pwindow.onInventoryEvent.bind(lastWindow.pwindow) + lastWindow.pwindow.onInventoryEvent = (type, containing, windowIndex, inventoryIndex, item) => { + if (inv.canvasManager.children[0].currentGuide) { + const isRightClick = type === 'rightclick' + const isLeftClick = type === 'leftclick' + if (isLeftClick || isRightClick) { + inv.canvasManager.children[0].showRecipesOrUsages(isLeftClick, item) + } + } else { + oldOnInventoryEvent(type, containing, windowIndex, inventoryIndex, item) + } + } lastWindow.pwindow.onJeiClick = (slotItem, _index, isRightclick) => { // slotItem is the slot from mapSlots const itemId = loadedData.itemsByName[slotItem.name]?.id @@ -472,21 +517,25 @@ const openWindow = (type: string | undefined) => { return } const item = new PrismarineItem(itemId, isRightclick ? 64 : 1, slotItem.metadata) - const freeSlot = bot.inventory.firstEmptyInventorySlot() - if (freeSlot === null) return - void bot.creative.setInventorySlot(freeSlot, item) + if (bot.game.gameMode === 'creative') { + const freeSlot = bot.inventory.firstEmptyInventorySlot() + if (freeSlot === null) return + void bot.creative.setInventorySlot(freeSlot, item) + } else { + inv.canvasManager.children[0].showRecipesOrUsages(!isRightclick, mapSlots([item])[0]) + } } - if (bot.game.gameMode === 'creative') { - lastWindow.pwindow.win.jeiSlotsPage = 0 - // todo workaround so inventory opens immediately (but still lags) - setTimeout(() => { - upJei('') - }) - miscUiState.displaySearchInput = true - } else { - lastWindow.pwindow.win.jeiSlots = [] - } + // if (bot.game.gameMode !== 'spectator') { + lastWindow.pwindow.win.jeiSlotsPage = 0 + // todo workaround so inventory opens immediately (though it still lags) + setTimeout(() => { + upJei('') + }) + miscUiState.displaySearchInput = true + // } else { + // lastWindow.pwindow.win.jeiSlots = [] + // } if (type === undefined) { // player inventory @@ -553,3 +602,74 @@ const getResultingRecipe = (slots: Array, gridRows: number) => { const item = new PrismarineItem(id, count, metadata) return item } + +const getAllItemRecipes = (itemName: string) => { + const item = loadedData.itemsByName[itemName] + if (!item) return + const itemId = item.id + const recipes = loadedData.recipes[itemId] + if (!recipes) return + const results = [] as Array<{ + result: Item, + ingredients: Item[], + description?: string + }> + + // get recipes here + for (const recipe of recipes) { + const { result } = recipe + if (!result) continue + const resultId = typeof result === 'number' ? result : Array.isArray(result) ? result[0]! : result.id + const resultCount = (typeof result === 'number' ? undefined : Array.isArray(result) ? result[1] : result.count) ?? 1 + const resultMetadata = typeof result === 'object' && !Array.isArray(result) ? result.metadata : undefined + const resultItem = new PrismarineItem(resultId!, resultCount, resultMetadata) + if ('inShape' in recipe) { + const ingredients = recipe.inShape + if (!ingredients) continue + // eslint-disable-next-line @typescript-eslint/no-loop-func + const ingredientsItems = ingredients.flatMap(items => items.map(item => new PrismarineItem((item ?? 0) as number, 1))) + results.push({ result: resultItem, ingredients: ingredientsItems }) + } + if ('ingredients' in recipe) { + const { ingredients } = recipe + if (!ingredients) continue + // eslint-disable-next-line @typescript-eslint/no-loop-func + const ingredientsItems = ingredients.map(item => new PrismarineItem((item ?? 0) as number, 1)) + results.push({ result: resultItem, ingredients: ingredientsItems, description: 'Shapeless' }) + } + } + return results.map(({ result, ingredients, description }) => { + return [ + 'CraftingTableGuide', + mapSlots([result])[0], + mapSlots(ingredients), + description + ] + }) +} + +const getAllItemUsages = (itemName: string) => { + const item = loadedData.itemsByName[itemName] + if (!item) return + const foundRecipeIds = [] as string[] + + for (const [id, recipes] of Object.entries(loadedData.recipes)) { + for (const recipe of recipes) { + if ('inShape' in recipe) { + if (recipe.inShape.some(row => row.includes(item.id))) { + foundRecipeIds.push(id) + } + } + if ('ingredients' in recipe) { + if (recipe.ingredients.includes(item.id)) { + foundRecipeIds.push(id) + } + } + } + } + + return foundRecipeIds.flatMap(id => { + // todo should use exact match, not include all recipes! + return getAllItemRecipes(loadedData.items[id].name) + }) +} diff --git a/src/itemsDescriptions.ts b/src/itemsDescriptions.ts new file mode 100644 index 000000000..662d7331a --- /dev/null +++ b/src/itemsDescriptions.ts @@ -0,0 +1,1290 @@ + +export const descriptionGenerators = new Map string)>() +descriptionGenerators.set(/_slab$/, name => 'Craft it by placing 3 blocks of the material in a row in a crafting table.') +descriptionGenerators.set(/_stairs$/, name => 'Craft it by placing 6 blocks of the material in a stair shape in a crafting table.') +descriptionGenerators.set(/_log$/, name => 'You can get it by chopping down a tree. To chop down a tree, hold down the left mouse button until the tree breaks.') +descriptionGenerators.set(/_leaves$/, name => 'You can get it by breaking the leaves of a tree with a tool that has the Silk Touch enchantment or by using shears.') +descriptionGenerators.set(['mangrove_roots'], name => 'You can get it by breaking the roots of a mangrove tree.') +descriptionGenerators.set(['mud'], 'Mud is a block found abundantly in mangrove swamps or created by using a water bottle on a dirt block. It can be used for crafting or converted into clay using pointed dripstone.') +descriptionGenerators.set(['clay'], 'Clay is a block found underwater or created by using a water bottle on a mud block. It can be used for crafting or converted into terracotta using a furnace.') +descriptionGenerators.set(['terracotta'], 'Terracotta is a block created by smelting clay in a furnace. It can be used for crafting or decoration.') +descriptionGenerators.set(['stone'], 'Stone is a block found underground.') +descriptionGenerators.set(['dirt'], 'Dirt is a block found on the surface.') +descriptionGenerators.set(['sand'], 'Sand is a block found on the surface near water.') +descriptionGenerators.set(['gravel'], 'Gravel is a block found on the surface and sometimes underground.') +descriptionGenerators.set(['sandstone'], 'Sandstone is a block found in deserts.') +descriptionGenerators.set(['red_sandstone'], 'Red sandstone is a block found in mesas.') +descriptionGenerators.set(['granite', 'diorite', 'andesite'], name => `${name.charAt(0).toUpperCase() + name.slice(1)} is a block found underground.`) +descriptionGenerators.set(['netherrack', 'soul_sand', 'soul_soil', 'glowstone'], name => `${name.charAt(0).toUpperCase() + name.slice(1)} is a block found in the Nether.`) +descriptionGenerators.set(['end_stone'], 'End stone is a block found in the End.') +descriptionGenerators.set(['obsidian'], 'Obsidian is a block created by pouring water on lava.') +descriptionGenerators.set(['glass'], 'Glass is a block created by smelting sand in a furnace.') +descriptionGenerators.set(['bedrock'], 'Bedrock is an indestructible block found at the bottom of the world in the Overworld and at the top of the world in the Nether.') +descriptionGenerators.set(['water', 'lava'], name => `${name.charAt(0).toUpperCase() + name.slice(1)} is a fluid found in the Overworld.`) +descriptionGenerators.set(/_sapling$/, name => `${name} drops from the leaves of a tree when it decays or is broken. It can be planted on dirt to grow a new tree.`) +descriptionGenerators.set(/^stripped_/, name => `${name} is created by using an axe on the block.`) +descriptionGenerators.set(['sponge'], 'Sponge is a block found in ocean monuments.') +descriptionGenerators.set(/^music_disc_/, name => `Music discs are rare items that can be found in dungeons or by trading with villagers. Also dropped by creepers when killed by a skeleton.`) +descriptionGenerators.set(/^enchanted_book$/, 'Enchanted books are rare items that can be found in dungeons or by trading with villagers.') +descriptionGenerators.set(/_spawn_egg$/, name => `${name} is an item that can be used to spawn a mob in Creative mode. Cannot be obtained in Survival mode.`) +descriptionGenerators.set(/_pottery_sherd$/, name => `${name} can be obtained only by brushing suspicious blocks, with the variants of sherd obtainable being dependent on the structure.`) +descriptionGenerators.set(['cracked_deepslate_bricks'], `Deepslate Bricks and Cracked Deepslate Bricks generate naturally in ancient cities.`) + +const moreGeneratedBlocks = { + 'natural_blocks': { + 'air': { + 'obtained_from': 'Naturally occurs in the world.' + }, + 'deepslate': { + 'obtained_from': 'Mined with a pickaxe in layers -64 to 16.', + 'rarity': 'Common' + }, + 'cobbled_deepslate': { + 'obtained_from': 'Mined from deepslate with any pickaxe.' + }, + 'calcite': { + 'obtained_from': 'Mined with a pickaxe, found in geodes.' + }, + 'tuff': { + 'obtained_from': 'Mined with a pickaxe in layers -64 to 16.', + 'rarity': 'Common' + }, + 'chiseled_tuff': { + 'obtained_from': 'Crafted from tuff.' + }, + 'polished_tuff': { + 'obtained_from': 'Crafted from tuff.' + }, + 'tuff_bricks': { + 'obtained_from': 'Crafted from tuff.' + }, + 'chiseled_tuff_bricks': { + 'obtained_from': 'Crafted from tuff bricks.' + }, + 'grass_block': { + 'obtained_from': 'Mined with a tool enchanted with Silk Touch.' + }, + 'podzol': { + 'obtained_from': 'Mined with a tool enchanted with Silk Touch, found in giant tree taiga biomes.' + }, + 'rooted_dirt': { + 'obtained_from': 'Mined with a shovel, found under azalea trees.' + }, + 'crimson_nylium': { + 'obtained_from': 'Mined with a pickaxe, found in the Nether.' + }, + 'warped_nylium': { + 'obtained_from': 'Mined with a pickaxe, found in the Nether.' + }, + 'cobblestone': { + 'obtained_from': 'Mined from stone, or from breaking stone structures.' + }, + 'mangrove_propagule': { + 'obtained_from': 'Harvested from mangrove trees.' + }, + 'suspicious_sand': { + 'obtained_from': 'Found in deserts and beaches.' + }, + 'suspicious_gravel': { + 'obtained_from': 'Found underwater.' + }, + 'red_sand': { + 'obtained_from': 'Mined from red sand in badlands biomes.' + }, + 'coal_ore': { + 'obtained_from': 'Mined with a pickaxe in layers 0 to 128.', + 'rarity': 'Common' + }, + 'deepslate_coal_ore': { + 'obtained_from': 'Mined with a pickaxe in layers -64 to 0.', + 'rarity': 'Rare' + }, + 'iron_ore': { + 'obtained_from': 'Mined with a pickaxe in layers 0 to 63.', + 'rarity': 'Common' + }, + 'deepslate_iron_ore': { + 'obtained_from': 'Mined with a pickaxe in layers -64 to 0.', + 'rarity': 'Uncommon' + }, + 'copper_ore': { + 'obtained_from': 'Mined with a pickaxe in layers 0 to 96.', + 'rarity': 'Common' + }, + 'deepslate_copper_ore': { + 'obtained_from': 'Mined with a pickaxe in layers -16 to 64.', + 'rarity': 'Uncommon' + }, + 'gold_ore': { + 'obtained_from': 'Mined with a pickaxe in layers -64 to 32.', + 'rarity': 'Uncommon' + }, + 'deepslate_gold_ore': { + 'obtained_from': 'Mined with a pickaxe in layers -64 to 0.', + 'rarity': 'Rare' + }, + 'redstone_ore': { + 'obtained_from': 'Mined with an iron pickaxe or higher in layers -64 to 16.', + 'rarity': 'Uncommon' + }, + 'deepslate_redstone_ore': { + 'obtained_from': 'Mined with an iron pickaxe or higher in layers -64 to 0.', + 'rarity': 'Uncommon' + }, + 'emerald_ore': { + 'obtained_from': 'Mined with an iron pickaxe or higher in mountain biomes, layers -16 to 256.', + 'rarity': 'Rare' + }, + 'deepslate_emerald_ore': { + 'obtained_from': 'Mined with an iron pickaxe or higher in mountain biomes, layers -64 to 0.', + 'rarity': 'Very Rare' + }, + 'lapis_ore': { + 'obtained_from': 'Mined with a stone pickaxe or higher in layers -64 to 32.', + 'rarity': 'Uncommon' + }, + 'deepslate_lapis_ore': { + 'obtained_from': 'Mined with a stone pickaxe or higher in layers -64 to 0.', + 'rarity': 'Rare' + }, + 'diamond_ore': { + 'obtained_from': 'Mined with an iron pickaxe or higher in layers -64 to 16.', + 'rarity': 'Rare' + }, + 'deepslate_diamond_ore': { + 'obtained_from': 'Mined with an iron pickaxe or higher in layers -64 to 0.', + 'rarity': 'Very Rare' + }, + 'nether_gold_ore': { + 'obtained_from': 'Mined with any pickaxe in the Nether.' + }, + 'nether_quartz_ore': { + 'obtained_from': 'Mined with any pickaxe in the Nether.' + }, + 'ancient_debris': { + 'obtained_from': 'Mined with a diamond or netherite pickaxe in the Nether, layers 8 to 22.', + 'rarity': 'Very Rare' + }, + 'budding_amethyst': { + 'obtained_from': 'Found in amethyst geodes, cannot be obtained as an item.' + }, + 'exposed_copper': { + 'obtained_from': 'Exposed copper block obtained through mining.' + }, + 'weathered_copper': { + 'obtained_from': 'Weathered copper block obtained through mining.' + }, + 'oxidized_copper': { + 'obtained_from': 'Oxidized copper block obtained through mining.' + }, + 'chiseled_copper': { + 'obtained_from': 'Crafted from copper blocks.' + }, + 'exposed_chiseled_copper': { + 'obtained_from': 'Exposed chiseled copper block obtained through mining.' + }, + 'weathered_chiseled_copper': { + 'obtained_from': 'Weathered chiseled copper block obtained through mining.' + }, + 'oxidized_chiseled_copper': { + 'obtained_from': 'Oxidized chiseled copper block obtained through mining.' + }, + 'waxed_chiseled_copper': { + 'obtained_from': 'Crafted from copper blocks, waxed to prevent oxidation.' + }, + 'waxed_exposed_chiseled_copper': { + 'obtained_from': 'Waxed exposed chiseled copper block obtained through mining.' + }, + 'waxed_weathered_chiseled_copper': { + 'obtained_from': 'Waxed weathered chiseled copper block obtained through mining.' + }, + 'waxed_oxidized_chiseled_copper': { + 'obtained_from': 'Waxed oxidized chiseled copper block obtained through mining.' + }, + 'crimson_stem': { + 'obtained_from': 'Mined from crimson trees in the Nether.' + }, + 'warped_stem': { + 'obtained_from': 'Mined from warped trees in the Nether.' + }, + 'stripped_crimson_stem': { + 'obtained_from': 'Stripped from crimson stem with an axe.' + }, + 'stripped_warped_stem': { + 'obtained_from': 'Stripped from warped stem with an axe.' + }, + 'stripped_bamboo_block': { + 'obtained_from': 'Crafted from bamboo.' + }, + 'sponge': { + 'obtained_from': 'Found in ocean monuments.' + }, + 'wet_sponge': { + 'obtained_from': 'Absorbs water, can be dried in a furnace.' + }, + 'cobweb': { + 'obtained_from': 'Mined with a sword or shears, found in mineshafts.' + }, + 'short_grass': { + 'obtained_from': 'Sheared from grass.' + }, + 'fern': { + 'obtained_from': 'Sheared from ferns in forest biomes.' + }, + 'azalea': { + 'obtained_from': 'Found in lush caves.' + }, + 'flowering_azalea': { + 'obtained_from': 'Found in lush caves.' + }, + 'dead_bush': { + 'obtained_from': 'Mined with shears in desert biomes.' + }, + 'seagrass': { + 'obtained_from': 'Sheared from underwater grass.' + }, + 'sea_pickle': { + 'obtained_from': 'Mined with shears from coral reefs.' + }, + 'dandelion': { + 'type': 'natural', + 'description': 'Dandelions are common flowers that spawn in plains, forests, and meadows.', + 'spawn_range': 'Surface' + }, + 'poppy': { + 'type': 'natural', + 'description': 'Poppies are common flowers that generate in plains, forests, and meadows.', + 'spawn_range': 'Surface' + }, + 'blue_orchid': { + 'type': 'natural', + 'description': 'Blue orchids spawn naturally in swamp biomes.', + 'spawn_range': 'Surface' + }, + 'allium': { + 'type': 'natural', + 'description': 'Alliums are flowers that generate in flower forest biomes.', + 'spawn_range': 'Surface' + }, + 'azure_bluet': { + 'type': 'natural', + 'description': 'Azure bluets are common flowers that spawn in plains and flower forest biomes.', + 'spawn_range': 'Surface' + }, + 'red_tulip': { + 'type': 'natural', + 'description': 'Red tulips are flowers found in flower forests and plains.', + 'spawn_range': 'Surface' + }, + 'orange_tulip': { + 'type': 'natural', + 'description': 'Orange tulips are flowers found in flower forests and plains.', + 'spawn_range': 'Surface' + }, + 'white_tulip': { + 'type': 'natural', + 'description': 'White tulips are flowers found in flower forests and plains.', + 'spawn_range': 'Surface' + }, + 'pink_tulip': { + 'type': 'natural', + 'description': 'Pink tulips are flowers found in flower forests and plains.', + 'spawn_range': 'Surface' + }, + 'oxeye_daisy': { + 'type': 'natural', + 'description': 'Oxeye daisies are common flowers that generate in plains and flower forest biomes.', + 'spawn_range': 'Surface' + }, + 'cornflower': { + 'type': 'natural', + 'description': 'Cornflowers spawn in plains, flower forests, and meadows.', + 'spawn_range': 'Surface' + }, + 'lily_of_the_valley': { + 'type': 'natural', + 'description': 'Lily of the valleys generate in flower forest biomes.', + 'spawn_range': 'Surface' + }, + 'wither_rose': { + 'type': 'dropped', + 'description': 'Wither roses are dropped when a mob is killed by the Wither boss.', + 'spawn_range': 'N/A' + }, + 'torchflower': { + 'type': 'crafted', + 'description': 'Torchflowers can be grown using torchflower seeds, which are found in archeology loot or by trading.', + 'spawn_range': 'N/A' + }, + 'pitcher_plant': { + 'type': 'crafted', + 'description': 'Pitcher plants can be grown using pitcher pods, which are found in archeology loot or by trading.', + 'spawn_range': 'N/A' + }, + 'spore_blossom': { + 'type': 'natural', + 'description': 'Spore blossoms generate naturally on the ceilings of lush caves.', + 'spawn_range': 'Underground' + }, + 'brown_mushroom': { + 'type': 'natural', + 'description': 'Brown mushrooms are found in dark areas, swamps, mushroom fields, and forests.', + 'spawn_range': 'Surface' + }, + 'red_mushroom': { + 'type': 'natural', + 'description': 'Red mushrooms are found in dark areas, swamps, mushroom fields, and forests.', + 'spawn_range': 'Surface' + }, + 'crimson_fungus': { + 'type': 'natural', + 'description': 'Crimson fungi generate naturally in crimson forests in the Nether.', + 'spawn_range': 'Nether' + }, + 'warped_fungus': { + 'type': 'natural', + 'description': 'Warped fungi generate naturally in warped forests in the Nether.', + 'spawn_range': 'Nether' + }, + 'crimson_roots': { + 'type': 'natural', + 'description': 'Crimson roots generate naturally in crimson forests in the Nether.', + 'spawn_range': 'Nether' + }, + 'warped_roots': { + 'type': 'natural', + 'description': 'Warped roots generate naturally in warped forests in the Nether.', + 'spawn_range': 'Nether' + }, + 'nether_sprouts': { + 'type': 'natural', + 'description': 'Nether sprouts generate naturally in warped forests in the Nether.', + 'spawn_range': 'Nether' + }, + 'weeping_vines': { + 'type': 'natural', + 'description': 'Weeping vines generate naturally in crimson forests in the Nether and grow downward from netherrack.', + 'spawn_range': 'Nether' + }, + 'twisting_vines': { + 'type': 'natural', + 'description': 'Twisting vines generate naturally in warped forests in the Nether and grow upward from the ground.', + 'spawn_range': 'Nether' + }, + 'sugar_cane': { + 'type': 'natural', + 'description': 'Sugar cane is found near water in most biomes.', + 'spawn_range': 'Surface' + }, + 'kelp': { + 'type': 'natural', + 'description': 'Kelp generates underwater in most ocean biomes.', + 'spawn_range': 'Water' + }, + 'pink_petals': { + 'type': 'natural', + 'description': 'Pink petals generate naturally in cherry grove biomes.', + 'spawn_range': 'Surface' + }, + 'moss_block': { + 'type': 'natural', + 'description': 'Moss blocks generate in lush caves and can also be obtained through trading or by using bone meal on moss carpets.', + 'spawn_range': 'Underground' + }, + 'hanging_roots': { + 'type': 'natural', + 'description': 'Hanging roots generate naturally in lush caves.', + 'spawn_range': 'Underground' + }, + 'big_dripleaf': { + 'type': 'natural', + 'description': 'Big dripleaf plants generate in lush caves and can also be obtained through trading.', + 'spawn_range': 'Underground' + }, + 'small_dripleaf': { + 'type': 'natural', + 'description': 'Small dripleaf plants generate in lush caves and can also be obtained through trading.', + 'spawn_range': 'Underground' + }, + 'bamboo': { + 'type': 'natural', + 'description': 'Bamboo generates in jungle biomes, especially bamboo jungles.', + 'spawn_range': 'Surface' + }, + 'smooth_quartz': { + 'type': 'crafted', + 'description': 'Smooth quartz is obtained by smelting blocks of quartz.', + 'spawn_range': 'N/A' + }, + 'smooth_red_sandstone': { + 'type': 'crafted', + 'description': 'Smooth red sandstone is obtained by smelting red sandstone.', + 'spawn_range': 'N/A' + }, + 'smooth_sandstone': { + 'type': 'crafted', + 'description': 'Smooth sandstone is obtained by smelting sandstone.', + 'spawn_range': 'N/A' + }, + 'smooth_stone': { + 'type': 'crafted', + 'description': 'Smooth stone is obtained by smelting regular stone.', + 'spawn_range': 'N/A' + }, + 'chorus_plant': { + 'type': 'natural', + 'description': 'Chorus plants generate naturally in the End and can be grown from chorus flowers.', + 'spawn_range': 'End' + }, + 'chorus_flower': { + 'type': 'natural', + 'description': 'Chorus flowers generate naturally in the End on top of chorus plants.', + 'spawn_range': 'End' + }, + 'spawner': { + 'type': 'natural', + 'description': 'Spawners generate in dungeons, mineshafts, and other structures.', + 'spawn_range': 'Underground' + }, + 'farmland': { + 'type': 'crafted', + 'description': 'Farmland is created by using a hoe on dirt or grass blocks.', + 'spawn_range': 'N/A' + }, + 'ice': { + 'type': 'natural', + 'description': 'Ice generates in snowy and icy biomes and can also be obtained by breaking ice blocks with a Silk Touch tool.', + 'spawn_range': 'Surface' + }, + 'cactus': { + 'type': 'natural', + 'description': 'Cacti generate naturally in desert biomes.', + 'spawn_range': 'Surface' + }, + 'pumpkin': { + 'type': 'natural', + 'description': 'Pumpkins generate naturally in most grassy biomes and can also be grown from pumpkin seeds.', + 'spawn_range': 'Surface' + }, + 'carved_pumpkin': { + 'type': 'crafted', + 'description': 'Carved pumpkins are obtained by using shears on a pumpkin.', + 'spawn_range': 'N/A' + }, + 'basalt': { + 'type': 'natural', + 'description': 'Basalt generates in the Nether in basalt deltas and can also be created by lava flowing over soul soil next to blue ice.', + 'spawn_range': 'Nether' + }, + 'smooth_basalt': { + 'type': 'natural', + 'description': 'Smooth basalt is found around amethyst geodes or can be obtained by smelting basalt.', + 'spawn_range': 'Underground' + }, + 'infested_stone': { + 'type': 'natural', + 'description': 'Infested stone blocks contain silverfish and generate in strongholds, underground.', + 'spawn_range': 'Underground' + }, + 'infested_cobblestone': { + 'type': 'natural', + 'description': 'Infested cobblestone blocks contain silverfish and generate in strongholds, underground.', + 'spawn_range': 'Underground' + }, + 'infested_stone_bricks': { + 'type': 'natural', + 'description': 'Infested stone bricks contain silverfish and generate in strongholds, underground.', + 'spawn_range': 'Underground' + }, + 'infested_mossy_stone_bricks': { + 'type': 'natural', + 'description': 'Infested mossy stone bricks contain silverfish and generate in strongholds, underground.', + 'spawn_range': 'Underground' + }, + 'infested_cracked_stone_bricks': { + 'type': 'natural', + 'description': 'Infested cracked stone bricks contain silverfish and generate in strongholds, underground.', + 'spawn_range': 'Underground' + }, + 'infested_chiseled_stone_bricks': { + 'type': 'natural', + 'description': 'Infested chiseled stone bricks contain silverfish and generate in strongholds, underground.', + 'spawn_range': 'Underground' + }, + 'infested_deepslate': { + 'type': 'natural', + 'description': 'Infested deepslate contains silverfish and generates in the deepslate layer underground.', + 'spawn_range': 'Underground' + }, + 'cracked_stone_bricks': { + 'type': 'crafted', + 'description': 'Cracked stone bricks are obtained by smelting stone bricks.', + 'spawn_range': 'N/A' + }, + 'cracked_deepslate_bricks': { + 'type': 'crafted', + 'description': 'Cracked deepslate bricks are obtained by smelting deepslate bricks.', + 'spawn_range': 'N/A' + }, + 'cracked_deepslate_tiles': { + 'type': 'crafted', + 'description': 'Cracked deepslate tiles are obtained by smelting deepslate tiles.', + 'spawn_range': 'N/A' + }, + 'reinforced_deepslate': { + 'type': 'crafted', + 'description': 'Reinforced deepslate is a strong block that cannot be obtained in survival mode.', + 'spawn_range': 'N/A' + }, + 'brown_mushroom_block': { + 'type': 'natural', + 'description': 'Brown mushroom blocks generate as part of huge mushrooms in dark forest biomes and mushroom fields.', + 'spawn_range': 'Surface' + }, + 'red_mushroom_block': { + 'type': 'natural', + 'description': 'Red mushroom blocks generate as part of huge mushrooms in dark forest biomes and mushroom fields.', + 'spawn_range': 'Surface' + }, + 'mushroom_stem': { + 'type': 'natural', + 'description': 'Mushroom stems generate as part of huge mushrooms in dark forest biomes and mushroom fields.', + 'spawn_range': 'Surface' + }, + 'vine': { + 'type': 'natural', + 'description': 'Vines generate naturally on trees and walls in jungle biomes, swamps, and lush caves.', + 'spawn_range': 'Surface' + }, + 'glow_lichen': { + 'type': 'natural', + 'description': 'Glow lichen generates naturally in caves and can spread to other blocks using bone meal.', + 'spawn_range': 'Underground' + }, + 'mycelium': { + 'type': 'natural', + 'description': 'Mycelium generates naturally in mushroom field biomes and spreads to dirt blocks.', + 'spawn_range': 'Surface' + }, + 'lily_pad': { + 'type': 'natural', + 'description': 'Lily pads generate naturally on the surface of water in swamps.', + 'spawn_range': 'Water' + }, + 'cracked_nether_bricks': { + 'type': 'crafted', + 'description': 'Cracked nether bricks are obtained by smelting nether bricks.', + 'spawn_range': 'N/A' + }, + 'sculk': { + 'type': 'natural', + 'description': 'Sculk generates naturally in the deep dark biome and spreads using a sculk catalyst.', + 'spawn_range': 'Underground' + }, + 'sculk_vein': { + 'type': 'natural', + 'description': 'Sculk veins generate naturally in the deep dark biome and spread using a sculk catalyst.', + 'spawn_range': 'Underground' + }, + 'sculk_catalyst': { + 'type': 'natural', + 'description': 'Sculk catalysts generate naturally in the deep dark biome and spread sculk blocks when mobs die nearby.', + 'spawn_range': 'Underground' + }, + 'sculk_shrieker': { + 'type': 'natural', + 'description': 'Sculk shriekers generate naturally in the deep dark biome and emit a loud shriek when activated.', + 'spawn_range': 'Underground' + }, + 'end_portal_frame': { + 'type': 'natural', + 'description': 'End portal frames generate naturally in strongholds, forming the structure of end portals.', + 'spawn_range': 'Underground' + }, + 'dragon_egg': { + 'type': 'dropped', + 'description': 'The dragon egg is dropped when the Ender Dragon is defeated for the first time.', + 'spawn_range': 'End' + }, + 'command_block': { + 'type': 'crafted', + 'description': 'Command blocks are powerful blocks used in commands and redstone, obtainable only via commands.', + 'spawn_range': 'N/A' + }, + 'chipped_anvil': { + 'type': 'crafted', + 'description': 'Chipped anvils are damaged versions of anvils and are used for repairing and enchanting.', + 'spawn_range': 'N/A' + }, + 'damaged_anvil': { + 'type': 'crafted', + 'description': 'Damaged anvils are further damaged versions of anvils and are used for repairing and enchanting.', + 'spawn_range': 'N/A' + }, + 'barrier': { + 'type': 'crafted', + 'description': 'Barriers are invisible blocks used in map-making and obtainable only via commands.', + 'spawn_range': 'N/A' + }, + 'light': { + 'type': 'crafted', + 'description': 'Light blocks are invisible blocks that emit light, obtainable only via commands.', + 'spawn_range': 'N/A' + }, + 'dirt_path': { + 'type': 'crafted', + 'description': 'Dirt paths are created by using a shovel on grass blocks and are commonly found in villages.', + 'spawn_range': 'Surface' + }, + 'sunflower': { + 'type': 'natural', + 'description': 'Sunflowers generate naturally in sunflower plains biomes.', + 'spawn_range': 'Surface' + }, + 'lilac': { + 'type': 'natural', + 'description': 'Lilacs generate naturally in forest biomes.', + 'spawn_range': 'Surface' + }, + 'rose_bush': { + 'type': 'natural', + 'description': 'Rose bushes generate naturally in forest biomes.', + 'spawn_range': 'Surface' + }, + 'peony': { + 'type': 'natural', + 'description': 'Peonies generate naturally in forest biomes.', + 'spawn_range': 'Surface' + }, + 'tall_grass': { + 'type': 'natural', + 'description': 'Tall grass generates naturally in various biomes and can be grown using bone meal.', + 'spawn_range': 'Surface' + }, + 'large_fern': { + 'type': 'natural', + 'description': 'Large ferns generate naturally in taiga biomes.', + 'spawn_range': 'Surface' + }, + 'repeating_command_block': { + 'type': 'crafted', + 'description': 'Repeating command blocks execute commands every tick and are obtainable only via commands.', + 'spawn_range': 'N/A' + }, + 'chain_command_block': { + 'type': 'crafted', + 'description': 'Chain command blocks execute commands when triggered and are obtainable only via commands.', + 'spawn_range': 'N/A' + }, + 'warped_wart_block': { + 'type': 'natural', + 'description': 'Warped wart blocks generate naturally in warped forests in the Nether.', + 'spawn_range': 'Nether' + }, + 'structure_void': { + 'type': 'crafted', + 'description': 'Structure voids are used in structure blocks to exclude certain blocks from being saved and are obtainable only via commands.', + 'spawn_range': 'N/A' + }, + 'white_shulker_box': { + 'type': 'crafted', + 'description': 'White shulker boxes are crafted from shulker shells and dye, and they function as portable storage.', + 'spawn_range': 'N/A' + }, + 'orange_shulker_box': { + 'type': 'crafted', + 'description': 'Orange shulker boxes are crafted from shulker shells and dye, and they function as portable storage.', + 'spawn_range': 'N/A' + }, + 'magenta_shulker_box': { + 'type': 'crafted', + 'description': 'Magenta shulker boxes are crafted from shulker shells and dye, and they function as portable storage.', + 'spawn_range': 'N/A' + }, + 'light_blue_shulker_box': { + 'type': 'crafted', + 'description': 'Light blue shulker boxes are crafted from shulker shells and dye, and they function as portable storage.', + 'spawn_range': 'N/A' + }, + 'yellow_shulker_box': { + 'type': 'crafted', + 'description': 'Yellow shulker boxes are crafted from shulker shells and dye, and they function as portable storage.', + 'spawn_range': 'N/A' + }, + 'lime_shulker_box': { + 'type': 'crafted', + 'description': 'Lime shulker boxes are crafted from shulker shells and dye, and they function as portable storage.', + 'spawn_range': 'N/A' + }, + 'pink_shulker_box': { + 'type': 'crafted', + 'description': 'Pink shulker boxes are crafted from shulker shells and dye, and they function as portable storage.', + 'spawn_range': 'N/A' + }, + 'gray_shulker_box': { + 'type': 'crafted', + 'description': 'Gray shulker boxes are crafted from shulker shells and dye, and they function as portable storage.', + 'spawn_range': 'N/A' + }, + 'light_gray_shulker_box': { + 'type': 'crafted', + 'description': 'Light gray shulker boxes are crafted from shulker shells and dye, and they function as portable storage.', + 'spawn_range': 'N/A' + }, + 'cyan_shulker_box': { + 'type': 'crafted', + 'description': 'Cyan shulker boxes are crafted from shulker shells and dye, and they function as portable storage.', + 'spawn_range': 'N/A' + }, + 'purple_shulker_box': { + 'type': 'crafted', + 'description': 'Purple shulker boxes are crafted from shulker shells and dye, and they function as portable storage.', + 'spawn_range': 'N/A' + }, + 'blue_shulker_box': { + 'type': 'crafted', + 'description': 'Blue shulker boxes are crafted from shulker shells and dye, and they function as portable storage.', + 'spawn_range': 'N/A' + }, + 'brown_shulker_box': { + 'type': 'crafted', + 'description': 'Brown shulker boxes are crafted from shulker shells and dye, and they function as portable storage.', + 'spawn_range': 'N/A' + }, + 'green_shulker_box': { + 'type': 'crafted', + 'description': 'Green shulker boxes are crafted from shulker shells and dye, and they function as portable storage.', + 'spawn_range': 'N/A' + }, + 'red_shulker_box': { + 'type': 'crafted', + 'description': 'Red shulker boxes are crafted from shulker shells and dye, and they function as portable storage.', + 'spawn_range': 'N/A' + }, + 'black_shulker_box': { + 'type': 'crafted', + 'description': 'Black shulker boxes are crafted from shulker shells and dye, and they function as portable storage.', + 'spawn_range': 'N/A' + }, + 'white_glazed_terracotta': { + 'type': 'crafted', + 'description': 'White glazed terracotta is obtained by smelting white terracotta and features decorative patterns.', + 'spawn_range': 'N/A' + }, + 'orange_glazed_terracotta': { + 'type': 'crafted', + 'description': 'Orange glazed terracotta is obtained by smelting orange terracotta and features decorative patterns.', + 'spawn_range': 'N/A' + }, + 'magenta_glazed_terracotta': { + 'type': 'crafted', + 'description': 'Magenta glazed terracotta is obtained by smelting magenta terracotta and features decorative patterns.', + 'spawn_range': 'N/A' + }, + 'light_blue_glazed_terracotta': { + 'type': 'crafted', + 'description': 'Light blue glazed terracotta is obtained by smelting light blue terracotta and features decorative patterns.', + 'spawn_range': 'N/A' + }, + 'yellow_glazed_terracotta': { + 'type': 'crafted', + 'description': 'Yellow glazed terracotta is obtained by smelting yellow terracotta and features decorative patterns.', + 'spawn_range': 'N/A' + }, + 'lime_glazed_terracotta': { + 'type': 'crafted', + 'description': 'Lime glazed terracotta is obtained by smelting lime terracotta and features decorative patterns.', + 'spawn_range': 'N/A' + }, + 'pink_glazed_terracotta': { + 'type': 'crafted', + 'description': 'Pink glazed terracotta is obtained by smelting pink terracotta and features decorative patterns.', + 'spawn_range': 'N/A' + }, + 'gray_glazed_terracotta': { + 'type': 'crafted', + 'description': 'Gray glazed terracotta is obtained by smelting gray terracotta and features decorative patterns.', + 'spawn_range': 'N/A' + }, + 'light_gray_glazed_terracotta': { + 'type': 'crafted', + 'description': 'Light gray glazed terracotta is obtained by smelting light gray terracotta and features decorative patterns.', + 'spawn_range': 'N/A' + }, + 'cyan_glazed_terracotta': { + 'type': 'crafted', + 'description': 'Cyan glazed terracotta is obtained by smelting cyan terracotta and features decorative patterns.', + 'spawn_range': 'N/A' + }, + 'purple_glazed_terracotta': { + 'type': 'crafted', + 'description': 'Purple glazed terracotta is obtained by smelting purple terracotta and features decorative patterns.', + 'spawn_range': 'N/A' + }, + 'blue_glazed_terracotta': { + 'type': 'crafted', + 'description': 'Blue glazed terracotta is obtained by smelting blue terracotta and features decorative patterns.', + 'spawn_range': 'N/A' + }, + 'brown_glazed_terracotta': { + 'type': 'crafted', + 'description': 'Brown glazed terracotta is obtained by smelting brown terracotta and features decorative patterns.', + 'spawn_range': 'N/A' + }, + 'green_glazed_terracotta': { + 'type': 'crafted', + 'description': 'Green glazed terracotta is obtained by smelting green terracotta and features decorative patterns.', + 'spawn_range': 'N/A' + }, + 'red_glazed_terracotta': { + 'type': 'crafted', + 'description': 'Red glazed terracotta is obtained by smelting red terracotta and features decorative patterns.', + 'spawn_range': 'N/A' + }, + 'black_glazed_terracotta': { + 'type': 'crafted', + 'description': 'Black glazed terracotta is obtained by smelting black terracotta and features decorative patterns.', + 'spawn_range': 'N/A' + }, + 'white_concrete': { + 'type': 'crafted', + 'description': 'White concrete is crafted from concrete powder and hardens when in contact with water.', + 'spawn_range': 'N/A' + }, + 'orange_concrete': { + 'type': 'crafted', + 'description': 'Orange concrete is crafted from concrete powder and hardens when in contact with water.', + 'spawn_range': 'N/A' + }, + 'magenta_concrete': { + 'type': 'crafted', + 'description': 'Magenta concrete is crafted from concrete powder and hardens when in contact with water.', + 'spawn_range': 'N/A' + }, + 'light_blue_concrete': { + 'type': 'crafted', + 'description': 'Light blue concrete is crafted from concrete powder and hardens when in contact with water.', + 'spawn_range': 'N/A' + }, + 'yellow_concrete': { + 'type': 'crafted', + 'description': 'Yellow concrete is crafted from concrete powder and hardens when in contact with water.', + 'spawn_range': 'N/A' + }, + 'lime_concrete': { + 'type': 'crafted', + 'description': 'Lime concrete is crafted from concrete powder and hardens when in contact with water.', + 'spawn_range': 'N/A' + }, + 'pink_concrete': { + 'type': 'crafted', + 'description': 'Pink concrete is crafted from concrete powder and hardens when in contact with water.', + 'spawn_range': 'N/A' + }, + 'gray_concrete': { + 'type': 'crafted', + 'description': 'Gray concrete is crafted from concrete powder and hardens when in contact with water.', + 'spawn_range': 'N/A' + }, + 'light_gray_concrete': { + 'type': 'crafted', + 'description': 'Light gray concrete is crafted from concrete powder and hardens when in contact with water.', + 'spawn_range': 'N/A' + }, + 'cyan_concrete': { + 'type': 'crafted', + 'description': 'Cyan concrete is crafted from concrete powder and hardens when in contact with water.', + 'spawn_range': 'N/A' + }, + 'purple_concrete': { + 'type': 'crafted', + 'description': 'Purple concrete is crafted from concrete powder and hardens when in contact with water.', + 'spawn_range': 'N/A' + }, + 'blue_concrete': { + 'type': 'crafted', + 'description': 'Blue concrete is crafted from concrete powder and hardens when in contact with water.', + 'spawn_range': 'N/A' + }, + 'brown_concrete': { + 'type': 'crafted', + 'description': 'Brown concrete is crafted from concrete powder and hardens when in contact with water.', + 'spawn_range': 'N/A' + }, + 'green_concrete': { + 'type': 'crafted', + 'description': 'Green concrete is crafted from concrete powder and hardens when in contact with water.', + 'spawn_range': 'N/A' + }, + 'red_concrete': { + 'type': 'crafted', + 'description': 'Red concrete is crafted from concrete powder and hardens when in contact with water.', + 'spawn_range': 'N/A' + }, + 'black_concrete': { + 'type': 'crafted', + 'description': 'Black concrete is crafted from concrete powder and hardens when in contact with water.', + 'spawn_range': 'N/A' + }, + 'white_concrete_powder': { + 'type': 'crafted', + 'description': 'White concrete powder is crafted from sand, gravel, and dye, and hardens into concrete when in contact with water.', + 'spawn_range': 'N/A' + }, + 'orange_concrete_powder': { + 'type': 'crafted', + 'description': 'Orange concrete powder is crafted from sand, gravel, and dye, and hardens into concrete when in contact with water.', + 'spawn_range': 'N/A' + }, + 'magenta_concrete_powder': { + 'type': 'crafted', + 'description': 'Magenta concrete powder is crafted from sand, gravel, and dye, and hardens into concrete when in contact with water.', + 'spawn_range': 'N/A' + }, + 'light_blue_concrete_powder': { + 'type': 'crafted', + 'description': 'Light blue concrete powder is crafted from sand, gravel, and dye, and hardens into concrete when in contact with water.', + 'spawn_range': 'N/A' + }, + 'yellow_concrete_powder': { + 'type': 'crafted', + 'description': 'Yellow concrete powder is crafted from sand, gravel, and dye, and hardens into concrete when in contact with water.', + 'spawn_range': 'N/A' + }, + 'lime_concrete_powder': { + 'type': 'crafted', + 'description': 'Lime concrete powder is crafted from sand, gravel, and dye, and hardens into concrete when in contact with water.', + 'spawn_range': 'N/A' + }, + 'pink_concrete_powder': { + 'type': 'crafted', + 'description': 'Pink concrete powder is crafted from sand, gravel, and dye, and hardens into concrete when in contact with water.', + 'spawn_range': 'N/A' + }, + 'gray_concrete_powder': { + 'type': 'crafted', + 'description': 'Gray concrete powder is crafted from sand, gravel, and dye, and hardens into concrete when in contact with water.', + 'spawn_range': 'N/A' + }, + 'light_gray_concrete_powder': { + 'type': 'crafted', + 'description': 'Light gray concrete powder is crafted from sand, gravel, and dye, and hardens into concrete when in contact with water.', + 'spawn_range': 'N/A' + }, + 'cyan_concrete_powder': { + 'type': 'crafted', + 'description': 'Cyan concrete powder is crafted from sand, gravel, and dye, and hardens into concrete when in contact with water.', + 'spawn_range': 'N/A' + }, + 'purple_concrete_powder': { + 'type': 'crafted', + 'description': 'Purple concrete powder is crafted from sand, gravel, and dye, and hardens into concrete when in contact with water.', + 'spawn_range': 'N/A' + }, + 'blue_concrete_powder': { + 'type': 'crafted', + 'description': 'Blue concrete powder is crafted from sand, gravel, and dye, and hardens into concrete when in contact with water.', + 'spawn_range': 'N/A' + }, + 'brown_concrete_powder': { + 'type': 'crafted', + 'description': 'Brown concrete powder is crafted from sand, gravel, and dye, and hardens into concrete when in contact with water.', + 'spawn_range': 'N/A' + }, + 'green_concrete_powder': { + 'type': 'crafted', + 'description': 'Green concrete powder is crafted from sand, gravel, and dye, and hardens into concrete when in contact with water.', + 'spawn_range': 'N/A' + }, + 'red_concrete_powder': { + 'type': 'crafted', + 'description': 'Red concrete powder is crafted from sand, gravel, and dye, and hardens into concrete when in contact with water.', + 'spawn_range': 'N/A' + }, + 'black_concrete_powder': { + 'type': 'crafted', + 'description': 'Black concrete powder is crafted from sand, gravel, and dye, and hardens into concrete when in contact with water.', + 'spawn_range': 'N/A' + }, + 'cyan_candle': { + 'type': 'crafted', + 'description': 'Cyan candles are crafted from string and dye and can be placed on blocks to emit light.', + 'spawn_range': 'N/A' + }, + 'pink_candle': { + 'type': 'crafted', + 'description': 'Pink candles are crafted from string and dye and can be placed on blocks to emit light.', + 'spawn_range': 'N/A' + }, + 'purple_candle': { + 'type': 'crafted', + 'description': 'Purple candles are crafted from string and dye and can be placed on blocks to emit light.', + 'spawn_range': 'N/A' + }, + 'blue_candle': { + 'type': 'crafted', + 'description': 'Blue candles are crafted from string and dye and can be placed on blocks to emit light.', + 'spawn_range': 'N/A' + }, + 'brown_candle': { + 'type': 'crafted', + 'description': 'Brown candles are crafted from string and dye and can be placed on blocks to emit light.', + 'spawn_range': 'N/A' + }, + 'green_candle': { + 'type': 'crafted', + 'description': 'Green candles are crafted from string and dye and can be placed on blocks to emit light.', + 'spawn_range': 'N/A' + }, + 'red_candle': { + 'type': 'crafted', + 'description': 'Red candles are crafted from string and dye and can be placed on blocks to emit light.', + 'spawn_range': 'N/A' + }, + 'black_candle': { + 'type': 'crafted', + 'description': 'Black candles are crafted from string and dye and can be placed on blocks to emit light.', + 'spawn_range': 'N/A' + }, + 'turtle_egg': 'can be obtained via turtle breeding on beaches, where turtles lay eggs that can be collected.', + 'sniffer_egg': 'can be found in buried treasure or ancient ruins, used to hatch sniffers.', + 'dead_tube_coral_block': 'can be obtained by mining tube coral blocks with a pickaxe without Silk Touch or when exposed to air.', + 'dead_brain_coral_block': 'can be obtained by mining brain coral blocks with a pickaxe without Silk Touch or when exposed to air.', + 'dead_bubble_coral_block': 'can be obtained by mining bubble coral blocks with a pickaxe without Silk Touch or when exposed to air.', + 'dead_fire_coral_block': 'can be obtained by mining fire coral blocks with a pickaxe without Silk Touch or when exposed to air.', + 'dead_horn_coral_block': 'can be obtained by mining horn coral blocks with a pickaxe without Silk Touch or when exposed to air.', + 'tube_coral_block': 'can be obtained by mining with a pickaxe enchanted with Silk Touch, found in warm ocean biomes.', + 'brain_coral_block': 'can be obtained by mining with a pickaxe enchanted with Silk Touch, found in warm ocean biomes.', + 'bubble_coral_block': 'can be obtained by mining with a pickaxe enchanted with Silk Touch, found in warm ocean biomes.', + 'fire_coral_block': 'can be obtained by mining with a pickaxe enchanted with Silk Touch, found in warm ocean biomes.', + 'horn_coral_block': 'can be obtained by mining with a pickaxe enchanted with Silk Touch, found in warm ocean biomes.', + 'tube_coral': 'can be obtained by mining with a pickaxe enchanted with Silk Touch, found in warm ocean biomes.', + 'brain_coral': 'can be obtained by mining with a pickaxe enchanted with Silk Touch, found in warm ocean biomes.', + 'bubble_coral': 'can be obtained by mining with a pickaxe enchanted with Silk Touch, found in warm ocean biomes.', + 'fire_coral': 'can be obtained by mining with a pickaxe enchanted with Silk Touch, found in warm ocean biomes.', + 'horn_coral': 'can be obtained by mining with a pickaxe enchanted with Silk Touch, found in warm ocean biomes.', + 'dead_brain_coral': 'can be obtained by mining brain coral without Silk Touch or when exposed to air.', + 'dead_bubble_coral': 'can be obtained by mining bubble coral without Silk Touch or when exposed to air.', + 'dead_fire_coral': 'can be obtained by mining fire coral without Silk Touch or when exposed to air.', + 'dead_horn_coral': 'can be obtained by mining horn coral without Silk Touch or when exposed to air.', + 'dead_tube_coral': 'can be obtained by mining tube coral without Silk Touch or when exposed to air.', + 'tube_coral_fan': 'can be obtained by mining with a pickaxe enchanted with Silk Touch, found in warm ocean biomes.', + 'brain_coral_fan': 'can be obtained by mining with a pickaxe enchanted with Silk Touch, found in warm ocean biomes.', + 'bubble_coral_fan': 'can be obtained by mining with a pickaxe enchanted with Silk Touch, found in warm ocean biomes.', + 'fire_coral_fan': 'can be obtained by mining with a pickaxe enchanted with Silk Touch, found in warm ocean biomes.', + 'horn_coral_fan': 'can be obtained by mining with a pickaxe enchanted with Silk Touch, found in warm ocean biomes.', + 'dead_tube_coral_fan': 'can be obtained by mining tube coral fans without Silk Touch or when exposed to air.', + 'dead_brain_coral_fan': 'can be obtained by mining brain coral fans without Silk Touch or when exposed to air.', + 'dead_bubble_coral_fan': 'can be obtained by mining bubble coral fans without Silk Touch or when exposed to air.', + 'dead_fire_coral_fan': 'can be obtained by mining fire coral fans without Silk Touch or when exposed to air.', + 'dead_horn_coral_fan': 'can be obtained by mining horn coral fans without Silk Touch or when exposed to air.', + 'sculk_sensor': 'can be obtained via Silk Touch enchantment on a pickaxe or found in ancient cities in the deep dark biome.', + 'copper_door': 'can be crafted using copper ingots.', + 'exposed_copper_door': 'can be obtained by allowing copper doors to oxidize or can be crafted directly.', + 'weathered_copper_door': 'can be obtained by allowing exposed copper doors to further oxidize or can be crafted directly.', + 'oxidized_copper_door': 'can be obtained by allowing weathered copper doors to fully oxidize or can be crafted directly.', + 'waxed_copper_door': 'can be crafted using copper ingots and honeycomb.', + 'waxed_exposed_copper_door': 'can be crafted using exposed copper doors and honeycomb.', + 'waxed_weathered_copper_door': 'can be crafted using weathered copper doors and honeycomb.', + 'waxed_oxidized_copper_door': 'can be crafted using oxidized copper doors and honeycomb.', + 'copper_trapdoor': 'can be crafted using copper ingots.', + 'exposed_copper_trapdoor': 'can be obtained by allowing copper trapdoors to oxidize or can be crafted directly.', + 'weathered_copper_trapdoor': 'can be obtained by allowing exposed copper trapdoors to further oxidize or can be crafted directly.', + 'oxidized_copper_trapdoor': 'can be obtained by allowing weathered copper trapdoors to fully oxidize or can be crafted directly.', + 'waxed_copper_trapdoor': 'can be crafted using copper ingots and honeycomb.', + 'waxed_exposed_copper_trapdoor': 'can be crafted using exposed copper trapdoors and honeycomb.', + 'waxed_weathered_copper_trapdoor': 'can be crafted using weathered copper trapdoors and honeycomb.', + 'waxed_oxidized_copper_trapdoor': 'can be crafted using oxidized copper trapdoors and honeycomb.', + 'saddle': 'can be obtained from fishing, dungeon chests, or trading with leatherworkers.', + 'elytra': 'can be found in end ships within end cities.', + 'structure_block': 'can be obtained using commands or in creative mode, used to save and load structures.', + 'jigsaw': 'can be obtained using commands or in creative mode, used to generate structures.', + 'scute': 'can be obtained when baby turtles grow into adults.', + 'apple': 'can be obtained by breaking oak and dark oak leaves or found in chests.', + 'charcoal': 'can be obtained by smelting logs or wood in a furnace.', + 'quartz': 'can be obtained by mining nether quartz ore in the Nether.', + 'amethyst_shard': 'can be obtained by mining amethyst clusters found in geodes with a pickaxe.', + 'netherite_scrap': 'can be obtained by smelting ancient debris found in the Nether.', + 'netherite_sword': 'can be crafted using a diamond sword and netherite ingot.', + 'netherite_shovel': 'can be crafted using a diamond shovel and netherite ingot.', + 'netherite_pickaxe': 'can be crafted using a diamond pickaxe and netherite ingot.', + 'netherite_axe': 'can be crafted using a diamond axe and netherite ingot.', + 'netherite_hoe': 'can be crafted using a diamond hoe and netherite ingot.', + 'string': 'can be obtained from killing spiders or breaking cobwebs.', + 'feather': 'can be obtained from killing chickens.', + 'gunpowder': 'can be obtained from killing creepers, ghasts, and witches.', + 'wheat_seeds': 'can be obtained by breaking tall grass or harvesting wheat crops.', + 'chainmail_helmet': 'can be obtained from chest loot, trading with villagers, or killing mobs wearing it.', + 'chainmail_chestplate': 'can be obtained from chest loot, trading with villagers, or killing mobs wearing it.', + 'chainmail_leggings': 'can be obtained from chest loot, trading with villagers, or killing mobs wearing it.', + 'chainmail_boots': 'can be obtained from chest loot, trading with villagers, or killing mobs wearing it.', + 'netherite_helmet': 'can be crafted using a diamond helmet and netherite ingot.', + 'netherite_chestplate': 'can be crafted using a diamond chestplate and netherite ingot.', + 'netherite_leggings': 'can be crafted using diamond leggings and netherite ingot.', + 'netherite_boots': 'can be crafted using diamond boots and netherite ingot.', + 'flint': 'can be obtained by breaking gravel blocks.', + 'porkchop': 'can be obtained by killing pigs.', + 'cooked_porkchop': 'can be obtained by cooking porkchop in a furnace, smoker, or campfire.', + 'enchanted_golden_apple': 'can be found in dungeon, bastion remnant, and mineshaft chests.', + 'water_bucket': 'can be obtained by using a bucket on a water source block.', + 'lava_bucket': 'can be obtained by using a bucket on a lava source block.', + 'powder_snow_bucket': 'can be obtained by using a bucket on powder snow.', + 'snowball': 'can be obtained by breaking snow blocks or using a shovel on snow.', + 'milk_bucket': 'can be obtained by using a bucket on a cow or mooshroom.', + 'pufferfish_bucket': 'can be obtained by using a bucket on a pufferfish in water.', + 'salmon_bucket': 'can be obtained by using a bucket on a salmon in water.', + 'cod_bucket': 'can be obtained by using a bucket on a cod in water.', + 'tropical_fish_bucket': 'can be obtained by using a bucket on a tropical fish in water.', + 'axolotl_bucket': 'can be obtained by using a bucket on an axolotl in water.', + 'tadpole_bucket': 'can be obtained by using a bucket on a tadpole in water.', + 'brick': 'can be obtained by smelting clay in a furnace.', + 'clay_ball': 'can be obtained by breaking clay blocks or from chest loot.', + 'egg': 'can be obtained from chickens periodically.', + 'bundle': 'can be crafted using rabbit hide and string.', + 'glowstone_dust': 'can be obtained by breaking glowstone blocks or killing witches.', + 'cod': 'can be obtained by fishing or killing cod in water.', + 'salmon': 'can be obtained by fishing or killing salmon in water.', + 'tropical_fish': 'can be obtained by fishing or killing tropical fish in water.', + 'pufferfish': 'can be obtained by fishing or killing pufferfish in water.', + 'cooked_cod': 'can be obtained by cooking cod in a furnace, smoker, or campfire.', + 'cooked_salmon': 'can be obtained by cooking salmon in a furnace, smoker, or campfire.', + 'ink_sac': 'can be obtained by killing squid or as loot from wandering traders.', + 'glow_ink_sac': 'can be obtained by killing glow squid.', + 'cocoa_beans': 'can be obtained from cocoa pods found on jungle trees.', + 'green_dye': 'can be obtained by smelting cactus in a furnace.', + 'bone': 'can be obtained by killing skeletons or from chest loot.', + 'crafter': 'can be obtained via crafting using specific materials (details vary by mod or version).', + 'filled_map': 'can be obtained by using an empty map item.', + 'melon_slice': 'can be obtained by breaking melon blocks.', + 'beef': 'can be obtained by killing cows.', + 'cooked_beef': 'can be obtained by cooking beef in a furnace, smoker, or campfire.', + 'chicken': 'can be obtained by killing chickens.', + 'cooked_chicken': 'can be obtained by cooking chicken in a furnace, smoker, or campfire.', + 'rotten_flesh': 'can be obtained by killing zombies or drowned.', + 'ender_pearl': 'can be obtained by killing endermen.', + 'blaze_rod': 'can be obtained by killing blazes in the Nether.', + 'ghast_tear': 'can be obtained by killing ghasts in the Nether.', + 'nether_wart': 'can be found in Nether fortresses and bastion remnants.', + 'potion': 'can be brewed using a brewing stand with various ingredients.', + 'spider_eye': 'can be obtained by killing spiders or witches.', + 'experience_bottle': 'can be obtained from trading with villagers or found in chest loot.', + 'written_book': 'can be crafted using a book and quill after writing in it.', + 'carrot': 'can be obtained by harvesting carrot crops or found in village farms.', + 'potato': 'can be obtained by harvesting potato crops or found in village farms.', + 'baked_potato': 'can be obtained by cooking potatoes in a furnace, smoker, or campfire.', + 'poisonous_potato': 'can be obtained by harvesting potato crops (rare chance).', + 'skeleton_skull': 'can be obtained by killing skeletons with a charged creeper explosion.', + 'wither_skeleton_skull': 'can be obtained by killing wither skeletons (rare drop).', + 'player_head': 'can be obtained via commands or by killing players in certain conditions (e.g., with a charged creeper).', + 'zombie_head': 'can be obtained by killing zombies with a charged creeper explosion.', + 'creeper_head': 'can be obtained by killing creepers with a charged creeper explosion.', + 'dragon_head': 'can be found at the end of end ships in end cities.', + 'piglin_head': 'can be obtained by killing piglins with a charged creeper explosion.', + 'nether_star': 'can be obtained by defeating the Wither boss.', + 'firework_star': 'can be crafted using gunpowder and dye.', + 'nether_brick': 'can be obtained by smelting netherrack in a furnace or found in Nether fortresses.', + 'prismarine_shard': 'can be obtained by killing guardians and elder guardians.', + 'prismarine_crystals': 'can be obtained by killing guardians and elder guardians or breaking sea lanterns.', + 'rabbit': 'can be obtained by killing rabbits.', + 'cooked_rabbit': 'can be obtained by cooking rabbit in a furnace, smoker, or campfire.', + 'rabbit_foot': 'can be obtained by killing rabbits (rare drop).', + 'rabbit_hide': 'can be obtained by killing rabbits.', + 'iron_horse_armor': 'can be found in dungeon, temple, and stronghold chests.', + 'golden_horse_armor': 'can be found in dungeon, temple, and stronghold chests.', + 'diamond_horse_armor': 'can be found in dungeon, temple, and stronghold chests.', + 'name_tag': 'can be obtained by fishing, dungeon chests, or trading with librarians.', + 'command_block_minecart': 'can be obtained using commands in creative mode.', + 'mutton': 'can be obtained by killing sheep.', + 'cooked_mutton': 'can be obtained by cooking mutton in a furnace, smoker, or campfire.', + 'chorus_fruit': 'can be obtained by breaking chorus plants found in the End.', + 'popped_chorus_fruit': 'can be obtained by smelting chorus fruit in a furnace.', + 'torchflower_seeds': 'can be obtained from torchflower plants, used for breeding and decoration.', + 'pitcher_pod': 'can be obtained from pitcher plants, used for breeding and decoration.', + 'beetroot': 'can be obtained by harvesting beetroot crops or found in village farms.', + 'beetroot_seeds': 'can be obtained by harvesting beetroot crops or from chests.', + 'dragon_breath': 'can be obtained by using an empty bottle on the ender dragon\'s breath attack.', + 'splash_potion': 'can be brewed using a brewing stand and gunpowder with various potions.', + 'tipped_arrow': 'can be crafted using arrows and lingering potions.', + 'lingering_potion': 'can be brewed using a brewing stand and dragon\'s breath with various potions.', + 'totem_of_undying': 'can be obtained by killing evokers in woodland mansions and during raids.', + 'shulker_shell': 'can be obtained by killing shulkers in end cities.', + 'knowledge_book': 'can be obtained using commands or given in custom advancements.', + 'debug_stick': 'can be obtained using commands in creative mode.', + 'disc_fragment_5': 'can be found in ancient city chests, used to craft music disc 5.', + 'trident': 'can be obtained by killing drowned (rare drop).', + 'phantom_membrane': 'can be obtained by killing phantoms.', + 'nautilus_shell': 'can be obtained from fishing, drowned, or wandering traders.', + 'heart_of_the_sea': 'can be found in buried treasure chests.', + 'suspicious_stew': 'can be crafted using mushrooms and various flowers or found in chests.', + 'globe_banner_pattern': 'can be obtained from trading with cartographer villagers.', + 'piglin_banner_pattern': 'can be obtained from bastion remnant chests.', + 'goat_horn': 'can be obtained when a goat rams a solid block.', + 'bell': 'can be obtained from village structures or crafted using iron ingots and wood.', + 'sweet_berries': 'can be obtained from sweet berry bushes found in taiga biomes.', + 'glow_berries': 'can be found in lush cave biomes or by trading with wandering traders.', + 'shroomlight': 'can be obtained by breaking shroomlights found in Nether forests.', + 'honeycomb': 'can be obtained by using shears on beehives or bee nests.', + 'bee_nest': 'can be found in forest biomes with birch or oak trees, especially in flower forests.', + 'crying_obsidian': 'can be found in ruined portals, bastion remnants, or bartered from piglins.', + 'blackstone': 'can be found in basalt deltas, bastion remnants, or crafted from polished blackstone.', + 'gilded_blackstone': 'can be found in bastion remnants.', + 'cracked_polished_blackstone_bricks': 'can be obtained by smelting polished blackstone bricks.', + 'small_amethyst_bud': 'can be found growing in amethyst geodes.', + 'medium_amethyst_bud': 'can be found growing in amethyst geodes.', + 'large_amethyst_bud': 'can be found growing in amethyst geodes.', + 'amethyst_cluster': 'can be found growing in amethyst geodes.', + 'pointed_dripstone': 'can be found in dripstone caves or created by placing a dripstone block under a water source block.', + 'ochre_froglight': 'can be obtained by leading a frog to eat a magma cube, dropping this item.', + 'verdant_froglight': 'can be obtained by leading a frog to eat a magma cube, dropping this item.', + 'pearlescent_froglight': 'can be obtained by leading a frog to eat a magma cube, dropping this item.', + 'frogspawn': 'Frogspawn is an item that can be found in the game Minecraft and is primarily used to breed frogs.', + 'echo_shard': 'Echo Shard is an item in Minecraft Dungeons, primarily used as a currency for trading with Piglin vendors.', + 'copper_grate': 'Copper Grate is a block in Minecraft that can be crafted from copper ingots, primarily used as a decorative block.', + 'exposed_copper_grate': 'Exposed Copper Grate is a variant of Copper Grate in Minecraft that has weathered to the exposed state over time.', + 'weathered_copper_grate': 'Weathered Copper Grate is a variant of Copper Grate in Minecraft that has weathered to the weathered state over time.', + 'oxidized_copper_grate': 'Oxidized Copper Grate is a variant of Copper Grate in Minecraft that has weathered to the oxidized state over time.', + 'waxed_copper_grate': 'Waxed Copper Grate is a variant of Copper Grate in Minecraft that has been waxed to prevent further weathering.', + 'waxed_exposed_copper_grate': 'Waxed Exposed Copper Grate is a variant of Exposed Copper Grate in Minecraft that has been waxed to prevent further weathering.', + 'waxed_weathered_copper_grate': 'Waxed Weathered Copper Grate is a variant of Weathered Copper Grate in Minecraft that has been waxed to prevent further weathering.', + 'waxed_oxidized_copper_grate': 'Waxed Oxidized Copper Grate is a variant of Oxidized Copper Grate in Minecraft that has been waxed to prevent further weathering.', + 'copper_bulb': 'Copper Bulb is a block in Minecraft that can be crafted from copper ingots, primarily used as a decorative block.', + 'exposed_copper_bulb': 'Exposed Copper Bulb is a variant of Copper Bulb in Minecraft that has weathered to the exposed state over time.', + 'weathered_copper_bulb': 'Weathered Copper Bulb is a variant of Copper Bulb in Minecraft that has weathered to the weathered state over time.', + 'oxidized_copper_bulb': 'Oxidized Copper Bulb is a variant of Copper Bulb in Minecraft that has weathered to the oxidized state over time.', + 'waxed_copper_bulb': 'Waxed Copper Bulb is a variant of Copper Bulb in Minecraft that has been waxed to prevent further weathering.', + 'waxed_exposed_copper_bulb': 'Waxed Exposed Copper Bulb is a variant of Exposed Copper Bulb in Minecraft that has been waxed to prevent further weathering.', + 'waxed_weathered_copper_bulb': 'Waxed Weathered Copper Bulb is a variant of Weathered Copper Bulb in Minecraft that has been waxed to prevent further weathering.', + 'waxed_oxidized_copper_bulb': 'Waxed Oxidized Copper Bulb is a variant of Oxidized Copper Bulb in Minecraft that has been waxed to prevent further weathering.', + 'trial_spawner': 'Trial Spawner is an item in Minecraft Dungeons, used in the Ancient Hunt game mode to summon trials for unique rewards.', + 'trial_key': 'Trial Key is an item in Minecraft Dungeons, obtained from defeating Ancient mobs in the Ancient Hunt game mode, used to unlock trials.' + } +} + +const lowerCaseFirstLetter = (string) => string.charAt(0).toLowerCase() + string.slice(1) +for (const [name, data] of Object.entries(moreGeneratedBlocks.natural_blocks)) { + let description = '' as string | ((name: string) => string) + if (typeof data === 'object') { + const obtainedFrom = 'obtained_from' in data ? data.obtained_from : 'description' in data ? data.description : '' + description = obtainedFrom + ('rarity' in data ? ` Rarity: ${data.rarity}` : '') + ('spawn_range' in data ? ` Spawn range: ${data.spawn_range}` : '') + } else { + description = (name) => `${lowerCaseFirstLetter(name)}: ${data}` + } + descriptionGenerators.set([name], description) +} + +export const getItemDescription = (item: import('prismarine-item').Item) => { + const { name } = item + let result: string | ((name: string) => string) = '' + for (const [names, description] of descriptionGenerators) { + if (Array.isArray(names) && names.includes(name)) { + result = description + } + if (typeof names === 'string' && names === name) { + result = description + } + if (names instanceof RegExp && names.test(name)) { + result = description + } + } + return typeof result === 'function' ? result(item.displayName) : result +} From 9958eb11b2ce7ba6e7b83044a140b04af3c90bc8 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Thu, 20 Jun 2024 05:41:58 +0300 Subject: [PATCH 03/19] minor inventory improvements --- pnpm-lock.yaml | 8 ++++---- src/inventoryWindows.ts | 11 ++++++----- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 952406e68..67b725825 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -305,7 +305,7 @@ importers: version: 1.0.0 minecraft-inventory-gui: specifier: github:zardoy/minecraft-inventory-gui#next - version: https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/5311971e783cc75e7759ea641bd1a15155d9c5cd(@types/react@18.2.20)(react@18.2.0) + version: https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/e5a0fe3670e5e1d7c6024e9024ccba9a4a88e232(@types/react@18.2.20)(react@18.2.0) mineflayer: specifier: github:zardoy/mineflayer version: https://codeload.github.com/zardoy/mineflayer/tar.gz/a4b1b4ba7f8c972cee9c0a16eb1191ff4d21fe23(encoding@0.1.13) @@ -6062,8 +6062,8 @@ packages: minecraft-folder-path@1.2.0: resolution: {integrity: sha512-qaUSbKWoOsH9brn0JQuBhxNAzTDMwrOXorwuRxdJKKKDYvZhtml+6GVCUrY5HRiEsieBEjCUnhVpDuQiKsiFaw==} - minecraft-inventory-gui@https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/5311971e783cc75e7759ea641bd1a15155d9c5cd: - resolution: {tarball: https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/5311971e783cc75e7759ea641bd1a15155d9c5cd} + minecraft-inventory-gui@https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/e5a0fe3670e5e1d7c6024e9024ccba9a4a88e232: + resolution: {tarball: https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/e5a0fe3670e5e1d7c6024e9024ccba9a4a88e232} version: 1.0.1 minecraft-protocol@https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/495eed56ab230b2615596590064671356d86a2dc: @@ -15731,7 +15731,7 @@ snapshots: minecraft-folder-path@1.2.0: {} - minecraft-inventory-gui@https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/5311971e783cc75e7759ea641bd1a15155d9c5cd(@types/react@18.2.20)(react@18.2.0): + minecraft-inventory-gui@https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/e5a0fe3670e5e1d7c6024e9024ccba9a4a88e232(@types/react@18.2.20)(react@18.2.0): dependencies: valtio: 1.11.2(@types/react@18.2.20)(react@18.2.0) transitivePeerDependencies: diff --git a/src/inventoryWindows.ts b/src/inventoryWindows.ts index a8bd5cff0..bd7543d05 100644 --- a/src/inventoryWindows.ts +++ b/src/inventoryWindows.ts @@ -603,6 +603,8 @@ const getResultingRecipe = (slots: Array, gridRows: number) => { return item } +const ingredientToItem = (recipeItem) => recipeItem === null ? null : new PrismarineItem(recipeItem, 1) + const getAllItemRecipes = (itemName: string) => { const item = loadedData.itemsByName[itemName] if (!item) return @@ -611,7 +613,7 @@ const getAllItemRecipes = (itemName: string) => { if (!recipes) return const results = [] as Array<{ result: Item, - ingredients: Item[], + ingredients: Array, description?: string }> @@ -626,15 +628,14 @@ const getAllItemRecipes = (itemName: string) => { if ('inShape' in recipe) { const ingredients = recipe.inShape if (!ingredients) continue - // eslint-disable-next-line @typescript-eslint/no-loop-func - const ingredientsItems = ingredients.flatMap(items => items.map(item => new PrismarineItem((item ?? 0) as number, 1))) + + const ingredientsItems = ingredients.flatMap(items => items.map(item => ingredientToItem(item))) results.push({ result: resultItem, ingredients: ingredientsItems }) } if ('ingredients' in recipe) { const { ingredients } = recipe if (!ingredients) continue - // eslint-disable-next-line @typescript-eslint/no-loop-func - const ingredientsItems = ingredients.map(item => new PrismarineItem((item ?? 0) as number, 1)) + const ingredientsItems = ingredients.map(item => ingredientToItem(item)) results.push({ result: resultItem, ingredients: ingredientsItems, description: 'Shapeless' }) } } From 8e7639a6583b6f345c9e9a171d940b41249b0e9b Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Fri, 21 Jun 2024 22:15:35 +0300 Subject: [PATCH 04/19] fix: display error message when modal to display not found fixes #119 fix: --- src/inventoryWindows.ts | 8 +++--- src/react/DeathScreenProvider.tsx | 6 ++--- src/react/NoModalFoundProvider.tsx | 40 ++++++++++++++++++++++++++++++ src/react/OptionsRenderApp.tsx | 2 ++ src/react/Screen.tsx | 5 ++-- src/react/TouchAreasControls.tsx | 4 +-- src/react/utilsApp.ts | 15 +++++++++++ src/reactUi.tsx | 2 ++ 8 files changed, 71 insertions(+), 11 deletions(-) create mode 100644 src/react/NoModalFoundProvider.tsx diff --git a/src/inventoryWindows.ts b/src/inventoryWindows.ts index bd7543d05..1f89f9a4f 100644 --- a/src/inventoryWindows.ts +++ b/src/inventoryWindows.ts @@ -419,8 +419,8 @@ const upJei = (search: string) => { } export const openItemsCanvas = (type, _bot = bot as typeof bot | null) => { - const inv = showInventory(type, getImage, {}, _bot) - inv.canvasManager.children[0].callbacks.getItemRecipes = (item) => { + const inv = showInventory(type, getImage, {}, _bot); + (inv.canvasManager.children[0].callbacks as any).getItemRecipes = (item) => { const allRecipes = getAllItemRecipes(item.name) inv.canvasManager.children[0].messageDisplay = '' const itemDescription = getItemDescription(item) @@ -436,7 +436,7 @@ export const openItemsCanvas = (type, _bot = bot as typeof bot | null) => { ] ] : []] } - inv.canvasManager.children[0].callbacks.getItemUsages = (item) => { + (inv.canvasManager.children[0].callbacks as any).getItemUsages = (item) => { const allItemUsages = getAllItemUsages(item.name) inv.canvasManager.children[0].messageDisplay = '' if (!allItemUsages?.length) { @@ -496,7 +496,7 @@ const openWindow = (type: string | undefined) => { } upWindowItems() - lastWindow.pwindow.touch = miscUiState.currentTouch + lastWindow.pwindow.touch = miscUiState.currentTouch ?? false const oldOnInventoryEvent = lastWindow.pwindow.onInventoryEvent.bind(lastWindow.pwindow) lastWindow.pwindow.onInventoryEvent = (type, containing, windowIndex, inventoryIndex, item) => { if (inv.canvasManager.children[0].currentGuide) { diff --git a/src/react/DeathScreenProvider.tsx b/src/react/DeathScreenProvider.tsx index 4e68dbec5..8d5ab1c87 100644 --- a/src/react/DeathScreenProvider.tsx +++ b/src/react/DeathScreenProvider.tsx @@ -41,11 +41,11 @@ export default () => { useEffect(() => { if (dieReasonProxy.value) { - showModal({ reactType: 'death-screen' }) - } else { + if (!isModalActive) showModal({ reactType: 'death-screen' }) + } else if (isModalActive) { hideModal({ reactType: 'death-screen' }) } - }, [dieReasonMessage]) + }, [dieReasonMessage, isModalActive]) if (!isModalActive || !dieReasonMessage || options.autoRespawn) return null diff --git a/src/react/NoModalFoundProvider.tsx b/src/react/NoModalFoundProvider.tsx new file mode 100644 index 000000000..5fa4dda4d --- /dev/null +++ b/src/react/NoModalFoundProvider.tsx @@ -0,0 +1,40 @@ +import { proxy, subscribe, useSnapshot } from 'valtio' +import { activeModalStack, hideCurrentModal } from '../globalState' +import Screen from './Screen' +import Button from './Button' +import { hardcodedKnownModals, watchedModalsFromHooks } from './utilsApp' + +const componentActive = proxy({ + enabled: false +}) + +subscribe(activeModalStack, () => { + const last = activeModalStack.at(-1) + let withWildCardModal = false + for (const modal of watchedModalsFromHooks) { + if (modal.endsWith('*') && last?.reactType.startsWith(modal.slice(0, -1))) { + withWildCardModal = true + break + } + } + + componentActive.enabled = !!last && !hardcodedKnownModals.some(x => last.reactType.startsWith(x)) && !watchedModalsFromHooks.has(last.reactType) && !withWildCardModal +}) + +export default () => { + const { enabled } = useSnapshot(componentActive) + const lastModal = useSnapshot(activeModalStack).at(-1)?.reactType + + if (!enabled) return null + return + + +} diff --git a/src/react/OptionsRenderApp.tsx b/src/react/OptionsRenderApp.tsx index 8167f820d..968d824aa 100644 --- a/src/react/OptionsRenderApp.tsx +++ b/src/react/OptionsRenderApp.tsx @@ -2,8 +2,10 @@ import { useSnapshot } from 'valtio' import { activeModalStack, hideCurrentModal } from '../globalState' import { OptionsGroupType } from '../optionsGuiScheme' import OptionsGroup from './OptionsGroup' +import { useIsModalActive } from './utilsApp' export default () => { + useIsModalActive('options-*') const { reactType } = useSnapshot(activeModalStack).at(-1) ?? {} if (!reactType?.startsWith('options-')) return const settingsGroup = reactType.slice('options-'.length) as OptionsGroupType diff --git a/src/react/Screen.tsx b/src/react/Screen.tsx index 249fb703d..a6392ee1a 100644 --- a/src/react/Screen.tsx +++ b/src/react/Screen.tsx @@ -2,13 +2,14 @@ interface Props { title: JSX.Element | string children: React.ReactNode backdrop?: boolean | 'dirt' + style?: React.CSSProperties } -export default ({ title, children, backdrop = true }: Props) => { +export default ({ title, children, backdrop = true, style }: Props) => { return ( <> {backdrop === 'dirt' ?
: backdrop ?
: null} -
+
{title}
{children} diff --git a/src/react/TouchAreasControls.tsx b/src/react/TouchAreasControls.tsx index b660fcb26..368307547 100644 --- a/src/react/TouchAreasControls.tsx +++ b/src/react/TouchAreasControls.tsx @@ -161,8 +161,8 @@ export default ({ touchActive, setupActive, buttonsPositions, closeButtonsSetup const elem = e.currentTarget as HTMLElement const size = 32 const scale = getCurrentAppScaling() - const xPerc = e.clientX / window.innerWidth * 100 - size / scale - const yPerc = e.clientY / window.innerHeight * 100 - size / scale + const xPerc = (e.clientX - size / 4 / scale) / window.innerWidth * 100 + const yPerc = (e.clientY - size / 4 / scale) / window.innerHeight * 100 elem.style.left = `${xPerc}%` elem.style.top = `${yPerc}%` newButtonPositions[name] = [xPerc, yPerc] diff --git a/src/react/utilsApp.ts b/src/react/utilsApp.ts index 3fd185159..06e8e908a 100644 --- a/src/react/utilsApp.ts +++ b/src/react/utilsApp.ts @@ -1,11 +1,26 @@ import { useSnapshot } from 'valtio' +import { useEffect, useMemo } from 'react' import { activeModalStack, miscUiState } from '../globalState' +export const watchedModalsFromHooks = new Set() +// todo should not be there +export const hardcodedKnownModals = [ + 'player_win:' +] export const useUsingTouch = () => { return useSnapshot(miscUiState).currentTouch } export const useIsModalActive = (modal: string, useIncludes = false) => { + useMemo(() => { + watchedModalsFromHooks.add(modal) + }, []) + useEffect(() => { + return () => { + watchedModalsFromHooks.delete(modal) + } + }, []) + const allStack = useSnapshot(activeModalStack) return useIncludes ? allStack.some(x => x.reactType === modal) : allStack.at(-1)?.reactType === modal } diff --git a/src/reactUi.tsx b/src/reactUi.tsx index 8a9cc32ce..26887d669 100644 --- a/src/reactUi.tsx +++ b/src/reactUi.tsx @@ -39,6 +39,7 @@ import GamepadUiCursor from './react/GamepadUiCursor' import KeybindingsScreenProvider from './react/KeybindingsScreenProvider' import HeldMapUi from './react/HeldMapUi' import BedTime from './react/BedTime' +import NoModalFoundProvider from './react/NoModalFoundProvider' const RobustPortal = ({ children, to }) => { return createPortal({children}, to) @@ -172,6 +173,7 @@ const App = () => { + {/* */} From dc8de80fd4e2567caece63e9b871f47025cc9931 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Fri, 21 Jun 2024 22:23:01 +0300 Subject: [PATCH 05/19] fix: improve new controls type: default button positions and allow to do setup from the main menu --- src/optionsStorage.ts | 12 ++++++------ src/react/TouchAreasControls.tsx | 13 +++++++------ src/react/TouchAreasControlsProvider.tsx | 18 ++++++++++++------ src/reactUi.tsx | 2 +- 4 files changed, 26 insertions(+), 19 deletions(-) diff --git a/src/optionsStorage.ts b/src/optionsStorage.ts index 2a13abf40..2842c9087 100644 --- a/src/optionsStorage.ts +++ b/src/optionsStorage.ts @@ -82,19 +82,19 @@ function getDefaultTouchControlsPositions () { return { action: [ 70, - 85 + 76 ], sneak: [ - 90, - 85 + 84, + 76 ], break: [ 70, - 65 + 60 ], jump: [ - 90, - 65 + 84, + 60 ], } as Record } diff --git a/src/react/TouchAreasControls.tsx b/src/react/TouchAreasControls.tsx index 368307547..29233b895 100644 --- a/src/react/TouchAreasControls.tsx +++ b/src/react/TouchAreasControls.tsx @@ -52,6 +52,7 @@ export const handleMovementStickDelta = (e?: { clientX, clientY }) => { } export default ({ touchActive, setupActive, buttonsPositions, closeButtonsSetup }: Props) => { + const bot = window.bot as typeof __type_bot | undefined if (setupActive) touchActive = true const joystickOuter = useRef(null) @@ -64,9 +65,9 @@ export default ({ touchActive, setupActive, buttonsPositions, closeButtonsSetup const buttonProps = (name: ButtonName) => { let active = { action: false, - sneak: bot.getControlState('sneak'), + sneak: bot?.getControlState('sneak'), break: false, - jump: bot.getControlState('jump'), + jump: bot?.getControlState('jump'), }[name] const holdDown = { action () { @@ -79,7 +80,7 @@ export default ({ touchActive, setupActive, buttonsPositions, closeButtonsSetup command: 'general.toggleSneakOrDown', schema: null as any, }) - active = bot.getControlState('sneak') + active = bot?.getControlState('sneak') }, break () { document.dispatchEvent(new MouseEvent('mousedown', { button: 0 })) @@ -91,7 +92,7 @@ export default ({ touchActive, setupActive, buttonsPositions, closeButtonsSetup command: 'general.jump', schema: null as any, }) - active = bot.controlState.jump + active = bot?.controlState.jump } } const holdUp = { @@ -102,7 +103,7 @@ export default ({ touchActive, setupActive, buttonsPositions, closeButtonsSetup command: 'general.toggleSneakOrDown', schema: null as any, }) - active = bot.getControlState('sneak') + active = bot?.getControlState('sneak') }, break () { document.dispatchEvent(new MouseEvent('mouseup', { button: 0 })) @@ -114,7 +115,7 @@ export default ({ touchActive, setupActive, buttonsPositions, closeButtonsSetup command: 'general.jump', schema: null as any, }) - active = bot.controlState.jump + active = bot?.controlState.jump } } diff --git a/src/react/TouchAreasControlsProvider.tsx b/src/react/TouchAreasControlsProvider.tsx index 24479e893..70c8bdf24 100644 --- a/src/react/TouchAreasControlsProvider.tsx +++ b/src/react/TouchAreasControlsProvider.tsx @@ -10,10 +10,16 @@ export default () => { const setupActive = useIsModalActive('touch-buttons-setup') const { touchControlsPositions, touchControlsType } = useSnapshot(options) - return { - if (newPositions) { - options.touchControlsPositions = newPositions - } - hideModal() - }} /> + return { + if (newPositions) { + options.touchControlsPositions = newPositions + } + hideModal() + }} + /> + } diff --git a/src/reactUi.tsx b/src/reactUi.tsx index 26887d669..f140326ee 100644 --- a/src/reactUi.tsx +++ b/src/reactUi.tsx @@ -115,7 +115,6 @@ const InGameUi = () => { -
@@ -173,6 +172,7 @@ const App = () => { + {/* */} From ef0dd1cf49f467e27b5bb5b5d2973dd839485d0a Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Fri, 21 Jun 2024 22:25:52 +0300 Subject: [PATCH 06/19] fix: allow to long tap interacte button in new touch controls. This now allows to use food on mobile & long-hold building --- src/react/TouchAreasControls.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/react/TouchAreasControls.tsx b/src/react/TouchAreasControls.tsx index 29233b895..0476be488 100644 --- a/src/react/TouchAreasControls.tsx +++ b/src/react/TouchAreasControls.tsx @@ -73,7 +73,6 @@ export default ({ touchActive, setupActive, buttonsPositions, closeButtonsSetup action () { document.dispatchEvent(new MouseEvent('mousedown', { button: 2 })) worldInteractions.update() - document.dispatchEvent(new MouseEvent('mouseup', { button: 2 })) }, sneak () { void contro.emit('trigger', { @@ -97,6 +96,7 @@ export default ({ touchActive, setupActive, buttonsPositions, closeButtonsSetup } const holdUp = { action () { + document.dispatchEvent(new MouseEvent('mouseup', { button: 2 })) }, sneak () { void contro.emit('release', { From a139ecdf1dcfa3d748f40464bbbb4ea4f221a93e Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Fri, 21 Jun 2024 22:33:38 +0300 Subject: [PATCH 07/19] fix: fix right click emulation with touch in inventory windows (allow to pick half of items on mobile, needed for crafting) --- pnpm-lock.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 67b725825..4edd6e2bf 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -305,7 +305,7 @@ importers: version: 1.0.0 minecraft-inventory-gui: specifier: github:zardoy/minecraft-inventory-gui#next - version: https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/e5a0fe3670e5e1d7c6024e9024ccba9a4a88e232(@types/react@18.2.20)(react@18.2.0) + version: https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/c50afc54e39817f7e4d313ce0f6fdaad71e7e4f4(@types/react@18.2.20)(react@18.2.0) mineflayer: specifier: github:zardoy/mineflayer version: https://codeload.github.com/zardoy/mineflayer/tar.gz/a4b1b4ba7f8c972cee9c0a16eb1191ff4d21fe23(encoding@0.1.13) @@ -6062,8 +6062,8 @@ packages: minecraft-folder-path@1.2.0: resolution: {integrity: sha512-qaUSbKWoOsH9brn0JQuBhxNAzTDMwrOXorwuRxdJKKKDYvZhtml+6GVCUrY5HRiEsieBEjCUnhVpDuQiKsiFaw==} - minecraft-inventory-gui@https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/e5a0fe3670e5e1d7c6024e9024ccba9a4a88e232: - resolution: {tarball: https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/e5a0fe3670e5e1d7c6024e9024ccba9a4a88e232} + minecraft-inventory-gui@https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/c50afc54e39817f7e4d313ce0f6fdaad71e7e4f4: + resolution: {tarball: https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/c50afc54e39817f7e4d313ce0f6fdaad71e7e4f4} version: 1.0.1 minecraft-protocol@https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/495eed56ab230b2615596590064671356d86a2dc: @@ -15731,7 +15731,7 @@ snapshots: minecraft-folder-path@1.2.0: {} - minecraft-inventory-gui@https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/e5a0fe3670e5e1d7c6024e9024ccba9a4a88e232(@types/react@18.2.20)(react@18.2.0): + minecraft-inventory-gui@https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/c50afc54e39817f7e4d313ce0f6fdaad71e7e4f4(@types/react@18.2.20)(react@18.2.0): dependencies: valtio: 1.11.2(@types/react@18.2.20)(react@18.2.0) transitivePeerDependencies: From 97ad6d9ff8d284730afddce406e11b01107abab0 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sat, 22 Jun 2024 18:35:10 +0300 Subject: [PATCH 08/19] fix: hotbar on start crash sentry-ref: 5467800534 --- src/react/HotbarRenderApp.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/react/HotbarRenderApp.tsx b/src/react/HotbarRenderApp.tsx index 9c6dbe407..600eaa361 100644 --- a/src/react/HotbarRenderApp.tsx +++ b/src/react/HotbarRenderApp.tsx @@ -111,7 +111,7 @@ export default () => { inv.canvas.style.pointerEvents = 'auto' container.current.appendChild(inv.canvas) const upHotbarItems = () => { - if (!viewer.world.downloadedTextureImage && !viewer.world.customTexturesDataUrl) return + if (!viewer.world.downloadedTextureImage || !viewer.world.customTexturesDataUrl) return upInventoryItems(true, inv) } From f8980836dc8e35df244cf4bb0baae6abff769f82 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sat, 22 Jun 2024 20:15:12 +0300 Subject: [PATCH 09/19] fix: fix basic vr support & test dom overlay sentry-ref: 5517390383, 5517390775 --- package.json | 3 ++- patches/three@0.154.0.patch | 16 ++++++++++++++++ pnpm-lock.yaml | 15 +++++++++------ src/styles.css | 3 +++ src/{vr.js => vr.ts} | 38 ++++++++++++++++++------------------- 5 files changed, 49 insertions(+), 26 deletions(-) create mode 100644 patches/three@0.154.0.patch rename src/{vr.js => vr.ts} (81%) diff --git a/package.json b/package.json index 56e3cfdd3..c03422604 100644 --- a/package.json +++ b/package.json @@ -168,7 +168,8 @@ "ignoreDependencies": [] }, "patchedDependencies": { - "minecraft-protocol@1.47.0": "patches/minecraft-protocol@1.47.0.patch" + "minecraft-protocol@1.47.0": "patches/minecraft-protocol@1.47.0.patch", + "three@0.154.0": "patches/three@0.154.0.patch" } }, "packageManager": "pnpm@9.0.4" diff --git a/patches/three@0.154.0.patch b/patches/three@0.154.0.patch new file mode 100644 index 000000000..e612415c7 --- /dev/null +++ b/patches/three@0.154.0.patch @@ -0,0 +1,16 @@ +diff --git a/examples/jsm/webxr/VRButton.js b/examples/jsm/webxr/VRButton.js +index 6856a21b17aa45d7922bbf776fd2d7e63c7a9b4e..0925b706f7629bd52f0bb5af469536af8f5fce2c 100644 +--- a/examples/jsm/webxr/VRButton.js ++++ b/examples/jsm/webxr/VRButton.js +@@ -62,7 +62,10 @@ class VRButton { + // ('local' is always available for immersive sessions and doesn't need to + // be requested separately.) + +- const sessionInit = { optionalFeatures: [ 'local-floor', 'bounded-floor', 'hand-tracking', 'layers' ] }; ++ const sessionInit = { ++ optionalFeatures: ['local-floor', 'bounded-floor', 'layers'], ++ domOverlay: { root: document.body }, ++ }; + navigator.xr.requestSession( 'immersive-vr', sessionInit ).then( onSessionStarted ); + + } else { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4edd6e2bf..1f2b48099 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -20,6 +20,9 @@ patchedDependencies: minecraft-protocol@1.47.0: hash: 2uxevyasyasdavsxuehfavgkjq path: patches/minecraft-protocol@1.47.0.patch + three@0.154.0: + hash: sj7ocb4p23jym6bkfgueanti2e + path: patches/three@0.154.0.patch importers: @@ -341,7 +344,7 @@ importers: version: 3.0.0 three: specifier: 0.154.0 - version: 0.154.0 + version: 0.154.0(patch_hash=sj7ocb4p23jym6bkfgueanti2e) timers-browserify: specifier: ^2.0.12 version: 2.0.12 @@ -410,7 +413,7 @@ importers: version: 4.7.2 three-stdlib: specifier: ^2.26.11 - version: 2.28.5(three@0.154.0) + version: 2.28.5(three@0.154.0(patch_hash=sj7ocb4p23jym6bkfgueanti2e)) three.meshline: specifier: ^1.3.0 version: 1.4.0 @@ -17565,7 +17568,7 @@ snapshots: dependencies: '@types/three': 0.156.0 skinview-utils: 0.7.1 - three: 0.154.0 + three: 0.154.0(patch_hash=sj7ocb4p23jym6bkfgueanti2e) slash@3.0.0: {} @@ -18018,7 +18021,7 @@ snapshots: dependencies: any-promise: 1.3.0 - three-stdlib@2.28.5(three@0.154.0): + three-stdlib@2.28.5(three@0.154.0(patch_hash=sj7ocb4p23jym6bkfgueanti2e)): dependencies: '@types/draco3d': 1.4.7 '@types/offscreencanvas': 2019.7.2 @@ -18026,11 +18029,11 @@ snapshots: draco3d: 1.5.6 fflate: 0.6.10 potpack: 1.0.2 - three: 0.154.0 + three: 0.154.0(patch_hash=sj7ocb4p23jym6bkfgueanti2e) three.meshline@1.4.0: {} - three@0.154.0: {} + three@0.154.0(patch_hash=sj7ocb4p23jym6bkfgueanti2e): {} throttle-debounce@3.0.1: {} diff --git a/src/styles.css b/src/styles.css index 139996e9f..9aea87a17 100644 --- a/src/styles.css +++ b/src/styles.css @@ -163,6 +163,9 @@ body { animation-timing-function: ease-in-out; animation-fill-mode: forwards; } +#viewer-canvas::xr-overlay { + display: none; +} .full-svg svg { width: 100%; diff --git a/src/vr.js b/src/vr.ts similarity index 81% rename from src/vr.js rename to src/vr.ts index 92f020f25..af1e885ae 100644 --- a/src/vr.js +++ b/src/vr.ts @@ -1,13 +1,13 @@ -const { VRButton } = require('three/examples/jsm/webxr/VRButton.js') -const { GLTFLoader } = require('three/examples/jsm/loaders/GLTFLoader.js') -const { XRControllerModelFactory } = require('three/examples/jsm/webxr/XRControllerModelFactory.js') -const { buttonMap: standardButtonsMap } = require('contro-max/build/gamepad') -const { activeModalStack, hideModal } = require('./globalState') +import { VRButton } from 'three/examples/jsm/webxr/VRButton.js' +import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js' +import { XRControllerModelFactory } from 'three/examples/jsm/webxr/XRControllerModelFactory.js' +import { buttonMap as standardButtonsMap } from 'contro-max/build/gamepad' +import { activeModalStack, hideModal } from './globalState' -async function initVR() { +export async function initVR () { const { renderer } = viewer if (!('xr' in navigator)) return - const isSupported = await navigator.xr.isSessionSupported('immersive-vr') && !!XRSession.prototype.updateRenderState // e.g. android webview doesn't support updateRenderState + const isSupported = await navigator.xr?.isSessionSupported('immersive-vr') && !!XRSession.prototype.updateRenderState // e.g. android webview doesn't support updateRenderState if (!isSupported) return // VR @@ -25,22 +25,23 @@ async function initVR() { // todo the logic written here can be hard to understand as it was designed to work in gamepad api emulation mode, will be refactored once there is a contro-max rewrite is done const virtualGamepadIndex = 4 let connectedVirtualGamepad + //@ts-expect-error const manageXrInputSource = ({ gamepad, handedness = defaultHandedness }, defaultHandedness, removeAction = false) => { if (handedness === 'right') { - const event = new Event(removeAction ? 'gamepaddisconnected' : 'gamepadconnected') // todo need to expose and use external gamepads api in contro-max instead + const event: any = new Event(removeAction ? 'gamepaddisconnected' : 'gamepadconnected') // todo need to expose and use external gamepads api in contro-max instead event.gamepad = removeAction ? connectedVirtualGamepad : { ...gamepad, mapping: 'standard', index: virtualGamepadIndex } connectedVirtualGamepad = event.gamepad window.dispatchEvent(event) } } - let hand1 = controllerModelFactory.createControllerModel(controller1) + let hand1: any = controllerModelFactory.createControllerModel(controller1) controller1.addEventListener('connected', (event) => { hand1.xrInputSource = event.data manageXrInputSource(event.data, 'left') user.add(controller1) }) controller1.add(hand1) - let hand2 = controllerModelFactory.createControllerModel(controller2) + let hand2: any = controllerModelFactory.createControllerModel(controller2) controller2.addEventListener('connected', (event) => { hand2.xrInputSource = event.data manageXrInputSource(event.data, 'right') @@ -50,15 +51,17 @@ async function initVR() { controller1.addEventListener('disconnected', () => { // don't handle removal of gamepads for now as is don't affect contro-max - hand1.xrInputSource = undefined manageXrInputSource(hand1.xrInputSource, 'left', true) + hand1.xrInputSource = undefined }) controller2.addEventListener('disconnected', () => { - hand2.xrInputSource = undefined manageXrInputSource(hand1.xrInputSource, 'right', true) + hand2.xrInputSource = undefined }) const originalGetGamepads = navigator.getGamepads.bind(navigator) + // is it okay to patch this? + //@ts-expect-error navigator.getGamepads = () => { const originalGamepads = originalGetGamepads() if (!hand1.xrInputSource || !hand2.xrInputSource) return originalGamepads @@ -105,15 +108,14 @@ async function initVR() { viewer.setFirstPersonCamera(null, bot.entity.yaw, bot.entity.pitch) // todo restore this logic (need to preserve ability to move camera) - // const xrCamera = renderer.xr.getCamera(viewer.camera) - // const d = xrCamera.getWorldDirection() // todo target + // const xrCamera = renderer.xr.getCamera() + // const d = xrCamera.getWorldDirection(new THREE.Vector3()) // bot.entity.yaw = Math.atan2(-d.x, -d.z) // bot.entity.pitch = Math.asin(d.y) // todo ? // bot.physics.stepHeight = 1 - viewer.update() viewer.render() }) renderer.xr.addEventListener('sessionstart', () => { @@ -128,8 +130,6 @@ async function initVR() { }) } -module.exports.initVR = initVR - const xrStandardRightButtonsMap = [ [0 /* trigger */, 'Right Trigger'], [1 /* squeeze */, 'Right Bumper'], @@ -146,9 +146,9 @@ const xrStandardLeftButtonsMap = [ [4 /* A */, 'X'], [5 /* B */, 'Y'], ] -const remapButtons = (rightButtons, leftButtons) => { +const remapButtons = (rightButtons: any[], leftButtons: any[]) => { // return remapped buttons - const remapped = [] + const remapped = [] as string[] const remapWithMap = (buttons, map) => { for (const [index, standardName] of map) { const standardMappingIndex = standardButtonsMap.findIndex((aliases) => aliases.find(alias => standardName === alias)) From 8d36e4ba4d635d64e8630d38a4460ffc0ce9f621 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sat, 22 Jun 2024 20:33:03 +0300 Subject: [PATCH 10/19] fix xr-overlay selector usage --- src/styles.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/styles.css b/src/styles.css index 9aea87a17..ad90b7fbd 100644 --- a/src/styles.css +++ b/src/styles.css @@ -163,7 +163,7 @@ body { animation-timing-function: ease-in-out; animation-fill-mode: forwards; } -#viewer-canvas::xr-overlay { +body::xr-overlay #viewer-canvas { display: none; } From d41219d55bb7e057e4c02074db36e274d541f448 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sun, 23 Jun 2024 10:38:47 +0300 Subject: [PATCH 11/19] fix: packets string was not displayed in f3 correctly --- src/react/DebugOverlay.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/react/DebugOverlay.tsx b/src/react/DebugOverlay.tsx index bb38672e8..cb8317678 100644 --- a/src/react/DebugOverlay.tsx +++ b/src/react/DebugOverlay.tsx @@ -76,7 +76,7 @@ export default () => { useEffect(() => { document.addEventListener('keydown', handleF3) const packetsUpdateInterval = setInterval(() => { - setPacketsString(prev => `↓ ${received.current.count} (${(received.current.size / 1024).toFixed(2)} KB/s, ${getFixedFilesize(receivedTotal.current)}) ↑ ${sent.current.count}`) + setPacketsString(`↓ ${received.current.count} (${(received.current.size / 1024).toFixed(2)} KB/s, ${getFixedFilesize(receivedTotal.current)}) ↑ ${sent.current.count}`) received.current = { ...defaultPacketsCount } sent.current = { ...defaultPacketsCount } packetsCountByNamePerSec.current.received = {} @@ -84,10 +84,10 @@ export default () => { }, 1000) const freqUpdateInterval = setInterval(() => { - setPos(prev => { return { ...bot.entity.position } }) - setSkyL(prev => bot.world.getSkyLight(bot.entity.position)) - setBlockL(prev => bot.world.getBlockLight(bot.entity.position)) - setBiomeId(prev => bot.world.getBiome(bot.entity.position)) + setPos({ ...bot.entity.position }) + setSkyL(bot.world.getSkyLight(bot.entity.position)) + setBlockL(bot.world.getBlockLight(bot.entity.position)) + setBiomeId(bot.world.getBiome(bot.entity.position)) setDimension(bot.game.dimension) setDay(bot.time.day) setCursorBlock(worldInteractions.cursorBlock) From 9961424585d3037995fd604824d145e18d10bdc1 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sun, 23 Jun 2024 06:39:39 +0300 Subject: [PATCH 12/19] add more space for notification --- src/react/Notification.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/react/Notification.tsx b/src/react/Notification.tsx index 0f3a63259..a9b7a95a2 100644 --- a/src/react/Notification.tsx +++ b/src/react/Notification.tsx @@ -36,20 +36,20 @@ export default ({ type = 'message', message, subMessage = '', open, icon = '', a position: 'fixed', top: 0, right: 0, - width: '155px', + width: '180px', whiteSpace: 'nowrap', fontSize: '9px', display: 'flex', gap: 4, alignItems: 'center', - padding: '3px 8px', + padding: '3px 5px', background: isError ? 'rgba(255, 0, 0, 0.7)' : 'rgba(0, 0, 0, 0.7)', borderRadius: '0 0 0 5px', pointerEvents: action ? '' : 'none', zIndex: 1200, // even above stats ...addStyles }}> - +
Date: Sun, 23 Jun 2024 06:39:27 +0300 Subject: [PATCH 13/19] some other annoying vr bugfixes: do not overlay hotbar with enter vr button --- prismarine-viewer/viewer/lib/worldrendererThree.ts | 3 ++- src/styles.css | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/prismarine-viewer/viewer/lib/worldrendererThree.ts b/prismarine-viewer/viewer/lib/worldrendererThree.ts index 9a76474b6..cc89a8238 100644 --- a/prismarine-viewer/viewer/lib/worldrendererThree.ts +++ b/prismarine-viewer/viewer/lib/worldrendererThree.ts @@ -162,7 +162,8 @@ export class WorldRendererThree extends WorldRendererCommon { render () { tweenJs.update() - this.renderer.render(this.scene, this.camera) + const cam = this.camera instanceof THREE.Group ? this.camera.children.find(child => child instanceof THREE.PerspectiveCamera) as THREE.PerspectiveCamera : this.camera + this.renderer.render(this.scene, cam) } renderSign (position: Vec3, rotation: number, isWall: boolean, isHanging: boolean, blockEntity) { diff --git a/src/styles.css b/src/styles.css index ad90b7fbd..b0e46cf2d 100644 --- a/src/styles.css +++ b/src/styles.css @@ -68,6 +68,7 @@ body { background: rgba(0, 0, 0, 0.3) !important; opacity: 0.7 !important; position: fixed !important; + bottom: 60px; } .dirt-bg { From 7ecaae089db531ec700823ca8bb510960ad1c8e1 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sun, 23 Jun 2024 06:37:23 +0300 Subject: [PATCH 14/19] add window.stats for debugging --- src/devtools.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/devtools.ts b/src/devtools.ts index e836af02b..73436347b 100644 --- a/src/devtools.ts +++ b/src/devtools.ts @@ -43,3 +43,15 @@ window.inspectPacket = (packetName, full = false) => { }) return returnobj } + +// for advanced debugging, use with watch expression + +let stats_ = {} +window.addStatHit = (key) => { + stats_[key] ??= 0 + stats_[key]++ +} +setInterval(() => { + window.stats = stats_ + stats_ = {} +}, 1000) From d880479c7be66e0042b39767d5bc11442afbee4f Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Sun, 23 Jun 2024 06:37:59 +0300 Subject: [PATCH 15/19] dont make stats overlay other components with useful info & clickable areas. display only with canvas --- src/topRightStats.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/topRightStats.ts b/src/topRightStats.ts index a508a2c24..71303e81a 100644 --- a/src/topRightStats.ts +++ b/src/topRightStats.ts @@ -22,7 +22,7 @@ const addStat = (dom, size = 80) => { dom.style.top = 0 dom.style.right = `${total}px` dom.style.width = '80px' - dom.style.zIndex = 1000 + dom.style.zIndex = 1 dom.style.opacity = '0.8' document.body.appendChild(dom) total += size From 8456fc6ce4ee93dec129a32aa7de7c8650e78f4a Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Mon, 24 Jun 2024 00:13:11 +0300 Subject: [PATCH 16/19] reduce memory usage by 10% --- prismarine-viewer/viewer/lib/mesher/models.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/prismarine-viewer/viewer/lib/mesher/models.ts b/prismarine-viewer/viewer/lib/mesher/models.ts index c978c240a..11988b920 100644 --- a/prismarine-viewer/viewer/lib/mesher/models.ts +++ b/prismarine-viewer/viewer/lib/mesher/models.ts @@ -623,8 +623,8 @@ export function getSectionGeometry (sx, sy, sz, world: World) { delete attr.t_colors delete attr.t_uvs - attr.positions = new Float32Array(attr.positions) as any - attr.normals = new Float32Array(attr.normals) as any + attr.positions = new Int8Array(attr.positions) as any + attr.normals = new Uint8ClampedArray(attr.normals) as any attr.colors = new Float32Array(attr.colors) as any attr.uvs = new Float32Array(attr.uvs) as any From 4fd607c17c22dbe021ee6fde251ded7912226636 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Mon, 24 Jun 2024 04:29:07 +0300 Subject: [PATCH 17/19] add useful worker proxy helper from webgpu implementation --- prismarine-viewer/viewer/lib/workerProxy.ts | 58 +++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 prismarine-viewer/viewer/lib/workerProxy.ts diff --git a/prismarine-viewer/viewer/lib/workerProxy.ts b/prismarine-viewer/viewer/lib/workerProxy.ts new file mode 100644 index 000000000..80a8cdb4d --- /dev/null +++ b/prismarine-viewer/viewer/lib/workerProxy.ts @@ -0,0 +1,58 @@ +export function createWorkerProxy void>> (handlers: T): { __workerProxy: T } { + addEventListener('message', (event) => { + const { type, args } = event.data + if (handlers[type]) { + handlers[type](...args) + } + }) + return null as any +} + +/** + * in main thread + * ```ts + * // either: + * import type { importedTypeWorkerProxy } from './worker' + * // or: + * type importedTypeWorkerProxy = import('./worker').importedTypeWorkerProxy + * + * const workerChannel = useWorkerProxy(worker) + * ``` + */ +export const useWorkerProxy = void> }> (worker: Worker, autoTransfer = true): T['__workerProxy'] & { + transfer: (...args: Transferable[]) => T['__workerProxy'] +} => { + // in main thread + return new Proxy({} as any, { + get: (target, prop) => { + if (prop === 'transfer') return (...transferable: Transferable[]) => { + return new Proxy({}, { + get: (target, prop) => { + return (...args: any[]) => { + worker.postMessage({ + type: prop, + args, + }, transferable) + } + } + }) + } + return (...args: any[]) => { + const transfer = autoTransfer ? args.filter(arg => arg instanceof ArrayBuffer || arg instanceof MessagePort || arg instanceof ImageBitmap || arg instanceof OffscreenCanvas || arg instanceof ImageData) : [] + worker.postMessage({ + type: prop, + args, + }, transfer) + } + } + }) +} + +// const workerProxy = createWorkerProxy({ +// startRender (canvas: HTMLCanvasElement) { +// }, +// }) + +// const worker = useWorkerProxy(null, workerProxy) + +// worker. From c7a8c34d89d42087818e6b2dbdd24721f1f0f07d Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Mon, 24 Jun 2024 06:31:04 +0300 Subject: [PATCH 18/19] improve fps in some scenarios by not spamming with setTimeout calls --- prismarine-viewer/viewer/lib/mesher/mesher.ts | 2 +- prismarine-viewer/viewer/lib/mesher/models.ts | 2 +- prismarine-viewer/viewer/lib/viewerWrapper.ts | 4 ++-- prismarine-viewer/viewer/lib/worldDataEmitter.ts | 15 ++++++++++----- scripts/esbuildPlugins.mjs | 2 +- src/styles.css | 2 +- 6 files changed, 16 insertions(+), 11 deletions(-) diff --git a/prismarine-viewer/viewer/lib/mesher/mesher.ts b/prismarine-viewer/viewer/lib/mesher/mesher.ts index d0c82280f..67eb7adc8 100644 --- a/prismarine-viewer/viewer/lib/mesher/mesher.ts +++ b/prismarine-viewer/viewer/lib/mesher/mesher.ts @@ -128,7 +128,7 @@ setInterval(() => { //@ts-ignore postMessage({ type: 'geometry', key, geometry }, transferable) } else { - console.info('[mesher] Missing section', x, y, z) + // console.info('[mesher] Missing section', x, y, z) } const dirtyTimes = dirtySections.get(key) if (!dirtyTimes) throw new Error('dirtySections.get(key) is falsy') diff --git a/prismarine-viewer/viewer/lib/mesher/models.ts b/prismarine-viewer/viewer/lib/mesher/models.ts index 11988b920..48874f66c 100644 --- a/prismarine-viewer/viewer/lib/mesher/models.ts +++ b/prismarine-viewer/viewer/lib/mesher/models.ts @@ -623,7 +623,7 @@ export function getSectionGeometry (sx, sy, sz, world: World) { delete attr.t_colors delete attr.t_uvs - attr.positions = new Int8Array(attr.positions) as any + attr.positions = new Float32Array(attr.positions) as any attr.normals = new Uint8ClampedArray(attr.normals) as any attr.colors = new Float32Array(attr.colors) as any attr.uvs = new Float32Array(attr.uvs) as any diff --git a/prismarine-viewer/viewer/lib/viewerWrapper.ts b/prismarine-viewer/viewer/lib/viewerWrapper.ts index b8017f384..57317f422 100644 --- a/prismarine-viewer/viewer/lib/viewerWrapper.ts +++ b/prismarine-viewer/viewer/lib/viewerWrapper.ts @@ -11,7 +11,8 @@ export class ViewerWrapper { renderIntervalUnfocused: number | undefined fpsInterval - constructor(public canvas: HTMLCanvasElement, public renderer?: THREE.WebGLRenderer) { + constructor (public canvas: HTMLCanvasElement, public renderer?: THREE.WebGLRenderer) { + if (this.renderer) this.globalObject.renderer = this.renderer } addToPage (startRendering = true) { if (this.addedToPage) throw new Error('Already added to page') @@ -30,7 +31,6 @@ export class ViewerWrapper { this.canvas.id = 'viewer-canvas' document.body.appendChild(this.canvas) - if (this.renderer) this.globalObject.renderer = this.renderer this.addedToPage = true let max = 0 diff --git a/prismarine-viewer/viewer/lib/worldDataEmitter.ts b/prismarine-viewer/viewer/lib/worldDataEmitter.ts index ed5780403..5df5cc73a 100644 --- a/prismarine-viewer/viewer/lib/worldDataEmitter.ts +++ b/prismarine-viewer/viewer/lib/worldDataEmitter.ts @@ -19,7 +19,7 @@ export class WorldDataEmitter extends EventEmitter { private eventListeners: Record = {}; private emitter: WorldDataEmitter - constructor(public world: import('prismarine-world').world.World | typeof __type_bot['world'], public viewDistance: number, position: Vec3 = new Vec3(0, 0, 0)) { + constructor (public world: typeof __type_bot['world'], public viewDistance: number, position: Vec3 = new Vec3(0, 0, 0)) { super() this.loadedChunks = {} this.lastPos = new Vec3(0, 0, 0).update(position) @@ -123,10 +123,15 @@ export class WorldDataEmitter extends EventEmitter { } async _loadChunks (positions: Vec3[], sliceSize = 5, waitTime = 0) { - for (let i = 0; i < positions.length; i += sliceSize) { - await new Promise((resolve) => setTimeout(resolve, waitTime)) - await Promise.all(positions.slice(i, i + sliceSize).map((p) => this.loadChunk(p))) - } + let i = 0 + const interval = setInterval(() => { + if (i >= positions.length) { + clearInterval(interval) + return + } + this.loadChunk(positions[i]) + i++ + }, 1) } readdDebug () { diff --git a/scripts/esbuildPlugins.mjs b/scripts/esbuildPlugins.mjs index fe4018f3c..5a2a4c39e 100644 --- a/scripts/esbuildPlugins.mjs +++ b/scripts/esbuildPlugins.mjs @@ -124,7 +124,7 @@ const plugins = [ }) const removeNodeModulesSourcemaps = (map) => { - const doNotRemove = ['prismarine', 'mineflayer', 'flying-squid', '@jspm/core', 'minecraft'] + const doNotRemove = ['prismarine', 'mineflayer', 'flying-squid', '@jspm/core', 'minecraft', 'three'] map.sourcesContent.forEach((_, i) => { if (map.sources[i].includes('node_modules') && !doNotRemove.some(x => map.sources[i].includes(x))) { map.sourcesContent[i] = null diff --git a/src/styles.css b/src/styles.css index b0e46cf2d..3d60bfce6 100644 --- a/src/styles.css +++ b/src/styles.css @@ -68,7 +68,7 @@ body { background: rgba(0, 0, 0, 0.3) !important; opacity: 0.7 !important; position: fixed !important; - bottom: 60px; + bottom: 60px !important; } .dirt-bg { From a3a530f3a324a8c8d4ef293c262dfee411fe3971 Mon Sep 17 00:00:00 2001 From: Vitaly Turovsky Date: Mon, 24 Jun 2024 06:34:48 +0300 Subject: [PATCH 19/19] cleanup: ImageData is not marked as transferrable --- prismarine-viewer/viewer/lib/workerProxy.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prismarine-viewer/viewer/lib/workerProxy.ts b/prismarine-viewer/viewer/lib/workerProxy.ts index 80a8cdb4d..adfe6ac24 100644 --- a/prismarine-viewer/viewer/lib/workerProxy.ts +++ b/prismarine-viewer/viewer/lib/workerProxy.ts @@ -38,7 +38,7 @@ export const useWorkerProxy = { - const transfer = autoTransfer ? args.filter(arg => arg instanceof ArrayBuffer || arg instanceof MessagePort || arg instanceof ImageBitmap || arg instanceof OffscreenCanvas || arg instanceof ImageData) : [] + const transfer = autoTransfer ? args.filter(arg => arg instanceof ArrayBuffer || arg instanceof MessagePort || arg instanceof ImageBitmap || arg instanceof OffscreenCanvas) : [] worker.postMessage({ type: prop, args,