diff --git a/.dockerignore b/.dockerignore index 285d1303..38ca0016 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,5 +1,3 @@ -# we dont want default config to be loaded in the dockerfile, but rather using a volume -config.json # build stuff node_modules public \ No newline at end of file diff --git a/.eslintrc.json b/.eslintrc.json index 98388260..a91015d2 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -96,5 +96,22 @@ "unicorn/filename-case": "off", "max-depth": "off" }, + "overrides": [ + { + "files": [ + "*.js" + ], + "rules": { + "space-before-function-paren": [ + "error", + { + "anonymous": "always", + "named": "never", + "asyncArrow": "always" + } + ] + } + } + ], "root": true } diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0663fce7..22c95b87 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,6 +8,11 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@master + - name: Setup Java JDK + uses: actions/setup-java@v1.4.3 + with: + java-version: 17 + java-package: jre - name: Install pnpm run: npm i -g pnpm@9.0.4 - uses: actions/setup-node@v4 diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 18c1a9bf..fc3f2f12 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -1,4 +1,4 @@ -name: Deploy to GitHub pages +name: Release env: VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} @@ -17,6 +17,7 @@ jobs: # - run: pnpm install # - run: pnpm build - run: vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }} + - run: node scripts/replaceFavicon.mjs ${{ secrets.FAVICON_MAIN }} # will install + build to .vercel/output/static - run: vercel build --token=${{ secrets.VERCEL_TOKEN }} --prod - run: pnpm build-storybook diff --git a/.gitignore b/.gitignore index 240b751a..3a188862 100644 --- a/.gitignore +++ b/.gitignore @@ -17,5 +17,6 @@ out .vercel generated storybook-static +server-jar src/react/npmReactComponents.ts diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx index a8a219f1..05c36eba 100644 --- a/.storybook/preview.tsx +++ b/.storybook/preview.tsx @@ -1,9 +1,10 @@ import React from 'react' -import type { Preview } from "@storybook/react"; +import type { Preview } from "@storybook/react" -import '../src/styles.css' import './storybook.css' +import '../src/styles.css' +import '../src/scaleInterface' const preview: Preview = { decorators: [ @@ -11,7 +12,7 @@ const preview: Preview = { const noScaling = c.parameters.noScaling return
-
; + }, ], parameters: { @@ -23,6 +24,6 @@ const preview: Preview = { }, }, }, -}; +} -export default preview; +export default preview diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 253b2a52..eaf77b66 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -21,7 +21,7 @@ How different modules are used: - `mineflayer` - provider `bot` variable and as mineflayer states it is a wrapper for the `node-minecraft-protocol` module and is used to connect and interact with real Java Minecraft servers. However not all events & properties are exposed and sometimes you have to use `bot._client.on('packet_name', data => ...)` to handle packets that are not handled via mineflayer API. Also you can use almost any mineflayer plugin. -## Making protocol changes +## Making protocol-related changes You can get a description of packets for the latest protocol version from and for previous protocol versions from (look for *Page* links that have *Protocol* in URL). @@ -37,6 +37,7 @@ Also there are [src/generatedClientPackets.ts](src/generatedClientPackets.ts) an - Some data are cached between restarts. If you see something doesn't work after upgrading dependencies, try to clear the by simply removing the `dist` folder. - The same folder `dist` is used for both development and production builds, so be careful when deploying the project. - Use `start-prod` script to start the project in production mode after running the `build` script to build the project. +- If CI is failing on the next branch for some reason, feel free to use the latest commit for release branch. We will update the base branch asap. Please, always make sure to allow maintainers do changes when opening PRs. ### Would be useful to have diff --git a/Dockerfile b/Dockerfile index aa9eb3dc..086240b5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,9 +1,22 @@ -FROM node:14-alpine +FROM node:18-alpine # Without git installing the npm packages fails RUN apk add git RUN mkdir /app WORKDIR /app COPY . /app -RUN npm install -RUN npm run build +# install python and other dependencies +RUN apk add python3 make g++ cairo-dev pango-dev jpeg-dev giflib-dev librsvg-dev +# install pnpm +RUN npm i -g pnpm@9.0.4 +RUN pnpm install +# only for prod +RUN pnpm run build +# --- +EXPOSE 8080 +# uncomment for development +# EXPOSE 9090 +# VOLUME /app/src +# VOLUME /app/prismarine-viewer +# ENTRYPOINT ["pnpm", "run", "run-all"] +# only for prod ENTRYPOINT ["npm", "run", "prod-start"] diff --git a/README.MD b/README.MD index ce368540..9a4ba24a 100644 --- a/README.MD +++ b/README.MD @@ -4,19 +4,20 @@ A true Minecraft client running in your browser! A port of the original game to the web, written in JavaScript using modern web technologies. -If you encounter any bugs or usability issues, please report them! +If you encounter any bugs or usability issues, please report them! For development, see [development](#development--debugging). You can try this out at [mcraft.fun](https://mcraft.fun/), [pcm.gg](https://pcm.gg) (short link) [mcon.vercel.app](https://mcon.vercel.app/) or the GitHub pages deploy. Every commit from the `develop` (default) branch is deployed to [s.mcraft.fun](https://s.mcraft.fun/) - so it's usually newer, but might be less stable. ### Big Features - Open any zip world file or even folder in read-write mode! -- Connect to cracked servers* (it's possible because of proxy servers, see below) +- Connect to Java servers running in both offline (cracked) and online mode* (it's possible because of proxy servers, see below) - Singleplayer mode with simple world generations! - Works offline - 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) @@ -34,11 +35,13 @@ Zip files and folders are supported. Just drag and drop them into the browser wi In case of opening zip files they are stored in your ram entirely, so there is a ~300mb file limit on IOS. Whatever offline mode you used (zip, folder, just single player), you can always export world with the `/export` command typed in the game chat. +![docs-assets/singleplayer-future-city-1-10-2.jpg](./docs-assets/singleplayer-future-city-1-10-2.jpg) + ### Servers -You can play almost on any server, supporting offline connections. +You can play almost on any Java server, vanilla servers are fully supported. See the [Mineflayer](https://github.com/PrismarineJS/mineflayer) repo for the list of supported versions (should support majority of versions). -There is a builtin proxy, but you can also host a your one! Just clone the repo, run `pnpm i` (following CONTRIBUTING.MD) and run `pnpm prod-start`, then you can specify `http://localhost:8080` in the proxy field. +There is a builtin proxy, but you can also host your one! Just clone the repo, run `pnpm i` (following CONTRIBUTING.MD) and run `pnpm prod-start`, then you can specify `http://localhost:8080` in the proxy field. MS account authentication will be supported soon. ### Rendering @@ -46,8 +49,9 @@ MS account authentication will be supported soon. #### Three.js Renderer - Uses WebGL2. Chunks are rendered using Geometry Buffers prepared by 4 mesher workers. -- Supports FXAA -- Doesn't support culling +- Entities & text rendering +- Supports resource packs +- Doesn't support occlusion culling @@ -63,9 +67,9 @@ There are many many settings, that are not exposed in the UI yet. You can find o To open the console, press `F12`, or if you are on mobile, you can type `#debug` in the URL (browser address bar), it wont't reload the page, but you will see a button to open the console. This way you can change advanced settings and see all errors or warnings. Also this way you can access global variables (described below). -### Debugging +### Development & Debugging -It should be easy to build/start the project locally. See [CONTRIBUTING.MD](./CONTRIBUTING.md) for more info. +It should be easy to build/start the project locally. See [CONTRIBUTING.MD](./CONTRIBUTING.md) for more info. Also you can look at Dockerfile for reference. There is world renderer playground ([link](https://mcon.vercel.app/playground.html)). @@ -109,10 +113,10 @@ Press `Y` to set query parameters to url of your current game state. - `?version=` - Set the version for server - `?lockConnect=true` - Disable cancel / save buttons, useful for integrates iframes - `?reconnect=true` - Reconnect to the server on page reloads. Available in **dev mode only** and very useful on server testing. - - `?loadSave=` - Load the save on load with the specified folder name (not title) - `?singleplayer=1` - Create empty world on load. Nothing will be saved - `?noSave=true` - Disable auto save on unload / disconnect / export. Only manual save with `/save` command will work + - `?map=` - Load the map from ZIP. You can use any url, but it must be CORS enabled. - `?setting=:` - Set the and lock the setting on load. You can set multiple settings by separating them with `&` e.g. `?setting=autoParkour:true&setting=renderDistance:4` @@ -120,9 +124,13 @@ Press `Y` to set query parameters to url of your current game state. ### Notable Things that Power this Project - [Mineflayer](https://github.com/PrismarineJS/mineflayer) - Handles all client-side communications with the server (including the builtin one) - forked -- [Flying Squid](https://github.com/prismarineJS/flying-squid) - The builtin server that makes single player possible! Here forked version is used. +- [Forked Flying Squid (Space Squid)](https://github.com/zardoy/space-squid) - The builtin offline server that makes single player & P2P possible! - [Prismarine Provider Anvil](https://github.com/PrismarineJS/prismarine-provider-anvil) - Handles world loading (region format) - [Prismarine Physics](https://github.com/PrismarineJS/prismarine-physics) - Does all the physics calculations - [Minecraft Protocol](https://github.com/PrismarineJS/node-minecraft-protocol) - Makes connections to servers possible - [Peer.js](https://peerjs.com/) - P2P networking (when you open to wan) - [Three.js](https://threejs.org/) - Helping in 3D rendering + +### Alternatives + +- [https://github.com/ClassiCube/ClassiCube](ClassiCube - Better C# Rewrite) [DEMO](https://www.classicube.net/server/play/?warned=true) diff --git a/README.NPM.MD b/README.NPM.MD index 24c90bc9..c44492c6 100644 --- a/README.NPM.MD +++ b/README.NPM.MD @@ -1,9 +1,13 @@ # Minecraft React +Minecraft UI components for React. + ```bash -yarn add minecraft-react +pnpm i minecraft-react ``` +![demo](https://github-production-user-asset-6210df.s3.amazonaws.com/46503702/346295584-80f3ed4a-cab6-45d2-8896-5e20233cc9b1.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAVCODYLSA53PQK4ZA%2F20240706%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20240706T195400Z&X-Amz-Expires=300&X-Amz-Signature=5b063823a57057c4042c15edd1db3edd107e00940fd0e66a2ba1df4e564a2809&X-Amz-SignedHeaders=host&actor_id=46503702&key_id=0&repo_id=432411890) + ## Usage ```jsx @@ -24,7 +28,7 @@ const App = () => { } ``` -See [Storybook](https://mcraft.fun/storybook/) or [Storybook (Mirror link)](https://mcon.vercel.app/storybook/) for more examples and full components list. Also take a look at the full [standalone example](https://github.com/zardoy/prismarine-web-client/tree/experiments/UiStandaloneExample.tsx). +See [Storybook](https://mcraft.fun/storybook/) or [Storybook (Mirror link)](https://mcon.vercel.app/storybook/) for more examples and full components list. Also take a look at the full [standalone example](https://github.com/zardoy/minecraft-web-client/tree/experiments/UiStandaloneExample.tsx). There are two types of components: diff --git a/assets/extra-textures/loading.png b/assets/extra-textures/loading.png deleted file mode 100644 index 4f6a121a..00000000 Binary files a/assets/extra-textures/loading.png and /dev/null differ diff --git a/assets/favicon.png b/assets/favicon.png index 4f0db721..046cacd0 100644 Binary files a/assets/favicon.png and b/assets/favicon.png differ diff --git a/config.json b/config.json index 1bbbfd47..e4f86060 100644 --- a/config.json +++ b/config.json @@ -15,9 +15,9 @@ "description": "One of the best servers here. Join now!" }, { - "ip": "play.minemalia.com", + "ip": "sus.shhnowisnottheti.me", "version": "1.18.2", - "description": "Only login with existing accounts." + "description": "Creative, your own 'boxes' (islands)" } ] } diff --git a/cypress/e2e/index.spec.ts b/cypress/e2e/index.spec.ts index 16425366..b0d7118b 100644 --- a/cypress/e2e/index.spec.ts +++ b/cypress/e2e/index.spec.ts @@ -1,4 +1,6 @@ +/* eslint-disable max-nested-callbacks */ /// +import supportedVersions from '../../src/supportedVersions.mjs' import { setOptions, cleanVisit, visit } from './shared' // todo use ssl @@ -12,7 +14,7 @@ const compareRenderedFlatWorld = () => { } const testWorldLoad = () => { - cy.document().then({ timeout: 20_000 }, doc => { + return cy.document().then({ timeout: 25_000 }, doc => { return new Cypress.Promise(resolve => { doc.addEventListener('cypress-world-ready', resolve) }) @@ -22,7 +24,7 @@ const testWorldLoad = () => { } it('Loads & renders singleplayer', () => { - visit('/?singleplayer=1') + cleanVisit('/?singleplayer=1') setOptions({ localServerOptions: { generation: { @@ -36,7 +38,7 @@ it('Loads & renders singleplayer', () => { testWorldLoad() }) -it('Joins to server', () => { +it('Joins to local flying-squid server', () => { visit('/?ip=localhost&version=1.16.1') window.localStorage.version = '' // todo replace with data-test @@ -47,6 +49,45 @@ it('Joins to server', () => { testWorldLoad() }) +it('Joins to local latest Java vanilla server', () => { + const version = supportedVersions.at(-1)! + cy.task('startServer', [version, 25_590]).then(() => { + visit('/?ip=localhost:25590&username=bot') + cy.get('[data-test-id="connect-qs"]').click() + testWorldLoad().then(() => { + let x = 0 + let z = 0 + cy.window().then((win) => { + x = win.bot.entity.position.x + z = win.bot.entity.position.z + }) + cy.document().trigger('keydown', { code: 'KeyW' }) + cy.wait(1500).then(() => { + cy.document().trigger('keyup', { code: 'KeyW' }) + cy.window().then(async (win) => { + // eslint-disable-next-line prefer-destructuring + const bot: typeof __type_bot = win.bot + // todo use f3 stats instead + if (bot.entity.position.x === x && bot.entity.position.z === z) { + throw new Error('Player not moved') + } + + bot.chat('Hello') // todo assert + bot.chat('/gamemode creative') + // bot.on('message', () => { + void bot.creative.setInventorySlot(bot.inventory.hotbarStart, new win.PrismarineItem(1, 1, 0)) + // }) + await bot.lookAt(bot.entity.position.offset(1, 0, 1)) + }).then(() => { + cy.document().trigger('mousedown', { button: 2, isTrusted: true, force: true }) // right click + cy.document().trigger('mouseup', { button: 2, isTrusted: true, force: true }) + cy.wait(1000) + }) + }) + }) + }) +}) + it('Loads & renders zip world', () => { cleanVisit() cy.get('[data-test-id="select-file-folder"]').click({ shiftKey: true }) @@ -54,9 +95,14 @@ it('Loads & renders zip world', () => { testWorldLoad() }) -it.skip('Performance test', () => { - // select that world - // from -2 85 24 - // await bot.loadPlugin(pathfinder.pathfinder) - // bot.pathfinder.goto(new pathfinder.goals.GoalXZ(28, -28)) + +it.skip('Loads & renders world from folder', () => { + cleanVisit() + // dragndrop folder + cy.get('[data-test-id="select-file-folder"]').click() + cy.get('input[type="file"]').selectFile('server-jar/world', { + force: true, + // action: 'drag-drop', + }) + testWorldLoad() }) diff --git a/cypress/e2e/shared.ts b/cypress/e2e/shared.ts index 9292a8d5..47518f1b 100644 --- a/cypress/e2e/shared.ts +++ b/cypress/e2e/shared.ts @@ -3,6 +3,9 @@ import { AppOptions } from '../../src/optionsStorage' export const cleanVisit = (url?) => { cy.clearLocalStorage() visit(url) + window.localStorage.options = { + chatOpacity: 0 + } } export const visit = (url = '/') => { window.localStorage.cypress = 'true' diff --git a/cypress/minecraft-server.mjs b/cypress/minecraft-server.mjs index 18f4e3db..32be0c9d 100644 --- a/cypress/minecraft-server.mjs +++ b/cypress/minecraft-server.mjs @@ -2,6 +2,7 @@ import mcServer from 'flying-squid' import defaultOptions from 'flying-squid/config/default-settings.json' assert { type: 'json' } +/** @type {Options} */ const serverOptions = { ...defaultOptions, 'online-mode': false, diff --git a/cypress/plugins/index.js b/cypress/plugins/index.js index 35dc2989..e55f5d26 100644 --- a/cypress/plugins/index.js +++ b/cypress/plugins/index.js @@ -2,11 +2,13 @@ const { cypressEsbuildPreprocessor } = require('cypress-esbuild-preprocessor') const { initPlugin } = require('cypress-plugin-snapshots/plugin') const polyfill = require('esbuild-plugin-polyfill-node') +const { startMinecraftServer } = require('./startServer') module.exports = (on, config) => { initPlugin(on, config) on('file:preprocessor', cypressEsbuildPreprocessor({ esbuildOptions: { + sourcemap: true, plugins: [ polyfill.polyfillNode({ polyfills: { @@ -17,10 +19,15 @@ module.exports = (on, config) => { }, })) on('task', { - log (message) { + log(message) { console.log(message) return null }, }) + on('task', { + async startServer([version, port]) { + return startMinecraftServer(version, port) + } + }) return config } diff --git a/cypress/plugins/ops.json b/cypress/plugins/ops.json new file mode 100644 index 00000000..ade5aaa5 --- /dev/null +++ b/cypress/plugins/ops.json @@ -0,0 +1,8 @@ +[ + { + "uuid": "67128b5b-2e6b-3ad1-baa0-1b937b03e5c5", + "name": "bot", + "level": 4, + "bypassesPlayerLimit": false + } +] diff --git a/cypress/plugins/server.properties b/cypress/plugins/server.properties new file mode 100644 index 00000000..5873a1aa --- /dev/null +++ b/cypress/plugins/server.properties @@ -0,0 +1,61 @@ +#Minecraft server properties +allow-flight=false +allow-nether=true +broadcast-console-to-ops=true +broadcast-rcon-to-ops=true +difficulty=peaceful +enable-command-block=false +enable-jmx-monitoring=false +enable-query=false +enable-rcon=false +enable-status=true +enforce-secure-profile=true +enforce-whitelist=false +entity-broadcast-range-percentage=100 +force-gamemode=false +function-permission-level=2 +gamemode=survival +generate-structures=true +generator-settings={} +hardcore=false +hide-online-players=false +initial-disabled-packs= +initial-enabled-packs=vanilla +level-name=world +level-seed= +level-type=flat +log-ips=true +max-build-height=256 +max-chained-neighbor-updates=1000000 +max-players=20 +max-tick-time=60000 +max-world-size=29999984 +motd=A Minecraft Server +network-compression-threshold=256 +online-mode=false +op-permission-level=4 +player-idle-timeout=0 +prevent-proxy-connections=false +pvp=true +query.port=25565 +rate-limit=0 +rcon.password= +rcon.port=25575 +require-resource-pack=false +resource-pack= +resource-pack-id= +resource-pack-prompt= +resource-pack-sha1= +server-ip= +server-port=25565 +simulation-distance=10 +snooper-enabled=true +spawn-animals=true +spawn-monsters=true +spawn-npcs=true +spawn-protection=16 +sync-chunk-writes=true +text-filtering-config= +use-native-transport=true +view-distance=10 +white-list=false diff --git a/cypress/plugins/startServer.ts b/cypress/plugins/startServer.ts new file mode 100644 index 00000000..ecf0d210 --- /dev/null +++ b/cypress/plugins/startServer.ts @@ -0,0 +1,45 @@ +import { ChildProcess, spawn } from 'child_process' +import * as fs from 'fs' +import * as path from 'path' +import { promisify } from 'util' +import { downloadServer } from 'minecraft-wrap' +import * as waitOn from 'wait-on' + +let prevProcess: ChildProcess | null = null +export const startMinecraftServer = async (version: string, port: number) => { + if (prevProcess) return null + const jar = `./server-jar/${version}.jar` + + const start = () => { + // if (prevProcess) { + // prevProcess.kill() + // } + + prevProcess = spawn('java', ['-jar', path.basename(jar), 'nogui', '--port', `${port}`], { + stdio: 'inherit', + cwd: path.dirname(jar), + }) + } + + let coldStart = false + if (fs.existsSync(jar)) { + start() + } else { + coldStart = true + promisify(downloadServer)(version, jar).then(() => { + // add eula.txt + fs.writeFileSync(path.join(path.dirname(jar), 'eula.txt'), 'eula=true') + // copy cypress/plugins/server.properties + fs.copyFileSync(path.join(__dirname, 'server.properties'), path.join(path.dirname(jar), 'server.properties')) + // copy ops.json + fs.copyFileSync(path.join(__dirname, 'ops.json'), path.join(path.dirname(jar), 'ops.json')) + start() + }) + } + + return new Promise((res) => { + waitOn({ resources: [`tcp:localhost:${port}`] }, () => { + setTimeout(() => res(null), coldStart ? 6500 : 2000) // todo retry instead of timeout + }) + }) +} diff --git a/docs-assets/singleplayer-future-city-1-10-2.jpg b/docs-assets/singleplayer-future-city-1-10-2.jpg new file mode 100644 index 00000000..e5be2ada Binary files /dev/null and b/docs-assets/singleplayer-future-city-1-10-2.jpg differ diff --git a/esbuild.mjs b/esbuild.mjs index 8cb7b4bd..e7a964b8 100644 --- a/esbuild.mjs +++ b/esbuild.mjs @@ -60,6 +60,7 @@ const buildOptions = { net: 'net-browserify', assert: 'assert', dns: './src/dns.js', + 'yggdrasil': './src/yggdrasilReplacement.ts', // todo write advancedAliases plugin }, inject: [ @@ -79,11 +80,15 @@ const buildOptions = { loader: { // todo use external or resolve issues with duplicating '.png': 'dataurl', + '.svg': 'dataurl', '.map': 'empty', '.vert': 'text', '.frag': 'text', '.wgsl': 'text', '.obj': 'text', + '.woff': 'dataurl', + '.woff2': 'dataurl', + '.ttf': 'dataurl', }, write: false, // todo would be better to enable? diff --git a/index.html b/index.html index 3e921855..62e109cd 100644 --- a/index.html +++ b/index.html @@ -14,7 +14,7 @@
Loading...
-
A true Minecraft client in your browser!
+
A true Minecraft client in your browser!
` @@ -94,9 +94,7 @@
-
- -
+
diff --git a/package.json b/package.json index a6a9db66..157be7ea 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "prismarine-web-client", + "name": "minecraft-web-client", "version": "0.0.0-dev", "description": "A minecraft client running in a browser", "scripts": { @@ -50,7 +50,7 @@ "@types/wicg-file-system-access": "^2023.10.2", "@webgpu/types": "^0.1.40", "@xmcl/text-component": "^2.1.3", - "@zardoy/react-util": "^0.2.0", + "@zardoy/react-util": "^0.2.4", "@zardoy/utils": "^0.0.11", "adm-zip": "^0.5.12", "browserfs": "github:zardoy/browserfs#build", @@ -65,21 +65,21 @@ "esbuild-plugin-polyfill-node": "^0.3.0", "express": "^4.18.2", "filesize": "^10.0.12", - "flying-squid": "npm:@zardoy/flying-squid@^0.0.20", + "flying-squid": "npm:@zardoy/flying-squid@^0.0.33", "fs-extra": "^11.1.1", "google-drive-browserfs": "github:zardoy/browserfs#google-drive", - "iconify-icon": "^1.0.8", "jszip": "^3.10.1", "lodash-es": "^4.17.21", "math.gl": "^4.0.0", "minecraft-assets": "^1.12.2", "minecraft-data": "3.65.0", - "minecraft-protocol": "github:PrismarineJS/node-minecraft-protocol", + "minecraft-protocol": "github:PrismarineJS/node-minecraft-protocol#master", "mineflayer-item-map-downloader": "github:zardoy/mineflayer-item-map-downloader", "mojangson": "^2.0.4", "net-browserify": "github:zardoy/prismarinejs-net-browserify", "node-gzip": "^1.1.2", "peerjs": "^1.5.0", + "pixelarticons": "^1.8.1", "pretty-bytes": "^6.1.1", "prismarine-provider-anvil": "github:zardoy/prismarine-provider-anvil#everything", "prosemirror-example-setup": "^1.2.2", @@ -104,6 +104,7 @@ "use-typed-event-listener": "^4.0.2", "valtio": "^1.11.1", "vec3": "^0.1.7", + "wait-on": "^7.2.0", "workbox-build": "^7.0.0" }, "devDependencies": { @@ -117,12 +118,13 @@ "@types/stats.js": "^0.17.1", "@types/three": "0.154.0", "@types/ua-parser-js": "^0.7.39", + "@types/wait-on": "^5.3.4", "@xmcl/installer": "^5.1.0", "assert": "^2.0.0", "browserify-zlib": "^0.2.0", "buffer": "^6.0.3", "constants-browserify": "^1.0.0", - "contro-max": "^0.1.6", + "contro-max": "^0.1.8", "crypto-browserify": "^3.12.0", "cypress": "^10.11.0", "cypress-esbuild-preprocessor": "^1.0.2", @@ -133,7 +135,7 @@ "http-server": "^14.1.1", "https-browserify": "^1.0.0", "minecraft-inventory-gui": "github:zardoy/minecraft-inventory-gui#next", - "mineflayer": "github:PrismarineJS/mineflayer", + "mineflayer": "github:zardoy/mineflayer", "mineflayer-pathfinder": "^2.4.4", "npm-run-all": "^4.1.5", "os-browserify": "^0.3.0", @@ -162,7 +164,7 @@ "prismarine-world": "github:zardoy/prismarine-world#next-era", "minecraft-data": "3.65.0", "prismarine-provider-anvil": "github:zardoy/prismarine-provider-anvil#everything", - "minecraft-protocol": "github:PrismarineJS/node-minecraft-protocol", + "minecraft-protocol": "github:PrismarineJS/node-minecraft-protocol#master", "react": "^18.2.0", "prismarine-chunk": "github:zardoy/prismarine-chunk" }, @@ -170,7 +172,9 @@ "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", + "pixelarticons@1.8.1": "patches/pixelarticons@1.8.1.patch" } }, "packageManager": "pnpm@9.0.4" diff --git a/package.npm.json b/package.npm.json index bae8b60f..7e13d67b 100644 --- a/package.npm.json +++ b/package.npm.json @@ -26,7 +26,7 @@ }, "module": "./dist/react/npmReactComponents.js", "types": "./dist/react/npmReactComponents.d.ts", - "repository": "zardoy/prismarine-web-client", + "repository": "zardoy/minecraft-web-client", "version": "0.0.0-dev", "dependencies": {}, "peerDependencies": { diff --git a/patches/minecraft-protocol@1.47.0.patch b/patches/minecraft-protocol@1.47.0.patch index 02bbdd5d..d03a38f0 100644 --- a/patches/minecraft-protocol@1.47.0.patch +++ b/patches/minecraft-protocol@1.47.0.patch @@ -12,12 +12,12 @@ index c437ecf3a0e4ab5758a48538c714b7e9651bb5da..d9c9895ae8614550aa09ad60a396ac32 debug('ping response', response) // TODO: could also use ping pre-connect to save description, type, max players, etc. @@ -40,6 +40,7 @@ module.exports = function (client, options) { - + // Reinitialize client object with new version TODO: move out of its constructor? client.version = minecraftVersion + await options.versionSelectedHook?.(client) client.state = states.HANDSHAKING - + // Let other plugins such as Forge/FML (modinfo) respond to the ping response diff --git a/src/client/encrypt.js b/src/client/encrypt.js index b9d21bab9faccd5dbf1975fc423fc55c73e906c5..99ffd76527b410e3a393181beb260108f4c63536 100644 @@ -34,7 +34,7 @@ index b9d21bab9faccd5dbf1975fc423fc55c73e906c5..99ffd76527b410e3a393181beb260108 + // clearTimeout(loginTimeout) + // }) } - + function onJoinServerResponse (err) { diff --git a/src/client.js b/src/client.js index c89375e32babbf3559655b1e95f6441b9a30796f..f24cd5dc8fa9a0a4000b184fb3c79590a3ad8b8a 100644 @@ -74,7 +74,7 @@ index c89375e32babbf3559655b1e95f6441b9a30796f..f24cd5dc8fa9a0a4000b184fb3c79590 } @@ -166,7 +174,10 @@ class Client extends EventEmitter { } - + const onFatalError = (err) => { - this.emit('error', err) + // todo find out what is trying to write after client disconnect @@ -83,7 +83,7 @@ index c89375e32babbf3559655b1e95f6441b9a30796f..f24cd5dc8fa9a0a4000b184fb3c79590 + } endSocket() } - + @@ -195,6 +206,8 @@ class Client extends EventEmitter { serializer -> framer -> socket -> splitter -> deserializer */ if (this.serializer) { @@ -94,7 +94,7 @@ index c89375e32babbf3559655b1e95f6441b9a30796f..f24cd5dc8fa9a0a4000b184fb3c79590 if (this.socket) this.socket.end() } @@ -236,8 +249,11 @@ class Client extends EventEmitter { - + write (name, params) { if (!this.serializer.writable) { return } - debug('writing packet ' + this.state + '.' + name) @@ -106,7 +106,7 @@ index c89375e32babbf3559655b1e95f6441b9a30796f..f24cd5dc8fa9a0a4000b184fb3c79590 + this.emit('writePacket', name, params) this.serializer.write({ name, params }) } - + diff --git a/src/index.d.ts b/src/index.d.ts index 0a5821c32d735e11205a280aa5a503c13533dc14..94a49f661d922478b940d853169b6087e6ec3df5 100644 --- a/src/index.d.ts @@ -126,5 +126,63 @@ index 0a5821c32d735e11205a280aa5a503c13533dc14..94a49f661d922478b940d853169b6087 + /** Can be used to prepare mc data on autoVersion (client.version has selected version) */ + versionSelectedHook?: (client: Client) => Promise | void } - + export class Server extends EventEmitter { +diff --git a/src/client/chat.js b/src/client/chat.js +index 5cad9954db13d7121ed0a03792c2304156cdf436..ffd7c7d6299ef54854e0923f8d5296bf2a58956b 100644 +--- a/src/client/chat.js ++++ b/src/client/chat.js +@@ -111,7 +111,7 @@ module.exports = function (client, options) { + for (const player of packet.data) { + if (!player.chatSession) continue + client._players[player.UUID] = { +- publicKey: crypto.createPublicKey({ key: player.chatSession.publicKey.keyBytes, format: 'der', type: 'spki' }), ++ // publicKey: crypto.createPublicKey({ key: player.chatSession.publicKey.keyBytes, format: 'der', type: 'spki' }), + publicKeyDER: player.chatSession.publicKey.keyBytes, + sessionUuid: player.chatSession.uuid + } +@@ -127,7 +127,7 @@ module.exports = function (client, options) { + for (const player of packet.data) { + if (player.crypto) { + client._players[player.UUID] = { +- publicKey: crypto.createPublicKey({ key: player.crypto.publicKey, format: 'der', type: 'spki' }), ++ // publicKey: crypto.createPublicKey({ key: player.crypto.publicKey, format: 'der', type: 'spki' }), + publicKeyDER: player.crypto.publicKey, + signature: player.crypto.signature, + displayName: player.displayName || player.name +@@ -198,7 +198,7 @@ module.exports = function (client, options) { + if (mcData.supportFeature('useChatSessions')) { + const tsDelta = BigInt(Date.now()) - packet.timestamp + const expired = !packet.timestamp || tsDelta > messageExpireTime || tsDelta < 0 +- const verified = !packet.unsignedChatContent && updateAndValidateSession(packet.senderUuid, packet.plainMessage, packet.signature, packet.index, packet.previousMessages, packet.salt, packet.timestamp) && !expired ++ const verified = false && !packet.unsignedChatContent && updateAndValidateSession(packet.senderUuid, packet.plainMessage, packet.signature, packet.index, packet.previousMessages, packet.salt, packet.timestamp) && !expired + if (verified) client._signatureCache.push(packet.signature) + client.emit('playerChat', { + plainMessage: packet.plainMessage, +@@ -363,7 +363,7 @@ module.exports = function (client, options) { + } + } + +- client._signedChat = (message, options = {}) => { ++ client._signedChat = async (message, options = {}) => { + options.timestamp = options.timestamp || BigInt(Date.now()) + options.salt = options.salt || 1n + +@@ -404,7 +404,7 @@ module.exports = function (client, options) { + message, + timestamp: options.timestamp, + salt: options.salt, +- signature: (client.profileKeys && client._session) ? client.signMessage(message, options.timestamp, options.salt, undefined, acknowledgements) : undefined, ++ signature: (client.profileKeys && client._session) ? await client.signMessage(message, options.timestamp, options.salt, undefined, acknowledgements) : undefined, + offset: client._lastSeenMessages.pending, + acknowledged + }) +@@ -418,7 +418,7 @@ module.exports = function (client, options) { + message, + timestamp: options.timestamp, + salt: options.salt, +- signature: client.profileKeys ? client.signMessage(message, options.timestamp, options.salt, options.preview) : Buffer.alloc(0), ++ signature: client.profileKeys ? await client.signMessage(message, options.timestamp, options.salt, options.preview) : Buffer.alloc(0), + signedPreview: options.didPreview, + previousMessages: client._lastSeenMessages.map((e) => ({ + messageSender: e.sender, diff --git a/patches/pixelarticons@1.8.1.patch b/patches/pixelarticons@1.8.1.patch new file mode 100644 index 00000000..10044536 --- /dev/null +++ b/patches/pixelarticons@1.8.1.patch @@ -0,0 +1,27 @@ +diff --git a/fonts/pixelart-icons-font.css b/fonts/pixelart-icons-font.css +index 3b2ebe839370d96bf93ef5ca94a827f07e49378d..103ab4d6b9f3b5c9f41d1407e3cbf4ac392fbf41 100644 +--- a/fonts/pixelart-icons-font.css ++++ b/fonts/pixelart-icons-font.css +@@ -1,16 +1,13 @@ + @font-face { + font-family: "pixelart-icons-font"; +- src: url('pixelart-icons-font.eot?t=1711815892278'); /* IE9*/ +- src: url('pixelart-icons-font.eot?t=1711815892278#iefix') format('embedded-opentype'), /* IE6-IE8 */ ++ src: + url("pixelart-icons-font.woff2?t=1711815892278") format("woff2"), + url("pixelart-icons-font.woff?t=1711815892278") format("woff"), + url('pixelart-icons-font.ttf?t=1711815892278') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/ +- url('pixelart-icons-font.svg?t=1711815892278#pixelart-icons-font') format('svg'); /* iOS 4.1- */ + } + + [class^="pixelart-icons-font-"], [class*=" pixelart-icons-font-"] { + font-family: 'pixelart-icons-font' !important; +- font-size:24px; + font-style:normal; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +@@ -503,4 +500,3 @@ + .pixelart-icons-font-zap:before { content: "\ebe4"; } + .pixelart-icons-font-zoom-in:before { content: "\ebe5"; } + .pixelart-icons-font-zoom-out:before { content: "\ebe6"; } +- diff --git a/patches/three@0.154.0.patch b/patches/three@0.154.0.patch new file mode 100644 index 00000000..e612415c --- /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 699bfb19..6d677dc9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,14 +12,20 @@ overrides: prismarine-world: github:zardoy/prismarine-world#next-era minecraft-data: 3.65.0 prismarine-provider-anvil: github:zardoy/prismarine-provider-anvil#everything - minecraft-protocol: github:PrismarineJS/node-minecraft-protocol + minecraft-protocol: github:PrismarineJS/node-minecraft-protocol#master react: ^18.2.0 prismarine-chunk: github:zardoy/prismarine-chunk patchedDependencies: minecraft-protocol@1.47.0: - hash: 2uxevyasyasdavsxuehfavgkjq + hash: 7otpchsbv7hxsuis4rrrwdtbve path: patches/minecraft-protocol@1.47.0.patch + pixelarticons@1.8.1: + hash: cclg2qo6d4yjs4evj64nr2mbwa + path: patches/pixelarticons@1.8.1.patch + three@0.154.0: + hash: sj7ocb4p23jym6bkfgueanti2e + path: patches/three@0.154.0.patch importers: @@ -62,8 +68,8 @@ importers: specifier: ^2.1.3 version: 2.1.3 '@zardoy/react-util': - specifier: ^0.2.0 - version: 0.2.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0) + specifier: ^0.2.4 + version: 0.2.4(react-dom@18.2.0(react@18.2.0))(react@18.2.0) '@zardoy/utils': specifier: ^0.0.11 version: 0.0.11 @@ -107,17 +113,14 @@ importers: specifier: ^10.0.12 version: 10.0.12 flying-squid: - specifier: npm:@zardoy/flying-squid@^0.0.20 - version: '@zardoy/flying-squid@0.0.20(encoding@0.1.13)' + specifier: npm:@zardoy/flying-squid@^0.0.33 + version: '@zardoy/flying-squid@0.0.33(encoding@0.1.13)' fs-extra: specifier: ^11.1.1 version: 11.1.1 google-drive-browserfs: specifier: github:zardoy/browserfs#google-drive version: browserfs@https://codeload.github.com/zardoy/browserfs/tar.gz/ab58ae8ef00e3a31db01909e365e6cb5188436e0 - iconify-icon: - specifier: ^1.0.8 - version: 1.0.8 jszip: specifier: ^3.10.1 version: 3.10.1 @@ -134,8 +137,8 @@ importers: specifier: 3.65.0 version: 3.65.0 minecraft-protocol: - specifier: github:PrismarineJS/node-minecraft-protocol - version: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/495eed56ab230b2615596590064671356d86a2dc(patch_hash=2uxevyasyasdavsxuehfavgkjq)(encoding@0.1.13) + specifier: github:PrismarineJS/node-minecraft-protocol#master + version: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/495eed56ab230b2615596590064671356d86a2dc(patch_hash=7otpchsbv7hxsuis4rrrwdtbve)(encoding@0.1.13) mineflayer-item-map-downloader: specifier: github:zardoy/mineflayer-item-map-downloader version: https://codeload.github.com/zardoy/mineflayer-item-map-downloader/tar.gz/642fd4f7023a98a96da4caf8f993f8e19361a1e7(encoding@0.1.13) @@ -144,19 +147,22 @@ importers: version: 2.0.4 net-browserify: specifier: github:zardoy/prismarinejs-net-browserify - version: https://codeload.github.com/zardoy/prismarinejs-net-browserify/tar.gz/7d827dba61bd2f9ac9a6086fe2079a0fccadd070 + version: https://codeload.github.com/zardoy/prismarinejs-net-browserify/tar.gz/231ef1737a078e8cb51e8c695b48d7b43ce3bc53 node-gzip: specifier: ^1.1.2 version: 1.1.2 peerjs: specifier: ^1.5.0 version: 1.5.0 + pixelarticons: + specifier: ^1.8.1 + version: 1.8.1(patch_hash=cclg2qo6d4yjs4evj64nr2mbwa) pretty-bytes: specifier: ^6.1.1 version: 6.1.1 prismarine-provider-anvil: specifier: github:zardoy/prismarine-provider-anvil#everything - version: https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/02d81b0eba6ab1c362862970954f9a3c150c9a29(minecraft-data@3.65.0) + version: https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/0228b5252f48a0d6ad7f36d7189851c427fbe8c4(minecraft-data@3.65.0) prosemirror-example-setup: specifier: ^1.2.2 version: 1.2.2 @@ -223,6 +229,9 @@ importers: vec3: specifier: ^0.1.7 version: 0.1.8 + wait-on: + specifier: ^7.2.0 + version: 7.2.0(debug@4.3.4) workbox-build: specifier: ^7.0.0 version: 7.0.0(@types/babel__core@7.20.2) @@ -261,6 +270,9 @@ importers: '@types/ua-parser-js': specifier: ^0.7.39 version: 0.7.39 + '@types/wait-on': + specifier: ^5.3.4 + version: 5.3.4 '@xmcl/installer': specifier: ^5.1.0 version: 5.1.0 @@ -277,7 +289,7 @@ importers: specifier: ^1.0.0 version: 1.0.0 contro-max: - specifier: ^0.1.6 + specifier: ^0.1.8 version: 0.1.8(typescript@5.5.0-beta) crypto-browserify: specifier: ^3.12.0 @@ -308,10 +320,10 @@ 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/5554c7ab0a74bce52aa5f5f04a48eb8d3b9ac65c(@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:PrismarineJS/mineflayer - version: https://codeload.github.com/PrismarineJS/mineflayer/tar.gz/ec76468c8ac4c6232bad3c2b66d4160f95f58396(encoding@0.1.13) + specifier: github:zardoy/mineflayer + version: https://codeload.github.com/zardoy/mineflayer/tar.gz/dddc683544317f117172077a9245a07be1b12479(encoding@0.1.13) mineflayer-pathfinder: specifier: ^2.4.4 version: 2.4.4 @@ -344,7 +356,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 @@ -395,10 +407,10 @@ importers: version: 1.3.6 prismarine-block: specifier: github:zardoy/prismarine-block#next-era - version: https://codeload.github.com/zardoy/prismarine-block/tar.gz/ada4ec3fdfbbc1cc20ab01d0e23f0718a77cc1a0 + version: https://codeload.github.com/zardoy/prismarine-block/tar.gz/dd4954fff3b334f8ce063d18e39b2e9414ece5b8 prismarine-chunk: specifier: github:zardoy/prismarine-chunk - version: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/eb39a905761a36f733a456110e6b49d655bf5c16(minecraft-data@3.65.0) + version: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/9662306deea57d8d0ba0a2a3f3f7adb95f0131e3(minecraft-data@3.65.0) prismarine-schematic: specifier: ^1.2.0 version: 1.2.3 @@ -416,7 +428,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 @@ -1718,6 +1730,12 @@ packages: '@gar/promisify@1.1.3': resolution: {integrity: sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==} + '@hapi/hoek@9.3.0': + resolution: {integrity: sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==} + + '@hapi/topo@5.1.0': + resolution: {integrity: sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==} + '@humanwhocodes/config-array@0.11.11': resolution: {integrity: sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==} engines: {node: '>=10.10.0'} @@ -1729,9 +1747,6 @@ packages: '@humanwhocodes/object-schema@1.2.1': resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==} - '@iconify/types@2.0.0': - resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==} - '@isaacs/cliui@8.0.2': resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} @@ -2405,6 +2420,15 @@ packages: '@rushstack/eslint-patch@1.4.0': resolution: {integrity: sha512-cEjvTPU32OM9lUFegJagO0mRnIn+rbqrG89vV8/xLnLFX0DoR0r1oy5IlTga71Q7uT3Qus7qm7wgeiMT/+Irlg==} + '@sideway/address@4.1.5': + resolution: {integrity: sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==} + + '@sideway/formula@3.0.1': + resolution: {integrity: sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==} + + '@sideway/pinpoint@2.0.0': + resolution: {integrity: sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==} + '@sinclair/typebox@0.27.8': resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} @@ -2811,15 +2835,12 @@ packages: '@types/node@16.18.58': resolution: {integrity: sha512-YGncyA25/MaVtQkjWW9r0EFBukZ+JulsLcVZBlGUfIb96OBMjkoRWwQo5IEWJ8Fj06Go3GHw+bjYDitv6BaGsA==} - '@types/node@20.11.19': - resolution: {integrity: sha512-7xMnVEcZFu0DikYjWOlRq7NTPETrm7teqUT2WkQjrTIkEgUyyGdWsj/Zg8bEJt5TNklzbPD1X3fqfsHw3SpapQ==} + '@types/node@20.12.8': + resolution: {integrity: sha512-NU0rJLJnshZWdE/097cdCBbyW1h4hEg0xpovcoAQYHl8dnEyp/NAOiE45pvc+Bd1Dt+2r94v2eGFpQJ4R7g+2w==} '@types/node@20.8.0': resolution: {integrity: sha512-LzcWltT83s1bthcvjBmiBvGJiiUe84NWRHkw+ZV6Fr41z2FbIzvc815dk2nQ3RAKMuN2fkenM/z3Xv2QzEpYxQ==} - '@types/node@20.8.10': - resolution: {integrity: sha512-TlgT8JntpcbmKUFzjhsyhGfP2fsiz1Mv56im6enJ905xG1DAYesxJaeSbGqQmAw8OWPdhyJGhGSQGKRNJ45u9w==} - '@types/normalize-package-data@2.4.2': resolution: {integrity: sha512-lqa4UEhhv/2sjjIQgjX8B+RBjj47eo0mzGasklVJ78UKGQY1r0VpB9XHDaZZO9qzEFDdy4MrXLuEaSmPrPSe/A==} @@ -2904,6 +2925,9 @@ packages: '@types/unist@3.0.2': resolution: {integrity: sha512-dqId9J8K/vGi5Zr7oo212BGii5m3q5Hxlkwy3WpYuKPklmBEvsbMYYyLxAQpSffdLl/gdW0XUpKWFvYmyoWCoQ==} + '@types/wait-on@5.3.4': + resolution: {integrity: sha512-EBsPjFMrFlMbbUFf9D1Fp+PAB2TwmUn7a3YtHyD9RLuTIk1jDd8SxXVAoez2Ciy+8Jsceo2MYEYZzJ/DvorOKw==} + '@types/webxr@0.5.7': resolution: {integrity: sha512-Rcgs5c2eNFnHp53YOjgtKfl/zWX1Y+uFGUwlSXrWcZWu3yhANRezmph4MninmqybUYT6g9ZE0aQ9QIdPkLR3Kg==} @@ -3081,17 +3105,17 @@ packages: resolution: {integrity: sha512-6xm38yGVIa6mKm/DUCF2zFFJhERh/QWp1ufm4cNUvxsONBmfPg8uZ9pZBdOmF6qFGr/HlT6ABBkCSx/dlEtvWg==} engines: {node: '>=12 <14 || 14.2 - 14.9 || >14.10.0'} - '@zardoy/flying-squid@0.0.20': - resolution: {integrity: sha512-WyejZS2Upzv86g6Ez5Z/4Pd0ea9tkFL2itAj24UNpO7fyzxuTN2Ag1Ouvh+MSkCloXhR4E/yoER2krHW8vzwBw==} + '@zardoy/flying-squid@0.0.33': + resolution: {integrity: sha512-zCgHinWrNbS4HugnA1GBMuKQ0rUemBg//b+XhefxKeGBg9ngk8UVlJoR6cCAaa67zjiauEq/rhnNKnA4V7vtuQ==} engines: {node: '>=8'} hasBin: true - '@zardoy/react-util@0.2.0': - resolution: {integrity: sha512-glABtx54mh4XSaK6BNALWE3mlshPjcPwPsRj/GnOXEA7WJY/6n43iJoukbaYF3758mGZRU5Fq6gklyFjBg0yHQ==} + '@zardoy/react-util@0.2.4': + resolution: {integrity: sha512-YRBbXi54QOgWGDSn3NLEMMGrWbfL/gn2khxO31HT0WPFB6IW2rSnB4hcy+S/nc+2D6PRNq4kQxGs4vTAe4a7Xg==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} peerDependencies: react: ^18.2.0 - react-dom: ^18.0.0 + react-dom: '>=18.0.0' '@zardoy/utils@0.0.11': resolution: {integrity: sha512-d6xBnSFCOa98HcL52xSBflJKjKpxfRhtr1eVexy89YujeCHSQhUMmSz9h07xyrulfW60k9tSeYH5reuqoh4l4w==} @@ -3317,9 +3341,6 @@ packages: resolution: {integrity: sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==} engines: {node: '>=0.10.0'} - asap@1.0.0: - resolution: {integrity: sha512-Ej9qjcXY+8Tuy1cNqiwNMwFRXOy9UwgTeMA8LxreodygIPV48lx8PU1ecFxb5ZeU1DpMKxiq6vGLTxcitWZPbA==} - asn1.js@5.4.1: resolution: {integrity: sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==} @@ -3403,6 +3424,9 @@ packages: axios@0.21.4: resolution: {integrity: sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==} + axios@1.7.2: + resolution: {integrity: sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==} + babel-core@7.0.0-bridge.0: resolution: {integrity: sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==} peerDependencies: @@ -4208,8 +4232,8 @@ packages: devlop@1.1.0: resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} - diamond-square@https://codeload.github.com/zardoy/diamond-square/tar.gz/915fce8e27fe8eb45464d89b9563956afa4f7687: - resolution: {tarball: https://codeload.github.com/zardoy/diamond-square/tar.gz/915fce8e27fe8eb45464d89b9563956afa4f7687} + diamond-square@https://codeload.github.com/zardoy/diamond-square/tar.gz/4bbe28dcad35403abaa925055e91f601a61b9015: + resolution: {tarball: https://codeload.github.com/zardoy/diamond-square/tar.gz/4bbe28dcad35403abaa925055e91f601a61b9015} version: 1.3.0 diff-sequences@29.6.3: @@ -4658,12 +4682,6 @@ packages: resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} engines: {node: '>= 0.6'} - event-promise@0.0.1: - resolution: {integrity: sha512-ouEmk2N0BalybPM0zmj3RHE93AX4p9hAIHZfbbqxolLChqCB6pcLDbYH6zZ8TaiFWImPHfs5kFnNpA0u9RdEaQ==} - - event-stream@3.3.4: - resolution: {integrity: sha512-QHpkERcGsR0T7Qm3HNJSyXKEEj8AHNxkY3PK8TS2KJvQ7NiSHe3DDpwVKKtoYprL/AreyzFBeIkBIWChAqn60g==} - event-target-shim@5.0.1: resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} engines: {node: '>=6'} @@ -5317,9 +5335,6 @@ packages: hyphenate-style-name@1.0.4: resolution: {integrity: sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ==} - iconify-icon@1.0.8: - resolution: {integrity: sha512-jvbUKHXf8EnGGArmhlP2IG8VqQLFFyTvTqb9LVL2TKTh7/eCCD1o2HHE9thpbJJb6B8hzhcFb6rOKhvo7reNKA==} - iconv-lite@0.4.24: resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} engines: {node: '>=0.10.0'} @@ -5739,8 +5754,11 @@ packages: jimp@0.10.3: resolution: {integrity: sha512-meVWmDMtyUG5uYjFkmzu0zBgnCvvxwWNi27c4cg55vWNVC9ES4Lcwb+ogx+uBBQE3Q+dLKjXaLl0JVW+nUNwbQ==} - jose@4.15.4: - resolution: {integrity: sha512-W+oqK4H+r5sITxfxpSU+MMdr/YSWGvgZMQDIsNoBDGGy4i7GBPTtvFKibQzW06n3U3TqHjhvBJsirShsEJ6eeQ==} + joi@17.13.1: + resolution: {integrity: sha512-vaBlIKCyo4FCUtCm7Eu4QZd/q02bWcxfUO6YSXAZOWF6gzcLBeba8kwotUdYJjDLW8Cz8RywsSOqiNJZW0mNvg==} + + jose@4.15.5: + resolution: {integrity: sha512-jc7BFxgKPKi94uOvEmzlSWFFe2+vASyXaKUpdQKatWAESU2MWjDfFf0fdfc83CDKcA5QecabZeNLyfhe3yKNkg==} jpeg-js@0.3.7: resolution: {integrity: sha512-9IXdWudL61npZjvLuVe/ktHiA41iE8qFyLB+4VDTblEsWBzeg8WQTlktdUK4CdncUqtUgUg0bbOmTE2bKBKaBQ==} @@ -6287,8 +6305,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/5554c7ab0a74bce52aa5f5f04a48eb8d3b9ac65c: - resolution: {tarball: https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/5554c7ab0a74bce52aa5f5f04a48eb8d3b9ac65c} + 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: @@ -6314,8 +6332,8 @@ packages: resolution: {integrity: sha512-QMMNPx4IyZE7ydAzjvGLQLCnQNUOfkk1qVZKxTTS9q3qPTAewz4GhsVUBtbQ8LSbHthe5RcQ1Sgxs4wlIma/Qw==} engines: {node: '>=18'} - mineflayer@https://codeload.github.com/PrismarineJS/mineflayer/tar.gz/ec76468c8ac4c6232bad3c2b66d4160f95f58396: - resolution: {tarball: https://codeload.github.com/PrismarineJS/mineflayer/tar.gz/ec76468c8ac4c6232bad3c2b66d4160f95f58396} + mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/dddc683544317f117172077a9245a07be1b12479: + resolution: {tarball: https://codeload.github.com/zardoy/mineflayer/tar.gz/dddc683544317f117172077a9245a07be1b12479} version: 4.20.1 engines: {node: '>=18'} @@ -6479,8 +6497,8 @@ packages: nested-error-stacks@2.1.1: resolution: {integrity: sha512-9iN1ka/9zmX1ZvLV9ewJYEk9h7RyRRtqdK0woXcqohu8EWIerfPUjYJPg0ULy0UqP7cslmdGc8xKDJcojlKiaw==} - net-browserify@https://codeload.github.com/zardoy/prismarinejs-net-browserify/tar.gz/7d827dba61bd2f9ac9a6086fe2079a0fccadd070: - resolution: {tarball: https://codeload.github.com/zardoy/prismarinejs-net-browserify/tar.gz/7d827dba61bd2f9ac9a6086fe2079a0fccadd070} + net-browserify@https://codeload.github.com/zardoy/prismarinejs-net-browserify/tar.gz/231ef1737a078e8cb51e8c695b48d7b43ce3bc53: + resolution: {tarball: https://codeload.github.com/zardoy/prismarinejs-net-browserify/tar.gz/231ef1737a078e8cb51e8c695b48d7b43ce3bc53} version: 0.2.4 nice-try@1.0.5: @@ -6900,6 +6918,9 @@ packages: resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} engines: {node: '>= 6'} + pixelarticons@1.8.1: + resolution: {integrity: sha512-4taoDCleft9RtzVHLA73VDnRBwJNqlwbW8ShO6S0G9b+bM5ArGe1MVFW9xpromuPvQgVUYCSjRxNAQuNtADqyA==} + pixelmatch@4.0.2: resolution: {integrity: sha512-J8B6xqiO37sU/gkcMglv6h5Jbd9xNER7aHzpfRdNmV4IbQBzBpe4l9XmbG+xPF/znacgu2jfEw+wHffaq/YkXA==} hasBin: true @@ -6997,18 +7018,20 @@ packages: minecraft-data: 3.65.0 prismarine-registry: ^1.1.0 - prismarine-block@https://codeload.github.com/zardoy/prismarine-block/tar.gz/ada4ec3fdfbbc1cc20ab01d0e23f0718a77cc1a0: - resolution: {tarball: https://codeload.github.com/zardoy/prismarine-block/tar.gz/ada4ec3fdfbbc1cc20ab01d0e23f0718a77cc1a0} + prismarine-block@https://codeload.github.com/zardoy/prismarine-block/tar.gz/dd4954fff3b334f8ce063d18e39b2e9414ece5b8: + resolution: {tarball: https://codeload.github.com/zardoy/prismarine-block/tar.gz/dd4954fff3b334f8ce063d18e39b2e9414ece5b8} version: 1.17.1 prismarine-chat@1.10.1: resolution: {integrity: sha512-XukYcuueuhDxzEXG7r8BZyt6jOObrPPB4JESCgb+/XenB9nExoSHF8eTQWWj8faKPLqm1dRQaYwFJlNBlJZJUw==} - prismarine-chat@1.9.1: - resolution: {integrity: sha512-x7WWa5MNhiLZSO6tw+YyKpzquFZ+DNISVgiV6K3SU0GsishMXe+nto02WhF/4AuFerKdugm9u1d/r4C4zSkJOg==} + prismarine-chunk@https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/9662306deea57d8d0ba0a2a3f3f7adb95f0131e3: + resolution: {tarball: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/9662306deea57d8d0ba0a2a3f3f7adb95f0131e3} + version: 1.35.0 + engines: {node: '>=14'} - prismarine-chunk@https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/eb39a905761a36f733a456110e6b49d655bf5c16: - resolution: {tarball: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/eb39a905761a36f733a456110e6b49d655bf5c16} + prismarine-chunk@https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/cea0b6c792d7dcbb69dfd20fa48be5fd60ce83ef: + resolution: {tarball: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/cea0b6c792d7dcbb69dfd20fa48be5fd60ce83ef} version: 1.35.0 engines: {node: '>=14'} @@ -7027,9 +7050,9 @@ packages: prismarine-physics@1.8.0: resolution: {integrity: sha512-gbM+S+bmVtOKVv+Z0WGaHMeEeBHISIDsRDRlv8sr0dex3ZJRhuq8djA02CBreguXtI18ZKh6q3TSj2qDr45NHA==} - prismarine-provider-anvil@https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/02d81b0eba6ab1c362862970954f9a3c150c9a29: - resolution: {tarball: https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/02d81b0eba6ab1c362862970954f9a3c150c9a29} - version: 2.7.0 + prismarine-provider-anvil@https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/0228b5252f48a0d6ad7f36d7189851c427fbe8c4: + resolution: {tarball: https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/0228b5252f48a0d6ad7f36d7189851c427fbe8c4} + version: 2.8.0 prismarine-realms@1.3.2: resolution: {integrity: sha512-5apl9Ru8veTj5q2OozRc4GZOuSIcs3yY4UEtALiLKHstBe8bRw8vNlaz4Zla3jsQ8yP/ul1b1IJINTRbocuA6g==} @@ -7048,9 +7071,9 @@ packages: prismarine-windows@2.9.0: resolution: {integrity: sha512-fm4kOLjGFPov7TEJRmXHoiPabxIQrG36r2mDjlNxfkcLfMHFb3/1ML6mp4iRQa7wL0GK4DIAyiBqCWoeWDxARg==} - prismarine-world@https://codeload.github.com/zardoy/prismarine-world/tar.gz/6ae6f009d38460de284f8c226c665f04cbad9465: - resolution: {tarball: https://codeload.github.com/zardoy/prismarine-world/tar.gz/6ae6f009d38460de284f8c226c665f04cbad9465} - version: 3.6.2 + prismarine-world@https://codeload.github.com/zardoy/prismarine-world/tar.gz/187a87f6d71cba12881a7bbaa510ed9085bf6da7: + resolution: {tarball: https://codeload.github.com/zardoy/prismarine-world/tar.gz/187a87f6d71cba12881a7bbaa510ed9085bf6da7} + version: 3.6.3 engines: {node: '>=8.0.0'} process-nextick-args@2.0.1: @@ -7085,9 +7108,6 @@ packages: resolution: {integrity: sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==} engines: {node: '>=10'} - promise@5.0.0: - resolution: {integrity: sha512-N2BfLz0Sigf7rsm5NnItRwTNqEDUF2ephwEXTcOAf2cO9NwZ9TnIjOmnQNtC0r70CV0S1+uc9mSMmFH7gxk87Q==} - prompts@2.4.2: resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} engines: {node: '>= 6'} @@ -7155,9 +7175,8 @@ packages: proxy-from-env@1.0.0: resolution: {integrity: sha512-F2JHgJQ1iqwnHDcQjVBsq3n/uoaFL+iPW/eAeL7kVxy/2RrWaN4WroKjjvbsoRtv0ftelNyC01bjRhn/bhcf4A==} - proxy-middleware@0.15.0: - resolution: {integrity: sha512-EGCG8SeoIRVMhsqHQUdDigB2i7qU7fCsWASwn54+nPutYO8n4q6EiwMzyfWlC+dzRFExP+kvcnDFdBDHoZBU7Q==} - engines: {node: '>=0.8.0'} + proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} psl@1.9.0: resolution: {integrity: sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==} @@ -7299,6 +7318,12 @@ packages: react-fast-compare@3.2.2: resolution: {integrity: sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==} + react-hook-form@7.52.0: + resolution: {integrity: sha512-mJX506Xc6mirzLsmXUJyqlAI3Kj9Ph2RhplYhUVffeOQSnubK2uVqBFOBJmvKikvbFV91pxVXmDiR+QMF19x6A==} + engines: {node: '>=12.22.0'} + peerDependencies: + react: ^18.2.0 + react-inspector@6.0.2: resolution: {integrity: sha512-x+b7LxhmHXjHoU/VrFAzw5iutsILRoYyDq97EDYdFpPLcvqtEzk4ZSZSQjnFPbr5T57tLXnHcqFYoN1pI6u8uQ==} peerDependencies: @@ -8752,6 +8777,11 @@ packages: w3c-keyname@2.2.8: resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==} + wait-on@7.2.0: + resolution: {integrity: sha512-wCQcHkRazgjG5XoAq9jbTMLpNIjoSlZslrJ2+N9MxDsGEv1HnFoVjOCexL0ESva7Y9cu350j+DWADdk54s4AFQ==} + engines: {node: '>=12.0.0'} + hasBin: true + walker@1.0.8: resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} @@ -10318,6 +10348,12 @@ snapshots: '@gar/promisify@1.1.3': optional: true + '@hapi/hoek@9.3.0': {} + + '@hapi/topo@5.1.0': + dependencies: + '@hapi/hoek': 9.3.0 + '@humanwhocodes/config-array@0.11.11': dependencies: '@humanwhocodes/object-schema': 1.2.1 @@ -10330,8 +10366,6 @@ snapshots: '@humanwhocodes/object-schema@1.2.1': {} - '@iconify/types@2.0.0': {} - '@isaacs/cliui@8.0.2': dependencies: string-width: 5.1.2 @@ -11159,6 +11193,14 @@ snapshots: '@rushstack/eslint-patch@1.4.0': {} + '@sideway/address@4.1.5': + dependencies: + '@hapi/hoek': 9.3.0 + + '@sideway/formula@3.0.1': {} + + '@sideway/pinpoint@2.0.0': {} + '@sinclair/typebox@0.27.8': {} '@socket.io/component-emitter@3.1.0': {} @@ -11830,8 +11872,7 @@ snapshots: magic-string: 0.25.9 string.prototype.matchall: 4.0.10 - '@tootallnate/once@2.0.0': - optional: true + '@tootallnate/once@2.0.0': {} '@tweenjs/tween.js@18.6.4': {} @@ -11861,7 +11902,7 @@ snapshots: '@types/body-parser@1.19.3': dependencies: '@types/connect': 3.4.36 - '@types/node': 20.8.0 + '@types/node': 20.12.8 '@types/chai-subset@1.3.3': dependencies: @@ -11871,7 +11912,7 @@ snapshots: '@types/connect@3.4.36': dependencies: - '@types/node': 20.8.10 + '@types/node': 20.12.8 '@types/cookie@0.4.1': {} @@ -11907,7 +11948,7 @@ snapshots: '@types/express-serve-static-core@4.17.37': dependencies: - '@types/node': 20.8.0 + '@types/node': 20.12.8 '@types/qs': 6.9.8 '@types/range-parser': 1.2.5 '@types/send': 0.17.2 @@ -11926,7 +11967,7 @@ snapshots: '@types/glob@7.2.0': dependencies: '@types/minimatch': 5.1.2 - '@types/node': 20.8.0 + '@types/node': 20.12.8 '@types/graceful-fs@4.1.7': dependencies: @@ -11985,16 +12026,12 @@ snapshots: '@types/node@16.18.58': {} - '@types/node@20.11.19': + '@types/node@20.12.8': dependencies: undici-types: 5.26.5 '@types/node@20.8.0': {} - '@types/node@20.8.10': - dependencies: - undici-types: 5.26.5 - '@types/normalize-package-data@2.4.2': {} '@types/offscreencanvas@2019.7.2': {} @@ -12051,7 +12088,7 @@ snapshots: dependencies: '@types/http-errors': 2.0.2 '@types/mime': 3.0.2 - '@types/node': 20.8.0 + '@types/node': 20.12.8 '@types/sinonjs__fake-timers@8.1.1': {} @@ -12083,6 +12120,10 @@ snapshots: '@types/unist@3.0.2': {} + '@types/wait-on@5.3.4': + dependencies: + '@types/node': 20.12.8 + '@types/webxr@0.5.7': {} '@types/wicg-file-system-access@2023.10.2': {} @@ -12099,7 +12140,7 @@ snapshots: '@types/yauzl@2.10.3': dependencies: - '@types/node': 20.11.19 + '@types/node': 20.12.8 optional: true '@typescript-eslint/eslint-plugin@6.1.0(@typescript-eslint/parser@6.7.3(eslint@8.50.0)(typescript@5.5.0-beta))(eslint@8.50.0)(typescript@5.5.0-beta)': @@ -12319,32 +12360,33 @@ snapshots: '@types/emscripten': 1.39.8 tslib: 1.14.1 - '@zardoy/flying-squid@0.0.20(encoding@0.1.13)': + '@zardoy/flying-squid@0.0.33(encoding@0.1.13)': dependencies: + '@tootallnate/once': 2.0.0 change-case: 4.1.2 colors: 1.4.0 - diamond-square: https://codeload.github.com/zardoy/diamond-square/tar.gz/915fce8e27fe8eb45464d89b9563956afa4f7687 + diamond-square: https://codeload.github.com/zardoy/diamond-square/tar.gz/4bbe28dcad35403abaa925055e91f601a61b9015 emit-then: 2.0.0 - event-promise: 0.0.1 exit-hook: 2.2.1 flatmap: 0.0.3 long: 5.2.3 minecraft-data: 3.65.0 - minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/495eed56ab230b2615596590064671356d86a2dc(patch_hash=2uxevyasyasdavsxuehfavgkjq)(encoding@0.1.13) + minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/495eed56ab230b2615596590064671356d86a2dc(patch_hash=7otpchsbv7hxsuis4rrrwdtbve)(encoding@0.1.13) mkdirp: 2.1.6 node-gzip: 1.1.2 node-rsa: 1.1.1 - prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/eb39a905761a36f733a456110e6b49d655bf5c16(minecraft-data@3.65.0) + prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/cea0b6c792d7dcbb69dfd20fa48be5fd60ce83ef(minecraft-data@3.65.0) prismarine-entity: 2.3.1 prismarine-item: 1.14.0 prismarine-nbt: 2.5.0 - prismarine-provider-anvil: https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/02d81b0eba6ab1c362862970954f9a3c150c9a29(minecraft-data@3.65.0) + prismarine-provider-anvil: https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/0228b5252f48a0d6ad7f36d7189851c427fbe8c4(minecraft-data@3.65.0) prismarine-windows: 2.9.0 - prismarine-world: https://codeload.github.com/zardoy/prismarine-world/tar.gz/6ae6f009d38460de284f8c226c665f04cbad9465 + prismarine-world: https://codeload.github.com/zardoy/prismarine-world/tar.gz/187a87f6d71cba12881a7bbaa510ed9085bf6da7 rambda: 9.2.0 random-seed: 0.3.0 range: 0.0.3 readline: 1.3.0 + sanitize-filename: 1.6.3 typed-emitter: 1.4.0 uuid-1345: 1.0.2 vec3: 0.1.8 @@ -12354,11 +12396,12 @@ snapshots: - encoding - supports-color - '@zardoy/react-util@0.2.0(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': + '@zardoy/react-util@0.2.4(react-dom@18.2.0(react@18.2.0))(react@18.2.0)': dependencies: classnames: 2.5.1 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) + react-hook-form: 7.52.0(react@18.2.0) '@zardoy/utils@0.0.11': dependencies: @@ -12592,8 +12635,6 @@ snapshots: arrify@1.0.1: {} - asap@1.0.0: {} - asn1.js@5.4.1: dependencies: bn.js: 4.12.0 @@ -12668,6 +12709,14 @@ snapshots: transitivePeerDependencies: - debug + axios@1.7.2(debug@4.3.4): + dependencies: + follow-redirects: 1.15.6(debug@4.3.4) + form-data: 4.0.0 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + babel-core@7.0.0-bridge.0(@babel/core@7.22.11): dependencies: '@babel/core': 7.22.11 @@ -13686,10 +13735,11 @@ snapshots: dependencies: dequal: 2.0.3 - diamond-square@https://codeload.github.com/zardoy/diamond-square/tar.gz/915fce8e27fe8eb45464d89b9563956afa4f7687: + diamond-square@https://codeload.github.com/zardoy/diamond-square/tar.gz/4bbe28dcad35403abaa925055e91f601a61b9015: dependencies: minecraft-data: 3.65.0 - prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/eb39a905761a36f733a456110e6b49d655bf5c16(minecraft-data@3.65.0) + prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/cea0b6c792d7dcbb69dfd20fa48be5fd60ce83ef(minecraft-data@3.65.0) + prismarine-registry: 1.7.0 random-seed: 0.3.0 vec3: 0.1.8 @@ -14413,20 +14463,6 @@ snapshots: etag@1.8.1: {} - event-promise@0.0.1: - dependencies: - promise: 5.0.0 - - event-stream@3.3.4: - dependencies: - duplexer: 0.1.2 - from: 0.1.7 - map-stream: 0.1.0 - pause-stream: 0.0.11 - split: 0.3.3 - stream-combiner: 0.0.4 - through: 2.3.8 - event-target-shim@5.0.1: {} eventemitter2@6.4.7: {} @@ -15254,10 +15290,6 @@ snapshots: hyphenate-style-name@1.0.4: {} - iconify-icon@1.0.8: - dependencies: - '@iconify/types': 2.0.0 - iconv-lite@0.4.24: dependencies: safer-buffer: 2.1.2 @@ -15664,7 +15696,15 @@ snapshots: core-js: 3.32.1 regenerator-runtime: 0.13.11 - jose@4.15.4: {} + joi@17.13.1: + dependencies: + '@hapi/hoek': 9.3.0 + '@hapi/topo': 5.1.0 + '@sideway/address': 4.1.5 + '@sideway/formula': 3.0.1 + '@sideway/pinpoint': 2.0.0 + + jose@4.15.5: {} jpeg-js@0.3.7: {} @@ -16353,16 +16393,16 @@ snapshots: minecraft-folder-path@1.2.0: {} - minecraft-inventory-gui@https://codeload.github.com/zardoy/minecraft-inventory-gui/tar.gz/5554c7ab0a74bce52aa5f5f04a48eb8d3b9ac65c(@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: - '@types/react' - react - minecraft-protocol@https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/495eed56ab230b2615596590064671356d86a2dc(patch_hash=2uxevyasyasdavsxuehfavgkjq)(encoding@0.1.13): + minecraft-protocol@https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/495eed56ab230b2615596590064671356d86a2dc(patch_hash=7otpchsbv7hxsuis4rrrwdtbve)(encoding@0.1.13): dependencies: - '@types/readable-stream': 4.0.10 + '@types/readable-stream': 4.0.12 aes-js: 3.1.2 buffer-equal: 1.0.1 debug: 4.3.4(supports-color@8.1.1) @@ -16373,7 +16413,7 @@ snapshots: minecraft-folder-path: 1.2.0 node-fetch: 2.7.0(encoding@0.1.13) node-rsa: 0.4.2 - prismarine-auth: 2.4.1(encoding@0.1.13) + prismarine-auth: 2.4.2(encoding@0.1.13) prismarine-chat: 1.10.1 prismarine-nbt: 2.5.0 prismarine-realms: 1.3.2(encoding@0.1.13) @@ -16419,7 +16459,7 @@ snapshots: mineflayer-pathfinder@2.4.4: dependencies: minecraft-data: 3.65.0 - prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/ada4ec3fdfbbc1cc20ab01d0e23f0718a77cc1a0 + prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/dd4954fff3b334f8ce063d18e39b2e9414ece5b8 prismarine-entity: 2.3.1 prismarine-item: 1.14.0 prismarine-nbt: 2.2.1 @@ -16429,11 +16469,11 @@ snapshots: mineflayer@4.20.1(encoding@0.1.13): dependencies: minecraft-data: 3.65.0 - minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/495eed56ab230b2615596590064671356d86a2dc(patch_hash=2uxevyasyasdavsxuehfavgkjq)(encoding@0.1.13) + minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/495eed56ab230b2615596590064671356d86a2dc(patch_hash=7otpchsbv7hxsuis4rrrwdtbve)(encoding@0.1.13) prismarine-biome: 1.3.0(minecraft-data@3.65.0)(prismarine-registry@1.7.0) - prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/ada4ec3fdfbbc1cc20ab01d0e23f0718a77cc1a0 - prismarine-chat: 1.9.1 - prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/eb39a905761a36f733a456110e6b49d655bf5c16(minecraft-data@3.65.0) + prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/dd4954fff3b334f8ce063d18e39b2e9414ece5b8 + prismarine-chat: 1.10.1 + prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/9662306deea57d8d0ba0a2a3f3f7adb95f0131e3(minecraft-data@3.65.0) prismarine-entity: 2.3.1 prismarine-item: 1.14.0 prismarine-nbt: 2.5.0 @@ -16441,7 +16481,7 @@ snapshots: prismarine-recipe: 1.3.1(prismarine-registry@1.7.0) prismarine-registry: 1.7.0 prismarine-windows: 2.9.0 - prismarine-world: https://codeload.github.com/zardoy/prismarine-world/tar.gz/6ae6f009d38460de284f8c226c665f04cbad9465 + prismarine-world: https://codeload.github.com/zardoy/prismarine-world/tar.gz/187a87f6d71cba12881a7bbaa510ed9085bf6da7 protodef: 1.15.0 typed-emitter: 1.4.0 vec3: 0.1.8 @@ -16449,14 +16489,14 @@ snapshots: - encoding - supports-color - mineflayer@https://codeload.github.com/PrismarineJS/mineflayer/tar.gz/ec76468c8ac4c6232bad3c2b66d4160f95f58396(encoding@0.1.13): + mineflayer@https://codeload.github.com/zardoy/mineflayer/tar.gz/dddc683544317f117172077a9245a07be1b12479(encoding@0.1.13): dependencies: minecraft-data: 3.65.0 - minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/495eed56ab230b2615596590064671356d86a2dc(patch_hash=2uxevyasyasdavsxuehfavgkjq)(encoding@0.1.13) + minecraft-protocol: https://codeload.github.com/PrismarineJS/node-minecraft-protocol/tar.gz/495eed56ab230b2615596590064671356d86a2dc(patch_hash=7otpchsbv7hxsuis4rrrwdtbve)(encoding@0.1.13) prismarine-biome: 1.3.0(minecraft-data@3.65.0)(prismarine-registry@1.7.0) - prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/ada4ec3fdfbbc1cc20ab01d0e23f0718a77cc1a0 - prismarine-chat: 1.9.1 - prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/eb39a905761a36f733a456110e6b49d655bf5c16(minecraft-data@3.65.0) + prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/dd4954fff3b334f8ce063d18e39b2e9414ece5b8 + prismarine-chat: 1.10.1 + prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/9662306deea57d8d0ba0a2a3f3f7adb95f0131e3(minecraft-data@3.65.0) prismarine-entity: 2.3.1 prismarine-item: 1.14.0 prismarine-nbt: 2.5.0 @@ -16464,7 +16504,7 @@ snapshots: prismarine-recipe: 1.3.1(prismarine-registry@1.7.0) prismarine-registry: 1.7.0 prismarine-windows: 2.9.0 - prismarine-world: https://codeload.github.com/zardoy/prismarine-world/tar.gz/6ae6f009d38460de284f8c226c665f04cbad9465 + prismarine-world: https://codeload.github.com/zardoy/prismarine-world/tar.gz/187a87f6d71cba12881a7bbaa510ed9085bf6da7 protodef: 1.15.0 typed-emitter: 1.4.0 vec3: 0.1.8 @@ -16646,7 +16686,7 @@ snapshots: nested-error-stacks@2.1.1: {} - net-browserify@https://codeload.github.com/zardoy/prismarinejs-net-browserify/tar.gz/7d827dba61bd2f9ac9a6086fe2079a0fccadd070: + net-browserify@https://codeload.github.com/zardoy/prismarinejs-net-browserify/tar.gz/231ef1737a078e8cb51e8c695b48d7b43ce3bc53: dependencies: body-parser: 1.20.2 express: 4.18.2 @@ -17108,6 +17148,8 @@ snapshots: pirates@4.0.6: {} + pixelarticons@1.8.1(patch_hash=cclg2qo6d4yjs4evj64nr2mbwa): {} + pixelmatch@4.0.2: dependencies: pngjs: 3.4.0 @@ -17215,11 +17257,11 @@ snapshots: minecraft-data: 3.65.0 prismarine-registry: 1.7.0 - prismarine-block@https://codeload.github.com/zardoy/prismarine-block/tar.gz/ada4ec3fdfbbc1cc20ab01d0e23f0718a77cc1a0: + prismarine-block@https://codeload.github.com/zardoy/prismarine-block/tar.gz/dd4954fff3b334f8ce063d18e39b2e9414ece5b8: dependencies: minecraft-data: 3.65.0 prismarine-biome: 1.3.0(minecraft-data@3.65.0)(prismarine-registry@1.7.0) - prismarine-chat: 1.9.1 + prismarine-chat: 1.10.1 prismarine-item: 1.14.0 prismarine-nbt: 2.5.0 prismarine-registry: 1.7.0 @@ -17230,17 +17272,23 @@ snapshots: prismarine-nbt: 2.5.0 prismarine-registry: 1.7.0 - prismarine-chat@1.9.1: - dependencies: - mojangson: 2.0.4 - prismarine-item: 1.14.0 - prismarine-nbt: 2.5.0 - prismarine-registry: 1.7.0 - - prismarine-chunk@https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/eb39a905761a36f733a456110e6b49d655bf5c16(minecraft-data@3.65.0): + prismarine-chunk@https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/9662306deea57d8d0ba0a2a3f3f7adb95f0131e3(minecraft-data@3.65.0): dependencies: prismarine-biome: 1.3.0(minecraft-data@3.65.0)(prismarine-registry@1.7.0) - prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/ada4ec3fdfbbc1cc20ab01d0e23f0718a77cc1a0 + prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/dd4954fff3b334f8ce063d18e39b2e9414ece5b8 + prismarine-nbt: 2.5.0 + prismarine-registry: 1.7.0 + smart-buffer: 4.2.0 + uint4: 0.1.2 + vec3: 0.1.8 + xxhash-wasm: 0.4.2 + transitivePeerDependencies: + - minecraft-data + + prismarine-chunk@https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/cea0b6c792d7dcbb69dfd20fa48be5fd60ce83ef(minecraft-data@3.65.0): + dependencies: + prismarine-biome: 1.3.0(minecraft-data@3.65.0)(prismarine-registry@1.7.0) + prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/dd4954fff3b334f8ce063d18e39b2e9414ece5b8 prismarine-nbt: 2.5.0 prismarine-registry: 1.7.0 smart-buffer: 4.2.0 @@ -17276,10 +17324,12 @@ snapshots: prismarine-nbt: 2.5.0 vec3: 0.1.8 - prismarine-provider-anvil@https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/02d81b0eba6ab1c362862970954f9a3c150c9a29(minecraft-data@3.65.0): + prismarine-provider-anvil@https://codeload.github.com/zardoy/prismarine-provider-anvil/tar.gz/0228b5252f48a0d6ad7f36d7189851c427fbe8c4(minecraft-data@3.65.0): dependencies: - prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/eb39a905761a36f733a456110e6b49d655bf5c16(minecraft-data@3.65.0) + prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/dd4954fff3b334f8ce063d18e39b2e9414ece5b8 + prismarine-chunk: https://codeload.github.com/zardoy/prismarine-chunk/tar.gz/9662306deea57d8d0ba0a2a3f3f7adb95f0131e3(minecraft-data@3.65.0) prismarine-nbt: 2.5.0 + prismarine-world: https://codeload.github.com/zardoy/prismarine-world/tar.gz/187a87f6d71cba12881a7bbaa510ed9085bf6da7 uint4: 0.1.2 vec3: 0.1.8 transitivePeerDependencies: @@ -17305,9 +17355,9 @@ snapshots: prismarine-schematic@1.2.3: dependencies: minecraft-data: 3.65.0 - prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/ada4ec3fdfbbc1cc20ab01d0e23f0718a77cc1a0 + prismarine-block: https://codeload.github.com/zardoy/prismarine-block/tar.gz/dd4954fff3b334f8ce063d18e39b2e9414ece5b8 prismarine-nbt: 2.2.1 - prismarine-world: https://codeload.github.com/zardoy/prismarine-world/tar.gz/6ae6f009d38460de284f8c226c665f04cbad9465 + prismarine-world: https://codeload.github.com/zardoy/prismarine-world/tar.gz/187a87f6d71cba12881a7bbaa510ed9085bf6da7 vec3: 0.1.8 prismarine-windows@2.9.0: @@ -17316,7 +17366,7 @@ snapshots: prismarine-registry: 1.7.0 typed-emitter: 2.1.0 - prismarine-world@https://codeload.github.com/zardoy/prismarine-world/tar.gz/6ae6f009d38460de284f8c226c665f04cbad9465: + prismarine-world@https://codeload.github.com/zardoy/prismarine-world/tar.gz/187a87f6d71cba12881a7bbaa510ed9085bf6da7: dependencies: vec3: 0.1.8 @@ -17339,10 +17389,6 @@ snapshots: retry: 0.12.0 optional: true - promise@5.0.0: - dependencies: - asap: 1.0.0 - prompts@2.4.2: dependencies: kleur: 3.0.3 @@ -17460,7 +17506,7 @@ snapshots: proxy-from-env@1.0.0: {} - proxy-middleware@0.15.0: {} + proxy-from-env@1.1.0: {} psl@1.9.0: {} @@ -17631,6 +17677,10 @@ snapshots: react-fast-compare@3.2.2: {} + react-hook-form@7.52.0(react@18.2.0): + dependencies: + react: 18.2.0 + react-inspector@6.0.2(react@18.2.0): dependencies: react: 18.2.0 @@ -18260,7 +18310,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: {} @@ -18729,7 +18779,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 @@ -18737,11 +18787,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: {} @@ -19291,6 +19341,16 @@ snapshots: w3c-keyname@2.2.8: {} + wait-on@7.2.0(debug@4.3.4): + dependencies: + axios: 1.7.2(debug@4.3.4) + joi: 17.13.1 + lodash: 4.17.21 + minimist: 1.2.8 + rxjs: 7.8.1 + transitivePeerDependencies: + - debug + walker@1.0.8: dependencies: makeerror: 1.0.12 diff --git a/prismarine-viewer/buildMesherWorker.mjs b/prismarine-viewer/buildMesherWorker.mjs index ee633ec4..80f612d3 100644 --- a/prismarine-viewer/buildMesherWorker.mjs +++ b/prismarine-viewer/buildMesherWorker.mjs @@ -6,6 +6,7 @@ import path from 'path' import { fileURLToPath } from 'url' import fs from 'fs' import { dynamicMcDataFiles } from './buildMesherConfig.mjs' +import { mesherSharedPlugins } from '../scripts/esbuildPlugins.mjs' const allowedBundleFiles = ['legacy', 'versions', 'protocolVersions', 'features'] @@ -34,6 +35,7 @@ const buildOptions = { 'process.env.BROWSER': '"true"', }, plugins: [ + ...mesherSharedPlugins, { name: 'external-json', setup(build) { diff --git a/prismarine-viewer/viewer/lib/dispose.js b/prismarine-viewer/viewer/lib/dispose.js deleted file mode 100644 index 15ec7b4b..00000000 --- a/prismarine-viewer/viewer/lib/dispose.js +++ /dev/null @@ -1,6 +0,0 @@ -module.exports = { - dispose3 (o) { - o.geometry?.dispose() - o.dispose?.() - } -} diff --git a/prismarine-viewer/viewer/lib/entities.js b/prismarine-viewer/viewer/lib/entities.js index 136c77fe..94f06e0c 100644 --- a/prismarine-viewer/viewer/lib/entities.js +++ b/prismarine-viewer/viewer/lib/entities.js @@ -2,7 +2,6 @@ import * as THREE from 'three' import * as TWEEN from '@tweenjs/tween.js' import * as Entity from './entity/EntityMesh' -import { dispose3 } from './dispose' import nbt from 'prismarine-nbt' import EventEmitter from 'events' import { PlayerObject, PlayerAnimation } from 'skinview3d' @@ -14,6 +13,7 @@ import { NameTagObject } from 'skinview3d/libs/nametag' import { flat, fromFormattedString } from '@xmcl/text-component' import mojangson from 'mojangson' import externalTexturesJson from './entity/externalTextures.json' +import { disposeObject } from './threeJsUtils' export const TWEEN_DURATION = 50 // todo should be 100 @@ -94,13 +94,14 @@ function getEntityMesh(entity, scene, options, overrides) { export class Entities extends EventEmitter { constructor(scene) { super() + /** @type {THREE.Scene} */ this.scene = scene this.entities = {} this.entitiesOptions = {} this.debugMode = 'none' this.onSkinUpdate = () => { } this.clock = new THREE.Clock() - this.visible = true + this.rendering = true this.itemsTexture = null this.getItemUv = undefined } @@ -108,7 +109,7 @@ export class Entities extends EventEmitter { clear() { for (const mesh of Object.values(this.entities)) { this.scene.remove(mesh) - dispose3(mesh) + disposeObject(mesh) } this.entities = {} } @@ -125,10 +126,14 @@ export class Entities extends EventEmitter { } } - setVisible(visible, /** @type {THREE.Object3D?} */entity = null) { - this.visible = visible - for (const mesh of entity ? [entity] : Object.values(this.entities)) { - mesh.visible = visible + setRendering(rendering, /** @type {THREE.Object3D?} */entity = null) { + this.rendering = rendering + for (const ent of entity ? [entity] : Object.values(this.entities)) { + if (rendering) { + if (!this.scene.children.includes(ent)) this.scene.add(ent) + } else { + this.scene.remove(ent) + } } } @@ -262,11 +267,15 @@ export class Entities extends EventEmitter { } - displaySimpleText(jsonLike) { + parseEntityLabel(jsonLike) { if (!jsonLike) return - const parsed = typeof jsonLike === 'string' ? mojangson.simplify(mojangson.parse(jsonLike)) : nbt.simplify(jsonLike) - const text = flat(parsed).map(x => x.text) - return text.join('') + try { + const parsed = typeof jsonLike === 'string' ? mojangson.simplify(mojangson.parse(jsonLike)) : nbt.simplify(jsonLike) + const text = flat(parsed).map(x => x.text) + return text.join('') + } catch (err) { + return jsonLike + } } update(/** @type {import('prismarine-entity').Entity & {delete?, pos}} */entity, overrides) { @@ -392,7 +401,7 @@ export class Entities extends EventEmitter { this.updatePlayerSkin(entity.id, '', overrides?.texture || stevePng) } this.setDebugMode(this.debugMode, group) - this.setVisible(this.visible, group) + this.setRendering(this.rendering, group) } //@ts-ignore @@ -405,7 +414,7 @@ export class Entities extends EventEmitter { } } // not player - const displayText = entity.metadata?.[3] && this.displaySimpleText(entity.metadata[2]) + const displayText = entity.metadata?.[3] && this.parseEntityLabel(entity.metadata[2]) if (entity.name !== 'player' && displayText) { addNametag({ ...entity, username: displayText }, this.entitiesOptions, this.entities[entity.id].children.find(c => c.name === 'mesh')) } @@ -456,7 +465,7 @@ export class Entities extends EventEmitter { if (e.additionalCleanup) e.additionalCleanup() this.emit('remove', entity) this.scene.remove(e) - dispose3(e) + disposeObject(e) // todo dispose textures as well ? delete this.entities[entity.id] } diff --git a/prismarine-viewer/viewer/lib/mesher/mesher.ts b/prismarine-viewer/viewer/lib/mesher/mesher.ts index 1fd771d7..57eb7c4f 100644 --- a/prismarine-viewer/viewer/lib/mesher/mesher.ts +++ b/prismarine-viewer/viewer/lib/mesher/mesher.ts @@ -19,6 +19,29 @@ function sectionKey (x, y, z) { return `${x},${y},${z}` } +const batchMessagesLimit = 100 + +let queuedMessages = [] as any[] +let queueWaiting = false +const postMessage = (data, transferList = []) => { + queuedMessages.push({ data, transferList }) + if (queuedMessages.length > batchMessagesLimit) { + drainQueue(0, batchMessagesLimit) + } + if (queueWaiting) return + queueWaiting = true + setTimeout(() => { + queueWaiting = false + drainQueue(0, queuedMessages.length) + }) +} + +function drainQueue (from, to) { + const messages = queuedMessages.slice(from, to) + global.postMessage(messages.map(m => m.data), messages.flatMap(m => m.transferList) as unknown as string) + queuedMessages = queuedMessages.slice(to) +} + function setSectionDirty (pos, value = true) { const x = Math.floor(pos.x / 16) * 16 const y = Math.floor(pos.y / 16) * 16 @@ -43,7 +66,7 @@ const softCleanup = () => { world = new World(world.config.version) } -self.onmessage = ({ data }) => { +const handleMessage = data => { const globalVar: any = globalThis if (data.type === 'mcData') { @@ -53,6 +76,7 @@ self.onmessage = ({ data }) => { if (data.config) { world ??= new World(data.config.version) world.config = { ...world.config, ...data.config } + globalThis.world = world } if (data.type === 'mesherData') { @@ -80,6 +104,15 @@ self.onmessage = ({ data }) => { } } +self.onmessage = ({ data }) => { + if (Array.isArray(data)) { + data.forEach(handleMessage) + return + } + + handleMessage(data) +} + setInterval(() => { if (world === null || !blockStatesReady) return @@ -96,7 +129,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 1d7fd70a..3bb9b596 100644 --- a/prismarine-viewer/viewer/lib/mesher/models.ts +++ b/prismarine-viewer/viewer/lib/mesher/models.ts @@ -1,7 +1,9 @@ import { Vec3 } from 'vec3' import type { BlockStatesOutput } from '../../prepare/modelsBuilder' import { World } from './world' -import { Block } from 'prismarine-block' +import { WorldBlock as Block } from './world' +import legacyJson from '../../../../src/preflatMap.json' +import { versionToNumber } from '../../prepare/utils' import { BlockType } from '../../../examples/shared' import { MesherGeometryOutput } from './shared' @@ -48,6 +50,53 @@ function prepareTints (tints) { }) } +const calculatedBlocksEntries = Object.entries(legacyJson.clientCalculatedBlocks) +export function preflatBlockCalculation (block: Block, world: World, position: Vec3) { + const type = calculatedBlocksEntries.find(([name, blocks]) => blocks.includes(block.name))?.[0] + if (!type) return + switch (type) { + case 'directional': { + const isSolidConnection = !block.name.includes('redstone') && !block.name.includes('tripwire') + const neighbors = [ + world.getBlock(position.offset(0, 0, 1)), + world.getBlock(position.offset(0, 0, -1)), + world.getBlock(position.offset(1, 0, 0)), + world.getBlock(position.offset(-1, 0, 0)) + ] + // set needed props to true: east:'false',north:'false',south:'false',west:'false' + const props = {} + for (const [i, neighbor] of neighbors.entries()) { + const isConnectedToSolid = isSolidConnection ? (neighbor && !neighbor.transparent) : false + if (isConnectedToSolid || neighbor?.name === block.name) { + props[['south', 'north', 'east', 'west'][i]] = 'true' + } + } + return props + } + // case 'gate_in_wall': {} + case 'block_snowy': { + const aboveIsSnow = world.getBlock(position.offset(0, 1, 0))?.name === 'snow' + return { + snowy: `${aboveIsSnow}` + } + } + case 'door': { + // upper half matches lower in + const half = block.getProperties().half + if (half === 'upper') { + // copy other properties + const lower = world.getBlock(position.offset(0, -1, 0)) + if (lower?.name === block.name) { + return { + ...lower.getProperties(), + half: 'upper' + } + } + } + } + } +} + function tintToGl (tint) { const r = (tint >> 16) & 0xff const g = (tint >> 8) & 0xff @@ -267,9 +316,9 @@ function renderLiquid (world, cursor, texture, type, biome, water, attr) { for (const pos of corners) { const height = cornerHeights[pos[2] * 2 + pos[0]] attr.t_positions.push( - (pos[0] ? 1 : 0) + (cursor.x & 15) - 8, - (pos[1] ? height : 0) + (cursor.y & 15) - 8, - (pos[2] ? 1 : 0) + (cursor.z & 15) - 8) + (pos[0] ? 0.999 : 0.001) + (cursor.x & 15) - 8, + (pos[1] ? height - 0.001 : 0.001) + (cursor.y & 15) - 8, + (pos[2] ? 0.999 : 0.001) + (cursor.z & 15) - 8) attr.t_normals.push(...dir) attr.t_uvs.push(pos[3] * su + u, pos[4] * sv * (pos[1] ? 1 : height) + v) attr.t_colors.push(tint[0], tint[1], tint[2]) @@ -426,7 +475,7 @@ function renderElement (world: World, cursor: Vec3, element, doAO: boolean, attr const aos: number[] = [] const neighborPos = position.plus(new Vec3(...dir)) - const baseLight = world.getLight(neighborPos) / 15 + const baseLight = world.getLight(neighborPos, undefined, undefined, block.name) / 15 for (const pos of corners) { let vertex = [ (pos[0] ? maxx : minx), @@ -543,7 +592,7 @@ export function getSectionGeometry (sx, sy, sz, world: World) { for (cursor.z = sz; cursor.z < sz + 16; cursor.z++) { for (cursor.x = sx; cursor.x < sx + 16; cursor.x++) { const block = world.getBlock(cursor)! - if (block.name.includes('_sign')) { + if (block.name.includes('_sign') || block.name === 'sign') { const key = `${cursor.x},${cursor.y},${cursor.z}` const props: any = block.getProperties() const facingRotationMap = { @@ -552,14 +601,33 @@ export function getSectionGeometry (sx, sy, sz, world: World) { "west": 1, "east": 3 } - const isWall = block.name.endsWith('wall_sign') || block.name.endsWith('hanging_sign') + const isWall = block.name.endsWith('wall_sign') || block.name.endsWith('wall_hanging_sign') + const isHanging = block.name.endsWith('hanging_sign') attr.signs[key] = { isWall, + isHanging, rotation: isWall ? facingRotationMap[props.facing] : +props.rotation } } const biome = block.biome.name - if (block.variant === undefined) { + + let preflatRecomputeVariant = !!(block as any)._originalProperties + if (world.preflat) { + const patchProperties = preflatBlockCalculation(block, world, cursor) + if (patchProperties) { + //@ts-ignore + block._originalProperties ??= block._properties + //@ts-ignore + block._properties = { ...block._originalProperties, ...patchProperties } + preflatRecomputeVariant = true + } else { + //@ts-ignore + block._properties = block._originalProperties ?? block._properties + //@ts-ignore + block._originalProperties = undefined + } + } + if (block.variant === undefined || preflatRecomputeVariant) { block.variant = getModelVariants(block) } @@ -637,7 +705,7 @@ export function getSectionGeometry (sx, sy, sz, world: World) { delete attr.t_uvs attr.positions = new Float32Array(attr.positions) as any - attr.normals = new Float32Array(attr.normals) as any + attr.normals = new Int8Array(attr.normals) as any attr.colors = new Float32Array(attr.colors) as any attr.uvs = new Float32Array(attr.uvs) as any @@ -680,7 +748,7 @@ function matchProperties (block: Block, /* to match against */properties: Record return true } -function getModelVariants (block: import('prismarine-block').Block) { +function getModelVariants (block: Block) { // air, cave_air, void_air and so on... // full list of invisible & special blocks https://minecraft.wiki/w/Model#Blocks_and_fluids if (block.name === '' || block.name === 'air' || block.name.endsWith('_air')) return [] diff --git a/prismarine-viewer/viewer/lib/mesher/test/tests.test.ts b/prismarine-viewer/viewer/lib/mesher/test/tests.test.ts index 22e3a7b2..de5db815 100644 --- a/prismarine-viewer/viewer/lib/mesher/test/tests.test.ts +++ b/prismarine-viewer/viewer/lib/mesher/test/tests.test.ts @@ -1,96 +1,140 @@ import { test, expect } from 'vitest' import { setup } from './mesherTester' +import minecraftData from 'minecraft-data' +import minecraftAssets from 'minecraft-assets' -const version = '1.18.1' +const version = minecraftAssets.versions.at(-1) const addPositions = [ - // [[0, 0, 0], 'diamond_block'], - [[1, 0, 0], 'stone'], - [[-1, 0, 0], 'stone'], - [[0, 1, 0], 'stone'], - [[0, -1, 0], 'stone'], - [[0, 0, 1], 'stone'], - [[0, 0, -1], 'stone'], + // [[0, 0, 0], 'diamond_block'], + // [[1, 0, 0], 'stone'], + // [[-1, 0, 0], 'stone'], + // [[0, 1, 0], 'stone'], + // [[0, -1, 0], 'stone'], + // [[0, 0, 1], 'stone'], + // [[0, 0, -1], 'stone'], ] as const test('Known blocks are not rendered', () => { - const { mesherWorld, getGeometry, pos, mcData } = setup(version, addPositions as any) + const { mesherWorld, getGeometry, pos, mcData } = setup(version, addPositions as any) + const ignoreAsExpected = ['air', 'cave_air', 'void_air', 'barrier', 'water', 'lava', 'moving_piston', 'light'] - let time = 0 - let times = 0 - const invalidBlocks = {}/* as {[number, number]} */ - for (const block of mcData.blocksArray) { - if (block.maxStateId! - block.minStateId! > 100) continue - for (let i = block.minStateId!; i <= block.maxStateId!; i++) { - if (block.transparent) continue - mesherWorld.setBlockStateId(pos, i) - const start = performance.now() - const { centerFaces, totalTiles, centerTileNeighbors } = getGeometry() - time += performance.now() - start - times++ - if (centerFaces === 0 && centerTileNeighbors !== 0) { - if (invalidBlocks[block.name]) continue - invalidBlocks[block.name] = [i - block.minStateId!, centerTileNeighbors] - // console.log('INVALID', block.name, centerTileNeighbors, i - block.minStateId) - } - } - } - console.log('Average time', time / times) - // Fully expected - expect(invalidBlocks).toMatchInlineSnapshot(` - { - "creeper_head": [ - 0, - 6, - ], - "creeper_wall_head": [ - 0, - 6, - ], - "dragon_head": [ - 0, - 6, - ], - "dragon_wall_head": [ - 0, - 6, - ], - "player_head": [ - 0, - 6, - ], - "player_wall_head": [ - 0, - 6, - ], - "powder_snow": [ - 0, - 6, - ], - "skeleton_skull": [ - 0, - 6, - ], - "skeleton_wall_skull": [ - 0, - 6, - ], - "wither_skeleton_skull": [ - 0, - 6, - ], - "wither_skeleton_wall_skull": [ - 0, - 6, - ], - "zombie_head": [ - 0, - 6, - ], - "zombie_wall_head": [ - 0, - 6, - ], + let time = 0 + let times = 0 + const invalidBlocks = {}/* as {[number, number]} */ + for (const block of mcData.blocksArray) { + if (ignoreAsExpected.includes(block.name)) continue + // if (block.maxStateId! - block.minStateId! > 100) continue + // for (let i = block.minStateId!; i <= block.maxStateId!; i++) { + for (let i = block.defaultState!; i <= block.defaultState!; i++) { + // if (block.transparent) continue + mesherWorld.setBlockStateId(pos, i) + const start = performance.now() + const { centerFaces, totalTiles, centerTileNeighbors } = getGeometry() + time += performance.now() - start + times++ + if (centerFaces === 0) { + if (invalidBlocks[block.name]) continue + invalidBlocks[block.name] = true + // invalidBlocks[block.name] = [i - block.defaultState!, centerTileNeighbors] + // console.log('INVALID', block.name, centerTileNeighbors, i - block.minStateId) } - `) + } + } + console.log('Average time', time / times) + // should be fixed, but to avoid regressions & for visibility + expect(invalidBlocks).toMatchInlineSnapshot(` + { + "black_banner": true, + "black_bed": true, + "black_candle": true, + "black_wall_banner": true, + "blue_banner": true, + "blue_bed": true, + "blue_candle": true, + "blue_wall_banner": true, + "brown_banner": true, + "brown_bed": true, + "brown_candle": true, + "brown_wall_banner": true, + "bubble_column": true, + "candle": true, + "creeper_head": true, + "creeper_wall_head": true, + "cyan_banner": true, + "cyan_bed": true, + "cyan_candle": true, + "cyan_wall_banner": true, + "dragon_head": true, + "dragon_wall_head": true, + "end_gateway": true, + "end_portal": true, + "gray_banner": true, + "gray_bed": true, + "gray_candle": true, + "gray_wall_banner": true, + "green_banner": true, + "green_bed": true, + "green_candle": true, + "green_wall_banner": true, + "light_blue_banner": true, + "light_blue_bed": true, + "light_blue_candle": true, + "light_blue_wall_banner": true, + "light_gray_banner": true, + "light_gray_bed": true, + "light_gray_candle": true, + "light_gray_wall_banner": true, + "lime_banner": true, + "lime_bed": true, + "lime_candle": true, + "lime_wall_banner": true, + "magenta_banner": true, + "magenta_bed": true, + "magenta_candle": true, + "magenta_wall_banner": true, + "orange_banner": true, + "orange_bed": true, + "orange_candle": true, + "orange_wall_banner": true, + "piglin_head": true, + "piglin_wall_head": true, + "pink_banner": true, + "pink_bed": true, + "pink_candle": true, + "pink_petals": true, + "pink_wall_banner": true, + "player_head": true, + "player_wall_head": true, + "powder_snow_cauldron": true, + "purple_banner": true, + "purple_bed": true, + "purple_candle": true, + "purple_wall_banner": true, + "red_banner": true, + "red_bed": true, + "red_candle": true, + "red_wall_banner": true, + "repeater": true, + "sea_pickle": true, + "skeleton_skull": true, + "skeleton_wall_skull": true, + "snow": true, + "structure_void": true, + "turtle_egg": true, + "water_cauldron": true, + "white_banner": true, + "white_bed": true, + "white_candle": true, + "white_wall_banner": true, + "wither_skeleton_skull": true, + "wither_skeleton_wall_skull": true, + "yellow_banner": true, + "yellow_bed": true, + "yellow_candle": true, + "yellow_wall_banner": true, + "zombie_head": true, + "zombie_wall_head": true, + } + `) }) diff --git a/prismarine-viewer/viewer/lib/mesher/world.ts b/prismarine-viewer/viewer/lib/mesher/world.ts index 9a4f0ab3..71b859f4 100644 --- a/prismarine-viewer/viewer/lib/mesher/world.ts +++ b/prismarine-viewer/viewer/lib/mesher/world.ts @@ -4,6 +4,7 @@ import { Block } from "prismarine-block" import { Vec3 } from 'vec3' import moreBlockDataGeneratedJson from '../moreBlockDataGenerated.json' import { defaultMesherConfig } from './shared' +import legacyJson from '../../../../src/preflatMap.json' const ignoreAoBlocks = Object.keys(moreBlockDataGeneratedJson.noOcclusions) @@ -17,7 +18,7 @@ function isCube (shapes) { return shape[0] === 0 && shape[1] === 0 && shape[2] === 0 && shape[3] === 1 && shape[4] === 1 && shape[5] === 1 } -export type WorldBlock = Block & { +export type WorldBlock = Omit & { variant?: any // todo isCube: boolean @@ -30,14 +31,16 @@ export class World { columns = {} as { [key: string]: import('prismarine-chunk/types/index').PCChunk } blockCache = {} biomeCache: { [id: number]: mcData.Biome } + preflat: boolean - constructor(version) { + constructor (version) { this.Chunk = Chunks(version) as any this.biomeCache = mcData(version).biomes + this.preflat = !mcData(version).supportFeature('blockStateId') this.config.version = version } - getLight (pos: Vec3, isNeighbor = false) { + getLight (pos: Vec3, isNeighbor = false, skipMoreChecks = false, curBlockName = '') { const { enableLighting, skyLight } = this.config if (!enableLighting) return 15 // const key = `${pos.x},${pos.y},${pos.z}` @@ -52,8 +55,17 @@ export class World { ) + 2 ) // lightsCache.set(key, result) - if (result === 2 && this.getBlock(pos)?.name.match(/_stairs|slab/)) { // todo this is obviously wrong - result = this.getLight(pos.offset(0, 1, 0)) + if (result === 2 && [this.getBlock(pos)?.name ?? '', curBlockName].some(x => x.match(/_stairs|slab|glass_pane/)) && !skipMoreChecks) { // todo this is obviously wrong + const lights = [ + this.getLight(pos.offset(0, 1, 0), undefined, true), + this.getLight(pos.offset(0, -1, 0), undefined, true), + this.getLight(pos.offset(0, 0, 1), undefined, true), + this.getLight(pos.offset(0, 0, -1), undefined, true), + this.getLight(pos.offset(1, 0, 0), undefined, true), + this.getLight(pos.offset(-1, 0, 0), undefined, true) + ].filter(x => x !== 2) + const min = Math.min(...lights) + result = min } if (isNeighbor && result === 2) result = 15 // TODO return result @@ -91,6 +103,8 @@ export class World { } getBlock (pos: Vec3): WorldBlock | null { + // for easier testing + if (!(pos instanceof Vec3)) pos = new Vec3(...pos as [number, number, number]) const key = columnKey(Math.floor(pos.x / 16) * 16, Math.floor(pos.z / 16) * 16) const column = this.columns[key] @@ -111,6 +125,25 @@ export class World { throw new Error('position is not reliable, use pos parameter instead of block.position') } }) + if (this.preflat) { + //@ts-ignore + b._properties = {} + + const namePropsStr = legacyJson.blocks[b.type + ':' + b.metadata] || findClosestLegacyBlockFallback(b.type, b.metadata, pos) + if (namePropsStr) { + b.name = namePropsStr.split('[')[0] + const propsStr = namePropsStr.split('[')?.[1]?.split(']') + if (propsStr) { + const newProperties = Object.fromEntries(propsStr.join('').split(',').map(x => { + let [key, val] = x.split('=') as any + if (!isNaN(val)) val = parseInt(val) + return [key, val] + })) + //@ts-ignore + b._properties = newProperties + } + } + } } const block = this.blockCache[stateId] @@ -127,6 +160,15 @@ export class World { } } +const findClosestLegacyBlockFallback = (id, metadata, pos) => { + console.warn(`[mesher] Unknown block with ${id}:${metadata} at ${pos}, falling back`) // todo has known issues + for (const [key, value] of Object.entries(legacyJson.blocks)) { + const [idKey, meta] = key.split(':') + if (idKey === id) return value + } + return null +} + // todo export in chunk instead const hasChunkSection = (column, pos) => { if (column._getSection) return column._getSection(pos) diff --git a/prismarine-viewer/viewer/lib/primitives.js b/prismarine-viewer/viewer/lib/primitives.js index f206ee87..c8a0c006 100644 --- a/prismarine-viewer/viewer/lib/primitives.js +++ b/prismarine-viewer/viewer/lib/primitives.js @@ -1,6 +1,5 @@ const THREE = require('three') const { MeshLine, MeshLineMaterial } = require('three.meshline') -const { dispose3 } = require('./dispose') function getMesh (primitive, camera) { if (primitive.type === 'line') { @@ -48,7 +47,7 @@ function getMesh (primitive, camera) { } class Primitives { - constructor (scene, camera) { + constructor(scene, camera) { this.scene = scene this.camera = camera this.primitives = {} @@ -57,7 +56,7 @@ class Primitives { clear () { for (const mesh of Object.values(this.primitives)) { this.scene.remove(mesh) - dispose3(mesh) + disposeObject(mesh) } this.primitives = {} } @@ -65,7 +64,7 @@ class Primitives { update (primitive) { if (this.primitives[primitive.id]) { this.scene.remove(this.primitives[primitive.id]) - dispose3(this.primitives[primitive.id]) + disposeObject(this.primitives[primitive.id]) delete this.primitives[primitive.id] } diff --git a/prismarine-viewer/viewer/lib/threeJsUtils.ts b/prismarine-viewer/viewer/lib/threeJsUtils.ts new file mode 100644 index 00000000..ee22c477 --- /dev/null +++ b/prismarine-viewer/viewer/lib/threeJsUtils.ts @@ -0,0 +1,12 @@ +import * as THREE from 'three' + +export const disposeObject = (obj: THREE.Object3D) => { + // not cleaning texture there as it might be used by other objects, but would be good to also do that + if (obj instanceof THREE.Mesh) { + obj.geometry?.dispose?.() + obj.material?.dispose?.() + } + if (obj.children) { + obj.children.forEach(disposeObject) + } +} diff --git a/prismarine-viewer/viewer/lib/viewer.ts b/prismarine-viewer/viewer/lib/viewer.ts index 45f3f6c4..2c37fc53 100644 --- a/prismarine-viewer/viewer/lib/viewer.ts +++ b/prismarine-viewer/viewer/lib/viewer.ts @@ -8,6 +8,7 @@ import { generateSpiralMatrix } from 'flying-squid/dist/utils' import { defaultWorldRendererConfig } from './worldrendererCommon' import { sendCameraToWorker } from '../../examples/webgpuRendererMain' import { WorldRendererThree } from './worldrendererThree' +import { versionToNumber } from '../prepare/utils' export class Viewer { scene: THREE.Scene @@ -80,7 +81,8 @@ export class Viewer { } setVersion (userVersion: string) { - const texturesVersion = getVersion(userVersion) + let texturesVersion = getVersion(userVersion) + if (versionToNumber(userVersion) < versionToNumber('1.13')) texturesVersion = '1.13.2' // we normalize to post-flatenning in mesher console.log('[viewer] Using version:', userVersion, 'textures:', texturesVersion) this.world.setVersion(userVersion, texturesVersion) this.entities.clear() @@ -203,6 +205,8 @@ export class Viewer { }) emitter.on('time', (timeOfDay) => { + this.world.timeUpdated?.(timeOfDay) + let skyLight = 15 if (timeOfDay < 0 || timeOfDay > 24000) { throw new Error("Invalid time of day. It should be between 0 and 24000.") diff --git a/prismarine-viewer/viewer/lib/viewerWrapper.ts b/prismarine-viewer/viewer/lib/viewerWrapper.ts index c6419dff..57317f42 100644 --- a/prismarine-viewer/viewer/lib/viewerWrapper.ts +++ b/prismarine-viewer/viewer/lib/viewerWrapper.ts @@ -5,12 +5,14 @@ export class ViewerWrapper { previousWindowWidth: number previousWindowHeight: number globalObject = globalThis as any - stopRenderOnBlur = true + stopRenderOnBlur = false addedToPage = false renderInterval = 0 + 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') @@ -29,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 @@ -44,7 +45,7 @@ export class ViewerWrapper { this.globalObject.requestAnimationFrame(this.render.bind(this)) } if (typeof window !== 'undefined') { - // this.trackWindowFocus() + this.trackWindowFocus() } } @@ -77,11 +78,12 @@ export class ViewerWrapper { for (const fn of beforeRenderFrame) fn() this.globalObject.requestAnimationFrame(this.render.bind(this)) if (this.globalObject.stopRender || this.renderer?.xr.isPresenting || (this.stopRenderOnBlur && !this.windowFocused)) return - if (this.renderInterval) { + const renderInterval = (this.windowFocused ? this.renderInterval : this.renderIntervalUnfocused) ?? this.renderInterval + if (renderInterval) { this.delta += time - this.lastTime this.lastTime = time - if (this.delta > this.renderInterval) { - this.delta %= this.renderInterval + if (this.delta > renderInterval) { + this.delta %= renderInterval // continue rendering } else { return diff --git a/prismarine-viewer/viewer/lib/workerProxy.ts b/prismarine-viewer/viewer/lib/workerProxy.ts new file mode 100644 index 00000000..adfe6ac2 --- /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) : [] + worker.postMessage({ + type: prop, + args, + }, transfer) + } + } + }) +} + +// const workerProxy = createWorkerProxy({ +// startRender (canvas: HTMLCanvasElement) { +// }, +// }) + +// const worker = useWorkerProxy(null, workerProxy) + +// worker. diff --git a/prismarine-viewer/viewer/lib/worldDataEmitter.ts b/prismarine-viewer/viewer/lib/worldDataEmitter.ts index f4ce9675..5df5cc73 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) @@ -83,10 +83,11 @@ export class WorldDataEmitter extends EventEmitter { get (_target, posKey, receiver) { if (typeof posKey !== 'string') return const [x, y, z] = posKey.split(',').map(Number) - return bot.world.getBlock(new Vec3(x, y, z)).entity + return bot.world.getBlock(new Vec3(x, y, z))?.entity }, })) this.emitter.emit('renderDistance', this.viewDistance) + this.emitter.emit('time', bot.time.timeOfDay) }) // node.js stream data event pattern if (this.emitter.listenerCount('blockEntities')) { @@ -122,9 +123,23 @@ 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 () { + const clonedLoadedChunks = { ...this.loadedChunks } + this.unloadAllChunks() + for (const loadedChunk in clonedLoadedChunks) { + const [x, z] = loadedChunk.split(',').map(Number) + this.loadChunk(new Vec3(x, 0, z)) } } @@ -186,7 +201,7 @@ export class WorldDataEmitter extends EventEmitter { const positions = generateSpiralMatrix(this.viewDistance).map(([x, z]) => { const pos = new Vec3((botX + x) * 16, 0, (botZ + z) * 16) if (!this.loadedChunks[`${pos.x},${pos.z}`]) return pos - return undefined + return undefined! }).filter(a => !!a) this.lastPos.update(pos) await this._loadChunks(positions) diff --git a/prismarine-viewer/viewer/lib/worldrendererCommon.ts b/prismarine-viewer/viewer/lib/worldrendererCommon.ts index b342e6d7..ce7c07b2 100644 --- a/prismarine-viewer/viewer/lib/worldrendererCommon.ts +++ b/prismarine-viewer/viewer/lib/worldrendererCommon.ts @@ -73,10 +73,10 @@ export abstract class WorldRendererCommon const src = typeof window === 'undefined' ? `${__dirname}/${workerName}` : workerName const worker: any = new Worker(src) - worker.onmessage = async ({ data }) => { + const handleMessage = (data) => { if (!this.active) return this.handleWorkerMessage(data) - await new Promise(resolve => { + new Promise(resolve => { setTimeout(resolve, 0) }) if (data.type === 'sectionFinished') { @@ -105,6 +105,13 @@ export abstract class WorldRendererCommon this.renderUpdateEmitter.emit('update') } } + worker.onmessage = ({ data }) => { + if (Array.isArray(data)) { + data.forEach(handleMessage) + return + } + handleMessage(data) + } if (worker.on) worker.on('message', (data) => { worker.onmessage({ data }) }) this.workers.push(worker) } @@ -123,6 +130,8 @@ export abstract class WorldRendererCommon allChunksLoaded?(): void + timeUpdated?(newTime: number): void + updateViewerPosition (pos: Vec3) { this.viewerPosition = pos for (const [key, value] of Object.entries(this.loadedChunks)) { @@ -213,6 +222,7 @@ export abstract class WorldRendererCommon } addColumn (x: number, z: number, chunk: any, isLightUpdate: boolean) { + if (!this.active) return if (this.workers.length === 0) throw new Error('workers not initialized yet') this.initialChunksLoad = false this.loadedChunks[`${x},${z}`] = true @@ -254,6 +264,9 @@ export abstract class WorldRendererCommon if ((pos.z & 15) === 15) this.setSectionDirty(pos.offset(0, 0, 16)) } + queueAwaited = false + messagesQueue = {} as { [workerIndex: string]: any[] } + setSectionDirty (pos: Vec3, value = true) { if (this.viewDistance === -1) throw new Error('viewDistance not set') this.allChunksFinished = false @@ -267,7 +280,9 @@ export abstract class WorldRendererCommon // is always dispatched to the same worker const hash = mod(Math.floor(pos.x / 16) + Math.floor(pos.y / 16) + Math.floor(pos.z / 16), this.workers.length) this.sectionsOutstanding.set(key, (this.sectionsOutstanding.get(key) ?? 0) + 1) - this.workers[hash].postMessage({ + this.messagesQueue[hash] ??= [] + this.messagesQueue[hash].push({ + // this.workers[hash].postMessage({ type: 'dirty', x: pos.x, y: pos.y, @@ -275,6 +290,21 @@ export abstract class WorldRendererCommon value, config: this.mesherConfig, }) + this.dispatchMessages() + } + + dispatchMessages () { + if (this.queueAwaited) return + this.queueAwaited = true + setTimeout(() => { + // group messages and send as one + for (const workerIndex in this.messagesQueue) { + const worker = this.workers[Number(workerIndex)] + worker.postMessage(this.messagesQueue[workerIndex]) + } + this.messagesQueue = {} + this.queueAwaited = false + }) } // Listen for chunk rendering updates emitted if a worker finished a render and resolve if the number diff --git a/prismarine-viewer/viewer/lib/worldrendererThree.ts b/prismarine-viewer/viewer/lib/worldrendererThree.ts index 236ff7e8..6f8e1b3a 100644 --- a/prismarine-viewer/viewer/lib/worldrendererThree.ts +++ b/prismarine-viewer/viewer/lib/worldrendererThree.ts @@ -1,7 +1,6 @@ import * as THREE from 'three' import { Vec3 } from 'vec3' import nbt from 'prismarine-nbt' -import { dispose3 } from './dispose' import PrismarineChatLoader from 'prismarine-chat' import { renderSign } from '../sign-renderer/' import { chunkPos, sectionPos } from './simpleUtils' @@ -12,6 +11,7 @@ function mod (x, n) { import { WorldRendererCommon, WorldRendererConfig } from './worldrendererCommon' import * as tweenJs from '@tweenjs/tween.js' import { BloomPass, RenderPass, UnrealBloomPass, EffectComposer, WaterPass, GlitchPass } from 'three-stdlib' +import { disposeObject } from './threeJsUtils' export class WorldRendererThree extends WorldRendererCommon { outputFormat = 'threeJs' as const @@ -19,13 +19,27 @@ export class WorldRendererThree extends WorldRendererCommon { sectionObjects: Record = {} chunkTextures = new Map() signsCache = new Map() + starField: StarField + cameraSectionPos: Vec3 = new Vec3(0, 0, 0) get tilesRendered () { return Object.values(this.sectionObjects).reduce((acc, obj) => acc + (obj as any).tilesCount, 0) } - constructor(public scene: THREE.Scene, public renderer: THREE.WebGLRenderer, public config: WorldRendererConfig) { + constructor (public scene: THREE.Scene, public renderer: THREE.WebGLRenderer, public config: WorldRendererConfig) { super(config) + this.starField = new StarField(scene) + } + + timeUpdated (newTime: number): void { + const nightTime = 13_500 + const morningStart = 23_000 + const displayStars = newTime > nightTime && newTime < morningStart + if (displayStars && !this.starField.points) { + this.starField.addToScene() + } else if (!displayStars && this.starField.points) { + this.starField.remove() + } } /** @@ -33,16 +47,18 @@ export class WorldRendererThree extends WorldRendererCommon { */ updatePosDataChunk (key: string) { const [x, y, z] = key.split(',').map(x => Math.floor(+x / 16)) - const [xPlayer, yPlayer, zPlayer] = this.camera.position.toArray().map(x => Math.floor(x / 16)) // sum of distances: x + y + z - const chunkDistance = Math.abs(x - xPlayer) + Math.abs(y - yPlayer) + Math.abs(z - zPlayer) + const chunkDistance = Math.abs(x - this.cameraSectionPos.x) + Math.abs(y - this.cameraSectionPos.y) + Math.abs(z - this.cameraSectionPos.z) const section = this.sectionObjects[key].children.find(child => child.name === 'mesh')! section.renderOrder = 500 - chunkDistance } updateViewerPosition (pos: Vec3): void { this.viewerPosition = pos - for (const [key, value] of Object.entries(this.sectionObjects)) { + const cameraPos = this.camera.position.toArray().map(x => Math.floor(x / 16)) as [number, number, number] + this.cameraSectionPos = new Vec3(...cameraPos) + for (const key in this.sectionObjects) { + const value = this.sectionObjects[key] if (!value) continue this.updatePosDataChunk(key) } @@ -53,7 +69,7 @@ export class WorldRendererThree extends WorldRendererCommon { let object: THREE.Object3D = this.sectionObjects[data.key] if (object) { this.scene.remove(object) - dispose3(object) + disposeObject(object) delete this.sectionObjects[data.key] } @@ -86,7 +102,10 @@ export class WorldRendererThree extends WorldRendererCommon { mesh.name = 'mesh' object = new THREE.Group() object.add(mesh) - const boxHelper = new THREE.BoxHelper(mesh, 0xffff00) + // mesh with static dimensions: 16x16x16 + const staticChunkMesh = new THREE.Mesh(new THREE.BoxGeometry(16, 16, 16), new THREE.MeshBasicMaterial({ color: 0x000000, transparent: true, opacity: 0 })) + staticChunkMesh.position.set(data.geometry.sx, data.geometry.sy, data.geometry.sz) + const boxHelper = new THREE.BoxHelper(staticChunkMesh, 0xffff00) boxHelper.name = 'helper' object.add(boxHelper) object.name = 'chunk' @@ -97,17 +116,22 @@ export class WorldRendererThree extends WorldRendererCommon { } // should not compute it once if (Object.keys(data.geometry.signs).length) { - for (const [posKey, { isWall, rotation }] of Object.entries(data.geometry.signs)) { + for (const [posKey, { isWall, isHanging, rotation }] of Object.entries(data.geometry.signs)) { const [x, y, z] = posKey.split(',') const signBlockEntity = this.blockEntities[posKey] if (!signBlockEntity) continue - const sign = this.renderSign(new Vec3(+x, +y, +z), rotation, isWall, nbt.simplify(signBlockEntity)) + const sign = this.renderSign(new Vec3(+x, +y, +z), rotation, isWall, isHanging, nbt.simplify(signBlockEntity)) if (!sign) continue object.add(sign) } } this.sectionObjects[data.key] = object this.updatePosDataChunk(data.key) + object.matrixAutoUpdate = false + mesh.onAfterRender = (renderer, scene, camera, geometry, material, group) => { + // mesh.matrixAutoUpdate = false + } + this.scene.add(object) } @@ -142,10 +166,11 @@ 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, blockEntity) { + renderSign (position: Vec3, rotation: number, isWall: boolean, isHanging: boolean, blockEntity) { const tex = this.getSignTexture(position, blockEntity) if (!tex) return @@ -161,14 +186,16 @@ export class WorldRendererThree extends WorldRendererCommon { const mesh = new THREE.Mesh(new THREE.PlaneGeometry(1, 1), new THREE.MeshBasicMaterial({ map: tex, transparent: true, })) mesh.renderOrder = 999 - // todo @sa2urami shouldnt all this be done in worker? - mesh.scale.set(1, 7 / 16, 1) - if (isWall) { - mesh.position.set(0, 0, -(8 - 1.5) / 16 + 0.001) + const lineHeight = 7 / 16 + const scaleFactor = isHanging ? 1.3 : 1 + mesh.scale.set(1 * scaleFactor, lineHeight * scaleFactor, 1 * scaleFactor) + + const thickness = (isHanging ? 2 : 1.5) / 16 + const wallSpacing = 0.25 / 16 + if (isWall && !isHanging) { + mesh.position.set(0, 0, -0.5 + thickness + wallSpacing + 0.0001) } else { - // standing - const faceEnd = 8.75 - mesh.position.set(0, 0, (faceEnd - 16 / 2) / 16 + 0.001) + mesh.position.set(0, 0, thickness / 2 + 0.0001) } const group = new THREE.Group() @@ -176,8 +203,10 @@ export class WorldRendererThree extends WorldRendererCommon { rotation * (isWall ? 90 : 45 / 2) ), 0) group.add(mesh) - const y = isWall ? 4.5 / 16 + mesh.scale.y / 2 : (1 - (mesh.scale.y / 2)) - group.position.set(position.x + 0.5, position.y + y, position.z + 0.5) + const height = (isHanging ? 10 : 8) / 16 + const heightOffset = (isHanging ? 0 : isWall ? 4.333 : 9.333) / 16 + const textPosition = height / 2 + heightOffset + group.position.set(position.x + 0.5, position.y + textPosition, position.z + 0.5) return group } @@ -244,6 +273,24 @@ export class WorldRendererThree extends WorldRendererCommon { } } + readdChunks () { + for (const key of Object.keys(this.sectionObjects)) { + this.scene.remove(this.sectionObjects[key]) + } + setTimeout(() => { + for (const key of Object.keys(this.sectionObjects)) { + this.scene.add(this.sectionObjects[key]) + } + }, 500) + } + + disableUpdates (children = this.scene.children) { + for (const child of children) { + child.matrixWorldNeedsUpdate = false + this.disableUpdates(child.children ?? []) + } + } + removeColumn (x, z) { super.removeColumn(x, z) @@ -254,7 +301,7 @@ export class WorldRendererThree extends WorldRendererCommon { const mesh = this.sectionObjects[key] if (mesh) { this.scene.remove(mesh) - dispose3(mesh) + disposeObject(mesh) } delete this.sectionObjects[key] } @@ -265,3 +312,113 @@ export class WorldRendererThree extends WorldRendererCommon { super.setSectionDirty(pos, value) } } + +class StarField { + points?: THREE.Points + private _enabled = true + get enabled () { + return this._enabled + } + set enabled (value) { + this._enabled = value + if (this.points) { + this.points.visible = value + } + } + + constructor (private scene: THREE.Scene) { + } + + addToScene () { + if (this.points || !this.enabled) return + + const radius = 80 + const depth = 50 + const count = 7000 + const factor = 7 + const saturation = 10 + const speed = 0.2 + + const geometry = new THREE.BufferGeometry() + + const genStar = r => new THREE.Vector3().setFromSpherical(new THREE.Spherical(r, Math.acos(1 - Math.random() * 2), Math.random() * 2 * Math.PI)) + + const positions = [] as number[] + const colors = [] as number[] + const sizes = Array.from({ length: count }, () => (0.5 + 0.5 * Math.random()) * factor) + const color = new THREE.Color() + let r = radius + depth + const increment = depth / count + for (let i = 0; i < count; i++) { + r -= increment * Math.random() + positions.push(...genStar(r).toArray()) + color.setHSL(i / count, saturation, 0.9) + colors.push(color.r, color.g, color.b) + } + + geometry.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3)) + geometry.setAttribute('color', new THREE.Float32BufferAttribute(colors, 3)) + geometry.setAttribute('size', new THREE.Float32BufferAttribute(sizes, 1)) + + // Create a material + const material = new StarfieldMaterial() + material.blending = THREE.AdditiveBlending + material.depthTest = false + material.transparent = true + + // Create points and add them to the scene + this.points = new THREE.Points(geometry, material) + this.scene.add(this.points) + + const clock = new THREE.Clock() + this.points.onBeforeRender = (renderer, scene, camera) => { + this.points?.position.copy?.(camera.position) + material.uniforms.time.value = clock.getElapsedTime() * speed + } + } + + remove () { + if (this.points) { + this.points.geometry.dispose(); + (this.points.material as THREE.Material).dispose() + this.scene.remove(this.points) + + this.points = undefined + } + } +} + +const version = parseInt(THREE.REVISION.replace(/\D+/g, '')) +class StarfieldMaterial extends THREE.ShaderMaterial { + constructor () { + super({ + uniforms: { time: { value: 0.0 }, fade: { value: 1.0 } }, + vertexShader: /* glsl */ ` + uniform float time; + attribute float size; + varying vec3 vColor; + attribute vec3 color; + void main() { + vColor = color; + vec4 mvPosition = modelViewMatrix * vec4(position, 0.5); + gl_PointSize = size * (30.0 / -mvPosition.z) * (3.0 + sin(time + 100.0)); + gl_Position = projectionMatrix * mvPosition; + }`, + fragmentShader: /* glsl */ ` + uniform sampler2D pointTexture; + uniform float fade; + varying vec3 vColor; + void main() { + float opacity = 1.0; + if (fade == 1.0) { + float d = distance(gl_PointCoord, vec2(0.5, 0.5)); + opacity = 1.0 / (1.0 + exp(16.0 * (d - 0.25))); + } + gl_FragColor = vec4(vColor, opacity); + + #include + #include <${version >= 154 ? 'colorspace_fragment' : 'encodings_fragment'}> + }`, + }) + } +} diff --git a/prismarine-viewer/viewer/prepare/atlas.ts b/prismarine-viewer/viewer/prepare/atlas.ts index 8da53e0c..cf73fdc4 100644 --- a/prismarine-viewer/viewer/prepare/atlas.ts +++ b/prismarine-viewer/viewer/prepare/atlas.ts @@ -31,20 +31,11 @@ export type JsonAtlas = { [file: string]: { u: number, v: number, - su?: number, - sv?: number, - animatedFrames?: number } } } -export const makeTextureAtlas = ( - input: string[], - getInputData: (name) => { contents: string, tileWidthMult?: number }, - tilesCount = input.length, - suSvOptimize: 'remove' | null = null, - renderAnimated = true -): { +export const makeTextureAtlas = (input: string[], getInputData: (name) => { contents: string, tileWidthMult?: number, origSizeTextures?}, tilesCount = input.length, suSvOptimize: 'remove' | null = null): { image: Buffer, canvas: Canvas, json: JsonAtlas @@ -56,53 +47,62 @@ export const makeTextureAtlas = ( const canvas = new Canvas(imgSize, imgSize, 'png' as any) const g = canvas.getContext('2d') - const texturesIndex = {} as JsonAtlas['textures'] + const texturesIndex = {} + + let nextX = 0 + let nextY = 0 + let rowMaxY = 0 + + const goToNextRow = () => { + nextX = 0 + nextY += rowMaxY + rowMaxY = 0 + } - let offset = 0 const suSv = tileSize / imgSize for (const i in input) { - const pos = +i + offset - const x = (pos % texSize) * tileSize - const y = Math.floor(pos / texSize) * tileSize - const img = new Image() - const keyValue = input[i]; - const inputData = getInputData(keyValue); + const keyValue = input[i] + const inputData = getInputData(keyValue) img.src = inputData.contents - const renderWidth = tileSize * (inputData.tileWidthMult ?? 1) - let animatedFrames = 0 - const addDebugText = (x, y) => { - return // disable debug text - g.fillStyle = 'black' - g.font = '8px Arial' - g.fillText(i, x, y) - } - if (img.height > tileSize && renderAnimated) { - const frames = img.height / tileSize; - animatedFrames = frames - console.log("Animated texture", keyValue, frames) - offset += frames - 1 - for (let i = 0; i < frames; i++) { - const x = ((pos + i) % texSize) * tileSize - const y = Math.floor((pos + i) / texSize) * tileSize - g.drawImage(img, 0, i * tileSize, renderWidth, tileSize, x, y, renderWidth, tileSize) - addDebugText(x, y) + let su = suSv + let sv = suSv + let renderWidth = tileSize * (inputData.tileWidthMult ?? 1) + let renderHeight = tileSize + if (inputData.origSizeTextures?.[keyValue]) { + // todo check have enough space + renderWidth = Math.ceil(img.width / tileSize) * tileSize + renderHeight = Math.ceil(img.height / tileSize) * tileSize + su = renderWidth / imgSize + sv = renderHeight / imgSize + if (renderHeight > imgSize || renderWidth > imgSize) { + throw new Error('Texture ' + keyValue + ' is too big') } - } else { - g.drawImage(img, 0, 0, renderWidth, tileSize, x, y, renderWidth, tileSize) - addDebugText(x, y) } + if (nextX + renderWidth > imgSize) { + goToNextRow() + } + + const x = nextX + const y = nextY + + nextX += renderWidth + rowMaxY = Math.max(rowMaxY, renderHeight) + if (nextX >= imgSize) { + goToNextRow() + } + + g.drawImage(img, 0, 0, renderWidth, renderHeight, x, y, renderWidth, renderHeight) + const cleanName = keyValue.split('.').slice(0, -1).join('.') || keyValue texturesIndex[cleanName] = { u: x / imgSize, v: y / imgSize, ...suSvOptimize === 'remove' ? {} : { - su: suSv, - sv: suSv - }, - textureName: cleanName, - animatedFrames: animatedFrames || undefined + su: su, + sv: sv + } } } @@ -123,7 +123,7 @@ export function makeBlockTextureAtlas (mcAssets: McAssets) { // const textureFiles = mostEncounteredBlocks.map(x => x + '.png') textureFiles.unshift(...localTextures) - const { generated: additionalTextures, twoTileTextures } = getAdditionalTextures() + const { generated: additionalTextures, origSizeTextures } = getAdditionalTextures() textureFiles.push(...Object.keys(additionalTextures)) const atlas = makeTextureAtlas(textureFiles, name => { @@ -136,7 +136,8 @@ export function makeBlockTextureAtlas (mcAssets: McAssets) { return { contents, - tileWidthMult: twoTileTextures.includes(name) ? 2 : undefined, + // tileWidthMult: twoTileTextures.includes(name) ? 2 : undefined, + origSizeTextures } }) return atlas diff --git a/prismarine-viewer/viewer/prepare/data/1.13/blockModels/sign/oak.json b/prismarine-viewer/viewer/prepare/data/1.13/blockModels/sign/oak.json new file mode 100644 index 00000000..2f16a429 --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.13/blockModels/sign/oak.json @@ -0,0 +1,6 @@ +{ + "parent": "sign/sign", + "textures": { + "sign": "entity/sign" + } +} diff --git a/prismarine-viewer/viewer/prepare/data/1.13/blockModels/sign/oak_wall.json b/prismarine-viewer/viewer/prepare/data/1.13/blockModels/sign/oak_wall.json new file mode 100644 index 00000000..dfdb230f --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.13/blockModels/sign/oak_wall.json @@ -0,0 +1,6 @@ +{ + "parent": "sign/sign_wall", + "textures": { + "sign": "entity/sign" + } +} diff --git a/prismarine-viewer/viewer/prepare/data/1.13/blockModels/sign/sign.json b/prismarine-viewer/viewer/prepare/data/1.13/blockModels/sign/sign.json new file mode 100644 index 00000000..4562cfae --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.13/blockModels/sign/sign.json @@ -0,0 +1,140 @@ +{ + "elements": [ + { + "from": [ + 7.25, + 0, + 7.25 + ], + "to": [ + 8.75, + 9.333, + 8.75 + ], + "faces": { + "north": { + "uv": [ + 1.5, + 8, + 2, + 15 + ], + "texture": "#sign" + }, + "east": { + "uv": [ + 1, + 8, + 1.5, + 15 + ], + "texture": "#sign" + }, + "south": { + "uv": [ + 0.5, + 8, + 1, + 15 + ], + "texture": "#sign" + }, + "west": { + "uv": [ + 0, + 8, + 0.5, + 15 + ], + "texture": "#sign" + }, + "up": { + "uv": [ + 0.5, + 7, + 1, + 8 + ], + "texture": "#sign" + }, + "down": { + "uv": [ + 1, + 7, + 1.5, + 8 + ], + "texture": "#sign" + } + } + }, + { + "from": [ + 0, + 9.333, + 7.25 + ], + "to": [ + 16, + 17.333, + 8.75 + ], + "faces": { + "north": { + "uv": [ + 7, + 1, + 13, + 7 + ], + "texture": "#sign" + }, + "east": { + "uv": [ + 6.5, + 1, + 7, + 7 + ], + "texture": "#sign" + }, + "south": { + "uv": [ + 0.5, + 1, + 6.5, + 7 + ], + "texture": "#sign" + }, + "west": { + "uv": [ + 0, + 1, + 0.5, + 7 + ], + "texture": "#sign" + }, + "up": { + "uv": [ + 0.5, + 0, + 6.5, + 1 + ], + "texture": "#sign" + }, + "down": { + "uv": [ + 6.5, + 1, + 12.5, + 0 + ], + "texture": "#sign" + } + } + } + ] +} \ No newline at end of file diff --git a/prismarine-viewer/viewer/prepare/data/1.13/blockModels/sign/sign_wall.json b/prismarine-viewer/viewer/prepare/data/1.13/blockModels/sign/sign_wall.json new file mode 100644 index 00000000..b743c983 --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.13/blockModels/sign/sign_wall.json @@ -0,0 +1,72 @@ +{ + "elements": [ + { + "from": [ + 0, + 4.333, + 0.25 + ], + "to": [ + 16, + 12.333, + 1.75 + ], + "faces": { + "north": { + "uv": [ + 7, + 1, + 13, + 7 + ], + "texture": "#sign" + }, + "east": { + "uv": [ + 6.5, + 1, + 7, + 7 + ], + "texture": "#sign" + }, + "south": { + "uv": [ + 0.5, + 1, + 6.5, + 7 + ], + "texture": "#sign" + }, + "west": { + "uv": [ + 0, + 1, + 0.5, + 7 + ], + "texture": "#sign" + }, + "up": { + "uv": [ + 0.5, + 0, + 6.5, + 1 + ], + "texture": "#sign" + }, + "down": { + "uv": [ + 6.5, + 1, + 12.5, + 0 + ], + "texture": "#sign" + } + } + } + ] +} \ No newline at end of file diff --git a/prismarine-viewer/viewer/prepare/data/1.13/blockStates/sign/sign.json b/prismarine-viewer/viewer/prepare/data/1.13/blockStates/sign/sign.json new file mode 100644 index 00000000..4ebcedcd --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.13/blockStates/sign/sign.json @@ -0,0 +1,67 @@ +{ + "variants": { + "rotation=0": { + "model": "sign/oak" + }, + "rotation=1": { + "model": "sign/oak", + "y": 22.5 + }, + "rotation=2": { + "model": "sign/oak", + "y": 45 + }, + "rotation=3": { + "model": "sign/oak", + "y": 67.5 + }, + "rotation=4": { + "model": "sign/oak", + "y": 90 + }, + "rotation=5": { + "model": "sign/oak", + "y": 112.5 + }, + "rotation=6": { + "model": "sign/oak", + "y": 135 + }, + "rotation=7": { + "model": "sign/oak", + "y": 157.5 + }, + "rotation=8": { + "model": "sign/oak", + "y": 180 + }, + "rotation=9": { + "model": "sign/oak", + "y": 202.5 + }, + "rotation=10": { + "model": "sign/oak", + "y": 225 + }, + "rotation=11": { + "model": "sign/oak", + "y": 247.5 + }, + "rotation=12": { + "model": "sign/oak", + "y": 270 + }, + "rotation=13": { + "model": "sign/oak", + "y": 292.5 + }, + "rotation=14": { + "model": "sign/oak", + "y": 315 + }, + "rotation=15": { + "model": "sign/oak", + "y": 337.5 + } + } +} \ No newline at end of file diff --git a/prismarine-viewer/viewer/prepare/data/1.13/blockStates/sign/wall_sign.json b/prismarine-viewer/viewer/prepare/data/1.13/blockStates/sign/wall_sign.json new file mode 100644 index 00000000..26453d53 --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.13/blockStates/sign/wall_sign.json @@ -0,0 +1,19 @@ +{ + "variants": { + "facing=south": { + "model": "sign/oak_wall" + }, + "facing=west": { + "model": "sign/oak_wall", + "y": 90 + }, + "facing=north": { + "model": "sign/oak_wall", + "y": 180 + }, + "facing=east": { + "model": "sign/oak_wall", + "y": 270 + } + } +} \ No newline at end of file diff --git a/prismarine-viewer/viewer/prepare/data/1.14/blockModels/sign/acacia.json b/prismarine-viewer/viewer/prepare/data/1.14/blockModels/sign/acacia.json new file mode 100644 index 00000000..7057ded0 --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.14/blockModels/sign/acacia.json @@ -0,0 +1,6 @@ +{ + "parent": "sign/sign", + "textures": { + "sign": "entity/signs/acacia" + } +} \ No newline at end of file diff --git a/prismarine-viewer/viewer/prepare/data/1.14/blockModels/sign/acacia_wall.json b/prismarine-viewer/viewer/prepare/data/1.14/blockModels/sign/acacia_wall.json new file mode 100644 index 00000000..70b755bf --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.14/blockModels/sign/acacia_wall.json @@ -0,0 +1,6 @@ +{ + "parent": "sign/sign_wall", + "textures": { + "sign": "entity/signs/acacia" + } +} \ No newline at end of file diff --git a/prismarine-viewer/viewer/prepare/data/1.14/blockModels/sign/birch.json b/prismarine-viewer/viewer/prepare/data/1.14/blockModels/sign/birch.json new file mode 100644 index 00000000..d20d1438 --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.14/blockModels/sign/birch.json @@ -0,0 +1,6 @@ +{ + "parent": "sign/sign", + "textures": { + "sign": "entity/signs/birch" + } +} \ No newline at end of file diff --git a/prismarine-viewer/viewer/prepare/data/1.14/blockModels/sign/birch_wall.json b/prismarine-viewer/viewer/prepare/data/1.14/blockModels/sign/birch_wall.json new file mode 100644 index 00000000..c7983bee --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.14/blockModels/sign/birch_wall.json @@ -0,0 +1,6 @@ +{ + "parent": "sign/sign_wall", + "textures": { + "sign": "entity/signs/birch" + } +} \ No newline at end of file diff --git a/prismarine-viewer/viewer/prepare/data/1.14/blockModels/sign/dark_oak.json b/prismarine-viewer/viewer/prepare/data/1.14/blockModels/sign/dark_oak.json new file mode 100644 index 00000000..803add52 --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.14/blockModels/sign/dark_oak.json @@ -0,0 +1,6 @@ +{ + "parent": "sign/sign", + "textures": { + "sign": "entity/signs/dark_oak" + } +} \ No newline at end of file diff --git a/prismarine-viewer/viewer/prepare/data/1.14/blockModels/sign/dark_oak_wall.json b/prismarine-viewer/viewer/prepare/data/1.14/blockModels/sign/dark_oak_wall.json new file mode 100644 index 00000000..b410acfe --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.14/blockModels/sign/dark_oak_wall.json @@ -0,0 +1,6 @@ +{ + "parent": "sign/sign_wall", + "textures": { + "sign": "entity/signs/dark_oak" + } +} \ No newline at end of file diff --git a/prismarine-viewer/viewer/prepare/data/1.14/blockModels/sign/jungle.json b/prismarine-viewer/viewer/prepare/data/1.14/blockModels/sign/jungle.json new file mode 100644 index 00000000..17d52250 --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.14/blockModels/sign/jungle.json @@ -0,0 +1,6 @@ +{ + "parent": "sign/sign", + "textures": { + "sign": "entity/signs/jungle" + } +} \ No newline at end of file diff --git a/prismarine-viewer/viewer/prepare/data/1.14/blockModels/sign/jungle_wall.json b/prismarine-viewer/viewer/prepare/data/1.14/blockModels/sign/jungle_wall.json new file mode 100644 index 00000000..bfe6c8f8 --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.14/blockModels/sign/jungle_wall.json @@ -0,0 +1,7 @@ +{ + "credit": "Made with Blockbench by TyBraniff for Bluemaps support.", + "parent": "sign/sign_wall", + "textures": { + "sign": "entity/signs/jungle" + } +} diff --git a/prismarine-viewer/viewer/prepare/data/1.14/blockModels/sign/spruce.json b/prismarine-viewer/viewer/prepare/data/1.14/blockModels/sign/spruce.json new file mode 100644 index 00000000..8f2b2179 --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.14/blockModels/sign/spruce.json @@ -0,0 +1,6 @@ +{ + "parent": "sign/sign", + "textures": { + "sign": "entity/signs/spruce" + } +} \ No newline at end of file diff --git a/prismarine-viewer/viewer/prepare/data/1.14/blockModels/sign/spruce_wall.json b/prismarine-viewer/viewer/prepare/data/1.14/blockModels/sign/spruce_wall.json new file mode 100644 index 00000000..1509eb3c --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.14/blockModels/sign/spruce_wall.json @@ -0,0 +1,6 @@ +{ + "parent": "sign/sign_wall", + "textures": { + "sign": "entity/signs/spruce" + } +} \ No newline at end of file diff --git a/prismarine-viewer/viewer/prepare/data/1.14/blockStates/sign/acacia_sign.json b/prismarine-viewer/viewer/prepare/data/1.14/blockStates/sign/acacia_sign.json new file mode 100644 index 00000000..370c2c84 --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.14/blockStates/sign/acacia_sign.json @@ -0,0 +1,67 @@ +{ + "variants": { + "rotation=0": { + "model": "sign/acacia" + }, + "rotation=1": { + "model": "sign/acacia", + "y": 22.5 + }, + "rotation=2": { + "model": "sign/acacia", + "y": 45 + }, + "rotation=3": { + "model": "sign/acacia", + "y": 67.5 + }, + "rotation=4": { + "model": "sign/acacia", + "y": 90 + }, + "rotation=5": { + "model": "sign/acacia", + "y": 112.5 + }, + "rotation=6": { + "model": "sign/acacia", + "y": 135 + }, + "rotation=7": { + "model": "sign/acacia", + "y": 157.5 + }, + "rotation=8": { + "model": "sign/acacia", + "y": 180 + }, + "rotation=9": { + "model": "sign/acacia", + "y": 202.5 + }, + "rotation=10": { + "model": "sign/acacia", + "y": 225 + }, + "rotation=11": { + "model": "sign/acacia", + "y": 247.5 + }, + "rotation=12": { + "model": "sign/acacia", + "y": 270 + }, + "rotation=13": { + "model": "sign/acacia", + "y": 292.5 + }, + "rotation=14": { + "model": "sign/acacia", + "y": 315 + }, + "rotation=15": { + "model": "sign/acacia", + "y": 337.5 + } + } +} \ No newline at end of file diff --git a/prismarine-viewer/viewer/prepare/data/1.14/blockStates/sign/acacia_wall_sign.json b/prismarine-viewer/viewer/prepare/data/1.14/blockStates/sign/acacia_wall_sign.json new file mode 100644 index 00000000..b524b126 --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.14/blockStates/sign/acacia_wall_sign.json @@ -0,0 +1,19 @@ +{ + "variants": { + "facing=south": { + "model": "sign/acacia_wall" + }, + "facing=west": { + "model": "sign/acacia_wall", + "y": 90 + }, + "facing=north": { + "model": "sign/acacia_wall", + "y": 180 + }, + "facing=east": { + "model": "sign/acacia_wall", + "y": 270 + } + } +} \ No newline at end of file diff --git a/prismarine-viewer/viewer/prepare/data/1.14/blockStates/sign/birch_sign.json b/prismarine-viewer/viewer/prepare/data/1.14/blockStates/sign/birch_sign.json new file mode 100644 index 00000000..2ffe5fd5 --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.14/blockStates/sign/birch_sign.json @@ -0,0 +1,67 @@ +{ + "variants": { + "rotation=0": { + "model": "sign/birch" + }, + "rotation=1": { + "model": "sign/birch", + "y": 22.5 + }, + "rotation=2": { + "model": "sign/birch", + "y": 45 + }, + "rotation=3": { + "model": "sign/birch", + "y": 67.5 + }, + "rotation=4": { + "model": "sign/birch", + "y": 90 + }, + "rotation=5": { + "model": "sign/birch", + "y": 112.5 + }, + "rotation=6": { + "model": "sign/birch", + "y": 135 + }, + "rotation=7": { + "model": "sign/birch", + "y": 157.5 + }, + "rotation=8": { + "model": "sign/birch", + "y": 180 + }, + "rotation=9": { + "model": "sign/birch", + "y": 202.5 + }, + "rotation=10": { + "model": "sign/birch", + "y": 225 + }, + "rotation=11": { + "model": "sign/birch", + "y": 247.5 + }, + "rotation=12": { + "model": "sign/birch", + "y": 270 + }, + "rotation=13": { + "model": "sign/birch", + "y": 292.5 + }, + "rotation=14": { + "model": "sign/birch", + "y": 315 + }, + "rotation=15": { + "model": "sign/birch", + "y": 337.5 + } + } +} \ No newline at end of file diff --git a/prismarine-viewer/viewer/prepare/data/1.14/blockStates/sign/birch_wall_sign.json b/prismarine-viewer/viewer/prepare/data/1.14/blockStates/sign/birch_wall_sign.json new file mode 100644 index 00000000..622924b5 --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.14/blockStates/sign/birch_wall_sign.json @@ -0,0 +1,19 @@ +{ + "variants": { + "facing=south": { + "model": "sign/birch_wall" + }, + "facing=west": { + "model": "sign/birch_wall", + "y": 90 + }, + "facing=north": { + "model": "sign/birch_wall", + "y": 180 + }, + "facing=east": { + "model": "sign/birch_wall", + "y": 270 + } + } +} \ No newline at end of file diff --git a/prismarine-viewer/viewer/prepare/data/1.14/blockStates/sign/dark_oak_sign.json b/prismarine-viewer/viewer/prepare/data/1.14/blockStates/sign/dark_oak_sign.json new file mode 100644 index 00000000..6001019b --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.14/blockStates/sign/dark_oak_sign.json @@ -0,0 +1,67 @@ +{ + "variants": { + "rotation=0": { + "model": "sign/dark_oak" + }, + "rotation=1": { + "model": "sign/dark_oak", + "y": 22.5 + }, + "rotation=2": { + "model": "sign/dark_oak", + "y": 45 + }, + "rotation=3": { + "model": "sign/dark_oak", + "y": 67.5 + }, + "rotation=4": { + "model": "sign/dark_oak", + "y": 90 + }, + "rotation=5": { + "model": "sign/dark_oak", + "y": 112.5 + }, + "rotation=6": { + "model": "sign/dark_oak", + "y": 135 + }, + "rotation=7": { + "model": "sign/dark_oak", + "y": 157.5 + }, + "rotation=8": { + "model": "sign/dark_oak", + "y": 180 + }, + "rotation=9": { + "model": "sign/dark_oak", + "y": 202.5 + }, + "rotation=10": { + "model": "sign/dark_oak", + "y": 225 + }, + "rotation=11": { + "model": "sign/dark_oak", + "y": 247.5 + }, + "rotation=12": { + "model": "sign/dark_oak", + "y": 270 + }, + "rotation=13": { + "model": "sign/dark_oak", + "y": 292.5 + }, + "rotation=14": { + "model": "sign/dark_oak", + "y": 315 + }, + "rotation=15": { + "model": "sign/dark_oak", + "y": 337.5 + } + } +} \ No newline at end of file diff --git a/prismarine-viewer/viewer/prepare/data/1.14/blockStates/sign/dark_oak_wall_sign.json b/prismarine-viewer/viewer/prepare/data/1.14/blockStates/sign/dark_oak_wall_sign.json new file mode 100644 index 00000000..4b5cc921 --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.14/blockStates/sign/dark_oak_wall_sign.json @@ -0,0 +1,19 @@ +{ + "variants": { + "facing=south": { + "model": "sign/dark_oak_wall" + }, + "facing=west": { + "model": "sign/dark_oak_wall", + "y": 90 + }, + "facing=north": { + "model": "sign/dark_oak_wall", + "y": 180 + }, + "facing=east": { + "model": "sign/dark_oak_wall", + "y": 270 + } + } +} \ No newline at end of file diff --git a/prismarine-viewer/viewer/prepare/data/1.14/blockStates/sign/jungle_sign.json b/prismarine-viewer/viewer/prepare/data/1.14/blockStates/sign/jungle_sign.json new file mode 100644 index 00000000..983c2d68 --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.14/blockStates/sign/jungle_sign.json @@ -0,0 +1,67 @@ +{ + "variants": { + "rotation=0": { + "model": "sign/jungle" + }, + "rotation=1": { + "model": "sign/jungle", + "y": 22.5 + }, + "rotation=2": { + "model": "sign/jungle", + "y": 45 + }, + "rotation=3": { + "model": "sign/jungle", + "y": 67.5 + }, + "rotation=4": { + "model": "sign/jungle", + "y": 90 + }, + "rotation=5": { + "model": "sign/jungle", + "y": 112.5 + }, + "rotation=6": { + "model": "sign/jungle", + "y": 135 + }, + "rotation=7": { + "model": "sign/jungle", + "y": 157.5 + }, + "rotation=8": { + "model": "sign/jungle", + "y": 180 + }, + "rotation=9": { + "model": "sign/jungle", + "y": 202.5 + }, + "rotation=10": { + "model": "sign/jungle", + "y": 225 + }, + "rotation=11": { + "model": "sign/jungle", + "y": 247.5 + }, + "rotation=12": { + "model": "sign/jungle", + "y": 270 + }, + "rotation=13": { + "model": "sign/jungle", + "y": 292.5 + }, + "rotation=14": { + "model": "sign/jungle", + "y": 315 + }, + "rotation=15": { + "model": "sign/jungle", + "y": 337.5 + } + } +} \ No newline at end of file diff --git a/prismarine-viewer/viewer/prepare/data/1.14/blockStates/sign/jungle_wall_sign.json b/prismarine-viewer/viewer/prepare/data/1.14/blockStates/sign/jungle_wall_sign.json new file mode 100644 index 00000000..898f7323 --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.14/blockStates/sign/jungle_wall_sign.json @@ -0,0 +1,19 @@ +{ + "variants": { + "facing=south": { + "model": "sign/jungle_wall" + }, + "facing=west": { + "model": "sign/jungle_wall", + "y": 90 + }, + "facing=north": { + "model": "sign/jungle_wall", + "y": 180 + }, + "facing=east": { + "model": "sign/jungle_wall", + "y": 270 + } + } +} \ No newline at end of file diff --git a/prismarine-viewer/viewer/prepare/data/1.14/blockStates/sign/oak_sign.json b/prismarine-viewer/viewer/prepare/data/1.14/blockStates/sign/oak_sign.json new file mode 100644 index 00000000..4ebcedcd --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.14/blockStates/sign/oak_sign.json @@ -0,0 +1,67 @@ +{ + "variants": { + "rotation=0": { + "model": "sign/oak" + }, + "rotation=1": { + "model": "sign/oak", + "y": 22.5 + }, + "rotation=2": { + "model": "sign/oak", + "y": 45 + }, + "rotation=3": { + "model": "sign/oak", + "y": 67.5 + }, + "rotation=4": { + "model": "sign/oak", + "y": 90 + }, + "rotation=5": { + "model": "sign/oak", + "y": 112.5 + }, + "rotation=6": { + "model": "sign/oak", + "y": 135 + }, + "rotation=7": { + "model": "sign/oak", + "y": 157.5 + }, + "rotation=8": { + "model": "sign/oak", + "y": 180 + }, + "rotation=9": { + "model": "sign/oak", + "y": 202.5 + }, + "rotation=10": { + "model": "sign/oak", + "y": 225 + }, + "rotation=11": { + "model": "sign/oak", + "y": 247.5 + }, + "rotation=12": { + "model": "sign/oak", + "y": 270 + }, + "rotation=13": { + "model": "sign/oak", + "y": 292.5 + }, + "rotation=14": { + "model": "sign/oak", + "y": 315 + }, + "rotation=15": { + "model": "sign/oak", + "y": 337.5 + } + } +} \ No newline at end of file diff --git a/prismarine-viewer/viewer/prepare/data/1.14/blockStates/sign/oak_wall_sign.json b/prismarine-viewer/viewer/prepare/data/1.14/blockStates/sign/oak_wall_sign.json new file mode 100644 index 00000000..26453d53 --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.14/blockStates/sign/oak_wall_sign.json @@ -0,0 +1,19 @@ +{ + "variants": { + "facing=south": { + "model": "sign/oak_wall" + }, + "facing=west": { + "model": "sign/oak_wall", + "y": 90 + }, + "facing=north": { + "model": "sign/oak_wall", + "y": 180 + }, + "facing=east": { + "model": "sign/oak_wall", + "y": 270 + } + } +} \ No newline at end of file diff --git a/prismarine-viewer/viewer/prepare/data/1.14/blockStates/sign/spruce_sign.json b/prismarine-viewer/viewer/prepare/data/1.14/blockStates/sign/spruce_sign.json new file mode 100644 index 00000000..78722223 --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.14/blockStates/sign/spruce_sign.json @@ -0,0 +1,67 @@ +{ + "variants": { + "rotation=0": { + "model": "sign/spruce" + }, + "rotation=1": { + "model": "sign/spruce", + "y": 22.5 + }, + "rotation=2": { + "model": "sign/spruce", + "y": 45 + }, + "rotation=3": { + "model": "sign/spruce", + "y": 67.5 + }, + "rotation=4": { + "model": "sign/spruce", + "y": 90 + }, + "rotation=5": { + "model": "sign/spruce", + "y": 112.5 + }, + "rotation=6": { + "model": "sign/spruce", + "y": 135 + }, + "rotation=7": { + "model": "sign/spruce", + "y": 157.5 + }, + "rotation=8": { + "model": "sign/spruce", + "y": 180 + }, + "rotation=9": { + "model": "sign/spruce", + "y": 202.5 + }, + "rotation=10": { + "model": "sign/spruce", + "y": 225 + }, + "rotation=11": { + "model": "sign/spruce", + "y": 247.5 + }, + "rotation=12": { + "model": "sign/spruce", + "y": 270 + }, + "rotation=13": { + "model": "sign/spruce", + "y": 292.5 + }, + "rotation=14": { + "model": "sign/spruce", + "y": 315 + }, + "rotation=15": { + "model": "sign/spruce", + "y": 337.5 + } + } +} \ No newline at end of file diff --git a/prismarine-viewer/viewer/prepare/data/1.14/blockStates/sign/spruce_wall_sign.json b/prismarine-viewer/viewer/prepare/data/1.14/blockStates/sign/spruce_wall_sign.json new file mode 100644 index 00000000..8366709a --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.14/blockStates/sign/spruce_wall_sign.json @@ -0,0 +1,19 @@ +{ + "variants": { + "facing=south": { + "model": "sign/spruce_wall" + }, + "facing=west": { + "model": "sign/spruce_wall", + "y": 90 + }, + "facing=north": { + "model": "sign/spruce_wall", + "y": 180 + }, + "facing=east": { + "model": "sign/spruce_wall", + "y": 270 + } + } +} \ No newline at end of file diff --git a/prismarine-viewer/viewer/prepare/data/1.16/blockModels/sign/crimson.json b/prismarine-viewer/viewer/prepare/data/1.16/blockModels/sign/crimson.json new file mode 100644 index 00000000..201e42ad --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.16/blockModels/sign/crimson.json @@ -0,0 +1,6 @@ +{ + "parent": "sign/sign", + "textures": { + "sign": "entity/signs/crimson" + } +} \ No newline at end of file diff --git a/prismarine-viewer/viewer/prepare/data/1.16/blockModels/sign/crimson_wall.json b/prismarine-viewer/viewer/prepare/data/1.16/blockModels/sign/crimson_wall.json new file mode 100644 index 00000000..3faf8661 --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.16/blockModels/sign/crimson_wall.json @@ -0,0 +1,6 @@ +{ + "parent": "sign/sign_wall", + "textures": { + "sign": "entity/signs/crimson" + } +} \ No newline at end of file diff --git a/prismarine-viewer/viewer/prepare/data/1.16/blockModels/sign/warped.json b/prismarine-viewer/viewer/prepare/data/1.16/blockModels/sign/warped.json new file mode 100644 index 00000000..6dd3269e --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.16/blockModels/sign/warped.json @@ -0,0 +1,6 @@ +{ + "parent": "sign/sign", + "textures": { + "sign": "entity/signs/warped" + } +} \ No newline at end of file diff --git a/prismarine-viewer/viewer/prepare/data/1.16/blockModels/sign/warped_wall.json b/prismarine-viewer/viewer/prepare/data/1.16/blockModels/sign/warped_wall.json new file mode 100644 index 00000000..a046ec14 --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.16/blockModels/sign/warped_wall.json @@ -0,0 +1,6 @@ +{ + "parent": "sign/sign_wall", + "textures": { + "sign": "entity/signs/warped" + } +} \ No newline at end of file diff --git a/prismarine-viewer/viewer/prepare/data/1.16/blockStates/sign/crimson_sign.json b/prismarine-viewer/viewer/prepare/data/1.16/blockStates/sign/crimson_sign.json new file mode 100644 index 00000000..5df00a29 --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.16/blockStates/sign/crimson_sign.json @@ -0,0 +1,67 @@ +{ + "variants": { + "rotation=0": { + "model": "sign/crimson" + }, + "rotation=1": { + "model": "sign/crimson", + "y": 22.5 + }, + "rotation=2": { + "model": "sign/crimson", + "y": 45 + }, + "rotation=3": { + "model": "sign/crimson", + "y": 67.5 + }, + "rotation=4": { + "model": "sign/crimson", + "y": 90 + }, + "rotation=5": { + "model": "sign/crimson", + "y": 112.5 + }, + "rotation=6": { + "model": "sign/crimson", + "y": 135 + }, + "rotation=7": { + "model": "sign/crimson", + "y": 157.5 + }, + "rotation=8": { + "model": "sign/crimson", + "y": 180 + }, + "rotation=9": { + "model": "sign/crimson", + "y": 202.5 + }, + "rotation=10": { + "model": "sign/crimson", + "y": 225 + }, + "rotation=11": { + "model": "sign/crimson", + "y": 247.5 + }, + "rotation=12": { + "model": "sign/crimson", + "y": 270 + }, + "rotation=13": { + "model": "sign/crimson", + "y": 292.5 + }, + "rotation=14": { + "model": "sign/crimson", + "y": 315 + }, + "rotation=15": { + "model": "sign/crimson", + "y": 337.5 + } + } +} \ No newline at end of file diff --git a/prismarine-viewer/viewer/prepare/data/1.16/blockStates/sign/crimson_wall_sign.json b/prismarine-viewer/viewer/prepare/data/1.16/blockStates/sign/crimson_wall_sign.json new file mode 100644 index 00000000..149227b2 --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.16/blockStates/sign/crimson_wall_sign.json @@ -0,0 +1,19 @@ +{ + "variants": { + "facing=south": { + "model": "sign/crimson_wall" + }, + "facing=west": { + "model": "sign/crimson_wall", + "y": 90 + }, + "facing=north": { + "model": "sign/crimson_wall", + "y": 180 + }, + "facing=east": { + "model": "sign/crimson_wall", + "y": 270 + } + } +} \ No newline at end of file diff --git a/prismarine-viewer/viewer/prepare/data/1.16/blockStates/sign/warped_sign.json b/prismarine-viewer/viewer/prepare/data/1.16/blockStates/sign/warped_sign.json new file mode 100644 index 00000000..4af216ca --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.16/blockStates/sign/warped_sign.json @@ -0,0 +1,67 @@ +{ + "variants": { + "rotation=0": { + "model": "sign/warped" + }, + "rotation=1": { + "model": "sign/warped", + "y": 22.5 + }, + "rotation=2": { + "model": "sign/warped", + "y": 45 + }, + "rotation=3": { + "model": "sign/warped", + "y": 67.5 + }, + "rotation=4": { + "model": "sign/warped", + "y": 90 + }, + "rotation=5": { + "model": "sign/warped", + "y": 112.5 + }, + "rotation=6": { + "model": "sign/warped", + "y": 135 + }, + "rotation=7": { + "model": "sign/warped", + "y": 157.5 + }, + "rotation=8": { + "model": "sign/warped", + "y": 180 + }, + "rotation=9": { + "model": "sign/warped", + "y": 202.5 + }, + "rotation=10": { + "model": "sign/warped", + "y": 225 + }, + "rotation=11": { + "model": "sign/warped", + "y": 247.5 + }, + "rotation=12": { + "model": "sign/warped", + "y": 270 + }, + "rotation=13": { + "model": "sign/warped", + "y": 292.5 + }, + "rotation=14": { + "model": "sign/warped", + "y": 315 + }, + "rotation=15": { + "model": "sign/warped", + "y": 337.5 + } + } +} \ No newline at end of file diff --git a/prismarine-viewer/viewer/prepare/data/1.16/blockStates/sign/warped_wall_sign.json b/prismarine-viewer/viewer/prepare/data/1.16/blockStates/sign/warped_wall_sign.json new file mode 100644 index 00000000..b1d7f5e0 --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.16/blockStates/sign/warped_wall_sign.json @@ -0,0 +1,19 @@ +{ + "variants": { + "facing=south": { + "model": "sign/warped_wall" + }, + "facing=west": { + "model": "sign/warped_wall", + "y": 90 + }, + "facing=north": { + "model": "sign/warped_wall", + "y": 180 + }, + "facing=east": { + "model": "sign/warped_wall", + "y": 270 + } + } +} \ No newline at end of file diff --git a/prismarine-viewer/viewer/prepare/data/1.19/blockModels/sign/mangrove.json b/prismarine-viewer/viewer/prepare/data/1.19/blockModels/sign/mangrove.json new file mode 100644 index 00000000..bb82e85a --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.19/blockModels/sign/mangrove.json @@ -0,0 +1,6 @@ +{ + "parent": "sign/sign", + "textures": { + "sign": "entity/signs/mangrove" + } +} \ No newline at end of file diff --git a/prismarine-viewer/viewer/prepare/data/1.19/blockModels/sign/mangrove_wall.json b/prismarine-viewer/viewer/prepare/data/1.19/blockModels/sign/mangrove_wall.json new file mode 100644 index 00000000..30e9bd55 --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.19/blockModels/sign/mangrove_wall.json @@ -0,0 +1,6 @@ +{ + "parent": "sign/sign_wall", + "textures": { + "sign": "entity/signs/mangrove" + } +} \ No newline at end of file diff --git a/prismarine-viewer/viewer/prepare/data/1.19/blockStates/sign/mangrove_sign.json b/prismarine-viewer/viewer/prepare/data/1.19/blockStates/sign/mangrove_sign.json new file mode 100644 index 00000000..54a92e7e --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.19/blockStates/sign/mangrove_sign.json @@ -0,0 +1,67 @@ +{ + "variants": { + "rotation=0": { + "model": "sign/mangrove" + }, + "rotation=1": { + "model": "sign/mangrove", + "y": 22.5 + }, + "rotation=2": { + "model": "sign/mangrove", + "y": 45 + }, + "rotation=3": { + "model": "sign/mangrove", + "y": 67.5 + }, + "rotation=4": { + "model": "sign/mangrove", + "y": 90 + }, + "rotation=5": { + "model": "sign/mangrove", + "y": 112.5 + }, + "rotation=6": { + "model": "sign/mangrove", + "y": 135 + }, + "rotation=7": { + "model": "sign/mangrove", + "y": 157.5 + }, + "rotation=8": { + "model": "sign/mangrove", + "y": 180 + }, + "rotation=9": { + "model": "sign/mangrove", + "y": 202.5 + }, + "rotation=10": { + "model": "sign/mangrove", + "y": 225 + }, + "rotation=11": { + "model": "sign/mangrove", + "y": 247.5 + }, + "rotation=12": { + "model": "sign/mangrove", + "y": 270 + }, + "rotation=13": { + "model": "sign/mangrove", + "y": 292.5 + }, + "rotation=14": { + "model": "sign/mangrove", + "y": 315 + }, + "rotation=15": { + "model": "sign/mangrove", + "y": 337.5 + } + } +} \ No newline at end of file diff --git a/prismarine-viewer/viewer/prepare/data/1.19/blockStates/sign/mangrove_wall_sign.json b/prismarine-viewer/viewer/prepare/data/1.19/blockStates/sign/mangrove_wall_sign.json new file mode 100644 index 00000000..d00760e7 --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.19/blockStates/sign/mangrove_wall_sign.json @@ -0,0 +1,19 @@ +{ + "variants": { + "facing=south": { + "model": "sign/mangrove_wall" + }, + "facing=west": { + "model": "sign/mangrove_wall", + "y": 90 + }, + "facing=north": { + "model": "sign/mangrove_wall", + "y": 180 + }, + "facing=east": { + "model": "sign/mangrove_wall", + "y": 270 + } + } +} \ No newline at end of file diff --git a/prismarine-viewer/viewer/prepare/data/1.20/blockModels/decorated_pot.json b/prismarine-viewer/viewer/prepare/data/1.20/blockModels/decorated_pot.json new file mode 100644 index 00000000..364c72d4 --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.20/blockModels/decorated_pot.json @@ -0,0 +1,47 @@ +{ + "texture_size": [32, 32], + "textures": { + "0": "entity/decorated_pot/decorated_pot_base" + }, + "elements": [ + { + "name": "Body", + "from": [1, 0, 1], + "to": [15, 16, 15], + "faces": { + "north": {"uv": [0, 6.5, 7, 13.5], "texture": "#0"}, + "east": {"uv": [0, 6.5, 7, 13.5], "texture": "#0"}, + "south": {"uv": [0, 6.5, 7, 13.5], "texture": "#0"}, + "west": {"uv": [0, 6.5, 7, 13.5], "texture": "#0"}, + "up": {"uv": [7, 6.5, 14, 13.5], "texture": "#0"}, + "down": {"uv": [7, 6.5, 14, 13.5], "texture": "#0"} + } + }, + { + "name": "Neck", + "from": [5, 16, 5], + "to": [11, 17, 11], + "faces": { + "north": {"uv": [6, 5.5, 9, 6], "texture": "#0"}, + "east": {"uv": [9, 5.5, 12, 6], "texture": "#0"}, + "south": {"uv": [2.5, 5.5, 5.5, 6], "texture": "#0"}, + "west": {"uv": [0, 5.5, 3, 6], "texture": "#0"}, + "up": {"uv": [0, 0, 3, 3], "texture": "#0"}, + "down": {"uv": [0, 0, 3, 3], "texture": "#0"} + } + }, + { + "name": "Head", + "from": [4, 17, 4], + "to": [12, 20, 12], + "faces": { + "north": {"uv": [0, 4, 4, 5.5], "texture": "#0"}, + "east": {"uv": [4, 4, 8, 5.5], "texture": "#0"}, + "south": {"uv": [8, 4, 12, 5.5], "texture": "#0"}, + "west": {"uv": [12, 4, 16, 5.5], "texture": "#0"}, + "up": {"uv": [4, 0, 8, 4], "texture": "#0"}, + "down": {"uv": [8, 0, 12, 4], "texture": "#0"} + } + } + ] +} \ No newline at end of file diff --git a/prismarine-viewer/viewer/prepare/data/1.20/blockModels/sign/acacia_hanging.json b/prismarine-viewer/viewer/prepare/data/1.20/blockModels/sign/acacia_hanging.json new file mode 100644 index 00000000..13702388 --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.20/blockModels/sign/acacia_hanging.json @@ -0,0 +1,7 @@ +{ + "credit": "Made with Blockbench by TyBraniff for Bluemaps support.", + "parent": "sign/hanging", + "textures": { + "wood": "entity/signs/hanging/acacia" + } +} \ No newline at end of file diff --git a/prismarine-viewer/viewer/prepare/data/1.20/blockModels/sign/acacia_wall_hanging.json b/prismarine-viewer/viewer/prepare/data/1.20/blockModels/sign/acacia_wall_hanging.json new file mode 100644 index 00000000..1e2a9d85 --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.20/blockModels/sign/acacia_wall_hanging.json @@ -0,0 +1,7 @@ +{ + "credit": "Made with Blockbench by TyBraniff for Bluemaps support.", + "parent": "sign/wall_hanging", + "textures": { + "wood": "entity/signs/hanging/acacia" + } +} \ No newline at end of file diff --git a/prismarine-viewer/viewer/prepare/data/1.20/blockModels/sign/bamboo.json b/prismarine-viewer/viewer/prepare/data/1.20/blockModels/sign/bamboo.json new file mode 100644 index 00000000..6c9fd930 --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.20/blockModels/sign/bamboo.json @@ -0,0 +1,6 @@ +{ + "parent": "sign/sign", + "textures": { + "sign": "entity/signs/bamboo" + } +} diff --git a/prismarine-viewer/viewer/prepare/data/1.20/blockModels/sign/bamboo_hanging.json b/prismarine-viewer/viewer/prepare/data/1.20/blockModels/sign/bamboo_hanging.json new file mode 100644 index 00000000..c5302b1b --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.20/blockModels/sign/bamboo_hanging.json @@ -0,0 +1,7 @@ +{ + "credit": "Made with Blockbench by TyBraniff for Bluemaps support.", + "parent": "sign/hanging", + "textures": { + "wood": "entity/signs/hanging/bamboo" + } +} \ No newline at end of file diff --git a/prismarine-viewer/viewer/prepare/data/1.20/blockModels/sign/bamboo_wall.json b/prismarine-viewer/viewer/prepare/data/1.20/blockModels/sign/bamboo_wall.json new file mode 100644 index 00000000..bf726f10 --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.20/blockModels/sign/bamboo_wall.json @@ -0,0 +1,6 @@ +{ + "parent": "sign/sign_wall", + "textures": { + "sign": "entity/signs/bamboo" + } +} diff --git a/prismarine-viewer/viewer/prepare/data/1.20/blockModels/sign/bamboo_wall_hanging.json b/prismarine-viewer/viewer/prepare/data/1.20/blockModels/sign/bamboo_wall_hanging.json new file mode 100644 index 00000000..d3a46453 --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.20/blockModels/sign/bamboo_wall_hanging.json @@ -0,0 +1,7 @@ +{ + "credit": "Made with Blockbench by TyBraniff for Bluemaps support.", + "parent": "sign/wall_hanging", + "textures": { + "wood": "entity/signs/hanging/bamboo" + } +} \ No newline at end of file diff --git a/prismarine-viewer/viewer/prepare/data/1.20/blockModels/sign/birch_hanging.json b/prismarine-viewer/viewer/prepare/data/1.20/blockModels/sign/birch_hanging.json new file mode 100644 index 00000000..71a4b708 --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.20/blockModels/sign/birch_hanging.json @@ -0,0 +1,7 @@ +{ + "credit": "Made with Blockbench by TyBraniff for Bluemaps support.", + "parent": "sign/hanging", + "textures": { + "wood": "entity/signs/hanging/birch" + } +} \ No newline at end of file diff --git a/prismarine-viewer/viewer/prepare/data/1.20/blockModels/sign/birch_wall_hanging.json b/prismarine-viewer/viewer/prepare/data/1.20/blockModels/sign/birch_wall_hanging.json new file mode 100644 index 00000000..13b215a5 --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.20/blockModels/sign/birch_wall_hanging.json @@ -0,0 +1,7 @@ +{ + "credit": "Made with Blockbench by TyBraniff for Bluemaps support.", + "parent": "sign/wall_hanging", + "textures": { + "wood": "entity/signs/hanging/birch" + } +} \ No newline at end of file diff --git a/prismarine-viewer/viewer/prepare/data/1.20/blockModels/sign/cherry.json b/prismarine-viewer/viewer/prepare/data/1.20/blockModels/sign/cherry.json new file mode 100644 index 00000000..406c6318 --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.20/blockModels/sign/cherry.json @@ -0,0 +1,6 @@ +{ + "parent": "sign/sign", + "textures": { + "sign": "entity/signs/cherry" + } +} diff --git a/prismarine-viewer/viewer/prepare/data/1.20/blockModels/sign/cherry_hanging.json b/prismarine-viewer/viewer/prepare/data/1.20/blockModels/sign/cherry_hanging.json new file mode 100644 index 00000000..6ff4c5b7 --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.20/blockModels/sign/cherry_hanging.json @@ -0,0 +1,7 @@ +{ + "credit": "Made with Blockbench by TyBraniff for Bluemaps support.", + "parent": "sign/hanging", + "textures": { + "wood": "entity/signs/hanging/cherry" + } +} \ No newline at end of file diff --git a/prismarine-viewer/viewer/prepare/data/1.20/blockModels/sign/cherry_wall.json b/prismarine-viewer/viewer/prepare/data/1.20/blockModels/sign/cherry_wall.json new file mode 100644 index 00000000..b3b07061 --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.20/blockModels/sign/cherry_wall.json @@ -0,0 +1,6 @@ +{ + "parent": "sign/sign_wall", + "textures": { + "sign": "entity/signs/cherry" + } +} diff --git a/prismarine-viewer/viewer/prepare/data/1.20/blockModels/sign/cherry_wall_hanging.json b/prismarine-viewer/viewer/prepare/data/1.20/blockModels/sign/cherry_wall_hanging.json new file mode 100644 index 00000000..aeef94bd --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.20/blockModels/sign/cherry_wall_hanging.json @@ -0,0 +1,7 @@ +{ + "credit": "Made with Blockbench by TyBraniff for Bluemaps support.", + "parent": "sign/wall_hanging", + "textures": { + "wood": "entity/signs/hanging/cherry" + } +} \ No newline at end of file diff --git a/prismarine-viewer/viewer/prepare/data/1.20/blockModels/sign/crimson_hanging.json b/prismarine-viewer/viewer/prepare/data/1.20/blockModels/sign/crimson_hanging.json new file mode 100644 index 00000000..a6c9286a --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.20/blockModels/sign/crimson_hanging.json @@ -0,0 +1,7 @@ +{ + "credit": "Made with Blockbench by TyBraniff for Bluemaps support.", + "parent": "sign/hanging", + "textures": { + "wood": "entity/signs/hanging/crimson" + } +} \ No newline at end of file diff --git a/prismarine-viewer/viewer/prepare/data/1.20/blockModels/sign/crimson_wall_hanging.json b/prismarine-viewer/viewer/prepare/data/1.20/blockModels/sign/crimson_wall_hanging.json new file mode 100644 index 00000000..20889940 --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.20/blockModels/sign/crimson_wall_hanging.json @@ -0,0 +1,7 @@ +{ + "credit": "Made with Blockbench by TyBraniff for Bluemaps support.", + "parent": "sign/wall_hanging", + "textures": { + "wood": "entity/signs/hanging/crimson" + } +} \ No newline at end of file diff --git a/prismarine-viewer/viewer/prepare/data/1.20/blockModels/sign/dark_oak_hanging.json b/prismarine-viewer/viewer/prepare/data/1.20/blockModels/sign/dark_oak_hanging.json new file mode 100644 index 00000000..506c4440 --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.20/blockModels/sign/dark_oak_hanging.json @@ -0,0 +1,7 @@ +{ + "credit": "Made with Blockbench by TyBraniff for Bluemaps support.", + "parent": "sign/hanging", + "textures": { + "wood": "entity/signs/hanging/dark_oak" + } +} \ No newline at end of file diff --git a/prismarine-viewer/viewer/prepare/data/1.20/blockModels/sign/dark_oak_wall_hanging.json b/prismarine-viewer/viewer/prepare/data/1.20/blockModels/sign/dark_oak_wall_hanging.json new file mode 100644 index 00000000..21c1ebd5 --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.20/blockModels/sign/dark_oak_wall_hanging.json @@ -0,0 +1,7 @@ +{ + "credit": "Made with Blockbench by TyBraniff for Bluemaps support.", + "parent": "sign/wall_hanging", + "textures": { + "wood": "entity/signs/hanging/dark_oak" + } +} \ No newline at end of file diff --git a/prismarine-viewer/viewer/prepare/data/1.20/blockModels/sign/hanging.json b/prismarine-viewer/viewer/prepare/data/1.20/blockModels/sign/hanging.json new file mode 100644 index 00000000..52d90ee3 --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.20/blockModels/sign/hanging.json @@ -0,0 +1,115 @@ +{ + "credit": "Made with Blockbench by TyBraniff for Bluemaps support.", + "ambientocclusion": false, + "texture_size": [ + 64, + 32 + ], + "textures": { + "wood": "entity/signs/hanging/oak" + }, + "elements": [ + { + "name": "Sign", + "from": [ + 1, + 0, + 7 + ], + "to": [ + 15, + 10, + 9 + ], + "faces": { + "north": { + "uv": [ + 4.5, + 7, + 8, + 12 + ], + "texture": "#wood" + }, + "east": { + "uv": [ + 4, + 7, + 4.5, + 12 + ], + "texture": "#wood" + }, + "south": { + "uv": [ + 0.5, + 7, + 4, + 12 + ], + "texture": "#wood" + }, + "west": { + "uv": [ + 0, + 7, + 0.5, + 12 + ], + "texture": "#wood" + }, + "up": { + "uv": [ + 4, + 6, + 0.5, + 7 + ], + "rotation": 180, + "texture": "#wood" + }, + "down": { + "uv": [ + 4, + 7, + 7.5, + 6 + ], + "texture": "#wood" + } + } + }, + { + "from": [ + 2, + 10, + 8 + ], + "to": [ + 14, + 16, + 8 + ], + "faces": { + "north": { + "uv": [ + 3.5, + 3, + 6.5, + 6 + ], + "texture": "#wood" + }, + "south": { + "uv": [ + 3.5, + 3, + 6.5, + 6 + ], + "texture": "#wood" + } + } + } + ] +} \ No newline at end of file diff --git a/prismarine-viewer/viewer/prepare/data/1.20/blockModels/sign/jungle_hanging.json b/prismarine-viewer/viewer/prepare/data/1.20/blockModels/sign/jungle_hanging.json new file mode 100644 index 00000000..db141f6d --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.20/blockModels/sign/jungle_hanging.json @@ -0,0 +1,7 @@ +{ + "credit": "Made with Blockbench by TyBraniff for Bluemaps support.", + "parent": "sign/hanging", + "textures": { + "wood": "entity/signs/hanging/jungle" + } +} \ No newline at end of file diff --git a/prismarine-viewer/viewer/prepare/data/1.20/blockModels/sign/jungle_wall_hanging.json b/prismarine-viewer/viewer/prepare/data/1.20/blockModels/sign/jungle_wall_hanging.json new file mode 100644 index 00000000..aefe92f3 --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.20/blockModels/sign/jungle_wall_hanging.json @@ -0,0 +1,7 @@ +{ + "credit": "Made with Blockbench by TyBraniff for Bluemaps support.", + "parent": "sign/wall_hanging", + "textures": { + "wood": "entity/signs/hanging/jungle" + } +} \ No newline at end of file diff --git a/prismarine-viewer/viewer/prepare/data/1.20/blockModels/sign/mangrove_hanging.json b/prismarine-viewer/viewer/prepare/data/1.20/blockModels/sign/mangrove_hanging.json new file mode 100644 index 00000000..e84c41f2 --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.20/blockModels/sign/mangrove_hanging.json @@ -0,0 +1,7 @@ +{ + "credit": "Made with Blockbench by TyBraniff for Bluemaps support.", + "parent": "sign/hanging", + "textures": { + "wood": "entity/signs/hanging/mangrove" + } +} \ No newline at end of file diff --git a/prismarine-viewer/viewer/prepare/data/1.20/blockModels/sign/mangrove_wall_hanging.json b/prismarine-viewer/viewer/prepare/data/1.20/blockModels/sign/mangrove_wall_hanging.json new file mode 100644 index 00000000..e5feb72e --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.20/blockModels/sign/mangrove_wall_hanging.json @@ -0,0 +1,7 @@ +{ + "credit": "Made with Blockbench by TyBraniff for Bluemaps support.", + "parent": "sign/wall_hanging", + "textures": { + "wood": "entity/signs/hanging/mangrove" + } +} \ No newline at end of file diff --git a/prismarine-viewer/viewer/prepare/data/1.20/blockModels/sign/oak_hanging.json b/prismarine-viewer/viewer/prepare/data/1.20/blockModels/sign/oak_hanging.json new file mode 100644 index 00000000..7437c82f --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.20/blockModels/sign/oak_hanging.json @@ -0,0 +1,7 @@ +{ + "credit": "Made with Blockbench by TyBraniff for Bluemaps support.", + "parent": "sign/hanging", + "textures": { + "wood": "entity/signs/hanging/oak" + } +} \ No newline at end of file diff --git a/prismarine-viewer/viewer/prepare/data/1.20/blockModels/sign/oak_wall_hanging.json b/prismarine-viewer/viewer/prepare/data/1.20/blockModels/sign/oak_wall_hanging.json new file mode 100644 index 00000000..3c8d9e5e --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.20/blockModels/sign/oak_wall_hanging.json @@ -0,0 +1,7 @@ +{ + "credit": "Made with Blockbench by TyBraniff for Bluemaps support.", + "parent": "sign/wall_hanging", + "textures": { + "wood": "entity/signs/hanging/oak" + } +} \ No newline at end of file diff --git a/prismarine-viewer/viewer/prepare/data/1.20/blockModels/sign/spruce_hanging.json b/prismarine-viewer/viewer/prepare/data/1.20/blockModels/sign/spruce_hanging.json new file mode 100644 index 00000000..3dee635d --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.20/blockModels/sign/spruce_hanging.json @@ -0,0 +1,7 @@ +{ + "credit": "Made with Blockbench by TyBraniff for Bluemaps support.", + "parent": "sign/hanging", + "textures": { + "wood": "entity/signs/hanging/spruce" + } +} \ No newline at end of file diff --git a/prismarine-viewer/viewer/prepare/data/1.20/blockModels/sign/spruce_wall_hanging.json b/prismarine-viewer/viewer/prepare/data/1.20/blockModels/sign/spruce_wall_hanging.json new file mode 100644 index 00000000..71e66b9c --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.20/blockModels/sign/spruce_wall_hanging.json @@ -0,0 +1,7 @@ +{ + "credit": "Made with Blockbench by TyBraniff for Bluemaps support.", + "parent": "sign/wall_hanging", + "textures": { + "wood": "entity/signs/hanging/spruce" + } +} \ No newline at end of file diff --git a/prismarine-viewer/viewer/prepare/data/1.20/blockModels/sign/wall_hanging.json b/prismarine-viewer/viewer/prepare/data/1.20/blockModels/sign/wall_hanging.json new file mode 100644 index 00000000..424ffe37 --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.20/blockModels/sign/wall_hanging.json @@ -0,0 +1,347 @@ +{ + "credit": "Made with Blockbench by TyBraniff for Bluemaps support.", + "ambientocclusion": false, + "texture_size": [ + 64, + 32 + ], + "textures": { + "wood": "entity/signs/hanging/oak" + }, + "elements": [ + { + "name": "Sign", + "from": [ + 1, + 0, + 7 + ], + "to": [ + 15, + 10, + 9 + ], + "rotation": { + "angle": 0, + "axis": "y", + "origin": [ + 8, + 8, + 8 + ] + }, + "faces": { + "north": { + "uv": [ + 4.5, + 7, + 8, + 12 + ], + "texture": "#wood" + }, + "east": { + "uv": [ + 4, + 7, + 4.5, + 12 + ], + "texture": "#wood" + }, + "south": { + "uv": [ + 0.5, + 7, + 4, + 12 + ], + "texture": "#wood" + }, + "west": { + "uv": [ + 0, + 7, + 0.5, + 12 + ], + "texture": "#wood" + }, + "up": { + "uv": [ + 4, + 6, + 0.5, + 7 + ], + "rotation": 180, + "texture": "#wood" + }, + "down": { + "uv": [ + 4, + 7, + 7.5, + 6 + ], + "texture": "#wood" + } + } + }, + { + "name": "Hanger", + "from": [ + 0, + 14, + 6 + ], + "to": [ + 16, + 16, + 10 + ], + "rotation": { + "angle": 0, + "axis": "y", + "origin": [ + 8, + 8, + 8 + ] + }, + "faces": { + "north": { + "uv": [ + 6, + 2, + 10, + 3 + ], + "texture": "#wood" + }, + "east": { + "uv": [ + 0, + 2, + 1, + 3 + ], + "texture": "#wood" + }, + "south": { + "uv": [ + 1, + 2, + 5, + 3 + ], + "texture": "#wood" + }, + "west": { + "uv": [ + 0, + 2, + 1, + 3 + ], + "texture": "#wood" + }, + "up": { + "uv": [ + 1, + 0, + 5, + 2 + ], + "rotation": 180, + "texture": "#wood" + }, + "down": { + "uv": [ + 5, + 0, + 9, + 2 + ], + "texture": "#wood" + } + } + }, + { + "name": "ChainA1", + "from": [ + 4.8, + 10, + 10.5 + ], + "to": [ + 6.2, + 14, + 10.5 + ], + "shade": false, + "rotation": { + "angle": -45, + "axis": "y", + "origin": [ + 8, + 8, + 8 + ], + "rescale": true + }, + "faces": { + "north": { + "uv": [ + 1.5, + 3, + 2.25, + 6 + ], + "texture": "#wood" + }, + "south": { + "uv": [ + 1.5, + 3, + 2.25, + 6 + ], + "texture": "#wood" + } + } + }, + { + "name": "ChainB2", + "from": [ + 10.5, + 10, + 4.8 + ], + "to": [ + 10.5, + 14, + 6.2 + ], + "shade": false, + "rotation": { + "angle": -45, + "axis": "y", + "origin": [ + 8, + 8, + 8 + ], + "rescale": true + }, + "faces": { + "east": { + "uv": [ + 0, + 3, + 0.75, + 6 + ], + "texture": "#wood" + }, + "west": { + "uv": [ + 0, + 3, + 0.75, + 6 + ], + "texture": "#wood" + } + } + }, + { + "name": "ChainB1", + "from": [ + 9.8, + 10, + 5.5 + ], + "to": [ + 11.2, + 14, + 5.5 + ], + "shade": false, + "rotation": { + "angle": -45, + "axis": "y", + "origin": [ + 8, + 8, + 8 + ], + "rescale": true + }, + "faces": { + "north": { + "uv": [ + 1.5, + 3, + 2.25, + 6 + ], + "texture": "#wood" + }, + "south": { + "uv": [ + 1.5, + 3, + 2.25, + 6 + ], + "texture": "#wood" + } + } + }, + { + "name": "ChainA2", + "from": [ + 5.5, + 10, + 9.8 + ], + "to": [ + 5.5, + 14, + 11.2 + ], + "shade": false, + "rotation": { + "angle": -45, + "axis": "y", + "origin": [ + 8, + 8, + 8 + ], + "rescale": true + }, + "faces": { + "east": { + "uv": [ + 0, + 3, + 0.75, + 6 + ], + "texture": "#wood" + }, + "west": { + "uv": [ + 0, + 3, + 0.75, + 6 + ], + "texture": "#wood" + } + } + } + ] +} \ No newline at end of file diff --git a/prismarine-viewer/viewer/prepare/data/1.20/blockModels/sign/warped_hanging.json b/prismarine-viewer/viewer/prepare/data/1.20/blockModels/sign/warped_hanging.json new file mode 100644 index 00000000..015ba2c0 --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.20/blockModels/sign/warped_hanging.json @@ -0,0 +1,7 @@ +{ + "credit": "Made with Blockbench by TyBraniff for Bluemaps support.", + "parent": "sign/hanging", + "textures": { + "wood": "entity/signs/hanging/warped" + } +} \ No newline at end of file diff --git a/prismarine-viewer/viewer/prepare/data/1.20/blockModels/sign/warped_wall_hanging.json b/prismarine-viewer/viewer/prepare/data/1.20/blockModels/sign/warped_wall_hanging.json new file mode 100644 index 00000000..8870c317 --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.20/blockModels/sign/warped_wall_hanging.json @@ -0,0 +1,7 @@ +{ + "credit": "Made with Blockbench by TyBraniff for Bluemaps support.", + "parent": "sign/wall_hanging", + "textures": { + "wood": "entity/signs/hanging/warped" + } +} \ No newline at end of file diff --git a/prismarine-viewer/viewer/prepare/data/1.20/blockStates/decorated_pot.json b/prismarine-viewer/viewer/prepare/data/1.20/blockStates/decorated_pot.json new file mode 100644 index 00000000..5b46a220 --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.20/blockStates/decorated_pot.json @@ -0,0 +1,19 @@ +{ + "variants": { + "facing=east": { + "model": "decorated_pot", + "y": 90 + }, + "facing=south": { + "model": "decorated_pot", + "y": 180 + }, + "facing=west": { + "model": "decorated_pot", + "y": 270 + }, + "facing=north": { + "model": "decorated_pot" + } + } +} \ No newline at end of file diff --git a/prismarine-viewer/viewer/prepare/data/1.20/blockStates/sign/acacia_hanging_sign.json b/prismarine-viewer/viewer/prepare/data/1.20/blockStates/sign/acacia_hanging_sign.json new file mode 100644 index 00000000..18a25013 --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.20/blockStates/sign/acacia_hanging_sign.json @@ -0,0 +1,67 @@ +{ + "variants": { + "rotation=0": { + "model": "sign/acacia_hanging", + "y": 180 + }, + "rotation=1": { + "model": "sign/acacia_hanging", + "y": 202.5 + }, + "rotation=2": { + "model": "sign/acacia_hanging", + "y": 225 + }, + "rotation=3": { + "model": "sign/acacia_hanging", + "y": 247.5 + }, + "rotation=4": { + "model": "sign/acacia_hanging", + "y": 270 + }, + "rotation=5": { + "model": "sign/acacia_hanging", + "y": 292.5 + }, + "rotation=6": { + "model": "sign/acacia_hanging", + "y": 315 + }, + "rotation=7": { + "model": "sign/acacia_hanging", + "y": 337.5 + }, + "rotation=8": { + "model": "sign/acacia_hanging" + }, + "rotation=9": { + "model": "sign/acacia_hanging", + "y": 22.5 + }, + "rotation=10": { + "model": "sign/acacia_hanging", + "y": 45 + }, + "rotation=11": { + "model": "sign/acacia_hanging", + "y": 67.5 + }, + "rotation=12": { + "model": "sign/acacia_hanging", + "y": 90 + }, + "rotation=13": { + "model": "sign/acacia_hanging", + "y": 112.5 + }, + "rotation=14": { + "model": "sign/acacia_hanging", + "y": 135 + }, + "rotation=15": { + "model": "sign/acacia_hanging", + "y": 157.5 + } + } +} \ No newline at end of file diff --git a/prismarine-viewer/viewer/prepare/data/1.20/blockStates/sign/acacia_wall_hanging_sign.json b/prismarine-viewer/viewer/prepare/data/1.20/blockStates/sign/acacia_wall_hanging_sign.json new file mode 100644 index 00000000..edbae40d --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.20/blockStates/sign/acacia_wall_hanging_sign.json @@ -0,0 +1,19 @@ +{ + "variants": { + "facing=east": { + "model": "sign/acacia_wall_hanging", + "y": 90 + }, + "facing=south": { + "model": "sign/acacia_wall_hanging", + "y": 180 + }, + "facing=west": { + "model": "sign/acacia_wall_hanging", + "y": 270 + }, + "facing=north": { + "model": "sign/acacia_wall_hanging" + } + } +} \ No newline at end of file diff --git a/prismarine-viewer/viewer/prepare/data/1.20/blockStates/sign/bamboo_hanging_sign.json b/prismarine-viewer/viewer/prepare/data/1.20/blockStates/sign/bamboo_hanging_sign.json new file mode 100644 index 00000000..5ff1854b --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.20/blockStates/sign/bamboo_hanging_sign.json @@ -0,0 +1,67 @@ +{ + "variants": { + "rotation=0": { + "model": "sign/bamboo_hanging", + "y": 180 + }, + "rotation=1": { + "model": "sign/bamboo_hanging", + "y": 202.5 + }, + "rotation=2": { + "model": "sign/bamboo_hanging", + "y": 225 + }, + "rotation=3": { + "model": "sign/bamboo_hanging", + "y": 247.5 + }, + "rotation=4": { + "model": "sign/bamboo_hanging", + "y": 270 + }, + "rotation=5": { + "model": "sign/bamboo_hanging", + "y": 292.5 + }, + "rotation=6": { + "model": "sign/bamboo_hanging", + "y": 315 + }, + "rotation=7": { + "model": "sign/bamboo_hanging", + "y": 337.5 + }, + "rotation=8": { + "model": "sign/bamboo_hanging" + }, + "rotation=9": { + "model": "sign/bamboo_hanging", + "y": 22.5 + }, + "rotation=10": { + "model": "sign/bamboo_hanging", + "y": 45 + }, + "rotation=11": { + "model": "sign/bamboo_hanging", + "y": 67.5 + }, + "rotation=12": { + "model": "sign/bamboo_hanging", + "y": 90 + }, + "rotation=13": { + "model": "sign/bamboo_hanging", + "y": 112.5 + }, + "rotation=14": { + "model": "sign/bamboo_hanging", + "y": 135 + }, + "rotation=15": { + "model": "sign/bamboo_hanging", + "y": 157.5 + } + } +} \ No newline at end of file diff --git a/prismarine-viewer/viewer/prepare/data/1.20/blockStates/sign/bamboo_sign.json b/prismarine-viewer/viewer/prepare/data/1.20/blockStates/sign/bamboo_sign.json new file mode 100644 index 00000000..1041460a --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.20/blockStates/sign/bamboo_sign.json @@ -0,0 +1,67 @@ +{ + "variants": { + "rotation=0": { + "model": "sign/bamboo" + }, + "rotation=1": { + "model": "sign/bamboo", + "y": 22.5 + }, + "rotation=2": { + "model": "sign/bamboo", + "y": 45 + }, + "rotation=3": { + "model": "sign/bamboo", + "y": 67.5 + }, + "rotation=4": { + "model": "sign/bamboo", + "y": 90 + }, + "rotation=5": { + "model": "sign/bamboo", + "y": 112.5 + }, + "rotation=6": { + "model": "sign/bamboo", + "y": 135 + }, + "rotation=7": { + "model": "sign/bamboo", + "y": 157.5 + }, + "rotation=8": { + "model": "sign/bamboo", + "y": 180 + }, + "rotation=9": { + "model": "sign/bamboo", + "y": 202.5 + }, + "rotation=10": { + "model": "sign/bamboo", + "y": 225 + }, + "rotation=11": { + "model": "sign/bamboo", + "y": 247.5 + }, + "rotation=12": { + "model": "sign/bamboo", + "y": 270 + }, + "rotation=13": { + "model": "sign/bamboo", + "y": 292.5 + }, + "rotation=14": { + "model": "sign/bamboo", + "y": 315 + }, + "rotation=15": { + "model": "sign/bamboo", + "y": 337.5 + } + } +} \ No newline at end of file diff --git a/prismarine-viewer/viewer/prepare/data/1.20/blockStates/sign/bamboo_wall_hanging_sign.json b/prismarine-viewer/viewer/prepare/data/1.20/blockStates/sign/bamboo_wall_hanging_sign.json new file mode 100644 index 00000000..3bd24804 --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.20/blockStates/sign/bamboo_wall_hanging_sign.json @@ -0,0 +1,19 @@ +{ + "variants": { + "facing=east": { + "model": "sign/bamboo_wall_hanging", + "y": 90 + }, + "facing=south": { + "model": "sign/bamboo_wall_hanging", + "y": 180 + }, + "facing=west": { + "model": "sign/bamboo_wall_hanging", + "y": 270 + }, + "facing=north": { + "model": "sign/bamboo_wall_hanging" + } + } +} \ No newline at end of file diff --git a/prismarine-viewer/viewer/prepare/data/1.20/blockStates/sign/bamboo_wall_sign.json b/prismarine-viewer/viewer/prepare/data/1.20/blockStates/sign/bamboo_wall_sign.json new file mode 100644 index 00000000..8b5ce481 --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.20/blockStates/sign/bamboo_wall_sign.json @@ -0,0 +1,19 @@ +{ + "variants": { + "facing=south": { + "model": "sign/bamboo_wall" + }, + "facing=west": { + "model": "sign/bamboo_wall", + "y": 90 + }, + "facing=north": { + "model": "sign/bamboo_wall", + "y": 180 + }, + "facing=east": { + "model": "sign/bamboo_wall", + "y": 270 + } + } +} \ No newline at end of file diff --git a/prismarine-viewer/viewer/prepare/data/1.20/blockStates/sign/birch_hanging_sign.json b/prismarine-viewer/viewer/prepare/data/1.20/blockStates/sign/birch_hanging_sign.json new file mode 100644 index 00000000..6052d4f7 --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.20/blockStates/sign/birch_hanging_sign.json @@ -0,0 +1,67 @@ +{ + "variants": { + "rotation=0": { + "model": "sign/birch_hanging", + "y": 180 + }, + "rotation=1": { + "model": "sign/birch_hanging", + "y": 202.5 + }, + "rotation=2": { + "model": "sign/birch_hanging", + "y": 225 + }, + "rotation=3": { + "model": "sign/birch_hanging", + "y": 247.5 + }, + "rotation=4": { + "model": "sign/birch_hanging", + "y": 270 + }, + "rotation=5": { + "model": "sign/birch_hanging", + "y": 292.5 + }, + "rotation=6": { + "model": "sign/birch_hanging", + "y": 315 + }, + "rotation=7": { + "model": "sign/birch_hanging", + "y": 337.5 + }, + "rotation=8": { + "model": "sign/birch_hanging" + }, + "rotation=9": { + "model": "sign/birch_hanging", + "y": 22.5 + }, + "rotation=10": { + "model": "sign/birch_hanging", + "y": 45 + }, + "rotation=11": { + "model": "sign/birch_hanging", + "y": 67.5 + }, + "rotation=12": { + "model": "sign/birch_hanging", + "y": 90 + }, + "rotation=13": { + "model": "sign/birch_hanging", + "y": 112.5 + }, + "rotation=14": { + "model": "sign/birch_hanging", + "y": 135 + }, + "rotation=15": { + "model": "sign/birch_hanging", + "y": 157.5 + } + } +} \ No newline at end of file diff --git a/prismarine-viewer/viewer/prepare/data/1.20/blockStates/sign/birch_wall_hanging_sign.json b/prismarine-viewer/viewer/prepare/data/1.20/blockStates/sign/birch_wall_hanging_sign.json new file mode 100644 index 00000000..656e8093 --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.20/blockStates/sign/birch_wall_hanging_sign.json @@ -0,0 +1,19 @@ +{ + "variants": { + "facing=east": { + "model": "sign/birch_wall_hanging", + "y": 90 + }, + "facing=south": { + "model": "sign/birch_wall_hanging", + "y": 180 + }, + "facing=west": { + "model": "sign/birch_wall_hanging", + "y": 270 + }, + "facing=north": { + "model": "sign/birch_wall_hanging" + } + } +} \ No newline at end of file diff --git a/prismarine-viewer/viewer/prepare/data/1.20/blockStates/sign/cherry_hanging_sign.json b/prismarine-viewer/viewer/prepare/data/1.20/blockStates/sign/cherry_hanging_sign.json new file mode 100644 index 00000000..32ce33dc --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.20/blockStates/sign/cherry_hanging_sign.json @@ -0,0 +1,67 @@ +{ + "variants": { + "rotation=0": { + "model": "sign/cherry_hanging", + "y": 180 + }, + "rotation=1": { + "model": "sign/cherry_hanging", + "y": 202.5 + }, + "rotation=2": { + "model": "sign/cherry_hanging", + "y": 225 + }, + "rotation=3": { + "model": "sign/cherry_hanging", + "y": 247.5 + }, + "rotation=4": { + "model": "sign/cherry_hanging", + "y": 270 + }, + "rotation=5": { + "model": "sign/cherry_hanging", + "y": 292.5 + }, + "rotation=6": { + "model": "sign/cherry_hanging", + "y": 315 + }, + "rotation=7": { + "model": "sign/cherry_hanging", + "y": 337.5 + }, + "rotation=8": { + "model": "sign/cherry_hanging" + }, + "rotation=9": { + "model": "sign/cherry_hanging", + "y": 22.5 + }, + "rotation=10": { + "model": "sign/cherry_hanging", + "y": 45 + }, + "rotation=11": { + "model": "sign/cherry_hanging", + "y": 67.5 + }, + "rotation=12": { + "model": "sign/cherry_hanging", + "y": 90 + }, + "rotation=13": { + "model": "sign/cherry_hanging", + "y": 112.5 + }, + "rotation=14": { + "model": "sign/cherry_hanging", + "y": 135 + }, + "rotation=15": { + "model": "sign/cherry_hanging", + "y": 157.5 + } + } +} \ No newline at end of file diff --git a/prismarine-viewer/viewer/prepare/data/1.20/blockStates/sign/cherry_sign.json b/prismarine-viewer/viewer/prepare/data/1.20/blockStates/sign/cherry_sign.json new file mode 100644 index 00000000..4e562a26 --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.20/blockStates/sign/cherry_sign.json @@ -0,0 +1,67 @@ +{ + "variants": { + "rotation=0": { + "model": "sign/cherry" + }, + "rotation=1": { + "model": "sign/cherry", + "y": 22.5 + }, + "rotation=2": { + "model": "sign/cherry", + "y": 45 + }, + "rotation=3": { + "model": "sign/cherry", + "y": 67.5 + }, + "rotation=4": { + "model": "sign/cherry", + "y": 90 + }, + "rotation=5": { + "model": "sign/cherry", + "y": 112.5 + }, + "rotation=6": { + "model": "sign/cherry", + "y": 135 + }, + "rotation=7": { + "model": "sign/cherry", + "y": 157.5 + }, + "rotation=8": { + "model": "sign/cherry", + "y": 180 + }, + "rotation=9": { + "model": "sign/cherry", + "y": 202.5 + }, + "rotation=10": { + "model": "sign/cherry", + "y": 225 + }, + "rotation=11": { + "model": "sign/cherry", + "y": 247.5 + }, + "rotation=12": { + "model": "sign/cherry", + "y": 270 + }, + "rotation=13": { + "model": "sign/cherry", + "y": 292.5 + }, + "rotation=14": { + "model": "sign/cherry", + "y": 315 + }, + "rotation=15": { + "model": "sign/cherry", + "y": 337.5 + } + } +} \ No newline at end of file diff --git a/prismarine-viewer/viewer/prepare/data/1.20/blockStates/sign/cherry_wall_hanging_sign.json b/prismarine-viewer/viewer/prepare/data/1.20/blockStates/sign/cherry_wall_hanging_sign.json new file mode 100644 index 00000000..3e0a2d04 --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.20/blockStates/sign/cherry_wall_hanging_sign.json @@ -0,0 +1,19 @@ +{ + "variants": { + "facing=east": { + "model": "sign/cherry_wall_hanging", + "y": 90 + }, + "facing=south": { + "model": "sign/cherry_wall_hanging", + "y": 180 + }, + "facing=west": { + "model": "sign/cherry_wall_hanging", + "y": 270 + }, + "facing=north": { + "model": "sign/cherry_wall_hanging" + } + } +} \ No newline at end of file diff --git a/prismarine-viewer/viewer/prepare/data/1.20/blockStates/sign/cherry_wall_sign.json b/prismarine-viewer/viewer/prepare/data/1.20/blockStates/sign/cherry_wall_sign.json new file mode 100644 index 00000000..1b13342c --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.20/blockStates/sign/cherry_wall_sign.json @@ -0,0 +1,19 @@ +{ + "variants": { + "facing=south": { + "model": "sign/cherry_wall" + }, + "facing=west": { + "model": "sign/cherry_wall", + "y": 90 + }, + "facing=north": { + "model": "sign/cherry_wall", + "y": 180 + }, + "facing=east": { + "model": "sign/cherry_wall", + "y": 270 + } + } +} \ No newline at end of file diff --git a/prismarine-viewer/viewer/prepare/data/1.20/blockStates/sign/crimson_hanging_sign.json b/prismarine-viewer/viewer/prepare/data/1.20/blockStates/sign/crimson_hanging_sign.json new file mode 100644 index 00000000..6e4131f2 --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.20/blockStates/sign/crimson_hanging_sign.json @@ -0,0 +1,67 @@ +{ + "variants": { + "rotation=0": { + "model": "sign/crimson_hanging", + "y": 180 + }, + "rotation=1": { + "model": "sign/crimson_hanging", + "y": 202.5 + }, + "rotation=2": { + "model": "sign/crimson_hanging", + "y": 225 + }, + "rotation=3": { + "model": "sign/crimson_hanging", + "y": 247.5 + }, + "rotation=4": { + "model": "sign/crimson_hanging", + "y": 270 + }, + "rotation=5": { + "model": "sign/crimson_hanging", + "y": 292.5 + }, + "rotation=6": { + "model": "sign/crimson_hanging", + "y": 315 + }, + "rotation=7": { + "model": "sign/crimson_hanging", + "y": 337.5 + }, + "rotation=8": { + "model": "sign/crimson_hanging" + }, + "rotation=9": { + "model": "sign/crimson_hanging", + "y": 22.5 + }, + "rotation=10": { + "model": "sign/crimson_hanging", + "y": 45 + }, + "rotation=11": { + "model": "sign/crimson_hanging", + "y": 67.5 + }, + "rotation=12": { + "model": "sign/crimson_hanging", + "y": 90 + }, + "rotation=13": { + "model": "sign/crimson_hanging", + "y": 112.5 + }, + "rotation=14": { + "model": "sign/crimson_hanging", + "y": 135 + }, + "rotation=15": { + "model": "sign/crimson_hanging", + "y": 157.5 + } + } +} \ No newline at end of file diff --git a/prismarine-viewer/viewer/prepare/data/1.20/blockStates/sign/crimson_wall_hanging_sign.json b/prismarine-viewer/viewer/prepare/data/1.20/blockStates/sign/crimson_wall_hanging_sign.json new file mode 100644 index 00000000..63c560ae --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.20/blockStates/sign/crimson_wall_hanging_sign.json @@ -0,0 +1,19 @@ +{ + "variants": { + "facing=east": { + "model": "sign/crimson_wall_hanging", + "y": 90 + }, + "facing=south": { + "model": "sign/crimson_wall_hanging", + "y": 180 + }, + "facing=west": { + "model": "sign/crimson_wall_hanging", + "y": 270 + }, + "facing=north": { + "model": "sign/crimson_wall_hanging" + } + } +} \ No newline at end of file diff --git a/prismarine-viewer/viewer/prepare/data/1.20/blockStates/sign/dark_oak_hanging_sign.json b/prismarine-viewer/viewer/prepare/data/1.20/blockStates/sign/dark_oak_hanging_sign.json new file mode 100644 index 00000000..a2bc0458 --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.20/blockStates/sign/dark_oak_hanging_sign.json @@ -0,0 +1,67 @@ +{ + "variants": { + "rotation=0": { + "model": "sign/dark_oak_hanging", + "y": 180 + }, + "rotation=1": { + "model": "sign/dark_oak_hanging", + "y": 202.5 + }, + "rotation=2": { + "model": "sign/dark_oak_hanging", + "y": 225 + }, + "rotation=3": { + "model": "sign/dark_oak_hanging", + "y": 247.5 + }, + "rotation=4": { + "model": "sign/dark_oak_hanging", + "y": 270 + }, + "rotation=5": { + "model": "sign/dark_oak_hanging", + "y": 292.5 + }, + "rotation=6": { + "model": "sign/dark_oak_hanging", + "y": 315 + }, + "rotation=7": { + "model": "sign/dark_oak_hanging", + "y": 337.5 + }, + "rotation=8": { + "model": "sign/dark_oak_hanging" + }, + "rotation=9": { + "model": "sign/dark_oak_hanging", + "y": 22.5 + }, + "rotation=10": { + "model": "sign/dark_oak_hanging", + "y": 45 + }, + "rotation=11": { + "model": "sign/dark_oak_hanging", + "y": 67.5 + }, + "rotation=12": { + "model": "sign/dark_oak_hanging", + "y": 90 + }, + "rotation=13": { + "model": "sign/dark_oak_hanging", + "y": 112.5 + }, + "rotation=14": { + "model": "sign/dark_oak_hanging", + "y": 135 + }, + "rotation=15": { + "model": "sign/dark_oak_hanging", + "y": 157.5 + } + } +} \ No newline at end of file diff --git a/prismarine-viewer/viewer/prepare/data/1.20/blockStates/sign/dark_oak_wall_hanging_sign.json b/prismarine-viewer/viewer/prepare/data/1.20/blockStates/sign/dark_oak_wall_hanging_sign.json new file mode 100644 index 00000000..138154f9 --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.20/blockStates/sign/dark_oak_wall_hanging_sign.json @@ -0,0 +1,19 @@ +{ + "variants": { + "facing=east": { + "model": "sign/dark_oak_wall_hanging", + "y": 90 + }, + "facing=south": { + "model": "sign/dark_oak_wall_hanging", + "y": 180 + }, + "facing=west": { + "model": "sign/dark_oak_wall_hanging", + "y": 270 + }, + "facing=north": { + "model": "sign/dark_oak_wall_hanging" + } + } +} \ No newline at end of file diff --git a/prismarine-viewer/viewer/prepare/data/1.20/blockStates/sign/jungle_hanging_sign.json b/prismarine-viewer/viewer/prepare/data/1.20/blockStates/sign/jungle_hanging_sign.json new file mode 100644 index 00000000..9f1f9eeb --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.20/blockStates/sign/jungle_hanging_sign.json @@ -0,0 +1,67 @@ +{ + "variants": { + "rotation=0": { + "model": "sign/jungle_hanging", + "y": 180 + }, + "rotation=1": { + "model": "sign/jungle_hanging", + "y": 202.5 + }, + "rotation=2": { + "model": "sign/jungle_hanging", + "y": 225 + }, + "rotation=3": { + "model": "sign/jungle_hanging", + "y": 247.5 + }, + "rotation=4": { + "model": "sign/jungle_hanging", + "y": 270 + }, + "rotation=5": { + "model": "sign/jungle_hanging", + "y": 292.5 + }, + "rotation=6": { + "model": "sign/jungle_hanging", + "y": 315 + }, + "rotation=7": { + "model": "sign/jungle_hanging", + "y": 337.5 + }, + "rotation=8": { + "model": "sign/jungle_hanging" + }, + "rotation=9": { + "model": "sign/jungle_hanging", + "y": 22.5 + }, + "rotation=10": { + "model": "sign/jungle_hanging", + "y": 45 + }, + "rotation=11": { + "model": "sign/jungle_hanging", + "y": 67.5 + }, + "rotation=12": { + "model": "sign/jungle_hanging", + "y": 90 + }, + "rotation=13": { + "model": "sign/jungle_hanging", + "y": 112.5 + }, + "rotation=14": { + "model": "sign/jungle_hanging", + "y": 135 + }, + "rotation=15": { + "model": "sign/jungle_hanging", + "y": 157.5 + } + } +} \ No newline at end of file diff --git a/prismarine-viewer/viewer/prepare/data/1.20/blockStates/sign/jungle_wall_hanging_sign.json b/prismarine-viewer/viewer/prepare/data/1.20/blockStates/sign/jungle_wall_hanging_sign.json new file mode 100644 index 00000000..3bdb8191 --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.20/blockStates/sign/jungle_wall_hanging_sign.json @@ -0,0 +1,19 @@ +{ + "variants": { + "facing=east": { + "model": "sign/jungle_wall_hanging", + "y": 90 + }, + "facing=south": { + "model": "sign/jungle_wall_hanging", + "y": 180 + }, + "facing=west": { + "model": "sign/jungle_wall_hanging", + "y": 270 + }, + "facing=north": { + "model": "sign/jungle_wall_hanging" + } + } +} \ No newline at end of file diff --git a/prismarine-viewer/viewer/prepare/data/1.20/blockStates/sign/mangrove_hanging_sign.json b/prismarine-viewer/viewer/prepare/data/1.20/blockStates/sign/mangrove_hanging_sign.json new file mode 100644 index 00000000..ff977160 --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.20/blockStates/sign/mangrove_hanging_sign.json @@ -0,0 +1,67 @@ +{ + "variants": { + "rotation=0": { + "model": "sign/mangrove_hanging", + "y": 180 + }, + "rotation=1": { + "model": "sign/mangrove_hanging", + "y": 202.5 + }, + "rotation=2": { + "model": "sign/mangrove_hanging", + "y": 225 + }, + "rotation=3": { + "model": "sign/mangrove_hanging", + "y": 247.5 + }, + "rotation=4": { + "model": "sign/mangrove_hanging", + "y": 270 + }, + "rotation=5": { + "model": "sign/mangrove_hanging", + "y": 292.5 + }, + "rotation=6": { + "model": "sign/mangrove_hanging", + "y": 315 + }, + "rotation=7": { + "model": "sign/mangrove_hanging", + "y": 337.5 + }, + "rotation=8": { + "model": "sign/mangrove_hanging" + }, + "rotation=9": { + "model": "sign/mangrove_hanging", + "y": 22.5 + }, + "rotation=10": { + "model": "sign/mangrove_hanging", + "y": 45 + }, + "rotation=11": { + "model": "sign/mangrove_hanging", + "y": 67.5 + }, + "rotation=12": { + "model": "sign/mangrove_hanging", + "y": 90 + }, + "rotation=13": { + "model": "sign/mangrove_hanging", + "y": 112.5 + }, + "rotation=14": { + "model": "sign/mangrove_hanging", + "y": 135 + }, + "rotation=15": { + "model": "sign/mangrove_hanging", + "y": 157.5 + } + } +} \ No newline at end of file diff --git a/prismarine-viewer/viewer/prepare/data/1.20/blockStates/sign/mangrove_wall_hanging_sign.json b/prismarine-viewer/viewer/prepare/data/1.20/blockStates/sign/mangrove_wall_hanging_sign.json new file mode 100644 index 00000000..9d1d019a --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.20/blockStates/sign/mangrove_wall_hanging_sign.json @@ -0,0 +1,19 @@ +{ + "variants": { + "facing=east": { + "model": "sign/mangrove_wall_hanging", + "y": 90 + }, + "facing=south": { + "model": "sign/mangrove_wall_hanging", + "y": 180 + }, + "facing=west": { + "model": "sign/mangrove_wall_hanging", + "y": 270 + }, + "facing=north": { + "model": "sign/mangrove_wall_hanging" + } + } +} \ No newline at end of file diff --git a/prismarine-viewer/viewer/prepare/data/1.20/blockStates/sign/oak_hanging_sign.json b/prismarine-viewer/viewer/prepare/data/1.20/blockStates/sign/oak_hanging_sign.json new file mode 100644 index 00000000..01e66da8 --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.20/blockStates/sign/oak_hanging_sign.json @@ -0,0 +1,67 @@ +{ + "variants": { + "rotation=0": { + "model": "sign/oak_hanging", + "y": 180 + }, + "rotation=1": { + "model": "sign/oak_hanging", + "y": 202.5 + }, + "rotation=2": { + "model": "sign/oak_hanging", + "y": 225 + }, + "rotation=3": { + "model": "sign/oak_hanging", + "y": 247.5 + }, + "rotation=4": { + "model": "sign/oak_hanging", + "y": 270 + }, + "rotation=5": { + "model": "sign/oak_hanging", + "y": 292.5 + }, + "rotation=6": { + "model": "sign/oak_hanging", + "y": 315 + }, + "rotation=7": { + "model": "sign/oak_hanging", + "y": 337.5 + }, + "rotation=8": { + "model": "sign/oak_hanging" + }, + "rotation=9": { + "model": "sign/oak_hanging", + "y": 22.5 + }, + "rotation=10": { + "model": "sign/oak_hanging", + "y": 45 + }, + "rotation=11": { + "model": "sign/oak_hanging", + "y": 67.5 + }, + "rotation=12": { + "model": "sign/oak_hanging", + "y": 90 + }, + "rotation=13": { + "model": "sign/oak_hanging", + "y": 112.5 + }, + "rotation=14": { + "model": "sign/oak_hanging", + "y": 135 + }, + "rotation=15": { + "model": "sign/oak_hanging", + "y": 157.5 + } + } +} \ No newline at end of file diff --git a/prismarine-viewer/viewer/prepare/data/1.20/blockStates/sign/oak_wall_hanging_sign.json b/prismarine-viewer/viewer/prepare/data/1.20/blockStates/sign/oak_wall_hanging_sign.json new file mode 100644 index 00000000..9af80947 --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.20/blockStates/sign/oak_wall_hanging_sign.json @@ -0,0 +1,19 @@ +{ + "variants": { + "facing=east": { + "model": "sign/oak_wall_hanging", + "y": 90 + }, + "facing=south": { + "model": "sign/oak_wall_hanging", + "y": 180 + }, + "facing=west": { + "model": "sign/oak_wall_hanging", + "y": 270 + }, + "facing=north": { + "model": "sign/oak_wall_hanging" + } + } +} \ No newline at end of file diff --git a/prismarine-viewer/viewer/prepare/data/1.20/blockStates/sign/spruce_hanging_sign.json b/prismarine-viewer/viewer/prepare/data/1.20/blockStates/sign/spruce_hanging_sign.json new file mode 100644 index 00000000..ee9509f6 --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.20/blockStates/sign/spruce_hanging_sign.json @@ -0,0 +1,67 @@ +{ + "variants": { + "rotation=0": { + "model": "sign/spruce_hanging", + "y": 180 + }, + "rotation=1": { + "model": "sign/spruce_hanging", + "y": 202.5 + }, + "rotation=2": { + "model": "sign/spruce_hanging", + "y": 225 + }, + "rotation=3": { + "model": "sign/spruce_hanging", + "y": 247.5 + }, + "rotation=4": { + "model": "sign/spruce_hanging", + "y": 270 + }, + "rotation=5": { + "model": "sign/spruce_hanging", + "y": 292.5 + }, + "rotation=6": { + "model": "sign/spruce_hanging", + "y": 315 + }, + "rotation=7": { + "model": "sign/spruce_hanging", + "y": 337.5 + }, + "rotation=8": { + "model": "sign/spruce_hanging" + }, + "rotation=9": { + "model": "sign/spruce_hanging", + "y": 22.5 + }, + "rotation=10": { + "model": "sign/spruce_hanging", + "y": 45 + }, + "rotation=11": { + "model": "sign/spruce_hanging", + "y": 67.5 + }, + "rotation=12": { + "model": "sign/spruce_hanging", + "y": 90 + }, + "rotation=13": { + "model": "sign/spruce_hanging", + "y": 112.5 + }, + "rotation=14": { + "model": "sign/spruce_hanging", + "y": 135 + }, + "rotation=15": { + "model": "sign/spruce_hanging", + "y": 157.5 + } + } +} \ No newline at end of file diff --git a/prismarine-viewer/viewer/prepare/data/1.20/blockStates/sign/spruce_wall_hanging_sign.json b/prismarine-viewer/viewer/prepare/data/1.20/blockStates/sign/spruce_wall_hanging_sign.json new file mode 100644 index 00000000..0b9ec25a --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.20/blockStates/sign/spruce_wall_hanging_sign.json @@ -0,0 +1,19 @@ +{ + "variants": { + "facing=east": { + "model": "sign/spruce_wall_hanging", + "y": 90 + }, + "facing=south": { + "model": "sign/spruce_wall_hanging", + "y": 180 + }, + "facing=west": { + "model": "sign/spruce_wall_hanging", + "y": 270 + }, + "facing=north": { + "model": "sign/spruce_wall_hanging" + } + } +} \ No newline at end of file diff --git a/prismarine-viewer/viewer/prepare/data/1.20/blockStates/sign/warped_hanging_sign.json b/prismarine-viewer/viewer/prepare/data/1.20/blockStates/sign/warped_hanging_sign.json new file mode 100644 index 00000000..93764856 --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.20/blockStates/sign/warped_hanging_sign.json @@ -0,0 +1,67 @@ +{ + "variants": { + "rotation=0": { + "model": "sign/warped_hanging", + "y": 180 + }, + "rotation=1": { + "model": "sign/warped_hanging", + "y": 202.5 + }, + "rotation=2": { + "model": "sign/warped_hanging", + "y": 225 + }, + "rotation=3": { + "model": "sign/warped_hanging", + "y": 247.5 + }, + "rotation=4": { + "model": "sign/warped_hanging", + "y": 270 + }, + "rotation=5": { + "model": "sign/warped_hanging", + "y": 292.5 + }, + "rotation=6": { + "model": "sign/warped_hanging", + "y": 315 + }, + "rotation=7": { + "model": "sign/warped_hanging", + "y": 337.5 + }, + "rotation=8": { + "model": "sign/warped_hanging" + }, + "rotation=9": { + "model": "sign/warped_hanging", + "y": 22.5 + }, + "rotation=10": { + "model": "sign/warped_hanging", + "y": 45 + }, + "rotation=11": { + "model": "sign/warped_hanging", + "y": 67.5 + }, + "rotation=12": { + "model": "sign/warped_hanging", + "y": 90 + }, + "rotation=13": { + "model": "sign/warped_hanging", + "y": 112.5 + }, + "rotation=14": { + "model": "sign/warped_hanging", + "y": 135 + }, + "rotation=15": { + "model": "sign/warped_hanging", + "y": 157.5 + } + } +} \ No newline at end of file diff --git a/prismarine-viewer/viewer/prepare/data/1.20/blockStates/sign/warped_wall_hanging_sign.json b/prismarine-viewer/viewer/prepare/data/1.20/blockStates/sign/warped_wall_hanging_sign.json new file mode 100644 index 00000000..378e80f8 --- /dev/null +++ b/prismarine-viewer/viewer/prepare/data/1.20/blockStates/sign/warped_wall_hanging_sign.json @@ -0,0 +1,19 @@ +{ + "variants": { + "facing=east": { + "model": "sign/warped_wall_hanging", + "y": 90 + }, + "facing=south": { + "model": "sign/warped_wall_hanging", + "y": 180 + }, + "facing=west": { + "model": "sign/warped_wall_hanging", + "y": 270 + }, + "facing=north": { + "model": "sign/warped_wall_hanging" + } + } +} \ No newline at end of file diff --git a/prismarine-viewer/viewer/prepare/generateTextures.ts b/prismarine-viewer/viewer/prepare/generateTextures.ts index 1a6ce3a9..025b4dca 100644 --- a/prismarine-viewer/viewer/prepare/generateTextures.ts +++ b/prismarine-viewer/viewer/prepare/generateTextures.ts @@ -6,6 +6,7 @@ import fs from 'fs-extra' import { prepareMoreGeneratedBlocks } from './moreGeneratedBlocks' import { generateItemsAtlases } from './genItemsAtlas' import { prepareWebglData } from './webglData' +import { versionToNumber } from './utils' const publicPath = path.resolve(__dirname, '../../public') @@ -30,6 +31,10 @@ Promise.resolve().then(async () => { if (!versions.includes(version)) { throw new Error(`Version ${version} is not supported by minecraft-assets`) } + if (versionToNumber(version) < versionToNumber('1.13')) { + // we normalize data to 1.13 for pre 1.13 versions + continue + } const assets = mcAssets(version) const { warnings: _warnings } = await prepareMoreGeneratedBlocks(assets) _warnings.forEach(x => warnings.add(x)) diff --git a/prismarine-viewer/viewer/prepare/modelsBuilder.ts b/prismarine-viewer/viewer/prepare/modelsBuilder.ts index 2a0cac57..b6e5268f 100644 --- a/prismarine-viewer/viewer/prepare/modelsBuilder.ts +++ b/prismarine-viewer/viewer/prepare/modelsBuilder.ts @@ -161,7 +161,7 @@ function prepareModel (model: BlockModel, texturesJson) { const getFinalTexture = (originalBlockName) => { // texture name e.g. blocks/anvil_base - const cleanBlockName = cleanupBlockName(originalBlockName); + const cleanBlockName = cleanupBlockName(originalBlockName) return { ...texturesJson[cleanBlockName], /* __debugName: cleanBlockName */ } } @@ -187,10 +187,12 @@ function prepareModel (model: BlockModel, texturesJson) { for (const sideName of Object.keys(elem.faces)) { const face = elem.faces[sideName] + const textureRaw = face.texture.charAt(0) === '#' + ? finalTextures![face.texture.slice(1)] + : getFinalTexture(face.texture) + if (!textureRaw) throw new Error(`Texture ${face.texture} in ${JSON.stringify(model.textures)} not found`) const finalTexture = deepCopy( - face.texture.charAt(0) === '#' - ? finalTextures![face.texture.slice(1)] - : getFinalTexture(face.texture) + textureRaw ) const _from = elem.from diff --git a/prismarine-viewer/viewer/prepare/moreGeneratedBlocks.ts b/prismarine-viewer/viewer/prepare/moreGeneratedBlocks.ts index e0d2ef41..e64b7cff 100644 --- a/prismarine-viewer/viewer/prepare/moreGeneratedBlocks.ts +++ b/prismarine-viewer/viewer/prepare/moreGeneratedBlocks.ts @@ -1,18 +1,17 @@ import Jimp from 'jimp' import minecraftData from 'minecraft-data' -import prismarineRegistry from 'prismarine-registry' import { McAssets } from './modelsBuilder' import path from 'path' import fs from 'fs' import { fileURLToPath } from 'url' +import { versionToNumber } from './utils' // todo refactor -const twoTileTextures: string[] = [] +const handledBlocks = ['water', 'lava', 'barrier'] +const origSizeTextures: string[] = [] let currentImage: Jimp let currentBlockName: string let currentMcAssets: McAssets -let isPreFlattening = false -const postFlatenningRegistry = prismarineRegistry('1.13') const __dirname = path.dirname(fileURLToPath(new URL(import.meta.url))) type SidesType = { @@ -24,9 +23,9 @@ type SidesType = { "down": string } -const getBlockStates = (name: string, postFlatenningName = name) => { - const mcData = isPreFlattening ? postFlatenningRegistry : minecraftData(currentMcAssets.version) - return mcData.blocksByName[isPreFlattening ? postFlatenningName : name]?.states +const getBlockStates = (name: string) => { + const mcData = minecraftData(currentMcAssets.version) + return mcData.blocksByName[name]?.states } export const addBlockCustomSidesModel = (name: string, sides: SidesType) => { @@ -85,7 +84,7 @@ const getBlockTexturesFromJimp = async > (sides: for (const [side, jimp] of Object.entries(sides)) { const textureName = `${textureNameBase}_${side}` const sideTexture = withUv ? { uv: [0, 0, jimp.getWidth(), jimp.getHeight()], texture: textureName } : textureName - const base64 = await jimp.getBase64Async(jimp.getMIME()) + const base64Url = await jimp.getBase64Async(jimp.getMIME()) if (side === 'side') { sidesTextures['north'] = sideTexture sidesTextures['east'] = sideTexture @@ -94,7 +93,7 @@ const getBlockTexturesFromJimp = async > (sides: } else { sidesTextures[side] = sideTexture } - generatedImageTextures[textureName] = base64 + generatedImageTextures[textureName] = base64Url } return sidesTextures @@ -123,115 +122,7 @@ const handleShulkerBox = async (dataBase: string, match: RegExpExecArray) => { await addSimpleCubeWithSides(shulkerBoxTextures) } -const handleSign = async (dataBase: string, match: RegExpExecArray) => { - const states = getBlockStates(currentBlockName, currentBlockName === 'wall_sign' ? 'wall_sign' : 'sign') - if (!states) return - - const [, signMaterial = ''] = match - currentImage = await Jimp.read(`${dataBase}entity/${signMaterial ? `signs/${signMaterial}` : 'sign'}.png`) - // todo cache - const signTextures = { - // todo correct mapping - // todo alg to fit to the side - signboard_side: justCrop(0, 2, 2, 12), - face: justCrop(2, 2, 24, 12), - up: justCrop(2, 0, 24, 2), - support: justCrop(0, 16, 2, 14) - } - const blockTextures = await getBlockTexturesFromJimp(signTextures, true) - - const isWall = currentBlockName.includes('wall_') - const isHanging = currentBlockName.includes('hanging_') - const rotationState = states.find(state => state.name === 'rotation') - const faceTexture = { texture: blockTextures.face.texture, uv: blockTextures.face.uv } - if (isWall || isHanging) { - // todo isHanging - if (!isHanging) { - const facingState = states.find(state => state.name === 'facing')! - const facingMap = { - south: 0, - west: 90, - north: 180, - east: 270 - } - - currentMcAssets.blocksStates[currentBlockName] = { - "variants": Object.fromEntries( - facingState.values!.map((_val, i) => { - const val = _val as string - return [`facing=${val}`, { - "model": currentBlockName, - y: facingMap[val], - }] - }) - ) - } - currentMcAssets.blocksModels[currentBlockName] = { - elements: [ - { - // signboard - "from": [0, 4.5, 0], - "to": [16, 11.5, 1.5], - faces: { - south: faceTexture, - east: blockTextures.signboard_side, - west: blockTextures.signboard_side, - up: blockTextures.up, - down: blockTextures.up, - }, - } - ], - } - } - } else if (rotationState) { - currentMcAssets.blocksStates[currentBlockName] = { - "variants": Object.fromEntries( - Array.from({ length: 16 }).map((_val, i) => { - return [`rotation=${i}`, { - "model": currentBlockName, - y: i * (45 / 2), - }] - }) - ) - } - - const supportTexture = blockTextures.support - // TODO fix models.ts, apply textures for signs correctly! - // const supportTexture = { texture: supportTextureImg, uv: [0, 0, 16, 16] } - currentMcAssets.blocksModels[currentBlockName] = { - elements: [ - { - // support post - "from": [7.5, 0, 7.5], - "to": [8.5, 9, 8.5], - faces: { - // todo 14 - north: supportTexture, - east: supportTexture, - south: supportTexture, - west: supportTexture, - } - }, - { - // signboard - "from": [0, 9, 7.25], - "to": [16, 16, 8.75], - faces: { - north: faceTexture, - south: faceTexture, - east: blockTextures.signboard_side, - west: blockTextures.signboard_side, - up: blockTextures.up, - down: blockTextures.up, - }, - } - ], - } - } - twoTileTextures.push(blockTextures.face.texture) - twoTileTextures.push(blockTextures.up.texture) -} - +// TODO! should not be there! move to data with signs! const chestModels = { chest: { "parent": "block/block", @@ -375,17 +266,11 @@ const handleChest = async (dataBase: string, match: RegExpExecArray) => { if (modelName.endsWith('_left')) chestTextureName = `${chestTextureName}_left` if (modelName.endsWith('_right')) chestTextureName = `${chestTextureName}_right` - // reading latest version since the texture wasn't changed, but in pre-flatenning need custom mapping for doubled_chest const texture = path.join(currentMcAssets.directory, `../1.19.1/entity/chest/${chestTextureName}.png`) currentImage = await Jimp.read(texture) const model = structuredClone(chestModels[modelName]) - // todo < 1.9 - if (currentMcAssets.version === '1.8.8') { - // doesn't have definition of block yet - model.parent = undefined - } model.textures.particle = particle const newModelName = `${currentBlockName}_${modelName}` for (const variant of blockStatesVariants) { @@ -410,15 +295,21 @@ const handleChest = async (dataBase: string, match: RegExpExecArray) => { currentMcAssets.blocksStates[currentBlockName] = blockStates } +async function loadBlockModelTextures (dataBase: string, blockModel: any) { + for (const key in blockModel.textures) { + let texture: string = blockModel.textures[key] + const useAssetsPath = !!texture.match(/^[0-9.]+\//) + blockModel.textures.particle = texture + generatedImageTextures[texture] = `data:image/png;base64,${fs.readFileSync(path.join(dataBase, useAssetsPath ? '..' : '', texture + '.png'), 'base64')}` + origSizeTextures[texture] = true + } +} + const handlers = [ [/(.+)_shulker_box$/, handleShulkerBox], [/^shulker_box$/, handleShulkerBox], - [/^sign$/, handleSign], - [/^standing_sign$/, handleSign], - [/^wall_sign$/, handleSign], - [/(.+)_wall_sign$/, handleSign], - [/(.+)_sign$/, handleSign], [/^(?:(ender|trapped)_)?chest$/, handleChest], + // [/(^|(.+)_)bed$/, handleBed], // no-op just suppress warning [/(^light|^moving_piston$)/, true], ] as const @@ -435,13 +326,66 @@ export const tryHandleBlockEntity = async (dataBase, blockName) => { } } +async function readAllBlockStates (blockStatesDir: string) { + const files = fs.readdirSync(blockStatesDir) + for (const file of files) { + if (file.endsWith('.json')) { + const state = JSON.parse(fs.readFileSync(path.join(blockStatesDir, file), 'utf-8')) + const name = file.replace('.json', '') + currentMcAssets.blocksStates[name] = state + handledBlocks.push(name) + } else { + await readAllBlockStates(path.join(blockStatesDir, file)) + } + } +} + +async function readAllBlockModels (dataBase: string, blockModelsDir: string, completePath: string) { + const actualPath = completePath.length ? completePath + "/" : "" + const files = fs.readdirSync(blockModelsDir) + for (const file of files) { + if (file.endsWith('.json')) { + const model = JSON.parse(fs.readFileSync(path.join(blockModelsDir, file), 'utf-8')) + const name = actualPath + file.replace('.json', '') + currentMcAssets.blocksModels[name] = model + await loadBlockModelTextures(dataBase, model) + } else { + await readAllBlockModels(dataBase, path.join(blockModelsDir, file), actualPath + file) + } + } +} + +const handleExternalData = async (assetsPathRoot: string, version: string) => { + const currentVersionNumber = versionToNumber(version) + const versions = fs.readdirSync(path.join(__dirname, 'data'), { withFileTypes: true }) + .filter(x => x.isDirectory()) + .map(x => x.name) + .sort((a, b) => versionToNumber(b) - versionToNumber(a)) + + const allAssetsVersions = fs.readdirSync(assetsPathRoot, { withFileTypes: true }) + .filter(x => x.isDirectory()) + .map(x => x.name) + .sort((a, b) => versionToNumber(b) - versionToNumber(a)) + + const getAssetsVersion = (version: string) => { + return allAssetsVersions[version] ?? allAssetsVersions.find(x => x.startsWith(version)) + } + + for (const curVer of versions) { + const baseDir = path.join(__dirname, 'data', curVer) + if (versionToNumber(curVer) > currentVersionNumber) continue + + const assetsVersion = getAssetsVersion(curVer) + await readAllBlockStates(path.join(baseDir, 'blockStates')) + await readAllBlockModels(path.join(assetsPathRoot, assetsVersion), path.join(baseDir, 'blockModels'), "") + } +} + export const prepareMoreGeneratedBlocks = async (mcAssets: McAssets) => { const mcData = minecraftData(mcAssets.version) - isPreFlattening = !mcData.supportFeature('blockStateId') const allTheBlocks = mcData.blocksArray.map(x => x.name) currentMcAssets = mcAssets - const handledBlocks = ['water', 'lava', 'barrier'] // todo const ignoredBlocks = ['skull', 'structure_void', 'banner', 'bed', 'end_portal'] @@ -456,6 +400,8 @@ export const prepareMoreGeneratedBlocks = async (mcAssets: McAssets) => { } } + await handleExternalData(path.join(mcAssets.directory, '..'), mcAssets.version) + const warnings: string[] = [] for (const [name, model] of Object.entries(mcAssets.blocksModels)) { if (Object.keys(model).length === 1 && model.textures) { @@ -471,5 +417,5 @@ export const prepareMoreGeneratedBlocks = async (mcAssets: McAssets) => { } export const getAdditionalTextures = () => { - return { generated: generatedImageTextures, twoTileTextures } + return { generated: generatedImageTextures, origSizeTextures } } diff --git a/scripts/downloadSoundsMap.mjs b/scripts/downloadSoundsMap.mjs index 066a3df7..3c335f8f 100644 --- a/scripts/downloadSoundsMap.mjs +++ b/scripts/downloadSoundsMap.mjs @@ -1,6 +1,6 @@ import fs from 'fs' -const url = 'https://github.com/zardoy/prismarine-web-client/raw/sounds-generated/sounds.js' +const url = 'https://github.com/zardoy/minecraft-web-client/raw/sounds-generated/sounds.js' const savePath = 'dist/sounds.js' fetch(url).then(res => res.text()).then(data => { fs.writeFileSync(savePath, data, 'utf8') diff --git a/scripts/esbuildPlugins.mjs b/scripts/esbuildPlugins.mjs index 04c1a459..b36f0214 100644 --- a/scripts/esbuildPlugins.mjs +++ b/scripts/esbuildPlugins.mjs @@ -7,7 +7,9 @@ import { filesize } from 'filesize' import MCProtocol from 'minecraft-protocol' import MCData from 'minecraft-data' import { throttle } from 'lodash-es' +import { fileURLToPath } from 'url' +const __dirname = dirname(fileURLToPath(new URL(import.meta.url))) const { supportedVersions } = MCProtocol const prod = process.argv.includes('--prod') @@ -34,8 +36,27 @@ export const startWatchingHmr = () => { } } +/** @type {import('esbuild').Plugin[]} */ +const mesherSharedPlugins = [ + { + name: 'minecraft-data', + setup(build) { + build.onLoad({ + filter: /data[\/\\]pc[\/\\]common[\/\\]legacy.json$/, + }, async (args) => { + const data = fs.readFileSync(join(__dirname, '../src/preflatMap.json'), 'utf8') + return { + contents: `module.exports = ${data}`, + loader: 'js', + } + }) + } + } +] + /** @type {import('esbuild').Plugin[]} */ const plugins = [ + ...mesherSharedPlugins, { name: 'strict-aliases', setup(build) { @@ -108,7 +129,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 @@ -344,4 +365,4 @@ const plugins = [ }) ] -export { plugins, connectedClients as clients } +export { plugins, connectedClients as clients, mesherSharedPlugins } diff --git a/scripts/genPixelartTypes.ts b/scripts/genPixelartTypes.ts new file mode 100644 index 00000000..e7c9649a --- /dev/null +++ b/scripts/genPixelartTypes.ts @@ -0,0 +1,16 @@ +import fs from 'fs' + +const icons = fs.readdirSync('node_modules/pixelarticons/svg') + +const addIconPath = '../../node_modules/pixelarticons/svg/' + +let str = 'export type PixelartIconsGenerated = {\n' +for (const icon of icons) { + const name = icon.replace('.svg', '') + // jsdoc + const jsdocImage = '![image](' + addIconPath + icon + ')' + str += ` /** ${jsdocImage} */\n` + str += ` '${name}': string;\n` +} +str += '}\n' +fs.writeFileSync('./src/react/pixelartIcons.generated.ts', str, 'utf8') diff --git a/scripts/getMissingRecipes.mjs b/scripts/getMissingRecipes.mjs new file mode 100644 index 00000000..59e78672 --- /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/scripts/prepareSounds.mjs b/scripts/prepareSounds.mjs index 4ed119cb..8f3e5bef 100644 --- a/scripts/prepareSounds.mjs +++ b/scripts/prepareSounds.mjs @@ -61,7 +61,7 @@ const downloadAllSounds = async () => { } prevSounds = soundAssets } - async function downloadSound ({ name, hash, size }, namePath, log) { + async function downloadSound({ name, hash, size }, namePath, log) { const savePath = path.resolve(`generated/sounds/${namePath}`) if (fs.existsSync(savePath)) { // console.log('skipped', name) @@ -86,7 +86,7 @@ const downloadAllSounds = async () => { } writer.close() } - async function downloadSounds (assets, addPath = '') { + async function downloadSounds(assets, addPath = '') { for (let i = 0; i < assets.length; i += 5) { await Promise.all(assets.slice(i, i + 5).map((asset, j) => downloadSound(asset, `${addPath}${asset.name}`, () => { console.log('downloading', addPath, asset.name, i + j, '/', assets.length) @@ -135,7 +135,7 @@ const convertSounds = async () => { } const CONCURRENCY = 5 - for(let i = 0; i < toConvert.length; i += CONCURRENCY) { + for (let i = 0; i < toConvert.length; i += CONCURRENCY) { await Promise.all(toConvert.slice(i, i + CONCURRENCY).map((oggPath, j) => convertSound(i + j))) } } @@ -221,7 +221,7 @@ const makeSoundsBundle = async () => { const allSoundsMeta = { format: 'mp3', - baseUrl: 'https://raw.githubusercontent.com/zardoy/prismarine-web-client/sounds-generated/sounds/' + baseUrl: 'https://raw.githubusercontent.com/zardoy/minecraft-web-client/sounds-generated/sounds/' } await build({ diff --git a/scripts/replaceFavicon.mjs b/scripts/replaceFavicon.mjs new file mode 100644 index 00000000..0c60d26d --- /dev/null +++ b/scripts/replaceFavicon.mjs @@ -0,0 +1,8 @@ +import fs from 'fs' + +const faviconUrl = process.argv[2] + +// save to assets/favicon.png +fetch(faviconUrl).then(res => res.arrayBuffer()).then(buffer => { + fs.writeFileSync('assets/favicon.png', Buffer.from(buffer)) +}) diff --git a/src/botUtils.ts b/src/botUtils.ts index fb4000a1..79b10118 100644 --- a/src/botUtils.ts +++ b/src/botUtils.ts @@ -101,7 +101,7 @@ export const formatMessage = (message: MessageInput, mcData: IndexedData = globa msglist = msglist.map(msg => { // normalize § - if (!msg.text.includes('§')) return msg + if (!msg.text.includes?.('§')) return msg const newMsg = fromFormattedString(msg.text) return flat(newMsg) }).flat(Infinity) @@ -117,6 +117,6 @@ const blockToItemRemaps = { } export const getItemFromBlock = (block: import('prismarine-block').Block) => { - const item = global.mcData.itemsByName[blockToItemRemaps[block.name] ?? block.name] + const item = global.loadedData.itemsByName[blockToItemRemaps[block.name] ?? block.name] return item } diff --git a/src/connect.ts b/src/connect.ts index 5e4df859..12a1fc5b 100644 --- a/src/connect.ts +++ b/src/connect.ts @@ -1,15 +1,18 @@ +import { AuthenticatedAccount } from './react/ServersListProvider' + export type ConnectOptions = { - server?: string; - singleplayer?: any; - username: string; - password?: any; - proxy?: any; - botVersion?: any; - serverOverrides?; - serverOverridesFlat?; - peerId?: string; - ignoreQs?: boolean; + server?: string + singleplayer?: any + username: string + proxy?: string + botVersion?: any + serverOverrides? + serverOverridesFlat? + peerId?: string + ignoreQs?: boolean onSuccessfulPlay?: () => void autoLoginPassword?: string serverIndex?: string + /** If true, will show a UI to authenticate with a new account */ + authenticatedAccount?: AuthenticatedAccount | true } diff --git a/src/controls.ts b/src/controls.ts index f3d6a4cb..f4e607a9 100644 --- a/src/controls.ts +++ b/src/controls.ts @@ -6,21 +6,25 @@ import { proxy, subscribe } from 'valtio' import { ControMax } from 'contro-max/build/controMax' import { CommandEventArgument, SchemaCommandInput } from 'contro-max/build/types' import { stringStartsWith } from 'contro-max/build/stringUtils' +import { UserOverrideCommand, UserOverridesConfig } from 'contro-max/build/types/store' import { isGameActive, showModal, gameAdditionalState, activeModalStack, hideCurrentModal, miscUiState } from './globalState' import { goFullscreen, pointerLock, reloadChunks } from './utils' import { options } from './optionsStorage' import { openPlayerInventory } from './inventoryWindows' import { chatInputValueGlobal } from './react/Chat' import { fsState } from './loadSave' +import { customCommandsConfig } from './customCommands' +import { CustomCommand } from './react/KeybindingsCustom' import { showOptionsModal } from './react/SelectOption' import widgets from './react/widgets' import { getItemFromBlock } from './botUtils' import { gamepadUiCursorState, moveGamepadCursorByPx } from './react/GamepadUiCursor' +import { updateBinds } from './react/KeybindingsScreenProvider' -// todo move this to shared file with component -export const customKeymaps = proxy(JSON.parse(localStorage.keymap || '{}')) + +export const customKeymaps = proxy(JSON.parse(localStorage.keymap || '{}')) as UserOverridesConfig subscribe(customKeymaps, () => { - localStorage.keymap = JSON.parse(customKeymaps) + localStorage.keymap = JSON.stringify(customKeymaps) }) const controlOptions = { @@ -36,12 +40,13 @@ export const contro = new ControMax({ sneak: ['ShiftLeft'], toggleSneakOrDown: [null, 'Right Stick'], sprint: ['ControlLeft', 'Left Stick'], - nextHotbarSlot: [null, 'Left Bumper'], - prevHotbarSlot: [null, 'Right Bumper'], + nextHotbarSlot: [null, 'Right Bumper'], + prevHotbarSlot: [null, 'Left Bumper'], attackDestroy: [null, 'Right Trigger'], interactPlace: [null, 'Left Trigger'], chat: [['KeyT', 'Enter']], command: ['Slash'], + swapHands: ['KeyF'], selectItem: ['KeyH'] // default will be removed }, ui: { @@ -53,7 +58,8 @@ export const contro = new ControMax({ }, advanced: { lockUrl: ['KeyY'], - } + }, + custom: {} as Record, // waila: { // showLookingBlockRecipe: ['Numpad3'], // showLookingBlockUsages: ['Numpad4'] @@ -81,10 +87,16 @@ export const contro = new ControMax({ window.controMax = contro export type Command = CommandEventArgument['command'] -export const setDoPreventDefault = (state: boolean) => { - controlOptions.preventDefault = state +updateBinds(customKeymaps) + +const updateDoPreventDefault = () => { + controlOptions.preventDefault = miscUiState.gameLoaded && !activeModalStack.length } +subscribe(miscUiState, updateDoPreventDefault) +subscribe(activeModalStack, updateDoPreventDefault) +updateDoPreventDefault() + const setSprinting = (state: boolean) => { bot.setControlState('sprint', state) gameAdditionalState.isSprinting = state @@ -285,6 +297,19 @@ function cycleHotbarSlot (dir: 1 | -1) { bot.setQuickBarSlot(newHotbarSlot) } +// custom commands handler +const customCommandsHandler = ({ command }) => { + const [section, name] = command.split('.') + if (!isGameActive(true) || section !== 'custom') return + + if (contro.userConfig?.custom) { + customCommandsConfig[(contro.userConfig.custom[name] as CustomCommand).type].handler( + (contro.userConfig.custom[name] as CustomCommand).inputs + ) + } +} +contro.on('trigger', customCommandsHandler) + contro.on('trigger', ({ command }) => { const willContinue = !isGameActive(true) alwaysPressedHandledCommand(command) @@ -313,6 +338,14 @@ contro.on('trigger', ({ command }) => { case 'general.toggleSneakOrDown': case 'general.sprint': case 'general.attackDestroy': + case 'general.swapHands': { + bot._client.write('entity_action', { + entityId: bot.entity.id, + actionId: 6, + jumpBoost: 0 + }) + break + } case 'general.interactPlace': // handled in onTriggerOrReleased break @@ -622,6 +655,24 @@ window.addEventListener('keydown', (e) => { } }) +window.addEventListener('keydown', (e) => { + if (e.code !== 'F2' || e.repeat || !isGameActive(true)) return + e.preventDefault() + const canvas = document.getElementById('viewer-canvas') as HTMLCanvasElement + if (!canvas) return + const link = document.createElement('a') + link.href = canvas.toDataURL('image/png') + const date = new Date() + link.download = `screenshot ${date.toLocaleString().replaceAll('.', '-').replace(',', '')}.png` + link.click() +}) + +window.addEventListener('keydown', (e) => { + if (e.code !== 'F1' || e.repeat || !isGameActive(true)) return + e.preventDefault() + miscUiState.showUI = !miscUiState.showUI +}) + // #region experimental debug things window.addEventListener('keydown', (e) => { if (e.code === 'F11') { diff --git a/src/createLocalServer.ts b/src/createLocalServer.ts index 44bc2187..d0beac9a 100644 --- a/src/createLocalServer.ts +++ b/src/createLocalServer.ts @@ -14,4 +14,4 @@ export const startLocalServer = (serverOptions) => { // features that flying-squid doesn't support at all // todo move & generate in flying-squid -export const unsupportedLocalServerFeatures = ['transactionPacketExists', 'teleportUsesOwnPacket', 'dimensionDataIsAvailable'] +export const unsupportedLocalServerFeatures = ['transactionPacketExists', 'teleportUsesOwnPacket'] diff --git a/src/cross_playstation_console_controller_gamepad_icon.svg b/src/cross_playstation_console_controller_gamepad_icon.svg new file mode 100644 index 00000000..d7d176e2 --- /dev/null +++ b/src/cross_playstation_console_controller_gamepad_icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/crypto.js b/src/crypto.js index 9034a397..c8470c86 100644 --- a/src/crypto.js +++ b/src/crypto.js @@ -1,2 +1,44 @@ +//@ts-check export * from 'crypto-browserify' -export function createPublicKey () { } +export function createPublicKey() { } + +// CUSTOM SIGN ASYNC IMPLEMENTATION FOR BROWSERS + +export async function sign(signatureAlgorithm, data, privateKeyPem) { + if (signatureAlgorithm !== 'RSA-SHA256') throw new Error(`Unsupported signature algorithm ${signatureAlgorithm}`) + if (typeof privateKeyPem !== 'string') throw new Error(`This implementation only supports strings as private keys`) + const privateKeyBuffer = pemToArrayBuffer(privateKeyPem) + const privateKey = await crypto.subtle.importKey( + 'pkcs8', // Chrome has difficulties with pkcs1 and the recommended format is pkcs8 + privateKeyBuffer, + { + name: 'RSASSA-PKCS1-v1_5', + hash: { name: 'SHA-256' }, + }, + true, + ['sign'] + ) + const signature = await crypto.subtle.sign( + { + name: 'RSASSA-PKCS1-v1_5', + hash: { name: 'SHA-256' }, // SHA-256 hash function + }, + privateKey, + data + ) + return signature +} + +function pemToArrayBuffer(pem) { + pem = pem.trim() + const pemHeader = '-----BEGIN RSA PRIVATE KEY-----' + const pemFooter = '-----END RSA PRIVATE KEY-----' + const pemContents = pem.slice(pemHeader.length, pem.length - pemFooter.length).trim() + const binaryDerString = atob(pemContents.replaceAll(/\s/g, '')) + const binaryDer = new Uint8Array(binaryDerString.length) + for (let i = 0; i < binaryDerString.length; i++) { + //@ts-expect-error + binaryDer[i] = binaryDerString.codePointAt(i) + } + return binaryDer.buffer +} diff --git a/src/customClient.js b/src/customClient.js index e349a837..75da50aa 100644 --- a/src/customClient.js +++ b/src/customClient.js @@ -7,12 +7,12 @@ const states = require('minecraft-protocol/src/states') window.serverDataChannel ??= {} export const customCommunication = { - sendData (data) { + sendData(data) { setTimeout(() => { window.serverDataChannel[this.isServer ? 'emitClient' : 'emitServer'](data) }) }, - receiverSetup (processData) { + receiverSetup(processData) { window.serverDataChannel[this.isServer ? 'emitServer' : 'emitClient'] = (data) => { processData(data) } @@ -20,18 +20,18 @@ export const customCommunication = { } class CustomChannelClient extends EventEmitter { - constructor (isServer, version) { + constructor(isServer, version) { super() this.version = version this.isServer = !!isServer this.state = states.HANDSHAKING } - get state () { + get state() { return this.protocolState } - setSerializer (state) { + setSerializer(state) { customCommunication.receiverSetup.call(this, (/** @type {{name, params, state?}} */parsed) => { if (!options.excludeCommunicationDebugEvents.includes(parsed.name)) { debug(`receive in ${this.isServer ? 'server' : 'client'}: ${parsed.name}`) @@ -42,7 +42,7 @@ class CustomChannelClient extends EventEmitter { } // eslint-disable-next-line @typescript-eslint/adjacent-overload-signatures, grouped-accessor-pairs - set state (newProperty) { + set state(newProperty) { const oldProperty = this.protocolState this.protocolState = newProperty @@ -51,12 +51,12 @@ class CustomChannelClient extends EventEmitter { this.emit('state', newProperty, oldProperty) } - end (reason) { + end(reason) { this._endReason = reason this.emit('end', this._endReason) // still emits on server side only, doesn't send anything to our client } - write (name, params) { + write(name, params) { if(!options.excludeCommunicationDebugEvents.includes(name)) { debug(`[${this.state}] from ${this.isServer ? 'server' : 'client'}: ` + name) debug(params) @@ -66,11 +66,11 @@ class CustomChannelClient extends EventEmitter { customCommunication.sendData.call(this, { name, params, state: this.state }) } - writeBundle (packets) { + writeBundle(packets) { // no-op } - writeRaw (buffer) { + writeRaw(buffer) { // no-op } } diff --git a/src/customCommands.ts b/src/customCommands.ts new file mode 100644 index 00000000..eddfc89e --- /dev/null +++ b/src/customCommands.ts @@ -0,0 +1,91 @@ +import { guiOptionsScheme, tryFindOptionConfig } from './optionsGuiScheme' +import { options } from './optionsStorage' + +export const customCommandsConfig = { + chat: { + input: [ + { + type: 'text', + placeholder: 'Command to send e.g. gamemode creative' + } + ], + handler ([command]) { + bot.chat(`/${command.replace(/^\//, '')}`) + } + }, + setOrToggleSetting: { + input: [ + { + type: 'select', + // maybe title case? + options: Object.keys(options) + }, + { + type: 'select', + options: ['toggle', 'set'] + }, + ([setting = '', action = ''] = []) => { + const value = options[setting] + if (!action || value === undefined || action === 'toggle') return null + if (action === 'set') { + const getBase = () => { + const config = tryFindOptionConfig(setting as any) + if (config && 'values' in config) { + return { + type: 'select', + options: config.values + } + } + if (config?.type === 'toggle' || typeof value === 'boolean') { + return { + type: 'select', + options: ['true', 'false'] + } + } + if (config?.type === 'slider' || value.type === 'number') { + return { + type: 'number', + } + } + return { + type: 'text' + } + } + return { + ...getBase(), + placeholder: value + } + } + } + ], + handler ([setting, action, value]) { + if (action === 'toggle') { + const value = options[setting] + const config = tryFindOptionConfig(setting) + if (config && 'values' in config && config.values) { + const { values } = config + const currentIndex = values.indexOf(value) + const nextIndex = (currentIndex + 1) % values.length + options[setting] = values[nextIndex] + } else { + options[setting] = typeof value === 'boolean' ? !value : typeof value === 'number' ? value + 1 : value + } + } else { + options[setting] = value + } + } + }, + jsScripts: { + input: [ + { + type: 'text', + placeholder: 'JavaScript code to run in main thread (sensitive!)' + } + ], + handler ([code]) { + // eslint-disable-next-line no-new-func -- this is a feature, not a bug + new Function(code)() + } + }, + // openCommandsScreen: {} +} diff --git a/src/dayCycle.ts b/src/dayCycle.ts index 2f59b530..f092c465 100644 --- a/src/dayCycle.ts +++ b/src/dayCycle.ts @@ -3,7 +3,7 @@ import { assertDefined } from './utils' import { updateBackground } from './water' export default () => { - bot.on('time', () => { + const timeUpdated = () => { assertDefined(viewer) // 0 morning const dayTotal = 24_000 @@ -40,5 +40,8 @@ export default () => { viewer.ambientLight.intensity = Math.max(int, 0.25) viewer.directionalLight.intensity = Math.min(int, 0.5) } - }) + } + + bot.on('time', timeUpdated) + timeUpdated() } diff --git a/src/devtools.ts b/src/devtools.ts index 4dbeb51d..73436347 100644 --- a/src/devtools.ts +++ b/src/devtools.ts @@ -29,7 +29,7 @@ window.len = (obj) => Object.keys(obj).length window.inspectPacket = (packetName, full = false) => { const listener = (...args) => console.log('packet', packetName, full ? args : args[0]) const attach = () => { - bot?.on(packetName, listener) + bot?._client.on(packetName, listener) } attach() customEvents.on('mineflayerBotCreated', attach) @@ -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) diff --git a/src/entities.ts b/src/entities.ts index dbb59bed..b238d4ea 100644 --- a/src/entities.ts +++ b/src/entities.ts @@ -14,6 +14,7 @@ const updateAutoJump = () => { jumpOnAllEdges: options.autoParkour, // strictBlockCollision: true, }) + if (autoJump === bot.autoJumper.enabled) return if (autoJump) { bot.autoJumper.enable() } else { diff --git a/src/errorLoadingScreenHelpers.ts b/src/errorLoadingScreenHelpers.ts new file mode 100644 index 00000000..2f882f89 --- /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/flyingSquidUtils.ts b/src/flyingSquidUtils.ts index bd312bfb..1f070022 100644 --- a/src/flyingSquidUtils.ts +++ b/src/flyingSquidUtils.ts @@ -33,7 +33,6 @@ export const saveServer = async (autoSave = true) => { export const disconnect = async () => { if (localServer) { await saveServer() - //@ts-expect-error todo expose! void localServer.quit() // todo investigate we should await } window.history.replaceState({}, '', `${window.location.pathname}`) // remove qs diff --git a/src/getCollisionShapes.ts b/src/getCollisionShapes.ts index 0faf5b6a..4ee0e802 100644 --- a/src/getCollisionShapes.ts +++ b/src/getCollisionShapes.ts @@ -1,4 +1,4 @@ -import { adoptBlockOrItemNamesFromLatest } from 'flying-squid/dist/blockRenames' +import { getRenamedData } from 'flying-squid/dist/blockRenames' import collisionShapesInit from '../generated/latestBlockCollisionsShapes.json' import outputInteractionShapesJson from './interactionShapesGenerated.json' @@ -6,7 +6,7 @@ import outputInteractionShapesJson from './interactionShapesGenerated.json' window.globalGetCollisionShapes = (version) => { // todo use the same in resourcepack const versionFrom = collisionShapesInit.version - const renamedBlocks = adoptBlockOrItemNamesFromLatest('blocks', Object.keys(collisionShapesInit.blocks), versionFrom, version) + const renamedBlocks = getRenamedData('blocks', Object.keys(collisionShapesInit.blocks), versionFrom, version) const collisionShapes = { ...collisionShapesInit, blocks: Object.fromEntries(Object.entries(collisionShapesInit.blocks).map(([, shape], i) => [renamedBlocks[i], shape])) @@ -17,7 +17,7 @@ window.globalGetCollisionShapes = (version) => { export default () => { customEvents.on('gameLoaded', () => { // todo also remap block states (e.g. redstone)! - const renamedBlocksInteraction = adoptBlockOrItemNamesFromLatest('blocks', Object.keys(outputInteractionShapesJson), '1.20.2', bot.version) + const renamedBlocksInteraction = getRenamedData('blocks', Object.keys(outputInteractionShapesJson), '1.20.2', bot.version) const interactionShapes = { ...outputInteractionShapesJson, ...Object.fromEntries(Object.entries(outputInteractionShapesJson).map(([block, shape], i) => [renamedBlocksInteraction[i], shape])) diff --git a/src/globalState.ts b/src/globalState.ts index 1b0526f9..b7dc1602 100644 --- a/src/globalState.ts +++ b/src/globalState.ts @@ -8,7 +8,7 @@ import type { OptionsGroupType } from './optionsGuiScheme' const notHideableModalsWithoutForce = new Set(['app-status']) -type Modal = ({ elem?: HTMLElement & Record } & { reactType?: string }) +type Modal = ({ elem?: HTMLElement & Record } & { reactType: string }) type ContextMenuItem = { callback; label } @@ -57,7 +57,7 @@ const showModalInner = (modal: Modal) => { } export const showModal = (elem: /* (HTMLElement & Record) | */{ reactType: string }) => { - const resolved = elem instanceof HTMLElement ? { elem: ref(elem) } : elem + const resolved = elem const curModal = activeModalStack.at(-1) if (/* elem === curModal?.elem || */(elem.reactType && elem.reactType === curModal?.reactType) || !showModalInner(resolved)) return if (curModal) defaultModalActions.hide(curModal) @@ -123,7 +123,7 @@ export type AppConfig = { defaultProxy?: string // defaultProxySave?: string // defaultVersion?: string - promoteServers?: Array<{ip, description, version?}> + promoteServers?: Array<{ ip, description, version?}> mapsProvider?: string } @@ -138,6 +138,7 @@ export const miscUiState = proxy({ wanOpened: false, /** wether game hud is shown (in playing state) */ gameLoaded: false, + showUI: true, loadedServerIndex: '', /** currently trying to load or loaded mc version, after all data is loaded */ loadedDataVersion: null as string | null, diff --git a/src/globals.d.ts b/src/globals.d.ts index 74c15791..2b82e7a1 100644 --- a/src/globals.d.ts +++ b/src/globals.d.ts @@ -3,7 +3,9 @@ declare const THREE: typeof import('three') // todo make optional declare const bot: Omit & { - world: import('prismarine-world').world.WorldSync + world: Omit & { + getBlock: (pos: import('vec3').Vec3) => import('prismarine-block').Block | null + } _client: Omit & { write: typeof import('./generatedClientPackets').clientWrite on: typeof import('./generatedServerPackets').clientOn diff --git a/src/guessProblem.ts b/src/guessProblem.ts deleted file mode 100644 index ecc7dbf1..00000000 --- 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/index.ts b/src/index.ts index 962f50b8..fb938aa4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,13 +2,14 @@ import './importsWorkaround' import './styles.css' import './globals' -import 'iconify-icon' import './devtools' import './entities' import './globalDomListeners' import initCollisionShapes from './getCollisionShapes' import { itemsAtlases, onGameLoad } from './inventoryWindows' import { supportedVersions } from 'minecraft-protocol' +import protocolMicrosoftAuth from 'minecraft-protocol/src/client/microsoftAuth' +import microsoftAuthflow from './microsoftAuthflow' import 'core-js/features/array/at' import 'core-js/features/promise/with-resolvers' @@ -17,6 +18,7 @@ import './scaleInterface' import itemsPng from 'prismarine-viewer/public/textures/items.png' import { initWithRenderer } from './topRightStats' import PrismarineBlock from 'prismarine-block' +import PrismarineItem from 'prismarine-item' import { options, watchValue } from './optionsStorage' import './reactUi.jsx' @@ -39,6 +41,7 @@ import * as THREE from 'three' import MinecraftData, { versionsByMinecraftVersion } from 'minecraft-data' import debug from 'debug' import { defaultsDeep } from 'lodash-es' +import initializePacketsReplay from './packetsReplay' import { initVR } from './vr' import { @@ -90,7 +93,11 @@ import { ViewerWrapper } from 'prismarine-viewer/viewer/lib/viewerWrapper' import './devReload' import './water' import { ConnectOptions } from './connect' -import { subscribe } from 'valtio' +import { ref, subscribe } from 'valtio' +import { signInMessageState } from './react/SignInMessageProvider' +import { updateAuthenticatedAccountData, updateLoadedServerData } from './react/ServersListProvider' +import { versionToNumber } from 'prismarine-viewer/viewer/prepare/utils' +import packetsPatcher from './packetsPatcher' import { initWebgpuRenderer } from 'prismarine-viewer/examples/webgpuRendererMain' import { addNewStat } from 'prismarine-viewer/examples/newStats' import { getVersion } from 'prismarine-viewer/viewer/lib/version' @@ -106,12 +113,16 @@ window.beforeRenderFrame = [] void registerServiceWorker() watchFov() initCollisionShapes() +initializePacketsReplay() +packetsPatcher() // Create three.js context, add to page let renderer: THREE.WebGLRenderer try { renderer = new THREE.WebGLRenderer({ powerPreference: options.gpuPreference, + preserveDrawingBuffer: true, + logarithmicDepthBuffer: true, }) } catch (err) { console.error(err) @@ -124,6 +135,7 @@ const renderWrapper = new ViewerWrapper(renderer.domElement, renderer) renderWrapper.addToPage() watchValue(options, (o) => { renderWrapper.renderInterval = o.frameLimit ? 1000 / o.frameLimit : 0 + renderWrapper.renderIntervalUnfocused = o.backgroundRendering === '5fps' ? 1000 / 5 : o.backgroundRendering === '20fps' ? 1000 / 20 : undefined }) const isFirefox = ua.getBrowser().name === 'Firefox' @@ -145,24 +157,35 @@ new THREE.TextureLoader().load(itemsPng, (texture) => { viewer.entities.itemsTexture = texture // todo unify viewer.entities.getItemUv = (id) => { - const name = loadedData.items[id]?.name - const uv = itemsAtlases.latest.textures[name] - if (!uv) { - const variant = viewer.world.downloadedBlockStatesData[name]?.variants?.[''] - if (!variant) return - const uvBlock = (Array.isArray(variant) ? variant[0] : variant).model?.elements?.[0]?.faces?.north.texture - if (!uvBlock) return + try { + const name = loadedData.items[id]?.name + const uv = itemsAtlases.latest.textures[name] + if (!uv) { + const variant = viewer.world.downloadedBlockStatesData[name]?.variants?.[''] + if (!variant) return + const faces = (Array.isArray(variant) ? variant[0] : variant).model?.elements?.[0]?.faces + const uvBlock = faces?.north?.texture ?? faces?.up?.texture ?? faces?.down?.texture ?? faces?.west?.texture ?? faces?.east?.texture ?? faces?.south?.texture + if (!uvBlock) return + return { + ...uvBlock, + size: Math.abs(uvBlock.su), + texture: viewer.world.material.map + } + } return { - ...uvBlock, - size: Math.abs(uvBlock.su), + ...uv, + size: itemsAtlases.latest.size, + texture: viewer.entities.itemsTexture + } + } catch (err) { + reportError?.(err) + return { + u: 0, + v: 0, + size: 16 / viewer.world.material.map!.image.width, texture: viewer.world.material.map } } - return { - ...uv, - size: itemsAtlases.latest.size, - texture: viewer.entities.itemsTexture - } } }) viewer.entities.entitiesOptions = { @@ -208,7 +231,7 @@ function hideCurrentScreens () { } const loadSingleplayer = (serverOverrides = {}, flattenedServerOverrides = {}) => { - void connect({ singleplayer: true, username: options.localUsername, password: '', serverOverrides, serverOverridesFlat: flattenedServerOverrides }) + void connect({ singleplayer: true, username: options.localUsername, serverOverrides, serverOverridesFlat: flattenedServerOverrides }) } function listenGlobalEvents () { window.addEventListener('connect', e => { @@ -264,7 +287,7 @@ async function connect (connectOptions: ConnectOptions) { const { renderDistance: renderDistanceSingleplayer, multiplayerRenderDistance } = options const server = cleanConnectIp(connectOptions.server, '25565') const proxy = cleanConnectIp(connectOptions.proxy, undefined) - const { username, password } = connectOptions + let { username } = connectOptions console.log(`connecting to ${server.host}:${server.port} with ${username}`) @@ -345,11 +368,17 @@ async function connect (connectOptions: ConnectOptions) { } const renderDistance = singleplayer ? renderDistanceSingleplayer : multiplayerRenderDistance + let updateDataAfterJoin = () => { } let localServer try { const serverOptions = defaultsDeep({}, connectOptions.serverOverrides ?? {}, options.localServerOptions, defaultServerOptions) Object.assign(serverOptions, connectOptions.serverOverridesFlat ?? {}) const downloadMcData = async (version: string) => { + if (connectOptions.authenticatedAccount && versionToNumber(version) < versionToNumber('1.19.4')) { + // todo support it (just need to fix .export crash) + throw new Error('Microsoft authentication is only supported in 1.19.4 and above (at least for now)') + } + // todo expose cache const lastVersion = supportedVersions.at(-1) if (version === lastVersion) { @@ -418,6 +447,7 @@ async function connect (connectOptions: ConnectOptions) { flyingSquidEvents() } + if (connectOptions.authenticatedAccount) username = 'not-used' let initialLoadingText: string if (singleplayer) { initialLoadingText = 'Local server is still starting' @@ -427,6 +457,20 @@ async function connect (connectOptions: ConnectOptions) { initialLoadingText = 'Connecting to server' } setLoadingScreenStatus(initialLoadingText) + + let newTokensCacheResult = null as any + const cachedTokens = typeof connectOptions.authenticatedAccount === 'object' ? connectOptions.authenticatedAccount.cachedTokens : {} + const authData = connectOptions.authenticatedAccount ? await microsoftAuthflow({ + tokenCaches: cachedTokens, + proxyBaseUrl: connectOptions.proxy, + setProgressText (text) { + setLoadingScreenStatus(text) + }, + setCacheResult (result) { + newTokensCacheResult = result + }, + }) : undefined + bot = mineflayer.createBot({ host: server.host, port: server.port ? +server.port : undefined, @@ -442,11 +486,56 @@ async function connect (connectOptions: ConnectOptions) { connect () { }, Client: CustomChannelClient as any, } : {}, + onMsaCode (data) { + signInMessageState.code = data.user_code + signInMessageState.link = data.verification_uri + signInMessageState.expiresOn = Date.now() + data.expires_in * 1000 + }, + sessionServer: authData?.sessionEndpoint, + auth: connectOptions.authenticatedAccount ? async (client, options) => { + authData!.setOnMsaCodeCallback(options.onMsaCode) + //@ts-expect-error + client.authflow = authData!.authFlow + try { + signInMessageState.abortController = ref(new AbortController()) + await Promise.race([ + protocolMicrosoftAuth.authenticate(client, options), + new Promise((_r, reject) => { + signInMessageState.abortController.signal.addEventListener('abort', () => { + reject(new Error('Aborted by user')) + }) + }) + ]) + if (signInMessageState.shouldSaveToken) { + updateAuthenticatedAccountData(accounts => { + const existingAccount = accounts.find(a => a.username === client.username) + if (existingAccount) { + existingAccount.cachedTokens = { ...existingAccount.cachedTokens, ...newTokensCacheResult } + } else { + accounts.push({ + username: client.username, + cachedTokens: { ...cachedTokens, ...newTokensCacheResult } + }) + } + return accounts + }) + updateDataAfterJoin = () => { + updateLoadedServerData(s => ({ ...s, authenticatedAccountOverride: client.username }), connectOptions.serverIndex) + } + } else { + updateDataAfterJoin = () => { + updateLoadedServerData(s => ({ ...s, authenticatedAccountOverride: undefined }), connectOptions.serverIndex) + } + } + setLoadingScreenStatus('Authentication successful. Logging in to server') + } finally { + signInMessageState.code = '' + } + } : undefined, username, - password, viewDistance: renderDistance, checkTimeoutInterval: 240 * 1000, - noPongTimeout: 240 * 1000, + // noPongTimeout: 240 * 1000, closeTimeout: 240 * 1000, respawn: options.autoRespawn, maxCatchupTicks: 0, @@ -479,6 +568,13 @@ async function connect (connectOptions: ConnectOptions) { //@ts-expect-error bot._client.socket._ws.addEventListener('close', () => { console.log('WebSocket connection closed') + setTimeout(() => { + if (bot) { + bot.emit('end', 'WebSocket connection closed with unknown reason') + } + }, 1000) + }) + bot._client.socket.on('close', () => { setTimeout(() => { if (bot) { bot.emit('end', 'WebSocket connection closed with unknown reason') @@ -486,6 +582,22 @@ async function connect (connectOptions: ConnectOptions) { }) }) }) + let i = 0 + //@ts-expect-error + bot.pingProxy = async () => { + const curI = ++i + return new Promise(resolve => { + //@ts-expect-error + bot._client.socket._ws.send(`ping:${curI}`) + const date = Date.now() + const onPong = (received) => { + if (received !== curI.toString()) return + bot._client.socket.off('pong' as any, onPong) + resolve(Date.now() - date) + } + bot._client.socket.on('pong' as any, onPong) + }) + } } // socket setup actually can be delayed because of dns lookup if (bot._client.socket) { @@ -552,19 +664,11 @@ async function connect (connectOptions: ConnectOptions) { errorAbortController.abort() const mcData = MinecraftData(bot.version) window.PrismarineBlock = PrismarineBlock(mcData.version.minecraftVersion!) + window.PrismarineItem = PrismarineItem(mcData.version.minecraftVersion!) window.loadedData = mcData window.Vec3 = Vec3 window.pathfinder = pathfinder - // patch mineflayer - // todo move to mineflayer - bot.inventory.on('updateSlot', (index) => { - if ((index as unknown as number) === bot.quickBarSlot + bot.inventory.hotbarStart) { - //@ts-expect-error - bot.emit('heldItemChanged') - } - }) - miscUiState.gameLoaded = true miscUiState.loadedServerIndex = connectOptions.serverIndex ?? '' customEvents.emit('gameLoaded') @@ -573,6 +677,7 @@ async function connect (connectOptions: ConnectOptions) { setLoadingScreenStatus('Placing blocks (starting viewer)') localStorage.lastConnectOptions = JSON.stringify(connectOptions) connectOptions.onSuccessfulPlay?.() + updateDataAfterJoin() if (connectOptions.autoLoginPassword) { bot.chat(`/login ${connectOptions.autoLoginPassword}`) } @@ -806,9 +911,12 @@ listenGlobalEvents() watchValue(miscUiState, async s => { if (s.appLoaded) { // fs ready const qs = new URLSearchParams(window.location.search) + const moreServerOptions = {} as Record + if (qs.has('version')) moreServerOptions.version = qs.get('version') if (qs.get('singleplayer') === '1') { loadSingleplayer({}, { - worldFolder: undefined + worldFolder: undefined, + ...moreServerOptions }) } if (qs.get('loadSave')) { diff --git a/src/inventoryWindows.ts b/src/inventoryWindows.ts index 94e748b8..4cf25205 100644 --- a/src/inventoryWindows.ts +++ b/src/inventoryWindows.ts @@ -1,4 +1,4 @@ -import { subscribe } from 'valtio' +import { proxy, subscribe } from 'valtio' import { showInventory } from 'minecraft-inventory-gui/web/ext.mjs' import InventoryGui from 'minecraft-assets/minecraft-assets/data/1.17.1/gui/container/inventory.png' import ChestLikeGui from 'minecraft-assets/minecraft-assets/data/1.17.1/gui/container/shulker_box.png' @@ -15,9 +15,7 @@ import BeaconGui from 'minecraft-assets/minecraft-assets/data/1.17.1/gui/contain import WidgetsGui from 'minecraft-assets/minecraft-assets/data/1.17.1/gui/widgets.png' import Dirt from 'minecraft-assets/minecraft-assets/data/1.17.1/blocks/dirt.png' -import { subscribeKey } from 'valtio/utils' -import MinecraftData, { RecipeItem } from 'minecraft-data' -import { getVersion } from 'prismarine-viewer/viewer/lib/version' +import { RecipeItem } from 'minecraft-data' import { versionToNumber } from 'prismarine-viewer/viewer/prepare/utils' import itemsPng from 'prismarine-viewer/public/textures/items.png' import itemsLegacyPng from 'prismarine-viewer/public/textures/items-legacy.png' @@ -30,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() @@ -67,11 +66,19 @@ let version: string let PrismarineBlock: typeof PrismarineBlockLoader.Block let PrismarineItem: typeof Item +export const allImagesLoadedState = proxy({ + value: false +}) + export const onGameLoad = (onLoad) => { + allImagesLoadedState.value = false let loaded = 0 const onImageLoaded = () => { loaded++ - if (loaded === 3) onLoad?.() + if (loaded === 3) { + onLoad?.() + allImagesLoadedState.value = true + } } version = bot.version getImage({ path: 'invsprite' }, onImageLoaded) @@ -121,7 +128,14 @@ 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) { + hideCurrentModal() + } }) customEvents.on('search', (q) => { @@ -306,20 +320,26 @@ export const getItemNameRaw = (item: Pick const itemNbt: PossibleItemProps = nbt.simplify(item.nbt) const customName = itemNbt.display?.Name if (!customName) return - const parsed = mojangson.simplify(mojangson.parse(customName)) - if (parsed.extra) { - return parsed as Record - } else { - return parsed as MessageFormatPart + try { + const parsed = mojangson.simplify(mojangson.parse(customName)) + if (parsed.extra) { + return parsed as Record + } else { + return parsed as MessageFormatPart + } + } catch (err) { + return { + text: customName + } } } const getItemName = (slot: Item | null) => { const parsed = getItemNameRaw(slot) - if (!parsed || parsed['extra']) return + if (!parsed) return // todo display full text renderer from sign renderer const text = flat(parsed as MessageFormatPart).map(x => x.text) - return text + return text.join('') } export const renderSlotExternal = (slot) => { @@ -340,6 +360,13 @@ const mapSlots = (slots: Array) => { try { const slotCustomProps = renderSlot(slot) Object.assign(slot, { ...slotCustomProps, displayName: ('nbt' in slot ? getItemName(slot) : undefined) ?? slot.displayName }) + //@ts-expect-error + slot.toJSON = () => { + // Allow to serialize slot to JSON as minecraft-inventory-gui creates icon property as cache (recursively) + //@ts-expect-error + const { icon, ...rest } = slot + return rest + } } catch (err) { inGameError(err) } @@ -362,12 +389,15 @@ export const onModalClose = (callback: () => any) => { callback() unsubscribe() } - }) + }, true) } const implementedContainersGuiMap = { // todo allow arbitrary size instead! + 'minecraft:generic_9x1': 'ChestWin', + 'minecraft:generic_9x2': 'ChestWin', 'minecraft:generic_9x3': 'ChestWin', + 'minecraft:generic_9x4': 'Generic95Win', 'minecraft:generic_9x5': 'Generic95Win', // hopper 'minecraft:generic_5x1': 'HopperWin', @@ -397,26 +427,58 @@ const upJei = (search: string) => { } export const openItemsCanvas = (type, _bot = bot as typeof bot | null) => { - const inv = showInventory(type, getImage, {}, _bot) + 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) + 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 as any).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 } +let skipClosePacketSending = false const openWindow = (type: string | undefined) => { // if (activeModalStack.some(x => x.reactType?.includes?.('player_win:'))) { if (activeModalStack.length) { // game is not in foreground, don't close current modal - if (type) bot.currentWindow?.['close']() - return + if (type) { + skipClosePacketSending = true + hideCurrentModal() + } else { + bot.currentWindow?.['close']() + return + } } showModal({ reactType: `player_win:${type}`, }) onModalClose(() => { // might be already closed (event fired) - if (type !== undefined && bot.currentWindow) bot.currentWindow['close']() + if (type !== undefined && bot.currentWindow && !skipClosePacketSending) bot.currentWindow['close']() lastWindow.destroy() lastWindow = null as any + window.lastWindow = lastWindow miscUiState.displaySearchInput = false destroyFn() + skipClosePacketSending = false }) cleanLoadedImagesCache() const inv = openItemsCanvas(type) @@ -426,8 +488,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() } @@ -437,7 +504,19 @@ 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) { + 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 @@ -446,21 +525,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 @@ -527,3 +610,75 @@ const getResultingRecipe = (slots: Array, gridRows: number) => { const item = new PrismarineItem(id, count, metadata) return item } + +const ingredientToItem = (recipeItem) => recipeItem === null ? null : new PrismarineItem(recipeItem, 1) + +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: Array, + 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 + + 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 + const ingredientsItems = ingredients.map(item => ingredientToItem(item)) + 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 00000000..662d7331 --- /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 +} diff --git a/src/loadSave.ts b/src/loadSave.ts index af9d078c..7ca454ff 100644 --- a/src/loadSave.ts +++ b/src/loadSave.ts @@ -1,15 +1,16 @@ import fs from 'fs' import path from 'path' -import { supportedVersions } from 'flying-squid/dist/lib/version' import * as nbt from 'prismarine-nbt' import { proxy } from 'valtio' import { gzip } from 'node-gzip' +import { versionToNumber } from 'prismarine-viewer/viewer/prepare/utils' import { options } from './optionsStorage' import { nameToMcOfflineUUID, disconnect } from './flyingSquidUtils' import { existsViaStats, forceCachedDataPaths, forceRedirectPaths, mkdirRecursive } from './browserfs' import { isMajorVersionGreater } from './utils' import { activeModalStacks, insertActiveModalStack, miscUiState } from './globalState' +import supportedVersions from './supportedVersions.mjs' // todo include name of opened handle (zip)! // additional fs metadata @@ -91,13 +92,12 @@ export const loadSave = async (root = '/world') => { const newVersion = '1.8.8' version = newVersion } - // const lastSupportedVersion = supportedVersions.at(-1)! - const lastTestedVersion = '1.18.2' + const lastSupportedVersion = supportedVersions.at(-1)! const firstSupportedVersion = supportedVersions[0] const lowerBound = isMajorVersionGreater(firstSupportedVersion, version) - const upperBound = isMajorVersionGreater(version, lastTestedVersion) + const upperBound = versionToNumber(version) > versionToNumber(lastSupportedVersion) if (lowerBound || upperBound) { - version = prompt(`Version ${version} is not supported, supported versions are ${supportedVersions.join(', ')}, what try to use instead?`, lowerBound ? firstSupportedVersion : lastTestedVersion) + version = prompt(`Version ${version} is not supported, supported versions are ${supportedVersions.join(', ')}, what try to use instead?`, lowerBound ? firstSupportedVersion : lastSupportedVersion) if (!version) return } if (levelDat.WorldGenSettings) { diff --git a/src/localServerMultiplayer.ts b/src/localServerMultiplayer.ts index c7c2cd28..ca85c126 100644 --- a/src/localServerMultiplayer.ts +++ b/src/localServerMultiplayer.ts @@ -129,8 +129,8 @@ export const connectToPeer = async (peerId: string) => { })) const clientDuplex = new CustomDuplex({}, (data) => { - // todo rm debug - console.debug('sending', data.toString()) + // todo debug until play state + // console.debug('sending', data.toString()) connection.send(data) }) connection.on('data', (data: any) => { diff --git a/src/microsoftAuthflow.ts b/src/microsoftAuthflow.ts new file mode 100644 index 00000000..36f4a121 --- /dev/null +++ b/src/microsoftAuthflow.ts @@ -0,0 +1,159 @@ +export default async ({ tokenCaches, proxyBaseUrl, setProgressText = (text) => { }, setCacheResult }) => { + let onMsaCodeCallback + // const authEndpoint = 'http://localhost:3000/' + // const sessionEndpoint = 'http://localhost:3000/session' + let authEndpoint = '' + let sessionEndpoint = '' + if (!proxyBaseUrl.startsWith('http')) proxyBaseUrl = `${isPageSecure() ? 'https' : 'http'}://${proxyBaseUrl}` + const url = proxyBaseUrl + '/api/vm/net/connect' + let result: Response + try { + result = await fetch(url) + } catch (err) { + throw new Error(`Selected proxy server ${proxyBaseUrl} most likely is down`) + } + try { + const json = await result.json() + authEndpoint = urlWithBase(json.capabilities.authEndpoint, proxyBaseUrl) + sessionEndpoint = urlWithBase(json.capabilities.sessionEndpoint, proxyBaseUrl) + if (!authEndpoint) throw new Error('No auth endpoint') + } catch (err) { + console.error(err) + throw new Error(`Selected proxy server ${proxyBaseUrl} does not support Microsoft authentication`) + } + const authFlow = { + async getMinecraftJavaToken () { + + setProgressText('Authenticating with Microsoft account') + let result = null + await fetch(authEndpoint, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(tokenCaches), + }).then(async response => { + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}: ${await response.text()}`) + } + + const reader = response.body!.getReader() + const decoder = new TextDecoder('utf8') + + const processText = ({ done, value = undefined as Uint8Array | undefined }) => { + if (done) { + return + } + + const processChunk = (chunkStr) => { + try { + const json = JSON.parse(chunkStr) + if (json.user_code) { + onMsaCodeCallback(json) + // this.codeCallback(json) + } + if (json.error) throw new Error(json.error) + if (json.token) result = json + if (json.newCache) setCacheResult(json.newCache) + } catch (err) { + } + } + + const strings = decoder.decode(value) + + for (const chunk of strings.split('\n\n')) { + processChunk(chunk) + } + + return reader.read().then(processText) + } + return reader.read().then(processText) + }) + if (!window.crypto && !isPageSecure()) throw new Error('Crypto API is available only in secure contexts. Be sure to use https!') + const restoredData = await restoreData(result) + restoredData.certificates.profileKeys.private = restoredData.certificates.profileKeys.privatePEM + return restoredData + } + } + return { + authFlow, + sessionEndpoint, + setOnMsaCodeCallback (callback) { + onMsaCodeCallback = callback + } + } +} + +function isPageSecure () { + return window.location.protocol === 'https:' +} + +// restore dates from strings +const restoreData = async (json) => { + const promises = [] as Array> + if (typeof json === 'object' && json) { + for (const [key, value] of Object.entries(json)) { + if (typeof value === 'string') { + promises.push(tryRestorePublicKey(value, key, json)) + if (value.endsWith('Z')) { + const date = new Date(value) + if (!isNaN(date.getTime())) { + json[key] = date + } + } + } + if (typeof value === 'object') { + // eslint-disable-next-line no-await-in-loop + await restoreData(value) + } + } + } + + await Promise.all(promises) + + return json +} + +const tryRestorePublicKey = async (value: string, name: string, parent: { [x: string]: any }) => { + value = value.trim() + if (!name.endsWith('PEM') || !value.startsWith('-----BEGIN RSA PUBLIC KEY-----') || !value.endsWith('-----END RSA PUBLIC KEY-----')) return + const der = pemToArrayBuffer(value) + const key = await window.crypto.subtle.importKey( + 'spki', // Specify that the data is in SPKI format + der, + { + name: 'RSA-OAEP', + hash: { name: 'SHA-256' } + }, + true, + ['encrypt'] // Specify key usages + ) + const originalName = name.replace('PEM', '') + const exported = await window.crypto.subtle.exportKey('spki', key) + const exportedBuffer = new Uint8Array(exported) + parent[originalName] = { + export () { + return exportedBuffer + } + } +} + +function pemToArrayBuffer (pem) { + // Fetch the part of the PEM string between header and footer + const pemHeader = '-----BEGIN RSA PUBLIC KEY-----' + const pemFooter = '-----END RSA PUBLIC KEY-----' + const pemContents = pem.slice(pemHeader.length, pem.length - pemFooter.length).trim() + const binaryDerString = atob(pemContents.replaceAll(/\s/g, '')) + const binaryDer = new Uint8Array(binaryDerString.length) + for (let i = 0; i < binaryDerString.length; i++) { + binaryDer[i] = binaryDerString.codePointAt(i)! + } + return binaryDer.buffer +} + +const urlWithBase = (url: string, base: string) => { + const urlObj = new URL(url, base) + base = base.replace(/^https?:\/\//, '') + urlObj.host = base.includes(':') ? base : `${base}:${isPageSecure() ? '443' : '80'}` + return urlObj.toString() +} diff --git a/src/optionsGuiScheme.tsx b/src/optionsGuiScheme.tsx index 1c92a39c..76799a60 100644 --- a/src/optionsGuiScheme.tsx +++ b/src/optionsGuiScheme.tsx @@ -9,6 +9,7 @@ import Slider from './react/Slider' import { getScreenRefreshRate, setLoadingScreenStatus } from './utils' import { openFilePicker, resetLocalStorageWithoutWorld } from './browserfs' import { getResourcePackName, resourcePackState, uninstallTexturePack } from './texturePack' +import { downloadPacketsReplay, packetsReplaceSessionState } from './packetsReplay' export const guiOptionsScheme: { @@ -43,6 +44,16 @@ export const guiOptionsScheme: { return + }, mouseSensX: {}, mouseSensY: { min: -1, @@ -213,14 +235,26 @@ export const guiOptionsScheme: { text: 'Always Mobile Controls', }, touchButtonsSize: { - min: 40 + min: 40, + disableIf: [ + 'touchControlsType', + 'joystick-buttons' + ], }, touchButtonsOpacity: { min: 10, - max: 90 + max: 90, + disableIf: [ + 'touchControlsType', + 'joystick-buttons' + ], }, touchButtonsPosition: { - max: 80 + max: 80, + disableIf: [ + 'touchControlsType', + 'joystick-buttons' + ], }, touchControlsType: { values: [['classic', 'Classic'], ['joystick-buttons', 'New']], @@ -242,6 +276,10 @@ export const guiOptionsScheme: { 'auto', 'never' ], + disableIf: [ + 'autoParkour', + true + ], }, autoParkour: {}, } @@ -272,6 +310,27 @@ export const guiOptionsScheme: { if (confirm('Are you sure you want to reset all settings?')) resetLocalStorageWithoutWorld() }}>Reset all settings }, + }, + { + custom () { + return Developer + }, + }, + { + custom () { + const { active } = useSnapshot(packetsReplaceSessionState) + return + }, + }, + { + custom () { + const { active } = useSnapshot(packetsReplaceSessionState) + return + }, } ], } @@ -282,3 +341,15 @@ const Category = ({ children }) =>
{children}
+ +export const tryFindOptionConfig = (option: keyof AppOptions) => { + for (const group of Object.values(guiOptionsScheme)) { + for (const optionConfig of group) { + if (option in optionConfig) { + return optionConfig[option] + } + } + } + + return null +} diff --git a/src/optionsStorage.ts b/src/optionsStorage.ts index 2f4256dd..2842c908 100644 --- a/src/optionsStorage.ts +++ b/src/optionsStorage.ts @@ -30,26 +30,10 @@ const defaultOptions = { touchButtonsSize: 40, touchButtonsOpacity: 80, touchButtonsPosition: 12, - touchControlsPositions: { - action: [ - 70, - 85 - ], - sneak: [ - 90, - 85 - ], - break: [ - 70, - 65 - ], - jump: [ - 90, - 65 - ], - } as Record, + touchControlsPositions: getDefaultTouchControlsPositions(), touchControlsType: 'classic' as 'classic' | 'joystick-buttons', gpuPreference: 'default' as 'default' | 'high-performance' | 'low-power', + backgroundRendering: '20fps' as 'full' | '20fps' | '5fps', /** @unstable */ disableAssets: false, /** @unstable */ @@ -58,6 +42,7 @@ const defaultOptions = { dayCycleAndLighting: true, loadPlayerSkins: true, lowMemoryMode: false, + starfieldRendering: true, // antiAliasing: false, showChunkBorders: false, // todo rename option @@ -74,6 +59,7 @@ const defaultOptions = { disableLoadPrompts: false, guestUsername: 'guest', askGuestName: true, + errorReporting: true, /** Actually might be useful */ showCursorBlockInSpectator: false, renderEntities: true, @@ -92,6 +78,27 @@ const defaultOptions = { wysiwygSignEditor: 'auto' as 'auto' | 'always' | 'never', } +function getDefaultTouchControlsPositions () { + return { + action: [ + 70, + 76 + ], + sneak: [ + 84, + 76 + ], + break: [ + 70, + 60 + ], + jump: [ + 84, + 60 + ], + } as Record +} + const qsOptionsRaw = new URLSearchParams(location.search).getAll('setting') export const qsOptions = Object.fromEntries(qsOptionsRaw.map(o => { const [key, value] = o.split(':') diff --git a/src/packetsPatcher.ts b/src/packetsPatcher.ts new file mode 100644 index 00000000..587c41c9 --- /dev/null +++ b/src/packetsPatcher.ts @@ -0,0 +1,14 @@ +// todo it should not be there, most likely it will be more automatically updated in the future +// todo these fixes should be ported to mineflayer + +export default () => { + customEvents.on('mineflayerBotCreated', () => { + bot._client.on('packet', (data, meta) => { + if (meta.name === 'map_chunk') { + if (data.groundUp && data.bitMap === 1 && data.chunkData.every(x => x === 0)) { + data.chunkData = Buffer.from(Array.from({ length: 12_544 }).fill(0) as any) + } + } + }) + }) +} diff --git a/src/packetsReplay.ts b/src/packetsReplay.ts new file mode 100644 index 00000000..57d0805e --- /dev/null +++ b/src/packetsReplay.ts @@ -0,0 +1,32 @@ +import { proxy } from 'valtio' +import { PacketsLogger } from './packetsReplayBase' + +export const packetsReplaceSessionState = proxy({ + active: false, +}) + +const replayLogger = new PacketsLogger() +export default () => { + customEvents.on('mineflayerBotCreated', () => { + replayLogger.contents = '' + bot._client.on('packet', (data, { name, state }) => { + if (!packetsReplaceSessionState.active) { + return + } + replayLogger.log(true, { name, state }, data) + }) + bot._client.on('writePacket' as any, (name, data) => { + if (!packetsReplaceSessionState.active) { + return + } + replayLogger.log(false, { name, state: bot._client.state }, data) + }) + }) +} + +export const downloadPacketsReplay = async () => { + const a = document.createElement('a') + a.href = `data:text/plain;charset=utf-8,${encodeURIComponent(replayLogger.contents)}` + a.download = `packets-replay-${new Date().toISOString()}.txt` + a.click() +} diff --git a/src/packetsReplayBase.ts b/src/packetsReplayBase.ts new file mode 100644 index 00000000..5e2bed09 --- /dev/null +++ b/src/packetsReplayBase.ts @@ -0,0 +1,56 @@ +export class PacketsLogger { + lastPacketTime = -1 + contents = '' + logOnly = [] as string[] + skip = [] as string[] + + logStr (str: string) { + this.contents += `${str}\n` + } + + log (isFromServer: boolean, packet: { name; state }, data: any) { + if (this.logOnly.length > 0 && !this.logOnly.includes(packet.name)) { + return + } + if (this.skip.length > 0 && this.skip.includes(packet.name)) { + return + } + if (this.lastPacketTime === -1) { + this.lastPacketTime = Date.now() + } + + const diff = `+${Date.now() - this.lastPacketTime}` + const str = `${isFromServer ? 'S' : 'C'} ${packet.state}:${packet.name} ${diff} ${JSON.stringify(data)}` + this.logStr(str) + this.lastPacketTime = Date.now() + } +} + +export type ParsedReplayPacket = { + name: string + params: any + state: string + diff: number + isFromServer: boolean +} +export function parseReplayContents (contents: string) { + const lines = contents.split('\n') + + const packets = [] as ParsedReplayPacket[] + for (let line of lines) { + line = line.trim() + if (!line || line.startsWith('#')) continue + const [side, nameState, diff, ...data] = line.split(' ') + const parsed = JSON.parse(data.join(' ')) + const [state, name] = nameState.split(':') + packets.push({ + name, + state, + params: parsed, + isFromServer: side.toUpperCase() === 'S', + diff: Number.parseInt(diff.slice(1), 10), + }) + } + + return packets +} diff --git a/src/preflatMap.json b/src/preflatMap.json new file mode 100644 index 00000000..fdf2640f --- /dev/null +++ b/src/preflatMap.json @@ -0,0 +1,1741 @@ +{ + "blocks": { + "0:0": "air", + "1:0": "stone", + "1:1": "granite", + "1:2": "polished_granite", + "1:3": "diorite", + "1:4": "polished_diorite", + "1:5": "andesite", + "1:6": "polished_andesite", + "2:0": "grass_block[snowy=false]", + "3:0": "dirt", + "3:1": "coarse_dirt", + "3:2": "podzol[snowy=false]", + "4:0": "cobblestone", + "5:0": "oak_planks", + "5:1": "spruce_planks", + "5:2": "birch_planks", + "5:3": "jungle_planks", + "5:4": "acacia_planks", + "5:5": "dark_oak_planks", + "6:0": "oak_sapling[stage=0]", + "6:1": "spruce_sapling[stage=0]", + "6:2": "birch_sapling[stage=0]", + "6:3": "jungle_sapling[stage=0]", + "6:4": "acacia_sapling[stage=0]", + "6:5": "dark_oak_sapling[stage=0]", + "6:8": "oak_sapling[stage=1]", + "6:9": "spruce_sapling[stage=1]", + "6:10": "birch_sapling[stage=1]", + "6:11": "jungle_sapling[stage=1]", + "6:12": "acacia_sapling[stage=1]", + "6:13": "dark_oak_sapling[stage=1]", + "7:0": "bedrock", + "8:0": "water[level=0]", + "8:1": "water[level=1]", + "8:2": "water[level=2]", + "8:3": "water[level=3]", + "8:4": "water[level=4]", + "8:5": "water[level=5]", + "8:6": "water[level=6]", + "8:7": "water[level=7]", + "8:8": "water[level=8]", + "8:9": "water[level=9]", + "8:10": "water[level=10]", + "8:11": "water[level=11]", + "8:12": "water[level=12]", + "8:13": "water[level=13]", + "8:14": "water[level=14]", + "8:15": "water[level=15]", + "9:0": "water[level=0]", + "9:1": "water[level=1]", + "9:2": "water[level=2]", + "9:3": "water[level=3]", + "9:4": "water[level=4]", + "9:5": "water[level=5]", + "9:6": "water[level=6]", + "9:7": "water[level=7]", + "9:8": "water[level=8]", + "9:9": "water[level=9]", + "9:10": "water[level=10]", + "9:11": "water[level=11]", + "9:12": "water[level=12]", + "9:13": "water[level=13]", + "9:14": "water[level=14]", + "9:15": "water[level=15]", + "10:0": "lava[level=0]", + "10:1": "lava[level=1]", + "10:2": "lava[level=2]", + "10:3": "lava[level=3]", + "10:4": "lava[level=4]", + "10:5": "lava[level=5]", + "10:6": "lava[level=6]", + "10:7": "lava[level=7]", + "10:8": "lava[level=8]", + "10:9": "lava[level=9]", + "10:10": "lava[level=10]", + "10:11": "lava[level=11]", + "10:12": "lava[level=12]", + "10:13": "lava[level=13]", + "10:14": "lava[level=14]", + "10:15": "lava[level=15]", + "11:0": "lava[level=0]", + "11:1": "lava[level=1]", + "11:2": "lava[level=2]", + "11:3": "lava[level=3]", + "11:4": "lava[level=4]", + "11:5": "lava[level=5]", + "11:6": "lava[level=6]", + "11:7": "lava[level=7]", + "11:8": "lava[level=8]", + "11:9": "lava[level=9]", + "11:10": "lava[level=10]", + "11:11": "lava[level=11]", + "11:12": "lava[level=12]", + "11:13": "lava[level=13]", + "11:14": "lava[level=14]", + "11:15": "lava[level=15]", + "12:0": "sand", + "12:1": "red_sand", + "13:0": "gravel", + "14:0": "gold_ore", + "15:0": "iron_ore", + "16:0": "coal_ore", + "17:0": "oak_log[axis=y]", + "17:1": "spruce_log[axis=y]", + "17:2": "birch_log[axis=y]", + "17:3": "jungle_log[axis=y]", + "17:4": "oak_log[axis=x]", + "17:5": "spruce_log[axis=x]", + "17:6": "birch_log[axis=x]", + "17:7": "jungle_log[axis=x]", + "17:8": "oak_log[axis=z]", + "17:9": "spruce_log[axis=z]", + "17:10": "birch_log[axis=z]", + "17:11": "jungle_log[axis=z]", + "17:12": "oak_bark", + "17:13": "spruce_bark", + "17:14": "birch_bark", + "17:15": "jungle_bark", + "18:0": "oak_leaves[check_decay=false,decayable=true]", + "18:1": "spruce_leaves[check_decay=false,decayable=true]", + "18:2": "birch_leaves[check_decay=false,decayable=true]", + "18:3": "jungle_leaves[check_decay=false,decayable=true]", + "18:4": "oak_leaves[check_decay=false,decayable=false]", + "18:5": "spruce_leaves[check_decay=false,decayable=false]", + "18:6": "birch_leaves[check_decay=false,decayable=false]", + "18:7": "jungle_leaves[check_decay=false,decayable=false]", + "18:8": "oak_leaves[check_decay=true,decayable=true]", + "18:9": "spruce_leaves[check_decay=true,decayable=true]", + "18:10": "birch_leaves[check_decay=true,decayable=true]", + "18:11": "jungle_leaves[check_decay=true,decayable=true]", + "18:12": "oak_leaves[check_decay=true,decayable=false]", + "18:13": "spruce_leaves[check_decay=true,decayable=false]", + "18:14": "birch_leaves[check_decay=true,decayable=false]", + "18:15": "jungle_leaves[check_decay=true,decayable=false]", + "19:0": "sponge", + "19:1": "wet_sponge", + "20:0": "glass", + "21:0": "lapis_ore", + "22:0": "lapis_block", + "23:0": "dispenser[facing=down,triggered=false]", + "23:1": "dispenser[facing=up,triggered=false]", + "23:2": "dispenser[facing=north,triggered=false]", + "23:3": "dispenser[facing=south,triggered=false]", + "23:4": "dispenser[facing=west,triggered=false]", + "23:5": "dispenser[facing=east,triggered=false]", + "23:8": "dispenser[facing=down,triggered=true]", + "23:9": "dispenser[facing=up,triggered=true]", + "23:10": "dispenser[facing=north,triggered=true]", + "23:11": "dispenser[facing=south,triggered=true]", + "23:12": "dispenser[facing=west,triggered=true]", + "23:13": "dispenser[facing=east,triggered=true]", + "24:0": "sandstone", + "24:1": "chiseled_sandstone", + "24:2": "cut_sandstone", + "25:0": "note_block", + "26:0": "red_bed[facing=south,occupied=false,part=foot]", + "26:1": "red_bed[facing=west,occupied=false,part=foot]", + "26:2": "red_bed[facing=north,occupied=false,part=foot]", + "26:3": "red_bed[facing=east,occupied=false,part=foot]", + "26:8": "red_bed[facing=south,occupied=false,part=head]", + "26:9": "red_bed[facing=west,occupied=false,part=head]", + "26:10": "red_bed[facing=north,occupied=false,part=head]", + "26:11": "red_bed[facing=east,occupied=false,part=head]", + "26:12": "red_bed[facing=south,occupied=true,part=head]", + "26:13": "red_bed[facing=west,occupied=true,part=head]", + "26:14": "red_bed[facing=north,occupied=true,part=head]", + "26:15": "red_bed[facing=east,occupied=true,part=head]", + "27:0": "powered_rail[powered=false,shape=north_south]", + "27:1": "powered_rail[powered=false,shape=east_west]", + "27:2": "powered_rail[powered=false,shape=ascending_east]", + "27:3": "powered_rail[powered=false,shape=ascending_west]", + "27:4": "powered_rail[powered=false,shape=ascending_north]", + "27:5": "powered_rail[powered=false,shape=ascending_south]", + "27:8": "powered_rail[powered=true,shape=north_south]", + "27:9": "powered_rail[powered=true,shape=east_west]", + "27:10": "powered_rail[powered=true,shape=ascending_east]", + "27:11": "powered_rail[powered=true,shape=ascending_west]", + "27:12": "powered_rail[powered=true,shape=ascending_north]", + "27:13": "powered_rail[powered=true,shape=ascending_south]", + "28:0": "detector_rail[powered=false,shape=north_south]", + "28:1": "detector_rail[powered=false,shape=east_west]", + "28:2": "detector_rail[powered=false,shape=ascending_east]", + "28:3": "detector_rail[powered=false,shape=ascending_west]", + "28:4": "detector_rail[powered=false,shape=ascending_north]", + "28:5": "detector_rail[powered=false,shape=ascending_south]", + "28:8": "detector_rail[powered=true,shape=north_south]", + "28:9": "detector_rail[powered=true,shape=east_west]", + "28:10": "detector_rail[powered=true,shape=ascending_east]", + "28:11": "detector_rail[powered=true,shape=ascending_west]", + "28:12": "detector_rail[powered=true,shape=ascending_north]", + "28:13": "detector_rail[powered=true,shape=ascending_south]", + "29:0": "sticky_piston[extended=false,facing=down]", + "29:1": "sticky_piston[extended=false,facing=up]", + "29:2": "sticky_piston[extended=false,facing=north]", + "29:3": "sticky_piston[extended=false,facing=south]", + "29:4": "sticky_piston[extended=false,facing=west]", + "29:5": "sticky_piston[extended=false,facing=east]", + "29:8": "sticky_piston[extended=true,facing=down]", + "29:9": "sticky_piston[extended=true,facing=up]", + "29:10": "sticky_piston[extended=true,facing=north]", + "29:11": "sticky_piston[extended=true,facing=south]", + "29:12": "sticky_piston[extended=true,facing=west]", + "29:13": "sticky_piston[extended=true,facing=east]", + "30:0": "cobweb", + "31:0": "dead_bush", + "31:1": "grass", + "31:2": "fern", + "32:0": "dead_bush", + "33:0": "piston[extended=false,facing=down]", + "33:1": "piston[extended=false,facing=up]", + "33:2": "piston[extended=false,facing=north]", + "33:3": "piston[extended=false,facing=south]", + "33:4": "piston[extended=false,facing=west]", + "33:5": "piston[extended=false,facing=east]", + "33:8": "piston[extended=true,facing=down]", + "33:9": "piston[extended=true,facing=up]", + "33:10": "piston[extended=true,facing=north]", + "33:11": "piston[extended=true,facing=south]", + "33:12": "piston[extended=true,facing=west]", + "33:13": "piston[extended=true,facing=east]", + "34:0": "piston_head[facing=down,short=false,type=normal]", + "34:1": "piston_head[facing=up,short=false,type=normal]", + "34:2": "piston_head[facing=north,short=false,type=normal]", + "34:3": "piston_head[facing=south,short=false,type=normal]", + "34:4": "piston_head[facing=west,short=false,type=normal]", + "34:5": "piston_head[facing=east,short=false,type=normal]", + "34:8": "piston_head[facing=down,short=false,type=sticky]", + "34:9": "piston_head[facing=up,short=false,type=sticky]", + "34:10": "piston_head[facing=north,short=false,type=sticky]", + "34:11": "piston_head[facing=south,short=false,type=sticky]", + "34:12": "piston_head[facing=west,short=false,type=sticky]", + "34:13": "piston_head[facing=east,short=false,type=sticky]", + "35:0": "white_wool", + "35:1": "orange_wool", + "35:2": "magenta_wool", + "35:3": "light_blue_wool", + "35:4": "yellow_wool", + "35:5": "lime_wool", + "35:6": "pink_wool", + "35:7": "gray_wool", + "35:8": "light_gray_wool", + "35:9": "cyan_wool", + "35:10": "purple_wool", + "35:11": "blue_wool", + "35:12": "brown_wool", + "35:13": "green_wool", + "35:14": "red_wool", + "35:15": "black_wool", + "36:0": "moving_piston[facing=down,type=normal]", + "36:1": "moving_piston[facing=up,type=normal]", + "36:2": "moving_piston[facing=north,type=normal]", + "36:3": "moving_piston[facing=south,type=normal]", + "36:4": "moving_piston[facing=west,type=normal]", + "36:5": "moving_piston[facing=east,type=normal]", + "36:8": "moving_piston[facing=down,type=sticky]", + "36:9": "moving_piston[facing=up,type=sticky]", + "36:10": "moving_piston[facing=north,type=sticky]", + "36:11": "moving_piston[facing=south,type=sticky]", + "36:12": "moving_piston[facing=west,type=sticky]", + "36:13": "moving_piston[facing=east,type=sticky]", + "37:0": "dandelion", + "38:0": "poppy", + "38:1": "blue_orchid", + "38:2": "allium", + "38:3": "azure_bluet", + "38:4": "red_tulip", + "38:5": "orange_tulip", + "38:6": "white_tulip", + "38:7": "pink_tulip", + "38:8": "oxeye_daisy", + "39:0": "brown_mushroom", + "40:0": "red_mushroom", + "41:0": "gold_block", + "42:0": "iron_block", + "43:0": "stone_slab[type=double]", + "43:1": "sandstone_slab[type=double]", + "43:2": "petrified_oak_slab[type=double]", + "43:3": "cobblestone_slab[type=double]", + "43:4": "brick_slab[type=double]", + "43:5": "stone_brick_slab[type=double]", + "43:6": "nether_brick_slab[type=double]", + "43:7": "quartz_slab[type=double]", + "43:8": "smooth_stone", + "43:9": "smooth_sandstone", + "43:10": "petrified_oak_slab[type=double]", + "43:11": "cobblestone_slab[type=double]", + "43:12": "brick_slab[type=double]", + "43:13": "stone_brick_slab[type=double]", + "43:14": "nether_brick_slab[type=double]", + "43:15": "smooth_quartz", + "44:0": "stone_slab[type=bottom]", + "44:1": "sandstone_slab[type=bottom]", + "44:2": "petrified_oak_slab[type=bottom]", + "44:3": "cobblestone_slab[type=bottom]", + "44:4": "brick_slab[type=bottom]", + "44:5": "stone_brick_slab[type=bottom]", + "44:6": "nether_brick_slab[type=bottom]", + "44:7": "quartz_slab[type=bottom]", + "44:8": "stone_slab[type=top]", + "44:9": "sandstone_slab[type=top]", + "44:10": "petrified_oak_slab[type=top]", + "44:11": "cobblestone_slab[type=top]", + "44:12": "brick_slab[type=top]", + "44:13": "stone_brick_slab[type=top]", + "44:14": "nether_brick_slab[type=top]", + "44:15": "quartz_slab[type=top]", + "45:0": "bricks", + "46:0": "tnt[unstable=false]", + "46:1": "tnt[unstable=true]", + "47:0": "bookshelf", + "48:0": "mossy_cobblestone", + "49:0": "obsidian", + "50:1": "wall_torch[facing=east]", + "50:2": "wall_torch[facing=west]", + "50:3": "wall_torch[facing=south]", + "50:4": "wall_torch[facing=north]", + "50:5": "torch", + "51:0": "fire[age=0,east=false,north=false,south=false,up=false,west=false]", + "51:1": "fire[age=1,east=false,north=false,south=false,up=false,west=false]", + "51:2": "fire[age=2,east=false,north=false,south=false,up=false,west=false]", + "51:3": "fire[age=3,east=false,north=false,south=false,up=false,west=false]", + "51:4": "fire[age=4,east=false,north=false,south=false,up=false,west=false]", + "51:5": "fire[age=5,east=false,north=false,south=false,up=false,west=false]", + "51:6": "fire[age=6,east=false,north=false,south=false,up=false,west=false]", + "51:7": "fire[age=7,east=false,north=false,south=false,up=false,west=false]", + "51:8": "fire[age=8,east=false,north=false,south=false,up=false,west=false]", + "51:9": "fire[age=9,east=false,north=false,south=false,up=false,west=false]", + "51:10": "fire[age=10,east=false,north=false,south=false,up=false,west=false]", + "51:11": "fire[age=11,east=false,north=false,south=false,up=false,west=false]", + "51:12": "fire[age=12,east=false,north=false,south=false,up=false,west=false]", + "51:13": "fire[age=13,east=false,north=false,south=false,up=false,west=false]", + "51:14": "fire[age=14,east=false,north=false,south=false,up=false,west=false]", + "51:15": "fire[age=15,east=false,north=false,south=false,up=false,west=false]", + "52:0": "mob_spawner", + "53:0": "oak_stairs[facing=east,half=bottom,shape=straight]", + "53:1": "oak_stairs[facing=west,half=bottom,shape=straight]", + "53:2": "oak_stairs[facing=south,half=bottom,shape=straight]", + "53:3": "oak_stairs[facing=north,half=bottom,shape=straight]", + "53:4": "oak_stairs[facing=east,half=top,shape=straight]", + "53:5": "oak_stairs[facing=west,half=top,shape=straight]", + "53:6": "oak_stairs[facing=south,half=top,shape=straight]", + "53:7": "oak_stairs[facing=north,half=top,shape=straight]", + "54:2": "chest[facing=north,type=single]", + "54:3": "chest[facing=south,type=single]", + "54:4": "chest[facing=west,type=single]", + "54:5": "chest[facing=east,type=single]", + "55:0": "redstone_wire[east=none,north=none,power=0,south=none,west=none]", + "55:1": "redstone_wire[east=none,north=none,power=1,south=none,west=none]", + "55:2": "redstone_wire[east=none,north=none,power=2,south=none,west=none]", + "55:3": "redstone_wire[east=none,north=none,power=3,south=none,west=none]", + "55:4": "redstone_wire[east=none,north=none,power=4,south=none,west=none]", + "55:5": "redstone_wire[east=none,north=none,power=5,south=none,west=none]", + "55:6": "redstone_wire[east=none,north=none,power=6,south=none,west=none]", + "55:7": "redstone_wire[east=none,north=none,power=7,south=none,west=none]", + "55:8": "redstone_wire[east=none,north=none,power=8,south=none,west=none]", + "55:9": "redstone_wire[east=none,north=none,power=9,south=none,west=none]", + "55:10": "redstone_wire[east=none,north=none,power=10,south=none,west=none]", + "55:11": "redstone_wire[east=none,north=none,power=11,south=none,west=none]", + "55:12": "redstone_wire[east=none,north=none,power=12,south=none,west=none]", + "55:13": "redstone_wire[east=none,north=none,power=13,south=none,west=none]", + "55:14": "redstone_wire[east=none,north=none,power=14,south=none,west=none]", + "55:15": "redstone_wire[east=none,north=none,power=15,south=none,west=none]", + "56:0": "diamond_ore", + "57:0": "diamond_block", + "58:0": "crafting_table", + "59:0": "wheat[age=0]", + "59:1": "wheat[age=1]", + "59:2": "wheat[age=2]", + "59:3": "wheat[age=3]", + "59:4": "wheat[age=4]", + "59:5": "wheat[age=5]", + "59:6": "wheat[age=6]", + "59:7": "wheat[age=7]", + "60:0": "farmland[moisture=0]", + "60:1": "farmland[moisture=1]", + "60:2": "farmland[moisture=2]", + "60:3": "farmland[moisture=3]", + "60:4": "farmland[moisture=4]", + "60:5": "farmland[moisture=5]", + "60:6": "farmland[moisture=6]", + "60:7": "farmland[moisture=7]", + "61:2": "furnace[facing=north,lit=false]", + "61:3": "furnace[facing=south,lit=false]", + "61:4": "furnace[facing=west,lit=false]", + "61:5": "furnace[facing=east,lit=false]", + "62:2": "furnace[facing=north,lit=true]", + "62:3": "furnace[facing=south,lit=true]", + "62:4": "furnace[facing=west,lit=true]", + "62:5": "furnace[facing=east,lit=true]", + "63:0": "sign[rotation=0]", + "63:1": "sign[rotation=1]", + "63:2": "sign[rotation=2]", + "63:3": "sign[rotation=3]", + "63:4": "sign[rotation=4]", + "63:5": "sign[rotation=5]", + "63:6": "sign[rotation=6]", + "63:7": "sign[rotation=7]", + "63:8": "sign[rotation=8]", + "63:9": "sign[rotation=9]", + "63:10": "sign[rotation=10]", + "63:11": "sign[rotation=11]", + "63:12": "sign[rotation=12]", + "63:13": "sign[rotation=13]", + "63:14": "sign[rotation=14]", + "63:15": "sign[rotation=15]", + "64:0": "oak_door[facing=east,half=lower,hinge=right,open=false,powered=false]", + "64:1": "oak_door[facing=south,half=lower,hinge=right,open=false,powered=false]", + "64:2": "oak_door[facing=west,half=lower,hinge=right,open=false,powered=false]", + "64:3": "oak_door[facing=north,half=lower,hinge=right,open=false,powered=false]", + "64:4": "oak_door[facing=east,half=lower,hinge=right,open=true,powered=false]", + "64:5": "oak_door[facing=south,half=lower,hinge=right,open=true,powered=false]", + "64:6": "oak_door[facing=west,half=lower,hinge=right,open=true,powered=false]", + "64:7": "oak_door[facing=north,half=lower,hinge=right,open=true,powered=false]", + "64:8": "oak_door[facing=east,half=upper,hinge=left,open=false,powered=false]", + "64:9": "oak_door[facing=east,half=upper,hinge=right,open=false,powered=false]", + "64:10": "oak_door[facing=east,half=upper,hinge=left,open=false,powered=true]", + "64:11": "oak_door[facing=east,half=upper,hinge=right,open=false,powered=true]", + "65:2": "ladder[facing=north]", + "65:3": "ladder[facing=south]", + "65:4": "ladder[facing=west]", + "65:5": "ladder[facing=east]", + "66:0": "rail[shape=north_south]", + "66:1": "rail[shape=east_west]", + "66:2": "rail[shape=ascending_east]", + "66:3": "rail[shape=ascending_west]", + "66:4": "rail[shape=ascending_north]", + "66:5": "rail[shape=ascending_south]", + "66:6": "rail[shape=south_east]", + "66:7": "rail[shape=south_west]", + "66:8": "rail[shape=north_west]", + "66:9": "rail[shape=north_east]", + "67:0": "cobblestone_stairs[facing=east,half=bottom,shape=straight]", + "67:1": "cobblestone_stairs[facing=west,half=bottom,shape=straight]", + "67:2": "cobblestone_stairs[facing=south,half=bottom,shape=straight]", + "67:3": "cobblestone_stairs[facing=north,half=bottom,shape=straight]", + "67:4": "cobblestone_stairs[facing=east,half=top,shape=straight]", + "67:5": "cobblestone_stairs[facing=west,half=top,shape=straight]", + "67:6": "cobblestone_stairs[facing=south,half=top,shape=straight]", + "67:7": "cobblestone_stairs[facing=north,half=top,shape=straight]", + "68:2": "wall_sign[facing=north]", + "68:3": "wall_sign[facing=south]", + "68:4": "wall_sign[facing=west]", + "68:5": "wall_sign[facing=east]", + "69:0": "lever[face=ceiling,facing=west,powered=false]", + "69:1": "lever[face=wall,facing=east,powered=false]", + "69:2": "lever[face=wall,facing=west,powered=false]", + "69:3": "lever[face=wall,facing=south,powered=false]", + "69:4": "lever[face=wall,facing=north,powered=false]", + "69:5": "lever[face=floor,facing=north,powered=false]", + "69:6": "lever[face=floor,facing=west,powered=false]", + "69:7": "lever[face=ceiling,facing=north,powered=false]", + "69:8": "lever[face=ceiling,facing=west,powered=true]", + "69:9": "lever[face=wall,facing=east,powered=true]", + "69:10": "lever[face=wall,facing=west,powered=true]", + "69:11": "lever[face=wall,facing=south,powered=true]", + "69:12": "lever[face=wall,facing=north,powered=true]", + "69:13": "lever[face=floor,facing=north,powered=true]", + "69:14": "lever[face=floor,facing=west,powered=true]", + "69:15": "lever[face=ceiling,facing=north,powered=true]", + "70:0": "stone_pressure_plate[powered=false]", + "70:1": "stone_pressure_plate[powered=true]", + "71:0": "iron_door[facing=east,half=lower,hinge=right,open=false,powered=false]", + "71:1": "iron_door[facing=south,half=lower,hinge=right,open=false,powered=false]", + "71:2": "iron_door[facing=west,half=lower,hinge=right,open=false,powered=false]", + "71:3": "iron_door[facing=north,half=lower,hinge=right,open=false,powered=false]", + "71:4": "iron_door[facing=east,half=lower,hinge=right,open=true,powered=false]", + "71:5": "iron_door[facing=south,half=lower,hinge=right,open=true,powered=false]", + "71:6": "iron_door[facing=west,half=lower,hinge=right,open=true,powered=false]", + "71:7": "iron_door[facing=north,half=lower,hinge=right,open=true,powered=false]", + "71:8": "iron_door[facing=east,half=upper,hinge=left,open=false,powered=false]", + "71:9": "iron_door[facing=east,half=upper,hinge=right,open=false,powered=false]", + "71:10": "iron_door[facing=east,half=upper,hinge=left,open=false,powered=true]", + "71:11": "iron_door[facing=east,half=upper,hinge=right,open=false,powered=true]", + "72:0": "oak_pressure_plate[powered=false]", + "72:1": "oak_pressure_plate[powered=true]", + "73:0": "redstone_ore[lit=false]", + "74:0": "redstone_ore[lit=true]", + "75:1": "redstone_wall_torch[facing=east,lit=false]", + "75:2": "redstone_wall_torch[facing=west,lit=false]", + "75:3": "redstone_wall_torch[facing=south,lit=false]", + "75:4": "redstone_wall_torch[facing=north,lit=false]", + "75:5": "redstone_torch[lit=false]", + "76:1": "redstone_wall_torch[facing=east,lit=true]", + "76:2": "redstone_wall_torch[facing=west,lit=true]", + "76:3": "redstone_wall_torch[facing=south,lit=true]", + "76:4": "redstone_wall_torch[facing=north,lit=true]", + "76:5": "redstone_torch[lit=true]", + "77:0": "stone_button[face=ceiling,facing=north,powered=false]", + "77:1": "stone_button[face=wall,facing=east,powered=false]", + "77:2": "stone_button[face=wall,facing=west,powered=false]", + "77:3": "stone_button[face=wall,facing=south,powered=false]", + "77:4": "stone_button[face=wall,facing=north,powered=false]", + "77:5": "stone_button[face=floor,facing=north,powered=false]", + "77:8": "stone_button[face=ceiling,facing=north,powered=true]", + "77:9": "stone_button[face=wall,facing=east,powered=true]", + "77:10": "stone_button[face=wall,facing=west,powered=true]", + "77:11": "stone_button[face=wall,facing=south,powered=true]", + "77:12": "stone_button[face=wall,facing=north,powered=true]", + "77:13": "stone_button[face=floor,facing=north,powered=true]", + "78:0": "snow[layers=1]", + "78:1": "snow[layers=2]", + "78:2": "snow[layers=3]", + "78:3": "snow[layers=4]", + "78:4": "snow[layers=5]", + "78:5": "snow[layers=6]", + "78:6": "snow[layers=7]", + "78:7": "snow[layers=8]", + "79:0": "ice", + "80:0": "snow_block", + "81:0": "cactus[age=0]", + "81:1": "cactus[age=1]", + "81:2": "cactus[age=2]", + "81:3": "cactus[age=3]", + "81:4": "cactus[age=4]", + "81:5": "cactus[age=5]", + "81:6": "cactus[age=6]", + "81:7": "cactus[age=7]", + "81:8": "cactus[age=8]", + "81:9": "cactus[age=9]", + "81:10": "cactus[age=10]", + "81:11": "cactus[age=11]", + "81:12": "cactus[age=12]", + "81:13": "cactus[age=13]", + "81:14": "cactus[age=14]", + "81:15": "cactus[age=15]", + "82:0": "clay", + "83:0": "sugar_cane[age=0]", + "83:1": "sugar_cane[age=1]", + "83:2": "sugar_cane[age=2]", + "83:3": "sugar_cane[age=3]", + "83:4": "sugar_cane[age=4]", + "83:5": "sugar_cane[age=5]", + "83:6": "sugar_cane[age=6]", + "83:7": "sugar_cane[age=7]", + "83:8": "sugar_cane[age=8]", + "83:9": "sugar_cane[age=9]", + "83:10": "sugar_cane[age=10]", + "83:11": "sugar_cane[age=11]", + "83:12": "sugar_cane[age=12]", + "83:13": "sugar_cane[age=13]", + "83:14": "sugar_cane[age=14]", + "83:15": "sugar_cane[age=15]", + "84:0": "jukebox[has_record=false]", + "84:1": "jukebox[has_record=true]", + "85:0": "oak_fence[east=false,north=false,south=false,west=false]", + "86:0": "carved_pumpkin[facing=south]", + "86:1": "carved_pumpkin[facing=west]", + "86:2": "carved_pumpkin[facing=north]", + "86:3": "carved_pumpkin[facing=east]", + "87:0": "netherrack", + "88:0": "soul_sand", + "89:0": "glowstone", + "90:1": "portal[axis=x]", + "90:2": "portal[axis=z]", + "91:0": "jack_o_lantern[facing=south]", + "91:1": "jack_o_lantern[facing=west]", + "91:2": "jack_o_lantern[facing=north]", + "91:3": "jack_o_lantern[facing=east]", + "92:0": "cake[bites=0]", + "92:1": "cake[bites=1]", + "92:2": "cake[bites=2]", + "92:3": "cake[bites=3]", + "92:4": "cake[bites=4]", + "92:5": "cake[bites=5]", + "92:6": "cake[bites=6]", + "93:0": "repeater[delay=1,facing=south,locked=false,powered=false]", + "93:1": "repeater[delay=1,facing=west,locked=false,powered=false]", + "93:2": "repeater[delay=1,facing=north,locked=false,powered=false]", + "93:3": "repeater[delay=1,facing=east,locked=false,powered=false]", + "93:4": "repeater[delay=2,facing=south,locked=false,powered=false]", + "93:5": "repeater[delay=2,facing=west,locked=false,powered=false]", + "93:6": "repeater[delay=2,facing=north,locked=false,powered=false]", + "93:7": "repeater[delay=2,facing=east,locked=false,powered=false]", + "93:8": "repeater[delay=3,facing=south,locked=false,powered=false]", + "93:9": "repeater[delay=3,facing=west,locked=false,powered=false]", + "93:10": "repeater[delay=3,facing=north,locked=false,powered=false]", + "93:11": "repeater[delay=3,facing=east,locked=false,powered=false]", + "93:12": "repeater[delay=4,facing=south,locked=false,powered=false]", + "93:13": "repeater[delay=4,facing=west,locked=false,powered=false]", + "93:14": "repeater[delay=4,facing=north,locked=false,powered=false]", + "93:15": "repeater[delay=4,facing=east,locked=false,powered=false]", + "94:0": "repeater[delay=1,facing=south,locked=false,powered=true]", + "94:1": "repeater[delay=1,facing=west,locked=false,powered=true]", + "94:2": "repeater[delay=1,facing=north,locked=false,powered=true]", + "94:3": "repeater[delay=1,facing=east,locked=false,powered=true]", + "94:4": "repeater[delay=2,facing=south,locked=false,powered=true]", + "94:5": "repeater[delay=2,facing=west,locked=false,powered=true]", + "94:6": "repeater[delay=2,facing=north,locked=false,powered=true]", + "94:7": "repeater[delay=2,facing=east,locked=false,powered=true]", + "94:8": "repeater[delay=3,facing=south,locked=false,powered=true]", + "94:9": "repeater[delay=3,facing=west,locked=false,powered=true]", + "94:10": "repeater[delay=3,facing=north,locked=false,powered=true]", + "94:11": "repeater[delay=3,facing=east,locked=false,powered=true]", + "94:12": "repeater[delay=4,facing=south,locked=false,powered=true]", + "94:13": "repeater[delay=4,facing=west,locked=false,powered=true]", + "94:14": "repeater[delay=4,facing=north,locked=false,powered=true]", + "94:15": "repeater[delay=4,facing=east,locked=false,powered=true]", + "95:0": "white_stained_glass", + "95:1": "orange_stained_glass", + "95:2": "magenta_stained_glass", + "95:3": "light_blue_stained_glass", + "95:4": "yellow_stained_glass", + "95:5": "lime_stained_glass", + "95:6": "pink_stained_glass", + "95:7": "gray_stained_glass", + "95:8": "light_gray_stained_glass", + "95:9": "cyan_stained_glass", + "95:10": "purple_stained_glass", + "95:11": "blue_stained_glass", + "95:12": "brown_stained_glass", + "95:13": "green_stained_glass", + "95:14": "red_stained_glass", + "95:15": "black_stained_glass", + "96:0": "oak_trapdoor[facing=north,half=bottom,open=false]", + "96:1": "oak_trapdoor[facing=south,half=bottom,open=false]", + "96:2": "oak_trapdoor[facing=west,half=bottom,open=false]", + "96:3": "oak_trapdoor[facing=east,half=bottom,open=false]", + "96:4": "oak_trapdoor[facing=north,half=bottom,open=true]", + "96:5": "oak_trapdoor[facing=south,half=bottom,open=true]", + "96:6": "oak_trapdoor[facing=west,half=bottom,open=true]", + "96:7": "oak_trapdoor[facing=east,half=bottom,open=true]", + "96:8": "oak_trapdoor[facing=north,half=top,open=false]", + "96:9": "oak_trapdoor[facing=south,half=top,open=false]", + "96:10": "oak_trapdoor[facing=west,half=top,open=false]", + "96:11": "oak_trapdoor[facing=east,half=top,open=false]", + "96:12": "oak_trapdoor[facing=north,half=top,open=true]", + "96:13": "oak_trapdoor[facing=south,half=top,open=true]", + "96:14": "oak_trapdoor[facing=west,half=top,open=true]", + "96:15": "oak_trapdoor[facing=east,half=top,open=true]", + "97:0": "infested_stone", + "97:1": "infested_cobblestone", + "97:2": "infested_stone_bricks", + "97:3": "infested_mossy_stone_bricks", + "97:4": "infested_cracked_stone_bricks", + "97:5": "infested_chiseled_stone_bricks", + "98:0": "stone_bricks", + "98:1": "mossy_stone_bricks", + "98:2": "cracked_stone_bricks", + "98:3": "chiseled_stone_bricks", + "99:0": "brown_mushroom_block[north=false,east=false,south=false,west=false,up=false,down=false]", + "99:1": "brown_mushroom_block[north=true,east=false,south=false,west=true,up=true,down=false]", + "99:2": "brown_mushroom_block[north=true,east=false,south=false,west=false,up=true,down=false]", + "99:3": "brown_mushroom_block[north=true,east=true,south=false,west=false,up=true,down=false]", + "99:4": "brown_mushroom_block[north=false,east=false,south=false,west=true,up=true,down=false]", + "99:5": "brown_mushroom_block[north=false,east=false,south=false,west=false,up=true,down=false]", + "99:6": "brown_mushroom_block[north=false,east=true,south=false,west=false,up=true,down=false]", + "99:7": "brown_mushroom_block[north=false,east=false,south=true,west=true,up=true,down=false]", + "99:8": "brown_mushroom_block[north=false,east=false,south=true,west=false,up=true,down=false]", + "99:9": "brown_mushroom_block[north=false,east=true,south=true,west=false,up=true,down=false]", + "99:10": "mushroom_stem[north=true,east=true,south=true,west=true,up=false,down=false]", + "99:14": "brown_mushroom_block[north=true,east=true,south=true,west=true,up=true,down=true]", + "99:15": "mushroom_stem[north=true,east=true,south=true,west=true,up=true,down=true]", + "100:0": "red_mushroom_block[north=false,east=false,south=false,west=false,up=false,down=false]", + "100:1": "red_mushroom_block[north=true,east=false,south=false,west=true,up=true,down=false]", + "100:2": "red_mushroom_block[north=true,east=false,south=false,west=false,up=true,down=false]", + "100:3": "red_mushroom_block[north=true,east=true,south=false,west=false,up=true,down=false]", + "100:4": "red_mushroom_block[north=false,east=false,south=false,west=true,up=true,down=false]", + "100:5": "red_mushroom_block[north=false,east=false,south=false,west=false,up=true,down=false]", + "100:6": "red_mushroom_block[north=false,east=true,south=false,west=false,up=true,down=false]", + "100:7": "red_mushroom_block[north=false,east=false,south=true,west=true,up=true,down=false]", + "100:8": "red_mushroom_block[north=false,east=false,south=true,west=false,up=true,down=false]", + "100:9": "red_mushroom_block[north=false,east=true,south=true,west=false,up=true,down=false]", + "100:10": "mushroom_stem[north=true,east=true,south=true,west=true,up=false,down=false]", + "100:14": "red_mushroom_block[north=true,east=true,south=true,west=true,up=true,down=true]", + "100:15": "mushroom_stem[north=true,east=true,south=true,west=true,up=true,down=true]", + "101:0": "iron_bars[east=false,north=false,south=false,west=false]", + "102:0": "glass_pane[east=false,north=false,south=false,west=false]", + "103:0": "melon_block", + "104:0": "pumpkin_stem[age=0]", + "104:1": "pumpkin_stem[age=1]", + "104:2": "pumpkin_stem[age=2]", + "104:3": "pumpkin_stem[age=3]", + "104:4": "pumpkin_stem[age=4]", + "104:5": "pumpkin_stem[age=5]", + "104:6": "pumpkin_stem[age=6]", + "104:7": "pumpkin_stem[age=7]", + "105:0": "melon_stem[age=0]", + "105:1": "melon_stem[age=1]", + "105:2": "melon_stem[age=2]", + "105:3": "melon_stem[age=3]", + "105:4": "melon_stem[age=4]", + "105:5": "melon_stem[age=5]", + "105:6": "melon_stem[age=6]", + "105:7": "melon_stem[age=7]", + "106:0": "vine[east=false,north=false,south=false,up=true,west=false]", + "106:1": "vine[east=false,north=false,south=true,up=true,west=false]", + "106:2": "vine[east=false,north=false,south=false,up=true,west=true]", + "106:3": "vine[east=false,north=false,south=true,up=true,west=true]", + "106:4": "vine[east=false,north=true,south=false,up=true,west=false]", + "106:5": "vine[east=false,north=true,south=true,up=true,west=false]", + "106:6": "vine[east=false,north=true,south=false,up=true,west=true]", + "106:7": "vine[east=false,north=true,south=true,up=true,west=true]", + "106:8": "vine[east=true,north=false,south=false,up=true,west=false]", + "106:9": "vine[east=true,north=false,south=true,up=true,west=false]", + "106:10": "vine[east=true,north=false,south=false,up=true,west=true]", + "106:11": "vine[east=true,north=false,south=true,up=true,west=true]", + "106:12": "vine[east=true,north=true,south=false,up=true,west=false]", + "106:13": "vine[east=true,north=true,south=true,up=true,west=false]", + "106:14": "vine[east=true,north=true,south=false,up=true,west=true]", + "106:15": "vine[east=true,north=true,south=true,up=true,west=true]", + "107:0": "oak_fence_gate[facing=south,in_wall=false,open=false,powered=false]", + "107:1": "oak_fence_gate[facing=west,in_wall=false,open=false,powered=false]", + "107:2": "oak_fence_gate[facing=north,in_wall=false,open=false,powered=false]", + "107:3": "oak_fence_gate[facing=east,in_wall=false,open=false,powered=false]", + "107:4": "oak_fence_gate[facing=south,in_wall=false,open=true,powered=false]", + "107:5": "oak_fence_gate[facing=west,in_wall=false,open=true,powered=false]", + "107:6": "oak_fence_gate[facing=north,in_wall=false,open=true,powered=false]", + "107:7": "oak_fence_gate[facing=east,in_wall=false,open=true,powered=false]", + "107:8": "oak_fence_gate[facing=south,in_wall=false,open=false,powered=true]", + "107:9": "oak_fence_gate[facing=west,in_wall=false,open=false,powered=true]", + "107:10": "oak_fence_gate[facing=north,in_wall=false,open=false,powered=true]", + "107:11": "oak_fence_gate[facing=east,in_wall=false,open=false,powered=true]", + "107:12": "oak_fence_gate[facing=south,in_wall=false,open=true,powered=true]", + "107:13": "oak_fence_gate[facing=west,in_wall=false,open=true,powered=true]", + "107:14": "oak_fence_gate[facing=north,in_wall=false,open=true,powered=true]", + "107:15": "oak_fence_gate[facing=east,in_wall=false,open=true,powered=true]", + "108:0": "brick_stairs[facing=east,half=bottom,shape=straight]", + "108:1": "brick_stairs[facing=west,half=bottom,shape=straight]", + "108:2": "brick_stairs[facing=south,half=bottom,shape=straight]", + "108:3": "brick_stairs[facing=north,half=bottom,shape=straight]", + "108:4": "brick_stairs[facing=east,half=top,shape=straight]", + "108:5": "brick_stairs[facing=west,half=top,shape=straight]", + "108:6": "brick_stairs[facing=south,half=top,shape=straight]", + "108:7": "brick_stairs[facing=north,half=top,shape=straight]", + "109:0": "stone_brick_stairs[facing=east,half=bottom,shape=straight]", + "109:1": "stone_brick_stairs[facing=west,half=bottom,shape=straight]", + "109:2": "stone_brick_stairs[facing=south,half=bottom,shape=straight]", + "109:3": "stone_brick_stairs[facing=north,half=bottom,shape=straight]", + "109:4": "stone_brick_stairs[facing=east,half=top,shape=straight]", + "109:5": "stone_brick_stairs[facing=west,half=top,shape=straight]", + "109:6": "stone_brick_stairs[facing=south,half=top,shape=straight]", + "109:7": "stone_brick_stairs[facing=north,half=top,shape=straight]", + "110:0": "mycelium[snowy=false]", + "111:0": "lily_pad", + "112:0": "nether_bricks", + "113:0": "nether_brick_fence[east=false,north=false,south=false,west=false]", + "114:0": "nether_brick_stairs[facing=east,half=bottom,shape=straight]", + "114:1": "nether_brick_stairs[facing=west,half=bottom,shape=straight]", + "114:2": "nether_brick_stairs[facing=south,half=bottom,shape=straight]", + "114:3": "nether_brick_stairs[facing=north,half=bottom,shape=straight]", + "114:4": "nether_brick_stairs[facing=east,half=top,shape=straight]", + "114:5": "nether_brick_stairs[facing=west,half=top,shape=straight]", + "114:6": "nether_brick_stairs[facing=south,half=top,shape=straight]", + "114:7": "nether_brick_stairs[facing=north,half=top,shape=straight]", + "115:0": "nether_wart[age=0]", + "115:1": "nether_wart[age=1]", + "115:2": "nether_wart[age=2]", + "115:3": "nether_wart[age=3]", + "116:0": "enchanting_table", + "117:0": "brewing_stand[has_bottle_0=false,has_bottle_1=false,has_bottle_2=false]", + "117:1": "brewing_stand[has_bottle_0=true,has_bottle_1=false,has_bottle_2=false]", + "117:2": "brewing_stand[has_bottle_0=false,has_bottle_1=true,has_bottle_2=false]", + "117:3": "brewing_stand[has_bottle_0=true,has_bottle_1=true,has_bottle_2=false]", + "117:4": "brewing_stand[has_bottle_0=false,has_bottle_1=false,has_bottle_2=true]", + "117:5": "brewing_stand[has_bottle_0=true,has_bottle_1=false,has_bottle_2=true]", + "117:6": "brewing_stand[has_bottle_0=false,has_bottle_1=true,has_bottle_2=true]", + "117:7": "brewing_stand[has_bottle_0=true,has_bottle_1=true,has_bottle_2=true]", + "118:0": "cauldron[level=0]", + "118:1": "cauldron[level=1]", + "118:2": "cauldron[level=2]", + "118:3": "cauldron[level=3]", + "119:0": "end_portal", + "120:0": "end_portal_frame[eye=false,facing=south]", + "120:1": "end_portal_frame[eye=false,facing=west]", + "120:2": "end_portal_frame[eye=false,facing=north]", + "120:3": "end_portal_frame[eye=false,facing=east]", + "120:4": "end_portal_frame[eye=true,facing=south]", + "120:5": "end_portal_frame[eye=true,facing=west]", + "120:6": "end_portal_frame[eye=true,facing=north]", + "120:7": "end_portal_frame[eye=true,facing=east]", + "121:0": "end_stone", + "122:0": "dragon_egg", + "123:0": "redstone_lamp[lit=false]", + "124:0": "redstone_lamp[lit=true]", + "125:0": "oak_slab[type=double]", + "125:1": "spruce_slab[type=double]", + "125:2": "birch_slab[type=double]", + "125:3": "jungle_slab[type=double]", + "125:4": "acacia_slab[type=double]", + "125:5": "dark_oak_slab[type=double]", + "126:0": "oak_slab[type=bottom]", + "126:1": "spruce_slab[type=bottom]", + "126:2": "birch_slab[type=bottom]", + "126:3": "jungle_slab[type=bottom]", + "126:4": "acacia_slab[type=bottom]", + "126:5": "dark_oak_slab[type=bottom]", + "126:8": "oak_slab[type=top]", + "126:9": "spruce_slab[type=top]", + "126:10": "birch_slab[type=top]", + "126:11": "jungle_slab[type=top]", + "126:12": "acacia_slab[type=top]", + "126:13": "dark_oak_slab[type=top]", + "127:0": "cocoa[age=0,facing=south]", + "127:1": "cocoa[age=0,facing=west]", + "127:2": "cocoa[age=0,facing=north]", + "127:3": "cocoa[age=0,facing=east]", + "127:4": "cocoa[age=1,facing=south]", + "127:5": "cocoa[age=1,facing=west]", + "127:6": "cocoa[age=1,facing=north]", + "127:7": "cocoa[age=1,facing=east]", + "127:8": "cocoa[age=2,facing=south]", + "127:9": "cocoa[age=2,facing=west]", + "127:10": "cocoa[age=2,facing=north]", + "127:11": "cocoa[age=2,facing=east]", + "128:0": "sandstone_stairs[facing=east,half=bottom,shape=straight]", + "128:1": "sandstone_stairs[facing=west,half=bottom,shape=straight]", + "128:2": "sandstone_stairs[facing=south,half=bottom,shape=straight]", + "128:3": "sandstone_stairs[facing=north,half=bottom,shape=straight]", + "128:4": "sandstone_stairs[facing=east,half=top,shape=straight]", + "128:5": "sandstone_stairs[facing=west,half=top,shape=straight]", + "128:6": "sandstone_stairs[facing=south,half=top,shape=straight]", + "128:7": "sandstone_stairs[facing=north,half=top,shape=straight]", + "129:0": "emerald_ore", + "130:2": "ender_chest[facing=north]", + "130:3": "ender_chest[facing=south]", + "130:4": "ender_chest[facing=west]", + "130:5": "ender_chest[facing=east]", + "131:0": "tripwire_hook[attached=false,facing=south,powered=false]", + "131:1": "tripwire_hook[attached=false,facing=west,powered=false]", + "131:2": "tripwire_hook[attached=false,facing=north,powered=false]", + "131:3": "tripwire_hook[attached=false,facing=east,powered=false]", + "131:4": "tripwire_hook[attached=true,facing=south,powered=false]", + "131:5": "tripwire_hook[attached=true,facing=west,powered=false]", + "131:6": "tripwire_hook[attached=true,facing=north,powered=false]", + "131:7": "tripwire_hook[attached=true,facing=east,powered=false]", + "131:8": "tripwire_hook[attached=false,facing=south,powered=true]", + "131:9": "tripwire_hook[attached=false,facing=west,powered=true]", + "131:10": "tripwire_hook[attached=false,facing=north,powered=true]", + "131:11": "tripwire_hook[attached=false,facing=east,powered=true]", + "131:12": "tripwire_hook[attached=true,facing=south,powered=true]", + "131:13": "tripwire_hook[attached=true,facing=west,powered=true]", + "131:14": "tripwire_hook[attached=true,facing=north,powered=true]", + "131:15": "tripwire_hook[attached=true,facing=east,powered=true]", + "132:0": "tripwire[attached=false,disarmed=false,east=false,north=false,powered=false,south=false,west=false]", + "132:1": "tripwire[attached=false,disarmed=false,east=false,north=false,powered=true,south=false,west=false]", + "132:4": "tripwire[attached=true,disarmed=false,east=false,north=false,powered=false,south=false,west=false]", + "132:5": "tripwire[attached=true,disarmed=false,east=false,north=false,powered=true,south=false,west=false]", + "132:8": "tripwire[attached=false,disarmed=true,east=false,north=false,powered=false,south=false,west=false]", + "132:9": "tripwire[attached=false,disarmed=true,east=false,north=false,powered=true,south=false,west=false]", + "132:12": "tripwire[attached=true,disarmed=true,east=false,north=false,powered=false,south=false,west=false]", + "132:13": "tripwire[attached=true,disarmed=true,east=false,north=false,powered=true,south=false,west=false]", + "133:0": "emerald_block", + "134:0": "spruce_stairs[facing=east,half=bottom,shape=straight]", + "134:1": "spruce_stairs[facing=west,half=bottom,shape=straight]", + "134:2": "spruce_stairs[facing=south,half=bottom,shape=straight]", + "134:3": "spruce_stairs[facing=north,half=bottom,shape=straight]", + "134:4": "spruce_stairs[facing=east,half=top,shape=straight]", + "134:5": "spruce_stairs[facing=west,half=top,shape=straight]", + "134:6": "spruce_stairs[facing=south,half=top,shape=straight]", + "134:7": "spruce_stairs[facing=north,half=top,shape=straight]", + "135:0": "birch_stairs[facing=east,half=bottom,shape=straight]", + "135:1": "birch_stairs[facing=west,half=bottom,shape=straight]", + "135:2": "birch_stairs[facing=south,half=bottom,shape=straight]", + "135:3": "birch_stairs[facing=north,half=bottom,shape=straight]", + "135:4": "birch_stairs[facing=east,half=top,shape=straight]", + "135:5": "birch_stairs[facing=west,half=top,shape=straight]", + "135:6": "birch_stairs[facing=south,half=top,shape=straight]", + "135:7": "birch_stairs[facing=north,half=top,shape=straight]", + "136:0": "jungle_stairs[facing=east,half=bottom,shape=straight]", + "136:1": "jungle_stairs[facing=west,half=bottom,shape=straight]", + "136:2": "jungle_stairs[facing=south,half=bottom,shape=straight]", + "136:3": "jungle_stairs[facing=north,half=bottom,shape=straight]", + "136:4": "jungle_stairs[facing=east,half=top,shape=straight]", + "136:5": "jungle_stairs[facing=west,half=top,shape=straight]", + "136:6": "jungle_stairs[facing=south,half=top,shape=straight]", + "136:7": "jungle_stairs[facing=north,half=top,shape=straight]", + "137:0": "command_block[conditional=false,facing=down]", + "137:1": "command_block[conditional=false,facing=up]", + "137:2": "command_block[conditional=false,facing=north]", + "137:3": "command_block[conditional=false,facing=south]", + "137:4": "command_block[conditional=false,facing=west]", + "137:5": "command_block[conditional=false,facing=east]", + "137:8": "command_block[conditional=true,facing=down]", + "137:9": "command_block[conditional=true,facing=up]", + "137:10": "command_block[conditional=true,facing=north]", + "137:11": "command_block[conditional=true,facing=south]", + "137:12": "command_block[conditional=true,facing=west]", + "137:13": "command_block[conditional=true,facing=east]", + "138:0": "beacon", + "139:0": "cobblestone_wall[east=false,north=false,south=false,up=false,west=false]", + "139:1": "mossy_cobblestone_wall[east=false,north=false,south=false,up=false,west=false]", + "140:0": "potted_cactus", + "140:1": "potted_cactus", + "140:2": "potted_cactus", + "140:3": "potted_cactus", + "140:4": "potted_cactus", + "140:5": "potted_cactus", + "140:6": "potted_cactus", + "140:7": "potted_cactus", + "140:8": "potted_cactus", + "140:9": "potted_cactus", + "140:10": "potted_cactus", + "140:11": "potted_cactus", + "140:12": "potted_cactus", + "140:13": "potted_cactus", + "140:14": "potted_cactus", + "140:15": "potted_cactus", + "141:0": "carrots[age=0]", + "141:1": "carrots[age=1]", + "141:2": "carrots[age=2]", + "141:3": "carrots[age=3]", + "141:4": "carrots[age=4]", + "141:5": "carrots[age=5]", + "141:6": "carrots[age=6]", + "141:7": "carrots[age=7]", + "142:0": "potatoes[age=0]", + "142:1": "potatoes[age=1]", + "142:2": "potatoes[age=2]", + "142:3": "potatoes[age=3]", + "142:4": "potatoes[age=4]", + "142:5": "potatoes[age=5]", + "142:6": "potatoes[age=6]", + "142:7": "potatoes[age=7]", + "143:0": "oak_button[face=ceiling,facing=north,powered=false]", + "143:1": "oak_button[face=wall,facing=east,powered=false]", + "143:2": "oak_button[face=wall,facing=west,powered=false]", + "143:3": "oak_button[face=wall,facing=south,powered=false]", + "143:4": "oak_button[face=wall,facing=north,powered=false]", + "143:5": "oak_button[face=floor,facing=north,powered=false]", + "143:8": "oak_button[face=ceiling,facing=north,powered=true]", + "143:9": "oak_button[face=wall,facing=east,powered=true]", + "143:10": "oak_button[face=wall,facing=west,powered=true]", + "143:11": "oak_button[face=wall,facing=south,powered=true]", + "143:12": "oak_button[face=wall,facing=north,powered=true]", + "143:13": "oak_button[face=floor,facing=north,powered=true]", + "144:0": "undefined[facing=down,nodrop=false]", + "144:1": "undefined[facing=up,nodrop=false]", + "144:2": "undefined[facing=north,nodrop=false]", + "144:3": "undefined[facing=south,nodrop=false]", + "144:4": "undefined[facing=west,nodrop=false]", + "144:5": "undefined[facing=east,nodrop=false]", + "144:8": "undefined[facing=down,nodrop=true]", + "144:9": "undefined[facing=up,nodrop=true]", + "144:10": "undefined[facing=north,nodrop=true]", + "144:11": "undefined[facing=south,nodrop=true]", + "144:12": "undefined[facing=west,nodrop=true]", + "144:13": "undefined[facing=east,nodrop=true]", + "145:0": "anvil[facing=south]", + "145:1": "anvil[facing=west]", + "145:2": "anvil[facing=north]", + "145:3": "anvil[facing=east]", + "145:4": "chipped_anvil[facing=south]", + "145:5": "chipped_anvil[facing=west]", + "145:6": "chipped_anvil[facing=north]", + "145:7": "chipped_anvil[facing=east]", + "145:8": "damaged_anvil[facing=south]", + "145:9": "damaged_anvil[facing=west]", + "145:10": "damaged_anvil[facing=north]", + "145:11": "damaged_anvil[facing=east]", + "146:2": "trapped_chest[facing=north,type=single]", + "146:3": "trapped_chest[facing=south,type=single]", + "146:4": "trapped_chest[facing=west,type=single]", + "146:5": "trapped_chest[facing=east,type=single]", + "147:0": "light_weighted_pressure_plate[power=0]", + "147:1": "light_weighted_pressure_plate[power=1]", + "147:2": "light_weighted_pressure_plate[power=2]", + "147:3": "light_weighted_pressure_plate[power=3]", + "147:4": "light_weighted_pressure_plate[power=4]", + "147:5": "light_weighted_pressure_plate[power=5]", + "147:6": "light_weighted_pressure_plate[power=6]", + "147:7": "light_weighted_pressure_plate[power=7]", + "147:8": "light_weighted_pressure_plate[power=8]", + "147:9": "light_weighted_pressure_plate[power=9]", + "147:10": "light_weighted_pressure_plate[power=10]", + "147:11": "light_weighted_pressure_plate[power=11]", + "147:12": "light_weighted_pressure_plate[power=12]", + "147:13": "light_weighted_pressure_plate[power=13]", + "147:14": "light_weighted_pressure_plate[power=14]", + "147:15": "light_weighted_pressure_plate[power=15]", + "148:0": "heavy_weighted_pressure_plate[power=0]", + "148:1": "heavy_weighted_pressure_plate[power=1]", + "148:2": "heavy_weighted_pressure_plate[power=2]", + "148:3": "heavy_weighted_pressure_plate[power=3]", + "148:4": "heavy_weighted_pressure_plate[power=4]", + "148:5": "heavy_weighted_pressure_plate[power=5]", + "148:6": "heavy_weighted_pressure_plate[power=6]", + "148:7": "heavy_weighted_pressure_plate[power=7]", + "148:8": "heavy_weighted_pressure_plate[power=8]", + "148:9": "heavy_weighted_pressure_plate[power=9]", + "148:10": "heavy_weighted_pressure_plate[power=10]", + "148:11": "heavy_weighted_pressure_plate[power=11]", + "148:12": "heavy_weighted_pressure_plate[power=12]", + "148:13": "heavy_weighted_pressure_plate[power=13]", + "148:14": "heavy_weighted_pressure_plate[power=14]", + "148:15": "heavy_weighted_pressure_plate[power=15]", + "149:0": "comparator[facing=south,mode=compare,powered=false]", + "149:1": "comparator[facing=west,mode=compare,powered=false]", + "149:2": "comparator[facing=north,mode=compare,powered=false]", + "149:3": "comparator[facing=east,mode=compare,powered=false]", + "149:4": "comparator[facing=south,mode=subtract,powered=false]", + "149:5": "comparator[facing=west,mode=subtract,powered=false]", + "149:6": "comparator[facing=north,mode=subtract,powered=false]", + "149:7": "comparator[facing=east,mode=subtract,powered=false]", + "149:8": "comparator[facing=south,mode=compare,powered=true]", + "149:9": "comparator[facing=west,mode=compare,powered=true]", + "149:10": "comparator[facing=north,mode=compare,powered=true]", + "149:11": "comparator[facing=east,mode=compare,powered=true]", + "149:12": "comparator[facing=south,mode=subtract,powered=true]", + "149:13": "comparator[facing=west,mode=subtract,powered=true]", + "149:14": "comparator[facing=north,mode=subtract,powered=true]", + "149:15": "comparator[facing=east,mode=subtract,powered=true]", + "150:0": "comparator[facing=south,mode=compare,powered=false]", + "150:1": "comparator[facing=west,mode=compare,powered=false]", + "150:2": "comparator[facing=north,mode=compare,powered=false]", + "150:3": "comparator[facing=east,mode=compare,powered=false]", + "150:4": "comparator[facing=south,mode=subtract,powered=false]", + "150:5": "comparator[facing=west,mode=subtract,powered=false]", + "150:6": "comparator[facing=north,mode=subtract,powered=false]", + "150:7": "comparator[facing=east,mode=subtract,powered=false]", + "150:8": "comparator[facing=south,mode=compare,powered=true]", + "150:9": "comparator[facing=west,mode=compare,powered=true]", + "150:10": "comparator[facing=north,mode=compare,powered=true]", + "150:11": "comparator[facing=east,mode=compare,powered=true]", + "150:12": "comparator[facing=south,mode=subtract,powered=true]", + "150:13": "comparator[facing=west,mode=subtract,powered=true]", + "150:14": "comparator[facing=north,mode=subtract,powered=true]", + "150:15": "comparator[facing=east,mode=subtract,powered=true]", + "151:0": "daylight_detector[inverted=false,power=0]", + "151:1": "daylight_detector[inverted=false,power=1]", + "151:2": "daylight_detector[inverted=false,power=2]", + "151:3": "daylight_detector[inverted=false,power=3]", + "151:4": "daylight_detector[inverted=false,power=4]", + "151:5": "daylight_detector[inverted=false,power=5]", + "151:6": "daylight_detector[inverted=false,power=6]", + "151:7": "daylight_detector[inverted=false,power=7]", + "151:8": "daylight_detector[inverted=false,power=8]", + "151:9": "daylight_detector[inverted=false,power=9]", + "151:10": "daylight_detector[inverted=false,power=10]", + "151:11": "daylight_detector[inverted=false,power=11]", + "151:12": "daylight_detector[inverted=false,power=12]", + "151:13": "daylight_detector[inverted=false,power=13]", + "151:14": "daylight_detector[inverted=false,power=14]", + "151:15": "daylight_detector[inverted=false,power=15]", + "152:0": "redstone_block", + "153:0": "nether_quartz_ore", + "154:0": "hopper[enabled=true,facing=down]", + "154:2": "hopper[enabled=true,facing=north]", + "154:3": "hopper[enabled=true,facing=south]", + "154:4": "hopper[enabled=true,facing=west]", + "154:5": "hopper[enabled=true,facing=east]", + "154:8": "hopper[enabled=false,facing=down]", + "154:10": "hopper[enabled=false,facing=north]", + "154:11": "hopper[enabled=false,facing=south]", + "154:12": "hopper[enabled=false,facing=west]", + "154:13": "hopper[enabled=false,facing=east]", + "155:0": "quartz_block", + "155:1": "chiseled_quartz_block", + "155:2": "quartz_pillar[axis=y]", + "155:3": "quartz_pillar[axis=x]", + "155:4": "quartz_pillar[axis=z]", + "156:0": "quartz_stairs[facing=east,half=bottom,shape=straight]", + "156:1": "quartz_stairs[facing=west,half=bottom,shape=straight]", + "156:2": "quartz_stairs[facing=south,half=bottom,shape=straight]", + "156:3": "quartz_stairs[facing=north,half=bottom,shape=straight]", + "156:4": "quartz_stairs[facing=east,half=top,shape=straight]", + "156:5": "quartz_stairs[facing=west,half=top,shape=straight]", + "156:6": "quartz_stairs[facing=south,half=top,shape=straight]", + "156:7": "quartz_stairs[facing=north,half=top,shape=straight]", + "157:0": "activator_rail[powered=false,shape=north_south]", + "157:1": "activator_rail[powered=false,shape=east_west]", + "157:2": "activator_rail[powered=false,shape=ascending_east]", + "157:3": "activator_rail[powered=false,shape=ascending_west]", + "157:4": "activator_rail[powered=false,shape=ascending_north]", + "157:5": "activator_rail[powered=false,shape=ascending_south]", + "157:8": "activator_rail[powered=true,shape=north_south]", + "157:9": "activator_rail[powered=true,shape=east_west]", + "157:10": "activator_rail[powered=true,shape=ascending_east]", + "157:11": "activator_rail[powered=true,shape=ascending_west]", + "157:12": "activator_rail[powered=true,shape=ascending_north]", + "157:13": "activator_rail[powered=true,shape=ascending_south]", + "158:0": "dropper[facing=down,triggered=false]", + "158:1": "dropper[facing=up,triggered=false]", + "158:2": "dropper[facing=north,triggered=false]", + "158:3": "dropper[facing=south,triggered=false]", + "158:4": "dropper[facing=west,triggered=false]", + "158:5": "dropper[facing=east,triggered=false]", + "158:8": "dropper[facing=down,triggered=true]", + "158:9": "dropper[facing=up,triggered=true]", + "158:10": "dropper[facing=north,triggered=true]", + "158:11": "dropper[facing=south,triggered=true]", + "158:12": "dropper[facing=west,triggered=true]", + "158:13": "dropper[facing=east,triggered=true]", + "159:0": "white_terracotta", + "159:1": "orange_terracotta", + "159:2": "magenta_terracotta", + "159:3": "light_blue_terracotta", + "159:4": "yellow_terracotta", + "159:5": "lime_terracotta", + "159:6": "pink_terracotta", + "159:7": "gray_terracotta", + "159:8": "light_gray_terracotta", + "159:9": "cyan_terracotta", + "159:10": "purple_terracotta", + "159:11": "blue_terracotta", + "159:12": "brown_terracotta", + "159:13": "green_terracotta", + "159:14": "red_terracotta", + "159:15": "black_terracotta", + "160:0": "white_stained_glass_pane[east=false,north=false,south=false,west=false]", + "160:1": "orange_stained_glass_pane[east=false,north=false,south=false,west=false]", + "160:2": "magenta_stained_glass_pane[east=false,north=false,south=false,west=false]", + "160:3": "light_blue_stained_glass_pane[east=false,north=false,south=false,west=false]", + "160:4": "yellow_stained_glass_pane[east=false,north=false,south=false,west=false]", + "160:5": "lime_stained_glass_pane[east=false,north=false,south=false,west=false]", + "160:6": "pink_stained_glass_pane[east=false,north=false,south=false,west=false]", + "160:7": "gray_stained_glass_pane[east=false,north=false,south=false,west=false]", + "160:8": "light_gray_stained_glass_pane[east=false,north=false,south=false,west=false]", + "160:9": "cyan_stained_glass_pane[east=false,north=false,south=false,west=false]", + "160:10": "purple_stained_glass_pane[east=false,north=false,south=false,west=false]", + "160:11": "blue_stained_glass_pane[east=false,north=false,south=false,west=false]", + "160:12": "brown_stained_glass_pane[east=false,north=false,south=false,west=false]", + "160:13": "green_stained_glass_pane[east=false,north=false,south=false,west=false]", + "160:14": "red_stained_glass_pane[east=false,north=false,south=false,west=false]", + "160:15": "black_stained_glass_pane[east=false,north=false,south=false,west=false]", + "161:0": "acacia_leaves[check_decay=false,decayable=true]", + "161:1": "dark_oak_leaves[check_decay=false,decayable=true]", + "161:4": "acacia_leaves[check_decay=false,decayable=false]", + "161:5": "dark_oak_leaves[check_decay=false,decayable=false]", + "161:8": "acacia_leaves[check_decay=true,decayable=true]", + "161:9": "dark_oak_leaves[check_decay=true,decayable=true]", + "161:12": "acacia_leaves[check_decay=true,decayable=false]", + "161:13": "dark_oak_leaves[check_decay=true,decayable=false]", + "162:0": "acacia_log[axis=y]", + "162:1": "dark_oak_log[axis=y]", + "162:4": "acacia_log[axis=x]", + "162:5": "dark_oak_log[axis=x]", + "162:8": "acacia_log[axis=z]", + "162:9": "dark_oak_log[axis=z]", + "162:12": "acacia_bark", + "162:13": "dark_oak_bark", + "163:0": "acacia_stairs[facing=east,half=bottom,shape=straight]", + "163:1": "acacia_stairs[facing=west,half=bottom,shape=straight]", + "163:2": "acacia_stairs[facing=south,half=bottom,shape=straight]", + "163:3": "acacia_stairs[facing=north,half=bottom,shape=straight]", + "163:4": "acacia_stairs[facing=east,half=top,shape=straight]", + "163:5": "acacia_stairs[facing=west,half=top,shape=straight]", + "163:6": "acacia_stairs[facing=south,half=top,shape=straight]", + "163:7": "acacia_stairs[facing=north,half=top,shape=straight]", + "164:0": "dark_oak_stairs[facing=east,half=bottom,shape=straight]", + "164:1": "dark_oak_stairs[facing=west,half=bottom,shape=straight]", + "164:2": "dark_oak_stairs[facing=south,half=bottom,shape=straight]", + "164:3": "dark_oak_stairs[facing=north,half=bottom,shape=straight]", + "164:4": "dark_oak_stairs[facing=east,half=top,shape=straight]", + "164:5": "dark_oak_stairs[facing=west,half=top,shape=straight]", + "164:6": "dark_oak_stairs[facing=south,half=top,shape=straight]", + "164:7": "dark_oak_stairs[facing=north,half=top,shape=straight]", + "165:0": "slime_block", + "166:0": "barrier", + "167:0": "iron_trapdoor[facing=north,half=bottom,open=false]", + "167:1": "iron_trapdoor[facing=south,half=bottom,open=false]", + "167:2": "iron_trapdoor[facing=west,half=bottom,open=false]", + "167:3": "iron_trapdoor[facing=east,half=bottom,open=false]", + "167:4": "iron_trapdoor[facing=north,half=bottom,open=true]", + "167:5": "iron_trapdoor[facing=south,half=bottom,open=true]", + "167:6": "iron_trapdoor[facing=west,half=bottom,open=true]", + "167:7": "iron_trapdoor[facing=east,half=bottom,open=true]", + "167:8": "iron_trapdoor[facing=north,half=top,open=false]", + "167:9": "iron_trapdoor[facing=south,half=top,open=false]", + "167:10": "iron_trapdoor[facing=west,half=top,open=false]", + "167:11": "iron_trapdoor[facing=east,half=top,open=false]", + "167:12": "iron_trapdoor[facing=north,half=top,open=true]", + "167:13": "iron_trapdoor[facing=south,half=top,open=true]", + "167:14": "iron_trapdoor[facing=west,half=top,open=true]", + "167:15": "iron_trapdoor[facing=east,half=top,open=true]", + "168:0": "prismarine", + "168:1": "prismarine_bricks", + "168:2": "dark_prismarine", + "169:0": "sea_lantern", + "170:0": "hay_block[axis=y]", + "170:4": "hay_block[axis=x]", + "170:8": "hay_block[axis=z]", + "171:0": "white_carpet", + "171:1": "orange_carpet", + "171:2": "magenta_carpet", + "171:3": "light_blue_carpet", + "171:4": "yellow_carpet", + "171:5": "lime_carpet", + "171:6": "pink_carpet", + "171:7": "gray_carpet", + "171:8": "light_gray_carpet", + "171:9": "cyan_carpet", + "171:10": "purple_carpet", + "171:11": "blue_carpet", + "171:12": "brown_carpet", + "171:13": "green_carpet", + "171:14": "red_carpet", + "171:15": "black_carpet", + "172:0": "terracotta", + "173:0": "coal_block", + "174:0": "packed_ice", + "175:0": "sunflower[half=lower]", + "175:1": "lilac[half=lower]", + "175:2": "tall_grass[half=lower]", + "175:3": "large_fern[half=lower]", + "175:4": "rose_bush[half=lower]", + "175:5": "peony[half=lower]", + "175:8": "peony[half=upper]", + "175:9": "peony[half=upper]", + "175:10": "peony[half=upper]", + "175:11": "peony[half=upper]", + "176:0": "white_banner[rotation=0]", + "176:1": "white_banner[rotation=1]", + "176:2": "white_banner[rotation=2]", + "176:3": "white_banner[rotation=3]", + "176:4": "white_banner[rotation=4]", + "176:5": "white_banner[rotation=5]", + "176:6": "white_banner[rotation=6]", + "176:7": "white_banner[rotation=7]", + "176:8": "white_banner[rotation=8]", + "176:9": "white_banner[rotation=9]", + "176:10": "white_banner[rotation=10]", + "176:11": "white_banner[rotation=11]", + "176:12": "white_banner[rotation=12]", + "176:13": "white_banner[rotation=13]", + "176:14": "white_banner[rotation=14]", + "176:15": "white_banner[rotation=15]", + "177:2": "white_wall_banner[facing=north]", + "177:3": "white_wall_banner[facing=south]", + "177:4": "white_wall_banner[facing=west]", + "177:5": "white_wall_banner[facing=east]", + "178:0": "daylight_detector[inverted=true,power=0]", + "178:1": "daylight_detector[inverted=true,power=1]", + "178:2": "daylight_detector[inverted=true,power=2]", + "178:3": "daylight_detector[inverted=true,power=3]", + "178:4": "daylight_detector[inverted=true,power=4]", + "178:5": "daylight_detector[inverted=true,power=5]", + "178:6": "daylight_detector[inverted=true,power=6]", + "178:7": "daylight_detector[inverted=true,power=7]", + "178:8": "daylight_detector[inverted=true,power=8]", + "178:9": "daylight_detector[inverted=true,power=9]", + "178:10": "daylight_detector[inverted=true,power=10]", + "178:11": "daylight_detector[inverted=true,power=11]", + "178:12": "daylight_detector[inverted=true,power=12]", + "178:13": "daylight_detector[inverted=true,power=13]", + "178:14": "daylight_detector[inverted=true,power=14]", + "178:15": "daylight_detector[inverted=true,power=15]", + "179:0": "red_sandstone", + "179:1": "chiseled_red_sandstone", + "179:2": "cut_red_sandstone", + "180:0": "red_sandstone_stairs[facing=east,half=bottom,shape=straight]", + "180:1": "red_sandstone_stairs[facing=west,half=bottom,shape=straight]", + "180:2": "red_sandstone_stairs[facing=south,half=bottom,shape=straight]", + "180:3": "red_sandstone_stairs[facing=north,half=bottom,shape=straight]", + "180:4": "red_sandstone_stairs[facing=east,half=top,shape=straight]", + "180:5": "red_sandstone_stairs[facing=west,half=top,shape=straight]", + "180:6": "red_sandstone_stairs[facing=south,half=top,shape=straight]", + "180:7": "red_sandstone_stairs[facing=north,half=top,shape=straight]", + "181:0": "red_sandstone_slab[type=double]", + "181:8": "smooth_red_sandstone", + "182:0": "red_sandstone_slab[type=bottom]", + "182:8": "red_sandstone_slab[type=top]", + "183:0": "spruce_fence_gate[facing=south,in_wall=false,open=false,powered=false]", + "183:1": "spruce_fence_gate[facing=west,in_wall=false,open=false,powered=false]", + "183:2": "spruce_fence_gate[facing=north,in_wall=false,open=false,powered=false]", + "183:3": "spruce_fence_gate[facing=east,in_wall=false,open=false,powered=false]", + "183:4": "spruce_fence_gate[facing=south,in_wall=false,open=true,powered=false]", + "183:5": "spruce_fence_gate[facing=west,in_wall=false,open=true,powered=false]", + "183:6": "spruce_fence_gate[facing=north,in_wall=false,open=true,powered=false]", + "183:7": "spruce_fence_gate[facing=east,in_wall=false,open=true,powered=false]", + "183:8": "spruce_fence_gate[facing=south,in_wall=false,open=false,powered=true]", + "183:9": "spruce_fence_gate[facing=west,in_wall=false,open=false,powered=true]", + "183:10": "spruce_fence_gate[facing=north,in_wall=false,open=false,powered=true]", + "183:11": "spruce_fence_gate[facing=east,in_wall=false,open=false,powered=true]", + "183:12": "spruce_fence_gate[facing=south,in_wall=false,open=true,powered=true]", + "183:13": "spruce_fence_gate[facing=west,in_wall=false,open=true,powered=true]", + "183:14": "spruce_fence_gate[facing=north,in_wall=false,open=true,powered=true]", + "183:15": "spruce_fence_gate[facing=east,in_wall=false,open=true,powered=true]", + "184:0": "birch_fence_gate[facing=south,in_wall=false,open=false,powered=false]", + "184:1": "birch_fence_gate[facing=west,in_wall=false,open=false,powered=false]", + "184:2": "birch_fence_gate[facing=north,in_wall=false,open=false,powered=false]", + "184:3": "birch_fence_gate[facing=east,in_wall=false,open=false,powered=false]", + "184:4": "birch_fence_gate[facing=south,in_wall=false,open=true,powered=false]", + "184:5": "birch_fence_gate[facing=west,in_wall=false,open=true,powered=false]", + "184:6": "birch_fence_gate[facing=north,in_wall=false,open=true,powered=false]", + "184:7": "birch_fence_gate[facing=east,in_wall=false,open=true,powered=false]", + "184:8": "birch_fence_gate[facing=south,in_wall=false,open=false,powered=true]", + "184:9": "birch_fence_gate[facing=west,in_wall=false,open=false,powered=true]", + "184:10": "birch_fence_gate[facing=north,in_wall=false,open=false,powered=true]", + "184:11": "birch_fence_gate[facing=east,in_wall=false,open=false,powered=true]", + "184:12": "birch_fence_gate[facing=south,in_wall=false,open=true,powered=true]", + "184:13": "birch_fence_gate[facing=west,in_wall=false,open=true,powered=true]", + "184:14": "birch_fence_gate[facing=north,in_wall=false,open=true,powered=true]", + "184:15": "birch_fence_gate[facing=east,in_wall=false,open=true,powered=true]", + "185:0": "jungle_fence_gate[facing=south,in_wall=false,open=false,powered=false]", + "185:1": "jungle_fence_gate[facing=west,in_wall=false,open=false,powered=false]", + "185:2": "jungle_fence_gate[facing=north,in_wall=false,open=false,powered=false]", + "185:3": "jungle_fence_gate[facing=east,in_wall=false,open=false,powered=false]", + "185:4": "jungle_fence_gate[facing=south,in_wall=false,open=true,powered=false]", + "185:5": "jungle_fence_gate[facing=west,in_wall=false,open=true,powered=false]", + "185:6": "jungle_fence_gate[facing=north,in_wall=false,open=true,powered=false]", + "185:7": "jungle_fence_gate[facing=east,in_wall=false,open=true,powered=false]", + "185:8": "jungle_fence_gate[facing=south,in_wall=false,open=false,powered=true]", + "185:9": "jungle_fence_gate[facing=west,in_wall=false,open=false,powered=true]", + "185:10": "jungle_fence_gate[facing=north,in_wall=false,open=false,powered=true]", + "185:11": "jungle_fence_gate[facing=east,in_wall=false,open=false,powered=true]", + "185:12": "jungle_fence_gate[facing=south,in_wall=false,open=true,powered=true]", + "185:13": "jungle_fence_gate[facing=west,in_wall=false,open=true,powered=true]", + "185:14": "jungle_fence_gate[facing=north,in_wall=false,open=true,powered=true]", + "185:15": "jungle_fence_gate[facing=east,in_wall=false,open=true,powered=true]", + "186:0": "dark_oak_fence_gate[facing=south,in_wall=false,open=false,powered=false]", + "186:1": "dark_oak_fence_gate[facing=west,in_wall=false,open=false,powered=false]", + "186:2": "dark_oak_fence_gate[facing=north,in_wall=false,open=false,powered=false]", + "186:3": "dark_oak_fence_gate[facing=east,in_wall=false,open=false,powered=false]", + "186:4": "dark_oak_fence_gate[facing=south,in_wall=false,open=true,powered=false]", + "186:5": "dark_oak_fence_gate[facing=west,in_wall=false,open=true,powered=false]", + "186:6": "dark_oak_fence_gate[facing=north,in_wall=false,open=true,powered=false]", + "186:7": "dark_oak_fence_gate[facing=east,in_wall=false,open=true,powered=false]", + "186:8": "dark_oak_fence_gate[facing=south,in_wall=false,open=false,powered=true]", + "186:9": "dark_oak_fence_gate[facing=west,in_wall=false,open=false,powered=true]", + "186:10": "dark_oak_fence_gate[facing=north,in_wall=false,open=false,powered=true]", + "186:11": "dark_oak_fence_gate[facing=east,in_wall=false,open=false,powered=true]", + "186:12": "dark_oak_fence_gate[facing=south,in_wall=false,open=true,powered=true]", + "186:13": "dark_oak_fence_gate[facing=west,in_wall=false,open=true,powered=true]", + "186:14": "dark_oak_fence_gate[facing=north,in_wall=false,open=true,powered=true]", + "186:15": "dark_oak_fence_gate[facing=east,in_wall=false,open=true,powered=true]", + "187:0": "acacia_fence_gate[facing=south,in_wall=false,open=false,powered=false]", + "187:1": "acacia_fence_gate[facing=west,in_wall=false,open=false,powered=false]", + "187:2": "acacia_fence_gate[facing=north,in_wall=false,open=false,powered=false]", + "187:3": "acacia_fence_gate[facing=east,in_wall=false,open=false,powered=false]", + "187:4": "acacia_fence_gate[facing=south,in_wall=false,open=true,powered=false]", + "187:5": "acacia_fence_gate[facing=west,in_wall=false,open=true,powered=false]", + "187:6": "acacia_fence_gate[facing=north,in_wall=false,open=true,powered=false]", + "187:7": "acacia_fence_gate[facing=east,in_wall=false,open=true,powered=false]", + "187:8": "acacia_fence_gate[facing=south,in_wall=false,open=false,powered=true]", + "187:9": "acacia_fence_gate[facing=west,in_wall=false,open=false,powered=true]", + "187:10": "acacia_fence_gate[facing=north,in_wall=false,open=false,powered=true]", + "187:11": "acacia_fence_gate[facing=east,in_wall=false,open=false,powered=true]", + "187:12": "acacia_fence_gate[facing=south,in_wall=false,open=true,powered=true]", + "187:13": "acacia_fence_gate[facing=west,in_wall=false,open=true,powered=true]", + "187:14": "acacia_fence_gate[facing=north,in_wall=false,open=true,powered=true]", + "187:15": "acacia_fence_gate[facing=east,in_wall=false,open=true,powered=true]", + "188:0": "spruce_fence[east=false,north=false,south=false,west=false]", + "189:0": "birch_fence[east=false,north=false,south=false,west=false]", + "190:0": "jungle_fence[east=false,north=false,south=false,west=false]", + "191:0": "dark_oak_fence[east=false,north=false,south=false,west=false]", + "192:0": "acacia_fence[east=false,north=false,south=false,west=false]", + "193:0": "spruce_door[facing=east,half=lower,hinge=right,open=false,powered=false]", + "193:1": "spruce_door[facing=south,half=lower,hinge=right,open=false,powered=false]", + "193:2": "spruce_door[facing=west,half=lower,hinge=right,open=false,powered=false]", + "193:3": "spruce_door[facing=north,half=lower,hinge=right,open=false,powered=false]", + "193:4": "spruce_door[facing=east,half=lower,hinge=right,open=true,powered=false]", + "193:5": "spruce_door[facing=south,half=lower,hinge=right,open=true,powered=false]", + "193:6": "spruce_door[facing=west,half=lower,hinge=right,open=true,powered=false]", + "193:7": "spruce_door[facing=north,half=lower,hinge=right,open=true,powered=false]", + "193:8": "spruce_door[facing=east,half=upper,hinge=left,open=false,powered=false]", + "193:9": "spruce_door[facing=east,half=upper,hinge=right,open=false,powered=false]", + "193:10": "spruce_door[facing=east,half=upper,hinge=left,open=false,powered=true]", + "193:11": "spruce_door[facing=east,half=upper,hinge=right,open=false,powered=true]", + "194:0": "birch_door[facing=east,half=lower,hinge=right,open=false,powered=false]", + "194:1": "birch_door[facing=south,half=lower,hinge=right,open=false,powered=false]", + "194:2": "birch_door[facing=west,half=lower,hinge=right,open=false,powered=false]", + "194:3": "birch_door[facing=north,half=lower,hinge=right,open=false,powered=false]", + "194:4": "birch_door[facing=east,half=lower,hinge=right,open=true,powered=false]", + "194:5": "birch_door[facing=south,half=lower,hinge=right,open=true,powered=false]", + "194:6": "birch_door[facing=west,half=lower,hinge=right,open=true,powered=false]", + "194:7": "birch_door[facing=north,half=lower,hinge=right,open=true,powered=false]", + "194:8": "birch_door[facing=east,half=upper,hinge=left,open=false,powered=false]", + "194:9": "birch_door[facing=east,half=upper,hinge=right,open=false,powered=false]", + "194:10": "birch_door[facing=east,half=upper,hinge=left,open=false,powered=true]", + "194:11": "birch_door[facing=east,half=upper,hinge=right,open=false,powered=true]", + "195:0": "jungle_door[facing=east,half=lower,hinge=right,open=false,powered=false]", + "195:1": "jungle_door[facing=south,half=lower,hinge=right,open=false,powered=false]", + "195:2": "jungle_door[facing=west,half=lower,hinge=right,open=false,powered=false]", + "195:3": "jungle_door[facing=north,half=lower,hinge=right,open=false,powered=false]", + "195:4": "jungle_door[facing=east,half=lower,hinge=right,open=true,powered=false]", + "195:5": "jungle_door[facing=south,half=lower,hinge=right,open=true,powered=false]", + "195:6": "jungle_door[facing=west,half=lower,hinge=right,open=true,powered=false]", + "195:7": "jungle_door[facing=north,half=lower,hinge=right,open=true,powered=false]", + "195:8": "jungle_door[facing=east,half=upper,hinge=left,open=false,powered=false]", + "195:9": "jungle_door[facing=east,half=upper,hinge=right,open=false,powered=false]", + "195:10": "jungle_door[facing=east,half=upper,hinge=left,open=false,powered=true]", + "195:11": "jungle_door[facing=east,half=upper,hinge=right,open=false,powered=true]", + "196:0": "acacia_door[facing=east,half=lower,hinge=right,open=false,powered=false]", + "196:1": "acacia_door[facing=south,half=lower,hinge=right,open=false,powered=false]", + "196:2": "acacia_door[facing=west,half=lower,hinge=right,open=false,powered=false]", + "196:3": "acacia_door[facing=north,half=lower,hinge=right,open=false,powered=false]", + "196:4": "acacia_door[facing=east,half=lower,hinge=right,open=true,powered=false]", + "196:5": "acacia_door[facing=south,half=lower,hinge=right,open=true,powered=false]", + "196:6": "acacia_door[facing=west,half=lower,hinge=right,open=true,powered=false]", + "196:7": "acacia_door[facing=north,half=lower,hinge=right,open=true,powered=false]", + "196:8": "acacia_door[facing=east,half=upper,hinge=left,open=false,powered=false]", + "196:9": "acacia_door[facing=east,half=upper,hinge=right,open=false,powered=false]", + "196:10": "acacia_door[facing=east,half=upper,hinge=left,open=false,powered=true]", + "196:11": "acacia_door[facing=east,half=upper,hinge=right,open=false,powered=true]", + "197:0": "dark_oak_door[facing=east,half=lower,hinge=right,open=false,powered=false]", + "197:1": "dark_oak_door[facing=south,half=lower,hinge=right,open=false,powered=false]", + "197:2": "dark_oak_door[facing=west,half=lower,hinge=right,open=false,powered=false]", + "197:3": "dark_oak_door[facing=north,half=lower,hinge=right,open=false,powered=false]", + "197:4": "dark_oak_door[facing=east,half=lower,hinge=right,open=true,powered=false]", + "197:5": "dark_oak_door[facing=south,half=lower,hinge=right,open=true,powered=false]", + "197:6": "dark_oak_door[facing=west,half=lower,hinge=right,open=true,powered=false]", + "197:7": "dark_oak_door[facing=north,half=lower,hinge=right,open=true,powered=false]", + "197:8": "dark_oak_door[facing=east,half=upper,hinge=left,open=false,powered=false]", + "197:9": "dark_oak_door[facing=east,half=upper,hinge=right,open=false,powered=false]", + "197:10": "dark_oak_door[facing=east,half=upper,hinge=left,open=false,powered=true]", + "197:11": "dark_oak_door[facing=east,half=upper,hinge=right,open=false,powered=true]", + "198:0": "end_rod[facing=down]", + "198:1": "end_rod[facing=up]", + "198:2": "end_rod[facing=north]", + "198:3": "end_rod[facing=south]", + "198:4": "end_rod[facing=west]", + "198:5": "end_rod[facing=east]", + "199:0": "chorus_plant[down=false,east=false,north=false,south=false,up=false,west=false]", + "200:0": "chorus_flower[age=0]", + "200:1": "chorus_flower[age=1]", + "200:2": "chorus_flower[age=2]", + "200:3": "chorus_flower[age=3]", + "200:4": "chorus_flower[age=4]", + "200:5": "chorus_flower[age=5]", + "201:0": "purpur_block", + "202:0": "purpur_pillar[axis=y]", + "202:4": "purpur_pillar[axis=x]", + "202:8": "purpur_pillar[axis=z]", + "203:0": "purpur_stairs[facing=east,half=bottom,shape=straight]", + "203:1": "purpur_stairs[facing=west,half=bottom,shape=straight]", + "203:2": "purpur_stairs[facing=south,half=bottom,shape=straight]", + "203:3": "purpur_stairs[facing=north,half=bottom,shape=straight]", + "203:4": "purpur_stairs[facing=east,half=top,shape=straight]", + "203:5": "purpur_stairs[facing=west,half=top,shape=straight]", + "203:6": "purpur_stairs[facing=south,half=top,shape=straight]", + "203:7": "purpur_stairs[facing=north,half=top,shape=straight]", + "204:0": "purpur_slab[type=double]", + "205:0": "purpur_slab[type=bottom]", + "205:8": "purpur_slab[type=top]", + "206:0": "end_stone_bricks", + "207:0": "beetroots[age=0]", + "207:1": "beetroots[age=1]", + "207:2": "beetroots[age=2]", + "207:3": "beetroots[age=3]", + "208:0": "grass_path", + "209:0": "end_gateway", + "210:0": "repeating_command_block[conditional=false,facing=down]", + "210:1": "repeating_command_block[conditional=false,facing=up]", + "210:2": "repeating_command_block[conditional=false,facing=north]", + "210:3": "repeating_command_block[conditional=false,facing=south]", + "210:4": "repeating_command_block[conditional=false,facing=west]", + "210:5": "repeating_command_block[conditional=false,facing=east]", + "210:8": "repeating_command_block[conditional=true,facing=down]", + "210:9": "repeating_command_block[conditional=true,facing=up]", + "210:10": "repeating_command_block[conditional=true,facing=north]", + "210:11": "repeating_command_block[conditional=true,facing=south]", + "210:12": "repeating_command_block[conditional=true,facing=west]", + "210:13": "repeating_command_block[conditional=true,facing=east]", + "211:0": "chain_command_block[conditional=false,facing=down]", + "211:1": "chain_command_block[conditional=false,facing=up]", + "211:2": "chain_command_block[conditional=false,facing=north]", + "211:3": "chain_command_block[conditional=false,facing=south]", + "211:4": "chain_command_block[conditional=false,facing=west]", + "211:5": "chain_command_block[conditional=false,facing=east]", + "211:8": "chain_command_block[conditional=true,facing=down]", + "211:9": "chain_command_block[conditional=true,facing=up]", + "211:10": "chain_command_block[conditional=true,facing=north]", + "211:11": "chain_command_block[conditional=true,facing=south]", + "211:12": "chain_command_block[conditional=true,facing=west]", + "211:13": "chain_command_block[conditional=true,facing=east]", + "212:0": "frosted_ice[age=0]", + "212:1": "frosted_ice[age=1]", + "212:2": "frosted_ice[age=2]", + "212:3": "frosted_ice[age=3]", + "213:0": "magma_block", + "214:0": "nether_wart_block", + "215:0": "red_nether_bricks", + "216:0": "bone_block[axis=y]", + "216:4": "bone_block[axis=x]", + "216:8": "bone_block[axis=z]", + "217:0": "structure_void", + "218:0": "observer[facing=down,powered=false]", + "218:1": "observer[facing=up,powered=false]", + "218:2": "observer[facing=north,powered=false]", + "218:3": "observer[facing=south,powered=false]", + "218:4": "observer[facing=west,powered=false]", + "218:5": "observer[facing=east,powered=false]", + "218:8": "observer[facing=down,powered=true]", + "218:9": "observer[facing=up,powered=true]", + "218:10": "observer[facing=north,powered=true]", + "218:11": "observer[facing=south,powered=true]", + "218:12": "observer[facing=west,powered=true]", + "218:13": "observer[facing=east,powered=true]", + "219:0": "white_shulker_box[facing=down]", + "219:1": "white_shulker_box[facing=up]", + "219:2": "white_shulker_box[facing=north]", + "219:3": "white_shulker_box[facing=south]", + "219:4": "white_shulker_box[facing=west]", + "219:5": "white_shulker_box[facing=east]", + "220:0": "orange_shulker_box[facing=down]", + "220:1": "orange_shulker_box[facing=up]", + "220:2": "orange_shulker_box[facing=north]", + "220:3": "orange_shulker_box[facing=south]", + "220:4": "orange_shulker_box[facing=west]", + "220:5": "orange_shulker_box[facing=east]", + "221:0": "magenta_shulker_box[facing=down]", + "221:1": "magenta_shulker_box[facing=up]", + "221:2": "magenta_shulker_box[facing=north]", + "221:3": "magenta_shulker_box[facing=south]", + "221:4": "magenta_shulker_box[facing=west]", + "221:5": "magenta_shulker_box[facing=east]", + "222:0": "light_blue_shulker_box[facing=down]", + "222:1": "light_blue_shulker_box[facing=up]", + "222:2": "light_blue_shulker_box[facing=north]", + "222:3": "light_blue_shulker_box[facing=south]", + "222:4": "light_blue_shulker_box[facing=west]", + "222:5": "light_blue_shulker_box[facing=east]", + "223:0": "yellow_shulker_box[facing=down]", + "223:1": "yellow_shulker_box[facing=up]", + "223:2": "yellow_shulker_box[facing=north]", + "223:3": "yellow_shulker_box[facing=south]", + "223:4": "yellow_shulker_box[facing=west]", + "223:5": "yellow_shulker_box[facing=east]", + "224:0": "lime_shulker_box[facing=down]", + "224:1": "lime_shulker_box[facing=up]", + "224:2": "lime_shulker_box[facing=north]", + "224:3": "lime_shulker_box[facing=south]", + "224:4": "lime_shulker_box[facing=west]", + "224:5": "lime_shulker_box[facing=east]", + "225:0": "pink_shulker_box[facing=down]", + "225:1": "pink_shulker_box[facing=up]", + "225:2": "pink_shulker_box[facing=north]", + "225:3": "pink_shulker_box[facing=south]", + "225:4": "pink_shulker_box[facing=west]", + "225:5": "pink_shulker_box[facing=east]", + "226:0": "gray_shulker_box[facing=down]", + "226:1": "gray_shulker_box[facing=up]", + "226:2": "gray_shulker_box[facing=north]", + "226:3": "gray_shulker_box[facing=south]", + "226:4": "gray_shulker_box[facing=west]", + "226:5": "gray_shulker_box[facing=east]", + "227:0": "light_gray_shulker_box[facing=down]", + "227:1": "light_gray_shulker_box[facing=up]", + "227:2": "light_gray_shulker_box[facing=north]", + "227:3": "light_gray_shulker_box[facing=south]", + "227:4": "light_gray_shulker_box[facing=west]", + "227:5": "light_gray_shulker_box[facing=east]", + "228:0": "cyan_shulker_box[facing=down]", + "228:1": "cyan_shulker_box[facing=up]", + "228:2": "cyan_shulker_box[facing=north]", + "228:3": "cyan_shulker_box[facing=south]", + "228:4": "cyan_shulker_box[facing=west]", + "228:5": "cyan_shulker_box[facing=east]", + "229:0": "purple_shulker_box[facing=down]", + "229:1": "purple_shulker_box[facing=up]", + "229:2": "purple_shulker_box[facing=north]", + "229:3": "purple_shulker_box[facing=south]", + "229:4": "purple_shulker_box[facing=west]", + "229:5": "purple_shulker_box[facing=east]", + "230:0": "blue_shulker_box[facing=down]", + "230:1": "blue_shulker_box[facing=up]", + "230:2": "blue_shulker_box[facing=north]", + "230:3": "blue_shulker_box[facing=south]", + "230:4": "blue_shulker_box[facing=west]", + "230:5": "blue_shulker_box[facing=east]", + "231:0": "brown_shulker_box[facing=down]", + "231:1": "brown_shulker_box[facing=up]", + "231:2": "brown_shulker_box[facing=north]", + "231:3": "brown_shulker_box[facing=south]", + "231:4": "brown_shulker_box[facing=west]", + "231:5": "brown_shulker_box[facing=east]", + "232:0": "green_shulker_box[facing=down]", + "232:1": "green_shulker_box[facing=up]", + "232:2": "green_shulker_box[facing=north]", + "232:3": "green_shulker_box[facing=south]", + "232:4": "green_shulker_box[facing=west]", + "232:5": "green_shulker_box[facing=east]", + "233:0": "red_shulker_box[facing=down]", + "233:1": "red_shulker_box[facing=up]", + "233:2": "red_shulker_box[facing=north]", + "233:3": "red_shulker_box[facing=south]", + "233:4": "red_shulker_box[facing=west]", + "233:5": "red_shulker_box[facing=east]", + "234:0": "black_shulker_box[facing=down]", + "234:1": "black_shulker_box[facing=up]", + "234:2": "black_shulker_box[facing=north]", + "234:3": "black_shulker_box[facing=south]", + "234:4": "black_shulker_box[facing=west]", + "234:5": "black_shulker_box[facing=east]", + "235:0": "white_glazed_terracotta[facing=south]", + "235:1": "white_glazed_terracotta[facing=west]", + "235:2": "white_glazed_terracotta[facing=north]", + "235:3": "white_glazed_terracotta[facing=east]", + "236:0": "orange_glazed_terracotta[facing=south]", + "236:1": "orange_glazed_terracotta[facing=west]", + "236:2": "orange_glazed_terracotta[facing=north]", + "236:3": "orange_glazed_terracotta[facing=east]", + "237:0": "magenta_glazed_terracotta[facing=south]", + "237:1": "magenta_glazed_terracotta[facing=west]", + "237:2": "magenta_glazed_terracotta[facing=north]", + "237:3": "magenta_glazed_terracotta[facing=east]", + "238:0": "light_blue_glazed_terracotta[facing=south]", + "238:1": "light_blue_glazed_terracotta[facing=west]", + "238:2": "light_blue_glazed_terracotta[facing=north]", + "238:3": "light_blue_glazed_terracotta[facing=east]", + "239:0": "yellow_glazed_terracotta[facing=south]", + "239:1": "yellow_glazed_terracotta[facing=west]", + "239:2": "yellow_glazed_terracotta[facing=north]", + "239:3": "yellow_glazed_terracotta[facing=east]", + "240:0": "lime_glazed_terracotta[facing=south]", + "240:1": "lime_glazed_terracotta[facing=west]", + "240:2": "lime_glazed_terracotta[facing=north]", + "240:3": "lime_glazed_terracotta[facing=east]", + "241:0": "pink_glazed_terracotta[facing=south]", + "241:1": "pink_glazed_terracotta[facing=west]", + "241:2": "pink_glazed_terracotta[facing=north]", + "241:3": "pink_glazed_terracotta[facing=east]", + "242:0": "gray_glazed_terracotta[facing=south]", + "242:1": "gray_glazed_terracotta[facing=west]", + "242:2": "gray_glazed_terracotta[facing=north]", + "242:3": "gray_glazed_terracotta[facing=east]", + "243:0": "light_gray_glazed_terracotta[facing=south]", + "243:1": "light_gray_glazed_terracotta[facing=west]", + "243:2": "light_gray_glazed_terracotta[facing=north]", + "243:3": "light_gray_glazed_terracotta[facing=east]", + "244:0": "cyan_glazed_terracotta[facing=south]", + "244:1": "cyan_glazed_terracotta[facing=west]", + "244:2": "cyan_glazed_terracotta[facing=north]", + "244:3": "cyan_glazed_terracotta[facing=east]", + "245:0": "purple_glazed_terracotta[facing=south]", + "245:1": "purple_glazed_terracotta[facing=west]", + "245:2": "purple_glazed_terracotta[facing=north]", + "245:3": "purple_glazed_terracotta[facing=east]", + "246:0": "blue_glazed_terracotta[facing=south]", + "246:1": "blue_glazed_terracotta[facing=west]", + "246:2": "blue_glazed_terracotta[facing=north]", + "246:3": "blue_glazed_terracotta[facing=east]", + "247:0": "brown_glazed_terracotta[facing=south]", + "247:1": "brown_glazed_terracotta[facing=west]", + "247:2": "brown_glazed_terracotta[facing=north]", + "247:3": "brown_glazed_terracotta[facing=east]", + "248:0": "green_glazed_terracotta[facing=south]", + "248:1": "green_glazed_terracotta[facing=west]", + "248:2": "green_glazed_terracotta[facing=north]", + "248:3": "green_glazed_terracotta[facing=east]", + "249:0": "red_glazed_terracotta[facing=south]", + "249:1": "red_glazed_terracotta[facing=west]", + "249:2": "red_glazed_terracotta[facing=north]", + "249:3": "red_glazed_terracotta[facing=east]", + "250:0": "black_glazed_terracotta[facing=south]", + "250:1": "black_glazed_terracotta[facing=west]", + "250:2": "black_glazed_terracotta[facing=north]", + "250:3": "black_glazed_terracotta[facing=east]", + "251:0": "white_concrete", + "251:1": "orange_concrete", + "251:2": "magenta_concrete", + "251:3": "light_blue_concrete", + "251:4": "yellow_concrete", + "251:5": "lime_concrete", + "251:6": "pink_concrete", + "251:7": "gray_concrete", + "251:8": "light_gray_concrete", + "251:9": "cyan_concrete", + "251:10": "purple_concrete", + "251:11": "blue_concrete", + "251:12": "brown_concrete", + "251:13": "green_concrete", + "251:14": "red_concrete", + "251:15": "black_concrete", + "252:0": "white_concrete_powder", + "252:1": "orange_concrete_powder", + "252:2": "magenta_concrete_powder", + "252:3": "light_blue_concrete_powder", + "252:4": "yellow_concrete_powder", + "252:5": "lime_concrete_powder", + "252:6": "pink_concrete_powder", + "252:7": "gray_concrete_powder", + "252:8": "light_gray_concrete_powder", + "252:9": "cyan_concrete_powder", + "252:10": "purple_concrete_powder", + "252:11": "blue_concrete_powder", + "252:12": "brown_concrete_powder", + "252:13": "green_concrete_powder", + "252:14": "red_concrete_powder", + "252:15": "black_concrete_powder", + "255:0": "structure_block[mode=save]", + "255:1": "structure_block[mode=load]", + "255:2": "structure_block[mode=corner]", + "255:3": "structure_block[mode=data]" + }, + "clientCalculatedBlocks": { + "block_snowy": [ + "grass_block", + "dirt", + "coarse_dirt", + "podzol", + "mycelium" + ], + "directional": [ + "fire", + "redstone_wire", + "oak_fence", + "iron_bars", + "glass_pane", + "vine", + "nether_brick_fence", + "tripwire", + "cobblestone_wall", + "mossy_cobblestone_wall", + "white_stained_glass_pane", + "orange_stained_glass_pane", + "magenta_stained_glass_pane", + "light_blue_stained_glass_pane", + "yellow_stained_glass_pane", + "lime_stained_glass_pane", + "pink_stained_glass_pane", + "gray_stained_glass_pane", + "light_gray_stained_glass_pane", + "cyan_stained_glass_pane", + "purple_stained_glass_pane", + "blue_stained_glass_pane", + "brown_stained_glass_pane", + "green_stained_glass_pane", + "red_stained_glass_pane", + "black_stained_glass_pane", + "spruce_fence", + "birch_fence", + "jungle_fence", + "dark_oak_fence", + "acacia_fence", + "chorus_plant" + ], + "door": [ + "oak_door", + "iron_door", + "spruce_door", + "birch_door", + "jungle_door", + "acacia_door", + "dark_oak_door" + ], + "repeater_locked": [ + "repeater" + ], + "gate_in_wall": [ + "oak_fence_gate", + "spruce_fence_gate", + "birch_fence_gate", + "jungle_fence_gate", + "dark_oak_fence_gate", + "acacia_fence_gate" + ] + } +} diff --git a/src/react/AddServerOrConnect.tsx b/src/react/AddServerOrConnect.tsx index 500c1e61..729f1ec2 100644 --- a/src/react/AddServerOrConnect.tsx +++ b/src/react/AddServerOrConnect.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import React, { useEffect } from 'react' import Screen from './Screen' import Input from './Input' import Button from './Button' @@ -10,7 +10,8 @@ export interface BaseServerInfo { versionOverride?: string proxyOverride?: string usernameOverride?: string - passwordOverride?: string + /** Username or always use new if true */ + authenticatedAccountOverride?: string | true } interface Props { @@ -21,9 +22,13 @@ interface Props { parseQs?: boolean onQsConnect?: (server: BaseServerInfo) => void defaults?: Pick + accounts?: string[] + authenticatedAccounts?: number } -export default ({ onBack, onConfirm, title = 'Add a Server', initialData, parseQs, onQsConnect, defaults }: Props) => { +const ELEMENTS_WIDTH = 190 + +export default ({ onBack, onConfirm, title = 'Add a Server', initialData, parseQs, onQsConnect, defaults, accounts, authenticatedAccounts }: Props) => { const qsParams = parseQs ? new URLSearchParams(window.location.search) : undefined const [serverName, setServerName] = React.useState(initialData?.name ?? qsParams?.get('name') ?? '') @@ -36,29 +41,37 @@ export default ({ onBack, onConfirm, title = 'Add a Server', initialData, parseQ const [versionOverride, setVersionOverride] = React.useState(initialData?.versionOverride ?? /* legacy */ initialData?.['version'] ?? qsParams?.get('version') ?? '') const [proxyOverride, setProxyOverride] = React.useState(initialData?.proxyOverride ?? qsParams?.get('proxy') ?? '') const [usernameOverride, setUsernameOverride] = React.useState(initialData?.usernameOverride ?? qsParams?.get('username') ?? '') - const [passwordOverride, setPasswordOverride] = React.useState(initialData?.passwordOverride ?? qsParams?.get('password') ?? '') const smallWidth = useIsSmallWidth() const lockConnect = qsParams?.get('lockConnect') === 'true' + const initialAccount = initialData?.authenticatedAccountOverride + const [accountIndex, setAccountIndex] = React.useState(initialAccount === true ? -2 : initialAccount ? (accounts?.includes(initialAccount) ? accounts.indexOf(initialAccount) : -2) : -1) + + const freshAccount = accountIndex === -2 + const noAccountSelected = accountIndex === -1 + const authenticatedAccountOverride = noAccountSelected ? undefined : freshAccount ? true : accounts?.[accountIndex] + + let ipFinal = serverIp.includes(':') ? serverIp : `${serverIp}:${serverPort}` + ipFinal = ipFinal.replace(/:$/, '') + const commonUseOptions: BaseServerInfo = { + name: serverName, + ip: ipFinal, + versionOverride: versionOverride || undefined, + proxyOverride: proxyOverride || undefined, + usernameOverride: usernameOverride || undefined, + authenticatedAccountOverride, + } return -
{ - e.preventDefault() - let ip = serverIp.includes(':') ? serverIp : `${serverIp}:${serverPort}` - ip = ip.replace(/:$/, '') - onConfirm({ - name: serverName, - ip, - versionOverride, - proxyOverride, - usernameOverride, - passwordOverride - }) - }} + { + e.preventDefault() + onConfirm(commonUseOptions) + }} >
Overrides:
setVersionOverride(value)} placeholder='Optional, but recommended to specify' /> setProxyOverride(value)} placeholder={defaults?.proxyOverride} /> - setUsernameOverride(value)} placeholder={defaults?.usernameOverride} /> - setPasswordOverride(value)} /* placeholder='For advanced usage only' */ /> - {!lockConnect && <>} + setUsernameOverride(value)} placeholder={defaults?.usernameOverride} disabled={!noAccountSelected} /> + + + {!lockConnect && <> + { + onBack() + }}>Cancel + Save + } {qsParams?.get('ip') &&
- + >Connect
}
} +const ButtonWrapper = ({ ...props }: React.ComponentProps) => { + props.style ??= {} + props.style.width = ELEMENTS_WIDTH + return + +} diff --git a/src/react/Button.tsx b/src/react/Button.tsx index 20dc0801..10ff2fb0 100644 --- a/src/react/Button.tsx +++ b/src/react/Button.tsx @@ -2,6 +2,7 @@ import classNames from 'classnames' import { createContext, FC, Ref, useContext } from 'react' import buttonCss from './button.module.css' import SharedHudVars from './SharedHudVars' +import PixelartIcon from './PixelartIcon' // testing in storybook from deathscreen @@ -17,7 +18,7 @@ const ButtonContext = createContext({ onClick () { }, }) -export const ButtonProvider: FC<{children, onClick}> = ({ children, onClick }) => { +export const ButtonProvider: FC<{ children, onClick }> = ({ children, onClick }) => { return {children} } @@ -39,7 +40,7 @@ export default (({ label, icon, children, inScreen, rootRef, type = 'button', .. return diff --git a/src/react/Chat.css b/src/react/Chat.css index 41917783..e4df8af8 100644 --- a/src/react/Chat.css +++ b/src/react/Chat.css @@ -24,6 +24,13 @@ div.chat-wrapper { left: 1px; box-sizing: border-box; background-color: rgba(0, 0, 0, 0); + display: flex; + align-items: center; + gap: 1px; +} + +.chat-input-wrapper form { + display: flex; } .chat-input { @@ -103,7 +110,8 @@ div.chat-wrapper { } .input-mobile #chatinput { - height: 20px; + height: 24px; + font-size: 13px; } .display-mobile { @@ -125,6 +133,7 @@ div.chat-wrapper { pointer-events: none; overflow: hidden; width: 100%; + height: 100%; scrollbar-width: var(--thin-if-firefox); } diff --git a/src/react/Chat.tsx b/src/react/Chat.tsx index b05f9930..60467171 100644 --- a/src/react/Chat.tsx +++ b/src/react/Chat.tsx @@ -1,10 +1,11 @@ import { proxy, subscribe } from 'valtio' import { useEffect, useMemo, useRef, useState } from 'react' -import { isCypress } from '../standaloneUtils' import { MessageFormatPart } from '../botUtils' import { MessagePart } from './MessageFormatted' import './Chat.css' import { isIos, reactKeyForMessage } from './utils' +import Button from './Button' +import { pixelartIcons } from './PixelartIcon' export type Message = { parts: MessageFormatPart[], @@ -36,6 +37,7 @@ type Props = { fetchCompletionItems?: (triggerKind: 'implicit' | 'explicit', completeValue: string, fullValue: string, abortController?: AbortController) => Promise // width?: number allowSelection?: boolean + inputDisabled?: string } export const chatInputValueGlobal = proxy({ @@ -53,7 +55,17 @@ export const fadeMessage = (message: Message, initialTimeout: boolean, requestUp }, initialTimeout ? 5000 : 0) } -export default ({ messages, opacity = 1, fetchCompletionItems, opened, sendMessage, onClose, usingTouch, allowSelection }: Props) => { +export default ({ + messages, + opacity = 1, + fetchCompletionItems, + opened, + sendMessage, + onClose, + usingTouch, + allowSelection, + inputDisabled +}: Props) => { const sendHistoryRef = useRef(JSON.parse(window.sessionStorage.chatHistory || '[]')) const [completePadText, setCompletePadText] = useState('') @@ -200,7 +212,7 @@ export default ({ messages, opacity = 1, fetchCompletionItems, opened, sendMessa return ( <> - diff --git a/src/react/ServersListProvider.tsx b/src/react/ServersListProvider.tsx index bd39bc10..a0fbec0b 100644 --- a/src/react/ServersListProvider.tsx +++ b/src/react/ServersListProvider.tsx @@ -7,6 +7,7 @@ import ServersList from './ServersList' import AddServerOrConnect, { BaseServerInfo } from './AddServerOrConnect' import { useDidUpdateEffect } from './utils' import { useIsModalActive } from './utilsApp' +import { showOptionsModal } from './SelectOption' interface StoreServerItem extends BaseServerInfo { lastJoined?: number @@ -46,6 +47,15 @@ type AdditionalDisplayData = { icon?: string } +export interface AuthenticatedAccount { + // type: 'microsoft' + username: string + cachedTokens?: { + data: any + expiresOn: number + } +} + const getInitialServersList = () => { if (localStorage['serversList']) return JSON.parse(localStorage['serversList']) as StoreServerItem[] @@ -62,7 +72,6 @@ const getInitialServersList = () => { if (localStorage['server']) { const legacyLastJoinedServer: StoreServerItem = { ip: localStorage['server'], - passwordOverride: localStorage['password'], versionOverride: localStorage['version'], lastJoined: Date.now() } @@ -104,16 +113,22 @@ const getInitialProxies = () => { return proxies } -export const updateLoadedServerData = (callback: (data: StoreServerItem) => StoreServerItem) => { +export const updateLoadedServerData = (callback: (data: StoreServerItem) => StoreServerItem, index = miscUiState.loadedServerIndex) => { + if (!index) index = miscUiState.loadedServerIndex + if (!index) return // function assumes component is not mounted to avoid sync issues after save - const { loadedServerIndex } = miscUiState - if (!loadedServerIndex) return const servers = getInitialServersList() - const server = servers[loadedServerIndex] - servers[loadedServerIndex] = callback(server) + const server = servers[index] + servers[index] = callback(server) setNewServersList(servers) } +export const updateAuthenticatedAccountData = (callback: (data: AuthenticatedAccount[]) => AuthenticatedAccount[]) => { + const accounts = JSON.parse(localStorage['authenticatedAccounts'] || '[]') as AuthenticatedAccount[] + const newAccounts = callback(accounts) + localStorage['authenticatedAccounts'] = JSON.stringify(newAccounts) +} + // todo move to base const normalizeIp = (ip: string) => ip.replace(/https?:\/\//, '').replace(/\/(:|$)/, '') @@ -122,6 +137,11 @@ const Inner = () => { const [selectedProxy, setSelectedProxy] = useState(localStorage.getItem('selectedProxy') ?? proxies?.[0] ?? '') const [serverEditScreen, setServerEditScreen] = useState(null) // true for add const [defaultUsername, setDefaultUsername] = useState(localStorage['username'] ?? (`mcrafter${Math.floor(Math.random() * 1000)}`)) + const [authenticatedAccounts, setAuthenticatedAccounts] = useState(JSON.parse(localStorage['authenticatedAccounts'] || '[]')) + + useEffect(() => { + localStorage.setItem('authenticatedAccounts', JSON.stringify(authenticatedAccounts)) + }, [authenticatedAccounts]) useEffect(() => { localStorage.setItem('username', defaultUsername) @@ -186,6 +206,12 @@ const Inner = () => { } }, [serverEditScreen]) + useDidUpdateEffect(() => { + if (!isEditScreenModal) { + setServerEditScreen(null) + } + }, [isEditScreenModal]) + if (isEditScreenModal) { return { } setServerEditScreen(null) }} + accounts={authenticatedAccounts.map(a => a.username)} initialData={!serverEditScreen || serverEditScreen === true ? undefined : serverEditScreen} onQsConnect={(info) => { const connectOptions: ConnectOptions = { @@ -216,7 +243,6 @@ const Inner = () => { server: normalizeIp(info.ip), proxy: info.proxyOverride || selectedProxy, botVersion: info.versionOverride, - password: info.passwordOverride, ignoreQs: true, } dispatchEvent(new CustomEvent('connect', { detail: connectOptions })) @@ -225,10 +251,11 @@ const Inner = () => { } return { + joinServer={(overrides, { shouldSave }) => { + const indexOrIp = overrides.ip let ip = indexOrIp let server: StoreServerItem | undefined - if (overrides.shouldSave === undefined) { + if (shouldSave === undefined) { // hack: inner component doesn't know of overrides for existing servers server = serversListSorted.find(s => s.index.toString() === indexOrIp)! ip = server.ip @@ -236,22 +263,30 @@ const Inner = () => { } const lastJoinedUsername = serversListSorted.find(s => s.usernameOverride)?.usernameOverride - let username = overrides.username || defaultUsername + let username = overrides.usernameOverride || defaultUsername if (!username) { username = prompt('Username', lastJoinedUsername || '') if (!username) return setDefaultUsername(username) } + let authenticatedAccount: AuthenticatedAccount | true | undefined + if (overrides.authenticatedAccountOverride) { + if (overrides.authenticatedAccountOverride === true) { + authenticatedAccount = true + } else { + authenticatedAccount = authenticatedAccounts.find(a => a.username === overrides.authenticatedAccountOverride) ?? true + } + } const options = { username, server: normalizeIp(ip), - proxy: overrides.proxy || selectedProxy, + proxy: overrides.proxyOverride || selectedProxy, botVersion: overrides.versionOverride ?? /* legacy */ overrides['version'], - password: overrides.password, ignoreQs: true, autoLoginPassword: server?.autoLogin?.[username], + authenticatedAccount, onSuccessfulPlay () { - if (overrides.shouldSave && !serversList.some(s => s.ip === ip)) { + if (shouldSave && !serversList.some(s => s.ip === ip)) { const newServersList: StoreServerItem[] = [...serversList, { ip, lastJoined: Date.now(), @@ -259,9 +294,10 @@ const Inner = () => { }] // setServersList(newServersList) setNewServersList(newServersList) // component is not mounted + miscUiState.loadedServerIndex = (newServersList.length - 1).toString() } - if (overrides.shouldSave === undefined) { // loading saved + if (shouldSave === undefined) { // loading saved // find and update const server = serversList.find(s => s.ip === ip) if (server) { @@ -280,13 +316,18 @@ const Inner = () => { localStorage.setItem('selectedProxy', selectedProxy) } }, - serverIndex: overrides.shouldSave ? serversList.length.toString() : indexOrIp // assume last + serverIndex: shouldSave ? serversList.length.toString() : indexOrIp // assume last } satisfies ConnectOptions dispatchEvent(new CustomEvent('connect', { detail: options })) // qsOptions }} username={defaultUsername} setUsername={setDefaultUsername} + onProfileClick={async () => { + const username = await showOptionsModal('Select authenticated account to remove', authenticatedAccounts.map(a => a.username)) + if (!username) return + setAuthenticatedAccounts(old => old.filter(a => a.username !== username)) + }} onWorldAction={(action, index) => { const server = serversList[index] if (!server) return diff --git a/src/react/SignEditorProvider.tsx b/src/react/SignEditorProvider.tsx index 666935b6..8131ae86 100644 --- a/src/react/SignEditorProvider.tsx +++ b/src/react/SignEditorProvider.tsx @@ -1,6 +1,5 @@ import { useMemo, useEffect, useState, useRef } from 'react' import { showModal, hideModal } from '../globalState' -import { setDoPreventDefault } from '../controls' import { options } from '../optionsStorage' import { useIsModalActive } from './utilsApp' import SignEditor, { ResultType } from './SignEditor' @@ -57,10 +56,6 @@ export default () => { target.setAttribute('maxlength', `${15 + Math.ceil(addLength)}`) } - useEffect(() => { - setDoPreventDefault(!isModalActive) // disable e.preventDefault() since we might be using wysiwyg editor which doesn't use textarea and need default browser behavior to ensure characters are being typed in contenteditable container. Ideally we should do e.preventDefault() only when either ctrl, cmd (meta) or alt key is pressed. - }, [isModalActive]) - useMemo(() => { bot._client.on('open_sign_entity', (packet) => { if (!options.autoSignEditor) return diff --git a/src/react/SignInMessage.stories.tsx b/src/react/SignInMessage.stories.tsx new file mode 100644 index 00000000..16528700 --- /dev/null +++ b/src/react/SignInMessage.stories.tsx @@ -0,0 +1,18 @@ +import type { Meta, StoryObj } from '@storybook/react' +import SignInMessage from './SignInMessage' + +const meta: Meta<{ open }> = { + component: SignInMessage as any, + render ({ open }) { + return + }, +} + +export default meta +type Story = StoryObj<{ open }> + +export const Primary: Story = { + args: { + }, +} diff --git a/src/react/SignInMessage.tsx b/src/react/SignInMessage.tsx new file mode 100644 index 00000000..c33cdf41 --- /dev/null +++ b/src/react/SignInMessage.tsx @@ -0,0 +1,109 @@ +import { useState } from 'react' +import { useUtilsEffect } from '@zardoy/react-util' +import PixelartIcon from './PixelartIcon' +import Screen from './Screen' +import Button from './Button' + +export default ({ + code = 'ABCD-EFGH-IJKL-MNOP', + loginLink = 'https://aka.ms/devicelogin', + connectingServer = 'mc.example.comsdlfjsklfjsfjdskfjsj', + warningText = true, + expiresEnd = Date.now() + 1000 * 60 * 5, + setSaveToken = (() => { }) as ((state: boolean) => void) | undefined, + defaultSaveToken = true, + onCancel = () => { }, + directLink = 'https://aka.ms/devicelogin' +}) => { + if (connectingServer.length > 30) connectingServer = connectingServer.slice(0, 30) + '...' + const [timeLeft, setTimeLeft] = useState(``) + + useUtilsEffect(({ interval }) => { + interval(1000, () => { + const timeLeft = Math.max(0, Math.ceil((expiresEnd - Date.now()) / 1000)) + const minutes = Math.floor(timeLeft / 60) + const seconds = timeLeft % 60 + setTimeLeft(`${minutes}:${seconds.toString().padStart(2, '0')}`) + if (timeLeft <= 0) setTimeLeft('Code expired!') + }) + }, []) + + return +
+
{code}
+
+ Waiting... {timeLeft} +
+
+ To join a Minecraft server {connectingServer} using your Microsoft account, you need to visit{' '} + Direct Link + {' '} or {' '} + {loginLink} + {' '} + and enter the code above. +
+ {warningText &&
+ Join only vanilla servers! This client is detectable and may result in a ban by anti-cheat plugins. +
} + {setSaveToken && } +
+ +
+} diff --git a/src/react/SignInMessageProvider.tsx b/src/react/SignInMessageProvider.tsx new file mode 100644 index 00000000..68ea83aa --- /dev/null +++ b/src/react/SignInMessageProvider.tsx @@ -0,0 +1,32 @@ +import { proxy, ref, useSnapshot } from 'valtio' +import SignInMessage from './SignInMessage' +import { lastConnectOptions } from './AppStatusProvider' + +export const signInMessageState = proxy({ + code: '', + link: '', + expiresOn: 0, + shouldSaveToken: true, + abortController: ref(new AbortController()), +}) + +export default () => { + const { code, expiresOn, link, shouldSaveToken } = useSnapshot(signInMessageState) + + if (!code) return null + + return { + signInMessageState.shouldSaveToken = state + }} + connectingServer={lastConnectOptions.value?.server ?? ''} + onCancel={() => { + signInMessageState.abortController.abort() + }} + directLink={`http://microsoft.com/link?otc=${code}`} + /> +} diff --git a/src/react/TitleProvider.tsx b/src/react/TitleProvider.tsx index 151a93c1..f19669ec 100644 --- a/src/react/TitleProvider.tsx +++ b/src/react/TitleProvider.tsx @@ -23,6 +23,7 @@ export default () => { const [openActionBar, setOpenActionBar] = useState(false) useMemo(() => { + // todo move to mineflayer bot._client.on('set_title_text', (packet) => { setTitle(JSON.parse(packet.text)) setOpenTitle(true) diff --git a/src/react/TouchAreasControls.tsx b/src/react/TouchAreasControls.tsx index b660fcb2..0476be48 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,22 +65,21 @@ 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 () { document.dispatchEvent(new MouseEvent('mousedown', { button: 2 })) worldInteractions.update() - document.dispatchEvent(new MouseEvent('mouseup', { button: 2 })) }, sneak () { void contro.emit('trigger', { command: 'general.toggleSneakOrDown', schema: null as any, }) - active = bot.getControlState('sneak') + active = bot?.getControlState('sneak') }, break () { document.dispatchEvent(new MouseEvent('mousedown', { button: 0 })) @@ -91,18 +91,19 @@ export default ({ touchActive, setupActive, buttonsPositions, closeButtonsSetup command: 'general.jump', schema: null as any, }) - active = bot.controlState.jump + active = bot?.controlState.jump } } const holdUp = { action () { + document.dispatchEvent(new MouseEvent('mouseup', { button: 2 })) }, sneak () { void contro.emit('release', { 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 } } @@ -161,8 +162,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/TouchAreasControlsProvider.tsx b/src/react/TouchAreasControlsProvider.tsx index 24479e89..70c8bdf2 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/react/globals.d.ts b/src/react/globals.d.ts index 842d27c4..53f2208e 100644 --- a/src/react/globals.d.ts +++ b/src/react/globals.d.ts @@ -25,21 +25,19 @@ declare module '*.png' { const png: string export default png } +declare module '*.svg' { + const svg: string + export default svg +} interface PromiseConstructor { withResolvers (): { - resolve: (value: T) => void; - reject: (reason: any) => void; - promise: Promise; + resolve: (value: T) => void + reject: (reason: any) => void + promise: Promise } } declare namespace JSX { - interface IntrinsicElements { - 'iconify-icon': { - icon: string - style?: React.CSSProperties - class?: string - } - } + interface IntrinsicElements { } } diff --git a/src/react/mainMenu.module.css b/src/react/mainMenu.module.css index 5b84431b..a6241806 100644 --- a/src/react/mainMenu.module.css +++ b/src/react/mainMenu.module.css @@ -104,7 +104,7 @@ padding-right: calc(env(safe-area-inset-right) / 2); } -@media only screen and (max-height: 313px) { +@media only screen and (max-height: 420px) { .root { --top-offset: 10px } diff --git a/src/react/pixelartIcons.generated.ts b/src/react/pixelartIcons.generated.ts new file mode 100644 index 00000000..b8f717c1 --- /dev/null +++ b/src/react/pixelartIcons.generated.ts @@ -0,0 +1,974 @@ +export type PixelartIconsGenerated = { + /** ![image](../../node_modules/pixelarticons/svg/4g.svg) */ + '4g': string; + /** ![image](../../node_modules/pixelarticons/svg/4k-box.svg) */ + '4k-box': string; + /** ![image](../../node_modules/pixelarticons/svg/4k.svg) */ + '4k': string; + /** ![image](../../node_modules/pixelarticons/svg/5g.svg) */ + '5g': string; + /** ![image](../../node_modules/pixelarticons/svg/ab-testing.svg) */ + 'ab-testing': string; + /** ![image](../../node_modules/pixelarticons/svg/ac.svg) */ + 'ac': string; + /** ![image](../../node_modules/pixelarticons/svg/add-box-multiple.svg) */ + 'add-box-multiple': string; + /** ![image](../../node_modules/pixelarticons/svg/add-box.svg) */ + 'add-box': string; + /** ![image](../../node_modules/pixelarticons/svg/add-col.svg) */ + 'add-col': string; + /** ![image](../../node_modules/pixelarticons/svg/add-grid.svg) */ + 'add-grid': string; + /** ![image](../../node_modules/pixelarticons/svg/add-row.svg) */ + 'add-row': string; + /** ![image](../../node_modules/pixelarticons/svg/alert.svg) */ + 'alert': string; + /** ![image](../../node_modules/pixelarticons/svg/align-center.svg) */ + 'align-center': string; + /** ![image](../../node_modules/pixelarticons/svg/align-justify.svg) */ + 'align-justify': string; + /** ![image](../../node_modules/pixelarticons/svg/align-left.svg) */ + 'align-left': string; + /** ![image](../../node_modules/pixelarticons/svg/align-right.svg) */ + 'align-right': string; + /** ![image](../../node_modules/pixelarticons/svg/analytics.svg) */ + 'analytics': string; + /** ![image](../../node_modules/pixelarticons/svg/anchor.svg) */ + 'anchor': string; + /** ![image](../../node_modules/pixelarticons/svg/android.svg) */ + 'android': string; + /** ![image](../../node_modules/pixelarticons/svg/animation.svg) */ + 'animation': string; + /** ![image](../../node_modules/pixelarticons/svg/archive.svg) */ + 'archive': string; + /** ![image](../../node_modules/pixelarticons/svg/arrow-bar-down.svg) */ + 'arrow-bar-down': string; + /** ![image](../../node_modules/pixelarticons/svg/arrow-bar-left.svg) */ + 'arrow-bar-left': string; + /** ![image](../../node_modules/pixelarticons/svg/arrow-bar-right.svg) */ + 'arrow-bar-right': string; + /** ![image](../../node_modules/pixelarticons/svg/arrow-bar-up.svg) */ + 'arrow-bar-up': string; + /** ![image](../../node_modules/pixelarticons/svg/arrow-down-box.svg) */ + 'arrow-down-box': string; + /** ![image](../../node_modules/pixelarticons/svg/arrow-down.svg) */ + 'arrow-down': string; + /** ![image](../../node_modules/pixelarticons/svg/arrow-left-box.svg) */ + 'arrow-left-box': string; + /** ![image](../../node_modules/pixelarticons/svg/arrow-left.svg) */ + 'arrow-left': string; + /** ![image](../../node_modules/pixelarticons/svg/arrow-right-box.svg) */ + 'arrow-right-box': string; + /** ![image](../../node_modules/pixelarticons/svg/arrow-right.svg) */ + 'arrow-right': string; + /** ![image](../../node_modules/pixelarticons/svg/arrow-up-box.svg) */ + 'arrow-up-box': string; + /** ![image](../../node_modules/pixelarticons/svg/arrow-up.svg) */ + 'arrow-up': string; + /** ![image](../../node_modules/pixelarticons/svg/arrows-horizontal.svg) */ + 'arrows-horizontal': string; + /** ![image](../../node_modules/pixelarticons/svg/arrows-vertical.svg) */ + 'arrows-vertical': string; + /** ![image](../../node_modules/pixelarticons/svg/art-text.svg) */ + 'art-text': string; + /** ![image](../../node_modules/pixelarticons/svg/article-multiple.svg) */ + 'article-multiple': string; + /** ![image](../../node_modules/pixelarticons/svg/article.svg) */ + 'article': string; + /** ![image](../../node_modules/pixelarticons/svg/aspect-ratio.svg) */ + 'aspect-ratio': string; + /** ![image](../../node_modules/pixelarticons/svg/at.svg) */ + 'at': string; + /** ![image](../../node_modules/pixelarticons/svg/attachment.svg) */ + 'attachment': string; + /** ![image](../../node_modules/pixelarticons/svg/audio-device.svg) */ + 'audio-device': string; + /** ![image](../../node_modules/pixelarticons/svg/avatar.svg) */ + 'avatar': string; + /** ![image](../../node_modules/pixelarticons/svg/backburger.svg) */ + 'backburger': string; + /** ![image](../../node_modules/pixelarticons/svg/battery-1.svg) */ + 'battery-1': string; + /** ![image](../../node_modules/pixelarticons/svg/battery-2.svg) */ + 'battery-2': string; + /** ![image](../../node_modules/pixelarticons/svg/battery-charging.svg) */ + 'battery-charging': string; + /** ![image](../../node_modules/pixelarticons/svg/battery-full.svg) */ + 'battery-full': string; + /** ![image](../../node_modules/pixelarticons/svg/battery.svg) */ + 'battery': string; + /** ![image](../../node_modules/pixelarticons/svg/bed.svg) */ + 'bed': string; + /** ![image](../../node_modules/pixelarticons/svg/bitcoin.svg) */ + 'bitcoin': string; + /** ![image](../../node_modules/pixelarticons/svg/bluetooth.svg) */ + 'bluetooth': string; + /** ![image](../../node_modules/pixelarticons/svg/book-open.svg) */ + 'book-open': string; + /** ![image](../../node_modules/pixelarticons/svg/book.svg) */ + 'book': string; + /** ![image](../../node_modules/pixelarticons/svg/bookmark.svg) */ + 'bookmark': string; + /** ![image](../../node_modules/pixelarticons/svg/bookmarks.svg) */ + 'bookmarks': string; + /** ![image](../../node_modules/pixelarticons/svg/briefcase-account.svg) */ + 'briefcase-account': string; + /** ![image](../../node_modules/pixelarticons/svg/briefcase-check.svg) */ + 'briefcase-check': string; + /** ![image](../../node_modules/pixelarticons/svg/briefcase-delete.svg) */ + 'briefcase-delete': string; + /** ![image](../../node_modules/pixelarticons/svg/briefcase-download.svg) */ + 'briefcase-download': string; + /** ![image](../../node_modules/pixelarticons/svg/briefcase-minus.svg) */ + 'briefcase-minus': string; + /** ![image](../../node_modules/pixelarticons/svg/briefcase-plus.svg) */ + 'briefcase-plus': string; + /** ![image](../../node_modules/pixelarticons/svg/briefcase-search-1.svg) */ + 'briefcase-search-1': string; + /** ![image](../../node_modules/pixelarticons/svg/briefcase-search.svg) */ + 'briefcase-search': string; + /** ![image](../../node_modules/pixelarticons/svg/briefcase-upload.svg) */ + 'briefcase-upload': string; + /** ![image](../../node_modules/pixelarticons/svg/briefcase.svg) */ + 'briefcase': string; + /** ![image](../../node_modules/pixelarticons/svg/bug.svg) */ + 'bug': string; + /** ![image](../../node_modules/pixelarticons/svg/building-community.svg) */ + 'building-community': string; + /** ![image](../../node_modules/pixelarticons/svg/building-skyscraper.svg) */ + 'building-skyscraper': string; + /** ![image](../../node_modules/pixelarticons/svg/building.svg) */ + 'building': string; + /** ![image](../../node_modules/pixelarticons/svg/buildings.svg) */ + 'buildings': string; + /** ![image](../../node_modules/pixelarticons/svg/bulletlist.svg) */ + 'bulletlist': string; + /** ![image](../../node_modules/pixelarticons/svg/bullseye-arrow.svg) */ + 'bullseye-arrow': string; + /** ![image](../../node_modules/pixelarticons/svg/bullseye.svg) */ + 'bullseye': string; + /** ![image](../../node_modules/pixelarticons/svg/bus.svg) */ + 'bus': string; + /** ![image](../../node_modules/pixelarticons/svg/cake.svg) */ + 'cake': string; + /** ![image](../../node_modules/pixelarticons/svg/calculator.svg) */ + 'calculator': string; + /** ![image](../../node_modules/pixelarticons/svg/calendar-alert.svg) */ + 'calendar-alert': string; + /** ![image](../../node_modules/pixelarticons/svg/calendar-arrow-left.svg) */ + 'calendar-arrow-left': string; + /** ![image](../../node_modules/pixelarticons/svg/calendar-arrow-right.svg) */ + 'calendar-arrow-right': string; + /** ![image](../../node_modules/pixelarticons/svg/calendar-check.svg) */ + 'calendar-check': string; + /** ![image](../../node_modules/pixelarticons/svg/calendar-export.svg) */ + 'calendar-export': string; + /** ![image](../../node_modules/pixelarticons/svg/calendar-grid.svg) */ + 'calendar-grid': string; + /** ![image](../../node_modules/pixelarticons/svg/calendar-import.svg) */ + 'calendar-import': string; + /** ![image](../../node_modules/pixelarticons/svg/calendar-minus.svg) */ + 'calendar-minus': string; + /** ![image](../../node_modules/pixelarticons/svg/calendar-month.svg) */ + 'calendar-month': string; + /** ![image](../../node_modules/pixelarticons/svg/calendar-multiple-check.svg) */ + 'calendar-multiple-check': string; + /** ![image](../../node_modules/pixelarticons/svg/calendar-multiple.svg) */ + 'calendar-multiple': string; + /** ![image](../../node_modules/pixelarticons/svg/calendar-plus.svg) */ + 'calendar-plus': string; + /** ![image](../../node_modules/pixelarticons/svg/calendar-range.svg) */ + 'calendar-range': string; + /** ![image](../../node_modules/pixelarticons/svg/calendar-remove.svg) */ + 'calendar-remove': string; + /** ![image](../../node_modules/pixelarticons/svg/calendar-search.svg) */ + 'calendar-search': string; + /** ![image](../../node_modules/pixelarticons/svg/calendar-sort-ascending.svg) */ + 'calendar-sort-ascending': string; + /** ![image](../../node_modules/pixelarticons/svg/calendar-sort-descending.svg) */ + 'calendar-sort-descending': string; + /** ![image](../../node_modules/pixelarticons/svg/calendar-text.svg) */ + 'calendar-text': string; + /** ![image](../../node_modules/pixelarticons/svg/calendar-today.svg) */ + 'calendar-today': string; + /** ![image](../../node_modules/pixelarticons/svg/calendar-tomorrow.svg) */ + 'calendar-tomorrow': string; + /** ![image](../../node_modules/pixelarticons/svg/calendar-week-begin.svg) */ + 'calendar-week-begin': string; + /** ![image](../../node_modules/pixelarticons/svg/calendar-week.svg) */ + 'calendar-week': string; + /** ![image](../../node_modules/pixelarticons/svg/calendar-weekend.svg) */ + 'calendar-weekend': string; + /** ![image](../../node_modules/pixelarticons/svg/calendar.svg) */ + 'calendar': string; + /** ![image](../../node_modules/pixelarticons/svg/camera-add.svg) */ + 'camera-add': string; + /** ![image](../../node_modules/pixelarticons/svg/camera-alt.svg) */ + 'camera-alt': string; + /** ![image](../../node_modules/pixelarticons/svg/camera-face.svg) */ + 'camera-face': string; + /** ![image](../../node_modules/pixelarticons/svg/camera.svg) */ + 'camera': string; + /** ![image](../../node_modules/pixelarticons/svg/car.svg) */ + 'car': string; + /** ![image](../../node_modules/pixelarticons/svg/card-id.svg) */ + 'card-id': string; + /** ![image](../../node_modules/pixelarticons/svg/card-plus.svg) */ + 'card-plus': string; + /** ![image](../../node_modules/pixelarticons/svg/card-stack.svg) */ + 'card-stack': string; + /** ![image](../../node_modules/pixelarticons/svg/card-text.svg) */ + 'card-text': string; + /** ![image](../../node_modules/pixelarticons/svg/card.svg) */ + 'card': string; + /** ![image](../../node_modules/pixelarticons/svg/cart.svg) */ + 'cart': string; + /** ![image](../../node_modules/pixelarticons/svg/cast.svg) */ + 'cast': string; + /** ![image](../../node_modules/pixelarticons/svg/cellular-signal-0.svg) */ + 'cellular-signal-0': string; + /** ![image](../../node_modules/pixelarticons/svg/cellular-signal-1.svg) */ + 'cellular-signal-1': string; + /** ![image](../../node_modules/pixelarticons/svg/cellular-signal-2.svg) */ + 'cellular-signal-2': string; + /** ![image](../../node_modules/pixelarticons/svg/cellular-signal-3.svg) */ + 'cellular-signal-3': string; + /** ![image](../../node_modules/pixelarticons/svg/cellular-signal-off.svg) */ + 'cellular-signal-off': string; + /** ![image](../../node_modules/pixelarticons/svg/chart-add.svg) */ + 'chart-add': string; + /** ![image](../../node_modules/pixelarticons/svg/chart-bar.svg) */ + 'chart-bar': string; + /** ![image](../../node_modules/pixelarticons/svg/chart-delete.svg) */ + 'chart-delete': string; + /** ![image](../../node_modules/pixelarticons/svg/chart-minus.svg) */ + 'chart-minus': string; + /** ![image](../../node_modules/pixelarticons/svg/chart-multiple.svg) */ + 'chart-multiple': string; + /** ![image](../../node_modules/pixelarticons/svg/chart.svg) */ + 'chart': string; + /** ![image](../../node_modules/pixelarticons/svg/chat.svg) */ + 'chat': string; + /** ![image](../../node_modules/pixelarticons/svg/check-double.svg) */ + 'check-double': string; + /** ![image](../../node_modules/pixelarticons/svg/check.svg) */ + 'check': string; + /** ![image](../../node_modules/pixelarticons/svg/checkbox-on.svg) */ + 'checkbox-on': string; + /** ![image](../../node_modules/pixelarticons/svg/checkbox.svg) */ + 'checkbox': string; + /** ![image](../../node_modules/pixelarticons/svg/checklist.svg) */ + 'checklist': string; + /** ![image](../../node_modules/pixelarticons/svg/chess.svg) */ + 'chess': string; + /** ![image](../../node_modules/pixelarticons/svg/chevron-down.svg) */ + 'chevron-down': string; + /** ![image](../../node_modules/pixelarticons/svg/chevron-left.svg) */ + 'chevron-left': string; + /** ![image](../../node_modules/pixelarticons/svg/chevron-right.svg) */ + 'chevron-right': string; + /** ![image](../../node_modules/pixelarticons/svg/chevron-up.svg) */ + 'chevron-up': string; + /** ![image](../../node_modules/pixelarticons/svg/chevrons-horizontal.svg) */ + 'chevrons-horizontal': string; + /** ![image](../../node_modules/pixelarticons/svg/chevrons-vertical.svg) */ + 'chevrons-vertical': string; + /** ![image](../../node_modules/pixelarticons/svg/circle.svg) */ + 'circle': string; + /** ![image](../../node_modules/pixelarticons/svg/clipboard.svg) */ + 'clipboard': string; + /** ![image](../../node_modules/pixelarticons/svg/clock.svg) */ + 'clock': string; + /** ![image](../../node_modules/pixelarticons/svg/close-box.svg) */ + 'close-box': string; + /** ![image](../../node_modules/pixelarticons/svg/close.svg) */ + 'close': string; + /** ![image](../../node_modules/pixelarticons/svg/cloud-done.svg) */ + 'cloud-done': string; + /** ![image](../../node_modules/pixelarticons/svg/cloud-download.svg) */ + 'cloud-download': string; + /** ![image](../../node_modules/pixelarticons/svg/cloud-moon.svg) */ + 'cloud-moon': string; + /** ![image](../../node_modules/pixelarticons/svg/cloud-sun.svg) */ + 'cloud-sun': string; + /** ![image](../../node_modules/pixelarticons/svg/cloud-upload.svg) */ + 'cloud-upload': string; + /** ![image](../../node_modules/pixelarticons/svg/cloud.svg) */ + 'cloud': string; + /** ![image](../../node_modules/pixelarticons/svg/cocktail.svg) */ + 'cocktail': string; + /** ![image](../../node_modules/pixelarticons/svg/code.svg) */ + 'code': string; + /** ![image](../../node_modules/pixelarticons/svg/coffee-alt.svg) */ + 'coffee-alt': string; + /** ![image](../../node_modules/pixelarticons/svg/coffee.svg) */ + 'coffee': string; + /** ![image](../../node_modules/pixelarticons/svg/coin.svg) */ + 'coin': string; + /** ![image](../../node_modules/pixelarticons/svg/collapse.svg) */ + 'collapse': string; + /** ![image](../../node_modules/pixelarticons/svg/colors-swatch.svg) */ + 'colors-swatch': string; + /** ![image](../../node_modules/pixelarticons/svg/command.svg) */ + 'command': string; + /** ![image](../../node_modules/pixelarticons/svg/comment.svg) */ + 'comment': string; + /** ![image](../../node_modules/pixelarticons/svg/contact-delete.svg) */ + 'contact-delete': string; + /** ![image](../../node_modules/pixelarticons/svg/contact-multiple.svg) */ + 'contact-multiple': string; + /** ![image](../../node_modules/pixelarticons/svg/contact-plus.svg) */ + 'contact-plus': string; + /** ![image](../../node_modules/pixelarticons/svg/contact.svg) */ + 'contact': string; + /** ![image](../../node_modules/pixelarticons/svg/copy.svg) */ + 'copy': string; + /** ![image](../../node_modules/pixelarticons/svg/corner-down-left.svg) */ + 'corner-down-left': string; + /** ![image](../../node_modules/pixelarticons/svg/corner-down-right.svg) */ + 'corner-down-right': string; + /** ![image](../../node_modules/pixelarticons/svg/corner-left-down.svg) */ + 'corner-left-down': string; + /** ![image](../../node_modules/pixelarticons/svg/corner-left-up.svg) */ + 'corner-left-up': string; + /** ![image](../../node_modules/pixelarticons/svg/corner-right-down.svg) */ + 'corner-right-down': string; + /** ![image](../../node_modules/pixelarticons/svg/corner-right-up.svg) */ + 'corner-right-up': string; + /** ![image](../../node_modules/pixelarticons/svg/corner-up-left.svg) */ + 'corner-up-left': string; + /** ![image](../../node_modules/pixelarticons/svg/corner-up-right.svg) */ + 'corner-up-right': string; + /** ![image](../../node_modules/pixelarticons/svg/credit-card-delete.svg) */ + 'credit-card-delete': string; + /** ![image](../../node_modules/pixelarticons/svg/credit-card-minus.svg) */ + 'credit-card-minus': string; + /** ![image](../../node_modules/pixelarticons/svg/credit-card-multiple.svg) */ + 'credit-card-multiple': string; + /** ![image](../../node_modules/pixelarticons/svg/credit-card-plus.svg) */ + 'credit-card-plus': string; + /** ![image](../../node_modules/pixelarticons/svg/credit-card-settings.svg) */ + 'credit-card-settings': string; + /** ![image](../../node_modules/pixelarticons/svg/credit-card-wireless.svg) */ + 'credit-card-wireless': string; + /** ![image](../../node_modules/pixelarticons/svg/credit-card.svg) */ + 'credit-card': string; + /** ![image](../../node_modules/pixelarticons/svg/crop.svg) */ + 'crop': string; + /** ![image](../../node_modules/pixelarticons/svg/cut.svg) */ + 'cut': string; + /** ![image](../../node_modules/pixelarticons/svg/dashboard.svg) */ + 'dashboard': string; + /** ![image](../../node_modules/pixelarticons/svg/debug-check.svg) */ + 'debug-check': string; + /** ![image](../../node_modules/pixelarticons/svg/debug-off.svg) */ + 'debug-off': string; + /** ![image](../../node_modules/pixelarticons/svg/debug-pause.svg) */ + 'debug-pause': string; + /** ![image](../../node_modules/pixelarticons/svg/debug-play.svg) */ + 'debug-play': string; + /** ![image](../../node_modules/pixelarticons/svg/debug-stop.svg) */ + 'debug-stop': string; + /** ![image](../../node_modules/pixelarticons/svg/debug.svg) */ + 'debug': string; + /** ![image](../../node_modules/pixelarticons/svg/delete.svg) */ + 'delete': string; + /** ![image](../../node_modules/pixelarticons/svg/deskphone.svg) */ + 'deskphone': string; + /** ![image](../../node_modules/pixelarticons/svg/device-laptop.svg) */ + 'device-laptop': string; + /** ![image](../../node_modules/pixelarticons/svg/device-phone.svg) */ + 'device-phone': string; + /** ![image](../../node_modules/pixelarticons/svg/device-tablet.svg) */ + 'device-tablet': string; + /** ![image](../../node_modules/pixelarticons/svg/device-tv-smart.svg) */ + 'device-tv-smart': string; + /** ![image](../../node_modules/pixelarticons/svg/device-tv.svg) */ + 'device-tv': string; + /** ![image](../../node_modules/pixelarticons/svg/device-vibrate.svg) */ + 'device-vibrate': string; + /** ![image](../../node_modules/pixelarticons/svg/device-watch.svg) */ + 'device-watch': string; + /** ![image](../../node_modules/pixelarticons/svg/devices.svg) */ + 'devices': string; + /** ![image](../../node_modules/pixelarticons/svg/dice.svg) */ + 'dice': string; + /** ![image](../../node_modules/pixelarticons/svg/dollar.svg) */ + 'dollar': string; + /** ![image](../../node_modules/pixelarticons/svg/downasaur.svg) */ + 'downasaur': string; + /** ![image](../../node_modules/pixelarticons/svg/download.svg) */ + 'download': string; + /** ![image](../../node_modules/pixelarticons/svg/draft.svg) */ + 'draft': string; + /** ![image](../../node_modules/pixelarticons/svg/drag-and-drop.svg) */ + 'drag-and-drop': string; + /** ![image](../../node_modules/pixelarticons/svg/drop-area.svg) */ + 'drop-area': string; + /** ![image](../../node_modules/pixelarticons/svg/drop-full.svg) */ + 'drop-full': string; + /** ![image](../../node_modules/pixelarticons/svg/drop-half.svg) */ + 'drop-half': string; + /** ![image](../../node_modules/pixelarticons/svg/drop.svg) */ + 'drop': string; + /** ![image](../../node_modules/pixelarticons/svg/duplicate-alt.svg) */ + 'duplicate-alt': string; + /** ![image](../../node_modules/pixelarticons/svg/duplicate.svg) */ + 'duplicate': string; + /** ![image](../../node_modules/pixelarticons/svg/edit-box.svg) */ + 'edit-box': string; + /** ![image](../../node_modules/pixelarticons/svg/edit.svg) */ + 'edit': string; + /** ![image](../../node_modules/pixelarticons/svg/euro.svg) */ + 'euro': string; + /** ![image](../../node_modules/pixelarticons/svg/expand.svg) */ + 'expand': string; + /** ![image](../../node_modules/pixelarticons/svg/external-link.svg) */ + 'external-link': string; + /** ![image](../../node_modules/pixelarticons/svg/eye-closed.svg) */ + 'eye-closed': string; + /** ![image](../../node_modules/pixelarticons/svg/eye.svg) */ + 'eye': string; + /** ![image](../../node_modules/pixelarticons/svg/file-alt.svg) */ + 'file-alt': string; + /** ![image](../../node_modules/pixelarticons/svg/file-delete.svg) */ + 'file-delete': string; + /** ![image](../../node_modules/pixelarticons/svg/file-flash.svg) */ + 'file-flash': string; + /** ![image](../../node_modules/pixelarticons/svg/file-minus.svg) */ + 'file-minus': string; + /** ![image](../../node_modules/pixelarticons/svg/file-multiple.svg) */ + 'file-multiple': string; + /** ![image](../../node_modules/pixelarticons/svg/file-off.svg) */ + 'file-off': string; + /** ![image](../../node_modules/pixelarticons/svg/file-plus.svg) */ + 'file-plus': string; + /** ![image](../../node_modules/pixelarticons/svg/file.svg) */ + 'file': string; + /** ![image](../../node_modules/pixelarticons/svg/fill-half.svg) */ + 'fill-half': string; + /** ![image](../../node_modules/pixelarticons/svg/fill.svg) */ + 'fill': string; + /** ![image](../../node_modules/pixelarticons/svg/flag.svg) */ + 'flag': string; + /** ![image](../../node_modules/pixelarticons/svg/flatten.svg) */ + 'flatten': string; + /** ![image](../../node_modules/pixelarticons/svg/flip-to-back.svg) */ + 'flip-to-back': string; + /** ![image](../../node_modules/pixelarticons/svg/flip-to-front.svg) */ + 'flip-to-front': string; + /** ![image](../../node_modules/pixelarticons/svg/float-center.svg) */ + 'float-center': string; + /** ![image](../../node_modules/pixelarticons/svg/float-left.svg) */ + 'float-left': string; + /** ![image](../../node_modules/pixelarticons/svg/float-right.svg) */ + 'float-right': string; + /** ![image](../../node_modules/pixelarticons/svg/folder-minus.svg) */ + 'folder-minus': string; + /** ![image](../../node_modules/pixelarticons/svg/folder-plus.svg) */ + 'folder-plus': string; + /** ![image](../../node_modules/pixelarticons/svg/folder-x.svg) */ + 'folder-x': string; + /** ![image](../../node_modules/pixelarticons/svg/folder.svg) */ + 'folder': string; + /** ![image](../../node_modules/pixelarticons/svg/forward.svg) */ + 'forward': string; + /** ![image](../../node_modules/pixelarticons/svg/forwardburger.svg) */ + 'forwardburger': string; + /** ![image](../../node_modules/pixelarticons/svg/frame-add.svg) */ + 'frame-add': string; + /** ![image](../../node_modules/pixelarticons/svg/frame-check.svg) */ + 'frame-check': string; + /** ![image](../../node_modules/pixelarticons/svg/frame-delete.svg) */ + 'frame-delete': string; + /** ![image](../../node_modules/pixelarticons/svg/frame-minus.svg) */ + 'frame-minus': string; + /** ![image](../../node_modules/pixelarticons/svg/frame.svg) */ + 'frame': string; + /** ![image](../../node_modules/pixelarticons/svg/gamepad.svg) */ + 'gamepad': string; + /** ![image](../../node_modules/pixelarticons/svg/gif.svg) */ + 'gif': string; + /** ![image](../../node_modules/pixelarticons/svg/gift.svg) */ + 'gift': string; + /** ![image](../../node_modules/pixelarticons/svg/git-branch.svg) */ + 'git-branch': string; + /** ![image](../../node_modules/pixelarticons/svg/git-commit.svg) */ + 'git-commit': string; + /** ![image](../../node_modules/pixelarticons/svg/git-merge.svg) */ + 'git-merge': string; + /** ![image](../../node_modules/pixelarticons/svg/git-pull-request.svg) */ + 'git-pull-request': string; + /** ![image](../../node_modules/pixelarticons/svg/github-2.svg) */ + 'github-2': string; + /** ![image](../../node_modules/pixelarticons/svg/github.svg) */ + 'github': string; + /** ![image](../../node_modules/pixelarticons/svg/gps.svg) */ + 'gps': string; + /** ![image](../../node_modules/pixelarticons/svg/grid.svg) */ + 'grid': string; + /** ![image](../../node_modules/pixelarticons/svg/group.svg) */ + 'group': string; + /** ![image](../../node_modules/pixelarticons/svg/hd.svg) */ + 'hd': string; + /** ![image](../../node_modules/pixelarticons/svg/headphone.svg) */ + 'headphone': string; + /** ![image](../../node_modules/pixelarticons/svg/headset.svg) */ + 'headset': string; + /** ![image](../../node_modules/pixelarticons/svg/heart.svg) */ + 'heart': string; + /** ![image](../../node_modules/pixelarticons/svg/hidden.svg) */ + 'hidden': string; + /** ![image](../../node_modules/pixelarticons/svg/home.svg) */ + 'home': string; + /** ![image](../../node_modules/pixelarticons/svg/hourglass.svg) */ + 'hourglass': string; + /** ![image](../../node_modules/pixelarticons/svg/hq.svg) */ + 'hq': string; + /** ![image](../../node_modules/pixelarticons/svg/human-handsdown.svg) */ + 'human-handsdown': string; + /** ![image](../../node_modules/pixelarticons/svg/human-handsup.svg) */ + 'human-handsup': string; + /** ![image](../../node_modules/pixelarticons/svg/human-height-alt.svg) */ + 'human-height-alt': string; + /** ![image](../../node_modules/pixelarticons/svg/human-height.svg) */ + 'human-height': string; + /** ![image](../../node_modules/pixelarticons/svg/human-run.svg) */ + 'human-run': string; + /** ![image](../../node_modules/pixelarticons/svg/human.svg) */ + 'human': string; + /** ![image](../../node_modules/pixelarticons/svg/image-arrow-right.svg) */ + 'image-arrow-right': string; + /** ![image](../../node_modules/pixelarticons/svg/image-broken.svg) */ + 'image-broken': string; + /** ![image](../../node_modules/pixelarticons/svg/image-delete.svg) */ + 'image-delete': string; + /** ![image](../../node_modules/pixelarticons/svg/image-flash.svg) */ + 'image-flash': string; + /** ![image](../../node_modules/pixelarticons/svg/image-frame.svg) */ + 'image-frame': string; + /** ![image](../../node_modules/pixelarticons/svg/image-gallery.svg) */ + 'image-gallery': string; + /** ![image](../../node_modules/pixelarticons/svg/image-multiple.svg) */ + 'image-multiple': string; + /** ![image](../../node_modules/pixelarticons/svg/image-new.svg) */ + 'image-new': string; + /** ![image](../../node_modules/pixelarticons/svg/image-plus.svg) */ + 'image-plus': string; + /** ![image](../../node_modules/pixelarticons/svg/image.svg) */ + 'image': string; + /** ![image](../../node_modules/pixelarticons/svg/inbox-all.svg) */ + 'inbox-all': string; + /** ![image](../../node_modules/pixelarticons/svg/inbox-full.svg) */ + 'inbox-full': string; + /** ![image](../../node_modules/pixelarticons/svg/inbox.svg) */ + 'inbox': string; + /** ![image](../../node_modules/pixelarticons/svg/info-box.svg) */ + 'info-box': string; + /** ![image](../../node_modules/pixelarticons/svg/invert.svg) */ + 'invert': string; + /** ![image](../../node_modules/pixelarticons/svg/iso.svg) */ + 'iso': string; + /** ![image](../../node_modules/pixelarticons/svg/kanban.svg) */ + 'kanban': string; + /** ![image](../../node_modules/pixelarticons/svg/keyboard.svg) */ + 'keyboard': string; + /** ![image](../../node_modules/pixelarticons/svg/label-alt-multiple.svg) */ + 'label-alt-multiple': string; + /** ![image](../../node_modules/pixelarticons/svg/label-alt.svg) */ + 'label-alt': string; + /** ![image](../../node_modules/pixelarticons/svg/label-sharp.svg) */ + 'label-sharp': string; + /** ![image](../../node_modules/pixelarticons/svg/label.svg) */ + 'label': string; + /** ![image](../../node_modules/pixelarticons/svg/layout-align-bottom.svg) */ + 'layout-align-bottom': string; + /** ![image](../../node_modules/pixelarticons/svg/layout-align-left.svg) */ + 'layout-align-left': string; + /** ![image](../../node_modules/pixelarticons/svg/layout-align-right.svg) */ + 'layout-align-right': string; + /** ![image](../../node_modules/pixelarticons/svg/layout-align-top.svg) */ + 'layout-align-top': string; + /** ![image](../../node_modules/pixelarticons/svg/layout-columns.svg) */ + 'layout-columns': string; + /** ![image](../../node_modules/pixelarticons/svg/layout-distribute-horizontal.svg) */ + 'layout-distribute-horizontal': string; + /** ![image](../../node_modules/pixelarticons/svg/layout-distribute-vertical.svg) */ + 'layout-distribute-vertical': string; + /** ![image](../../node_modules/pixelarticons/svg/layout-footer.svg) */ + 'layout-footer': string; + /** ![image](../../node_modules/pixelarticons/svg/layout-header.svg) */ + 'layout-header': string; + /** ![image](../../node_modules/pixelarticons/svg/layout-rows.svg) */ + 'layout-rows': string; + /** ![image](../../node_modules/pixelarticons/svg/layout-sidebar-left.svg) */ + 'layout-sidebar-left': string; + /** ![image](../../node_modules/pixelarticons/svg/layout-sidebar-right.svg) */ + 'layout-sidebar-right': string; + /** ![image](../../node_modules/pixelarticons/svg/layout.svg) */ + 'layout': string; + /** ![image](../../node_modules/pixelarticons/svg/lightbulb-2.svg) */ + 'lightbulb-2': string; + /** ![image](../../node_modules/pixelarticons/svg/lightbulb-on.svg) */ + 'lightbulb-on': string; + /** ![image](../../node_modules/pixelarticons/svg/lightbulb.svg) */ + 'lightbulb': string; + /** ![image](../../node_modules/pixelarticons/svg/link.svg) */ + 'link': string; + /** ![image](../../node_modules/pixelarticons/svg/list-box.svg) */ + 'list-box': string; + /** ![image](../../node_modules/pixelarticons/svg/list.svg) */ + 'list': string; + /** ![image](../../node_modules/pixelarticons/svg/loader.svg) */ + 'loader': string; + /** ![image](../../node_modules/pixelarticons/svg/lock-open.svg) */ + 'lock-open': string; + /** ![image](../../node_modules/pixelarticons/svg/lock.svg) */ + 'lock': string; + /** ![image](../../node_modules/pixelarticons/svg/login.svg) */ + 'login': string; + /** ![image](../../node_modules/pixelarticons/svg/logout.svg) */ + 'logout': string; + /** ![image](../../node_modules/pixelarticons/svg/luggage.svg) */ + 'luggage': string; + /** ![image](../../node_modules/pixelarticons/svg/mail-arrow-right.svg) */ + 'mail-arrow-right': string; + /** ![image](../../node_modules/pixelarticons/svg/mail-check.svg) */ + 'mail-check': string; + /** ![image](../../node_modules/pixelarticons/svg/mail-delete.svg) */ + 'mail-delete': string; + /** ![image](../../node_modules/pixelarticons/svg/mail-flash.svg) */ + 'mail-flash': string; + /** ![image](../../node_modules/pixelarticons/svg/mail-multiple.svg) */ + 'mail-multiple': string; + /** ![image](../../node_modules/pixelarticons/svg/mail-off.svg) */ + 'mail-off': string; + /** ![image](../../node_modules/pixelarticons/svg/mail-unread.svg) */ + 'mail-unread': string; + /** ![image](../../node_modules/pixelarticons/svg/mail.svg) */ + 'mail': string; + /** ![image](../../node_modules/pixelarticons/svg/map.svg) */ + 'map': string; + /** ![image](../../node_modules/pixelarticons/svg/mastodon.svg) */ + 'mastodon': string; + /** ![image](../../node_modules/pixelarticons/svg/membercard.svg) */ + 'membercard': string; + /** ![image](../../node_modules/pixelarticons/svg/menu.svg) */ + 'menu': string; + /** ![image](../../node_modules/pixelarticons/svg/message-arrow-left.svg) */ + 'message-arrow-left': string; + /** ![image](../../node_modules/pixelarticons/svg/message-arrow-right.svg) */ + 'message-arrow-right': string; + /** ![image](../../node_modules/pixelarticons/svg/message-bookmark.svg) */ + 'message-bookmark': string; + /** ![image](../../node_modules/pixelarticons/svg/message-clock.svg) */ + 'message-clock': string; + /** ![image](../../node_modules/pixelarticons/svg/message-delete.svg) */ + 'message-delete': string; + /** ![image](../../node_modules/pixelarticons/svg/message-flash.svg) */ + 'message-flash': string; + /** ![image](../../node_modules/pixelarticons/svg/message-image.svg) */ + 'message-image': string; + /** ![image](../../node_modules/pixelarticons/svg/message-minus.svg) */ + 'message-minus': string; + /** ![image](../../node_modules/pixelarticons/svg/message-plus.svg) */ + 'message-plus': string; + /** ![image](../../node_modules/pixelarticons/svg/message-processing.svg) */ + 'message-processing': string; + /** ![image](../../node_modules/pixelarticons/svg/message-reply.svg) */ + 'message-reply': string; + /** ![image](../../node_modules/pixelarticons/svg/message-text.svg) */ + 'message-text': string; + /** ![image](../../node_modules/pixelarticons/svg/message.svg) */ + 'message': string; + /** ![image](../../node_modules/pixelarticons/svg/minus.svg) */ + 'minus': string; + /** ![image](../../node_modules/pixelarticons/svg/missed-call.svg) */ + 'missed-call': string; + /** ![image](../../node_modules/pixelarticons/svg/modem.svg) */ + 'modem': string; + /** ![image](../../node_modules/pixelarticons/svg/money.svg) */ + 'money': string; + /** ![image](../../node_modules/pixelarticons/svg/monitor.svg) */ + 'monitor': string; + /** ![image](../../node_modules/pixelarticons/svg/mood-happy.svg) */ + 'mood-happy': string; + /** ![image](../../node_modules/pixelarticons/svg/mood-neutral.svg) */ + 'mood-neutral': string; + /** ![image](../../node_modules/pixelarticons/svg/mood-sad.svg) */ + 'mood-sad': string; + /** ![image](../../node_modules/pixelarticons/svg/moon-star.svg) */ + 'moon-star': string; + /** ![image](../../node_modules/pixelarticons/svg/moon-stars.svg) */ + 'moon-stars': string; + /** ![image](../../node_modules/pixelarticons/svg/moon.svg) */ + 'moon': string; + /** ![image](../../node_modules/pixelarticons/svg/more-horizontal.svg) */ + 'more-horizontal': string; + /** ![image](../../node_modules/pixelarticons/svg/more-vertical.svg) */ + 'more-vertical': string; + /** ![image](../../node_modules/pixelarticons/svg/mouse.svg) */ + 'mouse': string; + /** ![image](../../node_modules/pixelarticons/svg/move.svg) */ + 'move': string; + /** ![image](../../node_modules/pixelarticons/svg/movie.svg) */ + 'movie': string; + /** ![image](../../node_modules/pixelarticons/svg/music.svg) */ + 'music': string; + /** ![image](../../node_modules/pixelarticons/svg/next.svg) */ + 'next': string; + /** ![image](../../node_modules/pixelarticons/svg/note-delete.svg) */ + 'note-delete': string; + /** ![image](../../node_modules/pixelarticons/svg/note-multiple.svg) */ + 'note-multiple': string; + /** ![image](../../node_modules/pixelarticons/svg/note-plus.svg) */ + 'note-plus': string; + /** ![image](../../node_modules/pixelarticons/svg/note.svg) */ + 'note': string; + /** ![image](../../node_modules/pixelarticons/svg/notes-delete.svg) */ + 'notes-delete': string; + /** ![image](../../node_modules/pixelarticons/svg/notes-multiple.svg) */ + 'notes-multiple': string; + /** ![image](../../node_modules/pixelarticons/svg/notes-plus.svg) */ + 'notes-plus': string; + /** ![image](../../node_modules/pixelarticons/svg/notes.svg) */ + 'notes': string; + /** ![image](../../node_modules/pixelarticons/svg/notification-off.svg) */ + 'notification-off': string; + /** ![image](../../node_modules/pixelarticons/svg/notification.svg) */ + 'notification': string; + /** ![image](../../node_modules/pixelarticons/svg/open.svg) */ + 'open': string; + /** ![image](../../node_modules/pixelarticons/svg/paint-bucket.svg) */ + 'paint-bucket': string; + /** ![image](../../node_modules/pixelarticons/svg/paperclip.svg) */ + 'paperclip': string; + /** ![image](../../node_modules/pixelarticons/svg/pause.svg) */ + 'pause': string; + /** ![image](../../node_modules/pixelarticons/svg/percent.svg) */ + 'percent': string; + /** ![image](../../node_modules/pixelarticons/svg/picture-in-picture-alt.svg) */ + 'picture-in-picture-alt': string; + /** ![image](../../node_modules/pixelarticons/svg/picture-in-picture.svg) */ + 'picture-in-picture': string; + /** ![image](../../node_modules/pixelarticons/svg/pin.svg) */ + 'pin': string; + /** ![image](../../node_modules/pixelarticons/svg/pixelarticons.svg) */ + 'pixelarticons': string; + /** ![image](../../node_modules/pixelarticons/svg/play.svg) */ + 'play': string; + /** ![image](../../node_modules/pixelarticons/svg/playlist.svg) */ + 'playlist': string; + /** ![image](../../node_modules/pixelarticons/svg/plus.svg) */ + 'plus': string; + /** ![image](../../node_modules/pixelarticons/svg/power.svg) */ + 'power': string; + /** ![image](../../node_modules/pixelarticons/svg/prev.svg) */ + 'prev': string; + /** ![image](../../node_modules/pixelarticons/svg/print.svg) */ + 'print': string; + /** ![image](../../node_modules/pixelarticons/svg/radio-handheld.svg) */ + 'radio-handheld': string; + /** ![image](../../node_modules/pixelarticons/svg/radio-on.svg) */ + 'radio-on': string; + /** ![image](../../node_modules/pixelarticons/svg/radio-signal.svg) */ + 'radio-signal': string; + /** ![image](../../node_modules/pixelarticons/svg/radio-tower.svg) */ + 'radio-tower': string; + /** ![image](../../node_modules/pixelarticons/svg/reciept-alt.svg) */ + 'reciept-alt': string; + /** ![image](../../node_modules/pixelarticons/svg/reciept.svg) */ + 'reciept': string; + /** ![image](../../node_modules/pixelarticons/svg/redo.svg) */ + 'redo': string; + /** ![image](../../node_modules/pixelarticons/svg/reload.svg) */ + 'reload': string; + /** ![image](../../node_modules/pixelarticons/svg/remove-box-multiple.svg) */ + 'remove-box-multiple': string; + /** ![image](../../node_modules/pixelarticons/svg/remove-box.svg) */ + 'remove-box': string; + /** ![image](../../node_modules/pixelarticons/svg/repeat.svg) */ + 'repeat': string; + /** ![image](../../node_modules/pixelarticons/svg/reply-all.svg) */ + 'reply-all': string; + /** ![image](../../node_modules/pixelarticons/svg/reply.svg) */ + 'reply': string; + /** ![image](../../node_modules/pixelarticons/svg/rounded-corner.svg) */ + 'rounded-corner': string; + /** ![image](../../node_modules/pixelarticons/svg/save.svg) */ + 'save': string; + /** ![image](../../node_modules/pixelarticons/svg/scale.svg) */ + 'scale': string; + /** ![image](../../node_modules/pixelarticons/svg/script-text.svg) */ + 'script-text': string; + /** ![image](../../node_modules/pixelarticons/svg/script.svg) */ + 'script': string; + /** ![image](../../node_modules/pixelarticons/svg/scroll-horizontal.svg) */ + 'scroll-horizontal': string; + /** ![image](../../node_modules/pixelarticons/svg/scroll-vertical.svg) */ + 'scroll-vertical': string; + /** ![image](../../node_modules/pixelarticons/svg/sd.svg) */ + 'sd': string; + /** ![image](../../node_modules/pixelarticons/svg/search.svg) */ + 'search': string; + /** ![image](../../node_modules/pixelarticons/svg/section-copy.svg) */ + 'section-copy': string; + /** ![image](../../node_modules/pixelarticons/svg/section-minus.svg) */ + 'section-minus': string; + /** ![image](../../node_modules/pixelarticons/svg/section-plus.svg) */ + 'section-plus': string; + /** ![image](../../node_modules/pixelarticons/svg/section-x.svg) */ + 'section-x': string; + /** ![image](../../node_modules/pixelarticons/svg/section.svg) */ + 'section': string; + /** ![image](../../node_modules/pixelarticons/svg/server.svg) */ + 'server': string; + /** ![image](../../node_modules/pixelarticons/svg/sharp-corner.svg) */ + 'sharp-corner': string; + /** ![image](../../node_modules/pixelarticons/svg/shield-off.svg) */ + 'shield-off': string; + /** ![image](../../node_modules/pixelarticons/svg/shield.svg) */ + 'shield': string; + /** ![image](../../node_modules/pixelarticons/svg/ship.svg) */ + 'ship': string; + /** ![image](../../node_modules/pixelarticons/svg/shopping-bag.svg) */ + 'shopping-bag': string; + /** ![image](../../node_modules/pixelarticons/svg/shuffle.svg) */ + 'shuffle': string; + /** ![image](../../node_modules/pixelarticons/svg/sliders-2.svg) */ + 'sliders-2': string; + /** ![image](../../node_modules/pixelarticons/svg/sliders.svg) */ + 'sliders': string; + /** ![image](../../node_modules/pixelarticons/svg/sort-alphabetic.svg) */ + 'sort-alphabetic': string; + /** ![image](../../node_modules/pixelarticons/svg/sort-numeric.svg) */ + 'sort-numeric': string; + /** ![image](../../node_modules/pixelarticons/svg/sort.svg) */ + 'sort': string; + /** ![image](../../node_modules/pixelarticons/svg/speaker.svg) */ + 'speaker': string; + /** ![image](../../node_modules/pixelarticons/svg/speed-fast.svg) */ + 'speed-fast': string; + /** ![image](../../node_modules/pixelarticons/svg/speed-medium.svg) */ + 'speed-medium': string; + /** ![image](../../node_modules/pixelarticons/svg/speed-slow.svg) */ + 'speed-slow': string; + /** ![image](../../node_modules/pixelarticons/svg/spotlight.svg) */ + 'spotlight': string; + /** ![image](../../node_modules/pixelarticons/svg/store.svg) */ + 'store': string; + /** ![image](../../node_modules/pixelarticons/svg/subscriptions.svg) */ + 'subscriptions': string; + /** ![image](../../node_modules/pixelarticons/svg/subtitles.svg) */ + 'subtitles': string; + /** ![image](../../node_modules/pixelarticons/svg/suitcase.svg) */ + 'suitcase': string; + /** ![image](../../node_modules/pixelarticons/svg/sun-alt.svg) */ + 'sun-alt': string; + /** ![image](../../node_modules/pixelarticons/svg/sun.svg) */ + 'sun': string; + /** ![image](../../node_modules/pixelarticons/svg/switch.svg) */ + 'switch': string; + /** ![image](../../node_modules/pixelarticons/svg/sync.svg) */ + 'sync': string; + /** ![image](../../node_modules/pixelarticons/svg/tab.svg) */ + 'tab': string; + /** ![image](../../node_modules/pixelarticons/svg/table.svg) */ + 'table': string; + /** ![image](../../node_modules/pixelarticons/svg/tea.svg) */ + 'tea': string; + /** ![image](../../node_modules/pixelarticons/svg/teach.svg) */ + 'teach': string; + /** ![image](../../node_modules/pixelarticons/svg/text-add.svg) */ + 'text-add': string; + /** ![image](../../node_modules/pixelarticons/svg/text-colums.svg) */ + 'text-colums': string; + /** ![image](../../node_modules/pixelarticons/svg/text-search.svg) */ + 'text-search': string; + /** ![image](../../node_modules/pixelarticons/svg/text-wrap.svg) */ + 'text-wrap': string; + /** ![image](../../node_modules/pixelarticons/svg/timeline.svg) */ + 'timeline': string; + /** ![image](../../node_modules/pixelarticons/svg/toggle-left.svg) */ + 'toggle-left': string; + /** ![image](../../node_modules/pixelarticons/svg/toggle-right.svg) */ + 'toggle-right': string; + /** ![image](../../node_modules/pixelarticons/svg/tournament.svg) */ + 'tournament': string; + /** ![image](../../node_modules/pixelarticons/svg/track-changes.svg) */ + 'track-changes': string; + /** ![image](../../node_modules/pixelarticons/svg/trash-alt.svg) */ + 'trash-alt': string; + /** ![image](../../node_modules/pixelarticons/svg/trash.svg) */ + 'trash': string; + /** ![image](../../node_modules/pixelarticons/svg/trending-down.svg) */ + 'trending-down': string; + /** ![image](../../node_modules/pixelarticons/svg/trending-up.svg) */ + 'trending-up': string; + /** ![image](../../node_modules/pixelarticons/svg/trending.svg) */ + 'trending': string; + /** ![image](../../node_modules/pixelarticons/svg/trophy.svg) */ + 'trophy': string; + /** ![image](../../node_modules/pixelarticons/svg/truck.svg) */ + 'truck': string; + /** ![image](../../node_modules/pixelarticons/svg/undo.svg) */ + 'undo': string; + /** ![image](../../node_modules/pixelarticons/svg/ungroup.svg) */ + 'ungroup': string; + /** ![image](../../node_modules/pixelarticons/svg/unlink.svg) */ + 'unlink': string; + /** ![image](../../node_modules/pixelarticons/svg/upload.svg) */ + 'upload': string; + /** ![image](../../node_modules/pixelarticons/svg/user-minus.svg) */ + 'user-minus': string; + /** ![image](../../node_modules/pixelarticons/svg/user-plus.svg) */ + 'user-plus': string; + /** ![image](../../node_modules/pixelarticons/svg/user-x.svg) */ + 'user-x': string; + /** ![image](../../node_modules/pixelarticons/svg/user.svg) */ + 'user': string; + /** ![image](../../node_modules/pixelarticons/svg/users.svg) */ + 'users': string; + /** ![image](../../node_modules/pixelarticons/svg/video-off.svg) */ + 'video-off': string; + /** ![image](../../node_modules/pixelarticons/svg/video.svg) */ + 'video': string; + /** ![image](../../node_modules/pixelarticons/svg/view-col.svg) */ + 'view-col': string; + /** ![image](../../node_modules/pixelarticons/svg/view-list.svg) */ + 'view-list': string; + /** ![image](../../node_modules/pixelarticons/svg/viewport-narrow.svg) */ + 'viewport-narrow': string; + /** ![image](../../node_modules/pixelarticons/svg/viewport-wide.svg) */ + 'viewport-wide': string; + /** ![image](../../node_modules/pixelarticons/svg/visible.svg) */ + 'visible': string; + /** ![image](../../node_modules/pixelarticons/svg/volume-1.svg) */ + 'volume-1': string; + /** ![image](../../node_modules/pixelarticons/svg/volume-2.svg) */ + 'volume-2': string; + /** ![image](../../node_modules/pixelarticons/svg/volume-3.svg) */ + 'volume-3': string; + /** ![image](../../node_modules/pixelarticons/svg/volume-minus.svg) */ + 'volume-minus': string; + /** ![image](../../node_modules/pixelarticons/svg/volume-plus.svg) */ + 'volume-plus': string; + /** ![image](../../node_modules/pixelarticons/svg/volume-vibrate.svg) */ + 'volume-vibrate': string; + /** ![image](../../node_modules/pixelarticons/svg/volume-x.svg) */ + 'volume-x': string; + /** ![image](../../node_modules/pixelarticons/svg/volume.svg) */ + 'volume': string; + /** ![image](../../node_modules/pixelarticons/svg/wallet.svg) */ + 'wallet': string; + /** ![image](../../node_modules/pixelarticons/svg/warning-box.svg) */ + 'warning-box': string; + /** ![image](../../node_modules/pixelarticons/svg/wind.svg) */ + 'wind': string; + /** ![image](../../node_modules/pixelarticons/svg/zap.svg) */ + 'zap': string; + /** ![image](../../node_modules/pixelarticons/svg/zoom-in.svg) */ + 'zoom-in': string; + /** ![image](../../node_modules/pixelarticons/svg/zoom-out.svg) */ + 'zoom-out': string; +} diff --git a/src/react/ps_icons/circle_playstation_console_controller_gamepad_icon.svg b/src/react/ps_icons/circle_playstation_console_controller_gamepad_icon.svg new file mode 100644 index 00000000..d49af336 --- /dev/null +++ b/src/react/ps_icons/circle_playstation_console_controller_gamepad_icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/react/ps_icons/cross_playstation_console_controller_gamepad_icon.svg b/src/react/ps_icons/cross_playstation_console_controller_gamepad_icon.svg new file mode 100644 index 00000000..d7d176e2 --- /dev/null +++ b/src/react/ps_icons/cross_playstation_console_controller_gamepad_icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/react/ps_icons/playstation_square_console_controller_gamepad_icon.svg b/src/react/ps_icons/playstation_square_console_controller_gamepad_icon.svg new file mode 100644 index 00000000..9f08790e --- /dev/null +++ b/src/react/ps_icons/playstation_square_console_controller_gamepad_icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/react/ps_icons/playstation_triangle_console_controller_gamepad_icon.svg b/src/react/ps_icons/playstation_triangle_console_controller_gamepad_icon.svg new file mode 100644 index 00000000..a397c517 --- /dev/null +++ b/src/react/ps_icons/playstation_triangle_console_controller_gamepad_icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/react/storageProvider.ts b/src/react/storageProvider.ts new file mode 100644 index 00000000..df97d228 --- /dev/null +++ b/src/react/storageProvider.ts @@ -0,0 +1,13 @@ +import { CustomCommand } from './KeybindingsCustom' + +type StorageData = { + customCommands: Record + // ... +} + +export const getStoredValue = (name: T): StorageData[T] | undefined => { + return localStorage[name] ? JSON.parse(localStorage[name]) : undefined +} +export const setStoredValue = (name: T, value: StorageData[T]) => { + localStorage[name] = JSON.stringify(value) +} diff --git a/src/react/utilsApp.ts b/src/react/utilsApp.ts index 3fd18515..53df4cda 100644 --- a/src/react/utilsApp.ts +++ b/src/react/utilsApp.ts @@ -1,11 +1,27 @@ import { useSnapshot } from 'valtio' +import { useEffect, useMemo } from 'react' +import { useMedia } from 'react-use' 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 } @@ -13,3 +29,7 @@ export const useIsModalActive = (modal: string, useIncludes = false) => { export const useIsWidgetActive = (name: string) => { return useIsModalActive(`widget-${name}`) } + +export const useIsSmallWidth = () => { + return useMedia('(max-width: 550px)') +} diff --git a/src/reactUi.tsx b/src/reactUi.tsx index 9a79de34..34a567cd 100644 --- a/src/reactUi.tsx +++ b/src/reactUi.tsx @@ -4,7 +4,7 @@ import { useSnapshot } from 'valtio' import { QRCodeSVG } from 'qrcode.react' import { createPortal } from 'react-dom' import { useEffect, useMemo, useState } from 'react' -import { miscUiState } from './globalState' +import { activeModalStack, miscUiState } from './globalState' import DeathScreenProvider from './react/DeathScreenProvider' import OptionsRenderApp from './react/OptionsRenderApp' import MainMenuRenderApp from './react/MainMenuRenderApp' @@ -36,7 +36,11 @@ import Crosshair from './react/Crosshair' import ButtonAppProvider from './react/ButtonAppProvider' import ServersListProvider from './react/ServersListProvider' 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' +import SignInMessageProvider from './react/SignInMessageProvider' const RobustPortal = ({ children, to }) => { return createPortal({children}, to) @@ -93,28 +97,34 @@ const InGameComponent = ({ children }) => { } const InGameUi = () => { - const { gameLoaded } = useSnapshot(miscUiState) - if (!gameLoaded) return + const { gameLoaded, showUI: showUIRaw } = useSnapshot(miscUiState) + const hasModals = useSnapshot(activeModalStack).length > 0 + const showUI = showUIRaw || hasModals + if (!gameLoaded || !bot) return return <> {/* apply scaling */} - - - - - - - - - - - +
+ + + + + + + + + + +
- - - +
+ + + +
+ {showUI && }
@@ -122,7 +132,7 @@ const InGameUi = () => { {/* because of z-index */} - + {showUI && } @@ -157,11 +167,15 @@ const App = () => { + + + + {/* */} @@ -179,7 +193,7 @@ const App = () => { const PerComponentErrorBoundary = ({ children }) => { return children.map((child, i) => { const componentNameClean = (child.type.name || child.type.displayName || 'Unknown').replaceAll(/__|_COMPONENT/g, '') - showNotification(`UI component ${componentNameClean} crashed!`, 'Please report this. Use console to see more info.', true, undefined) + showNotification(`UI component ${componentNameClean} crashed!`, 'Please report this. Use console for more.', true, undefined) return null }}>{child}) } diff --git a/src/scaleInterface.ts b/src/scaleInterface.ts index 9eac457d..c7e08622 100644 --- a/src/scaleInterface.ts +++ b/src/scaleInterface.ts @@ -1,31 +1,37 @@ import { proxy } from 'valtio' import { subscribeKey } from 'valtio/utils' -import { options } from './optionsStorage' +import { options, watchValue } from './optionsStorage' export const currentScaling = proxy({ scale: 1, }) +window.currentScaling = currentScaling const setScale = () => { const scaleValues = [ - { width: 971, height: 670, scale: 2 }, - { width: null, height: 430, scale: 1.5 }, - { width: 590, height: null, scale: 1 } + { maxWidth: 971, maxHeight: null, scale: 2 }, + { maxWidth: null, maxHeight: 390, scale: 1.5 }, // todo allow to set the scaling at 360-400 (dynamic scaling setting) + { maxWidth: 590, maxHeight: null, scale: 1 }, + + { maxWidth: 590, minHeight: 240, scale: 1.4 }, ] const { innerWidth, innerHeight } = window let result = options.guiScale - for (const { width, height, scale } of scaleValues) { - if ((width && innerWidth <= width) || (height && innerHeight <= height)) { + for (const { maxWidth, maxHeight, scale, minHeight } of scaleValues) { + if ((!maxWidth || innerWidth <= maxWidth) && (!maxHeight || innerHeight <= maxHeight) && (!minHeight || innerHeight >= minHeight)) { result = scale } } currentScaling.scale = result - document.documentElement.style.setProperty('--guiScale', String(result)) } + setScale() subscribeKey(options, 'guiScale', setScale) +watchValue(currentScaling, (c) => { + document.documentElement.style.setProperty('--guiScale', String(c.scale)) +}) window.addEventListener('resize', setScale) diff --git a/src/screens.css b/src/screens.css index c4e7fe9b..752b5932 100644 --- a/src/screens.css +++ b/src/screens.css @@ -33,6 +33,7 @@ margin-top: 35px; /* todo remove it but without it in chrome android the screen is not scrollable */ overflow: auto; + height: fit-content; /* todo I'm not sure about it */ /* margin-top: calc(100% / 6 - 16px); */ align-items: center; @@ -40,6 +41,18 @@ gap: 10px; } +@media screen and (max-height: 426px) { + .fullscreen:not(.small-content) { + .screen-content { + margin-top: 14px; + } + + .screen-title { + margin-bottom: 5px; + } + } +} + .screen-items { display: grid; grid-template-columns: repeat(2, 1fr); diff --git a/src/soundSystem.ts b/src/soundSystem.ts index a2cc1f70..e7fdaee7 100644 --- a/src/soundSystem.ts +++ b/src/soundSystem.ts @@ -144,10 +144,12 @@ subscribeKey(miscUiState, 'gameLoaded', async () => { // movement happening if (Date.now() - lastStepSound > 300) { const blockUnder = bot.world.getBlock(bot.entity.position.offset(0, -1, 0)) - const stepSound = getStepSound(blockUnder) - if (stepSound) { - await playHardcodedSound(stepSound, undefined, 0.6)// todo not sure why 0.6 - lastStepSound = Date.now() + if (blockUnder) { + const stepSound = getStepSound(blockUnder) + if (stepSound) { + await playHardcodedSound(stepSound, undefined, 0.6)// todo not sure why 0.6 + lastStepSound = Date.now() + } } } } diff --git a/src/styles.css b/src/styles.css index 139996e9..f924a0c6 100644 --- a/src/styles.css +++ b/src/styles.css @@ -1,4 +1,5 @@ @import url(./screens.css); +@import url('pixelarticons/fonts/pixelart-icons-font.css'); :root { --guiScaleFactor: 3; @@ -18,6 +19,10 @@ a { color: white; } +[hidden] { + display: none !important; +} + html { -webkit-tap-highlight-color: rgba(0, 0, 0, 0); height: 100vh; @@ -68,6 +73,7 @@ body { background: rgba(0, 0, 0, 0.3) !important; opacity: 0.7 !important; position: fixed !important; + bottom: 60px !important; } .dirt-bg { @@ -163,6 +169,9 @@ body { animation-timing-function: ease-in-out; animation-fill-mode: forwards; } +body::xr-overlay #viewer-canvas { + display: none; +} .full-svg svg { width: 100%; @@ -190,35 +199,3 @@ body { opacity: 1; } } - -@media only screen and (max-width: 971px) { - #ui-root { - transform: scale(2); - width: calc(100% / 2); - height: calc(100% / 2); - } -} - -@media only screen and (max-height: 670px) { - #ui-root { - transform: scale(2); - width: calc(100% / 2); - height: calc(100% / 2); - } -} - -@media only screen and (max-width: 590px) { - #ui-root { - transform: scale(1); - width: calc(100% / 1); - height: calc(100% / 1); - } -} - -@media only screen and (max-height: 430px) { - #ui-root { - transform: scale(1.5); - width: calc(100% / 1.5); - height: calc(100% / 1.5); - } -} diff --git a/src/supportedVersions.mjs b/src/supportedVersions.mjs index 49d94806..9ff75576 100644 --- a/src/supportedVersions.mjs +++ b/src/supportedVersions.mjs @@ -2,4 +2,4 @@ import { supportedVersions } from 'minecraft-data' const ignoredVersionsRegex = /(^0\.30c$)|w|-pre|-rc/ -export default supportedVersions.pc.filter(v => !ignoredVersionsRegex.test(v)) +export default supportedVersions.pc.filter(x => x !== '1.7').filter(v => !ignoredVersionsRegex.test(v)) diff --git a/src/topRightStats.ts b/src/topRightStats.ts index ac3898e5..78dcd9c8 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 diff --git a/src/vr.js b/src/vr.ts similarity index 81% rename from src/vr.js rename to src/vr.ts index 45c25b3f..af1e885a 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)) diff --git a/src/watchOptions.ts b/src/watchOptions.ts index 56fc9c2c..e379cb50 100644 --- a/src/watchOptions.ts +++ b/src/watchOptions.ts @@ -41,7 +41,7 @@ export const watchOptionsAfterViewerInit = () => { }) watchValue(options, o => { - viewer.entities.setVisible(o.renderEntities) + viewer.entities.setRendering(o.renderEntities) }) // viewer.world.mesherConfig.smoothLighting = options.smoothLighting @@ -57,4 +57,9 @@ export const watchOptionsAfterViewerInit = () => { customEvents.on('gameLoaded', () => { viewer.world.mesherConfig.enableLighting = !bot.supportFeature('blockStateId') || options.newVersionsLighting }) + + watchValue(options, o => { + if (!(viewer.world instanceof WorldRendererThree)) return + viewer.world.starField.enabled = o.starfieldRendering + }) } diff --git a/src/water.ts b/src/water.ts index 9f8ec557..d8b4b41c 100644 --- a/src/water.ts +++ b/src/water.ts @@ -19,7 +19,7 @@ customEvents.on('gameLoaded', () => { } bot.on('physicsTick', () => { // todo - const _inWater = bot.world.getBlock(bot.entity.position.offset(0, 1, 0)).name === 'water' + const _inWater = bot.world.getBlock(bot.entity.position.offset(0, 1, 0))?.name === 'water' if (_inWater !== inWater) { inWater = _inWater updateInWater() diff --git a/src/workerWorkaround.ts b/src/workerWorkaround.ts index 00419b8c..7fad22cf 100644 --- a/src/workerWorkaround.ts +++ b/src/workerWorkaround.ts @@ -1,2 +1,4 @@ +//@ts-nocheck +// eslint-disable-next-line no-global-assign global = globalThis globalThis.window = globalThis diff --git a/src/worldInteractions.ts b/src/worldInteractions.ts index 2e20de29..777200ca 100644 --- a/src/worldInteractions.ts +++ b/src/worldInteractions.ts @@ -14,9 +14,11 @@ import destroyStage9 from 'minecraft-assets/minecraft-assets/data/1.10/blocks/de import { Vec3 } from 'vec3' import { LineMaterial, Wireframe, LineSegmentsGeometry } from 'three-stdlib' -import { isGameActive } from './globalState' +import { hideCurrentModal, isGameActive, showModal } from './globalState' import { assertDefined } from './utils' import { options } from './optionsStorage' +import { itemBeingUsed } from './react/Crosshair' +import { isCypress } from './standaloneUtils' function getViewDirection (pitch, yaw) { const csPitch = Math.cos(pitch) @@ -67,11 +69,13 @@ class WorldInteraction { } const breakMaterial = new THREE.MeshBasicMaterial({ transparent: true, - blending: THREE.MultiplyBlending + blending: THREE.MultiplyBlending, + alphaTest: 0.5, }) this.blockBreakMesh = new THREE.Mesh(new THREE.BoxGeometry(1, 1, 1), breakMaterial) this.blockBreakMesh.visible = false this.blockBreakMesh.renderOrder = 999 + this.blockBreakMesh.name = 'blockBreakMesh' viewer.scene.add(this.blockBreakMesh) // Setup events @@ -81,16 +85,18 @@ class WorldInteraction { this.lastBlockPlaced = 4 // ticks since last placed document.addEventListener('mousedown', (e) => { - if (e.isTrusted && !document.pointerLockElement) return + if (e.isTrusted && !document.pointerLockElement && !isCypress()) return if (!isGameActive(true)) return this.buttons[e.button] = true const entity = getEntityCursor() - if (entity && e.button === 2) { - bot.attack(entity) - } else { - // bot + if (entity) { + if (e.button === 0) { // left click + bot.attack(entity) + } else if (e.button === 2) { // right click + void bot.activateEntity(entity) + } } }) document.addEventListener('blur', (e) => { @@ -133,6 +139,9 @@ class WorldInteraction { } this.lastDugBlock = null }) + bot.on('heldItemChanged' as any, () => { + itemBeingUsed.name = null + }) const upLineMaterial = () => { const inCreative = bot.game.gameMode === 'creative' @@ -191,17 +200,44 @@ class WorldInteraction { // Place / interact / activate if (this.buttons[2] && this.lastBlockPlaced >= 4) { - const activate = bot.heldItem && ['egg', 'fishing_rod', 'firework_rocket', - 'fire_charge', 'snowball', 'ender_pearl', 'experience_bottle', 'potion', - 'glass_bottle', 'bucket', 'water_bucket', 'lava_bucket', 'milk_bucket', - 'minecart', 'boat', 'tnt_minecart', 'chest_minecart', 'hopper_minecart', - 'command_block_minecart', 'armor_stand', 'lead', 'name_tag', - // - 'writable_book', 'written_book', 'compass', 'clock', 'filled_map', 'empty_map', 'map', - 'shears', 'carrot_on_a_stick', 'warped_fungus_on_a_stick', - 'spawn_egg', 'trident', 'crossbow', 'elytra', 'shield', 'turtle_helmet', - ].includes(bot.heldItem.name) - if (cursorBlock && !activate) { + const activatableItems = (itemName: string) => { + return ['egg', 'fishing_rod', 'firework_rocket', + 'fire_charge', 'snowball', 'ender_pearl', 'experience_bottle', 'potion', + 'glass_bottle', 'bucket', 'water_bucket', 'lava_bucket', 'milk_bucket', + 'minecart', 'boat', 'tnt_minecart', 'chest_minecart', 'hopper_minecart', + 'command_block_minecart', 'armor_stand', 'lead', 'name_tag', + // + 'writable_book', 'written_book', 'compass', 'clock', 'filled_map', 'empty_map', 'map', + 'shears', 'carrot_on_a_stick', 'warped_fungus_on_a_stick', + 'spawn_egg', 'trident', 'crossbow', 'elytra', 'shield', 'turtle_helmet', 'bow', 'crossbow', 'bucket_of_cod', + ...loadedData.foodsArray.map((f) => f.name), + ].includes(itemName) + } + const activate = bot.heldItem && activatableItems(bot.heldItem.name) + let stop = false + if (!bot.controlState.sneak) { + if (cursorBlock?.name === 'bed' || cursorBlock?.name.endsWith('_bed')) { + stop = true + showModal({ reactType: 'bed' }) + let cancelSleep = true + void bot.sleep(cursorBlock).catch((e) => { + if (cancelSleep) { + hideCurrentModal() + } + // if (e.message === 'bot is not sleeping') return + bot._client.emit('chat', { + message: JSON.stringify({ + text: e.message, + }) + }) + }) + setTimeout(() => { + cancelSleep = false + }) + } + } + // todo placing with offhand + if (cursorBlock && !activate && !stop) { const vecArray = [new Vec3(0, -1, 0), new Vec3(0, 1, 0), new Vec3(0, 0, -1), new Vec3(0, 0, 1), new Vec3(-1, 0, 0), new Vec3(1, 0, 0)] //@ts-expect-error const delta = cursorBlock.intersect.minus(cursorBlock.position) @@ -219,11 +255,22 @@ class WorldInteraction { bot.lookAt = oldLookAt }).catch(console.warn) } - } else { - bot.activateItem() // todo offhand + } else if (!stop) { + const offhand = activate ? false : activatableItems(bot.inventory.slots[45]?.name ?? '') + bot.activateItem(offhand) // todo offhand + itemBeingUsed.name = (offhand ? bot.inventory.slots[45]?.name : bot.heldItem?.name) ?? null + itemBeingUsed.hand = offhand ? 1 : 0 } this.lastBlockPlaced = 0 } + // stop using activated item (cancel) + if (itemBeingUsed.name && !this.buttons[2]) { + itemBeingUsed.name = null + // "only foods and bow can be deactivated" - not true, shields also can be deactivated and client always sends this + // if (bot.heldItem && (loadedData.foodsArray.map((f) => f.name).includes(bot.heldItem.name) || bot.heldItem.name === 'bow')) { + bot.deactivateItem() + // } + } // Stop break if ((!this.buttons[0] && this.lastButtons[0]) || cursorChanged) { diff --git a/src/yggdrasilReplacement.ts b/src/yggdrasilReplacement.ts new file mode 100644 index 00000000..e0cab6f0 --- /dev/null +++ b/src/yggdrasilReplacement.ts @@ -0,0 +1,27 @@ +export const server = ({ host: sessionServer }) => { + return { + async join (accessToken, sessionSelectedProfileId, serverId, sharedSecret, publicKey, cb) { + try { + const result = await fetch(`${sessionServer}`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + accessToken, + selectedProfile: sessionSelectedProfileId, + serverId, + sharedSecret, + publicKey, + }), + }) + if (!result.ok) { + throw new Error(`Request failed ${await result.text()}`) + } + cb(null) + } catch (err) { + cb(err) + } + } + } +} diff --git a/tsconfig.json b/tsconfig.json index f7827cc2..59424cbe 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -17,6 +17,7 @@ "skipLibCheck": true, "strictBindCallApply": true, "experimentalDecorators": true, + "strictBindCallApply": true, // this the only options that allows smooth transition from js to ts (by not dropping types from js files) // however might need to consider includeing *only needed libraries* instead of using this "maxNodeModuleJsDepth": 1,