diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000000000000000000000000000000000000..bf554f516201293b4a31818d365cc979225dd190 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,5 @@ +# 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/.gitattributes b/.gitattributes index a6344aac8c09253b3b630fb776ae94478aa0275b..0d8ec3ae2959dda4e51f361375844a2a2d87e5ef 100644 --- a/.gitattributes +++ b/.gitattributes @@ -33,3 +33,7 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text *.zip filter=lfs diff=lfs merge=lfs -text *.zst filter=lfs diff=lfs merge=lfs -text *tfevents* filter=lfs diff=lfs merge=lfs -text +assets/invsprite.png filter=lfs diff=lfs merge=lfs -text +extra-textures/background/panorama_5.png filter=lfs diff=lfs merge=lfs -text +extra-textures/loading.psd filter=lfs diff=lfs merge=lfs -text +screenshot.png filter=lfs diff=lfs merge=lfs -text diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..13e47cf60ad64e41182ccab6c8551e1872994bff --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +node_modules/ +package-lock.json +.vscode +public +.DS_Store +.idea/ +*.iml \ No newline at end of file diff --git a/.gitpod b/.gitpod new file mode 100644 index 0000000000000000000000000000000000000000..9ea6b0adb9c442e6446819dbf55fa28f34a8477f --- /dev/null +++ b/.gitpod @@ -0,0 +1,2 @@ +tasks: +- command: npm install diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000000000000000000000000000000000000..e1976198cbd14e5f2f25349df6d36f148d988094 --- /dev/null +++ b/.npmignore @@ -0,0 +1,3 @@ +node_modules/ +package-lock.json +.vscode \ No newline at end of file diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000000000000000000000000000000000000..094e38421d4085d75e1223c8c02d400bd687613c --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +package-lock=false diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..70d9829f7c32ade1f56cb84b78f0b34b68edc083 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,9 @@ +FROM node:14-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 +ENTRYPOINT ["npm", "run", "prod-start"] diff --git a/HISTORY.md b/HISTORY.md new file mode 100644 index 0000000000000000000000000000000000000000..62529bbe8bbac20b0bb703dba8e291b1305dc0b2 --- /dev/null +++ b/HISTORY.md @@ -0,0 +1,43 @@ +## History + +### 1.6.0 +* [bump pviewer](https://github.com/PrismarineJS/prismarine-web-client/commit/901242b33151ad70d651e18416bd67b9949cc117) (thanks @rom1504) +* [Remove incorrect signals + change server. (#361)](https://github.com/PrismarineJS/prismarine-web-client/commit/a243144a9802e569ef2ee31dce5379c2f2ba3951) (thanks @rom1504) +* [Create commands.yml](https://github.com/PrismarineJS/prismarine-web-client/commit/17548409c1fc881a899dcad0a67a7a1a0ecf9157) (thanks @rom1504) +* [avoid using @azure/msal-node (#358)](https://github.com/PrismarineJS/prismarine-web-client/commit/d441efaef504a22b31f7d5eca8af9a5591f22e32) (thanks @rom1504) +* [bump mineflayer](https://github.com/PrismarineJS/prismarine-web-client/commit/d4c0c0c6e610cb2d1f836acb800cfa38777fc50e) (thanks @rom1504) +* [Should show error screen on error (#350)](https://github.com/PrismarineJS/prismarine-web-client/commit/2fba863d662cb895339c3eda905a4f1edc8d5db6) (thanks @zardoy) +* [[ImgBot] Optimize images (#348)](https://github.com/PrismarineJS/prismarine-web-client/commit/856639d586f1647bccef7abffe20a218ab232ddf) (thanks @Rahib777-7) + +### 1.5.0 + +* Add BossBars @sverben (PrismarineJS#319) +* save all play screen options @Fury-101 (PrismarineJS#326) + +### 1.4.0 + +* so much + +### 1.3.0 + +* a lot + +### 1.2.0 + +* custom proxy selection (@Karang) +* loading screen (@AwesomestCode) +* better chat (@AwesomestCode) +* default random name (@rom1504) +* fix pixelated (@Karang) +* placing and breaking (@ShrimpyStuff) +* crosshairs (@AwesomestCode) + +### 1.1.0 + +* chat +* better font +* pitch fix + +### 1.0.1 + +* initial implementation diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..61ce9b1c4a5e7780c081b89732221cd85086239e --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 PrismarineJS + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README_PT.md b/README_PT.md new file mode 100644 index 0000000000000000000000000000000000000000..77c4fc523afa1109683827bafc99e6d84783408b --- /dev/null +++ b/README_PT.md @@ -0,0 +1,104 @@ +# prismarine-web-client +[![NPM version](https://img.shields.io/npm/v/prismarine-web-client.svg)](http://npmjs.com/package/prismarine-web-client) +[![Build Status](https://github.com/PrismarineJS/prismarine-web-client/workflows/CI/badge.svg)](https://github.com/PrismarineJS/prismarine-web-client/actions?query=workflow%3A%22CI%22) +[![Discord](https://img.shields.io/badge/chat-on%20discord-brightgreen.svg)](https://discord.gg/GsEFRM8) +[![Try it on gitpod](https://img.shields.io/badge/try-on%20gitpod-brightgreen.svg)](https://gitpod.io/#https://github.com/PrismarineJS/prismarine-web-client) + +| 🇺🇸 [English](README.md) | 🇷🇺 [Russian](README_RU.md) | 🇵🇹 [Portuguese](README_PT.md) | +| ----------------------- | -------------------------- | ---------------------------- | + +Um cliente de Minecraft a funcionar numa página web. **Demostração em https://prismarinejs.github.io/prismarine-web-client/** + +## Como funciona +prismarine-web-client executa mineflayer e prismarine-viewer no teu navegador, que se conecta por WebSocket a uma proxy +que traduz o conexão do WebSocket em TCP para poderes conectar-te a servidores normais do Minecraft. Prismarine-web-client é basiado em: +* [prismarine-viewer](https://github.com/PrismarineJS/prismarine-viewer) para renderizar o mundo +* [mineflayer](https://github.com/PrismarineJS/mineflayer) um API incrível do cliente de Minecraft + +Da uma olhada nestes módulos se quiseres entender mais sobre como isto funciona e poderes contribuir! + +## Captura de tela +![Captura de tela do prismarine-web-client em ação](screenshot.png) + +## Demostração ao vivo +Clica neste endereço para o abrires no navegador, sem instalação necessária: https://prismarinejs.github.io/prismarine-web-client/ + +*Testado no Chrome & Firefox para plataformas desktop.* + +## Uso +Para hospedá-lo por si próprio, execute estes comandos no bash: +```bash +$ npm install -g prismarine-web-client +$ prismarine-web-client +``` +Finalmente, abra `http://localhost:8080` no seu navigador. + +## Conteúdos + +* Mostra criaturas e os jogadores +* Mostra os blocos +* Movimento (podes mover-te, e também ver entidades a mover-se em tempo real) +* Colocar e destruir blocos + +## Planeamentos +* Containers (inventário, baús, etc.) +* Sons +* Mais interações no mundo (atacar entidades, etc.) +* Renderizar cosméticos (ciclo dia-noite, nevoeiro, etc.) + +## Desenvolvimentos + +Se estiveres a contribuir/fazer alterações, precisas intalá-lo de outra forma. + +Primeiro, clona o repositório. + +Depois, defina o seu diretório de trabalho para o do repositório. Por examplo: +```bash +$ cd ~/prismarine-web-client/ +``` + +Finalmente, execute + +```bash +$ npm install +$ npm start +``` + +Isto vai começar o express e webpack no modo de desenvolvimento; quando salvares um arquivo, o build vai ser executado de novo (demora 5s), +e podes atualizar a página para veres o novo resultado. + +Conecta em http://localhost:8080 no teu navegador. + +Poderás ter que desativar o auto salvar no teu IDE para evitar estar constantemente a reconstruir; see https://webpack.js.org/guides/development/#adjusting-your-text-editor. + +Para conferir a build de produção (vai demorar alguns minutos para terminar), podes executar `npm run build-start`. + +Se estiveres interessado em contribuir, podes dar uma vista de olhos nos projetos em https://github.com/PrismarineJS/prismarine-web-client/projects. + +Algumas variáveis estão expostas no objeto global ``window`` para depuração: +* ``bot`` +* ``viewer`` +* ``mcData`` +* ``worldView`` +* ``Vec3`` +* ``pathfinder`` +* ``debugMenu`` + +### Adicionar coisas no debugMenu + +debugMenu.customEntries['myKey'] = 'myValue' +delete debugMenu.customEntries['myKey'] + +### Alguns exemplos de depuração + +Na devtools do chrome: + +* `bot.chat('test')` permite usar o chat +* `bot.chat(JSON.stringify(Object.values(bot.players).map(({username, ping}) => ({username, ping}))))` display the ping of everyone +* `window.bot.entity.position.y += 5` saltar +* `bot.chat(JSON.stringify(bot.findBlock({matching:(block) => block.name==='diamond_ore', maxDistance:256}).position))` encontrar a posição de um bloco de diamante +* `bot.physics.stepHeight = 2` permite andar sobre os blocos +* `bot.physics.sprintSpeed = 5` andar mais rápido +* `bot.loadPlugin(pathfinder.pathfinder)` em seguida `bot.pathfinder.goto(new pathfinder.goals.GoalXZ(100, 100))` para ir para a posição 100, 100 + +Para mais ideas de depuração, leia o documento [mineflayer](https://github.com/PrismarineJS/mineflayer). diff --git a/README_RU.md b/README_RU.md new file mode 100644 index 0000000000000000000000000000000000000000..bd632468a978a0a5961a8aee21b39979c4cdee5f --- /dev/null +++ b/README_RU.md @@ -0,0 +1,105 @@ +# prismarine-web-client +[![NPM version](https://img.shields.io/npm/v/prismarine-web-client.svg)](http://npmjs.com/package/prismarine-web-client) +[![Build Status](https://github.com/PrismarineJS/prismarine-web-client/workflows/CI/badge.svg)](https://github.com/PrismarineJS/prismarine-web-client/actions?query=workflow%3A%22CI%22) +[![Discord](https://img.shields.io/badge/chat-on%20discord-brightgreen.svg)](https://discord.gg/GsEFRM8) +[![Try it on gitpod](https://img.shields.io/badge/try-on%20gitpod-brightgreen.svg)](https://gitpod.io/#https://github.com/PrismarineJS/prismarine-web-client) + +| 🇺🇸 [English](README.md) | 🇷🇺 [Russian](README_RU.md) | 🇵🇹 [Portuguese](README_PT.md) | +| ----------------------- | -------------------------- | ---------------------------- | + +Клиент Minecraft, запущенный на веб-странице. **Демонстрация на https://prismarinejs.github.io/prismarine-web-client/** + + +## Как это работает +prismarine-web-client запускает mineflayer и prismarine-viewer в вашем браузере, которые подключаются к прокси через WebSocket +который переводит соединение WebSocket в TCP для подключения к обычным серверам Minecraft. Prismarine-web-client основан на: +* [prismarine-viewer](https://github.com/PrismarineJS/prismarine-viewer) для рендера мира +* [mineflayer](https://github.com/PrismarineJS/mineflayer) для высокоуровневого API клиента minecraft + +Проверьте эти модули, если вы хотите больше понять, как это работает, и внести свой вклад! + +## Скриншот +![Screenshot of prismarine-web-client in action](screenshot.png) + +## Демонстация +Нажмите на эту ссылку, чтобы открыть ее в вашем браузере, установка не требуется: https://prismarinejs.github.io/prismarine-web-client/ + +*Протестировано в Chrome и Firefox для настольных платформ.* + +## Использование +Чтобы хостить его самостоятельно, выполните эти команды в bash: +```bash +$ npm install -g prismarine-web-client +$ prismarine-web-client +``` +Наконец, откройте `http://localhost:8080` в вашем браузере. + +## Функции + +* Показывание мобов и игроков +* Показывание блоков +* Передвижение (Вы можете двигаться, и вы можете видеть передвижение других сущностей) +* Установка и ломание блоков + +## Roadmap +* Контейнеры (Инвентарь, сундуки, и так далее.) +* Звуки +* Больше взаимодействий с Миром (Атаковать мобов и других игроков, и так далее.) +* Косметические особенности рендера (Цикл дня и ночи, fog, и так далее.) + +## Разработка + +Если вы ввносите изменения, вам нужно установить его по-другому. + +Во-первых, клонируйте репозиторий. + +Затем установите свой рабочий каталог в каталог репозитория. Например: +```bash +$ cd ~/prismarine-web-client/ +``` + +Наконец, запустите. + +```bash +$ npm install +$ npm start +``` + +Это запустит express и webpack в режиме разработки: всякий раз, когда вы сохраняете файл, сборка будет переделана (это займет 5 секунд), +и вы можете обновить страницу, чтобы получить новый результат. + +Для входа в Prismarine Web Client откройте http://localhost:8080 в вашем браузере. + +Если вы захотите отключить автоматическое сохранение в своем IDE, чтобы избежать постоянной пересборки Web Client'а, смотрите: https://webpack.js.org/guides/development/#adjusting-your-text-editor + +Чтобы проверить сборку Web Client'а (на сборку потребуется минута), вы можете запустить `npm run build-start` + +Если вы заинтересованы в участии, вы можете проверить проекты [тут](https://github.com/PrismarineJS/prismarine-web-client/projects) + +Некоторые переменные которые отображаются в окне для отладки: +* bot +* viewer +* mcData +* worldView +* Vec3 +* pathfinder +* debugMenu + +### Как добавить дополнительные элементы в меню отладки ? + +debugMenu.customEntries['myKey'] = 'myValue' +delete debugMenu.customEntries['myKey'] + +### Некоторые примеры отладки + +В инструментах chrome dev + +* `bot.chat('test')` позволяет вам использовать чат +* `bot.chat(JSON.stringify(Object.values(bot.players).map(({username, ping}) => ({username, ping}))))` показывает пинг всех игроков +* `window.bot.entity.position.y += 5` прыжок +* `bot.chat(JSON.stringify(bot.findBlock({matching:(block) => block.name==='diamond_ore', maxDistance:256}).position))` ищет позицию алмазной руды +* `bot.physics.stepHeight = 2` позволяет вам взбиратся по блокам +* `bot.physics.sprintSpeed = 5` более быстрый бег +* `bot.loadPlugin(pathfinder.pathfinder)` затем `bot.pathfinder.goto(new pathfinder.goals.GoalXZ(100, 100))` идет к координатам 100, 100 + +Для получения дополнительных идей по отладке прочитайте [mineflayer](https://github.com/PrismarineJS/mineflayer) документацию diff --git a/assets/click_stereo.ogg b/assets/click_stereo.ogg new file mode 100644 index 0000000000000000000000000000000000000000..b4889da63483512786c016c57463e04f2706540a Binary files /dev/null and b/assets/click_stereo.ogg differ diff --git a/assets/favicon.ico b/assets/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..27f48c13add5e5bf93921eff48f96a3b76af19bb Binary files /dev/null and b/assets/favicon.ico differ diff --git a/assets/favicon.png b/assets/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..0b46a03263713ccb0df3231f28c542761d0569c0 Binary files /dev/null and b/assets/favicon.png differ diff --git a/assets/favicon.svg b/assets/favicon.svg new file mode 100644 index 0000000000000000000000000000000000000000..6b9f69bf53db2be442b774c82afa2d5cd762f8ac --- /dev/null +++ b/assets/favicon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/invsprite.png b/assets/invsprite.png new file mode 100644 index 0000000000000000000000000000000000000000..062e95fc5696dd99bb303e725ab512f74b9fd527 --- /dev/null +++ b/assets/invsprite.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f1ac25723319eaf53490652f2255c7e82d3f06805946d558aca239ba843f76b6 +size 2845482 diff --git a/assets/manifest.json b/assets/manifest.json new file mode 100644 index 0000000000000000000000000000000000000000..5bb456583ab50383c9328ab76267136ff85be4f7 --- /dev/null +++ b/assets/manifest.json @@ -0,0 +1,13 @@ +{ + "name": "Prismarine Web Client", + "short_name": "Prismarine Web Client", + "scope": "/", + "start_url": "/", + "icons": [{ + "src": "favicon.png", + "sizes": "512x512" + }], + "background_color": "#349474", + "theme_color": "#349474", + "display": "standalone" +} \ No newline at end of file diff --git a/assets/minecraftia.woff b/assets/minecraftia.woff new file mode 100644 index 0000000000000000000000000000000000000000..457901322abaccaca0b07e27cb4d7952f16e5c06 Binary files /dev/null and b/assets/minecraftia.woff differ diff --git a/assets/mojangles.ttf b/assets/mojangles.ttf new file mode 100644 index 0000000000000000000000000000000000000000..e7b87e9f56cf3459a0942a144142ba2556a15361 Binary files /dev/null and b/assets/mojangles.ttf differ diff --git a/config.json b/config.json new file mode 100644 index 0000000000000000000000000000000000000000..3e943f8564cc76c8b1905739e3bce6620380341a --- /dev/null +++ b/config.json @@ -0,0 +1,7 @@ +{ + "defaultHost": "kaboom.pw", + "defaultHostPort": 25565, + "defaultProxy": "", + "defaultProxyPort": 0, + "defaultVersion": "1.18.2" +} diff --git a/extra-textures/background/panorama_0.png b/extra-textures/background/panorama_0.png new file mode 100644 index 0000000000000000000000000000000000000000..185a3dda389c79f9e7ffee4602e23c5c8fea2210 Binary files /dev/null and b/extra-textures/background/panorama_0.png differ diff --git a/extra-textures/background/panorama_1.png b/extra-textures/background/panorama_1.png new file mode 100644 index 0000000000000000000000000000000000000000..c5cbfd0d231ed7f7d16405e1b01a9b24f3962cd9 Binary files /dev/null and b/extra-textures/background/panorama_1.png differ diff --git a/extra-textures/background/panorama_2.png b/extra-textures/background/panorama_2.png new file mode 100644 index 0000000000000000000000000000000000000000..f8fb7f79f7f5b22be3209960c3574555b3606a21 Binary files /dev/null and b/extra-textures/background/panorama_2.png differ diff --git a/extra-textures/background/panorama_3.png b/extra-textures/background/panorama_3.png new file mode 100644 index 0000000000000000000000000000000000000000..c029be04998a6f3246b009dcbab816c8fdb017d0 Binary files /dev/null and b/extra-textures/background/panorama_3.png differ diff --git a/extra-textures/background/panorama_4.png b/extra-textures/background/panorama_4.png new file mode 100644 index 0000000000000000000000000000000000000000..bed2ae74a23573978716023e8238335306f71790 Binary files /dev/null and b/extra-textures/background/panorama_4.png differ diff --git a/extra-textures/background/panorama_5.png b/extra-textures/background/panorama_5.png new file mode 100644 index 0000000000000000000000000000000000000000..811b90794dacc471c411b48df980c38fc0b23b23 --- /dev/null +++ b/extra-textures/background/panorama_5.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7888ab245c81f35c2e6c16d10872512e4c6fa7ab47a8f76063ef688213802007 +size 1122177 diff --git a/extra-textures/edition.png b/extra-textures/edition.png new file mode 100644 index 0000000000000000000000000000000000000000..995cdb17aec44da1245de5edf462008259dd499f Binary files /dev/null and b/extra-textures/edition.png differ diff --git a/extra-textures/gui.png b/extra-textures/gui.png new file mode 100644 index 0000000000000000000000000000000000000000..d583b6b4a5b70ccc90ec26ed5ff903179961d053 Binary files /dev/null and b/extra-textures/gui.png differ diff --git a/extra-textures/loading.png b/extra-textures/loading.png new file mode 100644 index 0000000000000000000000000000000000000000..4f6a121accea52a2a81ad9beb0587f14bb4185db Binary files /dev/null and b/extra-textures/loading.png differ diff --git a/extra-textures/loading.psd b/extra-textures/loading.psd new file mode 100644 index 0000000000000000000000000000000000000000..88d44fcad7c0f32f75fdc8c1326e4525c902b597 --- /dev/null +++ b/extra-textures/loading.psd @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:35bb9df1485124bc896ba3d16033b7cb0dbb92b87c8aa98b0a8c3f1c0c1e051e +size 2636661 diff --git a/index.html b/index.html new file mode 100644 index 0000000000000000000000000000000000000000..e6b2218d625894ff8ad4a18364b2188996da310c --- /dev/null +++ b/index.html @@ -0,0 +1,30 @@ + + + + Prismarine Web Client + + + + + + + + + + + + + + + +
+ + + + + + + +
+ + diff --git a/index.js b/index.js new file mode 100644 index 0000000000000000000000000000000000000000..23a5054a5316a66c019672e20b47dbd4819ac878 --- /dev/null +++ b/index.js @@ -0,0 +1,466 @@ +/* global THREE */ +require('./lib/chat') + +require('./lib/menus/components/button') +require('./lib/menus/components/edit_box') +require('./lib/menus/components/slider') +require('./lib/menus/components/hotbar') +require('./lib/menus/components/health_bar') +require('./lib/menus/components/food_bar') +require('./lib/menus/components/breath_bar') +require('./lib/menus/components/debug_overlay') +require('./lib/menus/components/playerlist_overlay') +require('./lib/menus/components/bossbars_overlay') +require('./lib/menus/hud') +require('./lib/menus/play_screen') +require('./lib/menus/pause_screen') +require('./lib/menus/loading_screen') +require('./lib/menus/keybinds_screen') +require('./lib/menus/options_screen') +require('./lib/menus/title_screen') + +const net = require('net') +const Cursor = require('./lib/cursor') + +// Workaround for process.versions.node not existing in the browser +process.versions.node = '14.0.0' + +const mineflayer = require('mineflayer') +const { WorldView, Viewer } = require('prismarine-viewer/viewer') +const pathfinder = require('mineflayer-pathfinder') +const { Vec3 } = require('vec3') +global.THREE = require('three') +const { initVR } = require('./lib/vr') + +if ('serviceWorker' in navigator) { + window.addEventListener('load', () => { + navigator.serviceWorker.register('/service-worker.js').then(registration => { + console.log('SW registered: ', registration) + }).catch(registrationError => { + console.log('SW registration failed: ', registrationError) + }) + }) +} + +const maxPitch = 0.5 * Math.PI +const minPitch = -0.5 * Math.PI + +// Create three.js context, add to page +const renderer = new THREE.WebGLRenderer() +renderer.setPixelRatio(window.devicePixelRatio || 1) +renderer.setSize(window.innerWidth, window.innerHeight) +document.body.appendChild(renderer.domElement) + +// Create viewer +const viewer = new Viewer(renderer) + +// Menu panorama background +function addPanoramaCubeMap () { + let time = 0 + viewer.camera = new THREE.PerspectiveCamera(85, window.innerWidth / window.innerHeight, 0.05, 1000) + viewer.camera.updateProjectionMatrix() + viewer.camera.position.set(0, 0, 0) + viewer.camera.rotation.set(0, 0, 0) + const panorGeo = new THREE.BoxGeometry(1000, 1000, 1000) + + const loader = new THREE.TextureLoader() + const panorMaterials = [ + new THREE.MeshBasicMaterial({ map: loader.load('extra-textures/background/panorama_1.png'), transparent: true, side: THREE.DoubleSide }), // WS + new THREE.MeshBasicMaterial({ map: loader.load('extra-textures/background/panorama_3.png'), transparent: true, side: THREE.DoubleSide }), // ES + new THREE.MeshBasicMaterial({ map: loader.load('extra-textures/background/panorama_4.png'), transparent: true, side: THREE.DoubleSide }), // Up + new THREE.MeshBasicMaterial({ map: loader.load('extra-textures/background/panorama_5.png'), transparent: true, side: THREE.DoubleSide }), // Down + new THREE.MeshBasicMaterial({ map: loader.load('extra-textures/background/panorama_0.png'), transparent: true, side: THREE.DoubleSide }), // NS + new THREE.MeshBasicMaterial({ map: loader.load('extra-textures/background/panorama_2.png'), transparent: true, side: THREE.DoubleSide }) // SS + ] + + const panoramaBox = new THREE.Mesh(panorGeo, panorMaterials) + + panoramaBox.onBeforeRender = () => { + time += 0.01 + panoramaBox.rotation.y = Math.PI + time * 0.01 + panoramaBox.rotation.z = Math.sin(-time * 0.001) * 0.001 + } + + const group = new THREE.Object3D() + group.add(panoramaBox) + + const Entity = require('prismarine-viewer/viewer/lib/entity/Entity') + for (let i = 0; i < 42; i++) { + const m = new Entity('1.16.4', 'squid').mesh + m.position.set(Math.random() * 30 - 15, Math.random() * 20 - 10, Math.random() * 10 - 17) + m.rotation.set(0, Math.PI + Math.random(), -Math.PI / 4, 'ZYX') + const v = Math.random() * 0.01 + m.children[0].onBeforeRender = () => { + m.rotation.y += v + m.rotation.z = Math.cos(panoramaBox.rotation.y * 3) * Math.PI / 4 - Math.PI / 2 + } + group.add(m) + } + + viewer.scene.add(group) + return group +} + +const panoramaCubeMap = addPanoramaCubeMap() + +function removePanorama () { + viewer.camera = new THREE.PerspectiveCamera(document.getElementById('options-screen').fov, window.innerWidth / window.innerHeight, 0.1, 1000) + viewer.camera.updateProjectionMatrix() + viewer.scene.remove(panoramaCubeMap) +} + +let animate = () => { + window.requestAnimationFrame(animate) + viewer.update() + renderer.render(viewer.scene, viewer.camera) +} +animate() + +window.addEventListener('resize', () => { + viewer.camera.aspect = window.innerWidth / window.innerHeight + viewer.camera.updateProjectionMatrix() + renderer.setSize(window.innerWidth, window.innerHeight) +}) + +const showEl = (str) => { document.getElementById(str).style = 'display:block' } +async function main () { + const menu = document.getElementById('play-screen') + + menu.addEventListener('connect', e => { + const options = e.detail + menu.style = 'display: none;' + showEl('loading-screen') + removePanorama() + connect(options) + }) +} + +async function connect (options) { + const loadingScreen = document.getElementById('loading-screen') + + const hud = document.getElementById('hud') + const chat = hud.shadowRoot.querySelector('#chat') + const debugMenu = hud.shadowRoot.querySelector('#debug-overlay') + const optionsScrn = document.getElementById('options-screen') + const keyBindScrn = document.getElementById('keybinds-screen') + const gameMenu = document.getElementById('pause-screen') + + const viewDistance = optionsScrn.renderDistance + const hostprompt = options.server + const proxyprompt = options.proxy + const username = options.username + const password = options.password + + let host, port, proxy, proxyport + if (!hostprompt.includes(':')) { + host = hostprompt + port = 25565 + } else { + [host, port] = hostprompt.split(':') + port = parseInt(port, 10) + } + + if (!proxyprompt.includes(':')) { + proxy = proxyprompt + proxyport = undefined + } else { + [proxy, proxyport] = proxyprompt.split(':') + proxyport = parseInt(proxyport, 10) + } + console.log(`connecting to ${host} ${port} with ${username}`) + + if (proxy) { + console.log(`using proxy ${proxy} ${proxyport}`) + net.setProxy({ hostname: proxy, port: proxyport }) + } + + loadingScreen.status = 'Logging in' + + const errorAbortController = new AbortController() + window.addEventListener('unhandledrejection', (e) => { + handleError(e.reason) + }) + window.addEventListener('error', (e) => { + handleError(e.message) + }) + const bot = mineflayer.createBot({ + host, + port, + version: options.botVersion === '' ? false : options.botVersion, + username, + password, + viewDistance: 'tiny', + checkTimeoutInterval: 240 * 1000, + noPongTimeout: 240 * 1000, + closeTimeout: 240 * 1000 + }) + hud.preload(bot) + + const handleError = (err) => { + console.log('Encountered error!', err) + loadingScreen.status = `Error encountered. Error message: ${err}. Please reload the page` + loadingScreen.style = 'display: block;' + loadingScreen.hasError = true + } + bot.on('error', handleError) + + bot.on('kicked', (kickReason) => { + console.log('User was kicked!', kickReason) + loadingScreen.status = `The Minecraft server kicked you. Kick reason: ${kickReason}. Please reload the page to rejoin` + loadingScreen.style = 'display: block;' + loadingScreen.hasError = true + }) + + bot.on('end', (endReason) => { + console.log('disconnected for', endReason) + loadingScreen.status = `You have been disconnected from the server. End reason: ${endReason}. Please reload the page to rejoin` + loadingScreen.style = 'display: block;' + loadingScreen.hasError = true + }) + + bot.once('login', () => { + loadingScreen.status = 'Loading world' + }) + + bot.once('spawn', () => { + const mcData = require('minecraft-data')(bot.version) + + loadingScreen.status = 'Placing blocks (starting viewer)' + + console.log('bot spawned - starting viewer') + + const version = bot.version + + const center = bot.entity.position + + console.log(viewDistance) + const worldView = new WorldView(bot.world, viewDistance, center) + + gameMenu.init(renderer) + optionsScrn.isInsideWorld = true + optionsScrn.addEventListener('fov_changed', (e) => { + viewer.camera.fov = e.detail.fov + viewer.camera.updateProjectionMatrix() + }) + + viewer.setVersion(version) + + window.worldView = worldView + window.bot = bot + window.mcData = mcData + window.viewer = viewer + window.Vec3 = Vec3 + window.pathfinder = pathfinder + window.debugMenu = debugMenu + window.settings = optionsScrn + window.renderer = renderer + + initVR(bot, renderer, viewer) + + const cursor = new Cursor(viewer, renderer, bot) + animate = () => { + window.requestAnimationFrame(animate) + viewer.update() + cursor.update(bot) + debugMenu.cursorBlock = cursor.cursorBlock + renderer.render(viewer.scene, viewer.camera) + } + + // Link WorldView and Viewer + viewer.listen(worldView) + worldView.listenToBot(bot) + worldView.init(bot.entity.position) + + // Bot position callback + function botPosition () { + viewer.setFirstPersonCamera(bot.entity.position, bot.entity.yaw, bot.entity.pitch) + worldView.updatePosition(bot.entity.position) + } + bot.on('move', botPosition) + botPosition() + + loadingScreen.status = 'Setting callbacks' + + function moveCallback (e) { + bot.entity.pitch -= e.movementY * optionsScrn.mouseSensitivityY * 0.0001 + bot.entity.pitch = Math.max(minPitch, Math.min(maxPitch, bot.entity.pitch)) + bot.entity.yaw -= e.movementX * optionsScrn.mouseSensitivityX * 0.0001 + + viewer.setFirstPersonCamera(null, bot.entity.yaw, bot.entity.pitch) + } + + function changeCallback () { + if (document.pointerLockElement === renderer.domElement || + document.mozPointerLockElement === renderer.domElement || + document.webkitPointerLockElement === renderer.domElement) { + document.addEventListener('mousemove', moveCallback, false) + } else { + document.removeEventListener('mousemove', moveCallback, false) + } + } + + document.addEventListener('pointerlockchange', changeCallback, false) + document.addEventListener('mozpointerlockchange', changeCallback, false) + document.addEventListener('webkitpointerlockchange', changeCallback, false) + + let lastTouch + document.addEventListener('touchmove', (e) => { + window.scrollTo(0, 0) + e.preventDefault() + e.stopPropagation() + if (lastTouch !== undefined) { + moveCallback({ movementX: e.touches[0].pageX - lastTouch.pageX, movementY: e.touches[0].pageY - lastTouch.pageY }) + } + lastTouch = e.touches[0] + }, { passive: false }) + + document.addEventListener('touchend', (e) => { + lastTouch = undefined + }, { passive: false }) + + renderer.domElement.requestPointerLock = renderer.domElement.requestPointerLock || + renderer.domElement.mozRequestPointerLock || + renderer.domElement.webkitRequestPointerLock + document.addEventListener('mousedown', (e) => { + if (!chat.inChat && !gameMenu.inMenu) { + renderer.domElement.requestPointerLock() + } + }) + + document.addEventListener('contextmenu', (e) => e.preventDefault(), false) + + window.addEventListener('blur', (e) => { + bot.clearControlStates() + }, false) + + document.addEventListener('keydown', (e) => { + if (chat.inChat) return + if (gameMenu.inMenu) return + + keyBindScrn.keymaps.forEach(km => { + if (e.code === km.key) { + switch (km.defaultKey) { + case 'KeyQ': + if (bot.heldItem) bot.tossStack(bot.heldItem) + break + case 'ControlLeft': + bot.setControlState('sprint', true) + break + case 'ShiftLeft': + bot.setControlState('sneak', true) + break + case 'Space': + bot.setControlState('jump', true) + break + case 'KeyD': + bot.setControlState('left', true) + break + case 'KeyA': + bot.setControlState('right', true) + break + case 'KeyS': + bot.setControlState('back', true) + break + case 'KeyW': + bot.setControlState('forward', true) + break + } + } + }) + }, false) + + document.addEventListener('keyup', (e) => { + keyBindScrn.keymaps.forEach(km => { + if (e.code === km.key) { + switch (km.defaultKey) { + case 'ControlLeft': + bot.setControlState('sprint', false) + break + case 'ShiftLeft': + bot.setControlState('sneak', false) + break + case 'Space': + bot.setControlState('jump', false) + break + case 'KeyD': + bot.setControlState('left', false) + break + case 'KeyA': + bot.setControlState('right', false) + break + case 'KeyS': + bot.setControlState('back', false) + break + case 'KeyW': + bot.setControlState('forward', false) + break + } + } + }) + }, false) + + loadingScreen.status = 'Done!' + console.log(loadingScreen.status) // only do that because it's read in index.html and npm run fix complains. + + hud.init(renderer, bot, host) + hud.style.display = 'block' + + setTimeout(function () { + // game in playable state show errors in console instead + errorAbortController.abort() + if (loadingScreen.hasError) return + // remove loading screen, wait a second to make sure a frame has properly rendered + loadingScreen.style = 'display: none;' + }, 2500) + }) +} + +/** + * @param {URLSearchParams} params + */ +async function fromTheOutside (params, addr) { + const opts = {} + const dfltConfig = await (await window.fetch('config.json')).json() + + let server, port, proxy, proxyPort + + if (address.includes(':')) { + const s = address.split(':') + server = s[0] + port = Number(s[1]) || 25565 + } else { + server = address + port = Number(params.get('port')) || 25565 + } + + const proxyAddr = params.get('proxy') + if (proxyAddr) { + const s = proxyAddr.split(':') + proxy = s[0] + proxyPort = Number(s[1] ?? 'NaN') || 22 + } else { + proxy = dfltConfig.defaultProxy + proxyPort = !dfltConfig.defaultProxy && !dfltConfig.defaultProxyPort ? '' : dfltConfig.defaultProxyPort ?? 443 + } + + opts.server = `${server}:${port}` + opts.proxy = `${proxy}:${proxyPort}` + opts.username = params.get('username') ?? `pviewer${Math.floor(Math.random() * 1000)}` + opts.password = params.get('password') ?? '' + opts.botVersion = params.get('version') ?? false + + console.log(opts) + + showEl('loading-screen') + removePanorama() + connect(opts) +} + +const params = new URLSearchParams(window.location.search) +let address +if ((address = params.get('address'))) { + fromTheOutside(params, address) +} else { + showEl('title-screen') + main() +} diff --git a/lib/chat.js b/lib/chat.js new file mode 100644 index 0000000000000000000000000000000000000000..d584b1f56bfa4af30a5e640c2d61362bff4844e8 --- /dev/null +++ b/lib/chat.js @@ -0,0 +1,361 @@ +const { LitElement, html, css } = require('lit') +const { isMobile } = require('./menus/components/common') + +const styles = { + black: 'color:#000000', + dark_blue: 'color:#0000AA', + dark_green: 'color:#00AA00', + dark_aqua: 'color:#00AAAA', + dark_red: 'color:#AA0000', + dark_purple: 'color:#AA00AA', + gold: 'color:#FFAA00', + gray: 'color:#AAAAAA', + dark_gray: 'color:#555555', + blue: 'color:#5555FF', + green: 'color:#55FF55', + aqua: 'color:#55FFFF', + red: 'color:#FF5555', + light_purple: 'color:#FF55FF', + yellow: 'color:#FFFF55', + white: 'color:#FFFFFF', + bold: 'font-weight:900', + strikethrough: 'text-decoration:line-through', + underlined: 'text-decoration:underline', + italic: 'font-style:italic' +} + +function colorShadow (hex, dim = 0.25) { + const color = parseInt(hex.replace('#', ''), 16) + + const r = (color >> 16 & 0xFF) * dim | 0 + const g = (color >> 8 & 0xFF) * dim | 0 + const b = (color & 0xFF) * dim | 0 + + const f = (c) => ('00' + c.toString(16)).substr(-2) + return `#${f(r)}${f(g)}${f(b)}` +} + +class ChatBox extends LitElement { + static get styles () { + return css` + .chat-wrapper { + position: fixed; + z-index: 10; + } + + .chat-display-wrapper { + bottom: 40px; + padding: 4px; + padding-left: 0; + max-height: var(--chatHeight); + width: var(--chatWidth); + transform-origin: bottom left; + transform: scale(var(--chatScale)); + } + + .chat-input-wrapper { + bottom: 1px; + width: calc(100% - 3px); + position: absolute; + left: 1px; + box-sizing: border-box; + overflow: hidden; + background-color: rgba(0, 0, 0, 0); + } + + .input-mobile { + top: 1px; + } + + .display-mobile { + top: 40px; + } + + .chat { + overflow: hidden; + color: white; + font-size: 10px; + margin: 0px; + line-height: 100%; + text-shadow: 1px 1px 0px #3f3f3f; + font-family: mojangles, minecraft, monospace; + width: 100%; + max-height: var(--chatHeight); + } + + input[type=text], #chatinput { + background-color: rgba(0, 0, 0, 0.5); + border: 1px solid rgba(0, 0, 0, 0); + display: none; + outline: none; + } + + #chatinput:focus { + border-color: white; + } + + .chat-message { + display: block; + padding-left: 4px; + background-color: rgba(0, 0, 0, 0.5); + } + + .chat-message-fadeout { + opacity: 1; + transition: all 3s; + } + + .chat-message-fade { + opacity: 0; + } + + .chat-message-faded { + transition: none !important; + } + + .chat-message-chat-opened { + opacity: 1 !important; + transition: none !important; + } + ` + } + + render () { + return html` +
+
+
  • Welcome to prismarine-web-client! Chat appears here.
  • +
    +
    +
    +
    + +
    +
    + ` + } + + constructor () { + super() + this.chatHistoryPos = 0 + this.chatHistory = [] + } + + enableChat (isCommand) { + const chat = this.shadowRoot.querySelector('#chat') + const chatInput = this.shadowRoot.querySelector('#chatinput') + + this.shadowRoot.querySelector('#chat-wrapper2').classList.toggle('input-mobile', isMobile()) + this.shadowRoot.querySelector('#chat-wrapper').classList.toggle('display-mobile', isMobile()) + + // Set inChat value + this.inChat = true + // Exit the pointer lock + document.exitPointerLock() + // Show chat input + chatInput.style.display = 'block' + // Show extended chat history + chat.style.maxHeight = 'var(--chatHeight)' + chat.scrollTop = chat.scrollHeight // Stay bottom of the list + if (isCommand) { // handle commands + chatInput.value = '/' + } + // Focus element + chatInput.focus() + this.chatHistoryPos = this.chatHistory.length + document.querySelector('#hud').shadowRoot.querySelector('#chat').shadowRoot.querySelectorAll('.chat-message').forEach(e => e.classList.add('chat-message-chat-opened')) + } + + /** + * @param {globalThis.THREE.Renderer} client + * @param {import('minecraft-protocol').Client} renderer + */ + init (client, renderer) { + this.inChat = false + const chat = this.shadowRoot.querySelector('#chat') + const gameMenu = document.getElementById('pause-screen') + const chatInput = this.shadowRoot.querySelector('#chatinput') + + renderer.domElement.requestPointerLock = renderer.domElement.requestPointerLock || + renderer.domElement.mozRequestPointerLock || + renderer.domElement.webkitRequestPointerLock + + // Show chat + chat.style.display = 'block' + + // Esc event - Doesnt work with onkeypress?! - keypressed is deprecated uk + document.addEventListener('keydown', e => { + if (gameMenu.inMenu) return + if (!this.inChat) return + e = e || window.event + if (e.code === 'Escape') { + disableChat() + } else if (e.code === 'ArrowUp') { + if (this.chatHistoryPos === 0) return + chatInput.value = this.chatHistory[--this.chatHistoryPos] !== undefined ? this.chatHistory[this.chatHistoryPos] : '' + setTimeout(() => { chatInput.setSelectionRange(-1, -1) }, 0) + } else if (e.code === 'ArrowDown') { + if (this.chatHistoryPos === this.chatHistory.length) return + chatInput.value = this.chatHistory[++this.chatHistoryPos] !== undefined ? this.chatHistory[this.chatHistoryPos] : '' + setTimeout(() => { chatInput.setSelectionRange(-1, -1) }, 0) + } + }) + + const keyBindScrn = document.getElementById('keybinds-screen') + + // Chat events + document.addEventListener('keypress', e => { + if (gameMenu.inMenu) return + e = e || window.event + if (this.inChat === false) { + keyBindScrn.keymaps.forEach(km => { + if (e.code === km.key) { + switch (km.defaultKey) { + case 'KeyT': + setTimeout(() => this.enableChat(false), 0) + break + case 'Slash': + setTimeout(() => this.enableChat(true), 0) + break + } + } + }) + + return false + } + + if (!this.inChat) return + + e.stopPropagation() + + if (e.code === 'Enter') { + this.chatHistory.push(chatInput.value) + client.write('chat', { message: chatInput.value }) + disableChat() + } + }) + + const disableChat = () => { + this.inChat = false + hideChat() + renderer.domElement.requestPointerLock() + } + + const hideChat = () => { + // Clear chat input + chatInput.value = '' + // Unfocus it + chatInput.blur() + // Hide it + chatInput.style.display = 'none' + // Hide extended chat history + chat.style.maxHeight = 'var(--chatHeight)' + chat.scrollTop = chat.scrollHeight // Stay bottom of the list + document.querySelector('#hud').shadowRoot.querySelector('#chat').shadowRoot.querySelectorAll('.chat-message').forEach(e => e.classList.remove('chat-message-chat-opened')) + } + + client.on('chat', (packet) => { + // Reading of chat message + const fullmessage = JSON.parse(packet.message.toString()) + const msglist = [] + + const colorF = (color) => { + return color.trim().startsWith('#') ? `color:${color}` : styles[color] ?? undefined + } + + const readMsg = (msglist, msg) => { + const styles = { + color: msg.color, + bold: !!msg.bold, + italic: !!msg.italic, + underlined: !!msg.underlined, + strikethrough: !!msg.strikethrough, + obfuscated: !!msg.obfuscated + } + + if (msg.text) { + msglist.push({ + text: msg.text, + ...styles + }) + } + + if (msg.translate) { + const tText = window.mcData.language[msg.translate] ?? msg.translate + + if (msg.with) { + const splited = tText.split(/%s|%\d+\$s/g) + + let i = 0 + splited.forEach((spl, j, arr) => { + msglist.push({ text: spl, ...styles }) + + if (j + 1 < arr.length) { + if (msg.with[i]) { + if (typeof msg.with[i] === 'string') { + readMsg(msglist, { + ...styles, + text: msg.with[i] + }) + } else { + readMsg(msglist, { + ...styles, + ...msg.with[i] + }) + } + } + i++ + } + }) + } else { + msglist.push({ + text: tText, + ...styles + }) + } + } + + if (msg.extra) { + msg.extra.forEach(ex => { + readMsg(msglist, { ...styles, ...ex }) + }) + } + } + + readMsg(msglist, fullmessage) + + const li = document.createElement('li') + msglist.forEach(msg => { + const span = document.createElement('span') + span.appendChild(document.createTextNode(msg.text)) + span.setAttribute( + 'style', + `${msg.color ? colorF(msg.color.toLowerCase()) + `; text-shadow: 1px 1px 0px ${colorShadow(colorF(msg.color.toLowerCase()).replace('color:', ''))}` : styles.white}; ${ + msg.bold ? styles.bold + ';' : '' + }${msg.italic ? styles.italic + ';' : ''}${ + msg.strikethrough ? styles.strikethrough + ';' : '' + }${msg.underlined ? styles.underlined + ';' : ''}` + ) + li.appendChild(span) + }) + chat.appendChild(li) + chat.scrollTop = chat.scrollHeight // Stay bottom of the list + // fading + li.classList.add('chat-message') + if (this.inChat) { + li.classList.add('chat-message-chat-opened') + } + setTimeout(() => { + li.classList.add('chat-message-fadeout') + li.classList.add('chat-message-fade') + setTimeout(() => { + li.classList.add('chat-message-faded') + }, 3000) + }, 5000) + }) + + hideChat() + } +} + +window.customElements.define('chat-box', ChatBox) diff --git a/lib/cursor.js b/lib/cursor.js new file mode 100644 index 0000000000000000000000000000000000000000..f9ee6c622c94836d0615e5509425befd69de8d54 --- /dev/null +++ b/lib/cursor.js @@ -0,0 +1,143 @@ +/* global THREE performance */ + +const { Vec3 } = require('vec3') + +function getViewDirection (pitch, yaw) { + const csPitch = Math.cos(pitch) + const snPitch = Math.sin(pitch) + const csYaw = Math.cos(yaw) + const snYaw = Math.sin(yaw) + return new Vec3(-snYaw * csPitch, snPitch, -csYaw * csPitch) +} + +class Cursor { + constructor (viewer, renderer, bot) { + // Init state + this.buttons = [false, false, false] + this.lastButtons = [false, false, false] + this.breakStartTime = 0 + this.cursorBlock = null + + // Setup graphics + const blockGeometry = new THREE.BoxGeometry(1.001, 1.001, 1.001) + this.cursorMesh = new THREE.LineSegments(new THREE.EdgesGeometry(blockGeometry), new THREE.LineBasicMaterial({ color: 0 })) + this.cursorMesh.visible = false + viewer.scene.add(this.cursorMesh) + + const loader = new THREE.TextureLoader() + this.breakTextures = [] + for (let i = 0; i < 10; i++) { + const texture = loader.load('textures/' + viewer.version + '/blocks/destroy_stage_' + i + '.png') + texture.magFilter = THREE.NearestFilter + texture.minFilter = THREE.NearestFilter + this.breakTextures.push(texture) + } + const breakMaterial = new THREE.MeshBasicMaterial({ + transparent: true, + blending: THREE.MultiplyBlending + }) + this.blockBreakMesh = new THREE.Mesh(blockGeometry, breakMaterial) + this.blockBreakMesh.visible = false + this.blockBreakMesh.renderOrder = 999 + viewer.scene.add(this.blockBreakMesh) + + // Setup events + document.addEventListener('mouseup', (e) => { + this.buttons[e.button] = false + }) + + document.addEventListener('mousedown', (e) => { + if (document.pointerLockElement !== renderer.domElement) return + this.buttons[e.button] = true + + const entity = bot.nearestEntity((e) => { + if (e.position.distanceTo(bot.entity.position) <= (bot.player.gamemode === 1 ? 5 : 3)) { + const dir = getViewDirection(bot.entity.pitch, bot.entity.yaw) + const { width, height } = e + const { x: eX, y: eY, z: eZ } = e.position + const { x: bX, y: bY, z: bZ } = bot.entity.position + const box = new THREE.Box3( + new THREE.Vector3(eX - width / 2, eY, eZ - width / 2), + new THREE.Vector3(eX + width / 2, eY + height, eZ + width / 2) + ) + + const r = new THREE.Raycaster( + new THREE.Vector3(bX, bY + 1.52, bZ), + new THREE.Vector3(dir.x, dir.y, dir.z) + ) + const int = r.ray.intersectBox(box, new THREE.Vector3(eX, eY, eZ)) + return int !== null + } + + return false + }) + + if (entity) { + bot.attack(entity) + } + }) + this.lastPlaced = 4 // ticks since last placed + bot.on('physicsTick', () => { if (this.lastPlaced < 4) this.lastPlaced++ }) + } + + update (bot) { + let cursorBlock = bot.blockAtCursor(6) + if (!bot.canDigBlock(cursorBlock)) cursorBlock = null + + let cursorChanged = !cursorBlock !== !this.cursorBlock + if (cursorBlock && this.cursorBlock) { + cursorChanged = !cursorBlock.position.equals(this.cursorBlock.position) + } + + // Place + if (cursorBlock && this.buttons[2] && (!this.lastButtons[2] || cursorChanged) && this.lastPlaced >= 4) { + 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)] + const delta = cursorBlock.intersect.minus(cursorBlock.position) + bot._placeBlockWithOptions(cursorBlock, vecArray[cursorBlock.face], { delta, forceLook: 'ignore' }) + bot.lastPlaced = 0 + } + + // Start break + if (cursorBlock && this.buttons[0] && (!this.lastButtons[0] || cursorChanged)) { + this.breakStartTime = performance.now() + try { + bot.dig(cursorBlock, 'ignore') + } catch (e) {} // we don't care if its aborted + } + + // Stop break + if (!this.buttons[0] && this.lastButtons[0]) { + try { + bot.stopDigging() // this shouldnt throw anything... + } catch (e) {} // to be reworked in mineflayer, then remove the try here + } + + // Show break animation + if (cursorBlock && this.buttons[0]) { + const elapsed = performance.now() - this.breakStartTime + const time = bot.digTime(cursorBlock) + const state = Math.floor((elapsed / time) * 10) + this.blockBreakMesh.position.set(cursorBlock.position.x + 0.5, cursorBlock.position.y + 0.5, cursorBlock.position.z + 0.5) + this.blockBreakMesh.material.map = this.breakTextures[state] + this.blockBreakMesh.visible = true + } else { + this.blockBreakMesh.visible = false + } + + // Show cursor + if (!cursorBlock) { + this.cursorMesh.visible = false + } else { + this.cursorMesh.visible = true + this.cursorMesh.position.set(cursorBlock.position.x + 0.5, cursorBlock.position.y + 0.5, cursorBlock.position.z + 0.5) + } + + // Update state + this.cursorBlock = cursorBlock + this.lastButtons[0] = this.buttons[0] + this.lastButtons[1] = this.buttons[1] + this.lastButtons[2] = this.buttons[2] + } +} + +module.exports = Cursor diff --git a/lib/dns.js b/lib/dns.js new file mode 100644 index 0000000000000000000000000000000000000000..2f1dba6668746197dc1ae986eded4d9f80b7d0b8 --- /dev/null +++ b/lib/dns.js @@ -0,0 +1,39 @@ +/* global XMLHttpRequest */ + +// Custom DNS resolver made by SiebeDW. Powered by google dns. +// Supported: SRV (not all errors support) +module.exports.resolveSrv = function (hostname, callback) { + const Http = new XMLHttpRequest() + const url = `https://dns.google.com/resolve?name=${hostname}&type=SRV` + Http.open('GET', url) + Http.responseType = 'json' + Http.send() + + Http.onload = function () { + const response = Http.response + if (response.Status === 3) { + const err = new Error('querySrv ENOTFOUND') + err.code = 'ENOTFOUND' + callback(err) + return + } + if (!response.Answer || response.Answer.length < 1) { + const err = new Error('querySrv ENODATA') + err.code = 'ENODATA' + callback(err) + return + } + const willreturn = [] + response.Answer.forEach(function (object) { + const data = object.data.split(' ') + willreturn.push({ + priority: data[0], + weight: data[1], + port: data[2], + name: data[3] + }) + }) + console.log(willreturn) + callback(null, willreturn) + } +} diff --git a/lib/invsprite.json b/lib/invsprite.json new file mode 100644 index 0000000000000000000000000000000000000000..e734e3c929354620498ecf93b8872c0a984cbb61 --- /dev/null +++ b/lib/invsprite.json @@ -0,0 +1,4812 @@ +{ + "air": { + "name": "Air", + "x": 832, + "y": 2336 + }, + "stone": { + "name": "Stone", + "x": 224, + "y": 992 + }, + "granite": { + "name": "Granite", + "x": 96, + "y": 3360 + }, + "polished_granite": { + "name": "Polished Granite", + "x": 384, + "y": 896 + }, + "diorite": { + "name": "Diorite", + "x": 64, + "y": 3360 + }, + "polished_diorite": { + "name": "Polished Diorite", + "x": 352, + "y": 896 + }, + "andesite": { + "name": "Andesite", + "x": 832, + "y": 3328 + }, + "polished_andesite": { + "name": "Polished Andesite", + "x": 320, + "y": 896 + }, + "grass_block": { + "name": "Grass Block", + "x": 640, + "y": 960 + }, + "dirt": { + "name": "Dirt", + "x": 576, + "y": 960 + }, + "coarse_dirt": { + "name": "Coarse Dirt", + "x": 416, + "y": 960 + }, + "podzol": { + "name": "Podzol", + "x": 352, + "y": 3360 + }, + "crimson_nylium": { + "name": "Crimson Nylium", + "x": 640, + "y": 128 + }, + "warped_nylium": { + "name": "Warped Nylium", + "x": 768, + "y": 128 + }, + "cobblestone": { + "name": "Cobblestone", + "x": 960, + "y": 3328 + }, + "oak_planks": { + "name": "Oak Planks", + "x": 192, + "y": 896 + }, + "spruce_planks": { + "name": "Spruce Planks", + "x": 0, + "y": 928 + }, + "birch_planks": { + "name": "Birch Planks", + "x": 832, + "y": 832 + }, + "jungle_planks": { + "name": "Jungle Planks", + "x": 992, + "y": 864 + }, + "acacia_planks": { + "name": "Acacia Planks", + "x": 672, + "y": 832 + }, + "dark_oak_planks": { + "name": "Dark Oak Planks", + "x": 512, + "y": 864 + }, + "crimson_planks": { + "name": "Crimson Planks", + "x": 544, + "y": 128 + }, + "warped_planks": { + "name": "Warped Planks", + "x": 576, + "y": 128 + }, + "oak_sapling": { + "name": "Oak Sapling", + "x": 192, + "y": 3424 + }, + "spruce_sapling": { + "name": "Spruce Sapling", + "x": 608, + "y": 3424 + }, + "birch_sapling": { + "name": "Birch Sapling", + "x": 416, + "y": 3392 + }, + "jungle_sapling": { + "name": "Jungle Sapling", + "x": 960, + "y": 3392 + }, + "acacia_sapling": { + "name": "Acacia Sapling", + "x": 224, + "y": 3392 + }, + "dark_oak_sapling": { + "name": "Dark Oak Sapling", + "x": 768, + "y": 3392 + }, + "bedrock": { + "name": "Bedrock", + "x": 320, + "y": 960 + }, + "sand": { + "name": "Sand", + "x": 480, + "y": 3360 + }, + "red_sand": { + "name": "Red Sand", + "x": 416, + "y": 3360 + }, + "gravel": { + "name": "Gravel", + "x": 128, + "y": 3360 + }, + "gold_ore": { + "name": "Gold Ore", + "x": 32, + "y": 3392 + }, + "iron_ore": { + "name": "Iron Ore", + "x": 64, + "y": 3392 + }, + "coal_ore": { + "name": "Coal Ore", + "x": 960, + "y": 3360 + }, + "nether_gold_ore": { + "name": "Nether Gold Ore", + "x": 288, + "y": 192 + }, + "oak_log": { + "name": "Oak Log", + "x": 160, + "y": 3424 + }, + "spruce_log": { + "name": "Spruce Log", + "x": 576, + "y": 3424 + }, + "birch_log": { + "name": "Birch Log", + "x": 384, + "y": 3392 + }, + "jungle_log": { + "name": "Jungle Log", + "x": 928, + "y": 3392 + }, + "acacia_log": { + "name": "Acacia Log", + "x": 192, + "y": 3392 + }, + "dark_oak_log": { + "name": "Dark Oak Log", + "x": 736, + "y": 3392 + }, + "crimson_stem": { + "name": "Crimson Stem", + "x": 672, + "y": 128 + }, + "warped_stem": { + "name": "Warped Stem", + "x": 800, + "y": 128 + }, + "stripped_oak_log": { + "name": "Stripped Oak Log", + "x": 288, + "y": 1664 + }, + "stripped_spruce_log": { + "name": "Stripped Spruce Log", + "x": 320, + "y": 1664 + }, + "stripped_birch_log": { + "name": "Stripped Birch Log", + "x": 192, + "y": 1664 + }, + "stripped_jungle_log": { + "name": "Stripped Jungle Log", + "x": 256, + "y": 1664 + }, + "stripped_acacia_log": { + "name": "Stripped Acacia Log", + "x": 160, + "y": 1664 + }, + "stripped_dark_oak_log": { + "name": "Stripped Dark Oak Log", + "x": 224, + "y": 1664 + }, + "stripped_crimson_stem": { + "name": "Stripped Crimson Stem", + "x": 448, + "y": 160 + }, + "stripped_warped_stem": { + "name": "Stripped Warped Stem", + "x": 480, + "y": 160 + }, + "stripped_oak_wood": { + "name": "Stripped Oak Wood", + "x": 576, + "y": 2080 + }, + "stripped_spruce_wood": { + "name": "Stripped Spruce Wood", + "x": 608, + "y": 2080 + }, + "stripped_birch_wood": { + "name": "Stripped Birch Wood", + "x": 640, + "y": 2080 + }, + "stripped_jungle_wood": { + "name": "Stripped Jungle Wood", + "x": 672, + "y": 2080 + }, + "stripped_acacia_wood": { + "name": "Stripped Acacia Wood", + "x": 704, + "y": 2080 + }, + "stripped_dark_oak_wood": { + "name": "Stripped Dark Oak Wood", + "x": 736, + "y": 2080 + }, + "stripped_crimson_hyphae": { + "name": "Stripped Crimson Hyphae", + "x": 224, + "y": 192 + }, + "stripped_warped_hyphae": { + "name": "Stripped Warped Hyphae", + "x": 256, + "y": 192 + }, + "oak_wood": { + "name": "Oak Wood", + "x": 704, + "y": 3264 + }, + "spruce_wood": { + "name": "Spruce Wood", + "x": 736, + "y": 3296 + }, + "birch_wood": { + "name": "Birch Wood", + "x": 0, + "y": 3232 + }, + "jungle_wood": { + "name": "Jungle Wood", + "x": 192, + "y": 3264 + }, + "acacia_wood": { + "name": "Acacia Wood", + "x": 768, + "y": 3200 + }, + "dark_oak_wood": { + "name": "Dark Oak Wood", + "x": 704, + "y": 3232 + }, + "crimson_hyphae": { + "name": "Crimson Hyphae", + "x": 160, + "y": 192 + }, + "warped_hyphae": { + "name": "Warped Hyphae", + "x": 192, + "y": 192 + }, + "oak_leaves": { + "name": "Oak Leaves", + "x": 128, + "y": 3424 + }, + "spruce_leaves": { + "name": "Spruce Leaves", + "x": 544, + "y": 3424 + }, + "birch_leaves": { + "name": "Birch Leaves", + "x": 352, + "y": 3392 + }, + "jungle_leaves": { + "name": "Jungle Leaves", + "x": 896, + "y": 3392 + }, + "acacia_leaves": { + "name": "Acacia Leaves", + "x": 160, + "y": 3392 + }, + "dark_oak_leaves": { + "name": "Dark Oak Leaves", + "x": 704, + "y": 3392 + }, + "sponge": { + "name": "Sponge", + "x": 896, + "y": 3584 + }, + "wet_sponge": { + "name": "Wet Sponge", + "x": 0, + "y": 3616 + }, + "glass": { + "name": "Glass", + "x": 960, + "y": 3232 + }, + "lapis_ore": { + "name": "Lapis Lazuli Ore", + "x": 96, + "y": 3392 + }, + "lapis_block": { + "name": "Lapis Lazuli Block", + "x": 256, + "y": 3264 + }, + "dispenser": { + "name": "Dispenser", + "x": 576, + "y": 3584 + }, + "sandstone": { + "name": "Sandstone", + "x": 512, + "y": 3360 + }, + "chiseled_sandstone": { + "name": "Chiseled Sandstone", + "x": 384, + "y": 3232 + }, + "cut_sandstone": { + "name": "Cut Sandstone", + "x": 544, + "y": 3232 + }, + "note_block": { + "name": "Note Block", + "x": 832, + "y": 3584 + }, + "powered_rail": { + "name": "Powered Rail", + "x": 448, + "y": 3328 + }, + "detector_rail": { + "name": "Detector Rail", + "x": 160, + "y": 3328 + }, + "sticky_piston": { + "name": "Sticky Piston", + "x": 704, + "y": 3328 + }, + "cobweb": { + "name": "Cobweb", + "x": 992, + "y": 3328 + }, + "grass": { + "name": "Grass", + "x": 864, + "y": 3392 + }, + "fern": { + "name": "Fern", + "x": 832, + "y": 3392 + }, + "dead_bush": { + "name": "Dead Bush", + "x": 800, + "y": 3392 + }, + "seagrass": { + "name": "Seagrass", + "x": 384, + "y": 2016 + }, + "sea_pickle": { + "name": "Sea Pickle", + "x": 224, + "y": 1728 + }, + "piston": { + "name": "Piston", + "x": 416, + "y": 3328 + }, + "white_wool": { + "name": "White Wool", + "x": 0, + "y": 1536 + }, + "orange_wool": { + "name": "Orange Wool", + "x": 896, + "y": 1504 + }, + "magenta_wool": { + "name": "Magenta Wool", + "x": 864, + "y": 1504 + }, + "light_blue_wool": { + "name": "Light Blue Wool", + "x": 768, + "y": 1504 + }, + "yellow_wool": { + "name": "Yellow Wool", + "x": 32, + "y": 1536 + }, + "lime_wool": { + "name": "Lime Wool", + "x": 832, + "y": 1504 + }, + "pink_wool": { + "name": "Pink Wool", + "x": 928, + "y": 1504 + }, + "gray_wool": { + "name": "Gray Wool", + "x": 704, + "y": 1504 + }, + "light_gray_wool": { + "name": "Light Gray Wool", + "x": 800, + "y": 1504 + }, + "cyan_wool": { + "name": "Cyan Wool", + "x": 672, + "y": 1504 + }, + "purple_wool": { + "name": "Purple Wool", + "x": 960, + "y": 1504 + }, + "blue_wool": { + "name": "Blue Wool", + "x": 608, + "y": 1504 + }, + "brown_wool": { + "name": "Brown Wool", + "x": 640, + "y": 1504 + }, + "green_wool": { + "name": "Green Wool", + "x": 736, + "y": 1504 + }, + "red_wool": { + "name": "Red Wool", + "x": 992, + "y": 1504 + }, + "black_wool": { + "name": "Black Wool", + "x": 576, + "y": 1504 + }, + "dandelion": { + "name": "Dandelion", + "x": 672, + "y": 3392 + }, + "poppy": { + "name": "Poppy", + "x": 352, + "y": 3424 + }, + "blue_orchid": { + "name": "Blue Orchid", + "x": 448, + "y": 3392 + }, + "allium": { + "name": "Allium", + "x": 256, + "y": 3392 + }, + "azure_bluet": { + "name": "Azure Bluet", + "x": 288, + "y": 3392 + }, + "red_tulip": { + "name": "Red Tulip", + "x": 480, + "y": 3424 + }, + "orange_tulip": { + "name": "Orange Tulip", + "x": 224, + "y": 3424 + }, + "white_tulip": { + "name": "White Tulip", + "x": 768, + "y": 3424 + }, + "pink_tulip": { + "name": "Pink Tulip", + "x": 320, + "y": 3424 + }, + "oxeye_daisy": { + "name": "Oxeye Daisy", + "x": 256, + "y": 3424 + }, + "cornflower": { + "name": "Cornflower", + "x": 640, + "y": 2368 + }, + "lily_of_the_valley": { + "name": "Lily of the Valley", + "x": 672, + "y": 2368 + }, + "wither_rose": { + "name": "Wither Rose", + "x": 608, + "y": 2368 + }, + "brown_mushroom": { + "name": "Brown Mushroom", + "x": 512, + "y": 3392 + }, + "red_mushroom": { + "name": "Red Mushroom", + "x": 448, + "y": 3424 + }, + "crimson_fungus": { + "name": "Crimson Fungus", + "x": 928, + "y": 128 + }, + "warped_fungus": { + "name": "Warped Fungus", + "x": 960, + "y": 128 + }, + "crimson_roots": { + "name": "Crimson Roots", + "x": 928, + "y": 160 + }, + "warped_roots": { + "name": "Warped Roots", + "x": 992, + "y": 160 + }, + "nether_sprouts": { + "name": "Nether Sprouts", + "x": 928, + "y": 64 + }, + "weeping_vines": { + "name": "Weeping Vines", + "x": 992, + "y": 128 + }, + "twisting_vines": { + "name": "Twisting Vines", + "x": 320, + "y": 192 + }, + "sugar_cane": { + "name": "Sugar Cane", + "x": 640, + "y": 3424 + }, + "kelp": { + "name": "Kelp", + "x": 576, + "y": 1664 + }, + "bamboo": { + "name": "Bamboo", + "x": 128, + "y": 2368 + }, + "gold_block": { + "name": "Block of Gold", + "x": 128, + "y": 3232 + }, + "iron_block": { + "name": "Block of Iron", + "x": 160, + "y": 3232 + }, + "oak_slab": { + "name": "Oak Slab", + "x": 640, + "y": 3264 + }, + "spruce_slab": { + "name": "Spruce Slab", + "x": 672, + "y": 3296 + }, + "birch_slab": { + "name": "Birch Slab", + "x": 960, + "y": 3200 + }, + "jungle_slab": { + "name": "Jungle Slab", + "x": 128, + "y": 3264 + }, + "acacia_slab": { + "name": "Acacia Slab", + "x": 704, + "y": 3200 + }, + "dark_oak_slab": { + "name": "Dark Oak Slab", + "x": 640, + "y": 3232 + }, + "crimson_slab": { + "name": "Crimson Slab", + "x": 32, + "y": 160 + }, + "warped_slab": { + "name": "Warped Slab", + "x": 128, + "y": 160 + }, + "stone_slab": { + "name": "Stone Slab", + "x": 864, + "y": 3296 + }, + "smooth_stone_slab": { + "name": "Smooth Stone Slab", + "x": 544, + "y": 3296 + }, + "sandstone_slab": { + "name": "Sandstone Slab", + "x": 192, + "y": 3296 + }, + "cut_sandstone_slab": { + "name": "Cut Sandstone Slab", + "x": 64, + "y": 2464 + }, + "petrified_oak_slab": { + "name": "Petrified Oak Slab", + "x": 224, + "y": 896 + }, + "cobblestone_slab": { + "name": "Cobblestone Slab", + "x": 416, + "y": 3232 + }, + "brick_slab": { + "name": "Brick Slab", + "x": 256, + "y": 3232 + }, + "stone_brick_slab": { + "name": "Stone Brick Slab", + "x": 128, + "y": 928 + }, + "nether_brick_slab": { + "name": "Nether Brick Slab", + "x": 480, + "y": 3264 + }, + "quartz_slab": { + "name": "Quartz Slab", + "x": 960, + "y": 3264 + }, + "red_sandstone_slab": { + "name": "Red Sandstone Slab", + "x": 96, + "y": 3296 + }, + "cut_red_sandstone_slab": { + "name": "Cut Red Sandstone Slab", + "x": 32, + "y": 2464 + }, + "purpur_slab": { + "name": "Purpur Slab", + "x": 896, + "y": 3264 + }, + "prismarine_slab": { + "name": "Dark Prismarine Slab", + "x": 736, + "y": 3232 + }, + "prismarine_brick_slab": { + "name": "Prismarine Brick Slab", + "x": 864, + "y": 3264 + }, + "dark_prismarine_slab": { + "name": "Dark Prismarine Slab", + "x": 736, + "y": 3232 + }, + "smooth_quartz": { + "name": "Smooth Quartz Block", + "x": 64, + "y": 864 + }, + "smooth_red_sandstone": { + "name": "Smooth Red Sandstone", + "x": 416, + "y": 3296 + }, + "smooth_sandstone": { + "name": "Smooth Sandstone", + "x": 512, + "y": 3296 + }, + "smooth_stone": { + "name": "Smooth Stone", + "x": 832, + "y": 896 + }, + "bricks": { + "name": "Bricks", + "x": 416, + "y": 96 + }, + "tnt": { + "name": "TNT", + "x": 960, + "y": 3584 + }, + "bookshelf": { + "name": "Bookshelf", + "x": 224, + "y": 3232 + }, + "mossy_cobblestone": { + "name": "Mossy Cobblestone", + "x": 192, + "y": 3360 + }, + "obsidian": { + "name": "Obsidian", + "x": 288, + "y": 3360 + }, + "torch": { + "name": "Torch", + "x": 928, + "y": 3360 + }, + "end_rod": { + "name": "End Rod", + "x": 672, + "y": 3168 + }, + "chorus_plant": { + "name": "Chorus Plant", + "x": 640, + "y": 3392 + }, + "chorus_flower": { + "name": "Chorus Flower", + "x": 608, + "y": 3392 + }, + "purpur_block": { + "name": "Purpur Block", + "x": 416, + "y": 896 + }, + "purpur_pillar": { + "name": "Purpur Pillar", + "x": 448, + "y": 896 + }, + "purpur_stairs": { + "name": "Purpur Stairs", + "x": 928, + "y": 3264 + }, + "spawner": { + "name": "Spawner", + "x": 576, + "y": 3360 + }, + "oak_stairs": { + "name": "Oak Stairs", + "x": 672, + "y": 3264 + }, + "chest": { + "name": "Chest", + "x": 704, + "y": 1216 + }, + "diamond_ore": { + "name": "Diamond Ore", + "x": 992, + "y": 3360 + }, + "diamond_block": { + "name": "Block of Diamond", + "x": 64, + "y": 3232 + }, + "crafting_table": { + "name": "Crafting Table", + "x": 512, + "y": 3584 + }, + "farmland": { + "name": "Farmland", + "x": 672, + "y": 864 + }, + "furnace": { + "name": "Furnace", + "x": 704, + "y": 3584 + }, + "ladder": { + "name": "Ladder", + "x": 224, + "y": 3264 + }, + "rail": { + "name": "Rail", + "x": 480, + "y": 3328 + }, + "cobblestone_stairs": { + "name": "Cobblestone Stairs", + "x": 448, + "y": 3232 + }, + "lever": { + "name": "Lever", + "x": 288, + "y": 3328 + }, + "stone_pressure_plate": { + "name": "Stone Pressure Plate", + "x": 768, + "y": 3328 + }, + "oak_pressure_plate": { + "name": "Oak Pressure Plate", + "x": 384, + "y": 3328 + }, + "spruce_pressure_plate": { + "name": "Spruce Pressure Plate", + "x": 672, + "y": 3328 + }, + "birch_pressure_plate": { + "name": "Birch Pressure Plate", + "x": 32, + "y": 3328 + }, + "jungle_pressure_plate": { + "name": "Jungle Pressure Plate", + "x": 256, + "y": 3328 + }, + "acacia_pressure_plate": { + "name": "Acacia Pressure Plate", + "x": 960, + "y": 3296 + }, + "dark_oak_pressure_plate": { + "name": "Dark Oak Pressure Plate", + "x": 128, + "y": 3328 + }, + "crimson_pressure_plate": { + "name": "Crimson Pressure Plate", + "x": 256, + "y": 160 + }, + "warped_pressure_plate": { + "name": "Warped Pressure Plate", + "x": 384, + "y": 160 + }, + "polished_blackstone_pressure_plate": { + "name": "Polished Blackstone Pressure Plate", + "x": 928, + "y": 256 + }, + "redstone_ore": { + "name": "Redstone Ore", + "x": 128, + "y": 3392 + }, + "redstone_torch": { + "name": "Redstone Torch", + "x": 608, + "y": 3328 + }, + "snow": { + "name": "Snow", + "x": 544, + "y": 3360 + }, + "ice": { + "name": "Ice", + "x": 160, + "y": 3360 + }, + "snow_block": { + "name": "Snow Block", + "x": 576, + "y": 3296 + }, + "cactus": { + "name": "Cactus", + "x": 544, + "y": 3392 + }, + "clay": { + "name": "Clay", + "x": 928, + "y": 3328 + }, + "jukebox": { + "name": "Jukebox", + "x": 768, + "y": 3584 + }, + "oak_fence": { + "name": "Oak Fence", + "x": 608, + "y": 3264 + }, + "spruce_fence": { + "name": "Spruce Fence", + "x": 640, + "y": 3296 + }, + "birch_fence": { + "name": "Birch Fence", + "x": 928, + "y": 3200 + }, + "jungle_fence": { + "name": "Jungle Fence", + "x": 96, + "y": 3264 + }, + "acacia_fence": { + "name": "Acacia Fence", + "x": 672, + "y": 3200 + }, + "dark_oak_fence": { + "name": "Dark Oak Fence", + "x": 608, + "y": 3232 + }, + "crimson_fence": { + "name": "Crimson Fence", + "x": 0, + "y": 160 + }, + "warped_fence": { + "name": "Warped Fence", + "x": 96, + "y": 160 + }, + "pumpkin": { + "name": "Pumpkin", + "x": 384, + "y": 3424 + }, + "carved_pumpkin": { + "name": "Carved Pumpkin", + "x": 576, + "y": 3392 + }, + "netherrack": { + "name": "Netherrack", + "x": 832, + "y": 3360 + }, + "soul_sand": { + "name": "Soul Sand", + "x": 864, + "y": 3360 + }, + "soul_soil": { + "name": "Soul Soil", + "x": 736, + "y": 128 + }, + "basalt": { + "name": "Basalt", + "x": 608, + "y": 128 + }, + "polished_basalt": { + "name": "Polished Basalt", + "x": 416, + "y": 192 + }, + "soul_torch": { + "name": "Soul Torch", + "x": 864, + "y": 128 + }, + "glowstone": { + "name": "Glowstone", + "x": 672, + "y": 3360 + }, + "jack_o_lantern": { + "name": "Jack o'Lantern", + "x": 32, + "y": 3264 + }, + "oak_trapdoor": { + "name": "Oak Trapdoor", + "x": 928, + "y": 928 + }, + "spruce_trapdoor": { + "name": "Spruce Trapdoor", + "x": 448, + "y": 1632 + }, + "birch_trapdoor": { + "name": "Birch Trapdoor", + "x": 480, + "y": 1632 + }, + "jungle_trapdoor": { + "name": "Jungle Trapdoor", + "x": 512, + "y": 1632 + }, + "acacia_trapdoor": { + "name": "Acacia Trapdoor", + "x": 544, + "y": 1632 + }, + "dark_oak_trapdoor": { + "name": "Dark Oak Trapdoor", + "x": 576, + "y": 1632 + }, + "crimson_trapdoor": { + "name": "Crimson Trapdoor", + "x": 288, + "y": 160 + }, + "warped_trapdoor": { + "name": "Warped Trapdoor", + "x": 416, + "y": 160 + }, + "infested_stone": { + "name": "Infested Stone", + "x": 224, + "y": 992 + }, + "infested_cobblestone": { + "name": "Infested Cobblestone", + "x": 960, + "y": 3328 + }, + "infested_stone_bricks": { + "name": "Infested Stone Bricks", + "x": 608, + "y": 3360 + }, + "infested_mossy_stone_bricks": { + "name": "Infested Mossy Stone Bricks", + "x": 224, + "y": 3360 + }, + "infested_cracked_stone_bricks": { + "name": "Infested Cracked Stone Bricks", + "x": 0, + "y": 3360 + }, + "infested_chiseled_stone_bricks": { + "name": "Infested Chiseled Stone Bricks", + "x": 896, + "y": 3328 + }, + "stone_bricks": { + "name": "Stone Bricks", + "x": 608, + "y": 3360 + }, + "mossy_stone_bricks": { + "name": "Mossy Stone Bricks", + "x": 224, + "y": 3360 + }, + "cracked_stone_bricks": { + "name": "Cracked Stone Bricks", + "x": 0, + "y": 3360 + }, + "chiseled_stone_bricks": { + "name": "Chiseled Stone Bricks", + "x": 896, + "y": 3328 + }, + "brown_mushroom_block": { + "name": "Brown Mushroom Block", + "x": 480, + "y": 3392 + }, + "red_mushroom_block": { + "name": "Red Mushroom Block", + "x": 416, + "y": 3424 + }, + "mushroom_stem": { + "name": "Mushroom Stem", + "x": 96, + "y": 3424 + }, + "iron_bars": { + "name": "Iron Bars", + "x": 0, + "y": 3264 + }, + "chain": { + "name": "Chain", + "x": 32, + "y": 288 + }, + "glass_pane": { + "name": "Glass Pane", + "x": 928, + "y": 3232 + }, + "melon": { + "name": "Melon", + "x": 64, + "y": 3424 + }, + "vine": { + "name": "Vines", + "x": 736, + "y": 3424 + }, + "oak_fence_gate": { + "name": "Oak Fence Gate", + "x": 576, + "y": 3264 + }, + "spruce_fence_gate": { + "name": "Spruce Fence Gate", + "x": 608, + "y": 3296 + }, + "birch_fence_gate": { + "name": "Birch Fence Gate", + "x": 896, + "y": 3200 + }, + "jungle_fence_gate": { + "name": "Jungle Fence Gate", + "x": 64, + "y": 3264 + }, + "acacia_fence_gate": { + "name": "Acacia Fence Gate", + "x": 640, + "y": 3200 + }, + "dark_oak_fence_gate": { + "name": "Dark Oak Fence Gate", + "x": 576, + "y": 3232 + }, + "crimson_fence_gate": { + "name": "Crimson Fence Gate", + "x": 576, + "y": 160 + }, + "warped_fence_gate": { + "name": "Warped Fence Gate", + "x": 608, + "y": 160 + }, + "brick_stairs": { + "name": "Brick Stairs", + "x": 288, + "y": 3232 + }, + "stone_brick_stairs": { + "name": "Stone Brick Stairs", + "x": 800, + "y": 3296 + }, + "mycelium": { + "name": "Mycelium", + "x": 256, + "y": 3360 + }, + "lily_pad": { + "name": "Lily Pad", + "x": 32, + "y": 3424 + }, + "nether_bricks": { + "name": "Nether Bricks", + "x": 768, + "y": 3360 + }, + "cracked_nether_bricks": { + "name": "Cracked Nether Bricks", + "x": 448, + "y": 256 + }, + "chiseled_nether_bricks": { + "name": "Chiseled Nether Bricks", + "x": 384, + "y": 256 + }, + "nether_brick_fence": { + "name": "Nether Brick Fence", + "x": 704, + "y": 3360 + }, + "nether_brick_stairs": { + "name": "Nether Brick Stairs", + "x": 736, + "y": 3360 + }, + "enchanting_table": { + "name": "Enchanting Table", + "x": 640, + "y": 3584 + }, + "end_portal_frame": { + "name": "End Portal Frame", + "x": 640, + "y": 3168 + }, + "end_stone": { + "name": "End Stone", + "x": 704, + "y": 3168 + }, + "end_stone_bricks": { + "name": "End Stone Bricks", + "x": 896, + "y": 3232 + }, + "dragon_egg": { + "name": "Dragon Egg", + "x": 576, + "y": 3168 + }, + "redstone_lamp": { + "name": "Redstone Lamp", + "x": 544, + "y": 3328 + }, + "sandstone_stairs": { + "name": "Sandstone Stairs", + "x": 224, + "y": 3296 + }, + "emerald_ore": { + "name": "Emerald Ore", + "x": 0, + "y": 3392 + }, + "ender_chest": { + "name": "Ender Chest", + "x": 672, + "y": 3584 + }, + "tripwire_hook": { + "name": "Tripwire Hook", + "x": 800, + "y": 3328 + }, + "emerald_block": { + "name": "Block of Emerald", + "x": 96, + "y": 3232 + }, + "spruce_stairs": { + "name": "Spruce Stairs", + "x": 704, + "y": 3296 + }, + "birch_stairs": { + "name": "Birch Stairs", + "x": 992, + "y": 3200 + }, + "jungle_stairs": { + "name": "Jungle Stairs", + "x": 160, + "y": 3264 + }, + "crimson_stairs": { + "name": "Crimson Stairs", + "x": 64, + "y": 160 + }, + "warped_stairs": { + "name": "Warped Stairs", + "x": 160, + "y": 160 + }, + "command_block": { + "name": "Impulse Command Block Revision 1", + "x": 736, + "y": 1216 + }, + "beacon": { + "name": "Beacon", + "x": 352, + "y": 3584 + }, + "cobblestone_wall": { + "name": "Cobblestone Wall", + "x": 480, + "y": 3232 + }, + "mossy_cobblestone_wall": { + "name": "Mossy Cobblestone Wall", + "x": 352, + "y": 3264 + }, + "brick_wall": { + "name": "Brick Wall", + "x": 320, + "y": 3232 + }, + "prismarine_wall": { + "name": "Prismarine Wall", + "x": 0, + "y": 96 + }, + "red_sandstone_wall": { + "name": "Red Sandstone Wall", + "x": 160, + "y": 3296 + }, + "mossy_stone_brick_wall": { + "name": "Mossy Stone Brick Wall", + "x": 448, + "y": 3264 + }, + "granite_wall": { + "name": "Granite Wall", + "x": 32, + "y": 2400 + }, + "stone_brick_wall": { + "name": "Stone Brick Wall", + "x": 832, + "y": 3296 + }, + "nether_brick_wall": { + "name": "Nether Brick Wall", + "x": 512, + "y": 3264 + }, + "andesite_wall": { + "name": "Andesite Wall", + "x": 864, + "y": 3200 + }, + "red_nether_brick_wall": { + "name": "Red Nether Brick Wall", + "x": 64, + "y": 3296 + }, + "sandstone_wall": { + "name": "Sandstone Wall", + "x": 256, + "y": 3296 + }, + "end_stone_brick_wall": { + "name": "End Stone Brick Wall", + "x": 0, + "y": 2400 + }, + "diorite_wall": { + "name": "Diorite Wall", + "x": 864, + "y": 3232 + }, + "blackstone_wall": { + "name": "Blackstone Wall", + "x": 672, + "y": 256 + }, + "polished_blackstone_wall": { + "name": "Polished Blackstone Wall", + "x": 864, + "y": 256 + }, + "polished_blackstone_brick_wall": { + "name": "Polished Blackstone Brick Wall", + "x": 768, + "y": 256 + }, + "stone_button": { + "name": "Stone Button", + "x": 736, + "y": 3328 + }, + "oak_button": { + "name": "Oak Button", + "x": 352, + "y": 3328 + }, + "spruce_button": { + "name": "Spruce Button", + "x": 640, + "y": 3328 + }, + "birch_button": { + "name": "Birch Button", + "x": 0, + "y": 3328 + }, + "jungle_button": { + "name": "Jungle Button", + "x": 224, + "y": 3328 + }, + "acacia_button": { + "name": "Acacia Button", + "x": 928, + "y": 3296 + }, + "dark_oak_button": { + "name": "Dark Oak Button", + "x": 96, + "y": 3328 + }, + "crimson_button": { + "name": "Crimson Button", + "x": 192, + "y": 160 + }, + "warped_button": { + "name": "Warped Button", + "x": 320, + "y": 160 + }, + "polished_blackstone_button": { + "name": "Polished Blackstone Button", + "x": 896, + "y": 256 + }, + "anvil": { + "name": "Anvil", + "x": 288, + "y": 3584 + }, + "chipped_anvil": { + "name": "Chipped Anvil", + "x": 480, + "y": 3584 + }, + "damaged_anvil": { + "name": "Damaged Anvil", + "x": 544, + "y": 3584 + }, + "trapped_chest": { + "name": "Trapped Chest", + "x": 992, + "y": 3584 + }, + "light_weighted_pressure_plate": { + "name": "Light Weighted Pressure Plate", + "x": 320, + "y": 3328 + }, + "heavy_weighted_pressure_plate": { + "name": "Heavy Weighted Pressure Plate", + "x": 192, + "y": 3328 + }, + "daylight_detector": { + "name": "Daylight Detector", + "x": 800, + "y": 1216 + }, + "redstone_block": { + "name": "Block of Redstone", + "x": 64, + "y": 3328 + }, + "nether_quartz_ore": { + "name": "Nether Quartz Ore", + "x": 800, + "y": 3360 + }, + "hopper": { + "name": "Hopper", + "x": 736, + "y": 3584 + }, + "chiseled_quartz_block": { + "name": "Chiseled Quartz Block", + "x": 224, + "y": 864 + }, + "quartz_block": { + "name": "Block of Quartz", + "x": 192, + "y": 3232 + }, + "quartz_bricks": { + "name": "Quartz Bricks", + "x": 992, + "y": 256 + }, + "quartz_pillar": { + "name": "Quartz Pillar", + "x": 288, + "y": 896 + }, + "quartz_stairs": { + "name": "Quartz Stairs", + "x": 992, + "y": 3264 + }, + "activator_rail": { + "name": "Activator Rail", + "x": 992, + "y": 3296 + }, + "dropper": { + "name": "Dropper", + "x": 608, + "y": 3584 + }, + "white_terracotta": { + "name": "White Terracotta", + "x": 672, + "y": 768 + }, + "orange_terracotta": { + "name": "Orange Terracotta", + "x": 928, + "y": 736 + }, + "magenta_terracotta": { + "name": "Magenta Terracotta", + "x": 736, + "y": 736 + }, + "light_blue_terracotta": { + "name": "Light Blue Terracotta", + "x": 160, + "y": 736 + }, + "yellow_terracotta": { + "name": "Yellow Terracotta", + "x": 864, + "y": 768 + }, + "lime_terracotta": { + "name": "Lime Terracotta", + "x": 544, + "y": 736 + }, + "pink_terracotta": { + "name": "Pink Terracotta", + "x": 96, + "y": 768 + }, + "gray_terracotta": { + "name": "Gray Terracotta", + "x": 800, + "y": 704 + }, + "light_gray_terracotta": { + "name": "Light Gray Terracotta", + "x": 352, + "y": 736 + }, + "cyan_terracotta": { + "name": "Cyan Terracotta", + "x": 608, + "y": 704 + }, + "purple_terracotta": { + "name": "Purple Terracotta", + "x": 288, + "y": 768 + }, + "blue_terracotta": { + "name": "Blue Terracotta", + "x": 224, + "y": 704 + }, + "brown_terracotta": { + "name": "Brown Terracotta", + "x": 416, + "y": 704 + }, + "green_terracotta": { + "name": "Green Terracotta", + "x": 992, + "y": 704 + }, + "red_terracotta": { + "name": "Red Terracotta", + "x": 480, + "y": 768 + }, + "black_terracotta": { + "name": "Black Terracotta", + "x": 32, + "y": 704 + }, + "barrier": { + "name": "Barrier", + "x": 320, + "y": 3584 + }, + "iron_trapdoor": { + "name": "Iron Trapdoor", + "x": 416, + "y": 928 + }, + "hay_block": { + "name": "Hay Bale", + "x": 992, + "y": 3232 + }, + "white_carpet": { + "name": "White Carpet", + "x": 64, + "y": 1632 + }, + "orange_carpet": { + "name": "Orange Carpet", + "x": 960, + "y": 1600 + }, + "magenta_carpet": { + "name": "Magenta Carpet", + "x": 928, + "y": 1600 + }, + "light_blue_carpet": { + "name": "Light Blue Carpet", + "x": 832, + "y": 1600 + }, + "yellow_carpet": { + "name": "Yellow Carpet", + "x": 96, + "y": 1632 + }, + "lime_carpet": { + "name": "Lime Carpet", + "x": 896, + "y": 1600 + }, + "pink_carpet": { + "name": "Pink Carpet", + "x": 992, + "y": 1600 + }, + "gray_carpet": { + "name": "Gray Carpet", + "x": 768, + "y": 1600 + }, + "light_gray_carpet": { + "name": "Light Gray Carpet", + "x": 864, + "y": 1600 + }, + "cyan_carpet": { + "name": "Cyan Carpet", + "x": 736, + "y": 1600 + }, + "purple_carpet": { + "name": "Purple Carpet", + "x": 0, + "y": 1632 + }, + "blue_carpet": { + "name": "Blue Carpet", + "x": 672, + "y": 1600 + }, + "brown_carpet": { + "name": "Brown Carpet", + "x": 704, + "y": 1600 + }, + "green_carpet": { + "name": "Green Carpet", + "x": 800, + "y": 1600 + }, + "red_carpet": { + "name": "Red Carpet", + "x": 32, + "y": 1632 + }, + "black_carpet": { + "name": "Black Carpet", + "x": 640, + "y": 1600 + }, + "terracotta": { + "name": "Terracotta", + "x": 640, + "y": 3360 + }, + "coal_block": { + "name": "Block of Coal", + "x": 32, + "y": 3232 + }, + "packed_ice": { + "name": "Packed Ice", + "x": 320, + "y": 3360 + }, + "acacia_stairs": { + "name": "Acacia Stairs", + "x": 736, + "y": 3200 + }, + "dark_oak_stairs": { + "name": "Dark Oak Stairs", + "x": 672, + "y": 3232 + }, + "slime_block": { + "name": "Slime Block", + "x": 736, + "y": 896 + }, + "grass_path": { + "name": "Grass Path", + "x": 96, + "y": 1344 + }, + "sunflower": { + "name": "Sunflower", + "x": 672, + "y": 3424 + }, + "lilac": { + "name": "Lilac", + "x": 0, + "y": 3424 + }, + "rose_bush": { + "name": "Rose Bush", + "x": 512, + "y": 3424 + }, + "peony": { + "name": "Peony", + "x": 288, + "y": 3424 + }, + "tall_grass": { + "name": "Tall Grass", + "x": 704, + "y": 3424 + }, + "large_fern": { + "name": "Large Fern", + "x": 992, + "y": 3392 + }, + "white_stained_glass": { + "name": "White Stained Glass", + "x": 704, + "y": 768 + }, + "orange_stained_glass": { + "name": "Orange Stained Glass", + "x": 960, + "y": 736 + }, + "magenta_stained_glass": { + "name": "Magenta Stained Glass", + "x": 768, + "y": 736 + }, + "light_blue_stained_glass": { + "name": "Light Blue Stained Glass", + "x": 192, + "y": 736 + }, + "yellow_stained_glass": { + "name": "Yellow Stained Glass", + "x": 896, + "y": 768 + }, + "lime_stained_glass": { + "name": "Lime Stained Glass", + "x": 576, + "y": 736 + }, + "pink_stained_glass": { + "name": "Pink Stained Glass", + "x": 128, + "y": 768 + }, + "gray_stained_glass": { + "name": "Gray Stained Glass", + "x": 832, + "y": 704 + }, + "light_gray_stained_glass": { + "name": "Light Gray Stained Glass", + "x": 384, + "y": 736 + }, + "cyan_stained_glass": { + "name": "Cyan Stained Glass", + "x": 640, + "y": 704 + }, + "purple_stained_glass": { + "name": "Purple Stained Glass", + "x": 320, + "y": 768 + }, + "blue_stained_glass": { + "name": "Blue Stained Glass", + "x": 256, + "y": 704 + }, + "brown_stained_glass": { + "name": "Brown Stained Glass", + "x": 448, + "y": 704 + }, + "green_stained_glass": { + "name": "Green Stained Glass", + "x": 0, + "y": 736 + }, + "red_stained_glass": { + "name": "Red Stained Glass", + "x": 512, + "y": 768 + }, + "black_stained_glass": { + "name": "Black Stained Glass", + "x": 64, + "y": 704 + }, + "white_stained_glass_pane": { + "name": "White Stained Glass Pane", + "x": 736, + "y": 768 + }, + "orange_stained_glass_pane": { + "name": "Orange Stained Glass Pane", + "x": 992, + "y": 736 + }, + "magenta_stained_glass_pane": { + "name": "Magenta Stained Glass Pane", + "x": 800, + "y": 736 + }, + "light_blue_stained_glass_pane": { + "name": "Light Blue Stained Glass Pane", + "x": 224, + "y": 736 + }, + "yellow_stained_glass_pane": { + "name": "Yellow Stained Glass Pane", + "x": 928, + "y": 768 + }, + "lime_stained_glass_pane": { + "name": "Lime Stained Glass Pane", + "x": 608, + "y": 736 + }, + "pink_stained_glass_pane": { + "name": "Pink Stained Glass Pane", + "x": 160, + "y": 768 + }, + "gray_stained_glass_pane": { + "name": "Gray Stained Glass Pane", + "x": 864, + "y": 704 + }, + "light_gray_stained_glass_pane": { + "name": "Light Gray Stained Glass Pane", + "x": 416, + "y": 736 + }, + "cyan_stained_glass_pane": { + "name": "Cyan Stained Glass Pane", + "x": 672, + "y": 704 + }, + "purple_stained_glass_pane": { + "name": "Purple Stained Glass Pane", + "x": 352, + "y": 768 + }, + "blue_stained_glass_pane": { + "name": "Blue Stained Glass Pane", + "x": 288, + "y": 704 + }, + "brown_stained_glass_pane": { + "name": "Brown Stained Glass Pane", + "x": 480, + "y": 704 + }, + "green_stained_glass_pane": { + "name": "Green Stained Glass Pane", + "x": 32, + "y": 736 + }, + "red_stained_glass_pane": { + "name": "Red Stained Glass Pane", + "x": 544, + "y": 768 + }, + "black_stained_glass_pane": { + "name": "Black Stained Glass Pane", + "x": 96, + "y": 704 + }, + "prismarine": { + "name": "Prismarine Wall", + "x": 0, + "y": 96 + }, + "prismarine_bricks": { + "name": "Prismarine Bricks", + "x": 384, + "y": 3360 + }, + "dark_prismarine": { + "name": "Dark Prismarine", + "x": 32, + "y": 3360 + }, + "prismarine_stairs": { + "name": "Dark Prismarine Stairs", + "x": 768, + "y": 3232 + }, + "prismarine_brick_stairs": { + "name": "Prismarine Brick Stairs", + "x": 448, + "y": 96 + }, + "dark_prismarine_stairs": { + "name": "Dark Prismarine Stairs", + "x": 768, + "y": 3232 + }, + "sea_lantern": { + "name": "Sea Lantern BE", + "x": 512, + "y": 192 + }, + "red_sandstone": { + "name": "Red Sandstone", + "x": 448, + "y": 3360 + }, + "chiseled_red_sandstone": { + "name": "Chiseled Red Sandstone", + "x": 352, + "y": 3232 + }, + "cut_red_sandstone": { + "name": "Cut Red Sandstone", + "x": 512, + "y": 3232 + }, + "red_sandstone_stairs": { + "name": "Red Sandstone Stairs", + "x": 128, + "y": 3296 + }, + "magma_block": { + "name": "Magma Block BE", + "x": 928, + "y": 480 + }, + "nether_wart_block": { + "name": "Nether Wart Block", + "x": 544, + "y": 3264 + }, + "warped_wart_block": { + "name": "Warped Wart Block", + "x": 832, + "y": 128 + }, + "red_nether_bricks": { + "name": "Red Nether Bricks", + "x": 704, + "y": 1344 + }, + "bone_block": { + "name": "Bone Block", + "x": 864, + "y": 3328 + }, + "structure_void": { + "name": "Structure Void", + "x": 928, + "y": 3584 + }, + "observer": { + "name": "Observer", + "x": 864, + "y": 3584 + }, + "shulker_box": { + "name": "Shulker Box", + "x": 448, + "y": 1600 + }, + "white_shulker_box": { + "name": "White Shulker Box", + "x": 512, + "y": 1600 + }, + "orange_shulker_box": { + "name": "Orange Shulker Box", + "x": 384, + "y": 1600 + }, + "magenta_shulker_box": { + "name": "Magenta Shulker Box", + "x": 352, + "y": 1600 + }, + "light_blue_shulker_box": { + "name": "Light Blue Shulker Box", + "x": 256, + "y": 1600 + }, + "yellow_shulker_box": { + "name": "Yellow Shulker Box", + "x": 544, + "y": 1600 + }, + "lime_shulker_box": { + "name": "Lime Shulker Box", + "x": 320, + "y": 1600 + }, + "pink_shulker_box": { + "name": "Pink Shulker Box", + "x": 416, + "y": 1600 + }, + "gray_shulker_box": { + "name": "Gray Shulker Box", + "x": 192, + "y": 1600 + }, + "light_gray_shulker_box": { + "name": "Light Gray Shulker Box", + "x": 288, + "y": 1600 + }, + "cyan_shulker_box": { + "name": "Cyan Shulker Box", + "x": 160, + "y": 1600 + }, + "purple_shulker_box": { + "name": "Purple Shulker Box", + "x": 608, + "y": 1408 + }, + "blue_shulker_box": { + "name": "Blue Shulker Box", + "x": 96, + "y": 1600 + }, + "brown_shulker_box": { + "name": "Brown Shulker Box", + "x": 128, + "y": 1600 + }, + "green_shulker_box": { + "name": "Green Shulker Box", + "x": 224, + "y": 1600 + }, + "red_shulker_box": { + "name": "Red Shulker Box", + "x": 480, + "y": 1600 + }, + "black_shulker_box": { + "name": "Black Shulker Box", + "x": 64, + "y": 1600 + }, + "white_glazed_terracotta": { + "name": "White Glazed Terracotta", + "x": 192, + "y": 1536 + }, + "orange_glazed_terracotta": { + "name": "Orange Glazed Terracotta", + "x": 160, + "y": 1536 + }, + "magenta_glazed_terracotta": { + "name": "Magenta Glazed Terracotta", + "x": 128, + "y": 1536 + }, + "light_blue_glazed_terracotta": { + "name": "Light Blue Glazed Terracotta", + "x": 704, + "y": 1472 + }, + "yellow_glazed_terracotta": { + "name": "Yellow Glazed Terracotta", + "x": 544, + "y": 1504 + }, + "lime_glazed_terracotta": { + "name": "Lime Glazed Terracotta", + "x": 896, + "y": 1472 + }, + "pink_glazed_terracotta": { + "name": "Pink Glazed Terracotta", + "x": 160, + "y": 1504 + }, + "gray_glazed_terracotta": { + "name": "Gray Glazed Terracotta", + "x": 512, + "y": 1472 + }, + "light_gray_glazed_terracotta": { + "name": "Light Gray Glazed Terracotta", + "x": 800, + "y": 1472 + }, + "cyan_glazed_terracotta": { + "name": "Cyan Glazed Terracotta", + "x": 96, + "y": 1536 + }, + "purple_glazed_terracotta": { + "name": "Purple Glazed Terracotta", + "x": 256, + "y": 1504 + }, + "blue_glazed_terracotta": { + "name": "Blue Glazed Terracotta", + "x": 224, + "y": 1472 + }, + "brown_glazed_terracotta": { + "name": "Brown Glazed Terracotta", + "x": 320, + "y": 1472 + }, + "green_glazed_terracotta": { + "name": "Green Glazed Terracotta", + "x": 608, + "y": 1472 + }, + "red_glazed_terracotta": { + "name": "Red Glazed Terracotta", + "x": 352, + "y": 1504 + }, + "black_glazed_terracotta": { + "name": "Black Glazed Terracotta", + "x": 128, + "y": 1472 + }, + "white_concrete": { + "name": "White Concrete", + "x": 384, + "y": 1504 + }, + "orange_concrete": { + "name": "Orange Concrete", + "x": 0, + "y": 1504 + }, + "magenta_concrete": { + "name": "Magenta Concrete", + "x": 928, + "y": 1472 + }, + "light_blue_concrete": { + "name": "Light Blue Concrete", + "x": 640, + "y": 1472 + }, + "yellow_concrete": { + "name": "Yellow Concrete", + "x": 480, + "y": 1504 + }, + "lime_concrete": { + "name": "Lime Concrete", + "x": 832, + "y": 1472 + }, + "pink_concrete": { + "name": "Pink Concrete", + "x": 96, + "y": 1504 + }, + "gray_concrete": { + "name": "Gray Concrete", + "x": 448, + "y": 1472 + }, + "light_gray_concrete": { + "name": "Light Gray Concrete", + "x": 736, + "y": 1472 + }, + "cyan_concrete": { + "name": "Cyan Concrete", + "x": 352, + "y": 1472 + }, + "purple_concrete": { + "name": "Purple Concrete", + "x": 192, + "y": 1504 + }, + "blue_concrete": { + "name": "Blue Concrete", + "x": 160, + "y": 1472 + }, + "brown_concrete": { + "name": "Brown Concrete", + "x": 256, + "y": 1472 + }, + "green_concrete": { + "name": "Green Concrete", + "x": 544, + "y": 1472 + }, + "red_concrete": { + "name": "Red Concrete", + "x": 288, + "y": 1504 + }, + "black_concrete": { + "name": "Black Concrete", + "x": 64, + "y": 1472 + }, + "white_concrete_powder": { + "name": "White Concrete Powder", + "x": 416, + "y": 1504 + }, + "orange_concrete_powder": { + "name": "Orange Concrete Powder", + "x": 32, + "y": 1504 + }, + "magenta_concrete_powder": { + "name": "Magenta Concrete Powder", + "x": 960, + "y": 1472 + }, + "light_blue_concrete_powder": { + "name": "Light Blue Concrete Powder", + "x": 672, + "y": 1472 + }, + "yellow_concrete_powder": { + "name": "Yellow Concrete Powder", + "x": 512, + "y": 1504 + }, + "lime_concrete_powder": { + "name": "Lime Concrete Powder", + "x": 864, + "y": 1472 + }, + "pink_concrete_powder": { + "name": "Pink Concrete Powder", + "x": 128, + "y": 1504 + }, + "gray_concrete_powder": { + "name": "Gray Concrete Powder", + "x": 480, + "y": 1472 + }, + "light_gray_concrete_powder": { + "name": "Light Gray Concrete Powder", + "x": 768, + "y": 1472 + }, + "cyan_concrete_powder": { + "name": "Cyan Concrete Powder", + "x": 384, + "y": 1472 + }, + "purple_concrete_powder": { + "name": "Purple Concrete Powder", + "x": 224, + "y": 1504 + }, + "blue_concrete_powder": { + "name": "Blue Concrete Powder", + "x": 192, + "y": 1472 + }, + "brown_concrete_powder": { + "name": "Brown Concrete Powder", + "x": 288, + "y": 1472 + }, + "green_concrete_powder": { + "name": "Green Concrete Powder", + "x": 576, + "y": 1472 + }, + "red_concrete_powder": { + "name": "Red Concrete Powder", + "x": 320, + "y": 1504 + }, + "black_concrete_powder": { + "name": "Black Concrete Powder", + "x": 96, + "y": 1472 + }, + "turtle_egg": { + "name": "Turtle Egg", + "x": 608, + "y": 1952 + }, + "dead_tube_coral_block": { + "name": "Dead Tube Coral Block", + "x": 640, + "y": 1696 + }, + "dead_brain_coral_block": { + "name": "Dead Brain Coral Block", + "x": 672, + "y": 1696 + }, + "dead_bubble_coral_block": { + "name": "Dead Bubble Coral Block", + "x": 704, + "y": 1696 + }, + "dead_fire_coral_block": { + "name": "Dead Fire Coral Block", + "x": 736, + "y": 1696 + }, + "dead_horn_coral_block": { + "name": "Dead Horn Coral Block", + "x": 768, + "y": 1696 + }, + "tube_coral_block": { + "name": "Tube Coral Block", + "x": 288, + "y": 1696 + }, + "brain_coral_block": { + "name": "Brain Coral Block", + "x": 320, + "y": 1696 + }, + "bubble_coral_block": { + "name": "Bubble Coral Block", + "x": 352, + "y": 1696 + }, + "fire_coral_block": { + "name": "Fire Coral Block", + "x": 384, + "y": 1696 + }, + "horn_coral_block": { + "name": "Horn Coral Block", + "x": 416, + "y": 1696 + }, + "tube_coral": { + "name": "Tube Coral", + "x": 448, + "y": 1696 + }, + "brain_coral": { + "name": "Brain Coral", + "x": 480, + "y": 1696 + }, + "bubble_coral": { + "name": "Bubble Coral", + "x": 512, + "y": 1696 + }, + "fire_coral": { + "name": "Fire Coral", + "x": 544, + "y": 1696 + }, + "horn_coral": { + "name": "Horn Coral", + "x": 576, + "y": 1696 + }, + "dead_brain_coral": { + "name": "Dead Brain Coral", + "x": 992, + "y": 2336 + }, + "dead_bubble_coral": { + "name": "Dead Bubble Coral", + "x": 0, + "y": 2368 + }, + "dead_fire_coral": { + "name": "Dead Fire Coral", + "x": 32, + "y": 2368 + }, + "dead_horn_coral": { + "name": "Dead Horn Coral", + "x": 64, + "y": 2368 + }, + "dead_tube_coral": { + "name": "Dead Tube Coral", + "x": 960, + "y": 2336 + }, + "tube_coral_fan": { + "name": "Tube Coral Fan", + "x": 704, + "y": 2112 + }, + "brain_coral_fan": { + "name": "Brain Coral Fan", + "x": 608, + "y": 2112 + }, + "bubble_coral_fan": { + "name": "Bubble Coral Fan", + "x": 640, + "y": 2112 + }, + "fire_coral_fan": { + "name": "Fire Coral Fan", + "x": 896, + "y": 1696 + }, + "horn_coral_fan": { + "name": "Horn Coral Fan", + "x": 672, + "y": 2112 + }, + "dead_tube_coral_fan": { + "name": "Dead Tube Coral Fan", + "x": 160, + "y": 2144 + }, + "dead_brain_coral_fan": { + "name": "Dead Brain Coral Fan", + "x": 32, + "y": 2144 + }, + "dead_bubble_coral_fan": { + "name": "Dead Bubble Coral Fan", + "x": 64, + "y": 2144 + }, + "dead_fire_coral_fan": { + "name": "Dead Fire Coral Fan", + "x": 96, + "y": 2144 + }, + "dead_horn_coral_fan": { + "name": "Dead Horn Coral Fan", + "x": 128, + "y": 2144 + }, + "blue_ice": { + "name": "Blue Ice", + "x": 192, + "y": 1728 + }, + "conduit": { + "name": "Conduit", + "x": 512, + "y": 2016 + }, + "polished_granite_stairs": { + "name": "Polished Granite Stairs", + "x": 960, + "y": 2400 + }, + "smooth_red_sandstone_stairs": { + "name": "Smooth Red Sandstone Stairs", + "x": 384, + "y": 3296 + }, + "mossy_stone_brick_stairs": { + "name": "Mossy Stone Brick Stairs", + "x": 416, + "y": 3264 + }, + "polished_diorite_stairs": { + "name": "Polished Diorite Stairs", + "x": 832, + "y": 3264 + }, + "mossy_cobblestone_stairs": { + "name": "Mossy Cobblestone Stairs", + "x": 320, + "y": 3264 + }, + "end_stone_brick_stairs": { + "name": "End Stone Brick Stairs", + "x": 576, + "y": 2400 + }, + "stone_stairs": { + "name": "Stone Stairs", + "x": 896, + "y": 3296 + }, + "smooth_sandstone_stairs": { + "name": "Smooth Sandstone Stairs", + "x": 480, + "y": 3296 + }, + "smooth_quartz_stairs": { + "name": "Smooth Quartz Stairs", + "x": 320, + "y": 3296 + }, + "granite_stairs": { + "name": "Granite Stairs", + "x": 640, + "y": 2400 + }, + "andesite_stairs": { + "name": "Andesite Stairs", + "x": 832, + "y": 3200 + }, + "red_nether_brick_stairs": { + "name": "Red Nether Brick Stairs", + "x": 32, + "y": 3296 + }, + "polished_andesite_stairs": { + "name": "Polished Andesite Stairs", + "x": 768, + "y": 3264 + }, + "diorite_stairs": { + "name": "Diorite Stairs", + "x": 832, + "y": 3232 + }, + "polished_granite_slab": { + "name": "Polished Granite Slab", + "x": 928, + "y": 2400 + }, + "smooth_red_sandstone_slab": { + "name": "Smooth Red Sandstone Slab", + "x": 352, + "y": 3296 + }, + "mossy_stone_brick_slab": { + "name": "Mossy Stone Brick Slab", + "x": 384, + "y": 3264 + }, + "polished_diorite_slab": { + "name": "Polished Diorite Slab", + "x": 800, + "y": 3264 + }, + "mossy_cobblestone_slab": { + "name": "Mossy Cobblestone Slab", + "x": 288, + "y": 3264 + }, + "end_stone_brick_slab": { + "name": "End Stone Brick Slab", + "x": 544, + "y": 2400 + }, + "smooth_sandstone_slab": { + "name": "Smooth Sandstone Slab", + "x": 448, + "y": 3296 + }, + "smooth_quartz_slab": { + "name": "Smooth Quartz Slab", + "x": 288, + "y": 3296 + }, + "granite_slab": { + "name": "Granite Slab", + "x": 608, + "y": 2400 + }, + "andesite_slab": { + "name": "Andesite Slab", + "x": 800, + "y": 3200 + }, + "red_nether_brick_slab": { + "name": "Red Nether Brick Slab", + "x": 0, + "y": 3296 + }, + "polished_andesite_slab": { + "name": "Polished Andesite Slab", + "x": 736, + "y": 3264 + }, + "diorite_slab": { + "name": "Diorite Slab", + "x": 800, + "y": 3232 + }, + "scaffolding": { + "name": "Scaffolding", + "x": 416, + "y": 2400 + }, + "iron_door": { + "name": "Iron Door", + "x": 384, + "y": 928 + }, + "oak_door": { + "name": "Oak Door", + "x": 512, + "y": 928 + }, + "spruce_door": { + "name": "Spruce Door", + "x": 800, + "y": 928 + }, + "birch_door": { + "name": "Birch Door", + "x": 256, + "y": 928 + }, + "jungle_door": { + "name": "Jungle Door", + "x": 448, + "y": 928 + }, + "acacia_door": { + "name": "Acacia Door", + "x": 192, + "y": 928 + }, + "dark_oak_door": { + "name": "Dark Oak Door", + "x": 320, + "y": 928 + }, + "crimson_door": { + "name": "Crimson Door", + "x": 224, + "y": 160 + }, + "warped_door": { + "name": "Warped Door", + "x": 352, + "y": 160 + }, + "repeater": { + "name": "Redstone Repeater", + "x": 576, + "y": 3328 + }, + "comparator": { + "name": "Redstone Comparator", + "x": 512, + "y": 3328 + }, + "structure_block": { + "name": "Structure Block", + "x": 96, + "y": 1376 + }, + "jigsaw": { + "name": "Jigsaw Block", + "x": 64, + "y": 288 + }, + "turtle_helmet": { + "name": "Turtle Shell", + "x": 512, + "y": 1664 + }, + "scute": { + "name": "Scute", + "x": 608, + "y": 1664 + }, + "flint_and_steel": { + "name": "Flint and Steel", + "x": 480, + "y": 3552 + }, + "apple": { + "name": "Apple", + "x": 768, + "y": 3168 + }, + "bow": { + "name": "Bow", + "x": 992, + "y": 1248 + }, + "arrow": { + "name": "Arrow", + "x": 576, + "y": 1248 + }, + "coal": { + "name": "Coal", + "x": 576, + "y": 3488 + }, + "charcoal": { + "name": "Charcoal", + "x": 512, + "y": 3488 + }, + "diamond": { + "name": "Diamond", + "x": 608, + "y": 3488 + }, + "iron_ingot": { + "name": "Iron Ingot", + "x": 896, + "y": 3488 + }, + "gold_ingot": { + "name": "Gold Ingot", + "x": 800, + "y": 3488 + }, + "netherite_ingot": { + "name": "Netherite Ingot", + "x": 192, + "y": 128 + }, + "netherite_scrap": { + "name": "Netherite Scrap", + "x": 224, + "y": 128 + }, + "wooden_sword": { + "name": "Wooden Sword", + "x": 800, + "y": 3616 + }, + "wooden_shovel": { + "name": "Wooden Shovel", + "x": 192, + "y": 3584 + }, + "wooden_pickaxe": { + "name": "Wooden Pickaxe", + "x": 160, + "y": 3584 + }, + "wooden_axe": { + "name": "Wooden Axe", + "x": 96, + "y": 3584 + }, + "wooden_hoe": { + "name": "Wooden Hoe", + "x": 128, + "y": 3584 + }, + "stone_sword": { + "name": "Stone Sword", + "x": 736, + "y": 3616 + }, + "stone_shovel": { + "name": "Stone Shovel", + "x": 64, + "y": 3584 + }, + "stone_pickaxe": { + "name": "Stone Pickaxe", + "x": 32, + "y": 3584 + }, + "stone_axe": { + "name": "Stone Axe", + "x": 992, + "y": 3552 + }, + "stone_hoe": { + "name": "Stone Hoe", + "x": 0, + "y": 3584 + }, + "golden_sword": { + "name": "Golden Sword", + "x": 608, + "y": 3616 + }, + "golden_shovel": { + "name": "Golden Shovel", + "x": 640, + "y": 3552 + }, + "golden_pickaxe": { + "name": "Golden Pickaxe", + "x": 608, + "y": 3552 + }, + "golden_axe": { + "name": "Golden Axe", + "x": 544, + "y": 3552 + }, + "golden_hoe": { + "name": "Golden Hoe", + "x": 576, + "y": 3552 + }, + "iron_sword": { + "name": "Iron Sword", + "x": 640, + "y": 3616 + }, + "iron_shovel": { + "name": "Iron Shovel", + "x": 768, + "y": 3552 + }, + "iron_pickaxe": { + "name": "Iron Pickaxe", + "x": 736, + "y": 3552 + }, + "iron_axe": { + "name": "Iron Axe", + "x": 672, + "y": 3552 + }, + "iron_hoe": { + "name": "Iron Hoe", + "x": 704, + "y": 3552 + }, + "diamond_sword": { + "name": "Diamond Sword", + "x": 576, + "y": 3616 + }, + "diamond_shovel": { + "name": "Diamond Shovel", + "x": 288, + "y": 3552 + }, + "diamond_pickaxe": { + "name": "Diamond Pickaxe", + "x": 256, + "y": 3552 + }, + "diamond_axe": { + "name": "Diamond Axe", + "x": 192, + "y": 3552 + }, + "diamond_hoe": { + "name": "Diamond Hoe", + "x": 224, + "y": 3552 + }, + "netherite_sword": { + "name": "Netherite Sword", + "x": 384, + "y": 128 + }, + "netherite_shovel": { + "name": "Netherite Shovel", + "x": 352, + "y": 128 + }, + "netherite_pickaxe": { + "name": "Netherite Pickaxe", + "x": 320, + "y": 128 + }, + "netherite_axe": { + "name": "Netherite Axe", + "x": 256, + "y": 128 + }, + "netherite_hoe": { + "name": "Netherite Hoe", + "x": 288, + "y": 128 + }, + "stick": { + "name": "Stick", + "x": 608, + "y": 1120 + }, + "bowl": { + "name": "Bowl", + "x": 384, + "y": 3520 + }, + "mushroom_stew": { + "name": "Mushroom Stew", + "x": 224, + "y": 3200 + }, + "string": { + "name": "String", + "x": 320, + "y": 3520 + }, + "feather": { + "name": "Feather", + "x": 704, + "y": 3488 + }, + "gunpowder": { + "name": "Gunpowder", + "x": 864, + "y": 3488 + }, + "wheat_seeds": { + "name": "Wheat Seeds", + "x": 320, + "y": 1056 + }, + "wheat": { + "name": "Wheat", + "x": 608, + "y": 3200 + }, + "bread": { + "name": "Bread", + "x": 896, + "y": 3168 + }, + "leather_helmet": { + "name": "Leather Cap", + "x": 608, + "y": 2496 + }, + "leather_chestplate": { + "name": "Leather Tunic", + "x": 672, + "y": 2496 + }, + "leather_leggings": { + "name": "Leather Pants", + "x": 640, + "y": 2496 + }, + "leather_boots": { + "name": "Leather Boots", + "x": 576, + "y": 2496 + }, + "chainmail_helmet": { + "name": "Chainmail Helmet", + "x": 320, + "y": 2464 + }, + "chainmail_chestplate": { + "name": "Chainmail Chestplate", + "x": 288, + "y": 2464 + }, + "chainmail_leggings": { + "name": "Chainmail Leggings", + "x": 352, + "y": 2464 + }, + "chainmail_boots": { + "name": "Chainmail Boots", + "x": 256, + "y": 2464 + }, + "iron_helmet": { + "name": "Iron Helmet", + "x": 512, + "y": 2496 + }, + "iron_chestplate": { + "name": "Iron Chestplate", + "x": 480, + "y": 2496 + }, + "iron_leggings": { + "name": "Iron Leggings", + "x": 544, + "y": 2496 + }, + "iron_boots": { + "name": "Iron Boots", + "x": 448, + "y": 2496 + }, + "diamond_helmet": { + "name": "Diamond Helmet", + "x": 160, + "y": 2496 + }, + "diamond_chestplate": { + "name": "Diamond Chestplate", + "x": 128, + "y": 2496 + }, + "diamond_leggings": { + "name": "Diamond Leggings", + "x": 192, + "y": 2496 + }, + "diamond_boots": { + "name": "Diamond Boots", + "x": 96, + "y": 2496 + }, + "golden_helmet": { + "name": "Golden Helmet", + "x": 320, + "y": 2496 + }, + "golden_chestplate": { + "name": "Golden Chestplate", + "x": 288, + "y": 2496 + }, + "golden_leggings": { + "name": "Golden Leggings", + "x": 352, + "y": 2496 + }, + "golden_boots": { + "name": "Golden Boots", + "x": 256, + "y": 2496 + }, + "netherite_helmet": { + "name": "Netherite Helmet", + "x": 480, + "y": 128 + }, + "netherite_chestplate": { + "name": "Netherite Chestplate", + "x": 448, + "y": 128 + }, + "netherite_leggings": { + "name": "Netherite Leggings", + "x": 512, + "y": 128 + }, + "netherite_boots": { + "name": "Netherite Boots", + "x": 416, + "y": 128 + }, + "flint": { + "name": "Flint", + "x": 736, + "y": 3488 + }, + "porkchop": { + "name": "Raw Porkchop", + "x": 480, + "y": 3200 + }, + "cooked_porkchop": { + "name": "Cooked Porkchop", + "x": 32, + "y": 3200 + }, + "painting": { + "name": "Painting", + "x": 544, + "y": 3136 + }, + "golden_apple": { + "name": "Golden Apple", + "x": 160, + "y": 3200 + }, + "enchanted_golden_apple": { + "name": "Enchanted Golden Apple", + "x": 128, + "y": 256 + }, + "oak_sign": { + "name": "Oak Sign", + "x": 896, + "y": 3360 + }, + "spruce_sign": { + "name": "Spruce Sign", + "x": 800, + "y": 2368 + }, + "birch_sign": { + "name": "Birch Sign", + "x": 768, + "y": 2368 + }, + "jungle_sign": { + "name": "Jungle Sign", + "x": 832, + "y": 2368 + }, + "acacia_sign": { + "name": "Acacia Sign", + "x": 864, + "y": 2368 + }, + "dark_oak_sign": { + "name": "Dark Oak Sign", + "x": 896, + "y": 2368 + }, + "crimson_sign": { + "name": "Crimson Sign", + "x": 512, + "y": 160 + }, + "warped_sign": { + "name": "Warped Sign", + "x": 544, + "y": 160 + }, + "bucket": { + "name": "Bucket", + "x": 288, + "y": 3136 + }, + "water_bucket": { + "name": "Water Bucket", + "x": 384, + "y": 3136 + }, + "lava_bucket": { + "name": "Lava Bucket", + "x": 320, + "y": 3136 + }, + "minecart": { + "name": "Minecart", + "x": 160, + "y": 3616 + }, + "saddle": { + "name": "Saddle", + "x": 928, + "y": 3552 + }, + "redstone": { + "name": "Redstone Dust", + "x": 224, + "y": 3520 + }, + "snowball": { + "name": "Snowball", + "x": 672, + "y": 3616 + }, + "oak_boat": { + "name": "Oak Boat", + "x": 352, + "y": 3616 + }, + "leather": { + "name": "Leather", + "x": 960, + "y": 3488 + }, + "milk_bucket": { + "name": "Milk Bucket", + "x": 352, + "y": 3136 + }, + "pufferfish_bucket": { + "name": "Bucket of Pufferfish", + "x": 704, + "y": 1664 + }, + "salmon_bucket": { + "name": "Bucket of Salmon", + "x": 736, + "y": 1664 + }, + "cod_bucket": { + "name": "Bucket of Cod", + "x": 672, + "y": 1664 + }, + "tropical_fish_bucket": { + "name": "Bucket of Tropical Fish", + "x": 352, + "y": 2016 + }, + "brick": { + "name": "Brick", + "x": 480, + "y": 3488 + }, + "clay_ball": { + "name": "Clay Ball", + "x": 544, + "y": 3488 + }, + "dried_kelp_block": { + "name": "Dried Kelp Block", + "x": 128, + "y": 1664 + }, + "paper": { + "name": "Paper", + "x": 64, + "y": 3520 + }, + "book": { + "name": "Book", + "x": 448, + "y": 3488 + }, + "slime_ball": { + "name": "Slimeball", + "x": 288, + "y": 3520 + }, + "chest_minecart": { + "name": "Minecart with Chest", + "x": 192, + "y": 3616 + }, + "furnace_minecart": { + "name": "Minecart with Furnace", + "x": 256, + "y": 3616 + }, + "egg": { + "name": "Egg", + "x": 128, + "y": 3200 + }, + "compass": { + "name": "Compass", + "x": 992, + "y": 480 + }, + "fishing_rod": { + "name": "Fishing Rod", + "x": 448, + "y": 3552 + }, + "clock": { + "name": "Clock", + "x": 960, + "y": 480 + }, + "glowstone_dust": { + "name": "Glowstone Dust", + "x": 768, + "y": 3488 + }, + "cod": { + "name": "Raw Cod", + "x": 896, + "y": 1664 + }, + "salmon": { + "name": "Raw Salmon", + "x": 928, + "y": 1664 + }, + "tropical_fish": { + "name": "Tropical Fish", + "x": 768, + "y": 1664 + }, + "pufferfish": { + "name": "Pufferfish", + "x": 864, + "y": 1664 + }, + "cooked_cod": { + "name": "Cooked Cod", + "x": 800, + "y": 1664 + }, + "cooked_salmon": { + "name": "Cooked Salmon", + "x": 832, + "y": 1664 + }, + "ink_sac": { + "name": "Ink Sac", + "x": 736, + "y": 3136 + }, + "cocoa_beans": { + "name": "Cocoa Beans", + "x": 608, + "y": 3136 + }, + "lapis_lazuli": { + "name": "Lapis Lazuli", + "x": 768, + "y": 3136 + }, + "white_dye": { + "name": "White Dye", + "x": 288, + "y": 2368 + }, + "orange_dye": { + "name": "Orange Dye", + "x": 928, + "y": 3136 + }, + "magenta_dye": { + "name": "Magenta Dye", + "x": 896, + "y": 3136 + }, + "light_blue_dye": { + "name": "Light Blue Dye", + "x": 800, + "y": 3136 + }, + "yellow_dye": { + "name": "Yellow Dye", + "x": 32, + "y": 3168 + }, + "lime_dye": { + "name": "Lime Dye", + "x": 864, + "y": 3136 + }, + "pink_dye": { + "name": "Pink Dye", + "x": 960, + "y": 3136 + }, + "gray_dye": { + "name": "Gray Dye", + "x": 672, + "y": 3136 + }, + "light_gray_dye": { + "name": "Light Gray Dye", + "x": 832, + "y": 3136 + }, + "cyan_dye": { + "name": "Cyan Dye", + "x": 640, + "y": 3136 + }, + "purple_dye": { + "name": "Purple Dye", + "x": 992, + "y": 3136 + }, + "blue_dye": { + "name": "Blue Dye", + "x": 224, + "y": 2368 + }, + "brown_dye": { + "name": "Brown Dye", + "x": 256, + "y": 2368 + }, + "green_dye": { + "name": "Green Dye", + "x": 704, + "y": 3136 + }, + "red_dye": { + "name": "Red Dye", + "x": 0, + "y": 3168 + }, + "black_dye": { + "name": "Black Dye", + "x": 192, + "y": 2368 + }, + "bone_meal": { + "name": "Bone Meal", + "x": 576, + "y": 3136 + }, + "bone": { + "name": "Bone", + "x": 416, + "y": 3488 + }, + "sugar": { + "name": "Sugar", + "x": 576, + "y": 3200 + }, + "cake": { + "name": "Cake", + "x": 416, + "y": 3584 + }, + "white_bed": { + "name": "White Bed", + "x": 512, + "y": 3168 + }, + "orange_bed": { + "name": "Orange Bed", + "x": 384, + "y": 3168 + }, + "magenta_bed": { + "name": "Magenta Bed", + "x": 352, + "y": 3168 + }, + "light_blue_bed": { + "name": "Light Blue Bed", + "x": 256, + "y": 3168 + }, + "yellow_bed": { + "name": "Yellow Bed", + "x": 544, + "y": 3168 + }, + "lime_bed": { + "name": "Lime Bed", + "x": 320, + "y": 3168 + }, + "pink_bed": { + "name": "Pink Bed", + "x": 416, + "y": 3168 + }, + "gray_bed": { + "name": "Gray Bed", + "x": 192, + "y": 3168 + }, + "light_gray_bed": { + "name": "Light Gray Bed", + "x": 288, + "y": 3168 + }, + "cyan_bed": { + "name": "Cyan Bed", + "x": 160, + "y": 3168 + }, + "purple_bed": { + "name": "Purple Bed", + "x": 448, + "y": 3168 + }, + "blue_bed": { + "name": "Blue Bed", + "x": 96, + "y": 3168 + }, + "brown_bed": { + "name": "Brown Bed", + "x": 128, + "y": 3168 + }, + "green_bed": { + "name": "Green Bed", + "x": 224, + "y": 3168 + }, + "red_bed": { + "name": "Red Bed", + "x": 480, + "y": 3168 + }, + "black_bed": { + "name": "Black Bed", + "x": 64, + "y": 3168 + }, + "cookie": { + "name": "Cookie", + "x": 96, + "y": 3200 + }, + "filled_map": { + "name": "Map", + "x": 832, + "y": 3552 + }, + "shears": { + "name": "Shears", + "x": 960, + "y": 3552 + }, + "melon_slice": { + "name": "Melon Slice", + "x": 192, + "y": 3200 + }, + "dried_kelp": { + "name": "Dried Kelp", + "x": 544, + "y": 1664 + }, + "pumpkin_seeds": { + "name": "Pumpkin Seeds", + "x": 128, + "y": 1056 + }, + "melon_seeds": { + "name": "Melon Seeds", + "x": 800, + "y": 1024 + }, + "beef": { + "name": "Raw Beef", + "x": 384, + "y": 3200 + }, + "cooked_beef": { + "name": "Steak", + "x": 480, + "y": 832 + }, + "chicken": { + "name": "Raw Chicken", + "x": 416, + "y": 3200 + }, + "cooked_chicken": { + "name": "Cooked Chicken", + "x": 992, + "y": 3168 + }, + "rotten_flesh": { + "name": "Rotten Flesh", + "x": 544, + "y": 3200 + }, + "ender_pearl": { + "name": "Ender Pearl", + "x": 672, + "y": 3488 + }, + "blaze_rod": { + "name": "Blaze Rod", + "x": 448, + "y": 640 + }, + "ghast_tear": { + "name": "Ghast Tear", + "x": 96, + "y": 3136 + }, + "gold_nugget": { + "name": "Gold Nugget", + "x": 832, + "y": 3488 + }, + "nether_wart": { + "name": "Nether Wart", + "x": 704, + "y": 640 + }, + "potion": { + "name": "Potion of Luck", + "x": 960, + "y": 3424 + }, + "glass_bottle": { + "name": "Glass Bottle", + "x": 512, + "y": 3552 + }, + "spider_eye": { + "name": "Spider Eye", + "x": 256, + "y": 3136 + }, + "fermented_spider_eye": { + "name": "Fermented Spider Eye", + "x": 64, + "y": 3136 + }, + "blaze_powder": { + "name": "Blaze Powder", + "x": 416, + "y": 640 + }, + "magma_cream": { + "name": "Magma Cream", + "x": 192, + "y": 3136 + }, + "brewing_stand": { + "name": "Brewing Stand", + "x": 384, + "y": 3584 + }, + "cauldron": { + "name": "Cauldron", + "x": 448, + "y": 3584 + }, + "ender_eye": { + "name": "Eye of Ender", + "x": 384, + "y": 3552 + }, + "glistering_melon_slice": { + "name": "Glistering Melon Slice", + "x": 128, + "y": 3136 + }, + "bat_spawn_egg": { + "name": "Bat Spawn Egg", + "x": 672, + "y": 1120 + }, + "bee_spawn_egg": { + "name": "Bee Spawn Egg", + "x": 224, + "y": 96 + }, + "blaze_spawn_egg": { + "name": "Blaze Spawn Egg", + "x": 704, + "y": 1120 + }, + "cat_spawn_egg": { + "name": "Cat Spawn Egg", + "x": 288, + "y": 2400 + }, + "cave_spider_spawn_egg": { + "name": "Cave Spider Spawn Egg", + "x": 736, + "y": 1120 + }, + "chicken_spawn_egg": { + "name": "Chicken Spawn Egg", + "x": 768, + "y": 1120 + }, + "cod_spawn_egg": { + "name": "Cod Spawn Egg", + "x": 0, + "y": 1696 + }, + "cow_spawn_egg": { + "name": "Cow Spawn Egg", + "x": 800, + "y": 1120 + }, + "creeper_spawn_egg": { + "name": "Creeper Spawn Egg", + "x": 832, + "y": 1120 + }, + "dolphin_spawn_egg": { + "name": "Dolphin Spawn Egg", + "x": 448, + "y": 1952 + }, + "donkey_spawn_egg": { + "name": "Donkey Spawn Egg", + "x": 832, + "y": 1344 + }, + "drowned_spawn_egg": { + "name": "Drowned Spawn Egg", + "x": 960, + "y": 1696 + }, + "elder_guardian_spawn_egg": { + "name": "Elder Guardian Spawn Egg", + "x": 864, + "y": 1344 + }, + "enderman_spawn_egg": { + "name": "Enderman Spawn Egg", + "x": 864, + "y": 1120 + }, + "endermite_spawn_egg": { + "name": "Endermite Spawn Egg", + "x": 896, + "y": 1120 + }, + "evoker_spawn_egg": { + "name": "Evoker Spawn Egg", + "x": 64, + "y": 1408 + }, + "fox_spawn_egg": { + "name": "Fox Spawn Egg", + "x": 992, + "y": 2432 + }, + "ghast_spawn_egg": { + "name": "Ghast Spawn Egg", + "x": 928, + "y": 1120 + }, + "guardian_spawn_egg": { + "name": "Guardian Spawn Egg", + "x": 960, + "y": 1120 + }, + "hoglin_spawn_egg": { + "name": "Hoglin Spawn Egg", + "x": 32, + "y": 192 + }, + "horse_spawn_egg": { + "name": "Horse Spawn Egg", + "x": 992, + "y": 1120 + }, + "husk_spawn_egg": { + "name": "Husk Spawn Egg", + "x": 896, + "y": 1344 + }, + "llama_spawn_egg": { + "name": "Llama Spawn Egg", + "x": 96, + "y": 1408 + }, + "magma_cube_spawn_egg": { + "name": "Magma Cube Spawn Egg", + "x": 0, + "y": 1152 + }, + "mooshroom_spawn_egg": { + "name": "Mooshroom Spawn Egg", + "x": 32, + "y": 1152 + }, + "mule_spawn_egg": { + "name": "Mule Spawn Egg", + "x": 928, + "y": 1344 + }, + "ocelot_spawn_egg": { + "name": "Ocelot Spawn Egg", + "x": 64, + "y": 1152 + }, + "panda_spawn_egg": { + "name": "Panda Spawn Egg", + "x": 352, + "y": 2400 + }, + "parrot_spawn_egg": { + "name": "Parrot Spawn Egg", + "x": 224, + "y": 1536 + }, + "phantom_spawn_egg": { + "name": "Phantom Spawn Egg", + "x": 672, + "y": 1952 + }, + "pig_spawn_egg": { + "name": "Pig Spawn Egg", + "x": 96, + "y": 1152 + }, + "piglin_spawn_egg": { + "name": "Piglin Spawn Egg", + "x": 0, + "y": 192 + }, + "piglin_brute_spawn_egg": { + "name": "Piglin Brute Spawn Egg", + "x": 160, + "y": 256 + }, + "pillager_spawn_egg": { + "name": "Pillager Spawn Egg", + "x": 384, + "y": 2400 + }, + "polar_bear_spawn_egg": { + "name": "Polar Bear Spawn Egg", + "x": 960, + "y": 1344 + }, + "pufferfish_spawn_egg": { + "name": "Pufferfish Spawn Egg", + "x": 32, + "y": 1696 + }, + "rabbit_spawn_egg": { + "name": "Rabbit Spawn Egg", + "x": 128, + "y": 1152 + }, + "ravager_spawn_egg": { + "name": "Ravager Spawn Egg", + "x": 320, + "y": 2400 + }, + "salmon_spawn_egg": { + "name": "Salmon Spawn Egg", + "x": 64, + "y": 1696 + }, + "sheep_spawn_egg": { + "name": "Sheep Spawn Egg", + "x": 160, + "y": 1152 + }, + "shulker_spawn_egg": { + "name": "Shulker Spawn Egg", + "x": 192, + "y": 1152 + }, + "silverfish_spawn_egg": { + "name": "Silverfish Spawn Egg", + "x": 224, + "y": 1152 + }, + "skeleton_spawn_egg": { + "name": "Skeleton Spawn Egg", + "x": 256, + "y": 1152 + }, + "skeleton_horse_spawn_egg": { + "name": "Skeleton Horse Spawn Egg", + "x": 992, + "y": 1344 + }, + "slime_spawn_egg": { + "name": "Slime Spawn Egg", + "x": 288, + "y": 1152 + }, + "spider_spawn_egg": { + "name": "Spider Spawn Egg", + "x": 320, + "y": 1152 + }, + "squid_spawn_egg": { + "name": "Squid Spawn Egg", + "x": 352, + "y": 1152 + }, + "stray_spawn_egg": { + "name": "Stray Spawn Egg", + "x": 0, + "y": 1376 + }, + "strider_spawn_egg": { + "name": "Strider Spawn Egg", + "x": 960, + "y": 192 + }, + "trader_llama_spawn_egg": { + "name": "Trader Llama Spawn Egg", + "x": 928, + "y": 2432 + }, + "tropical_fish_spawn_egg": { + "name": "Tropical Fish Spawn Egg", + "x": 800, + "y": 1696 + }, + "turtle_spawn_egg": { + "name": "Turtle Spawn Egg", + "x": 0, + "y": 1728 + }, + "vex_spawn_egg": { + "name": "Vex Spawn Egg", + "x": 128, + "y": 1408 + }, + "villager_spawn_egg": { + "name": "Villager Spawn Egg", + "x": 384, + "y": 1152 + }, + "vindicator_spawn_egg": { + "name": "Vindicator Spawn Egg", + "x": 160, + "y": 1408 + }, + "wandering_trader_spawn_egg": { + "name": "Wandering Trader Spawn Egg", + "x": 960, + "y": 2432 + }, + "witch_spawn_egg": { + "name": "Witch Spawn Egg", + "x": 416, + "y": 1152 + }, + "wither_skeleton_spawn_egg": { + "name": "Wither Skeleton Spawn Egg", + "x": 32, + "y": 1376 + }, + "wolf_spawn_egg": { + "name": "Wolf Spawn Egg", + "x": 448, + "y": 1152 + }, + "zoglin_spawn_egg": { + "name": "Zoglin Spawn Egg", + "x": 352, + "y": 256 + }, + "zombie_spawn_egg": { + "name": "Zombie Spawn Egg", + "x": 480, + "y": 1152 + }, + "zombie_horse_spawn_egg": { + "name": "Zombie Horse Spawn Egg", + "x": 64, + "y": 1376 + }, + "zombie_villager_spawn_egg": { + "name": "Zombie Villager Spawn Egg", + "x": 128, + "y": 1344 + }, + "zombified_piglin_spawn_egg": { + "name": "Zombified Piglin Spawn Egg", + "x": 512, + "y": 1152 + }, + "experience_bottle": { + "name": "Bottle o' Enchanting", + "x": 288, + "y": 3456 + }, + "fire_charge": { + "name": "Fire Charge", + "x": 416, + "y": 3552 + }, + "writable_book": { + "name": "Book and Quill", + "x": 352, + "y": 3520 + }, + "written_book": { + "name": "Written Book", + "x": 256, + "y": 3584 + }, + "emerald": { + "name": "Emerald", + "x": 640, + "y": 3488 + }, + "item_frame": { + "name": "Item Frame", + "x": 480, + "y": 3136 + }, + "flower_pot": { + "name": "Flower Pot", + "x": 448, + "y": 3136 + }, + "carrot": { + "name": "Carrot", + "x": 928, + "y": 3168 + }, + "potato": { + "name": "Potato", + "x": 288, + "y": 3200 + }, + "baked_potato": { + "name": "Baked Potato", + "x": 800, + "y": 3168 + }, + "poisonous_potato": { + "name": "Poisonous Potato", + "x": 256, + "y": 3200 + }, + "map": { + "name": "Empty Map", + "x": 320, + "y": 3552 + }, + "golden_carrot": { + "name": "Golden Carrot", + "x": 160, + "y": 3136 + }, + "skeleton_skull": { + "name": "Skeleton Skull", + "x": 192, + "y": 960 + }, + "wither_skeleton_skull": { + "name": "Wither Skeleton Skull", + "x": 224, + "y": 960 + }, + "player_head": { + "name": "Player Head", + "x": 160, + "y": 960 + }, + "zombie_head": { + "name": "Zombie Head", + "x": 256, + "y": 960 + }, + "creeper_head": { + "name": "Creeper Head", + "x": 96, + "y": 960 + }, + "dragon_head": { + "name": "Dragon Head", + "x": 128, + "y": 960 + }, + "carrot_on_a_stick": { + "name": "Carrot on a Stick", + "x": 416, + "y": 3520 + }, + "warped_fungus_on_a_stick": { + "name": "Warped Fungus on a Stick", + "x": 992, + "y": 192 + }, + "nether_star": { + "name": "Nether Star", + "x": 64, + "y": 352 + }, + "pumpkin_pie": { + "name": "Pumpkin Pie", + "x": 320, + "y": 3200 + }, + "firework_rocket": { + "name": "Firework Rocket", + "x": 736, + "y": 3168 + }, + "firework_star": { + "name": "Firework Star", + "x": 256, + "y": 800 + }, + "enchanted_book": { + "name": "Enchanted Book", + "x": 352, + "y": 3552 + }, + "nether_brick": { + "name": "Nether Brick", + "x": 992, + "y": 3488 + }, + "quartz": { + "name": "Nether Quartz", + "x": 0, + "y": 3520 + }, + "tnt_minecart": { + "name": "Minecart with TNT", + "x": 320, + "y": 3616 + }, + "hopper_minecart": { + "name": "Minecart with Hopper", + "x": 288, + "y": 3616 + }, + "prismarine_shard": { + "name": "Prismarine Shard", + "x": 160, + "y": 3520 + }, + "prismarine_crystals": { + "name": "Prismarine Crystals", + "x": 128, + "y": 3520 + }, + "rabbit": { + "name": "Raw Rabbit", + "x": 512, + "y": 3200 + }, + "cooked_rabbit": { + "name": "Cooked Rabbit", + "x": 64, + "y": 3200 + }, + "rabbit_stew": { + "name": "Rabbit Stew", + "x": 352, + "y": 3200 + }, + "rabbit_foot": { + "name": "Rabbit's Foot", + "x": 224, + "y": 3136 + }, + "rabbit_hide": { + "name": "Rabbit Hide", + "x": 192, + "y": 3520 + }, + "armor_stand": { + "name": "Armor Stand", + "x": 416, + "y": 3136 + }, + "iron_horse_armor": { + "name": "Iron Horse Armor", + "x": 192, + "y": 32 + }, + "golden_horse_armor": { + "name": "Golden Horse Armor", + "x": 32, + "y": 32 + }, + "diamond_horse_armor": { + "name": "Diamond Horse Armor", + "x": 896, + "y": 0 + }, + "leather_horse_armor": { + "name": "Leather Horse Armor", + "x": 160, + "y": 1376 + }, + "lead": { + "name": "Lead", + "x": 800, + "y": 3552 + }, + "name_tag": { + "name": "Name Tag", + "x": 864, + "y": 3552 + }, + "command_block_minecart": { + "name": "Minecart with Command Block", + "x": 224, + "y": 3616 + }, + "mutton": { + "name": "Raw Mutton", + "x": 448, + "y": 3200 + }, + "cooked_mutton": { + "name": "Cooked Mutton", + "x": 0, + "y": 3200 + }, + "white_banner": { + "name": "White Banner", + "x": 608, + "y": 768 + }, + "orange_banner": { + "name": "Orange Banner", + "x": 864, + "y": 736 + }, + "magenta_banner": { + "name": "Magenta Banner", + "x": 672, + "y": 736 + }, + "light_blue_banner": { + "name": "Light Blue Banner", + "x": 96, + "y": 736 + }, + "yellow_banner": { + "name": "Yellow Banner", + "x": 800, + "y": 768 + }, + "lime_banner": { + "name": "Lime Banner", + "x": 480, + "y": 736 + }, + "pink_banner": { + "name": "Pink Banner", + "x": 32, + "y": 768 + }, + "gray_banner": { + "name": "Gray Banner", + "x": 736, + "y": 704 + }, + "light_gray_banner": { + "name": "Light Gray Banner", + "x": 288, + "y": 736 + }, + "cyan_banner": { + "name": "Cyan Banner", + "x": 544, + "y": 704 + }, + "purple_banner": { + "name": "Purple Banner", + "x": 224, + "y": 768 + }, + "blue_banner": { + "name": "Blue Banner", + "x": 160, + "y": 704 + }, + "brown_banner": { + "name": "Brown Banner", + "x": 352, + "y": 704 + }, + "green_banner": { + "name": "Green Banner", + "x": 928, + "y": 704 + }, + "red_banner": { + "name": "Red Banner", + "x": 416, + "y": 768 + }, + "black_banner": { + "name": "Black Banner", + "x": 992, + "y": 672 + }, + "end_crystal": { + "name": "End Crystal", + "x": 608, + "y": 3168 + }, + "chorus_fruit": { + "name": "Chorus Fruit", + "x": 960, + "y": 3168 + }, + "popped_chorus_fruit": { + "name": "Popped Chorus Fruit", + "x": 96, + "y": 3520 + }, + "beetroot": { + "name": "Beetroot", + "x": 864, + "y": 3168 + }, + "beetroot_seeds": { + "name": "Beetroot Seeds", + "x": 320, + "y": 3392 + }, + "beetroot_soup": { + "name": "Beetroot Soup", + "x": 832, + "y": 3168 + }, + "dragon_breath": { + "name": "Dragon's Breath", + "x": 32, + "y": 3136 + }, + "splash_potion": { + "name": "Splash Potion of Luck", + "x": 512, + "y": 3456 + }, + "spectral_arrow": { + "name": "Spectral Arrow", + "x": 704, + "y": 3616 + }, + "tipped_arrow": { + "name": "Tipped Arrow", + "x": 544, + "y": 1248 + }, + "lingering_potion": { + "name": "Lingering Potion of Luck", + "x": 0, + "y": 3488 + }, + "shield": { + "name": "Shield", + "x": 960, + "y": 2496 + }, + "elytra": { + "name": "Elytra", + "x": 224, + "y": 2496 + }, + "spruce_boat": { + "name": "Spruce Boat", + "x": 384, + "y": 3616 + }, + "birch_boat": { + "name": "Birch Boat", + "x": 64, + "y": 3616 + }, + "jungle_boat": { + "name": "Jungle Boat", + "x": 128, + "y": 3616 + }, + "acacia_boat": { + "name": "Acacia Boat", + "x": 32, + "y": 3616 + }, + "dark_oak_boat": { + "name": "Dark Oak Boat", + "x": 96, + "y": 3616 + }, + "totem_of_undying": { + "name": "Totem of Undying", + "x": 768, + "y": 3616 + }, + "shulker_shell": { + "name": "Shulker Shell", + "x": 256, + "y": 3520 + }, + "iron_nugget": { + "name": "Iron Nugget", + "x": 928, + "y": 3488 + }, + "knowledge_book": { + "name": "Knowledge Book", + "x": 800, + "y": 3584 + }, + "debug_stick": { + "name": "Debug Stick", + "x": 608, + "y": 1120 + }, + "music_disc_pigstep": { + "name": "Music Disc 13", + "x": 0, + "y": 672 + }, + "trident": { + "name": "Trident", + "x": 608, + "y": 1696 + }, + "phantom_membrane": { + "name": "Phantom Membrane", + "x": 32, + "y": 1728 + }, + "nautilus_shell": { + "name": "Nautilus Shell", + "x": 448, + "y": 1728 + }, + "heart_of_the_sea": { + "name": "Heart of the Sea", + "x": 416, + "y": 1728 + }, + "crossbow": { + "name": "Crossbow", + "x": 416, + "y": 2368 + }, + "suspicious_stew": { + "name": "Suspicious Stew", + "x": 928, + "y": 2368 + }, + "loom": { + "name": "Loom", + "x": 416, + "y": 2432 + }, + "flower_banner_pattern": { + "name": "Banner Pattern", + "x": 704, + "y": 2368 + }, + "creeper_banner_pattern": { + "name": "Banner Pattern", + "x": 704, + "y": 2368 + }, + "skull_banner_pattern": { + "name": "Banner Pattern", + "x": 704, + "y": 2368 + }, + "mojang_banner_pattern": { + "name": "Banner Pattern", + "x": 704, + "y": 2368 + }, + "globe_banner_pattern": { + "name": "Banner Pattern", + "x": 704, + "y": 2368 + }, + "piglin_banner_pattern": { + "name": "Banner Pattern", + "x": 704, + "y": 2368 + }, + "composter": { + "name": "Composter", + "x": 864, + "y": 2432 + }, + "barrel": { + "name": "Barrel", + "x": 64, + "y": 2432 + }, + "smoker": { + "name": "Smoker", + "x": 480, + "y": 2432 + }, + "blast_furnace": { + "name": "Blast Furnace", + "x": 288, + "y": 2432 + }, + "cartography_table": { + "name": "Cartography Table", + "x": 608, + "y": 2432 + }, + "fletching_table": { + "name": "Fletching Table", + "x": 640, + "y": 2432 + }, + "grindstone": { + "name": "Grindstone", + "x": 896, + "y": 2432 + }, + "lectern": { + "name": "Lectern", + "x": 576, + "y": 0 + }, + "smithing_table": { + "name": "Smithing Table", + "x": 672, + "y": 2432 + }, + "stonecutter": { + "name": "Stonecutter BE", + "x": 800, + "y": 1056 + }, + "bell": { + "name": "Bell", + "x": 256, + "y": 2400 + }, + "lantern": { + "name": "Lantern", + "x": 576, + "y": 2432 + }, + "soul_lantern": { + "name": "Soul Lantern", + "x": 896, + "y": 128 + }, + "sweet_berries": { + "name": "Sweet Berries", + "x": 768, + "y": 2432 + }, + "campfire": { + "name": "Campfire", + "x": 832, + "y": 2432 + }, + "soul_campfire": { + "name": "Soul Campfire", + "x": 960, + "y": 256 + }, + "shroomlight": { + "name": "Shroomlight", + "x": 704, + "y": 128 + }, + "honeycomb": { + "name": "Honeycomb", + "x": 928, + "y": 32 + }, + "bee_nest": { + "name": "Bee Nest", + "x": 992, + "y": 32 + }, + "beehive": { + "name": "Beehive", + "x": 960, + "y": 32 + }, + "honey_bottle": { + "name": "Honey Bottle", + "x": 896, + "y": 32 + }, + "honey_block": { + "name": "Honey Block", + "x": 320, + "y": 96 + }, + "honeycomb_block": { + "name": "Honeycomb Block", + "x": 64, + "y": 96 + }, + "lodestone": { + "name": "Lodestone", + "x": 928, + "y": 192 + }, + "netherite_block": { + "name": "Block of Netherite", + "x": 96, + "y": 128 + }, + "ancient_debris": { + "name": "Ancient Debris", + "x": 160, + "y": 128 + }, + "target": { + "name": "Target", + "x": 96, + "y": 192 + }, + "crying_obsidian": { + "name": "Crying Obsidian", + "x": 128, + "y": 192 + }, + "blackstone": { + "name": "Blackstone", + "x": 96, + "y": 256 + }, + "blackstone_slab": { + "name": "Blackstone Slab", + "x": 608, + "y": 256 + }, + "blackstone_stairs": { + "name": "Blackstone Stairs", + "x": 640, + "y": 256 + }, + "gilded_blackstone": { + "name": "Gilded Blackstone", + "x": 512, + "y": 256 + }, + "polished_blackstone": { + "name": "Polished Blackstone", + "x": 544, + "y": 256 + }, + "polished_blackstone_slab": { + "name": "Polished Blackstone Slab", + "x": 800, + "y": 256 + }, + "polished_blackstone_stairs": { + "name": "Polished Blackstone Stairs", + "x": 832, + "y": 256 + }, + "chiseled_polished_blackstone": { + "name": "Chiseled Polished Blackstone", + "x": 416, + "y": 256 + }, + "polished_blackstone_bricks": { + "name": "Polished Blackstone Bricks", + "x": 576, + "y": 256 + }, + "polished_blackstone_brick_slab": { + "name": "Polished Blackstone Brick Slab", + "x": 704, + "y": 256 + }, + "polished_blackstone_brick_stairs": { + "name": "Polished Blackstone Brick Stairs", + "x": 736, + "y": 256 + }, + "cracked_polished_blackstone_bricks": { + "name": "Cracked Polished Blackstone Bricks", + "x": 480, + "y": 256 + }, + "respawn_anchor": { + "name": "Respawn Anchor", + "x": 448, + "y": 192 + } +} \ No newline at end of file diff --git a/lib/menus/components/bossbars_overlay.js b/lib/menus/components/bossbars_overlay.js new file mode 100644 index 0000000000000000000000000000000000000000..77d29166b631c9ed92607a414b251bd5bfb01a2a --- /dev/null +++ b/lib/menus/components/bossbars_overlay.js @@ -0,0 +1,147 @@ +const { LitElement, html, css } = require('lit') +const { styleMap } = require('lit/directives/style-map.js') + +const colors = ['pink', 'blue', 'red', 'green', 'yellow', 'purple', 'white'] +const divs = [0, 6, 10, 12, 20] +const translations = { + 'entity.minecraft.ender_dragon': 'Ender Dragon', + 'entity.minecraft.wither': 'Wither' +} +class BossBar extends LitElement { + constructor (bar) { + super() + this.bar = bar + this.title = '' + this.progress = 0 + this.bossBarStyles = {} + this.fillStyles = {} + this.div1Styles = {} + this.div2Styles = {} + } + + static get styles () { + return css` + .container { + display: flex; + flex-direction: column; + align-items: center; + } + .title { + font-size: 7px; + color: #fff; + } + .bossbar { + background-image: url("textures/1.18.1/gui/bars.png"); + width: 182px; + height: 5px; + position: relative; + } + .bossbar .fill { + content: ""; + position: absolute; + top: 0; + left: 0; + height: 5px; + width: 0; + background-image: url("textures/1.18.1/gui/bars.png"); + }` + } + + static get properties () { + return { + bar: { type: Object } + } + } + + render () { + this.updateBar(this.bar) + + return html` +
    +
    ${this.title}
    +
    +
    +
    +
    +
    +
    ` + } + + setTitle (bar) { + if (bar._title.text) this.title = bar._title.text + else this.title = translations[this._title.translate] || 'Unkown Entity' + } + + setColor (bar) { + this.bossBarStyles.backgroundPositionY = `-${colors.indexOf(bar._color) * 10}px` + this.fillStyles.backgroundPositionY = `-${colors.indexOf(bar._color) * 10 + 5}px` + } + + setProgress (bar) { + this.fillStyles.width = `${bar._health * 100}%` + this.div2Styles.width = `${bar._health * 100}%` + } + + setDiv (bar) { + this.div1Styles.backgroundPositionY = `-${divs.indexOf(bar._dividers) * 10 + 70}px` + this.div2Styles.backgroundPositionY = `-${divs.indexOf(bar._dividers) * 10 + 75}px` + } + + updateBar (bar) { + this.setTitle(bar) + this.setColor(bar) + this.setDiv(bar) + this.setProgress(bar) + } +} + +class BossBars extends LitElement { + constructor () { + super() + this.bossBars = new Map() + } + + static get styles () { + return css` + .bossBars { + display: flex; + flex-direction: column; + gap: 5px; + position: absolute; + top: 9px; + left: 50%; + transform: translate(-50%); + }` + } + + static get properties () { + return { + bossBars: { type: Map } + } + } + + render () { + return html`
    + ${[...this.bossBars.values()]} +
    ` + } + + init () { + this.bot.on('bossBarCreated', async (bossBar) => { + this.bossBars.set(bossBar.entityUUID, new BossBar(bossBar)) + this.requestUpdate() + }) + this.bot.on('bossBarUpdated', (bossBar) => { + const bar = this.bossBars.get(bossBar.entityUUID) + bar.bar = bossBar + bar.requestUpdate() + }) + this.bot.on('bossBarDeleted', (bossBar) => { + this.bossBars.delete(bossBar.entityUUID) + this.requestUpdate() + }) + } +} + +window.customElements.define('pmui-bossbars-overlay', BossBars) +window.customElements.define('pmui-bossbar', BossBar) diff --git a/lib/menus/components/breath_bar.js b/lib/menus/components/breath_bar.js new file mode 100644 index 0000000000000000000000000000000000000000..fe8fcca8a9888d000182bfbfbe930e6a457f5b53 --- /dev/null +++ b/lib/menus/components/breath_bar.js @@ -0,0 +1,88 @@ +const { LitElement, html, css } = require('lit') + +class BreathBar extends LitElement { + static get styles () { + return css` + .breathbar { + position: absolute; + display: flex; + flex-direction: row-reverse; + left: calc(50% + 91px); + transform: translate(-100%); + bottom: 40px; + --offset: calc(-1 * 16px); + --bg-x: calc(-1 * 16px); + --bg-y: calc(-1 * 18px); + } + + .breath { + width: 9px; + height: 9px; + margin-left: -1px; + } + + .breath.full { + background-image: url('textures/1.17.1/gui/icons.png'); + background-size: 256px; + background-position: var(--offset) var(--bg-y); + } + + .breath.half { + background-image: url('textures/1.17.1/gui/icons.png'); + background-size: 256px; + background-position: calc(var(--offset) - 9) var(--bg-y); + } + ` + } + + gameModeChanged (gamemode) { + this.shadowRoot.querySelector('#breathbar').classList.toggle('creative', gamemode === 1) + } + + updateOxygen (hValue) { + const breathbar = this.shadowRoot.querySelector('#breathbar') + breathbar.style.display = 'block' + + const breaths = breathbar.children + + for (let i = 0; i < breaths.length; i++) { + breaths[i].classList.remove('full') + breaths[i].classList.remove('half') + } + + for (let i = 0; i < Math.ceil(hValue / 2); i++) { + if (i >= breaths.length) break + + if (hValue % 2 !== 0 && Math.ceil(hValue / 2) === i + 1) { + breaths[i].classList.add('half') + } else { + breaths[i].classList.add('full') + } + } + + // if (hValue === 20) { + // setTimeout(() => { + // breathbar.style.display = 'none' + // }, 1000) + // } + } + + render () { + return html` +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + ` + } +} + +window.customElements.define('pmui-breathbar', BreathBar) diff --git a/lib/menus/components/button.js b/lib/menus/components/button.js new file mode 100644 index 0000000000000000000000000000000000000000..fde9b10e644364315a82964146bde0a37413d3a6 --- /dev/null +++ b/lib/menus/components/button.js @@ -0,0 +1,140 @@ +const { LitElement, html, css } = require('lit') + +const audioContext = new window.AudioContext() +const sounds = {} + +async function playSound (path) { + let volume = 1 + const options = document.getElementById('options-screen') + if (options) { + volume = options.sound / 100 + } + + let soundBuffer = sounds[path] + + if (!soundBuffer) { + const res = await window.fetch(path) + const data = await res.arrayBuffer() + + soundBuffer = await audioContext.decodeAudioData(data) + sounds[path] = soundBuffer + } + + const gainNode = audioContext.createGain() + const source = audioContext.createBufferSource() + source.buffer = soundBuffer + source.connect(gainNode) + gainNode.connect(audioContext.destination) + gainNode.gain.value = volume + source.start(0) +} + +class Button extends LitElement { + static get styles () { + return css` + .button { + --txrV: 66px; + position: relative; + width: 200px; + height: 20px; + font-family: minecraft, mojangles, monospace; + font-size: 10px; + color: white; + text-shadow: 1px 1px #222; + border: none; + z-index: 1; + outline: none; + } + + .button:hover, + .button:focus-visible { + --txrV: 86px; + } + + .button:disabled { + --txrV: 46px; + color: #A0A0A0; + text-shadow: 1px 1px #111; + } + + .button::after { + content: ''; + display: block; + position: absolute; + top: 0; + left: 0; + width: 50%; + height: 20px; + background: url('textures/1.17.1/gui/widgets.png'); + background-size: 256px; + background-position-y: calc(var(--txrV) * -1); + z-index: -1; + } + + .button::before { + content: ''; + display: block; + position: absolute; + top: 0; + left: 50%; + width: 50%; + height: 20px; + background: url('textures/1.17.1/gui/widgets.png'); + background-size: 256px; + background-position-x: calc(-200px + 100%); + background-position-y: calc(var(--txrV) * -1); + z-index: -1; + } + ` + } + + static get properties () { + return { + label: { + type: String, + attribute: 'pmui-label' + }, + width: { + type: String, + attribute: 'pmui-width' + }, + disabled: { + type: Boolean, + attribute: 'pmui-disabled' + }, + onPress: { + type: Function, + attribute: 'pmui-click' + } + } + } + + constructor () { + super() + this.label = '' + this.disabled = false + this.width = '200px' + this.onPress = () => {} + } + + render () { + return html` + ` + } + + onBtnClick () { + playSound('click_stereo.ogg') + this.dispatchEvent(new window.CustomEvent('pmui-click')) + } +} + +window.customElements.define('pmui-button', Button) +const _playSound = playSound +export { _playSound as playSound } diff --git a/lib/menus/components/common.js b/lib/menus/components/common.js new file mode 100644 index 0000000000000000000000000000000000000000..17b7275c9950d620b4cdd63f604227c3aa10e4bb --- /dev/null +++ b/lib/menus/components/common.js @@ -0,0 +1,72 @@ +import { css } from 'lit' + +const commonCss = css` + .dirt-bg { + position: absolute; + top: 0; + left: 0; + background: url('textures/1.17.1/gui/options_background.png'), rgba(0, 0, 0, 0.75); + background-size: 16px; + background-repeat: repeat; + width: 100%; + height: 100%; + transform-origin: top left; + transform: scale(2); + background-blend-mode: overlay; + } + + .bg { + position: absolute; + top: 0; + left: 0; + background: rgba(0, 0, 0, 0.75); + width: 100%; + height: 100%; + } + + .title { + position: absolute; + top: 0; + left: 50%; + transform: translate(-50%); + font-size: 10px; + color: white; + text-align: center; + text-shadow: 1px 1px #222; + } + + .text { + color: white; + font-size: 10px; + text-shadow: 1px 1px #222; + } +` + +/** @returns {boolean} */ +function isMobile () { + const m = require('ismobilejs').default() + return m.any +} + +/** + * @param {string} url + */ +function openURL (url) { + window.open(url, '_blank', 'noopener,noreferrer') +} + +/** + * @param {HTMLElement} prev + * @param {HTMLElement} next + */ +function displayScreen (prev, next) { + prev.style.display = 'none' + next.style.display = 'block' +} + +export { + commonCss, + isMobile, + openURL, + displayScreen +} diff --git a/lib/menus/components/debug_overlay.js b/lib/menus/components/debug_overlay.js new file mode 100644 index 0000000000000000000000000000000000000000..293929b11bb73867771ce16eb42ce46a8d0dc346 --- /dev/null +++ b/lib/menus/components/debug_overlay.js @@ -0,0 +1,146 @@ +const { LitElement, html, css } = require('lit') + +class DebugOverlay extends LitElement { + static get styles () { + return css` + .debug-left-side, + .debug-right-side { + position: absolute; + display: flex; + flex-direction: column; + z-index: 40; + } + + .debug-left-side { + top: 1px; + left: 1px; + } + + .debug-right-side { + top: 1px; + right: 1px; + } + + p { + display: block; + color: white; + font-size: 10px; + width: fit-content; + height: 9px; + margin: 0; + padding: 0; + padding-bottom: 1px; + background: rgba(110, 110, 110, 0.5); + } + + .debug-right-side p { + margin-left: auto; + } + + .empty { + display: block; + height: 9px; + } + ` + } + + static get properties () { + return { + showOverlay: { type: Boolean }, + cursorBlock: { type: Object }, + bot: { type: Object }, + customEntries: { type: Object } + } + } + + constructor () { + super() + this.showOverlay = false + this.customEntries = {} + } + + firstUpdated () { + document.addEventListener('keydown', e => { + e ??= window.event + if (e.key === 'F3') { + this.showOverlay = !this.showOverlay + e.preventDefault() + } + }) + } + + updated (changedProperties) { + if (changedProperties.has('bot')) { + this.bot.on('move', () => { + this.requestUpdate() + }) + this.bot.on('time', () => { + this.requestUpdate() + }) + this.bot.on('entitySpawn', () => { + this.requestUpdate() + }) + this.bot.on('entityGone', () => { + this.requestUpdate() + }) + } + } + + render () { + if (!this.showOverlay) { + return html`` + } + + const target = this.cursorBlock + const targetDiggable = (target && this.bot.canDigBlock(target)) + + const pos = this.bot.entity.position + const rot = [this.bot.entity.yaw, this.bot.entity.pitch] + + const viewDegToMinecraft = (yaw) => yaw % 360 - 180 * (yaw < 0 ? -1 : 1) + + const quadsDescription = [ + 'north (towards negative Z)', + 'east (towards positive X)', + 'south (towards positive Z)', + 'west (towards negative X)' + ] + + const minecraftYaw = viewDegToMinecraft(rot[0] * -180 / Math.PI) + const minecraftQuad = Math.floor(((minecraftYaw + 180) / 90 + 0.5) % 4) + + const renderProp = (name, value) => { + return html`

    ${name}: ${typeof value === 'boolean' ? html`${value}` : value}

    ` + } + + const skyL = this.bot.world.getSkyLight(this.bot.entity.position) + const biomeId = this.bot.world.getBiome(this.bot.entity.position) + + return html` +
    +

    Prismarine Web Client (${this.bot.version})

    +

    E: ${Object.values(this.bot.entities).length}

    +

    ${this.bot.game.dimension}

    +
    +

    XYZ: ${pos.x.toFixed(3)} / ${pos.y.toFixed(3)} / ${pos.z.toFixed(3)}

    +

    Chunk: ${Math.floor(pos.x % 16)} ~ ${Math.floor(pos.z % 16)} in ${Math.floor(pos.x / 16)} ~ ${Math.floor(pos.z / 16)}

    +

    Facing (viewer): ${rot[0].toFixed(3)} ${rot[1].toFixed(3)}

    +

    Facing (minecraft): ${quadsDescription[minecraftQuad]} (${minecraftYaw.toFixed(1)} ${(rot[1] * -180 / Math.PI).toFixed(1)})

    +

    Light: ${skyL} (${skyL} sky)

    +

    Biome: minecraft:${window.mcData.biomesArray[biomeId].name}

    +

    Day: ${this.bot.time.day}

    +
    + ${Object.entries(this.customEntries).map(([name, value]) => html`

    ${name}: ${value}

    `)} +
    + +
    +

    Renderer: three.js r${global.THREE.REVISION}

    +
    + ${targetDiggable ? html`

    ${target.name}

    ${Object.entries(target.getProperties()).map(([n, p], idx, arr) => renderProp(n, p, arr[idx + 1]))}` : ''} + ${targetDiggable ? html`

    Looking at: ${target.position.x} ${target.position.y} ${target.position.z}

    ` : ''} +
    + ` + } +} + +window.customElements.define('pmui-debug-overlay', DebugOverlay) diff --git a/lib/menus/components/edit_box.js b/lib/menus/components/edit_box.js new file mode 100644 index 0000000000000000000000000000000000000000..38d206466c0ab819ede2483d9f3e874518a40103 --- /dev/null +++ b/lib/menus/components/edit_box.js @@ -0,0 +1,97 @@ +const { LitElement, html, css } = require('lit') + +class EditBox extends LitElement { + static get styles () { + return css` + .edit-container { + position: relative; + width: 200px; + height: 20px; + background: black; + border: 1px solid grey; + } + + .edit-container:hover, + .edit-container:focus-within { + border-color: white; + } + + .edit-container label { + position: absolute; + z-index: 2; + pointer-events: none; + bottom: 21px; + left: 0; + font-size: 10px; + color: rgb(206, 206, 206); + text-shadow: 1px 1px black; + } + + .edit-box { + position: relative; + outline: none; + border: none; + background: none; + left: 1px; + width: calc(100% - 2px); + height: 100%; + font-family: minecraft, mojangles, monospace; + font-size: 10px; + color: white; + text-shadow: 1px 1px #222; + } + ` + } + + constructor () { + super() + this.width = '200px' + this.id = '' + this.value = '' + this.label = '' + } + + static get properties () { + return { + width: { + type: String, + attribute: 'pmui-width' + }, + id: { + type: String, + attribute: 'pmui-id' + }, + label: { + type: String, + attribute: 'pmui-label' + }, + value: { + type: String, + attribute: 'pmui-value' + } + } + } + + render () { + return html` +
    + + { this.value = e.target.value }} + class="edit-box"> +
    + ` + } +} + +window.customElements.define('pmui-editbox', EditBox) diff --git a/lib/menus/components/food_bar.js b/lib/menus/components/food_bar.js new file mode 100644 index 0000000000000000000000000000000000000000..de1342ce32ca96ba870b56feb7ee41a56aa8c453 --- /dev/null +++ b/lib/menus/components/food_bar.js @@ -0,0 +1,117 @@ +const { LitElement, html, css } = require('lit') + +class FoodBar extends LitElement { + static get styles () { + return css` + .foodbar { + position: absolute; + display: flex; + flex-direction: row-reverse; + left: calc(50% + 91px); + transform: translate(-100%); + bottom: 30px; + --lightened: 0; + --offset: calc(-1 * (52px)); + --bg-x: calc(-1 * (16px + 9px * var(--lightened))); + --bg-y: calc(-1 * 27px); + } + + .food { + width: 9px; + height: 9px; + background-image: url('textures/1.17.1/gui/icons.png'), url('textures/1.17.1/gui/icons.png'); + background-size: 256px, 256px; + background-position: var(--bg-x) var(--bg-y), var(--bg-x) var(--bg-y); + margin-left: -1px; + } + + .food.full { + background-position: var(--offset) var(--bg-y), var(--bg-x) var(--bg-y); + } + + .food.half { + background-position: calc(var(--offset) - 9px) var(--bg-y), var(--bg-x) var(--bg-y); + } + + .foodbar.low .food { + animation: lowHungerAnim 0.2s steps(2, end) infinite; + } + + .foodbar.low .food:nth-of-type(2n) { + animation-direction: reverse; + } + + .foodbar.low .food:nth-of-type(3n) { + animation-duration: 0.1s; + } + + .foodbar.updated { + animation: updatedAnim 0.3s steps(2, end) 2; + } + + @keyframes lowHungerAnim { + to { transform: translateY(1px); } + } + + @keyframes updatedAnim { + to { --lightened: 1; } + } + ` + } + + gameModeChanged (gamemode) { + this.shadowRoot.querySelector('#foodbar').classList.toggle('creative', gamemode === 1) + } + + onHungerUpdate () { + this.shadowRoot.querySelector('#foodbar').classList.toggle('updated', true) + if (this.hungerTimeout) clearTimeout(this.hungerTimeout) + this.hungerTimeout = setTimeout(() => { + this.shadowRoot.querySelector('#foodbar').classList.toggle('updated', false) + this.hungerTimeout = null + }, 1000) + } + + updateHunger (hValue, d) { + const foodbar = this.shadowRoot.querySelector('#foodbar') + foodbar.classList.toggle('low', hValue <= 5) + + const foods = foodbar.children + + for (let i = 0; i < foods.length; i++) { + foods[i].classList.remove('full') + foods[i].classList.remove('half') + } + + // if (d) this.onHungerUpdate() + + for (let i = 0; i < Math.ceil(hValue / 2); i++) { + if (i >= foods.length) break + + if (hValue % 2 !== 0 && Math.ceil(hValue / 2) === i + 1) { + foods[i].classList.add('half') + } else { + foods[i].classList.add('full') + } + } + } + + render () { + return html` +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + ` + } +} + +window.customElements.define('pmui-foodbar', FoodBar) diff --git a/lib/menus/components/health_bar.js b/lib/menus/components/health_bar.js new file mode 100644 index 0000000000000000000000000000000000000000..9c0d8803de8732b01f1361f5f35584ef6b1f98ba --- /dev/null +++ b/lib/menus/components/health_bar.js @@ -0,0 +1,158 @@ +const { LitElement, html, css } = require('lit') + +function getEffectClass (effect) { + switch (effect.id) { + case 19: return 'poisoned' + case 20: return 'withered' + case 22: return 'absorption' + default: return '' + } +} + +class HealthBar extends LitElement { + static get styles () { + return css` + .health { + position: absolute; + display: flex; + flex-direction: row; + left: calc(50% - 91px); + bottom: 30px; + --hardcore: 0; + --kind: 0; + --lightened: 0; + --offset: calc(-1 * (52px + (9px * (4 * var(--kind) + var(--lightened) * 2)) )); + --bg-x: calc(-1 * (16px + 9px * var(--lightened))); + --bg-y: calc(-1 * var(--hardcore) * 45px); + } + + .health.creative { + display: none; + } + + .health.hardcore { + --hardcore: 1; + } + + .health.poisoned { + --kind: 1; + } + + .health.withered { + --kind: 2; + } + + .health.absorption { + --kind: 3; + } + + .heart { + width: 9px; + height: 9px; + background-image: url('textures/1.17.1/gui/icons.png'), url('textures/1.17.1/gui/icons.png'); + background-size: 256px, 256px; + background-position: var(--bg-x) var(--bg-y), var(--bg-x) var(--bg-y); + margin-left: -1px; + } + + .heart.full { + background-position: var(--offset) var(--bg-y), var(--bg-x) var(--bg-y); + } + + .heart.half { + background-position: calc(var(--offset) - 9px) var(--bg-y), var(--bg-x) var(--bg-y); + } + + .health.low .heart { + animation: lowHealthAnim 0.2s steps(2, end) infinite; + } + + .health.low .heart:nth-of-type(2n) { + animation-direction: reverse; + } + + .health.low .heart:nth-of-type(3n) { + animation-duration: 0.1s; + } + + .health.damaged { + animation: damagedAnim 0.3s steps(2, end) 2; + } + + @keyframes lowHealthAnim { + to { + transform: translateY(1px); + } + } + + @keyframes damagedAnim { + to { --lightened: 1; } + } + ` + } + + effectAdded (effect) { + this.shadowRoot.querySelector('#health').classList.add(getEffectClass(effect)) + } + + effectEnded (effect) { + this.shadowRoot.querySelector('#health').classList.remove(getEffectClass(effect)) + } + + onDamage () { + this.shadowRoot.querySelector('#health').classList.toggle('damaged', true) + if (this.hurtTimeout) clearTimeout(this.hurtTimeout) + this.hurtTimeout = setTimeout(() => { + this.shadowRoot.querySelector('#health').classList.toggle('damaged', false) + this.hurtTimeout = null + }, 1000) + } + + gameModeChanged (gamemode, hardcore) { + this.shadowRoot.querySelector('#health').classList.toggle('creative', gamemode === 1) + this.shadowRoot.querySelector('#health').classList.toggle('hardcore', hardcore) + } + + updateHealth (hValue, d) { + const health = this.shadowRoot.querySelector('#health') + health.classList.toggle('low', hValue <= 4) + + const hearts = health.children + + for (let i = 0; i < hearts.length; i++) { + hearts[i].classList.remove('full') + hearts[i].classList.remove('half') + } + + if (d) this.onDamage() + + for (let i = 0; i < Math.ceil(hValue / 2); i++) { + if (i >= hearts.length) break + + if (hValue % 2 !== 0 && Math.ceil(hValue / 2) === i + 1) { + hearts[i].classList.add('half') + } else { + hearts[i].classList.add('full') + } + } + } + + render () { + return html` +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + ` + } +} + +window.customElements.define('pmui-healthbar', HealthBar) diff --git a/lib/menus/components/hotbar.js b/lib/menus/components/hotbar.js new file mode 100644 index 0000000000000000000000000000000000000000..e98a584864913eadf246cdf548e45070d26830af --- /dev/null +++ b/lib/menus/components/hotbar.js @@ -0,0 +1,210 @@ +const { LitElement, html, css } = require('lit') +const invsprite = require('../../invsprite.json') + +class Hotbar extends LitElement { + static get styles () { + return css` + .hotbar { + position: absolute; + bottom: 0; + left: 50%; + transform: translate(-50%); + width: 182px; + height: 22px; + background: url("textures/1.16.4/gui/widgets.png"); + background-size: 256px; + } + + #hotbar-selected { + position: absolute; + left: -1px; + top: -1px; + width: 24px; + height: 24px; + background: url("textures/1.16.4/gui/widgets.png"); + background-size: 256px; + background-position-y: -22px; + } + + #hotbar-items-wrapper { + position: absolute; + top: 0; + left: 1px; + display: flex; + flex-direction: row; + height: 22px; + margin: 0; + padding: 0; + } + + .hotbar-item { + position: relative; + width: 20px; + height: 22px; + } + + .item-icon { + top: 3px; + left: 2px; + position: absolute; + width: 32px; + height: 32px; + transform-origin: top left; + transform: scale(0.5); + background-image: url('invsprite.png'); + background-size: 1024px auto; + } + + .item-stack { + position: absolute; + color: white; + font-size: 10px; + text-shadow: 1px 1px 0 rgb(63, 63, 63); + right: 1px; + bottom: 1px; + } + + #hotbar-item-name { + color: white; + position: absolute; + bottom: 51px; + left: 50%; + transform: translate(-50%); + text-shadow: rgb(63, 63, 63) 1px 1px 0px; + font-family: mojangles, minecraft, monospace; + font-size: 10px; + text-align: center; + } + + .hotbar-item-name-fader { + opacity: 0; + transition: visibility 0s, opacity 1s linear; + transition-delay: 2s; + } + ` + } + + static get properties () { + return { + activeItemName: { type: String }, + bot: { type: Object }, + viewerVersion: { type: String } + } + } + + constructor () { + super() + this.activeItemName = '' + } + + updated (changedProperties) { + if (changedProperties.has('bot')) { + // inventory listener + this.bot.once('spawn', () => { + this.init() + }) + } + } + + init () { + this.reloadHotbar() + this.reloadHotbarSelected(0) + + document.addEventListener('wheel', (e) => { + const newSlot = ((this.bot.quickBarSlot + Math.sign(e.deltaY)) % 9 + 9) % 9 + this.reloadHotbarSelected(newSlot) + }) + + document.addEventListener('keydown', (e) => { + const numPressed = e.code.substr(5) + if (numPressed < 1 || numPressed > 9) return + this.reloadHotbarSelected(numPressed - 1) + }) + + this.bot.inventory.on('updateSlot', (slot, oldItem, newItem) => { + if (slot >= this.bot.inventory.hotbarStart + 9) return + if (slot < this.bot.inventory.hotbarStart) return + + const sprite = newItem ? invsprite[newItem.name] : invsprite.air + const slotEl = this.shadowRoot.getElementById('hotbar-' + (slot - this.bot.inventory.hotbarStart)) + const slotIcon = slotEl.children[0] + const slotStack = slotEl.children[1] + slotIcon.style['background-position-x'] = `-${sprite.x}px` + slotIcon.style['background-position-y'] = `-${sprite.y}px` + slotStack.innerHTML = newItem?.count > 1 ? newItem.count : '' + }) + } + + async reloadHotbar () { + for (let i = 0; i < 9; i++) { + const item = this.bot.inventory.slots[this.bot.inventory.hotbarStart + i] + const sprite = item ? invsprite[item.name] : invsprite.air + const slotEl = this.shadowRoot.getElementById('hotbar-' + i) + const slotIcon = slotEl.children[0] + const slotStack = slotEl.children[1] + slotIcon.style['background-position-x'] = `-${sprite.x}px` + slotIcon.style['background-position-y'] = `-${sprite.y}px` + slotStack.innerHTML = item?.count > 1 ? item.count : '' + } + } + + async reloadHotbarSelected (slot) { + const item = this.bot.inventory.slots[this.bot.inventory.hotbarStart + slot] + const newLeftPos = (-1 + 20 * slot) + 'px' + this.shadowRoot.getElementById('hotbar-selected').style.left = newLeftPos + this.bot.setQuickBarSlot(slot) + this.activeItemName = item?.displayName ?? '' + const name = this.shadowRoot.getElementById('hotbar-item-name') + name.classList.remove('hotbar-item-name-fader') + setTimeout(() => name.classList.add('hotbar-item-name-fader'), 10) + } + + render () { + return html` +
    +

    ${this.activeItemName}

    +
    +
    +
    +
    + +
    +
    +
    + +
    +
    +
    + +
    +
    +
    + +
    +
    +
    + +
    +
    +
    + +
    +
    +
    + +
    +
    +
    + +
    +
    +
    + +
    +
    +
    + ` + } +} + +window.customElements.define('pmui-hotbar', Hotbar) diff --git a/lib/menus/components/playerlist_overlay.js b/lib/menus/components/playerlist_overlay.js new file mode 100644 index 0000000000000000000000000000000000000000..c98441d8a51e6dd84a0751fced62ddf8f85d98cc --- /dev/null +++ b/lib/menus/components/playerlist_overlay.js @@ -0,0 +1,179 @@ +const { LitElement, html, css } = require('lit') + +const MAX_ROWS_PER_COL = 10 + +class PlayerListOverlay extends LitElement { + static get styles () { + return css` + .playerlist-container { + position: absolute; + background-color: rgba(0, 0, 0, 0.3); + top: 9px; + left: 50%; + transform: translate(-50%); + width: fit-content; + padding: 1px; + display: flex; + flex-direction: column; + gap: 1px 0; + place-items: center; + z-index: 30; + } + + .title { + color: white; + text-shadow: 1px 1px 0px #3f3f3f; + font-size: 10px; + margin: 0; + padding: 0; + } + + .playerlist-entry { + overflow: hidden; + color: white; + font-size: 10px; + margin: 0px; + line-height: calc(100% - 1px); + text-shadow: 1px 1px 0px #3f3f3f; + font-family: mojangles, minecraft, monospace; + background: rgba(255, 255, 255, 0.1); + width: 100%; + } + + .active-player { + color: rgb(42, 204, 237); + text-shadow: 1px 1px 0px rgb(4, 44, 67); + } + + .playerlist-ping { + text-align: right; + float: right; + padding-left: 10px; + } + + .playerlist-ping-value { + color: rgb(114, 255, 114); + text-shadow: 1px 1px 0px rgb(28, 105, 28); + float: left; + margin: 0; + margin-right: 1px; + } + + .playerlist-ping-label { + text-shadow: 1px 1px 0px #3f3f3f; + color: white; + float: right; + margin: 0px; + } + + .player-lists { + display: flex; + flex-direction: row; + place-items: center; + place-content: center; + gap: 0 4px; + } + + .player-list { + display: flex; + flex-direction: column; + gap: 1px 0; + min-width: 80px; + } + ` + } + + static get properties () { + return { + serverIP: { type: String }, + clientId: { type: String }, + players: { type: Object } + } + } + + constructor () { + super() + this.serverIP = '' + this.clientId = '' + this.players = {} + } + + init (bot, ip) { + const playerList = this.shadowRoot.querySelector('#playerlist-container') + + this.isOpen = false + this.players = bot.players + this.clientId = bot.player.uuid + this.serverIP = ip + + this.requestUpdate() + + const showList = (shouldShow = true) => { + playerList.style.display = shouldShow ? 'block' : 'none' + this.isOpen = shouldShow + } + + document.addEventListener('keydown', e => { + e ??= window.event + if (e.key === 'Tab') { + showList(true) + e.preventDefault() + } + }) + + document.addEventListener('keyup', e => { + if (!this.isOpen) return + e ??= window.event + if (e.key === 'Tab') { + showList(false) + e.preventDefault() + } + }) + + bot.on('playerUpdated', () => this.requestUpdate()) // LitElement seems to be batching requests, so it should be fine? + bot.on('playerJoined', () => this.requestUpdate()) + bot.on('playerLeft', () => this.requestUpdate()) + } + + render () { + const lists = [] + const players = Object.values(this.players).sort((a, b) => { + if (a.username > b.username) return 1 + if (a.username < b.username) return -1 + return 0 + }) + + let tempList = [] + for (let i = 0; i < players.length; i++) { + tempList.push(players[i]) + + if ((i + 1) / MAX_ROWS_PER_COL === 1 || i + 1 === players.length) { + lists.push([...tempList]) + tempList = [] + } + } + + return html` + + ` + } +} + +window.customElements.define('pmui-playerlist-overlay', PlayerListOverlay) diff --git a/lib/menus/components/slider.js b/lib/menus/components/slider.js new file mode 100644 index 0000000000000000000000000000000000000000..57e2317b420b86ca4ceb853fcc545279b7c2f071 --- /dev/null +++ b/lib/menus/components/slider.js @@ -0,0 +1,176 @@ +const { LitElement, html, css } = require('lit') + +class Slider extends LitElement { + static get styles () { + return css` + .slider-container { + --txrV: -46px; + position: relative; + width: 150px; + height: 20px; + font-family: minecraft, mojangles, monospace; + font-size: 10px; + color: white; + text-shadow: 1px 1px #220; + z-index: 1; + } + + .slider-thumb { + --txrV: -66px; + pointer-events: none; + width: 8px; + height: 20px; + position: absolute; + top: 0; + left: 0; + z-index: 3; + } + + .slider-container:hover .slider-thumb { + --txrV: -86px; + } + + .slider-container::after, + .slider-thumb::after { + content: ''; + display: block; + position: absolute; + top: 0; + left: 0; + width: 50%; + height: 20px; + background: url('textures/1.17.1/gui/widgets.png'); + background-size: 256px; + background-position-y: var(--txrV); + z-index: -1; + } + + .slider-container::before, + .slider-thumb::before { + content: ''; + display: block; + position: absolute; + top: 0; + left: 50%; + width: 50%; + height: 20px; + background: url('textures/1.17.1/gui/widgets.png'); + background-size: 256px; + background-position-x: calc(-200px + 100%); + background-position-y: var(--txrV); + z-index: -1; + } + + .slider { + display: block; + position: absolute; + top: 0; + left: 0; + -webkit-appearance: none; + appearance: none; + background: none; + width: 100%; + height: 20px; + margin: 0; + } + + .slider::-webkit-slider-thumb { + -webkit-appearance: none; + position: relative; + appearance: none; + width: 8px; + height: 20px; + background: transparent; + } + + .slider::-moz-range-thumb { + width: 8px; + height: 20px; + background: transparent; + } + + label { + pointer-events: none; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + z-index: 6; + width: max-content; + } + ` + } + + constructor () { + super() + this.label = '' + this.type = '%' + this.width = '150px' + this.value = '50' + this.min = '0' + this.max = '100' + this.ratio = (Number(this.value) - Number(this.min)) / (Number(this.max) - Number(this.min)) + } + + updated () { + this.ratio = (Number(this.value) - Number(this.min)) / (Number(this.max) - Number(this.min)) + } + + static get properties () { + return { + label: { + type: String, + attribute: 'pmui-label' + }, + type: { + type: String, + attribute: 'pmui-type' + }, + width: { + type: String, + attribute: 'pmui-width' + }, + value: { + type: String, + attribute: 'pmui-value' + }, + min: { + type: String, + attribute: 'pmui-min' + }, + max: { + type: String, + attribute: 'pmui-max' + }, + ratio: { type: Number } + } + } + + render () { + return html` +
    + { + const range = e.target + this.ratio = (range.value - range.min) / (range.max - range.min) + this.value = range.value + }}> +
    + +
    + ` + } +} + +window.customElements.define('pmui-slider', Slider) diff --git a/lib/menus/hud.js b/lib/menus/hud.js new file mode 100644 index 0000000000000000000000000000000000000000..25b63cef882b8b8b9079bc17f6673a8c78f80a30 --- /dev/null +++ b/lib/menus/hud.js @@ -0,0 +1,370 @@ +const { LitElement, html, css } = require('lit') +const { isMobile } = require('./components/common') + +class Hud extends LitElement { + static get styles () { + return css` + :host { + position: absolute; + top: 0; + left: 0; + z-index: -2; + width: 100%; + height: 100%; + } + + .crosshair { + width: 16px; + height: 16px; + background: url('textures/1.17.1/gui/icons.png'); + background-size: 256px; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + z-index: 2; + } + + #xp-label { + position: absolute; + top: -8px; + left: 50%; + transform: translate(-50%); + font-size: 10px; + font-family: minecraft, mojangles, monospace; + color: rgb(30, 250, 30); + text-shadow: 0px -1px #000, 0px 1px #000, 1px 0px #000, -1px 0px #000; + z-index: 10; + } + + #xp-bar-bg { + position: absolute; + left: 50%; + bottom: 24px; + transform: translate(-50%); + width: 182px; + height: 5px; + background-image: url('textures/1.16.4/gui/icons.png'); + background-size: 256px; + background-position-y: -64px; + } + + .xp-bar { + width: 182px; + height: 5px; + background-image: url('textures/1.17.1/gui/icons.png'); + background-size: 256px; + background-position-y: -69px; + } + + .mobile-top-btns { + display: none; + flex-direction: row; + position: absolute; + top: 0; + left: 50%; + transform: translate(-50%); + gap: 0 1px; + z-index: 20; + } + + .pause-btn, + .chat-btn { + border: none; + outline: none; + width: 18px; + height: 18px; + background-image: url('extra-textures/gui.png'); + background-size: 256px; + background-position-x: -200px; + background-position-y: -64px; + } + + .chat-btn { + background-position-y: -82px; + } + + .mobile-control-forward, + .mobile-control-back, + .mobile-control-left, + .mobile-control-sneak, + .mobile-control-jump, + .mobile-control-right { + position: absolute; + width: 22px; + height: 22px; + background-image: url('extra-textures/gui.png'); + background-size: 256px; + border: none; + outline: none; + } + + .mobile-control-forward:active, + .mobile-control-back:active, + .mobile-control-sneak:active, + .mobile-control-left:active, + .mobile-control-jump:active, + .mobile-control-right:active { + filter: brightness(85%); + } + + .mobile-control-forward { + top: 0; + left: 50%; + transform: translate(-50%); + background-position: -2px -109px; + } + + .mobile-control-back { + bottom: 0; + left: 50%; + transform: translate(-50%); + background-position: -54px -109px; + } + + .mobile-control-left { + top: 50%; + left: 0; + transform: translate(0, -50%); + background-position: -28px -109px; + } + + .mobile-control-right { + top: 50%; + right: 0; + transform: translate(0, -50%); + background-position: -80px -109px; + } + + .mobile-control-jump { + position: relative; + width: 18px; + height: 18px; + background-position: -108px -111px; + } + + .mobile-control-sneak { + width: 18px; + height: 18px; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background-position: -218px -82px; + } + .mobile-control-sneak.is-down { + background-position: -218px -64px; + } + + .mobile-controls-left { + display: none; + position: absolute; + left: 8px; + bottom: 0; + width: 70px; + height: 70px; + z-index: 30; + opacity: 0.65; + transform-origin: bottom left; + transform: scale(1.5); + } + + .mobile-controls-right { + display: none; + flex-direction: column; + place-items: center; + place-content: center; + position: absolute; + right: 20px; + bottom: 0; + width: 22px; + height: 70px; + z-index: 30; + opacity: 0.65; + transform-origin: bottom right; + transform: scale(1.5); + } + ` + } + + static get properties () { + return { + bot: { type: Object } + } + } + + /** + * @param {import('mineflayer').Bot} bot + */ + preload (bot) { + const bossBars = this.shadowRoot.querySelector('#bossbars-overlay') + bossBars.bot = bot + + bossBars.init() + } + + /** + * @param {globalThis.THREE.Renderer} renderer + * @param {import('mineflayer').Bot} bot + * @param {string} host + */ + init (renderer, bot, host) { + const debugMenu = this.shadowRoot.querySelector('#debug-overlay') + const playerList = this.shadowRoot.querySelector('#playerlist-overlay') + const healthbar = this.shadowRoot.querySelector('#health-bar') + const foodbar = this.shadowRoot.querySelector('#food-bar') + // const breathbar = this.shadowRoot.querySelector('#breath-bar') + const chat = this.shadowRoot.querySelector('#chat') + const hotbar = this.shadowRoot.querySelector('#hotbar') + const xpLabel = this.shadowRoot.querySelector('#xp-label') + + this.bot = bot + hotbar.bot = bot + debugMenu.bot = bot + + hotbar.init() + chat.init(bot._client, renderer) + bot.on('spawn', () => playerList.init(bot, host)) + + bot.on('entityHurt', (entity) => { + if (entity !== bot.entity) return + healthbar.onDamage() + }) + + bot.on('entityEffect', (entity, effect) => { + if (entity !== bot.entity) return + healthbar.effectAdded(effect) + }) + + bot.on('entityEffectEnd', (entity, effect) => { + if (entity !== bot.entity) return + healthbar.effectEnded(effect) + }) + + bot.on('game', () => { + healthbar.gameModeChanged(bot.player.gamemode, bot.game.hardcore) + foodbar.gameModeChanged(bot.player.gamemode) + // breathbar.gameModeChanged(bot.player.gamemode) + this.shadowRoot.querySelector('#xp-bar-bg').style.display = bot.player.gamemode === 1 ? 'none' : 'block' + }) + + bot.on('health', () => { + healthbar.updateHealth(bot.health, true) + foodbar.updateHunger(bot.food, true) + }) + + bot.on('experience', () => { + this.shadowRoot.querySelector('#xp-bar-bg').firstElementChild.style.width = `${182 * bot.experience.progress}px` + xpLabel.innerHTML = bot.experience.level + xpLabel.style.display = bot.experience.level > 0 ? 'block' : 'none' + }) + + // bot.on('breath', () => { + // breathbar.updateOxygen(bot.oxygenLevel) + // }) + + bot.on('spawn', () => { + this.shadowRoot.querySelector('#xp-bar-bg').style.display = bot.player.gamemode === 1 ? 'none' : 'block' + this.shadowRoot.querySelector('#xp-bar-bg').firstElementChild.style.width = `${182 * bot.experience.progress}px` + xpLabel.innerHTML = bot.experience.level + xpLabel.style.display = bot.experience.level > 0 ? 'block' : 'none' + healthbar.gameModeChanged(bot.player.gamemode, bot.game.hardcore) + healthbar.updateHealth(bot.health) + foodbar.updateHunger(bot.food) + // breathbar.updateOxygen(bot.oxygenLevel ?? 20) + }) + + if (document.getElementById('options-screen').forceMobileControls || isMobile()) { + this.showMobileControls(true) + } else { + this.showMobileControls(false) + } + } + + /** @param {boolean} bl */ + showMobileControls (bl) { + this.shadowRoot.querySelector('#mobile-top').style.display = bl ? 'flex' : 'none' + this.shadowRoot.querySelector('#mobile-left').style.display = bl ? 'block' : 'none' + this.shadowRoot.querySelector('#mobile-right').style.display = bl ? 'flex' : 'none' + } + + /** + * @param {string} id + * @param {boolean} action + */ + mobileControl (e, id, action) { + e.stopPropagation() + this.bot.setControlState(id, action) + } + + render () { + return html` +
    + + +
    +
    + + + + + +
    +
    + +
    + + + + +
    + + + + +
    +
    + +
    + + ` + } +} + +window.customElements.define('pmui-hud', Hud) diff --git a/lib/menus/keybinds_screen.js b/lib/menus/keybinds_screen.js new file mode 100644 index 0000000000000000000000000000000000000000..7598c2122786fef6e87c3b564c0529b31052483d --- /dev/null +++ b/lib/menus/keybinds_screen.js @@ -0,0 +1,172 @@ +const { LitElement, html, css } = require('lit') +const { commonCss, displayScreen } = require('./components/common') + +class KeyBindsScreen extends LitElement { + static get styles () { + return css` + ${commonCss} + .title { + top: 4px; + } + + main { + display: flex; + flex-direction: column; + position: absolute; + top: 30px; + left: 50%; + transform: translate(-50%); + width: 100%; + height: calc(100% - 64px); + place-items: center; + background: rgba(0, 0, 0, 0.5); + box-shadow: inset 0 3px 6px rgba(0, 0, 0, 0.7), inset 0 -3px 6px rgba(0, 0, 0, 0.7); + } + + .keymap-list { + width: 288px; + display: flex; + flex-direction: column; + padding: 4px 0; + overflow-y: auto; + } + + .keymap-list::-webkit-scrollbar { + width: 6px; + } + + .keymap-list::-webkit-scrollbar-track { + background: #000; + } + + .keymap-list::-webkit-scrollbar-thumb { + background: #ccc; + box-shadow: inset -1px -1px 0 #4f4f4f; + } + + .keymap-entry { + display: flex; + flex-direction: row; + width: 100%; + height: 20px; + place-content: center; + place-items: center; + justify-content: space-between; + } + + span { + color: white; + text-shadow: 1px 1px 0 rgb(63, 63, 63); + font-size: 10px; + } + + .keymap-entry-btns { + display: flex; + flex-direction: row; + gap: 4px; + } + + .bottom-btns { + display: flex; + flex-direction: row; + width: 310px; + height: 20px; + justify-content: space-between; + position: absolute; + bottom: 9px; + left: 50%; + transform: translate(-50%); + } + + ` + } + + static get properties () { + return { + keymaps: { type: Object }, + selected: { type: Number } + } + } + + constructor () { + super() + this.selected = -1 + this.keymaps = [ + { defaultKey: 'KeyW', key: 'KeyW', name: 'Walk Forwards' }, + { defaultKey: 'KeyS', key: 'KeyS', name: 'Walk Backwards' }, + { defaultKey: 'KeyA', key: 'KeyA', name: 'Strafe Left' }, + { defaultKey: 'KeyD', key: 'KeyD', name: 'Strafe Right' }, + { defaultKey: 'Space', key: 'Space', name: 'Jump' }, + { defaultKey: 'ShiftLeft', key: 'ShiftLeft', name: 'Sneak' }, + { defaultKey: 'ControlLeft', key: 'ControlLeft', name: 'Sprint' }, + { defaultKey: 'KeyT', key: 'KeyT', name: 'Open Chat' }, + { defaultKey: 'Slash', key: 'Slash', name: 'Open Command' }, + // { defaultKey: '0', key: '0', name: 'Attack/Destroy' }, + // { defaultKey: '1', key: '1', name: 'Place Block' }, + { defaultKey: 'KeyQ', key: 'KeyQ', name: 'Drop Item' } + // { defaultKey: 'Digit1', key: 'Digit1', name: 'Hotbar Slot 1' }, + // { defaultKey: 'Digit2', key: 'Digit2', name: 'Hotbar Slot 2' }, + // { defaultKey: 'Digit3', key: 'Digit3', name: 'Hotbar Slot 3' }, + // { defaultKey: 'Digit4', key: 'Digit4', name: 'Hotbar Slot 4' }, + // { defaultKey: 'Digit5', key: 'Digit5', name: 'Hotbar Slot 5' }, + // { defaultKey: 'Digit6', key: 'Digit6', name: 'Hotbar Slot 6' }, + // { defaultKey: 'Digit7', key: 'Digit7', name: 'Hotbar Slot 7' }, + // { defaultKey: 'Digit8', key: 'Digit8', name: 'Hotbar Slot 8' }, + // { defaultKey: 'Digit9', key: 'Digit9', name: 'Hotbar Slot 9' }, + // { defaultKey: 'KeyE', key: 'KeyE', name: 'Open Inventory' } + ] + + document.addEventListener('keydown', (e) => { + if (this.selected !== -1) { + this.keymaps[this.selected].key = e.code + this.selected = -1 + this.requestUpdate() + } + }) + } + + render () { + return html` +
    + +

    Key Binds

    + +
    +
    + ${this.keymaps.map((m, i) => html` +
    + ${m.name} + +
    + { + e.target.setAttribute('pmui-label', `> ${m.key} <`) + this.selected = i + this.requestUpdate() + }}> + { + this.keymaps[i].key = this.keymaps[i].defaultKey + this.requestUpdate() + this.selected = -1 + }}> +
    +
    + `)} +
    +
    + +
    + v.key !== v.defaultKey)} @pmui-click=${this.onResetAllPress}> + displayScreen(this, document.getElementById('options-screen'))}> +
    + ` + } + + onResetAllPress () { + for (let i = 0; i < this.keymaps.length; i++) { + this.keymaps[i].key = this.keymaps[i].defaultKey + } + this.requestUpdate() + } +} + +window.customElements.define('pmui-keybindsscreen', KeyBindsScreen) diff --git a/lib/menus/loading_screen.js b/lib/menus/loading_screen.js new file mode 100644 index 0000000000000000000000000000000000000000..3066022f041135dbad2ed4a2f9a5ef47e8890c23 --- /dev/null +++ b/lib/menus/loading_screen.js @@ -0,0 +1,67 @@ +const { LitElement, html, css } = require('lit') +const { commonCss } = require('./components/common') + +class LoadingScreen extends LitElement { + static get styles () { + return css` + ${commonCss} + .title { + top: 30px; + } + + #cancel-btn { + position: absolute; + top: calc(20% + 50px); + left: 50%; + transform: translate(-50%); + } + ` + } + + static get properties () { + return { + status: { type: String }, + loadingText: { type: String }, + hasError: { type: Number } + } + } + + constructor () { + super() + this.hasError = false + this.status = 'Waiting for JS load' + } + + firstUpdated () { + this.statusRunner() + } + + async statusRunner () { + const array = ['.', '..', '...', ''] + const timer = ms => new Promise((resolve) => setTimeout(resolve, ms)) + + const load = async () => { + for (let i = 0; true; i = ((i + 1) % array.length)) { + this.loadingText = this.status + array[i] + await timer(500) + } + } + + load() + } + + render () { + return html` +
    + +

    ${this.hasError ? this.status : this.loadingText}

    + + ${this.hasError + ? html` window.location.reload()}>` + : '' + } + ` + } +} + +window.customElements.define('pmui-loadingscreen', LoadingScreen) diff --git a/lib/menus/options_screen.js b/lib/menus/options_screen.js new file mode 100644 index 0000000000000000000000000000000000000000..a05ae55f2aa819770d3020c4a79dc30e771ae769 --- /dev/null +++ b/lib/menus/options_screen.js @@ -0,0 +1,158 @@ +const { LitElement, html, css } = require('lit') +const { commonCss, displayScreen, isMobile } = require('./components/common') + +class OptionsScreen extends LitElement { + static get styles () { + return css` + ${commonCss} + .title { + top: 4px; + } + + main { + display: flex; + flex-direction: column; + position: absolute; + top: calc(100% / 6 - 6px); + left: 50%; + width: 310px; + gap: 4px 0; + place-items: center; + place-content: center; + transform: translate(-50%); + } + + .wrapper { + display: flex; + flex-direction: row; + width: 100%; + gap: 0 10px; + height: 20px; + } + ` + } + + static get properties () { + return { + isInsideWorld: { type: Boolean }, + mouseSensitivityX: { type: Number }, + mouseSensitivityY: { type: Number }, + chatWidth: { type: Number }, + chatHeight: { type: Number }, + chatScale: { type: Number }, + sound: { type: Number }, + renderDistance: { type: Number }, + fov: { type: Number }, + guiScale: { type: Number } + } + } + + constructor () { + super() + this.isInsideWorld = false + + const getValue = (item, defaultValue, convertFn) => window.localStorage.getItem(item) ? convertFn(window.localStorage.getItem(item)) : defaultValue + + this.mouseSensitivityX = getValue('mouseSensX', 50, (v) => Math.floor(Number(v) * 10000)) + this.mouseSensitivityY = getValue('mouseSensY', 50, (v) => Math.floor(Number(v) * 10000)) + this.chatWidth = getValue('chatWidth', 320, (v) => Number(v)) + this.chatHeight = getValue('chatHeight', 180, (v) => Number(v)) + this.chatScale = getValue('chatScale', 100, (v) => Number(v)) + this.sound = getValue('sound', 50, (v) => Number(v)) + this.renderDistance = getValue('renderDistance', 6, (v) => Number(v)) + this.fov = getValue('fov', 75, (v) => Number(v)) + this.guiScale = getValue('guiScale', 3, (v) => Number(v)) + this.forceMobileControls = getValue('forceMobileControls', false, (v) => v === 'true') + + document.documentElement.style.setProperty('--chatScale', `${this.chatScale / 100}`) + document.documentElement.style.setProperty('--chatWidth', `${this.chatWidth}px`) + document.documentElement.style.setProperty('--chatHeight', `${this.chatHeight}px`) + document.documentElement.style.setProperty('--guiScale', `${this.guiScale}`) + } + + render () { + return html` +
    + +

    Options

    + +
    +
    + { + this.mouseSensitivityX = Number(e.target.value) + window.localStorage.setItem('mouseSensX', this.mouseSensitivityX * 0.0001) + }}> + { + this.mouseSensitivityY = Number(e.target.value) + window.localStorage.setItem('mouseSensY', this.mouseSensitivityY * 0.0001) + }}> +
    +
    + { + this.chatWidth = Number(e.target.value) + window.localStorage.setItem('chatWidth', `${this.chatWidth}`) + document.documentElement.style.setProperty('--chatWidth', `${this.chatWidth}px`) + }}> + { + this.chatHeight = Number(e.target.value) + window.localStorage.setItem('chatHeight', `${this.chatHeight}`) + document.documentElement.style.setProperty('--chatHeight', `${this.chatHeight}px`) + }}> +
    +
    + { + this.chatScale = Number(e.target.value) + window.localStorage.setItem('chatScale', `${this.chatScale}`) + document.documentElement.style.setProperty('--chatScale', `${this.chatScale / 100}`) + }}> + { + this.sound = Number(e.target.value) + window.localStorage.setItem('sound', `${this.sound}`) + }}> +
    +
    + displayScreen(this, document.getElementById('keybinds-screen'))}> + { + this.guiScale = Number(e.target.value) + window.localStorage.setItem('guiScale', `${this.guiScale}`) + document.documentElement.style.setProperty('--guiScale', `${this.guiScale}`) + }}> +
    + ${this.isInsideWorld +? '' +: html` +
    + { + this.renderDistance = Number(e.target.value) + window.localStorage.setItem('renderDistance', `${this.renderDistance}`) + }}> + { + this.fov = Number(e.target.value) + window.localStorage.setItem('fov', `${this.fov}`) + + this.dispatchEvent(new window.CustomEvent('fov_changed', { fov: this.fov })) + }}> +
    + `} + +
    + { + this.forceMobileControls = !this.forceMobileControls + window.localStorage.setItem('forceMobileControls', `${this.forceMobileControls}`) + if (this.forceMobileControls || isMobile()) { + document.getElementById('hud').showMobileControls(true) + } else { + document.getElementById('hud').showMobileControls(false) + } + this.requestUpdate() + } + }> +
    + + displayScreen(this, document.getElementById(this.isInsideWorld ? 'pause-screen' : 'title-screen'))}> +
    + ` + } +} + +window.customElements.define('pmui-optionsscreen', OptionsScreen) diff --git a/lib/menus/pause_screen.js b/lib/menus/pause_screen.js new file mode 100644 index 0000000000000000000000000000000000000000..d38567624c43d81cbf515da72db5c2d4ef2070a6 --- /dev/null +++ b/lib/menus/pause_screen.js @@ -0,0 +1,110 @@ +const { LitElement, html, css } = require('lit') +const { openURL, displayScreen } = require('./components/common') + +class PauseScreen extends LitElement { + static get styles () { + return css` + .bg { + position: absolute; + top: 0; + left: 0; + background: rgba(0, 0, 0, 0.75); + width: 100%; + height: 100%; + } + + .title { + position: absolute; + top: 40px; + left: 50%; + transform: translate(-50%); + font-size: 10px; + color: white; + text-shadow: 1px 1px #222; + } + + main { + display: flex; + flex-direction: column; + gap: 4px 0; + position: absolute; + left: 50%; + width: 204px; + top: calc(25% + 48px - 16px); + transform: translate(-50%); + } + + .row { + display: flex; + flex-direction: row; + justify-content: space-between; + width: 100%; + } + ` + } + + constructor () { + super() + this.inMenu = false + } + + init (renderer) { + const chat = document.getElementById('hud').shadowRoot.querySelector('#chat') + const self = this + + document.addEventListener('keydown', e => { + if (chat.inChat) return + e = e || window.event + if (e.keyCode === 27 || e.key === 'Escape' || e.key === 'Esc') { + if (self.inMenu) { + self.disableGameMenu(renderer) + } else { + self.enableGameMenu() + } + } + }) + } + + render () { + return html` +
    + +

    Game Menu

    + +
    + +
    + openURL('https://github.com/PrismarineJS/prismarine-web-client')}> + openURL('https://discord.gg/4Ucm684Fq3')}> +
    + displayScreen(this, document.getElementById('options-screen'))}> + window.location.reload()}> +
    + ` + } + + disableGameMenu (renderer = false) { + this.inMenu = false + this.style.display = 'none' + if (renderer) { + renderer.domElement.requestPointerLock() + } + } + + enableGameMenu () { + this.inMenu = true + document.exitPointerLock() + this.style.display = 'block' + this.focus() + } + + onReturnPress (renderer = false) { + this.inMenu = false + this.style.display = 'none' + if (renderer) { + renderer.domElement.requestPointerLock() + } + } +} + +window.customElements.define('pmui-pausescreen', PauseScreen) diff --git a/lib/menus/play_screen.js b/lib/menus/play_screen.js new file mode 100644 index 0000000000000000000000000000000000000000..57bd22efc251e1c1d51e59a63a309ec3aec58711 --- /dev/null +++ b/lib/menus/play_screen.js @@ -0,0 +1,163 @@ +const { LitElement, html, css } = require('lit') +const { commonCss, displayScreen } = require('./components/common') + +class PlayScreen extends LitElement { + static get styles () { + return css` + ${commonCss} + .title { + top: 12px; + } + + .edit-boxes { + position: absolute; + top: 59px; + left: 50%; + display: flex; + flex-direction: column; + gap: 14px 0; + transform: translate(-50%); + width: 310px; + } + + .wrapper { + width: 100%; + display: flex; + flex-direction: row; + gap: 0 4px; + } + + .button-wrapper { + display: flex; + flex-direction: row; + gap: 0 4px; + position: absolute; + bottom: 9px; + left: 50%; + transform: translate(-50%); + width: 310px; + } + + .extra-info-bv { + font-size: 10px; + color: rgb(206, 206, 206); + text-shadow: 1px 1px black; + position: absolute; + left: calc(50% + 2px); + bottom: -34px; + } + ` + } + + static get properties () { + return { + server: { type: String }, + serverport: { type: Number }, + proxy: { type: String }, + proxyport: { type: Number }, + username: { type: String }, + password: { type: String }, + version: { type: String } + } + } + + constructor () { + super() + this.username = window.localStorage.getItem('username') ?? 'pviewer' + (Math.floor(Math.random() * 1000)) + this.password = window.localStorage.getItem('password') ?? '' + window.fetch('config.json').then(res => res.json()).then(config => { + this.server = window.localStorage.getItem('server') ?? config.defaultHost + this.serverport = window.localStorage.getItem('serverport') ?? config.defaultHostPort ?? 25565 + this.proxy = window.localStorage.getItem('proxy') ?? config.defaultProxy + this.proxyport = window.localStorage.getItem('proxyport') ?? (!config.defaultProxy && !config.defaultProxyPort ? '' : config.defaultProxyPort ?? 443) + this.version = window.localStorage.getItem('version') ?? config.defaultVersion + }) + } + + render () { + return html` +
    + +

    Join a Server

    + +
    +
    + { this.server = e.target.value }} + > + { this.serverport = e.target.value }} + > +
    +
    + { this.proxy = e.target.value }} + > + { this.proxyport = e.target.value }} + > +
    +
    + { this.username = e.target.value }} + > + { this.version = e.target.value }} + > +
    +

    Leave blank and it will be chosen automatically

    +
    + +
    + + displayScreen(this, document.getElementById('title-screen'))}> +
    + ` + } + + onConnectPress () { + window.localStorage.setItem('username', this.username) + window.localStorage.setItem('password', this.password) + window.localStorage.setItem('server', this.server) + window.localStorage.setItem('serverport', this.serverport) + window.localStorage.setItem('proxy', this.proxy) + window.localStorage.setItem('proxyport', this.proxyport) + window.localStorage.setItem('version', this.version) + + this.dispatchEvent(new window.CustomEvent('connect', { + detail: { + server: `${this.server}:${this.serverport}`, + proxy: `${this.proxy}${this.proxy !== '' ? `:${this.proxyport}` : ''}`, + username: this.username, + password: this.password, + botVersion: this.version + } + })) + } +} + +window.customElements.define('pmui-playscreen', PlayScreen) diff --git a/lib/menus/title_screen.js b/lib/menus/title_screen.js new file mode 100644 index 0000000000000000000000000000000000000000..820910b8a2e73507db1ee7a3aaa557571d2677ec --- /dev/null +++ b/lib/menus/title_screen.js @@ -0,0 +1,124 @@ +const { openURL, displayScreen } = require('./components/common') +const { LitElement, html, css } = require('lit') + +class TitleScreen extends LitElement { + static get styles () { + return css` + .minecraft { + position: absolute; + top: 30px; + left: calc(50% - 137px); + } + + .minecraft .minec { + display: block; + position: absolute; + top: 0; + left: 0; + background-image: url('textures/1.17.1/gui/title/minecraft.png'); + background-size: 256px; + width: 155px; + height: 44px; + } + + .minecraft .raft { + display: block; + position: absolute; + top: 0; + left: 155px; + background-image: url('textures/1.17.1/gui/title/minecraft.png'); + background-size: 256px; + width: 155px; + height: 44px; + background-position-y: -45px; + } + + .minecraft .edition { + display: block; + position: absolute; + top: 37px; + left: calc(88px + 5px); + background-image: url('extra-textures/edition.png'); + background-size: 128px; + width: 88px; + height: 14px; + } + + .splash { + position: absolute; + top: 32px; + left: 227px; + color: #ff0; + transform: translate(-50%, -50%) rotateZ(-20deg) scale(1); + width: max-content; + text-shadow: 1px 1px #220; + font-size: 10px; + animation: splashAnim 400ms infinite alternate linear; + } + + @keyframes splashAnim { + to { + transform: translate(-50%, -50%) rotateZ(-20deg) scale(1.07); + } + } + + .menu { + display: flex; + flex-direction: column; + gap: 4px 0; + position: absolute; + top: calc(25% + 48px); + left: 50%; + width: 200px; + transform: translate(-50%); + } + + .menu-row { + display: flex; + flex-direction: row; + gap: 0 4px; + width: 100%; + } + + .bottom-info { + display: flex; + flex-direction: row; + justify-content: space-between; + position: absolute; + bottom: -1px; + left: 1px; + width: calc(100% - 2px); + color: white; + text-shadow: 1px 1px #222; + font-size: 10px; + } + ` + } + + render () { + return html` +
    +
    +
    +
    + Prismarine is a beautiful block +
    + + + +
    + Prismarine Web Client + A Minecraft client in the browser! +
    + ` + } +} + +window.customElements.define('pmui-titlescreen', TitleScreen) diff --git a/lib/perf_hooks_replacement.js b/lib/perf_hooks_replacement.js new file mode 100644 index 0000000000000000000000000000000000000000..8539602ab41a1727f9450eff3fc244c29ee83acb --- /dev/null +++ b/lib/perf_hooks_replacement.js @@ -0,0 +1 @@ +module.exports.performance = window.performance diff --git a/lib/vr.js b/lib/vr.js new file mode 100644 index 0000000000000000000000000000000000000000..7ada13acd8db30f768d116d64e14fd4fc3aff342 --- /dev/null +++ b/lib/vr.js @@ -0,0 +1,84 @@ +/* global THREE */ + +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 TWEEN = require('@tweenjs/tween.js') + +function initVR (bot, renderer, viewer) { + if (!('xr' in navigator)) return + if (!navigator.userAgent.includes('OculusBrowser')) return + + // VR + document.body.appendChild(VRButton.createButton(renderer)) + renderer.xr.enabled = true + + // hack for vr camera + const user = new THREE.Group() + user.add(viewer.camera) + viewer.scene.add(user) + const controllerModelFactory = new XRControllerModelFactory(new GLTFLoader()) + const controller1 = renderer.xr.getControllerGrip(0) + const controller2 = renderer.xr.getControllerGrip(1) + let hand1 = controllerModelFactory.createControllerModel(controller1) + controller1.addEventListener('connected', (event) => { + hand1.xrInputSource = event.data + user.add(controller1) + }) + controller1.add(hand1) + let hand2 = controllerModelFactory.createControllerModel(controller2) + controller2.addEventListener('connected', (event) => { + hand2.xrInputSource = event.data + user.add(controller2) + }) + controller2.add(hand2) + + viewer.setFirstPersonCamera = function (pos, yaw, pitch) { + if (pos) new TWEEN.Tween(user.position).to({ x: pos.x, y: pos.y, z: pos.z }, 50).start() + user.rotation.set(pitch, yaw, 0, 'ZYX') + } + + let rotSnapReset = true + let yawOffset = 0 + renderer.setAnimationLoop(() => { + if (hand1.xrInputSource && hand2.xrInputSource) { + hand1.xAxis = hand1.xrInputSource.gamepad.axes[2] + hand1.yAxis = hand1.xrInputSource.gamepad.axes[3] + hand2.xAxis = hand2.xrInputSource.gamepad.axes[2] + hand2.yAxis = hand2.xrInputSource.gamepad.axes[3] + if (hand1.xrInputSource.handedness === 'right') { + const tmp = hand2 + hand2 = hand1 + hand1 = tmp + } + } + + if (rotSnapReset) { + if (Math.abs(hand1.xAxis) > 0.8) { + yawOffset -= Math.PI / 4 * Math.sign(hand1.xAxis) + rotSnapReset = false + } + } else if (Math.abs(hand1.xAxis) < 0.1) { + rotSnapReset = true + } + + viewer.setFirstPersonCamera(null, yawOffset, 0) + + const xrCamera = renderer.xr.getCamera(viewer.camera) + const d = xrCamera.getWorldDirection() + bot.entity.yaw = Math.atan2(-d.x, -d.z) + bot.entity.pitch = Math.asin(d.y) + + bot.physics.stepHeight = 1 + bot.setControlState('forward', hand2.yAxis < -0.5) + bot.setControlState('back', hand2.yAxis > 0.5) + bot.setControlState('right', hand2.xAxis < -0.5) + bot.setControlState('left', hand2.xAxis > 0.5) + + TWEEN.update() + viewer.update() + renderer.render(viewer.scene, viewer.camera) + }) +} + +module.exports = { initVR } diff --git a/package.json b/package.json new file mode 100644 index 0000000000000000000000000000000000000000..027c561eb343cb634b1fdf36d228227ddc7c032e --- /dev/null +++ b/package.json @@ -0,0 +1,79 @@ +{ + "name": "prismarine-web-client", + "version": "1.6.0", + "description": "A minecraft client running in a browser", + "main": "index.js", + "scripts": { + "postinstall": "node scripts/patchPackages.js", + "build": "webpack --config webpack.prod.js", + "build-dev": "webpack --config webpack.dev.js", + "start": "node --max-old-space-size=8192 server.js 8080 dev", + "prod-start": "node server.js", + "build-dev-start": "npm run build-dev && npm run prod-start", + "build-start": "npm run build && npm run prod-start", + "prepublishOnly": "npm run build", + "lint": "standard", + "fix": "standard --fix", + "test": "npm run lint && mocha" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/PrismarineJS/prismarine-web-client.git" + }, + "keywords": [ + "prismarine", + "web", + "client" + ], + "author": "PrismarineJS", + "license": "MIT", + "bin": { + "prismarine-web-client": "./server.js" + }, + "bugs": { + "url": "https://github.com/PrismarineJS/prismarine-web-client/issues" + }, + "homepage": "https://github.com/PrismarineJS/prismarine-web-client#readme", + "dependencies": { + "compression": "^1.7.4", + "express": "^4.17.1", + "ismobilejs": "^1.1.1", + "lit": "^2.0.2", + "net-browserify": "PrismarineJS/net-browserify", + "querystring": "^0.2.1", + "url": "^0.11.0" + }, + "devDependencies": { + "assert": "^2.0.0", + "browserify-zlib": "^0.2.0", + "buffer": "^6.0.3", + "clean-webpack-plugin": "^4.0.0", + "constants-browserify": "^1.0.0", + "copy-webpack-plugin": "^11.0.0", + "crypto-browserify": "^3.12.0", + "events": "^3.2.0", + "html-webpack-plugin": "^5.3.1", + "http-browserify": "^1.7.0", + "http-server": "^14.1.0", + "https-browserify": "^1.0.0", + "lodash-webpack-plugin": "^0.11.6", + "memfs": "^3.2.0", + "mineflayer": "^4.17.0", + "mineflayer-pathfinder": "^2.0.0", + "mocha": "^10.0.0", + "os-browserify": "^0.3.0", + "path-browserify": "^1.0.1", + "prismarine-viewer": "^1.28.0", + "process": "PrismarineJS/node-process", + "standard": "^17.0.0", + "stream-browserify": "^3.0.0", + "three": "0.127.0", + "timers-browserify": "^2.0.12", + "webpack": "^5.11.0", + "webpack-cli": "^5.0.0", + "webpack-dev-middleware": "^6.0.0", + "webpack-dev-server": "^4.0.0", + "webpack-merge": "^5.7.3", + "workbox-webpack-plugin": "^6.1.2" + } +} diff --git a/screenshot.png b/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..9f661d51190ce16105438e9aa0980fe3952157f2 --- /dev/null +++ b/screenshot.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:05abe3a1da01114bc46c0a93f0f1f8ad7f99904ab21ea4f1bff5fcc7a335afce +size 2710634 diff --git a/scripts/patchPackages.js b/scripts/patchPackages.js new file mode 100644 index 0000000000000000000000000000000000000000..f6dd391d451b4ced1ec85058eda1865a2a1916e5 --- /dev/null +++ b/scripts/patchPackages.js @@ -0,0 +1,25 @@ +// @ts-check +const path = require('path') +const dataPath = path.join(require.resolve('minecraft-data'), '../data.js') + +const fs = require('fs') + +const lines = fs.readFileSync(dataPath, 'utf8').split('\n') +if (lines[0] === '//patched') { + console.log('Already patched') + process.exit(0) +} + +function removeLinesBetween (start, end) { + const startIndex = lines.findIndex(line => line === start) + if (startIndex === -1) return + const endIndex = startIndex + lines.slice(startIndex).findIndex(line => line === end) + // insert block comments + lines.splice(startIndex, 0, '/*') + lines.splice(endIndex + 2, 0, '*/') +} + +removeLinesBetween(" 'bedrock': {", ' }') + +lines.unshift('//patched') +fs.writeFileSync(dataPath, lines.join('\n'), 'utf8') diff --git a/server.js b/server.js new file mode 100644 index 0000000000000000000000000000000000000000..f78b9fec9541c32e5a0328f53d4aa96099006018 --- /dev/null +++ b/server.js @@ -0,0 +1,34 @@ +#!/usr/bin/env node + +const express = require('express') +const netApi = require('net-browserify') +const compression = require('compression') +const path = require('path') + +// Create our app +const app = express() + +app.get('/config.json', (_, res) => res.sendFile(path.join(__dirname, 'config.json'))) + +app.use(compression()) +app.use(netApi({ allowOrigin: '*' })) +if (process.argv[3] === 'dev') { + // https://webpack.js.org/guides/development/#using-webpack-dev-middleware + const webpackDevMiddleware = require('webpack-dev-middleware') + const config = require('./webpack.dev.js') + const webpack = require('webpack') + const compiler = webpack(config) + + app.use( + webpackDevMiddleware(compiler, { + publicPath: config.output.publicPath + }) + ) +} else { + app.use(express.static(path.join(__dirname, './public'))) +} + +// Start the server +const server = app.listen(process.argv[2] === undefined ? 8080 : process.argv[2], function () { + console.log('Server listening on port ' + server.address().port) +}) diff --git a/styles.css b/styles.css new file mode 100644 index 0000000000000000000000000000000000000000..b9bb8431772a4d8a283c92b27b5b0fe4009061bd --- /dev/null +++ b/styles.css @@ -0,0 +1,120 @@ +:root { + --guiScaleFactor: 3; + --guiScale: 3; + --chatWidth: 320px; + --chatHeight: 180px; + --chatScale: 1; +} + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +html { + height: 100vh; + overflow: hidden; +} + +.dirt-bg { + position: absolute; + top: 0; + left: 0; + background: url('textures/1.17.1/gui/options_background.png'), rgba(0, 0, 0, 0.7); + background-size: 16px; + background-repeat: repeat; + width: 100%; + height: 100%; + transform-origin: top left; + transform: scale(2); + background-blend-mode: overlay; +} + +@font-face { + font-family: minecraft; + src: url(minecraftia.woff); +} + +@font-face { + font-family: mojangles; + src: url(mojangles.ttf); +} + +body { + overflow: hidden; + position: relative; + margin:0; + padding:0; + height: 100vh; + font-family: sans-serif; + background: #333; + /* background: linear-gradient(#141e30, #243b55); */ + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +canvas { + position: absolute; + top: 0; + left: 0; + height: 100%; + width: 100%; + font-size: 0; + margin: 0; + padding: 0; +} + +#ui-root { + position: absolute; + top: 0; + left: 0; + transform-origin: top left; + transform: scale(var(--guiScale)); + width: calc(100% / var(--guiScale)); + height: calc(100% / var(--guiScale)); + z-index: 10; + image-rendering: optimizeSpeed; + image-rendering: -moz-crisp-edges; + image-rendering: -webkit-optimize-contrast; + image-rendering: -o-crisp-edges; + image-rendering: pixelated; + -ms-interpolation-mode: nearest-neighbor; + font-family: minecraft, mojangles, monospace; +} + +@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: 561px) { + #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); + width: calc(100% / 1); + height: calc(100% / 1); + } +} \ No newline at end of file diff --git a/test/basic.test.js b/test/basic.test.js new file mode 100644 index 0000000000000000000000000000000000000000..09e92f488c7889aa689bd6f0c85ddb757ab2a4e3 --- /dev/null +++ b/test/basic.test.js @@ -0,0 +1,7 @@ +/* eslint-env mocha */ + +describe('basic', () => { + it('test', () => { + + }) +}) diff --git a/webpack.common.js b/webpack.common.js new file mode 100644 index 0000000000000000000000000000000000000000..16fa228c4104dc04c822e897222a3ae24b5ff88d --- /dev/null +++ b/webpack.common.js @@ -0,0 +1,86 @@ +const webpack = require('webpack') +const path = require('path') +const CopyPlugin = require('copy-webpack-plugin') +const HtmlWebpackPlugin = require('html-webpack-plugin') +const WorkboxPlugin = require('workbox-webpack-plugin') +// https://webpack.js.org/guides/production/ + +const config = { + entry: path.resolve(__dirname, './index.js'), + output: { + path: path.resolve(__dirname, './public'), + filename: './index.js', + publicPath: './' + }, + resolve: { + alias: { + 'minecraft-protocol': path.resolve( + __dirname, + 'node_modules/minecraft-protocol/src/index.js' + ), // Hack to allow creating the client in a browser + express: false, + net: 'net-browserify', + fs: false, + jose: false, + '@azure/msal-node': false + }, + fallback: { + jose: false, + zlib: require.resolve('browserify-zlib'), + stream: require.resolve('stream-browserify'), + buffer: require.resolve('buffer/'), + events: require.resolve('events/'), + assert: require.resolve('assert/'), + crypto: require.resolve('crypto-browserify'), + path: require.resolve('path-browserify'), + constants: require.resolve('constants-browserify'), + os: require.resolve('os-browserify/browser'), + http: require.resolve('http-browserify'), + https: require.resolve('https-browserify'), + timers: require.resolve('timers-browserify'), + // fs: require.resolve("fs-memory/singleton"), + child_process: false, + tls: false, + perf_hooks: path.resolve(__dirname, 'lib/perf_hooks_replacement.js'), + dns: path.resolve(__dirname, 'lib/dns.js') + } + }, + plugins: [ + new HtmlWebpackPlugin({ + template: 'index.html', + hash: true, + minify: false + }), + // fix "process is not defined" error: + new webpack.ProvidePlugin({ + process: 'process/browser' + }), + new webpack.ProvidePlugin({ + Buffer: ['buffer', 'Buffer'] + }), + new webpack.NormalModuleReplacementPlugin( + /prismarine-viewer[/|\\]viewer[/|\\]lib[/|\\]utils/, + './utils.web.js' + ), + new WorkboxPlugin.GenerateSW({ + // these options encourage the ServiceWorkers to get in there fast + // and not allow any straggling "old" SWs to hang around + clientsClaim: true, + skipWaiting: true, + include: ['index.html', 'manifest.json'] // not caching a lot as anyway this works only online + }), + new CopyPlugin({ + patterns: [ + { from: path.join(__dirname, '/styles.css'), to: './styles.css' }, + { from: path.join(__dirname, '/node_modules/prismarine-viewer/public/blocksStates/'), to: './blocksStates/' }, + { from: path.join(__dirname, '/node_modules/prismarine-viewer/public/textures/'), to: './textures/' }, + { from: path.join(__dirname, '/node_modules/prismarine-viewer/public/worker.js'), to: './' }, + { from: path.join(__dirname, 'assets/'), to: './' }, + { from: path.join(__dirname, 'extra-textures/'), to: './extra-textures/' }, + { from: path.join(__dirname, 'config.json'), to: './config.json' } + ] + }) + ] +} + +module.exports = config diff --git a/webpack.dev.js b/webpack.dev.js new file mode 100644 index 0000000000000000000000000000000000000000..90628721e9482c86d2317124c5d7293a186d3838 --- /dev/null +++ b/webpack.dev.js @@ -0,0 +1,16 @@ +const { merge } = require('webpack-merge') +const common = require('./webpack.common.js') +const path = require('path') + +module.exports = merge(common, { + mode: 'development', + devtool: 'inline-source-map', + cache: true, + devServer: { + contentBase: path.resolve(__dirname, './public'), + compress: true, + inline: true, + // open: true, + hot: true + } +}) diff --git a/webpack.prod.js b/webpack.prod.js new file mode 100644 index 0000000000000000000000000000000000000000..281540b25437d2562e70d7f1b10d082ec160332f --- /dev/null +++ b/webpack.prod.js @@ -0,0 +1,15 @@ +const { merge } = require('webpack-merge') +const common = require('./webpack.common.js') + +const LodashModuleReplacementPlugin = require('lodash-webpack-plugin') +const { CleanWebpackPlugin } = require('clean-webpack-plugin') +const webpack = require('webpack') + +module.exports = merge(common, { + mode: 'production', + plugins: [ + new CleanWebpackPlugin(), + new webpack.optimize.ModuleConcatenationPlugin(), + new LodashModuleReplacementPlugin() + ] +})